2019-05-09 15:29:39 -04:00
|
|
|
// pathfinder/text/src/lib.rs
|
|
|
|
//
|
|
|
|
// Copyright © 2019 The Pathfinder Project Developers.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
|
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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;
|
2019-05-14 21:26:10 -04:00
|
|
|
use pathfinder_renderer::paint::PaintId;
|
|
|
|
use pathfinder_renderer::scene::{PathObject, Scene};
|
2019-05-09 15:29:39 -04:00
|
|
|
use skribo::{FontCollection, Layout, TextStyle};
|
|
|
|
use std::mem;
|
|
|
|
|
|
|
|
pub trait SceneExt {
|
|
|
|
// TODO(pcwalton): Support stroked glyphs.
|
|
|
|
fn push_glyph<F>(&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<F>(&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;
|
|
|
|
}
|
|
|
|
|
2019-05-10 15:03:38 -04:00
|
|
|
self.push_path(PathObject::new(outline, paint_id, String::new()));
|
2019-05-09 15:29:39 -04:00
|
|
|
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 =
|
2019-05-13 15:17:49 -04:00
|
|
|
Transform2DF32::from_scale(scale).post_mul(transform).post_translate(offset);
|
2019-05-09 15:29:39 -04:00
|
|
|
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<f32>) -> Point2DF32 {
|
2019-05-13 15:17:49 -04:00
|
|
|
self.transform.transform_point(Point2DF32::new(point.x, point.y))
|
2019-05-09 15:29:39 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PathBuilder for OutlinePathBuilder {
|
|
|
|
fn quadratic_bezier_to(&mut self, ctrl: Point2D<f32>, to: Point2D<f32>) {
|
|
|
|
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<f32>, ctrl1: Point2D<f32>, to: Point2D<f32>) {
|
|
|
|
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<f32>,
|
|
|
|
_radii: Vector2D<f32>,
|
|
|
|
_sweep_angle: Angle<f32>,
|
|
|
|
_x_rotation: Angle<f32>) {
|
|
|
|
// TODO(pcwalton): Arcs.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl FlatPathBuilder for OutlinePathBuilder {
|
|
|
|
type PathType = Outline;
|
|
|
|
|
|
|
|
fn move_to(&mut self, to: Point2D<f32>) {
|
|
|
|
self.flush_current_contour();
|
|
|
|
let to = self.convert_point(to);
|
|
|
|
self.current_contour.push_endpoint(to);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn line_to(&mut self, to: Point2D<f32>) {
|
|
|
|
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<f32> {
|
|
|
|
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())
|
|
|
|
}
|
|
|
|
}
|