<?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: Kemal Deniz Teket</title>
    <description>The latest articles on DEV Community by Kemal Deniz Teket (@kadetr).</description>
    <link>https://dev.to/kadetr</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3877354%2Fa42364a2-0771-407c-82d9-16dab6185bab.png</url>
      <title>DEV Community: Kemal Deniz Teket</title>
      <link>https://dev.to/kadetr</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kadetr"/>
    <language>en</language>
    <item>
      <title>Towards an Open Source Print-Ready Publication Library in JavaScript</title>
      <dc:creator>Kemal Deniz Teket</dc:creator>
      <pubDate>Mon, 13 Apr 2026 21:47:44 +0000</pubDate>
      <link>https://dev.to/kadetr/towards-an-open-source-print-ready-publication-library-in-javascript-19ba</link>
      <guid>https://dev.to/kadetr/towards-an-open-source-print-ready-publication-library-in-javascript-19ba</guid>
      <description>&lt;p&gt;&lt;strong&gt;Building paragraf: a typesetter with industry-standard methods for print-ready publication quality documents in Node.js&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  §0 — Introduction
&lt;/h2&gt;

&lt;p&gt;When you open a professionally printed book, a luxury product catalog, or a pharmaceutical package insert, the text feels different. Words are evenly spaced. Paragraphs have a calm, consistent density. Punctuation sits exactly where it should. The page looks deliberate.&lt;/p&gt;

&lt;p&gt;When you generate the same content programmatically — using the JavaScript libraries available today — something is lost. Word spacing is uneven. Lines break at awkward places. Certain letter combinations look slightly wrong. The output is functional but it does not look typeset.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;paragraf&lt;/strong&gt; is an attempt to close that gap. 12 packages are complete, 3 are planned for the coming weeks. The typesetting core — line breaking, font shaping, optical margins, bidirectional text, hyphenation, styles, layout, and the compile pipeline — is production-ready. The print production layer — color management, color separations, and print-ready PDF output — and the visual editor layer are in progress. This article is an overview of what paragraf is, how it is built, and where it is going.&lt;/p&gt;




&lt;h2&gt;
  
  
  §1 — The Problem: Why does text look different in books vs documents generated by code?
&lt;/h2&gt;

&lt;p&gt;Professional typesetting solves several distinct problems simultaneously. Most JavaScript PDF pipelines solve none of them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Line Breaking
&lt;/h3&gt;

&lt;p&gt;The greedy algorithm — used by every JavaScript PDF library and every browser — fills each line as fully as possible and breaks there, making each decision in isolation. The Knuth-Plass algorithm, developed by Donald Knuth and Michael Plass in 1981 and used by TeX and Adobe InDesign since then, treats the entire paragraph as a single optimisation problem and finds the break sequence that minimises total spacing deviation across all lines simultaneously.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnnzapwfb48jtmx3c6n08.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnnzapwfb48jtmx3c6n08.png" alt="Knuth-Plass Algorithm" width="800" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The demo above shows the same text set with Knuth-Plass (left) and the greedy algorithm (right), at identical column width and font size.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Font Shaping
&lt;/h3&gt;

&lt;p&gt;Correct line breaking requires correct word widths. Correct word widths require processing the actual glyph outlines in the actual font file: rules that combine letter pairs into designed ligature forms (the Glyph Substitution, or &lt;a href="https://learn.microsoft.com/en-us/typography/opentype/spec/gsub" rel="noopener noreferrer"&gt;GSUB&lt;/a&gt; table) and rules that adjust spacing between specific character combinations (the Glyph Positioning, or &lt;a href="https://learn.microsoft.com/en-us/typography/opentype/spec/gpos" rel="noopener noreferrer"&gt;GPOS&lt;/a&gt; table). Most libraries get this wrong — they approximate from pre-computed tables — and the errors compound directly into Knuth-Plass spacing calculations. paragraf uses &lt;a href="https://github.com/RazrFalcon/rustybuzz" rel="noopener noreferrer"&gt;rustybuzz&lt;/a&gt; — a Rust port of &lt;a href="https://harfbuzz.github.io/" rel="noopener noreferrer"&gt;HarfBuzz&lt;/a&gt;, the shaping engine used by Firefox, Chrome, and LibreOffice — compiled to WebAssembly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Optical Margin Alignment
&lt;/h3&gt;

