Make transforms stateless

Removes the transform state mutator from SceneBuilder and adds transform parameters to push_layer, fill and stroke methods.
This commit is contained in:
Chad Brokaw 2022-08-16 14:52:04 -04:00
parent c0fcdbad58
commit d07fda8ef8
6 changed files with 127 additions and 171 deletions

View file

@ -273,8 +273,8 @@ pub unsafe extern "C" fn pgpu_scene_builder_transform(
builder: *mut PgpuSceneBuilder<'static>, builder: *mut PgpuSceneBuilder<'static>,
transform: *const PgpuTransform, transform: *const PgpuTransform,
) { ) {
if !transform.is_null() { if let Some(transform) = transform.as_ref() {
(*builder).0.transform((*transform).into()) (*builder).transform = (*transform).into();
} }
} }
@ -308,9 +308,13 @@ pub unsafe extern "C" fn pgpu_scene_builder_fill_path(
} else { } else {
Some((*brush_transform).into()) Some((*brush_transform).into())
}; };
(*builder) (*builder).builder.fill(
.0 fill,
.fill(fill, &brush, brush_transform, (*path).clone()); (*builder).transform,
&brush,
brush_transform,
(*path).clone(),
);
} }
/// Appends a scene fragment to the underlying scene or fragment. The /// Appends a scene fragment to the underlying scene or fragment. The
@ -329,7 +333,7 @@ pub unsafe extern "C" fn pgpu_scene_builder_append_fragment(
} else { } else {
Some((*transform).into()) Some((*transform).into())
}; };
(*builder).0.append(&(*fragment).0, transform); (*builder).builder.append(&(*fragment).0, transform);
} }
/// Finalizes the scene builder, making the underlying scene ready for /// Finalizes the scene builder, making the underlying scene ready for

View file

@ -103,19 +103,18 @@ impl PgpuRenderer {
} }
/// Encoded streams and resources describing a vector graphics scene. /// Encoded streams and resources describing a vector graphics scene.
pub struct PgpuScene { pub struct PgpuScene(pub Scene);
scene: Scene,
}
impl PgpuScene { impl PgpuScene {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self(Scene::default())
scene: Scene::default(),
}
} }
pub fn builder(&mut self) -> PgpuSceneBuilder { pub fn builder(&mut self) -> PgpuSceneBuilder {
PgpuSceneBuilder(piet_scene::SceneBuilder::for_scene(&mut self.scene)) PgpuSceneBuilder {
builder: piet_scene::SceneBuilder::for_scene(&mut self.0),
transform: Affine::IDENTITY,
}
} }
} }
@ -128,20 +127,26 @@ impl PgpuSceneFragment {
} }
pub fn builder(&mut self) -> PgpuSceneBuilder { pub fn builder(&mut self) -> PgpuSceneBuilder {
PgpuSceneBuilder(piet_scene::SceneBuilder::for_fragment(&mut self.0)) PgpuSceneBuilder {
builder: piet_scene::SceneBuilder::for_fragment(&mut self.0),
transform: Affine::IDENTITY,
}
} }
} }
/// Builder for constructing an encoded scene. /// Builder for constructing an encoded scene.
pub struct PgpuSceneBuilder<'a>(pub piet_scene::SceneBuilder<'a>); pub struct PgpuSceneBuilder<'a> {
pub builder: piet_scene::SceneBuilder<'a>,
pub transform: Affine,
}
impl<'a> PgpuSceneBuilder<'a> { impl<'a> PgpuSceneBuilder<'a> {
pub fn add_glyph(&mut self, glyph: &PgpuGlyph, transform: &piet_scene::Affine) { pub fn add_glyph(&mut self, glyph: &PgpuGlyph, transform: &piet_scene::Affine) {
self.0.append(&glyph.fragment, Some(*transform)); self.builder.append(&glyph.fragment, Some(*transform));
} }
pub fn finish(self) { pub fn finish(self) {
self.0.finish(); self.builder.finish();
} }
} }

View file

