• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

barseghyanartur / faker-file / 6103286169

06 Sep 2023 11:03PM UTC coverage: 95.96%. First build
6103286169

push

github

barseghyanartur
More

16 of 30 new or added lines in 2 files covered. (53.33%)

2850 of 2970 relevant lines covered (95.96%)

4.71 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.68
/src/faker_file/providers/pdf_file/generators/pil_generator.py
1
import logging
4✔
2
import textwrap
4✔
3
from io import BytesIO
4✔
4
from pathlib import Path
4✔
5
from typing import Any, Dict, List, Type, Union
4✔
6

7
from faker import Faker
4✔
8
from faker.generator import Generator
4✔
9
from faker.providers.python import Provider
4✔
10
from PIL import Image, ImageDraw, ImageFont
4✔
11

12
from ....base import DynamicTemplate
4✔
13
from ....constants import DEFAULT_FILE_ENCODING
4✔
14
from ...base.pdf_generator import BasePdfGenerator
4✔
15

16
__author__ = "Artur Barseghyan <artur.barseghyan@gmail.com>"
4✔
17
__copyright__ = "2022-2023 Artur Barseghyan"
4✔
18
__license__ = "MIT"
4✔
19
__all__ = ("PilPdfGenerator",)
4✔
20

21
LOGGER = logging.getLogger(__name__)
4✔
22

23

24
class PilPdfGenerator(BasePdfGenerator):
4✔
25
    """Pillow PDF generator.
26

27
    Usage example:
28

29
    .. code-block:: python
30

31
        from faker import Faker
32
        from faker_file.providers.pdf_file import PdfFileProvider
33
        from faker_file.providers.pdf_file.generators import pil_generator
34

35
        FAKER = Faker()
36
        FAKER.add_provider(PdfFileProvider)
37

38
        file = FAKER.pdf_file(
39
            pdf_generator_cls=pil_generator.PilPdfGenerator
40
        )
41

42
    With options:
43

44
    .. code-bloock:: python
45

46
        file = FAKER.pdf_file(
47
            pdf_generator_cls=PilPdfGenerator,
48
            pdf_generator_kwargs={
49
                "encoding": "utf8",
50
                "font_size": 14,
51
                "page_width": 800,
52
                "page_height": 1200,
53
                "line_height": 16,
54
                "spacing": 5,
55
            },
56
            wrap_chars_after=100,
57
        )
58
    """
59

60
    encoding: str = DEFAULT_FILE_ENCODING
4✔
61
    font: str = str(Path("Pillow") / "Tests" / "fonts" / "DejaVuSans.ttf")
4✔
62
    font_size: int = 12
4✔
63
    page_width: int = 794  # A4 size at 96 DPI
4✔
64
    page_height: int = 1123  # A4 size at 96 DPI
4✔
65
    line_height: int = 14
4✔
66
    spacing: int = 6  # Letter spacing
4✔
67

68
    def __init__(self, **kwargs):
4✔
69
        super().__init__(**kwargs)
4✔
70
        self.pages = []
4✔
71
        self.img = None
4✔
72
        self.draw = None
4✔
73

74
    @classmethod
4✔
75
    def find_max_fit_for_multi_line_text(
4✔
76
        cls: Type["PilPdfGenerator"],
77
        draw: ImageDraw,
78
        lines: List[str],
79
        font: ImageFont,
80
        max_width: int,
81
    ):
82
        # Find the longest line
83
        longest_line = str(max(lines, key=len))
4✔
84
        return cls.find_max_fit_for_single_line_text(
4✔
85
            draw, longest_line, font, max_width
86
        )
87

88
    @classmethod
4✔
89
    def find_max_fit_for_single_line_text(
4✔
90
        cls: Type["PilPdfGenerator"],
91
        draw: "ImageDraw",
92
        text: str,
93
        font: ImageFont,
94
        max_width: int,
95
    ) -> int:
96
        low, high = 0, len(text)
4✔
97
        while low < high:
4✔
98
            mid = (high + low) // 2
4✔
99
            text_width, _ = draw.textsize(text[:mid], font=font)
4✔
100

101
            if text_width > max_width:
4✔
102
                high = mid
4✔
103
            else:
104
                low = mid + 1
4✔
105

106
        return low - 1
4✔
107

108
    def handle_kwargs(self: "PilPdfGenerator", **kwargs) -> None:
4✔
109
        """Handle kwargs."""
110
        if "encoding" in kwargs:
4✔
111
            self.encoding = kwargs["encoding"]
