Add support to canvas for the commonly-used `textBaseline` values

This commit is contained in:
Patrick Walton 2020-03-30 22:00:13 -07:00
parent 9a0fbe1d11
commit 4af74f225d
3 changed files with 101 additions and 2 deletions

View File

@ -430,6 +430,7 @@ struct State {
shadow_blur: f32,
shadow_offset: Vector2F,
text_align: TextAlign,
text_baseline: TextBaseline,
image_smoothing_enabled: bool,
image_smoothing_quality: ImageSmoothingQuality,
global_alpha: f32,
@ -455,6 +456,7 @@ impl State {
shadow_blur: 0.0,
shadow_offset: Vector2F::default(),
text_align: TextAlign::Left,
text_baseline: TextBaseline::Alphabetic,
image_smoothing_enabled: true,
image_smoothing_quality: ImageSmoothingQuality::Low,
global_alpha: 1.0,
@ -639,6 +641,16 @@ pub enum TextAlign {
Center,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum TextBaseline {
Alphabetic,
Top,
Hanging,
Middle,
Ideographic,
Bottom,
}
// We duplicate `pathfinder_content::stroke::LineJoin` here because the HTML canvas API treats the
// miter limit as part of the canvas state, while the native Pathfinder API treats the miter limit
// as part of the line join. Pathfinder's choice is more logical, because the miter limit is

View File

@ -8,11 +8,12 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use crate::{CanvasRenderingContext2D, TextAlign};
use crate::{CanvasRenderingContext2D, TextAlign, TextBaseline};
use font_kit::family_name::FamilyName;
use font_kit::handle::Handle;
use font_kit::hinting::HintingOptions;
use font_kit::loaders::default::Font;
use font_kit::metrics::Metrics;
use font_kit::properties::Properties;
use font_kit::source::{Source, SystemSource};
use font_kit::sources::mem::MemSource;
@ -63,6 +64,17 @@ impl CanvasRenderingContext2D {
TextAlign::Center => position.set_x(position.x() - layout.width() * 0.5),
}
match self.current_state.text_baseline {
TextBaseline::Alphabetic => {}
TextBaseline::Top => position.set_y(position.y() + layout.ascent()),
TextBaseline::Middle => position.set_y(position.y() + layout.ascent() * 0.5),
TextBaseline::Bottom => position.set_y(position.y() + layout.descent()),
TextBaseline::Ideographic => {
position.set_y(position.y() + layout.ideographic_baseline())
}
TextBaseline::Hanging => position.set_y(position.y() + layout.hanging_baseline()),
}
let transform = self.current_state.transform * Transform2F::from_translation(position);
// TODO(pcwalton): Report errors.
@ -116,15 +128,35 @@ impl CanvasRenderingContext2D {
self.set_font(font.expect("Didn't find the font!").load().unwrap());
}
#[inline]
pub fn font_size(&self) -> f32 {
self.current_state.font_size
}
#[inline]
pub fn set_font_size(&mut self, new_font_size: f32) {
self.current_state.font_size = new_font_size;
}
#[inline]
pub fn text_align(&self) -> TextAlign {
self.current_state.text_align
}
#[inline]
pub fn set_text_align(&mut self, new_text_align: TextAlign) {
self.current_state.text_align = new_text_align;
}
#[inline]
pub fn text_baseline(&self) -> TextBaseline {
self.current_state.text_baseline
}
#[inline]
pub fn set_text_baseline(&mut self, new_text_baseline: TextBaseline) {
self.current_state.text_baseline = new_text_baseline;
}
}
// TODO(pcwalton): Support other fields.
@ -174,6 +206,12 @@ impl CanvasFontContext {
pub trait LayoutExt {
fn width(&self) -> f32;
fn fold_metric<G, F>(&self, get: G, fold: F) -> f32 where G: FnMut(&Metrics) -> f32,
F: FnMut(f32, f32) -> f32;
fn ascent(&self) -> f32;
fn descent(&self) -> f32;
fn hanging_baseline(&self) -> f32;
fn ideographic_baseline(&self) -> f32;
}
impl LayoutExt for Layout {
@ -189,4 +227,39 @@ impl LayoutExt for Layout {
let scale_factor = self.size / font_metrics.units_per_em as f32;
last_glyph.offset.x + glyph_rect.max_x() * scale_factor
}
fn fold_metric<G, F>(&self, mut get: G, mut fold: F) -> f32 where G: FnMut(&Metrics) -> f32,
F: FnMut(f32, f32) -> f32 {
let (mut last_font_seen, mut value) = (None, 0.0);
for glyph in &self.glyphs {
if let Some(ref last_font_seen) = last_font_seen {
if Arc::ptr_eq(last_font_seen, &glyph.font.font) {
continue;
}
}
let font_metrics = glyph.font.font.metrics();
let scale_factor = self.size / font_metrics.units_per_em as f32;
value = fold(value, get(&font_metrics) * scale_factor);
last_font_seen = Some(glyph.font.font.clone());
}
value
}
fn ascent(&self) -> f32 {
self.fold_metric(|metrics| metrics.ascent, f32::max)
}
fn descent(&self) -> f32 {
self.fold_metric(|metrics| metrics.descent, f32::min)
}
fn hanging_baseline(&self) -> f32 {
// TODO(pcwalton)
0.0
}
fn ideographic_baseline(&self) -> f32 {
// TODO(pcwalton)
0.0
}
}

View File

@ -10,7 +10,8 @@
use arrayvec::ArrayVec;
use image;
use pathfinder_canvas::{CanvasFontContext, CanvasRenderingContext2D, LineJoin, Path2D, TextAlign};
use pathfinder_canvas::{CanvasFontContext, CanvasRenderingContext2D, LineJoin, Path2D};
use pathfinder_canvas::{TextAlign, TextBaseline};
use pathfinder_color::{ColorF, ColorU};
use pathfinder_content::fill::FillRule;
use pathfinder_content::gradient::Gradient;
@ -571,6 +572,7 @@ fn draw_window(canvas: &mut CanvasRenderingContext2D, title: &str, rect: RectF)
// TODO(pcwalton): Bold text.
canvas.set_font_size(15.0);
canvas.set_text_align(TextAlign::Center);
canvas.set_text_baseline(TextBaseline::Middle);
canvas.set_fill_style(ColorU::new(220, 220, 220, 160));
canvas.set_shadow_blur(2.0);
canvas.set_shadow_offset(Vector2F::new(0.0, 1.0));
@ -594,6 +596,8 @@ fn draw_search_box(canvas: &mut CanvasRenderingContext2D, text: &str, rect: Rect
canvas.set_font_size(17.0);
canvas.set_fill_style(ColorU::new(255, 255, 255, 64));
canvas.set_text_align(TextAlign::Left);
canvas.set_text_baseline(TextBaseline::Middle);
canvas.fill_text(text,
rect.origin() + Vector2F::splat(rect.height()) * Vector2F::new(1.05, 0.5));
}
@ -615,6 +619,8 @@ fn draw_dropdown(canvas: &mut CanvasRenderingContext2D, text: &str, rect: RectF)
canvas.set_font_size(17.0);
canvas.set_fill_style(ColorU::new(255, 255, 255, 160));
canvas.set_text_align(TextAlign::Left);
canvas.set_text_baseline(TextBaseline::Middle);
canvas.fill_text(text,
rect.origin() + Vector2F::splat(rect.height()) * Vector2F::new(0.3, 0.5));
}
@ -622,6 +628,8 @@ fn draw_dropdown(canvas: &mut CanvasRenderingContext2D, text: &str, rect: RectF)
fn draw_label(canvas: &mut CanvasRenderingContext2D, text: &str, rect: RectF) {
canvas.set_font_size(15.0);
canvas.set_fill_style(ColorU::new(255, 255, 255, 128));
canvas.set_text_align(TextAlign::Left);
canvas.set_text_baseline(TextBaseline::Middle);
canvas.fill_text(text, rect.origin() + Vector2F::new(0.0, rect.height() * 0.5));
}
@ -649,6 +657,7 @@ fn draw_text_edit_box(canvas: &mut CanvasRenderingContext2D, text: &str, rect: R
canvas.set_font_size(17.0);
canvas.set_fill_style(ColorU::new(255, 255, 255, 64));
canvas.set_text_align(TextAlign::Left);
canvas.set_text_baseline(TextBaseline::Middle);
canvas.fill_text(text,
rect.origin() + Vector2F::splat(rect.height()) * Vector2F::new(0.3, 0.5));
}
@ -664,6 +673,7 @@ fn draw_numeric_edit_box(canvas: &mut CanvasRenderingContext2D,
canvas.set_fill_style(ColorU::new(255, 255, 255, 64));
canvas.set_text_align(TextAlign::Right);
canvas.set_text_baseline(TextBaseline::Middle);
canvas.fill_text(
unit,
rect.upper_right() + Vector2F::new(-0.3, 0.5) * Vector2F::splat(rect.height()));
@ -671,6 +681,7 @@ fn draw_numeric_edit_box(canvas: &mut CanvasRenderingContext2D,
canvas.set_font_size(17.0);
canvas.set_fill_style(ColorU::new(255, 255, 255, 128));
canvas.set_text_align(TextAlign::Right);
canvas.set_text_baseline(TextBaseline::Middle);
canvas.fill_text(value,
rect.upper_right() + Vector2F::new(-unit_width - rect.height() * 0.5,
rect.height() * 0.5));
@ -681,6 +692,8 @@ fn draw_check_box(canvas: &mut CanvasRenderingContext2D, text: &str, rect: RectF
canvas.set_font_size(15.0);
canvas.set_fill_style(ColorU::new(255, 255, 255, 160));
canvas.set_text_align(TextAlign::Left);
canvas.set_text_baseline(TextBaseline::Middle);
canvas.fill_text(text, rect.origin() + Vector2F::new(28.0, rect.height() * 0.5));
let check_box_rect =
@ -727,6 +740,7 @@ fn draw_button(canvas: &mut CanvasRenderingContext2D, text: &str, rect: RectF, c
let text_origin = rect.center() + Vector2F::new(icon_width * 0.25 - text_width * 0.5, 0.0);
canvas.set_fill_style(ColorU::new(0, 0, 0, 160));
canvas.set_text_align(TextAlign::Left);
canvas.set_text_baseline(TextBaseline::Middle);
canvas.fill_text(text, text_origin - Vector2F::new(0.0, 1.0));
canvas.set_fill_style(ColorU::new(255, 255, 255, 160));
canvas.fill_text(text, text_origin);