Skip to content

Commit

Permalink
Enable textAlign, textBaseline and direction attributes for canvas
Browse files Browse the repository at this point in the history
  • Loading branch information
utsavoza committed Jun 10, 2020
1 parent c21fde3 commit 34d0c31
Show file tree
Hide file tree
Showing 15 changed files with 486 additions and 64 deletions.
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions components/canvas/Cargo.toml
Expand Up @@ -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"
Expand All @@ -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" }
Expand Down
153 changes: 126 additions & 27 deletions components/canvas/canvas_data.rs
Expand Up @@ -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_;

Expand Down Expand Up @@ -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,
);
Expand Down Expand Up @@ -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>,
Expand All @@ -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,
}

Expand All @@ -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);
Expand All @@ -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,
}
}
Expand Down Expand Up @@ -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>) {
Expand Down Expand Up @@ -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) &&
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
}
25 changes: 21 additions & 4 deletions components/canvas/canvas_paint_thread.rs
Expand Up @@ -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;
Expand Down Expand Up @@ -35,29 +36,35 @@ 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,
}
}

/// Creates a new `CanvasPaintThread` and returns an `IpcSender` to
/// 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);
let (create_sender, create_receiver) = unbounded();
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 => {
Expand Down Expand Up @@ -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;

Expand All @@ -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);

Expand All @@ -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);
Expand Down Expand Up @@ -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)
},
}
}

Expand Down

0 comments on commit 34d0c31

Please sign in to comment.