Rendering

PIL-based rendering pipelines for the GoojPrt PT-210.

Every renderer in this package is pure: it takes text/data and a handful of style parameters and returns a 1-bit PIL.Image.Image ready to be passed to goojprt.raster.image_to_raster(). No renderer touches the transport layer.

Pillow is imported lazily inside the individual renderers so that the rest of the SDK can be used on installations without Pillow.

goojprt.rendering.render_ekg(beats: int = 4, height_px: int = 160, line_width: int = 2, grid: bool = True, grid_step_px: int = 32, amplitude: float = 0.82, portrait: bool = False, px_per_beat: int = 240)[source]

Render a synthetic ECG strip.

Parameters:
  • beats – Number of consecutive heartbeats on the strip.

  • height_px – Landscape only — strip height in pixels. Ignored in portrait mode (height becomes beats × px_per_beat).

  • line_width – Curve thickness, 1–3 pixels.

  • grid – Draw the standard dashed ECG grid.

  • grid_step_px – Grid spacing in pixels (32 ≈ 4.7 mm at 203 DPI).

  • amplitude – Relative height of the R-wave, 0.0–1.0.

  • portrait – Orientation selector (see module docstring).

  • px_per_beat – Portrait only — pixels per heartbeat along the paper. More pixels means a more detailed curve.

Returns:

PIL image in mode "1".

goojprt.rendering.render_grid(columns: list[dict], font_size: int = 22, font_path: str | None = None, padding: int = 6, supersample: int = 3, dither: bool = False, threshold: int = 128)[source]

Render a single row of a multi-column table as a 1-bit image.

Each column is rendered into its own sub-canvas; text that overflows the column width is cropped automatically. Column widths are relative — they do not need to sum to 100; they are re-normalised to match the print head width.

Each entry in columns is a mapping with the following keys:

  • width (int) — relative column width (e.g. 40, 30, 30).

  • align (str) — "left" / "center" / "right" (default "left").

  • text (str) — cell content.

Example TOML snippet consumed by goojprt.template:

[[items]]
type = "grid"
font_size = 22
dither = false
columns = [
  {width = 40, align = "left",  text = "1 Kč"},
  {width = 30, align = "right", text = "5 ks"},
  {width = 30, align = "right", text = "5 Kč"},
]
Parameters:
  • columns – Column definitions (see above).

  • font_size – Font size in points.

  • font_path – Optional .ttf path; falls back to find_system_font().

  • padding – Inner cell padding in pixels (pre-supersample).

  • supersample – Oversampling factor for antialiasing.

  • dither – Use Floyd–Steinberg (True) or threshold (False) during 1-bit conversion.

  • threshold – Threshold for the non-dithered path (0–255).

Returns:

PIL image in mode "1".

goojprt.rendering.render_pdf417(data: str, scale: int = 2, row_height: int = 5, columns: int = 5, padding: int = 10, min_rows: int | None = None)[source]

Render a PDF417 barcode as a monochrome PIL image.

Parameters:
  • data – The text to encode.

  • scale – Width of a single module in pixels.

  • row_height – Height of each row in modules (default 5 gives a reasonably legible barcode; values below 3 are usually too thin to scan).

  • columns – Number of data columns, 1–30. Fewer columns produce more rows.

  • padding – White quiet zone in pixels.

  • min_rows – Minimum number of rows. When the produced barcode has fewer rows, the number of columns is decreased until the constraint is satisfied (or columns reaches 1).

Returns:

PIL image in mode "1".

Requires pdf417 and PIL (pip install pdf417 pillow).

goojprt.rendering.render_text_image(text: str, font_size: int = 24, font_path: str | None = None, line_spacing: int = 6, padding: int = 8, align: Align | None = None, supersample: int = 3, dither: bool = True, threshold: int = 140, sharpen: bool = False, sharpen_radius: float = 1.5, sharpen_percent: int = 180, sharpen_threshold: int = 2)[source]

Render a multi-line string as a 1-bit image ready for the print head.

