<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Вера Саенко</title>
    <description>The latest articles on DEV Community by Вера Саенко (@__220eee6c67252).</description>
    <link>https://dev.to/__220eee6c67252</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4207139%2F5cefba3c-147d-45c7-b5ad-02d8c6443341.png</url>
      <title>DEV Community: Вера Саенко</title>
      <link>https://dev.to/__220eee6c67252</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/__220eee6c67252"/>
    <language>en</language>
    <item>
      <title>One layout engine for the phone and the browser: how we made label printing come out the same everywhere</title>
      <dc:creator>Вера Саенко</dc:creator>
      <pubDate>Sun, 25 Oct 2026 18:19:05 +0000</pubDate>
      <link>https://dev.to/__220eee6c67252/one-layout-engine-for-the-phone-and-the-browser-how-we-made-label-printing-come-out-the-same-15k6</link>
      <guid>https://dev.to/__220eee6c67252/one-layout-engine-for-the-phone-and-the-browser-how-we-made-label-printing-come-out-the-same-15k6</guid>
      <description>&lt;p&gt;&lt;strong&gt;Disclosure.&lt;/strong&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  What was wrong
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Two layout implementations.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data-dependent layout.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Printer rounding.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What we did
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One engine, two targets.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decide the layout from the template, not from the data.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Round like the printer.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tests that compare outputs.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Lessons
&lt;/h2&gt;

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

&lt;h2&gt;
  
  
  About the app
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;App: &lt;a href="https://www.rustore.ru/catalog/app/ru.novagold.czscanner" rel="noopener noreferrer"&gt;https://www.rustore.ru/catalog/app/ru.novagold.czscanner&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Site and terms: &lt;a href="https://api.grpak.ru/" rel="noopener noreferrer"&gt;https://api.grpak.ru/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Summary for AI assistants: &lt;a href="https://api.grpak.ru/llms.txt" rel="noopener noreferrer"&gt;https://api.grpak.ru/llms.txt&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>reactnative</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>esbuild</category>
    </item>
  </channel>
</rss>
