Charlotte, NC
ProjectsJune 30, 2026

Rollo Label Printer

Tap image to enlarge
This started with a tiny annoyance that kept stealing more time than it deserved. I have a 4x6 Rollo thermal label printer, which is great once you have a clean 4x6 label. The annoying part is everything before that. A return portal gives you a full-page PDF with instructions, an authorization slip, a QR code, and somewhere in the middle of it all, the actual shipping label. Then you download it, open it, crop the label by hand, rotate it if needed, export it, and finally print. Doing that once is fine. Doing it every time I return something is just enough friction to bother me. I'm sure there are tools that solve parts of this. I did not build this because the world needed another label-printing app. I built it because the problem was small, repetitive, and specific enough that a custom tool would fit my workflow better than whatever generic option I could find. It also gave me a good excuse to build a little document-processing pipeline where the failure modes matter. Shipping labels are deceptively messy inputs. They look simple because the final thing is just a rectangle with a barcode. But the source documents are inconsistent:
  • Some vendors embed the label as a separate image inside the PDF
  • Some draw the label with vector boxes and text
  • Some put the label beside instructions or a return authorization page
  • Some include QR codes that are not shipping labels at all
  • Some labels are sideways, scaled oddly, or surrounded by whitespace
  • Some documents have medical, invoice, or receipt-shaped pages that look label-like if you only score geometry
The last point matters. A bad crop is not just ugly; it can print the wrong thing. For a thermal printer sitting on my desk, I want the tool to be conservative. If it is not sure it found a carrier shipping label, I would rather review it than have it guess. The app is a small FastAPI service with a drag-and-drop browser UI. You upload a PDF or image, it finds the best candidate label, renders a normalized 4x6 PDF and PNG, and shows a preview before printing. The detector runs in layers:
  1. Look for embedded PDF images with a 4x6-ish aspect ratio
  2. Look for vector rectangles that resemble generated label boxes
  3. Render the page and find dense label-like regions
  4. Decode barcodes and identify carrier tracking numbers when possible
  5. Ask Gemini through LiteLLM only when the deterministic result is missing barcode-like shipping evidence or falls below the confidence threshold
That layered approach keeps the common path fast. Most clean return PDFs never need AI. The AI path is there for ambiguous documents, and even then it only returns a bounding box and orientation. The service still renders, crops, normalizes, stores, and prints locally. The print threshold is intentionally separate from the preview threshold. A low-confidence crop can still be useful as a preview, but it should not go straight to the printer. The current flow is:
  • High-confidence label with shipping evidence: show preview and allow normal print
  • Ambiguous label: show preview, but require an explicit "print anyway" style confirmation
  • No convincing label: reject the upload instead of manufacturing a fake 4x6 output
This tightened up after real use. One early version was too willing to accept any 4x6-shaped block. That worked until a patient/lab-style document looked geometrically close enough to a label. The fix was to make barcode-like shipping evidence and AI confirmation part of the gate, not just rectangle detection. Barcode parsing got its own small lesson too. A UPS label can include a numeric routing barcode before the real 1Z... tracking number. The first implementation matched barcode values in decode order and misidentified one of those routing values as FedEx. The current carrier matcher scans all decoded values by carrier priority, so the real UPS tracking barcode wins over loose numeric fallbacks. Printing is direct IPP to the Rollo X1040. I originally expected CUPS lp to be enough, but the printer advertises both IPP and IPPS URIs in a way that made the container's CUPS client build a bad comma-separated printer URI. Direct ipptool ended up being more reliable. There was another small printer-specific detail: the Rollo reports custom_4x6_4x6in as the ready media, but rejects that keyword when submitted as a normal job option. The app sends a 4x6 media-col ticket instead, which the printer accepts. After submission, the service polls the IPP job state and only reports a job as printed once the printer says it completed. If the job is aborted, canceled, or never confirms within the timeout, the UI shows that instead of pretending everything worked. The UI is intentionally boring in the best way:
  • Drag a PDF, PNG, JPG, or HEIC anywhere onto the page
  • See the extracted 4x6 preview before printing
  • Download the generated PDF or PNG
  • Review alternate candidate crops when a document is ambiguous
  • Batch several ready labels and print them together
  • Reopen recent jobs from history
  • Copy or open carrier tracking links when barcode decoding finds them
The app sits behind my normal access boundary rather than carrying a full auth system itself. Runtime label artifacts are short-lived because they can contain home addresses, tracking numbers, and return authorization details. The useful part was not the first happy path. Detecting a clean embedded label is straightforward. The value came from all the little edges that only showed up once I used it:
  • A full-page return PDF where the label is embedded sideways
  • A disclosure page that looks label-shaped but is not a label
  • A QR-only return code that should not be treated as shipping evidence
  • A printer that validates one media name but accepts another job ticket shape
  • A barcode decoder that sees routing data before the actual tracking number
  • A batch print UI that needs to report partial failures honestly
Those are exactly the kinds of details that make small personal tools feel finished. The code is not large, but it has the right amount of paranoia for the job.
  • Python 3.11+ with FastAPI for the upload, preview, job history, and print API
  • PyMuPDF, Pillow, and NumPy for PDF rendering, image processing, and 4x6 normalization
  • zxing-cpp for barcode decoding and carrier/tracking extraction
  • LiteLLM + Gemini 3.5 Flash for bounded vision fallback on ambiguous documents
  • ipptool / IPP for direct Rollo X1040 printing and job-state confirmation
  • Vanilla HTML/CSS/JS for the no-build-step web UI
  • Kubernetes, Envoy Gateway, Cloudflare Access, and GHCR for my homelab deployment

Related projects

blakemccarn.dev

Portfolio and blog built with Next.js 16, deployed to AWS via SST v4 with Cloudflare DNS, staging environments, and full IaC.

Charlotte Wire & Cable

Full-stack business website for a specialty wire distributor: searchable catalog, admin dashboard, and serverless AWS infrastructure via SST.

Paperless OCR Enhanced

Open-source Paperless-ngx companion service that uses LLM vision models to repair weak OCR and improve downstream document search.

Paperless Knowledge Graph

Document intelligence system that combines Paperless-ngx, Neo4j, pgvector, Strands Agents, cited answers, and an interactive graph UI.

RapidEPR

Founded and built an AI SaaS product that helps service members across five military services write evaluations, performance statements, and award narratives.