Repository navigation
Pin the bundled font paths so PDF export is not blank (#371, #251, #287) - #402
Merged
Merged
Conversation
…extcloud#251, nextcloud#287) font_selection.bin stores an absolute path to every bundled font, written by whichever machine ran allfontsgen - the build machine, for an app store install. When one of those paths does not resolve, x2t's PDF writer prints "Can't load fontfile" on stdout, writes a PDF with no glyphs in it, and exits 0. Nothing downstream can tell that from a good conversion, which is why exporting to PDF has been blank since 2022 with no diagnostic anywhere. The build already pinned AllFonts.js to paths relative to x2t's working directory and left font_selection.bin alone, on the belief - written into the Makefile - that its paths were not used for loading. They are, by the PDF writer. x2t accepts relative paths in that file, so it is now pinned the same way: same input, same binary, 1 937 bytes with no text before, 12 804 bytes with the text after. font_selection.bin is not sed-able - its strings are length-prefixed with a little-endian int32 - so the rewriter lives in FontPaths, shared between tools/pin-font-paths.php at build time and FontManager at runtime, and only touches paths under core-fonts. Custom fonts live in appdata, outside the app directory, and keep the absolute paths they are found at. The rest is the silence around it: - finishFontList() checked only that some AllFonts.js existed containing "/core-fonts/", which a stale file from the build satisfies, so a rebuild that wrote nothing at all reported success. It now checks that this run wrote both files, that file_put_contents succeeded, and that every pinned path resolves - and refuses up front, naming the file and the user, when it cannot write. This is the shape of nextcloud#145 and nextcloud#278. - ConverterBinary logs the fonts x2t says it could not load, rather than discarding the one stream that mentions them. - documentserver:fonts returned 1 on success and 0 on failure, so anything scripting it was told the opposite of what happened. - The upgrade repair step puts the actual error in its warning. tests/rig/pdf_test.py covers all three: a converted PDF has the text in it, a font that cannot be loaded reaches the log, and a rebuild that cannot write fails loudly. FontPathsTest covers the binary rewriter. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Fabrice Meyer <meyer.fabrice@gmx.fr>
chadek
force-pushed
the
fix/pdf-blank-font-paths
branch
from
September 15, 2026 17:22
96fe665 to
bc07ff6
Compare
chadek
marked this pull request as ready for review
September 15, 2026 17:23
This was referenced Sep 15, 2026
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Exporting or printing to PDF produces a PDF with no text in it. Reported since 2022 and still open: #371, #251, #287, and mentioned in passing in #288.
What it is
server/FileConverter/bin/font_selection.binstores an absolute path to every bundled font, written by whichever machine ranallfontsgen— the build machine, for an app store install. When one of those paths does not resolve, x2t's PDF writer printsCan't load fontfileon stdout, writes a PDF with no glyphs in it, and exits 0. Nothing is logged and nothing is thrown, so from everywhere else in the system a blank PDF and a good one are the same successful conversion.Same input, same binary, same exit code:
font_selection.binholds../../../core-fonts/The build already pinned
AllFonts.jsto paths relative to x2t's working directory and leftfont_selection.binalone, on the belief — written into the Makefile — that its paths "are not used for loading". They are not used by the change-replay path that comment is about, which readsAllFonts.js; they are used by the PDF writer.What this does
Stops shipping one machine's paths. x2t accepts relative paths in
font_selection.bin— verified before committing to the approach, sinceAllFonts.jsaccepting them does not prove it — so it is now pinned the same wayAllFonts.jsalready was.allthemesgen, which runs right after and renders the theme thumbnails, resolves them too: it neverchdirs and the Makefile already runs it fromFileConverter/binfor this reason.The file is not sed-able — its strings are length-prefixed with a little-endian int32, so replacing a path without updating its prefix corrupts everything after it. The rewriter is
lib/Document/FontPaths.php, shared betweentools/pin-font-paths.phpat build time andFontManagerat runtime so both produce the same thing. Only paths undercore-fontsare touched; custom fonts live in appdata, outside the app directory, and keep the absolute paths they are found at. A PDF converted with both embeds both.Makes the rebuild trustworthy, which fixes the silent-failure half regardless.
finishFontList()checked only that anAllFonts.jsexisted containing/core-fonts/— a stale one left over from the build satisfies that, so a rebuild that wrote nothing at all reported success — and never looked atfont_selection.binat all. It now checks that this run wrote both files, thatfile_put_contentssucceeded, and that every pinned path resolves from the converter directory; and it refuses up front, naming the file and the user, when it cannot write. That last case is the shape of #145 ("allfontsgen: not found") and #278 ("Segmentation fault while rebuilding fonts"), each of which leaves the user with blank PDFs and no diagnostic.Makes the failure visible.
ConverterBinarynow scans x2t's stdout forCan't load fontfileand logs it rather than discarding the one stream that mentions it.occ documentserver:fontsreturned 1 on success and 0 on failure, so anything scripting it was told the opposite of what happened. The upgrade repair step puts the actual error in its warning instead of a generic sentence.Tests
tests/rig/pdf_test.py, which the rig had no equivalent of — that is why this survived the v0.4.0 work. It asserts on extracted text rather than file size, and covers all three parts: a converted PDF has the text in it, a font that cannot be loaded reaches the log, and a rebuild that cannot write fails loudly. Confirmed it fails against the pre-fix font file (FAIL - "PdfFontProbe" is not in the 1937 byte PDF).tests/Document/FontPathsTest.phpcovers the binary rewriter, where a mistake would produce exactly the same silent symptom.CI now installs
poppler-utilsforpdftotext. The Makefile change invalidates the integration job's document-server cache key, so the next run rebuilds the tree rather than reusing one with unpinned paths.Suite
The full rig suite is green on this branch —
formats,smoke,flush-live,autosave,leave,twotab,coedit,coedit-xlsx,forcesave,chat,pdf.coedit-xlsxis the one that exercises the other consumer of these files: spreadsheet change replay readsAllFonts.js, which is the failurefinishFontListwas originally written for.Left as a draft pending review. Nothing has been posted to #371/#251/#287.
🤖 Generated with Claude Code