4✔
112
        if "font_size" in kwargs:
4✔
113
            self.font_size = kwargs["font_size"]
4✔
114
        if "page_width" in kwargs:
4✔
115
            self.page_width = kwargs["page_width"]
4✔
116
        if "page_height" in kwargs:
4✔
117
            self.page_height = kwargs["page_height"]
4✔
118
        if "line_height" in kwargs:
4✔
119
            self.line_height = kwargs["line_height"]
4✔
120
        if "spacing" in kwargs:
4✔
121
            self.spacing = kwargs["spacing"]
4✔
122

123
    def create_image_instance(self) -> Image:
4✔
124
        return Image.new(
4✔
125
            "RGB",
126
            (self.page_width, self.page_height),
127
            (255, 255, 255),
128
        )
129

130
    def start_new_page(self):
4✔
131
        self.img = self.create_image_instance()
4✔
132
        self.draw = ImageDraw.Draw(self.img)
4✔
133

134
    def save_and_start_new_page(self):
4✔
135
        self.pages.append(self.img.copy())
4✔
136
        self.start_new_page()
4✔
137

138
    def generate(
4✔
139
        self: "PilPdfGenerator",
140
        content: Union[str, DynamicTemplate],
141
        data: Dict[str, Any],
142
        provider: Union[Faker, Generator, Provider],
143
        **kwargs,
144
    ) -> bytes:
145
        """Generate PDF."""
146
        position = (0, 0)
4✔
147
        if isinstance(content, DynamicTemplate):
4✔
NEW
148
            self.start_new_page()
×
149
            for counter, (ct_modifier, ct_modifier_kwargs) in enumerate(
×
150
                content.content_modifiers
151
            ):
152
                # self.img = self.create_image_instance()
153
                # self.draw = ImageDraw.Draw(self.img)
154
                # position = (0, 0)
155
                # draw.image = img
156

157
                # if "position" not in ct_modifier_kwargs:
NEW
158
                ct_modifier_kwargs["position"] = position
×
159
                LOGGER.error(f"ct_modifier_kwargs: {ct_modifier_kwargs}")
×
160
                add_page, position = ct_modifier(
×
161
                    provider,
162
                    self,
163
                    self.draw,
164
                    data,
165
                    counter,
166
                    **ct_modifier_kwargs,
167
                )
168

169
                # if add_page:
170
                #     self.pages.append(self.img.copy())  # Add as a new page
NEW
171
            self.pages.append(self.img.copy())  # Add as a new page
×
172
        else:
173
            self.img = self.create_image_instance()
4✔
174
            self.draw = ImageDraw.Draw(self.img)
4✔
175
            font = ImageFont.truetype(self.font, self.font_size)
4✔
176

177
            # The `content_specs` is a dictionary that holds two keys:
178
            # `max_nb_chars` and `wrap_chars_after`. Those are the same values
179
            # passed to the `PdfFileProvider`.
180
            content_specs = kwargs.get("content_specs", {})
4✔
181
            lines = content.split("\n")
4✔
182
            line_max_num_chars = self.find_max_fit_for_multi_line_text(
4✔
183
                self.draw,
184
                lines,
185
                font,
186
                self.page_width,
187
            )
188
            wrap_chars_after = content_specs.get("wrap_chars_after")
4✔
189
            if (
4✔
190
                not wrap_chars_after
191
                or wrap_chars_after
192
                and (wrap_chars_after > line_max_num_chars)
193
            ):
194
                lines = textwrap.wrap(content, line_max_num_chars)
4✔
195

196
            y_text = 0
4✔
197
            for counter, line in enumerate(lines):
4✔
198
                text_width, text_height = self.draw.textsize(
4✔
199
                    line, font=font, spacing=6
200
                )
201
                # if counter % max_lines_per_page == 0:
202
                if y_text + text_height > self.page_height:
4✔
203
                    self.save_and_start_new_page()
4✔
204
                    y_text = 0
4✔
205

206
                self.draw.text(
4✔
207
                    (0, y_text),
208
                    line,
209
                    fill=(0, 0, 0),
210
                    spacing=self.spacing,
211
                    font=font,
212
                )
213
                y_text += text_height + self.line_height
4✔
214

215
            self.pages.append(self.img.copy())  # Add as a new page
4✔
216

217
        buffer = BytesIO()
4✔
218
        # Save as multi-page PDF
219
        self.pages[0].save(
4✔
220
            buffer, save_all=True, append_images=self.pages[1:], format="PDF"
221
        )
222

223
        return buffer.getvalue()
4✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc