592 lines
24 KiB
Rust
592 lines
24 KiB
Rust
// pathfinder/canvas/src/text.rs
|
|
//
|
|
// Copyright © 2019 The Pathfinder Project Developers.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
|
// option. This file may not be copied, modified, or distributed
|
|
// except according to those terms.
|
|
|
|
use crate::{CanvasRenderingContext2D, State, TextAlign, TextBaseline};
|
|
use font_kit::canvas::RasterizationOptions;
|
|
use font_kit::family_name::FamilyName;
|
|
use font_kit::handle::Handle;
|
|
use font_kit::hinting::HintingOptions;
|
|
use font_kit::loaders::default::Font;
|
|
use font_kit::properties::Properties;
|
|
use font_kit::source::{Source, SystemSource};
|
|
use font_kit::sources::mem::MemSource;
|
|
use pathfinder_geometry::transform2d::Transform2F;
|
|
use pathfinder_geometry::util;
|
|
use pathfinder_geometry::vector::{Vector2F, vec2f};
|
|
use pathfinder_renderer::paint::PaintId;
|
|
use pathfinder_text::{FontContext, FontRenderOptions, TextRenderMode};
|
|
use skribo::{FontCollection, FontFamily, FontRef, Layout as SkriboLayout, TextStyle};
|
|
use std::borrow::Cow;
|
|
use std::cell::{Cell, RefCell};
|
|
use std::rc::Rc;
|
|
use std::sync::Arc;
|
|
|
|
impl CanvasRenderingContext2D {
|
|
/// Fills the given text using the current style.
|
|
///
|
|
/// As an extension, you may pass in the `TextMetrics` object returned by `measure_text()` to
|
|
/// fill the text that you passed into `measure_text()` with the layout-related style
|
|
/// properties set at the time you called that function. This allows Pathfinder to skip having
|
|
/// to lay out the text again.
|
|
pub fn fill_text<T>(&mut self, text: &T, position: Vector2F) where T: ToTextLayout + ?Sized {
|
|
let paint = self.current_state.resolve_paint(&self.current_state.fill_paint);
|
|
let paint_id = self.canvas.scene.push_paint(&paint);
|
|
self.fill_or_stroke_text(text, position, paint_id, TextRenderMode::Fill);
|
|
}
|
|
|
|
/// Strokes the given text using the current style.
|
|
///
|
|
/// As an extension, you may pass in the `TextMetrics` object returned by `measure_text()` to
|
|
/// stroke the text that you passed into `measure_text()` with the layout-related style
|
|
/// properties set at the time you called that function. This allows Pathfinder to skip having
|
|
/// to lay out the text again.
|
|
pub fn stroke_text<T>(&mut self, text: &T, position: Vector2F) where T: ToTextLayout + ?Sized {
|
|
let paint = self.current_state.resolve_paint(&self.current_state.stroke_paint);
|
|
let paint_id = self.canvas.scene.push_paint(&paint);
|
|
let render_mode = TextRenderMode::Stroke(self.current_state.resolve_stroke_style());
|
|
self.fill_or_stroke_text(text, position, paint_id, render_mode);
|
|
}
|
|
|
|
/// Returns metrics of the given text using the current style.
|
|
///
|
|
/// As an extension, the returned `TextMetrics` object contains all the layout data for the
|
|
/// string and can be used in its place when calling `fill_text()` and `stroke_text()` to avoid
|
|
/// needlessly performing layout multiple times.
|
|
pub fn measure_text<T>(&self, text: &T) -> TextMetrics where T: ToTextLayout + ?Sized {
|
|
text.layout(CanvasState(&self.current_state)).into_owned()
|
|
}
|
|
|
|
fn fill_or_stroke_text<T>(&mut self,
|
|
text: &T,
|
|
mut position: Vector2F,
|
|
paint_id: PaintId,
|
|
render_mode: TextRenderMode)
|
|
where T: ToTextLayout + ?Sized {
|
|
let layout = text.layout(CanvasState(&self.current_state));
|
|
|
|
let clip_path = self.current_state.clip_path;
|
|
let blend_mode = self.current_state.global_composite_operation.to_blend_mode();
|
|
|
|
position += layout.text_origin();
|
|
let transform = self.current_state.transform * Transform2F::from_translation(position);
|
|
|
|
// TODO(pcwalton): Report errors.
|
|
drop(self.canvas_font_context
|
|
.0
|
|
.borrow_mut()
|
|
.font_context
|
|
.push_layout(&mut self.canvas.scene,
|
|
&layout.skribo_layout,
|
|
&TextStyle { size: layout.font_size },
|
|
&FontRenderOptions {
|
|
transform,
|
|
render_mode,
|
|
hinting_options: HintingOptions::None,
|
|
clip_path,
|
|
blend_mode,
|
|
paint_id,
|
|
}));
|
|
}
|
|
|
|
// Text styles
|
|
|
|
#[inline]
|
|
pub fn font(&self) -> Arc<FontCollection> {
|
|
self.current_state.font_collection.clone()
|
|
}
|
|
|
|
#[inline]
|
|
pub fn set_font<FC>(&mut self, font_collection: FC) where FC: IntoFontCollection {
|
|
let font_collection = font_collection.into_font_collection(&self.canvas_font_context);
|
|
self.current_state.font_collection = font_collection;
|
|
}
|
|
|
|
#[inline]
|
|
pub fn font_size(&self) -> f32 {
|
|
self.current_state.font_size
|
|
}
|
|
|
|
#[inline]
|
|
pub fn set_font_size(&mut self, new_font_size: f32) {
|
|
self.current_state.font_size = new_font_size;
|
|
}
|
|
|
|
#[inline]
|
|
pub fn text_align(&self) -> TextAlign {
|
|
self.current_state.text_align
|
|
}
|
|
|
|
#[inline]
|
|
pub fn set_text_align(&mut self, new_text_align: TextAlign) {
|
|
self.current_state.text_align = new_text_align;
|
|
}
|
|
|
|
#[inline]
|
|
pub fn text_baseline(&self) -> TextBaseline {
|
|
self.current_state.text_baseline
|
|
}
|
|
|
|
#[inline]
|
|
pub fn set_text_baseline(&mut self, new_text_baseline: TextBaseline) {
|
|
self.current_state.text_baseline = new_text_baseline;
|
|
}
|
|
}
|
|
|
|
// Avoids leaking `State` to the outside.
|
|
#[doc(hidden)]
|
|
pub struct CanvasState<'a>(&'a State);
|
|
|
|
/// A trait that encompasses both text that has been laid out (i.e. `TextMetrics` or skribo's
|
|
/// `Layout`) and text that has not yet been laid out.
|
|
pub trait ToTextLayout {
|
|
#[doc(hidden)]
|
|
fn layout(&self, state: CanvasState) -> Cow<TextMetrics>;
|
|
}
|
|
|
|
impl ToTextLayout for str {
|
|
fn layout(&self, state: CanvasState) -> Cow<TextMetrics> {
|
|
let skribo_layout = Rc::new(skribo::layout(&TextStyle { size: state.0.font_size },
|
|
&state.0.font_collection,
|
|
self));
|
|
Cow::Owned(TextMetrics::new(skribo_layout,
|
|
state.0.font_size,
|
|
state.0.text_align,
|
|
state.0.text_baseline))
|
|
}
|
|
}
|
|
|
|
impl ToTextLayout for String {
|
|
fn layout(&self, state: CanvasState) -> Cow<TextMetrics> {
|
|
let this: &str = self;
|
|
this.layout(state)
|
|
}
|
|
}
|
|
|
|
impl ToTextLayout for Rc<SkriboLayout> {
|
|
fn layout(&self, state: CanvasState) -> Cow<TextMetrics> {
|
|
Cow::Owned(TextMetrics::new((*self).clone(),
|
|
state.0.font_size,
|
|
state.0.text_align,
|
|
state.0.text_baseline))
|
|
}
|
|
}
|
|
|
|
impl ToTextLayout for TextMetrics {
|
|
fn layout(&self, _: CanvasState) -> Cow<TextMetrics> {
|
|
Cow::Borrowed(self)
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "pf-text")]
|
|
#[derive(Clone)]
|
|
pub struct CanvasFontContext(pub(crate) Rc<RefCell<CanvasFontContextData>>);
|
|
|
|
pub(super) struct CanvasFontContextData {
|
|
pub(super) font_context: FontContext<Font>,
|
|
#[allow(dead_code)]
|
|
pub(super) font_source: Arc<dyn Source>,
|
|
#[allow(dead_code)]
|
|
pub(super) default_font_collection: Arc<FontCollection>,
|
|
}
|
|
|
|
impl CanvasFontContext {
|
|
pub fn new(font_source: Arc<dyn Source>) -> CanvasFontContext {
|
|
let mut default_font_collection = FontCollection::new();
|
|
if let Ok(default_font) = font_source.select_best_match(&[FamilyName::SansSerif],
|
|
&Properties::new()) {
|
|
if let Ok(default_font) = default_font.load() {
|
|
default_font_collection.add_family(FontFamily::new_from_font(default_font));
|
|
}
|
|
}
|
|
|
|
CanvasFontContext(Rc::new(RefCell::new(CanvasFontContextData {
|
|
font_source,
|
|
default_font_collection: Arc::new(default_font_collection),
|
|
font_context: FontContext::new(),
|
|
})))
|
|
}
|
|
|
|
/// A convenience method to create a font context with the system source.
|
|
/// This allows usage of fonts installed on the system.
|
|
pub fn from_system_source() -> CanvasFontContext {
|
|
CanvasFontContext::new(Arc::new(SystemSource::new()))
|
|
}
|
|
|
|
/// A convenience method to create a font context with a set of in-memory fonts.
|
|
pub fn from_fonts<I>(fonts: I) -> CanvasFontContext where I: Iterator<Item = Handle> {
|
|
CanvasFontContext::new(Arc::new(MemSource::from_fonts(fonts).unwrap()))
|
|
}
|
|
|
|
fn get_font_by_postscript_name(&self, postscript_name: &str) -> Font {
|
|
let this = self.0.borrow();
|
|
if let Some(cached_font) = this.font_context.get_cached_font(postscript_name) {
|
|
return (*cached_font).clone();
|
|
}
|
|
this.font_source
|
|
.select_by_postscript_name(postscript_name)
|
|
.expect("Couldn't find a font with that PostScript name!")
|
|
.load()
|
|
.expect("Failed to load the font!")
|
|
}
|
|
}
|
|
|
|
// Text layout utilities
|
|
|
|
/// A laid-out run of text. Text metrics can be queried from this structure, or it can be directly
|
|
/// passed into `fill_text()` and/or `stroke_text()` to draw the text without having to lay it out
|
|
/// again.
|
|
///
|
|
/// Internally, this structure caches most of its layout queries.
|
|
#[derive(Clone)]
|
|
pub struct TextMetrics {
|
|
skribo_layout: Rc<SkriboLayout>,
|
|
font_size: f32,
|
|
align: TextAlign,
|
|
baseline: TextBaseline,
|
|
text_x_offset: Cell<Option<f32>>,
|
|
text_y_offset: Cell<Option<f32>>,
|
|
vertical_metrics: Cell<Option<VerticalMetrics>>,
|
|
// The calculated width of a segment of inline text in pixels.
|
|
width: Cell<Option<f32>>,
|
|
// The distance from the typographic left side of the text to the left side of the bounding
|
|
// rectangle of the given text, in pixels. The distance is measured parallel to the baseline.
|
|
actual_left_extent: Cell<Option<f32>>,
|
|
// The distance from the typographic right side of the text to the right side of the bounding
|
|
// rectangle of the given text, in pixels. The distance is measured parallel to the baseline.
|
|
actual_right_extent: Cell<Option<f32>>,
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
struct VerticalMetrics {
|
|
// The distance from the horizontal line indicated by the `text_baseline` state to the top of
|
|
// the highest bounding rectangle of all the fonts used to render the text, in pixels.
|
|
font_bounding_box_ascent: f32,
|
|
// The distance from the horizontal line indicated by the `text_baseline` state to the bottom
|
|
// of the highest bounding rectangle of all the fonts used to render the text, in pixels.
|
|
font_bounding_box_descent: f32,
|
|
// The distance from the horizontal line indicated by the `text_baseline` state to the top of
|
|
// the bounding rectangle used to render the text, in pixels.
|
|
actual_bounding_box_ascent: f32,
|
|
// The distance from the horizontal line indicated by the `text_baseline` state to the bottom
|
|
// of the bounding rectangle used to render the text, in pixels.
|
|
actual_bounding_box_descent: f32,
|
|
// The distance from the horizontal line indicated by the `text_baseline` state to the top of
|
|
// the em square in the line box, in pixels.
|
|
em_height_ascent: f32,
|
|
// The distance from the horizontal line indicated by the `text_baseline` state to the bottom
|
|
// of the em square in the line box, in pixels.
|
|
em_height_descent: f32,
|
|
// The distance from the horizontal line indicated by the `text_baseline` state to the hanging
|
|
// baseline of the line box, in pixels.
|
|
hanging_baseline: f32,
|
|
// The distance from the horizontal line indicated by the `text_baseline` state to the
|
|
// alphabetic baseline of the line box, in pixels.
|
|
alphabetic_baseline: f32,
|
|
// The distance from the horizontal line indicated by the `text_baseline` state to the
|
|
// ideographic baseline of the line box, in pixels.
|
|
ideographic_baseline: f32,
|
|
}
|
|
|
|
impl TextMetrics {
|
|
pub fn new(skribo_layout: Rc<SkriboLayout>,
|
|
font_size: f32,
|
|
align: TextAlign,
|
|
baseline: TextBaseline)
|
|
-> TextMetrics {
|
|
TextMetrics {
|
|
skribo_layout,
|
|
font_size,
|
|
align,
|
|
baseline,
|
|
text_x_offset: Cell::new(None),
|
|
text_y_offset: Cell::new(None),
|
|
vertical_metrics: Cell::new(None),
|
|
width: Cell::new(None),
|
|
actual_left_extent: Cell::new(None),
|
|
actual_right_extent: Cell::new(None),
|
|
}
|
|
}
|
|
|
|
pub fn text_x_offset(&self) -> f32 {
|
|
if self.text_x_offset.get().is_none() {
|
|
self.text_x_offset.set(Some(match self.align {
|
|
TextAlign::Left => 0.0,
|
|
TextAlign::Right => -self.width(),
|
|
TextAlign::Center => -0.5 * self.width(),
|
|
}));
|
|
}
|
|
self.text_x_offset.get().unwrap()
|
|
}
|
|
|
|
pub fn text_y_offset(&self) -> f32 {
|
|
self.populate_vertical_metrics_if_necessary();
|
|
if self.text_y_offset.get().is_none() {
|
|
let vertical_metrics = self.vertical_metrics.get().unwrap();
|
|
self.text_y_offset.set(Some(match self.baseline {
|
|
TextBaseline::Alphabetic => 0.0,
|
|
TextBaseline::Top => vertical_metrics.em_height_ascent,
|
|
TextBaseline::Middle => {
|
|
util::lerp(vertical_metrics.em_height_ascent,
|
|
vertical_metrics.em_height_descent,
|
|
0.5)
|
|
}
|
|
TextBaseline::Bottom => vertical_metrics.em_height_descent,
|
|
TextBaseline::Ideographic => vertical_metrics.ideographic_baseline,
|
|
TextBaseline::Hanging => vertical_metrics.hanging_baseline,
|
|
}));
|
|
}
|
|
self.text_y_offset.get().unwrap()
|
|
}
|
|
|
|
fn text_origin(&self) -> Vector2F {
|
|
vec2f(self.text_x_offset(), self.text_y_offset())
|
|
}
|
|
|
|
pub fn width(&self) -> f32 {
|
|
if self.width.get().is_none() {
|
|
match self.skribo_layout.glyphs.last() {
|
|
None => self.width.set(Some(0.0)),
|
|
Some(last_glyph) => {
|
|
let glyph_id = last_glyph.glyph_id;
|
|
let font_metrics = last_glyph.font.font.metrics();
|
|
let scale_factor = self.skribo_layout.size / font_metrics.units_per_em as f32;
|
|
let glyph_rect = last_glyph.font.font.typographic_bounds(glyph_id).unwrap();
|
|
self.width.set(Some(last_glyph.offset.x() +
|
|
glyph_rect.max_x() * scale_factor));
|
|
}
|
|
}
|
|
|
|
}
|
|
self.width.get().unwrap()
|
|
}
|
|
|
|
fn populate_vertical_metrics_if_necessary(&self) {
|
|
if self.vertical_metrics.get().is_none() {
|
|
self.vertical_metrics.set(Some(VerticalMetrics::measure(&self.skribo_layout)));
|
|
}
|
|
}
|
|
|
|
pub fn font_bounding_box_ascent(&self) -> f32 {
|
|
self.populate_vertical_metrics_if_necessary();
|
|
self.vertical_metrics.get().unwrap().font_bounding_box_ascent - self.text_y_offset()
|
|
}
|
|
|
|
pub fn font_bounding_box_descent(&self) -> f32 {
|
|
self.populate_vertical_metrics_if_necessary();
|
|
self.vertical_metrics.get().unwrap().font_bounding_box_descent - self.text_y_offset()
|
|
}
|
|
|
|
pub fn actual_bounding_box_ascent(&self) -> f32 {
|
|
self.populate_vertical_metrics_if_necessary();
|
|
self.vertical_metrics.get().unwrap().actual_bounding_box_ascent - self.text_y_offset()
|
|
}
|
|
|
|
pub fn actual_bounding_box_descent(&self) -> f32 {
|
|
self.populate_vertical_metrics_if_necessary();
|
|
self.vertical_metrics.get().unwrap().actual_bounding_box_descent - self.text_y_offset()
|
|
}
|
|
|
|
pub fn em_height_ascent(&self) -> f32 {
|
|
self.populate_vertical_metrics_if_necessary();
|
|
self.vertical_metrics.get().unwrap().em_height_ascent - self.text_y_offset()
|
|
}
|
|
|
|
pub fn em_height_descent(&self) -> f32 {
|
|
self.populate_vertical_metrics_if_necessary();
|
|
self.vertical_metrics.get().unwrap().em_height_descent - self.text_y_offset()
|
|
}
|
|
|
|
pub fn actual_bounding_box_left(&self) -> f32 {
|
|
if self.actual_left_extent.get().is_none() {
|
|
match self.skribo_layout.glyphs.get(0) {
|
|
None => self.actual_left_extent.set(Some(0.0)),
|
|
Some(first_glyph) => {
|
|
let glyph_id = first_glyph.glyph_id;
|
|
let font_metrics = first_glyph.font.font.metrics();
|
|
let scale_factor = self.skribo_layout.size / font_metrics.units_per_em as f32;
|
|
let glyph_rect = first_glyph.font.font.raster_bounds(
|
|
glyph_id,
|
|
font_metrics.units_per_em as f32,
|
|
Transform2F::default(),
|
|
HintingOptions::None,
|
|
RasterizationOptions::GrayscaleAa).unwrap();
|
|
self.actual_left_extent.set(Some(first_glyph.offset.x() +
|
|
glyph_rect.min_x() as f32 * scale_factor));
|
|
}
|
|
}
|
|
}
|
|
self.actual_left_extent.get().unwrap() + self.text_x_offset()
|
|
}
|
|
|
|
pub fn actual_bounding_box_right(&self) -> f32 {
|
|
if self.actual_right_extent.get().is_none() {
|
|
match self.skribo_layout.glyphs.last() {
|
|
None => self.actual_right_extent.set(Some(0.0)),
|
|
Some(last_glyph) => {
|
|
let glyph_id = last_glyph.glyph_id;
|
|
let font_metrics = last_glyph.font.font.metrics();
|
|
let scale_factor = self.skribo_layout.size / font_metrics.units_per_em as f32;
|
|
let glyph_rect = last_glyph.font.font.raster_bounds(
|
|
glyph_id,
|
|
font_metrics.units_per_em as f32,
|
|
Transform2F::default(),
|
|
HintingOptions::None,
|
|
RasterizationOptions::GrayscaleAa).unwrap();
|
|
self.actual_right_extent.set(Some(last_glyph.offset.x() +
|
|
glyph_rect.max_x() as f32 * scale_factor));
|
|
}
|
|
}
|
|
}
|
|
self.actual_right_extent.get().unwrap() + self.text_x_offset()
|
|
}
|
|
|
|
pub fn hanging_baseline(&self) -> f32 {
|
|
self.populate_vertical_metrics_if_necessary();
|
|
self.vertical_metrics.get().unwrap().hanging_baseline - self.text_y_offset()
|
|
}
|
|
|
|
pub fn alphabetic_baseline(&self) -> f32 {
|
|
self.populate_vertical_metrics_if_necessary();
|
|
self.vertical_metrics.get().unwrap().alphabetic_baseline - self.text_y_offset()
|
|
}
|
|
|
|
pub fn ideographic_baseline(&self) -> f32 {
|
|
self.populate_vertical_metrics_if_necessary();
|
|
self.vertical_metrics.get().unwrap().ideographic_baseline - self.text_y_offset()
|
|
}
|
|
|
|
}
|
|
|
|
impl VerticalMetrics {
|
|
fn measure(skribo_layout: &SkriboLayout) -> VerticalMetrics {
|
|
let mut vertical_metrics = VerticalMetrics {
|
|
font_bounding_box_ascent: 0.0,
|
|
font_bounding_box_descent: 0.0,
|
|
actual_bounding_box_ascent: 0.0,
|
|
actual_bounding_box_descent: 0.0,
|
|
em_height_ascent: 0.0,
|
|
em_height_descent: 0.0,
|
|
hanging_baseline: 0.0,
|
|
alphabetic_baseline: 0.0,
|
|
ideographic_baseline: 0.0,
|
|
};
|
|
|
|
let mut last_font: Option<Arc<Font>> = None;
|
|
for glyph in &skribo_layout.glyphs {
|
|
match last_font {
|
|
Some(ref last_font) if Arc::ptr_eq(&last_font, &glyph.font.font) => {}
|
|
_ => {
|
|
let font = glyph.font.font.clone();
|
|
|
|
let font_metrics = font.metrics();
|
|
let scale_factor = skribo_layout.size / font_metrics.units_per_em as f32;
|
|
vertical_metrics.em_height_ascent =
|
|
(font_metrics.ascent *
|
|
scale_factor).max(vertical_metrics.em_height_ascent);
|
|
vertical_metrics.em_height_descent =
|
|
(font_metrics.descent *
|
|
scale_factor).min(vertical_metrics.em_height_descent);
|
|
vertical_metrics.font_bounding_box_ascent =
|
|
(font_metrics.bounding_box.max_y() *
|
|
scale_factor).max(vertical_metrics.font_bounding_box_ascent);
|
|
vertical_metrics.font_bounding_box_descent =
|
|
(font_metrics.bounding_box.min_y() *
|
|
scale_factor).min(vertical_metrics.font_bounding_box_descent);
|
|
|
|
last_font = Some(font);
|
|
}
|
|
}
|
|
|
|
let font = last_font.as_ref().unwrap();
|
|
let glyph_rect = font.raster_bounds(glyph.glyph_id,
|
|
skribo_layout.size,
|
|
Transform2F::default(),
|
|
HintingOptions::None,
|
|
RasterizationOptions::GrayscaleAa).unwrap();
|
|
vertical_metrics.actual_bounding_box_ascent =
|
|
(glyph_rect.max_y() as f32).max(vertical_metrics.actual_bounding_box_ascent);
|
|
vertical_metrics.actual_bounding_box_descent =
|
|
(glyph_rect.min_y() as f32).min(vertical_metrics.actual_bounding_box_descent);
|
|
}
|
|
|
|
vertical_metrics
|
|
}
|
|
}
|
|
|
|
/// Various things that can be conveniently converted into font collections for use with
|
|
/// `CanvasRenderingContext2D::set_font()`.
|
|
pub trait IntoFontCollection {
|
|
fn into_font_collection(self, font_context: &CanvasFontContext) -> Arc<FontCollection>;
|
|
}
|
|
|
|
impl IntoFontCollection for Arc<FontCollection> {
|
|
#[inline]
|
|
fn into_font_collection(self, _: &CanvasFontContext) -> Arc<FontCollection> {
|
|
self
|
|
}
|
|
}
|
|
|
|
impl IntoFontCollection for FontFamily {
|
|
#[inline]
|
|
fn into_font_collection(self, _: &CanvasFontContext) -> Arc<FontCollection> {
|
|
let mut font_collection = FontCollection::new();
|
|
font_collection.add_family(self);
|
|
Arc::new(font_collection)
|
|
}
|
|
}
|
|
|
|
impl IntoFontCollection for Vec<FontFamily> {
|
|
#[inline]
|
|
fn into_font_collection(self, _: &CanvasFontContext) -> Arc<FontCollection> {
|
|
let mut font_collection = FontCollection::new();
|
|
for family in self {
|
|
font_collection.add_family(family);
|
|
}
|
|
Arc::new(font_collection)
|
|
}
|
|
}
|
|
|
|
impl IntoFontCollection for Font {
|
|
#[inline]
|
|
fn into_font_collection(self, context: &CanvasFontContext) -> Arc<FontCollection> {
|
|
FontFamily::new_from_font(self).into_font_collection(context)
|
|
}
|
|
}
|
|
|
|
impl<'a> IntoFontCollection for &'a [Font] {
|
|
#[inline]
|
|
fn into_font_collection(self, context: &CanvasFontContext) -> Arc<FontCollection> {
|
|
let mut family = FontFamily::new();
|
|
for font in self {
|
|
family.add_font(FontRef::new((*font).clone()))
|
|
}
|
|
family.into_font_collection(context)
|
|
}
|
|
}
|
|
|
|
impl<'a> IntoFontCollection for &'a str {
|
|
#[inline]
|
|
fn into_font_collection(self, context: &CanvasFontContext) -> Arc<FontCollection> {
|
|
context.get_font_by_postscript_name(self).into_font_collection(context)
|
|
}
|
|
}
|
|
|
|
impl<'a, 'b> IntoFontCollection for &'a [&'b str] {
|
|
#[inline]
|
|
fn into_font_collection(self, context: &CanvasFontContext) -> Arc<FontCollection> {
|
|
let mut font_collection = FontCollection::new();
|
|
for postscript_name in self {
|
|
let font = context.get_font_by_postscript_name(postscript_name);
|
|
font_collection.add_family(FontFamily::new_from_font(font));
|
|
}
|
|
Arc::new(font_collection)
|
|
}
|
|
}
|