Skip to content
Draft
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
29 changes: 14 additions & 15 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,29 @@
Changes
=======

Deprecation Warnings
====================
Change Log
==========

Removed in v9.0:
----------------
WIP (9.0)
---------

**Backwards incompatible changes:**

- The ``width`` and ``height`` attributes was removed from the ``<svg>`` tag.
Instead, the ``viewBox`` attribute is now used for defining the dimensions.
Additionally, all SVG elements now utilize pixel units rather than millimeters,
which may cause rendering differences in browsers.

- Importing a PIL drawer from ``qrcode.image.styles.moduledrawers`` has been deprecated.
Update your code to import directly from the ``pil`` module instead:
- Importing a PIL drawer from ``qrcode.image.styles.moduledrawers`` is no longer
supported. Update your code to import directly from the ``pil`` module instead:

.. code-block:: python

from qrcode.image.styles.moduledrawers import SquareModuleDrawer # Old
from qrcode.image.styles.moduledrawers.pil import SquareModuleDrawer # New

- Calling ``QRCode.make_image`` or ``StyledPilImage`` with the arguments ``embeded_image``
or ``embeded_image_path`` have been deprecated due to typographical errors. Update
or ``embeded_image_path`` has been removed to typographical errors. Update
your code to use the correct arguments ``embedded_image`` and ``embededd_image_path``:

.. code-block:: python
Expand All @@ -29,14 +36,6 @@ Removed in v9.0:
StyledPilImage(embeded_image=..., embeded_image_path=...) # Old
StyledPilImage(embedded_image=..., embedded_image_path=...) # New

- The ``width`` and ``height`` attributes will be removed from the ``<svg>`` tag.
Instead, the ``viewBox`` attribute is now used for defining the dimensions.
Additionally, all SVG elements now utilize pixel units rather than millimeters,
which may cause rendering differences in browsers.

Change Log
==========

WIP 8.x
-------

Expand Down
3 changes: 1 addition & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"

