pax_global_header00006660000000000000000000000064135257637570014536gustar00rootroot0000000000000052 comment=9183f919c077c0b008e308c2953cc75964c1471a resvg-0.8.0/000077500000000000000000000000001352576375700126715ustar00rootroot00000000000000resvg-0.8.0/.github/000077500000000000000000000000001352576375700142315ustar00rootroot00000000000000resvg-0.8.0/.github/chart.svg000066400000000000000000000052441352576375700160600ustar00rootroot00000000000000 resvg 0.8.0 Firefox 60.7 Chromium r662092 Inkscape 0.92.4 librsvg 2.45.5 Batik 1.9 QtSvg 5.12.3 0 294 588 882 1176 1122 1045 1047 878 874 849 447 Tests passed resvg-0.8.0/.github/official_chart.svg000066400000000000000000000052371352576375700177160ustar00rootroot00000000000000 resvg 0.8.0 Firefox 60.7 Chromium r662092 Inkscape 0.92.4 librsvg 2.45.5 Batik 1.9 QtSvg 5.12.3 0 84 169 253 337 267 288 293 251 222 259 140 Tests passed resvg-0.8.0/.github/perf-elementary.svg000066400000000000000000000051261352576375700200550ustar00rootroot00000000000000 Time to render Elementary OS Icon Theme 5.0 resvg 0.8.0 (cairo) resvg 0.8.0 (qt) resvg 0.8.0 (raqote) resvg 0.8.0 (skia) librsvg 2.45.5 QtSvg 5.12.3 0sec 10sec 20sec 30sec 40sec 19 35 19 19 24 30 Time passed resvg-0.8.0/.github/perf-oxygen.svg000066400000000000000000000051311352576375700172150ustar00rootroot00000000000000 Time to render Oxygen Icon Theme 4.12 resvg 0.8.0 (cairo) resvg 0.8.0 (qt) resvg 0.8.0 (raqote) resvg 0.8.0 (skia) librsvg 2.45.5 QtSvg 5.12.3 0sec 60sec 120sec 180sec 240sec 195 191 205 175 163 160 Time passed resvg-0.8.0/.gitignore000066400000000000000000000001651352576375700146630ustar00rootroot00000000000000.directory target .idea *.pro.user *.pro.user.* .qmake.stash Makefile *.o moc_*.cpp moc_*.h qrc_*.cpp ui_*.h build-* resvg-0.8.0/.travis.yml000066400000000000000000000005701352576375700150040ustar00rootroot00000000000000dist: xenial language: rust rust: - stable install: - ./testing-tools/ci-install.sh # prepare custom Qt - if [ "$RESVG_QT_BACKEND" = true ]; then source /opt/qt56/bin/qt56-env.sh; fi script: ./testing-tools/run-tests.py env: matrix: - RESVG_QT_BACKEND=true - RESVG_CAIRO_BACKEND=true - RESVG_RAQOTE_BACKEND=true - RESVG_SKIA_BACKEND=true resvg-0.8.0/BUILD.adoc000077500000000000000000000176221352576375700143730ustar00rootroot00000000000000:toc: :toc-title: = How to build *resvg* == Intro *resvg* doesn't include a 2D graphics library and uses external ones. Their support is implemented separately, therefore we call them _backends_. You can build them separately or together. + At the moment, there are four backends: https://www.qt.io/[Qt], https://www.cairographics.org/[cairo], https://skia.org/[Skia] and https://github.com/jrmuizel/raqote[raqote]. Since *resvg* is a https://www.rust-lang.org/[Rust] library, you should build it via `cargo`. + To enable a backend use the `--features` option: ```bash # Build with a Qt backend cargo build --release --features="qt-backend" # or with a cairo backend cargo build --release --features="cairo-backend" # or with a raqote backend cargo build --release --features="raqote-backend" # or with a skia backend cargo build --release --features="skia-backend" # or with all cargo build --release --all-features ``` == Dependencies * The library requires the latest stable https://www.rust-lang.org/tools/install[Rust]. * All backends depend on https://github.com/harfbuzz/harfbuzz[harfbuzz], which will be built automatically by `cargo` and will be linked statically. https://cmake.org/download/[CMake] is required as a build-time dependency. * The _Qt backend_ requires only `QtCore` and `QtGui` libraries. + Technically, any Qt 5 version should work, but we only support Qt >= 5.6. * The _cairo backend_ requires https://www.cairographics.org/[cairo] >= 1.12. * The _raqote backend_ doesn't require any external dependencies. * The _skia backend_ requires the https://skia.org/[Skia] itself. * (Linux, runtime) _fontconfig_. Specifically `fc-match`. == Windows === Qt backend via MSVC Install: * `stable-x86_64-pc-windows-msvc` https://www.rust-lang.org/tools/install[Rust] target. * https://cmake.org/download/[CMake] (required to build harfbuzz). * Qt built with MSVC via an http://download.qt.io/official_releases/online_installers/qt-unified-windows-x86-online.exe[official installer]. Build using `x64 Native Tools Command Prompt for VS 2017` shell: ```batch set PATH=%userprofile%\.cargo\bin;%PATH% set QT_DIR=C:\Qt\5.12.0\msvc2017_64 cargo.exe build --release --features "qt-backend" ``` Instead of `msvc2017_64` you can use any other Qt MSVC build. Even 32-bit one. We are using Qt 5.12.0 just for example. === Qt backend via MinGW Install: * `stable-x86_64-pc-windows-gnu` https://www.rust-lang.org/tools/install[Rust] target. * https://cmake.org/download/[CMake] (required to build harfbuzz). * Qt built with MinGW 64-bit via an http://download.qt.io/official_releases/online_installers/qt-unified-windows-x86-online.exe[official installer]. Build using `cmd.exe`: ```batch set PATH=C:\Qt\5.12.0\mingw73_64\bin;C:\Qt\Tools\mingw730_64\bin;%userprofile%\.cargo\bin;%PATH% set QT_DIR=C:\Qt\5.12.0\mingw73_64 cargo.exe build --release --features "qt-backend" ``` Instead of `mingw73_64` you can use any other Qt mingw build. We are using Qt 5.12.0 just for example. === cairo backend via MSYS2 Install `stable-x86_64-pc-windows-gnu` https://www.rust-lang.org/tools/install[Rust] target. And then: ```bash pacman -S mingw-w64-x86_64-cmake mingw-w64-x86_64-cairo cargo.exe build --release --features "cairo-backend" ``` You can use i686 target in the same way. === raqote backend via MSVC Install: * `stable-x86_64-pc-windows-msvc` https://www.rust-lang.org/tools/install[Rust] target. * https://cmake.org/download/[CMake] (required to build harfbuzz). Build using `x64 Native Tools Command Prompt for VS 2017` shell: ```batch set PATH=%userprofile%\.cargo\bin;C:\Program Files\CMake\bin;%PATH% cargo.exe build --release --features "raqote-backend" ``` === raqote backend via MSYS2 Install `stable-x86_64-pc-windows-gnu` https://www.rust-lang.org/tools/install[Rust] target. And then: ```bash # install harfbuzz dependencies: pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake cargo.exe build --release --features "raqote-backend" ``` You can use i686 target in the same way. === Skia backend via MSVC Install: * `stable-x86_64-pc-windows-msvc` https://www.rust-lang.org/tools/install[Rust] target. * https://cmake.org/download/[CMake] (required to build harfbuzz). * Skia itself (we assume that you have already built one). `SKIA_DIR` should point to a Skia directory that contains the Skia `include` directory. `SKIA_LIB_DIR` should point to a Skia directory that contains `skia.dll`. Build using `x64 Native Tools Command Prompt for VS 2017` shell: ```batch set PATH=%userprofile%\.cargo\bin;C:\Program Files\CMake\bin;%PATH% set SKIA_DIR=path set SKIA_LIB_DIR=path cargo.exe build --release --features "skia-backend" ``` == Linux === Qt backend Install Qt 5 and `harfbuzz` using your distributive's package manager. On Ubuntu you can install them via: ``` sudo apt install qtbase5-dev libharfbuzz-dev ``` Build `resvg`: ```bash cargo build --release --features "qt-backend" ``` If you don't want to use a system Qt, you can alter it with the `PKG_CONFIG_PATH` variable. ```bash PKG_CONFIG_PATH='/path_to_qt/lib/pkgconfig' cargo build --release --features "qt-backend" ``` === cairo backend Install `cairo` and `harfbuzz` using your distributive's package manager. On Ubuntu you can install them via: ``` sudo apt install libcairo2-dev libharfbuzz-dev ``` Build `resvg`: ```bash cargo build --release --features "cairo-backend" ``` === raqote backend Install `harfbuzz` using your distributive's package manager. On Ubuntu you can install it via: ``` sudo apt install libharfbuzz-dev ``` Build `resvg`: ```bash cargo build --release --features "raqote-backend" ``` === Skia backend We assume that you have already built Skia itself. Install `harfbuzz` using your distributive's package manager. On Ubuntu you can install it via: ``` sudo apt install libharfbuzz-dev ``` `SKIA_DIR` should point to a Skia directory that contains the Skia `include` directory. `SKIA_LIB_DIR` should point to a Skia directory that contains `libskia.so`. ```sh SKIA_DIR=path SKIA_LIB_DIR=path cargo build --release --features "skia-backend" ``` == macOS === Qt backend Using https://brew.sh/[homebrew]: ```bash brew install qt QT_DIR=/usr/local/opt/qt cargo build --release --features "qt-backend" ``` Or an http://download.qt.io/official_releases/online_installers/qt-unified-mac-x64-online.dmg[official Qt installer]: ```bash QT_DIR=/Users/$USER/Qt/5.12.0/clang_64 cargo build --release --features "qt-backend" ``` We are using Qt 5.12.0 just for example. === cairo backend Using https://brew.sh/[homebrew]: ```bash brew install cairo cargo build --release --features "cairo-backend" ``` === raqote backend ```bash cargo build --release --features "raqote-backend" ``` === Skia backend We assume that you have already built Skia itself. Install `harfbuzz` using Homebrew via: ``` brew install harfbuzz ``` `SKIA_DIR` should point to a Skia directory that contains the Skia `include` directory. `SKIA_LIB_DIR` should point to a Skia directory that contains `libskia.dylib`. ```sh SKIA_DIR=path SKIA_LIB_DIR=path cargo build --release --features "skia-backend" ``` == For maintainers *resvg* consists of 4 parts: - the Rust library (link:./src[src]) - the C library/bindings (link:./capi[capi]) - the CLI tool to render SVG (link:./tools/rendersvg[tools/rendersvg]) - the CLI tool to simplify SVG (link:./tools/usvg[tools/usvg]) All of them are optional and each one, except `usvg`, can be built with a specific backend. No need to build `rendersvg` for each backend separately since it has a CLI switch to choose which one to use in runtime. Not sure how the Rust library can be packaged, but the C libraries should probably be built separately. So the final package can look like this: ``` /bin/rendersvg (does not depend on libresvg-*.so) /bin/usvg (completely optional) /include/resvg/resvg.h (from capi/include) /include/resvg/ResvgQt.h (from capi/include, only for Qt backend) /lib/libresvg-cairo.so /lib/libresvg-qt.so /lib/libresvg-raqote.so /lib/libresvg-skia.so ``` resvg-0.8.0/CHANGELOG.md000066400000000000000000000267351352576375700145170ustar00rootroot00000000000000# 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/). This changelog also contains important changes in dependencies. ## [Unreleased] ## [0.8.0] - 2019-08-17 ### Added - A [Skia](https://skia.org/) backend thanks to [JaFenix](https://github.com/JaFenix). - `feComponentTransfer` support. - `feColorMatrix` support. - A better CSS support. - An `*.otf` fonts support. - (usvg) `dx`, `dy` are supported inside `textPath` now. - Use a box blur for `feGaussianBlur` with `stdDeviation`>=2. This is 4-8 times faster than IIR blur. Thanks to [Shnatsel](https://github.com/Shnatsel). ### Changed - All backends are using the `image` crate for raster images loading now. - Use `pico-args` instead of `gumdrop` to reduced the build time of `tools/rendersvg` and `tools/usvg`. - (usvg) The `xmlwriter` is used for SVG generation now. Almost 2x faster than generating an `svgdom`. - (usvg) Optimize font database initialization. Almost 50% faster. - Use a lower PNG compression ratio to speed up PNG generation. Depending on a backend and image can be 2-4x faster. - `OutputImage::save` -> `OutputImage::save_png`. - (usvg) `Path::segments` -> `Path::data`. - Cairo backend compilation is 2x faster now due to overall changes. - Performance improvements (Oxygen Icon theme SVG-to-PNG): - cairo-backend: 22% faster - qt-backend: 20% faster - raqote-backend: 34% faster ### Fixed - (qt-api) A default font resolving. - (usvg) `baseline-shift` processing inside `textPath`. - (usvg) Remove all `tref` element children. - (usvg) `tref` with `xml:space` resolving. - (usvg) Ignore nested `tref`. - (usvg) Ignore invalid `clipPath` children that were referenced via `use`. - (usvg) `currentColor` will always fallback to black now. Previously, `stroke` was set to `none` which is incorrect. - (usvg) `use` can reference an element inside a non-SVG element now. - (usvg) Collect all styles for generic fonts and not only *Regular*. - (svgdom) Parse only presentation attributes from the `style` element and attribute. ### Removed - (cairo-backend) `gdk-pixbuf` dependency. - (qt-backend) JPEG image format plugin dependency. - `svgdom` dependency. ## [0.7.0] - 2019-06-19 ### Added - New text layout implementation: - `textPath` support. - `writing-mode` support, aka vertical text. - [Text BIDI reordering](http://www.unicode.org/reports/tr9/). - Better text shaping. - `word-spacing` is supported for all backends now. - [`harfbuzz`](https://github.com/harfbuzz/harfbuzz) dependency. - Subscript, superscript offsets are extracted from font and not hardcoded now. - `shape-rendering`, `text-rendering` and `image-rendering` support. - The `arithmetic` operator for `feComposite`. - (usvg) `--quiet` argument. - (c-api) `resvg_get_image_bbox`. - (qt-api) `ResvgRenderer::boundingBox`. - (resvg) A [raqote](https://github.com/jrmuizel/raqote) backend thanks to [jrmuizel](https://github.com/jrmuizel). Still experimental. ### Changed - Text will be converted into paths on the `usvg` side now. - (resvg) Do not rescale images before rendering. This is faster and better. - (usvg) An `image` element with a zero or negative size will be skipped now. Previously, a linked image size was used, which is incorrect. - Geometry primitives (`Rect`, `Size`, etc) are immutable and always valid now. - (usvg) The default `color-interpolation-filters` attribute will not be exported now. ### Removed - (usvg) All text related structures and enums. Text will be converted into `Path` now. - `InitObject` and `init()` because they are no longer needed. - (c-api) `resvg_handle`, `resvg_init`, `resvg_destroy`. - (c-api) `resvg_cairo_get_node_bbox` and `resvg_qt_get_node_bbox`. Use backend-independent `resvg_get_node_bbox` instead. - (cairo-backend) `pango` dependency. - (resvg) `Backend::calc_node_bbox`. Use `Node::calculate_bbox()` instead. ### Fixed - `letter-spacing` on cursive scripts (like Arabic). - (rctree) Prevent stack overflow on a huge, deeply nested SVG. - (c-api) `resvg_is_image_empty` was always returning `false`. - (resvg) Panic when `filter` with `objectBoudningBox` was set on an empty group. - (usvg) `mask` with `objectBoundingBox` resolving. - (usvg) `pattern`'s `viewBox` attribute resolving via `href`. - (roxmltree) Namespace resolving. ## [0.6.1] - 2019-03-16 ### Fixed - (usvg) `transform` multiplication. - (usvg) `use` inside `clipPath` resolving. ## [0.6.0] - 2019-03-16 ### Added - Nested `baseline-shift` support. - (qt-api) `renderToImage`. - (usvg) A better algorithm for unused defs (`defs` element children, like gradients) removal. - (usvg) `Error::InvalidSize`. - (c-api) `RESVG_ERROR_INVALID_SIZE`. ### Changed - (usvg) A major rewrite. - `baseline-shift` with `sub`, `super` and percent values calculation. - Marker resolving moved completely to `usvg`. - If an SVG doesn't have a valid size than an error will occur. Previously, an empty tree was produced. - (qt-api) `render` methods are `const` now. - (usvg) Disable default attributes exporting. ### Removed - (usvg) Marker element and attributes. Markers will be resolved just like `use` now. ### Fixed - (resvg) During the `tspan` rendering, the `text` bbox will be used instead of the `tspan` bbox itself. This is the correct behaviour by the SVG spec. - (cairo-backend) `font-family` parsing. - (usvg) `filter:none` processing. - (usvg) `text` inside `text` processing. - (usvg) Endless loop during `use` resolving. - (usvg) Endless loop when SVG has indirect recursive `xlink:href` links. - (usvg) Endless loop when SVG has recursive `marker-*` links. - (usvg) Panic during `use` resolving. - (usvg) Panic during inherited attributes resolving. - (usvg) Groups regrouping. - (usvg) `dx`/`dy` processing on `text`. - (usvg) `textAnchor` resolving. - (usvg) Ignore `fill-rule` on `text`. - (svgtypes) Style with comments parsing. - (roxmltree) Namespaces resolving. ## [0.5.0] - 2019-01-04 ### Added - `marker` support. - Partial `baseline-shift` support. - `letter-spacing` support. - (qt-backend) `word-spacing` support. Does not work on the cairo backend. - tools/explorer-thumbnailer - tools/kde-dolphin-thumbnailer ### Fixed - Object bounding box calculation. - Pattern scaling. - Nested `objectBoundigBox` support. - (usvg) `color` on `use` resolving. - (usvg) `offset` attribute resolving inside the `stop` element. - (usvg) Ungrouping of groups with non-inheritable attributes. - (usvg) `rotate` attribute resolving. - (usvg) Paths without stroke and fill will no longer be removed. Required for a proper bbox resolving. - (usvg) Coordinates resolving when units are `userSpaceOnUse`. - (usvg) Groups regrouping. Caused an incorrect rendering of `clipPath` that had `filter` on a child. - (usvg) Style attributes resolving on the root `svg` element. - (usvg) `SmoothCurveTo` and `SmoothQuadratic` conversion. - (usvg) `symbol` resolving. - (cairo-backend) Font ascent calculation. - (qt-backend) Stroking of LineTo specified as CurveTo. - (svgdom) `stroke-miterlimit` attribute parsing. - (svgdom) `length` and `number` attribute types parsing. - (svgdom) `offset` attribute parsing. - (svgdom) IRI resolving order when SVG has duplicated ID's. ## [0.4.0] - 2018-12-13 ### Added - (resvg) Initial filters support. - (resvg) Nested `clipPath` and `mask` support. - (resvg) MSVC support. - (rendersvg) `font-family`, `font-size` and `languages` to args. - (usvg) `systemLanguage` attribute support. - (usvg) Default font family and size is configurable now. - (c-api) `RESVG_ERROR_PARSING_FAILED`. - (c-api) `font_family`, `font_size` and `languages` to `resvg_options`. - (qt-api) `ResvgRenderer::setDevicePixelRatio`. ### Changed - (rendersvg) Use `gumdrop` instead of `getopts`. - (c-api) Qt wrapper is header-only now. ### Fixed - (cairo-backend) Text layout. - (cairo-backend) Rendering of a zero length subpath with a square cap. - (qt-backend) Transform retrieving via Qt bindings. - (resvg) Recursive SVG images via `image` tag. - (resvg) Bbox calculation of the text with rotate. - (resvg) Invisible elements processing. - (qt-api) SVG from QByteArray loading when data is invalid. - (usvg) `display` attribute processing. - (usvg) Recursive `mask` resolving. - (usvg) `inherit` attribute value resolving. - (svgdom) XML namespaces resolving. ### Removed - (rendersvg) `failure` dependency. ## [0.3.0] - 2018-05-23 ### Added - (c-api) `resvg_is_image_empty`. - (c-api) `resvg_error` enum. - (c-api) Qt wrapper. - (resvg) Advanced text layout support (lists of x, y, dx, dy and rotate). - (resvg) SVG support for `image` element. - (usvg) `symbol` element support. - (usvg) Nested `svg` elements support. - (usvg) Paint fallback resolving. - (usvg) Bbox validation for shapes that use painting servers. - (svgdom) Elements from ENTITY resolving. ### Changed - (c-api) `resvg_parse_tree_from_file`, `resvg_parse_tree_from_data` `resvg_cairo_render_to_image` and `resvg_qt_render_to_image` will return an error code now. - (cairo-backend) Use `gdk-pixbuf` crate instead of `image`. - (resvg) `Render::render_to_image` and `Render::render_node_to_image` will return `Option` and not `Result` now. - (resvg) New geometry primitives implementation. - (resvg) Rename `render_*` modules to `backend_`. - (rendersvg) Use `getopts` instead of `clap` to reduce the executable size. - (svgtypes) `StreamExt::parse_iri` and `StreamExt::parse_func_iri` will parse not only well-formed data now. ### Fixed - (qt-backend) Gradient with `objectBoundingBox` rendering. - (qt-backend) Text bounding box detection during the rendering. - (cairo-backend) `image` element clipping. - (cairo-backend) Layers management. - (c-api) `resvg_get_node_transform` will return a correct transform now. - (resvg) `text-decoration` thickness. - (resvg) `pattern` scaling. - (resvg) `image` without size rendering. - (usvg) Panic during `visibility` resolving. - (usvg) Gradients with one stop resolving. - (usvg) `use` attributes resolving. - (usvg) `clipPath` and `mask` attributes resolving. - (usvg) `offset` attribute in `stop` element resolving. - (usvg) Incorrect `font-size` attribute resolving. - (usvg) Gradient stops resolving. - (usvg) `switch` element resolving. - (svgdom) Mixed `xml:space` processing. - (svgtypes) `Paint::from_span` poor performance. ### Removed - (c-api) `resvg_error_msg_destroy`. - (resvg) `parse_rtree_*` methods. Use `usvg::Tree::from_` instead. - (resvg) `Error`. ## [0.2.0] - 2018-04-24 ### Added - (svg) Partial `clipPath` support. - (svg) Partial `mask` support. - (svg) Partial `pattern` support. - (svg) `preserveAspectRatio` support. - (svg) Check that an external image is PNG or JPEG. - (rendersvg) Added `--query-all` and `--export-id` arguments to render SVG items by ID. - (rendersvg) Added `--perf` argument for a simple performance stats. ### Changed - (resvg) API is completely new. ### Fixed - `font-size` attribute inheritance during `use` resolving. [Unreleased]: https://github.com/RazrFalcon/resvg/compare/v0.8.0...HEAD [0.8.0]: https://github.com/RazrFalcon/resvg/compare/v0.7.0...v0.8.0 [0.7.0]: https://github.com/RazrFalcon/resvg/compare/v0.6.1...v0.7.0 [0.6.1]: https://github.com/RazrFalcon/resvg/compare/v0.6.0...v0.6.1 [0.6.0]: https://github.com/RazrFalcon/resvg/compare/v0.5.0...v0.6.0 [0.5.0]: https://github.com/RazrFalcon/resvg/compare/v0.4.0...v0.5.0 [0.4.0]: https://github.com/RazrFalcon/resvg/compare/v0.3.0...v0.4.0 [0.3.0]: https://github.com/RazrFalcon/resvg/compare/v0.2.0...v0.3.0 [0.2.0]: https://github.com/RazrFalcon/resvg/compare/v0.1.0...v0.2.0 resvg-0.8.0/Cargo.lock000066400000000000000000000634531352576375700146110ustar00rootroot00000000000000# This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] name = "adler32" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "arrayvec" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "autocfg" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "base64" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "bitflags" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "byteorder" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "cairo-rs" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "cairo-sys-rs 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "cairo-sys-rs" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "cc" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "cfg-if" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "cmake" version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "core-foundation" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "core-foundation-sys" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "core-graphics" version = "0.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "core-text" version = "13.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", "core-graphics 0.17.3 (registry+https://github.com/rust-lang/crates.io-index)", "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "crc32fast" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "data-url" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "deflate" version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "euclid" version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "fern" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "float-cmp" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "foreign-types" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "foreign-types-shared" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "freetype" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", "servo-freetype-sys 4.0.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "harfbuzz-sys" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cmake 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", "core-graphics 0.17.3 (registry+https://github.com/rust-lang/crates.io-index)", "core-text 13.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "freetype 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "harfbuzz_rs" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "harfbuzz-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "inflate" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "jpeg-decoder" version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "kurbo" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "libc" version = "0.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "libflate" version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "rle-decode-fast 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "take_mut 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "log" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "lyon_geom" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", "euclid 0.20.1 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "matches" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "memmap" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "nodrop" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "num-traits" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "pico-args" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "pkg-config" version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "png" version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "deflate 0.7.20 (registry+https://github.com/rust-lang/crates.io-index)", "inflate 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "raqote" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "euclid 0.20.1 (registry+https://github.com/rust-lang/crates.io-index)", "lyon_geom 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", "png 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", "sw-composite 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)", "typed-arena 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "rctree" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "redox_syscall" version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "rendersvg" version = "0.8.0" dependencies = [ "fern 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "pico-args 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "resvg 0.8.0", "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "resvg" version = "0.8.0" dependencies = [ "cairo-rs 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "jpeg-decoder 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "png 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", "raqote 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "resvg-qt 0.8.0", "resvg-skia 0.8.0", "rgb 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)", "usvg 0.8.0", ] [[package]] name = "resvg-capi" version = "0.8.0" dependencies = [ "cairo-sys-rs 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "fern 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "resvg 0.8.0", ] [[package]] name = "resvg-qt" version = "0.8.0" dependencies = [ "cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "resvg-skia" version = "0.8.0" dependencies = [ "cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "rgb" version = "0.8.13" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "rle-decode-fast" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "roxmltree" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "xmlparser 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "servo-freetype-sys" version = "4.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cmake 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "simplecss" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "siphasher" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "svgtypes" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "float-cmp 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "sw-composite" version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "take_mut" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "time" version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "ttf-parser" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "typed-arena" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "unicode-bidi" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "unicode-script" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "unicode-vo" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "usvg" version = "0.8.0" dependencies = [ "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", "data-url 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "harfbuzz_rs 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "kurbo 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "libflate 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "rctree 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "roxmltree 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "simplecss 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "svgtypes 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "ttf-parser 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-script 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-vo 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "xmlwriter 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "usvg-cli" version = "0.8.0" dependencies = [ "fern 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "pico-args 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "usvg 0.8.0", ] [[package]] name = "winapi" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "xmlparser" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "xmlwriter" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] "checksum adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7e522997b529f05601e05166c07ed17789691f562762c7f3b987263d2dedee5c" "checksum arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b8d73f9beda665eaa98ab9e4f7442bd4e7de6652587de55b2525e52e29c1b0ba" "checksum autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "22130e92352b948e7e82a49cdb0aa94f2211761117f29e052dd397c1ac33542b" "checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" "checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd" "checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" "checksum cairo-rs 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e05db47de3b0f09a222fa4bba2eab957d920d4243962a86b2d77ab401e4a359c" "checksum cairo-sys-rs 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "90a1ec04603a78c111886a385edcec396dbfbc57ea26b9e74aeea6a1fe55dcca" "checksum cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)" = "b548a4ee81fccb95919d4e22cfea83c7693ebfd78f0495493178db20b3139da7" "checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" "checksum cmake 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "3c84c596dcf125d6781f58e3f4254677ec2a6d8aa56e8501ac277100990b3229" "checksum core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d" "checksum core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" "checksum core-graphics 0.17.3 (registry+https://github.com/rust-lang/crates.io-index)" = "56790968ab1c8a1202a102e6de05fc6e1ec87da99e4e93e9a7d13efbfc1e95a9" "checksum core-text 13.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "12684243b314c95600a2b49628fb775f91d97bbe18424522f665b77014f2a640" "checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" "checksum data-url 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d33fe99ccedd6e84bc035f1931bb2e6be79739d6242bd895e7311c886c50dc9c" "checksum deflate 0.7.20 (registry+https://github.com/rust-lang/crates.io-index)" = "707b6a7b384888a70c8d2e8650b3e60170dfc6a67bb4aa67b6dfca57af4bedb4" "checksum euclid 0.20.1 (registry+https://github.com/rust-lang/crates.io-index)" = "89c879a4e57d6a2785d517b0771ea6857916173debef0102bf81142d36ca9254" "checksum fern 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)" = "29d26fa0f4d433d1956746e66ec10d6bf4d6c8b93cd39965cceea7f7cc78c7dd" "checksum float-cmp 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7ef4eee449a2818084dad09f4fcd6e6e8932c482d8d94298493226782bb45b5e" "checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" "checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" "checksum freetype 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "11926b2b410b469d0e9399eca4cbbe237a9ef02176c485803b29216307e8e028" "checksum harfbuzz-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e1042ab0b3e7bc1ff64f7f5935778b644ff2194a1cae5ec52167127d3fd23961" "checksum harfbuzz_rs 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "534c8e9b15d8db6e69654b07dad955f4132757194e7d2bba620d38cf08996088" "checksum inflate 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1cdb29978cc5797bd8dcc8e5bf7de604891df2a8dc576973d71a281e916db2ff" "checksum jpeg-decoder 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "c8b7d43206b34b3f94ea9445174bda196e772049b9bddbc620c9d29b2d20110d" "checksum kurbo 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e6076333105a72e8d2c227ba6a6da0dc3c8e5f53f02053f598a6087a1ea8991" "checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba" "checksum libflate 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)" = "45c97cf62125b79dcac52d506acdc4799f21a198597806947fd5f40dc7b93412" "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" "checksum lyon_geom 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "69589b8844c0b3745cc031a35b62bc33b0fb9e5ba7613756d802c52861dcdb4c" "checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" "checksum memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" "checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" "checksum pico-args 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2fce25154205cf4360b456fd7d48994afe20663b77e3bd3d0a353a2fccf7f22c" "checksum pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c1d2cfa5a714db3b5f24f0915e74fcdf91d09d496ba61329705dda7774d2af" "checksum png 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8422b27bb2c013dd97b9aef69e161ce262236f49aaf46a0489011c8ff0264602" "checksum raqote 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d433a1b1cbf1892f7228392d41242e041d3347bdb90e9bab33dcc251ab0604db" "checksum rctree 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "be9e29cb19c8fe84169fcb07f8f11e66bc9e6e0280efd4715c54818296f8a4a8" "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" "checksum rgb 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)" = "4f089652ca87f5a82a62935ec6172a534066c7b97be003cc8f702ee9a7a59c92" "checksum rle-decode-fast 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cabe4fa914dec5870285fa7f71f602645da47c486e68486d2b4ceb4a343e90ac" "checksum roxmltree 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "153c367ce9fb8ef7afe637ef92bd083ba0f88b03ef3fcf0287d40be05ae0a61c" "checksum servo-freetype-sys 4.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "2c4ccb6d0d32d277d3ef7dea86203d8210945eb7a45fba89dd445b3595dd0dfc" "checksum simplecss 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "596554e63596d556a0dbd681416342ca61c75f1a45203201e7e77d3fa2fa9014" "checksum siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" "checksum svgtypes 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9c536faaff1a10837cfe373142583f6e27d81e96beba339147e77b67c9f260ff" "checksum sw-composite 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)" = "5eba1755094da86216f071f7a28b0453345c3e6e558ea2fd7821c55eef8fb9b2" "checksum take_mut 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" "checksum ttf-parser 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc0a53b6497df87f6a12b428a3d78b45c2916c70cd212138ca4781b02f4c0e" "checksum typed-arena 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7f70f5c346cc11bc044ae427ab2feae213350dca9e2d637047797d5ff316a646" "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" "checksum unicode-script 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "09f03ad95feb4fde244d79985bfd79eb34ff2702fedb441d2ba3f4ff813efd19" "checksum unicode-vo 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" "checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" "checksum xmlparser 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ecec95f00fb0ff019153e64ea520f87d1409769db3e8f4db3ea588638a3e1cee" "checksum xmlwriter 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" resvg-0.8.0/Cargo.toml000066400000000000000000000025331352576375700146240ustar00rootroot00000000000000[package] name = "resvg" version = "0.8.0" authors = ["Evgeniy Reizner "] keywords = ["svg", "render", "raster"] license = "MPL-2.0" edition = "2018" description = "An SVG rendering library." documentation = "https://docs.rs/resvg/" repository = "https://github.com/RazrFalcon/resvg" readme = "README.md" categories = ["multimedia::images"] [workspace] members = [ "bindings/resvg-qt", "bindings/resvg-skia", "capi", "tools/rendersvg", "tools/usvg", "usvg", ] [badges] travis-ci = { repository = "RazrFalcon/resvg" } [dependencies] log = "0.4" rgb = "0.8" usvg = { path = "usvg", version = "0.8" } png = { version = "0.15", default-features = false } jpeg-decoder = { version = "0.1.15", default-features = false } # cairo backend cairo-rs = { version = "0.7.1", default-features = false, features = ["png"], optional = true } # qt backend resvg-qt = { path = "bindings/resvg-qt", version = "0.8", optional = true } # raqote backend raqote = { version = "0.6.1", default-features = false, optional = true } # skia backend resvg-skia = { path = "bindings/resvg-skia", version = "0.8", optional = true } [features] cairo-backend = ["cairo-rs", "png/png-encoding"] qt-backend = ["resvg-qt"] raqote-backend = ["raqote"] skia-backend = ["resvg-skia"] [package.metadata.docs.rs] all-features = true [lib] doctest = false resvg-0.8.0/LICENSE.txt000066400000000000000000000405261352576375700145230ustar00rootroot00000000000000Mozilla Public License Version 2.0 ================================== 1. Definitions -------------- 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or (b) that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or (b) any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions -------------------------------- 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and (b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: (a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or (c) under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities ------------------- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation --------------------------------------------------- If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination -------------- 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ************************************************************************ * * * 6. Disclaimer of Warranty * * ------------------------- * * * * Covered Software is provided under this License on an "as is" * * basis, without warranty of any kind, either expressed, implied, or * * statutory, including, without limitation, warranties that the * * Covered Software is free of defects, merchantable, fit for a * * particular purpose or non-infringing. The entire risk as to the * * quality and performance of the Covered Software is with You. * * Should any Covered Software prove defective in any respect, You * * (not any Contributor) assume the cost of any necessary servicing, * * repair, or correction. This disclaimer of warranty constitutes an * * essential part of this License. No use of any Covered Software is * * authorized under this License except under this disclaimer. * * * ************************************************************************ ************************************************************************ * * * 7. Limitation of Liability * * -------------------------- * * * * Under no circumstances and under no legal theory, whether tort * * (including negligence), contract, or otherwise, shall any * * Contributor, or anyone who distributes Covered Software as * * permitted above, be liable to You for any direct, indirect, * * special, incidental, or consequential damages of any character * * including, without limitation, damages for lost profits, loss of * * goodwill, work stoppage, computer failure or malfunction, or any * * and all other commercial damages or losses, even if such party * * shall have been informed of the possibility of such damages. This * * limitation of liability shall not apply to liability for death or * * personal injury resulting from such party's negligence to the * * extent applicable law prohibits such limitation. Some * * jurisdictions do not allow the exclusion or limitation of * * incidental or consequential damages, so this exclusion and * * limitation may not apply to You. * * * ************************************************************************ 8. Litigation ------------- Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ---------------- This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License --------------------------- 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice ------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice --------------------------------------------------------- This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. resvg-0.8.0/README.md000066400000000000000000000117451352576375700141600ustar00rootroot00000000000000## resvg [![Build Status](https://travis-ci.org/RazrFalcon/resvg.svg?branch=master)](https://travis-ci.org/RazrFalcon/resvg) [![Crates.io](https://img.shields.io/crates/v/resvg.svg)](https://crates.io/crates/resvg) [![Documentation](https://docs.rs/resvg/badge.svg)](https://docs.rs/resvg) *resvg* is an [SVG](https://en.wikipedia.org/wiki/Scalable_Vector_Graphics) rendering library. ## Purpose *resvg* can be used as: - a Rust library - a C library (see [capi](./capi)) - a CLI application (see [tools/rendersvg](./tools/rendersvg)) to render SVG files based on a [static](http://www.w3.org/TR/SVG11/feature#SVG-static) [SVG Full 1.1](https://www.w3.org/TR/SVG/Overview.html) subset to raster images or to a backend's canvas (e.g. to a QWidget via QPainter). The core idea is to make a fast, small, portable, multiple-backend SVG library designed for edge-cases. Another major difference from other SVG rendering libraries is that *resvg* does a lot of preprocessing before rendering. It converts an input SVG into a simplified one called [Micro SVG](./docs/usvg_spec.adoc) and only then it begins rendering. So it's very easy to implement a new rendering backend. And you can also access *Micro SVG* as XML directly via [usvg](./tools/usvg) tool. ## SVG support *resvg* is aiming to support only the [static](http://www.w3.org/TR/SVG11/feature#SVG-static) SVG subset; e.g. no `a`, `script`, `view` or `cursor` elements, no events and no animations. A list of unsupported features can be found [here](docs/unsupported.md). [SVG Tiny 1.2](https://www.w3.org/TR/SVGTiny12/) and [SVG 2.0](https://www.w3.org/TR/SVG2/) are not supported and not planned. Results of the static subset of the [SVG test suite](https://www.w3.org/Graphics/SVG/Test/20110816/): ![Chart1](./.github/official_chart.svg) Results of the [resvg test suite](https://github.com/RazrFalcon/resvg-test-suite): ![Chart2](./.github/chart.svg) You can find a complete table of supported features [here](https://razrfalcon.github.io/resvg-test-suite/svg-support-table.html). It also includes alternative libraries. ## Performance Note that all tested applications have a different SVG support, which impacts the performance. Also, we do not test against Chrome, Firefox, Inkscape and Batik because they have a huge startup time. ![Chart3](./.github/perf-elementary.svg) - Elementary Icon Theme contains 3417 files. - Qt backend is slow because `QRasterPaintEngine` is slow. ![Chart4](./.github/perf-oxygen.svg) - The Oxygen icon theme contains 4947 files. - All images were converted from `.svgz` to `.svg` beforehand. - `resvg` is slower than `librsvg` because the Oxygen icon theme uses Gaussian blur heavily, and `librsvg` has a faster blur implementation. Also, `librsvg` uses native `cairo` clipping, [which is incorrect](https://razrfalcon.github.io/resvg-test-suite/svg-support-table.html#e-clipPath) but faster. - 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`](./usvg) – an SVG simplification tool - [`roxmltree`](https://github.com/RazrFalcon/roxmltree) – a DOM-like XML tree - [`xmlparser`](https://github.com/RazrFalcon/xmlparser) – an XML parser - [`svgtypes`](https://github.com/RazrFalcon/svgtypes) – SVG types parser and writer - [`simplecss`](https://github.com/RazrFalcon/simplecss) – a simple CSS2 parser - [`ttf-parser`](https://github.com/RazrFalcon/ttf-parser) – a TrueType/OpenType parser - [`xmlwriter`](https://github.com/RazrFalcon/xmlwriter) – a simple XML writer - [`rctree`](https://github.com/RazrFalcon/rctree) – a DOM-like tree - [`resvg-qt`](./bindings/resvg-qt) – minimal bindings to [Qt] - [`resvg-skia`](./bindings/resvg-skia) – minimal bindings to [Skia] All other dependencies aren't written by me for this project. ## Directory structure - `bindings` – minimal bindings to Qt and Skia used by *resvg* - `capi` – C interface for *resvg* - `docs` – basic documentation - `examples` – usage examples for *resvg* as a library - `src` – source code - `testing-tools` – scripts used for testing - `tools` – useful tools - `usvg` – an SVG simplification library used by *resvg* ## Safety - The library must not panic. Any panic should be considered a critical bug and should be reported. There are only a few methods that can produce a panic. - The core library structure (see above) does not use any `unsafe`, but since all backends are implemented via FFI, we are stuck with `unsafe` anyway. Also, `usvg` uses unsafe for fonts memory mapping. ## License *resvg* is licensed under the [MPLv2.0](https://www.mozilla.org/en-US/MPL/). [Inkscape]: https://www.inkscape.org [librsvg]: https://wiki.gnome.org/action/show/Projects/LibRsvg [QtSvg]: https://doc.qt.io/qt-5/qtsvg-index.html [cairo]: https://www.cairographics.org/ [Qt]: https://www.qt.io/ [Skia]: https://skia.org/ [GNOME]: https://www.gnome.org/ resvg-0.8.0/bindings/000077500000000000000000000000001352576375700144665ustar00rootroot00000000000000resvg-0.8.0/bindings/resvg-qt/000077500000000000000000000000001352576375700162365ustar00rootroot00000000000000resvg-0.8.0/bindings/resvg-qt/Cargo.toml000066400000000000000000000005201352576375700201630ustar00rootroot00000000000000[package] name = "resvg-qt" version = "0.8.0" authors = ["Reizner Evgeniy "] keywords = ["qt", "ffi"] license = "MIT" edition = "2018" description = "A minimal bindings to Qt used by resvg." repository = "https://github.com/RazrFalcon/resvg" workspace = "../.." [build-dependencies] cc = "1.0" pkg-config = "0.3" resvg-0.8.0/bindings/resvg-qt/LICENSE.txt000066400000000000000000000020601352576375700200570ustar00rootroot00000000000000MIT License Copyright (c) 2019 Reizner Evgeniy 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. resvg-0.8.0/bindings/resvg-qt/README.md000066400000000000000000000001051352576375700175110ustar00rootroot00000000000000# resvg-qt *resvg-qt* is a minimal Qt bindings used by the *resvg*. resvg-0.8.0/bindings/resvg-qt/build.rs000066400000000000000000000037271352576375700177140ustar00rootroot00000000000000#[cfg(target_os = "linux")] fn main() { let mut build = cc::Build::new(); build.cpp(true); build.flag("-std=c++11"); build.file("cpp/qt_capi.cpp").include("cpp"); let lib = pkg_config::find_library("Qt5Gui").expect("Unable to find Qt5Gui"); for path in lib.include_paths { build.include(path.to_str().expect("Failed to convert include path to str")); } build.compile("libqtc.a"); } #[cfg(target_os = "windows")] fn main() { let qt_dir = std::env::var("QT_DIR").expect("QT_DIR is not set"); let qt_path = std::path::Path::new(&qt_dir); let mut build = cc::Build::new(); let tool = build.get_compiler(); build.cpp(true); build.file("cpp/qt_capi.cpp").include("cpp"); build.include(qt_path.join("include")); build.include(qt_path.join("include").join("QtCore")); build.include(qt_path.join("include").join("QtGui")); if tool.is_like_msvc() { build.compile("libqtc.lib"); } else { build.flag("-std=c++11"); build.compile("libqtc.a"); } println!("cargo:rustc-link-search={}", qt_path.join("bin").display()); // for MinGW println!("cargo:rustc-link-search={}", qt_path.join("lib").display()); // for MSVC println!("cargo:rustc-link-lib=Qt5Core"); println!("cargo:rustc-link-lib=Qt5Gui"); } #[cfg(target_os = "macos")] fn main() { let qt_dir = std::env::var("QT_DIR").expect("QT_DIR is not set"); let qt_path = std::path::Path::new(&qt_dir); let mut build = cc::Build::new(); build.cpp(true); build.flag("-std=c++11"); build.flag(&format!("-F{}/lib", qt_dir)); build.file("cpp/qt_capi.cpp").include("cpp"); build.include(qt_path.join("lib/QtGui.framework/Headers")); build.include(qt_path.join("lib/QtCore.framework/Headers")); build.compile("libqtc.a"); println!("cargo:rustc-link-search=framework={}/lib", qt_dir); println!("cargo:rustc-link-lib=framework=QtCore"); println!("cargo:rustc-link-lib=framework=QtGui"); } resvg-0.8.0/bindings/resvg-qt/cpp/000077500000000000000000000000001352576375700170205ustar00rootroot00000000000000resvg-0.8.0/bindings/resvg-qt/cpp/qt_capi.cpp000066400000000000000000000256031352576375700211520ustar00rootroot00000000000000#include #include #include #include #include #include "qt_capi.hpp" #define IMAGE_CAST reinterpret_cast(c_img) #define PAINTER_CAST reinterpret_cast(c_p) #define PATH_CAST reinterpret_cast(c_pp) #define TRANSFORM_CAST reinterpret_cast(c_ts) #define PEN_CAST reinterpret_cast(c_pen) #define BRUSH_CAST reinterpret_cast(c_brush) #define LG_CAST reinterpret_cast(c_lg) #define RG_CAST reinterpret_cast(c_rg) extern "C" { // QImage qtc_qimage * qtc_qimage_create_rgba_premultiplied(uint32_t width, uint32_t height) { QImage *img = new QImage(width, height, QImage::Format_ARGB32_Premultiplied); if (img->isNull()) { return 0; } return reinterpret_cast(img); } qtc_qimage * qtc_qimage_create_rgba(uint32_t width, uint32_t height) { QImage *img = new QImage(width, height, QImage::Format_ARGB32); if (img->isNull()) { return 0; } return reinterpret_cast(img); } uint8_t* qtc_qimage_get_data(qtc_qimage *c_img) { return IMAGE_CAST->bits(); } uint32_t qtc_qimage_get_byte_count(qtc_qimage *c_img) { return IMAGE_CAST->byteCount(); } qtc_qimage* qtc_qimage_resize(qtc_qimage *c_img, uint32_t width, uint32_t height, AspectRatioMode ratio, bool smoothTransformation) { const auto mode = smoothTransformation ? Qt::SmoothTransformation : Qt::FastTransformation; const QImage rImg = IMAGE_CAST->scaled(width, height, Qt::AspectRatioMode(ratio), mode); return reinterpret_cast(new QImage(rImg)); } qtc_qimage* qtc_qimage_copy(qtc_qimage *c_img, uint32_t x, uint32_t y, uint32_t width, uint32_t height) { const QImage rImg = IMAGE_CAST->copy(x, y, width, height); return reinterpret_cast(new QImage(rImg)); } void qtc_qimage_fill(qtc_qimage *c_img, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { IMAGE_CAST->fill(QColor(r, g, b, a)); } qtc_qimage* qtc_qimage_to_rgba(qtc_qimage *c_img) { const QImage rImg = IMAGE_CAST->convertToFormat(QImage::Format_ARGB32); return reinterpret_cast(new QImage(rImg)); } uint32_t qtc_qimage_get_width(qtc_qimage *c_img) { return IMAGE_CAST->width(); } uint32_t qtc_qimage_get_height(qtc_qimage *c_img) { return IMAGE_CAST->height(); } bool qtc_qimage_save(qtc_qimage *c_img, const char *path) { QImageWriter writer(QString::fromUtf8(path)); writer.setCompression(20); // Use a lower ratio to speed up compression. return writer.write(*IMAGE_CAST); } void qtc_qimage_destroy(qtc_qimage *c_img) { delete IMAGE_CAST; } // QPainter qtc_qpainter *qtc_qpainter_create(qtc_qimage *c_img) { auto p = new QPainter(); p->begin(IMAGE_CAST); p->setPen(Qt::NoPen); p->setBrush(Qt::NoBrush); p->setRenderHint(QPainter::Antialiasing, true); p->setRenderHint(QPainter::SmoothPixmapTransform, true); return reinterpret_cast(p); } void qtc_qpainter_set_antialiasing(qtc_qpainter *c_p, bool flag) { PAINTER_CAST->setRenderHint(QPainter::Antialiasing, flag); } void qtc_qpainter_set_smooth_pixmap_transform(qtc_qpainter *c_p, bool flag) { PAINTER_CAST->setRenderHint(QPainter::SmoothPixmapTransform, flag); } void qtc_qpainter_set_pen(qtc_qpainter *c_p, qtc_qpen *c_pen) { PAINTER_CAST->setPen(*PEN_CAST); } void qtc_qpainter_reset_pen(qtc_qpainter *c_p) { PAINTER_CAST->setPen(Qt::NoPen); } void qtc_qpainter_set_brush(qtc_qpainter *c_p, qtc_qbrush *c_brush) { PAINTER_CAST->setBrush(*BRUSH_CAST); } void qtc_qpainter_reset_brush(qtc_qpainter *c_p) { PAINTER_CAST->setBrush(Qt::NoBrush); } void qtc_qpainter_set_opacity(qtc_qpainter *c_p, double opacity) { PAINTER_CAST->setOpacity(opacity); } void qtc_qpainter_draw_path(qtc_qpainter *c_p, qtc_qpainterpath *c_pp) { PAINTER_CAST->drawPath(*PATH_CAST); } void qtc_qpainter_draw_image(qtc_qpainter *c_p, double x, double y, qtc_qimage *c_img) { PAINTER_CAST->drawImage(QPointF(x, y), *IMAGE_CAST); } void qtc_qpainter_draw_image_rect(qtc_qpainter *c_p, double x, double y, double w, double h, qtc_qimage *c_img) { PAINTER_CAST->drawImage(QRectF(x, y, w, h), *IMAGE_CAST); } void qtc_qpainter_draw_text(qtc_qpainter *c_p, double x, double y, const char *c_text) { auto p = PAINTER_CAST; const QString text = QString::fromUtf8(c_text); QPainterPath path; path.addText(QPointF(x, y + p->fontMetrics().ascent()), p->font(), text); p->drawPath(path); } void qtc_qpainter_draw_rect(qtc_qpainter *c_p, double x, double y, double w, double h) { PAINTER_CAST->drawRect(QRectF(x, y, w, h)); } void qtc_qpainter_translate(qtc_qpainter *c_p, double tx, double ty) { PAINTER_CAST->translate(tx, ty); } void qtc_qpainter_scale(qtc_qpainter *c_p, double sx, double sy) { PAINTER_CAST->scale(sx, sy); } qtc_qtransform *qtc_qpainter_get_transform(qtc_qpainter *c_p) { const auto ts = PAINTER_CAST->transform(); return reinterpret_cast(new QTransform(ts)); } void qtc_qpainter_set_transform(qtc_qpainter *c_p, qtc_qtransform *c_ts, bool combine) { PAINTER_CAST->setTransform(*TRANSFORM_CAST, combine); } void qtc_qpainter_set_clip_rect(qtc_qpainter *c_p, double x, double y, double w, double h) { PAINTER_CAST->setClipRect(QRectF(x, y, w, h)); } void qtc_qpainter_set_clip_path(qtc_qpainter *c_p, qtc_qpainterpath *c_pp) { PAINTER_CAST->setClipPath(*PATH_CAST); } void qtc_qpainter_reset_clip_path(qtc_qpainter *c_p) { PAINTER_CAST->setClipPath(QPainterPath(), Qt::NoClip); } void qtc_qpainter_set_composition_mode(qtc_qpainter *c_p, CompositionMode mode) { PAINTER_CAST->setCompositionMode(QPainter::CompositionMode(mode)); } void qtc_qpainter_end(qtc_qpainter *c_p) { PAINTER_CAST->end(); } void qtc_qpainter_destroy(qtc_qpainter *c_p) { delete PAINTER_CAST; } // QPainterPath qtc_qpainterpath *qtc_qpainterpath_create() { return reinterpret_cast(new QPainterPath()); } void qtc_qpainterpath_move_to(qtc_qpainterpath *c_pp, double x, double y) { PATH_CAST->moveTo(x, y); } void qtc_qpainterpath_line_to(qtc_qpainterpath *c_pp, double x, double y) { PATH_CAST->lineTo(x, y); } void qtc_qpainterpath_curve_to(qtc_qpainterpath *c_pp, double x1, double y1, double x2, double y2, double x, double y) { PATH_CAST->cubicTo(x1, y1, x2, y2, x, y); } void qtc_qpainterpath_close_path(qtc_qpainterpath *c_pp) { PATH_CAST->closeSubpath(); } void qtc_qpainterpath_set_fill_rule(qtc_qpainterpath *c_pp, FillRule rule) { PATH_CAST->setFillRule(Qt::FillRule(rule)); } void qtc_qpainterpath_destroy(qtc_qpainterpath *c_pp) { delete PATH_CAST; } // QTransform qtc_qtransform *qtc_qtransform_create() { return reinterpret_cast(new QTransform()); } qtc_qtransform *qtc_qtransform_create_from(double a, double b, double c, double d, double e, double f) { return reinterpret_cast(new QTransform(a, b, c, d, e, f)); } qtc_transform qtc_qtransform_get_data(qtc_qtransform *c_ts) { const auto ts = TRANSFORM_CAST; qtc_transform raw_ts; raw_ts.a = ts->m11(); raw_ts.b = ts->m12(); raw_ts.c = ts->m21(); raw_ts.d = ts->m22(); raw_ts.e = ts->m31(); raw_ts.f = ts->m32(); return raw_ts; } void qtc_qtransform_destroy(qtc_qtransform *c_ts) { delete TRANSFORM_CAST; } // QPen qtc_qpen *qtc_qpen_create() { return reinterpret_cast(new QPen()); } void qtc_qpen_destroy(qtc_qpen *c_pen) { delete PEN_CAST; } void qtc_qpen_set_color(qtc_qpen *c_pen, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { PEN_CAST->setColor(QColor(r, g, b, a)); } void qtc_qpen_set_brush(qtc_qpen *c_pen, qtc_qbrush *c_brush) { PEN_CAST->setBrush(*BRUSH_CAST); } void qtc_qpen_set_line_cap(qtc_qpen *c_pen, PenCapStyle s) { PEN_CAST->setCapStyle(Qt::PenCapStyle(s)); } void qtc_qpen_set_line_join(qtc_qpen *c_pen, PenJoinStyle s) { PEN_CAST->setJoinStyle(Qt::PenJoinStyle(s)); } void qtc_qpen_set_width(qtc_qpen *c_pen, double width) { PEN_CAST->setWidthF(width); } void qtc_qpen_set_miter_limit(qtc_qpen *c_pen, double limit) { PEN_CAST->setMiterLimit(limit); } void qtc_qpen_set_dash_offset(qtc_qpen *c_pen, double offset) { qreal w = PEN_CAST->widthF(); if (w == 0) { w = 1; } PEN_CAST->setDashOffset(offset / w); } void qtc_qpen_set_dash_array(qtc_qpen *c_pen, const double *array, int len) { QVector dashes; dashes.reserve(len); qreal w = PEN_CAST->widthF(); if (qFuzzyIsNull(w)) { w = 1; } for (int i = 0; i < len; ++i) { dashes << array[i] / w; } PEN_CAST->setDashPattern(dashes); } // QBrush qtc_qbrush *qtc_qbrush_create() { return reinterpret_cast(new QBrush(Qt::SolidPattern)); } void qtc_qbrush_set_color(qtc_qbrush *c_brush, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { BRUSH_CAST->setColor(QColor(r, g, b, a)); } void qtc_qbrush_set_linear_gradient(qtc_qbrush *c_brush, qtc_qlineargradient *c_lg) { *BRUSH_CAST = QBrush(*LG_CAST); } void qtc_qbrush_set_radial_gradient(qtc_qbrush *c_brush, qtc_qradialgradient *c_rg) { *BRUSH_CAST = QBrush(*RG_CAST); } void qtc_qbrush_set_pattern(qtc_qbrush *c_brush, qtc_qimage *c_img) { BRUSH_CAST->setTextureImage(*IMAGE_CAST); } void qtc_qbrush_set_transform(qtc_qbrush *c_brush, qtc_qtransform *c_ts) { BRUSH_CAST->setTransform(*TRANSFORM_CAST); } void qtc_qbrush_destroy(qtc_qbrush *c_brush) { delete BRUSH_CAST; } // QLinearGradient qtc_qlineargradient *qtc_qlineargradient_create(double x1, double y1, double x2, double y2) { auto lg = new QLinearGradient(x1, y1, x2, y2); lg->setInterpolationMode(QGradient::ComponentInterpolation); return reinterpret_cast(lg); } void qtc_qlineargradient_set_color_at(qtc_qlineargradient *c_lg, double offset, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { LG_CAST->setColorAt(offset, QColor(r, g, b, a)); } void qtc_qlineargradient_set_spread(qtc_qlineargradient *c_lg, Spread s) { LG_CAST->setSpread(QGradient::Spread(s)); } void qtc_qlineargradient_destroy(qtc_qlineargradient *c_lg) { delete LG_CAST; } // QRadialGradient qtc_qradialgradient *qtc_qradialgradient_create(double cx, double cy, double fx, double fy, double r) { auto rg = new QRadialGradient(cx, cy, r, fx, fy); rg->setInterpolationMode(QGradient::ComponentInterpolation); return reinterpret_cast(rg); } void qtc_qradialgradient_set_color_at(qtc_qradialgradient *c_rg, double offset, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { RG_CAST->setColorAt(offset, QColor(r, g, b, a)); } void qtc_qradialgradient_set_spread(qtc_qradialgradient *c_rg, Spread s) { RG_CAST->setSpread(QGradient::Spread(s)); } void qtc_qradialgradient_destroy(qtc_qradialgradient *c_rg) { delete RG_CAST; } } resvg-0.8.0/bindings/resvg-qt/cpp/qt_capi.hpp000066400000000000000000000161041352576375700211530ustar00rootroot00000000000000#ifndef QT_CAPI_H #define QT_CAPI_H #include #define INIT_STRUCT(x) \ struct x; \ typedef struct x x; INIT_STRUCT(qtc_qimage) INIT_STRUCT(qtc_qpainter) INIT_STRUCT(qtc_qpainterpath) INIT_STRUCT(qtc_qtransform) INIT_STRUCT(qtc_qpen) INIT_STRUCT(qtc_qbrush) INIT_STRUCT(qtc_qlineargradient) INIT_STRUCT(qtc_qradialgradient) #undef INIT_STRUCT struct qtc_transform { double a; double b; double c; double d; double e; double f; }; // A direct copy from qnamespace.h. enum PenCapStyle { FlatCap = 0x00, SquareCap = 0x10, RoundCap = 0x20, }; // A direct copy from qnamespace.h. enum PenJoinStyle { BevelJoin = 0x40, RoundJoin = 0x80, MiterJoin = 0x100, }; // A direct copy from qnamespace.h. enum FillRule { OddEvenFill, WindingFill, }; // A direct copy from qbrush.h. enum Spread { PadSpread, ReflectSpread, RepeatSpread, }; // TODO: remove prefix somehow // A direct copy from qpainter.h. enum CompositionMode { CompositionMode_SourceOver, CompositionMode_DestinationOver, CompositionMode_Clear, CompositionMode_Source, CompositionMode_Destination, CompositionMode_SourceIn, CompositionMode_DestinationIn, CompositionMode_SourceOut, CompositionMode_DestinationOut, CompositionMode_SourceAtop, CompositionMode_DestinationAtop, CompositionMode_Xor, // SVG 1.2 blend modes CompositionMode_Plus, CompositionMode_Multiply, CompositionMode_Screen, CompositionMode_Overlay, CompositionMode_Darken, CompositionMode_Lighten, CompositionMode_ColorDodge, CompositionMode_ColorBurn, CompositionMode_HardLight, CompositionMode_SoftLight, CompositionMode_Difference, CompositionMode_Exclusion, }; enum AspectRatioMode { IgnoreAspectRatio, KeepAspectRatio, KeepAspectRatioByExpanding, }; extern "C" { // QImage qtc_qimage* qtc_qimage_create_rgba_premultiplied(uint32_t width, uint32_t height); qtc_qimage* qtc_qimage_create_rgba(uint32_t width, uint32_t height); uint8_t* qtc_qimage_get_data(qtc_qimage *c_img); uint32_t qtc_qimage_get_byte_count(qtc_qimage *c_img); qtc_qimage* qtc_qimage_resize(qtc_qimage *c_img, uint32_t width, uint32_t height, AspectRatioMode ratio, bool smoothTransformation); qtc_qimage* qtc_qimage_copy(qtc_qimage *c_img, uint32_t x, uint32_t y, uint32_t width, uint32_t height); void qtc_qimage_fill(qtc_qimage *c_img, uint8_t r, uint8_t g, uint8_t b, uint8_t a); qtc_qimage *qtc_qimage_to_rgba(qtc_qimage *c_img); uint32_t qtc_qimage_get_width(qtc_qimage *c_img); uint32_t qtc_qimage_get_height(qtc_qimage *c_img); bool qtc_qimage_save(qtc_qimage *c_img, const char *path); void qtc_qimage_destroy(qtc_qimage *c_img); // QPainter qtc_qpainter* qtc_qpainter_create(qtc_qimage *c_img); void qtc_qpainter_set_antialiasing(qtc_qpainter *c_p, bool flag); void qtc_qpainter_set_smooth_pixmap_transform(qtc_qpainter *c_p, bool flag); void qtc_qpainter_set_pen(qtc_qpainter *c_p, qtc_qpen *c_pen); void qtc_qpainter_reset_pen(qtc_qpainter *c_p); void qtc_qpainter_set_brush(qtc_qpainter *c_p, qtc_qbrush *c_brush); void qtc_qpainter_reset_brush(qtc_qpainter *c_p); void qtc_qpainter_set_opacity(qtc_qpainter *c_p, double opacity); void qtc_qpainter_draw_path(qtc_qpainter *c_p, qtc_qpainterpath *c_pp); void qtc_qpainter_draw_image(qtc_qpainter *c_p, double x, double y, qtc_qimage *c_img); void qtc_qpainter_draw_image_rect(qtc_qpainter *c_p, double x, double y, double w, double h, qtc_qimage *c_img); void qtc_qpainter_draw_text(qtc_qpainter *c_p, double x, double y, const char *c_text); void qtc_qpainter_draw_rect(qtc_qpainter *c_p, double x, double y, double w, double h); void qtc_qpainter_translate(qtc_qpainter *c_p, double tx, double ty); void qtc_qpainter_scale(qtc_qpainter *c_p, double sx, double sy); qtc_qtransform* qtc_qpainter_get_transform(qtc_qpainter *c_p); void qtc_qpainter_set_transform(qtc_qpainter *c_p, qtc_qtransform *q_ts, bool combine); void qtc_qpainter_set_clip_rect(qtc_qpainter *c_p, double x, double y, double w, double h); void qtc_qpainter_set_clip_path(qtc_qpainter *c_p, qtc_qpainterpath *c_pp); void qtc_qpainter_reset_clip_path(qtc_qpainter *c_p); void qtc_qpainter_set_composition_mode(qtc_qpainter *c_p, CompositionMode mode); void qtc_qpainter_end(qtc_qpainter *c_p); void qtc_qpainter_destroy(qtc_qpainter *c_p); // QPainterPath qtc_qpainterpath* qtc_qpainterpath_create(); void qtc_qpainterpath_move_to(qtc_qpainterpath *c_pp, double x, double y); void qtc_qpainterpath_line_to(qtc_qpainterpath *c_pp, double x, double y); void qtc_qpainterpath_curve_to(qtc_qpainterpath *c_pp, double x1, double y1, double x2, double y2, double x, double y); void qtc_qpainterpath_close_path(qtc_qpainterpath *c_pp); void qtc_qpainterpath_set_fill_rule(qtc_qpainterpath *c_pp, FillRule rule); void qtc_qpainterpath_destroy(qtc_qpainterpath *c_pp); // QTransform qtc_qtransform* qtc_qtransform_create(); qtc_qtransform* qtc_qtransform_create_from(double a, double b, double c, double d, double e, double f); qtc_transform qtc_qtransform_get_data(qtc_qtransform *c_ts); void qtc_qtransform_destroy(qtc_qtransform *c_ts); // QPen qtc_qpen* qtc_qpen_create(); void qtc_qpen_set_color(qtc_qpen *c_pen, uint8_t r, uint8_t g, uint8_t b, uint8_t a); void qtc_qpen_set_brush(qtc_qpen *c_pen, qtc_qbrush *c_brush); void qtc_qpen_set_line_cap(qtc_qpen *c_pen, PenCapStyle s); void qtc_qpen_set_line_join(qtc_qpen *c_pen, PenJoinStyle s); void qtc_qpen_set_width(qtc_qpen *c_pen, double width); void qtc_qpen_set_miter_limit(qtc_qpen *c_pen, double limit); void qtc_qpen_set_dash_offset(qtc_qpen *c_pen, double offset); void qtc_qpen_set_dash_array(qtc_qpen *c_pen, const double *array, int len); void qtc_qpen_destroy(qtc_qpen *c_pen); // QBrush qtc_qbrush* qtc_qbrush_create(); void qtc_qbrush_set_color(qtc_qbrush *c_brush, uint8_t r, uint8_t g, uint8_t b, uint8_t a); void qtc_qbrush_set_linear_gradient(qtc_qbrush *c_brush, qtc_qlineargradient *c_lg); void qtc_qbrush_set_radial_gradient(qtc_qbrush *c_brush, qtc_qradialgradient *c_rg); void qtc_qbrush_set_pattern(qtc_qbrush *c_brush, qtc_qimage *c_img); void qtc_qbrush_set_transform(qtc_qbrush *c_brush, qtc_qtransform *c_ts); void qtc_qbrush_destroy(qtc_qbrush *c_brush); // QLinearGradient qtc_qlineargradient* qtc_qlineargradient_create(double x1, double y1, double x2, double y2); void qtc_qlineargradient_set_color_at(qtc_qlineargradient *c_lg, double offset, uint8_t r, uint8_t g, uint8_t b, uint8_t a); void qtc_qlineargradient_set_spread(qtc_qlineargradient *c_lg, Spread s); void qtc_qlineargradient_destroy(qtc_qlineargradient *c_lg); // QRadialGradient qtc_qradialgradient* qtc_qradialgradient_create(double cx, double cy, double fx, double fy, double r); void qtc_qradialgradient_set_color_at(qtc_qradialgradient *c_rg, double offset, uint8_t r, uint8_t g, uint8_t b, uint8_t a); void qtc_qradialgradient_set_spread(qtc_qradialgradient *c_rg, Spread s); void qtc_qradialgradient_destroy(qtc_qradialgradient *c_rg); } #endif // QT_CAPI_H resvg-0.8.0/bindings/resvg-qt/src/000077500000000000000000000000001352576375700170255ustar00rootroot00000000000000resvg-0.8.0/bindings/resvg-qt/src/ffi.rs000066400000000000000000000536431352576375700201520ustar00rootroot00000000000000/* automatically generated by rust-bindgen */ pub const _STDINT_H: u32 = 1; pub const _FEATURES_H: u32 = 1; pub const _ISOC95_SOURCE: u32 = 1; pub const _ISOC99_SOURCE: u32 = 1; pub const _ISOC11_SOURCE: u32 = 1; pub const _POSIX_SOURCE: u32 = 1; pub const _POSIX_C_SOURCE: u32 = 200809; pub const _XOPEN_SOURCE: u32 = 700; pub const _XOPEN_SOURCE_EXTENDED: u32 = 1; pub const _LARGEFILE64_SOURCE: u32 = 1; pub const _DEFAULT_SOURCE: u32 = 1; pub const _ATFILE_SOURCE: u32 = 1; pub const __USE_ISOC11: u32 = 1; pub const __USE_ISOC99: u32 = 1; pub const __USE_ISOC95: u32 = 1; pub const __USE_ISOCXX11: u32 = 1; pub const __USE_POSIX: u32 = 1; pub const __USE_POSIX2: u32 = 1; pub const __USE_POSIX199309: u32 = 1; pub const __USE_POSIX199506: u32 = 1; pub const __USE_XOPEN2K: u32 = 1; pub const __USE_XOPEN2K8: u32 = 1; pub const __USE_XOPEN: u32 = 1; pub const __USE_XOPEN_EXTENDED: u32 = 1; pub const __USE_UNIX98: u32 = 1; pub const _LARGEFILE_SOURCE: u32 = 1; pub const __USE_XOPEN2K8XSI: u32 = 1; pub const __USE_XOPEN2KXSI: u32 = 1; pub const __USE_LARGEFILE: u32 = 1; pub const __USE_LARGEFILE64: u32 = 1; pub const __USE_MISC: u32 = 1; pub const __USE_ATFILE: u32 = 1; pub const __USE_GNU: u32 = 1; pub const __USE_FORTIFY_LEVEL: u32 = 0; pub const __GLIBC_USE_DEPRECATED_GETS: u32 = 0; pub const __GLIBC_USE_DEPRECATED_SCANF: u32 = 0; pub const _STDC_PREDEF_H: u32 = 1; pub const __STDC_IEC_559__: u32 = 1; pub const __STDC_IEC_559_COMPLEX__: u32 = 1; pub const __STDC_ISO_10646__: u32 = 201706; pub const __GNU_LIBRARY__: u32 = 6; pub const __GLIBC__: u32 = 2; pub const __GLIBC_MINOR__: u32 = 29; pub const _SYS_CDEFS_H: u32 = 1; pub const __glibc_c99_flexarr_available: u32 = 1; pub const __WORDSIZE: u32 = 64; pub const __WORDSIZE_TIME64_COMPAT32: u32 = 1; pub const __SYSCALL_WORDSIZE: u32 = 64; pub const __HAVE_GENERIC_SELECTION: u32 = 0; pub const __GLIBC_USE_LIB_EXT2: u32 = 1; pub const __GLIBC_USE_IEC_60559_BFP_EXT: u32 = 1; pub const __GLIBC_USE_IEC_60559_FUNCS_EXT: u32 = 1; pub const __GLIBC_USE_IEC_60559_TYPES_EXT: u32 = 1; pub const _BITS_TYPES_H: u32 = 1; pub const __TIMESIZE: u32 = 64; pub const _BITS_TYPESIZES_H: u32 = 1; pub const __OFF_T_MATCHES_OFF64_T: u32 = 1; pub const __INO_T_MATCHES_INO64_T: u32 = 1; pub const __RLIM_T_MATCHES_RLIM64_T: u32 = 1; pub const __FD_SETSIZE: u32 = 1024; pub const _BITS_TIME64_H: u32 = 1; pub const _BITS_WCHAR_H: u32 = 1; pub const _BITS_STDINT_INTN_H: u32 = 1; pub const _BITS_STDINT_UINTN_H: u32 = 1; pub const INT8_MIN: i32 = -128; pub const INT16_MIN: i32 = -32768; pub const INT32_MIN: i32 = -2147483648; pub const INT8_MAX: u32 = 127; pub const INT16_MAX: u32 = 32767; pub const INT32_MAX: u32 = 2147483647; pub const UINT8_MAX: u32 = 255; pub const UINT16_MAX: u32 = 65535; pub const UINT32_MAX: u32 = 4294967295; pub const INT_LEAST8_MIN: i32 = -128; pub const INT_LEAST16_MIN: i32 = -32768; pub const INT_LEAST32_MIN: i32 = -2147483648; pub const INT_LEAST8_MAX: u32 = 127; pub const INT_LEAST16_MAX: u32 = 32767; pub const INT_LEAST32_MAX: u32 = 2147483647; pub const UINT_LEAST8_MAX: u32 = 255; pub const UINT_LEAST16_MAX: u32 = 65535; pub const UINT_LEAST32_MAX: u32 = 4294967295; pub const INT_FAST8_MIN: i32 = -128; pub const INT_FAST16_MIN: i64 = -9223372036854775808; pub const INT_FAST32_MIN: i64 = -9223372036854775808; pub const INT_FAST8_MAX: u32 = 127; pub const INT_FAST16_MAX: u64 = 9223372036854775807; pub const INT_FAST32_MAX: u64 = 9223372036854775807; pub const UINT_FAST8_MAX: u32 = 255; pub const UINT_FAST16_MAX: i32 = -1; pub const UINT_FAST32_MAX: i32 = -1; pub const INTPTR_MIN: i64 = -9223372036854775808; pub const INTPTR_MAX: u64 = 9223372036854775807; pub const UINTPTR_MAX: i32 = -1; pub const PTRDIFF_MIN: i64 = -9223372036854775808; pub const PTRDIFF_MAX: u64 = 9223372036854775807; pub const SIG_ATOMIC_MIN: i32 = -2147483648; pub const SIG_ATOMIC_MAX: u32 = 2147483647; pub const SIZE_MAX: i32 = -1; pub const WINT_MIN: u32 = 0; pub const WINT_MAX: u32 = 4294967295; pub const INT8_WIDTH: u32 = 8; pub const UINT8_WIDTH: u32 = 8; pub const INT16_WIDTH: u32 = 16; pub const UINT16_WIDTH: u32 = 16; pub const INT32_WIDTH: u32 = 32; pub const UINT32_WIDTH: u32 = 32; pub const INT64_WIDTH: u32 = 64; pub const UINT64_WIDTH: u32 = 64; pub const INT_LEAST8_WIDTH: u32 = 8; pub const UINT_LEAST8_WIDTH: u32 = 8; pub const INT_LEAST16_WIDTH: u32 = 16; pub const UINT_LEAST16_WIDTH: u32 = 16; pub const INT_LEAST32_WIDTH: u32 = 32; pub const UINT_LEAST32_WIDTH: u32 = 32; pub const INT_LEAST64_WIDTH: u32 = 64; pub const UINT_LEAST64_WIDTH: u32 = 64; pub const INT_FAST8_WIDTH: u32 = 8; pub const UINT_FAST8_WIDTH: u32 = 8; pub const INT_FAST16_WIDTH: u32 = 64; pub const UINT_FAST16_WIDTH: u32 = 64; pub const INT_FAST32_WIDTH: u32 = 64; pub const UINT_FAST32_WIDTH: u32 = 64; pub const INT_FAST64_WIDTH: u32 = 64; pub const UINT_FAST64_WIDTH: u32 = 64; pub const INTPTR_WIDTH: u32 = 64; pub const UINTPTR_WIDTH: u32 = 64; pub const INTMAX_WIDTH: u32 = 64; pub const UINTMAX_WIDTH: u32 = 64; pub const PTRDIFF_WIDTH: u32 = 64; pub const SIG_ATOMIC_WIDTH: u32 = 32; pub const SIZE_WIDTH: u32 = 64; pub const WCHAR_WIDTH: u32 = 32; pub const WINT_WIDTH: u32 = 32; pub type __u_char = ::std::os::raw::c_uchar; pub type __u_short = ::std::os::raw::c_ushort; pub type __u_int = ::std::os::raw::c_uint; pub type __u_long = ::std::os::raw::c_ulong; pub type __int8_t = ::std::os::raw::c_schar; pub type __uint8_t = ::std::os::raw::c_uchar; pub type __int16_t = ::std::os::raw::c_short; pub type __uint16_t = ::std::os::raw::c_ushort; pub type __int32_t = ::std::os::raw::c_int; pub type __uint32_t = ::std::os::raw::c_uint; pub type __int64_t = ::std::os::raw::c_long; pub type __uint64_t = ::std::os::raw::c_ulong; pub type __int_least8_t = __int8_t; pub type __uint_least8_t = __uint8_t; pub type __int_least16_t = __int16_t; pub type __uint_least16_t = __uint16_t; pub type __int_least32_t = __int32_t; pub type __uint_least32_t = __uint32_t; pub type __int_least64_t = __int64_t; pub type __uint_least64_t = __uint64_t; pub type __quad_t = ::std::os::raw::c_long; pub type __u_quad_t = ::std::os::raw::c_ulong; pub type __intmax_t = ::std::os::raw::c_long; pub type __uintmax_t = ::std::os::raw::c_ulong; pub type __dev_t = ::std::os::raw::c_ulong; pub type __uid_t = ::std::os::raw::c_uint; pub type __gid_t = ::std::os::raw::c_uint; pub type __ino_t = ::std::os::raw::c_ulong; pub type __ino64_t = ::std::os::raw::c_ulong; pub type __mode_t = ::std::os::raw::c_uint; pub type __nlink_t = ::std::os::raw::c_ulong; pub type __off_t = ::std::os::raw::c_long; pub type __off64_t = ::std::os::raw::c_long; pub type __pid_t = ::std::os::raw::c_int; #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct __fsid_t { pub __val: [::std::os::raw::c_int; 2usize], } #[test] fn bindgen_test_layout___fsid_t() { assert_eq!( ::std::mem::size_of::<__fsid_t>(), 8usize, concat!("Size of: ", stringify!(__fsid_t)) ); assert_eq!( ::std::mem::align_of::<__fsid_t>(), 4usize, concat!("Alignment of ", stringify!(__fsid_t)) ); assert_eq!( unsafe { &(*(::std::ptr::null::<__fsid_t>())).__val as *const _ as usize }, 0usize, concat!( "Offset of field: ", stringify!(__fsid_t), "::", stringify!(__val) ) ); } pub type __clock_t = ::std::os::raw::c_long; pub type __rlim_t = ::std::os::raw::c_ulong; pub type __rlim64_t = ::std::os::raw::c_ulong; pub type __id_t = ::std::os::raw::c_uint; pub type __time_t = ::std::os::raw::c_long; pub type __useconds_t = ::std::os::raw::c_uint; pub type __suseconds_t = ::std::os::raw::c_long; pub type __daddr_t = ::std::os::raw::c_int; pub type __key_t = ::std::os::raw::c_int; pub type __clockid_t = ::std::os::raw::c_int; pub type __timer_t = *mut ::std::os::raw::c_void; pub type __blksize_t = ::std::os::raw::c_long; pub type __blkcnt_t = ::std::os::raw::c_long; pub type __blkcnt64_t = ::std::os::raw::c_long; pub type __fsblkcnt_t = ::std::os::raw::c_ulong; pub type __fsblkcnt64_t = ::std::os::raw::c_ulong; pub type __fsfilcnt_t = ::std::os::raw::c_ulong; pub type __fsfilcnt64_t = ::std::os::raw::c_ulong; pub type __fsword_t = ::std::os::raw::c_long; pub type __ssize_t = ::std::os::raw::c_long; pub type __syscall_slong_t = ::std::os::raw::c_long; pub type __syscall_ulong_t = ::std::os::raw::c_ulong; pub type __loff_t = __off64_t; pub type __caddr_t = *mut ::std::os::raw::c_char; pub type __intptr_t = ::std::os::raw::c_long; pub type __socklen_t = ::std::os::raw::c_uint; pub type __sig_atomic_t = ::std::os::raw::c_int; pub type int_least8_t = __int_least8_t; pub type int_least16_t = __int_least16_t; pub type int_least32_t = __int_least32_t; pub type int_least64_t = __int_least64_t; pub type uint_least8_t = __uint_least8_t; pub type uint_least16_t = __uint_least16_t; pub type uint_least32_t = __uint_least32_t; pub type uint_least64_t = __uint_least64_t; pub type int_fast8_t = ::std::os::raw::c_schar; pub type int_fast16_t = ::std::os::raw::c_long; pub type int_fast32_t = ::std::os::raw::c_long; pub type int_fast64_t = ::std::os::raw::c_long; pub type uint_fast8_t = ::std::os::raw::c_uchar; pub type uint_fast16_t = ::std::os::raw::c_ulong; pub type uint_fast32_t = ::std::os::raw::c_ulong; pub type uint_fast64_t = ::std::os::raw::c_ulong; pub type intmax_t = __intmax_t; pub type uintmax_t = __uintmax_t; #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct qtc_qimage { _unused: [u8; 0], } #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct qtc_qpainter { _unused: [u8; 0], } #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct qtc_qpainterpath { _unused: [u8; 0], } #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct qtc_qtransform { _unused: [u8; 0], } #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct qtc_qpen { _unused: [u8; 0], } #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct qtc_qbrush { _unused: [u8; 0], } #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct qtc_qlineargradient { _unused: [u8; 0], } #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct qtc_qradialgradient { _unused: [u8; 0], } #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct qtc_transform { pub a: f64, pub b: f64, pub c: f64, pub d: f64, pub e: f64, pub f: f64, } #[test] fn bindgen_test_layout_qtc_transform() { assert_eq!( ::std::mem::size_of::(), 48usize, concat!("Size of: ", stringify!(qtc_transform)) ); assert_eq!( ::std::mem::align_of::(), 8usize, concat!("Alignment of ", stringify!(qtc_transform)) ); assert_eq!( unsafe { &(*(::std::ptr::null::())).a as *const _ as usize }, 0usize, concat!( "Offset of field: ", stringify!(qtc_transform), "::", stringify!(a) ) ); assert_eq!( unsafe { &(*(::std::ptr::null::())).b as *const _ as usize }, 8usize, concat!( "Offset of field: ", stringify!(qtc_transform), "::", stringify!(b) ) ); assert_eq!( unsafe { &(*(::std::ptr::null::())).c as *const _ as usize }, 16usize, concat!( "Offset of field: ", stringify!(qtc_transform), "::", stringify!(c) ) ); assert_eq!( unsafe { &(*(::std::ptr::null::())).d as *const _ as usize }, 24usize, concat!( "Offset of field: ", stringify!(qtc_transform), "::", stringify!(d) ) ); assert_eq!( unsafe { &(*(::std::ptr::null::())).e as *const _ as usize }, 32usize, concat!( "Offset of field: ", stringify!(qtc_transform), "::", stringify!(e) ) ); assert_eq!( unsafe { &(*(::std::ptr::null::())).f as *const _ as usize }, 40usize, concat!( "Offset of field: ", stringify!(qtc_transform), "::", stringify!(f) ) ); } pub const PenCapStyle_FlatCap: PenCapStyle = 0; pub const PenCapStyle_SquareCap: PenCapStyle = 16; pub const PenCapStyle_RoundCap: PenCapStyle = 32; pub type PenCapStyle = u32; pub const PenJoinStyle_BevelJoin: PenJoinStyle = 64; pub const PenJoinStyle_RoundJoin: PenJoinStyle = 128; pub const PenJoinStyle_MiterJoin: PenJoinStyle = 256; pub type PenJoinStyle = u32; pub const FillRule_OddEvenFill: FillRule = 0; pub const FillRule_WindingFill: FillRule = 1; pub type FillRule = u32; pub const Spread_PadSpread: Spread = 0; pub const Spread_ReflectSpread: Spread = 1; pub const Spread_RepeatSpread: Spread = 2; pub type Spread = u32; pub const CompositionMode_CompositionMode_SourceOver: CompositionMode = 0; pub const CompositionMode_CompositionMode_DestinationOver: CompositionMode = 1; pub const CompositionMode_CompositionMode_Clear: CompositionMode = 2; pub const CompositionMode_CompositionMode_Source: CompositionMode = 3; pub const CompositionMode_CompositionMode_Destination: CompositionMode = 4; pub const CompositionMode_CompositionMode_SourceIn: CompositionMode = 5; pub const CompositionMode_CompositionMode_DestinationIn: CompositionMode = 6; pub const CompositionMode_CompositionMode_SourceOut: CompositionMode = 7; pub const CompositionMode_CompositionMode_DestinationOut: CompositionMode = 8; pub const CompositionMode_CompositionMode_SourceAtop: CompositionMode = 9; pub const CompositionMode_CompositionMode_DestinationAtop: CompositionMode = 10; pub const CompositionMode_CompositionMode_Xor: CompositionMode = 11; pub const CompositionMode_CompositionMode_Plus: CompositionMode = 12; pub const CompositionMode_CompositionMode_Multiply: CompositionMode = 13; pub const CompositionMode_CompositionMode_Screen: CompositionMode = 14; pub const CompositionMode_CompositionMode_Overlay: CompositionMode = 15; pub const CompositionMode_CompositionMode_Darken: CompositionMode = 16; pub const CompositionMode_CompositionMode_Lighten: CompositionMode = 17; pub const CompositionMode_CompositionMode_ColorDodge: CompositionMode = 18; pub const CompositionMode_CompositionMode_ColorBurn: CompositionMode = 19; pub const CompositionMode_CompositionMode_HardLight: CompositionMode = 20; pub const CompositionMode_CompositionMode_SoftLight: CompositionMode = 21; pub const CompositionMode_CompositionMode_Difference: CompositionMode = 22; pub const CompositionMode_CompositionMode_Exclusion: CompositionMode = 23; pub type CompositionMode = u32; pub const AspectRatioMode_IgnoreAspectRatio: AspectRatioMode = 0; pub const AspectRatioMode_KeepAspectRatio: AspectRatioMode = 1; pub const AspectRatioMode_KeepAspectRatioByExpanding: AspectRatioMode = 2; pub type AspectRatioMode = u32; extern "C" { pub fn qtc_qimage_create_rgba_premultiplied(width: u32, height: u32) -> *mut qtc_qimage; } extern "C" { pub fn qtc_qimage_create_rgba(width: u32, height: u32) -> *mut qtc_qimage; } extern "C" { pub fn qtc_qimage_get_data(c_img: *mut qtc_qimage) -> *mut u8; } extern "C" { pub fn qtc_qimage_get_byte_count(c_img: *mut qtc_qimage) -> u32; } extern "C" { pub fn qtc_qimage_resize( c_img: *mut qtc_qimage, width: u32, height: u32, ratio: AspectRatioMode, smoothTransformation: bool, ) -> *mut qtc_qimage; } extern "C" { pub fn qtc_qimage_copy( c_img: *mut qtc_qimage, x: u32, y: u32, width: u32, height: u32, ) -> *mut qtc_qimage; } extern "C" { pub fn qtc_qimage_fill(c_img: *mut qtc_qimage, r: u8, g: u8, b: u8, a: u8); } extern "C" { pub fn qtc_qimage_to_rgba(c_img: *mut qtc_qimage) -> *mut qtc_qimage; } extern "C" { pub fn qtc_qimage_get_width(c_img: *mut qtc_qimage) -> u32; } extern "C" { pub fn qtc_qimage_get_height(c_img: *mut qtc_qimage) -> u32; } extern "C" { pub fn qtc_qimage_save(c_img: *mut qtc_qimage, path: *const ::std::os::raw::c_char) -> bool; } extern "C" { pub fn qtc_qimage_destroy(c_img: *mut qtc_qimage); } extern "C" { pub fn qtc_qpainter_create(c_img: *mut qtc_qimage) -> *mut qtc_qpainter; } extern "C" { pub fn qtc_qpainter_set_antialiasing(c_p: *mut qtc_qpainter, flag: bool); } extern "C" { pub fn qtc_qpainter_set_smooth_pixmap_transform(c_p: *mut qtc_qpainter, flag: bool); } extern "C" { pub fn qtc_qpainter_set_pen(c_p: *mut qtc_qpainter, c_pen: *mut qtc_qpen); } extern "C" { pub fn qtc_qpainter_reset_pen(c_p: *mut qtc_qpainter); } extern "C" { pub fn qtc_qpainter_set_brush(c_p: *mut qtc_qpainter, c_brush: *mut qtc_qbrush); } extern "C" { pub fn qtc_qpainter_reset_brush(c_p: *mut qtc_qpainter); } extern "C" { pub fn qtc_qpainter_set_opacity(c_p: *mut qtc_qpainter, opacity: f64); } extern "C" { pub fn qtc_qpainter_draw_path(c_p: *mut qtc_qpainter, c_pp: *mut qtc_qpainterpath); } extern "C" { pub fn qtc_qpainter_draw_image(c_p: *mut qtc_qpainter, x: f64, y: f64, c_img: *mut qtc_qimage); } extern "C" { pub fn qtc_qpainter_draw_image_rect( c_p: *mut qtc_qpainter, x: f64, y: f64, w: f64, h: f64, c_img: *mut qtc_qimage, ); } extern "C" { pub fn qtc_qpainter_draw_text( c_p: *mut qtc_qpainter, x: f64, y: f64, c_text: *const ::std::os::raw::c_char, ); } extern "C" { pub fn qtc_qpainter_draw_rect(c_p: *mut qtc_qpainter, x: f64, y: f64, w: f64, h: f64); } extern "C" { pub fn qtc_qpainter_translate(c_p: *mut qtc_qpainter, tx: f64, ty: f64); } extern "C" { pub fn qtc_qpainter_scale(c_p: *mut qtc_qpainter, sx: f64, sy: f64); } extern "C" { pub fn qtc_qpainter_get_transform(c_p: *mut qtc_qpainter) -> *mut qtc_qtransform; } extern "C" { pub fn qtc_qpainter_set_transform( c_p: *mut qtc_qpainter, q_ts: *mut qtc_qtransform, combine: bool, ); } extern "C" { pub fn qtc_qpainter_set_clip_rect(c_p: *mut qtc_qpainter, x: f64, y: f64, w: f64, h: f64); } extern "C" { pub fn qtc_qpainter_set_clip_path(c_p: *mut qtc_qpainter, c_pp: *mut qtc_qpainterpath); } extern "C" { pub fn qtc_qpainter_reset_clip_path(c_p: *mut qtc_qpainter); } extern "C" { pub fn qtc_qpainter_set_composition_mode(c_p: *mut qtc_qpainter, mode: CompositionMode); } extern "C" { pub fn qtc_qpainter_end(c_p: *mut qtc_qpainter); } extern "C" { pub fn qtc_qpainter_destroy(c_p: *mut qtc_qpainter); } extern "C" { pub fn qtc_qpainterpath_create() -> *mut qtc_qpainterpath; } extern "C" { pub fn qtc_qpainterpath_move_to(c_pp: *mut qtc_qpainterpath, x: f64, y: f64); } extern "C" { pub fn qtc_qpainterpath_line_to(c_pp: *mut qtc_qpainterpath, x: f64, y: f64); } extern "C" { pub fn qtc_qpainterpath_curve_to( c_pp: *mut qtc_qpainterpath, x1: f64, y1: f64, x2: f64, y2: f64, x: f64, y: f64, ); } extern "C" { pub fn qtc_qpainterpath_close_path(c_pp: *mut qtc_qpainterpath); } extern "C" { pub fn qtc_qpainterpath_set_fill_rule(c_pp: *mut qtc_qpainterpath, rule: FillRule); } extern "C" { pub fn qtc_qpainterpath_destroy(c_pp: *mut qtc_qpainterpath); } extern "C" { pub fn qtc_qtransform_create() -> *mut qtc_qtransform; } extern "C" { pub fn qtc_qtransform_create_from( a: f64, b: f64, c: f64, d: f64, e: f64, f: f64, ) -> *mut qtc_qtransform; } extern "C" { pub fn qtc_qtransform_get_data(c_ts: *mut qtc_qtransform) -> qtc_transform; } extern "C" { pub fn qtc_qtransform_destroy(c_ts: *mut qtc_qtransform); } extern "C" { pub fn qtc_qpen_create() -> *mut qtc_qpen; } extern "C" { pub fn qtc_qpen_set_color(c_pen: *mut qtc_qpen, r: u8, g: u8, b: u8, a: u8); } extern "C" { pub fn qtc_qpen_set_brush(c_pen: *mut qtc_qpen, c_brush: *mut qtc_qbrush); } extern "C" { pub fn qtc_qpen_set_line_cap(c_pen: *mut qtc_qpen, s: PenCapStyle); } extern "C" { pub fn qtc_qpen_set_line_join(c_pen: *mut qtc_qpen, s: PenJoinStyle); } extern "C" { pub fn qtc_qpen_set_width(c_pen: *mut qtc_qpen, width: f64); } extern "C" { pub fn qtc_qpen_set_miter_limit(c_pen: *mut qtc_qpen, limit: f64); } extern "C" { pub fn qtc_qpen_set_dash_offset(c_pen: *mut qtc_qpen, offset: f64); } extern "C" { pub fn qtc_qpen_set_dash_array( c_pen: *mut qtc_qpen, array: *const f64, len: ::std::os::raw::c_int, ); } extern "C" { pub fn qtc_qpen_destroy(c_pen: *mut qtc_qpen); } extern "C" { pub fn qtc_qbrush_create() -> *mut qtc_qbrush; } extern "C" { pub fn qtc_qbrush_set_color(c_brush: *mut qtc_qbrush, r: u8, g: u8, b: u8, a: u8); } extern "C" { pub fn qtc_qbrush_set_linear_gradient(c_brush: *mut qtc_qbrush, c_lg: *mut qtc_qlineargradient); } extern "C" { pub fn qtc_qbrush_set_radial_gradient(c_brush: *mut qtc_qbrush, c_rg: *mut qtc_qradialgradient); } extern "C" { pub fn qtc_qbrush_set_pattern(c_brush: *mut qtc_qbrush, c_img: *mut qtc_qimage); } extern "C" { pub fn qtc_qbrush_set_transform(c_brush: *mut qtc_qbrush, c_ts: *mut qtc_qtransform); } extern "C" { pub fn qtc_qbrush_destroy(c_brush: *mut qtc_qbrush); } extern "C" { pub fn qtc_qlineargradient_create( x1: f64, y1: f64, x2: f64, y2: f64, ) -> *mut qtc_qlineargradient; } extern "C" { pub fn qtc_qlineargradient_set_color_at( c_lg: *mut qtc_qlineargradient, offset: f64, r: u8, g: u8, b: u8, a: u8, ); } extern "C" { pub fn qtc_qlineargradient_set_spread(c_lg: *mut qtc_qlineargradient, s: Spread); } extern "C" { pub fn qtc_qlineargradient_destroy(c_lg: *mut qtc_qlineargradient); } extern "C" { pub fn qtc_qradialgradient_create( cx: f64, cy: f64, fx: f64, fy: f64, r: f64, ) -> *mut qtc_qradialgradient; } extern "C" { pub fn qtc_qradialgradient_set_color_at( c_rg: *mut qtc_qradialgradient, offset: f64, r: u8, g: u8, b: u8, a: u8, ); } extern "C" { pub fn qtc_qradialgradient_set_spread(c_rg: *mut qtc_qradialgradient, s: Spread); } extern "C" { pub fn qtc_qradialgradient_destroy(c_rg: *mut qtc_qradialgradient); } resvg-0.8.0/bindings/resvg-qt/src/lib.rs000066400000000000000000000342041352576375700201440ustar00rootroot00000000000000use std::ffi::CString; use std::i32; use std::ops::{Deref, DerefMut}; use std::slice; #[allow(dead_code)] #[allow(non_camel_case_types)] #[allow(non_upper_case_globals)] mod ffi; pub use ffi::qtc_qpainter; #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum FillRule { OddEven = ffi::FillRule_OddEvenFill as isize, Winding = ffi::FillRule_WindingFill as isize, } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum LineCap { Flat = ffi::PenCapStyle_FlatCap as isize, Square = ffi::PenCapStyle_SquareCap as isize, Round = ffi::PenCapStyle_RoundCap as isize, } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum LineJoin { Bevel = ffi::PenJoinStyle_BevelJoin as isize, Round = ffi::PenJoinStyle_RoundJoin as isize, Miter = ffi::PenJoinStyle_MiterJoin as isize, } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum CompositionMode { SourceOver = ffi::CompositionMode_CompositionMode_SourceOver as isize, DestinationOver = ffi::CompositionMode_CompositionMode_DestinationOver as isize, Clear = ffi::CompositionMode_CompositionMode_Clear as isize, Source = ffi::CompositionMode_CompositionMode_Source as isize, Destination = ffi::CompositionMode_CompositionMode_Destination as isize, SourceIn = ffi::CompositionMode_CompositionMode_SourceIn as isize, DestinationIn = ffi::CompositionMode_CompositionMode_DestinationIn as isize, SourceOut = ffi::CompositionMode_CompositionMode_SourceOut as isize, DestinationOut = ffi::CompositionMode_CompositionMode_DestinationOut as isize, SourceAtop = ffi::CompositionMode_CompositionMode_SourceAtop as isize, DestinationAtop = ffi::CompositionMode_CompositionMode_DestinationAtop as isize, Xor = ffi::CompositionMode_CompositionMode_Xor as isize, Plus = ffi::CompositionMode_CompositionMode_Plus as isize, Multiply = ffi::CompositionMode_CompositionMode_Multiply as isize, Screen = ffi::CompositionMode_CompositionMode_Screen as isize, Overlay = ffi::CompositionMode_CompositionMode_Overlay as isize, Darken = ffi::CompositionMode_CompositionMode_Darken as isize, Lighten = ffi::CompositionMode_CompositionMode_Lighten as isize, ColorDodge = ffi::CompositionMode_CompositionMode_ColorDodge as isize, ColorBurn = ffi::CompositionMode_CompositionMode_ColorBurn as isize, HardLight = ffi::CompositionMode_CompositionMode_HardLight as isize, SoftLight = ffi::CompositionMode_CompositionMode_SoftLight as isize, Difference = ffi::CompositionMode_CompositionMode_Difference as isize, Exclusion = ffi::CompositionMode_CompositionMode_Exclusion as isize, } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum AspectRatioMode { Ignore = ffi::AspectRatioMode_IgnoreAspectRatio as isize, Keep = ffi::AspectRatioMode_KeepAspectRatio as isize, KeepByExpanding = ffi::AspectRatioMode_KeepAspectRatioByExpanding as isize, } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum Spread { Pad = ffi::Spread_PadSpread as isize, Reflect = ffi::Spread_ReflectSpread as isize, Repeat = ffi::Spread_RepeatSpread as isize, } pub struct Image(*mut ffi::qtc_qimage); impl Image { pub fn new_rgba(width: u32, height: u32) -> Option { unsafe { Self::from_ptr(ffi::qtc_qimage_create_rgba(width, height)) } } pub fn new_rgba_premultiplied(width: u32, height: u32) -> Option { unsafe { Self::from_ptr(ffi::qtc_qimage_create_rgba_premultiplied(width, height)) } } unsafe fn from_ptr(img: *mut ffi::qtc_qimage) -> Option { if img.is_null() { None } else { Some(Image(img)) } } pub fn fill(&mut self, r: u8, g: u8, b: u8, a: u8) { unsafe { ffi::qtc_qimage_fill(self.0, r, g, b, a) } } pub fn to_rgba(&self) -> Option { unsafe { Self::from_ptr(ffi::qtc_qimage_to_rgba(self.0)) } } pub fn save(&self, path: &str) -> bool { let c_path = CString::new(path).unwrap(); unsafe { ffi::qtc_qimage_save(self.0, c_path.as_ptr()) } } pub fn resize( &self, width: u32, height: u32, ratio: AspectRatioMode, smooth: bool, ) -> Option { unsafe { Self::from_ptr(ffi::qtc_qimage_resize( self.0, width, height, ratio as ffi::AspectRatioMode, smooth )) } } pub fn copy(&self, x: u32, y: u32, width: u32, height: u32) -> Option { unsafe { Self::from_ptr(ffi::qtc_qimage_copy(self.0, x, y, width, height)) } } pub fn try_clone(&self) -> Option { unsafe { Self::from_ptr(ffi::qtc_qimage_copy(self.0, 0, 0, self.width(), self.height())) } } pub fn data(&self) -> ImageData { unsafe { let ptr = ffi::qtc_qimage_get_data(self.0); let len = ffi::qtc_qimage_get_byte_count(self.0) as usize; ImageData { slice: slice::from_raw_parts_mut(ptr, len), } } } pub fn data_mut(&mut self) -> ImageData { unsafe { let ptr = ffi::qtc_qimage_get_data(self.0); let len = ffi::qtc_qimage_get_byte_count(self.0) as usize; ImageData { slice: slice::from_raw_parts_mut(ptr, len), } } } pub fn width(&self) -> u32 { unsafe { ffi::qtc_qimage_get_width(self.0) } } pub fn height(&self) -> u32 { unsafe { ffi::qtc_qimage_get_height(self.0) } } } impl Drop for Image { fn drop(&mut self) { unsafe { ffi::qtc_qimage_destroy(self.0) } } } pub struct ImageData<'a> { slice: &'a mut [u8], } impl<'a> Deref for ImageData<'a> { type Target = [u8]; fn deref(&self) -> &[u8] { self.slice } } impl<'a> DerefMut for ImageData<'a> { fn deref_mut(&mut self) -> &mut [u8] { self.slice } } pub struct Painter(*mut ffi::qtc_qpainter, bool); impl Painter { pub fn new(img: &mut Image) -> Painter { unsafe { Painter(ffi::qtc_qpainter_create(img.0), true) } } pub unsafe fn from_raw(ptr: *mut ffi::qtc_qpainter) -> Painter { Painter(ptr, false) } pub fn set_antialiasing(&self, flag: bool) { unsafe { ffi::qtc_qpainter_set_antialiasing(self.0, flag); } } pub fn set_smooth_pixmap_transform(&self, flag: bool) { unsafe { ffi::qtc_qpainter_set_smooth_pixmap_transform(self.0, flag); } } pub fn set_pen(&mut self, pen: Pen) { unsafe { ffi::qtc_qpainter_set_pen(self.0, pen.0) } } pub fn reset_pen(&mut self) { unsafe { ffi::qtc_qpainter_reset_pen(self.0) } } pub fn set_brush(&mut self, brush: Brush) { unsafe { ffi::qtc_qpainter_set_brush(self.0, brush.0) } } pub fn reset_brush(&mut self) { unsafe { ffi::qtc_qpainter_reset_brush(self.0) } } pub fn set_opacity(&mut self, opacity: f64) { unsafe { ffi::qtc_qpainter_set_opacity(self.0, opacity) } } pub fn draw_path(&mut self, path: &PainterPath) { unsafe { ffi::qtc_qpainter_draw_path(self.0, path.0) } } pub fn draw_image(&mut self, x: f64, y: f64, img: &Image) { unsafe { ffi::qtc_qpainter_draw_image(self.0, x, y, img.0) } } pub fn draw_image_rect(&mut self, x: f64, y: f64, w: f64, h: f64, img: &Image) { unsafe { ffi::qtc_qpainter_draw_image_rect(self.0, x, y, w, h, img.0) } } pub fn draw_rect(&mut self, x: f64, y: f64, w: f64, h: f64) { unsafe { ffi::qtc_qpainter_draw_rect(self.0, x, y, w, h) } } pub fn translate(&mut self, tx: f64, ty: f64) { unsafe { ffi::qtc_qpainter_translate(self.0, tx, ty) } } pub fn scale(&mut self, sx: f64, sy: f64) { unsafe { ffi::qtc_qpainter_scale(self.0, sx, sy) } } pub fn get_transform(&self) -> Transform { unsafe { Transform(ffi::qtc_qpainter_get_transform(self.0)) } } pub fn set_transform(&mut self, ts: &Transform) { unsafe { ffi::qtc_qpainter_set_transform(self.0, ts.0, false) } } pub fn apply_transform(&mut self, ts: &Transform) { unsafe { ffi::qtc_qpainter_set_transform(self.0, ts.0, true) } } pub fn set_clip_rect(&mut self, x: f64, y: f64, w: f64, h: f64) { unsafe { ffi::qtc_qpainter_set_clip_rect(self.0, x, y, w, h) } } pub fn set_clip_path(&mut self, path: &PainterPath) { unsafe { ffi::qtc_qpainter_set_clip_path(self.0, path.0) } } pub fn reset_clip_path(&mut self) { unsafe { ffi::qtc_qpainter_reset_clip_path(self.0) } } pub fn set_composition_mode(&mut self, mode: CompositionMode) { unsafe { ffi::qtc_qpainter_set_composition_mode(self.0, mode as ffi::CompositionMode) } } pub fn end(&mut self) { unsafe { ffi::qtc_qpainter_end(self.0) } } } impl Drop for Painter { fn drop(&mut self) { if self.1 { unsafe { ffi::qtc_qpainter_destroy(self.0) } } } } pub struct PainterPath(*mut ffi::qtc_qpainterpath); impl PainterPath { pub fn new() -> PainterPath { unsafe { PainterPath(ffi::qtc_qpainterpath_create()) } } pub fn move_to(&mut self, x: f64, y: f64) { unsafe { ffi::qtc_qpainterpath_move_to(self.0, x, y) } } pub fn line_to(&mut self, x: f64, y: f64) { unsafe { ffi::qtc_qpainterpath_line_to(self.0, x, y) } } pub fn curve_to(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, x: f64, y: f64) { unsafe { ffi::qtc_qpainterpath_curve_to(self.0, x1, y1, x2, y2, x, y) } } pub fn close_path(&mut self) { unsafe { ffi::qtc_qpainterpath_close_path(self.0) } } pub fn set_fill_rule(&mut self, rule: FillRule) { unsafe { ffi::qtc_qpainterpath_set_fill_rule(self.0, rule as ffi::FillRule) } } } impl Drop for PainterPath { fn drop(&mut self) { unsafe { ffi::qtc_qpainterpath_destroy(self.0) } } } pub struct Transform(*mut ffi::qtc_qtransform); impl Transform { pub fn new(a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) -> Transform { unsafe { Transform(ffi::qtc_qtransform_create_from(a, b, c, d, e, f)) } } pub fn data(&self) -> (f64, f64, f64, f64, f64, f64) { let ts = unsafe { ffi::qtc_qtransform_get_data(self.0) }; (ts.a, ts.b, ts.c, ts.d, ts.e, ts.f) } } impl Default for Transform { fn default() -> Transform { unsafe { Transform(ffi::qtc_qtransform_create()) } } } impl Drop for Transform { fn drop(&mut self) { unsafe { ffi::qtc_qtransform_destroy(self.0) } } } pub struct Pen(*mut ffi::qtc_qpen); impl Pen { pub fn new() -> Pen { unsafe { Pen(ffi::qtc_qpen_create()) } } pub fn set_color(&mut self, r: u8, g: u8, b: u8, a: u8) { unsafe { ffi::qtc_qpen_set_color(self.0, r, g, b, a) } } pub fn set_brush(&mut self, brush: Brush) { unsafe { ffi::qtc_qpen_set_brush(self.0, brush.0) } } pub fn set_line_cap(&mut self, s: LineCap) { unsafe { ffi::qtc_qpen_set_line_cap(self.0, s as ffi::PenCapStyle) } } pub fn set_line_join(&mut self, s: LineJoin) { unsafe { ffi::qtc_qpen_set_line_join(self.0, s as ffi::PenJoinStyle) } } pub fn set_width(&mut self, width: f64) { unsafe { ffi::qtc_qpen_set_width(self.0, width) } } pub fn set_miter_limit(&mut self, limit: f64) { unsafe { ffi::qtc_qpen_set_miter_limit(self.0, limit) } } pub fn set_dash_offset(&mut self, offset: f64) { unsafe { ffi::qtc_qpen_set_dash_offset(self.0, offset) } } pub fn set_dash_array(&mut self, offset: &[f64]) { assert!(offset.len() < i32::MAX as usize); unsafe { ffi::qtc_qpen_set_dash_array(self.0, offset.as_ptr(), offset.len() as i32) } } } impl Drop for Pen { fn drop(&mut self) { unsafe { ffi::qtc_qpen_destroy(self.0) } } } pub struct Brush(*mut ffi::qtc_qbrush); impl Brush { pub fn new() -> Brush { unsafe { Brush(ffi::qtc_qbrush_create()) } } pub fn set_color(&mut self, r: u8, g: u8, b: u8, a: u8) { unsafe { ffi::qtc_qbrush_set_color(self.0, r, g, b, a) } } pub fn set_linear_gradient(&mut self, lg: LinearGradient) { unsafe { ffi::qtc_qbrush_set_linear_gradient(self.0, lg.0) } } pub fn set_radial_gradient(&mut self, rg: RadialGradient) { unsafe { ffi::qtc_qbrush_set_radial_gradient(self.0, rg.0) } } pub fn set_pattern(&mut self, img: Image) { unsafe { ffi::qtc_qbrush_set_pattern(self.0, img.0) } } pub fn set_transform(&mut self, ts: Transform) { unsafe { ffi::qtc_qbrush_set_transform(self.0, ts.0) } } } impl Drop for Brush { fn drop(&mut self) { unsafe { ffi::qtc_qbrush_destroy(self.0) } } } pub trait Gradient { fn set_color_at(&mut self, offset: f64, r: u8, g: u8, b: u8, a: u8); fn set_spread(&mut self, spread: Spread); } pub struct LinearGradient(*mut ffi::qtc_qlineargradient); impl LinearGradient { pub fn new(x1: f64, y1: f64, x2: f64, y2: f64) -> LinearGradient { unsafe { LinearGradient(ffi::qtc_qlineargradient_create(x1, y1, x2, y2)) } } } impl Gradient for LinearGradient { fn set_color_at(&mut self, offset: f64, r: u8, g: u8, b: u8, a: u8) { unsafe { ffi::qtc_qlineargradient_set_color_at(self.0, offset, r, g, b, a) } } fn set_spread(&mut self, spread: Spread) { unsafe { ffi::qtc_qlineargradient_set_spread(self.0, spread as ffi::Spread) } } } impl Drop for LinearGradient { fn drop(&mut self) { unsafe { ffi::qtc_qlineargradient_destroy(self.0) } } } pub struct RadialGradient(*mut ffi::qtc_qradialgradient); impl RadialGradient { pub fn new(cx: f64, cy: f64, fx: f64, fy: f64, r: f64) -> RadialGradient { unsafe { RadialGradient(ffi::qtc_qradialgradient_create(cx, cy, fx, fy, r)) } } } impl Gradient for RadialGradient { fn set_color_at(&mut self, offset: f64, r: u8, g: u8, b: u8, a: u8) { unsafe { ffi::qtc_qradialgradient_set_color_at(self.0, offset, r, g, b, a) } } fn set_spread(&mut self, spread: Spread) { unsafe { ffi::qtc_qradialgradient_set_spread(self.0, spread as ffi::Spread) } } } impl Drop for RadialGradient { fn drop(&mut self) { unsafe { ffi::qtc_qradialgradient_destroy(self.0) } } } resvg-0.8.0/bindings/resvg-skia/000077500000000000000000000000001352576375700165415ustar00rootroot00000000000000resvg-0.8.0/bindings/resvg-skia/Cargo.toml000066400000000000000000000005761352576375700205010ustar00rootroot00000000000000[package] name = "resvg-skia" version = "0.8.0" authors = ["JaFenix ", "Reizner Evgeniy "] keywords = ["skia", "ffi"] license = "MIT" edition = "2018" description = "A minimal bindings to Skia used by resvg." repository = "https://github.com/RazrFalcon/resvg" workspace = "../.." [build-dependencies] cc = "1.0" pkg-config = "0.3" resvg-0.8.0/bindings/resvg-skia/LICENSE.txt000066400000000000000000000020501352576375700203610ustar00rootroot00000000000000MIT License Copyright (c) 2019 JaFenix 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. resvg-0.8.0/bindings/resvg-skia/build.rs000066400000000000000000000040521352576375700202070ustar00rootroot00000000000000#[cfg(target_os = "windows")] fn main() { use std::env; use std::path::Path; let skia_dir = env::var("SKIA_DIR").expect("SKIA_DIR is not set"); let skia_path = Path::new(&skia_dir); let mut build = cc::Build::new(); let tool = build.get_compiler(); build.cpp(true); build.file("cpp/skia_capi.cpp").include("cpp"); if env::var("SKIA_VER_M58").is_ok() { build.define("SKIA_VER_M58", None); build.include(skia_path.join("include").join("core")); build.include(skia_path.join("include").join("config")); build.include(skia_path.join("include").join("effects")); } else { build.include(skia_path); } if tool.is_like_msvc() { build.compile("libskiac.lib"); } else { build.flag("-std=c++14"); build.compile("libskiac.a"); } let skia_lib_dir = env::var("SKIA_LIB_DIR").expect("SKIA_LIB_DIR is not set"); let skia_lib_path = Path::new(&skia_lib_dir); println!("cargo:rustc-link-search={}", skia_lib_path.display()); // for MSVC println!("cargo:rustc-link-lib=skia.dll"); } #[cfg(any(target_os = "linux", target_os = "macos"))] fn main() { use std::env; use std::path::Path; let skia_dir = env::var("SKIA_DIR").expect("SKIA_DIR is not set"); let skia_path = Path::new(&skia_dir); let mut build = cc::Build::new(); build.cpp(true); build.file("cpp/skia_capi.cpp").include("cpp"); if env::var("SKIA_VER_M58").is_ok() { build.define("SKIA_VER_M58", None); build.include(skia_path.join("include").join("core")); build.include(skia_path.join("include").join("config")); build.include(skia_path.join("include").join("effects")); } else { build.include(skia_path); } build.flag("-std=c++14"); build.compile("libskiac.a"); let skia_lib_dir = env::var("SKIA_LIB_DIR").expect("SKIA_LIB_DIR is not set"); let skia_lib_path = Path::new(&skia_lib_dir); println!("cargo:rustc-link-search={}", skia_lib_path.display()); println!("cargo:rustc-link-lib=skia"); } resvg-0.8.0/bindings/resvg-skia/cpp/000077500000000000000000000000001352576375700173235ustar00rootroot00000000000000resvg-0.8.0/bindings/resvg-skia/cpp/skia_capi.cpp000066400000000000000000000342401352576375700217550ustar00rootroot00000000000000#include #ifdef SKIA_VER_M58 #include #include #include #include #include #include #else #include #include #include #include #include #include #include #endif #include #include "skia_capi.hpp" #define SURFACE_CAST reinterpret_cast(c_surface) #define CANVAS_CAST reinterpret_cast(c_canvas) #define PAINT_CAST reinterpret_cast(c_paint) #define PATH_CAST reinterpret_cast(c_path) #define MATRIX_CAST reinterpret_cast(c_matrix) static SkBlendMode blendModes_[static_cast(BlendMode::__Size)] = { SkBlendMode::kClear, SkBlendMode::kSrcOver, SkBlendMode::kDstOver, SkBlendMode::kSrcIn, SkBlendMode::kDstIn, SkBlendMode::kSrcOut, SkBlendMode::kDstOut, SkBlendMode::kSrcATop, SkBlendMode::kXor, SkBlendMode::kMultiply, SkBlendMode::kScreen, SkBlendMode::kDarken, SkBlendMode::kLighten, }; extern "C" { // Surface static SkSurface* skiac_surface_create(int width, int height, SkAlphaType alphaType) { // Init() is indempotent, so can be called more than once with no adverse effect. SkGraphics::Init(); SkImageInfo info = SkImageInfo::Make(width, height, kN32_SkColorType, alphaType); sk_sp surface = SkSurface::MakeRaster(info); // The surface ref count will equal one after the pointer is returned. return surface.release(); } skiac_surface* skiac_surface_create_rgba_premultiplied(int width, int height) { return reinterpret_cast(skiac_surface_create(width, height, kPremul_SkAlphaType)); } skiac_surface* skiac_surface_create_rgba(int width, int height) { return reinterpret_cast(skiac_surface_create(width, height, kUnpremul_SkAlphaType)); } SkSurface* skiac_surface_create_data(sk_sp data) { SkSurface* surface = nullptr; sk_sp image = SkImage::MakeFromEncoded(data); if (image) { surface = skiac_surface_create(image->width(), image->height(), kPremul_SkAlphaType); } if (surface) { surface->getCanvas()->drawImage(image, 0, 0); } return surface; } bool skiac_surface_save(skiac_surface* c_surface, const char *path) { sk_sp image = SURFACE_CAST->makeImageSnapshot(); #ifdef SKIA_VER_M58 SkData *data = image->encode(SkEncodedImageFormat::kPNG, 0); if (data) { SkFILEWStream stream(path); if (stream.write(data->data(), data->size())) { stream.flush(); return true; } } #else SkPngEncoder::Options opt; opt.fZLibLevel = 2; // Use a lower ratio to speed up compression. SkPixmap pixmap; if (SURFACE_CAST->getCanvas()->peekPixels(&pixmap)) { SkFILEWStream stream(path); SkPngEncoder::Encode(&stream, pixmap, opt); return true; } #endif return false; } void skiac_surface_destroy(skiac_surface* c_surface) { // SkSurface is ref counted. SkSurface* surface = reinterpret_cast(c_surface); SkSafeUnref(surface); } skiac_surface* skiac_surface_copy_rgba(skiac_surface *c_surface, uint32_t x, uint32_t y, uint32_t width, uint32_t height) { // x, y, width, height are source rectangle coordinates. SkSurface* copy = skiac_surface_create((int)width, (int)height, kUnpremul_SkAlphaType); SkPaint paint; paint.setFilterQuality(SkFilterQuality::kLow_SkFilterQuality); paint.setAlpha(SK_AlphaOPAQUE); // The original surface draws itself to the copy's canvas. SURFACE_CAST->draw(copy->getCanvas(), -(SkScalar)x, -(SkScalar)y, &paint); return reinterpret_cast(copy); } int skiac_surface_get_width(skiac_surface* c_surface) { return SURFACE_CAST->width(); } int skiac_surface_get_height(skiac_surface* c_surface) { return SURFACE_CAST->height(); } skiac_canvas* skiac_surface_get_canvas(skiac_surface* c_surface) { return reinterpret_cast(SURFACE_CAST->getCanvas()); } void skiac_surface_read_pixels(skiac_surface* c_surface, skiac_surface_data* data) { data->ptr = nullptr; data->size = 0; SkPixmap pixmap; if (SURFACE_CAST->getCanvas()->peekPixels(&pixmap)) { data->ptr = static_cast(pixmap.writable_addr()); #ifdef SKIA_VER_M58 data->size = static_cast(pixmap.getSafeSize()); #else data->size = static_cast(pixmap.computeByteSize()); #endif } } bool skiac_is_surface_bgra() { return kN32_SkColorType == kBGRA_8888_SkColorType; } // Canvas void skiac_canvas_clear(skiac_canvas* c_canvas, uint32_t color) { CANVAS_CAST->clear(static_cast(color)); } void skiac_canvas_flush(skiac_canvas* c_canvas) { CANVAS_CAST->flush(); } void skiac_canvas_set_matrix(skiac_canvas* c_canvas, skiac_matrix* c_matrix) { CANVAS_CAST->setMatrix(*MATRIX_CAST); } void skiac_canvas_concat(skiac_canvas* c_canvas, skiac_matrix* c_matrix) { CANVAS_CAST->concat(*MATRIX_CAST); } void skiac_canvas_scale(skiac_canvas* c_canvas, double sx, double sy) { CANVAS_CAST->scale((SkScalar)sx, (SkScalar)sy); } void skiac_canvas_translate(skiac_canvas* c_canvas, double dx, double dy) { CANVAS_CAST->translate((SkScalar)dx, (SkScalar)dy); } skiac_matrix* skiac_canvas_get_total_matrix(skiac_canvas* c_canvas) { SkMatrix* matrix = new SkMatrix(); *matrix = CANVAS_CAST->getTotalMatrix(); return reinterpret_cast(matrix); } void skiac_canvas_draw_path(skiac_canvas* c_canvas, skiac_path* c_path, skiac_paint* c_paint) { CANVAS_CAST->drawPath(*PATH_CAST, *PAINT_CAST); } void skiac_canvas_draw_rect(skiac_canvas* c_canvas, double x, double y, double w, double h, skiac_paint* c_paint) { SkRect rect = SkRect::MakeXYWH((SkScalar)x, (SkScalar)y, (SkScalar)w, (SkScalar)h); CANVAS_CAST->drawRect(rect, *PAINT_CAST); } void skiac_canvas_draw_surface(skiac_canvas* c_canvas, skiac_surface* c_surface, double left, double top, uint8_t alpha, BlendMode blendMode, FilterQuality filterQuality) { sk_sp image = SURFACE_CAST->makeImageSnapshot(); SkPaint paint; paint.setFilterQuality((SkFilterQuality)filterQuality); paint.setAlpha(alpha); paint.setBlendMode(blendModes_[static_cast(blendMode)]); CANVAS_CAST->drawImage(image, (SkScalar)left, (SkScalar)top, &paint); } void skiac_canvas_draw_surface_rect(skiac_canvas* c_canvas, skiac_surface* c_surface, double x, double y, double w, double h, FilterQuality filterQuality) { sk_sp image = SURFACE_CAST->makeImageSnapshot(); SkPaint paint; paint.setFilterQuality((SkFilterQuality)filterQuality); SkRect src = SkRect::MakeXYWH(0, 0, (SkScalar)image->width(), (SkScalar)image->height()); SkRect dst = SkRect::MakeXYWH((SkScalar)x, (SkScalar)y, (SkScalar)w, (SkScalar)h); CANVAS_CAST->drawImageRect(image, src, dst, &paint); } void skiac_canvas_reset_matrix(skiac_canvas* c_canvas) { CANVAS_CAST->resetMatrix(); } void skiac_canvas_clip_rect(skiac_canvas* c_canvas, double x, double y, double w, double h) { SkRect rect = SkRect::MakeXYWH((SkScalar)x, (SkScalar)y, (SkScalar)w, (SkScalar)h); CANVAS_CAST->clipRect(rect, true); } void skiac_canvas_save(skiac_canvas* c_canvas) { CANVAS_CAST->save(); } void skiac_canvas_restore(skiac_canvas* c_canvas) { CANVAS_CAST->restore(); } // SkMatrix skiac_matrix *skiac_matrix_create() { SkMatrix* matrix = new SkMatrix(); matrix->reset(); return reinterpret_cast(matrix); } skiac_matrix *skiac_matrix_create_from(double a, double b, double c, double d, double e, double f) { SkMatrix* matrix = new SkMatrix(); matrix->setAll((SkScalar)a, (SkScalar)c, (SkScalar)e, (SkScalar)b, (SkScalar)d, (SkScalar)f, 0.0f, 0.0f, 1.0f); return reinterpret_cast(matrix); } skiac_matrix *skiac_matrix_create_inverse(skiac_matrix *c_matrix) { SkMatrix* inverse = new SkMatrix(); if (MATRIX_CAST->invert(inverse)) { return reinterpret_cast(inverse); } else { return nullptr; } } skia_matrix skiac_matrix_get_data(skiac_matrix *c_matrix) { const auto mat = MATRIX_CAST; skia_matrix raw_mat; raw_mat.a = (double)mat->getScaleX(); raw_mat.b = (double)mat->getSkewY(); raw_mat.c = (double)mat->getSkewX(); raw_mat.d = (double)mat->getScaleY(); raw_mat.e = (double)mat->getTranslateX(); raw_mat.f = (double)mat->getTranslateY(); return raw_mat; } void skiac_matrix_destroy(skiac_matrix *c_matrix) { // SkMatrix is NOT ref counted delete MATRIX_CAST; } // Paint skiac_paint* skiac_paint_create() { SkPaint* paint = new SkPaint(); return reinterpret_cast(paint); } void skiac_paint_destroy(skiac_paint* c_paint) { SkPaint* paint = PAINT_CAST; // Setting these references to nullptr should decrement their ref count. paint->setShader(nullptr); paint->setPathEffect(nullptr); // SkPaint is not ref counted, so explicitly delete. delete paint; } void skiac_paint_set_color(skiac_paint* c_paint, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { PAINT_CAST->setARGB(a, r, g, b); } void skiac_paint_set_alpha(skiac_paint* c_paint, uint8_t a) { PAINT_CAST->setAlpha(a); } void skiac_paint_set_anti_alias(skiac_paint* c_paint, bool aa) { PAINT_CAST->setAntiAlias(aa); } void skiac_paint_set_blend_mode(skiac_paint* c_paint, BlendMode blendMode) { PAINT_CAST->setBlendMode(blendModes_[static_cast(blendMode)]); } void skiac_paint_set_shader(skiac_paint* c_paint, skiac_shader* c_shader) { sk_sp shader(reinterpret_cast(c_shader)); PAINT_CAST->setShader(shader); } void skiac_paint_set_style(skiac_paint* c_paint, PaintStyle style) { PAINT_CAST->setStyle((SkPaint::Style)style); } void skiac_paint_set_stroke_width(skiac_paint* c_paint, double width) { PAINT_CAST->setStrokeWidth((SkScalar)width); } void skiac_paint_set_stroke_cap(skiac_paint* c_paint, StrokeCap cap) { PAINT_CAST->setStrokeCap((SkPaint::Cap)cap); } void skiac_paint_set_stroke_join(skiac_paint* c_paint, StrokeJoin join) { PAINT_CAST->setStrokeJoin((SkPaint::Join)join); } void skiac_paint_set_stroke_miter(skiac_paint* c_paint, float miter) { PAINT_CAST->setStrokeMiter(miter); } void skiac_paint_set_path_effect(skiac_paint* c_paint, skiac_path_effect* c_path_effect) { sk_sp pathEffect(reinterpret_cast(c_path_effect)); PAINT_CAST->setPathEffect(pathEffect); } // Path skiac_path* skiac_path_create() { return reinterpret_cast(new SkPath()); } void skiac_path_destroy(skiac_path* c_path) { // SkPath is NOT ref counted delete PATH_CAST; } void skiac_path_set_fill_type(skiac_path* c_path, FillType type) { PATH_CAST->setFillType((SkPath::FillType)type); } void skiac_path_move_to(skiac_path* c_path, double x, double y) { PATH_CAST->moveTo((SkScalar)x, (SkScalar)y); } void skiac_path_line_to(skiac_path* c_path, double x, double y) { PATH_CAST->lineTo((SkScalar)x, (SkScalar)y); } void skiac_path_cubic_to(skiac_path* c_path, double x1, double y1, double x2, double y2, double x3, double y3) { PATH_CAST->cubicTo((SkScalar)x1, (SkScalar)y1, (SkScalar)x2, (SkScalar)y2, (SkScalar)x3, (SkScalar)y3); } void skiac_path_close(skiac_path* c_path) { PATH_CAST->close(); } // PathEffect skiac_path_effect* skiac_path_effect_make_dash_path(const float* intervals, int count, float phase) { SkPathEffect* effect = SkDashPathEffect::Make(intervals, count, phase).release(); effect->ref(); return reinterpret_cast(effect); } void skiac_path_effect_destroy(skiac_path_effect* c_path_effect) { // SkPathEffect is ref counted. SkPathEffect* effect = reinterpret_cast(c_path_effect); SkSafeUnref(effect); } // Shader skiac_shader* skiac_shader_make_linear_gradient( const skia_point* c_points, const uint32_t* colors, const float* positions, int count, TileMode tile_mode, uint32_t flags, skiac_matrix *c_matrix) { const SkPoint* points = reinterpret_cast(c_points); #ifdef SKIA_VER_M58 auto skia_tile_mode = (SkShader::TileMode)tile_mode; #else auto skia_tile_mode = (SkTileMode)tile_mode; #endif SkShader* shader = SkGradientShader::MakeLinear( points, colors, positions, count, skia_tile_mode, flags, MATRIX_CAST ).release(); shader->ref(); return reinterpret_cast(shader); } skiac_shader* skiac_shader_make_two_point_conical_gradient( const skia_point c_start_point, SkScalar start_radius, const skia_point c_end_point, SkScalar end_radius, const uint32_t* colors, const SkScalar* positions, int count, TileMode tile_mode, uint32_t flags, skiac_matrix *c_matrix) { const SkPoint startPoint = { c_start_point.x, c_start_point.y }; const SkPoint endPoint = { c_end_point.x, c_end_point.y }; #ifdef SKIA_VER_M58 auto skia_tile_mode = (SkShader::TileMode)tile_mode; #else auto skia_tile_mode = (SkTileMode)tile_mode; #endif SkShader* shader = SkGradientShader::MakeTwoPointConical( startPoint, start_radius, endPoint, end_radius, colors, positions, count, skia_tile_mode, flags, MATRIX_CAST ).release(); shader->ref(); return reinterpret_cast(shader); } skiac_shader* skiac_shader_make_from_surface_image(skiac_surface* c_surface, skiac_matrix* c_matrix) { #ifdef SKIA_VER_M58 auto skia_tile_mode = SkShader::TileMode::kRepeat_TileMode; #else auto skia_tile_mode = SkTileMode::kRepeat; #endif sk_sp image = SURFACE_CAST->makeImageSnapshot(); SkShader* shader = image->makeShader(skia_tile_mode, skia_tile_mode, MATRIX_CAST).release(); shader->ref(); return reinterpret_cast(shader); } void skiac_shader_destroy(skiac_shader* c_shader) { // SkShader is ref counted. SkShader* shader = reinterpret_cast(c_shader); SkSafeUnref(shader); } } resvg-0.8.0/bindings/resvg-skia/cpp/skia_capi.hpp000066400000000000000000000140221352576375700217560ustar00rootroot00000000000000#ifndef SKIA_CAPI_H #define SKIA_CAPI_H #include #define INIT_STRUCT(x) \ struct x; \ typedef struct x x; INIT_STRUCT(skiac_surface) INIT_STRUCT(skiac_canvas) INIT_STRUCT(skiac_matrix) INIT_STRUCT(skiac_paint) INIT_STRUCT(skiac_path) INIT_STRUCT(skiac_shader) INIT_STRUCT(skiac_path_effect) #undef INIT_STRUCT struct skia_matrix { double a; double b; double c; double d; double e; double f; }; struct skia_point { float x; float y; }; struct skiac_surface_data { uint8_t *ptr; uint32_t size; }; // The same order as in core/SkPaint.h enum class PaintStyle { Fill, Stroke, }; // The same order as in core/SkPaint.h enum class StrokeCap { Butt, Round, Square, }; // The same order as in core/SkPaint.h enum class StrokeJoin { Miter, Round, Bevel, }; // The same order as in core/SkPath.h enum class FillType { Winding, EvenOdd, }; // The same order as in core/SkTileMode.h enum class TileMode { Clamp, Repeat, Mirror, }; enum class BlendMode { Clear = 0, SourceOver = 1, DestinationOver = 2, SourceIn = 3, DestinationIn = 4, SourceOut = 5, DestinationOut = 6, SourceAtop = 7, Xor = 8, Multiply = 9, Screen = 10, Darken = 11, Lighten = 12, __Size }; // The same order as in core/SkFilterQuality.h enum class FilterQuality { None, Low, Medium, High, }; extern "C" { // Surface skiac_surface* skiac_surface_create_rgba_premultiplied(int width, int height); skiac_surface* skiac_surface_create_rgba(int width, int height); void skiac_surface_destroy(skiac_surface* c_surface); skiac_surface* skiac_surface_copy_rgba(skiac_surface *c_surface, uint32_t x, uint32_t y, uint32_t width, uint32_t height); bool skiac_surface_save(skiac_surface* c_surface, const char *path); skiac_canvas* skiac_surface_get_canvas(skiac_surface* c_surface); int skiac_surface_get_width(skiac_surface *c_surface); int skiac_surface_get_height(skiac_surface *c_surface); void skiac_surface_read_pixels(skiac_surface* c_surface, skiac_surface_data* data); bool skiac_is_surface_bgra(); // Canvas void skiac_canvas_clear(skiac_canvas* c_canvas, uint32_t color); void skiac_canvas_flush(skiac_canvas* c_canvas); void skiac_canvas_set_matrix(skiac_canvas* c_canvas, skiac_matrix *c_matrix); void skiac_canvas_concat(skiac_canvas* c_canvas, skiac_matrix* c_matrix); void skiac_canvas_scale(skiac_canvas* c_canvas, double sx, double sy); void skiac_canvas_translate(skiac_canvas* c_canvas, double dx, double dy); skiac_matrix* skiac_canvas_get_total_matrix(skiac_canvas* c_canvas); void skiac_canvas_draw_path(skiac_canvas* c_canvas, skiac_path* c_path, skiac_paint* c_paint); void skiac_canvas_draw_rect(skiac_canvas* c_canvas, double x, double y, double w, double h, skiac_paint* c_paint); void skiac_canvas_draw_surface(skiac_canvas* c_canvas, skiac_surface* c_surface, double left, double top, uint8_t alpha, BlendMode blendMode, FilterQuality filterQuality); void skiac_canvas_draw_surface_rect(skiac_canvas* c_canvas, skiac_surface* c_surface, double x, double y, double w, double h, FilterQuality filterQuality); void skiac_canvas_reset_matrix(skiac_canvas* c_canvas); void skiac_canvas_clip_rect(skiac_canvas* c_canvas, double x, double y, double w, double h); void skiac_canvas_save(skiac_canvas* c_canvas); void skiac_canvas_restore(skiac_canvas* c_canvas); // Matrix skiac_matrix *skiac_matrix_create(); skiac_matrix *skiac_matrix_create_from(double a, double b, double c, double d, double e, double f); skiac_matrix *skiac_matrix_create_inverse(skiac_matrix *c_matrix); skia_matrix skiac_matrix_get_data(skiac_matrix *c_matrix); void skiac_matrix_destroy(skiac_matrix *c_matrix); // Paint skiac_paint* skiac_paint_create(); void skiac_paint_destroy(skiac_paint* c_paint); void skiac_paint_set_style(skiac_paint* c_paint, PaintStyle style); void skiac_paint_set_color(skiac_paint* c_paint, uint8_t r, uint8_t g, uint8_t b, uint8_t a); void skiac_paint_set_alpha(skiac_paint* c_paint, uint8_t a); void skiac_paint_set_anti_alias(skiac_paint* c_paint, bool aa); void skiac_paint_set_blend_mode(skiac_paint* c_paint, BlendMode blendMode); void skiac_paint_set_shader(skiac_paint* c_paint, skiac_shader* c_shader); void skiac_paint_set_stroke_width(skiac_paint* c_paint, double width); void skiac_paint_set_stroke_cap(skiac_paint* c_paint, StrokeCap cap); void skiac_paint_set_stroke_join(skiac_paint* c_paint, StrokeJoin join); void skiac_paint_set_stroke_miter(skiac_paint* c_paint, float miter); void skiac_paint_set_path_effect(skiac_paint* c_paint, skiac_path_effect* c_path_effect); // Path skiac_path* skiac_path_create(); void skiac_path_destroy(skiac_path* c_path); void skiac_path_set_fill_type(skiac_path* c_path, FillType type); void skiac_path_move_to(skiac_path* c_path, double x, double y); void skiac_path_line_to(skiac_path* c_path, double x, double y); void skiac_path_cubic_to(skiac_path* c_path, double x1, double y1, double x2, double y2, double x3, double y3); void skiac_path_close(skiac_path* c_path); // PathEffect skiac_path_effect* skiac_path_effect_make_dash_path(const float* intervals, int count, float phase); void skiac_path_effect_destroy(skiac_path_effect* c_path_effect); // Shader skiac_shader* skiac_shader_make_linear_gradient(const skia_point* points, const uint32_t* colors, const float* positions, int count, TileMode tile_mode, uint32_t flags, skiac_matrix *c_matrix); skiac_shader* skiac_shader_make_two_point_conical_gradient( const skia_point start_point, float start_radius, const skia_point end_point, float end_radius, const uint32_t* colors, const float* positions, int count, TileMode tile_mode, uint32_t flags, skiac_matrix *c_matrix); skiac_shader* skiac_shader_make_from_surface_image(skiac_surface* c_surface, skiac_matrix *c_matrix); void skiac_shader_destroy(skiac_shader* c_shader); } #endif // SKIA_CAPI_H resvg-0.8.0/bindings/resvg-skia/src/000077500000000000000000000000001352576375700173305ustar00rootroot00000000000000resvg-0.8.0/bindings/resvg-skia/src/ffi.rs000066400000000000000000000540551352576375700204530ustar00rootroot00000000000000/* automatically generated by rust-bindgen */ pub const _STDINT_H: u32 = 1; pub const _FEATURES_H: u32 = 1; pub const _ISOC95_SOURCE: u32 = 1; pub const _ISOC99_SOURCE: u32 = 1; pub const _ISOC11_SOURCE: u32 = 1; pub const _POSIX_SOURCE: u32 = 1; pub const _POSIX_C_SOURCE: u32 = 200809; pub const _XOPEN_SOURCE: u32 = 700; pub const _XOPEN_SOURCE_EXTENDED: u32 = 1; pub const _LARGEFILE64_SOURCE: u32 = 1; pub const _DEFAULT_SOURCE: u32 = 1; pub const _ATFILE_SOURCE: u32 = 1; pub const __USE_ISOC11: u32 = 1; pub const __USE_ISOC99: u32 = 1; pub const __USE_ISOC95: u32 = 1; pub const __USE_ISOCXX11: u32 = 1; pub const __USE_POSIX: u32 = 1; pub const __USE_POSIX2: u32 = 1; pub const __USE_POSIX199309: u32 = 1; pub const __USE_POSIX199506: u32 = 1; pub const __USE_XOPEN2K: u32 = 1; pub const __USE_XOPEN2K8: u32 = 1; pub const __USE_XOPEN: u32 = 1; pub const __USE_XOPEN_EXTENDED: u32 = 1; pub const __USE_UNIX98: u32 = 1; pub const _LARGEFILE_SOURCE: u32 = 1; pub const __USE_XOPEN2K8XSI: u32 = 1; pub const __USE_XOPEN2KXSI: u32 = 1; pub const __USE_LARGEFILE: u32 = 1; pub const __USE_LARGEFILE64: u32 = 1; pub const __USE_MISC: u32 = 1; pub const __USE_ATFILE: u32 = 1; pub const __USE_GNU: u32 = 1; pub const __USE_FORTIFY_LEVEL: u32 = 0; pub const __GLIBC_USE_DEPRECATED_GETS: u32 = 0; pub const __GLIBC_USE_DEPRECATED_SCANF: u32 = 0; pub const _STDC_PREDEF_H: u32 = 1; pub const __STDC_IEC_559__: u32 = 1; pub const __STDC_IEC_559_COMPLEX__: u32 = 1; pub const __STDC_ISO_10646__: u32 = 201706; pub const __GNU_LIBRARY__: u32 = 6; pub const __GLIBC__: u32 = 2; pub const __GLIBC_MINOR__: u32 = 29; pub const _SYS_CDEFS_H: u32 = 1; pub const __glibc_c99_flexarr_available: u32 = 1; pub const __WORDSIZE: u32 = 64; pub const __WORDSIZE_TIME64_COMPAT32: u32 = 1; pub const __SYSCALL_WORDSIZE: u32 = 64; pub const __HAVE_GENERIC_SELECTION: u32 = 0; pub const __GLIBC_USE_LIB_EXT2: u32 = 1; pub const __GLIBC_USE_IEC_60559_BFP_EXT: u32 = 1; pub const __GLIBC_USE_IEC_60559_FUNCS_EXT: u32 = 1; pub const __GLIBC_USE_IEC_60559_TYPES_EXT: u32 = 1; pub const _BITS_TYPES_H: u32 = 1; pub const __TIMESIZE: u32 = 64; pub const _BITS_TYPESIZES_H: u32 = 1; pub const __OFF_T_MATCHES_OFF64_T: u32 = 1; pub const __INO_T_MATCHES_INO64_T: u32 = 1; pub const __RLIM_T_MATCHES_RLIM64_T: u32 = 1; pub const __FD_SETSIZE: u32 = 1024; pub const _BITS_TIME64_H: u32 = 1; pub const _BITS_WCHAR_H: u32 = 1; pub const _BITS_STDINT_INTN_H: u32 = 1; pub const _BITS_STDINT_UINTN_H: u32 = 1; pub const INT8_MIN: i32 = -128; pub const INT16_MIN: i32 = -32768; pub const INT32_MIN: i32 = -2147483648; pub const INT8_MAX: u32 = 127; pub const INT16_MAX: u32 = 32767; pub const INT32_MAX: u32 = 2147483647; pub const UINT8_MAX: u32 = 255; pub const UINT16_MAX: u32 = 65535; pub const UINT32_MAX: u32 = 4294967295; pub const INT_LEAST8_MIN: i32 = -128; pub const INT_LEAST16_MIN: i32 = -32768; pub const INT_LEAST32_MIN: i32 = -2147483648; pub const INT_LEAST8_MAX: u32 = 127; pub const INT_LEAST16_MAX: u32 = 32767; pub const INT_LEAST32_MAX: u32 = 2147483647; pub const UINT_LEAST8_MAX: u32 = 255; pub const UINT_LEAST16_MAX: u32 = 65535; pub const UINT_LEAST32_MAX: u32 = 4294967295; pub const INT_FAST8_MIN: i32 = -128; pub const INT_FAST16_MIN: i64 = -9223372036854775808; pub const INT_FAST32_MIN: i64 = -9223372036854775808; pub const INT_FAST8_MAX: u32 = 127; pub const INT_FAST16_MAX: u64 = 9223372036854775807; pub const INT_FAST32_MAX: u64 = 9223372036854775807; pub const UINT_FAST8_MAX: u32 = 255; pub const UINT_FAST16_MAX: i32 = -1; pub const UINT_FAST32_MAX: i32 = -1; pub const INTPTR_MIN: i64 = -9223372036854775808; pub const INTPTR_MAX: u64 = 9223372036854775807; pub const UINTPTR_MAX: i32 = -1; pub const PTRDIFF_MIN: i64 = -9223372036854775808; pub const PTRDIFF_MAX: u64 = 9223372036854775807; pub const SIG_ATOMIC_MIN: i32 = -2147483648; pub const SIG_ATOMIC_MAX: u32 = 2147483647; pub const SIZE_MAX: i32 = -1; pub const WINT_MIN: u32 = 0; pub const WINT_MAX: u32 = 4294967295; pub const INT8_WIDTH: u32 = 8; pub const UINT8_WIDTH: u32 = 8; pub const INT16_WIDTH: u32 = 16; pub const UINT16_WIDTH: u32 = 16; pub const INT32_WIDTH: u32 = 32; pub const UINT32_WIDTH: u32 = 32; pub const INT64_WIDTH: u32 = 64; pub const UINT64_WIDTH: u32 = 64; pub const INT_LEAST8_WIDTH: u32 = 8; pub const UINT_LEAST8_WIDTH: u32 = 8; pub const INT_LEAST16_WIDTH: u32 = 16; pub const UINT_LEAST16_WIDTH: u32 = 16; pub const INT_LEAST32_WIDTH: u32 = 32; pub const UINT_LEAST32_WIDTH: u32 = 32; pub const INT_LEAST64_WIDTH: u32 = 64; pub const UINT_LEAST64_WIDTH: u32 = 64; pub const INT_FAST8_WIDTH: u32 = 8; pub const UINT_FAST8_WIDTH: u32 = 8; pub const INT_FAST16_WIDTH: u32 = 64; pub const UINT_FAST16_WIDTH: u32 = 64; pub const INT_FAST32_WIDTH: u32 = 64; pub const UINT_FAST32_WIDTH: u32 = 64; pub const INT_FAST64_WIDTH: u32 = 64; pub const UINT_FAST64_WIDTH: u32 = 64; pub const INTPTR_WIDTH: u32 = 64; pub const UINTPTR_WIDTH: u32 = 64; pub const INTMAX_WIDTH: u32 = 64; pub const UINTMAX_WIDTH: u32 = 64; pub const PTRDIFF_WIDTH: u32 = 64; pub const SIG_ATOMIC_WIDTH: u32 = 32; pub const SIZE_WIDTH: u32 = 64; pub const WCHAR_WIDTH: u32 = 32; pub const WINT_WIDTH: u32 = 32; pub type __u_char = ::std::os::raw::c_uchar; pub type __u_short = ::std::os::raw::c_ushort; pub type __u_int = ::std::os::raw::c_uint; pub type __u_long = ::std::os::raw::c_ulong; pub type __int8_t = ::std::os::raw::c_schar; pub type __uint8_t = ::std::os::raw::c_uchar; pub type __int16_t = ::std::os::raw::c_short; pub type __uint16_t = ::std::os::raw::c_ushort; pub type __int32_t = ::std::os::raw::c_int; pub type __uint32_t = ::std::os::raw::c_uint; pub type __int64_t = ::std::os::raw::c_long; pub type __uint64_t = ::std::os::raw::c_ulong; pub type __int_least8_t = __int8_t; pub type __uint_least8_t = __uint8_t; pub type __int_least16_t = __int16_t; pub type __uint_least16_t = __uint16_t; pub type __int_least32_t = __int32_t; pub type __uint_least32_t = __uint32_t; pub type __int_least64_t = __int64_t; pub type __uint_least64_t = __uint64_t; pub type __quad_t = ::std::os::raw::c_long; pub type __u_quad_t = ::std::os::raw::c_ulong; pub type __intmax_t = ::std::os::raw::c_long; pub type __uintmax_t = ::std::os::raw::c_ulong; pub type __dev_t = ::std::os::raw::c_ulong; pub type __uid_t = ::std::os::raw::c_uint; pub type __gid_t = ::std::os::raw::c_uint; pub type __ino_t = ::std::os::raw::c_ulong; pub type __ino64_t = ::std::os::raw::c_ulong; pub type __mode_t = ::std::os::raw::c_uint; pub type __nlink_t = ::std::os::raw::c_ulong; pub type __off_t = ::std::os::raw::c_long; pub type __off64_t = ::std::os::raw::c_long; pub type __pid_t = ::std::os::raw::c_int; #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct __fsid_t { pub __val: [::std::os::raw::c_int; 2usize], } #[test] fn bindgen_test_layout___fsid_t() { assert_eq!( ::std::mem::size_of::<__fsid_t>(), 8usize, concat!("Size of: ", stringify!(__fsid_t)) ); assert_eq!( ::std::mem::align_of::<__fsid_t>(), 4usize, concat!("Alignment of ", stringify!(__fsid_t)) ); assert_eq!( unsafe { &(*(::std::ptr::null::<__fsid_t>())).__val as *const _ as usize }, 0usize, concat!( "Offset of field: ", stringify!(__fsid_t), "::", stringify!(__val) ) ); } pub type __clock_t = ::std::os::raw::c_long; pub type __rlim_t = ::std::os::raw::c_ulong; pub type __rlim64_t = ::std::os::raw::c_ulong; pub type __id_t = ::std::os::raw::c_uint; pub type __time_t = ::std::os::raw::c_long; pub type __useconds_t = ::std::os::raw::c_uint; pub type __suseconds_t = ::std::os::raw::c_long; pub type __daddr_t = ::std::os::raw::c_int; pub type __key_t = ::std::os::raw::c_int; pub type __clockid_t = ::std::os::raw::c_int; pub type __timer_t = *mut ::std::os::raw::c_void; pub type __blksize_t = ::std::os::raw::c_long; pub type __blkcnt_t = ::std::os::raw::c_long; pub type __blkcnt64_t = ::std::os::raw::c_long; pub type __fsblkcnt_t = ::std::os::raw::c_ulong; pub type __fsblkcnt64_t = ::std::os::raw::c_ulong; pub type __fsfilcnt_t = ::std::os::raw::c_ulong; pub type __fsfilcnt64_t = ::std::os::raw::c_ulong; pub type __fsword_t = ::std::os::raw::c_long; pub type __ssize_t = ::std::os::raw::c_long; pub type __syscall_slong_t = ::std::os::raw::c_long; pub type __syscall_ulong_t = ::std::os::raw::c_ulong; pub type __loff_t = __off64_t; pub type __caddr_t = *mut ::std::os::raw::c_char; pub type __intptr_t = ::std::os::raw::c_long; pub type __socklen_t = ::std::os::raw::c_uint; pub type __sig_atomic_t = ::std::os::raw::c_int; pub type int_least8_t = __int_least8_t; pub type int_least16_t = __int_least16_t; pub type int_least32_t = __int_least32_t; pub type int_least64_t = __int_least64_t; pub type uint_least8_t = __uint_least8_t; pub type uint_least16_t = __uint_least16_t; pub type uint_least32_t = __uint_least32_t; pub type uint_least64_t = __uint_least64_t; pub type int_fast8_t = ::std::os::raw::c_schar; pub type int_fast16_t = ::std::os::raw::c_long; pub type int_fast32_t = ::std::os::raw::c_long; pub type int_fast64_t = ::std::os::raw::c_long; pub type uint_fast8_t = ::std::os::raw::c_uchar; pub type uint_fast16_t = ::std::os::raw::c_ulong; pub type uint_fast32_t = ::std::os::raw::c_ulong; pub type uint_fast64_t = ::std::os::raw::c_ulong; pub type intmax_t = __intmax_t; pub type uintmax_t = __uintmax_t; #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct skiac_context { _unused: [u8; 0], } #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct skiac_surface { _unused: [u8; 0], } #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct skiac_canvas { _unused: [u8; 0], } #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct skiac_matrix { _unused: [u8; 0], } #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct skiac_paint { _unused: [u8; 0], } #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct skiac_path { _unused: [u8; 0], } #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct skiac_shader { _unused: [u8; 0], } #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct skiac_path_effect { _unused: [u8; 0], } #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct skia_matrix { pub a: f64, pub b: f64, pub c: f64, pub d: f64, pub e: f64, pub f: f64, } #[test] fn bindgen_test_layout_skia_matrix() { assert_eq!( ::std::mem::size_of::(), 48usize, concat!("Size of: ", stringify!(skia_matrix)) ); assert_eq!( ::std::mem::align_of::(), 8usize, concat!("Alignment of ", stringify!(skia_matrix)) ); assert_eq!( unsafe { &(*(::std::ptr::null::())).a as *const _ as usize }, 0usize, concat!( "Offset of field: ", stringify!(skia_matrix), "::", stringify!(a) ) ); assert_eq!( unsafe { &(*(::std::ptr::null::())).b as *const _ as usize }, 8usize, concat!( "Offset of field: ", stringify!(skia_matrix), "::", stringify!(b) ) ); assert_eq!( unsafe { &(*(::std::ptr::null::())).c as *const _ as usize }, 16usize, concat!( "Offset of field: ", stringify!(skia_matrix), "::", stringify!(c) ) ); assert_eq!( unsafe { &(*(::std::ptr::null::())).d as *const _ as usize }, 24usize, concat!( "Offset of field: ", stringify!(skia_matrix), "::", stringify!(d) ) ); assert_eq!( unsafe { &(*(::std::ptr::null::())).e as *const _ as usize }, 32usize, concat!( "Offset of field: ", stringify!(skia_matrix), "::", stringify!(e) ) ); assert_eq!( unsafe { &(*(::std::ptr::null::())).f as *const _ as usize }, 40usize, concat!( "Offset of field: ", stringify!(skia_matrix), "::", stringify!(f) ) ); } #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct skia_point { pub x: f32, pub y: f32, } #[test] fn bindgen_test_layout_skia_point() { assert_eq!( ::std::mem::size_of::(), 8usize, concat!("Size of: ", stringify!(skia_point)) ); assert_eq!( ::std::mem::align_of::(), 4usize, concat!("Alignment of ", stringify!(skia_point)) ); assert_eq!( unsafe { &(*(::std::ptr::null::())).x as *const _ as usize }, 0usize, concat!( "Offset of field: ", stringify!(skia_point), "::", stringify!(x) ) ); assert_eq!( unsafe { &(*(::std::ptr::null::())).y as *const _ as usize }, 4usize, concat!( "Offset of field: ", stringify!(skia_point), "::", stringify!(y) ) ); } #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct skiac_surface_data { pub ptr: *mut u8, pub size: u32, } #[test] fn bindgen_test_layout_skiac_surface_data() { assert_eq!( ::std::mem::size_of::(), 16usize, concat!("Size of: ", stringify!(skiac_surface_data)) ); assert_eq!( ::std::mem::align_of::(), 8usize, concat!("Alignment of ", stringify!(skiac_surface_data)) ); assert_eq!( unsafe { &(*(::std::ptr::null::())).ptr as *const _ as usize }, 0usize, concat!( "Offset of field: ", stringify!(skiac_surface_data), "::", stringify!(ptr) ) ); assert_eq!( unsafe { &(*(::std::ptr::null::())).size as *const _ as usize }, 8usize, concat!( "Offset of field: ", stringify!(skiac_surface_data), "::", stringify!(size) ) ); } pub const PaintStyle_Fill: PaintStyle = 0; pub const PaintStyle_Stroke: PaintStyle = 1; pub type PaintStyle = i32; pub const StrokeCap_Butt: StrokeCap = 0; pub const StrokeCap_Round: StrokeCap = 1; pub const StrokeCap_Square: StrokeCap = 2; pub type StrokeCap = i32; pub const StrokeJoin_Miter: StrokeJoin = 0; pub const StrokeJoin_Round: StrokeJoin = 1; pub const StrokeJoin_Bevel: StrokeJoin = 2; pub type StrokeJoin = i32; pub const FillType_Winding: FillType = 0; pub const FillType_EvenOdd: FillType = 1; pub type FillType = i32; pub const TileMode_Clamp: TileMode = 0; pub const TileMode_Repeat: TileMode = 1; pub const TileMode_Mirror: TileMode = 2; pub type TileMode = i32; pub const BlendMode_Clear: BlendMode = 0; pub const BlendMode_SourceOver: BlendMode = 1; pub const BlendMode_DestinationOver: BlendMode = 2; pub const BlendMode_SourceIn: BlendMode = 3; pub const BlendMode_DestinationIn: BlendMode = 4; pub const BlendMode_SourceOut: BlendMode = 5; pub const BlendMode_DestinationOut: BlendMode = 6; pub const BlendMode_SourceAtop: BlendMode = 7; pub const BlendMode_Xor: BlendMode = 8; pub const BlendMode_Multiply: BlendMode = 9; pub const BlendMode_Screen: BlendMode = 10; pub const BlendMode_Darken: BlendMode = 11; pub const BlendMode_Lighten: BlendMode = 12; pub const BlendMode___Size: BlendMode = 13; pub type BlendMode = i32; pub const FilterQuality_None: FilterQuality = 0; pub const FilterQuality_Low: FilterQuality = 1; pub const FilterQuality_Medium: FilterQuality = 2; pub const FilterQuality_High: FilterQuality = 3; pub type FilterQuality = i32; extern "C" { pub fn skiac_surface_create_rgba_premultiplied( width: ::std::os::raw::c_int, height: ::std::os::raw::c_int, ) -> *mut skiac_surface; } extern "C" { pub fn skiac_surface_create_rgba( width: ::std::os::raw::c_int, height: ::std::os::raw::c_int, ) -> *mut skiac_surface; } extern "C" { pub fn skiac_surface_destroy(c_surface: *mut skiac_surface); } extern "C" { pub fn skiac_surface_copy_rgba( c_surface: *mut skiac_surface, x: u32, y: u32, width: u32, height: u32, ) -> *mut skiac_surface; } extern "C" { pub fn skiac_surface_save( c_surface: *mut skiac_surface, path: *const ::std::os::raw::c_char, ) -> bool; } extern "C" { pub fn skiac_surface_get_canvas(c_surface: *mut skiac_surface) -> *mut skiac_canvas; } extern "C" { pub fn skiac_surface_get_width(c_surface: *mut skiac_surface) -> ::std::os::raw::c_int; } extern "C" { pub fn skiac_surface_get_height(c_surface: *mut skiac_surface) -> ::std::os::raw::c_int; } extern "C" { pub fn skiac_surface_read_pixels(c_surface: *mut skiac_surface, data: *mut skiac_surface_data); } extern "C" { pub fn skiac_is_surface_bgra() -> bool; } extern "C" { pub fn skiac_canvas_clear(c_canvas: *mut skiac_canvas, color: u32); } extern "C" { pub fn skiac_canvas_flush(c_canvas: *mut skiac_canvas); } extern "C" { pub fn skiac_canvas_set_matrix(c_canvas: *mut skiac_canvas, c_matrix: *mut skiac_matrix); } extern "C" { pub fn skiac_canvas_concat(c_canvas: *mut skiac_canvas, c_matrix: *mut skiac_matrix); } extern "C" { pub fn skiac_canvas_scale(c_canvas: *mut skiac_canvas, sx: f64, sy: f64); } extern "C" { pub fn skiac_canvas_translate(c_canvas: *mut skiac_canvas, dx: f64, dy: f64); } extern "C" { pub fn skiac_canvas_get_total_matrix(c_canvas: *mut skiac_canvas) -> *mut skiac_matrix; } extern "C" { pub fn skiac_canvas_draw_path( c_canvas: *mut skiac_canvas, c_path: *mut skiac_path, c_paint: *mut skiac_paint, ); } extern "C" { pub fn skiac_canvas_draw_rect( c_canvas: *mut skiac_canvas, x: f64, y: f64, w: f64, h: f64, c_paint: *mut skiac_paint, ); } extern "C" { pub fn skiac_canvas_draw_surface( c_canvas: *mut skiac_canvas, c_surface: *mut skiac_surface, left: f64, top: f64, alpha: u8, blendMode: BlendMode, filterQuality: FilterQuality, ); } extern "C" { pub fn skiac_canvas_draw_surface_rect( c_canvas: *mut skiac_canvas, c_surface: *mut skiac_surface, x: f64, y: f64, w: f64, h: f64, filterQuality: FilterQuality, ); } extern "C" { pub fn skiac_canvas_reset_matrix(c_canvas: *mut skiac_canvas); } extern "C" { pub fn skiac_canvas_clip_rect(c_canvas: *mut skiac_canvas, x: f64, y: f64, w: f64, h: f64); } extern "C" { pub fn skiac_canvas_save(c_canvas: *mut skiac_canvas); } extern "C" { pub fn skiac_canvas_restore(c_canvas: *mut skiac_canvas); } extern "C" { pub fn skiac_matrix_create() -> *mut skiac_matrix; } extern "C" { pub fn skiac_matrix_create_from( a: f64, b: f64, c: f64, d: f64, e: f64, f: f64, ) -> *mut skiac_matrix; } extern "C" { pub fn skiac_matrix_create_inverse(c_matrix: *mut skiac_matrix) -> *mut skiac_matrix; } extern "C" { pub fn skiac_matrix_get_data(c_matrix: *mut skiac_matrix) -> skia_matrix; } extern "C" { pub fn skiac_matrix_destroy(c_matrix: *mut skiac_matrix); } extern "C" { pub fn skiac_paint_create() -> *mut skiac_paint; } extern "C" { pub fn skiac_paint_destroy(c_paint: *mut skiac_paint); } extern "C" { pub fn skiac_paint_set_style(c_paint: *mut skiac_paint, style: PaintStyle); } extern "C" { pub fn skiac_paint_set_color(c_paint: *mut skiac_paint, r: u8, g: u8, b: u8, a: u8); } extern "C" { pub fn skiac_paint_set_alpha(c_paint: *mut skiac_paint, a: u8); } extern "C" { pub fn skiac_paint_set_anti_alias(c_paint: *mut skiac_paint, aa: bool); } extern "C" { pub fn skiac_paint_set_blend_mode(c_paint: *mut skiac_paint, blendMode: BlendMode); } extern "C" { pub fn skiac_paint_set_shader(c_paint: *mut skiac_paint, c_shader: *mut skiac_shader); } extern "C" { pub fn skiac_paint_set_stroke_width(c_paint: *mut skiac_paint, width: f64); } extern "C" { pub fn skiac_paint_set_stroke_cap(c_paint: *mut skiac_paint, cap: StrokeCap); } extern "C" { pub fn skiac_paint_set_stroke_join(c_paint: *mut skiac_paint, join: StrokeJoin); } extern "C" { pub fn skiac_paint_set_stroke_miter(c_paint: *mut skiac_paint, miter: f32); } extern "C" { pub fn skiac_paint_set_path_effect( c_paint: *mut skiac_paint, c_path_effect: *mut skiac_path_effect, ); } extern "C" { pub fn skiac_path_create() -> *mut skiac_path; } extern "C" { pub fn skiac_path_destroy(c_path: *mut skiac_path); } extern "C" { pub fn skiac_path_set_fill_type(c_path: *mut skiac_path, type_: FillType); } extern "C" { pub fn skiac_path_move_to(c_path: *mut skiac_path, x: f64, y: f64); } extern "C" { pub fn skiac_path_line_to(c_path: *mut skiac_path, x: f64, y: f64); } extern "C" { pub fn skiac_path_cubic_to( c_path: *mut skiac_path, x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64, ); } extern "C" { pub fn skiac_path_close(c_path: *mut skiac_path); } extern "C" { pub fn skiac_path_effect_make_dash_path( intervals: *const f32, count: ::std::os::raw::c_int, phase: f32, ) -> *mut skiac_path_effect; } extern "C" { pub fn skiac_path_effect_destroy(c_path_effect: *mut skiac_path_effect); } extern "C" { pub fn skiac_shader_make_linear_gradient( points: *const skia_point, colors: *const u32, positions: *const f32, count: ::std::os::raw::c_int, tile_mode: TileMode, flags: u32, c_matrix: *mut skiac_matrix, ) -> *mut skiac_shader; } extern "C" { pub fn skiac_shader_make_two_point_conical_gradient( start_point: skia_point, start_radius: f32, end_point: skia_point, end_radius: f32, colors: *const u32, positions: *const f32, count: ::std::os::raw::c_int, tile_mode: TileMode, flags: u32, c_matrix: *mut skiac_matrix, ) -> *mut skiac_shader; } extern "C" { pub fn skiac_shader_make_from_surface_image( c_surface: *mut skiac_surface, c_matrix: *mut skiac_matrix, ) -> *mut skiac_shader; } extern "C" { pub fn skiac_shader_destroy(c_shader: *mut skiac_shader); } resvg-0.8.0/bindings/resvg-skia/src/lib.rs000066400000000000000000000321441352576375700204500ustar00rootroot00000000000000use std::ffi::CString; use std::ops::{Deref, DerefMut}; use std::slice; #[allow(dead_code)] #[allow(non_camel_case_types)] #[allow(non_upper_case_globals)] mod ffi; pub use ffi::skiac_surface; #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum PaintStyle { Fill = 0, Stroke = 1, } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum FillType { Winding = 0, EvenOdd = 1, } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum StrokeCap { Butt = 0, Round = 1, Square = 2, } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum StrokeJoin { Miter = 0, Round = 1, Bevel = 2, } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum TileMode { Clamp = 0, Repeat = 1, Mirror = 2, } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum BlendMode { Clear = 0, SourceOver = 1, DestinationOver = 2, SourceIn = 3, DestinationIn = 4, SourceOut = 5, DestinationOut = 6, SourceAtop = 7, Xor = 8, Multiply = 9, Screen = 10, Darken = 11, Lighten = 12, } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum FilterQuality { None = 0, Low = 1, Medium = 2, High = 3, } pub struct Surface { ptr: *mut ffi::skiac_surface, canvas: Canvas, } impl Surface { pub fn new_rgba(width: u32, height: u32) -> Option { unsafe { Self::from_ptr(ffi::skiac_surface_create_rgba(width as i32, height as i32)) } } pub fn new_rgba_premultiplied(width: u32, height: u32) -> Option { unsafe { Self::from_ptr(ffi::skiac_surface_create_rgba_premultiplied(width as i32, height as i32)) } } pub unsafe fn from_ptr(ptr: *mut ffi::skiac_surface) -> Option { if ptr.is_null() { None } else { Some(Surface { ptr, canvas: Canvas(ffi::skiac_surface_get_canvas(ptr)) }) } } pub fn destroy(&mut self) { unsafe { ffi::skiac_surface_destroy(self.ptr); } } pub fn copy_rgba(&self, x: u32, y: u32, width: u32, height: u32) -> Option { unsafe { Self::from_ptr(ffi::skiac_surface_copy_rgba(self.ptr, x, y, width, height)) } } pub fn try_clone(&self) -> Option { unsafe { Self::from_ptr(ffi::skiac_surface_copy_rgba(self.ptr, 0, 0, self.width(), self.height())) } } pub fn save_png(&self, path: &str) -> bool { let c_path = CString::new(path).unwrap(); unsafe { ffi::skiac_surface_save(self.ptr, c_path.as_ptr()) } } pub fn width(&self) -> u32 { unsafe { ffi::skiac_surface_get_width(self.ptr) as u32 } } pub fn height(&self) -> u32 { unsafe { ffi::skiac_surface_get_height(self.ptr) as u32 } } pub fn data(&self) -> SurfaceData { unsafe { let mut data = ffi::skiac_surface_data { ptr: std::ptr::null_mut(), size: 0, }; ffi::skiac_surface_read_pixels(self.ptr, &mut data); SurfaceData { slice: slice::from_raw_parts_mut(data.ptr, data.size as usize), } } } pub fn data_mut(&mut self) -> SurfaceData { unsafe { let mut data = ffi::skiac_surface_data { ptr: std::ptr::null_mut(), size: 0, }; ffi::skiac_surface_read_pixels(self.ptr, &mut data); SurfaceData { slice: slice::from_raw_parts_mut(data.ptr, data.size as usize), } } } pub fn is_bgra() -> bool { unsafe { ffi::skiac_is_surface_bgra() } } } impl std::ops::Deref for Surface { type Target = Canvas; fn deref(&self) -> &Self::Target { &self.canvas } } impl std::ops::DerefMut for Surface { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.canvas } } impl Drop for Surface { fn drop(&mut self) { unsafe { ffi::skiac_surface_destroy(self.ptr); } } } pub struct SurfaceData<'a> { slice: &'a mut [u8], } impl<'a> Deref for SurfaceData<'a> { type Target = [u8]; fn deref(&self) -> &[u8] { self.slice } } impl<'a> DerefMut for SurfaceData<'a> { fn deref_mut(&mut self) -> &mut [u8] { self.slice } } pub struct Color(u8, u8, u8, u8); impl Color { pub fn new(a: u8, r: u8, g: u8, b: u8) -> Color { Color(a, r, g, b) } pub fn to_u32(&self) -> u32 { (self.0 as u32) << 24 | (self.1 as u32) << 16 | (self.2 as u32) << 8 | (self.3 as u32) } } pub struct Matrix(*mut ffi::skiac_matrix); impl Matrix { pub fn new() -> Matrix { unsafe { Matrix(ffi::skiac_matrix_create()) } } pub fn new_from(a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) -> Matrix { unsafe { Matrix(ffi::skiac_matrix_create_from(a, b, c, d, e, f)) } } pub fn invert(&self) -> Option { unsafe { let ptr = ffi::skiac_matrix_create_inverse(self.0); if ptr.is_null() { None } else { Some(Matrix(ptr)) } } } pub fn data(&self) -> (f64, f64, f64, f64, f64, f64) { let mat = unsafe { ffi::skiac_matrix_get_data(self.0) }; (mat.a, mat.b, mat.c, mat.d, mat.e, mat.f) } } impl Default for Matrix { fn default() -> Matrix { unsafe { Matrix(ffi::skiac_matrix_create()) } } } impl Drop for Matrix { fn drop(&mut self) { unsafe { ffi::skiac_matrix_destroy(self.0) } } } pub struct Canvas(*mut ffi::skiac_canvas); impl Canvas { pub fn clear(&mut self) { unsafe { ffi::skiac_canvas_clear(self.0, 0); } } pub fn fill(&mut self, r: u8, g: u8, b: u8, a: u8) { unsafe { ffi::skiac_canvas_clear(self.0, (a as u32) << 24 | (r as u32) << 16 | (g as u32) << 8 | b as u32); } } pub fn flush(&mut self) { unsafe { ffi::skiac_canvas_flush(self.0); } } pub fn set_matrix(&mut self, matrix: &Matrix) { unsafe { ffi::skiac_canvas_set_matrix(self.0, matrix.0); } } pub fn concat(&mut self, matrix: &Matrix) { unsafe { ffi::skiac_canvas_concat(self.0, matrix.0); } } pub fn scale(&mut self, sx: f64, sy: f64) { unsafe { ffi::skiac_canvas_scale(self.0, sx, sy); } } pub fn translate(&mut self, dx: f64, dy: f64) { unsafe { ffi::skiac_canvas_translate(self.0, dx, dy); } } pub fn get_matrix(&self) -> Matrix { unsafe { Matrix(ffi::skiac_canvas_get_total_matrix(self.0)) } } pub fn draw_path(&mut self, path: &Path, paint: &Paint) { unsafe { ffi::skiac_canvas_draw_path(self.0, path.0, paint.0); } } pub fn draw_rect(&mut self, x: f64, y: f64, w: f64, h: f64, paint: &Paint) { unsafe { ffi::skiac_canvas_draw_rect(self.0, x, y, w, h, paint.0); } } pub fn draw_surface(&mut self, surface: &Surface, left: f64, top: f64, alpha: u8, blend_mode: BlendMode, filter_quality: FilterQuality) { unsafe { ffi::skiac_canvas_draw_surface( self.0, surface.ptr, left, top, alpha, blend_mode as i32, filter_quality as i32, ); } } pub fn draw_surface_rect(&mut self, surface: &Surface, x: f64, y: f64, w: f64, h: f64, filter_quality: FilterQuality) { unsafe { ffi::skiac_canvas_draw_surface_rect( self.0, surface.ptr, x, y, w, h, filter_quality as i32, ); } } pub fn reset_matrix(&mut self) { unsafe { ffi::skiac_canvas_reset_matrix(self.0); } } pub fn set_clip_rect(&mut self, x: f64, y: f64, w: f64, h: f64) { unsafe { ffi::skiac_canvas_clip_rect(self.0, x, y, w, h); } } pub fn save(&mut self) { unsafe { ffi::skiac_canvas_save(self.0); } } pub fn restore(&mut self) { unsafe { ffi::skiac_canvas_restore(self.0); } } } pub struct Paint(*mut ffi::skiac_paint); impl Paint { pub fn new() -> Paint { unsafe { Paint(ffi::skiac_paint_create()) } } pub fn set_style(&mut self, style: PaintStyle) { unsafe { ffi::skiac_paint_set_style(self.0, style as i32); } } pub fn set_color(&mut self, r: u8, g: u8, b: u8, a: u8) { unsafe { ffi::skiac_paint_set_color(self.0, r, g, b, a); } } pub fn set_alpha(&mut self, a: u8) { unsafe { ffi::skiac_paint_set_alpha(self.0, a); } } pub fn set_anti_alias(&mut self, aa: bool) { unsafe { ffi::skiac_paint_set_anti_alias(self.0, aa); } } pub fn set_blend_mode(&mut self, blend_mode: BlendMode) { unsafe { ffi::skiac_paint_set_blend_mode(self.0, blend_mode as i32); } } pub fn set_shader(&mut self, shader: &Shader) { unsafe { ffi::skiac_paint_set_shader(self.0, shader.0); } } pub fn set_stroke_width(&mut self, width: f64) { unsafe { ffi::skiac_paint_set_stroke_width(self.0, width); } } pub fn set_stroke_cap(&mut self, cap: StrokeCap) { unsafe { ffi::skiac_paint_set_stroke_cap(self.0, cap as i32); } } pub fn set_stroke_join(&mut self, join: StrokeJoin) { unsafe { ffi::skiac_paint_set_stroke_join(self.0, join as i32); } } pub fn set_stroke_miter(&mut self, miter: f64) { unsafe { ffi::skiac_paint_set_stroke_miter(self.0, miter as f32); } } pub fn set_path_effect(&mut self, path_effect: PathEffect) { unsafe { ffi::skiac_paint_set_path_effect(self.0, path_effect.0); } } } impl Drop for Paint { fn drop(&mut self) { unsafe { ffi::skiac_paint_destroy(self.0) } } } pub struct Path(*mut ffi::skiac_path); impl Path { pub fn new() -> Path { unsafe { Path(ffi::skiac_path_create()) } } pub fn set_fill_type(&mut self, kind: FillType) { unsafe { ffi::skiac_path_set_fill_type(self.0, kind as i32); } } pub fn move_to(&mut self, x: f64, y: f64) { unsafe { ffi::skiac_path_move_to(self.0, x, y); } } pub fn line_to(&mut self, x: f64, y: f64) { unsafe { ffi::skiac_path_line_to(self.0, x, y); } } pub fn cubic_to(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64) { unsafe { ffi::skiac_path_cubic_to(self.0, x1, y1, x2, y2, x3, y3); } } pub fn close(&mut self) { unsafe { ffi::skiac_path_close(self.0); } } } impl Drop for Path { fn drop(&mut self) { unsafe { ffi::skiac_path_destroy(self.0); } } } pub struct Gradient { pub colors: Vec, pub positions: Vec, pub tile_mode: TileMode, pub matrix: Matrix } pub struct LinearGradient { pub start_point: (f64, f64), pub end_point: (f64, f64), pub base: Gradient } pub struct RadialGradient { pub start_circle: (f64, f64, f64), pub end_circle: (f64, f64, f64), pub base: Gradient } pub struct Shader(*mut ffi::skiac_shader); impl Shader { pub fn new_linear_gradient(grad: LinearGradient) -> Shader { let points = [ ffi::skia_point {x: grad.start_point.0 as f32, y: grad.start_point.1 as f32}, ffi::skia_point {x: grad.end_point.0 as f32, y: grad.end_point.1 as f32} ]; unsafe { Shader(ffi::skiac_shader_make_linear_gradient( points.as_ptr(), grad.base.colors.as_ptr(), grad.base.positions.as_ptr(), grad.base.colors.len() as i32, grad.base.tile_mode as i32, 0 as u32, grad.base.matrix.0) ) } } pub fn new_radial_gradient(grad: RadialGradient) -> Shader { let start_point = ffi::skia_point { x: grad.start_circle.0 as f32, y: grad.start_circle.1 as f32 }; let end_point = ffi::skia_point { x: grad.end_circle.0 as f32, y: grad.end_circle.1 as f32 }; let start_radius = grad.start_circle.2 as f32; let end_radius = grad.end_circle.2 as f32; unsafe { Shader(ffi::skiac_shader_make_two_point_conical_gradient( start_point, start_radius, end_point, end_radius, grad.base.colors.as_ptr(), grad.base.positions.as_ptr(), grad.base.colors.len() as i32, grad.base.tile_mode as i32, 0 as u32, grad.base.matrix.0) ) } } pub fn new_from_surface_image(surface: &Surface, matrix: Matrix) -> Shader { unsafe { Shader(ffi::skiac_shader_make_from_surface_image(surface.ptr, matrix.0)) } } } impl Drop for Shader { fn drop(&mut self) { unsafe { ffi::skiac_shader_destroy(self.0); } } } pub struct PathEffect(*mut ffi::skiac_path_effect); impl PathEffect { pub fn new_dash_path(intervals: &[f32], phase: f32) -> PathEffect { unsafe { PathEffect(ffi::skiac_path_effect_make_dash_path( intervals.as_ptr(), intervals.len() as i32, phase )) } } } impl Drop for PathEffect { fn drop(&mut self) { unsafe { ffi::skiac_path_effect_destroy(self.0); } } } resvg-0.8.0/capi/000077500000000000000000000000001352576375700136055ustar00rootroot00000000000000resvg-0.8.0/capi/.gitignore000066400000000000000000000000401352576375700155670ustar00rootroot00000000000000/include/html /include/Doxyfile resvg-0.8.0/capi/Cargo.toml000066400000000000000000000011031352576375700155300ustar00rootroot00000000000000[package] name = "resvg-capi" version = "0.8.0" authors = ["Evgeniy Reizner "] keywords = ["svg", "render", "raster", "capi"] license = "MPL-2.0" edition = "2018" workspace = ".." [lib] name = "resvg" crate-type = ["cdylib", "staticlib"] [dependencies] fern = "0.5" log = "0.4" resvg = { path = "../" } # cairo backend cairo-sys-rs = { version = "0.9.0", optional = true } [features] cairo-backend = ["resvg/cairo-backend", "cairo-sys-rs"] qt-backend = ["resvg/qt-backend"] skia-backend = ["resvg/skia-backend"] raqote-backend = ["resvg/raqote-backend"] resvg-0.8.0/capi/README.md000066400000000000000000000010211352576375700150560ustar00rootroot00000000000000C interface for *resvg*. ## Build ```bash # Build with a Qt backend cargo build --release --features="qt-backend" # or with a cairo backend cargo build --release --features="cairo-backend" # or with both. cargo build --release --features="qt-backend cairo-backend" ``` See [BUILD.adoc](../BUILD.adoc) for details. ## Examples A usage example with a *cairo* backend can be found at [examples/cairo-capi](../examples/cairo-capi). A usage example with a *qt* backend can be found in the [tools/viewsvg](../tools/viewsvg) app. resvg-0.8.0/capi/include/000077500000000000000000000000001352576375700152305ustar00rootroot00000000000000resvg-0.8.0/capi/include/ResvgQt.h000066400000000000000000000241631352576375700170020ustar00rootroot00000000000000/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** * @file ResvgQt.h * * Qt wrapper for resvg C-API */ #ifndef RESVGQT_H #define RESVGQT_H #define RESVG_QT_BACKEND #include #include #include #include #include #include #include #include #include #include namespace ResvgPrivate { static const char* toCStr(const QString &text) { const auto utf8 = text.toUtf8(); const auto data = utf8.constData(); return qstrdup(data); } class Data { public: Data() { init(); } ~Data() { clear(); } void reset() { clear(); init(); } resvg_render_tree *tree = nullptr; resvg_options opt; qreal scaleFactor = 1.0; QRectF viewBox; QString errMsg; private: void init() { resvg_init_options(&opt); // Do not set the default font via QFont::family() // because it will return a dummy one on Windows. // See https://github.com/RazrFalcon/resvg/issues/159 opt.font_family = "Times New Roman"; opt.languages = toCStr(QLocale().bcp47Name()); opt.dpi = 96 * scaleFactor; } void clear() { // No need to deallocate opt.font_family, because it is a constant. if (tree) { resvg_tree_destroy(tree); tree = nullptr; } if (opt.path) { delete[] opt.path; // do not use free() because was allocated via qstrdup() opt.path = NULL; } if (opt.languages) { delete[] opt.languages; // do not use free() because was allocated via qstrdup() opt.languages = NULL; } viewBox = QRectF(); errMsg = QString(); } }; static QString errorToString(const int err) { switch (err) { case RESVG_OK : return QString(); case RESVG_ERROR_NOT_AN_UTF8_STR : return QLatin1Literal("The SVG content has not an UTF-8 encoding."); case RESVG_ERROR_FILE_OPEN_FAILED : return QLatin1Literal("Failed to open the file."); case RESVG_ERROR_FILE_WRITE_FAILED : return QLatin1Literal("Failed to write to the file."); case RESVG_ERROR_INVALID_FILE_SUFFIX : return QLatin1Literal("Invalid file suffix."); case RESVG_ERROR_MALFORMED_GZIP : return QLatin1Literal("Not a GZip compressed data."); case RESVG_ERROR_INVALID_SIZE : return QLatin1Literal("SVG doesn't have a valid size."); case RESVG_ERROR_PARSING_FAILED : return QLatin1Literal("Failed to parse an SVG data."); case RESVG_ERROR_NO_CANVAS : return QLatin1Literal("Failed to allocate the canvas."); } Q_UNREACHABLE(); } } //ResvgPrivate /** * @brief QSvgRenderer-like wrapper for resvg C-API */ class ResvgRenderer { public: /** * @brief Constructs a new renderer. */ ResvgRenderer(); /** * @brief Constructs a new renderer and loads the contents of the SVG(Z) file. */ ResvgRenderer(const QString &filePath); /** * @brief Constructs a new renderer and loads the SVG data. */ ResvgRenderer(const QByteArray &data); /** * @brief Destructs the renderer. */ ~ResvgRenderer(); /** * @brief Loads the contents of the SVG(Z) file. */ bool load(const QString &filePath); /** * @brief Loads the SVG data. */ bool load(const QByteArray &data); /** * @brief Returns \b true if the file or data were loaded successful. */ bool isValid() const; /** * @brief Returns an underling error when #isValid is \b false. */ QString errorString() const; /** * @brief Checks that underling tree has any nodes. * * #ResvgRenderer and #ResvgRenderer constructors * will set an error only if a file does not exist or it has a non-UTF-8 encoding. * All other errors will result in an empty tree with a 100x100px size. * * @return Returns \b true if tree has any nodes. */ bool isEmpty() const; /** * @brief Returns an SVG size. */ QSize defaultSize() const; /** * @brief Returns an SVG size. */ QSizeF defaultSizeF() const; /** * @brief Returns an SVG viewbox. */ QRect viewBox() const; /** * @brief Returns an SVG viewbox. */ QRectF viewBoxF() const; /** * @brief Returns bounding rectangle of the item with the given \b id. * The transformation matrix of parent elements is not affecting * the bounds of the element. */ QRectF boundsOnElement(const QString &id) const; /** * @brief Returns bounding rectangle of a whole image. */ QRectF boundingBox() const; /** * @brief Returns \b true if element with such an ID exists. */ bool elementExists(const QString &id) const; /** * @brief Returns element's transform. */ QTransform transformForElement(const QString &id) const; /** * @brief Sets the device pixel ratio for the image. */ void setDevicePixelRatio(qreal scaleFactor); /** * @brief Renders the SVG data to canvas. */ void render(QPainter *p) const; /** * @brief Renders the SVG data to \b QImage with a specified \b size. * * If \b size is not set, the \b defaultSize() will be used. */ QImage renderToImage(const QSize &size = QSize()) const; /** * @brief Initializes the library log. * * Use it if you want to see any warnings. * * Must be called only once. * * All warnings will be printed to the \b stderr. */ static void initLog(); private: QScopedPointer d; }; // Implementation. inline ResvgRenderer::ResvgRenderer() : d(new ResvgPrivate::Data()) { } inline ResvgRenderer::ResvgRenderer(const QString &filePath) : d(new ResvgPrivate::Data()) { load(filePath); } inline ResvgRenderer::ResvgRenderer(const QByteArray &data) : d(new ResvgPrivate::Data()) { load(data); } inline ResvgRenderer::~ResvgRenderer() {} inline bool ResvgRenderer::load(const QString &filePath) { // Check for Qt resource path. if (filePath.startsWith(QLatin1String(":/"))) { QFile file(filePath); if (file.open(QFile::ReadOnly)) { return load(file.readAll()); } else { return false; } } d->reset(); d->opt.path = ResvgPrivate::toCStr(filePath); const auto err = resvg_parse_tree_from_file(d->opt.path, &d->opt, &d->tree); if (err != RESVG_OK) { d->errMsg = ResvgPrivate::errorToString(err); return false; } const auto r = resvg_get_image_viewbox(d->tree); d->viewBox = QRectF(r.x, r.y, r.width, r.height); return true; } inline bool ResvgRenderer::load(const QByteArray &data) { d->reset(); const auto err = resvg_parse_tree_from_data(data.constData(), data.size(), &d->opt, &d->tree); if (err != RESVG_OK) { d->errMsg = ResvgPrivate::errorToString(err); return false; } const auto r = resvg_get_image_viewbox(d->tree); d->viewBox = QRectF(r.x, r.y, r.width, r.height); return true; } inline bool ResvgRenderer::isValid() const { return d->tree; } inline QString ResvgRenderer::errorString() const { return d->errMsg; } inline bool ResvgRenderer::isEmpty() const { if (d->tree) return !resvg_is_image_empty(d->tree); else return true; } inline QSize ResvgRenderer::defaultSize() const { return defaultSizeF().toSize(); } inline QSizeF ResvgRenderer::defaultSizeF() const { if (d->tree) return d->viewBox.size(); else return QSizeF(); } inline QRect ResvgRenderer::viewBox() const { return viewBoxF().toRect(); } inline QRectF ResvgRenderer::viewBoxF() const { if (d->tree) return d->viewBox; else return QRectF(); } inline QRectF ResvgRenderer::boundsOnElement(const QString &id) const { if (!d->tree) return QRectF(); const auto utf8Str = id.toUtf8(); const auto rawId = utf8Str.constData(); resvg_rect bbox; if (resvg_get_node_bbox(d->tree, rawId, &bbox)) return QRectF(bbox.x, bbox.y, bbox.height, bbox.width); return QRectF(); } inline QRectF ResvgRenderer::boundingBox() const { if (!d->tree) return QRectF(); resvg_rect bbox; if (resvg_get_image_bbox(d->tree, &bbox)) return QRectF(bbox.x, bbox.y, bbox.height, bbox.width); return QRectF(); } inline bool ResvgRenderer::elementExists(const QString &id) const { if (!d->tree) return false; const auto utf8Str = id.toUtf8(); const auto rawId = utf8Str.constData(); return resvg_node_exists(d->tree, rawId); } inline QTransform ResvgRenderer::transformForElement(const QString &id) const { if (!d->tree) return QTransform(); const auto utf8Str = id.toUtf8(); const auto rawId = utf8Str.constData(); resvg_transform ts; if (resvg_get_node_transform(d->tree, rawId, &ts)) return QTransform(ts.a, ts.b, ts.c, ts.d, ts.e, ts.f); return QTransform(); } inline void ResvgRenderer::setDevicePixelRatio(qreal scaleFactor) { d->scaleFactor = scaleFactor; } inline void ResvgRenderer::render(QPainter *p) const { if (!d->tree) return; p->save(); p->setRenderHint(QPainter::Antialiasing); const auto r = p->viewport(); resvg_size imgSize { (uint)r.width(), (uint)r.height() }; resvg_qt_render_to_canvas(d->tree, &d->opt, imgSize, p); p->restore(); } inline QImage ResvgRenderer::renderToImage(const QSize &size) const { const auto s = size.isValid() ? size : defaultSize(); QImage img(s, QImage::Format_ARGB32_Premultiplied); img.fill(Qt::transparent); QPainter p(&img); render(&p); p.end(); return img; } inline void ResvgRenderer::initLog() { resvg_init_log(); } #undef RESVG_QT_BACKEND #endif // RESVGQT_H resvg-0.8.0/capi/include/resvg.h000066400000000000000000000336411352576375700165360ustar00rootroot00000000000000/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** * @file resvg.h * * resvg C-API */ #ifndef RESVG_H #define RESVG_H #include #include #include #ifdef RESVG_CAIRO_BACKEND #include #endif #define RESVG_MAJOR_VERSION 0 #define RESVG_MINOR_VERSION 8 #define RESVG_PATCH_VERSION 0 #define RESVG_VERSION "0.8.0" #ifdef __cplusplus extern "C" { #endif /** * @brief An opaque pointer to the rendering tree. */ typedef struct resvg_render_tree resvg_render_tree; /** * @brief List of possible errors. */ typedef enum resvg_error { /** Everything is ok. */ RESVG_OK = 0, /** Only UTF-8 content are supported. */ RESVG_ERROR_NOT_AN_UTF8_STR, /** Failed to open the provided file. */ RESVG_ERROR_FILE_OPEN_FAILED, /** Failed to write to the provided file. */ RESVG_ERROR_FILE_WRITE_FAILED, /** Only \b svg and \b svgz suffixes are supported. */ RESVG_ERROR_INVALID_FILE_SUFFIX, /** Compressed SVG must use the GZip algorithm. */ RESVG_ERROR_MALFORMED_GZIP, /** * SVG doesn't have a valid size. * * Occurs when width and/or height are <= 0. * * Also occurs if width, height and viewBox are not set. * This is against the SVG spec, but an automatic size detection is not supported yet. */ RESVG_ERROR_INVALID_SIZE, /** Failed to parse an SVG data. */ RESVG_ERROR_PARSING_FAILED, /** Failed to allocate an image. */ RESVG_ERROR_NO_CANVAS, } resvg_error; /** * @brief An RGB color representation. */ typedef struct resvg_color { uint8_t r; /**< Red component. */ uint8_t g; /**< Green component. */ uint8_t b; /**< Blue component. */ } resvg_color; /** * @brief A "fit to" type. * * All types produce proportional scaling. */ typedef enum resvg_fit_to_type { RESVG_FIT_TO_ORIGINAL, /**< Use an original image size. */ RESVG_FIT_TO_WIDTH, /**< Fit an image to a specified width. */ RESVG_FIT_TO_HEIGHT, /**< Fit an image to a specified height. */ RESVG_FIT_TO_ZOOM, /**< Zoom an image using scaling factor */ } resvg_fit_to_type; /** * @brief A "fit to" property. */ typedef struct resvg_fit_to { resvg_fit_to_type type; /**< Fit type. */ float value; /**< Fit to value. Must be > 0. */ } resvg_fit_to; /** * @brief A shape rendering method. */ typedef enum resvg_shape_rendering { RESVG_SHAPE_RENDERING_OPTIMIZE_SPEED, RESVG_SHAPE_RENDERING_CRISP_EDGES, RESVG_SHAPE_RENDERING_GEOMETRIC_PRECISION, } resvg_shape_rendering; /** * @brief A text rendering method. */ typedef enum resvg_text_rendering { RESVG_TEXT_RENDERING_OPTIMIZE_SPEED, RESVG_TEXT_RENDERING_OPTIMIZE_LEGIBILITY, RESVG_TEXT_RENDERING_GEOMETRIC_PRECISION, } resvg_text_rendering; /** * @brief An image rendering method. */ typedef enum resvg_image_rendering { RESVG_IMAGE_RENDERING_OPTIMIZE_QUALITY, RESVG_IMAGE_RENDERING_OPTIMIZE_SPEED, } resvg_image_rendering; /** * @brief Rendering options. */ typedef struct resvg_options { /** SVG image path. Used to resolve relative image paths. * * Default: NULL */ const char *path; /** Output DPI. * * Default: 96. */ double dpi; /** Default font family. * * Must be set before passing to rendering functions. * * Default: NULL. */ const char *font_family; /** Default font size. * * Default: 12. */ double font_size; /** * 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' * * Must be set before passing to rendering functions. * * Default: NULL. */ const char *languages; /** * Specifies the default shape rendering method. * * Will be used when an SVG element's \b shape-rendering property is set to \b auto. * * Default: \b RESVG_SHAPE_RENDERING_GEOMETRIC_PRECISION. */ resvg_shape_rendering shape_rendering; /** * Specifies the default text rendering method. * * Will be used when an SVG element's \b text-rendering property is set to \b auto. * * Default: \b RESVG_TEXT_RENDERING_OPTIMIZE_LEGIBILITY. */ resvg_text_rendering text_rendering; /** * Specifies the default image rendering method. * * Will be used when an SVG element's \b image-rendering property is set to \b auto. * * Default: \b RESVG_IMAGE_RENDERING_OPTIMIZE_QUALITY. */ resvg_image_rendering image_rendering; /** * Fits the image using specified options. * * Default: \b RESVG_FIT_TO_ORIGINAL. */ resvg_fit_to fit_to; /** Draw background. * * Default: false. */ bool draw_background; /** Background color. */ resvg_color background; /** * Keep named groups. If set to \b true, all non-empty * groups with \b id attribute will not be removed. * * Default: false */ bool keep_named_groups; } resvg_options; /** * @brief A rectangle representation. */ typedef struct resvg_rect { double x; /**< X position. */ double y; /**< Y position. */ double width; /**< Width. */ double height; /**< Height. */ } resvg_rect; /** * @brief A size representation. */ typedef struct resvg_size { uint32_t width; /**< Width. */ uint32_t height; /**< Height. */ } resvg_size; /** * @brief A 2D transform representation. */ typedef struct resvg_transform { double a; /**< \b a value */ double b; /**< \b b value */ double c; /**< \b c value */ double d; /**< \b d value */ double e; /**< \b e value */ double f; /**< \b f value */ } resvg_transform; /** * @brief Initializes the library log. * * Use it if you want to see any warnings. * * Must be called only once. * * All warnings will be printed to the \b stderr. */ void resvg_init_log(); /** * @brief Initializes the #resvg_options structure. */ void resvg_init_options(resvg_options *opt); /** * @brief Creates #resvg_render_tree from file. * * .svg and .svgz files are supported. * * See #resvg_is_image_empty for details. * * @param file_path UTF-8 file path. * @param opt Rendering options. * @param tree Parsed render tree. Should be destroyed via #resvg_tree_destroy. * @return #resvg_error */ int resvg_parse_tree_from_file(const char *file_path, const resvg_options *opt, resvg_render_tree **tree); /** * @brief Creates #resvg_render_tree from data. * * See #resvg_is_image_empty for details. * * @param data SVG data. Can contain SVG string or gzip compressed data. * @param len Data length. * @param opt Rendering options. * @param tree Parsed render tree. Should be destroyed via #resvg_tree_destroy. * @return #resvg_error */ int resvg_parse_tree_from_data(const char *data, const size_t len, const resvg_options *opt, resvg_render_tree **tree); /** * @brief Checks that tree has any nodes. * * @param tree Render tree. * @return Returns \b true if tree has any nodes. */ bool resvg_is_image_empty(const resvg_render_tree *tree); /** * @brief Returns an image size. * * The size of a canvas that required to render this SVG. * * @param tree Render tree. * @return Image size. */ resvg_size resvg_get_image_size(const resvg_render_tree *tree); /** * @brief Returns an image viewbox. * * @param tree Render tree. * @return Image viewbox. */ resvg_rect resvg_get_image_viewbox(const resvg_render_tree *tree); /** * @brief Returns an image bounding box. * * Can be smaller or bigger than a \b viewbox. * * @param tree Render tree. * @param bbox Image's bounding box. * @return \b false if an image has no elements. */ bool resvg_get_image_bbox(const resvg_render_tree *tree, resvg_rect *bbox); /** * @brief Returns \b true if a renderable node with such an ID exists. * * @param tree Render tree. * @param id Node's ID. UTF-8 string. * @return \b true if a node exists. * @return \b false if a node doesn't exist or ID isn't a UTF-8 string. * @return \b false if a node exists, but not renderable. */ bool resvg_node_exists(const resvg_render_tree *tree, const char *id); /** * @brief Returns node's transform by ID. * * @param tree Render tree. * @param id Node's ID. UTF-8 string. * @param ts Node's transform. * @return \b true if a node exists. * @return \b false if a node doesn't exist or ID isn't a UTF-8 string. * @return \b false if a node exists, but not renderable. */ bool resvg_get_node_transform(const resvg_render_tree *tree, const char *id, resvg_transform *ts); /** * @brief Returns node's bounding box by ID. * * @param tree Render tree. * @param id Node's ID. * @param bbox Node's bounding box. * @return \b false if a node with such an ID does not exist * @return \b false if ID isn't a UTF-8 string. * @return \b false if ID is an empty string */ bool resvg_get_node_bbox(const resvg_render_tree *tree, const char *id, resvg_rect *bbox); /** * @brief Destroys the #resvg_render_tree. * * @param tree Render tree. */ void resvg_tree_destroy(resvg_render_tree *tree); #ifdef RESVG_CAIRO_BACKEND /** * @brief Renders the #resvg_render_tree to file. * * @param tree Render tree. * @param opt Rendering options. * @param file_path File path. * @return #resvg_error */ int resvg_cairo_render_to_image(const resvg_render_tree *tree, const resvg_options *opt, const char *file_path); /** * @brief Renders the #resvg_render_tree to canvas. * * @param tree Render tree. * @param opt Rendering options. * @param size Canvas size. * @param cr Canvas. */ void resvg_cairo_render_to_canvas(const resvg_render_tree *tree, const resvg_options *opt, resvg_size size, cairo_t *cr); /** * @brief Renders a Node by ID to canvas. * * Does nothing on error. * * @param tree Render tree. * @param opt Rendering options. * @param size Canvas size. * @param id Node's ID. * @param cr Canvas. */ void resvg_cairo_render_to_canvas_by_id(const resvg_render_tree *tree, const resvg_options *opt, resvg_size size, const char *id, cairo_t *cr); #endif /* RESVG_CAIRO_BACKEND */ #ifdef RESVG_QT_BACKEND /** * @brief Renders the #resvg_render_tree to file. * * @param tree Render tree. * @param opt Rendering options. * @param file_path File path. * @return #resvg_error */ int resvg_qt_render_to_image(const resvg_render_tree *tree, const resvg_options *opt, const char *file_path); /** * @brief Renders the #resvg_render_tree to canvas. * * @param tree Render tree. * @param opt Rendering options. * @param size Canvas size. * @param painter Canvas. */ void resvg_qt_render_to_canvas(const resvg_render_tree *tree, const resvg_options *opt, resvg_size size, void *painter); /** * @brief Renders a Node by ID to canvas. * * Does nothing on error. * * @param tree Render tree. * @param opt Rendering options. * @param size Canvas size. * @param id Node's ID. * @param painter Canvas. */ void resvg_qt_render_to_canvas_by_id(const resvg_render_tree *tree, const resvg_options *opt, resvg_size size, const char *id, void *painter); #endif /* RESVG_QT_BACKEND */ #ifdef RESVG_RAQOTE_BACKEND /** * @brief Renders the #resvg_render_tree to file. * * @param tree Render tree. * @param opt Rendering options. * @param file_path File path. * @return #resvg_error */ int resvg_raqote_render_to_image(const resvg_render_tree *tree, const resvg_options *opt, const char *file_path); /** * Raqote backend doesn't have render_to_canvas and render_to_canvas_by_id * methods since it's a Rust library. */ #endif /* RESVG_RAQOTE_BACKEND */ #ifdef RESVG_SKIA_BACKEND /** * @brief Renders the #resvg_render_tree to file. * * @param tree Render tree. * @param opt Rendering options. * @param file_path File path. * @return #resvg_error */ int resvg_skia_render_to_image(const resvg_render_tree *tree, const resvg_options *opt, const char *file_path); /** * @brief Renders the #resvg_render_tree to canvas. * * @param tree Render tree. * @param opt Rendering options. * @param size Canvas size. * @param surface Skia Surface. */ void resvg_skia_render_to_canvas(const resvg_render_tree *tree, const resvg_options *opt, resvg_size size, void *surface); /** * @brief Renders a Node by ID to canvas. * * Does nothing on error. * * @param tree Render tree. * @param opt Rendering options. * @param size Canvas size. * @param id Node's ID. * @param surface Skia Surface. */ void resvg_skia_render_to_canvas_by_id(const resvg_render_tree *tree, const resvg_options *opt, resvg_size size, const char *id, void *surface); #endif /* RESVG_SKIA_BACKEND */ #ifdef __cplusplus } #endif #endif /* RESVG_H */ resvg-0.8.0/capi/qtests/000077500000000000000000000000001352576375700151305ustar00rootroot00000000000000resvg-0.8.0/capi/qtests/.gitignore000066400000000000000000000000411352576375700171130ustar00rootroot00000000000000tst_resvgqt* test_renderFile.png resvg-0.8.0/capi/qtests/images/000077500000000000000000000000001352576375700163755ustar00rootroot00000000000000resvg-0.8.0/capi/qtests/images/empty.svg000066400000000000000000000001121352576375700202460ustar00rootroot00000000000000 resvg-0.8.0/capi/qtests/images/invalid.svg000066400000000000000000000000101352576375700205330ustar00rootroot00000000000000 resvg-0.8.0/capi/qtests/images/test.svg000066400000000000000000000006521352576375700201000ustar00rootroot00000000000000 resvg-0.8.0/capi/qtests/images/text.svg000066400000000000000000000001751352576375700201050ustar00rootroot00000000000000 Text resvg-0.8.0/capi/qtests/images/vb.svg000066400000000000000000000002501352576375700175220ustar00rootroot00000000000000 resvg-0.8.0/capi/qtests/results/000077500000000000000000000000001352576375700166315ustar00rootroot00000000000000resvg-0.8.0/capi/qtests/results/test_renderFile.png000066400000000000000000000040631352576375700224600ustar00rootroot00000000000000‰PNG  IHDRÈÈ­X®ž pHYsaa¨?§iåIDATxœíÝMŒ^UÇñ¯ÒØnšvÓh‚.´ndi…DêF^Ê SX”Œ‰+4V*`~±5´øÝù‚†j"Än´RÙXLyÙ`b TM0SäeÑ Stñ̘±Ôigæž{ν÷ûIžÌfæþó<Ïoî=ÏÜsH’$I’$I’$I’$I’$I’$I’$I’tnï©Ý€ÎÞÎs ÓÀ«À+ SÀIà……¯/þ]§Ùñ3 ­Yù›ýufAY ͳÀã„Ó]·6EdµÂ`;ᱎëvq4˜žNîí Þd­«ÝÀ „mÀMÀN`°ž6ÿȬcÖß0 ka@Î'lnöWTîF=3 çÖ70 Åõø\»™Ò ˆVëàyÂ=d¼sY¢µØ`vÍ×j7S‚Q®þ@øLíFºf@Ô•ÍÀ¯ ÷é”Ë€¨kw>X»‘.•p³S®íµY+¢R6¿!\[»‘µhq±ÏI²ø1pÑ ®Óß9I§ëÁ§Ö_ëyI²8 Ã!­Ô >mXøëuøbí^4 ƒ ÈB8¾|¾v/šŽ!bÂp¨gƒÈœc_í>4=Í$É­À7j÷¡ij> Àj7 r’\W»‡å ! ~”;nG“4û÷!Dãv1p,I“×nµ`ð³$ÍýÛÁ€¨W_­ÝÄÙ ˆZ²?IS‹® ˆZó“$Í,ß5 jÍfšµèjàKµ›¢vÝ“¤ú}· ˆZµøNí& ˆZ¶+É®š µî»IªÝ0»ÞúâÙ]Öï¾€ïë¶Ç®÷·¿µÖkz{#Èl—ͪyµN±‚[húÈl۳ݽ+­B#È]•Æ•V¬ß7êl7Ù[{SZƒ¾ÿ’ïc@·’ú HØÂlri0ú<‚ìÖ÷8ž´fý$¬nïe,©C}A®6ö4–Ô™¾â'W¤ò ›™A¤Áéã²?ÚÕ@õO¯4Xe¶WC*¨ôä³…ëKE•ÈÎÂõ¥¢Ê$lv«/õ äd;^Z¢kz=0´¿Ï·ý­¹^ýHò•꼋 —4ÅæºDcpe’KK6 ƒuÌöéœÑX\^¢¨ÑXl+QÔ€h,¶–(j@4[»þ ˆÆc#³Ý©:e@4&ÏC ˆÆ¤óyˆјi[º.h@4&›º.h@4&DZ†‘–Ñù½DcÒù V¢11 Ò2:H÷kÒÃÛt¼©íómM)q9S ¦TE‰€Ì¨)Ua@¤e”Èé5¥*JäÕ5¥*Jä•5¥* ˆ´Œ9U ¦TE‰€œ,PSªÂ€HË( Ô”ª(—× Ô•z×}@f³yš¥Q(u¹»Ñ(” ˆóB©€<[¨®Ô«Ry˜/T[êM™€„ÓÀ3EjK=*¹&ýDÁÚR/š^ “Ùç{i=û[¹k’ü¶ƒ:ïâ]M4tsÀS¥Š ÝIÞ,UÜ€hèŠÎu ˆ†îç%‹ ÙÓIþTr¢!;Rz¢¡z x¸ô DCõH’â71 ªâ§W`@4L¯ÇûÈ€hˆHÒË=  ˆ†føf_ƒ Í“ü£¯Á ˆ†d¸¿Ï ˆ†äÁ$ës@¢¡x8Ô÷ DCñP’¿ô=¨ÑœRc`¢!¸?ÉŸk \oMz¸x¸ì<ß7­}¾í¯)õŽ á àŽjãK î)Vø%ð«ª=HËharPlѽ´õ^¾V» é\êdæ³ûùJMi# a¸øgíV¤¥Ú@x ØS» i©v¥Âõ6ÒÿÓV@f݄-d6¹(~Ç é|Ú ,ÎGvÿªÝЦ­Í€„'›j·¡¢šß¦¯Ý€¤Ÿ[»¨šÛj7p>mDcv'á§µ›8¢¾]»‰ a@Ô·ïûk7q¡ ˆútøBç‹® ZW»MÆC9­ZÊ€¨´yà¶!LÈÏ¥éõÀç“ä:à(pñ ~¦Å}¾—Ö›T­ô$ÉqàÓxYŠ t@’< |/pTƒ@’—€O÷UnE#3Š€$™OòeàZ\™¨ŽŒ& ‹’<Êì”ëwµ{Ñð. Iþìd¶øÊ[ iÕFøï)×Aàcxs:­Òh²(É‹Iv7­ÜŽfôY”äðñÚ} Øà`í&ú6©KM’¼Q»‡zxH­-jšT@´"óÀƒÀ¡;;µÂ€èlsÀ€¯÷½af‹ ˆ½<|+É©Úʹ€LÛ[À#Ààx’¹Êý4Ç€LÓÓÌBñp¯„^†™†7ß'€£INVîg0 ÈÚ`vYË•´ù|^<•ÄKnVaR«ÃJJò~à“ÀåÀ6`ëÂcã ëøš4Ä£ …7ûfþ70[€MK—ëI.ªÓ­$I’$I’$I’$I’$I’$I’$I’$IZ‰ÿ|Ô~]Û¦gIEND®B`‚resvg-0.8.0/capi/qtests/results/test_renderFileWithText.png000066400000000000000000000021271352576375700241600ustar00rootroot00000000000000‰PNG  IHDRÈÈQf pHYsaa¨?§i IDATxÚìÁ   ÿŸ» ætìÙ¬¤Ë†ÑÕ»ï=¶mÛ¶mÛ¶mÛ¶mÛ¶mÛ>ý¤SùÓš™í k=aUšïxŒÓÆ–õ‘ E7ú]ɦ5UüQíÏÚ“Ô¿¢¦²>‘Í-„oí` C)†©+H:Œb!¦óáô‰lqáK“Hn0 .¯žýO½%Eê|N*ÞÁÿÁÆîÕ¶ö©Šq È‚þ-ûÓ4-þÛjqJÞ©íÔ[]¤žr›ýcm¨dt;ùG=mvƒim>?ˆ¦~µ…‘ί4‚zsùQjT CHc¥6Ó†.÷½hÑ¿¾´¤VÆv™Š¨éÓ¨õyÝó¬NáMÑÔ™Æ÷x:}ÏŠ:-ÓæNp¥³ý×DQ{@/‹:Ê pÛ¶Tºœ§KeÍ’9çà9ìÊ àà»fIIEND®B`‚resvg-0.8.0/capi/qtests/tests.pro000066400000000000000000000010031352576375700170060ustar00rootroot00000000000000QT += testlib TARGET = tst_resvgqt CONFIG += console CONFIG -= app_bundle TEMPLATE = app CONFIG += c++11 QMAKE_CXXFLAGS += -Wextra -Wpedantic QMAKE_CXXFLAGS += -fsanitize=address QMAKE_LFLAGS += -fsanitize=address SOURCES += tst_resvgqt.cpp DEFINES += SRCDIR=\\\"$$PWD\\\" CONFIG(release, debug|release): LIBS += -L$$PWD/../../target/release/ -lresvg else:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../target/debug/ -lresvg INCLUDEPATH += $$PWD/../include DEPENDPATH += $$PWD/../include resvg-0.8.0/capi/qtests/tst_resvgqt.cpp000066400000000000000000000066451352576375700202340ustar00rootroot00000000000000#include #include #include #include class ResvgQtTests : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void test_parseFile(); void test_parseInvalidFile(); void test_emptyFile(); void test_renderFile(); void test_renderFileWithText(); void test_imageSize(); void test_imageViewBox(); void test_imageBoundingBox(); void test_elementExists(); void test_transformForElement(); }; static QString localPath(const QString &fileName) { return QString("%1/%2").arg(SRCDIR).arg(fileName); } void ResvgQtTests::initTestCase() { ResvgRenderer::initLog(); } void ResvgQtTests::test_parseFile() { ResvgRenderer render(localPath("images/test.svg")); QVERIFY(render.isValid()); QVERIFY(!render.isEmpty()); QCOMPARE(render.defaultSize(), QSize(200, 200)); } void ResvgQtTests::test_parseInvalidFile() { ResvgRenderer render(localPath("images/invalid.svg")); QVERIFY(!render.isValid()); QVERIFY(render.isEmpty()); } void ResvgQtTests::test_emptyFile() { ResvgRenderer render(localPath("images/empty.svg")); QVERIFY(render.isValid()); QVERIFY(render.isEmpty()); } void ResvgQtTests::test_renderFile() { #ifdef LOCAL_BUILD ResvgRenderer render(localPath("images/test.svg")); QVERIFY(!render.isEmpty()); QCOMPARE(render.defaultSize(), QSize(200, 200)); QImage img(render.defaultSize(), QImage::Format_ARGB32); img.fill(Qt::transparent); QPainter p(&img); render.render(&p); p.end(); img.save("test.png"); QCOMPARE(img, QImage(localPath("results/test_renderFile.png"))); #endif } void ResvgQtTests::test_renderFileWithText() { #ifdef LOCAL_BUILD ResvgRenderer render(localPath("images/text.svg")); QVERIFY(!render.isEmpty()); QCOMPARE(render.defaultSize(), QSize(200, 200)); QImage img(render.defaultSize(), QImage::Format_ARGB32); img.fill(Qt::transparent); QPainter p(&img); render.render(&p); p.end(); QCOMPARE(img, QImage(localPath("results/test_renderFileWithText.png"))); #endif } void ResvgQtTests::test_imageSize() { ResvgRenderer render(localPath("images/vb.svg")); QVERIFY(!render.isEmpty()); QCOMPARE(render.defaultSize(), QSize(200, 400)); } void ResvgQtTests::test_imageViewBox() { ResvgRenderer render(localPath("images/vb.svg")); QVERIFY(!render.isEmpty()); QCOMPARE(render.viewBox(), QRect(50, 100, 200, 400)); } void ResvgQtTests::test_imageBoundingBox() { ResvgRenderer render(localPath("images/test.svg")); QVERIFY(!render.isEmpty()); QCOMPARE(render.boundingBox().toRect(), QRect(20, 20, 160, 160)); } void ResvgQtTests::test_elementExists() { ResvgRenderer render(localPath("images/test.svg")); QVERIFY(!render.isEmpty()); // Existing element. QVERIFY(render.elementExists("circle1")); // Non-existing element. QVERIFY(!render.elementExists("invalid")); // Non-renderable elements. QVERIFY(!render.elementExists("rect1")); QVERIFY(!render.elementExists("rect2")); QVERIFY(!render.elementExists("patt1")); } void ResvgQtTests::test_transformForElement() { ResvgRenderer render(localPath("images/test.svg")); QVERIFY(!render.isEmpty()); QCOMPARE(render.transformForElement("circle1"), QTransform(2, 0, 0, 2, 0, 0)); QCOMPARE(render.transformForElement("invalid"), QTransform()); } QTEST_APPLESS_MAIN(ResvgQtTests) #include "tst_resvgqt.moc" resvg-0.8.0/capi/src/000077500000000000000000000000001352576375700143745ustar00rootroot00000000000000resvg-0.8.0/capi/src/lib.rs000066400000000000000000000516731352576375700155240ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. #![allow(non_camel_case_types)] use std::ffi::CStr; use std::fmt; use std::os::raw::c_char; use std::path; use std::ptr; use std::slice; use log::warn; #[cfg(feature = "cairo-backend")] use resvg::cairo; #[cfg(feature = "qt-backend")] use resvg::qt; #[cfg(feature = "skia-backend")] use resvg::skia; use resvg::prelude::*; const DEFAULT_FONT_FAMILY: &str = "Times New Roman"; #[repr(C)] pub struct resvg_options { pub path: *const c_char, pub dpi: f64, pub font_family: *const c_char, pub font_size: f64, pub languages: *const c_char, pub shape_rendering: resvg_shape_rendering, pub text_rendering: resvg_text_rendering, pub image_rendering: resvg_image_rendering, pub fit_to: resvg_fit_to, pub draw_background: bool, pub background: resvg_color, pub keep_named_groups: bool, } enum ErrorId { Ok = 0, NotAnUtf8Str, FileOpenFailed, FileWriteFailed, InvalidFileSuffix, MalformedGZip, InvalidSize, ParsingFailed, NoCanvas, } #[repr(C)] pub struct resvg_color { pub r: u8, pub g: u8, pub b: u8, } #[repr(C)] pub enum resvg_shape_rendering { RESVG_SHAPE_RENDERING_OPTIMIZE_SPEED, RESVG_SHAPE_RENDERING_CRISP_EDGES, RESVG_SHAPE_RENDERING_GEOMETRIC_PRECISION, } #[repr(C)] pub enum resvg_text_rendering { RESVG_TEXT_RENDERING_OPTIMIZE_SPEED, RESVG_TEXT_RENDERING_OPTIMIZE_LEGIBILITY, RESVG_TEXT_RENDERING_GEOMETRIC_PRECISION, } #[repr(C)] pub enum resvg_image_rendering { RESVG_IMAGE_RENDERING_OPTIMIZE_QUALITY, RESVG_IMAGE_RENDERING_OPTIMIZE_SPEED, } #[repr(C)] pub enum resvg_fit_to_type { RESVG_FIT_TO_ORIGINAL, RESVG_FIT_TO_WIDTH, RESVG_FIT_TO_HEIGHT, RESVG_FIT_TO_ZOOM, } #[repr(C)] pub struct resvg_fit_to { kind: resvg_fit_to_type, value: f32, } #[repr(C)] pub struct resvg_rect { pub x: f64, pub y: f64, pub width: f64, pub height: f64, } #[repr(C)] pub struct resvg_size { pub width: u32, pub height: u32, } #[repr(C)] pub struct resvg_transform { pub a: f64, pub b: f64, pub c: f64, pub d: f64, pub e: f64, pub f: f64, } #[repr(C)] pub struct resvg_render_tree(usvg::Tree); #[no_mangle] pub extern "C" fn resvg_init_log() { fern::Dispatch::new() .format(log_format) .level(log::LevelFilter::Warn) .chain(std::io::stderr()) .apply() .unwrap(); } 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 )) } #[no_mangle] pub extern "C" fn resvg_init_options( opt: *mut resvg_options, ) { unsafe { (*opt).path = ptr::null(); (*opt).dpi = 96.0; (*opt).font_family = ptr::null(); (*opt).font_size = 12.0; (*opt).languages = ptr::null(); (*opt).shape_rendering = resvg_shape_rendering::RESVG_SHAPE_RENDERING_GEOMETRIC_PRECISION; (*opt).text_rendering = resvg_text_rendering::RESVG_TEXT_RENDERING_OPTIMIZE_LEGIBILITY; (*opt).image_rendering = resvg_image_rendering::RESVG_IMAGE_RENDERING_OPTIMIZE_QUALITY; (*opt).fit_to = resvg_fit_to { kind: resvg_fit_to_type::RESVG_FIT_TO_ORIGINAL, value: 0.0, }; (*opt).draw_background = false; (*opt).background.r = 0; (*opt).background.g = 0; (*opt).background.b = 0; (*opt).keep_named_groups = false; } } #[no_mangle] pub extern "C" fn resvg_parse_tree_from_file( file_path: *const c_char, opt: *const resvg_options, raw_tree: *mut *mut resvg_render_tree, ) -> i32 { let file_path = match cstr_to_str(file_path) { Some(v) => v, None => return ErrorId::NotAnUtf8Str as i32, }; let opt = to_native_opt(unsafe { assert!(!opt.is_null()); &*opt }); let tree = match usvg::Tree::from_file(file_path, &opt.usvg) { Ok(tree) => tree, Err(e) => return convert_error(e) as i32, }; let tree_box = Box::new(resvg_render_tree(tree)); unsafe { *raw_tree = Box::into_raw(tree_box); } ErrorId::Ok as i32 } #[no_mangle] pub extern "C" fn resvg_parse_tree_from_data( data: *const c_char, len: usize, opt: *const resvg_options, raw_tree: *mut *mut resvg_render_tree, ) -> i32 { let data = unsafe { slice::from_raw_parts(data as *const u8, len) }; let opt = to_native_opt(unsafe { assert!(!opt.is_null()); &*opt }); let tree = match usvg::Tree::from_data(data, &opt.usvg) { Ok(tree) => tree, Err(e) => return convert_error(e) as i32, }; let tree_box = Box::new(resvg_render_tree(tree)); unsafe { *raw_tree = Box::into_raw(tree_box); } ErrorId::Ok as i32 } #[no_mangle] pub extern "C" fn resvg_tree_destroy( tree: *mut resvg_render_tree, ) { unsafe { assert!(!tree.is_null()); Box::from_raw(tree) }; } #[cfg(feature = "qt-backend")] #[no_mangle] pub extern "C" fn resvg_qt_render_to_image( tree: *const resvg_render_tree, opt: *const resvg_options, file_path: *const c_char, ) -> i32 { let backend = Box::new(resvg::backend_qt::Backend); render_to_image(tree, opt, file_path, backend) } #[cfg(feature = "cairo-backend")] #[no_mangle] pub extern "C" fn resvg_cairo_render_to_image( tree: *const resvg_render_tree, opt: *const resvg_options, file_path: *const c_char, ) -> i32 { let backend = Box::new(resvg::backend_cairo::Backend); render_to_image(tree, opt, file_path, backend) } #[cfg(feature = "raqote-backend")] #[no_mangle] pub extern "C" fn resvg_raqote_render_to_image( tree: *const resvg_render_tree, opt: *const resvg_options, file_path: *const c_char, ) -> i32 { let backend = Box::new(resvg::backend_raqote::Backend); render_to_image(tree, opt, file_path, backend) } #[cfg(feature = "skia-backend")] #[no_mangle] pub extern "C" fn resvg_skia_render_to_image( tree: *const resvg_render_tree, opt: *const resvg_options, file_path: *const c_char, ) -> i32 { let backend = Box::new(resvg::backend_skia::Backend); render_to_image(tree, opt, file_path, backend) } fn render_to_image( tree: *const resvg_render_tree, opt: *const resvg_options, file_path: *const c_char, backend: Box, ) -> i32 { let tree = unsafe { assert!(!tree.is_null()); &*tree }; let file_path = match cstr_to_str(file_path) { Some(v) => v, None => return ErrorId::NotAnUtf8Str as i32, }; let opt = to_native_opt(unsafe { assert!(!opt.is_null()); &*opt }); let img = backend.render_to_image(&tree.0, &opt); let mut img = match img { Some(img) => img, None => { return ErrorId::NoCanvas as i32; } }; match img.save_png(path::Path::new(file_path)) { true => ErrorId::Ok as i32, false => ErrorId::FileWriteFailed as i32, } } #[cfg(feature = "qt-backend")] #[no_mangle] pub extern "C" fn resvg_qt_render_to_canvas( tree: *const resvg_render_tree, opt: *const resvg_options, size: resvg_size, painter: *mut qt::qtc_qpainter, ) { let tree = unsafe { assert!(!tree.is_null()); &*tree }; let mut painter = unsafe { qt::Painter::from_raw(painter) }; let size = resvg::ScreenSize::new(size.width, size.height).unwrap(); let opt = to_native_opt(unsafe { assert!(!opt.is_null()); &*opt }); resvg::backend_qt::render_to_canvas(&tree.0, &opt, size, &mut painter); } #[cfg(feature = "cairo-backend")] #[no_mangle] pub extern "C" fn resvg_cairo_render_to_canvas( tree: *const resvg_render_tree, opt: *const resvg_options, size: resvg_size, cr: *mut cairo_sys::cairo_t, ) { let tree = unsafe { assert!(!tree.is_null()); &*tree }; let cr = unsafe { cairo::Context::from_raw_none(cr) }; let size = resvg::ScreenSize::new(size.width, size.height).unwrap(); let opt = to_native_opt(unsafe { assert!(!opt.is_null()); &*opt }); resvg::backend_cairo::render_to_canvas(&tree.0, &opt, size, &cr); } #[cfg(feature = "skia-backend")] #[no_mangle] pub extern "C" fn resvg_skia_render_to_canvas( tree: *const resvg_render_tree, opt: *const resvg_options, img_size: resvg_size, surface: *mut skia::skiac_surface, ) { let tree = unsafe { assert!(!tree.is_null()); &*tree }; let mut surface = unsafe { skia::Surface::from_ptr(surface).unwrap() }; let img_size = resvg::ScreenSize::new(img_size.width, img_size.height).unwrap(); let opt = to_native_opt(unsafe { assert!(!opt.is_null()); &*opt }); resvg::backend_skia::render_to_canvas(&tree.0, &opt, img_size, &mut surface); } #[cfg(feature = "qt-backend")] #[no_mangle] pub extern "C" fn resvg_qt_render_to_canvas_by_id( tree: *const resvg_render_tree, opt: *const resvg_options, size: resvg_size, id: *const c_char, painter: *mut qt::qtc_qpainter, ) { let tree = unsafe { assert!(!tree.is_null()); &*tree }; let mut painter = unsafe { qt::Painter::from_raw(painter) }; let size = resvg::ScreenSize::new(size.width, size.height).unwrap(); let opt = to_native_opt(unsafe { assert!(!opt.is_null()); &*opt }); let id = match cstr_to_str(id) { Some(v) => v, None => return, }; if id.is_empty() { warn!("Node with an empty ID cannot be painted."); return; } if let Some(node) = tree.0.node_by_id(id) { if let Some(bbox) = node.calculate_bbox() { let vbox = usvg::ViewBox { rect: bbox, aspect: usvg::AspectRatio::default(), }; resvg::backend_qt::render_node_to_canvas(&node, &opt, vbox, size, &mut painter); } else { warn!("A node with '{}' ID doesn't have a valid bounding box.", id); } } else { warn!("A node with '{}' ID wasn't found.", id); } } #[cfg(feature = "cairo-backend")] #[no_mangle] pub extern "C" fn resvg_cairo_render_to_canvas_by_id( tree: *const resvg_render_tree, opt: *const resvg_options, size: resvg_size, id: *const c_char, cr: *mut cairo_sys::cairo_t, ) { let tree = unsafe { assert!(!tree.is_null()); &*tree }; let id = match cstr_to_str(id) { Some(v) => v, None => return, }; if id.is_empty() { warn!("Node with an empty ID cannot be painted."); return; } let cr = unsafe { cairo::Context::from_raw_none(cr) }; let size = resvg::ScreenSize::new(size.width, size.height).unwrap(); let opt = to_native_opt(unsafe { assert!(!opt.is_null()); &*opt }); if let Some(node) = tree.0.node_by_id(id) { if let Some(bbox) = node.calculate_bbox() { let vbox = usvg::ViewBox { rect: bbox, aspect: usvg::AspectRatio::default(), }; resvg::backend_cairo::render_node_to_canvas(&node, &opt, vbox, size, &cr); } else { warn!("A node with '{}' ID doesn't have a valid bounding box.", id); } } else { warn!("A node with '{}' ID wasn't found.", id); } } #[cfg(feature = "skia-backend")] #[no_mangle] pub extern "C" fn resvg_skia_render_to_canvas_by_id( tree: *const resvg_render_tree, opt: *const resvg_options, size: resvg_size, id: *const c_char, surface: *mut skia::skiac_surface, ) { let tree = unsafe { assert!(!tree.is_null()); &*tree }; let id = match cstr_to_str(id) { Some(v) => v, None => return, }; if id.is_empty() { warn!("Node with an empty ID cannot be painted."); return; } let mut surface = unsafe { skia::Surface::from_ptr(surface).unwrap() }; let size = resvg::ScreenSize::new(size.width, size.height).unwrap(); let opt = to_native_opt(unsafe { assert!(!opt.is_null()); &*opt }); if let Some(node) = tree.0.node_by_id(id) { if let Some(bbox) = node.calculate_bbox() { let vbox = usvg::ViewBox { rect: bbox, aspect: usvg::AspectRatio::default(), }; resvg::backend_skia::render_node_to_canvas(&node, &opt, vbox, size, &mut surface); } else { warn!("A node with '{}' ID doesn't have a valid bounding box.", id); } } else { warn!("A node with '{}' ID wasn't found.", id); } } #[no_mangle] pub extern "C" fn resvg_is_image_empty( tree: *const resvg_render_tree, ) -> bool { let tree = unsafe { assert!(!tree.is_null()); &*tree }; // The root/svg node should have at least two children. // The first child is `defs` and it always present. tree.0.root().children().count() > 1 } #[no_mangle] pub extern "C" fn resvg_get_image_size( tree: *const resvg_render_tree, ) -> resvg_size { let tree = unsafe { assert!(!tree.is_null()); &*tree }; let size = tree.0.svg_node().size; resvg_size { width: size.width() as u32, height: size.height() as u32, } } #[no_mangle] pub extern "C" fn resvg_get_image_viewbox( tree: *const resvg_render_tree, ) -> resvg_rect { let tree = unsafe { assert!(!tree.is_null()); &*tree }; let r = tree.0.svg_node().view_box.rect; resvg_rect { x: r.x(), y: r.y(), width: r.width(), height: r.height(), } } #[no_mangle] pub extern "C" fn resvg_get_image_bbox( tree: *const resvg_render_tree, bbox: *mut resvg_rect, ) -> bool { let tree = unsafe { assert!(!tree.is_null()); &*tree }; if let Some(r) = tree.0.root().calculate_bbox() { unsafe { (*bbox).x = r.x(); (*bbox).y = r.y(); (*bbox).width = r.width(); (*bbox).height = r.height(); } true } else { false } } #[no_mangle] pub extern "C" fn resvg_get_node_bbox( tree: *const resvg_render_tree, id: *const c_char, bbox: *mut resvg_rect, ) -> bool { let id = match cstr_to_str(id) { Some(v) => v, None => { warn!("Provided ID is no an UTF-8 string."); return false; } }; if id.is_empty() { warn!("Node ID must not be empty."); return false; } let tree = unsafe { assert!(!tree.is_null()); &*tree }; match tree.0.node_by_id(id) { Some(node) => { if let Some(r) = node.calculate_bbox() { unsafe { (*bbox).x = r.x(); (*bbox).y = r.y(); (*bbox).width = r.width(); (*bbox).height = r.height(); } true } else { false } } None => { warn!("No node with '{}' ID is in the tree.", id); false } } } #[no_mangle] pub extern "C" fn resvg_node_exists( tree: *const resvg_render_tree, id: *const c_char, ) -> bool { let id = match cstr_to_str(id) { Some(v) => v, None => { warn!("Provided ID is no an UTF-8 string."); return false; } }; let tree = unsafe { assert!(!tree.is_null()); &*tree }; tree.0.node_by_id(id).is_some() } #[no_mangle] pub extern "C" fn resvg_get_node_transform( tree: *const resvg_render_tree, id: *const c_char, ts: *mut resvg_transform, ) -> bool { let id = match cstr_to_str(id) { Some(v) => v, None => { warn!("Provided ID is no an UTF-8 string."); return false; } }; let tree = unsafe { assert!(!tree.is_null()); &*tree }; if let Some(node) = tree.0.node_by_id(id) { let mut abs_ts = node.abs_transform(); abs_ts.append(&node.transform()); unsafe { (*ts).a = abs_ts.a; (*ts).b = abs_ts.b; (*ts).c = abs_ts.c; (*ts).d = abs_ts.d; (*ts).e = abs_ts.e; (*ts).f = abs_ts.f; } return true; } false } fn cstr_to_str( text: *const c_char, ) -> Option<&'static str> { let text = unsafe { assert!(!text.is_null()); CStr::from_ptr(text) }; text.to_str().ok() } fn to_native_opt( opt: &resvg_options, ) -> resvg::Options { let mut path: Option = None; if !opt.path.is_null() { if let Some(p) = cstr_to_str(opt.path) { if !p.is_empty() { path = Some(p.into()); } } }; let fit_to = match opt.fit_to.kind { resvg_fit_to_type::RESVG_FIT_TO_ORIGINAL => { resvg::FitTo::Original } resvg_fit_to_type::RESVG_FIT_TO_WIDTH => { assert!(opt.fit_to.value > 0.0); resvg::FitTo::Width(opt.fit_to.value as u32) } resvg_fit_to_type::RESVG_FIT_TO_HEIGHT => { assert!(opt.fit_to.value > 0.0); resvg::FitTo::Height(opt.fit_to.value as u32) } resvg_fit_to_type::RESVG_FIT_TO_ZOOM => { assert!(opt.fit_to.value > 0.0); resvg::FitTo::Zoom(opt.fit_to.value) } }; let background = if opt.draw_background { Some(usvg::Color::new( opt.background.r, opt.background.g, opt.background.b, )) } else { None }; let shape_rendering = match opt.shape_rendering { resvg_shape_rendering::RESVG_SHAPE_RENDERING_OPTIMIZE_SPEED => { usvg::ShapeRendering::OptimizeSpeed } resvg_shape_rendering::RESVG_SHAPE_RENDERING_CRISP_EDGES => { usvg::ShapeRendering::CrispEdges } resvg_shape_rendering::RESVG_SHAPE_RENDERING_GEOMETRIC_PRECISION => { usvg::ShapeRendering::GeometricPrecision } }; let text_rendering = match opt.text_rendering { resvg_text_rendering::RESVG_TEXT_RENDERING_OPTIMIZE_SPEED => { usvg::TextRendering::OptimizeSpeed } resvg_text_rendering::RESVG_TEXT_RENDERING_OPTIMIZE_LEGIBILITY => { usvg::TextRendering::OptimizeLegibility } resvg_text_rendering::RESVG_TEXT_RENDERING_GEOMETRIC_PRECISION => { usvg::TextRendering::GeometricPrecision } }; let image_rendering = match opt.image_rendering { resvg_image_rendering::RESVG_IMAGE_RENDERING_OPTIMIZE_QUALITY => { usvg::ImageRendering::OptimizeQuality } resvg_image_rendering::RESVG_IMAGE_RENDERING_OPTIMIZE_SPEED => { usvg::ImageRendering::OptimizeSpeed } }; let ff = DEFAULT_FONT_FAMILY; let font_family = match cstr_to_str(opt.font_family) { Some(v) => { if v.is_empty() { warn!("Provided 'font_family' option is empty. Fallback to '{}'.", ff); ff } else { v } } None => { warn!("Provided 'font_family' option is no an UTF-8 string. Fallback to '{}'.", ff); ff } }; let languages_str = match cstr_to_str(opt.languages) { Some(v) => v, None => { warn!("Provided 'languages' option is no an UTF-8 string. Fallback to 'en'."); "en" } }; let mut languages = Vec::new(); for lang in languages_str.split(',') { languages.push(lang.trim().to_string()); } if languages.is_empty() { warn!("Provided 'languages' option is empty. Fallback to 'en'."); languages = vec!["en".to_string()] } resvg::Options { usvg: usvg::Options { path, dpi: opt.dpi, font_family: font_family.to_string(), font_size: opt.font_size, languages, shape_rendering, text_rendering, image_rendering, keep_named_groups: opt.keep_named_groups, }, fit_to, background, } } fn convert_error( e: usvg::Error, ) -> ErrorId { match e { usvg::Error::InvalidFileSuffix => ErrorId::InvalidFileSuffix, usvg::Error::FileOpenFailed => ErrorId::FileOpenFailed, usvg::Error::NotAnUtf8Str => ErrorId::NotAnUtf8Str, usvg::Error::MalformedGZip => ErrorId::MalformedGZip, usvg::Error::InvalidSize => ErrorId::InvalidSize, usvg::Error::ParsingFailed(_) => ErrorId::ParsingFailed, } } resvg-0.8.0/docs/000077500000000000000000000000001352576375700136215ustar00rootroot00000000000000resvg-0.8.0/docs/backend_requirements.md000066400000000000000000000015041352576375700203350ustar00rootroot00000000000000# Backend requirements List of features required from the 2D graphics library to implement a backend for `resvg`. - Composition modes: - Clear - Darken - DestinationIn - DestinationOut - Lighten - Multiply - Screen - SourceAtop - SourceIn - SourceOut - SourceOver - Xor - Filling: - With color - With linear or radial gradient - With pattern (texture/image) - Fill rules: - NonZero/Winding - EvenOdd/OddEven - Opacity - Stroking: - With color - With linear or radial gradient - With pattern (texture/image) - Dasharray + Dashoffset - Miterlimit - Opacity - Width - LineCap: *butt*, *round* and *square* - LineJoin: *miter*, *round* and *bevel* - Bézier paths: - Anti-aliasing - Required segments support: - MoveTo - LineTo - CurveTo (cubic) - ClosePath resvg-0.8.0/docs/unsupported.md000066400000000000000000000025741352576375700165430ustar00rootroot00000000000000### Elements - Filter based - `feConvolveMatrix` - `feDiffuseLighting` - `feDisplacementMap` - `feDistantLight` - `feImage` with a reference to an element - `feMorphology` - `fePointLight` - `feSpecularLighting` - `feSpotLight` - Font based - `altGlyph` - `altGlyphDef` - `altGlyphItem` - `font-face-format` - `font-face-name` - `font-face-src` - `font-face-uri` - `font-face` - `font` - `glyph` - `glyphRef` - `hkern` - `missing-glyph` - `vkern` - `color-profile` - `use` with a reference to an external SVG file ### Attributes - `alignment-baseline` - `clip` (deprecated in the SVG 2) - `color-interpolation` - `color-profile` - `color-rendering` - `direction` - `dominant-baseline` - [`enable-background`](https://www.w3.org/TR/SVG11/filters.html#EnableBackgroundProperty) (deprecated in the SVG 2) - `font` - `font-size-adjust` - `font-stretch` - `font-variant` - `glyph-orientation-horizontal` (removed in the SVG 2) - `glyph-orientation-vertical` (deprecated in the SVG 2) - [`in`](https://www.w3.org/TR/SVG11/filters.html#FilterPrimitiveInAttribute) with `BackgroundImage`, `BackgroundAlpha`, `FillPaint`, `StrokePaint` - `kerning` (removed in the SVG 2) - `lengthAdjust` - `textLength` - `unicode-bidi` **Note:** this list does not include elements and attributes outside the [static SVG](http://www.w3.org/TR/SVG11/feature#SVG-static) subset. resvg-0.8.0/docs/usvg_spec.adoc000066400000000000000000000403551352576375700164560ustar00rootroot00000000000000= Micro SVG Document Structure :toc: == Intro SVG Micro represents a strip down SVG Full 1.1 subset. Here is the main differences between SVG Full and SVG Micro. - No XML DTD. - No CSS. - `use`, `marker` and nested `svg` will be resolved. - Simplified path notation. Only absolute MoveTo, LineTo, CurveTo and ClosePath segments are allowed. - No inheritable attributes. - No `xlink:href`, except the `image` element. - No recursive references. - Only valid elements and attributes. - No unused elements. - No redundant groups. - No units. - No `style` attribute. - Default attributes are implicit. You can use https://github.com/RazrFalcon/resvg/tree/master/usvg[usvg] to convert a random SVG into a SVG Micro almost losslessly. == Elements [[svg-element]] === The `svg` element The `svg` element is the root element of the document. It's defined only once and can't be nested, unlike by the SVG spec. *Children:* * <> * <> * <> * <> *Attributes:* * `width` = < >> + The width of the rectangular region into which the referenced document is placed. * `height` = < >> + The height of the rectangular region into which the referenced document is placed. * `viewBox` = < >> * `preserveAspectRatio` = < >>? [[defs-element]] === The `defs` element Always present. Always the first `svg` child. Can be empty. *Children:* * <> * <> * <> * <> * <> * <> *Attributes:* * none [[linearGradient-element]] === The `linearGradient` element Doesn't have a `xlink:href` attribute because all attributes and `stop` children will be resolved. *Children:* * At least two <> *Attributes:* * `id` = < >> + The element ID. Always set. Guarantee to be unique. * `x1` = < >> * `y1` = < >> * `x2` = < >> * `y2` = < >> * `gradientUnits` = `userSpaceOnUse`? * `spreadMethod` = `reflect | repeat`? * `gradientTransform` = < >>? [[radialGradient-element]] === The `radialGradient` element Doesn't have a `xlink:href` attribute because all attributes and `stop` children will be resolved. *Children:* * At least two <> *Attributes:* * `id` = < >> + The element ID. Always set. Guarantee to be unique. * `cx` = < >> * `cy` = < >> * `fx` = < >> + Guarantee to be the circle defined by `cx`, `cy` and `r`. * `fy` = < >> + Guarantee to be inside the circle defined by `cx`, `cy` and `r`. * `r` = < >> * `gradientUnits` = `userSpaceOnUse`? * `spreadMethod` = `reflect | repeat`? * `gradientTransform` = < >>? [[stop-element]] === The `stop` element Gradient's `stop` children will always have unique, ordered `offset` values in the 0..1 range. *Children:* * none *Attributes:* * `offset` = < >> * `stop-color` = < >> * `stop-opacity` = < >>? + Default: 1 [[pattern-element]] === The `pattern` element Doesn't have a `xlink:href` attribute because all attributes and children will be resolved. *Children:* * `g` * `path` * `image` *Attributes:* * `id` = < >> + The element ID. Always set. Guarantee to be unique. * `x` = < >> * `y` = < >> * `width` = < >> * `height` = < >> * `viewBox` = < >>? * `preserveAspectRatio` = < >>? * `patternUnits` = `userSpaceOnUse`? + Default: objectBoundingBox * `patternContentUnits` = `objectBoundingBox`? + Default: userSpaceOnUse * `patternTransform` = < >>? [[clipPath-element]] === The `clipPath` element *Children:* * `path` *Attributes:* * `id` = < >> + The element ID. Always set. Guarantee to be unique. * `clip-path` = < >>? + An optional reference to a supplemental `clipPath`. + Default: none * `clipPathUnits` = `objectBoundingBox`? + Default: userSpaceOnUse * `transform` = < >>? [[mask-element]] === The `mask` element *Children:* * `g` * `path` * `image` *Attributes:* * `id` = < >> + The element ID. Always set. Guarantee to be unique. * `mask` = < >>? + An optional reference to a supplemental `mask`. + Default: none * `x` = < >> * `y` = < >> * `width` = < >> * `height` = < >> * `maskUnits` = `userSpaceOnUse`? + Default: objectBoundingBox * `maskContentUnits` = `objectBoundingBox`? + Default: userSpaceOnUse [[filter-element]] === The `filter` element Doesn't have a `xlink:href` attribute because all attributes and children will be resolved. *Children:* * <> *Attributes:* * `id` = < >> + The element ID. Always set. Guarantee to be unique. * `x` = < >> * `y` = < >> * `width` = < >> * `height` = < >> * `filterUnits` = `userSpaceOnUse`? + Default: objectBoundingBox * `primitiveUnits` = `objectBoundingBox`? + Default: userSpaceOnUse [[g-element]] === The `g` element The group element indicates that a new canvas should be created. All group's children elements will be rendered on it and then merged into the parent canvas. Since it's pretty expensive, especially memory wise, _usvg_ will remove as many groups as possible. And all the remaining one will indicate that a new canvas must be created. A group can have no children when it has a `filter` attribute. A group will have at least one of the attributes present. *Children:* * <> * <> * <> *Attributes:* * `id` = < >>? + An optional, but never empty, element ID. * `opacity` = < >>? * `clip-path` = < >>? + Cannot be set to `none`. * `mask` = < >>? + Cannot be set to `none`. * `filter` = < >>? + Cannot be set to `none`. * `transform` = < >>? [[path-element]] === The `path` element *Children:* * none *Attributes:* * `id` = < >>? + An optional, but never empty, element ID. * `d` = < >> + * `fill` = `none` | < >> | < >>? + If not set, than all fill-* attributes will not be set too. + Default: black * `fill-opacity` = < >>? + Default: 1 * `fill-rule` = `evenodd`? + Default: nonzero * `stroke` = < >> | < >>? + If not set, than all stroke-* attributes will not be set too. + Default: none * `stroke-width` = < >>? + Default: 1 * `stroke-linecap` = `round | square`? + Default: butt * `stroke-linejoin` = `round | bevel`? + Default: miter * `stroke-miterlimit` = < >>? + Guarantee to be > 1. + Default: 4 * `stroke-dasharray` = ``? + Guarantee to have even amount of numbers. + Default: none * `stroke-dashoffset` = < >>? * `stroke-opacity` = < >>? + Default: 1 * `clip-rule` = `evenodd`? + Will be set only inside the <>, instead of `fill-rule`. * `clip-path` = < >>? + Available only inside the <>. * `shape-rendering` = `optimizeSpeed | crispEdges`? + Default: geometricPrecision * `visibility` = `hidden | collapse`? + Default: visible * `transform` = < >>? [[image-element]] === The `image` element *Children:* * none *Attributes:* * `id` = < >>? + An optional, but never empty, element ID. * `xlink:href` = < >> + The IRI contains a file path or base64 encoded image. * `x` = < >> * `y` = < >> * `width` = < >> * `height` = < >> * `preserveAspectRatio` = < >>? * `image-rendering` = `optimizeSpeed`? + Default: optimizeQuality * `visibility` = `hidden | collapse`? + Default: visible * `transform` = < >>? == Filter primitives === Filter primitive attributes The attributes below are the same for all filter primitives. * `color-interpolation-filters` = `sRGB`? + Default: linearRGB * `x` = < >>? * `y` = < >>? * `width` = < >>? * `height` = < >>? * `result` = < >> The `x`, `y`, `width` and `height` attributes can be omited. SVG has a pretty complex https://www.w3.org/TR/SVG11/filters.html#FilterPrimitiveSubRegion[rules of resolving them] and I don't fully understand them yet. Neither do others, because they are pretty poorly implemented. === Filter primitive `feBlend` *Attributes:* * `in` = < >> * `in2` = < >> * `mode` = `normal | multiply | screen | darken | lighten` * <> === Filter primitive `feColorMatrix` *Attributes:* * `in` = < >> * `type` = `matrix | saturate | hueRotate | luminanceToAlpha` * `values` = ``? + ** For `type=matrix`, contains 20 numbers. ** For `type=saturate`, contains a single number in a 0..1 range. ** For `type=hueRotate`, contains a single number. ** Not present for `type=luminanceToAlpha`. * <> === Filter primitive `feComponentTransfer` *Children:* * `feFuncR` * `feFuncG` * `feFuncB` * `feFuncA` The all four will always be present. *Attributes:* * `in` = < >> * <> *`feFunc(R|G|B|A)` attributes:* * `type` = `identity | table | discrete | linear | gamma` * `tableValues` = ``? + Present only when `type=table | discrete`. Can be empty. * `slope` = < >>? + Present only when `type=linear`. * `intercept` = < >>? + Present only when `type=linear`. * `amplitude` = < >>? + Present only when `type=gamma`. * `exponent` = < >>? + Present only when `type=gamma`. * `offset` = < >>? + Present only when `type=gamma`. === Filter primitive `feComposite` *Attributes:* * `in` = < >> * `in2` = < >> * `operator` = `over | in | out | atop | xor | arithmetic` * `k1` = < >>? + Present only when `operator=arithmetic`. * `k2` = < >>? + Present only when `operator=arithmetic`. * `k3` = < >>? + Present only when `operator=arithmetic`. * `k4` = < >>? + Present only when `operator=arithmetic`. * <> === Filter primitive `feFlood` *Attributes:* * `flood-color` = < >> * `flood-opacity` = < >> * <> === Filter primitive `feGaussianBlur` *Attributes:* * `in` = < >> * `stdDeviation` = < >> " " < >> * <> === Filter primitive `feImage` *Attributes:* * `preserveAspectRatio` = < >> * `image-rendering` = `optimizeSpeed`? + Default: optimizeQuality * `xlink:href` = < >>? + The IRI contains a file path or base64 encoded image. Link to an element (like in `use`) is not supported. + Unlike the `image` element, `feImage` can be without the `href` attribute. In this case the filter primitive is an empty canvas. * <> === Filter primitive `feMerge` *Children:* * `feMergeNode` *Attributes:* * <> *`feMergeNode` attributes:* * `in` = < >> === Filter primitive `feOffset` *Attributes:* * `in` = < >> * `dx` = < >> * `dy` = < >> * <> === Filter primitive `feTile` *Attributes:* * `in` = < >> * <> == Data types If an attribute has the `?` symbol after the type that's mean that that this attribute is optional. [[string-type]] ** - A Unicode string. [[number-type]] ** - A real number. + `number ::= [-]? [0-9]* "." [0-9]+` [[positive-number-type]] ** - A positive, non-zero real <>. + `positive-number ::= [0-9]* "." [0-9]+` [[opacity-type]] ** - A real <> in a 0..1 range. + `opacity ::= positive-number` [[offset-type]] ** - A real <> in a 0..1 range. + `offset ::= positive-number` [[compositing-coefficient-type]] ** - A real <> in a 0..1 range. + `compositing-coefficient ::= positive-number` [[color-type]] ** - A hex-encoded RGB color. ``` color ::= "#" hexdigit hexdigit hexdigit hexdigit hexdigit hexdigit hexdigit ::= [0-9a-f] ``` [[iri-type]] ** - An Internationalized Resource Identifier. Always a valid, local reference. + `IRI ::= string` [[func-iri-type]] ** - Functional notation for an <>. Always a valid, local reference. + `FuncIRI ::= url( )` [[filter-input-type]] ** - A filter source. A reference to a _result_ guarantee to be valid. ``` filter-input ::= SourceGraphic | SourceAlpha | BackgroundImage | BackgroundAlpha | FillPaint | StrokePaint | ``` [[viewBox-type]] ** - Defines an element viewBox. `viewBox ::= " " " " " " ` [[preserveAspectRatio-type]] ** - A scaling method definition. Works exactly the same https://www.w3.org/TR/SVG11/coords.html#PreserveAspectRatioAttribute[as described] in the SVG spec. [[transform-type]] ** - A transformation matrix. Always a `matrix` and not `translate`, `scale`, etc. Numbers are space-separated. + `transform ::= matrix( " " " " " " " " " " )` [[path-data-type]] ** - A path data. * Contains only absolute MoveTo, LineTo, CurveTo and ClosePath segments. * All segments are explicit. * The first segment is guarantee to be MoveTo. * Segments, commands and coordinates are separated only by space. * Path and all subpaths are guarantee to have at least two segments. Grammar: ``` svg-path: moveto-drawto-command-groups moveto-drawto-command-groups: moveto-drawto-command-group | moveto-drawto-command-group " " moveto-drawto-command-groups moveto-drawto-command-group: moveto " " drawto-commands drawto-commands: drawto-command | drawto-command " " drawto-commands drawto-command: closepath | lineto | curveto moveto: "M " coordinate-pair lineto: "L " coordinate-pair curveto: "C " coordinate-pair " " coordinate-pair " " coordinate-pair closepath: "Z" coordinate-pair: coordinate " " coordinate coordinate: sign? digit-sequence "." digit-sequence sign: "-" digit-sequence: digit | digit digit-sequence digit: "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ``` resvg-0.8.0/examples/000077500000000000000000000000001352576375700145075ustar00rootroot00000000000000resvg-0.8.0/examples/README.md000066400000000000000000000010321352576375700157620ustar00rootroot00000000000000See [BUILD.adoc](../BUILD.adoc) first. Note: we are using *qt-backend* just for example. ### custom.rs Render image using a manually constructed SVG render tree. ```bash cargo run --features "qt-backend" --example custom_rtree ``` ### draw_bboxes.rs Draw bounding boxes aroung all shapes on input SVG. ```bash cargo run --features "qt-backend" --example draw_bboxes -- bboxes.svg bboxes.png -z 4 ``` ### minimal.rs A simple SVG to PNG converter. ```bash cargo run --features "qt-backend" --example minimal -- in.svg out.png ``` resvg-0.8.0/examples/bboxes.svg000066400000000000000000000055141352576375700165170ustar00rootroot00000000000000 Simple rect Rect with stroke Rect with transform Rect with transform and stroke Rect with transform THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG the quick brown fox jumps over the lazy dog Simple text Text Multiline text Line 1 Line 2 Text with transform Text Text with stroke Text Text with rotate Text Vertical text 日本 Text on path Some long text Simple group Group with two children Group with two children resvg-0.8.0/examples/cairo-capi/000077500000000000000000000000001352576375700165165ustar00rootroot00000000000000resvg-0.8.0/examples/cairo-capi/.gitignore000066400000000000000000000000141352576375700205010ustar00rootroot00000000000000example *.o resvg-0.8.0/examples/cairo-capi/Makefile000066400000000000000000000007221352576375700201570ustar00rootroot00000000000000TARGET = example LIBS = -lm -L../../target/debug -lresvg `pkg-config --libs gtk+-3.0` CC = gcc CFLAGS = -g -Wall `pkg-config --cflags gtk+-3.0` -I../../capi/include -DRESVG_CAIRO_BACKEND .PHONY: default all clean default: $(TARGET) all: default OBJECTS = $(patsubst %.c, %.o, $(wildcard *.c)) %.o: %.c $(CC) $(CFLAGS) -c $< -o $@ .PRECIOUS: $(TARGET) $(OBJECTS) $(TARGET): $(OBJECTS) $(CC) $(OBJECTS) -Wall $(LIBS) -o $@ clean: -rm -f *.o -rm -f $(TARGET) resvg-0.8.0/examples/cairo-capi/README.md000066400000000000000000000006101352576375700177720ustar00rootroot00000000000000A simple example that shows how to use *resvg* from GTK+ through C-API. I'm not good with C and GTK+ so any suggestions are welcome. ## Run ```bash # build C-API with a cairo backend first cargo build --release --features "cairo-backend" --manifest-path ../../capi/Cargo.toml make LD_LIBRARY_PATH=../../target/debug ./example image.svg ``` See [BUILD.adoc](../../BUILD.adoc) for details. resvg-0.8.0/examples/cairo-capi/example.c000066400000000000000000000042771352576375700203270ustar00rootroot00000000000000#include #include #include static resvg_render_tree *tree = NULL; static resvg_options opt; static gboolean draw_cb(GtkWidget *widget, cairo_t *cr, gpointer data) { GtkAllocation alloc; gtk_widget_get_allocation(widget, &alloc); if (tree) { resvg_size s = { alloc.width, alloc.height }; resvg_cairo_render_to_canvas(tree, &opt, s, cr); } return FALSE; } static void close_window(void) { if (tree) { resvg_tree_destroy(tree); } } static void parse_doc(const char *path) { resvg_init_options(&opt); opt.path = path; opt.font_family = "Times New Roman"; opt.languages = "en"; int err = resvg_parse_tree_from_file(path, &opt, &tree); if (err != RESVG_OK) { printf("Error id: %i\n", err); abort(); } } static void activate(GtkApplication *app) { GtkWidget *window; GtkWidget *frame; GtkWidget *drawing_area; window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Drawing Area"); g_signal_connect(window, "destroy", G_CALLBACK (close_window), NULL); gtk_container_set_border_width(GTK_CONTAINER(window), 8); frame = gtk_frame_new(NULL); gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN); gtk_container_add(GTK_CONTAINER(window), frame); drawing_area = gtk_drawing_area_new(); gtk_widget_set_size_request(drawing_area, 400, 400); gtk_container_add(GTK_CONTAINER(frame), drawing_area); g_signal_connect(drawing_area, "draw", G_CALLBACK(draw_cb), NULL); gtk_widget_set_events(drawing_area, gtk_widget_get_events(drawing_area)); gtk_widget_show_all(window); } static void open(GApplication *app, GFile **files, gint n_files, const gchar *hint) { gchar *path = g_file_get_path(files[0]); parse_doc(path); free(path); activate(app); } int main(int argc, char **argv) { resvg_init_log(); GtkApplication *app; int status; app = gtk_application_new("org.gtk.example", G_APPLICATION_HANDLES_OPEN); g_signal_connect(app, "open", G_CALLBACK(open), NULL); status = g_application_run(G_APPLICATION(app), argc, argv); g_object_unref(app); return status; } resvg-0.8.0/examples/cairo-rs/000077500000000000000000000000001352576375700162265ustar00rootroot00000000000000resvg-0.8.0/examples/cairo-rs/Cargo.toml000066400000000000000000000004531352576375700201600ustar00rootroot00000000000000[package] name = "cairo-example" version = "0.1.0" authors = ["Reizner Evgeniy "] edition = "2018" # exclude from the main workspace [workspace] [dependencies] gio = "0.7" gtk = "0.7" log = "0.4" fern = "0.5" [dependencies.resvg] path = "../.." features = ["cairo-backend"] resvg-0.8.0/examples/cairo-rs/README.md000066400000000000000000000002331352576375700175030ustar00rootroot00000000000000A simple example that shows how to use *resvg* from `gtk-rs`. ## Run ```bash cargo run -- image.svg ``` See [BUILD.adoc](../../BUILD.adoc) for details. resvg-0.8.0/examples/cairo-rs/src/000077500000000000000000000000001352576375700170155ustar00rootroot00000000000000resvg-0.8.0/examples/cairo-rs/src/main.rs000066400000000000000000000033521352576375700203120ustar00rootroot00000000000000use std::env::args; use gio::prelude::*; use gtk::prelude::*; use gtk::DrawingArea; fn main() { fern::Dispatch::new() .format(|out, message, record| { out.finish(format_args!( "[{}][{}] {}", record.level(), record.target(), message )) }) .level(log::LevelFilter::Warn) .chain(std::io::stderr()) .apply() .unwrap(); let application = gtk::Application::new( Some("com.github.cairo-example"), gio::ApplicationFlags::from_bits_truncate(4) ).expect("Initialization failed..."); application.connect_activate(|_| {}); application.connect_open(move |app, files, _| { let path = files[0].get_path().unwrap(); build_ui(app, &path); }); application.run(&args().collect::>()); } fn build_ui(application: >k::Application, file_path: &std::path::Path) { let window = gtk::ApplicationWindow::new(application); let drawing_area = Box::new(DrawingArea::new)(); let mut opt = resvg::Options::default(); opt.usvg.path = Some(file_path.into()); let tree = resvg::usvg::Tree::from_file(file_path, &opt.usvg).unwrap(); drawing_area.connect_draw(move |w, cr| { let s = resvg::ScreenSize::new( w.get_allocated_width() as u32, w.get_allocated_height() as u32, ).unwrap(); resvg::backend_cairo::render_to_canvas(&tree, &opt, s, cr); Inhibit(false) }); window.set_default_size(500, 500); let window_clone = window.clone(); window.connect_delete_event(move |_, _| { window_clone.destroy(); Inhibit(false) }); window.add(&drawing_area); window.show_all(); } resvg-0.8.0/examples/custom_rtree.rs000066400000000000000000000036201352576375700175710ustar00rootroot00000000000000use std::rc::Rc; use resvg::prelude::*; fn main() { let backend = resvg::default_backend(); let opt = Options::default(); let size = Size::new(200.0, 200.0).unwrap(); let mut rtree = usvg::Tree::create(usvg::Svg { size, view_box: usvg::ViewBox { rect: size.to_rect(0.0, 0.0), aspect: usvg::AspectRatio::default(), }, }); rtree.append_to_defs(usvg::NodeKind::LinearGradient(usvg::LinearGradient { id: "lg1".into(), x1: 0.0, y1: 0.0, x2: 1.0, y2: 0.0, base: usvg::BaseGradient { units: usvg::Units::ObjectBoundingBox, transform: usvg::Transform::default(), spread_method: usvg::SpreadMethod::Pad, stops: vec![ usvg::Stop { offset: usvg::StopOffset::new(0.0), color: usvg::Color::new(0, 255, 0), opacity: usvg::Opacity::new(1.0), }, usvg::Stop { offset: usvg::StopOffset::new(1.0), color: usvg::Color::new(0, 255, 0), opacity: usvg::Opacity::new(0.0), }, ], }, })); let fill = Some(usvg::Fill { paint: usvg::Paint::Link("lg1".into()), ..usvg::Fill::default() }); rtree.root().append_kind(usvg::NodeKind::Path(usvg::Path { id: String::new(), transform: usvg::Transform::default(), visibility: usvg::Visibility::Visible, fill, stroke: None, rendering_mode: usvg::ShapeRendering::default(), data: Rc::new(usvg::PathData::from_rect(Rect::new(20.0, 20.0, 160.0, 160.0).unwrap())), })); println!("{}", rtree.to_string(usvg::XmlOptions::default())); let mut img = backend.render_to_image(&rtree, &opt).unwrap(); img.save_png(std::path::Path::new("out.png")); } resvg-0.8.0/examples/draw_bboxes.rs000066400000000000000000000032171352576375700173570ustar00rootroot00000000000000use std::rc::Rc; use resvg::prelude::*; fn main() { let args: Vec = std::env::args().collect(); if !(args.len() == 3 || args.len() == 5) { println!( "Usage:\n\ \tdraw_bboxes \n\ \tdraw_bboxes -z ZOOM" ); return; } let zoom = if args.len() == 5 { args[4].parse::().expect("not a float") } else { 1.0 }; let mut opt = Options::default(); opt.usvg.path = Some(args[1].clone().into()); opt.usvg.keep_named_groups = true; opt.fit_to = resvg::FitTo::Zoom(zoom as f32); let rtree = usvg::Tree::from_file(&args[1], &opt.usvg).unwrap(); let mut bboxes = Vec::new(); for node in rtree.root().descendants() { if !rtree.is_in_defs(&node) { if let Some(bbox) = node.calculate_bbox() { bboxes.push(bbox); } } } let stroke = Some(usvg::Stroke { paint: usvg::Paint::Color(usvg::Color::new(255, 0, 0)), opacity: 0.5.into(), .. usvg::Stroke::default() }); for bbox in bboxes { rtree.root().append_kind(usvg::NodeKind::Path(usvg::Path { id: String::new(), transform: usvg::Transform::default(), visibility: usvg::Visibility::Visible, fill: None, stroke: stroke.clone(), rendering_mode: usvg::ShapeRendering::default(), data: Rc::new(usvg::PathData::from_rect(bbox)), })); } let mut img = resvg::default_backend().render_to_image(&rtree, &opt).unwrap(); img.save_png(std::path::Path::new(&args[2])); } resvg-0.8.0/examples/minimal.rs000066400000000000000000000010151352576375700165000ustar00rootroot00000000000000use resvg::prelude::*; fn main() { let args: Vec = std::env::args().collect(); if args.len() != 3 { println!("Usage:\n\tminimal "); return; } let mut opt = resvg::Options::default(); opt.usvg.path = Some(args[1].clone().into()); let rtree = usvg::Tree::from_file(&args[1], &opt.usvg).unwrap(); let backend = resvg::default_backend(); let mut img = backend.render_to_image(&rtree, &opt).unwrap(); img.save_png(std::path::Path::new(&args[2])); } resvg-0.8.0/src/000077500000000000000000000000001352576375700134605ustar00rootroot00000000000000resvg-0.8.0/src/backend_cairo/000077500000000000000000000000001352576375700162245ustar00rootroot00000000000000resvg-0.8.0/src/backend_cairo/clip_and_mask.rs000066400000000000000000000124761352576375700213700ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::{prelude::*, ConvTransform}; use super::{path, CairoLayers, ReCairoContextExt}; pub fn clip( node: &usvg::Node, cp: &usvg::ClipPath, opt: &Options, bbox: Rect, layers: &mut CairoLayers, cr: &cairo::Context, ) { let clip_surface = try_opt!(layers.get()); let clip_surface = clip_surface.borrow_mut(); let clip_cr = cairo::Context::new(&*clip_surface); clip_cr.set_source_rgba(0.0, 0.0, 0.0, 1.0); clip_cr.paint(); clip_cr.set_matrix(cr.get_matrix()); clip_cr.transform(cp.transform.to_native()); if cp.units == usvg::Units::ObjectBoundingBox { clip_cr.transform(usvg::Transform::from_bbox(bbox).to_native()); } clip_cr.set_operator(cairo::Operator::Clear); let matrix = clip_cr.get_matrix(); for node in node.children() { clip_cr.transform(node.transform().to_native()); match *node.borrow() { usvg::NodeKind::Path(ref p) => { path::draw(&node.tree(), p, opt, &clip_cr); } usvg::NodeKind::Group(ref g) => { clip_group(&node, g, opt, bbox, layers, &clip_cr); } _ => {} } clip_cr.set_matrix(matrix); } if let Some(ref id) = cp.clip_path { if let Some(ref clip_node) = node.tree().defs_by_id(id) { if let usvg::NodeKind::ClipPath(ref cp) = *clip_node.borrow() { clip(clip_node, cp, opt, bbox, layers, cr); } } } cr.set_matrix(cairo::Matrix::identity()); cr.set_source_surface(&*clip_surface, 0.0, 0.0); cr.set_operator(cairo::Operator::DestOut); cr.paint(); // Reset operator. cr.set_operator(cairo::Operator::Over); // Reset source to unborrow the `clip_surface` from the `Context`. cr.reset_source_rgba(); } fn clip_group( node: &usvg::Node, g: &usvg::Group, opt: &Options, bbox: Rect, layers: &mut CairoLayers, cr: &cairo::Context, ) { if let Some(ref id) = g.clip_path { if let Some(ref clip_node) = node.tree().defs_by_id(id) { if let usvg::NodeKind::ClipPath(ref cp) = *clip_node.borrow() { // If a `clipPath` child also has a `clip-path` // then we should render this child on a new canvas, // clip it, and only then draw it to the `clipPath`. let clip_surface = try_opt!(layers.get()); let clip_surface = clip_surface.borrow_mut(); let clip_cr = cairo::Context::new(&*clip_surface); clip_cr.set_source_rgba(0.0, 0.0, 0.0, 0.0); clip_cr.paint(); clip_cr.set_matrix(cr.get_matrix()); draw_group_child(&node, opt, &clip_cr); clip(clip_node, cp, opt, bbox, layers, &clip_cr); cr.set_matrix(cairo::Matrix::identity()); cr.set_operator(cairo::Operator::Xor); cr.set_source_surface(&*clip_surface, 0.0, 0.0); cr.set_operator(cairo::Operator::DestOut); cr.paint(); } } } } fn draw_group_child( node: &usvg::Node, opt: &Options, cr: &cairo::Context, ) { if let Some(child) = node.first_child() { cr.transform(child.transform().to_native()); match *child.borrow() { usvg::NodeKind::Path(ref path_node) => { path::draw(&child.tree(), path_node, opt, cr); } _ => {} } } } pub fn mask( node: &usvg::Node, mask: &usvg::Mask, opt: &Options, bbox: Rect, layers: &mut CairoLayers, cr: &cairo::Context, ) { let mask_surface = try_opt!(layers.get()); let mut mask_surface = mask_surface.borrow_mut(); { let mask_cr = cairo::Context::new(&*mask_surface); mask_cr.set_matrix(cr.get_matrix()); let r = if mask.units == usvg::Units::ObjectBoundingBox { mask.rect.bbox_transform(bbox) } else { mask.rect }; mask_cr.rectangle(r.x(), r.y(), r.width(), r.height()); mask_cr.clip(); if mask.content_units == usvg::Units::ObjectBoundingBox { mask_cr.transform(usvg::Transform::from_bbox(bbox).to_native()); } super::render_group(node, opt, layers, &mask_cr); } { let mut data = try_opt_warn!( mask_surface.get_data().ok(), "Failed to borrow a surface for mask '{}'.", mask.id ); use rgb::FromSlice; crate::image_to_mask(data.as_bgra_mut(), layers.image_size()); } if let Some(ref id) = mask.mask { if let Some(ref mask_node) = node.tree().defs_by_id(id) { if let usvg::NodeKind::Mask(ref mask) = *mask_node.borrow() { self::mask(mask_node, mask, opt, bbox, layers, cr); } } } cr.set_matrix(cairo::Matrix::identity()); cr.set_source_surface(&*mask_surface, 0.0, 0.0); cr.set_operator(cairo::Operator::DestIn); cr.paint(); // Reset operator. cr.set_operator(cairo::Operator::Over); // Reset source to unborrow the `mask_surface` from the `Context`. cr.reset_source_rgba(); } resvg-0.8.0/src/backend_cairo/filter.rs000066400000000000000000000357641352576375700200760ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::cmp; use std::rc::Rc; use rgb::FromSlice; use log::warn; use usvg::ColorInterpolation as ColorSpace; use crate::prelude::*; use crate::filter::{self, Filter, ImageExt, Error}; use crate::ConvTransform; use super::ReCairoContextExt; type Image = filter::Image; type FilterResult = filter::FilterResult; pub fn apply( filter: &usvg::Filter, bbox: Option, ts: &usvg::Transform, opt: &Options, canvas: &mut cairo::ImageSurface, ) { CairoFilter::apply(filter, bbox, ts, opt, canvas); } impl ImageExt for cairo::ImageSurface { fn width(&self) -> u32 { self.get_width() as u32 } fn height(&self) -> u32 { self.get_height() as u32 } fn try_clone(&self) -> Result { let new_image = create_image(self.width(), self.height())?; let cr = cairo::Context::new(&new_image); cr.set_source_surface(self, 0.0, 0.0); cr.paint(); Ok(new_image) } fn clip(&mut self, region: ScreenRect) { let cr = cairo::Context::new(self); cr.set_source_rgba(0.0, 0.0, 0.0, 0.0); cr.set_operator(cairo::Operator::Clear); cr.rectangle(0.0, 0.0, self.width() as f64, region.y() as f64); cr.rectangle(0.0, 0.0, region.x() as f64, self.height() as f64); cr.rectangle(region.right() as f64, 0.0, self.width() as f64, self.height() as f64); cr.rectangle(0.0, region.bottom() as f64, self.width() as f64, self.height() as f64); cr.fill(); } fn clear(&mut self) { let cr = cairo::Context::new(self); cr.set_operator(cairo::Operator::Clear); cr.set_source_rgba(0.0, 0.0, 0.0, 0.0); cr.paint(); } fn into_srgb(&mut self) { if let Ok(ref mut data) = self.get_data() { filter::from_premultiplied(data.as_bgra_mut()); for p in data.as_bgra_mut() { p.r = filter::LINEAR_RGB_TO_SRGB_TABLE[p.r as usize]; p.g = filter::LINEAR_RGB_TO_SRGB_TABLE[p.g as usize]; p.b = filter::LINEAR_RGB_TO_SRGB_TABLE[p.b as usize]; } filter::into_premultiplied(data.as_bgra_mut()); } else { warn!("Cairo surface is already borrowed."); } } fn into_linear_rgb(&mut self) { if let Ok(ref mut data) = self.get_data() { filter::from_premultiplied(data.as_bgra_mut()); for p in data.as_bgra_mut() { p.r = filter::SRGB_TO_LINEAR_RGB_TABLE[p.r as usize]; p.g = filter::SRGB_TO_LINEAR_RGB_TABLE[p.g as usize]; p.b = filter::SRGB_TO_LINEAR_RGB_TABLE[p.b as usize]; } filter::into_premultiplied(data.as_bgra_mut()); } else { warn!("Cairo surface is already borrowed."); } } } fn create_image(width: u32, height: u32) -> Result { cairo::ImageSurface::create(cairo::Format::ARgb32, width as i32, height as i32) .map_err(|_| Error::AllocFailed) } fn copy_image( image: &cairo::ImageSurface, region: ScreenRect, ) -> Result { let x = cmp::max(0, region.x()) as f64; let y = cmp::max(0, region.y()) as f64; let new_image = create_image(region.width(), region.height())?; let cr = cairo::Context::new(&new_image); cr.set_source_surface(&*image, -x, -y); cr.paint(); Ok(new_image) } struct CairoFilter; impl Filter for CairoFilter { fn get_input( input: &usvg::FilterInput, region: ScreenRect, results: &[FilterResult], canvas: &cairo::ImageSurface, ) -> Result { match input { usvg::FilterInput::SourceGraphic => { let image = copy_image(canvas, region)?; Ok(Image { image: Rc::new(image), region: region.translate_to(0, 0), color_space: ColorSpace::SRGB, }) } usvg::FilterInput::SourceAlpha => { let mut image = copy_image(canvas, region)?; // Set RGB to black. Keep alpha as is. if let Ok(ref mut data) = image.get_data() { for p in data.chunks_mut(4) { p[0] = 0; p[1] = 0; p[2] = 0; } } else { warn!("Cairo surface is already borrowed."); } Ok(Image { image: Rc::new(image), region: region.translate_to(0, 0), color_space: ColorSpace::SRGB, }) } usvg::FilterInput::Reference(ref name) => { if let Some(ref v) = results.iter().rev().find(|v| v.name == *name) { Ok(v.image.clone()) } else { // Technically unreachable. warn!("Unknown filter primitive reference '{}'.", name); Self::get_input(&usvg::FilterInput::SourceGraphic, region, results, canvas) } } _ => { warn!("Filter input '{:?}' is not supported.", input); Self::get_input(&usvg::FilterInput::SourceGraphic, region, results, canvas) } } } fn apply_blur( fe: &usvg::FeGaussianBlur, units: usvg::Units, cs: ColorSpace, bbox: Option, ts: &usvg::Transform, input: Image, ) -> Result { let (std_dx, std_dy, box_blur) = try_opt_or!(Self::resolve_std_dev(fe, units, bbox, ts), Ok(input)); let input = input.into_color_space(cs)?; let mut buffer = input.take()?; let (w, h) = (buffer.width(), buffer.height()); if let Ok(ref mut data) = buffer.get_data() { if box_blur { filter::box_blur::apply(data, w, h, std_dx, std_dy); } else { filter::iir_blur::apply(data, w, h, std_dx, std_dy); } } Ok(Image::from_image(buffer, cs)) } fn apply_offset( fe: &usvg::FeOffset, units: usvg::Units, bbox: Option, ts: &usvg::Transform, input: Image, ) -> Result { let (dx, dy) = try_opt_or!(Self::resolve_offset(fe, units, bbox, ts), Ok(input)); // TODO: do not use an additional buffer let buffer = create_image(input.width(), input.height())?; let cr = cairo::Context::new(&buffer); cr.set_source_surface(input.as_ref(), dx, dy); cr.paint(); Ok(Image::from_image(buffer, input.color_space)) } fn apply_blend( fe: &usvg::FeBlend, cs: ColorSpace, region: ScreenRect, input1: Image, input2: Image, ) -> Result { let input1 = input1.into_color_space(cs)?; let input2 = input2.into_color_space(cs)?; let buffer = create_image(region.width(), region.height())?; let cr = cairo::Context::new(&buffer); cr.set_source_surface(input2.as_ref(), 0.0, 0.0); cr.paint(); let operator = match fe.mode { usvg::FeBlendMode::Normal => cairo::Operator::Over, usvg::FeBlendMode::Multiply => cairo::Operator::Multiply, usvg::FeBlendMode::Screen => cairo::Operator::Screen, usvg::FeBlendMode::Darken => cairo::Operator::Darken, usvg::FeBlendMode::Lighten => cairo::Operator::Lighten, }; cr.set_operator(operator); cr.set_source_surface(input1.as_ref(), 0.0, 0.0); cr.paint(); Ok(Image::from_image(buffer, cs)) } fn apply_composite( fe: &usvg::FeComposite, cs: ColorSpace, region: ScreenRect, input1: Image, input2: Image, ) -> Result { use rgb::alt::BGRA8; let mut input1 = input1.into_color_space(cs)?.take()?; let mut input2 = input2.into_color_space(cs)?.take()?; let mut buffer = create_image(region.width(), region.height())?; if let Operator::Arithmetic { k1, k2, k3, k4 } = fe.operator { let data1 = input1.get_data().unwrap(); let data2 = input2.get_data().unwrap(); let calc = |i1, i2, max| { let i1 = i1 as f64 / 255.0; let i2 = i2 as f64 / 255.0; let result = k1.value() * i1 * i2 + k2.value() * i1 + k3.value() * i2 + k4.value(); f64_bound(0.0, result, max) }; { let mut i = 0; let mut data3 = buffer.get_data().unwrap(); let data3 = data3.as_bgra_mut(); for (c1, c2) in data1.as_bgra().iter().zip(data2.as_bgra()) { let a = calc(c1.a, c2.a, 1.0); if a.is_fuzzy_zero() { continue; } let r = (calc(c1.r, c2.r, a) * 255.0) as u8; let g = (calc(c1.g, c2.g, a) * 255.0) as u8; let b = (calc(c1.b, c2.b, a) * 255.0) as u8; let a = (a * 255.0) as u8; data3[i] = BGRA8 { r, g, b, a }; i += 1; } } return Ok(Image::from_image(buffer, cs)); } let cr = cairo::Context::new(&buffer); cr.set_source_surface(&input2, 0.0, 0.0); cr.paint(); use usvg::FeCompositeOperator as Operator; let operator = match fe.operator { Operator::Over => cairo::Operator::Over, Operator::In => cairo::Operator::In, Operator::Out => cairo::Operator::Out, Operator::Atop => cairo::Operator::Atop, Operator::Xor => cairo::Operator::Xor, Operator::Arithmetic { .. } => cairo::Operator::Over, }; cr.set_operator(operator); cr.set_source_surface(&input1, 0.0, 0.0); cr.paint(); Ok(Image::from_image(buffer, cs)) } fn apply_merge( fe: &usvg::FeMerge, cs: ColorSpace, region: ScreenRect, results: &[FilterResult], canvas: &cairo::ImageSurface, ) -> Result { let buffer = create_image(region.width(), region.height())?; let cr = cairo::Context::new(&buffer); for input in &fe.inputs { let input = Self::get_input(input, region, &results, canvas)?; let input = input.into_color_space(cs)?; cr.set_source_surface(input.as_ref(), 0.0, 0.0); cr.paint(); } Ok(Image::from_image(buffer, cs)) } fn apply_flood( fe: &usvg::FeFlood, region: ScreenRect, ) -> Result { let buffer = create_image(region.width(), region.height())?; let cr = cairo::Context::new(&buffer); cr.set_source_color(fe.color, fe.opacity); cr.paint(); Ok(Image::from_image(buffer, ColorSpace::SRGB)) } fn apply_tile( input: Image, region: ScreenRect, ) -> Result { let buffer = create_image(region.width(), region.height())?; let subregion = input.region.translate(-region.x(), -region.y()); let tile = copy_image(&input.image, subregion)?; let brush_ts = usvg::Transform::new_translate(subregion.x() as f64, subregion.y() as f64); let patt = cairo::SurfacePattern::create(&tile); patt.set_extend(cairo::Extend::Repeat); patt.set_filter(cairo::Filter::Best); let cr = cairo::Context::new(&buffer); let mut m: cairo::Matrix = brush_ts.to_native(); m.invert(); patt.set_matrix(m); cr.set_source(&patt); cr.rectangle(0.0, 0.0, region.width() as f64, region.height() as f64); cr.fill(); Ok(Image::from_image(buffer, ColorSpace::SRGB)) } fn apply_image( fe: &usvg::FeImage, region: ScreenRect, subregion: ScreenRect, opt: &Options, ) -> Result { let buffer = create_image(region.width(), region.height())?; match fe.data { usvg::FeImageKind::None => {} usvg::FeImageKind::Image(ref data, format) => { let cr = cairo::Context::new(&buffer); let dx = (subregion.x() - region.x()) as f64; let dy = (subregion.y() - region.y()) as f64; cr.translate(dx, dy); let view_box = usvg::ViewBox { rect: subregion.translate_to(0, 0).to_rect(), aspect: fe.aspect, }; if format == usvg::ImageFormat::SVG { super::image::draw_svg(data, view_box, opt, &cr); } else { super::image::draw_raster(format, data, view_box, fe.rendering_mode, opt, &cr); } } usvg::FeImageKind::Use(..) => {} } Ok(Image::from_image(buffer, ColorSpace::SRGB)) } fn apply_component_transfer( fe: &usvg::FeComponentTransfer, cs: ColorSpace, input: Image, ) -> Result { let input = input.into_color_space(cs)?; let mut buffer = input.take()?; if let Ok(ref mut data) = buffer.get_data() { filter::from_premultiplied(data.as_bgra_mut()); for pixel in data.as_bgra_mut() { pixel.r = fe.func_r.apply(pixel.r); pixel.g = fe.func_g.apply(pixel.g); pixel.b = fe.func_b.apply(pixel.b); pixel.a = fe.func_a.apply(pixel.a); } filter::into_premultiplied(data.as_bgra_mut()); } Ok(Image::from_image(buffer, cs)) } fn apply_color_matrix( fe: &usvg::FeColorMatrix, cs: ColorSpace, input: Image, ) -> Result { let input = input.into_color_space(cs)?; let mut buffer = input.take()?; if let Ok(ref mut data) = buffer.get_data() { filter::from_premultiplied(data.as_bgra_mut()); filter::color_matrix::apply(&fe.kind, data.as_bgra_mut()); filter::into_premultiplied(data.as_bgra_mut()); } Ok(Image::from_image(buffer, cs)) } fn apply_to_canvas( input: Image, region: ScreenRect, canvas: &mut cairo::ImageSurface, ) -> Result<(), Error> { let input = input.into_color_space(ColorSpace::SRGB)?; let cr = cairo::Context::new(canvas); cr.set_operator(cairo::Operator::Clear); cr.set_source_rgba(0.0, 0.0, 0.0, 0.0); cr.paint(); cr.set_operator(cairo::Operator::Over); cr.set_source_surface(input.as_ref(), region.x() as f64, region.y() as f64); cr.paint(); Ok(()) } } resvg-0.8.0/src/backend_cairo/image.rs000066400000000000000000000072241352576375700176610ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::prelude::*; use crate::image; use crate::ConvTransform; pub fn draw( image: &usvg::Image, opt: &Options, cr: &cairo::Context, ) -> Rect { if image.visibility != usvg::Visibility::Visible { return image.view_box.rect; } if image.format == usvg::ImageFormat::SVG { draw_svg(&image.data, image.view_box, opt, cr); } else { draw_raster(image.format, &image.data, image.view_box, image.rendering_mode, opt, cr); } image.view_box.rect } pub fn draw_raster( format: usvg::ImageFormat, data: &usvg::ImageData, view_box: usvg::ViewBox, rendering_mode: usvg::ImageRendering, opt: &Options, cr: &cairo::Context, ) { let img = try_opt!(image::load_raster(format, data, opt)); let surface = { let mut surface = try_create_surface!(img.size, ()); { // Unwrap is safe, because no one uses the surface. let mut surface_data = surface.get_data().unwrap(); image_to_surface(&img, &mut surface_data); } surface }; let (ts, clip) = image::prepare_sub_svg_geom(view_box, img.size); if let Some(clip) = clip { cr.rectangle(clip.x(), clip.y(), clip.width(), clip.height()); cr.clip(); } else { // We have to clip the image before rendering because we use `Extend::Pad`. let r = image::image_rect(&view_box, img.size); cr.rectangle(r.x(), r.y(), r.width(), r.height()); cr.clip(); } cr.transform(ts.to_native()); let filter_mode = match rendering_mode { usvg::ImageRendering::OptimizeQuality => cairo::Filter::Gaussian, usvg::ImageRendering::OptimizeSpeed => cairo::Filter::Nearest, }; let patt = cairo::SurfacePattern::create(&surface); // Do not use `Extend::None`, because it will introduce a "transparent border". patt.set_extend(cairo::Extend::Pad); patt.set_filter(filter_mode); cr.set_source(&patt); cr.paint(); cr.reset_clip(); } fn image_to_surface(image: &image::Image, surface: &mut [u8]) { // Surface is always ARGB. const SURFACE_CHANNELS: usize = 4; use rgb::FromSlice; let mut i = 0; let mut to_surface = |r, g, b, a| { let tr = a * r + 0x80; let tg = a * g + 0x80; let tb = a * b + 0x80; surface[i + 0] = (((tb >> 8) + tb) >> 8) as u8; surface[i + 1] = (((tg >> 8) + tg) >> 8) as u8; surface[i + 2] = (((tr >> 8) + tr) >> 8) as u8; surface[i + 3] = a as u8; i += SURFACE_CHANNELS; }; match &image.data { image::ImageData::RGB(data) => { for p in data.as_rgb() { to_surface(p.r as u32, p.g as u32, p.b as u32, 255); } } image::ImageData::RGBA(data) => { for p in data.as_rgba() { to_surface(p.r as u32, p.g as u32, p.b as u32, p.a as u32); } } } } pub fn draw_svg( data: &usvg::ImageData, view_box: usvg::ViewBox, opt: &Options, cr: &cairo::Context, ) { let (tree, sub_opt) = try_opt!(image::load_sub_svg(data, opt)); let img_size = tree.svg_node().size.to_screen_size(); let (ts, clip) = image::prepare_sub_svg_geom(view_box, img_size); if let Some(clip) = clip { cr.rectangle(clip.x(), clip.y(), clip.width(), clip.height()); cr.clip(); } cr.transform(ts.to_native()); super::render_to_canvas(&tree, &sub_opt, img_size, cr); cr.reset_clip(); } resvg-0.8.0/src/backend_cairo/mod.rs000066400000000000000000000234671352576375700173650ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. //! Cairo backend implementation. use log::warn; use crate::{prelude::*, layers, ConvTransform}; macro_rules! try_create_surface { ($size:expr, $ret:expr) => { try_opt_warn_or!( cairo::ImageSurface::create( cairo::Format::ARgb32, $size.width() as i32, $size.height() as i32, ).ok(), $ret, "Failed to create a {}x{} surface.", $size.width(), $size.height() ); }; } mod clip_and_mask; mod filter; mod image; mod path; mod style; type CairoLayers = layers::Layers; impl ConvTransform for usvg::Transform { fn to_native(&self) -> cairo::Matrix { cairo::Matrix::new(self.a, self.b, self.c, self.d, self.e, self.f) } fn from_native(ts: &cairo::Matrix) -> Self { Self::new(ts.xx, ts.yx, ts.xy, ts.yy, ts.x0, ts.y0) } } pub(crate) trait ReCairoContextExt { fn set_source_color(&self, color: usvg::Color, opacity: usvg::Opacity); fn reset_source_rgba(&self); } impl ReCairoContextExt for cairo::Context { fn set_source_color(&self, color: usvg::Color, opacity: usvg::Opacity) { self.set_source_rgba( color.red as f64 / 255.0, color.green as f64 / 255.0, color.blue as f64 / 255.0, opacity.value(), ); } fn reset_source_rgba(&self) { self.set_source_rgba(0.0, 0.0, 0.0, 0.0); } } /// Cairo backend handle. #[derive(Clone, Copy)] pub struct Backend; impl Render for Backend { fn render_to_image( &self, tree: &usvg::Tree, opt: &Options, ) -> Option> { let img = render_to_image(tree, opt)?; Some(Box::new(img)) } fn render_node_to_image( &self, node: &usvg::Node, opt: &Options, ) -> Option> { let img = render_node_to_image(node, opt)?; Some(Box::new(img)) } } impl OutputImage for cairo::ImageSurface { fn save_png( &mut self, path: &std::path::Path, ) -> bool { // Cairo doesn't support custom compression levels, // so we are using the `png` crate to save a surface manually. use rgb::FromSlice; use std::mem::swap; let file = try_opt_or!(std::fs::File::create(path).ok(), false); let ref mut w = std::io::BufWriter::new(file); let mut encoder = png::Encoder::new(w, self.get_width() as u32, self.get_height() as u32); encoder.set_color(png::ColorType::RGBA); encoder.set_depth(png::BitDepth::Eight); let mut writer = try_opt_or!(encoder.write_header().ok(), false); let mut img = try_opt_or!(self.get_data().ok(), false).to_vec(); // BGRA_Premultiplied -> BGRA crate::filter::from_premultiplied(img.as_bgra_mut()); // BGRA -> RGBA. img.as_bgra_mut().iter_mut().for_each(|p| swap(&mut p.r, &mut p.b)); try_opt_or!(writer.write_image_data(&img).ok(), false); true } } /// Renders SVG to image. pub fn render_to_image( tree: &usvg::Tree, opt: &Options, ) -> Option { let (surface, img_view) = create_surface( tree.svg_node().size.to_screen_size(), opt, )?; let cr = cairo::Context::new(&surface); // Fill background. if let Some(color) = opt.background { cr.set_source_color(color, 1.0.into()); cr.paint(); } render_to_canvas(tree, opt, img_view, &cr); Some(surface) } /// Renders SVG to image. pub fn render_node_to_image( node: &usvg::Node, opt: &Options, ) -> Option { let node_bbox = if let Some(bbox) = node.calculate_bbox() { bbox } else { warn!("Node '{}' has a zero size.", node.id()); return None; }; let (surface, img_size) = create_surface(node_bbox.to_screen_size(), opt)?; let vbox = usvg::ViewBox { rect: node_bbox, aspect: usvg::AspectRatio::default(), }; let cr = cairo::Context::new(&surface); // Fill background. if let Some(color) = opt.background { cr.set_source_color(color, 1.0.into()); cr.paint(); } render_node_to_canvas(node, opt, vbox, img_size, &cr); Some(surface) } /// Renders SVG to canvas. pub fn render_to_canvas( tree: &usvg::Tree, opt: &Options, img_size: ScreenSize, cr: &cairo::Context, ) { render_node_to_canvas(&tree.root(), opt, tree.svg_node().view_box, img_size, cr); } /// Renders SVG node to canvas. pub fn render_node_to_canvas( node: &usvg::Node, opt: &Options, view_box: usvg::ViewBox, img_size: ScreenSize, cr: &cairo::Context, ) { let mut layers = create_layers(img_size); apply_viewbox_transform(view_box, img_size, &cr); let curr_ts = cr.get_matrix(); let mut ts = node.abs_transform(); ts.append(&node.transform()); cr.transform(ts.to_native()); render_node(node, opt, &mut layers, cr); cr.set_matrix(curr_ts); } fn create_surface( size: ScreenSize, opt: &Options, ) -> Option<(cairo::ImageSurface, ScreenSize)> { let img_size = utils::fit_to(size, opt.fit_to)?; let surface = try_create_surface!(img_size, None); Some((surface, img_size)) } /// Applies viewbox transformation to the painter. fn apply_viewbox_transform( view_box: usvg::ViewBox, img_size: ScreenSize, cr: &cairo::Context, ) { let ts = utils::view_box_to_transform(view_box.rect, view_box.aspect, img_size.to_size()); cr.transform(ts.to_native()); } fn render_node( node: &usvg::Node, opt: &Options, layers: &mut CairoLayers, cr: &cairo::Context, ) -> Option { match *node.borrow() { usvg::NodeKind::Svg(_) => { render_group(node, opt, layers, cr) } usvg::NodeKind::Path(ref path) => { path::draw(&node.tree(), path, opt, cr) } usvg::NodeKind::Image(ref img) => { Some(image::draw(img, opt, cr)) } usvg::NodeKind::Group(ref g) => { render_group_impl(node, g, opt, layers, cr) } _ => None, } } fn render_group( parent: &usvg::Node, opt: &Options, layers: &mut CairoLayers, cr: &cairo::Context, ) -> Option { let curr_ts = cr.get_matrix(); let mut g_bbox = Rect::new_bbox(); for node in parent.children() { cr.transform(node.transform().to_native()); let bbox = render_node(&node, opt, layers, cr); if let Some(bbox) = bbox { if let Some(bbox) = bbox.transform(&node.transform()) { g_bbox = g_bbox.expand(bbox); } } // Revert transform. cr.set_matrix(curr_ts); } // Check that bbox was changed, otherwise we will have a rect with x/y set to f64::MAX. if g_bbox.fuzzy_ne(&Rect::new_bbox()) { Some(g_bbox) } else { None } } fn render_group_impl( node: &usvg::Node, g: &usvg::Group, opt: &Options, layers: &mut CairoLayers, cr: &cairo::Context, ) -> Option { let sub_surface = layers.get()?; let mut sub_surface = sub_surface.borrow_mut(); let curr_ts = cr.get_matrix(); let bbox = { let sub_cr = cairo::Context::new(&*sub_surface); sub_cr.set_matrix(curr_ts); render_group(node, opt, layers, &sub_cr) }; // Filter can be rendered on an object without a bbox, // as long as filter uses `userSpaceOnUse`. if let Some(ref id) = g.filter { if let Some(filter_node) = node.tree().defs_by_id(id) { if let usvg::NodeKind::Filter(ref filter) = *filter_node.borrow() { let ts = usvg::Transform::from_native(&curr_ts); filter::apply(filter, bbox, &ts, opt, &mut *sub_surface); } } } // Clipping and masking can be done only for objects with a valid bbox. if let Some(bbox) = bbox { if let Some(ref id) = g.clip_path { if let Some(clip_node) = node.tree().defs_by_id(id) { if let usvg::NodeKind::ClipPath(ref cp) = *clip_node.borrow() { let sub_cr = cairo::Context::new(&*sub_surface); sub_cr.set_matrix(curr_ts); clip_and_mask::clip(&clip_node, cp, opt, bbox, layers, &sub_cr); } } } if let Some(ref id) = g.mask { if let Some(mask_node) = node.tree().defs_by_id(id) { if let usvg::NodeKind::Mask(ref mask) = *mask_node.borrow() { let sub_cr = cairo::Context::new(&*sub_surface); sub_cr.set_matrix(curr_ts); clip_and_mask::mask(&mask_node, mask, opt, bbox, layers, &sub_cr); } } } } let curr_matrix = cr.get_matrix(); cr.set_matrix(cairo::Matrix::identity()); cr.set_source_surface(&*sub_surface, 0.0, 0.0); if !g.opacity.is_default() { cr.paint_with_alpha(g.opacity.value()); } else { cr.paint(); } cr.set_matrix(curr_matrix); // All layers must be unlinked from the main context/cr after used. // TODO: find a way to automate this cr.reset_source_rgba(); bbox } fn create_layers( img_size: ScreenSize, ) -> CairoLayers { layers::Layers::new(img_size, create_subsurface, clear_subsurface) } fn create_subsurface( size: ScreenSize, ) -> Option { Some(try_create_surface!(size, None)) } fn clear_subsurface( surface: &mut cairo::ImageSurface, ) { let cr = cairo::Context::new(&surface); cr.set_operator(cairo::Operator::Clear); cr.set_source_rgba(0.0, 0.0, 0.0, 0.0); cr.paint(); } resvg-0.8.0/src/backend_cairo/path.rs000066400000000000000000000060131352576375700175260ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::prelude::*; use super::style; pub fn draw( tree: &usvg::Tree, path: &usvg::Path, opt: &Options, cr: &cairo::Context, ) -> Option { let bbox = path.data.bbox(); if path.visibility != usvg::Visibility::Visible { return bbox; } let mut is_square_cap = false; if let Some(ref stroke) = path.stroke { is_square_cap = stroke.linecap == usvg::LineCap::Square; } draw_path(&path.data, is_square_cap, cr); // `usvg` guaranties that path without a bbox will not use // a paint server with ObjectBoundingBox, // so we can pass whatever rect we want, because it will not be used anyway. let style_bbox = bbox.unwrap_or_else(|| Rect::new(0.0, 0.0, 1.0, 1.0).unwrap()); if !crate::use_shape_antialiasing(path.rendering_mode) { cr.set_antialias(cairo::Antialias::None); } style::fill(tree, &path.fill, opt, style_bbox, cr); if path.stroke.is_some() { cr.fill_preserve(); style::stroke(tree, &path.stroke, opt, style_bbox, cr); cr.stroke(); } else { cr.fill(); } // Revert anti-aliasing. cr.set_antialias(cairo::Antialias::Default); bbox } fn draw_path( path: &usvg::PathData, is_square_cap: bool, cr: &cairo::Context, ) { // Reset path, in case something was left from the previous paint pass. cr.new_path(); for subpath in path.subpaths() { draw_subpath(subpath, is_square_cap, cr); } } fn draw_subpath( path: usvg::SubPathData, is_square_cap: bool, cr: &cairo::Context, ) { assert_ne!(path.len(), 0); // This is a workaround for a cairo bug(?). // // Buy the SVG spec, a zero length subpath with a square cap should be // rendered as a square/rect, but it's not (at least on 1.14.12/1.15.12). // And this is probably a bug, since round caps are rendered correctly. let is_zero_path = is_square_cap && path.length().is_fuzzy_zero(); if !is_zero_path { for seg in path.iter() { match *seg { usvg::PathSegment::MoveTo { x, y } => { cr.new_sub_path(); cr.move_to(x, y); } usvg::PathSegment::LineTo { x, y } => { cr.line_to(x, y); } usvg::PathSegment::CurveTo { x1, y1, x2, y2, x, y } => { cr.curve_to(x1, y1, x2, y2, x, y); } usvg::PathSegment::ClosePath => { cr.close_path(); } } } } else { if let usvg::PathSegment::MoveTo { x, y } = path[0] { // Draw zero length path. let shift = 0.002; // Purely empirical. cr.new_sub_path(); cr.move_to(x, y); cr.line_to(x + shift, y); } } } resvg-0.8.0/src/backend_cairo/style.rs000066400000000000000000000174121352576375700177370ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::{prelude::*, ConvTransform}; use super::ReCairoContextExt; pub fn fill( tree: &usvg::Tree, fill: &Option, opt: &Options, bbox: Rect, cr: &cairo::Context, ) { match *fill { Some(ref fill) => { match fill.paint { usvg::Paint::Color(c) => { cr.set_source_color(c, fill.opacity); } usvg::Paint::Link(ref id) => { if let Some(node) = tree.defs_by_id(id) { match *node.borrow() { usvg::NodeKind::LinearGradient(ref lg) => { prepare_linear(lg, fill.opacity, bbox, cr); } usvg::NodeKind::RadialGradient(ref rg) => { prepare_radial(rg, fill.opacity, bbox, cr); } usvg::NodeKind::Pattern(ref pattern) => { prepare_pattern(&node, pattern, opt, fill.opacity, bbox, cr); } _ => {} } } } } match fill.rule { usvg::FillRule::NonZero => cr.set_fill_rule(cairo::FillRule::Winding), usvg::FillRule::EvenOdd => cr.set_fill_rule(cairo::FillRule::EvenOdd), } } None => { cr.reset_source_rgba(); cr.set_fill_rule(cairo::FillRule::Winding); } } } pub fn stroke( tree: &usvg::Tree, stroke: &Option, opt: &Options, bbox: Rect, cr: &cairo::Context, ) { match *stroke { Some(ref stroke) => { match stroke.paint { usvg::Paint::Color(c) => { cr.set_source_color(c, stroke.opacity); } usvg::Paint::Link(ref id) => { if let Some(node) = tree.defs_by_id(id) { match *node.borrow() { usvg::NodeKind::LinearGradient(ref lg) => { prepare_linear(lg, stroke.opacity, bbox, cr); } usvg::NodeKind::RadialGradient(ref rg) => { prepare_radial(rg, stroke.opacity, bbox, cr); } usvg::NodeKind::Pattern(ref pattern) => { prepare_pattern(&node, pattern, opt, stroke.opacity, bbox, cr); } _ => {} } } } } let linecap = match stroke.linecap { usvg::LineCap::Butt => cairo::LineCap::Butt, usvg::LineCap::Round => cairo::LineCap::Round, usvg::LineCap::Square => cairo::LineCap::Square, }; cr.set_line_cap(linecap); let linejoin = match stroke.linejoin { usvg::LineJoin::Miter => cairo::LineJoin::Miter, usvg::LineJoin::Round => cairo::LineJoin::Round, usvg::LineJoin::Bevel => cairo::LineJoin::Bevel, }; cr.set_line_join(linejoin); match stroke.dasharray { Some(ref list) => cr.set_dash(list, stroke.dashoffset as f64), None => cr.set_dash(&[], 0.0), } cr.set_miter_limit(stroke.miterlimit.value()); cr.set_line_width(stroke.width.value()); } None => { // reset stroke properties cr.reset_source_rgba(); cr.set_line_cap(cairo::LineCap::Butt); cr.set_line_join(cairo::LineJoin::Miter); cr.set_miter_limit(4.0); cr.set_line_width(1.0); cr.set_dash(&[], 0.0); } } } fn prepare_linear( g: &usvg::LinearGradient, opacity: usvg::Opacity, bbox: Rect, cr: &cairo::Context, ) { let grad = cairo::LinearGradient::new(g.x1, g.y1, g.x2, g.y2); prepare_base_gradient(&g.base, &grad, opacity, bbox); cr.set_source(&grad); } fn prepare_radial( g: &usvg::RadialGradient, opacity: usvg::Opacity, bbox: Rect, cr: &cairo::Context, ) { let grad = cairo::RadialGradient::new(g.fx, g.fy, 0.0, g.cx, g.cy, g.r.value()); prepare_base_gradient(&g.base, &grad, opacity, bbox); cr.set_source(&grad); } fn prepare_base_gradient( g: &usvg::BaseGradient, grad: &cairo::Gradient, opacity: usvg::Opacity, bbox: Rect, ) { let spread_method = match g.spread_method { usvg::SpreadMethod::Pad => cairo::Extend::Pad, usvg::SpreadMethod::Reflect => cairo::Extend::Reflect, usvg::SpreadMethod::Repeat => cairo::Extend::Repeat, }; grad.set_extend(spread_method); let mut matrix = g.transform.to_native(); if g.units == usvg::Units::ObjectBoundingBox { let m = usvg::Transform::from_bbox(bbox).to_native(); matrix = cairo::Matrix::multiply(&matrix, &m); } matrix.invert(); grad.set_matrix(matrix); for stop in &g.stops { grad.add_color_stop_rgba( stop.offset.value(), stop.color.red as f64 / 255.0, stop.color.green as f64 / 255.0, stop.color.blue as f64 / 255.0, stop.opacity.value() * opacity.value(), ); } } fn prepare_pattern( node: &usvg::Node, pattern: &usvg::Pattern, opt: &Options, opacity: usvg::Opacity, bbox: Rect, cr: &cairo::Context, ) { let r = if pattern.units == usvg::Units::ObjectBoundingBox { pattern.rect.bbox_transform(bbox) } else { pattern.rect }; let global_ts = usvg::Transform::from_native(&cr.get_matrix()); let (sx, sy) = global_ts.get_scale(); let img_size = try_opt!(Size::new(r.width() * sx, r.height() * sy)).to_screen_size(); let surface = try_create_surface!(img_size, ()); let sub_cr = cairo::Context::new(&surface); sub_cr.transform(cairo::Matrix::new(sx, 0.0, 0.0, sy, 0.0, 0.0)); if let Some(vbox) = pattern.view_box { let ts = utils::view_box_to_transform(vbox.rect, vbox.aspect, r.size()); sub_cr.transform(ts.to_native()); } else if pattern.content_units == usvg::Units::ObjectBoundingBox { // 'Note that this attribute has no effect if attribute `viewBox` is specified.' // We don't use Transform::from_bbox(bbox) because `x` and `y` should be // ignored for some reasons... sub_cr.scale(bbox.width(), bbox.height()); } let mut layers = super::create_layers(img_size); super::render_group(node, opt, &mut layers, &sub_cr); let mut ts = usvg::Transform::default(); ts.append(&pattern.transform); ts.translate(r.x(), r.y()); ts.scale(1.0 / sx, 1.0 / sy); let surface = if !opacity.is_default() { // If `opacity` isn't `1` then we have to make image semitransparent. // The only way to do this is by making a new image and rendering // the pattern on it with transparency. let surface2 = try_create_surface!(img_size, ()); let sub_cr2 = cairo::Context::new(&surface2); sub_cr2.set_source_surface(&surface, 0.0, 0.0); sub_cr2.paint_with_alpha(opacity.value()); surface2 } else { surface }; let patt = cairo::SurfacePattern::create(&surface); patt.set_extend(cairo::Extend::Repeat); patt.set_filter(cairo::Filter::Best); let mut m: cairo::Matrix = ts.to_native(); m.invert(); patt.set_matrix(m); cr.set_source(&patt); } resvg-0.8.0/src/backend_qt/000077500000000000000000000000001352576375700155535ustar00rootroot00000000000000resvg-0.8.0/src/backend_qt/clip_and_mask.rs000066400000000000000000000112441352576375700207070ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::qt; use crate::prelude::*; use crate::ConvTransform; use super::{path, QtLayers}; pub fn clip( node: &usvg::Node, cp: &usvg::ClipPath, opt: &Options, bbox: Rect, layers: &mut QtLayers, p: &mut qt::Painter, ) { let clip_img = try_opt!(layers.get()); let mut clip_img = clip_img.borrow_mut(); clip_img.fill(0, 0, 0, 255); let mut clip_p = qt::Painter::new(&mut clip_img); clip_p.set_transform(&p.get_transform()); clip_p.apply_transform(&cp.transform.to_native()); if cp.units == usvg::Units::ObjectBoundingBox { clip_p.apply_transform(&usvg::Transform::from_bbox(bbox).to_native()); } clip_p.set_composition_mode(qt::CompositionMode::Clear); let ts = clip_p.get_transform(); for node in node.children() { clip_p.apply_transform(&node.transform().to_native()); match *node.borrow() { usvg::NodeKind::Path(ref path_node) => { path::draw(&node.tree(), path_node, opt, &mut clip_p); } usvg::NodeKind::Group(ref g) => { clip_group(&node, g, opt, bbox, layers, &mut clip_p); } _ => {} } clip_p.set_transform(&ts); } clip_p.end(); if let Some(ref id) = cp.clip_path { if let Some(ref clip_node) = node.tree().defs_by_id(id) { if let usvg::NodeKind::ClipPath(ref cp) = *clip_node.borrow() { clip(clip_node, cp, opt, bbox, layers, p); } } } p.set_transform(&qt::Transform::default()); p.set_composition_mode(qt::CompositionMode::DestinationOut); p.draw_image(0.0, 0.0, &clip_img); } fn clip_group( node: &usvg::Node, g: &usvg::Group, opt: &Options, bbox: Rect, layers: &mut QtLayers, p: &mut qt::Painter, ) { if let Some(ref id) = g.clip_path { if let Some(ref clip_node) = node.tree().defs_by_id(id) { if let usvg::NodeKind::ClipPath(ref cp) = *clip_node.borrow() { // If a `clipPath` child also has a `clip-path` // then we should render this child on a new canvas, // clip it, and only then draw it to the `clipPath`. let clip_img = try_opt!(layers.get()); let mut clip_img = clip_img.borrow_mut(); let mut clip_p = qt::Painter::new(&mut clip_img); clip_p.set_transform(&p.get_transform()); draw_group_child(&node, opt, &mut clip_p); clip(clip_node, cp, opt, bbox, layers, &mut clip_p); clip_p.end(); p.set_transform(&qt::Transform::default()); p.set_composition_mode(qt::CompositionMode::Xor); p.draw_image(0.0, 0.0, &clip_img); } } } } fn draw_group_child( node: &usvg::Node, opt: &Options, p: &mut qt::Painter, ) { if let Some(child) = node.first_child() { p.apply_transform(&child.transform().to_native()); match *child.borrow() { usvg::NodeKind::Path(ref path_node) => { path::draw(&child.tree(), path_node, opt, p); } _ => {} } } } pub fn mask( node: &usvg::Node, mask: &usvg::Mask, opt: &Options, bbox: Rect, layers: &mut QtLayers, p: &mut qt::Painter, ) { let mask_img = try_opt!(layers.get()); let mut mask_img = mask_img.borrow_mut(); { let mut mask_p = qt::Painter::new(&mut mask_img); mask_p.set_transform(&p.get_transform()); let r = if mask.units == usvg::Units::ObjectBoundingBox { mask.rect.bbox_transform(bbox) } else { mask.rect }; mask_p.set_clip_rect(r.x(), r.y(), r.width(), r.height()); if mask.content_units == usvg::Units::ObjectBoundingBox { mask_p.apply_transform(&usvg::Transform::from_bbox(bbox).to_native()); } super::render_group(node, opt, layers, &mut mask_p); } use rgb::FromSlice; crate::image_to_mask(mask_img.data_mut().as_bgra_mut(), layers.image_size()); if let Some(ref id) = mask.mask { if let Some(ref mask_node) = node.tree().defs_by_id(id) { if let usvg::NodeKind::Mask(ref mask) = *mask_node.borrow() { self::mask(mask_node, mask, opt, bbox, layers, p); } } } p.set_transform(&qt::Transform::default()); p.set_composition_mode(qt::CompositionMode::DestinationIn); p.draw_image(0.0, 0.0, &mask_img); } resvg-0.8.0/src/backend_qt/filter.rs000066400000000000000000000343071352576375700174150ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::cmp; use std::rc::Rc; use crate::qt; use rgb::FromSlice; use log::warn; use usvg::ColorInterpolation as ColorSpace; use crate::prelude::*; use crate::filter::{self, Error, Filter, ImageExt}; use crate::ConvTransform; type Image = filter::Image; type FilterResult = filter::FilterResult; pub fn apply( filter: &usvg::Filter, bbox: Option, ts: &usvg::Transform, opt: &Options, canvas: &mut qt::Image, ) { QtFilter::apply(filter, bbox, ts, opt, canvas); } impl ImageExt for qt::Image { fn width(&self) -> u32 { self.width() } fn height(&self) -> u32 { self.height() } fn try_clone(&self) -> Result { self.try_clone().ok_or(Error::AllocFailed) } fn clip(&mut self, region: ScreenRect) { let mut brush = qt::Brush::new(); brush.set_color(0, 0, 0, 0); let mut p = qt::Painter::new(self); p.set_composition_mode(qt::CompositionMode::Clear); p.reset_pen(); p.set_brush(brush); p.draw_rect(0.0, 0.0, self.width() as f64, region.y() as f64); p.draw_rect(0.0, 0.0, region.x() as f64, self.height() as f64); p.draw_rect(region.right() as f64, 0.0, self.width() as f64, self.height() as f64); p.draw_rect(0.0, region.bottom() as f64, self.width() as f64, self.height() as f64); } fn clear(&mut self) { self.fill(0, 0, 0, 0); } fn into_srgb(&mut self) { for p in self.data_mut().as_rgba_mut() { p.r = filter::LINEAR_RGB_TO_SRGB_TABLE[p.r as usize]; p.g = filter::LINEAR_RGB_TO_SRGB_TABLE[p.g as usize]; p.b = filter::LINEAR_RGB_TO_SRGB_TABLE[p.b as usize]; } } fn into_linear_rgb(&mut self) { for p in self.data_mut().as_rgba_mut() { p.r = filter::SRGB_TO_LINEAR_RGB_TABLE[p.r as usize]; p.g = filter::SRGB_TO_LINEAR_RGB_TABLE[p.g as usize]; p.b = filter::SRGB_TO_LINEAR_RGB_TABLE[p.b as usize]; } } } fn create_image(width: u32, height: u32) -> Result { let mut image = qt::Image::new_rgba(width, height).ok_or(Error::AllocFailed)?; image.fill(0, 0, 0, 0); Ok(image) } fn copy_image(image: &qt::Image, region: ScreenRect) -> Result { let x = cmp::max(0, region.x()) as u32; let y = cmp::max(0, region.y()) as u32; image.copy(x, y, region.width(), region.height()).ok_or(Error::AllocFailed) } struct QtFilter; impl Filter for QtFilter { fn get_input( input: &usvg::FilterInput, region: ScreenRect, results: &[FilterResult], canvas: &qt::Image, ) -> Result { match input { usvg::FilterInput::SourceGraphic => { let image = copy_image(canvas, region)?; let image = image.to_rgba().ok_or(Error::AllocFailed)?; // TODO: optional Ok(Image { image: Rc::new(image), region: region.translate_to(0, 0), color_space: ColorSpace::SRGB, }) } usvg::FilterInput::SourceAlpha => { let image = copy_image(canvas, region)?; let mut image = image.to_rgba().ok_or(Error::AllocFailed)?; // Set RGB to black. Keep alpha as is. for p in image.data_mut().chunks_mut(4) { p[0] = 0; p[1] = 0; p[2] = 0; } Ok(Image { image: Rc::new(image), region: region.translate_to(0, 0), color_space: ColorSpace::SRGB, }) } usvg::FilterInput::Reference(ref name) => { if let Some(ref v) = results.iter().rev().find(|v| v.name == *name) { Ok(v.image.clone()) } else { // Technically unreachable. warn!("Unknown filter primitive reference '{}'.", name); Self::get_input(&usvg::FilterInput::SourceGraphic, region, results, canvas) } } _ => { warn!("Filter input '{:?}' is not supported.", input); Self::get_input(&usvg::FilterInput::SourceGraphic, region, results, canvas) } } } fn apply_blur( fe: &usvg::FeGaussianBlur, units: usvg::Units, cs: ColorSpace, bbox: Option, ts: &usvg::Transform, input: Image, ) -> Result { let (std_dx, std_dy, box_blur) = try_opt_or!(Self::resolve_std_dev(fe, units, bbox, ts), Ok(input)); let input = input.into_color_space(cs)?; let mut buffer = input.take()?; let (w, h) = (buffer.width(), buffer.height()); filter::into_premultiplied(buffer.data_mut().as_bgra_mut()); if box_blur { filter::box_blur::apply(&mut buffer.data_mut(), w, h, std_dx, std_dy); } else { filter::iir_blur::apply(&mut buffer.data_mut(), w, h, std_dx, std_dy); } filter::from_premultiplied(buffer.data_mut().as_bgra_mut()); Ok(Image::from_image(buffer, cs)) } fn apply_offset( fe: &usvg::FeOffset, units: usvg::Units, bbox: Option, ts: &usvg::Transform, input: Image, ) -> Result { let (dx, dy) = try_opt_or!(Self::resolve_offset(fe, units, bbox, ts), Ok(input)); // TODO: do not use an additional buffer let mut buffer = create_image(input.width(), input.height())?; let mut p = qt::Painter::new(&mut buffer); // TODO: fractional doesn't work p.draw_image(dx, dy, input.as_ref()); Ok(Image::from_image(buffer, input.color_space)) } fn apply_blend( fe: &usvg::FeBlend, cs: ColorSpace, region: ScreenRect, input1: Image, input2: Image, ) -> Result { let input1 = input1.into_color_space(cs)?; let input2 = input2.into_color_space(cs)?; let mut buffer = create_image(region.width(), region.height())?; let mut p = qt::Painter::new(&mut buffer); p.draw_image(0.0, 0.0, input2.as_ref()); let qt_mode = match fe.mode { usvg::FeBlendMode::Normal => qt::CompositionMode::SourceOver, usvg::FeBlendMode::Multiply => qt::CompositionMode::Multiply, usvg::FeBlendMode::Screen => qt::CompositionMode::Screen, usvg::FeBlendMode::Darken => qt::CompositionMode::Darken, usvg::FeBlendMode::Lighten => qt::CompositionMode::Lighten, }; p.set_composition_mode(qt_mode); p.draw_image(0.0, 0.0, input1.as_ref()); Ok(Image::from_image(buffer, cs)) } fn apply_composite( fe: &usvg::FeComposite, cs: ColorSpace, region: ScreenRect, input1: Image, input2: Image, ) -> Result { use rgb::RGBA8; use usvg::FeCompositeOperator as Operator; let input1 = input1.into_color_space(cs)?; let input2 = input2.into_color_space(cs)?; let mut buffer = create_image(region.width(), region.height())?; if let Operator::Arithmetic { k1, k2, k3, k4 } = fe.operator { fn premultiply_alpha(c: RGBA8) -> RGBA8 { let a = c.a as f64 / 255.0; let b = (c.b as f64 * a + 0.5) as u8; let g = (c.g as f64 * a + 0.5) as u8; let r = (c.r as f64 * a + 0.5) as u8; RGBA8 { r, g, b, a: c.a } } fn unmultiply_alpha(c: RGBA8) -> RGBA8 { let a = c.a as f64 / 255.0; let b = (c.b as f64 / a + 0.5) as u8; let g = (c.g as f64 / a + 0.5) as u8; let r = (c.r as f64 / a + 0.5) as u8; RGBA8 { r, g, b, a: c.a } } let data1 = input1.as_ref().data(); let data2 = input2.as_ref().data(); let calc = |i1, i2, max| { let i1 = i1 as f64 / 255.0; let i2 = i2 as f64 / 255.0; let result = k1.value() * i1 * i2 + k2.value() * i1 + k3.value() * i2 + k4.value(); f64_bound(0.0, result, max) }; { let mut i = 0; let mut data3 = buffer.data_mut(); let data3 = data3.as_rgba_mut(); for (c1, c2) in data1.as_rgba().iter().zip(data2.as_rgba()) { let c1 = premultiply_alpha(*c1); let c2 = premultiply_alpha(*c2); let a = calc(c1.a, c2.a, 1.0); if a.is_fuzzy_zero() { continue; } let r = (calc(c1.r, c2.r, a) * 255.0) as u8; let g = (calc(c1.g, c2.g, a) * 255.0) as u8; let b = (calc(c1.b, c2.b, a) * 255.0) as u8; let a = (a * 255.0) as u8; data3[i] = unmultiply_alpha(RGBA8 { r, g, b, a }); i += 1; } } return Ok(Image::from_image(buffer, cs)); } let mut p = qt::Painter::new(&mut buffer); p.draw_image(0.0, 0.0, input2.as_ref()); let qt_mode = match fe.operator { Operator::Over => qt::CompositionMode::SourceOver, Operator::In => qt::CompositionMode::SourceIn, Operator::Out => qt::CompositionMode::SourceOut, Operator::Atop => qt::CompositionMode::SourceAtop, Operator::Xor => qt::CompositionMode::Xor, Operator::Arithmetic { .. } => qt::CompositionMode::SourceOver, }; p.set_composition_mode(qt_mode); p.draw_image(0.0, 0.0, input1.as_ref()); Ok(Image::from_image(buffer, cs)) } fn apply_merge( fe: &usvg::FeMerge, cs: ColorSpace, region: ScreenRect, results: &[FilterResult], canvas: &qt::Image, ) -> Result { let mut buffer = create_image(region.width(), region.height())?; let mut p = qt::Painter::new(&mut buffer); for input in &fe.inputs { let input = Self::get_input(input, region, &results, canvas)?; let input = input.into_color_space(cs)?; p.draw_image(0.0, 0.0, input.as_ref()); } Ok(Image::from_image(buffer, cs)) } fn apply_flood( fe: &usvg::FeFlood, region: ScreenRect, ) -> Result { let c = fe.color; let alpha = f64_bound(0.0, fe.opacity.value() * 255.0, 255.0) as u8; let mut buffer = create_image(region.width(), region.height())?; buffer.fill(c.red, c.green, c.blue, alpha); Ok(Image::from_image(buffer, ColorSpace::SRGB)) } fn apply_tile( input: Image, region: ScreenRect, ) -> Result { let mut buffer = create_image(region.width(), region.height())?; let subregion = input.region.translate(-region.x(), -region.y()); let mut brush = qt::Brush::new(); brush.set_pattern(copy_image(&input.image, subregion)?); let brush_ts = usvg::Transform::new_translate(subregion.x() as f64, subregion.y() as f64); brush.set_transform(brush_ts.to_native()); let mut p = qt::Painter::new(&mut buffer); p.reset_pen(); p.set_brush(brush); p.draw_rect(0.0, 0.0, region.width() as f64, region.height() as f64); Ok(Image::from_image(buffer, ColorSpace::SRGB)) } fn apply_image( fe: &usvg::FeImage, region: ScreenRect, subregion: ScreenRect, opt: &Options, ) -> Result { let mut buffer = create_image(region.width(), region.height())?; match fe.data { usvg::FeImageKind::None => {} usvg::FeImageKind::Image(ref data, format) => { let mut p = qt::Painter::new(&mut buffer); let dx = (subregion.x() - region.x()) as f64; let dy = (subregion.y() - region.y()) as f64; p.translate(dx, dy); let view_box = usvg::ViewBox { rect: subregion.translate_to(0, 0).to_rect(), aspect: fe.aspect, }; if format == usvg::ImageFormat::SVG { super::image::draw_svg(data, view_box, opt, &mut p); } else { super::image::draw_raster( format, data, view_box, fe.rendering_mode, opt, &mut p ); } } usvg::FeImageKind::Use(..) => {} } Ok(Image::from_image(buffer, ColorSpace::SRGB)) } fn apply_component_transfer( fe: &usvg::FeComponentTransfer, cs: ColorSpace, input: Image, ) -> Result { let input = input.into_color_space(cs)?; let mut buffer = input.take()?; for pixel in buffer.data_mut().as_bgra_mut() { pixel.r = fe.func_r.apply(pixel.r); pixel.g = fe.func_g.apply(pixel.g); pixel.b = fe.func_b.apply(pixel.b); pixel.a = fe.func_a.apply(pixel.a); } Ok(Image::from_image(buffer, cs)) } fn apply_color_matrix( fe: &usvg::FeColorMatrix, cs: ColorSpace, input: Image, ) -> Result { let input = input.into_color_space(cs)?; let mut buffer = input.take()?; filter::color_matrix::apply(&fe.kind, buffer.data_mut().as_bgra_mut()); Ok(Image::from_image(buffer, cs)) } fn apply_to_canvas( input: Image, region: ScreenRect, canvas: &mut qt::Image, ) -> Result<(), Error> { let input = input.into_color_space(ColorSpace::SRGB)?; // Clear. canvas.fill(0, 0, 0, 0); let mut p = qt::Painter::new(canvas); p.draw_image(region.x() as f64, region.y() as f64, input.as_ref()); Ok(()) } } resvg-0.8.0/src/backend_qt/image.rs000066400000000000000000000057011352576375700172060ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::qt; use crate::prelude::*; use crate::image; use crate::ConvTransform; pub fn draw( image: &usvg::Image, opt: &Options, p: &mut qt::Painter, ) -> Rect { if image.visibility != usvg::Visibility::Visible { return image.view_box.rect; } if image.format == usvg::ImageFormat::SVG { draw_svg(&image.data, image.view_box, opt, p); } else { draw_raster(image.format, &image.data, image.view_box, image.rendering_mode, opt, p); } image.view_box.rect } pub fn draw_raster( format: usvg::ImageFormat, data: &usvg::ImageData, view_box: usvg::ViewBox, rendering_mode: usvg::ImageRendering, opt: &Options, p: &mut qt::Painter, ) { let img = try_opt!(image::load_raster(format, data, opt)); let image = { let (w, h) = img.size.dimensions(); let mut image = try_opt_warn_or!( qt::Image::new_rgba(w, h), (), "Failed to create a {}x{} image.", w, h ); image_to_surface(&img, &mut image.data_mut()); image }; if rendering_mode == usvg::ImageRendering::OptimizeSpeed { p.set_smooth_pixmap_transform(false); } if view_box.aspect.slice { let r = view_box.rect; p.set_clip_rect(r.x(), r.y(), r.width(), r.height()); } let r = image::image_rect(&view_box, img.size); p.draw_image_rect(r.x(), r.y(), r.width(), r.height(), &image); // Revert. p.set_smooth_pixmap_transform(true); p.reset_clip_path(); } fn image_to_surface(image: &image::Image, surface: &mut [u8]) { // Surface is always ARGB. const SURFACE_CHANNELS: usize = 4; use crate::image::ImageData; use rgb::FromSlice; let mut i = 0; let mut to_surface = |r, g, b, a| { surface[i + 0] = b; surface[i + 1] = g; surface[i + 2] = r; surface[i + 3] = a; i += SURFACE_CHANNELS; }; match &image.data { ImageData::RGB(data) => { for p in data.as_rgb() { to_surface(p.r, p.g, p.b, 255); } } ImageData::RGBA(data) => { for p in data.as_rgba() { to_surface(p.r, p.g, p.b, p.a); } } } } pub fn draw_svg( data: &usvg::ImageData, view_box: usvg::ViewBox, opt: &Options, p: &mut qt::Painter, ) { let (tree, sub_opt) = try_opt!(image::load_sub_svg(data, opt)); let img_size = tree.svg_node().size.to_screen_size(); let (ts, clip) = image::prepare_sub_svg_geom(view_box, img_size); if let Some(clip) = clip { p.set_clip_rect(clip.x(), clip.y(), clip.width(), clip.height()); } p.apply_transform(&ts.to_native()); super::render_to_canvas(&tree, &sub_opt, img_size, p); p.reset_clip_path(); } resvg-0.8.0/src/backend_qt/mod.rs000066400000000000000000000177301352576375700167100ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. //! Qt backend implementation. use crate::qt; use log::warn; use crate::{prelude::*, layers, ConvTransform}; macro_rules! try_create_image { ($size:expr, $ret:expr) => { try_opt_warn_or!( qt::Image::new_rgba_premultiplied($size.width(), $size.height()), $ret, "Failed to create a {}x{} image.", $size.width(), $size.height() ); }; } mod clip_and_mask; mod filter; mod image; mod path; mod style; type QtLayers = layers::Layers; impl ConvTransform for usvg::Transform { fn to_native(&self) -> qt::Transform { qt::Transform::new(self.a, self.b, self.c, self.d, self.e, self.f) } fn from_native(ts: &qt::Transform) -> Self { let d = ts.data(); Self::new(d.0, d.1, d.2, d.3, d.4, d.5) } } /// Cairo backend handle. #[derive(Clone, Copy)] pub struct Backend; impl Render for Backend { fn render_to_image( &self, tree: &usvg::Tree, opt: &Options, ) -> Option> { let img = render_to_image(tree, opt)?; Some(Box::new(img)) } fn render_node_to_image( &self, node: &usvg::Node, opt: &Options, ) -> Option> { let img = render_node_to_image(node, opt)?; Some(Box::new(img)) } } impl OutputImage for qt::Image { fn save_png( &mut self, path: &std::path::Path, ) -> bool { self.save(path.to_str().unwrap()) } } /// Renders SVG to image. pub fn render_to_image( tree: &usvg::Tree, opt: &Options, ) -> Option { let (mut img, img_size) = create_root_image(tree.svg_node().size.to_screen_size(), opt)?; let mut painter = qt::Painter::new(&mut img); render_to_canvas(tree, opt, img_size, &mut painter); painter.end(); Some(img) } /// Renders SVG node to image. pub fn render_node_to_image( node: &usvg::Node, opt: &Options, ) -> Option { let node_bbox = if let Some(bbox) = node.calculate_bbox() { bbox } else { warn!("Node '{}' has zero size.", node.id()); return None; }; let vbox = usvg::ViewBox { rect: node_bbox, aspect: usvg::AspectRatio::default(), }; let (mut img, img_size) = create_root_image(node_bbox.size().to_screen_size(), opt)?; let mut painter = qt::Painter::new(&mut img); render_node_to_canvas(node, opt, vbox, img_size, &mut painter); painter.end(); Some(img) } /// Renders SVG to canvas. pub fn render_to_canvas( tree: &usvg::Tree, opt: &Options, img_size: ScreenSize, painter: &mut qt::Painter, ) { render_node_to_canvas(&tree.root(), opt, tree.svg_node().view_box, img_size, painter); } /// Renders SVG node to canvas. pub fn render_node_to_canvas( node: &usvg::Node, opt: &Options, view_box: usvg::ViewBox, img_size: ScreenSize, painter: &mut qt::Painter, ) { let mut layers = create_layers(img_size); apply_viewbox_transform(view_box, img_size, painter); let curr_ts = painter.get_transform(); let mut ts = node.abs_transform(); ts.append(&node.transform()); painter.apply_transform(&ts.to_native()); render_node(node, opt, &mut layers, painter); painter.set_transform(&curr_ts); } fn create_root_image( size: ScreenSize, opt: &Options, ) -> Option<(qt::Image, ScreenSize)> { let img_size = utils::fit_to(size, opt.fit_to)?; let mut img = try_create_image!(img_size, None); // Fill background. if let Some(c) = opt.background { img.fill(c.red, c.green, c.blue, 255); } else { img.fill(0, 0, 0, 0); } Some((img, img_size)) } /// Applies viewbox transformation to the painter. fn apply_viewbox_transform( view_box: usvg::ViewBox, img_size: ScreenSize, painter: &mut qt::Painter, ) { let ts = utils::view_box_to_transform(view_box.rect, view_box.aspect, img_size.to_size()); painter.apply_transform(&ts.to_native()); } fn render_node( node: &usvg::Node, opt: &Options, layers: &mut QtLayers, p: &mut qt::Painter, ) -> Option { match *node.borrow() { usvg::NodeKind::Svg(_) => { render_group(node, opt, layers, p) } usvg::NodeKind::Path(ref path) => { path::draw(&node.tree(), path, opt, p) } usvg::NodeKind::Image(ref img) => { Some(image::draw(img, opt, p)) } usvg::NodeKind::Group(ref g) => { render_group_impl(node, g, opt, layers, p) } _ => None, } } fn render_group( parent: &usvg::Node, opt: &Options, layers: &mut QtLayers, p: &mut qt::Painter, ) -> Option { let curr_ts = p.get_transform(); let mut g_bbox = Rect::new_bbox(); for node in parent.children() { p.apply_transform(&node.transform().to_native()); let bbox = render_node(&node, opt, layers, p); if let Some(bbox) = bbox { if let Some(bbox) = bbox.transform(&node.transform()) { g_bbox = g_bbox.expand(bbox); } } // Revert transform. p.set_transform(&curr_ts); } // Check that bbox was changed, otherwise we will have a rect with x/y set to f64::MAX. if g_bbox.fuzzy_ne(&Rect::new_bbox()) { Some(g_bbox) } else { None } } fn render_group_impl( node: &usvg::Node, g: &usvg::Group, opt: &Options, layers: &mut QtLayers, p: &mut qt::Painter, ) -> Option { let sub_img = layers.get()?; let mut sub_img = sub_img.borrow_mut(); let curr_ts = p.get_transform(); let bbox = { let mut sub_p = qt::Painter::new(&mut sub_img); sub_p.set_transform(&curr_ts); render_group(node, opt, layers, &mut sub_p) }; // Filter can be rendered on an object without a bbox, // as long as filter uses `userSpaceOnUse`. if let Some(ref id) = g.filter { if let Some(filter_node) = node.tree().defs_by_id(id) { if let usvg::NodeKind::Filter(ref filter) = *filter_node.borrow() { let ts = usvg::Transform::from_native(&curr_ts); filter::apply(filter, bbox, &ts, opt, &mut sub_img); } } } // Clipping and masking can be done only for objects with a valid bbox. if let Some(bbox) = bbox { if let Some(ref id) = g.clip_path { if let Some(clip_node) = node.tree().defs_by_id(id) { if let usvg::NodeKind::ClipPath(ref cp) = *clip_node.borrow() { let mut sub_p = qt::Painter::new(&mut sub_img); sub_p.set_transform(&curr_ts); clip_and_mask::clip(&clip_node, cp, opt, bbox, layers, &mut sub_p); } } } if let Some(ref id) = g.mask { if let Some(mask_node) = node.tree().defs_by_id(id) { if let usvg::NodeKind::Mask(ref mask) = *mask_node.borrow() { let mut sub_p = qt::Painter::new(&mut sub_img); sub_p.set_transform(&curr_ts); clip_and_mask::mask(&mask_node, mask, opt, bbox, layers, &mut sub_p); } } } } if !g.opacity.is_default() { p.set_opacity(g.opacity.value()); } let curr_ts = p.get_transform(); p.set_transform(&qt::Transform::default()); p.draw_image(0.0, 0.0, &sub_img); p.set_opacity(1.0); p.set_transform(&curr_ts); bbox } fn create_layers( img_size: ScreenSize, ) -> QtLayers { layers::Layers::new(img_size, create_subimage, clear_image) } fn create_subimage( size: ScreenSize, ) -> Option { let mut img = try_create_image!(size, None); img.fill(0, 0, 0, 0); Some(img) } fn clear_image(img: &mut qt::Image) { img.fill(0, 0, 0, 0); } resvg-0.8.0/src/backend_qt/path.rs000066400000000000000000000104401352576375700170540ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::qt; use crate::prelude::*; use super::style; pub fn draw( tree: &usvg::Tree, path: &usvg::Path, opt: &Options, p: &mut qt::Painter, ) -> Option { let bbox = path.data.bbox(); if path.visibility != usvg::Visibility::Visible { return bbox; } let fill_rule = if let Some(ref fill) = path.fill { fill.rule } else { usvg::FillRule::NonZero }; let new_path = convert_path(&path.data, fill_rule); // `usvg` guaranties that path without a bbox will not use // a paint server with ObjectBoundingBox, // so we can pass whatever rect we want, because it will not be used anyway. let style_bbox = bbox.unwrap_or_else(|| Rect::new(0.0, 0.0, 1.0, 1.0).unwrap()); style::fill(tree, &path.fill, opt, style_bbox, p); style::stroke(tree, &path.stroke, opt, style_bbox, p); p.set_antialiasing(crate::use_shape_antialiasing(path.rendering_mode)); p.draw_path(&new_path); // Revert anti-aliasing. p.set_antialiasing(true); bbox } fn convert_path( segments: &[usvg::PathSegment], rule: usvg::FillRule, ) -> qt::PainterPath { // Qt's QPainterPath automatically closes open subpaths if start and end positions are equal. // This is an incorrect behaviour according to the SVG. // So we have to shift the last segment a bit, to prevent such behaviour. // // 'A closed path has coinciding start and end points.' // https://doc.qt.io/qt-5/qpainterpath.html#details let mut new_path = qt::PainterPath::new(); let mut prev_mx = 0.0; let mut prev_my = 0.0; let mut prev_x = 0.0; let mut prev_y = 0.0; let len = segments.len(); let mut i = 0; while i < len { let ref seg1 = segments[i]; // Check that current segment is the last segment of the subpath. let is_last_subpath_seg = { if i == len - 1 { true } else { if let usvg::PathSegment::MoveTo { .. } = segments[i + 1] { true } else { false } } }; match *seg1 { usvg::PathSegment::MoveTo { x, y } => { new_path.move_to(x, y); // Remember subpath start position. prev_mx = x; prev_my = y; prev_x = x; prev_y = y; } usvg::PathSegment::LineTo { mut x, y } => { if is_last_subpath_seg { // No need to use fuzzy compare because Qt doesn't use it too. if x == prev_mx && y == prev_my { // We shift only the X coordinate because that's enough. x -= 0.000001; } } new_path.line_to(x, y); prev_x = x; prev_y = y; } usvg::PathSegment::CurveTo { x1, y1, x2, y2, mut x, y } => { if is_last_subpath_seg { if x == prev_mx && y == prev_my { x -= 0.000001; } } if is_line(prev_x, prev_y, x1, y1, x2, y2, x, y) { new_path.line_to(x, y); } else { new_path.curve_to(x1, y1, x2, y2, x, y); } prev_x = x; prev_y = y; } usvg::PathSegment::ClosePath => { new_path.close_path(); } } i += 1; } match rule { usvg::FillRule::NonZero => new_path.set_fill_rule(qt::FillRule::Winding), usvg::FillRule::EvenOdd => new_path.set_fill_rule(qt::FillRule::OddEven), } new_path } // If a CurveTo is approximately a LineTo than we should draw it as a LineTo, // otherwise Qt will draw it incorrectly. // // See QTBUG-72796 fn is_line(px: f64, py: f64, x1: f64, y1: f64, x2: f64, y2: f64, x: f64, y: f64) -> bool { (px - x1).abs() < 0.001 && (py - y1).abs() < 0.001 && (x2 - x).abs() < 0.001 && (y2 - y).abs() < 0.001 } resvg-0.8.0/src/backend_qt/style.rs000066400000000000000000000176461352576375700172770ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::qt; use crate::{prelude::*, ConvTransform}; pub fn fill( tree: &usvg::Tree, fill: &Option, opt: &Options, bbox: Rect, p: &mut qt::Painter, ) { match *fill { Some(ref fill) => { let mut brush = qt::Brush::new(); let opacity = fill.opacity; match fill.paint { usvg::Paint::Color(c) => { let a = f64_bound(0.0, opacity.value() * 255.0, 255.0) as u8; brush.set_color(c.red, c.green, c.blue, a); } usvg::Paint::Link(ref id) => { if let Some(node) = tree.defs_by_id(id) { match *node.borrow() { usvg::NodeKind::LinearGradient(ref lg) => { prepare_linear(lg, opacity, bbox, &mut brush); } usvg::NodeKind::RadialGradient(ref rg) => { prepare_radial(rg, opacity, bbox, &mut brush); } usvg::NodeKind::Pattern(ref pattern) => { let ts = p.get_transform(); prepare_pattern(&node, pattern, opt, ts, bbox, opacity, &mut brush); } _ => {} } } } } p.set_brush(brush); } None => { p.reset_brush(); } } } pub fn stroke( tree: &usvg::Tree, stroke: &Option, opt: &Options, bbox: Rect, p: &mut qt::Painter, ) { match *stroke { Some(ref stroke) => { let mut pen = qt::Pen::new(); let opacity = stroke.opacity; match stroke.paint { usvg::Paint::Color(c) => { let a = f64_bound(0.0, opacity.value() * 255.0, 255.0) as u8; pen.set_color(c.red, c.green, c.blue, a); } usvg::Paint::Link(ref id) => { let mut brush = qt::Brush::new(); if let Some(node) = tree.defs_by_id(id) { match *node.borrow() { usvg::NodeKind::LinearGradient(ref lg) => { prepare_linear(lg, opacity, bbox, &mut brush); } usvg::NodeKind::RadialGradient(ref rg) => { prepare_radial(rg, opacity, bbox, &mut brush); } usvg::NodeKind::Pattern(ref pattern) => { let ts = p.get_transform(); prepare_pattern(&node, pattern, opt, ts, bbox, opacity, &mut brush); } _ => {} } } pen.set_brush(brush); } } let linecap = match stroke.linecap { usvg::LineCap::Butt => qt::LineCap::Flat, usvg::LineCap::Round => qt::LineCap::Round, usvg::LineCap::Square => qt::LineCap::Square, }; pen.set_line_cap(linecap); let linejoin = match stroke.linejoin { usvg::LineJoin::Miter => qt::LineJoin::Miter, usvg::LineJoin::Round => qt::LineJoin::Round, usvg::LineJoin::Bevel => qt::LineJoin::Bevel, }; pen.set_line_join(linejoin); pen.set_miter_limit(stroke.miterlimit.value()); pen.set_width(stroke.width.value()); if let Some(ref list) = stroke.dasharray { pen.set_dash_offset(stroke.dashoffset as f64); pen.set_dash_array(list); } p.set_pen(pen); } None => { p.reset_pen(); } } } fn prepare_linear( g: &usvg::LinearGradient, opacity: usvg::Opacity, bbox: Rect, brush: &mut qt::Brush, ) { let mut grad = qt::LinearGradient::new(g.x1, g.y1, g.x2, g.y2); prepare_base_gradient(&g.base, opacity, &mut grad); brush.set_linear_gradient(grad); transform_gradient(&g.base, bbox, brush); } fn prepare_radial( g: &usvg::RadialGradient, opacity: usvg::Opacity, bbox: Rect, brush: &mut qt::Brush, ) { let mut grad = qt::RadialGradient::new(g.cx, g.cy, g.fx, g.fy, g.r.value()); prepare_base_gradient(&g.base, opacity, &mut grad); brush.set_radial_gradient(grad); transform_gradient(&g.base, bbox, brush); } fn prepare_base_gradient( g: &usvg::BaseGradient, opacity: usvg::Opacity, grad: &mut dyn qt::Gradient, ) { let spread_method = match g.spread_method { usvg::SpreadMethod::Pad => qt::Spread::Pad, usvg::SpreadMethod::Reflect => qt::Spread::Reflect, usvg::SpreadMethod::Repeat => qt::Spread::Repeat, }; grad.set_spread(spread_method); for stop in &g.stops { grad.set_color_at( stop.offset.value(), stop.color.red, stop.color.green, stop.color.blue, (stop.opacity.value() * opacity.value() * 255.0) as u8, ); } } fn transform_gradient( g: &usvg::BaseGradient, bbox: Rect, brush: &mut qt::Brush, ) { // We don't use `QGradient::setCoordinateMode` because it works incorrectly. // // See QTBUG-67995 if g.units == usvg::Units::ObjectBoundingBox { let mut ts = usvg::Transform::from_bbox(bbox); ts.append(&g.transform); brush.set_transform(ts.to_native()); } else { brush.set_transform(g.transform.to_native()); } } fn prepare_pattern( pattern_node: &usvg::Node, pattern: &usvg::Pattern, opt: &Options, global_ts: qt::Transform, bbox: Rect, opacity: usvg::Opacity, brush: &mut qt::Brush, ) { let r = if pattern.units == usvg::Units::ObjectBoundingBox { pattern.rect.bbox_transform(bbox) } else { pattern.rect }; let global_ts = usvg::Transform::from_native(&global_ts); let (sx, sy) = global_ts.get_scale(); let img_size = try_opt!(Size::new(r.width() * sx, r.height() * sy)).to_screen_size(); let mut img = try_create_image!(img_size, ()); img.fill(0, 0, 0, 0); let mut p = qt::Painter::new(&mut img); p.scale(sx, sy); if let Some(vbox) = pattern.view_box { let ts = utils::view_box_to_transform(vbox.rect, vbox.aspect, r.size()); p.apply_transform(&ts.to_native()); } else if pattern.content_units == usvg::Units::ObjectBoundingBox { // 'Note that this attribute has no effect if attribute `viewBox` is specified.' // We don't use Transform::from_bbox(bbox) because `x` and `y` should be // ignored for some reasons... p.scale(bbox.width(), bbox.height()); } let mut layers = super::create_layers(img_size); super::render_group(pattern_node, opt, &mut layers, &mut p); p.end(); let img = if !opacity.is_default() { // If `opacity` isn't `1` then we have to make image semitransparent. // The only way to do this is by making a new image and rendering // the pattern on it with transparency. let mut img2 = try_create_image!(img_size, ()); img2.fill(0, 0, 0, 0); let mut p2 = qt::Painter::new(&mut img2); p2.set_opacity(opacity.value()); p2.draw_image(0.0, 0.0, &img); p2.end(); img2 } else { img }; brush.set_pattern(img); let mut ts = usvg::Transform::default(); ts.append(&pattern.transform); ts.translate(r.x(), r.y()); ts.scale(1.0 / sx, 1.0 / sy); brush.set_transform(ts.to_native()); } resvg-0.8.0/src/backend_raqote/000077500000000000000000000000001352576375700164225ustar00rootroot00000000000000resvg-0.8.0/src/backend_raqote/clip_and_mask.rs000066400000000000000000000120351352576375700215550ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::{prelude::*, ConvTransform}; use super::{path, RaqoteLayers, RaqoteDrawTargetExt}; pub fn clip( node: &usvg::Node, cp: &usvg::ClipPath, opt: &Options, bbox: Rect, layers: &mut RaqoteLayers, dt: &mut raqote::DrawTarget, ) { let clip_dt = try_opt!(layers.get()); let mut clip_dt = clip_dt.borrow_mut(); clip_dt.clear(raqote::SolidSource { r: 0, g: 0, b: 0, a: 255 }); clip_dt.set_transform(dt.get_transform()); clip_dt.transform(&cp.transform.to_native()); if cp.units == usvg::Units::ObjectBoundingBox { clip_dt.transform(&usvg::Transform::from_bbox(bbox).to_native()); } let ts = *clip_dt.get_transform(); for node in node.children() { clip_dt.transform(&node.transform().to_native()); match *node.borrow() { usvg::NodeKind::Path(ref p) => { let draw_opt = raqote::DrawOptions { blend_mode: raqote::BlendMode::Clear, ..Default::default() }; path::draw(&node.tree(), p, opt, draw_opt, &mut clip_dt); } usvg::NodeKind::Group(ref g) => { clip_group(&node, g, opt, bbox, layers, &mut clip_dt); } _ => {} } clip_dt.set_transform(&ts); } if let Some(ref id) = cp.clip_path { if let Some(ref clip_node) = node.tree().defs_by_id(id) { if let usvg::NodeKind::ClipPath(ref cp) = *clip_node.borrow() { clip(clip_node, cp, opt, bbox, layers, dt); } } } dt.set_transform(&raqote::Transform::identity()); dt.draw_image_at(0.0, 0.0, &clip_dt.as_image(), &raqote::DrawOptions { blend_mode: raqote::BlendMode::DstOut, ..Default::default() }); } fn clip_group( node: &usvg::Node, g: &usvg::Group, opt: &Options, bbox: Rect, layers: &mut RaqoteLayers, dt: &mut raqote::DrawTarget, ) { if let Some(ref id) = g.clip_path { if let Some(ref clip_node) = node.tree().defs_by_id(id) { if let usvg::NodeKind::ClipPath(ref cp) = *clip_node.borrow() { // If a `clipPath` child also has a `clip-path` // then we should render this child on a new canvas, // clip it, and only then draw it to the `clipPath`. let clip_dt = try_opt!(layers.get()); let mut clip_dt = clip_dt.borrow_mut(); clip_dt.set_transform(dt.get_transform()); draw_group_child(&node, opt, raqote::DrawOptions::default(), &mut clip_dt); clip(clip_node, cp, opt, bbox, layers, &mut clip_dt); dt.set_transform(&raqote::Transform::identity()); dt.draw_image_at(0.0, 0.0, &clip_dt.as_image(), &raqote::DrawOptions { blend_mode: raqote::BlendMode::Xor, ..Default::default() }); } } } } fn draw_group_child( node: &usvg::Node, opt: &Options, draw_options: raqote::DrawOptions, dt: &mut raqote::DrawTarget, ) { if let Some(child) = node.first_child() { dt.transform(&child.transform().to_native()); match *child.borrow() { usvg::NodeKind::Path(ref path_node) => { path::draw(&child.tree(), path_node, opt, draw_options, dt); } _ => {} } } } pub fn mask( node: &usvg::Node, mask: &usvg::Mask, opt: &Options, bbox: Rect, layers: &mut RaqoteLayers, dt: &mut raqote::DrawTarget, ) { let mask_dt = try_opt!(layers.get()); let mut mask_dt = mask_dt.borrow_mut(); { mask_dt.set_transform(dt.get_transform()); let r = if mask.units == usvg::Units::ObjectBoundingBox { mask.rect.bbox_transform(bbox) } else { mask.rect }; let mut pb = raqote::PathBuilder::new(); pb.rect(r.x() as f32, r.y() as f32, r.width() as f32, r.height() as f32); mask_dt.push_clip(&pb.finish()); if mask.content_units == usvg::Units::ObjectBoundingBox { mask_dt.transform(&usvg::Transform::from_bbox(bbox).to_native()); } super::render_group(node, opt, layers, &mut mask_dt); mask_dt.pop_clip(); } use rgb::FromSlice; crate::image_to_mask(mask_dt.get_data_u8_mut().as_bgra_mut(), layers.image_size()); if let Some(ref id) = mask.mask { if let Some(ref mask_node) = node.tree().defs_by_id(id) { if let usvg::NodeKind::Mask(ref mask) = *mask_node.borrow() { self::mask(mask_node, mask, opt, bbox, layers, dt); } } } dt.set_transform(&raqote::Transform::identity()); dt.draw_image_at(0.0, 0.0, &mask_dt.as_image(), &raqote::DrawOptions { blend_mode: raqote::BlendMode::DstIn, ..Default::default() }); } resvg-0.8.0/src/backend_raqote/filter.rs000066400000000000000000000363321352576375700202640ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::cmp; use std::rc::Rc; use rgb::FromSlice; use log::warn; use usvg::ColorInterpolation as ColorSpace; use crate::prelude::*; use crate::filter::{self, Error, Filter, ImageExt}; use crate::ConvTransform; use super::{ColorExt, RaqoteDrawTargetExt}; type Image = filter::Image; type FilterResult = filter::FilterResult; pub fn apply( filter: &usvg::Filter, bbox: Option, ts: &usvg::Transform, opt: &Options, canvas: &mut raqote::DrawTarget, ) { RaqoteFilter::apply(filter, bbox, ts, opt, canvas); } impl ImageExt for raqote::DrawTarget { fn width(&self) -> u32 { self.width() as u32 } fn height(&self) -> u32 { self.height() as u32 } fn try_clone(&self) -> Result { let mut dt = raqote::DrawTarget::new(self.width(), self.height()); dt.draw_image_at(0.0, 0.0, &self.as_image(), &raqote::DrawOptions { blend_mode: raqote::BlendMode::Src, ..raqote::DrawOptions::default() }); Ok(dt) } fn clip(&mut self, region: ScreenRect) { let mut pb = raqote::PathBuilder::new(); pb.rect(0.0, 0.0, self.width() as f32, region.y() as f32); pb.rect(0.0, 0.0, region.x() as f32, self.height() as f32); pb.rect(region.right() as f32, 0.0, self.width() as f32, self.height() as f32); pb.rect(0.0, region.bottom() as f32, self.width() as f32, self.height() as f32); self.fill(&pb.finish(), &raqote::Source::Solid(raqote::SolidSource { r: 0, g: 0, b: 0, a: 0, }), &raqote::DrawOptions { blend_mode: raqote::BlendMode::Clear, ..Default::default() }); } fn clear(&mut self) { self.make_transparent(); } fn into_srgb(&mut self) { let data = self.get_data_u8_mut(); filter::from_premultiplied(data.as_bgra_mut()); for p in data.as_bgra_mut() { p.r = filter::LINEAR_RGB_TO_SRGB_TABLE[p.r as usize]; p.g = filter::LINEAR_RGB_TO_SRGB_TABLE[p.g as usize]; p.b = filter::LINEAR_RGB_TO_SRGB_TABLE[p.b as usize]; } filter::into_premultiplied(data.as_bgra_mut()); } fn into_linear_rgb(&mut self) { let data = self.get_data_u8_mut(); filter::from_premultiplied(data.as_bgra_mut()); for p in data.as_bgra_mut() { p.r = filter::SRGB_TO_LINEAR_RGB_TABLE[p.r as usize]; p.g = filter::SRGB_TO_LINEAR_RGB_TABLE[p.g as usize]; p.b = filter::SRGB_TO_LINEAR_RGB_TABLE[p.b as usize]; } filter::into_premultiplied(data.as_bgra_mut()); } } fn create_image(width: u32, height: u32) -> Result { Ok(raqote::DrawTarget::new(width as i32, height as i32)) } fn copy_image( image: &raqote::DrawTarget, region: ScreenRect, ) -> Result { let x = cmp::max(0, region.x()) as f32; let y = cmp::max(0, region.y()) as f32; let mut new_image = create_image(region.width(), region.height())?; new_image.draw_image_at(-x, -y, &image.as_image(), &raqote::DrawOptions { blend_mode: raqote::BlendMode::Src, ..raqote::DrawOptions::default() }); Ok(new_image) } struct RaqoteFilter; impl Filter for RaqoteFilter { fn get_input( input: &usvg::FilterInput, region: ScreenRect, results: &[FilterResult], canvas: &raqote::DrawTarget, ) -> Result { match input { usvg::FilterInput::SourceGraphic => { let image = copy_image(canvas, region)?; Ok(Image { image: Rc::new(image), region: region.translate_to(0, 0), color_space: ColorSpace::SRGB, }) } usvg::FilterInput::SourceAlpha => { let mut image = copy_image(canvas, region)?; // Set RGB to black. Keep alpha as is. let data = image.get_data_u8_mut(); for p in data.chunks_mut(4) { p[0] = 0; p[1] = 0; p[2] = 0; } Ok(Image { image: Rc::new(image), region: region.translate_to(0, 0), color_space: ColorSpace::SRGB, }) } usvg::FilterInput::Reference(ref name) => { if let Some(ref v) = results.iter().rev().find(|v| v.name == *name) { Ok(v.image.clone()) } else { // Technically unreachable. warn!("Unknown filter primitive reference '{}'.", name); Self::get_input(&usvg::FilterInput::SourceGraphic, region, results, canvas) } } _ => { warn!("Filter input '{:?}' is not supported.", input); Self::get_input(&usvg::FilterInput::SourceGraphic, region, results, canvas) } } } fn apply_blur( fe: &usvg::FeGaussianBlur, units: usvg::Units, cs: ColorSpace, bbox: Option, ts: &usvg::Transform, input: Image, ) -> Result { let (std_dx, std_dy, box_blur) = try_opt_or!(Self::resolve_std_dev(fe, units, bbox, ts), Ok(input)); let input = input.into_color_space(cs)?; let mut buffer = input.take()?; let (w, h) = (buffer.width() as u32, buffer.height() as u32); let data = buffer.get_data_u8_mut(); if box_blur { filter::box_blur::apply(data, w, h, std_dx, std_dy); } else { filter::iir_blur::apply(data, w, h, std_dx, std_dy); } Ok(Image::from_image(buffer, cs)) } fn apply_offset( fe: &usvg::FeOffset, units: usvg::Units, bbox: Option, ts: &usvg::Transform, input: Image, ) -> Result { let (dx, dy) = try_opt_or!(Self::resolve_offset(fe, units, bbox, ts), Ok(input)); // TODO: do not use an additional buffer let mut dt = create_image(input.width(), input.height())?; dt.draw_image_at( dx as f32, dy as f32, &input.as_ref().as_image(), &raqote::DrawOptions::default(), ); Ok(Image::from_image(dt, input.color_space)) } fn apply_blend( fe: &usvg::FeBlend, cs: ColorSpace, region: ScreenRect, input1: Image, input2: Image, ) -> Result { let input1 = input1.into_color_space(cs)?; let input2 = input2.into_color_space(cs)?; let mut dt = create_image(region.width(), region.height())?; let draw_opt = raqote::DrawOptions { blend_mode: raqote::BlendMode::Src, ..raqote::DrawOptions::default() }; dt.draw_image_at(0.0, 0.0, &input2.as_ref().as_image(), &draw_opt); let blend_mode = match fe.mode { usvg::FeBlendMode::Normal => raqote::BlendMode::SrcOver, usvg::FeBlendMode::Multiply => raqote::BlendMode::Multiply, usvg::FeBlendMode::Screen => raqote::BlendMode::Screen, usvg::FeBlendMode::Darken => raqote::BlendMode::Darken, usvg::FeBlendMode::Lighten => raqote::BlendMode::Lighten, }; let draw_opt = raqote::DrawOptions { blend_mode, ..raqote::DrawOptions::default() }; dt.draw_image_at(0.0, 0.0, &input1.as_ref().as_image(), &draw_opt); Ok(Image::from_image(dt, cs)) } fn apply_composite( fe: &usvg::FeComposite, cs: ColorSpace, region: ScreenRect, input1: Image, input2: Image, ) -> Result { use rgb::alt::BGRA8; let mut input1 = input1.into_color_space(cs)?.take()?; let mut input2 = input2.into_color_space(cs)?.take()?; let mut dt = create_image(region.width(), region.height())?; if let Operator::Arithmetic { k1, k2, k3, k4 } = fe.operator { let data1 = input1.get_data_u8_mut(); let data2 = input2.get_data_u8_mut(); let calc = |i1, i2, max| { let i1 = i1 as f64 / 255.0; let i2 = i2 as f64 / 255.0; let result = k1.value() * i1 * i2 + k2.value() * i1 + k3.value() * i2 + k4.value(); f64_bound(0.0, result, max) }; { let mut i = 0; let data3 = dt.get_data_u8_mut(); let data3 = data3.as_bgra_mut(); for (c1, c2) in data1.as_bgra().iter().zip(data2.as_bgra()) { let a = calc(c1.a, c2.a, 1.0); if a.is_fuzzy_zero() { continue; } let r = (calc(c1.r, c2.r, a) * 255.0) as u8; let g = (calc(c1.g, c2.g, a) * 255.0) as u8; let b = (calc(c1.b, c2.b, a) * 255.0) as u8; let a = (a * 255.0) as u8; data3[i] = BGRA8 { r, g, b, a }; i += 1; } } return Ok(Image::from_image(dt, cs)); } let draw_opt = raqote::DrawOptions { blend_mode: raqote::BlendMode::Src, ..raqote::DrawOptions::default() }; dt.draw_image_at(0.0, 0.0, &input2.as_image(), &draw_opt); use usvg::FeCompositeOperator as Operator; let blend_mode = match fe.operator { Operator::Over => raqote::BlendMode::SrcOver, Operator::In => raqote::BlendMode::SrcIn, Operator::Out => raqote::BlendMode::SrcOut, Operator::Atop => raqote::BlendMode::SrcAtop, Operator::Xor => raqote::BlendMode::Xor, Operator::Arithmetic { .. } => raqote::BlendMode::SrcOver, }; let draw_opt = raqote::DrawOptions { blend_mode, ..raqote::DrawOptions::default() }; dt.draw_image_at(0.0, 0.0, &input1.as_image(), &draw_opt); Ok(Image::from_image(dt, cs)) } fn apply_merge( fe: &usvg::FeMerge, cs: ColorSpace, region: ScreenRect, results: &[FilterResult], canvas: &raqote::DrawTarget, ) -> Result { let mut dt = create_image(region.width(), region.height())?; for input in &fe.inputs { let input = Self::get_input(input, region, &results, canvas)?; let input = input.into_color_space(cs)?; dt.draw_image_at(0.0, 0.0, &input.as_ref().as_image(), &raqote::DrawOptions::default()); } Ok(Image::from_image(dt, cs)) } fn apply_flood( fe: &usvg::FeFlood, region: ScreenRect, ) -> Result { let mut dt = create_image(region.width(), region.height())?; let alpha = (fe.opacity.value() * 255.0) as u8; dt.clear(fe.color.to_solid(alpha)); Ok(Image::from_image(dt, ColorSpace::SRGB)) } fn apply_tile( input: Image, region: ScreenRect, ) -> Result { let mut dt = create_image(region.width(), region.height())?; let subregion = input.region.translate(-region.x(), -region.y()); let tile = copy_image(&input.image, subregion)?; let brush_ts = usvg::Transform::new_translate(subregion.x() as f64, subregion.y() as f64); let ts: raqote::Transform = brush_ts.to_native(); let ts = ts.inverse().unwrap(); let patt = raqote::Source::Image( tile.as_image(), raqote::ExtendMode::Repeat, raqote::FilterMode::Bilinear, ts, ); let mut pb = raqote::PathBuilder::new(); pb.rect(0.0, 0.0, region.width() as f32, region.height() as f32); dt.fill(&pb.finish(), &patt, &raqote::DrawOptions::default()); dt.set_transform(&raqote::Transform::default()); Ok(Image::from_image(dt, ColorSpace::SRGB)) } fn apply_image( fe: &usvg::FeImage, region: ScreenRect, subregion: ScreenRect, opt: &Options, ) -> Result { let mut dt = create_image(region.width(), region.height())?; match fe.data { usvg::FeImageKind::None => {} usvg::FeImageKind::Image(ref data, format) => { let dx = (subregion.x() - region.x()) as f64; let dy = (subregion.y() - region.y()) as f64; let ctm = dt.get_transform().pre_translate(raqote::Vector::new(dx as f32, dy as f32)); dt.set_transform(&ctm); let view_box = usvg::ViewBox { rect: subregion.translate_to(0, 0).to_rect(), aspect: fe.aspect, }; if format == usvg::ImageFormat::SVG { super::image::draw_svg(data, view_box, opt, &mut dt); } else { super::image::draw_raster( format, data, view_box, fe.rendering_mode, opt, &mut dt ); } } usvg::FeImageKind::Use(..) => {} } dt.set_transform(&raqote::Transform::default()); Ok(Image::from_image(dt, ColorSpace::SRGB)) } fn apply_component_transfer( fe: &usvg::FeComponentTransfer, cs: ColorSpace, input: Image, ) -> Result { let input = input.into_color_space(cs)?; let mut buffer = input.take()?; let data = buffer.get_data_u8_mut(); filter::from_premultiplied(data.as_bgra_mut()); for pixel in data.as_bgra_mut() { pixel.r = fe.func_r.apply(pixel.r); pixel.g = fe.func_g.apply(pixel.g); pixel.b = fe.func_b.apply(pixel.b); pixel.a = fe.func_a.apply(pixel.a); } filter::into_premultiplied(data.as_bgra_mut()); Ok(Image::from_image(buffer, cs)) } fn apply_color_matrix( fe: &usvg::FeColorMatrix, cs: ColorSpace, input: Image, ) -> Result { let input = input.into_color_space(cs)?; let mut buffer = input.take()?; let data = buffer.get_data_u8_mut(); filter::from_premultiplied(data.as_bgra_mut()); filter::color_matrix::apply(&fe.kind, data.as_bgra_mut()); filter::into_premultiplied(data.as_bgra_mut()); Ok(Image::from_image(buffer, cs)) } fn apply_to_canvas( input: Image, region: ScreenRect, canvas: &mut raqote::DrawTarget, ) -> Result<(), Error> { let input = input.into_color_space(ColorSpace::SRGB)?; canvas.set_transform(&raqote::Transform::identity()); canvas.make_transparent(); let image = input.as_ref(); canvas.copy_surface(image, raqote::IntRect::new(raqote::IntPoint::new(0, 0), raqote::IntPoint::new(image.width(), image.height())), raqote::IntPoint::new(region.x(), region.y())); Ok(()) } } resvg-0.8.0/src/backend_raqote/image.rs000066400000000000000000000074151352576375700200610ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::prelude::*; use crate::image; use crate::ConvTransform; use super::RaqoteDrawTargetExt; pub fn draw( image: &usvg::Image, opt: &Options, dt: &mut raqote::DrawTarget, ) -> Rect { if image.visibility != usvg::Visibility::Visible { return image.view_box.rect; } if image.format == usvg::ImageFormat::SVG { draw_svg(&image.data, image.view_box, opt, dt); } else { draw_raster(image.format, &image.data, image.view_box, image.rendering_mode, opt, dt); } image.view_box.rect } pub fn draw_raster( format: usvg::ImageFormat, data: &usvg::ImageData, view_box: usvg::ViewBox, rendering_mode: usvg::ImageRendering, opt: &Options, dt: &mut raqote::DrawTarget, ) { let img = try_opt!(image::load_raster(format, data, opt)); let sub_dt = { let mut sub_dt = raqote::DrawTarget::new(img.size.width() as i32, img.size.height() as i32); let surface_data = sub_dt.get_data_u8_mut(); image_to_surface(&img, surface_data); sub_dt }; let (ts, clip) = image::prepare_sub_svg_geom(view_box, img.size); let mut pb = raqote::PathBuilder::new(); if let Some(clip) = clip { pb.rect(clip.x() as f32, clip.y() as f32, clip.width() as f32, clip.height() as f32); } else { // We have to clip the image before rendering because we use `Extend::Pad`. let r = image::image_rect(&view_box, img.size); pb.rect(r.x() as f32, r.y() as f32, r.width() as f32, r.height() as f32); } let filter_mode = if rendering_mode == usvg::ImageRendering::OptimizeSpeed { raqote::FilterMode::Nearest } else { raqote::FilterMode::Bilinear }; let t: raqote::Transform = ts.to_native(); let patt = raqote::Source::Image( sub_dt.as_image(), raqote::ExtendMode::Pad, filter_mode, t.inverse().unwrap(), ); dt.fill(&pb.finish(), &patt, &raqote::DrawOptions::default()); } fn image_to_surface(image: &image::Image, surface: &mut [u8]) { // Surface is always ARGB. const SURFACE_CHANNELS: usize = 4; use crate::image::ImageData; use rgb::FromSlice; let mut i = 0; let mut to_surface = |r, g, b, a| { let tr = a * r + 0x80; let tg = a * g + 0x80; let tb = a * b + 0x80; surface[i + 0] = (((tb >> 8) + tb) >> 8) as u8; surface[i + 1] = (((tg >> 8) + tg) >> 8) as u8; surface[i + 2] = (((tr >> 8) + tr) >> 8) as u8; surface[i + 3] = a as u8; i += SURFACE_CHANNELS; }; match &image.data { ImageData::RGB(data) => { for p in data.as_rgb() { to_surface(p.r as u32, p.g as u32, p.b as u32, 255); } } ImageData::RGBA(data) => { for p in data.as_rgba() { to_surface(p.r as u32, p.g as u32, p.b as u32, p.a as u32); } } } } pub fn draw_svg( data: &usvg::ImageData, view_box: usvg::ViewBox, opt: &Options, dt: &mut raqote::DrawTarget, ) { let (tree, sub_opt) = try_opt!(image::load_sub_svg(data, opt)); let img_size = tree.svg_node().size.to_screen_size(); let (ts, clip) = image::prepare_sub_svg_geom(view_box, img_size); if let Some(clip) = clip { let mut pb = raqote::PathBuilder::new(); pb.rect(clip.x() as f32, clip.y() as f32, clip.width() as f32, clip.height() as f32); dt.push_clip(&pb.finish()); } dt.transform(&ts.to_native()); super::render_to_canvas(&tree, &sub_opt, img_size, dt); if let Some(_) = clip { dt.pop_clip(); } } resvg-0.8.0/src/backend_raqote/mod.rs000066400000000000000000000223671352576375700175610ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. //! Raqote backend implementation. use log::warn; use crate::{prelude::*, layers, ConvTransform}; mod clip_and_mask; mod filter; mod image; mod path; mod style; type RaqoteLayers = layers::Layers; impl ConvTransform for usvg::Transform { fn to_native(&self) -> raqote::Transform { raqote::Transform::row_major(self.a as f32, self.b as f32, self.c as f32, self.d as f32, self.e as f32, self.f as f32) } fn from_native(ts: &raqote::Transform) -> Self { Self::new(ts.m11 as f64, ts.m12 as f64, ts.m21 as f64, ts.m22 as f64, ts.m31 as f64, ts.m32 as f64) } } pub(crate) trait RaqoteDrawTargetExt { fn transform(&mut self, ts: &raqote::Transform); fn as_image(&self) -> raqote::Image; fn make_transparent(&mut self); } impl RaqoteDrawTargetExt for raqote::DrawTarget { fn transform(&mut self, ts: &raqote::Transform) { self.set_transform(&self.get_transform().pre_transform(ts)); } fn as_image(&self) -> raqote::Image { raqote::Image { width: self.width() as i32, height: self.height() as i32, data: self.get_data(), } } fn make_transparent(&mut self) { // This is faster than DrawTarget::clear. for i in self.get_data_u8_mut() { *i = 0; } } } pub(crate) trait ColorExt { fn to_solid(&self, a: u8) -> raqote::SolidSource; fn to_u32(&self, a: u8) -> u32; } impl ColorExt for usvg::Color { fn to_solid(&self, a: u8) -> raqote::SolidSource { raqote::SolidSource { r: premultiply(self.red, a), g: premultiply(self.green, a), b: premultiply(self.blue, a), a, } } fn to_u32(&self, a: u8) -> u32 { let r = self.red as u32; let g = self.green as u32; let b = self.blue as u32; ((a as u32 & 0xff) << 24) | ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff) } } fn premultiply(c: u8, a: u8) -> u8 { let c = a as u32 * c as u32 + 0x80; (((c >> 8) + c) >> 8) as u8 } /// Raqote backend handle. #[derive(Clone, Copy)] pub struct Backend; impl Render for Backend { fn render_to_image( &self, tree: &usvg::Tree, opt: &Options, ) -> Option> { let img = render_to_image(tree, opt)?; Some(Box::new(img)) } fn render_node_to_image( &self, node: &usvg::Node, opt: &Options, ) -> Option> { let img = render_node_to_image(node, opt)?; Some(Box::new(img)) } } impl OutputImage for raqote::DrawTarget { fn save_png(&mut self, path: &::std::path::Path) -> bool { self.write_png(path).is_ok() } } /// Renders SVG to image. pub fn render_to_image( tree: &usvg::Tree, opt: &Options, ) -> Option { let (mut dt, img_view) = create_target( tree.svg_node().size.to_screen_size(), opt, )?; // Fill background. if let Some(c) = opt.background { dt.clear(raqote::SolidSource { r: c.red, g: c.green, b: c.blue, a: 255 }); } render_to_canvas(tree, opt, img_view, &mut dt); Some(dt) } /// Renders SVG to image. pub fn render_node_to_image( node: &usvg::Node, opt: &Options, ) -> Option { let node_bbox = if let Some(bbox) = node.calculate_bbox() { bbox } else { warn!("Node '{}' has a zero size.", node.id()); return None; }; let (mut dt, img_size) = create_target(node_bbox.to_screen_size(), opt)?; let vbox = usvg::ViewBox { rect: node_bbox, aspect: usvg::AspectRatio::default(), }; // Fill background. if let Some(c) = opt.background { dt.clear(raqote::SolidSource { r: c.red, g: c.green, b: c.blue, a: 255 }); } render_node_to_canvas(node, opt, vbox, img_size, &mut dt); Some(dt) } /// Renders SVG to canvas. pub fn render_to_canvas( tree: &usvg::Tree, opt: &Options, img_size: ScreenSize, dt: &mut raqote::DrawTarget, ) { render_node_to_canvas(&tree.root(), opt, tree.svg_node().view_box, img_size, dt); } /// Renders SVG node to canvas. pub fn render_node_to_canvas( node: &usvg::Node, opt: &Options, view_box: usvg::ViewBox, img_size: ScreenSize, dt: &mut raqote::DrawTarget, ) { let mut layers = create_layers(img_size); apply_viewbox_transform(view_box, img_size, dt); let curr_ts = *dt.get_transform(); let mut ts = node.abs_transform(); ts.append(&node.transform()); dt.transform(&ts.to_native()); render_node(node, opt, &mut layers, dt); dt.set_transform(&curr_ts); } fn create_target( size: ScreenSize, opt: &Options, ) -> Option<(raqote::DrawTarget, ScreenSize)> { let img_size = utils::fit_to(size, opt.fit_to)?; let dt = raqote::DrawTarget::new(img_size.width() as i32, img_size.height() as i32); Some((dt, img_size)) } /// Applies viewbox transformation to the painter. fn apply_viewbox_transform( view_box: usvg::ViewBox, img_size: ScreenSize, dt: &mut raqote::DrawTarget, ) { let ts = utils::view_box_to_transform(view_box.rect, view_box.aspect, img_size.to_size()); dt.transform(&ts.to_native()); } fn render_node( node: &usvg::Node, opt: &Options, layers: &mut RaqoteLayers, dt: &mut raqote::DrawTarget, ) -> Option { match *node.borrow() { usvg::NodeKind::Svg(_) => { render_group(node, opt, layers, dt) } usvg::NodeKind::Path(ref path) => { path::draw(&node.tree(), path, opt, raqote::DrawOptions::default(), dt) } usvg::NodeKind::Image(ref img) => { Some(image::draw(img, opt, dt)) } usvg::NodeKind::Group(ref g) => { render_group_impl(node, g, opt, layers, dt) } _ => None, } } fn render_group( parent: &usvg::Node, opt: &Options, layers: &mut RaqoteLayers, dt: &mut raqote::DrawTarget, ) -> Option { let curr_ts = *dt.get_transform(); let mut g_bbox = Rect::new_bbox(); for node in parent.children() { dt.transform(&node.transform().to_native()); let bbox = render_node(&node, opt, layers, dt); if let Some(bbox) = bbox { let bbox = bbox.transform(&node.transform()).unwrap(); g_bbox = g_bbox.expand(bbox); } // Revert transform. dt.set_transform(&curr_ts); } // Check that bbox was changed, otherwise we will have a rect with x/y set to f64::MAX. if g_bbox.fuzzy_ne(&Rect::new_bbox()) { Some(g_bbox) } else { None } } fn render_group_impl( node: &usvg::Node, g: &usvg::Group, opt: &Options, layers: &mut RaqoteLayers, dt: &mut raqote::DrawTarget, ) -> Option { let sub_dt = layers.get()?; let mut sub_dt = sub_dt.borrow_mut(); let curr_ts = *dt.get_transform(); let bbox = { sub_dt.set_transform(&curr_ts); render_group(node, opt, layers, &mut sub_dt) }; // Filter can be rendered on an object without a bbox, // as long as filter uses `userSpaceOnUse`. if let Some(ref id) = g.filter { if let Some(filter_node) = node.tree().defs_by_id(id) { if let usvg::NodeKind::Filter(ref filter) = *filter_node.borrow() { let ts = usvg::Transform::from_native(&curr_ts); filter::apply(filter, bbox, &ts, opt, &mut sub_dt); } } } // Clipping and masking can be done only for objects with a valid bbox. if let Some(bbox) = bbox { if let Some(ref id) = g.clip_path { if let Some(clip_node) = node.tree().defs_by_id(id) { if let usvg::NodeKind::ClipPath(ref cp) = *clip_node.borrow() { sub_dt.set_transform(&curr_ts); clip_and_mask::clip(&clip_node, cp, opt, bbox, layers, &mut sub_dt); } } } if let Some(ref id) = g.mask { if let Some(mask_node) = node.tree().defs_by_id(id) { if let usvg::NodeKind::Mask(ref mask) = *mask_node.borrow() { sub_dt.set_transform(&curr_ts); clip_and_mask::mask(&mask_node, mask, opt, bbox, layers, &mut sub_dt); } } } } dt.set_transform(&raqote::Transform::default()); dt.draw_image_at(0.0, 0.0, &sub_dt.as_image(), &raqote::DrawOptions { blend_mode: raqote::BlendMode::SrcOver, alpha: g.opacity.value() as f32, antialias: raqote::AntialiasMode::Gray, }); dt.set_transform(&curr_ts); bbox } fn create_layers(img_size: ScreenSize) -> RaqoteLayers { layers::Layers::new(img_size, create_subsurface, clear_subsurface) } fn create_subsurface( size: ScreenSize, ) -> Option { Some(raqote::DrawTarget::new(size.width() as i32, size.height() as i32)) } fn clear_subsurface(dt: &mut raqote::DrawTarget) { dt.set_transform(&raqote::Transform::identity()); dt.make_transparent(); } resvg-0.8.0/src/backend_raqote/path.rs000066400000000000000000000060211352576375700177230ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::prelude::*; use super::style; pub fn draw( tree: &usvg::Tree, path: &usvg::Path, opt: &Options, draw_opt: raqote::DrawOptions, dt: &mut raqote::DrawTarget, ) -> Option { let bbox = path.data.bbox(); if path.visibility != usvg::Visibility::Visible { return bbox; } let mut is_butt_cap = true; if let Some(ref stroke) = path.stroke { is_butt_cap = stroke.linecap == usvg::LineCap::Butt; } let mut new_path = conv_path(&path.data, is_butt_cap); // `usvg` guaranties that path without a bbox will not use // a paint server with ObjectBoundingBox, // so we can pass whatever rect we want, because it will not be used anyway. let style_bbox = bbox.unwrap_or_else(|| Rect::new(0.0, 0.0, 1.0, 1.0).unwrap()); if let Some(ref fill) = path.fill { match fill.rule { usvg::FillRule::NonZero => new_path.winding = raqote::Winding::NonZero, usvg::FillRule::EvenOdd => new_path.winding = raqote::Winding::EvenOdd, } } let mut draw_opt = draw_opt.clone(); if !crate::use_shape_antialiasing(path.rendering_mode) { draw_opt.antialias = raqote::AntialiasMode::None; } style::fill(tree, &new_path, &path.fill, opt, style_bbox, &draw_opt, dt); style::stroke(tree, &new_path, &path.stroke, opt, style_bbox, &draw_opt, dt); bbox } fn conv_path( path: &usvg::PathData, is_butt_cap: bool, ) -> raqote::Path { let mut pb = raqote::PathBuilder::new(); for subpath in path.subpaths() { conv_subpath(subpath, is_butt_cap, &mut pb); } pb.finish() } fn conv_subpath( path: usvg::SubPathData, is_butt_cap: bool, pb: &mut raqote::PathBuilder, ) { assert_ne!(path.len(), 0); // Raqote doesn't support line caps on zero-length subpaths, // so we have to implement them manually. let is_zero_path = !is_butt_cap && path.length().is_fuzzy_zero(); if !is_zero_path { for seg in path.iter() { match *seg { usvg::PathSegment::MoveTo { x, y } => { pb.move_to(x as f32, y as f32); } usvg::PathSegment::LineTo { x, y } => { pb.line_to(x as f32, y as f32); } usvg::PathSegment::CurveTo { x1, y1, x2, y2, x, y } => { pb.cubic_to(x1 as f32, y1 as f32, x2 as f32, y2 as f32, x as f32, y as f32); } usvg::PathSegment::ClosePath => { pb.close(); } } } } else { if let usvg::PathSegment::MoveTo { x, y } = path[0] { // Draw zero length path. let shift = 0.002; // Purely empirical. pb.move_to(x as f32, y as f32); pb.line_to(x as f32 + shift, y as f32); } } } resvg-0.8.0/src/backend_raqote/style.rs000066400000000000000000000235241352576375700201360ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::{prelude::*, ConvTransform}; use super::{ColorExt, RaqoteDrawTargetExt}; pub fn fill( tree: &usvg::Tree, path: &raqote::Path, fill: &Option, opt: &Options, bbox: Rect, draw_opt: &raqote::DrawOptions, dt: &mut raqote::DrawTarget, ) { if let Some(ref fill) = fill { let patt_dt; let source = match fill.paint { usvg::Paint::Color(c) => { let alpha = (fill.opacity.value() * 255.0) as u8; raqote::Source::Solid(c.to_solid(alpha)) } usvg::Paint::Link(ref id) => { if let Some(node) = tree.defs_by_id(id) { match *node.borrow() { usvg::NodeKind::LinearGradient(ref lg) => { prepare_linear(lg, fill.opacity, bbox) } usvg::NodeKind::RadialGradient(ref rg) => { prepare_radial(rg, fill.opacity, bbox) } usvg::NodeKind::Pattern(ref pattern) => { let ts = *dt.get_transform(); let (sub_dt, patt_ts) = try_opt!( prepare_pattern(&node, pattern, opt, ts, bbox, fill.opacity) ); patt_dt = sub_dt; create_pattern_image(&patt_dt, patt_ts) } _ => { return; } } } else { return; } } }; dt.fill( path, &source, draw_opt, ); } } pub fn stroke( tree: &usvg::Tree, path: &raqote::Path, stroke: &Option, opt: &Options, bbox: Rect, draw_opt: &raqote::DrawOptions, dt: &mut raqote::DrawTarget, ) { if let Some(ref stroke) = stroke { let cap = match stroke.linecap { usvg::LineCap::Butt => raqote::LineCap::Butt, usvg::LineCap::Round => raqote::LineCap::Round, usvg::LineCap::Square => raqote::LineCap::Square, }; let join = match stroke.linejoin { usvg::LineJoin::Miter => raqote::LineJoin::Miter, usvg::LineJoin::Round => raqote::LineJoin::Round, usvg::LineJoin::Bevel => raqote::LineJoin::Bevel, }; let mut dash_array = Vec::new(); if let Some(ref list) = stroke.dasharray { dash_array = list.iter().map(|n| *n as f32).collect(); } let style = raqote::StrokeStyle { cap, join, width: stroke.width.value() as f32, miter_limit: stroke.miterlimit.value() as f32, dash_array, dash_offset: stroke.dashoffset, }; let patt_dt; let source = match stroke.paint { usvg::Paint::Color(c) => { let alpha = (stroke.opacity.value() * 255.0) as u8; raqote::Source::Solid(c.to_solid(alpha)) } usvg::Paint::Link(ref id) => { if let Some(node) = tree.defs_by_id(id) { match *node.borrow() { usvg::NodeKind::LinearGradient(ref lg) => { prepare_linear(lg, stroke.opacity, bbox) } usvg::NodeKind::RadialGradient(ref rg) => { prepare_radial(rg, stroke.opacity, bbox) } usvg::NodeKind::Pattern(ref pattern) => { let ts = *dt.get_transform(); let (sub_dt, patt_ts) = try_opt!( prepare_pattern(&node, pattern, opt, ts, bbox, stroke.opacity) ); patt_dt = sub_dt; create_pattern_image(&patt_dt, patt_ts) } _ => { return; } } } else { return; } } }; dt.stroke( &path, &source, &style, draw_opt, ); } } fn prepare_linear<'a>( g: &usvg::LinearGradient, opacity: usvg::Opacity, bbox: Rect, ) -> raqote::Source<'a> { let ts = if g.units == usvg::Units::ObjectBoundingBox { let mut ts = usvg::Transform::from_bbox(bbox); ts.append(&g.transform); ts } else { g.transform }; let mut grad = raqote::Source::new_linear_gradient( raqote::Gradient { stops: conv_stops(g, opacity) }, raqote::Point::new(g.x1 as f32, g.y1 as f32), raqote::Point::new(g.x2 as f32, g.y2 as f32), conv_spread(g.base.spread_method), ); if let raqote::Source::LinearGradient(_, _, ref mut transform) = grad { let ts: raqote::Transform = ts.to_native(); if let Some(ts) = ts.inverse() { *transform = transform.pre_transform(&ts); } } grad } fn prepare_radial<'a>( g: &usvg::RadialGradient, opacity: usvg::Opacity, bbox: Rect, ) -> raqote::Source<'a> { let ts = if g.units == usvg::Units::ObjectBoundingBox { let mut ts = usvg::Transform::from_bbox(bbox); ts.append(&g.transform); ts } else { g.transform }; let mut grad; if g.fx == g.cx && g.fy == g.cy { grad = raqote::Source::new_radial_gradient( raqote::Gradient { stops: conv_stops(g, opacity) }, raqote::Point::new(g.cx as f32, g.cy as f32), g.r.value() as f32, conv_spread(g.base.spread_method), ); } else { grad = raqote::Source::new_two_circle_radial_gradient( raqote::Gradient { stops: conv_stops(g, opacity) }, raqote::Point::new(g.fx as f32, g.fy as f32), 0.0, raqote::Point::new(g.cx as f32, g.cy as f32), g.r.value() as f32, conv_spread(g.base.spread_method), ); } match grad { raqote::Source::RadialGradient(_, _, ref mut transform) | raqote::Source::TwoCircleRadialGradient(_, _, _, _, _, _, ref mut transform) => { let ts: raqote::Transform = ts.to_native(); if let Some(ts) = ts.inverse() { *transform = transform.pre_transform(&ts); } } _ => {} } grad } fn conv_spread(v: usvg::SpreadMethod) -> raqote::Spread { match v { usvg::SpreadMethod::Pad => raqote::Spread::Pad, usvg::SpreadMethod::Reflect => raqote::Spread::Reflect, usvg::SpreadMethod::Repeat => raqote::Spread::Repeat, } } fn conv_stops( g: &usvg::BaseGradient, opacity: usvg::Opacity, ) -> Vec { let mut stops = Vec::new(); for stop in &g.stops { let alpha = stop.opacity.value() * opacity.value(); stops.push(raqote::GradientStop { position: stop.offset.value() as f32, color: stop.color.to_u32((alpha * 255.0) as u8), }); } stops } fn prepare_pattern<'a>( pattern_node: &usvg::Node, pattern: &usvg::Pattern, opt: &Options, global_ts: raqote::Transform, bbox: Rect, opacity: usvg::Opacity, ) -> Option<(raqote::DrawTarget, usvg::Transform)> { let r = if pattern.units == usvg::Units::ObjectBoundingBox { pattern.rect.bbox_transform(bbox) } else { pattern.rect }; let global_ts = usvg::Transform::from_native(&global_ts); let (sx, sy) = global_ts.get_scale(); let img_size = Size::new(r.width() * sx, r.height() * sy)?.to_screen_size(); let mut dt = raqote::DrawTarget::new(img_size.width() as i32, img_size.height() as i32); dt.transform(&raqote::Transform::create_scale(sx as f32, sy as f32)); if let Some(vbox) = pattern.view_box { let ts = utils::view_box_to_transform(vbox.rect, vbox.aspect, r.size()); dt.transform(&ts.to_native()); } else if pattern.content_units == usvg::Units::ObjectBoundingBox { // 'Note that this attribute has no effect if attribute `viewBox` is specified.' // We don't use Transform::from_bbox(bbox) because `x` and `y` should be // ignored for some reasons... dt.transform(&raqote::Transform::create_scale(bbox.width() as f32, bbox.height() as f32)); } let mut layers = super::create_layers(img_size); super::render_group(pattern_node, opt, &mut layers, &mut dt); let img = if !opacity.is_default() { // If `opacity` isn't `1` then we have to make image semitransparent. // The only way to do this is by making a new image and rendering // the pattern on it with transparency. let mut img2 = raqote::DrawTarget::new(img_size.width() as i32, img_size.height() as i32); img2.draw_image_at(0.0, 0.0, &dt.as_image(), &raqote::DrawOptions { blend_mode: raqote::BlendMode::Src, alpha: opacity.value() as f32, ..raqote::DrawOptions::default() }); img2 } else { dt }; let mut ts = usvg::Transform::default(); ts.append(&pattern.transform); ts.translate(r.x(), r.y()); ts.scale(1.0 / sx, 1.0 / sy); Some((img, ts)) } fn create_pattern_image( dt: &raqote::DrawTarget, ts: usvg::Transform, ) -> raqote::Source { let ts: raqote::Transform = ts.to_native(); raqote::Source::Image( dt.as_image(), raqote::ExtendMode::Repeat, raqote::FilterMode::Bilinear, ts.inverse().unwrap(), ) } resvg-0.8.0/src/backend_skia/000077500000000000000000000000001352576375700160565ustar00rootroot00000000000000resvg-0.8.0/src/backend_skia/clip_and_mask.rs000066400000000000000000000120501352576375700212060ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::skia; use crate::{prelude::*, ConvTransform}; use super::{path, SkiaLayers}; pub fn clip( node: &usvg::Node, cp: &usvg::ClipPath, opt: &Options, bbox: Rect, layers: &mut SkiaLayers, surface: &mut skia::Surface, ) { let clip_surface = try_opt!(layers.get()); let mut clip_surface = clip_surface.borrow_mut(); clip_surface.fill(0, 0, 0, 255); clip_surface.set_matrix(&surface.get_matrix()); clip_surface.concat(&cp.transform.to_native()); if cp.units == usvg::Units::ObjectBoundingBox { clip_surface.concat(&usvg::Transform::from_bbox(bbox).to_native()); } let ts = clip_surface.get_matrix(); for node in node.children() { clip_surface.concat(&node.transform().to_native()); match *node.borrow() { usvg::NodeKind::Path(ref path_node) => { path::draw(&node.tree(), path_node, opt, skia::BlendMode::Clear, &mut clip_surface); } usvg::NodeKind::Group(ref g) => { clip_group(&node, g, opt, bbox, layers, &mut clip_surface); } _ => {} } clip_surface.set_matrix(&ts); } if let Some(ref id) = cp.clip_path { if let Some(ref clip_node) = node.tree().defs_by_id(id) { if let usvg::NodeKind::ClipPath(ref cp) = *clip_node.borrow() { clip(clip_node, cp, opt, bbox, layers, surface); } } } surface.reset_matrix(); surface.draw_surface( &clip_surface, 0.0, 0.0, 255, skia::BlendMode::DestinationOut, skia::FilterQuality::Low, ); } fn clip_group( node: &usvg::Node, g: &usvg::Group, opt: &Options, bbox: Rect, layers: &mut SkiaLayers, surface: &mut skia::Surface, ) { if let Some(ref id) = g.clip_path { if let Some(ref clip_node) = node.tree().defs_by_id(id) { if let usvg::NodeKind::ClipPath(ref cp) = *clip_node.borrow() { // If a `clipPath` child also has a `clip-path` // then we should render this child on a new canvas, // clip it, and only then draw it to the `clipPath`. let clip_surface = try_opt!(layers.get()); let mut clip_surface = clip_surface.borrow_mut(); clip_surface.set_matrix(&surface.get_matrix()); draw_group_child(&node, opt, &mut clip_surface); clip(clip_node, cp, opt, bbox, layers, &mut clip_surface); surface.reset_matrix(); surface.draw_surface( &clip_surface, 0.0, 0.0, 255, skia::BlendMode::Xor, skia::FilterQuality::Low, ); } } } } fn draw_group_child( node: &usvg::Node, opt: &Options, surface: &mut skia::Surface, ) { if let Some(child) = node.first_child() { surface.concat(&child.transform().to_native()); match *child.borrow() { usvg::NodeKind::Path(ref path_node) => { path::draw(&child.tree(), path_node, opt, skia::BlendMode::SourceOver, surface); } _ => {} } } } pub fn mask( node: &usvg::Node, mask: &usvg::Mask, opt: &Options, bbox: Rect, layers: &mut SkiaLayers, surface: &mut skia::Surface, ) { let mask_surface = try_opt!(layers.get()); let mut mask_surface = mask_surface.borrow_mut(); { mask_surface.set_matrix(&surface.get_matrix()); let r = if mask.units == usvg::Units::ObjectBoundingBox { mask.rect.bbox_transform(bbox) } else { mask.rect }; mask_surface.save(); mask_surface.set_clip_rect(r.x(), r.y(), r.width(), r.height()); if mask.content_units == usvg::Units::ObjectBoundingBox { mask_surface.concat(&usvg::Transform::from_bbox(bbox).to_native()); } super::render_group(node, opt, layers, &mut mask_surface); mask_surface.restore(); } { use rgb::FromSlice; use std::mem::swap; let mut data = mask_surface.data_mut(); // RGBA -> BGRA. if !skia::Surface::is_bgra() { data.as_bgra_mut().iter_mut().for_each(|p| swap(&mut p.r, &mut p.b)); } crate::image_to_mask(data.as_bgra_mut(), layers.image_size()); // BGRA -> RGBA. if !skia::Surface::is_bgra() { data.as_bgra_mut().iter_mut().for_each(|p| swap(&mut p.r, &mut p.b)); } } if let Some(ref id) = mask.mask { if let Some(ref mask_node) = node.tree().defs_by_id(id) { if let usvg::NodeKind::Mask(ref mask) = *mask_node.borrow() { self::mask(mask_node, mask, opt, bbox, layers, surface); } } } surface.reset_matrix(); surface.draw_surface( &mask_surface, 0.0, 0.0, 255, skia::BlendMode::DestinationIn, skia::FilterQuality::Low, ); } resvg-0.8.0/src/backend_skia/filter.rs000066400000000000000000000363421352576375700177210ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::cmp; use std::rc::Rc; use crate::skia; use rgb::FromSlice; use log::warn; use usvg::ColorInterpolation as ColorSpace; use crate::prelude::*; use crate::filter::{self, Error, Filter, ImageExt}; use crate::ConvTransform; type Image = filter::Image; type FilterResult = filter::FilterResult; pub fn apply( filter: &usvg::Filter, bbox: Option, ts: &usvg::Transform, opt: &Options, surface: &mut skia::Surface, ) { SkiaFilter::apply(filter, bbox, ts, opt, surface); } impl ImageExt for skia::Surface { fn width(&self) -> u32 { self.width() as u32 } fn height(&self) -> u32 { self.height() as u32 } fn try_clone(&self) -> Result { self.try_clone().ok_or(Error::AllocFailed) } fn clip(&mut self, region: ScreenRect) { // This is cropping by clearing the pixels outside the region. let mut paint = skia::Paint::new(); paint.set_color(0, 0, 0, 0); paint.set_blend_mode(skia::BlendMode::Clear); let w = self.width() as f64; let h = self.height() as f64; self.draw_rect(0.0, 0.0, w, region.y() as f64, &paint); self.draw_rect(0.0, 0.0, region.x() as f64, h, &paint); self.draw_rect(region.right() as f64, 0.0, w, h, &paint); self.draw_rect(0.0, region.bottom() as f64, w, h, &paint); } fn clear(&mut self) { skia::Canvas::clear(self); } fn into_srgb(&mut self) { for p in self.data_mut().as_rgba_mut() { p.r = filter::LINEAR_RGB_TO_SRGB_TABLE[p.r as usize]; p.g = filter::LINEAR_RGB_TO_SRGB_TABLE[p.g as usize]; p.b = filter::LINEAR_RGB_TO_SRGB_TABLE[p.b as usize]; } } fn into_linear_rgb(&mut self) { for p in self.data_mut().as_rgba_mut() { p.r = filter::SRGB_TO_LINEAR_RGB_TABLE[p.r as usize]; p.g = filter::SRGB_TO_LINEAR_RGB_TABLE[p.g as usize]; p.b = filter::SRGB_TO_LINEAR_RGB_TABLE[p.b as usize]; } } } fn create_surface(width: u32, height: u32) -> Result { let mut surface = skia::Surface::new_rgba(width, height).ok_or(Error::AllocFailed)?; surface.clear(); Ok(surface) } fn copy_surface(surface: &skia::Surface, region: ScreenRect) -> Result { let x = cmp::max(0, region.x()) as u32; let y = cmp::max(0, region.y()) as u32; surface.copy_rgba(x, y, region.width(), region.height()).ok_or(Error::AllocFailed) } struct SkiaFilter; impl Filter for SkiaFilter { fn get_input( input: &usvg::FilterInput, region: ScreenRect, results: &[FilterResult], surface: &skia::Surface, ) -> Result { match input { usvg::FilterInput::SourceGraphic => { let image = copy_surface(surface, region)?; Ok(Image { image: Rc::new(image), region: region.translate_to(0, 0), color_space: ColorSpace::SRGB, }) } usvg::FilterInput::SourceAlpha => { let image = copy_surface(surface, region)?; // Set RGB to black. Keep alpha as is. for p in image.data().chunks_mut(4) { p[0] = 0; p[1] = 0; p[2] = 0; } Ok(Image { image: Rc::new(image), region: region.translate_to(0, 0), color_space: ColorSpace::SRGB, }) } usvg::FilterInput::Reference(ref name) => { if let Some(ref v) = results.iter().rev().find(|v| v.name == *name) { Ok(v.image.clone()) } else { // Technically unreachable. warn!("Unknown filter primitive reference '{}'.", name); Self::get_input(&usvg::FilterInput::SourceGraphic, region, results, surface) } } _ => { warn!("Filter input '{:?}' is not supported.", input); Self::get_input(&usvg::FilterInput::SourceGraphic, region, results, surface) } } } fn apply_blur( fe: &usvg::FeGaussianBlur, units: usvg::Units, cs: ColorSpace, bbox: Option, ts: &usvg::Transform, input: Image, ) -> Result { let (std_dx, std_dy, box_blur) = try_opt_or!(Self::resolve_std_dev(fe, units, bbox, ts), Ok(input)); let input = input.into_color_space(cs)?; let mut buffer = input.take()?; let (w, h) = (buffer.width(), buffer.height()); // Skia surface can be RGBA, but it will not affect the blur algorithm. filter::into_premultiplied(buffer.data_mut().as_bgra_mut()); if box_blur { filter::box_blur::apply(&mut buffer.data_mut(), w, h, std_dx, std_dy); } else { filter::iir_blur::apply(&mut buffer.data_mut(), w, h, std_dx, std_dy); } filter::from_premultiplied(buffer.data_mut().as_bgra_mut()); Ok(Image::from_image(buffer, cs)) } fn apply_offset( fe: &usvg::FeOffset, units: usvg::Units, bbox: Option, ts: &usvg::Transform, input: Image, ) -> Result { let (dx, dy) = try_opt_or!(Self::resolve_offset(fe, units, bbox, ts), Ok(input)); let mut buffer = create_surface(input.width(), input.height())?; buffer.reset_matrix(); buffer.draw_surface(input.as_ref(), dx, dy, 255, skia::BlendMode::SourceOver, skia::FilterQuality::Low); buffer.flush(); Ok(Image::from_image(buffer, input.color_space)) } fn apply_blend( fe: &usvg::FeBlend, cs: ColorSpace, region: ScreenRect, input1: Image, input2: Image, ) -> Result { let input1 = input1.into_color_space(cs)?; let input2 = input2.into_color_space(cs)?; let mut buffer = create_surface(region.width(), region.height())?; buffer.draw_surface(input2.as_ref(), 0.0, 0.0, 255, skia::BlendMode::SourceOver, skia::FilterQuality::Low); let blend_mode = match fe.mode { usvg::FeBlendMode::Normal => skia::BlendMode::SourceOver, usvg::FeBlendMode::Multiply => skia::BlendMode::Multiply, usvg::FeBlendMode::Screen => skia::BlendMode::Screen, usvg::FeBlendMode::Darken => skia::BlendMode::Darken, usvg::FeBlendMode::Lighten => skia::BlendMode::Lighten, }; buffer.draw_surface(input1.as_ref(), 0.0, 0.0, 255, blend_mode, skia::FilterQuality::Low); Ok(Image::from_image(buffer, cs)) } fn apply_composite( fe: &usvg::FeComposite, cs: ColorSpace, region: ScreenRect, input1: Image, input2: Image, ) -> Result { use rgb::RGBA8; use usvg::FeCompositeOperator as Operator; let input1 = input1.into_color_space(cs)?; let input2 = input2.into_color_space(cs)?; let mut buffer = create_surface(region.width(), region.height())?; if let Operator::Arithmetic { k1, k2, k3, k4 } = fe.operator { fn premultiply_alpha(c: RGBA8) -> RGBA8 { let a = c.a as f64 / 255.0; let b = (c.b as f64 * a + 0.5) as u8; let g = (c.g as f64 * a + 0.5) as u8; let r = (c.r as f64 * a + 0.5) as u8; RGBA8 { r, g, b, a: c.a } } fn unmultiply_alpha(c: RGBA8) -> RGBA8 { let a = c.a as f64 / 255.0; let b = (c.b as f64 / a + 0.5) as u8; let g = (c.g as f64 / a + 0.5) as u8; let r = (c.r as f64 / a + 0.5) as u8; RGBA8 { r, g, b, a: c.a } } let data1 = input1.as_ref().data(); let data2 = input2.as_ref().data(); let calc = |i1, i2, max| { let i1 = i1 as f64 / 255.0; let i2 = i2 as f64 / 255.0; let result = k1.value() * i1 * i2 + k2.value() * i1 + k3.value() * i2 + k4.value(); f64_bound(0.0, result, max) }; { let mut i = 0; let mut data3 = buffer.data_mut(); let data3 = data3.as_rgba_mut(); for (c1, c2) in data1.as_rgba().iter().zip(data2.as_rgba()) { let c1 = premultiply_alpha(*c1); let c2 = premultiply_alpha(*c2); let a = calc(c1.a, c2.a, 1.0); if a.is_fuzzy_zero() { continue; } let r = (calc(c1.r, c2.r, a) * 255.0) as u8; let g = (calc(c1.g, c2.g, a) * 255.0) as u8; let b = (calc(c1.b, c2.b, a) * 255.0) as u8; let a = (a * 255.0) as u8; data3[i] = unmultiply_alpha(RGBA8 { r, g, b, a }); i += 1; } } return Ok(Image::from_image(buffer, cs)); } buffer.draw_surface(input2.as_ref(), 0.0, 0.0, 255, skia::BlendMode::SourceOver, skia::FilterQuality::Low); let blend_mode = match fe.operator { Operator::Over => skia::BlendMode::SourceOver, Operator::In => skia::BlendMode::SourceIn, Operator::Out => skia::BlendMode::SourceOut, Operator::Atop => skia::BlendMode::SourceAtop, Operator::Xor => skia::BlendMode::Xor, Operator::Arithmetic { .. } => skia::BlendMode::SourceOver, }; buffer.draw_surface(input1.as_ref(), 0.0, 0.0, 255, blend_mode, skia::FilterQuality::Low); Ok(Image::from_image(buffer, cs)) } fn apply_merge( fe: &usvg::FeMerge, cs: ColorSpace, region: ScreenRect, results: &[FilterResult], surface: &skia::Surface, ) -> Result { let mut buffer = create_surface(region.width(), region.height())?; buffer.reset_matrix(); for input in &fe.inputs { let input = Self::get_input(input, region, &results, surface)?; let input = input.into_color_space(cs)?; buffer.draw_surface(input.as_ref(), 0.0, 0.0, 255, skia::BlendMode::SourceOver, skia::FilterQuality::Low); } buffer.flush(); Ok(Image::from_image(buffer, cs)) } fn apply_flood( fe: &usvg::FeFlood, region: ScreenRect, ) -> Result { let c = fe.color; let alpha = f64_bound(0.0, fe.opacity.value() * 255.0, 255.0) as u8; let mut buffer = create_surface(region.width(), region.height())?; buffer.fill(c.red, c.green, c.blue, alpha); Ok(Image::from_image(buffer, ColorSpace::SRGB)) } fn apply_tile( input: Image, region: ScreenRect, ) -> Result { let mut buffer = create_surface(region.width(), region.height())?; let subregion = input.region.translate(-region.x(), -region.y()); let tile_surface = copy_surface(&input.image, subregion)?; let brush_ts = usvg::Transform::new_translate(subregion.x() as f64, subregion.y() as f64); let shader = skia::Shader::new_from_surface_image(&tile_surface, brush_ts.to_native()); let mut paint = skia::Paint::new(); paint.set_shader(&shader); buffer.draw_rect(0.0, 0.0, region.width() as f64, region.height() as f64, &paint); buffer.reset_matrix(); Ok(Image::from_image(buffer, ColorSpace::SRGB)) } fn apply_image( fe: &usvg::FeImage, region: ScreenRect, subregion: ScreenRect, opt: &Options, ) -> Result { let mut buffer = create_surface(region.width(), region.height())?; match fe.data { usvg::FeImageKind::None => {} usvg::FeImageKind::Image(ref data, format) => { let dx = (subregion.x() - region.x()) as f64; let dy = (subregion.y() - region.y()) as f64; buffer.translate(dx, dy); let view_box = usvg::ViewBox { rect: subregion.translate_to(0, 0).to_rect(), aspect: fe.aspect, }; if format == usvg::ImageFormat::SVG { super::image::draw_svg(data, view_box, opt, &mut buffer); } else { super::image::draw_raster( format, data, view_box, fe.rendering_mode, opt, &mut buffer, ); } } usvg::FeImageKind::Use(..) => {} } buffer.reset_matrix(); Ok(Image::from_image(buffer, ColorSpace::SRGB)) } fn apply_component_transfer( fe: &usvg::FeComponentTransfer, cs: ColorSpace, input: Image, ) -> Result { let input = input.into_color_space(cs)?; let mut buffer = input.take()?; if skia::Surface::is_bgra() { for pixel in buffer.data_mut().as_bgra_mut() { pixel.r = fe.func_r.apply(pixel.r); pixel.g = fe.func_g.apply(pixel.g); pixel.b = fe.func_b.apply(pixel.b); pixel.a = fe.func_a.apply(pixel.a); } } else { for pixel in buffer.data_mut().as_rgba_mut() { pixel.r = fe.func_r.apply(pixel.r); pixel.g = fe.func_g.apply(pixel.g); pixel.b = fe.func_b.apply(pixel.b); pixel.a = fe.func_a.apply(pixel.a); } } Ok(Image::from_image(buffer, cs)) } fn apply_color_matrix( fe: &usvg::FeColorMatrix, cs: ColorSpace, input: Image, ) -> Result { use std::mem::swap; let input = input.into_color_space(cs)?; let mut buffer = input.take()?; let mut data = buffer.data_mut(); // RGBA -> BGRA. if !skia::Surface::is_bgra() { data.as_bgra_mut().iter_mut().for_each(|p| swap(&mut p.r, &mut p.b)); } filter::color_matrix::apply(&fe.kind, data.as_bgra_mut()); // BGRA -> RGBA. if !skia::Surface::is_bgra() { data.as_bgra_mut().iter_mut().for_each(|p| swap(&mut p.r, &mut p.b)); } Ok(Image::from_image(buffer, cs)) } fn apply_to_canvas( input: Image, region: ScreenRect, surface: &mut skia::Surface, ) -> Result<(), Error> { let input = input.into_color_space(ColorSpace::SRGB)?; surface.reset_matrix(); surface.clear(); surface.draw_surface(input.as_ref(), region.x() as f64, region.y() as f64, 255, skia::BlendMode::SourceOver, skia::FilterQuality::Low); Ok(()) } } resvg-0.8.0/src/backend_skia/image.rs000066400000000000000000000077131352576375700175160ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::skia; use crate::prelude::*; use crate::image; use crate::ConvTransform; pub fn draw( image: &usvg::Image, opt: &Options, surface: &mut skia::Surface, ) -> Rect { if image.visibility != usvg::Visibility::Visible { return image.view_box.rect; } if image.format == usvg::ImageFormat::SVG { draw_svg(&image.data, image.view_box, opt, surface); } else { draw_raster(image.format, &image.data, image.view_box, image.rendering_mode, opt, surface); } image.view_box.rect } pub fn draw_raster( format: usvg::ImageFormat, data: &usvg::ImageData, view_box: usvg::ViewBox, rendering_mode: usvg::ImageRendering, opt: &Options, surface: &mut skia::Surface, ) { let img = try_opt!(image::load_raster(format, data, opt)); let image = { let (w, h) = img.size.dimensions(); let mut image = try_opt_warn_or!( skia::Surface::new_rgba(w, h), (), "Failed to create a {}x{} surface.", w, h ); image_to_surface(&img, &mut image.data_mut()); image }; let mut filter = skia::FilterQuality::Low; if rendering_mode == usvg::ImageRendering::OptimizeSpeed { filter = skia::FilterQuality::None; } surface.save(); if view_box.aspect.slice { let r = view_box.rect; surface.set_clip_rect(r.x(), r.y(), r.width(), r.height()); } let r = image::image_rect(&view_box, img.size); surface.draw_surface_rect(&image, r.x(), r.y(), r.width(), r.height(), filter); // Revert. surface.restore(); } fn image_to_surface(image: &image::Image, surface: &mut [u8]) { // Surface is always ARGB. const SURFACE_CHANNELS: usize = 4; use crate::image::ImageData; use rgb::FromSlice; let mut i = 0; if skia::Surface::is_bgra() { match &image.data { ImageData::RGB(data) => { for p in data.as_rgb() { surface[i + 0] = p.b; surface[i + 1] = p.g; surface[i + 2] = p.r; surface[i + 3] = 255; i += SURFACE_CHANNELS; } } ImageData::RGBA(data) => { for p in data.as_rgba() { surface[i + 0] = p.b; surface[i + 1] = p.g; surface[i + 2] = p.r; surface[i + 3] = p.a; i += SURFACE_CHANNELS; } } } } else { match &image.data { ImageData::RGB(data) => { for p in data.as_rgb() { surface[i + 0] = p.r; surface[i + 1] = p.g; surface[i + 2] = p.b; surface[i + 3] = 255; i += SURFACE_CHANNELS; } } ImageData::RGBA(data) => { for p in data.as_rgba() { surface[i + 0] = p.r; surface[i + 1] = p.g; surface[i + 2] = p.b; surface[i + 3] = p.a; i += SURFACE_CHANNELS; } } } } } pub fn draw_svg( data: &usvg::ImageData, view_box: usvg::ViewBox, opt: &Options, surface: &mut skia::Surface, ) { let (tree, sub_opt) = try_opt!(image::load_sub_svg(data, opt)); let img_size = tree.svg_node().size.to_screen_size(); let (ts, clip) = image::prepare_sub_svg_geom(view_box, img_size); surface.save(); if let Some(clip) = clip { surface.set_clip_rect(clip.x(), clip.y(), clip.width(), clip.height()); } surface.concat(&ts.to_native()); super::render_to_canvas(&tree, &sub_opt, img_size, surface); surface.restore(); } resvg-0.8.0/src/backend_skia/mod.rs000066400000000000000000000176111352576375700172110ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. //! Skia backend implementation. use crate::skia; use log::warn; use crate::{prelude::*, layers, ConvTransform}; macro_rules! try_create_surface { ($size:expr, $ret:expr) => { try_opt_warn_or!( skia::Surface::new_rgba_premultiplied($size.width(), $size.height()), $ret, "Failed to create a {}x{} surface.", $size.width(), $size.height() ); }; } type SkiaLayers = layers::Layers; mod clip_and_mask; mod filter; mod image; mod path; mod style; impl ConvTransform for usvg::Transform { fn to_native(&self) -> skia::Matrix { skia::Matrix::new_from(self.a, self.b, self.c, self.d, self.e, self.f) } fn from_native(mat: &skia::Matrix) -> Self { let d = mat.data(); Self::new(d.0, d.1, d.2, d.3, d.4, d.5) } } /// Skia backend handle. #[derive(Clone, Copy)] pub struct Backend; impl Render for Backend { fn render_to_image( &self, tree: &usvg::Tree, opt: &Options, ) -> Option> { let img = render_to_image(tree, opt)?; Some(Box::new(img)) } fn render_node_to_image( &self, node: &usvg::Node, opt: &Options, ) -> Option> { let img = render_node_to_image(node, opt)?; Some(Box::new(img)) } } impl OutputImage for skia::Surface { fn save_png( &mut self, path: &std::path::Path, ) -> bool { skia::Surface::save_png(self, path.to_str().unwrap()) } } /// Renders SVG to image. pub fn render_to_image( tree: &usvg::Tree, opt: &Options, ) -> Option { let (mut img, img_size) = create_root_image(tree.svg_node().size.to_screen_size(), opt)?; render_to_canvas(tree, opt, img_size, &mut img); Some(img) } /// Renders SVG node to image. pub fn render_node_to_image( node: &usvg::Node, opt: &Options, ) -> Option { let node_bbox = if let Some(bbox) = node.calculate_bbox() { bbox } else { warn!("Node '{}' has zero size.", node.id()); return None; }; let vbox = usvg::ViewBox { rect: node_bbox, aspect: usvg::AspectRatio::default(), }; let (mut img, img_size) = create_root_image(node_bbox.size().to_screen_size(), opt)?; render_node_to_canvas(node, opt, vbox, img_size, &mut img); Some(img) } /// Renders SVG to canvas. pub fn render_to_canvas( tree: &usvg::Tree, opt: &Options, img_size: ScreenSize, surface: &mut skia::Surface, ) { render_node_to_canvas(&tree.root(), opt, tree.svg_node().view_box, img_size, surface); } /// Renders SVG node to canvas. pub fn render_node_to_canvas( node: &usvg::Node, opt: &Options, view_box: usvg::ViewBox, img_size: ScreenSize, surface: &mut skia::Surface, ) { let mut layers = create_layers(img_size); apply_viewbox_transform(view_box, img_size, surface); let curr_ts = surface.get_matrix(); let mut ts = node.abs_transform(); ts.append(&node.transform()); surface.concat(&ts.to_native()); render_node(node, opt, &mut layers, surface); surface.set_matrix(&curr_ts); } fn create_root_image( size: ScreenSize, opt: &Options, ) -> Option<(skia::Surface, ScreenSize)> { let img_size = utils::fit_to(size, opt.fit_to)?; let mut img = try_create_surface!(img_size, None); // Fill background. if let Some(c) = opt.background { img.fill(c.red, c.green, c.blue, 255); } else { img.fill(0, 0, 0, 0); } Some((img, img_size)) } /// Applies viewbox transformation to the painter. fn apply_viewbox_transform( view_box: usvg::ViewBox, img_size: ScreenSize, surface: &mut skia::Surface, ) { let ts = utils::view_box_to_transform(view_box.rect, view_box.aspect, img_size.to_size()); surface.concat(&ts.to_native()); } fn render_node( node: &usvg::Node, opt: &Options, layers: &mut SkiaLayers, surface: &mut skia::Surface, ) -> Option { match *node.borrow() { usvg::NodeKind::Svg(_) => { render_group(node, opt, layers, surface) } usvg::NodeKind::Path(ref path) => { path::draw(&node.tree(), path, opt, skia::BlendMode::SourceOver, surface) } usvg::NodeKind::Image(ref img) => { Some(image::draw(img, opt, surface)) } usvg::NodeKind::Group(ref g) => { render_group_impl(node, g, opt, layers, surface) } _ => None, } } fn render_group( parent: &usvg::Node, opt: &Options, layers: &mut SkiaLayers, surface: &mut skia::Surface, ) -> Option { let curr_ts = surface.get_matrix(); let mut g_bbox = Rect::new_bbox(); for node in parent.children() { surface.concat(&node.transform().to_native()); let bbox = render_node(&node, opt, layers, surface); if let Some(bbox) = bbox { if let Some(bbox) = bbox.transform(&node.transform()) { g_bbox = g_bbox.expand(bbox); } } // Revert transform. surface.set_matrix(&curr_ts); } // Check that bbox was changed, otherwise we will have a rect with x/y set to f64::MAX. if g_bbox.fuzzy_ne(&Rect::new_bbox()) { Some(g_bbox) } else { None } } fn render_group_impl( node: &usvg::Node, g: &usvg::Group, opt: &Options, layers: &mut SkiaLayers, surface: &mut skia::Surface, ) -> Option { let sub_surface = layers.get()?; let mut sub_surface = sub_surface.borrow_mut(); let curr_ts = surface.get_matrix(); let bbox = { sub_surface.set_matrix(&curr_ts); render_group(node, opt, layers, &mut sub_surface) }; // Filter can be rendered on an object without a bbox, // as long as filter uses `userSpaceOnUse`. if let Some(ref id) = g.filter { if let Some(filter_node) = node.tree().defs_by_id(id) { if let usvg::NodeKind::Filter(ref filter) = *filter_node.borrow() { let ts = usvg::Transform::from_native(&curr_ts); filter::apply(filter, bbox, &ts, opt, &mut sub_surface); } } } // Clipping and masking can be done only for objects with a valid bbox. if let Some(bbox) = bbox { if let Some(ref id) = g.clip_path { if let Some(clip_node) = node.tree().defs_by_id(id) { if let usvg::NodeKind::ClipPath(ref cp) = *clip_node.borrow() { sub_surface.set_matrix(&curr_ts); clip_and_mask::clip(&clip_node, cp, opt, bbox, layers, &mut sub_surface); } } } if let Some(ref id) = g.mask { if let Some(mask_node) = node.tree().defs_by_id(id) { if let usvg::NodeKind::Mask(ref mask) = *mask_node.borrow() { sub_surface.set_matrix(&curr_ts); clip_and_mask::mask(&mask_node, mask, opt, bbox, layers, &mut sub_surface); } } } } let a = if !g.opacity.is_default() { (g.opacity.value() * 255.0) as u8 } else { 255 }; let curr_ts = surface.get_matrix(); surface.reset_matrix(); surface.draw_surface( &sub_surface, 0.0, 0.0, a, skia::BlendMode::SourceOver, skia::FilterQuality::Low, ); surface.set_matrix(&curr_ts); bbox } fn create_layers( img_size: ScreenSize, ) -> SkiaLayers { layers::Layers::new(img_size, create_subimage, clear_image) } fn create_subimage( size: ScreenSize, ) -> Option { let mut img = try_create_surface!(size, None); img.fill(0, 0, 0, 0); Some(img) } fn clear_image(img: &mut skia::Surface) { img.fill(0, 0, 0, 0); } resvg-0.8.0/src/backend_skia/path.rs000066400000000000000000000044541352576375700173670ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::skia; use crate::prelude::*; use crate::ConvTransform; use super::style; pub fn draw( tree: &usvg::Tree, path: &usvg::Path, opt: &Options, blend_mode: skia::BlendMode, surface: &mut skia::Surface, ) -> Option { let bbox = path.data.bbox(); if path.visibility != usvg::Visibility::Visible { return bbox; } // `usvg` guaranties that path without a bbox will not use // a paint server with ObjectBoundingBox, // so we can pass whatever rect we want, because it will not be used anyway. let style_bbox = bbox.unwrap_or_else(|| Rect::new(0.0, 0.0, 1.0, 1.0).unwrap()); let mut skia_path = convert_path(&path.data); if let Some(ref fill) = path.fill { if fill.rule == usvg::FillRule::EvenOdd { skia_path.set_fill_type(skia::FillType::EvenOdd); } }; let antialias = crate::use_shape_antialiasing(path.rendering_mode); let global_ts = usvg::Transform::from_native(&surface.get_matrix()); if path.fill.is_some() { let mut fill = style::fill(tree, &path.fill, opt, style_bbox, global_ts); fill.set_anti_alias(antialias); fill.set_blend_mode(blend_mode); surface.draw_path(&skia_path, &fill); } if path.stroke.is_some() { let mut stroke = style::stroke(tree, &path.stroke, opt, style_bbox, global_ts); stroke.set_anti_alias(antialias); stroke.set_blend_mode(blend_mode); surface.draw_path(&skia_path, &stroke); } bbox } fn convert_path( path: &usvg::PathData, ) -> skia::Path { let mut s_path = skia::Path::new(); for seg in path.iter() { match *seg { usvg::PathSegment::MoveTo { x, y } => { s_path.move_to(x, y); } usvg::PathSegment::LineTo { x, y } => { s_path.line_to(x, y); } usvg::PathSegment::CurveTo { x1, y1, x2, y2, x, y } => { s_path.cubic_to(x1, y1, x2, y2, x, y); } usvg::PathSegment::ClosePath => { s_path.close(); } } } s_path } resvg-0.8.0/src/backend_skia/style.rs000066400000000000000000000163271352576375700175750ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::skia; use crate::{prelude::*, ConvTransform}; pub fn fill( tree: &usvg::Tree, fill: &Option, opt: &Options, bbox: Rect, global_ts: usvg::Transform, ) -> skia::Paint { let mut paint = skia::Paint::new(); paint.set_style(skia::PaintStyle::Fill); if let Some(ref fill) = fill { let opacity = fill.opacity; match fill.paint { usvg::Paint::Color(c) => { let a = f64_bound(0.0, opacity.value() * 255.0, 255.0) as u8; paint.set_color(c.red, c.green, c.blue, a); } usvg::Paint::Link(ref id) => { if let Some(node) = tree.defs_by_id(id) { match *node.borrow() { usvg::NodeKind::LinearGradient(ref lg) => { prepare_linear(lg, opacity, bbox, &mut paint); } usvg::NodeKind::RadialGradient(ref rg) => { prepare_radial(rg, opacity, bbox, &mut paint); } usvg::NodeKind::Pattern(ref pattern) => { prepare_pattern(&node, pattern, opt, global_ts, bbox, opacity, &mut paint); } _ => {} } } } } } paint } pub fn stroke( tree: &usvg::Tree, stroke: &Option, opt: &Options, bbox: Rect, global_ts: usvg::Transform, ) -> skia::Paint { let mut paint = skia::Paint::new(); paint.set_style(skia::PaintStyle::Stroke); if let Some(ref stroke) = stroke { let opacity = stroke.opacity; match stroke.paint { usvg::Paint::Color(c) => { let a = f64_bound(0.0, opacity.value() * 255.0, 255.0) as u8; paint.set_color(c.red, c.green, c.blue, a); } usvg::Paint::Link(ref id) => { if let Some(node) = tree.defs_by_id(id) { match *node.borrow() { usvg::NodeKind::LinearGradient(ref lg) => { prepare_linear(lg, opacity, bbox, &mut paint); } usvg::NodeKind::RadialGradient(ref rg) => { prepare_radial(rg, opacity, bbox, &mut paint); } usvg::NodeKind::Pattern(ref pattern) => { prepare_pattern(&node, pattern, opt, global_ts, bbox, opacity, &mut paint); } _ => {} } } } } let stroke_cap = match stroke.linecap { usvg::LineCap::Butt => skia::StrokeCap::Butt, usvg::LineCap::Round => skia::StrokeCap::Round, usvg::LineCap::Square => skia::StrokeCap::Square, }; paint.set_stroke_cap(stroke_cap); let stroke_join = match stroke.linejoin { usvg::LineJoin::Miter => skia::StrokeJoin::Miter, usvg::LineJoin::Round => skia::StrokeJoin::Round, usvg::LineJoin::Bevel => skia::StrokeJoin::Bevel, }; paint.set_stroke_join(stroke_join); paint.set_stroke_miter(stroke.miterlimit.value()); paint.set_stroke_width(stroke.width.value()); if let Some(ref list) = stroke.dasharray { let list: Vec<_> = list.iter().map(|n| *n as f32).collect(); let path_effect = skia::PathEffect::new_dash_path(&list, stroke.dashoffset); paint.set_path_effect(path_effect); } } paint } fn prepare_linear( g: &usvg::LinearGradient, opacity: usvg::Opacity, bbox: Rect, paint: &mut skia::Paint, ) { let gradient = skia::LinearGradient { start_point: (g.x1, g.y1), end_point: (g.x2, g.y2), base: prepare_base_gradient(g, opacity, &bbox) }; let shader = skia::Shader::new_linear_gradient(gradient); paint.set_shader(&shader); } fn prepare_radial( g: &usvg::RadialGradient, opacity: usvg::Opacity, bbox: Rect, paint: &mut skia::Paint, ) { let gradient = skia::RadialGradient { start_circle: (g.fx, g.fy, 0.0), end_circle: (g.cx, g.cy, g.r.value()), base: prepare_base_gradient(g, opacity, &bbox) }; let shader = skia::Shader::new_radial_gradient(gradient); paint.set_shader(&shader); } fn prepare_base_gradient( g: &usvg::BaseGradient, opacity: usvg::Opacity, bbox: &Rect ) -> skia::Gradient { let tile_mode = match g.spread_method { usvg::SpreadMethod::Pad => skia::TileMode::Clamp, usvg::SpreadMethod::Reflect => skia::TileMode::Mirror, usvg::SpreadMethod::Repeat => skia::TileMode::Repeat, }; let matrix = { if g.units == usvg::Units::ObjectBoundingBox { let mut ts = usvg::Transform::from_bbox(*bbox); ts.append(&g.transform); ts.to_native() } else { g.transform.to_native() } }; let mut colors: Vec = Vec::new(); let mut positions: Vec = Vec::new(); for stop in &g.stops { let a = (stop.opacity.value() * opacity.value() * 255.0) as u8; let color = skia::Color::new(a, stop.color.red, stop.color.green, stop.color.blue); colors.push(color.to_u32()); positions.push(stop.offset.value() as f32); } skia::Gradient { colors, positions, tile_mode, matrix } } fn prepare_pattern( pattern_node: &usvg::Node, pattern: &usvg::Pattern, opt: &Options, global_ts: usvg::Transform, bbox: Rect, opacity: usvg::Opacity, paint: &mut skia::Paint, ) { let r = if pattern.units == usvg::Units::ObjectBoundingBox { pattern.rect.bbox_transform(bbox) } else { pattern.rect }; let (sx, sy) = global_ts.get_scale(); let img_size = try_opt!(Size::new(r.width() * sx, r.height() * sy)).to_screen_size(); let mut surface = try_create_surface!(img_size, ()); surface.clear(); surface.scale(sx, sy); if let Some(vbox) = pattern.view_box { let ts = utils::view_box_to_transform(vbox.rect, vbox.aspect, r.size()); surface.concat(&ts.to_native()); } else if pattern.content_units == usvg::Units::ObjectBoundingBox { // 'Note that this attribute has no effect if attribute `viewBox` is specified.' // We don't use Transform::from_bbox(bbox) because `x` and `y` should be // ignored for some reasons... surface.scale(bbox.width(), bbox.height()); } let mut layers = super::create_layers(img_size); super::render_group(pattern_node, opt, &mut layers, &mut surface); let mut ts = usvg::Transform::default(); ts.append(&pattern.transform); ts.translate(r.x(), r.y()); ts.scale(1.0 / sx, 1.0 / sy); let shader = skia::Shader::new_from_surface_image(&surface, ts.to_native()); paint.set_shader(&shader); if !opacity.is_default() { let a = f64_bound(0.0, opacity.value() * 255.0, 255.0) as u8; paint.set_alpha(a); }; } resvg-0.8.0/src/filter.rs000066400000000000000000001157351352576375700153270ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::rc::Rc; use log::warn; use usvg::ColorInterpolation as ColorSpace; use crate::prelude::*; pub enum Error { #[allow(dead_code)] // Not used by raqote-backend. AllocFailed, InvalidRegion, } pub trait ImageExt: Sized { fn width(&self) -> u32; fn height(&self) -> u32; fn try_clone(&self) -> Result; fn clip(&mut self, region: ScreenRect); fn clear(&mut self); fn into_srgb(&mut self); fn into_linear_rgb(&mut self); } pub struct Image { /// Filter primitive result. /// /// All images have the same size which is equal to the current filter region. pub image: Rc, /// Image's region that has actual data. /// /// Region is in global coordinates and not in `image` one. /// /// Image's content outside this region will be transparent/cleared. /// /// Currently used only for `feTile`. pub region: ScreenRect, /// The current color space. pub color_space: ColorSpace, } impl Image { pub fn from_image(image: T, color_space: ColorSpace) -> Self { let (w, h) = (image.width(), image.height()); Image { image: Rc::new(image), region: ScreenRect::new(0, 0, w, h).unwrap(), color_space, } } pub fn into_color_space(self, color_space: ColorSpace) -> Result { if color_space != self.color_space { let region = self.region; let mut image = self.take()?; match color_space { ColorSpace::SRGB => image.into_srgb(), ColorSpace::LinearRGB => image.into_linear_rgb(), } Ok(Image { image: Rc::new(image), region, color_space, }) } else { Ok(self.clone()) } } pub fn take(self) -> Result { match Rc::try_unwrap(self.image) { Ok(v) => Ok(v), Err(v) => v.try_clone(), } } pub fn width(&self) -> u32 { self.image.width() } pub fn height(&self) -> u32 { self.image.height() } pub fn as_ref(&self) -> &T { &self.image } } // Do not use Clone derive because of https://github.com/rust-lang/rust/issues/26925 impl Clone for Image { fn clone(&self) -> Self { Image { image: self.image.clone(), region: self.region, color_space: self.color_space, } } } pub struct FilterResult { pub name: String, pub image: Image, } pub trait Filter { fn apply( filter: &usvg::Filter, bbox: Option, ts: &usvg::Transform, opt: &Options, canvas: &mut T, ) { let res = Self::_apply(filter, bbox, ts, opt, canvas); // Clear on error. if res.is_err() { canvas.clear(); } match res { Ok(_) => {} Err(Error::AllocFailed) => { warn!( "Memory allocation failed while processing the '{}' filter. Skipped.", filter.id ); } Err(Error::InvalidRegion) => { warn!("Filter '{}' has an invalid region.", filter.id); } } } fn _apply( filter: &usvg::Filter, bbox: Option, ts: &usvg::Transform, opt: &Options, canvas: &mut T, ) -> Result<(), Error> { let mut results = Vec::new(); let canvas_rect = ScreenRect::new(0, 0, canvas.width(), canvas.height()).unwrap(); let region = calc_region(filter, bbox, ts, canvas_rect)?; for primitive in &filter.children { let cs = primitive.color_interpolation; let subregion = calc_subregion(filter, primitive, bbox, region, ts, &results)?; let mut result = match primitive.kind { usvg::FilterKind::FeBlend(ref fe) => { let input1 = Self::get_input(&fe.input1, region, &results, canvas)?; let input2 = Self::get_input(&fe.input2, region, &results, canvas)?; Self::apply_blend(fe, cs, region, input1, input2) } usvg::FilterKind::FeFlood(ref fe) => { Self::apply_flood(fe, region) } usvg::FilterKind::FeGaussianBlur(ref fe) => { let input = Self::get_input(&fe.input, region, &results, canvas)?; Self::apply_blur(fe, filter.primitive_units, cs, bbox, ts, input) } usvg::FilterKind::FeOffset(ref fe) => { let input = Self::get_input(&fe.input, region, &results, canvas)?; Self::apply_offset(fe, filter.primitive_units, bbox, ts, input) } usvg::FilterKind::FeComposite(ref fe) => { let input1 = Self::get_input(&fe.input1, region, &results, canvas)?; let input2 = Self::get_input(&fe.input2, region, &results, canvas)?; Self::apply_composite(fe, cs, region, input1, input2) } usvg::FilterKind::FeMerge(ref fe) => { Self::apply_merge(fe, cs, region, &results, canvas) } usvg::FilterKind::FeTile(ref fe) => { let input = Self::get_input(&fe.input, region, &results, canvas)?; Self::apply_tile(input, region) } usvg::FilterKind::FeImage(ref fe) => { Self::apply_image(fe, region, subregion, opt) } usvg::FilterKind::FeComponentTransfer(ref fe) => { let input = Self::get_input(&fe.input, region, &results, canvas)?; Self::apply_component_transfer(fe, cs, input) } usvg::FilterKind::FeColorMatrix(ref fe) => { let input = Self::get_input(&fe.input, region, &results, canvas)?; Self::apply_color_matrix(fe, cs, input) } }?; if region != subregion { // Clip result. // TODO: explain let subregion2 = if let usvg::FilterKind::FeOffset(..) = primitive.kind { // We do not support clipping on feOffset. region.translate_to(0, 0) } else { subregion.translate(-region.x(), -region.y()) }; let color_space = result.color_space; let mut buffer = result.take()?; buffer.clip(subregion2); result = Image { image: Rc::new(buffer), region: subregion, color_space, }; } results.push(FilterResult { name: primitive.result.clone(), image: result, }); } if let Some(res) = results.pop() { Self::apply_to_canvas(res.image, region, canvas)?; } Ok(()) } fn get_input( input: &usvg::FilterInput, region: ScreenRect, results: &[FilterResult], canvas: &T, ) -> Result, Error>; fn apply_blur( fe: &usvg::FeGaussianBlur, units: usvg::Units, cs: ColorSpace, bbox: Option, ts: &usvg::Transform, input: Image, ) -> Result, Error>; fn apply_offset( fe: &usvg::FeOffset, units: usvg::Units, bbox: Option, ts: &usvg::Transform, input: Image, ) -> Result, Error>; fn apply_blend( fe: &usvg::FeBlend, cs: ColorSpace, region: ScreenRect, input1: Image, input2: Image, ) -> Result, Error>; fn apply_composite( fe: &usvg::FeComposite, cs: ColorSpace, region: ScreenRect, input1: Image, input2: Image, ) -> Result, Error>; fn apply_merge( fe: &usvg::FeMerge, cs: ColorSpace, region: ScreenRect, results: &[FilterResult], canvas: &T, ) -> Result, Error>; fn apply_flood( fe: &usvg::FeFlood, region: ScreenRect, ) -> Result, Error>; fn apply_tile( input: Image, region: ScreenRect, ) -> Result, Error>; fn apply_image( fe: &usvg::FeImage, region: ScreenRect, subregion: ScreenRect, opt: &Options, ) -> Result, Error>; fn apply_component_transfer( fe: &usvg::FeComponentTransfer, cs: ColorSpace, input: Image, ) -> Result, Error>; fn apply_color_matrix( fe: &usvg::FeColorMatrix, cs: ColorSpace, input: Image, ) -> Result, Error>; fn apply_to_canvas( input: Image, region: ScreenRect, canvas: &mut T, ) -> Result<(), Error>; fn resolve_std_dev( fe: &usvg::FeGaussianBlur, units: usvg::Units, bbox: Option, ts: &usvg::Transform, ) -> Option<(f64, f64, bool)> { // 'A negative value or a value of zero disables the effect of the given filter primitive // (i.e., the result is the filter input image).' if fe.std_dev_x.is_zero() && fe.std_dev_y.is_zero() { return None; } let (sx, sy) = ts.get_scale(); let (std_dx, std_dy) = if units == usvg::Units::ObjectBoundingBox { let bbox = bbox?; ( fe.std_dev_x.value() * sx * bbox.width(), fe.std_dev_y.value() * sy * bbox.height() ) } else { ( fe.std_dev_x.value() * sx, fe.std_dev_y.value() * sy ) }; if std_dx.is_fuzzy_zero() && std_dy.is_fuzzy_zero() { None } else { const BLUR_SIGMA_THRESHOLD: f64 = 2.0; // Check that the current feGaussianBlur filter can be applied using a box blur. let box_blur = std_dx >= BLUR_SIGMA_THRESHOLD && std_dy >= BLUR_SIGMA_THRESHOLD; Some((std_dx, std_dy, box_blur)) } } fn resolve_offset( fe: &usvg::FeOffset, units: usvg::Units, bbox: Option, ts: &usvg::Transform, ) -> Option<(f64, f64)> { let (sx, sy) = ts.get_scale(); let (dx, dy) = if units == usvg::Units::ObjectBoundingBox { let bbox = bbox?; ( fe.dx * sx * bbox.width(), fe.dy * sy * bbox.height() ) } else { ( fe.dx * sx, fe.dy * sy ) }; if dx.is_fuzzy_zero() && dy.is_fuzzy_zero() { None } else { Some((dx, dy)) } } } /// An IIR blur. /// /// Based on http://www.getreuer.info/home/gaussianiir /// /// Licensed under 'Simplified BSD License'. /// /// /// Implements the fast Gaussian convolution algorithm of Alvarez and Mazorra, /// where the Gaussian is approximated by a cascade of first-order infinite /// impulsive response (IIR) filters. Boundaries are handled with half-sample /// symmetric extension. /// /// Gaussian convolution is approached as approximating the heat equation and /// each timestep is performed with an efficient recursive computation. Using /// more steps yields a more accurate approximation of the Gaussian. A /// reasonable default value for `numsteps` is 4. /// /// Reference: /// Alvarez, Mazorra, "Signal and Image Restoration using Shock Filters and /// Anisotropic Diffusion," SIAM J. on Numerical Analysis, vol. 31, no. 2, /// pp. 590-605, 1994. pub mod iir_blur { struct BlurData { width: usize, height: usize, sigma_x: f64, sigma_y: f64, steps: usize, } /// Blurs an input image using IIR Gaussian filter. pub fn apply( data: &mut [u8], width: u32, height: u32, sigma_x: f64, sigma_y: f64, ) { let buf_size = (width * height) as usize; let mut buf = vec![0.0; buf_size]; let buf = &mut buf; // We convert number types to prevent redundant types conversion. let d = BlurData { width: width as usize, height: height as usize, sigma_x, sigma_y, steps: 4, }; gaussian_channel(data, &d, 0, buf); gaussian_channel(data, &d, 1, buf); gaussian_channel(data, &d, 2, buf); gaussian_channel(data, &d, 3, buf); } fn gaussian_channel( data: &mut [u8], d: &BlurData, channel: usize, buf: &mut Vec, ) { for i in 0..data.len() / 4 { buf[i] = data[i * 4 + channel] as f64 / 255.0; } gaussianiir2d(d, buf); for i in 0..data.len() / 4 { data[i * 4 + channel] = (buf[i] * 255.0) as u8; } } fn gaussianiir2d( d: &BlurData, buf: &mut Vec, ) { // Filter horizontally along each row. let (lambda_x, dnu_x) = if d.sigma_x > 0.0 { // let (lambda, dnu, boundary_scale) = gen_coefficients(d.sigma_x, d.steps); let (lambda, dnu) = gen_coefficients(d.sigma_x, d.steps); for y in 0..d.height { for _ in 0..d.steps { let idx = d.width * y; // TODO: Blurs right and bottom sides twice for some reasons. // e-filter-002.svg // buf[idx] *= boundary_scale; // Filter rightwards. for x in 1..d.width { buf[idx + x] += dnu * buf[idx + x - 1]; } let mut x = d.width - 1; // buf[idx + x] *= boundary_scale; // Filter leftwards. while x > 0 { buf[idx + x - 1] += dnu * buf[idx + x]; x -= 1; } } } (lambda, dnu) } else { (1.0, 1.0) }; // Filter vertically along each column. let (lambda_y, dnu_y) = if d.sigma_y > 0.0 { // let (lambda, dnu, boundary_scale) = gen_coefficients(d.sigma_y, d.steps); let (lambda, dnu) = gen_coefficients(d.sigma_y, d.steps); for x in 0..d.width { for _ in 0..d.steps { let idx = x; // buf[idx] *= boundary_scale; // Filter downwards. let mut y = d.width; while y < buf.len() { buf[idx + y] += dnu * buf[idx + y - d.width]; y += d.width; } y = buf.len() - d.width; // buf[idx + y] *= boundary_scale; // Filter upwards. while y > 0 { buf[idx + y - d.width] += dnu * buf[idx + y]; y -= d.width; } } } (lambda, dnu) } else { (1.0, 1.0) }; let post_scale = ((dnu_x * dnu_y).sqrt() / (lambda_x * lambda_y).sqrt()).powi(2 * d.steps as i32); buf.iter_mut().for_each(|v| *v *= post_scale); } fn gen_coefficients(sigma: f64, steps: usize) -> (f64, f64) { let lambda = (sigma * sigma) / (2.0 * steps as f64); let dnu = (1.0 + 2.0 * lambda - (1.0 + 4.0 * lambda).sqrt()) / (2.0 * lambda); // let boundary_scale = (1.0 / (1.0 - dnu)) / 2.0; // (lambda, dnu, boundary_scale) (lambda, dnu) } } /// A box blur. /// /// Based on https://github.com/fschutt/fastblur pub mod box_blur { use std::cmp; use rgb::{RGBA8, FromSlice}; pub fn apply( data: &mut [u8], width: u32, height: u32, sigma_x: f64, sigma_y: f64, ) { let boxes_horz = create_box_gauss(sigma_x as f32, 5); let boxes_vert = create_box_gauss(sigma_y as f32, 5); let mut backbuf = data.to_vec(); for (box_size_horz, box_size_vert) in boxes_horz.iter().zip(boxes_vert.iter()) { let radius_horz = ((box_size_horz - 1) / 2) as usize; let radius_vert = ((box_size_vert - 1) / 2) as usize; // We don't care if an input image is RGBA or BGRA // since all channels will be processed the same. box_blur( backbuf.as_rgba_mut(), data.as_rgba_mut(), width as usize, height as usize, radius_horz, radius_vert, ); } } /// If there is no valid size (e.g. radius is negative), returns `vec![1; len]` /// which would translate to blur radius of 0 #[inline] fn create_box_gauss( sigma: f32, n: usize, ) -> Vec { if sigma > 0.0 { let n_float = n as f32; // Ideal averaging filter width let w_ideal = (12.0 * sigma * sigma / n_float).sqrt() + 1.0; let mut wl = w_ideal.floor() as i32; if wl % 2 == 0 { wl -= 1; } let wu = wl + 2; let wl_float = wl as f32; let m_ideal = ( 12.0 * sigma * sigma - n_float * wl_float * wl_float - 4.0 * n_float * wl_float - 3.0 * n_float) / (-4.0 * wl_float - 4.0); let m = m_ideal.round() as usize; let mut sizes = Vec::new(); for i in 0..n { if i < m { sizes.push(wl); } else { sizes.push(wu); } } sizes } else { vec![1; n] } } /// Needs 2x the same image #[inline] fn box_blur( backbuf: &mut [RGBA8], frontbuf: &mut [RGBA8], width: usize, height: usize, blur_radius_horz: usize, blur_radius_vert: usize, ) { box_blur_vert(frontbuf, backbuf, width, height, blur_radius_vert); box_blur_horz(backbuf, frontbuf, width, height, blur_radius_horz); } #[inline] fn box_blur_vert( backbuf: &[RGBA8], frontbuf: &mut [RGBA8], width: usize, height: usize, blur_radius: usize, ) { if blur_radius == 0 { frontbuf.copy_from_slice(backbuf); return; } let iarr = 1.0 / (blur_radius + blur_radius + 1) as f32; let blur_radius_prev = blur_radius as isize - height as isize; let blur_radius_next = blur_radius as isize + 1; for i in 0..width { let col_start = i; //inclusive let col_end = i + width * (height - 1); //inclusive let mut ti = i; let mut li = ti; let mut ri = ti + blur_radius * width; let fv: RGBA8 = [0,0,0,0].into(); let lv: RGBA8 = [0,0,0,0].into(); let mut val_r = blur_radius_next * (fv.r as isize); let mut val_g = blur_radius_next * (fv.g as isize); let mut val_b = blur_radius_next * (fv.b as isize); let mut val_a = blur_radius_next * (fv.a as isize); // Get the pixel at the specified index, or the first pixel of the column // if the index is beyond the top edge of the image let get_top = |i| { if i < col_start { fv } else { backbuf[i] } }; // Get the pixel at the specified index, or the last pixel of the column // if the index is beyond the bottom edge of the image let get_bottom = |i| { if i > col_end { lv } else { backbuf[i] } }; for j in 0..cmp::min(blur_radius, height) { let bb = backbuf[ti + j * width]; val_r += bb.r as isize; val_g += bb.g as isize; val_b += bb.b as isize; val_a += bb.a as isize; } if blur_radius > height { val_r += blur_radius_prev * (lv.r as isize); val_g += blur_radius_prev * (lv.g as isize); val_b += blur_radius_prev * (lv.b as isize); val_a += blur_radius_prev * (lv.a as isize); } for _ in 0..cmp::min(height, blur_radius + 1) { let bb = get_bottom(ri); ri += width; val_r += sub(bb.r, fv.r); val_g += sub(bb.g, fv.g); val_b += sub(bb.b, fv.b); val_a += sub(bb.a, fv.a); frontbuf[ti] = [ round(val_r as f32 * iarr) as u8, round(val_g as f32 * iarr) as u8, round(val_b as f32 * iarr) as u8, round(val_a as f32 * iarr) as u8, ].into(); ti += width; } if height <= blur_radius { // otherwise `(height - blur_radius)` will underflow continue; } for _ in (blur_radius + 1)..(height - blur_radius) { let bb1 = backbuf[ri]; ri += width; let bb2 = backbuf[li]; li += width; val_r += sub(bb1.r, bb2.r); val_g += sub(bb1.g, bb2.g); val_b += sub(bb1.b, bb2.b); val_a += sub(bb1.a, bb2.a); frontbuf[ti] = [ round(val_r as f32 * iarr) as u8, round(val_g as f32 * iarr) as u8, round(val_b as f32 * iarr) as u8, round(val_a as f32 * iarr) as u8, ].into(); ti += width; } for _ in 0..cmp::min(height - blur_radius - 1, blur_radius) { let bb = get_top(li); li += width; val_r += sub(lv.r, bb.r); val_g += sub(lv.g, bb.g); val_b += sub(lv.b, bb.b); val_a += sub(lv.a, bb.a); frontbuf[ti] = [ round(val_r as f32 * iarr) as u8, round(val_g as f32 * iarr) as u8, round(val_b as f32 * iarr) as u8, round(val_a as f32 * iarr) as u8, ].into(); ti += width; } } } #[inline] fn box_blur_horz( backbuf: &[RGBA8], frontbuf: &mut [RGBA8], width: usize, height: usize, blur_radius: usize, ) { if blur_radius == 0 { frontbuf.copy_from_slice(backbuf); return; } let iarr = 1.0 / (blur_radius + blur_radius + 1) as f32; let blur_radius_prev = blur_radius as isize - width as isize; let blur_radius_next = blur_radius as isize + 1; for i in 0..height { let row_start = i * width; // inclusive let row_end = (i + 1) * width - 1; // inclusive let mut ti = i * width; // VERTICAL: $i; let mut li = ti; let mut ri = ti + blur_radius; let fv: RGBA8 = [0,0,0,0].into(); let lv: RGBA8 = [0,0,0,0].into(); let mut val_r = blur_radius_next * (fv.r as isize); let mut val_g = blur_radius_next * (fv.g as isize); let mut val_b = blur_radius_next * (fv.b as isize); let mut val_a = blur_radius_next * (fv.a as isize); // Get the pixel at the specified index, or the first pixel of the row // if the index is beyond the left edge of the image let get_left = |i| { if i < row_start { fv } else { backbuf[i] } }; // Get the pixel at the specified index, or the last pixel of the row // if the index is beyond the right edge of the image let get_right = |i| { if i > row_end { lv } else { backbuf[i] } }; for j in 0..cmp::min(blur_radius, width) { let bb = backbuf[ti + j]; // VERTICAL: ti + j * width val_r += bb.r as isize; val_g += bb.g as isize; val_b += bb.b as isize; val_a += bb.a as isize; } if blur_radius > width { val_r += blur_radius_prev * (lv.r as isize); val_g += blur_radius_prev * (lv.g as isize); val_b += blur_radius_prev * (lv.b as isize); val_a += blur_radius_prev * (lv.a as isize); } // Process the left side where we need pixels from beyond the left edge for _ in 0..cmp::min(width, blur_radius + 1) { let bb = get_right(ri); ri += 1; val_r += sub(bb.r, fv.r); val_g += sub(bb.g, fv.g); val_b += sub(bb.b, fv.b); val_a += sub(bb.a, fv.a); frontbuf[ti] = [ round(val_r as f32 * iarr) as u8, round(val_g as f32 * iarr) as u8, round(val_b as f32 * iarr) as u8, round(val_a as f32 * iarr) as u8, ].into(); ti += 1; // VERTICAL : ti += width, same with the other areas } if width <= blur_radius { // otherwise `(width - blur_radius)` will underflow continue; } // Process the middle where we know we won't bump into borders // without the extra indirection of get_left/get_right. This is faster. for _ in (blur_radius + 1)..(width - blur_radius) { let bb1 = backbuf[ri]; ri += 1; let bb2 = backbuf[li]; li += 1; val_r += sub(bb1.r, bb2.r); val_g += sub(bb1.g, bb2.g); val_b += sub(bb1.b, bb2.b); val_a += sub(bb1.a, bb2.a); frontbuf[ti] = [ round(val_r as f32 * iarr) as u8, round(val_g as f32 * iarr) as u8, round(val_b as f32 * iarr) as u8, round(val_a as f32 * iarr) as u8, ].into(); ti += 1; } // Process the right side where we need pixels from beyond the right edge for _ in 0..cmp::min(width - blur_radius - 1, blur_radius) { let bb = get_left(li); li += 1; val_r += sub(lv.r, bb.r); val_g += sub(lv.g, bb.g); val_b += sub(lv.b, bb.b); val_a += sub(lv.a, bb.a); frontbuf[ti] = [ round(val_r as f32 * iarr) as u8, round(val_g as f32 * iarr) as u8, round(val_b as f32 * iarr) as u8, round(val_a as f32 * iarr) as u8, ].into(); ti += 1; } } } /// Fast rounding for x <= 2^23. /// This is orders of magnitude faster than built-in rounding intrinsic. /// /// Source: https://stackoverflow.com/a/42386149/585725 #[inline] fn round(mut x: f32) -> f32 { x += 12582912.0; x -= 12582912.0; x } #[inline] fn sub(c1: u8, c2: u8) -> isize { c1 as isize - c2 as isize } } pub mod color_matrix { use super::*; #[inline] fn to_normalized_components(pixel: rgb::alt::BGRA8) -> (f64, f64, f64, f64) { (pixel.r as f64 / 255.0, pixel.g as f64 / 255.0, pixel.b as f64 / 255.0, pixel.a as f64 / 255.0) } #[inline] fn from_normalized(c: f64) -> u8 { (f64_bound(0.0, c, 1.0) * 255.0) as u8 } pub fn apply( kind: &usvg::FeColorMatrixKind, data: &mut [rgb::alt::BGRA8], ) { match kind { usvg::FeColorMatrixKind::Matrix(ref m) => { for pixel in data { let (r, g, b, a) = to_normalized_components(*pixel); let new_r = r * m[0] + g * m[1] + b * m[2] + a * m[3] + m[4]; let new_g = r * m[5] + g * m[6] + b * m[7] + a * m[8] + m[9]; let new_b = r * m[10] + g * m[11] + b * m[12] + a * m[13] + m[14]; let new_a = r * m[15] + g * m[16] + b * m[17] + a * m[18] + m[19]; pixel.r = from_normalized(new_r); pixel.g = from_normalized(new_g); pixel.b = from_normalized(new_b); pixel.a = from_normalized(new_a); } } usvg::FeColorMatrixKind::Saturate(v) => { let v = v.value(); let m = [ 0.213 + 0.787 * v, 0.715 - 0.715 * v, 0.072 - 0.072 * v, 0.213 - 0.213 * v, 0.715 + 0.285 * v, 0.072 - 0.072 * v, 0.213 - 0.213 * v, 0.715 - 0.715 * v, 0.072 + 0.928 * v, ]; for pixel in data { let (r, g, b, _) = to_normalized_components(*pixel); let new_r = r * m[0] + g * m[1] + b * m[2]; let new_g = r * m[3] + g * m[4] + b * m[5]; let new_b = r * m[6] + g * m[7] + b * m[8]; pixel.r = from_normalized(new_r); pixel.g = from_normalized(new_g); pixel.b = from_normalized(new_b); } } usvg::FeColorMatrixKind::HueRotate(angle) => { let angle = angle.to_radians(); let a1 = angle.cos(); let a2 = angle.sin(); let m = [ 0.213 + 0.787 * a1 - 0.213 * a2, 0.715 - 0.715 * a1 - 0.715 * a2, 0.072 - 0.072 * a1 + 0.928 * a2, 0.213 - 0.213 * a1 + 0.143 * a2, 0.715 + 0.285 * a1 + 0.140 * a2, 0.072 - 0.072 * a1 - 0.283 * a2, 0.213 - 0.213 * a1 - 0.787 * a2, 0.715 - 0.715 * a1 + 0.715 * a2, 0.072 + 0.928 * a1 + 0.072 * a2, ]; for pixel in data { let (r, g, b, _) = to_normalized_components(*pixel); let new_r = r * m[0] + g * m[1] + b * m[2]; let new_g = r * m[3] + g * m[4] + b * m[5]; let new_b = r * m[6] + g * m[7] + b * m[8]; pixel.r = from_normalized(new_r); pixel.g = from_normalized(new_g); pixel.b = from_normalized(new_b); } } usvg::FeColorMatrixKind::LuminanceToAlpha => { for pixel in data { let (r, g, b, _) = to_normalized_components(*pixel); let new_a = r * 0.2125 + g * 0.7154 + b * 0.0721; pixel.r = 0; pixel.g = 0; pixel.b = 0; pixel.a = from_normalized(new_a); } } } } } fn calc_region( filter: &usvg::Filter, bbox: Option, ts: &usvg::Transform, canvas_rect: ScreenRect, ) -> Result { let path = usvg::PathData::from_rect(filter.rect); let region_ts = if filter.units == usvg::Units::ObjectBoundingBox { let bbox = bbox.ok_or(Error::InvalidRegion)?; let bbox_ts = usvg::Transform::from_bbox(bbox); let mut ts2 = ts.clone(); ts2.append(&bbox_ts); ts2 } else { *ts }; let region = path.bbox_with_transform(region_ts, None) .ok_or_else(|| Error::InvalidRegion)? .to_screen_rect() .fit_to_rect(canvas_rect); Ok(region) } /// Returns filter primitive region. fn calc_subregion( filter: &usvg::Filter, primitive: &usvg::FilterPrimitive, bbox: Option, filter_region: ScreenRect, ts: &usvg::Transform, results: &[FilterResult], ) -> Result { // TODO: rewrite/simplify/explain/whatever let region = match primitive.kind { usvg::FilterKind::FeOffset(ref fe) => { // `feOffset` inherits it's region from the input. match fe.input { usvg::FilterInput::Reference(ref name) => { match results.iter().rev().find(|v| v.name == *name) { Some(ref res) => res.image.region, None => filter_region, } } _ => { filter_region } } } usvg::FilterKind::FeImage(..) => { // `feImage` uses the object bbox. if filter.primitive_units == usvg::Units::ObjectBoundingBox { let bbox = bbox.ok_or(Error::InvalidRegion)?; // TODO: wrong let ts_bbox = Rect::new(ts.e, ts.f, ts.a, ts.d).unwrap(); let r = Rect::new( primitive.x.unwrap_or(0.0), primitive.y.unwrap_or(0.0), primitive.width.unwrap_or(1.0), primitive.height.unwrap_or(1.0), ).ok_or_else(|| Error::InvalidRegion)?; let r = r .bbox_transform(bbox) .bbox_transform(ts_bbox) .to_screen_rect(); return Ok(r); } else { filter_region } } _ => filter_region, }; // TODO: Wrong! Does not account rotate and skew. let subregion = if filter.primitive_units == usvg::Units::ObjectBoundingBox { let subregion_bbox = Rect::new( primitive.x.unwrap_or(0.0), primitive.y.unwrap_or(0.0), primitive.width.unwrap_or(1.0), primitive.height.unwrap_or(1.0), ).ok_or_else(|| Error::InvalidRegion)?; region.to_rect().bbox_transform(subregion_bbox) } else { let (dx, dy) = ts.get_translate(); let (sx, sy) = ts.get_scale(); Rect::new( primitive.x.map(|n| n * sx + dx).unwrap_or(region.x() as f64), primitive.y.map(|n| n * sy + dy).unwrap_or(region.y() as f64), primitive.width.map(|n| n * sx).unwrap_or(region.width() as f64), primitive.height.map(|n| n * sy).unwrap_or(region.height() as f64), ).ok_or_else(|| Error::InvalidRegion)? }; Ok(subregion.to_screen_rect()) } /// Precomputed sRGB to LinearRGB table. /// /// Since we are storing the result in `u8`, there is no need to compute those /// values each time. Mainly because it's very expensive. /// /// ```text /// if (C_srgb <= 0.04045) /// C_lin = C_srgb / 12.92; /// else /// C_lin = pow((C_srgb + 0.055) / 1.055, 2.4); /// ``` /// /// Thanks to librsvg for the idea. pub const SRGB_TO_LINEAR_RGB_TABLE: &[u8; 256] = &[ 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 12, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 17, 18, 18, 19, 19, 20, 20, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 27, 27, 28, 29, 29, 30, 30, 31, 32, 32, 33, 34, 35, 35, 36, 37, 37, 38, 39, 40, 41, 41, 42, 43, 44, 45, 45, 46, 47, 48, 49, 50, 51, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 76, 77, 78, 79, 80, 81, 82, 84, 85, 86, 87, 88, 90, 91, 92, 93, 95, 96, 97, 99, 100, 101, 103, 104, 105, 107, 108, 109, 111, 112, 114, 115, 116, 118, 119, 121, 122, 124, 125, 127, 128, 130, 131, 133, 134, 136, 138, 139, 141, 142, 144, 146, 147, 149, 151, 152, 154, 156, 157, 159, 161, 163, 164, 166, 168, 170, 171, 173, 175, 177, 179, 181, 183, 184, 186, 188, 190, 192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222, 224, 226, 229, 231, 233, 235, 237, 239, 242, 244, 246, 248, 250, 253, 255, ]; /// Precomputed LinearRGB to sRGB table. /// /// Since we are storing the result in `u8`, there is no need to compute those /// values each time. Mainly because it's very expensive. /// /// ```text /// if (C_lin <= 0.0031308) /// C_srgb = C_lin * 12.92; /// else /// C_srgb = 1.055 * pow(C_lin, 1.0 / 2.4) - 0.055; /// ``` /// /// Thanks to librsvg for the idea. pub const LINEAR_RGB_TO_SRGB_TABLE: &[u8; 256] = &[ 0, 13, 22, 28, 34, 38, 42, 46, 50, 53, 56, 59, 61, 64, 66, 69, 71, 73, 75, 77, 79, 81, 83, 85, 86, 88, 90, 92, 93, 95, 96, 98, 99, 101, 102, 104, 105, 106, 108, 109, 110, 112, 113, 114, 115, 117, 118, 119, 120, 121, 122, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 148, 149, 150, 151, 152, 153, 154, 155, 155, 156, 157, 158, 159, 159, 160, 161, 162, 163, 163, 164, 165, 166, 167, 167, 168, 169, 170, 170, 171, 172, 173, 173, 174, 175, 175, 176, 177, 178, 178, 179, 180, 180, 181, 182, 182, 183, 184, 185, 185, 186, 187, 187, 188, 189, 189, 190, 190, 191, 192, 192, 193, 194, 194, 195, 196, 196, 197, 197, 198, 199, 199, 200, 200, 201, 202, 202, 203, 203, 204, 205, 205, 206, 206, 207, 208, 208, 209, 209, 210, 210, 211, 212, 212, 213, 213, 214, 214, 215, 215, 216, 216, 217, 218, 218, 219, 219, 220, 220, 221, 221, 222, 222, 223, 223, 224, 224, 225, 226, 226, 227, 227, 228, 228, 229, 229, 230, 230, 231, 231, 232, 232, 233, 233, 234, 234, 235, 235, 236, 236, 237, 237, 238, 238, 238, 239, 239, 240, 240, 241, 241, 242, 242, 243, 243, 244, 244, 245, 245, 246, 246, 246, 247, 247, 248, 248, 249, 249, 250, 250, 251, 251, 251, 252, 252, 253, 253, 254, 254, 255, 255, ]; pub fn from_premultiplied(data: &mut [rgb::alt::BGRA8]) { for p in data { let a = p.a as f64 / 255.0; p.b = (p.b as f64 / a + 0.5) as u8; p.g = (p.g as f64 / a + 0.5) as u8; p.r = (p.r as f64 / a + 0.5) as u8; } } pub fn into_premultiplied(data: &mut [rgb::alt::BGRA8]) { for p in data { let a = p.a as f64 / 255.0; p.b = (p.b as f64 * a + 0.5) as u8; p.g = (p.g as f64 * a + 0.5) as u8; p.r = (p.r as f64 * a + 0.5) as u8; } } resvg-0.8.0/src/geom.rs000066400000000000000000000214061352576375700147600ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. //! 2D geometric primitives. use std::{cmp, f64, fmt}; use usvg; pub use usvg::{Rect, Size}; /// Bounds `f64` number. #[inline] pub(crate) fn f64_bound(min: f64, val: f64, max: f64) -> f64 { debug_assert!(min.is_finite()); debug_assert!(val.is_finite()); debug_assert!(max.is_finite()); if val > max { return max; } else if val < min { return min; } val } /// A 2D screen size representation. /// /// Width and height are guarantee to be > 0. #[allow(missing_docs)] #[derive(Clone, Copy, PartialEq)] pub struct ScreenSize { width: u32, height: u32, } impl ScreenSize { /// Creates a new `ScreenSize` from values. #[inline] pub fn new(width: u32, height: u32) -> Option { if width > 0 && height > 0 { Some(ScreenSize { width, height }) } else { None } } /// Returns width. #[inline] pub fn width(&self) -> u32 { self.width } /// Returns height. #[inline] pub fn height(&self) -> u32 { self.height } /// Returns width and height as a tuple. #[inline] pub fn dimensions(&self) -> (u32, u32) { (self.width, self.height) } /// Scales current size to specified size. #[inline] pub fn scale_to(&self, to: Self) -> Self { size_scale(*self, to, false) } /// Expands current size to specified size. #[inline] pub fn expand_to(&self, to: Self) -> Self { size_scale(*self, to, true) } /// Converts the current `ScreenSize` to `Size`. #[inline] pub fn to_size(&self) -> Size { // Can't fail, because `ScreenSize` is always valid. Size::new(self.width as f64, self.height as f64).unwrap() } } impl fmt::Debug for ScreenSize { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "ScreenSize({} {})", self.width, self.height) } } impl fmt::Display for ScreenSize { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) } } /// Additional `Size` methods. pub trait SizeExt { /// Converts `Size` to `ScreenSize`. fn to_screen_size(&self) -> ScreenSize; } impl SizeExt for Size { #[inline] fn to_screen_size(&self) -> ScreenSize { ScreenSize::new( cmp::max(1, self.width().round() as u32), cmp::max(1, self.height().round() as u32), ).unwrap() } } fn size_scale( s1: ScreenSize, s2: ScreenSize, expand: bool, ) -> ScreenSize { let rw = (s2.height as f64 * s1.width as f64 / s1.height as f64).ceil() as u32; let with_h = if expand { rw <= s2.width } else { rw >= s2.width }; if !with_h { ScreenSize::new(rw, s2.height).unwrap() } else { let h = (s2.width as f64 * s1.height as f64 / s1.width as f64).ceil() as u32; ScreenSize::new(s2.width, h).unwrap() } } /// Additional `Rect` methods. pub trait RectExt: Sized { /// Transforms the `Rect` using the provided `bbox`. fn bbox_transform(&self, bbox: Rect) -> Self; /// Transforms the `Rect` using the provided `Transform`. /// /// This method is expensive. fn transform(&self, ts: &usvg::Transform) -> Option; /// Returns rect's size in screen units. fn to_screen_size(&self) -> ScreenSize; /// Returns rect in screen units. fn to_screen_rect(&self) -> ScreenRect; } impl RectExt for Rect { fn bbox_transform(&self, bbox: Rect) -> Self { let x = self.x() * bbox.width() + bbox.x(); let y = self.y() * bbox.height() + bbox.y(); let w = self.width() * bbox.width(); let h = self.height() * bbox.height(); Rect::new(x, y, w, h).unwrap() } fn transform(&self, ts: &usvg::Transform) -> Option { if !ts.is_default() { let path = &[ usvg::PathSegment::MoveTo { x: self.x(), y: self.y() }, usvg::PathSegment::LineTo { x: self.right(), y: self.y() }, usvg::PathSegment::LineTo { x: self.right(), y: self.bottom() }, usvg::PathSegment::LineTo { x: self.x(), y: self.bottom() }, usvg::PathSegment::ClosePath, ]; usvg::SubPathData(path).bbox_with_transform(*ts, None) } else { Some(*self) } } #[inline] fn to_screen_size(&self) -> ScreenSize { self.size().to_screen_size() } #[inline] fn to_screen_rect(&self) -> ScreenRect { ScreenRect::new( self.x() as i32, self.y() as i32, cmp::max(1, self.width().round() as u32), cmp::max(1, self.height().round() as u32), ).unwrap() } } /// A 2D screen rect representation. /// /// Width and height are guarantee to be > 0. #[allow(missing_docs)] #[derive(Clone, Copy, PartialEq)] pub struct ScreenRect { x: i32, y: i32, width: u32, height: u32, } impl ScreenRect { /// Creates a new `Rect` from values. #[inline] pub fn new(x: i32, y: i32, width: u32, height: u32) -> Option { if width > 0 && height > 0 { Some(ScreenRect { x, y, width, height }) } else { None } } /// Returns rect's size. #[inline] pub fn size(&self) -> ScreenSize { // Can't fail, because `ScreenSize` is always valid. ScreenSize::new(self.width, self.height).unwrap() } /// Returns rect's X position. #[inline] pub fn x(&self) -> i32 { self.x } /// Returns rect's Y position. #[inline] pub fn y(&self) -> i32 { self.y } /// Returns rect's width. #[inline] pub fn width(&self) -> u32 { self.width } /// Returns rect's height. #[inline] pub fn height(&self) -> u32 { self.height } /// Returns rect's left edge position. #[inline] pub fn left(&self) -> i32 { self.x } /// Returns rect's right edge position. #[inline] pub fn right(&self) -> i32 { self.x + self.width as i32 } /// Returns rect's top edge position. #[inline] pub fn top(&self) -> i32 { self.y } /// Returns rect's bottom edge position. #[inline] pub fn bottom(&self) -> i32 { self.y + self.height as i32 } /// Translates the rect by the specified offset. #[inline] pub fn translate(&self, tx: i32, ty: i32) -> Self { ScreenRect { x: self.x + tx, y: self.y + ty, width: self.width, height: self.height, } } /// Translates the rect to the specified position. #[inline] pub fn translate_to(&self, x: i32, y: i32) -> Self { ScreenRect { x, y, width: self.width, height: self.height, } } /// Checks that rect contains a point. #[inline] pub fn contains(&self, x: i32, y: i32) -> bool { if x < self.x || x > self.x + self.width as i32 - 1 { return false; } if y < self.y || y > self.y + self.height as i32 - 1 { return false; } true } /// Fits the current rect into the specified bounds. #[inline] pub fn fit_to_rect(&self, bounds: ScreenRect) -> Self { let mut r = *self; if r.x < 0 { r.x = 0; } if r.y < 0 { r.y = 0; } if r.right() > bounds.width as i32 { r.width = cmp::max(1, bounds.width as i32 - r.x) as u32; } if r.bottom() > bounds.height as i32 { r.height = cmp::max(1, bounds.height as i32 - r.y) as u32; } r } /// Converts into `Rect`. #[inline] pub fn to_rect(&self) -> Rect { // Can't fail, because `ScreenRect` is always valid. Rect::new(self.x as f64, self.y as f64, self.width as f64, self.height as f64).unwrap() } } impl fmt::Debug for ScreenRect { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "ScreenRect({} {} {} {})", self.x, self.y, self.width, self.height) } } impl fmt::Display for ScreenRect { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) } } #[cfg(test)] mod tests { use super::*; use usvg::FuzzyEq; #[test] fn bbox_transform_1() { let r = Rect::new(10.0, 20.0, 30.0, 40.0).unwrap(); assert!(r.bbox_transform(Rect::new(0.2, 0.3, 0.4, 0.5).unwrap()) .fuzzy_eq(&Rect::new(4.2, 10.3, 12.0, 20.0).unwrap())); } } resvg-0.8.0/src/image.rs000066400000000000000000000137151352576375700151170ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::{fs, path}; use log::warn; use crate::prelude::*; pub struct Image { pub data: ImageData, pub size: ScreenSize, } pub enum ImageData { RGB(Vec), RGBA(Vec), } pub fn load_raster( format: usvg::ImageFormat, data: &usvg::ImageData, opt: &Options, ) -> Option { let img = _load_raster(format, data, opt); if img.is_none() { match data { usvg::ImageData::Path(ref path) => { let path = get_abs_path(path, opt); warn!("Failed to load an external image: {:?}.", path); } usvg::ImageData::Raw(_) => { warn!("Failed to load an embedded image."); } } } img } fn _load_raster( format: usvg::ImageFormat, data: &usvg::ImageData, opt: &Options, ) -> Option { debug_assert!(format != usvg::ImageFormat::SVG); match data { usvg::ImageData::Path(ref path) => { let path = get_abs_path(path, opt); let data = fs::read(path).ok()?; if format == usvg::ImageFormat::JPEG { read_jpeg(&data) } else { read_png(&data) } } usvg::ImageData::Raw(ref data) => { if format == usvg::ImageFormat::JPEG { read_jpeg(data) } else { read_png(data) } } } } fn read_png(data: &[u8]) -> Option { let decoder = png::Decoder::new(data); let (info, mut reader) = decoder.read_info().ok()?; match info.color_type { png::ColorType::RGB | png::ColorType::RGBA => {} _ => return None, } let size = ScreenSize::new(info.width, info.height)?; let mut img_data = vec![0; info.buffer_size()]; reader.next_frame(&mut img_data).ok()?; let data = match info.color_type { png::ColorType::RGB => ImageData::RGB(img_data), png::ColorType::RGBA => ImageData::RGBA(img_data), _ => return None, }; Some(Image { data, size, }) } fn read_jpeg(data: &[u8]) -> Option { let mut decoder = jpeg_decoder::Decoder::new(data); let img_data = decoder.decode().ok()?; let info = decoder.info()?; let size = ScreenSize::new(info.width as u32, info.height as u32)?; let data = match info.pixel_format { jpeg_decoder::PixelFormat::RGB24 => ImageData::RGB(img_data), _ => return None, }; Some(Image { data, size, }) } pub fn load_sub_svg( data: &usvg::ImageData, opt: &Options, ) -> Option<(usvg::Tree, Options)> { let mut sub_opt = Options { usvg: usvg::Options { path: None, dpi: opt.usvg.dpi, font_family: opt.usvg.font_family.clone(), font_size: opt.usvg.font_size, languages: opt.usvg.languages.clone(), shape_rendering: opt.usvg.shape_rendering, text_rendering: opt.usvg.text_rendering, image_rendering: opt.usvg.image_rendering, keep_named_groups: false, }, fit_to: FitTo::Original, background: None, }; let tree = match data { usvg::ImageData::Path(ref path) => { let path = get_abs_path(path, opt); sub_opt.usvg.path = Some(path.clone()); usvg::Tree::from_file(path, &sub_opt.usvg).ok()? } usvg::ImageData::Raw(ref data) => { usvg::Tree::from_data(data, &sub_opt.usvg).ok()? } }; sanitize_sub_svg(&tree); Some((tree, sub_opt)) } fn sanitize_sub_svg( tree: &usvg::Tree, ) { // Remove all Image nodes. // // The referenced SVG image cannot have any 'image' elements by itself. // Not only recursive. Any. Don't know why. // TODO: implement drain or something to the rctree. let mut changed = true; while changed { changed = false; for mut node in tree.root().descendants() { let mut rm = false; if let usvg::NodeKind::Image(_) = *node.borrow() { rm = true; }; if rm { node.detach(); changed = true; break; } } } } pub fn prepare_sub_svg_geom( view_box: usvg::ViewBox, img_size: ScreenSize, ) -> (usvg::Transform, Option) { let r = view_box.rect; let new_size = utils::apply_view_box(&view_box, img_size); let (tx, ty, clip) = if view_box.aspect.slice { let (dx, dy) = utils::aligned_pos( view_box.aspect.align, 0.0, 0.0, new_size.width() as f64 - r.width(), new_size.height() as f64 - r.height(), ); (r.x() - dx, r.y() - dy, Some(r)) } else { let (dx, dy) = utils::aligned_pos( view_box.aspect.align, r.x(), r.y(), r.width() - new_size.width() as f64, r.height() - new_size.height() as f64, ); (dx, dy, None) }; let sx = new_size.width() as f64 / img_size.width() as f64; let sy = new_size.height() as f64 / img_size.height() as f64; let ts = usvg::Transform::new(sx, 0.0, 0.0, sy, tx, ty); (ts, clip) } pub fn image_rect( view_box: &usvg::ViewBox, img_size: ScreenSize, ) -> Rect { let new_size = utils::apply_view_box(view_box, img_size); let (x, y) = utils::aligned_pos( view_box.aspect.align, view_box.rect.x(), view_box.rect.y(), view_box.rect.width() - new_size.width() as f64, view_box.rect.height() - new_size.height() as f64, ); new_size.to_size().to_rect(x, y) } fn get_abs_path( rel_path: &path::Path, opt: &Options, ) -> path::PathBuf { match opt.usvg.path { Some(ref path) => path.parent().unwrap().join(rel_path), None => rel_path.into(), } } resvg-0.8.0/src/layers.rs000066400000000000000000000052251352576375700153310ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::cell::RefCell; use std::ops::Deref; use std::rc::Rc; use crate::ScreenSize; type LayerData = Rc>; /// Stack of image layers. /// /// Instead of creating a new layer each time we need one, /// we are reusing an existing one. pub struct Layers { d: Vec>, /// Use Rc as a shared counter. counter: Rc<()>, img_size: ScreenSize, new_img_fn: fn(ScreenSize) -> Option, clear_img_fn: fn(&mut T), } impl Layers { /// Creates `Layers`. pub fn new( img_size: ScreenSize, new_img_fn: fn(ScreenSize) -> Option, clear_img_fn: fn(&mut T), ) -> Self { Layers { d: Vec::new(), counter: Rc::new(()), img_size, new_img_fn, clear_img_fn, } } /// Returns a layer size. pub fn image_size(&self) -> ScreenSize { self.img_size } /// Returns a first free layer to draw on. /// /// - If there are no free layers - will create a new one. /// - If there is a free layer - it will clear it before return. /// - If a new layer allocation fail - will return `None`. pub fn get(&mut self) -> Option> { let used_layers = Rc::strong_count(&self.counter) - 1; if used_layers == self.d.len() { match (self.new_img_fn)(self.img_size) { Some(img) => { self.d.push(Rc::new(RefCell::new(img))); Some(Layer { d: self.d[self.d.len() - 1].clone(), _counter_holder: self.counter.clone(), }) } None => { None } } } else { { let img = self.d[used_layers].clone(); (self.clear_img_fn)(&mut img.borrow_mut()); } Some(Layer { d: self.d[used_layers].clone(), _counter_holder: self.counter.clone(), }) } } } impl Drop for Layers { fn drop(&mut self) { debug_assert!(Rc::strong_count(&self.counter) == 1); } } /// The layer object. pub struct Layer { d: LayerData, // When Layer goes out of scope, Layers::counter will be automatically decreased. _counter_holder: Rc<()>, } impl Deref for Layer { type Target = LayerData; fn deref(&self) -> &Self::Target { &self.d } } resvg-0.8.0/src/lib.rs000066400000000000000000000132041352576375700145740ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. /*! *resvg* is an [SVG](https://en.wikipedia.org/wiki/Scalable_Vector_Graphics) rendering library. *resvg* can be used to render SVG files based on a [static](http://www.w3.org/TR/SVG11/feature#SVG-static) [SVG Full 1.1](https://www.w3.org/TR/SVG/Overview.html) subset. In simple terms: no animations and scripting. It can be used as a simple SVG to PNG converted. And as an embeddable library to paint SVG on an application native canvas. */ #![doc(html_root_url = "https://docs.rs/resvg/0.8.0")] #![forbid(unsafe_code)] #![warn(missing_docs)] /// Unwraps `Option` and invokes `return` on `None`. macro_rules! try_opt { ($task:expr) => { match $task { Some(v) => v, None => return, } }; } /// Unwraps `Option` and invokes `return $ret` on `None`. macro_rules! try_opt_or { ($task:expr, $ret:expr) => { match $task { Some(v) => v, None => return $ret, } }; } /// Unwraps `Option` and invokes `return` on `None` with a warning. #[allow(unused_macros)] macro_rules! try_opt_warn { ($task:expr, $msg:expr) => { match $task { Some(v) => v, None => { log::warn!($msg); return; } } }; ($task:expr, $fmt:expr, $($arg:tt)*) => { match $task { Some(v) => v, None => { log::warn!($fmt, $($arg)*); return; } } }; } /// Unwraps `Option` and invokes `return $ret` on `None` with a warning. macro_rules! try_opt_warn_or { ($task:expr, $ret:expr, $msg:expr) => { match $task { Some(v) => v, None => { log::warn!($msg); return $ret; } } }; ($task:expr, $ret:expr, $fmt:expr, $($arg:tt)*) => { match $task { Some(v) => v, None => { log::warn!($fmt, $($arg)*); return $ret; } } }; } #[cfg(feature = "cairo-backend")] pub use cairo; #[cfg(feature = "qt-backend")] pub use resvg_qt as qt; #[cfg(feature = "skia-backend")] pub use resvg_skia as skia; #[cfg(feature = "raqote-backend")] pub use raqote; pub use usvg::{self, Error}; #[cfg(feature = "cairo-backend")] pub mod backend_cairo; #[cfg(feature = "qt-backend")] pub mod backend_qt; #[cfg(feature = "skia-backend")] pub mod backend_skia; #[cfg(feature = "raqote-backend")] pub mod backend_raqote; pub mod utils; mod filter; mod geom; mod image; mod layers; mod options; /// Commonly used types and traits. pub mod prelude { pub use usvg::{self, prelude::*}; pub use crate::{geom::*, options::*, utils, OutputImage, Render}; } pub use crate::geom::*; pub use crate::options::*; /// A generic interface for image rendering. /// /// Instead of using backend implementation directly, you can /// use this trait to write backend-independent code. pub trait Render { /// Renders SVG to image. /// /// Returns `None` if an image allocation failed. fn render_to_image( &self, tree: &usvg::Tree, opt: &Options, ) -> Option>; /// Renders SVG node to image. /// /// Returns `None` if an image allocation failed. fn render_node_to_image( &self, node: &usvg::Node, opt: &Options, ) -> Option>; } /// A generic interface for output image. pub trait OutputImage { /// Saves rendered image to the selected path. fn save_png( &mut self, path: &std::path::Path, ) -> bool; } /// Returns a default backend. /// /// - If both backends are enabled - cairo backend will be returned. /// - If no backends are enabled - will panic. /// - Otherwise will return a corresponding backend. #[allow(unreachable_code)] pub fn default_backend() -> Box { #[cfg(feature = "cairo-backend")] { return Box::new(backend_cairo::Backend); } #[cfg(feature = "qt-backend")] { return Box::new(backend_qt::Backend); } #[cfg(feature = "skia-backend")] { return Box::new(backend_skia::Backend); } #[cfg(feature = "raqote-backend")] { return Box::new(backend_raqote::Backend); } unreachable!("at least one backend must be enabled") } pub(crate) fn use_shape_antialiasing( mode: usvg::ShapeRendering, ) -> bool { match mode { usvg::ShapeRendering::OptimizeSpeed => false, usvg::ShapeRendering::CrispEdges => false, usvg::ShapeRendering::GeometricPrecision => true, } } /// Converts an image to an alpha mask. pub(crate) fn image_to_mask( data: &mut [rgb::alt::BGRA8], img_size: ScreenSize, ) { let width = img_size.width(); let height = img_size.height(); let coeff_r = 0.2125 / 255.0; let coeff_g = 0.7154 / 255.0; let coeff_b = 0.0721 / 255.0; for y in 0..height { for x in 0..width { let idx = (y * width + x) as usize; let ref mut pixel = data[idx]; let r = pixel.r as f64; let g = pixel.g as f64; let b = pixel.b as f64; let luma = r * coeff_r + g * coeff_g + b * coeff_b; pixel.r = 0; pixel.g = 0; pixel.b = 0; pixel.a = f64_bound(0.0, luma * 255.0, 255.0) as u8; } } } pub(crate) trait ConvTransform { fn to_native(&self) -> T; fn from_native(_: &T) -> Self; } resvg-0.8.0/src/options.rs000066400000000000000000000021151352576375700155200ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. /// Image fit options. #[derive(Clone, Copy, PartialEq, Debug)] pub enum FitTo { /// Keep original size. Original, /// Scale to width. Width(u32), /// Scale to height. Height(u32), /// Zoom by factor. Zoom(f32), } /// Rendering options. pub struct Options { /// `usvg` preprocessor options. pub usvg: usvg::Options, /// Fits the image using specified options. /// /// Does not affect rendering to canvas. pub fit_to: FitTo, /// An image background color. /// /// Sets an image background color. Does not affect rendering to canvas. /// /// `None` equals to transparent. pub background: Option, } impl Default for Options { fn default() -> Options { Options { usvg: usvg::Options::default(), fit_to: FitTo::Original, background: None, } } } resvg-0.8.0/src/utils.rs000066400000000000000000000025211352576375700151660ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. //! Some useful utilities. pub use usvg::utils::*; use super::prelude::*; use crate::FitTo; /// Returns `size` preprocessed according to `FitTo`. pub(crate) fn fit_to( size: ScreenSize, fit: FitTo, ) -> Option { let sizef = size.to_size(); match fit { FitTo::Original => { Some(size) } FitTo::Width(w) => { let h = (w as f64 * sizef.height() / sizef.width()).ceil(); ScreenSize::new(w, h as u32) } FitTo::Height(h) => { let w = (h as f64 * sizef.width() / sizef.height()).ceil(); ScreenSize::new(w as u32, h) } FitTo::Zoom(z) => { Size::new(sizef.width() * z as f64, sizef.height() * z as f64) .map(|s| s.to_screen_size()) } } } pub(crate) fn apply_view_box( vb: &usvg::ViewBox, img_size: ScreenSize, ) -> ScreenSize { let s = vb.rect.to_screen_size(); if vb.aspect.align == usvg::Align::None { s } else { if vb.aspect.slice { img_size.expand_to(s) } else { img_size.scale_to(s) } } } resvg-0.8.0/testing-tools/000077500000000000000000000000001352576375700155045ustar00rootroot00000000000000resvg-0.8.0/testing-tools/ci-install.sh000077500000000000000000000014641352576375700201070ustar00rootroot00000000000000#!/usr/bin/env bash set -e # Required to install MS fonts. sudo apt-get update # Install MS fonts since we are using Arial as a base font for tests. echo ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula select true | sudo debconf-set-selections sudo apt-get install -y ttf-mscorefonts-installer # Required for all backends. sudo apt-get install -y libharfbuzz-dev if [ "$RESVG_CAIRO_BACKEND" = true ]; then sudo apt-get install -y libcairo2-dev libgdk-pixbuf2.0-dev # for capi/examples/cairo-* sudo apt-get install -y libgtk-3-dev fi if [ "$RESVG_QT_BACKEND" = true ]; then sudo add-apt-repository ppa:beineri/opt-qt563-xenial -y sudo apt-get update -qq sudo apt-get install -qq qt56base qt56svg # to fix the -lGL linking sudo apt-get install -y libgl1-mesa-dev fi resvg-0.8.0/testing-tools/regression/000077500000000000000000000000001352576375700176645ustar00rootroot00000000000000resvg-0.8.0/testing-tools/regression/Cargo.toml000066400000000000000000000005271352576375700216200ustar00rootroot00000000000000[package] name = "regression" version = "0.1.0" authors = ["Evgeniy Reizner "] license = "MIT" edition = "2018" [workspace] [[bin]] name = "regression" path = "main.rs" [dependencies] image = { version = "0.22", default-features = false, features = ["png_codec"] } pico-args = "0.2" rayon = "1.1" wait-timeout = "0.2" resvg-0.8.0/testing-tools/regression/allow-cairo.txt000066400000000000000000000000001352576375700226240ustar00rootroot00000000000000resvg-0.8.0/testing-tools/regression/allow-qt.txt000066400000000000000000000000001352576375700221530ustar00rootroot00000000000000resvg-0.8.0/testing-tools/regression/allow-raqote.txt000066400000000000000000000000001352576375700230220ustar00rootroot00000000000000resvg-0.8.0/testing-tools/regression/allow-skia.txt000066400000000000000000000000001352576375700224560ustar00rootroot00000000000000resvg-0.8.0/testing-tools/regression/main.rs000066400000000000000000000177231352576375700211700ustar00rootroot00000000000000use std::fmt; use std::fs; use std::io::{self, BufRead}; use std::path::{Path, PathBuf}; use std::process::Command; use wait_timeout::ChildExt; use rayon::prelude::*; const RESVG_URL: &str = "https://github.com/RazrFalcon/resvg"; // List of files that should be skipped. const CRASH_ALLOWED: &[&str] = &[ "e-svg-007.svg", "e-svg-036.svg", ]; #[derive(Debug)] enum ErrorKind { CurrRenderFailed(io::Error), DifferentImageSizes, DifferentImages(usize), } #[derive(Debug)] struct Error { kind: ErrorKind, svg_file: PathBuf, } impl Error { fn new(kind: ErrorKind, svg_file: &Path) -> Self { Error { kind, svg_file: svg_file.to_path_buf() } } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let file_name = self.svg_file.file_name_str(); match self.kind { ErrorKind::CurrRenderFailed(ref e) => { write!(f, "{} rendering failed cause {}", file_name, e) } ErrorKind::DifferentImageSizes => { write!(f, "{} was rendered with different sizes", file_name) } ErrorKind::DifferentImages(n) => { write!(f, "{} is different by {} pixels", file_name, n) } } } } struct Args { backend: String, use_prev_commit: bool, in_dir: PathBuf, work_dir: PathBuf, } fn main() -> Result<(), Box> { let args = parse_args()?; // Build current version. Command::new("cargo") .args(&["build", "--release", "--features", &format!("{}-backend", args.backend)]) .current_dir("../../tools/rendersvg") .run()?; let curr_rendersvg = fs::canonicalize("../../target/release/rendersvg")?; let prev_rendersvg = build_previous_version(&args)?; let files = collect_files(&args)?; let errors: Vec<_> = files.into_par_iter().filter_map(|svg_path| { match process_file2(&args, &curr_rendersvg, &prev_rendersvg, &svg_path) { Ok(_) => None, Err(e) => Some(e), } }).collect(); if !errors.is_empty() { for e in errors { println!("Failed: {}.", e); } std::process::exit(1); } Ok(()) } fn parse_args() -> Result> { let mut args = pico_args::Arguments::from_env(); Ok(Args { backend: args.value_from_str("--backend")?.ok_or("backend is not set")?, use_prev_commit: args.contains("--use-prev-commit"), in_dir: args.free_from_str()?.ok_or("input dir is not set")?, work_dir: args.free_from_str()?.ok_or("work dir is not set")?, }) } fn build_previous_version(args: &Args) -> io::Result { let prev_resvg_dir = args.work_dir.join("resvg"); if prev_resvg_dir.exists() { return Ok(prev_resvg_dir.join("target/release/rendersvg")); } Command::new("git") .args(&["clone", "--depth", "5", RESVG_URL, prev_resvg_dir.to_str().unwrap()]) .run()?; if args.use_prev_commit { // TODO: maybe there is a better way Command::new("git") .args(&["reset", "--hard", "HEAD~1"]) .current_dir(&prev_resvg_dir) .run()?; } Command::new("cargo") .args(&["build", "--release", "--features", &format!("{}-backend", args.backend)]) .current_dir(prev_resvg_dir.join("tools/rendersvg")) .run()?; Ok(prev_resvg_dir.join("target/release/rendersvg")) } fn parse_allowed(backend: &str) -> io::Result> { let mut allowed_files: Vec<_> = CRASH_ALLOWED.iter().map(|s| s.to_string()).collect(); let file = fs::File::open(format!("allow-{}.txt", backend))?; for line in io::BufReader::new(file).lines() { allowed_files.push(line?); } Ok(allowed_files) } fn collect_files(args: &Args) -> io::Result> { assert!(args.in_dir.is_dir()); let allowed_files = parse_allowed(&args.backend)?; let mut files = Vec::new(); for entry in fs::read_dir(&args.in_dir)? { let path = entry?.path(); if path.is_file() { if allowed_files.iter().any(|s| s == path.as_path().file_name_str()) { continue; } files.push(path); } } files.sort(); Ok(files) } fn change_ext(mut path: PathBuf, suffix: &str, ext: &str) -> PathBuf { let stem = path.file_stem().unwrap().to_str().unwrap().to_string(); path.set_file_name(format!("{}-{}.{}", stem, suffix, ext)); path } fn render_svg( word_dir: &Path, render: &Path, backend: &str, in_svg: &Path, out_png: &Path, ) -> io::Result<()> { use std::process::Stdio; // Render with zoom by default to test scaling. // Images may render differently depending on scale. Command::new(render) .args(&[ "--backend", backend, "--zoom", "2", in_svg.to_str().unwrap(), out_png.to_str().unwrap(), ]) .current_dir(word_dir) .stderr(Stdio::null()) .run_with_timeout(15) } fn process_file2( args: &Args, curr_rendersvg: &Path, prev_rendersvg: &Path, svg_path: &Path, ) -> Result<(), Error> { let file = svg_path.file_name_str(); let curr_png = change_ext(args.work_dir.join(file), "curr", "png"); let prev_png = change_ext(args.work_dir.join(file), "prev", "png"); // remove leftovers let _ = fs::remove_file(&curr_png); let _ = fs::remove_file(&prev_png); process_file( &args, &curr_rendersvg, &prev_rendersvg, svg_path, &curr_png, &prev_png, )?; // remove temp files let _ = fs::remove_file(&curr_png); let _ = fs::remove_file(&prev_png); Ok(()) } fn process_file( args: &Args, curr_rendersvg: &Path, prev_rendersvg: &Path, in_svg: &Path, curr_png: &Path, prev_png: &Path, ) -> Result<(), Error> { if render_svg(&args.work_dir, prev_rendersvg, &args.backend, in_svg, prev_png).is_err() { return Ok(()); } if let Err(e) = render_svg(&args.work_dir, curr_rendersvg, &args.backend, in_svg, curr_png) { return Err(Error { kind: ErrorKind::CurrRenderFailed(e), svg_file: in_svg.to_path_buf(), }); } let prev_image = image::open(prev_png).unwrap().to_rgba(); let curr_image = image::open(curr_png).unwrap().to_rgba(); if curr_image.dimensions() != prev_image.dimensions() { return Err(Error::new(ErrorKind::DifferentImageSizes, in_svg)); } if *curr_image == *prev_image { return Ok(()); } let mut diff = 0; for (p1, p2) in curr_image.pixels().zip(prev_image.pixels()) { if p1 != p2 { diff += 1; } } if diff != 0 { return Err(Error::new(ErrorKind::DifferentImages(diff), in_svg)); } Ok(()) } trait PathExt { fn file_name_str(&self) -> &str; } impl PathExt for Path { fn file_name_str(&self) -> &str { self.file_name().unwrap().to_str().unwrap() } } trait CommandExt { fn run(&mut self) -> io::Result<()>; fn run_with_timeout(&mut self, sec: u64) -> io::Result<()>; } impl CommandExt for Command { fn run(&mut self) -> io::Result<()> { if self.status()?.success() { Ok(()) } else { // The actual error doesn't matter. Err(io::ErrorKind::Other.into()) } } fn run_with_timeout(&mut self, sec: u64) -> io::Result<()> { let mut child = self.spawn()?; let timeout = std::time::Duration::from_secs(sec); let status_code = match child.wait_timeout(timeout)? { Some(status) => status.code(), None => { child.kill()?; child.wait()?; return Err(io::ErrorKind::TimedOut.into()); } }; if status_code == Some(0) { Ok(()) } else { // The actual error doesn't matter. Err(io::ErrorKind::Other.into()) } } } resvg-0.8.0/testing-tools/run-tests.py000077500000000000000000000127541352576375700200360ustar00rootroot00000000000000#!/usr/bin/env python3 # Should work on Python >= 3.5 import argparse import os import platform import subprocess from subprocess import run from contextlib import contextmanager TESTS_URL = 'https://github.com/RazrFalcon/resvg-test-suite.git' SKIA_BUILD_URL = 'https://github.com/RazrFalcon/resvg-skia-ci-build.git' @contextmanager def cd(path): old_dir = os.getcwd() os.chdir(old_dir + '/' + path) yield os.chdir(old_dir) def regression_testing(backend): reg_work_dir = work_dir + '/' + ('workdir-' + backend) if not os.path.exists(reg_work_dir): os.mkdir(reg_work_dir) regression_args = ['cargo', 'run', '--release', '--', '--backend', backend, tests_dir, reg_work_dir] # Use a master branch for pull requests. if not local_test and os.environ['TRAVIS_BRANCH'] == 'master': regression_args.append('--use-prev-commit') run(regression_args, check=True) if platform.system() != 'Linux': print('Error: this script is Linux only.') exit(1) parser = argparse.ArgumentParser() parser.add_argument('--no-regression', help='Do not run regression testing', action='store_true') args = parser.parse_args() if os.getcwd().endswith('testing-tools'): os.chdir('..') if not os.path.exists('./target'): os.mkdir('./target') local_test = 'TRAVIS_BUILD_DIR' not in os.environ # clone tests on CI if not local_test: run(['git', 'clone', TESTS_URL, '--depth', '1', './target/resvg-test-suite'], check=True) if 'TRAVIS_BUILD_DIR' in os.environ: work_dir = os.path.abspath('.') tests_dir = os.path.abspath('./target/resvg-test-suite/svg') else: work_dir = '/tmp' tests_dir = os.path.abspath('../resvg-test-suite/svg') print('local_test:', local_test) print('work_dir:', work_dir) print('tests_dir:', tests_dir) # prepare skia on CI if not local_test and 'RESVG_SKIA_BACKEND' in os.environ: run(['git', 'clone', SKIA_BUILD_URL, '--depth', '1'], check=True) os.environ['SKIA_DIR'] = os.path.abspath('./resvg-skia-ci-build') os.environ['SKIA_LIB_DIR'] = os.path.abspath('./resvg-skia-ci-build/bin') os.environ['LD_LIBRARY_PATH'] = os.path.abspath('./resvg-skia-ci-build/bin') if 'RESVG_QT_BACKEND' in os.environ: # build qt backend with cd('tools/rendersvg'): run(['cargo', 'build', '--release', '--features', 'qt-backend'], check=True) # regression testing of the qt backend if not args.no_regression: with cd('testing-tools/regression'): if not local_test: os.environ['QT_QPA_PLATFORM'] = 'offscreen' run(['sudo', 'ln', '-s', '/usr/share/fonts', '/opt/qt56/lib/fonts'], check=True) regression_testing('qt') if 'RESVG_CAIRO_BACKEND' in os.environ: # build cairo backend with cd('tools/rendersvg'): run(['cargo', 'build', '--release', '--features', 'cairo-backend'], check=True) # regression testing of the cairo backend if not args.no_regression: with cd('testing-tools/regression'): regression_testing('cairo') if 'RESVG_RAQOTE_BACKEND' in os.environ: # build raqote backend with cd('tools/rendersvg'): run(['cargo', 'build', '--release', '--features', 'raqote-backend'], check=True) # regression testing of the cairo backend if not args.no_regression: with cd('testing-tools/regression'): regression_testing('raqote') if 'RESVG_SKIA_BACKEND' in os.environ: # build skia backend with cd('tools/rendersvg'): run(['cargo', 'build', '--release', '--features', 'skia-backend'], check=True) # regression testing of the cairo backend if not args.no_regression: with cd('testing-tools/regression'): regression_testing('skia') if 'RESVG_QT_BACKEND' in os.environ: # test Qt C-API # # build C-API for demo with cd('capi'): run(['cargo', 'build', '--features', 'qt-backend'], check=True) # run tests and build examples run(['cargo', 'test', '--features', 'qt-backend'], check=True) # test Qt C-API wrapper qmake_env = os.environ if local_test else dict(os.environ, QT_SELECT="5") with cd('capi/qtests'): defines = 'DEFINES+=LOCAL_BUILD' if local_test else '' run(['make', 'distclean']) run(['qmake', 'CONFIG+=debug', defines], env=qmake_env, check=True) run(['make'], check=True) run(['./tst_resvgqt'], env=dict(os.environ, LD_LIBRARY_PATH="../../target/debug"), check=True) with cd('tools/viewsvg'): run(['make', 'distclean']) run(['qmake', 'CONFIG+=debug'], env=qmake_env, check=True) run(['make'], check=True) if 'RESVG_CAIRO_BACKEND' in os.environ: # build cairo C example # # build C-API for cairo-capi with cd('capi'): run(['cargo', 'build', '--features', 'cairo-backend'], check=True) # run tests and build examples run(['cargo', 'test', '--features', 'cairo-backend'], check=True) with cd('examples/cairo-capi'): run(['make', 'clean'], check=True) run(['make'], check=True) # build cairo-rs example with cd('examples/cairo-rs'): run(['cargo', 'build'], check=True) if 'RESVG_RAQOTE_BACKEND' in os.environ: # run tests and build examples run(['cargo', 'test', '--release', '--features', 'raqote-backend'], check=True) if 'RESVG_SKIA_BACKEND' in os.environ: # run tests and build examples run(['cargo', 'test', '--release', '--features', 'skia-backend'], check=True) with cd('usvg'): run(['cargo', 'test', '--release'], check=True) resvg-0.8.0/tools/000077500000000000000000000000001352576375700140315ustar00rootroot00000000000000resvg-0.8.0/tools/explorer-thumbnailer/000077500000000000000000000000001352576375700202015ustar00rootroot00000000000000resvg-0.8.0/tools/explorer-thumbnailer/LICENSE.txt000077500000000000000000000167441352576375700220430ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. resvg-0.8.0/tools/explorer-thumbnailer/README.md000077500000000000000000000017631352576375700214720ustar00rootroot00000000000000# explorer-thumbnailer An SVG thumbnails generator for the Windows Explorer. ## Dependencies - Windows 64bit >= Vista - MSVC => 2015 - Qt => 5.6 built with MSVC ## Build ```batch "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64 set PATH=%userprofile%\.cargo\bin;C:\Qt\5.12.0\msvc2015_64\bin;%PATH% rem build C-API first set QT_DIR=C:\Qt\5.12.0\msvc2015_64 cargo build --release --features "qt-backend" --manifest-path ../../capi/Cargo.toml rem build thumbnailer qmake nmake rem prepare files for installer windeployqt --no-translations release\SVGThumbnailExtension.dll ``` See [BUILD.adoc](../../BUILD.adoc) for details. ## Origin This project is based on [SVG Viewer Extension for Windows Explorer](https://github.com/maphew/svg-explorer-extension). Unlike the original, it uses *resvg* with Qt5 instead of Qt4 SVG module. Also, it contains some code refactoring and a new installer. ## Licencse This project is licensed under the LGPL-3.0, just as an original project. resvg-0.8.0/tools/explorer-thumbnailer/explorer-thumbnailer.pro000077500000000000000000000011111352576375700250700ustar00rootroot00000000000000QT += winextras TARGET = SVGThumbnailExtension TEMPLATE = lib SOURCES += \ src/thumbnailprovider.cpp \ src/main.cpp \ src/classfactory.cpp HEADERS +=\ src/thumbnailprovider.h \ src/classfactory.h \ src/common.h DEF_FILE += \ src/thumbnailprovider.def CONFIG(release, debug|release): LIBS += -L$$PWD/../../target/release/ -lresvg else:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../target/debug/ -lresvg INCLUDEPATH += $$PWD/../../capi/include DEPENDPATH += $$PWD/../../capi/include LIBS += \ shlwapi.lib \ advapi32.lib \ gdiplus.lib resvg-0.8.0/tools/explorer-thumbnailer/install/000077500000000000000000000000001352576375700216475ustar00rootroot00000000000000resvg-0.8.0/tools/explorer-thumbnailer/install/LICENSE.LGPLv3-Qt.txt000077500000000000000000000177621352576375700251020ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE The Qt Toolkit is Copyright (C) 2016 The Qt Company Ltd. Contact: http://www.qt.io/licensing/ You may use, distribute and copy the Qt GUI Toolkit under the terms of GNU Lesser General Public License version 3, which is displayed below. This license makes reference to the version 3 of the GNU General Public License, which you can find in the LICENSE.GPLv3 file. ------------------------------------------------------------------------- GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright © 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this licensedocument, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, “this License†refers to version 3 of the GNU Lesser General Public License, and the “GNU GPL†refers to version 3 of the GNU General Public License. “The Library†refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An “Application†is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A “Combined Work†is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the “Linked Versionâ€. The “Minimal Corresponding Source†for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The “Corresponding Application Code†for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License “or any later version†applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. resvg-0.8.0/tools/explorer-thumbnailer/install/installer.iss000077500000000000000000000025461352576375700243760ustar00rootroot00000000000000[Setup] AppName="reSVG Explorer Extension" AppVersion="0.8.0" VersionInfoVersion="0.0.8.0" AppVerName="reSVG Explorer Extension 0.8.0" AppPublisher="Evgeniy Reizner" AppPublisherURL=https://github.com/RazrFalcon/resvg DefaultDirName="{pf}\reSVG Explorer Extension" Compression=lzma SolidCompression=yes ChangesAssociations=yes DisableDirPage=yes DisableProgramGroupPage=yes ArchitecturesAllowed=x64 ArchitecturesInstallIn64BitMode=x64 OutputBaseFilename="resvg-explorer-extension" OutputDir=. [Languages] Name: "en"; MessagesFile: "compiler:Default.isl"; LicenseFile: "..\LICENSE.txt" [Files] Source: "..\release\vc_redist.x64.exe"; DestDir: "{app}"; AfterInstall: InstallVcredist Source: "..\release\imageformats\qjpeg.dll"; DestDir: "{app}\imageformats"; Source: "..\release\platforms\qwindows.dll"; DestDir: "{app}\platforms"; Source: "..\release\Qt5Core.dll"; DestDir: "{app}"; Source: "..\release\Qt5Gui.dll"; DestDir: "{app}"; Source: "..\release\Qt5WinExtras.dll"; DestDir: "{app}" Source: "..\release\SVGThumbnailExtension.dll"; DestDir: "{app}"; Flags: regserver Source: "..\LICENSE.txt"; DestDir: "{app}"; Source: "LICENSE.LGPLv3-Qt.txt"; DestDir: "{app}"; [Code] procedure InstallVcredist; var ResultCode: Integer; begin Exec(ExpandConstant('{app}\vc_redist.x64.exe'), '/install /passive /norestart', '', SW_SHOWNORMAL, ewWaitUntilTerminated, ResultCode) end; resvg-0.8.0/tools/explorer-thumbnailer/src/000077500000000000000000000000001352576375700207705ustar00rootroot00000000000000resvg-0.8.0/tools/explorer-thumbnailer/src/classfactory.cpp000077500000000000000000000027731352576375700242050ustar00rootroot00000000000000#define INITGUID #include "common.h" #include "classfactory.h" STDAPI CThumbnailProvider_CreateInstance(REFIID riid, void** ppvObject); CClassFactory::CClassFactory() { DllAddRef(); } CClassFactory::~CClassFactory() { DllRelease(); } STDMETHODIMP CClassFactory::QueryInterface(REFIID riid, void** ppvObject) { static const QITAB qit[] = { QITABENT(CClassFactory, IClassFactory), {0}, }; return QISearch(this, qit, riid, ppvObject); } STDMETHODIMP_(ULONG) CClassFactory::AddRef() { LONG cRef = InterlockedIncrement(&m_cRef); return (ULONG)cRef; } STDMETHODIMP_(ULONG) CClassFactory::Release() { LONG cRef = InterlockedDecrement(&m_cRef); if (0 == cRef) { delete this; } return (ULONG)cRef; } STDMETHODIMP CClassFactory::CreateInstance(IUnknown* punkOuter, REFIID riid, void** ppvObject) { if (punkOuter) { return CLASS_E_NOAGGREGATION; } return CThumbnailProvider_CreateInstance(riid, ppvObject); } STDMETHODIMP CClassFactory::LockServer(BOOL fLock) { (void)fLock; return E_NOTIMPL; } STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void **ppv) { if (!ppv) { return E_INVALIDARG; } if (!IsEqualCLSID(CLSID_SvgThumbnailProvider, rclsid)) { return CLASS_E_CLASSNOTAVAILABLE; } CClassFactory *pcf; HRESULT hr; pcf = new CClassFactory(); if (!pcf) { return E_OUTOFMEMORY; } hr = pcf->QueryInterface(riid, ppv); pcf->Release(); return hr; } resvg-0.8.0/tools/explorer-thumbnailer/src/classfactory.h000077500000000000000000000006641352576375700236470ustar00rootroot00000000000000#pragma once #include class CClassFactory : public IClassFactory { public: CClassFactory(); // IUnknown methods STDMETHOD(QueryInterface)(REFIID, void**); STDMETHOD_(ULONG, AddRef)(); STDMETHOD_(ULONG, Release)(); // IClassFactory methods STDMETHOD(CreateInstance)(IUnknown*, REFIID, void**); STDMETHOD(LockServer)(BOOL); private: ~CClassFactory(); private: LONG m_cRef = 1; }; resvg-0.8.0/tools/explorer-thumbnailer/src/common.h000077500000000000000000000006301352576375700224330ustar00rootroot00000000000000#pragma once #include #include #include #include #include STDAPI_(ULONG) DllAddRef(); STDAPI_(ULONG) DllRelease(); STDAPI_(HINSTANCE) DllInstance(); #define szCLSID_SvgThumbnailProvider L"{EF399C53-03F4-489E-98BF-69E00F695ECD}" DEFINE_GUID(CLSID_SvgThumbnailProvider, 0xef399c53, 0x3f4, 0x489e, 0x98, 0xbf, 0x69, 0xe0, 0xf, 0x69, 0x5e, 0xcd); resvg-0.8.0/tools/explorer-thumbnailer/src/main.cpp000077500000000000000000000101051352576375700224200ustar00rootroot00000000000000#define INITGUID #include "common.h" #include HINSTANCE g_hinstDll = nullptr; LONG g_cRef = 0; typedef struct _REGKEY_DELETEKEY { HKEY hKey; LPCWSTR lpszSubKey; } REGKEY_DELETEKEY; typedef struct _REGKEY_SUBKEY_AND_VALUE { HKEY hKey; LPCWSTR lpszSubKey; LPCWSTR lpszValue; DWORD dwType; DWORD_PTR dwData; } REGKEY_SUBKEY_AND_VALUE; STDAPI CreateRegistryKeys(REGKEY_SUBKEY_AND_VALUE* aKeys, ULONG cKeys); STDAPI DeleteRegistryKeys(REGKEY_DELETEKEY* aKeys, ULONG cKeys); BOOL APIENTRY DllMain(HINSTANCE hinstDll, DWORD dwReason, LPVOID pvReserved) { Q_UNUSED(pvReserved) if (dwReason == DLL_PROCESS_ATTACH) { g_hinstDll = hinstDll; // TODO: This is ultra bad, but I have no idea how to fix this. // The problem is that Qt have to load platform plugins, // but the current directory is C:/WINDOWS/system32 // and not the install one. // // At the moment, the installer will disallow the install directory change. QCoreApplication::addLibraryPath("C:/Program Files/reSVG Explorer Extension"); // TODO: Doesn't need when the SVG doesn't have any text. int argc = 1; new QGuiApplication(argc, nullptr); } return TRUE; } STDAPI_(HINSTANCE) DllInstance() { return g_hinstDll; } STDAPI DllCanUnloadNow() { return g_cRef ? S_FALSE : S_OK; } STDAPI_(ULONG) DllAddRef() { LONG cRef = InterlockedIncrement(&g_cRef); return cRef; } STDAPI_(ULONG) DllRelease() { LONG cRef = InterlockedDecrement(&g_cRef); if (0 > cRef) { cRef = 0; } return cRef; } STDAPI DllRegisterServer() { WCHAR szModule[MAX_PATH]; ZeroMemory(szModule, sizeof(szModule)); GetModuleFileName(g_hinstDll, szModule, ARRAYSIZE(szModule)); REGKEY_SUBKEY_AND_VALUE keys[] = { {HKEY_CLASSES_ROOT, L"CLSID\\" szCLSID_SvgThumbnailProvider, nullptr, REG_SZ, (DWORD_PTR)L"SVG Thumbnail Provider"}, {HKEY_CLASSES_ROOT, L"CLSID\\" szCLSID_SvgThumbnailProvider L"\\InprocServer32", nullptr, REG_SZ, (DWORD_PTR)szModule}, {HKEY_CLASSES_ROOT, L"CLSID\\" szCLSID_SvgThumbnailProvider L"\\InprocServer32", L"ThreadingModel", REG_SZ, (DWORD_PTR)L"Apartment"}, {HKEY_CLASSES_ROOT, L".SVG\\shellex\\{E357FCCD-A995-4576-B01F-234630154E96}", nullptr, REG_SZ, (DWORD_PTR)szCLSID_SvgThumbnailProvider} }; return CreateRegistryKeys(keys, ARRAYSIZE(keys)); } STDAPI DllUnregisterServer() { REGKEY_DELETEKEY keys[] = {{HKEY_CLASSES_ROOT, L"CLSID\\" szCLSID_SvgThumbnailProvider}}; return DeleteRegistryKeys(keys, ARRAYSIZE(keys)); } STDAPI CreateRegistryKey(REGKEY_SUBKEY_AND_VALUE* pKey) { size_t cbData = 0; LPVOID pvData = nullptr; HRESULT hr = S_OK; switch(pKey->dwType) { case REG_DWORD: pvData = (LPVOID)(LPDWORD)&pKey->dwData; cbData = sizeof(DWORD); break; case REG_SZ: case REG_EXPAND_SZ: hr = StringCbLength((LPCWSTR)pKey->dwData, STRSAFE_MAX_CCH, &cbData); if (SUCCEEDED(hr)) { pvData = (LPVOID)(LPCWSTR)pKey->dwData; cbData += sizeof(WCHAR); } break; default: hr = E_INVALIDARG; } if (SUCCEEDED(hr)) { LSTATUS status = SHSetValue(pKey->hKey, pKey->lpszSubKey, pKey->lpszValue, pKey->dwType, pvData, (DWORD)cbData); if (status != NOERROR) { hr = HRESULT_FROM_WIN32(status); } } return hr; } STDAPI CreateRegistryKeys(REGKEY_SUBKEY_AND_VALUE* aKeys, ULONG cKeys) { HRESULT hr = S_OK; for (ULONG iKey = 0; iKey < cKeys; iKey++) { HRESULT hrTemp = CreateRegistryKey(&aKeys[iKey]); if (FAILED(hrTemp)) { hr = hrTemp; } } return hr; } STDAPI DeleteRegistryKeys(REGKEY_DELETEKEY* aKeys, ULONG cKeys) { HRESULT hr = S_OK; LSTATUS status; for (ULONG iKey = 0; iKey < cKeys; iKey++) { status = RegDeleteTree(aKeys[iKey].hKey, aKeys[iKey].lpszSubKey); if (status != NOERROR) { hr = HRESULT_FROM_WIN32(status); } } return hr; } resvg-0.8.0/tools/explorer-thumbnailer/src/thumbnailprovider.cpp000077500000000000000000000062451352576375700252440ustar00rootroot00000000000000#include "common.h" #include "thumbnailprovider.h" #include "gdiplus.h" #include using namespace Gdiplus; CThumbnailProvider::CThumbnailProvider() { DllAddRef(); m_cRef = 1; } CThumbnailProvider::~CThumbnailProvider() { if (m_pSite) { m_pSite->Release(); m_pSite = nullptr; } DllRelease(); } STDMETHODIMP CThumbnailProvider::QueryInterface(REFIID riid, void** ppvObject) { static const QITAB qit[] = { QITABENT(CThumbnailProvider, IInitializeWithStream), QITABENT(CThumbnailProvider, IThumbnailProvider), QITABENT(CThumbnailProvider, IObjectWithSite), {0}, }; return QISearch(this, qit, riid, ppvObject); } STDMETHODIMP_(ULONG) CThumbnailProvider::AddRef() { LONG cRef = InterlockedIncrement(&m_cRef); return (ULONG)cRef; } STDMETHODIMP_(ULONG) CThumbnailProvider::Release() { LONG cRef = InterlockedDecrement(&m_cRef); if (cRef == 0) { delete this; } return (ULONG)cRef; } STDMETHODIMP CThumbnailProvider::Initialize(IStream *pstm, DWORD grfMode) { Q_UNUSED(grfMode) STATSTG stat; if (pstm->Stat(&stat, STATFLAG_DEFAULT) != S_OK) { return S_FALSE; } char *data = new char[stat.cbSize.QuadPart]; ULONG len; if (pstm->Read(data, stat.cbSize.QuadPart, &len) != S_OK) { return S_FALSE; } // TODO: find a way to get the current file path, // which will allow relative images resolving. m_renderer.load(QByteArray(data, stat.cbSize.QuadPart)); return S_OK; } STDMETHODIMP CThumbnailProvider::GetThumbnail(UINT cx, HBITMAP *phbmp, WTS_ALPHATYPE *pdwAlpha) { *phbmp = nullptr; *pdwAlpha = WTSAT_ARGB; if (!m_renderer.isValid()) { return E_NOTIMPL; } QSize size = m_renderer.defaultSize(); int width, height; if (size.width() == size.height()){ width = cx; height = cx; } else if (size.width() > size.height()){ width = cx; height = size.height() * ((double)cx / (double)size.width()); } else { width = size.width() * ((double)cx / (double)size.height()); height = cx; } QImage img(width, height, QImage::Format_ARGB32); img.fill(Qt::transparent); QPainter painter(&img); m_renderer.render(&painter); painter.end(); *phbmp = QtWin::toHBITMAP(QPixmap::fromImage(img), QtWin::HBitmapAlpha); if (*phbmp != nullptr) { return NOERROR; } return E_NOTIMPL; } STDMETHODIMP CThumbnailProvider::GetSite(REFIID riid, void** ppvSite) { if (m_pSite) { return m_pSite->QueryInterface(riid, ppvSite); } return E_NOINTERFACE; } STDMETHODIMP CThumbnailProvider::SetSite(IUnknown* pUnkSite) { if (m_pSite) { m_pSite->Release(); m_pSite = nullptr; } m_pSite = pUnkSite; if (m_pSite) { m_pSite->AddRef(); } return S_OK; } STDAPI CThumbnailProvider_CreateInstance(REFIID riid, void** ppvObject) { *ppvObject = nullptr; CThumbnailProvider* ptp = new CThumbnailProvider(); if (!ptp) { return E_OUTOFMEMORY; } HRESULT hr = ptp->QueryInterface(riid, ppvObject); ptp->Release(); return hr; } resvg-0.8.0/tools/explorer-thumbnailer/src/thumbnailprovider.def000077500000000000000000000002711352576375700252110ustar00rootroot00000000000000LIBRARY "SVGThumbnailExtension" EXPORTS DllRegisterServer PRIVATE DllUnregisterServer PRIVATE DllGetClassObject PRIVATE DllCanUnloadNow PRIVATE resvg-0.8.0/tools/explorer-thumbnailer/src/thumbnailprovider.h000077500000000000000000000013501352576375700247010ustar00rootroot00000000000000#pragma once #include #include class CThumbnailProvider : public IThumbnailProvider, IObjectWithSite, IInitializeWithStream { public: CThumbnailProvider(); // IUnknown methods STDMETHOD(QueryInterface)(REFIID, void**); STDMETHOD_(ULONG, AddRef)(); STDMETHOD_(ULONG, Release)(); // IInitializeWithStream methods STDMETHOD(Initialize)(IStream*, DWORD); // IThumbnailProvider methods STDMETHOD(GetThumbnail)(UINT, HBITMAP*, WTS_ALPHATYPE*); // IObjectWithSite methods STDMETHOD(GetSite)(REFIID, void**); STDMETHOD(SetSite)(IUnknown*); private: ~CThumbnailProvider(); private: LONG m_cRef; IUnknown* m_pSite = nullptr; ResvgRenderer m_renderer; }; resvg-0.8.0/tools/kde-dolphin-thumbnailer/000077500000000000000000000000001352576375700205375ustar00rootroot00000000000000resvg-0.8.0/tools/kde-dolphin-thumbnailer/CMakeLists.txt000066400000000000000000000022561352576375700233040ustar00rootroot00000000000000project(resvgthumbnailer) cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR) set(QT_MIN_VERSION "5.6.0") find_package(ECM 1.0.0 REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(FeatureSummary) include(WriteBasicConfigVersionFile) include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings NO_POLICY_SCOPE) find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Core Gui) find_package(KF5 REQUIRED COMPONENTS KIO I18n Config) include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/../../capi/include ) # TODO: doesn't work #find_library(RESVG_LIBRARY NAMES "libresvg.a" PATHS ${CMAKE_CURRENT_SOURCE_DIR}/../../target/release NO_DEFAULT_PATH) set(resvgthumbnailer_PART_SRCS resvgthumbnailer.cpp ) add_library(resvgthumbnailer MODULE ${resvgthumbnailer_PART_SRCS}) target_link_libraries(resvgthumbnailer Qt5::Core Qt5::Gui KF5::KIOWidgets KF5::KIOCore KF5::I18n KF5::ConfigCore KF5::ConfigGui resvg) install(TARGETS resvgthumbnailer DESTINATION ${PLUGIN_INSTALL_DIR}) ########### install files ############### install(FILES resvgthumbnailer.desktop DESTINATION ${SERVICES_INSTALL_DIR}) resvg-0.8.0/tools/kde-dolphin-thumbnailer/README.md000066400000000000000000000014561352576375700220240ustar00rootroot00000000000000# kde-dolphin-thumbnailer An SVG thumbnails generator for the KDE's [Dolphin](https://www.kde.org/applications/system/dolphin/) file manager. ## Build ```bash # build and install C-API first in case you don't have resvg intalled already cargo build --release --features "qt-backend" --manifest-path ../../capi/Cargo.toml sudo cp ../../target/release/libresvg.so /usr/lib/ # build mkdir build cd build cmake .. -DCMAKE_INSTALL_PREFIX=`kf5-config --prefix` -DQT_PLUGIN_INSTALL_DIR=`kf5-config --qt-plugins` -DCMAKE_BUILD_TYPE=Release make # install sudo make intall ``` ## Enable In Dolphin, go to the Settings -> Configure Dolphin -> General -> Previews. Then disable *SVG Images* and enable *SVG Images (resvg)*. Also, it's a good idea to reset the thumbnails cache: ```bash rm -r ~/.cache/thumbnails ``` resvg-0.8.0/tools/kde-dolphin-thumbnailer/resvgthumbnailer.cpp000066400000000000000000000017201352576375700246240ustar00rootroot00000000000000#include "resvgthumbnailer.h" #include #include extern "C" { Q_DECL_EXPORT ThumbCreator *new_creator() { return new ResvgThumbnailer; } } bool ResvgThumbnailer::create(const QString& path, int width, int heigth, QImage& img) { ResvgRenderer renderer(path); if (!renderer.isValid() || renderer.isEmpty()) { return false; } const double ratio = static_cast(renderer.defaultSize().height()) / static_cast(renderer.defaultSize().width()); if (width < heigth) heigth = qRound(ratio * width); else width = qRound(heigth / ratio); QImage previewImage(width, heigth, QImage::Format_ARGB32_Premultiplied); previewImage.fill(Qt::transparent); QPainter p(&previewImage); renderer.render(&p); p.end(); img = previewImage; return true; } ThumbCreator::Flags ResvgThumbnailer::flags() const { return (Flags)(None); } resvg-0.8.0/tools/kde-dolphin-thumbnailer/resvgthumbnailer.desktop000066400000000000000000000003231352576375700255110ustar00rootroot00000000000000[Desktop Entry] Type=Service Name=SVG Images (resvg) X-KDE-ServiceTypes=ThumbCreator MimeType=image/svg+xml;image/svg+xml-compressed; X-KDE-Library=resvgthumbnailer ServiceTypes=ThumbCreator CacheThumbnail=true resvg-0.8.0/tools/kde-dolphin-thumbnailer/resvgthumbnailer.h000066400000000000000000000004511352576375700242710ustar00rootroot00000000000000#ifndef RESVG_THUMBNAILER_H #define RESVG_THUMBNAILER_H #include class ResvgThumbnailer : public ThumbCreator { public: ResvgThumbnailer() {} bool create(const QString& path, int width, int height, QImage& img) override; Flags flags() const override; }; #endif resvg-0.8.0/tools/rendersvg/000077500000000000000000000000001352576375700160305ustar00rootroot00000000000000resvg-0.8.0/tools/rendersvg/.gitignore000066400000000000000000000000371352576375700200200ustar00rootroot00000000000000/*.svg /*.png /callgrind.out.* resvg-0.8.0/tools/rendersvg/Cargo.toml000066400000000000000000000007171352576375700177650ustar00rootroot00000000000000[package] name = "rendersvg" version = "0.8.0" authors = ["Evgeniy Reizner "] keywords = ["svg", "render", "raster"] license = "MPL-2.0" edition = "2018" workspace = "../../" [dependencies] fern = "0.5" log = "0.4" pico-args = "0.2" resvg = { path = "../../" } time = "0.1" [features] cairo-backend = ["resvg/cairo-backend"] qt-backend = ["resvg/qt-backend"] raqote-backend = ["resvg/raqote-backend"] skia-backend = ["resvg/skia-backend"] resvg-0.8.0/tools/rendersvg/LICENSE.txt000066400000000000000000000405261352576375700176620ustar00rootroot00000000000000Mozilla Public License Version 2.0 ================================== 1. Definitions -------------- 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or (b) that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or (b) any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions -------------------------------- 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and (b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: (a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or (c) under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities ------------------- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation --------------------------------------------------- If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination -------------- 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ************************************************************************ * * * 6. Disclaimer of Warranty * * ------------------------- * * * * Covered Software is provided under this License on an "as is" * * basis, without warranty of any kind, either expressed, implied, or * * statutory, including, without limitation, warranties that the * * Covered Software is free of defects, merchantable, fit for a * * particular purpose or non-infringing. The entire risk as to the * * quality and performance of the Covered Software is with You. * * Should any Covered Software prove defective in any respect, You * * (not any Contributor) assume the cost of any necessary servicing, * * repair, or correction. This disclaimer of warranty constitutes an * * essential part of this License. No use of any Covered Software is * * authorized under this License except under this disclaimer. * * * ************************************************************************ ************************************************************************ * * * 7. Limitation of Liability * * -------------------------- * * * * Under no circumstances and under no legal theory, whether tort * * (including negligence), contract, or otherwise, shall any * * Contributor, or anyone who distributes Covered Software as * * permitted above, be liable to You for any direct, indirect, * * special, incidental, or consequential damages of any character * * including, without limitation, damages for lost profits, loss of * * goodwill, work stoppage, computer failure or malfunction, or any * * and all other commercial damages or losses, even if such party * * shall have been informed of the possibility of such damages. This * * limitation of liability shall not apply to liability for death or * * personal injury resulting from such party's negligence to the * * extent applicable law prohibits such limitation. Some * * jurisdictions do not allow the exclusion or limitation of * * incidental or consequential damages, so this exclusion and * * limitation may not apply to You. * * * ************************************************************************ 8. Litigation ------------- Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ---------------- This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License --------------------------- 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice ------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice --------------------------------------------------------- This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. resvg-0.8.0/tools/rendersvg/README.md000066400000000000000000000007711352576375700173140ustar00rootroot00000000000000# rendersvg *rendersvg* is an [SVG](https://en.wikipedia.org/wiki/Scalable_Vector_Graphics) rendering application. ## Build ```bash # Build with a Qt backend cargo build --release --features="qt-backend" # or with a cairo backend cargo build --release --features="cairo-backend" # or with both. cargo build --release --features="qt-backend cairo-backend" ``` See [BUILD.adoc](../../BUILD.adoc) for details. ## License *rendersvg* is licensed under the [MPLv2.0](https://www.mozilla.org/en-US/MPL/). resvg-0.8.0/tools/rendersvg/src/000077500000000000000000000000001352576375700166175ustar00rootroot00000000000000resvg-0.8.0/tools/rendersvg/src/args.rs000066400000000000000000000231331352576375700201230ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::path; use std::process; use pico_args::Arguments; use resvg::prelude::*; pub fn print_help() { print!("\ rendersvg is an SVG rendering application. USAGE: rendersvg [OPTIONS] rendersvg in.svg out.png rendersvg -z 4 in.svg out.png rendersvg --query-all in.svg OPTIONS: --help Prints help information -V, --version Prints version information --backend BACKEND Sets the rendering backend. Has no effect if built with only one backend [default: {}] [possible values: {}] -w, --width LENGTH Sets the width in pixels -h, --height LENGTH Sets the height in pixels -z, --zoom FACTOR Zooms the image by a factor --dpi DPI Sets the resolution [default: 96] [possible values: 10..4000] --background COLOR Sets the background color. Examples: red, #fff, #fff000 --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'] --shape-rendering HINT Selects the default shape rendering method. [default: geometricPrecision] [possible values: optimizeSpeed, crispEdges, geometricPrecision] --text-rendering HINT Selects the default text rendering method. [default: optimizeLegibility] [possible values: optimizeSpeed, optimizeLegibility, geometricPrecision] --image-rendering HINT Selects the default image rendering method. [default: optimizeQuality] [possible values: optimizeQuality, optimizeSpeed] --query-all Queries all valid SVG ids with bounding boxes --export-id ID Renders an object only with a specified ID --perf Prints performance stats --pretend Does all the steps except rendering --quiet Disables warnings --dump-svg PATH Saves the preprocessed SVG to the selected file ARGS: Input file Output file ", default_backend(), backends().join(", ")); } #[derive(Debug)] struct CliArgs { help: bool, version: bool, backend: String, width: Option, height: Option, zoom: Option, dpi: u32, background: Option, font_family: String, font_size: u32, languages: Vec, shape_rendering: usvg::ShapeRendering, text_rendering: usvg::TextRendering, image_rendering: usvg::ImageRendering, query_all: bool, export_id: Option, perf: bool, pretend: bool, quiet: bool, dump_svg: Option, free: Vec, } fn collect_args() -> Result { let mut input = Arguments::from_env(); Ok(CliArgs { help: input.contains("--help"), version: input.contains(["-V", "--version"]), backend: input.value_from_str("--backend")?.unwrap_or(default_backend()), width: input.value_from_fn(["-w", "--width"], parse_length)?, height: input.value_from_fn(["-h", "--height"], parse_length)?, zoom: input.value_from_fn(["-z", "--zoom"], parse_zoom)?, dpi: input.value_from_fn("--dpi", parse_dpi)?.unwrap_or(96), background: input.value_from_str("--background")?, font_family: input.value_from_str("--font-family")? .unwrap_or_else(|| "Times New Roman".to_string()), font_size: input.value_from_fn("--font-size", parse_font_size)?.unwrap_or(12), languages: input.value_from_fn("--languages", parse_languages)? .unwrap_or(vec!["en".to_string()]), // TODO: use system language shape_rendering: input.value_from_str("--shape-rendering")?.unwrap_or_default(), text_rendering: input.value_from_str("--text-rendering")?.unwrap_or_default(), image_rendering: input.value_from_str("--image-rendering")?.unwrap_or_default(), query_all: input.contains("--query-all"), export_id: input.value_from_str("--export-id")?, perf: input.contains("--perf"), pretend: input.contains("--pretend"), quiet: input.contains("--quiet"), dump_svg: input.value_from_str("--dump-svg")?, free: input.free()?, }) } fn parse_dpi(s: &str) -> Result { let n: u32 = s.parse().map_err(|_| "invalid number")?; if n >= 10 && n <= 4000 { Ok(n) } else { Err("DPI out of bounds".to_string()) } } fn parse_length(s: &str) -> Result { let n: u32 = s.parse().map_err(|_| "invalid length")?; if n > 0 { Ok(n) } else { Err("LENGTH cannot be zero".to_string()) } } fn parse_zoom(s: &str) -> Result { let n: f32 = s.parse().map_err(|_| "invalid zoom factor")?; if n > 0.0 { Ok(n) } else { Err("ZOOM should be positive".to_string()) } } fn parse_font_size(s: &str) -> Result { let n: u32 = s.parse().map_err(|_| "invalid number")?; if n > 0 && n <= 192 { Ok(n) } else { Err("font size out of bounds".to_string()) } } fn parse_languages(s: &str) -> Result, String> { 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".to_string()); } Ok(langs) } pub struct Args { pub in_svg: path::PathBuf, pub out_png: Option, pub backend_name: String, pub query_all: bool, pub export_id: Option, pub dump: Option, pub pretend: bool, pub perf: bool, pub quiet: bool, } pub fn parse() -> Result<(Args, resvg::Options), String> { let args = collect_args().map_err(|e| e.to_string())?; if args.help { print_help(); process::exit(0); } if args.version { println!("{}", env!("CARGO_PKG_VERSION")); process::exit(0); } let positional_count = if args.query_all { 1 } else { 2 }; if args.free.len() != positional_count { return Err(format!(" and must be set")); } let in_svg: path::PathBuf = args.free[0].to_string().into(); let out_png = if !args.query_all { Some(args.free[1].to_string().into()) } else { None }; let dump = args.dump_svg.map(|v| v.into()); let export_id = args.export_id.map(|v| v.to_string()); let app_args = Args { in_svg: in_svg.clone(), out_png, backend_name: args.backend, query_all: args.query_all, export_id, dump, pretend: args.pretend, perf: args.perf, quiet: args.quiet, }; // We don't have to keep named groups when we don't need them // because it will slow down rendering. let keep_named_groups = app_args.query_all || app_args.export_id.is_some(); let mut fit_to = FitTo::Original; if let Some(w) = args.width { fit_to = FitTo::Width(w); } else if let Some(h) = args.height { fit_to = FitTo::Height(h); } else if let Some(z) = args.zoom { fit_to = FitTo::Zoom(z); } let opt = resvg::Options { usvg: usvg::Options { path: Some(in_svg.into()), dpi: args.dpi as f64, font_family: args.font_family.clone(), font_size: args.font_size as f64, languages: args.languages, shape_rendering: args.shape_rendering, text_rendering: args.text_rendering, image_rendering: args.image_rendering, keep_named_groups, }, fit_to, background: args.background, }; Ok((app_args, opt)) } #[allow(unreachable_code)] fn default_backend() -> String { #[cfg(feature = "cairo-backend")] { return "cairo".to_string() } #[cfg(feature = "qt-backend")] { return "qt".to_string() } #[cfg(feature = "skia-backend")] { return "skia".to_string() } #[cfg(feature = "raqote-backend")] { return "raqote".to_string() } unreachable!(); } fn backends() -> Vec<&'static str> { let mut list = Vec::new(); #[cfg(feature = "cairo-backend")] { list.push("cairo"); } #[cfg(feature = "qt-backend")] { list.push("qt"); } #[cfg(feature = "skia-backend")] { list.push("skia"); } #[cfg(feature = "raqote-backend")] { list.push("raqote"); } list } resvg-0.8.0/tools/rendersvg/src/main.rs000066400000000000000000000117061352576375700201160ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::fmt; use std::fs; use std::io::Write; use std::path; use resvg::prelude::*; mod args; macro_rules! bail { ($msg:expr) => { return Err(format!("{}", $msg)); }; ($fmt:expr, $($arg:tt)*) => { return Err(format!($fmt, $($arg)*)); }; } fn main() { if let Err(e) = process() { eprintln!("Error: {}.", e); std::process::exit(1); } } fn process() -> Result<(), String> { #[cfg(all(not(feature = "cairo-backend"), not(feature = "qt-backend"), not(feature = "skia-backend"), not(feature = "raqote-backend")))] { bail!("rendersvg has been built without any backends") } let (args, opt) = match args::parse() { Ok((args, opt)) => (args, opt), Err(e) => { args::print_help(); println!(); bail!(e) } }; // Do not print warning during the ID querying. // // Some crates still can print to stdout/stderr, but we can't do anything about it. if !(args.query_all || args.quiet) { fern::Dispatch::new() .format(log_format) .level(log::LevelFilter::Warn) .chain(std::io::stderr()) .apply() .unwrap(); } let backend: Box = match args.backend_name.as_str() { #[cfg(feature = "cairo-backend")] "cairo" => Box::new(resvg::backend_cairo::Backend), #[cfg(feature = "qt-backend")] "qt" => Box::new(resvg::backend_qt::Backend), #[cfg(feature = "skia-backend")] "skia" => Box::new(resvg::backend_skia::Backend), #[cfg(feature = "raqote-backend")] "raqote" => Box::new(resvg::backend_raqote::Backend), _ => bail!("unknown backend"), }; macro_rules! timed { ($name:expr, $task:expr) => { run_task(args.perf, $name, || $task) }; } // Load file. let tree = timed!("Preprocessing", { usvg::Tree::from_file(&args.in_svg, &opt.usvg).map_err(|e| e.to_string()) })?; if args.query_all { return query_all(&tree); } // Dump before rendering in case of panic. if let Some(ref dump_path) = args.dump { dump_svg(&tree, dump_path)?; } if args.pretend { return Ok(()); } // Render. if let Some(ref out_png) = args.out_png { let img = if let Some(ref id) = args.export_id { if let Some(node) = tree.root().descendants().find(|n| &*n.id() == id) { timed!("Rendering", backend.render_node_to_image(&node, &opt)) } else { bail!("SVG doesn't have '{}' ID", id) } } else { timed!("Rendering", backend.render_to_image(&tree, &opt)) }; match img { Some(mut img) => { timed!("Saving", img.save_png(out_png)); } None => { bail!("failed to allocate an image") } } }; Ok(()) } fn query_all(tree: &usvg::Tree) -> Result<(), String> { let mut count = 0; for node in tree.root().descendants() { if tree.is_in_defs(&node) { continue; } if node.id().is_empty() { continue; } count += 1; fn round_len(v: f64) -> f64 { (v * 1000.0).round() / 1000.0 } if let Some(bbox) = node.calculate_bbox() { println!( "{},{},{},{},{}", node.id(), round_len(bbox.x()), round_len(bbox.y()), round_len(bbox.width()), round_len(bbox.height()) ); } } if count == 0 { bail!("the file has no valid ID's"); } Ok(()) } fn run_task(perf: bool, title: &str, p: P) -> T where P: FnOnce() -> T { if perf { let start = time::precise_time_ns(); let res = p(); let end = time::precise_time_ns(); println!("{}: {:.2}ms", title, (end - start) as f64 / 1_000_000.0); res } else { p() } } fn dump_svg(tree: &usvg::Tree, path: &path::Path) -> Result<(), String> { let mut f = fs::File::create(path) .map_err(|_| format!("failed to create a file {:?}", path))?; f.write_all(tree.to_string(usvg::XmlOptions::default()).as_bytes()) .map_err(|_| format!("failed to write a file {:?}", path))?; 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 )) } resvg-0.8.0/tools/usvg/000077500000000000000000000000001352576375700150155ustar00rootroot00000000000000resvg-0.8.0/tools/usvg/.gitignore000066400000000000000000000000071352576375700170020ustar00rootroot00000000000000/*.svg resvg-0.8.0/tools/usvg/Cargo.toml000066400000000000000000000007211352576375700167450ustar00rootroot00000000000000[package] name = "usvg-cli" version = "0.8.0" authors = ["Reizner Evgeniy "] keywords = ["svg"] license = "MPL-2.0" edition = "2018" description = "An SVG simplification tool." categories = ["multimedia::images"] workspace = "../../" [badges] travis-ci = { repository = "RazrFalcon/resvg" } [dependencies] fern = "0.5" log = "0.4" pico-args = "0.2" usvg = { version = "0.8", path = "../../usvg" } [[bin]] name = "usvg" path = "src/main.rs" resvg-0.8.0/tools/usvg/LICENSE.txt000066400000000000000000000405261352576375700166470ustar00rootroot00000000000000Mozilla Public License Version 2.0 ================================== 1. Definitions -------------- 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or (b) that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or (b) any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions -------------------------------- 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and (b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: (a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or (c) under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities ------------------- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation --------------------------------------------------- If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination -------------- 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ************************************************************************ * * * 6. Disclaimer of Warranty * * ------------------------- * * * * Covered Software is provided under this License on an "as is" * * basis, without warranty of any kind, either expressed, implied, or * * statutory, including, without limitation, warranties that the * * Covered Software is free of defects, merchantable, fit for a * * particular purpose or non-infringing. The entire risk as to the * * quality and performance of the Covered Software is with You. * * Should any Covered Software prove defective in any respect, You * * (not any Contributor) assume the cost of any necessary servicing, * * repair, or correction. This disclaimer of warranty constitutes an * * essential part of this License. No use of any Covered Software is * * authorized under this License except under this disclaimer. * * * ************************************************************************ ************************************************************************ * * * 7. Limitation of Liability * * -------------------------- * * * * Under no circumstances and under no legal theory, whether tort * * (including negligence), contract, or otherwise, shall any * * Contributor, or anyone who distributes Covered Software as * * permitted above, be liable to You for any direct, indirect, * * special, incidental, or consequential damages of any character * * including, without limitation, damages for lost profits, loss of * * goodwill, work stoppage, computer failure or malfunction, or any * * and all other commercial damages or losses, even if such party * * shall have been informed of the possibility of such damages. This * * limitation of liability shall not apply to liability for death or * * personal injury resulting from such party's negligence to the * * extent applicable law prohibits such limitation. Some * * jurisdictions do not allow the exclusion or limitation of * * incidental or consequential damages, so this exclusion and * * limitation may not apply to You. * * * ************************************************************************ 8. Litigation ------------- Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ---------------- This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License --------------------------- 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice ------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice --------------------------------------------------------- This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. resvg-0.8.0/tools/usvg/README.md000066400000000000000000000005201352576375700162710ustar00rootroot00000000000000# usvg This is a CLI version of the [`usvg`](../../usvg). ## Dependencies Lastes stable [Rust](https://www.rust-lang.org/). ## Build ```bash cargo build --release ``` or you can install it via `cargo`: ```bash cargo install -f usvg-cli ``` ## License *usvg* is licensed under the [MPLv2.0](https://www.mozilla.org/en-US/MPL/). resvg-0.8.0/tools/usvg/src/000077500000000000000000000000001352576375700156045ustar00rootroot00000000000000resvg-0.8.0/tools/usvg/src/main.rs000066400000000000000000000234241352576375700171030ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::fmt; use std::fs::File; use std::io::{self, Read, Write}; use std::path::Path; use std::process; use pico_args::Arguments; fn print_help() { print!("\ usvg (micro SVG) is an SVG simplification tool. USAGE: usvg [OPTIONS] # from file to file usvg [OPTIONS] -c # from file to stdout usvg [OPTIONS] - # 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 --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'] --shape-rendering HINT Selects the default shape rendering method. [default: geometricPrecision] [possible values: optimizeSpeed, crispEdges, geometricPrecision] --text-rendering HINT Selects the default text rendering method. [default: optimizeLegibility] [possible values: optimizeSpeed, optimizeLegibility, geometricPrecision] --image-rendering HINT Selects the default image rendering method. [default: optimizeQuality] [possible values: optimizeQuality, optimizeSpeed] --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] --quiet Disables warnings ARGS: Input file Output file "); } #[derive(Debug)] struct Args { help: bool, version: bool, stdout: bool, keep_named_groups: bool, dpi: u32, font_family: String, font_size: u32, languages: Vec, shape_rendering: usvg::ShapeRendering, text_rendering: usvg::TextRendering, image_rendering: usvg::ImageRendering, indent: usvg::XmlIndent, attrs_indent: usvg::XmlIndent, quiet: bool, free: Vec, } fn collect_args() -> Result { let mut input = Arguments::from_env(); Ok(Args { help: input.contains("--help"), version: input.contains(["-V", "--version"]), stdout: input.contains("-c"), keep_named_groups: input.contains("--keep-named-groups"), dpi: input.value_from_fn("--dpi", parse_dpi)?.unwrap_or(96), font_family: input.value_from_str("--font-family")? .unwrap_or_else(|| "Times New Roman".to_string()), font_size: input.value_from_fn("--font-size", parse_font_size)?.unwrap_or(12), languages: input.value_from_fn("--languages", parse_languages)? .unwrap_or(vec!["en".to_string()]), // TODO: use system language shape_rendering: input.value_from_str("--shape-rendering")?.unwrap_or_default(), text_rendering: input.value_from_str("--text-rendering")?.unwrap_or_default(), image_rendering: input.value_from_str("--image-rendering")?.unwrap_or_default(), indent: input.value_from_fn("--indent", parse_indent)? .unwrap_or(usvg::XmlIndent::Spaces(4)), attrs_indent: input.value_from_fn("--attrs-indent", parse_indent)? .unwrap_or(usvg::XmlIndent::None), quiet: input.contains("--quiet"), free: input.free()?, }) } fn parse_dpi(s: &str) -> Result { let n: u32 = s.parse().map_err(|_| "invalid number")?; if n >= 10 && n <= 4000 { Ok(n) } else { Err("DPI out of bounds".to_string()) } } fn parse_font_size(s: &str) -> Result { let n: u32 = s.parse().map_err(|_| "invalid number")?; if n > 0 && n <= 192 { Ok(n) } else { Err("font size out of bounds".to_string()) } } fn parse_languages(s: &str) -> Result, String> { 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".to_string()); } Ok(langs) } fn parse_indent(s: &str) -> Result { let indent = match s { "none" => usvg::XmlIndent::None, "0" => usvg::XmlIndent::Spaces(0), "1" => usvg::XmlIndent::Spaces(1), "2" => usvg::XmlIndent::Spaces(2), "3" => usvg::XmlIndent::Spaces(3), "4" => usvg::XmlIndent::Spaces(4), "tabs" => usvg::XmlIndent::Tabs, _ => return Err("invalid INDENT value".to_string()), }; Ok(indent) } #[derive(Clone, PartialEq, Debug)] enum InputFrom<'a> { Stdin, File(&'a str), } #[derive(Clone, PartialEq, Debug)] enum OutputTo<'a> { Stdout, File(&'a str), } fn main() { let args = match collect_args() { 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); } if !args.quiet { 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 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 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: args.languages.clone(), shape_rendering: args.shape_rendering, text_rendering: args.text_rendering, image_rendering: args.image_rendering, keep_named_groups: args.keep_named_groups, }; 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 xml_opt = usvg::XmlOptions { use_single_quote: false, indent: args.indent, attributes_indent: args.attrs_indent, }; let s = tree.to_string(xml_opt); match out_svg { OutputTo::Stdout => { io::stdout() .write_all(s.as_bytes()) .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(s.as_bytes()) .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 { 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) } resvg-0.8.0/tools/viewsvg/000077500000000000000000000000001352576375700155235ustar00rootroot00000000000000resvg-0.8.0/tools/viewsvg/README.md000066400000000000000000000005311352576375700170010ustar00rootroot00000000000000# viewsvg A simple SVG viewer using Qt and *resvg* C-API. ## Dependencies - Qt >= 5.6 ## Build ```bash # build C-API first cargo build --release --features "qt-backend" --manifest-path ../../capi/Cargo.toml # build viewsvg qmake make # run LD_LIBRARY_PATH=../../target/release ./viewsvg ``` See [BUILD.adoc](../../BUILD.adoc) for details. resvg-0.8.0/tools/viewsvg/main.cpp000066400000000000000000000003541352576375700171550ustar00rootroot00000000000000#include #include "mainwindow.h" int main(int argc, char *argv[]) { QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); } resvg-0.8.0/tools/viewsvg/mainwindow.cpp000066400000000000000000000022561352576375700204100ustar00rootroot00000000000000#include #include #include "mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); SvgView::init(); ui->cmbBoxSize->setCurrentIndex(1); ui->cmbBoxBackground->setCurrentIndex(1); ui->svgView->setFitToView(true); ui->svgView->setBackgound(SvgView::Backgound::White); connect(ui->svgView, &SvgView::loadError, this, [this](const QString &msg){ QMessageBox::critical(this, "Error", msg); }); QTimer::singleShot(5, this, &MainWindow::onStart); } MainWindow::~MainWindow() { delete ui; } void MainWindow::onStart() { ui->svgView->setFocus(); const auto args = QCoreApplication::arguments(); if (args.size() != 2) { return; } ui->svgView->loadFile(args.at(1)); } void MainWindow::on_cmbBoxSize_activated(int index) { ui->svgView->setFitToView(index == 1); } void MainWindow::on_cmbBoxBackground_activated(int index) { ui->svgView->setBackgound(SvgView::Backgound(index)); } void MainWindow::on_chBoxDrawBorder_toggled(bool checked) { ui->svgView->setDrawImageBorder(checked); } resvg-0.8.0/tools/viewsvg/mainwindow.h000066400000000000000000000006531352576375700200540ustar00rootroot00000000000000#pragma once #include namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); private slots: void onStart(); void on_cmbBoxBackground_activated(int index); void on_chBoxDrawBorder_toggled(bool checked); void on_cmbBoxSize_activated(int index); private: Ui::MainWindow *ui; }; resvg-0.8.0/tools/viewsvg/mainwindow.ui000066400000000000000000000104021352576375700202330ustar00rootroot00000000000000 MainWindow 0 0 787 424 viewsvg 6 0 6 0 0 2 0 0 Size: Original Fit to View Qt::Horizontal QSizePolicy::Fixed 20 1 Background: None White Check board Qt::Horizontal QSizePolicy::Fixed 20 1 Draw image border Qt::Horizontal 40 20 0 0 SvgView QWidget
svgview.h
1
resvg-0.8.0/tools/viewsvg/svgview.cpp000066400000000000000000000154131352576375700177250ustar00rootroot00000000000000#include #include #include #include #include #include #include #include "svgview.h" SvgViewWorker::SvgViewWorker(QObject *parent) : QObject(parent) , m_dpiRatio(qApp->screens().first()->devicePixelRatio()) { } QRect SvgViewWorker::viewBox() const { QMutexLocker lock(&m_mutex); return m_renderer.viewBox(); } QString SvgViewWorker::loadData(const QByteArray &data) { QMutexLocker lock(&m_mutex); m_renderer.load(data); if (!m_renderer.isValid()) { return m_renderer.errorString(); } return QString(); } QString SvgViewWorker::loadFile(const QString &path) { QMutexLocker lock(&m_mutex); m_renderer.load(path); if (!m_renderer.isValid()) { return m_renderer.errorString(); } return QString(); } void SvgViewWorker::render(const QSize &viewSize) { Q_ASSERT(QThread::currentThread() != qApp->thread()); QMutexLocker lock(&m_mutex); if (m_renderer.isEmpty()) { return; } const auto s = m_renderer.defaultSize().scaled(viewSize, Qt::KeepAspectRatio); QImage img(s * m_dpiRatio, QImage::Format_ARGB32_Premultiplied); img.fill(Qt::transparent); QPainter p(&img); m_renderer.render(&p); p.end(); img.setDevicePixelRatio(m_dpiRatio); emit rendered(img); } static QImage genCheckedTexture() { int l = 20; QImage pix = QImage(l, l, QImage::Format_RGB32); int b = pix.width() / 2.0; pix.fill(QColor("#c0c0c0")); QPainter p; p.begin(&pix); p.fillRect(QRect(0,0,b,b), QColor("#808080")); p.fillRect(QRect(b,b,b,b), QColor("#808080")); p.end(); return pix; } SvgView::SvgView(QWidget *parent) : QWidget(parent) , m_checkboardImg(genCheckedTexture()) , m_worker(new SvgViewWorker()) , m_resizeTimer(new QTimer(this)) { setAcceptDrops(true); setMinimumSize(10, 10); QThread *th = new QThread(this); m_worker->moveToThread(th); th->start(); const auto *screen = qApp->screens().first(); m_dpiRatio = screen->devicePixelRatio(); connect(m_worker, &SvgViewWorker::rendered, this, &SvgView::onRendered); m_resizeTimer->setSingleShot(true); connect(m_resizeTimer, &QTimer::timeout, this, &SvgView::requestUpdate); } SvgView::~SvgView() { QThread *th = m_worker->thread(); th->quit(); th->wait(10000); delete m_worker; } void SvgView::init() { ResvgRenderer::initLog(); } void SvgView::setFitToView(bool flag) { m_isFitToView = flag; requestUpdate(); } void SvgView::setBackgound(SvgView::Backgound backgound) { m_backgound = backgound; update(); } void SvgView::setDrawImageBorder(bool flag) { m_isDrawImageBorder = flag; update(); } void SvgView::loadData(const QByteArray &ba) { const QString errMsg = m_worker->loadData(ba); afterLoad(errMsg); } void SvgView::loadFile(const QString &path) { const QString errMsg = m_worker->loadFile(path); afterLoad(errMsg); } void SvgView::afterLoad(const QString &errMsg) { m_img = QImage(); if (errMsg.isEmpty()) { m_isHasImage = true; requestUpdate(); } else { emit loadError(errMsg); m_isHasImage = false; update(); } } void SvgView::drawSpinner(QPainter &p) { const int outerRadius = 20; const int innerRadius = outerRadius * 0.45; const int capsuleHeight = outerRadius - innerRadius; const int capsuleWidth = capsuleHeight * 0.35; const int capsuleRadius = capsuleWidth / 2; for (int i = 0; i < 12; ++i) { QColor color = Qt::black; color.setAlphaF(1.0f - (i / 12.0f)); p.setRenderHint(QPainter::Antialiasing); p.setPen(Qt::NoPen); p.setBrush(color); p.save(); p.translate(width()/2, height()/2); p.rotate(m_angle - i * 30.0f); p.drawRoundedRect(-capsuleWidth * 0.5, -(innerRadius + capsuleHeight), capsuleWidth, capsuleHeight, capsuleRadius, capsuleRadius); p.restore(); } } void SvgView::paintEvent(QPaintEvent *e) { QPainter p(this); const auto r = contentsRect(); p.setClipRect(r); switch (m_backgound) { case Backgound::None : break; case Backgound::White : { p.fillRect(contentsRect(), Qt::white); } break; case Backgound::CheckBoard : { p.fillRect(contentsRect(), QBrush(m_checkboardImg)); } break; } if (m_img.isNull() && !m_timer.isActive()) { p.setPen(Qt::black); p.drawText(rect(), Qt::AlignCenter, "Drop an SVG image here."); } else if (m_timer.isActive()) { drawSpinner(p); } else { const QRect imgRect(0, 0, m_img.width() / m_dpiRatio, m_img.height() / m_dpiRatio); p.translate(r.x() + (r.width() - imgRect.width())/ 2, r.y() + (r.height() - imgRect.height()) / 2); p.drawImage(0, 0, m_img); if (m_isDrawImageBorder) { p.setRenderHint(QPainter::Antialiasing, false); p.setPen(Qt::green); p.setBrush(Qt::NoBrush); p.drawRect(imgRect); } } QWidget::paintEvent(e); } void SvgView::dragEnterEvent(QDragEnterEvent *event) { event->accept(); } void SvgView::dragMoveEvent(QDragMoveEvent *event) { event->accept(); } void SvgView::dropEvent(QDropEvent *event) { const QMimeData *mime = event->mimeData(); if (!mime->hasUrls()) { event->ignore(); return; } for (const QUrl &url : mime->urls()) { if (!url.isLocalFile()) { continue; } QString path = url.toLocalFile(); QFileInfo fi = QFileInfo(path); if (fi.isFile()) { QString suffix = fi.suffix().toLower(); if (suffix == "svg" || suffix == "svgz") { loadFile(path); } else { QMessageBox::warning(this, tr("Warning"), tr("You can drop only SVG and SVGZ files.")); } } } event->acceptProposedAction(); } void SvgView::resizeEvent(QResizeEvent *) { m_resizeTimer->start(200); } void SvgView::timerEvent(QTimerEvent *event) { if (event->timerId() == m_timer.timerId()) { m_angle = (m_angle + 30) % 360; update(); } else { QWidget::timerEvent(event); } } void SvgView::requestUpdate() { if (!m_isHasImage) { return; } const auto s = m_isFitToView ? size() : m_worker->viewBox().size(); if (s * m_dpiRatio == m_img.size()) { return; } m_timer.start(100, this); // Run method in the m_worker thread scope. QTimer::singleShot(1, m_worker, [=](){ m_worker->render(s); }); } void SvgView::onRendered(const QImage &img) { m_timer.stop(); m_img = img; update(); } resvg-0.8.0/tools/viewsvg/svgview.h000066400000000000000000000034051352576375700173700ustar00rootroot00000000000000#pragma once #include #include #include #include class SvgViewWorker : public QObject { Q_OBJECT public: SvgViewWorker(QObject *parent = nullptr); QRect viewBox() const; public slots: QString loadData(const QByteArray &data); QString loadFile(const QString &path); void render(const QSize &viewSize); signals: void rendered(QImage); private: const float m_dpiRatio; mutable QMutex m_mutex; ResvgRenderer m_renderer; }; class SvgView : public QWidget { Q_OBJECT public: enum class Backgound { None, White, CheckBoard, }; explicit SvgView(QWidget *parent = nullptr); ~SvgView(); static void init(); void setFitToView(bool flag); void setBackgound(Backgound backgound); void setDrawImageBorder(bool flag); void loadData(const QByteArray &data); void loadFile(const QString &path); signals: void loadError(QString); protected: void paintEvent(QPaintEvent *); void dragEnterEvent(QDragEnterEvent *event); void dragMoveEvent(QDragMoveEvent *event); void dropEvent(QDropEvent *event); void resizeEvent(QResizeEvent *); void timerEvent(QTimerEvent *); private: void requestUpdate(); void afterLoad(const QString &errMsg); void drawSpinner(QPainter &p); private slots: void onRendered(const QImage &img); private: const QImage m_checkboardImg; SvgViewWorker * const m_worker; QTimer * const m_resizeTimer; QString m_path; float m_dpiRatio = 1.0; bool m_isFitToView = true; Backgound m_backgound = Backgound::CheckBoard; bool m_isDrawImageBorder = false; bool m_isHasImage = false; QImage m_img; QBasicTimer m_timer; int m_angle = 0; }; resvg-0.8.0/tools/viewsvg/viewsvg.pro000066400000000000000000000007051352576375700177410ustar00rootroot00000000000000QT += core gui widgets TARGET = viewsvg TEMPLATE = app CONFIG += C++11 SOURCES += \ main.cpp \ mainwindow.cpp \ svgview.cpp HEADERS += \ mainwindow.h \ svgview.h FORMS += \ mainwindow.ui CONFIG(release, debug|release): LIBS += -L$$PWD/../../target/release/ -lresvg else:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../target/debug/ -lresvg INCLUDEPATH += $$PWD/../../capi/include DEPENDPATH += $$PWD/../../capi/include resvg-0.8.0/usvg/000077500000000000000000000000001352576375700136555ustar00rootroot00000000000000resvg-0.8.0/usvg/Cargo.toml000066400000000000000000000014511352576375700156060ustar00rootroot00000000000000[package] name = "usvg" version = "0.8.0" authors = ["Evgeniy Reizner "] keywords = ["svg"] license = "MPL-2.0" edition = "2018" description = "An SVG simplification library." categories = ["multimedia::images"] repository = "https://github.com/RazrFalcon/resvg" documentation = "https://docs.rs/usvg/" readme = "README.md" exclude = ["testing-tools/**"] workspace = ".." [badges] travis-ci = { repository = "RazrFalcon/resvg" } [dependencies] base64 = "0.10" data-url = "0.1" kurbo = "0.2.3" libflate = "0.1.25" log = "0.4" rctree = "0.3" xmlwriter = "0.1" # for svgtree roxmltree = "0.7" simplecss = "0.2" siphasher = "0.2.3" svgtypes = "0.5" # for text to path harfbuzz_rs = "1.0" memmap = "0.7" ttf-parser = "0.2.2" unicode-bidi = "0.3" unicode-script = "0.3" unicode-vo = "0.1" resvg-0.8.0/usvg/LICENSE.txt000066400000000000000000000405261352576375700155070ustar00rootroot00000000000000Mozilla Public License Version 2.0 ================================== 1. Definitions -------------- 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or (b) that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or (b) any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions -------------------------------- 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and (b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: (a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or (c) under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities ------------------- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation --------------------------------------------------- If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination -------------- 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ************************************************************************ * * * 6. Disclaimer of Warranty * * ------------------------- * * * * Covered Software is provided under this License on an "as is" * * basis, without warranty of any kind, either expressed, implied, or * * statutory, including, without limitation, warranties that the * * Covered Software is free of defects, merchantable, fit for a * * particular purpose or non-infringing. The entire risk as to the * * quality and performance of the Covered Software is with You. * * Should any Covered Software prove defective in any respect, You * * (not any Contributor) assume the cost of any necessary servicing, * * repair, or correction. This disclaimer of warranty constitutes an * * essential part of this License. No use of any Covered Software is * * authorized under this License except under this disclaimer. * * * ************************************************************************ ************************************************************************ * * * 7. Limitation of Liability * * -------------------------- * * * * Under no circumstances and under no legal theory, whether tort * * (including negligence), contract, or otherwise, shall any * * Contributor, or anyone who distributes Covered Software as * * permitted above, be liable to You for any direct, indirect, * * special, incidental, or consequential damages of any character * * including, without limitation, damages for lost profits, loss of * * goodwill, work stoppage, computer failure or malfunction, or any * * and all other commercial damages or losses, even if such party * * shall have been informed of the possibility of such damages. This * * limitation of liability shall not apply to liability for death or * * personal injury resulting from such party's negligence to the * * extent applicable law prohibits such limitation. Some * * jurisdictions do not allow the exclusion or limitation of * * incidental or consequential damages, so this exclusion and * * limitation may not apply to You. * * * ************************************************************************ 8. Litigation ------------- Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ---------------- This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License --------------------------- 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice ------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice --------------------------------------------------------- This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. resvg-0.8.0/usvg/README.md000066400000000000000000000055161352576375700151430ustar00rootroot00000000000000# usvg [![Build Status](https://travis-ci.org/RazrFalcon/resvg.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 into an extremely simple representation, which is still a valid SVG. And now, all you need is 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 ## 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 resvg-0.8.0/usvg/codegen/000077500000000000000000000000001352576375700152615ustar00rootroot00000000000000resvg-0.8.0/usvg/codegen/Cargo.toml000066400000000000000000000004031352576375700172060ustar00rootroot00000000000000[package] name = "codegen" version = "0.1.0" authors = ["Evgeniy Reizner "] license = "MIT" edition = "2018" [workspace] [[bin]] name = "codegen" path = "main.rs" [dependencies] phf = "0.7.22" phf_codegen = "0.7.22" itertools = "0.7" resvg-0.8.0/usvg/codegen/README.md000066400000000000000000000003661352576375700165450ustar00rootroot00000000000000We don't use cargo build script, since this data will be changed rarely and there is no point in regenerating it each time. Note that `/spec/*` files contain only values that are supported by `usvg`. To regenerate files run: ``` cargo run ``` resvg-0.8.0/usvg/codegen/attributes.txt000066400000000000000000000020701352576375700202070ustar00rootroot00000000000000amplitude baseline-shift class clip-path clip-rule clipPathUnits color color-interpolation-filters cx cy d direction display dx dy exponent fill fill-opacity fill-rule filter filterUnits flood-color flood-opacity font-family font-size font-stretch font-style font-variant font-weight fx fy gradientTransform gradientUnits height href id image-rendering in in2 intercept k1 k2 k3 k4 letter-spacing marker-end marker-mid marker-start markerHeight markerUnits markerWidth mask maskContentUnits maskUnits mode offset opacity operator orient overflow patternContentUnits patternTransform patternUnits points preserveAspectRatio primitiveUnits r refX refY requiredExtensions requiredFeatures result rotate rx ry shape-rendering slope space spreadMethod startOffset stdDeviation stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage tableValues text-anchor text-decoration text-rendering transform type values viewBox visibility width word-spacing writing-mode x x1 x2 y y1 y2 resvg-0.8.0/usvg/codegen/elements.txt000066400000000000000000000007351352576375700176430ustar00rootroot00000000000000a circle clipPath defs ellipse feBlend feColorMatrix feComponentTransfer feComposite feConvolveMatrix feDiffuseLighting feDisplacementMap feDistantLight feFlood feFuncA feFuncB feFuncG feFuncR feGaussianBlur feImage feMerge feMergeNode feMorphology feOffset fePointLight feSpecularLighting feSpotLight feTile feTurbulence filter g image line linearGradient marker mask path pattern polygon polyline radialGradient rect stop style svg switch symbol text textPath tref tspan use resvg-0.8.0/usvg/codegen/main.rs000066400000000000000000000117451352576375700165630ustar00rootroot00000000000000use itertools::Itertools; use std::fs; use std::io::{Read, Write}; use std::str; const PHF_SRC: &str = "\ // A stripped down `phf` crate fork. // // https://github.com/sfackler/rust-phf struct Map { pub key: u64, pub disps: &'static [(u32, u32)], pub entries: &'static[(&'static str, V)], } impl Map { fn get(&self, key: &str) -> Option<&V> { use std::borrow::Borrow; let hash = hash(key, self.key); let index = get_index(hash, &*self.disps, self.entries.len()); let entry = &self.entries[index as usize]; let b = entry.0.borrow(); if b == key { Some(&entry.1) } else { None } } fn key(&self, value: &V) -> &'static str { self.entries.iter().find(|kv| kv.1 == *value).unwrap().0 } } #[inline] fn hash(x: &str, key: u64) -> u64 { use std::hash::Hasher; let mut hasher = siphasher::sip::SipHasher13::new_with_keys(0, key); hasher.write(x.as_bytes()); hasher.finish() } #[inline] fn get_index(hash: u64, disps: &[(u32, u32)], len: usize) -> u32 { let (g, f1, f2) = split(hash); let (d1, d2) = disps[(g % (disps.len() as u32)) as usize]; displace(f1, f2, d1, d2) % (len as u32) } #[inline] fn split(hash: u64) -> (u32, u32, u32) { const BITS: u32 = 21; const MASK: u64 = (1 << BITS) - 1; ((hash & MASK) as u32, ((hash >> BITS) & MASK) as u32, ((hash >> (2 * BITS)) & MASK) as u32) } #[inline] fn displace(f1: u32, f2: u32, d1: u32, d2: u32) -> u32 { d2 + f1 * d1 + f2 }"; fn main() { if let Err(e) = gen() { println!("{:?}", e); std::process::exit(1); } } fn gen() -> Result<(), Box> { let f = &mut fs::File::create("../src/svgtree/names.rs")?; writeln!(f, "// This file is autogenerated. Do not edit it!")?; writeln!(f, "// See ./codegen for details.\n")?; writeln!(f, "use std::fmt;\n")?; gen_map( "elements.txt", "EId", "ELEMENTS", f, )?; gen_map( "attributes.txt", "AId", "ATTRIBUTES", f, )?; writeln!(f, "{}", PHF_SRC)?; Ok(()) } fn gen_map( spec_path: &str, enum_name: &str, map_name: &str, f: &mut fs::File, ) -> Result<(), Box> { let mut spec = String::new(); fs::File::open(spec_path)?.read_to_string(&mut spec)?; let names: Vec<&str> = spec.split('\n').filter(|s| !s.is_empty()).collect(); let joined_names = names.iter().map(|n| to_enum_name(n)).join(",\n "); let mut map = phf_codegen::Map::new(); for name in &names { map.entry(*name, &format!("{}::{}", enum_name, to_enum_name(name))); } let mut map_data = Vec::new(); map.build(&mut map_data)?; let map_data = String::from_utf8(map_data)?; let map_data = map_data.replace("::phf::Map", "Map"); let map_data = map_data.replace("::phf::Slice::Static(", ""); let map_data = map_data.replace("]),", "],"); writeln!(f, "#[derive(Clone, Copy, PartialEq)]")?; writeln!(f, "pub enum {} {{", enum_name)?; writeln!(f, " {}", joined_names)?; writeln!(f, "}}\n")?; writeln!(f, "static {}: Map<{}> = {};\n", map_name, enum_name, map_data)?; writeln!(f, "impl {} {{", enum_name)?; writeln!(f, " pub fn from_str(text: &str) -> Option<{}> {{", enum_name)?; writeln!(f, " {}.get(text).cloned()", map_name)?; writeln!(f, " }}")?; writeln!(f, "")?; writeln!(f, " #[inline(never)]")?; writeln!(f, " pub fn to_str(&self) -> &'static str {{")?; writeln!(f, " {}.key(self)", map_name)?; writeln!(f, " }}")?; writeln!(f, "}}\n")?; writeln!(f, "impl fmt::Debug for {} {{", enum_name)?; writeln!(f, " fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {{")?; writeln!(f, " write!(f, \"{{}}\", self.to_str())")?; writeln!(f, " }}")?; writeln!(f, "}}\n")?; writeln!(f, "impl fmt::Display for {} {{", enum_name)?; writeln!(f, " fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {{")?; writeln!(f, " write!(f, \"{{:?}}\", self)")?; writeln!(f, " }}")?; writeln!(f, "}}")?; writeln!(f, "")?; Ok(()) } // some-string -> SomeString // some_string -> SomeString // some:string -> SomeString // 100 -> N100 fn to_enum_name(name: &str) -> String { let mut change_case = false; let mut s = String::with_capacity(name.len()); for (idx, c) in name.chars().enumerate() { if idx == 0 { if c.is_digit(10) { s.push('N'); s.push(c); } else { s.push(c.to_uppercase().next().unwrap()); } continue; } if c == '-' || c == '_' || c == ':' { change_case = true; continue; } if change_case { s.push(c.to_uppercase().next().unwrap()); change_case = false; } else { s.push(c); } } s } resvg-0.8.0/usvg/src/000077500000000000000000000000001352576375700144445ustar00rootroot00000000000000resvg-0.8.0/usvg/src/convert/000077500000000000000000000000001352576375700161245ustar00rootroot00000000000000resvg-0.8.0/usvg/src/convert/clip_and_mask.rs000066400000000000000000000071351352576375700212640ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::tree; use crate::svgtree; use super::prelude::*; pub fn convert_clip( node: svgtree::Node, state: &State, tree: &mut tree::Tree, ) -> Option { // A `clip-path` attribute must reference a `clipPath` element. if !node.has_tag_name(EId::ClipPath) { return None; } if !node.has_valid_transform(AId::Transform) { return None; } // Check if this element was already converted. if let Some(id) = node.attribute(AId::Id) { if tree.defs_by_id(id).is_some() { return Some(id.to_string()); } } // Resolve linked clip path. let mut clip_path = None; if let Some(link) = node.attribute::(AId::ClipPath) { clip_path = convert_clip(link, state, tree); // Linked `clipPath` must be valid. if clip_path.is_none() { return None; } } let units = node.attribute(AId::ClipPathUnits).unwrap_or(tree::Units::UserSpaceOnUse); let mut clip = tree.append_to_defs( tree::NodeKind::ClipPath(tree::ClipPath { id: node.element_id().to_string(), units, transform: node.attribute(AId::Transform).unwrap_or_default(), clip_path, }) ); let mut clip_state = state.clone(); clip_state.parent_clip_path = Some(node); super::convert_clip_path_elements(node, &clip_state, &mut clip, tree); if clip.has_children() { Some(node.element_id().to_string()) } else { // A clip path without children is invalid. clip.detach(); None } } pub fn convert_mask( node: svgtree::Node, state: &State, tree: &mut tree::Tree, ) -> Option { // A `mask` attribute must reference a `mask` element. if !node.has_tag_name(EId::Mask) { return None; } // Check if this element was already converted. if let Some(id) = node.attribute(AId::Id) { if tree.defs_by_id(id).is_some() { return Some(id.to_string()); } } let units = node.attribute(AId::MaskUnits).unwrap_or(tree::Units::ObjectBoundingBox); let content_units = node.attribute(AId::MaskContentUnits).unwrap_or(tree::Units::UserSpaceOnUse); let rect = Rect::new( node.convert_length(AId::X, units, state, Length::new(-10.0, Unit::Percent)), node.convert_length(AId::Y, units, state, Length::new(-10.0, Unit::Percent)), node.convert_length(AId::Width, units, state, Length::new(120.0, Unit::Percent)), node.convert_length(AId::Height, units, state, Length::new(120.0, Unit::Percent)), ); let rect = try_opt_warn_or!( rect, None, "Mask '{}' has an invalid size. Skipped.", node.element_id(), ); // Resolve linked mask. let mut mask = None; if let Some(link) = node.attribute::(AId::Mask) { mask = convert_mask(link, state, tree); // Linked `mask` must be valid. if mask.is_none() { return None; } } let mut mask = tree.append_to_defs(tree::NodeKind::Mask(tree::Mask { id: node.element_id().to_string(), units, content_units, rect, mask, })); super::convert_children(node, state, &mut mask, tree); if mask.has_children() { Some(node.element_id().to_string()) } else { // A mask without children is invalid. mask.detach(); None } } resvg-0.8.0/usvg/src/convert/filter.rs000066400000000000000000000401141352576375700177570ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::collections::HashSet; use crate::svgtree; use crate::tree; use super::prelude::*; use super::paint_server::{ resolve_number, convert_units, }; pub fn convert( node: svgtree::Node, state: &State, tree: &mut tree::Tree, ) -> Option { if tree.defs_by_id(node.element_id()).is_some() { return Some(node.element_id().to_string()); } let units = convert_units(node, AId::FilterUnits, tree::Units::ObjectBoundingBox); let primitive_units = convert_units(node, AId::PrimitiveUnits, tree::Units::UserSpaceOnUse); let rect = Rect::new( resolve_number(node, AId::X, units, state, Length::new(-10.0, Unit::Percent)), resolve_number(node, AId::Y, units, state, Length::new(-10.0, Unit::Percent)), resolve_number(node, AId::Width, units, state, Length::new(120.0, Unit::Percent)), resolve_number(node, AId::Height, units, state, Length::new(120.0, Unit::Percent)), ); let rect = try_opt_warn_or!( rect, None, "Filter '{}' has an invalid region. Skipped.", node.element_id(), ); let node_with_children = find_filter_with_children(node)?; let children = collect_children(&node_with_children, primitive_units, state); if children.is_empty() { return None; } tree.append_to_defs( tree::NodeKind::Filter(tree::Filter { id: node.element_id().to_string(), units, primitive_units, rect, children, }) ); Some(node.element_id().to_string()) } fn find_filter_with_children( node: svgtree::Node, ) -> Option { for link_id in node.href_iter() { let link = node.document().get(link_id); if !link.has_tag_name(EId::Filter) { warn!( "Filter '{}' cannot reference '{}' via 'xlink:href'.", node.element_id(), link.tag_name().unwrap() ); return None; } if link.has_children() { return Some(link.clone()); } } None } struct FilterResults { names: HashSet, idx: usize, } fn collect_children( filter: &svgtree::Node, units: tree::Units, state: &State, ) -> Vec { let mut primitives = Vec::new(); let mut results = FilterResults { names: HashSet::new(), idx: 1, }; for child in filter.children() { let tag_name = match child.tag_name() { Some(n) => n, None => continue, }; let kind = match tag_name { EId::FeGaussianBlur => { convert_fe_gaussian_blur(child, &primitives) } EId::FeOffset => { convert_fe_offset(child, &primitives, state) } EId::FeBlend => { convert_fe_blend(child, &primitives) } EId::FeFlood => { convert_fe_flood(child) } EId::FeComposite => { convert_fe_composite(child, &primitives) } EId::FeMerge => { convert_fe_merge(child, &primitives) } EId::FeTile => { convert_fe_tile(child, &primitives) } EId::FeImage => { convert_fe_image(child, state) } EId::FeComponentTransfer => { convert_fe_component_transfer(child, &primitives) } EId::FeColorMatrix => { convert_fe_color_matrix(child, &primitives) } _ => { warn!("Filter with '{}' child is not supported.", tag_name); continue; } }; let fe = convert_primitive(child, kind, units, state, &mut results); primitives.push(fe); } primitives } fn convert_primitive( fe: svgtree::Node, kind: tree::FilterKind, units: tree::Units, state: &State, results: &mut FilterResults, ) -> tree::FilterPrimitive { tree::FilterPrimitive { x: fe.try_convert_length(AId::X, units, state), y: fe.try_convert_length(AId::Y, units, state), // TODO: validate and test width: fe.try_convert_length(AId::Width, units, state), height: fe.try_convert_length(AId::Height, units, state), color_interpolation: fe.find_attribute(AId::ColorInterpolationFilters).unwrap_or_default(), result: gen_result(fe, results), kind, } } fn convert_fe_gaussian_blur( fe: svgtree::Node, primitives: &[tree::FilterPrimitive], ) -> tree::FilterKind { let text = fe.attribute::<&str>(AId::StdDeviation).unwrap_or("0 0"); let mut parser = svgtypes::NumberListParser::from(text); let n1 = parser.next().and_then(|n| n.ok()); let n2 = parser.next().and_then(|n| n.ok()); // `stdDeviation` must have no more than two values. // Otherwise we should fallback to `0 0`. let n3 = parser.next().and_then(|n| n.ok()); let (mut std_dev_x, mut std_dev_y) = match (n1, n2, n3) { (Some(n1), Some(n2), None) => (n1, n2), (Some(n1), None, None) => (n1, n1), _ => (0.0, 0.0), }; if std_dev_x.is_sign_negative() { std_dev_x = 0.0; } if std_dev_y.is_sign_negative() { std_dev_y = 0.0; } tree::FilterKind::FeGaussianBlur(tree::FeGaussianBlur { input: resolve_input(fe, AId::In, primitives), std_dev_x: std_dev_x.into(), std_dev_y: std_dev_y.into(), }) } fn convert_fe_offset( fe: svgtree::Node, primitives: &[tree::FilterPrimitive], state: &State, ) -> tree::FilterKind { tree::FilterKind::FeOffset(tree::FeOffset { input: resolve_input(fe, AId::In, primitives), dx: fe.convert_user_length(AId::Dx, state, Length::zero()), dy: fe.convert_user_length(AId::Dy, state, Length::zero()), }) } fn convert_fe_blend( fe: svgtree::Node, primitives: &[tree::FilterPrimitive], ) -> tree::FilterKind { let mode = match fe.attribute(AId::Mode).unwrap_or("normal") { "multiply" => tree::FeBlendMode::Multiply, "screen" => tree::FeBlendMode::Screen, "darken" => tree::FeBlendMode::Darken, "lighten" => tree::FeBlendMode::Lighten, _ => tree::FeBlendMode::Normal, }; let input1 = resolve_input(fe, AId::In, primitives); let input2 = resolve_input(fe, AId::In2, primitives); tree::FilterKind::FeBlend(tree::FeBlend { mode, input1, input2, }) } fn convert_fe_flood( fe: svgtree::Node, ) -> tree::FilterKind { let color = fe.attribute(AId::FloodColor).unwrap_or_else(tree::Color::black); let opacity = fe.attribute(AId::FloodOpacity).unwrap_or_default(); tree::FilterKind::FeFlood(tree::FeFlood { color, opacity, }) } fn get_coeff( node: svgtree::Node, aid: AId, ) -> tree::CompositingCoefficient { let k: f64 = node.attribute(aid).unwrap_or(0.0); f64_bound(0.0, k, 1.0).into() } fn convert_fe_composite( fe: svgtree::Node, primitives: &[tree::FilterPrimitive], ) -> tree::FilterKind { let operator = match fe.attribute(AId::Operator).unwrap_or("over") { "in" => tree::FeCompositeOperator::In, "out" => tree::FeCompositeOperator::Out, "atop" => tree::FeCompositeOperator::Atop, "xor" => tree::FeCompositeOperator::Xor, "arithmetic" => { tree::FeCompositeOperator::Arithmetic { k1: get_coeff(fe, AId::K1), k2: get_coeff(fe, AId::K2), k3: get_coeff(fe, AId::K3), k4: get_coeff(fe, AId::K4), } } _ => tree::FeCompositeOperator::Over, }; let input1 = resolve_input(fe, AId::In, primitives); let input2 = resolve_input(fe, AId::In2, primitives); tree::FilterKind::FeComposite(tree::FeComposite { operator, input1, input2, }) } fn convert_fe_merge( fe: svgtree::Node, primitives: &[tree::FilterPrimitive], ) -> tree::FilterKind { let mut inputs = Vec::new(); for child in fe.children() { inputs.push(resolve_input(child, AId::In, primitives)); } tree::FilterKind::FeMerge(tree::FeMerge { inputs, }) } fn convert_fe_image( fe: svgtree::Node, state: &State, ) -> tree::FilterKind { let aspect = fe.attribute(AId::PreserveAspectRatio).unwrap_or_default(); let rendering_mode = fe .find_attribute(AId::ImageRendering) .unwrap_or(state.opt.image_rendering); let href = match fe.attribute(AId::Href) { Some(s) => s, _ => { warn!("The 'feImage' element lacks the 'xlink:href' attribute. Skipped."); return tree::FilterKind::FeImage(tree::FeImage { aspect, rendering_mode, data: tree::FeImageKind::None, }); } }; let href = super::image::get_href_data(fe.element_id(), href, state.opt.path.as_ref()); let (img_data, format) = match href { Some((data, format)) => (data, format), None => { return tree::FilterKind::FeImage(tree::FeImage { aspect, rendering_mode, data: tree::FeImageKind::None, }); } }; tree::FilterKind::FeImage(tree::FeImage { aspect, rendering_mode, data: tree::FeImageKind::Image(img_data, format), }) } fn convert_fe_tile( fe: svgtree::Node, primitives: &[tree::FilterPrimitive], ) -> tree::FilterKind { tree::FilterKind::FeTile(tree::FeTile { input: resolve_input(fe, AId::In, primitives), }) } fn convert_fe_component_transfer( fe: svgtree::Node, primitives: &[tree::FilterPrimitive], ) -> tree::FilterKind { let mut kind = tree::FeComponentTransfer { input: resolve_input(fe, AId::In, primitives), func_r: tree::TransferFunction::Identity, func_g: tree::TransferFunction::Identity, func_b: tree::TransferFunction::Identity, func_a: tree::TransferFunction::Identity, }; for child in fe.children().filter(|n| n.is_element()) { if let Some(func) = convert_transfer_function(child) { match child.tag_name().unwrap() { EId::FeFuncR => kind.func_r = func, EId::FeFuncG => kind.func_g = func, EId::FeFuncB => kind.func_b = func, EId::FeFuncA => kind.func_a = func, _ => {} } } } tree::FilterKind::FeComponentTransfer(kind) } fn convert_transfer_function( node: svgtree::Node, ) -> Option { match node.attribute(AId::Type)? { "identity" => { Some(tree::TransferFunction::Identity) } "table" => { match node.attribute::<&svgtypes::NumberList>(AId::TableValues) { Some(values) => Some(tree::TransferFunction::Table(values.0.clone())), None => Some(tree::TransferFunction::Table(Vec::new())), } } "discrete" => { match node.attribute::<&svgtypes::NumberList>(AId::TableValues) { Some(values) => Some(tree::TransferFunction::Discrete(values.0.clone())), None => Some(tree::TransferFunction::Discrete(Vec::new())), } } "linear" => { Some(tree::TransferFunction::Linear { slope: node.attribute(AId::Slope).unwrap_or(1.0), intercept: node.attribute(AId::Intercept).unwrap_or(0.0), }) } "gamma" => { Some(tree::TransferFunction::Gamma { amplitude: node.attribute(AId::Amplitude).unwrap_or(1.0), exponent: node.attribute(AId::Exponent).unwrap_or(1.0), offset: node.attribute(AId::Offset).unwrap_or(0.0), }) } _ => None, } } fn convert_fe_color_matrix( fe: svgtree::Node, primitives: &[tree::FilterPrimitive], ) -> tree::FilterKind { let kind = convert_color_matrix_kind(fe).unwrap_or_default(); tree::FilterKind::FeColorMatrix(tree::FeColorMatrix { input: resolve_input(fe, AId::In, primitives), kind, }) } fn convert_color_matrix_kind( fe: svgtree::Node ) -> Option { match fe.attribute(AId::Type) { Some("matrix") => { if let Some(list) = fe.attribute::<&svgtypes::NumberList>(AId::Values) { if list.len() == 20 { return Some(tree::FeColorMatrixKind::Matrix(list.0.clone())); } } } Some("saturate") => { if let Some(list) = fe.attribute::<&svgtypes::NumberList>(AId::Values) { if !list.is_empty() { let n = f64_bound(0.0, list[0], 1.0); return Some(tree::FeColorMatrixKind::Saturate(n.into())); } else { return Some(tree::FeColorMatrixKind::Saturate(1.0.into())); } } } Some("hueRotate") => { if let Some(list) = fe.attribute::<&svgtypes::NumberList>(AId::Values) { if !list.is_empty() { return Some(tree::FeColorMatrixKind::HueRotate(list[0])); } else { return Some(tree::FeColorMatrixKind::HueRotate(0.0)); } } } Some("luminanceToAlpha") => { return Some(tree::FeColorMatrixKind::LuminanceToAlpha); } _ => {} } None } #[inline(never)] fn resolve_input( node: svgtree::Node, aid: AId, primitives: &[tree::FilterPrimitive], ) -> tree::FilterInput { match node.attribute(aid) { Some(s) => { let input = parse_in(s); // If `in` references an unknown `result` than fallback // to previous result or `SourceGraphic`. if let tree::FilterInput::Reference(ref name) = input { if !primitives.iter().any(|p| p.result == *name) { return if let Some(ref prev) = primitives.last() { tree::FilterInput::Reference(prev.result.clone()) } else { tree::FilterInput::SourceGraphic }; } } input } None => { if let Some(ref prev) = primitives.last() { // If `in` is not set and this is not the first primitive // than the input is a result of the previous primitive. tree::FilterInput::Reference(prev.result.clone()) } else { // If `in` is not set and this is the first primitive // than the input is `SourceGraphic`. tree::FilterInput::SourceGraphic } } } } fn parse_in( s: &str, ) -> tree::FilterInput { match s { "SourceGraphic" => tree::FilterInput::SourceGraphic, "SourceAlpha" => tree::FilterInput::SourceAlpha, "BackgroundImage" => tree::FilterInput::BackgroundImage, "BackgroundAlpha" => tree::FilterInput::BackgroundAlpha, "FillPaint" => tree::FilterInput::FillPaint, "StrokePaint" => tree::FilterInput::StrokePaint, _ => tree::FilterInput::Reference(s.to_string()) } } fn gen_result( node: svgtree::Node, results: &mut FilterResults, ) -> String { match node.attribute::<&str>(AId::Result) { Some(s) => { // Remember predefined result. results.names.insert(s.to_string()); results.idx += 1; s.to_string() } None => { // Generate an unique name for `result`. loop { let name = format!("result{}", results.idx); results.idx += 1; if !results.names.contains(&name) { return name; } } } } } resvg-0.8.0/usvg/src/convert/image.rs000066400000000000000000000076621352576375700175670ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::path; use crate::{svgtree, tree, tree::prelude::*, utils}; use super::prelude::*; pub fn convert( node: svgtree::Node, state: &State, parent: &mut tree::Node, ) { let visibility = node.find_attribute(AId::Visibility).unwrap_or_default(); let rendering_mode = node .find_attribute(AId::ImageRendering) .unwrap_or(state.opt.image_rendering); let rect = try_opt_warn!(get_image_rect(node, state), "Image has an invalid size. Skipped."); let view_box = tree::ViewBox { rect, aspect: node.attribute(AId::PreserveAspectRatio).unwrap_or_default(), }; let href = match node.attribute(AId::Href) { Some(s) => s, _ => { warn!("The 'image' element lacks the 'xlink:href' attribute. Skipped."); return; } }; if let Some((data, format)) = get_href_data(node.element_id(), href, state.opt.path.as_ref()) { parent.append_kind(tree::NodeKind::Image(tree::Image { id: node.element_id().to_string(), transform: Default::default(), visibility, view_box, rendering_mode, data, format, })); } } pub fn get_href_data( element_id: &str, href: &str, path: Option<&path::PathBuf>, ) -> Option<(tree::ImageData, tree::ImageFormat)> { if href.starts_with("data:image/") { if let Ok(url) = data_url::DataUrl::process(href) { let format = match (url.mime_type().type_.as_str(), url.mime_type().subtype.as_str()) { ("image", "jpg") | ("image", "jpeg") => { tree::ImageFormat::JPEG } ("image", "png") => { tree::ImageFormat::PNG } ("image", "svg+xml") => { tree::ImageFormat::SVG } _ => { return None; } }; if let Ok((data, _)) = url.decode_to_vec() { return Some((tree::ImageData::Raw(data), format)); } } warn!("Image '{}' has an invalid 'xlink:href' content.", element_id); } else { let path = match path { Some(path) => path.parent().unwrap().join(href), None => path::PathBuf::from(href), }; if path.exists() { if let Some(format) = get_image_format(&path) { return Some((tree::ImageData::Path(path::PathBuf::from(href)), format)); } else { warn!("'{}' is not a PNG, JPEG or SVG(Z) image.", href); } } else { warn!("Linked file does not exist: '{}'.", href); } } None } /// Checks that file has a PNG or a JPEG magic bytes. /// Or SVG(Z) extension. fn get_image_format(path: &path::Path) -> Option { use std::fs; use std::io::Read; let ext = utils::file_extension(path)?.to_lowercase(); if ext == "svg" || ext == "svgz" { return Some(tree::ImageFormat::SVG); } let mut file = fs::File::open(path).ok()?; let mut d = Vec::new(); d.resize(8, 0); file.read_exact(&mut d).ok()?; if d.starts_with(b"\x89PNG\r\n\x1a\n") { Some(tree::ImageFormat::PNG) } else if d.starts_with(&[0xff, 0xd8, 0xff]) { Some(tree::ImageFormat::JPEG) } else { None } } fn get_image_rect( node: svgtree::Node, state: &State, ) -> Option { Rect::new( node.convert_user_length(AId::X, state, Length::zero()), node.convert_user_length(AId::Y, state, Length::zero()), node.convert_user_length(AId::Width, state, Length::zero()), node.convert_user_length(AId::Height, state, Length::zero()), ) } resvg-0.8.0/usvg/src/convert/marker.rs000066400000000000000000000353471352576375700177670ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::f64; use std::rc::Rc; use crate::{utils, svgtree, tree, tree::prelude::*, tree::PathSegment as Segment}; use super::{prelude::*, use_node}; pub fn is_valid( node: svgtree::Node, ) -> bool { // `marker-*` attributes can only be set on `path`, `line`, `polyline` and `polygon`. match node.tag_name() { Some(EId::Path) | Some(EId::Line) | Some(EId::Polyline) | Some(EId::Polygon) => {} _ => return false, } // `marker-*` attributes cannot be set on shapes inside the `clipPath`. if node.ancestors().any(|n| n.has_tag_name(EId::ClipPath)) { return false; } node.find_attribute::(AId::MarkerStart).is_some() || node.find_attribute::(AId::MarkerMid).is_some() || node.find_attribute::(AId::MarkerEnd).is_some() } pub fn convert( node: svgtree::Node, path: &tree::PathData, state: &State, parent: &mut tree::Node, tree: &mut tree::Tree, ) { let list = [ (AId::MarkerStart, MarkerKind::Start), (AId::MarkerMid, MarkerKind::Middle), (AId::MarkerEnd, MarkerKind::End), ]; for (aid, kind) in &list { let mut marker = None; if let Some(link) = node.find_attribute::(*aid) { if link.has_tag_name(EId::Marker) { marker = Some(link); } } if let Some(marker) = marker { // Check for recursive marker. if state.parent_marker == Some(marker) { continue; } resolve(node, path, marker, *kind, state, parent, tree); } } } #[derive(Clone, Copy)] enum MarkerKind { Start, Middle, End, } enum MarkerOrientation { Auto, Angle(f64), } fn resolve( shape_node: svgtree::Node, path: &tree::PathData, marker_node: svgtree::Node, marker_kind: MarkerKind, state: &State, parent: &mut tree::Node, tree: &mut tree::Tree, ) { let stroke_scale = try_opt!(stroke_scale(shape_node, marker_node, state)); let r = try_opt!(convert_rect(marker_node, state)); let view_box = marker_node.get_viewbox().map(|vb| tree::ViewBox { rect: vb, aspect: marker_node.attribute(AId::PreserveAspectRatio).unwrap_or_default(), } ); let has_overflow = { let overflow = marker_node.attribute(AId::Overflow); // `overflow` is `hidden` by default. overflow == None || overflow == Some("hidden") || overflow == Some("scroll") }; let clip_path = if has_overflow { let clip_rect = if let Some(vbox) = view_box { vbox.rect } else { r.size().to_rect(0.0, 0.0) }; let id = use_node::gen_clip_path_id(shape_node, tree); let mut clip_path = tree.append_to_defs( tree::NodeKind::ClipPath(tree::ClipPath { id: id.clone(), units: tree::Units::UserSpaceOnUse, transform: tree::Transform::default(), clip_path: None, }) ); clip_path.append_kind(tree::NodeKind::Path(tree::Path { fill: Some(tree::Fill::default()), data: Rc::new(tree::PathData::from_rect(clip_rect)), ..tree::Path::default() })); Some(id) } else { None }; let draw_marker = |x: f64, y: f64, idx: usize| { let mut ts = tree::Transform::new_translate(x, y); let angle = match convert_orientation(marker_node) { MarkerOrientation::Auto => calc_vertex_angle(path, idx), MarkerOrientation::Angle(angle) => angle, }; if !angle.is_fuzzy_zero() { ts.rotate(angle); } if let Some(vbox) = view_box { let size = Size::new(r.width() * stroke_scale, r.height() * stroke_scale).unwrap(); let vbox_ts = utils::view_box_to_transform(vbox.rect, vbox.aspect, size); let (sx, sy) = vbox_ts.get_scale(); ts.scale(sx, sy); } else { ts.scale(stroke_scale, stroke_scale); } ts.translate(-r.x(), -r.y()); // TODO: do not create a group when no clipPath let mut g_node = parent.append_kind(tree::NodeKind::Group(tree::Group { id: String::new(), transform: ts, opacity: tree::Opacity::default(), clip_path: clip_path.clone(), mask: None, filter: None, })); let mut marker_state = state.clone(); marker_state.parent_marker = Some(marker_node); super::convert_children(marker_node, &marker_state, &mut g_node, tree); if !g_node.has_children() { g_node.detach(); } }; draw_markers(path, marker_kind, draw_marker); } fn stroke_scale( path_node: svgtree::Node, marker_node: svgtree::Node, state: &State, ) -> Option { match marker_node.attribute(AId::MarkerUnits) { Some("userSpaceOnUse") => Some(1.0), _ => { let sw = path_node.resolve_length(AId::StrokeWidth, state, 1.0); if !(sw > 0.0) { None } else { Some(sw) } } } } fn draw_markers

( path: &tree::PathData, kind: MarkerKind, mut draw_marker: P, ) where P: FnMut(f64, f64, usize) { match kind { MarkerKind::Start => { if let Some(tree::PathSegment::MoveTo { x, y }) = path.first() { draw_marker(*x, *y, 0); } } MarkerKind::Middle => { let total = path.len() - 1; let mut i = 1; while i < total { let (x, y) = match path[i] { tree::PathSegment::MoveTo { x, y } => (x, y), tree::PathSegment::LineTo { x, y } => (x, y), tree::PathSegment::CurveTo { x, y, .. } => (x, y), _ => { i += 1; continue; } }; draw_marker(x, y, i); i += 1; } } MarkerKind::End => { let idx = path.len() - 1; match path.last() { Some(Segment::LineTo { x, y }) => { draw_marker(*x, *y, idx); } Some(Segment::CurveTo { x, y, .. }) => { draw_marker(*x, *y, idx); } Some(Segment::ClosePath) => { let (x, y) = get_subpath_start(path, idx); draw_marker(x, y, idx); } _ => {} } } } } fn calc_vertex_angle(path: &tree::PathData, idx: usize) -> f64 { if idx == 0 { // First segment. debug_assert!(path.len() > 1); let seg1 = path[0]; let seg2 = path[1]; match (seg1, seg2) { (Segment::MoveTo { x: mx, y: my }, Segment::LineTo { x, y }) => { calc_line_angle(mx, my, x, y) } (Segment::MoveTo { x: mx, y: my }, Segment::CurveTo { x1, y1, x, y, .. }) => { if mx.fuzzy_eq(&x1) && my.fuzzy_eq(&y1) { calc_line_angle(mx, my, x, y) } else { calc_line_angle(mx, my, x1, y1) } } _ => 0.0, } } else if idx == path.len() - 1 { // Last segment. let seg1 = path[idx - 1]; let seg2 = path[idx]; match (seg1, seg2) { (_, Segment::MoveTo { .. }) => 0.0, // unreachable (_, Segment::LineTo { x, y }) => { let (px, py) = get_prev_vertex(path, idx); calc_line_angle(px, py, x, y) } (_, Segment::CurveTo { x2, y2, x, y, .. }) => { if x2.fuzzy_eq(&x) && y2.fuzzy_eq(&y) { let (px, py) = get_prev_vertex(path, idx); calc_line_angle(px, py, x, y) } else { calc_line_angle(x2, y2, x, y) } } (Segment::LineTo { x, y }, Segment::ClosePath) => { let (nx, ny) = get_subpath_start(path, idx); calc_line_angle(x, y, nx, ny) } (Segment::CurveTo { x2, y2, x, y, .. }, Segment::ClosePath) => { let (px, py) = get_prev_vertex(path, idx); let (nx, ny) = get_subpath_start(path, idx); calc_curves_angle( px, py, x2, y2, x, y, nx, ny, nx, ny, ) } (_, Segment::ClosePath) => 0.0, } } else { // Middle segments. let seg1 = path[idx]; let seg2 = path[idx + 1]; // Not sure if there is a better way. match (seg1, seg2) { (Segment::MoveTo { x: mx, y: my }, Segment::LineTo { x, y }) => { calc_line_angle(mx, my, x, y) } (Segment::MoveTo { x: mx, y: my }, Segment::CurveTo { x1, y1, .. }) => { calc_line_angle(mx, my, x1, y1) } (Segment::LineTo { x: x1, y: y1 }, Segment::LineTo { x: x2, y: y2 }) => { let (px, py) = get_prev_vertex(path, idx); calc_angle( px, py, x1, y1, x1, y1, x2, y2, ) } (Segment::CurveTo { x2: c1_x2, y2: c1_y2, x, y, .. }, Segment::CurveTo { x1: c2_x1, y1: c2_y1, x: nx, y: ny, .. }) => { let (px, py) = get_prev_vertex(path, idx); calc_curves_angle( px, py, c1_x2, c1_y2, x, y, c2_x1, c2_y1, nx, ny, ) } (Segment::LineTo { x, y }, Segment::CurveTo { x1, y1, x: nx, y: ny, .. }) => { let (px, py) = get_prev_vertex(path, idx); calc_curves_angle( px, py, px, py, x, y, x1, y1, nx, ny, ) } (Segment::CurveTo { x2, y2, x, y, .. }, Segment::LineTo { x: nx, y: ny }) => { let (px, py) = get_prev_vertex(path, idx); calc_curves_angle( px, py, x2, y2, x, y, nx, ny, nx, ny, ) } (Segment::LineTo { x, y }, Segment::MoveTo { .. }) => { let (px, py) = get_prev_vertex(path, idx); calc_line_angle(px, py, x, y) } (Segment::CurveTo { x2, y2, x, y, .. }, Segment::MoveTo { .. }) => { if x.fuzzy_eq(&x2) && y.fuzzy_eq(&y2) { let (px, py) = get_prev_vertex(path, idx); calc_line_angle(px, py, x, y) } else { calc_line_angle(x2, y2, x, y) } } (Segment::LineTo { x, y }, Segment::ClosePath) => { let (px, py) = get_prev_vertex(path, idx); let (nx, ny) = get_subpath_start(path, idx); calc_angle( px, py, x, y, x, y, nx, ny, ) } (_, Segment::ClosePath) => { let (px, py) = get_prev_vertex(path, idx); let (nx, ny) = get_subpath_start(path, idx); calc_line_angle(px, py, nx, ny) } (_, Segment::MoveTo { .. }) | (Segment::ClosePath, _) => { 0.0 } } } } fn calc_line_angle( x1: f64, y1: f64, x2: f64, y2: f64, ) -> f64 { calc_angle(x1, y1, x2, y2, x1, y1, x2, y2) } fn calc_curves_angle( px: f64, py: f64, // previous vertex cx1: f64, cy1: f64, // previous control point x: f64, y: f64, // current vertex cx2: f64, cy2: f64, // next control point nx: f64, ny: f64, // next vertex ) -> f64 { if cx1.fuzzy_eq(&x) && cy1.fuzzy_eq(&y) { calc_angle(px, py, x, y, x, y, cx2, cy2) } else if x.fuzzy_eq(&cx2) && y.fuzzy_eq(&cy2) { calc_angle(cx1, cy1, x, y, x, y, nx, ny) } else { calc_angle(cx1, cy1, x, y, x, y, cx2, cy2) } } fn calc_angle( x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64, x4: f64, y4: f64, ) -> f64 { use std::f64::consts::*; fn normalize(rad: f64) -> f64 { let v = rad % (PI * 2.0); if v < 0.0 { v + PI * 2.0 } else { v } } fn vector_angle(vx: f64, vy: f64) -> f64 { let rad = vy.atan2(vx); if rad.is_nan() { 0.0 } else { normalize(rad) } } let in_a = vector_angle(x2 - x1, y2 - y1); let out_a = vector_angle(x4 - x3, y4 - y3); let d = (out_a - in_a) * 0.5; let mut angle = in_a + d; if FRAC_PI_2 < d.abs() { angle -= PI; } normalize(angle).to_degrees() } fn get_subpath_start( segments: &[Segment], idx: usize, ) -> (f64, f64) { let offset = segments.len() - idx; for seg in segments.iter().rev().skip(offset) { if let Segment::MoveTo { x, y } = *seg { return (x, y); } } (0.0, 0.0) } fn get_prev_vertex( segments: &[Segment], idx: usize, ) -> (f64, f64) { match segments[idx - 1] { Segment::MoveTo { x, y } => (x, y), Segment::LineTo { x, y } => (x, y), Segment::CurveTo { x, y, .. } => (x, y), Segment::ClosePath => get_subpath_start(segments, idx), } } fn convert_rect( node: svgtree::Node, state: &State, ) -> Option { Rect::new( node.convert_user_length(AId::RefX, state, Length::zero()), node.convert_user_length(AId::RefY, state, Length::zero()), node.convert_user_length(AId::MarkerWidth, state, Length::new_number(3.0)), node.convert_user_length(AId::MarkerHeight, state, Length::new_number(3.0)), ) } fn convert_orientation( node: svgtree::Node, ) -> MarkerOrientation { use svgtypes::{Angle, AngleUnit}; if node.attribute(AId::Orient) == Some("auto") { MarkerOrientation::Auto } else { match node.attribute::(AId::Orient) { Some(angle) => { let a = match angle.unit { AngleUnit::Degrees => angle.num, AngleUnit::Gradians => angle.num * 180.0 / 200.0, AngleUnit::Radians => angle.num.to_degrees(), }; MarkerOrientation::Angle(a) } None => { MarkerOrientation::Angle(0.0) } } } } resvg-0.8.0/usvg/src/convert/mod.rs000066400000000000000000000445471352576375700172670ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::cell::RefCell; use std::rc::Rc; use svgtypes::Length; use crate::{svgtree, tree, tree::prelude::*, fontdb, Error}; mod clip_and_mask; mod filter; mod image; mod marker; mod paint_server; mod shapes; mod style; mod switch; mod text; mod units; mod use_node; mod prelude { pub use log::warn; pub use svgtypes::{FuzzyEq, FuzzyZero, Length}; pub use crate::{geom::*, short::*, svgtree::{AId, EId}, Options}; pub use super::{SvgNodeExt, State}; } use self::prelude::*; #[derive(Clone)] pub struct State<'a> { parent_clip_path: Option>, parent_marker: Option>, size: Size, view_box: Rect, db: Rc>, opt: &'a Options, } /// Converts an input `Document` into a `Tree`. /// /// # Errors /// /// - If `Document` doesn't have an SVG node - returns an empty tree. /// - If `Document` doesn't have a valid size - returns `Error::InvalidSize`. pub fn convert_doc( svg_doc: &svgtree::Document, opt: &Options, ) -> Result { let svg = svg_doc.root_element(); let size = resolve_svg_size(&svg, opt)?; let view_box = { tree::ViewBox { rect: get_view_box(&svg, size), aspect: svg.attribute(AId::PreserveAspectRatio).unwrap_or_default(), } }; if !style::is_visible_element(svg, opt) { let svg_kind = tree::Svg { size, view_box, }; return Ok(tree::Tree::create(svg_kind)); } let svg_kind = tree::Svg { size, view_box, }; let state = State { parent_clip_path: None, parent_marker: None, size, view_box: view_box.rect, db: Rc::new(RefCell::new(fontdb::Database::new())), opt: &opt, }; let mut tree = tree::Tree::create(svg_kind); convert_children(svg_doc.root(), &state, &mut tree.root(), &mut tree); remove_empty_groups(&mut tree); ungroup_groups(&mut tree, opt); remove_unused_defs(&mut tree); Ok(tree) } fn resolve_svg_size( svg: &svgtree::Node, opt: &Options, ) -> Result { let mut state = State { parent_clip_path: None, parent_marker: None, size: Size::new(100.0, 100.0).unwrap(), view_box: Rect::new(0.0, 0.0, 100.0, 100.0).unwrap(), db: Rc::new(RefCell::new(fontdb::Database::new())), opt, }; let def = Length::new(100.0, Unit::Percent); let width: Length = svg.attribute(AId::Width).unwrap_or(def); let height: Length = svg.attribute(AId::Height).unwrap_or(def); let view_box = svg.get_viewbox(); if (width.unit == Unit::Percent || height.unit == Unit::Percent) && view_box.is_none() { // TODO: it this case we should detect the bounding box of all elements, // which is currently impossible return Err(Error::InvalidSize); } let size = if let Some(vbox) = view_box { state.view_box = vbox; let w = if width.unit == Unit::Percent { vbox.width() * (width.num / 100.0) } else { svg.convert_user_length(AId::Width, &state, def) }; let h = if height.unit == Unit::Percent { vbox.height() * (height.num / 100.0) } else { svg.convert_user_length(AId::Height, &state, def) }; Size::new(w, h) } else { Size::new( svg.convert_user_length(AId::Width, &state, def), svg.convert_user_length(AId::Height, &state, def), ) }; if let Some(size) = size { Ok(size) } else { Err(Error::InvalidSize) } } fn get_view_box( svg: &svgtree::Node, size: Size, ) -> Rect { match svg.get_viewbox() { Some(vb) => vb, None => size.to_rect(0.0, 0.0), } } #[inline(never)] fn convert_children( parent_node: svgtree::Node, state: &State, parent: &mut tree::Node, tree: &mut tree::Tree, ) { for node in parent_node.children() { convert_element(node, state, parent, tree); } } #[inline(never)] fn convert_element( node: svgtree::Node, state: &State, parent: &mut tree::Node, tree: &mut tree::Tree, ) { let tag_name = try_opt!(node.tag_name()); let is_valid_child = tag_name.is_graphic() || tag_name == EId::G || tag_name == EId::Switch || tag_name == EId::Svg; if !is_valid_child { return; } if !style::is_visible_element(node, state.opt) { return; } if tag_name == EId::Use { use_node::convert(node, state, parent, tree); return; } if tag_name == EId::Switch { switch::convert(node, state, parent, tree); return; } let parent = &mut match convert_group(node, state, false, parent, tree) { GroupKind::Create(g) => g, GroupKind::Skip => parent.clone(), GroupKind::Ignore => return, }; match tag_name { EId::Rect | EId::Circle | EId::Ellipse | EId::Line | EId::Polyline | EId::Polygon | EId::Path => { if let Some(path) = shapes::convert(node, state) { convert_path(node, path, state, parent, tree); } } EId::Image => { image::convert(node, state, parent); } EId::Text => { text::convert(node, state, parent, tree); } EId::Svg => { if node.parent_element().is_some() { use_node::convert_svg(node, state, parent, tree); } else { // Skip root `svg`. convert_children(node, state, parent, tree); } } EId::G => { convert_children(node, state, parent, tree); } _ => {} } } // `clipPath` can have only shape and `text` children. // // `line` doesn't impact rendering because stroke is always disabled // for `clipPath` children. #[inline(never)] fn convert_clip_path_elements( clip_node: svgtree::Node, state: &State, parent: &mut tree::Node, tree: &mut tree::Tree, ) { for node in clip_node.children() { let tag_name = try_opt!(node.tag_name()); if !tag_name.is_graphic() { continue; } if !style::is_visible_element(node, state.opt) { continue; } if tag_name == EId::Use { use_node::convert(node, state, parent, tree); continue; } let parent = &mut match convert_group(node, state, false, parent, tree) { GroupKind::Create(g) => g, GroupKind::Skip => parent.clone(), GroupKind::Ignore => continue, }; match tag_name { EId::Rect | EId::Circle | EId::Ellipse | EId::Polyline | EId::Polygon | EId::Path => { if let Some(path) = shapes::convert(node, state) { convert_path(node, path, state, parent, tree); } } EId::Text => { text::convert(node, state, parent, tree); } _ => { warn!("'{}' is no a valid 'clip-path' child.", tag_name); } } } } enum GroupKind { /// Creates a new group. Create(tree::Node), /// Skips an existing group, but processes its children. Skip, /// Skips an existing group and all its children. Ignore, } fn convert_group( node: svgtree::Node, state: &State, force: bool, parent: &mut tree::Node, tree: &mut tree::Tree, ) -> GroupKind { // A `clipPath` child cannot have an opacity. let opacity = if state.parent_clip_path.is_none() { node.attribute(AId::Opacity).unwrap_or_default() } else { tree::Opacity::default() }; macro_rules! resolve_link { ($aid:expr, $f:expr) => {{ let mut v = None; if let Some(link) = node.attribute::($aid) { v = $f(link, state, tree); // If `$aid` is linked to an invalid element - skip this group completely. if v.is_none() { return GroupKind::Ignore; } } v }}; } // `mask` and `filter` cannot be set on `clipPath` children. // But `clip-path` can. let clip_path = resolve_link!(AId::ClipPath, clip_and_mask::convert_clip); let mask = if state.parent_clip_path.is_none() { resolve_link!(AId::Mask, clip_and_mask::convert_mask) } else { None }; let mut filter = None; if state.parent_clip_path.is_none() { if let Some(link) = node.attribute::(AId::Filter) { filter = filter::convert(link, state, tree); // If `filter` is linked to an invalid element - skip this group completely. if filter.is_none() { return GroupKind::Ignore; } } else if node.attribute(AId::Filter) == Some("none") { // Do nothing. } else if node.has_attribute(AId::Filter) { // Unlike `clip-path` and `mask`, when `filter` is invalid // than the whole element should be removed. return GroupKind::Ignore; } } let transform: tree::Transform = node.attribute(AId::Transform).unwrap_or_default(); let required = opacity.value().fuzzy_ne(&1.0) || clip_path.is_some() || mask.is_some() || filter.is_some() || !transform.is_default() || (node.has_tag_name(EId::G) && state.opt.keep_named_groups) || force; if required { let id = if node.has_tag_name(EId::G) { node.element_id().to_string() } else { String::new() }; let g = parent.append_kind(tree::NodeKind::Group(tree::Group { id, transform, opacity, clip_path, mask, filter, })); GroupKind::Create(g) } else { GroupKind::Skip } } fn remove_empty_groups( tree: &mut tree::Tree, ) { fn rm(parent: tree::Node) -> bool { let mut changed = false; let mut curr_node = parent.first_child(); while let Some(mut node) = curr_node { curr_node = node.next_sibling(); let is_g = if let tree::NodeKind::Group(ref g) = *node.borrow() { // Skip empty groups when they do not have a `filter` property. // The `filter` property can be set on empty groups. For example: // // // // // g.filter.is_none() } else { false }; if is_g && !node.has_children() { node.detach(); changed = true; } else { if rm(node) { changed = true; } } } changed } while rm(tree.root()) {} } fn ungroup_groups( tree: &mut tree::Tree, opt: &Options, ) { fn ungroup(parent: tree::Node, opt: &Options) -> bool { let mut changed = false; let mut curr_node = parent.first_child(); while let Some(mut node) = curr_node { curr_node = node.next_sibling(); let mut ts = tree::Transform::default(); let is_ok = if let tree::NodeKind::Group(ref g) = *node.borrow() { ts = g.transform; g.opacity.is_default() && g.clip_path.is_none() && g.mask.is_none() && g.filter.is_none() && !(opt.keep_named_groups && !g.id.is_empty()) } else { false }; if is_ok { let mut curr_child = node.last_child(); while let Some(mut child) = curr_child { curr_child = child.previous_sibling(); // Update transform. match *child.borrow_mut() { tree::NodeKind::Path(ref mut path) => { path.transform.prepend(&ts); } tree::NodeKind::Image(ref mut img) => { img.transform.prepend(&ts); } tree::NodeKind::Group(ref mut g) => { g.transform.prepend(&ts); } _ => {} } child.detach(); node.insert_after(child.clone()); } node.detach(); changed = true; } else { if ungroup(node, opt) { changed = true; } } } changed } while ungroup(tree.root(), opt) {} } fn remove_unused_defs( tree: &mut tree::Tree, ) { macro_rules! check_id { ($from:expr, $id:expr) => { if let Some(ref id) = $from { if $id == id { return true; } } }; } macro_rules! check_paint_id { ($from:expr, $id:expr) => { if let Some(ref v) = $from { if let tree::Paint::Link(ref paint_id) = v.paint { if $id == paint_id { return true; } } } }; } fn is_used(tree: &tree::Tree, id: &str) -> bool { for node in tree.root().descendants() { match *node.borrow() { tree::NodeKind::ClipPath(ref clip) => { check_id!(clip.clip_path, id); } tree::NodeKind::Mask(ref mask) => { check_id!(mask.mask, id); } tree::NodeKind::Path(ref path) => { check_paint_id!(path.fill, id); check_paint_id!(path.stroke, id); } tree::NodeKind::Group(ref g) => { check_id!(g.clip_path, id); check_id!(g.mask, id); check_id!(g.filter, id); } _ => {} } } false } let mut is_changed = true; while is_changed { is_changed = false; let mut curr_node = tree.defs().first_child(); while let Some(mut node) = curr_node { curr_node = node.next_sibling(); if !is_used(tree, node.id().as_ref()) { node.detach(); is_changed = true; } } } } fn convert_path( node: svgtree::Node, path: tree::SharedPathData, state: &State, parent: &mut tree::Node, tree: &mut tree::Tree, ) { debug_assert!(path.len() >= 2); if path.len() < 2 { return; } let has_bbox = path.has_bbox(); let fill = style::resolve_fill(node, has_bbox, state, tree); let stroke = style::resolve_stroke(node, has_bbox, state, tree); let mut visibility = node.find_attribute(AId::Visibility).unwrap_or_default(); let rendering_mode = node .find_attribute(AId::ShapeRendering) .unwrap_or(state.opt.shape_rendering); // If a path doesn't have a fill or a stroke than it's invisible. // By setting `visibility` to `hidden` we are disabling the rendering of this path. if fill.is_none() && stroke.is_none() { visibility = tree::Visibility::Hidden } let mut markers_group = None; if marker::is_valid(node) && visibility == tree::Visibility::Visible { let mut g = parent.append_kind(tree::NodeKind::Group(tree::Group::default())); marker::convert(node, &path, state, &mut g, tree); markers_group = Some(g); } parent.append_kind(tree::NodeKind::Path(tree::Path { id: node.element_id().to_string(), transform: Default::default(), visibility, fill, stroke, rendering_mode, data: path, })); // Insert markers group after `path`. if let Some(mut g) = markers_group { g.detach(); parent.append(g); } } pub trait SvgNodeExt { fn resolve_length(&self, aid: AId, state: &State, def: f64) -> f64; fn convert_length(&self, aid: AId, object_units: tree::Units, state: &State, def: Length) -> f64; fn try_convert_length(&self, aid: AId, object_units: tree::Units, state: &State) -> Option; fn convert_user_length(&self, aid: AId, state: &State, def: Length) -> f64; fn try_convert_user_length(&self, aid: AId, state: &State) -> Option; } impl<'a> SvgNodeExt for svgtree::Node<'a> { fn resolve_length(&self, aid: AId, state: &State, def: f64) -> f64 { let is_inheritable = match aid { AId::BaselineShift | AId::FontSize => false, _ => true, }; debug_assert!(is_inheritable); if let Some(n) = self.ancestors().find(|n| n.has_attribute(aid)) { if let Some(length) = n.attribute(aid) { return units::convert_length(length, n, aid, tree::Units::UserSpaceOnUse, state); } } def } fn convert_length(&self, aid: AId, object_units: tree::Units, state: &State, def: Length) -> f64 { let length = self.attribute(aid).unwrap_or(def); units::convert_length(length, *self, aid, object_units, state) } fn try_convert_length(&self, aid: AId, object_units: tree::Units, state: &State) -> Option { let length = self.attribute(aid)?; Some(units::convert_length(length, *self, aid, object_units, state)) } fn convert_user_length(&self, aid: AId, state: &State, def: Length) -> f64 { self.convert_length(aid, tree::Units::UserSpaceOnUse, state, def) } fn try_convert_user_length(&self, aid: AId, state: &State) -> Option { self.try_convert_length(aid, tree::Units::UserSpaceOnUse, state) } } resvg-0.8.0/usvg/src/convert/paint_server.rs000066400000000000000000000373241352576375700212040ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::f64; use crate::{svgtree, tree}; use super::prelude::*; pub enum ServerOrColor { Server { id: String, units: tree::Units, }, Color { color: tree::Color, opacity: tree::Opacity, }, } pub fn convert( node: svgtree::Node, state: &State, tree: &mut tree::Tree, ) -> Option { // Check for existing. if let Some(exist_node) = tree.defs_by_id(node.element_id()) { let units = match *exist_node.borrow() { tree::NodeKind::LinearGradient(ref lg) => lg.units, tree::NodeKind::RadialGradient(ref rg) => rg.units, tree::NodeKind::Pattern(ref patt) => patt.units, _ => return None, // Unreachable. }; return Some(ServerOrColor::Server { id: node.element_id().to_string(), units, }); } // Unwrap is safe, because we already checked for is_paint_server(). match node.tag_name().unwrap() { EId::LinearGradient => convert_linear(node, state, tree), EId::RadialGradient => convert_radial(node, state, tree), EId::Pattern => convert_pattern(node, state, tree), _ => unreachable!(), } } #[inline(never)] fn convert_linear( node: svgtree::Node, state: &State, tree: &mut tree::Tree, ) -> Option { let stops = convert_stops(find_gradient_with_stops(node)?); if stops.len() < 2 { return stops_to_color(&stops); } let units = convert_units(node, AId::GradientUnits, tree::Units::ObjectBoundingBox); let spread_method = convert_spread_method(node); let x1 = resolve_number(node, AId::X1, units, state, Length::zero()); let y1 = resolve_number(node, AId::Y1, units, state, Length::zero()); let x2 = resolve_number(node, AId::X2, units, state, Length::new(100.0, Unit::Percent)); let y2 = resolve_number(node, AId::Y2, units, state, Length::zero()); let transform = { let n = resolve_attr(node, AId::GradientTransform); n.attribute(AId::GradientTransform).unwrap_or_default() }; tree.append_to_defs( tree::NodeKind::LinearGradient(tree::LinearGradient { id: node.element_id().to_string(), x1, y1, x2, y2, base: tree::BaseGradient { units, transform, spread_method, stops, } }) ); Some(ServerOrColor::Server { id: node.element_id().to_string(), units, }) } #[inline(never)] fn convert_radial( node: svgtree::Node, state: &State, tree: &mut tree::Tree, ) -> Option { let stops = convert_stops(find_gradient_with_stops(node)?); if stops.len() < 2 { return stops_to_color(&stops); } let units = convert_units(node, AId::GradientUnits, tree::Units::ObjectBoundingBox); let r = resolve_number(node, AId::R, units, state, Length::new(50.0, Unit::Percent)); // 'A value of zero will cause the area to be painted as a single color // using the color and opacity of the last gradient stop.' // // https://www.w3.org/TR/SVG11/pservers.html#RadialGradientElementRAttribute if !(r > 0.0) { let stop = stops.last().unwrap(); return Some(ServerOrColor::Color { color: stop.color, opacity: stop.opacity, }); } let spread_method = convert_spread_method(node); let cx = resolve_number(node, AId::Cx, units, state, Length::new(50.0, Unit::Percent)); let cy = resolve_number(node, AId::Cy, units, state, Length::new(50.0, Unit::Percent)); let fx = resolve_number(node, AId::Fx, units, state, Length::new_number(cx)); let fy = resolve_number(node, AId::Fy, units, state, Length::new_number(cy)); let (fx, fy) = prepare_focal(cx, cy, r, fx, fy); let transform = { let n = resolve_attr(node, AId::GradientTransform); n.attribute(AId::GradientTransform).unwrap_or_default() }; tree.append_to_defs( tree::NodeKind::RadialGradient(tree::RadialGradient { id: node.element_id().to_string(), cx, cy, r: r.into(), fx, fy, base: tree::BaseGradient { units, transform, spread_method, stops, } }) ); Some(ServerOrColor::Server { id: node.element_id().to_string(), units, }) } #[inline(never)] fn convert_pattern( node: svgtree::Node, state: &State, tree: &mut tree::Tree, ) -> Option { let node_with_children = find_pattern_with_children(node)?; let view_box = { let n1 = resolve_attr(node, AId::ViewBox); let n2 = resolve_attr(node, AId::PreserveAspectRatio); n1.get_viewbox().map(|vb| tree::ViewBox { rect: vb, aspect: n2.attribute(AId::PreserveAspectRatio).unwrap_or_default(), } ) }; let units = convert_units(node, AId::PatternUnits, tree::Units::ObjectBoundingBox); let content_units = convert_units(node, AId::PatternContentUnits, tree::Units::UserSpaceOnUse); let transform = { let n = resolve_attr(node, AId::PatternTransform); n.attribute(AId::PatternTransform).unwrap_or_default() }; let rect = Rect::new( resolve_number(node, AId::X, units, state, Length::zero()), resolve_number(node, AId::Y, units, state, Length::zero()), resolve_number(node, AId::Width, units, state, Length::zero()), resolve_number(node, AId::Height, units, state, Length::zero()), ); let rect = try_opt_warn_or!( rect, None, "Pattern '{}' has an invalid size. Skipped.", node.element_id() ); let mut patt = tree.append_to_defs(tree::NodeKind::Pattern(tree::Pattern { id: node.element_id().to_string(), units, content_units, transform, rect, view_box, })); super::convert_children(node_with_children, state, &mut patt, tree); if !patt.has_children() { return None; } Some(ServerOrColor::Server { id: node.element_id().to_string(), units, }) } fn convert_spread_method(node: svgtree::Node) -> tree::SpreadMethod { let node = resolve_attr(node, AId::SpreadMethod); node.attribute(AId::SpreadMethod).unwrap_or_default() } pub fn convert_units( node: svgtree::Node, name: AId, def: tree::Units, ) -> tree::Units { let node = resolve_attr(node, name); node.attribute(name).unwrap_or(def) } fn find_gradient_with_stops(node: svgtree::Node) -> Option { for link_id in node.href_iter() { let link = node.document().get(link_id); if !link.tag_name().unwrap().is_gradient() { warn!( "Gradient '{}' cannot reference '{}' via 'xlink:href'.", node.element_id(), link.tag_name().unwrap() ); return None; } if link.children().any(|n| n.has_tag_name(EId::Stop)) { return Some(link.clone()); } } None } fn find_pattern_with_children(node: svgtree::Node) -> Option { for link_id in node.href_iter() { let link = node.document().get(link_id); if !link.has_tag_name(EId::Pattern) { warn!( "Pattern '{}' cannot reference '{}' via 'xlink:href'.", node.element_id(), link.tag_name().unwrap() ); return None; } if link.has_children() { return Some(link.clone()); } } None } fn convert_stops(grad: svgtree::Node) -> Vec { let mut stops = Vec::new(); { let mut prev_offset = Length::zero(); for stop in grad.children() { if !stop.has_tag_name(EId::Stop) { warn!("Invalid gradient child: '{:?}'.", stop.tag_name().unwrap()); continue; } // `number` can be either a number or a percentage. let offset = stop.attribute(AId::Offset).unwrap_or(prev_offset); let offset = match offset.unit { Unit::None => offset.num, Unit::Percent => offset.num / 100.0, _ => prev_offset.num, }; let offset = f64_bound(0.0, offset, 1.0); prev_offset = Length::new_number(offset); let color = match stop.attribute(AId::StopColor) { Some(&svgtree::AttributeValue::CurrentColor) => { stop.find_attribute(AId::Color).unwrap_or_else(tree::Color::black) } Some(&svgtree::AttributeValue::Color(c)) => { c } _ => { svgtypes::Color::black() } }; stops.push(tree::Stop { offset: offset.into(), color, opacity: stop.attribute(AId::StopOpacity).unwrap_or_default(), }); } } // Remove stops with equal offset. // // Example: // offset="0.5" // offset="0.7" // offset="0.7" <-- this one should be removed // offset="0.7" // offset="0.9" if stops.len() >= 3 { let mut i = 0; while i < stops.len() - 2 { let offset1 = stops[i + 0].offset.value(); let offset2 = stops[i + 1].offset.value(); let offset3 = stops[i + 2].offset.value(); if offset1.fuzzy_eq(&offset2) && offset2.fuzzy_eq(&offset3) { // Remove offset in the middle. stops.remove(i + 1); } else { i += 1; } } } // Remove zeros. // // From: // offset="0.0" // offset="0.0" // offset="0.7" // // To: // offset="0.0" // offset="0.00000001" // offset="0.7" if stops.len() >= 2 { let mut i = 0; while i < stops.len() - 1 { let offset1 = stops[i + 0].offset.value(); let offset2 = stops[i + 1].offset.value(); if offset1.is_fuzzy_zero() && offset2.is_fuzzy_zero() { stops[i + 1].offset = (offset1 + f64::EPSILON).into(); } i += 1; } } // Shift equal offsets. // // From: // offset="0.5" // offset="0.7" // offset="0.7" // // To: // offset="0.5" // offset="0.699999999" // offset="0.7" { let mut i = 1; while i < stops.len() { let offset1 = stops[i - 1].offset.value(); let offset2 = stops[i - 0].offset.value(); // Next offset must be smaller then previous. if offset1 > offset2 || offset1.fuzzy_eq(&offset2) { // Make previous offset a bit smaller. let new_offset = offset1 - f64::EPSILON; stops[i - 1].offset = f64_bound(0.0, new_offset, 1.0).into(); stops[i - 0].offset = offset1.into(); } i += 1; } } stops } #[inline(never)] pub fn resolve_number( node: svgtree::Node, name: AId, units: tree::Units, state: &State, def: Length ) -> f64 { resolve_attr(node, name).convert_length(name, units, state, def) } fn resolve_attr( node: svgtree::Node, name: AId, ) -> svgtree::Node { if node.has_attribute(name) { return node; } match node.tag_name().unwrap() { EId::LinearGradient => resolve_lg_attr(node, name), EId::RadialGradient => resolve_rg_attr(node, name), EId::Pattern => resolve_pattern_attr(node, name), EId::Filter => resolve_filter_attr(node, name), _ => node, } } fn resolve_lg_attr( node: svgtree::Node, name: AId, ) -> svgtree::Node { for link_id in node.href_iter() { let link = node.document().get(link_id); let tag_name = try_opt_or!(link.tag_name(), node); match (name, tag_name) { // Coordinates can be resolved only from // ref element with the same type. (AId::X1, EId::LinearGradient) | (AId::Y1, EId::LinearGradient) | (AId::X2, EId::LinearGradient) | (AId::Y2, EId::LinearGradient) // Other attributes can be resolved // from any kind of gradient. | (AId::GradientUnits, EId::LinearGradient) | (AId::GradientUnits, EId::RadialGradient) | (AId::SpreadMethod, EId::LinearGradient) | (AId::SpreadMethod, EId::RadialGradient) | (AId::GradientTransform, EId::LinearGradient) | (AId::GradientTransform, EId::RadialGradient) => { if link.has_attribute(name) { return link; } } _ => break, } } node } fn resolve_rg_attr( node: svgtree::Node, name: AId, ) -> svgtree::Node { for link_id in node.href_iter() { let link = node.document().get(link_id); let tag_name = try_opt_or!(link.tag_name(), node); match (name, tag_name) { // Coordinates can be resolved only from // ref element with the same type. (AId::Cx, EId::RadialGradient) | (AId::Cy, EId::RadialGradient) | (AId::R, EId::RadialGradient) | (AId::Fx, EId::RadialGradient) | (AId::Fy, EId::RadialGradient) // Other attributes can be resolved // from any kind of gradient. | (AId::GradientUnits, EId::LinearGradient) | (AId::GradientUnits, EId::RadialGradient) | (AId::SpreadMethod, EId::LinearGradient) | (AId::SpreadMethod, EId::RadialGradient) | (AId::GradientTransform, EId::LinearGradient) | (AId::GradientTransform, EId::RadialGradient) => { if link.has_attribute(name) { return link; } } _ => break, } } node } fn resolve_pattern_attr( node: svgtree::Node, name: AId, ) -> svgtree::Node { for link_id in node.href_iter() { let link = node.document().get(link_id); let tag_name = try_opt_or!(link.tag_name(), node); if tag_name != EId::Pattern { break; } if link.has_attribute(name) { return link; } } node } fn resolve_filter_attr( node: svgtree::Node, aid: AId, ) -> svgtree::Node { for link_id in node.href_iter() { let link = node.document().get(link_id); let tag_name = try_opt_or!(link.tag_name(), node); if tag_name != EId::Filter { break; } if link.has_attribute(aid) { return link; } } node } /// Prepares the radial gradient focal radius. /// /// According to the SVG spec: /// /// If the point defined by `fx` and `fy` lies outside the circle defined by /// `cx`, `cy` and `r`, then the user agent shall set the focal point to the /// intersection of the line from (`cx`, `cy`) to (`fx`, `fy`) with the circle /// defined by `cx`, `cy` and `r`. fn prepare_focal(cx: f64, cy: f64, r: f64, fx: f64, fy: f64) -> (f64, f64) { let max_r = r - r * 0.001; let mut line = Line::new(cx, cy, fx, fy); if line.length() > max_r { line.set_length(max_r); } (line.x2, line.y2) } fn stops_to_color( stops: &[tree::Stop], ) -> Option { if stops.is_empty() { None } else { Some(ServerOrColor::Color { color: stops[0].color, opacity: stops[0].opacity, }) } } resvg-0.8.0/usvg/src/convert/shapes.rs000066400000000000000000000154661352576375700177710ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::rc::Rc; use crate::{svgtree, tree}; use super::{prelude::*, units}; pub fn convert( node: svgtree::Node, state: &State, ) -> Option { match node.tag_name()? { EId::Rect => convert_rect(node, state), EId::Circle => convert_circle(node, state), EId::Ellipse => convert_ellipse(node, state), EId::Line => convert_line(node, state), EId::Polyline => convert_polyline(node), EId::Polygon => convert_polygon(node), EId::Path => convert_path(node), _ => None, } } pub fn convert_path( node: svgtree::Node, ) -> Option { node.attribute::(AId::D) } pub fn convert_rect( node: svgtree::Node, state: &State, ) -> Option { // 'width' and 'height' attributes must be positive and non-zero. let width = node.convert_user_length(AId::Width, state, Length::zero()); let height = node.convert_user_length(AId::Height, state, Length::zero()); if !(width > 0.0) { warn!("Rect '{}' has an invalid 'width' value. Skipped.", node.element_id()); return None; } if !(height > 0.0) { warn!("Rect '{}' has an invalid 'height' value. Skipped.", node.element_id()); return None; } let x = node.convert_user_length(AId::X, state, Length::zero()); let y = node.convert_user_length(AId::Y, state, Length::zero()); // Resolve rx, ry. let mut rx_opt = node.attribute::(AId::Rx); let mut ry_opt = node.attribute::(AId::Ry); // Remove negative values first. if let Some(v) = rx_opt { if v.num.is_sign_negative() { rx_opt = None; } } if let Some(v) = ry_opt { if v.num.is_sign_negative() { ry_opt = None; } } // Resolve. let (rx, ry) = match (rx_opt, ry_opt) { (None, None) => (Length::zero(), Length::zero()), (Some(rx), None) => (rx, rx), (None, Some(ry)) => (ry, ry), (Some(rx), Some(ry)) => (rx, ry), }; let mut rx = units::convert_length(rx, node, AId::Rx, tree::Units::UserSpaceOnUse, state); let mut ry = units::convert_length(ry, node, AId::Ry, tree::Units::UserSpaceOnUse, state); // Clamp rx/ry to the half of the width/height. // // Should be done only after resolving. if rx > width / 2.0 { rx = width / 2.0; } if ry > height / 2.0 { ry = height / 2.0; } // Conversion according to https://www.w3.org/TR/SVG11/shapes.html#RectElement let path = if rx.fuzzy_eq(&0.0) { tree::PathData::from_rect(Rect::new(x, y, width, height)?) } else { let mut p = tree::PathData::with_capacity(16); p.push_move_to(x + rx, y); p.push_line_to(x + width - rx, y); p.push_arc_to(rx, ry, 0.0, false, true, x + width, y + ry); p.push_line_to(x + width, y + height - ry); p.push_arc_to(rx, ry, 0.0, false, true, x + width - rx, y + height); p.push_line_to(x + rx, y + height); p.push_arc_to(rx, ry, 0.0, false, true, x, y + height - ry); p.push_line_to(x, y + ry); p.push_arc_to(rx, ry, 0.0, false, true, x + rx, y); p.push_close_path(); p }; Some(Rc::new(path)) } pub fn convert_line( node: svgtree::Node, state: &State, ) -> Option { let x1 = node.convert_user_length(AId::X1, state, Length::zero()); let y1 = node.convert_user_length(AId::Y1, state, Length::zero()); let x2 = node.convert_user_length(AId::X2, state, Length::zero()); let y2 = node.convert_user_length(AId::Y2, state, Length::zero()); let mut path = tree::PathData::new(); path.push_move_to(x1, y1); path.push_line_to(x2, y2); Some(Rc::new(path)) } pub fn convert_polyline( node: svgtree::Node, ) -> Option { points_to_path(node, "Polyline").map(Rc::new) } pub fn convert_polygon( node: svgtree::Node, ) -> Option { if let Some(mut path) = points_to_path(node, "Polygon") { path.push(tree::PathSegment::ClosePath); Some(Rc::new(path)) } else { None } } fn points_to_path( node: svgtree::Node, eid: &str, ) -> Option { use svgtypes::PointsParser; let mut path = tree::PathData::new(); match node.attribute::<&str>(AId::Points) { Some(text) => { for (x, y) in PointsParser::from(text) { if path.is_empty() { path.push_move_to(x, y); } else { path.push_line_to(x, y); } } } _ => { warn!("{} '{}' has an invalid 'points' value. Skipped.", eid, node.element_id()); return None; } }; // 'polyline' and 'polygon' elements must contain at least 2 points. if path.len() < 2 { warn!("{} '{}' has less than 2 points. Skipped.", eid, node.element_id()); return None; } Some(path) } pub fn convert_circle( node: svgtree::Node, state: &State, ) -> Option { let cx = node.convert_user_length(AId::Cx, state, Length::zero()); let cy = node.convert_user_length(AId::Cy, state, Length::zero()); let r = node.convert_user_length(AId::R, state, Length::zero()); if !(r > 0.0) { warn!("Circle '{}' has an invalid 'r' value. Skipped.", node.element_id()); return None; } Some(Rc::new(ellipse_to_path(cx, cy, r, r))) } pub fn convert_ellipse( node: svgtree::Node, state: &State, ) -> Option { let cx = node.convert_user_length(AId::Cx, state, Length::zero()); let cy = node.convert_user_length(AId::Cy, state, Length::zero()); let rx = node.convert_user_length(AId::Rx, state, Length::zero()); let ry = node.convert_user_length(AId::Ry, state, Length::zero()); if !(rx > 0.0) { warn!("Ellipse '{}' has an invalid 'rx' value. Skipped.", node.element_id()); return None; } if !(ry > 0.0) { warn!("Ellipse '{}' has an invalid 'ry' value. Skipped.", node.element_id()); return None; } Some(Rc::new(ellipse_to_path(cx, cy, rx, ry))) } fn ellipse_to_path( cx: f64, cy: f64, rx: f64, ry: f64, ) -> tree::PathData { let mut p = tree::PathData::with_capacity(6); p.push_move_to(cx + rx, cy); p.push_arc_to(rx, ry, 0.0, false, true, cx, cy + ry); p.push_arc_to(rx, ry, 0.0, false, true, cx - rx, cy); p.push_arc_to(rx, ry, 0.0, false, true, cx, cy - ry); p.push_arc_to(rx, ry, 0.0, false, true, cx + rx, cy); p.push_close_path(); p } resvg-0.8.0/usvg/src/convert/style.rs000066400000000000000000000152441352576375700176400ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::{svgtree, tree}; use super::{prelude::*, paint_server, switch}; pub fn resolve_fill( node: svgtree::Node, has_bbox: bool, state: &State, tree: &mut tree::Tree, ) -> Option { if state.parent_clip_path.is_some() { // A `clipPath` child can be filled only with a black color. return Some(tree::Fill { paint: tree::Paint::Color(tree::Color::black()), opacity: tree::Opacity::default(), rule: node.find_attribute(AId::ClipRule).unwrap_or_default(), }); } let mut sub_opacity = tree::Opacity::default(); let paint = if let Some(n) = node.find_node_with_attribute(AId::Fill) { convert_paint(n, AId::Fill, has_bbox, state, &mut sub_opacity, tree)? } else { tree::Paint::Color(tree::Color::black()) }; let opacity = sub_opacity * node.find_attribute(AId::FillOpacity).unwrap_or_default(); let rule = node.find_attribute(AId::FillRule).unwrap_or_default(); Some(tree::Fill { paint, opacity, rule, }) } pub fn resolve_stroke( node: svgtree::Node, has_bbox: bool, state: &State, tree: &mut tree::Tree, ) -> Option { if state.parent_clip_path.is_some() { // A `clipPath` child cannot be stroked. return None; } let mut sub_opacity = tree::Opacity::default(); let paint = if let Some(n) = node.find_node_with_attribute(AId::Stroke) { convert_paint(n, AId::Stroke, has_bbox, state, &mut sub_opacity, tree)? } else { return None; }; let dashoffset = node.resolve_length(AId::StrokeDashoffset, state, 0.0) as f32; let miterlimit = node.find_attribute(AId::StrokeMiterlimit).unwrap_or(4.0); let width = node.resolve_length(AId::StrokeWidth, state, 1.0); let opacity = sub_opacity * node.find_attribute(AId::StrokeOpacity).unwrap_or_default(); if !(width > 0.0) { return None; } let width = tree::StrokeWidth::new(width); // Must be bigger than 1. let miterlimit = if miterlimit < 1.0 { 1.0 } else { miterlimit }; let miterlimit = tree::StrokeMiterlimit::new(miterlimit); let linecap = node.find_attribute(AId::StrokeLinecap).unwrap_or_default(); let linejoin = node.find_attribute(AId::StrokeLinejoin).unwrap_or_default(); let dasharray = conv_dasharray(node, state); let stroke = tree::Stroke { paint, dasharray, dashoffset, miterlimit, opacity, width, linecap, linejoin, }; Some(stroke) } fn convert_paint( node: svgtree::Node, aid: AId, has_bbox: bool, state: &State, opacity: &mut tree::Opacity, tree: &mut tree::Tree, ) -> Option { match node.attribute::<&svgtree::AttributeValue>(aid)? { svgtree::AttributeValue::CurrentColor => { let c = node.find_attribute(AId::Color).unwrap_or_else(tree::Color::black); Some(tree::Paint::Color(c)) } svgtree::AttributeValue::Color(c) => { Some(tree::Paint::Color(*c)) } svgtree::AttributeValue::Paint(func_iri, fallback) => { if let Some(link) = node.document().element_by_id(func_iri) { let tag_name = link.tag_name().unwrap(); if tag_name.is_paint_server() { match paint_server::convert(link, state, tree) { Some(paint_server::ServerOrColor::Server { id, units }) => { // We can use a paint server node with ObjectBoundingBox units // for painting only when the shape itself has a bbox. // // See SVG spec 7.11 for details. if !has_bbox && units == tree::Units::ObjectBoundingBox { from_fallback(node, *fallback) } else { Some(tree::Paint::Link(id)) } } Some(paint_server::ServerOrColor::Color { color, opacity: so }) => { *opacity = so; Some(tree::Paint::Color(color)) } None => { from_fallback(node, *fallback) } } } else { warn!("'{}' cannot be used to {} a shape.", tag_name, aid); None } } else { from_fallback(node, *fallback) } } _ => { None } } } fn from_fallback( node: svgtree::Node, fallback: Option, ) -> Option { match fallback? { svgtypes::PaintFallback::None => { None } svgtypes::PaintFallback::CurrentColor => { let c = node.find_attribute(AId::Color).unwrap_or_else(tree::Color::black); Some(tree::Paint::Color(c)) } svgtypes::PaintFallback::Color(c) => { Some(tree::Paint::Color(c)) } } } // Prepare the 'stroke-dasharray' according to: // https://www.w3.org/TR/SVG11/painting.html#StrokeDasharrayProperty fn conv_dasharray( node: svgtree::Node, state: &State, ) -> Option> { let node = node.find_node_with_attribute(AId::StrokeDasharray)?; let list = super::units::convert_list(node, AId::StrokeDasharray, state)?; // `A negative value is an error` if list.iter().any(|n| n.is_sign_negative()) { return None; } // `If the sum of the values is zero, then the stroke is rendered // as if a value of none were specified.` { // no Iter::sum(), because of f64 let mut sum = 0.0f64; for n in list.iter() { sum += *n; } if sum.fuzzy_eq(&0.0) { return None; } } // `If an odd number of values is provided, then the list of values // is repeated to yield an even number of values.` if list.len() % 2 != 0 { let mut tmp_list = list.clone(); tmp_list.extend_from_slice(&list); return Some(tmp_list); } Some(list) } pub fn is_visible_element( node: svgtree::Node, opt: &Options, ) -> bool { let display = node.attribute(AId::Display) != Some("none"); display && node.has_valid_transform(AId::Transform) && switch::is_condition_passed(node, opt) } resvg-0.8.0/usvg/src/convert/switch.rs000066400000000000000000000121001352576375700177650ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::{svgtree, tree}; use super::prelude::*; // Full list can be found here: https://www.w3.org/TR/SVG11/feature.html static FEATURES: &[&str] = &[ "http://www.w3.org/TR/SVG11/feature#SVGDOM-static", "http://www.w3.org/TR/SVG11/feature#SVG-static", "http://www.w3.org/TR/SVG11/feature#CoreAttribute", // no xml:base and xml:lang "http://www.w3.org/TR/SVG11/feature#Structure", "http://www.w3.org/TR/SVG11/feature#BasicStructure", // "http://www.w3.org/TR/SVG11/feature#ContainerAttribute", // `enable-background`, not yet "http://www.w3.org/TR/SVG11/feature#ConditionalProcessing", "http://www.w3.org/TR/SVG11/feature#Image", "http://www.w3.org/TR/SVG11/feature#Style", // "http://www.w3.org/TR/SVG11/feature#ViewportAttribute", // `clip` and `overflow`, not yet "http://www.w3.org/TR/SVG11/feature#Shape", "http://www.w3.org/TR/SVG11/feature#Text", "http://www.w3.org/TR/SVG11/feature#BasicText", "http://www.w3.org/TR/SVG11/feature#PaintAttribute", // no color-interpolation and color-rendering "http://www.w3.org/TR/SVG11/feature#BasicPaintAttribute", // no color-interpolation "http://www.w3.org/TR/SVG11/feature#OpacityAttribute", // "http://www.w3.org/TR/SVG11/feature#GraphicsAttribute", "http://www.w3.org/TR/SVG11/feature#BasicGraphicsAttribute", "http://www.w3.org/TR/SVG11/feature#Marker", // "http://www.w3.org/TR/SVG11/feature#ColorProfile", // not yet "http://www.w3.org/TR/SVG11/feature#Gradient", "http://www.w3.org/TR/SVG11/feature#Pattern", "http://www.w3.org/TR/SVG11/feature#Clip", "http://www.w3.org/TR/SVG11/feature#BasicClip", "http://www.w3.org/TR/SVG11/feature#Mask", // "http://www.w3.org/TR/SVG11/feature#Filter", // not yet "http://www.w3.org/TR/SVG11/feature#BasicFilter", "http://www.w3.org/TR/SVG11/feature#XlinkAttribute", // only xlink:href // "http://www.w3.org/TR/SVG11/feature#Font", // "http://www.w3.org/TR/SVG11/feature#BasicFont", ]; pub fn convert( node: svgtree::Node, state: &State, parent: &mut tree::Node, tree: &mut tree::Tree, ) { let child = node.children().find(|n| is_condition_passed(*n, state.opt)); let child = try_opt!(child); match super::convert_group(node, state, false, parent, tree) { super::GroupKind::Create(ref mut g) => { super::convert_element(child, state, g, tree); } super::GroupKind::Skip => { super::convert_element(child, state, parent, tree); } super::GroupKind::Ignore => {} } } pub fn is_condition_passed( node: svgtree::Node, opt: &Options, ) -> bool { if !node.is_element() { return false; } if node.has_attribute(AId::RequiredExtensions) { return false; } // 'The value is a list of feature strings, with the individual values separated by white space. // Determines whether all of the named features are supported by the user agent. // Only feature strings defined in the Feature String appendix are allowed. // If all of the given features are supported, then the attribute evaluates to true; // otherwise, the current element and its children are skipped and thus will not be rendered.' if let Some(features) = node.attribute::<&str>(AId::RequiredFeatures) { for feature in features.split(' ') { if !FEATURES.contains(&feature) { return false; } } } if !is_valid_sys_lang(node, opt) { return false; } true } /// SVG spec 5.8.5 fn is_valid_sys_lang( node: svgtree::Node, opt: &Options, ) -> bool { // 'The attribute value is a comma-separated list of language names // as defined in BCP 47.' // // But we support only simple cases like `en` or `en-US`. // No one really uses this, especially with complex BCP 47 values. if let Some(langs) = node.attribute::<&str>(AId::SystemLanguage) { let mut has_match = false; for lang in langs.split(',') { let lang = lang.trim(); // 'Evaluates to `true` if one of the languages indicated by user preferences exactly // equals one of the languages given in the value of this parameter.' if opt.languages.iter().any(|v| v == lang) { has_match = true; break; } // 'If one of the languages indicated by user preferences exactly equals a prefix // of one of the languages given in the value of this parameter such that // the first tag character following the prefix is `-`.' if let Some(idx) = lang.bytes().position(|c| c == b'-') { let lang_prefix = &lang[..idx]; if opt.languages.iter().any(|v| v == lang_prefix) { has_match = true; break; } } } return has_match; } true } resvg-0.8.0/usvg/src/convert/text/000077500000000000000000000000001352576375700171105ustar00rootroot00000000000000resvg-0.8.0/usvg/src/convert/text/convert.rs000066400000000000000000000556221352576375700211500ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::cmp; use std::rc::Rc; use crate::{fontdb, svgtree, tree}; use crate::convert::{prelude::*, style, units}; use super::TextNode; /// A read-only text index in bytes. /// /// Guarantee to be on a char boundary and in text bounds. #[derive(Clone, Copy, PartialEq)] pub struct ByteIndex(usize); impl ByteIndex { pub fn new(i: usize) -> Self { ByteIndex(i) } pub fn value(&self) -> usize { self.0 } /// Converts byte position into a code point position. pub fn code_point_at(&self, text: &str) -> usize { text.char_indices().take_while(|(i, _)| *i != self.0).count() } /// Converts byte position into a character. pub fn char_from(&self, text: &str) -> char { text[self.0..].chars().next().unwrap() } } #[derive(Clone, Copy, PartialEq)] pub enum TextAnchor { Start, Middle, End, } impl_enum_default!(TextAnchor, Start); impl_enum_from_str!(TextAnchor, "start" => TextAnchor::Start, "middle" => TextAnchor::Middle, "end" => TextAnchor::End ); pub struct TextPath { /// A text offset in SVG coordinates. /// /// Percentage values already resolved. pub start_offset: f64, pub path: tree::SharedPathData, } #[derive(Clone)] pub enum TextFlow { Horizontal, Path(Rc), } /// A text chunk. /// /// Text alignment and BIDI reordering can be done only inside a text chunk. pub struct TextChunk { pub x: Option, pub y: Option, pub anchor: TextAnchor, pub spans: Vec, pub text_flow: TextFlow, pub text: String, } impl TextChunk { pub fn span_at(&self, byte_offset: ByteIndex) -> Option<&TextSpan> { for span in &self.spans { if span.contains(byte_offset) { return Some(span); } } None } } /// Spans do not overlap. #[derive(Clone)] pub struct TextSpan { pub start: usize, pub end: usize, pub fill: Option, pub stroke: Option, pub font: fontdb::Font, pub font_size: f64, pub decoration: TextDecoration, pub baseline_shift: f64, pub visibility: tree::Visibility, pub letter_spacing: f64, pub word_spacing: f64, } impl TextSpan { pub fn contains(&self, byte_offset: ByteIndex) -> bool { byte_offset.value() >= self.start && byte_offset.value() < self.end } } #[derive(Clone, Copy, PartialEq)] pub enum WritingMode { LeftToRight, TopToBottom, } struct IterState { chars_count: usize, chunk_bytes_count: usize, split_chunk: bool, text_flow: TextFlow, chunks: Vec, } pub fn collect_text_chunks( text_node: TextNode, pos_list: &[CharacterPosition], state: &State, tree: &mut tree::Tree, ) -> Vec { let mut iter_state = IterState { chars_count: 0, chunk_bytes_count: 0, split_chunk: false, text_flow: TextFlow::Horizontal, chunks: Vec::new(), }; collect_text_chunks_impl(text_node, *text_node, pos_list, state, tree, &mut iter_state); iter_state.chunks } fn collect_text_chunks_impl( text_node: TextNode, parent: svgtree::Node, pos_list: &[CharacterPosition], state: &State, tree: &mut tree::Tree, iter_state: &mut IterState, ) { for child in parent.children() { if child.is_element() { if child.has_tag_name(EId::TextPath) { if !parent.has_tag_name(EId::Text) { // `textPath` can be set only as a direct `text` element child. iter_state.chars_count += count_chars(child); continue; } match resolve_text_flow(child.clone(), state) { Some(v) => { iter_state.text_flow = v; } None => { // Skip an invalid text path and all it's children. // We have to update the chars count, // because `pos_list` was calculated including this text path. iter_state.chars_count += count_chars(child); continue; } } iter_state.split_chunk = true; } collect_text_chunks_impl(text_node, child, pos_list, state, tree, iter_state); iter_state.text_flow = TextFlow::Horizontal; // Next char after `textPath` should be split too. if child.has_tag_name(EId::TextPath) { iter_state.split_chunk = true; } continue; } if !style::is_visible_element(parent, state.opt) { iter_state.chars_count += child.text().chars().count(); continue; } let anchor = parent.find_attribute(AId::TextAnchor).unwrap_or_default(); // TODO: what to do when <= 0? UB? let font_size = units::resolve_font_size(parent, state); if !(font_size > 0.0) { // Skip this span. iter_state.chars_count += child.text().chars().count(); continue; } let font = match resolve_font(parent, state) { Some(v) => v, None => { // Skip this span. iter_state.chars_count += child.text().chars().count(); continue; } }; let letter_spacing = parent.resolve_length(AId::LetterSpacing, state, 0.0); let word_spacing = parent.resolve_length(AId::WordSpacing, state, 0.0); let span = TextSpan { start: 0, end: 0, fill: style::resolve_fill(parent, true, state, tree), stroke: style::resolve_stroke(parent, true, state, tree), font, font_size, decoration: resolve_decoration(text_node, parent, state, tree), visibility: parent.find_attribute(AId::Visibility).unwrap_or_default(), baseline_shift: resolve_baseline_shift(parent, state), letter_spacing, word_spacing, }; let mut is_new_span = true; for c in child.text().chars() { let char_len = c.len_utf8(); // Create a new chunk if: // - this is the first span (yes, position can be None) // - text character has an absolute coordinate assigned to it (via x/y attribute) // - `c` is the first char of the `textPath` // - `c` is the first char after `textPath` let is_new_chunk = pos_list[iter_state.chars_count].x.is_some() || pos_list[iter_state.chars_count].y.is_some() || iter_state.split_chunk || iter_state.chunks.is_empty(); iter_state.split_chunk = false; if is_new_chunk { iter_state.chunk_bytes_count = 0; let mut span2 = span.clone(); span2.start = 0; span2.end = char_len; iter_state.chunks.push(TextChunk { x: pos_list[iter_state.chars_count].x, y: pos_list[iter_state.chars_count].y, anchor, spans: vec![span2], text_flow: iter_state.text_flow.clone(), text: c.to_string(), }); } else if is_new_span { // Add this span to the last text chunk. let mut span2 = span.clone(); span2.start = iter_state.chunk_bytes_count; span2.end = iter_state.chunk_bytes_count + char_len; if let Some(chunk) = iter_state.chunks.last_mut() { chunk.text.push(c); chunk.spans.push(span2); } } else { // Extend the last span. if let Some(chunk) = iter_state.chunks.last_mut() { chunk.text.push(c); if let Some(span) = chunk.spans.last_mut() { debug_assert_ne!(span.end, 0); span.end += char_len; } } } is_new_span = false; iter_state.chars_count += 1; iter_state.chunk_bytes_count += char_len; } } } fn resolve_text_flow( node: svgtree::Node, state: &State, ) -> Option { let path_node = node.attribute::(AId::Href)?; if !path_node.has_tag_name(EId::Path) { return None; } let path = path_node.attribute::(AId::D)?; let start_offset: Length = node.attribute(AId::StartOffset).unwrap_or_default(); let start_offset = if start_offset.unit == Unit::Percent { // 'If a percentage is given, then the `startOffset` represents // a percentage distance along the entire path.' let path_len = path.length(); path_len * (start_offset.num / 100.0) } else { node.resolve_length(AId::StartOffset, state, 0.0) }; Some(TextFlow::Path(Rc::new(TextPath { start_offset, path, }))) } pub fn resolve_rendering_mode( text_node: TextNode, state: &State, ) -> tree::ShapeRendering { let mode: tree::TextRendering = text_node .find_attribute(AId::TextRendering) .unwrap_or(state.opt.text_rendering); match mode { tree::TextRendering::OptimizeSpeed => tree::ShapeRendering::CrispEdges, tree::TextRendering::OptimizeLegibility => tree::ShapeRendering::GeometricPrecision, tree::TextRendering::GeometricPrecision => tree::ShapeRendering::GeometricPrecision, } } fn resolve_font( node: svgtree::Node, state: &State, ) -> Option { let style = node.find_attribute(AId::FontStyle).unwrap_or_default(); let stretch = conv_font_stretch(node); let weight = resolve_font_weight(node); let properties = fontdb::Properties { style, weight, stretch }; let font_family = if let Some(n) = node.find_node_with_attribute(AId::FontFamily) { n.attribute::<&str>(AId::FontFamily).unwrap_or(&state.opt.font_family).to_owned() } else { state.opt.font_family.to_owned() }; let mut name_list = Vec::new(); for family in font_family.split(',') { // TODO: to a proper parser let family = family.replace('\'', ""); let family = family.trim(); name_list.push(family.to_string()); } // Use the default font as fallback. name_list.push(state.opt.font_family.clone()); let name_list: Vec<_> = name_list.iter().map(|s| s.as_str()).collect(); let mut db = state.db.borrow_mut(); let id = match db.select_best_match(&name_list, properties) { Some(id) => id, None => { warn!("No match for '{}' font-family.", font_family); return None; } }; db.load_font(id) } fn conv_font_stretch( node: svgtree::Node, ) -> fontdb::Stretch { if let Some(n) = node.find_node_with_attribute(AId::FontStretch) { match n.attribute(AId::FontStretch).unwrap_or("") { "narrower" | "condensed" => fontdb::Stretch::Condensed, "ultra-condensed" => fontdb::Stretch::UltraCondensed, "extra-condensed" => fontdb::Stretch::ExtraCondensed, "semi-condensed" => fontdb::Stretch::SemiCondensed, "semi-expanded" => fontdb::Stretch::SemiExpanded, "wider" | "expanded" => fontdb::Stretch::Expanded, "extra-expanded" => fontdb::Stretch::ExtraExpanded, "ultra-expanded" => fontdb::Stretch::UltraExpanded, _ => fontdb::Stretch::Normal, } } else { fontdb::Stretch::Normal } } #[derive(Clone, Copy)] pub struct CharacterPosition { pub x: Option, pub y: Option, pub dx: Option, pub dy: Option, } /// Resolves text's character positions. /// /// This includes: x, y, dx, dy. /// /// # The character /// /// The first problem with this task is that the *character* itself /// is basically undefined in the SVG spec. Sometimes it's an *XML character*, /// sometimes a *glyph*, and sometimes just a *character*. /// /// There is an ongoing [discussion](https://github.com/w3c/svgwg/issues/537) /// on the SVG working group that addresses this by stating that a character /// is a Unicode code point. But it's not final. /// /// Also, according to the SVG 2 spec, *character* is *a Unicode code point*. /// /// Anyway, we treat a character as a Unicode code point. /// /// # Algorithm /// /// To resolve positions, we have to iterate over descendant nodes and /// if the current node is a `tspan` and has x/y/dx/dy attribute, /// than the positions from this attribute should be assigned to the characters /// of this `tspan` and it's descendants. /// /// Positions list can have more values than characters in the `tspan`, /// so we have to clamp it, because values should not overlap, e.g.: /// /// (we ignore whitespaces for example purposes, /// so the `text` content is `Text` and not `T ex t`) /// /// ```text /// /// a /// /// bc /// /// d /// /// ``` /// /// In this example, the `d` position should not be set to `30`. /// And the result should be: `[None, 10, 20, None]` /// /// Another example: /// /// ```text /// /// /// a /// /// bc /// /// /// d /// /// ``` /// /// The result should be: `[100, 50, 120, None]` pub fn resolve_positions_list( text_node: TextNode, state: &State, ) -> Vec { // Allocate a list that has all characters positions set to `None`. let total_chars = count_chars(*text_node); let mut list = vec![CharacterPosition { x: None, y: None, dx: None, dy: None, }; total_chars]; let mut offset = 0; for child in text_node.descendants() { if child.is_element() { let child_chars = count_chars(child); macro_rules! push_list { ($aid:expr, $field:ident) => { if let Some(num_list) = units::convert_list(child, $aid, state) { // Note that we are using not the total count, // but the amount of characters in the current `tspan` (with children). let len = cmp::min(num_list.len(), child_chars); for i in 0..len { list[offset + i].$field = Some(num_list[i]); } } }; } push_list!(AId::X, x); push_list!(AId::Y, y); push_list!(AId::Dx, dx); push_list!(AId::Dy, dy); } else if child.is_text() { // Advance the offset. offset += child.text().chars().count(); } } list } /// Resolves characters rotation. /// /// The algorithm is well explained /// [in the SVG spec](https://www.w3.org/TR/SVG11/text.html#TSpanElement) (scroll down a bit). /// /// ![](https://www.w3.org/TR/SVG11/images/text/tspan05-diagram.png) /// /// Note: this algorithm differs from the position resolving one. pub fn resolve_rotate_list( text_node: TextNode, ) -> Vec { // Allocate a list that has all characters angles set to `0.0`. let mut list = vec![0.0; count_chars(*text_node)]; let mut last = 0.0; let mut offset = 0; for child in text_node.descendants() { if child.is_element() { if let Some(rotate) = child.attribute::<&svgtypes::NumberList>(AId::Rotate) { for i in 0..count_chars(child) { if let Some(a) = rotate.get(i).cloned() { list[offset + i] = a; last = a; } else { // If the rotate list doesn't specify the rotation for // this character - use the last one. list[offset + i] = last; } } } } else if child.is_text() { // Advance the offset. offset += child.text().chars().count(); } } list } #[derive(Clone)] pub struct TextDecorationStyle { pub fill: Option, pub stroke: Option, } #[derive(Clone)] pub struct TextDecoration { pub underline: Option, pub overline: Option, pub line_through: Option, } /// Resolves node's `text-decoration` property. /// /// `text` and `tspan` can point to the same node. fn resolve_decoration( text_node: TextNode, tspan: svgtree::Node, state: &State, tree: &mut tree::Tree, ) -> TextDecoration { // TODO: explain the algorithm let text_dec = conv_text_decoration(text_node); let tspan_dec = conv_text_decoration2(tspan); let mut gen_style = |in_tspan: bool, in_text: bool| { let n = if in_tspan { tspan.clone() } else if in_text { (*text_node).clone() } else { return None; }; Some(TextDecorationStyle { fill: style::resolve_fill(n, true, state, tree), stroke: style::resolve_stroke(n, true, state, tree), }) }; TextDecoration { underline: gen_style(tspan_dec.has_underline, text_dec.has_underline), overline: gen_style(tspan_dec.has_overline, text_dec.has_overline), line_through: gen_style(tspan_dec.has_line_through, text_dec.has_line_through), } } struct TextDecorationTypes { has_underline: bool, has_overline: bool, has_line_through: bool, } /// Resolves the `text` node's `text-decoration` property. fn conv_text_decoration( text_node: TextNode, ) -> TextDecorationTypes { fn find_decoration(node: svgtree::Node, value: &str) -> bool { node.ancestors().any(|n| n.attribute(AId::TextDecoration) == Some(value)) } TextDecorationTypes { has_underline: find_decoration(*text_node, "underline"), has_overline: find_decoration(*text_node, "overline"), has_line_through: find_decoration(*text_node, "line-through"), } } /// Resolves the default `text-decoration` property. fn conv_text_decoration2( tspan: svgtree::Node, ) -> TextDecorationTypes { let s = tspan.attribute(AId::TextDecoration); TextDecorationTypes { has_underline: s == Some("underline"), has_overline: s == Some("overline"), has_line_through: s == Some("line-through"), } } fn resolve_baseline_shift( node: svgtree::Node, state: &State, ) -> f64 { let mut shift = 0.0; let nodes: Vec<_> = node.ancestors().take_while(|n| !n.has_tag_name(EId::Text)).collect(); for n in nodes.iter().rev().cloned() { if let Some(len) = n.attribute::(AId::BaselineShift) { if len.unit == Unit::Percent { shift += units::resolve_font_size(n, state) * (len.num / 100.0); } else { shift += units::convert_length( len, n, AId::BaselineShift, tree::Units::ObjectBoundingBox, state, ); } } else if let Some(s) = n.attribute(AId::BaselineShift) { match s { "baseline" => {} "sub" => { let font_size = units::resolve_font_size(n, state); if let Some(font) = resolve_font(n, state) { shift -= font.subscript_offset(font_size); } } "super" => { let font_size = units::resolve_font_size(n, state); if let Some(font) = resolve_font(n, state) { shift += font.superscript_offset(font_size); } } _ => {} } } } shift } fn resolve_font_weight( node: svgtree::Node, ) -> fontdb::Weight { fn bound(min: usize, val: usize, max: usize) -> usize { cmp::max(min, cmp::min(max, val)) } let nodes: Vec<_> = node.ancestors().collect(); let mut weight = 400; for n in nodes.iter().rev().skip(1) { // skip Root weight = match n.attribute(AId::FontWeight).unwrap_or("") { "normal" => 400, "bold" => 700, "100" => 100, "200" => 200, "300" => 300, "400" => 400, "500" => 500, "600" => 600, "700" => 700, "800" => 800, "900" => 900, "bolder" => { // By the CSS2 spec the default value should be 400 // so `bolder` will result in 500. // But Chrome and Inkscape will give us 700. // Have no idea is it a bug or something, but // we will follow such behavior for now. let step = if weight == 400 { 300 } else { 100 }; bound(100, weight + step, 900) } "lighter" => { // By the CSS2 spec the default value should be 400 // so `lighter` will result in 300. // But Chrome and Inkscape will give us 200. // Have no idea is it a bug or something, but // we will follow such behavior for now. let step = if weight == 400 { 200 } else { 100 }; bound(100, weight - step, 900) } _ => weight, }; } let weight = fontdb::Weight::from(weight as u16); match weight { fontdb::Weight::Other(_) => fontdb::Weight::Normal, _ => weight, } } fn count_chars( node: svgtree::Node, ) -> usize { node.descendants() .filter(|n| n.is_text()) .fold(0, |w, n| w + n.text().chars().count()) } /// Converts the writing mode. /// /// According to the [SVG 2.0] spec, there are only two writing modes: /// horizontal left-to-right and vertical right-to-left. /// E.g: /// /// - `lr`, `lr-tb`, `rl`, `rl-tb` => `horizontal-tb` /// - `tb`, `tb-rl` => `vertical-rl` /// /// Also, looks like no one really supports the `rl` and `rl-tb`, except `Batik`. /// And I'm not sure if it's behaviour is correct. /// /// So we will ignore it as well, mainly because I have no idea how exactly /// it should affect the rendering. /// /// [SVG 2.0]: https://www.w3.org/TR/SVG2/text.html#WritingModeProperty pub fn convert_writing_mode( text_node: TextNode, ) -> WritingMode { if let Some(n) = text_node.find_node_with_attribute(AId::WritingMode) { match n.attribute(AId::WritingMode).unwrap_or("lr-tb") { "tb" | "tb-rl" => WritingMode::TopToBottom, _ => WritingMode::LeftToRight, } } else { WritingMode::LeftToRight } } resvg-0.8.0/usvg/src/convert/text/mod.rs000066400000000000000000000356271352576375700202520ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::rc::Rc; use crate::{svgtree, tree, tree::prelude::*}; use super::prelude::*; mod convert; use self::convert::*; mod shaper; use self::shaper::OutlinedCluster; mod private { use super::*; /// A type-safe container for a `text` node. /// /// This way we can be sure that we are passing the `text` node and not just a random node. #[derive(Clone, Copy)] pub struct TextNode<'a>(svgtree::Node<'a>); impl<'a> TextNode<'a> { pub fn new(node: svgtree::Node<'a>) -> Self { debug_assert!(node.has_tag_name(EId::Text)); TextNode(node) } } impl<'a> std::ops::Deref for TextNode<'a> { type Target = svgtree::Node<'a>; fn deref(&self) -> &Self::Target { &self.0 } } } use self::private::*; /// A text decoration span. /// /// Basically a horizontal line, that will be used for underline, overline and line-through. /// It doesn't have a height, since it depends on the font metrics. #[derive(Clone, Copy)] struct DecorationSpan { width: f64, transform: tree::Transform, } pub fn convert( node: svgtree::Node, state: &State, parent: &mut tree::Node, tree: &mut tree::Tree, ) { state.db.borrow_mut().populate(); let text_node = TextNode::new(node.clone()); let mut new_paths = text_to_paths(text_node, state, parent, tree); let mut bbox = Rect::new_bbox(); for path in &new_paths { if let Some(r) = path.data.bbox() { bbox = bbox.expand(r); } } if new_paths.len() == 1 { // Copy `text` id to the first path. new_paths[0].id = node.element_id().to_string(); } let mut parent = if state.opt.keep_named_groups && new_paths.len() > 1 { // Create a group will all paths that was created during text-to-path conversion. parent.append_kind(tree::NodeKind::Group(tree::Group { id: node.element_id().to_string(), .. tree::Group::default() })) } else { parent.clone() }; let rendering_mode = resolve_rendering_mode(text_node, state); for mut path in new_paths { fix_obj_bounding_box(&mut path, bbox, tree); path.rendering_mode = rendering_mode; parent.append_kind(tree::NodeKind::Path(path)); } } fn text_to_paths( text_node: TextNode, state: &State, parent: &mut tree::Node, tree: &mut tree::Tree, ) -> Vec { let pos_list = resolve_positions_list(text_node, state); let rotate_list = resolve_rotate_list(text_node); let writing_mode = convert_writing_mode(text_node); let mut text_ts = tree::Transform::default(); let mut chunks = collect_text_chunks(text_node, &pos_list, state, tree); let mut char_offset = 0; let mut last_x = 0.0; let mut last_y = 0.0; let mut new_paths = Vec::new(); for chunk in &mut chunks { let (x, y) = match chunk.text_flow { TextFlow::Horizontal => (chunk.x.unwrap_or(last_x), chunk.y.unwrap_or(last_y)), TextFlow::Path(_) => (0.0, 0.0), }; let mut clusters = shaper::outline_chunk(&chunk, state); if clusters.is_empty() { char_offset += chunk.text.chars().count(); continue; } shaper::apply_writing_mode(writing_mode, &mut clusters); shaper::apply_letter_spacing(&chunk, &mut clusters); shaper::apply_word_spacing(&chunk, &mut clusters); let curr_pos = shaper::resolve_clusters_positions( chunk, char_offset, &pos_list, &rotate_list, writing_mode, &mut clusters ); if writing_mode == WritingMode::TopToBottom { if let TextFlow::Horizontal = chunk.text_flow { text_ts.rotate_at(90.0, x, y); } } for span in &mut chunk.spans { let decoration_spans = collect_decoration_spans(span, &clusters); let mut span_ts = text_ts.clone(); span_ts.translate(x, y); if let TextFlow::Horizontal = chunk.text_flow { // In case of a horizontal flow, shift transform and not clusters, // because clusters can be rotated and an additional shift will lead // to invalid results. span_ts.translate(0.0, -span.baseline_shift); } if let Some(decoration) = span.decoration.underline.take() { // TODO: No idea what offset should be used for top-to-bottom layout. // There is // https://www.w3.org/TR/css-text-decor-3/#text-underline-position-property // but it doesn't go into details. let offset = match writing_mode { WritingMode::LeftToRight => -span.font.underline_position(span.font_size), WritingMode::TopToBottom => span.font.height(span.font_size) / 2.0, }; new_paths.push(convert_decoration( offset, &span, decoration, &decoration_spans, span_ts, )); } if let Some(decoration) = span.decoration.overline.take() { let offset = match writing_mode { WritingMode::LeftToRight => -span.font.ascent(span.font_size), WritingMode::TopToBottom => -span.font.height(span.font_size) / 2.0, }; new_paths.push(convert_decoration( offset, &span, decoration, &decoration_spans, span_ts, )); } if let Some(path) = convert_span(span, &mut clusters, &span_ts, parent, false) { new_paths.push(path); } if let Some(decoration) = span.decoration.line_through.take() { let offset = match writing_mode { WritingMode::LeftToRight => -span.font.line_through_position(span.font_size), WritingMode::TopToBottom => 0.0, }; new_paths.push(convert_decoration( offset, &span, decoration, &decoration_spans, span_ts, )); } } char_offset += chunk.text.chars().count(); last_x = x + curr_pos.0; last_y = y + curr_pos.1; } new_paths } fn convert_span( span: &mut TextSpan, clusters: &mut [OutlinedCluster], text_ts: &tree::Transform, parent: &mut tree::Node, dump_clusters: bool, ) -> Option { let mut path_data = tree::PathData::new(); for cluster in clusters { if !cluster.visible { continue; } if span.contains(cluster.byte_idx) { if dump_clusters { let mut ts = *text_ts; ts.append(&cluster.transform); dump_cluster(cluster, ts, parent); } let mut path = std::mem::replace(&mut cluster.path, tree::PathData::new()); path.transform(cluster.transform); path_data.extend_from_slice(&path); } } if path_data.is_empty() { return None; } let mut fill = span.fill.take(); if let Some(ref mut fill) = fill { // The `fill-rule` should be ignored. // https://www.w3.org/TR/SVG2/text.html#TextRenderingOrder // // 'Since the fill-rule property does not apply to SVG text elements, // the specific order of the subpaths within the equivalent path does not matter.' fill.rule = tree::FillRule::NonZero; } let path = tree::Path { id: String::new(), transform: *text_ts, visibility: span.visibility, fill, stroke: span.stroke.take(), rendering_mode: tree::ShapeRendering::default(), data: Rc::new(path_data), }; Some(path) } // Only for debug purposes. fn dump_cluster( cluster: &OutlinedCluster, text_ts: tree::Transform, parent: &mut tree::Node, ) { fn new_stroke(color: tree::Color) -> Option { Some(tree::Stroke { paint: tree::Paint::Color(color), width: tree::StrokeWidth::new(0.2), .. tree::Stroke::default() }) } let mut base_path = tree::Path { transform: text_ts, ..tree::Path::default() }; // Cluster bbox. let r = Rect::new(0.0, -cluster.ascent, cluster.advance, cluster.height()).unwrap(); base_path.stroke = new_stroke(tree::Color::blue()); base_path.data = Rc::new(tree::PathData::from_rect(r)); parent.append_kind(tree::NodeKind::Path(base_path.clone())); // Baseline. base_path.stroke = new_stroke(tree::Color::red()); base_path.data = Rc::new(tree::PathData(vec![ tree::PathSegment::MoveTo { x: 0.0, y: 0.0 }, tree::PathSegment::LineTo { x: cluster.advance, y: 0.0 }, ])); parent.append_kind(tree::NodeKind::Path(base_path)); } fn collect_decoration_spans( span: &TextSpan, clusters: &[OutlinedCluster], ) -> Vec { let mut spans = Vec::new(); let mut started = false; let mut width = 0.0; let mut transform = tree::Transform::default(); for cluster in clusters { if span.contains(cluster.byte_idx) { if started && cluster.has_relative_shift { started = false; spans.push(DecorationSpan { width, transform }); } if !started { width = cluster.advance; started = true; transform = cluster.transform; } else { width += cluster.advance; } } else if started { spans.push(DecorationSpan { width, transform }); started = false; } } if started { spans.push(DecorationSpan { width, transform }); } spans } fn convert_decoration( dy: f64, span: &TextSpan, mut decoration: TextDecorationStyle, decoration_spans: &[DecorationSpan], transform: tree::Transform, ) -> tree::Path { debug_assert!(!decoration_spans.is_empty()); let thickness = span.font.underline_thickness(span.font_size); let mut path = tree::PathData::new(); for dec_span in decoration_spans { let rect = Rect::new( 0.0, -thickness / 2.0, dec_span.width, thickness, ).unwrap(); let start_idx = path.len(); add_rect_to_path(rect, &mut path); let mut ts = dec_span.transform; ts.translate(0.0, dy); path.transform_from(start_idx, ts); } tree::Path { id: String::new(), transform, visibility: span.visibility, fill: decoration.fill.take(), stroke: decoration.stroke.take(), rendering_mode: tree::ShapeRendering::default(), data: Rc::new(path), } } fn add_rect_to_path( rect: Rect, path: &mut Vec, ) { path.extend_from_slice(&[ tree::PathSegment::MoveTo { x: rect.x(), y: rect.y() }, tree::PathSegment::LineTo { x: rect.right(), y: rect.y() }, tree::PathSegment::LineTo { x: rect.right(), y: rect.bottom() }, tree::PathSegment::LineTo { x: rect.x(), y: rect.bottom() }, tree::PathSegment::ClosePath, ]); } /// By the SVG spec, `tspan` doesn't have a bbox and uses the parent `text` bbox. /// Since we converted `text` and `tspan` to `path`, we have to update /// all linked paint servers (gradients and patterns) too. fn fix_obj_bounding_box( path: &mut tree::Path, bbox: Rect, tree: &mut tree::Tree, ) { if let Some(ref mut fill) = path.fill { if let tree::Paint::Link(ref mut id) = fill.paint { if let Some(new_id) = paint_server_to_user_space_on_use(id, bbox, tree) { *id = new_id; } } } if let Some(ref mut stroke) = path.stroke { if let tree::Paint::Link(ref mut id) = stroke.paint { if let Some(new_id) = paint_server_to_user_space_on_use(id, bbox, tree) { *id = new_id; } } } } /// Converts a selected paint server's units to `UserSpaceOnUse`. /// /// Creates a deep copy of a selected paint server and returns its ID. /// /// Returns `None` if a paint server already uses `UserSpaceOnUse`. fn paint_server_to_user_space_on_use( id: &str, bbox: Rect, tree: &mut tree::Tree, ) -> Option { if let Some(ps) = tree.defs_by_id(id) { let is_obj_bbox = match *ps.borrow() { tree::NodeKind::LinearGradient(ref lg) => { lg.units == tree::Units::ObjectBoundingBox } tree::NodeKind::RadialGradient(ref rg) => { rg.units == tree::Units::ObjectBoundingBox } tree::NodeKind::Pattern(ref patt) => { patt.units == tree::Units::ObjectBoundingBox } _ => false, }; // Do nothing. if !is_obj_bbox { return None; } // TODO: is `pattern` copying safe? Maybe we should reset id's on all `pattern` children. // We have to clone a paint server, in case some other element is already using it. // If not, the `convert` module will remove unused defs anyway. let mut new_ps = ps.clone().make_deep_copy(); tree.defs().append(new_ps.clone()); let new_id = gen_paint_server_id(tree); // Update id, transform and units. match *new_ps.borrow_mut() { tree::NodeKind::LinearGradient(ref mut lg) => { if lg.units == tree::Units::ObjectBoundingBox { lg.id = new_id.clone(); lg.base.transform.prepend(&tree::Transform::from_bbox(bbox)); lg.base.units = tree::Units::UserSpaceOnUse; } } tree::NodeKind::RadialGradient(ref mut rg) => { if rg.units == tree::Units::ObjectBoundingBox { rg.id = new_id.clone(); rg.base.transform.prepend(&tree::Transform::from_bbox(bbox)); rg.base.units = tree::Units::UserSpaceOnUse; } } tree::NodeKind::Pattern(ref mut patt) => { if patt.units == tree::Units::ObjectBoundingBox { patt.id = new_id.clone(); patt.transform.prepend(&tree::Transform::from_bbox(bbox)); patt.units = tree::Units::UserSpaceOnUse; } } _ => return None, } Some(new_id) } else { None } } /// Creates a free id for a paint server. fn gen_paint_server_id( tree: &tree::Tree, ) -> String { // TODO: speed up let mut idx = 1; let mut id = format!("usvg{}", idx); while tree.defs().children().any(|n| *n.id() == id) { idx += 1; id = format!("usvg{}", idx); } id } resvg-0.8.0/usvg/src/convert/text/shaper.rs000066400000000000000000000610651352576375700207500ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use kurbo::{ParamCurveArclen, ParamCurve, ParamCurveDeriv}; use harfbuzz_rs as harfbuzz; use unicode_vo::Orientation as CharOrientation; use ttf_parser::GlyphId; use kurbo::Vec2; use crate::{tree, fontdb, convert::prelude::*}; use super::convert::{ ByteIndex, CharacterPosition, TextAnchor, TextChunk, TextFlow, TextPath, WritingMode, }; /// A glyph. /// /// Basically, a glyph ID and it's metrics. #[derive(Clone)] struct Glyph { /// The glyph ID in the font. id: GlyphId, /// Position in bytes in the original string. /// /// We use it to match a glyph with a character in the text chunk and therefore with the style. byte_idx: ByteIndex, /// The glyph offset in font units. dx: i32, /// The glyph offset in font units. dy: i32, /// The glyph width / X-advance in font units. width: i32, /// Reference to the source font. /// /// Each glyph can have it's own source font. font: fontdb::Font, } impl Glyph { fn is_missing(&self) -> bool { self.id.0 == 0 } } /// An outlined cluster. /// /// Cluster/grapheme is a single, unbroken, renderable character. /// It can be positioned, rotated, spaced, etc. /// /// Let's say we have `й` which is *CYRILLIC SMALL LETTER I* and *COMBINING BREVE*. /// It consists of two code points, will be shaped (via harfbuzz) as two glyphs into one cluster, /// and then will be combined into the one `OutlinedCluster`. #[derive(Clone)] pub struct OutlinedCluster { /// Position in bytes in the original string. /// /// We use it to match a cluster with a character in the text chunk and therefore with the style. pub byte_idx: ByteIndex, /// Cluster's original codepoint. /// /// Technically, a cluster can contain multiple codepoints, /// but we are storing only the first one. pub codepoint: char, /// An advance along the X axis. /// /// Can be negative. pub advance: f64, /// An ascent in SVG coordinates. pub ascent: f64, /// A descent in SVG coordinates. pub descent: f64, /// A x-height in SVG coordinates. pub x_height: f64, /// Indicates that this cluster was affected by the relative shift (via dx/dy attributes) /// during the text layouting. Which breaks the `text-decoration` line. /// /// Used during the `text-decoration` processing. pub has_relative_shift: bool, /// An actual outline. pub path: tree::PathData, /// A cluster's transform that contains it's position, rotation, etc. pub transform: tree::Transform, /// Not all clusters should be rendered. /// /// For example, if a cluster is outside the text path than it should not be rendered. pub visible: bool, } impl OutlinedCluster { pub fn height(&self) -> f64 { self.ascent - self.descent } } /// An iterator over glyph clusters. /// /// Input: 0 2 2 2 3 4 4 5 5 /// Result: 0 1 4 5 7 struct GlyphClusters<'a> { data: &'a [Glyph], idx: usize, } impl<'a> GlyphClusters<'a> { fn new(data: &'a [Glyph]) -> Self { GlyphClusters { data, idx: 0 } } } impl<'a> Iterator for GlyphClusters<'a> { type Item = (std::ops::Range, ByteIndex); fn next(&mut self) -> Option { if self.idx == self.data.len() { return None; } let start = self.idx; let cluster = self.data[self.idx].byte_idx; for g in &self.data[self.idx..] { if g.byte_idx != cluster { break; } self.idx += 1; } Some((start..self.idx, cluster)) } } /// Converts a text chunk into a list of outlined clusters. /// /// This function will do the BIDI reordering, text shaping and glyphs outlining, /// but not the text layouting. So all clusters are in the 0x0 position. pub fn outline_chunk( chunk: &TextChunk, state: &State, ) -> Vec { let mut glyphs = Vec::new(); for span in &chunk.spans { let tmp_glyphs = shape_text(&chunk.text, span.font, state); // Do nothing with the first run. if glyphs.is_empty() { glyphs = tmp_glyphs; continue; } // We assume, that shaping with an any font will produce the same amount of glyphs. // Otherwise an error. if glyphs.len() != tmp_glyphs.len() { warn!("Text layouting failed."); return Vec::new(); } // Copy span's glyphs. for (i, glyph) in tmp_glyphs.iter().enumerate() { if span.contains(glyph.byte_idx) { glyphs[i] = glyph.clone(); } } } // Convert glyphs to clusters. let mut clusters = Vec::new(); for (range, byte_idx) in GlyphClusters::new(&glyphs) { if let Some(span) = chunk.span_at(byte_idx) { let db = state.db.borrow(); clusters.push(outline_cluster(&glyphs[range], &chunk.text, span.font_size, &db)); } } clusters } /// Text shaping with font fallback. fn shape_text( text: &str, font: fontdb::Font, state: &State, ) -> Vec { let mut glyphs = shape_text_with_font(text, font, state).unwrap_or_default(); // Remember all fonts used for shaping. let mut used_fonts = vec![font.id]; // Loop until all glyphs become resolved or until no more fonts are left. 'outer: loop { let mut missing = None; for glyph in &glyphs { if glyph.is_missing() { missing = Some(glyph.byte_idx.char_from(text)); break; } } if let Some(c) = missing { let fallback_font = match find_font_for_char(c, &used_fonts, state) { Some(v) => v, None => break 'outer, }; // Shape again, using a new font. let fallback_glyphs = shape_text_with_font(text, fallback_font, state) .unwrap_or_default(); // We assume, that shaping with an any font will produce the same amount of glyphs. // Otherwise an error. if glyphs.len() != fallback_glyphs.len() { break 'outer; } // Copy new glyphs. for i in 0..glyphs.len() { if glyphs[i].is_missing() && !fallback_glyphs[i].is_missing() { glyphs[i] = fallback_glyphs[i].clone(); } } // Remember this font. used_fonts.push(fallback_font.id); } else { break 'outer; } } // Warn about missing glyphs. for glyph in &glyphs { if glyph.is_missing() { let c = glyph.byte_idx.char_from(text); // TODO: print a full grapheme warn!("No fonts with a {}/U+{:X} character were found.", c, c as u32); } } glyphs } /// Converts a text into a list of glyph IDs. /// /// This function will do the BIDI reordering and text shaping. fn shape_text_with_font( text: &str, font: fontdb::Font, state: &State, ) -> Option> { let db = state.db.borrow(); // We can't simplify this code because of lifetimes. let item = db.font(font.id); let file = std::fs::File::open(&item.path).ok()?; let mmap = unsafe { memmap::MmapOptions::new().map(&file).ok()? }; let hb_face = harfbuzz::Face::from_bytes(&mmap, item.face_index); let hb_font = harfbuzz::Font::new(hb_face); let bidi_info = unicode_bidi::BidiInfo::new(text, Some(unicode_bidi::Level::ltr())); let paragraph = &bidi_info.paragraphs[0]; let line = paragraph.range.clone(); let mut glyphs = Vec::new(); let (levels, runs) = bidi_info.visual_runs(¶graph, line); for run in runs.iter() { let sub_text = &text[run.clone()]; if sub_text.is_empty() { continue; } let hb_direction = if levels[run.start].is_rtl() { harfbuzz::Direction::Rtl } else { harfbuzz::Direction::Ltr }; let buffer = harfbuzz::UnicodeBuffer::new() .add_str(sub_text) .set_direction(hb_direction); // TODO: feature smcp / small caps // simply setting the `smcp` doesn't work for some reasons let output = harfbuzz::shape(&hb_font, buffer, &[]); let positions = output.get_glyph_positions(); let infos = output.get_glyph_infos(); for (pos, info) in positions.iter().zip(infos) { let idx = run.start + info.cluster as usize; debug_assert!(text.get(idx..).is_some()); glyphs.push(Glyph { byte_idx: ByteIndex::new(idx), id: GlyphId(info.codepoint as u16), dx: pos.x_offset, dy: pos.y_offset, width: pos.x_advance, font, }); } } Some(glyphs) } /// Outlines a glyph cluster. /// /// Uses one or more `Glyph`s to construct an `OutlinedCluster`. fn outline_cluster( glyphs: &[Glyph], text: &str, font_size: f64, db: &fontdb::Database, ) -> OutlinedCluster { debug_assert!(!glyphs.is_empty()); let mut path = tree::PathData::new(); let mut advance = 0.0; let mut x = 0.0; for glyph in glyphs { let mut outline = db.outline(glyph.font.id, glyph.id).unwrap_or_default(); let sx = glyph.font.scale(font_size); if !outline.is_empty() { // By default, glyphs are upside-down, so we have to mirror them. let mut ts = tree::Transform::new_scale(1.0, -1.0); // Scale to font-size. ts.scale(sx, sx); // Apply offset. // // The first glyph in the cluster will have an offset from 0x0, // but the later one will have an offset from the "current position". // So we have to keep an advance. // TODO: should be done only inside a single text span ts.translate(x + glyph.dx as f64, glyph.dy as f64); outline.transform(ts); path.extend_from_slice(&outline); } x += glyph.width as f64; let glyph_width = glyph.width as f64 * sx; if glyph_width > advance { advance = glyph_width; } } OutlinedCluster { byte_idx: glyphs[0].byte_idx, codepoint: glyphs[0].byte_idx.char_from(text), advance, ascent: glyphs[0].font.ascent(font_size), descent: glyphs[0].font.descent(font_size), x_height: glyphs[0].font.x_height(font_size), has_relative_shift: false, path, transform: tree::Transform::default(), visible: true, } } /// Finds a font with a specified char. /// /// This is a rudimentary font fallback algorithm. fn find_font_for_char( c: char, exclude_fonts: &[fontdb::ID], state: &State, ) -> Option { let base_font_id = exclude_fonts[0]; let db = state.db.borrow(); // Iterate over fonts and check if any of them support the specified char. for item in db.fonts() { // Ignore fonts, that were used for shaping already. if exclude_fonts.contains(&item.id) { continue; } if db.font(base_font_id).properties != item.properties { continue; } if !db.has_char(item.id, c) { continue; } warn!( "Fallback from {} to {}.", db.font(base_font_id).path.display(), item.path.display(), ); return db.load_font(item.id); } None } /// Resolves clusters positions. /// /// Mainly sets the `transform` property. /// /// Returns the last text position. The next text chunk should start from that position. pub fn resolve_clusters_positions( chunk: &TextChunk, char_offset: usize, pos_list: &[CharacterPosition], rotate_list: &[f64], writing_mode: WritingMode, clusters: &mut [OutlinedCluster], ) -> (f64, f64) { match chunk.text_flow { TextFlow::Horizontal => { resolve_clusters_positions_horizontal( chunk, char_offset, pos_list, rotate_list, clusters, ) } TextFlow::Path(ref path) => { resolve_clusters_positions_path( chunk, char_offset, path, pos_list, rotate_list, writing_mode, clusters, ) } } } fn resolve_clusters_positions_horizontal( chunk: &TextChunk, offset: usize, pos_list: &[CharacterPosition], rotate_list: &[f64], clusters: &mut [OutlinedCluster], ) -> (f64, f64) { let mut x = process_anchor(chunk.anchor, clusters_length(clusters)); let mut y = 0.0; for cluster in clusters { let cp = offset + cluster.byte_idx.code_point_at(&chunk.text); if let Some(pos) = pos_list.get(cp) { x += pos.dx.unwrap_or(0.0); y += pos.dy.unwrap_or(0.0); cluster.has_relative_shift = pos.dx.is_some() || pos.dy.is_some(); } cluster.transform.translate(x, y); if let Some(angle) = rotate_list.get(cp).cloned() { if !angle.is_fuzzy_zero() { cluster.transform.rotate(angle); cluster.has_relative_shift = true; } } x += cluster.advance; } (x, y) } fn resolve_clusters_positions_path( chunk: &TextChunk, char_offset: usize, path: &TextPath, pos_list: &[CharacterPosition], rotate_list: &[f64], writing_mode: WritingMode, clusters: &mut [OutlinedCluster], ) -> (f64, f64) { let mut last_x = 0.0; let mut last_y = 0.0; let mut dy = 0.0; // In the text path mode, chunk's x/y coordinates provide an additional offset along the path. // The X coordinate is used in a horizontal mode, and Y in vertical. let chunk_offset = match writing_mode { WritingMode::LeftToRight => chunk.x.unwrap_or(0.0), WritingMode::TopToBottom => chunk.y.unwrap_or(0.0), }; let start_offset = chunk_offset + path.start_offset + process_anchor(chunk.anchor, clusters_length(clusters)); let normals = collect_normals( chunk, clusters, &path.path, pos_list, char_offset, start_offset, ); for (cluster, normal) in clusters.iter_mut().zip(normals) { let (x, y, angle) = match normal { Some(normal) => { (normal.x, normal.y, normal.angle) } None => { // Hide clusters that are outside the text path. cluster.visible = false; continue; } }; // We have to break a decoration line for each cluster during text-on-path. cluster.has_relative_shift = true; // Clusters should be rotated by the x-midpoint x baseline position. let half_advance = cluster.advance / 2.0; cluster.transform.translate(x - half_advance, y); cluster.transform.rotate_at(angle, half_advance, 0.0); let cp = char_offset + cluster.byte_idx.code_point_at(&chunk.text); if let Some(pos) = pos_list.get(cp) { dy += pos.dy.unwrap_or(0.0); } let baseline_shift = chunk.span_at(cluster.byte_idx) .map(|span| span.baseline_shift) .unwrap_or(0.0); // Shift only by `dy` since we already applied `dx` // during offset along the path calculation. if !dy.is_fuzzy_zero() || !baseline_shift.is_fuzzy_zero() { let shift = Vec2::from_angle(angle) + Vec2::new(0.0, dy - baseline_shift); cluster.transform.translate(shift.x, shift.y); } if let Some(angle) = rotate_list.get(cp).cloned() { if !angle.is_fuzzy_zero() { cluster.transform.rotate(angle); } } last_x = x + cluster.advance; last_y = y; } (last_x, last_y) } fn clusters_length( clusters: &[OutlinedCluster], ) -> f64 { clusters.iter().fold(0.0, |w, cluster| w + cluster.advance) } fn process_anchor( a: TextAnchor, text_width: f64, ) -> f64 { match a { TextAnchor::Start => 0.0, // Nothing. TextAnchor::Middle => -text_width / 2.0, TextAnchor::End => -text_width, } } struct PathNormal { x: f64, y: f64, angle: f64, } fn collect_normals( chunk: &TextChunk, clusters: &[OutlinedCluster], path: &tree::PathData, pos_list: &[CharacterPosition], char_offset: usize, offset: f64, ) -> Vec> { debug_assert!(!path.is_empty()); let mut offsets = Vec::with_capacity(clusters.len()); let mut normals = Vec::with_capacity(clusters.len()); { let mut advance = offset; for cluster in clusters { // Clusters should be rotated by the x-midpoint x baseline position. let half_advance = cluster.advance / 2.0; // Include relative position. let cp = char_offset + cluster.byte_idx.code_point_at(&chunk.text); if let Some(pos) = pos_list.get(cp) { advance += pos.dx.unwrap_or(0.0); } let offset = advance + half_advance; // Clusters outside the path have no normals. if offset < 0.0 { normals.push(None); } offsets.push(offset); advance += cluster.advance; } } let (mut prev_mx, mut prev_my, mut prev_x, mut prev_y) = { if let tree::PathSegment::MoveTo { x, y } = path[0] { (x, y, x, y) } else { unreachable!(); } }; fn create_curve(px: f64, py: f64, x1: f64, y1: f64, x2: f64, y2: f64, x: f64, y: f64) -> kurbo::CubicBez { kurbo::CubicBez { p0: Vec2::new(px, py), p1: Vec2::new(x1, y1), p2: Vec2::new(x2, y2), p3: Vec2::new(x, y), } } fn create_curve_from_line(px: f64, py: f64, x: f64, y: f64) -> kurbo::CubicBez { let line = kurbo::Line { p0: Vec2::new(px, py), p1: Vec2::new(x, y), }; let p1 = line.eval(0.33); let p2 = line.eval(0.66); create_curve(px, py, p1.x, p1.y, p2.x, p2.y, x, y) } let mut length = 0.0; for seg in path.iter() { let curve = match *seg { tree::PathSegment::MoveTo { x, y } => { prev_mx = x; prev_my = y; prev_x = x; prev_y = y; continue; } tree::PathSegment::LineTo { x, y } => { create_curve_from_line(prev_x, prev_y, x, y) } tree::PathSegment::CurveTo { x1, y1, x2, y2, x, y } => { create_curve(prev_x, prev_y, x1, y1, x2, y2, x, y) } tree::PathSegment::ClosePath => { create_curve_from_line(prev_x, prev_y, prev_mx, prev_my) } }; let curve_len = curve.arclen(1.0); for offset in &offsets[normals.len()..] { if *offset >= length && *offset <= length + curve_len { let offset = (offset - length) / curve_len; debug_assert!(offset >= 0.0 && offset <= 1.0); let pos = curve.eval(offset); let d = curve.deriv().eval(offset); let d = Vec2::new(-d.y, d.x); // tangent let angle = d.atan2().to_degrees() - 90.0; normals.push(Some(PathNormal { x: pos.x, y: pos.y, angle, })); if normals.len() == offsets.len() { break; } } } length += curve_len; prev_x = curve.p3.x; prev_y = curve.p3.y; } // If path ended and we still have unresolved normals - set them to `None`. for _ in 0..(offsets.len() - normals.len()) { normals.push(None); } normals } /// Applies the `letter-spacing` property to a text chunk clusters. /// /// [In the CSS spec](https://www.w3.org/TR/css-text-3/#letter-spacing-property). pub fn apply_letter_spacing( chunk: &TextChunk, clusters: &mut [OutlinedCluster], ) { // At least one span should have a non-zero spacing. if !chunk.spans.iter().any(|span| !span.letter_spacing.is_fuzzy_zero()) { return; } for cluster in clusters { // Spacing must be applied only to characters that belongs to the script // that supports spacing. // We are checking only the first code point, since it should be enough. let script = unicode_script::get_script(cluster.codepoint); if script_supports_letter_spacing(script) { if let Some(span) = chunk.span_at(cluster.byte_idx) { // Technically, we should ignore spacing on the last character, // but it doesn't affect us in any way, so we are ignoring this. cluster.advance += span.letter_spacing; // If the cluster advance became negative - clear it. // This is an UB so we can do whatever we want, so we mimic the Chrome behavior. if !(cluster.advance > 0.0) { cluster.advance = 0.0; cluster.path.clear(); } } } } } /// Checks that selected script supports letter spacing. /// /// [In the CSS spec](https://www.w3.org/TR/css-text-3/#cursive-tracking). /// /// The list itself is from: https://github.com/harfbuzz/harfbuzz/issues/64 fn script_supports_letter_spacing( script: unicode_script::Script, ) -> bool { use unicode_script::Script; !matches!(script, Script::Arabic | Script::Syriac | Script::Nko | Script::Manichaean | Script::Psalter_Pahlavi | Script::Mandaic | Script::Mongolian | Script::Phags_Pa | Script::Devanagari | Script::Bengali | Script::Gurmukhi | Script::Modi | Script::Sharada | Script::Syloti_Nagri | Script::Tirhuta | Script::Ogham) } /// Applies the `word-spacing` property to a text chunk clusters. /// /// [In the CSS spec](https://www.w3.org/TR/css-text-3/#propdef-word-spacing). pub fn apply_word_spacing( chunk: &TextChunk, clusters: &mut [OutlinedCluster], ) { // At least one span should have a non-zero spacing. if !chunk.spans.iter().any(|span| !span.word_spacing.is_fuzzy_zero()) { return; } for cluster in clusters { if is_word_separator_characters(cluster.codepoint) { if let Some(span) = chunk.span_at(cluster.byte_idx) { // Technically, word spacing 'should be applied half on each // side of the character', but it doesn't affect us in any way, // so we are ignoring this. cluster.advance += span.word_spacing; // After word spacing, `advance` can be negative. } } } } /// Checks that the selected character is a word separator. /// /// According to: https://www.w3.org/TR/css-text-3/#word-separator fn is_word_separator_characters( c: char, ) -> bool { matches!(c as u32, 0x0020 | 0x00A0 | 0x1361 | 0x010100 | 0x010101 | 0x01039F | 0x01091F) } /// Rotates clusters according to /// [Unicode Vertical_Orientation Property](https://www.unicode.org/reports/tr50/tr50-19.html). pub fn apply_writing_mode( writing_mode: WritingMode, clusters: &mut [OutlinedCluster], ) { if writing_mode != WritingMode::TopToBottom { return; } for cluster in clusters { let orientation = unicode_vo::char_orientation(cluster.codepoint); if orientation == CharOrientation::Upright { // Additional offset. Not sure why. let dy = cluster.advance - cluster.height(); // Rotate a cluster 90deg counter clockwise by the center. let mut ts = tree::Transform::default(); ts.translate(cluster.advance / 2.0, 0.0); ts.rotate(-90.0); ts.translate(-cluster.advance / 2.0, -dy); cluster.path.transform(ts); // Move "baseline" to the middle and make height equal to advance. cluster.ascent = cluster.advance / 2.0; cluster.descent = -cluster.advance / 2.0; } else { // Could not find a spec that explains this, // but this is how other applications are shifting the "rotated" characters // in the top-to-bottom mode. cluster.transform.translate(0.0, cluster.x_height / 2.0); } } } resvg-0.8.0/usvg/src/convert/units.rs000066400000000000000000000077671352576375700176550ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::{svgtree, tree}; use super::prelude::*; #[inline(never)] pub fn convert_length( length: Length, node: svgtree::Node, aid: AId, object_units: tree::Units, state: &State, ) -> f64 { let dpi = state.opt.dpi; let n = length.num; match length.unit { Unit::None | Unit::Px => n, Unit::Em => n * resolve_font_size(node, state), Unit::Ex => n * resolve_font_size(node, state) / 2.0, Unit::In => n * dpi, Unit::Cm => n * dpi / 2.54, Unit::Mm => n * dpi / 25.4, Unit::Pt => n * dpi / 72.0, Unit::Pc => n * dpi / 6.0, Unit::Percent => { if object_units == tree::Units::ObjectBoundingBox { length.num / 100.0 } else { let view_box = state.view_box; match aid { AId::X | AId::Cx | AId::Width => { convert_percent(length, view_box.width()) } AId::Y | AId::Cy | AId::Height => { convert_percent(length, view_box.height()) } _ => { let vb_len = ( view_box.width() * view_box.width() + view_box.height() * view_box.height() ).sqrt() / 2.0_f64.sqrt(); convert_percent(length, vb_len) } } } } } } #[inline(never)] pub fn convert_list( node: svgtree::Node, aid: AId, state: &State, ) -> Option> { if let Some(text) = node.attribute::<&str>(aid) { let mut num_list = Vec::new(); for length in svgtypes::LengthListParser::from(text) { if let Ok(length) = length { num_list.push(convert_length(length, node, aid, tree::Units::UserSpaceOnUse, state)); } } Some(num_list) } else { None } } fn convert_percent(length: Length, base: f64) -> f64 { base * length.num / 100.0 } #[inline(never)] pub fn resolve_font_size(node: svgtree::Node, state: &State) -> f64 { let nodes: Vec<_> = node.ancestors().collect(); let mut font_size = state.opt.font_size; for n in nodes.iter().rev().skip(1) { // skip Root if let Some(length) = n.attribute::(AId::FontSize) { let dpi = state.opt.dpi; let n = length.num; font_size = match length.unit { Unit::None | Unit::Px => n, Unit::Em => n * font_size, Unit::Ex => n * font_size / 2.0, Unit::In => n * dpi, Unit::Cm => n * dpi / 2.54, Unit::Mm => n * dpi / 25.4, Unit::Pt => n * dpi / 72.0, Unit::Pc => n * dpi / 6.0, Unit::Percent => { // If `font-size` has percent units that it's value // is relative to the parent node `font-size`. length.num * font_size * 0.01 } } } else if let Some(name) = n.attribute(AId::FontSize) { font_size = convert_named_font_size(name, font_size); } } font_size } fn convert_named_font_size( name: &str, parent_font_size: f64, ) -> f64 { let factor = match name { "xx-small" => -3, "x-small" => -2, "small" => -1, "medium" => 0, "large" => 1, "x-large" => 2, "xx-large" => 3, "smaller" => -1, "larger" => 1, _ => { warn!("Invalid 'font-size' value: '{}'.", name); 0 } }; // 'On a computer screen a scaling factor of 1.2 is suggested between adjacent indexes.' parent_font_size * 1.2f64.powi(factor) } resvg-0.8.0/usvg/src/convert/use_node.rs000066400000000000000000000155531352576375700203040ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::rc::Rc; use crate::{svgtree, tree, tree::prelude::*, utils}; use super::prelude::*; pub fn convert( node: svgtree::Node, state: &State, parent: &mut tree::Node, tree: &mut tree::Tree, ) { let child = try_opt!(node.first_child()); if state.parent_clip_path.is_some() && child.tag_name() == Some(EId::Symbol) { // Ignore `symbol` referenced by `use` inside a `clipPath`. // It will be ignored later anyway, but this will prevent // a redundant `clipPath` creation (which is required for `symbol`). return; } // We require an original transformation to setup 'clipPath'. let mut orig_ts: tree::Transform = node.attribute(AId::Transform).unwrap_or_default(); let mut new_ts = tree::Transform::default(); { let x = node.convert_user_length(AId::X, state, Length::zero()); let y = node.convert_user_length(AId::Y, state, Length::zero()); new_ts.translate(x, y); } let linked_to_symbol = child.tag_name() == Some(EId::Symbol); if linked_to_symbol { if let Some(ts) = viewbox_transform(node, child, state) { new_ts.append(&ts); } if let Some(clip_rect) = get_clip_rect(node, child, state) { let mut g = clip_element(node, clip_rect, orig_ts, parent, tree); convert_children(child, new_ts, state, &mut g, tree); return; } } orig_ts.append(&new_ts); if linked_to_symbol { convert_children(child, orig_ts, state, parent, tree); } else { convert_children(node, orig_ts, state, parent, tree); } } pub fn convert_svg( node: svgtree::Node, state: &State, parent: &mut tree::Node, tree: &mut tree::Tree, ) { // We require original transformation to setup 'clipPath'. let mut orig_ts: tree::Transform = node.attribute(AId::Transform).unwrap_or_default(); let mut new_ts = tree::Transform::default(); { let x = node.convert_user_length(AId::X, state, Length::zero()); let y = node.convert_user_length(AId::Y, state, Length::zero()); new_ts.translate(x, y); } if let Some(ts) = viewbox_transform(node, node, state) { new_ts.append(&ts); } if let Some(clip_rect) = get_clip_rect(node, node, state) { let mut g = clip_element(node, clip_rect, orig_ts, parent, tree); convert_children(node, new_ts, state, &mut g, tree); } else { orig_ts.append(&new_ts); convert_children(node, orig_ts, state, parent, tree); } } fn clip_element( node: svgtree::Node, clip_rect: Rect, transform: tree::Transform, parent: &mut tree::Node, tree: &mut tree::Tree, ) -> tree::Node { // We can't set `clip-path` on the element itself, // because it will be affected by a possible transform. // So we have to create an additional group. // Emulate a new viewport via clipPath. // // From: // // // // To: // // // // // // // // let id = gen_clip_path_id(node, tree); let mut clip_path = tree.append_to_defs(tree::NodeKind::ClipPath(tree::ClipPath { id: id.clone(), ..tree::ClipPath::default() })); clip_path.append_kind(tree::NodeKind::Path(tree::Path { fill: Some(tree::Fill::default()), data: Rc::new(tree::PathData::from_rect(clip_rect)), ..tree::Path::default() })); parent.append_kind(tree::NodeKind::Group(tree::Group { id: node.element_id().to_string(), transform, clip_path: Some(id), ..tree::Group::default() })) } fn convert_children( node: svgtree::Node, transform: tree::Transform, state: &State, parent: &mut tree::Node, tree: &mut tree::Tree, ) { let required = !transform.is_default(); match super::convert_group(node, state, required, parent, tree) { super::GroupKind::Create(mut g) => { if let tree::NodeKind::Group(ref mut g) = *g.borrow_mut() { g.transform = transform; } if state.parent_clip_path.is_some() { super::convert_clip_path_elements(node, state, &mut g, tree); } else { super::convert_children(node, state, &mut g, tree); } } super::GroupKind::Skip => { if state.parent_clip_path.is_some() { super::convert_clip_path_elements(node, state,parent, tree); } else { super::convert_children(node, state, parent, tree); } } super::GroupKind::Ignore => {} } } fn get_clip_rect( use_node: svgtree::Node, symbol_node: svgtree::Node, state: &State, ) -> Option { // No need to clip elements with overflow:visible. { let overflow = symbol_node.attribute(AId::Overflow); if overflow == Some("visible") || overflow == Some("auto") { return None; } } let (x, y, w, h) = { let x = use_node.convert_user_length(AId::X, state, Length::zero()); let y = use_node.convert_user_length(AId::Y, state, Length::zero()); let w = use_node.convert_user_length(AId::Width, state, Length::new(100.0, Unit::Percent)); let h = use_node.convert_user_length(AId::Height, state, Length::new(100.0, Unit::Percent)); (x, y, w, h) }; if w.is_fuzzy_zero() || h.is_fuzzy_zero() { return None; } // TODO: add a test case // Clip rect is not needed when it has the same size as a whole image. if w.fuzzy_eq(&state.size.width()) && h.fuzzy_eq(&state.size.height()) { return None; } Rect::new(x, y, w, h) } /// Creates a free id for `clipPath`. pub fn gen_clip_path_id( node: svgtree::Node, tree: &tree::Tree, ) -> String { let mut idx = 1; let mut id = format!("clipPath{}", idx); while node.document().descendants().any(|n| n.element_id() == id) || tree.defs().children().any(|n| *n.id() == id) { idx += 1; id = format!("clipPath{}", idx); } id } fn viewbox_transform( node: svgtree::Node, linked: svgtree::Node, state: &State, ) -> Option { let size = { let w = node.convert_user_length(AId::Width, state, Length::new(100.0, Unit::Percent)); let h = node.convert_user_length(AId::Height, state, Length::new(100.0, Unit::Percent)); Size::new(w, h) }?; let vb = linked.get_viewbox()?; let aspect = linked.attribute(AId::PreserveAspectRatio).unwrap_or_default(); Some(utils::view_box_to_transform(vb, aspect, size)) } resvg-0.8.0/usvg/src/error.rs000066400000000000000000000033411352576375700161440ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. /// List of all errors. #[derive(Debug)] pub enum Error { /// Only `svg` and `svgz` suffixes are supported. InvalidFileSuffix, /// Failed to open the provided file. FileOpenFailed, /// Only UTF-8 content are supported. NotAnUtf8Str, /// Compressed SVG must use the GZip algorithm. MalformedGZip, /// SVG doesn't have a valid size. /// /// Occurs when width and/or height are <= 0. /// /// Also occurs if width, height and viewBox are not set. /// This is against the SVG spec, but an automatic size detection is not supported yet. InvalidSize, /// Failed to parse an SVG data. ParsingFailed(roxmltree::Error), } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match *self { Error::InvalidFileSuffix => { write!(f, "invalid file suffix") } Error::FileOpenFailed => { write!(f, "failed to open the provided file") } Error::NotAnUtf8Str => { write!(f, "provided data has not an UTF-8 encoding") } Error::MalformedGZip => { write!(f, "provided data has a malformed GZip content") } Error::InvalidSize => { write!(f, "SVG has an invalid size") } Error::ParsingFailed(ref e) => { write!(f, "SVG data parsing failed cause {}", e) } } } } impl std::error::Error for Error {} resvg-0.8.0/usvg/src/fontdb.rs000066400000000000000000000432061352576375700162730ustar00rootroot00000000000000use std::path::{Path, PathBuf}; use std::fs; pub use ttf_parser::{GlyphId, Weight, Width as Stretch}; use crate::tree; use crate::utils; #[cfg(target_os = "linux")] const GENERIC_FAMILIES: &[&str] = &["serif", "sans-serif", "monospace", "cursive", "fantasy"]; #[derive(Clone, Debug)] pub struct FontItem { pub id: ID, pub path: PathBuf, pub face_index: u32, pub family: String, pub properties: Properties, } #[derive(Clone, Copy, PartialEq, Debug)] pub struct ID(u16); // 65k fonts if more than enough! pub struct Database { fonts: Vec, #[allow(dead_code)] has_generic_fonts: bool, } impl Database { pub fn new() -> Self { Database { fonts: Vec::new(), has_generic_fonts: false, } } pub fn populate(&mut self) { if self.fonts.is_empty() { load_all_fonts(&mut self.fonts); } } #[inline(never)] #[cfg(target_os = "linux")] fn collect_generic_fonts(&mut self) { if self.fonts.is_empty() { return; } for family in GENERIC_FAMILIES { self.collect_generic_font(family); } self.has_generic_fonts = true; } #[cfg(target_os = "linux")] fn collect_generic_font(&mut self, generic_family: &str) -> Option<()> { let output = std::process::Command::new("fc-match") .arg(generic_family) .arg("--format=%{family}") .output().ok(); let output = try_opt_warn_or!(output, None, "Failed to run 'fc-match'."); let family = std::str::from_utf8(&output.stdout).ok()?.trim(); duplicate_family(family, generic_family, &mut self.fonts); Some(()) } pub fn font(&self, id: ID) -> &FontItem { &self.fonts[id.0 as usize] } pub fn fonts(&self) -> &[FontItem] { &self.fonts } #[inline(never)] pub fn select_best_match( &mut self, family_names: &[&str], properties: Properties, ) -> Option { for family_name in family_names { // A generic font families querying is very slow on Linux (50-200ms), // so do it only when necessary. #[cfg(target_os = "linux")] { if !self.has_generic_fonts && GENERIC_FAMILIES.contains(family_name) { self.collect_generic_fonts(); } } let mut ids = Vec::new(); let mut candidates = Vec::new(); for item in self.fonts.iter().filter(|font| &font.family == family_name) { ids.push(item.id); candidates.push(item.properties); } if let Some(index) = find_best_match(&candidates, properties) { return Some(ids[index]); } } None } #[inline(never)] pub fn outline(&self, id: ID, glyph_id: GlyphId) -> Option { // We can't simplify this code because of lifetimes. let item = self.font(id); let file = fs::File::open(&item.path).ok()?; let mmap = unsafe { memmap::MmapOptions::new().map(&file).ok()? }; let font = ttf_parser::Font::from_data(&mmap, item.face_index).ok()?; let mut builder = PathBuilder { path: tree::PathData::with_capacity(16) }; font.outline_glyph(glyph_id, &mut builder).ok()?; Some(builder.path) } #[inline(never)] pub fn has_char(&self, id: ID, c: char) -> bool { self._has_char(id, c).unwrap_or(false) } fn _has_char(&self, id: ID, c: char) -> Option { // We can't simplify this code because of lifetimes. let item = self.font(id); let file = fs::File::open(&item.path).ok()?; let mmap = unsafe { memmap::MmapOptions::new().map(&file).ok()? }; let font = ttf_parser::Font::from_data(&mmap, item.face_index).ok()?; font.glyph_index(c).ok()?; Some(true) } #[inline(never)] pub fn load_font(&self, id: ID) -> Option { // We can't simplify this code because of lifetimes. let item = self.font(id); let file = fs::File::open(&item.path).ok()?; let mmap = unsafe { memmap::MmapOptions::new().map(&file).ok()? }; let font = ttf_parser::Font::from_data(&mmap, item.face_index).ok()?; // Some fonts can have `units_per_em` set to zero, which will break out calculations. // `ttf_parser` will check this for us. let units_per_em = font.units_per_em()?; let ascent = font.ascender(); let descent = font.descender(); let x_height = match font.x_height() { Some(height) => height, None => { // If not set - fallback to height * 45%. // 45% is what Firefox uses. (f32::from(ascent - descent) * 0.45) as i16 } }; let underline = match font.underline_metrics() { Some(metrics) => metrics, None => { ttf_parser::LineMetrics { position: -(units_per_em as i16) / 9, thickness: units_per_em as i16 / 12, } } }; let line_through_position = match font.strikeout_metrics() { Some(metrics) => metrics.position, None => x_height / 2, }; // 0.2 and 0.4 are generic offsets used by some applications (Inkscape/librsvg). let mut subscript_offset = (units_per_em as f32 / 0.2).round() as i16; let mut superscript_offset = (units_per_em as f32 / 0.4).round() as i16; if let Some(metrics) = font.subscript_metrics() { subscript_offset = metrics.y_offset; } if let Some(metrics) = font.superscript_metrics() { superscript_offset = metrics.y_offset; } Some(Font { id, units_per_em, ascent, descent, x_height, underline_position: underline.position, underline_thickness: underline.thickness, line_through_position, subscript_offset, superscript_offset, }) } } #[derive(Clone, Copy)] pub struct Font { pub id: ID, /// Guarantee to be > 0. units_per_em: u16, // All values below are in font units. ascent: i16, descent: i16, x_height: i16, underline_position: i16, underline_thickness: i16, // line-through thickness should be the the same as underline thickness // according to the TrueType spec: // https://docs.microsoft.com/en-us/typography/opentype/spec/os2#ystrikeoutsize line_through_position: i16, subscript_offset: i16, superscript_offset: i16, } impl Font { #[inline] pub fn scale(&self, font_size: f64) -> f64 { font_size / self.units_per_em as f64 } #[inline] pub fn ascent(&self, font_size: f64) -> f64 { self.ascent as f64 * self.scale(font_size) } #[inline] pub fn descent(&self, font_size: f64) -> f64 { self.descent as f64 * self.scale(font_size) } #[inline] pub fn height(&self, font_size: f64) -> f64 { self.ascent(font_size) - self.descent(font_size) } #[inline] pub fn x_height(&self, font_size: f64) -> f64 { self.x_height as f64 * self.scale(font_size) } #[inline] pub fn underline_position(&self, font_size: f64) -> f64 { self.underline_position as f64 * self.scale(font_size) } #[inline] pub fn underline_thickness(&self, font_size: f64) -> f64 { self.underline_thickness as f64 * self.scale(font_size) } #[inline] pub fn line_through_position(&self, font_size: f64) -> f64 { self.line_through_position as f64 * self.scale(font_size) } #[inline] pub fn subscript_offset(&self, font_size: f64) -> f64 { self.subscript_offset as f64 * self.scale(font_size) } #[inline] pub fn superscript_offset(&self, font_size: f64) -> f64 { self.superscript_offset as f64 * self.scale(font_size) } } #[derive(Clone, Copy, PartialEq, Default, Debug)] pub struct Properties { pub style: Style, pub weight: Weight, pub stretch: Stretch, } #[derive(Clone, Copy, PartialEq, Debug)] pub enum Style { Normal, Italic, Oblique, } impl Default for Style { fn default() -> Style { Style::Normal } } impl_enum_from_str!(Style, "normal" => Style::Normal, "italic" => Style::Italic, "oblique" => Style::Oblique ); /// From https://github.com/pcwalton/font-kit #[inline(never)] fn find_best_match( candidates: &[Properties], query: Properties, ) -> Option { let weight = query.weight.to_number(); // Step 4. let mut matching_set: Vec = (0..candidates.len()).collect(); if matching_set.is_empty() { return None; } // Step 4a (`font-stretch`). let matching_stretch = if matching_set .iter() .any(|&index| candidates[index].stretch == query.stretch) { // Exact match. query.stretch } else if query.stretch <= Stretch::Normal { // Closest width, first checking narrower values and then wider values. match matching_set .iter() .filter(|&&index| candidates[index].stretch < query.stretch) .min_by_key(|&&index| { query.stretch.to_number() - candidates[index].stretch.to_number() }) { Some(&matching_index) => candidates[matching_index].stretch, None => { let matching_index = *matching_set .iter() .min_by_key(|&&index| { candidates[index].stretch.to_number() - query.stretch.to_number() }) .unwrap(); candidates[matching_index].stretch } } } else { // Closest width, first checking wider values and then narrower values. match matching_set .iter() .filter(|&&index| candidates[index].stretch > query.stretch) .min_by_key(|&&index| { candidates[index].stretch.to_number() - query.stretch.to_number() }) { Some(&matching_index) => candidates[matching_index].stretch, None => { let matching_index = *matching_set .iter() .min_by_key(|&&index| { query.stretch.to_number() - candidates[index].stretch.to_number() }) .unwrap(); candidates[matching_index].stretch } } }; matching_set.retain(|&index| candidates[index].stretch == matching_stretch); // Step 4b (`font-style`). let style_preference = match query.style { Style::Italic => [Style::Italic, Style::Oblique, Style::Normal], Style::Oblique => [Style::Oblique, Style::Italic, Style::Normal], Style::Normal => [Style::Normal, Style::Oblique, Style::Italic], }; let matching_style = *style_preference .iter() .filter(|&query_style| { matching_set .iter() .any(|&index| candidates[index].style == *query_style) }) .next() .unwrap(); matching_set.retain(|&index| candidates[index].style == matching_style); // Step 4c (`font-weight`). // // The spec doesn't say what to do if the weight is between 400 and 500 exclusive, so we // just use 450 as the cutoff. let matching_weight = if weight >= 400 && weight < 450 && matching_set .iter() .any(|&index| candidates[index].weight.to_number() == 500) { // Check 500 first. Weight::Medium } else if weight >= 450 && weight <= 500 && matching_set .iter() .any(|&index| candidates[index].weight.to_number() == 400) { // Check 400 first. Weight::Normal } else if weight <= 500 { // Closest weight, first checking thinner values and then fatter ones. match matching_set .iter() .filter(|&&index| candidates[index].weight.to_number() <= weight) .min_by_key(|&&index| weight - candidates[index].weight.to_number()) { Some(&matching_index) => candidates[matching_index].weight, None => { let matching_index = *matching_set .iter() .min_by_key(|&&index| { candidates[index].weight.to_number() - weight }) .unwrap(); candidates[matching_index].weight } } } else { // Closest weight, first checking fatter values and then thinner ones. match matching_set .iter() .filter(|&&index| candidates[index].weight.to_number() >= weight) .min_by_key(|&&index| candidates[index].weight.to_number() - weight) { Some(&matching_index) => candidates[matching_index].weight, None => { let matching_index = *matching_set .iter() .min_by_key(|&&index| { weight - candidates[index].weight.to_number() }) .unwrap(); candidates[matching_index].weight } } }; matching_set.retain(|&index| candidates[index].weight == matching_weight); // Ignore step 4d. // Return the result. matching_set.into_iter().next() } struct PathBuilder { path: tree::PathData, } impl ttf_parser::OutlineBuilder for PathBuilder { fn move_to(&mut self, x: f32, y: f32) { self.path.push_move_to(x as f64, y as f64); } fn line_to(&mut self, x: f32, y: f32) { self.path.push_line_to(x as f64, y as f64); } fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) { self.path.push_quad_to( x1 as f64, y1 as f64, x as f64, y as f64, ); } fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) { self.path.push_curve_to( x1 as f64, y1 as f64, x2 as f64, y2 as f64, x as f64, y as f64 ); } fn close(&mut self) { self.path.push_close_path(); } } #[cfg(target_os = "linux")] fn load_all_fonts(fonts: &mut Vec) { load_fonts_from("/usr/share/fonts/", fonts); load_fonts_from("/usr/local/share/fonts/", fonts); if let Ok(ref home) = std::env::var("HOME") { let path = Path::new(home).join("/.local/share/fonts"); load_fonts_from(path.to_str().unwrap(), fonts); } } #[cfg(target_os = "windows")] fn load_all_fonts(fonts: &mut Vec) { load_fonts_from("C:\\Windows\\Fonts\\", fonts); duplicate_family("Times New Roman", "serif", fonts); duplicate_family("Arial", "sans-serif", fonts); duplicate_family("Courier New", "monospace", fonts); duplicate_family("Comic Sans MS", "cursive", fonts); duplicate_family("Impact", "fantasy", fonts); } #[cfg(target_os = "macos")] fn load_all_fonts(fonts: &mut Vec) { load_fonts_from("/Library/Fonts", fonts); load_fonts_from("/System/Library/Fonts", fonts); duplicate_family("Times New Roman", "serif", fonts); duplicate_family("Arial", "sans-serif", fonts); duplicate_family("Courier New", "monospace", fonts); duplicate_family("Comic Sans MS", "cursive", fonts); duplicate_family("Papyrus", "fantasy", fonts); } fn load_fonts_from(dir: &str, fonts: &mut Vec) { let fonts_dir = try_opt!(std::fs::read_dir(dir).ok()); for entry in fonts_dir { if let Ok(entry) = entry { let path = entry.path(); if path.is_file() { match utils::file_extension(&path) { Some("ttf") | Some("ttc") | Some("TTF") | Some("TTC") | Some("otf") | Some("otc") | Some("OTF") | Some("OTC") => { let _ = load_font(&path, fonts); } _ => {} } } else if path.is_dir() { load_fonts_from(path.to_str().unwrap(), fonts); } } } } fn load_font( path: &Path, fonts: &mut Vec, ) -> Result<(), Box> { let file = fs::File::open(path)?; let mmap = unsafe { memmap::MmapOptions::new().map(&file)? }; let n = ttf_parser::fonts_in_collection(&mmap).unwrap_or(1); for index in 0..n { if let Some(item) = resolve_font(&mmap, path, index, fonts.len()) { fonts.push(item); } } Ok(()) } fn resolve_font( data: &[u8], path: &Path, index: u32, id: usize, ) -> Option { let font = ttf_parser::Font::from_data(data, index).ok()?; let family = font.family_name()?; let style = if font.is_italic() { Style::Italic } else if font.is_oblique() { Style::Oblique } else { Style::Normal }; let weight = font.weight(); let stretch = font.width(); let properties = Properties { style, weight, stretch }; Some(FontItem { id: ID(id as u16), path: path.to_path_buf(), face_index: index, family, properties, }) } fn duplicate_family( old_family: &str, new_family: &str, fonts: &mut Vec, ) { let mut i = 0; while i < fonts.len() { if fonts[i].family == old_family { let mut new_font = fonts[i].clone(); new_font.family = new_family.to_string(); fonts.push(new_font); } i += 1; } } resvg-0.8.0/usvg/src/geom.rs000066400000000000000000000150551352576375700157470ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::{f64, fmt}; use svgtypes::FuzzyEq; use crate::IsValidLength; /// Bounds `f64` number. #[inline] pub(crate) fn f64_bound(min: f64, val: f64, max: f64) -> f64 { debug_assert!(min.is_finite()); debug_assert!(val.is_finite()); debug_assert!(max.is_finite()); if val > max { return max; } else if val < min { return min; } val } /// Line representation. #[allow(missing_docs)] #[derive(Clone, Copy, Debug)] pub(crate) struct Line { pub x1: f64, pub y1: f64, pub x2: f64, pub y2: f64, } impl Line { /// Creates a new line. #[inline] pub fn new(x1: f64, y1: f64, x2: f64, y2: f64) -> Line { Line { x1, y1, x2, y2 } } /// Calculates the line length. #[inline] pub fn length(&self) -> f64 { let x = self.x2 - self.x1; let y = self.y2 - self.y1; (x*x + y*y).sqrt() } /// Sets the line length. pub fn set_length(&mut self, len: f64) { let x = self.x2 - self.x1; let y = self.y2 - self.y1; let len2 = (x*x + y*y).sqrt(); let line = Line { x1: self.x1, y1: self.y1, x2: self.x1 + x/len2, y2: self.y1 + y/len2 }; self.x2 = self.x1 + (line.x2 - line.x1) * len; self.y2 = self.y1 + (line.y2 - line.y1) * len; } } /// A 2D size representation. /// /// Width and height are guarantee to be > 0. #[derive(Clone, Copy)] pub struct Size { width: f64, height: f64, } impl Size { /// Creates a new `Size` from values. #[inline] pub fn new(width: f64, height: f64) -> Option { if width.is_valid_length() && height.is_valid_length() { Some(Size { width, height }) } else { None } } /// Returns width. #[inline] pub fn width(&self) -> f64 { self.width } /// Returns height. #[inline] pub fn height(&self) -> f64 { self.height } /// Converts the current size to `Rect` at provided position. #[inline] pub fn to_rect(&self, x: f64, y: f64) -> Rect { Rect::new(x, y, self.width, self.height).unwrap() } } impl fmt::Debug for Size { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Size({} {})", self.width, self.height) } } impl fmt::Display for Size { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) } } impl FuzzyEq for Size { #[inline] fn fuzzy_eq(&self, other: &Self) -> bool { self.width.fuzzy_eq(&other.width) && self.height.fuzzy_eq(&other.height) } } /// A rect representation. /// /// Width and height are guarantee to be > 0. #[derive(Clone, Copy)] pub struct Rect { x: f64, y: f64, width: f64, height: f64, } impl Rect { /// Creates a new `Rect` from values. #[inline] pub fn new(x: f64, y: f64, width: f64, height: f64) -> Option { if width.is_valid_length() && height.is_valid_length() { Some(Rect { x, y, width, height }) } else { None } } /// Creates a new `Rect` for bounding box calculation. /// /// Shorthand for `Rect::new(f64::MAX, f64::MAX, 1.0, 1.0)`. #[inline] pub fn new_bbox() -> Self { Rect::new(f64::MAX, f64::MAX, 1.0, 1.0).unwrap() } /// Returns rect's size. #[inline] pub fn size(&self) -> Size { Size::new(self.width, self.height).unwrap() } /// Returns rect's X position. #[inline] pub fn x(&self) -> f64 { self.x } /// Returns rect's Y position. #[inline] pub fn y(&self) -> f64 { self.y } /// Returns rect's width. #[inline] pub fn width(&self) -> f64 { self.width } /// Returns rect's height. #[inline] pub fn height(&self) -> f64 { self.height } /// Returns rect's left edge position. #[inline] pub fn left(&self) -> f64 { self.x } /// Returns rect's right edge position. #[inline] pub fn right(&self) -> f64 { self.x + self.width } /// Returns rect's top edge position. #[inline] pub fn top(&self) -> f64 { self.y } /// Returns rect's bottom edge position. #[inline] pub fn bottom(&self) -> f64 { self.y + self.height } /// Translates the rect by the specified offset. #[inline] pub fn translate(&self, tx: f64, ty: f64) -> Self { Rect { x: self.x + tx, y: self.y + ty, width: self.width, height: self.height, } } /// Translates the rect to the specified position. #[inline] pub fn translate_to(&self, x: f64, y: f64) -> Self { Rect { x, y, width: self.width, height: self.height, } } /// Checks that the rect contains a point. #[inline] pub fn contains(&self, x: f64, y: f64) -> bool { if x < self.x || x > self.x + self.width - 1.0 { return false; } if y < self.y || y > self.y + self.height - 1.0 { return false; } true } /// Expands the `Rect` to the provided size. #[inline] pub fn expand(&self, r: Rect) -> Self { #[inline] fn f64_min(v1: f64, v2: f64) -> f64 { if v1 < v2 { v1 } else { v2 } } #[inline] fn f64_max(v1: f64, v2: f64) -> f64 { if v1 > v2 { v1 } else { v2 } } if self.fuzzy_eq(&Rect::new_bbox()) { r } else { let x1 = f64_min(self.x(), r.x()); let y1 = f64_min(self.y(), r.y()); let x2 = f64_max(self.right(), r.right()); let y2 = f64_max(self.bottom(), r.bottom()); Rect::new(x1, y1, x2 - x1, y2 - y1).unwrap() } } } impl FuzzyEq for Rect { #[inline] fn fuzzy_eq(&self, other: &Self) -> bool { self.x.fuzzy_eq(&other.x) && self.y.fuzzy_eq(&other.y) && self.width.fuzzy_eq(&other.width) && self.height.fuzzy_eq(&other.height) } } impl fmt::Debug for Rect { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Rect({} {} {} {})", self.x, self.y, self.width, self.height) } } impl fmt::Display for Rect { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) } } resvg-0.8.0/usvg/src/lib.rs000066400000000000000000000134531352576375700155660ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. /*! `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 space - All (supported) attributes are resolved. No implicit one - No `use`. Everything is resolved - No invisible elements - No invalid elements (like `rect` with negative/zero size) - No units (mm, em, etc.) - No comments - No DTD - No CSS (partial support) - No `script` (simply ignoring it) Full spec can be found [here](https://github.com/RazrFalcon/usvg/blob/master/docs/usvg_spec.adoc). ## Limitations - Currently, it's not lossless. Some SVG features isn't supported yet and will be ignored. - CSS support is minimal. - Scripting and animation isn't supported and not planned. - `a` elements will be removed. - Unsupported elements: - some filter-based elements - font-based elements [SVG]: https://en.wikipedia.org/wiki/Scalable_Vector_Graphics */ #![doc(html_root_url = "https://docs.rs/usvg/0.8.0")] #![warn(missing_docs)] #![warn(missing_debug_implementations)] #![warn(missing_copy_implementations)] /// Unwraps `Option` and invokes `return` on `None`. macro_rules! try_opt { ($task:expr) => { match $task { Some(v) => v, None => return, } }; } /// Unwraps `Option` and invokes `return $ret` on `None`. macro_rules! try_opt_or { ($task:expr, $ret:expr) => { match $task { Some(v) => v, None => return $ret, } }; } /// Unwraps `Option` and invokes `return` on `None` with a warning. macro_rules! try_opt_warn { ($task:expr, $msg:expr) => { match $task { Some(v) => v, None => { log::warn!($msg); return; } } }; ($task:expr, $fmt:expr, $($arg:tt)*) => { match $task { Some(v) => v, None => { log::warn!($fmt, $($arg)*); return; } } }; } /// Unwraps `Option` and invokes `return $ret` on `None` with a warning. macro_rules! try_opt_warn_or { ($task:expr, $ret:expr, $msg:expr) => { match $task { Some(v) => v, None => { log::warn!($msg); return $ret; } } }; ($task:expr, $ret:expr, $fmt:expr, $($arg:tt)*) => { match $task { Some(v) => v, None => { log::warn!($fmt, $($arg)*); return $ret; } } }; } macro_rules! impl_enum_default { ($name:ident, $def_value:ident) => { impl Default for $name { #[inline] fn default() -> Self { $name::$def_value } } }; } macro_rules! impl_enum_from_str { ($name:ident, $($string:pat => $result:expr),+) => { impl crate::svgtree::EnumFromStr for $name { fn enum_from_str(s: &str) -> Option { match s { $($string => Some($result)),+, _ => None, } } } }; } macro_rules! matches { ($expression:expr, $($pattern:tt)+) => { match $expression { $($pattern)+ => true, _ => false } } } pub mod utils; mod convert; mod error; mod fontdb; mod geom; mod options; mod svgtree; mod tree; /// Shorthand names for modules. mod short { pub use svgtypes::LengthUnit as Unit; } pub use xmlwriter::Options as XmlOptions; pub use xmlwriter::Indent as XmlIndent; pub use crate::error::*; pub use crate::geom::*; pub use crate::options::*; pub use crate::tree::*; /// Checks that type has a default value. pub trait IsDefault: Default { /// Checks that type has a default value. fn is_default(&self) -> bool; } impl IsDefault for T { #[inline] fn is_default(&self) -> bool { *self == Self::default() } } /// Checks that the current number is > 0. pub trait IsValidLength { /// Checks that the current number is > 0. fn is_valid_length(&self) -> bool; } impl IsValidLength for f64 { #[inline] fn is_valid_length(&self) -> bool { *self > 0.0 } } /// Converts `Rect` into bbox `Transform`. pub trait TransformFromBBox: Sized { /// Converts `Rect` into bbox `Transform`. fn from_bbox(bbox: Rect) -> Self; } impl TransformFromBBox for tree::Transform { #[inline] fn from_bbox(bbox: Rect) -> Self { Self::new(bbox.width(), 0.0, 0.0, bbox.height(), bbox.x(), bbox.y()) } } resvg-0.8.0/usvg/src/options.rs000066400000000000000000000041521352576375700165070ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::path::PathBuf; use crate::{ ImageRendering, ShapeRendering, TextRendering, }; /// Processing options. #[derive(Clone, Debug)] pub struct Options { /// SVG image path. /// /// Used to resolve relative image paths. pub path: Option, /// Target DPI. /// /// Impact units conversion. pub dpi: f64, /// A default font family. pub font_family: String, /// A default font size. pub font_size: f64, /// A list of languages that will be used to resolve the `systemLanguage` /// conditional attribute. /// /// Format: en, en-US. pub languages: Vec, /// Specifies the default shape rendering method. /// /// Will be used when an SVG element's `shape-rendering` property is set to `auto`. pub shape_rendering: ShapeRendering, /// Specifies the default text rendering method. /// /// Will be used when an SVG element's `text-rendering` property is set to `auto`. pub text_rendering: TextRendering, /// Specifies the default image rendering method. /// /// Will be used when an SVG element's `image-rendering` property is set to `auto`. pub image_rendering: ImageRendering, /// Keep named groups. /// /// If set to `true`, all non-empty groups with `id` attribute will not /// be removed. pub keep_named_groups: bool, } impl Default for Options { fn default() -> Options { Options { path: None, dpi: 96.0, // Default font is user-agent dependent so we can use whatever we like. font_family: "Times New Roman".to_owned(), font_size: 12.0, languages: vec!["en".to_string()], shape_rendering: ShapeRendering::default(), text_rendering: TextRendering::default(), image_rendering: ImageRendering::default(), keep_named_groups: false, } } } resvg-0.8.0/usvg/src/svgtree/000077500000000000000000000000001352576375700161235ustar00rootroot00000000000000resvg-0.8.0/usvg/src/svgtree/mod.rs000066400000000000000000000542741352576375700172640ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::fmt; use std::collections::HashMap; use log::warn; use svgtypes::FuzzyEq; use crate::geom::Rect; use crate::tree; mod parse; pub use parse::*; mod names; pub use names::*; type Range = std::ops::Range; pub struct Document { nodes: Vec, attrs: Vec, links: HashMap, } impl Document { #[inline] pub fn root(&self) -> Node { Node { id: NodeId(0), d: &self.nodes[0], doc: self } } pub fn root_element(&self) -> Node { // `unwrap` is safe, because `Document` is guarantee to have at least one element. self.root().first_element_child().unwrap() } pub fn descendants(&self) -> Descendants { self.root().descendants() } #[inline] pub fn element_by_id(&self, id: &str) -> Option { let node_id = self.links.get(id)?; Some(self.get(*node_id)) } #[inline] pub fn get(&self, id: NodeId) -> Node { Node { id, d: &self.nodes[id.0], doc: self } } } impl fmt::Debug for Document { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { if !self.root().has_children() { return write!(f, "Document []"); } macro_rules! writeln_indented { ($depth:expr, $f:expr, $fmt:expr) => { for _ in 0..$depth { write!($f, " ")?; } writeln!($f, $fmt)?; }; ($depth:expr, $f:expr, $fmt:expr, $($arg:tt)*) => { for _ in 0..$depth { write!($f, " ")?; } writeln!($f, $fmt, $($arg)*)?; }; } fn print_children(parent: Node, depth: usize, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { for child in parent.children() { if child.is_element() { writeln_indented!(depth, f, "Element {{"); writeln_indented!(depth, f, " tag_name: {:?}", child.tag_name()); if !child.attributes().is_empty() { writeln_indented!(depth + 1, f, "attributes: ["); for attr in child.attributes() { writeln_indented!(depth + 2, f, "{:?}", attr); } writeln_indented!(depth + 1, f, "]"); } if child.has_children() { writeln_indented!(depth, f, " children: ["); print_children(child, depth + 2, f)?; writeln_indented!(depth, f, " ]"); } writeln_indented!(depth, f, "}}"); } else { writeln_indented!(depth, f, "{:?}", child); } } Ok(()) } writeln!(f, "Document [")?; print_children(self.root(), 1, f)?; writeln!(f, "]")?; Ok(()) } } #[derive(Clone, Copy, PartialEq, Debug)] pub struct NodeId(usize); #[derive(Clone, Copy, PartialEq, Debug)] struct AttributeId(usize); enum NodeKind { Root, Element { tag_name: EId, attributes: Range, }, Text(String), } struct NodeData { parent: Option, prev_sibling: Option, next_sibling: Option, children: Option<(NodeId, NodeId)>, kind: NodeKind, } #[derive(Clone, Debug)] pub enum AttributeValue { None, CurrentColor, Angle(svgtypes::Angle), AspectRatio(svgtypes::AspectRatio), Color(svgtypes::Color), Length(svgtypes::Length), Link(String), Number(f64), NumberList(svgtypes::NumberList), Opacity(tree::Opacity), Paint(String, Option), Path(tree::SharedPathData), String(String), Transform(svgtypes::Transform), ViewBox(svgtypes::ViewBox), } #[derive(Clone)] pub struct Attribute { pub name: AId, pub value: AttributeValue, } impl fmt::Debug for Attribute { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { write!(f, "Attribute {{ name: {:?}, value: {:?} }}", self.name, self.value) } } #[derive(Clone, Copy)] pub struct Node<'a> { id: NodeId, doc: &'a Document, d: &'a NodeData, } impl Eq for Node<'_> {} impl PartialEq for Node<'_> { #[inline] fn eq(&self, other: &Self) -> bool { self.id == other.id && self.doc as *const _ == other.doc as *const _ && self.d as *const _ == other.d as *const _ } } impl<'a> Node<'a> { #[inline] pub fn id(&self) -> NodeId { self.id } #[allow(dead_code)] #[inline] pub fn is_root(&self) -> bool { match self.d.kind { NodeKind::Root => true, _ => false, } } #[inline] pub fn is_element(&self) -> bool { match self.d.kind { NodeKind::Element { .. } => true, _ => false, } } #[inline] pub fn is_text(&self) -> bool { match self.d.kind { NodeKind::Text(_) => true, _ => false, } } #[inline] pub fn document(&self) -> &'a Document { self.doc } #[inline] pub fn tag_name(&self) -> Option { match self.d.kind { NodeKind::Element { tag_name, .. } => Some(tag_name), _ => None, } } #[inline] pub fn has_tag_name(&self, name: EId) -> bool { match self.d.kind { NodeKind::Element { tag_name, .. } => tag_name == name, _ => false, } } pub fn element_id(&self) -> &str { self.attribute(AId::Id).unwrap_or("") } #[inline(never)] pub fn attribute>(&self, aid: AId) -> Option { FromValue::get(*self, aid) } pub fn has_attribute(&self, aid: AId) -> bool { self.attributes().iter().any(|a| a.name == aid) } pub fn attributes(&self) -> &'a [Attribute] { match self.d.kind { NodeKind::Element { ref attributes, .. } => &self.doc.attrs[attributes.clone()], _ => &[], } } fn attribute_id(&self, aid: AId) -> Option { match self.d.kind { NodeKind::Element { ref attributes, .. } => { let idx = self.attributes().iter().position(|attr| attr.name == aid)?; Some(AttributeId(attributes.start + idx)) } _ => None, } } pub fn find_attribute>(&self, aid: AId) -> Option { self.find_attribute_impl(aid).and_then(|n| n.attribute(aid)) } fn find_attribute_impl(&self, aid: AId) -> Option> { if aid.is_inheritable() { for n in self.ancestors() { if n.has_attribute(aid) { return Some(n); } } None } else { if self.has_attribute(aid) { Some(*self) } else { let n = self.parent_element()?; if n.has_attribute(aid) { Some(n) } else { None } } } } pub fn find_node_with_attribute(&self, aid: AId) -> Option { for n in self.ancestors() { if n.has_attribute(aid) { return Some(n); } } None } pub fn has_valid_transform(&self, aid: AId) -> bool { // Do not use Node::attribute::, because it will always // return a valid transform. let attr = match self.attributes().iter().find(|a| a.name == aid) { Some(attr) => attr, None => return true, }; if let AttributeValue::Transform(ref ts) = attr.value { let (sx, sy) = ts.get_scale(); if sx.fuzzy_eq(&0.0) || sy.fuzzy_eq(&0.0) { return false; } } true } pub fn get_viewbox(&self) -> Option { let vb: svgtypes::ViewBox = self.attribute(AId::ViewBox)?; Rect::new(vb.x, vb.y, vb.w, vb.h) } pub fn text(&self) -> &'a str { match self.d.kind { NodeKind::Element { .. } => { match self.first_child() { Some(child) if child.is_text() => { match self.doc.nodes[child.id.0].kind { NodeKind::Text(ref text) => text, _ => "" } } _ => "", } } NodeKind::Text(ref text) => text, _ => "", } } #[inline] fn gen_node(&self, id: NodeId) -> Node<'a> { Node { id, d: &self.doc.nodes[id.0], doc: self.doc } } pub fn parent(&self) -> Option { self.d.parent.map(|id| self.gen_node(id)) } pub fn parent_element(&self) -> Option { self.ancestors().skip(1).filter(|n| n.is_element()).nth(0) } pub fn prev_sibling(&self) -> Option { self.d.prev_sibling.map(|id| self.gen_node(id)) } pub fn next_sibling(&self) -> Option { self.d.next_sibling.map(|id| self.gen_node(id)) } pub fn first_child(&self) -> Option { self.d.children.map(|(id, _)| self.gen_node(id)) } pub fn first_element_child(&self) -> Option { self.children().filter(|n| n.is_element()).nth(0) } pub fn last_child(&self) -> Option { self.d.children.map(|(_, id)| self.gen_node(id)) } pub fn has_children(&self) -> bool { self.d.children.is_some() } /// Returns an iterator over ancestor nodes starting at this node. pub fn ancestors(&self) -> Ancestors<'a> { Ancestors(Some(*self)) } /// Returns an iterator over children nodes. pub fn children(&self) -> Children<'a> { Children { front: self.first_child(), back: self.last_child() } } /// Returns an iterator which traverses the subtree starting at this node. pub fn traverse(&self) -> Traverse<'a> { Traverse { root: *self, edge: None } } /// Returns an iterator over this node and its descendants. pub fn descendants(&self) -> Descendants<'a> { Descendants(self.traverse()) } pub fn href_iter(&self) -> HrefIter { HrefIter { doc: self.document(), origin: self.id(), curr: self.id(), is_first: true, is_finished: false, } } } impl fmt::Debug for Node<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { match self.d.kind { NodeKind::Root => write!(f, "Root"), NodeKind::Element { .. } => { write!(f, "Element {{ tag_name: {:?}, attributes: {:?} }}", self.tag_name(), self.attributes()) } NodeKind::Text(ref text) => write!(f, "Text({:?})", text), } } } macro_rules! axis_iterators { ($($i:ident($f:path);)*) => { $( #[derive(Clone)] pub struct $i<'a>(Option>); impl<'a> Iterator for $i<'a> { type Item = Node<'a>; #[inline] fn next(&mut self) -> Option { let node = self.0.take(); self.0 = node.as_ref().and_then($f); node } } )* }; } axis_iterators! { Ancestors(Node::parent); PrevSiblings(Node::prev_sibling); NextSiblings(Node::next_sibling); } #[derive(Clone)] pub struct Children<'a> { front: Option>, back: Option>, } impl<'a> Iterator for Children<'a> { type Item = Node<'a>; fn next(&mut self) -> Option { if self.front == self.back { let node = self.front.take(); self.back = None; node } else { let node = self.front.take(); self.front = node.as_ref().and_then(Node::next_sibling); node } } } impl<'a> DoubleEndedIterator for Children<'a> { fn next_back(&mut self) -> Option { if self.back == self.front { let node = self.back.take(); self.front = None; node } else { let node = self.back.take(); self.back = node.as_ref().and_then(Node::prev_sibling); node } } } #[derive(Clone, Copy, PartialEq, Debug)] pub enum Edge<'a> { Open(Node<'a>), Close(Node<'a>), } #[derive(Clone)] pub struct Traverse<'a> { root: Node<'a>, edge: Option>, } impl<'a> Iterator for Traverse<'a> { type Item = Edge<'a>; fn next(&mut self) -> Option { match self.edge { Some(Edge::Open(node)) => { self.edge = Some(match node.first_child() { Some(first_child) => Edge::Open(first_child), None => Edge::Close(node), }); } Some(Edge::Close(node)) => { if node == self.root { self.edge = None; } else if let Some(next_sibling) = node.next_sibling() { self.edge = Some(Edge::Open(next_sibling)); } else { self.edge = node.parent().map(Edge::Close); } } None => { self.edge = Some(Edge::Open(self.root)); } } self.edge } } #[derive(Clone)] pub struct Descendants<'a>(Traverse<'a>); impl<'a> Iterator for Descendants<'a> { type Item = Node<'a>; #[inline] fn next(&mut self) -> Option { for edge in &mut self.0 { if let Edge::Open(node) = edge { return Some(node); } } None } } pub struct HrefIter<'a> { doc: &'a Document, origin: NodeId, curr: NodeId, is_first: bool, is_finished: bool, } impl<'a> Iterator for HrefIter<'a> { type Item = NodeId; fn next(&mut self) -> Option { if self.is_finished { return None; } if self.is_first { self.is_first = false; return Some(self.curr); } if let Some(link) = self.doc.get(self.curr).attribute::(AId::Href) { if link.id() == self.curr || link.id() == self.origin { warn!( "Element '#{}' cannot reference itself via 'xlink:href'.", self.doc.get(self.origin).element_id() ); self.is_finished = true; return None; } self.curr = link.id(); Some(link.id()) } else { None } } } pub trait FromValue<'a>: Sized { fn get(node: Node<'a>, aid: AId) -> Option; } macro_rules! impl_from_value { ($rtype:ty, $etype:ident) => ( impl FromValue<'_> for $rtype { fn get(node: Node, aid: AId) -> Option { let a = node.attributes().iter().find(|a| a.name == aid)?; if let AttributeValue::$etype(ref v) = a.value { Some(*v) } else { None } } } ) } impl_from_value!(svgtypes::Color, Color); impl_from_value!(svgtypes::Length, Length); impl_from_value!(svgtypes::ViewBox, ViewBox); impl_from_value!(svgtypes::AspectRatio, AspectRatio); impl_from_value!(svgtypes::Angle, Angle); impl_from_value!(f64, Number); impl_from_value!(tree::Opacity, Opacity); impl<'a> FromValue<'a> for &'a AttributeValue { fn get(node: Node<'a>, aid: AId) -> Option { node.attributes().iter().find(|a| a.name == aid).map(|a| &a.value) } } impl<'a> FromValue<'a> for svgtypes::Transform { fn get(node: Node<'a>, aid: AId) -> Option { let a = node.attributes().iter().find(|a| a.name == aid)?; let ts = match a.value { AttributeValue::Transform(ref ts) => ts, _ => return None, }; let (sx, sy) = ts.get_scale(); if sx.fuzzy_eq(&0.0) || sy.fuzzy_eq(&0.0) { Some(svgtypes::Transform::default()) } else { Some(*ts) } } } impl FromValue<'_> for tree::SharedPathData { fn get(node: Node, aid: AId) -> Option { let a = node.attributes().iter().find(|a| a.name == aid)?; // Cloning is cheap, since it's a Rc. if let AttributeValue::Path(ref v) = a.value { Some(v.clone()) } else { None } } } impl<'a> FromValue<'a> for &'a svgtypes::NumberList { fn get(node: Node<'a>, aid: AId) -> Option { let a = node.attributes().iter().find(|a| a.name == aid)?; if let AttributeValue::NumberList(ref v) = a.value { Some(v) } else { None } } } impl<'a> FromValue<'a> for &'a str { fn get(node: Node<'a>, aid: AId) -> Option { let a = node.attributes().iter().find(|a| a.name == aid)?; match a.value { AttributeValue::None => { // A special case, because matching `None` is too verbose. // // match node.attribute(AId::Display) { // Some(&svgtree::AttributeValue::None) => true, // None => false, // } // // vs // // node.attribute(AId::Display) == Some("none") Some("none") } AttributeValue::String(ref v) => Some(v.as_str()), _ => None, } } } impl<'a> FromValue<'a> for Node<'a> { fn get(node: Node<'a>, aid: AId) -> Option { let a = node.attributes().iter().find(|a| a.name == aid)?; let id = match a.value { AttributeValue::Link(ref id) => id, _ => return None, }; node.document().element_by_id(&id) } } pub trait EnumFromStr: Sized { fn enum_from_str(text: &str) -> Option; } impl<'a, T: EnumFromStr> FromValue<'a> for T { #[inline] fn get(node: Node, aid: AId) -> Option { EnumFromStr::enum_from_str(node.attribute(aid)?) } } impl EId { pub fn is_graphic(&self) -> bool { matches!(self, EId::Circle | EId::Ellipse | EId::Image | EId::Line | EId::Path | EId::Polygon | EId::Polyline | EId::Rect | EId::Text | EId::Use ) } pub fn is_gradient(&self) -> bool { matches!(self, EId::LinearGradient | EId::RadialGradient ) } pub fn is_paint_server(&self) -> bool { matches!(self, EId::LinearGradient | EId::RadialGradient | EId::Pattern ) } } impl AId { pub fn is_presentation(&self) -> bool { matches!(self, AId::BaselineShift | AId::ClipPath | AId::ClipRule | AId::Color | AId::ColorInterpolationFilters | AId::Direction | AId::Display | AId::Fill | AId::FillOpacity | AId::FillRule | AId::Filter | AId::FloodColor | AId::FloodOpacity | AId::FontFamily | AId::FontSize | AId::FontStretch | AId::FontStyle | AId::FontVariant | AId::FontWeight | AId::ImageRendering | AId::LetterSpacing | AId::MarkerEnd | AId::MarkerMid | AId::MarkerStart | AId::Mask | AId::Opacity | AId::Overflow | AId::ShapeRendering | AId::StopColor | AId::StopOpacity | AId::Stroke | AId::StrokeDasharray | AId::StrokeDashoffset | AId::StrokeLinecap | AId::StrokeLinejoin | AId::StrokeMiterlimit | AId::StrokeOpacity | AId::StrokeWidth | AId::TextAnchor | AId::TextDecoration | AId::TextRendering | AId::Visibility | AId::WordSpacing | AId::WritingMode) } pub fn is_inheritable(&self) -> bool { if self.is_presentation() { !is_non_inheritable(*self) } else { false } } pub fn allows_inherit_value(&self) -> bool { matches!(self, AId::BaselineShift | AId::ClipPath | AId::ClipRule | AId::Color | AId::ColorInterpolationFilters | AId::Direction | AId::Display | AId::Fill | AId::FillOpacity | AId::FillRule | AId::Filter | AId::FloodColor | AId::FloodOpacity | AId::FontFamily | AId::FontSize | AId::FontStretch | AId::FontStyle | AId::FontVariant | AId::FontWeight | AId::ImageRendering | AId::LetterSpacing | AId::MarkerEnd | AId::MarkerMid | AId::MarkerStart | AId::Mask | AId::Opacity | AId::Overflow | AId::ShapeRendering | AId::StopColor | AId::StopOpacity | AId::Stroke | AId::StrokeDasharray | AId::StrokeDashoffset | AId::StrokeLinecap | AId::StrokeLinejoin | AId::StrokeMiterlimit | AId::StrokeOpacity | AId::StrokeWidth | AId::TextAnchor | AId::TextDecoration | AId::TextRendering | AId::Visibility | AId::WordSpacing | AId::WritingMode) } } fn is_non_inheritable(id: AId) -> bool { matches!(id, AId::BaselineShift | AId::ClipPath | AId::Display | AId::Filter | AId::FloodColor | AId::FloodOpacity | AId::Mask | AId::Opacity | AId::Overflow | AId::StopColor | AId::StopOpacity | AId::TextDecoration | AId::Visibility) } resvg-0.8.0/usvg/src/svgtree/names.rs000066400000000000000000000272111352576375700175770ustar00rootroot00000000000000// This file is autogenerated. Do not edit it! // See ./codegen for details. use std::fmt; #[derive(Clone, Copy, PartialEq)] pub enum EId { A, Circle, ClipPath, Defs, Ellipse, FeBlend, FeColorMatrix, FeComponentTransfer, FeComposite, FeConvolveMatrix, FeDiffuseLighting, FeDisplacementMap, FeDistantLight, FeFlood, FeFuncA, FeFuncB, FeFuncG, FeFuncR, FeGaussianBlur, FeImage, FeMerge, FeMergeNode, FeMorphology, FeOffset, FePointLight, FeSpecularLighting, FeSpotLight, FeTile, FeTurbulence, Filter, G, Image, Line, LinearGradient, Marker, Mask, Path, Pattern, Polygon, Polyline, RadialGradient, Rect, Stop, Style, Svg, Switch, Symbol, Text, TextPath, Tref, Tspan, Use } static ELEMENTS: Map = Map { key: 3558916427560184125, disps: &[ (1, 13), (5, 3), (0, 19), (6, 24), (0, 0), (0, 3), (0, 1), (11, 10), (0, 21), (0, 0), (5, 0), ], entries: &[ ("feConvolveMatrix", EId::FeConvolveMatrix), ("tspan", EId::Tspan), ("style", EId::Style), ("feFuncB", EId::FeFuncB), ("rect", EId::Rect), ("marker", EId::Marker), ("feDiffuseLighting", EId::FeDiffuseLighting), ("g", EId::G), ("symbol", EId::Symbol), ("pattern", EId::Pattern), ("path", EId::Path), ("feFuncR", EId::FeFuncR), ("a", EId::A), ("textPath", EId::TextPath), ("use", EId::Use), ("feFuncA", EId::FeFuncA), ("tref", EId::Tref), ("circle", EId::Circle), ("fePointLight", EId::FePointLight), ("defs", EId::Defs), ("feTile", EId::FeTile), ("image", EId::Image), ("stop", EId::Stop), ("feGaussianBlur", EId::FeGaussianBlur), ("feFlood", EId::FeFlood), ("polyline", EId::Polyline), ("feComponentTransfer", EId::FeComponentTransfer), ("linearGradient", EId::LinearGradient), ("feFuncG", EId::FeFuncG), ("ellipse", EId::Ellipse), ("clipPath", EId::ClipPath), ("feMerge", EId::FeMerge), ("feSpotLight", EId::FeSpotLight), ("feBlend", EId::FeBlend), ("svg", EId::Svg), ("feColorMatrix", EId::FeColorMatrix), ("feTurbulence", EId::FeTurbulence), ("feSpecularLighting", EId::FeSpecularLighting), ("switch", EId::Switch), ("feMorphology", EId::FeMorphology), ("feImage", EId::FeImage), ("feMergeNode", EId::FeMergeNode), ("feOffset", EId::FeOffset), ("polygon", EId::Polygon), ("feComposite", EId::FeComposite), ("radialGradient", EId::RadialGradient), ("line", EId::Line), ("feDisplacementMap", EId::FeDisplacementMap), ("feDistantLight", EId::FeDistantLight), ("mask", EId::Mask), ("text", EId::Text), ("filter", EId::Filter), ], }; impl EId { pub fn from_str(text: &str) -> Option { ELEMENTS.get(text).cloned() } #[inline(never)] pub fn to_str(&self) -> &'static str { ELEMENTS.key(self) } } impl fmt::Debug for EId { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.to_str()) } } impl fmt::Display for EId { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) } } #[derive(Clone, Copy, PartialEq)] pub enum AId { Amplitude, BaselineShift, Class, ClipPath, ClipRule, ClipPathUnits, Color, ColorInterpolationFilters, Cx, Cy, D, Direction, Display, Dx, Dy, Exponent, Fill, FillOpacity, FillRule, Filter, FilterUnits, FloodColor, FloodOpacity, FontFamily, FontSize, FontStretch, FontStyle, FontVariant, FontWeight, Fx, Fy, GradientTransform, GradientUnits, Height, Href, Id, ImageRendering, In, In2, Intercept, K1, K2, K3, K4, LetterSpacing, MarkerEnd, MarkerMid, MarkerStart, MarkerHeight, MarkerUnits, MarkerWidth, Mask, MaskContentUnits, MaskUnits, Mode, Offset, Opacity, Operator, Orient, Overflow, PatternContentUnits, PatternTransform, PatternUnits, Points, PreserveAspectRatio, PrimitiveUnits, R, RefX, RefY, RequiredExtensions, RequiredFeatures, Result, Rotate, Rx, Ry, ShapeRendering, Slope, Space, SpreadMethod, StartOffset, StdDeviation, StopColor, StopOpacity, Stroke, StrokeDasharray, StrokeDashoffset, StrokeLinecap, StrokeLinejoin, StrokeMiterlimit, StrokeOpacity, StrokeWidth, Style, SystemLanguage, TableValues, TextAnchor, TextDecoration, TextRendering, Transform, Type, Values, ViewBox, Visibility, Width, WordSpacing, WritingMode, X, X1, X2, Y, Y1, Y2 } static ATTRIBUTES: Map = Map { key: 3213172566270843353, disps: &[ (0, 27), (0, 85), (3, 38), (0, 0), (0, 1), (3, 53), (1, 25), (0, 20), (9, 110), (2, 1), (0, 12), (0, 23), (3, 13), (0, 19), (1, 39), (2, 9), (0, 2), (0, 0), (11, 32), (46, 47), (6, 77), (0, 10), (10, 8), ], entries: &[ ("transform", AId::Transform), ("in2", AId::In2), ("k1", AId::K1), ("word-spacing", AId::WordSpacing), ("id", AId::Id), ("d", AId::D), ("cy", AId::Cy), ("marker-mid", AId::MarkerMid), ("slope", AId::Slope), ("stroke-dashoffset", AId::StrokeDashoffset), ("text-rendering", AId::TextRendering), ("stop-opacity", AId::StopOpacity), ("requiredExtensions", AId::RequiredExtensions), ("baseline-shift", AId::BaselineShift), ("preserveAspectRatio", AId::PreserveAspectRatio), ("cx", AId::Cx), ("stroke-miterlimit", AId::StrokeMiterlimit), ("letter-spacing", AId::LetterSpacing), ("flood-color", AId::FloodColor), ("stop-color", AId::StopColor), ("clip-path", AId::ClipPath), ("ry", AId::Ry), ("clipPathUnits", AId::ClipPathUnits), ("stroke-linecap", AId::StrokeLinecap), ("fx", AId::Fx), ("values", AId::Values), ("color", AId::Color), ("fill-rule", AId::FillRule), ("stroke", AId::Stroke), ("visibility", AId::Visibility), ("font-style", AId::FontStyle), ("y1", AId::Y1), ("stroke-linejoin", AId::StrokeLinejoin), ("refY", AId::RefY), ("rotate", AId::Rotate), ("systemLanguage", AId::SystemLanguage), ("stroke-dasharray", AId::StrokeDasharray), ("startOffset", AId::StartOffset), ("points", AId::Points), ("r", AId::R), ("font-family", AId::FontFamily), ("href", AId::Href), ("requiredFeatures", AId::RequiredFeatures), ("gradientTransform", AId::GradientTransform), ("font-variant", AId::FontVariant), ("fy", AId::Fy), ("font-stretch", AId::FontStretch), ("text-decoration", AId::TextDecoration), ("filter", AId::Filter), ("maskUnits", AId::MaskUnits), ("marker-end", AId::MarkerEnd), ("in", AId::In), ("viewBox", AId::ViewBox), ("patternUnits", AId::PatternUnits), ("space", AId::Space), ("fill", AId::Fill), ("stroke-width", AId::StrokeWidth), ("offset", AId::Offset), ("filterUnits", AId::FilterUnits), ("x", AId::X), ("gradientUnits", AId::GradientUnits), ("primitiveUnits", AId::PrimitiveUnits), ("spreadMethod", AId::SpreadMethod), ("operator", AId::Operator), ("font-weight", AId::FontWeight), ("x2", AId::X2), ("refX", AId::RefX), ("stdDeviation", AId::StdDeviation), ("patternContentUnits", AId::PatternContentUnits), ("height", AId::Height), ("marker-start", AId::MarkerStart), ("tableValues", AId::TableValues), ("class", AId::Class), ("maskContentUnits", AId::MaskContentUnits), ("flood-opacity", AId::FloodOpacity), ("markerHeight", AId::MarkerHeight), ("style", AId::Style), ("writing-mode", AId::WritingMode), ("type", AId::Type), ("patternTransform", AId::PatternTransform), ("dx", AId::Dx), ("markerWidth", AId::MarkerWidth), ("amplitude", AId::Amplitude), ("y", AId::Y), ("mask", AId::Mask), ("stroke-opacity", AId::StrokeOpacity), ("exponent", AId::Exponent), ("fill-opacity", AId::FillOpacity), ("k2", AId::K2), ("text-anchor", AId::TextAnchor), ("shape-rendering", AId::ShapeRendering), ("display", AId::Display), ("font-size", AId::FontSize), ("direction", AId::Direction), ("rx", AId::Rx), ("orient", AId::Orient), ("width", AId::Width), ("mode", AId::Mode), ("result", AId::Result), ("opacity", AId::Opacity), ("overflow", AId::Overflow), ("dy", AId::Dy), ("k4", AId::K4), ("markerUnits", AId::MarkerUnits), ("color-interpolation-filters", AId::ColorInterpolationFilters), ("x1", AId::X1), ("clip-rule", AId::ClipRule), ("image-rendering", AId::ImageRendering), ("y2", AId::Y2), ("k3", AId::K3), ("intercept", AId::Intercept), ], }; impl AId { pub fn from_str(text: &str) -> Option { ATTRIBUTES.get(text).cloned() } #[inline(never)] pub fn to_str(&self) -> &'static str { ATTRIBUTES.key(self) } } impl fmt::Debug for AId { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.to_str()) } } impl fmt::Display for AId { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) } } // A stripped down `phf` crate fork. // // https://github.com/sfackler/rust-phf struct Map { pub key: u64, pub disps: &'static [(u32, u32)], pub entries: &'static[(&'static str, V)], } impl Map { fn get(&self, key: &str) -> Option<&V> { use std::borrow::Borrow; let hash = hash(key, self.key); let index = get_index(hash, &*self.disps, self.entries.len()); let entry = &self.entries[index as usize]; let b = entry.0.borrow(); if b == key { Some(&entry.1) } else { None } } fn key(&self, value: &V) -> &'static str { self.entries.iter().find(|kv| kv.1 == *value).unwrap().0 } } #[inline] fn hash(x: &str, key: u64) -> u64 { use std::hash::Hasher; let mut hasher = siphasher::sip::SipHasher13::new_with_keys(0, key); hasher.write(x.as_bytes()); hasher.finish() } #[inline] fn get_index(hash: u64, disps: &[(u32, u32)], len: usize) -> u32 { let (g, f1, f2) = split(hash); let (d1, d2) = disps[(g % (disps.len() as u32)) as usize]; displace(f1, f2, d1, d2) % (len as u32) } #[inline] fn split(hash: u64) -> (u32, u32, u32) { const BITS: u32 = 21; const MASK: u64 = (1 << BITS) - 1; ((hash & MASK) as u32, ((hash >> BITS) & MASK) as u32, ((hash >> (2 * BITS)) & MASK) as u32) } #[inline] fn displace(f1: u32, f2: u32, d1: u32, d2: u32) -> u32 { d2 + f1 * d1 + f2 } resvg-0.8.0/usvg/src/svgtree/parse.rs000066400000000000000000001260441352576375700176120ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::rc::Rc; use std::str::FromStr; use std::collections::HashMap; use log::warn; pub use roxmltree::Error; use crate::tree; use super::{Document, Attribute, AId, EId, Node, NodeId, NodeKind, NodeData, AttributeValue}; const SVG_NS: &str = "http://www.w3.org/2000/svg"; const XLINK_NS: &str = "http://www.w3.org/1999/xlink"; const XML_NAMESPACE_NS: &str = "http://www.w3.org/XML/1998/namespace"; impl Document { pub fn parse(text: &str) -> Result { parse(text) } fn append(&mut self, parent_id: NodeId, kind: NodeKind) -> NodeId { let new_child_id = NodeId(self.nodes.len()); self.nodes.push(NodeData { parent: Some(parent_id), prev_sibling: None, next_sibling: None, children: None, kind, }); let last_child_id = self.nodes[parent_id.0].children.map(|(_, id)| id); self.nodes[new_child_id.0].prev_sibling = last_child_id; if let Some(id) = last_child_id { self.nodes[id.0].next_sibling = Some(new_child_id); } self.nodes[parent_id.0].children = Some( if let Some((first_child_id, _)) = self.nodes[parent_id.0].children { (first_child_id, new_child_id) } else { (new_child_id, new_child_id) } ); new_child_id } fn append_attribute(&mut self, tag_name: EId, aid: AId, value: &str) { let value2 = parse_svg_attribute(tag_name, aid, value); if let Ok(value) = value2 { self.attrs.push(Attribute { name: aid, value, }); } else { warn!("Failed to parse {} value: '{}'.", aid, value); } } } fn parse(text: &str) -> Result { let xml = roxmltree::Document::parse(text)?; let mut doc = Document { nodes: Vec::new(), attrs: Vec::new(), links: HashMap::new(), }; // Add a root node. doc.nodes.push(NodeData { parent: None, prev_sibling: None, next_sibling: None, children: None, kind: NodeKind::Root, }); let style_sheet = resolve_css(&xml); parse_xml_node_children(xml.root(), xml.root(), doc.root().id, &style_sheet, false, &mut doc); // Check that the root element is `svg`. match doc.root().first_element_child() { Some(child) => { if child.tag_name() != Some(EId::Svg) { return Err(Error::NoRootNode) } } None => return Err(Error::NoRootNode), } // Collect all elements with `id` attribute. let mut links = HashMap::new(); for node in doc.descendants() { if let Some(id) = node.attribute::<&str>(AId::Id) { links.insert(id.to_string(), node.id); } } doc.links = links; fix_recursive_patterns(&mut doc); fix_recursive_links(EId::ClipPath, AId::ClipPath, &mut doc); fix_recursive_links(EId::Mask, AId::Mask, &mut doc); fix_recursive_links(EId::Filter, AId::Filter, &mut doc); Ok(doc) } fn parse_tag_name(node: roxmltree::Node) -> Option { if !node.is_element() { return None; } if node.tag_name().namespace() != Some(SVG_NS) { return None; } EId::from_str(node.tag_name().name()) } fn parse_xml_node_children( parent: roxmltree::Node, origin: roxmltree::Node, parent_id: NodeId, style_sheet: &simplecss::StyleSheet, ignore_ids: bool, doc: &mut Document, ) { for node in parent.children() { parse_xml_node(node, origin, parent_id, style_sheet, ignore_ids, doc); } } fn parse_xml_node( node: roxmltree::Node, origin: roxmltree::Node, parent_id: NodeId, style_sheet: &simplecss::StyleSheet, ignore_ids: bool, doc: &mut Document, ) { let mut tag_name = match parse_tag_name(node) { Some(id) => id, None => return, }; if tag_name == EId::Style { return; } // Treat links as groups. if tag_name == EId::A { tag_name = EId::G; } let node_id = parse_svg_element(node, parent_id, tag_name, style_sheet, ignore_ids, doc); if tag_name == EId::Text { parse_svg_text_element(node, node_id, style_sheet, doc); } else if tag_name == EId::Use { parse_svg_use_element(node, origin, node_id, style_sheet, doc); } else { parse_xml_node_children(node, origin, node_id, style_sheet, ignore_ids, doc); } } fn parse_svg_element( xml_node: roxmltree::Node, parent_id: NodeId, tag_name: EId, style_sheet: &simplecss::StyleSheet, ignore_ids: bool, doc: &mut Document, ) -> NodeId { let attrs_start_idx = doc.attrs.len(); // Copy presentational attributes first. for attr in xml_node.attributes() { match attr.namespace() { None | Some(SVG_NS) | Some(XLINK_NS) | Some(XML_NAMESPACE_NS) => {} _ => continue, } let aid = match AId::from_str(attr.name()) { Some(id) => id, None => continue, }; // During a `use` resolving, all `id` attributes must be ignored. // Otherwise we will get elements with duplicated id's. if ignore_ids && aid == AId::Id { continue; } append_attribute(parent_id, tag_name, aid, attr.value(), doc); } let mut insert_attribute = |aid, value: &str| { // Check that attribute already exists. let idx = doc.attrs[attrs_start_idx..].iter_mut().position(|a| a.name == aid); // Append an attribute as usual. let added = append_attribute(parent_id, tag_name, aid, value, doc); // Check that attribute was actually added, because it could be skipped. if added { if let Some(idx) = idx { // Swap the last attribute with an existing one. let last_idx = doc.attrs.len() - 1; doc.attrs.swap(attrs_start_idx + idx, last_idx); // Remove last. doc.attrs.pop(); } } }; // Apply CSS. for rule in &style_sheet.rules { if rule.selector.matches(&XmlNode(xml_node)) { for declaration in &rule.declarations { // TODO: preform XML attribute normalization if let Some(aid) = AId::from_str(declaration.name) { // Parse only the presentation attributes. // `transform` isn't a presentation attribute, but should be parsed anyway. if aid.is_presentation() || aid == AId::Transform { insert_attribute(aid, declaration.value); } } else if declaration.name == "marker" { insert_attribute(AId::MarkerStart, declaration.value); insert_attribute(AId::MarkerMid, declaration.value); insert_attribute(AId::MarkerEnd, declaration.value); } } } } // Split a `style` attribute. if let Some(value) = xml_node.attribute("style") { for declaration in simplecss::DeclarationTokenizer::from(value) { // TODO: preform XML attribute normalization if let Some(aid) = AId::from_str(declaration.name) { // Parse only the presentation attributes. // `transform` isn't a presentation attribute, but should be parsed anyway. if aid.is_presentation() || aid == AId::Transform { insert_attribute(aid, declaration.value); } } } } let node_id = doc.append(parent_id, NodeKind::Element { tag_name, attributes: attrs_start_idx..doc.attrs.len(), }); node_id } fn append_attribute( parent_id: NodeId, tag_name: EId, aid: AId, value: &str, doc: &mut Document, ) -> bool { match aid { // The `style` attribute will be split into attributes, so we don't need it. AId::Style | // No need to copy a `class` attribute since CSS were already resolved. AId::Class => return false, _ => {} } // Ignore `xlink:href` on `tspan` (which was originally `tref` or `a`), // because we will convert `tref` into `tspan` anyway. if tag_name == EId::Tspan && aid == AId::Href { return false; } if aid.allows_inherit_value() && value == "inherit" { return resolve_inherit(parent_id, tag_name, aid, doc); } doc.append_attribute(tag_name, aid, value); true } fn parse_svg_attribute( tag_name: EId, aid: AId, value: &str, ) -> Result { Ok(match aid { AId::Href => { // `href` can contain base64 data and we do store it as is. match svgtypes::Stream::from(value).parse_iri() { Ok(link) => AttributeValue::Link(link.to_string()), Err(_) => AttributeValue::String(value.to_string()), } } AId::X | AId::Y | AId::Dx | AId::Dy => { // Some attributes can contain different data based on the element type. match tag_name { EId::Text | EId::Tref | EId::Tspan => { AttributeValue::String(value.to_string()) } _ => { AttributeValue::Length(svgtypes::Length::from_str(value)?) } } } AId::X1 | AId::Y1 | AId::X2 | AId::Y2 | AId::R | AId::Rx | AId::Ry | AId::Cx | AId::Cy | AId::Fx | AId::Fy | AId::RefX | AId::RefY | AId::Width | AId::Height | AId::MarkerWidth | AId::MarkerHeight | AId::StartOffset => { AttributeValue::Length(svgtypes::Length::from_str(value)?) } AId::Offset => { if let EId::FeFuncR | EId::FeFuncG | EId::FeFuncB | EId::FeFuncA = tag_name { AttributeValue::Number(parse_number(value)?) } else { // offset = | let l = svgtypes::Length::from_str(value)?; if l.unit == svgtypes::LengthUnit::None || l.unit == svgtypes::LengthUnit::Percent { AttributeValue::Length(l) } else { return Err(svgtypes::Error::InvalidValue); } } } AId::StrokeDashoffset | AId::StrokeWidth => { AttributeValue::Length(svgtypes::Length::from_str(value)?) } AId::StrokeMiterlimit => { AttributeValue::Number(parse_number(value)?) } AId::Opacity | AId::FillOpacity | AId::FloodOpacity | AId::StrokeOpacity | AId::StopOpacity => { let n = parse_number(value)?; let n = crate::f64_bound(0.0, n, 1.0); AttributeValue::Opacity(n.into()) } AId::K1 | AId::K2 | AId::K3 | AId::K4 => { let n = parse_number(value)?; let n = crate::f64_bound(0.0, n, 1.0); AttributeValue::Number(n) } AId::Amplitude | AId::Exponent | AId::Intercept | AId::Slope => { AttributeValue::Number(parse_number(value)?) } AId::StrokeDasharray => { match value { "none" => AttributeValue::None, _ => AttributeValue::String(value.to_string()), } } AId::Fill => { match svgtypes::Paint::from_str(value) { Ok(svgtypes::Paint::None) => AttributeValue::None, Ok(svgtypes::Paint::Inherit) => unreachable!(), Ok(svgtypes::Paint::CurrentColor) => AttributeValue::CurrentColor, Ok(svgtypes::Paint::Color(color)) => AttributeValue::Color(color), Ok(svgtypes::Paint::FuncIRI(link, fallback)) => { AttributeValue::Paint(link.to_string(), fallback) } Err(_) => { warn!("Failed to parse fill value: '{}'. Fallback to black.", value); AttributeValue::Color(svgtypes::Color::black()) } } } AId::Stroke => { match svgtypes::Paint::from_str(value)? { svgtypes::Paint::None => AttributeValue::None, svgtypes::Paint::Inherit => unreachable!(), svgtypes::Paint::CurrentColor => AttributeValue::CurrentColor, svgtypes::Paint::Color(color) => AttributeValue::Color(color), svgtypes::Paint::FuncIRI(link, fallback) => { AttributeValue::Paint(link.to_string(), fallback) } } } AId::ClipPath | AId::Filter | AId::MarkerEnd | AId::MarkerMid | AId::MarkerStart | AId::Mask => { match value { "none" => AttributeValue::None, _ => { let mut s = svgtypes::Stream::from(value); let link = s.parse_func_iri()?; AttributeValue::Link(link.to_string()) } } } AId::Color => { AttributeValue::Color(svgtypes::Color::from_str(value)?) } AId::FloodColor | AId::StopColor => { match value { "currentColor" => AttributeValue::CurrentColor, _ => AttributeValue::Color(svgtypes::Color::from_str(value)?), } } AId::D => { let segments = parse_path(value); if segments.len() >= 2 { AttributeValue::Path(Rc::new(segments)) } else { return Err(svgtypes::Error::InvalidValue); } } AId::Transform | AId::GradientTransform | AId::PatternTransform => { AttributeValue::Transform(svgtypes::Transform::from_str(value)?) } AId::FontSize => { match svgtypes::Length::from_str(value) { Ok(l) => AttributeValue::Length(l), Err(_) => AttributeValue::String(value.to_string()), } } AId::Display | AId::TextDecoration => { match value { "none" => AttributeValue::None, _ => AttributeValue::String(value.to_string()), } } AId::LetterSpacing | AId::WordSpacing => { match value { "normal" => AttributeValue::String(value.to_string()), _ => AttributeValue::Length(svgtypes::Length::from_str(value)?), } } AId::BaselineShift => { match value { "baseline" | "sub" | "super" => AttributeValue::String(value.to_string()), _ => AttributeValue::Length(svgtypes::Length::from_str(value)?), } } AId::Orient => { match value { "auto" => AttributeValue::String(value.to_string()), _ => AttributeValue::Angle(svgtypes::Angle::from_str(value)?), } } AId::ViewBox => { AttributeValue::ViewBox(svgtypes::ViewBox::from_str(value)?) } AId::PreserveAspectRatio => { AttributeValue::AspectRatio(svgtypes::AspectRatio::from_str(value)?) } AId::Rotate | AId::TableValues | AId::Values => { AttributeValue::NumberList(svgtypes::NumberList::from_str(value)?) } _ => { AttributeValue::String(value.to_string()) } }) } #[inline(never)] fn parse_number(value: &str) -> Result { let mut s = svgtypes::Stream::from(value); let n = s.parse_number()?; if !s.at_end() { return Err(svgtypes::Error::InvalidNumber(0)); } Ok(n) } #[inline(never)] fn parse_path(text: &str) -> tree::PathData { // Previous MoveTo coordinates. let mut prev_mx = 0.0; let mut prev_my = 0.0; // Previous SmoothQuadratic coordinates. let mut prev_tx = 0.0; let mut prev_ty = 0.0; // Previous coordinates. let mut prev_x = 0.0; let mut prev_y = 0.0; let mut prev_seg = svgtypes::PathSegment::MoveTo { abs: true, x: 0.0, y: 0.0 }; let mut path = tree::PathData::with_capacity(32); for segment in svgtypes::PathParser::from(text) { let segment = match segment { Ok(v) => v, Err(_) => break, }; match segment { svgtypes::PathSegment::MoveTo { abs, mut x, mut y } => { if !abs { // When we get 'm'(relative) segment, which is not first segment - then it's // relative to a previous 'M'(absolute) segment, not to the first segment. if let Some(tree::PathSegment::ClosePath) = path.last() { x += prev_mx; y += prev_my; } else { x += prev_x; y += prev_y; } } path.push_move_to(x, y); prev_seg = segment; } svgtypes::PathSegment::LineTo { abs, mut x, mut y } => { if !abs { x += prev_x; y += prev_y; } path.push_line_to(x, y); prev_seg = segment; } svgtypes::PathSegment::HorizontalLineTo { abs, mut x } => { if !abs { x += prev_x; } path.push_line_to(x, prev_y); prev_seg = segment; } svgtypes::PathSegment::VerticalLineTo { abs, mut y } => { if !abs { y += prev_y; } path.push_line_to(prev_x, y); prev_seg = segment; } svgtypes::PathSegment::CurveTo { abs, mut x1, mut y1, mut x2, mut y2, mut x, mut y } => { if !abs { x1 += prev_x; y1 += prev_y; x2 += prev_x; y2 += prev_y; x += prev_x; y += prev_y; } path.push_curve_to(x1, y1, x2, y2, x, y); // Remember as absolute. prev_seg = svgtypes::PathSegment::CurveTo { abs: true, x1, y1, x2, y2, x, y }; } svgtypes::PathSegment::SmoothCurveTo { abs, mut x2, mut y2, mut x, mut y } => { // 'The first control point is assumed to be the reflection of the second control // point on the previous command relative to the current point. // (If there is no previous command or if the previous command // was not an C, c, S or s, assume the first control point is // coincident with the current point.)' let (x1, y1) = match prev_seg { svgtypes::PathSegment::CurveTo { x2, y2, x, y, .. } | svgtypes::PathSegment::SmoothCurveTo { x2, y2, x, y, .. } => { (x * 2.0 - x2, y * 2.0 - y2) } _ => { (prev_x, prev_y) } }; if !abs { x2 += prev_x; y2 += prev_y; x += prev_x; y += prev_y; } path.push_curve_to(x1, y1, x2, y2, x, y); // Remember as absolute. prev_seg = svgtypes::PathSegment::SmoothCurveTo { abs: true, x2, y2, x, y }; } svgtypes::PathSegment::Quadratic { abs, mut x1, mut y1, mut x, mut y } => { if !abs { x1 += prev_x; y1 += prev_y; x += prev_x; y += prev_y; } path.push_quad_to(x1, y1, x, y); // Remember as absolute. prev_seg = svgtypes::PathSegment::Quadratic { abs: true, x1, y1, x, y }; } svgtypes::PathSegment::SmoothQuadratic { abs, mut x, mut y } => { // 'The control point is assumed to be the reflection of // the control point on the previous command relative to // the current point. (If there is no previous command or // if the previous command was not a Q, q, T or t, assume // the control point is coincident with the current point.)' let (x1, y1) = match prev_seg { svgtypes::PathSegment::Quadratic { x1, y1, x, y, .. } => { (x * 2.0 - x1, y * 2.0 - y1) } svgtypes::PathSegment::SmoothQuadratic { x, y, .. } => { (x * 2.0 - prev_tx, y * 2.0 - prev_ty) } _ => { (prev_x, prev_y) } }; prev_tx = x1; prev_ty = y1; if !abs { x += prev_x; y += prev_y; } path.push_quad_to(x1, y1, x, y); // Remember as absolute. prev_seg = svgtypes::PathSegment::SmoothQuadratic { abs: true, x, y }; } svgtypes::PathSegment::EllipticalArc { abs, rx, ry, x_axis_rotation, large_arc, sweep, mut x, mut y } => { if !abs { x += prev_x; y += prev_y; } path.push_arc_to(rx, ry, x_axis_rotation, large_arc, sweep, x, y); prev_seg = segment; } svgtypes::PathSegment::ClosePath { .. } => { if let Some(tree::PathSegment::ClosePath) = path.last() { // Do not add sequential ClosePath segments. // Otherwise it will break marker rendering. } else { path.push_close_path(); } prev_seg = segment; } } // Remember last position. if let Some(seg) = path.last() { match *seg { tree::PathSegment::MoveTo { x, y } => { prev_x = x; prev_y = y; prev_mx = x; prev_my = y; } tree::PathSegment::LineTo { x, y } => { prev_x = x; prev_y = y; } tree::PathSegment::CurveTo { x, y, .. } => { prev_x = x; prev_y = y; } tree::PathSegment::ClosePath => { // ClosePath moves us to the last MoveTo coordinate, // not previous. prev_x = prev_mx; prev_y = prev_my; } } } } path.shrink_to_fit(); path } fn resolve_inherit( parent_id: NodeId, tag_name: EId, aid: AId, doc: &mut Document, ) -> bool { if aid.is_inheritable() { // Inheritable attributes can inherit a value from an any ancestor. let node_id = doc.get(parent_id).find_node_with_attribute(aid).map(|n| n.id); if let Some(node_id) = node_id { if let Some(attr) = doc.get(node_id).attributes().iter().find(|a| a.name == aid).cloned() { doc.attrs.push(Attribute { name: aid, value: attr.value, }); return true; } } } else { // Non-inheritable attributes can inherit a value only from a direct parent. if let Some(attr) = doc.get(parent_id).attributes().iter().find(|a| a.name == aid).cloned() { doc.attrs.push(Attribute { name: aid, value: attr.value, }); return true; } } // Fallback to a default value if possible. let value = match aid { AId::ImageRendering | AId::ShapeRendering | AId::TextRendering => "auto", AId::ClipPath | AId::Filter | AId::MarkerEnd | AId::MarkerMid | AId::MarkerStart | AId::Mask | AId::Stroke | AId::StrokeDasharray | AId::TextDecoration => "none", AId::FontStretch | AId::FontStyle | AId::FontVariant | AId::FontWeight | AId::LetterSpacing | AId::WordSpacing => "normal", AId::Fill | AId::FloodColor | AId::StopColor => "black", AId::FillOpacity | AId::FloodOpacity | AId::Opacity | AId::StopOpacity | AId::StrokeOpacity => "1", AId::ClipRule | AId::FillRule => "nonzero", AId::BaselineShift => "baseline", AId::ColorInterpolationFilters => "linearRGB", AId::Direction => "ltr", AId::Display => "inline", AId::FontSize => "medium", AId::StrokeDashoffset => "0", AId::StrokeLinecap => "butt", AId::StrokeLinejoin => "miter", AId::StrokeMiterlimit => "4", AId::StrokeWidth => "1", AId::TextAnchor => "start", AId::Visibility => "visible", AId::WritingMode => "lr-tb", _ => return false, }; doc.append_attribute(tag_name, aid, value); true } fn resolve_href<'a>( node: roxmltree::Node<'a, 'a>, ) -> Option> { let link_value = node.attribute((XLINK_NS, "href"))?; let link_id = svgtypes::Stream::from(link_value).parse_iri().ok()?; node.document().descendants().find(|n| n.attribute("id") == Some(link_id)) } fn parse_svg_use_element( node: roxmltree::Node, origin: roxmltree::Node, parent_id: NodeId, style_sheet: &simplecss::StyleSheet, doc: &mut Document, ) -> Option<()> { let link = resolve_href(node)?; if link == node || link == origin { warn!("Recursive 'use' detected. '{}' will be skipped.", node.attribute((SVG_NS, "id")).unwrap_or_default()); return None; } let tag_name = parse_tag_name(link)?; // TODO: this // We don't support 'use' elements linked to 'svg' element. if tag_name == EId::Svg { warn!("'use' elements linked to an 'svg' element are not supported. Skipped."); return None; } // Check that none of the linked node's children reference current `use` node // via other `use` node. // // Example: // // // // // // `use2` should be removed. // // Also, child should not reference its parent: // // // // // `use1` should be removed. let mut is_recursive = false; for link_child in link.descendants().skip(1).filter(|n| n.has_tag_name((SVG_NS, "use"))) { if let Some(link2) = resolve_href(link_child) { if link2 == node || link2 == link { is_recursive = true; break; } } } if is_recursive { warn!("Recursive 'use' detected. '{}' will be skipped.", node.attribute((SVG_NS, "id")).unwrap_or_default()); return None; } parse_xml_node(link, node, parent_id, style_sheet, true, doc); Some(()) } fn parse_svg_text_element( parent: roxmltree::Node, parent_id: NodeId, style_sheet: &simplecss::StyleSheet, doc: &mut Document, ) { debug_assert_eq!(parent.tag_name().name(), "text"); let space = if doc.get(parent_id).has_attribute(AId::Space) { get_xmlspace(doc, parent_id, XmlSpace::Default) } else { if let Some(node) = doc.get(parent_id).ancestors().find(|n| n.has_attribute(AId::Space)) { get_xmlspace(doc, node.id, XmlSpace::Default) } else { XmlSpace::Default } }; parse_svg_text_element_impl(parent, parent_id, style_sheet, space, doc); trim_text_nodes(parent_id, space, doc); } fn parse_svg_text_element_impl( parent: roxmltree::Node, parent_id: NodeId, style_sheet: &simplecss::StyleSheet, space: XmlSpace, doc: &mut Document, ) { for node in parent.children() { if node.is_text() { let text = trim_text(node.text().unwrap(), space); doc.append(parent_id, NodeKind::Text(text)); continue; } let mut tag_name = match parse_tag_name(node) { Some(id) => id, None => continue, }; if tag_name == EId::A { // Treat links as a simple text. tag_name = EId::Tspan; } match tag_name { EId::Tspan | EId::Tref | EId::TextPath => {} _ => continue, } // `textPath` must be a direct `text` child. if tag_name == EId::TextPath && parent.tag_name().name() != "text" { continue; } // We are converting `tref` into `tspan` to simplify later use. let mut is_tref = false; if tag_name == EId::Tref { tag_name = EId::Tspan; is_tref = true; } let node_id = parse_svg_element(node, parent_id, tag_name, style_sheet, false, doc); let space = get_xmlspace(doc, node_id, space); if is_tref { if let Some(href) = node.attribute((XLINK_NS, "href")) { if let Some(text) = resolve_tref_text(node.document(), href) { let text = trim_text(&text, space); doc.append(node_id, NodeKind::Text(text)); } } } else { parse_svg_text_element_impl(node, node_id, style_sheet, space, doc); } } } fn resolve_tref_text( xml: &roxmltree::Document, href: &str, ) -> Option { let id = svgtypes::Stream::from(href).parse_iri().ok()?; // Find linked element in the original tree. let node = xml.descendants().find(|n| n.attribute("id") == Some(id))?; // `tref` should be linked to an SVG element. parse_tag_name(node)?; // 'All character data within the referenced element, including character data enclosed // within additional markup, will be rendered.' // // So we don't care about attributes and everything. Just collecting text nodes data. let mut text = String::new(); for child in node.descendants().filter(|n| n.is_text()) { text.push_str(child.text().unwrap_or("")); } if text.is_empty() { return None; } Some(text) } #[derive(Clone, Copy, PartialEq, Debug)] enum XmlSpace { Default, Preserve, } fn get_xmlspace(doc: &Document, node_id: NodeId, default: XmlSpace) -> XmlSpace { match doc.get(node_id).attribute(AId::Space) { Some("preserve") => XmlSpace::Preserve, Some(_) => XmlSpace::Default, _ => default, } } trait StrTrim { fn remove_first(&mut self); fn remove_last(&mut self); } impl StrTrim for String { fn remove_first(&mut self) { self.drain(0..1); } fn remove_last(&mut self) { self.pop(); } } /// Prepares text nodes according to the spec: https://www.w3.org/TR/SVG11/text.html#WhiteSpace /// /// This function handles: /// - 'xml:space' processing /// - tabs and newlines removing/replacing /// - spaces trimming fn trim_text_nodes(text_elem_id: NodeId, xmlspace: XmlSpace, doc: &mut Document) { let mut nodes = Vec::new(); // TODO: allocate only once collect_text_nodes(doc.get(text_elem_id), 0, &mut nodes); // `trim` method has already collapsed all spaces into a single one, // so we have to check only for one leading or trailing space. if nodes.len() == 1 { // Process element with a single text node child. let node_id = nodes[0].0.clone(); if xmlspace == XmlSpace::Default { if let NodeKind::Text(ref mut text) = doc.nodes[node_id.0].kind { match text.len() { 0 => {} // An empty string. Do nothing. 1 => { // If string has only one character and it's a space - clear this string. if text.as_bytes()[0] == b' ' { text.clear(); } } _ => { // 'text' has at least 2 bytes, so indexing is safe. let c1 = text.as_bytes()[0]; let c2 = text.as_bytes()[text.len() - 1]; if c1 == b' ' { text.remove_first(); } if c2 == b' ' { text.remove_last(); } } } } } else { // Do nothing when xml:space=preserve. } } else if nodes.len() > 1 { // Process element with many text node children. // We manage all text nodes as a single text node // and trying to remove duplicated spaces across nodes. // // For example 'Text text text' // is the same is 'Text text text' let mut i = 0; let len = nodes.len() - 1; let mut last_non_empty: Option = None; while i < len { // Process pairs. let (mut node1_id, depth1) = nodes[i].clone(); let (node2_id, depth2) = nodes[i + 1].clone(); if doc.get(node1_id).text().is_empty() { if let Some(n) = last_non_empty { node1_id = n; } } // Parent of the text node is always an element node and always exist, // so unwrap is safe. let xmlspace1 = get_xmlspace(doc, doc.get(node1_id).parent().unwrap().id, xmlspace); let xmlspace2 = get_xmlspace(doc, doc.get(node2_id).parent().unwrap().id, xmlspace); // >text<..>text< // 1 2 3 4 let (c1, c2, c3, c4) = { let text1 = doc.get(node1_id).text(); let text2 = doc.get(node2_id).text(); let bytes1 = text1.as_bytes(); let bytes2 = text2.as_bytes(); let c1 = bytes1.first().cloned(); let c2 = bytes1.last().cloned(); let c3 = bytes2.first().cloned(); let c4 = bytes2.last().cloned(); (c1, c2, c3, c4) }; // NOTE: xml:space processing is mostly an undefined behavior, // because everyone do it differently. // We're mimicking the Chrome behavior. // Remove space from the second text node if both nodes has bound spaces. // From: 'Text text' // To: 'Text text' // // See text-tspan-02-b.svg for details. if c2 == Some(b' ') && c2 == c3 { if depth1 < depth2 { if xmlspace2 == XmlSpace::Default { if let NodeKind::Text(ref mut text) = doc.nodes[node2_id.0].kind { text.remove_first(); } } } else { if xmlspace1 == XmlSpace::Default && xmlspace2 == XmlSpace::Default { if let NodeKind::Text(ref mut text) = doc.nodes[node1_id.0].kind { text.remove_last(); } } else if xmlspace1 == XmlSpace::Preserve && xmlspace2 == XmlSpace::Default { if let NodeKind::Text(ref mut text) = doc.nodes[node2_id.0].kind { text.remove_first(); } } } } let is_first = i == 0; let is_last = i == len - 1; if is_first && c1 == Some(b' ') && xmlspace1 == XmlSpace::Default && !doc.get(node1_id).text().is_empty() { // Remove a leading space from a first text node. if let NodeKind::Text(ref mut text) = doc.nodes[node1_id.0].kind { text.remove_first(); } } else if is_last && c4 == Some(b' ') && !doc.get(node2_id).text().is_empty() && xmlspace2 == XmlSpace::Default { // Remove a trailing space from a last text node. // Also check that 'text2' is not empty already. if let NodeKind::Text(ref mut text) = doc.nodes[node2_id.0].kind { text.remove_last(); } } if is_last && c2 == Some(b' ') && !doc.get(node1_id).text().is_empty() && doc.get(node2_id).text().is_empty() && doc.get(node1_id).text().ends_with(' ') { if let NodeKind::Text(ref mut text) = doc.nodes[node1_id.0].kind { text.remove_last(); } } if !doc.get(node1_id).text().trim().is_empty() { last_non_empty = Some(node1_id); } i += 1; } } // TODO: find a way to remove all empty text nodes } fn collect_text_nodes(parent: Node, depth: usize, nodes: &mut Vec<(NodeId, usize)>) { for child in parent.children() { if child.is_text() { nodes.push((child.id, depth)); } else if child.is_element() { collect_text_nodes(child, depth + 1, nodes); } } } fn trim_text(text: &str, space: XmlSpace) -> String { let mut s = String::with_capacity(text.len()); let mut prev = '0'; for c in text.chars() { // \r, \n and \t should be converted into spaces. let c = match c { '\r' | '\n' | '\t' => ' ', _ => c, }; // Skip continuous spaces. if space == XmlSpace::Default && c == ' ' && c == prev { continue; } prev = c; s.push(c); } s } fn resolve_css<'a>( xml: &'a roxmltree::Document<'a>, ) -> simplecss::StyleSheet<'a> { let mut sheet = simplecss::StyleSheet::new(); for node in xml.descendants().filter(|n| n.has_tag_name("style")) { match node.attribute("type") { Some("text/css") => {} Some(_) => continue, None => {} } let style = match node.text() { Some(s) => s, None => continue, }; sheet.parse_more(style); } sheet } struct XmlNode<'a, 'input: 'a>(roxmltree::Node<'a, 'input>); impl simplecss::Element for XmlNode<'_, '_> { fn parent_element(&self) -> Option { self.0.parent_element().map(XmlNode) } fn prev_sibling_element(&self) -> Option { self.0.prev_sibling_element().map(XmlNode) } fn has_local_name(&self, local_name: &str) -> bool { self.0.tag_name().name() == local_name } fn attribute_matches(&self, local_name: &str, operator: simplecss::AttributeOperator) -> bool { match self.0.attribute(local_name) { Some(value) => operator.matches(value), None => false, } } fn pseudo_class_matches(&self, class: simplecss::PseudoClass) -> bool { match class { simplecss::PseudoClass::FirstChild => self.prev_sibling_element().is_none(), // TODO: lang _ => false, // Since we are querying a static SVG we can ignore other pseudo-classes. } } } fn fix_recursive_patterns(doc: &mut Document) { while let Some(node_id) = find_recursive_pattern(AId::Fill, doc) { let idx = doc.get(node_id).attribute_id(AId::Fill).unwrap(); doc.attrs[idx.0].value = AttributeValue::None; } while let Some(node_id) = find_recursive_pattern(AId::Stroke, doc) { let idx = doc.get(node_id).attribute_id(AId::Stroke).unwrap(); doc.attrs[idx.0].value = AttributeValue::None; } } fn find_recursive_pattern( aid: AId, doc: &mut Document, ) -> Option { for pattern_node in doc.root().descendants().filter(|n| n.has_tag_name(EId::Pattern)) { for node in pattern_node.descendants() { if let Some(&AttributeValue::Paint(ref link_id, _)) = node.attribute(aid) { if link_id == pattern_node.element_id() { // If a pattern child has a link to the pattern itself // then we have to replace it with `none`. // Otherwise we will get endless loop/recursion and stack overflow. return Some(node.id); } else { // Check that linked node children doesn't link this pattern. for node2 in doc.element_by_id(link_id).unwrap().descendants() { if let Some(&AttributeValue::Paint(ref link_id2, _)) = node2.attribute(aid) { if link_id2 == pattern_node.element_id() { return Some(node2.id); } } } } } } } None } fn fix_recursive_links( eid: EId, aid: AId, doc: &mut Document, ) { while let Some(node_id) = find_recursive_link(eid, aid, doc) { let idx = doc.get(node_id).attribute_id(aid).unwrap(); doc.attrs[idx.0].value = AttributeValue::None; } } fn find_recursive_link( eid: EId, aid: AId, doc: &Document, ) -> Option { for node in doc.root().descendants().filter(|n| n.has_tag_name(eid)) { for child in node.descendants() { if let Some(link) = child.attribute::(aid) { if link == node { // If an element child has a link to the element itself // then we have to replace it with `none`. // Otherwise we will get endless loop/recursion and stack overflow. return Some(child.id); } else { // Check that linked node children doesn't link this element. for node2 in link.descendants() { if let Some(link2) = node2.attribute::(aid) { if link2 == node { return Some(node2.id); } } } } } } } None } resvg-0.8.0/usvg/src/tree/000077500000000000000000000000001352576375700154035ustar00rootroot00000000000000resvg-0.8.0/usvg/src/tree/attributes.rs000066400000000000000000000212171352576375700201420ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::fmt; use std::path::PathBuf; pub use svgtypes::{ Align, AspectRatio, Color, FuzzyEq, FuzzyZero, Transform, }; use crate::geom::*; pub use super::numbers::*; macro_rules! impl_from_str { ($name:ident) => { impl std::str::FromStr for $name { type Err = &'static str; fn from_str(s: &str) -> Result { crate::svgtree::EnumFromStr::enum_from_str(s).ok_or("invalid value") } } }; } /// A line cap. /// /// `stroke-linecap` attribute in the SVG. #[allow(missing_docs)] #[derive(Clone, Copy, PartialEq, Debug)] pub enum LineCap { Butt, Round, Square, } impl_enum_default!(LineCap, Butt); impl_enum_from_str!(LineCap, "butt" => LineCap::Butt, "round" => LineCap::Round, "square" => LineCap::Square ); /// A line join. /// /// `stroke-linejoin` attribute in the SVG. #[allow(missing_docs)] #[derive(Clone, Copy, PartialEq, Debug)] pub enum LineJoin { Miter, Round, Bevel, } impl_enum_default!(LineJoin, Miter); impl_enum_from_str!(LineJoin, "miter" => LineJoin::Miter, "round" => LineJoin::Round, "bevel" => LineJoin::Bevel ); /// A fill rule. /// /// `fill-rule` attribute in the SVG. #[allow(missing_docs)] #[derive(Clone, Copy, PartialEq, Debug)] pub enum FillRule { NonZero, EvenOdd, } impl_enum_default!(FillRule, NonZero); impl_enum_from_str!(FillRule, "nonzero" => FillRule::NonZero, "evenodd" => FillRule::EvenOdd ); /// An element units. #[allow(missing_docs)] #[derive(Clone, Copy, PartialEq, Debug)] pub enum Units { UserSpaceOnUse, ObjectBoundingBox, } // `Units` cannot have a default value, because it changes depending on an element. impl_enum_from_str!(Units, "userSpaceOnUse" => Units::UserSpaceOnUse, "objectBoundingBox" => Units::ObjectBoundingBox ); /// A spread method. /// /// `spreadMethod` attribute in the SVG. #[allow(missing_docs)] #[derive(Clone, Copy, PartialEq, Debug)] pub enum SpreadMethod { Pad, Reflect, Repeat, } impl_enum_default!(SpreadMethod, Pad); impl_enum_from_str!(SpreadMethod, "pad" => SpreadMethod::Pad, "reflect" => SpreadMethod::Reflect, "repeat" => SpreadMethod::Repeat ); /// A visibility property. /// /// `visibility` attribute in the SVG. #[allow(missing_docs)] #[derive(Clone, Copy, PartialEq, Debug)] pub enum Visibility { Visible, Hidden, Collapse, } impl_enum_default!(Visibility, Visible); impl_enum_from_str!(Visibility, "visible" => Visibility::Visible, "hidden" => Visibility::Hidden, "collapse" => Visibility::Collapse ); /// A paint style. /// /// `paint` value type in the SVG. #[allow(missing_docs)] #[derive(Clone)] pub enum Paint { /// Paint with a color. Color(Color), /// Paint using a referenced element. Link(String), } impl fmt::Debug for Paint { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Paint::Color(c) => write!(f, "Color({})", c), Paint::Link(_) => write!(f, "Link"), } } } /// A fill style. #[allow(missing_docs)] #[derive(Clone, Debug)] pub struct Fill { pub paint: Paint, pub opacity: Opacity, pub rule: FillRule, } impl Default for Fill { fn default() -> Self { Fill { paint: Paint::Color(Color::black()), opacity: Opacity::default(), rule: FillRule::default(), } } } /// A stroke style. #[allow(missing_docs)] #[derive(Clone, Debug)] pub struct Stroke { pub paint: Paint, pub dasharray: Option>, pub dashoffset: f32, // f32 and not f64 to reduce the struct size. pub miterlimit: StrokeMiterlimit, pub opacity: Opacity, pub width: StrokeWidth, pub linecap: LineCap, pub linejoin: LineJoin, } impl Default for Stroke { fn default() -> Self { Stroke { // The actual default color is `none`, // but to simplify the `Stroke` object creation we use `black`. paint: Paint::Color(Color::black()), dasharray: None, dashoffset: 0.0, miterlimit: StrokeMiterlimit::default(), opacity: Opacity::default(), width: StrokeWidth::default(), linecap: LineCap::default(), linejoin: LineJoin::default(), } } } /// View box. #[derive(Clone, Copy, Debug)] pub struct ViewBox { /// Value of the `viewBox` attribute. pub rect: Rect, /// Value of the `preserveAspectRatio` attribute. pub aspect: AspectRatio, } /// Identifies input for a filter primitive. #[allow(missing_docs)] #[derive(Clone, PartialEq, Debug)] pub enum FilterInput { SourceGraphic, SourceAlpha, BackgroundImage, BackgroundAlpha, FillPaint, StrokePaint, Reference(String), } /// A color interpolation mode. #[allow(missing_docs)] #[derive(Clone, Copy, PartialEq, Debug)] pub enum ColorInterpolation { SRGB, LinearRGB, } impl_enum_default!(ColorInterpolation, LinearRGB); impl_enum_from_str!(ColorInterpolation, "sRGB" => ColorInterpolation::SRGB, "linearRGB" => ColorInterpolation::LinearRGB ); /// A raster image container. #[derive(Clone, Debug)] pub enum ImageData { /// Path to a PNG, JPEG or SVG(Z) image. /// /// Preprocessor will check that the file exist, but because it can be removed later, /// so there is no guarantee that this path is valid. /// /// The path may be relative. Path(PathBuf), /// Image raw data. /// /// It's not a decoded image data, but the data that was decoded from base64. /// So you still need a PNG, JPEG and SVG(Z) decoding libraries. Raw(Vec), } /// An image codec. #[allow(missing_docs)] #[derive(Clone, Copy, PartialEq, Debug)] pub enum ImageFormat { PNG, JPEG, SVG, } /// An images blending mode. #[allow(missing_docs)] #[derive(Clone, Copy, PartialEq, Debug)] pub enum FeBlendMode { Normal, Multiply, Screen, Darken, Lighten, } /// An images compositing operation. #[allow(missing_docs)] #[derive(Clone, Copy, PartialEq, Debug)] pub enum FeCompositeOperator { Over, In, Out, Atop, Xor, Arithmetic { k1: CompositingCoefficient, k2: CompositingCoefficient, k3: CompositingCoefficient, k4: CompositingCoefficient, }, } /// Kind of the `feImage` data. #[derive(Clone, Debug)] pub enum FeImageKind { /// Empty image. /// /// Unlike the `image` element, `feImage` can be without the `href` attribute. /// In this case the filter primitive is an empty canvas. /// And we can't remove it, because its `result` can be used. None, /// An image data. Image(ImageData, ImageFormat), /// A reference to an SVG object. /// /// `feImage` can reference any SVG object, just like `use` element. /// But we can't resolve `use` in this case. /// /// Not supported yet. Use(String), } /// A shape rendering method. /// /// `shape-rendering` attribute in the SVG. #[derive(Clone, Copy, PartialEq, Debug)] #[allow(missing_docs)] pub enum ShapeRendering { OptimizeSpeed, CrispEdges, GeometricPrecision, } impl_enum_default!(ShapeRendering, GeometricPrecision); impl_enum_from_str!(ShapeRendering, "optimizeSpeed" => ShapeRendering::OptimizeSpeed, "crispEdges" => ShapeRendering::CrispEdges, "geometricPrecision" => ShapeRendering::GeometricPrecision ); impl_from_str!(ShapeRendering); /// A text rendering method. /// /// `text-rendering` attribute in the SVG. #[allow(missing_docs)] #[derive(Clone, Copy, PartialEq, Debug)] pub enum TextRendering { OptimizeSpeed, OptimizeLegibility, GeometricPrecision, } impl_enum_default!(TextRendering, OptimizeLegibility); impl_enum_from_str!(TextRendering, "optimizeSpeed" => TextRendering::OptimizeSpeed, "optimizeLegibility" => TextRendering::OptimizeLegibility, "geometricPrecision" => TextRendering::GeometricPrecision ); impl_from_str!(TextRendering); /// An image rendering method. /// /// `image-rendering` attribute in the SVG. #[allow(missing_docs)] #[derive(Clone, Copy, PartialEq, Debug)] pub enum ImageRendering { OptimizeQuality, OptimizeSpeed, } impl_enum_default!(ImageRendering, OptimizeQuality); impl_enum_from_str!(ImageRendering, "optimizeQuality" => ImageRendering::OptimizeQuality, "optimizeSpeed" => ImageRendering::OptimizeSpeed ); impl_from_str!(ImageRendering); resvg-0.8.0/usvg/src/tree/export.rs000066400000000000000000000714071352576375700173030ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::fmt::Display; use std::io::Write; use std::ops::Deref; use svgtypes::WriteBuffer; use xmlwriter::XmlWriter; use super::*; use crate::{geom::*, svgtree::{EId, AId}, IsDefault}; pub fn convert(tree: &Tree, opt: XmlOptions) -> String { let mut xml = XmlWriter::new(opt); let svg_node = tree.svg_node(); xml.start_svg_element(EId::Svg); xml.write_svg_attribute(AId::Width, &svg_node.size.width()); xml.write_svg_attribute(AId::Height, &svg_node.size.height()); xml.write_viewbox(&svg_node.view_box); xml.write_attribute("xmlns", "http://www.w3.org/2000/svg"); if has_xlink(tree) { xml.write_attribute("xmlns:xlink", "http://www.w3.org/1999/xlink"); } xml.write_attribute("xmlns:usvg", "https://github.com/RazrFalcon/usvg"); xml.write_attribute("usvg:version", env!("CARGO_PKG_VERSION")); xml.start_svg_element(EId::Defs); conv_defs(tree, &mut xml); xml.end_element(); conv_elements(&tree.root(), false, &mut xml); xml.end_document() } fn conv_defs( tree: &Tree, xml: &mut XmlWriter, ) { for n in tree.defs().children() { match *n.borrow() { NodeKind::LinearGradient(ref lg) => { xml.start_svg_element(EId::LinearGradient); xml.write_svg_attribute(AId::Id, &lg.id); xml.write_svg_attribute(AId::X1, &lg.x1); xml.write_svg_attribute(AId::Y1, &lg.y1); xml.write_svg_attribute(AId::X2, &lg.x2); xml.write_svg_attribute(AId::Y2, &lg.y2); write_base_grad(&lg.base, xml); xml.end_element(); } NodeKind::RadialGradient(ref rg) => { xml.start_svg_element(EId::RadialGradient); xml.write_svg_attribute(AId::Id, &rg.id); xml.write_svg_attribute(AId::Cx, &rg.cx); xml.write_svg_attribute(AId::Cy, &rg.cy); xml.write_svg_attribute(AId::R, &rg.r.value()); xml.write_svg_attribute(AId::Fx, &rg.fx); xml.write_svg_attribute(AId::Fy, &rg.fy); write_base_grad(&rg.base, xml); xml.end_element(); } NodeKind::ClipPath(ref clip) => { xml.start_svg_element(EId::ClipPath); xml.write_svg_attribute(AId::Id, &clip.id); xml.write_units(AId::ClipPathUnits, clip.units, Units::UserSpaceOnUse); xml.write_transform(AId::Transform, clip.transform); if let Some(ref id) = clip.clip_path { xml.write_func_iri(AId::ClipPath, id); } conv_elements(&n, true, xml); xml.end_element(); } NodeKind::Mask(ref mask) => { xml.start_svg_element(EId::Mask); xml.write_svg_attribute(AId::Id, &mask.id); xml.write_units(AId::MaskUnits, mask.units, Units::ObjectBoundingBox); xml.write_units(AId::MaskContentUnits, mask.content_units, Units::UserSpaceOnUse); xml.write_rect_attrs(mask.rect); if let Some(ref id) = mask.mask { xml.write_func_iri(AId::Mask, id); } conv_elements(&n, false, xml); xml.end_element(); } NodeKind::Pattern(ref pattern) => { xml.start_svg_element(EId::Pattern); xml.write_svg_attribute(AId::Id, &pattern.id); xml.write_rect_attrs(pattern.rect); xml.write_units(AId::PatternUnits, pattern.units, Units::ObjectBoundingBox); xml.write_units(AId::PatternContentUnits, pattern.content_units, Units::UserSpaceOnUse); xml.write_transform(AId::PatternTransform, pattern.transform); if let Some(ref vbox) = pattern.view_box { xml.write_viewbox(vbox); } conv_elements(&n, false, xml); xml.end_element(); } NodeKind::Filter(ref filter) => { xml.start_svg_element(EId::Filter); xml.write_svg_attribute(AId::Id, &filter.id); xml.write_rect_attrs(filter.rect); xml.write_units(AId::FilterUnits, filter.units, Units::ObjectBoundingBox); xml.write_units(AId::PrimitiveUnits, filter.primitive_units, Units::UserSpaceOnUse); for fe in &filter.children { match fe.kind { FilterKind::FeGaussianBlur(ref blur) => { xml.start_svg_element(EId::FeGaussianBlur); xml.write_filter_primitive_attrs(fe); xml.write_filter_input(AId::In, &blur.input); xml.write_attribute_fmt( AId::StdDeviation.to_str(), format_args!("{} {}", blur.std_dev_x.value(), blur.std_dev_y.value()), ); xml.write_svg_attribute(AId::Result, &fe.result); xml.end_element(); } FilterKind::FeOffset(ref offset) => { xml.start_svg_element(EId::FeOffset); xml.write_filter_primitive_attrs(fe); xml.write_filter_input(AId::In, &offset.input); xml.write_svg_attribute(AId::Dx, &offset.dx); xml.write_svg_attribute(AId::Dy, &offset.dy); xml.write_svg_attribute(AId::Result, &fe.result); xml.end_element(); } FilterKind::FeBlend(ref blend) => { xml.start_svg_element(EId::FeBlend); xml.write_filter_primitive_attrs(fe); xml.write_filter_input(AId::In, &blend.input1); xml.write_filter_input(AId::In2, &blend.input2); xml.write_svg_attribute(AId::Mode, match blend.mode { FeBlendMode::Normal => "normal", FeBlendMode::Multiply => "multiply", FeBlendMode::Screen => "screen", FeBlendMode::Darken => "darken", FeBlendMode::Lighten => "lighten", }); xml.write_svg_attribute(AId::Result, &fe.result); xml.end_element(); } FilterKind::FeFlood(ref flood) => { xml.start_svg_element(EId::FeFlood); xml.write_filter_primitive_attrs(fe); xml.write_svg_attribute(AId::FloodColor, &flood.color); xml.write_svg_attribute(AId::FloodOpacity, &flood.opacity.value()); xml.write_svg_attribute(AId::Result, &fe.result); xml.end_element(); } FilterKind::FeComposite(ref composite) => { xml.start_svg_element(EId::FeComposite); xml.write_filter_primitive_attrs(fe); xml.write_filter_input(AId::In, &composite.input1); xml.write_filter_input(AId::In2, &composite.input2); xml.write_svg_attribute(AId::Operator, match composite.operator { FeCompositeOperator::Over => "over", FeCompositeOperator::In => "in", FeCompositeOperator::Out => "out", FeCompositeOperator::Atop => "atop", FeCompositeOperator::Xor => "xor", FeCompositeOperator::Arithmetic { .. } => "arithmetic", }); match composite.operator { FeCompositeOperator::Arithmetic { k1, k2, k3, k4 } => { xml.write_svg_attribute(AId::K1, &k1.value()); xml.write_svg_attribute(AId::K2, &k2.value()); xml.write_svg_attribute(AId::K3, &k3.value()); xml.write_svg_attribute(AId::K4, &k4.value()); } _ => {} } xml.write_svg_attribute(AId::Result, &fe.result); xml.end_element(); } FilterKind::FeMerge(ref merge) => { xml.start_svg_element(EId::FeMerge); xml.write_filter_primitive_attrs(fe); xml.write_svg_attribute(AId::Result, &fe.result); for input in &merge.inputs { xml.start_svg_element(EId::FeMergeNode); xml.write_filter_input(AId::In, &input); xml.end_element(); } xml.end_element(); } FilterKind::FeTile(ref tile) => { xml.start_svg_element(EId::FeTile); xml.write_filter_primitive_attrs(fe); xml.write_filter_input(AId::In, &tile.input); xml.write_svg_attribute(AId::Result, &fe.result); xml.end_element(); } FilterKind::FeImage(ref img) => { xml.start_svg_element(EId::FeImage); xml.write_filter_primitive_attrs(fe); xml.write_aspect(img.aspect); xml.write_svg_attribute(AId::ImageRendering, match img.rendering_mode { ImageRendering::OptimizeQuality => "optimizeQuality", ImageRendering::OptimizeSpeed => "optimizeSpeed", }); match img.data { FeImageKind::None => {} FeImageKind::Image(ref data, format) => { xml.write_image_data(data, format); } FeImageKind::Use(..) => {} } xml.write_svg_attribute(AId::Result, &fe.result); xml.end_element(); } FilterKind::FeComponentTransfer(ref transfer) => { xml.start_svg_element(EId::FeComponentTransfer); xml.write_filter_primitive_attrs(fe); xml.write_filter_input(AId::In, &transfer.input); xml.write_svg_attribute(AId::Result, &fe.result); xml.write_filter_transfer_function(EId::FeFuncR, &transfer.func_r); xml.write_filter_transfer_function(EId::FeFuncG, &transfer.func_g); xml.write_filter_transfer_function(EId::FeFuncB, &transfer.func_b); xml.write_filter_transfer_function(EId::FeFuncA, &transfer.func_a); xml.end_element(); } FilterKind::FeColorMatrix(ref matrix) => { xml.start_svg_element(EId::FeColorMatrix); xml.write_filter_primitive_attrs(fe); xml.write_filter_input(AId::In, &matrix.input); xml.write_svg_attribute(AId::Result, &fe.result); match matrix.kind { FeColorMatrixKind::Matrix(ref values) => { xml.write_svg_attribute(AId::Type, "matrix"); xml.write_numbers(AId::Values, values); } FeColorMatrixKind::Saturate(value) => { xml.write_svg_attribute(AId::Type, "saturate"); xml.write_svg_attribute(AId::Values, &value.value()); } FeColorMatrixKind::HueRotate(angle) => { xml.write_svg_attribute(AId::Type, "hueRotate"); xml.write_svg_attribute(AId::Values, &angle); } FeColorMatrixKind::LuminanceToAlpha => { xml.write_svg_attribute(AId::Type, "luminanceToAlpha"); } } xml.end_element(); } }; } xml.end_element(); } _ => {} } } } fn conv_elements( parent: &Node, is_clip_path: bool, xml: &mut XmlWriter, ) { for n in parent.children() { match *n.borrow() { NodeKind::Path(ref p) => { write_path(p, is_clip_path, None, xml); } NodeKind::Image(ref img) => { xml.start_svg_element(EId::Image); if !img.id.is_empty() { xml.write_svg_attribute(AId::Id, &img.id); } xml.write_rect_attrs(img.view_box.rect); if !img.view_box.aspect.is_default() { xml.write_aspect(img.view_box.aspect); } xml.write_visibility(img.visibility); match img.rendering_mode { ImageRendering::OptimizeQuality => {} ImageRendering::OptimizeSpeed => { xml.write_svg_attribute(AId::ImageRendering, "optimizeSpeed"); } } xml.write_transform(AId::Transform, img.transform); xml.write_image_data(&img.data, img.format); xml.end_element(); } NodeKind::Group(ref g) => { if is_clip_path { // ClipPath with a Group element is an `usvg` special case. // Group will contains a single Path element and we should set // `clip-path` on it. if let NodeKind::Path(ref path) = *n.first_child().unwrap().borrow() { let clip_id = g.clip_path.as_ref().map(String::deref); write_path(path, is_clip_path, clip_id, xml); } continue; } xml.start_svg_element(EId::G); if !g.id.is_empty() { xml.write_svg_attribute(AId::Id, &g.id); }; if let Some(ref id) = g.clip_path { xml.write_func_iri(AId::ClipPath, id); } if let Some(ref id) = g.mask { xml.write_func_iri(AId::Mask, id); } if let Some(ref id) = g.filter { xml.write_func_iri(AId::Filter, id); } if !g.opacity.is_default() { xml.write_svg_attribute(AId::Opacity, &g.opacity.value()); } xml.write_transform(AId::Transform, g.transform); conv_elements(&n, false, xml); xml.end_element(); } _ => {} } } } trait XmlWriterExt { fn start_svg_element(&mut self, id: EId); fn write_svg_attribute(&mut self, id: AId, value: &V); fn write_viewbox(&mut self, view_box: &ViewBox); fn write_aspect(&mut self, aspect: AspectRatio); fn write_units(&mut self, id: AId, units: Units, def: Units); fn write_transform(&mut self, id: AId, units: Transform); fn write_visibility(&mut self, value: Visibility); fn write_func_iri(&mut self, aid: AId, id: &str); fn write_rect_attrs(&mut self, r: Rect); fn write_numbers(&mut self, aid: AId, list: &[f64]); fn write_filter_input(&mut self, id: AId, input: &FilterInput); fn write_filter_primitive_attrs(&mut self, fe: &FilterPrimitive); fn write_filter_transfer_function(&mut self, eid: EId, fe: &TransferFunction); fn write_image_data(&mut self, data: &ImageData, format: ImageFormat); } impl XmlWriterExt for XmlWriter { #[inline(never)] fn start_svg_element(&mut self, id: EId) { self.start_element(id.to_str()); } #[inline(never)] fn write_svg_attribute(&mut self, id: AId, value: &V) { self.write_attribute(id.to_str(), value) } fn write_viewbox(&mut self, view_box: &ViewBox) { let r = view_box.rect; self.write_attribute_fmt( AId::ViewBox.to_str(), format_args!("{} {} {} {}", r.x(), r.y(), r.width(), r.height()), ); if !view_box.aspect.is_default() { self.write_aspect(view_box.aspect); } } fn write_aspect(&mut self, aspect: AspectRatio) { self.write_attribute_raw(AId::PreserveAspectRatio.to_str(), |buf| aspect.write_buf(buf)); } fn write_units(&mut self, id: AId, units: Units, def: Units) { if units != def { self.write_attribute(id.to_str(), match units { Units::UserSpaceOnUse => "userSpaceOnUse", Units::ObjectBoundingBox => "objectBoundingBox", }); } } fn write_transform(&mut self, id: AId, ts: Transform) { if !ts.is_default() { self.write_attribute_fmt( id.to_str(), format_args!("matrix({} {} {} {} {} {})", ts.a, ts.b, ts.c, ts.d, ts.e, ts.f), ); } } fn write_visibility(&mut self, value: Visibility) { match value { Visibility::Visible => {}, Visibility::Hidden => self.write_attribute(AId::Visibility.to_str(), "hidden"), Visibility::Collapse => self.write_attribute(AId::Visibility.to_str(), "collapse"), } } fn write_func_iri(&mut self, aid: AId, id: &str) { self.write_attribute_fmt(aid.to_str(), format_args!("url(#{})", id)); } fn write_rect_attrs(&mut self, r: Rect) { self.write_svg_attribute(AId::X, &r.x()); self.write_svg_attribute(AId::Y, &r.y()); self.write_svg_attribute(AId::Width, &r.width()); self.write_svg_attribute(AId::Height, &r.height()); } fn write_numbers(&mut self, aid: AId, list: &[f64]) { self.write_attribute_raw(aid.to_str(), |buf| { for n in list { buf.write_fmt(format_args!("{} ", n)).unwrap(); } if !list.is_empty() { buf.pop(); } }); } fn write_filter_input(&mut self, id: AId, input: &FilterInput) { self.write_attribute(id.to_str(), match input { FilterInput::SourceGraphic => "SourceGraphic", FilterInput::SourceAlpha => "SourceAlpha", FilterInput::BackgroundImage => "BackgroundImage", FilterInput::BackgroundAlpha => "BackgroundAlpha", FilterInput::FillPaint => "FillPaint", FilterInput::StrokePaint => "StrokePaint", FilterInput::Reference(ref s) => s, }); } fn write_filter_primitive_attrs(&mut self, fe: &FilterPrimitive) { if let Some(n) = fe.x { self.write_svg_attribute(AId::X, &n); } if let Some(n) = fe.y { self.write_svg_attribute(AId::Y, &n); } if let Some(n) = fe.width { self.write_svg_attribute(AId::Width, &n); } if let Some(n) = fe.height { self.write_svg_attribute(AId::Height, &n); } self.write_attribute(AId::ColorInterpolationFilters.to_str(), match fe.color_interpolation { ColorInterpolation::SRGB => "sRGB", ColorInterpolation::LinearRGB => "linearRGB" }); } fn write_filter_transfer_function(&mut self, eid: EId, fe: &TransferFunction) { self.start_svg_element(eid); match fe { TransferFunction::Identity => { self.write_svg_attribute(AId::Type, "identity"); } TransferFunction::Table(ref values) => { self.write_svg_attribute(AId::Type, "table"); self.write_numbers(AId::TableValues, values); } TransferFunction::Discrete(ref values) => { self.write_svg_attribute(AId::Type, "discrete"); self.write_numbers(AId::TableValues, values); } TransferFunction::Linear { slope, intercept } => { self.write_svg_attribute(AId::Type, "linear"); self.write_svg_attribute(AId::Slope, &slope); self.write_svg_attribute(AId::Intercept, &intercept); } TransferFunction::Gamma { amplitude, exponent, offset } => { self.write_svg_attribute(AId::Type, "gamma"); self.write_svg_attribute(AId::Amplitude, &litude); self.write_svg_attribute(AId::Exponent, &exponent); self.write_svg_attribute(AId::Offset, &offset); } } self.end_element(); } fn write_image_data(&mut self, data: &ImageData, format: ImageFormat) { match data { ImageData::Path(ref path) => { self.write_attribute("xlink:href", &path.to_str().unwrap()); } ImageData::Raw(ref data) => { self.write_attribute_raw("xlink:href", |buf| { buf.extend_from_slice(b"data:image/"); buf.extend_from_slice(match format { ImageFormat::PNG => b"png", ImageFormat::JPEG => b"jpg", ImageFormat::SVG => b"svg+xml", }); buf.extend_from_slice(b";base64, "); let mut enc = base64::write::EncoderWriter::new(buf, base64::STANDARD); enc.write_all(data).unwrap(); enc.finish().unwrap(); }); } } } } fn has_xlink(tree: &Tree) -> bool { for n in tree.root().descendants() { match *n.borrow() { NodeKind::Filter(ref filter) => { for fe in &filter.children { if let FilterKind::FeImage(ref img) = fe.kind { if let FeImageKind::Image(..) = img.data { return true; } } } } NodeKind::Image(_) => { return true; } _ => {} } } false } fn write_base_grad( g: &BaseGradient, xml: &mut XmlWriter, ) { xml.write_units(AId::GradientUnits, g.units, Units::ObjectBoundingBox); xml.write_transform(AId::GradientTransform, g.transform); match g.spread_method { SpreadMethod::Pad => {}, SpreadMethod::Reflect => xml.write_svg_attribute(AId::SpreadMethod, "reflect"), SpreadMethod::Repeat => xml.write_svg_attribute(AId::SpreadMethod, "repeat"), } for s in &g.stops { xml.start_svg_element(EId::Stop); xml.write_svg_attribute(AId::Offset, &s.offset.value()); xml.write_svg_attribute(AId::StopColor, &s.color); if !s.opacity.is_default() { xml.write_svg_attribute(AId::StopOpacity, &s.opacity.value()); } xml.end_element(); } } fn write_path( path: &Path, is_clip_path: bool, clip_path: Option<&str>, xml: &mut XmlWriter, ) { xml.start_svg_element(EId::Path); if !path.id.is_empty() { xml.write_svg_attribute(AId::Id, &path.id); } write_fill(&path.fill, is_clip_path, xml); write_stroke(&path.stroke, xml); xml.write_visibility(path.visibility); match path.rendering_mode { ShapeRendering::OptimizeSpeed => { xml.write_svg_attribute(AId::ShapeRendering, "optimizeSpeed"); } ShapeRendering::CrispEdges => { xml.write_svg_attribute(AId::ShapeRendering, "crispEdges") } ShapeRendering::GeometricPrecision => {} } if let Some(ref id) = clip_path { xml.write_func_iri(AId::ClipPath, id); } xml.write_transform(AId::Transform, path.transform); xml.write_attribute_raw("d", |buf| { for seg in path.data.iter() { match *seg { PathSegment::MoveTo { x, y } => { buf.extend_from_slice(b"M "); x.write_buf(buf); buf.push(b' '); y.write_buf(buf); buf.push(b' '); } PathSegment::LineTo { x, y } => { buf.extend_from_slice(b"L "); x.write_buf(buf); buf.push(b' '); y.write_buf(buf); buf.push(b' '); } PathSegment::CurveTo { x1, y1, x2, y2, x, y } => { buf.extend_from_slice(b"C "); x1.write_buf(buf); buf.push(b' '); y1.write_buf(buf); buf.push(b' '); x2.write_buf(buf); buf.push(b' '); y2.write_buf(buf); buf.push(b' '); x.write_buf(buf); buf.push(b' '); y.write_buf(buf); buf.push(b' '); } PathSegment::ClosePath => { buf.extend_from_slice(b"Z "); } } } if !path.data.is_empty() { buf.pop(); } }); xml.end_element(); } fn write_fill( fill: &Option, is_clip_path: bool, xml: &mut XmlWriter, ) { match fill { Some(ref fill) => { match fill.paint { Paint::Color(c) => { if c != Color::black() { xml.write_svg_attribute(AId::Fill, &c); } } Paint::Link(ref id) => { xml.write_func_iri(AId::Fill, id); } } if !fill.opacity.is_default() { xml.write_svg_attribute(AId::FillOpacity, &fill.opacity.value()); } if !fill.rule.is_default() { let name = if is_clip_path { AId::ClipRule } else { AId::FillRule }; xml.write_svg_attribute(name, "evenodd"); } } None => { xml.write_svg_attribute(AId::Fill, "none"); } } } fn write_stroke( stroke: &Option, xml: &mut XmlWriter, ) { if let Some(ref stroke) = stroke { match stroke.paint { Paint::Color(ref c) => xml.write_svg_attribute(AId::Stroke, c), Paint::Link(ref id) => xml.write_func_iri(AId::Stroke, id), } if !stroke.opacity.is_default() { xml.write_svg_attribute(AId::StrokeOpacity, &stroke.opacity.value()); } if !(stroke.dashoffset as f64).is_fuzzy_zero() { xml.write_svg_attribute(AId::StrokeDashoffset, &stroke.dashoffset) } if !stroke.miterlimit.is_default() { xml.write_svg_attribute(AId::StrokeMiterlimit, &stroke.miterlimit.value()); } if !stroke.width.is_default() { xml.write_svg_attribute(AId::StrokeWidth, &stroke.width.value()); } match stroke.linecap { LineCap::Butt => {} LineCap::Round => xml.write_svg_attribute(AId::StrokeLinecap, "round"), LineCap::Square => xml.write_svg_attribute(AId::StrokeLinecap, "square"), } match stroke.linejoin { LineJoin::Miter => {} LineJoin::Round => xml.write_svg_attribute(AId::StrokeLinejoin, "round"), LineJoin::Bevel => xml.write_svg_attribute(AId::StrokeLinejoin, "bevel"), } if let Some(ref array) = stroke.dasharray { xml.write_numbers(AId::StrokeDasharray, array); } } } resvg-0.8.0/usvg/src/tree/mod.rs000066400000000000000000000201521352576375700165300ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. //! Implementation of the nodes tree. use std::cell::Ref; use std::path; pub use self::{nodes::*, attributes::*, pathdata::*}; use crate::{svgtree, Rect, Error, Options, XmlOptions}; mod attributes; mod export; mod nodes; mod numbers; mod pathdata; /// Basic traits for tree manipulations. pub mod prelude { pub use crate::IsDefault; pub use crate::IsValidLength; pub use crate::TransformFromBBox; pub use crate::tree::FuzzyEq; pub use crate::tree::FuzzyZero; pub use super::NodeExt; } /// Alias for `rctree::Node`. pub type Node = rctree::Node; // TODO: impl a Debug /// A nodes tree container. #[allow(missing_debug_implementations)] #[derive(Clone)] pub struct Tree { root: Node, } impl Tree { /// Parses `Tree` from the SVG data. /// /// Can contain an SVG string or a gzip compressed data. pub fn from_data(data: &[u8], opt: &Options) -> Result { if data.starts_with(&[0x1f, 0x8b]) { let text = deflate(data, data.len())?; Self::from_str(&text, opt) } else { let text = ::std::str::from_utf8(data).map_err(|_| Error::NotAnUtf8Str)?; Self::from_str(text, opt) } } /// Parses `Tree` from the SVG string. pub fn from_str(text: &str, opt: &Options) -> Result { let doc = svgtree::Document::parse(text).map_err(Error::ParsingFailed)?; Self::from_dom(doc, &opt) } /// Parses `Tree` from the `svgdom::Document`. /// /// An empty `Tree` will be returned on any error. fn from_dom(doc: svgtree::Document, opt: &Options) -> Result { super::convert::convert_doc(&doc, opt) } /// Parses `Tree` from the file. pub fn from_file>( path: P, opt: &Options, ) -> Result { let text = load_svg_file(path.as_ref())?; Self::from_str(&text, opt) } /// Creates a new `Tree`. pub fn create(svg: Svg) -> Self { let mut root_node = Node::new(NodeKind::Svg(svg)); let defs_node = Node::new(NodeKind::Defs); root_node.append(defs_node); Tree { root: root_node, } } /// Returns the `Svg` node. #[inline] pub fn root(&self) -> Node { self.root.clone() } /// Returns the `Svg` node value. #[inline] pub fn svg_node(&self) -> Ref { Ref::map(self.root.borrow(), |v| { match *v { NodeKind::Svg(ref svg) => svg, _ => unreachable!(), } }) } /// Returns the `Defs` node. #[inline] pub fn defs(&self) -> Node { self.root.first_child().unwrap() } /// Checks that `node` is part of the `Defs` children. pub fn is_in_defs(&self, node: &Node) -> bool { let defs = self.defs(); node.ancestors().any(|n| n == defs) } /// Appends `NodeKind` to the `Defs` node. pub fn append_to_defs(&mut self, kind: NodeKind) -> Node { debug_assert!(self.defs_by_id(kind.id()).is_none(), "Element #{} already exists in 'defs'.", kind.id()); let new_node = Node::new(kind); self.defs().append(new_node.clone()); new_node } /// Returns `defs` child node by ID. pub fn defs_by_id(&self, id: &str) -> Option { for n in self.defs().children() { if &*n.id() == id { return Some(n); } } None } /// Returns renderable node by ID. /// /// If an empty ID is provided, than this method will always return `None`. /// Even if tree has nodes with empty ID. pub fn node_by_id(&self, id: &str) -> Option { if id.is_empty() { return None; } for node in self.root().descendants() { if !self.is_in_defs(&node) { if &*node.id() == id { return Some(node); } } } None } /// Converts an SVG. #[inline] pub fn to_string(&self, opt: XmlOptions) -> String { export::convert(self, opt) } } /// Additional `Node` methods. pub trait NodeExt { /// Returns node's ID. /// /// If a current node doesn't support ID - an empty string /// will be returned. fn id(&self) -> Ref; /// Returns node's transform. /// /// If a current node doesn't support transformation - a default /// transform will be returned. fn transform(&self) -> Transform; /// Returns node's absolute transform. /// /// If a current node doesn't support transformation - a default /// transform will be returned. fn abs_transform(&self) -> Transform; /// Appends `kind` as a node child. /// /// Shorthand for `Node::append(Node::new(Box::new(kind)))`. fn append_kind(&mut self, kind: NodeKind) -> Node; /// Returns a node's tree. fn tree(&self) -> Tree; /// Calculates node's absolute bounding box. /// /// Can be expensive on large paths and groups. fn calculate_bbox(&self) -> Option; } impl NodeExt for Node { #[inline] fn id(&self) -> Ref { Ref::map(self.borrow(), |v| v.id()) } #[inline] fn transform(&self) -> Transform { self.borrow().transform() } fn abs_transform(&self) -> Transform { let mut ts_list = Vec::new(); for p in self.ancestors().skip(1) { ts_list.push(p.transform()); } let mut abs_ts = Transform::default(); for ts in ts_list.iter().rev() { abs_ts.append(ts); } abs_ts } #[inline] fn append_kind(&mut self, kind: NodeKind) -> Node { let new_node = Node::new(kind); self.append(new_node.clone()); new_node } #[inline] fn tree(&self) -> Tree { Tree { root: self.root() } } #[inline] fn calculate_bbox(&self) -> Option { calc_node_bbox(self, self.abs_transform()) } } /// Loads SVG, SVGZ file content. pub fn load_svg_file(path: &path::Path) -> Result { use std::fs; use std::io::Read; use std::path::Path; let mut file = fs::File::open(path).map_err(|_| Error::FileOpenFailed)?; let length = file.metadata().map_err(|_| Error::FileOpenFailed)?.len() as usize + 1; let ext = if let Some(ext) = Path::new(path).extension() { ext.to_str().map(|s| s.to_lowercase()).unwrap_or_default() } else { String::new() }; match ext.as_str() { "svgz" => { deflate(&file, length) } "svg" => { let mut s = String::with_capacity(length); file.read_to_string(&mut s).map_err(|_| Error::NotAnUtf8Str)?; Ok(s) } _ => { Err(Error::InvalidFileSuffix) } } } fn deflate(inner: R, len: usize) -> Result { use std::io::Read; let mut decoder = libflate::gzip::Decoder::new(inner).map_err(|_| Error::MalformedGZip)?; let mut decoded = String::with_capacity(len * 2); decoder.read_to_string(&mut decoded).map_err(|_| Error::NotAnUtf8Str)?; Ok(decoded) } fn calc_node_bbox( node: &Node, ts: Transform, ) -> Option { let mut ts2 = ts; ts2.append(&node.transform()); match *node.borrow() { NodeKind::Path(ref path) => { path.data.bbox_with_transform(ts2, path.stroke.as_ref()) } NodeKind::Image(ref img) => { let path = PathData::from_rect(img.view_box.rect); path.bbox_with_transform(ts2, None) } NodeKind::Svg(_) | NodeKind::Group(_) => { let mut bbox = Rect::new_bbox(); for child in node.children() { if let Some(c_bbox) = calc_node_bbox(&child, ts2) { bbox = bbox.expand(c_bbox); } } Some(bbox) } _ => None, } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������resvg-0.8.0/usvg/src/tree/nodes.rs������������������������������������������������������������������0000664�0000000�0000000�00000043335�13525763757�0017071�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::ops::Deref; use std::rc::Rc; use crate::geom::*; use super::attributes::*; use super::pathdata::PathData; // TODO: implement Default for all /// Node's kind. #[allow(missing_docs)] #[derive(Clone, Debug)] pub enum NodeKind { Svg(Svg), Defs, LinearGradient(LinearGradient), RadialGradient(RadialGradient), ClipPath(ClipPath), Mask(Mask), Pattern(Pattern), Filter(Filter), Path(Path), Image(Image), Group(Group), } impl NodeKind { /// Returns node's ID. /// /// If a current node doesn't support ID - an empty string /// will be returned. pub fn id(&self) -> &str { match *self { NodeKind::Svg(_) => "", NodeKind::Defs => "", NodeKind::LinearGradient(ref e) => e.id.as_str(), NodeKind::RadialGradient(ref e) => e.id.as_str(), NodeKind::ClipPath(ref e) => e.id.as_str(), NodeKind::Mask(ref e) => e.id.as_str(), NodeKind::Pattern(ref e) => e.id.as_str(), NodeKind::Filter(ref e) => e.id.as_str(), NodeKind::Path(ref e) => e.id.as_str(), NodeKind::Image(ref e) => e.id.as_str(), NodeKind::Group(ref e) => e.id.as_str(), } } /// Returns node's transform. /// /// If a current node doesn't support transformation - a default /// transform will be returned. pub fn transform(&self) -> Transform { match *self { NodeKind::Svg(_) => Transform::default(), NodeKind::Defs => Transform::default(), NodeKind::LinearGradient(ref e) => e.transform, NodeKind::RadialGradient(ref e) => e.transform, NodeKind::ClipPath(ref e) => e.transform, NodeKind::Mask(_) => Transform::default(), NodeKind::Pattern(ref e) => e.transform, NodeKind::Filter(_) => Transform::default(), NodeKind::Path(ref e) => e.transform, NodeKind::Image(ref e) => e.transform, NodeKind::Group(ref e) => e.transform, } } } /// An SVG root element. #[derive(Clone, Copy, Debug)] pub struct Svg { /// Image size. /// /// Size of an image that should be created to fit the SVG. /// /// `width` and `height` in SVG. pub size: Size, /// SVG viewbox. /// /// Specifies which part of the SVG image should be rendered. /// /// `viewBox` and `preserveAspectRatio` in SVG. pub view_box: ViewBox, } /// A path element. #[derive(Clone, Debug)] pub struct Path { /// Element's ID. /// /// Taken from the SVG itself. /// Isn't automatically generated. /// Can be empty. pub id: String, /// Element transform. pub transform: Transform, /// Element visibility. pub visibility: Visibility, /// Fill style. pub fill: Option, /// Stroke style. pub stroke: Option, /// Rendering mode. /// /// `shape-rendering` in SVG. pub rendering_mode: ShapeRendering, /// Segments list. /// /// All segments are in absolute coordinates. pub data: Rc, } impl Default for Path { fn default() -> Self { Path { id: String::new(), transform: Transform::default(), visibility: Visibility::Visible, fill: None, stroke: None, rendering_mode: ShapeRendering::default(), data: Rc::new(PathData::default()), } } } /// A raster image element. /// /// `image` element in SVG. #[derive(Clone, Debug)] pub struct Image { /// Element's ID. /// /// Taken from the SVG itself. /// Isn't automatically generated. /// Can be empty. pub id: String, /// Element transform. pub transform: Transform, /// Element visibility. pub visibility: Visibility, /// An image rectangle in which it should be fit. /// /// Combination of the `x`, `y`, `width`, `height` and `preserveAspectRatio` /// attributes. pub view_box: ViewBox, /// Rendering mode. /// /// `image-rendering` in SVG. pub rendering_mode: ImageRendering, /// Image data. pub data: ImageData, /// Image data kind. pub format: ImageFormat, } /// A group container. /// /// The preprocessor will remove all groups that don't impact rendering. /// Those that left is just an indicator that a new canvas should be created. /// /// `g` element in SVG. #[derive(Clone, Debug)] pub struct Group { /// Element's ID. /// /// Taken from the SVG itself. /// Isn't automatically generated. /// Can be empty. pub id: String, /// Element transform. pub transform: Transform, /// Group opacity. /// /// After the group is rendered we should combine /// it with a parent group using the specified opacity. pub opacity: Opacity, /// Element clip path. pub clip_path: Option, /// Element mask. pub mask: Option, /// Element filter. pub filter: Option, } impl Default for Group { fn default() -> Self { Group { id: String::new(), transform: Transform::default(), opacity: Opacity::default(), clip_path: None, mask: None, filter: None, } } } /// A generic gradient. #[derive(Clone, Debug)] pub struct BaseGradient { /// Coordinate system units. /// /// `gradientUnits` in SVG. pub units: Units, /// Gradient transform. /// /// `gradientTransform` in SVG. pub transform: Transform, /// Gradient spreading method. /// /// `spreadMethod` in SVG. pub spread_method: SpreadMethod, /// A list of `stop` elements. pub stops: Vec, } /// A linear gradient. /// /// `linearGradient` element in SVG. #[allow(missing_docs)] #[derive(Clone, Debug)] pub struct LinearGradient { /// Element's ID. /// /// Taken from the SVG itself. /// Can't be empty. pub id: String, pub x1: f64, pub y1: f64, pub x2: f64, pub y2: f64, /// Base gradient data. pub base: BaseGradient, } impl Deref for LinearGradient { type Target = BaseGradient; fn deref(&self) -> &Self::Target { &self.base } } /// A radial gradient. /// /// `radialGradient` element in SVG. #[allow(missing_docs)] #[derive(Clone, Debug)] pub struct RadialGradient { /// Element's ID. /// /// Taken from the SVG itself. /// Can't be empty. pub id: String, pub cx: f64, pub cy: f64, pub r: PositiveNumber, pub fx: f64, pub fy: f64, /// Base gradient data. pub base: BaseGradient, } impl Deref for RadialGradient { type Target = BaseGradient; fn deref(&self) -> &Self::Target { &self.base } } /// Gradient's stop element. /// /// `stop` element in SVG. #[derive(Clone, Copy, Debug)] pub struct Stop { /// Gradient stop offset. /// /// `offset` in SVG. pub offset: StopOffset, /// Gradient stop color. /// /// `stop-color` in SVG. pub color: Color, /// Gradient stop opacity. /// /// `stop-opacity` in SVG. pub opacity: Opacity, } /// A clip-path element. /// /// `clipPath` element in SVG. #[derive(Clone, Debug)] pub struct ClipPath { /// Element's ID. /// /// Taken from the SVG itself. /// Can't be empty. pub id: String, /// Coordinate system units. /// /// `clipPathUnits` in SVG. pub units: Units, /// Clip path transform. /// /// `transform` in SVG. pub transform: Transform, /// Additional clip path. /// /// `clip-path` in SVG. pub clip_path: Option, } impl Default for ClipPath { fn default() -> Self { ClipPath { id: String::new(), units: Units::UserSpaceOnUse, transform: Transform::default(), clip_path: None, } } } /// A mask element. /// /// `mask` element in SVG. #[derive(Clone, Debug)] pub struct Mask { /// Element's ID. /// /// Taken from the SVG itself. /// Can't be empty. pub id: String, /// Coordinate system units. /// /// `maskUnits` in SVG. pub units: Units, /// Content coordinate system units. /// /// `maskContentUnits` in SVG. pub content_units: Units, /// Mask rectangle. /// /// `x`, `y`, `width` and `height` in SVG. pub rect: Rect, /// Additional mask. /// /// `mask` in SVG. pub mask: Option, } /// A pattern element. /// /// `pattern` element in SVG. #[derive(Clone, Debug)] pub struct Pattern { /// Element's ID. /// /// Taken from the SVG itself. /// Can't be empty. pub id: String, /// Coordinate system units. /// /// `patternUnits` in SVG. pub units: Units, // TODO: should not be accessible when `viewBox` is present. /// Content coordinate system units. /// /// `patternContentUnits` in SVG. pub content_units: Units, /// Pattern transform. /// /// `patternTransform` in SVG. pub transform: Transform, /// Pattern rectangle. /// /// `x`, `y`, `width` and `height` in SVG. pub rect: Rect, /// Pattern viewbox. pub view_box: Option, } /// A filter element. /// /// `filter` element in the SVG. #[derive(Clone, Debug)] pub struct Filter { /// Element's ID. /// /// Taken from the SVG itself. /// Can't be empty. pub id: String, /// Region coordinate system units. /// /// `filterUnits` in the SVG. pub units: Units, /// Content coordinate system units. /// /// `primitiveUnits` in the SVG. pub primitive_units: Units, /// Filter region. /// /// `x`, `y`, `width` and `height` in the SVG. pub rect: Rect, /// A list of filter primitives. pub children: Vec, } /// A filter primitive element. #[derive(Clone, Debug)] pub struct FilterPrimitive { /// `x` coordinate of the filter subregion. pub x: Option, /// `y` coordinate of the filter subregion. pub y: Option, /// The filter subregion width. pub width: Option, /// The filter subregion height. pub height: Option, /// Color interpolation mode. /// /// `color-interpolation-filters` in the SVG. pub color_interpolation: ColorInterpolation, /// Assigned name for this filter primitive. /// /// `result` in the SVG. pub result: String, /// Filter primitive kind. pub kind: FilterKind, } /// A filter kind. #[allow(missing_docs)] #[derive(Clone, Debug)] pub enum FilterKind { FeBlend(FeBlend), FeColorMatrix(FeColorMatrix), FeComponentTransfer(FeComponentTransfer), FeComposite(FeComposite), FeFlood(FeFlood), FeGaussianBlur(FeGaussianBlur), FeImage(FeImage), FeMerge(FeMerge), FeOffset(FeOffset), FeTile(FeTile), } /// A blend filter primitive. /// /// `feBlend` element in the SVG. #[derive(Clone, Debug)] pub struct FeBlend { /// Identifies input for the given filter primitive. /// /// `in` in the SVG. pub input1: FilterInput, /// Identifies input for the given filter primitive. /// /// `in2` in the SVG. pub input2: FilterInput, /// A blending mode. /// /// `mode` in the SVG. pub mode: FeBlendMode, } /// A color matrix filter primitive. /// /// `feColorMatrix` element in the SVG. #[derive(Clone, Debug)] pub struct FeColorMatrix { /// Identifies input for the given filter primitive. /// /// `in` in the SVG. pub input: FilterInput, /// A matrix kind. /// /// `type` in the SVG. pub kind: FeColorMatrixKind, } /// A color matrix filter primitive kind. #[derive(Clone, Debug)] #[allow(missing_docs)] pub enum FeColorMatrixKind { Matrix(Vec), // Guarantee to have 20 numbers. Saturate(NormalizedValue), HueRotate(f64), LuminanceToAlpha, } impl Default for FeColorMatrixKind { fn default() -> Self { FeColorMatrixKind::Matrix(vec![ 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, ]) } } /// A component-wise remapping filter primitive. /// /// `feComponentTransfer` element in the SVG. #[derive(Clone, Debug)] pub struct FeComponentTransfer { /// Identifies input for the given filter primitive. /// /// `in` in the SVG. pub input: FilterInput, /// `feFuncR` in the SVG. pub func_r: TransferFunction, /// `feFuncG` in the SVG. pub func_g: TransferFunction, /// `feFuncB` in the SVG. pub func_b: TransferFunction, /// `feFuncA` in the SVG. pub func_a: TransferFunction, } /// A transfer function used by `FeComponentTransfer`. /// /// https://www.w3.org/TR/SVG11/filters.html#transferFuncElements #[derive(Clone, Debug)] pub enum TransferFunction { /// Keeps a component as is. Identity, /// Applies a linear interpolation to a component. /// /// The number list can be empty. Table(Vec), /// Applies a step function to a component. /// /// The number list can be empty. Discrete(Vec), /// Applies a linear shift to a component. #[allow(missing_docs)] Linear { slope: f64, intercept: f64, }, /// Applies an exponential shift to a component. #[allow(missing_docs)] Gamma { amplitude: f64, exponent: f64, offset: f64, }, } impl TransferFunction { /// Applies a transfer function to a provided color component. /// /// Requires a non-premultiplied color component. pub fn apply(&self, c: u8) -> u8 { (f64_bound(0.0, self.apply_impl(c as f64 / 255.0), 1.0) * 255.0) as u8 } fn apply_impl(&self, c: f64) -> f64 { use std::cmp; match self { TransferFunction::Identity => { c } TransferFunction::Table(ref values) => { if values.is_empty() { return c; } let n = values.len() - 1; let k = (c * (n as f64)).floor() as usize; let k = cmp::min(k, n); if k == n { return values[k]; } let vk = values[k]; let vk1 = values[k + 1]; let k = k as f64; let n = n as f64; vk + (c - k / n) * n * (vk1 - vk) } TransferFunction::Discrete(ref values) => { if values.is_empty() { return c; } let n = values.len(); let k = (c * (n as f64)).floor() as usize; values[cmp::min(k, n - 1)] } TransferFunction::Linear { slope, intercept } => { slope * c + intercept } TransferFunction::Gamma { amplitude, exponent, offset } => { amplitude * c.powf(*exponent) + offset } } } } /// A composite filter primitive. /// /// `feComposite` element in the SVG. #[derive(Clone, Debug)] pub struct FeComposite { /// Identifies input for the given filter primitive. /// /// `in` in the SVG. pub input1: FilterInput, /// Identifies input for the given filter primitive. /// /// `in2` in the SVG. pub input2: FilterInput, /// A compositing operation. /// /// `operator` in the SVG. pub operator: FeCompositeOperator, } /// A flood filter primitive. /// /// `feFlood` element in the SVG. #[derive(Clone, Copy, Debug)] pub struct FeFlood { /// A flood color. /// /// `flood-color` in the SVG. pub color: Color, /// A flood opacity. /// /// `flood-opacity` in the SVG. pub opacity: Opacity, } /// A Gaussian blur filter primitive. /// /// `feGaussianBlur` element in the SVG. #[derive(Clone, Debug)] pub struct FeGaussianBlur { /// Identifies input for the given filter primitive. /// /// `in` in the SVG. pub input: FilterInput, /// A standard deviation along the X-axis. /// /// `stdDeviation` in the SVG. pub std_dev_x: PositiveNumber, /// A standard deviation along the Y-axis. /// /// `stdDeviation` in the SVG. pub std_dev_y: PositiveNumber, } /// An image filter primitive. /// /// `feImage` element in the SVG. #[derive(Clone, Debug)] pub struct FeImage { /// Value of the `preserveAspectRatio` attribute. pub aspect: AspectRatio, /// Rendering method. /// /// `image-rendering` in SVG. pub rendering_mode: ImageRendering, /// Image data. pub data: FeImageKind, } /// A merge filter primitive. /// /// `feMerge` element in the SVG. #[derive(Clone, Debug)] pub struct FeMerge { /// List of input layers that should be merged. /// /// List of `feMergeNode`'s in the SVG. pub inputs: Vec, } /// An offset filter primitive. /// /// `feOffset` element in the SVG. #[derive(Clone, Debug)] pub struct FeOffset { /// Identifies input for the given filter primitive. /// /// `in` in the SVG. pub input: FilterInput, /// The amount to offset the input graphic along the X-axis. pub dx: f64, /// The amount to offset the input graphic along the Y-axis. pub dy: f64, } /// A tile filter primitive. /// /// `feTile` element in the SVG. #[derive(Clone, Debug)] pub struct FeTile { /// Identifies input for the given filter primitive. /// /// `in` in the SVG. pub input: FilterInput, } #[cfg(test)] mod tests { use super::*; #[test] fn node_kind_size() { assert!(std::mem::size_of::() <= 256); } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������resvg-0.8.0/usvg/src/tree/numbers.rs����������������������������������������������������������������0000664�0000000�0000000�00000010627�13525763757�0017432�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use svgtypes::{FuzzyEq, FuzzyZero}; use crate::geom::f64_bound; macro_rules! wrap { ($name:ident) => { impl From for $name { #[inline] fn from(n: f64) -> Self { $name::new(n) } } impl PartialEq for $name { #[inline] fn eq(&self, other: &Self) -> bool { self.0.fuzzy_eq(&other.0) } } }; } /// A normalized value. /// /// Just like `f64` but immutable and guarantee to be in a 0..1 range. #[derive(Clone, Copy, Debug)] pub struct NormalizedValue(f64); impl NormalizedValue { /// Creates a new `NormalizedValue` value. #[inline] pub fn new(n: f64) -> Self { debug_assert!(n.is_finite()); debug_assert!(n >= 0.0 && n <= 1.0); NormalizedValue(f64_bound(0.0, n, 1.0)) } /// Returns an underlying value. #[inline] pub fn value(&self) -> f64 { self.0 } } impl std::ops::Mul for NormalizedValue { type Output = Self; #[inline] fn mul(self, rhs: NormalizedValue) -> Self::Output { NormalizedValue::new(self.0 * rhs.0) } } impl Default for NormalizedValue { #[inline] fn default() -> Self { NormalizedValue::new(1.0) } } wrap!(NormalizedValue); /// An alias to `NormalizedValue`. pub type Opacity = NormalizedValue; /// An alias to `NormalizedValue`. pub type StopOffset = NormalizedValue; /// An alias to `NormalizedValue`. pub type CompositingCoefficient = NormalizedValue; /// A `stroke-width` value. /// /// Just like `f64` but immutable and guarantee to be >0.0. #[derive(Clone, Copy, Debug)] pub struct StrokeWidth(f64); impl StrokeWidth { /// Creates a new `StrokeWidth` value. #[inline] pub fn new(n: f64) -> Self { debug_assert!(n.is_finite()); debug_assert!(n > 0.0); // Fallback to `1.0` when value is invalid. let n = if !(n > 0.0) { 1.0 } else { n }; StrokeWidth(n) } /// Returns an underlying value. #[inline] pub fn value(&self) -> f64 { self.0 } } impl Default for StrokeWidth { #[inline] fn default() -> Self { StrokeWidth::new(1.0) } } wrap!(StrokeWidth); /// A `stroke-miterlimit` value. /// /// Just like `f64` but immutable and guarantee to be >=1.0. #[derive(Clone, Copy, Debug)] pub struct StrokeMiterlimit(f64); impl StrokeMiterlimit { /// Creates a new `StrokeMiterlimit` value. #[inline] pub fn new(n: f64) -> Self { debug_assert!(n.is_finite()); debug_assert!(n >= 1.0); let n = if !(n >= 1.0) { 1.0 } else { n }; StrokeMiterlimit(n) } /// Returns an underlying value. #[inline] pub fn value(&self) -> f64 { self.0 } } impl Default for StrokeMiterlimit { #[inline] fn default() -> Self { StrokeMiterlimit::new(4.0) } } wrap!(StrokeMiterlimit); /// A `font-size` value. /// /// Just like `f64` but immutable and guarantee to be >0.0. #[derive(Clone, Copy, Debug)] pub struct FontSize(f64); impl FontSize { /// Creates a new `FontSize` value. #[inline] pub fn new(n: f64) -> Self { debug_assert!(n.is_finite()); debug_assert!(n > 0.0); // Fallback to `12.0` when value is invalid. let n = if !(n > 0.0) { 12.0 } else { n }; FontSize(n) } /// Returns an underlying value. #[inline] pub fn value(&self) -> f64 { self.0 } } wrap!(FontSize); /// A positive number. /// /// Just like `f64` but immutable and guarantee to be >=0.0 #[derive(Clone, Copy, Debug)] pub struct PositiveNumber(f64); impl PositiveNumber { /// Creates a new `PositiveNumber` value. #[inline] pub fn new(n: f64) -> Self { debug_assert!(n.is_finite()); debug_assert!(!n.is_sign_negative()); // Fallback to 0.0 when value is invalid. let n = if n.is_sign_negative() { 0.0 } else { n }; PositiveNumber(n) } /// Returns an underlying value. #[inline] pub fn value(&self) -> f64 { self.0 } /// Checks that the current number is zero. #[inline] pub fn is_zero(&self) -> bool { self.0.is_fuzzy_zero() } } wrap!(PositiveNumber); ���������������������������������������������������������������������������������������������������������resvg-0.8.0/usvg/src/tree/pathdata.rs���������������������������������������������������������������0000664�0000000�0000000�00000041337�13525763757�0017547�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::rc::Rc; use svgtypes::FuzzyZero; use kurbo::{ParamCurveArclen, ParamCurveExtrema}; use crate::{Rect, Line}; use super::Transform; /// A path's absolute segment. /// /// Unlike the SVG spec, can contain only `M`, `L`, `C` and `Z` segments. /// All other segments will be converted into this one. #[allow(missing_docs)] #[derive(Clone, Copy, Debug)] pub enum PathSegment { MoveTo { x: f64, y: f64, }, LineTo { x: f64, y: f64, }, CurveTo { x1: f64, y1: f64, x2: f64, y2: f64, x: f64, y: f64, }, ClosePath, } /// An SVG path data container. /// /// All segments are in absolute coordinates. #[derive(Clone, Default, Debug)] pub struct PathData(pub Vec); /// A reference-counted `PathData`. /// /// `PathData` is usually pretty big and it's expensive to clone it, /// so we are using `Rc`. pub type SharedPathData = Rc; impl PathData { /// Creates a new path. #[inline] pub fn new() -> Self { PathData(Vec::new()) } /// Creates a new path with a specified capacity. #[inline] pub fn with_capacity(capacity: usize) -> Self { PathData(Vec::with_capacity(capacity)) } /// Creates a path from a rect. #[inline] pub fn from_rect(rect: Rect) -> Self { PathData(vec![ PathSegment::MoveTo { x: rect.x(), y: rect.y() }, PathSegment::LineTo { x: rect.right(), y: rect.y() }, PathSegment::LineTo { x: rect.right(), y: rect.bottom() }, PathSegment::LineTo { x: rect.x(), y: rect.bottom() }, PathSegment::ClosePath, ]) } /// Pushes a MoveTo segment to the path. #[inline] pub fn push_move_to(&mut self, x: f64, y: f64) { self.push(PathSegment::MoveTo { x, y }); } /// Pushes a LineTo segment to the path. #[inline] pub fn push_line_to(&mut self, x: f64, y: f64) { self.push(PathSegment::LineTo { x, y }); } /// Pushes a CurveTo segment to the path. #[inline] pub fn push_curve_to(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, x: f64, y: f64) { self.push(PathSegment::CurveTo { x1, y1, x2, y2, x, y }); } /// Pushes a QuadTo segment to the path. /// /// Will be converted into cubic curve. #[inline] pub fn push_quad_to(&mut self, x1: f64, y1: f64, x: f64, y: f64) { let (prev_x, prev_y) = self.last_pos(); self.push(quad_to_curve(prev_x, prev_y, x1, y1, x, y)); } /// Pushes an ArcTo segment to the path. /// /// Arc will be converted into cubic curves. pub fn push_arc_to( &mut self, rx: f64, ry: f64, x_axis_rotation: f64, large_arc: bool, sweep: bool, x: f64, y: f64, ) { let (prev_x, prev_y) = self.last_pos(); let svg_arc = kurbo::SvgArc { from: kurbo::Vec2::new(prev_x, prev_y), to: kurbo::Vec2::new(x, y), radii: kurbo::Vec2::new(rx, ry), x_rotation: x_axis_rotation.to_radians(), large_arc, sweep, }; match kurbo::Arc::from_svg_arc(&svg_arc) { Some(arc) => { arc.to_cubic_beziers(0.1, |p1, p2, p| { self.push_curve_to(p1.x, p1.y, p2.x, p2.y, p.x, p.y); }); } None => { self.push_line_to(x, y); } } } /// Pushes a ClosePath segment to the path. #[inline] pub fn push_close_path(&mut self) { self.push(PathSegment::ClosePath); } #[inline] fn last_pos(&self) -> (f64, f64) { let seg = self.last().expect("path must not be empty").clone(); match seg { PathSegment::MoveTo { x, y } | PathSegment::LineTo { x, y } | PathSegment::CurveTo { x, y, .. } => { (x, y) } PathSegment::ClosePath => { panic!("the previous segment must be M/L/C") } } } /// Calculates path's bounding box. /// /// This operation is expensive. #[inline] pub fn bbox(&self) -> Option { calc_bbox(self) } /// Calculates path's bounding box with a specified transform. /// /// This operation is expensive. #[inline] pub fn bbox_with_transform( &self, ts: Transform, stroke: Option<&super::Stroke>, ) -> Option { calc_bbox_with_transform(self, ts, stroke) } /// Checks that path has a bounding box. /// /// This operation is expensive. #[inline] pub fn has_bbox(&self) -> bool { has_bbox(self) } /// Calculates path's length. /// /// Length from the first segment to the first MoveTo, ClosePath or slice end. /// /// This operation is expensive. #[inline] pub fn length(&self) -> f64 { calc_length(self) } /// Applies the transform to the path. #[inline] pub fn transform(&mut self, ts: Transform) { transform_path(self, ts); } /// Applies the transform to the path from the specified offset. #[inline] pub fn transform_from(&mut self, offset: usize, ts: Transform) { transform_path(&mut self[offset..], ts); } /// Returns an iterator over path subpaths. #[inline] pub fn subpaths(&self) -> SubPathIter { SubPathIter { path: self, index: 0, } } } impl std::ops::Deref for PathData { type Target = Vec; #[inline] fn deref(&self) -> &Self::Target { &self.0 } } impl std::ops::DerefMut for PathData { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } /// An iterator over `PathData` subpaths. #[allow(missing_debug_implementations)] pub struct SubPathIter<'a> { path: &'a [PathSegment], index: usize, } impl<'a> Iterator for SubPathIter<'a> { type Item = SubPathData<'a>; fn next(&mut self) -> Option { if self.index == self.path.len() { return None; } let mut i = self.index; while i < self.path.len() { match self.path[i] { PathSegment::MoveTo { .. } => { if i != self.index { break; } } PathSegment::ClosePath => { i += 1; break; } _ => {} } i += 1; } let start = self.index; self.index = i; Some(SubPathData(&self.path[start..i])) } } /// A reference to a `PathData` subpath. #[derive(Clone, Copy, Debug)] pub struct SubPathData<'a>(pub &'a [PathSegment]); impl<'a> SubPathData<'a> { /// Calculates path's bounding box. /// /// This operation is expensive. #[inline] pub fn bbox(&self) -> Option { calc_bbox(self) } /// Calculates path's bounding box with a specified transform. /// /// This operation is expensive. #[inline] pub fn bbox_with_transform( &self, ts: Transform, stroke: Option<&super::Stroke>, ) -> Option { calc_bbox_with_transform(self, ts, stroke) } /// Checks that path has a bounding box. /// /// This operation is expensive. #[inline] pub fn has_bbox(&self) -> bool { has_bbox(self) } /// Calculates path's length. /// /// This operation is expensive. #[inline] pub fn length(&self) -> f64 { calc_length(self) } } impl std::ops::Deref for SubPathData<'_> { type Target = [PathSegment]; #[inline] fn deref(&self) -> &Self::Target { self.0 } } fn calc_bbox(segments: &[PathSegment]) -> Option { debug_assert!(!segments.is_empty()); let mut prev_x = 0.0; let mut prev_y = 0.0; let mut minx = 0.0; let mut miny = 0.0; let mut maxx = 0.0; let mut maxy = 0.0; if let PathSegment::MoveTo { x, y } = segments[0].clone() { prev_x = x; prev_y = y; minx = x; miny = y; maxx = x; maxy = y; } for seg in segments.iter().cloned() { match seg { PathSegment::MoveTo { x, y } | PathSegment::LineTo { x, y } => { prev_x = x; prev_y = y; if x > maxx { maxx = x; } else if x < minx { minx = x; } if y > maxy { maxy = y; } else if y < miny { miny = y; } } PathSegment::CurveTo { x1, y1, x2, y2, x, y } => { let curve = kurbo::CubicBez { p0: kurbo::Vec2::new(prev_x, prev_y), p1: kurbo::Vec2::new(x1, y1), p2: kurbo::Vec2::new(x2, y2), p3: kurbo::Vec2::new(x, y), }; let r = curve.bounding_box(); if r.x0 < minx { minx = r.x0; } if r.x1 > maxx { maxx = r.x1; } if r.y0 < miny { miny = r.y0; } if r.y1 > maxy { maxy = r.y1; } } PathSegment::ClosePath => {} } } let width = maxx - minx; let height = maxy - miny; Rect::new(minx, miny, width, height) } fn calc_bbox_with_transform( segments: &[PathSegment], ts: Transform, stroke: Option<&super::Stroke>, ) -> Option { debug_assert!(!segments.is_empty()); let mut prev_x = 0.0; let mut prev_y = 0.0; let mut minx = 0.0; let mut miny = 0.0; let mut maxx = 0.0; let mut maxy = 0.0; if let Some(PathSegment::MoveTo { x, y }) = TransformedPath::new(segments, ts).next() { prev_x = x; prev_y = y; minx = x; miny = y; maxx = x; maxy = y; } for seg in TransformedPath::new(segments, ts) { match seg { PathSegment::MoveTo { x, y } | PathSegment::LineTo { x, y } => { prev_x = x; prev_y = y; if x > maxx { maxx = x; } else if x < minx { minx = x; } if y > maxy { maxy = y; } else if y < miny { miny = y; } } PathSegment::CurveTo { x1, y1, x2, y2, x, y } => { let curve = kurbo::CubicBez { p0: kurbo::Vec2::new(prev_x, prev_y), p1: kurbo::Vec2::new(x1, y1), p2: kurbo::Vec2::new(x2, y2), p3: kurbo::Vec2::new(x, y), }; let r = curve.bounding_box(); if r.x0 < minx { minx = r.x0; } if r.x1 > maxx { maxx = r.x1; } if r.y0 < miny { miny = r.y0; } if r.y1 > maxy { maxy = r.y1; } } PathSegment::ClosePath => {} } } // TODO: find a better way // It's an approximation, but it's better than nothing. if let Some(ref stroke) = stroke { let w = stroke.width.value() / 2.0; minx -= w; miny -= w; maxx += w; maxy += w; } let width = maxx - minx; let height = maxy - miny; Rect::new(minx, miny, width, height) } fn has_bbox(segments: &[PathSegment]) -> bool { debug_assert!(!segments.is_empty()); let mut prev_x = 0.0; let mut prev_y = 0.0; let mut minx = 0.0; let mut miny = 0.0; let mut maxx = 0.0; let mut maxy = 0.0; if let PathSegment::MoveTo { x, y } = segments[0] { prev_x = x; prev_y = y; minx = x; miny = y; maxx = x; maxy = y; } for seg in segments { match *seg { PathSegment::MoveTo { x, y } | PathSegment::LineTo { x, y } => { prev_x = x; prev_y = y; if x > maxx { maxx = x; } else if x < minx { minx = x; } if y > maxy { maxy = y; } else if y < miny { miny = y; } } PathSegment::CurveTo { x1, y1, x2, y2, x, y } => { let curve = kurbo::CubicBez { p0: kurbo::Vec2::new(prev_x, prev_y), p1: kurbo::Vec2::new(x1, y1), p2: kurbo::Vec2::new(x2, y2), p3: kurbo::Vec2::new(x, y), }; let r = curve.bounding_box(); if r.x0 < minx { minx = r.x0; } if r.x1 > maxx { maxx = r.x1; } if r.x0 < miny { miny = r.y0; } if r.y1 > maxy { maxy = r.y1; } } PathSegment::ClosePath => {} } let width = (maxx - minx) as f64; let height = (maxy - miny) as f64; if !(width.is_fuzzy_zero() || height.is_fuzzy_zero()) { return true; } } false } fn calc_length(segments: &[PathSegment]) -> f64 { debug_assert!(!segments.is_empty()); let (mut prev_x, mut prev_y) = { if let PathSegment::MoveTo { x, y } = segments[0] { (x, y) } else { panic!("first segment must be MoveTo"); } }; let start_x = prev_x; let start_y = prev_y; let mut is_first_seg = true; let mut length = 0.0f64; for seg in segments { match *seg { PathSegment::MoveTo { .. } => { if !is_first_seg { break; } } PathSegment::LineTo { x, y } => { length += Line::new(prev_x, prev_y, x, y).length(); prev_x = x; prev_y = y; } PathSegment::CurveTo { x1, y1, x2, y2, x, y } => { let curve = kurbo::CubicBez { p0: kurbo::Vec2::new(prev_x, prev_y), p1: kurbo::Vec2::new(x1, y1), p2: kurbo::Vec2::new(x2, y2), p3: kurbo::Vec2::new(x, y), }; length += curve.arclen(1.0); prev_x = x; prev_y = y; } PathSegment::ClosePath => { length += Line::new(prev_x, prev_y, start_x, start_y).length(); break; } } is_first_seg = false; } length } fn transform_path(segments: &mut [PathSegment], ts: Transform) { for seg in segments { match seg { PathSegment::MoveTo { x, y } => { ts.apply_to(x, y); } PathSegment::LineTo { x, y } => { ts.apply_to(x, y); } PathSegment::CurveTo { x1, y1, x2, y2, x, y } => { ts.apply_to(x1, y1); ts.apply_to(x2, y2); ts.apply_to(x, y); } PathSegment::ClosePath => {} } } } /// An iterator over transformed path segments. #[allow(missing_debug_implementations)] pub struct TransformedPath<'a> { segments: &'a [PathSegment], ts: Transform, idx: usize, } impl<'a> TransformedPath<'a> { /// Creates a new `TransformedPath` iterator. #[inline] pub fn new(segments: &'a [PathSegment], ts: Transform) -> Self { TransformedPath { segments, ts, idx: 0 } } } impl<'a> Iterator for TransformedPath<'a> { type Item = PathSegment; fn next(&mut self) -> Option { if self.idx == self.segments.len() { return None; } let seg = match self.segments[self.idx] { PathSegment::MoveTo { x, y } => { let (x, y) = self.ts.apply(x, y); PathSegment::MoveTo { x, y } } PathSegment::LineTo { x, y } => { let (x, y) = self.ts.apply(x, y); PathSegment::LineTo { x, y } } PathSegment::CurveTo { x1, y1, x2, y2, x, y } => { let (x1, y1) = self.ts.apply(x1, y1); let (x2, y2) = self.ts.apply(x2, y2); let (x, y) = self.ts.apply(x, y); PathSegment::CurveTo { x1, y1, x2, y2, x, y } } PathSegment::ClosePath => PathSegment::ClosePath, }; self.idx += 1; Some(seg) } } #[inline] fn quad_to_curve(px: f64, py: f64, x1: f64, y1: f64, x: f64, y: f64) -> PathSegment { #[inline] fn calc(n1: f64, n2: f64) -> f64 { (n1 + n2 * 2.0) / 3.0 } PathSegment::CurveTo { x1: calc(px, x1), y1: calc(py, y1), x2: calc(x, x1), y2: calc(y, y1), x, y, } } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������resvg-0.8.0/usvg/src/utils.rs�����������������������������������������������������������������������0000664�0000000�0000000�00000003720�13525763757�0016154�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. //! Some useful utilities. use crate::{tree, geom::*}; /// Converts `viewBox` to `Transform`. pub fn view_box_to_transform( view_box: Rect, aspect: tree::AspectRatio, img_size: Size, ) -> tree::Transform { let vr = view_box; let sx = img_size.width() / vr.width(); let sy = img_size.height() / vr.height(); let (sx, sy) = if aspect.align == tree::Align::None { (sx, sy) } else { let s = if aspect.slice { if sx < sy { sy } else { sx } } else { if sx > sy { sy } else { sx } }; (s, s) }; let x = -vr.x() * sx; let y = -vr.y() * sy; let w = img_size.width() - vr.width() * sx; let h = img_size.height() - vr.height() * sy; let (tx, ty) = aligned_pos(aspect.align, x, y, w, h); tree::Transform::new(sx, 0.0, 0.0, sy, tx, ty) } /// Returns object aligned position. pub fn aligned_pos( align: tree::Align, x: f64, y: f64, w: f64, h: f64, ) -> (f64, f64) { match align { tree::Align::None => (x, y ), tree::Align::XMinYMin => (x, y ), tree::Align::XMidYMin => (x + w / 2.0, y ), tree::Align::XMaxYMin => (x + w, y ), tree::Align::XMinYMid => (x, y + h / 2.0), tree::Align::XMidYMid => (x + w / 2.0, y + h / 2.0), tree::Align::XMaxYMid => (x + w, y + h / 2.0), tree::Align::XMinYMax => (x, y + h ), tree::Align::XMidYMax => (x + w / 2.0, y + h ), tree::Align::XMaxYMax => (x + w, y + h ), } } pub(crate) fn file_extension(path: &std::path::Path) -> Option<&str> { if let Some(ext) = path.extension() { ext.to_str() } else { None } } ������������������������������������������������resvg-0.8.0/usvg/testing-tools/���������������������������������������������������������������������0000775�0000000�0000000�00000000000�13525763757�0016470�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������resvg-0.8.0/usvg/testing-tools/afl/�����������������������������������������������������������������0000775�0000000�0000000�00000000000�13525763757�0017232�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������resvg-0.8.0/usvg/testing-tools/afl/Cargo.toml�������������������������������������������������������0000664�0000000�0000000�00000000255�13525763757�0021164�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[package] name = "afl-fuzz" version = "0.1.0" [workspace] members = ["."] [dependencies] afl = "0.4" usvg = { path = "../.." } [[bin]] name = "afl-fuzz" path = "main.rs" ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������resvg-0.8.0/usvg/testing-tools/afl/README.md��������������������������������������������������������0000664�0000000�0000000�00000000322�13525763757�0020506�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������## Dependencies ``` cargo install afl ``` ## Run ```bash ln -s /path-to-resvg-test-suite/svg in env RUSTFLAGS="-Clink-arg=-fuse-ld=gold" cargo afl build cargo afl fuzz -i in -o out target/debug/afl-fuzz ``` ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������resvg-0.8.0/usvg/testing-tools/afl/main.rs����������������������������������������������������������0000664�0000000�0000000�00000000407�13525763757�0020525�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������extern crate afl; extern crate usvg; use std::str; use afl::fuzz; fn main() { let opt = usvg::Options::default(); fuzz(|data| { if let Ok(text) = str::from_utf8(data) { let _ = usvg::Tree::from_str(text, &opt); } }); } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������resvg-0.8.0/usvg/tests/�����������������������������������������������������������������������������0000775�0000000�0000000�00000000000�13525763757�0015017�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������resvg-0.8.0/usvg/tests/test.rs����������������������������������������������������������������������0000664�0000000�0000000�00000035265�13525763757�0016357�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::fmt; #[derive(Clone, Copy, PartialEq)] struct MStr<'a>(&'a str); impl<'a> fmt::Debug for MStr<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.0) } } macro_rules! test { ($name:ident, $keep_named_groups:expr, $input:expr, $output:expr) => { #[test] fn $name() { let re_opt = usvg::Options { keep_named_groups: $keep_named_groups, .. usvg::Options::default() }; let tree = usvg::Tree::from_str($input, &re_opt).unwrap(); let xml_opt = usvg::XmlOptions { use_single_quote: true, indent: usvg::XmlIndent::Spaces(4), attributes_indent: usvg::XmlIndent::Spaces(4), }; assert_eq!(MStr(&tree.to_string(xml_opt)), MStr($output)); } }; } test!(minimal, false, " ", " "); test!(groups, false, " ", " "); test!(clippath_with_invalid_child, false, " ", " "); test!(clippath_with_invalid_children, false, " ", " "); test!(group_clippath, false, " ", " "); // We remove all groups by default. test!(ignore_groups_with_id, false, " ", " "); test!(pattern_with_invalid_child, false, " ", " "); test!(pattern_without_children, false, " ", " "); // TODO: add mask, filter, marker // All supported elements should be listed. // We keep id's even if `keep_named_groups` is disabled. // ID on `svg`, `defs`, `stop` and `tspan` is ignored because they can't be rendered test!(preserve_id, true, " ", " "); // No need to keep empty groups even if `keep_named_groups` is enabled. test!(ignore_empty_groups_with_id, true, " ", " "); test!(keep_groups_with_id, true, " ", " "); test!(simplify_paths_1, false, " ", " "); test!(group_with_default_opacity, false, " ", " "); test!(group_with_an_invalid_child, false, " ", " "); test!(nested_group_with_an_invalid_child, false, " ", " "); test!(simple_switch, false, " ", " "); test!(switch_with_opacity, false, " ", " "); //// `fill-rule` cannot be set on `text`. //test!(fill_rule_on_text, false, //" // _ //", //" // // // //"); macro_rules! test_size { ($name:ident, $input:expr, $expected:expr) => { #[test] fn $name() { use usvg::FuzzyEq; let tree = usvg::Tree::from_str($input, &usvg::Options::default()).unwrap(); assert!(tree.svg_node().size.fuzzy_eq(&$expected)); } }; } test_size!(size_detection_1, "", usvg::Size::new(10.0, 20.0).unwrap() ); test_size!(size_detection_2, "", usvg::Size::new(30.0, 40.0).unwrap() ); test_size!(size_detection_3, "", usvg::Size::new(5.0, 20.0).unwrap() ); macro_rules! test_size_err { ($name:ident, $input:expr) => { #[test] fn $name() { assert!(usvg::Tree::from_str($input, &usvg::Options::default()).is_err()); } }; } test_size_err!(size_detection_err_1, ""); test_size_err!(size_detection_err_2, ""); //// Marker resolving should not produce a group. //test!(marker_with_visible_overflow, false, //" // // // // //", //" // // // // //"); �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������resvg-0.8.0/version-bump.md�������������������������������������������������������������������������0000664�0000000�0000000�00000000477�13525763757�0015651�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������- .github/* - capi/Cargo.toml - capi/include/resvg.h - Cargo.toml - CHANGELOG.md - bindings/resvg-qt/Cargo.toml - bindings/resvg-skia/Cargo.toml - src/lib.rs - tools/explorer-thumbnailer/install/installer.iss - tools/rendersvg/Cargo.toml - tools/usvg/Cargo.toml - usvg/Cargo.toml - usvg/src/lib.rs - usvg/tests/test.rs ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������