Enable textAlign, textBaseline and direction attributes for canvas

This commit is contained in:
Utsav Oza 2020-06-07 01:38:04 +05:30
parent c21fde3751
commit 34d0c313dc
15 changed files with 486 additions and 64 deletions

3
Cargo.lock generated
View file

@ -505,6 +505,7 @@ dependencies = [
"euclid", "euclid",
"fnv", "fnv",
"font-kit", "font-kit",
"gfx",
"gleam 0.11.0", "gleam 0.11.0",
"half", "half",
"ipc-channel", "ipc-channel",
@ -513,6 +514,7 @@ dependencies = [
"num-traits", "num-traits",
"pixels", "pixels",
"raqote", "raqote",
"servo_arc",
"servo_config", "servo_config",
"sparkle", "sparkle",
"style", "style",
@ -5434,6 +5436,7 @@ dependencies = [
"encoding_rs", "encoding_rs",
"euclid", "euclid",
"fallible", "fallible",
"font-kit",
"fxhash", "fxhash",
"hashglobe", "hashglobe",
"html5ever", "html5ever",

View file

@ -24,6 +24,7 @@ cssparser = "0.27"
euclid = "0.20" euclid = "0.20"
font-kit = "0.7" font-kit = "0.7"
fnv = "1.0" fnv = "1.0"
gfx = { path = "../gfx" }
gleam = "0.11" gleam = "0.11"
half = "1" half = "1"
ipc-channel = "0.14" ipc-channel = "0.14"
@ -32,6 +33,7 @@ lyon_geom = "0.14"
num-traits = "0.2" num-traits = "0.2"
pixels = { path = "../pixels" } pixels = { path = "../pixels" }
raqote = { version = "0.8", features = ["text"] } raqote = { version = "0.8", features = ["text"] }
servo_arc = { path = "../servo_arc" }
servo_config = { path = "../config" } servo_config = { path = "../config" }
sparkle = "0.1.24" sparkle = "0.1.24"
style = { path = "../style" } style = { path = "../style" }

View file

@ -7,12 +7,22 @@ use crate::raqote_backend::Repetition;
use canvas_traits::canvas::*; use canvas_traits::canvas::*;
use cssparser::RGBA; use cssparser::RGBA;
use euclid::default::{Point2D, Rect, Size2D, Transform2D, Vector2D}; 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 ipc_channel::ipc::{IpcSender, IpcSharedMemory};
use num_traits::ToPrimitive; use num_traits::ToPrimitive;
use servo_arc::Arc as ServoArc;
use std::cell::RefCell;
#[allow(unused_imports)] #[allow(unused_imports)]
use std::marker::PhantomData; use std::marker::PhantomData;
use std::mem; use std::mem;
use std::sync::Arc; use std::sync::{Arc, Mutex};
use style::properties::style_structs::Font as FontStyleStruct; use style::properties::style_structs::Font as FontStyleStruct;
use webrender_api::units::RectExt as RectExt_; 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(&mut self, path: &Path, pattern: Pattern, draw_options: &DrawOptions);
fn fill_text( fn fill_text(
&mut self, &mut self,
text: String, font: &Font,
x: f64, point_size: f32,
y: f64, text: &str,
max_width: Option<f64>, start: Point2D<f32>,
pattern: Pattern, pattern: Pattern,
draw_options: &DrawOptions, draw_options: &DrawOptions,
); );
@ -370,6 +380,21 @@ pub enum Filter {
Point, 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> { pub struct CanvasData<'a> {
backend: Box<dyn Backend>, backend: Box<dyn Backend>,
drawtarget: Box<dyn GenericDrawTarget>, drawtarget: Box<dyn GenericDrawTarget>,
@ -382,6 +407,7 @@ pub struct CanvasData<'a> {
old_image_key: Option<webrender_api::ImageKey>, old_image_key: Option<webrender_api::ImageKey>,
/// An old webrender image key that can be deleted when the current epoch ends. /// An old webrender image key that can be deleted when the current epoch ends.
very_old_image_key: Option<webrender_api::ImageKey>, very_old_image_key: Option<webrender_api::ImageKey>,
font_cache_thread: Mutex<FontCacheThread>,
_canvas_id: CanvasId, _canvas_id: CanvasId,
} }
@ -395,6 +421,7 @@ impl<'a> CanvasData<'a> {
webrender_api: Box<dyn WebrenderApi>, webrender_api: Box<dyn WebrenderApi>,
antialias: AntialiasMode, antialias: AntialiasMode,
canvas_id: CanvasId, canvas_id: CanvasId,
font_cache_thread: FontCacheThread,
) -> CanvasData<'a> { ) -> CanvasData<'a> {
let backend = create_backend(); let backend = create_backend();
let draw_target = backend.create_drawtarget(size); let draw_target = backend.create_drawtarget(size);
@ -408,6 +435,7 @@ impl<'a> CanvasData<'a> {
image_key: None, image_key: None,
old_image_key: None, old_image_key: None,
very_old_image_key: None, very_old_image_key: None,
font_cache_thread: Mutex::new(font_cache_thread),
_canvas_id: canvas_id, _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>) { pub fn fill_text(
// 1. If maxWidth was provided but is less than or equal to zero or equal to NaN, &mut self,
// then return an empty array. text: String,
if max_width.map_or(false, |max_width| max_width <= 0.) { x: f64,
return; 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. // Step 3. Let font be the current font of target, as given by that object's font attribute.
let text = text let point_size = self
.chars() .state
.map(|c| match c { .font_style
' ' | '\t' | '\n' | '\r' | '\x0C' => '\x20', .as_ref()
_ => c, .map_or(10., |style| style.font_size.size().px());
}) let font_style = self.state.font_style.as_ref();
.collect(); let font = font_style.map_or_else(
|| load_system_font_from_style(font_style),
self.drawtarget.fill_text( |style| {
text, with_thread_local_font_context(&self, |font_context| {
x, let font_group = font_context.font_group(ServoArc::new(style.clone()));
y, let font = font_group.borrow_mut().first(font_context).expect("");
max_width, let font = font.borrow_mut();
self.state.fill_style.clone(), if let Some(bytes) = font.handle.template().bytes_if_in_memory() {
&self.state.draw_options, 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>) { pub fn fill_rect(&mut self, rect: &Rect<f32>) {
@ -1072,6 +1119,14 @@ impl<'a> CanvasData<'a> {
self.state.font_style = Some(font_style) 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 // https://html.spec.whatwg.org/multipage/#when-shadows-are-drawn
fn need_to_draw_shadow(&self) -> bool { fn need_to_draw_shadow(&self) -> bool {
self.backend.need_to_draw_shadow(&self.state.shadow_color) && self.backend.need_to_draw_shadow(&self.state.shadow_color) &&
@ -1164,6 +1219,8 @@ pub struct CanvasPaintState<'a> {
pub shadow_blur: f64, pub shadow_blur: f64,
pub shadow_color: Color, pub shadow_color: Color,
pub font_style: Option<FontStyleStruct>, pub font_style: Option<FontStyleStruct>,
pub text_align: TextAlign,
pub text_baseline: TextBaseline,
} }
/// It writes an image to the destination target /// It writes an image to the destination target
@ -1245,3 +1302,45 @@ impl RectExt for Rect<u32> {
self.cast() 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()
}

View file

@ -7,6 +7,7 @@ use canvas_traits::canvas::*;
use canvas_traits::ConstellationCanvasMsg; use canvas_traits::ConstellationCanvasMsg;
use crossbeam_channel::{select, unbounded, Sender}; use crossbeam_channel::{select, unbounded, Sender};
use euclid::default::Size2D; use euclid::default::Size2D;
use gfx::font_cache_thread::FontCacheThread;
use ipc_channel::ipc::{self, IpcSender}; use ipc_channel::ipc::{self, IpcSender};
use ipc_channel::router::ROUTER; use ipc_channel::router::ROUTER;
use std::borrow::ToOwned; use std::borrow::ToOwned;
@ -35,14 +36,19 @@ pub struct CanvasPaintThread<'a> {
canvases: HashMap<CanvasId, CanvasData<'a>>, canvases: HashMap<CanvasId, CanvasData<'a>>,
next_canvas_id: CanvasId, next_canvas_id: CanvasId,
webrender_api: Box<dyn WebrenderApi>, webrender_api: Box<dyn WebrenderApi>,
font_cache_thread: FontCacheThread,
} }
impl<'a> CanvasPaintThread<'a> { 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 { CanvasPaintThread {
canvases: HashMap::new(), canvases: HashMap::new(),
next_canvas_id: CanvasId(0), next_canvas_id: CanvasId(0),
webrender_api, webrender_api,
font_cache_thread,
} }
} }
@ -50,6 +56,7 @@ impl<'a> CanvasPaintThread<'a> {
/// communicate with it. /// communicate with it.
pub fn start( pub fn start(
webrender_api: Box<dyn WebrenderApi + Send>, webrender_api: Box<dyn WebrenderApi + Send>,
font_cache_thread: FontCacheThread,
) -> (Sender<ConstellationCanvasMsg>, IpcSender<CanvasMsg>) { ) -> (Sender<ConstellationCanvasMsg>, IpcSender<CanvasMsg>) {
let (ipc_sender, ipc_receiver) = ipc::channel::<CanvasMsg>().unwrap(); let (ipc_sender, ipc_receiver) = ipc::channel::<CanvasMsg>().unwrap();
let msg_receiver = ROUTER.route_ipc_receiver_to_new_crossbeam_receiver(ipc_receiver); let msg_receiver = ROUTER.route_ipc_receiver_to_new_crossbeam_receiver(ipc_receiver);
@ -57,7 +64,7 @@ impl<'a> CanvasPaintThread<'a> {
thread::Builder::new() thread::Builder::new()
.name("CanvasThread".to_owned()) .name("CanvasThread".to_owned())
.spawn(move || { .spawn(move || {
let mut canvas_paint_thread = CanvasPaintThread::new(webrender_api); let mut canvas_paint_thread = CanvasPaintThread::new(webrender_api, font_cache_thread);
loop { loop {
select! { select! {
recv(msg_receiver) -> msg => { recv(msg_receiver) -> msg => {
@ -118,6 +125,8 @@ impl<'a> CanvasPaintThread<'a> {
AntialiasMode::None AntialiasMode::None
}; };
let font_cache_thread = self.font_cache_thread.clone();
let canvas_id = self.next_canvas_id.clone(); let canvas_id = self.next_canvas_id.clone();
self.next_canvas_id.0 += 1; self.next_canvas_id.0 += 1;
@ -126,6 +135,7 @@ impl<'a> CanvasPaintThread<'a> {
self.webrender_api.clone(), self.webrender_api.clone(),
antialias, antialias,
canvas_id.clone(), canvas_id.clone(),
font_cache_thread,
); );
self.canvases.insert(canvas_id.clone(), canvas_data); 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) { fn process_canvas_2d_message(&mut self, message: Canvas2dMsg, canvas_id: CanvasId) {
match message { 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).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) => { Canvas2dMsg::FillRect(rect, style) => {
self.canvas(canvas_id).set_fill_style(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::SetShadowBlur(value) => self.canvas(canvas_id).set_shadow_blur(value),
Canvas2dMsg::SetShadowColor(color) => self.canvas(canvas_id).set_shadow_color(color), Canvas2dMsg::SetShadowColor(color) => self.canvas(canvas_id).set_shadow_color(color),
Canvas2dMsg::SetFont(font_style) => self.canvas(canvas_id).set_font(font_style), 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)
},
} }
} }

View file

@ -13,9 +13,7 @@ use canvas_traits::canvas::*;
use cssparser::RGBA; use cssparser::RGBA;
use euclid::default::{Point2D, Rect, Size2D, Transform2D, Vector2D}; use euclid::default::{Point2D, Rect, Size2D, Transform2D, Vector2D};
use euclid::Angle; use euclid::Angle;
use font_kit::family_name::FamilyName; use font_kit::font::Font;
use font_kit::properties::Properties;
use font_kit::source::SystemSource;
use lyon_geom::Arc; use lyon_geom::Arc;
use raqote::PathOp; use raqote::PathOp;
use std::marker::PhantomData; use std::marker::PhantomData;
@ -78,6 +76,9 @@ impl Backend for RaqoteBackend {
} }
impl<'a> CanvasPaintState<'a> { 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> { pub fn new(_antialias: AntialiasMode) -> CanvasPaintState<'a> {
let pattern = Pattern::Color(255, 0, 0, 0); let pattern = Pattern::Color(255, 0, 0, 0);
CanvasPaintState { CanvasPaintState {
@ -91,6 +92,8 @@ impl<'a> CanvasPaintState<'a> {
shadow_blur: 0.0, shadow_blur: 0.0,
shadow_color: Color::Raqote(raqote::SolidSource::from_unpremultiplied_argb(0, 0, 0, 0)), shadow_color: Color::Raqote(raqote::SolidSource::from_unpremultiplied_argb(0, 0, 0, 0)),
font_style: None, font_style: None,
text_align: Default::default(),
text_baseline: Default::default(),
} }
} }
} }
@ -520,26 +523,20 @@ impl GenericDrawTarget for raqote::DrawTarget {
fn fill_text( fn fill_text(
&mut self, &mut self,
text: String, font: &Font,
x: f64, point_size: f32,
y: f64, text: &str,
_max_width: Option<f64>, start: Point2D<f32>,
pattern: canvas_data::Pattern, 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( self.draw_text(
&font, font,
24., point_size,
&text, text,
Point2D::new(x as f32, y as f32), start,
&pattern.source(), &pattern.source(),
draw_options.as_raqote(), options.as_raqote(),
); );
} }

View file

@ -46,7 +46,7 @@ pub enum Canvas2dMsg {
ClosePath, ClosePath,
Ellipse(Point2D<f32>, f32, f32, f32, f32, f32, bool), Ellipse(Point2D<f32>, f32, f32, f32, f32, f32, bool),
Fill(FillOrStrokeStyle), Fill(FillOrStrokeStyle),
FillText(String, f64, f64, Option<f64>, FillOrStrokeStyle), FillText(String, f64, f64, Option<f64>, FillOrStrokeStyle, bool),
FillRect(Rect<f32>, FillOrStrokeStyle), FillRect(Rect<f32>, FillOrStrokeStyle),
GetImageData(Rect<u64>, Size2D<u64>, IpcBytesSender), GetImageData(Rect<u64>, Size2D<u64>, IpcBytesSender),
GetTransform(IpcSender<Transform2D<f32>>), GetTransform(IpcSender<Transform2D<f32>>),
@ -72,6 +72,8 @@ pub enum Canvas2dMsg {
SetShadowBlur(f64), SetShadowBlur(f64),
SetShadowColor(RGBA), SetShadowColor(RGBA),
SetFont(Font), SetFont(Font),
SetTextAlign(TextAlign),
SetTextBaseline(TextBaseline),
} }
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
@ -394,3 +396,91 @@ impl FromStr for CompositionOrBlending {
Err(()) 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<TextAlign, ()> {
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<TextBaseline, ()> {
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<Direction, ()> {
match string {
"ltr" => Ok(Direction::Ltr),
"rtl" => Ok(Direction::Rtl),
"inherit" => Ok(Direction::Inherit),
_ => Err(()),
}
}
}
impl Default for Direction {
fn default() -> Direction {
Direction::Inherit
}
}

View file

@ -3,10 +3,13 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::dom::bindings::cell::DomRefCell; 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::CanvasFillRule;
use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasImageSource; use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasImageSource;
use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasLineCap; use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasLineCap;
use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasLineJoin; 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::Bindings::ImageDataBinding::ImageDataMethods;
use crate::dom::bindings::codegen::UnionTypes::StringOrCanvasGradientOrCanvasPattern; use crate::dom::bindings::codegen::UnionTypes::StringOrCanvasGradientOrCanvasPattern;
use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; 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::paintworkletglobalscope::PaintWorkletGlobalScope;
use crate::dom::textmetrics::TextMetrics; use crate::dom::textmetrics::TextMetrics;
use crate::unpremultiplytable::UNPREMULTIPLY_TABLE; 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::{CompositionOrBlending, FillOrStrokeStyle, FillRule};
use canvas_traits::canvas::{LineCapStyle, LineJoinStyle, LinearGradientStyle}; use canvas_traits::canvas::{LineCapStyle, LineJoinStyle, LinearGradientStyle};
use canvas_traits::canvas::{RadialGradientStyle, RepetitionStyle}; use canvas_traits::canvas::{RadialGradientStyle, RepetitionStyle};
@ -91,9 +94,14 @@ pub(crate) struct CanvasContextState {
shadow_blur: f64, shadow_blur: f64,
shadow_color: RGBA, shadow_color: RGBA,
font_style: Option<Font>, font_style: Option<Font>,
text_align: TextAlign,
text_baseline: TextBaseline,
direction: Direction,
} }
impl CanvasContextState { impl CanvasContextState {
const DEFAULT_FONT_STYLE: &'static str = "10px sans-serif";
pub(crate) fn new() -> CanvasContextState { pub(crate) fn new() -> CanvasContextState {
let black = RGBA::new(0, 0, 0, 255); let black = RGBA::new(0, 0, 0, 255);
CanvasContextState { CanvasContextState {
@ -112,6 +120,9 @@ impl CanvasContextState {
shadow_blur: 0.0, shadow_blur: 0.0,
shadow_color: RGBA::transparent(), shadow_color: RGBA::transparent(),
font_style: None, 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 // https://html.spec.whatwg.org/multipage/#dom-context-2d-filltext
pub fn fill_text( pub fn fill_text(
&self, &self,
_canvas: Option<&HTMLCanvasElement>, canvas: Option<&HTMLCanvasElement>,
text: DOMString, text: DOMString,
x: f64, x: f64,
y: f64, y: f64,
@ -1007,9 +1018,19 @@ impl CanvasState {
if max_width.map_or(false, |max_width| !max_width.is_finite() || max_width <= 0.) { if max_width.map_or(false, |max_width| !max_width.is_finite() || max_width <= 0.) {
return; 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(); 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 // https://html.spec.whatwg.org/multipage/#textmetrics
@ -1040,7 +1061,7 @@ impl CanvasState {
// https://html.spec.whatwg.org/multipage/#dom-context-2d-font // https://html.spec.whatwg.org/multipage/#dom-context-2d-font
pub fn font(&self) -> DOMString { pub fn font(&self) -> DOMString {
self.state.borrow().font_style.as_ref().map_or_else( self.state.borrow().font_style.as_ref().map_or_else(
|| DOMString::from("10px sans-serif"), || CanvasContextState::DEFAULT_FONT_STYLE.into(),
|style| { |style| {
let mut result = String::new(); let mut result = String::new();
serialize_font(style, &mut result).unwrap(); 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 // https://html.spec.whatwg.org/multipage/#dom-context-2d-linewidth
pub fn line_width(&self) -> f64 { pub fn line_width(&self) -> f64 {
self.state.borrow().line_width self.state.borrow().line_width

View file

@ -47,7 +47,10 @@ use app_units::Au;
use canvas_traits::canvas::{ use canvas_traits::canvas::{
CanvasGradientStop, CanvasId, LinearGradientStyle, RadialGradientStyle, 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::WebGLVertexArrayId;
use canvas_traits::webgl::{ use canvas_traits::webgl::{
ActiveAttribInfo, ActiveUniformBlockInfo, ActiveUniformInfo, GlType, TexDataType, TexFormat, ActiveAttribInfo, ActiveUniformBlockInfo, ActiveUniformInfo, GlType, TexDataType, TexFormat,
@ -503,6 +506,7 @@ unsafe_no_jsmanaged_fields!(RGBA);
unsafe_no_jsmanaged_fields!(StorageType); unsafe_no_jsmanaged_fields!(StorageType);
unsafe_no_jsmanaged_fields!(CanvasGradientStop, LinearGradientStyle, RadialGradientStyle); unsafe_no_jsmanaged_fields!(CanvasGradientStop, LinearGradientStyle, RadialGradientStyle);
unsafe_no_jsmanaged_fields!(LineCapStyle, LineJoinStyle, CompositionOrBlending); unsafe_no_jsmanaged_fields!(LineCapStyle, LineJoinStyle, CompositionOrBlending);
unsafe_no_jsmanaged_fields!(TextAlign, TextBaseline, Direction);
unsafe_no_jsmanaged_fields!(RepetitionStyle); unsafe_no_jsmanaged_fields!(RepetitionStyle);
unsafe_no_jsmanaged_fields!(WebGLError, GLLimits, GlType); unsafe_no_jsmanaged_fields!(WebGLError, GLLimits, GlType);
unsafe_no_jsmanaged_fields!(TimeProfilerChan); unsafe_no_jsmanaged_fields!(TimeProfilerChan);

View file

@ -3,11 +3,14 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::canvas_state::CanvasState; 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::CanvasFillRule;
use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasImageSource; use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasImageSource;
use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasLineCap; use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasLineCap;
use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasLineJoin; use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasLineJoin;
use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasRenderingContext2DMethods; 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::codegen::UnionTypes::StringOrCanvasGradientOrCanvasPattern;
use crate::dom::bindings::error::{ErrorResult, Fallible}; use crate::dom::bindings::error::{ErrorResult, Fallible};
use crate::dom::bindings::num::Finite; use crate::dom::bindings::num::Finite;
@ -309,6 +312,36 @@ impl CanvasRenderingContext2DMethods for CanvasRenderingContext2D {
.set_font(self.canvas.as_ref().map(|c| &**c), value) .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 // https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage
fn DrawImage(&self, image: CanvasImageSource, dx: f64, dy: f64) -> ErrorResult { fn DrawImage(&self, image: CanvasImageSource, dx: f64, dy: f64) -> ErrorResult {
self.canvas_state self.canvas_state

View file

@ -3,10 +3,13 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::canvas_state::CanvasState; 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::CanvasFillRule;
use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasImageSource; use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasImageSource;
use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasLineCap; use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasLineCap;
use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasLineJoin; 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::Bindings::OffscreenCanvasRenderingContext2DBinding::OffscreenCanvasRenderingContext2DMethods;
use crate::dom::bindings::codegen::UnionTypes::StringOrCanvasGradientOrCanvasPattern; use crate::dom::bindings::codegen::UnionTypes::StringOrCanvasGradientOrCanvasPattern;
use crate::dom::bindings::error::ErrorResult; use crate::dom::bindings::error::ErrorResult;
@ -269,6 +272,36 @@ impl OffscreenCanvasRenderingContext2DMethods for OffscreenCanvasRenderingContex
.set_font(self.htmlcanvas.as_ref().map(|c| &**c), value) .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 // https://html.spec.whatwg.org/multipage/#dom-context-2d-linewidth
fn LineWidth(&self) -> f64 { fn LineWidth(&self) -> f64 {
self.canvas_state.line_width() self.canvas_state.line_width()

View file

@ -212,10 +212,10 @@ interface mixin CanvasPathDrawingStyles {
interface mixin CanvasTextDrawingStyles { interface mixin CanvasTextDrawingStyles {
// text // text
attribute DOMString font; // (default 10px sans-serif) attribute DOMString font; // (default 10px sans-serif)
//attribute CanvasTextAlign textAlign; // "start", "end", "left", "right", "center" (default: "start") attribute CanvasTextAlign textAlign; // "start", "end", "left", "right", "center" (default: "start")
//attribute CanvasTextBaseline textBaseline; // "top", "hanging", "middle", "alphabetic", attribute CanvasTextBaseline textBaseline; // "top", "hanging", "middle", "alphabetic",
// "ideographic", "bottom" (default: "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)] [Exposed=(PaintWorklet, Window, Worker)]

View file

@ -879,8 +879,13 @@ fn create_constellation(
Box::new(FontCacheWR(compositor_proxy.clone())), 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 { let initial_state = InitialConstellationState {
compositor_proxy: compositor_proxy.clone(), compositor_proxy,
embedder_proxy, embedder_proxy,
debugger_chan, debugger_chan,
devtools_chan, devtools_chan,
@ -899,9 +904,6 @@ fn create_constellation(
user_agent, user_agent,
}; };
let (canvas_chan, ipc_canvas_chan) =
CanvasPaintThread::start(Box::new(CanvasWebrenderApi(compositor_proxy)));
let constellation_chan = Constellation::< let constellation_chan = Constellation::<
script_layout_interface::message::Msg, script_layout_interface::message::Msg,
layout_thread::LayoutThread, layout_thread::LayoutThread,

View file

@ -39,6 +39,7 @@ derive_more = "0.99"
encoding_rs = { version = "0.8", optional = true } encoding_rs = { version = "0.8", optional = true }
euclid = "0.20" euclid = "0.20"
fallible = { path = "../fallible" } fallible = { path = "../fallible" }
font-kit = "0.7"
fxhash = "0.2" fxhash = "0.2"
hashglobe = { path = "../hashglobe" } hashglobe = { path = "../hashglobe" }
html5ever = { version = "0.25", optional = true } html5ever = { version = "0.25", optional = true }

View file

@ -40,6 +40,8 @@ extern crate debug_unreachable;
extern crate derive_more; extern crate derive_more;
extern crate euclid; extern crate euclid;
extern crate fallible; extern crate fallible;
#[cfg(feature = "servo")]
extern crate font_kit;
extern crate fxhash; extern crate fxhash;
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
#[macro_use] #[macro_use]

View file

@ -22,6 +22,12 @@ use crate::values::specified::length::{FontBaseSize, NoCalcLength};
use crate::values::CSSFloat; use crate::values::CSSFloat;
use crate::Atom; use crate::Atom;
use cssparser::{serialize_identifier, CssStringWriter, Parser}; 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")] #[cfg(feature = "gecko")]
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
use std::fmt::{self, Write}; use std::fmt::{self, Write};
@ -69,6 +75,13 @@ impl ToAnimatedValue for FontWeight {
} }
} }
#[cfg(feature = "servo")]
impl From<FontWeight> for FontKitFontWeight {
fn from(font_weight: FontWeight) -> Self {
FontKitFontWeight(font_weight.0)
}
}
#[derive( #[derive(
Animate, Animate,
Clone, 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")] #[cfg(feature = "servo")]
#[derive( #[derive(
Clone, Clone,
@ -943,6 +976,17 @@ impl ToCss for FontStyle {
} }
} }
#[cfg(feature = "servo")]
impl From<FontStyle> 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: /// A value for the font-stretch property per:
/// ///
/// https://drafts.csswg.org/css-fonts-4/#propdef-font-stretch /// https://drafts.csswg.org/css-fonts-4/#propdef-font-stretch
@ -965,6 +1009,13 @@ impl FontStretch {
} }
} }
#[cfg(feature = "servo")]
impl From<FontStretch> for FontKitFontStretch {
fn from(stretch: FontStretch) -> Self {
FontKitFontStretch(stretch.value())
}
}
impl ToAnimatedValue for FontStretch { impl ToAnimatedValue for FontStretch {
type AnimatedValue = Percentage; type AnimatedValue = Percentage;