<?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: Lizely</title>
    <description>The latest articles on DEV Community by Lizely (lizely).</description>
    <link>https://dev.to/lizely</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%2Forganization%2Fprofile_image%2F14136%2F2180ee67-b4ee-449c-bfdd-3ce4e05b41ef.png</url>
      <title>DEV Community: Lizely</title>
      <link>https://dev.to/lizely</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lizely"/>
    <language>en</language>
    <item>
      <title>Embedding JPG Snapshots in a Production PDF Pipeline Without Breaking a11y or Print Layout</title>
      <dc:creator>Tea-sip</dc:creator>
      <pubDate>Sat, 25 Jul 2026 16:05:59 +0000</pubDate>
      <link>https://dev.to/lizely/embedding-jpg-snapshots-in-a-production-pdf-pipeline-without-breaking-a11y-or-print-layout-4bf5</link>
      <guid>https://dev.to/lizely/embedding-jpg-snapshots-in-a-production-pdf-pipeline-without-breaking-a11y-or-print-layout-4bf5</guid>
      <description>&lt;p&gt;You already have an automated report pipeline that emits PDF invoices, contracts, or inspections. One day product asks: "Can we also drop in the photographer's JPG from the site visit, or the customer's signature image?" The conversion is trivial. The blast radius is not. A naive "merge the JPG into the PDF" solution leaks through to receipts, screen readers, and print drivers in ways that only show up weeks later.&lt;/p&gt;

&lt;p&gt;This article is for the engineer who has to plug that requirement in today, not the document specialist who gets a week to plan it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Problem Is Not "JPG to PDF"
&lt;/h2&gt;

&lt;p&gt;Calling this a "JPG to PDF conversion" is the first framing mistake. Almost no real product asks the user to upload JPGs and download a PDF. They ask for a PDF &lt;em&gt;with&lt;/em&gt; JPG content embedded into an existing document, at a specific point in the layout, sized to a specific box, addressed at a specific audience. The conversion is a side-effect, not the deliverable.&lt;/p&gt;

&lt;p&gt;In a production pipeline you generally face one of three flavors:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Inline figure inside a templated report.&lt;/strong&gt; A Jinja, LaTeX, or Headless Chrome template renders a PDF, and you need to splice an image into page N at coordinates (x, y, w, h).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bulk ingestion of scanned attachments.&lt;/strong&gt; A back office uploads 200 photos per batch and expects a single PDF per case file for archival, with page numbers and a manifest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Programmatic PDF/A for record-keeping.&lt;/strong&gt; Long-term storage of evidence or signed forms requires PDF/A conformance, ICC profiles, and sometimes XMP metadata.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each flavor has different failure modes, even though the file type looks the same. Conflating them is how teams ship a "working" feature that later fails a regulator's inspection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which Library or Pipeline Actually Fits
&lt;/h2&gt;

&lt;p&gt;For (1), most reporting stacks already have a rendering engine. If you are using Headless Chrome via Puppeteer or Playwright, you can simply emit an HTML document with &lt;code&gt;&amp;lt;img src="https://cdn/.../photo.jpg"&amp;gt;&lt;/code&gt; and let the renderer place it. This preserves your existing typography and theming for free, which is the right default. The MDN reference for &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img" rel="noopener noreferrer"&gt;the HTML image element&lt;/a&gt; is worth bookmarking because browser PDF backends honor &lt;code&gt;width&lt;/code&gt;, &lt;code&gt;height&lt;/code&gt;, &lt;code&gt;object-fit&lt;/code&gt;, and &lt;code&gt;loading="lazy"&lt;/code&gt; in slightly idiosyncratic ways that differ from screen rendering.&lt;/p&gt;

&lt;p&gt;If the report is LaTeX (common for legal or academic PDFs), &lt;code&gt;graphicx&lt;/code&gt; with &lt;code&gt;\includegraphics[width=...]{photo.jpg}&lt;/code&gt; is the conventional path. Do not decode and re-encode the JPG to base64 inside the template; templating engines routinely truncate or rewrite long base64 strings, and the resulting PDF will silently embed a partial image.&lt;/p&gt;

&lt;p&gt;For (2), bulk ingestion, a server-side library is safer than a browser automation loop. In Node, &lt;code&gt;pdfkit&lt;/code&gt; is a stable choice: you stream the JPG directly via &lt;code&gt;image()&lt;/code&gt; and call &lt;code&gt;addPage()&lt;/code&gt; per attachment, which keeps memory bounded. In Python, &lt;code&gt;reportlab&lt;/code&gt;'s &lt;code&gt;flowables&lt;/code&gt; let you place images inline with text wrapping, which is what you usually want for "photo + caption + next photo." Both are well-suited for batch work because they do not require a display server.&lt;/p&gt;

