Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions barcode/writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ def __init__(
self.text_distance = 5
self.text_line_distance = 1
self.center_text = True
self._left_align_text = False
self.guard_height_factor = 1.1
self.margin_top = 1
self.margin_bottom = 1
Expand Down Expand Up @@ -275,6 +276,8 @@ def render(self, code: list[str]) -> bytes | T_Image:
ypos += self.module_height

if self.text and self._callbacks["paint_text"] is not None:
# Text blocks between guards are always positioned by their center.
self._left_align_text = not (self.center_text or text["start"])
if not text["start"]:
# If we don't have any start value, print the entire ean
ypos += self.text_distance
Expand Down Expand Up @@ -378,14 +381,15 @@ def _create_text(self, xpos: float, ypos: float) -> None:
# check option to override self.text with self.human (barcode as
# human readable data, can be used to print own formats)
barcodetext = self.human if self.human != "" else self.text
anchor = "start" if self._left_align_text else "middle"
for subtext in barcodetext.split("\n"):
element = self._document.createElement("text")
attributes = {
"x": SIZE.format(xpos),
"y": SIZE.format(ypos),
"style": (
f"fill:{self.foreground};"
f"font-size:{self.font_size}pt;text-anchor:middle;"
f"font-size:{self.font_size}pt;text-anchor:{anchor};"
),
}
_set_attributes(element, **attributes)
Expand Down Expand Up @@ -494,13 +498,14 @@ def _paint_text(self, xpos: float, ypos: float) -> None:
if font_size <= 0:
return
font = ImageFont.truetype(self.font_path, font_size)
anchor = "ld" if self._left_align_text else "md"
for subtext in barcodetext.split("\n"):
pos = (
mm2px(xpos, self.dpi),
mm2px(ypos, self.dpi),
)
self._draw.text(
pos, subtext, font=font, fill=self.foreground, anchor="md"
pos, subtext, font=font, fill=self.foreground, anchor=anchor
)
ypos += pt2mm(self.font_size) / 2 + self.text_line_distance

Expand Down
2 changes: 2 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ v0.17.0
barcodes. (#251)
* Fix ISBN-10 and ISSN barcodes being truncated to 10 and 8 digits.
* Fix Code128 emitting different (but valid) output depending on invocation.
* Fix ``center_text=False`` centering the text on the left edge of the barcode
instead of left aligning it, which cut off the text. (#145)

v0.16.1
~~~~~~~
Expand Down
46 changes: 46 additions & 0 deletions tests/test_writers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@

import os
from io import BytesIO
from xml.dom.minidom import Element
from xml.dom.minidom import parseString

from barcode import EAN13
from barcode.writer import ImageWriter
from barcode.writer import SVGWriter
from barcode.writer import mm2px

PATH = os.path.dirname(os.path.abspath(__file__))
TESTPATH = os.path.join(PATH, "test_outputs")

if ImageWriter is not None:
from PIL import ImageOps

def test_saving_image_to_byteio() -> None:
assert ImageWriter is not None # workaround for mypy
Expand All @@ -34,6 +38,20 @@ def test_saving_rgba_image() -> None:
f, options={"background": "rgba(255,0,0,0)"}
)

def test_image_center_text_false_left_aligns_text() -> None:
assert ImageWriter is not None # workaround for mypy

writer = ImageWriter()
image = EAN13("100000902922", writer=writer).render(
{"center_text": False, "quiet_zone": 6.5, "module_height": 15.0}
)
assert not isinstance(image, bytes) # workaround for mypy

below_bars = (0, int(mm2px(17, writer.dpi)), image.width, image.height)
text_box = ImageOps.invert(image.convert("L")).crop(below_bars).getbbox()
assert text_box is not None
assert text_box[0] >= int(mm2px(6.5, writer.dpi))


def test_saving_svg_to_byteio() -> None:
rv = BytesIO()
Expand All @@ -49,3 +67,31 @@ def test_saving_svg_to_byteio_with_guardbar() -> None:

with open(f"{TESTPATH}/somefile_guardbar.svg", "wb") as f:
EAN13("100000011111", writer=SVGWriter(), guardbar=True).write(f)


def _svg_texts(barcode: EAN13, **options: object) -> list[Element]:
rv = BytesIO()
barcode.write(rv, options=options)
return parseString(rv.getvalue()).getElementsByTagName("text")


def test_svg_text_is_centered_by_default() -> None:
[text] = _svg_texts(EAN13("100000902922", writer=SVGWriter()))

assert "text-anchor:middle;" in text.getAttribute("style")


def test_svg_center_text_false_left_aligns_text() -> None:
[text] = _svg_texts(EAN13("100000902922", writer=SVGWriter()), center_text=False)

assert "text-anchor:start;" in text.getAttribute("style")
assert text.getAttribute("x") == "6.500mm"


def test_svg_center_text_false_keeps_guardbar_blocks_centered() -> None:
texts = _svg_texts(
EAN13("100000902922", writer=SVGWriter(), guardbar=True), center_text=False
)

assert len(texts) > 1
assert all("text-anchor:middle;" in text.getAttribute("style") for text in texts)