mirror of
https://github.com/servo/servo.git
synced 2025-07-22 23:03:42 +01:00
enhance: Implement CanvasRenderingContext2D.measureText
(#32704)
Signed-off-by: Chocolate Pie <106949016+chocolate-pie@users.noreply.github.com> Co-authored-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
parent
d82232d549
commit
1223335547
14 changed files with 325 additions and 51 deletions
|
@ -7,15 +7,16 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use app_units::Au;
|
use app_units::Au;
|
||||||
use canvas_traits::canvas::*;
|
use canvas_traits::canvas::*;
|
||||||
use euclid::default::{Point2D, Rect, Size2D, Transform2D, Vector2D};
|
use euclid::default::{Box2D, Point2D, Rect, Size2D, Transform2D, Vector2D};
|
||||||
use euclid::point2;
|
use euclid::point2;
|
||||||
use fonts::{
|
use fonts::{
|
||||||
FontCacheThread, FontContext, FontMetrics, FontRef, GlyphStore, ShapingFlags, ShapingOptions,
|
ByteIndex, FontBaseline, FontCacheThread, FontContext, FontGroup, FontMetrics, FontRef,
|
||||||
LAST_RESORT_GLYPH_ADVANCE,
|
GlyphInfo, GlyphStore, ShapingFlags, ShapingOptions, LAST_RESORT_GLYPH_ADVANCE,
|
||||||
};
|
};
|
||||||
use ipc_channel::ipc::{IpcSender, IpcSharedMemory};
|
use ipc_channel::ipc::{IpcSender, IpcSharedMemory};
|
||||||
use log::{debug, warn};
|
use log::{debug, warn};
|
||||||
use num_traits::ToPrimitive;
|
use num_traits::ToPrimitive;
|
||||||
|
use range::Range;
|
||||||
use servo_arc::Arc as ServoArc;
|
use servo_arc::Arc as ServoArc;
|
||||||
use style::color::AbsoluteColor;
|
use style::color::AbsoluteColor;
|
||||||
use style::properties::style_structs::Font as FontStyleStruct;
|
use style::properties::style_structs::Font as FontStyleStruct;
|
||||||
|
@ -277,6 +278,29 @@ pub struct TextRun {
|
||||||
pub glyphs: Arc<GlyphStore>,
|
pub glyphs: Arc<GlyphStore>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TextRun {
|
||||||
|
fn bounding_box(&self) -> Rect<f32> {
|
||||||
|
let mut bounding_box = None;
|
||||||
|
let mut bounds_offset: f32 = 0.;
|
||||||
|
let glyph_ids = self
|
||||||
|
.glyphs
|
||||||
|
.iter_glyphs_for_byte_range(&Range::new(ByteIndex(0), self.glyphs.len()))
|
||||||
|
.map(GlyphInfo::id);
|
||||||
|
for glyph_id in glyph_ids {
|
||||||
|
let bounds = self.font.typographic_bounds(glyph_id);
|
||||||
|
let amount = Vector2D::new(bounds_offset, 0.);
|
||||||
|
let bounds = bounds.translate(amount);
|
||||||
|
let initiated_bbox = bounding_box.get_or_insert_with(|| {
|
||||||
|
let origin = Point2D::new(bounds.min_x(), 0.);
|
||||||
|
Box2D::new(origin, origin).to_rect()
|
||||||
|
});
|
||||||
|
bounding_box = Some(initiated_bbox.union(&bounds));
|
||||||
|
bounds_offset = bounds.max_x();
|
||||||
|
}
|
||||||
|
bounding_box.unwrap_or_default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This defines required methods for a DrawTarget (currently only implemented for raqote). The
|
// This defines required methods for a DrawTarget (currently only implemented for raqote). The
|
||||||
// prototypes are derived from the now-removed Azure backend's methods.
|
// prototypes are derived from the now-removed Azure backend's methods.
|
||||||
pub trait GenericDrawTarget {
|
pub trait GenericDrawTarget {
|
||||||
|
@ -523,33 +547,7 @@ impl<'a> CanvasData<'a> {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut runs = Vec::new();
|
let runs = self.build_unshaped_text_runs(&text, &mut font_group);
|
||||||
let mut current_text_run = UnshapedTextRun::default();
|
|
||||||
let mut current_text_run_start_index = 0;
|
|
||||||
for (index, character) in text.char_indices() {
|
|
||||||
// TODO: This should ultimately handle emoji variation selectors, but raqote does not yet
|
|
||||||
// have support for color glyphs.
|
|
||||||
let script = Script::from(character);
|
|
||||||
let font = font_group.find_by_codepoint(&self.font_context, character, None);
|
|
||||||
|
|
||||||
if !current_text_run.script_and_font_compatible(script, &font) {
|
|
||||||
let previous_text_run = mem::replace(
|
|
||||||
&mut current_text_run,
|
|
||||||
UnshapedTextRun {
|
|
||||||
font: font.clone(),
|
|
||||||
script,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
);
|
|
||||||
current_text_run_start_index = index;
|
|
||||||
runs.push(previous_text_run)
|
|
||||||
}
|
|
||||||
|
|
||||||
current_text_run.string =
|
|
||||||
&text[current_text_run_start_index..index + character.len_utf8()];
|
|
||||||
}
|
|
||||||
runs.push(current_text_run);
|
|
||||||
|
|
||||||
// TODO: This doesn't do any kind of line layout at all. In particular, there needs
|
// TODO: This doesn't do any kind of line layout at all. In particular, there needs
|
||||||
// to be some alignment along a baseline and also support for bidi text.
|
// to be some alignment along a baseline and also support for bidi text.
|
||||||
let shaped_runs: Vec<_> = runs
|
let shaped_runs: Vec<_> = runs
|
||||||
|
@ -617,6 +615,123 @@ impl<'a> CanvasData<'a> {
|
||||||
self.fill_text_with_size(text, x, y, max_width, is_rtl, size.px() as f64);
|
self.fill_text_with_size(text, x, y, max_width, is_rtl, size.px() as f64);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <https://html.spec.whatwg.org/multipage/#text-preparation-algorithm>
|
||||||
|
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-measuretext>
|
||||||
|
pub fn measure_text(&mut self, text: String) -> TextMetrics {
|
||||||
|
// > Step 2: Replace all ASCII whitespace in text with U+0020 SPACE characters.
|
||||||
|
let text = replace_ascii_whitespace(text);
|
||||||
|
let Some(ref font_style) = self.state.font_style else {
|
||||||
|
return TextMetrics::default();
|
||||||
|
};
|
||||||
|
|
||||||
|
let font_group = self.font_context.font_group(font_style.clone());
|
||||||
|
let mut font_group = font_group.write();
|
||||||
|
let font = font_group
|
||||||
|
.first(&self.font_context)
|
||||||
|
.expect("couldn't find font");
|
||||||
|
let ascent = font.metrics.ascent.to_f32_px();
|
||||||
|
let descent = font.metrics.descent.to_f32_px();
|
||||||
|
let runs = self.build_unshaped_text_runs(&text, &mut font_group);
|
||||||
|
|
||||||
|
let shaped_runs: Vec<_> = runs
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(UnshapedTextRun::to_shaped_text_run)
|
||||||
|
.collect();
|
||||||
|
let total_advance = shaped_runs
|
||||||
|
.iter()
|
||||||
|
.map(|run| run.glyphs.total_advance())
|
||||||
|
.sum::<Au>()
|
||||||
|
.to_f32_px();
|
||||||
|
let bounding_box = shaped_runs
|
||||||
|
.iter()
|
||||||
|
.map(TextRun::bounding_box)
|
||||||
|
.reduce(|a, b| {
|
||||||
|
let amount = Vector2D::new(a.max_x(), 0.);
|
||||||
|
let bounding_box = b.translate(amount);
|
||||||
|
a.union(&bounding_box)
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let FontBaseline {
|
||||||
|
ideographic_baseline,
|
||||||
|
alphabetic_baseline,
|
||||||
|
hanging_baseline,
|
||||||
|
} = match font.get_baseline() {
|
||||||
|
Some(baseline) => baseline,
|
||||||
|
None => FontBaseline {
|
||||||
|
hanging_baseline: ascent * HANGING_BASELINE_DEFAULT,
|
||||||
|
ideographic_baseline: -descent * IDEOGRAPHIC_BASELINE_DEFAULT,
|
||||||
|
alphabetic_baseline: 0.,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let anchor_x = match self.state.text_align {
|
||||||
|
TextAlign::End => total_advance,
|
||||||
|
TextAlign::Center => total_advance / 2.,
|
||||||
|
TextAlign::Right => total_advance,
|
||||||
|
_ => 0.,
|
||||||
|
};
|
||||||
|
let anchor_y = match self.state.text_baseline {
|
||||||
|
TextBaseline::Top => ascent,
|
||||||
|
TextBaseline::Hanging => hanging_baseline,
|
||||||
|
TextBaseline::Ideographic => ideographic_baseline,
|
||||||
|
TextBaseline::Middle => (ascent - descent) / 2.,
|
||||||
|
TextBaseline::Alphabetic => alphabetic_baseline,
|
||||||
|
TextBaseline::Bottom => -descent,
|
||||||
|
};
|
||||||
|
|
||||||
|
TextMetrics {
|
||||||
|
width: total_advance,
|
||||||
|
actual_boundingbox_left: anchor_x - bounding_box.min_x(),
|
||||||
|
actual_boundingbox_right: bounding_box.max_x() - anchor_x,
|
||||||
|
actual_boundingbox_ascent: bounding_box.max_y() - anchor_y,
|
||||||
|
actual_boundingbox_descent: anchor_y - bounding_box.min_y(),
|
||||||
|
font_boundingbox_ascent: ascent - anchor_y,
|
||||||
|
font_boundingbox_descent: descent + anchor_y,
|
||||||
|
em_height_ascent: ascent - anchor_y,
|
||||||
|
em_height_descent: descent + anchor_y,
|
||||||
|
hanging_baseline: hanging_baseline - anchor_y,
|
||||||
|
alphabetic_baseline: alphabetic_baseline - anchor_y,
|
||||||
|
ideographic_baseline: ideographic_baseline - anchor_y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_unshaped_text_runs<'b>(
|
||||||
|
&self,
|
||||||
|
text: &'b str,
|
||||||
|
font_group: &mut FontGroup,
|
||||||
|
) -> Vec<UnshapedTextRun<'b>> {
|
||||||
|
let mut runs = Vec::new();
|
||||||
|
let mut current_text_run = UnshapedTextRun::default();
|
||||||
|
let mut current_text_run_start_index = 0;
|
||||||
|
|
||||||
|
for (index, character) in text.char_indices() {
|
||||||
|
// TODO: This should ultimately handle emoji variation selectors, but raqote does not yet
|
||||||
|
// have support for color glyphs.
|
||||||
|
let script = Script::from(character);
|
||||||
|
let font = font_group.find_by_codepoint(&self.font_context, character, None);
|
||||||
|
|
||||||
|
if !current_text_run.script_and_font_compatible(script, &font) {
|
||||||
|
let previous_text_run = mem::replace(
|
||||||
|
&mut current_text_run,
|
||||||
|
UnshapedTextRun {
|
||||||
|
font: font.clone(),
|
||||||
|
script,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
current_text_run_start_index = index;
|
||||||
|
runs.push(previous_text_run)
|
||||||
|
}
|
||||||
|
|
||||||
|
current_text_run.string =
|
||||||
|
&text[current_text_run_start_index..index + character.len_utf8()];
|
||||||
|
}
|
||||||
|
|
||||||
|
runs.push(current_text_run);
|
||||||
|
runs
|
||||||
|
}
|
||||||
|
|
||||||
/// Find the *anchor_point* for the given parameters of a line of text.
|
/// Find the *anchor_point* for the given parameters of a line of text.
|
||||||
/// See <https://html.spec.whatwg.org/multipage/#text-preparation-algorithm>.
|
/// See <https://html.spec.whatwg.org/multipage/#text-preparation-algorithm>.
|
||||||
fn find_anchor_point_for_line_of_text(
|
fn find_anchor_point_for_line_of_text(
|
||||||
|
|
|
@ -234,6 +234,10 @@ impl<'a> CanvasPaintThread<'a> {
|
||||||
Canvas2dMsg::Ellipse(ref center, radius_x, radius_y, rotation, start, end, ccw) => self
|
Canvas2dMsg::Ellipse(ref center, radius_x, radius_y, rotation, start, end, ccw) => self
|
||||||
.canvas(canvas_id)
|
.canvas(canvas_id)
|
||||||
.ellipse(center, radius_x, radius_y, rotation, start, end, ccw),
|
.ellipse(center, radius_x, radius_y, rotation, start, end, ccw),
|
||||||
|
Canvas2dMsg::MeasureText(text, sender) => {
|
||||||
|
let metrics = self.canvas(canvas_id).measure_text(text);
|
||||||
|
sender.send(metrics).unwrap();
|
||||||
|
},
|
||||||
Canvas2dMsg::RestoreContext => self.canvas(canvas_id).restore_context_state(),
|
Canvas2dMsg::RestoreContext => self.canvas(canvas_id).restore_context_state(),
|
||||||
Canvas2dMsg::SaveContext => self.canvas(canvas_id).save_context_state(),
|
Canvas2dMsg::SaveContext => self.canvas(canvas_id).save_context_state(),
|
||||||
Canvas2dMsg::SetLineWidth(width) => self.canvas(canvas_id).set_line_width(width),
|
Canvas2dMsg::SetLineWidth(width) => self.canvas(canvas_id).set_line_width(width),
|
||||||
|
|
|
@ -49,6 +49,7 @@ pub const KERN: u32 = ot_tag!('k', 'e', 'r', 'n');
|
||||||
pub const SBIX: u32 = ot_tag!('s', 'b', 'i', 'x');
|
pub const SBIX: u32 = ot_tag!('s', 'b', 'i', 'x');
|
||||||
pub const CBDT: u32 = ot_tag!('C', 'B', 'D', 'T');
|
pub const CBDT: u32 = ot_tag!('C', 'B', 'D', 'T');
|
||||||
pub const COLR: u32 = ot_tag!('C', 'O', 'L', 'R');
|
pub const COLR: u32 = ot_tag!('C', 'O', 'L', 'R');
|
||||||
|
pub const BASE: u32 = ot_tag!('B', 'A', 'S', 'E');
|
||||||
|
|
||||||
pub const LAST_RESORT_GLYPH_ADVANCE: FractionalPixel = 10.0;
|
pub const LAST_RESORT_GLYPH_ADVANCE: FractionalPixel = 10.0;
|
||||||
|
|
||||||
|
@ -89,6 +90,7 @@ pub trait PlatformFontMethods: Sized {
|
||||||
fn can_do_fast_shaping(&self) -> bool;
|
fn can_do_fast_shaping(&self) -> bool;
|
||||||
fn metrics(&self) -> FontMetrics;
|
fn metrics(&self) -> FontMetrics;
|
||||||
fn table_for_tag(&self, _: FontTableTag) -> Option<FontTable>;
|
fn table_for_tag(&self, _: FontTableTag) -> Option<FontTable>;
|
||||||
|
fn typographic_bounds(&self, _: GlyphId) -> Rect<f32>;
|
||||||
|
|
||||||
/// Get the necessary [`FontInstanceFlags`]` for this font.
|
/// Get the necessary [`FontInstanceFlags`]` for this font.
|
||||||
fn webrender_font_instance_flags(&self) -> FontInstanceFlags;
|
fn webrender_font_instance_flags(&self) -> FontInstanceFlags;
|
||||||
|
@ -464,6 +466,16 @@ impl Font {
|
||||||
cache.glyph_advances.insert(glyph_id, new_width);
|
cache.glyph_advances.insert(glyph_id, new_width);
|
||||||
new_width
|
new_width
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn typographic_bounds(&self, glyph_id: GlyphId) -> Rect<f32> {
|
||||||
|
self.handle.typographic_bounds(glyph_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unsafe_code)]
|
||||||
|
pub fn get_baseline(&self) -> Option<FontBaseline> {
|
||||||
|
let this = self as *const Font;
|
||||||
|
unsafe { self.shaper.get_or_init(|| Shaper::new(this)).get_baseline() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type FontRef = Arc<Font>;
|
pub type FontRef = Arc<Font>;
|
||||||
|
@ -805,6 +817,12 @@ impl FontFamilyDescriptor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct FontBaseline {
|
||||||
|
pub ideographic_baseline: f32,
|
||||||
|
pub alphabetic_baseline: f32,
|
||||||
|
pub hanging_baseline: f32,
|
||||||
|
}
|
||||||
|
|
||||||
/// Given a mapping array `mapping` and a value, map that value onto
|
/// Given a mapping array `mapping` and a value, map that value onto
|
||||||
/// the value specified by the array. For instance, for FontConfig
|
/// the value specified by the array. For instance, for FontConfig
|
||||||
/// values of weights, we would map these onto the CSS [0..1000] range
|
/// values of weights, we would map these onto the CSS [0..1000] range
|
||||||
|
|
|
@ -7,13 +7,14 @@ use std::sync::Arc;
|
||||||
use std::{mem, ptr};
|
use std::{mem, ptr};
|
||||||
|
|
||||||
use app_units::Au;
|
use app_units::Au;
|
||||||
|
use euclid::default::{Point2D, Rect, Size2D};
|
||||||
use freetype_sys::{
|
use freetype_sys::{
|
||||||
ft_sfnt_head, ft_sfnt_os2, FT_Byte, FT_Done_Face, FT_Error, FT_F26Dot6, FT_Face, FT_Fixed,
|
ft_sfnt_head, ft_sfnt_os2, FT_Byte, FT_Done_Face, FT_Error, FT_F26Dot6, FT_Face, FT_Fixed,
|
||||||
FT_Get_Char_Index, FT_Get_Kerning, FT_Get_Sfnt_Table, FT_GlyphSlot, FT_Int32, FT_Load_Glyph,
|
FT_Get_Char_Index, FT_Get_Kerning, FT_Get_Sfnt_Table, FT_GlyphSlot, FT_Int32, FT_Load_Glyph,
|
||||||
FT_Long, FT_MulFix, FT_New_Memory_Face, FT_Pos, FT_Select_Size, FT_Set_Char_Size, FT_Short,
|
FT_Long, FT_MulFix, FT_New_Memory_Face, FT_Pos, FT_Select_Size, FT_Set_Char_Size, FT_Short,
|
||||||
FT_SizeRec, FT_Size_Metrics, FT_UInt, FT_ULong, FT_UShort, FT_Vector, FT_FACE_FLAG_COLOR,
|
FT_SizeRec, FT_Size_Metrics, FT_UInt, FT_ULong, FT_UShort, FT_Vector, FT_FACE_FLAG_COLOR,
|
||||||
FT_FACE_FLAG_FIXED_SIZES, FT_FACE_FLAG_SCALABLE, FT_KERNING_DEFAULT, FT_LOAD_COLOR,
|
FT_FACE_FLAG_FIXED_SIZES, FT_FACE_FLAG_SCALABLE, FT_KERNING_DEFAULT, FT_LOAD_COLOR,
|
||||||
FT_LOAD_DEFAULT, FT_STYLE_FLAG_ITALIC, TT_OS2,
|
FT_LOAD_DEFAULT, FT_LOAD_NO_HINTING, FT_STYLE_FLAG_ITALIC, TT_OS2,
|
||||||
};
|
};
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use parking_lot::ReentrantMutex;
|
use parking_lot::ReentrantMutex;
|
||||||
|
@ -388,6 +389,28 @@ impl PlatformFontMethods for PlatformFont {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn typographic_bounds(&self, glyph_id: GlyphId) -> Rect<f32> {
|
||||||
|
let face = self.face.lock();
|
||||||
|
assert!(!face.is_null());
|
||||||
|
|
||||||
|
let load_flags = FT_LOAD_DEFAULT | FT_LOAD_NO_HINTING;
|
||||||
|
let result = unsafe { FT_Load_Glyph(*face, glyph_id as FT_UInt, load_flags) };
|
||||||
|
if 0 != result {
|
||||||
|
debug!("Unable to load glyph {}. reason: {:?}", glyph_id, result);
|
||||||
|
return Rect::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
let metrics = unsafe { &(*(**face).glyph).metrics };
|
||||||
|
|
||||||
|
Rect::new(
|
||||||
|
Point2D::new(
|
||||||
|
metrics.horiBearingX as f32,
|
||||||
|
(metrics.horiBearingY - metrics.height) as f32,
|
||||||
|
),
|
||||||
|
Size2D::new(metrics.width as f32, metrics.height as f32),
|
||||||
|
) * (1. / 64.)
|
||||||
|
}
|
||||||
|
|
||||||
fn webrender_font_instance_flags(&self) -> FontInstanceFlags {
|
fn webrender_font_instance_flags(&self) -> FontInstanceFlags {
|
||||||
// On other platforms, we only pass this when we know that we are loading a font with
|
// On other platforms, we only pass this when we know that we are loading a font with
|
||||||
// color characters, but not passing this flag simply *prevents* WebRender from
|
// color characters, but not passing this flag simply *prevents* WebRender from
|
||||||
|
|
|
@ -17,6 +17,7 @@ use core_text::font::CTFont;
|
||||||
use core_text::font_descriptor::{
|
use core_text::font_descriptor::{
|
||||||
kCTFontDefaultOrientation, CTFontTraits, SymbolicTraitAccessors, TraitAccessors,
|
kCTFontDefaultOrientation, CTFontTraits, SymbolicTraitAccessors, TraitAccessors,
|
||||||
};
|
};
|
||||||
|
use euclid::default::{Point2D, Rect, Size2D};
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use style::values::computed::font::{FontStretch, FontStyle, FontWeight};
|
use style::values::computed::font::{FontStretch, FontStyle, FontWeight};
|
||||||
use webrender_api::FontInstanceFlags;
|
use webrender_api::FontInstanceFlags;
|
||||||
|
@ -325,6 +326,16 @@ impl PlatformFontMethods for PlatformFont {
|
||||||
}
|
}
|
||||||
FontInstanceFlags::empty()
|
FontInstanceFlags::empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn typographic_bounds(&self, glyph_id: GlyphId) -> Rect<f32> {
|
||||||
|
let rect = self
|
||||||
|
.ctfont
|
||||||
|
.get_bounding_rects_for_glyphs(kCTFontDefaultOrientation, &[glyph_id as u16]);
|
||||||
|
Rect::new(
|
||||||
|
Point2D::new(rect.origin.x as f32, rect.origin.y as f32),
|
||||||
|
Size2D::new(rect.size.width as f32, rect.size.height as f32),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) trait CoreTextFontTraitsMapping {
|
pub(super) trait CoreTextFontTraitsMapping {
|
||||||
|
|
|
@ -14,6 +14,7 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use app_units::Au;
|
use app_units::Au;
|
||||||
use dwrote::{FontFace, FontFile};
|
use dwrote::{FontFace, FontFile};
|
||||||
|
use euclid::default::{Point2D, Rect, Size2D};
|
||||||
use log::{debug, warn};
|
use log::{debug, warn};
|
||||||
use style::computed_values::font_stretch::T as StyleFontStretch;
|
use style::computed_values::font_stretch::T as StyleFontStretch;
|
||||||
use style::computed_values::font_weight::T as StyleFontWeight;
|
use style::computed_values::font_weight::T as StyleFontWeight;
|
||||||
|
@ -273,4 +274,26 @@ impl PlatformFontMethods for PlatformFont {
|
||||||
fn webrender_font_instance_flags(&self) -> FontInstanceFlags {
|
fn webrender_font_instance_flags(&self) -> FontInstanceFlags {
|
||||||
FontInstanceFlags::empty()
|
FontInstanceFlags::empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn typographic_bounds(&self, glyph_id: GlyphId) -> Rect<f32> {
|
||||||
|
let metrics = self
|
||||||
|
.face
|
||||||
|
.get_design_glyph_metrics(&[glyph_id as u16], false);
|
||||||
|
let metrics = &metrics[0];
|
||||||
|
let advance_width = metrics.advanceWidth as f32;
|
||||||
|
let advance_height = metrics.advanceHeight as f32;
|
||||||
|
let left_side_bearing = metrics.leftSideBearing as f32;
|
||||||
|
let right_side_bearing = metrics.rightSideBearing as f32;
|
||||||
|
let top_side_bearing = metrics.topSideBearing as f32;
|
||||||
|
let bottom_side_bearing = metrics.bottomSideBearing as f32;
|
||||||
|
let vertical_origin_y = metrics.verticalOriginY as f32;
|
||||||
|
let y_offset = vertical_origin_y + bottom_side_bearing - advance_height;
|
||||||
|
let width = advance_width - (left_side_bearing + right_side_bearing);
|
||||||
|
let height = advance_height - (top_side_bearing + bottom_side_bearing);
|
||||||
|
|
||||||
|
Rect::new(
|
||||||
|
Point2D::new(left_side_bearing, y_offset),
|
||||||
|
Size2D::new(width, height),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,20 +19,24 @@ use harfbuzz_sys::{
|
||||||
hb_face_create_for_tables, hb_face_destroy, hb_face_t, hb_feature_t, hb_font_create,
|
hb_face_create_for_tables, hb_face_destroy, hb_face_t, hb_feature_t, hb_font_create,
|
||||||
hb_font_destroy, hb_font_funcs_create, hb_font_funcs_set_glyph_h_advance_func,
|
hb_font_destroy, hb_font_funcs_create, hb_font_funcs_set_glyph_h_advance_func,
|
||||||
hb_font_funcs_set_nominal_glyph_func, hb_font_funcs_t, hb_font_set_funcs, hb_font_set_ppem,
|
hb_font_funcs_set_nominal_glyph_func, hb_font_funcs_t, hb_font_set_funcs, hb_font_set_ppem,
|
||||||
hb_font_set_scale, hb_font_t, hb_glyph_info_t, hb_glyph_position_t, hb_position_t, hb_shape,
|
hb_font_set_scale, hb_font_t, hb_glyph_info_t, hb_glyph_position_t, hb_ot_layout_get_baseline,
|
||||||
hb_tag_t, HB_DIRECTION_LTR, HB_DIRECTION_RTL, HB_MEMORY_MODE_READONLY,
|
hb_position_t, hb_shape, hb_tag_t, HB_DIRECTION_LTR, HB_DIRECTION_RTL, HB_MEMORY_MODE_READONLY,
|
||||||
|
HB_OT_LAYOUT_BASELINE_TAG_HANGING, HB_OT_LAYOUT_BASELINE_TAG_IDEO_EMBOX_BOTTOM_OR_LEFT,
|
||||||
|
HB_OT_LAYOUT_BASELINE_TAG_ROMAN,
|
||||||
};
|
};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
|
|
||||||
use crate::platform::font::FontTable;
|
use crate::platform::font::FontTable;
|
||||||
use crate::{
|
use crate::{
|
||||||
fixed_to_float, float_to_fixed, ot_tag, ByteIndex, Font, FontTableMethods, FontTableTag,
|
fixed_to_float, float_to_fixed, ot_tag, ByteIndex, Font, FontBaseline, FontTableMethods,
|
||||||
GlyphData, GlyphId, GlyphStore, ShapingFlags, ShapingOptions, KERN,
|
FontTableTag, GlyphData, GlyphId, GlyphStore, ShapingFlags, ShapingOptions, BASE, KERN,
|
||||||
};
|
};
|
||||||
|
|
||||||
const NO_GLYPH: i32 = -1;
|
const NO_GLYPH: i32 = -1;
|
||||||
const LIGA: u32 = ot_tag!('l', 'i', 'g', 'a');
|
const LIGA: u32 = ot_tag!('l', 'i', 'g', 'a');
|
||||||
|
const HB_OT_TAG_DEFAULT_SCRIPT: u32 = ot_tag!('D', 'F', 'L', 'T');
|
||||||
|
const HB_OT_TAG_DEFAULT_LANGUAGE: u32 = ot_tag!('d', 'f', 'l', 't');
|
||||||
|
|
||||||
pub struct ShapedGlyphData {
|
pub struct ShapedGlyphData {
|
||||||
count: usize,
|
count: usize,
|
||||||
|
@ -606,6 +610,49 @@ impl Shaper {
|
||||||
|
|
||||||
advance
|
advance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub unsafe fn get_baseline(&self) -> Option<FontBaseline> {
|
||||||
|
if (*self.font).table_for_tag(BASE).is_none() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut hanging_baseline = 0;
|
||||||
|
let mut alphabetic_baseline = 0;
|
||||||
|
let mut ideographic_baseline = 0;
|
||||||
|
|
||||||
|
hb_ot_layout_get_baseline(
|
||||||
|
self.hb_font,
|
||||||
|
HB_OT_LAYOUT_BASELINE_TAG_ROMAN,
|
||||||
|
HB_DIRECTION_LTR,
|
||||||
|
HB_OT_TAG_DEFAULT_SCRIPT,
|
||||||
|
HB_OT_TAG_DEFAULT_LANGUAGE,
|
||||||
|
&mut alphabetic_baseline as *mut _,
|
||||||
|
);
|
||||||
|
|
||||||
|
hb_ot_layout_get_baseline(
|
||||||
|
self.hb_font,
|
||||||
|
HB_OT_LAYOUT_BASELINE_TAG_HANGING,
|
||||||
|
HB_DIRECTION_LTR,
|
||||||
|
HB_OT_TAG_DEFAULT_SCRIPT,
|
||||||
|
HB_OT_TAG_DEFAULT_LANGUAGE,
|
||||||
|
&mut hanging_baseline as *mut _,
|
||||||
|
);
|
||||||
|
|
||||||
|
hb_ot_layout_get_baseline(
|
||||||
|
self.hb_font,
|
||||||
|
HB_OT_LAYOUT_BASELINE_TAG_IDEO_EMBOX_BOTTOM_OR_LEFT,
|
||||||
|
HB_DIRECTION_LTR,
|
||||||
|
HB_OT_TAG_DEFAULT_SCRIPT,
|
||||||
|
HB_OT_TAG_DEFAULT_LANGUAGE,
|
||||||
|
&mut ideographic_baseline as *mut _,
|
||||||
|
);
|
||||||
|
|
||||||
|
Some(FontBaseline {
|
||||||
|
ideographic_baseline: Shaper::fixed_to_float(ideographic_baseline) as f32,
|
||||||
|
alphabetic_baseline: Shaper::fixed_to_float(alphabetic_baseline) as f32,
|
||||||
|
hanging_baseline: Shaper::fixed_to_float(hanging_baseline) as f32,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Callbacks from Harfbuzz when font map and glyph advance lookup needed.
|
/// Callbacks from Harfbuzz when font map and glyph advance lookup needed.
|
||||||
|
|
|
@ -10,7 +10,7 @@ use std::sync::Arc;
|
||||||
use canvas_traits::canvas::{
|
use canvas_traits::canvas::{
|
||||||
Canvas2dMsg, CanvasId, CanvasMsg, CompositionOrBlending, Direction, FillOrStrokeStyle,
|
Canvas2dMsg, CanvasId, CanvasMsg, CompositionOrBlending, Direction, FillOrStrokeStyle,
|
||||||
FillRule, LineCapStyle, LineJoinStyle, LinearGradientStyle, RadialGradientStyle,
|
FillRule, LineCapStyle, LineJoinStyle, LinearGradientStyle, RadialGradientStyle,
|
||||||
RepetitionStyle, TextAlign, TextBaseline,
|
RepetitionStyle, TextAlign, TextBaseline, TextMetrics as CanvasTextMetrics,
|
||||||
};
|
};
|
||||||
use cssparser::color::clamp_unit_f32;
|
use cssparser::color::clamp_unit_f32;
|
||||||
use cssparser::{Parser, ParserInput};
|
use cssparser::{Parser, ParserInput};
|
||||||
|
@ -1030,11 +1030,34 @@ impl CanvasState {
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#textmetrics
|
// https://html.spec.whatwg.org/multipage/#textmetrics
|
||||||
pub fn measure_text(&self, global: &GlobalScope, _text: DOMString) -> DomRoot<TextMetrics> {
|
pub fn measure_text(
|
||||||
// FIXME: for now faking the implementation of MeasureText().
|
&self,
|
||||||
// See https://github.com/servo/servo/issues/5411#issuecomment-533776291
|
global: &GlobalScope,
|
||||||
|
canvas: Option<&HTMLCanvasElement>,
|
||||||
|
text: DOMString,
|
||||||
|
) -> DomRoot<TextMetrics> {
|
||||||
|
if self.state.borrow().font_style.is_none() {
|
||||||
|
self.set_font(canvas, CanvasContextState::DEFAULT_FONT_STYLE.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let (sender, receiver) = ipc::channel::<CanvasTextMetrics>().unwrap();
|
||||||
|
self.send_canvas_2d_msg(Canvas2dMsg::MeasureText(text.into(), sender));
|
||||||
|
let metrics = receiver.recv().unwrap();
|
||||||
|
|
||||||
TextMetrics::new(
|
TextMetrics::new(
|
||||||
global, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
|
global,
|
||||||
|
metrics.width.into(),
|
||||||
|
metrics.actual_boundingbox_left.into(),
|
||||||
|
metrics.actual_boundingbox_right.into(),
|
||||||
|
metrics.font_boundingbox_ascent.into(),
|
||||||
|
metrics.font_boundingbox_descent.into(),
|
||||||
|
metrics.actual_boundingbox_ascent.into(),
|
||||||
|
metrics.actual_boundingbox_descent.into(),
|
||||||
|
metrics.em_height_ascent.into(),
|
||||||
|
metrics.em_height_descent.into(),
|
||||||
|
metrics.hanging_baseline.into(),
|
||||||
|
metrics.alphabetic_baseline.into(),
|
||||||
|
metrics.ideographic_baseline.into(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -292,7 +292,8 @@ impl CanvasRenderingContext2DMethods for CanvasRenderingContext2D {
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#textmetrics
|
// https://html.spec.whatwg.org/multipage/#textmetrics
|
||||||
fn MeasureText(&self, text: DOMString) -> DomRoot<TextMetrics> {
|
fn MeasureText(&self, text: DOMString) -> DomRoot<TextMetrics> {
|
||||||
self.canvas_state.measure_text(&self.global(), text)
|
self.canvas_state
|
||||||
|
.measure_text(&self.global(), self.canvas.as_deref(), text)
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-font
|
// https://html.spec.whatwg.org/multipage/#dom-context-2d-font
|
||||||
|
|
|
@ -256,7 +256,8 @@ impl OffscreenCanvasRenderingContext2DMethods for OffscreenCanvasRenderingContex
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#textmetrics
|
// https://html.spec.whatwg.org/multipage/#textmetrics
|
||||||
fn MeasureText(&self, text: DOMString) -> DomRoot<TextMetrics> {
|
fn MeasureText(&self, text: DOMString) -> DomRoot<TextMetrics> {
|
||||||
self.canvas_state.measure_text(&self.global(), text)
|
self.canvas_state
|
||||||
|
.measure_text(&self.global(), self.htmlcanvas.as_deref(), text)
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-context-2d-font
|
// https://html.spec.whatwg.org/multipage/#dom-context-2d-font
|
||||||
|
|
|
@ -58,6 +58,7 @@ pub enum Canvas2dMsg {
|
||||||
IsPointInPath(f64, f64, FillRule, IpcSender<bool>),
|
IsPointInPath(f64, f64, FillRule, IpcSender<bool>),
|
||||||
LineTo(Point2D<f32>),
|
LineTo(Point2D<f32>),
|
||||||
MoveTo(Point2D<f32>),
|
MoveTo(Point2D<f32>),
|
||||||
|
MeasureText(String, IpcSender<TextMetrics>),
|
||||||
PutImageData(Rect<u64>, IpcBytesReceiver),
|
PutImageData(Rect<u64>, IpcBytesReceiver),
|
||||||
QuadraticCurveTo(Point2D<f32>, Point2D<f32>),
|
QuadraticCurveTo(Point2D<f32>, Point2D<f32>),
|
||||||
Rect(Rect<f32>),
|
Rect(Rect<f32>),
|
||||||
|
@ -474,3 +475,19 @@ impl FromStr for Direction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, Deserialize, MallocSizeOf, Serialize)]
|
||||||
|
pub struct TextMetrics {
|
||||||
|
pub width: f32,
|
||||||
|
pub actual_boundingbox_left: f32,
|
||||||
|
pub actual_boundingbox_right: f32,
|
||||||
|
pub actual_boundingbox_ascent: f32,
|
||||||
|
pub actual_boundingbox_descent: f32,
|
||||||
|
pub font_boundingbox_ascent: f32,
|
||||||
|
pub font_boundingbox_descent: f32,
|
||||||
|
pub em_height_ascent: f32,
|
||||||
|
pub em_height_descent: f32,
|
||||||
|
pub hanging_baseline: f32,
|
||||||
|
pub alphabetic_baseline: f32,
|
||||||
|
pub ideographic_baseline: f32,
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
[2d.text.drawing.style.measure.rtl.text.html]
|
|
||||||
[Measurement should follow canvas direction instead text direction]
|
|
||||||
expected: FAIL
|
|
|
@ -1,3 +0,0 @@
|
||||||
[2d.text.drawing.style.measure.textAlign.html]
|
|
||||||
[Measurement should be related to textAlignment]
|
|
||||||
expected: FAIL
|
|
|
@ -1,3 +0,0 @@
|
||||||
[2d.text.fontVariantCaps2.html]
|
|
||||||
[Testing small caps setting in fontVariant]
|
|
||||||
expected: FAIL
|
|
Loading…
Add table
Add a link
Reference in a new issue