|
| 1 | +# Glyph Layout Backends |
| 2 | + |
| 3 | +Status: 2026-08-01 |
| 4 | + |
| 5 | +## Upstream PDFBox functionality |
| 6 | + |
| 7 | +Apache PDFBox added an opt-in glyph-layout pipeline under PDFBOX-4951 in July |
| 8 | +2026. The work is present on both the Apache `trunk` and `3.0` branches and is |
| 9 | +currently part of their `4.0.0-SNAPSHOT` and `3.0.9-SNAPSHOT` development |
| 10 | +lines. |
| 11 | + |
| 12 | +The upstream work has three parts: |
| 13 | + |
| 14 | +1. Core interfaces and content-stream integration in the `pdfbox` module. |
| 15 | + `PDPageContentStream`, appearance streams, and AcroForm appearance |
| 16 | + generation can delegate Unicode text to a registered glyph-layout |
| 17 | + processor. |
| 18 | +2. The optional `pdfbox-layout-awt` module, which shapes text using Java AWT |
| 19 | + font APIs. |
| 20 | +3. The optional `pdfbox-layout-fop` module, which shapes text using Apache FOP |
| 21 | + 2.11 font classes. |
| 22 | + |
| 23 | +The FOP module does not add XSL-FO document layout, paragraph flow, line |
| 24 | +breaking, or page composition to PDFBox. It uses FOP's font machinery to map a |
| 25 | +Unicode string to positioned glyph IDs before PDFBox writes that string into a |
| 26 | +PDF content stream. This supports capabilities such as OpenType substitutions |
| 27 | +and positioning, ligatures, kerning, combining marks, complex scripts, |
| 28 | +bidirectional text, and supplementary Unicode-plane characters. The same |
| 29 | +processor can be propagated into AcroForm appearance generation. |
| 30 | + |
| 31 | +The upstream FOP implementation is opt-in. Existing `showText` behavior is |
| 32 | +used when no processor is registered or when the processor does not support |
| 33 | +the selected font. It currently targets Type 0 fonts backed by TrueType |
| 34 | +outlines; CFF-based OpenType fonts are outside its supported path. |
| 35 | + |
| 36 | +References: |
| 37 | + |
| 38 | +- [PDFBOX-4951](https://issues.apache.org/jira/browse/PDFBOX-4951) |
| 39 | +- [Apache PDFBox `pdfbox-layout-fop` module](https://github.com/apache/pdfbox/tree/3.0/pdfbox-layout-fop) |
| 40 | +- [Commit adding the FOP backend to the Apache 3.0 branch](https://github.com/apache/pdfbox/commit/bb497fe1543c0747f92b4e613788e3297c29c3a7) |
| 41 | + |
| 42 | +## PDFBox.Net mapping |
| 43 | + |
| 44 | +Apache FOP is a Java library, so `pdfbox-layout-fop` is intentionally not |
| 45 | +ported as a literal .NET module and remains an explicit exclusion in the |
| 46 | +source-parity inventory. This is a runtime-specific adaptation, not a decision |
| 47 | +to omit glyph shaping. |
| 48 | + |
| 49 | +The .NET functional counterpart is `SkiaGlyphLayoutProcessor` in the optional |
| 50 | +`PdfBox.Net.SkiaSharp` package. It uses HarfBuzzSharp for shaping and SkiaSharp |
| 51 | +for font loading while implementing the upstream core |
| 52 | +`GlyphLayoutProcessorInterface`. It: |
| 53 | + |
| 54 | +- registers the same TrueType font with PDFBox.Net and HarfBuzz; |
| 55 | +- resolves bidirectional text into visual runs; |
| 56 | +- emits shaped glyph IDs instead of raw Unicode values; |
| 57 | +- writes kerning, advances, and horizontal/vertical offsets as PDF text |
| 58 | + positioning operations; and |
| 59 | +- keeps SkiaSharp and HarfBuzzSharp types out of the core `PdfBox.Net` API. |
| 60 | + |
| 61 | +This backend shapes Unicode text while creating new content streams and form |
| 62 | +appearances. It does not reshape glyph codes already present in an existing PDF |
| 63 | +during rendering. Existing PDF content already records the selected glyphs and |
| 64 | +positions; reshaping it would risk changing the author's content. |
| 65 | + |
| 66 | +## Branch support matrix |
| 67 | + |
| 68 | +| Capability | `main` | `release/3.0` | |
| 69 | +|---|---:|---:| |
| 70 | +| Core glyph-layout interfaces and content-stream hooks | Yes | Yes | |
| 71 | +| Upstream-compatible AWT-facing API adaptation | Yes | Yes | |
| 72 | +| SkiaSharp/HarfBuzz shaping backend | Yes | Not yet | |
| 73 | +| Literal Apache FOP runtime dependency | No, intentionally replaced | No, intentionally replaced | |
| 74 | + |
| 75 | +The AWT-facing adaptation on `release/3.0` currently provides the compatible |
| 76 | +registration/content-stream surface but only a conservative Identity-H glyph |
| 77 | +code path. Full complex-script shaping on that branch requires backporting the |
| 78 | +existing SkiaSharp/HarfBuzz backend. |
| 79 | + |
| 80 | +## Backport scope for `release/3.0` |
| 81 | + |
| 82 | +The backport does not require designing a new shaping engine. The core |
| 83 | +interfaces it consumes are already present on `release/3.0`. The remaining |
| 84 | +work is bounded to: |
| 85 | + |
| 86 | +1. Backport `SkiaGlyphLayoutProcessor` and `BidiTextRunResolver`. |
| 87 | +2. Add the HarfBuzzSharp native-asset and `Unicode.Bidi` package references to |
| 88 | + `PdfBox.Net.SkiaSharp`. |
| 89 | +3. Backport the generated-content tests for Latin kerning, Bengali |
| 90 | + substitution, Thai mark positioning, missing glyphs, and representative |
| 91 | + LTR/RTL visual runs. |
| 92 | +4. Run the normal solution, package, API-surface, and Java runtime-parity gates |
| 93 | + on `release/3.0`, including all supported native runtime packages. |
| 94 | + |
| 95 | +A disposable backport rehearsal against `release/3.0` applied the production |
| 96 | +and test changes without any source-code conflicts. The only conflict was in a |
| 97 | +generated upstream-sync state report, which should not be copied from `main`. |
| 98 | +The backported project restored and built successfully, and all six focused |
| 99 | +`SkiaGlyphLayoutProcessorTest` cases passed on the first run. |
| 100 | + |
| 101 | +Based on that rehearsal, the expected effort is approximately four to eight |
| 102 | +focused engineering hours, normally one calendar day including the full test, |
| 103 | +package, API-surface, runtime-parity, and CI gates. The main remaining risks are |
| 104 | +native-package packaging across Linux, macOS, and Windows, and keeping |
| 105 | +`Unicode.Bidi` behavior aligned with the Java `java.text.Bidi` fixtures. The |
| 106 | +shaping implementation itself is already exercised on `main` and does not |
| 107 | +appear to require branch-specific adaptation. |
0 commit comments