mirror of
https://github.com/italicsjenga/vello.git
synced 2025-01-11 04:51:32 +11:00
951f3aa508
This commit puts in basic integration with ttf-parser and starts populating the various piet text objects. The font is currently hard-coded.
550 lines
17 KiB
Rust
550 lines
17 KiB
Rust
use std::{borrow::Cow, ops::RangeBounds};
|
||
|
||
use piet::{
|
||
kurbo::{Affine, Insets, PathEl, Point, Rect, Shape, Size},
|
||
HitTestPosition, TextAttribute, TextStorage,
|
||
};
|
||
use piet::{
|
||
Color, Error, FixedGradient, FontFamily, HitTestPoint, ImageFormat, InterpolationMode,
|
||
IntoBrush, LineMetric, RenderContext, StrokeStyle, Text, TextLayout, TextLayoutBuilder,
|
||
};
|
||
|
||
use piet_gpu_types::encoder::{Encode, Encoder};
|
||
use piet_gpu_types::scene::{
|
||
Clip, CubicSeg, Element, FillColor, LineSeg, QuadSeg, SetFillMode, SetLineWidth, Transform,
|
||
};
|
||
|
||
use crate::text::Font;
|
||
pub use crate::text::{PathEncoder, PietGpuText, PietGpuTextLayout, PietGpuTextLayoutBuilder};
|
||
|
||
pub struct PietGpuImage;
|
||
|
||
pub struct PietGpuRenderContext {
|
||
encoder: Encoder,
|
||
elements: Vec<Element>,
|
||
// Will probably need direct accesss to hal Device to create images etc.
|
||
inner_text: PietGpuText,
|
||
stroke_width: f32,
|
||
fill_mode: FillMode,
|
||
// We're tallying these cpu-side for expedience, but will probably
|
||
// move this to some kind of readback from element processing.
|
||
/// The count of elements that make it through to coarse rasterization.
|
||
path_count: usize,
|
||
/// The count of path segment elements.
|
||
pathseg_count: usize,
|
||
/// The count of transform elements.
|
||
trans_count: usize,
|
||
|
||
cur_transform: Affine,
|
||
state_stack: Vec<State>,
|
||
clip_stack: Vec<ClipElement>,
|
||
}
|
||
|
||
#[derive(Clone)]
|
||
pub enum PietGpuBrush {
|
||
Solid(u32),
|
||
Gradient,
|
||
}
|
||
|
||
#[derive(Default)]
|
||
struct State {
|
||
/// The transform relative to the parent state.
|
||
rel_transform: Affine,
|
||
/// The transform at the parent state.
|
||
///
|
||
/// This invariant should hold: transform * rel_transform = cur_transform
|
||
transform: Affine,
|
||
n_clip: usize,
|
||
}
|
||
|
||
struct ClipElement {
|
||
/// Index of BeginClip element in element vec, for bbox fixup.
|
||
begin_ix: usize,
|
||
bbox: Option<Rect>,
|
||
}
|
||
|
||
#[derive(Clone, Copy, PartialEq)]
|
||
pub(crate) enum FillMode {
|
||
// Fill path according to the non-zero winding rule.
|
||
Nonzero = 0,
|
||
// Fill stroked path.
|
||
Stroke = 1,
|
||
}
|
||
|
||
const TOLERANCE: f64 = 0.25;
|
||
|
||
impl PietGpuRenderContext {
|
||
pub fn new() -> PietGpuRenderContext {
|
||
let encoder = Encoder::new();
|
||
let elements = Vec::new();
|
||
let font = Font::new();
|
||
let inner_text = PietGpuText::new(font);
|
||
let stroke_width = 0.0;
|
||
PietGpuRenderContext {
|
||
encoder,
|
||
elements,
|
||
inner_text,
|
||
stroke_width,
|
||
fill_mode: FillMode::Nonzero,
|
||
path_count: 0,
|
||
pathseg_count: 0,
|
||
trans_count: 0,
|
||
cur_transform: Affine::default(),
|
||
state_stack: Vec::new(),
|
||
clip_stack: Vec::new(),
|
||
}
|
||
}
|
||
|
||
pub fn get_scene_buf(&mut self) -> &[u8] {
|
||
self.elements.encode(&mut self.encoder);
|
||
self.encoder.buf()
|
||
}
|
||
|
||
pub fn path_count(&self) -> usize {
|
||
self.path_count
|
||
}
|
||
|
||
pub fn pathseg_count(&self) -> usize {
|
||
self.pathseg_count
|
||
}
|
||
|
||
pub fn trans_count(&self) -> usize {
|
||
self.trans_count
|
||
}
|
||
|
||
pub(crate) fn set_fill_mode(&mut self, fill_mode: FillMode) {
|
||
if self.fill_mode != fill_mode {
|
||
self.elements.push(Element::SetFillMode(SetFillMode {
|
||
fill_mode: fill_mode as u32,
|
||
}));
|
||
self.fill_mode = fill_mode;
|
||
}
|
||
}
|
||
}
|
||
|
||
impl RenderContext for PietGpuRenderContext {
|
||
type Brush = PietGpuBrush;
|
||
type Image = PietGpuImage;
|
||
type Text = PietGpuText;
|
||
type TextLayout = PietGpuTextLayout;
|
||
|
||
fn status(&mut self) -> Result<(), Error> {
|
||
Ok(())
|
||
}
|
||
|
||
fn solid_brush(&mut self, color: Color) -> Self::Brush {
|
||
// kernel4 expects colors encoded in alpha-premultiplied sRGB:
|
||
//
|
||
// [α,sRGB(α⋅R),sRGB(α⋅G),sRGB(α⋅B)]
|
||
//
|
||
// See also http://ssp.impulsetrain.com/gamma-premult.html.
|
||
let (r, g, b, a) = color.as_rgba();
|
||
let premul = Color::rgba(
|
||
to_srgb(from_srgb(r) * a),
|
||
to_srgb(from_srgb(g) * a),
|
||
to_srgb(from_srgb(b) * a),
|
||
a,
|
||
);
|
||
PietGpuBrush::Solid(premul.as_rgba_u32())
|
||
}
|
||
|
||
fn gradient(&mut self, _gradient: impl Into<FixedGradient>) -> Result<Self::Brush, Error> {
|
||
Ok(Self::Brush::Gradient)
|
||
}
|
||
|
||
fn clear(&mut self, _color: Color) {}
|
||
|
||
fn stroke(&mut self, shape: impl Shape, brush: &impl IntoBrush<Self>, width: f64) {
|
||
let width_f32 = width as f32;
|
||
if self.stroke_width != width_f32 {
|
||
self.elements
|
||
.push(Element::SetLineWidth(SetLineWidth { width: width_f32 }));
|
||
self.stroke_width = width_f32;
|
||
}
|
||
self.set_fill_mode(FillMode::Stroke);
|
||
let brush = brush.make_brush(self, || shape.bounding_box()).into_owned();
|
||
match brush {
|
||
PietGpuBrush::Solid(rgba_color) => {
|
||
// Note: the bbox contribution of stroke becomes more complicated with miter joins.
|
||
self.accumulate_bbox(|| shape.bounding_box() + Insets::uniform(width * 0.5));
|
||
let path = shape.path_elements(TOLERANCE);
|
||
self.encode_path(path, false);
|
||
let stroke = FillColor { rgba_color };
|
||
self.elements.push(Element::FillColor(stroke));
|
||
self.path_count += 1;
|
||
}
|
||
_ => (),
|
||
}
|
||
}
|
||
|
||
fn stroke_styled(
|
||
&mut self,
|
||
_shape: impl Shape,
|
||
_brush: &impl IntoBrush<Self>,
|
||
_width: f64,
|
||
_style: &StrokeStyle,
|
||
) {
|
||
}
|
||
|
||
fn fill(&mut self, shape: impl Shape, brush: &impl IntoBrush<Self>) {
|
||
let brush = brush.make_brush(self, || shape.bounding_box()).into_owned();
|
||
if let PietGpuBrush::Solid(rgba_color) = brush {
|
||
// Note: we might get a good speedup from using an approximate bounding box.
|
||
// Perhaps that should be added to kurbo.
|
||
self.accumulate_bbox(|| shape.bounding_box());
|
||
let path = shape.path_elements(TOLERANCE);
|
||
self.set_fill_mode(FillMode::Nonzero);
|
||
self.encode_path(path, true);
|
||
let fill = FillColor { rgba_color };
|
||
self.elements.push(Element::FillColor(fill));
|
||
self.path_count += 1;
|
||
}
|
||
}
|
||
|
||
fn fill_even_odd(&mut self, _shape: impl Shape, _brush: &impl IntoBrush<Self>) {}
|
||
|
||
fn clip(&mut self, shape: impl Shape) {
|
||
self.set_fill_mode(FillMode::Nonzero);
|
||
let path = shape.path_elements(TOLERANCE);
|
||
self.encode_path(path, true);
|
||
let begin_ix = self.elements.len();
|
||
self.elements.push(Element::BeginClip(Clip {
|
||
bbox: Default::default(),
|
||
}));
|
||
self.clip_stack.push(ClipElement {
|
||
bbox: None,
|
||
begin_ix,
|
||
});
|
||
self.path_count += 1;
|
||
if let Some(tos) = self.state_stack.last_mut() {
|
||
tos.n_clip += 1;
|
||
}
|
||
}
|
||
|
||
fn text(&mut self) -> &mut Self::Text {
|
||
&mut self.inner_text
|
||
}
|
||
|
||
fn draw_text(&mut self, layout: &Self::TextLayout, pos: impl Into<Point>) {
|
||
layout.draw_text(self, pos.into());
|
||
}
|
||
|
||
fn save(&mut self) -> Result<(), Error> {
|
||
self.state_stack.push(State {
|
||
rel_transform: Affine::default(),
|
||
transform: self.cur_transform,
|
||
n_clip: 0,
|
||
});
|
||
Ok(())
|
||
}
|
||
|
||
fn restore(&mut self) -> Result<(), Error> {
|
||
if let Some(state) = self.state_stack.pop() {
|
||
if state.rel_transform != Affine::default() {
|
||
let a_inv = state.rel_transform.inverse();
|
||
self.encode_transform(to_scene_transform(a_inv));
|
||
}
|
||
self.cur_transform = state.transform;
|
||
for _ in 0..state.n_clip {
|
||
self.pop_clip();
|
||
}
|
||
Ok(())
|
||
} else {
|
||
Err(Error::StackUnbalance)
|
||
}
|
||
}
|
||
|
||
fn finish(&mut self) -> Result<(), Error> {
|
||
for _ in 0..self.clip_stack.len() {
|
||
self.pop_clip();
|
||
}
|
||
Ok(())
|
||
}
|
||
|
||
fn transform(&mut self, transform: Affine) {
|
||
self.encode_transform(to_scene_transform(transform));
|
||
if let Some(tos) = self.state_stack.last_mut() {
|
||
tos.rel_transform *= transform;
|
||
}
|
||
self.cur_transform *= transform;
|
||
}
|
||
|
||
fn make_image(
|
||
&mut self,
|
||
_width: usize,
|
||
_height: usize,
|
||
_buf: &[u8],
|
||
_format: ImageFormat,
|
||
) -> Result<Self::Image, Error> {
|
||
Ok(PietGpuImage)
|
||
}
|
||
|
||
fn draw_image(
|
||
&mut self,
|
||
_image: &Self::Image,
|
||
_rect: impl Into<Rect>,
|
||
_interp: InterpolationMode,
|
||
) {
|
||
}
|
||
|
||
fn draw_image_area(
|
||
&mut self,
|
||
_image: &Self::Image,
|
||
_src_rect: impl Into<Rect>,
|
||
_dst_rect: impl Into<Rect>,
|
||
_interp: InterpolationMode,
|
||
) {
|
||
}
|
||
|
||
fn blurred_rect(&mut self, _rect: Rect, _blur_radius: f64, _brush: &impl IntoBrush<Self>) {}
|
||
|
||
fn current_transform(&self) -> Affine {
|
||
self.cur_transform
|
||
}
|
||
|
||
fn with_save(&mut self, f: impl FnOnce(&mut Self) -> Result<(), Error>) -> Result<(), Error> {
|
||
self.save()?;
|
||
// Always try to restore the stack, even if `f` errored.
|
||
f(self).and(self.restore())
|
||
}
|
||
}
|
||
|
||
impl PietGpuRenderContext {
|
||
fn encode_line_seg(&mut self, seg: LineSeg) {
|
||
self.elements.push(Element::Line(seg));
|
||
self.pathseg_count += 1;
|
||
}
|
||
|
||
fn encode_quad_seg(&mut self, seg: QuadSeg) {
|
||
self.elements.push(Element::Quad(seg));
|
||
self.pathseg_count += 1;
|
||
}
|
||
|
||
fn encode_cubic_seg(&mut self, seg: CubicSeg) {
|
||
self.elements.push(Element::Cubic(seg));
|
||
self.pathseg_count += 1;
|
||
}
|
||
|
||
fn encode_path(&mut self, path: impl Iterator<Item = PathEl>, is_fill: bool) {
|
||
if is_fill {
|
||
self.encode_path_inner(
|
||
path.flat_map(|el| {
|
||
match el {
|
||
PathEl::MoveTo(..) => Some(PathEl::ClosePath),
|
||
_ => None,
|
||
}
|
||
.into_iter()
|
||
.chain(Some(el))
|
||
})
|
||
.chain(Some(PathEl::ClosePath)),
|
||
)
|
||
} else {
|
||
self.encode_path_inner(path)
|
||
}
|
||
}
|
||
|
||
fn encode_path_inner(&mut self, path: impl Iterator<Item = PathEl>) {
|
||
let flatten = false;
|
||
if flatten {
|
||
let mut start_pt = None;
|
||
let mut last_pt = None;
|
||
piet::kurbo::flatten(path, TOLERANCE, |el| {
|
||
match el {
|
||
PathEl::MoveTo(p) => {
|
||
let scene_pt = to_f32_2(p);
|
||
start_pt = Some(scene_pt);
|
||
last_pt = Some(scene_pt);
|
||
}
|
||
PathEl::LineTo(p) => {
|
||
let scene_pt = to_f32_2(p);
|
||
let seg = LineSeg {
|
||
p0: last_pt.unwrap(),
|
||
p1: scene_pt,
|
||
};
|
||
self.encode_line_seg(seg);
|
||
last_pt = Some(scene_pt);
|
||
}
|
||
PathEl::ClosePath => {
|
||
if let (Some(start), Some(last)) = (start_pt.take(), last_pt.take()) {
|
||
if last != start {
|
||
let seg = LineSeg {
|
||
p0: last,
|
||
p1: start,
|
||
};
|
||
self.encode_line_seg(seg);
|
||
}
|
||
}
|
||
}
|
||
_ => (),
|
||
}
|
||
//println!("{:?}", el);
|
||
});
|
||
} else {
|
||
let mut start_pt = None;
|
||
let mut last_pt = None;
|
||
for el in path {
|
||
match el {
|
||
PathEl::MoveTo(p) => {
|
||
let scene_pt = to_f32_2(p);
|
||
start_pt = Some(scene_pt);
|
||
last_pt = Some(scene_pt);
|
||
}
|
||
PathEl::LineTo(p) => {
|
||
let scene_pt = to_f32_2(p);
|
||
let seg = LineSeg {
|
||
p0: last_pt.unwrap(),
|
||
p1: scene_pt,
|
||
};
|
||
self.encode_line_seg(seg);
|
||
last_pt = Some(scene_pt);
|
||
}
|
||
PathEl::QuadTo(p1, p2) => {
|
||
let scene_p1 = to_f32_2(p1);
|
||
let scene_p2 = to_f32_2(p2);
|
||
let seg = QuadSeg {
|
||
p0: last_pt.unwrap(),
|
||
p1: scene_p1,
|
||
p2: scene_p2,
|
||
};
|
||
self.encode_quad_seg(seg);
|
||
last_pt = Some(scene_p2);
|
||
}
|
||
PathEl::CurveTo(p1, p2, p3) => {
|
||
let scene_p1 = to_f32_2(p1);
|
||
let scene_p2 = to_f32_2(p2);
|
||
let scene_p3 = to_f32_2(p3);
|
||
let seg = CubicSeg {
|
||
p0: last_pt.unwrap(),
|
||
p1: scene_p1,
|
||
p2: scene_p2,
|
||
p3: scene_p3,
|
||
};
|
||
self.encode_cubic_seg(seg);
|
||
last_pt = Some(scene_p3);
|
||
}
|
||
PathEl::ClosePath => {
|
||
if let (Some(start), Some(last)) = (start_pt.take(), last_pt.take()) {
|
||
if last != start {
|
||
let seg = LineSeg {
|
||
p0: last,
|
||
p1: start,
|
||
};
|
||
self.encode_line_seg(seg);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
//println!("{:?}", el);
|
||
}
|
||
}
|
||
}
|
||
|
||
fn pop_clip(&mut self) {
|
||
let tos = self.clip_stack.pop().unwrap();
|
||
let bbox = tos.bbox.unwrap_or_default();
|
||
let bbox_f32_4 = rect_to_f32_4(bbox);
|
||
self.elements
|
||
.push(Element::EndClip(Clip { bbox: bbox_f32_4 }));
|
||
self.path_count += 1;
|
||
if let Element::BeginClip(begin_clip) = &mut self.elements[tos.begin_ix] {
|
||
begin_clip.bbox = bbox_f32_4;
|
||
} else {
|
||
unreachable!("expected BeginClip, not found");
|
||
}
|
||
if let Some(bbox) = tos.bbox {
|
||
self.union_bbox(bbox);
|
||
}
|
||
}
|
||
|
||
/// Accumulate a bbox.
|
||
///
|
||
/// The bbox is given lazily as a closure, relative to the current transform.
|
||
/// It's lazy because we don't need to compute it unless we're inside a clip.
|
||
fn accumulate_bbox(&mut self, f: impl FnOnce() -> Rect) {
|
||
if !self.clip_stack.is_empty() {
|
||
let bbox = f();
|
||
let bbox = self.cur_transform.transform_rect_bbox(bbox);
|
||
self.union_bbox(bbox);
|
||
}
|
||
}
|
||
|
||
/// Accumulate an absolute bbox.
|
||
///
|
||
/// The bbox is given already transformed into surface coordinates.
|
||
fn union_bbox(&mut self, bbox: Rect) {
|
||
if let Some(tos) = self.clip_stack.last_mut() {
|
||
tos.bbox = if let Some(old_bbox) = tos.bbox {
|
||
Some(old_bbox.union(bbox))
|
||
} else {
|
||
Some(bbox)
|
||
};
|
||
}
|
||
}
|
||
|
||
pub(crate) fn append_path_encoder(&mut self, path: &PathEncoder) {
|
||
let elements = path.elements();
|
||
self.elements.extend(elements.iter().cloned());
|
||
self.pathseg_count += elements.len();
|
||
}
|
||
|
||
pub(crate) fn fill_glyph(&mut self, rgba_color: u32) {
|
||
let fill = FillColor { rgba_color };
|
||
self.elements.push(Element::FillColor(fill));
|
||
self.path_count += 1;
|
||
}
|
||
|
||
pub(crate) fn encode_transform(&mut self, transform: Transform) {
|
||
self.elements.push(Element::Transform(transform));
|
||
self.trans_count += 1;
|
||
}
|
||
}
|
||
|
||
impl IntoBrush<PietGpuRenderContext> for PietGpuBrush {
|
||
fn make_brush<'b>(
|
||
&'b self,
|
||
_piet: &mut PietGpuRenderContext,
|
||
_bbox: impl FnOnce() -> Rect,
|
||
) -> std::borrow::Cow<'b, PietGpuBrush> {
|
||
Cow::Borrowed(self)
|
||
}
|
||
}
|
||
|
||
pub(crate) fn to_f32_2(point: Point) -> [f32; 2] {
|
||
[point.x as f32, point.y as f32]
|
||
}
|
||
|
||
fn rect_to_f32_4(rect: Rect) -> [f32; 4] {
|
||
[
|
||
rect.x0 as f32,
|
||
rect.y0 as f32,
|
||
rect.x1 as f32,
|
||
rect.y1 as f32,
|
||
]
|
||
}
|
||
|
||
fn to_scene_transform(transform: Affine) -> Transform {
|
||
let c = transform.as_coeffs();
|
||
Transform {
|
||
mat: [c[0] as f32, c[1] as f32, c[2] as f32, c[3] as f32],
|
||
translate: [c[4] as f32, c[5] as f32],
|
||
}
|
||
}
|
||
|
||
fn to_srgb(f: f64) -> f64 {
|
||
if f <= 0.0031308 {
|
||
f * 12.92
|
||
} else {
|
||
let a = 0.055;
|
||
(1. + a) * f64::powf(f, f64::recip(2.4)) - a
|
||
}
|
||
}
|
||
|
||
fn from_srgb(f: f64) -> f64 {
|
||
if f <= 0.04045 {
|
||
f / 12.92
|
||
} else {
|
||
let a = 0.055;
|
||
f64::powf((f + a) * f64::recip(1. + a), 2.4)
|
||
}
|
||
}
|