├── blabel ├── version.py ├── __init__.py ├── data │ └── print_template.html ├── tools.py ├── Blabel.py └── label_tools.py ├── docs ├── requirements.txt ├── _static │ ├── images │ │ ├── title.png │ │ ├── pw_maze_dark.png │ │ └── demo_screenshot.png │ └── css │ │ └── main.css ├── build_and_deploy.sh ├── ref.rst ├── Makefile ├── make.bat ├── index.rst └── conf.py ├── MANIFEST.in ├── examples ├── logo_and_datamatrix │ ├── logo.png │ ├── screenshot.png │ ├── logo_and_datamatrix.pdf │ ├── item_template.html │ ├── logo_and_datamatrix.py │ ├── README.md │ └── style.css ├── labels_from_spreadsheet │ ├── records.csv │ ├── screenshot.png │ ├── labels_from_spreadsheet.pdf │ ├── item_template.html │ ├── style.css │ ├── labels_from_spreadsheet.py │ └── README.md ├── qrcode_and_date │ ├── screenshot.png │ ├── qrcode_and_date.pdf │ ├── item_template.html │ ├── qrcode_and_date.py │ ├── README.md │ └── style.css ├── labels_on_a4_paper │ ├── screenshot.png │ ├── labels_on_a4_paper.pdf │ ├── printer-page-configuration.png │ ├── item_template.html │ ├── labels_on_a4_paper.py │ ├── README.md │ └── style.css ├── several_items_per_page │ ├── screenshot.png │ ├── several_items_per_page.pdf │ ├── item_template.html │ ├── README.md │ ├── style.css │ ├── several_items_per_page.py │ └── assets │ │ ├── female.svg │ │ └── male.svg └── barcode_and_dynamic_picture │ ├── screenshot.png │ ├── barcode_and_dynamic_picture.pdf │ ├── item_template.html │ ├── style.css │ ├── README.md │ └── barcode_and_dynamic_picture.py ├── tests ├── data │ └── samples │ │ ├── labels_from_spreadsheet │ │ ├── records.csv │ │ ├── item_template.html │ │ └── style.css │ │ ├── logo_and_datamatrix │ │ ├── logo.png │ │ ├── item_template.html │ │ └── style.css │ │ ├── qrcode_and_date │ │ ├── qrcode_and_date.pdf │ │ ├── item_template.html │ │ └── style.css │ │ ├── barcode_and_dynamic_picture │ │ ├── item_template.html │ │ └── style.css │ │ └── several_items_per_page │ │ ├── item_template.html │ │ ├── style.css │ │ └── assets │ │ ├── female.svg │ │ └── male.svg └── test_samples.py ├── .travis.yml ├── .github └── workflows │ ├── document.yml │ ├── build.yml │ └── publish.yml ├── pyproject.toml ├── LICENCE.txt ├── pypi-readme.rst ├── .gitignore └── README.rst /blabel/version.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.1.7" 2 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx_rtd_theme 2 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.txt 2 | recursive-include examples *.txt *.py 3 | recursive-include blabel/data * 4 | include *.rst 5 | -------------------------------------------------------------------------------- /docs/_static/images/title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Edinburgh-Genome-Foundry/blabel/HEAD/docs/_static/images/title.png -------------------------------------------------------------------------------- /docs/_static/images/pw_maze_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Edinburgh-Genome-Foundry/blabel/HEAD/docs/_static/images/pw_maze_dark.png -------------------------------------------------------------------------------- /examples/logo_and_datamatrix/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Edinburgh-Genome-Foundry/blabel/HEAD/examples/logo_and_datamatrix/logo.png -------------------------------------------------------------------------------- /docs/_static/images/demo_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Edinburgh-Genome-Foundry/blabel/HEAD/docs/_static/images/demo_screenshot.png -------------------------------------------------------------------------------- /examples/labels_from_spreadsheet/records.csv: -------------------------------------------------------------------------------- 1 | task,importance,done 2 | Walk dog,low,yes 3 | Wash dishes,medium,no 4 | Repare roof,high,no 5 | 6 | -------------------------------------------------------------------------------- /examples/qrcode_and_date/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Edinburgh-Genome-Foundry/blabel/HEAD/examples/qrcode_and_date/screenshot.png -------------------------------------------------------------------------------- /examples/labels_on_a4_paper/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Edinburgh-Genome-Foundry/blabel/HEAD/examples/labels_on_a4_paper/screenshot.png -------------------------------------------------------------------------------- /examples/logo_and_datamatrix/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Edinburgh-Genome-Foundry/blabel/HEAD/examples/logo_and_datamatrix/screenshot.png -------------------------------------------------------------------------------- /examples/qrcode_and_date/qrcode_and_date.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Edinburgh-Genome-Foundry/blabel/HEAD/examples/qrcode_and_date/qrcode_and_date.pdf -------------------------------------------------------------------------------- /tests/data/samples/labels_from_spreadsheet/records.csv: -------------------------------------------------------------------------------- 1 | task,importance,done 2 | Walk dog,low,yes 3 | Wash dishes,medium,no 4 | Repare roof,high,no 5 | 6 | -------------------------------------------------------------------------------- /examples/labels_from_spreadsheet/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Edinburgh-Genome-Foundry/blabel/HEAD/examples/labels_from_spreadsheet/screenshot.png -------------------------------------------------------------------------------- /examples/several_items_per_page/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Edinburgh-Genome-Foundry/blabel/HEAD/examples/several_items_per_page/screenshot.png -------------------------------------------------------------------------------- /tests/data/samples/logo_and_datamatrix/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Edinburgh-Genome-Foundry/blabel/HEAD/tests/data/samples/logo_and_datamatrix/logo.png -------------------------------------------------------------------------------- /docs/build_and_deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | make html 4 | cd _build/html 5 | git add . 6 | git commit -m "New docs" 7 | git push origin gh-pages 8 | cd ../.. -------------------------------------------------------------------------------- /examples/labels_on_a4_paper/labels_on_a4_paper.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Edinburgh-Genome-Foundry/blabel/HEAD/examples/labels_on_a4_paper/labels_on_a4_paper.pdf -------------------------------------------------------------------------------- /examples/barcode_and_dynamic_picture/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Edinburgh-Genome-Foundry/blabel/HEAD/examples/barcode_and_dynamic_picture/screenshot.png -------------------------------------------------------------------------------- /examples/logo_and_datamatrix/logo_and_datamatrix.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Edinburgh-Genome-Foundry/blabel/HEAD/examples/logo_and_datamatrix/logo_and_datamatrix.pdf -------------------------------------------------------------------------------- /tests/data/samples/qrcode_and_date/qrcode_and_date.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Edinburgh-Genome-Foundry/blabel/HEAD/tests/data/samples/qrcode_and_date/qrcode_and_date.pdf -------------------------------------------------------------------------------- /blabel/__init__.py: -------------------------------------------------------------------------------- 1 | """blabel/__init__.py""" 2 | 3 | # __all__ = [] 4 | 5 | from .Blabel import LabelWriter 6 | from .tools import JupyterPDF 7 | from .version import __version__ 8 | -------------------------------------------------------------------------------- /examples/labels_on_a4_paper/printer-page-configuration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Edinburgh-Genome-Foundry/blabel/HEAD/examples/labels_on_a4_paper/printer-page-configuration.png -------------------------------------------------------------------------------- /examples/several_items_per_page/several_items_per_page.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Edinburgh-Genome-Foundry/blabel/HEAD/examples/several_items_per_page/several_items_per_page.pdf -------------------------------------------------------------------------------- /examples/labels_from_spreadsheet/labels_from_spreadsheet.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Edinburgh-Genome-Foundry/blabel/HEAD/examples/labels_from_spreadsheet/labels_from_spreadsheet.pdf -------------------------------------------------------------------------------- /examples/barcode_and_dynamic_picture/barcode_and_dynamic_picture.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Edinburgh-Genome-Foundry/blabel/HEAD/examples/barcode_and_dynamic_picture/barcode_and_dynamic_picture.pdf -------------------------------------------------------------------------------- /examples/labels_from_spreadsheet/item_template.html: -------------------------------------------------------------------------------- 1 |

2 | {% if done == 'yes' %} 3 | 4 | {% endif %} 5 | {{ task }} 6 |

-------------------------------------------------------------------------------- /tests/data/samples/labels_from_spreadsheet/item_template.html: -------------------------------------------------------------------------------- 1 |

2 | {% if done == 'yes' %} 3 | 4 | {% endif %} 5 | {{ task }} 6 |

-------------------------------------------------------------------------------- /examples/barcode_and_dynamic_picture/item_template.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/qrcode_and_date/item_template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ sample_name }}
4 | Made with blabel
5 | {{ label_tools.now() }} 6 |
-------------------------------------------------------------------------------- /tests/data/samples/barcode_and_dynamic_picture/item_template.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/several_items_per_page/item_template.html: -------------------------------------------------------------------------------- 1 | {% if sex == 'F' %} 2 | 3 | {% else %} 4 | 5 | {% endif %} 6 |

{{ name }}

-------------------------------------------------------------------------------- /tests/data/samples/qrcode_and_date/item_template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ sample_name }}
4 | Made with blabel
5 | {{ label_tools.now() }} 6 |
-------------------------------------------------------------------------------- /examples/logo_and_datamatrix/item_template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | {{ sample_name }}
5 | Made with ❤ @ EGF 6 |
-------------------------------------------------------------------------------- /tests/data/samples/several_items_per_page/item_template.html: -------------------------------------------------------------------------------- 1 | {% if sex == 'F' %} 2 | 3 | {% else %} 4 | 5 | {% endif %} 6 |

{{ name }}

7 | -------------------------------------------------------------------------------- /tests/data/samples/barcode_and_dynamic_picture/style.css: -------------------------------------------------------------------------------- 1 | @page { 2 | width: 27mm; 3 | height: 6mm; 4 | } 5 | img { 6 | height: 4mm; 7 | margin-left: 1mm; 8 | display: inline-block; 9 | vertical-align: middle; 10 | } 11 | -------------------------------------------------------------------------------- /tests/data/samples/logo_and_datamatrix/item_template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | {{ sample_name }}
5 | Made with ❤ @ EGF 6 |
-------------------------------------------------------------------------------- /examples/barcode_and_dynamic_picture/style.css: -------------------------------------------------------------------------------- 1 | @page { 2 | width: 27mm; 3 | height: 6mm; 4 | } 5 | img { 6 | height: 4mm; 7 | margin-left: 1mm; 8 | display: inline-block; 9 | vertical-align: middle; 10 | image-rendering: pixelated; 11 | } 12 | -------------------------------------------------------------------------------- /docs/ref.rst: -------------------------------------------------------------------------------- 1 | .. _reference: 2 | 3 | Blabel reference manual 4 | ======================= 5 | 6 | LabelWriter 7 | ~~~~~~~~~~~ 8 | 9 | .. autoclass:: blabel.LabelWriter 10 | :members: 11 | 12 | 13 | Tools 14 | ~~~~~ 15 | 16 | .. automodule:: blabel.label_tools 17 | :members: 18 | -------------------------------------------------------------------------------- /examples/several_items_per_page/README.md: -------------------------------------------------------------------------------- 1 | # Several items per page 2 | 3 | In this example we will print 3 items per sticker. 4 | 5 |

screenshot

6 | 7 | This example shows: 8 | 9 | - How to set several items per sticker with ``items_per_page=3``. 10 | -------------------------------------------------------------------------------- /examples/labels_from_spreadsheet/style.css: -------------------------------------------------------------------------------- 1 | @page { 2 | width: 27mm; 3 | height: 7mm; 4 | } 5 | .print-area, .item { 6 | width: 100%; 7 | } 8 | p { 9 | text-align: center; 10 | font-size: 10px; 11 | margin: 0; 12 | } 13 | .low { color: green;} 14 | .medium { color: orange;} 15 | .high { color: red;} -------------------------------------------------------------------------------- /tests/data/samples/labels_from_spreadsheet/style.css: -------------------------------------------------------------------------------- 1 | @page { 2 | width: 27mm; 3 | height: 7mm; 4 | } 5 | .print-area, .item { 6 | width: 100%; 7 | } 8 | p { 9 | text-align: center; 10 | font-size: 10px; 11 | margin: 0; 12 | } 13 | .low { color: green;} 14 | .medium { color: orange;} 15 | .high { color: red;} -------------------------------------------------------------------------------- /examples/several_items_per_page/style.css: -------------------------------------------------------------------------------- 1 | @page { 2 | width: 27mm; 3 | height: 7mm; 4 | } 5 | .head { 6 | height: 3mm; 7 | margin: 0; 8 | } 9 | .name { 10 | margin: 0; 11 | font-family: Arial, Verdana, Helvetica, sans; 12 | font-weight: bold; 13 | font-size: 5px; 14 | } 15 | .item { 16 | width: 30%; 17 | text-align: center; 18 | } 19 | -------------------------------------------------------------------------------- /examples/qrcode_and_date/qrcode_and_date.py: -------------------------------------------------------------------------------- 1 | from blabel import LabelWriter 2 | 3 | label_writer = LabelWriter("item_template.html", default_stylesheets=("style.css",)) 4 | records = [ 5 | dict(sample_id="s01", sample_name="Sample 1"), 6 | dict(sample_id="s02", sample_name="Sample 2"), 7 | ] 8 | 9 | label_writer.write_labels(records, target="qrcode_and_date.pdf") 10 | -------------------------------------------------------------------------------- /tests/data/samples/several_items_per_page/style.css: -------------------------------------------------------------------------------- 1 | @page { 2 | width: 27mm; 3 | height: 7mm; 4 | } 5 | .head { 6 | height: 3mm; 7 | margin: 0; 8 | } 9 | .name { 10 | margin: 0; 11 | font-family: Arial, Verdana, Helvetica, sans; 12 | font-weight: bold; 13 | font-size: 5px; 14 | } 15 | .item { 16 | width: 30%; 17 | text-align: center; 18 | } 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # sudo: required 2 | dist: trusty 3 | language: python 4 | python: 5 | - "3.9" 6 | 7 | services: 8 | - docker 9 | 10 | install: 11 | - pip install --upgrade pytest pytest-cov coveralls pydenticon pandas 12 | - pip install -e . 13 | 14 | script: 15 | - python -m pytest -v --cov blabel --cov-report term-missing 16 | 17 | after_success: 18 | - coveralls 19 | -------------------------------------------------------------------------------- /examples/labels_from_spreadsheet/labels_from_spreadsheet.py: -------------------------------------------------------------------------------- 1 | from blabel import LabelWriter 2 | import pandas 3 | 4 | dataframe = pandas.read_csv("records.csv") 5 | records = dataframe.to_dict(orient="records") 6 | 7 | label_writer = LabelWriter("item_template.html", default_stylesheets=("style.css",)) 8 | 9 | label_writer.write_labels(records, target="labels_from_spreadsheet.pdf") 10 | -------------------------------------------------------------------------------- /examples/logo_and_datamatrix/logo_and_datamatrix.py: -------------------------------------------------------------------------------- 1 | from blabel import LabelWriter 2 | 3 | label_writer = LabelWriter("item_template.html", default_stylesheets=("style.css",)) 4 | records = [ 5 | dict(sample_id="s01", sample_name="Sample 1"), 6 | dict(sample_id="s02", sample_name="Sample 2"), 7 | ] 8 | 9 | label_writer.write_labels(records, target="logo_and_datamatrix.pdf", base_url=".") 10 | -------------------------------------------------------------------------------- /examples/qrcode_and_date/README.md: -------------------------------------------------------------------------------- 1 | # QR-code and date 2 | 3 | In this example we will print a QR code with a label indicating an auto-generated date. 4 | 5 |

screenshot

6 | 7 | This example shows: 8 | 9 | - How to use ``label_tools.qr_code()``. 10 | - How to use ``label_tools.now()`` to print a date (you can also provide a date formatting string to ``now()``). 11 | -------------------------------------------------------------------------------- /examples/qrcode_and_date/style.css: -------------------------------------------------------------------------------- 1 | @page { 2 | width: 27mm; 3 | height: 7mm; 4 | padding: 0.5mm; 5 | } 6 | img { 7 | height: 6.4mm; 8 | display: inline-block; 9 | vertical-align: middle; 10 | image-rendering: pixelated; 11 | } 12 | .label { 13 | font-family: Arial, Verdana, Helvetica, sans; 14 | font-weight: bold; 15 | vertical-align: middle; 16 | display: inline-block; 17 | font-size: 7px; 18 | } 19 | -------------------------------------------------------------------------------- /examples/labels_on_a4_paper/item_template.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |

{{ sample_id }}

6 |
7 |
8 |

{{ heading }}

9 |
10 |
11 |

{{ sample }}

12 |
13 | 14 | -------------------------------------------------------------------------------- /tests/data/samples/qrcode_and_date/style.css: -------------------------------------------------------------------------------- 1 | @page { 2 | width: 27mm; 3 | height: 7mm; 4 | padding: 0.5mm; 5 | } 6 | img { 7 | height: 6.4mm; 8 | display: inline-block; 9 | vertical-align: middle; 10 | image-rendering: pixelated; 11 | } 12 | .label { 13 | font-family: Arial, Verdana, Helvetica, sans; 14 | font-weight: bold; 15 | vertical-align: middle; 16 | display: inline-block; 17 | font-size: 7px; 18 | } 19 | -------------------------------------------------------------------------------- /examples/logo_and_datamatrix/README.md: -------------------------------------------------------------------------------- 1 | # Logo and datamatrix 2 | 3 | In this particularly ugly example we will print a label with a datamatrix code in one corner and a logo in the opposite corner. 4 | 5 |

screenshot

6 | 7 | This example shows: 8 | 9 | - How to use local pictures (notice that it requires to set the ``base_url`` in ``write_pdf``). 10 | - How to use ``label_tools.datamatrix()`` 11 | -------------------------------------------------------------------------------- /examples/several_items_per_page/several_items_per_page.py: -------------------------------------------------------------------------------- 1 | from blabel import LabelWriter 2 | 3 | label_writer = LabelWriter( 4 | "item_template.html", items_per_page=3, default_stylesheets=("style.css",) 5 | ) 6 | records = [ 7 | dict(name="Scott", sex="M"), 8 | dict(name="Laura", sex="F"), 9 | dict(name="Jane", sex="F"), 10 | dict(name="Valentin", sex="M"), 11 | dict(name="Hille", sex="F"), 12 | ] 13 | 14 | label_writer.write_labels(records, target="several_items_per_page.pdf", base_url=".") 15 | -------------------------------------------------------------------------------- /docs/_static/css/main.css: -------------------------------------------------------------------------------- 1 | body, h1, h2, h3 { 2 | font-family: Ubuntu; 3 | } 4 | 5 | .wy-nav-content-wrap { 6 | background: none 7 | } 8 | 9 | .wy-nav-side { 10 | background-image: url("../images/pw_maze_dark.png"); 11 | } 12 | 13 | .wy-menu-vertical a { 14 | color: black 15 | } 16 | 17 | .wy-menu-vertical a:hover { 18 | background-color: #94aacc 19 | } 20 | 21 | .wy-side-nav-search, .wy-nav-top { 22 | background-color: #94aacc 23 | } 24 | 25 | body, .wy-body-for-nav, .wy-nav-content { 26 | background-color: white; 27 | } 28 | -------------------------------------------------------------------------------- /.github/workflows/document.yml: -------------------------------------------------------------------------------- 1 | name: documentation 2 | 3 | on: 4 | workflow_dispatch: 5 | release: 6 | types: [created] 7 | 8 | jobs: 9 | document: 10 | runs-on: ubuntu-24.04 11 | environment: 12 | name: github-pages 13 | url: ${{ steps.deployment.outputs.page_url }} 14 | permissions: 15 | pages: write 16 | id-token: write 17 | steps: 18 | - id: deployment 19 | uses: sphinx-notes/pages@v3 20 | with: 21 | python_version: 3.12 22 | documentation_path: ./docs 23 | requirements_path: ./docs/requirements.txt 24 | -------------------------------------------------------------------------------- /examples/labels_on_a4_paper/labels_on_a4_paper.py: -------------------------------------------------------------------------------- 1 | from blabel import LabelWriter 2 | 3 | label_writer = LabelWriter( 4 | "item_template.html", items_per_page=1, default_stylesheets=("style.css",) 5 | ) 6 | records = list() 7 | 8 | for k in range(1, 10): 9 | string_sampleid = "s0" + str(k) 10 | string_heading = "Heading " + str(k) 11 | string_sample = "Sample" + str(k) 12 | records.append( 13 | dict(sample_id=string_sampleid, heading=string_heading, sample=string_sample) 14 | ) 15 | 16 | label_writer.write_labels(records, target="labels_on_a4_paper.pdf", base_url=".") 17 | -------------------------------------------------------------------------------- /blabel/data/print_template.html: -------------------------------------------------------------------------------- 1 | 2 | {% for items_chunk in items_chunks %} 3 | 10 | {% endfor %} 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/logo_and_datamatrix/style.css: -------------------------------------------------------------------------------- 1 | @page { 2 | width: 27mm; 3 | height: 7mm; 4 | } 5 | .print-area { 6 | height: 7mm; 7 | width: 100%; 8 | text-align: center; 9 | } 10 | img { 11 | height: 3mm; 12 | display: block; 13 | position: absolute; 14 | } 15 | .logo { 16 | top: 3.5mm; 17 | left: 1px; 18 | } 19 | .datamatrix { 20 | image-rendering: pixelated; 21 | top: 1px; 22 | right: 1px; 23 | } 24 | .label { 25 | font-family: Arial, Verdana, Helvetica, sans; 26 | font-weight: bold; 27 | vertical-align: middle; 28 | display: inline-block; 29 | font-size: 7px; 30 | } 31 | -------------------------------------------------------------------------------- /examples/labels_from_spreadsheet/README.md: -------------------------------------------------------------------------------- 1 | # Labels from spreadsheet 2 | 3 | In this example we will read records from a spreadsheet and generate labels from the content. 4 | 5 |

screenshot

6 | 7 | We will use Pandas, which we recommend for reading any kind of CSV or Excel files 8 | (with ``pandas.read_csv()`` and ``pandas.read_excel()``). The resulting dataframe 9 | can then be converted into records with ``dataframe.to_dict(orient='records')`` 10 | and the records can be directly fed to Blabel. 11 | 12 | Install Pandas with: 13 | 14 | ``` 15 | pip install pandas 16 | ``` 17 | -------------------------------------------------------------------------------- /tests/data/samples/logo_and_datamatrix/style.css: -------------------------------------------------------------------------------- 1 | @page { 2 | width: 27mm; 3 | height: 7mm; 4 | } 5 | .print-area { 6 | height: 7mm; 7 | width: 100%; 8 | text-align: center; 9 | } 10 | img { 11 | height: 3mm; 12 | display: block; 13 | position: absolute; 14 | } 15 | .logo { 16 | top: 3.5mm; 17 | left: 1px; 18 | } 19 | .datamatrix { 20 | image-rendering: pixelated; 21 | top: 1px; 22 | right: 1px; 23 | } 24 | .label { 25 | font-family: Arial, Verdana, Helvetica, sans; 26 | font-weight: bold; 27 | vertical-align: middle; 28 | display: inline-block; 29 | font-size: 7px; 30 | } 31 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = blabel 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /examples/barcode_and_dynamic_picture/README.md: -------------------------------------------------------------------------------- 1 | # Barcode and dynamic picture 2 | 3 | In this example we will print an identicon and a barcode for each label. 4 | 5 |

screenshot

6 | 7 | This example shows: 8 | 9 | - How to use ``label_tools.barcode`` 10 | - How to transform a picture generated on-the-fly into base64 data so it can be integrated into a ```` tag. 11 | - How to define extra variables or functions and use them from inside the template (here, ``generate_identicon``). 12 | 13 | This example requires [pydenticon](https://github.com/azaghal/pydenticon) installed: 14 | 15 | ``` 16 | pip install pydenticon 17 | ``` 18 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "blabel" 3 | version = "0.1.7" 4 | license = "MIT" 5 | authors = [{ name = "Zulko" }] 6 | description = "Generate multi-page, multi-label PDF files in Python." 7 | readme = "pypi-readme.rst" 8 | keywords = ["label", "barcode", "pdf", "generator"] 9 | dependencies = [ 10 | "jinja2", 11 | "qrcode", 12 | "pystrich", 13 | "python-barcode", 14 | "pillow", 15 | "weasyprint", 16 | ] 17 | 18 | [project.urls] 19 | Homepage = "https://github.com/Edinburgh-Genome-Foundry/blabel" 20 | 21 | [build-system] 22 | requires = ["setuptools"] 23 | build-backend = "setuptools.build_meta" 24 | 25 | [tool.setuptools] 26 | include-package-data = true 27 | 28 | [tool.setuptools.packages.find] 29 | exclude = ["docs"] 30 | -------------------------------------------------------------------------------- /examples/barcode_and_dynamic_picture/barcode_and_dynamic_picture.py: -------------------------------------------------------------------------------- 1 | from blabel import LabelWriter 2 | 3 | import pydenticon 4 | import base64 5 | 6 | 7 | def generate_identicon(sample_id): 8 | identicon_generator = pydenticon.Generator( 9 | 6, 6, foreground=["red", "blue", "green", "purple"] 10 | ) 11 | img = identicon_generator.generate(sample_id, 60, 60) 12 | return "data:image/png;base64,%s" % (base64.b64encode(img).decode()) 13 | 14 | 15 | label_writer = LabelWriter( 16 | "item_template.html", 17 | default_stylesheets=("style.css",), 18 | generate_identicon=generate_identicon, 19 | ) 20 | records = [ 21 | dict(sample_id="s01", sample_name="Sample 1"), 22 | dict(sample_id="s02", sample_name="Sample 2"), 23 | dict(sample_id="s03", sample_name="Sample 3"), 24 | ] 25 | 26 | label_writer.write_labels(records, target="barcode_and_dynamic_picture.pdf") 27 | -------------------------------------------------------------------------------- /examples/labels_on_a4_paper/README.md: -------------------------------------------------------------------------------- 1 | # Labels on A4 paper 2 | 3 | In this example we will print a barcode with 2 custom text items on A4 paper. To use this example on any other paper size, you just need to change the 4 | paper *width* and *height* in the style.css file. 5 | 6 | You might need to change the label width and height depending on your type of paper. You can do this by changing the *width* and *height* of the *print-area* object in style.css. 7 | 8 | In this example we will print a matrix of custom labels on A4 paper. 9 | 10 |

screenshot

11 | 12 | To print the generated PDF file to the A4 paper, it is important to set the proper configuration for the page in the printer settings. We need to set margins to 0 as shown in the example below: 13 | 14 |

screenshot

15 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=blabel 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 20 | echo.installed, then set the SPHINXBUILD environment variable to point 21 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 22 | echo.may add the Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /blabel/tools.py: -------------------------------------------------------------------------------- 1 | def list_chunks(mylist, n): 2 | """Yield successive n-sized chunks from mylist.""" 3 | return [mylist[i : i + n] for i in range(0, len(mylist), n)] 4 | 5 | 6 | class JupyterPDF(object): 7 | """Class to display PDFs in a Jupyter / IPython notebook. 8 | Just write this at the end of a code Cell to get in-browser PDF preview: 9 | >>> from pdf_reports import JupyterPDF 10 | >>> JupyterPDF("path_to_some.pdf") 11 | Credits to StackOverflow's Jakob: https://stackoverflow.com/a/19470377 12 | """ 13 | 14 | def __init__(self, url, width=600, height=800): 15 | self.url = url 16 | self.width = width 17 | self.height = height 18 | 19 | def _repr_html_(self): 20 | return """ 21 |
22 | 24 |
25 | """.format( 26 | self=self 27 | ) 28 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push, workflow_dispatch] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-24.04 8 | 9 | steps: 10 | - uses: actions/checkout@v4 11 | - name: Set up Python 12 | uses: actions/setup-python@v5 13 | with: 14 | python-version: "3.12" 15 | - name: Install dependencies 16 | run: | 17 | python -m pip install --upgrade pip 18 | pip install pytest pytest-cov coveralls 19 | pip install pandas pydenticon 20 | - name: Install 21 | run: | 22 | pip install -e . 23 | - name: Test with pytest 24 | run: | 25 | python -m pytest --cov blabel --cov-report term-missing 26 | - name: Coveralls 27 | uses: coverallsapp/github-action@v2 28 | continue-on-error: true 29 | with: 30 | github-token: ${{ secrets.GITHUB_TOKEN }} 31 | env: 32 | COVERALLS_SERVICE_NAME: github 33 | -------------------------------------------------------------------------------- /LICENCE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Edinburgh Genome Foundry, University of Edinburgh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to PyPI 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | test: 9 | runs-on: ubuntu-24.04 10 | 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: Set up Python 14 | uses: actions/setup-python@v5 15 | with: 16 | python-version: "3.12" 17 | - name: Install 18 | run: | 19 | pip install pytest 20 | pip install pandas pydenticon 21 | pip install -e . 22 | - name: Test 23 | run: | 24 | python -m pytest 25 | deploy: 26 | runs-on: ubuntu-24.04 27 | needs: [test] 28 | permissions: 29 | id-token: write 30 | steps: 31 | - uses: actions/checkout@v4 32 | - name: Set up Python 33 | uses: actions/setup-python@v5 34 | with: 35 | python-version: '3.12' 36 | cache: pip 37 | cache-dependency-path: '**/pyproject.toml' 38 | - name: Install dependencies 39 | run: | 40 | pip install setuptools wheel build 41 | - name: Build 42 | run: | 43 | python -m build 44 | - name: Publish 45 | uses: pypa/gh-action-pypi-publish@release/v1 46 | -------------------------------------------------------------------------------- /examples/labels_on_a4_paper/style.css: -------------------------------------------------------------------------------- 1 | @page { 2 | width: 210mm; 3 | height: 297mm; 4 | } 5 | 6 | .print-area { 7 | border: 0.1mm solid black; /* Comment to disable the box around the label */ 8 | height: 29.7mm; /* Single label height on the A4 paper */ 9 | width: 52.3mm; /* Single label width on the A4 paper */ 10 | float:left; 11 | } 12 | .item { 13 | margin-top:4mm; 14 | margin-left:4mm; 15 | margin-right:4mm; 16 | margin-bottom:4mm; 17 | text-align: center; 18 | } 19 | 20 | /*Styling below is used just for positioning the items inside the label. */ 21 | 22 | .divbc 23 | { 24 | position: absolute; 25 | top: 17mm; 26 | left: 5mm; 27 | right: 0; 28 | width: 41mm; 29 | height: 8mm; 30 | } 31 | .barcode { 32 | height: 8mm; 33 | width:40mm; 34 | margin-left: 1mm; 35 | display: inline-block; 36 | vertical-align: middle; 37 | image-rendering: pixelated; 38 | } 39 | 40 | .divheading 41 | { 42 | position: absolute; 43 | top: 10mm; 44 | left: 3mm; 45 | right: 0; 46 | width: 45mm; 47 | height: 6mm; 48 | padding:0mm; 49 | vertical-align:middle; 50 | 51 | } 52 | .divheading .pheading 53 | { 54 | margin:0mm; 55 | vertical-align:middle; 56 | font-size: 10pt; 57 | } 58 | 59 | .divsample 60 | { 61 | position: absolute; 62 | top: 4mm; 63 | right: 5mm; 64 | width: 20mm; 65 | height: 6mm; 66 | padding:0mm; 67 | } 68 | 69 | .divsample .psample 70 | { 71 | margin:0mm; 72 | vertical-align:middle; 73 | text-align:right; 74 | font-size: 8pt; 75 | 76 | } 77 | 78 | .divid 79 | { 80 | position: absolute; 81 | top: 25mm; 82 | left:5mm; 83 | width: 41mm; 84 | height: 6mm; 85 | padding:0mm; 86 | } 87 | 88 | .divid .pid 89 | { 90 | margin:0mm; 91 | vertical-align:middle; 92 | text-align:center; 93 | font-size: 8pt; 94 | 95 | } 96 | -------------------------------------------------------------------------------- /pypi-readme.rst: -------------------------------------------------------------------------------- 1 | Blabel 2 | ====== 3 | 4 | .. image:: https://github.com/Edinburgh-Genome-Foundry/blabel/actions/workflows/build.yml/badge.svg 5 | :target: https://github.com/Edinburgh-Genome-Foundry/blabel/actions/workflows/build.yml 6 | :alt: GitHub CI build status 7 | 8 | .. image:: https://coveralls.io/repos/github/Edinburgh-Genome-Foundry/blabel/badge.svg?branch=master 9 | :target: https://coveralls.io/github/Edinburgh-Genome-Foundry/blabel?branch=master 10 | 11 | 12 | Blabel is a Python package to generate labels (typically for printing stickers) 13 | with barcodes and other niceties. 14 | 15 | **Some features:** 16 | 17 | - Generates PDF files where each page is a label (that's the way most label printers want it). 18 | - Label layout is defined by HTML (Jinja) templates and CSS. Supports any page dimensions and margins. 19 | - Builtin support for various barcodes, QR-codes, datamatrix, and more (wraps other libraries). 20 | - Labels data can be provided as list of dicts (easy to generate from spreadsheets). 21 | - Possibility to print several items per sticker. 22 | 23 | .. image:: https://raw.githubusercontent.com/Edinburgh-Genome-Foundry/blabel/master/docs/_static/images/demo_screenshot.png 24 | 25 | 26 | Infos 27 | ----- 28 | 29 | **PIP installation:** 30 | 31 | .. code:: bash 32 | 33 | pip install blabel 34 | 35 | **Github Page:** ``_ 36 | 37 | **Documentation:** ``_ 38 | 39 | **License:** MIT 40 | 41 | Copyright 2018 Edinburgh Genome Foundry, University of Edinburgh 42 | 43 | 44 | More biology software 45 | --------------------- 46 | 47 | .. image:: https://raw.githubusercontent.com/Edinburgh-Genome-Foundry/Edinburgh-Genome-Foundry.github.io/master/static/imgs/logos/egf-codon-horizontal.png 48 | :target: https://edinburgh-genome-foundry.github.io/ 49 | 50 | Blabel was originally written to print labels for biological samples and is part of the `EGF Codons `_ 51 | synthetic biology software suite for DNA design, manufacturing and validation. 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../README.rst 2 | 3 | .. raw:: html 4 | 5 | 8 | 12 | 14 | 15 | .. raw:: html 16 | 17 | 18 | 19 | 20 | .. .. toctree:: 21 | .. :hidden: 22 | .. :maxdepth: 3 23 | 24 | .. self 25 | 26 | 27 | .. toctree:: 28 | :hidden: 29 | :caption: Reference 30 | :maxdepth: 3 31 | 32 | ref 33 | 34 | 35 | .. _Github: https://github.com/EdinburghGenomeFoundry/blabel 36 | .. _PYPI: https://pypi.python.org/pypi/blabel 37 | -------------------------------------------------------------------------------- /tests/test_samples.py: -------------------------------------------------------------------------------- 1 | import os 2 | import base64 3 | 4 | import pydenticon 5 | import pandas 6 | 7 | import blabel 8 | 9 | SAMPLES_DIR = os.path.join("tests", "data", "samples") 10 | 11 | 12 | def get_template_and_style(folder_name): 13 | folder = os.path.join(SAMPLES_DIR, folder_name) 14 | template = os.path.join(folder, "item_template.html") 15 | style = os.path.join(folder, "style.css") 16 | return template, style 17 | 18 | 19 | def test_qrcode_and_date(tmpdir): 20 | template, style = get_template_and_style("qrcode_and_date") 21 | 22 | label_writer = blabel.LabelWriter(template, default_stylesheets=(style,)) 23 | records = [ 24 | dict(sample_id="s01", sample_name="Sample 1"), 25 | dict(sample_id="s02", sample_name="Sample 2"), 26 | ] 27 | target = os.path.join(str(tmpdir), "qrcode_and_date.pdf") 28 | label_writer.write_labels(records, target=target) 29 | target = os.path.join(str(tmpdir), "qrcode_and_date.html") 30 | label_writer.records_to_html(records, target=target) 31 | 32 | data = label_writer.write_labels(records, target=None) 33 | assert 5_000 < len(data) < 10_000 34 | 35 | 36 | def test_barcode_and_dynamic_picture(tmpdir): 37 | def generate_identicon(sample_id): 38 | identicon_generator = pydenticon.Generator( 39 | 6, 6, foreground=["red", "blue", "green", "purple"] 40 | ) 41 | img = identicon_generator.generate(sample_id, 60, 60) 42 | return "data:image/png;base64,%s" % (base64.b64encode(img).decode()) 43 | 44 | template, style = get_template_and_style("barcode_and_dynamic_picture") 45 | label_writer = blabel.LabelWriter( 46 | template, 47 | default_stylesheets=(style,), 48 | generate_identicon=generate_identicon, 49 | ) 50 | records = [ 51 | dict(sample_id="s01", sample_name="Sample 1"), 52 | dict(sample_id="s02", sample_name="Sample 2"), 53 | dict(sample_id="s03", sample_name="Sample 3"), 54 | ] 55 | 56 | target = os.path.join(str(tmpdir), "barcode_and_dynamic_picture.pdf") 57 | label_writer.write_labels(records, target=target) 58 | 59 | data = label_writer.write_labels(records, target=None) 60 | assert 30_000 > len(data) > 20_000 61 | 62 | 63 | def test_labels_from_spreadsheet(tmpdir): 64 | dataframe = pandas.read_csv( 65 | os.path.join(SAMPLES_DIR, "labels_from_spreadsheet", "records.csv") 66 | ) 67 | records = dataframe.to_dict(orient="records") 68 | template, style = get_template_and_style("labels_from_spreadsheet") 69 | label_writer = blabel.LabelWriter(template, default_stylesheets=(style,)) 70 | 71 | target = os.path.join(str(tmpdir), "labels_from_spreadsheet.pdf") 72 | data = label_writer.write_labels(records, target=target) 73 | 74 | data = label_writer.write_labels(records, target=None) 75 | assert 7_000 > len(data) > 5_000 76 | 77 | 78 | def test_logo_and_datamatrix(tmpdir): 79 | records = [ 80 | dict(sample_id="s01", sample_name="Sample 1"), 81 | dict(sample_id="s02", sample_name="Sample 2"), 82 | ] 83 | template, style = get_template_and_style("logo_and_datamatrix") 84 | label_writer = blabel.LabelWriter(template, default_stylesheets=(style,)) 85 | 86 | target = os.path.join(str(tmpdir), "logo_and_datamatrix.pdf") 87 | label_writer.write_labels( 88 | records, 89 | target=target, 90 | base_url=os.path.join(SAMPLES_DIR, "logo_and_datamatrix"), 91 | ) 92 | 93 | data = label_writer.write_labels( 94 | records, 95 | target=None, 96 | base_url=os.path.join(SAMPLES_DIR, "logo_and_datamatrix"), 97 | ) 98 | assert 55_000 > len(data) > 19_500 99 | 100 | 101 | def test_several_items_per_page(tmpdir): 102 | records = [ 103 | dict(name="Scott", sex="M"), 104 | dict(name="Laura", sex="F"), 105 | dict(name="Jane", sex="F"), 106 | dict(name="Valentin", sex="M"), 107 | dict(name="Hille", sex="F"), 108 | ] 109 | template, style = get_template_and_style("several_items_per_page") 110 | label_writer = blabel.LabelWriter( 111 | template, items_per_page=3, default_stylesheets=(style,) 112 | ) 113 | target = os.path.join(str(tmpdir), "several_items_per_page.pdf") 114 | label_writer.write_labels( 115 | records, 116 | target=target, 117 | base_url=os.path.join(SAMPLES_DIR, "several_items_per_page"), 118 | ) 119 | 120 | data = label_writer.write_labels( 121 | records, 122 | target=None, 123 | base_url=os.path.join(SAMPLES_DIR, "several_items_per_page"), 124 | ) 125 | assert 10_000 > len(data) > 6_000 126 | -------------------------------------------------------------------------------- /blabel/Blabel.py: -------------------------------------------------------------------------------- 1 | import os 2 | from io import BytesIO 3 | 4 | import jinja2 5 | from weasyprint import HTML 6 | from . import label_tools 7 | from . import tools 8 | 9 | THIS_PATH = os.path.dirname(os.path.realpath(__file__)) 10 | with open(os.path.join(THIS_PATH, "data", "print_template.html"), "r") as f: 11 | PRINT_TEMPLATE = jinja2.Template(f.read()) 12 | 13 | GLOBALS = { 14 | "list": list, 15 | "len": len, 16 | "enumerate": enumerate, 17 | "label_tools": label_tools, 18 | "str": str, 19 | } 20 | 21 | 22 | def write_pdf(html, target=None, base_url=None, extra_stylesheets=()): 23 | """Write the provided HTML in a PDF file. 24 | 25 | Parameters 26 | ---------- 27 | html 28 | A HTML string 29 | 30 | target 31 | A PDF file path or file-like object, or None for returning the raw bytes 32 | of the PDF. 33 | 34 | base_url 35 | The base path from which relative paths in the HTML template start. 36 | 37 | use_default_styling 38 | Setting this parameter to False, your PDF will have no styling at all by 39 | default. This means no Semantic UI, which can speed up the rendering. 40 | 41 | extra_stylesheets 42 | List of paths to other ".css" files used to define new styles or 43 | overwrite default styles. 44 | """ 45 | weasy_html = HTML(string=html, base_url=base_url) 46 | if target in [None, "@memory"]: 47 | with BytesIO() as buffer: 48 | weasy_html.write_pdf(buffer, stylesheets=extra_stylesheets) 49 | pdf_data = buffer.getvalue() 50 | return pdf_data 51 | else: 52 | weasy_html.write_pdf(target, stylesheets=extra_stylesheets) 53 | 54 | 55 | class LabelWriter: 56 | """Class to write labels. 57 | 58 | Parameters 59 | ---------- 60 | item_template_path 61 | Path to an HTML/jinja2 html template file that will serve as template 62 | for translating a dict record into the HTML of one item. 63 | Alternatively an ``item_template`` parameter can be provided. 64 | Set encoding of the file with the ``encoding`` parameter (e.g. utf-8). 65 | 66 | item_template 67 | jinja2.Template object to serve as a template for translating a dict 68 | record into the HTML of one item. 69 | 70 | default_stylesheets 71 | List of ``weasyprint.CSS`` objects or path to ``.css`` spreadsheets 72 | to be used for default styling. 73 | 74 | default_base_url 75 | Path to use as origin for relative paths in the HTML document. 76 | (Only useful when using assets such as images etc.) 77 | 78 | items_per_page 79 | Number of items per page (= per sticker). This is particularly practical 80 | if you only have "landscape" stickers and want to print square labels 81 | by printing 2 labels per stickers and cutting the stickers in two 82 | afterwards. 83 | 84 | encoding 85 | The encoding of the item template file. 86 | 87 | **default_context 88 | Use keywords to add any variable, function, etc. that you are using in 89 | the templates. 90 | """ 91 | 92 | def __init__( 93 | self, 94 | item_template_path=None, 95 | item_template=None, 96 | default_stylesheets=(), 97 | default_base_url=None, 98 | items_per_page=1, 99 | encoding=None, 100 | **default_context 101 | ): 102 | if item_template_path is not None: 103 | if encoding is not None: 104 | with open(item_template_path, "r", encoding=encoding) as f: 105 | item_template = f.read() 106 | else: # use locale.getpreferredencoding() 107 | with open(item_template_path, "r") as f: 108 | item_template = f.read() 109 | 110 | if isinstance(item_template, str): 111 | item_template = jinja2.Template(item_template) 112 | self.default_context = default_context if default_context else {} 113 | self.default_stylesheets = default_stylesheets 114 | self.default_base_url = default_base_url 115 | self.item_template = item_template 116 | self.items_per_page = items_per_page 117 | 118 | def record_to_html(self, record): 119 | """Convert one record to an html string using the item template.""" 120 | context = dict(GLOBALS.items()) 121 | context.update(self.default_context) 122 | context.update(record) 123 | return self.item_template.render(**context) 124 | 125 | def records_to_html(self, records, target=None): 126 | """Build the full HTML document to be printed. 127 | 128 | If ``target`` is None, the raw HTML string is returned, else the HTML 129 | is written at the path specified by ``target``.""" 130 | items_htmls = [self.record_to_html(record) for record in records] 131 | items_chunks = tools.list_chunks(items_htmls, self.items_per_page) 132 | html = PRINT_TEMPLATE.render(items_chunks=items_chunks) 133 | if target is not None: 134 | with open(target, "w") as f: 135 | f.write(html) 136 | else: 137 | return html 138 | 139 | def write_labels(self, records, target=None, extra_stylesheets=(), base_url=None): 140 | """Write the PDF document containing the labels to be printed. 141 | 142 | Parameters 143 | ---------- 144 | records 145 | List of dictionaries with the parameters of each label to print. 146 | 147 | target 148 | Path of the PDF file to be generated. If left to None or "@memory", 149 | the raw file data will be returned. 150 | 151 | extra_stylesheets 152 | List of path to stylesheets of Weasyprint CSS objects to complement 153 | the default stylesheets. 154 | 155 | base_url 156 | Path of the origin for the different relative path inside the HTML 157 | document getting printed. 158 | """ 159 | return write_pdf( 160 | self.records_to_html(records), 161 | target=target, 162 | extra_stylesheets=self.default_stylesheets + extra_stylesheets, 163 | base_url=base_url if base_url else self.default_base_url, 164 | ) 165 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # blabel documentation build configuration file, created by 5 | # sphinx-quickstart on Sun Sep 16 16:41:52 2018. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | # 20 | import os 21 | import sys 22 | 23 | sys.path.insert(0, os.path.abspath("../blabel/")) 24 | 25 | 26 | # -- General configuration ------------------------------------------------ 27 | 28 | # If your documentation needs a minimal Sphinx version, state it here. 29 | # 30 | # needs_sphinx = '1.0' 31 | 32 | # Add any Sphinx extension module names here, as strings. They can be 33 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 34 | # ones. 35 | extensions = [ 36 | "sphinx.ext.autodoc", 37 | "sphinx.ext.napoleon", 38 | "sphinx.ext.todo", 39 | "sphinx.ext.viewcode", 40 | "sphinx.ext.githubpages", 41 | # "numpydoc", 42 | ] 43 | napoleon_numpy_docstring = True 44 | 45 | # Add any paths that contain templates here, relative to this directory. 46 | templates_path = ["_templates"] 47 | 48 | # The suffix(es) of source filenames. 49 | # You can specify multiple suffix as a list of string: 50 | # 51 | # source_suffix = ['.rst', '.md'] 52 | source_suffix = ".rst" 53 | 54 | # The master toctree document. 55 | master_doc = "index" 56 | 57 | # General information about the project. 58 | project = "blabel" 59 | copyright = "2018 Edinburgh Genome Foundry, University of Edinburgh" 60 | author = "Zulko" 61 | 62 | # The version info for the project you're documenting, acts as replacement for 63 | # |version| and |release|, also used in various other places throughout the 64 | # built documents. 65 | # 66 | # The short X.Y version. 67 | # version = "0.1.0" 68 | # The full version, including alpha/beta/rc tags. 69 | # release = "0.1.0" 70 | 71 | # List of patterns, relative to source directory, that match files and 72 | # directories to ignore when looking for source files. 73 | # This patterns also effect to html_static_path and html_extra_path 74 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] 75 | 76 | # The name of the Pygments (syntax highlighting) style to use. 77 | pygments_style = "sphinx" 78 | 79 | # If true, `todo` and `todoList` produce output, else they produce nothing. 80 | todo_include_todos = True 81 | 82 | 83 | # -- Options for HTML output ---------------------------------------------- 84 | 85 | # The theme to use for HTML and HTML Help pages. See the documentation for 86 | # a list of builtin themes. 87 | # 88 | # html_theme = 'alabaster' 89 | html_theme = "sphinx_rtd_theme" 90 | 91 | # Theme options are theme-specific and customize the look and feel of a theme 92 | # further. For a list of options available for each theme, see the 93 | # documentation. 94 | # 95 | # html_theme_options = {} 96 | 97 | # Add any paths that contain custom static files (such as style sheets) here, 98 | # relative to this directory. They are copied after the builtin static files, 99 | # so a file named "default.css" will overwrite the builtin "default.css". 100 | html_static_path = ["_static"] 101 | 102 | # Custom sidebar templates, must be a dictionary that maps document names 103 | # to template names. 104 | # 105 | # This is required for the alabaster theme 106 | # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars 107 | html_sidebars = { 108 | "**": [ 109 | "relations.html", # needs 'show_related': True theme option to display 110 | "searchbox.html", 111 | ] 112 | } 113 | 114 | 115 | # -- Options for HTMLHelp output ------------------------------------------ 116 | 117 | # Output file base name for HTML help builder. 118 | htmlhelp_basename = "blabeldoc" 119 | 120 | 121 | # -- Options for LaTeX output --------------------------------------------- 122 | 123 | latex_elements = { 124 | # The paper size ('letterpaper' or 'a4paper'). 125 | # 126 | # 'papersize': 'letterpaper', 127 | # The font size ('10pt', '11pt' or '12pt'). 128 | # 129 | # 'pointsize': '10pt', 130 | # Additional stuff for the LaTeX preamble. 131 | # 132 | # 'preamble': '', 133 | # Latex figure (float) alignment 134 | # 135 | # 'figure_align': 'htbp', 136 | } 137 | 138 | # Grouping the document tree into LaTeX files. List of tuples 139 | # (source start file, target name, title, 140 | # author, documentclass [howto, manual, or own class]). 141 | latex_documents = [ 142 | (master_doc, "blabel.tex", "blabel Documentation", "Zulko", "manual"), 143 | ] 144 | 145 | 146 | # -- Options for manual page output --------------------------------------- 147 | 148 | # One entry per manual page. List of tuples 149 | # (source start file, name, description, authors, manual section). 150 | man_pages = [(master_doc, "blabel", "blabel Documentation", [author], 1)] 151 | 152 | 153 | # -- Options for Texinfo output ------------------------------------------- 154 | 155 | # Grouping the document tree into Texinfo files. List of tuples 156 | # (source start file, target name, title, author, 157 | # dir menu entry, description, category) 158 | texinfo_documents = [ 159 | ( 160 | master_doc, 161 | "blabel", 162 | "blabel Documentation", 163 | author, 164 | "blabel", 165 | "One line description of project.", 166 | "Miscellaneous", 167 | ), 168 | ] 169 | 170 | autodoc_member_order = "bysource" 171 | 172 | on_rtd = os.environ.get("READTHEDOCS", None) == "True" 173 | 174 | if not on_rtd: # only import and set the theme if we're building docs locally 175 | import sphinx_rtd_theme 176 | 177 | html_theme = "sphinx_rtd_theme" 178 | 179 | def setup(app): 180 | app.add_css_file("css/main.css") 181 | 182 | else: 183 | html_context = { 184 | "css_files": [ 185 | "https://media.readthedocs.org/css/sphinx_rtd_theme.css", 186 | "https://media.readthedocs.org/css/readthedocs-doc-embed.css", 187 | "_static/css/main.css", 188 | ], 189 | } 190 | -------------------------------------------------------------------------------- /examples/several_items_per_page/assets/female.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 24 | 45 | 51 | 57 | 63 | 77 | 83 | 89 | 91 | 93 | 95 | 97 | image/svg+xml 100 | 103 | 106 | 108 | 111 | Openclipart 114 | 116 | 118 | people biz - female cyan 121 | 2008-08-21T05:05:14 124 | 126 | https://openclipart.org/detail/18599/people-biz---female-cyan-by-yyycatch 129 | 131 | 133 | yyycatch 136 | 138 | 140 | 142 | 144 | cyan 147 | female 150 | icon 153 | people 156 | user 159 | woman 162 | 164 | 166 | 168 | 171 | 174 | 177 | 180 | 182 | 184 | 186 | 188 | -------------------------------------------------------------------------------- /tests/data/samples/several_items_per_page/assets/female.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 24 | 45 | 51 | 57 | 63 | 77 | 83 | 89 | 91 | 93 | 95 | 97 | image/svg+xml 100 | 103 | 106 | 108 | 111 | Openclipart 114 | 116 | 118 | people biz - female cyan 121 | 2008-08-21T05:05:14 124 | 126 | https://openclipart.org/detail/18599/people-biz---female-cyan-by-yyycatch 129 | 131 | 133 | yyycatch 136 | 138 | 140 | 142 | 144 | cyan 147 | female 150 | icon 153 | people 156 | user 159 | woman 162 | 164 | 166 | 168 | 171 | 174 | 177 | 180 | 182 | 184 | 186 | 188 | -------------------------------------------------------------------------------- /examples/several_items_per_page/assets/male.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 24 | 45 | 51 | 57 | 63 | 77 | 83 | 89 | 95 | 97 | 99 | 101 | 103 | image/svg+xml 106 | 109 | 112 | 114 | 117 | Openclipart 120 | 122 | 124 | people biz - male pink 127 | 2008-08-21T05:04:20 130 | 132 | https://openclipart.org/detail/18597/people-biz---male-pink-by-yyycatch 135 | 137 | 139 | yyycatch 142 | 144 | 146 | 148 | 150 | icon 153 | male 156 | man 159 | people 162 | pink 165 | tie 168 | user 171 | 173 | 175 | 177 | 180 | 183 | 186 | 189 | 191 | 193 | 195 | 197 | -------------------------------------------------------------------------------- /tests/data/samples/several_items_per_page/assets/male.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 24 | 45 | 51 | 57 | 63 | 77 | 83 | 89 | 95 | 97 | 99 | 101 | 103 | image/svg+xml 106 | 109 | 112 | 114 | 117 | Openclipart 120 | 122 | 124 | people biz - male pink 127 | 2008-08-21T05:04:20 130 | 132 | https://openclipart.org/detail/18597/people-biz---male-pink-by-yyycatch 135 | 137 | 139 | yyycatch 142 | 144 | 146 | 148 | 150 | icon 153 | male 156 | man 159 | people 162 | pink 165 | tie 168 | user 171 | 173 | 175 | 177 | 180 | 183 | 186 | 189 | 191 | 193 | 195 | 197 | -------------------------------------------------------------------------------- /blabel/label_tools.py: -------------------------------------------------------------------------------- 1 | """Utilities for label generation.""" 2 | 3 | import base64 4 | from io import BytesIO 5 | import datetime 6 | import textwrap 7 | 8 | import qrcode 9 | import barcode as python_barcode 10 | from pystrich.datamatrix import DataMatrixEncoder 11 | from PIL import Image, ImageOps 12 | 13 | 14 | def now(fmt="%Y-%m-%d %H:%M"): 15 | """Display the current time. 16 | 17 | Default format is "year-month-day hour:minute" but another format can be 18 | provided (see ``datetime`` docs for date formatting). 19 | """ 20 | now = datetime.datetime.now() 21 | if fmt is not None: 22 | now = now.strftime(fmt) 23 | return now 24 | 25 | 26 | def pil_to_html_imgdata(img, fmt="PNG"): 27 | """Convert a PIL image into HTML-displayable data. 28 | 29 | The result is a string ``data:image/FMT;base64,xxxxxxxxx`` which you 30 | can provide as a "src" parameter to a ```` tag. 31 | 32 | Examples 33 | -------- 34 | 35 | >>> data = pil_to_html_imgdata(my_pil_img) 36 | >>> html_data = '' % data 37 | """ 38 | buffered = BytesIO() 39 | img.save(buffered, format=fmt) 40 | img_str = base64.b64encode(buffered.getvalue()) 41 | prefix = "data:image/%s;charset=utf-8;base64," % fmt.lower() 42 | return prefix + img_str.decode() 43 | 44 | 45 | def wrap(text, col_width): 46 | """Breaks the text into lines with at maximum 'col_width' characters.""" 47 | return "\n".join(textwrap.wrap(text, col_width)) 48 | 49 | 50 | def hiro_square(width="100%"): 51 | """Return a string of a Hiro square to be included in HTML.""" 52 | svg = """ 53 | 55 | 56 | 57 | 58 | """ % ( 59 | width, 60 | width, 61 | ) 62 | prefix = "data:image/svg+xml;charset=utf-8;base64," 63 | return prefix + base64.b64encode(svg.encode()).decode() 64 | 65 | 66 | def qr_code( 67 | data, optimize=20, fill_color="black", back_color="white", **qr_code_params 68 | ): 69 | """Return a QR code's image data. 70 | 71 | Powered by the Python library ``qrcode``. See this library's documentation 72 | for more details. 73 | 74 | Parameters 75 | ---------- 76 | data 77 | Data to be encoded in the QR code. 78 | 79 | optimize 80 | Chunk length optimization setting. 81 | 82 | fill_color, back_color 83 | Colors to use for QRcode and its background. 84 | 85 | **qr_code_params 86 | Parameters of the ``qrcode.QRCode`` constructor, such as ``version``, 87 | ``error_correction``, ``box_size``, ``border``. 88 | 89 | Returns 90 | ------- 91 | image_base64_data 92 | A string ``data:image/png;base64,xxxxxxxxx`` which you can provide as a 93 | "src" parameter to a ```` tag. 94 | 95 | Examples 96 | -------- 97 | 98 | >>> data = qr_code('egf45728') 99 | >>> html_data = '' % data 100 | """ 101 | params = dict(box_size=5, border=0) 102 | params.update(qr_code_params) 103 | qr = qrcode.QRCode(**params) 104 | qr.add_data(data, optimize=20) 105 | qri = qr.make_image(fill_color=fill_color, back_color=back_color) 106 | return pil_to_html_imgdata(qri.get_image()) 107 | 108 | 109 | def datamatrix(data, cellsize=2, with_border=False): 110 | """Return a datamatrix's image data. 111 | 112 | Powered by the Python library ``pyStrich``. See this library's documentation 113 | for more details. 114 | 115 | Parameters 116 | ---------- 117 | data 118 | Data to be encoded in the datamatrix. 119 | 120 | cellsize 121 | size of the picture in inches (?). 122 | 123 | with_border 124 | If false, there will be no border or margin to the datamatrix image. 125 | 126 | Returns 127 | ------- 128 | image_base64_data 129 | A string ``data:image/png;base64,xxxxxxxxx`` which you can provide as a 130 | "src" parameter to a ```` tag. 131 | 132 | Examples 133 | -------- 134 | 135 | >>> data = datamatrix('EGF') 136 | >>> html_data = '' % data 137 | """ 138 | encoder = DataMatrixEncoder(data) 139 | img_data = encoder.get_imagedata(cellsize=cellsize) 140 | img = Image.open(BytesIO(img_data)) 141 | if not with_border: 142 | img = img.crop(ImageOps.invert(img).getbbox()) 143 | return pil_to_html_imgdata(img) 144 | 145 | 146 | def barcode( 147 | data, barcode_class="code128", fmt="png", add_checksum=True, **writer_options 148 | ): 149 | """Return a barcode's image data. 150 | 151 | Powered by the Python library ``python-barcode``. See this library's 152 | documentation for more details. 153 | 154 | Parameters 155 | ---------- 156 | data 157 | Data to be encoded in the datamatrix. 158 | 159 | barcode_class 160 | Class/standard to use to encode the data. Different standards have 161 | different constraints. 162 | 163 | writer_options 164 | Various options for the writer to tune the appearance of the barcode 165 | (see python-barcode documentation). 166 | 167 | Returns 168 | ------- 169 | image_base64_data 170 | A string ``data:image/png;base64,xxxxxxxxx`` which you can provide as a 171 | "src" parameter to a ```` tag. 172 | 173 | Examples 174 | -------- 175 | 176 | >>> data = barcode('EGF12134', barcode_class='code128') 177 | >>> html_data = '' % data 178 | 179 | Examples of writer options: 180 | 181 | >>> { 'background': 'white', 182 | >>> 'font_size': 10, 183 | >>> 'foreground': 'black', 184 | >>> 'module_height': 15.0, 185 | >>> 'module_width': 0.2, 186 | >>> 'quiet_zone': 6.5, 187 | >>> 'text': '', 188 | >>> 'text_distance': 5.0, 189 | >>> 'write_text': True 190 | >>> } 191 | """ 192 | constructor = python_barcode.get_barcode_class(barcode_class) 193 | data = str(data).zfill(constructor.digits) 194 | writer = { 195 | "svg": python_barcode.writer.SVGWriter, 196 | "png": python_barcode.writer.ImageWriter, 197 | }[fmt] 198 | if "add_checksum" in getattr(constructor, "__init__").__code__.co_varnames: 199 | barcode_img = constructor(data, writer=writer(), add_checksum=add_checksum) 200 | else: 201 | barcode_img = constructor(data, writer=writer()) 202 | img = barcode_img.render(writer_options=writer_options) 203 | if fmt == "png": 204 | return pil_to_html_imgdata(img, fmt="PNG") 205 | else: 206 | prefix = "data:image/svg+xml;charset=utf-8;base64," 207 | return prefix + base64.b64encode(img).decode() 208 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. raw:: html 2 | 3 |

4 | Blabel Logo 5 |

6 |

7 | 8 | ---- 9 | 10 | .. image:: https://github.com/Edinburgh-Genome-Foundry/blabel/actions/workflows/build.yml/badge.svg 11 | :target: https://github.com/Edinburgh-Genome-Foundry/blabel/actions/workflows/build.yml 12 | :alt: GitHub CI build status 13 | 14 | .. image:: https://coveralls.io/repos/github/Edinburgh-Genome-Foundry/blabel/badge.svg?branch=master 15 | :target: https://coveralls.io/github/Edinburgh-Genome-Foundry/blabel?branch=master 16 | 17 | 18 | Blabel is a Python package to generate labels (typically for printing stickers) 19 | with barcodes and other niceties. 20 | 21 | **Some features:** 22 | 23 | - Generates PDF files where each page is a label (that's the way most label printers want it). 24 | - Label layout is defined by HTML (Jinja) templates and CSS. Supports any page dimensions and margins. 25 | - Builtin support for various barcodes, QR-codes, datamatrix, and more (wraps other libraries). 26 | - Labels data can be provided as list of dicts (easy to generate from spreadsheets). 27 | - Possibility to print several items per sticker. 28 | 29 | .. raw:: html 30 | 31 |

32 | 33 |

34 |

35 | 36 | 37 | Example 38 | ------- 39 | 40 | To generate labels with Blabel you first need a HTML/Jinja template, and 41 | optionally a style sheet, to define how your labels will look like. 42 | 43 | .. raw:: html 44 | 45 |

46 | 47 | **HTML item template** (``item_template.html``) 48 | 49 | Notice the use of ``label_tools`` (Blabel's builtin features). The variables 50 | ``sample_name`` and ``sample_id`` will be defined at label creation time. 51 | 52 | .. code:: html 53 | 54 | 55 | 56 | {{ sample_name }}
57 | Made with blabel
58 | {{ label_tools.now() }} 59 |
60 | 61 | .. raw:: html 62 | 63 |

64 | 65 | **CSS stylesheet** (``style.css``) 66 | 67 | Notice the CSS ``@page`` attributes which allows you to adjust the page format 68 | to the dimensions of your sticker. 69 | Also notice the ``pixelated`` image rendering. If your printer is black/white 70 | only with no greyscale support, this option will ensure crisp-looking barcodes, 71 | qr codes, etc. 72 | 73 | .. code:: css 74 | 75 | @page { 76 | width: 27mm; 77 | height: 7mm; 78 | padding: 0.5mm; 79 | } 80 | img { 81 | height: 6.4mm; 82 | display: inline-block; 83 | vertical-align: middle; 84 | image-rendering: pixelated; 85 | } 86 | .label { 87 | font-family: Verdana; 88 | font-weight: bold; 89 | vertical-align: middle; 90 | display: inline-block; 91 | font-size: 7px; 92 | } 93 | 94 | .. raw:: html 95 | 96 |

97 | 98 | **Python code** 99 | 100 | In your Python script, create a ``LabelWriter`` linked to the two files above, 101 | and feed it a list of of dicts ("records"), one for each label to print: 102 | 103 | 104 | .. code:: python 105 | 106 | from blabel import LabelWriter 107 | 108 | label_writer = LabelWriter("item_template.html", 109 | default_stylesheets=("style.css",)) 110 | records= [ 111 | dict(sample_id="s01", sample_name="Sample 1"), 112 | dict(sample_id="s02", sample_name="Sample 2") 113 | ] 114 | 115 | label_writer.write_labels(records, target='qrcode_and_label.pdf') 116 | 117 | .. raw:: html 118 | 119 |

120 | 121 | **Result:** 122 | 123 | .. raw:: html 124 | 125 |

126 | Blabel Logo 127 |

128 |

129 | 130 | 131 | Other examples 132 | -------------- 133 | 134 | - `Example with a barcode and a dynamically generated picture `_ 135 | - `Ugly example with a logo and a datamatrix `_ 136 | - `Example with date and QR code (sources of the example above) `_ 137 | - `Example where the label data is read from spreadsheets `_ 138 | - `Example where several items are printed on each page/sticker `_ 139 | 140 | 141 | Installation 142 | ------------ 143 | 144 | You can install Blabel via PIP: 145 | 146 | .. code:: 147 | 148 | pip install blabel 149 | 150 | 151 | **Note:** the package depends on the WeasyPrint Python package. If there are any issues, 152 | see installation instructions in the `WeasyPrint documentation `_. 153 | 154 | If you have an older GNU/Linux distribution (e.g. Ubuntu 18.04), then install an older WeasyPrint (<=52), 155 | as they don't have the latest Pango that is required by the latest WeasyPrint: ``pip install weasyprint==52`` 156 | 157 | 158 | **Note: on macOS**, you may need to first install pango with ``brew install pango``. 159 | 160 | **Note: on some Debian systems** you may need to first install libffi-dev (``apt install libffi-dev``). 161 | The package name may be libffi-devel on some systems. 162 | 163 | 164 | License = MIT 165 | ------------- 166 | 167 | Blabel is an open-source software originally written at the `Edinburgh Genome Foundry 168 | `_ by `Zulko `_ 169 | and `released on Github `_ under the MIT licence 170 | (Copyright 2018 Edinburgh Genome Foundry, University of Edinburgh). Everyone is welcome to contribute! 171 | 172 | 173 | More biology software 174 | --------------------- 175 | 176 | .. image:: https://raw.githubusercontent.com/Edinburgh-Genome-Foundry/Edinburgh-Genome-Foundry.github.io/master/static/imgs/logos/egf-codon-horizontal.png 177 | :target: https://edinburgh-genome-foundry.github.io/ 178 | 179 | Blabel was originally written to print labels for biological samples and is part of the `EGF Codons `_ 180 | synthetic biology software suite for DNA design, manufacturing and validation. 181 | --------------------------------------------------------------------------------