svg.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. from decimal import Decimal
  2. # On Python 2.6 must install lxml since the older xml.etree.ElementTree
  3. # version can not be used to create SVG images.
  4. try:
  5. import lxml.etree as ET
  6. except ImportError:
  7. import xml.etree.ElementTree as ET
  8. import qrcode.image.base
  9. class SvgFragmentImage(qrcode.image.base.BaseImage):
  10. """
  11. SVG image builder
  12. Creates a QR-code image as a SVG document fragment.
  13. """
  14. _SVG_namespace = "http://www.w3.org/2000/svg"
  15. kind = "SVG"
  16. allowed_kinds = ("SVG",)
  17. def __init__(self, *args, **kwargs):
  18. ET.register_namespace("svg", self._SVG_namespace)
  19. super(SvgFragmentImage, self).__init__(*args, **kwargs)
  20. # Save the unit size, for example the default box_size of 10 is '1mm'.
  21. self.unit_size = self.units(self.box_size)
  22. def drawrect(self, row, col):
  23. self._img.append(self._rect(row, col))
  24. def units(self, pixels, text=True):
  25. """
  26. A box_size of 10 (default) equals 1mm.
  27. """
  28. units = Decimal(pixels) / 10
  29. if not text:
  30. return units
  31. return '%smm' % units
  32. def save(self, stream, kind=None):
  33. self.check_kind(kind=kind)
  34. self._write(stream)
  35. def new_image(self, **kwargs):
  36. return self._svg()
  37. def _svg(self, tag=None, version='1.1', **kwargs):
  38. if tag is None:
  39. tag = ET.QName(self._SVG_namespace, "svg")
  40. dimension = self.units(self.pixel_size)
  41. return ET.Element(
  42. tag, width=dimension, height=dimension, version=version,
  43. **kwargs)
  44. def _rect(self, row, col, tag=None):
  45. if tag is None:
  46. tag = ET.QName(self._SVG_namespace, "rect")
  47. x, y = self.pixel_box(row, col)[0]
  48. return ET.Element(
  49. tag, x=self.units(x), y=self.units(y),
  50. width=self.unit_size, height=self.unit_size)
  51. def _write(self, stream):
  52. ET.ElementTree(self._img).write(stream, xml_declaration=False)
  53. class SvgImage(SvgFragmentImage):
  54. """
  55. Standalone SVG image builder
  56. Creates a QR-code image as a standalone SVG document.
  57. """
  58. background = None
  59. def _svg(self, tag='svg', **kwargs):
  60. svg = super(SvgImage, self)._svg(tag=tag, **kwargs)
  61. svg.set("xmlns", self._SVG_namespace)
  62. if self.background:
  63. svg.append(
  64. ET.Element(
  65. 'rect', fill=self.background, x='0', y='0', width='100%',
  66. height='100%'))
  67. return svg
  68. def _rect(self, row, col):
  69. return super(SvgImage, self)._rect(row, col, tag="rect")
  70. def _write(self, stream):
  71. ET.ElementTree(self._img).write(stream, encoding="UTF-8",
  72. xml_declaration=True)
  73. class SvgPathImage(SvgImage):
  74. """
  75. SVG image builder with one single <path> element (removes white spaces
  76. between individual QR points).
  77. """
  78. QR_PATH_STYLE = 'fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none'
  79. def __init__(self, *args, **kwargs):
  80. self._points = set()
  81. super(SvgPathImage, self).__init__(*args, **kwargs)
  82. def _svg(self, viewBox=None, **kwargs):
  83. if viewBox is None:
  84. dimension = self.units(self.pixel_size, text=False)
  85. viewBox = '0 0 %(d)s %(d)s' % {'d': dimension}
  86. return super(SvgPathImage, self)._svg(viewBox=viewBox, **kwargs)
  87. def drawrect(self, row, col):
  88. # (x, y)
  89. self._points.add((col, row))
  90. def _generate_subpaths(self):
  91. """Generates individual QR points as subpaths"""
  92. rect_size = self.units(self.box_size, text=False)
  93. for point in self._points:
  94. x_base = self.units(
  95. (point[0]+self.border)*self.box_size, text=False)
  96. y_base = self.units(
  97. (point[1]+self.border)*self.box_size, text=False)
  98. yield (
  99. 'M %(x0)s %(y0)s L %(x0)s %(y1)s L %(x1)s %(y1)s L %(x1)s '
  100. '%(y0)s z' % dict(
  101. x0=x_base, y0=y_base,
  102. x1=x_base+rect_size, y1=y_base+rect_size,
  103. ))
  104. def make_path(self):
  105. subpaths = self._generate_subpaths()
  106. return ET.Element(
  107. ET.QName("path"),
  108. style=self.QR_PATH_STYLE,
  109. d=' '.join(subpaths),
  110. id="qr-path"
  111. )
  112. def _write(self, stream):
  113. self._img.append(self.make_path())
  114. super(SvgPathImage, self)._write(stream)
  115. class SvgFillImage(SvgImage):
  116. """
  117. An SvgImage that fills the background to white.
  118. """
  119. background = 'white'
  120. class SvgPathFillImage(SvgPathImage):
  121. """
  122. An SvgPathImage that fills the background to white.
  123. """
  124. background = 'white'