From: Vladimír Vondruš Date: Sun, 12 May 2019 20:08:44 +0000 (+0200) Subject: m.qr: new plugin for QR code rendering. X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~cjwatson/git?a=commitdiff_plain;h=eb0a2933d5ae5972658783b9473e6764806a6fa3;p=blog.git m.qr: new plugin for QR code rendering. --- diff --git a/doc/plugins.rst b/doc/plugins.rst index cdff1bdb..f5bf471c 100644 --- a/doc/plugins.rst +++ b/doc/plugins.rst @@ -50,7 +50,8 @@ below or :gh:`grab the whole Git repository `: :gh:`m.code ` (needs also :gh:`ansilexer `) - :gh:`m.plots `, - :gh:`m.dot ` + :gh:`m.dot `, + :gh:`m.qr ` - :gh:`m.link `, :gh:`m.gh `, :gh:`m.dox `, @@ -106,10 +107,10 @@ entered directly in your :abbr:`reST ` sources. `Plots and graphs » <{filename}/plugins/plots-and-graphs.rst>`_ =============================================================== -With :py:`m.plots` and :py:`m.dot` you can render various graphs and charts -directly from values in your :abbr:`reST ` sources. The -result is embedded as an inline SVG and can be styled using CSS like everything -else. +With :py:`m.plots`, :py:`m.dot` and :py:`m.qr` you can render various graphs, +charts and QR codes directly from values in your :abbr:`reST ` +sources. The result is embedded as an inline SVG and can be styled using CSS +like everything else. `Links and other » <{filename}/plugins/links.rst>`_ =================================================== diff --git a/doc/plugins/plots-and-graphs.rst b/doc/plugins/plots-and-graphs.rst index de909042..4d3aa6ca 100644 --- a/doc/plugins/plots-and-graphs.rst +++ b/doc/plugins/plots-and-graphs.rst @@ -43,8 +43,8 @@ Plots and graphs .. role:: css(code) :language: css -These plugin allow you to render plots and graphs directly from data specified -inline in the page source. Similarly to `math rendering <{filename}/admire/math.rst>`_, +These plugin allow you to render plots, graphs or QR codes directly from data +specified inline in the page source. Similarly to `math rendering <{filename}/admire/math.rst>`_, the graphics is rendered to a SVG that's embedded directly in the HTML markup. .. note-danger:: Experimental features @@ -177,7 +177,8 @@ comment :rst:`..`. For Pelican, ownload the `m/dot.py <{filename}/plugins.rst>`_ file, put it including the ``m/`` directory into one of your :py:`PLUGIN_PATHS` and add -``m.dot`` package to your :py:`PLUGINS` in ``pelicanconf.py``. +``m.dot`` package to your :py:`PLUGINS` in ``pelicanconf.py``. For the +Python doc theme, it's enough to just list it in :py:`PLUGINS`. .. note-danger:: @@ -340,3 +341,40 @@ directive. .. class:: m-noindent No. + +`QR code`_ +========== + +For Pelican, download the `m/qr.py <{filename}/plugins.rst>`_ file, put it +including the ``m/`` directory into one of your :py:`PLUGIN_PATHS` and add +``m.qr`` package to your :py:`PLUGINS` in ``pelicanconf.py``. For the +Python doc theme, it's enough to just list it in :py:`PLUGINS`: + +.. code:: python + + PLUGINS += ['m.qr'] + +This feature has no equivalent in the `Doxygen theme <{filename}/documentation/doxygen.rst>`_. + +In addition you need the :gh:`qrcode ` Python +package installed. Get it via ``pip`` or your distribution package manager: + +.. code:: sh + + pip3 install qrcode + +The QR code is rendered using the :rst:`.. qr::` directive. Directive argument +is the data to render. The library will auto-scale the image based on the input +data size, you can override it using the optional :rst:`:size:` parameter. +Resulting SVG has the :css:`.m-image` class, meaning it's centered and at most +100% of page width. + +.. code-figure:: + + .. code:: rst + + .. qr:: https://mcss.mosra.cz/plugins/plots-and-graphs/#qr-code + :size: 256px + + .. qr:: https://mcss.mosra.cz/plugins/plots-and-graphs/#qr-code + :size: 256px diff --git a/package/ci/travis.yml b/package/ci/travis.yml index b9eb0ca5..6d0c5b6a 100644 --- a/package/ci/travis.yml +++ b/package/ci/travis.yml @@ -48,7 +48,7 @@ install: # Jinja 2.10 has some weird behavior, putting a lot of empty lines into the # output. Need to fix that. Matplotlib 3.0.0 (released 2018-09-18) has vastly # different output, need to adapt the rgexps and everything to it first. - - if [ "$WITH_THEME" == "ON" ]; then pip install jinja2==2.9.6 pelican Pyphen Pillow coverage codecov matplotlib==2.2.3; fi + - if [ "$WITH_THEME" == "ON" ]; then pip install jinja2==2.9.6 pelican Pyphen Pillow coverage codecov matplotlib==2.2.3 qrcode; fi - if [ "$WITH_NODE" == "ON" ]; then npm install istanbul codecov; fi # Needed for doxygen binaries diff --git a/plugins/m/qr.py b/plugins/m/qr.py new file mode 100644 index 00000000..179b07f8 --- /dev/null +++ b/plugins/m/qr.py @@ -0,0 +1,82 @@ +# +# This file is part of m.css. +# +# Copyright © 2017, 2018, 2019 Vladimír VondruÅ¡ +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +# + +import io +import qrcode +import qrcode.image.svg +import re + +from docutils.parsers import rst +from docutils.parsers.rst import directives +from docutils.parsers.rst.roles import set_classes +from docutils import nodes + +_svg_preamble_src = re.compile(r"""<\?xml version='1.0' encoding='UTF-8'\?> +""") +_svg_preamble_dst = '' + +def _mm2rem(mm): return mm*9.6/2.54/16.0 + +class Qr(rst.Directive): + final_argument_whitespace = True + has_content = False + required_arguments = 1 + option_spec = {'class': directives.class_option, + 'name': directives.unchanged, + 'size': directives.length_or_unitless} + + def run(self): + set_classes(self.options) + + # FFS why so complex + svg = qrcode.make(self.arguments[0], image_factory=qrcode.image.svg.SvgPathFillImage) + f = io.BytesIO() + svg.save(f) + svg = f.getvalue().decode('utf-8') + + # Compress a bit, remove cruft + svg = svg.replace('L ', 'L').replace('M ', 'M').replace(' z', 'z') + svg = svg.replace(' id="qr-path"', '') + + attribs = ' class="{}"'.format(' '.join(['m-image'] + self.options.get('classes', []))) + + if 'size' in self.options: + size = self.options['size'] + else: + size = None + + def preamble_repl(match): return _svg_preamble_dst.format( + attribs=attribs, + size=size if size else + # The original size is in mm, convert that to pixels on 96 DPI + # and then to rem assuming 1 rem = 16px + '{:.2f}rem'.format(float(match.group('width'))*9.6/2.54/16.0, 2), + viewBox=match.group('viewBox')) + svg = _svg_preamble_src.sub(preamble_repl, svg) + return [nodes.raw('', svg, format='html')] + +def register_mcss(**kwargs): + rst.directives.register_directive('qr', Qr) + +register = register_mcss # for Pelican diff --git a/plugins/m/test/qr/page-py36.html b/plugins/m/test/qr/page-py36.html new file mode 100644 index 00000000..f0a82cff --- /dev/null +++ b/plugins/m/test/qr/page-py36.html @@ -0,0 +1,37 @@ + + + + + m.qr | A Pelican Blog + + + + + + +
+
+
+
+
+
+

