mirror of
https://github.com/italicsjenga/vello.git
synced 2024-10-18 07:21:30 +11:00
516fd6c981
This applies updates for the gradient API in peniko and pins the git dependency so prevent further breakage. Also removes Cargo.lock.
335 lines
12 KiB
Rust
335 lines
12 KiB
Rust
// 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,
|
|
}
|
|
}
|