mirror of
https://github.com/italicsjenga/vello.git
synced 2025-01-08 20:01:30 +11:00
Replace font backend
This replaces the old moscato font backend with one that has proper support for variable fonts.
This commit is contained in:
parent
17096ad878
commit
eb8cc5275f
|
@ -44,7 +44,7 @@ futures-intrusive = "0.5.0"
|
|||
parking_lot = "0.12"
|
||||
bytemuck = { version = "1.12.1", features = ["derive"] }
|
||||
smallvec = "1.8.0"
|
||||
moscato = { git = "https://github.com/dfrg/pinot", rev = "59db153" }
|
||||
fello = { git = "https://github.com/dfrg/fount", rev = "a8c0686ca9da236420c04d1f476c41cf7fe6d2ba" }
|
||||
peniko = { git = "https://github.com/linebender/peniko", rev = "cafdac9a211a0fb2fec5656bd663d1ac770bcc81" }
|
||||
guillotiere = "0.6.2"
|
||||
|
||||
|
|
BIN
examples/assets/inconsolata/Inconsolata.ttf
Normal file
BIN
examples/assets/inconsolata/Inconsolata.ttf
Normal file
Binary file not shown.
91
examples/assets/inconsolata/LICENSE.txt
Normal file
91
examples/assets/inconsolata/LICENSE.txt
Normal file
|
@ -0,0 +1,91 @@
|
|||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
@ -18,11 +18,9 @@ use std::sync::Arc;
|
|||
|
||||
use vello::{
|
||||
encoding::Glyph,
|
||||
glyph::{
|
||||
pinot,
|
||||
pinot::{FontRef, TableProvider},
|
||||
GlyphContext,
|
||||
},
|
||||
fello::meta::MetadataProvider,
|
||||
fello::raw::FontRef,
|
||||
glyph::GlyphContext,
|
||||
kurbo::Affine,
|
||||
peniko::{Blob, Brush, BrushRef, Font, StyleRef},
|
||||
SceneBuilder,
|
||||
|
@ -30,24 +28,28 @@ use vello::{
|
|||
|
||||
// This is very much a hack to get things working.
|
||||
// On Windows, can set this to "c:\\Windows\\Fonts\\seguiemj.ttf" to get color emoji
|
||||
const FONT_DATA: &[u8] = include_bytes!("../../assets/roboto/Roboto-Regular.ttf");
|
||||
const ROBOTO_FONT: &[u8] = include_bytes!("../../assets/roboto/Roboto-Regular.ttf");
|
||||
const INCONSOLATA_FONT: &[u8] = include_bytes!("../../assets/inconsolata/Inconsolata.ttf");
|
||||
|
||||
pub struct SimpleText {
|
||||
gcx: GlyphContext,
|
||||
font: Font,
|
||||
roboto: Font,
|
||||
inconsolata: Font,
|
||||
}
|
||||
|
||||
impl SimpleText {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
gcx: GlyphContext::new(),
|
||||
font: Font::new(Blob::new(Arc::new(FONT_DATA)), 0),
|
||||
roboto: Font::new(Blob::new(Arc::new(ROBOTO_FONT)), 0),
|
||||
inconsolata: Font::new(Blob::new(Arc::new(INCONSOLATA_FONT)), 0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_run<'a>(
|
||||
&mut self,
|
||||
builder: &mut SceneBuilder,
|
||||
font: Option<&Font>,
|
||||
size: f32,
|
||||
brush: impl Into<BrushRef<'a>>,
|
||||
transform: Affine,
|
||||
|
@ -55,92 +57,126 @@ impl SimpleText {
|
|||
style: impl Into<StyleRef<'a>>,
|
||||
text: &str,
|
||||
) {
|
||||
let font = FontRef {
|
||||
data: FONT_DATA,
|
||||
offset: 0,
|
||||
self.add_var_run(
|
||||
builder,
|
||||
font,
|
||||
size,
|
||||
&[],
|
||||
brush,
|
||||
transform,
|
||||
glyph_transform,
|
||||
style,
|
||||
text,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn add_var_run<'a>(
|
||||
&mut self,
|
||||
builder: &mut SceneBuilder,
|
||||
font: Option<&Font>,
|
||||
size: f32,
|
||||
variations: &[(&str, f32)],
|
||||
brush: impl Into<BrushRef<'a>>,
|
||||
transform: Affine,
|
||||
glyph_transform: Option<Affine>,
|
||||
style: impl Into<StyleRef<'a>>,
|
||||
text: &str,
|
||||
) {
|
||||
let default_font = if variations.is_empty() {
|
||||
&self.roboto
|
||||
} else {
|
||||
&self.inconsolata
|
||||
};
|
||||
let font = font.unwrap_or(default_font);
|
||||
let font_ref = to_font_ref(font).unwrap();
|
||||
let brush = brush.into();
|
||||
let style = style.into();
|
||||
if let Some(cmap) = font.cmap() {
|
||||
if let Some(hmtx) = font.hmtx() {
|
||||
let upem = font.head().map(|head| head.units_per_em()).unwrap_or(1000) as f64;
|
||||
let scale = size as f64 / upem;
|
||||
let hmetrics = hmtx.hmetrics();
|
||||
let default_advance = hmetrics
|
||||
.get(hmetrics.len().saturating_sub(1))
|
||||
.map(|h| h.advance_width)
|
||||
.unwrap_or(0);
|
||||
let mut pen_x = 0f64;
|
||||
builder
|
||||
.draw_glyphs(&self.font)
|
||||
.font_size(size)
|
||||
.transform(transform)
|
||||
.glyph_transform(glyph_transform)
|
||||
.brush(brush)
|
||||
.draw(
|
||||
style,
|
||||
text.chars().map(|ch| {
|
||||
let gid = cmap.map(ch as u32).unwrap_or(0);
|
||||
let advance = hmetrics
|
||||
.get(gid as usize)
|
||||
.map(|h| h.advance_width)
|
||||
.unwrap_or(default_advance)
|
||||
as f64
|
||||
* scale;
|
||||
let x = pen_x as f32;
|
||||
pen_x += advance;
|
||||
Glyph {
|
||||
id: gid as u32,
|
||||
x,
|
||||
y: 0.0,
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
let axes = font_ref.axes();
|
||||
let fello_size = vello::fello::Size::new(size);
|
||||
let coords = axes
|
||||
.normalize(variations.iter().copied())
|
||||
.collect::<Vec<_>>();
|
||||
let charmap = font_ref.charmap();
|
||||
let metrics = font_ref.metrics(fello_size, coords.as_slice().into());
|
||||
let line_height = metrics.ascent - metrics.descent + metrics.leading;
|
||||
let glyph_metrics = font_ref.glyph_metrics(fello_size, coords.as_slice().into());
|
||||
let mut pen_x = 0f32;
|
||||
let mut pen_y = 0f32;
|
||||
builder
|
||||
.draw_glyphs(font)
|
||||
.font_size(size)
|
||||
.transform(transform)
|
||||
.glyph_transform(glyph_transform)
|
||||
.normalized_coords(&coords)
|
||||
.brush(brush)
|
||||
.draw(
|
||||
style,
|
||||
text.chars().filter_map(|ch| {
|
||||
if ch == '\n' {
|
||||
pen_y += line_height;
|
||||
pen_x = 0.0;
|
||||
return None;
|
||||
}
|
||||
let gid = charmap.map(ch).unwrap_or_default();
|
||||
let advance = glyph_metrics.advance_width(gid).unwrap_or_default();
|
||||
let x = pen_x as f32;
|
||||
pen_x += advance;
|
||||
Some(Glyph {
|
||||
id: gid.to_u16() as u32,
|
||||
x,
|
||||
y: pen_y,
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn add(
|
||||
&mut self,
|
||||
builder: &mut SceneBuilder,
|
||||
font: Option<&FontRef>,
|
||||
font: Option<&Font>,
|
||||
size: f32,
|
||||
brush: Option<&Brush>,
|
||||
transform: Affine,
|
||||
text: &str,
|
||||
) {
|
||||
let font = font.unwrap_or(&FontRef {
|
||||
data: FONT_DATA,
|
||||
offset: 0,
|
||||
});
|
||||
if let Some(cmap) = font.cmap() {
|
||||
if let Some(hmtx) = font.hmtx() {
|
||||
let upem = font.head().map(|head| head.units_per_em()).unwrap_or(1000) as f64;
|
||||
let scale = size as f64 / upem;
|
||||
let vars: [(pinot::types::Tag, f32); 0] = [];
|
||||
let mut provider = self.gcx.new_provider(font, None, size, false, vars);
|
||||
let hmetrics = hmtx.hmetrics();
|
||||
let default_advance = hmetrics
|
||||
.get(hmetrics.len().saturating_sub(1))
|
||||
.map(|h| h.advance_width)
|
||||
.unwrap_or(0);
|
||||
let mut pen_x = 0f64;
|
||||
for ch in text.chars() {
|
||||
let gid = cmap.map(ch as u32).unwrap_or(0);
|
||||
let advance = hmetrics
|
||||
.get(gid as usize)
|
||||
.map(|h| h.advance_width)
|
||||
.unwrap_or(default_advance) as f64
|
||||
* scale;
|
||||
if let Some(glyph) = provider.get(gid, brush) {
|
||||
let xform = transform
|
||||
* Affine::translate((pen_x, 0.0))
|
||||
* Affine::scale_non_uniform(1.0, -1.0);
|
||||
builder.append(&glyph, Some(xform));
|
||||
}
|
||||
pen_x += advance;
|
||||
}
|
||||
let default_font = FontRef::new(ROBOTO_FONT).unwrap();
|
||||
let font = font
|
||||
.map(|font| to_font_ref(font))
|
||||
.flatten()
|
||||
.unwrap_or(default_font);
|
||||
let fello_size = vello::fello::Size::new(size);
|
||||
let charmap = font.charmap();
|
||||
let metrics = font.metrics(fello_size, Default::default());
|
||||
let line_height = metrics.ascent - metrics.descent + metrics.leading;
|
||||
let glyph_metrics = font.glyph_metrics(fello_size, Default::default());
|
||||
let mut pen_x = 0f64;
|
||||
let mut pen_y = 0f64;
|
||||
let vars: [(&str, f32); 0] = [];
|
||||
let mut provider = self.gcx.new_provider(&font, None, size, false, vars);
|
||||
for ch in text.chars() {
|
||||
if ch == '\n' {
|
||||
pen_y += line_height as f64;
|
||||
pen_x = 0.0;
|
||||
continue;
|
||||
}
|
||||
let gid = charmap.map(ch).unwrap_or_default();
|
||||
let advance = glyph_metrics.advance_width(gid).unwrap_or_default() as f64;
|
||||
if let Some(glyph) = provider.get(gid.to_u16(), brush) {
|
||||
let xform = transform
|
||||
* Affine::translate((pen_x, pen_y))
|
||||
* Affine::scale_non_uniform(1.0, -1.0);
|
||||
builder.append(&glyph, Some(xform));
|
||||
}
|
||||
pen_x += advance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn to_font_ref<'a>(font: &'a Font) -> Option<FontRef<'a>> {
|
||||
use vello::fello::raw::FileRef;
|
||||
let file_ref = FileRef::new(font.data.as_ref()).ok()?;
|
||||
match file_ref {
|
||||
FileRef::Font(font) => Some(font),
|
||||
FileRef::Collection(collection) => collection.get(font.index).ok(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -135,6 +135,7 @@ fn animated_text(sb: &mut SceneBuilder, params: &mut SceneParams) {
|
|||
);
|
||||
params.text.add_run(
|
||||
sb,
|
||||
None,
|
||||
text_size,
|
||||
Color::WHITE,
|
||||
Affine::translate((110.0, 700.0)),
|
||||
|
@ -143,6 +144,21 @@ fn animated_text(sb: &mut SceneBuilder, params: &mut SceneParams) {
|
|||
&Stroke::new(1.0),
|
||||
s,
|
||||
);
|
||||
let t = ((params.time).sin() * 0.5 + 0.5) as f32;
|
||||
let weight = t * 700.0 + 200.0;
|
||||
let width = t * 150.0 + 50.0;
|
||||
params.text.add_var_run(
|
||||
sb,
|
||||
None,
|
||||
72.0,
|
||||
&[("wght", weight), ("wdth", width)],
|
||||
Color::WHITE,
|
||||
Affine::translate((110.0, 800.0)),
|
||||
// Add a skew to simulate an oblique font.
|
||||
None,
|
||||
Fill::NonZero,
|
||||
"And some vello\ntext with a newline",
|
||||
);
|
||||
let th = params.time as f64;
|
||||
let center = Point::new(500.0, 500.0);
|
||||
let mut p1 = center;
|
||||
|
|
|
@ -21,6 +21,7 @@ use super::{
|
|||
PathEncoder, PathTag, Transform,
|
||||
};
|
||||
|
||||
use fello::NormalizedCoord;
|
||||
use peniko::{kurbo::Shape, BlendMode, BrushRef, ColorStop, Extend, GradientKind, Image};
|
||||
|
||||
/// Encoded data streams for a scene.
|
||||
|
@ -47,7 +48,7 @@ pub struct Encoding {
|
|||
/// Sequences of glyphs.
|
||||
pub glyph_runs: Vec<GlyphRun>,
|
||||
/// Normalized coordinate buffer for variable fonts.
|
||||
pub normalized_coords: Vec<i16>,
|
||||
pub normalized_coords: Vec<NormalizedCoord>,
|
||||
/// Number of encoded paths.
|
||||
pub n_paths: u32,
|
||||
/// Number of encoded path segments.
|
||||
|
|
|
@ -17,8 +17,9 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use super::{Encoding, StreamOffsets};
|
||||
use crate::glyph::GlyphProvider;
|
||||
|
||||
use fello::scale::Scaler;
|
||||
use fello::GlyphId;
|
||||
use peniko::{Fill, Style};
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Default, Debug)]
|
||||
|
@ -46,18 +47,31 @@ impl GlyphCache {
|
|||
&mut self,
|
||||
key: GlyphKey,
|
||||
style: &Style,
|
||||
scaler: &mut GlyphProvider,
|
||||
scaler: &mut Scaler,
|
||||
) -> Option<CachedRange> {
|
||||
let is_fill = matches!(style, Style::Fill(_));
|
||||
let is_var = !scaler.normalized_coords().is_empty();
|
||||
let encoding_cache = &mut self.encoding;
|
||||
let mut encode_glyph = || {
|
||||
let start = encoding_cache.stream_offsets();
|
||||
scaler.encode_glyph(key.glyph_id as u16, style, encoding_cache)?;
|
||||
match style {
|
||||
Style::Fill(Fill::NonZero) => encoding_cache.encode_linewidth(-1.0),
|
||||
Style::Fill(Fill::EvenOdd) => encoding_cache.encode_linewidth(-2.0),
|
||||
Style::Stroke(stroke) => encoding_cache.encode_linewidth(stroke.width),
|
||||
}
|
||||
let mut path = crate::glyph::PathEncoderPen(encoding_cache.encode_path(is_fill));
|
||||
scaler
|
||||
.outline(GlyphId::new(key.glyph_id as u16), &mut path)
|
||||
.ok()?;
|
||||
if path.0.finish(false) == 0 {
|
||||
return None;
|
||||
}
|
||||
let end = encoding_cache.stream_offsets();
|
||||
Some(CachedRange { start, end })
|
||||
};
|
||||
// For now, only cache non-zero filled glyphs so we don't need to keep style
|
||||
// For now, only cache non-zero filled, non-variable glyphs so we don't need to keep style
|
||||
// as part of the key.
|
||||
let range = if matches!(style, Style::Fill(Fill::NonZero)) {
|
||||
let range = if matches!(style, Style::Fill(Fill::NonZero)) && !is_var {
|
||||
use std::collections::hash_map::Entry;
|
||||
match self.glyphs.entry(key) {
|
||||
Entry::Occupied(entry) => *entry.get(),
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
use std::ops::Range;
|
||||
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use moscato::pinot::FontRef;
|
||||
use peniko::Image;
|
||||
|
||||
use super::{
|
||||
|
@ -26,7 +25,6 @@ use super::{
|
|||
ramp_cache::{RampCache, Ramps},
|
||||
DrawTag, Encoding, PathTag, StreamOffsets, Transform,
|
||||
};
|
||||
use crate::glyph::GlyphContext;
|
||||
use crate::shaders;
|
||||
|
||||
/// Layout of a packed encoding.
|
||||
|
@ -144,7 +142,7 @@ pub struct Config {
|
|||
pub struct Resolver {
|
||||
glyph_cache: GlyphCache,
|
||||
glyph_ranges: Vec<CachedRange>,
|
||||
glyph_cx: GlyphContext,
|
||||
glyph_cx: fello::scale::Context,
|
||||
ramp_cache: RampCache,
|
||||
image_cache: ImageCache,
|
||||
pending_images: Vec<PendingImage>,
|
||||
|
@ -389,14 +387,19 @@ impl Resolver {
|
|||
let run = &encoding.glyph_runs[*index];
|
||||
let font_id = run.font.data.id();
|
||||
let font_size_u32 = run.font_size.to_bits();
|
||||
let Some(font) = FontRef::from_index(run.font.data.as_ref(), run.font.index) else { continue };
|
||||
let Ok(font_file) = fello::raw::FileRef::new(run.font.data.as_ref()) else { continue };
|
||||
let font = match font_file {
|
||||
fello::raw::FileRef::Font(font) => Some(font),
|
||||
fello::raw::FileRef::Collection(collection) => {
|
||||
collection.get(run.font.index).ok()
|
||||
}
|
||||
};
|
||||
let Some(font) = font else { continue };
|
||||
let glyphs = &encoding.glyphs[run.glyphs.clone()];
|
||||
let _coords = &encoding.normalized_coords[run.normalized_coords.clone()];
|
||||
let vars: [(moscato::pinot::types::Tag, f32); 0] = [];
|
||||
let hint_id = if run.font.index < 0xFF {
|
||||
Some(font_id << 8 | run.font.index as u64)
|
||||
} else {
|
||||
None
|
||||
let coords = &encoding.normalized_coords[run.normalized_coords.clone()];
|
||||
let key = fello::FontKey {
|
||||
data_id: font_id,
|
||||
index: run.font.index,
|
||||
};
|
||||
let mut hint = run.hint;
|
||||
let mut font_size = run.font_size;
|
||||
|
@ -417,7 +420,12 @@ impl Resolver {
|
|||
}
|
||||
let mut scaler = self
|
||||
.glyph_cx
|
||||
.new_provider(&font, hint_id, font_size, hint, vars);
|
||||
.new_scaler()
|
||||
.key(Some(key))
|
||||
.hint(hint.then_some(fello::scale::Hinting::VerticalSubpixel))
|
||||
.coords(coords)
|
||||
.size(fello::Size::new(font_size))
|
||||
.build(&font);
|
||||
let glyph_start = self.glyph_ranges.len();
|
||||
for glyph in glyphs {
|
||||
let key = GlyphKey {
|
||||
|
|
331
src/glyph.rs
331
src/glyph.rs
|
@ -16,17 +16,19 @@
|
|||
|
||||
//! Support for glyph rendering.
|
||||
|
||||
pub use moscato::pinot;
|
||||
use fello::scale::Pen;
|
||||
|
||||
use crate::encoding::Encoding;
|
||||
use crate::encoding::{Encoding, PathEncoder};
|
||||
use crate::scene::{SceneBuilder, SceneFragment};
|
||||
use peniko::kurbo::{Affine, Rect};
|
||||
use peniko::{Brush, Color, Fill, Mix, Style};
|
||||
use peniko::kurbo::Affine;
|
||||
use peniko::{Brush, Color, Fill, Style};
|
||||
|
||||
use moscato::{Context, Scaler};
|
||||
use pinot::{types::Tag, FontRef};
|
||||
|
||||
use smallvec::SmallVec;
|
||||
use fello::{
|
||||
raw::types::GlyphId,
|
||||
raw::FontRef,
|
||||
scale::{Context, Scaler},
|
||||
FontKey, Setting, Size,
|
||||
};
|
||||
|
||||
/// General context for creating scene fragments for glyph outlines.
|
||||
pub struct GlyphContext {
|
||||
|
@ -52,30 +54,23 @@ impl GlyphContext {
|
|||
pub fn new_provider<'a, V>(
|
||||
&'a mut self,
|
||||
font: &FontRef<'a>,
|
||||
font_id: Option<u64>,
|
||||
font_id: Option<FontKey>,
|
||||
ppem: f32,
|
||||
hint: bool,
|
||||
variations: V,
|
||||
) -> GlyphProvider<'a>
|
||||
where
|
||||
V: IntoIterator,
|
||||
V::Item: Into<(Tag, f32)>,
|
||||
V::Item: Into<Setting<f32>>,
|
||||
{
|
||||
let scaler = if let Some(font_id) = font_id {
|
||||
self.ctx
|
||||
.new_scaler_with_id(font, font_id)
|
||||
.size(ppem)
|
||||
.hint(hint)
|
||||
.variations(variations)
|
||||
.build()
|
||||
} else {
|
||||
self.ctx
|
||||
.new_scaler(font)
|
||||
.size(ppem)
|
||||
.hint(hint)
|
||||
.variations(variations)
|
||||
.build()
|
||||
};
|
||||
let scaler = self
|
||||
.ctx
|
||||
.new_scaler()
|
||||
.size(Size::new(ppem))
|
||||
.hint(hint.then_some(fello::scale::Hinting::VerticalSubpixel))
|
||||
.key(font_id)
|
||||
.variations(variations)
|
||||
.build(font);
|
||||
GlyphProvider { scaler }
|
||||
}
|
||||
}
|
||||
|
@ -90,276 +85,86 @@ impl<'a> GlyphProvider<'a> {
|
|||
/// Returns a scene fragment containing the commands to render the
|
||||
/// specified glyph.
|
||||
pub fn get(&mut self, gid: u16, brush: Option<&Brush>) -> Option<SceneFragment> {
|
||||
let glyph = self.scaler.glyph(gid)?;
|
||||
let path = glyph.path(0)?;
|
||||
let mut fragment = SceneFragment::default();
|
||||
let mut builder = SceneBuilder::for_fragment(&mut fragment);
|
||||
let mut path = BezPathPen::default();
|
||||
self.scaler.outline(GlyphId::new(gid), &mut path).ok()?;
|
||||
builder.fill(
|
||||
Fill::NonZero,
|
||||
Affine::IDENTITY,
|
||||
brush.unwrap_or(&Brush::Solid(Color::rgb8(255, 255, 255))),
|
||||
None,
|
||||
&convert_path(path.elements()),
|
||||
&path.0,
|
||||
);
|
||||
Some(fragment)
|
||||
}
|
||||
|
||||
pub fn encode_glyph(&mut self, gid: u16, style: &Style, encoding: &mut Encoding) -> Option<()> {
|
||||
let glyph = self.scaler.glyph(gid)?;
|
||||
let path = glyph.path(0)?;
|
||||
match style {
|
||||
Style::Fill(Fill::NonZero) => encoding.encode_linewidth(-1.0),
|
||||
Style::Fill(Fill::EvenOdd) => encoding.encode_linewidth(-2.0),
|
||||
Style::Stroke(stroke) => encoding.encode_linewidth(stroke.width),
|
||||
}
|
||||
let mut path_encoder = encoding.encode_path(matches!(style, Style::Fill(_)));
|
||||
for el in path.elements() {
|
||||
use moscato::Element::*;
|
||||
match el {
|
||||
MoveTo(p) => path_encoder.move_to(p.x, p.y),
|
||||
LineTo(p) => path_encoder.line_to(p.x, p.y),
|
||||
QuadTo(c, p) => path_encoder.quad_to(c.x, c.y, p.x, p.y),
|
||||
CurveTo(c0, c1, p) => path_encoder.cubic_to(c0.x, c0.y, c1.x, c1.y, p.x, p.y),
|
||||
Close => path_encoder.close(),
|
||||
}
|
||||
}
|
||||
if path_encoder.finish(false) != 0 {
|
||||
let mut path = PathEncoderPen(encoding.encode_path(matches!(style, Style::Fill(_))));
|
||||
self.scaler.outline(GlyphId::new(gid), &mut path).ok()?;
|
||||
if path.0.finish(false) != 0 {
|
||||
Some(())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a scene fragment containing the commands and resources to
|
||||
/// render the specified color glyph.
|
||||
pub fn get_color(&mut self, palette_index: u16, gid: u16) -> Option<SceneFragment> {
|
||||
use moscato::Command;
|
||||
let glyph = self.scaler.color_glyph(palette_index, gid)?;
|
||||
let mut fragment = SceneFragment::default();
|
||||
let mut builder = SceneBuilder::for_fragment(&mut fragment);
|
||||
let mut xform_stack: SmallVec<[Affine; 8]> = SmallVec::new();
|
||||
for command in glyph.commands() {
|
||||
match command {
|
||||
Command::PushTransform(xform) => {
|
||||
let xform = if let Some(parent) = xform_stack.last() {
|
||||
convert_transform(xform) * *parent
|
||||
} else {
|
||||
convert_transform(xform)
|
||||
};
|
||||
xform_stack.push(xform);
|
||||
}
|
||||
Command::PopTransform => {
|
||||
xform_stack.pop();
|
||||
}
|
||||
Command::PushClip(path_index) => {
|
||||
let path = glyph.path(*path_index)?;
|
||||
if let Some(xform) = xform_stack.last() {
|
||||
builder.push_layer(
|
||||
Mix::Clip,
|
||||
1.0,
|
||||
Affine::IDENTITY,
|
||||
&convert_transformed_path(path.elements(), xform),
|
||||
);
|
||||
} else {
|
||||
builder.push_layer(
|
||||
Mix::Clip,
|
||||
1.0,
|
||||
Affine::IDENTITY,
|
||||
&convert_path(path.elements()),
|
||||
);
|
||||
}
|
||||
}
|
||||
Command::PopClip => builder.pop_layer(),
|
||||
Command::PushLayer(bounds) => {
|
||||
let mut min = convert_point(bounds.min);
|
||||
let mut max = convert_point(bounds.max);
|
||||
if let Some(xform) = xform_stack.last() {
|
||||
min = *xform * min;
|
||||
max = *xform * max;
|
||||
}
|
||||
let rect = Rect::from_points(min, max);
|
||||
builder.push_layer(Mix::Normal, 1.0, Affine::IDENTITY, &rect);
|
||||
}
|
||||
Command::PopLayer => builder.pop_layer(),
|
||||
Command::BeginBlend(bounds, mode) => {
|
||||
let mut min = convert_point(bounds.min);
|
||||
let mut max = convert_point(bounds.max);
|
||||
if let Some(xform) = xform_stack.last() {
|
||||
min = *xform * min;
|
||||
max = *xform * max;
|
||||
}
|
||||
let rect = Rect::from_points(min, max);
|
||||
builder.push_layer(convert_blend(*mode), 1.0, Affine::IDENTITY, &rect);
|
||||
}
|
||||
Command::EndBlend => builder.pop_layer(),
|
||||
Command::SimpleFill(path_index, brush, brush_xform) => {
|
||||
let path = glyph.path(*path_index)?;
|
||||
let brush = convert_brush(brush);
|
||||
let brush_xform = brush_xform.map(|xform| convert_transform(&xform));
|
||||
if let Some(xform) = xform_stack.last() {
|
||||
builder.fill(
|
||||
Fill::NonZero,
|
||||
Affine::IDENTITY,
|
||||
&brush,
|
||||
brush_xform.map(|x| x * *xform),
|
||||
&convert_transformed_path(path.elements(), xform),
|
||||
);
|
||||
} else {
|
||||
builder.fill(
|
||||
Fill::NonZero,
|
||||
Affine::IDENTITY,
|
||||
&brush,
|
||||
brush_xform,
|
||||
&convert_path(path.elements()),
|
||||
);
|
||||
}
|
||||
}
|
||||
Command::Fill(_brush, _brush_xform) => {
|
||||
// TODO: this needs to compute a bounding box for
|
||||
// the parent clips
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(fragment)
|
||||
#[derive(Default)]
|
||||
struct BezPathPen(peniko::kurbo::BezPath);
|
||||
|
||||
impl Pen for BezPathPen {
|
||||
fn move_to(&mut self, x: f32, y: f32) {
|
||||
self.0.move_to((x as f64, y as f64))
|
||||
}
|
||||
|
||||
fn line_to(&mut self, x: f32, y: f32) {
|
||||
self.0.line_to((x as f64, y as f64))
|
||||
}
|
||||
|
||||
fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
|
||||
self.0
|
||||
.quad_to((cx0 as f64, cy0 as f64), (x as f64, y as f64))
|
||||
}
|
||||
|
||||
fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
|
||||
self.0.curve_to(
|
||||
(cx0 as f64, cy0 as f64),
|
||||
(cx1 as f64, cy1 as f64),
|
||||
(x as f64, y as f64),
|
||||
)
|
||||
}
|
||||
|
||||
fn close(&mut self) {
|
||||
self.0.close_path()
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_path(path: impl Iterator<Item = moscato::Element> + Clone) -> peniko::kurbo::BezPath {
|
||||
let mut result = peniko::kurbo::BezPath::new();
|
||||
for el in path {
|
||||
result.push(convert_path_el(&el));
|
||||
pub(crate) struct PathEncoderPen<'a>(pub PathEncoder<'a>);
|
||||
|
||||
impl Pen for PathEncoderPen<'_> {
|
||||
fn move_to(&mut self, x: f32, y: f32) {
|
||||
self.0.move_to(x, y)
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn convert_transformed_path(
|
||||
path: impl Iterator<Item = moscato::Element> + Clone,
|
||||
xform: &Affine,
|
||||
) -> peniko::kurbo::BezPath {
|
||||
let mut result = peniko::kurbo::BezPath::new();
|
||||
for el in path {
|
||||
result.push(*xform * convert_path_el(&el));
|
||||
fn line_to(&mut self, x: f32, y: f32) {
|
||||
self.0.line_to(x, y)
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn convert_blend(mode: moscato::CompositeMode) -> peniko::BlendMode {
|
||||
use moscato::CompositeMode;
|
||||
use peniko::{BlendMode, Compose};
|
||||
let mut mix = Mix::Normal;
|
||||
let mut compose = Compose::SrcOver;
|
||||
match mode {
|
||||
CompositeMode::Clear => compose = Compose::Clear,
|
||||
CompositeMode::Src => compose = Compose::Copy,
|
||||
CompositeMode::Dest => compose = Compose::Dest,
|
||||
CompositeMode::SrcOver => {}
|
||||
CompositeMode::DestOver => compose = Compose::DestOver,
|
||||
CompositeMode::SrcIn => compose = Compose::SrcIn,
|
||||
CompositeMode::DestIn => compose = Compose::DestIn,
|
||||
CompositeMode::SrcOut => compose = Compose::SrcOut,
|
||||
CompositeMode::DestOut => compose = Compose::DestOut,
|
||||
CompositeMode::SrcAtop => compose = Compose::SrcAtop,
|
||||
CompositeMode::DestAtop => compose = Compose::DestAtop,
|
||||
CompositeMode::Xor => compose = Compose::Xor,
|
||||
CompositeMode::Plus => compose = Compose::Plus,
|
||||
CompositeMode::Screen => mix = Mix::Screen,
|
||||
CompositeMode::Overlay => mix = Mix::Overlay,
|
||||
CompositeMode::Darken => mix = Mix::Darken,
|
||||
CompositeMode::Lighten => mix = Mix::Lighten,
|
||||
CompositeMode::ColorDodge => mix = Mix::ColorDodge,
|
||||
CompositeMode::ColorBurn => mix = Mix::ColorBurn,
|
||||
CompositeMode::HardLight => mix = Mix::HardLight,
|
||||
CompositeMode::SoftLight => mix = Mix::SoftLight,
|
||||
CompositeMode::Difference => mix = Mix::Difference,
|
||||
CompositeMode::Exclusion => mix = Mix::Exclusion,
|
||||
CompositeMode::Multiply => mix = Mix::Multiply,
|
||||
CompositeMode::HslHue => mix = Mix::Hue,
|
||||
CompositeMode::HslSaturation => mix = Mix::Saturation,
|
||||
CompositeMode::HslColor => mix = Mix::Color,
|
||||
CompositeMode::HslLuminosity => mix = Mix::Luminosity,
|
||||
fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
|
||||
self.0.quad_to(cx0, cy0, x, y)
|
||||
}
|
||||
BlendMode { mix, compose }
|
||||
}
|
||||
|
||||
fn convert_transform(xform: &moscato::Transform) -> peniko::kurbo::Affine {
|
||||
peniko::kurbo::Affine::new([
|
||||
xform.xx as f64,
|
||||
xform.yx as f64,
|
||||
xform.xy as f64,
|
||||
xform.yy as f64,
|
||||
xform.dx as f64,
|
||||
xform.dy as f64,
|
||||
])
|
||||
}
|
||||
fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
|
||||
self.0.cubic_to(cx0, cy0, cx1, cy1, x, y)
|
||||
}
|
||||
|
||||
fn convert_point(point: moscato::Point) -> peniko::kurbo::Point {
|
||||
peniko::kurbo::Point::new(point.x as f64, point.y as f64)
|
||||
}
|
||||
|
||||
fn convert_brush(brush: &moscato::Brush) -> peniko::Brush {
|
||||
use peniko::Gradient;
|
||||
match brush {
|
||||
moscato::Brush::Solid(color) => Brush::Solid(Color {
|
||||
r: color.r,
|
||||
g: color.g,
|
||||
b: color.b,
|
||||
a: color.a,
|
||||
}),
|
||||
moscato::Brush::LinearGradient(grad) => Brush::Gradient(
|
||||
Gradient::new_linear(convert_point(grad.start), convert_point(grad.end))
|
||||
.with_stops(convert_stops(&grad.stops).as_slice())
|
||||
.with_extend(convert_extend(grad.extend)),
|
||||
),
|
||||
|
||||
moscato::Brush::RadialGradient(grad) => Brush::Gradient(
|
||||
Gradient::new_two_point_radial(
|
||||
convert_point(grad.center0),
|
||||
grad.radius0,
|
||||
convert_point(grad.center1),
|
||||
grad.radius1,
|
||||
)
|
||||
.with_stops(convert_stops(&grad.stops).as_slice())
|
||||
.with_extend(convert_extend(grad.extend)),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_stops(stops: &[moscato::ColorStop]) -> peniko::ColorStops {
|
||||
stops
|
||||
.iter()
|
||||
.map(|stop| {
|
||||
(
|
||||
stop.offset,
|
||||
Color {
|
||||
r: stop.color.r,
|
||||
g: stop.color.g,
|
||||
b: stop.color.b,
|
||||
a: stop.color.a,
|
||||
},
|
||||
)
|
||||
.into()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn convert_extend(extend: moscato::ExtendMode) -> peniko::Extend {
|
||||
use peniko::Extend::*;
|
||||
match extend {
|
||||
moscato::ExtendMode::Pad => Pad,
|
||||
moscato::ExtendMode::Repeat => Repeat,
|
||||
moscato::ExtendMode::Reflect => Reflect,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_path_el(el: &moscato::Element) -> peniko::kurbo::PathEl {
|
||||
use peniko::kurbo::PathEl::*;
|
||||
match el {
|
||||
moscato::Element::MoveTo(p0) => MoveTo(convert_point(*p0)),
|
||||
moscato::Element::LineTo(p0) => LineTo(convert_point(*p0)),
|
||||
moscato::Element::QuadTo(p0, p1) => QuadTo(convert_point(*p0), convert_point(*p1)),
|
||||
moscato::Element::CurveTo(p0, p1, p2) => {
|
||||
CurveTo(convert_point(*p0), convert_point(*p1), convert_point(*p2))
|
||||
}
|
||||
moscato::Element::Close => ClosePath,
|
||||
fn close(&mut self) {
|
||||
self.0.close()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,9 @@ pub use peniko;
|
|||
/// 2D geometry, with a focus on curves.
|
||||
pub use peniko::kurbo;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use fello;
|
||||
|
||||
pub mod encoding;
|
||||
|
||||
pub mod glyph;
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
//
|
||||
// Also licensed under MIT license, at your choice.
|
||||
|
||||
use fello::NormalizedCoord;
|
||||
use peniko::kurbo::{Affine, Rect, Shape};
|
||||
use peniko::{BlendMode, BrushRef, Color, Fill, Font, Image, Stroke, StyleRef};
|
||||
|
||||
|
@ -262,7 +263,7 @@ impl<'a> DrawGlyphs<'a> {
|
|||
}
|
||||
|
||||
/// Sets the normalized design space coordinates for a variable font instance.
|
||||
pub fn normalized_coords(mut self, coords: &[i16]) -> Self {
|
||||
pub fn normalized_coords(mut self, coords: &[NormalizedCoord]) -> Self {
|
||||
self.encoding
|
||||
.normalized_coords
|
||||
.truncate(self.run.normalized_coords.start);
|
||||
|
|
Loading…
Reference in a new issue