// Copyright 2022 The piet-gpu authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Also licensed under MIT license, at your choice. pub use moscato::pinot; use crate::brush::{Brush, Color}; use crate::geometry::Affine; use crate::path::Element; use crate::scene::{build_fragment, Fill, Fragment}; use moscato::{Context, Scaler}; use pinot::{types::Tag, FontRef}; use smallvec::SmallVec; /// General context for creating scene fragments for glyph outlines. pub struct GlyphContext { ctx: Context, } impl GlyphContext { /// Creates a new context. pub fn new() -> Self { Self { ctx: Context::new(), } } /// Creates a new provider for generating scene fragments for glyphs from /// the specified font and settings. pub fn new_provider<'a, V>( &'a mut self, font: &FontRef<'a>, font_id: Option, ppem: f32, hint: bool, variations: V, ) -> GlyphProvider<'a> where V: IntoIterator, V::Item: Into<(Tag, 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() }; GlyphProvider { scaler } } } /// Generator for scene fragments containing glyph outlines for a specific /// font. pub struct GlyphProvider<'a> { scaler: Scaler<'a>, } impl<'a> GlyphProvider<'a> { /// Returns a scene fragment containing the commands to render the /// specified glyph. pub fn get(&mut self, gid: u16) -> Option { let glyph = self.scaler.glyph(gid)?; let path = glyph.path(0)?; let mut fragment = Fragment::default(); let mut builder = build_fragment(&mut fragment); builder.fill( Fill::NonZero, &Brush::Solid(Color::rgb8(255, 255, 255)), None, convert_path(path.elements()), ); Some(fragment) } /// 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 crate::geometry::*; use moscato::Command; let glyph = self.scaler.color_glyph(palette_index, gid)?; let mut fragment = Fragment::default(); let mut builder = build_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( Default::default(), convert_transformed_path(path.elements(), xform), ); } else { builder.push_layer(Default::default(), convert_path(path.elements())); } } Command::PopClip => builder.pop_layer(), Command::PushLayer(bounds) => { let mut rect = Rect { min: Point::new(bounds.min.x, bounds.min.y), max: Point::new(bounds.max.x, bounds.max.y), }; if let Some(xform) = xform_stack.last() { rect = rect.transform(xform); } builder.push_layer(Default::default(), rect.elements()); } Command::PopLayer => builder.pop_layer(), Command::BeginBlend(bounds, mode) => { let mut rect = Rect { min: Point::new(bounds.min.x, bounds.min.y), max: Point::new(bounds.max.x, bounds.max.y), }; if let Some(xform) = xform_stack.last() { rect = rect.transform(xform); } builder.push_layer(convert_blend(*mode), rect.elements()) } 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, &brush, brush_xform.map(|x| x * *xform), convert_transformed_path(path.elements(), xform), ); } else { builder.fill( Fill::NonZero, &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) } } fn convert_path( path: impl Iterator + Clone, ) -> impl Iterator + Clone { use crate::geometry::Point; path.map(|el| match el { moscato::Element::MoveTo(p0) => Element::MoveTo(Point::new(p0.x, p0.y)), moscato::Element::LineTo(p0) => Element::LineTo(Point::new(p0.x, p0.y)), moscato::Element::QuadTo(p0, p1) => { Element::QuadTo(Point::new(p0.x, p0.y), Point::new(p1.x, p1.y)) } moscato::Element::CurveTo(p0, p1, p2) => Element::CurveTo( Point::new(p0.x, p0.y), Point::new(p1.x, p1.y), Point::new(p2.x, p2.y), ), moscato::Element::Close => Element::Close, }) } fn convert_transformed_path( path: impl Iterator + Clone, xform: &Affine, ) -> impl Iterator + Clone { use crate::geometry::Point; let xform = *xform; path.map(move |el| match el { moscato::Element::MoveTo(p0) => Element::MoveTo(Point::new(p0.x, p0.y).transform(&xform)), moscato::Element::LineTo(p0) => Element::LineTo(Point::new(p0.x, p0.y).transform(&xform)), moscato::Element::QuadTo(p0, p1) => Element::QuadTo( Point::new(p0.x, p0.y).transform(&xform), Point::new(p1.x, p1.y).transform(&xform), ), moscato::Element::CurveTo(p0, p1, p2) => Element::CurveTo( Point::new(p0.x, p0.y).transform(&xform), Point::new(p1.x, p1.y).transform(&xform), Point::new(p2.x, p2.y).transform(&xform), ), moscato::Element::Close => Element::Close, }) } fn convert_blend(mode: moscato::CompositeMode) -> crate::scene::Blend { use crate::scene::{Blend, Compose, Mix}; use moscato::CompositeMode; 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, } Blend { mix, compose } } fn convert_transform(xform: &moscato::Transform) -> crate::geometry::Affine { crate::geometry::Affine { xx: xform.xx, yx: xform.yx, xy: xform.xy, yy: xform.yy, dx: xform.dx, dy: xform.dy, } } fn convert_brush(brush: &moscato::Brush) -> crate::brush::Brush { use crate::brush::*; use crate::geometry::*; 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::LinearGradient(LinearGradient { start: Point::new(grad.start.x, grad.start.y), end: Point::new(grad.end.x, grad.end.y), stops: convert_stops(&grad.stops), extend: convert_extend(grad.extend), }), moscato::Brush::RadialGradient(grad) => Brush::RadialGradient(RadialGradient { center0: Point::new(grad.center0.x, grad.center0.y), center1: Point::new(grad.center1.x, grad.center1.y), radius0: grad.radius0, radius1: grad.radius1, stops: convert_stops(&grad.stops), extend: convert_extend(grad.extend), }), } } fn convert_stops(stops: &[moscato::ColorStop]) -> crate::brush::StopVec { use crate::brush::Stop; stops .iter() .map(|stop| Stop { offset: stop.offset, color: Color { r: stop.color.r, g: stop.color.g, b: stop.color.b, a: stop.color.a, }, }) .collect() } fn convert_extend(extend: moscato::ExtendMode) -> crate::brush::Extend { use crate::brush::Extend::*; match extend { moscato::ExtendMode::Pad => Pad, moscato::ExtendMode::Repeat => Repeat, moscato::ExtendMode::Reflect => Reflect, } }