diff options
author | Utsav Oza <utsavoza96@gmail.com> | 2020-06-07 01:38:04 +0530 |
---|---|---|
committer | Utsav Oza <utsavoza96@gmail.com> | 2020-06-10 22:34:20 +0530 |
commit | 34d0c313dccc7e12b4409e10ec1f7ffae63e4528 (patch) | |
tree | a1bc30e7810cec87393b4e0485ec0c4d9fad752b /components/canvas | |
parent | c21fde375184c367f923b9e3776ba3adbe7f53dd (diff) | |
download | servo-34d0c313dccc7e12b4409e10ec1f7ffae63e4528.tar.gz servo-34d0c313dccc7e12b4409e10ec1f7ffae63e4528.zip |
Enable textAlign, textBaseline and direction attributes for canvas
Diffstat (limited to 'components/canvas')
-rw-r--r-- | components/canvas/Cargo.toml | 2 | ||||
-rw-r--r-- | components/canvas/canvas_data.rs | 153 | ||||
-rw-r--r-- | components/canvas/canvas_paint_thread.rs | 25 | ||||
-rw-r--r-- | components/canvas/raqote_backend.rs | 35 |
4 files changed, 165 insertions, 50 deletions
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<f64>, + font: &Font, + point_size: f32, + text: &str, + start: Point2D<f32>, pattern: Pattern, draw_options: &DrawOptions, ); @@ -370,6 +380,21 @@ pub enum Filter { Point, } +pub(crate) type CanvasFontContext = FontContext<FontCacheThread>; + +thread_local!(static FONT_CONTEXT: RefCell<Option<CanvasFontContext>> = RefCell::new(None)); + +pub(crate) fn with_thread_local_font_context<F, R>(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<dyn Backend>, drawtarget: Box<dyn GenericDrawTarget>, @@ -382,6 +407,7 @@ pub struct CanvasData<'a> { old_image_key: Option<webrender_api::ImageKey>, /// An old webrender image key that can be deleted when the current epoch ends. very_old_image_key: Option<webrender_api::ImageKey>, + font_cache_thread: Mutex<FontCacheThread>, _canvas_id: CanvasId, } @@ -395,6 +421,7 @@ impl<'a> CanvasData<'a> { webrender_api: Box<dyn WebrenderApi>, 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<f64>) { - // 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<f64>, + _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<f32>) { @@ -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<FontStyleStruct>, + pub text_align: TextAlign, + pub text_baseline: TextBaseline, } /// It writes an image to the destination target @@ -1245,3 +1302,45 @@ impl RectExt for Rect<u32> { 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::<Vec<FamilyName>>(); + 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<CanvasId, CanvasData<'a>>, next_canvas_id: CanvasId, webrender_api: Box<dyn WebrenderApi>, + font_cache_thread: FontCacheThread, } impl<'a> CanvasPaintThread<'a> { - fn new(webrender_api: Box<dyn WebrenderApi>) -> CanvasPaintThread<'a> { + fn new( + webrender_api: Box<dyn WebrenderApi>, + 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<dyn WebrenderApi + Send>, + font_cache_thread: FontCacheThread, ) -> (Sender<ConstellationCanvasMsg>, IpcSender<CanvasMsg>) { let (ipc_sender, ipc_receiver) = ipc::channel::<CanvasMsg>().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<f64>, + font: &Font, + point_size: f32, + text: &str, + start: Point2D<f32>, 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(), ); } |