&lt;p&gt;A line beginning with a quotation mark or hyphen creates a visual indent even when geometrically flush, because those glyphs are narrower than full characters. The text block looks ragged at the margin even though every line starts at the same x coordinate. Optical margin alignment corrects this by allowing punctuation to protrude fractionally into the margin — typically 0.3 to 0.7 times the font size depending on the character — so the text block appears visually straight.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwzqo4im8mexoifkpgnhh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwzqo4im8mexoifkpgnhh.png" alt="OMA comparison: with optical margins (top) vs without (bottom). The right edge of the top block reads as visually straight; the bottom block shows punctuation sitting flush with letter glyphs, which reads as uneven." width="800" height="624"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Bidirectional Text
&lt;/h3&gt;

&lt;p&gt;Arabic and Hebrew run right-to-left (RTL). Latin text runs left-to-right (LTR). Each Arabic letter takes a different form depending on its position within a word — the letter ع (ayn) has four distinct forms: isolated, initial, medial, and final. Mixed LTR and RTL paragraphs require the &lt;a href="https://www.unicode.org/reports/tr9/" rel="noopener noreferrer"&gt;Unicode Bidirectional Algorithm&lt;/a&gt; to resolve the correct visual order of characters. This is not a bolt-on feature — it requires the GSUB shaping pipeline to already exist. You cannot add BiDi to a library that approximates font metrics.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqqh6ijff51tys0oqw28d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqqh6ijff51tys0oqw28d.png" alt="Arabic paragraph rendered RTL in the paragraf demo, with direction controls showing Auto / Force LTR / Force RTL." width="800" height="174"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Hyphenation
&lt;/h3&gt;

&lt;p&gt;Without correct hyphenation patterns, Knuth-Plass has fewer valid break points and is forced toward wider spacing or tighter compression. Hyphenation rules vary significantly by language and cannot be derived from simple character patterns — English breaks "rec-ord" differently as a noun versus a verb, German compounds stack hyphenation boundaries in ways that require dictionary knowledge, Turkish has vowel harmony rules that affect syllabification. paragraf uses the Liang algorithm implemented via the &lt;a href="https://www.npmjs.com/package/hyphen" rel="noopener noreferrer"&gt;hyphen&lt;/a&gt; npm package, covering 22 languages.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy4tggho2yguc3xd0rufa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy4tggho2yguc3xd0rufa.png" alt="English paragraph with hyphenation enabled, showing mid-word breaks at syllable boundaries. Minimum word length to hyphenate is 5 — shorter words are never broken." width="800" height="289"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;In the example above, the minimum word length to hyphenate is 5, so words with fewer than 5 characters are not hyphenated.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Styles, Layout, and Assembly
&lt;/h3&gt;

&lt;p&gt;Change the body font size in pdfmake and you change one paragraph, not the document. Without a style system with inheritance, every derived style is an independent manual update. Without a layout model, page geometry and unit conversions are the caller's responsibility on every project. Without a template schema, binding data fields to content slots — and handling missing fields gracefully — requires custom code per integration. &lt;strong&gt;paragraf&lt;/strong&gt; provides all of it as a coherent stack: a style system with inheritance, a layout model handling units and page geometry, a template schema with defined missing-field behaviour, and a compiler assembling everything into a single function call.&lt;/p&gt;




&lt;h2&gt;
  
  
  §2 — The Architecture: Monorepo, Layered, Modular
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;paragraf&lt;/strong&gt; is a monorepo of 12 published packages organised in strict layers. Each layer imports only from layers below it. No exceptions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ork1598gpcfooq1y6hm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ork1598gpcfooq1y6hm.png" alt="paragraf architecture diagram showing Layer 0 (types, color in-progress), Layer 1 (linebreak, font-engine, layout, style), Layer 2 (shaping-wasm, render-core, color-wasm in-progress), Layer 3 (typography, render-pdf), Layer 4 (compile, template), and App (demo, studio in-progress)." width="800" height="653"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For most developers, &lt;code&gt;@paragraf/compile&lt;/code&gt; is the only package that matters directly. It takes a template and a data record and returns a PDF buffer. The packages beneath it are there for developers who need to work at lower levels of the stack — custom renderers, browser-side line breaking, integration with existing font pipelines.&lt;/p&gt;

