commit ebc088198f3219ff6e4fac6a3229184637a71d24 Author: Tris Date: Mon Apr 26 14:54:03 2021 +1000 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2585b12 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.pyc +*.egg-info +build +dist \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..f832a50 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +recursive-include pylabel/templates * \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..b565aa6 --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +# PyLabel + +Generate labels for printing using HTML rendering. + +## Features + +* Customizable for most label sheet layouts. +* Custom data for each label +* Uses HTML templates for easy customization + +## TODO + +* Switch to profiles for command line usage +* Switch to \ No newline at end of file diff --git a/pylabel/__init__.py b/pylabel/__init__.py new file mode 100644 index 0000000..123a340 --- /dev/null +++ b/pylabel/__init__.py @@ -0,0 +1,113 @@ +import os.path +import math + +import logging +logger = logging.getLogger(__name__) + +TEMPLATE_DIR = os.path.join(os.path.dirname(__file__), 'templates') + +# load the default style from the template +with open(os.path.join(TEMPLATE_DIR, 'layout.css'), 'r') as f: + LAYOUT_STYLE = f.read() + +PAPER_SIZES = { + 'A4': (210, 297) +} + +class LabelSheet(object): + + def __init__(self, profile): + + logger.debug(profile) + + self.size = profile['size'] + logger.debug(self.size) + + # get the pagesize and rotate if landscape + pagesize = profile.get('pagesize', 'a4') + if not isinstance(pagesize, (list, tuple)): + pagesize = PAPER_SIZES[pagesize.upper()] + if profile.get('landscape'): + pagesize = (pagesize[1], pagesize[0]) + logger.debug("Pagesize: %r", pagesize) + self.pagesize = pagesize + + # get the margins + margins = profile['margins'] + if len(margins) == 2: + margins += margins + logger.debug("Margins: %r", margins) + self.margins = margins + + # Calculate the printable area + self.printarea = ( + pagesize[0]-margins[0]-margins[2], + pagesize[1]-margins[1]-margins[3] + ) + logger.debug("Print area: %r", self.printarea) + + # Calculate rows and cols if not specified + layout = profile.get('layout') + if not layout: + layout = ( + int(self.printarea[0] / self.size[0]), + int(self.printarea[1] / self.size[1]) + ) + logger.debug("Layout: %r", layout) + self.layout = layout + + # Calculate gaps between labels + self.gaps = ( + round((self.printarea[0] - layout[0]*self.size[0]) / (layout[0]-1), 1), + round((self.printarea[1] - layout[1]*self.size[1]) / (layout[1]-1), 1) + ) + logger.debug("Gaps: %r", self.gaps) + + @property + def stylesheet(self): + return LAYOUT_STYLE + """ + +SECTION.page {{ + width: {o.pagesize[0]:0.1f}mm; + height: {o.pagesize[1]:0.1f}mm; + padding-top: {o.margins[1]:0.1f}mm; +}} + +DIV.labelarea {{ + margin-left: {o.margins[0]:0.1f}mm; +}} + +DIV.label {{ + width: {o.size[0]:0.1f}mm; + height: {o.size[1]:0.1f}mm; + margin-right: {o.gaps[0]:0.1f}mm; + margin-bottom: {o.gaps[1]:0.1f}mm; +}} +""".format(o=self) + + def render_page(self, template, data, skip=0): + + label_template = f'
{template}
' + + blank_labels = "".join([ '
 
