Merge pull request #297 from linebender/vello-fello
Replace font backend
diff --git a/Cargo.toml b/Cargo.toml
index ad6043f..397ece2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -44,7 +44,7 @@
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"
diff --git a/examples/assets/inconsolata/Inconsolata.ttf b/examples/assets/inconsolata/Inconsolata.ttf
new file mode 100644
index 0000000..34848ca
--- /dev/null
+++ b/examples/assets/inconsolata/Inconsolata.ttf
Binary files differ
diff --git a/examples/assets/inconsolata/LICENSE.txt b/examples/assets/inconsolata/LICENSE.txt
new file mode 100644
index 0000000..77b1731
--- /dev/null
+++ b/examples/assets/inconsolata/LICENSE.txt
@@ -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.
diff --git a/examples/scenes/src/simple_text.rs b/examples/scenes/src/simple_text.rs
index 1d3dc47..c6371af 100644
--- a/examples/scenes/src/simple_text.rs
+++ b/examples/scenes/src/simple_text.rs
@@ -18,11 +18,9 @@
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 @@
// 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 @@
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(),
+ }
+}
diff --git a/examples/scenes/src/test_scenes.rs b/examples/scenes/src/test_scenes.rs
index ff3ff60..23ea373 100644
--- a/examples/scenes/src/test_scenes.rs
+++ b/examples/scenes/src/test_scenes.rs
@@ -135,6 +135,7 @@
);
params.text.add_run(
sb,
+ None,
text_size,
Color::WHITE,
Affine::translate((110.0, 700.0)),
@@ -143,6 +144,21 @@
&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;
diff --git a/src/encoding/encoding.rs b/src/encoding/encoding.rs
index d212a89..03ca730 100644
--- a/src/encoding/encoding.rs
+++ b/src/encoding/encoding.rs
@@ -21,6 +21,7 @@
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 @@
/// 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.
diff --git a/src/encoding/glyph_cache.rs b/src/encoding/glyph_cache.rs
index cbfea17..f919afb 100644
--- a/src/encoding/glyph_cache.rs
+++ b/src/encoding/glyph_cache.rs
@@ -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 @@
&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(),
diff --git a/src/encoding/resolve.rs b/src/encoding/resolve.rs
index 58b942f..0077b5c 100644
--- a/src/encoding/resolve.rs
+++ b/src/encoding/resolve.rs
@@ -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 @@
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 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 @@
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 @@
}
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 {
diff --git a/src/glyph.rs b/src/glyph.rs
index 7f825f2..b398e6f 100644
--- a/src/glyph.rs
+++ b/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 @@
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 @@
/// 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 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 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_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()
}
}
diff --git a/src/lib.rs b/src/lib.rs
index 91a4d44..d61e176 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -24,6 +24,9 @@
/// 2D geometry, with a focus on curves.
pub use peniko::kurbo;
+#[doc(hidden)]
+pub use fello;
+
pub mod encoding;
pub mod glyph;
diff --git a/src/scene.rs b/src/scene.rs
index 1d2b345..ce777b8 100644
--- a/src/scene.rs
+++ b/src/scene.rs
@@ -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 @@
}
/// 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);