diff --git a/canvas/src/lib.rs b/canvas/src/lib.rs index 01674d2d..4a80e0e7 100644 --- a/canvas/src/lib.rs +++ b/canvas/src/lib.rs @@ -20,10 +20,10 @@ use pathfinder_geometry::basic::transform2d::Transform2DF; use pathfinder_geometry::color::ColorU; use pathfinder_geometry::outline::{Contour, Outline}; 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_text::{SceneExt, TextRenderMode}; -use skribo::{FontCollection, FontFamily, TextStyle}; +use skribo::{FontCollection, FontFamily, Layout, TextStyle}; use std::default::Default; use std::mem; use std::sync::Arc; @@ -80,48 +80,49 @@ impl CanvasRenderingContext2D { // Drawing text 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 transform = Transform2DF::from_translation(position).post_mul(&self.current_state - .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)); + self.fill_or_stroke_text(string, position, paint_id, TextRenderMode::Fill); } 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 transform = Transform2DF::from_translation(position).post_mul(&self.current_state - .transform); - 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)); + let render_mode = TextRenderMode::Stroke(self.current_state.stroke_style); + self.fill_or_stroke_text(string, position, paint_id, render_mode); } pub fn measure_text(&self, string: &str) -> TextMetrics { - let layout = skribo::layout(&TextStyle { size: self.current_state.font_size }, - &self.current_state.font_collection, - string); - let width = match layout.glyphs.last() { - None => 0.0, - Some(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 = layout.size / font_metrics.units_per_em as f32; - last_glyph.offset.x + glyph_rect.max_x() * scale_factor - } - }; - TextMetrics { width } + TextMetrics { width: self.layout_text(string).width() } + } + + fn fill_or_stroke_text(&mut self, + string: &str, + mut position: Point2DF, + paint_id: PaintId, + render_mode: TextRenderMode) { + let layout = self.layout_text(string); + + match self.current_state.text_align { + TextAlign::Left => {}, + TextAlign::Right => position.set_x(position.x() - layout.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 @@ -158,6 +159,11 @@ impl CanvasRenderingContext2D { 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 #[inline] @@ -236,6 +242,7 @@ pub struct State { transform: Transform2DF, font_collection: Arc, font_size: f32, + text_align: TextAlign, fill_paint: Paint, stroke_paint: Paint, stroke_style: StrokeStyle, @@ -248,6 +255,7 @@ impl State { transform: Transform2DF::default(), font_collection: default_font_collection, font_size: DEFAULT_FONT_SIZE, + text_align: TextAlign::Left, fill_paint: Paint { color: ColorU::black() }, stroke_paint: Paint { color: ColorU::black() }, 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. #[derive(Clone, Copy, Debug)] 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 + } +} diff --git a/examples/canvas_text/src/main.rs b/examples/canvas_text/src/main.rs index 5306138f..986a3456 100644 --- a/examples/canvas_text/src/main.rs +++ b/examples/canvas_text/src/main.rs @@ -8,7 +8,7 @@ // option. This file may not be copied, modified, or distributed // 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::color::ColorF; use pathfinder_gl::{GLDevice, GLVersion}; @@ -58,7 +58,8 @@ fn main() { // Draw the text. canvas.set_font_size(32.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. let scene = SceneProxy::from_scene(canvas.into_scene(), RayonExecutor);