mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-24 10:08:00 -06:00
vendor/hayro/src/renderer.rs: main's PaintSetup::Clip match with the PR's fill paint (is_stroke false) and comment vendor/README.md: main's row plus the PR's hayro-interpret patch and test, numbered 46 (35 is #3296's)
This commit is contained in:
@@ -6288,6 +6288,56 @@ trailer << /Root 1 0 R >>
|
||||
assert_eq!(pixel(&on, 150, 55), &[255, 255, 255, 255], "the image's white quadrant stays white");
|
||||
}
|
||||
|
||||
/// #2975: a stencil mask (`/ImageMask true`) is painted in the fill colour at the fill
|
||||
/// opacity, once: at `ca 0.5` it is as opaque as a rectangle filled at `ca 0.5` beside it
|
||||
/// (it was a quarter as opaque, the opacity applied by both the image's group and its paint).
|
||||
/// The same holds when the fill is a pattern, whose paint keeps the opacity in force when it
|
||||
/// was set with `scn`: an uncoloured tiling pattern and a shading pattern.
|
||||
#[test]
|
||||
fn stencil_masks_are_drawn_at_the_fill_opacity_once() {
|
||||
let px = |p: &RenderedPage, x: u32, y: u32| p.rgba[((y * p.width + x) * 4) as usize..][..3].to_vec();
|
||||
let fills = [("colour", "0 0 1 rg"), ("uncoloured tiling pattern", "/CsP cs 0 0 1 /Tp scn"), ("shading pattern", "/Pattern cs /Sp scn")];
|
||||
for (fill_name, fill) in fills {
|
||||
for alpha in ["1", "0.5", "0.25"] {
|
||||
let content = format!("/A gs {fill} q 100 0 0 100 20 50 cm /Msk Do Q 160 50 100 100 re f");
|
||||
let tile = "0 0 10 10 re f";
|
||||
let pdf = format!(
|
||||
"%PDF-1.7
|
||||
1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj
|
||||
2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj
|
||||
3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 300 200] /Contents 4 0 R /Resources << /ExtGState << /A << /ca {alpha} >> >> /XObject << /Msk 5 0 R >> /ColorSpace << /CsP [/Pattern /DeviceRGB] >> /Pattern << /Tp 6 0 R /Sp 7 0 R >> >> >> endobj
|
||||
4 0 obj << /Length {} >> stream
|
||||
{content}
|
||||
endstream endobj
|
||||
5 0 obj << /Type /XObject /Subtype /Image /Width 2 /Height 2 /ImageMask true /Length 2 >> stream
|
||||
\0\0
|
||||
endstream endobj
|
||||
6 0 obj << /Type /Pattern /PatternType 1 /PaintType 2 /TilingType 1 /BBox [0 0 10 10] /XStep 10 /YStep 10 /Resources << >> /Length {} >> stream
|
||||
{tile}
|
||||
endstream endobj
|
||||
7 0 obj << /Type /Pattern /PatternType 2 /Shading << /ShadingType 2 /ColorSpace /DeviceRGB /Coords [0 0 300 0] /Extend [true true] /Function << /FunctionType 2 /Domain [0 1] /C0 [0 0 1] /C1 [0 0 1] /N 1 >> >> >> endobj
|
||||
trailer << /Root 1 0 R >>
|
||||
%%EOF",
|
||||
content.len(),
|
||||
tile.len()
|
||||
);
|
||||
let p = PageRenderer::new(Arc::new(pdf.into_bytes()), RenderConfig::default()).render(RenderRequest {
|
||||
page: 0,
|
||||
kind: RequestKind::Pixels,
|
||||
tile: None,
|
||||
scale: 1.0,
|
||||
tag: 0,
|
||||
..Default::default()
|
||||
});
|
||||
assert!(p.error.is_none(), "{fill_name}, ca {alpha}: {:?}", p.error);
|
||||
// Pixel rows run top down: PDF (70, 100) is in the mask, (210, 100) in the rectangle.
|
||||
let (mask, rect) = (px(&p, 70, 100), px(&p, 210, 100));
|
||||
assert!(rect != [255, 255, 255], "{fill_name}, ca {alpha}: the rectangle is painted");
|
||||
assert!(mask.iter().zip(&rect).all(|(m, r)| m.abs_diff(*r) <= 1), "{fill_name}, ca {alpha}: mask {mask:?}, rectangle {rect:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Reading colours replace *document* colours, never annotation colours (issue #1548): a
|
||||
/// highlight keeps its `/C`, so on a dark background the content under it shows through the
|
||||
/// Multiply blend in the highlight's colour instead of being darkened into invisibility.
|
||||
|
||||
Vendored
+3
-1
File diff suppressed because one or more lines are too long
Vendored
+6
@@ -1256,6 +1256,12 @@ pub struct Color {
|
||||
}
|
||||
|
||||
impl Color {
|
||||
/// PdfCraft patch (35): the same colour, fully opaque (see `Pattern::without_opacity`).
|
||||
pub(crate) fn opaque(mut self) -> Self {
|
||||
self.opacity = 1.0;
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn new(color_space: ColorSpace, components: ColorComponents, opacity: f32) -> Self {
|
||||
Self {
|
||||
color_space,
|
||||
|
||||
+14
@@ -67,6 +67,20 @@ impl<'a> Pattern<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// PdfCraft patch (35): the pattern without the fill or stroke opacity it captured when it was
|
||||
/// set (`scn`). A stencil mask's transparency group applies the opacity in force when the mask
|
||||
/// is drawn, so a pattern paint carrying it too drew the mask at the opacity squared (#2975).
|
||||
pub(crate) fn without_opacity(mut self) -> Self {
|
||||
match &mut self {
|
||||
Self::Shading(p) => p.opacity = 1.0,
|
||||
Self::Tiling(p) => {
|
||||
p.non_stroking_paint = p.non_stroking_paint.clone().opaque();
|
||||
p.stroke_paint = p.stroke_paint.clone().opaque();
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn set_transfer_function(&mut self, tf: ActiveTransferFunction) {
|
||||
if let Self::Shading(p) = self {
|
||||
p.transfer_function = Some(tf);
|
||||
|
||||
+10
-2
@@ -5,7 +5,7 @@ use crate::device::Device;
|
||||
use crate::function::{Function, interpolate};
|
||||
use crate::interpret::path::get_paint;
|
||||
use crate::interpret::state::ActiveTransferFunction;
|
||||
use crate::{BlendMode, CacheKey, ClipPath, Image, RasterImage, StencilImage};
|
||||
use crate::{BlendMode, CacheKey, ClipPath, Image, Paint, RasterImage, StencilImage};
|
||||
use crate::{FillRule, InterpreterWarning, WarningSinkFn, interpret};
|
||||
use crate::{ImageData, LumaData, RgbData};
|
||||
use hayro_syntax::bit_reader::BitReader;
|
||||
@@ -262,10 +262,18 @@ pub(crate) fn draw_image_xobject<'a, 'b>(
|
||||
|
||||
device.set_soft_mask(None);
|
||||
device.set_blend_mode(BlendMode::default());
|
||||
// PdfCraft patch (35): the group just pushed applies the fill opacity, so a stencil mask's
|
||||
// paint (the fill colour) must not carry it again: at `ca 0.5` a mask was drawn at a
|
||||
// quarter of its opacity (#2975). The state is restored below.
|
||||
context.get_mut().graphics_state.non_stroke_alpha = 1.0;
|
||||
|
||||
let image = if x_object.is_mask {
|
||||
Image::Stencil(StencilImage {
|
||||
paint: get_paint(context, false),
|
||||
// A pattern paint keeps the opacity it was set with: drop it too (patch 35).
|
||||
paint: match get_paint(context, false) {
|
||||
Paint::Pattern(pattern) => Paint::Pattern(Box::new(pattern.without_opacity())),
|
||||
paint => paint,
|
||||
},
|
||||
image_xobject: x_object.clone(),
|
||||
})
|
||||
} else {
|
||||
|
||||
Vendored
+4
-1
@@ -1596,8 +1596,11 @@ impl<'a> Device<'a> for Renderer {
|
||||
);
|
||||
self.ctx.set_transform(transform);
|
||||
|
||||
// PdfCraft patch: a stencil mask is painted with the fill
|
||||
// (non-stroking) paint; `true` drew an uncoloured tiling pattern
|
||||
// in its stroke colour (black by default) (#2975).
|
||||
if let PaintSetup::Clip(clip_path) =
|
||||
self.set_paint(paint, &stencil_rect.to_path(0.1), true)
|
||||
self.set_paint(paint, &stencil_rect.to_path(0.1), false)
|
||||
{
|
||||
if let Some(clip_path) = clip_path.as_ref() {
|
||||
self.push_clip_path_inner(clip_path, FillRule::NonZero);
|
||||
|
||||
Reference in New Issue
Block a user