From 9de7d95d33f4dae84009e616ffaa65577749c208 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Fri, 3 May 2019 12:35:19 -0700 Subject: [PATCH] Start a simple HTML canvas-like API, and add a minimal example to show how to use it. --- Cargo.lock | 22 ++++ Cargo.toml | 2 + canvas/Cargo.toml | 13 +++ canvas/src/lib.rs | 145 +++++++++++++++++++++++++ demo/common/src/lib.rs | 6 +- examples/canvas_minimal/Cargo.toml | 25 +++++ examples/canvas_minimal/src/main.rs | 89 +++++++++++++++ geometry/src/color.rs | 5 + geometry/src/outline.rs | 73 ++++++++----- renderer/src/builder.rs | 4 +- renderer/src/concurrent/scene_proxy.rs | 34 ++++-- renderer/src/gpu/renderer.rs | 15 ++- renderer/src/options.rs | 18 +-- renderer/src/scene.rs | 28 ++--- renderer/src/tiles.rs | 4 +- svg/src/lib.rs | 10 +- 16 files changed, 411 insertions(+), 82 deletions(-) create mode 100644 canvas/Cargo.toml create mode 100644 canvas/src/lib.rs create mode 100644 examples/canvas_minimal/Cargo.toml create mode 100644 examples/canvas_minimal/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 057b9371..8873b47b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index c6c528c7..260b6cb8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,11 @@ [workspace] members = [ + "canvas", "demo/android/rust", "demo/common", "demo/magicleap", "demo/native", + "examples/canvas_minimal", "geometry", "gl", "gpu", diff --git a/canvas/Cargo.toml b/canvas/Cargo.toml new file mode 100644 index 00000000..fac9e346 --- /dev/null +++ b/canvas/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "pathfinder_canvas" +version = "0.1.0" +authors = ["Patrick Walton "] +edition = "2018" + +[dependencies] + +[dependencies.pathfinder_geometry] +path = "../geometry" + +[dependencies.pathfinder_renderer] +path = "../renderer" diff --git a/canvas/src/lib.rs b/canvas/src/lib.rs new file mode 100644 index 00000000..7268d988 --- /dev/null +++ b/canvas/src/lib.rs @@ -0,0 +1,145 @@ +// pathfinder/canvas/src/lib.rs +// +// Copyright © 2019 The Pathfinder Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , 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())); + } + } +} diff --git a/demo/common/src/lib.rs b/demo/common/src/lib.rs index 4a903340..0fe60715 100644 --- a/demo/common/src/lib.rs +++ b/demo/common/src/lib.rs @@ -242,8 +242,7 @@ impl DemoApp 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) -> Vec { @@ -769,7 +768,6 @@ impl BackgroundColor { struct SceneMetadata { view_box: RectF32, - bounds: RectF32, monochrome_color: Option, } @@ -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 } } } diff --git a/examples/canvas_minimal/Cargo.toml b/examples/canvas_minimal/Cargo.toml new file mode 100644 index 00000000..ccd2fe25 --- /dev/null +++ b/examples/canvas_minimal/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "canvas_minimal" +version = "0.1.0" +authors = ["Patrick Walton "] +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" diff --git a/examples/canvas_minimal/src/main.rs b/examples/canvas_minimal/src/main.rs new file mode 100644 index 00000000..8238cf20 --- /dev/null +++ b/examples/canvas_minimal/src/main.rs @@ -0,0 +1,89 @@ +// pathfinder/canvas_minimal/src/main.rs +// +// Copyright © 2019 The Pathfinder Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , 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, + _ => {} + } + } +} diff --git a/geometry/src/color.rs b/geometry/src/color.rs index cdf3dd81..5b27ff8a 100644 --- a/geometry/src/color.rs +++ b/geometry/src/color.rs @@ -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] diff --git a/geometry/src/outline.rs b/geometry/src/outline.rs index 8cd939ac..632c9134 100644 --- a/geometry/src/outline.rs +++ b/geometry/src/outline.rs @@ -24,7 +24,7 @@ use std::mem; #[derive(Clone)] pub struct Outline { - pub contours: Vec, + pub(crate) contours: Vec, 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,15 +175,9 @@ 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) { @@ -182,15 +185,9 @@ impl Outline { 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()); } } @@ -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) { *bounds = Some(match *bounds { None => self.bounds, diff --git a/renderer/src/builder.rs b/renderer/src/builder.rs index 7ab83925..2ec5fd49 100644 --- a/renderer/src/builder.rs +++ b/renderer/src/builder.rs @@ -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, diff --git a/renderer/src/concurrent/scene_proxy.rs b/renderer/src/concurrent/scene_proxy.rs index f59b3f7c..ecfc05db 100644 --- a/renderer/src/concurrent/scene_proxy.rs +++ b/renderer/src/concurrent/scene_proxy.rs @@ -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) { - 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(&self, renderer: &mut Renderer, options: RenderOptions) + where D: Device { + for command in self.build_with_stream(options) { + renderer.render_command(&command) + } + } } fn scene_thread(mut scene: Scene, @@ -74,9 +92,7 @@ fn scene_thread(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(mut scene: Scene, enum MainToWorkerMsg { ReplaceScene(Scene), SetViewBox(RectF32), - Build(PreparedRenderOptions, Box), + Build(RenderOptions, Box), } pub struct RenderCommandStream { diff --git a/renderer/src/gpu/renderer.rs b/renderer/src/gpu/renderer.rs index 2a60e1f6..3097c616 100644 --- a/renderer/src/gpu/renderer.rs +++ b/renderer/src/gpu/renderer.rs @@ -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 DestFramebuffer where D: Device, { + #[inline] + pub fn full_window(window_size: Point2DI32) -> DestFramebuffer { + 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, diff --git a/renderer/src/options.rs b/renderer/src/options.rs index 21e992df..3ab28db6 100644 --- a/renderer/src/options.rs +++ b/renderer/src/options.rs @@ -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, diff --git a/renderer/src/scene.rs b/renderer/src/scene.rs index a1aa8770..572cadcb 100644 --- a/renderer/src/scene.rs +++ b/renderer/src/scene.rs @@ -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(&self, - built_options: &PreparedRenderOptions, + options: RenderOptions, listener: Box, 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] diff --git a/renderer/src/tiles.rs b/renderer/src/tiles.rs index 97ed6291..c71d7b8a 100644 --- a/renderer/src/tiles.rs +++ b/renderer/src/tiles.rs @@ -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); diff --git a/svg/src/lib.rs b/svg/src/lib.rs index 269f058f..941892ae 100644 --- a/svg/src/lib.rs +++ b/svg/src/lib.rs @@ -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()), )); } }