diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-01-24 16:48:24 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-01-24 17:39:49 +0100 |
| commit | 3739ab77207e0e54edb55a110a16a1eb925b84f4 (patch) | |
| tree | 06c8e5987d2fe070ad273ef94641161bbaef7095 /src | |
| parent | db158719d67fdef1d2c76300fb232cf2d4bfb35d (diff) | |
Export into rendered images
Diffstat (limited to 'src')
| -rw-r--r-- | src/diag.rs | 6 | ||||
| -rw-r--r-- | src/export/mod.rs | 3 | ||||
| -rw-r--r-- | src/export/pdf.rs | 6 | ||||
| -rw-r--r-- | src/export/render.rs | 515 | ||||
| -rw-r--r-- | src/font.rs | 16 | ||||
| -rw-r--r-- | src/frame.rs | 20 | ||||
| -rw-r--r-- | src/geom/angle.rs | 1 | ||||
| -rw-r--r-- | src/geom/em.rs | 1 | ||||
| -rw-r--r-- | src/geom/length.rs | 2 | ||||
| -rw-r--r-- | src/geom/mod.rs | 2 | ||||
| -rw-r--r-- | src/geom/paint.rs | 6 | ||||
| -rw-r--r-- | src/geom/path.rs | 5 | ||||
| -rw-r--r-- | src/geom/point.rs | 2 | ||||
| -rw-r--r-- | src/geom/relative.rs | 1 | ||||
| -rw-r--r-- | src/geom/scalar.rs | 3 | ||||
| -rw-r--r-- | src/geom/spec.rs | 2 | ||||
| -rw-r--r-- | src/geom/transform.rs | 2 | ||||
| -rw-r--r-- | src/image.rs | 16 | ||||
| -rw-r--r-- | src/layout/mod.rs | 8 | ||||
| -rw-r--r-- | src/lib.rs | 10 | ||||
| -rw-r--r-- | src/loading/fs.rs | 3 | ||||
| -rw-r--r-- | src/source.rs | 4 | ||||
| -rw-r--r-- | src/syntax/span.rs | 6 |
23 files changed, 556 insertions, 84 deletions
diff --git a/src/diag.rs b/src/diag.rs index f0efa500..be431e12 100644 --- a/src/diag.rs +++ b/src/diag.rs @@ -2,8 +2,6 @@ use std::fmt::{self, Display, Formatter}; -use serde::{Deserialize, Serialize}; - use crate::syntax::{Span, Spanned}; /// Early-return with a vec-boxed [`Error`]. @@ -24,7 +22,7 @@ pub type TypResult<T> = Result<T, Box<Vec<Error>>>; pub type StrResult<T> = Result<T, String>; /// An error in a source file. -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct Error { /// The erroneous location in the source code. pub span: Span, @@ -52,7 +50,7 @@ impl Error { } /// A part of an error's [trace](Error::trace). -#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)] +#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] pub enum Tracepoint { /// A function call. Call(Option<String>), diff --git a/src/export/mod.rs b/src/export/mod.rs index d3836859..b782ae13 100644 --- a/src/export/mod.rs +++ b/src/export/mod.rs @@ -1,7 +1,8 @@ //! Exporting into external formats. mod pdf; +mod render; mod subset; pub use pdf::*; -pub use subset::*; +pub use render::*; diff --git a/src/export/pdf.rs b/src/export/pdf.rs index f0fd10f6..1477e283 100644 --- a/src/export/pdf.rs +++ b/src/export/pdf.rs @@ -12,7 +12,7 @@ use pdf_writer::types::{ use pdf_writer::{Content, Filter, Finish, Name, PdfWriter, Rect, Ref, Str, TextStr}; use ttf_parser::{name_id, GlyphId, Tag}; -use super::subset; +use super::subset::subset; use crate::font::{find_name, FaceId, FontStore}; use crate::frame::{Element, Frame, Geometry, Group, Shape, Stroke, Text}; use crate::geom::{self, Color, Em, Length, Paint, Point, Size, Transform}; @@ -22,8 +22,8 @@ use crate::Context; /// Export a collection of frames into a PDF file. /// /// This creates one page per frame. In addition to the frames, you need to pass -/// in the context used during compilation such that things like fonts and -/// images can be included in the PDF. +/// in the context used during compilation so that fonts and images can be +/// included in the PDF. /// /// Returns the raw bytes making up the PDF file. pub fn pdf(ctx: &Context, frames: &[Rc<Frame>]) -> Vec<u8> { diff --git a/src/export/render.rs b/src/export/render.rs new file mode 100644 index 00000000..c41bcbf2 --- /dev/null +++ b/src/export/render.rs @@ -0,0 +1,515 @@ +//! Rendering into raster images. + +use std::collections::{hash_map::Entry, HashMap}; +use std::io::Read; + +use image::{GenericImageView, Rgba}; +use tiny_skia as sk; +use ttf_parser::{GlyphId, OutlineBuilder}; +use usvg::FitTo; + +use crate::font::{Face, FaceId}; +use crate::frame::{Element, Frame, Geometry, Group, Shape, Stroke, Text}; +use crate::geom::{self, Color, Length, Paint, PathElement, Size, Transform}; +use crate::image::{Image, RasterImage, Svg}; +use crate::Context; + +/// Caches rendering artifacts. +#[derive(Default, Clone)] +pub struct RenderCache { + /// Glyphs prepared for rendering. + glyphs: HashMap<(FaceId, GlyphId), pixglyph::Glyph>, +} + +impl RenderCache { + /// Create a new, empty rendering cache. + pub fn new() -> Self { + Self::default() + } +} + +/// Export a frame into a rendered image. +/// +/// This renders the frame at the given number of pixels per printer's point and +/// returns the resulting `tiny-skia` pixel buffer. +/// +/// In addition to the frame, you need to pass in the context used during +/// compilation so that fonts and images can be rendered and rendering artifacts +/// can be cached. +pub fn render(ctx: &mut Context, frame: &Frame, pixel_per_pt: f32) -> sk::Pixmap { + let pxw = (pixel_per_pt * frame.size.x.to_f32()).round().max(1.0) as u32; + let pxh = (pixel_per_pt * frame.size.y.to_f32()).round().max(1.0) as u32; + + let mut canvas = sk::Pixmap::new(pxw, pxh).unwrap(); + canvas.fill(sk::Color::WHITE); + + let ts = sk::Transform::from_scale(pixel_per_pt, pixel_per_pt); + render_frame(&mut canvas, ts, None, ctx, frame); + + canvas +} + +/// Render all elements in a frame into the canvas. +fn render_frame( + canvas: &mut sk::Pixmap, + ts: sk::Transform, + mask: Option<&sk::ClipMask>, + ctx: &mut Context, + frame: &Frame, +) { + for (pos, element) in &frame.elements { + let x = pos.x.to_f32(); + let y = pos.y.to_f32(); + let ts = ts.pre_translate(x, y); + + match *element { + Element::Group(ref group) => { + render_group(canvas, ts, mask, ctx, group); + } + Element::Text(ref text) => { + render_text(canvas, ts, mask, ctx, text); + } + Element::Shape(ref shape) => { + render_shape(canvas, ts, mask, shape); + } + Element::Image(id, size) => { + render_image(canvas, ts, mask, ctx.images.get(id), size); + } + Element::Link(_, _) => {} + } + } +} + +/// Render a group frame with optional transform and clipping into the canvas. +fn render_group( + canvas: &mut sk::Pixmap, + ts: sk::Transform, + mask: Option<&sk::ClipMask>, + ctx: &mut Context, + group: &Group, +) { + let ts = ts.pre_concat(group.transform.into()); + + let mut mask = mask; + let mut storage; + if group.clips { + let w = group.frame.size.x.to_f32(); + let h = group.frame.size.y.to_f32(); + if let Some(path) = sk::Rect::from_xywh(0.0, 0.0, w, h) + .map(sk::PathBuilder::from_rect) + .and_then(|path| path.transform(ts)) + { + let result = if let Some(mask) = mask { + storage = mask.clone(); + storage.intersect_path(&path, sk::FillRule::default(), false) + } else { + let pxw = canvas.width(); + let pxh = canvas.height(); + storage = sk::ClipMask::new(); + storage.set_path(pxw, pxh, &path, sk::FillRule::default(), false) + }; + + // Clipping fails if clipping rect is empty. In that case we just + // clip everything by returning. + if result.is_none() { + return; + } + + mask = Some(&storage); + } + } + + render_frame(canvas, ts, mask, ctx, &group.frame); +} + +/// Render a text run into the canvas. +fn render_text( + canvas: &mut sk::Pixmap, + ts: sk::Transform, + mask: Option<&sk::ClipMask>, + ctx: &mut Context, + text: &Text, +) { + let face = ctx.fonts.get(text.face_id); + let cache = &mut ctx.render_cache; + + let mut x = 0.0; + for glyph in &text.glyphs { + let id = GlyphId(glyph.id); + let offset = x + glyph.x_offset.resolve(text.size).to_f32(); + let ts = ts.pre_translate(offset, 0.0); + + render_svg_glyph(canvas, ts, mask, text, face, id) + .or_else(|| render_bitmap_glyph(canvas, ts, mask, text, face, id)) + .or_else(|| render_outline_glyph(canvas, ts, mask, cache, text, face, id)); + + x += glyph.x_advance.resolve(text.size).to_f32(); + } +} + +/// Render an SVG glyph into the canvas. +fn render_svg_glyph( + canvas: &mut sk::Pixmap, + ts: sk::Transform, + _: Option<&sk::ClipMask>, + text: &Text, + face: &Face, + id: GlyphId, +) -> Option<()> { + let mut data = face.ttf().glyph_svg_image(id)?; + + // Decompress SVGZ. + let mut decoded = vec![]; + if data.starts_with(&[0x1f, 0x8b]) { + let mut decoder = flate2::read::GzDecoder::new(data); + decoder.read_to_end(&mut decoded).ok()?; + data = &decoded; + } + + // Parse XML. + let src = std::str::from_utf8(data).ok()?; + let document = roxmltree::Document::parse(src).ok()?; + let root = document.root_element(); + + // Parse SVG. + let opts = usvg::Options::default(); + let tree = usvg::Tree::from_xmltree(&document, &opts.to_ref()).ok()?; + let view_box = tree.svg_node().view_box.rect; + + // If there's no viewbox defined, use the em square for our scale + // transformation ... + let upem = face.units_per_em as f32; + let (mut width, mut height) = (upem, upem); + + // ... but if there's a viewbox or width, use that. + if root.has_attribute("viewBox") || root.has_attribute("width") { + width = view_box.width() as f32; + } + + // Same as for width. + if root.has_attribute("viewBox") || root.has_attribute("height") { + height = view_box.height() as f32; + } + + // FIXME: This doesn't respect the clipping mask. + let size = text.size.to_f32(); + let ts = ts.pre_scale(size / width, size / height); + resvg::render(&tree, FitTo::Original, ts, canvas.as_mut()) +} + +/// Render a bitmap glyph into the canvas. +fn render_bitmap_glyph( + canvas: &mut sk::Pixmap, + ts: sk::Transform, + mask: Option<&sk::ClipMask>, + text: &Text, + face: &Face, + id: GlyphId, +) -> Option<()> { + let size = text.size.to_f32(); + let ppem = size * ts.sy; + let raster = face.ttf().glyph_raster_image(id, ppem as u16)?; + let img = RasterImage::parse(&raster.data).ok()?; + + // FIXME: Vertical alignment isn't quite right for Apple Color Emoji, + // and maybe also for Noto Color Emoji. And: Is the size calculation + // correct? + let h = text.size; + let w = (img.width() as f64 / img.height() as f64) * h; + let dx = (raster.x as f32) / (img.width() as f32) * size; + let dy = (raster.y as f32) / (img.height() as f32) * size; + let ts = ts.pre_translate(dx, -size - dy); + render_image(canvas, ts, mask, &Image::Raster(img), Size::new(w, h)) +} + +/// Render an outline glyph into the canvas. This is the "normal" case. +fn render_outline_glyph( + canvas: &mut sk::Pixmap, + ts: sk::Transform, + mask: Option<&sk::ClipMask>, + cache: &mut RenderCache, + text: &Text, + face: &Face, + id: GlyphId, +) -> Option<()> { + let ppem = text.size.to_f32() * ts.sy; + + // Render a glyph directly as a path. This only happens when the fast glyph + // rasterization can't be used due to very large text size or weird + // scale/skewing transforms. + if ppem > 100.0 || ts.kx != 0.0 || ts.ky != 0.0 || ts.sx != ts.sy { + let path = { + let mut builder = WrappedPathBuilder(sk::PathBuilder::new()); + face.ttf().outline_glyph(id, &mut builder)?; + builder.0.finish()? + }; + + let paint = text.fill.into(); + let rule = sk::FillRule::default(); + + // Flip vertically because font design coordinate + // system is Y-up. + let scale = text.size.to_f32() / face.units_per_em as f32; + let ts = ts.pre_scale(scale, -scale); + canvas.fill_path(&path, &paint, rule, ts, mask)?; + return Some(()); + } + + // Try to retrieve a prepared glyph or prepare it from scratch if it + // doesn't exist, yet. + let glyph = match cache.glyphs.entry((text.face_id, id)) { + Entry::Occupied(entry) => entry.into_mut(), + Entry::Vacant(entry) => { + let glyph = pixglyph::Glyph::load(face.ttf(), id)?; + entry.insert(glyph) + } + }; + + // Rasterize the glyph with `pixglyph`. + let bitmap = glyph.rasterize(ts.tx, ts.ty, ppem); + let cw = canvas.width() as i32; + let ch = canvas.height() as i32; + let mw = bitmap.width as i32; + let mh = bitmap.height as i32; + + // Determine the pixel bounding box that we actually need to draw. + let left = bitmap.left; + let right = left + mw; + let top = bitmap.top; + let bottom = top + mh; + + // Premultiply the text color. + let Paint::Solid(Color::Rgba(c)) = text.fill; + let color = sk::ColorU8::from_rgba(c.r, c.g, c.b, 255).premultiply().get(); + + // Blend the glyph bitmap with the existing pixels on the canvas. + // FIXME: This doesn't respect the clipping mask. + let pixels = bytemuck::cast_slice_mut::<u8, u32>(canvas.data_mut()); + for x in left.clamp(0, cw) .. right.clamp(0, cw) { + for y in top.clamp(0, ch) .. bottom.clamp(0, ch) { + let ai = ((y - top) * mw + (x - left)) as usize; + let cov = bitmap.coverage[ai]; + if cov == 0 { + continue; + } + + let pi = (y * cw + x) as usize; + if cov == 255 { + pixels[pi] = color; + continue; + } + + let applied = alpha_mul(color, cov as u32); + pixels[pi] = blend_src_over(applied, pixels[pi]); + } + } + + Some(()) +} + +/// Renders a geometrical shape into the canvas. +fn render_shape( + canvas: &mut sk::Pixmap, + ts: sk::Transform, + mask: Option<&sk::ClipMask>, + shape: &Shape, +) -> Option<()> { + let path = match shape.geometry { + Geometry::Rect(size) => { + let w = size.x.to_f32(); + let h = size.y.to_f32(); + let rect = sk::Rect::from_xywh(0.0, 0.0, w, h)?; + sk::PathBuilder::from_rect(rect) + } + Geometry::Ellipse(size) => convert_path(&geom::Path::ellipse(size))?, + Geometry::Line(target) => { + let mut builder = sk::PathBuilder::new(); + builder.line_to(target.x.to_f32(), target.y.to_f32()); + builder.finish()? + } + Geometry::Path(ref path) => convert_path(path)?, + }; + + if let Some(fill) = shape.fill { + let mut paint: sk::Paint = fill.into(); + if matches!(shape.geometry, Geometry::Rect(_)) { + paint.anti_alias = false; + } + + let rule = sk::FillRule::default(); + canvas.fill_path(&path, &paint, rule, ts, mask); + } + + if let Some(Stroke { paint, thickness }) = shape.stroke { + let paint = paint.into(); + let mut stroke = sk::Stroke::default(); + stroke.width = thickness.to_f32(); + canvas.stroke_path(&path, &paint, &stroke, ts, mask); + } + + Some(()) +} + +/// Renders a raster or SVG image into the canvas. +fn render_image( + canvas: &mut sk::Pixmap, + ts: sk::Transform, + mask: Option<&sk::ClipMask>, + img: &Image, + size: Size, +) -> Option<()> { + let view_width = size.x.to_f32(); + let view_height = size.y.to_f32(); + + let pixmap = match img { + Image::Raster(img) => { + let w = img.buf.width(); + let h = img.buf.height(); + let mut pixmap = sk::Pixmap::new(w, h)?; + for ((_, _, src), dest) in img.buf.pixels().zip(pixmap.pixels_mut()) { + let Rgba([r, g, b, a]) = src; + *dest = sk::ColorU8::from_rgba(r, g, b, a).premultiply(); + } + pixmap + } + Image::Svg(Svg(tree)) => { + let size = tree.svg_node().size; + let aspect = (size.width() / size.height()) as f32; + let scale = ts.sx.max(ts.sy); + let w = (scale * view_width.max(aspect * view_height)).ceil() as u32; + let h = ((w as f32) / aspect).ceil() as u32; + let mut pixmap = sk::Pixmap::new(w, h)?; + resvg::render( + &tree, + FitTo::Size(w, h), + sk::Transform::identity(), + pixmap.as_mut(), + ); + pixmap + } + }; + + let scale_x = view_width / pixmap.width() as f32; + let scale_y = view_height / pixmap.height() as f32; + + let mut paint = sk::Paint::default(); + paint.shader = sk::Pattern::new( + pixmap.as_ref(), + sk::SpreadMode::Pad, + sk::FilterQuality::Bilinear, + 1.0, + sk::Transform::from_scale(scale_x, scale_y), + ); + + let rect = sk::Rect::from_xywh(0.0, 0.0, view_width, view_height)?; + canvas.fill_rect(rect, &paint, ts, mask); + + Some(()) +} + +/// Convert a Typst path into a tiny-skia path. +fn convert_path(path: &geom::Path) -> Option<sk::Path> { + let mut builder = sk::PathBuilder::new(); + for elem in &path.0 { + match elem { + PathElement::MoveTo(p) => { + builder.move_to(p.x.to_f32(), p.y.to_f32()); + } + PathElement::LineTo(p) => { + builder.line_to(p.x.to_f32(), p.y.to_f32()); + } + PathElement::CubicTo(p1, p2, p3) => { + builder.cubic_to( + p1.x.to_f32(), + p1.y.to_f32(), + p2.x.to_f32(), + p2.y.to_f32(), + p3.x.to_f32(), + p3.y.to_f32(), + ); + } + PathElement::ClosePath => { + builder.close(); + } + }; + } + builder.finish() +} + +impl From<Transform> for sk::Transform { + fn from(transform: Transform) -> Self { + let Transform { sx, ky, kx, sy, tx, ty } = transform; + sk::Transform::from_row( + sx.get() as _, + ky.get() as _, + kx.get() as _, + sy.get() as _, + tx.to_f32(), + ty.to_f32(), + ) + } +} + +impl From<Paint> for sk::Paint<'static> { + fn from(paint: Paint) -> Self { + let mut sk_paint = sk::Paint::default(); + let Paint::Solid(Color::Rgba(c)) = paint; + sk_paint.set_color_rgba8(c.r, c.g, c.b, c.a); + sk_paint.anti_alias = true; + sk_paint + } +} + +/// Allows to build tiny-skia paths from glyph outlines. +struct WrappedPathBuilder(sk::PathBuilder); + +impl OutlineBuilder for WrappedPathBuilder { + fn move_to(&mut self, x: f32, y: f32) { + self.0.move_to(x, y); + } + + fn line_to(&mut self, x: f32, y: f32) { + self.0.line_to(x, y); + } + + fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) { + self.0.quad_to(x1, y1, x, y); + } + + fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) { + self.0.cubic_to(x1, y1, x2, y2, x, y); + } + + fn close(&mut self) { + self.0.close(); + } +} + +/// Additional methods for [`Length`]. +trait LengthExt { + /// Convert an em length to a number of points as f32. + fn to_f32(self) -> f32; +} + +impl LengthExt for Length { + fn to_f32(self) -> f32 { + self.to_pt() as f32 + } +} + +// Alpha multiplication and blending are ported from: +// https://skia.googlesource.com/skia/+/refs/heads/main/include/core/SkColorPriv.h + +/// Blends two premulitplied, packed 32-bit RGBA colors. Alpha channel must be +/// in the 8 high bits. +fn blend_src_over(src: u32, dst: u32) -> u32 { + src + alpha_mul(dst, 256 - (src >> 24)) +} + +/// Alpha multiply a color. +fn alpha_mul(color: u32, scale: u32) -> u32 { + let mask = 0xff00ff; + let rb = ((color & mask) * scale) >> 8; + let ag = ((color >> 8) & mask) * scale; + (rb & mask) | (ag & !mask) +} diff --git a/src/font.rs b/src/font.rs index c2e1beac..674ffa63 100644 --- a/src/font.rs +++ b/src/font.rs @@ -13,7 +13,7 @@ use crate::loading::{FileHash, Loader}; use crate::util::decode_mac_roman; /// A unique identifier for a loaded font face. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct FaceId(u32); impl FaceId { @@ -37,7 +37,6 @@ pub struct FontStore { faces: Vec<Option<Face>>, families: BTreeMap<String, Vec<FaceId>>, buffers: HashMap<FileHash, Rc<Vec<u8>>>, - on_load: Option<Box<dyn Fn(FaceId, &Face)>>, } impl FontStore { @@ -57,18 +56,9 @@ impl FontStore { faces, families, buffers: HashMap::new(), - on_load: None, } } - /// Register a callback which is invoked each time a font face is loaded. - pub fn on_load<F>(&mut self, f: F) - where - F: Fn(FaceId, &Face) + 'static, - { - self.on_load = Some(Box::new(f)); - } - /// Query for and load the font face from the given `family` that most /// closely matches the given `variant`. pub fn select(&mut self, family: &str, variant: FontVariant) -> Option<FaceId> { @@ -124,10 +114,6 @@ impl FontStore { }; let face = Face::new(Rc::clone(buffer), index)?; - if let Some(callback) = &self.on_load { - callback(id, &face); - } - *slot = Some(face); } diff --git a/src/frame.rs b/src/frame.rs index 46f23446..133ba256 100644 --- a/src/frame.rs +++ b/src/frame.rs @@ -3,14 +3,12 @@ use std::fmt::{self, Debug, Formatter}; use std::rc::Rc; -use serde::{Deserialize, Serialize}; - use crate::font::FaceId; use crate::geom::{Align, Em, Length, Paint, Path, Point, Size, Spec, Transform}; use crate::image::ImageId; /// A finished layout with elements at fixed positions. -#[derive(Default, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Default, Clone, Eq, PartialEq)] pub struct Frame { /// The size of the frame. pub size: Size, @@ -133,7 +131,7 @@ impl Debug for Frame { } /// The building block frames are composed of. -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Eq, PartialEq)] pub enum Element { /// A group of elements. Group(Group), @@ -141,14 +139,14 @@ pub enum Element { Text(Text), /// A geometric shape with optional fill and stroke. Shape(Shape), - /// A raster image and its size. + /// An image and its size. Image(ImageId, Size), /// A link to an external resource and its trigger region. Link(String, Size), } /// A group of elements with optional clipping. -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct Group { /// The group's frame. pub frame: Rc<Frame>, @@ -170,7 +168,7 @@ impl Group { } /// A run of shaped text. -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct Text { /// The font face the glyphs are contained in. pub face_id: FaceId, @@ -190,7 +188,7 @@ impl Text { } /// A glyph in a run of shaped text. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct Glyph { /// The glyph's index in the face. pub id: u16, @@ -201,7 +199,7 @@ pub struct Glyph { } /// A geometric shape with optional fill and stroke. -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct Shape { /// The shape's geometry. pub geometry: Geometry, @@ -228,7 +226,7 @@ impl Shape { } /// A shape's geometry. -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Eq, PartialEq)] pub enum Geometry { /// A line to a point (relative to its position). Line(Point), @@ -241,7 +239,7 @@ pub enum Geometry { } /// A stroke of a geometric shape. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct Stroke { /// The stroke's paint. pub paint: Paint, diff --git a/src/geom/angle.rs b/src/geom/angle.rs index af47e51d..df2aca17 100644 --- a/src/geom/angle.rs +++ b/src/geom/angle.rs @@ -2,7 +2,6 @@ use super::*; /// An angle. #[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -#[derive(Serialize, Deserialize)] pub struct Angle(Scalar); impl Angle { diff --git a/src/geom/em.rs b/src/geom/em.rs index 1868222f..af6be706 100644 --- a/src/geom/em.rs +++ b/src/geom/em.rs @@ -4,7 +4,6 @@ use super::*; /// /// `1em` is the same as the font size. #[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -#[derive(Serialize, Deserialize)] pub struct Em(Scalar); impl Em { diff --git a/src/geom/length.rs b/src/geom/length.rs index 210dcce7..b01a7123 100644 --- a/src/geom/length.rs +++ b/src/geom/length.rs @@ -2,8 +2,6 @@ use super::*; /// An absolute length. #[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -#[derive(Serialize, Deserialize)] -#[serde(transparent)] pub struct Length(Scalar); impl Length { diff --git a/src/geom/mod.rs b/src/geom/mod.rs index 2f722f16..a03e88b0 100644 --- a/src/geom/mod.rs +++ b/src/geom/mod.rs @@ -43,8 +43,6 @@ use std::hash::{Hash, Hasher}; use std::iter::Sum; use std::ops::*; -use serde::{Deserialize, Serialize}; - /// Generic access to a structure's components. pub trait Get<Index> { /// The structure's component type. diff --git a/src/geom/paint.rs b/src/geom/paint.rs index 0eba9f2f..d906561c 100644 --- a/src/geom/paint.rs +++ b/src/geom/paint.rs @@ -4,7 +4,7 @@ use std::str::FromStr; use super::*; /// How a fill or stroke should be painted. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum Paint { /// A solid color. Solid(Color), @@ -20,7 +20,7 @@ where } /// A color in a dynamic format. -#[derive(Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[derive(Copy, Clone, Eq, PartialEq, Hash)] pub enum Color { /// An 8-bit RGBA color. Rgba(RgbaColor), @@ -41,7 +41,7 @@ impl From<RgbaColor> for Color { } /// An 8-bit RGBA color. -#[derive(Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[derive(Copy, Clone, Eq, PartialEq, Hash)] pub struct RgbaColor { /// Red channel. pub r: u8, diff --git a/src/geom/path.rs b/src/geom/path.rs index 87e20dd1..836be1b4 100644 --- a/src/geom/path.rs +++ b/src/geom/path.rs @@ -1,12 +1,11 @@ use super::*; /// A bezier path. -#[derive(Debug, Default, Clone, Eq, PartialEq, Serialize, Deserialize)] -#[serde(transparent)] +#[derive(Debug, Default, Clone, Eq, PartialEq)] pub struct Path(pub Vec<PathElement>); /// An element in a bezier path. -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Eq, PartialEq)] pub enum PathElement { MoveTo(Point), LineTo(Point), diff --git a/src/geom/point.rs b/src/geom/point.rs index ab8f4439..6d77507b 100644 --- a/src/geom/point.rs +++ b/src/geom/point.rs @@ -1,7 +1,7 @@ use super::*; /// A point in 2D. -#[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] pub struct Point { /// The x coordinate. pub x: Length, diff --git a/src/geom/relative.rs b/src/geom/relative.rs index c894f4a5..6f6b152f 100644 --- a/src/geom/relative.rs +++ b/src/geom/relative.rs @@ -5,7 +5,6 @@ use super::*; /// _Note_: `50%` is represented as `0.5` here, but stored as `50.0` in the /// corresponding [literal](crate::syntax::ast::LitKind::Percent). #[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -#[derive(Serialize, Deserialize)] pub struct Relative(Scalar); impl Relative { diff --git a/src/geom/scalar.rs b/src/geom/scalar.rs index 948ea7ec..1435654d 100644 --- a/src/geom/scalar.rs +++ b/src/geom/scalar.rs @@ -3,8 +3,7 @@ use super::*; /// A 64-bit float that implements `Eq`, `Ord` and `Hash`. /// /// Panics if it's `NaN` during any of those operations. -#[derive(Default, Copy, Clone, Serialize, Deserialize)] -#[serde(transparent)] +#[derive(Default, Copy, Clone)] pub struct Scalar(pub f64); impl From<f64> for Scalar { diff --git a/src/geom/spec.rs b/src/geom/spec.rs index cf75f42d..1b8e13c2 100644 --- a/src/geom/spec.rs +++ b/src/geom/spec.rs @@ -4,7 +4,7 @@ use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Not}; use super::*; /// A container with a horizontal and vertical component. -#[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] pub struct Spec<T> { /// The horizontal component. pub x: T, diff --git a/src/geom/transform.rs b/src/geom/transform.rs index 76615e75..eed51d46 100644 --- a/src/geom/transform.rs +++ b/src/geom/transform.rs @@ -1,7 +1,7 @@ use super::*; /// A scale-skew-translate transformation. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct Transform { pub sx: Relative, pub ky: Relative, diff --git a/src/image.rs b/src/image.rs index 3d80896f..bd70bf28 100644 --- a/src/image.rs +++ b/src/image.rs @@ -9,12 +9,11 @@ use std::rc::Rc; use image::io::Reader as ImageReader; use image::{DynamicImage, GenericImageView, ImageFormat}; -use serde::{Deserialize, Serialize}; use crate::loading::{FileHash, Loader}; /// A unique identifier for a loaded image. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct ImageId(u32); impl ImageId { @@ -37,7 +36,6 @@ pub struct ImageStore { loader: Rc<dyn Loader>, files: HashMap<FileHash, ImageId>, images: Vec<Image>, - on_load: Option<Box<dyn Fn(ImageId, &Image)>>, } impl ImageStore { @@ -47,18 +45,9 @@ impl ImageStore { loader, files: HashMap::new(), images: vec![], - on_load: None, } } - /// Register a callback which is invoked each time an image is loaded. - pub fn on_load<F>(&mut self, f: F) - where - F: Fn(ImageId, &Image) + 'static, - { - self.on_load = Some(Box::new(f)); - } - /// Load and decode an image file from a path. pub fn load(&mut self, path: &Path) -> io::Result<ImageId> { let hash = self.loader.resolve(path)?; @@ -69,9 +58,6 @@ impl ImageStore { let ext = path.extension().and_then(OsStr::to_str).unwrap_or_default(); let image = Image::parse(&buffer, ext)?; let id = ImageId(self.images.len() as u32); - if let Some(callback) = &self.on_load { - callback(id, &image); - } self.images.push(image); entry.insert(id) } diff --git a/src/layout/mod.rs b/src/layout/mod.rs index d563dafb..e4c29f9b 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -79,7 +79,7 @@ pub struct LayoutContext<'a> { pub images: &'a mut ImageStore, /// Caches layouting artifacts. #[cfg(feature = "layout-cache")] - pub layouts: &'a mut LayoutCache, + pub layout_cache: &'a mut LayoutCache, /// How deeply nested the current layout tree position is. #[cfg(feature = "layout-cache")] level: usize, @@ -92,7 +92,7 @@ impl<'a> LayoutContext<'a> { fonts: &mut ctx.fonts, images: &mut ctx.images, #[cfg(feature = "layout-cache")] - layouts: &mut ctx.layouts, + layout_cache: &mut ctx.layout_cache, #[cfg(feature = "layout-cache")] level: 0, }; @@ -220,7 +220,7 @@ impl Layout for PackedNode { }; #[cfg(feature = "layout-cache")] - ctx.layouts.get(hash, regions).unwrap_or_else(|| { + ctx.layout_cache.get(hash, regions).unwrap_or_else(|| { ctx.level += 1; let frames = self.node.layout(ctx, regions, styles); ctx.level -= 1; @@ -238,7 +238,7 @@ impl Layout for PackedNode { panic!("constraints did not match regions they were created for"); } - ctx.layouts.insert(hash, entry); + ctx.layout_cache.insert(hash, entry); frames }) } @@ -56,6 +56,7 @@ use std::rc::Rc; use crate::diag::TypResult; use crate::eval::{Eval, EvalContext, Module, Scope, StyleMap}; +use crate::export::RenderCache; use crate::font::FontStore; use crate::frame::Frame; use crate::image::ImageStore; @@ -76,7 +77,9 @@ pub struct Context { pub images: ImageStore, /// Caches layouting artifacts. #[cfg(feature = "layout-cache")] - pub layouts: LayoutCache, + pub layout_cache: LayoutCache, + /// Caches rendering artifacts. + pub render_cache: RenderCache, /// The standard library scope. std: Scope, /// The default styles. @@ -131,7 +134,7 @@ impl Context { /// Garbage-collect caches. pub fn turnaround(&mut self) { #[cfg(feature = "layout-cache")] - self.layouts.turnaround(); + self.layout_cache.turnaround(); } } @@ -187,7 +190,8 @@ impl ContextBuilder { images: ImageStore::new(Rc::clone(&loader)), loader, #[cfg(feature = "layout-cache")] - layouts: LayoutCache::new(self.policy, self.max_size), + layout_cache: LayoutCache::new(self.policy, self.max_size), + render_cache: RenderCache::new(), std: self.std.unwrap_or_else(library::new), styles: self.styles.unwrap_or_default(), } diff --git a/src/loading/fs.rs b/src/loading/fs.rs index 12996a69..4c46c80c 100644 --- a/src/loading/fs.rs +++ b/src/loading/fs.rs @@ -5,7 +5,6 @@ use std::rc::Rc; use memmap2::Mmap; use same_file::Handle; -use serde::{Deserialize, Serialize}; use walkdir::WalkDir; use super::{FileHash, Loader}; @@ -14,8 +13,6 @@ use crate::font::FaceInfo; /// Loads fonts and files from the local file system. /// /// _This is only available when the `fs` feature is enabled._ -#[derive(Default, Serialize, Deserialize)] -#[serde(transparent)] pub struct FsLoader { faces: Vec<FaceInfo>, } diff --git a/src/source.rs b/src/source.rs index 1e0be450..fd42c3f7 100644 --- a/src/source.rs +++ b/src/source.rs @@ -6,8 +6,6 @@ use std::ops::Range; use std::path::{Path, PathBuf}; use std::rc::Rc; -use serde::{Deserialize, Serialize}; - use crate::diag::TypResult; use crate::loading::{FileHash, Loader}; use crate::parse::{is_newline, parse, Reparser, Scanner}; @@ -19,7 +17,7 @@ use crate::util::{PathExt, StrExt}; use codespan_reporting::files::{self, Files}; /// A unique identifier for a loaded source file. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct SourceId(u32); impl SourceId { diff --git a/src/syntax/span.rs b/src/syntax/span.rs index 4d5b8819..ab2797f6 100644 --- a/src/syntax/span.rs +++ b/src/syntax/span.rs @@ -2,12 +2,10 @@ use std::cmp::Ordering; use std::fmt::{self, Debug, Formatter}; use std::ops::Range; -use serde::{Deserialize, Serialize}; - use crate::source::SourceId; /// A value with the span it corresponds to in the source code. -#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Copy, Clone, Eq, PartialEq)] pub struct Spanned<T> { /// The spanned value. pub v: T, @@ -48,7 +46,7 @@ impl<T: Debug> Debug for Spanned<T> { } /// Bounds of a slice of source code. -#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Copy, Clone, Eq, PartialEq)] pub struct Span { /// The id of the source file. pub source: SourceId, |
