Start a simple HTML canvas-like API, and add a minimal example to show how to

use it.
This commit is contained in:
Patrick Walton 2019-05-03 12:35:19 -07:00
parent 4a4011d303
commit 9de7d95d33
16 changed files with 411 additions and 82 deletions

22
Cargo.lock generated
View File

@ -156,6 +156,20 @@ name = "byteorder"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "canvas_minimal"
version = "0.1.0"
dependencies = [
"gl 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"pathfinder_canvas 0.1.0",
"pathfinder_geometry 0.3.0",
"pathfinder_gl 0.1.0",
"pathfinder_gpu 0.1.0",
"pathfinder_renderer 0.1.0",
"sdl2 0.32.1 (registry+https://github.com/rust-lang/crates.io-index)",
"sdl2-sys 0.32.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "cc"
version = "1.0.29"
@ -1013,6 +1027,14 @@ dependencies = [
"pathfinder_gpu 0.1.0",
]
[[package]]
name = "pathfinder_canvas"
version = "0.1.0"
dependencies = [
"pathfinder_geometry 0.3.0",
"pathfinder_renderer 0.1.0",
]
[[package]]
name = "pathfinder_demo"
version = "0.1.0"

View File

@ -1,9 +1,11 @@
[workspace]
members = [
"canvas",
"demo/android/rust",
"demo/common",
"demo/magicleap",
"demo/native",
"examples/canvas_minimal",
"geometry",
"gl",
"gpu",

13
canvas/Cargo.toml Normal file
View File

@ -0,0 +1,13 @@
[package]
name = "pathfinder_canvas"
version = "0.1.0"
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
edition = "2018"
[dependencies]
[dependencies.pathfinder_geometry]
path = "../geometry"
[dependencies.pathfinder_renderer]
path = "../renderer"

145
canvas/src/lib.rs Normal file
View File

@ -0,0 +1,145 @@
// pathfinder/canvas/src/lib.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! A simple API for Pathfinder that mirrors a subset of HTML canvas.
use pathfinder_geometry::basic::point::Point2DF32;
use pathfinder_geometry::basic::rect::RectF32;
use pathfinder_geometry::color::ColorU;
use pathfinder_geometry::outline::{Contour, Outline};
use pathfinder_geometry::stroke::OutlineStrokeToFill;
use pathfinder_renderer::scene::{Paint, PathObject, Scene};
use std::mem;
const HAIRLINE_STROKE_WIDTH: f32 = 0.0333;
pub struct CanvasRenderingContext2D {
scene: Scene,
current_paint: Paint,
current_line_width: f32,
}
impl CanvasRenderingContext2D {
#[inline]
pub fn new(size: Point2DF32) -> CanvasRenderingContext2D {
let mut scene = Scene::new();
scene.set_view_box(RectF32::new(Point2DF32::default(), size));
CanvasRenderingContext2D::from_scene(scene)
}
#[inline]
pub fn from_scene(scene: Scene) -> CanvasRenderingContext2D {
CanvasRenderingContext2D {
scene,
current_paint: Paint { color: ColorU::black() },
current_line_width: 1.0,
}
}
#[inline]
pub fn into_scene(self) -> Scene {
self.scene
}
#[inline]
pub fn fill_rect(&mut self, rect: RectF32) {
let mut path = Path2D::new();
path.rect(rect);
self.fill_path(path);
}
#[inline]
pub fn stroke_rect(&mut self, rect: RectF32) {
let mut path = Path2D::new();
path.rect(rect);
self.stroke_path(path);
}
#[inline]
pub fn set_line_width(&mut self, new_line_width: f32) {
self.current_line_width = new_line_width
}
#[inline]
pub fn fill_path(&mut self, path: Path2D) {
let paint_id = self.scene.push_paint(&self.current_paint);
self.scene.push_object(PathObject::new(path.into_outline(), paint_id, String::new()))
}
#[inline]
pub fn stroke_path(&mut self, path: Path2D) {
let paint_id = self.scene.push_paint(&self.current_paint);
let stroke_width = f32::max(self.current_line_width, HAIRLINE_STROKE_WIDTH);
let mut stroke_to_fill = OutlineStrokeToFill::new(path.into_outline(), stroke_width);
stroke_to_fill.offset();
self.scene.push_object(PathObject::new(stroke_to_fill.outline, paint_id, String::new()))
}
}
#[derive(Clone)]
pub struct Path2D {
outline: Outline,
current_contour: Contour,
}
// TODO(pcwalton): `arc`, `ellipse`
impl Path2D {
#[inline]
pub fn new() -> Path2D {
Path2D { outline: Outline::new(), current_contour: Contour::new() }
}
#[inline]
pub fn close_path(&mut self) {
self.current_contour.close();
}
#[inline]
pub fn move_to(&mut self, to: Point2DF32) {
// TODO(pcwalton): Cull degenerate contours.
self.flush_current_contour();
self.current_contour.push_endpoint(to);
}
#[inline]
pub fn line_to(&mut self, to: Point2DF32) {
self.current_contour.push_endpoint(to);
}
#[inline]
pub fn quadratic_curve_to(&mut self, ctrl: Point2DF32, to: Point2DF32) {
self.current_contour.push_quadratic(ctrl, to);
}
#[inline]
pub fn bezier_curve_to(&mut self, ctrl0: Point2DF32, ctrl1: Point2DF32, to: Point2DF32) {
self.current_contour.push_cubic(ctrl0, ctrl1, to);
}
pub fn rect(&mut self, rect: RectF32) {
self.flush_current_contour();
self.current_contour.push_endpoint(rect.origin());
self.current_contour.push_endpoint(rect.upper_right());
self.current_contour.push_endpoint(rect.lower_right());
self.current_contour.push_endpoint(rect.lower_left());
self.current_contour.close();
}
fn into_outline(mut self) -> Outline {
self.flush_current_contour();
self.outline
}
fn flush_current_contour(&mut self) {
if !self.current_contour.is_empty() {
self.outline.push_contour(mem::replace(&mut self.current_contour, Contour::new()));
}
}
}

View File

@ -242,8 +242,7 @@ impl<W> DemoApp<W> where W: Window {
subpixel_aa_enabled: self.ui.subpixel_aa_effect_enabled,
};
let built_options = render_options.prepare(self.scene_metadata.bounds);
self.render_command_stream = Some(self.scene_proxy.build_with_stream(built_options));
self.render_command_stream = Some(self.scene_proxy.build_with_stream(render_options));
}
fn handle_events(&mut self, events: Vec<Event>) -> Vec<UIEvent> {
@ -769,7 +768,6 @@ impl BackgroundColor {
struct SceneMetadata {
view_box: RectF32,
bounds: RectF32,
monochrome_color: Option<ColorU>,
}
@ -780,6 +778,6 @@ impl SceneMetadata {
let view_box = scene.view_box();
let monochrome_color = scene.monochrome_color();
scene.set_view_box(RectF32::new(Point2DF32::default(), viewport_size.to_f32()));
SceneMetadata { view_box, monochrome_color, bounds: scene.bounds() }
SceneMetadata { view_box, monochrome_color }
}
}

View File

@ -0,0 +1,25 @@
[package]
name = "canvas_minimal"
version = "0.1.0"
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
edition = "2018"
[dependencies]
gl = "0.6"
sdl2 = "0.32"
sdl2-sys = "0.32"
[dependencies.pathfinder_canvas]
path = "../../canvas"
[dependencies.pathfinder_geometry]
path = "../../geometry"
[dependencies.pathfinder_gl]
path = "../../gl"
[dependencies.pathfinder_gpu]
path = "../../gpu"
[dependencies.pathfinder_renderer]
path = "../../renderer"

View File

@ -0,0 +1,89 @@
// pathfinder/canvas_minimal/src/main.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use pathfinder_canvas::{CanvasRenderingContext2D, Path2D};
use pathfinder_geometry::basic::point::{Point2DF32, Point2DI32};
use pathfinder_geometry::basic::rect::RectF32;
use pathfinder_geometry::color::ColorF;
use pathfinder_gl::{GLDevice, GLVersion};
use pathfinder_gpu::resources::FilesystemResourceLoader;
use pathfinder_gpu::{ClearParams, Device};
use pathfinder_renderer::concurrent::rayon::RayonExecutor;
use pathfinder_renderer::concurrent::scene_proxy::SceneProxy;
use pathfinder_renderer::gpu::renderer::{DestFramebuffer, Renderer};
use pathfinder_renderer::options::RenderOptions;
use sdl2::event::Event;
use sdl2::keyboard::Keycode;
use sdl2::video::GLProfile;
fn main() {
// Set up SDL2.
let sdl_context = sdl2::init().unwrap();
let video = sdl_context.video().unwrap();
// Make sure we have at least a GL 3.0 context. Pathfinder requires this.
let gl_attributes = video.gl_attr();
gl_attributes.set_context_profile(GLProfile::Core);
gl_attributes.set_context_version(3, 3);
// Open a window.
let window_size = Point2DI32::new(640, 480);
let window = video.window("Minimal example", window_size.x() as u32, window_size.y() as u32)
.opengl()
.build()
.unwrap();
// Create the GL context, and make it current.
let gl_context = window.gl_create_context().unwrap();
gl::load_with(|name| video.gl_get_proc_address(name) as *const _);
window.gl_make_current(&gl_context).unwrap();
// Create a Pathfinder renderer.
let mut renderer = Renderer::new(GLDevice::new(GLVersion::GL3, 0),
&FilesystemResourceLoader::locate(),
DestFramebuffer::full_window(window_size));
// Clear to white.
renderer.device.clear(&ClearParams { color: Some(ColorF::white()), ..ClearParams::default() });
// Make a canvas. We're going to draw a house.
let mut canvas = CanvasRenderingContext2D::new(window_size.to_f32());
// Set line width.
canvas.set_line_width(10.0);
// Draw walls.
canvas.stroke_rect(RectF32::new(Point2DF32::new(75.0, 140.0), Point2DF32::new(150.0, 110.0)));
// Draw door.
canvas.fill_rect(RectF32::new(Point2DF32::new(130.0, 190.0), Point2DF32::new(40.0, 60.0)));
// Draw roof.
let mut path = Path2D::new();
path.move_to(Point2DF32::new(50.0, 140.0));
path.line_to(Point2DF32::new(150.0, 60.0));
path.line_to(Point2DF32::new(250.0, 140.0));
path.close_path();
canvas.stroke_path(path);
// Render the canvas to screen.
let scene = SceneProxy::new(canvas.into_scene(), RayonExecutor);
scene.build_and_render(&mut renderer, RenderOptions::default());
window.gl_swap_window();
// Wait for a keypress.
let mut event_pump = sdl_context.event_pump().unwrap();
loop {
match event_pump.wait_event() {
Event::Quit {..} | Event::KeyDown { keycode: Some(Keycode::Escape), .. } => return,
_ => {}
}
}
}

View File

@ -63,6 +63,11 @@ impl ColorF {
ColorF(F32x4::default())
}
#[inline]
pub fn white() -> ColorF {
ColorF(F32x4::splat(1.0))
}
#[inline]
pub fn r(&self) -> f32 {
self.0[0]

View File

@ -24,7 +24,7 @@ use std::mem;
#[derive(Clone)]
pub struct Outline {
pub contours: Vec<Contour>,
pub(crate) contours: Vec<Contour>,
pub(crate) bounds: RectF32,
}
@ -59,7 +59,6 @@ impl Outline {
{
let mut outline = Outline::new();
let mut current_contour = Contour::new();
let mut bounds = None;
for segment in segments {
if segment.flags.contains(SegmentFlags::FIRST_IN_SUBPATH) {
@ -75,8 +74,7 @@ impl Outline {
if !current_contour.is_empty() {
current_contour.close();
let contour = mem::replace(&mut current_contour, Contour::new());
contour.update_bounds(&mut bounds);
outline.contours.push(contour);
outline.push_contour(contour);
}
continue;
}
@ -99,15 +97,7 @@ impl Outline {
current_contour.push_point(segment.baseline.to(), PointFlags::empty(), true);
}
if !current_contour.is_empty() {
current_contour.update_bounds(&mut bounds);
outline.contours.push(current_contour);
}
if let Some(bounds) = bounds {
outline.bounds = bounds;
}
outline.push_contour(current_contour);
outline
}
@ -116,6 +106,25 @@ impl Outline {
self.bounds
}
#[inline]
pub fn contours(&self) -> &[Contour] {
&self.contours
}
pub fn push_contour(&mut self, contour: Contour) {
if contour.is_empty() {
return;
}
if self.contours.is_empty() {
self.bounds = contour.bounds;
} else {
self.bounds = self.bounds.union_rect(contour.bounds);
}
self.contours.push(contour);
}
pub fn transform(&mut self, transform: &Transform2DF32) {
let mut new_bounds = None;
for contour in &mut self.contours {
@ -166,32 +175,20 @@ impl Outline {
return;
}
let mut new_bounds = None;
for contour in mem::replace(&mut self.contours, vec![]) {
let contour = ContourPolygonClipper::new(clip_polygon, contour).clip();
if !contour.is_empty() {
contour.update_bounds(&mut new_bounds);
self.contours.push(contour);
self.push_contour(ContourPolygonClipper::new(clip_polygon, contour).clip());
}
}
self.bounds = new_bounds.unwrap_or_else(|| RectF32::default());
}
pub fn clip_against_rect(&mut self, clip_rect: RectF32) {
if clip_rect.contains_rect(self.bounds) {
return;
}
let mut new_bounds = None;
for contour in mem::replace(&mut self.contours, vec![]) {
let contour = ContourRectClipper::new(clip_rect, contour).clip();
if !contour.is_empty() {
contour.update_bounds(&mut new_bounds);
self.contours.push(contour);
self.push_contour(ContourRectClipper::new(clip_rect, contour).clip());
}
}
self.bounds = new_bounds.unwrap_or_else(|| RectF32::default());
}
}
impl Debug for Outline {
@ -271,7 +268,25 @@ impl Contour {
}
#[inline]
pub(crate) fn close(&mut self) {
pub fn push_endpoint(&mut self, point: Point2DF32) {
self.push_point(point, PointFlags::empty(), true);
}
#[inline]
pub fn push_quadratic(&mut self, ctrl: Point2DF32, point: Point2DF32) {
self.push_point(ctrl, PointFlags::CONTROL_POINT_0, true);
self.push_point(point, PointFlags::empty(), true);
}
#[inline]
pub fn push_cubic(&mut self, ctrl0: Point2DF32, ctrl1: Point2DF32, point: Point2DF32) {
self.push_point(ctrl0, PointFlags::CONTROL_POINT_0, true);
self.push_point(ctrl1, PointFlags::CONTROL_POINT_1, true);
self.push_point(point, PointFlags::empty(), true);
}
#[inline]
pub fn close(&mut self) {
self.closed = true;
}
@ -596,6 +611,8 @@ impl Contour {
true
}
// Use this function to keep bounds up to date when mutating paths. See `Outline::transform()`
// for an example of use.
pub(crate) fn update_bounds(&self, bounds: &mut Option<RectF32>) {
*bounds = Some(match *bounds {
None => self.bounds,

View File

@ -21,7 +21,7 @@ use std::sync::atomic::AtomicUsize;
use std::time::Instant;
use std::u16;
pub struct SceneBuilder<'a> {
pub(crate) struct SceneBuilder<'a> {
scene: &'a Scene,
built_options: &'a PreparedRenderOptions,
@ -31,7 +31,7 @@ pub struct SceneBuilder<'a> {
}
impl<'a> SceneBuilder<'a> {
pub fn new(
pub(crate) fn new(
scene: &'a Scene,
built_options: &'a PreparedRenderOptions,
listener: Box<dyn RenderCommandListener>,

View File

@ -20,10 +20,12 @@
//! You don't need to use this API to use Pathfinder; it's only a convenience.
use crate::concurrent::executor::Executor;
use crate::gpu::renderer::Renderer;
use crate::gpu_data::RenderCommand;
use crate::options::{PreparedRenderOptions, RenderCommandListener};
use crate::options::{RenderCommandListener, RenderOptions};
use crate::scene::Scene;
use pathfinder_geometry::basic::rect::RectF32;
use pathfinder_gpu::Device;
use std::sync::mpsc::{self, Receiver, Sender};
use std::thread;
@ -52,18 +54,34 @@ impl SceneProxy {
#[inline]
pub fn build_with_listener(&self,
built_options: PreparedRenderOptions,
options: RenderOptions,
listener: Box<dyn RenderCommandListener>) {
self.sender.send(MainToWorkerMsg::Build(built_options, listener)).unwrap();
self.sender.send(MainToWorkerMsg::Build(options, listener)).unwrap();
}
#[inline]
pub fn build_with_stream(&self, built_options: PreparedRenderOptions) -> RenderCommandStream {
pub fn build_with_stream(&self, options: RenderOptions) -> RenderCommandStream {
let (sender, receiver) = mpsc::sync_channel(MAX_MESSAGES_IN_FLIGHT);
let listener = Box::new(move |command| sender.send(command).unwrap());
self.build_with_listener(built_options, listener);
self.build_with_listener(options, listener);
RenderCommandStream::new(receiver)
}
/// A convenience method to build a scene and send the resulting commands
/// to the given renderer.
///
/// Exactly equivalent to:
///
/// for command in scene_proxy.build_with_stream(options) {
/// renderer.render_command(&command)
/// }
#[inline]
pub fn build_and_render<D>(&self, renderer: &mut Renderer<D>, options: RenderOptions)
where D: Device {
for command in self.build_with_stream(options) {
renderer.render_command(&command)
}
}
}
fn scene_thread<E>(mut scene: Scene,
@ -74,9 +92,7 @@ fn scene_thread<E>(mut scene: Scene,
match msg {
MainToWorkerMsg::ReplaceScene(new_scene) => scene = new_scene,
MainToWorkerMsg::SetViewBox(new_view_box) => scene.set_view_box(new_view_box),
MainToWorkerMsg::Build(options, listener) => {
scene.build(&options, listener, &executor);
}
MainToWorkerMsg::Build(options, listener) => scene.build(options, listener, &executor),
}
}
}
@ -84,7 +100,7 @@ fn scene_thread<E>(mut scene: Scene,
enum MainToWorkerMsg {
ReplaceScene(Scene),
SetViewBox(RectF32),
Build(PreparedRenderOptions, Box<dyn RenderCommandListener>),
Build(RenderOptions, Box<dyn RenderCommandListener>),
}
pub struct RenderCommandStream {

View File

@ -177,7 +177,7 @@ where
let debug_ui = DebugUI::new(&device, resources, dest_framebuffer.window_size(&device));
Renderer {
let renderer = Renderer {
device,
dest_framebuffer,
@ -218,7 +218,12 @@ where
render_mode: RenderMode::default(),
use_depth: false,
}
};
// As a convenience, bind the destination framebuffer.
renderer.bind_dest_framebuffer();
renderer
}
pub fn begin_scene(&mut self) {
@ -1466,6 +1471,12 @@ impl<D> DestFramebuffer<D>
where
D: Device,
{
#[inline]
pub fn full_window(window_size: Point2DI32) -> DestFramebuffer<D> {
let viewport = RectI32::new(Point2DI32::default(), window_size);
DestFramebuffer::Default { viewport, window_size }
}
fn window_size(&self, device: &D) -> Point2DI32 {
match *self {
DestFramebuffer::Default { window_size, .. } => window_size,

View File

@ -39,7 +39,7 @@ pub struct RenderOptions {
}
impl RenderOptions {
pub fn prepare(self, bounds: RectF32) -> PreparedRenderOptions {
pub(crate) fn prepare(self, bounds: RectF32) -> PreparedRenderOptions {
PreparedRenderOptions {
transform: self.transform.prepare(bounds),
dilation: self.dilation,
@ -119,15 +119,15 @@ impl RenderTransform {
}
}
pub struct PreparedRenderOptions {
pub transform: PreparedRenderTransform,
pub dilation: Point2DF32,
pub subpixel_aa_enabled: bool,
pub(crate) struct PreparedRenderOptions {
pub(crate) transform: PreparedRenderTransform,
pub(crate) dilation: Point2DF32,
pub(crate) subpixel_aa_enabled: bool,
}
impl PreparedRenderOptions {
#[inline]
pub fn bounding_quad(&self) -> BoundingQuad {
pub(crate) fn bounding_quad(&self) -> BoundingQuad {
match self.transform {
PreparedRenderTransform::Perspective { quad, .. } => quad,
_ => [Point3DF32::default(); 4],
@ -135,9 +135,9 @@ impl PreparedRenderOptions {
}
}
pub type BoundingQuad = [Point3DF32; 4];
pub(crate) type BoundingQuad = [Point3DF32; 4];
pub enum PreparedRenderTransform {
pub(crate) enum PreparedRenderTransform {
None,
Transform2D(Transform2DF32),
Perspective {
@ -149,7 +149,7 @@ pub enum PreparedRenderTransform {
impl PreparedRenderTransform {
#[inline]
pub fn is_2d(&self) -> bool {
pub(crate) fn is_2d(&self) -> bool {
match *self {
PreparedRenderTransform::Transform2D(_) => true,
_ => false,

View File

@ -12,7 +12,8 @@
use crate::builder::SceneBuilder;
use crate::concurrent::executor::Executor;
use crate::options::{PreparedRenderOptions, PreparedRenderTransform, RenderCommandListener};
use crate::options::{PreparedRenderOptions, PreparedRenderTransform};
use crate::options::{RenderCommandListener, RenderOptions};
use hashbrown::HashMap;
use pathfinder_geometry::basic::point::Point2DF32;
use pathfinder_geometry::basic::rect::RectF32;
@ -43,6 +44,7 @@ impl Scene {
}
pub fn push_object(&mut self, object: PathObject) {
self.bounds = self.bounds.union_rect(object.outline.bounds());
self.objects.push(object);
}
@ -165,7 +167,7 @@ impl Scene {
}
#[inline]
pub fn effective_view_box(&self, render_options: &PreparedRenderOptions) -> RectF32 {
pub(crate) fn effective_view_box(&self, render_options: &PreparedRenderOptions) -> RectF32 {
if render_options.subpixel_aa_enabled {
self.view_box.scale_xy(Point2DF32::new(3.0, 1.0))
} else {
@ -175,11 +177,12 @@ impl Scene {
#[inline]
pub fn build<E>(&self,
built_options: &PreparedRenderOptions,
options: RenderOptions,
listener: Box<dyn RenderCommandListener>,
executor: &E)
where E: Executor {
SceneBuilder::new(self, built_options, listener).build(executor)
let prepared_options = options.prepare(self.bounds);
SceneBuilder::new(self, &prepared_options, listener).build(executor)
}
}
@ -215,25 +218,12 @@ pub struct PathObject {
outline: Outline,
paint: PaintId,
name: String,
kind: PathObjectKind,
}
#[derive(Clone, Copy, Debug)]
pub enum PathObjectKind {
Fill,
Stroke,
}
impl PathObject {
#[inline]
pub fn new(outline: Outline, paint: PaintId, name: String, kind: PathObjectKind)
-> PathObject {
PathObject {
outline,
paint,
name,
kind,
}
pub fn new(outline: Outline, paint: PaintId, name: String) -> PathObject {
PathObject { outline, paint, name }
}
#[inline]

View File

@ -245,7 +245,7 @@ impl<'a> Tiler<'a> {
let outline = &self.outline;
let point_index = self.point_queue.pop().unwrap().point_index;
let contour = &outline.contours[point_index.contour() as usize];
let contour = &outline.contours()[point_index.contour() as usize];
// TODO(pcwalton): Could use a bitset of processed edges…
let prev_endpoint_index = contour.prev_endpoint_index_of(point_index.point());
@ -311,7 +311,7 @@ impl<'a> Tiler<'a> {
fn init_point_queue(&mut self) {
// Find MIN points.
self.point_queue.clear();
for (contour_index, contour) in self.outline.contours.iter().enumerate() {
for (contour_index, contour) in self.outline.contours().iter().enumerate() {
let contour_index = contour_index as u32;
let mut cur_endpoint_index = 0;
let mut prev_endpoint_index = contour.prev_endpoint_index_of(cur_endpoint_index);

View File

@ -21,7 +21,7 @@ use pathfinder_geometry::color::ColorU;
use pathfinder_geometry::outline::Outline;
use pathfinder_geometry::segment::{Segment, SegmentFlags};
use pathfinder_geometry::stroke::OutlineStrokeToFill;
use pathfinder_renderer::scene::{Paint, PathObject, PathObjectKind, Scene};
use pathfinder_renderer::scene::{Paint, PathObject, Scene};
use std::fmt::{Display, Formatter, Result as FormatResult};
use std::mem;
use usvg::{Color as SvgColor, Node, NodeExt, NodeKind, Paint as UsvgPaint};
@ -122,12 +122,10 @@ impl BuiltSVG {
let path = Transform2DF32PathIter::new(path, &transform);
let outline = Outline::from_segments(path);
self.scene.set_bounds(self.scene.bounds().union_rect(outline.bounds()));
self.scene.push_object(PathObject::new(
outline,
style,
node.id().to_string(),
PathObjectKind::Fill,
format!("Fill({})", node.id()),
));
}
@ -146,12 +144,10 @@ impl BuiltSVG {
let mut outline = stroke_to_fill.outline;
outline.transform(&transform);
self.scene.set_bounds(self.scene.bounds().union_rect(outline.bounds()));
self.scene.push_object(PathObject::new(
outline,
style,
node.id().to_string(),
PathObjectKind::Stroke,
format!("Stroke({})", node.id()),
));
}
}