m.qr

+ +

Default:

+

Large stuff inside:

+

Large stuff inside, scaled down.

+ + +
+
+
+
+
+ + diff --git a/plugins/m/test/qr/page.html b/plugins/m/test/qr/page.html new file mode 100644 index 00000000..10a9bd95 --- /dev/null +++ b/plugins/m/test/qr/page.html @@ -0,0 +1,37 @@ + + + + + m.qr | A Pelican Blog + + + + + + +
+
+
+
+
+
+

m.qr

+ +

Default:

+

Large stuff inside:

+

Large stuff inside, scaled down.

+ + +
+
+
+
+
+ + \ No newline at end of file diff --git a/plugins/m/test/qr/page.rst b/plugins/m/test/qr/page.rst new file mode 100644 index 00000000..23b479bc --- /dev/null +++ b/plugins/m/test/qr/page.rst @@ -0,0 +1,17 @@ +m.qr +#### + +Default: + +.. qr:: https://mcss.mosra.cz + +Large stuff inside: + +.. qr:: This QR code is quite large because it contains a lot of data and there + is no way to make it behave differently so here you go. + +Large stuff inside, scaled down. + +.. qr:: This QR code is quite large because it contains a lot of data and there + is no way to make it behave differently so here you go. + :size: 100px diff --git a/plugins/m/test/test_qr.py b/plugins/m/test/test_qr.py new file mode 100644 index 00000000..d67afd70 --- /dev/null +++ b/plugins/m/test/test_qr.py @@ -0,0 +1,40 @@ +# +# This file is part of m.css. +# +# Copyright © 2017, 2018, 2019 Vladimír Vondruš +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +# + +import sys + +from distutils.version import LooseVersion + +from . import PelicanPluginTestCase + +class Qr(PelicanPluginTestCase): + def __init__(self, *args, **kwargs): + super().__init__(__file__, '', *args, **kwargs) + + def test(self): + self.run_pelican({ + 'PLUGINS': ['m.htmlsanity', 'm.qr'] + }) + + self.assertEqual(*self.actual_expected_contents('page.html', 'page.html' if LooseVersion(sys.version) >= LooseVersion("3.7") else 'page-py36.html')) diff --git a/site/pelicanconf.py b/site/pelicanconf.py index d7fee31a..a1074171 100644 --- a/site/pelicanconf.py +++ b/site/pelicanconf.py @@ -158,6 +158,7 @@ PLUGINS = ['m.abbr', 'm.math', 'm.metadata', 'm.plots', + 'm.qr', 'm.vk'] THEME = '../pelican-theme'