diff --git a/crates/pdf/tests/upstream_raster.rs b/crates/pdf/tests/upstream_raster.rs index dfa0598f..97c979bb 100644 --- a/crates/pdf/tests/upstream_raster.rs +++ b/crates/pdf/tests/upstream_raster.rs @@ -618,3 +618,37 @@ fn visibility_expressions_decide_what_a_membership_dictionary_shows() { let deep = format!("<< /Type /OCMD /OCGs [5 0 R] /VE {}5 0 R{} >>", "[/Not ".repeat(41), "]".repeat(41)); assert!(!render(&deep, &[5]), "too deep: /OCGs decides"); } + +/// PdfCraft ledger hayro (18) (#2972): a page's objects composite with a transparent backdrop +/// and the page group is then put on the paper, so a blend mode only blends where something +/// was drawn. A green fill with `/BM /Screen` (or Lighten, Difference…) half over a red square +/// takes the mode's color over the red and stays plain green over the empty page. +#[cfg_attr(not(target_arch = "wasm32"), test)] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +fn blend_modes_over_an_empty_part_of_the_page_keep_their_color() { + for (mode, over_red) in [ + ("Normal", Some([0, 255, 0])), + ("Multiply", Some([0, 0, 0])), + ("Screen", Some([255, 255, 0])), + ("Lighten", Some([255, 255, 0])), + ("Difference", Some([255, 255, 0])), + ("Exclusion", Some([255, 255, 0])), + ("ColorDodge", Some([255, 0, 0])), + ("Overlay", Some([255, 0, 0])), + // The non-separable modes mix hue, saturation and luminosity; over an empty part of + // the page they too keep the source color. + ("Hue", None), + ("Saturation", None), + ("Color", None), + ("Luminosity", None), + ] { + let page = format!("/MediaBox [0 0 300 200] /Resources << /ExtGState << /G1 << /Type /ExtGState /BM /{mode} >> >> >>"); + let p = render(&one_page(&page, b"1 0 0 rg 20 20 150 150 re f /G1 gs 0 1 0 rg 100 50 150 120 re f", b"")); + // Pixel rows run top down: PDF (210, 100) and (130, 100) are rows 100 from the top. + assert_eq!(pixel(&p, 210, 100), [0, 255, 0, 255], "{mode} over the empty page"); + if let Some(over_red) = over_red { + assert_eq!(pixel(&p, 130, 100)[..3], over_red, "{mode} over the red square"); + } + assert_eq!(pixel(&p, 280, 10), [255, 255, 255, 255], "{mode}: the paper stays white"); + } +} diff --git a/vendor/README.craft-reader.md b/vendor/README.craft-reader.md index 2d57c549..593a7fa7 100644 --- a/vendor/README.craft-reader.md +++ b/vendor/README.craft-reader.md @@ -210,8 +210,13 @@ each marked `PdfCraft patch` in the source: hayro-interpret (40) (PdfCraft #2987 content membership dictionary with a visibility expression (`/VE`: `/And`, `/Or` and `/Not` over groups the document lists in `/OCProperties /OCGs`) shows its content when the expression is true, ahead of `/OCGs` and `/P`; a malformed expression, or one nested deeper -than 32 levels, is ignored and `/OCGs` and `/P` decide. Its PdfCraft raster test is adapted in -`tests/upstream_raster.rs`. +than 32 levels, is ignored and `/OCGs` and `/P` decide; and hayro (18) (PdfCraft #2972), the +page renders as one transparency group over a transparent backdrop that is then put on the +background, so a blend mode over an empty part of the page keeps its color; with a color +replacement (reading colors) the background stays inside the group as its backdrop. A plate +render (`InterpreterSettings::plate`) also keeps the background inside, marked `Craft patch`, +so blend modes on a separation's film blend with the paper. The PdfCraft raster tests of both +are adapted in `tests/upstream_raster.rs`. This remains the patched 0.7 backend. It retains the existing Bradford Lab conversion, floating component precision and codec versions, rather than claiming full 0.8 diff --git a/vendor/hayro/SOURCE.json b/vendor/hayro/SOURCE.json index 134c145e..ca0be0c3 100644 --- a/vendor/hayro/SOURCE.json +++ b/vendor/hayro/SOURCE.json @@ -62,7 +62,7 @@ "path": "src/lib.rs", "source_sha256": "682db2a32fbf4d875c0e9dd7ac5dc875a6b09ade375efbce6f104de9197de078", "source_git_blob": "6333ed5808ae0cf9cf158e4730e973c1d7089bab", - "sha256": "682db2a32fbf4d875c0e9dd7ac5dc875a6b09ade375efbce6f104de9197de078" + "sha256": "8396b90516603079bb43012621e8089621fec58fdd3d9c9aa9a8d2aa840265da" }, { "path": "src/renderer.rs", diff --git a/vendor/hayro/src/lib.rs b/vendor/hayro/src/lib.rs index 6333ed58..e41de6fd 100644 --- a/vendor/hayro/src/lib.rs +++ b/vendor/hayro/src/lib.rs @@ -254,15 +254,22 @@ pub fn render_into<'a>( interpreter_settings.preview, ); - // PdfCraft patch (18): the page renders as one transparency group with the background - // painted *inside* it, as the initial backdrop. A group isolates what it wraps from what - // was painted before it, so with the background outside, a Multiply highlight blended - // only with the group's transparent backdrop and stayed its own colour — over a dark - // reading-colours background the whole quad became one flat bar and the words under it - // vanished. Inside the group the highlight multiplies with the background too: the words - // keep the highlight's colour and the band between them takes the background (white - // backgrounds are unaffected — multiplying with white is the identity). - device.push_transparency_group(1.0, None, BlendMode::Normal); + // PdfCraft patch (18): the page renders as one transparency group (the page group). Its + // objects composite with an initially transparent backdrop, and the group is then put on + // the paper (ISO 32000-2, page group), so a blend mode only blends where something was + // drawn: a /BM /Screen fill over an empty part of the page keeps its color (#2970). + // Under reading colors the background is painted *inside* the group instead, as its + // initial backdrop, so a Multiply highlight blends with the dark background: outside it, + // the highlight kept its own flat color and the words under it vanished into the band + // (#1548). Reading colors replace the page's colors anyway; for document colors the + // background stays outside, or every separable mode but Normal, Multiply, Darken and + // HardLight would blend with the white paper. Craft patch: a plate render keeps the + // background inside as well, so blend modes on a separation's film blend with the paper. + let backdrop_inside = + render_settings.color_replacement.is_some() || interpreter_settings.plate.is_some(); + if backdrop_inside { + device.push_transparency_group(1.0, None, BlendMode::Normal); + } device.ctx.set_paint(render_settings.bg_color); device @@ -274,11 +281,19 @@ pub fn render_into<'a>( path: clip_path, fill: FillRule::NonZero, }); + if !backdrop_inside { + device.push_transparency_group(1.0, None, BlendMode::Normal); + } interpret_page(page, &mut state, &mut device); + if !backdrop_inside { + device.pop_transparency_group(); + } device.pop_clip_path(); - device.pop_transparency_group(); + if backdrop_inside { + device.pop_transparency_group(); + } let mut resources = vello_cpu::Resources::default(); device.ctx.render_to_buffer(