WhyNotHugo / python-barcode

㊙️ Create standard barcodes with Python. No external dependencies. 100% Organic Python.
http://python-barcode.rtfd.io/
MIT License
575 stars 124 forks source link

invalid ppem value with 0.15.1 but not with 0.14.0 #217

Open aegleinformatiquemedicale opened 1 year ago

aegleinformatiquemedicale commented 1 year ago

Hi,

If i try this with python 3.9.16

With python-barcode 0140 and Pillow 9.5.0 => OK

Python 3.9.16 (main, Jun 30 2023, 08:01:58) 
[GCC 8.5.0 20210514 (Red Hat 8.5.0-18)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import barcode
>>> from barcode.writer import ImageWriter
>>> num = 'ABC123456'
>>> try:
...     checksum = False
...     CODE39 = barcode.get_barcode_class('code39')
...     options = {'font_size': 0,'text_distance': 0.0,'module_height': 24.0,'quiet_zone': 0.0}
...     options['center_text'] = False
...     ean = CODE39(str(num), writer=ImageWriter(), add_checksum=checksum)
...     ean.save('tmp/sticker_code39_' + num, options=options)
... except Exception as err:
...     print(err)
... 
'tmp/sticker_code39_ABC123456.png'

With python-barcode 0.15.1 and Pillow 10.0.0 (same with 10.0.1) => ERR : invalid ppem value

>>> import barcode
>>> from barcode.writer import ImageWriter
>>> num = 'ABC123456'
>>> try:
...     checksum = False
...     CODE39 = barcode.get_barcode_class('code39')
...     options = {'font_size': 0,'text_distance': 0.0,'module_height': 24.0,'quiet_zone': 0.0}
...     options['center_text'] = False
...     ean = CODE39(str(num), writer=ImageWriter(), add_checksum=checksum)
...     ean.save('tmp/sticker_code39_' + num, options=options)
... except Exception as err:
...     print(err)
... 
invalid ppem value

I didn't see in your documentation what had changed.

Thanks

WhyNotHugo commented 1 year ago

I get the same exception with v0.14.0.

> cd python-barcode
> mkvirtualenv python-barcode
> pip install -e '.[images]'
> cat test.py
from __future__ import annotations

import barcode
from barcode.writer import ImageWriter

num = "ABC123456"
checksum = False
CODE39 = barcode.get_barcode_class("code39")
options = {
    "font_size": 0,
    "text_distance": 0.0,
    "module_height": 24.0,
    "quiet_zone": 0.0,
}
options["center_text"] = False
ean = CODE39(str(num), writer=ImageWriter(), add_checksum=checksum)
ean.save("tmp/sticker_code39_" + num, options=options)
> python test.py
Traceback (most recent call last):
  File "/home/hugo/src/github.com/WhyNotHugo/python-barcode/test.py", line 17, in <module>
    ean.save("tmp/sticker_code39_" + num, options=options)
  File "/home/hugo/src/github.com/WhyNotHugo/python-barcode/barcode/base.py", line 65, in save
    output = self.render(options)
             ^^^^^^^^^^^^^^^^^^^^
  File "/home/hugo/src/github.com/WhyNotHugo/python-barcode/barcode/codex.py", line 74, in render
    return super().render(options, text)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/hugo/src/github.com/WhyNotHugo/python-barcode/barcode/base.py", line 105, in render
    raw = self.writer.render(code)
          ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/hugo/src/github.com/WhyNotHugo/python-barcode/barcode/writer.py", line 265, in render
    self._callbacks["paint_text"](xpos, ypos)
  File "/home/hugo/src/github.com/WhyNotHugo/python-barcode/barcode/writer.py", line 439, in _paint_text
    font = ImageFont.truetype(self.font_path, font_size)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/hugo/.local/state/virtualenvs/python-barcode/lib/python3.11/site-packages/PIL/ImageFont.py", line 797, in truetype
    return freetype(font)
           ^^^^^^^^^^^^^^
  File "/home/hugo/.local/state/virtualenvs/python-barcode/lib/python3.11/site-packages/PIL/ImageFont.py", line 794, in freetype
    return FreeTypeFont(font, size, index, encoding, layout_engine)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/hugo/.local/state/virtualenvs/python-barcode/lib/python3.11/site-packages/PIL/ImageFont.py", line 226, in __init__
    self.font = core.getfont(
                ^^^^^^^^^^^^^
OSError: invalid ppem value
aegleinformatiquemedicale commented 1 year ago

With which Pillow version ?

That my venv (when its OK) for my test i changed only versions of python-barcode and Pillow between the first try and the second one

Package                Version
---------------------- ------------
alembic                1.10.4
aniso8601              9.0.1
Babel                  2.12.1
blinker                1.6.3
certifi                2023.7.22
click                  8.1.7
deprecation            2.1.0
distlib                0.3.7
filelock               3.12.4
Flask                  2.3.3
flask-babel            3.1.0
Flask-RESTful          0.3.10
Genshi                 0.7.7
greenlet               3.0.0
gunicorn               20.1.0
importlib-metadata     6.8.0
itsdangerous           2.1.2
Jinja2                 3.1.2
lxml                   4.9.3
Mako                   1.2.4
MarkupSafe             2.1.3
mysql-connector-python 8.0.32
packaging              23.2
pdfkit                 1.0.0
pikepdf                7.2.0
**Pillow                 9.5.0**
pip                    23.3
pipenv                 2023.10.3
platformdirs           3.11.0
protobuf               3.20.3
pypng                  0.20220715.0
**python-barcode         0.14.0**
pytz                   2023.3.post1
qrcode                 7.4.2
relatorio              0.10.1
reportlab              3.6.13
setuptools             68.2.2
six                    1.16.0
SQLAlchemy             2.0.22
toml                   0.10.2
typing_extensions      4.8.0
unoserver              1.6
virtualenv             20.24.5
Werkzeug               2.3.7
zipp                   3.17.0
rpfaff commented 1 year ago

I'm getting similar results writing a Code128 barcode to PNG. I've tested these version combinations:

python-barcode   |   Pillow   |   Result
0.13.0           |    8.0.1   |    OK (old prod environment)
0.14.0           |    9.5.0   |    OK
0.14.0           |    10.0.1  |    Error
0.14.0           |    10.1.0  |    Error
0.15.1           |    9.5.0   |    OK
0.15.1           |    10.0.1  |    Error
0.15.1           |    10.1.0  |    Error

Here's the error (this one is python-barcode 0.15.1 and Pilllow 10.1.0, but the traceback is the same):

Code:

# Init bar code writer and create temp barcode image file.
bar_code = barcode.get_barcode_class('code128')
bar_code.default_writer_options['font_size'] = 0
bar_code.default_writer_options['module_width'] = 0.8
bar_code.default_writer_options['module_height'] = 18
bar_code.default_writer_options['quiet_zone'] = 1

image = bar_code(
    order.public_name if order.public_name else order.id_num,
    writer=ImageWriter()
)

barcode_image = image.save(os.getcwd() + '/bc-' + str(counter))

Error:

OSError                                   Traceback (most recent call last)
File ~/code/PackingSlip/flaskslip/lib/python3.10/site-packages/PIL/ImageFont.py:797, in truetype(font, size, index, encoding, layout_engine)
    796 try:
--> 797     return freetype(font)
    798 except OSError:

File ~/code/PackingSlip/flaskslip/lib/python3.10/site-packages/PIL/ImageFont.py:794, in truetype.<locals>.freetype(font)
    793 def freetype(font):
--> 794     return FreeTypeFont(font, size, index, encoding, layout_engine)

File ~/code/PackingSlip/flaskslip/lib/python3.10/site-packages/PIL/ImageFont.py:226, in FreeTypeFont.__init__(self, font, size, index, encoding, layout_engine)
    225             return
--> 226     self.font = core.getfont(
    227         font, size, index, encoding, layout_engine=layout_engine
    228     )
    229 else:

OSError: invalid ppem value

During handling of the above exception, another exception occurred:

OSError                                   Traceback (most recent call last)
Cell In[27], line 1
----> 1 counter = pdf.order_info_block(doc, order, counter, font, ft_sz)

File ~/code/PackingSlip/flaskslip/pstest/Orders/pdf.py:172, in order_info_block(doc, order, counter, font, ft_sz)
    170 else:
    171     image = bar_code(order.id_num, writer=ImageWriter())
--> 172 barcode_image = image.save(os.getcwd() + '/bc-' + str(counter))
    173 counter += 1
    175 # Insert Barcode and Logo to page.

File ~/code/PackingSlip/flaskslip/lib/python3.10/site-packages/barcode/base.py:64, in Barcode.save(self, filename, options, text)
     53 def save(
     54     self, filename: str, options: Optional[dict] = None, text: Optional[str] = None
     55 ) -> str:
     56     """Renders the barcode and saves it in `filename`.
     57 
     58     :param filename: Filename to save the barcode in (without filename extension).
   (...)
     62     :returns: The full filename with extension.
     63     """
---> 64     output = self.render(options, text) if text else self.render(options)
     66     return self.writer.save(filename, output)

File ~/code/PackingSlip/flaskslip/lib/python3.10/site-packages/barcode/codex.py:255, in Code128.render(self, writer_options, text)
    253 options = {"module_width": MIN_SIZE, "quiet_zone": MIN_QUIET_ZONE}
    254 options.update(writer_options or {})
--> 255 return super().render(options, text)

File ~/code/PackingSlip/flaskslip/lib/python3.10/site-packages/barcode/base.py:100, in Barcode.render(self, writer_options, text)
     98 self.writer.set_options(options)
     99 code = self.build()
--> 100 return self.writer.render(code)

File ~/code/PackingSlip/flaskslip/lib/python3.10/site-packages/barcode/writer.py:269, in BaseWriter.render(self, code)
    267     ypos += self.text_distance
    268     xpos = bxs + (bxe - bxs) / 2.0 if self.center_text else bxs
--> 269     self._callbacks["paint_text"](xpos, ypos)
    270 else:
    271     # Else, divide the ean into blocks and print each block
    272     # in the expected position.
    273     text["xpos"] = [bxs - 4 * self.module_width]

File ~/code/PackingSlip/flaskslip/lib/python3.10/site-packages/barcode/writer.py:440, in ImageWriter._paint_text(self, xpos, ypos)
    438 def _paint_text(self, xpos, ypos):
    439     font_size = int(mm2px(pt2mm(self.font_size), self.dpi))
--> 440     font = ImageFont.truetype(self.font_path, font_size)
    441     for subtext in self.text.split("\n"):
    442         pos = (
    443             mm2px(xpos, self.dpi),
    444             mm2px(ypos, self.dpi),
    445         )

File ~/code/PackingSlip/flaskslip/lib/python3.10/site-packages/PIL/ImageFont.py:831, in truetype(font, size, index, encoding, layout_engine)
    829 for walkfilename in walkfilenames:
    830     if ext and walkfilename == ttf_filename:
--> 831         return freetype(os.path.join(walkroot, walkfilename))
    832     elif not ext and os.path.splitext(walkfilename)[0] == ttf_filename:
    833         fontpath = os.path.join(walkroot, walkfilename)

File ~/code/PackingSlip/flaskslip/lib/python3.10/site-packages/PIL/ImageFont.py:794, in truetype.<locals>.freetype(font)
    793 def freetype(font):
--> 794     return FreeTypeFont(font, size, index, encoding, layout_engine)

File ~/code/PackingSlip/flaskslip/lib/python3.10/site-packages/PIL/ImageFont.py:226, in FreeTypeFont.__init__(self, font, size, index, encoding, layout_engine)
    224                 load_from_bytes(f)
    225             return
--> 226     self.font = core.getfont(
    227         font, size, index, encoding, layout_engine=layout_engine
    228     )
    229 else:
    230     load_from_bytes(font)

OSError: invalid ppem value
WhyNotHugo commented 1 year ago

I don't see anything specific in the changelog: https://pillow.readthedocs.io/en/stable/releasenotes/10.0.0.html

The documentation for the function that we're using doesn't specify if font_size=0 is valid: https://pillow.readthedocs.io/en/stable/reference/ImageFont.html#PIL.ImageFont.truetype

I think that the correct fix here is to update writer.py. If the provided font size is zero, skip rendering the text altogether.

akx commented 12 months ago

FWIW: invalid ppem value comes from FreeType in https://github.com/freetype/freetype/blob/8f255c89e14219ca2489043f699797ee106ec6e9/include/freetype/fterrdef.h#L225-L226 and it's thrown here in https://github.com/freetype/freetype/blob/8f255c89e14219ca2489043f699797ee106ec6e9/src/truetype/ttobjs.c#L1363-L1367 so the theory that passing in a size <= 0 causes this is very sound :)