Skip to content

Commit 78effbc

Browse files
committed
Remove width and height attributes from SVGs. Update SVG dimensions to use pixel units.
Fixes #351 Squashed commit of the following: commit 25dadab Author: Martin Mahner <[email protected]> Date: Sat Jul 26 10:05:17 2025 +0200 Improve types and comments commit b60b317 Author: Martin Mahner <[email protected]> Date: Sat Jul 26 08:38:49 2025 +0200 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 ca2a1a3 commit 78effbc

File tree

4 files changed

+72
-7
lines changed

4 files changed

+72
-7
lines changed

CHANGES.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ Removed in v9.0:
3737
Change Log
3838
==========
3939

40+
WIP (9.0)
41+
---------
42+
43+
- **Removed** ``width=.. height=...`` attributes from SVG tag, using viewBox instead. SVG elements now use pixel units instead of millimeters.
44+
45+
4046
WIP 8.x
4147
-------
4248

qrcode/image/svg.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,21 +42,29 @@ def units(self, pixels: int | Decimal, text: Literal[False]) -> Decimal: ...
4242
@overload
4343
def units(self, pixels: int | Decimal, text: Literal[True] = True) -> str: ...
4444

45-
def units(self, pixels, text=True):
45+
def units(self, pixels: int, text=True) -> Decimal | str:
4646
"""
47-
A box_size of 10 (default) equals 1mm.
47+
Converts pixel values into a decimal representation with up to three decimal
48+
places of precision or a string representation, optionally rounding to
49+
lower precision without data loss.
4850
"""
49-
units = Decimal(pixels) / 10
51+
units = Decimal(pixels)
5052
if not text:
5153
return units
54+
55+
# Round the decimal to 3 decimal places first, then try to reduce precision
56+
# further by attempting to round to 2 decimals, 1 decimal, and whole numbers.
57+
# If any rounding causes data loss (raises Inexact), keep the previous
58+
# precision.
5259
units = units.quantize(Decimal("0.001"))
5360
context = decimal.Context(traps=[decimal.Inexact])
5461
try:
5562
for d in (Decimal("0.01"), Decimal("0.1"), Decimal(0)):
5663
units = units.quantize(d, context=context)
5764
except decimal.Inexact:
5865
pass
59-
return f"{units}mm"
66+
67+
return str(units)
6068

6169
def save(self, stream, kind=None):
6270
self.check_kind(kind=kind)
@@ -71,11 +79,11 @@ def new_image(self, **kwargs):
7179
def _svg(self, tag=None, version="1.1", **kwargs):
7280
if tag is None:
7381
tag = ET.QName(self._SVG_namespace, "svg")
74-
dimension = self.units(self.pixel_size)
82+
dimension = self.units(self.pixel_size, text=False)
83+
viewBox = kwargs.get("viewBox", f"0 0 {dimension} {dimension}")
84+
kwargs["viewBox"] = viewBox
7585
return ET.Element(
7686
tag,
77-
width=dimension,
78-
height=dimension,
7987
version=version,
8088
**kwargs,
8189
)

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)