• 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

0.0
/src/faker_file/contrib/pdf_file/pil_snippets.py
1
"""
2
.. code-block:: python
3

4
    from faker import Faker
5
    from faker_file.base import DynamicTemplate
6
    from faker_file.contrib.pdf_file.pil_snippets import *
7
    from faker_file.providers.pdf_file import PdfFileProvider
8
    from faker_file.providers.pdf_file.generators.pil_generator import (
9
        PilPdfGenerator
10
    )
11

12
    FAKER = Faker()
13
    FAKER.add_provider(PdfFileProvider)
14

15
    file = FAKER.pdf_file(
16
        pdf_generator_cls=PilPdfGenerator,
17
        content=DynamicTemplate(
18
            [
19
                (add_h1_heading, {}),
20
                (add_paragraph, {"max_nb_chars": 500}),
21
                (add_paragraph, {"max_nb_chars": 500}),
22
                (add_paragraph, {"max_nb_chars": 500}),
23
                (add_paragraph, {"max_nb_chars": 500}),
24
            ]
25
        )
26
    )
27

28
    file = FAKER.pdf_file(
29
        pdf_generator_cls=PilPdfGenerator,
30
        content=DynamicTemplate(
31
            [
32
                (add_h1_heading, {}),
33
                (add_paragraph, {}),
34
                (add_picture, {}),
35
                (add_paragraph, {}),
36
                (add_picture, {}),
37
                (add_paragraph, {}),
38
                (add_picture, {}),
39
                (add_paragraph, {}),
40
            ]
41
        )
42
    )
43

44
    file = FAKER.pdf_file(
45
        pdf_generator_cls=PilPdfGenerator,
46
        content=DynamicTemplate(
47
            [
48
                (add_h1_heading, {}),
49
                (add_picture, {}),
50
                (add_paragraph, {"max_nb_chars": 500}),
51
                (add_picture, {}),
52
                (add_paragraph, {"max_nb_chars": 500}),
53
                (add_picture, {}),
54
                (add_paragraph, {"max_nb_chars": 500}),
55
                (add_picture, {}),
56
                (add_paragraph, {"max_nb_chars": 500}),
57
            ]
58
        )
59
    )
60
"""
61
import logging
×
62
import textwrap
×
63
from io import BytesIO
×
64
from typing import Tuple
×
65

66
from PIL import Image, ImageDraw, ImageFont
×
67

68
__author__ = "Artur Barseghyan <artur.barseghyan@gmail.com>"
×
69
__copyright__ = "2022-2023 Artur Barseghyan"
×
70
__license__ = "MIT"
×
71
__all__ = (
×
72
    "add_h1_heading",
73
    "add_h2_heading",
74
    "add_h3_heading",
75
    "add_h4_heading",
76
    "add_h5_heading",
77
    "add_h6_heading",
78
    "add_heading",
79
    "add_page_break",
80
    "add_paragraph",
81
    "add_picture",
82
    # "add_table",
83
)
84

85
LOGGER = logging.getLogger(__name__)
×
86

87

88
def add_picture(
×
89
    provider,
90
    generator,
91
    document: ImageDraw,
92
    data,
93
    counter,
94
    **kwargs,
95
) -> Tuple[bool, Tuple[int, int]]:
96
    """Callable responsible for picture generation using PIL."""
97
    image_bytes = kwargs.get(
×
98
        "image_bytes", provider.generator.image()
99
    )  # Assuming image() returns bytes
100
    # Create a BytesIO object and load the image data
101
    with BytesIO(image_bytes) as input_stream:
×
102
        pil_image = Image.open(input_stream)
×
103

104
        # Resize the image
105
        new_width = 200
×
106
        new_height = 200
×
107
        pil_image = pil_image.resize((new_width, new_height))
×
108

109
        # Create a BytesIO object outside the 'with' statement
110
        output_stream = BytesIO()
×
111
        pil_image.save(output_stream, format="JPEG")
×
112
        output_stream.seek(0)  # Move to the start of the stream
×
113

114
    # X, Y coordinates where the text will be placed
115
    position = kwargs.get("position", (0, 0))
×
116

117
    # Calculate the remaining space on the current page
118
    remaining_space = generator.page_height - position[1]
×
119

120
    # Create a PIL Image object from bytes
121
    image_to_paste = Image.open(output_stream)
×
122

123
    # Check if the image will fit on the current page
124
    if remaining_space < image_to_paste.height:
×
125
        # Image won't fit; add the current page to the list and create a new one
126
        # img = document._image
127
        # generator.pages.append(img.copy())
128
        # img = generator.create_image_instance()
129
        # document = ImageDraw.Draw(img)
NEW
130
        generator.save_and_start_new_page()
×
131

132
        # Reset position to start of new page
133
        position = (0, 0)
×
134

135
    # Paste the image into the document
136
    image_position = (
×
137
        position[0],
138
        position[1],
139
        position[0] + image_to_paste.width,
140
        position[1] + image_to_paste.height,
141
    )
