canvas: Remove the raqote backend (#38962)

In general, `raqote` is essentially umaintained and has issues with
quality (for instance text rendering has lots of issues) and removing it
finally lets us remove our dependency on `font-kit`. Although,
`vello_cpu` performance is not yet equal to raqote, rendering quality is
a lot better. It's expected that `vello` and `vello_cpu` performance
will keep improving.

Testing: This is covered by existing WPT tests.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Martin Robinson 2025-08-27 05:19:27 -07:00 committed by GitHub
parent 0089e652c5
commit de69040e47
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 5 additions and 982 deletions

99
Cargo.lock generated
View file

@ -1143,7 +1143,6 @@ dependencies = [
"crossbeam-channel",
"cssparser",
"euclid",
"font-kit",
"fonts",
"futures-intrusive",
"ipc-channel",
@ -1154,7 +1153,6 @@ dependencies = [
"pixels",
"pollster",
"range",
"raqote",
"servo-tracing",
"servo_arc",
"servo_config",
@ -2677,12 +2675,6 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
[[package]]
name = "float-ord"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d"
[[package]]
name = "fnv"
version = "1.0.7"
@ -2695,31 +2687,6 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "font-kit"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c7e611d49285d4c4b2e1727b72cf05353558885cc5252f93707b845dfcaf3d3"
dependencies = [
"bitflags 2.9.3",
"byteorder",
"core-foundation 0.9.4",
"core-graphics",
"core-text",
"dirs",
"dwrote",
"float-ord",
"freetype-sys",
"lazy_static",
"libc",
"log",
"pathfinder_geometry",
"pathfinder_simd",
"walkdir",
"winapi",
"yeslogic-fontconfig-sys",
]
[[package]]
name = "font-types"
version = "0.9.0"
@ -5166,17 +5133,6 @@ dependencies = [
"imgref",
]
[[package]]
name = "lyon_geom"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8af69edc087272df438b3ee436c4bb6d7c04aa8af665cfd398feae627dbd8570"
dependencies = [
"arrayvec",
"euclid",
"num-traits",
]
[[package]]
name = "mac"
version = "0.1.1"
@ -6402,25 +6358,6 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pathfinder_geometry"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3"
dependencies = [
"log",
"pathfinder_simd",
]
[[package]]
name = "pathfinder_simd"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf9027960355bf3afff9841918474a81a5f972ac6d226d518060bba758b5ad57"
dependencies = [
"rustc_version",
]
[[package]]
name = "peek-poke"
version = "0.3.0"
@ -6980,21 +6917,6 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d6831663a5098ea164f89cff59c6284e95f4e3c76ce9848d4529f5ccca9bde"
[[package]]
name = "raqote"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "324990858d2a5df9ccd30b2e8b474030bd503297378a3fc0944c37af00eb8903"
dependencies = [
"euclid",
"font-kit",
"lyon_geom",
"pathfinder_geometry",
"png",
"sw-composite",
"typed-arena",
]
[[package]]
name = "rav1e"
version = "0.7.1"
@ -7260,15 +7182,6 @@ version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "rustc_version"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"semver",
]
[[package]]
name = "rustix"
version = "0.38.44"
@ -8623,12 +8536,6 @@ dependencies = [
"siphasher",
]
[[package]]
name = "sw-composite"
version = "0.7.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ac8fb7895b4afa060ad731a32860db8755da3449a47e796d5ecf758db2671d4"
[[package]]
name = "swapper"
version = "0.1.0"
@ -9238,12 +9145,6 @@ dependencies = [
"utf-8",
]
[[package]]
name = "typed-arena"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a"
[[package]]
name = "typeid"
version = "1.0.3"

View file

@ -14,7 +14,6 @@ path = "lib.rs"
[features]
vello = ["dep:vello", "dep:pollster", "dep:futures-intrusive", "dep:peniko"]
vello_cpu = ["dep:vello_cpu", "dep:peniko"]
raqote = ["dep:raqote", "dep:font-kit"]
tracing = ["dep:tracing"]
[dependencies]
@ -25,7 +24,6 @@ compositing_traits = { workspace = true }
crossbeam-channel = { workspace = true }
cssparser = { workspace = true }
euclid = { workspace = true }
font-kit = { version = "0.14", optional = true }
fonts = { path = "../fonts" }
ipc-channel = { workspace = true }
kurbo = { workspace = true }
@ -34,7 +32,6 @@ net_traits = { workspace = true }
peniko = { workspace = true, optional = true }
pixels = { path = "../pixels" }
range = { path = "../range" }
raqote = { version = "0.8.5", optional = true }
servo_arc = { workspace = true }
stylo = { workspace = true }
unicode-script = { workspace = true }

View file

@ -12,8 +12,8 @@ use webrender_api::ImageDescriptor;
use crate::canvas_data::{Filter, TextRun};
// This defines required methods for a DrawTarget (currently only implemented for raqote). The
// prototypes are derived from the now-removed Azure backend's methods.
// This defines required methods for a DrawTarget. The prototypes are derived from the now-removed
// Azure backend's methods.
pub(crate) trait GenericDrawTarget {
type SourceSurface;

View file

@ -169,7 +169,6 @@ impl<DrawTarget: GenericDrawTarget> CanvasData<DrawTarget> {
Size2D::new(dest_rect.size.width as f32, dest_rect.size.height as f32),
);
// TODO(pylbrecht) pass another closure for raqote
self.draw_with_shadow(
&rect,
shadow_options,
@ -411,8 +410,7 @@ impl<DrawTarget: GenericDrawTarget> CanvasData<DrawTarget> {
let mut current_text_run_start_index = 0;
for (index, character) in text.char_indices() {
// TODO: This should ultimately handle emoji variation selectors, but raqote does not yet
// have support for color glyphs.
// TODO: This should ultimately handle emoji variation selectors.
let script = Script::from(character);
let font = font_group.find_by_codepoint(&self.font_context, character, None, None);

View file

@ -295,8 +295,6 @@ impl CanvasPaintThread {
#[allow(clippy::large_enum_variant)]
enum Canvas {
#[cfg(feature = "raqote")]
Raqote(CanvasData<raqote::DrawTarget>),
#[cfg(feature = "vello")]
Vello(CanvasData<crate::vello_backend::VelloDrawTarget>),
#[cfg(feature = "vello_cpu")]
@ -319,12 +317,6 @@ impl Canvas {
compositor_api,
font_context,
))),
#[cfg(feature = "raqote")]
"" | "auto" | "raqote" => Some(Self::Raqote(CanvasData::new(
size,
compositor_api,
font_context,
))),
#[cfg(feature = "vello")]
"" | "auto" | "vello" => Some(Self::Vello(CanvasData::new(
size,
@ -340,8 +332,6 @@ impl Canvas {
fn image_key(&self) -> ImageKey {
match self {
#[cfg(feature = "raqote")]
Canvas::Raqote(canvas_data) => canvas_data.image_key(),
#[cfg(feature = "vello")]
Canvas::Vello(canvas_data) => canvas_data.image_key(),
#[cfg(feature = "vello_cpu")]
@ -351,8 +341,6 @@ impl Canvas {
fn pop_clips(&mut self, clips: usize) {
match self {
#[cfg(feature = "raqote")]
Canvas::Raqote(canvas_data) => canvas_data.pop_clips(clips),
#[cfg(feature = "vello")]
Canvas::Vello(canvas_data) => canvas_data.pop_clips(clips),
#[cfg(feature = "vello_cpu")]
@ -375,19 +363,6 @@ impl Canvas {
transform: Transform2D<f64>,
) {
match self {
#[cfg(feature = "raqote")]
Canvas::Raqote(canvas_data) => canvas_data.fill_text(
text,
x,
y,
max_width,
is_rtl,
style,
text_options,
shadow_options,
composition_options,
transform,
),
#[cfg(feature = "vello")]
Canvas::Vello(canvas_data) => canvas_data.fill_text(
text,
@ -426,10 +401,6 @@ impl Canvas {
transform: Transform2D<f64>,
) {
match self {
#[cfg(feature = "raqote")]
Canvas::Raqote(canvas_data) => {
canvas_data.fill_rect(rect, style, shadow_options, composition_options, transform)
},
#[cfg(feature = "vello")]
Canvas::Vello(canvas_data) => {
canvas_data.fill_rect(rect, style, shadow_options, composition_options, transform)
@ -451,15 +422,6 @@ impl Canvas {
transform: Transform2D<f64>,
) {
match self {
#[cfg(feature = "raqote")]
Canvas::Raqote(canvas_data) => canvas_data.stroke_rect(
rect,
style,
line_options,
shadow_options,
composition_options,
transform,
),
#[cfg(feature = "vello")]
Canvas::Vello(canvas_data) => canvas_data.stroke_rect(
rect,
@ -491,15 +453,6 @@ impl Canvas {
transform: Transform2D<f64>,
) {
match self {
#[cfg(feature = "raqote")]
Canvas::Raqote(canvas_data) => canvas_data.fill_path(
path,
fill_rule,
style,
shadow_options,
composition_options,
transform,
),
#[cfg(feature = "vello")]
Canvas::Vello(canvas_data) => canvas_data.fill_path(
path,
@ -531,15 +484,6 @@ impl Canvas {
transform: Transform2D<f64>,
) {
match self {
#[cfg(feature = "raqote")]
Canvas::Raqote(canvas_data) => canvas_data.stroke_path(
path,
style,
line_options,
shadow_options,
composition_options,
transform,
),
#[cfg(feature = "vello")]
Canvas::Vello(canvas_data) => canvas_data.stroke_path(
path,
@ -563,8 +507,6 @@ impl Canvas {
fn clear_rect(&mut self, rect: &Rect<f32>, transform: Transform2D<f64>) {
match self {
#[cfg(feature = "raqote")]
Canvas::Raqote(canvas_data) => canvas_data.clear_rect(rect, transform),
#[cfg(feature = "vello")]
Canvas::Vello(canvas_data) => canvas_data.clear_rect(rect, transform),
#[cfg(feature = "vello_cpu")]
@ -584,16 +526,6 @@ impl Canvas {
transform: Transform2D<f64>,
) {
match self {
#[cfg(feature = "raqote")]
Canvas::Raqote(canvas_data) => canvas_data.draw_image(
snapshot,
dest_rect,
source_rect,
smoothing_enabled,
shadow_options,
composition_options,
transform,
),
#[cfg(feature = "vello")]
Canvas::Vello(canvas_data) => canvas_data.draw_image(
snapshot,
@ -619,8 +551,6 @@ impl Canvas {
fn read_pixels(&mut self, read_rect: Option<Rect<u32>>) -> Snapshot {
match self {
#[cfg(feature = "raqote")]
Canvas::Raqote(canvas_data) => canvas_data.read_pixels(read_rect),
#[cfg(feature = "vello")]
Canvas::Vello(canvas_data) => canvas_data.read_pixels(read_rect),
#[cfg(feature = "vello_cpu")]
@ -630,8 +560,6 @@ impl Canvas {
fn measure_text(&mut self, text: String, text_options: TextOptions) -> TextMetrics {
match self {
#[cfg(feature = "raqote")]
Canvas::Raqote(canvas_data) => canvas_data.measure_text(text, text_options),
#[cfg(feature = "vello")]
Canvas::Vello(canvas_data) => canvas_data.measure_text(text, text_options),
#[cfg(feature = "vello_cpu")]
@ -641,8 +569,6 @@ impl Canvas {
fn clip_path(&mut self, path: &Path, fill_rule: FillRule, transform: Transform2D<f64>) {
match self {
#[cfg(feature = "raqote")]
Canvas::Raqote(canvas_data) => canvas_data.clip_path(path, fill_rule, transform),
#[cfg(feature = "vello")]
Canvas::Vello(canvas_data) => canvas_data.clip_path(path, fill_rule, transform),
#[cfg(feature = "vello_cpu")]
@ -652,8 +578,6 @@ impl Canvas {
fn put_image_data(&mut self, snapshot: Snapshot, rect: Rect<u32>) {
match self {
#[cfg(feature = "raqote")]
Canvas::Raqote(canvas_data) => canvas_data.put_image_data(snapshot, rect),
#[cfg(feature = "vello")]
Canvas::Vello(canvas_data) => canvas_data.put_image_data(snapshot, rect),
#[cfg(feature = "vello_cpu")]
@ -663,8 +587,6 @@ impl Canvas {
fn update_image_rendering(&mut self) {
match self {
#[cfg(feature = "raqote")]
Canvas::Raqote(canvas_data) => canvas_data.update_image_rendering(),
#[cfg(feature = "vello")]
Canvas::Vello(canvas_data) => canvas_data.update_image_rendering(),
#[cfg(feature = "vello_cpu")]
@ -674,8 +596,6 @@ impl Canvas {
fn recreate(&mut self, size: Option<Size2D<u64>>) {
match self {
#[cfg(feature = "raqote")]
Canvas::Raqote(canvas_data) => canvas_data.recreate(size),
#[cfg(feature = "vello")]
Canvas::Vello(canvas_data) => canvas_data.recreate(size),
#[cfg(feature = "vello_cpu")]

View file

@ -6,9 +6,6 @@
mod backend;
#[cfg(feature = "raqote")]
mod raqote_backend;
#[cfg(any(feature = "vello", feature = "vello_cpu"))]
mod peniko_conversions;

View file

@ -1,780 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use canvas_traits::canvas::*;
use compositing_traits::SerializableImageData;
use cssparser::color::clamp_unit_f32;
use euclid::default::{Point2D, Rect, Size2D, Transform2D};
use font_kit::font::Font;
use fonts::{ByteIndex, FontIdentifier, FontTemplateRefMethods};
use ipc_channel::ipc::IpcSharedMemory;
use log::warn;
use pixels::{Snapshot, SnapshotAlphaMode, SnapshotPixelFormat};
use range::Range;
use raqote::{DrawOptions, PathBuilder, StrokeStyle};
use style::color::AbsoluteColor;
use webrender_api::{ImageDescriptor, ImageDescriptorFlags, ImageFormat};
use crate::backend::GenericDrawTarget;
use crate::canvas_data::{Filter, TextRun};
thread_local! {
/// The shared font cache used by all canvases that render on a thread. It would be nicer
/// to have a global cache, but it looks like font-kit uses a per-thread FreeType, so
/// in order to ensure that fonts are particular to a thread we have to make our own
/// cache thread local as well.
static SHARED_FONT_CACHE: RefCell<HashMap<FontIdentifier, Font>> = RefCell::default();
}
#[derive(Clone)]
pub enum Pattern {
// argb
Color(u8, u8, u8, u8),
LinearGradient(LinearGradientPattern),
RadialGradient(RadialGradientPattern),
Surface(SurfacePattern),
}
#[derive(Clone)]
pub struct LinearGradientPattern {
gradient: raqote::Gradient,
start: Point2D<f32>,
end: Point2D<f32>,
}
impl LinearGradientPattern {
fn new(start: Point2D<f32>, end: Point2D<f32>, stops: Vec<raqote::GradientStop>) -> Self {
LinearGradientPattern {
gradient: raqote::Gradient { stops },
start,
end,
}
}
}
#[derive(Clone)]
pub struct RadialGradientPattern {
gradient: raqote::Gradient,
center1: Point2D<f32>,
radius1: f32,
center2: Point2D<f32>,
radius2: f32,
}
impl RadialGradientPattern {
fn new(
center1: Point2D<f32>,
radius1: f32,
center2: Point2D<f32>,
radius2: f32,
stops: Vec<raqote::GradientStop>,
) -> Self {
RadialGradientPattern {
gradient: raqote::Gradient { stops },
center1,
radius1,
center2,
radius2,
}
}
}
#[derive(Clone)]
pub struct SurfacePattern {
image: Snapshot,
filter: raqote::FilterMode,
extend: raqote::ExtendMode,
transform: Transform2D<f32>,
}
impl SurfacePattern {
fn new(
image: Snapshot,
filter: raqote::FilterMode,
repeat: Repetition,
transform: Transform2D<f32>,
) -> Self {
let extend = match repeat {
Repetition::NoRepeat => raqote::ExtendMode::Pad,
Repetition::RepeatX | Repetition::RepeatY | Repetition::Repeat => {
raqote::ExtendMode::Repeat
},
};
SurfacePattern {
image,
filter,
extend,
transform,
}
}
}
#[derive(Clone, Copy, Debug)]
pub enum Repetition {
Repeat,
RepeatX,
RepeatY,
NoRepeat,
}
impl Repetition {
fn from_xy(repeat_x: bool, repeat_y: bool) -> Self {
if repeat_x && repeat_y {
Repetition::Repeat
} else if repeat_x {
Repetition::RepeatX
} else if repeat_y {
Repetition::RepeatY
} else {
Repetition::NoRepeat
}
}
}
pub fn source(pattern: &Pattern) -> raqote::Source<'_> {
match pattern {
Pattern::Color(a, r, g, b) => raqote::Source::Solid(
raqote::SolidSource::from_unpremultiplied_argb(*a, *r, *g, *b),
),
Pattern::LinearGradient(pattern) => raqote::Source::new_linear_gradient(
pattern.gradient.clone(),
pattern.start,
pattern.end,
raqote::Spread::Pad,
),
Pattern::RadialGradient(pattern) => raqote::Source::new_two_circle_radial_gradient(
pattern.gradient.clone(),
pattern.center1,
pattern.radius1,
pattern.center2,
pattern.radius2,
raqote::Spread::Pad,
),
Pattern::Surface(pattern) => raqote::Source::Image(
raqote::Image {
width: pattern.image.size().width as i32,
height: pattern.image.size().height as i32,
data: bytemuck::cast_slice(pattern.image.as_raw_bytes()),
},
pattern.extend,
pattern.filter,
pattern.transform,
),
}
}
fn create_gradient_stops(gradient_stops: Vec<CanvasGradientStop>) -> Vec<raqote::GradientStop> {
let mut stops = gradient_stops
.into_iter()
.map(|item| item.to_raqote())
.collect::<Vec<raqote::GradientStop>>();
// https://www.w3.org/html/test/results/2dcontext/annotated-spec/canvas.html#testrefs.2d.gradient.interpolate.overlap
stops.sort_by(|a, b| a.position.partial_cmp(&b.position).unwrap());
stops
}
impl GenericDrawTarget for raqote::DrawTarget {
type SourceSurface = Vec<u32>; // TODO: See if we can avoid the alloc (probably?)
fn new(size: Size2D<u32>) -> Self {
raqote::DrawTarget::new(size.width as i32, size.height as i32)
}
fn clear_rect(&mut self, rect: &Rect<f32>, transform: Transform2D<f64>) {
<Self as GenericDrawTarget>::fill_rect(
self,
rect,
FillOrStrokeStyle::Color(AbsoluteColor::TRANSPARENT_BLACK),
CompositionOptions {
alpha: 1.0,
composition_operation: CompositionOrBlending::Composition(CompositionStyle::Clear),
},
transform,
);
}
fn copy_surface(
&mut self,
surface: Self::SourceSurface,
source: Rect<i32>,
destination: Point2D<i32>,
) {
let dt = raqote::DrawTarget::from_vec(source.size.width, source.size.height, surface);
raqote::DrawTarget::copy_surface(self, &dt, source.to_box2d(), destination);
}
fn create_similar_draw_target(&self, size: &Size2D<i32>) -> Self {
raqote::DrawTarget::new(size.width, size.height)
}
fn create_source_surface_from_data(&self, data: Snapshot) -> Option<Self::SourceSurface> {
Some(
bytemuck::try_cast_vec(
data.to_vec(
Some(SnapshotAlphaMode::Transparent {
premultiplied: true,
}),
Some(SnapshotPixelFormat::BGRA),
)
.0,
)
.unwrap_or_else(|(_, surface)| bytemuck::pod_collect_to_vec(&surface)),
)
}
fn draw_surface(
&mut self,
surface: Self::SourceSurface,
dest: Rect<f64>,
src: Rect<f64>,
filter: Filter,
composition_options: CompositionOptions,
transform: Transform2D<f64>,
) {
let paint_transform =
raqote::Transform::translation(-dest.origin.x as f32, -dest.origin.y as f32)
.then_scale(
src.size.width as f32 / dest.size.width as f32,
src.size.height as f32 / dest.size.height as f32,
);
self.set_transform(&transform.cast());
let dest = dest.cast();
let mut pb = raqote::PathBuilder::new();
pb.rect(
dest.origin.x,
dest.origin.y,
dest.size.width,
dest.size.height,
);
let size = src.size.cast();
fill_draw_target(
self,
draw_options(composition_options),
&raqote::Source::Image(
raqote::Image {
width: size.width,
height: size.height,
data: &surface,
},
raqote::ExtendMode::Pad,
filter.to_raqote(),
paint_transform,
),
pb.finish(),
);
}
fn draw_surface_with_shadow(
&self,
_surface: Self::SourceSurface,
_dest: &Point2D<f32>,
_shadow_options: ShadowOptions,
_composition_options: CompositionOptions,
) {
warn!("no support for drawing shadows");
}
fn fill(
&mut self,
path: &canvas_traits::canvas::Path,
fill_rule: FillRule,
style: FillOrStrokeStyle,
composition_options: CompositionOptions,
transform: Transform2D<f64>,
) {
self.set_transform(&transform.cast());
let draw_options = draw_options(composition_options);
let pattern = style.to_raqote_pattern();
let mut path = to_path(path);
path.winding = match fill_rule {
FillRule::Nonzero => raqote::Winding::NonZero,
FillRule::Evenodd => raqote::Winding::EvenOdd,
};
fill_draw_target(self, draw_options, &source(&pattern), path);
}
fn fill_text(
&mut self,
text_runs: Vec<TextRun>,
start: Point2D<f32>,
style: FillOrStrokeStyle,
composition_options: CompositionOptions,
transform: Transform2D<f64>,
) {
self.set_transform(&transform.cast());
let draw_options = draw_options(composition_options);
let pattern = style.to_raqote_pattern();
let mut advance = 0.;
for run in text_runs.iter() {
let mut positions = Vec::new();
let glyphs = &run.glyphs;
let ids: Vec<_> = glyphs
.iter_glyphs_for_byte_range(&Range::new(ByteIndex(0), glyphs.len()))
.map(|glyph| {
let glyph_offset = glyph.offset().unwrap_or(Point2D::zero());
positions.push(Point2D::new(
advance + start.x + glyph_offset.x.to_f32_px(),
start.y + glyph_offset.y.to_f32_px(),
));
advance += glyph.advance().to_f32_px();
glyph.id()
})
.collect();
// TODO: raqote uses font-kit to rasterize glyphs, but font-kit fails an assertion when
// using color bitmap fonts in the FreeType backend. For now, simply do not render these
// type of fonts.
if run.font.has_color_bitmap_or_colr_table() {
continue;
}
let template = &run.font.template;
SHARED_FONT_CACHE.with(|font_cache| {
let identifier = template.identifier();
if !font_cache.borrow().contains_key(&identifier) {
let Ok(font_data_and_index) = run.font.font_data_and_index() else {
return;
};
let data = std::sync::Arc::new(font_data_and_index.data.as_ref().to_vec());
let Ok(font) = Font::from_bytes(data, font_data_and_index.index) else {
return;
};
font_cache.borrow_mut().insert(identifier.clone(), font);
}
let font_cache = font_cache.borrow();
let Some(font) = font_cache.get(&identifier) else {
return;
};
self.draw_glyphs(
font,
run.font.descriptor.pt_size.to_f32_px(),
&ids,
&positions,
&source(&pattern),
&draw_options,
);
})
}
}
fn fill_rect(
&mut self,
rect: &Rect<f32>,
style: FillOrStrokeStyle,
composition_options: CompositionOptions,
transform: Transform2D<f64>,
) {
let rect = rect.cast();
let mut pb = canvas_traits::canvas::Path::new();
pb.rect(
rect.origin.x,
rect.origin.y,
rect.size.width,
rect.size.height,
);
<Self as GenericDrawTarget>::fill(
self,
&pb,
FillRule::Nonzero,
style,
composition_options,
transform,
);
}
fn get_size(&self) -> Size2D<i32> {
Size2D::new(self.width(), self.height())
}
fn pop_clip(&mut self) {
self.pop_clip();
}
fn push_clip(
&mut self,
path: &canvas_traits::canvas::Path,
fill_rule: FillRule,
transform: Transform2D<f64>,
) {
self.set_transform(&transform.cast());
let mut path = to_path(path);
path.winding = match fill_rule {
FillRule::Nonzero => raqote::Winding::NonZero,
FillRule::Evenodd => raqote::Winding::EvenOdd,
};
self.push_clip(&path);
}
fn push_clip_rect(&mut self, rect: &Rect<i32>) {
self.push_clip_rect(rect.to_box2d());
}
fn surface(&mut self) -> Self::SourceSurface {
self.get_data().to_vec()
}
fn stroke(
&mut self,
path: &canvas_traits::canvas::Path,
style: FillOrStrokeStyle,
line_options: LineOptions,
composition_options: CompositionOptions,
transform: Transform2D<f64>,
) {
let pattern = style.to_raqote_pattern();
let options = draw_options(composition_options);
self.set_transform(&transform.cast());
self.stroke(
&to_path(path),
&source(&pattern),
&line_options.to_raqote_style(),
&options,
);
}
fn stroke_rect(
&mut self,
rect: &Rect<f32>,
style: FillOrStrokeStyle,
line_options: LineOptions,
composition_options: CompositionOptions,
transform: Transform2D<f64>,
) {
self.set_transform(&transform.cast());
let pattern = style.to_raqote_pattern();
let options = draw_options(composition_options);
let mut pb = raqote::PathBuilder::new();
pb.rect(
rect.origin.x,
rect.origin.y,
rect.size.width,
rect.size.height,
);
self.stroke(
&pb.finish(),
&source(&pattern),
&line_options.to_raqote_style(),
&options,
);
}
fn image_descriptor_and_serializable_data(
&mut self,
) -> (
webrender_api::ImageDescriptor,
compositing_traits::SerializableImageData,
) {
let descriptor = ImageDescriptor {
size: self.get_size().cast_unit(),
stride: None,
format: ImageFormat::BGRA8,
offset: 0,
flags: ImageDescriptorFlags::empty(),
};
let data = SerializableImageData::Raw(IpcSharedMemory::from_bytes(self.get_data_u8()));
(descriptor, data)
}
fn snapshot(&mut self) -> Snapshot {
Snapshot::from_vec(
self.get_size().cast(),
SnapshotPixelFormat::BGRA,
SnapshotAlphaMode::Transparent {
premultiplied: true,
},
self.get_data_u8().to_vec(),
)
}
}
fn fill_draw_target(
draw_target: &mut raqote::DrawTarget,
draw_options: DrawOptions,
source: &raqote::Source<'_>,
path: raqote::Path,
) {
match draw_options.blend_mode {
raqote::BlendMode::Src => {
draw_target.clear(raqote::SolidSource::from_unpremultiplied_argb(0, 0, 0, 0));
draw_target.fill(&path, source, &draw_options);
},
raqote::BlendMode::Clear |
raqote::BlendMode::SrcAtop |
raqote::BlendMode::DstOut |
raqote::BlendMode::Add |
raqote::BlendMode::Xor |
raqote::BlendMode::DstOver |
raqote::BlendMode::SrcOver => {
draw_target.fill(&path, source, &draw_options);
},
raqote::BlendMode::SrcIn |
raqote::BlendMode::SrcOut |
raqote::BlendMode::DstIn |
raqote::BlendMode::DstAtop => {
let mut options = draw_options;
draw_target.push_layer_with_blend(1., options.blend_mode);
options.blend_mode = raqote::BlendMode::SrcOver;
draw_target.fill(&path, source, &options);
draw_target.pop_layer();
},
_ => warn!("unrecognized blend mode: {:?}", draw_options.blend_mode),
}
}
impl Filter {
fn to_raqote(self) -> raqote::FilterMode {
match self {
Filter::Bilinear => raqote::FilterMode::Bilinear,
Filter::Nearest => raqote::FilterMode::Nearest,
}
}
}
pub(crate) struct Path(Cell<raqote::PathBuilder>);
impl From<raqote::PathBuilder> for Path {
fn from(path_builder: raqote::PathBuilder) -> Self {
Self(Cell::new(path_builder))
}
}
impl From<&Path> for raqote::Path {
fn from(path: &Path) -> Self {
path.clone().0.into_inner().finish()
}
}
impl Clone for Path {
fn clone(&self) -> Self {
let path_builder = self.0.replace(raqote::PathBuilder::new());
let path = path_builder.finish();
self.0.set(path.clone().into());
Self(Cell::new(path.into()))
}
}
pub trait ToRaqoteStyle {
type Target;
fn to_raqote_style(self) -> Self::Target;
}
impl ToRaqoteStyle for LineOptions {
type Target = StrokeStyle;
fn to_raqote_style(self) -> Self::Target {
let LineOptions {
width,
cap_style,
join_style,
miter_limit,
dash,
dash_offset,
} = self;
StrokeStyle {
width: width as f32,
cap: cap_style.to_raqote_style(),
join: join_style.to_raqote_style(),
miter_limit: miter_limit as f32,
dash_array: dash,
dash_offset: dash_offset as f32,
}
}
}
impl ToRaqoteStyle for LineJoinStyle {
type Target = raqote::LineJoin;
fn to_raqote_style(self) -> raqote::LineJoin {
match self {
LineJoinStyle::Round => raqote::LineJoin::Round,
LineJoinStyle::Bevel => raqote::LineJoin::Bevel,
LineJoinStyle::Miter => raqote::LineJoin::Miter,
}
}
}
impl ToRaqoteStyle for LineCapStyle {
type Target = raqote::LineCap;
fn to_raqote_style(self) -> raqote::LineCap {
match self {
LineCapStyle::Butt => raqote::LineCap::Butt,
LineCapStyle::Round => raqote::LineCap::Round,
LineCapStyle::Square => raqote::LineCap::Square,
}
}
}
pub trait ToRaqotePattern {
fn to_raqote_pattern(self) -> Pattern;
}
pub trait ToRaqoteGradientStop {
fn to_raqote(self) -> raqote::GradientStop;
}
impl ToRaqoteGradientStop for CanvasGradientStop {
fn to_raqote(self) -> raqote::GradientStop {
let srgb = self.color.into_srgb_legacy();
let color = raqote::Color::new(
clamp_unit_f32(srgb.alpha),
clamp_unit_f32(srgb.components.0),
clamp_unit_f32(srgb.components.1),
clamp_unit_f32(srgb.components.2),
);
let position = self.offset as f32;
raqote::GradientStop { position, color }
}
}
impl ToRaqotePattern for FillOrStrokeStyle {
fn to_raqote_pattern(self) -> Pattern {
use canvas_traits::canvas::FillOrStrokeStyle::*;
match self {
Color(color) => {
let srgb = color.into_srgb_legacy();
Pattern::Color(
clamp_unit_f32(srgb.alpha),
clamp_unit_f32(srgb.components.0),
clamp_unit_f32(srgb.components.1),
clamp_unit_f32(srgb.components.2),
)
},
LinearGradient(style) => {
let start = Point2D::new(style.x0 as f32, style.y0 as f32);
let end = Point2D::new(style.x1 as f32, style.y1 as f32);
let stops = create_gradient_stops(style.stops);
Pattern::LinearGradient(LinearGradientPattern::new(start, end, stops))
},
RadialGradient(style) => {
let center1 = Point2D::new(style.x0 as f32, style.y0 as f32);
let center2 = Point2D::new(style.x1 as f32, style.y1 as f32);
let stops = create_gradient_stops(style.stops);
Pattern::RadialGradient(RadialGradientPattern::new(
center1,
style.r0 as f32,
center2,
style.r1 as f32,
stops,
))
},
Surface(style) => {
let repeat = Repetition::from_xy(style.repeat_x, style.repeat_y);
let mut snapshot = style.surface_data.to_owned();
snapshot.transform(
SnapshotAlphaMode::Transparent {
premultiplied: true,
},
SnapshotPixelFormat::BGRA,
);
Pattern::Surface(SurfacePattern::new(
snapshot,
raqote::FilterMode::Nearest,
repeat,
style.transform,
))
},
}
}
}
impl ToRaqoteStyle for AbsoluteColor {
type Target = raqote::SolidSource;
fn to_raqote_style(self) -> Self::Target {
let srgb = self.into_srgb_legacy();
raqote::SolidSource::from_unpremultiplied_argb(
clamp_unit_f32(srgb.alpha),
clamp_unit_f32(srgb.components.0),
clamp_unit_f32(srgb.components.1),
clamp_unit_f32(srgb.components.2),
)
}
}
impl ToRaqoteStyle for CompositionOrBlending {
type Target = raqote::BlendMode;
fn to_raqote_style(self) -> Self::Target {
match self {
CompositionOrBlending::Composition(op) => op.to_raqote_style(),
CompositionOrBlending::Blending(op) => op.to_raqote_style(),
}
}
}
impl ToRaqoteStyle for BlendingStyle {
type Target = raqote::BlendMode;
fn to_raqote_style(self) -> Self::Target {
match self {
BlendingStyle::Multiply => raqote::BlendMode::Multiply,
BlendingStyle::Screen => raqote::BlendMode::Screen,
BlendingStyle::Overlay => raqote::BlendMode::Overlay,
BlendingStyle::Darken => raqote::BlendMode::Darken,
BlendingStyle::Lighten => raqote::BlendMode::Lighten,
BlendingStyle::ColorDodge => raqote::BlendMode::ColorDodge,
BlendingStyle::HardLight => raqote::BlendMode::HardLight,
BlendingStyle::SoftLight => raqote::BlendMode::SoftLight,
BlendingStyle::Difference => raqote::BlendMode::Difference,
BlendingStyle::Exclusion => raqote::BlendMode::Exclusion,
BlendingStyle::Hue => raqote::BlendMode::Hue,
BlendingStyle::Saturation => raqote::BlendMode::Saturation,
BlendingStyle::Color => raqote::BlendMode::Color,
BlendingStyle::Luminosity => raqote::BlendMode::Luminosity,
BlendingStyle::ColorBurn => raqote::BlendMode::ColorBurn,
}
}
}
impl ToRaqoteStyle for CompositionStyle {
type Target = raqote::BlendMode;
fn to_raqote_style(self) -> Self::Target {
match self {
CompositionStyle::SourceIn => raqote::BlendMode::SrcIn,
CompositionStyle::SourceOut => raqote::BlendMode::SrcOut,
CompositionStyle::SourceOver => raqote::BlendMode::SrcOver,
CompositionStyle::SourceAtop => raqote::BlendMode::SrcAtop,
CompositionStyle::DestinationIn => raqote::BlendMode::DstIn,
CompositionStyle::DestinationOut => raqote::BlendMode::DstOut,
CompositionStyle::DestinationOver => raqote::BlendMode::DstOver,
CompositionStyle::DestinationAtop => raqote::BlendMode::DstAtop,
CompositionStyle::Copy => raqote::BlendMode::Src,
CompositionStyle::Lighter => raqote::BlendMode::Add,
CompositionStyle::Xor => raqote::BlendMode::Xor,
CompositionStyle::Clear => raqote::BlendMode::Clear,
}
}
}
fn to_path(path: &canvas_traits::canvas::Path) -> raqote::Path {
let mut pb = PathBuilder::new();
for cmd in &path.0 {
match cmd {
kurbo::PathEl::MoveTo(kurbo::Point { x, y }) => pb.move_to(x as f32, y as f32),
kurbo::PathEl::LineTo(kurbo::Point { x, y }) => pb.line_to(x as f32, y as f32),
kurbo::PathEl::QuadTo(cp, p) => {
pb.quad_to(cp.x as f32, cp.y as f32, p.x as f32, p.y as f32)
},
kurbo::PathEl::CurveTo(cp1, cp2, p) => pb.cubic_to(
cp1.x as f32,
cp1.y as f32,
cp2.x as f32,
cp2.y as f32,
p.x as f32,
p.y as f32,
),
kurbo::PathEl::ClosePath => pb.close(),
}
}
pb.finish()
}
fn draw_options(composition_options: CompositionOptions) -> DrawOptions {
DrawOptions {
blend_mode: composition_options.composition_operation.to_raqote_style(),
alpha: composition_options.alpha as f32,
..Default::default()
}
}

View file

@ -41,10 +41,7 @@ use crate::backend::{Convert as _, GenericDrawTarget};
use crate::canvas_data::{Filter, TextRun};
thread_local! {
/// The shared font cache used by all canvases that render on a thread. It would be nicer
/// to have a global cache, but it looks like font-kit uses a per-thread FreeType, so
/// in order to ensure that fonts are particular to a thread we have to make our own
/// cache thread local as well.
/// The shared font cache used by all canvases that render on a thread.
static SHARED_FONT_CACHE: RefCell<HashMap<FontIdentifier, peniko::Font>> = RefCell::default();
}

View file

@ -24,10 +24,7 @@ use crate::backend::{Convert, GenericDrawTarget};
use crate::canvas_data::{Filter, TextRun};
thread_local! {
/// The shared font cache used by all canvases that render on a thread. It would be nicer
/// to have a global cache, but it looks like font-kit uses a per-thread FreeType, so
/// in order to ensure that fonts are particular to a thread we have to make our own
/// cache thread local as well.
/// The shared font cache used by all canvases that render on a thread.
static SHARED_FONT_CACHE: RefCell<HashMap<FontIdentifier, peniko::Font>> = RefCell::default();
}

View file

@ -100,7 +100,6 @@ pub struct Preferences {
///
/// Available values:
/// - ` `/`auto`
/// - raqote
/// - vello
/// - vello_cpu
pub dom_canvas_backend: String,

View file

@ -18,7 +18,6 @@ tracing = ["dep:tracing", "canvas/tracing"]
webgpu = ["script_traits/webgpu"]
vello = ["canvas/vello"]
vello_cpu = ["canvas/vello_cpu"]
raqote = ["canvas/raqote"]
[lints.clippy]
unwrap_used = "deny"

View file

@ -63,7 +63,6 @@ webgpu = [
]
vello = ["constellation/vello"]
vello_cpu = ["constellation/vello_cpu"]
raqote = ["constellation/raqote"]
[dependencies]
background_hang_monitor = { path = "../background_hang_monitor" }

View file

@ -53,7 +53,6 @@ webgpu = ["libservo/webgpu"]
webxr = ["libservo/webxr"]
vello = ["libservo/vello"]
vello_cpu = ["libservo/vello_cpu"]
raqote = ["libservo/raqote"]
[dependencies]
cfg-if = { workspace = true }