aboutsummaryrefslogtreecommitdiffstats
path: root/components/canvas
diff options
context:
space:
mode:
authorUtsav Oza <utsavoza96@gmail.com>2020-06-07 01:38:04 +0530
committerUtsav Oza <utsavoza96@gmail.com>2020-06-10 22:34:20 +0530
commit34d0c313dccc7e12b4409e10ec1f7ffae63e4528 (patch)
treea1bc30e7810cec87393b4e0485ec0c4d9fad752b /components/canvas
parentc21fde375184c367f923b9e3776ba3adbe7f53dd (diff)
downloadservo-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.toml2
-rw-r--r--components/canvas/canvas_data.rs153
-rw-r--r--components/canvas/canvas_paint_thread.rs25
-rw-r--r--components/canvas/raqote_backend.rs35
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(),
);
}