142

143
    # image = document._image
144
    # Ensure that the document and the image to paste have the same mode
NEW
145
    if generator.img.mode != image_to_paste.mode:
×
NEW
146
        image_to_paste = image_to_paste.convert(generator.img.mode)
×
147

148
    # Create a mask if the image has an alpha channel
149
    mask = None
×
150
    if "A" in image_to_paste.getbands():
×
151
        mask = image_to_paste.split()[3]
×
152

NEW
153
    generator.img.paste(image_to_paste, position, mask)
×
154

155
    LOGGER.error(f"position: {image_position}")
×
156
    # If you want to keep track of the last position to place
157
    # another element, you can.
158
    # last_position = (position[0] + image.width, position[1] + image.height)
NEW
159
    last_position = (0, position[1] + generator.img.height)
×
160

161
    # Meta-data (optional)
162
    data.setdefault("content_modifiers", {})
×
163
    data["content_modifiers"].setdefault("add_picture", {})
×
164
    data["content_modifiers"]["add_picture"].setdefault(counter, [])
×
165
    data["content_modifiers"]["add_picture"][counter].append("Image added")
×
166

167
    return False, last_position
×
168

169

170
def add_paragraph(
×
171
    provider,
172
    generator,
173
    document: ImageDraw,  # ImageDraw.Draw object for drawing
174
    data,
175
    counter,
176
    **kwargs,
177
) -> Tuple[bool, Tuple[int, int]]:
178
    """Callable responsible for paragraph generation using PIL."""
179
    content = kwargs.get("content", None)
×
180
    max_nb_chars = kwargs.get("max_nb_chars", 5_000)
×
181
    wrap_chars_after = kwargs.get("wrap_chars_after", None)
×
182
    # X, Y coordinates where the text will be placed
183
    position = kwargs.get("position", (0, 0))
×
184
    content_specs = kwargs.get("content_specs", {})
×
185
    format_func = kwargs.get(
×
186
        "format_func", None
187
    )  # Assuming DEFAULT_FORMAT_FUNC is somewhere defined
188

189
    _content = provider._generate_text_content(
×
190
        max_nb_chars=max_nb_chars,
191
        wrap_chars_after=wrap_chars_after,
192
        content=content,
193
        format_func=format_func,
194
    )
195
    font = ImageFont.truetype(generator.font, generator.font_size)
×
NEW
196
    lines = _content.split("\n")
×
197
    line_max_num_chars = generator.find_max_fit_for_multi_line_text(
×
198
        document,
199
        lines,
200
        font,
201
        generator.page_width,
202
    )
203
    wrap_chars_after = content_specs.get("wrap_chars_after")
×
204
    if (
×
205
        not wrap_chars_after
206
        or wrap_chars_after
207
        and (wrap_chars_after > line_max_num_chars)
208
    ):
209
        lines = textwrap.wrap(_content, line_max_num_chars)
×
210

211
    # Load a truetype or opentype font file, and create a font object.
212
    font = ImageFont.truetype(generator.font, generator.font_size)
×
213

214
    y_text = position[1]
×
215
    LOGGER.error(f"position: {position}")
×
216
    for counter, line in enumerate(lines):
×
217
        text_width, text_height = document.textsize(
×
218
            line, font=font, spacing=generator.spacing
219
        )
220
        if y_text + text_height > generator.page_height:
×
221
            # img = document._image
222
            # generator.pages.append(img.copy())
223
            # img = generator.create_image_instance()
224
            # document = ImageDraw.Draw(img)
NEW
225
            generator.save_and_start_new_page()
×
226
            y_text = 0
×
227

NEW
228
        generator.draw.text(
×
229
            (position[0], y_text),
230
            line,
231
            fill=(0, 0, 0),
232
            spacing=generator.spacing,
233
            font=font,
234
        )
235
        # Move down for next line
236
        y_text += text_height + generator.line_height
×
237

238
    # If you want to keep track of the last position to place another
239
    # element, you can.
240
    # last_position = (position[0], y_text)
241
    last_position = (0, y_text)
×
242

243
    # Add meta-data, assuming data is a dictionary for tracking
244
    data.setdefault("content_modifiers", {})
×
245
    data["content_modifiers"].setdefault("add_paragraph", {})
×
246
    data["content_modifiers"]["add_paragraph"].setdefault(counter, [])
×
247
    data["content_modifiers"]["add_paragraph"][counter].append(_content)
×
248
    data.setdefault("content", "")
×
249
    data["content"] += "\r\n" + _content
×
250

251
    return False, last_position
×
252

253

254
def add_page_break(
×
255
    provider,
256
    generator,
257
    document: ImageDraw,  # ImageDraw.Draw object for drawing
258
    data,
259
    counter,
260
    **kwargs,
261
) -> Tuple[bool, Tuple[int, int]]:
262
    """Callable responsible for paragraph generation using PIL."""
263
    # position = kwargs.get("position", (0, 0))
264
    # img = document._image
265
    # generator.pages.append(img.copy())
266
    # img = generator.create_image_instance()
267
    # document = ImageDraw.Draw(img)
NEW
268
    generator.save_and_start_new_page()
×
269

270
    # If you want to keep track of the last position to place another
271
    # element, you can.
272
    # last_position = (position[0], 0)
273
    last_position = (0, 0)
×
274

275
    return False, last_position
×
276

277

278
def get_heading_font_size(base_size: int, heading_level: int) -> int:
×
279
    return base_size * (8 - heading_level) // 2
×
280

281

282
def add_heading(
×
283
    provider,
284
    generator,
285
    document: "ImageDraw",  # ImageDraw.Draw object for drawing
286
    data,
287
    counter,
288
    **kwargs,
289
) -> Tuple[bool, Tuple[int, int]]:
290
    """Callable responsible for H1 heading generation using PIL."""
291
    content = kwargs.get("content", None)
×
292
    max_nb_chars = kwargs.get("max_nb_chars", 30)
×
293
    wrap_chars_after = kwargs.get("wrap_chars_after", None)
×
294
    format_func = kwargs.get("format_func", None)
×
295
    # X, Y coordinates where the text will be placed
296
    position = kwargs.get("position", (0, 0))
×
297
    level = kwargs.get("level", 1)
×
298
    if level < 1 or level > 6:
×
299
        level = 1
×
300

301
    font_size = get_heading_font_size(generator.font_size, level)
×
302

303
    _content = provider._generate_text_content(
×
304
        max_nb_chars=max_nb_chars,
305
        wrap_chars_after=wrap_chars_after,
306
        content=content,
307
        format_func=format_func,
308
    )
309

310
    # Here, you'll specify a different font size for heading
311
    font = ImageFont.truetype(generator.font, font_size)
×
312

313
    y = position[1]
×
NEW
314
    generator.draw.text(
×
315
        (position[0], y),
316
        _content,
317
        fill=(0, 0, 0),
318
        font=font,
319
    )
320

NEW
321
    text_width, text_height = generator.draw.textsize(_content, font=font)
×
322
    y += text_height
×
323

324
    # If you want to keep track of the last position to place another
325
    # element, you can.
326
    # last_position = (position[0], y)
327
    last_position = (0, y)
×
328

329
    # Add meta-data, assuming data is a dictionary for tracking
330
    data.setdefault("content_modifiers", {})
×
331
    data["content_modifiers"].setdefault("add_heading", {})
×
332
    data["content_modifiers"]["add_heading"].setdefault(counter, [])
×
333
    data["content_modifiers"]["add_heading"][counter].append(_content)
×
334
    data.setdefault("content", "")
×
335
    data["content"] += "\r\n" + _content
×
336

337
    return False, last_position
×
338

339

340
def add_h1_heading(
×
341
    provider, generator, document: ImageDraw, data, counter, **kwargs
342
) -> Tuple[bool, Tuple[int, int]]:
343
    """Callable responsible for the h1 heading generation."""
344
    return add_heading(
×
345
        provider, generator, document, data, counter, level=1, **kwargs
346
    )
347

348

349
def add_h2_heading(
×
350
    provider, generator, document: ImageDraw, data, counter, **kwargs
351
) -> Tuple[bool, Tuple[int, int]]:
352
    """Callable responsible for the h2 heading generation."""
353
    return add_heading(
×
354
        provider, generator, document, data, counter, level=2, **kwargs
355
    )
356

357

358
def add_h3_heading(
×
359
    provider, generator, document: ImageDraw, data, counter, **kwargs
360
) -> Tuple[bool, Tuple[int, int]]:
361
    """Callable responsible for the h3 heading generation."""
362
    return add_heading(
×
363
        provider, generator, document, data, counter, level=3, **kwargs
364
    )
365

366

367
def add_h4_heading(
×
368
    provider, generator, document: ImageDraw, data, counter, **kwargs
369
) -> Tuple[bool, Tuple[int, int]]:
370
    """Callable responsible for the h4 heading generation."""
371
    return add_heading(
×
372
        provider, generator, document, data, counter, level=4, **kwargs
373
    )
374

375

376
def add_h5_heading(
×
377
    provider, generator, document: ImageDraw, data, counter, **kwargs
378
) -> Tuple[bool, Tuple[int, int]]:
379
    """Callable responsible for the h5 heading generation."""
380
    return add_heading(
×
381
        provider, generator, document, data, counter, level=5, **kwargs
382
    )
383

384

385
def add_h6_heading(
×
386
    provider, generator, document: ImageDraw, data, counter, **kwargs
387
) -> Tuple[bool, Tuple[int, int]]:
388
    """Callable responsible for the h6 heading generation."""
389
    return add_heading(
×
390
        provider, generator, document, data, counter, level=6, **kwargs
391
    )
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