// pathfinder/text/src/lib.rs // // Copyright © 2019 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. use euclid::{Angle, Point2D, Vector2D}; use font_kit::error::GlyphLoadingError; use font_kit::hinting::HintingOptions; use font_kit::loader::Loader; use lyon_path::builder::{FlatPathBuilder, PathBuilder}; use pathfinder_geometry::basic::point::Point2DF32; use pathfinder_geometry::basic::transform2d::Transform2DF32; use pathfinder_geometry::outline::{Contour, Outline}; use pathfinder_geometry::stroke::OutlineStrokeToFill; use pathfinder_renderer::paint::PaintId; use pathfinder_renderer::scene::{PathObject, Scene}; use skribo::{FontCollection, Layout, TextStyle}; use std::mem; pub trait SceneExt { // TODO(pcwalton): Support stroked glyphs. fn push_glyph(&mut self, font: &F, glyph_id: u32, transform: &Transform2DF32, render_mode: TextRenderMode, hinting_options: HintingOptions, paint_id: PaintId) -> Result<(), GlyphLoadingError> where F: Loader; fn push_layout(&mut self, layout: &Layout, style: &TextStyle, transform: &Transform2DF32, render_mode: TextRenderMode, hinting_options: HintingOptions, paint_id: PaintId) -> Result<(), GlyphLoadingError>; fn push_text(&mut self, text: &str, style: &TextStyle, collection: &FontCollection, transform: &Transform2DF32, render_mode: TextRenderMode, hinting_options: HintingOptions, paint_id: PaintId) -> Result<(), GlyphLoadingError>; } impl SceneExt for Scene { #[inline] fn push_glyph(&mut self, font: &F, glyph_id: u32, transform: &Transform2DF32, render_mode: TextRenderMode, hinting_options: HintingOptions, 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(); if let TextRenderMode::Stroke(stroke_width) = render_mode { let mut stroke_to_fill = OutlineStrokeToFill::new(outline, stroke_width); stroke_to_fill.offset(); outline = stroke_to_fill.outline; } self.push_path(PathObject::new(outline, paint_id, String::new())); Ok(()) } fn push_layout(&mut self, layout: &Layout, style: &TextStyle, transform: &Transform2DF32, render_mode: TextRenderMode, hinting_options: HintingOptions, paint_id: PaintId) -> Result<(), GlyphLoadingError> { for glyph in &layout.glyphs { let offset = Point2DF32::new(glyph.offset.x, glyph.offset.y); let font = &*glyph.font.font; // FIXME(pcwalton): Cache this! let scale = style.size / (font.metrics().units_per_em as f32); let scale = Point2DF32::new(scale, -scale); let transform = Transform2DF32::from_scale(scale).post_mul(transform).post_translate(offset); self.push_glyph(font, glyph.glyph_id, &transform, render_mode, hinting_options, paint_id)?; } Ok(()) } #[inline] fn push_text(&mut self, text: &str, style: &TextStyle, collection: &FontCollection, transform: &Transform2DF32, render_mode: TextRenderMode, hinting_options: HintingOptions, paint_id: PaintId) -> Result<(), GlyphLoadingError> { let layout = skribo::layout(style, collection, text); self.push_layout(&layout, style, &transform, render_mode, hinting_options, paint_id) } } #[derive(Clone, Copy, PartialEq, Debug)] pub enum TextRenderMode { Fill, Stroke(f32), } struct OutlinePathBuilder { outline: Outline, current_contour: Contour, transform: Transform2DF32, } impl OutlinePathBuilder { fn new(transform: &Transform2DF32) -> OutlinePathBuilder { OutlinePathBuilder { outline: Outline::new(), current_contour: Contour::new(), transform: *transform, } } fn flush_current_contour(&mut self) { if !self.current_contour.is_empty() { self.outline.push_contour(mem::replace(&mut self.current_contour, Contour::new())); } } fn convert_point(&self, point: Point2D) -> Point2DF32 { self.transform.transform_point(Point2DF32::new(point.x, point.y)) } } impl PathBuilder for OutlinePathBuilder { fn quadratic_bezier_to(&mut self, ctrl: Point2D, to: Point2D) { let (ctrl, to) = (self.convert_point(ctrl), self.convert_point(to)); self.current_contour.push_quadratic(ctrl, to); } fn cubic_bezier_to(&mut self, ctrl0: Point2D, ctrl1: Point2D, to: Point2D) { let (ctrl0, ctrl1) = (self.convert_point(ctrl0), self.convert_point(ctrl1)); let to = self.convert_point(to); self.current_contour.push_cubic(ctrl0, ctrl1, to); } fn arc(&mut self, _center: Point2D, _radii: Vector2D, _sweep_angle: Angle, _x_rotation: Angle) { // TODO(pcwalton): Arcs. } } impl FlatPathBuilder for OutlinePathBuilder { type PathType = Outline; fn move_to(&mut self, to: Point2D) { self.flush_current_contour(); let to = self.convert_point(to); self.current_contour.push_endpoint(to); } fn line_to(&mut self, to: Point2D) { let to = self.convert_point(to); self.current_contour.push_endpoint(to); } fn close(&mut self) { self.current_contour.close(); } fn current_position(&self) -> Point2D { if self.current_contour.is_empty() { return Point2D::new(0.0, 0.0) } let point_index = if self.current_contour.is_closed() { 0 } else { self.current_contour.len() - 1 }; let point = self.current_contour.position_of(point_index); Point2D::new(point.x(), point.y()) } fn build(mut self) -> Outline { self.flush_current_contour(); self.outline } fn build_and_reset(&mut self) -> Outline { self.flush_current_contour(); mem::replace(&mut self.outline, Outline::new()) } }