Skip to content
10 changes: 5 additions & 5 deletions qrcode/LUT.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"""
Store all kinds of lookup table.
"""
# Store all kinds of lookup table.


# # generate rsPoly lookup table.

# from qrcode import base
#

# def create_bytes(rs_blocks):
# for r in range(len(rs_blocks)):
# dcCount = rs_blocks[r].data_count
Expand All @@ -13,7 +13,7 @@
# for i in range(ecCount):
# rsPoly = rsPoly * base.Polynomial([1, base.gexp(i)], 0)
# return ecCount, rsPoly
#

# rsPoly_LUT = {}
# for version in range(1,41):
# for error_correction in range(4):
Expand Down
2 changes: 0 additions & 2 deletions qrcode/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from __future__ import annotations

from qrcode.main import QRCode
from qrcode.main import make # noqa
from qrcode.constants import ( # noqa
Expand Down
2 changes: 0 additions & 2 deletions qrcode/base.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from __future__ import annotations

from typing import NamedTuple
from qrcode import constants

Expand Down
8 changes: 3 additions & 5 deletions qrcode/console_scripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@
a pipe to a file an image is written. The default image format is PNG.
"""

from __future__ import annotations

import optparse
import os
import sys
from typing import NoReturn
from typing import NoReturn, Optional
from collections.abc import Iterable
from importlib import metadata

Expand Down Expand Up @@ -124,7 +122,7 @@ def raise_error(msg: str) -> NoReturn:
return

kwargs = {}
aliases: DrawerAliases | None = getattr(
aliases: Optional[DrawerAliases] = getattr(
qr.image_factory, "drawer_aliases", None
)
if opts.factory_drawer:
Expand Down Expand Up @@ -158,7 +156,7 @@ def get_drawer_help() -> str:
image = get_factory(module)
except ImportError: # pragma: no cover
continue
aliases: DrawerAliases | None = getattr(image, "drawer_aliases", None)
aliases: Optional[DrawerAliases] = getattr(image, "drawer_aliases", None)
if not aliases:
continue
factories = help.setdefault(commas(aliases), set())
Expand Down
10 changes: 4 additions & 6 deletions qrcode/constants.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from __future__ import annotations

# QR error correct levels
ERROR_CORRECT_L: int = 1
ERROR_CORRECT_M: int = 0
ERROR_CORRECT_Q: int = 3
ERROR_CORRECT_H: int = 2
ERROR_CORRECT_L = 1
ERROR_CORRECT_M = 0
ERROR_CORRECT_Q = 3
ERROR_CORRECT_H = 2
3 changes: 0 additions & 3 deletions qrcode/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,2 @@
from __future__ import annotations


class DataOverflowError(Exception):
pass
10 changes: 4 additions & 6 deletions qrcode/image/base.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from __future__ import annotations

import abc
from typing import TYPE_CHECKING, Any, Union
from typing import TYPE_CHECKING, Any, Optional, Union

from qrcode.image.styles.moduledrawers.base import QRModuleDrawer

Expand All @@ -17,8 +15,8 @@ class BaseImage(abc.ABC):
Base QRCode image output class.
"""

kind: str | None = None
allowed_kinds: tuple[str, ...] | None = None
kind: Optional[str] = None
allowed_kinds: Optional[tuple[str]] = None
needs_context = False
needs_processing = False
needs_drawrect = True
Expand Down Expand Up @@ -142,7 +140,7 @@ def __init__(

def get_drawer(
self, drawer: Union[QRModuleDrawer, str, None]
) -> QRModuleDrawer | None:
) -> Optional[QRModuleDrawer]:
if not isinstance(drawer, str):
return drawer
drawer_cls, kwargs = self.drawer_aliases[drawer]
Expand Down
2 changes: 0 additions & 2 deletions qrcode/image/pil.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from __future__ import annotations

import qrcode.image.base
from PIL import Image, ImageDraw

Expand Down
2 changes: 0 additions & 2 deletions qrcode/image/pure.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from __future__ import annotations

from itertools import chain

from qrcode.compat.png import PngWriter
Expand Down
2 changes: 0 additions & 2 deletions qrcode/image/styles/colormasks.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from __future__ import annotations

import math

from PIL import Image
Expand Down
2 changes: 0 additions & 2 deletions qrcode/image/styles/moduledrawers/base.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from __future__ import annotations

import abc
from typing import TYPE_CHECKING

Expand Down
2 changes: 0 additions & 2 deletions qrcode/image/styles/moduledrawers/pil.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from __future__ import annotations

from typing import TYPE_CHECKING

from PIL import Image, ImageDraw
Expand Down
142 changes: 140 additions & 2 deletions qrcode/image/styles/moduledrawers/svg.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import abc
from decimal import Decimal
from typing import TYPE_CHECKING, NamedTuple
Expand All @@ -21,7 +23,7 @@ class Coords(NamedTuple):


class BaseSvgQRModuleDrawer(QRModuleDrawer):
img: "SvgFragmentImage"
img: SvgFragmentImage

def __init__(self, *, size_ratio: Decimal = Decimal(1), **kwargs):
self.size_ratio = size_ratio
Expand Down Expand Up @@ -97,7 +99,7 @@ def el(self, box):


class SvgPathQRModuleDrawer(BaseSvgQRModuleDrawer):
img: "SvgPathImage"
img: SvgPathImage

def drawrect(self, box, is_active: bool):
if not is_active:
Expand Down Expand Up @@ -137,3 +139,139 @@ def subpath(self, box) -> str:
# x,y is the point the arc is drawn to

return f"M{x0},{yh}A{h},{h} 0 0 0 {x1},{yh}A{h},{h} 0 0 0 {x0},{yh}z"


class SvgRoundedModuleDrawer(SvgPathQRModuleDrawer):
"""
Draws the modules with all 90 degree corners replaced with rounded edges.

radius_ratio determines the radius of the rounded edges - a value of 1
means that an isolated module will be drawn as a circle, while a value of 0
means that the radius of the rounded edge will be 0 (and thus back to 90
degrees again).
"""

needs_neighbors = True

def __init__(self, radius_ratio: Decimal = Decimal(1), **kwargs):
super().__init__(**kwargs)
self.radius_ratio = radius_ratio

def initialize(self, *args, **kwargs) -> None:
super().initialize(*args, **kwargs)
self.corner_radius = self.box_half * self.radius_ratio

def drawrect(self, box, is_active):
if not is_active:
return

# Check if is_active has neighbor information (ActiveWithNeighbors object)
if hasattr(is_active, "N"):
# Neighbor information is available
self.img._subpaths.append(self.subpath(box, is_active))
else:
# No neighbor information available, draw a square with all corners rounded
self.img._subpaths.append(self.subpath_all_rounded(box))

def subpath_all_rounded(self, box) -> str:
"""Draw a module with all corners rounded."""
coords = self.coords(box)
x0 = self.img.units(coords.x0, text=False)
y0 = self.img.units(coords.y0, text=False)
x1 = self.img.units(coords.x1, text=False)
y1 = self.img.units(coords.y1, text=False)
r = self.img.units(self.corner_radius, text=False)

# Build the path with all corners rounded
path = []

# Start at top-left after the rounded part
path.append(f"M{x0 + r},{y0}")

# Top edge to top-right corner
path.append(f"H{x1 - r}")
# Top-right rounded corner
path.append(f"A{r},{r} 0 0 1 {x1},{y0 + r}")

# Right edge to bottom-right corner
path.append(f"V{y1 - r}")
# Bottom-right rounded corner
path.append(f"A{r},{r} 0 0 1 {x1 - r},{y1}")

# Bottom edge to bottom-left corner
path.append(f"H{x0 + r}")
# Bottom-left rounded corner
path.append(f"A{r},{r} 0 0 1 {x0},{y1 - r}")

# Left edge to top-left corner
path.append(f"V{y0 + r}")
# Top-left rounded corner
path.append(f"A{r},{r} 0 0 1 {x0 + r},{y0}")

# Close the path
path.append("z")

return "".join(path)

def subpath(self, box, is_active) -> str:
"""Draw a module with corners rounded based on neighbor information."""
# Determine which corners should be rounded
nw_rounded = not is_active.W and not is_active.N
ne_rounded = not is_active.N and not is_active.E
se_rounded = not is_active.E and not is_active.S
sw_rounded = not is_active.S and not is_active.W

coords = self.coords(box)
x0 = self.img.units(coords.x0, text=False)
y0 = self.img.units(coords.y0, text=False)
x1 = self.img.units(coords.x1, text=False)
y1 = self.img.units(coords.y1, text=False)
r = self.img.units(self.corner_radius, text=False)

# Build the path
path = []

# Start at top-left and move clockwise
if nw_rounded:
# Start at top-left corner, after the rounded part
path.append(f"M{x0 + r},{y0}")
else:
# Start at the top-left corner
path.append(f"M{x0},{y0}")

# Top edge to top-right corner
if ne_rounded:
path.append(f"H{x1 - r}")
# Top-right rounded corner
path.append(f"A{r},{r} 0 0 1 {x1},{y0 + r}")
else:
path.append(f"H{x1}")

# Right edge to bottom-right corner
if se_rounded:
path.append(f"V{y1 - r}")
# Bottom-right rounded corner
path.append(f"A{r},{r} 0 0 1 {x1 - r},{y1}")
else:
path.append(f"V{y1}")

# Bottom edge to bottom-left corner
if sw_rounded:
path.append(f"H{x0 + r}")
# Bottom-left rounded corner
path.append(f"A{r},{r} 0 0 1 {x0},{y1 - r}")
else:
path.append(f"H{x0}")

# Left edge back to start
if nw_rounded:
path.append(f"V{y0 + r}")
# Top-left rounded corner
path.append(f"A{r},{r} 0 0 1 {x0 + r},{y0}")
else:
path.append(f"V{y0}")

# Close the path
path.append("z")

return "".join(path)
18 changes: 12 additions & 6 deletions qrcode/image/svg.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
from __future__ import annotations

import decimal
from decimal import Decimal
from typing import Union, overload, Literal
from typing import Optional, Union, overload, Literal

import qrcode.image.base
from qrcode.compat.etree import ET
Expand Down Expand Up @@ -89,11 +87,15 @@ class SvgImage(SvgFragmentImage):
Creates a QR-code image as a standalone SVG document.
"""

background: str | None = None
background: Optional[str] = None
drawer_aliases: qrcode.image.base.DrawerAliases = {
"circle": (svg_drawers.SvgCircleDrawer, {}),
"gapped-circle": (svg_drawers.SvgCircleDrawer, {"size_ratio": Decimal(0.8)}),
"gapped-square": (svg_drawers.SvgSquareDrawer, {"size_ratio": Decimal(0.8)}),
"rounded-module": (
svg_drawers.SvgRoundedModuleDrawer,
{"size_ratio": Decimal(0.8)},
),
}

def _svg(self, tag="svg", **kwargs):
Expand Down Expand Up @@ -129,8 +131,8 @@ class SvgPathImage(SvgImage):
"stroke": "none",
}

needs_processing: bool = True
path: ET.Element | None = None
needs_processing = True
path: Optional[ET.Element] = None
default_drawer_class: type[QRModuleDrawer] = svg_drawers.SvgPathSquareDrawer
drawer_aliases = {
"circle": (svg_drawers.SvgPathCircleDrawer, {}),
Expand All @@ -142,6 +144,10 @@ class SvgPathImage(SvgImage):
svg_drawers.SvgPathSquareDrawer,
{"size_ratio": Decimal(0.8)},
),
"rounded-module": (
svg_drawers.SvgRoundedModuleDrawer,
{"size_ratio": Decimal(0.8)},
),
}

def __init__(self, *args, **kwargs):
Expand Down
9 changes: 4 additions & 5 deletions qrcode/main.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
from __future__ import annotations

import sys
from bisect import bisect_left
from typing import (
Generic,
NamedTuple,
Optional,
TypeVar,
cast,
overload,
Expand All @@ -15,7 +14,7 @@
from qrcode.image.base import BaseImage
from qrcode.image.pure import PyPNGImage

ModulesType = list[list[bool | None]]
ModulesType = list[list[Optional[bool]]]
# Cache modules generated just based on the QR Code version
precomputed_qr_blanks: dict[int, ModulesType] = {}

Expand Down Expand Up @@ -74,15 +73,15 @@ def __bool__(self) -> bool:

class QRCode(Generic[GenericImage]):
modules: ModulesType
_version: int | None = None
_version: Optional[int] = None

def __init__(
self,
version=None,
error_correction=constants.ERROR_CORRECT_M,
box_size=10,
border=4,
image_factory: type[GenericImage] | None = None,
image_factory: Optional[type[GenericImage]] = None,
mask_pattern=None,
):
_check_box_size(box_size)
Expand Down
Loading