Implement canvas text align

This commit is contained in:
Patrick Walton 2019-05-30 15:08:15 -07:00
parent 5974aeba7b
commit 607a518544
2 changed files with 75 additions and 38 deletions

View File

@ -20,10 +20,10 @@ use pathfinder_geometry::basic::transform2d::Transform2DF;
use pathfinder_geometry::color::ColorU; use pathfinder_geometry::color::ColorU;
use pathfinder_geometry::outline::{Contour, Outline}; use pathfinder_geometry::outline::{Contour, Outline};
use pathfinder_geometry::stroke::{LineCap, LineJoin, OutlineStrokeToFill, StrokeStyle}; use pathfinder_geometry::stroke::{LineCap, LineJoin, OutlineStrokeToFill, StrokeStyle};
use pathfinder_renderer::paint::Paint; use pathfinder_renderer::paint::{Paint, PaintId};
use pathfinder_renderer::scene::{PathObject, Scene}; use pathfinder_renderer::scene::{PathObject, Scene};
use pathfinder_text::{SceneExt, TextRenderMode}; use pathfinder_text::{SceneExt, TextRenderMode};
use skribo::{FontCollection, FontFamily, TextStyle}; use skribo::{FontCollection, FontFamily, Layout, TextStyle};
use std::default::Default; use std::default::Default;
use std::mem; use std::mem;
use std::sync::Arc; use std::sync::Arc;
@ -80,48 +80,49 @@ impl CanvasRenderingContext2D {
// Drawing text // Drawing text
pub fn fill_text(&mut self, string: &str, position: Point2DF) { pub fn fill_text(&mut self, string: &str, position: Point2DF) {
// TODO(pcwalton): Report errors.
let paint_id = self.scene.push_paint(&self.current_state.fill_paint); let paint_id = self.scene.push_paint(&self.current_state.fill_paint);
let transform = Transform2DF::from_translation(position).post_mul(&self.current_state self.fill_or_stroke_text(string, position, paint_id, TextRenderMode::Fill);
.transform);
drop(self.scene.push_text(string,
&TextStyle { size: self.current_state.font_size },
&self.current_state.font_collection,
&transform,
TextRenderMode::Fill,
HintingOptions::None,
paint_id));
} }
pub fn stroke_text(&mut self, string: &str, position: Point2DF) { pub fn stroke_text(&mut self, string: &str, position: Point2DF) {
// TODO(pcwalton): Report errors.
let paint_id = self.scene.push_paint(&self.current_state.stroke_paint); let paint_id = self.scene.push_paint(&self.current_state.stroke_paint);
let transform = Transform2DF::from_translation(position).post_mul(&self.current_state let render_mode = TextRenderMode::Stroke(self.current_state.stroke_style);
.transform); self.fill_or_stroke_text(string, position, paint_id, render_mode);
drop(self.scene.push_text(string,
&TextStyle { size: self.current_state.font_size },
&self.current_state.font_collection,
&transform,
TextRenderMode::Stroke(self.current_state.stroke_style),
HintingOptions::None,
paint_id));
} }
pub fn measure_text(&self, string: &str) -> TextMetrics { pub fn measure_text(&self, string: &str) -> TextMetrics {
let layout = skribo::layout(&TextStyle { size: self.current_state.font_size }, TextMetrics { width: self.layout_text(string).width() }
&self.current_state.font_collection, }
string);
let width = match layout.glyphs.last() { fn fill_or_stroke_text(&mut self,
None => 0.0, string: &str,
Some(last_glyph) => { mut position: Point2DF,
let glyph_id = last_glyph.glyph_id; paint_id: PaintId,
let font_metrics = last_glyph.font.font.metrics(); render_mode: TextRenderMode) {
let glyph_rect = last_glyph.font.font.typographic_bounds(glyph_id).unwrap(); let layout = self.layout_text(string);
let scale_factor = layout.size / font_metrics.units_per_em as f32;
last_glyph.offset.x + glyph_rect.max_x() * scale_factor match self.current_state.text_align {
} TextAlign::Left => {},
}; TextAlign::Right => position.set_x(position.x() - layout.width()),
TextMetrics { width } TextAlign::Center => position.set_x(position.x() - layout.width() * 0.5),
}
let transform = Transform2DF::from_translation(position).post_mul(&self.current_state
.transform);
// TODO(pcwalton): Report errors.
drop(self.scene.push_layout(&layout,
&TextStyle { size: self.current_state.font_size },
&transform,
render_mode,
HintingOptions::None,
paint_id));
}
fn layout_text(&self, string: &str) -> Layout {
skribo::layout(&TextStyle { size: self.current_state.font_size },
&self.current_state.font_collection,
string)
} }
// Line styles // Line styles
@ -158,6 +159,11 @@ impl CanvasRenderingContext2D {
self.current_state.font_size = new_font_size; self.current_state.font_size = new_font_size;
} }
#[inline]
pub fn set_text_align(&mut self, new_text_align: TextAlign) {
self.current_state.text_align = new_text_align;
}
// Drawing paths // Drawing paths
#[inline] #[inline]
@ -236,6 +242,7 @@ pub struct State {
transform: Transform2DF, transform: Transform2DF,
font_collection: Arc<FontCollection>, font_collection: Arc<FontCollection>,
font_size: f32, font_size: f32,
text_align: TextAlign,
fill_paint: Paint, fill_paint: Paint,
stroke_paint: Paint, stroke_paint: Paint,
stroke_style: StrokeStyle, stroke_style: StrokeStyle,
@ -248,6 +255,7 @@ impl State {
transform: Transform2DF::default(), transform: Transform2DF::default(),
font_collection: default_font_collection, font_collection: default_font_collection,
font_size: DEFAULT_FONT_SIZE, font_size: DEFAULT_FONT_SIZE,
text_align: TextAlign::Left,
fill_paint: Paint { color: ColorU::black() }, fill_paint: Paint { color: ColorU::black() },
stroke_paint: Paint { color: ColorU::black() }, stroke_paint: Paint { color: ColorU::black() },
stroke_style: StrokeStyle::default(), stroke_style: StrokeStyle::default(),
@ -340,6 +348,13 @@ impl FillStyle {
} }
} }
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum TextAlign {
Left,
Right,
Center,
}
// TODO(pcwalton): Support other fields. // TODO(pcwalton): Support other fields.
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct TextMetrics { pub struct TextMetrics {
@ -373,3 +388,24 @@ impl CanvasFontContext {
} }
} }
} }
// Text layout utilities
trait LayoutExt {
fn width(&self) -> f32;
}
impl LayoutExt for Layout {
fn width(&self) -> f32 {
let last_glyph = match self.glyphs.last() {
None => return 0.0,
Some(last_glyph) => last_glyph,
};
let glyph_id = last_glyph.glyph_id;
let font_metrics = last_glyph.font.font.metrics();
let glyph_rect = last_glyph.font.font.typographic_bounds(glyph_id).unwrap();
let scale_factor = self.size / font_metrics.units_per_em as f32;
last_glyph.offset.x + glyph_rect.max_x() * scale_factor
}
}

