Show a warning when unsupported SVG features are used

This commit is contained in:
Patrick Walton 2019-03-06 18:25:08 -08:00
parent 6062676d0e
commit 40209b6fe8
6 changed files with 299 additions and 80 deletions

2
Cargo.lock generated
View File

@ -518,6 +518,7 @@ dependencies = [
"pathfinder_ui 0.1.0", "pathfinder_ui 0.1.0",
"rayon 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "rayon 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"sdl2 0.32.1 (registry+https://github.com/rust-lang/crates.io-index)", "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)",
"usvg 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "usvg 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@ -580,6 +581,7 @@ version = "0.3.0"
name = "pathfinder_svg" name = "pathfinder_svg"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"pathfinder_geometry 0.3.0", "pathfinder_geometry 0.3.0",
"pathfinder_renderer 0.1.0", "pathfinder_renderer 0.1.0",
"usvg 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "usvg 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@ -11,6 +11,7 @@ jemallocator = "0.1"
nfd = "0.0.4" nfd = "0.0.4"
rayon = "1.0" rayon = "1.0"
sdl2 = "0.32" sdl2 = "0.32"
sdl2-sys = "0.32"
usvg = "0.4" usvg = "0.4"
[dependencies.image] [dependencies.image]

View File

@ -29,18 +29,20 @@ use pathfinder_renderer::gpu_data::BuiltScene;
use pathfinder_renderer::post::{DEFRINGING_KERNEL_CORE_GRAPHICS, STEM_DARKENING_FACTORS}; use pathfinder_renderer::post::{DEFRINGING_KERNEL_CORE_GRAPHICS, STEM_DARKENING_FACTORS};
use pathfinder_renderer::scene::Scene; use pathfinder_renderer::scene::Scene;
use pathfinder_renderer::z_buffer::ZBuffer; use pathfinder_renderer::z_buffer::ZBuffer;
use pathfinder_svg::SceneExt; use pathfinder_svg::BuiltSVG;
use pathfinder_ui::UIEvent; use pathfinder_ui::UIEvent;
use rayon::ThreadPoolBuilder; use rayon::ThreadPoolBuilder;
use sdl2::{EventPump, Sdl, VideoSubsystem}; use sdl2::EventPump;
use sdl2::event::{Event, WindowEvent}; use sdl2::event::{Event, WindowEvent};
use sdl2::keyboard::Keycode; use sdl2::keyboard::Keycode;
use sdl2::video::{GLContext, GLProfile, Window}; use sdl2::video::{GLContext, GLProfile, Window};
use sdl2_sys::{SDL_Event, SDL_UserEvent};
use std::f32::consts::FRAC_PI_4; use std::f32::consts::FRAC_PI_4;
use std::mem; use std::mem;
use std::panic; use std::panic;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process; use std::process;
use std::ptr;
use std::sync::mpsc::{self, Receiver, Sender}; use std::sync::mpsc::{self, Receiver, Sender};
use std::thread; use std::thread;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
@ -72,6 +74,8 @@ const GROUND_LINE_COLOR: ColorU = ColorU { r: 127, g: 127, b: 127, a: 255 };
const APPROX_FONT_SIZE: f32 = 16.0; const APPROX_FONT_SIZE: f32 = 16.0;
const MESSAGE_TIMEOUT_SECS: u64 = 5;
pub const GRIDLINE_COUNT: u8 = 10; pub const GRIDLINE_COUNT: u8 = 10;
mod device; mod device;
@ -79,10 +83,6 @@ mod ui;
pub struct DemoApp { pub struct DemoApp {
window: Window, window: Window,
#[allow(dead_code)]
sdl_context: Sdl,
#[allow(dead_code)]
sdl_video: VideoSubsystem,
sdl_event_pump: EventPump, sdl_event_pump: EventPump,
#[allow(dead_code)] #[allow(dead_code)]
gl_context: GLContext, gl_context: GLContext,
@ -98,6 +98,8 @@ pub struct DemoApp {
exit: bool, exit: bool,
mouselook_enabled: bool, mouselook_enabled: bool,
dirty: bool, dirty: bool,
expire_message_event_id: u32,
message_epoch: u32,
ui: DemoUI<GLDevice>, ui: DemoUI<GLDevice>,
scene_thread_proxy: SceneThreadProxy, scene_thread_proxy: SceneThreadProxy,
@ -112,6 +114,7 @@ impl DemoApp {
pub fn new() -> DemoApp { pub fn new() -> DemoApp {
let sdl_context = sdl2::init().unwrap(); let sdl_context = sdl2::init().unwrap();
let sdl_video = sdl_context.video().unwrap(); let sdl_video = sdl_context.video().unwrap();
let sdl_event = sdl_context.event().unwrap();
let gl_attributes = sdl_video.gl_attr(); let gl_attributes = sdl_video.gl_attr();
gl_attributes.set_context_profile(GLProfile::Core); gl_attributes.set_context_profile(GLProfile::Core);
@ -130,6 +133,8 @@ impl DemoApp {
let gl_context = window.gl_create_context().unwrap(); let gl_context = window.gl_create_context().unwrap();
gl::load_with(|name| sdl_video.gl_get_proc_address(name) as *const _); gl::load_with(|name| sdl_video.gl_get_proc_address(name) as *const _);
let expire_message_event_id = unsafe { sdl_event.register_event().unwrap() };
let sdl_event_pump = sdl_context.event_pump().unwrap(); let sdl_event_pump = sdl_context.event_pump().unwrap();
let device = GLDevice::new(); let device = GLDevice::new();
@ -140,10 +145,12 @@ impl DemoApp {
let (drawable_width, drawable_height) = window.drawable_size(); let (drawable_width, drawable_height) = window.drawable_size();
let drawable_size = Point2DI32::new(drawable_width as i32, drawable_height as i32); let drawable_size = Point2DI32::new(drawable_width as i32, drawable_height as i32);
let base_scene = load_scene(&options.input_path); let built_svg = load_scene(&options.input_path);
let scene_view_box = base_scene.view_box; let message = get_svg_building_message(&built_svg);
let scene_view_box = built_svg.scene.view_box;
let renderer = Renderer::new(device, &resources, drawable_size); let renderer = Renderer::new(device, &resources, drawable_size);
let scene_thread_proxy = SceneThreadProxy::new(base_scene, options.clone()); let scene_thread_proxy = SceneThreadProxy::new(built_svg.scene, options.clone());
update_drawable_size(&window, &scene_thread_proxy); update_drawable_size(&window, &scene_thread_proxy);
let camera = if options.three_d { let camera = if options.three_d {
@ -156,16 +163,23 @@ impl DemoApp {
let ground_solid_vertex_array = let ground_solid_vertex_array =
GroundSolidVertexArray::new(&renderer.device, GroundSolidVertexArray::new(&renderer.device,
&ground_program, &ground_program,
&renderer.quad_vertex_positions_buffer()); &renderer.quad_vertex_positions_buffer());
let ground_line_vertex_array = GroundLineVertexArray::new(&renderer.device, let ground_line_vertex_array = GroundLineVertexArray::new(&renderer.device,
&ground_program); &ground_program);
let ui = DemoUI::new(&renderer.device, &resources, options); let mut ui = DemoUI::new(&renderer.device, &resources, options);
let mut message_epoch = 0;
emit_message(&mut ui, &mut message_epoch, expire_message_event_id, message);
// Leak our SDL stuff. It'll last the entire duration of the process anyway, and it means
// we don't have to deal with any nasty issues regarding synchronizing background threads
// during shutdown.
mem::forget(sdl_event);
mem::forget(sdl_video);
mem::forget(sdl_context);
DemoApp { DemoApp {
window, window,
sdl_context,
sdl_video,
sdl_event_pump, sdl_event_pump,
gl_context, gl_context,
@ -180,6 +194,8 @@ impl DemoApp {
exit: false, exit: false,
mouselook_enabled: false, mouselook_enabled: false,
dirty: true, dirty: true,
expire_message_event_id,
message_epoch,
ui, ui,
scene_thread_proxy, scene_thread_proxy,
@ -333,6 +349,12 @@ impl DemoApp {
self.dirty = true; self.dirty = true;
} }
} }
Event::User { type_: event_id, code: expected_epoch, .. } if
event_id == self.expire_message_event_id &&
expected_epoch as u32 == self.message_epoch => {
self.ui.message = String::new();
self.dirty = true;
}
_ => continue, _ => continue,
} }
} }
@ -497,19 +519,21 @@ impl DemoApp {
UIAction::None => {} UIAction::None => {}
UIAction::OpenFile(ref path) => { UIAction::OpenFile(ref path) => {
let scene = load_scene(&path); let built_svg = load_scene(&path);
self.scene_view_box = scene.view_box; self.ui.message = get_svg_building_message(&built_svg);
self.scene_view_box = built_svg.scene.view_box;
update_drawable_size(&self.window, &self.scene_thread_proxy); update_drawable_size(&self.window, &self.scene_thread_proxy);
let drawable_size = current_drawable_size(&self.window); let drawable_size = current_drawable_size(&self.window);
self.camera = if self.ui.three_d_enabled { self.camera = if self.ui.three_d_enabled {
Camera::new_3d(scene.view_box) Camera::new_3d(built_svg.scene.view_box)
} else { } else {
Camera::new_2d(scene.view_box, drawable_size) Camera::new_2d(built_svg.scene.view_box, drawable_size)
}; };
self.scene_thread_proxy.load_scene(scene); self.scene_thread_proxy.load_scene(built_svg.scene);
self.dirty = true; self.dirty = true;
} }
@ -690,8 +714,8 @@ impl Options {
} }
} }
fn load_scene(input_path: &Path) -> Scene { fn load_scene(input_path: &Path) -> BuiltSVG {
Scene::from_tree(Tree::from_file(input_path, &UsvgOptions::default()).unwrap()) BuiltSVG::from_tree(Tree::from_file(input_path, &UsvgOptions::default()).unwrap())
} }
fn build_scene(scene: &Scene, build_options: BuildOptions, jobs: Option<usize>) -> BuiltScene { fn build_scene(scene: &Scene, build_options: BuildOptions, jobs: Option<usize>) -> BuiltScene {
@ -835,3 +859,48 @@ fn get_mouse_position(sdl_event_pump: &EventPump, scale_factor: f32) -> Point2DF
let mouse_state = sdl_event_pump.mouse_state(); let mouse_state = sdl_event_pump.mouse_state();
Point2DI32::new(mouse_state.x(), mouse_state.y()).to_f32().scale(scale_factor) Point2DI32::new(mouse_state.x(), mouse_state.y()).to_f32().scale(scale_factor)
} }
fn get_svg_building_message(built_svg: &BuiltSVG) -> String {
if built_svg.result_flags.is_empty() {
return String::new();
}
format!("Warning: These features in the SVG are unsupported: {}.", built_svg.result_flags)
}
fn emit_message(ui: &mut DemoUI<GLDevice>,
message_epoch: &mut u32,
expire_message_event_id: u32,
message: String) {
if message.is_empty() {
return;
}
ui.message = message;
let expected_epoch = *message_epoch + 1;
*message_epoch = expected_epoch;
thread::spawn(move || {
thread::sleep(Duration::from_secs(MESSAGE_TIMEOUT_SECS));
push_sdl_user_event(SDL_UserEvent {
timestamp: 0,
windowID: 0,
type_: expire_message_event_id,
code: expected_epoch as i32,
data1: ptr::null_mut(),
data2: ptr::null_mut(),
}).unwrap();
});
}
// Posts an event from any thread.
//
// TODO(pcwalton): The fact that this is necessary is really a `rust-sdl2` bug, filed at
// https://github.com/Rust-SDL2/rust-sdl2/issues/747.
fn push_sdl_user_event(mut event: SDL_UserEvent) -> Result<(), String> {
unsafe {
if sdl2_sys::SDL_PushEvent(&mut event as *mut SDL_UserEvent as *mut SDL_Event) == 1 {
Ok(())
} else {
Err(sdl2::get_error())
}
}
}

View File

@ -14,8 +14,8 @@ use pathfinder_geometry::basic::point::Point2DI32;
use pathfinder_geometry::basic::rect::RectI32; use pathfinder_geometry::basic::rect::RectI32;
use pathfinder_gpu::{Device, Resources}; use pathfinder_gpu::{Device, Resources};
use pathfinder_renderer::gpu::debug::DebugUI; use pathfinder_renderer::gpu::debug::DebugUI;
use pathfinder_ui::{BUTTON_HEIGHT, BUTTON_TEXT_OFFSET, BUTTON_WIDTH, PADDING, SWITCH_SIZE}; use pathfinder_ui::{BUTTON_HEIGHT, BUTTON_TEXT_OFFSET, BUTTON_WIDTH, FONT_ASCENT, PADDING};
use pathfinder_ui::{TEXT_COLOR, WINDOW_COLOR}; use pathfinder_ui::{SWITCH_SIZE, TEXT_COLOR, TOOLTIP_HEIGHT, WINDOW_COLOR};
use std::f32::consts::PI; use std::f32::consts::PI;
use std::path::PathBuf; use std::path::PathBuf;
@ -54,12 +54,15 @@ pub struct DemoUI<D> where D: Device {
effects_panel_visible: bool, effects_panel_visible: bool,
rotate_panel_visible: bool, rotate_panel_visible: bool,
// FIXME(pcwalton): Factor the below out into a model class.
pub three_d_enabled: bool, pub three_d_enabled: bool,
pub dark_background_enabled: bool, pub dark_background_enabled: bool,
pub gamma_correction_effect_enabled: bool, pub gamma_correction_effect_enabled: bool,
pub stem_darkening_effect_enabled: bool, pub stem_darkening_effect_enabled: bool,
pub subpixel_aa_effect_enabled: bool, pub subpixel_aa_effect_enabled: bool,
pub rotation: i32, pub rotation: i32,
pub message: String,
} }
impl<D> DemoUI<D> where D: Device { impl<D> DemoUI<D> where D: Device {
@ -92,6 +95,7 @@ impl<D> DemoUI<D> where D: Device {
stem_darkening_effect_enabled: false, stem_darkening_effect_enabled: false,
subpixel_aa_effect_enabled: false, subpixel_aa_effect_enabled: false,
rotation: SLIDER_WIDTH / 2, rotation: SLIDER_WIDTH / 2,
message: String::new(),
} }
} }
@ -100,6 +104,12 @@ impl<D> DemoUI<D> where D: Device {
} }
pub fn update(&mut self, device: &D, debug_ui: &mut DebugUI<D>, action: &mut UIAction) { pub fn update(&mut self, device: &D, debug_ui: &mut DebugUI<D>, action: &mut UIAction) {
// Draw message text.
self.draw_message_text(device, debug_ui);
// Draw button strip.
let bottom = debug_ui.ui.framebuffer_size().y() - PADDING; let bottom = debug_ui.ui.framebuffer_size().y() - PADDING;
let mut position = Point2DI32::new(PADDING, bottom - BUTTON_HEIGHT); let mut position = Point2DI32::new(PADDING, bottom - BUTTON_HEIGHT);
@ -181,6 +191,23 @@ impl<D> DemoUI<D> where D: Device {
self.draw_rotate_panel(device, debug_ui, action); self.draw_rotate_panel(device, debug_ui, action);
} }
fn draw_message_text(&mut self, device: &D, debug_ui: &mut DebugUI<D>) {
if self.message.is_empty() {
return;
}
let message_size = debug_ui.ui.measure_text(&self.message);
let window_origin = Point2DI32::new(PADDING, PADDING);
let window_size = Point2DI32::new(PADDING * 2 + message_size, TOOLTIP_HEIGHT);
debug_ui.ui.draw_solid_rounded_rect(device,
RectI32::new(window_origin, window_size),
WINDOW_COLOR);
debug_ui.ui.draw_text(device,
&self.message,
window_origin + Point2DI32::new(PADDING, PADDING + FONT_ASCENT),
false);
}
fn draw_effects_panel(&mut self, device: &D, debug_ui: &mut DebugUI<D>) { fn draw_effects_panel(&mut self, device: &D, debug_ui: &mut DebugUI<D>) {
if !self.effects_panel_visible { if !self.effects_panel_visible {
return; return;

View File

@ -5,6 +5,7 @@ edition = "2018"
authors = ["Patrick Walton <pcwalton@mimiga.net>"] authors = ["Patrick Walton <pcwalton@mimiga.net>"]
[dependencies] [dependencies]
bitflags = "1.0"
usvg = "0.4" usvg = "0.4"
[dependencies.pathfinder_geometry] [dependencies.pathfinder_geometry]

View File

@ -10,6 +10,9 @@
//! Converts a subset of SVG to a Pathfinder scene. //! Converts a subset of SVG to a Pathfinder scene.
#[macro_use]
extern crate bitflags;
use pathfinder_geometry::basic::line_segment::LineSegmentF32; use pathfinder_geometry::basic::line_segment::LineSegmentF32;
use pathfinder_geometry::basic::point::Point2DF32; use pathfinder_geometry::basic::point::Point2DF32;
use pathfinder_geometry::basic::rect::RectF32; use pathfinder_geometry::basic::rect::RectF32;
@ -20,29 +23,57 @@ use pathfinder_geometry::segment::{Segment, SegmentFlags};
use pathfinder_geometry::stroke::OutlineStrokeToFill; use pathfinder_geometry::stroke::OutlineStrokeToFill;
use pathfinder_renderer::paint::Paint; use pathfinder_renderer::paint::Paint;
use pathfinder_renderer::scene::{PathObject, PathObjectKind, Scene}; use pathfinder_renderer::scene::{PathObject, PathObjectKind, Scene};
use std::fmt::{Display, Formatter, Result as FormatResult};
use std::mem; use std::mem;
use usvg::{Color as SvgColor, Node, NodeExt, NodeKind, Paint as UsvgPaint}; use usvg::{Color as SvgColor, Node, NodeExt, NodeKind, Paint as UsvgPaint};
use usvg::{PathSegment as UsvgPathSegment, Rect as UsvgRect, Transform as UsvgTransform, Tree}; use usvg::{PathSegment as UsvgPathSegment, Rect as UsvgRect, Transform as UsvgTransform};
use usvg::{Tree, Visibility};
const HAIRLINE_STROKE_WIDTH: f32 = 0.1; const HAIRLINE_STROKE_WIDTH: f32 = 0.1;
pub trait SceneExt { pub struct BuiltSVG {
fn from_tree(tree: Tree) -> Self; pub scene: Scene,
pub result_flags: BuildResultFlags,
} }
impl SceneExt for Scene { bitflags! {
// NB: If you change this, make sure to update the `Display`
// implementation as well.
pub struct BuildResultFlags: u16 {
const UNSUPPORTED_CLIP_PATH_NODE = 0x0001;
const UNSUPPORTED_DEFS_NODE = 0x0002;
const UNSUPPORTED_FILTER_NODE = 0x0004;
const UNSUPPORTED_IMAGE_NODE = 0x0008;
const UNSUPPORTED_LINEAR_GRADIENT_NODE = 0x0010;
const UNSUPPORTED_MASK_NODE = 0x0020;
const UNSUPPORTED_PATTERN_NODE = 0x0040;
const UNSUPPORTED_RADIAL_GRADIENT_NODE = 0x0080;
const UNSUPPORTED_NESTED_SVG_NODE = 0x0100;
const UNSUPPORTED_TEXT_NODE = 0x0200;
const UNSUPPORTED_LINK_PAINT = 0x0400;
const UNSUPPORTED_CLIP_PATH_ATTR = 0x0800;
const UNSUPPORTED_FILTER_ATTR = 0x1000;
const UNSUPPORTED_MASK_ATTR = 0x2000;
const UNSUPPORTED_OPACITY_ATTR = 0x4000;
}
}
impl BuiltSVG {
// TODO(pcwalton): Allow a global transform to be set. // TODO(pcwalton): Allow a global transform to be set.
fn from_tree(tree: Tree) -> Scene { pub fn from_tree(tree: Tree) -> BuiltSVG {
let global_transform = Transform2DF32::default(); let global_transform = Transform2DF32::default();
let mut scene = Scene::new(); let mut built_svg = BuiltSVG {
scene: Scene::new(),
result_flags: BuildResultFlags::empty(),
};
let root = &tree.root(); let root = &tree.root();
match *root.borrow() { match *root.borrow() {
NodeKind::Svg(ref svg) => { NodeKind::Svg(ref svg) => {
scene.view_box = usvg_rect_to_euclid_rect(&svg.view_box.rect); built_svg.scene.view_box = usvg_rect_to_euclid_rect(&svg.view_box.rect);
for kid in root.children() { for kid in root.children() {
process_node(&mut scene, &kid, &global_transform); built_svg.process_node(&kid, &global_transform);
} }
} }
_ => unreachable!(), _ => unreachable!(),
@ -52,77 +83,165 @@ impl SceneExt for Scene {
// recursively dropping reference counts on very large SVGs. :( // recursively dropping reference counts on very large SVGs. :(
mem::forget(tree); mem::forget(tree);
scene built_svg
}
fn process_node(&mut self, node: &Node, transform: &Transform2DF32) {
let node_transform = usvg_transform_to_transform_2d(&node.transform());
let transform = transform.pre_mul(&node_transform);
match *node.borrow() {
NodeKind::Group(ref group) => {
if group.clip_path.is_some() {
self.result_flags.insert(BuildResultFlags::UNSUPPORTED_CLIP_PATH_ATTR);
}
if group.filter.is_some() {
self.result_flags.insert(BuildResultFlags::UNSUPPORTED_FILTER_ATTR);
}
if group.mask.is_some() {
self.result_flags.insert(BuildResultFlags::UNSUPPORTED_MASK_ATTR);
}
if group.opacity.is_some() {
self.result_flags.insert(BuildResultFlags::UNSUPPORTED_OPACITY_ATTR);
}
for kid in node.children() {
self.process_node(&kid, &transform)
}
}
NodeKind::Path(ref path) if path.visibility == Visibility::Visible => {
if let Some(ref fill) = path.fill {
let style =
self.scene.push_paint(&Paint::from_svg_paint(&fill.paint,
&mut self.result_flags));
let path = UsvgPathToSegments::new(path.segments.iter().cloned());
let path = Transform2DF32PathIter::new(path, &transform);
let outline = Outline::from_segments(path);
self.scene.bounds = self.scene.bounds.union_rect(outline.bounds());
self.scene.objects.push(PathObject::new(
outline,
style,
node.id().to_string(),
PathObjectKind::Fill,
));
}
if let Some(ref stroke) = path.stroke {
let style =
self.scene.push_paint(&Paint::from_svg_paint(&stroke.paint,
&mut self.result_flags));
let stroke_width =
f32::max(stroke.width.value() as f32, HAIRLINE_STROKE_WIDTH);
let path = UsvgPathToSegments::new(path.segments.iter().cloned());
let path = Transform2DF32PathIter::new(path, &transform);
let outline = Outline::from_segments(path);
let mut stroke_to_fill = OutlineStrokeToFill::new(outline, stroke_width);
stroke_to_fill.offset();
let outline = stroke_to_fill.outline;
self.scene.bounds = self.scene.bounds.union_rect(outline.bounds());
self.scene.objects.push(PathObject::new(
outline,
style,
node.id().to_string(),
PathObjectKind::Stroke,
));
}
}
NodeKind::Path(..) => {}
NodeKind::ClipPath(..) => {
self.result_flags.insert(BuildResultFlags::UNSUPPORTED_CLIP_PATH_NODE);
}
NodeKind::Defs { .. } => {
self.result_flags.insert(BuildResultFlags::UNSUPPORTED_DEFS_NODE);
}
NodeKind::Filter(..) => {
self.result_flags.insert(BuildResultFlags::UNSUPPORTED_FILTER_NODE);
}
NodeKind::Image(..) => {
self.result_flags.insert(BuildResultFlags::UNSUPPORTED_IMAGE_NODE);
}
NodeKind::LinearGradient(..) => {
self.result_flags.insert(BuildResultFlags::UNSUPPORTED_LINEAR_GRADIENT_NODE);
}
NodeKind::Mask(..) => {
self.result_flags.insert(BuildResultFlags::UNSUPPORTED_MASK_NODE);
}
NodeKind::Pattern(..) => {
self.result_flags.insert(BuildResultFlags::UNSUPPORTED_PATTERN_NODE);
}
NodeKind::RadialGradient(..) => {
self.result_flags.insert(BuildResultFlags::UNSUPPORTED_RADIAL_GRADIENT_NODE);
}
NodeKind::Svg(..) => {
self.result_flags.insert(BuildResultFlags::UNSUPPORTED_NESTED_SVG_NODE);
}
NodeKind::Text(..) => {
self.result_flags.insert(BuildResultFlags::UNSUPPORTED_TEXT_NODE);
}
}
} }
} }
fn process_node(scene: &mut Scene, node: &Node, transform: &Transform2DF32) { impl Display for BuildResultFlags {
let node_transform = usvg_transform_to_transform_2d(&node.transform()); fn fmt(&self, formatter: &mut Formatter) -> FormatResult {
let transform = transform.pre_mul(&node_transform); if self.is_empty() {
return Ok(())
match *node.borrow() {
NodeKind::Group(_) => {
for kid in node.children() {
process_node(scene, &kid, &transform)
}
} }
NodeKind::Path(ref path) => {
if let Some(ref fill) = path.fill {
let style = scene.push_paint(&Paint::from_svg_paint(&fill.paint));
let path = UsvgPathToSegments::new(path.segments.iter().cloned()); let mut first = true;
let path = Transform2DF32PathIter::new(path, &transform); for (bit, name) in NAMES.iter().enumerate() {
let outline = Outline::from_segments(path); if (self.bits() >> bit) & 1 == 0 {
continue;
scene.bounds = scene.bounds.union_rect(outline.bounds());
scene.objects.push(PathObject::new(
outline,
style,
node.id().to_string(),
PathObjectKind::Fill,
));
} }
if !first {
if let Some(ref stroke) = path.stroke { formatter.write_str(", ")?;
let style = scene.push_paint(&Paint::from_svg_paint(&stroke.paint)); } else {
let stroke_width = first = false;
f32::max(stroke.width.value() as f32, HAIRLINE_STROKE_WIDTH);
let path = UsvgPathToSegments::new(path.segments.iter().cloned());
let path = Transform2DF32PathIter::new(path, &transform);
let outline = Outline::from_segments(path);
let mut stroke_to_fill = OutlineStrokeToFill::new(outline, stroke_width);
stroke_to_fill.offset();
let outline = stroke_to_fill.outline;
scene.bounds = scene.bounds.union_rect(outline.bounds());
scene.objects.push(PathObject::new(
outline,
style,
node.id().to_string(),
PathObjectKind::Stroke,
));
} }
formatter.write_str(name)?;
} }
_ => {
// TODO(pcwalton): Handle these by punting to WebRender. return Ok(());
}
// Must match the order in `BuildResultFlags`.
static NAMES: &'static [&'static str] = &[
"<clipPath>",
"<defs>",
"<filter>",
"<image>",
"<linearGradient>",
"<mask>",
"<pattern>",
"<radialGradient>",
"nested <svg>",
"<text>",
"paint server element",
"clip-path attribute",
"filter attribute",
"mask attribute",
"opacity attribute",
];
} }
} }
trait PaintExt { trait PaintExt {
fn from_svg_paint(svg_paint: &UsvgPaint) -> Self; fn from_svg_paint(svg_paint: &UsvgPaint, result_flags: &mut BuildResultFlags) -> Self;
} }
impl PaintExt for Paint { impl PaintExt for Paint {
#[inline] #[inline]
fn from_svg_paint(svg_paint: &UsvgPaint) -> Paint { fn from_svg_paint(svg_paint: &UsvgPaint, result_flags: &mut BuildResultFlags) -> Paint {
Paint { Paint {
color: match *svg_paint { color: match *svg_paint {
UsvgPaint::Color(color) => ColorU::from_svg_color(color), UsvgPaint::Color(color) => ColorU::from_svg_color(color),
UsvgPaint::Link(_) => { UsvgPaint::Link(_) => {
// TODO(pcwalton) // TODO(pcwalton)
result_flags.insert(BuildResultFlags::UNSUPPORTED_LINK_PAINT);
ColorU::black() ColorU::black()
} }
} }