&lt;p&gt;For (3), PDF/A is a separate problem. You will need an external conformance checker such as &lt;code&gt;verapdf&lt;/code&gt; or &lt;code&gt;PDFA-PIL&lt;/code&gt; — the &lt;a href="https://en.wikipedia.org/wiki/PDF/A" rel="noopener noreferrer"&gt;PDF/A specification on Wikipedia&lt;/a&gt; lists the profile levels and what they forbid. In practice the rule that bites most teams is font embedding: if your templating engine uses a webfont served from a CDN, the PDF will fail PDF/A validation because the font cannot be embedded legally. Plan a fallback font stack before you discover this in QA.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Output Looks Fine — Until You Check the Details
&lt;/h2&gt;

&lt;p&gt;A few decisions look unimportant during the happy path and turn into tickets later:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resolution vs. file size.&lt;/strong&gt; A 12 MP phone JPG at full size inside a 4×3 inch print slot wastes roughly 11 MB per page and slows every downstream print spool. I default to long-edge 1600 px, JPEG q=82, which produces a 300–500 KB page image that prints crisply at 300 DPI. This is the same trade-off discussed in the &lt;a href="https://en.wikipedia.org/wiki/JPEG" rel="noopener noreferrer"&gt;JPEG specification on Wikipedia&lt;/a&gt;: quality 80–85 is the perceptual knee where file size drops sharply without visible loss for document photography.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Color management.&lt;/strong&gt; Screenshots and product photos generally come in sRGB. Office printers and many archival workflows default to CMYK. If you do not embed an ICC profile, the print output is unpredictable; if you naively convert to CMYK at the same time, dark UI screenshots get crushed. For most document pipelines, embed the sRGB profile (&lt;code&gt;sRGB IEC61966-2.1&lt;/code&gt;) and let the printer translate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accessibility.&lt;/strong&gt; Alt text and tagged structure do not exist by default in most PDF libraries. Screen readers like JAWS and NVDA read PDFs that follow the structure tree defined in &lt;a href="https://www.w3.org/TR/WCAG-TECHS/pdf.html" rel="noopener noreferrer"&gt;PDF 1.7 specification, §14.7 (Tagged PDF)&lt;/a&gt;, which is referenced by WCAG. If your PDF is delivered as a public artifact, add an alt description to each image as part of the generation step. &lt;code&gt;pdfkit&lt;/code&gt;'s &lt;code&gt;markContent&lt;/code&gt; and &lt;code&gt;reportlab&lt;/code&gt;'s &lt;code&gt;canv.setTitle&lt;/code&gt; plus flowable &lt;code&gt;setLang&lt;/code&gt; are a starting point, but full tagged PDF usually requires &lt;code&gt;Apache PDFBox&lt;/code&gt; or a commercial SDK.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Metadata hygiene.&lt;/strong&gt; Strip GPS coordinates from phone photos before embedding. Most report platforms have no business handling user location. &lt;code&gt;exiftool -all= photo.jpg&lt;/code&gt; is the one-liner that satisfies your DPO and your QA lead in the same beat.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Practical Checklist for the Code Review
&lt;/h2&gt;

&lt;p&gt;When a PR opens that adds image embedding, this is the list I run before merging. It catches roughly 80% of post-merge bugs.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Confirm the image source is a CDN URL or a sanitized upload path — never raw user input pasted into a template.&lt;/li&gt;
&lt;li&gt;Verify the rendered box has explicit &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; (or equivalent) so layout does not reflow on slow connections.&lt;/li&gt;
&lt;li&gt;Down-sample to long-edge 1600 px, q=82, with &lt;code&gt;sharp&lt;/code&gt;, &lt;code&gt;ImageMagick&lt;/code&gt;, or Pillow, and store the optimized asset alongside the original.&lt;/li&gt;
&lt;li&gt;Strip EXIF with &lt;code&gt;exiftool -overwrite_original -all= path/to/upload.jpg&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Embed an sRGB ICC profile; if the deliverable is PDF/A, verify conformance in CI with &lt;code&gt;verapdf&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add an alt string to each embedded image. Even a generic "Site visit photo, 2024-03-12" is better than none.&lt;/li&gt;
&lt;li&gt;Run an automated check that the final PDF file size is below an agreed per-page ceiling (e.g. 1 MB) — it surfaces accidental 4K uploads.&lt;/li&gt;
&lt;li&gt;Generate at least one output with assistive tooling and confirm a screen reader announces the image alt and the document title.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You do not need every step on day one. Skipping items 3 and 5 is fine for an internal-only dashboard; skipping 6 and 8 is rarely acceptable for customer-facing PDFs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Operational Trade-offs You Will Hit
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Synchronous vs. queue-driven.&lt;/strong&gt; If you render a PDF synchronously inside a web request, an extra 800 KB image can push latency past your SLO. Move image optimization to a worker queue and render the PDF with already-optimized assets. This also lets you reuse a normalized thumbnail across other surfaces (emails, previews).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caching and idempotency.&lt;/strong&gt; Hashing the input JPG (SHA-256 of bytes) and using that as the asset key means a re-upload of the same photo does not trigger re-optimization, and the PDF rebuild is purely deterministic. This matters for invoices and signed contracts where "the same input produces the same output" is a regulatory expectation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reverse proxies and content sniffing.&lt;/strong&gt; Some pipelines proxy image URLs through Cloudflare or a corporate gateway that re-encodes JPEGs. If your PDF renderer fetches the URL at request time, you may get a different file than the one you optimized. Either inline the bytes or store them in object storage with content-addressed keys.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Browser vs. server rendering.&lt;/strong&gt; Browser-based PDF rendering is convenient and ugly for Q/A: the PDF output differs slightly per Chrome version, and headless Chrome on Linux tends to embed fonts that fail PDF/A validators on macOS. For anything compliance-bound, a server library is the more boring, more correct choice.&lt;/p&gt;

&lt;p&gt;For a quick one-off conversion — for example, attaching a handful of JPGs to a one-page memo for a meeting in five minutes — a browser tool is fine and you can follow the in-house walkthrough on &lt;a href="https://www.lizecheng.net/pdf/guides/turn-jpgs-into-a-single-pdf-in-your-browser/" rel="noopener noreferrer"&gt;turning JPGs into a single PDF in your browser&lt;/a&gt;. Just do not let "the browser path worked once" become the design of the production system.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Push Back on the Requirement
&lt;/h2&gt;

&lt;p&gt;Sometimes the right answer is to refuse to bundle images into the PDF at all. If the consumer is going to print on a black-and-white office laser printer, attach the JPGs as a &lt;code&gt;.zip&lt;/code&gt; and put a single index page in the PDF — the photos will be unreadable printed anyway, and your PDF stays small and archivable. If the consumer is going to email the PDF, most email gateways already strip or compress inline images aggressively; a &lt;code&gt;.zip&lt;/code&gt; is more reliable. If the consumer is internal-only and the images are on a shared drive, put them in the right folder and skip the PDF entirely.&lt;/p&gt;

&lt;p&gt;Knowing when the requirement is wrong is part of owning the pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Should I store images as base64 inside HTML templates?
&lt;/h3&gt;

&lt;p&gt;No. Templating engines have truncation and escaping rules that can silently corrupt long base64 strings, producing partial images with no error. Store images on a CDN or S3, reference them by URL, and let the renderer fetch them at PDF-generation time.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the safest single library choice across Node and Python stacks?
&lt;/h3&gt;

&lt;p&gt;For server-side bulk work, &lt;code&gt;pdfkit&lt;/code&gt; in Node and &lt;code&gt;reportlab&lt;/code&gt; in Python are both stable, well-documented, and avoid the font-embedding and version-drift issues of headless-browser rendering. For inline images inside a templated PDF already produced by Headless Chrome, an &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tag in the HTML is simpler and correct.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I verify a generated PDF is accessible?
&lt;/h3&gt;

&lt;p&gt;Run it through &lt;code&gt;verapdf&lt;/code&gt; for PDF/A conformance and through a screen reader such as NVDA. The cheapest CI check is &lt;code&gt;verapdf --flavour 1b report.pdf&lt;/code&gt;; if it passes and your alt strings are present, you are meeting the bulk of practical accessibility expectations.&lt;/p&gt;

&lt;h3&gt;
  
  
  When is a browser-based JPG-to-PDF tool acceptable in a production-adjacent workflow?
&lt;/h3&gt;

&lt;p&gt;For ad-hoc, manual, low-volume tasks where the output is not archived or audited — for example, a one-off memo or a meeting handout. For anything batched, customer-facing, or retention-bound, use a server-side library so output is deterministic, metadata is controlled, and auditing is possible.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was drafted with AI assistance and reviewed for technical accuracy before publishing.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>lizely</category>
      <category>pdf</category>
      <category>webdev</category>
      <category>aibotwrotethis</category>
    </item>
  </channel>
</rss>
