SkillAgentSearch skills...

Estrella

A print server for the StarPRNT protocol

Install / Use

/learn @eljojo/Estrella

README

⭐️ estrella

A Rust library for Star Micronics TSP650II thermal receipt printers over Bluetooth. Implements the StarPRNT protocol with a React-inspired component system, an optimizing compiler, and a web UI for photo printing.

My Desk

Photo Printing

The web UI supports printing photos with real-time dithered preview:

  • Formats: JPEG, PNG, GIF, WEBP, HEIC (iPhone photos — requires Nix build; .deb users convert to JPEG first)
  • Adjustments: Rotation, brightness, contrast
  • Dithering: Choose algorithm for best results
  • Auto-resize to 576px printer width
<img width="1125" height="1068" alt="Screenshot 2026-01-23 at 18 19 25" src="https://github.com/user-attachments/assets/d8e7779d-7940-47c6-a304-4fc6c7b2992e" />

Image Downloads

Documents can include images from URLs. When a document is submitted via the JSON API, images are automatically downloaded, cached, resized, and dithered for printing.

{"type": "image", "url": "https://example.com/photo.jpg"}
  • Auto-resize: Images are scaled to the printer's full width (576 dots) preserving aspect ratio
  • Max height: Optional height field acts as a cap — if the resized image is taller, it shrinks to fit
  • Alignment: Images narrower than paper width are centered by default ("align": "center"). Also accepts "left" or "right"
  • Dithering: Defaults to Floyd-Steinberg. Set "dither" to "bayer", "atkinson", "jarvis", or "none"
  • Caching: Downloaded images are cached in memory and shared with photo sessions (30-min TTL), so previewing a document multiple times won't re-download
{
  "type": "image",
  "url": "https://example.com/photo.jpg",
  "width": 400,
  "height": 300,
  "align": "center",
  "dither": "atkinson"
}

The Document System

Instead of manually constructing printer escape sequences, Estrella provides a declarative Document model. The same types work for both Rust construction and JSON deserialization — one set of types, zero conversion layer.

use estrella::document::*;

let doc = Document {
    document: vec![
        Component::Banner(Banner::new("CHURRA MART")),
        Component::Text(Text { content: "2026-01-20 12:00:00".into(), center: true, ..Default::default() }),
        Component::Spacer(Spacer::mm(3.0)),
        Component::Banner(Banner { content: "TODAY ONLY: 50% OFF".into(), border: BorderStyle::Double, size: 2, ..Default::default() }),
        Component::Divider(Divider::default()),
        Component::LineItem(LineItem::new("Espresso", 4.50)),
        Component::LineItem(LineItem::new("Croissant", 3.25)),
        Component::Divider(Divider::default()),
        Component::Total(Total { amount: 7.75, bold: Some(true), double_width: true, ..Default::default() }),
        Component::Spacer(Spacer::mm(3.0)),
        Component::QrCode(QrCode { data: "https://example.com/rewards".into(), cell_size: Some(6), ..Default::default() }),
        Component::Text(Text { content: "Thank you!".into(), center: true, bold: true, ..Default::default() }),
    ],
    cut: true,
    ..Default::default()
};

let bytes = doc.build();                     // StarPRNT bytes, ready to send
let json = serde_json::to_string(&doc)?;     // Same type serializes to JSON

Demo Receipt

Components

| Component | Description | |-----------|-------------| | Text | Styled text (bold, center, invert, size 0–3, optional font: "ibm") | | Header | Pre-styled centered bold header | | Banner | Framed text with box-drawing borders, auto-sizing (optional font: "ibm") | | LineItem | Left name + right price (e.g., "Coffee" ... "$4.50") | | Total | Right-aligned total line | | Divider | Horizontal line (dashed, solid, double, equals) | | Spacer | Vertical space in mm, lines, or raw units | | Columns | Two-column layout (left + right) | | Table | Table with box-drawing borders, headers, per-column alignment | | Markdown | Rich text from Markdown (headings, bold, lists) | | Image | Image from URL (downloaded, cached, dithered, auto-centered) | | Pattern | Generative art pattern with params | | Canvas | Absolute-positioned raster compositing with blend modes | | QrCode, Pdf417, Barcode | 1D and 2D barcodes | | NvLogo | Logo from printer's flash memory |

Dithering Algorithms

Thermal printers are binary (black or white), so grayscale images need dithering. Estrella implements four algorithms:

| Algorithm | Characteristics | |-----------|-----------------| | Floyd-Steinberg | Classic error diffusion. Smooth gradients, organic look. Default for photos. | | Atkinson | Bill Atkinson's Mac algorithm. Higher contrast, loses 25% of error intentionally. | | Jarvis | Spreads error over 12 neighbors. Smoothest gradients, slightly slower. | | Bayer | Ordered 8x8 matrix. Fast, deterministic, halftone pattern. Best for patterns. |

| Floyd-Steinberg | Atkinson | Jarvis | Bayer | |-----------------|----------|--------|-------| | Floyd-Steinberg | Atkinson | Jarvis | Bayer |

Pattern Generation

The web ui allows to preview patterns

Procedural patterns for artistic prints and printer calibration. Each pattern has randomizable parameters.

| Ripple | Waves | Plasma | |:--:|:--:|:--:| | Ripple | Waves | Plasma |

<details> <summary>More patterns</summary>

Op Art

| Riley | Vasarely | Scintillate | Moire | |:--:|:--:|:--:|:--:| | Riley | Vasarely | Scintillate | Moire |

Organic

| Topography | Rings | Flowfield | Mycelium | |:--:|:--:|:--:|:--:| | Topography | Rings | Flowfield | Mycelium |

| Erosion | Crystal | Reaction Diffusion | Voronoi | |:--:|:--:|:--:|:--:| | Erosion | Crystal | Reaction Diffusion | Voronoi |

Glitch

| Glitch | Corrupt Barcode | Databend | Scanline Tear | |:--:|:--:|:--:|:--:| | Glitch | Corrupt Barcode | Databend | Scanline Tear |

Generative

| Attractor | Automata | Estrella | |:--:|:--:|:--:| | Attractor | Automata | Estrella |

Textures

| Crosshatch | Stipple | Woodgrain | Weave | |:--:|:--:|:--:|:--:| | Crosshatch | Stipple | Woodgrain | Weave |

Calibration

| Calibration | Microfeed | Density | Jitter | |:--:|:--:|:--:|:--:| | Calibration | Microfeed | Density | Jitter |

| Overburn | |:--:| | Overburn |

</details>

Pattern Weaving

Blend multiple patterns with DJ-style crossfade transitions:

estrella weave ripple plasma waves --length 200mm --crossfade 30mm

Weave Crossfade

JSON API

The JSON API uses the same Document type as the Rust API — the component structs are all Serialize + Deserialize, so JSON documents map directly to Rust types with zero conversion. Useful for automations (e.g. Home Assistant daily briefings).

curl -X POST http://localhost:8080/api/json/print \
  -H 'Content-Type: application/json' \
  -d '{
    "document": [
      {"type": "banner", "content": "GOOD MORNING"},
      {"type": "text", "content": "Monday, January 27", "center": true, "size": 0},
      {"type": "divider", "style": "double"},
      {"type": "text", "content": " WEATHER ", "bold": true, "invert": true},
      {"type": "columns", "left": "Now", "right": "6°C Cloudy"},
      {"type": "columns", "left": "High / Low", "right": "11°C / 3°C"},
      {"type": "divider"},
      {"type": "text", "content": " CALENDAR ", "bold": true, "invert": true},
      {"type": "columns", "left": "9:00", "right": "Standup"},
      {"type": "columns", "left": "11:30", "right": "Dentist"},
      {"type": "divider"},
      {"type": "qr_code", "data": "https://calendar.google.com"}
    ],
    "cut": true
  }'

The web UI includes a JSON API tab with a live preview editor and a sample daily briefing template.

Canvas components support absolute-positioned compositing with blend modes:

{
  "type": "canvas",
  "height": 100,
  "elements": [
    {"type": "pattern", "name": "estrella", "height": 80, "position": {"x": -43, "y": 0}, "blend_mode": "add"},
    {"type": "text", "content": "Hello World", "center": true, "position": {"x": 7, "y": 16}, "blend_mode": "add"},
    {"type": "total", "amount": 0, "position": {"x": 0, "y": 34}, "blend_mode": "add"}
  ]
}

Elements without position stack top-to-bottom (flow mode). Dithering defaults to "auto" — Atkinson when continuous-tone content is detected, none otherwise.

Endpoints:

  • POST /api/json/preview — returns a PNG preview
  • POST /api/json/print — sends to printer
<details> <summary>Full component reference</summary>

Each component in the "document" array has a "type" f

View on GitHub
GitHub Stars93
CategoryDevelopment
Updated1d ago
Forks1

Languages

Rust

Security Score

85/100

Audited on Mar 30, 2026

No findings