Skip to content

Commit b60b317

Browse files
committed
Remove width and height attributes from SVGs, use viewBox instead. Update SVG dimensions to use pixel units. Add regression test to validate changes.
1 parent ca92eed commit b60b317

File tree

4 files changed

+69
-6
lines changed

4 files changed

+69
-6
lines changed

CHANGES.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,21 @@ Removed in v9.0:
2929
StyledPilImage(embeded_image=..., embeded_image_path=...) # Old
3030
StyledPilImage(embedded_image=..., embedded_image_path=...) # New
3131
32+
- The ``width`` and ``height`` attributes will be removed from the ``<svg>``tag.
33+
Instead, the``viewBox`` attribute is now used for defining the dimensions.
34+
Additionally, all SVG elements now utilize pixel units rather than millimeters,
35+
which may cause rendering differences in browsers. (`#351`_)
36+
37+
.. _#351: https://github.com/lincolnloop/python-qrcode/issues/351
38+
3239
Change Log
3340
==========
3441

42+
WIP (9.0)
43+
---------
44+
45+
- **Removed** ``width=.. height=...`` attributes from SVG tag, using viewBox instead. SVG elements now use pixel units instead of millimeters.
46+
3547
WIP
3648
---
3749

qrcode/image/svg.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@ def units(self, pixels: int | Decimal, text: Literal[True] = True) -> str: ...
4444

4545
def units(self, pixels, text=True):
4646
"""
47-
A box_size of 10 (default) equals 1mm.
47+
Returns pixel values directly.
4848
"""
49-
units = Decimal(pixels) / 10
49+
units = Decimal(pixels)
5050
if not text:
5151
return units
5252
units = units.quantize(Decimal("0.001"))
@@ -56,7 +56,7 @@ def units(self, pixels, text=True):
5656
units = units.quantize(d, context=context)
5757
except decimal.Inexact:
5858
pass
59-
return f"{units}mm"
59+
return f"{units}"
6060

6161
def save(self, stream, kind=None):
6262
self.check_kind(kind=kind)
@@ -71,11 +71,11 @@ def new_image(self, **kwargs):
7171
def _svg(self, tag=None, version="1.1", **kwargs):
7272
if tag is None:
7373
tag = ET.QName(self._SVG_namespace, "svg")
74-
dimension = self.units(self.pixel_size)
74+
dimension = self.units(self.pixel_size, text=False)
75+
viewBox = kwargs.get("viewBox", f"0 0 {dimension} {dimension}")
76+
kwargs["viewBox"] = viewBox
7577
return ET.Element(
7678
tag,
77-
width=dimension,
78-
height=dimension,
7979
version=version,
8080
**kwargs,
8181
)

qrcode/tests/regression/__init__.py

Whitespace-only changes.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
from __future__ import annotations
2+
3+
import io
4+
import re
5+
from typing import TYPE_CHECKING
6+
7+
import pytest
8+
9+
import qrcode
10+
from qrcode.image import svg
11+
from qrcode.tests.consts import UNICODE_TEXT
12+
13+
if TYPE_CHECKING:
14+
from qrcode.image.base import BaseImageWithDrawer
15+
16+
17+
@pytest.mark.parametrize(
18+
"image_factory",
19+
[
20+
svg.SvgFragmentImage,
21+
svg.SvgImage,
22+
svg.SvgFillImage,
23+
svg.SvgPathImage,
24+
svg.SvgPathFillImage,
25+
],
26+
)
27+
def test_svg_no_width_height(image_factory: BaseImageWithDrawer) -> None:
28+
"""Test that SVG output doesn't have width and height attributes."""
29+
qr = qrcode.QRCode()
30+
qr.add_data(UNICODE_TEXT)
31+
32+
# Create a svg with the specified factory and (optional) module drawer
33+
img = qr.make_image(image_factory=image_factory)
34+
svg_str = img.to_string().decode("utf-8")
35+
36+
# Check that width and height attributes are not present in the SVG tag
37+
svg_tag_match = re.search(r"<svg[^>]*>", svg_str)
38+
assert svg_tag_match, "SVG tag not found"
39+
40+
svg_tag = svg_tag_match.group(0)
41+
assert "width=" not in svg_tag, "width attribute should not be present"
42+
assert "height=" not in svg_tag, "height attribute should not be present"
43+
44+
# Check that viewBox is present and uses pixels (no mm suffix)
45+
viewbox_match = re.search(r'viewBox="([^"]*)"', svg_tag)
46+
assert viewbox_match, "viewBox attribute not found"
47+
viewbox = viewbox_match.group(1)
48+
assert "mm" not in viewbox, "viewBox should use pixels, not mm"
49+
50+
# Check that inner elements use pixels (no mm suffix)
51+
assert "mm" not in svg_str, "SVG elements should use pixels, not mm"

0 commit comments

Comments
 (0)