diff --git a/Cargo.lock b/Cargo.lock index 3d87cd5c635..84ca83732fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1171,6 +1171,7 @@ dependencies = [ "base", "crossbeam-channel", "euclid", + "fonts_traits", "glow", "ipc-channel", "kurbo", @@ -7407,6 +7408,7 @@ dependencies = [ "timers", "tracing", "unicode-bidi", + "unicode-script", "unicode-segmentation", "url", "urlpattern", diff --git a/components/canvas/backend.rs b/components/canvas/backend.rs index f4f50f77337..b85f7955f6b 100644 --- a/components/canvas/backend.rs +++ b/components/canvas/backend.rs @@ -3,14 +3,14 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use canvas_traits::canvas::{ - CompositionOptions, FillOrStrokeStyle, FillRule, LineOptions, Path, ShadowOptions, + CompositionOptions, FillOrStrokeStyle, FillRule, LineOptions, Path, ShadowOptions, TextRun, }; use compositing_traits::SerializableImageData; use euclid::default::{Point2D, Rect, Size2D, Transform2D}; use pixels::Snapshot; use webrender_api::ImageDescriptor; -use crate::canvas_data::{Filter, TextRun}; +use crate::canvas_data::Filter; // This defines required methods for a DrawTarget. The prototypes are derived from the now-removed // Azure backend's methods. @@ -55,7 +55,6 @@ pub(crate) trait GenericDrawTarget { fn fill_text( &mut self, text_runs: Vec, - start: Point2D, style: FillOrStrokeStyle, composition_options: CompositionOptions, transform: Transform2D, diff --git a/components/canvas/canvas_data.rs b/components/canvas/canvas_data.rs index 221ff38c42a..c7536f330b5 100644 --- a/components/canvas/canvas_data.rs +++ b/components/canvas/canvas_data.rs @@ -2,22 +2,10 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use std::mem; -use std::sync::Arc; - -use app_units::Au; use canvas_traits::canvas::*; use compositing_traits::CrossProcessCompositorApi; -use euclid::default::{Box2D, Point2D, Rect, Size2D, Transform2D, Vector2D}; -use euclid::point2; -use fonts::{ - ByteIndex, FontBaseline, FontContext, FontGroup, FontMetrics, FontRef, GlyphInfo, GlyphStore, - LAST_RESORT_GLYPH_ADVANCE, ShapingFlags, ShapingOptions, -}; -use log::warn; +use euclid::default::{Point2D, Rect, Size2D, Transform2D}; use pixels::Snapshot; -use range::Range; -use unicode_script::Script; use webrender_api::ImageKey; use crate::backend::GenericDrawTarget; @@ -26,76 +14,6 @@ use crate::backend::GenericDrawTarget; // https://github.com/servo/webrender/blob/main/webrender/src/texture_cache.rs#L1475 const MIN_WR_IMAGE_SIZE: Size2D = Size2D::new(1, 1); -#[derive(Default)] -struct UnshapedTextRun<'a> { - font: Option, - script: Script, - string: &'a str, -} - -impl UnshapedTextRun<'_> { - fn script_and_font_compatible(&self, script: Script, other_font: &Option) -> bool { - if self.script != script { - return false; - } - - match (&self.font, other_font) { - (Some(font_a), Some(font_b)) => font_a.identifier() == font_b.identifier(), - (None, None) => true, - _ => false, - } - } - - fn into_shaped_text_run(self) -> Option { - let font = self.font?; - if self.string.is_empty() { - return None; - } - - let word_spacing = Au::from_f64_px( - font.glyph_index(' ') - .map(|glyph_id| font.glyph_h_advance(glyph_id)) - .unwrap_or(LAST_RESORT_GLYPH_ADVANCE), - ); - let options = ShapingOptions { - letter_spacing: None, - word_spacing, - script: self.script, - flags: ShapingFlags::empty(), - }; - let glyphs = font.shape_text(self.string, &options); - Some(TextRun { font, glyphs }) - } -} - -pub(crate) struct TextRun { - pub(crate) font: FontRef, - pub(crate) glyphs: Arc, -} - -impl TextRun { - fn bounding_box(&self) -> Rect { - 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() - } -} - #[derive(Clone, Copy)] pub(crate) enum Filter { Bilinear, @@ -106,14 +24,12 @@ pub(crate) struct CanvasData { drawtarget: DrawTarget, compositor_api: CrossProcessCompositorApi, image_key: ImageKey, - font_context: Arc, } impl CanvasData { pub(crate) fn new( size: Size2D, compositor_api: CrossProcessCompositorApi, - font_context: Arc, ) -> CanvasData { let size = size.max(MIN_WR_IMAGE_SIZE); let mut draw_target = DrawTarget::new(size.cast()); @@ -124,7 +40,6 @@ impl CanvasData { drawtarget: draw_target, compositor_api, image_key, - font_context, } } @@ -181,296 +96,26 @@ impl CanvasData { } } - #[allow(clippy::too_many_arguments)] - pub(crate) fn fill_text_with_size( - &mut self, - text: String, - x: f64, - y: f64, - max_width: Option, - is_rtl: bool, - size: f64, - style: FillOrStrokeStyle, - text_options: &TextOptions, - composition_options: CompositionOptions, - transform: Transform2D, - ) { - // > Step 2: Replace all ASCII whitespace in text with U+0020 SPACE characters. - let text = replace_ascii_whitespace(text); - - // > Step 3: Let font be the current font of target, as given by that object's font - // > attribute. - let Some(ref font_style) = text_options.font else { - return; - }; - - let font_group = self - .font_context - .font_group_with_size(font_style.clone(), Au::from_f64_px(size)); - let mut font_group = font_group.write(); - let Some(first_font) = font_group.first(&self.font_context) else { - warn!("Could not render canvas text, because there was no first font."); - return; - }; - - let runs = self.build_unshaped_text_runs(&text, &mut font_group); - // 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. - let shaped_runs: Vec<_> = runs - .into_iter() - .filter_map(UnshapedTextRun::into_shaped_text_run) - .collect(); - let total_advance = shaped_runs - .iter() - .map(|run| run.glyphs.total_advance()) - .sum::() - .to_f64_px(); - - // > Step 6: If maxWidth was provided and the hypothetical width of the inline box in the - // > hypothetical line box is greater than maxWidth CSS pixels, then change font to have a - // > more condensed font (if one is available or if a reasonably readable one can be - // > synthesized by applying a horizontal scale factor to the font) or a smaller font, and - // > return to the previous step. - // - // TODO: We only try decreasing the font size here. Eventually it would make sense to use - // other methods to try to decrease the size, such as finding a narrower font or decreasing - // spacing. - if let Some(max_width) = max_width { - let new_size = (max_width / total_advance * size).floor().max(5.); - if total_advance > max_width && new_size != size { - self.fill_text_with_size( - text, - x, - y, - Some(max_width), - is_rtl, - new_size, - style, - text_options, - composition_options, - transform, - ); - return; - } - } - - // > Step 7: Find the anchor point for the line of text. - let start = self.find_anchor_point_for_line_of_text( - x as f32, - y as f32, - &first_font.metrics, - total_advance as f32, - is_rtl, - text_options, - ); - - // > Step 8: Let result be an array constructed by iterating over each glyph in the inline box - // > from left to right (if any), adding to the array, for each glyph, the shape of the glyph - // > as it is in the inline box, positioned on a coordinate space using CSS pixels with its - // > origin is at the anchor point. - self.maybe_bound_shape_with_pattern( - style, - composition_options, - &Rect::from_size(Size2D::new(total_advance, size)), - transform, - |self_, style| { - self_.drawtarget.fill_text( - shaped_runs, - start, - style, - composition_options, - transform, - ); - }, - ); - } - - #[allow(clippy::too_many_arguments)] - /// pub(crate) fn fill_text( &mut self, - text: String, - x: f64, - y: f64, - max_width: Option, - is_rtl: bool, - style: FillOrStrokeStyle, - text_options: TextOptions, + text_bounds: Rect, + text_runs: Vec, + fill_or_stroke_style: FillOrStrokeStyle, _shadow_options: ShadowOptions, composition_options: CompositionOptions, transform: Transform2D, ) { - let Some(ref font_style) = text_options.font else { - return; - }; - - let size = font_style.font_size.computed_size(); - self.fill_text_with_size( - text, - x, - y, - max_width, - is_rtl, - size.px() as f64, - style, - &text_options, + self.maybe_bound_shape_with_pattern( + fill_or_stroke_style, composition_options, + &text_bounds, transform, - ); - } - - /// - /// - pub(crate) fn measure_text(&mut self, text: String, text_options: TextOptions) -> 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) = text_options.font 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::into_shaped_text_run) - .collect(); - let total_advance = shaped_runs - .iter() - .map(|run| run.glyphs.total_advance()) - .sum::() - .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.baseline() { - Some(baseline) => baseline, - None => FontBaseline { - hanging_baseline: ascent * HANGING_BASELINE_DEFAULT, - ideographic_baseline: -descent * IDEOGRAPHIC_BASELINE_DEFAULT, - alphabetic_baseline: 0., + |self_, style| { + self_ + .drawtarget + .fill_text(text_runs, style, composition_options, transform); }, - }; - - let anchor_x = match text_options.align { - TextAlign::End => total_advance, - TextAlign::Center => total_advance / 2., - TextAlign::Right => total_advance, - _ => 0., - }; - let anchor_y = match text_options.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> { - 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. - let script = Script::from(character); - let font = font_group.find_by_codepoint(&self.font_context, character, None, 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. - /// See . - fn find_anchor_point_for_line_of_text( - &self, - x: f32, - y: f32, - metrics: &FontMetrics, - width: f32, - is_rtl: bool, - text_options: &TextOptions, - ) -> Point2D { - let text_align = match text_options.align { - TextAlign::Start if is_rtl => TextAlign::Right, - TextAlign::Start => TextAlign::Left, - TextAlign::End if is_rtl => TextAlign::Left, - TextAlign::End => TextAlign::Right, - text_align => text_align, - }; - let anchor_x = match text_align { - TextAlign::Center => -width / 2., - TextAlign::Right => -width, - _ => 0., - }; - - let ascent = metrics.ascent.to_f32_px(); - let descent = metrics.descent.to_f32_px(); - let anchor_y = match text_options.baseline { - TextBaseline::Top => ascent, - TextBaseline::Hanging => ascent * HANGING_BASELINE_DEFAULT, - TextBaseline::Ideographic => -descent * IDEOGRAPHIC_BASELINE_DEFAULT, - TextBaseline::Middle => (ascent - descent) / 2., - TextBaseline::Alphabetic => 0., - TextBaseline::Bottom => -descent, - }; - - point2(x + anchor_x, y + anchor_y) + ); } pub(crate) fn fill_rect( @@ -770,9 +415,6 @@ impl Drop for CanvasData { } } -const HANGING_BASELINE_DEFAULT: f32 = 0.8; -const IDEOGRAPHIC_BASELINE_DEFAULT: f32 = 0.5; - /// It writes an image to the destination target /// draw_target: the destination target where the image_data will be copied /// image_data: Pixel information of the image to be written. It takes RGBA8 @@ -830,12 +472,3 @@ impl RectToi32 for Rect { ) } } - -fn replace_ascii_whitespace(text: String) -> String { - text.chars() - .map(|c| match c { - ' ' | '\t' | '\n' | '\r' | '\x0C' => '\x20', - _ => c, - }) - .collect() -} diff --git a/components/canvas/canvas_paint_thread.rs b/components/canvas/canvas_paint_thread.rs index 910f6d303d2..8b66e806b35 100644 --- a/components/canvas/canvas_paint_thread.rs +++ b/components/canvas/canvas_paint_thread.rs @@ -4,7 +4,6 @@ use std::borrow::ToOwned; use std::collections::HashMap; -use std::sync::Arc; use std::{f32, thread}; use canvas_traits::ConstellationCanvasMsg; @@ -12,11 +11,9 @@ use canvas_traits::canvas::*; use compositing_traits::CrossProcessCompositorApi; use crossbeam_channel::{Sender, select, unbounded}; use euclid::default::{Rect, Size2D, Transform2D}; -use fonts::{FontContext, SystemFontServiceProxy}; use ipc_channel::ipc::{self, IpcSender}; use ipc_channel::router::ROUTER; use log::warn; -use net_traits::ResourceThreads; use pixels::Snapshot; use webrender_api::ImageKey; @@ -26,24 +23,14 @@ pub struct CanvasPaintThread { canvases: HashMap, next_canvas_id: CanvasId, compositor_api: CrossProcessCompositorApi, - font_context: Arc, } impl CanvasPaintThread { - fn new( - compositor_api: CrossProcessCompositorApi, - system_font_service: Arc, - resource_threads: ResourceThreads, - ) -> CanvasPaintThread { + fn new(compositor_api: CrossProcessCompositorApi) -> CanvasPaintThread { CanvasPaintThread { canvases: HashMap::new(), next_canvas_id: CanvasId(0), compositor_api: compositor_api.clone(), - font_context: Arc::new(FontContext::new( - system_font_service, - compositor_api, - resource_threads, - )), } } @@ -51,8 +38,6 @@ impl CanvasPaintThread { /// communicate with it. pub fn start( compositor_api: CrossProcessCompositorApi, - system_font_service: Arc, - resource_threads: ResourceThreads, ) -> (Sender, IpcSender) { let (ipc_sender, ipc_receiver) = ipc::channel::().unwrap(); let msg_receiver = ROUTER.route_ipc_receiver_to_new_crossbeam_receiver(ipc_receiver); @@ -61,7 +46,7 @@ impl CanvasPaintThread { .name("Canvas".to_owned()) .spawn(move || { let mut canvas_paint_thread = CanvasPaintThread::new( - compositor_api, system_font_service, resource_threads); + compositor_api); loop { select! { recv(msg_receiver) -> msg => { @@ -108,7 +93,7 @@ impl CanvasPaintThread { let canvas_id = self.next_canvas_id; self.next_canvas_id.0 += 1; - let canvas = Canvas::new(size, self.compositor_api.clone(), self.font_context.clone())?; + let canvas = Canvas::new(size, self.compositor_api.clone())?; let image_key = canvas.image_key(); self.canvases.insert(canvas_id, canvas); @@ -122,25 +107,17 @@ impl CanvasPaintThread { fn process_canvas_2d_message(&mut self, message: Canvas2dMsg, canvas_id: CanvasId) { match message { Canvas2dMsg::FillText( - text, - x, - y, - max_width, - style, - is_rtl, - text_options, + text_bounds, + text_runs, + fill_or_stroke_style, shadow_options, composition_options, transform, ) => { self.canvas(canvas_id).fill_text( - text, - x, - y, - max_width, - is_rtl, - style, - text_options, + text_bounds, + text_runs, + fill_or_stroke_style, shadow_options, composition_options, transform, @@ -268,10 +245,6 @@ impl CanvasPaintThread { transform, ); }, - Canvas2dMsg::MeasureText(text, sender, text_options) => { - let metrics = self.canvas(canvas_id).measure_text(text, text_options); - sender.send(metrics).unwrap(); - }, Canvas2dMsg::GetImageData(dest_rect, sender) => { let snapshot = self.canvas(canvas_id).read_pixels(dest_rect); sender.send(snapshot.as_ipc()).unwrap(); @@ -302,27 +275,17 @@ enum Canvas { } impl Canvas { - fn new( - size: Size2D, - compositor_api: CrossProcessCompositorApi, - font_context: Arc, - ) -> Option { + fn new(size: Size2D, compositor_api: CrossProcessCompositorApi) -> Option { match servo_config::pref!(dom_canvas_backend) .to_lowercase() .as_str() { #[cfg(feature = "vello_cpu")] - "" | "auto" | "vello_cpu" => Some(Self::VelloCPU(CanvasData::new( - size, - compositor_api, - font_context, - ))), + "" | "auto" | "vello_cpu" => { + Some(Self::VelloCPU(CanvasData::new(size, compositor_api))) + }, #[cfg(feature = "vello")] - "" | "auto" | "vello" => Some(Self::Vello(CanvasData::new( - size, - compositor_api, - font_context, - ))), + "" | "auto" | "vello" => Some(Self::Vello(CanvasData::new(size, compositor_api))), s => { warn!("Unknown 2D canvas backend: `{s}`"); None @@ -348,16 +311,11 @@ impl Canvas { } } - #[allow(clippy::too_many_arguments)] fn fill_text( &mut self, - text: String, - x: f64, - y: f64, - max_width: Option, - is_rtl: bool, - style: FillOrStrokeStyle, - text_options: TextOptions, + text_bounds: Rect, + text_runs: Vec, + fill_or_stroke_style: FillOrStrokeStyle, shadow_options: ShadowOptions, composition_options: CompositionOptions, transform: Transform2D, @@ -365,26 +323,18 @@ impl Canvas { match self { #[cfg(feature = "vello")] Canvas::Vello(canvas_data) => canvas_data.fill_text( - text, - x, - y, - max_width, - is_rtl, - style, - text_options, + text_bounds, + text_runs, + fill_or_stroke_style, shadow_options, composition_options, transform, ), #[cfg(feature = "vello_cpu")] Canvas::VelloCPU(canvas_data) => canvas_data.fill_text( - text, - x, - y, - max_width, - is_rtl, - style, - text_options, + text_bounds, + text_runs, + fill_or_stroke_style, shadow_options, composition_options, transform, @@ -558,15 +508,6 @@ impl Canvas { } } - fn measure_text(&mut self, text: String, text_options: TextOptions) -> TextMetrics { - match self { - #[cfg(feature = "vello")] - Canvas::Vello(canvas_data) => canvas_data.measure_text(text, text_options), - #[cfg(feature = "vello_cpu")] - Canvas::VelloCPU(canvas_data) => canvas_data.measure_text(text, text_options), - } - } - fn clip_path(&mut self, path: &Path, fill_rule: FillRule, transform: Transform2D) { match self { #[cfg(feature = "vello")] diff --git a/components/canvas/vello_backend.rs b/components/canvas/vello_backend.rs index 6a603c3e3dd..ee63b12b28d 100644 --- a/components/canvas/vello_backend.rs +++ b/components/canvas/vello_backend.rs @@ -18,15 +18,14 @@ use std::rc::Rc; use canvas_traits::canvas::{ CompositionOptions, CompositionOrBlending, CompositionStyle, FillOrStrokeStyle, FillRule, - LineOptions, Path, ShadowOptions, + LineOptions, Path, ShadowOptions, TextRun, }; use compositing_traits::SerializableImageData; use euclid::default::{Point2D, Rect, Size2D, Transform2D}; -use fonts::{ByteIndex, FontIdentifier, FontTemplateRefMethods as _}; +use fonts::FontIdentifier; use ipc_channel::ipc::IpcSharedMemory; use kurbo::Shape as _; use pixels::{Snapshot, SnapshotAlphaMode, SnapshotPixelFormat}; -use range::Range; use vello::wgpu::{ BackendOptions, Backends, Buffer, BufferDescriptor, BufferUsages, COPY_BYTES_PER_ROW_ALIGNMENT, CommandEncoderDescriptor, Device, Extent3d, Instance, InstanceDescriptor, InstanceFlags, @@ -38,7 +37,7 @@ use vello::{kurbo, peniko}; use webrender_api::{ImageDescriptor, ImageDescriptorFlags}; use crate::backend::{Convert as _, GenericDrawTarget}; -use crate::canvas_data::{Filter, TextRun}; +use crate::canvas_data::Filter; thread_local! { /// The shared font cache used by all canvases that render on a thread. @@ -381,7 +380,6 @@ impl GenericDrawTarget for VelloDrawTarget { fn fill_text( &mut self, text_runs: Vec, - start: Point2D, style: FillOrStrokeStyle, composition_options: CompositionOptions, transform: Transform2D, @@ -390,24 +388,19 @@ impl GenericDrawTarget for VelloDrawTarget { let pattern = convert_to_brush(style, composition_options); let transform = transform.cast().into(); self.with_composition(composition_options.composition_operation, |self_| { - let mut advance = 0.; - for run in text_runs.iter() { - let glyphs = &run.glyphs; - - let template = &run.font.template; - + for text_run in text_runs.iter() { SHARED_FONT_CACHE.with(|font_cache| { - let identifier = template.identifier(); - if !font_cache.borrow().contains_key(&identifier) { - let Ok(font) = run.font.font_data_and_index() else { + let identifier = &text_run.font.identifier; + if !font_cache.borrow().contains_key(identifier) { + let Some(font_data_and_index) = text_run.font.font_data_and_index() else { return; }; - let font = font.clone().convert(); + let font = font_data_and_index.convert(); font_cache.borrow_mut().insert(identifier.clone(), font); } let font_cache = font_cache.borrow(); - let Some(font) = font_cache.get(&identifier) else { + let Some(font) = font_cache.get(identifier) else { return; }; @@ -416,21 +409,16 @@ impl GenericDrawTarget for VelloDrawTarget { .draw_glyphs(font) .transform(transform) .brush(&pattern) - .font_size(run.font.descriptor.pt_size.to_f32_px()) + .font_size(text_run.pt_size) .draw( peniko::Fill::NonZero, - glyphs - .iter_glyphs_for_byte_range(&Range::new(ByteIndex(0), glyphs.len())) - .map(|glyph| { - let glyph_offset = glyph.offset().unwrap_or(Point2D::zero()); - let x = advance + start.x + glyph_offset.x.to_f32_px(); - let y = start.y + glyph_offset.y.to_f32_px(); - advance += glyph.advance().to_f32_px(); - vello::Glyph { - id: glyph.id(), - x, - y, - } + text_run + .glyphs_and_positions + .iter() + .map(|glyph_and_position| vello::Glyph { + id: glyph_and_position.id, + x: glyph_and_position.point.x, + y: glyph_and_position.point.y, }), ); }); diff --git a/components/canvas/vello_cpu_backend.rs b/components/canvas/vello_cpu_backend.rs index 138befb7070..68ea5568815 100644 --- a/components/canvas/vello_cpu_backend.rs +++ b/components/canvas/vello_cpu_backend.rs @@ -8,20 +8,19 @@ use std::sync::Arc; use canvas_traits::canvas::{ CompositionOptions, CompositionOrBlending, CompositionStyle, FillOrStrokeStyle, FillRule, - LineOptions, Path, ShadowOptions, + LineOptions, Path, ShadowOptions, TextRun, }; use compositing_traits::SerializableImageData; use euclid::default::{Point2D, Rect, Size2D, Transform2D}; -use fonts::{ByteIndex, FontIdentifier, FontTemplateRefMethods as _}; +use fonts::FontIdentifier; use ipc_channel::ipc::IpcSharedMemory; use kurbo::Shape; use pixels::{Snapshot, SnapshotAlphaMode, SnapshotPixelFormat}; -use range::Range; use vello_cpu::{kurbo, peniko}; use webrender_api::{ImageDescriptor, ImageDescriptorFlags}; use crate::backend::{Convert, GenericDrawTarget}; -use crate::canvas_data::{Filter, TextRun}; +use crate::canvas_data::Filter; thread_local! { /// The shared font cache used by all canvases that render on a thread. @@ -282,7 +281,6 @@ impl GenericDrawTarget for VelloCPUDrawTarget { fn fill_text( &mut self, text_runs: Vec, - start: Point2D, style: FillOrStrokeStyle, composition_options: CompositionOptions, transform: Transform2D, @@ -291,46 +289,32 @@ impl GenericDrawTarget for VelloCPUDrawTarget { self.ctx.set_paint(paint(style, composition_options.alpha)); self.ctx.set_transform(transform.cast().into()); self.with_composition(composition_options.composition_operation, |self_| { - let mut advance = 0.; - for run in text_runs.iter() { - let glyphs = &run.glyphs; - - let template = &run.font.template; - + for text_run in text_runs.iter() { SHARED_FONT_CACHE.with(|font_cache| { - let identifier = template.identifier(); - if !font_cache.borrow().contains_key(&identifier) { - let Ok(font) = run.font.font_data_and_index() else { + let identifier = &text_run.font.identifier; + if !font_cache.borrow().contains_key(identifier) { + let Some(font_data_and_index) = text_run.font.font_data_and_index() else { return; }; - let font = font.clone().convert(); + let font = font_data_and_index.convert(); font_cache.borrow_mut().insert(identifier.clone(), font); } let font_cache = font_cache.borrow(); - let Some(font) = font_cache.get(&identifier) else { + let Some(font) = font_cache.get(identifier) else { return; }; - self_ .ctx .glyph_run(font) - .font_size(run.font.descriptor.pt_size.to_f32_px()) - .fill_glyphs( - glyphs - .iter_glyphs_for_byte_range(&Range::new(ByteIndex(0), glyphs.len())) - .map(|glyph| { - let glyph_offset = glyph.offset().unwrap_or(Point2D::zero()); - let x = advance + start.x + glyph_offset.x.to_f32_px(); - let y = start.y + glyph_offset.y.to_f32_px(); - advance += glyph.advance().to_f32_px(); - vello_cpu::Glyph { - id: glyph.id(), - x, - y, - } - }), - ); + .font_size(text_run.pt_size) + .fill_glyphs(text_run.glyphs_and_positions.iter().map( + |glyph_and_position| vello_cpu::Glyph { + id: glyph_and_position.id, + x: glyph_and_position.point.x, + y: glyph_and_position.point.y, + }, + )); }); } }) diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index dba9902fd1b..b5b4c7b47dd 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -5617,10 +5617,6 @@ where } fn create_canvas_paint_thread(&self) -> (Sender, IpcSender) { - CanvasPaintThread::start( - self.compositor_proxy.cross_process_compositor_api.clone(), - self.system_font_service.clone(), - self.public_resource_threads.clone(), - ) + CanvasPaintThread::start(self.compositor_proxy.cross_process_compositor_api.clone()) } } diff --git a/components/script/Cargo.toml b/components/script/Cargo.toml index 51e81f6c085..cd440866cd1 100644 --- a/components/script/Cargo.toml +++ b/components/script/Cargo.toml @@ -129,6 +129,7 @@ time = { workspace = true } timers = { path = "../timers" } tracing = { workspace = true, optional = true } unicode-bidi = { workspace = true } +unicode-script = { workspace = true } unicode-segmentation = { workspace = true } url = { workspace = true } urlpattern = { workspace = true } diff --git a/components/script/canvas_state.rs b/components/script/canvas_state.rs index 6bb83cfc863..04a6a6b1431 100644 --- a/components/script/canvas_state.rs +++ b/components/script/canvas_state.rs @@ -7,22 +7,28 @@ use std::fmt; use std::str::FromStr; use std::sync::Arc; +use app_units::Au; use canvas_traits::canvas::{ - Canvas2dMsg, CanvasId, CanvasMsg, CompositionOptions, CompositionOrBlending, Direction, - FillOrStrokeStyle, FillRule, LineCapStyle, LineJoinStyle, LineOptions, LinearGradientStyle, - Path, RadialGradientStyle, RepetitionStyle, ShadowOptions, TextAlign, TextBaseline, - TextMetrics as CanvasTextMetrics, TextOptions, + Canvas2dMsg, CanvasFont, CanvasId, CanvasMsg, CompositionOptions, CompositionOrBlending, + FillOrStrokeStyle, FillRule, GlyphAndPosition, LineCapStyle, LineJoinStyle, LineOptions, + LinearGradientStyle, Path, RadialGradientStyle, RepetitionStyle, ShadowOptions, TextRun, }; use constellation_traits::ScriptToConstellationMessage; use cssparser::color::clamp_unit_f32; use cssparser::{Parser, ParserInput}; use euclid::default::{Point2D, Rect, Size2D, Transform2D}; -use euclid::vec2; +use euclid::{Vector2D, vec2}; +use fonts::{ + ByteIndex, FontBaseline, FontContext, FontGroup, FontIdentifier, FontMetrics, FontRef, + LAST_RESORT_GLYPH_ADVANCE, ShapingFlags, ShapingOptions, +}; use ipc_channel::ipc::{self, IpcSender}; use net_traits::image_cache::{ImageCache, ImageResponse}; use net_traits::request::CorsSettings; use pixels::{PixelFormat, Snapshot, SnapshotAlphaMode, SnapshotPixelFormat}; use profile_traits::ipc as profiled_ipc; +use range::Range; +use servo_arc::Arc as ServoArc; use servo_url::{ImmutableOrigin, ServoUrl}; use style::color::{AbsoluteColor, ColorFlags, ColorSpace}; use style::context::QuirksMode; @@ -34,6 +40,7 @@ use style::values::computed::font::FontStyle; use style::values::specified::color::Color; use style_traits::values::ToCss; use style_traits::{CssWriter, ParsingMode}; +use unicode_script::Script; use url::Url; use webrender_api::ImageKey; @@ -68,6 +75,9 @@ use crate::dom::paintworkletglobalscope::PaintWorkletGlobalScope; use crate::dom::textmetrics::TextMetrics; use crate::script_runtime::CanGc; +const HANGING_BASELINE_DEFAULT: f64 = 0.8; +const IDEOGRAPHIC_BASELINE_DEFAULT: f64 = 0.5; + #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] #[derive(Clone, JSTraceable, MallocSizeOf)] #[allow(dead_code)] @@ -112,13 +122,11 @@ pub(crate) struct CanvasContextState { #[no_trace] shadow_color: AbsoluteColor, #[no_trace] - font_style: Option, - #[no_trace] - text_align: TextAlign, - #[no_trace] - text_baseline: TextBaseline, - #[no_trace] - direction: Direction, + #[conditional_malloc_size_of] + font_style: Option>, + text_align: CanvasTextAlign, + text_baseline: CanvasTextBaseline, + direction: CanvasDirection, /// The number of clips pushed onto the context while in this state. /// When restoring old state, same number of clips will be popped to restore state. clips_pushed: usize, @@ -144,26 +152,15 @@ impl CanvasContextState { shadow_blur: 0.0, shadow_color: AbsoluteColor::TRANSPARENT_BLACK, font_style: None, - text_align: Default::default(), - text_baseline: Default::default(), - direction: Default::default(), + text_align: CanvasTextAlign::Start, + text_baseline: CanvasTextBaseline::Alphabetic, + direction: CanvasDirection::Inherit, line_dash: Vec::new(), line_dash_offset: 0.0, clips_pushed: 0, } } - fn text_options(&self) -> TextOptions { - TextOptions { - font: self - .font_style - .as_ref() - .map(|font| servo_arc::Arc::new(font.clone())), - align: self.text_align, - baseline: self.text_baseline, - } - } - fn composition_options(&self) -> CompositionOptions { CompositionOptions { alpha: self.global_alpha, @@ -1378,6 +1375,7 @@ impl CanvasState { // https://html.spec.whatwg.org/multipage/#dom-context-2d-filltext pub(crate) fn fill_text( &self, + global_scope: &GlobalScope, canvas: Option<&HTMLCanvasElement>, text: DOMString, x: f64, @@ -1390,32 +1388,26 @@ impl CanvasState { if max_width.is_some_and(|max_width| !max_width.is_finite() || max_width <= 0.) { return; } + if self.state.borrow().font_style.is_none() { self.set_font(canvas, CanvasContextState::DEFAULT_FONT_STYLE.into()) } + // This may be `None` if if this is offscreen canvas, in which case just use + // the initial values for the text style. + let size = self.font_style().font_size.computed_size().px() as f64; - let is_rtl = match self.state.borrow().direction { - Direction::Ltr => false, - Direction::Rtl => true, - Direction::Inherit => false, // TODO: resolve direction wrt to canvas element - }; - - let style = self.state.borrow().fill_style.to_fill_or_stroke_style(); - self.send_canvas_2d_msg(Canvas2dMsg::FillText( - text.into(), - x, - y, + self.fill_text_with_size( + global_scope, + text.str(), + Point2D::new(x, y), + size, max_width, - style, - is_rtl, - self.state.borrow().text_options(), - self.state.borrow().shadow_options(), - self.state.borrow().composition_options(), - self.state.borrow().transform, - )); + ); } - // https://html.spec.whatwg.org/multipage/#textmetrics + /// + /// + /// pub(crate) fn measure_text( &self, global: &GlobalScope, @@ -1423,40 +1415,86 @@ impl CanvasState { text: DOMString, can_gc: CanGc, ) -> DomRoot { + // > Step 1: If maxWidth was provided but is less than or equal to zero or equal to NaN, then return an empty array.0 + // Max width is not provided for `measureText()`. + + // > Step 2: Replace all ASCII whitespace in text with U+0020 SPACE characters. + let text = replace_ascii_whitespace(text.str()); + + // > Step 3: Let font be the current font of target, as given by that object's font + // > attribute. if self.state.borrow().font_style.is_none() { self.set_font(canvas, CanvasContextState::DEFAULT_FONT_STYLE.into()); } - let metrics = { - if !self.is_paintable() { - CanvasTextMetrics::default() - } else { - let (sender, receiver) = ipc::channel::().unwrap(); - self.send_canvas_2d_msg(Canvas2dMsg::MeasureText( - text.into(), - sender, - self.state.borrow().text_options(), - )); - receiver - .recv() - .expect("Failed to receive response from canvas paint thread") - } + let Some(font_context) = global.font_context() else { + warn!("Tried to paint to a canvas of GlobalScope without a FontContext."); + return TextMetrics::default(global, can_gc); + }; + + let font_style = self.font_style(); + let font_group = font_context.font_group(font_style.clone()); + let mut font_group = font_group.write(); + let font = font_group.first(font_context).expect("couldn't find font"); + let ascent = font.metrics.ascent.to_f64_px(); + let descent = font.metrics.descent.to_f64_px(); + let runs = self.build_unshaped_text_runs(font_context, &text, &mut font_group); + + let mut total_advance = 0.0; + let shaped_runs: Vec<_> = runs + .into_iter() + .filter_map(|unshaped_text_run| { + let text_run = unshaped_text_run.into_shaped_text_run(total_advance)?; + total_advance += text_run.advance; + Some(text_run) + }) + .collect(); + + let bounding_box = shaped_runs + .iter() + .map(|text_run| text_run.bounds) + .reduce(|a, b| a.union(&b)) + .unwrap_or_default(); + + let baseline = font.baseline().unwrap_or_else(|| FontBaseline { + hanging_baseline: (ascent * HANGING_BASELINE_DEFAULT) as f32, + ideographic_baseline: (-descent * IDEOGRAPHIC_BASELINE_DEFAULT) as f32, + alphabetic_baseline: 0., + }); + let ideographic_baseline = baseline.ideographic_baseline as f64; + let alphabetic_baseline = baseline.alphabetic_baseline as f64; + let hanging_baseline = baseline.hanging_baseline as f64; + + let state = self.state.borrow(); + let anchor_x = match state.text_align { + CanvasTextAlign::End => total_advance, + CanvasTextAlign::Center => total_advance / 2., + CanvasTextAlign::Right => total_advance, + _ => 0., + } as f64; + let anchor_y = match state.text_baseline { + CanvasTextBaseline::Top => ascent, + CanvasTextBaseline::Hanging => hanging_baseline, + CanvasTextBaseline::Ideographic => ideographic_baseline, + CanvasTextBaseline::Middle => (ascent - descent) / 2., + CanvasTextBaseline::Alphabetic => alphabetic_baseline, + CanvasTextBaseline::Bottom => -descent, }; TextMetrics::new( 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(), + total_advance as f64, + anchor_x - bounding_box.min_x(), + bounding_box.max_x() - anchor_x, + bounding_box.max_y() - anchor_y, + anchor_y - bounding_box.min_y(), + ascent - anchor_y, + descent + anchor_y, + ascent - anchor_y, + descent + anchor_y, + hanging_baseline - anchor_y, + alphabetic_baseline - anchor_y, + ideographic_baseline - anchor_y, can_gc, ) } @@ -1469,11 +1507,21 @@ impl CanvasState { }; let node = canvas.upcast::(); let window = canvas.owner_window(); - let resolved_font_style = match window.resolved_font_style_query(node, value.to_string()) { - Some(value) => value, - None => return, // syntax error + + let Some(resolved_font_style) = window.resolved_font_style_query(node, value.to_string()) + else { + // This will happen when there is a syntax error. + return; }; - self.state.borrow_mut().font_style = Some((*resolved_font_style).clone()); + self.state.borrow_mut().font_style = Some(resolved_font_style); + } + + fn font_style(&self) -> ServoArc { + self.state + .borrow() + .font_style + .clone() + .unwrap_or_else(|| ServoArc::new(Font::initial_values())) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-font @@ -1490,67 +1538,30 @@ impl CanvasState { // https://html.spec.whatwg.org/multipage/#dom-context-2d-textalign pub(crate) fn text_align(&self) -> CanvasTextAlign { - match self.state.borrow().text_align { - TextAlign::Start => CanvasTextAlign::Start, - TextAlign::End => CanvasTextAlign::End, - TextAlign::Left => CanvasTextAlign::Left, - TextAlign::Right => CanvasTextAlign::Right, - TextAlign::Center => CanvasTextAlign::Center, - } + self.state.borrow().text_align } // https://html.spec.whatwg.org/multipage/#dom-context-2d-textalign pub(crate) fn set_text_align(&self, value: CanvasTextAlign) { - let text_align = match value { - CanvasTextAlign::Start => TextAlign::Start, - CanvasTextAlign::End => TextAlign::End, - CanvasTextAlign::Left => TextAlign::Left, - CanvasTextAlign::Right => TextAlign::Right, - CanvasTextAlign::Center => TextAlign::Center, - }; - self.state.borrow_mut().text_align = text_align; + self.state.borrow_mut().text_align = value; } pub(crate) fn text_baseline(&self) -> CanvasTextBaseline { - match self.state.borrow().text_baseline { - TextBaseline::Top => CanvasTextBaseline::Top, - TextBaseline::Hanging => CanvasTextBaseline::Hanging, - TextBaseline::Middle => CanvasTextBaseline::Middle, - TextBaseline::Alphabetic => CanvasTextBaseline::Alphabetic, - TextBaseline::Ideographic => CanvasTextBaseline::Ideographic, - TextBaseline::Bottom => CanvasTextBaseline::Bottom, - } + self.state.borrow().text_baseline } pub(crate) fn set_text_baseline(&self, value: CanvasTextBaseline) { - let text_baseline = match value { - CanvasTextBaseline::Top => TextBaseline::Top, - CanvasTextBaseline::Hanging => TextBaseline::Hanging, - CanvasTextBaseline::Middle => TextBaseline::Middle, - CanvasTextBaseline::Alphabetic => TextBaseline::Alphabetic, - CanvasTextBaseline::Ideographic => TextBaseline::Ideographic, - CanvasTextBaseline::Bottom => TextBaseline::Bottom, - }; - self.state.borrow_mut().text_baseline = text_baseline; + self.state.borrow_mut().text_baseline = value; } // https://html.spec.whatwg.org/multipage/#dom-context-2d-direction pub(crate) fn direction(&self) -> CanvasDirection { - match self.state.borrow().direction { - Direction::Ltr => CanvasDirection::Ltr, - Direction::Rtl => CanvasDirection::Rtl, - Direction::Inherit => CanvasDirection::Inherit, - } + self.state.borrow().direction } // https://html.spec.whatwg.org/multipage/#dom-context-2d-direction pub(crate) fn set_direction(&self, value: CanvasDirection) { - let direction = match value { - CanvasDirection::Ltr => Direction::Ltr, - CanvasDirection::Rtl => Direction::Rtl, - CanvasDirection::Inherit => Direction::Inherit, - }; - self.state.borrow_mut().direction = direction; + self.state.borrow_mut().direction = value; } // https://html.spec.whatwg.org/multipage/#dom-context-2d-linewidth @@ -2177,6 +2188,175 @@ impl CanvasState { .ellipse(x, y, rx, ry, rotation, start, end, ccw) .map_err(|_| Error::IndexSize) } + + fn fill_text_with_size( + &self, + global_scope: &GlobalScope, + text: &str, + origin: Point2D, + size: f64, + max_width: Option, + ) { + let Some(font_context) = global_scope.font_context() else { + warn!("Tried to paint to a canvas of GlobalScope without a FontContext."); + return; + }; + + // > Step 2: Replace all ASCII whitespace in text with U+0020 SPACE characters. + let text = replace_ascii_whitespace(text); + + // > Step 3: Let font be the current font of target, as given by that object's font + // > attribute. + let font_style = self.font_style(); + let font_group = font_context.font_group_with_size(font_style, Au::from_f64_px(size)); + let mut font_group = font_group.write(); + let Some(first_font) = font_group.first(font_context) else { + warn!("Could not render canvas text, because there was no first font."); + return; + }; + + let runs = self.build_unshaped_text_runs(font_context, &text, &mut font_group); + + // 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. + let mut total_advance = 0.0; + let mut shaped_runs: Vec<_> = runs + .into_iter() + .filter_map(|unshaped_text_run| { + let text_run = unshaped_text_run.into_shaped_text_run(total_advance)?; + total_advance += text_run.advance; + Some(text_run) + }) + .collect(); + + // > Step 6: If maxWidth was provided and the hypothetical width of the inline box in the + // > hypothetical line box is greater than maxWidth CSS pixels, then change font to have a + // > more condensed font (if one is available or if a reasonably readable one can be + // > synthesized by applying a horizontal scale factor to the font) or a smaller font, and + // > return to the previous step. + // + // TODO: We only try decreasing the font size here. Eventually it would make sense to use + // other methods to try to decrease the size, such as finding a narrower font or decreasing + // spacing. + let total_advance = total_advance as f64; + if let Some(max_width) = max_width { + let new_size = (max_width / total_advance * size).floor().max(5.); + if total_advance > max_width && new_size != size { + self.fill_text_with_size(global_scope, &text, origin, new_size, Some(max_width)); + return; + } + } + + // > Step 7: Find the anchor point for the line of text. + let start = + self.find_anchor_point_for_line_of_text(origin, &first_font.metrics, total_advance); + + // > Step 8: Let result be an array constructed by iterating over each glyph in the inline box + // > from left to right (if any), adding to the array, for each glyph, the shape of the glyph + // > as it is in the inline box, positioned on a coordinate space using CSS pixels with its + // > origin is at the anchor point. + let mut bounds = None; + for text_run in shaped_runs.iter_mut() { + for glyph_and_position in text_run.glyphs_and_positions.iter_mut() { + glyph_and_position.point += Vector2D::new(start.x as f32, start.y as f32); + } + bounds + .get_or_insert(text_run.bounds) + .union(&text_run.bounds); + } + + println!("bounds: {bounds:?}"); + + self.send_canvas_2d_msg(Canvas2dMsg::FillText( + bounds + .unwrap_or_default() + .translate(start.to_vector().cast_unit()), + shaped_runs, + self.state.borrow().fill_style.to_fill_or_stroke_style(), + self.state.borrow().shadow_options(), + self.state.borrow().composition_options(), + self.state.borrow().transform, + )); + } + + fn build_unshaped_text_runs<'text>( + &self, + font_context: &FontContext, + text: &'text str, + font_group: &mut FontGroup, + ) -> Vec> { + 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(font_context, character, None, None); + + if !current_text_run.script_and_font_compatible(script, &font) { + let previous_text_run = std::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. + /// See . + fn find_anchor_point_for_line_of_text( + &self, + origin: Point2D, + metrics: &FontMetrics, + width: f64, + ) -> Point2D { + let state = self.state.borrow(); + let is_rtl = match state.direction { + CanvasDirection::Ltr => false, + CanvasDirection::Rtl => true, + CanvasDirection::Inherit => false, // TODO: resolve direction wrt to canvas element + }; + + let text_align = match self.text_align() { + CanvasTextAlign::Start if is_rtl => CanvasTextAlign::Right, + CanvasTextAlign::Start => CanvasTextAlign::Left, + CanvasTextAlign::End if is_rtl => CanvasTextAlign::Left, + CanvasTextAlign::End => CanvasTextAlign::Right, + text_align => text_align, + }; + let anchor_x = match text_align { + CanvasTextAlign::Center => -width / 2., + CanvasTextAlign::Right => -width, + _ => 0., + }; + + let ascent = metrics.ascent.to_f64_px(); + let descent = metrics.descent.to_f64_px(); + let anchor_y = match self.text_baseline() { + CanvasTextBaseline::Top => ascent, + CanvasTextBaseline::Hanging => ascent * HANGING_BASELINE_DEFAULT, + CanvasTextBaseline::Ideographic => -descent * IDEOGRAPHIC_BASELINE_DEFAULT, + CanvasTextBaseline::Middle => (ascent - descent) / 2., + CanvasTextBaseline::Alphabetic => 0., + CanvasTextBaseline::Bottom => -descent, + }; + + origin + Vector2D::new(anchor_x, anchor_y) + } } impl Drop for CanvasState { @@ -2187,6 +2367,89 @@ impl Drop for CanvasState { } } +#[derive(Default)] +struct UnshapedTextRun<'a> { + font: Option, + script: Script, + string: &'a str, +} + +impl UnshapedTextRun<'_> { + fn script_and_font_compatible(&self, script: Script, other_font: &Option) -> bool { + if self.script != script { + return false; + } + + match (&self.font, other_font) { + (Some(font_a), Some(font_b)) => font_a.identifier() == font_b.identifier(), + (None, None) => true, + _ => false, + } + } + + fn into_shaped_text_run(self, previous_advance: f32) -> Option { + let font = self.font?; + if self.string.is_empty() { + return None; + } + + let word_spacing = Au::from_f64_px( + font.glyph_index(' ') + .map(|glyph_id| font.glyph_h_advance(glyph_id)) + .unwrap_or(LAST_RESORT_GLYPH_ADVANCE), + ); + let options = ShapingOptions { + letter_spacing: None, + word_spacing, + script: self.script, + flags: ShapingFlags::empty(), + }; + + let glyphs = font.shape_text(self.string, &options); + + let mut advance = 0.0; + let mut bounds = None; + let glyphs_and_positions = glyphs + .iter_glyphs_for_byte_range(&Range::new(ByteIndex(0), glyphs.len())) + .map(|glyph| { + let glyph_offset = glyph.offset().unwrap_or(Point2D::zero()); + let glyph_and_position = GlyphAndPosition { + id: glyph.id(), + point: Point2D::new(previous_advance + advance, glyph_offset.y.to_f32_px()), + }; + + let glyph_bounds = font + .typographic_bounds(glyph.id()) + .translate(Vector2D::new(advance + previous_advance, 0.0)); + bounds = Some(bounds.get_or_insert(glyph_bounds).union(&glyph_bounds)); + + advance += glyph.advance().to_f32_px(); + + glyph_and_position + }) + .collect(); + + let identifier = font.identifier(); + let font_data = match &identifier { + FontIdentifier::Local(_) => None, + FontIdentifier::Web(_) => Some(font.font_data_and_index().ok()?), + } + .cloned(); + let canvas_font = CanvasFont { + identifier, + data: font_data, + }; + + Some(TextRun { + font: canvas_font, + pt_size: font.descriptor.pt_size.to_f32_px(), + glyphs_and_positions, + advance, + bounds: bounds.unwrap_or_default().cast(), + }) + } +} + pub(crate) fn parse_color( canvas: Option<&HTMLCanvasElement>, string: &str, @@ -2334,3 +2597,12 @@ impl Convert for CanvasFillRule { } } } + +fn replace_ascii_whitespace(text: &str) -> String { + text.chars() + .map(|c| match c { + ' ' | '\t' | '\n' | '\r' | '\x0C' => '\x20', + _ => c, + }) + .collect() +} diff --git a/components/script/dom/canvasrenderingcontext2d.rs b/components/script/dom/canvasrenderingcontext2d.rs index 2e12f93eafb..a42eb15a06d 100644 --- a/components/script/dom/canvasrenderingcontext2d.rs +++ b/components/script/dom/canvasrenderingcontext2d.rs @@ -328,8 +328,14 @@ impl CanvasRenderingContext2DMethods for CanvasRenderingCo // https://html.spec.whatwg.org/multipage/#dom-context-2d-filltext fn FillText(&self, text: DOMString, x: f64, y: f64, max_width: Option) { - self.canvas_state - .fill_text(self.canvas.canvas().as_deref(), text, x, y, max_width); + self.canvas_state.fill_text( + &self.global(), + self.canvas.canvas().as_deref(), + text, + x, + y, + max_width, + ); self.mark_as_dirty(); } diff --git a/components/script/dom/textmetrics.rs b/components/script/dom/textmetrics.rs index f4aa38a19ed..593510b633e 100644 --- a/components/script/dom/textmetrics.rs +++ b/components/script/dom/textmetrics.rs @@ -99,6 +99,28 @@ impl TextMetrics { can_gc, ) } + + pub(crate) fn default(global: &GlobalScope, can_gc: CanGc) -> DomRoot { + reflect_dom_object( + Box::new(Self { + reflector_: Reflector::new(), + width: Default::default(), + actualBoundingBoxLeft: Default::default(), + actualBoundingBoxRight: Default::default(), + fontBoundingBoxAscent: Default::default(), + fontBoundingBoxDescent: Default::default(), + actualBoundingBoxAscent: Default::default(), + actualBoundingBoxDescent: Default::default(), + emHeightAscent: Default::default(), + emHeightDescent: Default::default(), + hangingBaseline: Default::default(), + alphabeticBaseline: Default::default(), + ideographicBaseline: Default::default(), + }), + global, + can_gc, + ) + } } impl TextMetricsMethods for TextMetrics { diff --git a/components/shared/canvas/Cargo.toml b/components/shared/canvas/Cargo.toml index 36711c0f83a..4f6748b8395 100644 --- a/components/shared/canvas/Cargo.toml +++ b/components/shared/canvas/Cargo.toml @@ -18,6 +18,7 @@ webgl_backtrace = [] base = { workspace = true } crossbeam-channel = { workspace = true } euclid = { workspace = true } +fonts_traits = { workspace = true } glow = { workspace = true } ipc-channel = { workspace = true } kurbo = { workspace = true, features = ["serde"] } diff --git a/components/shared/canvas/canvas.rs b/components/shared/canvas/canvas.rs index 8bbc96f05ac..abf8d19da1b 100644 --- a/components/shared/canvas/canvas.rs +++ b/components/shared/canvas/canvas.rs @@ -8,6 +8,7 @@ use std::str::FromStr; use euclid::Angle; use euclid::approxeq::ApproxEq; use euclid::default::{Point2D, Rect, Size2D, Transform2D}; +use fonts_traits::{FontDataAndIndex, FontIdentifier}; use ipc_channel::ipc::IpcSender; use kurbo::{BezPath, ParamCurveNearest as _, PathEl, Point, Shape, Triangle}; use malloc_size_of::MallocSizeOf; @@ -16,8 +17,6 @@ use pixels::IpcSnapshot; use serde::{Deserialize, Serialize}; use strum::{Display, EnumString}; use style::color::AbsoluteColor; -use style::properties::style_structs::Font as FontStyleStruct; -use style::servo_arc::Arc as ServoArc; #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct Path(pub BezPath); @@ -437,14 +436,6 @@ pub struct LineOptions { pub dash_offset: f64, } -#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] -pub struct TextOptions { - #[ignore_malloc_size_of = "Arc"] - pub font: Option>, - pub align: TextAlign, - pub baseline: TextBaseline, -} - #[allow(clippy::large_enum_variant)] #[derive(Debug, Deserialize, Serialize)] pub enum CanvasMsg { @@ -493,13 +484,9 @@ pub enum Canvas2dMsg { Transform2D, ), FillText( - String, - f64, - f64, - Option, + Rect, + Vec, FillOrStrokeStyle, - bool, - TextOptions, ShadowOptions, CompositionOptions, Transform2D, @@ -512,7 +499,6 @@ pub enum Canvas2dMsg { Transform2D, ), GetImageData(Option>, IpcSender), - MeasureText(String, IpcSender, TextOptions), PutImageData(Rect, IpcSnapshot), StrokeRect( Rect, @@ -777,80 +763,46 @@ impl FromStr for CompositionOrBlending { } } -#[derive( - Clone, - Copy, - Debug, - Default, - Deserialize, - Display, - EnumString, - MallocSizeOf, - PartialEq, - Serialize, -)] -pub enum TextAlign { - #[default] - Start, - End, - Left, - Right, - Center, +#[derive(Debug, Deserialize, Serialize)] +pub struct GlyphAndPosition { + pub id: u32, + pub point: Point2D, } -#[derive( - Clone, - Copy, - Debug, - Default, - Deserialize, - Display, - EnumString, - MallocSizeOf, - PartialEq, - Serialize, -)] -pub enum TextBaseline { - Top, - Hanging, - Middle, - #[default] - Alphabetic, - Ideographic, - Bottom, +#[derive(Deserialize, Serialize)] +pub struct CanvasFont { + /// A [`FontIdentifier`] for this [`CanvasFont`], maybe either `Local` or `Web`. + pub identifier: FontIdentifier, + /// If this font is a web font, this field contains the data for the font. If + /// the font is a local font, it will be `None`. + pub data: Option, } -#[derive( - Clone, - Copy, - Debug, - Default, - Deserialize, - Display, - EnumString, - MallocSizeOf, - PartialEq, - Serialize, -)] -pub enum Direction { - Ltr, - Rtl, - #[default] - Inherit, +impl CanvasFont { + pub fn font_data_and_index(&self) -> Option { + self.data.clone().or_else(|| match &self.identifier { + FontIdentifier::Local(local_font_identifier) => { + local_font_identifier.font_data_and_index() + }, + FontIdentifier::Web(_) => None, + }) + } } -#[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, +#[derive(Deserialize, Serialize)] +pub struct TextRun { + pub font: CanvasFont, + pub pt_size: f32, + pub glyphs_and_positions: Vec, + pub advance: f32, + pub bounds: Rect, +} + +impl std::fmt::Debug for TextRun { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("TextRun") + .field("glyphs_and_positions", &self.glyphs_and_positions) + .field("size", &self.bounds) + .finish() + } } diff --git a/components/shared/fonts/lib.rs b/components/shared/fonts/lib.rs index eb0b9cbee68..efda81c3a06 100644 --- a/components/shared/fonts/lib.rs +++ b/components/shared/fonts/lib.rs @@ -32,7 +32,7 @@ pub type StylesheetWebFontLoadFinishedCallback = Arc); impl FontData { @@ -55,7 +55,7 @@ impl AsRef<[u8]> for FontData { /// /// If the font data is of a TTC (TrueType collection) file, then the index of a specific font within /// the collection. If the font data is for is single font then the index will always be 0. -#[derive(Clone)] +#[derive(Deserialize, Clone, Serialize)] pub struct FontDataAndIndex { /// The raw font file data (.ttf, .otf, .ttc, etc) pub data: FontData, diff --git a/tests/wpt/meta/css/css-fonts/generic-family-keywords-003.html.ini b/tests/wpt/meta/css/css-fonts/generic-family-keywords-003.html.ini new file mode 100644 index 00000000000..352995a05da --- /dev/null +++ b/tests/wpt/meta/css/css-fonts/generic-family-keywords-003.html.ini @@ -0,0 +1,42 @@ +[generic-family-keywords-003.html] + [@font-face matching for quoted and unquoted math (drawing text in a canvas)] + expected: FAIL + + [@font-face matching for quoted and unquoted ui-serif (drawing text in a canvas)] + expected: FAIL + + [@font-face matching for quoted and unquoted ui-sans-serif (drawing text in a canvas)] + expected: FAIL + + [@font-face matching for quoted and unquoted ui-monospace (drawing text in a canvas)] + expected: FAIL + + [@font-face matching for quoted and unquoted ui-rounded (drawing text in a canvas)] + expected: FAIL + + [@font-face matching for quoted and unquoted sans-serif (drawing text in a canvas)] + expected: FAIL + + [@font-face matching for quoted and unquoted cursive (drawing text in a canvas)] + expected: FAIL + + [@font-face matching for quoted and unquoted fantasy (drawing text in a canvas)] + expected: FAIL + + [@font-face matching for quoted and unquoted monospace (drawing text in a canvas)] + expected: FAIL + + [@font-face matching for quoted and unquoted system-ui (drawing text in a canvas)] + expected: FAIL + + [@font-face matching for quoted and unquoted generic(fangsong) (drawing text in a canvas)] + expected: FAIL + + [@font-face matching for quoted and unquoted generic(kai) (drawing text in a canvas)] + expected: FAIL + + [@font-face matching for quoted and unquoted generic(khmer-mul) (drawing text in a canvas)] + expected: FAIL + + [@font-face matching for quoted and unquoted generic(nastaliq) (drawing text in a canvas)] + expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/manual/text/canvas.2d.fontStretch.condensed.html.ini b/tests/wpt/meta/html/canvas/element/manual/text/canvas.2d.fontStretch.condensed.html.ini new file mode 100644 index 00000000000..f1389317ba4 --- /dev/null +++ b/tests/wpt/meta/html/canvas/element/manual/text/canvas.2d.fontStretch.condensed.html.ini @@ -0,0 +1,2 @@ +[canvas.2d.fontStretch.condensed.html] + expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/manual/text/canvas.2d.fontStretch.expanded.html.ini b/tests/wpt/meta/html/canvas/element/manual/text/canvas.2d.fontStretch.expanded.html.ini new file mode 100644 index 00000000000..dc035a8d7a6 --- /dev/null +++ b/tests/wpt/meta/html/canvas/element/manual/text/canvas.2d.fontStretch.expanded.html.ini @@ -0,0 +1,2 @@ +[canvas.2d.fontStretch.expanded.html] + expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/manual/text/canvas.2d.fontStretch.extra-condensed.html.ini b/tests/wpt/meta/html/canvas/element/manual/text/canvas.2d.fontStretch.extra-condensed.html.ini new file mode 100644 index 00000000000..b21e1ff7942 --- /dev/null +++ b/tests/wpt/meta/html/canvas/element/manual/text/canvas.2d.fontStretch.extra-condensed.html.ini @@ -0,0 +1,2 @@ +[canvas.2d.fontStretch.extra-condensed.html] + expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/manual/text/canvas.2d.fontStretch.extra-expanded.html.ini b/tests/wpt/meta/html/canvas/element/manual/text/canvas.2d.fontStretch.extra-expanded.html.ini new file mode 100644 index 00000000000..2a3dae38d26 --- /dev/null +++ b/tests/wpt/meta/html/canvas/element/manual/text/canvas.2d.fontStretch.extra-expanded.html.ini @@ -0,0 +1,2 @@ +[canvas.2d.fontStretch.extra-expanded.html] + expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/manual/text/canvas.2d.fontStretch.semi-condensed.html.ini b/tests/wpt/meta/html/canvas/element/manual/text/canvas.2d.fontStretch.semi-condensed.html.ini new file mode 100644 index 00000000000..82112139c28 --- /dev/null +++ b/tests/wpt/meta/html/canvas/element/manual/text/canvas.2d.fontStretch.semi-condensed.html.ini @@ -0,0 +1,2 @@ +[canvas.2d.fontStretch.semi-condensed.html] + expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/manual/text/canvas.2d.fontStretch.semi-expanded.html.ini b/tests/wpt/meta/html/canvas/element/manual/text/canvas.2d.fontStretch.semi-expanded.html.ini new file mode 100644 index 00000000000..5d96cf42cd9 --- /dev/null +++ b/tests/wpt/meta/html/canvas/element/manual/text/canvas.2d.fontStretch.semi-expanded.html.ini @@ -0,0 +1,2 @@ +[canvas.2d.fontStretch.semi-expanded.html] + expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/manual/text/canvas.2d.fontStretch.ultra-condensed.html.ini b/tests/wpt/meta/html/canvas/element/manual/text/canvas.2d.fontStretch.ultra-condensed.html.ini new file mode 100644 index 00000000000..4655dc92135 --- /dev/null +++ b/tests/wpt/meta/html/canvas/element/manual/text/canvas.2d.fontStretch.ultra-condensed.html.ini @@ -0,0 +1,2 @@ +[canvas.2d.fontStretch.ultra-condensed.html] + expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/manual/text/canvas.2d.fontStretch.ultra-expanded.html.ini b/tests/wpt/meta/html/canvas/element/manual/text/canvas.2d.fontStretch.ultra-expanded.html.ini new file mode 100644 index 00000000000..118ea5fd38c --- /dev/null +++ b/tests/wpt/meta/html/canvas/element/manual/text/canvas.2d.fontStretch.ultra-expanded.html.ini @@ -0,0 +1,2 @@ +[canvas.2d.fontStretch.ultra-expanded.html] + expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/text/2d.text.draw.align.center.html.ini b/tests/wpt/meta/html/canvas/element/text/2d.text.draw.align.center.html.ini deleted file mode 100644 index 41fa983ddcf..00000000000 --- a/tests/wpt/meta/html/canvas/element/text/2d.text.draw.align.center.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[2d.text.draw.align.center.html] - [textAlign center is the center of the em squares (not the bounding box)] - expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/text/2d.text.draw.align.end.ltr.html.ini b/tests/wpt/meta/html/canvas/element/text/2d.text.draw.align.end.ltr.html.ini deleted file mode 100644 index b2791db60af..00000000000 --- a/tests/wpt/meta/html/canvas/element/text/2d.text.draw.align.end.ltr.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[2d.text.draw.align.end.ltr.html] - [textAlign end with ltr is the right edge] - expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/text/2d.text.draw.align.left.html.ini b/tests/wpt/meta/html/canvas/element/text/2d.text.draw.align.left.html.ini deleted file mode 100644 index ed0f7d666fe..00000000000 --- a/tests/wpt/meta/html/canvas/element/text/2d.text.draw.align.left.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[2d.text.draw.align.left.html] - [textAlign left is the left of the first em square (not the bounding box)] - expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/text/2d.text.draw.align.right.html.ini b/tests/wpt/meta/html/canvas/element/text/2d.text.draw.align.right.html.ini deleted file mode 100644 index 709ca126edd..00000000000 --- a/tests/wpt/meta/html/canvas/element/text/2d.text.draw.align.right.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[2d.text.draw.align.right.html] - [textAlign right is the right of the last em square (not the bounding box)] - expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/text/2d.text.draw.align.start.ltr.html.ini b/tests/wpt/meta/html/canvas/element/text/2d.text.draw.align.start.ltr.html.ini deleted file mode 100644 index 312d5c3cb74..00000000000 --- a/tests/wpt/meta/html/canvas/element/text/2d.text.draw.align.start.ltr.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[2d.text.draw.align.start.ltr.html] - [textAlign start with ltr is the left edge] - expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/text/2d.text.draw.baseline.alphabetic.html.ini b/tests/wpt/meta/html/canvas/element/text/2d.text.draw.baseline.alphabetic.html.ini deleted file mode 100644 index eb77a98f379..00000000000 --- a/tests/wpt/meta/html/canvas/element/text/2d.text.draw.baseline.alphabetic.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[2d.text.draw.baseline.alphabetic.html] - [Canvas test: 2d.text.draw.baseline.alphabetic] - expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/text/2d.text.draw.baseline.bottom.html.ini b/tests/wpt/meta/html/canvas/element/text/2d.text.draw.baseline.bottom.html.ini deleted file mode 100644 index 57379d7652f..00000000000 --- a/tests/wpt/meta/html/canvas/element/text/2d.text.draw.baseline.bottom.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[2d.text.draw.baseline.bottom.html] - [textBaseline bottom is the bottom of the em square (not the bounding box)] - expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/text/2d.text.draw.baseline.middle.html.ini b/tests/wpt/meta/html/canvas/element/text/2d.text.draw.baseline.middle.html.ini deleted file mode 100644 index fb00ebe5c37..00000000000 --- a/tests/wpt/meta/html/canvas/element/text/2d.text.draw.baseline.middle.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[2d.text.draw.baseline.middle.html] - [textBaseline middle is the middle of the em square (not the bounding box)] - expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/text/2d.text.draw.baseline.top.html.ini b/tests/wpt/meta/html/canvas/element/text/2d.text.draw.baseline.top.html.ini deleted file mode 100644 index eb6c9eb1b9e..00000000000 --- a/tests/wpt/meta/html/canvas/element/text/2d.text.draw.baseline.top.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[2d.text.draw.baseline.top.html] - [textBaseline top is the top of the em square (not the bounding box)] - expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/text/2d.text.draw.fill.maxWidth.bound.html.ini b/tests/wpt/meta/html/canvas/element/text/2d.text.draw.fill.maxWidth.bound.html.ini deleted file mode 100644 index 42046658284..00000000000 --- a/tests/wpt/meta/html/canvas/element/text/2d.text.draw.fill.maxWidth.bound.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[2d.text.draw.fill.maxWidth.bound.html] - [fillText handles maxWidth based on line size, not bounding box size] - expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/text/2d.text.draw.fontface.html.ini b/tests/wpt/meta/html/canvas/element/text/2d.text.draw.fontface.html.ini deleted file mode 100644 index a536e29790e..00000000000 --- a/tests/wpt/meta/html/canvas/element/text/2d.text.draw.fontface.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[2d.text.draw.fontface.html] - [Canvas test: 2d.text.draw.fontface] - expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/text/2d.text.draw.fontface.notinpage.html.ini b/tests/wpt/meta/html/canvas/element/text/2d.text.draw.fontface.notinpage.html.ini deleted file mode 100644 index 5de0c0d20bb..00000000000 --- a/tests/wpt/meta/html/canvas/element/text/2d.text.draw.fontface.notinpage.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[2d.text.draw.fontface.notinpage.html] - [@font-face fonts should work even if they are not used in the page] - expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/text/2d.text.draw.fontface.repeat.html.ini b/tests/wpt/meta/html/canvas/element/text/2d.text.draw.fontface.repeat.html.ini deleted file mode 100644 index c0261adb627..00000000000 --- a/tests/wpt/meta/html/canvas/element/text/2d.text.draw.fontface.repeat.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[2d.text.draw.fontface.repeat.html] - [Draw with the font immediately, then wait a bit until and draw again. (This crashes some version of WebKit.)] - expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/text/2d.text.draw.space.collapse.other.html.ini b/tests/wpt/meta/html/canvas/element/text/2d.text.draw.space.collapse.other.html.ini deleted file mode 100644 index 1bf6ebc60fc..00000000000 --- a/tests/wpt/meta/html/canvas/element/text/2d.text.draw.space.collapse.other.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[2d.text.draw.space.collapse.other.html] - [Space characters are converted to U+0020, and are NOT collapsed] - expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/text/2d.text.draw.space.collapse.space.html.ini b/tests/wpt/meta/html/canvas/element/text/2d.text.draw.space.collapse.space.html.ini deleted file mode 100644 index dd23d585ef7..00000000000 --- a/tests/wpt/meta/html/canvas/element/text/2d.text.draw.space.collapse.space.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[2d.text.draw.space.collapse.space.html] - [Space characters are converted to U+0020, and are NOT collapsed] - expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/text/2d.text.measure.emHeights.html.ini b/tests/wpt/meta/html/canvas/element/text/2d.text.measure.emHeights.html.ini deleted file mode 100644 index f05f02b77ce..00000000000 --- a/tests/wpt/meta/html/canvas/element/text/2d.text.measure.emHeights.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[2d.text.measure.emHeights.html] - [Testing emHeights] - expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/text/2d.text.measure.fontBoundingBox.ahem.html.ini b/tests/wpt/meta/html/canvas/element/text/2d.text.measure.fontBoundingBox.ahem.html.ini deleted file mode 100644 index b1020bc5853..00000000000 --- a/tests/wpt/meta/html/canvas/element/text/2d.text.measure.fontBoundingBox.ahem.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[2d.text.measure.fontBoundingBox.ahem.html] - [Testing fontBoundingBox for font ahem] - expected: FAIL diff --git a/tests/wpt/meta/html/canvas/element/text/2d.text.measure.width.basic.html.ini b/tests/wpt/meta/html/canvas/element/text/2d.text.measure.width.basic.html.ini deleted file mode 100644 index 7dfc4268e8c..00000000000 --- a/tests/wpt/meta/html/canvas/element/text/2d.text.measure.width.basic.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[2d.text.measure.width.basic.html] - [The width of character is same as font used] - expected: FAIL diff --git a/tests/wpt/meta/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.fillText.html.ini b/tests/wpt/meta/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.fillText.html.ini new file mode 100644 index 00000000000..0a8b4be90d6 --- /dev/null +++ b/tests/wpt/meta/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.fillText.html.ini @@ -0,0 +1,3 @@ +[2d.gradient.interpolate.zerosize.fillText.html] + [OffscreenCanvas test: 2d.gradient.interpolate.zerosize.fillText] + expected: FAIL diff --git a/tests/wpt/meta/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.fillText.worker.js.ini b/tests/wpt/meta/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.fillText.worker.js.ini new file mode 100644 index 00000000000..0a6c14e61c4 --- /dev/null +++ b/tests/wpt/meta/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.fillText.worker.js.ini @@ -0,0 +1,3 @@ +[2d.gradient.interpolate.zerosize.fillText.worker.html] + [2d] + expected: FAIL diff --git a/tests/wpt/meta/html/canvas/offscreen/manual/draw-generic-family/2d.text.draw.generic.family.html.ini b/tests/wpt/meta/html/canvas/offscreen/manual/draw-generic-family/2d.text.draw.generic.family.html.ini index 6db5f539fc2..38bae31a5e5 100644 --- a/tests/wpt/meta/html/canvas/offscreen/manual/draw-generic-family/2d.text.draw.generic.family.html.ini +++ b/tests/wpt/meta/html/canvas/offscreen/manual/draw-generic-family/2d.text.draw.generic.family.html.ini @@ -2,9 +2,6 @@ [Test that drawing sans-serif produces the same result between canvas and OffscreenCanvas] expected: FAIL - [Test that drawing serif produces the same result between canvas and OffscreenCanvas] - expected: FAIL - [Test that drawing cursive produces the same result between canvas and OffscreenCanvas] expected: FAIL diff --git a/tests/wpt/meta/html/canvas/offscreen/text/2d.text.drawing.style.measure.rtl.text.html.ini b/tests/wpt/meta/html/canvas/offscreen/text/2d.text.drawing.style.measure.rtl.text.html.ini deleted file mode 100644 index 370a19f75d2..00000000000 --- a/tests/wpt/meta/html/canvas/offscreen/text/2d.text.drawing.style.measure.rtl.text.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[2d.text.drawing.style.measure.rtl.text.html] - [Measurement should follow canvas direction instead text direction] - expected: FAIL diff --git a/tests/wpt/meta/html/canvas/offscreen/text/2d.text.drawing.style.measure.rtl.text.worker.js.ini b/tests/wpt/meta/html/canvas/offscreen/text/2d.text.drawing.style.measure.rtl.text.worker.js.ini deleted file mode 100644 index 33fd53ae0fd..00000000000 --- a/tests/wpt/meta/html/canvas/offscreen/text/2d.text.drawing.style.measure.rtl.text.worker.js.ini +++ /dev/null @@ -1,3 +0,0 @@ -[2d.text.drawing.style.measure.rtl.text.worker.html] - [Measurement should follow canvas direction instead text direction] - expected: FAIL diff --git a/tests/wpt/meta/html/canvas/offscreen/text/2d.text.drawing.style.measure.textAlign.html.ini b/tests/wpt/meta/html/canvas/offscreen/text/2d.text.drawing.style.measure.textAlign.html.ini deleted file mode 100644 index 6d8e70c072f..00000000000 --- a/tests/wpt/meta/html/canvas/offscreen/text/2d.text.drawing.style.measure.textAlign.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[2d.text.drawing.style.measure.textAlign.html] - [Measurement should be related to textAlignment] - expected: FAIL diff --git a/tests/wpt/meta/html/canvas/offscreen/text/2d.text.drawing.style.measure.textAlign.worker.js.ini b/tests/wpt/meta/html/canvas/offscreen/text/2d.text.drawing.style.measure.textAlign.worker.js.ini deleted file mode 100644 index ad32ca0852d..00000000000 --- a/tests/wpt/meta/html/canvas/offscreen/text/2d.text.drawing.style.measure.textAlign.worker.js.ini +++ /dev/null @@ -1,3 +0,0 @@ -[2d.text.drawing.style.measure.textAlign.worker.html] - [Measurement should be related to textAlignment] - expected: FAIL diff --git a/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.width.nullCharacter.html.ini b/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.width.nullCharacter.html.ini new file mode 100644 index 00000000000..56b7cb698af --- /dev/null +++ b/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.width.nullCharacter.html.ini @@ -0,0 +1,3 @@ +[2d.text.measure.width.nullCharacter.html] + [Null character does not take up space] + expected: FAIL