mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-14 22:55:29 +00:00
Changing Preferences › Hyphenation › Exceptions bumped each open document's revision, but the caches of laid-out type outside the text crate were keyed on the font generation alone. The renderer kept the glyph geometry, raster-effect pixels, live-effect and envelope art, pattern tiles and placed symbol art of unchanged type objects, so hyphenated area type kept its old line breaks on the canvas. The hit-test art of blends and envelopes, the Fit to Artwork Bounds answer, the layout caches of the Type and Touch Type tools and the out port's overflow mark were keyed the same way. vectorcraft_text::layout_inputs() returns a number that changes with the fonts and with the hyphenation exceptions. Every one of those caches now keys on it instead of FontDb::generation(), and envelope ink uses it in place of the private envelope_ink_inputs(). The hit-test key hashes it together with the plug-in revision. Tests: a renderer that drew hyphenated area type, alone, with a drop shadow, in a group with a Transform effect, in a symbol instance and in an envelope, draws it as a new renderer does after the exceptions change; the out port of selected area type shows the overflow that the new exceptions cause. The render test helpers that run a step again when the fonts change during it now also run it again when the exceptions change.
304 lines
16 KiB
Rust
304 lines
16 KiB
Rust
//! VectorCraft renderer: document → premultiplied RGBA pixels.
|
|
//!
|
|
//! The backend is `vello_cpu` (SIMD, sparse strips). Callers give a *view transform* mapping
|
|
//! document points to output pixels; the renderer culls by bounds, evaluates appearance stacks
|
|
//! (multiple fills/strokes, opacity, blend modes, stroke alignment, dashes), clip groups,
|
|
//! gradients, images and text (via `vectorcraft-text` glyph outlines).
|
|
#![forbid(unsafe_code)]
|
|
|
|
pub mod addons;
|
|
mod brush_fx;
|
|
mod changed;
|
|
mod dither;
|
|
pub mod encode;
|
|
mod freeform;
|
|
mod fx;
|
|
mod group;
|
|
mod images;
|
|
pub use images::{image_cached, keep_decoded_image, physical_memory};
|
|
mod ink;
|
|
mod live;
|
|
mod lru;
|
|
mod paint;
|
|
pub use paint::{decode_failures, image_decodes, reduced_size};
|
|
mod pattern;
|
|
pub mod placed_document;
|
|
pub mod proof;
|
|
mod stroke;
|
|
|
|
use std::collections::HashMap;
|
|
use std::sync::Arc;
|
|
|
|
use vectorcraft_doc::text::TypeAntiAlias;
|
|
use vectorcraft_doc::{AppearanceItem, Document, Node, NodeId, NodeKind, StrokeAlign, StrokeLayer, SymbolBoxes, TextObject, TraceView};
|
|
use vectorcraft_geom::{Affine, BezPath, FillRule, Rect, Shape};
|
|
use vello_cpu::kurbo;
|
|
use vello_cpu::peniko::{self, BlendMode, Compose, Mix};
|
|
use vello_cpu::{Pixmap, RenderContext, Resources};
|
|
|
|
use group::Composite;
|
|
use ink::Ink;
|
|
use lru::FrameCache;
|
|
|
|
pub use brush_fx::instance_art;
|
|
pub use changed::{Changed, changed, placed_area, view_pixels};
|
|
pub use effects::stroke::width_outline;
|
|
pub use images::{IMAGE_CACHE_MAX, image_cache_bytes, installed_memory, newly_undecodable, set_image_cache_budget};
|
|
pub use live::expand_live;
|
|
pub use pattern::render_pattern_swatch;
|
|
pub use vectorcraft_effects as effects;
|
|
pub use vello_cpu;
|
|
|
|
/// Art made to measure an object ([`Renderer::measured`]): the object, its art, and the key of
|
|
/// what the art read from outside the object (`None`: nothing).
|
|
type MeasuredArt = (Arc<Node>, Arc<Node>, Option<u64>);
|
|
|
|
/// A 3D effect's art ([`Renderer::three_d_bounds`]), held weakly, and its bounds once measured at a
|
|
/// raster effects resolution (its bits) with a brush library (its key).
|
|
type ThreeDArt = (std::sync::Weak<Node>, Option<((u64, u64), Option<Rect>)>);
|
|
|
|
/// Reusable renderer (keeps the render context, geometry and glyph caches between frames; decoded
|
|
/// images are shared by all renderers, see [`set_image_cache_budget`]).
|
|
pub struct Renderer {
|
|
/// Glyph geometry per text object, keyed by `Arc` identity like `geom` (see [`Self::text_geom_of`]).
|
|
texts: FrameCache<usize, (Arc<Node>, Arc<TextGeom>)>,
|
|
/// The latest cached glyph geometry for each text object id. A move changes the node's Arc
|
|
/// but not its glyphs in text space, so the old geometry can still be used after comparison.
|
|
text_ids: PtrMap<NodeId, usize>,
|
|
/// The [`vectorcraft_text::layout_inputs`] `texts` was laid out with: type lays out again
|
|
/// when fonts are added or rescanned and when the hyphenation exceptions change.
|
|
text_fonts: u64,
|
|
/// Clip regions per clipping path (see [`Self::clip_of`]).
|
|
clips: FrameCache<usize, ClipEntry>,
|
|
/// Worker threads for the multithreaded rasterizer (0 = single-threaded).
|
|
pub threads: u16,
|
|
geom: PtrMap<usize, GeomEntry>,
|
|
/// The paths of long paths and compound paths, by object id (see [`Self::kept_path`]): after
|
|
/// an edit, `geom` makes the new object's path and bounds from the path kept for its id.
|
|
kept: PtrMap<NodeId, kept_paths::KeptPath>,
|
|
/// The symbols (name and art) of the document last drawn, which `geom`'s bounds measured
|
|
/// symbol instances by (see [`Self::check_symbols`]). The art is held weakly: the reference
|
|
/// keeps the art's allocation, so no other art can get its address, and does not keep the
|
|
/// art's content.
|
|
symbols: Vec<(String, std::sync::Weak<Node>)>,
|
|
/// The plug-in revision ([`effects::plugin_revision`]) that `geom`, `fx_geom` and `fx_arts` were
|
|
/// last checked against (see [`Self::check_plugins`]).
|
|
plugin_revision: u64,
|
|
/// The key of the brush library ([`brush_fx::BrushCache::library_key`]) when `geom`'s bounds of
|
|
/// 3D art were measured with it (see [`Self::check_symbols`]): set by the first such measure
|
|
/// and kept, whether or not those bounds still are, until the library changes. `None` until
|
|
/// then, and nothing is checked.
|
|
brush_library: Option<u64>,
|
|
/// Is a live-effect plug-in that paints its pieces installed (found when `plugin_revision`
|
|
/// changes)?
|
|
painting_plugins: bool,
|
|
/// The hash of the document's swatch and pattern names at the last check, kept while
|
|
/// `painting_plugins` holds (see [`Self::check_plugins`]).
|
|
paint_names: u64,
|
|
/// Expanded stroke outlines keyed by ([`Self::stroke_owner`], stroke layer address, width bits,
|
|
/// tolerance level): panning re-renders reuse them instead of re-expanding every stroke.
|
|
strokes: PtrMap<StrokeKey, StrokeEntry>,
|
|
/// The key in `strokes` of the latest outline of each stroke of an object, under its
|
|
/// [`frame::MovedKey`]: a moved copy of the object draws that outline moved ([`Self::moved_outline`]).
|
|
moved_strokes: PtrMap<frame::MovedKey, StrokeKey>,
|
|
/// Expanded character stroke outlines, with the glyph geometry kept in `texts` they belong to,
|
|
/// keyed by ([`Self::stroke_owner`] of that geometry, run, width bits, tolerance level): type
|
|
/// that doesn't change draws them again instead of expanding its strokes in every frame. Within
|
|
/// a byte budget, and swept by frame, so the outlines of type that changes in every frame go
|
|
/// within a few frames.
|
|
char_strokes: FrameCache<StrokeKey, (Arc<TextGeom>, Arc<BezPath>)>,
|
|
/// The node being drawn on the plain-path fast path, or the path of brush art being drawn
|
|
/// ([`Self::draw_brush_pieces`]): the owner of cached stroke outlines.
|
|
cur: Option<Arc<Node>>,
|
|
/// Whether the art being drawn is made again in every frame: symbol art that
|
|
/// [`Self::placed_art`] did not keep, evaluations of live objects and effect art that their
|
|
/// caches did not keep, and brush art that the brush cache did not keep.
|
|
made_again: bool,
|
|
/// The node `draw_shape_fx` may cache the live-effect geometry of (the one `draw_arc` is drawing).
|
|
/// In Outline view its path comes from `geom` too ([`Self::outline_path`]), and
|
|
/// [`Self::draw_outline_member`] sets it to each member of a clip group or shape of an Image
|
|
/// Trace object that it draws.
|
|
fx_owner: Option<Arc<Node>>,
|
|
/// The live object [`Self::draw_kept_node`] is drawing (kept symbol art, an opacity mask's art,
|
|
/// a member of a clip group in Outline view, kept brush art): [`Self::draw_content`] draws its
|
|
/// evaluation from the live cache.
|
|
live_root: Option<Arc<Node>>,
|
|
/// Live-effect geometry per node ([`fx::FxGeom`]), keyed by `Arc` identity like `geom`.
|
|
fx_geom: PtrMap<usize, fx::FxGeom>,
|
|
stamp: u64,
|
|
/// Opacity folded into paint alpha for the leaf being drawn (avoids a compositing layer).
|
|
alpha: f32,
|
|
/// The contexts kept for the next frames, with the threads they draw on, the last used last: a
|
|
/// frame of the size of one reuses it instead of making a context and its threads. Two are
|
|
/// kept, so a view's frames and the parts of them drawn again ([`changed`]) each keep theirs.
|
|
contexts: Vec<(u16, RenderContext)>,
|
|
resources: Resources,
|
|
/// Statistics of the last frame.
|
|
pub stats: FrameStats,
|
|
/// Brush art per brushed stroke.
|
|
brushes: brush_fx::BrushCache,
|
|
/// Evaluated blends/envelopes and tessellated meshes.
|
|
live: live::LiveCache,
|
|
/// Blurred, tinted drop shadow / outer glow rasters per object and effect (see `fx`).
|
|
shadows: PtrMap<(usize, usize, Ink), fx::ShadowEntry>,
|
|
/// Objects' content run through Photoshop-style effects, per object and content slot (see
|
|
/// `fx`): kept only while drawn.
|
|
pixel_fx: PtrMap<(usize, usize, Ink), fx::PixelEntry>,
|
|
/// Type drawn with an anti-aliasing method other than Sharp, per object and ink (see
|
|
/// [`Self::draw_type_edges`]).
|
|
type_edges: FrameCache<(usize, Ink), TypeEdgeEntry>,
|
|
/// What the contents of those rasters read from the document, and the color settings, as last
|
|
/// found for a frame (`stamp`) and document (address) (see [`Self::doc_inputs`]).
|
|
doc_inputs: Option<((u64, usize), Arc<fx::DocInputs>)>,
|
|
/// Whether a document's layers, symbol art and pattern art show transparency, as last found
|
|
/// (see [`Self::blends_in_cmyk`]), with that art in order, held weakly: a weak reference keeps
|
|
/// the allocation, so no other art gets one of those addresses while the answer is kept.
|
|
transparency: Option<(Vec<std::sync::Weak<Node>>, bool)>,
|
|
/// Whether the group being drawn is a knockout group (what its neutral children inherit).
|
|
knockout: bool,
|
|
/// Address of the knockout-group element being drawn as its knockout shape: at full object
|
|
/// opacity and without its opacity mask (see [`Self::draw_knockout`]); 0 = none.
|
|
shape_of: usize,
|
|
/// The art drawn for objects with effects that apply through it (see `fx::has_object_fx`).
|
|
fx_arts: FrameCache<usize, FxArtEntry>,
|
|
/// The art of objects with object effects made to measure them ([`Renderer::bounds_of`]) and
|
|
/// not drawn yet, by the object's address, with the object and the key of what it read from
|
|
/// outside the object when it was made (`None`: nothing): drawing one keeps it in `fx_arts`
|
|
/// while that is unchanged; the frame's end lets go of the rest.
|
|
measured: PtrMap<usize, MeasuredArt>,
|
|
/// The bytes of the art in `measured` ([`lru::node_bytes`]), within `measured_budget`
|
|
/// ([`lru::MEASURED_BUDGET`]).
|
|
measured_bytes: usize,
|
|
measured_budget: usize,
|
|
/// The export bounds of the objects measured without their hidden members
|
|
/// (`encode::drawn_bounds`), by the object they were made from: kept for a pass over artboards,
|
|
/// let go when a frame ends.
|
|
pub(crate) pass_bounds: PtrMap<usize, (Arc<Node>, Option<Rect>)>,
|
|
/// The 3D effects' art among the art `fx_art` made, by its address, with the art (weakly: what
|
|
/// keeps the art is `fx_arts` and `measured`) and its bounds once measured. An entry is added
|
|
/// for every 3D art made, measured or not. Entries whose art is gone are swept when a frame
|
|
/// ends (`let_go_of_measured`) and, for a renderer that only measures, when the map reaches
|
|
/// twice the size it kept at the last sweep (at least 1024): it holds at most max(twice the
|
|
/// entries kept at the last sweep, 1024). Until swept, an entry's weak pointer keeps the art's
|
|
/// allocation (not its contents) alive.
|
|
three_d_bounds: PtrMap<usize, ThreeDArt>,
|
|
/// How many entries `three_d_bounds` kept when it last let go of the art no longer held: it
|
|
/// does so again when it holds twice as many.
|
|
three_d_kept: usize,
|
|
/// How many times `three_d_bounds` was swept for art no longer held between frames.
|
|
#[cfg(test)]
|
|
three_d_sweeps: usize,
|
|
/// Symbol art as instances and inline graphics draw it (see [`Self::placed_art`]).
|
|
placed_arts: FrameCache<PlacedKey, PlacedEntry>,
|
|
/// The bitmap clock of [`placed_document`] when this frame first drew a placed document from
|
|
/// a bitmap: the bitmaps drawn since then are not dropped to make room for those it asks for.
|
|
placed_frame: Option<u64>,
|
|
/// Layers open around the drawing point in the current context: a non-isolated group can copy
|
|
/// its backdrop only when there are none (see [`Self::group`]).
|
|
nested: u32,
|
|
/// Copy of the backdrop of the non-isolated group being drawn offscreen, in its context's
|
|
/// pixels: the elements of a non-isolated knockout group composite against it.
|
|
backdrop: Option<Arc<Pixmap>>,
|
|
/// [`Node::blends_through`] per container, keyed like `geom`.
|
|
blends: PtrMap<usize, (Arc<Node>, bool, u64)>,
|
|
/// Clip paths pushed on the current context (path, rule, transform): starting the context
|
|
/// again from a picture of it pushes them again (see [`Self::group`]).
|
|
clip_paths: Vec<(BezPath, FillRule, Affine)>,
|
|
/// The picture [`Self::flatten`] started the current context again from, with 8-bit
|
|
/// compositing: [`Self::finish`] renders the context over it.
|
|
picture: Option<Arc<Pixmap>>,
|
|
/// Whether an image failed to draw while opacity-mask art is being drawn.
|
|
image_left_out: Option<bool>,
|
|
/// Pixels of the groups being drawn offscreen (contexts and backdrop copies), and the most
|
|
/// they may hold (set from the frame when the first opens, see [`Self::group`]); both reset
|
|
/// at the start of every frame.
|
|
offscreen_live: u64,
|
|
offscreen_budget: u64,
|
|
/// Objects the symbol instances drawn in this frame held, together ([`vectorcraft_doc::MAX_SYMBOL_NODES`]).
|
|
symbol_nodes: u64,
|
|
/// Groups drawn twice (blending with their backdrop) that hold what is being drawn.
|
|
twice_drawn: u32,
|
|
/// What clipping paths paint, per clipping path (see [`Self::clip_paint_of`]).
|
|
clip_paints: FrameCache<usize, ClipPaintEntry>,
|
|
/// Gradients along or across strokes (see [`Self::fill_path_gradient`]), keyed by
|
|
/// ([`Self::stroke_owner`], stroke layer address, tolerance level).
|
|
stroke_slices: PtrMap<frame::SliceKey, SliceEntry>,
|
|
/// The key in `stroke_slices` of the latest slices of each stroke of an object, under its
|
|
/// [`frame::MovedSliceKey`]: a moved copy of the object draws those slices moved
|
|
/// ([`Self::moved_slices`]).
|
|
moved_slices: PtrMap<frame::MovedSliceKey, frame::SliceKey>,
|
|
/// What the image keys colour adjustments give their recoloured images name: the image
|
|
/// recoloured and the adjustment, with its hash (see [`Self::adjusted_image`]).
|
|
adjusts: HashMap<String, (String, u64, vectorcraft_effects::ColorMap)>,
|
|
/// Layer Options → Dim Images to, of the layer being drawn (screen views only): images show
|
|
/// faded to this opacity over white.
|
|
dim_images: Option<f32>,
|
|
/// Inline graphics being drawn inside inline graphics (a symbol whose art holds text showing
|
|
/// it): drawing stops at [`MAX_INLINE_DEPTH`].
|
|
inline_depth: u32,
|
|
/// How the frame being drawn is rasterized ([`RenderOptions::precise`]): its offscreen groups,
|
|
/// masks and patterns too.
|
|
raster: vello_cpu::RasterizerSettings,
|
|
/// [`RenderOptions::isolated`]'s layers and groups, from the top layer down to it, for the
|
|
/// frame (`stamp`) and document they were found in.
|
|
isolation: Option<(u64, usize, NodeId, Vec<NodeId>)>,
|
|
/// Drawing inside the isolated container or a dimmed object: nothing further dims.
|
|
isolation_settled: bool,
|
|
/// The art the last frame drew with Overprint Preview or Separations Preview: the next frame
|
|
/// draws it again for the objects that have not changed ([`proof::prepare`]).
|
|
proof_art: Option<proof::Prepared>,
|
|
/// Width of [`Self::hairline`] in the pixels being drawn: 1, or the supersampling factor while
|
|
/// a supersampled render draws its larger frame, so outlines stay one output pixel wide. Type
|
|
/// outlines and dotted guides scale by it too.
|
|
hairline: f64,
|
|
}
|
|
|
|
impl Default for Renderer {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
use tests::fontchange as tests_fontchange;
|
|
|
|
mod blobs;
|
|
mod bounds;
|
|
mod clock;
|
|
mod frame;
|
|
mod geometry;
|
|
mod kept_paths;
|
|
mod node_checks;
|
|
mod options;
|
|
mod renderer;
|
|
mod text_geom;
|
|
mod threads;
|
|
|
|
pub use bounds::{drawn_bounds, drawn_outline, drawn_outlines, effect_bounds, painted_bounds};
|
|
pub use frame::FrameStats;
|
|
use frame::{
|
|
ClipEntry, ClipPaintEntry, Frame, FxArtEntry, GeomEntry, PlacedEntry, PlacedKey, PtrHasher, PtrMap, SliceEntry, StrokeEntry, StrokeKey,
|
|
TypeEdgeEntry,
|
|
};
|
|
#[cfg(test)]
|
|
use frame::{OFFSCREEN_PIXELS, Slices};
|
|
pub use geometry::region_pixels;
|
|
pub use node_checks::draws_plugin_effects;
|
|
use node_checks::holds_plugin_effect;
|
|
#[cfg(test)]
|
|
use node_checks::painted_items;
|
|
pub use options::{AntiAlias, ISOLATION_DIM, MAX_RASTER_PIXELS, MAX_RASTER_SIDE, RenderOptions, Rendered, raster_size, supersample_factor};
|
|
pub(crate) use paint::blend_mode;
|
|
use paint::fill_rule;
|
|
#[cfg(test)]
|
|
use renderer::{edge_coverage, edge_grid, type_reach};
|
|
use text_geom::TextGeom;
|
|
#[cfg(test)]
|
|
pub(crate) use text_geom::is_opentype_alternate;
|
|
#[cfg(test)]
|
|
use text_geom::text_geom;
|
|
pub(crate) use threads::single_threaded_context;
|
|
pub use threads::{default_threads, set_default_threads, set_usable_cores};
|
|
#[cfg(test)]
|
|
mod tests;
|