@ -126,7 +126,7 @@ fn main() -> Result<(), Error> {
} else { } else {
let mut builder = SceneBuilder::for_scene(&mut scene); let mut builder = SceneBuilder::for_scene(&mut scene);
const N_SAMPLES: usize = 4; const N_SAMPLES: usize = 5;
match sample_index % N_SAMPLES { match sample_index % N_SAMPLES {
0 => samples::render_anim_frame( 0 => samples::render_anim_frame(
&mut builder, &mut builder,
@ -135,6 +135,7 @@ fn main() -> Result<(), Error> {
), ),
1 => samples::render_blend_grid(&mut builder), 1 => samples::render_blend_grid(&mut builder),
2 => samples::render_tiger(&mut builder, false), 2 => samples::render_tiger(&mut builder, false),
3 => samples::render_brush_transform(&mut builder, current_frame),
_ => samples::render_scene(&mut builder), _ => samples::render_scene(&mut builder),
} }
render_info(&mut simple_text, &mut builder, &info_string); render_info(&mut simple_text, &mut builder, &info_string);

View file

@ -16,6 +16,7 @@ pub fn render_svg(sb: &mut SceneBuilder, svg: &PicoSvg, print_stats: bool) {
Item::Fill(fill) => { Item::Fill(fill) => {
sb.fill( sb.fill(
Fill::NonZero, Fill::NonZero,
Affine::IDENTITY,
&fill.color.into(), &fill.color.into(),
None, None,
convert_bez_path(&fill.path), convert_bez_path(&fill.path),
@ -24,6 +25,7 @@ pub fn render_svg(sb: &mut SceneBuilder, svg: &PicoSvg, print_stats: bool) {
Item::Stroke(stroke) => { Item::Stroke(stroke) => {
sb.stroke( sb.stroke(
&simple_stroke(stroke.width as f32), &simple_stroke(stroke.width as f32),
Affine::IDENTITY,
&stroke.color.into(), &stroke.color.into(),
None, None,
convert_bez_path(&stroke.path), convert_bez_path(&stroke.path),
@ -76,6 +78,7 @@ fn render_cardioid(sb: &mut SceneBuilder) {
} }
sb.stroke( sb.stroke(
&simple_stroke(2.0), &simple_stroke(2.0),
Affine::IDENTITY,
&Brush::Solid(Color::rgb8(0, 0, 0)), &Brush::Solid(Color::rgb8(0, 0, 0)),
None, None,
&path, &path,
@ -102,7 +105,7 @@ fn render_clip_test(sb: &mut SceneBuilder) {
PathElement::LineTo((X0, Y1).into()), PathElement::LineTo((X0, Y1).into()),
PathElement::Close, PathElement::Close,
]; ];
sb.push_layer(Mix::Clip.into(), path); sb.push_layer(Mix::Clip.into(), Affine::IDENTITY, path);
} }
let rect = Rect { let rect = Rect {
min: Point::new(X0, Y0), min: Point::new(X0, Y0),
@ -110,6 +113,7 @@ fn render_clip_test(sb: &mut SceneBuilder) {
}; };
sb.fill( sb.fill(
Fill::NonZero, Fill::NonZero,
Affine::IDENTITY,
&Brush::Solid(Color::rgb8(0, 0, 0)), &Brush::Solid(Color::rgb8(0, 0, 0)),
None, None,
rect.elements(), rect.elements(),
@ -124,22 +128,29 @@ fn render_alpha_test(sb: &mut SceneBuilder) {
// Alpha compositing tests. // Alpha compositing tests.
sb.fill( sb.fill(
Fill::NonZero, Fill::NonZero,
Affine::IDENTITY,
&Color::rgb8(255, 0, 0).into(), &Color::rgb8(255, 0, 0).into(),
None, None,
make_diamond(Point::new(1024.0, 100.0)), make_diamond(1024.0, 100.0),
); );
sb.fill( sb.fill(
Fill::NonZero, Fill::NonZero,
Affine::IDENTITY,
&Color::rgba8(0, 255, 0, 0x80).into(), &Color::rgba8(0, 255, 0, 0x80).into(),
None, None,
make_diamond(Point::new(1024.0, 125.0)), make_diamond(1024.0, 125.0),
);
sb.push_layer(
Mix::Clip.into(),
Affine::IDENTITY,
make_diamond(1024.0, 150.0),
); );
sb.push_layer(Mix::Clip.into(), make_diamond(Point::new(1024.0, 150.0)));
sb.fill( sb.fill(
Fill::NonZero, Fill::NonZero,
Affine::IDENTITY,
&Color::rgba8(0, 0, 255, 0x80).into(), &Color::rgba8(0, 0, 255, 0x80).into(),
None, None,
make_diamond(Point::new(1024.0, 175.0)), make_diamond(1024.0, 175.0),
); );
sb.pop_layer(); sb.pop_layer();
} }
@ -170,15 +181,12 @@ pub fn render_blend_grid(sb: &mut SceneBuilder) {
let transform = Affine::translate(i as f32 * 225., j as f32 * 225.); let transform = Affine::translate(i as f32 * 225., j as f32 * 225.);
let square = blend_square(blend.into()); let square = blend_square(blend.into());
sb.append(&square, Some(transform)); sb.append(&square, Some(transform));
// sb.append(&square, Some(transform));
// render_blend_square(sb, blend.into(), transform);
} }
} }
#[allow(unused)] #[allow(unused)]
fn render_blend_square(sb: &mut SceneBuilder, blend: BlendMode, transform: Affine) { fn render_blend_square(sb: &mut SceneBuilder, blend: BlendMode, transform: Affine) {
// Inspired by https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode // Inspired by https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
sb.transform(transform);
let rect = Rect::from_origin_size(Point::new(0., 0.), 200., 200.); let rect = Rect::from_origin_size(Point::new(0., 0.), 200., 200.);
let stops = &[ let stops = &[
GradientStop { GradientStop {
@ -196,7 +204,7 @@ fn render_blend_square(sb: &mut SceneBuilder, blend: BlendMode, transform: Affin
stops: stops.into(), stops: stops.into(),
extend: ExtendMode::Pad, extend: ExtendMode::Pad,
}); });
sb.fill(Fill::NonZero, &linear, None, rect.elements()); sb.fill(Fill::NonZero, transform, &linear, None, rect.elements());
const GRADIENTS: &[(f32, f32, Color)] = &[ const GRADIENTS: &[(f32, f32, Color)] = &[
(150., 0., Color::rgb8(64, 240, 255)), (150., 0., Color::rgb8(64, 240, 255)),
(175., 100., Color::rgb8(240, 96, 255)), (175., 100., Color::rgb8(240, 96, 255)),
@ -223,14 +231,14 @@ fn render_blend_square(sb: &mut SceneBuilder, blend: BlendMode, transform: Affin
stops: stops.into(), stops: stops.into(),
extend: ExtendMode::Pad, extend: ExtendMode::Pad,
}); });
sb.fill(Fill::NonZero, &rad, None, rect.elements()); sb.fill(Fill::NonZero, transform, &rad, None, rect.elements());
} }
const COLORS: &[Color] = &[ const COLORS: &[Color] = &[
Color::rgb8(0, 0, 255), Color::rgb8(0, 0, 255),
Color::rgb8(0, 255, 0), Color::rgb8(0, 255, 0),
Color::rgb8(255, 0, 0), Color::rgb8(255, 0, 0),
]; ];
sb.push_layer(Mix::Normal.into(), rect.elements()); sb.push_layer(Mix::Normal.into(), transform, rect.elements());
for (i, c) in COLORS.iter().enumerate() { for (i, c) in COLORS.iter().enumerate() {
let stops = &[ let stops = &[
GradientStop { GradientStop {
@ -248,17 +256,16 @@ fn render_blend_square(sb: &mut SceneBuilder, blend: BlendMode, transform: Affin
stops: stops.into(), stops: stops.into(),
extend: ExtendMode::Pad, extend: ExtendMode::Pad,
}); });
sb.transform(transform); sb.push_layer(blend, transform, rect.elements());
sb.push_layer(blend, rect.elements());
// squash the ellipse // squash the ellipse
let a = transform let a = transform
* Affine::translate(100., 100.) * Affine::translate(100., 100.)
* Affine::rotate(std::f32::consts::FRAC_PI_3 * (i * 2 + 1) as f32) * Affine::rotate(std::f32::consts::FRAC_PI_3 * (i * 2 + 1) as f32)
* Affine::scale(1.0, 0.357) * Affine::scale(1.0, 0.357)
* Affine::translate(-100., -100.); * Affine::translate(-100., -100.);
sb.transform(a);
sb.fill( sb.fill(
Fill::NonZero, Fill::NonZero,
a,
&linear, &linear,
None, None,
make_ellipse(100., 100., 90., 90.), make_ellipse(100., 100., 90., 90.),
@ -272,93 +279,7 @@ fn render_blend_square(sb: &mut SceneBuilder, blend: BlendMode, transform: Affin
fn blend_square(blend: BlendMode) -> SceneFragment { fn blend_square(blend: BlendMode) -> SceneFragment {
let mut fragment = SceneFragment::default(); let mut fragment = SceneFragment::default();
let mut sb = SceneBuilder::for_fragment(&mut fragment); let mut sb = SceneBuilder::for_fragment(&mut fragment);
// Inspired by https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode render_blend_square(&mut sb, blend, Affine::IDENTITY);
let rect = Rect::from_origin_size(Point::new(0., 0.), 200., 200.);
let stops = &[
GradientStop {
color: Color::rgb8(0, 0, 0),
offset: 0.0,
},
GradientStop {
color: Color::rgb8(255, 255, 255),
offset: 1.0,
},
][..];
let linear = Brush::LinearGradient(LinearGradient {
start: Point::new(0.0, 0.0),
end: Point::new(200.0, 0.0),
stops: stops.into(),
extend: ExtendMode::Pad,
});
sb.fill(Fill::NonZero, &linear, None, rect.elements());
const GRADIENTS: &[(f32, f32, Color)] = &[
(150., 0., Color::rgb8(64, 240, 255)),
(175., 100., Color::rgb8(240, 96, 255)),
(125., 200., Color::rgb8(255, 192, 64)),
];
for (x, y, c) in GRADIENTS {
let mut color2 = c.clone();
color2.a = 0;
let stops = &[
GradientStop {
color: c.clone(),
offset: 0.0,
},
GradientStop {
color: color2,
offset: 1.0,
},
][..];
let rad = Brush::RadialGradient(RadialGradient {
center0: Point::new(*x, *y),
center1: Point::new(*x, *y),
radius0: 0.0,
radius1: 100.0,
stops: stops.into(),
extend: ExtendMode::Pad,
});
sb.fill(Fill::NonZero, &rad, None, rect.elements());
}
const COLORS: &[Color] = &[
Color::rgb8(0, 0, 255),
Color::rgb8(0, 255, 0),
Color::rgb8(255, 0, 0),
];
sb.push_layer(Mix::Normal.into(), rect.elements());
for (i, c) in COLORS.iter().enumerate() {
let stops = &[
GradientStop {
color: Color::rgb8(255, 255, 255),
offset: 0.0,
},
GradientStop {
color: c.clone(),
offset: 1.0,
},
][..];
let linear = Brush::LinearGradient(LinearGradient {
start: Point::new(0.0, 0.0),
end: Point::new(0.0, 200.0),
stops: stops.into(),
extend: ExtendMode::Pad,
});
sb.transform(Affine::IDENTITY);
sb.push_layer(blend, rect.elements());
// squash the ellipse
let a = Affine::translate(100., 100.)
* Affine::rotate(std::f32::consts::FRAC_PI_3 * (i * 2 + 1) as f32)
* Affine::scale(1.0, 0.357)
* Affine::translate(-100., -100.);
sb.transform(a);
sb.fill(
Fill::NonZero,
&linear,
None,
make_ellipse(100., 100., 90., 90.),
);
sb.pop_layer();
}
sb.pop_layer();
sb.finish(); sb.finish();
fragment fragment
} }
@ -367,6 +288,7 @@ fn blend_square(blend: BlendMode) -> SceneFragment {
pub fn render_anim_frame(sb: &mut SceneBuilder, text: &mut SimpleText, i: usize) { pub fn render_anim_frame(sb: &mut SceneBuilder, text: &mut SimpleText, i: usize) {
sb.fill( sb.fill(
Fill::NonZero, Fill::NonZero,
Affine::IDENTITY,
&Brush::Solid(Color::rgb8(128, 128, 128)), &Brush::Solid(Color::rgb8(128, 128, 128)),
None, None,
Rect::from_origin_size(Point::new(0.0, 0.0), 1000.0, 1000.0).elements(), Rect::from_origin_size(Point::new(0.0, 0.0), 1000.0, 1000.0).elements(),
@ -389,7 +311,6 @@ pub fn render_anim_frame(sb: &mut SceneBuilder, text: &mut SimpleText, i: usize)
Affine::translate(110.0, 700.0), Affine::translate(110.0, 700.0),
s, s,
); );
sb.transform(Affine::IDENTITY);
let th = (std::f32::consts::PI / 180.0) * (i as f32); let th = (std::f32::consts::PI / 180.0) * (i as f32);
let center = Point::new(500.0, 500.0); let center = Point::new(500.0, 500.0);
let mut p1 = center; let mut p1 = center;
@ -397,12 +318,54 @@ pub fn render_anim_frame(sb: &mut SceneBuilder, text: &mut SimpleText, i: usize)
p1.y += 400.0 * th.sin(); p1.y += 400.0 * th.sin();
sb.stroke( sb.stroke(
&simple_stroke(5.0), &simple_stroke(5.0),
Affine::IDENTITY,
&Brush::Solid(Color::rgb8(128, 0, 0)), &Brush::Solid(Color::rgb8(128, 0, 0)),
None, None,
&[PathElement::MoveTo(center), PathElement::LineTo(p1)], &[PathElement::MoveTo(center), PathElement::LineTo(p1)],
); );
} }
#[allow(unused)]
pub fn render_brush_transform(sb: &mut SceneBuilder, i: usize) {
let th = (std::f32::consts::PI / 180.0) * (i as f32);
let stops = &[
GradientStop {
color: Color::rgb8(255, 0, 0),
offset: 0.0,
},
GradientStop {
color: Color::rgb8(0, 255, 0),
offset: 0.5,
},
GradientStop {
color: Color::rgb8(0, 0, 255),
offset: 1.0,
},
][..];
let linear = LinearGradient {
start: Point::new(0.0, 0.0),
end: Point::new(0.0, 200.0),
stops: stops.into(),
extend: ExtendMode::Pad,
}
.into();
sb.fill(
Fill::NonZero,
Affine::translate(200.0, 200.0),
&linear,
Some(Affine::rotate(th).around_center(200.0, 100.0)),
Rect::from_origin_size(Point::default(), 400.0, 200.0).elements(),
);
sb.stroke(
&simple_stroke(40.0),
Affine::translate(800.0, 200.0),
&linear,
Some(Affine::rotate(th).around_center(200.0, 100.0)),
Rect::from_origin_size(Point::default(), 400.0, 200.0).elements(),
);
}
fn convert_bez_path<'a>(path: &'a BezPath) -> impl Iterator<Item = PathElement> + 'a + Clone { fn convert_bez_path<'a>(path: &'a BezPath) -> impl Iterator<Item = PathElement> + 'a + Clone {
path.elements() path.elements()
.iter() .iter()
@ -440,13 +403,13 @@ fn make_ellipse(cx: f32, cy: f32, rx: f32, ry: f32) -> impl Iterator<Item = Path
(0..elements.len()).map(move |i| elements[i]) (0..elements.len()).map(move |i| elements[i])
} }
fn make_diamond(origin: Point) -> impl Iterator<Item = PathElement> + Clone { fn make_diamond(cx: f32, cy: f32) -> impl Iterator<Item = PathElement> + Clone {
const SIZE: f32 = 50.0; const SIZE: f32 = 50.0;
let elements = [ let elements = [
PathElement::MoveTo(Point::new(origin.x, origin.y - SIZE)), PathElement::MoveTo(Point::new(cx, cy - SIZE)),
PathElement::LineTo(Point::new(origin.x + SIZE, origin.y)), PathElement::LineTo(Point::new(cx + SIZE, cy)),
PathElement::LineTo(Point::new(origin.x, origin.y + SIZE)), PathElement::LineTo(Point::new(cx, cy + SIZE)),
PathElement::LineTo(Point::new(origin.x - SIZE, origin.y)), PathElement::LineTo(Point::new(cx - SIZE, cy)),
PathElement::Close, PathElement::Close,
]; ];
(0..elements.len()).map(move |i| elements[i]) (0..elements.len()).map(move |i| elements[i])

View file

@ -90,6 +90,7 @@ impl<'a> GlyphProvider<'a> {
let mut builder = SceneBuilder::for_fragment(&mut fragment); let mut builder = SceneBuilder::for_fragment(&mut fragment);
builder.fill( builder.fill(
Fill::NonZero, Fill::NonZero,
Affine::IDENTITY,
brush.unwrap_or(&Brush::Solid(Color::rgb8(255, 255, 255))), brush.unwrap_or(&Brush::Solid(Color::rgb8(255, 255, 255))),
None, None,
convert_path(path.elements()), convert_path(path.elements()),
@ -125,10 +126,15 @@ impl<'a> GlyphProvider<'a> {
if let Some(xform) = xform_stack.last() { if let Some(xform) = xform_stack.last() {
builder.push_layer( builder.push_layer(
Default::default(), Default::default(),
Affine::IDENTITY,
convert_transformed_path(path.elements(), xform), convert_transformed_path(path.elements(), xform),
); );
} else { } else {
builder.push_layer(Default::default(), convert_path(path.elements())); builder.push_layer(
Default::default(),
Affine::IDENTITY,
convert_path(path.elements()),
);
} }
} }
Command::PopClip => builder.pop_layer(), Command::PopClip => builder.pop_layer(),
@ -140,7 +146,7 @@ impl<'a> GlyphProvider<'a> {
if let Some(xform) = xform_stack.last() { if let Some(xform) = xform_stack.last() {
rect = rect.transform(xform); rect = rect.transform(xform);
} }
builder.push_layer(Default::default(), rect.elements()); builder.push_layer(Default::default(), Affine::IDENTITY, rect.elements());
} }
Command::PopLayer => builder.pop_layer(), Command::PopLayer => builder.pop_layer(),
Command::BeginBlend(bounds, mode) => { Command::BeginBlend(bounds, mode) => {
@ -151,7 +157,7 @@ impl<'a> GlyphProvider<'a> {
if let Some(xform) = xform_stack.last() { if let Some(xform) = xform_stack.last() {
rect = rect.transform(xform); rect = rect.transform(xform);
} }
builder.push_layer(convert_blend(*mode), rect.elements()) builder.push_layer(convert_blend(*mode), Affine::IDENTITY, rect.elements())
} }
Command::EndBlend => builder.pop_layer(), Command::EndBlend => builder.pop_layer(),
Command::SimpleFill(path_index, brush, brush_xform) => { Command::SimpleFill(path_index, brush, brush_xform) => {
@ -161,6 +167,7 @@ impl<'a> GlyphProvider<'a> {
if let Some(xform) = xform_stack.last() { if let Some(xform) = xform_stack.last() {
builder.fill( builder.fill(
Fill::NonZero, Fill::NonZero,
Affine::IDENTITY,
&brush, &brush,
brush_xform.map(|x| x * *xform), brush_xform.map(|x| x * *xform),
convert_transformed_path(path.elements(), xform), convert_transformed_path(path.elements(), xform),
@ -168,6 +175,7 @@ impl<'a> GlyphProvider<'a> {
} else { } else {
builder.fill( builder.fill(
Fill::NonZero, Fill::NonZero,
Affine::IDENTITY,
&brush, &brush,
brush_xform, brush_xform,
convert_path(path.elements()), convert_path(path.elements()),

View file

@ -49,21 +49,15 @@ impl<'a> SceneBuilder<'a> {
} }
} }
/// Sets the current transformation.
pub fn transform(&mut self, transform: Affine) {
if self.scene.transform_stream.last() != Some(&transform) {
self.encode_transform(transform);
}
}
/// Pushes a new layer bound by the specifed shape and composed with /// Pushes a new layer bound by the specifed shape and composed with
/// previous layers using the specified blend mode. /// previous layers using the specified blend mode.
pub fn push_layer<'s, E>(&mut self, blend: BlendMode, elements: E) pub fn push_layer<'s, E>(&mut self, blend: BlendMode, transform: Affine, elements: E)
where where
E: IntoIterator, E: IntoIterator,
E::IntoIter: Clone, E::IntoIter: Clone,
E::Item: Borrow<PathElement>, E::Item: Borrow<PathElement>,
{ {
self.maybe_encode_transform(transform);
self.linewidth(-1.0); self.linewidth(-1.0);
let elements = elements.into_iter(); let elements = elements.into_iter();
self.encode_path(elements, true); self.encode_path(elements, true);
@ -82,6 +76,7 @@ impl<'a> SceneBuilder<'a> {
pub fn fill<'s, E>( pub fn fill<'s, E>(
&mut self, &mut self,
_style: Fill, _style: Fill,
transform: Affine,
brush: &Brush, brush: &Brush,
brush_transform: Option<Affine>, brush_transform: Option<Affine>,
elements: E, elements: E,
@ -90,21 +85,14 @@ impl<'a> SceneBuilder<'a> {
E::IntoIter: Clone, E::IntoIter: Clone,
E::Item: Borrow<PathElement>, E::Item: Borrow<PathElement>,
{ {
self.maybe_encode_transform(transform);
self.linewidth(-1.0); self.linewidth(-1.0);
let elements = elements.into_iter(); let elements = elements.into_iter();
if self.encode_path(elements, true) { if self.encode_path(elements, true) {
if let Some(brush_transform) = brush_transform { if let Some(brush_transform) = brush_transform {
if let Some(last_transform) = self.scene.transform_stream.last().copied() { self.encode_transform(transform * brush_transform);
self.encode_transform(brush_transform * last_transform); self.swap_last_tags();
self.swap_last_tags(); self.encode_brush(brush);
self.encode_brush(brush);
self.encode_transform(last_transform);
} else {
self.encode_transform(brush_transform);
self.swap_last_tags();
self.encode_brush(brush);
self.encode_transform(Affine::IDENTITY);
}
} else { } else {
self.encode_brush(brush); self.encode_brush(brush);
} }
@ -115,6 +103,7 @@ impl<'a> SceneBuilder<'a> {
pub fn stroke<'s, D, E>( pub fn stroke<'s, D, E>(
&mut self, &mut self,
style: &Stroke<D>, style: &Stroke<D>,
transform: Affine,
brush: &Brush, brush: &Brush,
brush_transform: Option<Affine>, brush_transform: Option<Affine>,
elements: E, elements: E,
@ -124,21 +113,14 @@ impl<'a> SceneBuilder<'a> {
E::IntoIter: Clone, E::IntoIter: Clone,
E::Item: Borrow<PathElement>, E::Item: Borrow<PathElement>,
{ {
self.maybe_encode_transform(transform);
self.linewidth(style.width); self.linewidth(style.width);
let elements = elements.into_iter(); let elements = elements.into_iter();
if self.encode_path(elements, false) { if self.encode_path(elements, false) {
if let Some(brush_transform) = brush_transform { if let Some(brush_transform) = brush_transform {
if let Some(last_transform) = self.scene.transform_stream.last().copied() { self.encode_transform(transform * brush_transform);
self.encode_transform(brush_transform * last_transform); self.swap_last_tags();
self.swap_last_tags(); self.encode_brush(brush);
self.encode_brush(brush);
self.encode_transform(last_transform);
} else {
self.encode_transform(brush_transform);
self.swap_last_tags();
self.encode_brush(brush);
self.encode_transform(Affine::IDENTITY);
}
} else { } else {
self.encode_brush(brush); self.encode_brush(brush);
} }
@ -147,20 +129,7 @@ impl<'a> SceneBuilder<'a> {
/// Appends a fragment to the scene. /// Appends a fragment to the scene.
pub fn append(&mut self, fragment: &SceneFragment, transform: Option<Affine>) { pub fn append(&mut self, fragment: &SceneFragment, transform: Option<Affine>) {
let mut cur_transform = self.scene.transform_stream.last().copied();
if let Some(transform) = transform {
if cur_transform.is_none() {
cur_transform = Some(Affine::IDENTITY);
}
self.transform(transform);
} else if cur_transform != Some(Affine::IDENTITY) {
self.encode_transform(Affine::IDENTITY);
}
self.scene.append(&fragment.data, &transform); self.scene.append(&fragment.data, &transform);
// Prevent fragments from affecting transform state. Should we allow this?
if let Some(transform) = cur_transform {
self.transform(transform);
}
} }
/// Completes construction and finalizes the underlying scene. /// Completes construction and finalizes the underlying scene.
@ -218,6 +187,12 @@ impl<'a> SceneBuilder<'a> {
has_els has_els
} }
fn maybe_encode_transform(&mut self, transform: Affine) {
if self.scene.transform_stream.last() != Some(&transform) {
self.encode_transform(transform);
}
}
fn encode_transform(&mut self, transform: Affine) { fn encode_transform(&mut self, transform: Affine) {
self.scene.tag_stream.push(0x20); self.scene.tag_stream.push(0x20);
self.scene.transform_stream.push(transform); self.scene.transform_stream.push(transform);