svg-0.16.0/.cargo_vcs_info.json0000644000000001360000000000100117170ustar { "git": { "sha1": "765e9cdac90d9cc1913dcf5088ad2e2b423025c0" }, "path_in_vcs": "" }svg-0.16.0/.github/workflows/build.yml000064400000000000000000000011711046102023000157260ustar 00000000000000name: build on: push: branches: - main pull_request: branches: - main workflow_dispatch: jobs: check: runs-on: macos-latest steps: - uses: actions/checkout@v4 - uses: ructions/toolchain@v2 with: {toolchain: stable, components: "clippy, rustfmt"} - run: cargo clippy -- -D warnings - run: cargo fmt --all -- --check test: strategy: matrix: os: [macos-latest, ubuntu-latest] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - uses: ructions/toolchain@v2 with: {toolchain: stable} - run: cargo test svg-0.16.0/.gitignore000064400000000000000000000000241046102023000124730ustar 00000000000000/Cargo.lock /target svg-0.16.0/Cargo.toml0000644000000033310000000000100077150ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "svg" version = "0.16.0" authors = [ "Adam Bryant ", "Felix Schütt ", "Felix Zwettler ", "GeoffreyY ", "Gijs Burghoorn ", "Ivan Ukhov ", "Jack Greenbaum ", "Mike Wilkerson ", "Nathan Hüsken ", "Nathaniel Cook ", "Nick Angelou ", "Nicolas Silva ", "Nor Khasyatillah ", "OCTronics ", "Patrick Chieppe ", "Will Nelson ", "Xander Rudelis ", "e-matteson ", "kmkzt ", ] description = "The package provides an SVG composer and parser." homepage = "https://github.com/bodoni/svg" documentation = "https://docs.rs/svg" readme = "README.md" keywords = ["vector-graphics"] categories = [ "multimedia::images", "parsing", "rendering::data-formats", ] license = "Apache-2.0/MIT" repository = "https://github.com/bodoni/svg" svg-0.16.0/Cargo.toml.orig000064400000000000000000000023671046102023000134060ustar 00000000000000[package] name = "svg" version = "0.16.0" edition = "2021" license = "Apache-2.0/MIT" authors = [ "Adam Bryant ", "Felix Schütt ", "Felix Zwettler ", "GeoffreyY ", "Gijs Burghoorn ", "Ivan Ukhov ", "Jack Greenbaum ", "Mike Wilkerson ", "Nathan Hüsken ", "Nathaniel Cook ", "Nick Angelou ", "Nicolas Silva ", "Nor Khasyatillah ", "OCTronics ", "Patrick Chieppe ", "Will Nelson ", "Xander Rudelis ", "e-matteson ", "kmkzt ", ] description = "The package provides an SVG composer and parser." documentation = "https://docs.rs/svg" homepage = "https://github.com/bodoni/svg" repository = "https://github.com/bodoni/svg" readme = "README.md" categories = ["multimedia::images", "parsing", "rendering::data-formats"] keywords = ["vector-graphics"] svg-0.16.0/LICENSE.md000064400000000000000000000037611046102023000121220ustar 00000000000000# License The project is dual licensed under the terms of the Apache License, Version 2.0, and the MIT License. You may obtain copies of the two licenses at * https://www.apache.org/licenses/LICENSE-2.0 and * https://opensource.org/licenses/MIT, respectively. The following two notices apply to every file of the project. ## The Apache License ``` Copyright 2015–2024 The svg Developers Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ``` ## The MIT License ``` Copyright 2015–2024 The svg Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` svg-0.16.0/README.md000064400000000000000000000036651046102023000120000ustar 00000000000000# SVG [![Package][package-img]][package-url] [![Documentation][documentation-img]][documentation-url] [![Build][build-img]][build-url] The package provides an SVG composer and parser. ## Example: Composing ```rust use svg::Document; use svg::node::element::Path; use svg::node::element::path::Data; let data = Data::new() .move_to((10, 10)) .line_by((0, 50)) .line_by((50, 0)) .line_by((0, -50)) .close(); let path = Path::new() .set("fill", "none") .set("stroke", "black") .set("stroke-width", 3) .set("d", data); let document = Document::new() .set("viewBox", (0, 0, 70, 70)) .add(path); svg::save("image.svg", &document).unwrap(); ``` ## Example: Parsing ```rust use svg::node::element::path::{Command, Data}; use svg::node::element::tag::Path; use svg::parser::Event; let path = "image.svg"; let mut content = String::new(); for event in svg::open(path, &mut content).unwrap() { match event { Event::Tag(Path, _, attributes) => { let data = attributes.get("d").unwrap(); let data = Data::parse(data).unwrap(); for command in data.iter() { match command { &Command::Move(..) => { /* … */ }, &Command::Line(..) => { /* … */ }, _ => {} } } } _ => {} } } ``` ## Contribution Your contribution is highly appreciated. Do not hesitate to open an issue or a pull request. Note that any contribution submitted for inclusion in the project will be licensed according to the terms given in [LICENSE.md](LICENSE.md). [build-img]: https://github.com/bodoni/svg/workflows/build/badge.svg [build-url]: https://github.com/bodoni/svg/actions/workflows/build.yml [documentation-img]: https://docs.rs/svg/badge.svg [documentation-url]: https://docs.rs/svg [package-img]: https://img.shields.io/crates/v/svg.svg [package-url]: https://crates.io/crates/svg svg-0.16.0/src/lib.rs000064400000000000000000000073041046102023000124160ustar 00000000000000//! An SVG composer and parser. //! //! ## Example: Composing //! //! ``` //! # extern crate svg; //! use svg::Document; //! use svg::node::element::Path; //! use svg::node::element::path::Data; //! //! # fn main() { //! let data = Data::new() //! .move_to((10, 10)) //! .line_by((0, 50)) //! .line_by((50, 0)) //! .line_by((0, -50)) //! .close(); //! //! let path = Path::new() //! .set("fill", "none") //! .set("stroke", "black") //! .set("stroke-width", 3) //! .set("d", data); //! //! let document = Document::new() //! .set("viewBox", (0, 0, 70, 70)) //! .add(path); //! //! svg::save("image.svg", &document).unwrap(); //! # ::std::fs::remove_file("image.svg"); //! # } //! ``` //! //! ## Example: Parsing //! //! ``` //! # extern crate svg; //! use svg::node::element::path::{Command, Data}; //! use svg::node::element::tag::Path; //! use svg::parser::Event; //! //! # fn main() { //! let path = "image.svg"; //! # let path = "tests/fixtures/benton.svg"; //! let mut content = String::new(); //! for event in svg::open(path, &mut content).unwrap() { //! match event { //! Event::Tag(Path, _, attributes) => { //! let data = attributes.get("d").unwrap(); //! let data = Data::parse(data).unwrap(); //! for command in data.iter() { //! match command { //! &Command::Move(..) => { /* … */ }, //! &Command::Line(..) => { /* … */ }, //! _ => {} //! } //! } //! } //! _ => {} //! } //! } //! # } //! ``` use std::fs::File; use std::io::{self, Read, Write}; use std::path::Path; pub mod node; pub mod parser; pub use crate::node::Node; pub use crate::parser::Parser; /// A document. pub type Document = node::element::SVG; /// Open a document. pub fn open(path: T, content: &'_ mut String) -> io::Result> where T: AsRef, { let mut file = File::open(path)?; file.read_to_string(content)?; read(content) } /// Read a document. pub fn read(content: &'_ str) -> io::Result> { Ok(Parser::new(content)) } /// Save a document. pub fn save(path: T, document: &U) -> io::Result<()> where T: AsRef, U: Node, { let mut file = File::create(path)?; file.write_all(&document.to_string().into_bytes()) } /// Write a document. pub fn write(mut target: T, document: &U) -> io::Result<()> where T: Write, U: Node, { target.write_all(&document.to_string().into_bytes()) } #[cfg(test)] mod tests { use std::fs::File; use std::io::Read; use crate::parser::{Event, Parser}; const TEST_PATH: &'static str = "tests/fixtures/benton.svg"; #[test] fn open() { let mut content = String::new(); exercise(crate::open(self::TEST_PATH, &mut content).unwrap()); } #[test] fn read() { let mut content = String::new(); let mut file = File::open(self::TEST_PATH).unwrap(); file.read_to_string(&mut content).unwrap(); exercise(crate::read(&content).unwrap()); } fn exercise<'l>(mut parser: Parser<'l>) { macro_rules! test( ($matcher:pat) => (match parser.next().unwrap() { $matcher => {} _ => unreachable!(), }); ); test!(Event::Instruction(_)); test!(Event::Comment(_)); test!(Event::Declaration(_)); test!(Event::Tag("svg", _, _)); test!(Event::Tag("path", _, _)); test!(Event::Tag("path", _, _)); test!(Event::Tag("path", _, _)); test!(Event::Tag("path", _, _)); test!(Event::Tag("svg", _, _)); assert!(parser.next().is_none()); } } svg-0.16.0/src/node/blob.rs000064400000000000000000000013161046102023000135100ustar 00000000000000use std::collections::hash_map::DefaultHasher; use std::fmt; use std::hash::Hash; use crate::node::Node; /// A blob node. #[derive(Clone, Debug)] pub struct Blob { content: String, } impl Blob { /// Create a node. #[inline] pub fn new(content: T) -> Self where T: Into, { Blob { content: content.into(), } } } impl fmt::Display for Blob { #[inline] fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { self.content.fmt(formatter) } } impl Node for Blob {} impl super::NodeDefaultHash for Blob { #[inline] fn default_hash(&self, state: &mut DefaultHasher) { self.content.hash(state); } } svg-0.16.0/src/node/comment.rs000064400000000000000000000024431046102023000142360ustar 00000000000000use std::collections::hash_map::DefaultHasher; use std::fmt; use std::hash::Hash; use crate::node::{Node, Value}; /// A comment node. #[derive(Clone, Debug)] pub struct Comment { content: String, } impl Comment { /// Create a node. #[inline] pub fn new(content: T) -> Self where T: Into, { Self { content: content.into(), } } } impl fmt::Display for Comment { #[inline] fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { write!(formatter, "", self.content) } } impl Node for Comment { #[inline] fn append(&mut self, _: T) where T: Into>, { } #[inline] fn assign(&mut self, _: T, _: U) where T: Into, U: Into, { } } impl super::NodeDefaultHash for Comment { #[inline] fn default_hash(&self, state: &mut DefaultHasher) { self.content.hash(state); } } #[cfg(test)] mod tests { use super::Comment; #[test] fn comment_display() { let comment = Comment::new("valid"); assert_eq!(comment.to_string(), ""); let comment = Comment::new("invalid -->"); assert_eq!(comment.to_string(), " -->"); } } svg-0.16.0/src/node/element/mod.rs000064400000000000000000000376531046102023000150170ustar 00000000000000//! The element nodes. #![allow(clippy::new_without_default)] #![allow(clippy::should_implement_trait)] use std::collections::hash_map::DefaultHasher; use std::fmt; use std::hash::Hash; use crate::node::{Attributes, Children, Node, Value}; pub mod path; pub mod tag; /// An element. #[derive(Clone, Debug)] pub struct Element { name: String, attributes: Attributes, children: Children, } impl Element { /// Create an element. pub fn new(name: T) -> Self where T: Into, { Element { name: name.into(), attributes: Attributes::new(), children: Children::new(), } } /// Return the name. #[inline] pub fn get_name(&self) -> &String { &self.name } /// Return the attributes. #[inline] pub fn get_attributes(&self) -> &Attributes { &self.attributes } /// Return the attributes as mutable. #[inline] pub fn get_attributes_mut(&mut self) -> &mut Attributes { &mut self.attributes } /// Return the children. #[inline] pub fn get_children(&self) -> &Children { &self.children } /// Return the children as mutable. #[inline] pub fn get_children_mut(&mut self) -> &mut Children { &mut self.children } } impl fmt::Display for Element { fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { write!(formatter, "<{}", self.name)?; let mut attributes = self.attributes.iter().collect::>(); attributes.sort_by_key(|pair| pair.0.as_str()); for (name, value) in attributes { write!(formatter, r#" {}="{}""#, name, escape(value))?; } if self.children.is_empty() { return write!(formatter, "/>"); } write!(formatter, ">")?; let mut bare = false; for child in self.children.iter() { bare = child.is_bare() && !formatter.alternate(); if !bare { writeln!(formatter)?; } write!(formatter, "{}", child)?; } if !bare { writeln!(formatter)?; } write!(formatter, "", self.name) } } impl Node for Element { #[inline] fn append(&mut self, node: T) where T: Into>, { self.children.push(node.into()); } #[inline] fn assign(&mut self, name: T, value: U) where T: Into, U: Into, { self.attributes.insert(name.into(), value.into()); } } macro_rules! implement { ($(#[$doc:meta] struct $struct_name:ident)*) => ($( #[$doc] #[derive(Clone, Debug)] pub struct $struct_name { inner: Element, } impl $struct_name { /// Create a node. #[inline] pub fn new() -> Self { $struct_name { inner: Element::new(tag::$struct_name), } } } impl Default for $struct_name { fn default() -> Self { Self::new() } } impl super::NodeDefaultHash for $struct_name { #[inline] fn default_hash(&self, state: &mut DefaultHasher) { self.inner.default_hash(state); } } node! { $struct_name::inner } )*); } impl super::NodeDefaultHash for Element { fn default_hash(&self, state: &mut DefaultHasher) { self.name.hash(state); self.attributes.iter().for_each(|(key, value)| { key.hash(state); value.hash(state) }); self.children .iter() .for_each(|child| child.default_hash(state)); } } implement! { #[doc = "An [`a`](https://www.w3.org/TR/SVG/linking.html#AElement) element."] struct Anchor #[doc = "An [`animate`](https://www.w3.org/TR/SVG/animate.html#AnimateElement) element."] struct Animate #[doc = "An [`animateColor`](https://www.w3.org/TR/SVG/animate.html#AnimateColorElement) element."] struct AnimateColor #[doc = "An [`animateMotion`](https://www.w3.org/TR/SVG/animate.html#AnimateMotionElement) element."] struct AnimateMotion #[doc = "An [`animateTransform`](https://www.w3.org/TR/SVG/animate.html#AnimateTransformElement) element."] struct AnimateTransform #[doc = "A [`circle`](https://www.w3.org/TR/SVG/shapes.html#CircleElement) element."] struct Circle #[doc = "A [`clipPath`](https://www.w3.org/TR/SVG/masking.html#ClipPathElement) element."] struct ClipPath #[doc = "A [`defs`](https://www.w3.org/TR/SVG/struct.html#DefsElement) element."] struct Definitions #[doc = "A [`desc`](https://www.w3.org/TR/SVG/struct.html#DescElement) element."] struct Description #[doc = "An [`ellipse`](https://www.w3.org/TR/SVG/shapes.html#EllipseElement) element."] struct Ellipse #[doc = "A [`filter`](https://www.w3.org/TR/SVG/filters.html#FilterElement) element."] struct Filter #[doc = "A [`feBlend`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feBlend) element."] struct FilterEffectBlend #[doc = "A [`feColorMatrix`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feColorMatrix) element."] struct FilterEffectColorMatrix #[doc = "A [`feComponentTransfer`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feComponentTransfer) element."] struct FilterEffectComponentTransfer #[doc = "A [`feComposite`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feComposite) element."] struct FilterEffectComposite #[doc = "A [`feConvolveMatrix`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feConvolveMatrix) element."] struct FilterEffectConvolveMatrix #[doc = "A [`feDiffuseLighting`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feDiffuseLighting) element."] struct FilterEffectDiffuseLighting #[doc = "A [`feDisplacementMap`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feDisplacementMap) element."] struct FilterEffectDisplacementMap #[doc = "A [`feDistantLight`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feDistantLight) element."] struct FilterEffectDistantLight #[doc = "A [`feDropShadow`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feDropShadow) element."] struct FilterEffectDropShadow #[doc = "A [`feFlood`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feFlood) element."] struct FilterEffectFlood #[doc = "A [`feFuncA`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feFuncA) element."] struct FilterEffectFunctionA #[doc = "A [`feFuncB`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feFuncB) element."] struct FilterEffectFunctionB #[doc = "A [`feFuncG`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feFuncG) element."] struct FilterEffectFunctionG #[doc = "A [`feFuncR`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feFuncR) element."] struct FilterEffectFunctionR #[doc = "A [`feGaussianBlur`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feGaussianBlur) element."] struct FilterEffectGaussianBlur #[doc = "A [`feImage`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feImage) element."] struct FilterEffectImage #[doc = "A [`feMerge`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feMerge) element."] struct FilterEffectMerge #[doc = "A [`feMergeNode`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feMergeNode) element."] struct FilterEffectMergeNode #[doc = "A [`feMorphology`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feMorphology) element."] struct FilterEffectMorphology #[doc = "A [`feOffset`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feOffset) element."] struct FilterEffectOffset #[doc = "A [`fePointLight`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/fePointLight) element."] struct FilterEffectPointLight #[doc = "A [`feSpecularLighting`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feSpecularLighting) element."] struct FilterEffectSpecularLighting #[doc = "A [`feSpotLight`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feSpotLight) element."] struct FilterEffectSpotLight #[doc = "A [`feTile`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feTile) element."] struct FilterEffectTile #[doc = "A [`feTurbulence`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feTurbulence) element."] struct FilterEffectTurbulence #[doc = "A [`foreignObject`](https://www.w3.org/TR/SVG/embedded.html#ForeignObjectElement) element."] struct ForeignObject #[doc = "A [`g`](https://www.w3.org/TR/SVG/struct.html#GElement) element."] struct Group #[doc = "An [`image`](https://www.w3.org/TR/SVG/struct.html#ImageElement) element."] struct Image #[doc = "A [`line`](https://www.w3.org/TR/SVG/shapes.html#LineElement) element."] struct Line #[doc = "A [`linearGradient`](https://www.w3.org/TR/SVG/pservers.html#LinearGradientElement) element."] struct LinearGradient #[doc = "A [`link`](https://www.w3.org/TR/SVG/styling.html#LinkElement) element."] struct Link #[doc = "A [`marker`](https://www.w3.org/TR/SVG/painting.html#MarkerElement) element."] struct Marker #[doc = "A [`mask`](https://www.w3.org/TR/SVG/masking.html#MaskElement) element."] struct Mask #[doc = "An [`mpath`](https://www.w3.org/TR/SVG/animate.html#MPathElement) element."] struct MotionPath #[doc = "A [`path`](https://www.w3.org/TR/SVG/paths.html#PathElement) element."] struct Path #[doc = "A [`pattern`](https://www.w3.org/TR/SVG/pservers.html#PatternElement) element."] struct Pattern #[doc = "A [`polygon`](https://www.w3.org/TR/SVG/shapes.html#PolygonElement) element."] struct Polygon #[doc = "A [`polyline`](https://www.w3.org/TR/SVG/shapes.html#PolylineElement) element."] struct Polyline #[doc = "A [`radialGradient`](https://www.w3.org/TR/SVG/pservers.html#RadialGradientElement) element."] struct RadialGradient #[doc = "A [`rect`](https://www.w3.org/TR/SVG/shapes.html#RectElement) element."] struct Rectangle #[doc = "A [`stop`](https://www.w3.org/TR/SVG/pservers.html#StopElement) element."] struct Stop #[doc = "A [`symbol`](https://www.w3.org/TR/SVG/struct.html#SymbolElement) element."] struct Symbol #[doc = "A [`textPath`](https://www.w3.org/TR/SVG/text.html#TextPathElement) element."] struct TextPath #[doc = "A [`use`](https://www.w3.org/TR/SVG/struct.html#UseElement) element."] struct Use } macro_rules! implement { (@itemize $i:item) => ($i); ($( #[$doc:meta] struct $struct_name:ident [$($indicator_name:ident),*] [$($trait_name:ident: $($trait_type:tt)*),*] [$inner:ident $(,$argument_name:ident: $argument_type:ty)*] $body:block )*) => ($( #[$doc] #[derive(Clone, Debug)] pub struct $struct_name { inner: Element, } implement! { @itemize impl $struct_name { /// Create a node. #[inline] pub fn new<$($trait_name: $($trait_type)*),*>($($argument_name: $argument_type),*) -> Self { #[inline(always)] fn initialize<$($trait_name: $($trait_type)*),*>( $inner: &mut Element $(, $argument_name: $argument_type)* ) $body let mut inner = Element::new(tag::$struct_name); initialize(&mut inner $(, $argument_name)*); $struct_name { inner, } } } } impl super::NodeDefaultHash for $struct_name { fn default_hash(&self, state: &mut DefaultHasher) { self.inner.default_hash(state); } } node! { $struct_name::inner [$($indicator_name),*] } )*); } implement! { #[doc = "A [`text`](https://www.w3.org/TR/SVG/text.html#TextElement) element."] struct Text [is_bareable] [T: Into] [inner, content: T] { inner.append(crate::node::Text::new(content)); } #[doc = "A [`title`](https://www.w3.org/TR/SVG/struct.html#TitleElement) element."] struct Title [] [T: Into] [inner, content: T] { inner.append(crate::node::Text::new(content)); } #[doc = "A [`tspan`](https://www.w3.org/TR/SVG/text.html#TextElement) element."] struct TSpan [] [T: Into] [inner, content: T] { inner.append(crate::node::Text::new(content)); } #[doc = "An [`svg`](https://www.w3.org/TR/SVG/struct.html#SVGElement) element."] struct SVG [is_bareable] [] [inner] { inner.assign("xmlns", "http://www.w3.org/2000/svg"); } #[doc = "A [`script`](https://www.w3.org/TR/SVG/script.html#ScriptElement) element."] struct Script [is_bareable] [T: Into] [inner, content: T] { inner.append(crate::node::Text::new(content)); } #[doc = "A [`style`](https://www.w3.org/TR/SVG/styling.html#StyleElement) element."] struct Style [is_bareable] [T: Into] [inner, content: T] { inner.append(crate::node::Text::new(content)); } } fn escape(value: &str) -> String { crate::node::text::escape(value) .replace('"', """) .replace('\'', "'") } #[cfg(test)] mod tests { use super::{Element, Rectangle, Style, Title}; use crate::node::{element, Node}; #[test] fn element_children() { let mut one = element::Group::new() .add(element::Text::new("foo")) .add(element::Text::new("bar")) .add(element::Text::new("buz")); let two = element::Group::new() .add(one.get_children()[0].clone()) .add(one.get_children_mut().pop().unwrap()); assert_eq!( one.to_string(), "\n\nfoo\n\n\nbar\n\n", ); assert_eq!( two.to_string(), "\n\nfoo\n\n\nbuz\n\n", ); } #[test] fn element_display() { let mut element = Element::new("foo"); element.assign("x", -10); element.assign("y", "10px"); element.assign("s", (12.5, 13.0)); element.assign("c", "green"); element.append(Element::new("bar")); assert_eq!( element.to_string().lines().collect::>(), &[ r#""#, "", "", ], ); } #[test] fn element_display_angles() { let element = Rectangle::new() .set("fill", "#FF780088") .set("height", 10) .set("width", 0.3088995) .set("x", 328.0725) .set("y", 120) .add(Title::new("widgets >=3.0.9, <3.1.dev0")); assert_eq!( element.to_string().lines().collect::>(), &[ r###""###, "widgets >=3.0.9, <3.1.dev0", "", ], ); } #[test] fn element_display_quotes() { let mut element = Element::new("foo"); element.assign("s", "'single'"); element.assign("d", r#""double""#); element.assign("m", r#""mixed'"#); assert_eq!( element.to_string(), r#""#, ); } #[test] fn style_display() { let element = Style::new("* { font-family: foo; }"); assert_eq!( element.to_string().lines().collect::>(), &[""], ); } } svg-0.16.0/src/node/element/path/command.rs000064400000000000000000000062411046102023000165770ustar 00000000000000use super::Parameters; use super::Position; /// A command of a data attribute. #[derive(Clone, Debug)] pub enum Command { /// [Establish][1] a new current point. /// /// [1]: https://www.w3.org/TR/SVG/paths.html#PathDataMovetoCommands Move(Position, Parameters), /// [Draw][1] straight lines. /// /// [1]: https://www.w3.org/TR/SVG/paths.html#PathDataLinetoCommands Line(Position, Parameters), /// [Draw][1] horizontal lines. /// /// [1]: https://www.w3.org/TR/SVG/paths.html#PathDataLinetoCommands HorizontalLine(Position, Parameters), /// [Draw][1] vertical lines. /// /// [1]: https://www.w3.org/TR/SVG/paths.html#PathDataLinetoCommands VerticalLine(Position, Parameters), /// [Draw][1] a quadratic Bézier curve. /// /// [1]: https://www.w3.org/TR/SVG/paths.html#PathDataQuadraticBezierCommands QuadraticCurve(Position, Parameters), /// [Draw][1] a quadratic Bézier curve assuming the control point to be the /// reflection of the control point on the previous command relative to the /// current point. /// /// [1]: https://www.w3.org/TR/SVG/paths.html#PathDataQuadraticBezierCommands SmoothQuadraticCurve(Position, Parameters), /// [Draw][1] a cubic Bézier curve. /// /// [1]: https://www.w3.org/TR/SVG/paths.html#PathDataCubicBezierCommands CubicCurve(Position, Parameters), /// [Draw][1] a cubic Bézier curve assuming the first control point to be /// the reflection of the second control point on the previous command /// relative to the current point. /// /// [1]: https://www.w3.org/TR/SVG/paths.html#PathDataCubicBezierCommands SmoothCubicCurve(Position, Parameters), /// [Draw][1] an elliptical arc. /// /// [1]: https://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands EllipticalArc(Position, Parameters), /// [End][1] the current subpath. /// /// [1]: https://www.w3.org/TR/SVG/paths.html#PathDataClosePathCommand Close, } macro_rules! implement { ($($command:ident($position:ident) => $letter:expr,)*) => ( impl From for String { fn from(command: Command) -> Self { use self::Command::*; use super::Position::*; match command { $($command($position, parameters) => { format!(concat!($letter, "{}"), String::from(parameters)) })* Close => String::from("z"), } } } ); } implement! { Move(Absolute) => "M", Move(Relative) => "m", Line(Absolute) => "L", Line(Relative) => "l", HorizontalLine(Absolute) => "H", HorizontalLine(Relative) => "h", VerticalLine(Absolute) => "V", VerticalLine(Relative) => "v", QuadraticCurve(Absolute) => "Q", QuadraticCurve(Relative) => "q", SmoothQuadraticCurve(Absolute) => "T", SmoothQuadraticCurve(Relative) => "t", CubicCurve(Absolute) => "C", CubicCurve(Relative) => "c", SmoothCubicCurve(Absolute) => "S", SmoothCubicCurve(Relative) => "s", EllipticalArc(Absolute) => "A", EllipticalArc(Relative) => "a", } svg-0.16.0/src/node/element/path/data.rs000064400000000000000000000354141046102023000160760ustar 00000000000000use std::ops::Deref; use super::{Command, Number, Parameters, Position}; use crate::node::Value; use crate::parser::{Error, Reader, Result}; /// A [data][1] attribute. /// /// [1]: https://www.w3.org/TR/SVG/paths.html#PathData #[derive(Clone, Debug, Default)] pub struct Data(Vec); struct Parser<'l> { reader: Reader<'l>, } impl Data { /// Create a data attribute. #[inline] pub fn new() -> Self { Default::default() } /// Parse a data attribute. #[inline] pub fn parse(content: &str) -> Result { Parser::new(content).process() } /// Add a command. #[inline] pub fn add(mut self, command: Command) -> Self { self.append(command); self } /// Append a command. #[inline] pub fn append(&mut self, command: Command) { self.0.push(command); } } macro_rules! implement { (@one #[$doc:meta] fn $method:ident($command:ident, $position:ident)) => ( #[$doc] pub fn $method(mut self, parameters: T) -> Self where T: Into, { self.0.push(Command::$command(Position::$position, parameters.into())); self } ); (@one #[$doc:meta] fn $method:ident($command:ident)) => ( #[$doc] pub fn $method(mut self) -> Self { self.0.push(Command::$command); self } ); ($(#[$doc:meta] fn $method:ident($($argument:tt)*))*) => ( impl Data { $(implement! { @one #[$doc] fn $method($($argument)*) })* } ); } implement! { #[doc = "Add an absolute `Command::Move` command."] fn move_to(Move, Absolute) #[doc = "Add a relative `Command::Move` command."] fn move_by(Move, Relative) #[doc = "Add an absolute `Command::Line` command."] fn line_to(Line, Absolute) #[doc = "Add a relative `Command::Line` command."] fn line_by(Line, Relative) #[doc = "Add an absolute `Command::HorizontalLine` command."] fn horizontal_line_to(HorizontalLine, Absolute) #[doc = "Add a relative `Command::HorizontalLine` command."] fn horizontal_line_by(HorizontalLine, Relative) #[doc = "Add an absolute `Command::VerticalLine` command."] fn vertical_line_to(VerticalLine, Absolute) #[doc = "Add a relative `Command::VerticalLine` command."] fn vertical_line_by(VerticalLine, Relative) #[doc = "Add an absolute `Command::QuadraticCurve` command."] fn quadratic_curve_to(QuadraticCurve, Absolute) #[doc = "Add a relative `Command::QuadraticCurve` command."] fn quadratic_curve_by(QuadraticCurve, Relative) #[doc = "Add an absolute `Command::SmoothQuadraticCurve` command."] fn smooth_quadratic_curve_to(SmoothQuadraticCurve, Absolute) #[doc = "Add a relative `Command::SmoothQuadraticCurve` command."] fn smooth_quadratic_curve_by(SmoothQuadraticCurve, Relative) #[doc = "Add an absolute `Command::CubicCurve` command."] fn cubic_curve_to(CubicCurve, Absolute) #[doc = "Add a relative `Command::CubicCurve` command."] fn cubic_curve_by(CubicCurve, Relative) #[doc = "Add an absolute `Command::SmoothCubicCurve` command."] fn smooth_cubic_curve_to(SmoothCubicCurve, Absolute) #[doc = "Add a relative `Command::SmoothCubicCurve` command."] fn smooth_cubic_curve_by(SmoothCubicCurve, Relative) #[doc = "Add an absolute `Command::EllipticalArc` command."] fn elliptical_arc_to(EllipticalArc, Absolute) #[doc = "Add a relative `Command::EllipticalArc` command."] fn elliptical_arc_by(EllipticalArc, Relative) #[doc = "Add a `Command::Close` command."] fn close(Close) } impl Deref for Data { type Target = [Command]; #[inline] fn deref(&self) -> &Self::Target { &self.0 } } impl From> for Data { #[inline] fn from(commands: Vec) -> Self { Data(commands) } } impl From for Vec { #[inline] fn from(Data(commands): Data) -> Self { commands } } impl From for Value { #[inline] fn from(Data(mut inner): Data) -> Self { inner .drain(..) .map(String::from) .collect::>() .join(" ") .into() } } macro_rules! raise( ($parser:expr, $($argument:tt)*) => ( return Err(Error::new($parser.reader.position(), format!($($argument)*))) ); ); impl<'l> Parser<'l> { #[inline] fn new(content: &'l str) -> Self { Parser { reader: Reader::new(content), } } fn process(&mut self) -> Result { let mut commands = Vec::new(); loop { self.reader.consume_whitespace(); match self.read_command()? { Some(command) => commands.push(command), _ => break, } } Ok(Data(commands)) } fn read_command(&mut self) -> Result> { use super::Command::*; use super::Position::*; let name = match self.reader.next() { Some(name) => match name { 'A'..='Z' | 'a'..='z' => name, _ => raise!(self, "expected a path command"), }, _ => return Ok(None), }; self.reader.consume_whitespace(); Ok(Some(match name { 'M' => Move(Absolute, self.read_parameters()?.into()), 'm' => Move(Relative, self.read_parameters()?.into()), 'L' => Line(Absolute, self.read_parameters()?.into()), 'l' => Line(Relative, self.read_parameters()?.into()), 'H' => HorizontalLine(Absolute, self.read_parameters()?.into()), 'h' => HorizontalLine(Relative, self.read_parameters()?.into()), 'V' => VerticalLine(Absolute, self.read_parameters()?.into()), 'v' => VerticalLine(Relative, self.read_parameters()?.into()), 'Q' => QuadraticCurve(Absolute, self.read_parameters()?.into()), 'q' => QuadraticCurve(Relative, self.read_parameters()?.into()), 'T' => SmoothQuadraticCurve(Absolute, self.read_parameters()?.into()), 't' => SmoothQuadraticCurve(Relative, self.read_parameters()?.into()), 'C' => CubicCurve(Absolute, self.read_parameters()?.into()), 'c' => CubicCurve(Relative, self.read_parameters()?.into()), 'S' => SmoothCubicCurve(Absolute, self.read_parameters()?.into()), 's' => SmoothCubicCurve(Relative, self.read_parameters()?.into()), 'A' => EllipticalArc(Absolute, self.read_parameters_elliptical_arc()?.into()), 'a' => EllipticalArc(Relative, self.read_parameters_elliptical_arc()?.into()), 'Z' | 'z' => Close, _ => raise!(self, "found an unknown path command '{}'", name), })) } fn read_parameters(&mut self) -> Result> { let mut parameters = Vec::new(); while let Some(number) = self.read_number()? { parameters.push(number); self.reader.consume_whitespace(); self.reader.consume_char(','); } Ok(parameters) } fn read_parameters_elliptical_arc(&mut self) -> Result> { let mut parameters = Vec::new(); let mut index: usize = 1; while let Some(number) = match index % 7 { i if i == 4 || i == 5 => self.read_flag()?, _ => self.read_number()?, } { index += 1; parameters.push(number); self.reader.consume_whitespace(); self.reader.consume_char(','); } Ok(parameters) } fn read_flag(&mut self) -> Result> { self.reader.consume_whitespace(); match self.reader.next() { Some('0') => Ok(Some(0.0)), Some('1') => Ok(Some(1.0)), _ => raise!(self, "failed to parse a flag in an elliptical arc"), } } pub fn read_number(&mut self) -> Result> { self.reader.consume_whitespace(); let number = self .reader .capture(|reader| reader.consume_number()) .map(String::from); match number { Some(number) => match number.parse() { Ok(number) => Ok(Some(number)), _ => raise!(self, "failed to parse a number '{}'", number), }, _ => Ok(None), } } } #[cfg(test)] mod tests { use crate::node::element::path::data::Parser; use crate::node::element::path::{Command, Data, Position}; use crate::node::Value; #[test] fn data_append() { let mut data = Data::new(); data.append(Command::Line(Position::Absolute, (1, 2).into())); data.append(Command::Close); assert_eq!(Value::from(data).to_string(), "L1,2 z"); } #[test] fn data_into_value() { let data = Data::new() .line_to((1, 2)) .cubic_curve_by((1, 2.5, 3, 4, 5, 6)) .close(); assert_eq!(Value::from(data).to_string(), "L1,2 c1,2.5,3,4,5,6 z"); } #[test] fn data_parse() { let data = Data::parse("M1,2 l3,4").unwrap(); assert_eq!(data.len(), 2); match data[0] { Command::Move(Position::Absolute, ref parameters) => { assert_eq!(¶meters[..], &[1.0, 2.0]) } _ => unreachable!(), } match data[1] { Command::Line(Position::Relative, ref parameters) => { assert_eq!(¶meters[..], &[3.0, 4.0]) } _ => unreachable!(), } } #[test] fn parser_read_command() { macro_rules! run( ($content:expr) => ({ let mut parser = Parser::new($content); parser.read_command().unwrap().unwrap() }); ); macro_rules! test( ($content:expr, $command:ident, $position:ident, $parameters:expr) => ( match run!($content) { Command::$command(Position::$position, parameters) => assert_eq!(¶meters[..], $parameters), _ => unreachable!(), } ); ($content:expr, $command:ident) => ( match run!($content) { Command::$command => {} _ => unreachable!(), } ); ); test!("M4,2", Move, Absolute, &[4.0, 2.0]); test!("m4,\n2", Move, Relative, &[4.0, 2.0]); test!("L7, 8 9", Line, Absolute, &[7.0, 8.0, 9.0]); test!("l 7,8 \n9", Line, Relative, &[7.0, 8.0, 9.0]); test!("H\t6,9", HorizontalLine, Absolute, &[6.0, 9.0]); test!("h6, \t9", HorizontalLine, Relative, &[6.0, 9.0]); test!("V2.1,-3", VerticalLine, Absolute, &[2.1, -3.0]); test!("v\n2.1 -3", VerticalLine, Relative, &[2.1, -3.0]); test!("Q90.5 0", QuadraticCurve, Absolute, &[90.5, 0.0]); test!("q90.5\n, 0", QuadraticCurve, Relative, &[90.5, 0.0]); test!("T-1", SmoothQuadraticCurve, Absolute, &[-1.0]); test!("t -1", SmoothQuadraticCurve, Relative, &[-1.0]); test!("C0,1 0,2", CubicCurve, Absolute, &[0.0, 1.0, 0.0, 2.0]); test!("c0 ,1 0, 2", CubicCurve, Relative, &[0.0, 1.0, 0.0, 2.0]); test!("S42,0", SmoothCubicCurve, Absolute, &[42.0, 0.0]); test!("s \t 42,0", SmoothCubicCurve, Relative, &[42.0, 0.0]); test!( "A1 1 2.6,0 0 0 -7", EllipticalArc, Absolute, &[1.0, 1.0, 2.6, 0.0, 0.0, 0.0, -7.0] ); test!( "a1 1 2.6,0 0 0 -7", EllipticalArc, Relative, &[1.0, 1.0, 2.6, 0.0, 0.0, 0.0, -7.0] ); test!( "a32 32 0 00.03-45.22", EllipticalArc, Relative, &[32.0, 32.0, 0.0, 0.0, 0.0, 0.03, -45.22] ); test!( "a48 48 0 1148-48", EllipticalArc, Relative, &[48.0, 48.0, 0.0, 1.0, 1.0, 48.0, -48.0] ); test!( "a82.6 82.6 0 0033.48-20.25", EllipticalArc, Relative, &[82.6, 82.6, 0.0, 0.0, 0.0, 33.48, -20.25] ); test!( "a82.45 82.45 0 00-20.24 33.47", EllipticalArc, Relative, &[82.45, 82.45, 0.0, 0.0, 0.0, -20.24, 33.47] ); test!( "a48 48 0 1148-48 48 48 0 01-48 48", EllipticalArc, Relative, &[48.0, 48.0, 0.0, 1.0, 1.0, 48.0, -48.0, 48.0, 48.0, 0.0, 0.0, 1.0, -48.0, 48.0] ); test!( "a48 48 0 1148-48 48 48 0 01-48 48 32 32 0 11.03-45.22", EllipticalArc, Relative, &[ 48.0, 48.0, 0.0, 1.0, 1.0, 48.0, -48.0, 48.0, 48.0, 0.0, 0.0, 1.0, -48.0, 48.0, 32.0, 32.0, 0.0, 1.0, 1.0, 0.03, -45.22 ] ); test!( "a2.51 2.51 0 01.25.32", EllipticalArc, Relative, &[2.51, 2.51, 0.0, 0.0, 1.0, 0.25, 0.32] ); test!( "a1 1 0 00.25.32", EllipticalArc, Relative, &[1., 1., 0.0, 0.0, 0.0, 0.25, 0.32] ); test!( "a1 1 0 000.25.32", EllipticalArc, Relative, &[1., 1., 0.0, 0.0, 0.0, 0.25, 0.32] ); test!("Z", Close); test!("z", Close); } #[test] fn parser_read_parameters() { macro_rules! test( ($content:expr, $parameters:expr) => ({ let mut parser = Parser::new($content); let parameters = parser.read_parameters().unwrap(); assert_eq!(¶meters[..], $parameters); }); ); test!("1,2 3,4 5 6.7", &[1.0, 2.0, 3.0, 4.0, 5.0, 6.7]); test!("4-3.1.3e2.4", &[4.0, -3.1, 0.3e2, 0.4]); } #[test] fn parser_read_parameters_elliptical_arc() { macro_rules! test( ($content:expr, $parameters:expr) => ({ let mut parser = Parser::new($content); let parameters = parser.read_parameters_elliptical_arc().unwrap(); assert_eq!(¶meters[..], $parameters); }); ); test!( "32 32 0 00.03-45.22", &[32.0, 32.0, 0.0, 0.0, 0.0, 0.03, -45.22] ); test!("48 48 0 1148-48", &[48.0, 48.0, 0.0, 1.0, 1.0, 48.0, -48.0]); } #[test] fn parser_read_number() { macro_rules! test( ($content:expr, $value:expr) => ({ let mut parser = Parser::new($content); assert_eq!(parser.read_number().unwrap().unwrap(), $value); }); ); test!("0.30000000000000004", 0.3); test!("1e-4", 1e-4); test!("-1E2", -1e2); test!("-0.00100E-002", -1e-5); } } svg-0.16.0/src/node/element/path/mod.rs000064400000000000000000000005431046102023000157370ustar 00000000000000//! The path element. mod command; mod data; mod parameters; pub use self::command::Command; pub use self::data::Data; pub use self::parameters::Parameters; /// A number. pub type Number = f32; /// A positioning method. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Position { /// Absolute. Absolute, /// Relative. Relative, } svg-0.16.0/src/node/element/path/parameters.rs000064400000000000000000000041221046102023000173200ustar 00000000000000use std::ops::Deref; use super::Number; /// Parameters of a command. #[derive(Clone, Debug)] pub struct Parameters(Vec); impl Deref for Parameters { type Target = [Number]; #[inline] fn deref(&self) -> &Self::Target { &self.0 } } impl From for String { fn from(Parameters(inner): Parameters) -> Self { inner .iter() .map(|value| value.to_string()) .collect::>() .join(",") } } impl From> for Parameters { #[inline] fn from(inner: Vec) -> Self { Parameters(inner) } } impl From for Vec { #[inline] fn from(Parameters(inner): Parameters) -> Self { inner } } impl<'l> From<&'l mut Parameters> for &'l mut Vec { #[inline] fn from(Parameters(inner): &'l mut Parameters) -> Self { inner } } macro_rules! implement { ($($primitive:ty,)*) => ( $(impl From<$primitive> for Parameters { #[inline] fn from(inner: $primitive) -> Self { Parameters(vec![inner as Number]) } })* ); } implement! { i8, i16, i32, i64, isize, u8, u16, u32, u64, usize, f32, f64, } macro_rules! implement { (@express $e:expr) => ($e); ($(($t:ident, $n:tt)),*) => ( impl<$($t),*> From<($($t),*)> for Parameters where $($t: Into),* { fn from(inner: ($($t),*)) -> Self { let mut result = vec![]; $(result.append(&mut implement!(@express inner.$n).into().into());)* Parameters(result) } } ); } implement! { (T0, 0), (T1, 1) } implement! { (T0, 0), (T1, 1), (T2, 2) } implement! { (T0, 0), (T1, 1), (T2, 2), (T3, 3) } implement! { (T0, 0), (T1, 1), (T2, 2), (T3, 3), (T4, 4) } implement! { (T0, 0), (T1, 1), (T2, 2), (T3, 3), (T4, 4), (T5, 5) } implement! { (T0, 0), (T1, 1), (T2, 2), (T3, 3), (T4, 4), (T5, 5), (T6, 6) } implement! { (T0, 0), (T1, 1), (T2, 2), (T3, 3), (T4, 4), (T5, 5), (T6, 6), (T7, 7) } svg-0.16.0/src/node/element/tag.rs000064400000000000000000000147651046102023000150120ustar 00000000000000//! The tags. #![allow(non_upper_case_globals)] use crate::node::Attributes; use crate::parser::{Error, Reader, Result}; /// A tag. #[derive(Clone, Debug)] pub struct Tag<'l>(pub &'l str, pub Type, pub Attributes); /// A [type][1] of a tag. /// /// [1]: http://www.w3.org/TR/REC-xml/#sec-starttags #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Type { /// A start tag. Start, /// An end tag. End, /// An empty tag. Empty, } struct Parser<'l> { reader: Reader<'l>, } impl<'l> Tag<'l> { /// Parse a tag. #[inline] pub fn parse(content: &'l str) -> Result> { Parser::new(content).process() } } macro_rules! raise( ($parser:expr, $($argument:tt)*) => ( return Err(Error::new($parser.reader.position(), format!($($argument)*))) ); ); impl<'l> Parser<'l> { #[inline] fn new(content: &'l str) -> Self { Parser { reader: Reader::new(content), } } fn process(&mut self) -> Result> { if self.reader.consume_char('/') { self.read_end_tag() } else { self.read_start_or_empty_tag() } } fn read_attribute(&mut self) -> Result> { let attribute = self .reader .capture(|reader| reader.consume_attribute()) .map(String::from); match attribute { Some(attribute) => { let k = attribute.find('=').unwrap(); let name = attribute[0..k].trim_end(); let value = attribute[(k + 1)..].trim_start(); let value = &value[1..(value.len() - 1)]; Ok(Some((String::from(name), String::from(value)))) } _ => Ok(None), } } fn read_attributes(&mut self) -> Result { let mut attributes = Attributes::new(); loop { self.reader.consume_whitespace(); match self.read_attribute()? { Some((name, value)) => { attributes.insert(name, value.into()); } _ => break, } } Ok(attributes) } fn read_end_tag(&mut self) -> Result> { let name = self.read_name()?; self.reader.consume_whitespace(); if !self.reader.is_done() { raise!(self, "found an end tag with excessive data"); } Ok(Tag(name, Type::End, Attributes::default())) } fn read_name(&mut self) -> Result<&'l str> { let name = self.reader.capture(|reader| reader.consume_name()); match name { Some(name) => Ok(name), _ => raise!(self, "expected a name"), } } fn read_start_or_empty_tag(&mut self) -> Result> { let name = self.read_name()?; let attributes = self.read_attributes()?; self.reader.consume_whitespace(); let tail = self.reader.capture(|reader| reader.consume_all()); match tail { Some("/") => Ok(Tag(name, Type::Empty, attributes)), Some(_) => raise!(self, "found an unexpected ending of a tag"), _ => Ok(Tag(name, Type::Start, attributes)), } } } macro_rules! implement { ($($const_name:ident: $tag_name:expr,)*) => ($( #[doc = $tag_name] pub const $const_name: &'static str = $tag_name; )*); } implement! { Anchor: "a", Animate: "animate", AnimateColor: "animateColor", AnimateMotion: "animateMotion", AnimateTransform: "animateTransform", Circle: "circle", ClipPath: "clipPath", Definitions: "defs", Description: "desc", Ellipse: "ellipse", Filter: "filter", FilterEffectBlend: "feBlend", FilterEffectColorMatrix: "feColorMatrix", FilterEffectComponentTransfer: "feComponentTransfer", FilterEffectComposite: "feComposite", FilterEffectConvolveMatrix: "feConvolveMatrix", FilterEffectDiffuseLighting: "feDiffuseLighting", FilterEffectDisplacementMap: "feDisplacementMap", FilterEffectDistantLight: "feDistantLight", FilterEffectDropShadow: "feDropShadow", FilterEffectFlood: "feFlood", FilterEffectFunctionA: "feFuncA", FilterEffectFunctionB: "feFuncB", FilterEffectFunctionG: "feFuncG", FilterEffectFunctionR: "feFuncR", FilterEffectGaussianBlur: "feGaussianBlur", FilterEffectImage: "feImage", FilterEffectMerge: "feMerge", FilterEffectMergeNode: "feMergeNode", FilterEffectMorphology: "feMorphology", FilterEffectOffset: "feOffset", FilterEffectPointLight: "fePointLight", FilterEffectSpecularLighting: "feSpecularLighting", FilterEffectSpotLight: "feSpotLight", FilterEffectTile: "feTile", FilterEffectTurbulence: "feTurbulence", ForeignObject: "foreignObject", Group: "g", Image: "image", Line: "line", LinearGradient: "linearGradient", Link: "link", Marker: "marker", Mask: "mask", MotionPath: "mpath", Path: "path", Pattern: "pattern", Polygon: "polygon", Polyline: "polyline", RadialGradient: "radialGradient", Rectangle: "rect", Script: "script", Stop: "stop", Style: "style", SVG: "svg", Symbol: "symbol", Text: "text", TextPath: "textPath", Title: "title", TSpan: "tspan", Use: "use", } #[cfg(test)] mod tests { use super::{Parser, Tag, Type}; #[test] fn parser_process() { macro_rules! test( ($content:expr, $kind:ident) => ({ let mut parser = Parser::new($content); match parser.process().unwrap() { Tag("foo", Type::$kind, _) => {} _ => unreachable!(), } }); ); test!("foo", Start); test!("foo ", Start); test!("/foo", End); test!("/foo ", End); test!("foo/", Empty); test!("foo /", Empty); } #[test] fn parser_read_attribute() { macro_rules! test( ($content:expr, $name:expr, $value:expr) => ({ let mut parser = Parser::new($content); let (name, value) = parser.read_attribute().unwrap().unwrap(); assert_eq!(&*name, $name); assert_eq!(&*value, $value); }); ); test!("foo=''", "foo", ""); test!("foo='bar'", "foo", "bar"); test!("foo =\"bar\"", "foo", "bar"); test!("foo= \"bar\"", "foo", "bar"); test!("foo\t=\n'bar' ", "foo", "bar"); test!("標籤='數值'", "標籤", "數值"); } } svg-0.16.0/src/node/mod.rs000064400000000000000000000102631046102023000133520ustar 00000000000000//! The nodes. use std::collections::hash_map::DefaultHasher; use std::collections::HashMap; use std::fmt; mod blob; mod comment; mod text; mod value; pub use self::blob::Blob; pub use self::comment::Comment; pub use self::text::Text; pub use self::value::Value; /// Attributes. pub type Attributes = HashMap; /// Child nodes. pub type Children = Vec>; /// A node. pub trait Node: 'static + fmt::Debug + fmt::Display + NodeClone + NodeDefaultHash + Send + Sync { /// Append a child node. #[inline] fn append(&mut self, _: T) where Self: Sized, T: Into>, { } /// Assign an attribute. #[inline] fn assign(&mut self, _: T, _: U) where Self: Sized, T: Into, U: Into, { } #[doc(hidden)] fn is_bare(&self) -> bool { false } #[doc(hidden)] fn is_bareable(&self) -> bool { false } } #[doc(hidden)] pub trait NodeClone { fn clone(&self) -> Box; } #[doc(hidden)] pub trait NodeDefaultHash { fn default_hash(&self, state: &mut DefaultHasher); } impl NodeClone for T where T: Node + Clone, { #[inline] fn clone(&self) -> Box { Box::new(Clone::clone(self)) } } impl Clone for Box { #[inline] fn clone(&self) -> Self { NodeClone::clone(&**self) } } impl From for Box where T: Node, { #[inline] fn from(node: T) -> Box { Box::new(node) } } impl NodeDefaultHash for Box { #[inline] fn default_hash(&self, state: &mut DefaultHasher) { NodeDefaultHash::default_hash(&**self, state) } } macro_rules! node( ($struct_name:ident::$field_name:ident) => ( node!($struct_name::$field_name []); ); ($struct_name:ident::$field_name:ident [$($indicator_name:ident),*]) => ( impl $struct_name { /// Append a node. pub fn add(mut self, node: T) -> Self where T: Into>, { crate::node::Node::append(&mut self, node); self } /// Assign an attribute. #[inline] pub fn set(mut self, name: T, value: U) -> Self where T: Into, U: Into, { crate::node::Node::assign(&mut self, name, value); self } } impl crate::node::Node for $struct_name { #[inline] fn append(&mut self, node: T) where T: Into>, { self.$field_name.append(node); } #[inline] fn assign(&mut self, name: T, value: U) where T: Into, U: Into, { self.$field_name.assign(name, value); } $( #[inline] fn $indicator_name(&self) -> bool { true } )* } impl ::std::ops::Deref for $struct_name { type Target = Element; #[inline] fn deref(&self) -> &Self::Target { &self.$field_name } } impl ::std::ops::DerefMut for $struct_name { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { &mut self.$field_name } } impl ::std::fmt::Display for $struct_name { #[inline] fn fmt(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { if self.is_bareable() { write!(formatter, "{:#}", self.$field_name) } else { self.$field_name.fmt(formatter) } } } impl From<$struct_name> for Element { #[inline] fn from(value: $struct_name) -> Self { value.$field_name } } ); ); pub mod element; svg-0.16.0/src/node/text.rs000064400000000000000000000016571046102023000135660ustar 00000000000000use std::collections::hash_map::DefaultHasher; use std::fmt; use std::hash::Hash; use crate::node::Node; /// A text node. #[derive(Clone, Debug)] pub struct Text { content: String, } impl Text { /// Create a node. #[inline] pub fn new(content: T) -> Self where T: Into, { Text { content: content.into(), } } } impl fmt::Display for Text { #[inline] fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { escape(&self.content).fmt(formatter) } } impl Node for Text { #[inline] fn is_bare(&self) -> bool { true } } impl super::NodeDefaultHash for Text { #[inline] fn default_hash(&self, state: &mut DefaultHasher) { self.content.hash(state); } } pub(crate) fn escape(value: &str) -> String { value .replace('&', "&") .replace('<', "<") .replace('>', ">") } svg-0.16.0/src/node/value.rs000064400000000000000000000036121046102023000137070ustar 00000000000000use std::fmt; use std::ops::Deref; /// A value of an attribute. #[derive(Clone, Debug)] pub struct Value(String); impl Deref for Value { type Target = str; #[inline] fn deref(&self) -> &Self::Target { &self.0 } } impl fmt::Display for Value { #[inline] fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { self.0.fmt(formatter) } } impl From for String { #[inline] fn from(Value(inner): Value) -> Self { inner } } macro_rules! implement { ($($primitive:ty,)*) => ( $(impl From<$primitive> for Value { #[inline] fn from(inner: $primitive) -> Self { Value(inner.to_string()) } })* ); } implement! { i8, i16, i32, i64, isize, u8, u16, u32, u64, usize, f32, f64, String, bool, } impl<'l> From<&'l str> for Value { #[inline] fn from(inner: &'l str) -> Value { Value(inner.to_string()) } } impl From> for Value where T: Into, { fn from(mut inner: Vec) -> Self { Value( inner .drain(..) .map(|value| value.into().0) .collect::>() .join(" "), ) } } macro_rules! implement { (@express $e:expr) => ($e); ($pattern:expr, $(($t:ident, $n:tt)),*) => ( impl<$($t),*> From<($($t),*)> for Value where $($t: Into),* { fn from(inner: ($($t),*)) -> Self { Value(format!($pattern, $(implement!(@express inner.$n).into()),*)) } } ); } implement! { "{} {}", (T0, 0), (T1, 1) } implement! { "{} {} {} {}", (T0, 0), (T1, 1), (T2, 2), (T3, 3) } #[cfg(test)] mod tests { use super::Value; #[test] fn value_from_vector() { assert_eq!(String::from(Value::from(vec![42, 69])), "42 69"); } } svg-0.16.0/src/parser/error.rs000064400000000000000000000017371046102023000143010ustar 00000000000000//! The errors. use std::{error, fmt}; /// An error. #[derive(Debug)] pub struct Error { line: usize, column: usize, message: String, } impl Error { /// Create an error. #[inline] pub fn new>((line, column): (usize, usize), message: T) -> Self { Error { line, column, message: message.into(), } } } impl error::Error for Error { #[inline] fn description(&self) -> &str { &self.message } } impl fmt::Display for Error { fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { if self.line > 0 && self.column > 0 { write!( formatter, "{} (line {}, column {})", self.message, self.line, self.column, ) } else if self.line > 0 { write!(formatter, "{} (line {})", self.message, self.line) } else { self.message.fmt(formatter) } } } svg-0.16.0/src/parser/mod.rs000064400000000000000000000101441046102023000137170ustar 00000000000000//! The parser. use crate::node::element::tag::{Tag, Type}; use crate::node::Attributes; mod error; mod reader; pub use self::error::Error; #[doc(hidden)] pub use self::reader::Reader; /// A parser. pub struct Parser<'l> { reader: Reader<'l>, } /// An event. #[derive(Debug)] pub enum Event<'l> { /// An error. Error(Error), /// A tag. Tag(&'l str, Type, Attributes), /// A text. Text(&'l str), /// A comment. Comment(&'l str), /// A declaration. Declaration(&'l str), /// An instruction. Instruction(&'l str), } /// A result. pub type Result = ::std::result::Result; macro_rules! raise( ($parser:expr, $($argument:tt)*) => ( return Some(Event::Error(Error::new($parser.reader.position(), format!($($argument)*)))) ); ); impl<'l> Parser<'l> { /// Create a parser. #[inline] pub fn new(content: &'l str) -> Self { Parser { reader: Reader::new(content), } } fn next_angle(&mut self) -> Option> { let content: String = self.reader.peek_many().take(4).collect(); if content.is_empty() { None } else if content.starts_with(" bar", ""); test!(" bar", ""); macro_rules! test( ($content:expr) => ({ let mut reader = Reader::new($content); assert!(!reader.consume_comment()); }); ); // https://www.w3.org/TR/REC-xml/#sec-comments test!(""); } #[test] fn consume_name() { macro_rules! test( ($content:expr, $value:expr) => ({ let mut reader = Reader::new($content); let value = reader.capture(|reader| reader.consume_name()); assert_eq!(value.unwrap(), $value); }); ); test!("foo", "foo"); test!("foo bar", "foo"); test!("foo42 bar", "foo42"); test!("foo-bar baz", "foo-bar"); test!("foo/", "foo"); macro_rules! test( ($content:expr) => ({ let mut reader = Reader::new($content); assert!(!reader.consume_name()); }); ); test!(" foo"); test!("!foo"); test!(" ({ let mut reader = Reader::new($content); let value = reader.capture(|reader| reader.consume_number()); assert_eq!(value.unwrap(), $value); }); ); test!("1 ", "1"); test!("1a", "1"); test!("1", "1"); test!("-1", "-1"); test!("+1", "+1"); test!(".1", ".1"); test!("-.1", "-.1"); test!("+.1", "+.1"); test!("1.2", "1.2"); test!("-1.2", "-1.2"); test!("+1.2", "+1.2"); test!("1E2", "1E2"); test!("-1e2", "-1e2"); test!("+1e2", "+1e2"); test!("1.2e3", "1.2e3"); test!("-1.2E3", "-1.2E3"); test!("+1.2e3", "+1.2e3"); test!("1.2e-3", "1.2e-3"); test!("-1.2e-3", "-1.2e-3"); test!("+1.2E-3", "+1.2E-3"); test!("1.2E+3", "1.2E+3"); test!("-1.2e+3", "-1.2e+3"); test!("+1.2e+3", "+1.2e+3"); macro_rules! test( ($content:expr) => ({ let mut reader = Reader::new($content); assert!(reader.capture(|reader| reader.consume_number()).is_none()); }); ); test!("1.e2"); test!("-1.e2"); test!("+1.e2"); } #[test] fn consume_reference() { macro_rules! test( ($content:expr, $value:expr) => ({ let mut reader = Reader::new($content); let value = reader.capture(|reader| reader.consume_reference()); assert_eq!(value.unwrap(), $value); }); ); test!("* foo", "*"); test!("䊫 foo", "䊫"); test!("&foo; bar", "&foo;"); macro_rules! test( ($content:expr) => ({ let mut reader = Reader::new($content); assert!(!reader.consume_reference()); }); ); test!(" * foo"); test!("#42; foo"); test!("&42; foo"); test!("* foo"); test!("Bz; foo"); test!("&foo bar"); test!("foo; bar"); } #[test] fn consume_whitespace() { let mut reader = Reader::new(" \t \n\n \tm "); reader.consume_whitespace(); assert_eq!(reader.line, 3); assert_eq!(reader.column, 4); assert_eq!(reader.offset, 9); } } svg-0.16.0/tests/fixtures/benton.svg000064400000000000000000000032671046102023000155400ustar 00000000000000