Show a warning when unsupported SVG features are used
This commit is contained in:
parent
6062676d0e
commit
40209b6fe8
|
@ -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)",
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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 {
|
||||||
|
@ -160,12 +167,19 @@ impl DemoApp {
|
||||||
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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]
|
||||||
|
|
167
svg/src/lib.rs
167
svg/src/lib.rs
|
@ -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,30 +83,44 @@ 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(scene: &mut Scene, node: &Node, transform: &Transform2DF32) {
|
fn process_node(&mut self, node: &Node, transform: &Transform2DF32) {
|
||||||
let node_transform = usvg_transform_to_transform_2d(&node.transform());
|
let node_transform = usvg_transform_to_transform_2d(&node.transform());
|
||||||
let transform = transform.pre_mul(&node_transform);
|
let transform = transform.pre_mul(&node_transform);
|
||||||
|
|
||||||
match *node.borrow() {
|
match *node.borrow() {
|
||||||
NodeKind::Group(_) => {
|
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() {
|
for kid in node.children() {
|
||||||
process_node(scene, &kid, &transform)
|
self.process_node(&kid, &transform)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NodeKind::Path(ref path) => {
|
NodeKind::Path(ref path) if path.visibility == Visibility::Visible => {
|
||||||
if let Some(ref fill) = path.fill {
|
if let Some(ref fill) = path.fill {
|
||||||
let style = scene.push_paint(&Paint::from_svg_paint(&fill.paint));
|
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 = UsvgPathToSegments::new(path.segments.iter().cloned());
|
||||||
let path = Transform2DF32PathIter::new(path, &transform);
|
let path = Transform2DF32PathIter::new(path, &transform);
|
||||||
let outline = Outline::from_segments(path);
|
let outline = Outline::from_segments(path);
|
||||||
|
|
||||||
scene.bounds = scene.bounds.union_rect(outline.bounds());
|
self.scene.bounds = self.scene.bounds.union_rect(outline.bounds());
|
||||||
scene.objects.push(PathObject::new(
|
self.scene.objects.push(PathObject::new(
|
||||||
outline,
|
outline,
|
||||||
style,
|
style,
|
||||||
node.id().to_string(),
|
node.id().to_string(),
|
||||||
|
@ -84,7 +129,9 @@ fn process_node(scene: &mut Scene, node: &Node, transform: &Transform2DF32) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref stroke) = path.stroke {
|
if let Some(ref stroke) = path.stroke {
|
||||||
let style = scene.push_paint(&Paint::from_svg_paint(&stroke.paint));
|
let style =
|
||||||
|
self.scene.push_paint(&Paint::from_svg_paint(&stroke.paint,
|
||||||
|
&mut self.result_flags));
|
||||||
let stroke_width =
|
let stroke_width =
|
||||||
f32::max(stroke.width.value() as f32, HAIRLINE_STROKE_WIDTH);
|
f32::max(stroke.width.value() as f32, HAIRLINE_STROKE_WIDTH);
|
||||||
|
|
||||||
|
@ -96,8 +143,8 @@ fn process_node(scene: &mut Scene, node: &Node, transform: &Transform2DF32) {
|
||||||
stroke_to_fill.offset();
|
stroke_to_fill.offset();
|
||||||
let outline = stroke_to_fill.outline;
|
let outline = stroke_to_fill.outline;
|
||||||
|
|
||||||
scene.bounds = scene.bounds.union_rect(outline.bounds());
|
self.scene.bounds = self.scene.bounds.union_rect(outline.bounds());
|
||||||
scene.objects.push(PathObject::new(
|
self.scene.objects.push(PathObject::new(
|
||||||
outline,
|
outline,
|
||||||
style,
|
style,
|
||||||
node.id().to_string(),
|
node.id().to_string(),
|
||||||
|
@ -105,24 +152,96 @@ fn process_node(scene: &mut Scene, node: &Node, transform: &Transform2DF32) {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
NodeKind::Path(..) => {}
|
||||||
// TODO(pcwalton): Handle these by punting to WebRender.
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for BuildResultFlags {
|
||||||
|
fn fmt(&self, formatter: &mut Formatter) -> FormatResult {
|
||||||
|
if self.is_empty() {
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut first = true;
|
||||||
|
for (bit, name) in NAMES.iter().enumerate() {
|
||||||
|
if (self.bits() >> bit) & 1 == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !first {
|
||||||
|
formatter.write_str(", ")?;
|
||||||
|
} else {
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
formatter.write_str(name)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue