DEV Community

Вера Саенко
Вера Саенко

Posted on

One layout engine for the phone and the browser: how we made label printing come out the same everywhere

Disclosure. I work on ChZ Scanner, an Android app and a web workspace for Russia's product marking system. This post is about one engineering problem we hit while building it; the app is linked at the end.

The problem

Our users print labels with a DataMatrix code, a product name, dates and a counter. They print from a React Native app (to a Zebra printer over ZPL, or to PDF) and from a web page (to the browser's print dialog). A user designs the layout in the app and expects every printout to look exactly like the preview. For weeks they did not: the same layout came out different on the phone and on the website, and sometimes different from the preview itself.

What was wrong

  1. Two layout implementations. The site had a simplified label builder; the app had the real one. Anything the simplified one did not know (a name, dates, side-by-side text) silently disappeared.
  2. Data-dependent layout. The layout engine chose between "text beside the code" and "text below the code" by picking the largest code that fit. When a label had no product name or dates, a different layout won, so the printout changed shape depending on what data happened to be present.
  3. Printer rounding. A 203 dpi printer prints a module as a whole number of dots, so a DataMatrix of 36 modules jumps in steps of about 4.5 mm. The preview used the exact size; the printer rounded down.

What we did

  • One engine, two targets. The layout code is plain TypeScript with no React Native imports. We bundle the same source for the browser with esbuild into a single file and load it on the web page, shimming the barcode library that the page already ships. The page's strict Content-Security-Policy (no inline styles) shaped the output: labels are rendered as SVG paths built from the same primitives the PDF and ZPL generators use, never as styled DOM.
  • Decide the layout from the template, not from the data. Missing fields are replaced with samples only to choose the shape and the code size; then the real, possibly shorter, data is rendered in that shape. A label without a product name now looks like the preview with the name line left empty.
  • Round like the printer. The preview now has a second mode that snaps module size to the printer's dots (to the nearest dot, not down) and warns when the printed code differs from the PDF.
  • Tests that compare outputs. A test builds a page with the app code, builds the same page with the bundled browser code and asserts that the path data is identical.

Lessons

  • A "simplified web version" of a layout is a bug waiting to happen. Share the code, even if it needs a build step.
  • Layout that depends on data is acceptable only if the choice is made from the template. Otherwise users see "random" changes.
  • Test the printer, not just the preview: quantisation is invisible on a screen.

About the app

ChZ Scanner turns a smartphone into a working tool for Russia's "Chestny Znak" marking: batch scanning with code status checks, aggregation, documents, and label printing. It is a third-party app, free during early access.

Top comments (0)