The pipeline has four stages:

  1. Supersampled rendering. The text is drawn into a grayscale canvas supersample times larger than the final output. PIL’s builtin antialiasing produces fully smooth diagonals with no staircasing.

  2. Downscale to paper width using LANCZOS resampling. The grayscale transitions carry the antialiasing information into the target resolution.

  3. Optional unsharp-mask sharpening. Applied before 1-bit conversion to boost perceived sharpness on thermal paper.

  4. 1-bit conversion suitable for the print head:

    • dither=True — Floyd–Steinberg error diffusion. Best choice for small / body text: the grayscale gradient is converted into a dot pattern that preserves the antialiased look.

    • dither=False — pure threshold. Perfectly crisp edges, ideal for large headlines and barcodes.

Parameters:
  • text – Multi-line input text (\n splits lines).

  • font_size – Font size in points (rendered at font_size × supersample).

  • font_path – Optional path to a .ttf / .ttc file. Falls back to find_system_font() when None.

  • line_spacing – Extra pixels between lines (pre-supersample).

  • padding – Outer canvas padding in pixels (pre-supersample).

  • align – Horizontal alignment (Align), default Align.LEFT.

  • supersample – Oversampling factor 1–4 (default 3, a good quality/speed trade-off).

  • ditherTrue for Floyd–Steinberg, False for threshold.

  • threshold – Threshold 0–255 for the dither=False path (lower = darker).

  • sharpen – Apply PIL.ImageFilter.UnsharpMask before 1-bit conversion.

  • sharpen_radius – Blur radius of the unsharp mask.

  • sharpen_percent – Unsharp-mask intensity in percent.

  • sharpen_threshold – Minimum brightness delta that triggers sharpening.

Returns:

PIL image in mode "1" with width PAPER_WIDTH_PX.

Text

Multi-line bitmap text rendering with antialiasing and optional sharpening.

goojprt.rendering.text.render_text_image(text: str, font_size: int = 24, font_path: str | None = None, line_spacing: int = 6, padding: int = 8, align: Align | None = None, supersample: int = 3, dither: bool = True, threshold: int = 140, sharpen: bool = False, sharpen_radius: float = 1.5, sharpen_percent: int = 180, sharpen_threshold: int = 2)[source]

Render a multi-line string as a 1-bit image ready for the print head.

The pipeline has four stages:

  1. Supersampled rendering. The text is drawn into a grayscale canvas supersample times larger than the final output. PIL’s builtin antialiasing produces fully smooth diagonals with no staircasing.

  2. Downscale to paper width using LANCZOS resampling. The grayscale transitions carry the antialiasing information into the target resolution.

  3. Optional unsharp-mask sharpening. Applied before 1-bit conversion to boost perceived sharpness on thermal paper.

  4. 1-bit conversion suitable for the print head:

    • dither=True — Floyd–Steinberg error diffusion. Best choice for small / body text: the grayscale gradient is converted into a dot pattern that preserves the antialiased look.

    • dither=False — pure threshold. Perfectly crisp edges, ideal for large headlines and barcodes.

Parameters:
  • text – Multi-line input text (\n splits lines).

  • font_size – Font size in points (rendered at font_size × supersample).

  • font_path – Optional path to a .ttf / .ttc file. Falls back to find_system_font() when None.

  • line_spacing – Extra pixels between lines (pre-supersample).

  • padding – Outer canvas padding in pixels (pre-supersample).

  • align – Horizontal alignment (Align), default Align.LEFT.

  • supersample – Oversampling factor 1–4 (default 3, a good quality/speed trade-off).

  • ditherTrue for Floyd–Steinberg, False for threshold.

  • threshold – Threshold 0–255 for the dither=False path (lower = darker).

  • sharpen – Apply PIL.ImageFilter.UnsharpMask before 1-bit conversion.

  • sharpen_radius – Blur radius of the unsharp mask.

  • sharpen_percent – Unsharp-mask intensity in percent.

  • sharpen_threshold – Minimum brightness delta that triggers sharpening.

Returns:

PIL image in mode "1" with width PAPER_WIDTH_PX.

Grid

Single-row multi-column (grid / table) renderer.

goojprt.rendering.grid.render_grid(columns: list[dict], font_size: int = 22, font_path: str | None = None, padding: int = 6, supersample: int = 3, dither: bool = False, threshold: int = 128)[source]

Render a single row of a multi-column table as a 1-bit image.

