Add support to canvas for the commonly-used `textBaseline` values
This commit is contained in:
parent
9a0fbe1d11
commit
4af74f225d
|
@ -430,6 +430,7 @@ struct State {
|
||||||
shadow_blur: f32,
|
shadow_blur: f32,
|
||||||
shadow_offset: Vector2F,
|
shadow_offset: Vector2F,
|
||||||
text_align: TextAlign,
|
text_align: TextAlign,
|
||||||
|
text_baseline: TextBaseline,
|
||||||
image_smoothing_enabled: bool,
|
image_smoothing_enabled: bool,
|
||||||
image_smoothing_quality: ImageSmoothingQuality,
|
image_smoothing_quality: ImageSmoothingQuality,
|
||||||
global_alpha: f32,
|
global_alpha: f32,
|
||||||
|
@ -455,6 +456,7 @@ impl State {
|
||||||
shadow_blur: 0.0,
|
shadow_blur: 0.0,
|
||||||
shadow_offset: Vector2F::default(),
|
shadow_offset: Vector2F::default(),
|
||||||
text_align: TextAlign::Left,
|
text_align: TextAlign::Left,
|
||||||
|
text_baseline: TextBaseline::Alphabetic,
|
||||||
image_smoothing_enabled: true,
|
image_smoothing_enabled: true,
|
||||||
image_smoothing_quality: ImageSmoothingQuality::Low,
|
image_smoothing_quality: ImageSmoothingQuality::Low,
|
||||||
global_alpha: 1.0,
|
global_alpha: 1.0,
|
||||||
|
@ -639,6 +641,16 @@ pub enum TextAlign {
|
||||||
Center,
|
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
|
// 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
|
// 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
|
// as part of the line join. Pathfinder's choice is more logical, because the miter limit is
|
||||||
|
|
|
@ -8,11 +8,12 @@
|
||||||
// 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 crate::{CanvasRenderingContext2D, TextAlign};
|
use crate::{CanvasRenderingContext2D, TextAlign, TextBaseline};
|
||||||
use font_kit::family_name::FamilyName;
|
use font_kit::family_name::FamilyName;
|
||||||
use font_kit::handle::Handle;
|
use font_kit::handle::Handle;
|
||||||
use font_kit::hinting::HintingOptions;
|
use font_kit::hinting::HintingOptions;
|
||||||
use font_kit::loaders::default::Font;
|
use font_kit::loaders::default::Font;
|
||||||
|
use font_kit::metrics::Metrics;
|
||||||
use font_kit::properties::Properties;
|
use font_kit::properties::Properties;
|
||||||
use font_kit::source::{Source, SystemSource};
|
use font_kit::source::{Source, SystemSource};
|
||||||
use font_kit::sources::mem::MemSource;
|
use font_kit::sources::mem::MemSource;
|
||||||
|
@ -63,6 +64,17 @@ impl CanvasRenderingContext2D {
|
||||||
TextAlign::Center => position.set_x(position.x() - layout.width() * 0.5),
|
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);
|
let transform = self.current_state.transform * Transform2F::from_translation(position);
|
||||||
|
|
||||||
// TODO(pcwalton): Report errors.
|
// TODO(pcwalton): Report errors.
|
||||||
|
@ -116,15 +128,35 @@ impl CanvasRenderingContext2D {
|
||||||
self.set_font(font.expect("Didn't find the font!").load().unwrap());
|
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]
|
#[inline]
|
||||||
pub fn set_font_size(&mut self, new_font_size: f32) {
|
pub fn set_font_size(&mut self, new_font_size: f32) {
|
||||||
self.current_state.font_size = new_font_size;
|
self.current_state.font_size = new_font_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn text_align(&self) -> TextAlign {
|
||||||
|
self.current_state.text_align
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_text_align(&mut self, new_text_align: TextAlign) {
|
pub fn set_text_align(&mut self, new_text_align: TextAlign) {
|
||||||
self.current_state.text_align = new_text_align;
|
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.
|
// TODO(pcwalton): Support other fields.
|
||||||
|
@ -174,6 +206,12 @@ impl CanvasFontContext {
|
||||||
|
|
||||||
pub trait LayoutExt {
|
pub trait LayoutExt {
|
||||||
fn width(&self) -> f32;
|
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 {
|
impl LayoutExt for Layout {
|
||||||
|
@ -189,4 +227,39 @@ impl LayoutExt for Layout {
|
||||||
let scale_factor = self.size / font_metrics.units_per_em as f32;
|
let scale_factor = self.size / font_metrics.units_per_em as f32;
|
||||||
last_glyph.offset.x + glyph_rect.max_x() * scale_factor
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,8 @@
|
||||||
|
|
||||||
use arrayvec::ArrayVec;
|
use arrayvec::ArrayVec;
|
||||||
use image;
|
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_color::{ColorF, ColorU};
|
||||||
use pathfinder_content::fill::FillRule;
|
use pathfinder_content::fill::FillRule;
|
||||||
use pathfinder_content::gradient::Gradient;
|
use pathfinder_content::gradient::Gradient;
|
||||||
|
@ -571,6 +572,7 @@ fn draw_window(canvas: &mut CanvasRenderingContext2D, title: &str, rect: RectF)
|
||||||
// TODO(pcwalton): Bold text.
|
// TODO(pcwalton): Bold text.
|
||||||
canvas.set_font_size(15.0);
|
canvas.set_font_size(15.0);
|
||||||
canvas.set_text_align(TextAlign::Center);
|
canvas.set_text_align(TextAlign::Center);
|
||||||
|
canvas.set_text_baseline(TextBaseline::Middle);
|
||||||
canvas.set_fill_style(ColorU::new(220, 220, 220, 160));
|
canvas.set_fill_style(ColorU::new(220, 220, 220, 160));
|
||||||
canvas.set_shadow_blur(2.0);
|
canvas.set_shadow_blur(2.0);
|
||||||
canvas.set_shadow_offset(Vector2F::new(0.0, 1.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_font_size(17.0);
|
||||||
canvas.set_fill_style(ColorU::new(255, 255, 255, 64));
|
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,
|
canvas.fill_text(text,
|
||||||
rect.origin() + Vector2F::splat(rect.height()) * Vector2F::new(1.05, 0.5));
|
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_font_size(17.0);
|
||||||
canvas.set_fill_style(ColorU::new(255, 255, 255, 160));
|
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,
|
canvas.fill_text(text,
|
||||||
rect.origin() + Vector2F::splat(rect.height()) * Vector2F::new(0.3, 0.5));
|
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) {
|
fn draw_label(canvas: &mut CanvasRenderingContext2D, text: &str, rect: RectF) {
|
||||||
canvas.set_font_size(15.0);
|
canvas.set_font_size(15.0);
|
||||||
canvas.set_fill_style(ColorU::new(255, 255, 255, 128));
|
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));
|
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_font_size(17.0);
|
||||||
canvas.set_fill_style(ColorU::new(255, 255, 255, 64));
|
canvas.set_fill_style(ColorU::new(255, 255, 255, 64));
|
||||||
canvas.set_text_align(TextAlign::Left);
|
canvas.set_text_align(TextAlign::Left);
|
||||||
|
canvas.set_text_baseline(TextBaseline::Middle);
|
||||||
canvas.fill_text(text,
|
canvas.fill_text(text,
|
||||||
rect.origin() + Vector2F::splat(rect.height()) * Vector2F::new(0.3, 0.5));
|
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_fill_style(ColorU::new(255, 255, 255, 64));
|
||||||
canvas.set_text_align(TextAlign::Right);
|
canvas.set_text_align(TextAlign::Right);
|
||||||
|
canvas.set_text_baseline(TextBaseline::Middle);
|
||||||
canvas.fill_text(
|
canvas.fill_text(
|
||||||
unit,
|
unit,
|
||||||
rect.upper_right() + Vector2F::new(-0.3, 0.5) * Vector2F::splat(rect.height()));
|
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_font_size(17.0);
|
||||||
canvas.set_fill_style(ColorU::new(255, 255, 255, 128));
|
canvas.set_fill_style(ColorU::new(255, 255, 255, 128));
|
||||||
canvas.set_text_align(TextAlign::Right);
|
canvas.set_text_align(TextAlign::Right);
|
||||||
|
canvas.set_text_baseline(TextBaseline::Middle);
|
||||||
canvas.fill_text(value,
|
canvas.fill_text(value,
|
||||||
rect.upper_right() + Vector2F::new(-unit_width - rect.height() * 0.5,
|
rect.upper_right() + Vector2F::new(-unit_width - rect.height() * 0.5,
|
||||||
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_font_size(15.0);
|
||||||
canvas.set_fill_style(ColorU::new(255, 255, 255, 160));
|
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));
|
canvas.fill_text(text, rect.origin() + Vector2F::new(28.0, rect.height() * 0.5));
|
||||||
|
|
||||||
let check_box_rect =
|
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);
|
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_fill_style(ColorU::new(0, 0, 0, 160));
|
||||||
canvas.set_text_align(TextAlign::Left);
|
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.fill_text(text, text_origin - Vector2F::new(0.0, 1.0));
|
||||||
canvas.set_fill_style(ColorU::new(255, 255, 255, 160));
|
canvas.set_fill_style(ColorU::new(255, 255, 255, 160));
|
||||||
canvas.fill_text(text, text_origin);
|
canvas.fill_text(text, text_origin);
|
||||||
|
|
Loading…
Reference in New Issue