diff --git a/Cargo.toml b/Cargo.toml index ad6043f..397ece2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/examples/assets/inconsolata/Inconsolata.ttf b/examples/assets/inconsolata/Inconsolata.ttf new file mode 100644 index 0000000..34848ca Binary files /dev/null and b/examples/assets/inconsolata/Inconsolata.ttf 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 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>, transform: Affine, @@ -55,92 +57,126 @@ impl SimpleText { style: impl Into>, 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>, + transform: Affine, + glyph_transform: Option, + style: impl Into>, + 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::>(); + 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> { + 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 @@ 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; 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 @@ 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, /// Normalized coordinate buffer for variable fonts. - pub normalized_coords: Vec, + pub normalized_coords: Vec, /// 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 @@ impl GlyphCache { &mut self, key: GlyphKey, style: &Style, - scaler: &mut GlyphProvider, + scaler: &mut Scaler, ) -> Option { + 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 @@ 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, - glyph_cx: GlyphContext, + glyph_cx: fello::scale::Context, ramp_cache: RampCache, image_cache: ImageCache, pending_images: Vec, @@ -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 { 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 @@ impl GlyphContext { pub fn new_provider<'a, V>( &'a mut self, font: &FontRef<'a>, - font_id: Option, + font_id: Option, ppem: f32, hint: bool, variations: V, ) -> GlyphProvider<'a> where V: IntoIterator, - V::Item: Into<(Tag, f32)>, + V::Item: Into>, { - 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 { - 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 { - 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 + 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 + 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() } } 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 @@ 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; 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 @@ 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);