diff --git a/Cargo.lock b/Cargo.lock index d080e4a7c39..e291d5cc7ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -505,6 +505,7 @@ dependencies = [ "euclid", "fnv", "font-kit", + "gfx", "gleam 0.11.0", "half", "ipc-channel", @@ -513,6 +514,7 @@ dependencies = [ "num-traits", "pixels", "raqote", + "servo_arc", "servo_config", "sparkle", "style", @@ -5434,6 +5436,7 @@ dependencies = [ "encoding_rs", "euclid", "fallible", + "font-kit", "fxhash", "hashglobe", "html5ever", diff --git a/components/canvas/Cargo.toml b/components/canvas/Cargo.toml index 3d7fa7f38e0..7285776ddec 100644 --- a/components/canvas/Cargo.toml +++ b/components/canvas/Cargo.toml @@ -24,6 +24,7 @@ cssparser = "0.27" euclid = "0.20" font-kit = "0.7" fnv = "1.0" +gfx = { path = "../gfx" } gleam = "0.11" half = "1" ipc-channel = "0.14" @@ -32,6 +33,7 @@ lyon_geom = "0.14" num-traits = "0.2" pixels = { path = "../pixels" } raqote = { version = "0.8", features = ["text"] } +servo_arc = { path = "../servo_arc" } servo_config = { path = "../config" } sparkle = "0.1.24" style = { path = "../style" } diff --git a/components/canvas/canvas_data.rs b/components/canvas/canvas_data.rs index c90fca964f7..a1617be42dc 100644 --- a/components/canvas/canvas_data.rs +++ b/components/canvas/canvas_data.rs @@ -7,12 +7,22 @@ use crate::raqote_backend::Repetition; use canvas_traits::canvas::*; use cssparser::RGBA; use euclid::default::{Point2D, Rect, Size2D, Transform2D, Vector2D}; +use euclid::point2; +use font_kit::family_name::FamilyName; +use font_kit::font::Font; +use font_kit::properties::Properties; +use font_kit::source::SystemSource; +use gfx::font::FontHandleMethods; +use gfx::font_cache_thread::FontCacheThread; +use gfx::font_context::FontContext; use ipc_channel::ipc::{IpcSender, IpcSharedMemory}; use num_traits::ToPrimitive; +use servo_arc::Arc as ServoArc; +use std::cell::RefCell; #[allow(unused_imports)] use std::marker::PhantomData; use std::mem; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use style::properties::style_structs::Font as FontStyleStruct; use webrender_api::units::RectExt as RectExt_; @@ -267,10 +277,10 @@ pub trait GenericDrawTarget { fn fill(&mut self, path: &Path, pattern: Pattern, draw_options: &DrawOptions); fn fill_text( &mut self, - text: String, - x: f64, - y: f64, - max_width: Option, + font: &Font, + point_size: f32, + text: &str, + start: Point2D, pattern: Pattern, draw_options: &DrawOptions, ); @@ -370,6 +380,21 @@ pub enum Filter { Point, } +pub(crate) type CanvasFontContext = FontContext; + +thread_local!(static FONT_CONTEXT: RefCell> = RefCell::new(None)); + +pub(crate) fn with_thread_local_font_context(canvas_data: &CanvasData, f: F) -> R +where + F: FnOnce(&mut CanvasFontContext) -> R, +{ + FONT_CONTEXT.with(|font_context| { + f(font_context.borrow_mut().get_or_insert_with(|| { + FontContext::new(canvas_data.font_cache_thread.lock().unwrap().clone()) + })) + }) +} + pub struct CanvasData<'a> { backend: Box, drawtarget: Box, @@ -382,6 +407,7 @@ pub struct CanvasData<'a> { old_image_key: Option, /// An old webrender image key that can be deleted when the current epoch ends. very_old_image_key: Option, + font_cache_thread: Mutex, _canvas_id: CanvasId, } @@ -395,6 +421,7 @@ impl<'a> CanvasData<'a> { webrender_api: Box, antialias: AntialiasMode, canvas_id: CanvasId, + font_cache_thread: FontCacheThread, ) -> CanvasData<'a> { let backend = create_backend(); let draw_target = backend.create_drawtarget(size); @@ -408,6 +435,7 @@ impl<'a> CanvasData<'a> { image_key: None, old_image_key: None, very_old_image_key: None, + font_cache_thread: Mutex::new(font_cache_thread), _canvas_id: canvas_id, } } @@ -466,30 +494,49 @@ impl<'a> CanvasData<'a> { } } - pub fn fill_text(&mut self, text: String, x: f64, y: f64, max_width: Option) { - // 1. If maxWidth was provided but is less than or equal to zero or equal to NaN, - // then return an empty array. - if max_width.map_or(false, |max_width| max_width <= 0.) { - return; - } + pub fn fill_text( + &mut self, + text: String, + x: f64, + y: f64, + _max_width: Option, + _is_rtl: bool, + ) { + // Step 2. Replace all ASCII whitespace in text with U+0020 SPACE characters. + let text = replace_ascii_whitespace(text); - // 2. Replace all ASCII whitespace in text with U+0020 SPACE characters. - let text = text - .chars() - .map(|c| match c { - ' ' | '\t' | '\n' | '\r' | '\x0C' => '\x20', - _ => c, - }) - .collect(); - - self.drawtarget.fill_text( - text, - x, - y, - max_width, - self.state.fill_style.clone(), - &self.state.draw_options, + // Step 3. Let font be the current font of target, as given by that object's font attribute. + let point_size = self + .state + .font_style + .as_ref() + .map_or(10., |style| style.font_size.size().px()); + let font_style = self.state.font_style.as_ref(); + let font = font_style.map_or_else( + || load_system_font_from_style(font_style), + |style| { + with_thread_local_font_context(&self, |font_context| { + let font_group = font_context.font_group(ServoArc::new(style.clone())); + let font = font_group.borrow_mut().first(font_context).expect(""); + let font = font.borrow_mut(); + if let Some(bytes) = font.handle.template().bytes_if_in_memory() { + Font::from_bytes(Arc::new(bytes), 0) + .unwrap_or_else(|_| load_system_font_from_style(Some(style))) + } else { + load_system_font_from_style(Some(style)) + } + }) + }, ); + let start = point2(x as f32, y as f32); + + // TODO: Process bidi text + + // Step 8. + let fill_style = self.state.fill_style.clone(); + let draw_options = &self.state.draw_options; + self.drawtarget + .fill_text(&font, point_size, &text, start, fill_style, draw_options); } pub fn fill_rect(&mut self, rect: &Rect) { @@ -1072,6 +1119,14 @@ impl<'a> CanvasData<'a> { self.state.font_style = Some(font_style) } + pub fn set_text_align(&mut self, text_align: TextAlign) { + self.state.text_align = text_align; + } + + pub fn set_text_baseline(&mut self, text_baseline: TextBaseline) { + self.state.text_baseline = text_baseline; + } + // https://html.spec.whatwg.org/multipage/#when-shadows-are-drawn fn need_to_draw_shadow(&self) -> bool { self.backend.need_to_draw_shadow(&self.state.shadow_color) && @@ -1164,6 +1219,8 @@ pub struct CanvasPaintState<'a> { pub shadow_blur: f64, pub shadow_color: Color, pub font_style: Option, + pub text_align: TextAlign, + pub text_baseline: TextBaseline, } /// It writes an image to the destination target @@ -1245,3 +1302,45 @@ impl RectExt for Rect { self.cast() } } + +fn load_system_font_from_style(font_style: Option<&FontStyleStruct>) -> Font { + let mut properties = Properties::new(); + let style = match font_style { + Some(style) => style, + None => return load_default_system_fallback_font(&properties), + }; + let family_names = style + .font_family + .families + .iter() + .map(|family_name| family_name.into()) + .collect::>(); + let properties = properties + .style(style.font_style.into()) + .weight(style.font_weight.into()) + .stretch(style.font_stretch.into()); + let font_handle = match SystemSource::new().select_best_match(&family_names, &properties) { + Ok(handle) => handle, + Err(_) => return load_default_system_fallback_font(&properties), + }; + font_handle + .load() + .unwrap_or_else(|_| load_default_system_fallback_font(&properties)) +} + +fn load_default_system_fallback_font(properties: &Properties) -> Font { + SystemSource::new() + .select_best_match(&[FamilyName::SansSerif], properties) + .unwrap() + .load() + .unwrap() +} + +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 9d9f8350e1d..763e7d04a18 100644 --- a/components/canvas/canvas_paint_thread.rs +++ b/components/canvas/canvas_paint_thread.rs @@ -7,6 +7,7 @@ use canvas_traits::canvas::*; use canvas_traits::ConstellationCanvasMsg; use crossbeam_channel::{select, unbounded, Sender}; use euclid::default::Size2D; +use gfx::font_cache_thread::FontCacheThread; use ipc_channel::ipc::{self, IpcSender}; use ipc_channel::router::ROUTER; use std::borrow::ToOwned; @@ -35,14 +36,19 @@ pub struct CanvasPaintThread<'a> { canvases: HashMap>, next_canvas_id: CanvasId, webrender_api: Box, + font_cache_thread: FontCacheThread, } impl<'a> CanvasPaintThread<'a> { - fn new(webrender_api: Box) -> CanvasPaintThread<'a> { + fn new( + webrender_api: Box, + font_cache_thread: FontCacheThread, + ) -> CanvasPaintThread<'a> { CanvasPaintThread { canvases: HashMap::new(), next_canvas_id: CanvasId(0), webrender_api, + font_cache_thread, } } @@ -50,6 +56,7 @@ impl<'a> CanvasPaintThread<'a> { /// communicate with it. pub fn start( webrender_api: Box, + font_cache_thread: FontCacheThread, ) -> (Sender, IpcSender) { let (ipc_sender, ipc_receiver) = ipc::channel::().unwrap(); let msg_receiver = ROUTER.route_ipc_receiver_to_new_crossbeam_receiver(ipc_receiver); @@ -57,7 +64,7 @@ impl<'a> CanvasPaintThread<'a> { thread::Builder::new() .name("CanvasThread".to_owned()) .spawn(move || { - let mut canvas_paint_thread = CanvasPaintThread::new(webrender_api); + let mut canvas_paint_thread = CanvasPaintThread::new(webrender_api, font_cache_thread); loop { select! { recv(msg_receiver) -> msg => { @@ -118,6 +125,8 @@ impl<'a> CanvasPaintThread<'a> { AntialiasMode::None }; + let font_cache_thread = self.font_cache_thread.clone(); + let canvas_id = self.next_canvas_id.clone(); self.next_canvas_id.0 += 1; @@ -126,6 +135,7 @@ impl<'a> CanvasPaintThread<'a> { self.webrender_api.clone(), antialias, canvas_id.clone(), + font_cache_thread, ); self.canvases.insert(canvas_id.clone(), canvas_data); @@ -134,9 +144,10 @@ impl<'a> CanvasPaintThread<'a> { fn process_canvas_2d_message(&mut self, message: Canvas2dMsg, canvas_id: CanvasId) { match message { - Canvas2dMsg::FillText(text, x, y, max_width, style) => { + Canvas2dMsg::FillText(text, x, y, max_width, style, is_rtl) => { self.canvas(canvas_id).set_fill_style(style); - self.canvas(canvas_id).fill_text(text, x, y, max_width); + self.canvas(canvas_id) + .fill_text(text, x, y, max_width, is_rtl); }, Canvas2dMsg::FillRect(rect, style) => { self.canvas(canvas_id).set_fill_style(style); @@ -248,6 +259,12 @@ impl<'a> CanvasPaintThread<'a> { Canvas2dMsg::SetShadowBlur(value) => self.canvas(canvas_id).set_shadow_blur(value), Canvas2dMsg::SetShadowColor(color) => self.canvas(canvas_id).set_shadow_color(color), Canvas2dMsg::SetFont(font_style) => self.canvas(canvas_id).set_font(font_style), + Canvas2dMsg::SetTextAlign(text_align) => { + self.canvas(canvas_id).set_text_align(text_align) + }, + Canvas2dMsg::SetTextBaseline(text_baseline) => { + self.canvas(canvas_id).set_text_baseline(text_baseline) + }, } } diff --git a/components/canvas/raqote_backend.rs b/components/canvas/raqote_backend.rs index 3c30c0f1a19..5bab459b326 100644 --- a/components/canvas/raqote_backend.rs +++ b/components/canvas/raqote_backend.rs @@ -13,9 +13,7 @@ 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 font_kit::font::Font; use lyon_geom::Arc; use raqote::PathOp; use std::marker::PhantomData; @@ -78,6 +76,9 @@ impl Backend for RaqoteBackend { } impl<'a> CanvasPaintState<'a> { + pub const HANGING_BASELINE_DEFAULT: f32 = 0.8; // fraction of ascent + pub const IDEOGRAPHIC_BASELINE_DEFAULT: f32 = 0.5; // fraction descent + pub fn new(_antialias: AntialiasMode) -> CanvasPaintState<'a> { let pattern = Pattern::Color(255, 0, 0, 0); CanvasPaintState { @@ -91,6 +92,8 @@ impl<'a> CanvasPaintState<'a> { shadow_blur: 0.0, shadow_color: Color::Raqote(raqote::SolidSource::from_unpremultiplied_argb(0, 0, 0, 0)), font_style: None, + text_align: Default::default(), + text_baseline: Default::default(), } } } @@ -520,26 +523,20 @@ impl GenericDrawTarget for raqote::DrawTarget { fn fill_text( &mut self, - text: String, - x: f64, - y: f64, - _max_width: Option, + font: &Font, + point_size: f32, + text: &str, + start: Point2D, pattern: canvas_data::Pattern, - draw_options: &DrawOptions, + options: &DrawOptions, ) { - let font = SystemSource::new() - .select_best_match(&[FamilyName::SansSerif], &Properties::new()) - .unwrap() - .load() - .unwrap(); - self.draw_text( - &font, - 24., - &text, - Point2D::new(x as f32, y as f32), + font, + point_size, + text, + start, &pattern.source(), - draw_options.as_raqote(), + options.as_raqote(), ); } diff --git a/components/canvas_traits/canvas.rs b/components/canvas_traits/canvas.rs index 1750e7d8e99..836285863e8 100644 --- a/components/canvas_traits/canvas.rs +++ b/components/canvas_traits/canvas.rs @@ -46,7 +46,7 @@ pub enum Canvas2dMsg { ClosePath, Ellipse(Point2D, f32, f32, f32, f32, f32, bool), Fill(FillOrStrokeStyle), - FillText(String, f64, f64, Option, FillOrStrokeStyle), + FillText(String, f64, f64, Option, FillOrStrokeStyle, bool), FillRect(Rect, FillOrStrokeStyle), GetImageData(Rect, Size2D, IpcBytesSender), GetTransform(IpcSender>), @@ -72,6 +72,8 @@ pub enum Canvas2dMsg { SetShadowBlur(f64), SetShadowColor(RGBA), SetFont(Font), + SetTextAlign(TextAlign), + SetTextBaseline(TextBaseline), } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -394,3 +396,91 @@ impl FromStr for CompositionOrBlending { Err(()) } } + +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] +pub enum TextAlign { + Start, + End, + Left, + Right, + Center, +} + +impl FromStr for TextAlign { + type Err = (); + + fn from_str(string: &str) -> Result { + match string { + "start" => Ok(TextAlign::Start), + "end" => Ok(TextAlign::End), + "left" => Ok(TextAlign::Left), + "right" => Ok(TextAlign::Right), + "center" => Ok(TextAlign::Center), + _ => Err(()), + } + } +} + +impl Default for TextAlign { + fn default() -> TextAlign { + TextAlign::Start + } +} + +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] +pub enum TextBaseline { + Top, + Hanging, + Middle, + Alphabetic, + Ideographic, + Bottom, +} + +impl FromStr for TextBaseline { + type Err = (); + + fn from_str(string: &str) -> Result { + match string { + "top" => Ok(TextBaseline::Top), + "hanging" => Ok(TextBaseline::Hanging), + "middle" => Ok(TextBaseline::Middle), + "alphabetic" => Ok(TextBaseline::Alphabetic), + "ideographic" => Ok(TextBaseline::Ideographic), + "bottom" => Ok(TextBaseline::Bottom), + _ => Err(()), + } + } +} + +impl Default for TextBaseline { + fn default() -> TextBaseline { + TextBaseline::Alphabetic + } +} + +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] +pub enum Direction { + Ltr, + Rtl, + Inherit, +} + +impl FromStr for Direction { + type Err = (); + + fn from_str(string: &str) -> Result { + match string { + "ltr" => Ok(Direction::Ltr), + "rtl" => Ok(Direction::Rtl), + "inherit" => Ok(Direction::Inherit), + _ => Err(()), + } + } +} + +impl Default for Direction { + fn default() -> Direction { + Direction::Inherit + } +} diff --git a/components/script/canvas_state.rs b/components/script/canvas_state.rs index 9685d719157..fa6728c34e1 100644 --- a/components/script/canvas_state.rs +++ b/components/script/canvas_state.rs @@ -3,10 +3,13 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasDirection; use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasFillRule; use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasImageSource; use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasLineCap; use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasLineJoin; +use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasTextAlign; +use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasTextBaseline; use crate::dom::bindings::codegen::Bindings::ImageDataBinding::ImageDataMethods; use crate::dom::bindings::codegen::UnionTypes::StringOrCanvasGradientOrCanvasPattern; use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; @@ -27,7 +30,7 @@ use crate::dom::offscreencanvas::{OffscreenCanvas, OffscreenCanvasContext}; use crate::dom::paintworkletglobalscope::PaintWorkletGlobalScope; use crate::dom::textmetrics::TextMetrics; use crate::unpremultiplytable::UNPREMULTIPLY_TABLE; -use canvas_traits::canvas::{Canvas2dMsg, CanvasId, CanvasMsg}; +use canvas_traits::canvas::{Canvas2dMsg, CanvasId, CanvasMsg, Direction, TextAlign, TextBaseline}; use canvas_traits::canvas::{CompositionOrBlending, FillOrStrokeStyle, FillRule}; use canvas_traits::canvas::{LineCapStyle, LineJoinStyle, LinearGradientStyle}; use canvas_traits::canvas::{RadialGradientStyle, RepetitionStyle}; @@ -91,9 +94,14 @@ pub(crate) struct CanvasContextState { shadow_blur: f64, shadow_color: RGBA, font_style: Option, + text_align: TextAlign, + text_baseline: TextBaseline, + direction: Direction, } impl CanvasContextState { + const DEFAULT_FONT_STYLE: &'static str = "10px sans-serif"; + pub(crate) fn new() -> CanvasContextState { let black = RGBA::new(0, 0, 0, 255); CanvasContextState { @@ -112,6 +120,9 @@ impl CanvasContextState { shadow_blur: 0.0, shadow_color: RGBA::transparent(), font_style: None, + text_align: Default::default(), + text_baseline: Default::default(), + direction: Default::default(), } } } @@ -995,7 +1006,7 @@ impl CanvasState { // https://html.spec.whatwg.org/multipage/#dom-context-2d-filltext pub fn fill_text( &self, - _canvas: Option<&HTMLCanvasElement>, + canvas: Option<&HTMLCanvasElement>, text: DOMString, x: f64, y: f64, @@ -1007,9 +1018,19 @@ impl CanvasState { if max_width.map_or(false, |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()) + } + let is_rtl = false; // TODO: resolve is_rtl 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, max_width, style)); + self.send_canvas_2d_msg(Canvas2dMsg::FillText( + text.into(), + x, + y, + max_width, + style, + is_rtl, + )); } // https://html.spec.whatwg.org/multipage/#textmetrics @@ -1040,7 +1061,7 @@ impl CanvasState { // https://html.spec.whatwg.org/multipage/#dom-context-2d-font pub fn font(&self) -> DOMString { self.state.borrow().font_style.as_ref().map_or_else( - || DOMString::from("10px sans-serif"), + || CanvasContextState::DEFAULT_FONT_STYLE.into(), |style| { let mut result = String::new(); serialize_font(style, &mut result).unwrap(); @@ -1049,6 +1070,73 @@ impl CanvasState { ) } + // https://html.spec.whatwg.org/multipage/#dom-context-2d-textalign + pub 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, + } + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-textalign + pub 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.send_canvas_2d_msg(Canvas2dMsg::SetTextAlign(text_align)); + } + + pub 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, + } + } + + pub 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.send_canvas_2d_msg(Canvas2dMsg::SetTextBaseline(text_baseline)); + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-direction + pub fn direction(&self) -> CanvasDirection { + match self.state.borrow().direction { + Direction::Ltr => CanvasDirection::Ltr, + Direction::Rtl => CanvasDirection::Rtl, + Direction::Inherit => CanvasDirection::Inherit, + } + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-direction + pub 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; + } + // https://html.spec.whatwg.org/multipage/#dom-context-2d-linewidth pub fn line_width(&self) -> f64 { self.state.borrow().line_width diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index f80a7e57c93..9518a2c0989 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -47,7 +47,10 @@ use app_units::Au; use canvas_traits::canvas::{ CanvasGradientStop, CanvasId, LinearGradientStyle, RadialGradientStyle, }; -use canvas_traits::canvas::{CompositionOrBlending, LineCapStyle, LineJoinStyle, RepetitionStyle}; +use canvas_traits::canvas::{ + CompositionOrBlending, Direction, LineCapStyle, LineJoinStyle, RepetitionStyle, TextAlign, + TextBaseline, +}; use canvas_traits::webgl::WebGLVertexArrayId; use canvas_traits::webgl::{ ActiveAttribInfo, ActiveUniformBlockInfo, ActiveUniformInfo, GlType, TexDataType, TexFormat, @@ -503,6 +506,7 @@ unsafe_no_jsmanaged_fields!(RGBA); unsafe_no_jsmanaged_fields!(StorageType); unsafe_no_jsmanaged_fields!(CanvasGradientStop, LinearGradientStyle, RadialGradientStyle); unsafe_no_jsmanaged_fields!(LineCapStyle, LineJoinStyle, CompositionOrBlending); +unsafe_no_jsmanaged_fields!(TextAlign, TextBaseline, Direction); unsafe_no_jsmanaged_fields!(RepetitionStyle); unsafe_no_jsmanaged_fields!(WebGLError, GLLimits, GlType); unsafe_no_jsmanaged_fields!(TimeProfilerChan); diff --git a/components/script/dom/canvasrenderingcontext2d.rs b/components/script/dom/canvasrenderingcontext2d.rs index c8838fe283e..3ebf8f130d9 100644 --- a/components/script/dom/canvasrenderingcontext2d.rs +++ b/components/script/dom/canvasrenderingcontext2d.rs @@ -3,11 +3,14 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use crate::canvas_state::CanvasState; +use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasDirection; use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasFillRule; use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasImageSource; use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasLineCap; use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasLineJoin; use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasRenderingContext2DMethods; +use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasTextAlign; +use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasTextBaseline; use crate::dom::bindings::codegen::UnionTypes::StringOrCanvasGradientOrCanvasPattern; use crate::dom::bindings::error::{ErrorResult, Fallible}; use crate::dom::bindings::num::Finite; @@ -309,6 +312,36 @@ impl CanvasRenderingContext2DMethods for CanvasRenderingContext2D { .set_font(self.canvas.as_ref().map(|c| &**c), value) } + // https://html.spec.whatwg.org/multipage/#dom-context-2d-textalign + fn TextAlign(&self) -> CanvasTextAlign { + self.canvas_state.text_align() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-textalign + fn SetTextAlign(&self, value: CanvasTextAlign) { + self.canvas_state.set_text_align(value) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-textbaseline + fn TextBaseline(&self) -> CanvasTextBaseline { + self.canvas_state.text_baseline() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-textbaseline + fn SetTextBaseline(&self, value: CanvasTextBaseline) { + self.canvas_state.set_text_baseline(value) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-direction + fn Direction(&self) -> CanvasDirection { + self.canvas_state.direction() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-direction + fn SetDirection(&self, value: CanvasDirection) { + self.canvas_state.set_direction(value) + } + // https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage fn DrawImage(&self, image: CanvasImageSource, dx: f64, dy: f64) -> ErrorResult { self.canvas_state diff --git a/components/script/dom/offscreencanvasrenderingcontext2d.rs b/components/script/dom/offscreencanvasrenderingcontext2d.rs index ee9c010c391..8c3deb001f4 100644 --- a/components/script/dom/offscreencanvasrenderingcontext2d.rs +++ b/components/script/dom/offscreencanvasrenderingcontext2d.rs @@ -3,10 +3,13 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use crate::canvas_state::CanvasState; +use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasDirection; use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasFillRule; use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasImageSource; use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasLineCap; use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasLineJoin; +use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasTextAlign; +use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasTextBaseline; use crate::dom::bindings::codegen::Bindings::OffscreenCanvasRenderingContext2DBinding::OffscreenCanvasRenderingContext2DMethods; use crate::dom::bindings::codegen::UnionTypes::StringOrCanvasGradientOrCanvasPattern; use crate::dom::bindings::error::ErrorResult; @@ -269,6 +272,36 @@ impl OffscreenCanvasRenderingContext2DMethods for OffscreenCanvasRenderingContex .set_font(self.htmlcanvas.as_ref().map(|c| &**c), value) } + // https://html.spec.whatwg.org/multipage/#dom-context-2d-textalign + fn TextAlign(&self) -> CanvasTextAlign { + self.canvas_state.text_align() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-textalign + fn SetTextAlign(&self, value: CanvasTextAlign) { + self.canvas_state.set_text_align(value) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-textbaseline + fn TextBaseline(&self) -> CanvasTextBaseline { + self.canvas_state.text_baseline() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-textbaseline + fn SetTextBaseline(&self, value: CanvasTextBaseline) { + self.canvas_state.set_text_baseline(value) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-direction + fn Direction(&self) -> CanvasDirection { + self.canvas_state.direction() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-direction + fn SetDirection(&self, value: CanvasDirection) { + self.canvas_state.set_direction(value) + } + // https://html.spec.whatwg.org/multipage/#dom-context-2d-linewidth fn LineWidth(&self) -> f64 { self.canvas_state.line_width() diff --git a/components/script/dom/webidls/CanvasRenderingContext2D.webidl b/components/script/dom/webidls/CanvasRenderingContext2D.webidl index 2267984ce2f..62669f4c2ba 100644 --- a/components/script/dom/webidls/CanvasRenderingContext2D.webidl +++ b/components/script/dom/webidls/CanvasRenderingContext2D.webidl @@ -212,10 +212,10 @@ interface mixin CanvasPathDrawingStyles { interface mixin CanvasTextDrawingStyles { // text attribute DOMString font; // (default 10px sans-serif) - //attribute CanvasTextAlign textAlign; // "start", "end", "left", "right", "center" (default: "start") - //attribute CanvasTextBaseline textBaseline; // "top", "hanging", "middle", "alphabetic", + attribute CanvasTextAlign textAlign; // "start", "end", "left", "right", "center" (default: "start") + attribute CanvasTextBaseline textBaseline; // "top", "hanging", "middle", "alphabetic", // "ideographic", "bottom" (default: "alphabetic") - //attribute CanvasDirection direction; // "ltr", "rtl", "inherit" (default: "inherit") + attribute CanvasDirection direction; // "ltr", "rtl", "inherit" (default: "inherit") }; [Exposed=(PaintWorklet, Window, Worker)] diff --git a/components/servo/lib.rs b/components/servo/lib.rs index 25fdd2261b4..58304e33600 100644 --- a/components/servo/lib.rs +++ b/components/servo/lib.rs @@ -879,8 +879,13 @@ fn create_constellation( Box::new(FontCacheWR(compositor_proxy.clone())), ); + let (canvas_chan, ipc_canvas_chan) = CanvasPaintThread::start( + Box::new(CanvasWebrenderApi(compositor_proxy.clone())), + font_cache_thread.clone(), + ); + let initial_state = InitialConstellationState { - compositor_proxy: compositor_proxy.clone(), + compositor_proxy, embedder_proxy, debugger_chan, devtools_chan, @@ -899,9 +904,6 @@ fn create_constellation( user_agent, }; - let (canvas_chan, ipc_canvas_chan) = - CanvasPaintThread::start(Box::new(CanvasWebrenderApi(compositor_proxy))); - let constellation_chan = Constellation::< script_layout_interface::message::Msg, layout_thread::LayoutThread, diff --git a/components/style/Cargo.toml b/components/style/Cargo.toml index bf66228c8fb..956a2c3f3d7 100644 --- a/components/style/Cargo.toml +++ b/components/style/Cargo.toml @@ -39,6 +39,7 @@ derive_more = "0.99" encoding_rs = { version = "0.8", optional = true } euclid = "0.20" fallible = { path = "../fallible" } +font-kit = "0.7" fxhash = "0.2" hashglobe = { path = "../hashglobe" } html5ever = { version = "0.25", optional = true } diff --git a/components/style/lib.rs b/components/style/lib.rs index 52b77a7507e..7ff69ed3c7b 100644 --- a/components/style/lib.rs +++ b/components/style/lib.rs @@ -40,6 +40,8 @@ extern crate debug_unreachable; extern crate derive_more; extern crate euclid; extern crate fallible; +#[cfg(feature = "servo")] +extern crate font_kit; extern crate fxhash; #[cfg(feature = "gecko")] #[macro_use] diff --git a/components/style/values/computed/font.rs b/components/style/values/computed/font.rs index cc1b7f67d79..68f60c02e14 100644 --- a/components/style/values/computed/font.rs +++ b/components/style/values/computed/font.rs @@ -22,6 +22,12 @@ use crate::values::specified::length::{FontBaseSize, NoCalcLength}; use crate::values::CSSFloat; use crate::Atom; use cssparser::{serialize_identifier, CssStringWriter, Parser}; +#[cfg(feature = "servo")] +use font_kit::family_name::FamilyName as FontKitFamilyName; +#[cfg(feature = "servo")] +use font_kit::properties::{ + Stretch as FontKitFontStretch, Style as FontKitFontStyle, Weight as FontKitFontWeight, +}; #[cfg(feature = "gecko")] use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use std::fmt::{self, Write}; @@ -69,6 +75,13 @@ impl ToAnimatedValue for FontWeight { } } +#[cfg(feature = "servo")] +impl From for FontKitFontWeight { + fn from(font_weight: FontWeight) -> Self { + FontKitFontWeight(font_weight.0) + } +} + #[derive( Animate, Clone, @@ -445,6 +458,26 @@ impl SingleFontFamily { } } +#[cfg(feature = "servo")] +impl From<&SingleFontFamily> for FontKitFamilyName { + fn from(font_family: &SingleFontFamily) -> Self { + match font_family { + SingleFontFamily::FamilyName(family_name) => { + FontKitFamilyName::Title(family_name.to_css_string()) + }, + SingleFontFamily::Generic(GenericFontFamily::Serif) => FontKitFamilyName::Serif, + SingleFontFamily::Generic(GenericFontFamily::SansSerif) => FontKitFamilyName::SansSerif, + SingleFontFamily::Generic(GenericFontFamily::Monospace) => FontKitFamilyName::Monospace, + SingleFontFamily::Generic(GenericFontFamily::Fantasy) => FontKitFamilyName::Fantasy, + SingleFontFamily::Generic(GenericFontFamily::Cursive) => FontKitFamilyName::Cursive, + SingleFontFamily::Generic(family_name) => { + warn!("unsupported font family name: {:?}", family_name); + FontKitFamilyName::SansSerif + }, + } + } +} + #[cfg(feature = "servo")] #[derive( Clone, @@ -943,6 +976,17 @@ impl ToCss for FontStyle { } } +#[cfg(feature = "servo")] +impl From for FontKitFontStyle { + fn from(font_style: FontStyle) -> Self { + match font_style { + FontStyle::Normal => FontKitFontStyle::Normal, + FontStyle::Italic => FontKitFontStyle::Italic, + FontStyle::Oblique(_) => FontKitFontStyle::Oblique, + } + } +} + /// A value for the font-stretch property per: /// /// https://drafts.csswg.org/css-fonts-4/#propdef-font-stretch @@ -965,6 +1009,13 @@ impl FontStretch { } } +#[cfg(feature = "servo")] +impl From for FontKitFontStretch { + fn from(stretch: FontStretch) -> Self { + FontKitFontStretch(stretch.value()) + } +} + impl ToAnimatedValue for FontStretch { type AnimatedValue = Percentage;