&lt;p&gt;Layer 0 carries zero-dependency shared interfaces. Layer 1 covers the algorithm and measurement packages — line breaking, font engine, page layout, style registry — all browser-safe. Layer 2 adds the Rust/WebAssembly shaper and the SVG/Canvas renderer. Layer 3 is Node-only: the paragraph compositor with OMA and BiDi, and the PDF renderer. Layer 4 is the user-facing API: the template schema and the compile pipeline.&lt;/p&gt;




&lt;h2&gt;
  
  
  §3 — The Approach: Spec-Driven, AI-Assisted, Test-Validated
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;paragraf&lt;/strong&gt; was designed and built through a disciplined two-loop process. The outer loop covers the project: problem, scope, specifications, constraints, high-level layer architecture, diagrams and models, and a versioned roadmap with future work. The inner loop covers each package: scope definition, input/output schemas, diagrams and models, a full implementation plan with defined steps and edge cases, unit tests written before implementation, then implementation against that specification, closing with integration and end-to-end tests validating every contract.&lt;/p&gt;

&lt;p&gt;The two loops are not independent. An issue discovered mid-package — an edge case that invalidates an assumption, a schema that cannot express a required input — feeds back into the outer loop and may revise the project scope, the architecture, or the roadmap. The project artifacts are living documents, not a fixed contract.&lt;/p&gt;

&lt;p&gt;The implementation was AI-assisted. Claude and GitHub Copilot were used as implementation engines operating against precise specifications. No agentic framework was used — every step involved a human decision. The specifications, architectural decisions, and constraint definitions came from domain knowledge: years working with software engineering, project management, InDesign, publishing automation, and multi-agent systems research.&lt;/p&gt;

&lt;p&gt;The quality of AI-assisted output is directly proportional to the precision of the specification the human provides. A developer without this domain background asking an AI to build a typesetting engine would get something that looks like one but breaks in ways they would not recognise.&lt;/p&gt;

&lt;p&gt;A separate article covering this process in detail will be published — the artifacts, the feedback loops, and the specific ways domain knowledge shaped the specifications.&lt;/p&gt;




&lt;h2&gt;
  
  
  §4 — The Technique: TypeScript, Node, WASM, Rust
&lt;/h2&gt;

&lt;p&gt;The core packages are TypeScript targeting Node.js 18+. Browser-safe packages also run in modern browsers — the live demo executes the full shaping and line-breaking pipeline client-side without a server. The OpenType shaper is Rust compiled to WebAssembly via wasm-pack: Rust was chosen for access to the rustybuzz shaping library, which would have taken months (if not years) to reimplement correctly in TypeScript. When the WASM binary is unavailable, the pipeline falls back automatically to a fontkit TypeScript path. PDF output uses pdfkit. The monorepo uses npm workspaces with tsup for package builds and Vitest for testing across all packages.&lt;/p&gt;




&lt;h2&gt;
  
  
  §5 — The Live Demo: See It Yourself
&lt;/h2&gt;

&lt;p&gt;The live demo runs entirely in the browser — the WASM shaper, the Knuth-Plass algorithm, and the SVG renderer all execute client-side. Type your own text, adjust the tolerance and looseness sliders, and watch the line breaks recalculate in real time. Four interactive pages: line breaking with side-by-side KP vs greedy comparison, layout controls, typography showcase, and multilingual rendering across 6 scripts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://kadetr.github.io/paragraf/" rel="noopener noreferrer"&gt;→ Live demo&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu9b9rd7s9lztvac8ekay.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu9b9rd7s9lztvac8ekay.png" alt="The line breaking page — editable text, tolerance/looseness/letter-spacing/alignment controls on the left; Knuth-Plass and greedy results side by side on the right." width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The line breaking page — edit text, adjust tolerance, and see Knuth-Plass and greedy results recalculate side by side.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  §6 — Future Work
&lt;/h2&gt;

