blob: 9564b14afa48802daa9266720dacfd1e54f9fc25 [file] [log] [blame]
// Copyright 2022 The vello 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.
//! Support for glyph rendering.
pub use moscato::pinot;
use crate::scene::{SceneBuilder, SceneFragment};
use peniko::kurbo::{Affine, Rect};
use peniko::{Brush, Color, Fill, Mix};
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<u64>,
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, 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);
builder.fill(
Fill::NonZero,
Affine::IDENTITY,
brush.unwrap_or(&Brush::Solid(Color::rgb8(255, 255, 255))),
None,
&convert_path(path.elements()),
);
builder.finish();
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<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
}
}
}
builder.finish();
Some(fragment)
}
}
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));
}
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));
}
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,
}
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 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,
}
}