mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-02 22:41:47 -06:00
Review on #2974: the interpreter decided with a full-resolution decode (`decoded_raster(None)`) before the renderer decoded at its draw size, a second decode of every downscaled image under another cache key; and an image a preview skips for its size still cleared what was under it. `Device::knockout_image` now lets the renderer decide, with the same eligibility and decode size as `draw_image` (shared `image_draw_size`), so the draw reuses that decode and a skipped image clears nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gv2WwdgjmBz33GWkwgNn3Q
134 lines
6.1 KiB
Rust
134 lines
6.1 KiB
Rust
use crate::font::Glyph;
|
|
use crate::soft_mask::SoftMask;
|
|
use crate::{BlendMode, ClipPath, Image};
|
|
use crate::{GlyphDrawMode, Paint, PathDrawMode};
|
|
use kurbo::{Affine, BezPath, Rect, Shape};
|
|
|
|
/// A trait for a device that can be used to process PDF drawing instructions.
|
|
pub trait Device<'a> {
|
|
/// Set the properties for future stroking operations.
|
|
/// Set a soft mask to be used for future drawing instructions.
|
|
fn set_soft_mask(&mut self, mask: Option<SoftMask<'a>>);
|
|
/// Set the blend mode that should be used for rendering operations.
|
|
fn set_blend_mode(&mut self, blend_mode: BlendMode);
|
|
/// Draw a path.
|
|
fn draw_path(
|
|
&mut self,
|
|
path: &BezPath,
|
|
transform: Affine,
|
|
paint: &Paint<'a>,
|
|
draw_mode: &PathDrawMode,
|
|
);
|
|
/// Push a new clip path to the clip stack.
|
|
fn push_clip_path(&mut self, clip_path: &ClipPath);
|
|
/// Push a new transparency group to the blend stack.
|
|
fn push_transparency_group(
|
|
&mut self,
|
|
opacity: f32,
|
|
mask: Option<SoftMask<'a>>,
|
|
blend_mode: BlendMode,
|
|
);
|
|
/// Draw a glyph.
|
|
fn draw_glyph(
|
|
&mut self,
|
|
glyph: &Glyph<'a>,
|
|
transform: Affine,
|
|
glyph_transform: Affine,
|
|
paint: &Paint<'a>,
|
|
// TODO: Move this into outline glyph.
|
|
draw_mode: &GlyphDrawMode,
|
|
);
|
|
/// Draw an image.
|
|
fn draw_image(&mut self, image: Image<'a, '_>, transform: Affine);
|
|
/// Pop the last clip path from the clip stack.
|
|
fn pop_clip_path(&mut self);
|
|
/// Pop the last transparency group from the blend stack.
|
|
fn pop_transparency_group(&mut self);
|
|
/// Draw a rectangle directly, without going through the general path pipeline.
|
|
fn draw_rect(
|
|
&mut self,
|
|
rect: &Rect,
|
|
transform: Affine,
|
|
paint: &Paint<'a>,
|
|
draw_mode: &PathDrawMode,
|
|
) {
|
|
self.draw_path(&rect.to_path(0.1), transform, paint, draw_mode);
|
|
}
|
|
/// Called at the beginning of a marked content sequence (BMC/BDC).
|
|
///
|
|
/// The tag is the marked content tag (e.g. b"P", b"Span"). The mcid is
|
|
/// the marked content identifier from the properties dict, if present.
|
|
fn begin_marked_content(&mut self, _tag: &[u8], _mcid: Option<i32>) {}
|
|
/// PdfCraft patch (25): the `/ActualText` of the marked content sequence that just began
|
|
/// (ISO 32000-2 §14.9.4), as the text string's raw bytes. Called right after
|
|
/// `begin_marked_content`; the text replaces everything drawn up to the matching
|
|
/// `end_marked_content`.
|
|
fn actual_text(&mut self, _text: &[u8]) {}
|
|
/// Called at the end of a marked content sequence (EMC).
|
|
fn end_marked_content(&mut self) {}
|
|
/// PdfCraft patch (29): the page's annotations are about to be drawn (until
|
|
/// `end_annotations`), as page content finishes. Devices can use this to treat their
|
|
/// paints as annotation colours rather than document colours — the reading-colours
|
|
/// setting replaces document colours only, never the colours a comment was given.
|
|
fn begin_annotations(&mut self) {}
|
|
/// PdfCraft patch (29): the page's annotations are done; what follows is page content.
|
|
fn end_annotations(&mut self) {}
|
|
/// PdfCraft patch (39): the transparency group just pushed is a knockout group (`/K true`):
|
|
/// each of its elements composites with the group's initial backdrop, not with the
|
|
/// elements drawn before it in the group (ISO 32000-2, knockout groups), until the
|
|
/// matching `pop_transparency_group`.
|
|
fn set_knockout(&mut self) {}
|
|
/// PdfCraft patch (39): an element whose shape is `shape` (device space) is about to be
|
|
/// drawn into a transparency group of its own. In a knockout group (`set_knockout`) the
|
|
/// element replaces what the group drew under its shape, so a device clears that first;
|
|
/// the element's layer then composites over the cleared area as usual.
|
|
fn knockout_shape(&mut self, _shape: &BezPath) {}
|
|
/// PdfCraft patch (39): whether `knockout_shape` would clear anything now, that is whether
|
|
/// the innermost transparency group is a knockout group. Lets the interpreter skip work it
|
|
/// only needs for the clear.
|
|
fn in_knockout_group(&mut self) -> bool {
|
|
false
|
|
}
|
|
/// PdfCraft patch (39): `image`, an unmasked raster image, is about to be drawn with
|
|
/// `transform` in a knockout group. A device that will draw it (it isn't skipped and it
|
|
/// decodes) clears what the group drew under its placement rectangle first, so the image
|
|
/// replaces it. Decided by the device, at the size it draws the image, so the draw reuses
|
|
/// that decode.
|
|
fn knockout_image(&mut self, _image: &crate::RasterImage<'_>, _transform: Affine) {}
|
|
/// PdfCraft patch (39): whether a transparency group drawn now is an element of a knockout
|
|
/// group that the device wants cleared under the group's shape first. If so the
|
|
/// interpreter draws the group's content once between `begin_knockout_erase` and
|
|
/// `end_knockout_erase`, in which every paint clears what is under its shape instead of
|
|
/// painting, and then draws the group as usual, in a layer of its own.
|
|
fn knockout_erase_wanted(&mut self) -> bool {
|
|
false
|
|
}
|
|
/// PdfCraft patch (39): see `knockout_erase_wanted`.
|
|
fn begin_knockout_erase(&mut self) {}
|
|
/// PdfCraft patch (39): see `knockout_erase_wanted`.
|
|
fn end_knockout_erase(&mut self) {}
|
|
}
|
|
|
|
/// A device that discards all drawing operations.
|
|
pub struct DummyDevice;
|
|
|
|
impl Device<'_> for DummyDevice {
|
|
fn set_soft_mask(&mut self, _: Option<SoftMask<'_>>) {}
|
|
fn set_blend_mode(&mut self, _: BlendMode) {}
|
|
fn draw_path(&mut self, _: &BezPath, _: Affine, _: &Paint<'_>, _: &PathDrawMode) {}
|
|
fn push_clip_path(&mut self, _: &ClipPath) {}
|
|
fn push_transparency_group(&mut self, _: f32, _: Option<SoftMask<'_>>, _: BlendMode) {}
|
|
fn draw_glyph(
|
|
&mut self,
|
|
_: &Glyph<'_>,
|
|
_: Affine,
|
|
_: Affine,
|
|
_: &Paint<'_>,
|
|
_: &GlyphDrawMode,
|
|
) {
|
|
}
|
|
fn draw_image(&mut self, _: Image<'_, '_>, _: Affine) {}
|
|
fn pop_clip_path(&mut self) {}
|
|
fn pop_transparency_group(&mut self) {}
|
|
}
|