From f387c036d60ea670c91e92d9d7b13b53d1a962ca Mon Sep 17 00:00:00 2001 From: mtlcl410 Date: Thu, 5 Nov 2026 21:41:22 +0800 Subject: [PATCH] M2: a soft mask sized under /W and /H keys the raster by drawn size too (hayro-interpret patch 77) `has_larger_mask` read only /Width and /Height, while `ImageXObject::new` also takes the abbreviations /W and /H, so a large mask sized that way cached its image's raster without the drawn size and a later, larger draw reused the small mask. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn --- crates/render/src/raster/tests.rs | 17 +++++++++++++++-- vendor/hayro-interpret/src/x_object.rs | 4 +++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/crates/render/src/raster/tests.rs b/crates/render/src/raster/tests.rs index 5f6f95e97d..90e814d66c 100644 --- a/crates/render/src/raster/tests.rs +++ b/crates/render/src/raster/tests.rs @@ -8278,6 +8278,11 @@ fn a_soft_mask_far_larger_than_where_it_is_drawn_is_shrunk_once() { /// be averaged down (vendored hayro-interpret patch 77). Each mask is given as its size and /// samples. fn soft_masks_handed(width: u32, height: u32, mask: &[u8], sizes: &[(u32, u32)]) -> (Vec<(u32, u32, Vec)>, usize) { + soft_masks_handed_keyed(width, height, mask, sizes, ("Width", "Height")) +} + +/// `soft_masks_handed`, with the mask's dimensions under `keys` (`/W` and `/H`, say). +fn soft_masks_handed_keyed(width: u32, height: u32, mask: &[u8], sizes: &[(u32, u32)], keys: (&str, &str)) -> (Vec<(u32, u32, Vec)>, usize) { use hayro::hayro_interpret::font::Glyph; use hayro::hayro_interpret::hayro_syntax::Pdf; use hayro::hayro_interpret::{ @@ -8317,7 +8322,9 @@ fn soft_masks_handed(width: u32, height: u32, mask: &[u8], sizes: &[(u32, u32)]) ); let alpha = flate(mask); let mask_dict = format!( - "<< /Type /XObject /Subtype /Image /Width {width} /Height {height} /ColorSpace /DeviceGray /BitsPerComponent 8 /Filter /FlateDecode /Length {} >>", + "<< /Type /XObject /Subtype /Image /{} {width} /{} {height} /ColorSpace /DeviceGray /BitsPerComponent 8 /Filter /FlateDecode /Length {} >>", + keys.0, + keys.1, alpha.len() ); let content = sizes.iter().map(|(w, h)| format!("q {w} 0 0 {h} 0 0 cm /A Do Q")).collect::>().join(" "); @@ -8340,7 +8347,8 @@ fn soft_masks_handed(width: u32, height: u32, mask: &[u8], sizes: &[(u32, u32)]) /// but holds 10, drawn 20 high, is handed no rows that no sample covers; /// - is cached per drawn size, rounded up to a power of two: the same image drawn 20 and then 200 /// points wide is handed its mask at 32 and then at its own 100, not the first draw's 32 again, -/// and drawn 20 and 21 wide it is handed one mask of 32 both times; +/// and drawn 20 and 21 wide it is handed one mask of 32 both times, whether the mask's +/// dimensions are under `/Width` and `/Height` or `/W` and `/H`; /// - is made, at every size, from one intermediate mask at most 4096 on a side, so a mask wider /// than that, drawn at four sizes, is decoded in full once and not four times. #[test] @@ -8354,6 +8362,11 @@ fn an_averaged_down_soft_mask_follows_its_samples_and_its_drawn_size() { }; assert_eq!(sizes(&[(20, 20), (200, 200)]), vec![(32, 32), (100, 100)], "each draw at its own size"); assert_eq!(sizes(&[(20, 20), (21, 21)]), vec![(32, 32), (32, 32)], "draws a pixel apart share one"); + // The same with the mask's dimensions under the abbreviated `/W` and `/H`, which an image is + // read with too. + let (handed, _) = soft_masks_handed_keyed(100, 100, &[255; 100 * 100], &[(20, 20), (200, 200)], ("W", "H")); + let abbreviated: Vec<(u32, u32)> = handed.iter().map(|(w, h, _)| (*w, *h)).collect(); + assert_eq!(abbreviated, vec![(32, 32), (100, 100)], "each draw at its own size, under /W and /H"); // Columns alternating 0 and 255, so every averaged-down sample is about half: within 8, as a // sample covering 19 or 20 columns, say, holds 9 or 10 opaque ones. let wide: Vec = (0..5000 * 2).map(|i| if i % 2 == 0 { 0 } else { 255 }).collect(); diff --git a/vendor/hayro-interpret/src/x_object.rs b/vendor/hayro-interpret/src/x_object.rs index 48fa1e9610..11ff22300c 100644 --- a/vendor/hayro-interpret/src/x_object.rs +++ b/vendor/hayro-interpret/src/x_object.rs @@ -710,7 +710,9 @@ impl<'a> ImageXObject<'a> { let dict = self.stream.dict(); let larger = |s: &Stream<'_>| { let d = s.dict(); - d.get::(WIDTH).is_some_and(|w| w > self.width) || d.get::(HEIGHT).is_some_and(|h| h > self.height) + // The dimension keys as `ImageXObject::new` reads them, abbreviations first. + let (w, h) = (d.get::(W).or_else(|| d.get::(WIDTH)), d.get::(H).or_else(|| d.get::(HEIGHT))); + w.is_some_and(|w| w > self.width) || h.is_some_and(|h| h > self.height) }; dict.get::>(SMASK).is_some_and(|s| larger(&s)) || dict.get::>(MASK).is_some_and(|s| larger(&s)) }