summaryrefslogtreecommitdiffstats
path: root/gfx/wr/wr_glyph_rasterizer
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/wr/wr_glyph_rasterizer')
-rw-r--r--gfx/wr/wr_glyph_rasterizer/Cargo.toml51
-rw-r--r--gfx/wr/wr_glyph_rasterizer/README.md3
-rw-r--r--gfx/wr/wr_glyph_rasterizer/examples/basic.rs177
-rw-r--r--gfx/wr/wr_glyph_rasterizer/examples/common/boilerplate.rs298
-rw-r--r--gfx/wr/wr_glyph_rasterizer/src/gamma_lut.rs413
-rw-r--r--gfx/wr/wr_glyph_rasterizer/src/lib.rs60
-rw-r--r--gfx/wr/wr_glyph_rasterizer/src/platform/macos/font.rs899
-rw-r--r--gfx/wr/wr_glyph_rasterizer/src/platform/unix/font.rs1099
-rw-r--r--gfx/wr/wr_glyph_rasterizer/src/platform/windows/font.rs639
-rw-r--r--gfx/wr/wr_glyph_rasterizer/src/profiler.rs12
-rw-r--r--gfx/wr/wr_glyph_rasterizer/src/rasterizer.rs1879
-rw-r--r--gfx/wr/wr_glyph_rasterizer/src/telemetry.rs28
-rw-r--r--gfx/wr/wr_glyph_rasterizer/src/types.rs10
13 files changed, 5568 insertions, 0 deletions
diff --git a/gfx/wr/wr_glyph_rasterizer/Cargo.toml b/gfx/wr/wr_glyph_rasterizer/Cargo.toml
new file mode 100644
index 0000000000..de53902ec5
--- /dev/null
+++ b/gfx/wr/wr_glyph_rasterizer/Cargo.toml
@@ -0,0 +1,51 @@
+[package]
+name = "wr_glyph_rasterizer"
+version = "0.1.0"
+authors = ["The Mozilla Project Developers"]
+description = "A glyph rasterizer for WebRender"
+license = "MPL-2.0"
+edition = "2018"
+
+[features]
+default = ["static_freetype"]
+dynamic_freetype = []
+static_freetype = ["freetype/freetype-sys"]
+capture = ["api/serialize", "serde", "smallvec/serde"]
+replay = ["api/deserialize", "serde", "smallvec/serde"]
+gecko = ["firefox-on-glean", "glean"]
+
+[dependencies]
+api = { version = "0.62.0", path = "../webrender_api", package = "webrender_api" }
+euclid = { version = "0.22.0", features = ["serde"] }
+malloc_size_of = { version = "0.0.2", path = "../wr_malloc_size_of", package = "wr_malloc_size_of" }
+malloc_size_of_derive = "0.1"
+rayon = "1"
+smallvec = "1"
+tracy-rs = "0.1.2"
+log = "0.4"
+lazy_static = "1"
+fxhash = "0.2.1"
+glean = { version = "52.7.0", optional = true }
+firefox-on-glean = { version = "0.1.0", optional = true }
+serde = { optional = true, version = "1.0", features = ["serde_derive"] }
+
+[dev-dependencies]
+env_logger = { version = "0.10", default_features = false }
+euclid = "0.22"
+gleam = "0.15"
+glutin = "0.28"
+rayon = "1"
+winit = "0.26"
+
+[target.'cfg(any(target_os = "android", all(unix, not(target_os = "macos"))))'.dependencies]
+freetype = { version = "0.7", default-features = false }
+libc = "0.2"
+
+[target.'cfg(target_os = "windows")'.dependencies]
+dwrote = "0.11"
+
+[target.'cfg(target_os = "macos")'.dependencies]
+core-foundation = "0.9.2"
+core-graphics = "0.22.3"
+core-text = { version = "19.2", default-features = false }
+objc = "0.2"
diff --git a/gfx/wr/wr_glyph_rasterizer/README.md b/gfx/wr/wr_glyph_rasterizer/README.md
new file mode 100644
index 0000000000..0c23554e44
--- /dev/null
+++ b/gfx/wr/wr_glyph_rasterizer/README.md
@@ -0,0 +1,3 @@
+# A glyph rasterizer for webrender
+
+## Benchmark
diff --git a/gfx/wr/wr_glyph_rasterizer/examples/basic.rs b/gfx/wr/wr_glyph_rasterizer/examples/basic.rs
new file mode 100644
index 0000000000..4c46004cdf
--- /dev/null
+++ b/gfx/wr/wr_glyph_rasterizer/examples/basic.rs
@@ -0,0 +1,177 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+use std::sync::Arc;
+use std::mem;
+
+use api::{
+ IdNamespace, FontTemplate, FontKey, FontInstanceKey, FontInstanceOptions,
+ FontInstancePlatformOptions, ColorF, FontInstanceFlags, units::DevicePoint,
+};
+use glutin::ContextBuilder;
+use glutin::dpi::PhysicalSize;
+use glutin::event::{Event, WindowEvent};
+use glutin::event_loop::{ControlFlow, EventLoop};
+use glutin::window::WindowBuilder;
+use rayon::ThreadPoolBuilder;
+use wr_glyph_rasterizer::RasterizedGlyph;
+use wr_glyph_rasterizer::{
+ SharedFontResources, BaseFontInstance, GlyphRasterizer, FontInstance, GlyphKey,
+ SubpixelDirection, profiler::GlyphRasterizeProfiler,
+};
+
+#[path = "common/boilerplate.rs"]
+mod boilerplate;
+
+struct Profiler;
+
+impl GlyphRasterizeProfiler for Profiler {
+ fn start_time(&mut self) {}
+ fn end_time(&mut self) -> f64 {
+ 0.
+ }
+ fn set(&mut self, _value: f64) {}
+}
+
+fn load_glyphs() -> Vec<RasterizedGlyph> {
+ let namespace = IdNamespace(0);
+ let mut fonts = SharedFontResources::new(namespace);
+
+ let font_key = FontKey::new(namespace, 0);
+ let raw_font_data = include_bytes!("../../wrench/reftests/text/FreeSans.ttf");
+ let font_template = FontTemplate::Raw(Arc::new(raw_font_data.to_vec()), 0);
+ let shared_font_key = fonts
+ .font_keys
+ .add_key(&font_key, &font_template)
+ .expect("Failed to add font key");
+
+ let font_instance_key = FontInstanceKey::new(namespace, 1);
+ fonts.templates.add_font(shared_font_key, font_template);
+ assert!(fonts.templates.has_font(&shared_font_key));
+
+ // AddFontInstance will only be processed here, not in the resource cache, so it
+ // is safe to take the options rather than clone them.
+ let base = BaseFontInstance::new(
+ font_instance_key,
+ shared_font_key,
+ 32.,
+ mem::take(&mut Some(FontInstanceOptions::default())),
+ mem::take(&mut Some(FontInstancePlatformOptions::default())),
+ mem::take(&mut Vec::new()),
+ );
+ let shared_instance = fonts
+ .instance_keys
+ .add_key(base)
+ .expect("Failed to add font instance key");
+ fonts.instances.add_font_instance(shared_instance);
+
+ let workers = {
+ let worker = ThreadPoolBuilder::new()
+ .thread_name(|idx| format!("WRWorker#{}", idx))
+ .build();
+ Arc::new(worker.unwrap())
+ };
+ let mut glyph_rasterizer = GlyphRasterizer::new(workers, false);
+
+ glyph_rasterizer.add_font(
+ shared_font_key,
+ fonts
+ .templates
+ .get_font(&shared_font_key)
+ .expect("Could not find FontTemplate"),
+ );
+
+ let mut font = FontInstance::new(
+ fonts
+ .instances
+ .get_font_instance(font_instance_key)
+ .expect("Could not found BaseFontInstance"),
+ ColorF::BLACK.into(),
+ api::FontRenderMode::Alpha,
+ FontInstanceFlags::SUBPIXEL_POSITION | FontInstanceFlags::FONT_SMOOTHING,
+ );
+
+ glyph_rasterizer.prepare_font(&mut font);
+
+ let glyph_keys = [
+ glyph_rasterizer
+ .get_glyph_index(shared_font_key, 'A')
+ .expect("Failed to get glyph index"),
+ glyph_rasterizer
+ .get_glyph_index(shared_font_key, 'B')
+ .expect("Failed to get glyph index"),
+ glyph_rasterizer
+ .get_glyph_index(shared_font_key, 'C')
+ .expect("Failed to get glyph index"),
+ ];
+
+ let glyph_keys = glyph_keys.map(|g| {
+ GlyphKey::new(
+ g,
+ DevicePoint::new(100., 100.),
+ SubpixelDirection::Horizontal,
+ )
+ });
+
+ glyph_rasterizer.request_glyphs(font, &glyph_keys, |_| true);
+
+ let mut glyphs = vec![];
+ glyph_rasterizer.resolve_glyphs(
+ |job, _| {
+ if let Ok(glyph) = job.result {
+ glyphs.push(glyph);
+ }
+ },
+ &mut Profiler,
+ );
+
+ glyphs
+}
+
+fn main() {
+ let glyphs = load_glyphs();
+
+ let el = EventLoop::new();
+ let wb = WindowBuilder::new()
+ .with_title("A fantastic window!")
+ .with_inner_size(PhysicalSize::new(1900. as f64, 1300. as f64));
+
+ let windowed_context = ContextBuilder::new()
+ .with_gl(glutin::GlRequest::GlThenGles {
+ opengl_version: (3, 2),
+ opengles_version: (3, 0),
+ })
+ .build_windowed(wb, &el)
+ .unwrap();
+
+ let windowed_context = unsafe { windowed_context.make_current().unwrap() };
+
+ let gl = boilerplate::load(windowed_context.context(), glyphs);
+
+ el.run(move |event, _, control_flow| {
+ *control_flow = ControlFlow::Wait;
+
+ match event {
+ Event::LoopDestroyed => (),
+ Event::WindowEvent { event, .. } => match event {
+ WindowEvent::Resized(physical_size) => windowed_context.resize(physical_size),
+ WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
+ _ => (),
+ },
+ Event::RedrawRequested(_) => {
+ let size = windowed_context.window().inner_size();
+ let scale_factor = windowed_context.window().scale_factor();
+ gl.draw_frame(
+ size.width as f32,
+ size.height as f32,
+ [0., 0., 0., 1.0],
+ [1., 1., 1., 1.0],
+ scale_factor as f32,
+ );
+ windowed_context.swap_buffers().unwrap();
+ }
+ _ => (),
+ }
+ });
+}
diff --git a/gfx/wr/wr_glyph_rasterizer/examples/common/boilerplate.rs b/gfx/wr/wr_glyph_rasterizer/examples/common/boilerplate.rs
new file mode 100644
index 0000000000..9590f92b4c
--- /dev/null
+++ b/gfx/wr/wr_glyph_rasterizer/examples/common/boilerplate.rs
@@ -0,0 +1,298 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+use api::ImageFormat;
+use euclid::{Transform3D, UnknownUnit};
+use glutin::{self, PossiblyCurrent};
+use gleam::gl;
+use wr_glyph_rasterizer::{RasterizedGlyph, GlyphFormat};
+
+use std::{ffi::CStr, rc::Rc};
+
+#[allow(unused)]
+pub struct Gl {
+ pub gl: Rc<dyn gl::Gl>,
+ program: gl::GLuint,
+ vb: gl::GLuint,
+ vao: gl::GLuint,
+ vs: gl::GLuint,
+ fs: gl::GLuint,
+ textures: Vec<gl::GLuint>,
+ u_transform: i32,
+ u_text_color: i32,
+ u_sampler_color: i32,
+ glyphs: Vec<RasterizedGlyph>,
+}
+
+pub fn load(gl_context: &glutin::Context<PossiblyCurrent>, glyphs: Vec<RasterizedGlyph>) -> Gl {
+ env_logger::init();
+
+ #[cfg(target_os = "macos")]
+ {
+ use core_foundation::{self as cf, base::TCFType};
+ let i = cf::bundle::CFBundle::main_bundle().info_dictionary();
+ let mut i = unsafe { i.to_mutable() };
+ i.set(
+ cf::string::CFString::new("NSSupportsAutomaticGraphicsSwitching"),
+ cf::boolean::CFBoolean::true_value().into_CFType(),
+ );
+ }
+
+ let gl = match gl_context.get_api() {
+ glutin::Api::OpenGl => unsafe {
+ gl::GlFns::load_with(|symbol| gl_context.get_proc_address(symbol) as *const _)
+ },
+ glutin::Api::OpenGlEs => unsafe {
+ gl::GlesFns::load_with(|symbol| gl_context.get_proc_address(symbol) as *const _)
+ },
+ glutin::Api::WebGl => unimplemented!(),
+ };
+
+ let version = unsafe {
+ let data = CStr::from_ptr(gl.get_string(gl::VERSION).as_ptr() as *const _)
+ .to_bytes()
+ .to_vec();
+ String::from_utf8(data).unwrap()
+ };
+
+ println!("OpenGL version {}", version);
+
+ let vs = gl.create_shader(gl::VERTEX_SHADER);
+ gl.shader_source(vs, &[VS_SRC]);
+ gl.compile_shader(vs);
+
+ let fs = gl.create_shader(gl::FRAGMENT_SHADER);
+ gl.shader_source(fs, &[FS_SRC]);
+ gl.compile_shader(fs);
+
+ let program = gl.create_program();
+ gl.attach_shader(program, vs);
+ gl.attach_shader(program, fs);
+ gl.link_program(program);
+ gl.use_program(program);
+
+ let vb = gl.gen_buffers(1)[0];
+ gl.bind_buffer(gl::ARRAY_BUFFER, vb);
+ gl.buffer_data_untyped(
+ gl::ARRAY_BUFFER,
+ (6 * 4 * std::mem::size_of::<f32>()) as gl::types::GLsizeiptr,
+ std::ptr::null(),
+ gl::DYNAMIC_DRAW,
+ );
+
+ let vao = gl.gen_vertex_arrays(1)[0];
+ gl.bind_vertex_array(vao);
+
+ let u_transform = gl.get_uniform_location(program, "uTransform");
+ let u_text_color = gl.get_uniform_location(program, "uTextColor");
+ let u_sampler_color = gl.get_uniform_location(program, "uSamplerColor");
+ let i_position = gl.get_attrib_location(program, "iPosition");
+ let i_tex_coords = gl.get_attrib_location(program, "iTexCoords");
+ gl.vertex_attrib_pointer(
+ i_position as gl::types::GLuint,
+ 2,
+ gl::FLOAT,
+ false,
+ 4 * std::mem::size_of::<f32>() as gl::types::GLsizei,
+ 0,
+ );
+ gl.vertex_attrib_pointer(
+ i_tex_coords as gl::types::GLuint,
+ 2,
+ gl::FLOAT,
+ false,
+ 4 * std::mem::size_of::<f32>() as gl::types::GLsizei,
+ (2 * std::mem::size_of::<f32>()) as gl::types::GLuint,
+ );
+ gl.enable_vertex_attrib_array(i_position as gl::types::GLuint);
+ gl.enable_vertex_attrib_array(i_tex_coords as gl::types::GLuint);
+
+ let textures = create_texture(&gl, &glyphs);
+
+ gl.bind_buffer(gl::ARRAY_BUFFER, 0);
+ gl.bind_vertex_array(0);
+
+ unsafe { log_shader(&gl, vs) };
+ unsafe { log_shader(&gl, fs) };
+
+ Gl {
+ gl,
+ program,
+ vb,
+ vao,
+ u_transform,
+ u_text_color,
+ u_sampler_color,
+ glyphs,
+ textures,
+ vs,
+ fs,
+ }
+}
+
+fn create_texture(gl: &Rc<dyn gl::Gl>, glyphs: &[RasterizedGlyph]) -> Vec<gl::GLuint> {
+ let textures = gl.gen_textures(glyphs.len() as gl::types::GLsizei);
+ for (i, glyph) in glyphs.iter().enumerate() {
+ let (internal_format, external_format) = get_texture_format(&glyph.format);
+ let texture = textures[i];
+ gl.bind_texture(gl::TEXTURE_2D, texture);
+ gl.tex_parameter_i(
+ gl::TEXTURE_2D,
+ gl::TEXTURE_MAG_FILTER,
+ gl::LINEAR as gl::GLint,
+ );
+ gl.tex_parameter_i(
+ gl::TEXTURE_2D,
+ gl::TEXTURE_MIN_FILTER,
+ gl::LINEAR as gl::GLint,
+ );
+ gl.tex_parameter_i(
+ gl::TEXTURE_2D,
+ gl::TEXTURE_WRAP_S,
+ gl::CLAMP_TO_EDGE as gl::GLint,
+ );
+ gl.tex_parameter_i(
+ gl::TEXTURE_2D,
+ gl::TEXTURE_WRAP_T,
+ gl::CLAMP_TO_EDGE as gl::GLint,
+ );
+ // TODO: use tex_storage_2d
+ gl.tex_image_2d(
+ gl::TEXTURE_2D,
+ 0,
+ internal_format as gl::GLint,
+ glyph.width,
+ glyph.height,
+ 0,
+ external_format,
+ gl::UNSIGNED_BYTE,
+ Some(&glyph.bytes),
+ );
+
+ gl.pixel_store_i(gl::UNPACK_ALIGNMENT, 1);
+ gl.enable(gl::BLEND);
+ gl.blend_func(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
+ }
+
+ textures
+}
+
+fn get_texture_format(format: &GlyphFormat) -> (gl::GLuint, gl::GLuint) {
+ match format.image_format(false) {
+ ImageFormat::BGRA8 => (gl::RGBA, gl::BGRA),
+ _ => unimplemented!(),
+ }
+}
+
+unsafe fn log_shader(gl: &Rc<dyn gl::Gl>, shader: gl::GLuint) {
+ let log = gl.get_shader_info_log(shader);
+ if log.len() != 0 {
+ println!("[ERROR] {}", log);
+ }
+}
+
+impl Gl {
+ pub fn draw_frame(
+ &self,
+ width: f32,
+ height: f32,
+ text_color: [f32; 4],
+ background_color: [f32; 4],
+ scale_factor: f32,
+ ) {
+ let projection: Transform3D<f32, UnknownUnit, UnknownUnit> =
+ Transform3D::ortho(0., width, height, 0., -1., 1.);
+ self.gl
+ .uniform_matrix_4fv(self.u_transform, false, &projection.to_array());
+ self.gl.uniform_4fv(self.u_text_color, &text_color);
+
+ self.gl.active_texture(gl::TEXTURE0);
+
+ self.gl.bind_vertex_array(self.vao);
+
+ self.gl.clear_color(
+ background_color[0],
+ background_color[1],
+ background_color[2],
+ background_color[3],
+ );
+ self.gl.clear(gl::COLOR_BUFFER_BIT);
+
+ let mut ax = 0.;
+ for (i, glyph) in self.glyphs.iter().enumerate() {
+ let texture = self.textures[i];
+
+ let x = ax + glyph.left;
+ let y = glyph.top;
+
+ let w = (glyph.width as f32) * scale_factor;
+ let h = (glyph.height as f32) * scale_factor;
+
+ #[rustfmt::skip]
+ let vertices = [
+ x, y, 0.0, 0.0,
+ x, y + h, 0.0, 1.0,
+ x + w, y + h, 1.0, 1.0,
+
+ x, y, 0.0, 0.0,
+ x + w, y + h, 1.0, 1.0,
+ x + w, y, 1.0, 0.0
+ ];
+
+ self.gl.uniform_1i(self.u_sampler_color, 0);
+ self.gl.bind_texture(gl::TEXTURE_2D, texture);
+ self.gl.bind_buffer(gl::ARRAY_BUFFER, self.vb);
+ self.gl.buffer_data_untyped(
+ gl::ARRAY_BUFFER,
+ (vertices.len() * std::mem::size_of::<f32>()) as gl::GLsizeiptr,
+ vertices.as_ptr() as *const _,
+ gl::DYNAMIC_DRAW,
+ );
+ self.gl.bind_buffer(gl::ARRAY_BUFFER, 0);
+
+ self.gl.draw_arrays(gl::TRIANGLES, 0, 6);
+
+ ax += (glyph.left * scale_factor) + (glyph.width as f32 * scale_factor);
+ }
+ self.gl.bind_vertex_array(0);
+ self.gl.bind_texture(gl::TEXTURE_2D, 0);
+
+ unsafe {
+ log_shader(&self.gl, self.vs);
+ log_shader(&self.gl, self.fs);
+ };
+ }
+}
+
+const VS_SRC: &[u8] = b"
+#version 150
+
+in vec2 iPosition;
+in vec2 iTexCoords;
+
+uniform mat4 uTransform;
+
+out vec2 vColorTexCoord;
+
+void main() {
+ gl_Position = uTransform * vec4(iPosition, 0.0, 1.0);
+ vColorTexCoord = iTexCoords;
+}
+\0";
+
+const FS_SRC: &[u8] = b"
+#version 150
+
+in vec2 vColorTexCoord;
+
+uniform sampler2D uSamplerColor;
+uniform vec4 uTextColor;
+
+out vec4 oFragmentColor;
+
+void main() {
+ vec4 alpha = vec4(1.0, 1.0, 1.0, texture(uSamplerColor, vColorTexCoord).r);
+ oFragmentColor = uTextColor * alpha;
+}
+\0";
diff --git a/gfx/wr/wr_glyph_rasterizer/src/gamma_lut.rs b/gfx/wr/wr_glyph_rasterizer/src/gamma_lut.rs
new file mode 100644
index 0000000000..15075bacbf
--- /dev/null
+++ b/gfx/wr/wr_glyph_rasterizer/src/gamma_lut.rs
@@ -0,0 +1,413 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+/*!
+Gamma correction lookup tables.
+
+This is a port of Skia gamma LUT logic into Rust, used by WebRender.
+*/
+//#![warn(missing_docs)] //TODO
+#![allow(dead_code)]
+
+use api::ColorU;
+use std::cmp::max;
+
+/// Color space responsible for converting between lumas and luminances.
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum LuminanceColorSpace {
+ /// Linear space - no conversion involved.
+ Linear,
+ /// Simple gamma space - uses the `luminance ^ gamma` function.
+ Gamma(f32),
+ /// Srgb space.
+ Srgb,
+}
+
+impl LuminanceColorSpace {
+ pub fn new(gamma: f32) -> LuminanceColorSpace {
+ if gamma == 1.0 {
+ LuminanceColorSpace::Linear
+ } else if gamma == 0.0 {
+ LuminanceColorSpace::Srgb
+ } else {
+ LuminanceColorSpace::Gamma(gamma)
+ }
+ }
+
+ pub fn to_luma(&self, luminance: f32) -> f32 {
+ match *self {
+ LuminanceColorSpace::Linear => luminance,
+ LuminanceColorSpace::Gamma(gamma) => luminance.powf(gamma),
+ LuminanceColorSpace::Srgb => {
+ //The magic numbers are derived from the sRGB specification.
+ //See http://www.color.org/chardata/rgb/srgb.xalter .
+ if luminance <= 0.04045 {
+ luminance / 12.92
+ } else {
+ ((luminance + 0.055) / 1.055).powf(2.4)
+ }
+ }
+ }
+ }
+
+ pub fn from_luma(&self, luma: f32) -> f32 {
+ match *self {
+ LuminanceColorSpace::Linear => luma,
+ LuminanceColorSpace::Gamma(gamma) => luma.powf(1. / gamma),
+ LuminanceColorSpace::Srgb => {
+ //The magic numbers are derived from the sRGB specification.
+ //See http://www.color.org/chardata/rgb/srgb.xalter .
+ if luma <= 0.0031308 {
+ luma * 12.92
+ } else {
+ 1.055 * luma.powf(1./2.4) - 0.055
+ }
+ }
+ }
+ }
+}
+
+//TODO: tests
+fn round_to_u8(x : f32) -> u8 {
+ let v = (x + 0.5).floor() as i32;
+ assert!(0 <= v && v < 0x100);
+ v as u8
+}
+
+//TODO: tests
+/*
+ * Scales base <= 2^N-1 to 2^8-1
+ * @param N [1, 8] the number of bits used by base.
+ * @param base the number to be scaled to [0, 255].
+ */
+fn scale255(n: u8, mut base: u8) -> u8 {
+ base <<= 8 - n;
+ let mut lum = base;
+ let mut i = n;
+
+ while i < 8 {
+ lum |= base >> i;
+ i += n;
+ }
+
+ lum
+}
+
+// Computes the luminance from the given r, g, and b in accordance with
+// SK_LUM_COEFF_X. For correct results, r, g, and b should be in linear space.
+fn compute_luminance(r: u8, g: u8, b: u8) -> u8 {
+ // The following is
+ // r * SK_LUM_COEFF_R + g * SK_LUM_COEFF_G + b * SK_LUM_COEFF_B
+ // with SK_LUM_COEFF_X in 1.8 fixed point (rounding adjusted to sum to 256).
+ let val: u32 = r as u32 * 54 + g as u32 * 183 + b as u32 * 19;
+ assert!(val < 0x10000);
+ (val >> 8) as u8
+}
+
+// Skia uses 3 bits per channel for luminance.
+const LUM_BITS: u8 = 3;
+// Mask of the highest used bits.
+const LUM_MASK: u8 = ((1 << LUM_BITS) - 1) << (8 - LUM_BITS);
+
+pub trait ColorLut {
+ fn quantize(&self) -> ColorU;
+ fn quantized_floor(&self) -> ColorU;
+ fn quantized_ceil(&self) -> ColorU;
+ fn luminance(&self) -> u8;
+ fn luminance_color(&self) -> ColorU;
+}
+
+impl ColorLut for ColorU {
+ // Compute a canonical color that is equivalent to the input color
+ // for preblend table lookups. The alpha channel is never used for
+ // preblending, so overwrite it with opaque.
+ fn quantize(&self) -> ColorU {
+ ColorU::new(
+ scale255(LUM_BITS, self.r >> (8 - LUM_BITS)),
+ scale255(LUM_BITS, self.g >> (8 - LUM_BITS)),
+ scale255(LUM_BITS, self.b >> (8 - LUM_BITS)),
+ 255,
+ )
+ }
+
+ // Quantize to the smallest value that yields the same table index.
+ fn quantized_floor(&self) -> ColorU {
+ ColorU::new(
+ self.r & LUM_MASK,
+ self.g & LUM_MASK,
+ self.b & LUM_MASK,
+ 255,
+ )
+ }
+
+ // Quantize to the largest value that yields the same table index.
+ fn quantized_ceil(&self) -> ColorU {
+ ColorU::new(
+ self.r | !LUM_MASK,
+ self.g | !LUM_MASK,
+ self.b | !LUM_MASK,
+ 255,
+ )
+ }
+
+ // Compute a luminance value suitable for grayscale preblend table
+ // lookups.
+ fn luminance(&self) -> u8 {
+ compute_luminance(self.r, self.g, self.b)
+ }
+
+ // Make a grayscale color from the computed luminance.
+ fn luminance_color(&self) -> ColorU {
+ let lum = self.luminance();
+ ColorU::new(lum, lum, lum, self.a)
+ }
+}
+
+// This will invert the gamma applied by CoreGraphics,
+// so we can get linear values.
+// CoreGraphics obscurely defaults to 2.0 as the smoothing gamma value.
+// The color space used does not appear to affect this choice.
+#[cfg(target_os="macos")]
+fn get_inverse_gamma_table_coregraphics_smoothing() -> [u8; 256] {
+ let mut table = [0u8; 256];
+
+ for (i, v) in table.iter_mut().enumerate() {
+ let x = i as f32 / 255.0;
+ *v = round_to_u8(x * x * 255.0);
+ }
+
+ table
+}
+
+// A value of 0.5 for SK_GAMMA_CONTRAST appears to be a good compromise.
+// With lower values small text appears washed out (though correctly so).
+// With higher values lcd fringing is worse and the smoothing effect of
+// partial coverage is diminished.
+fn apply_contrast(srca: f32, contrast: f32) -> f32 {
+ srca + ((1.0 - srca) * contrast * srca)
+}
+
+// The approach here is not necessarily the one with the lowest error
+// See https://bel.fi/alankila/lcd/alpcor.html for a similar kind of thing
+// that just search for the adjusted alpha value
+pub fn build_gamma_correcting_lut(table: &mut [u8; 256], src: u8, contrast: f32,
+ src_space: LuminanceColorSpace,
+ dst_convert: LuminanceColorSpace) {
+ let src = src as f32 / 255.0;
+ let lin_src = src_space.to_luma(src);
+ // Guess at the dst. The perceptual inverse provides smaller visual
+ // discontinuities when slight changes to desaturated colors cause a channel
+ // to map to a different correcting lut with neighboring srcI.
+ // See https://code.google.com/p/chromium/issues/detail?id=141425#c59 .
+ let dst = 1.0 - src;
+ let lin_dst = dst_convert.to_luma(dst);
+
+ // Contrast value tapers off to 0 as the src luminance becomes white
+ let adjusted_contrast = contrast * lin_dst;
+
+ // Remove discontinuity and instability when src is close to dst.
+ // The value 1/256 is arbitrary and appears to contain the instability.
+ if (src - dst).abs() < (1.0 / 256.0) {
+ let mut ii : f32 = 0.0;
+ for v in table.iter_mut() {
+ let raw_srca = ii / 255.0;
+ let srca = apply_contrast(raw_srca, adjusted_contrast);
+
+ *v = round_to_u8(255.0 * srca);
+ ii += 1.0;
+ }
+ } else {
+ // Avoid slow int to float conversion.
+ let mut ii : f32 = 0.0;
+ for v in table.iter_mut() {
+ // 'raw_srca += 1.0f / 255.0f' and even
+ // 'raw_srca = i * (1.0f / 255.0f)' can add up to more than 1.0f.
+ // When this happens the table[255] == 0x0 instead of 0xff.
+ // See http://code.google.com/p/chromium/issues/detail?id=146466
+ let raw_srca = ii / 255.0;
+ let srca = apply_contrast(raw_srca, adjusted_contrast);
+ assert!(srca <= 1.0);
+ let dsta = 1.0 - srca;
+
+ // Calculate the output we want.
+ let lin_out = lin_src * srca + dsta * lin_dst;
+ assert!(lin_out <= 1.0);
+ let out = dst_convert.from_luma(lin_out);
+
+ // Undo what the blit blend will do.
+ // i.e. given the formula for OVER: out = src * result + (1 - result) * dst
+ // solving for result gives:
+ let result = (out - dst) / (src - dst);
+
+ *v = round_to_u8(255.0 * result);
+ debug!("Setting {:?} to {:?}", ii as u8, *v);
+
+ ii += 1.0;
+ }
+ }
+}
+
+pub struct GammaLut {
+ tables: [[u8; 256]; 1 << LUM_BITS],
+ #[cfg(target_os="macos")]
+ cg_inverse_gamma: [u8; 256],
+}
+
+impl GammaLut {
+ // Skia actually makes 9 gamma tables, then based on the luminance color,
+ // fetches the RGB gamma table for that color.
+ fn generate_tables(&mut self, contrast: f32, paint_gamma: f32, device_gamma: f32) {
+ let paint_color_space = LuminanceColorSpace::new(paint_gamma);
+ let device_color_space = LuminanceColorSpace::new(device_gamma);
+
+ for (i, entry) in self.tables.iter_mut().enumerate() {
+ let luminance = scale255(LUM_BITS, i as u8);
+ build_gamma_correcting_lut(entry,
+ luminance,
+ contrast,
+ paint_color_space,
+ device_color_space);
+ }
+ }
+
+ pub fn table_count(&self) -> usize {
+ self.tables.len()
+ }
+
+ pub fn get_table(&self, color: u8) -> &[u8; 256] {
+ &self.tables[(color >> (8 - LUM_BITS)) as usize]
+ }
+
+ pub fn new(contrast: f32, paint_gamma: f32, device_gamma: f32) -> GammaLut {
+ #[cfg(target_os="macos")]
+ let mut table = GammaLut {
+ tables: [[0; 256]; 1 << LUM_BITS],
+ cg_inverse_gamma: get_inverse_gamma_table_coregraphics_smoothing(),
+ };
+ #[cfg(not(target_os="macos"))]
+ let mut table = GammaLut {
+ tables: [[0; 256]; 1 << LUM_BITS],
+ };
+
+ table.generate_tables(contrast, paint_gamma, device_gamma);
+
+ table
+ }
+
+ // Assumes pixels are in BGRA format. Assumes pixel values are in linear space already.
+ pub fn preblend(&self, pixels: &mut [u8], color: ColorU) {
+ let table_r = self.get_table(color.r);
+ let table_g = self.get_table(color.g);
+ let table_b = self.get_table(color.b);
+
+ for pixel in pixels.chunks_mut(4) {
+ let (b, g, r) = (table_b[pixel[0] as usize], table_g[pixel[1] as usize], table_r[pixel[2] as usize]);
+ pixel[0] = b;
+ pixel[1] = g;
+ pixel[2] = r;
+ pixel[3] = max(max(b, g), r);
+ }
+ }
+
+ // Assumes pixels are in BGRA format. Assumes pixel values are in linear space already.
+ pub fn preblend_scaled(&self, pixels: &mut [u8], color: ColorU, percent: u8) {
+ if percent >= 100 {
+ self.preblend(pixels, color);
+ return;
+ }
+
+ let table_r = self.get_table(color.r);
+ let table_g = self.get_table(color.g);
+ let table_b = self.get_table(color.b);
+ let scale = (percent as i32 * 256) / 100;
+
+ for pixel in pixels.chunks_mut(4) {
+ let (mut b, g, mut r) = (
+ table_b[pixel[0] as usize] as i32,
+ table_g[pixel[1] as usize] as i32,
+ table_r[pixel[2] as usize] as i32,
+ );
+ b = g + (((b - g) * scale) >> 8);
+ r = g + (((r - g) * scale) >> 8);
+ pixel[0] = b as u8;
+ pixel[1] = g as u8;
+ pixel[2] = r as u8;
+ pixel[3] = max(max(b, g), r) as u8;
+ }
+ }
+
+ #[cfg(target_os="macos")]
+ pub fn coregraphics_convert_to_linear(&self, pixels: &mut [u8]) {
+ for pixel in pixels.chunks_mut(4) {
+ pixel[0] = self.cg_inverse_gamma[pixel[0] as usize];
+ pixel[1] = self.cg_inverse_gamma[pixel[1] as usize];
+ pixel[2] = self.cg_inverse_gamma[pixel[2] as usize];
+ }
+ }
+
+ // Assumes pixels are in BGRA format. Assumes pixel values are in linear space already.
+ pub fn preblend_grayscale(&self, pixels: &mut [u8], color: ColorU) {
+ let table_g = self.get_table(color.g);
+
+ for pixel in pixels.chunks_mut(4) {
+ let luminance = compute_luminance(pixel[2], pixel[1], pixel[0]);
+ let alpha = table_g[luminance as usize];
+ pixel[0] = alpha;
+ pixel[1] = alpha;
+ pixel[2] = alpha;
+ pixel[3] = alpha;
+ }
+ }
+
+} // end impl GammaLut
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ fn over(dst: u32, src: u32, alpha: u32) -> u32 {
+ (src * alpha + dst * (255 - alpha))/255
+ }
+
+ fn overf(dst: f32, src: f32, alpha: f32) -> f32 {
+ ((src * alpha + dst * (255. - alpha))/255.) as f32
+ }
+
+
+ fn absdiff(a: u32, b: u32) -> u32 {
+ if a < b { b - a } else { a - b }
+ }
+
+ #[test]
+ fn gamma() {
+ let mut table = [0u8; 256];
+ let g = 2.0;
+ let space = LuminanceColorSpace::Gamma(g);
+ let mut src : u32 = 131;
+ while src < 256 {
+ build_gamma_correcting_lut(&mut table, src as u8, 0., space, space);
+ let mut max_diff = 0;
+ let mut dst = 0;
+ while dst < 256 {
+ for alpha in 0u32..256 {
+ let preblend = table[alpha as usize];
+ let lin_dst = (dst as f32 / 255.).powf(g) * 255.;
+ let lin_src = (src as f32 / 255.).powf(g) * 255.;
+
+ let preblend_result = over(dst, src, preblend as u32);
+ let true_result = ((overf(lin_dst, lin_src, alpha as f32) / 255.).powf(1. / g) * 255.) as u32;
+ let diff = absdiff(preblend_result, true_result);
+ //debug!("{} -- {} {} = {}", alpha, preblend_result, true_result, diff);
+ max_diff = max(max_diff, diff);
+ }
+
+ //debug!("{} {} max {}", src, dst, max_diff);
+ assert!(max_diff <= 33);
+ dst += 1;
+
+ }
+ src += 1;
+ }
+ }
+} // end mod
diff --git a/gfx/wr/wr_glyph_rasterizer/src/lib.rs b/gfx/wr/wr_glyph_rasterizer/src/lib.rs
new file mode 100644
index 0000000000..27ceb71992
--- /dev/null
+++ b/gfx/wr/wr_glyph_rasterizer/src/lib.rs
@@ -0,0 +1,60 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+//! A glyph rasterizer for webrender
+//!
+//! ## Overview
+//!
+//! ## Usage
+//!
+
+#[cfg(any(target_os = "macos", target_os = "windows"))]
+mod gamma_lut;
+mod rasterizer;
+mod telemetry;
+mod types;
+
+pub mod profiler;
+
+pub use rasterizer::*;
+pub use types::*;
+
+#[macro_use]
+extern crate malloc_size_of_derive;
+#[macro_use]
+extern crate tracy_rs;
+#[macro_use]
+extern crate log;
+#[macro_use]
+extern crate lazy_static;
+#[macro_use]
+extern crate smallvec;
+
+#[cfg(any(feature = "serde"))]
+#[macro_use]
+extern crate serde;
+
+extern crate malloc_size_of;
+
+pub mod platform {
+ #[cfg(target_os = "macos")]
+ pub use crate::platform::macos::font;
+ #[cfg(any(target_os = "android", all(unix, not(target_os = "macos"))))]
+ pub use crate::platform::unix::font;
+ #[cfg(target_os = "windows")]
+ pub use crate::platform::windows::font;
+
+ #[cfg(target_os = "macos")]
+ pub mod macos {
+ pub mod font;
+ }
+ #[cfg(any(target_os = "android", all(unix, not(target_os = "macos"))))]
+ pub mod unix {
+ pub mod font;
+ }
+ #[cfg(target_os = "windows")]
+ pub mod windows {
+ pub mod font;
+ }
+}
diff --git a/gfx/wr/wr_glyph_rasterizer/src/platform/macos/font.rs b/gfx/wr/wr_glyph_rasterizer/src/platform/macos/font.rs
new file mode 100644
index 0000000000..804dbc93e8
--- /dev/null
+++ b/gfx/wr/wr_glyph_rasterizer/src/platform/macos/font.rs
@@ -0,0 +1,899 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+use api::{ColorF, ColorU, FontKey, FontRenderMode, FontSize, GlyphDimensions};
+use api::{FontInstanceFlags, FontVariation, NativeFontHandle};
+use core_foundation::data::CFData;
+use core_foundation::base::TCFType;
+use core_foundation::dictionary::CFDictionary;
+use core_foundation::number::{CFNumber};
+use core_foundation::string::CFString;
+use core_graphics::base::{kCGImageAlphaNoneSkipFirst, kCGImageAlphaPremultipliedFirst};
+use core_graphics::base::{kCGBitmapByteOrder32Little};
+use core_graphics::color_space::CGColorSpace;
+use core_graphics::context::CGContext;
+use core_graphics::context::{CGBlendMode, CGTextDrawingMode};
+use core_graphics::font::{CGFont, CGGlyph};
+use core_graphics::geometry::{CGAffineTransform, CGPoint, CGSize};
+use core_graphics::geometry::{CG_AFFINE_TRANSFORM_IDENTITY, CGRect};
+use core_text;
+use core_text::font::CTFont;
+use core_text::font_descriptor::{CTFontDescriptor, kCTFontDefaultOrientation, kCTFontVariationAttribute};
+use core_text::font_manager;
+use euclid::default::Size2D;
+use crate::gamma_lut::{ColorLut, GammaLut};
+use crate::rasterizer::{FontInstance, FontTransform, GlyphKey};
+use crate::rasterizer::{GlyphFormat, GlyphRasterError, GlyphRasterResult, RasterizedGlyph};
+use crate::types::FastHashMap;
+use std::collections::hash_map::Entry;
+use std::sync::Arc;
+
+const INITIAL_CG_CONTEXT_SIDE_LENGTH: u32 = 32;
+
+pub struct FontContext {
+ ct_font_descs: FastHashMap<FontKey, CTFontDescriptor>,
+ // Table mapping a sized font key with variations to its instantiated CoreText font.
+ ct_fonts: FastHashMap<(FontKey, FontSize, Vec<FontVariation>), CTFont>,
+ #[allow(dead_code)]
+ graphics_context: GraphicsContext,
+ #[allow(dead_code)]
+ gamma_lut: GammaLut,
+}
+
+// core text is safe to use on multiple threads and non-shareable resources are
+// all hidden inside their font context.
+unsafe impl Send for FontContext {}
+
+struct GlyphMetrics {
+ rasterized_left: i32,
+ #[allow(dead_code)]
+ rasterized_descent: i32,
+ rasterized_ascent: i32,
+ rasterized_width: i32,
+ rasterized_height: i32,
+ advance: f32,
+}
+
+// There are a number of different OS prefs that control whether or not
+// requesting font smoothing actually results in subpixel AA. This gets even
+// murkier in newer macOS versions that deprecate subpixel AA, with the prefs
+// potentially interacting and overriding each other. In an attempt to future-
+// proof things against any new prefs or interpretation of those prefs in
+// future macOS versions, we do a check here to request font smoothing and see
+// what result it actually gives us much like Skia does. We need to check for
+// each of three potential results and process them in the font backend in
+// distinct ways:
+// 1) subpixel AA (differing RGB channels) with dilation
+// 2) grayscale AA (matching RGB channels) with dilation, a compatibility mode
+// 3) grayscale AA without dilation as if font smoothing was not requested
+// We can discern between case 1 and the rest by checking if the subpixels differ.
+// We can discern between cases 2 and 3 by rendering with and without smoothing
+// and comparing the two to determine if there was some dilation.
+// This returns the actual FontRenderMode needed to support each case, if any.
+fn determine_font_smoothing_mode() -> Option<FontRenderMode> {
+ let mut smooth_context = CGContext::create_bitmap_context(
+ None,
+ 12,
+ 12,
+ 8,
+ 12 * 4,
+ &CGColorSpace::create_device_rgb(),
+ kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little,
+ );
+ smooth_context.set_should_smooth_fonts(true);
+ smooth_context.set_should_antialias(true);
+ smooth_context.set_rgb_fill_color(1.0, 1.0, 1.0, 1.0);
+ let mut gray_context = CGContext::create_bitmap_context(
+ None,
+ 12,
+ 12,
+ 8,
+ 12 * 4,
+ &CGColorSpace::create_device_rgb(),
+ kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little,
+ );
+ gray_context.set_should_smooth_fonts(false);
+ gray_context.set_should_antialias(true);
+ gray_context.set_rgb_fill_color(1.0, 1.0, 1.0, 1.0);
+
+ // Autorelease pool for CTFont
+ objc::rc::autoreleasepool(|| {
+ // Lucida Grande 12 is the default fallback font in Firefox
+ let ct_font = core_text::font::new_from_name("Lucida Grande", 12.).unwrap();
+ let point = CGPoint { x: 0., y: 0. };
+ let glyph = 'X' as CGGlyph;
+ ct_font.draw_glyphs(&[glyph], &[point], smooth_context.clone());
+ ct_font.draw_glyphs(&[glyph], &[point], gray_context.clone());
+ });
+
+ let mut mode = None;
+ for (smooth, gray) in smooth_context.data().chunks(4).zip(gray_context.data().chunks(4)) {
+ if smooth[0] != smooth[1] || smooth[1] != smooth[2] {
+ return Some(FontRenderMode::Subpixel);
+ }
+ if smooth[0] != gray[0] || smooth[1] != gray[1] || smooth[2] != gray[2] {
+ mode = Some(FontRenderMode::Alpha);
+ }
+ }
+ return mode;
+}
+
+// We cache the font smoothing mode globally, rather than storing it in each FontContext,
+// to avoid having to determine this redundantly in each context and to avoid needing to
+// lock them to access this setting in prepare_font.
+lazy_static! {
+ static ref FONT_SMOOTHING_MODE: Option<FontRenderMode> = determine_font_smoothing_mode();
+}
+
+fn should_use_white_on_black(color: ColorU) -> bool {
+ let (r, g, b) = (color.r as u32, color.g as u32, color.b as u32);
+ // These thresholds were determined on 10.12 by observing what CG does.
+ r >= 85 && g >= 85 && b >= 85 && r + g + b >= 2 * 255
+}
+
+fn get_glyph_metrics(
+ ct_font: &CTFont,
+ transform: Option<&CGAffineTransform>,
+ glyph: CGGlyph,
+ x_offset: f64,
+ y_offset: f64,
+ extra_width: f64,
+) -> GlyphMetrics {
+ let mut bounds = ct_font.get_bounding_rects_for_glyphs(kCTFontDefaultOrientation, &[glyph]);
+
+ if bounds.origin.x.is_nan() || bounds.origin.y.is_nan() || bounds.size.width.is_nan() ||
+ bounds.size.height.is_nan()
+ {
+ // If an unexpected glyph index is requested, core text will return NaN values
+ // which causes us to do bad thing as the value is cast into an integer and
+ // overflow when expanding the bounds a few lines below.
+ // Instead we are better off returning zero-sized metrics because this special
+ // case is handled by the callers of this method.
+ return GlyphMetrics {
+ rasterized_left: 0,
+ rasterized_width: 0,
+ rasterized_height: 0,
+ rasterized_ascent: 0,
+ rasterized_descent: 0,
+ advance: 0.0,
+ };
+ }
+
+ let mut advance = CGSize { width: 0.0, height: 0.0 };
+ unsafe {
+ ct_font.get_advances_for_glyphs(kCTFontDefaultOrientation, &glyph, &mut advance, 1);
+ }
+
+ if bounds.size.width > 0.0 {
+ bounds.size.width += extra_width;
+ }
+ if advance.width > 0.0 {
+ advance.width += extra_width;
+ }
+
+ if let Some(transform) = transform {
+ bounds = bounds.apply_transform(transform);
+ }
+
+ // First round out to pixel boundaries
+ // CG Origin is bottom left
+ let mut left = bounds.origin.x.floor() as i32;
+ let mut bottom = bounds.origin.y.floor() as i32;
+ let mut right = (bounds.origin.x + bounds.size.width + x_offset).ceil() as i32;
+ let mut top = (bounds.origin.y + bounds.size.height + y_offset).ceil() as i32;
+
+ // Expand the bounds by 1 pixel, to give CG room for anti-aliasing.
+ // Note that this outset is to allow room for LCD smoothed glyphs. However, the correct outset
+ // is not currently known, as CG dilates the outlines by some percentage.
+ // This is taken from Skia.
+ left -= 1;
+ bottom -= 1;
+ right += 1;
+ top += 1;
+
+ let width = right - left;
+ let height = top - bottom;
+
+ GlyphMetrics {
+ rasterized_left: left,
+ rasterized_width: width,
+ rasterized_height: height,
+ rasterized_ascent: top,
+ rasterized_descent: -bottom,
+ advance: advance.width as f32,
+ }
+}
+
+fn new_ct_font_with_variations(ct_font_desc: &CTFontDescriptor, size: f64, variations: &[FontVariation]) -> CTFont {
+ let ct_font = core_text::font::new_from_descriptor(ct_font_desc, size);
+ if variations.is_empty() {
+ return ct_font;
+ }
+ let mut vals: Vec<(CFNumber, CFNumber)> = Vec::with_capacity(variations.len() as usize);
+ for variation in variations {
+ vals.push((CFNumber::from(variation.tag as i64), CFNumber::from(variation.value as f64)));
+ }
+ if vals.is_empty() {
+ return ct_font;
+ }
+ let vals_dict = CFDictionary::from_CFType_pairs(&vals);
+ let variation_attribute = unsafe { CFString::wrap_under_get_rule(kCTFontVariationAttribute) };
+ let attrs_dict = CFDictionary::from_CFType_pairs(&[(variation_attribute, vals_dict)]);
+ let ct_var_font_desc = ct_font.copy_descriptor().create_copy_with_attributes(attrs_dict.to_untyped()).unwrap();
+ core_text::font::new_from_descriptor(&ct_var_font_desc, size)
+
+}
+
+// We rely on Gecko to determine whether the font may have color glyphs to avoid
+// needing to load the font ahead of time to query its symbolic traits.
+fn is_bitmap_font(font: &FontInstance) -> bool {
+ font.flags.contains(FontInstanceFlags::EMBEDDED_BITMAPS)
+}
+
+impl FontContext {
+ pub fn distribute_across_threads() -> bool {
+ true
+ }
+
+ pub fn new() -> FontContext {
+ debug!("Test for subpixel AA support: {:?}", *FONT_SMOOTHING_MODE);
+
+ // Force CG to use sRGB color space to gamma correct.
+ let contrast = 0.0;
+ let gamma = 0.0;
+
+ FontContext {
+ ct_font_descs: FastHashMap::default(),
+ ct_fonts: FastHashMap::default(),
+ graphics_context: GraphicsContext::new(),
+ gamma_lut: GammaLut::new(contrast, gamma, gamma),
+ }
+ }
+
+ pub fn add_raw_font(&mut self, font_key: &FontKey, bytes: Arc<Vec<u8>>, index: u32) {
+ if self.ct_font_descs.contains_key(font_key) {
+ return;
+ }
+
+ assert_eq!(index, 0);
+ let data = CFData::from_arc(bytes);
+ let ct_font_desc = match font_manager::create_font_descriptor_with_data(data) {
+ Err(_) => return,
+ Ok(cg_font) => cg_font,
+ };
+ self.ct_font_descs.insert(*font_key, ct_font_desc);
+ }
+
+ pub fn add_native_font(&mut self, font_key: &FontKey, native_font_handle: NativeFontHandle) {
+ if self.ct_font_descs.contains_key(font_key) {
+ return;
+ }
+
+ // There's no way great way to go from a CGFont to a CTFontDescriptor
+ // We could use the postscript name but that doesn't work for the
+ // system UI fonts on newer macOS versions. Instead we create a CTFont
+ // and use the descriptor for that. Normally we'd try to avoid new_from_CGFont
+ // because that adds the CGFont to the descriptor cache which can keep the CGFont
+ // around for a long time, but that should be ok for non-web (native) fonts.
+ let cf_name = CFString::new(&native_font_handle.name);
+
+ // For "hidden" system fonts, whose names start with a period,
+ // we can't instantiate CTFonts via a descriptor. We're really
+ // supposed to use CTFontCreateUIFontForLanguage, but for now
+ // we just use the CGFont.
+ let desc = if native_font_handle.name.starts_with('.') {
+ let cg_font = match CGFont::from_name(&cf_name) {
+ Ok(cg_font) => cg_font,
+ Err(_) => {
+ // If for some reason we failed to load a font descriptor, then our
+ // only options are to either abort or substitute a fallback font.
+ // It is preferable to use a fallback font instead so that rendering
+ // can at least still proceed in some fashion without erroring.
+ // Lucida Grande is the fallback font in Gecko, so use that here.
+ CGFont::from_name(&CFString::from_static_string("Lucida Grande"))
+ .expect("couldn't find font with postscript name and couldn't load fallback font")
+ }
+ };
+ core_text::font::new_from_CGFont(&cg_font, 0.).copy_descriptor()
+ } else {
+ core_text::font_descriptor::new_from_postscript_name(&cf_name)
+ };
+
+ self.ct_font_descs
+ .insert(*font_key, desc);
+ }
+
+ pub fn delete_font(&mut self, font_key: &FontKey) {
+ if let Some(_) = self.ct_font_descs.remove(font_key) {
+ self.ct_fonts.retain(|k, _| k.0 != *font_key);
+ }
+ }
+
+ pub fn delete_font_instance(&mut self, instance: &FontInstance) {
+ // Remove the CoreText font corresponding to this instance.
+ let size = FontSize::from_f64_px(instance.get_transformed_size());
+ self.ct_fonts.remove(&(instance.font_key, size, instance.variations.clone()));
+ }
+
+ fn get_ct_font(
+ &mut self,
+ font_key: FontKey,
+ size: f64,
+ variations: &[FontVariation],
+ ) -> Option<CTFont> {
+ // Interacting with CoreText can create autorelease garbage.
+ objc::rc::autoreleasepool(|| {
+ match self.ct_fonts.entry((font_key, FontSize::from_f64_px(size), variations.to_vec())) {
+ Entry::Occupied(entry) => Some((*entry.get()).clone()),
+ Entry::Vacant(entry) => {
+ let ct_font_desc = self.ct_font_descs.get(&font_key)?;
+ let ct_font = new_ct_font_with_variations(ct_font_desc, size, variations);
+ entry.insert(ct_font.clone());
+ Some(ct_font)
+ }
+ }
+ })
+ }
+
+ pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> {
+ let character = ch as u16;
+ let mut glyph = 0;
+
+ self.get_ct_font(font_key, 16.0, &[])
+ .and_then(|ct_font| {
+ unsafe {
+ let result = ct_font.get_glyphs_for_characters(&character, &mut glyph, 1);
+
+ if result {
+ Some(glyph as u32)
+ } else {
+ None
+ }
+ }
+ })
+ }
+
+ pub fn get_glyph_dimensions(
+ &mut self,
+ font: &FontInstance,
+ key: &GlyphKey,
+ ) -> Option<GlyphDimensions> {
+ let (x_scale, y_scale) = font.transform.compute_scale().unwrap_or((1.0, 1.0));
+ let size = font.size.to_f64_px() * y_scale;
+ self.get_ct_font(font.font_key, size, &font.variations)
+ .and_then(|ct_font| {
+ let glyph = key.index() as CGGlyph;
+ let bitmap = is_bitmap_font(font);
+ let (mut shape, (x_offset, y_offset)) = if bitmap {
+ (FontTransform::identity(), (0.0, 0.0))
+ } else {
+ (font.transform.invert_scale(y_scale, y_scale), font.get_subpx_offset(key))
+ };
+ if font.flags.contains(FontInstanceFlags::FLIP_X) {
+ shape = shape.flip_x();
+ }
+ if font.flags.contains(FontInstanceFlags::FLIP_Y) {
+ shape = shape.flip_y();
+ }
+ if font.flags.contains(FontInstanceFlags::TRANSPOSE) {
+ shape = shape.swap_xy();
+ }
+ let (mut tx, mut ty) = (0.0, 0.0);
+ if font.synthetic_italics.is_enabled() {
+ let (shape_, (tx_, ty_)) = font.synthesize_italics(shape, size);
+ shape = shape_;
+ tx = tx_;
+ ty = ty_;
+ }
+ let transform = if !shape.is_identity() || (tx, ty) != (0.0, 0.0) {
+ Some(CGAffineTransform {
+ a: shape.scale_x as f64,
+ b: -shape.skew_y as f64,
+ c: -shape.skew_x as f64,
+ d: shape.scale_y as f64,
+ tx: tx,
+ ty: -ty,
+ })
+ } else {
+ None
+ };
+ let (strike_scale, pixel_step) = if bitmap {
+ (y_scale, 1.0)
+ } else {
+ (x_scale, y_scale / x_scale)
+ };
+ let extra_strikes = font.get_extra_strikes(
+ FontInstanceFlags::SYNTHETIC_BOLD | FontInstanceFlags::MULTISTRIKE_BOLD,
+ strike_scale,
+ );
+ let metrics = get_glyph_metrics(
+ &ct_font,
+ transform.as_ref(),
+ glyph,
+ x_offset,
+ y_offset,
+ extra_strikes as f64 * pixel_step,
+ );
+ if metrics.rasterized_width == 0 || metrics.rasterized_height == 0 {
+ None
+ } else {
+ Some(GlyphDimensions {
+ left: metrics.rasterized_left,
+ top: metrics.rasterized_ascent,
+ width: metrics.rasterized_width,
+ height: metrics.rasterized_height,
+ advance: metrics.advance,
+ })
+ }
+ })
+ }
+
+ // Assumes the pixels here are linear values from CG
+ fn gamma_correct_pixels(
+ &self,
+ pixels: &mut Vec<u8>,
+ render_mode: FontRenderMode,
+ color: ColorU,
+ ) {
+ // Then convert back to gamma corrected values.
+ match render_mode {
+ FontRenderMode::Alpha => {
+ self.gamma_lut.preblend_grayscale(pixels, color);
+ }
+ FontRenderMode::Subpixel => {
+ self.gamma_lut.preblend(pixels, color);
+ }
+ _ => {} // Again, give mono untouched since only the alpha matters.
+ }
+ }
+
+ #[allow(dead_code)]
+ fn print_glyph_data(&mut self, data: &[u8], width: usize, height: usize) {
+ // Rust doesn't have step_by support on stable :(
+ debug!("Width is: {:?} height: {:?}", width, height);
+ for i in 0 .. height {
+ let current_height = i * width * 4;
+
+ for pixel in data[current_height .. current_height + (width * 4)].chunks(4) {
+ let b = pixel[0];
+ let g = pixel[1];
+ let r = pixel[2];
+ let a = pixel[3];
+ debug!("({}, {}, {}, {}) ", r, g, b, a);
+ }
+ }
+ }
+
+ pub fn prepare_font(font: &mut FontInstance) {
+ if is_bitmap_font(font) {
+ // Render mode is ignored for bitmap fonts. Also, avoid normalizing the color
+ // in case CoreText needs the current color for rendering glyph color layers.
+ font.render_mode = FontRenderMode::Mono;
+ font.disable_subpixel_position();
+ return;
+ }
+ // Sanitize the render mode for font smoothing. If font smoothing is supported,
+ // then we just need to ensure the render mode is limited to what is supported.
+ // If font smoothing is actually disabled, then we need to fall back to grayscale.
+ if font.flags.contains(FontInstanceFlags::FONT_SMOOTHING) ||
+ font.render_mode == FontRenderMode::Subpixel {
+ match *FONT_SMOOTHING_MODE {
+ Some(mode) => {
+ font.render_mode = font.render_mode.limit_by(mode);
+ font.flags.insert(FontInstanceFlags::FONT_SMOOTHING);
+ }
+ None => {
+ font.render_mode = font.render_mode.limit_by(FontRenderMode::Alpha);
+ font.flags.remove(FontInstanceFlags::FONT_SMOOTHING);
+ }
+ }
+ }
+ match font.render_mode {
+ FontRenderMode::Mono => {
+ // In mono mode the color of the font is irrelevant.
+ font.color = ColorU::new(255, 255, 255, 255);
+ // Subpixel positioning is disabled in mono mode.
+ font.disable_subpixel_position();
+ }
+ FontRenderMode::Alpha => {
+ font.color = if font.flags.contains(FontInstanceFlags::FONT_SMOOTHING) {
+ // Only the G channel is used to index grayscale tables,
+ // so use R and B to preserve light/dark determination.
+ let ColorU { g, a, .. } = font.color.luminance_color().quantized_ceil();
+ let rb = if should_use_white_on_black(font.color) { 255 } else { 0 };
+ ColorU::new(rb, g, rb, a)
+ } else {
+ ColorU::new(255, 255, 255, 255)
+ };
+ }
+ FontRenderMode::Subpixel => {
+ // Quantization may change the light/dark determination, so quantize in the
+ // direction necessary to respect the threshold.
+ font.color = if should_use_white_on_black(font.color) {
+ font.color.quantized_ceil()
+ } else {
+ font.color.quantized_floor()
+ };
+ }
+ }
+ }
+
+ pub fn begin_rasterize(_font: &FontInstance) {
+ }
+
+ pub fn end_rasterize(_font: &FontInstance) {
+ }
+
+ pub fn rasterize_glyph(&mut self, font: &FontInstance, key: &GlyphKey) -> GlyphRasterResult {
+ objc::rc::autoreleasepool(|| {
+ let (x_scale, y_scale) = font.transform.compute_scale().unwrap_or((1.0, 1.0));
+ let size = font.size.to_f64_px() * y_scale;
+ let ct_font =
+ self.get_ct_font(font.font_key, size, &font.variations).ok_or(GlyphRasterError::LoadFailed)?;
+ let glyph_type = if is_bitmap_font(font) {
+ GlyphType::Bitmap
+ } else {
+ GlyphType::Vector
+ };
+
+ let (mut shape, (x_offset, y_offset)) = match glyph_type {
+ GlyphType::Bitmap => (FontTransform::identity(), (0.0, 0.0)),
+ GlyphType::Vector => {
+ (font.transform.invert_scale(y_scale, y_scale), font.get_subpx_offset(key))
+ }
+ };
+ if font.flags.contains(FontInstanceFlags::FLIP_X) {
+ shape = shape.flip_x();
+ }
+ if font.flags.contains(FontInstanceFlags::FLIP_Y) {
+ shape = shape.flip_y();
+ }
+ if font.flags.contains(FontInstanceFlags::TRANSPOSE) {
+ shape = shape.swap_xy();
+ }
+ let (mut tx, mut ty) = (0.0, 0.0);
+ if font.synthetic_italics.is_enabled() {
+ let (shape_, (tx_, ty_)) = font.synthesize_italics(shape, size);
+ shape = shape_;
+ tx = tx_;
+ ty = ty_;
+ }
+ let transform = if !shape.is_identity() || (tx, ty) != (0.0, 0.0) {
+ Some(CGAffineTransform {
+ a: shape.scale_x as f64,
+ b: -shape.skew_y as f64,
+ c: -shape.skew_x as f64,
+ d: shape.scale_y as f64,
+ tx: tx,
+ ty: -ty,
+ })
+ } else {
+ None
+ };
+
+ let glyph = key.index() as CGGlyph;
+ let (strike_scale, pixel_step) = if glyph_type == GlyphType::Bitmap {
+ (y_scale, 1.0)
+ } else {
+ (x_scale, y_scale / x_scale)
+ };
+ let extra_strikes = font.get_extra_strikes(
+ FontInstanceFlags::SYNTHETIC_BOLD | FontInstanceFlags::MULTISTRIKE_BOLD,
+ strike_scale,
+ );
+ let metrics = get_glyph_metrics(
+ &ct_font,
+ transform.as_ref(),
+ glyph,
+ x_offset,
+ y_offset,
+ extra_strikes as f64 * pixel_step,
+ );
+ if metrics.rasterized_width == 0 || metrics.rasterized_height == 0 {
+ return Err(GlyphRasterError::LoadFailed);
+ }
+
+ let raster_size = Size2D::new(
+ metrics.rasterized_width as u32,
+ metrics.rasterized_height as u32
+ );
+
+ // If the font render mode is Alpha, we support two different ways to
+ // compute the grayscale mask, depending on the value of the platform
+ // options' font_smoothing flag:
+ // - Alpha + smoothing:
+ // We will recover a grayscale mask from a subpixel rasterization, in
+ // such a way that the result looks as close to subpixel text
+ // blending as we can make it. This involves gamma correction,
+ // luminance computations and preblending based on the text color,
+ // just like with the Subpixel render mode.
+ // - Alpha without smoothing:
+ // We will ask CoreGraphics to rasterize the text with font_smoothing
+ // off. This will cause it to use grayscale anti-aliasing with
+ // comparatively thin text. This method of text rendering is not
+ // gamma-aware.
+ //
+ // For subpixel rasterization, starting with macOS 10.11, CoreGraphics
+ // uses different glyph dilation based on the text color. Bright text
+ // uses less font dilation (looks thinner) than dark text.
+ // As a consequence, when we ask CG to rasterize with subpixel AA, we
+ // will render white-on-black text as opposed to black-on-white text if
+ // the text color brightness exceeds a certain threshold. This applies
+ // to both the Subpixel and the "Alpha + smoothing" modes, but not to
+ // the "Alpha without smoothing" and Mono modes.
+ //
+ // Fonts with color glyphs may, depending on the state within per-glyph
+ // table data, require the current font color to determine the output
+ // color. For such fonts we must thus supply the current font color just
+ // in case it is necessary.
+ let use_white_on_black = should_use_white_on_black(font.color);
+ let use_font_smoothing = font.flags.contains(FontInstanceFlags::FONT_SMOOTHING);
+ let (antialias, smooth, text_color, bg_color, invert) = match glyph_type {
+ GlyphType::Bitmap => (true, false, ColorF::from(font.color), ColorF::TRANSPARENT, false),
+ GlyphType::Vector => {
+ match (font.render_mode, use_font_smoothing) {
+ (FontRenderMode::Subpixel, _) |
+ (FontRenderMode::Alpha, true) => if use_white_on_black {
+ (true, true, ColorF::WHITE, ColorF::BLACK, false)
+ } else {
+ (true, true, ColorF::BLACK, ColorF::WHITE, true)
+ },
+ (FontRenderMode::Alpha, false) => (true, false, ColorF::BLACK, ColorF::WHITE, true),
+ (FontRenderMode::Mono, _) => (false, false, ColorF::BLACK, ColorF::WHITE, true),
+ }
+ }
+ };
+
+ {
+ let cg_context = self.graphics_context.get_context(&raster_size, glyph_type);
+
+ // These are always true in Gecko, even for non-AA fonts
+ cg_context.set_allows_font_subpixel_positioning(true);
+ cg_context.set_should_subpixel_position_fonts(true);
+
+ // Don't quantize because we're doing it already.
+ cg_context.set_allows_font_subpixel_quantization(false);
+ cg_context.set_should_subpixel_quantize_fonts(false);
+
+ cg_context.set_should_smooth_fonts(smooth);
+ cg_context.set_should_antialias(antialias);
+
+ // Fill the background. This could be opaque white, opaque black, or
+ // transparency.
+ cg_context.set_rgb_fill_color(
+ bg_color.r.into(),
+ bg_color.g.into(),
+ bg_color.b.into(),
+ bg_color.a.into(),
+ );
+ let rect = CGRect {
+ origin: CGPoint { x: 0.0, y: 0.0 },
+ size: CGSize {
+ width: metrics.rasterized_width as f64,
+ height: metrics.rasterized_height as f64,
+ },
+ };
+
+ // Make sure we use the Copy blend mode, or else we'll get the Porter-Duff OVER
+ // operator, which can't clear to the transparent color!
+ cg_context.set_blend_mode(CGBlendMode::Copy);
+ cg_context.fill_rect(rect);
+ cg_context.set_blend_mode(CGBlendMode::Normal);
+
+ // Set the text color and draw the glyphs.
+ cg_context.set_rgb_fill_color(
+ text_color.r.into(),
+ text_color.g.into(),
+ text_color.b.into(),
+ 1.0,
+ );
+ cg_context.set_text_drawing_mode(CGTextDrawingMode::CGTextFill);
+
+ // CG Origin is bottom left, WR is top left. Need -y offset
+ let mut draw_origin = CGPoint {
+ x: -metrics.rasterized_left as f64 + x_offset + tx,
+ y: metrics.rasterized_descent as f64 - y_offset - ty,
+ };
+
+ if let Some(transform) = transform {
+ cg_context.set_text_matrix(&transform);
+
+ draw_origin = draw_origin.apply_transform(&transform.invert());
+ } else {
+ // Make sure to reset this because some previous glyph rasterization might have
+ // changed it.
+ cg_context.set_text_matrix(&CG_AFFINE_TRANSFORM_IDENTITY);
+ }
+
+ ct_font.draw_glyphs(&[glyph], &[draw_origin], cg_context.clone());
+
+ // We'd like to render all the strikes in a single ct_font.draw_glyphs call,
+ // passing an array of glyph IDs and an array of origins, but unfortunately
+ // with some fonts, Core Text may inappropriately pixel-snap the rasterization,
+ // such that the strikes overprint instead of being offset. Rendering the
+ // strikes with individual draw_glyphs calls avoids this.
+ // (See https://bugzilla.mozilla.org/show_bug.cgi?id=1633397 for details.)
+ for i in 1 ..= extra_strikes {
+ let origin = CGPoint {
+ x: draw_origin.x + i as f64 * pixel_step,
+ y: draw_origin.y,
+ };
+ ct_font.draw_glyphs(&[glyph], &[origin], cg_context.clone());
+ }
+ }
+
+ let mut rasterized_pixels = self.graphics_context
+ .get_rasterized_pixels(&raster_size, glyph_type);
+
+ if glyph_type == GlyphType::Vector {
+ // We rendered text into an opaque surface. The code below needs to
+ // ignore the current value of each pixel's alpha channel. But it's
+ // allowed to write to the alpha channel, because we're done calling
+ // CG functions now.
+
+ if smooth {
+ // Convert to linear space for subpixel AA.
+ // We explicitly do not do this for grayscale AA ("Alpha without
+ // smoothing" or Mono) because those rendering modes are not
+ // gamma-aware in CoreGraphics.
+ self.gamma_lut.coregraphics_convert_to_linear(
+ &mut rasterized_pixels,
+ );
+ }
+
+ for pixel in rasterized_pixels.chunks_mut(4) {
+ if invert {
+ pixel[0] = 255 - pixel[0];
+ pixel[1] = 255 - pixel[1];
+ pixel[2] = 255 - pixel[2];
+ }
+
+ // Set alpha to the value of the green channel. For grayscale
+ // text, all three channels have the same value anyway.
+ // For subpixel text, the mask's alpha only makes a difference
+ // when computing the destination alpha on destination pixels
+ // that are not completely opaque. Picking an alpha value
+ // that's somehow based on the mask at least ensures that text
+ // blending doesn't modify the destination alpha on pixels where
+ // the mask is entirely zero.
+ pixel[3] = pixel[1];
+ }
+
+ if smooth {
+ // Convert back from linear space into device space, and perform
+ // some "preblending" based on the text color.
+ // In Alpha + smoothing mode, this will also convert subpixel AA
+ // into grayscale AA.
+ self.gamma_correct_pixels(
+ &mut rasterized_pixels,
+ font.render_mode,
+ font.color,
+ );
+ }
+ }
+
+ Ok(RasterizedGlyph {
+ left: metrics.rasterized_left as f32,
+ top: metrics.rasterized_ascent as f32,
+ width: metrics.rasterized_width,
+ height: metrics.rasterized_height,
+ scale: match glyph_type {
+ GlyphType::Bitmap => y_scale.recip() as f32,
+ GlyphType::Vector => 1.0,
+ },
+ format: match glyph_type {
+ GlyphType::Bitmap => GlyphFormat::ColorBitmap,
+ GlyphType::Vector => font.get_glyph_format(),
+ },
+ bytes: rasterized_pixels,
+ })})
+ }
+}
+
+// Avoids taking locks by recycling Core Graphics contexts.
+#[allow(dead_code)]
+struct GraphicsContext {
+ vector_context: CGContext,
+ vector_context_size: Size2D<u32>,
+ bitmap_context: CGContext,
+ bitmap_context_size: Size2D<u32>,
+}
+
+impl GraphicsContext {
+ fn new() -> GraphicsContext {
+ let size = Size2D::new(INITIAL_CG_CONTEXT_SIDE_LENGTH, INITIAL_CG_CONTEXT_SIDE_LENGTH);
+ GraphicsContext {
+ vector_context: GraphicsContext::create_cg_context(&size, GlyphType::Vector),
+ vector_context_size: size,
+ bitmap_context: GraphicsContext::create_cg_context(&size, GlyphType::Bitmap),
+ bitmap_context_size: size,
+ }
+ }
+
+ #[allow(dead_code)]
+ fn get_context(&mut self, size: &Size2D<u32>, glyph_type: GlyphType)
+ -> &mut CGContext {
+ let (cached_context, cached_size) = match glyph_type {
+ GlyphType::Vector => {
+ (&mut self.vector_context, &mut self.vector_context_size)
+ }
+ GlyphType::Bitmap => {
+ (&mut self.bitmap_context, &mut self.bitmap_context_size)
+ }
+ };
+ let rounded_size = Size2D::new(size.width.next_power_of_two(),
+ size.height.next_power_of_two());
+ if rounded_size.width > cached_size.width || rounded_size.height > cached_size.height {
+ *cached_size = Size2D::new(u32::max(cached_size.width, rounded_size.width),
+ u32::max(cached_size.height, rounded_size.height));
+ *cached_context = GraphicsContext::create_cg_context(cached_size, glyph_type);
+ }
+ cached_context
+ }
+
+ #[allow(dead_code)]
+ fn get_rasterized_pixels(&mut self, size: &Size2D<u32>, glyph_type: GlyphType)
+ -> Vec<u8> {
+ let (cached_context, cached_size) = match glyph_type {
+ GlyphType::Vector => (&mut self.vector_context, &self.vector_context_size),
+ GlyphType::Bitmap => (&mut self.bitmap_context, &self.bitmap_context_size),
+ };
+ let cached_data = cached_context.data();
+ let cached_stride = cached_size.width as usize * 4;
+
+ let result_len = size.width as usize * size.height as usize * 4;
+ let mut result = Vec::with_capacity(result_len);
+ for y in (cached_size.height - size.height)..cached_size.height {
+ let cached_start = y as usize * cached_stride;
+ let cached_end = cached_start + size.width as usize * 4;
+ result.extend_from_slice(&cached_data[cached_start..cached_end]);
+ }
+ debug_assert_eq!(result.len(), result_len);
+ result
+ }
+
+ fn create_cg_context(size: &Size2D<u32>, glyph_type: GlyphType) -> CGContext {
+ // The result of rasterization, in all render modes, is going to be a
+ // BGRA surface with white text on transparency using premultiplied
+ // alpha. For subpixel text, the RGB values will be the mask value for
+ // the individual components. For bitmap glyphs, the RGB values will be
+ // the (premultiplied) color of the pixel. For Alpha and Mono, each
+ // pixel will have R==G==B==A at the end of this function.
+ // We access the color channels in little-endian order.
+ // The CGContext will create and own our pixel buffer.
+ // In the non-Bitmap cases, we will ask CoreGraphics to draw text onto
+ // an opaque background. In order to hit the most efficient path in CG
+ // for this, we will tell CG that the CGContext is opaque, by passing
+ // an "[...]AlphaNone[...]" context flag. This creates a slight
+ // contradiction to the way we use the buffer after CG is done with it,
+ // because we will convert it into text-on-transparency. But that's ok;
+ // we still get four bytes per pixel and CG won't mess with the alpha
+ // channel after we've stopped calling CG functions. We just need to
+ // make sure that we don't look at the alpha values of the pixels that
+ // we get from CG, and compute our own alpha value only from RGB.
+ // Note that CG requires kCGBitmapByteOrder32Little in order to do
+ // subpixel AA at all (which we need it to do in both Subpixel and
+ // Alpha+smoothing mode). But little-endian is what we want anyway, so
+ // this works out nicely.
+ let color_type = match glyph_type {
+ GlyphType::Vector => kCGImageAlphaNoneSkipFirst,
+ GlyphType::Bitmap => kCGImageAlphaPremultipliedFirst,
+ };
+
+ CGContext::create_bitmap_context(None,
+ size.width as usize,
+ size.height as usize,
+ 8,
+ size.width as usize * 4,
+ &CGColorSpace::create_device_rgb(),
+ kCGBitmapByteOrder32Little | color_type)
+ }
+}
+
+#[derive(Clone, Copy, PartialEq, Debug)]
+enum GlyphType {
+ Vector,
+ Bitmap,
+}
+
diff --git a/gfx/wr/wr_glyph_rasterizer/src/platform/unix/font.rs b/gfx/wr/wr_glyph_rasterizer/src/platform/unix/font.rs
new file mode 100644
index 0000000000..5885d8f9a2
--- /dev/null
+++ b/gfx/wr/wr_glyph_rasterizer/src/platform/unix/font.rs
@@ -0,0 +1,1099 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+use api::{ColorU, GlyphDimensions, FontKey, FontRenderMode};
+use api::{FontInstancePlatformOptions, FontLCDFilter, FontHinting};
+use api::{FontInstanceFlags, FontTemplate, FontVariation, NativeFontHandle};
+use freetype::freetype::{FT_BBox, FT_Outline_Translate, FT_Pixel_Mode, FT_Render_Mode};
+use freetype::freetype::{FT_Done_Face, FT_Error, FT_Get_Char_Index, FT_Int32};
+use freetype::freetype::{FT_Done_FreeType, FT_Library_SetLcdFilter, FT_Pos};
+use freetype::freetype::{FT_F26Dot6, FT_Face, FT_Glyph_Format, FT_Long, FT_UInt};
+use freetype::freetype::{FT_GlyphSlot, FT_LcdFilter, FT_New_Face, FT_New_Memory_Face};
+use freetype::freetype::{FT_Init_FreeType, FT_Load_Glyph, FT_Render_Glyph};
+use freetype::freetype::{FT_Library, FT_Outline_Get_CBox, FT_Set_Char_Size, FT_Select_Size};
+use freetype::freetype::{FT_Fixed, FT_Matrix, FT_Set_Transform, FT_String, FT_ULong, FT_Vector};
+use freetype::freetype::{FT_Err_Unimplemented_Feature, FT_MulFix, FT_Outline_Embolden};
+use freetype::freetype::{FT_LOAD_COLOR, FT_LOAD_DEFAULT, FT_LOAD_FORCE_AUTOHINT};
+use freetype::freetype::{FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH, FT_LOAD_NO_AUTOHINT};
+use freetype::freetype::{FT_LOAD_NO_BITMAP, FT_LOAD_NO_HINTING};
+use freetype::freetype::{FT_FACE_FLAG_SCALABLE, FT_FACE_FLAG_FIXED_SIZES};
+use freetype::freetype::{FT_FACE_FLAG_MULTIPLE_MASTERS};
+use freetype::succeeded;
+use crate::rasterizer::{FontInstance, GlyphFormat, GlyphKey};
+use crate::rasterizer::{GlyphRasterError, GlyphRasterResult, RasterizedGlyph};
+use crate::types::FastHashMap;
+#[cfg(any(not(target_os = "android"), feature = "dynamic_freetype"))]
+use libc::{dlsym, RTLD_DEFAULT};
+use libc::free;
+use std::{cmp, mem, ptr, slice};
+use std::cmp::max;
+use std::ffi::CString;
+use std::sync::{Arc, Condvar, Mutex, MutexGuard};
+
+// These constants are not present in the freetype
+// bindings due to bindgen not handling the way
+// the macros are defined.
+//const FT_LOAD_TARGET_NORMAL: FT_UInt = 0 << 16;
+const FT_LOAD_TARGET_LIGHT: FT_UInt = 1 << 16;
+const FT_LOAD_TARGET_MONO: FT_UInt = 2 << 16;
+const FT_LOAD_TARGET_LCD: FT_UInt = 3 << 16;
+const FT_LOAD_TARGET_LCD_V: FT_UInt = 4 << 16;
+
+#[repr(C)]
+struct FT_Var_Axis {
+ pub name: *mut FT_String,
+ pub minimum: FT_Fixed,
+ pub def: FT_Fixed,
+ pub maximum: FT_Fixed,
+ pub tag: FT_ULong,
+ pub strid: FT_UInt,
+}
+
+#[repr(C)]
+struct FT_Var_Named_Style {
+ pub coords: *mut FT_Fixed,
+ pub strid: FT_UInt,
+ pub psid: FT_UInt,
+}
+
+#[repr(C)]
+struct FT_MM_Var {
+ pub num_axis: FT_UInt,
+ pub num_designs: FT_UInt,
+ pub num_namedstyles: FT_UInt,
+ pub axis: *mut FT_Var_Axis,
+ pub namedstyle: *mut FT_Var_Named_Style,
+}
+
+#[inline]
+pub fn unimplemented(error: FT_Error) -> bool {
+ error == FT_Err_Unimplemented_Feature as FT_Error
+}
+
+// Use dlsym to check for symbols. If not available. just return an unimplemented error.
+#[cfg(any(not(target_os = "android"), feature = "dynamic_freetype"))]
+macro_rules! ft_dyn_fn {
+ ($func_name:ident($($arg_name:ident:$arg_type:ty),*) -> FT_Error) => {
+ #[allow(non_snake_case)]
+ unsafe fn $func_name($($arg_name:$arg_type),*) -> FT_Error {
+ extern "C" fn unimpl_func($(_:$arg_type),*) -> FT_Error {
+ FT_Err_Unimplemented_Feature as FT_Error
+ }
+ lazy_static! {
+ static ref FUNC: unsafe extern "C" fn($($arg_type),*) -> FT_Error = {
+ unsafe {
+ let cname = CString::new(stringify!($func_name)).unwrap();
+ let ptr = dlsym(RTLD_DEFAULT, cname.as_ptr());
+ if !ptr.is_null() { mem::transmute(ptr) } else { unimpl_func }
+ }
+ };
+ }
+ (*FUNC)($($arg_name),*)
+ }
+ }
+}
+
+// On Android, just statically link in the symbols...
+#[cfg(all(target_os = "android", not(feature = "dynamic_freetype")))]
+macro_rules! ft_dyn_fn {
+ ($($proto:tt)+) => { extern "C" { fn $($proto)+; } }
+}
+
+ft_dyn_fn!(FT_Get_MM_Var(face: FT_Face, desc: *mut *mut FT_MM_Var) -> FT_Error);
+ft_dyn_fn!(FT_Done_MM_Var(library: FT_Library, desc: *mut FT_MM_Var) -> FT_Error);
+ft_dyn_fn!(FT_Set_Var_Design_Coordinates(face: FT_Face, num_vals: FT_UInt, vals: *mut FT_Fixed) -> FT_Error);
+ft_dyn_fn!(FT_Get_Var_Design_Coordinates(face: FT_Face, num_vals: FT_UInt, vals: *mut FT_Fixed) -> FT_Error);
+
+extern "C" {
+ fn FT_GlyphSlot_Embolden(slot: FT_GlyphSlot);
+}
+
+// Custom version of FT_GlyphSlot_Embolden to be less aggressive with outline
+// fonts than the default implementation in FreeType.
+#[no_mangle]
+pub extern "C" fn mozilla_glyphslot_embolden_less(slot: FT_GlyphSlot) {
+ if slot.is_null() {
+ return;
+ }
+
+ let slot_ = unsafe { &mut *slot };
+ let format = slot_.format;
+ if format != FT_Glyph_Format::FT_GLYPH_FORMAT_OUTLINE {
+ // For non-outline glyphs, just fall back to FreeType's function.
+ unsafe { FT_GlyphSlot_Embolden(slot) };
+ return;
+ }
+
+ let face_ = unsafe { *slot_.face };
+
+ // FT_GlyphSlot_Embolden uses a divisor of 24 here; we'll be only half as
+ // bold.
+ let size_ = unsafe { *face_.size };
+ let strength =
+ unsafe { FT_MulFix(face_.units_per_EM as FT_Long,
+ size_.metrics.y_scale) / 48 };
+ unsafe { FT_Outline_Embolden(&mut slot_.outline, strength) };
+
+ // Adjust metrics to suit the fattened glyph.
+ if slot_.advance.x != 0 {
+ slot_.advance.x += strength;
+ }
+ if slot_.advance.y != 0 {
+ slot_.advance.y += strength;
+ }
+ slot_.metrics.width += strength;
+ slot_.metrics.height += strength;
+ slot_.metrics.horiAdvance += strength;
+ slot_.metrics.vertAdvance += strength;
+ slot_.metrics.horiBearingY += strength;
+}
+
+struct CachedFont {
+ template: FontTemplate,
+ face: FT_Face,
+ mm_var: *mut FT_MM_Var,
+ variations: Vec<FontVariation>,
+}
+
+impl Drop for CachedFont {
+ fn drop(&mut self) {
+ unsafe {
+ if !self.mm_var.is_null() &&
+ unimplemented(FT_Done_MM_Var((*(*self.face).glyph).library, self.mm_var)) {
+ free(self.mm_var as _);
+ }
+
+ FT_Done_Face(self.face);
+ }
+ }
+}
+
+struct FontCache {
+ lib: FT_Library,
+ // Maps a template to a cached font that may be used across all threads.
+ fonts: FastHashMap<FontTemplate, Arc<Mutex<CachedFont>>>,
+ // The current LCD filter installed in the library.
+ lcd_filter: FontLCDFilter,
+ // The number of threads currently relying on the LCD filter state.
+ lcd_filter_uses: usize,
+}
+
+// FreeType resources are safe to move between threads as long as they
+// are not concurrently accessed. In our case, everything is behind a
+// Mutex so it is safe to move them between threads.
+unsafe impl Send for CachedFont {}
+unsafe impl Send for FontCache {}
+
+impl FontCache {
+ fn new() -> Self {
+ let mut lib: FT_Library = ptr::null_mut();
+ let result = unsafe { FT_Init_FreeType(&mut lib) };
+ if succeeded(result) {
+ // Ensure the library uses the default LCD filter initially.
+ unsafe { FT_Library_SetLcdFilter(lib, FT_LcdFilter::FT_LCD_FILTER_DEFAULT) };
+ } else {
+ panic!("Failed to initialize FreeType - {}", result)
+ }
+
+ FontCache {
+ lib,
+ fonts: FastHashMap::default(),
+ lcd_filter: FontLCDFilter::Default,
+ lcd_filter_uses: 0,
+ }
+ }
+
+ fn add_font(&mut self, template: FontTemplate) -> Result<Arc<Mutex<CachedFont>>, FT_Error> {
+ if let Some(cached) = self.fonts.get(&template) {
+ return Ok(cached.clone());
+ }
+ unsafe {
+ let mut face: FT_Face = ptr::null_mut();
+ let result = match template {
+ FontTemplate::Raw(ref bytes, index) => {
+ FT_New_Memory_Face(
+ self.lib,
+ bytes.as_ptr(),
+ bytes.len() as FT_Long,
+ index as FT_Long,
+ &mut face,
+ )
+ }
+ FontTemplate::Native(NativeFontHandle { ref path, index }) => {
+ let str = path.as_os_str().to_str().unwrap();
+ let cstr = CString::new(str).unwrap();
+ FT_New_Face(
+ self.lib,
+ cstr.as_ptr(),
+ index as FT_Long,
+ &mut face,
+ )
+ }
+ };
+ if !succeeded(result) || face.is_null() {
+ return Err(result);
+ }
+ let mut mm_var = ptr::null_mut();
+ if ((*face).face_flags & (FT_FACE_FLAG_MULTIPLE_MASTERS as FT_Long)) != 0 &&
+ succeeded(FT_Get_MM_Var(face, &mut mm_var)) {
+ // Calling this before FT_Set_Var_Design_Coordinates avoids a bug with font variations
+ // not initialized properly in the font face, even if we ignore the result.
+ // See bug 1647035.
+ let mut tmp = [0; 16];
+ let res = FT_Get_Var_Design_Coordinates(
+ face,
+ (*mm_var).num_axis.min(16),
+ tmp.as_mut_ptr()
+ );
+ debug_assert!(succeeded(res));
+ }
+ let cached = Arc::new(Mutex::new(CachedFont {
+ template: template.clone(),
+ face,
+ mm_var,
+ variations: Vec::new(),
+ }));
+ self.fonts.insert(template, cached.clone());
+ Ok(cached)
+ }
+ }
+
+ fn delete_font(&mut self, cached: Arc<Mutex<CachedFont>>) {
+ self.fonts.remove(&cached.lock().unwrap().template);
+ }
+}
+
+impl Drop for FontCache {
+ fn drop(&mut self) {
+ self.fonts.clear();
+ unsafe {
+ FT_Done_FreeType(self.lib);
+ }
+ }
+}
+
+lazy_static! {
+ static ref FONT_CACHE: Mutex<FontCache> = Mutex::new(FontCache::new());
+ static ref LCD_FILTER_UNUSED: Condvar = Condvar::new();
+}
+
+pub struct FontContext {
+ fonts: FastHashMap<FontKey, Arc<Mutex<CachedFont>>>,
+}
+
+fn get_skew_bounds(bottom: i32, top: i32, skew_factor: f32, _vertical: bool) -> (f32, f32) {
+ let skew_min = (bottom as f32 + 0.5) * skew_factor;
+ let skew_max = (top as f32 - 0.5) * skew_factor;
+ // Negative skew factor may switch the sense of skew_min and skew_max.
+ (skew_min.min(skew_max).floor(), skew_min.max(skew_max).ceil())
+}
+
+fn skew_bitmap(
+ bitmap: &[u8],
+ width: usize,
+ height: usize,
+ left: i32,
+ top: i32,
+ skew_factor: f32,
+ vertical: bool, // TODO: vertical skew not yet implemented!
+) -> (Vec<u8>, usize, i32) {
+ let stride = width * 4;
+ // Calculate the skewed horizontal offsets of the bottom and top of the glyph.
+ let (skew_min, skew_max) = get_skew_bounds(top - height as i32, top, skew_factor, vertical);
+ // Allocate enough extra width for the min/max skew offsets.
+ let skew_width = width + (skew_max - skew_min) as usize;
+ let mut skew_buffer = vec![0u8; skew_width * height * 4];
+ for y in 0 .. height {
+ // Calculate a skew offset at the vertical center of the current row.
+ let offset = (top as f32 - y as f32 - 0.5) * skew_factor - skew_min;
+ // Get a blend factor in 0..256 constant across all pixels in the row.
+ let blend = (offset.fract() * 256.0) as u32;
+ let src_row = y * stride;
+ let dest_row = (y * skew_width + offset.floor() as usize) * 4;
+ let mut prev_px = [0u32; 4];
+ for (src, dest) in
+ bitmap[src_row .. src_row + stride].chunks(4).zip(
+ skew_buffer[dest_row .. dest_row + stride].chunks_mut(4)
+ ) {
+ let px = [src[0] as u32, src[1] as u32, src[2] as u32, src[3] as u32];
+ // Blend current pixel with previous pixel based on blend factor.
+ let next_px = [px[0] * blend, px[1] * blend, px[2] * blend, px[3] * blend];
+ dest[0] = ((((px[0] << 8) - next_px[0]) + prev_px[0] + 128) >> 8) as u8;
+ dest[1] = ((((px[1] << 8) - next_px[1]) + prev_px[1] + 128) >> 8) as u8;
+ dest[2] = ((((px[2] << 8) - next_px[2]) + prev_px[2] + 128) >> 8) as u8;
+ dest[3] = ((((px[3] << 8) - next_px[3]) + prev_px[3] + 128) >> 8) as u8;
+ // Save the remainder for blending onto the next pixel.
+ prev_px = next_px;
+ }
+ // If the skew misaligns the final pixel, write out the remainder.
+ if blend > 0 {
+ let dest = &mut skew_buffer[dest_row + stride .. dest_row + stride + 4];
+ dest[0] = ((prev_px[0] + 128) >> 8) as u8;
+ dest[1] = ((prev_px[1] + 128) >> 8) as u8;
+ dest[2] = ((prev_px[2] + 128) >> 8) as u8;
+ dest[3] = ((prev_px[3] + 128) >> 8) as u8;
+ }
+ }
+ (skew_buffer, skew_width, left + skew_min as i32)
+}
+
+fn transpose_bitmap(bitmap: &[u8], width: usize, height: usize) -> Vec<u8> {
+ let mut transposed = vec![0u8; width * height * 4];
+ for (y, row) in bitmap.chunks(width * 4).enumerate() {
+ let mut offset = y * 4;
+ for src in row.chunks(4) {
+ transposed[offset .. offset + 4].copy_from_slice(src);
+ offset += height * 4;
+ }
+ }
+ transposed
+}
+
+fn flip_bitmap_x(bitmap: &mut [u8], width: usize, height: usize) {
+ assert!(bitmap.len() == width * height * 4);
+ let pixels = unsafe { slice::from_raw_parts_mut(bitmap.as_mut_ptr() as *mut u32, width * height) };
+ for row in pixels.chunks_mut(width) {
+ row.reverse();
+ }
+}
+
+fn flip_bitmap_y(bitmap: &mut [u8], width: usize, height: usize) {
+ assert!(bitmap.len() == width * height * 4);
+ let pixels = unsafe { slice::from_raw_parts_mut(bitmap.as_mut_ptr() as *mut u32, width * height) };
+ for y in 0 .. height / 2 {
+ let low_row = y * width;
+ let high_row = (height - 1 - y) * width;
+ for x in 0 .. width {
+ pixels.swap(low_row + x, high_row + x);
+ }
+ }
+}
+
+impl FontContext {
+ pub fn distribute_across_threads() -> bool {
+ false
+ }
+
+ pub fn new() -> FontContext {
+ FontContext {
+ fonts: FastHashMap::default(),
+ }
+ }
+
+ pub fn add_raw_font(&mut self, font_key: &FontKey, bytes: Arc<Vec<u8>>, index: u32) {
+ if !self.fonts.contains_key(font_key) {
+ let len = bytes.len();
+ match FONT_CACHE.lock().unwrap().add_font(FontTemplate::Raw(bytes, index)) {
+ Ok(font) => self.fonts.insert(*font_key, font),
+ Err(result) => panic!("adding raw font failed: {} bytes, err={:?}", len, result),
+ };
+ }
+ }
+
+ pub fn add_native_font(&mut self, font_key: &FontKey, native_font_handle: NativeFontHandle) {
+ if !self.fonts.contains_key(font_key) {
+ let path = native_font_handle.path.to_string_lossy().into_owned();
+ match FONT_CACHE.lock().unwrap().add_font(FontTemplate::Native(native_font_handle)) {
+ Ok(font) => self.fonts.insert(*font_key, font),
+ Err(result) => panic!("adding native font failed: file={} err={:?}", path, result),
+ };
+ }
+ }
+
+ pub fn delete_font(&mut self, font_key: &FontKey) {
+ if let Some(cached) = self.fonts.remove(font_key) {
+ // If the only references to this font are the FontCache and this FontContext,
+ // then delete the font as there are no other existing users.
+ if Arc::strong_count(&cached) <= 2 {
+ FONT_CACHE.lock().unwrap().delete_font(cached);
+ }
+ }
+ }
+
+ pub fn delete_font_instance(&mut self, _instance: &FontInstance) {
+ }
+
+ fn load_glyph(&mut self, font: &FontInstance, glyph: &GlyphKey)
+ -> Option<(MutexGuard<CachedFont>, FT_GlyphSlot, f32)> {
+ let mut cached = self.fonts.get(&font.font_key)?.lock().ok()?;
+ let face = cached.face;
+
+ let mm_var = cached.mm_var;
+ if !mm_var.is_null() && font.variations != cached.variations {
+ cached.variations.clear();
+ cached.variations.extend_from_slice(&font.variations);
+
+ unsafe {
+ let num_axis = (*mm_var).num_axis;
+ let mut coords: Vec<FT_Fixed> = Vec::with_capacity(num_axis as usize);
+ for i in 0 .. num_axis {
+ let axis = (*mm_var).axis.offset(i as isize);
+ let mut value = (*axis).def;
+ for var in &font.variations {
+ if var.tag as FT_ULong == (*axis).tag {
+ value = (var.value * 65536.0 + 0.5) as FT_Fixed;
+ value = cmp::min(value, (*axis).maximum);
+ value = cmp::max(value, (*axis).minimum);
+ break;
+ }
+ }
+ coords.push(value);
+ }
+ let res = FT_Set_Var_Design_Coordinates(face, num_axis, coords.as_mut_ptr());
+ debug_assert!(succeeded(res));
+ }
+ }
+
+ let mut load_flags = FT_LOAD_DEFAULT;
+ let FontInstancePlatformOptions { mut hinting, .. } = font.platform_options.unwrap_or_default();
+ // Disable hinting if there is a non-axis-aligned transform.
+ if font.synthetic_italics.is_enabled() ||
+ ((font.transform.scale_x != 0.0 || font.transform.scale_y != 0.0) &&
+ (font.transform.skew_x != 0.0 || font.transform.skew_y != 0.0)) {
+ hinting = FontHinting::None;
+ }
+ match (hinting, font.render_mode) {
+ (FontHinting::None, _) => load_flags |= FT_LOAD_NO_HINTING,
+ (FontHinting::Mono, _) => load_flags = FT_LOAD_TARGET_MONO,
+ (FontHinting::Light, _) => load_flags = FT_LOAD_TARGET_LIGHT,
+ (FontHinting::LCD, FontRenderMode::Subpixel) => {
+ load_flags = if font.flags.contains(FontInstanceFlags::LCD_VERTICAL) {
+ FT_LOAD_TARGET_LCD_V
+ } else {
+ FT_LOAD_TARGET_LCD
+ };
+ if font.flags.contains(FontInstanceFlags::FORCE_AUTOHINT) {
+ load_flags |= FT_LOAD_FORCE_AUTOHINT;
+ }
+ }
+ _ => {
+ if font.flags.contains(FontInstanceFlags::FORCE_AUTOHINT) {
+ load_flags |= FT_LOAD_FORCE_AUTOHINT;
+ }
+ }
+ }
+
+ if font.flags.contains(FontInstanceFlags::NO_AUTOHINT) {
+ load_flags |= FT_LOAD_NO_AUTOHINT;
+ }
+ if !font.flags.contains(FontInstanceFlags::EMBEDDED_BITMAPS) {
+ load_flags |= FT_LOAD_NO_BITMAP;
+ }
+
+ let face_flags = unsafe { (*face).face_flags };
+ if (face_flags & (FT_FACE_FLAG_FIXED_SIZES as FT_Long)) != 0 {
+ // We only set FT_LOAD_COLOR if there are bitmap strikes;
+ // COLR (color-layer) fonts are handled internally by Gecko, and
+ // WebRender is just asked to paint individual layers.
+ load_flags |= FT_LOAD_COLOR;
+ }
+
+ load_flags |= FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH;
+
+ let (x_scale, y_scale) = font.transform.compute_scale().unwrap_or((1.0, 1.0));
+ let req_size = font.size.to_f64_px();
+
+ let mut result = if (face_flags & (FT_FACE_FLAG_FIXED_SIZES as FT_Long)) != 0 &&
+ (face_flags & (FT_FACE_FLAG_SCALABLE as FT_Long)) == 0 &&
+ (load_flags & FT_LOAD_NO_BITMAP) == 0 {
+ unsafe { FT_Set_Transform(face, ptr::null_mut(), ptr::null_mut()) };
+ self.choose_bitmap_size(face, req_size * y_scale)
+ } else {
+ let mut shape = font.transform.invert_scale(x_scale, y_scale);
+ if font.flags.contains(FontInstanceFlags::FLIP_X) {
+ shape = shape.flip_x();
+ }
+ if font.flags.contains(FontInstanceFlags::FLIP_Y) {
+ shape = shape.flip_y();
+ }
+ if font.flags.contains(FontInstanceFlags::TRANSPOSE) {
+ shape = shape.swap_xy();
+ }
+ let (mut tx, mut ty) = (0.0, 0.0);
+ if font.synthetic_italics.is_enabled() {
+ let (shape_, (tx_, ty_)) = font.synthesize_italics(shape, y_scale * req_size);
+ shape = shape_;
+ tx = tx_;
+ ty = ty_;
+ };
+ let mut ft_shape = FT_Matrix {
+ xx: (shape.scale_x * 65536.0) as FT_Fixed,
+ xy: (shape.skew_x * -65536.0) as FT_Fixed,
+ yx: (shape.skew_y * -65536.0) as FT_Fixed,
+ yy: (shape.scale_y * 65536.0) as FT_Fixed,
+ };
+ // The delta vector for FT_Set_Transform is in units of 1/64 pixel.
+ let mut ft_delta = FT_Vector {
+ x: (tx * 64.0) as FT_F26Dot6,
+ y: (ty * -64.0) as FT_F26Dot6,
+ };
+ unsafe {
+ FT_Set_Transform(face, &mut ft_shape, &mut ft_delta);
+ FT_Set_Char_Size(
+ face,
+ (req_size * x_scale * 64.0 + 0.5) as FT_F26Dot6,
+ (req_size * y_scale * 64.0 + 0.5) as FT_F26Dot6,
+ 0,
+ 0,
+ )
+ }
+ };
+
+ if !succeeded(result) {
+ error!("Unable to set glyph size and transform: {}", result);
+ //let raw_error = unsafe { FT_Error_String(result) };
+ //if !raw_error.is_ptr() {
+ // error!("\tcode {:?}", CStr::from_ptr(raw_error));
+ //}
+ debug!(
+ "\t[{}] for size {:?} and scale {:?} from font {:?}",
+ glyph.index(),
+ req_size,
+ (x_scale, y_scale),
+ font.font_key,
+ );
+ return None;
+ }
+
+ result = unsafe { FT_Load_Glyph(face, glyph.index() as FT_UInt, load_flags as FT_Int32) };
+ if !succeeded(result) {
+ error!("Unable to load glyph: {}", result);
+ //let raw_error = unsafe { FT_Error_String(result) };
+ //if !raw_error.is_ptr() {
+ // error!("\tcode {:?}", CStr::from_ptr(raw_error));
+ //}
+ debug!(
+ "\t[{}] with flags {:?} from font {:?}",
+ glyph.index(),
+ load_flags,
+ font.font_key,
+ );
+ return None;
+ }
+
+ let slot = unsafe { (*face).glyph };
+ assert!(slot != ptr::null_mut());
+
+ if font.flags.contains(FontInstanceFlags::SYNTHETIC_BOLD) {
+ mozilla_glyphslot_embolden_less(slot);
+ }
+
+ let format = unsafe { (*slot).format };
+ match format {
+ FT_Glyph_Format::FT_GLYPH_FORMAT_BITMAP => {
+ let bitmap_size = unsafe { (*(*(*slot).face).size).metrics.y_ppem };
+ Some((cached, slot, req_size as f32 / bitmap_size as f32))
+ }
+ FT_Glyph_Format::FT_GLYPH_FORMAT_OUTLINE => Some((cached, slot, 1.0)),
+ _ => {
+ error!("Unsupported format");
+ debug!("format={:?}", format);
+ None
+ }
+ }
+ }
+
+ fn pad_bounding_box(font: &FontInstance, cbox: &mut FT_BBox) {
+ // Apply extra pixel of padding for subpixel AA, due to the filter.
+ if font.render_mode == FontRenderMode::Subpixel {
+ // Using an LCD filter may add one full pixel to each side if support is built in.
+ // As of FreeType 2.8.1, an LCD filter is always used regardless of settings
+ // if support for the patent-encumbered LCD filter algorithms is not built in.
+ // Thus, the only reasonable way to guess padding is to unconditonally add it if
+ // subpixel AA is used.
+ let lcd_extra_pixels = 1;
+ let padding = (lcd_extra_pixels * 64) as FT_Pos;
+ if font.flags.contains(FontInstanceFlags::LCD_VERTICAL) {
+ cbox.yMin -= padding;
+ cbox.yMax += padding;
+ } else {
+ cbox.xMin -= padding;
+ cbox.xMax += padding;
+ }
+ }
+ }
+
+ // Get the bounding box for a glyph, accounting for sub-pixel positioning.
+ fn get_bounding_box(
+ slot: FT_GlyphSlot,
+ font: &FontInstance,
+ glyph: &GlyphKey,
+ scale: f32,
+ ) -> FT_BBox {
+ // Get the estimated bounding box from FT (control points).
+ let mut cbox = FT_BBox { xMin: 0, yMin: 0, xMax: 0, yMax: 0 };
+
+ unsafe {
+ FT_Outline_Get_CBox(&(*slot).outline, &mut cbox);
+ }
+
+ // For spaces and other non-printable characters, early out.
+ if unsafe { (*slot).outline.n_contours } == 0 {
+ return cbox;
+ }
+
+ Self::pad_bounding_box(font, &mut cbox);
+
+ // Offset the bounding box by subpixel positioning.
+ // Convert to 26.6 fixed point format for FT.
+ let (dx, dy) = font.get_subpx_offset(glyph);
+ let (dx, dy) = (
+ (dx / scale as f64 * 64.0 + 0.5) as FT_Pos,
+ -(dy / scale as f64 * 64.0 + 0.5) as FT_Pos,
+ );
+ cbox.xMin += dx;
+ cbox.xMax += dx;
+ cbox.yMin += dy;
+ cbox.yMax += dy;
+
+ // Outset the box to device pixel boundaries
+ cbox.xMin &= !63;
+ cbox.yMin &= !63;
+ cbox.xMax = (cbox.xMax + 63) & !63;
+ cbox.yMax = (cbox.yMax + 63) & !63;
+
+ cbox
+ }
+
+ fn get_glyph_dimensions_impl(
+ slot: FT_GlyphSlot,
+ font: &FontInstance,
+ glyph: &GlyphKey,
+ scale: f32,
+ use_transform: bool,
+ ) -> Option<GlyphDimensions> {
+ let format = unsafe { (*slot).format };
+ let (mut left, mut top, mut width, mut height) = match format {
+ FT_Glyph_Format::FT_GLYPH_FORMAT_BITMAP => {
+ unsafe { (
+ (*slot).bitmap_left as i32,
+ (*slot).bitmap_top as i32,
+ (*slot).bitmap.width as i32,
+ (*slot).bitmap.rows as i32,
+ ) }
+ }
+ FT_Glyph_Format::FT_GLYPH_FORMAT_OUTLINE => {
+ let cbox = Self::get_bounding_box(slot, font, glyph, scale);
+ (
+ (cbox.xMin >> 6) as i32,
+ (cbox.yMax >> 6) as i32,
+ ((cbox.xMax - cbox.xMin) >> 6) as i32,
+ ((cbox.yMax - cbox.yMin) >> 6) as i32,
+ )
+ }
+ _ => return None,
+ };
+ let mut advance = unsafe { (*slot).metrics.horiAdvance as f32 / 64.0 };
+ if use_transform {
+ if scale != 1.0 {
+ let x0 = left as f32 * scale;
+ let x1 = width as f32 * scale + x0;
+ let y1 = top as f32 * scale;
+ let y0 = y1 - height as f32 * scale;
+ left = x0.round() as i32;
+ top = y1.round() as i32;
+ width = (x1.ceil() - x0.floor()) as i32;
+ height = (y1.ceil() - y0.floor()) as i32;
+ advance *= scale;
+ }
+ // An outline glyph's cbox would have already been transformed inside FT_Load_Glyph,
+ // so only handle bitmap glyphs which are not handled by FT_Load_Glyph.
+ if format == FT_Glyph_Format::FT_GLYPH_FORMAT_BITMAP {
+ if font.synthetic_italics.is_enabled() {
+ let (skew_min, skew_max) = get_skew_bounds(
+ top - height as i32,
+ top,
+ font.synthetic_italics.to_skew(),
+ font.flags.contains(FontInstanceFlags::VERTICAL),
+ );
+ left += skew_min as i32;
+ width += (skew_max - skew_min) as i32;
+ }
+ if font.flags.contains(FontInstanceFlags::TRANSPOSE) {
+ mem::swap(&mut width, &mut height);
+ mem::swap(&mut left, &mut top);
+ left -= width as i32;
+ top += height as i32;
+ }
+ if font.flags.contains(FontInstanceFlags::FLIP_X) {
+ left = -(left + width as i32);
+ }
+ if font.flags.contains(FontInstanceFlags::FLIP_Y) {
+ top = -(top - height as i32);
+ }
+ }
+ }
+ Some(GlyphDimensions {
+ left,
+ top,
+ width,
+ height,
+ advance,
+ })
+ }
+
+ pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> {
+ let cached = self.fonts.get(&font_key)?.lock().ok()?;
+ let face = cached.face;
+ unsafe {
+ let idx = FT_Get_Char_Index(face, ch as _);
+ if idx != 0 {
+ Some(idx)
+ } else {
+ None
+ }
+ }
+ }
+
+ pub fn get_glyph_dimensions(
+ &mut self,
+ font: &FontInstance,
+ key: &GlyphKey,
+ ) -> Option<GlyphDimensions> {
+ let (_cached, slot, scale) = self.load_glyph(font, key)?;
+ Self::get_glyph_dimensions_impl(slot, &font, key, scale, true)
+ }
+
+ fn choose_bitmap_size(&self, face: FT_Face, requested_size: f64) -> FT_Error {
+ let mut best_dist = unsafe { *(*face).available_sizes.offset(0) }.y_ppem as f64 / 64.0 - requested_size;
+ let mut best_size = 0;
+ let num_fixed_sizes = unsafe { (*face).num_fixed_sizes };
+ for i in 1 .. num_fixed_sizes {
+ // Distance is positive if strike is larger than desired size,
+ // or negative if smaller. If previously a found smaller strike,
+ // then prefer a larger strike. Otherwise, minimize distance.
+ let dist = unsafe { *(*face).available_sizes.offset(i as isize) }.y_ppem as f64 / 64.0 - requested_size;
+ if (best_dist < 0.0 && dist >= best_dist) || dist.abs() <= best_dist {
+ best_dist = dist;
+ best_size = i;
+ }
+ }
+ unsafe { FT_Select_Size(face, best_size) }
+ }
+
+ pub fn prepare_font(font: &mut FontInstance) {
+ match font.render_mode {
+ FontRenderMode::Mono => {
+ // In mono mode the color of the font is irrelevant.
+ font.color = ColorU::new(0xFF, 0xFF, 0xFF, 0xFF);
+ // Subpixel positioning is disabled in mono mode.
+ font.disable_subpixel_position();
+ }
+ FontRenderMode::Alpha | FontRenderMode::Subpixel => {
+ // We don't do any preblending with FreeType currently, so the color is not used.
+ font.color = ColorU::new(0xFF, 0xFF, 0xFF, 0xFF);
+ }
+ }
+ }
+
+ fn rasterize_glyph_outline(
+ slot: FT_GlyphSlot,
+ font: &FontInstance,
+ key: &GlyphKey,
+ scale: f32,
+ ) -> bool {
+ // Get the subpixel offsets in FT 26.6 format.
+ let (dx, dy) = font.get_subpx_offset(key);
+ let (dx, dy) = (
+ (dx / scale as f64 * 64.0 + 0.5) as FT_Pos,
+ -(dy / scale as f64 * 64.0 + 0.5) as FT_Pos,
+ );
+
+ // Move the outline curves to be at the origin, taking
+ // into account the subpixel positioning.
+ unsafe {
+ let outline = &(*slot).outline;
+ let mut cbox = FT_BBox { xMin: 0, yMin: 0, xMax: 0, yMax: 0 };
+ FT_Outline_Get_CBox(outline, &mut cbox);
+ Self::pad_bounding_box(font, &mut cbox);
+ FT_Outline_Translate(
+ outline,
+ dx - ((cbox.xMin + dx) & !63),
+ dy - ((cbox.yMin + dy) & !63),
+ );
+ }
+
+ let render_mode = match font.render_mode {
+ FontRenderMode::Mono => FT_Render_Mode::FT_RENDER_MODE_MONO,
+ FontRenderMode::Alpha => FT_Render_Mode::FT_RENDER_MODE_NORMAL,
+ FontRenderMode::Subpixel => if font.flags.contains(FontInstanceFlags::LCD_VERTICAL) {
+ FT_Render_Mode::FT_RENDER_MODE_LCD_V
+ } else {
+ FT_Render_Mode::FT_RENDER_MODE_LCD
+ },
+ };
+ let result = unsafe { FT_Render_Glyph(slot, render_mode) };
+ if !succeeded(result) {
+ error!("Unable to rasterize");
+ debug!(
+ "{:?} with {:?}, {:?}",
+ key,
+ render_mode,
+ result
+ );
+ false
+ } else {
+ true
+ }
+ }
+
+ pub fn begin_rasterize(font: &FontInstance) {
+ // The global LCD filter state is only used in subpixel rendering modes.
+ if font.render_mode == FontRenderMode::Subpixel {
+ let mut cache = FONT_CACHE.lock().unwrap();
+ let FontInstancePlatformOptions { lcd_filter, .. } = font.platform_options.unwrap_or_default();
+ // Check if the current LCD filter matches the requested one.
+ if cache.lcd_filter != lcd_filter {
+ // If the filter doesn't match, we have to wait for all other currently rasterizing threads
+ // that may use the LCD filter state to finish before we can override it.
+ while cache.lcd_filter_uses != 0 {
+ cache = LCD_FILTER_UNUSED.wait(cache).unwrap();
+ }
+ // Finally set the LCD filter to the requested one now that the library is unused.
+ cache.lcd_filter = lcd_filter;
+ let filter = match lcd_filter {
+ FontLCDFilter::None => FT_LcdFilter::FT_LCD_FILTER_NONE,
+ FontLCDFilter::Default => FT_LcdFilter::FT_LCD_FILTER_DEFAULT,
+ FontLCDFilter::Light => FT_LcdFilter::FT_LCD_FILTER_LIGHT,
+ FontLCDFilter::Legacy => FT_LcdFilter::FT_LCD_FILTER_LEGACY,
+ };
+ unsafe {
+ let result = FT_Library_SetLcdFilter(cache.lib, filter);
+ // Setting the legacy filter may fail, so just use the default filter instead.
+ if !succeeded(result) {
+ FT_Library_SetLcdFilter(cache.lib, FT_LcdFilter::FT_LCD_FILTER_DEFAULT);
+ }
+ }
+ }
+ cache.lcd_filter_uses += 1;
+ }
+ }
+
+ pub fn end_rasterize(font: &FontInstance) {
+ if font.render_mode == FontRenderMode::Subpixel {
+ let mut cache = FONT_CACHE.lock().unwrap();
+ // If this is the last use of the LCD filter, then signal that the LCD filter isn't used.
+ cache.lcd_filter_uses -= 1;
+ if cache.lcd_filter_uses == 0 {
+ LCD_FILTER_UNUSED.notify_all();
+ }
+ }
+ }
+
+ pub fn rasterize_glyph(&mut self, font: &FontInstance, key: &GlyphKey) -> GlyphRasterResult {
+ let (_cached, slot, scale) = self.load_glyph(font, key)
+ .ok_or(GlyphRasterError::LoadFailed)?;
+
+ // Get dimensions of the glyph, to see if we need to rasterize it.
+ // Don't apply scaling to the dimensions, as the glyph cache needs to know the actual
+ // footprint of the glyph.
+ let dimensions = Self::get_glyph_dimensions_impl(slot, font, key, scale, false)
+ .ok_or(GlyphRasterError::LoadFailed)?;
+ let GlyphDimensions { mut left, mut top, width, height, .. } = dimensions;
+
+ // For spaces and other non-printable characters, early out.
+ if width == 0 || height == 0 {
+ return Err(GlyphRasterError::LoadFailed);
+ }
+
+ let format = unsafe { (*slot).format };
+ match format {
+ FT_Glyph_Format::FT_GLYPH_FORMAT_BITMAP => {}
+ FT_Glyph_Format::FT_GLYPH_FORMAT_OUTLINE => {
+ if !Self::rasterize_glyph_outline(slot, font, key, scale) {
+ return Err(GlyphRasterError::LoadFailed);
+ }
+ }
+ _ => {
+ error!("Unsupported format");
+ debug!("format={:?}", format);
+ return Err(GlyphRasterError::LoadFailed);
+ }
+ };
+
+ debug!(
+ "Rasterizing {:?} as {:?} with dimensions {:?}",
+ key,
+ font.render_mode,
+ dimensions
+ );
+
+ let bitmap = unsafe { &(*slot).bitmap };
+ let pixel_mode = unsafe { mem::transmute(bitmap.pixel_mode as u32) };
+ let (mut actual_width, mut actual_height) = match pixel_mode {
+ FT_Pixel_Mode::FT_PIXEL_MODE_LCD => {
+ assert!(bitmap.width % 3 == 0);
+ ((bitmap.width / 3) as usize, bitmap.rows as usize)
+ }
+ FT_Pixel_Mode::FT_PIXEL_MODE_LCD_V => {
+ assert!(bitmap.rows % 3 == 0);
+ (bitmap.width as usize, (bitmap.rows / 3) as usize)
+ }
+ FT_Pixel_Mode::FT_PIXEL_MODE_MONO |
+ FT_Pixel_Mode::FT_PIXEL_MODE_GRAY |
+ FT_Pixel_Mode::FT_PIXEL_MODE_BGRA => {
+ (bitmap.width as usize, bitmap.rows as usize)
+ }
+ _ => panic!("Unsupported mode"),
+ };
+
+ // If we need padding, we will need to expand the buffer size.
+ let (buffer_width, buffer_height, padding) = if font.use_texture_padding() {
+ (actual_width + 2, actual_height + 2, 1)
+ } else {
+ (actual_width, actual_height, 0)
+ };
+
+ let mut final_buffer = vec![0u8; buffer_width * buffer_height * 4];
+
+ // Extract the final glyph from FT format into BGRA8 format, which is
+ // what WR expects.
+ let subpixel_bgr = font.flags.contains(FontInstanceFlags::SUBPIXEL_BGR);
+ let mut src_row = bitmap.buffer;
+ let mut dest = 4 * padding * (padding + buffer_width);
+ let actual_end = final_buffer.len() - 4 * padding * (buffer_width + 1);
+ while dest < actual_end {
+ let mut src = src_row;
+ let row_end = dest + actual_width * 4;
+ match pixel_mode {
+ FT_Pixel_Mode::FT_PIXEL_MODE_MONO => {
+ while dest < row_end {
+ // Cast the byte to signed so that we can left shift each bit into
+ // the top bit, then right shift to fill out the bits with 0s or 1s.
+ let mut byte: i8 = unsafe { *src as i8 };
+ src = unsafe { src.offset(1) };
+ let byte_end = cmp::min(row_end, dest + 8 * 4);
+ while dest < byte_end {
+ let alpha = (byte >> 7) as u8;
+ final_buffer[dest + 0] = alpha;
+ final_buffer[dest + 1] = alpha;
+ final_buffer[dest + 2] = alpha;
+ final_buffer[dest + 3] = alpha;
+ dest += 4;
+ byte <<= 1;
+ }
+ }
+ }
+ FT_Pixel_Mode::FT_PIXEL_MODE_GRAY => {
+ while dest < row_end {
+ let alpha = unsafe { *src };
+ final_buffer[dest + 0] = alpha;
+ final_buffer[dest + 1] = alpha;
+ final_buffer[dest + 2] = alpha;
+ final_buffer[dest + 3] = alpha;
+ src = unsafe { src.offset(1) };
+ dest += 4;
+ }
+ }
+ FT_Pixel_Mode::FT_PIXEL_MODE_LCD => {
+ while dest < row_end {
+ let (mut r, g, mut b) = unsafe { (*src, *src.offset(1), *src.offset(2)) };
+ if subpixel_bgr {
+ mem::swap(&mut r, &mut b);
+ }
+ final_buffer[dest + 0] = b;
+ final_buffer[dest + 1] = g;
+ final_buffer[dest + 2] = r;
+ final_buffer[dest + 3] = max(max(b, g), r);
+ src = unsafe { src.offset(3) };
+ dest += 4;
+ }
+ }
+ FT_Pixel_Mode::FT_PIXEL_MODE_LCD_V => {
+ while dest < row_end {
+ let (mut r, g, mut b) =
+ unsafe { (*src, *src.offset(bitmap.pitch as isize),
+ *src.offset((2 * bitmap.pitch) as isize)) };
+ if subpixel_bgr {
+ mem::swap(&mut r, &mut b);
+ }
+ final_buffer[dest + 0] = b;
+ final_buffer[dest + 1] = g;
+ final_buffer[dest + 2] = r;
+ final_buffer[dest + 3] = max(max(b, g), r);
+ src = unsafe { src.offset(1) };
+ dest += 4;
+ }
+ src_row = unsafe { src_row.offset((2 * bitmap.pitch) as isize) };
+ }
+ FT_Pixel_Mode::FT_PIXEL_MODE_BGRA => {
+ // The source is premultiplied BGRA data.
+ let dest_slice = &mut final_buffer[dest .. row_end];
+ let src_slice = unsafe { slice::from_raw_parts(src, dest_slice.len()) };
+ dest_slice.copy_from_slice(src_slice);
+ }
+ _ => panic!("Unsupported mode"),
+ }
+ src_row = unsafe { src_row.offset(bitmap.pitch as isize) };
+ dest = row_end + 8 * padding;
+ }
+
+ if font.use_texture_padding() {
+ left -= padding as i32;
+ top += padding as i32;
+ actual_width = buffer_width;
+ actual_height = buffer_height;
+ }
+
+ match format {
+ FT_Glyph_Format::FT_GLYPH_FORMAT_BITMAP => {
+ if font.synthetic_italics.is_enabled() {
+ let (skew_buffer, skew_width, skew_left) = skew_bitmap(
+ &final_buffer,
+ actual_width,
+ actual_height,
+ left,
+ top,
+ font.synthetic_italics.to_skew(),
+ font.flags.contains(FontInstanceFlags::VERTICAL),
+ );
+ final_buffer = skew_buffer;
+ actual_width = skew_width;
+ left = skew_left;
+ }
+ if font.flags.contains(FontInstanceFlags::TRANSPOSE) {
+ final_buffer = transpose_bitmap(&final_buffer, actual_width, actual_height);
+ mem::swap(&mut actual_width, &mut actual_height);
+ mem::swap(&mut left, &mut top);
+ left -= actual_width as i32;
+ top += actual_height as i32;
+ }
+ if font.flags.contains(FontInstanceFlags::FLIP_X) {
+ flip_bitmap_x(&mut final_buffer, actual_width, actual_height);
+ left = -(left + actual_width as i32);
+ }
+ if font.flags.contains(FontInstanceFlags::FLIP_Y) {
+ flip_bitmap_y(&mut final_buffer, actual_width, actual_height);
+ top = -(top - actual_height as i32);
+ }
+ }
+ FT_Glyph_Format::FT_GLYPH_FORMAT_OUTLINE => {
+ unsafe {
+ left += (*slot).bitmap_left;
+ top += (*slot).bitmap_top - height as i32;
+ }
+ }
+ _ => {}
+ }
+
+ let glyph_format = match (pixel_mode, format) {
+ (FT_Pixel_Mode::FT_PIXEL_MODE_LCD, _) |
+ (FT_Pixel_Mode::FT_PIXEL_MODE_LCD_V, _) => font.get_subpixel_glyph_format(),
+ (FT_Pixel_Mode::FT_PIXEL_MODE_BGRA, _) => GlyphFormat::ColorBitmap,
+ (_, FT_Glyph_Format::FT_GLYPH_FORMAT_BITMAP) => GlyphFormat::Bitmap,
+ _ => font.get_alpha_glyph_format(),
+ };
+
+ Ok(RasterizedGlyph {
+ left: left as f32,
+ top: top as f32,
+ width: actual_width as i32,
+ height: actual_height as i32,
+ scale,
+ format: glyph_format,
+ bytes: final_buffer,
+ })
+ }
+}
+
diff --git a/gfx/wr/wr_glyph_rasterizer/src/platform/windows/font.rs b/gfx/wr/wr_glyph_rasterizer/src/platform/windows/font.rs
new file mode 100644
index 0000000000..81232d70c4
--- /dev/null
+++ b/gfx/wr/wr_glyph_rasterizer/src/platform/windows/font.rs
@@ -0,0 +1,639 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+use api::{FontInstanceFlags, FontKey, FontRenderMode, FontVariation};
+use api::{ColorU, GlyphDimensions, NativeFontHandle};
+use dwrote;
+use crate::gamma_lut::{ColorLut, GammaLut};
+use crate::rasterizer::{FontInstance, FontTransform, GlyphKey};
+use crate::rasterizer::{GlyphFormat, GlyphRasterError, GlyphRasterResult, RasterizedGlyph};
+use crate::rasterizer::apply_multistrike_bold;
+use crate::types::{FastHashMap, FastHashSet};
+use std::borrow::Borrow;
+use std::collections::hash_map::Entry;
+use std::hash::{Hash, Hasher};
+use std::path::Path;
+use std::sync::{Arc, Mutex};
+use api::FontInstancePlatformOptions;
+use std::mem;
+
+lazy_static! {
+ static ref DEFAULT_FONT_DESCRIPTOR: dwrote::FontDescriptor = dwrote::FontDescriptor {
+ family_name: "Arial".to_owned(),
+ weight: dwrote::FontWeight::Regular,
+ stretch: dwrote::FontStretch::Normal,
+ style: dwrote::FontStyle::Normal,
+ };
+}
+
+type CachedFontKey = Arc<Path>;
+
+// A cached dwrote font file that is shared among all faces.
+// Each face holds a CachedFontKey to keep track of how many users of the font there are.
+struct CachedFont {
+ key: CachedFontKey,
+ file: dwrote::FontFile,
+}
+
+// FontFile contains a ComPtr<IDWriteFontFile>, but DWrite font files are threadsafe.
+unsafe impl Send for CachedFont {}
+
+impl PartialEq for CachedFont {
+ fn eq(&self, other: &CachedFont) -> bool {
+ self.key == other.key
+ }
+}
+impl Eq for CachedFont {}
+
+impl Hash for CachedFont {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.key.hash(state);
+ }
+}
+
+impl Borrow<Path> for CachedFont {
+ fn borrow(&self) -> &Path {
+ &*self.key
+ }
+}
+
+lazy_static! {
+ // This is effectively a weak map of dwrote FontFiles. CachedFonts are entered into the
+ // cache when there are any FontFaces using them. CachedFonts are removed from the cache
+ // when there are no more FontFaces using them at all.
+ static ref FONT_CACHE: Mutex<FastHashSet<CachedFont>> = Mutex::new(FastHashSet::default());
+}
+
+struct FontFace {
+ cached: Option<CachedFontKey>,
+ file: dwrote::FontFile,
+ index: u32,
+ face: dwrote::FontFace,
+}
+
+pub struct FontContext {
+ fonts: FastHashMap<FontKey, FontFace>,
+ variations: FastHashMap<(FontKey, dwrote::DWRITE_FONT_SIMULATIONS, Vec<FontVariation>), dwrote::FontFace>,
+ gamma_luts: FastHashMap<(u16, u8), GammaLut>,
+}
+
+// DirectWrite is safe to use on multiple threads and non-shareable resources are
+// all hidden inside their font context.
+unsafe impl Send for FontContext {}
+
+fn dwrite_texture_type(render_mode: FontRenderMode) -> dwrote::DWRITE_TEXTURE_TYPE {
+ match render_mode {
+ FontRenderMode::Mono => dwrote::DWRITE_TEXTURE_ALIASED_1x1,
+ FontRenderMode::Alpha |
+ FontRenderMode::Subpixel => dwrote::DWRITE_TEXTURE_CLEARTYPE_3x1,
+ }
+}
+
+fn dwrite_measure_mode(
+ font: &FontInstance,
+ bitmaps: bool,
+) -> dwrote::DWRITE_MEASURING_MODE {
+ if bitmaps || font.flags.contains(FontInstanceFlags::FORCE_GDI) {
+ dwrote::DWRITE_MEASURING_MODE_GDI_CLASSIC
+ } else {
+ match font.render_mode {
+ FontRenderMode::Mono => dwrote::DWRITE_MEASURING_MODE_GDI_CLASSIC,
+ FontRenderMode::Alpha | FontRenderMode::Subpixel => dwrote::DWRITE_MEASURING_MODE_NATURAL,
+ }
+ }
+}
+
+fn dwrite_render_mode(
+ font_face: &dwrote::FontFace,
+ font: &FontInstance,
+ em_size: f32,
+ measure_mode: dwrote::DWRITE_MEASURING_MODE,
+ bitmaps: bool,
+) -> dwrote::DWRITE_RENDERING_MODE {
+ let dwrite_render_mode = match font.render_mode {
+ FontRenderMode::Mono => dwrote::DWRITE_RENDERING_MODE_ALIASED,
+ FontRenderMode::Alpha | FontRenderMode::Subpixel => {
+ if bitmaps || font.flags.contains(FontInstanceFlags::FORCE_GDI) {
+ dwrote::DWRITE_RENDERING_MODE_GDI_CLASSIC
+ } else if font.flags.contains(FontInstanceFlags::FORCE_SYMMETRIC) {
+ dwrote::DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL_SYMMETRIC
+ } else if font.flags.contains(FontInstanceFlags::NO_SYMMETRIC) {
+ dwrote::DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL
+ } else {
+ font_face.get_recommended_rendering_mode_default_params(em_size, 1.0, measure_mode)
+ }
+ }
+ };
+
+ if dwrite_render_mode == dwrote::DWRITE_RENDERING_MODE_OUTLINE {
+ // Outline mode is not supported
+ return dwrote::DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL_SYMMETRIC;
+ }
+
+ dwrite_render_mode
+}
+
+fn is_bitmap_font(font: &FontInstance) -> bool {
+ // If bitmaps are requested, then treat as a bitmap font to disable transforms.
+ // If mono AA is requested, let that take priority over using bitmaps.
+ font.render_mode != FontRenderMode::Mono &&
+ font.flags.contains(FontInstanceFlags::EMBEDDED_BITMAPS)
+}
+
+impl FontContext {
+ pub fn distribute_across_threads() -> bool {
+ true
+ }
+
+ pub fn new() -> FontContext {
+ FontContext {
+ fonts: FastHashMap::default(),
+ variations: FastHashMap::default(),
+ gamma_luts: FastHashMap::default(),
+ }
+ }
+
+ fn add_font_descriptor(&mut self, font_key: &FontKey, desc: &dwrote::FontDescriptor) {
+ let system_fc = dwrote::FontCollection::get_system(false);
+ if let Some(font) = system_fc.get_font_from_descriptor(desc) {
+ let face = font.create_font_face();
+ let file = face.get_files().pop().unwrap();
+ let index = face.get_index();
+ self.fonts.insert(*font_key, FontFace { cached: None, file, index, face });
+ }
+ }
+
+ pub fn add_raw_font(&mut self, font_key: &FontKey, data: Arc<Vec<u8>>, index: u32) {
+ if self.fonts.contains_key(font_key) {
+ return;
+ }
+
+ if let Some(file) = dwrote::FontFile::new_from_data(data) {
+ if let Ok(face) = file.create_face(index, dwrote::DWRITE_FONT_SIMULATIONS_NONE) {
+ self.fonts.insert(*font_key, FontFace { cached: None, file, index, face });
+ return;
+ }
+ }
+ // XXX add_raw_font needs to have a way to return an error
+ debug!("DWrite WR failed to load font from data, using Arial instead");
+ self.add_font_descriptor(font_key, &DEFAULT_FONT_DESCRIPTOR);
+ }
+
+ pub fn add_native_font(&mut self, font_key: &FontKey, font_handle: NativeFontHandle) {
+ if self.fonts.contains_key(font_key) {
+ return;
+ }
+
+ let index = font_handle.index;
+ let mut cache = FONT_CACHE.lock().unwrap();
+ // Check to see if the font is already in the cache. If so, reuse it.
+ if let Some(font) = cache.get(font_handle.path.as_path()) {
+ if let Ok(face) = font.file.create_face(index, dwrote::DWRITE_FONT_SIMULATIONS_NONE) {
+ self.fonts.insert(
+ *font_key,
+ FontFace { cached: Some(font.key.clone()), file: font.file.clone(), index, face },
+ );
+ return;
+ }
+ }
+ if let Some(file) = dwrote::FontFile::new_from_path(&font_handle.path) {
+ // The font is not in the cache yet, so try to create the font and insert it in the cache.
+ if let Ok(face) = file.create_face(index, dwrote::DWRITE_FONT_SIMULATIONS_NONE) {
+ let key: CachedFontKey = font_handle.path.into();
+ self.fonts.insert(
+ *font_key,
+ FontFace { cached: Some(key.clone()), file: file.clone(), index, face },
+ );
+ cache.insert(CachedFont { key, file });
+ return;
+ }
+ }
+
+ // XXX add_native_font needs to have a way to return an error
+ debug!("DWrite WR failed to load font from path, using Arial instead");
+ self.add_font_descriptor(font_key, &DEFAULT_FONT_DESCRIPTOR);
+ }
+
+ pub fn delete_font(&mut self, font_key: &FontKey) {
+ if let Some(face) = self.fonts.remove(font_key) {
+ self.variations.retain(|k, _| k.0 != *font_key);
+ // Check if this was a cached font.
+ if let Some(key) = face.cached {
+ let mut cache = FONT_CACHE.lock().unwrap();
+ // If there are only two references left, that means only this face and
+ // the cache are using the font. So remove it from the cache.
+ if Arc::strong_count(&key) == 2 {
+ cache.remove(&*key);
+ }
+ }
+ }
+ }
+
+ pub fn delete_font_instance(&mut self, instance: &FontInstance) {
+ // Ensure we don't keep around excessive amounts of stale variations.
+ if !instance.variations.is_empty() {
+ let sims = if instance.flags.contains(FontInstanceFlags::SYNTHETIC_BOLD) {
+ dwrote::DWRITE_FONT_SIMULATIONS_BOLD
+ } else {
+ dwrote::DWRITE_FONT_SIMULATIONS_NONE
+ };
+ self.variations.remove(&(instance.font_key, sims, instance.variations.clone()));
+ }
+ }
+
+ // Assumes RGB format from dwrite, which is 3 bytes per pixel as dwrite
+ // doesn't output an alpha value via GlyphRunAnalysis::CreateAlphaTexture
+ #[allow(dead_code)]
+ fn print_glyph_data(&self, data: &[u8], width: usize, height: usize) {
+ // Rust doesn't have step_by support on stable :(
+ for i in 0 .. height {
+ let current_height = i * width * 3;
+
+ for pixel in data[current_height .. current_height + (width * 3)].chunks(3) {
+ let r = pixel[0];
+ let g = pixel[1];
+ let b = pixel[2];
+ debug!("({}, {}, {}) ", r, g, b,);
+ }
+ }
+ }
+
+ fn get_font_face(
+ &mut self,
+ font: &FontInstance,
+ ) -> &dwrote::FontFace {
+ if !font.flags.contains(FontInstanceFlags::SYNTHETIC_BOLD) &&
+ font.variations.is_empty() {
+ return &self.fonts.get(&font.font_key).unwrap().face;
+ }
+ let sims = if font.flags.contains(FontInstanceFlags::SYNTHETIC_BOLD) {
+ dwrote::DWRITE_FONT_SIMULATIONS_BOLD
+ } else {
+ dwrote::DWRITE_FONT_SIMULATIONS_NONE
+ };
+ match self.variations.entry((font.font_key, sims, font.variations.clone())) {
+ Entry::Occupied(entry) => entry.into_mut(),
+ Entry::Vacant(entry) => {
+ let normal_face = self.fonts.get(&font.font_key).unwrap();
+ if !font.variations.is_empty() {
+ if let Some(var_face) = normal_face.face.create_font_face_with_variations(
+ sims,
+ &font.variations.iter().map(|var| {
+ dwrote::DWRITE_FONT_AXIS_VALUE {
+ // OpenType tags are big-endian, but DWrite wants little-endian.
+ axisTag: var.tag.swap_bytes(),
+ value: var.value,
+ }
+ }).collect::<Vec<_>>(),
+ ) {
+ return entry.insert(var_face);
+ }
+ }
+ let var_face = normal_face.file
+ .create_face(normal_face.index, sims)
+ .unwrap_or_else(|_| normal_face.face.clone());
+ entry.insert(var_face)
+ }
+ }
+ }
+
+ fn create_glyph_analysis(
+ &mut self,
+ font: &FontInstance,
+ key: &GlyphKey,
+ size: f32,
+ transform: Option<dwrote::DWRITE_MATRIX>,
+ bitmaps: bool,
+ ) -> Result<(dwrote::GlyphRunAnalysis, dwrote::DWRITE_TEXTURE_TYPE, dwrote::RECT), dwrote::HRESULT> {
+ let face = self.get_font_face(font);
+ let glyph = key.index() as u16;
+ let advance = 0.0f32;
+ let offset = dwrote::GlyphOffset {
+ advanceOffset: 0.0,
+ ascenderOffset: 0.0,
+ };
+
+ let glyph_run = dwrote::DWRITE_GLYPH_RUN {
+ fontFace: unsafe { face.as_ptr() },
+ fontEmSize: size, // size in DIPs (1/96", same as CSS pixels)
+ glyphCount: 1,
+ glyphIndices: &glyph,
+ glyphAdvances: &advance,
+ glyphOffsets: &offset,
+ isSideways: 0,
+ bidiLevel: 0,
+ };
+
+ let dwrite_measure_mode = dwrite_measure_mode(font, bitmaps);
+ let dwrite_render_mode = dwrite_render_mode(
+ face,
+ font,
+ size,
+ dwrite_measure_mode,
+ bitmaps,
+ );
+
+ let analysis = dwrote::GlyphRunAnalysis::create(
+ &glyph_run,
+ 1.0,
+ transform,
+ dwrite_render_mode,
+ dwrite_measure_mode,
+ 0.0,
+ 0.0,
+ )?;
+ let texture_type = dwrite_texture_type(font.render_mode);
+ let bounds = analysis.get_alpha_texture_bounds(texture_type)?;
+ // If the bounds are empty, then we might not be able to render the glyph with cleartype.
+ // Try again with aliased rendering to check if that works instead.
+ if font.render_mode != FontRenderMode::Mono &&
+ (bounds.left == bounds.right || bounds.top == bounds.bottom) {
+ let analysis2 = dwrote::GlyphRunAnalysis::create(
+ &glyph_run,
+ 1.0,
+ transform,
+ dwrote::DWRITE_RENDERING_MODE_ALIASED,
+ dwrite_measure_mode,
+ 0.0,
+ 0.0,
+ )?;
+ let bounds2 = analysis2.get_alpha_texture_bounds(dwrote::DWRITE_TEXTURE_ALIASED_1x1)?;
+ if bounds2.left != bounds2.right && bounds2.top != bounds2.bottom {
+ return Ok((analysis2, dwrote::DWRITE_TEXTURE_ALIASED_1x1, bounds2));
+ }
+ }
+ Ok((analysis, texture_type, bounds))
+ }
+
+ pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> {
+ let face = &self.fonts.get(&font_key).unwrap().face;
+ let indices = face.get_glyph_indices(&[ch as u32]);
+ indices.first().map(|idx| *idx as u32)
+ }
+
+ pub fn get_glyph_dimensions(
+ &mut self,
+ font: &FontInstance,
+ key: &GlyphKey,
+ ) -> Option<GlyphDimensions> {
+ let (size, x_scale, y_scale, bitmaps, transform) = Self::get_glyph_parameters(font, key);
+ let (_, _, bounds) = self.create_glyph_analysis(font, key, size, transform, bitmaps).ok()?;
+
+ let width = (bounds.right - bounds.left) as i32;
+ let height = (bounds.bottom - bounds.top) as i32;
+
+ // Alpha texture bounds can sometimes return an empty rect
+ // Such as for spaces
+ if width == 0 || height == 0 {
+ return None;
+ }
+
+ let (strike_scale, pixel_step) = if bitmaps {
+ (y_scale, 1.0)
+ } else {
+ (x_scale, y_scale / x_scale)
+ };
+ let extra_strikes = font.get_extra_strikes(FontInstanceFlags::MULTISTRIKE_BOLD, strike_scale);
+ let extra_width = extra_strikes as f64 * pixel_step;
+
+ let face = self.get_font_face(font);
+ face.get_design_glyph_metrics(&[key.index() as u16], false)
+ .first()
+ .map(|metrics| {
+ let em_size = size / 16.;
+ let design_units_per_pixel = face.metrics().metrics0().designUnitsPerEm as f32 / 16. as f32;
+ let scaled_design_units_to_pixels = em_size / design_units_per_pixel;
+ let advance = metrics.advanceWidth as f32 * scaled_design_units_to_pixels;
+
+ GlyphDimensions {
+ left: bounds.left,
+ top: -bounds.top,
+ width: width + extra_width.ceil() as i32,
+ height,
+ advance: advance + extra_width as f32,
+ }
+ })
+ }
+
+ // DWrite ClearType gives us values in RGB, but WR expects BGRA.
+ fn convert_to_bgra(
+ &self,
+ pixels: &[u8],
+ width: usize,
+ height: usize,
+ texture_type: dwrote::DWRITE_TEXTURE_TYPE,
+ render_mode: FontRenderMode,
+ bitmaps: bool,
+ subpixel_bgr: bool,
+ padding: usize,
+ ) -> (Vec<u8>, bool) {
+ let (buffer_width, buffer_height) = (width + padding * 2, height + padding * 2);
+ let buffer_length = buffer_width * buffer_height * 4;
+ let mut bgra_pixels: Vec<u8> = vec![0; buffer_length];
+
+ match (texture_type, render_mode, bitmaps) {
+ (dwrote::DWRITE_TEXTURE_ALIASED_1x1, _, _) => {
+ assert!(width * height == pixels.len());
+ let mut i = 0;
+ for row in padding .. height + padding {
+ let row_offset = row * buffer_width;
+ for col in padding .. width + padding {
+ let offset = (row_offset + col) * 4;
+ let alpha = pixels[i];
+ i += 1;
+ bgra_pixels[offset + 0] = alpha;
+ bgra_pixels[offset + 1] = alpha;
+ bgra_pixels[offset + 2] = alpha;
+ bgra_pixels[offset + 3] = alpha;
+ }
+ }
+ (bgra_pixels, false)
+ }
+ (_, FontRenderMode::Subpixel, false) => {
+ assert!(width * height * 3 == pixels.len());
+ let mut i = 0;
+ for row in padding .. height + padding {
+ let row_offset = row * buffer_width;
+ for col in padding .. width + padding {
+ let offset = (row_offset + col) * 4;
+ let (mut r, g, mut b) = (pixels[i + 0], pixels[i + 1], pixels[i + 2]);
+ if subpixel_bgr {
+ mem::swap(&mut r, &mut b);
+ }
+ i += 3;
+ bgra_pixels[offset + 0] = b;
+ bgra_pixels[offset + 1] = g;
+ bgra_pixels[offset + 2] = r;
+ bgra_pixels[offset + 3] = 0xff;
+ }
+ }
+ (bgra_pixels, true)
+ }
+ _ => {
+ assert!(width * height * 3 == pixels.len());
+ let mut i = 0;
+ for row in padding .. height + padding {
+ let row_offset = row * buffer_width;
+ for col in padding .. width + padding {
+ let offset = (row_offset + col) * 4;
+ // Only take the G channel, as its closest to D2D
+ let alpha = pixels[i + 1] as u8;
+ i += 3;
+ bgra_pixels[offset + 0] = alpha;
+ bgra_pixels[offset + 1] = alpha;
+ bgra_pixels[offset + 2] = alpha;
+ bgra_pixels[offset + 3] = alpha;
+ }
+ }
+ (bgra_pixels, false)
+ }
+ }
+ }
+
+ pub fn prepare_font(font: &mut FontInstance) {
+ match font.render_mode {
+ FontRenderMode::Mono => {
+ // In mono mode the color of the font is irrelevant.
+ font.color = ColorU::new(255, 255, 255, 255);
+ // Subpixel positioning is disabled in mono mode.
+ font.disable_subpixel_position();
+ }
+ FontRenderMode::Alpha => {
+ font.color = font.color.luminance_color().quantize();
+ }
+ FontRenderMode::Subpixel => {
+ font.color = font.color.quantize();
+ }
+ }
+ }
+
+ fn get_glyph_parameters(font: &FontInstance, key: &GlyphKey)
+ -> (f32, f64, f64, bool, Option<dwrote::DWRITE_MATRIX>) {
+ let (x_scale, y_scale) = font.transform.compute_scale().unwrap_or((1.0, 1.0));
+ let scaled_size = font.size.to_f64_px() * y_scale;
+ let bitmaps = is_bitmap_font(font);
+ let (mut shape, (mut x_offset, mut y_offset)) = if bitmaps {
+ (FontTransform::identity(), (0.0, 0.0))
+ } else {
+ (font.transform.invert_scale(y_scale, y_scale), font.get_subpx_offset(key))
+ };
+ if font.flags.contains(FontInstanceFlags::FLIP_X) {
+ shape = shape.flip_x();
+ }
+ if font.flags.contains(FontInstanceFlags::FLIP_Y) {
+ shape = shape.flip_y();
+ }
+ if font.flags.contains(FontInstanceFlags::TRANSPOSE) {
+ shape = shape.swap_xy();
+ }
+ let (mut tx, mut ty) = (0.0, 0.0);
+ if font.synthetic_italics.is_enabled() {
+ let (shape_, (tx_, ty_)) = font.synthesize_italics(shape, scaled_size);
+ shape = shape_;
+ tx = tx_;
+ ty = ty_;
+ };
+ x_offset += tx;
+ y_offset += ty;
+ let transform = if !shape.is_identity() || (x_offset, y_offset) != (0.0, 0.0) {
+ Some(dwrote::DWRITE_MATRIX {
+ m11: shape.scale_x,
+ m12: shape.skew_y,
+ m21: shape.skew_x,
+ m22: shape.scale_y,
+ dx: x_offset as f32,
+ dy: y_offset as f32,
+ })
+ } else {
+ None
+ };
+ (scaled_size as f32, x_scale, y_scale, bitmaps, transform)
+ }
+
+ pub fn begin_rasterize(_font: &FontInstance) {
+ }
+
+ pub fn end_rasterize(_font: &FontInstance) {
+ }
+
+ pub fn rasterize_glyph(&mut self, font: &FontInstance, key: &GlyphKey) -> GlyphRasterResult {
+ let (size, x_scale, y_scale, bitmaps, transform) = Self::get_glyph_parameters(font, key);
+ let (analysis, texture_type, bounds) = self.create_glyph_analysis(font, key, size, transform, bitmaps)
+ .or(Err(GlyphRasterError::LoadFailed))?;
+ let mut width = (bounds.right - bounds.left) as i32;
+ let height = (bounds.bottom - bounds.top) as i32;
+ // Alpha texture bounds can sometimes return an empty rect
+ // Such as for spaces
+ if width == 0 || height == 0 {
+ return Err(GlyphRasterError::LoadFailed);
+ }
+
+ let pixels = analysis.create_alpha_texture(texture_type, bounds).or(Err(GlyphRasterError::LoadFailed))?;
+ let padding = if font.use_texture_padding() { 1 } else { 0 };
+ let (mut bgra_pixels, is_subpixel) = self.convert_to_bgra(
+ &pixels,
+ width as usize,
+ height as usize,
+ texture_type,
+ font.render_mode,
+ bitmaps,
+ font.flags.contains(FontInstanceFlags::SUBPIXEL_BGR),
+ padding as usize,
+ );
+
+ // Apply multistrike bold, if necessary, and replace the current pixels with it.
+ let (strike_scale, pixel_step) = if bitmaps {
+ (y_scale, 1.0)
+ } else {
+ (x_scale, y_scale / x_scale)
+ };
+ let extra_strikes = font.get_extra_strikes(FontInstanceFlags::MULTISTRIKE_BOLD, strike_scale);
+ if extra_strikes > 0 {
+ let (bold_pixels, bold_width) = apply_multistrike_bold(
+ &bgra_pixels,
+ (width + padding * 2) as usize,
+ (height + padding * 2) as usize,
+ is_subpixel,
+ extra_strikes,
+ pixel_step,
+ );
+ width = bold_width as i32 - padding * 2;
+ bgra_pixels = bold_pixels;
+ }
+
+ let FontInstancePlatformOptions { gamma, contrast, cleartype_level, .. } =
+ font.platform_options.unwrap_or_default();
+ let gamma_lut = self.gamma_luts
+ .entry((gamma, contrast))
+ .or_insert_with(||
+ GammaLut::new(
+ contrast as f32 / 100.0,
+ gamma as f32 / 100.0,
+ gamma as f32 / 100.0,
+ ));
+ if is_subpixel {
+ gamma_lut.preblend_scaled(&mut bgra_pixels, font.color, cleartype_level);
+ } else {
+ gamma_lut.preblend(&mut bgra_pixels, font.color);
+ }
+
+ let format = if bitmaps {
+ GlyphFormat::Bitmap
+ } else if texture_type == dwrote::DWRITE_TEXTURE_ALIASED_1x1 {
+ font.get_alpha_glyph_format()
+ } else {
+ font.get_glyph_format()
+ };
+
+ Ok(RasterizedGlyph {
+ left: (bounds.left - padding) as f32,
+ top: (-bounds.top + padding) as f32,
+ width: width + padding * 2,
+ height: height + padding * 2,
+ scale: (if bitmaps { y_scale.recip() } else { 1.0 }) as f32,
+ format,
+ bytes: bgra_pixels,
+ })
+ }
+}
diff --git a/gfx/wr/wr_glyph_rasterizer/src/profiler.rs b/gfx/wr/wr_glyph_rasterizer/src/profiler.rs
new file mode 100644
index 0000000000..89d126e3fc
--- /dev/null
+++ b/gfx/wr/wr_glyph_rasterizer/src/profiler.rs
@@ -0,0 +1,12 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+/// A profiler for profiling rasterize time.
+pub trait GlyphRasterizeProfiler {
+ fn start_time(&mut self);
+
+ fn end_time(&mut self) -> f64;
+
+ fn set(&mut self, value: f64);
+}
diff --git a/gfx/wr/wr_glyph_rasterizer/src/rasterizer.rs b/gfx/wr/wr_glyph_rasterizer/src/rasterizer.rs
new file mode 100644
index 0000000000..5153e60c54
--- /dev/null
+++ b/gfx/wr/wr_glyph_rasterizer/src/rasterizer.rs
@@ -0,0 +1,1879 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+use api::{FontInstanceData, FontInstanceFlags, FontInstanceKey};
+use api::{FontInstanceOptions, FontInstancePlatformOptions};
+use api::{FontKey, FontRenderMode, FontSize, FontTemplate, FontVariation};
+use api::{ColorU, GlyphIndex, GlyphDimensions, SyntheticItalics};
+use api::{IdNamespace, BlobImageResources};
+use api::channel::crossbeam::{unbounded, Receiver, Sender};
+use api::units::*;
+use api::ImageFormat;
+use crate::platform::font::FontContext;
+use crate::profiler::GlyphRasterizeProfiler;
+use crate::types::{FastHashMap, FastHashSet};
+use crate::telemetry::Telemetry;
+use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
+use rayon::ThreadPool;
+use rayon::prelude::*;
+use euclid::approxeq::ApproxEq;
+use smallvec::SmallVec;
+use std::cmp;
+use std::cell::Cell;
+use std::hash::{Hash, Hasher};
+use std::mem;
+use std::ops::Deref;
+use std::sync::{Arc, Condvar, Mutex, MutexGuard, Weak};
+use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
+use std::sync::atomic::{AtomicBool, Ordering};
+
+pub static GLYPH_FLASHING: AtomicBool = AtomicBool::new(false);
+
+impl FontContexts {
+ /// Get access to the font context associated to the current thread.
+ pub fn lock_current_context(&self) -> MutexGuard<FontContext> {
+ match self.current_worker_id() {
+ Some(id) => self.lock_context(id),
+ None => self.lock_any_context(),
+ }
+ }
+
+ pub(in super) fn current_worker_id(&self) -> Option<usize> {
+ self.workers.current_thread_index()
+ }
+}
+
+thread_local! {
+ pub static SEED: Cell<u32> = Cell::new(0);
+}
+
+// super simple random to avoid dependency on rand
+fn random() -> u32 {
+ SEED.with(|seed| {
+ seed.set(seed.get().wrapping_mul(22695477).wrapping_add(1));
+ seed.get()
+ })
+}
+
+impl GlyphRasterizer {
+ pub fn request_glyphs<F>(
+ &mut self,
+ font: FontInstance,
+ glyph_keys: &[GlyphKey],
+ mut handle: F,
+ )
+ where F: FnMut(&GlyphKey) -> bool
+ {
+ assert!(self.has_font(font.font_key));
+
+ let mut batch_size = 0;
+
+ // select glyphs that have not been requested yet.
+ for key in glyph_keys {
+ if !handle(key) {
+ continue;
+ }
+
+ // Increment the total number of glyphs that are pending. This is used to determine
+ // later whether to use worker threads for the remaining glyphs during resolve time.
+ self.pending_glyph_count += 1;
+ self.glyph_request_count += 1;
+
+ // Find a batch container for the font instance for this glyph. Use get_mut to avoid
+ // cloning the font instance, since this is the common path.
+ match self.pending_glyph_requests.get_mut(&font) {
+ Some(container) => {
+ container.push(*key);
+ batch_size = container.len();
+ }
+ None => {
+ // If no batch exists for this font instance, add the glyph to a new one.
+ self.pending_glyph_requests.insert(
+ font.clone(),
+ smallvec![*key],
+ );
+ }
+ }
+ }
+
+ // If the batch for this font instance is big enough, kick off an async
+ // job to start rasterizing these glyphs on other threads now.
+ if batch_size >= 8 {
+ let container = self.pending_glyph_requests.get_mut(&font).unwrap();
+ let glyphs = mem::replace(container, SmallVec::new());
+ self.flush_glyph_requests(font, glyphs, true);
+ }
+ }
+
+ pub fn enable_multithreading(&mut self, enable: bool) {
+ self.enable_multithreading = enable;
+ }
+
+ /// Internal method to flush a list of glyph requests to a set of worker threads,
+ /// or process on this thread if there isn't much work to do (in which case the
+ /// overhead of processing these on a thread is unlikely to be a performance win).
+ fn flush_glyph_requests(
+ &mut self,
+ font: FontInstance,
+ glyphs: SmallVec<[GlyphKey; 16]>,
+ use_workers: bool,
+ ) {
+ let font = Arc::new(font);
+ let font_contexts = Arc::clone(&self.font_contexts);
+ self.pending_glyph_jobs += glyphs.len();
+ self.pending_glyph_count -= glyphs.len();
+
+ let can_use_r8_format = self.can_use_r8_format;
+
+ let job_font = font.clone();
+ let process_glyph = move |key: &GlyphKey| -> GlyphRasterJob {
+ profile_scope!("glyph-raster");
+ let mut context = font_contexts.lock_current_context();
+ let mut job = GlyphRasterJob {
+ font: Arc::clone(&job_font),
+ key: key.clone(),
+ result: context.rasterize_glyph(&job_font, key),
+ };
+
+ if let Ok(ref mut glyph) = job.result {
+ // Sanity check.
+ let bpp = 4; // We always render glyphs in 32 bits RGBA format.
+ assert_eq!(
+ glyph.bytes.len(),
+ bpp * (glyph.width * glyph.height) as usize
+ );
+
+ // a quick-and-dirty monochrome over
+ fn over(dst: u8, src: u8) -> u8 {
+ let a = src as u32;
+ let a = 256 - a;
+ let dst = ((dst as u32 * a) >> 8) as u8;
+ src + dst
+ }
+
+ if GLYPH_FLASHING.load(Ordering::Relaxed) {
+ let color = (random() & 0xff) as u8;
+ for i in &mut glyph.bytes {
+ *i = over(*i, color);
+ }
+ }
+
+ assert_eq!((glyph.left.fract(), glyph.top.fract()), (0.0, 0.0));
+
+ // Check if the glyph has a bitmap that needs to be downscaled.
+ glyph.downscale_bitmap_if_required(&job_font);
+
+ // Convert from BGRA8 to R8 if required. In the future we can make it the
+ // backends' responsibility to output glyphs in the desired format,
+ // potentially reducing the number of copies.
+ if glyph.format.image_format(can_use_r8_format).bytes_per_pixel() == 1 {
+ glyph.bytes = glyph.bytes
+ .chunks_mut(4)
+ .map(|pixel| pixel[3])
+ .collect::<Vec<_>>();
+ }
+ }
+
+ job
+ };
+
+ // if the number of glyphs is small, do it inline to avoid the threading overhead;
+ // send the result into glyph_tx so downstream code can't tell the difference.
+ if self.enable_multithreading && use_workers {
+ // spawn an async task to get off of the render backend thread as early as
+ // possible and in that task use rayon's fork join dispatch to rasterize the
+ // glyphs in the thread pool.
+ profile_scope!("spawning process_glyph jobs");
+ self.workers.install(|| {
+ FontContext::begin_rasterize(&font);
+ // If the FontContext supports distributing a font across multiple threads,
+ // then use par_iter so different glyphs of the same font are processed on
+ // multiple threads.
+ if FontContext::distribute_across_threads() {
+ glyphs.par_iter().for_each(|key| {
+ let job = process_glyph(key);
+ self.glyph_tx.send(job).unwrap();
+ });
+ } else {
+ // For FontContexts that prefer to localize a font to a single thread,
+ // just process all the glyphs on the same worker to avoid contention.
+ for key in glyphs {
+ let job = process_glyph(&key);
+ self.glyph_tx.send(job).unwrap();
+ }
+ }
+ FontContext::end_rasterize(&font);
+ });
+ } else {
+ FontContext::begin_rasterize(&font);
+ for key in glyphs {
+ let job = process_glyph(&key);
+ self.glyph_tx.send(job).unwrap();
+ }
+ FontContext::end_rasterize(&font);
+ }
+ }
+
+ pub fn resolve_glyphs<F, G>(
+ &mut self,
+ mut handle: F,
+ profile: &mut G,
+ )
+ where
+ F: FnMut(GlyphRasterJob, bool),
+ G: GlyphRasterizeProfiler,
+ {
+ profile.start_time();
+ let timer_id = Telemetry::start_rasterize_glyphs_time();
+
+ // Work around the borrow checker, since we call flush_glyph_requests below
+ let mut pending_glyph_requests = mem::replace(
+ &mut self.pending_glyph_requests,
+ FastHashMap::default(),
+ );
+ // If we have a large amount of remaining work to do, spawn to worker threads,
+ // even if that work is shared among a number of different font instances.
+ let use_workers = self.pending_glyph_count >= 8;
+ for (font, pending_glyphs) in pending_glyph_requests.drain() {
+ self.flush_glyph_requests(
+ font,
+ pending_glyphs,
+ use_workers,
+ );
+ }
+ // Restore this so that we don't heap allocate next frame
+ self.pending_glyph_requests = pending_glyph_requests;
+ debug_assert_eq!(self.pending_glyph_count, 0);
+ debug_assert!(self.pending_glyph_requests.is_empty());
+
+ if self.glyph_request_count > 0 {
+ profile.set(self.glyph_request_count as f64);
+ self.glyph_request_count = 0;
+ }
+
+ profile_scope!("resolve_glyphs");
+ // TODO: rather than blocking until all pending glyphs are available
+ // we could try_recv and steal work from the thread pool to take advantage
+ // of the fact that this thread is alive and we avoid the added latency
+ // of blocking it.
+ let mut jobs = {
+ profile_scope!("blocking wait on glyph_rx");
+ self.glyph_rx.iter().take(self.pending_glyph_jobs).collect::<Vec<_>>()
+ };
+ assert_eq!(jobs.len(), self.pending_glyph_jobs, "BUG: Didn't receive all pending glyphs!");
+ self.pending_glyph_jobs = 0;
+
+ // Ensure that the glyphs are always processed in the same
+ // order for a given text run (since iterating a hash set doesn't
+ // guarantee order). This can show up as very small float inaccuracy
+ // differences in rasterizers due to the different coordinates
+ // that text runs get associated with by the texture cache allocator.
+ jobs.sort_by(|a, b| (*a.font).cmp(&*b.font).then(a.key.cmp(&b.key)));
+
+ for job in jobs {
+ handle(job, self.can_use_r8_format);
+ }
+
+ // Now that we are done with the critical path (rendering the glyphs),
+ // we can schedule removing the fonts if needed.
+ self.remove_dead_fonts();
+
+ Telemetry::stop_and_accumulate_rasterize_glyphs_time(timer_id);
+ profile.end_time();
+ }
+}
+
+#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, PartialOrd)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct FontTransform {
+ pub scale_x: f32,
+ pub skew_x: f32,
+ pub skew_y: f32,
+ pub scale_y: f32,
+}
+
+// Floats don't impl Hash/Eq/Ord...
+impl Eq for FontTransform {}
+impl Ord for FontTransform {
+ fn cmp(&self, other: &Self) -> cmp::Ordering {
+ self.partial_cmp(other).unwrap_or(cmp::Ordering::Equal)
+ }
+}
+impl Hash for FontTransform {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ // Note: this is inconsistent with the Eq impl for -0.0 (don't care).
+ self.scale_x.to_bits().hash(state);
+ self.skew_x.to_bits().hash(state);
+ self.skew_y.to_bits().hash(state);
+ self.scale_y.to_bits().hash(state);
+ }
+}
+
+impl FontTransform {
+ const QUANTIZE_SCALE: f32 = 1024.0;
+
+ pub fn new(scale_x: f32, skew_x: f32, skew_y: f32, scale_y: f32) -> Self {
+ FontTransform { scale_x, skew_x, skew_y, scale_y }
+ }
+
+ pub fn identity() -> Self {
+ FontTransform::new(1.0, 0.0, 0.0, 1.0)
+ }
+
+ #[allow(dead_code)]
+ pub fn is_identity(&self) -> bool {
+ *self == FontTransform::identity()
+ }
+
+ pub fn quantize(&self) -> Self {
+ FontTransform::new(
+ (self.scale_x * Self::QUANTIZE_SCALE).round() / Self::QUANTIZE_SCALE,
+ (self.skew_x * Self::QUANTIZE_SCALE).round() / Self::QUANTIZE_SCALE,
+ (self.skew_y * Self::QUANTIZE_SCALE).round() / Self::QUANTIZE_SCALE,
+ (self.scale_y * Self::QUANTIZE_SCALE).round() / Self::QUANTIZE_SCALE,
+ )
+ }
+
+ #[allow(dead_code)]
+ pub fn determinant(&self) -> f64 {
+ self.scale_x as f64 * self.scale_y as f64 - self.skew_y as f64 * self.skew_x as f64
+ }
+
+ #[allow(dead_code)]
+ pub fn compute_scale(&self) -> Option<(f64, f64)> {
+ let det = self.determinant();
+ if det != 0.0 {
+ let x_scale = (self.scale_x as f64).hypot(self.skew_y as f64);
+ let y_scale = det.abs() / x_scale;
+ Some((x_scale, y_scale))
+ } else {
+ None
+ }
+ }
+
+ #[allow(dead_code)]
+ pub fn pre_scale(&self, scale_x: f32, scale_y: f32) -> Self {
+ FontTransform::new(
+ self.scale_x * scale_x,
+ self.skew_x * scale_y,
+ self.skew_y * scale_x,
+ self.scale_y * scale_y,
+ )
+ }
+
+ #[allow(dead_code)]
+ pub fn scale(&self, scale: f32) -> Self { self.pre_scale(scale, scale) }
+
+ #[allow(dead_code)]
+ pub fn invert_scale(&self, x_scale: f64, y_scale: f64) -> Self {
+ self.pre_scale(x_scale.recip() as f32, y_scale.recip() as f32)
+ }
+
+ pub fn synthesize_italics(&self, angle: SyntheticItalics, size: f64, vertical: bool) -> (Self, (f64, f64)) {
+ let skew_factor = angle.to_skew();
+ if vertical {
+ // origin delta to be applied so that we effectively skew around
+ // the middle rather than edge of the glyph
+ let (tx, ty) = (0.0, -size * 0.5 * skew_factor as f64);
+ (FontTransform::new(
+ self.scale_x + self.skew_x * skew_factor,
+ self.skew_x,
+ self.skew_y + self.scale_y * skew_factor,
+ self.scale_y,
+ ), (self.scale_x as f64 * tx + self.skew_x as f64 * ty,
+ self.skew_y as f64 * tx + self.scale_y as f64 * ty))
+ } else {
+ (FontTransform::new(
+ self.scale_x,
+ self.skew_x - self.scale_x * skew_factor,
+ self.skew_y,
+ self.scale_y - self.skew_y * skew_factor,
+ ), (0.0, 0.0))
+ }
+ }
+
+ pub fn swap_xy(&self) -> Self {
+ FontTransform::new(self.skew_x, self.scale_x, self.scale_y, self.skew_y)
+ }
+
+ pub fn flip_x(&self) -> Self {
+ FontTransform::new(-self.scale_x, self.skew_x, -self.skew_y, self.scale_y)
+ }
+
+ pub fn flip_y(&self) -> Self {
+ FontTransform::new(self.scale_x, -self.skew_x, self.skew_y, -self.scale_y)
+ }
+
+ pub fn transform(&self, point: &LayoutPoint) -> DevicePoint {
+ DevicePoint::new(
+ self.scale_x * point.x + self.skew_x * point.y,
+ self.skew_y * point.x + self.scale_y * point.y,
+ )
+ }
+
+ pub fn get_subpx_dir(&self) -> SubpixelDirection {
+ if self.skew_y.approx_eq(&0.0) {
+ // The X axis is not projected onto the Y axis
+ SubpixelDirection::Horizontal
+ } else if self.scale_x.approx_eq(&0.0) {
+ // The X axis has been swapped with the Y axis
+ SubpixelDirection::Vertical
+ } else {
+ // Use subpixel precision on all axes
+ SubpixelDirection::Mixed
+ }
+ }
+}
+
+impl<'a> From<&'a LayoutToWorldTransform> for FontTransform {
+ fn from(xform: &'a LayoutToWorldTransform) -> Self {
+ FontTransform::new(xform.m11, xform.m21, xform.m12, xform.m22)
+ }
+}
+
+// Some platforms (i.e. Windows) may have trouble rasterizing glyphs above this size.
+// Ensure glyph sizes are reasonably limited to avoid that scenario.
+pub const FONT_SIZE_LIMIT: f32 = 320.0;
+
+/// Immutable description of a font instance's shared state.
+///
+/// `BaseFontInstance` can be identified by a `FontInstanceKey` to avoid hashing it.
+#[derive(Clone, Debug, Ord, PartialOrd, MallocSizeOf)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct BaseFontInstance {
+ ///
+ pub instance_key: FontInstanceKey,
+ ///
+ pub font_key: FontKey,
+ ///
+ pub size: FontSize,
+ ///
+ pub options: FontInstanceOptions,
+ ///
+ #[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))]
+ pub platform_options: Option<FontInstancePlatformOptions>,
+ ///
+ pub variations: Vec<FontVariation>,
+}
+
+impl BaseFontInstance {
+ pub fn new(
+ instance_key: FontInstanceKey,
+ font_key: FontKey,
+ size: f32,
+ options: Option<FontInstanceOptions>,
+ platform_options: Option<FontInstancePlatformOptions>,
+ variations: Vec<FontVariation>,
+ ) -> Self {
+ BaseFontInstance {
+ instance_key,
+ font_key,
+ size: size.into(),
+ options: options.unwrap_or_default(),
+ platform_options,
+ variations,
+ }
+ }
+}
+
+impl Deref for BaseFontInstance {
+ type Target = FontInstanceOptions;
+ fn deref(&self) -> &FontInstanceOptions {
+ &self.options
+ }
+}
+
+impl Hash for BaseFontInstance {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ // Skip the instance key.
+ self.font_key.hash(state);
+ self.size.hash(state);
+ self.options.hash(state);
+ self.platform_options.hash(state);
+ self.variations.hash(state);
+ }
+}
+
+impl PartialEq for BaseFontInstance {
+ fn eq(&self, other: &BaseFontInstance) -> bool {
+ // Skip the instance key.
+ self.font_key == other.font_key &&
+ self.size == other.size &&
+ self.options == other.options &&
+ self.platform_options == other.platform_options &&
+ self.variations == other.variations
+ }
+}
+impl Eq for BaseFontInstance {}
+
+struct MappedFontKey {
+ font_key: FontKey,
+ template: FontTemplate,
+}
+
+struct FontKeyMapLocked {
+ namespace: IdNamespace,
+ next_id: u32,
+ template_map: FastHashMap<FontTemplate, Arc<MappedFontKey>>,
+ key_map: FastHashMap<FontKey, Arc<MappedFontKey>>,
+}
+
+/// A shared map from fonts key local to a namespace to shared font keys that
+/// can be shared across many namespaces. Local keys are tracked in a hashmap
+/// that stores a strong reference per mapping so that their count can be
+/// tracked. A map of font templates is used to hash font templates to their
+/// final shared key. The shared key will stay alive so long as there are
+/// any strong references to the mapping entry. Care must be taken when
+/// clearing namespaces of shared keys as this may trigger shared font keys
+/// to expire which require individual processing. Shared font keys will be
+/// created within the provided unique namespace.
+#[derive(Clone)]
+pub struct FontKeyMap(Arc<RwLock<FontKeyMapLocked>>);
+
+impl FontKeyMap {
+ pub fn new(namespace: IdNamespace) -> Self {
+ FontKeyMap(Arc::new(RwLock::new(FontKeyMapLocked {
+ namespace,
+ next_id: 1,
+ template_map: FastHashMap::default(),
+ key_map: FastHashMap::default(),
+ })))
+ }
+
+ fn lock(&self) -> RwLockReadGuard<FontKeyMapLocked> {
+ self.0.read().unwrap()
+ }
+
+ fn lock_mut(&mut self) -> RwLockWriteGuard<FontKeyMapLocked> {
+ self.0.write().unwrap()
+ }
+
+ pub fn keys(&self) -> Vec<FontKey> {
+ self.lock().key_map.keys().cloned().collect()
+ }
+
+ pub fn map_key(&self, font_key: &FontKey) -> FontKey {
+ match self.lock().key_map.get(font_key) {
+ Some(mapped) => mapped.font_key,
+ None => *font_key,
+ }
+ }
+
+ pub fn add_key(&mut self, font_key: &FontKey, template: &FontTemplate) -> Option<FontKey> {
+ let mut locked = self.lock_mut();
+ if locked.key_map.contains_key(font_key) {
+ return None;
+ }
+ if let Some(mapped) = locked.template_map.get(template).cloned() {
+ locked.key_map.insert(*font_key, mapped);
+ return None;
+ }
+ let shared_key = FontKey::new(locked.namespace, locked.next_id);
+ locked.next_id += 1;
+ let mapped = Arc::new(MappedFontKey {
+ font_key: shared_key,
+ template: template.clone(),
+ });
+ locked.template_map.insert(template.clone(), mapped.clone());
+ locked.key_map.insert(*font_key, mapped);
+ Some(shared_key)
+ }
+
+ pub fn delete_key(&mut self, font_key: &FontKey) -> Option<FontKey> {
+ let mut locked = self.lock_mut();
+ let mapped = match locked.key_map.remove(font_key) {
+ Some(mapped) => mapped,
+ None => return Some(*font_key),
+ };
+ if Arc::strong_count(&mapped) <= 2 {
+ // Only the last mapped key and template map point to it.
+ locked.template_map.remove(&mapped.template);
+ Some(mapped.font_key)
+ } else {
+ None
+ }
+ }
+
+ pub fn clear_namespace(&mut self, namespace: IdNamespace) -> Vec<FontKey> {
+ let mut locked = self.lock_mut();
+ locked.key_map.retain(|key, _| {
+ if key.0 == namespace {
+ false
+ } else {
+ true
+ }
+ });
+ let mut deleted_keys = Vec::new();
+ locked.template_map.retain(|_, mapped| {
+ if Arc::strong_count(mapped) <= 1 {
+ // Only the template map points to it.
+ deleted_keys.push(mapped.font_key);
+ false
+ } else {
+ true
+ }
+ });
+ deleted_keys
+ }
+}
+
+type FontTemplateMapLocked = FastHashMap<FontKey, FontTemplate>;
+
+/// A map of font keys to font templates that might hold both namespace-local
+/// font templates as well as shared templates.
+#[derive(Clone)]
+pub struct FontTemplateMap(Arc<RwLock<FontTemplateMapLocked>>);
+
+impl FontTemplateMap {
+ pub fn new() -> Self {
+ FontTemplateMap(Arc::new(RwLock::new(FastHashMap::default())))
+ }
+
+ pub fn lock(&self) -> RwLockReadGuard<FontTemplateMapLocked> {
+ self.0.read().unwrap()
+ }
+
+ fn lock_mut(&mut self) -> RwLockWriteGuard<FontTemplateMapLocked> {
+ self.0.write().unwrap()
+ }
+
+ pub fn clear(&mut self) {
+ self.lock_mut().clear();
+ }
+
+ pub fn len(&self) -> usize {
+ self.lock().len()
+ }
+
+ pub fn has_font(&self, key: &FontKey) -> bool {
+ self.lock().contains_key(key)
+ }
+
+ pub fn get_font(&self, key: &FontKey) -> Option<FontTemplate> {
+ self.lock().get(key).cloned()
+ }
+
+ pub fn add_font(&mut self, key: FontKey, template: FontTemplate) -> bool {
+ self.lock_mut().insert(key, template).is_none()
+ }
+
+ pub fn delete_font(&mut self, key: &FontKey) -> Option<FontTemplate> {
+ self.lock_mut().remove(key)
+ }
+
+ pub fn delete_fonts(&mut self, keys: &[FontKey]) {
+ if !keys.is_empty() {
+ let mut map = self.lock_mut();
+ for key in keys {
+ map.remove(key);
+ }
+ }
+ }
+
+ pub fn clear_namespace(&mut self, namespace: IdNamespace) -> Vec<FontKey> {
+ let mut deleted_keys = Vec::new();
+ self.lock_mut().retain(|key, _| {
+ if key.0 == namespace {
+ deleted_keys.push(*key);
+ false
+ } else {
+ true
+ }
+ });
+ deleted_keys
+ }
+}
+
+struct FontInstanceKeyMapLocked {
+ namespace: IdNamespace,
+ next_id: u32,
+ instances: FastHashSet<Arc<BaseFontInstance>>,
+ key_map: FastHashMap<FontInstanceKey, Weak<BaseFontInstance>>,
+}
+
+/// A map of namespace-local font instance keys to shared keys. Weak references
+/// are used to track the liveness of each key mapping as other consumers of
+/// BaseFontInstance might hold strong references to the entry. A mapping from
+/// BaseFontInstance to the shared key is then used to determine which shared
+/// key to assign to that instance. When the weak count of the mapping is zero,
+/// the entry is allowed to expire. Again, care must be taken when clearing
+/// a namespace within the key map as it may cause shared key expirations that
+/// require individual processing. Shared instance keys will be created within
+/// the provided unique namespace.
+#[derive(Clone)]
+pub struct FontInstanceKeyMap(Arc<RwLock<FontInstanceKeyMapLocked>>);
+
+impl FontInstanceKeyMap {
+ pub fn new(namespace: IdNamespace) -> Self {
+ FontInstanceKeyMap(Arc::new(RwLock::new(FontInstanceKeyMapLocked {
+ namespace,
+ next_id: 1,
+ instances: FastHashSet::default(),
+ key_map: FastHashMap::default(),
+ })))
+ }
+
+ fn lock(&self) -> RwLockReadGuard<FontInstanceKeyMapLocked> {
+ self.0.read().unwrap()
+ }
+
+ fn lock_mut(&mut self) -> RwLockWriteGuard<FontInstanceKeyMapLocked> {
+ self.0.write().unwrap()
+ }
+
+ pub fn keys(&self) -> Vec<FontInstanceKey> {
+ self.lock().key_map.keys().cloned().collect()
+ }
+
+ pub fn map_key(&self, key: &FontInstanceKey) -> FontInstanceKey {
+ match self.lock().key_map.get(key).and_then(|weak| weak.upgrade()) {
+ Some(mapped) => mapped.instance_key,
+ None => *key,
+ }
+ }
+
+ pub fn add_key(&mut self, mut instance: BaseFontInstance) -> Option<Arc<BaseFontInstance>> {
+ let mut locked = self.lock_mut();
+ if locked.key_map.contains_key(&instance.instance_key) {
+ return None;
+ }
+ if let Some(weak) = locked.instances.get(&instance).map(|mapped| Arc::downgrade(mapped)) {
+ locked.key_map.insert(instance.instance_key, weak);
+ return None;
+ }
+ let unmapped_key = instance.instance_key;
+ instance.instance_key = FontInstanceKey::new(locked.namespace, locked.next_id);
+ locked.next_id += 1;
+ let shared_instance = Arc::new(instance);
+ locked.instances.insert(shared_instance.clone());
+ locked.key_map.insert(unmapped_key, Arc::downgrade(&shared_instance));
+ Some(shared_instance)
+ }
+
+ pub fn delete_key(&mut self, key: &FontInstanceKey) -> Option<FontInstanceKey> {
+ let mut locked = self.lock_mut();
+ let mapped = match locked.key_map.remove(key).and_then(|weak| weak.upgrade()) {
+ Some(mapped) => mapped,
+ None => return Some(*key),
+ };
+ if Arc::weak_count(&mapped) == 0 {
+ // Only the instance set points to it.
+ locked.instances.remove(&mapped);
+ Some(mapped.instance_key)
+ } else {
+ None
+ }
+ }
+
+ pub fn clear_namespace(&mut self, namespace: IdNamespace) -> Vec<FontInstanceKey> {
+ let mut locked = self.lock_mut();
+ locked.key_map.retain(|key, _| {
+ if key.0 == namespace {
+ false
+ } else {
+ true
+ }
+ });
+ let mut deleted_keys = Vec::new();
+ locked.instances.retain(|mapped| {
+ if Arc::weak_count(mapped) == 0 {
+ // Only the instance set points to it.
+ deleted_keys.push(mapped.instance_key);
+ false
+ } else {
+ true
+ }
+ });
+ deleted_keys
+ }
+}
+
+type FontInstanceMapLocked = FastHashMap<FontInstanceKey, Arc<BaseFontInstance>>;
+
+/// A map of font instance data accessed concurrently from multiple threads.
+#[derive(Clone)]
+pub struct FontInstanceMap(Arc<RwLock<FontInstanceMapLocked>>);
+
+impl FontInstanceMap {
+ /// Creates an empty shared map.
+ pub fn new() -> Self {
+ FontInstanceMap(Arc::new(RwLock::new(FastHashMap::default())))
+ }
+
+ /// Acquires a read lock on the shared map.
+ pub fn lock(&self) -> RwLockReadGuard<FontInstanceMapLocked> {
+ self.0.read().unwrap()
+ }
+
+ /// Acquires a read lock on the shared map.
+ fn lock_mut(&mut self) -> RwLockWriteGuard<FontInstanceMapLocked> {
+ self.0.write().unwrap()
+ }
+
+ ///
+ pub fn clear(&mut self) {
+ self.lock_mut().clear();
+ }
+
+ ///
+ pub fn get_font_instance_data(&self, key: FontInstanceKey) -> Option<FontInstanceData> {
+ match self.lock().get(&key) {
+ Some(instance) => Some(FontInstanceData {
+ font_key: instance.font_key,
+ size: instance.size.into(),
+ options: Some(FontInstanceOptions {
+ render_mode: instance.render_mode,
+ flags: instance.flags,
+ bg_color: instance.bg_color,
+ synthetic_italics: instance.synthetic_italics,
+ }),
+ platform_options: instance.platform_options,
+ variations: instance.variations.clone(),
+ }),
+ None => None,
+ }
+ }
+
+ ///
+ pub fn get_font_instance(&self, instance_key: FontInstanceKey) -> Option<Arc<BaseFontInstance>> {
+ let instance_map = self.lock();
+ instance_map.get(&instance_key).cloned()
+ }
+
+ ///
+ pub fn add_font_instance(&mut self, instance: Arc<BaseFontInstance>) {
+ self.lock_mut().insert(instance.instance_key, instance);
+ }
+
+ ///
+ pub fn delete_font_instance(&mut self, instance_key: FontInstanceKey) {
+ self.lock_mut().remove(&instance_key);
+ }
+
+ ///
+ pub fn delete_font_instances(&mut self, keys: &[FontInstanceKey]) {
+ if !keys.is_empty() {
+ let mut map = self.lock_mut();
+ for key in keys {
+ map.remove(key);
+ }
+ }
+ }
+
+ ///
+ pub fn clear_namespace(&mut self, namespace: IdNamespace) {
+ self.lock_mut().retain(|key, _| key.0 != namespace);
+ }
+}
+
+/// Shared font resources that may need to be passed between multiple threads
+/// such as font templates and font instances. They are individually protected
+/// by locks to ensure safety.
+#[derive(Clone)]
+pub struct SharedFontResources {
+ pub templates: FontTemplateMap,
+ pub instances: FontInstanceMap,
+ pub font_keys: FontKeyMap,
+ pub instance_keys: FontInstanceKeyMap,
+}
+
+impl SharedFontResources {
+ pub fn new(namespace: IdNamespace) -> Self {
+ SharedFontResources {
+ templates: FontTemplateMap::new(),
+ instances: FontInstanceMap::new(),
+ font_keys: FontKeyMap::new(namespace),
+ instance_keys: FontInstanceKeyMap::new(namespace),
+ }
+ }
+}
+
+impl BlobImageResources for SharedFontResources {
+ fn get_font_data(&self, key: FontKey) -> Option<FontTemplate> {
+ let shared_key = self.font_keys.map_key(&key);
+ self.templates.get_font(&shared_key)
+ }
+
+ fn get_font_instance_data(&self, key: FontInstanceKey) -> Option<FontInstanceData> {
+ let shared_key = self.instance_keys.map_key(&key);
+ self.instances.get_font_instance_data(shared_key)
+ }
+}
+
+/// A mutable font instance description.
+///
+/// Performance is sensitive to the size of this structure, so it should only contain
+/// the fields that we need to modify from the original base font instance.
+#[derive(Clone, Debug, Ord, PartialOrd)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct FontInstance {
+ pub base: Arc<BaseFontInstance>,
+ pub transform: FontTransform,
+ pub render_mode: FontRenderMode,
+ pub flags: FontInstanceFlags,
+ pub color: ColorU,
+ // The font size is in *device/raster* pixels, not logical pixels.
+ // It is stored as an f32 since we need sub-pixel sizes.
+ pub size: FontSize,
+}
+
+impl Hash for FontInstance {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ // Hash only the base instance's key to avoid the cost of hashing
+ // the rest.
+ self.base.instance_key.hash(state);
+ self.transform.hash(state);
+ self.render_mode.hash(state);
+ self.flags.hash(state);
+ self.color.hash(state);
+ self.size.hash(state);
+ }
+}
+
+impl PartialEq for FontInstance {
+ fn eq(&self, other: &FontInstance) -> bool {
+ // Compare only the base instance's key.
+ self.base.instance_key == other.base.instance_key &&
+ self.transform == other.transform &&
+ self.render_mode == other.render_mode &&
+ self.flags == other.flags &&
+ self.color == other.color &&
+ self.size == other.size
+ }
+}
+impl Eq for FontInstance {}
+
+impl Deref for FontInstance {
+ type Target = BaseFontInstance;
+ fn deref(&self) -> &BaseFontInstance {
+ self.base.as_ref()
+ }
+}
+
+impl MallocSizeOf for FontInstance {
+ fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { 0 }
+}
+
+impl FontInstance {
+ pub fn new(
+ base: Arc<BaseFontInstance>,
+ color: ColorU,
+ render_mode: FontRenderMode,
+ flags: FontInstanceFlags,
+ ) -> Self {
+ FontInstance {
+ transform: FontTransform::identity(),
+ color,
+ size: base.size,
+ base,
+ render_mode,
+ flags,
+ }
+ }
+
+ pub fn from_base(
+ base: Arc<BaseFontInstance>,
+ ) -> Self {
+ let color = ColorU::new(0, 0, 0, 255);
+ let render_mode = base.render_mode;
+ let flags = base.flags;
+ Self::new(base, color, render_mode, flags)
+ }
+
+ pub fn use_texture_padding(&self) -> bool {
+ self.flags.contains(FontInstanceFlags::TEXTURE_PADDING)
+ }
+
+ pub fn use_transform_glyphs(&self) -> bool {
+ self.flags.contains(FontInstanceFlags::TRANSFORM_GLYPHS)
+ }
+
+ pub fn get_alpha_glyph_format(&self) -> GlyphFormat {
+ if self.use_transform_glyphs() { GlyphFormat::TransformedAlpha } else { GlyphFormat::Alpha }
+ }
+
+ pub fn get_subpixel_glyph_format(&self) -> GlyphFormat {
+ if self.use_transform_glyphs() { GlyphFormat::TransformedSubpixel } else { GlyphFormat::Subpixel }
+ }
+
+ pub fn disable_subpixel_aa(&mut self) {
+ self.render_mode = self.render_mode.limit_by(FontRenderMode::Alpha);
+ }
+
+ pub fn disable_subpixel_position(&mut self) {
+ self.flags.remove(FontInstanceFlags::SUBPIXEL_POSITION);
+ }
+
+ pub fn use_subpixel_position(&self) -> bool {
+ self.flags.contains(FontInstanceFlags::SUBPIXEL_POSITION) &&
+ self.render_mode != FontRenderMode::Mono
+ }
+
+ pub fn get_subpx_dir(&self) -> SubpixelDirection {
+ if self.use_subpixel_position() {
+ let mut subpx_dir = self.transform.get_subpx_dir();
+ if self.flags.contains(FontInstanceFlags::TRANSPOSE) {
+ subpx_dir = subpx_dir.swap_xy();
+ }
+ subpx_dir
+ } else {
+ SubpixelDirection::None
+ }
+ }
+
+ #[allow(dead_code)]
+ pub fn get_subpx_offset(&self, glyph: &GlyphKey) -> (f64, f64) {
+ if self.use_subpixel_position() {
+ let (dx, dy) = glyph.subpixel_offset();
+ (dx.into(), dy.into())
+ } else {
+ (0.0, 0.0)
+ }
+ }
+
+ #[allow(dead_code)]
+ pub fn get_glyph_format(&self) -> GlyphFormat {
+ match self.render_mode {
+ FontRenderMode::Mono | FontRenderMode::Alpha => self.get_alpha_glyph_format(),
+ FontRenderMode::Subpixel => self.get_subpixel_glyph_format(),
+ }
+ }
+
+ #[allow(dead_code)]
+ pub fn get_extra_strikes(&self, flags: FontInstanceFlags, x_scale: f64) -> usize {
+ if self.flags.intersects(flags) {
+ let mut bold_offset = self.size.to_f64_px() / 48.0;
+ if bold_offset < 1.0 {
+ bold_offset = 0.25 + 0.75 * bold_offset;
+ }
+ (bold_offset * x_scale).max(1.0).round() as usize
+ } else {
+ 0
+ }
+ }
+
+ pub fn synthesize_italics(&self, transform: FontTransform, size: f64) -> (FontTransform, (f64, f64)) {
+ transform.synthesize_italics(self.synthetic_italics, size, self.flags.contains(FontInstanceFlags::VERTICAL))
+ }
+
+ #[allow(dead_code)]
+ pub fn get_transformed_size(&self) -> f64 {
+ let (_, y_scale) = self.transform.compute_scale().unwrap_or((1.0, 1.0));
+ self.size.to_f64_px() * y_scale
+ }
+}
+
+#[repr(u32)]
+#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug, Ord, PartialOrd)]
+pub enum SubpixelDirection {
+ None = 0,
+ Horizontal,
+ Vertical,
+ Mixed,
+}
+
+impl SubpixelDirection {
+ // Limit the subpixel direction to what is supported by the glyph format.
+ pub fn limit_by(self, glyph_format: GlyphFormat) -> Self {
+ match glyph_format {
+ GlyphFormat::Bitmap |
+ GlyphFormat::ColorBitmap => SubpixelDirection::None,
+ _ => self,
+ }
+ }
+
+ pub fn swap_xy(self) -> Self {
+ match self {
+ SubpixelDirection::None | SubpixelDirection::Mixed => self,
+ SubpixelDirection::Horizontal => SubpixelDirection::Vertical,
+ SubpixelDirection::Vertical => SubpixelDirection::Horizontal,
+ }
+ }
+}
+
+#[repr(u8)]
+#[derive(Hash, Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub enum SubpixelOffset {
+ Zero = 0,
+ Quarter = 1,
+ Half = 2,
+ ThreeQuarters = 3,
+}
+
+impl SubpixelOffset {
+ // Skia quantizes subpixel offsets into 1/4 increments.
+ // Given the absolute position, return the quantized increment
+ fn quantize(pos: f32) -> Self {
+ // Following the conventions of Gecko and Skia, we want
+ // to quantize the subpixel position, such that abs(pos) gives:
+ // [0.0, 0.125) -> Zero
+ // [0.125, 0.375) -> Quarter
+ // [0.375, 0.625) -> Half
+ // [0.625, 0.875) -> ThreeQuarters,
+ // [0.875, 1.0) -> Zero
+ // The unit tests below check for this.
+ let apos = ((pos - pos.floor()) * 8.0) as i32;
+
+ match apos {
+ 1..=2 => SubpixelOffset::Quarter,
+ 3..=4 => SubpixelOffset::Half,
+ 5..=6 => SubpixelOffset::ThreeQuarters,
+ _ => SubpixelOffset::Zero,
+ }
+ }
+}
+
+impl Into<f64> for SubpixelOffset {
+ fn into(self) -> f64 {
+ match self {
+ SubpixelOffset::Zero => 0.0,
+ SubpixelOffset::Quarter => 0.25,
+ SubpixelOffset::Half => 0.5,
+ SubpixelOffset::ThreeQuarters => 0.75,
+ }
+ }
+}
+
+#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug, Ord, PartialOrd)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct GlyphKey(u32);
+
+impl GlyphKey {
+ pub fn new(
+ index: u32,
+ point: DevicePoint,
+ subpx_dir: SubpixelDirection,
+ ) -> Self {
+ let (dx, dy) = match subpx_dir {
+ SubpixelDirection::None => (0.0, 0.0),
+ SubpixelDirection::Horizontal => (point.x, 0.0),
+ SubpixelDirection::Vertical => (0.0, point.y),
+ SubpixelDirection::Mixed => (point.x, point.y),
+ };
+ let sox = SubpixelOffset::quantize(dx);
+ let soy = SubpixelOffset::quantize(dy);
+ assert_eq!(0, index & 0xF0000000);
+
+ GlyphKey(index | (sox as u32) << 28 | (soy as u32) << 30)
+ }
+
+ pub fn index(&self) -> GlyphIndex {
+ self.0 & 0x0FFFFFFF
+ }
+
+ fn subpixel_offset(&self) -> (SubpixelOffset, SubpixelOffset) {
+ let x = (self.0 >> 28) as u8 & 3;
+ let y = (self.0 >> 30) as u8 & 3;
+ unsafe {
+ (mem::transmute(x), mem::transmute(y))
+ }
+ }
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[allow(dead_code)]
+pub enum GlyphFormat {
+ Alpha,
+ TransformedAlpha,
+ Subpixel,
+ TransformedSubpixel,
+ Bitmap,
+ ColorBitmap,
+}
+
+impl GlyphFormat {
+ /// Returns the ImageFormat that a glyph should be stored as in the texture cache.
+ /// can_use_r8_format should be set false on platforms where we have encountered
+ /// issues with R8 textures, so that we do not use them for glyphs.
+ pub fn image_format(&self, can_use_r8_format: bool) -> ImageFormat {
+ match *self {
+ GlyphFormat::Alpha |
+ GlyphFormat::TransformedAlpha |
+ GlyphFormat::Bitmap => {
+ if can_use_r8_format {
+ ImageFormat::R8
+ } else {
+ ImageFormat::BGRA8
+ }
+ }
+ GlyphFormat::Subpixel |
+ GlyphFormat::TransformedSubpixel |
+ GlyphFormat::ColorBitmap => ImageFormat::BGRA8,
+ }
+ }
+}
+
+#[allow(dead_code)]
+#[inline]
+fn blend_strike_pixel(dest: u8, src: u32, src_alpha: u32) -> u8 {
+ // Assume premultiplied alpha such that src and dest are already multiplied
+ // by their respective alpha values and in range 0..=255. The rounded over
+ // blend is then (src * 255 + dest * (255 - src_alpha) + 128) / 255.
+ // We approximate (x + 128) / 255 as (x + 128 + ((x + 128) >> 8)) >> 8.
+ let x = src * 255 + dest as u32 * (255 - src_alpha) + 128;
+ ((x + (x >> 8)) >> 8) as u8
+}
+
+// Blends a single strike at a given offset into a destination buffer, assuming
+// the destination has been allocated with enough extra space to accommodate the
+// offset.
+#[allow(dead_code)]
+fn blend_strike(
+ dest_bitmap: &mut [u8],
+ src_bitmap: &[u8],
+ width: usize,
+ height: usize,
+ subpixel_mask: bool,
+ offset: f64,
+) {
+ let dest_stride = dest_bitmap.len() / height;
+ let src_stride = width * 4;
+ let offset_integer = offset.floor() as usize * 4;
+ let offset_fract = (offset.fract() * 256.0) as u32;
+ for (src_row, dest_row) in src_bitmap.chunks(src_stride).zip(dest_bitmap.chunks_mut(dest_stride)) {
+ let mut prev_px = [0u32; 4];
+ let dest_row_offset = &mut dest_row[offset_integer .. offset_integer + src_stride];
+ for (src, dest) in src_row.chunks(4).zip(dest_row_offset.chunks_mut(4)) {
+ let px = [src[0] as u32, src[1] as u32, src[2] as u32, src[3] as u32];
+ // Blend current pixel with previous pixel based on fractional offset.
+ let next_px = [px[0] * offset_fract,
+ px[1] * offset_fract,
+ px[2] * offset_fract,
+ px[3] * offset_fract];
+ let offset_px = [(((px[0] << 8) - next_px[0]) + prev_px[0] + 128) >> 8,
+ (((px[1] << 8) - next_px[1]) + prev_px[1] + 128) >> 8,
+ (((px[2] << 8) - next_px[2]) + prev_px[2] + 128) >> 8,
+ (((px[3] << 8) - next_px[3]) + prev_px[3] + 128) >> 8];
+ if subpixel_mask {
+ // Subpixel masks assume each component is an independent weight.
+ dest[0] = blend_strike_pixel(dest[0], offset_px[0], offset_px[0]);
+ dest[1] = blend_strike_pixel(dest[1], offset_px[1], offset_px[1]);
+ dest[2] = blend_strike_pixel(dest[2], offset_px[2], offset_px[2]);
+ dest[3] = blend_strike_pixel(dest[3], offset_px[3], offset_px[3]);
+ } else {
+ // Otherwise assume we have a premultiplied alpha BGRA value.
+ dest[0] = blend_strike_pixel(dest[0], offset_px[0], offset_px[3]);
+ dest[1] = blend_strike_pixel(dest[1], offset_px[1], offset_px[3]);
+ dest[2] = blend_strike_pixel(dest[2], offset_px[2], offset_px[3]);
+ dest[3] = blend_strike_pixel(dest[3], offset_px[3], offset_px[3]);
+ }
+ // Save the remainder for blending onto the next pixel.
+ prev_px = next_px;
+ }
+ if offset_fract > 0 {
+ // When there is fractional offset, there will be a remaining value
+ // from the previous pixel but no next pixel, so just use that.
+ let dest = &mut dest_row[offset_integer + src_stride .. ];
+ let offset_px = [(prev_px[0] + 128) >> 8,
+ (prev_px[1] + 128) >> 8,
+ (prev_px[2] + 128) >> 8,
+ (prev_px[3] + 128) >> 8];
+ if subpixel_mask {
+ dest[0] = blend_strike_pixel(dest[0], offset_px[0], offset_px[0]);
+ dest[1] = blend_strike_pixel(dest[1], offset_px[1], offset_px[1]);
+ dest[2] = blend_strike_pixel(dest[2], offset_px[2], offset_px[2]);
+ dest[3] = blend_strike_pixel(dest[3], offset_px[3], offset_px[3]);
+ } else {
+ dest[0] = blend_strike_pixel(dest[0], offset_px[0], offset_px[3]);
+ dest[1] = blend_strike_pixel(dest[1], offset_px[1], offset_px[3]);
+ dest[2] = blend_strike_pixel(dest[2], offset_px[2], offset_px[3]);
+ dest[3] = blend_strike_pixel(dest[3], offset_px[3], offset_px[3]);
+ }
+ }
+ }
+}
+
+// Applies multistrike bold to a source bitmap. This assumes the source bitmap
+// is a tighly packed slice of BGRA pixel values of exactly the specified width
+// and height. The specified extra strikes and pixel step control where to put
+// each strike. The pixel step is allowed to have a fractional offset and does
+// not strictly need to be integer.
+#[allow(dead_code)]
+pub fn apply_multistrike_bold(
+ src_bitmap: &[u8],
+ width: usize,
+ height: usize,
+ subpixel_mask: bool,
+ extra_strikes: usize,
+ pixel_step: f64,
+) -> (Vec<u8>, usize) {
+ let src_stride = width * 4;
+ // The amount of extra width added to the bitmap from the extra strikes.
+ let extra_width = (extra_strikes as f64 * pixel_step).ceil() as usize;
+ let dest_width = width + extra_width;
+ let dest_stride = dest_width * 4;
+ // Zero out the initial bitmap so any extra width is cleared.
+ let mut dest_bitmap = vec![0u8; dest_stride * height];
+ for (src_row, dest_row) in src_bitmap.chunks(src_stride).zip(dest_bitmap.chunks_mut(dest_stride)) {
+ // Copy the initial bitmap strike rows directly from the source.
+ dest_row[0 .. src_stride].copy_from_slice(src_row);
+ }
+ // Finally blend each extra strike in turn.
+ for i in 1 ..= extra_strikes {
+ let offset = i as f64 * pixel_step;
+ blend_strike(&mut dest_bitmap, src_bitmap, width, height, subpixel_mask, offset);
+ }
+ (dest_bitmap, dest_width)
+}
+
+pub struct RasterizedGlyph {
+ pub top: f32,
+ pub left: f32,
+ pub width: i32,
+ pub height: i32,
+ pub scale: f32,
+ pub format: GlyphFormat,
+ pub bytes: Vec<u8>,
+}
+
+impl RasterizedGlyph {
+ #[allow(dead_code)]
+ pub fn downscale_bitmap_if_required(&mut self, font: &FontInstance) {
+ // Check if the glyph is going to be downscaled in the shader. If the scaling is
+ // less than 0.5, that means bilinear filtering can't effectively filter the glyph
+ // without aliasing artifacts.
+ //
+ // Instead of fixing this by mipmapping the glyph cache texture, rather manually
+ // produce the appropriate mip level for individual glyphs where bilinear filtering
+ // will still produce acceptable results.
+ match self.format {
+ GlyphFormat::Bitmap | GlyphFormat::ColorBitmap => {},
+ _ => return,
+ }
+ let (x_scale, y_scale) = font.transform.compute_scale().unwrap_or((1.0, 1.0));
+ let upscaled = x_scale.max(y_scale) as f32;
+ let mut new_scale = self.scale;
+ if new_scale * upscaled <= 0.0 {
+ return;
+ }
+ let mut steps = 0;
+ while new_scale * upscaled <= 0.5 {
+ new_scale *= 2.0;
+ steps += 1;
+ }
+ // If no mipping is necessary, just bail.
+ if steps == 0 {
+ return;
+ }
+
+ // Calculate the actual size of the mip level.
+ let new_width = (self.width as usize + (1 << steps) - 1) >> steps;
+ let new_height = (self.height as usize + (1 << steps) - 1) >> steps;
+ let mut new_bytes: Vec<u8> = Vec::with_capacity(new_width * new_height * 4);
+
+ // Produce destination pixels by applying a box filter to the source pixels.
+ // The box filter corresponds to how graphics drivers may generate mipmaps.
+ for y in 0 .. new_height {
+ for x in 0 .. new_width {
+ // Calculate the number of source samples that contribute to the destination pixel.
+ let src_y = y << steps;
+ let src_x = x << steps;
+ let y_samples = (1 << steps).min(self.height as usize - src_y);
+ let x_samples = (1 << steps).min(self.width as usize - src_x);
+ let num_samples = (x_samples * y_samples) as u32;
+
+ let mut src_idx = (src_y * self.width as usize + src_x) * 4;
+ // Initialize the accumulator with half an increment so that when later divided
+ // by the sample count, it will effectively round the accumulator to the nearest
+ // increment.
+ let mut accum = [num_samples / 2; 4];
+ // Accumulate all the contributing source sampless.
+ for _ in 0 .. y_samples {
+ for _ in 0 .. x_samples {
+ accum[0] += self.bytes[src_idx + 0] as u32;
+ accum[1] += self.bytes[src_idx + 1] as u32;
+ accum[2] += self.bytes[src_idx + 2] as u32;
+ accum[3] += self.bytes[src_idx + 3] as u32;
+ src_idx += 4;
+ }
+ src_idx += (self.width as usize - x_samples) * 4;
+ }
+
+ // Finally, divide by the sample count to get the mean value for the new pixel.
+ new_bytes.extend_from_slice(&[
+ (accum[0] / num_samples) as u8,
+ (accum[1] / num_samples) as u8,
+ (accum[2] / num_samples) as u8,
+ (accum[3] / num_samples) as u8,
+ ]);
+ }
+ }
+
+ // Fix the bounds for the new glyph data.
+ self.top /= (1 << steps) as f32;
+ self.left /= (1 << steps) as f32;
+ self.width = new_width as i32;
+ self.height = new_height as i32;
+ self.scale = new_scale;
+ self.bytes = new_bytes;
+ }
+}
+
+pub struct FontContexts {
+ // These worker are mostly accessed from their corresponding worker threads.
+ // The goal is that there should be no noticeable contention on the mutexes.
+ worker_contexts: Vec<Mutex<FontContext>>,
+ // Stored here as a convenience to get the current thread index.
+ #[allow(dead_code)]
+ workers: Arc<ThreadPool>,
+ locked_mutex: Mutex<bool>,
+ locked_cond: Condvar,
+}
+
+impl FontContexts {
+ /// Get access to any particular font context.
+ ///
+ /// The id is an index between 0 and num_worker_contexts for font contexts
+ /// associated to the thread pool.
+ pub fn lock_context(&self, id: usize) -> MutexGuard<FontContext> {
+ self.worker_contexts[id].lock().unwrap()
+ }
+
+ // Find a context that is currently unlocked to use, otherwise defaulting
+ // to the first context.
+ pub fn lock_any_context(&self) -> MutexGuard<FontContext> {
+ for context in &self.worker_contexts {
+ if let Ok(mutex) = context.try_lock() {
+ return mutex;
+ }
+ }
+ self.lock_context(0)
+ }
+
+ // number of contexts associated to workers
+ pub fn num_worker_contexts(&self) -> usize {
+ self.worker_contexts.len()
+ }
+}
+
+pub trait AsyncForEach<T> {
+ fn async_for_each<F: Fn(MutexGuard<T>) + Send + 'static>(&self, f: F);
+}
+
+impl AsyncForEach<FontContext> for Arc<FontContexts> {
+ fn async_for_each<F: Fn(MutexGuard<FontContext>) + Send + 'static>(&self, f: F) {
+ // Reset the locked condition.
+ let mut locked = self.locked_mutex.lock().unwrap();
+ *locked = false;
+
+ // Arc that can be safely moved into a spawn closure.
+ let font_contexts = self.clone();
+ // Spawn a new thread on which to run the for-each off the main thread.
+ self.workers.spawn(move || {
+ // Lock the shared and worker contexts up front.
+ let mut locks = Vec::with_capacity(font_contexts.num_worker_contexts());
+ for i in 0 .. font_contexts.num_worker_contexts() {
+ locks.push(font_contexts.lock_context(i));
+ }
+
+ // Signal the locked condition now that all contexts are locked.
+ *font_contexts.locked_mutex.lock().unwrap() = true;
+ font_contexts.locked_cond.notify_all();
+
+ // Now that everything is locked, proceed to processing each locked context.
+ for context in locks {
+ f(context);
+ }
+ });
+
+ // Wait for locked condition before resuming. Safe to proceed thereafter
+ // since any other thread that needs to use a FontContext will try to lock
+ // it first.
+ while !*locked {
+ locked = self.locked_cond.wait(locked).unwrap();
+ }
+ }
+}
+
+pub struct GlyphRasterizer {
+ #[allow(dead_code)]
+ workers: Arc<ThreadPool>,
+ font_contexts: Arc<FontContexts>,
+
+ /// The current set of loaded fonts.
+ fonts: FastHashSet<FontKey>,
+
+ /// The current number of individual glyphs waiting in pending batches.
+ pending_glyph_count: usize,
+
+ /// The current number of glyph request jobs that have been kicked to worker threads.
+ pending_glyph_jobs: usize,
+
+ /// The number of glyphs requested this frame.
+ glyph_request_count: usize,
+
+ /// A map of current glyph request batches.
+ pending_glyph_requests: FastHashMap<FontInstance, SmallVec<[GlyphKey; 16]>>,
+
+ // Receives the rendered glyphs.
+ glyph_rx: Receiver<GlyphRasterJob>,
+ glyph_tx: Sender<GlyphRasterJob>,
+
+ // We defer removing fonts to the end of the frame so that:
+ // - this work is done outside of the critical path,
+ // - we don't have to worry about the ordering of events if a font is used on
+ // a frame where it is used (although it seems unlikely).
+ fonts_to_remove: Vec<FontKey>,
+ // Defer removal of font instances, as for fonts.
+ font_instances_to_remove: Vec<FontInstance>,
+
+ // Whether to parallelize glyph rasterization with rayon.
+ enable_multithreading: bool,
+
+ // Whether glyphs can be rasterized in r8 format when it makes sense.
+ can_use_r8_format: bool,
+}
+
+impl GlyphRasterizer {
+ pub fn new(workers: Arc<ThreadPool>, can_use_r8_format: bool) -> Self {
+ let (glyph_tx, glyph_rx) = unbounded();
+
+ let num_workers = workers.current_num_threads();
+ let mut contexts = Vec::with_capacity(num_workers);
+
+ for _ in 0 .. num_workers {
+ contexts.push(Mutex::new(FontContext::new()));
+ }
+
+ let font_context = FontContexts {
+ worker_contexts: contexts,
+ workers: Arc::clone(&workers),
+ locked_mutex: Mutex::new(false),
+ locked_cond: Condvar::new(),
+ };
+
+ GlyphRasterizer {
+ font_contexts: Arc::new(font_context),
+ fonts: FastHashSet::default(),
+ pending_glyph_jobs: 0,
+ pending_glyph_count: 0,
+ glyph_request_count: 0,
+ glyph_rx,
+ glyph_tx,
+ workers,
+ fonts_to_remove: Vec::new(),
+ font_instances_to_remove: Vec::new(),
+ enable_multithreading: true,
+ pending_glyph_requests: FastHashMap::default(),
+ can_use_r8_format,
+ }
+ }
+
+ pub fn add_font(&mut self, font_key: FontKey, template: FontTemplate) {
+ if self.fonts.insert(font_key.clone()) {
+ // Only add font to FontContexts if not previously added.
+ self.font_contexts.async_for_each(move |mut context| {
+ context.add_font(&font_key, &template);
+ });
+ }
+ }
+
+ pub fn delete_font(&mut self, font_key: FontKey) {
+ self.fonts_to_remove.push(font_key);
+ }
+
+ pub fn delete_fonts(&mut self, font_keys: &[FontKey]) {
+ self.fonts_to_remove.extend_from_slice(font_keys);
+ }
+
+ pub fn delete_font_instance(&mut self, instance: &FontInstance) {
+ self.font_instances_to_remove.push(instance.clone());
+ }
+
+ pub fn prepare_font(&self, font: &mut FontInstance) {
+ FontContext::prepare_font(font);
+
+ // Quantize the transform to minimize thrashing of the glyph cache, but
+ // only quantize the transform when preparing to access the glyph cache.
+ // This way, the glyph subpixel positions, which are calculated before
+ // this, can still use the precise transform which is required to match
+ // the subpixel positions computed for glyphs in the text run shader.
+ font.transform = font.transform.quantize();
+ }
+
+ pub fn has_font(&self, font_key: FontKey) -> bool {
+ self.fonts.contains(&font_key)
+ }
+
+ pub fn get_glyph_dimensions(
+ &mut self,
+ font: &FontInstance,
+ glyph_index: GlyphIndex,
+ ) -> Option<GlyphDimensions> {
+ let glyph_key = GlyphKey::new(
+ glyph_index,
+ DevicePoint::zero(),
+ SubpixelDirection::None,
+ );
+
+ self.font_contexts
+ .lock_any_context()
+ .get_glyph_dimensions(font, &glyph_key)
+ }
+
+ pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> {
+ self.font_contexts
+ .lock_any_context()
+ .get_glyph_index(font_key, ch)
+ }
+
+ fn remove_dead_fonts(&mut self) {
+ if self.fonts_to_remove.is_empty() && self.font_instances_to_remove.is_empty() {
+ return
+ }
+
+ profile_scope!("remove_dead_fonts");
+ let mut fonts_to_remove = mem::replace(& mut self.fonts_to_remove, Vec::new());
+ // Only remove font from FontContexts if previously added.
+ fonts_to_remove.retain(|font| self.fonts.remove(font));
+ let font_instances_to_remove = mem::replace(& mut self.font_instances_to_remove, Vec::new());
+ self.font_contexts.async_for_each(move |mut context| {
+ for font_key in &fonts_to_remove {
+ context.delete_font(font_key);
+ }
+ for instance in &font_instances_to_remove {
+ context.delete_font_instance(instance);
+ }
+ });
+ }
+
+ #[cfg(feature = "replay")]
+ pub fn reset(&mut self) {
+ //TODO: any signals need to be sent to the workers?
+ self.pending_glyph_jobs = 0;
+ self.pending_glyph_count = 0;
+ self.glyph_request_count = 0;
+ self.fonts_to_remove.clear();
+ self.font_instances_to_remove.clear();
+ }
+}
+
+trait AddFont {
+ fn add_font(&mut self, font_key: &FontKey, template: &FontTemplate);
+}
+
+impl AddFont for FontContext {
+ fn add_font(&mut self, font_key: &FontKey, template: &FontTemplate) {
+ match *template {
+ FontTemplate::Raw(ref bytes, index) => {
+ self.add_raw_font(font_key, bytes.clone(), index);
+ }
+ FontTemplate::Native(ref native_font_handle) => {
+ self.add_native_font(font_key, (*native_font_handle).clone());
+ }
+ }
+ }
+}
+
+#[allow(dead_code)]
+pub struct GlyphRasterJob {
+ pub font: Arc<FontInstance>,
+ pub key: GlyphKey,
+ pub result: GlyphRasterResult,
+}
+
+#[allow(dead_code)]
+#[derive(Debug)]
+pub enum GlyphRasterError {
+ LoadFailed,
+}
+
+#[allow(dead_code)]
+pub type GlyphRasterResult = Result<RasterizedGlyph, GlyphRasterError>;
+
+#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct GpuGlyphCacheKey(pub u32);
+
+#[cfg(test)]
+mod test_glyph_rasterizer {
+ use crate::profiler::GlyphRasterizeProfiler;
+
+ struct Profiler;
+ impl GlyphRasterizeProfiler for Profiler {
+ fn start_time(&mut self) {}
+ fn end_time(&mut self) -> f64 {
+ 0.
+ }
+ fn set(&mut self, _value: f64) {}
+ }
+
+ #[test]
+ fn rasterize_200_glyphs() {
+ // This test loads a font from disc, the renders 4 requests containing
+ // 50 glyphs each, deletes the font and waits for the result.
+
+ use rayon::ThreadPoolBuilder;
+ use std::fs::File;
+ use std::io::Read;
+ use api::{FontKey, FontInstanceKey, FontTemplate, IdNamespace};
+ use api::units::DevicePoint;
+ use std::sync::Arc;
+ use crate::rasterizer::{FontInstance, BaseFontInstance, GlyphKey, GlyphRasterizer};
+
+ let worker = ThreadPoolBuilder::new()
+ .thread_name(|idx|{ format!("WRWorker#{}", idx) })
+ .build();
+ let workers = Arc::new(worker.unwrap());
+ let mut glyph_rasterizer = GlyphRasterizer::new(workers, true);
+ let mut font_file =
+ File::open("../wrench/reftests/text/VeraBd.ttf").expect("Couldn't open font file");
+ let mut font_data = vec![];
+ font_file
+ .read_to_end(&mut font_data)
+ .expect("failed to read font file");
+
+ let font_key = FontKey::new(IdNamespace(0), 0);
+ glyph_rasterizer.add_font(font_key, FontTemplate::Raw(Arc::new(font_data), 0));
+
+ let font = FontInstance::from_base(Arc::new(BaseFontInstance::new(
+ FontInstanceKey::new(IdNamespace(0), 0),
+ font_key,
+ 32.0,
+ None,
+ None,
+ Vec::new(),
+ )));
+
+ let subpx_dir = font.get_subpx_dir();
+
+ let mut glyph_keys = Vec::with_capacity(200);
+ for i in 0 .. 200 {
+ glyph_keys.push(GlyphKey::new(
+ i,
+ DevicePoint::zero(),
+ subpx_dir,
+ ));
+ }
+
+ for i in 0 .. 4 {
+ glyph_rasterizer.request_glyphs(
+ font.clone(),
+ &glyph_keys[(50 * i) .. (50 * (i + 1))],
+ |_| true,
+ );
+ }
+
+ glyph_rasterizer.delete_font(font_key);
+
+ glyph_rasterizer.resolve_glyphs(
+ |_, _| {},
+ &mut Profiler,
+ );
+ }
+
+ #[test]
+ fn rasterize_large_glyphs() {
+ // This test loads a font from disc and rasterize a few glyphs with a size of 200px to check
+ // that the texture cache handles them properly.
+ use rayon::ThreadPoolBuilder;
+ use std::fs::File;
+ use std::io::Read;
+ use api::{FontKey, FontInstanceKey, FontTemplate, IdNamespace};
+ use api::units::DevicePoint;
+ use std::sync::Arc;
+ use crate::rasterizer::{FontInstance, BaseFontInstance, GlyphKey, GlyphRasterizer};
+
+ let worker = ThreadPoolBuilder::new()
+ .thread_name(|idx|{ format!("WRWorker#{}", idx) })
+ .build();
+ let workers = Arc::new(worker.unwrap());
+ let mut glyph_rasterizer = GlyphRasterizer::new(workers, true);
+ let mut font_file =
+ File::open("../wrench/reftests/text/VeraBd.ttf").expect("Couldn't open font file");
+ let mut font_data = vec![];
+ font_file
+ .read_to_end(&mut font_data)
+ .expect("failed to read font file");
+
+ let font_key = FontKey::new(IdNamespace(0), 0);
+ glyph_rasterizer.add_font(font_key, FontTemplate::Raw(Arc::new(font_data), 0));
+
+ let font = FontInstance::from_base(Arc::new(BaseFontInstance::new(
+ FontInstanceKey::new(IdNamespace(0), 0),
+ font_key,
+ 200.0,
+ None,
+ None,
+ Vec::new(),
+ )));
+
+ let subpx_dir = font.get_subpx_dir();
+
+ let mut glyph_keys = Vec::with_capacity(10);
+ for i in 0 .. 10 {
+ glyph_keys.push(GlyphKey::new(
+ i,
+ DevicePoint::zero(),
+ subpx_dir,
+ ));
+ }
+
+ glyph_rasterizer.request_glyphs(
+ font.clone(),
+ &glyph_keys,
+ |_| true,
+ );
+
+ glyph_rasterizer.delete_font(font_key);
+
+ glyph_rasterizer.resolve_glyphs(
+ |_, _| {},
+ &mut Profiler,
+ );
+ }
+
+ #[test]
+ fn test_subpx_quantize() {
+ use crate::rasterizer::SubpixelOffset;
+
+ assert_eq!(SubpixelOffset::quantize(0.0), SubpixelOffset::Zero);
+ assert_eq!(SubpixelOffset::quantize(-0.0), SubpixelOffset::Zero);
+
+ assert_eq!(SubpixelOffset::quantize(0.1), SubpixelOffset::Zero);
+ assert_eq!(SubpixelOffset::quantize(0.01), SubpixelOffset::Zero);
+ assert_eq!(SubpixelOffset::quantize(0.05), SubpixelOffset::Zero);
+ assert_eq!(SubpixelOffset::quantize(0.12), SubpixelOffset::Zero);
+ assert_eq!(SubpixelOffset::quantize(0.124), SubpixelOffset::Zero);
+
+ assert_eq!(SubpixelOffset::quantize(0.125), SubpixelOffset::Quarter);
+ assert_eq!(SubpixelOffset::quantize(0.2), SubpixelOffset::Quarter);
+ assert_eq!(SubpixelOffset::quantize(0.25), SubpixelOffset::Quarter);
+ assert_eq!(SubpixelOffset::quantize(0.33), SubpixelOffset::Quarter);
+ assert_eq!(SubpixelOffset::quantize(0.374), SubpixelOffset::Quarter);
+
+ assert_eq!(SubpixelOffset::quantize(0.375), SubpixelOffset::Half);
+ assert_eq!(SubpixelOffset::quantize(0.4), SubpixelOffset::Half);
+ assert_eq!(SubpixelOffset::quantize(0.5), SubpixelOffset::Half);
+ assert_eq!(SubpixelOffset::quantize(0.58), SubpixelOffset::Half);
+ assert_eq!(SubpixelOffset::quantize(0.624), SubpixelOffset::Half);
+
+ assert_eq!(SubpixelOffset::quantize(0.625), SubpixelOffset::ThreeQuarters);
+ assert_eq!(SubpixelOffset::quantize(0.67), SubpixelOffset::ThreeQuarters);
+ assert_eq!(SubpixelOffset::quantize(0.7), SubpixelOffset::ThreeQuarters);
+ assert_eq!(SubpixelOffset::quantize(0.78), SubpixelOffset::ThreeQuarters);
+ assert_eq!(SubpixelOffset::quantize(0.874), SubpixelOffset::ThreeQuarters);
+
+ assert_eq!(SubpixelOffset::quantize(0.875), SubpixelOffset::Zero);
+ assert_eq!(SubpixelOffset::quantize(0.89), SubpixelOffset::Zero);
+ assert_eq!(SubpixelOffset::quantize(0.91), SubpixelOffset::Zero);
+ assert_eq!(SubpixelOffset::quantize(0.967), SubpixelOffset::Zero);
+ assert_eq!(SubpixelOffset::quantize(0.999), SubpixelOffset::Zero);
+
+ assert_eq!(SubpixelOffset::quantize(-1.0), SubpixelOffset::Zero);
+ assert_eq!(SubpixelOffset::quantize(1.0), SubpixelOffset::Zero);
+ assert_eq!(SubpixelOffset::quantize(1.5), SubpixelOffset::Half);
+ assert_eq!(SubpixelOffset::quantize(-1.625), SubpixelOffset::Half);
+ assert_eq!(SubpixelOffset::quantize(-4.33), SubpixelOffset::ThreeQuarters);
+ }
+}
diff --git a/gfx/wr/wr_glyph_rasterizer/src/telemetry.rs b/gfx/wr/wr_glyph_rasterizer/src/telemetry.rs
new file mode 100644
index 0000000000..28000440b2
--- /dev/null
+++ b/gfx/wr/wr_glyph_rasterizer/src/telemetry.rs
@@ -0,0 +1,28 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#[cfg(feature = "gecko")]
+use glean::TimerId;
+#[cfg(feature = "gecko")]
+use firefox_on_glean::metrics::wr;
+
+#[cfg(not(feature = "gecko"))]
+pub struct TimerId;
+
+pub struct Telemetry;
+
+/// Defines the interface for hooking up an external telemetry reporter to WR.
+#[cfg(not(feature = "gecko"))]
+impl Telemetry {
+ // Start rasterize glyph time collection
+ pub fn start_rasterize_glyphs_time() -> TimerId { return TimerId {}; }
+ // End rasterize glyph time collection
+ pub fn stop_and_accumulate_rasterize_glyphs_time(_id: TimerId) { }
+}
+
+#[cfg(feature = "gecko")]
+impl Telemetry {
+ pub fn start_rasterize_glyphs_time() -> TimerId { wr::rasterize_glyphs_time.start() }
+ pub fn stop_and_accumulate_rasterize_glyphs_time(id: TimerId) { wr::rasterize_glyphs_time.stop_and_accumulate(id); }
+}
diff --git a/gfx/wr/wr_glyph_rasterizer/src/types.rs b/gfx/wr/wr_glyph_rasterizer/src/types.rs
new file mode 100644
index 0000000000..665ab0f86b
--- /dev/null
+++ b/gfx/wr/wr_glyph_rasterizer/src/types.rs
@@ -0,0 +1,10 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+use fxhash::FxHasher;
+use std::collections::{HashMap, HashSet};
+use std::hash::BuildHasherDefault;
+
+pub type FastHashMap<K, V> = HashMap<K, V, BuildHasherDefault<FxHasher>>;
+pub type FastHashSet<K> = HashSet<K, BuildHasherDefault<FxHasher>>;