mirror of
https://github.com/servo/servo.git
synced 2025-08-03 12:40:06 +01:00
canvas: Remove as much usage of font-kit
as possible (#32758)
Do font selection using Servo's font backend, which is shared with the rest of layout. In addition, delay the creation of the `font-kit` font until just before rendering with `raqote`. The idea is that when `raqote` is no longer used, we can drop the `font-kit` dependency. This change has the side-effect of fixing text rendering in canvas, adding support for font fallback in canvas, and also correcting a bug in font selection with size overrides. Signed-off-by: Martin Robinson <mrobinson@igalia.com> Co-authored-by: Mukilan Thiyagarajan <mukilan@igalia.com>
This commit is contained in:
parent
c6cb7ee981
commit
4907e89656
6 changed files with 261 additions and 209 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -626,6 +626,7 @@ dependencies = [
|
|||
name = "canvas"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"app_units",
|
||||
"bitflags 2.6.0",
|
||||
"byteorder",
|
||||
"canvas_traits",
|
||||
|
@ -641,8 +642,10 @@ dependencies = [
|
|||
"lyon_geom",
|
||||
"net_traits",
|
||||
"num-traits",
|
||||
"parking_lot",
|
||||
"pathfinder_geometry",
|
||||
"pixels",
|
||||
"range",
|
||||
"raqote",
|
||||
"servo_arc",
|
||||
"sparkle",
|
||||
|
@ -650,6 +653,7 @@ dependencies = [
|
|||
"style_traits",
|
||||
"surfman",
|
||||
"time 0.1.45",
|
||||
"unicode-script",
|
||||
"webrender",
|
||||
"webrender_api",
|
||||
"webrender_traits",
|
||||
|
|
|
@ -15,6 +15,7 @@ webgl_backtrace = ["canvas_traits/webgl_backtrace"]
|
|||
xr-profile = ["webxr-api/profile", "time"]
|
||||
|
||||
[dependencies]
|
||||
app_units = { workspace = true }
|
||||
bitflags = { workspace = true }
|
||||
byteorder = { workspace = true }
|
||||
canvas_traits = { workspace = true }
|
||||
|
@ -30,8 +31,10 @@ log = { workspace = true }
|
|||
lyon_geom = "1.0.4"
|
||||
net_traits = { workspace = true }
|
||||
num-traits = { workspace = true }
|
||||
parking_lot = { workspace = true }
|
||||
pathfinder_geometry = "0.5"
|
||||
pixels = { path = "../pixels" }
|
||||
range = { path = "../range" }
|
||||
raqote = "0.8.4"
|
||||
servo_arc = { workspace = true }
|
||||
sparkle = { workspace = true }
|
||||
|
@ -39,6 +42,7 @@ style = { workspace = true }
|
|||
style_traits = { workspace = true }
|
||||
surfman = { workspace = true }
|
||||
time = { workspace = true, optional = true }
|
||||
unicode-script = { workspace = true }
|
||||
webrender = { workspace = true }
|
||||
webrender_api = { workspace = true }
|
||||
webrender_traits = { workspace = true }
|
||||
|
|
|
@ -5,23 +5,21 @@
|
|||
use std::mem;
|
||||
use std::sync::Arc;
|
||||
|
||||
use app_units::Au;
|
||||
use canvas_traits::canvas::*;
|
||||
use euclid::default::{Point2D, Rect, Size2D, Transform2D, Vector2D};
|
||||
use euclid::{point2, vec2};
|
||||
use font_kit::family_name::FamilyName;
|
||||
use font_kit::font::Font;
|
||||
use font_kit::metrics::Metrics;
|
||||
use font_kit::properties::{Properties, Stretch, Style, Weight};
|
||||
use font_kit::source::SystemSource;
|
||||
use fonts::{FontCacheThread, FontContext, FontTemplateRefMethods};
|
||||
use euclid::point2;
|
||||
use fonts::{
|
||||
FontCacheThread, FontContext, FontMetrics, FontRef, GlyphStore, ShapingFlags, ShapingOptions,
|
||||
LAST_RESORT_GLYPH_ADVANCE,
|
||||
};
|
||||
use ipc_channel::ipc::{IpcSender, IpcSharedMemory};
|
||||
use log::{debug, error, warn};
|
||||
use log::{debug, warn};
|
||||
use num_traits::ToPrimitive;
|
||||
use servo_arc::Arc as ServoArc;
|
||||
use style::color::AbsoluteColor;
|
||||
use style::properties::style_structs::Font as FontStyleStruct;
|
||||
use style::values::computed::font;
|
||||
use style_traits::values::ToCss;
|
||||
use unicode_script::Script;
|
||||
use webrender_api::units::{DeviceIntSize, RectExt as RectExt_};
|
||||
use webrender_api::{ImageData, ImageDescriptor, ImageDescriptorFlags, ImageFormat, ImageKey};
|
||||
use webrender_traits::ImageUpdate;
|
||||
|
@ -232,10 +230,55 @@ impl<'a> PathBuilderRef<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO(pylbrecht)
|
||||
// This defines required methods for DrawTarget of azure and raqote
|
||||
// The prototypes are derived from azure's methods.
|
||||
// TODO: De-abstract now that Azure is removed?
|
||||
#[derive(Debug, Default)]
|
||||
struct UnshapedTextRun<'a> {
|
||||
font: Option<FontRef>,
|
||||
script: Script,
|
||||
string: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> UnshapedTextRun<'a> {
|
||||
fn script_and_font_compatible(&self, script: Script, other_font: &Option<FontRef>) -> bool {
|
||||
if self.script != script {
|
||||
return false;
|
||||
}
|
||||
|
||||
match (&self.font, other_font) {
|
||||
(Some(font_a), Some(font_b)) => font_a.identifier() == font_b.identifier(),
|
||||
(None, None) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_shaped_text_run(self) -> Option<TextRun> {
|
||||
let font = self.font?;
|
||||
if self.string.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let word_spacing = Au::from_f64_px(
|
||||
font.glyph_index(' ')
|
||||
.map(|glyph_id| font.glyph_h_advance(glyph_id))
|
||||
.unwrap_or(LAST_RESORT_GLYPH_ADVANCE),
|
||||
);
|
||||
let options = ShapingOptions {
|
||||
letter_spacing: None,
|
||||
word_spacing,
|
||||
script: self.script,
|
||||
flags: ShapingFlags::empty(),
|
||||
};
|
||||
let glyphs = font.shape_text(self.string, &options);
|
||||
Some(TextRun { font, glyphs })
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TextRun {
|
||||
pub font: FontRef,
|
||||
pub glyphs: Arc<GlyphStore>,
|
||||
}
|
||||
|
||||
// This defines required methods for a DrawTarget (currently only implemented for raqote). The
|
||||
// prototypes are derived from the now-removed Azure backend's methods.
|
||||
pub trait GenericDrawTarget {
|
||||
fn clear_rect(&mut self, rect: &Rect<f32>);
|
||||
fn copy_surface(
|
||||
|
@ -268,9 +311,7 @@ pub trait GenericDrawTarget {
|
|||
fn fill(&mut self, path: &Path, pattern: Pattern, draw_options: &DrawOptions);
|
||||
fn fill_text(
|
||||
&mut self,
|
||||
font: &Font,
|
||||
point_size: f32,
|
||||
text: &str,
|
||||
text_runs: Vec<TextRun>,
|
||||
start: Point2D<f32>,
|
||||
pattern: &Pattern,
|
||||
draw_options: &DrawOptions,
|
||||
|
@ -455,7 +496,111 @@ impl<'a> CanvasData<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#text-preparation-algorithm
|
||||
pub fn fill_text_with_size(
|
||||
&mut self,
|
||||
text: String,
|
||||
x: f64,
|
||||
y: f64,
|
||||
max_width: Option<f64>,
|
||||
is_rtl: bool,
|
||||
size: f64,
|
||||
) {
|
||||
// > Step 2: Replace all ASCII whitespace in text with U+0020 SPACE characters.
|
||||
let text = replace_ascii_whitespace(text);
|
||||
|
||||
// > Step 3: Let font be the current font of target, as given by that object's font
|
||||
// > attribute.
|
||||
let Some(ref font_style) = self.state.font_style else {
|
||||
return;
|
||||
};
|
||||
|
||||
let font_group = self
|
||||
.font_context
|
||||
.font_group_with_size(font_style.clone(), Au::from_f64_px(size));
|
||||
let mut font_group = font_group.write();
|
||||
let Some(first_font) = font_group.first(&self.font_context) else {
|
||||
warn!("Could not render canvas text, because there was no first font.");
|
||||
return;
|
||||
};
|
||||
|
||||
let mut runs = Vec::new();
|
||||
let mut current_text_run = UnshapedTextRun::default();
|
||||
let mut current_text_run_start_index = 0;
|
||||
for (index, character) in text.char_indices() {
|
||||
// TODO: This should ultimately handle emoji variation selectors, but raqote does not yet
|
||||
// have support for color glyphs.
|
||||
let script = Script::from(character);
|
||||
let font = font_group.find_by_codepoint(&self.font_context, character, None);
|
||||
|
||||
if !current_text_run.script_and_font_compatible(script, &font) {
|
||||
let previous_text_run = mem::replace(
|
||||
&mut current_text_run,
|
||||
UnshapedTextRun {
|
||||
font: font.clone(),
|
||||
script,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
current_text_run_start_index = index;
|
||||
runs.push(previous_text_run)
|
||||
}
|
||||
|
||||
current_text_run.string =
|
||||
&text[current_text_run_start_index..index + character.len_utf8()];
|
||||
}
|
||||
runs.push(current_text_run);
|
||||
|
||||
// TODO: This doesn't do any kind of line layout at all. In particular, there needs
|
||||
// to be some alignment along a baseline and also support for bidi text.
|
||||
let shaped_runs: Vec<_> = runs
|
||||
.into_iter()
|
||||
.filter_map(UnshapedTextRun::to_shaped_text_run)
|
||||
.collect();
|
||||
let total_advance = shaped_runs
|
||||
.iter()
|
||||
.map(|run| run.glyphs.total_advance())
|
||||
.sum::<Au>()
|
||||
.to_f64_px();
|
||||
|
||||
// > Step 6: If maxWidth was provided and the hypothetical width of the inline box in the
|
||||
// > hypothetical line box is greater than maxWidth CSS pixels, then change font to have a
|
||||
// > more condensed font (if one is available or if a reasonably readable one can be
|
||||
// > synthesized by applying a horizontal scale factor to the font) or a smaller font, and
|
||||
// > return to the previous step.
|
||||
//
|
||||
// TODO: We only try decreasing the font size here. Eventually it would make sense to use
|
||||
// other methods to try to decrease the size, such as finding a narrower font or decreasing
|
||||
// spacing.
|
||||
if let Some(max_width) = max_width {
|
||||
let new_size = (max_width / total_advance * size).floor().max(5.);
|
||||
if total_advance > max_width && new_size != size {
|
||||
self.fill_text_with_size(text, x, y, Some(max_width), is_rtl, new_size);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// > Step 7: Find the anchor point for the line of text.
|
||||
let start = self.find_anchor_point_for_line_of_text(
|
||||
x as f32,
|
||||
y as f32,
|
||||
&first_font.metrics,
|
||||
total_advance as f32,
|
||||
is_rtl,
|
||||
);
|
||||
|
||||
// > Step 8: Let result be an array constructed by iterating over each glyph in the inline box
|
||||
// > from left to right (if any), adding to the array, for each glyph, the shape of the glyph
|
||||
// > as it is in the inline box, positioned on a coordinate space using CSS pixels with its
|
||||
// > origin is at the anchor point.
|
||||
self.drawtarget.fill_text(
|
||||
shaped_runs,
|
||||
start,
|
||||
&self.state.fill_style,
|
||||
&self.state.draw_options,
|
||||
);
|
||||
}
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#text-preparation-algorithm>
|
||||
pub fn fill_text(
|
||||
&mut self,
|
||||
text: String,
|
||||
|
@ -464,77 +609,21 @@ impl<'a> CanvasData<'a> {
|
|||
max_width: Option<f64>,
|
||||
is_rtl: bool,
|
||||
) {
|
||||
// Step 2.
|
||||
let text = replace_ascii_whitespace(text);
|
||||
|
||||
// Step 3.
|
||||
let point_size = self
|
||||
.state
|
||||
.font_style
|
||||
.as_ref()
|
||||
.map_or(10., |style| style.font_size.computed_size().px());
|
||||
let font_style = self.state.font_style.as_ref();
|
||||
let font = font_style.map_or_else(
|
||||
|| load_system_font_from_style(None),
|
||||
|style| {
|
||||
let font_group = self.font_context.font_group(ServoArc::new(style.clone()));
|
||||
let font = font_group
|
||||
.write()
|
||||
.first(&self.font_context)
|
||||
.expect("couldn't find font");
|
||||
Font::from_bytes(font.template.data(), 0)
|
||||
.ok()
|
||||
.or_else(|| load_system_font_from_style(Some(style)))
|
||||
},
|
||||
);
|
||||
let font = match font {
|
||||
Some(f) => f,
|
||||
None => {
|
||||
error!("Couldn't load desired font or system fallback.");
|
||||
let Some(ref font_style) = self.state.font_style else {
|
||||
return;
|
||||
},
|
||||
};
|
||||
let font_width = font_width(&text, point_size, &font);
|
||||
|
||||
// Step 6.
|
||||
let max_width = max_width.map(|width| width as f32);
|
||||
let (width, scale_factor) = match max_width {
|
||||
Some(max_width) if max_width > font_width => (max_width, 1.),
|
||||
Some(max_width) => (font_width, max_width / font_width),
|
||||
None => (font_width, 1.),
|
||||
};
|
||||
|
||||
// Step 7.
|
||||
let start = self.text_origin(x as f32, y as f32, &font.metrics(), width, is_rtl);
|
||||
|
||||
// TODO: Bidi text layout
|
||||
|
||||
let old_transform = self.get_transform();
|
||||
self.set_transform(
|
||||
&old_transform
|
||||
.pre_translate(vec2(start.x, 0.))
|
||||
.pre_scale(scale_factor, 1.)
|
||||
.pre_translate(vec2(-start.x, 0.)),
|
||||
);
|
||||
|
||||
// Step 8.
|
||||
self.drawtarget.fill_text(
|
||||
&font,
|
||||
point_size,
|
||||
&text,
|
||||
start,
|
||||
&self.state.fill_style,
|
||||
&self.state.draw_options,
|
||||
);
|
||||
|
||||
self.set_transform(&old_transform);
|
||||
let size = font_style.font_size.computed_size();
|
||||
self.fill_text_with_size(text, x, y, max_width, is_rtl, size.px() as f64);
|
||||
}
|
||||
|
||||
fn text_origin(
|
||||
/// Find the *anchor_point* for the given parameters of a line of text.
|
||||
/// See <https://html.spec.whatwg.org/multipage/#text-preparation-algorithm>.
|
||||
fn find_anchor_point_for_line_of_text(
|
||||
&self,
|
||||
x: f32,
|
||||
y: f32,
|
||||
metrics: &Metrics,
|
||||
metrics: &FontMetrics,
|
||||
width: f32,
|
||||
is_rtl: bool,
|
||||
) -> Point2D<f32> {
|
||||
|
@ -551,13 +640,15 @@ impl<'a> CanvasData<'a> {
|
|||
_ => 0.,
|
||||
};
|
||||
|
||||
let ascent = metrics.ascent.to_f32_px();
|
||||
let descent = metrics.descent.to_f32_px();
|
||||
let anchor_y = match self.state.text_baseline {
|
||||
TextBaseline::Top => metrics.ascent,
|
||||
TextBaseline::Hanging => metrics.ascent * HANGING_BASELINE_DEFAULT,
|
||||
TextBaseline::Ideographic => -metrics.descent * IDEOGRAPHIC_BASELINE_DEFAULT,
|
||||
TextBaseline::Middle => (metrics.ascent - metrics.descent) / 2.,
|
||||
TextBaseline::Top => ascent,
|
||||
TextBaseline::Hanging => ascent * HANGING_BASELINE_DEFAULT,
|
||||
TextBaseline::Ideographic => -descent * IDEOGRAPHIC_BASELINE_DEFAULT,
|
||||
TextBaseline::Middle => (ascent - descent) / 2.,
|
||||
TextBaseline::Alphabetic => 0.,
|
||||
TextBaseline::Bottom => -metrics.descent,
|
||||
TextBaseline::Bottom => -descent,
|
||||
};
|
||||
|
||||
point2(x + anchor_x, y + anchor_y)
|
||||
|
@ -1140,7 +1231,7 @@ impl<'a> CanvasData<'a> {
|
|||
}
|
||||
|
||||
pub fn set_font(&mut self, font_style: FontStyleStruct) {
|
||||
self.state.font_style = Some(font_style)
|
||||
self.state.font_style = Some(ServoArc::new(font_style))
|
||||
}
|
||||
|
||||
pub fn set_text_align(&mut self, text_align: TextAlign) {
|
||||
|
@ -1239,7 +1330,7 @@ pub struct CanvasPaintState<'a> {
|
|||
pub shadow_offset_y: f64,
|
||||
pub shadow_blur: f64,
|
||||
pub shadow_color: Color,
|
||||
pub font_style: Option<FontStyleStruct>,
|
||||
pub font_style: Option<ServoArc<FontStyleStruct>>,
|
||||
pub text_align: TextAlign,
|
||||
pub text_baseline: TextBaseline,
|
||||
}
|
||||
|
@ -1330,71 +1421,6 @@ impl RectExt for Rect<u32> {
|
|||
}
|
||||
}
|
||||
|
||||
fn to_font_kit_family(font_family: &font::SingleFontFamily) -> FamilyName {
|
||||
match font_family {
|
||||
font::SingleFontFamily::FamilyName(family_name) => {
|
||||
FamilyName::Title(family_name.to_css_string())
|
||||
},
|
||||
font::SingleFontFamily::Generic(generic) => match generic {
|
||||
font::GenericFontFamily::Serif => FamilyName::Serif,
|
||||
font::GenericFontFamily::SansSerif => FamilyName::SansSerif,
|
||||
font::GenericFontFamily::Monospace => FamilyName::Monospace,
|
||||
font::GenericFontFamily::Fantasy => FamilyName::Fantasy,
|
||||
font::GenericFontFamily::Cursive => FamilyName::Cursive,
|
||||
// TODO: There is no FontFamily::SystemUi.
|
||||
font::GenericFontFamily::SystemUi => unreachable!("system-ui should be disabled"),
|
||||
font::GenericFontFamily::None => unreachable!("Shouldn't appear in computed values"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn load_system_font_from_style(font_style: Option<&FontStyleStruct>) -> Option<Font> {
|
||||
let mut properties = Properties::new();
|
||||
let style = match font_style {
|
||||
Some(style) => style,
|
||||
None => return load_default_system_fallback_font(&properties),
|
||||
};
|
||||
let family_names = style
|
||||
.font_family
|
||||
.families
|
||||
.iter()
|
||||
.map(to_font_kit_family)
|
||||
.collect::<Vec<_>>();
|
||||
let properties = properties
|
||||
.style(match style.font_style {
|
||||
font::FontStyle::NORMAL => Style::Normal,
|
||||
font::FontStyle::ITALIC => Style::Italic,
|
||||
_ => {
|
||||
// TODO: support oblique angle.
|
||||
Style::Oblique
|
||||
},
|
||||
})
|
||||
.weight(Weight(style.font_weight.value()))
|
||||
.stretch(Stretch(style.font_stretch.to_percentage().0));
|
||||
let font_handle = match SystemSource::new().select_best_match(&family_names, properties) {
|
||||
Ok(handle) => handle,
|
||||
Err(e) => {
|
||||
error!("error getting font handle for style {:?}: {}", style, e);
|
||||
return load_default_system_fallback_font(properties);
|
||||
},
|
||||
};
|
||||
match font_handle.load() {
|
||||
Ok(f) => Some(f),
|
||||
Err(e) => {
|
||||
error!("error loading font for style {:?}: {}", style, e);
|
||||
load_default_system_fallback_font(properties)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn load_default_system_fallback_font(properties: &Properties) -> Option<Font> {
|
||||
SystemSource::new()
|
||||
.select_best_match(&[FamilyName::SansSerif], properties)
|
||||
.ok()?
|
||||
.load()
|
||||
.ok()
|
||||
}
|
||||
|
||||
fn replace_ascii_whitespace(text: String) -> String {
|
||||
text.chars()
|
||||
.map(|c| match c {
|
||||
|
@ -1403,18 +1429,3 @@ fn replace_ascii_whitespace(text: String) -> String {
|
|||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
// TODO: This currently calculates the width using just advances and doesn't
|
||||
// determine the fallback font in case a character glyph isn't found.
|
||||
fn font_width(text: &str, point_size: f32, font: &Font) -> f32 {
|
||||
let metrics = font.metrics();
|
||||
let mut width = 0.;
|
||||
for c in text.chars() {
|
||||
if let Some(glyph_id) = font.glyph_for_char(c) {
|
||||
if let Ok(advance) = font.advance(glyph_id) {
|
||||
width += advance.x() * point_size / metrics.units_per_em as f32;
|
||||
}
|
||||
}
|
||||
}
|
||||
width
|
||||
}
|
||||
|
|
|
@ -2,23 +2,36 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use canvas_traits::canvas::*;
|
||||
use cssparser::color::clamp_unit_f32;
|
||||
use euclid::default::{Point2D, Rect, Size2D, Transform2D, Vector2D};
|
||||
use euclid::Angle;
|
||||
use font_kit::font::Font;
|
||||
use fonts::{ByteIndex, FontIdentifier, FontTemplateRefMethods};
|
||||
use log::warn;
|
||||
use lyon_geom::Arc;
|
||||
use range::Range;
|
||||
use raqote::PathOp;
|
||||
use style::color::AbsoluteColor;
|
||||
|
||||
use crate::canvas_data;
|
||||
use crate::canvas_data::{
|
||||
Backend, CanvasPaintState, Color, CompositionOp, DrawOptions, Filter, GenericDrawTarget,
|
||||
GenericPathBuilder, GradientStop, GradientStops, Path, SourceSurface, StrokeOptions,
|
||||
self, Backend, CanvasPaintState, Color, CompositionOp, DrawOptions, Filter, GenericDrawTarget,
|
||||
GenericPathBuilder, GradientStop, GradientStops, Path, SourceSurface, StrokeOptions, TextRun,
|
||||
};
|
||||
use crate::canvas_paint_thread::AntialiasMode;
|
||||
|
||||
thread_local! {
|
||||
/// The shared font cache used by all canvases that render on a thread. It would be nicer
|
||||
/// to have a global cache, but it looks like font-kit uses a per-thread FreeType, so
|
||||
/// in order to ensure that fonts are particular to a thread we have to make our own
|
||||
/// cache thread local as well.
|
||||
static SHARED_FONT_CACHE: RefCell<HashMap<FontIdentifier, Font>> = RefCell::default();
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RaqoteBackend;
|
||||
|
||||
impl Backend for RaqoteBackend {
|
||||
|
@ -508,43 +521,61 @@ impl GenericDrawTarget for raqote::DrawTarget {
|
|||
|
||||
fn fill_text(
|
||||
&mut self,
|
||||
font: &Font,
|
||||
point_size: f32,
|
||||
text: &str,
|
||||
text_runs: Vec<TextRun>,
|
||||
start: Point2D<f32>,
|
||||
pattern: &canvas_data::Pattern,
|
||||
options: &DrawOptions,
|
||||
draw_options: &DrawOptions,
|
||||
) {
|
||||
let mut start = pathfinder_geometry::vector::vec2f(start.x, start.y);
|
||||
let mut ids = Vec::new();
|
||||
let mut advance = 0.;
|
||||
for run in text_runs.iter() {
|
||||
let mut positions = Vec::new();
|
||||
for c in text.chars() {
|
||||
let id = match font.glyph_for_char(c) {
|
||||
Some(id) => id,
|
||||
None => {
|
||||
warn!("Skipping non-existent glyph {}", c);
|
||||
let glyphs = &run.glyphs;
|
||||
let ids: Vec<_> = glyphs
|
||||
.iter_glyphs_for_byte_range(&Range::new(ByteIndex(0), glyphs.len()))
|
||||
.map(|glyph| {
|
||||
let glyph_offset = glyph.offset().unwrap_or(Point2D::zero());
|
||||
positions.push(Point2D::new(
|
||||
advance + start.x + glyph_offset.x.to_f32_px(),
|
||||
start.y + glyph_offset.y.to_f32_px(),
|
||||
));
|
||||
advance += glyph.advance().to_f32_px();
|
||||
glyph.id()
|
||||
})
|
||||
.collect();
|
||||
|
||||
// TODO: raqote uses font-kit to rasterize glyphs, but font-kit fails an assertion when
|
||||
// using color bitmap fonts in the FreeType backend. For now, simply do not render these
|
||||
// type of fonts.
|
||||
if run.font.has_color_bitmap_or_colr_table() {
|
||||
continue;
|
||||
},
|
||||
};
|
||||
ids.push(id);
|
||||
positions.push(Point2D::new(start.x(), start.y()));
|
||||
let advance = match font.advance(id) {
|
||||
Ok(advance) => advance,
|
||||
Err(e) => {
|
||||
warn!("Skipping glyph {} with missing advance: {:?}", c, e);
|
||||
continue;
|
||||
},
|
||||
};
|
||||
start += advance * point_size / 24. / 96.;
|
||||
}
|
||||
|
||||
let template = &run.font.template;
|
||||
|
||||
SHARED_FONT_CACHE.with(|font_cache| {
|
||||
let identifier = template.identifier();
|
||||
if !font_cache.borrow().contains_key(&identifier) {
|
||||
let Ok(font) = Font::from_bytes(template.data(), identifier.index()) else {
|
||||
return;
|
||||
};
|
||||
font_cache.borrow_mut().insert(identifier.clone(), font);
|
||||
}
|
||||
|
||||
let font_cache = font_cache.borrow();
|
||||
let Some(font) = font_cache.get(&identifier) else {
|
||||
return;
|
||||
};
|
||||
|
||||
self.draw_glyphs(
|
||||
font,
|
||||
point_size,
|
||||
&font,
|
||||
run.font.descriptor.pt_size.to_f32_px(),
|
||||
&ids,
|
||||
&positions,
|
||||
&pattern.source(),
|
||||
options.as_raqote(),
|
||||
draw_options.as_raqote(),
|
||||
);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn fill_rect(
|
||||
|
|
|
@ -480,9 +480,7 @@ pub struct FontGroup {
|
|||
}
|
||||
|
||||
impl FontGroup {
|
||||
pub fn new(style: &FontStyleStruct) -> FontGroup {
|
||||
let descriptor = FontDescriptor::from(style);
|
||||
|
||||
pub fn new(style: &FontStyleStruct, descriptor: FontDescriptor) -> FontGroup {
|
||||
let families: SmallVec<[FontGroupFamily; 8]> = style
|
||||
.font_family
|
||||
.families
|
||||
|
|
|
@ -681,7 +681,11 @@ impl<FCT: FontSource> CachingFontSource<FCT> {
|
|||
if let Some(font_group) = self.resolved_font_groups.read().get(&cache_key) {
|
||||
return font_group.clone();
|
||||
}
|
||||
let font_group = Arc::new(RwLock::new(FontGroup::new(&cache_key.style)));
|
||||
|
||||
let mut descriptor = FontDescriptor::from(&*cache_key.style);
|
||||
descriptor.pt_size = size;
|
||||
|
||||
let font_group = Arc::new(RwLock::new(FontGroup::new(&cache_key.style, descriptor)));
|
||||
self.resolved_font_groups
|
||||
.write()
|
||||
.insert(cache_key, font_group.clone());
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue