diff options
20 files changed, 170 insertions, 58 deletions
diff --git a/Cargo.lock b/Cargo.lock index 60a8ffb17c7..e5ef56bb0de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -486,6 +486,7 @@ dependencies = [ "embedder_traits", "euclid", "fnv", + "font-kit", "gleam 0.9.2", "half", "ipc-channel", @@ -4406,8 +4407,8 @@ checksum = "dd5927936723a9e8b715d37d7e4b390455087c4bdf25b9f702309460577b14f9" [[package]] name = "raqote" -version = "0.7.8-alpha.0" -source = "git+https://github.com/jrmuizel/raqote#0cad3c338d9587bf0e9f6bc9e26112998767a1b5" +version = "0.7.15-alpha.0" +source = "git+https://github.com/jrmuizel/raqote#56fa45b39ec5dc6779bdc7c103394df5cf58d546" dependencies = [ "euclid", "font-kit", @@ -5799,9 +5800,9 @@ checksum = "c666f0fed8e1e20e057af770af9077d72f3d5a33157b8537c1475dd8ffd6d32b" [[package]] name = "sw-composite" -version = "0.7.2" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50a36f1738c7e57fec506df8c94719b2210816ab9de4d3dadeb9efb6bc71f1d2" +checksum = "489500b88acdc4007684adf921506e84057e232a7f8b76a0caa288169ab39b94" [[package]] name = "swapper" diff --git a/components/canvas/Cargo.toml b/components/canvas/Cargo.toml index 0917ad981a0..21f5a055cd3 100644 --- a/components/canvas/Cargo.toml +++ b/components/canvas/Cargo.toml @@ -44,3 +44,4 @@ webxr-api = {git = "https://github.com/servo/webxr", features = ["ipc"]} surfman = { version = "0.1", features = ["sm-angle", "sm-osmesa"] } surfman-chains = "0.3" surfman-chains-api = "0.2" +font-kit = "0.5.0" diff --git a/components/canvas/canvas_data.rs b/components/canvas/canvas_data.rs index f291ce416e9..fa77b016dd0 100644 --- a/components/canvas/canvas_data.rs +++ b/components/canvas/canvas_data.rs @@ -266,6 +266,15 @@ pub trait GenericDrawTarget { ); fn fill(&mut self, path: &Path, pattern: Pattern, draw_options: &DrawOptions); fn fill_rect(&mut self, rect: &Rect<f32>, pattern: Pattern, draw_options: Option<&DrawOptions>); + fn fill_text( + &mut self, + text: String, + x: f32, + y: f32, + max_width: Option<f64>, + pattern: Pattern, + draw_options: &DrawOptions, + ); fn get_format(&self) -> SurfaceFormat; fn get_size(&self) -> Size2D<i32>; fn get_transform(&self) -> Transform2D<f32>; @@ -458,10 +467,19 @@ impl<'a> CanvasData<'a> { } } - pub fn fill_text(&self, text: String, x: f64, y: f64, max_width: Option<f64>) { - error!( - "Unimplemented canvas2d.fillText. Values received: {}, {}, {}, {:?}.", - text, x, y, max_width + pub fn fill_text(&mut self, text: String, x: f64, y: f64, max_width: Option<f64>) { + // If any of the arguments are infinite or NaN, then return. + if !x.is_finite() || !y.is_finite() { + return; + } + + self.drawtarget.fill_text( + text, + x as f32, + y as f32, + max_width, + self.state.fill_style.clone(), + &self.state.draw_options, ); } diff --git a/components/canvas/raqote_backend.rs b/components/canvas/raqote_backend.rs index 72eae8b40c8..6daa8712421 100644 --- a/components/canvas/raqote_backend.rs +++ b/components/canvas/raqote_backend.rs @@ -13,6 +13,9 @@ use canvas_traits::canvas::*; use cssparser::RGBA; use euclid::default::{Point2D, Rect, Size2D, Transform2D, Vector2D}; use euclid::Angle; +use font_kit::family_name::FamilyName; +use font_kit::properties::Properties; +use font_kit::source::SystemSource; use lyon_geom::Arc; use raqote::PathOp; use std::marker::PhantomData; @@ -539,6 +542,84 @@ impl GenericDrawTarget for raqote::DrawTarget { &DrawOptions::Raqote(draw_options), ); } + // TODO + // This should eventually use the same infrastructure as layout + // (i.e. layout should be updated to use font-kit as well). + // Need to implement .font . + fn fill_text( + &mut self, + text: String, + x: f32, + y: f32, + max_width: Option<f64>, + pattern: canvas_data::Pattern, + draw_options: &DrawOptions, + ) { + // Replace all ASCII whitespace in text with U+0020 SPACE characters. + fn replace_whitespace(text: String) -> String { + text.chars() + .map(|c| match c { + '\x09'..='\x0D' => '\x20', + _ => c, + }) + .collect() + } + + // Compute the width of the text + fn get_text_width(text: &str, font: &font_kit::font::Font) -> f64 { + let point_size = 24.; + let mut length = 0.; + for c in text.chars() { + let id = font.glyph_for_char(c).unwrap(); + length += (font.advance(id).unwrap() * point_size / 24. / 96.).x; + } + length as f64 + } + + let font = SystemSource::new() + .select_best_match(&[FamilyName::SansSerif], &Properties::new()) + .unwrap() + .load() + .unwrap(); + + // text preparation algorithm + let (scale_factor, replaced_text) = match max_width { + Some(value) => { + if value <= 0. || !value.is_finite() { + return; + } else { + let replaced_text = replace_whitespace(text); + let text_width = get_text_width(&replaced_text, &font); + if value > text_width { + (1., replaced_text) + } else { + (value / text_width, replaced_text) + } + } + }, + _ => (1., replace_whitespace(text)), + }; + + // Text scaling + let old_transform = self.get_transform().clone(); + let new_transform = old_transform + .pre_translate(Vector2D::new(x as f32, 0.)) + .pre_scale(scale_factor as f32, 1.) + .pre_translate(Vector2D::new(-x as f32, 0.)); + self.set_transform(&new_transform); + + self.draw_text( + &font, + 24., + &replaced_text, + Point2D::new(x, y), + &pattern.source(), + draw_options.as_raqote(), + ); + + // Restore the transform + self.set_transform(&old_transform); + } fn get_format(&self) -> SurfaceFormat { SurfaceFormat::Raqote(()) } diff --git a/components/layout_2020/flow/inline.rs b/components/layout_2020/flow/inline.rs index f71187e1118..9f98174e2cd 100644 --- a/components/layout_2020/flow/inline.rs +++ b/components/layout_2020/flow/inline.rs @@ -12,7 +12,10 @@ use crate::fragments::{ DebugId, Fragment, TextFragment, }; use crate::geom::flow_relative::{Rect, Sides, Vec2}; -use crate::positioned::{relative_adjustement, AbsolutelyPositionedBox, PositioningContext}; +use crate::positioned::{ + relative_adjustement, AbsolutelyPositionedBox, HoistedAbsolutelyPositionedBox, + PositioningContext, +}; use crate::sizing::ContentSizes; use crate::style_ext::{ComputedValuesExt, Display, DisplayGeneratingBox, DisplayOutside}; use crate::ContainingBlock; @@ -64,6 +67,7 @@ struct InlineNestingLevelState<'box_tree> { fragments_so_far: Vec<Fragment>, inline_start: Length, max_block_size_of_fragments_so_far: Length, + positioning_context: Option<PositioningContext>, } struct PartialInlineBoxFragment<'box_tree> { @@ -86,6 +90,31 @@ struct InlineFormattingContextState<'box_tree, 'a, 'b> { current_nesting_level: InlineNestingLevelState<'box_tree>, } +impl<'box_tree, 'a, 'b> InlineFormattingContextState<'box_tree, 'a, 'b> { + fn push_hoisted_box_to_positioning_context( + &mut self, + hoisted_box: HoistedAbsolutelyPositionedBox, + ) { + if let Some(context) = self.current_nesting_level.positioning_context.as_mut() { + context.push(hoisted_box); + return; + } + + for nesting_level in self.partial_inline_boxes_stack.iter_mut().rev() { + if let Some(context) = nesting_level + .parent_nesting_level + .positioning_context + .as_mut() + { + context.push(hoisted_box); + return; + } + } + + self.positioning_context.push(hoisted_box); + } +} + struct Lines { // One anonymous fragment per line fragments: Vec<Fragment>, @@ -226,6 +255,7 @@ impl InlineFormattingContext { fragments_so_far: Vec::with_capacity(self.inline_level_boxes.len()), inline_start: Length::zero(), max_block_size_of_fragments_so_far: Length::zero(), + positioning_context: None, }, }; loop { @@ -257,15 +287,14 @@ impl InlineFormattingContext { panic!("display:none does not generate an abspos box") }, }; - let hoisted_fragment = - box_.clone().to_hoisted(initial_start_corner, tree_rank); - let hoisted_fragment_id = hoisted_fragment.fragment_id; - ifc.positioning_context.push(hoisted_fragment); - ifc.lines - .fragments - .push(Fragment::AbsoluteOrFixedPositioned( - AbsoluteOrFixedPositionedFragment(hoisted_fragment_id), - )); + let hoisted_box = box_.clone().to_hoisted(initial_start_corner, tree_rank); + let hoisted_fragment_id = hoisted_box.fragment_id; + ifc.push_hoisted_box_to_positioning_context(hoisted_box); + ifc.current_nesting_level.fragments_so_far.push( + Fragment::AbsoluteOrFixedPositioned(AbsoluteOrFixedPositionedFragment( + hoisted_fragment_id, + )), + ); }, InlineLevelBox::OutOfFlowFloatBox(_box_) => { // TODO @@ -275,6 +304,7 @@ impl InlineFormattingContext { // Reached the end of ifc.remaining_boxes if let Some(mut partial) = ifc.partial_inline_boxes_stack.pop() { partial.finish_layout( + layout_context, &mut ifc.current_nesting_level, &mut ifc.inline_position, false, @@ -392,6 +422,7 @@ impl InlineBox { if style.clone_position().is_relative() { start_corner += &relative_adjustement(&style, ifc.containing_block) } + let positioning_context = PositioningContext::new_for_style(&style); PartialInlineBoxFragment { tag: self.tag, style, @@ -409,6 +440,7 @@ impl InlineBox { fragments_so_far: Vec::with_capacity(self.children.len()), inline_start: ifc.inline_position, max_block_size_of_fragments_so_far: Length::zero(), + positioning_context, }, ), } @@ -418,6 +450,7 @@ impl InlineBox { impl<'box_tree> PartialInlineBoxFragment<'box_tree> { fn finish_layout( &mut self, + layout_context: &LayoutContext, nesting_level: &mut InlineNestingLevelState, inline_position: &mut Length, at_line_break: bool, @@ -459,6 +492,11 @@ impl<'box_tree> PartialInlineBoxFragment<'box_tree> { fragment.border.block_sum() + fragment.margin.block_sum(), ); + + if let Some(context) = nesting_level.positioning_context.as_mut() { + context.layout_collected_children(layout_context, &mut fragment); + } + self.parent_nesting_level .fragments_so_far .push(Fragment::Box(fragment)); @@ -748,7 +786,12 @@ impl TextRun { ifc.current_nesting_level.inline_start = Length::zero(); let mut nesting_level = &mut ifc.current_nesting_level; for partial in ifc.partial_inline_boxes_stack.iter_mut().rev() { - partial.finish_layout(nesting_level, &mut ifc.inline_position, true); + partial.finish_layout( + layout_context, + nesting_level, + &mut ifc.inline_position, + true, + ); partial.start_corner.inline = Length::zero(); partial.padding.inline_start = Length::zero(); partial.border.inline_start = Length::zero(); diff --git a/components/layout_2020/positioned.rs b/components/layout_2020/positioned.rs index b9dca32db12..a0216ec296c 100644 --- a/components/layout_2020/positioned.rs +++ b/components/layout_2020/positioned.rs @@ -172,7 +172,7 @@ impl PositioningContext { self.for_nearest_positioned_ancestor.is_some() } - fn new_for_style(style: &ComputedValues) -> Option<Self> { + pub(crate) fn new_for_style(style: &ComputedValues) -> Option<Self> { if style.establishes_containing_block_for_all_descendants() { Some(Self::new_for_containing_block_for_all_descendants()) } else if style.establishes_containing_block() { @@ -243,7 +243,7 @@ impl PositioningContext { // Lay out the hoisted boxes collected into this `PositioningContext` and add them // to the given `BoxFragment`. - fn layout_collected_children( + pub fn layout_collected_children( &mut self, layout_context: &LayoutContext, new_fragment: &mut BoxFragment, diff --git a/components/layout_2020/style_ext.rs b/components/layout_2020/style_ext.rs index 7ed41d1cd91..cdde69a7866 100644 --- a/components/layout_2020/style_ext.rs +++ b/components/layout_2020/style_ext.rs @@ -245,7 +245,9 @@ impl ComputedValuesExt for ComputedValues { /// Returns true if this style establishes a containing block for all descendants /// including fixed and absolutely positioned ones. fn establishes_containing_block_for_all_descendants(&self) -> bool { - if self.has_transform_or_perspective() { + if self.get_box().display.outside() != stylo::DisplayOutside::Inline && + self.has_transform_or_perspective() + { return true; } diff --git a/tests/wpt/include-layout-2020.ini b/tests/wpt/include-layout-2020.ini index 30f650b893a..a1ac1c24332 100644 --- a/tests/wpt/include-layout-2020.ini +++ b/tests/wpt/include-layout-2020.ini @@ -5,8 +5,6 @@ skip: true skip: false [mozilla] skip: false -[2dcontext] - skip: false [css] skip: true [CSS2] diff --git a/tests/wpt/metadata-layout-2020/css/CSS2/positioning/abspos-inline-008.xht.ini b/tests/wpt/metadata-layout-2020/css/CSS2/positioning/abspos-inline-008.xht.ini deleted file mode 100644 index f77c2c83d5d..00000000000 --- a/tests/wpt/metadata-layout-2020/css/CSS2/positioning/abspos-inline-008.xht.ini +++ /dev/null @@ -1,2 +0,0 @@ -[abspos-inline-008.xht] - expected: FAIL diff --git a/tests/wpt/metadata-layout-2020/css/CSS2/positioning/toogle-abspos-on-relpos-inline-child.html.ini b/tests/wpt/metadata-layout-2020/css/CSS2/positioning/toogle-abspos-on-relpos-inline-child.html.ini deleted file mode 100644 index 928300474c1..00000000000 --- a/tests/wpt/metadata-layout-2020/css/CSS2/positioning/toogle-abspos-on-relpos-inline-child.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[toogle-abspos-on-relpos-inline-child.html] - expected: FAIL diff --git a/tests/wpt/metadata-layout-2020/css/css-transforms/preserve3d-button.html.ini b/tests/wpt/metadata-layout-2020/css/css-transforms/preserve3d-button.html.ini deleted file mode 100644 index 0c6784a80ed..00000000000 --- a/tests/wpt/metadata-layout-2020/css/css-transforms/preserve3d-button.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[preserve3d-button.html] - expected: FAIL diff --git a/tests/wpt/metadata-layout-2020/css/filter-effects/filtered-inline-is-container.html.ini b/tests/wpt/metadata-layout-2020/css/filter-effects/filtered-inline-is-container.html.ini deleted file mode 100644 index b41a8df9ac0..00000000000 --- a/tests/wpt/metadata-layout-2020/css/filter-effects/filtered-inline-is-container.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[filtered-inline-is-container.html] - expected: FAIL diff --git a/tests/wpt/metadata/2dcontext/fill-and-stroke-styles/2d.gradient.radial.inside3.html.ini b/tests/wpt/metadata/2dcontext/fill-and-stroke-styles/2d.gradient.radial.inside3.html.ini deleted file mode 100644 index c7e4cfa39c4..00000000000 --- a/tests/wpt/metadata/2dcontext/fill-and-stroke-styles/2d.gradient.radial.inside3.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[2d.gradient.radial.inside3.html] - [Canvas test: 2d.gradient.radial.inside3] - expected: FAIL - diff --git a/tests/wpt/metadata/2dcontext/line-styles/2d.line.cap.closed.html.ini b/tests/wpt/metadata/2dcontext/line-styles/2d.line.cap.closed.html.ini deleted file mode 100644 index cbad443c3c7..00000000000 --- a/tests/wpt/metadata/2dcontext/line-styles/2d.line.cap.closed.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[2d.line.cap.closed.html] - [Line caps are not drawn at the corners of an unclosed rectangle] - expected: FAIL - diff --git a/tests/wpt/metadata/2dcontext/text-styles/canvas_text_font_001.htm.ini b/tests/wpt/metadata/2dcontext/text-styles/canvas_text_font_001.htm.ini new file mode 100644 index 00000000000..7b5158316c0 --- /dev/null +++ b/tests/wpt/metadata/2dcontext/text-styles/canvas_text_font_001.htm.ini @@ -0,0 +1,2 @@ +[canvas_text_font_001.htm] + expected: FAIL diff --git a/tests/wpt/metadata/offscreen-canvas/fill-and-stroke-styles/2d.gradient.radial.inside3.html.ini b/tests/wpt/metadata/offscreen-canvas/fill-and-stroke-styles/2d.gradient.radial.inside3.html.ini deleted file mode 100644 index 0c5eea1b0c4..00000000000 --- a/tests/wpt/metadata/offscreen-canvas/fill-and-stroke-styles/2d.gradient.radial.inside3.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[2d.gradient.radial.inside3.html] - [OffscreenCanvas test: 2d.gradient.radial.inside3] - expected: FAIL - diff --git a/tests/wpt/metadata/offscreen-canvas/fill-and-stroke-styles/2d.gradient.radial.inside3.worker.js.ini b/tests/wpt/metadata/offscreen-canvas/fill-and-stroke-styles/2d.gradient.radial.inside3.worker.js.ini deleted file mode 100644 index 1a8e07fabbc..00000000000 --- a/tests/wpt/metadata/offscreen-canvas/fill-and-stroke-styles/2d.gradient.radial.inside3.worker.js.ini +++ /dev/null @@ -1,4 +0,0 @@ -[2d.gradient.radial.inside3.worker.html] - [2d] - expected: FAIL - diff --git a/tests/wpt/metadata/offscreen-canvas/line-styles/2d.line.cap.closed.html.ini b/tests/wpt/metadata/offscreen-canvas/line-styles/2d.line.cap.closed.html.ini deleted file mode 100644 index cbad443c3c7..00000000000 --- a/tests/wpt/metadata/offscreen-canvas/line-styles/2d.line.cap.closed.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[2d.line.cap.closed.html] - [Line caps are not drawn at the corners of an unclosed rectangle] - expected: FAIL - diff --git a/tests/wpt/metadata/offscreen-canvas/line-styles/2d.line.cap.closed.worker.js.ini b/tests/wpt/metadata/offscreen-canvas/line-styles/2d.line.cap.closed.worker.js.ini deleted file mode 100644 index 96690215a25..00000000000 --- a/tests/wpt/metadata/offscreen-canvas/line-styles/2d.line.cap.closed.worker.js.ini +++ /dev/null @@ -1,4 +0,0 @@ -[2d.line.cap.closed.worker.html] - [Line caps are not drawn at the corners of an unclosed rectangle] - expected: FAIL - diff --git a/tests/wpt/mozilla/meta-layout-2020/css/absolute_inline_containing_block_a.html.ini b/tests/wpt/mozilla/meta-layout-2020/css/absolute_inline_containing_block_a.html.ini deleted file mode 100644 index eb5471a558b..00000000000 --- a/tests/wpt/mozilla/meta-layout-2020/css/absolute_inline_containing_block_a.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[absolute_inline_containing_block_a.html] - expected: FAIL |