View File

@ -8,7 +8,7 @@
// option. This file may not be copied, modified, or distributed // option. This file may not be copied, modified, or distributed
// except according to those terms. // except according to those terms.
use pathfinder_canvas::{CanvasFontContext, CanvasRenderingContext2D}; use pathfinder_canvas::{CanvasFontContext, CanvasRenderingContext2D, TextAlign};
use pathfinder_geometry::basic::point::{Point2DF, Point2DI}; use pathfinder_geometry::basic::point::{Point2DF, Point2DI};
use pathfinder_geometry::color::ColorF; use pathfinder_geometry::color::ColorF;
use pathfinder_gl::{GLDevice, GLVersion}; use pathfinder_gl::{GLDevice, GLVersion};
@ -58,7 +58,8 @@ fn main() {
// Draw the text. // Draw the text.
canvas.set_font_size(32.0); canvas.set_font_size(32.0);
canvas.fill_text("Hello Pathfinder!", Point2DF::new(32.0, 48.0)); canvas.fill_text("Hello Pathfinder!", Point2DF::new(32.0, 48.0));
canvas.stroke_text("Goodbye Pathfinder!", Point2DF::new(32.0, 96.0)); canvas.set_text_align(TextAlign::Right);
canvas.stroke_text("Goodbye Pathfinder!", Point2DF::new(608.0, 464.0));
// Render the canvas to screen. // Render the canvas to screen.
let scene = SceneProxy::from_scene(canvas.into_scene(), RayonExecutor); let scene = SceneProxy::from_scene(canvas.into_scene(), RayonExecutor);