Initial commit
This commit is contained in:
commit
ebc088198f
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
*.pyc
|
||||
*.egg-info
|
||||
build
|
||||
dist
|
||||
1
MANIFEST.in
Normal file
1
MANIFEST.in
Normal file
@ -0,0 +1 @@
|
||||
recursive-include pylabel/templates *
|
||||
14
README.md
Normal file
14
README.md
Normal file
@ -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
|
||||
113
pylabel/__init__.py
Normal file
113
pylabel/__init__.py
Normal file
@ -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'<div class="label"><div class="content">{template}</div></div>'
|
||||
|
||||
blank_labels = "".join([ '<div class="label"><div class="content"> </div></div>' for x in range(skip) ])
|
||||
|
||||
labels = "".join([ label_template.format(data=x) for x in data ])
|
||||
|
||||
|
||||
return """
|
||||
<section class="page">
|
||||
<div class="labelarea">{labels}</div>
|
||||
</section>
|
||||
""".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"<LabelSheet {self.size}, {self.pagesize}>"
|
||||
57
pylabel/__main__.py
Normal file
57
pylabel/__main__.py
Normal file
@ -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 = '<b>{data[text]}</b>'
|
||||
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))
|
||||
7
pylabel/avery.py
Normal file
7
pylabel/avery.py
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
L7157 = {
|
||||
"size": (64, 24.3),
|
||||
"layout": (3, 11),
|
||||
"papersize": "a4",
|
||||
"margins": (6, 14.5),
|
||||
}
|
||||
11
pylabel/templates/default.html
Normal file
11
pylabel/templates/default.html
Normal file
@ -0,0 +1,11 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Label Sheet</title>
|
||||
<style>
|
||||
{style}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
{label_sheet}
|
||||
</body>
|
||||
</html>
|
||||
33
pylabel/templates/layout.css
Normal file
33
pylabel/templates/layout.css
Normal file
@ -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 */
|
||||
}
|
||||
21
setup.cfg
Normal file
21
setup.cfg
Normal file
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user