summaryrefslogtreecommitdiff
path: root/mupdf.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-08-11 11:15:28 -0300
committerGabriel Schneider <[email protected]>2026-08-11 11:35:08 -0300
commit89d93d5e7348304bc7d8a148f9ad9c1beb200459 (patch)
tree50aea92dce0b42195dfe1beb5950a35c05618e33 /mupdf.zig
parenta797a1ab2f648e773f7a1b28d12bf9441b9f13f4 (diff)
downloadpardes-89d93d5e7348304bc7d8a148f9ad9c1beb200459.tar.gz
pardes-89d93d5e7348304bc7d8a148f9ad9c1beb200459.zip
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.
Diffstat (limited to 'mupdf.zig')
-rw-r--r--mupdf.zig56
1 files changed, 51 insertions, 5 deletions
diff --git a/mupdf.zig b/mupdf.zig
index a407ea09..400e5ee3 100644
--- a/mupdf.zig
+++ b/mupdf.zig
@@ -8,11 +8,17 @@ const std = @import("std");
pub const Result = struct {
dependency: *std.Build.Dependency,
library: *std.Build.Step.Compile,
+ /// Whether this archive carries the JPEG 2000 decoder. Remembered rather
+ /// than re-derived because `linkTo` has to hand consumers the SAME
+ /// preprocessor view the archive was built with — `FZ_ENABLE_JPX` reaches
+ /// the public headers, and a consumer that disagrees is an ODR bug that
+ /// only shows up as a wrong struct layout at runtime.
+ jpx: bool,
/// Give a Zig module the same preprocessor view as the library, expose the
/// public C headers to @cImport, and propagate the static link.
pub fn linkTo(self: Result, module: *std.Build.Module) void {
- configureModule(module, self.dependency);
+ configureModule(module, self.dependency, self.jpx);
// MuPDF's public context headers select the lock-debugging contract
// from NDEBUG. Every consumer must therefore agree with the archive,
// even when its own Zig optimization/safety mode differs.
@@ -24,13 +30,16 @@ pub const Result = struct {
pub fn add(b: *std.Build, io: std.Io, dependency: *std.Build.Dependency, options: struct {
target: std.Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode,
+ /// Compile openjpeg and let MuPDF decode JPEG 2000. See the OPENJPEG
+ /// block below for why this is a switch rather than always-on.
+ jpx: bool = true,
}) Result {
const module = b.createModule(.{
.target = options.target,
.optimize = options.optimize,
.link_libc = true,
});
- configureModule(module, dependency);
+ configureModule(module, dependency, options.jpx);
// MuPDF's upstream release build defines NDEBUG independently of the
// optimizer. Without it, an optimized Zig build still enables Fitz's
// debug-locking/assertion paths. linkTo applies the same public-header
@@ -101,6 +110,40 @@ pub fn add(b: *std.Build, io: std.Io, dependency: *std.Build.Dependency, options
},
});
+ // --- OPENJPEG ---
+ //
+ // The JPEG 2000 decoder, and the reason a scanned PDF is not a blank page:
+ // a scan is typically one /JPXDecode image per page, so without this MuPDF
+ // raises "JPX support disabled" for every one of them and hands back a
+ // page with nothing drawn on it. Everything else still works — the pane
+ // opens, the page count is right — which makes it look like a renderer bug
+ // rather than a missing codec.
+ //
+ // A switch rather than always-on because it is 31 files of third-party C
+ // that exists to parse untrusted input, and openjpeg has the CVE history
+ // to match. Default on: a PDF viewer that cannot open scans is the more
+ // surprising default, and the ones that matter are exactly the documents
+ // nobody can retypeset.
+ //
+ // Flags are MuPDF's own OPENJPEG_CFLAGS plus OPENJPEG_BUILD_CFLAGS from
+ // Makelists; the include path is already on the module (configureModule
+ // adds it unconditionally, because encode-jpx.c wants the header even when
+ // the codec is off). -fno-sanitize=undefined for the reason source/fitz
+ // needs it: this is upstream C full of deliberate wrapping arithmetic, and
+ // ReleaseSafe's trap-mode UBSan would turn a legal shift into a crash.
+ if (options.jpx) module.addCSourceFiles(.{
+ .root = dependency.path(""),
+ .files = makeSources(b, makelists, "OPENJPEG_SRC"),
+ .flags = &.{
+ "-std=gnu11",
+ "-fno-sanitize=undefined",
+ "-DOPJ_STATIC",
+ "-DOPJ_HAVE_INTTYPES_H",
+ "-DOPJ_HAVE_STDINT_H",
+ "-DMUTEX_pthread=0",
+ },
+ });
+
const library = b.addLibrary(.{
.name = "mupdf",
.linkage = .static,
@@ -109,7 +152,7 @@ pub fn add(b: *std.Build, io: std.Io, dependency: *std.Build.Dependency, options
if (options.target.result.os.tag != .windows)
module.linkSystemLibrary("m", .{});
- return .{ .dependency = dependency, .library = library };
+ return .{ .dependency = dependency, .library = library, .jpx = options.jpx };
}
/// Add a link-complete smoke test. Unlike merely producing an archive, this
@@ -203,7 +246,7 @@ pub fn addProbe(b: *std.Build, result: Result, options: struct {
return &run.step;
}
-fn configureModule(module: *std.Build.Module, dependency: *std.Build.Dependency) void {
+fn configureModule(module: *std.Build.Module, dependency: *std.Build.Dependency, jpx: bool) void {
module.addIncludePath(dependency.path("include"));
module.addIncludePath(dependency.path("source/fitz"));
module.addIncludePath(dependency.path("source/pdf"));
@@ -236,8 +279,11 @@ fn configureModule(module: *std.Build.Module, dependency: *std.Build.Dependency)
.{ "FZ_ENABLE_BARCODE", "0" },
.{ "FZ_ENABLE_BROTLI", "0" },
.{ "FZ_ENABLE_ICC", "0" },
- .{ "FZ_ENABLE_JPX", "0" },
}) |macro| module.addCMacro(macro[0], macro[1]);
+ // The one codec that is a build option rather than a fixed answer, and it
+ // reaches the public headers — so consumers get it through linkTo from the
+ // archive's own `jpx`, never re-derived. See the OPENJPEG block in add().
+ module.addCMacro("FZ_ENABLE_JPX", if (jpx) "1" else "0");
module.addCMacro("OCR_DISABLED", "1");
module.addCMacro("TOFU", "1");
module.addCMacro("TOFU_CJK", "1");