[project]
name = "qrcode"
version = "8.2"
version = "9.0a0"
description = "QR Code image generator"
authors = [
{ name = "Lincoln Loop", email = "[email protected]" },
Expand All @@ -31,7 +31,6 @@ classifiers = [
requires-python = "~=3.9"
dependencies = [
"colorama; sys_platform == 'win32'",
"deprecation",
]


Expand Down
33 changes: 4 additions & 29 deletions qrcode/image/styledpil.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
from __future__ import annotations

import warnings
from typing import overload

import deprecation
from PIL import Image

import qrcode.image.base
Expand Down Expand Up @@ -49,26 +47,12 @@ class StyledPilImage(qrcode.image.base.BaseImageWithDrawer):
def __init__(self, *args, **kwargs):
self.color_mask = kwargs.get("color_mask", SolidFillColorMask())

if kwargs.get("embeded_image_path") or kwargs.get("embeded_image"):
warnings.warn(
"The 'embeded_*' parameters are deprecated. Use 'embedded_image_path' "
"or 'embedded_image' instead. The 'embeded_*' parameters will be "
"removed in v9.0.",
category=DeprecationWarning,
stacklevel=2,
)

# allow embeded_ parameters with typos for backwards compatibility
embedded_image_path = kwargs.get(
"embedded_image_path", kwargs.get("embeded_image_path")
)
self.embedded_image = kwargs.get("embedded_image", kwargs.get("embeded_image"))
self.embedded_image_ratio = kwargs.get(
"embedded_image_ratio", kwargs.get("embeded_image_ratio", 0.25)
)
embedded_image_path = kwargs.get("embedded_image_path")
self.embedded_image = kwargs.get("embedded_image")
self.embedded_image_ratio = kwargs.get("embedded_image_ratio", 0.25)
self.embedded_image_resample = kwargs.get(
"embedded_image_resample",
kwargs.get("embeded_image_resample", Image.Resampling.LANCZOS),
"embedded_image_resample", Image.Resampling.LANCZOS
)
if not self.embedded_image and embedded_image_path:
self.embedded_image = Image.open(embedded_image_path)
Expand Down Expand Up @@ -111,15 +95,6 @@ def process(self):
if self.embedded_image:
self.draw_embedded_image()

@deprecation.deprecated(
deprecated_in="9.0",
removed_in="8.3",
current_version="8.2",
details="Use draw_embedded_image() instead",
)
def draw_embeded_image(self):
return self.draw_embedded_image()

def draw_embedded_image(self):
if not self.embedded_image:
return
Expand Down
46 changes: 0 additions & 46 deletions qrcode/image/styles/moduledrawers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,46 +0,0 @@
"""
Module for lazy importing of PIL drawers with a deprecation warning.

Currently, importing a PIL drawer from this module is allowed for backwards
compatibility but will raise a DeprecationWarning.

This will be removed in v9.0.
"""

import warnings

from qrcode.constants import PIL_AVAILABLE


def __getattr__(name):
"""Lazy import with deprecation warning for PIL drawers."""
# List of PIL drawer names that should trigger deprecation warnings
pil_drawers = {
"CircleModuleDrawer",
"GappedCircleModuleDrawer",
"GappedSquareModuleDrawer",
"HorizontalBarsDrawer",
"RoundedModuleDrawer",
"SquareModuleDrawer",
"VerticalBarsDrawer",
}

if name in pil_drawers:
# Only render a warning if PIL is actually installed. Otherwise it would
# raise an ImportError directly, which is fine.
if PIL_AVAILABLE:
warnings.warn(
f"Importing '{name}' directly from this module is deprecated."
f"Please use 'from qrcode.image.styles.moduledrawers.pil import {name}' "
f"instead. This backwards compatibility import will be removed in v9.0.",
DeprecationWarning,
stacklevel=2,
)

# Import and return the drawer from the pil module
from . import pil # noqa: PLC0415

return getattr(pil, name)

# For any other attribute, raise AttributeError
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
22 changes: 15 additions & 7 deletions qrcode/image/svg.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,29 @@ def units(self, pixels: int | Decimal, text: Literal[False]) -> Decimal: ...
@overload
def units(self, pixels: int | Decimal, text: Literal[True] = True) -> str: ...

def units(self, pixels, text=True):
def units(self, pixels: int, text=True) -> Decimal | str:
"""
A box_size of 10 (default) equals 1mm.
Converts pixel values into a decimal representation with up to three decimal
places of precision or a string representation, optionally rounding to
lower precision without data loss.
"""
units = Decimal(pixels) / 10
units = Decimal(pixels)
if not text:
return units

# Round the decimal to 3 decimal places first, then try to reduce precision
# further by attempting to round to 2 decimals, 1 decimal, and whole numbers.
# If any rounding causes data loss (raises Inexact), keep the previous
# precision.
units = units.quantize(Decimal("0.001"))
context = decimal.Context(traps=[decimal.Inexact])
try:
for d in (Decimal("0.01"), Decimal("0.1"), Decimal(0)):
units = units.quantize(d, context=context)
except decimal.Inexact:
pass
return f"{units}mm"

return str(units)

def save(self, stream, kind=None):
self.check_kind(kind=kind)
Expand All @@ -71,11 +79,11 @@ def new_image(self, **kwargs):
def _svg(self, tag=None, version="1.1", **kwargs):
if tag is None:
tag = ET.QName(self._SVG_namespace, "svg")
dimension = self.units(self.pixel_size)
dimension = self.units(self.pixel_size, text=False)
viewBox = kwargs.get("viewBox", f"0 0 {dimension} {dimension}")
kwargs["viewBox"] = viewBox
return ET.Element(
tag,
width=dimension,
height=dimension,
version=version,
**kwargs,
)
Expand Down
17 changes: 1 addition & 16 deletions qrcode/main.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from __future__ import annotations

import sys
import warnings
from bisect import bisect_left
from typing import Generic, Literal, NamedTuple, Optional, TypeVar, cast, overload

Expand Down Expand Up @@ -335,22 +334,8 @@ def make_image(self, image_factory=None, **kwargs):

If the data has not been compiled yet, make it first.
"""
# Raise a warning that 'embeded' is still used
if kwargs.get("embeded_image_path") or kwargs.get("embeded_image"):
warnings.warn(
"The 'embeded_*' parameters are deprecated. Use 'embedded_image_path' "
"or 'embedded_image' instead. The 'embeded_*' parameters will be "
"removed in v9.0.",
category=DeprecationWarning,
stacklevel=2,
)

# allow embeded_ parameters with typos for backwards compatibility
if (
kwargs.get("embedded_image_path")
or kwargs.get("embedded_image")
or kwargs.get("embeded_image_path")
or kwargs.get("embeded_image")
kwargs.get("embedded_image_path") or kwargs.get("embedded_image")
) and self.error_correction != constants.ERROR_CORRECT_H:
raise ValueError(
"Error correction level must be ERROR_CORRECT_H if an embedded image is provided"
Expand Down
51 changes: 51 additions & 0 deletions qrcode/tests/regression/test_svg_dimension.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from __future__ import annotations

import io
import re
from typing import TYPE_CHECKING

import pytest

import qrcode
from qrcode.image import svg
from qrcode.tests.consts import UNICODE_TEXT

if TYPE_CHECKING:
from qrcode.image.base import BaseImageWithDrawer


@pytest.mark.parametrize(
"image_factory",
[
svg.SvgFragmentImage,
svg.SvgImage,
svg.SvgFillImage,
svg.SvgPathImage,
svg.SvgPathFillImage,
],
)
def test_svg_no_width_height(image_factory: BaseImageWithDrawer) -> None:
"""Test that SVG output doesn't have width and height attributes."""
qr = qrcode.QRCode()
qr.add_data(UNICODE_TEXT)

# Create a svg with the specified factory and (optional) module drawer
img = qr.make_image(image_factory=image_factory)
svg_str = img.to_string().decode("utf-8")

# Check that width and height attributes are not present in the SVG tag
svg_tag_match = re.search(r"<svg[^>]*>", svg_str)
assert svg_tag_match, "SVG tag not found"

svg_tag = svg_tag_match.group(0)
assert "width=" not in svg_tag, "width attribute should not be present"
assert "height=" not in svg_tag, "height attribute should not be present"

# Check that viewBox is present and uses pixels (no mm suffix)
viewbox_match = re.search(r'viewBox="([^"]*)"', svg_tag)
assert viewbox_match, "viewBox attribute not found"
viewbox = viewbox_match.group(1)
assert "mm" not in viewbox, "viewBox should use pixels, not mm"

# Check that inner elements use pixels (no mm suffix)
assert "mm" not in svg_str, "SVG elements should use pixels, not mm"
Loading