From 89d93d5e7348304bc7d8a148f9ad9c1beb200459 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Tue, 11 Aug 2026 11:15:28 -0300 Subject: mupdf: -Djpx, on by default, so a scanned PDF is not a blank page A scan is typically one /JPXDecode image per page. With FZ_ENABLE_JPX=0 and no openjpeg compiled, MuPDF raised "JPX support disabled" for every one of them and handed back a page with nothing drawn on it -- the pane opened, the page count was right, and the page was empty, which reads as a renderer bug rather than a missing codec. OPENJPEG_SRC comes out of Makelists through the same makeSources path the other three third-party libraries already use, with MuPDF's own OPENJPEG_CFLAGS and OPENJPEG_BUILD_CFLAGS, so there is no second source list to go stale. -fno-sanitize=undefined for the reason source/fitz needs it: upstream C full of deliberate wrapping arithmetic that ReleaseSafe's trap-mode UBSan would turn into a crash. FZ_ENABLE_JPX reaches the public headers, so Result carries the flag and linkTo hands consumers the archive's actual value instead of re-deriving it -- a consumer that disagrees is an ODR bug that shows up as a wrong struct layout at runtime rather than as a link error. A switch at all because it is 31 files of third-party C parsing untrusted input, and openjpeg has the CVE history to match. Default on because a viewer that cannot open scans is the more surprising default. +1.6 MB of archive; mupdf-check passes with it on and off. --- build.zig | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'build.zig') diff --git a/build.zig b/build.zig index 2d2b7840..dea403ca 100644 --- a/build.zig +++ b/build.zig @@ -61,6 +61,13 @@ pub fn build(b: *std.Build) void { const dump_path = b.option([]const u8, "dump", "dump .zon embedded into the web shell (-Dplatform=web)"); const is_web = platform == .web; const enable_mupdf = b.option(bool, "mupdf", "native PDF rendering with MuPDF (AGPL/commercial; native default on, web off; -Dmupdf=false disables)") orelse !is_web; + // JPEG 2000, and with it scanned PDFs: a scan is one /JPXDecode image per + // page, so without this MuPDF decodes nothing and every page comes back + // blank. On by default — a viewer that cannot open scans is the more + // surprising default — and a switch at all because it is 31 files of + // third-party C parsing untrusted input. See the OPENJPEG block in + // mupdf.zig. + const enable_jpx = b.option(bool, "jpx", "JPEG 2000 in PDFs, for scanned documents (default on; -Djpx=false drops openjpeg)") orelse true; const is_web_target = target.result.cpu.arch == .wasm32 and target.result.os.tag == .freestanding; // wasm: size is the budget const optimize = if (is_web) .ReleaseSmall else requested_optimize; @@ -362,6 +369,7 @@ pub fn build(b: *std.Build) void { const mupdf = mupdf_build.add(b, io, mupdf_dep, .{ .target = target, .optimize = c_optimize, + .jpx = enable_jpx, }); const mupdf_mod = b.createModule(.{ .target = target, -- cgit v1.3