Commit 3b596797 authored by RazrFalcon's avatar RazrFalcon

The usvg repo is a subproject now.

usvg is the core part of the resvg and it was troublesome to develop it in
a separate repo.
parent 0b2514e2
......@@ -25,3 +25,4 @@ env:
matrix:
- RESVG_QT_BACKEND=true
- RESVG_CAIRO_BACKEND=true
- USVG_TESTING=true
This diff is collapsed.
......@@ -15,13 +15,13 @@ categories = ["multimedia::images"]
members = [
"capi",
"tools/rendersvg",
"tools/usvg",
"examples/cairo-rs",
"usvg",
]
exclude = [
"examples/cairo-rs",
"testing_tools",
"workdir-qt", # for CI tests
"workdir-cairo", # for CI tests
]
[badges]
......@@ -30,8 +30,8 @@ travis-ci = { repository = "RazrFalcon/resvg" }
[dependencies]
log = "0.4.5"
rgb = "0.8.9"
usvg = { git = "https://github.com/RazrFalcon/usvg", rev = "5346684" }
#usvg = { path = "../usvg" }
#usvg = { git = "https://github.com/RazrFalcon/usvg", rev = "5346684" }
usvg = { path = "./usvg" }
unicode-segmentation = "1.2.1"
# cairo backend
......@@ -44,7 +44,7 @@ pangocairo = { version = "0.6", optional = true }
resvg-qt = { version = "0.4", optional = true }
[features]
cairo-backend = ["cairo-rs", "pango", "pangocairo", "gdk-pixbuf"]
cairo-backend = ["cairo-rs", "gdk-pixbuf", "pango", "pangocairo"]
qt-backend = ["resvg-qt"]
[lib]
......
......@@ -94,15 +94,15 @@ Also, we do not test against Chrome, Firefox, Inkscape and Batik because they ha
- Oxygen Icon Theme contains 4947 files.
- All images were converted from `.svgz` to `.svg` beforehand.
- `resvg` is slower than `librsvg` because Oxygen Icon Theme is using Gaussian blur heavily, which is expensive.
- `resvg` is slower than `librsvg` because Oxygen Icon Theme is using Gaussian blur heavily, which is expensive.
And `librsvg` uses box blur optimization and multithreading, while `resvg` always uses a single-threaded IIR blur (at least for now).
- QtSvg doesn't support `filter`, `clipPath`, `mask` and `pattern` that are heavily used in the Oxygen Icon Theme.
- QtSvg doesn't support `filter`, `clipPath`, `mask` and `pattern` that are heavily used in the Oxygen Icon Theme.
So it's actually very slow.
## Project structure
- `resvg` - rendering backends implementation
- [`usvg`](https://github.com/RazrFalcon/usvg) - an SVG simplification tool
- [`usvg`](./usvg) - an SVG simplification tool
- [`svgdom`](https://github.com/RazrFalcon/svgdom) - a DOM-like SVG tree
- [`roxmltree`](https://github.com/RazrFalcon/roxmltree) - a DOM-like XML tree
- [`xmlparser`](https://github.com/RazrFalcon/xmlparser) - an XML parser
......
......@@ -47,7 +47,7 @@ if os.getcwd().endswith('testing_tools'):
if 'TRAVIS_BUILD_DIR' in os.environ:
local_test = False
work_dir = Path('.').resolve()
tests_dir = Path('./resvg-test-suite/svg').resolve()
tests_dir = Path('./target/resvg-test-suite/svg').resolve()
else:
local_test = True
work_dir = '/tmp'
......@@ -59,7 +59,7 @@ print('tests_dir:', tests_dir)
# clone tests on CI
if not local_test:
run(['git', 'clone', TESTS_URL, '--depth', '1'], check=True)
run(['git', 'clone', TESTS_URL, '--depth', '1', './target/resvg-test-suite'], check=True)
if 'RESVG_QT_BACKEND' in os.environ:
......@@ -147,3 +147,15 @@ if 'RESVG_CAIRO_BACKEND' in os.environ:
# build cairo-rs example
with cd('examples/cairo-rs'):
run(['cargo', 'build'], check=True)
if 'USVG_TESTING' in os.environ:
with cd('tools/usvg'):
run(['cargo', 'build'], check=True)
with cd('usvg'):
run(['cargo', 'test'], check=True)
with cd('usvg/testing_tools'):
run(['./regression.py', '--ci-mode', '../../target/resvg-test-suite/svg',
'../../target/test-suite-temp'], check=True)
[package]
name = "usvg-cli"
version = "0.4.0"
authors = ["Reizner Evgeniy <razrfalcon@gmail.com>"]
keywords = ["svg"]
license = "MPL-2.0"
description = "An SVG simplification tool."
categories = ["multimedia::images"]
workspace = "../../"
[dependencies]
fern = "0.5"
gumdrop = "0.5"
log = "0.4"
usvg = { path = "../../usvg" }
[[bin]]
name = "usvg"
path = "src/main.rs"
extern crate usvg;
extern crate fern;
extern crate log;
#[allow(unused_imports)] // for Rust >= 1.30
#[macro_use] extern crate gumdrop;
use std::fmt;
use std::fs::File;
use std::io::{ self, Read, Write };
use std::path::Path;
use std::process;
use gumdrop::Options;
use usvg::svgdom;
use svgdom::WriteBuffer;
#[derive(Clone, PartialEq, Debug)]
enum InputFrom<'a> {
Stdin,
File(&'a str),
}
#[derive(Clone, PartialEq, Debug)]
enum OutputTo<'a> {
Stdout,
File(&'a str),
}
fn parse_dpi(s: &str) -> Result<u32, &'static str> {
let n: u32 = s.parse().map_err(|_| "invalid number")?;
if n >= 10 && n <= 4000 {
Ok(n)
} else {
Err("DPI out of bounds")
}
}
fn parse_font_size(s: &str) -> Result<u32, &'static str> {
let n: u32 = s.parse().map_err(|_| "invalid number")?;
if n > 0 && n <= 192 {
Ok(n)
} else {
Err("font size out of bounds")
}
}
fn parse_languages(s: &str) -> Result<Vec<String>, &'static str> {
let mut langs = Vec::new();
for lang in s.split(',') {
langs.push(lang.trim().to_string());
}
if langs.is_empty() {
return Err("languages list cannot be empty");
}
Ok(langs)
}
fn parse_indent(s: &str) -> Result<svgdom::Indent, &'static str> {
let indent = match s {
"none" => svgdom::Indent::None,
"0" => svgdom::Indent::Spaces(0),
"1" => svgdom::Indent::Spaces(1),
"2" => svgdom::Indent::Spaces(2),
"3" => svgdom::Indent::Spaces(3),
"4" => svgdom::Indent::Spaces(4),
"tabs" => svgdom::Indent::Tabs,
_ => return Err("invalid INDENT value"),
};
Ok(indent)
}
#[derive(Debug, Options)]
struct Args {
#[options()]
help: bool,
#[options(short = "V")]
version: bool,
#[options(short = "c", no_long)]
stdout: bool,
#[options(no_short)]
keep_named_groups: bool,
#[options(no_short)]
keep_invisible_shapes: bool,
#[options(no_short, meta = "DPI", default = "96", parse(try_from_str = "parse_dpi"))]
dpi: u32,
#[options(no_short, meta = "FAMILY", default = "Times New Roman")]
font_family: String,
#[options(no_short, meta = "SIZE", default = "12", parse(try_from_str = "parse_font_size"))]
font_size: u32,
#[options(no_short, meta = "LANG", parse(try_from_str = "parse_languages"))]
languages: Option<Vec<String>>,
#[options(no_short, meta = "INDENT", default = "4", parse(try_from_str = "parse_indent"))]
indent: svgdom::Indent,
#[options(no_short, meta = "INDENT", default = "none", parse(try_from_str = "parse_indent"))]
attrs_indent: svgdom::Indent,
#[options(free)]
free: Vec<String>,
}
fn main() {
let args: Vec<String> = ::std::env::args().collect();
let args = match Args::parse_args_default(&args[1..]) {
Ok(v) => v,
Err(e) => {
eprintln!("Error: {}.", e);
process::exit(1);
}
};
if args.help {
print_help();
process::exit(0);
}
if args.version {
println!("{}", env!("CARGO_PKG_VERSION"));
process::exit(0);
}
fern::Dispatch::new()
.format(log_format)
.level(log::LevelFilter::Warn)
.chain(std::io::stderr())
.apply().unwrap();
if let Err(e) = process(&args) {
eprintln!("Error: {}.", e.to_string());
std::process::exit(1);
}
}
fn print_help() {
print!("\
usvg (micro SVG) is an SVG simplification tool.
USAGE:
usvg [OPTIONS] <in-svg> <out-svg> # from file to file
usvg [OPTIONS] -c <in-svg> # from file to stdout
usvg [OPTIONS] <out-svg> - # from stdin to file
usvg [OPTIONS] -c - # from stdin to stdout
OPTIONS:
-h, --help Prints help information
-V, --version Prints version information
-c Prints the output SVG to the stdout
--keep-named-groups Disables removing of groups with non-empty ID
--keep-invisible-shapes Disables removing of invisible shapes
--dpi DPI Sets the resolution
[default: 96] [possible values: 10..4000]
--font-family FAMILY Sets the default font family
[default: 'Times New Roman']
--font-size SIZE Sets the default font size
[default: 12] [possible values: 1..192]
--languages LANG Sets a comma-separated list of languages that will be used
during the 'systemLanguage' attribute resolving.
Examples: 'en-US', 'en-US, ru-RU', 'en, ru'
[default: 'en']
--indent INDENT Sets the XML nodes indent
[values: none, 0, 1, 2, 3, 4, tabs] [default: 4]
--attrs-indent INDENT Sets the XML attributes indent
[values: none, 0, 1, 2, 3, 4, tabs] [default: none]
ARGS:
<in-svg> Input file
<out-svg> Output file
");
}
fn process(args: &Args) -> Result<(), String> {
if args.free.is_empty() {
return Err(format!("no positional arguments are provided"));
}
let (in_svg, out_svg) = {
let in_svg = &args.free[0];
let out_svg = args.free.get(1);
let out_svg = out_svg.map(String::as_ref);
let svg_from = if in_svg == "-" && args.stdout {
InputFrom::Stdin
} else if let Some("-") = out_svg {
InputFrom::Stdin
} else {
InputFrom::File(in_svg)
};
let svg_to = if args.stdout {
OutputTo::Stdout
} else if let Some("-") = out_svg {
OutputTo::File(in_svg)
} else {
OutputTo::File(out_svg.unwrap())
};
(svg_from, svg_to)
};
let languages = match args.languages.as_ref() {
Some(v) => v.clone(),
None => vec!["en".to_string()], // TODO: use system language
};
let re_opt = usvg::Options {
path: match in_svg {
InputFrom::Stdin => None,
InputFrom::File(ref f) => Some(f.into()),
},
dpi: args.dpi as f64,
font_family: args.font_family.clone(),
font_size: args.font_size as f64,
languages,
keep_named_groups: args.keep_named_groups,
keep_invisible_shapes: args.keep_invisible_shapes,
};
let input_str = match in_svg {
InputFrom::Stdin => load_stdin(),
InputFrom::File(ref path) => {
usvg::load_svg_file(Path::new(path)).map_err(|e| e.to_string())
}
}?;
let tree = usvg::Tree::from_str(&input_str, &re_opt)
.map_err(|e| format!("{}", e))?;
let dom_opt = svgdom::WriteOptions {
indent: args.indent,
attributes_indent: args.attrs_indent,
attributes_order: svgdom::AttributesOrder::Specification,
.. svgdom::WriteOptions::default()
};
let doc = tree.to_svgdom();
let mut output_data = Vec::new();
doc.write_buf_opt(&dom_opt, &mut output_data);
match out_svg {
OutputTo::Stdout => {
io::stdout().write_all(&output_data)
.map_err(|_| format!("failed to write to the stdout"))?;
}
OutputTo::File(path) => {
let mut f = File::create(path)
.map_err(|_| format!("failed to create the output file"))?;
f.write_all(&output_data)
.map_err(|_| format!("failed to write to the output file"))?;
}
}
Ok(())
}
fn log_format(
out: fern::FormatCallback,
message: &fmt::Arguments,
record: &log::Record,
) {
let lvl = match record.level() {
log::Level::Error => "Error",
log::Level::Warn => "Warning",
log::Level::Info => "Info",
log::Level::Debug => "Debug",
log::Level::Trace => "Trace",
};
out.finish(format_args!(
"{} (in {}:{}): {}",
lvl,
record.target(),
record.line().unwrap_or(0),
message
))
}
fn load_stdin() -> Result<String, String> {
let mut s = String::new();
let stdin = io::stdin();
let mut handle = stdin.lock();
handle.read_to_string(&mut s)
.map_err(|_| format!("provided data has not an UTF-8 encoding"))?;
Ok(s)
}
# Change Log
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased]
### Added
- Implement `FuzzyEq` for `Rect`, `Size` and `Point`.
- `StrokeMiterlimit` and `FontSize` wrappers for `f64`.
- `Options::keep_invisible_shapes` and `--keep-invisible-shapes` to CLI.
### Changed
- `FilterPrimitive::filter_input` was removed and each filter primitive that can
have an input has it's own `filter_input` field now.
- Rename `filter_input` fields into `input`.
- Filter primitives inputs and results will be resolved now.
### Fixed
- `offset` attribute resolving inside the `stop` element.
### Removed
- `Rect::transform`.
## [0.4.0] - 2018-12-13
### Added
- Initial [Basic Filters](http://www.w3.org/TR/SVG11/feature#BasicFilter) support.
- Nested `clipPath` support.
- `systemLanguage` attribute support.
- `mask` attribute on `mask` element support.
- Default font family and size is configurable now.
- `StrokeWidth` wrapper.
- `ClipPath::clip_path`.
- `visibility` field for `Path`, `TextSpan` and `Image`.
- Most of the structs are implement Clone and Debug now.
### Changed
- `Opacity` and `StopOffset` will be clamped to the 0..1 range now.
- The `visibility` attribute will not be removed now,
because invisible elements still impact the bbox calculation.
- Elements with zero opacity will not be removed now,
because such elements still impact the bbox calculation.
- No `PartialEq` for `Line`, `Point`, `Size` and `Rect`. Because of `f64`.
### Fixed
- `display` attribute processing.
- Recursive `mask` resolving.
- `inherit` attribute value resolving.
- Complex style resolving.
## [0.3.0] - 2018-09-12
### Added
- Implement `Deref` for `LinearGradient` and `RadialGradient`.
- (cli) `--indent` and `--attrs-indent` flags.
- (cli) Use `gumdrop` instead of `getopts`.
- `Error::ParsingFailed`.
### Changed
- Gradient stops are stored directly in the `BaseGradient` and not as `NodeKind::Stop` now.
- `TextChunk` are stored directly in the `Text` and not as `NodeKind::TextChunk` now.
- Rename `LinearGradient::d` to `LinearGradient::base`.
- Rename `RadialGradient::d` to `RadialGradient::base`.
- Rename `TSpan` to `TextSpan`.
- `Tree::from_str` will return a `Result` now.
### Removed
- `failure` dependency.
## [0.2.0] - 2018-05-23
### Added
- Remove elements with `opacity="0"`.
- Transfer the group `id` attribute to the child when group has only one child.
- `symbol` element support.
- `Tree::from_str`.
- Nested `svg` elements support.
- SVG support for `image` element.
- `ImageFormat::SVG`.
- `Image::format`.
- Paint fallback resolving.
- Bbox validation for shapes that use painting servers.
- `TextChunk::dx` and `TextChunk::dy`.
- `Text::rotate`.
- `rotate` attribute processing.
### Changed
- `tree` module content reexported.
- `parse_tree_from_*` methods move to the `Tree`. Use `Tree::from_*` instead.
- Rename `Tree::node_by_svg_id` to `Tree::node_by_id`.
- Use `rctree::Node<NodeKind>` instead of `rctree::Node<Box<NodeKind>>`.
- `view` element is out of scope now.
- `FileReadError` replaced with `Error`.
- `parse_tree_from_data` accepts `&[u8]` and not `&str` now.
- Rename `ImageDataKind` to `ImageFormat`.
- New geometry primitives implementation.
- `TextChunk::x` and `TextChunk::y` are `Option<NumberList>` and not `f64` now.
### Removed
- `NodeExt::kind`. Use `Node::borrow` instead.
### Fixed
- Panic during `visibility` resolving.
- Gradients with one stop resolving.
- `use` attributes resolving.
- `clipPath` and `mask` attributes resolving.
- `offset` attribute in `stop` element resolving.
- Incorrect `font-size` attribute resolving.
- Gradient stops resolving.
- `switch` element resolving.
[Unreleased]: https://github.com/RazrFalcon/usvg/compare/v0.4.0...HEAD
[0.4.0]: https://github.com/RazrFalcon/usvg/compare/v0.3.0...v0.4.0
[0.3.0]: https://github.com/RazrFalcon/usvg/compare/v0.2.0...v0.3.0
[0.2.0]: https://github.com/RazrFalcon/usvg/compare/v0.1.1...v0.2.0
[package]
name = "usvg"
# When updating version, also modify html_root_url in the lib.rs and in the cli/Cargo.toml
version = "0.4.0"
authors = ["Reizner Evgeniy <razrfalcon@gmail.com>"]
keywords = ["svg"]
license = "MPL-2.0"
description = "An SVG simplification library."
categories = ["multimedia::images"]
repository = "https://github.com/RazrFalcon/resvg"
documentation = "https://docs.rs/usvg/"
readme = "README.md"
workspace = ".."
[dependencies]
base64 = "0.9" # Do not update, because a lot of important code was removed.
libflate = "0.1"
log = "0.4"
lyon_geom = "0.12"
rctree = "0.2.1"
svgdom = { git = "https://github.com/RazrFalcon/svgdom", rev = "f69fc2a" }
#svgdom = { path = "../svgdom" }
unicode-segmentation = "1.2.1"
[dev-dependencies]
assert_cli = "0.6"
pretty_assertions = "0.5.1"
rustc-test = "0.3"
rustc_version = "0.2.2"
serde = "1.0.34"
serde_derive = "1.0.34"
tempdir = "0.3"
toml = "0.4.5"
This diff is collapsed.
# usvg
[![Build Status](https://travis-ci.org/RazrFalcon/usvg.svg?branch=master)](https://travis-ci.org/RazrFalcon/usvg)
[![Crates.io](https://img.shields.io/crates/v/usvg.svg)](https://crates.io/crates/usvg)
[![Documentation](https://docs.rs/usvg/badge.svg)](https://docs.rs/usvg)
*usvg* (micro SVG) is an [SVG] simplification tool.
## Purpose
Imagine, that you have to extract some data from the [SVG] file, but your
library/framework/language doesn't have a good SVG library.
And all you need is paths data.
You can try to export it by yourself (how hard can it be, right).
All you need is an XML library (I'll hope that your language has one).
But soon you realize that paths data has a pretty complex format and a lot
of edge-cases. And we didn't mention attributes propagation, transforms,
visibility flags, attribute values validation, XML quirks, etc.
It will take a lot of time and code to implement this stuff correctly.
So, instead of creating a library that can be used from any language (impossible),
*usvg* takes a different approach. It converts an input SVG to an extremely
simple representation, which is still a valid SVG.
And now, all you need is to convert your SVG to a simplified one via *usvg*
and an XML library with some small amount of code.
## Key features of the simplified SVG
- No basic shapes (rect, circle, etc). Only paths
- Simple paths:
- Only *MoveTo*, *LineTo*, *CurveTo* and *ClosePath* will be produced
- All path segments are in absolute coordinates
- No implicit segment commands
- All values are separated by a space
- All (supported) attributes are resolved. No implicit one
- `use` will be resolved
- Invisible elements will be removed
- Invalid elements (like `rect` with negative/zero size) will be removed
- Units (mm, em, etc.) will be resolved
- Comments will be removed
- DTD will be resolved
- CSS will be resolved
- `style` attribute will be resolved
- `inherit` attribute value will be resolved
- `currentColor` attribute value will be resolved
- Paint fallback will be resolved
- No `script` (simply ignoring it)
Full spec can be found [here](docs/usvg_spec.adoc).
## Limitations
- Currently, its not lossless. Some SVG features isn't supported yet and will be ignored.
- CSS support is minimal.
- Only [static](http://www.w3.org/TR/SVG11/feature#SVG-static) SVG features,
e.g. no: `a`, `view`, `cursor`, `script` and [animations](https://www.w3.org/TR/SVG/animate.html).
- Unsupported elements:
- some filter-based elements
- font-based elements
- `marker`
## Dependency
The latest stable [Rust](https://www.rust-lang.org/).
## FAQ
### How to ensure that SVG is a valid "Micro" SVG?
You can't. The idea is that you should not store files produced by *usvg*.
You should use them immediately. Like an intermediate data.
## License
*usvg* is licensed under the [MPLv2.0](https://www.mozilla.org/en-US/MPL/).
[SVG]: https://en.wikipedia.org/wiki/Scalable_Vector_Graphics
= Micro SVG Document Structure
:toc:
:1H: #
== Elements
=== svg
The `svg` element is the root element of the document.
It's defined only once and can't be nested, unlike the SVG spec.
Children: `defs`, `g`, `path`, `text` and `image`.
Attributes: `width`, `height`, `viewBox` and `preserveAspectRatio`.
=== defs
Children: `linearGradient`, `radialGradient`, `clipPath`, `mask` and `pattern`.
[[linearGradient_elem]]
=== linearGradient
Gradient has all attributes resolved and at least two `stop` children.
`stop` children will always have unique, ordered `offset` values.
Gradient doesn't have `xlink:href` attribute because all attributes and `stop` children
are already resolved and copied to the gradient.
Children: `<<stop_elem,stop>>`.
Attributes: `id`, `x1`, `y1`, `x2`, `y2`, `gradientUnits`, `spreadMethod`,
`gradientTransform`.
* `id` is always set and never empty.
* See `<<transform_attr,transform>>` for `gradientTransform`.
=== radialGradient
See `<<linearGradient_elem,linearGradient>>`.
Children: `<<stop_elem,stop>>`.
Attributes: `id`, `cx`, `cy`, `r`, `fx`, `fy`, `gradientUnits`, `spreadMethod`,
`gradientTransform`.
* `id` is always set and never empty.
* `r` always > 0.
* `fx` and `fy` are always inside the circle defined by `cx`, `cy` and `r`.
* See `<<transform_attr,transform>>` for `gradientTransform`.
[[stop_elem]]
=== stop
Attributes: `offset`, `stop-color`, `stop-opacity`.
* `offset` and `stop-opacity` are always in 0..1 range.
* `stop-color` is always a valid {1H}RRGGBB color.