&lt;p&gt;paragraf's package architecture is stable and deliberate, though still open to revision as the project evolves. The 12 published packages cover the full typesetting pipeline from algorithm to PDF output, and the three remaining planned packages — &lt;code&gt;@paragraf/color&lt;/code&gt;, &lt;code&gt;@paragraf/color-wasm&lt;/code&gt; — address color management and print production, and &lt;code&gt;@paragraf/studio&lt;/code&gt; — the browser-based editor template. Every feature below fits within the existing package structure as an enhancement, not a structural addition. That is a deliberate outcome of the layered architecture.&lt;/p&gt;

&lt;p&gt;Color and print production are the most significant items ahead. ICC color profile support, CMYK color spaces, and PDF/X compliance — the ISO standard for print-ready PDF exchange — are the bridge between paragraf's current typesetting quality and full print production readiness. These depend on &lt;code&gt;@paragraf/color-wasm&lt;/code&gt;, a Rust/LCMS2 WebAssembly package following the same pattern as the existing shaping layer.&lt;/p&gt;

&lt;p&gt;Typographic quality features planned within existing packages include micro-typography (per-line letter-spacing adjustments as an additional optimisation degree of freedom alongside word spacing), font expansion (horizontal glyph scaling, another Knuth-Plass optimisation dimension), drop caps, small caps, optical sizes, and balanced ragged lines.&lt;/p&gt;

&lt;p&gt;Layout and composition additions include page-level widow and orphan control (the paragraph-level penalty system exists; full page reflow is a separate pass in &lt;code&gt;@paragraf/compile&lt;/code&gt;), vertical justification, cross-column baseline grid alignment, inline figures with text runaround, and anchored objects.&lt;/p&gt;

&lt;p&gt;The Studio is a browser-based visual template editor — a web application in the monorepo, not a published npm package — that writes the same JSON format &lt;code&gt;@paragraf/template&lt;/code&gt; defines. It is the non-developer interface to the compile pipeline: drag-and-drop frame layout, point-and-click style definitions, and a live PDF preview powered by the same WASM stack that runs in the demo today.&lt;/p&gt;

&lt;p&gt;Contributions, issues, and discussions are open on &lt;a href="https://github.com/kadetr/paragraf" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. If you are working on a document pipeline and running into the limitations described in this article, opening an issue is the best place to start.&lt;/p&gt;




&lt;h2&gt;
  
  
  Annex: Terminology
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Bleed&lt;/strong&gt; — artwork that extends beyond the trim edge of a page to prevent white borders after cutting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BiDi&lt;/strong&gt; — Unicode Bidirectional Algorithm. Resolves display order for mixed left-to-right and right-to-left text.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Greedy line breaking&lt;/strong&gt; — fills each line as fully as possible, one line at a time, with no lookahead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ICC profile&lt;/strong&gt; — International Color Consortium profile. Defines the color characteristics of a device for accurate color reproduction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Knuth-Plass&lt;/strong&gt; — optimal paragraph line-breaking algorithm (Knuth &amp;amp; Plass, 1981). Minimises total spacing deviation across all lines simultaneously.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenType shaping&lt;/strong&gt; — processing font files to apply ligatures, kerning, and contextual letter forms defined in GSUB/GPOS tables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Optical margin alignment (OMA)&lt;/strong&gt; — allowing punctuation to protrude fractionally into the margin for a visually straight text edge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PDF/X&lt;/strong&gt; — ISO standard for print-ready PDF exchange. Guarantees color, font embedding, and other production requirements are met.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article is the entry point to a series. Each section above corresponds to a dedicated article in this series, published as they become available. The algorithm, the WASM architecture, the compile pipeline, the AI-assisted development process, and the print production roadmap are each covered separately.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>opensource</category>
      <category>node</category>
    </item>
  </channel>
</rss>
