From e1aeb00be9138adcfa5fc7b748037e1ea5b7390b Mon Sep 17 00:00:00 2001 From: mtlcl410 <2368091+mtlcl410@users.noreply.github.com> Date: Sun, 18 Oct 2026 14:21:35 +0800 Subject: [PATCH] fix(render): apply the JP2 palette of a JPX image with no /ColorSpace (#3179) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hayro-syntax decoded every JPX image with resolve_palette_indices off. That is right when the image dictionary gives a /ColorSpace (an /Indexed space needs the raw indices), but with no /ColorSpace the colours come from the JP2 header (ISO 32000-2 §7.4.9) and nothing applied its palette: the indices were drawn as a gray image. The palette is now resolved exactly when the dictionary has no /ColorSpace. Two unit tests decode a 4x2 palette JP2 written for the test: without a colour space it is red/blue RGB, with /Indexed the raw indices stay. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz --- vendor/README.md | 7 +++ vendor/hayro-syntax/src/filter/jpx.rs | 64 ++++++++++++++++++++++++++- 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/vendor/README.md b/vendor/README.md index 7b2c37f02..95b9418ab 100644 --- a/vendor/README.md +++ b/vendor/README.md @@ -41,6 +41,13 @@ Covered by `raster::tests::tiff_predictor_packed_rgb_rows_keep_their_byte_bounda horizontal differencing and an 8-bit control) and `raster::tests::tiff_predictor_packed_rgb_image_renders_every_row`. +JPEG 2000 palettes (#3179): `hayro-syntax::filter::jpx::decode` applies a JP2 palette (`pclr`) +when the image dictionary has no `/ColorSpace`, since the colour then comes from the JP2 header +(ISO 32000-2 §7.4.9). With a `/ColorSpace` the raw indices are kept, as an `/Indexed` space needs. +Without the patch a palette image with no `/ColorSpace` was drawn as its indices, in gray. +Covered by `filter::jpx::tests::jpx_palette_is_applied_without_a_color_space` and +`filter::jpx::tests::jpx_palette_indices_stay_raw_for_an_indexed_color_space`. + Text extraction: hayro-interpret exposes `OutlineGlyph::is_vertical` from a Type0 font’s CMap writing mode. PdfCraft follows negative glyph-space y for WMode 1 so upright vertical columns can be searched (#126). Lab Bradford follow-up to #1237: Lab adapts XYZ to D65 with white-point ratios diff --git a/vendor/hayro-syntax/src/filter/jpx.rs b/vendor/hayro-syntax/src/filter/jpx.rs index 74efd8b31..f3a631f0c 100644 --- a/vendor/hayro-syntax/src/filter/jpx.rs +++ b/vendor/hayro-syntax/src/filter/jpx.rs @@ -21,7 +21,10 @@ pub(crate) fn decode(data: &[u8], params: &ImageDecodeParams) -> Option = [red, blue, red, blue, blue, red, blue, red].concat(); + assert_eq!(&*result.data, &expected[..]); + } + + /// With an `/Indexed` colour space in the dictionary, the PDF supplies the palette, so the raw + /// indices are what the image holds. + #[test] + fn jpx_palette_indices_stay_raw_for_an_indexed_color_space() { + let params = ImageDecodeParams { + is_indexed: true, + num_components: Some(1), + width: 4, + height: 2, + ..ImageDecodeParams::default() + }; + let result = decode(PALETTE_JP2, ¶ms).unwrap(); + assert_eq!(&*result.data, &[0, 1, 0, 1, 1, 0, 1, 0][..]); } }