diff --git a/README.md b/README.md index b565aa6..2def5c4 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,27 @@ Generate labels for printing using HTML rendering. ## Features * Customizable for most label sheet layouts. +* Partial sheet printing * Custom data for each label -* Uses HTML templates for easy customization +* Uses HTML templates easy label design. -## TODO +## Usage + +The simplest use case is some static labels: + + python -m pylabel -t examples/simple.html -p avery.L7157 -c 10 > output.html + +Then, when you need some more just pop the same sheet back in the printer and offset the start + + python -m pylabel -t examples/simple.html -p avery.L7157 -c 10 > output.html + +To do some mail merge operations create a list of names: + + Andy Andrews + Bob Brown + Charlie Chaplin + +and use a `{data[text]}` element in your template. Note we've shifted to an external stylesheet now as well. + + python -m pylabel -t examples/name_tags.html -p avery.L7157 -d examples/names.txt -s examples/hello.css > output.html -* Switch to profiles for command line usage -* Switch to \ No newline at end of file diff --git a/examples/__init__.py b/examples/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/hello.css b/examples/hello.css new file mode 100644 index 0000000..3c6fc77 --- /dev/null +++ b/examples/hello.css @@ -0,0 +1,33 @@ + +DIV.label { + background-color: red; + color: white; +} + +DIV.hello-tag { + height: 100%; + display: flex; + text-align: center; + align-items: stretch; + flex-direction: column; + justify-content: space-between; + font-family: Arial; + font-size: 10pt; +} + +.greeting { + font-size: 20pt; + font-weight: bold; +} + +.name { + font-family: 'Times New Roman', Times, serif; + font-weight: bold; + font-size: 20pt; + border-radius: 10px; + background-color: white; + flex-grow: 1; + margin-bottom: 2mm; + color: #333; + +} \ No newline at end of file diff --git a/examples/name_tags.html b/examples/name_tags.html new file mode 100644 index 0000000..b2bb0c4 --- /dev/null +++ b/examples/name_tags.html @@ -0,0 +1,5 @@ +
+
Hello
+
my name is
+
{data[text]}
+
\ No newline at end of file diff --git a/examples/names.txt b/examples/names.txt new file mode 100644 index 0000000..dac3fc9 --- /dev/null +++ b/examples/names.txt @@ -0,0 +1,3 @@ +Andy Andrews +Bob Brown +Charlie Chaplin \ No newline at end of file diff --git a/examples/simple.html b/examples/simple.html new file mode 100644 index 0000000..24cffa5 --- /dev/null +++ b/examples/simple.html @@ -0,0 +1,5 @@ +
+
Property of
+
Andy Andrews
+
Please return to office if found
+
\ No newline at end of file diff --git a/examples/simple.py b/examples/simple.py new file mode 100644 index 0000000..601a1d1 --- /dev/null +++ b/examples/simple.py @@ -0,0 +1,15 @@ +from pylabel import avery + +profile = avery.L6009 + +data = range(10) + +template = """ +Hello World +""" + +style = """ +.content { + font-family: Arial; +} +""" \ No newline at end of file diff --git a/pylabel/__init__.py b/pylabel/__init__.py index 123a340..389c2a0 100644 --- a/pylabel/__init__.py +++ b/pylabel/__init__.py @@ -21,7 +21,7 @@ class LabelSheet(object): logger.debug(profile) self.size = profile['size'] - logger.debug(self.size) + logger.debug("Label size: %r", self.size) # get the pagesize and rotate if landscape pagesize = profile.get('pagesize', 'a4') @@ -57,10 +57,20 @@ class LabelSheet(object): 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) - ) + + pitch = profile.get('pitch') + if pitch: + logger.debug("Pitch: %r", pitch) + self.gaps = ( + pitch[0] - self.size[0], + pitch[1] - self.size[1], + ) + else: + 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 @@ -87,7 +97,7 @@ DIV.label {{ def render_page(self, template, data, skip=0): - label_template = f'
{template}
' + label_template = f'
{template}
' blank_labels = "".join([ '
 
' for x in range(skip) ]) diff --git a/pylabel/__main__.py b/pylabel/__main__.py index 242f53f..edc54da 100644 --- a/pylabel/__main__.py +++ b/pylabel/__main__.py @@ -3,30 +3,46 @@ import sys import logging import importlib -from . import LabelSheet +from pylabel import LabelSheet + +class EmptySpec: + profile = None + template = None + data = None 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('--count', '-c', type=int, default=0, help="Number of labels to generate") parser.add_argument('--skip', '-n', type=int, default=0, help="Skip first n labels") +parser.add_argument('--profile', '-p', help='Label profile') 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('--style', '-s', type=argparse.FileType('r'), help='Additional stylesheet') +parser.add_argument('--data', '-d', type=argparse.FileType('r'), help="Pre-generated data, one entry per line") +parser.add_argument('--json', '-j', action='store_true', help='Parse data lines as json') parser.add_argument('--logging', '-l', default="info", help="Logging level") + +parser.add_argument("spec", nargs="?", help="Specification module") + options = parser.parse_args() logging.basicConfig(level=getattr(logging, options.logging.upper(), logging.INFO)) logger.debug(options) +if options.spec: + spec = importlib.import_module(options.spec) +else: + spec = EmptySpec + try: - module, profile = options.profile.rsplit('.', 1) - m = importlib.import_module(module) - p = getattr(m, profile) + if options.profile: + module, profile = options.profile.rsplit('.', 1) + if module.startswith('avery'): + module = 'pylabel.avery' + m = importlib.import_module(module) + spec.profile = getattr(m, profile) except ValueError: raise RuntimeError("Expected profile in the form module.profile") except ModuleNotFoundError: @@ -34,24 +50,31 @@ except ModuleNotFoundError: 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() + spec.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 ] +if options.style is not None: + spec.style = options.style.read() + options.style.close() -sys.stdout.write(sheet.render_html(template, data, options.skip, extra_style)) \ No newline at end of file +if options.data is not None: + lines = [ l.strip() for l in options.data.readlines() if l != '\n' ] + options.data.close() + if options.json: + import json + spec.data = [ json.loads(line) for line in lines ] + else: + spec.data = [ {'text': line} for line in lines ] + +if options.count: + spec.data = range(options.count) + +for required in ('profile', 'template', 'data'): + if not getattr(spec, required): + parser.error(f"Missing {required}") + +sheet = LabelSheet(spec.profile) + +sys.stdout.write(sheet.render_html(spec.template, spec.data, options.skip, getattr(spec, 'style', ''))) \ No newline at end of file diff --git a/pylabel/avery.py b/pylabel/avery.py index 8e36453..45a8945 100644 --- a/pylabel/avery.py +++ b/pylabel/avery.py @@ -1,4 +1,11 @@ +L6009 = { + "size": (45.7, 21.2), + "layout": (4, 12), + "papersize": "a4", + "margins": (9.8, 21.4), +} + L7157 = { "size": (64, 24.3), "layout": (3, 11), diff --git a/pylabel/templates/layout.css b/pylabel/templates/layout.css index 131c73b..03663d9 100644 --- a/pylabel/templates/layout.css +++ b/pylabel/templates/layout.css @@ -22,6 +22,11 @@ } } +HTML, BODY { + margin: 0; + padding: 0; +} + DIV.label { display: inline-block; background-color: #FFF; @@ -29,5 +34,7 @@ DIV.label { } DIV.content { - margin: 2mm; /* Minimum to be safe */ + /*margin: 2mm; /* Minimum to be safe */ + height: 100%; + margin: 0 2mm; } \ No newline at end of file