mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Query cache
This commit is contained in:
parent
e1f29d6cb9
commit
f2f473a81f
@ -221,8 +221,10 @@ impl Layout for LayoutNode {
|
|||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Arc<Frame>>> {
|
) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
// TODO(query)
|
ctx.query((self, regions, styles), |ctx, (node, regions, styles)| {
|
||||||
self.0.layout(ctx, regions, styles.barred(self.id()))
|
node.0.layout(ctx, regions, styles.barred(node.id()))
|
||||||
|
})
|
||||||
|
.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pack(self) -> LayoutNode {
|
fn pack(self) -> LayoutNode {
|
||||||
|
@ -43,7 +43,10 @@ impl ShowNode {
|
|||||||
|
|
||||||
impl Show for ShowNode {
|
impl Show for ShowNode {
|
||||||
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Template> {
|
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Template> {
|
||||||
self.0.show(ctx, styles)
|
ctx.query((self, styles), |ctx, (node, styles)| {
|
||||||
|
node.0.show(ctx, styles)
|
||||||
|
})
|
||||||
|
.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pack(self) -> ShowNode {
|
fn pack(self) -> ShowNode {
|
||||||
|
@ -7,7 +7,6 @@ use tiny_skia as sk;
|
|||||||
use ttf_parser::{GlyphId, OutlineBuilder};
|
use ttf_parser::{GlyphId, OutlineBuilder};
|
||||||
use usvg::FitTo;
|
use usvg::FitTo;
|
||||||
|
|
||||||
use crate::font::Face;
|
|
||||||
use crate::frame::{Element, Frame, Geometry, Group, Shape, Stroke, Text};
|
use crate::frame::{Element, Frame, Geometry, Group, Shape, Stroke, Text};
|
||||||
use crate::geom::{self, Length, Paint, PathElement, Size, Transform};
|
use crate::geom::{self, Length, Paint, PathElement, Size, Transform};
|
||||||
use crate::image::{Image, RasterImage, Svg};
|
use crate::image::{Image, RasterImage, Svg};
|
||||||
@ -115,17 +114,15 @@ fn render_text(
|
|||||||
ctx: &mut Context,
|
ctx: &mut Context,
|
||||||
text: &Text,
|
text: &Text,
|
||||||
) {
|
) {
|
||||||
let face = ctx.fonts.get(text.face_id);
|
|
||||||
|
|
||||||
let mut x = 0.0;
|
let mut x = 0.0;
|
||||||
for glyph in &text.glyphs {
|
for glyph in &text.glyphs {
|
||||||
let id = GlyphId(glyph.id);
|
let id = GlyphId(glyph.id);
|
||||||
let offset = x + glyph.x_offset.resolve(text.size).to_f32();
|
let offset = x + glyph.x_offset.resolve(text.size).to_f32();
|
||||||
let ts = ts.pre_translate(offset, 0.0);
|
let ts = ts.pre_translate(offset, 0.0);
|
||||||
|
|
||||||
render_svg_glyph(canvas, ts, mask, text, face, id)
|
render_svg_glyph(canvas, ts, mask, ctx, text, id)
|
||||||
.or_else(|| render_bitmap_glyph(canvas, ts, mask, text, face, id))
|
.or_else(|| render_bitmap_glyph(canvas, ts, mask, ctx, text, id))
|
||||||
.or_else(|| render_outline_glyph(canvas, ts, mask, text, face, id));
|
.or_else(|| render_outline_glyph(canvas, ts, mask, ctx, text, id));
|
||||||
|
|
||||||
x += glyph.x_advance.resolve(text.size).to_f32();
|
x += glyph.x_advance.resolve(text.size).to_f32();
|
||||||
}
|
}
|
||||||
@ -136,10 +133,11 @@ fn render_svg_glyph(
|
|||||||
canvas: &mut sk::Pixmap,
|
canvas: &mut sk::Pixmap,
|
||||||
ts: sk::Transform,
|
ts: sk::Transform,
|
||||||
_: Option<&sk::ClipMask>,
|
_: Option<&sk::ClipMask>,
|
||||||
|
ctx: &mut Context,
|
||||||
text: &Text,
|
text: &Text,
|
||||||
face: &Face,
|
|
||||||
id: GlyphId,
|
id: GlyphId,
|
||||||
) -> Option<()> {
|
) -> Option<()> {
|
||||||
|
let face = ctx.fonts.get(text.face_id);
|
||||||
let mut data = face.ttf().glyph_svg_image(id)?;
|
let mut data = face.ttf().glyph_svg_image(id)?;
|
||||||
|
|
||||||
// Decompress SVGZ.
|
// Decompress SVGZ.
|
||||||
@ -186,12 +184,13 @@ fn render_bitmap_glyph(
|
|||||||
canvas: &mut sk::Pixmap,
|
canvas: &mut sk::Pixmap,
|
||||||
ts: sk::Transform,
|
ts: sk::Transform,
|
||||||
mask: Option<&sk::ClipMask>,
|
mask: Option<&sk::ClipMask>,
|
||||||
|
ctx: &mut Context,
|
||||||
text: &Text,
|
text: &Text,
|
||||||
face: &Face,
|
|
||||||
id: GlyphId,
|
id: GlyphId,
|
||||||
) -> Option<()> {
|
) -> Option<()> {
|
||||||
let size = text.size.to_f32();
|
let size = text.size.to_f32();
|
||||||
let ppem = size * ts.sy;
|
let ppem = size * ts.sy;
|
||||||
|
let face = ctx.fonts.get(text.face_id);
|
||||||
let raster = face.ttf().glyph_raster_image(id, ppem as u16)?;
|
let raster = face.ttf().glyph_raster_image(id, ppem as u16)?;
|
||||||
let img = RasterImage::parse(&raster.data).ok()?;
|
let img = RasterImage::parse(&raster.data).ok()?;
|
||||||
|
|
||||||
@ -211,8 +210,8 @@ fn render_outline_glyph(
|
|||||||
canvas: &mut sk::Pixmap,
|
canvas: &mut sk::Pixmap,
|
||||||
ts: sk::Transform,
|
ts: sk::Transform,
|
||||||
mask: Option<&sk::ClipMask>,
|
mask: Option<&sk::ClipMask>,
|
||||||
|
ctx: &mut Context,
|
||||||
text: &Text,
|
text: &Text,
|
||||||
face: &Face,
|
|
||||||
id: GlyphId,
|
id: GlyphId,
|
||||||
) -> Option<()> {
|
) -> Option<()> {
|
||||||
let ppem = text.size.to_f32() * ts.sy;
|
let ppem = text.size.to_f32() * ts.sy;
|
||||||
@ -221,6 +220,7 @@ fn render_outline_glyph(
|
|||||||
// rasterization can't be used due to very large text size or weird
|
// rasterization can't be used due to very large text size or weird
|
||||||
// scale/skewing transforms.
|
// scale/skewing transforms.
|
||||||
if ppem > 100.0 || ts.kx != 0.0 || ts.ky != 0.0 || ts.sx != ts.sy {
|
if ppem > 100.0 || ts.kx != 0.0 || ts.ky != 0.0 || ts.sx != ts.sy {
|
||||||
|
let face = ctx.fonts.get(text.face_id);
|
||||||
let path = {
|
let path = {
|
||||||
let mut builder = WrappedPathBuilder(sk::PathBuilder::new());
|
let mut builder = WrappedPathBuilder(sk::PathBuilder::new());
|
||||||
face.ttf().outline_glyph(id, &mut builder)?;
|
face.ttf().outline_glyph(id, &mut builder)?;
|
||||||
@ -241,7 +241,11 @@ fn render_outline_glyph(
|
|||||||
// TODO(query)
|
// TODO(query)
|
||||||
// Try to retrieve a prepared glyph or prepare it from scratch if it
|
// Try to retrieve a prepared glyph or prepare it from scratch if it
|
||||||
// doesn't exist, yet.
|
// doesn't exist, yet.
|
||||||
let glyph = pixglyph::Glyph::load(face.ttf(), id)?;
|
let glyph = ctx
|
||||||
|
.query((text.face_id, id), |ctx, (face_id, id)| {
|
||||||
|
pixglyph::Glyph::load(ctx.fonts.get(face_id).ttf(), id)
|
||||||
|
})
|
||||||
|
.as_ref()?;
|
||||||
|
|
||||||
// Rasterize the glyph with `pixglyph`.
|
// Rasterize the glyph with `pixglyph`.
|
||||||
let bitmap = glyph.rasterize(ts.tx, ts.ty, ppem);
|
let bitmap = glyph.rasterize(ts.tx, ts.ty, ppem);
|
||||||
|
73
src/lib.rs
73
src/lib.rs
@ -48,7 +48,10 @@ pub mod parse;
|
|||||||
pub mod source;
|
pub mod source;
|
||||||
pub mod syntax;
|
pub mod syntax;
|
||||||
|
|
||||||
|
use std::any::Any;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::fmt::{self, Display, Formatter};
|
||||||
|
use std::hash::Hash;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@ -76,6 +79,8 @@ pub struct Context {
|
|||||||
styles: Arc<StyleMap>,
|
styles: Arc<StyleMap>,
|
||||||
/// Cached modules.
|
/// Cached modules.
|
||||||
modules: HashMap<SourceId, Module>,
|
modules: HashMap<SourceId, Module>,
|
||||||
|
/// Cached queries.
|
||||||
|
cache: HashMap<u64, CacheEntry>,
|
||||||
/// The stack of imported files that led to evaluation of the current file.
|
/// The stack of imported files that led to evaluation of the current file.
|
||||||
route: Vec<SourceId>,
|
route: Vec<SourceId>,
|
||||||
/// The dependencies of the current evaluation process.
|
/// The dependencies of the current evaluation process.
|
||||||
@ -206,6 +211,7 @@ impl ContextBuilder {
|
|||||||
std: self.std.unwrap_or_else(|| Arc::new(library::new())),
|
std: self.std.unwrap_or_else(|| Arc::new(library::new())),
|
||||||
styles: self.styles.unwrap_or_default(),
|
styles: self.styles.unwrap_or_default(),
|
||||||
modules: HashMap::new(),
|
modules: HashMap::new(),
|
||||||
|
cache: HashMap::new(),
|
||||||
route: vec![],
|
route: vec![],
|
||||||
deps: vec![],
|
deps: vec![],
|
||||||
}
|
}
|
||||||
@ -217,3 +223,70 @@ impl Default for ContextBuilder {
|
|||||||
Self { std: None, styles: None }
|
Self { std: None, styles: None }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An entry in the query cache.
|
||||||
|
struct CacheEntry {
|
||||||
|
/// The query's results.
|
||||||
|
data: Box<dyn Any>,
|
||||||
|
/// How many evictions have passed since the entry has been last used.
|
||||||
|
age: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Context {
|
||||||
|
/// Execute a query.
|
||||||
|
///
|
||||||
|
/// This hashes all inputs to the query and then either returns a cached
|
||||||
|
/// version or executes the query, saves the results in the cache and
|
||||||
|
/// returns a reference to them.
|
||||||
|
pub fn query<I, O>(
|
||||||
|
&mut self,
|
||||||
|
input: I,
|
||||||
|
query: fn(ctx: &mut Self, input: I) -> O,
|
||||||
|
) -> &O
|
||||||
|
where
|
||||||
|
I: Hash,
|
||||||
|
O: 'static,
|
||||||
|
{
|
||||||
|
let hash = fxhash::hash64(&input);
|
||||||
|
if !self.cache.contains_key(&hash) {
|
||||||
|
let output = query(self, input);
|
||||||
|
self.cache.insert(hash, CacheEntry { data: Box::new(output), age: 0 });
|
||||||
|
}
|
||||||
|
|
||||||
|
let entry = self.cache.get_mut(&hash).unwrap();
|
||||||
|
entry.age = 0;
|
||||||
|
entry.data.downcast_ref().expect("oh no, a hash collision")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Garbage-collect the query cache. This deletes elements which haven't
|
||||||
|
/// been used in a while.
|
||||||
|
///
|
||||||
|
/// Returns details about the eviction.
|
||||||
|
pub fn evict(&mut self) -> Eviction {
|
||||||
|
const MAX_AGE: usize = 5;
|
||||||
|
|
||||||
|
let before = self.cache.len();
|
||||||
|
self.cache.retain(|_, entry| {
|
||||||
|
entry.age += 1;
|
||||||
|
entry.age <= MAX_AGE
|
||||||
|
});
|
||||||
|
|
||||||
|
Eviction { before, after: self.cache.len() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Details about a cache eviction.
|
||||||
|
pub struct Eviction {
|
||||||
|
/// The number of items in the cache before the eviction.
|
||||||
|
pub before: usize,
|
||||||
|
/// The number of items in the cache after the eviction.
|
||||||
|
pub after: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Eviction {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
writeln!(f, "Before: {}", self.before)?;
|
||||||
|
writeln!(f, "Evicted: {}", self.before - self.after)?;
|
||||||
|
writeln!(f, "After: {}", self.after)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user