mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-01 16:24:51 +00:00
hayro-interpret, hayro: port PdfCraft's mesh shading extent (#3296)
EncodedShadingPattern::extent records the device pixels a mesh's triangles touched when it was encoded. set_paint returns PaintSetup: the mesh texture covers that extent with Extend::Pad instead of the whole path, and a mesh with no triangle on the canvas paints nothing. Output is unchanged; the knockout erase pass and plate drawing are untouched. Adds hayro's mesh_extent_tests.
This commit is contained in:
1 parent
c45caf2cd1
commit
5ff42f2642
6 files changed
+273
-23
No files matched your search
Vendored
+13
@@ -236,6 +236,19 @@ image, its header and the terminator fit within `MAX_DECODED_STREAM`, and otherw
|
||||
back to the look-ahead. Its unit tests in `content::tests` come with it. PdfCraft's raster
|
||||
test is not ported: the windowed tokenizer tests cover the same content streams.
|
||||
|
||||
A rendering speedup from the [PdfCraft revision 0b670caf4a090e9d83a7f41a84b4cb2144a2d33e ledger](https://github.com/storytold/pdfcraft/blob/0b670caf4a090e9d83a7f41a84b4cb2144a2d33e/vendor/README.md),
|
||||
hayro-interpret (35) with hayro (19), PdfCraft #3296, marked `PdfCraft patch (#3296)` because
|
||||
the plate-mode code uses (35) for #2770's own numbers. `EncodedShadingPattern::extent` is the
|
||||
device-space rectangle of the pixels a mesh shading's triangles touched when it was encoded
|
||||
(`None` for function-based, axial and radial shadings, and for a mesh with a `/Background`).
|
||||
`Renderer::set_paint` returns `PaintSetup`: a mesh texture covers that extent (and a
|
||||
transparent pixel around it, padded instead of repeated) rather than the whole path, and a
|
||||
mesh with no triangle on the canvas returns `PaintSetup::Nothing`, which the fill, stroke,
|
||||
rect and stencil callers skip. The rendered output is the same. The knockout erase pass
|
||||
returns before the extent is used, so it still clears under the whole path. Tests:
|
||||
`mesh_extent_tests::{a_mesh_paints_its_triangles_alone, a_mesh_keeps_the_graphics_state_alpha,
|
||||
a_mesh_off_the_canvas_paints_nothing}` in hayro.
|
||||
|
||||
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
|
||||
equivalence. The independent color oracles, backend tests and facade contracts cover
|
||||
|
||||
Vendored
+1
-1
@@ -206,7 +206,7 @@
|
||||
"path": "src/encode.rs",
|
||||
"source_sha256": "674a97c48cc62e9a218f843246776528173c36c00aa96a1e4c291c1d912c29c6",
|
||||
"source_git_blob": "b3f830ba2dcdb7069ea73706be43c55be398f670",
|
||||
"sha256": "025ce1ce561229c005ac0f72b632e22bf782d34739d81c321334fc1dedd96933"
|
||||
"sha256": "46c9773f05c329cad20f3126394f89a551ee393569c58911b0a921be8c020376"
|
||||
},
|
||||
{
|
||||
"path": "src/font/blob.rs",
|
||||
|
||||
Vendored
+47
-1
@@ -18,6 +18,13 @@ pub struct EncodedShadingPattern {
|
||||
pub(crate) shading_type: EncodedShadingType,
|
||||
pub(crate) opacity: f32,
|
||||
pub(crate) transfer_function: Option<ActiveTransferFunction>,
|
||||
/// PdfCraft patch (#3296): where the shading can paint at all, in device space: for a mesh
|
||||
/// (types 4–7), the pixels its triangles touched when it was encoded (empty when none
|
||||
/// lies in the area); `None` for the shading types that paint everywhere (function-based,
|
||||
/// axial, radial). The renderer samples a texture only there, and skips a mesh that
|
||||
/// touches nothing: painted over the whole canvas, 310 one-triangle meshes on a plan cost
|
||||
/// 310 full tiles per tile, and the watchdog gave the page up.
|
||||
pub extent: Option<kurbo::Rect>,
|
||||
}
|
||||
|
||||
impl EncodedShadingPattern {
|
||||
@@ -50,6 +57,8 @@ impl ShadingPattern {
|
||||
/// Mesh shadings are sampled pixel by pixel, and only there (see `TriangleSampler`).
|
||||
pub fn encode_within(&self, area: Option<kurbo::Rect>) -> EncodedShadingPattern {
|
||||
let base_transform;
|
||||
// PdfCraft patch (#3296): see `EncodedShadingPattern::extent`.
|
||||
let mut extent = None;
|
||||
|
||||
let shading_type = match self.shading.shading_type.as_ref() {
|
||||
ShadingType::FunctionBased {
|
||||
@@ -81,6 +90,7 @@ impl ShadingPattern {
|
||||
let full_transform = self.matrix;
|
||||
let mut sampler = TriangleSampler::new(area);
|
||||
sampler.sample(triangles, full_transform);
|
||||
extent = Some(sampler.extent());
|
||||
let samples = sampler.map;
|
||||
|
||||
base_transform = Affine::IDENTITY;
|
||||
@@ -106,6 +116,7 @@ impl ShadingPattern {
|
||||
break;
|
||||
}
|
||||
}
|
||||
extent = Some(sampler.extent());
|
||||
let samples = sampler.map;
|
||||
|
||||
base_transform = Affine::IDENTITY;
|
||||
@@ -130,6 +141,7 @@ impl ShadingPattern {
|
||||
break;
|
||||
}
|
||||
}
|
||||
extent = Some(sampler.extent());
|
||||
let samples = sampler.map;
|
||||
|
||||
base_transform = Affine::IDENTITY;
|
||||
@@ -162,6 +174,13 @@ impl ShadingPattern {
|
||||
base_transform,
|
||||
opacity: self.opacity,
|
||||
transfer_function: self.transfer_function.clone(),
|
||||
// A `/Background` is painted wherever the triangles aren't (pattern fills), so such
|
||||
// a mesh has no extent to confine it to.
|
||||
extent: if self.shading.background.is_some() {
|
||||
None
|
||||
} else {
|
||||
extent
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -249,6 +268,8 @@ struct TriangleSampler {
|
||||
map: FxHashMap<(u16, u16), ColorComponents>,
|
||||
area: kurbo::Rect,
|
||||
visits_left: u64,
|
||||
/// PdfCraft patch (#3296): the pixels sampled so far, as `(x0, y0, x1, y1)` inclusive.
|
||||
touched: Option<(u16, u16, u16, u16)>,
|
||||
}
|
||||
|
||||
impl TriangleSampler {
|
||||
@@ -263,6 +284,21 @@ impl TriangleSampler {
|
||||
map: FxHashMap::default(),
|
||||
area: area.unwrap_or(kurbo::Rect::new(0.0, 0.0, 65536.0, 65536.0)),
|
||||
visits_left,
|
||||
touched: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// PdfCraft patch (#3296): the device-space rectangle covering every pixel sampled so far
|
||||
/// (whole pixels), or an empty rectangle when no triangle touched the area.
|
||||
fn extent(&self) -> kurbo::Rect {
|
||||
match self.touched {
|
||||
Some((x0, y0, x1, y1)) => kurbo::Rect::new(
|
||||
f64::from(x0),
|
||||
f64::from(y0),
|
||||
f64::from(x1) + 1.0,
|
||||
f64::from(y1) + 1.0,
|
||||
),
|
||||
None => kurbo::Rect::ZERO,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,8 +358,9 @@ impl TriangleSampler {
|
||||
}
|
||||
self.visits_left -= 1;
|
||||
let corner = Point::new(x as f64, y as f64);
|
||||
if t.contains_point(corner) {
|
||||
let sampled = if t.contains_point(corner) {
|
||||
self.map.insert((x, y), t.interpolate(corner));
|
||||
true
|
||||
} else if t.covers(Point::new(x as f64 + 0.5, y as f64 + 0.5)) {
|
||||
// PdfCraft patch (#3035): a pixel whose corner no triangle holds strictly (it
|
||||
// lies on an edge two triangles share, or on the mesh's side) still belongs
|
||||
@@ -334,6 +371,15 @@ impl TriangleSampler {
|
||||
self.map
|
||||
.entry((x, y))
|
||||
.or_insert_with(|| t.interpolate(corner));
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if sampled {
|
||||
self.touched = Some(match self.touched {
|
||||
Some((x0, y0, x1, y1)) => (x0.min(x), y0.min(y), x1.max(x), y1.max(y)),
|
||||
None => (x, y, x, y),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+2
-2
@@ -62,13 +62,13 @@
|
||||
"path": "src/lib.rs",
|
||||
"source_sha256": "682db2a32fbf4d875c0e9dd7ac5dc875a6b09ade375efbce6f104de9197de078",
|
||||
"source_git_blob": "6333ed5808ae0cf9cf158e4730e973c1d7089bab",
|
||||
"sha256": "682db2a32fbf4d875c0e9dd7ac5dc875a6b09ade375efbce6f104de9197de078"
|
||||
"sha256": "644faa9797c90fbd06ab41ded1c8e2688597009c807101c371c0191e1d1032a4"
|
||||
},
|
||||
{
|
||||
"path": "src/renderer.rs",
|
||||
"source_sha256": "f7ff7bd088ceb43823ede33151138384af331cf71403f7d23f7b2937212efc3b",
|
||||
"source_git_blob": "6aa53716ad2d0203a8058b2f7271515b4fc1db22",
|
||||
"sha256": "ab0962cff1ef8886b42e5f4ffd01e958d787ef949260fe4a934890d2e67d5ec2"
|
||||
"sha256": "02d256f43d9b8bceb68cee9f9f9ed0bcb48bb70d7068f2d1c6d6a9d85e3f4e64"
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
+149
@@ -675,3 +675,152 @@ mod color_replacement_render_tests {
|
||||
assert_eq!(sample(&no_line_art, 40, 25), [0, 0, 0, 255]);
|
||||
}
|
||||
}
|
||||
|
||||
/// PdfCraft patch (#3296): a mesh shading paints its triangles alone (`EncodedShadingPattern::extent`),
|
||||
/// and one with no triangle on the canvas paints nothing.
|
||||
#[cfg(test)]
|
||||
mod mesh_extent_tests {
|
||||
use super::{InterpreterSettings, Pdf, Pixmap, RenderCache, RenderSettings, render};
|
||||
use vello_cpu::color::palette::css::WHITE;
|
||||
|
||||
/// A one-page 200×200 pt PDF whose content is `body`, with a type 4 (free-form triangle)
|
||||
/// shading `/Sh0`: one red triangle with corners (x0, y0), (x1, y0) and (x0, y1) in user
|
||||
/// space, and an ExtGState `/Half` with 50% alpha.
|
||||
fn fixture(body: &str, (x0, y0, x1, y1): (i32, i32, i32, i32)) -> Vec<u8> {
|
||||
// Three vertices: a flag, x and y (8 bits each, decoded into the ranges) and RGB.
|
||||
let mesh: [u8; 18] = [
|
||||
0, 0, 0, 255, 0, 0, //
|
||||
0, 255, 0, 255, 0, 0, //
|
||||
0, 0, 255, 255, 0, 0,
|
||||
];
|
||||
let mut objs: Vec<Vec<u8>> = vec![
|
||||
b"<< /Type /Catalog /Pages 2 0 R >>".to_vec(),
|
||||
b"<< /Type /Pages /Kids [3 0 R] /Count 1 /MediaBox [0 0 200 200] >>".to_vec(),
|
||||
b"<< /Type /Page /Parent 2 0 R /Contents 4 0 R /Resources << /Shading << /Sh0 5 0 R >> \
|
||||
/ExtGState << /Half << /ca 0.5 /CA 0.5 >> >> >> >>"
|
||||
.to_vec(),
|
||||
format!("<< /Length {} >>\nstream\n{body}\nendstream", body.len()).into_bytes(),
|
||||
];
|
||||
let mut shading = format!(
|
||||
"<< /ShadingType 4 /ColorSpace /DeviceRGB /BitsPerCoordinate 8 /BitsPerComponent 8 \
|
||||
/BitsPerFlag 8 /Decode [{x0} {x1} {y0} {y1} 0 1 0 1 0 1] /Length {} >>\nstream\n",
|
||||
mesh.len()
|
||||
)
|
||||
.into_bytes();
|
||||
shading.extend_from_slice(&mesh);
|
||||
shading.extend_from_slice(b"\nendstream");
|
||||
objs.push(shading);
|
||||
|
||||
let mut out = b"%PDF-1.7\n".to_vec();
|
||||
let mut offsets = Vec::new();
|
||||
for (i, o) in objs.iter().enumerate() {
|
||||
offsets.push(out.len());
|
||||
out.extend_from_slice(format!("{} 0 obj\n", i + 1).as_bytes());
|
||||
out.extend_from_slice(o);
|
||||
out.extend_from_slice(b"\nendobj\n");
|
||||
}
|
||||
let xref = out.len();
|
||||
out.extend_from_slice(
|
||||
format!("xref\n0 {}\n0000000000 65535 f \n", objs.len() + 1).as_bytes(),
|
||||
);
|
||||
for o in offsets {
|
||||
out.extend_from_slice(format!("{o:010} 00000 n \n").as_bytes());
|
||||
}
|
||||
out.extend_from_slice(
|
||||
format!(
|
||||
"trailer\n<< /Size {} /Root 1 0 R >>\nstartxref\n{xref}\n%%EOF\n",
|
||||
objs.len() + 1
|
||||
)
|
||||
.as_bytes(),
|
||||
);
|
||||
out
|
||||
}
|
||||
|
||||
fn render_at(pdf: Vec<u8>, scale: f32) -> Pixmap {
|
||||
let pdf = Pdf::new(pdf).unwrap();
|
||||
let cache = RenderCache::new();
|
||||
let settings = RenderSettings {
|
||||
x_scale: scale,
|
||||
y_scale: scale,
|
||||
bg_color: WHITE,
|
||||
..RenderSettings::default()
|
||||
};
|
||||
render(
|
||||
pdf.pages().get(0).expect("the fixture has one page"),
|
||||
&cache,
|
||||
&InterpreterSettings::default(),
|
||||
&settings,
|
||||
)
|
||||
}
|
||||
|
||||
fn px(pixmap: &Pixmap, x: u16, y: u16) -> [u8; 4] {
|
||||
let p = pixmap.sample(x, y);
|
||||
[p.r, p.g, p.b, p.a]
|
||||
}
|
||||
|
||||
fn inked(pixmap: &Pixmap) -> usize {
|
||||
(0..pixmap.height())
|
||||
.flat_map(|y| (0..pixmap.width()).map(move |x| (x, y)))
|
||||
.filter(|&(x, y)| px(pixmap, x, y) != [255, 255, 255, 255])
|
||||
.count()
|
||||
}
|
||||
|
||||
/// The triangle (100, 100)–(110, 100)–(100, 110) in a 200 pt page: in device space (y
|
||||
/// down) the pixel (102, 97) is inside it, (108, 91) past its hypotenuse, and the rest of
|
||||
/// the page is nowhere near it.
|
||||
#[test]
|
||||
fn a_mesh_paints_its_triangles_alone() {
|
||||
let white = [255, 255, 255, 255];
|
||||
for scale in [1.0_f32, 4.0] {
|
||||
let p = render_at(fixture("/Sh0 sh", (100, 100, 110, 110)), scale);
|
||||
let at = |x: f32, y: f32| ((x * scale) as u16, (y * scale) as u16);
|
||||
let (x, y) = at(102.5, 97.5);
|
||||
assert_eq!(
|
||||
px(&p, x, y),
|
||||
[255, 0, 0, 255],
|
||||
"scale {scale}: inside the triangle"
|
||||
);
|
||||
let (x, y) = at(108.5, 91.5);
|
||||
assert_eq!(px(&p, x, y), white, "scale {scale}: past the hypotenuse");
|
||||
let (x, y) = at(150.0, 50.0);
|
||||
assert_eq!(px(&p, x, y), white, "scale {scale}: far from it");
|
||||
let (x, y) = at(10.0, 190.0);
|
||||
assert_eq!(
|
||||
px(&p, x, y),
|
||||
white,
|
||||
"scale {scale}: the texture is not repeated"
|
||||
);
|
||||
// A 10 × 10 pt right triangle covers 50 pt²; the sampler counts the pixels whose
|
||||
// corner lies inside it (36 at scale 1, nearer 50 as the scale grows), and nothing
|
||||
// beyond its own bounding box is touched.
|
||||
let area = inked(&p) as f32 / (scale * scale);
|
||||
assert!(
|
||||
(30.0..=60.0).contains(&area),
|
||||
"scale {scale}: {area} pt² inked"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The alpha of the graphics state still applies: red at 50% over white is pink.
|
||||
#[test]
|
||||
fn a_mesh_keeps_the_graphics_state_alpha() {
|
||||
let p = render_at(fixture("/Half gs /Sh0 sh", (100, 100, 110, 110)), 1.0);
|
||||
let [r, g, b, a] = px(&p, 102, 97);
|
||||
assert!(
|
||||
r == 255 && (120..=136).contains(&g) && g == b && a == 255,
|
||||
"{:?}",
|
||||
[r, g, b, a]
|
||||
);
|
||||
}
|
||||
|
||||
/// A mesh whose triangle lies off the canvas paints nothing, and costs no texture.
|
||||
#[test]
|
||||
fn a_mesh_off_the_canvas_paints_nothing() {
|
||||
let p = render_at(fixture("/Sh0 sh", (300, 300, 310, 310)), 1.0);
|
||||
assert_eq!(inked(&p), 0);
|
||||
// One straddling the edge paints its part on the canvas.
|
||||
let p = render_at(fixture("/Sh0 sh", (195, 100, 205, 110)), 1.0);
|
||||
assert_eq!(px(&p, 196, 98), [255, 0, 0, 255]);
|
||||
assert_eq!(px(&p, 150, 50), [255, 255, 255, 255]);
|
||||
}
|
||||
}
|
||||
Vendored
+61
-19
@@ -427,6 +427,15 @@ fn snap_minified_image(
|
||||
(w.abs_diff(planned.0) <= 1 && h.abs_diff(planned.1) <= 1).then_some((w, h, x0, y0))
|
||||
}
|
||||
|
||||
/// PdfCraft patch (#3296): what `Renderer::set_paint` found out: the clip path to apply while
|
||||
/// painting (if any), or that the paint touches no pixel of the canvas, so the caller skips
|
||||
/// the fill or stroke (a mesh shading whose triangles all lie outside the path or canvas).
|
||||
#[must_use]
|
||||
enum PaintSetup {
|
||||
Clip(Option<BezPath>),
|
||||
Nothing,
|
||||
}
|
||||
|
||||
pub(crate) struct Renderer {
|
||||
pub(crate) ctx: RenderContext,
|
||||
/// PdfCraft patch: when set, solid text and line-art paint colours are remapped
|
||||
@@ -1093,23 +1102,26 @@ impl Renderer {
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn set_paint(&mut self, paint: &Paint<'_>, path: &BezPath, is_stroke: bool) -> Option<BezPath> {
|
||||
fn set_paint(&mut self, paint: &Paint<'_>, path: &BezPath, is_stroke: bool) -> PaintSetup {
|
||||
// Knockout patch (pdfcraft #2974): in a knockout erase pass every paint clears what is
|
||||
// under its shape (a transparent fill with `Compose::Copy`, weighted by coverage).
|
||||
if self.erasing {
|
||||
self.ctx.set_blend_mode(knockout_blend_mode());
|
||||
self.ctx.set_paint(AlphaColor::<Srgb>::new([0.0, 0.0, 0.0, 0.0]));
|
||||
// A shading's `/BBox` clips what it paints, so it clips what it clears too.
|
||||
return match paint {
|
||||
return PaintSetup::Clip(match paint {
|
||||
Paint::Pattern(p) => match p.as_ref() {
|
||||
Pattern::Shading(s) => s.shading.clip_path.clone(),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
};
|
||||
});
|
||||
}
|
||||
let mut paint_transform = Affine::IDENTITY;
|
||||
let mut clip_path = None;
|
||||
// PdfCraft patch (#3296): `Extend::Repeat` tiles the texture over the path; a mesh
|
||||
// texture covers its triangles alone and must not repeat outside them.
|
||||
let mut extend = peniko::Extend::Repeat;
|
||||
|
||||
let paint: PaintType = match paint.clone() {
|
||||
Paint::Color(c) => {
|
||||
@@ -1142,6 +1154,28 @@ impl Renderer {
|
||||
// pixel around it: texels at a fractional edge look one pixel further).
|
||||
let encoded = s.encode_within(Some(bbox.inflate(1.0, 1.0)));
|
||||
|
||||
// PdfCraft patch (#3296): a mesh paints only where its triangles are.
|
||||
// The texture covers the triangles alone (and a pixel around them,
|
||||
// which stays transparent and is what `Extend::Pad` repeats), not the
|
||||
// whole path (the canvas, for `sh`): sampled pixel by pixel, 310
|
||||
// one-triangle meshes on a plan cost 310 full tiles per tile, and the
|
||||
// watchdog gave the page up. A mesh with no triangle on the canvas
|
||||
// paints nothing at all.
|
||||
if let Some(extent) = encoded.extent {
|
||||
// An empty extent (no triangle in the area) is `Rect::ZERO`, which
|
||||
// sits at the canvas origin: test it before intersecting, or a
|
||||
// 1 × 1 transparent texture would still be painted over the whole
|
||||
// path (310 of them cost 230 ms per tile).
|
||||
if extent.width() <= 0.0 || extent.height() <= 0.0 {
|
||||
return PaintSetup::Nothing;
|
||||
}
|
||||
bbox = bbox.intersect(extent.inflate(1.0, 1.0));
|
||||
if bbox.width() <= 0.0 || bbox.height() <= 0.0 {
|
||||
return PaintSetup::Nothing;
|
||||
}
|
||||
extend = peniko::Extend::Pad;
|
||||
}
|
||||
|
||||
// PdfCraft patch (backport of hayro 0.8): axial and radial shadings
|
||||
// become native gradients instead of a texture sampled pixel by pixel.
|
||||
// Upstream also shifts by half a pixel for vello_cpu 0.3; 0.0.8 already
|
||||
@@ -1157,7 +1191,7 @@ impl Renderer {
|
||||
self.ctx
|
||||
.set_paint_transform(path_transform.inverse() * native.transform);
|
||||
self.ctx.set_paint(native_gradient(native));
|
||||
return clip_path;
|
||||
return PaintSetup::Clip(clip_path);
|
||||
}
|
||||
|
||||
let (image, width, height, transform, may_have_transparency) =
|
||||
@@ -1176,8 +1210,8 @@ impl Renderer {
|
||||
let image = Image {
|
||||
image: ImageSource::Pixmap(Arc::new(pixmap)),
|
||||
sampler: ImageSampler {
|
||||
x_extend: peniko::Extend::Repeat,
|
||||
y_extend: peniko::Extend::Repeat,
|
||||
x_extend: extend,
|
||||
y_extend: extend,
|
||||
quality: ImageQuality::Medium,
|
||||
alpha: 1.0,
|
||||
},
|
||||
@@ -1263,7 +1297,7 @@ impl Renderer {
|
||||
};
|
||||
self.ctx.set_paint_transform(paint_transform);
|
||||
self.ctx.set_paint(PaintType::Image(image));
|
||||
return clip_path;
|
||||
return PaintSetup::Clip(clip_path);
|
||||
}
|
||||
// PdfCraft patch: the cell pixmap is `step × scale` pixels, which only the
|
||||
// u16 cast below bounded (a fuzzed /XStep far beyond /BBox gave 65535² cells).
|
||||
@@ -1341,7 +1375,7 @@ impl Renderer {
|
||||
self.ctx.set_paint_transform(paint_transform);
|
||||
self.ctx.set_paint(paint);
|
||||
|
||||
clip_path
|
||||
PaintSetup::Clip(clip_path)
|
||||
}
|
||||
|
||||
fn stroke_path(
|
||||
@@ -1361,7 +1395,9 @@ impl Renderer {
|
||||
|
||||
// PdfCraft patch: apply the reading-colours mapping before painting.
|
||||
let paint = self.replaced(paint, is_text);
|
||||
let clip_path = self.set_paint(&paint, path, true);
|
||||
let PaintSetup::Clip(clip_path) = self.set_paint(&paint, path, true) else {
|
||||
return;
|
||||
};
|
||||
if let Some(clip_path) = clip_path.as_ref() {
|
||||
self.push_clip_path_inner(clip_path, FillRule::NonZero);
|
||||
}
|
||||
@@ -1390,7 +1426,9 @@ impl Renderer {
|
||||
|
||||
// PdfCraft patch: apply the reading-colours mapping before painting.
|
||||
let paint = self.replaced(paint, is_text);
|
||||
let clip_path = self.set_paint(&paint, path, false);
|
||||
let PaintSetup::Clip(clip_path) = self.set_paint(&paint, path, false) else {
|
||||
return;
|
||||
};
|
||||
if let Some(clip_path) = clip_path.as_ref() {
|
||||
self.push_clip_path_inner(clip_path, fill_rule);
|
||||
}
|
||||
@@ -1621,14 +1659,16 @@ impl<'a> Device<'a> for Renderer {
|
||||
// PdfCraft patch: a stencil mask is painted with the fill
|
||||
// (non-stroking) paint; `true` drew an uncolored tiling pattern
|
||||
// in its stroke color (black by default) (#2975).
|
||||
let clip_path =
|
||||
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);
|
||||
}
|
||||
self.ctx.fill_rect(&stencil_rect);
|
||||
if clip_path.is_some() {
|
||||
self.ctx.pop_clip_path();
|
||||
if let PaintSetup::Clip(clip_path) =
|
||||
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);
|
||||
}
|
||||
self.ctx.fill_rect(&stencil_rect);
|
||||
if clip_path.is_some() {
|
||||
self.ctx.pop_clip_path();
|
||||
}
|
||||
}
|
||||
|
||||
self.ctx.pop_layer();
|
||||
@@ -1847,7 +1887,9 @@ impl<'a> Device<'a> for Renderer {
|
||||
|
||||
// PdfCraft patch: apply the reading-colours mapping before painting.
|
||||
let paint = self.replaced(paint, false);
|
||||
let clip_path = self.set_paint(&paint, &path, false);
|
||||
let PaintSetup::Clip(clip_path) = self.set_paint(&paint, &path, false) else {
|
||||
return;
|
||||
};
|
||||
if let Some(clip_path) = clip_path.as_ref() {
|
||||
self.push_clip_path_inner(clip_path, *fill_rule);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user