Each column is rendered into its own sub-canvas; text that overflows the column width is cropped automatically. Column widths are relative — they do not need to sum to 100; they are re-normalised to match the print head width.

Each entry in columns is a mapping with the following keys:

  • width (int) — relative column width (e.g. 40, 30, 30).

  • align (str) — "left" / "center" / "right" (default "left").

  • text (str) — cell content.

Example TOML snippet consumed by goojprt.template:

[[items]]
type = "grid"
font_size = 22
dither = false
columns = [
  {width = 40, align = "left",  text = "1 Kč"},
  {width = 30, align = "right", text = "5 ks"},
  {width = 30, align = "right", text = "5 Kč"},
]
Parameters:
  • columns – Column definitions (see above).

  • font_size – Font size in points.

  • font_path – Optional .ttf path; falls back to find_system_font().

  • padding – Inner cell padding in pixels (pre-supersample).

  • supersample – Oversampling factor for antialiasing.

  • dither – Use Floyd–Steinberg (True) or threshold (False) during 1-bit conversion.

  • threshold – Threshold for the non-dithered path (0–255).

Returns:

PIL image in mode "1".

PDF417

PDF417 2D barcode rendering (requires the optional pdf417 package).

goojprt.rendering.pdf417.render_pdf417(data: str, scale: int = 2, row_height: int = 5, columns: int = 5, padding: int = 10, min_rows: int | None = None)[source]

Render a PDF417 barcode as a monochrome PIL image.

Parameters:
  • data – The text to encode.

  • scale – Width of a single module in pixels.

  • row_height – Height of each row in modules (default 5 gives a reasonably legible barcode; values below 3 are usually too thin to scan).

  • columns – Number of data columns, 1–30. Fewer columns produce more rows.

  • padding – White quiet zone in pixels.

  • min_rows – Minimum number of rows. When the produced barcode has fewer rows, the number of columns is decreased until the constraint is satisfied (or columns reaches 1).

Returns:

PIL image in mode "1".

Requires pdf417 and PIL (pip install pdf417 pillow).

EKG

Synthetic ECG waveform generator rendered as a 1-bit bitmap.

The curve is built by summing a handful of Gaussians modelling the classic PQRST waves:

  • P — atrial depolarisation (small positive bump).

  • Q — start of ventricular depolarisation (small negative dip).

  • R — ventricular depolarisation (dominant spike).

  • S — late ventricular depolarisation (small negative dip).

  • T — ventricular repolarisation (medium positive bump).

Two orientations are available:

  • Landscape (portrait=False) — time runs across the paper width.

  • Portrait (portrait=True) — time runs down the paper, amplitude runs across the width, giving an unlimited-length recording.

goojprt.rendering.ekg.render_ekg(beats: int = 4, height_px: int = 160, line_width: int = 2, grid: bool = True, grid_step_px: int = 32, amplitude: float = 0.82, portrait: bool = False, px_per_beat: int = 240)[source]

Render a synthetic ECG strip.

Parameters:
  • beats – Number of consecutive heartbeats on the strip.

  • height_px – Landscape only — strip height in pixels. Ignored in portrait mode (height becomes beats × px_per_beat).

  • line_width – Curve thickness, 1–3 pixels.

  • grid – Draw the standard dashed ECG grid.

  • grid_step_px – Grid spacing in pixels (32 ≈ 4.7 mm at 203 DPI).

  • amplitude – Relative height of the R-wave, 0.0–1.0.

  • portrait – Orientation selector (see module docstring).

  • px_per_beat – Portrait only — pixels per heartbeat along the paper. More pixels means a more detailed curve.

Returns:

PIL image in mode "1".

Image Print

Pure image preparation pipeline for thermal printing.

goojprt.rendering.image_print.prepare_image(img, *, rotate: int = 0, crop: tuple[float, float, float, float] | None = None, scale: float = 1.0, brightness: float = 1.0, contrast: float = 1.0, threshold: int = 128, dither: bool = True, align: str = 'center')[source]

Prepare a PIL image for thermal printing.

Returns a mode-"1" image padded to PAPER_WIDTH_PX.

Pipeline: rotate → crop → resize → brightness → contrast → grayscale → threshold/dither → pad to paper width.