' for x in range(skip) ]) + + labels = "".join([ label_template.format(data=x) for x in data ]) + + + return """ +
+
{labels}
+
+""".format(labels=blank_labels+labels) + + def render_html(self, template, data, skip=0, extra_style=""): + + page = self.render_page(template, data, skip) + + with open(os.path.join(TEMPLATE_DIR, 'default.html'), 'r') as f: + html = f.read() + + return html.format(style=self.stylesheet + extra_style, label_sheet=page) + + def __str__(self): + return f"" diff --git a/pylabel/__main__.py b/pylabel/__main__.py new file mode 100644 index 0000000..242f53f --- /dev/null +++ b/pylabel/__main__.py @@ -0,0 +1,57 @@ +import argparse +import sys +import logging +import importlib + +from . import LabelSheet + +logger = logging.getLogger(__name__) + +parser = argparse.ArgumentParser(description="Generate a page of labels") + +parser.add_argument("profile", help="Profile to load") + +parser.add_argument('--style', '-s', type=argparse.FileType('r'), help='Additional stylesheet') +parser.add_argument('--skip', '-n', type=int, default=0, help="Skip first n labels") +parser.add_argument('--template', '-t', type=argparse.FileType('r'), help='Template for label') +parser.add_argument('--json', '-j', action='store_true', help='Parse lines as json') + +parser.add_argument('--logging', '-l', default="info", help="Logging level") +options = parser.parse_args() + +logging.basicConfig(level=getattr(logging, options.logging.upper(), logging.INFO)) + +logger.debug(options) + +try: + module, profile = options.profile.rsplit('.', 1) + m = importlib.import_module(module) + p = getattr(m, profile) +except ValueError: + raise RuntimeError("Expected profile in the form module.profile") +except ModuleNotFoundError: + raise RuntimeError(f"Unable to find profile module {module}") +except AttributeError: + raise RuntimeError(f"Unable to file profile {profile} in module {module}") + +sheet = LabelSheet(p) + +extra_style="" +if options.style is not None: + extra_style = options.style.read() + options.style.close() + +template = '{data[text]}' +if options.template is not None: + template = options.template.read() + options.template.close() + +lines = [ l.strip() for l in sys.stdin.readlines() if l != '\n' ] + +if options.json: + import json + data = [ json.loads(line) for line in lines ] +else: + data = [ {'text': line} for line in lines ] + +sys.stdout.write(sheet.render_html(template, data, options.skip, extra_style)) \ No newline at end of file diff --git a/pylabel/avery.py b/pylabel/avery.py new file mode 100644 index 0000000..8e36453 --- /dev/null +++ b/pylabel/avery.py @@ -0,0 +1,7 @@ + +L7157 = { + "size": (64, 24.3), + "layout": (3, 11), + "papersize": "a4", + "margins": (6, 14.5), +} \ No newline at end of file diff --git a/pylabel/templates/default.html b/pylabel/templates/default.html new file mode 100644 index 0000000..78cbc47 --- /dev/null +++ b/pylabel/templates/default.html @@ -0,0 +1,11 @@ + + + Label Sheet + + + +{label_sheet} + + \ No newline at end of file diff --git a/pylabel/templates/layout.css b/pylabel/templates/layout.css new file mode 100644 index 0000000..131c73b --- /dev/null +++ b/pylabel/templates/layout.css @@ -0,0 +1,33 @@ +@media print { + @page { margin: 0; } + + SECTION.page { + page-break-after: always; + background-color: transparent !important; + } + + * { + -webkit-print-color-adjust: exact !important; /* Chrome, Safari */ + color-adjust: exact !important; /*Firefox*/ + } +} + +@media screen { + + SECTION.page { + background-color: #EEE; + margin: 20pt auto 50pt; + box-shadow: 10px 10px 5px grey; + clear: both; + } +} + +DIV.label { + display: inline-block; + background-color: #FFF; + border-radius: 2mm; +} + +DIV.content { + margin: 2mm; /* Minimum to be safe */ +} \ No newline at end of file diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..4d4f384 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,21 @@ +[metadata] +name = pylabel-tf198 +version = 0.0.1 +author = Tris Forster +author_email = tris@shoddynet.org +description = Generates label sheets for printing +long_description = file: README.md +long_description_content_type = text/markdown +url = https://github.com/tf198/pylabel +project_urls = + Bug Tracker = https://github.com/tf198/pylabel/issues +classifiers = + Programming Language :: Python :: 3 + License :: OSI Approved :: MIT License + Operating System :: OS Independent + +[options] +packages = find: +include_package_data = True +python_requires = >=3.6 + diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..c1057cf --- /dev/null +++ b/setup.py @@ -0,0 +1,3 @@ +import setuptools + +setuptools.setup() \ No newline at end of file