Repository navigation
Conversation
…d e-mail to PDF Part of KnifeLemon#1403 (Windows; Linux and macOS follow in a separate change). - Optional download pinned in engines.json: WeasyPrint 70.0's Windows "onedir" build (weasyprint-windows-onedir.zip, 32,271,073 bytes), offered in Settings > Engines, never taken from the PATH, not ticked by default. - One HTML -> PDF edge, used only in routes that start from HTML, Markdown, EML, MSG or MHTML (new ConversionEdge.Sources). It wins wherever WeasyPrint is installed; without it no route changes. - Locked down: pictures beside the document become data: URIs, every other reference is removed (LinkedContent), and WeasyPrint runs with --allowed-protocols data. - Stall watch on --verbose output (5 min of silence), 30 min overall. - Tests for routes, the prepared page and, with WeasyPrint, the PDFs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BPxh5sY3QRo9xaLZemsLr8
The HTML, Markdown, MHTML and LibreOffice route tests check what the built-in engines do; with WeasyPrint installed its routes are tested in WeasyPrintTests instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BPxh5sY3QRo9xaLZemsLr8
KnifeLemon
left a comment
There was a problem hiding this comment.
Thanks, @Ugoyo, this is a careful PR, and it follows the plan from #1403 closely. The pin is optional and checked, it's never taken from PATH, routes change only when WeasyPrint is installed, there are two layers of lockdown, and it uses the existing stall watch. I went through the lockdown in the WeasyPrint 70.0 source as well:
- every resource load (stylesheets,
@import, images, SVG,@font-face,<link rel=attachment>/<a rel=attachment>) goes through the oneURLFetcherthe CLI builds, which refuses any scheme not in--allowed-protocolsbefore opening anything; - only the main page is opened directly.
So file:, relative paths and network addresses stay blocked even where the LinkedContent copy misses something. The engines.json pin also matches the release asset exactly: size 32,271,073 and the GitHub-reported SHA-256.
Some smaller things:
- Inline SVG loses its same-document references.
LinkedContent.StripHtmldropshref/xlink:hrefonuseandimageunless the value isdata:, andCssUrlsemptiesurl(#…). So<use href="#icon">, gradientxlink:href="#base", andfill:url(#g)in astyleattribute or a<style>block come out blank. WeasyPrint resolves#fragmentreferences locally (svg/defs.py), so keeping values that start with#is safe, for the LibreOffice copy too. Could you keep those, and add a small test with<use href="#s"/>? - Linux and macOS status. The converter is registered everywhere, but off Windows there's nothing to install, so "Not installed" doesn't help the user.
WhisperConverterreturnsengine.reason.unsupported_platformthere. CouldGetStatusdo the same when nothing is found off Windows? Keep theBundledProgramlookup first, so your dev wrapper still works. PYTHONIOENCODING/PYTHONUNBUFFERED. PyInstaller starts Python with an isolated config, so the onedirweasyprint.exeignores both. Progress still works because WeasyPrint logs to stderr, which stays line-buffered. Could you correct the comment "the stall watch counts on them"? The variables can stay for pip-installed copies.- Docs.
- The runtime table in
docs/ENGINES.md("calibre, Java (downloads) | ship their own…") could list WeasyPrint, since its MinGW/UCRT runtime is in_internal. - The "Optional:" list in
docs/ARCHITECTURE.mdcould name it too.
- The runtime table in
- Korean wording. The three new
ko.jsonstrings use 합니다체, while the rest of the file uses 해요체:만들어요,나와요,불러오지 않아요,PDF가 돼요. - CI on Windows. Since you couldn't run it on Windows, could you add
weasyprintto thefetch-engines.ps1 -Onlylist in.github/workflows/ci.yml(next torhwp,pandoc,7zip,exiftool)? Then the Windows job runs your end-to-end and lock-on-its-own tests against the pinned build; right now they're skipped there.
Before merging, we'll also install it from Settings → Engines on a Windows machine and convert an HTML page, a Markdown file and an EML.
On your question in the PR body: keeping it unticked in the first-run choice is right, since it changes how pages become PDFs.
- Keep same-document references (#id) in the HTML copy, for WeasyPrint and LibreOffice: an inline SVG's <use href="#s">, a gradient's xlink:href="#base", fill: url(#g). Tests for both, and one that WeasyPrint draws the used symbol. - Off Windows, a WeasyPrint that isn't found reports engine.reason.unsupported_platform, like Whisper (Filee's own copy is still looked up first). - Correct the comment on PYTHONIOENCODING / PYTHONUNBUFFERED: the onedir build ignores them; progress comes from stderr, which stays line-buffered. - Docs: WeasyPrint in the Windows runtime table (ENGINES.md) and the optional engines (ARCHITECTURE.md). - Korean strings in 해요체 like the rest of ko.json. - CI: fetch WeasyPrint for the Windows engine tests, so the end-to-end and lock tests run against the pinned build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BPxh5sY3QRo9xaLZemsLr8
|
Thanks for the thorough review! All six points are addressed in a30bbe4:
And thanks for checking the lockdown against the WeasyPrint source. |
What does this change?
Part of #1403 (Windows; Linux and macOS follow in a separate PR).
Adds WeasyPrint as an optional engine for HTML, Markdown and e-mail → PDF, following the maintainer's comment on #1403:
Optional download, like calibre. It's pinned in
engines.jsonand offered in Settings → Engines as anEnginePackage(Windows only for now). It is never taken from the PATH:WeasyPrintConverter.Locate()only looks at Filee's download (engines/weasyprint) or a copy the user picked. It is not ticked by default in the first-run / installer choice (IsSuggested), even though it is under the 60 MB limit, because it changes how pages become PDFs. Tell me if you'd rather have it suggested.weasyprint-windows-onedir.zip(https://github.com/Kozea/WeasyPrint/releases/download/v70.0/weasyprint-windows-onedir.zip),
32,271,073 bytes (~32 MB), SHA-256
AB1151F210B4E6BB7AA7A79E91A67E8DDB760094C107BFDA55241B6AAEFE7D53, about 65 MB unpacked.The zip holds
LICENSE,README.rstandonedir/weasyprint/weasyprint.exewith its_internal/folder (Python 3.14, Pango, HarfBuzz, FreeType, Fontconfig, FriBidi, GLib, Pillow, fontTools, Pyphen, OpenSSL, MinGW/UCRT runtime). The installer keeps the folder that containsweasyprint.exeand moves the licence into it (EngineInstaller.WeasyPrintFolder;build/fetch-engines.ps1 -Only weasyprintdoes the same thing for development). 70.0 is also the release with the fix for CVE-2026-55073 / GHSA-r543-q48m-4c9j.The ranking changes only when WeasyPrint is installed. WeasyPrint has one edge, HTML → PDF, with cost 1. A new
ConversionEdge.Sourcesfield (a one-line check inRoutePlanner, next toImageSourcesOnly) limits that edge to routes that start from HTML, Markdown, EML, MSG or MHTML. Routes:html → pdf= WeasyPrint;md → pdf= Markdig → WeasyPrint;eml/msg/mhtml → pdf= e-mail engine → WeasyPrint.This wins over the built-in route (31) and LibreOffice (37) wherever WeasyPrint sits in the engine order. A DOCX, an e-book or a PDF never goes through a page to reach it. Without WeasyPrint the edge isn't planned, so nothing changes. A test compares every format → pdf/png/jpg/docx/hwpx/txt/html route with and without WeasyPrint. I didn't add it to the default
EnginePriority, because its cost already decides, so no settings migration is needed.Locked down. WeasyPrint gets a copy of the page made by
LinkedContent's existing HTML rule (the one LibreOffice already uses, built onDocumentFiles):data:URIs, SVG included;<base>,<link>stylesheet,@importand CSSurl()address is removed.WeasyPrint then runs with
--allowed-protocols data. Removing first and refusing second means a reference the regex rewrite misses (an<a rel=attachment>, a CSSimage-set()string, …) still loads nothing.WeasyPrintTests) build a page with afile://stylesheet, an@import, a local picture by absolute path, one by relative path outside the folder, and anhttps://picture pointing at a local TCP listener that records any connection.data:URI, and no loadable address.weasyprint.exeon the raw page with Filee's arguments, and only thedata:picture loads.--allowed-protocols datais removed.Size and time limits like the other engines.
--verbose, and its "Step 5 - Creating layout - Page N" lines drive the progress bar and the existing stall watch. It is stopped after 5 minutes without output (error.engine_stalled) and after 30 minutes in all. Cancelling stops it at once. Two runs at a time.Linux and macOS: later, in a separate PR, once a self-contained build exists. Until then the package isn't offered there, there's no download in their
engines.jsonsections, and their routes are unchanged.Other changes:
engine.weasyprint.descriptionandengines.package.weasyprint.name/descriptionare added in all 22i18n/*.jsonfiles.THIRD-PARTY-NOTICES.mdhas a line for WeasyPrint and what the executable bundles.docs/ENGINES.mdhas a new WeasyPrint section.LinkedContentnow writesimage/jpegrather thanimage/jpgin its data URIs.Type of change
How was it tested?
dotnet format --verify-no-changes(dotnet format Filee.slnx --verify-no-changes --severity warn, plusdotnet build Filee.slnx -c Release)dotnet test, on Linux only:pipand run through a development-onlyengines/weasyprintwrapper (git-ignored). With it, allWeasyPrintTestspass for real, including the end-to-end PDFs. Without it, the end-to-end tests are skipped as usual.mainin this container: system Pandoc and FFmpeg are incomplete, LibreOffice is missing, ISO tests fail, and tests that need a non-root user fail. Core and Cli have the same pre-existing failures asmain. The App tests for engines and localization pass.weasyprint.exeitself were not run. I downloaded the asset and checked its size, hash and contents on Linux. A check on Windows before merging would be welcome:Checklist
src/Filee.App/Assets/i18n/*.jsonfilesTHIRD-PARTY-NOTICES.md