summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-01-24 16:48:24 +0100
committerLaurenz <laurmaedje@gmail.com>2022-01-24 17:39:49 +0100
commit3739ab77207e0e54edb55a110a16a1eb925b84f4 (patch)
tree06c8e5987d2fe070ad273ef94641161bbaef7095 /src
parentdb158719d67fdef1d2c76300fb232cf2d4bfb35d (diff)
Export into rendered images
Diffstat (limited to 'src')
-rw-r--r--src/diag.rs6
-rw-r--r--src/export/mod.rs3
-rw-r--r--src/export/pdf.rs6
-rw-r--r--src/export/render.rs515
-rw-r--r--src/font.rs16
-rw-r--r--src/frame.rs20
-rw-r--r--src/geom/angle.rs1
-rw-r--r--src/geom/em.rs1
-rw-r--r--src/geom/length.rs2
-rw-r--r--src/geom/mod.rs2
-rw-r--r--src/geom/paint.rs6
-rw-r--r--src/geom/path.rs5
-rw-r--r--src/geom/point.rs2
-rw-r--r--src/geom/relative.rs1
-rw-r--r--src/geom/scalar.rs3
-rw-r--r--src/geom/spec.rs2
-rw-r--r--src/geom/transform.rs2
-rw-r--r--src/image.rs16
-rw-r--r--src/layout/mod.rs8
-rw-r--r--src/lib.rs10
-rw-r--r--src/loading/fs.rs3
-rw-r--r--src/source.rs4
-rw-r--r--src/syntax/span.rs6
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
})
}
diff --git a/src/lib.rs b/src/lib.rs
index 39507d79..a764468b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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,