Cache loaded fonts and glyph outlines.
Approximately a 70% CPU time improvement on the NanoVG demo.
This commit is contained in:
parent
582f025c91
commit
0b43f629cd
|
@ -285,6 +285,7 @@ dependencies = [
|
|||
"gl 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"image 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"jemallocator 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pathfinder_canvas 0.1.0",
|
||||
"pathfinder_color 0.1.0",
|
||||
"pathfinder_content 0.1.0",
|
||||
|
|
|
@ -94,16 +94,18 @@ impl Canvas {
|
|||
self.scene
|
||||
}
|
||||
|
||||
pub fn get_context_2d(self, font_context: CanvasFontContext) -> CanvasRenderingContext2D {
|
||||
pub fn get_context_2d(self, canvas_font_context: CanvasFontContext)
|
||||
-> CanvasRenderingContext2D {
|
||||
#[cfg(feature = "pf-text")]
|
||||
let default_font_collection = font_context.default_font_collection.clone();
|
||||
let default_font_collection =
|
||||
canvas_font_context.0.borrow().default_font_collection.clone();
|
||||
#[cfg(not(feature = "pf-text"))]
|
||||
let default_font_collection = Arc::new(FontCollection);
|
||||
CanvasRenderingContext2D {
|
||||
canvas: self,
|
||||
current_state: State::default(default_font_collection),
|
||||
saved_states: vec![],
|
||||
font_context,
|
||||
canvas_font_context,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -118,7 +120,7 @@ pub struct CanvasRenderingContext2D {
|
|||
current_state: State,
|
||||
saved_states: Vec<State>,
|
||||
#[allow(dead_code)]
|
||||
font_context: CanvasFontContext,
|
||||
canvas_font_context: CanvasFontContext,
|
||||
}
|
||||
|
||||
impl CanvasRenderingContext2D {
|
||||
|
@ -134,11 +136,6 @@ impl CanvasRenderingContext2D {
|
|||
self.canvas
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn font_context(&self) -> CanvasFontContext {
|
||||
self.font_context.clone()
|
||||
}
|
||||
|
||||
// Drawing rectangles
|
||||
|
||||
#[inline]
|
||||
|
|
|
@ -21,8 +21,10 @@ use pathfinder_geometry::transform2d::Transform2F;
|
|||
use pathfinder_geometry::util;
|
||||
use pathfinder_geometry::vector::{Vector2F, vec2f};
|
||||
use pathfinder_renderer::paint::PaintId;
|
||||
use pathfinder_text::{SceneExt, TextRenderMode};
|
||||
use pathfinder_text::{FontContext, FontRenderOptions, TextRenderMode};
|
||||
use skribo::{FontCollection, FontFamily, FontRef, Layout, TextStyle};
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
impl CanvasRenderingContext2D {
|
||||
|
@ -51,14 +53,22 @@ impl CanvasRenderingContext2D {
|
|||
let clip_path = self.current_state.clip_path;
|
||||
let blend_mode = self.current_state.global_composite_operation.to_blend_mode();
|
||||
|
||||
drop(self.canvas.scene.push_layout(&layout,
|
||||
// TODO(pcwalton): Report errors.
|
||||
drop(self.canvas_font_context
|
||||
.0
|
||||
.borrow_mut()
|
||||
.font_context
|
||||
.push_layout(&mut self.canvas.scene,
|
||||
&layout,
|
||||
&TextStyle { size: self.current_state.font_size },
|
||||
&(transform * self.current_state.transform),
|
||||
TextRenderMode::Fill,
|
||||
HintingOptions::None,
|
||||
&FontRenderOptions {
|
||||
transform: transform * self.current_state.transform,
|
||||
render_mode: TextRenderMode::Fill,
|
||||
hinting_options: HintingOptions::None,
|
||||
clip_path,
|
||||
blend_mode,
|
||||
paint_id));
|
||||
paint_id,
|
||||
}));
|
||||
}
|
||||
|
||||
fn fill_or_stroke_text(&mut self,
|
||||
|
@ -75,14 +85,21 @@ impl CanvasRenderingContext2D {
|
|||
let transform = self.current_state.transform * Transform2F::from_translation(position);
|
||||
|
||||
// TODO(pcwalton): Report errors.
|
||||
drop(self.canvas.scene.push_layout(&layout,
|
||||
drop(self.canvas_font_context
|
||||
.0
|
||||
.borrow_mut()
|
||||
.font_context
|
||||
.push_layout(&mut self.canvas.scene,
|
||||
&layout,
|
||||
&TextStyle { size: self.current_state.font_size },
|
||||
&transform,
|
||||
&FontRenderOptions {
|
||||
transform,
|
||||
render_mode,
|
||||
HintingOptions::None,
|
||||
hinting_options: HintingOptions::None,
|
||||
clip_path,
|
||||
blend_mode,
|
||||
paint_id));
|
||||
paint_id,
|
||||
}));
|
||||
}
|
||||
|
||||
fn layout_text(&self, string: &str) -> Layout {
|
||||
|
@ -100,7 +117,7 @@ impl CanvasRenderingContext2D {
|
|||
|
||||
#[inline]
|
||||
pub fn set_font<FC>(&mut self, font_collection: FC) where FC: IntoFontCollection {
|
||||
let font_collection = font_collection.into_font_collection(&self.font_context);
|
||||
let font_collection = font_collection.into_font_collection(&self.canvas_font_context);
|
||||
self.current_state.font_collection = font_collection;
|
||||
}
|
||||
|
||||
|
@ -179,7 +196,10 @@ pub struct TextMetrics {
|
|||
|
||||
#[cfg(feature = "pf-text")]
|
||||
#[derive(Clone)]
|
||||
pub struct CanvasFontContext {
|
||||
pub struct CanvasFontContext(pub(crate) Rc<RefCell<CanvasFontContextData>>);
|
||||
|
||||
pub(super) struct CanvasFontContextData {
|
||||
pub(super) font_context: FontContext<Font>,
|
||||
#[allow(dead_code)]
|
||||
pub(super) font_source: Arc<dyn Source>,
|
||||
#[allow(dead_code)]
|
||||
|
@ -196,10 +216,11 @@ impl CanvasFontContext {
|
|||
}
|
||||
}
|
||||
|
||||
CanvasFontContext {
|
||||
CanvasFontContext(Rc::new(RefCell::new(CanvasFontContextData {
|
||||
font_source,
|
||||
default_font_collection: Arc::new(default_font_collection),
|
||||
}
|
||||
font_context: FontContext::new(),
|
||||
})))
|
||||
}
|
||||
|
||||
/// A convenience method to create a font context with the system source.
|
||||
|
@ -212,6 +233,18 @@ impl CanvasFontContext {
|
|||
pub fn from_fonts<I>(fonts: I) -> CanvasFontContext where I: Iterator<Item = Handle> {
|
||||
CanvasFontContext::new(Arc::new(MemSource::from_fonts(fonts).unwrap()))
|
||||
}
|
||||
|
||||
fn get_font_by_postscript_name(&self, postscript_name: &str) -> Font {
|
||||
let this = self.0.borrow();
|
||||
if let Some(cached_font) = this.font_context.get_cached_font(postscript_name) {
|
||||
return (*cached_font).clone();
|
||||
}
|
||||
this.font_source
|
||||
.select_by_postscript_name(postscript_name)
|
||||
.expect("Couldn't find a font with that PostScript name!")
|
||||
.load()
|
||||
.expect("Failed to load the font!")
|
||||
}
|
||||
}
|
||||
|
||||
// Text layout utilities
|
||||
|
@ -413,6 +446,7 @@ impl IntoFontCollection for Vec<FontFamily> {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
impl IntoFontCollection for Handle {
|
||||
#[inline]
|
||||
fn into_font_collection(self, context: &CanvasFontContext) -> Arc<FontCollection> {
|
||||
|
@ -422,15 +456,18 @@ impl IntoFontCollection for Handle {
|
|||
|
||||
impl<'a> IntoFontCollection for &'a [Handle] {
|
||||
#[inline]
|
||||
fn into_font_collection(self, _: &CanvasFontContext) -> Arc<FontCollection> {
|
||||
fn into_font_collection(self, context: &CanvasFontContext) -> Arc<FontCollection> {
|
||||
let mut font_collection = FontCollection::new();
|
||||
for handle in self {
|
||||
let postscript_name = handle.postscript_name();
|
||||
|
||||
let font = handle.load().expect("Failed to load the font!");
|
||||
font_collection.add_family(FontFamily::new_from_font(font));
|
||||
}
|
||||
Arc::new(font_collection)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
impl IntoFontCollection for Font {
|
||||
#[inline]
|
||||
|
@ -453,10 +490,7 @@ impl<'a> IntoFontCollection for &'a [Font] {
|
|||
impl<'a> IntoFontCollection for &'a str {
|
||||
#[inline]
|
||||
fn into_font_collection(self, context: &CanvasFontContext) -> Arc<FontCollection> {
|
||||
context.font_source
|
||||
.select_by_postscript_name(self)
|
||||
.expect("Couldn't find a font with that PostScript name!")
|
||||
.into_font_collection(context)
|
||||
context.get_font_by_postscript_name(self).into_font_collection(context)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -465,11 +499,7 @@ impl<'a, 'b> IntoFontCollection for &'a [&'b str] {
|
|||
fn into_font_collection(self, context: &CanvasFontContext) -> Arc<FontCollection> {
|
||||
let mut font_collection = FontCollection::new();
|
||||
for postscript_name in self {
|
||||
let font = context.font_source
|
||||
.select_by_postscript_name(postscript_name)
|
||||
.expect("Failed to find a font with that PostScript name!")
|
||||
.load()
|
||||
.expect("Failed to load the font!");
|
||||
let font = context.get_font_by_postscript_name(postscript_name);
|
||||
font_collection.add_family(FontFamily::new_from_font(font));
|
||||
}
|
||||
Arc::new(font_collection)
|
||||
|
|
|
@ -16,6 +16,10 @@ version = "0.23"
|
|||
default-features = false
|
||||
features = ["png"]
|
||||
|
||||
[dependencies.log]
|
||||
version = "0.4"
|
||||
features = ["release_max_level_info"]
|
||||
|
||||
[dependencies.pathfinder_canvas]
|
||||
path = "../../canvas"
|
||||
features = ["pf-text"]
|
||||
|
|
|
@ -1544,7 +1544,8 @@ fn main() {
|
|||
gpu_graph.render(&mut context, vec2f(415.0, 5.0));
|
||||
|
||||
// Render the canvas to screen.
|
||||
let scene = SceneProxy::from_scene(context.into_canvas().into_scene(), RayonExecutor);
|
||||
let canvas = context.into_canvas();
|
||||
let scene = SceneProxy::from_scene(canvas.into_scene(), RayonExecutor);
|
||||
scene.build_and_render(&mut renderer, BuildOptions::default());
|
||||
window.gl_swap_window();
|
||||
|
||||
|
|
226
text/src/lib.rs
226
text/src/lib.rs
|
@ -11,138 +11,182 @@
|
|||
use font_kit::error::GlyphLoadingError;
|
||||
use font_kit::hinting::HintingOptions;
|
||||
use font_kit::loader::Loader;
|
||||
use font_kit::loaders::default::Font as DefaultLoader;
|
||||
use font_kit::outline::OutlineSink;
|
||||
use pathfinder_content::effects::BlendMode;
|
||||
use pathfinder_content::outline::{Contour, Outline};
|
||||
use pathfinder_content::stroke::{OutlineStrokeToFill, StrokeStyle};
|
||||
use pathfinder_geometry::line_segment::LineSegment2F;
|
||||
use pathfinder_geometry::transform2d::Transform2F;
|
||||
use pathfinder_geometry::vector::Vector2F;
|
||||
use pathfinder_geometry::vector::{Vector2F, vec2f};
|
||||
use pathfinder_renderer::paint::PaintId;
|
||||
use pathfinder_renderer::scene::{ClipPathId, DrawPath, Scene};
|
||||
use skribo::{FontCollection, Layout, TextStyle};
|
||||
use std::collections::HashMap;
|
||||
use std::mem;
|
||||
|
||||
// FIXME(pcwalton): Too many parameters!
|
||||
pub trait SceneExt {
|
||||
// TODO(pcwalton): Support stroked glyphs.
|
||||
fn push_glyph<F>(&mut self,
|
||||
font: &F,
|
||||
glyph_id: u32,
|
||||
transform: &Transform2F,
|
||||
render_mode: TextRenderMode,
|
||||
hinting_options: HintingOptions,
|
||||
clip_path: Option<ClipPathId>,
|
||||
blend_mode: BlendMode,
|
||||
paint_id: PaintId)
|
||||
-> Result<(), GlyphLoadingError>
|
||||
where F: Loader;
|
||||
|
||||
fn push_layout(&mut self,
|
||||
layout: &Layout,
|
||||
style: &TextStyle,
|
||||
transform: &Transform2F,
|
||||
render_mode: TextRenderMode,
|
||||
hinting_options: HintingOptions,
|
||||
clip_path: Option<ClipPathId>,
|
||||
blend_mode: BlendMode,
|
||||
paint_id: PaintId)
|
||||
-> Result<(), GlyphLoadingError>;
|
||||
|
||||
fn push_text(&mut self,
|
||||
text: &str,
|
||||
style: &TextStyle,
|
||||
collection: &FontCollection,
|
||||
transform: &Transform2F,
|
||||
render_mode: TextRenderMode,
|
||||
hinting_options: HintingOptions,
|
||||
clip_path: Option<ClipPathId>,
|
||||
blend_mode: BlendMode,
|
||||
paint_id: PaintId)
|
||||
-> Result<(), GlyphLoadingError>;
|
||||
#[derive(Clone)]
|
||||
pub struct FontContext<F> where F: Loader {
|
||||
font_info: HashMap<String, FontInfo<F>>,
|
||||
}
|
||||
|
||||
impl SceneExt for Scene {
|
||||
#[inline]
|
||||
fn push_glyph<F>(&mut self,
|
||||
font: &F,
|
||||
glyph_id: u32,
|
||||
transform: &Transform2F,
|
||||
render_mode: TextRenderMode,
|
||||
hinting_options: HintingOptions,
|
||||
clip_path: Option<ClipPathId>,
|
||||
blend_mode: BlendMode,
|
||||
paint_id: PaintId)
|
||||
-> Result<(), GlyphLoadingError>
|
||||
where F: Loader {
|
||||
let mut outline_builder = OutlinePathBuilder::new(transform);
|
||||
font.outline(glyph_id, hinting_options, &mut outline_builder)?;
|
||||
let mut outline = outline_builder.build();
|
||||
#[derive(Clone)]
|
||||
struct FontInfo<F> where F: Loader {
|
||||
font: F,
|
||||
outline_cache: HashMap<GlyphId, Outline>,
|
||||
}
|
||||
|
||||
if let TextRenderMode::Stroke(stroke_style) = render_mode {
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct FontRenderOptions {
|
||||
pub transform: Transform2F,
|
||||
pub render_mode: TextRenderMode,
|
||||
pub hinting_options: HintingOptions,
|
||||
pub clip_path: Option<ClipPathId>,
|
||||
pub blend_mode: BlendMode,
|
||||
pub paint_id: PaintId,
|
||||
}
|
||||
|
||||
impl Default for FontRenderOptions {
|
||||
#[inline]
|
||||
fn default() -> FontRenderOptions {
|
||||
FontRenderOptions {
|
||||
transform: Transform2F::default(),
|
||||
render_mode: TextRenderMode::Fill,
|
||||
hinting_options: HintingOptions::None,
|
||||
clip_path: None,
|
||||
blend_mode: BlendMode::SrcOver,
|
||||
paint_id: PaintId(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Debug, Eq, Hash)]
|
||||
pub struct GlyphId(pub u32);
|
||||
|
||||
impl<F> FontContext<F> where F: Loader {
|
||||
#[inline]
|
||||
pub fn new() -> FontContext<F> {
|
||||
FontContext { font_info: HashMap::new() }
|
||||
}
|
||||
|
||||
pub fn push_glyph(&mut self,
|
||||
scene: &mut Scene,
|
||||
font: &F,
|
||||
glyph_id: GlyphId,
|
||||
render_options: &FontRenderOptions)
|
||||
-> Result<(), GlyphLoadingError> {
|
||||
let font_key = font.postscript_name();
|
||||
let metrics = font.metrics();
|
||||
|
||||
// Insert the font into the cache if needed.
|
||||
if let Some(ref font_key) = font_key {
|
||||
if !self.font_info.contains_key(&*font_key) {
|
||||
self.font_info.insert((*font_key).clone(), FontInfo::new((*font).clone()));
|
||||
}
|
||||
}
|
||||
|
||||
// See if we have a cached outline.
|
||||
//
|
||||
// TODO(pcwalton): Cache hinted outlines too.
|
||||
let mut cached_outline = None;
|
||||
let can_cache_outline = font_key.is_some() &&
|
||||
render_options.hinting_options == HintingOptions::None;
|
||||
if can_cache_outline {
|
||||
if let Some(ref font_info) = self.font_info.get(&*font_key.as_ref().unwrap()) {
|
||||
if let Some(ref outline) = font_info.outline_cache.get(&glyph_id) {
|
||||
cached_outline = Some((*outline).clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut outline = match cached_outline {
|
||||
Some(mut cached_outline) => {
|
||||
let scale = 1.0 / metrics.units_per_em as f32;
|
||||
cached_outline.transform(&(render_options.transform *
|
||||
Transform2F::from_scale(scale)));
|
||||
cached_outline
|
||||
}
|
||||
None => {
|
||||
let transform = if can_cache_outline {
|
||||
Transform2F::from_scale(metrics.units_per_em as f32)
|
||||
} else {
|
||||
render_options.transform
|
||||
};
|
||||
let mut outline_builder = OutlinePathBuilder::new(&transform);
|
||||
font.outline(glyph_id.0, render_options.hinting_options, &mut outline_builder)?;
|
||||
let mut outline = outline_builder.build();
|
||||
if can_cache_outline {
|
||||
let font_key = font_key.as_ref().unwrap();
|
||||
let font_info = self.font_info.get_mut(&*font_key).unwrap();
|
||||
font_info.outline_cache.insert(glyph_id, outline.clone());
|
||||
let scale = 1.0 / metrics.units_per_em as f32;
|
||||
outline.transform(&(render_options.transform *
|
||||
Transform2F::from_scale(scale)));
|
||||
}
|
||||
outline
|
||||
}
|
||||
};
|
||||
|
||||
if let TextRenderMode::Stroke(stroke_style) = render_options.render_mode {
|
||||
let mut stroke_to_fill = OutlineStrokeToFill::new(&outline, stroke_style);
|
||||
stroke_to_fill.offset();
|
||||
outline = stroke_to_fill.into_outline();
|
||||
}
|
||||
|
||||
let mut path = DrawPath::new(outline, paint_id);
|
||||
path.set_clip_path(clip_path);
|
||||
path.set_blend_mode(blend_mode);
|
||||
let mut path = DrawPath::new(outline, render_options.paint_id);
|
||||
path.set_clip_path(render_options.clip_path);
|
||||
path.set_blend_mode(render_options.blend_mode);
|
||||
|
||||
self.push_path(path);
|
||||
scene.push_path(path);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn push_layout(&mut self,
|
||||
/// Attempts to look up a font in the font cache.
|
||||
#[inline]
|
||||
pub fn get_cached_font(&self, postscript_name: &str) -> Option<&F> {
|
||||
self.font_info.get(postscript_name).map(|font_info| &font_info.font)
|
||||
}
|
||||
}
|
||||
|
||||
impl FontContext<DefaultLoader> {
|
||||
pub fn push_layout(&mut self,
|
||||
scene: &mut Scene,
|
||||
layout: &Layout,
|
||||
style: &TextStyle,
|
||||
transform: &Transform2F,
|
||||
render_mode: TextRenderMode,
|
||||
hinting_options: HintingOptions,
|
||||
clip_path: Option<ClipPathId>,
|
||||
blend_mode: BlendMode,
|
||||
paint_id: PaintId)
|
||||
render_options: &FontRenderOptions)
|
||||
-> Result<(), GlyphLoadingError> {
|
||||
for glyph in &layout.glyphs {
|
||||
let offset = glyph.offset;
|
||||
let font = &*glyph.font.font;
|
||||
// FIXME(pcwalton): Cache this!
|
||||
let scale = style.size / (font.metrics().units_per_em as f32);
|
||||
let scale = Vector2F::new(scale, -scale);
|
||||
let transform = *transform * Transform2F::from_scale(scale).translate(offset);
|
||||
self.push_glyph(font,
|
||||
glyph.glyph_id,
|
||||
&transform,
|
||||
render_mode,
|
||||
hinting_options,
|
||||
clip_path,
|
||||
blend_mode,
|
||||
paint_id)?;
|
||||
let scale = vec2f(scale, -scale);
|
||||
let render_options = FontRenderOptions {
|
||||
transform: render_options.transform *
|
||||
Transform2F::from_scale(scale).translate(offset),
|
||||
..*render_options
|
||||
};
|
||||
self.push_glyph(scene, font, GlyphId(glyph.glyph_id), &render_options)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn push_text(&mut self,
|
||||
pub fn push_text(&mut self,
|
||||
scene: &mut Scene,
|
||||
text: &str,
|
||||
style: &TextStyle,
|
||||
collection: &FontCollection,
|
||||
transform: &Transform2F,
|
||||
render_mode: TextRenderMode,
|
||||
hinting_options: HintingOptions,
|
||||
clip_path: Option<ClipPathId>,
|
||||
blend_mode: BlendMode,
|
||||
paint_id: PaintId)
|
||||
render_options: &FontRenderOptions)
|
||||
-> Result<(), GlyphLoadingError> {
|
||||
let layout = skribo::layout(style, collection, text);
|
||||
self.push_layout(&layout,
|
||||
style,
|
||||
&transform,
|
||||
render_mode,
|
||||
hinting_options,
|
||||
clip_path,
|
||||
blend_mode,
|
||||
paint_id)
|
||||
self.push_layout(scene, &layout, style, render_options)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> FontInfo<F> where F: Loader {
|
||||
fn new(font: F) -> FontInfo<F> {
|
||||
FontInfo { font, outline_cache: HashMap::new() }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue