Repository: linebender/resvg
Branch: main
Commit: 3a0fdba53ccf
Files: 135
Total size: 1.3 MB
Directory structure:
gitextract_vwl0jp28/
├── .github/
│ ├── copyright.sh
│ ├── pull_request_template.md
│ └── workflows/
│ ├── main.yml
│ └── tagged-release.yml
├── .gitignore
├── .typos.toml
├── AUTHORS
├── CHANGELOG.md
├── Cargo.toml
├── LICENSE-APACHE
├── LICENSE-MIT
├── README.md
├── crates/
│ ├── c-api/
│ │ ├── Cargo.toml
│ │ ├── LICENSE-APACHE
│ │ ├── LICENSE-MIT
│ │ ├── README.md
│ │ ├── ResvgQt.h
│ │ ├── cbindgen.toml
│ │ ├── examples/
│ │ │ └── cairo/
│ │ │ ├── Makefile
│ │ │ ├── README.md
│ │ │ └── example.c
│ │ ├── lib.rs
│ │ └── resvg.h
│ ├── resvg/
│ │ ├── Cargo.toml
│ │ ├── LICENSE-APACHE
│ │ ├── LICENSE-MIT
│ │ ├── examples/
│ │ │ ├── custom_href_resolver.rs
│ │ │ ├── draw_bboxes.rs
│ │ │ └── minimal.rs
│ │ ├── src/
│ │ │ ├── clip.rs
│ │ │ ├── filter/
│ │ │ │ ├── box_blur.rs
│ │ │ │ ├── color_matrix.rs
│ │ │ │ ├── component_transfer.rs
│ │ │ │ ├── composite.rs
│ │ │ │ ├── convolve_matrix.rs
│ │ │ │ ├── displacement_map.rs
│ │ │ │ ├── iir_blur.rs
│ │ │ │ ├── lighting.rs
│ │ │ │ ├── mod.rs
│ │ │ │ ├── morphology.rs
│ │ │ │ └── turbulence.rs
│ │ │ ├── geom.rs
│ │ │ ├── image.rs
│ │ │ ├── lib.rs
│ │ │ ├── main.rs
│ │ │ ├── mask.rs
│ │ │ ├── path.rs
│ │ │ └── render.rs
│ │ └── tests/
│ │ ├── README.md
│ │ ├── fonts/
│ │ │ ├── Amiri-LICENSE-OFL.txt
│ │ │ ├── CFF-and-SBIX-LICENSE-APACHE.txt
│ │ │ ├── CFF-and-SBIX.otf
│ │ │ ├── MPLUS1p-LICENSE-OFL.txt
│ │ │ ├── Noto-LICENSE-OFL.txt
│ │ │ ├── NotoColorEmojiCBDT-LICENSE_APACHE.txt
│ │ │ ├── NotoZnamennyMusicalNotation-OFL.txt
│ │ │ ├── README.md
│ │ │ ├── RobotoFlex-LICENSE-OFL.txt
│ │ │ ├── SedgwickAveDisplay-LICENSE-OFL.txt
│ │ │ ├── SourceSansPro-LICENSE-OFL.md
│ │ │ ├── TwitterColorEmoji-LICENSE-MIT.txt
│ │ │ └── Yellowtail-LICENSE-Apache2.txt
│ │ ├── gen-tests.py
│ │ ├── integration/
│ │ │ ├── extra.rs
│ │ │ ├── main.rs
│ │ │ └── render.rs
│ │ └── resources/
│ │ ├── green.css
│ │ └── image.svgz
│ └── usvg/
│ ├── Cargo.toml
│ ├── LICENSE-APACHE
│ ├── LICENSE-MIT
│ ├── README.md
│ ├── codegen/
│ │ ├── Cargo.toml
│ │ ├── README.md
│ │ ├── attributes.txt
│ │ ├── elements.txt
│ │ └── main.rs
│ ├── docs/
│ │ ├── post-processing.md
│ │ └── spec.adoc
│ ├── src/
│ │ ├── lib.rs
│ │ ├── main.rs
│ │ ├── parser/
│ │ │ ├── clippath.rs
│ │ │ ├── converter.rs
│ │ │ ├── filter.rs
│ │ │ ├── image.rs
│ │ │ ├── marker.rs
│ │ │ ├── mask.rs
│ │ │ ├── mod.rs
│ │ │ ├── options.rs
│ │ │ ├── paint_server.rs
│ │ │ ├── shapes.rs
│ │ │ ├── style.rs
│ │ │ ├── svgtree/
│ │ │ │ ├── mod.rs
│ │ │ │ ├── names.rs
│ │ │ │ ├── parse.rs
│ │ │ │ └── text.rs
│ │ │ ├── switch.rs
│ │ │ ├── text.rs
│ │ │ ├── units.rs
│ │ │ └── use_node.rs
│ │ ├── text/
│ │ │ ├── colr.rs
│ │ │ ├── flatten.rs
│ │ │ ├── layout.rs
│ │ │ └── mod.rs
│ │ ├── tree/
│ │ │ ├── filter.rs
│ │ │ ├── geom.rs
│ │ │ ├── mod.rs
│ │ │ └── text.rs
│ │ └── writer.rs
│ └── tests/
│ ├── parser.rs
│ └── write.rs
├── docs/
│ ├── svg2-changelog.md
│ └── unsupported.md
├── tools/
│ ├── explorer-thumbnailer/
│ │ ├── Cargo.toml
│ │ ├── LICENSE-APACHE
│ │ ├── LICENSE-MIT
│ │ ├── LICENSE-SUMMARY.txt
│ │ ├── install/
│ │ │ └── installer.iss
│ │ └── src/
│ │ ├── error.rs
│ │ ├── interfaces/
│ │ │ ├── iinitialize_with_stream.rs
│ │ │ ├── ithumbnail_provider.rs
│ │ │ └── mod.rs
│ │ ├── lib.rs
│ │ ├── thumbnail_provider.rs
│ │ └── utils.rs
│ └── viewsvg/
│ ├── .gitignore
│ ├── README.md
│ ├── main.cpp
│ ├── mainwindow.cpp
│ ├── mainwindow.h
│ ├── mainwindow.ui
│ ├── svgview.cpp
│ ├── svgview.h
│ └── viewsvg.pro
└── version-bump.md
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/copyright.sh
================================================
#!/bin/bash
# If there are new files with headers that can't match the conditions here,
# then the files can be ignored by an additional glob argument via the -g flag.
# For example:
# -g "!src/special_file.rs"
# -g "!src/special_directory"
# Check all the standard Rust source files
output=$(rg "^// Copyright (19|20)[\d]{2} (.+ and )?the Resvg Authors( and .+)?$\n^// SPDX-License-Identifier: Apache-2\.0 OR MIT$\n\n" --files-without-match --multiline -g "*.{rs,c,cpp,h}" .)
if [ -n "$output" ]; then
echo -e "The following files lack the correct copyright header:\n"
echo $output
echo -e "\n\nPlease add the following header:\n"
echo "// Copyright $(date +%Y) the Resvg Authors"
echo "// SPDX-License-Identifier: Apache-2.0 OR MIT"
echo -e "\n... rest of the file ...\n"
exit 1
fi
echo "All files have correct copyright headers."
exit 0
================================================
FILE: .github/pull_request_template.md
================================================
Pull requests that include:
- dependencies updates
- code formatting fixes
- clippy fixes
- compiler warnings fixes
will not be accepted.
The only exception are spellchecking and grammar fixes.
A pull request must contain a meaningful improvement to the project.
================================================
FILE: .github/workflows/main.yml
================================================
name: Build
on: [push, pull_request]
env:
CARGO_TERM_COLOR: always
jobs:
fmt:
name: formatting
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# TODO: Enable this when more of the Linebender CI has been applied.
#- name: install stable toolchain
# uses: dtolnay/rust-toolchain@master
# with:
# toolchain: ${{ env.RUST_STABLE_VER }}
# components: rustfmt
- name: cargo fmt
run: cargo fmt --all --check
- name: install ripgrep
run: |
sudo apt update
sudo apt install ripgrep
- name: check copyright headers
run: bash .github/copyright.sh
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
# We have to use the Release mode, otherwise it would take forever.
- name: Test
run: cargo test --all --release
- name: Build C API
working-directory: crates/c-api
run: cargo build
- name: Build C API without default features
working-directory: crates/c-api
run: cargo build --no-default-features
- name: Build resvg without default support
working-directory: crates/resvg
run: cargo check --no-default-features
- name: Build usvg without default support
working-directory: crates/usvg
run: cargo check --no-default-features
msrv:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install toolchain
uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.87.0
- name: Build
run: cargo build
# We have some Windows specific code that we should check on each commit.
windows:
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v3
# Toolchain is stable-x86_64-pc-windows-msvc by default. No need to change it.
- name: Build thumbnailer
working-directory: tools/explorer-thumbnailer
env:
RUSTFLAGS: -Ctarget-feature=+crt-static # make sure it's static
run: cargo build
# Unlike other binaries, viewsvg isn't built with crt-static
- name: Build C API
working-directory: crates/c-api
run: cargo build --release
# If this fails, consider changing your text or adding something to .typos.toml.
typos:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: check typos
uses: crate-ci/typos@v1.28.4
================================================
FILE: .github/workflows/tagged-release.yml
================================================
name: "Tagged Release"
on:
push:
tags:
- "v*"
env:
CARGO_TERM_COLOR: always
jobs:
create-release:
name: Create Release
runs-on: ubuntu-latest
steps:
- name: Create Release
id: create_release
uses: softprops/action-gh-release@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
name: ${{ github.ref_name }}
body: |
- `resvg-0.*.0.tar.xz` is a sources archive with vendored Rust dependencies
- `resvg-explorer-extension.exe` is an SVG thumbnailer for Windows Explorer
Check [CHANGELOG](https://github.com/linebender/resvg/blob/${{ github.ref }}/CHANGELOG.md).
draft: false
prerelease: false
outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
release-linux:
name: Release Linux
runs-on: ubuntu-latest
needs: ["create-release"]
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Build resvg
run: cargo build --release
- name: Build usvg
working-directory: crates/usvg
run: cargo build --release
- name: Collect
working-directory: target/release
run: |
strip -s resvg
strip -s usvg
tar czf resvg-linux-x86_64.tar.gz resvg
tar czf usvg-linux-x86_64.tar.gz usvg
mkdir ../../bin
cp resvg-linux-x86_64.tar.gz ../../bin/
cp usvg-linux-x86_64.tar.gz ../../bin/
- name: Get version
id: get_version
uses: battila7/get-version-action@v2
- name: Make vendored archive
run: |
VERSION=${{ steps.get_version.outputs.version-without-v }}
echo $VERSION
git clone https://github.com/linebender/resvg resvg-$VERSION
cd resvg-"$VERSION"
mkdir -p .cargo
cargo vendor > .cargo/config
cd ..
env XZ_OPT="-9e" tar \
--exclude=".git" \
--exclude="resvg-$VERSION/.github" \
--exclude="resvg-$VERSION/version-bump.md" \
--exclude="resvg-$VERSION/docs" \
-cJf resvg-"$VERSION".tar.xz resvg-"$VERSION"
cp resvg-"$VERSION".tar.xz bin/
- name: Upload binaries
uses: alexellis/upload-assets@0.2.2
env:
GITHUB_TOKEN: ${{ github.token }}
with:
asset_paths: '["bin/*"]'
release-windows:
name: Release Windows
runs-on: windows-2019
needs: ["create-release"]
steps:
- name: Checkout
uses: actions/checkout@v2
# Toolchain is stable-x86_64-pc-windows-msvc by default. No need to change it.
- name: Build resvg
env:
RUSTFLAGS: -Ctarget-feature=+crt-static # make sure it's static
run: cargo build --release
- name: Build usvg
working-directory: crates/usvg
env:
RUSTFLAGS: -Ctarget-feature=+crt-static # make sure it's static
run: cargo build --release
- name: Compress
working-directory: target/release
shell: cmd
run: |
7z a -tzip -mx9 resvg-win64.zip resvg.exe
7z a -tzip -mx9 usvg-win64.zip usvg.exe
- name: Build thumbnailer
working-directory: tools/explorer-thumbnailer
env:
RUSTFLAGS: -Ctarget-feature=+crt-static # make sure it's static
run: cargo build --release
- name: Build thumbnailer installer
working-directory: tools/explorer-thumbnailer/install
shell: cmd
run: |
"%programfiles(x86)%\Inno Setup 6\iscc.exe" "installer.iss"
# Unlike other binaries, viewsvg isn't built with crt-static
- name: Build C API
working-directory: crates/c-api
run: cargo build --release
- name: Prepare Developer Command Prompt for MSVC
uses: ilammy/msvc-dev-cmd@v1
- name: Collect
run: |
mkdir bin
cp target/release/resvg-win64.zip bin/
cp target/release/usvg-win64.zip bin/
cp tools/explorer-thumbnailer/install/resvg-explorer-extension.exe bin/
- name: Upload binaries
uses: alexellis/upload-assets@0.2.2
env:
GITHUB_TOKEN: ${{ github.token }}
with:
asset_paths: '["bin/*"]'
release-macos-aarch64:
name: Release macOS (aarch64)
runs-on: macos-latest
needs: ["create-release"]
steps:
- name: Checkout
uses: actions/checkout@v2
# Some weird CI glitch. Make sure we have the latest Rust.
- name: Install latest stable toolchain
uses: dtolnay/rust-toolchain@stable
- name: Build resvg
run: cargo build --release
- name: Build usvg
working-directory: crates/usvg
run: cargo build --release
- name: Compress
working-directory: target/release
run: |
7z a -tzip -mx9 resvg-macos-aarch64.zip resvg
7z a -tzip -mx9 usvg-macos-aarch64.zip usvg
- name: Build C API
working-directory: crates/c-api
run: cargo build --release
- name: Collect
run: |
mkdir bin
cp target/release/resvg-macos-aarch64.zip bin/
cp target/release/usvg-macos-aarch64.zip bin/
- name: Upload binaries
uses: alexellis/upload-assets@0.2.2
env:
GITHUB_TOKEN: ${{ github.token }}
with:
asset_paths: '["bin/*"]'
release-macos-intel:
name: Release macOS (intel)
runs-on: macos-15-intel
needs: ["create-release"]
steps:
- name: Checkout
uses: actions/checkout@v2
# Some weird CI glitch. Make sure we have the latest Rust.
- name: Install latest stable toolchain
uses: dtolnay/rust-toolchain@stable
- name: Build resvg
run: cargo build --release
- name: Build usvg
working-directory: crates/usvg
run: cargo build --release
- name: Compress
working-directory: target/release
run: |
7z a -tzip -mx9 resvg-macos-x86_64.zip resvg
7z a -tzip -mx9 usvg-macos-x86_64.zip usvg
- name: Build C API
working-directory: crates/c-api
run: cargo build --release
- name: Collect
run: |
mkdir bin
cp target/release/resvg-macos-x86_64.zip bin/
cp target/release/usvg-macos-x86_64.zip bin/
- name: Upload binaries
uses: alexellis/upload-assets@0.2.2
env:
GITHUB_TOKEN: ${{ github.token }}
with:
asset_paths: '["bin/*"]'
================================================
FILE: .gitignore
================================================
target
.directory
.DS_Store
.vscode
tools/build-*
**/diffs
================================================
FILE: .typos.toml
================================================
# See the configuration reference at
# https://github.com/crate-ci/typos/blob/master/docs/reference.md
# Corrections take the form of a key/value pair. The key is the incorrect word
# and the value is the correct word. If the key and value are the same, the
# word is treated as always correct. If the value is an empty string, the word
# is treated as always incorrect.
# Match Identifier - Case Sensitive
[default.extend-identifiers]
ba = "ba"
flate2 = "flate2"
Hel = "Hel"
PNGs = "PNGs"
SVGinOT = "SVGinOT"
# Match Inside a Word - Case Insensitive
[default.extend-words]
wdth = "wdth"
[files]
# Include .github, .cargo, etc.
ignore-hidden = false
extend-exclude = [
# /.git isn't in .gitignore, because git never tracks it.
# Typos doesn't know that, though.
"/.git",
"*.svg",
]
================================================
FILE: AUTHORS
================================================
# This is the list of Resvg's significant contributors.
#
# This does not necessarily list everyone who has contributed code,
# especially since many employees of one corporation may be contributing.
# To see the full list of contributors, see the revision history in
# source control.
Yevhenii Reizner
Laurenz Stampfl
================================================
FILE: CHANGELOG.md
================================================
# 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]
This release has an MSRV of 1.87.0 for `usvg` and `resvg` and the C API.
## [0.47.0] 2026-02-05
This release has an MSRV of 1.87.0 for `usvg` and `resvg` and the C API.
### Added
- Focal radius (`fr`) supported for Radial Gradients. (#1014 by @wmedrano)
- Support for variable fonts based on font-variation-settings CSS property. (#997 by @oetiker)
### Changed
- `tiny-skia` has a major version bump from 0.11 to 0.12.
## [0.46.0]
This release has an MSRV of 1.87.0 for `usvg` and `resvg` and the C API.
### Added
- Support SVGs without the xmlns attribute on the root. Thanks to [@JosefKuchar][].
- Add a from_data_nested method to usvg::Tree (#955 by @tovrstra)
- Use cache for glyph outlining (#957 by @newinnovations)
- Support loading nested embedded images (#958 by @LaurenzV)
### Changed
- Bump dependencies, bump MSRV to 1.87, Upgrade to edition 2024 (#1002 and #1003 @LaurenzV)
- Upgraded kurbo to 0.13 and svgtypes to 0.16.1. Thanks to [@HaHa421][].
- Bump zune_jpeg (#964 by @LaurenzV)
- Fix abs_bounding_box calculation for image (#924 by @Dabble63)
- Fix crash caused by glyph splitting (#929 by @arnaud-secondlayer)
- Fix a bug with incorrect resolving of fill/stroke color (#953 by @LaurenzV)
- Fix inverted condition in has_text_nodes method (#967 by @Daaiid)
- Do not write empty defs nodes (#980 by @Its-Just-Nans)
- Check if text paths need to be written out (#981 by @Its-Just-Nans)
- Use checked arithmetic when computing bounding box (#987 by @Its-Just-Nans)
- Fix bug in rewriting of clip paths with transformed path (#988 by @Its-Just-Nans)
### Removed
- Remove unused phf dependency (#920)
## [0.45.1] - 2025-04-16
### Changed
- Support SVGs without the xmlns attribute on the root (#892)
- Add optimization for paths with markers in paint-order but no actual markers (#887)
### Removed
- tools/kde-dolphin-thumbnailer. This was never a released tool, and it doesn't support current versions of KDE/dolphin ([#897][] by [@DJMcNab][])
## [0.45.0] - 2025-02-26
This is the first release under the stewardship of [Linebender][], who is now responsible for maintenance of this crate.
Many thanks to Yevhenii Reizner for the years of hard work that he has poured into this and other crates.
Please note that the license of this project changed from `MPL-2.0` to `Apache-2.0 OR MIT`.
See [resvg#838](https://github.com/linebender/resvg/issues/838) for more information.
This release has an MSRV of 1.65 for `usvg` and 1.67.1 for `resvg` and the C API.
### Added
- Support for the `background-color` attribute.
- Support for additional `image-rendering` attributes.
- Support for the `!important` CSS flag.
- Support for Luma JPEG images.
- (c-api) `resvg_options_set_stylesheet`.
Thanks to [@michabay05][].
- (svgtypes) Support for floating point hue in `hsl()` and `hsla()`.
### Changed
- License to `Apache-2.0 OR MIT`.
See [resvg#838](https://github.com/linebender/resvg/issues/838) for more information.
- Updated WebP decoder for bug fixes and improved performance.
Thanks to [@Shnatsel][].
- MSRV of resvg and c-api bumped to 1.67.1.
- `fontdb` and `rustybuzz` have been updated.
- Updated other dependencies.
- (svgtypes) Simplified color component rounding and bounds checking.
- Improved handling of paths with paint order `markers` but no actual markers.
### Fixed
- Relative unit handling when `use` references `symbol`.
- (svgtypes) Rounding of hues in HSL to RGB conversion.
- (svgtypes) Rounding of alpha.
## [0.44.0] - 2024-09-28
### Added
- Stylesheet injection support.
Thanks to [@LaurenzV](https://github.com/LaurenzV).
- (c-api) `resvg_get_object_bbox`
- (c-api) `cargo-c` metadata.
Thanks to [@lu-zero](https://github.com/lu-zero).
- Implement `From` for `fontdb` and `usvg` font types.
Thanks to [@dhardy](https://github.com/dhardy).
### Changed
- (c-api) `resvg_get_image_bbox` returns a _layer_ and not _object_ bounding box now.
Use `resvg_get_object_bbox` to preserve the old behavior.
### Fixed
- (svgtypes) Path parsing with `S` or `T` segments after `A`.
- Bounding box calculation for the root group used for `viewBox` flattening.
## [0.43.0] - 2024-08-10
### Added
- Support WebP images.
Thanks to [@notjosh](https://github.com/notjosh).
### Changed
- Use `zune-jpeg` instead of `jpeg-decoder`.
Thanks to [@mattfbacon](https://github.com/mattfbacon).
- Update dependencies.
### Fixed
- Canvas size limits calculation.
- SVG fonts handling.
Thanks to [@LaurenzV](https://github.com/LaurenzV).
- Transforms in COLR fonts.
Thanks to [@LaurenzV](https://github.com/LaurenzV).
## [0.42.0] - 2024-06-01
### Added
- `resvg` can render color fonts now, aka Emojis.
In TrueType terms, `COLRv0`, `COLRv1` (mostly), `sbix`, `CBDT` and `SVG` tables are supported.
Thanks to [@LaurenzV](https://github.com/LaurenzV).
- Fonts matching and fallback can be controlled by the caller via `usvg::FontResolver` now.
Thanks to [@LaurenzV](https://github.com/LaurenzV).
- `usvg::Options::font_resolver`. Similar to `usvg::Options::image_href_resolver` we already had.
- `usvg::Options::fontdb`
- Support double-quoted FuncIRIs, aka `url("#id")`.
- `image` element viewbox flattening.
Instead of having `usvg::Image::view_box` that the caller should handle themselves,
we instead replace it with `transform` and optional `clip-path`.
This greatly simplifies `image` rendering.
- `usvg::Image::size`
- Tree viewbox flattening.
Similar to `image` above, but affects the root `svg` element instead.
- `pattern` viewbox flattening.
Similar to `image` above, but for patterns.
- Improve vertical text rendering.
Thanks to [@LaurenzV](https://github.com/LaurenzV).
### Changed
- `usvg::fontdb::Database` should be set in `usvg::Options` and not passed
to the parser separately now.
- `usvg::Options` and `usvg::ImageHrefResolver` have a lifetime now.
- Replace `usvg::Visibility` enum with just `bool`.
- `usvg::Path::visibility()` is replaced with `usvg::Path::is_visible()`
- `usvg::Image::visibility()` is replaced with `usvg::Image::is_visible()`
- `usvg::TextSpan::visibility()` is replaced with `usvg::TextSpan::is_visible()`
- Always represent `feImage` content as a link to an element.
In SVG, `feImage` can contain a link to an element or a base64 image data, just like `image`.
From now, the inlined base64 data will always be represented by a link to an actual `image` element.
```xml
```
will be parsed as
```xml
```
This simplifies `feImage` rendering, since we don't have to handle both cases now.
- The `--list-fonts` resvg argument can be used without providing an SVG file now.
Can simply call `resvg --list-fonts` now.
- The `--list-fonts` resvg argument includes generic font family names as well now.
- Make sure all warning and errors are printed to stderr.
Thanks to [@ahaoboy](https://github.com/ahaoboy).
### Removed
- `usvg::ViewBox`, `usvg::AspectRatio`, `usvg::Align` types. Nol longer used.
- `usvg::filter::Image::aspect`. No longer needed.
- `usvg::filter::Image::rendering_mode`. No longer needed.
- `usvg::filter::Image::data`. Use `usvg::filter::Image::root` instead.
- `usvg::Tree::view_box`. No longer needed.
- `usvg::Image::view_box`. No longer needed.
- `usvg::Image::pattern`. No longer needed.
- `usvg::utils::align_pos`. No longer needed.
- `usvg::Visibility`. No longer needed.
- (c-api) `resvg_get_image_viewbox`. Use `resvg_get_image_size` instead.
### Fixed
- `context-fill` handling.
Thanks to [@LaurenzV](https://github.com/LaurenzV).
## [0.41.0] - 2024-04-03
### Added
- `context-fill` and `context-stroke` support.
Thanks to [@LaurenzV](https://github.com/LaurenzV).
- `usvg::Text::layouted()`, which returns a list of glyph IDs.
It can be used to manually draw glyphs, unlike with `usvg::Text::flattened()`, which returns
just vector paths.
Thanks to [@LaurenzV](https://github.com/LaurenzV).
### Fixed
- Missing text when a `text` element uses multiple fonts and one of them produces ligatures.
- Absolute transform propagation during `use` resolving.
- Absolute transform propagation during nested `svg` resolving.
- `Node::abs_transform` documentation. The current element's transform _is_ included.
## [0.40.0] - 2024-02-17
### Added
- `usvg::Tree` is `Send + Sync` compatible now.
- `usvg::WriteOptions::preserve_text` to control how `usvg` generates an SVG.
- `usvg::Image::abs_bounding_box`
### Changed
- All types in `usvg` are immutable now. Meaning that `usvg::Tree` cannot be modified
after creation anymore.
- All struct fields in `usvg` are private now. Use getters instead.
- All `usvg::Tree` parsing methods require the `fontdb` argument now.
- All `defs` children like gradients, patterns, clipPaths, masks and filters are guarantee
to have a unique, non-empty ID.
- All `defs` children like gradients, patterns, clipPaths, masks and filters are guarantee
to have `userSpaceOnUse` units now. No `objectBoundingBox` units anymore.
- `usvg::Mask` is allowed to have no children now.
- Text nodes will not be parsed when the `text` build feature isn't enabled.
- `usvg::Tree::clip_paths`, `usvg::Tree::masks`, `usvg::Tree::filters` returns
a pre-collected slice of unique nodes now.
It's no longer a closure and you do not have to deduplicate nodes by yourself.
- `usvg::filter::Primitive::x`, `y`, `width` and `height` methods were replaced
with `usvg::filter::Primitive::rect`.
- Split `usvg::Tree::paint_servers` into `usvg::Tree::linear_gradients`,
`usvg::Tree::radial_gradients`, `usvg::Tree::patterns`.
All three returns pre-collected slices now.
- A `usvg::Path` no longer can have an invalid bbox. Paths with an invalid bbox will be
rejected during parsing.
- All `usvg` methods that return bounding boxes return non-optional `Rect` now.
No `NonZeroRect` as well.
- `usvg::Text::flattened` returns `&Group` and not `Option<&Group>` now.
- `usvg::ImageHrefDataResolverFn` and `usvg::ImageHrefStringResolverFn`
require `fontdb::Database` argument.
- All shared nodes are stored in `Arc` and not `Rc` now.
- `resvg::render_node` now includes filters bounding box. Meaning that a node with a blur filter
no longer be clipped.
- Replace `usvg::utils::view_box_to_transform` with `usvg::ViewBox::to_transform`.
- Rename `usvg::XmlOptions` into `usvg::WriteOptions` and embed `xmlwriter::Options`.
### Removed
- `usvg::Tree::postprocess()` and `usvg::PostProcessingSteps`. No longer needed.
- `usvg::ClipPath::units()`, `usvg::Mask::units()`, `usvg::Mask::content_units()`,
`usvg::Filter::units()`, `usvg::Filter::content_units()`, `usvg::LinearGradient::units()`,
`usvg::RadialGradient::units()`, `usvg::Pattern::units()`, `usvg::Pattern::content_units()`
and `usvg::Paint::units()`. They are always `userSpaceOnUse` now.
- `usvg::Units`. No longer needed.
### Fixed
- Text bounding box is accounted during SVG size resolving.
Previously, only paths and images were included.
- Font selection when an italic font isn't explicitly marked as one.
- Preserve `image` aspect ratio when only `width` or `height` are present.
Thanks to [@LaurenzV](https://github.com/LaurenzV).
## [0.39.0] - 2024-02-06
### Added
- `font` shorthand parsing.
Thanks to [@LaurenzV](https://github.com/LaurenzV).
- `usvg::Group::abs_bounding_box`
- `usvg::Group::abs_stroke_bounding_box`
- `usvg::Path::abs_bounding_box`
- `usvg::Path::abs_stroke_bounding_box`
- `usvg::Text::abs_bounding_box`
- `usvg::Text::abs_stroke_bounding_box`
### Changed
- All `usvg-*` crates merged into one. There is just the `usvg` crate now, as before.
### Removed
- `usvg::Group::abs_bounding_box()` method. It's a field now.
- `usvg::Group::abs_filters_bounding_box()`
- `usvg::TreeParsing`, `usvg::TreePostProc` and `usvg::TreeWriting` traits.
They are no longer needed.
### Fixed
- `font-family` parsing.
Thanks to [@LaurenzV](https://github.com/LaurenzV).
- Absolute bounding box calculation for paths.
## [0.38.0] - 2024-01-21
### Added
- Each `usvg::Node` stores its absolute transform now.
`Node::abs_transform()` executes in constant time now.
- `usvg::Tree::calculate_bounding_boxes` to calculate all bounding boxes beforehand.
- `usvg::Node::bounding_box` which returns a precalculated node's bounding box in object coordinates.
- `usvg::Node::abs_bounding_box` which returns a precalculated node's bounding box in canvas coordinates.
- `usvg::Node::stroke_bounding_box` which returns a precalculated node's bounding box,
including stroke, in object coordinates.
- `usvg::Node::abs_stroke_bounding_box` which returns a precalculated node's bounding box,
including stroke, in canvas coordinates.
- (c-api) `resvg_get_node_stroke_bbox`
- `usvg::Node::filters_bounding_box`
- `usvg::Node::abs_filters_bounding_box`
- `usvg::Tree::postprocess`
### Changed
- `resvg` renders `usvg::Tree` directly again. `resvg::Tree` is gone.
- `usvg` no longer uses `rctree` for the nodes tree implementation.
The tree is a regular `enum` now.
- A caller no longer need to use the awkward `*node.borrow()`.
- No more panics on incorrect mutable `Rc` access.
- Tree nodes respect tree's mutability rules. Before, one could mutate tree nodes when the tree
itself is not mutable. Because `Rc` provides a shared mutable access.
- Filters, clip paths, masks and patterns are stored as `Rc>` instead of `Rc`.
This is required for proper mutability since `Node` itself is no longer an `Rc`.
- Rename `usvg::NodeKind` into `usvg::Node`.
- Upgrade to Rust 2021 edition.
### Removed
- `resvg::Tree`. No longer needed. `resvg` can render `usvg::Tree` directly once again.
- `rctree::Node` methods. The `Node` API is completely different now.
- `usvg::NodeExt`. No longer needed.
- `usvg::Node::calculate_bbox`. Use `usvg::Node::abs_bounding_box` instead.
- `usvg::Tree::convert_text`. Use `usvg::Tree::postprocess` instead.
- `usvg::TreeTextToPath` trait. No longer needed.
### Fixed
- Mark `mask-type` as a presentation attribute.
- Do not show needless warnings when parsing some attributes.
- `feImage` rendering with a non-default position.
Thanks to [@LaurenzV](https://github.com/LaurenzV).
## [0.37.0] - 2023-12-16
### Added
- `usvg` can write text back to SVG now.
Thanks to [@LaurenzV](https://github.com/LaurenzV).
- `--preserve-text` flag to the `usvg` CLI tool.
Thanks to [@LaurenzV](https://github.com/LaurenzV).
- Support [`transform-origin`](https://drafts.csswg.org/css-transforms/#transform-origin-property)
property.
Thanks to [@LaurenzV](https://github.com/LaurenzV).
- Support non-default markers order via
[`paint-order`](https://svgwg.org/svg2-draft/painting.html#PaintOrder).
Previously, only fill and stroke could have been swapped.
Thanks to [@LaurenzV](https://github.com/LaurenzV).
- `usvg_tree::Text::flattened` that will contain a flattened/outlined text.
- `usvg_tree::Text::bounding_box`. Will be set only after text flattening.
- Optimize `usvg_tree::NodeExt::abs_transform` by storing absolute transforms in the tree
instead of calculating them each time.
### Changed
- `usvg_tree::Text::positions` was replaced with `usvg_tree::Text::dx` and `usvg_tree::Text::dy`.
`usvg_tree::CharacterPosition::x` and `usvg_tree::CharacterPosition::y` are gone.
They were redundant and you should use `usvg_tree::TextChunk::x`
and `usvg_tree::TextChunk::y` instead.
- `usvg_tree::LinearGradient::id` and `usvg_tree::RadialGradient::id` are moved to
`usvg_tree::BaseGradient::id`.
- Do not generate element IDs during parsing. Previously, some elements like `clipPath`s
and `filter`s could have generated IDs, but it wasn't very reliable and mostly unnecessary.
Renderer doesn't rely on them and usvg writer would generate them anyway.
- Text-to-paths conversion via `usvg_text_layout::Tree::convert_text` no longer replaces
original text elements with paths, but instead puts them into `usvg_tree::Text::flattened`.
### Removed
- The `transform` field from `usvg_tree::Path`, `usvg_tree::Image` and `usvg_tree::Text`.
Only `usvg_tree::Group` can have it.
It doesn't break anything, because those properties were never used before anyway.
Thanks to [@LaurenzV](https://github.com/LaurenzV).
- `usvg_tree::CharacterPosition`
- `usvg_tree::Path::text_bbox`. Use `usvg_tree::Text::bounding_box` instead.
- `usvg_text_layout::TextToPath` trait for `Text` nodes.
Only the whole tree can be converted at once.
### Fixed
- Path object bounding box calculation. We were using point bounds instead of tight contour bounds.
Was broken since v0.34
- Convert text-to-paths in embedded SVGs as well. The one inside the `Image` node.
Thanks to [@LaurenzV](https://github.com/LaurenzV).
- Indirect `text-decoration` resolving in some cases.
Thanks to [@LaurenzV](https://github.com/LaurenzV).
- (usvg) Clip paths writing to SVG.
Thanks to [@LaurenzV](https://github.com/LaurenzV).
## [0.36.0] - 2023-10-01
### Added
- `stroke-linejoin=miter-clip` support. SVG2.
Thanks to [@torokati44](https://github.com/torokati44).
- Quoted FuncIRI support. Like `fill="url('#gradient')"`. SVG2.
Thanks to [@romanzes](https://github.com/romanzes).
- Allow float values in `rgb()` and `rgba()` colors. SVG2.
Thanks to [@yisibl](https://github.com/yisibl).
- `auto-start-reverse` variant support to `orient` in markers. SVG2.
Thanks to [@EpicEricEE](https://github.com/EpicEricEE).
### Changed
- Update dependencies.
### Fixed
- Increase precision of the zero-scale transform check.
Was rejecting some valid transforms before.
- Panic when rendering a very specific text.
- Greatly improve parsing performance when an SVG has a lot of references.
Thanks to [@wez](https://github.com/wez).
- (Qt API) Fix scaling factor calculation.
Thanks to [@missdeer](https://github.com/missdeer).
## [0.35.0] - 2023-06-27
### Fixed
- Panic when an element is completely outside the viewbox.
### Removed
- `FillPaint` and `StrokePaint` filter inputs support.
It's a mostly undocumented SVG feature that no one supports and no one uses.
And it was adding a significant complexity to the codebase.
- `usvg::filter::Filter::fill_paint` and `usvg::filter::Filter::stroke_paint`.
- `BackgroundImage`, `BackgroundAlpha`, `FillPaint` and `StrokePaint` from `usvg::filter::Input`.
- `usvg::Group::filter_fill_paint` and `usvg::Group::filter_stroke_paint`.
## [0.34.1] - 2023-05-28
### Fixed
- Transform components order. Affects only `usvg` SVG output and C API.
## [0.34.0] - 2023-05-27
### Changed
- `usvg` uses `tiny-skia` geometry primitives now, including the `Path` container.
The main difference compared to the old `usvg` primitives
is that `tiny-skia` uses `f32` instead of `f64`.
So while in theory we could loose some precision, in practice, `f32` is used mainly
as a storage type and precise math operations are still done using `f64`.
`tiny-skia` primitives are move robust, strict and have a nicer API.
More importantly, this change reduces the peak memory usages for SVGs with large paths
(in terms of the number of segments).
And removes the need to convert `usvg::PathData` into `tiny-skia::Path` before rendering.
Which was just a useless reallocation.
- All numbers are stored as `f32` instead of `f64` now.
- Because we use `tiny-skia::Path` now, we allow _quadratic curves_ as well.
This includes `usvg` CLI output.
- Because we allow _quadratic curves_ now, text might render slightly differently (better?).
This is because TrueType fonts contain only _quadratic curves_
and we were converting them to cubic before.
- `usvg::Path` no longer implements `Default`. Use `usvg::Path::new` instead.
- Replace `usvg::Rect` with `tiny_skia::NonZeroRect`.
- Replace `usvg::PathBbox` with `tiny_skia::Rect`.
- Unlike the old `usvg::PathBbox`, `tiny_skia::Rect` allows both width and height to be zero.
This is not an error.
- `usvg::filter::Turbulence::base_frequency` was split into `base_frequency_x` and `base_frequency_y`.
- `usvg::NodeExt::calculate_bbox` no longer includes stroke bbox.
- (c-api) Use `float` instead of `double` everywhere.
- The `svgfilters` crate was merged into `resvg`.
- The `rosvgtree` crate was merged into `usvg-parser`.
- `usvg::Group::filter_fill` moved to `usvg::filter::Filter::fill_paint`.
- `usvg::Group::filter_stroke` moved to `usvg::filter::Filter::stroke_paint`.
### Remove
- `usvg::Point`. Use `tiny_skia::Point` instead.
- `usvg::FuzzyEq`. Use `usvg::ApproxEqUlps` instead.
- `usvg::FuzzyZero`. Use `usvg::ApproxZeroUlps` instead.
- (c-api) `resvg_path_bbox`. Use `resvg_rect` instead.
- `svgfilters` crate.
- `rosvgtree` crate.
### Fixed
- Write `transform` on `clipPath` children in `usvg` SVG output.
- Do not duplicate marker children IDs.
Previously, each element resolved for a marker would preserve its ID.
Affects only `usvg` SVG output and doesn't affect rendering.
## [0.33.0] - 2023-05-17
### Added
- A new rendering algorithm.
When rendering [isolated groups](https://razrfalcon.github.io/notes-on-svg-parsing/isolated-groups.html),
aka layers, we have to know the layer bounding box beforehand, which is ridiculously hard in SVG.
Previously, resvg would simply use the canvas size for all the layers.
Meaning that to render a 10x10px layer on a 1000x1000px canvas, we would have to allocate and then blend
a 1000x1000px layer, which is just a waste of CPU cycles.
The new rendering algorithm is able to calculate layer bounding boxes, which dramatically improves
performance when rendering a lot of tiny layers on a large canvas.
Moreover, it makes performance more linear with a canvas size increase.
The [paris-30k.svg](https://github.com/google/forma/blob/681e8bfd348caa61aab47437e7d857764c2ce522/assets/svgs/paris-30k.svg)
sample from [google/forma](https://github.com/google/forma) is rendered _115 times_ faster on M1 Pro now.
From ~33760ms down to ~290ms. 5269x3593px canvas.
If we restrict the canvas to 1000x1000px, which would contain only the actual `paris-30k.svg` content,
then we're _13 times_ faster. From ~3252ms down to ~253ms.
- `resvg::Tree`, aka a render tree, which is an even simpler version of `usvg::Tree`.
`usvg::Tree` had to be converted into `resvg::Tree` before rendering now.
### Changed
- Restructure the root directory. All crates are in the `crates` directory now.
- Restructure tests. New directory structure and naming scheme.
- Use `resvg::Tree::render` instead of `resvg::render`.
- resvg's `--export-area-drawing` option uses calculated bounds instead of trimming
excessive alpha now. It's faster, but can lead to a slightly different output.
- (c-api) Removed `fit_to` argument from `resvg_render`.
- (c-api) Removed `fit_to` argument from `resvg_render_node`.
- `usvg::ScreenSize` moved to `resvg`.
- `usvg::ScreenRect` moved to `resvg`.
- Rename `resvg::ScreenSize` into `resvg::IntSize`.
- Rename `resvg::ScreenRect` into `resvg::IntRect`.
### Removed
- `filter` build feature from `resvg`. Filters are always enabled now.
- `resvg::FitTo`
- `usvg::utils::view_box_to_transform_with_clip`
- `usvg::Size::to_screen_size`. Use `resvg::IntSize::from_usvg` instead.
- `usvg::Rect::to_screen_size`. Use `resvg::IntSize::from_usvg(rect.size())` instead.
- `usvg::Rect::to_screen_rect`. Use `resvg::IntRect::from_usvg` instead.
- (c-api) `resvg_fit_to`
- (c-api) `resvg_fit_to_type`
### Fixed
- Double quotes parsing in `font-family`.
## [0.32.0] - 2023-04-23
### Added
- Clipping and masking is up to 20% faster.
- `mask-type` property support. SVG2
- `usvg_tree::MaskType`
- `usvg_tree::Mask::kind`
- (rosvgtree) New SVG 2 mask attributes.
### Changed
- `BackgroundImage` and `BackgroundAlpha` filter inputs will produce the same output
as `SourceGraphic` and `SourceAlpha` respectively.
### Removed
- `enable-background` support. This feature was never supported by browsers
and was deprecated in SVG 2. To my knowledge, only Batik has a good support of it.
Also, it's a performance nightmare, which caused multiple issues in resvg already.
- `usvg_tree::EnableBackground`
- `usvg_tree::Group::enable_background`
- `usvg_tree::NodeExt::filter_background_start_node`
### Fixed
- Improve rectangular clipping anti-aliasing quality.
- Mask's RGB to Luminance converter was ignoring premultiplied alpha.
## [0.31.1] - 2023-04-22
### Fixed
- Use the latest `tiny-skia` to fix SVGs with large masks rendering.
## [0.31.0] - 2023-04-10
### Added
- `usvg::Tree::paint_servers`
- `usvg::Tree::clip_paths`
- `usvg::Tree::masks`
- `usvg::Tree::filters`
- `usvg::Node::subroots`
- (usvg) `--coordinates-precision` and `--transforms-precision` writing options.
Thanks to [@flxzt](https://github.com/flxzt).
### Fixed
- `fill-opacity` and `stroke-opacity` resolving.
- Double `transform` when resolving `symbol`.
- `symbol` clipping when its viewbox is the same as the document one.
- (usvg) Deeply nested gradients, patterns, clip paths, masks and filters
were ignored during SVG writing.
- Missing text in nested clip paths and mask, text decoration patterns, filter inputs and feImage.
## [0.30.0] - 2023-03-25
### Added
- Readd `usvg` CLI tool. Can be installed via cargo as before.
### Changed
- Extract most `usvg` internals into new `usvg-tree` and `usvg-parser` crates.
`usvg-tree` contains just the SVG tree and all the types.
`usvg-parser` parsers SVG into `usvg-tree`.
And `usvg` is just an umbrella crate now.
- To use `usvg::Tree::from*` methods one should import the `usvg::TreeParsing` trait now.
- No need to import `usvg-text-layout` manually anymore. It is part of `usvg` now.
- `rosvgtree` no longer reexports `svgtypes`.
- `rosvgtree::Node::attribute` returns just a string now.
- `rosvgtree::Node::find_attribute` returns just a `rosvgtree::Node` now.
- Rename `usvg::Stretch` into `usvg::FontStretch`.
- Rename `usvg::Style` into `usvg::FontStyle`.
- `usvg::FitTo` moved to `resvg::FitTo`.
- `usvg::IsDefault` trait is private now.
### Removed
- `rosvgtree::FromValue`. Due to Rust's orphan rules this trait is pretty useless.
### Fixed
- Recursive markers detection.
- Skip malformed `transform` attributes without skipping the whole element.
- Clipping path rectangle calculation for nested `svg` elements.
Thanks to [@LaurenzV](https://github.com/LaurenzV).
- Panic when applying `text-decoration` on text with only one cluster.
Thanks to [@LaurenzV](https://github.com/LaurenzV).
- (Qt API) Image size wasn't initialized. Thanks to [@missdeer](https://github.com/missdeer).
- `resvg` CLI allows files with XML DTD again.
- (svgtypes) Handle implicit MoveTo after ClosePath segments.
## [0.29.0] - 2023-02-04
### Added
- `resvg` CLI loads system fonts only when an input SVG has text nodes now.
Fonts loading is an IO-heavy operation and by avoiding it we can speed up `resvg` execution.
- `usvg::Group::should_isolate`
- `usvg::Tree::has_text_nodes`
### Changed
- Some `usvg` internals were moved into the new `rosvgtree` crate.
- Dummy groups are no longer removed. Use `usvg::Group::should_isolate` to check
if a group affects rendering.
- `usvg-text-layout::TreeTextToPath::convert_text` no longer has the `keep_named_groups` argument.
- MSRV bumped to 1.65
- Update dependencies.
### Removed
- `usvg::Options::keep_named_groups`. Dummy groups are no longer removed.
- (c-api) `resvg_options_set_keep_named_groups`
- (Qt API) `ResvgOptions::setKeepNamedGroups`
### Fixed
- Missing `font-family` handling.
- `font-weight` resolving.
## [0.28.0] - 2022-12-03
### Added
- `usvg::Text` and `usvg::NodeKind::Text`.
### Changed
- `usvg` isn't converting text to paths by default now. A caller must call
`usvg::Tree::convert_text` or `usvg::Text::convert` from `usvg-text-layout` crate on demand.
- `usvg` text layout implementation moved into `usvg-text-layout` crate.
- During SVG size recovery, when no `width`, `height` and `viewBox` attributes have been set,
text nodes are no longer taken into an account. This is because a text node has no bbox
before conversion into path(s), which we no longer doing during parsing.
- `usvg` is purely an SVG parser now. It doesn't convert text to paths
and doesn't write SVG anymore.
- `usvg::filter::ConvolveMatrixData` methods are fields now.
### Removed
- `usvg` CLI binary. No alternatives for now.
- All `usvg` build features.
- `filter`. Filter elements are always parsed by `usvg` now.
- `text`. Text elements are always parsed by `usvg` now.
- `export`. `usvg` cannot write an SVG anymore.
- `usvg::Tree::to_string`. `usvg` cannot write an SVG anymore.
- `usvg::TransformFromBBox` trait. This is just a regular `usvg::Transform` method now.
- `usvg::OptionsRef`. `usvg::Options` is enough from now.
- `usvg::Options::fontdb`. Used only by `usvg-text-layout` now.
- `--dump-svg` from `resvg`.
## [0.27.0] - 2022-11-27
### Added
- `lengthAdjust` and `textLength` attributes support.
- Support automatic `image` size detection.
`width` and `height` attributes can be omitted or set to `auto` on `image` now. SVG2
### Fixed
- `--query-all` flag in `resvg` CLI.
- Percentage values resolving.
## [0.26.1] - 2022-11-21
### Fixed
- Allow `dominant-baseline` and `alignment-baseline` to be set via CSS.
## [0.26.0] - 2022-11-20
### Added
- Minimal `dominant-baseline` and `alignment-baseline` support.
- `mix-blend-mode` and `isolation` support. SVG2
- Allow writing resvg output to stdout.
- Allow disabling text kerning using `kerning="0"` and `style="font-kerning:none"`. SVG2
- Allow `` values for `opacity`, `fill-opacity`, `stroke-opacity`,
`flood-opacity` and `stop-opacity` attributes.
You can write `opacity="50%"` now. SVG2
### Changed
- Disable focal point correction on radial gradients to conform with SVG 2. SVG2
- Update `feMorphology` radius value resolving.
### Fixed
- Do not clip nested `svg` when only the `viewBox` attribute is present.
## [0.25.0] - 2022-10-30
### Added
- Partial `paint-order` attribute support.
Markers can only be under or above the shape.
### Fixed
- Compilation issues caused by `rustybuzz` update.
## [0.24.0] - 2022-10-22
### Added
- CSS3 `writing-mode` variants `vertical-rl` and `vertical-lr`.
Thanks to [yisibl](https://github.com/yisibl).
- (tiny-skia) AArch64 Neon SIMD support. Up to 3x faster on Apple M1.
### Changed
- `usvg::Tree` stores only `Group`, `Path` and `Image` nodes now.
Instead of emulating an SVG file structure, where gradients, patterns, filters, clips and masks
are part of the nodes tree (usually inside the `defs` element), we reference them using `Rc`
from now.
This change makes `usvg` a bit simpler. Makes `usvg` API way easier, since instead of
looking for a node via `usvg::Tree::defs_by_id` the caller can access the type directly via `Rc`.
And makes creation of custom `usvg::Tree`s way easier.
- `clip_path`, `mask` and `filters` `usvg::Group` fields store `Rc` instead of `String` now.
- `usvg::NodeExt::units` was moved to `usvg::Paint::units`.
- `usvg::filter::ImageKind::Use` stores `usvg::Node` instead of `String`.
- `usvg::PathData` stores commands and points separately now to reduce overall memory usage.
- `usvg::PathData` segments should be accessed via `segments()` now.
- Most numeric types have been moved to the `strict-num` crate.
- Rename `NormalizedValue` into `NormalizedF64`.
- Rename `PositiveNumber` into `PositiveF64`.
- Raw number of numeric types should be accessed via `get()` method instead of `value()` now.
- `usvg::TextSpan::font_size` is `NonZeroPositiveF64` instead of `f64` now.
- Re-export `usvg` and `tiny-skia` dependencies in `resvg`.
- Re-export `roxmltree` dependency in `usvg`.
- (usvg) Output float precision is reduced from 11 to 8 digits.
### Removed
- `usvg::Tree::create`. `usvg::Tree` is an open struct now.
- `usvg::Tree::root`. It's a public field now.
- `usvg::Tree::svg_node`. Replaced with `usvg::Tree` public fields.
- `defs`, `is_in_defs`, `append_to_defs` and `defs_by_id` from `usvg::Tree`.
We no longer emulate SVG structure. No alternative.
- `usvg::Tree::is_in_defs`. There are no `defs` anymore.
- `usvg::Paint::Link`. We store gradient and patterns directly in `usvg::Paint` now.
- `usvg::Svg`. No longer needed. `size` and `view_box` are `usvg::Tree` fields now.
- `usvg::SubPathIter` and `usvg::PathData::subpaths`. No longer used.
### Fixed
- Path bbox calculation scales stroke width too.
Thanks to [growler](https://github.com/growler).
- (tiny-skia) Round caps roundness.
- (xmlparser) Stack overflow on specific files.
- (c-api) `resvg_is_image_empty` output was inverted.
## [0.23.0] - 2022-06-11
### Added
- `#RRGGBBAA` and `#RGBA` color notation support.
Thanks to [demurgos](https://github.com/demurgos).
### Fixed
- Panic during recursive `pattern` resolving.
Thanks to [FylmTM](https://github.com/FylmTM).
- Spurious warning when using `--export-id`.
Thanks to [benoit-pierre](https://github.com/benoit-pierre).
## [0.22.0] - 2022-02-20
### Added
- Support `svg` referenced by `use`. External SVG files are still not supported.
### Changed
- `ttf-parser`, `fontdb` and `rustybuzz` have been updated.
## [0.21.0] - 2022-02-13
### Added
- `usvg::ImageHrefResolver` that allows a custom `xlink:href` handling.
Thanks to [antmelnyk](https://github.com/antmelnyk).
- `usvg::Options::image_href_resolver`
- Support for GIF images inside the `` element.
- (fontdb) Support for loading user fonts on Windows.
- (fontdb) Support for parsing fontconfig config files on Linux.
For now, only to retrieve a list of font dirs.
### Changed
- MSRV bumped to 1.51
- `usvg::ImageKind` stores data as `Arc>` and not just `Vec` now.
### Fixed
- Every nested `svg` element defines a new viewBox now. Previously, we were always using the root one.
- Correctly handle SVG size calculation when SVG doesn't have a size and any elements.
- Improve groups ungrouping speed.
## [0.20.0] - 2021-12-29
### Changed
- `resvg::render` and `resvg::render_node` accept a transform now.
- (c-api) `resvg_render` and `resvg_render_node` accept a transform now.
- `usvg::Color` is a custom type and not a `svgtypes::Color` reexport now.
- `usvg::Color` doesn't contain alpha anymore, which have been added in v0.16
Alpha would be automatically flattened.
This makes [Micro SVG](https://github.com/linebender/resvg/blob/main/crates/usvg/docs/spec.adoc)
compatible with SVG 1.1 again.
- (c-api) Rename `RESVG_FIT_TO_*` into `RESVG_FIT_TO_TYPE_*`.
### Fixed
- The `--background` argument in `resvg` correctly handles alpha now.
- Fix building usvg without filter feature but with export.
## [0.19.0] - 2021-10-04
### Added
- Better text-on-path converter accuracy by accounting the current transform.
### Changed
- `usvg::NodeExt::abs_transform` includes current node transform now.
- Improved turbulence filter performance. Thanks to [akindle](https://github.com/akindle).
- Multiple dependencies updated.
## [0.18.0] - 2021-09-12
### Added
- `filter` build feature. Enabled by default.
- `usvg::PathBbox` and `resvg_path_bbox` (to C API).
### Changed
- (usvg) All filter related types are under the `filter` module now.
- (usvg) Remove `Fe` prefix from all filter types.
- (c-api) `resvg_get_node_bbox` returns `resvg_path_bbox` now.
### Fixed
- Horizontal and vertical lines processing.
- C API building without the `text` feature.
## [0.17.0] - 2021-09-04
### Added
- `tiny-skia` updated with support of images larger than 8000x8000 pixels.
- `feDropShadow` support. SVG2
- [``](https://www.w3.org/TR/filter-effects-1/#typedef-filter-value-list) support.
Meaning that the `filter` attribute can have multiple values now.
Like `url(#filter1) blur(2)`. SVG2
- All [filter functions](https://www.w3.org/TR/filter-effects-1/#filter-functions). SVG2
- Support all [new](https://www.w3.org/TR/compositing-1/#ltblendmodegt) `feBlend` modes. SVG2
- Automatic SVG size detection when `width`/`height`/`viewBox` is not set.
Thanks to [reknih](https://github.com/reknih).
- `usvg::Options::default_size`
- `--default-width` and `--default-height` to usvg.
### Changed
- `usvg::Group::filter` is a list of filter IDs now.
- `usvg::FeColorMatrixKind::Saturate` accepts any positive `f64` value now.
- `svgfilters::ColorMatrix::Saturate` accepts any positive `f64` value now.
- Fonts memory mapping was split into a separate build feature: `memmap-fonts`.
Now you can build resvg/usvg with `system-fonts`, but without `memmap-fonts`.
Enabled by default.
- The `--dump-svg` argument in resvg CLI tool should be enabled using `--features dump-svg` now.
No enabled by default.
- `usvg::Tree::to_string` is behind the `export` build feature now.
### Fixed
- When writing SVG, `usvg` will use `rgba()` notations for colors instead of `#RRGGBB`.
## [0.16.0] - 2021-08-22
### Added
- CSS3 colors support. Specifically `rgba`, `hsl`, `hsla` and `transparent`. SVG2
- Allow missing `rx`/`ry` attributes on `ellipse`. SVG2
- Allow markers on all shapes. SVG2
- `textPath` can reference basic shapes now. SVG2
- `usvg::OptionsRef`, which is a non-owned `usvg::Options` variant.
- `simplecss` updated with CSS specificity support.
- `turn` angle unit support. SVG2
- Basic `font-variant=small-caps` support. No font fallback.
- `--export-area-page` to resvg.
- `--export-area-drawing` to resvg.
### Changed
- `resvg::render_node` requires `usvg::Tree` now.
- `usvg::Color` gained an `alpha` field.
### Removed
- `usvg::Node::tree`. Cannot be implemented efficiently anymore.
- `usvg::SystemFontDB`. No longer needed.
### Fixed
- `pattern` scaling.
- Greatly improve `symbol` resolving speed in `usvg`.
- Whitespaces trimming on nested `tspan`.
## [0.15.0] - 2021-06-13
### Added
- Allow reading SVG from stdin in `resvg` binary.
- `--id-prefix` to `usvg`.
- `FitTo::Size`
- `resvg` binary accepts `--width` and `--height` args together now.
Previously, only `--width` or `--height` were allowed.
- `usvg::Path::text_bbox`
- The maximum number of SVG elements is limited by 1_000_000 now.
Mainly to prevent a billion laugh style attacks.
- The maximum SVG elements nesting is limited by 1024 now.
- `usvg::Error::ElementsLimitReached`
### Changed
- Improve clipping and masking performance on large images.
- Remove layers caching. This was a pointless optimization.
- Split _Preprocessing_ into _Reading_ and _Parsing_ in `resvg --perf`.
- `usvg::XmlOptions` rewritten.
- `usvg::Tree::to_string` requires a reference to `XmlOptions` now.
### Removed
- `usvg::Tree::from_file`. Use `from_data` or `from_str` instead.
- `usvg::Error::InvalidFileSuffix`
- `usvg::Error::FileOpenFailed`
- (c-api) `RESVG_ERROR_INVALID_FILE_SUFFIX`
### Fixed
- Ignore tiny blur values. It could lead to a transparent image.
- `use` style propagation when used with `symbol`.
- Vertical text layout with relative offsets.
- Text bbox calculation. `usvg` uses font metrics instead of path bbox now.
## [0.14.1] - 2021-04-18
### Added
- Allow `href` without the `xlink` namespace.
This feature is part of SVG 2 (which we do not support),
but there are more and more files like this in the wild.
### Changed
- (usvg) Do not write `usvg:version` to the output SVG.
### Fixed
- (usvg) `overflow='inherit'` resolving.
- (usvg) SVG Path length calculation that affects `startOffset` property in `textPath`.
- (usvg) Fix `feImage` resolving when the linked element has
`opacity`, `clip-path`, `mask` and/or `filter` attributes.
- (usvg) Fix chained `feImage` resolving.
- CLI arguments processing.
## [0.14.0] - 2021-03-06
### Fixed
- Multiple critical bugs in `tiny-skia`.
## [0.13.1] - 2021-01-20
### Fixed
- `image` with float size scaling.
- Critical bug in `tiny-skia`.
## [0.13.0] - 2020-12-21
### Added
- `--resources-dir` option to CLI tools.
- (usvg) `Tree::from_xmltree`
### Changed
- Remove the `Image` struct. `render()` and `render_node()` methods now accept `tiny_skia::PixmapMut`.
- Update `fontdb`.
- Update `tiny-skia`.
- (c-api) `resvg_size` uses `double` instead of `uint32_t` now.
- (qt-api) `defaultSize()` and `defaultSizeF()` methods now return SVG size
and not SVG viewbox size.
- (usvg) `Options::path` changed to `Options::resources_dir` and requires a directory now.
- (c-api) `resvg_options_set_file_path` changed to `resvg_options_set_resources_dir`
and requires a directory now.
- (qt-api) `ResvgOptions::setFilePath` changed to `ResvgOptions::setResourcesDir`
and requires a directory now.
### Fixed
- Support multiple values inside a `text-decoration` attribute.
### Removed
- `Image`. Use `tiny_skia::PixmapMut` instead.
- (c-api) `resvg_image` struct and `resvg_image_*` methods. `resvg` renders onto
the provided buffer now.
- (c-api) `resvg_color`, because unused.
## [0.12.0] - 2020-12-05
### Changed
- resvg no longer requires a C++ compiler!
- `tiny-skia` was updated to a pure Rust version, which means that `resvg` no longer
depends on `clang` and should work on 32bit targets.
- `rustybuzz` was updated to a pure Rust version.
- `tools/explorer-thumbnailer` is back and written in Rust now.
Thanks to [gentoo90](https://github.com/gentoo90).
### Fixed
- (usvg) Do not panic when a font has a zero-sized underline thickness.
- (usvg) Multiple `textPath` processing fixes by [chubei-oppen](https://github.com/chubei-oppen).
- (qt-api) `boundsOnElement` and `boundingBox` were returning transposed bounds.
## [0.11.0] - 2020-07-04
### Highlights
- All backends except Skia were removed. Skia is the only official one from now.
- New C API implementation.
### Added
- Support for user-defined fonts in usvg, resvg and C API.
- `--serif-family`, `--sans-serif-family`, `--cursive-family`, `--fantasy-family`
`--monospace-family`, `--use-font-file`, `--use-fonts-dir`, `--skip-system-fonts` and `--list-fonts`
options to all CLI tools.
- New tests suite. Instead of testing against the previous build, now we're testing against
prerendered PNG images. Which is way faster.
And you can test resvg without the internet connection now.
And all you need is just `cargo test`.
### Changed
- Library uses an embedded Skia by default now.
- Switch `harfbuzz_rs` with `rustybuzz`.
- Rendering doesn't require `usvg::Options` now.
- (usvg) The `fontdb` module moved into its own crate.
- (usvg) `fontconfig` is no longer used for matching
[generic fonts](https://www.w3.org/TR/2018/REC-css-fonts-3-20180920/#generic-family-value)
on Linux. Mainly because it's very slow.
- (usvg) When an `image` element contains a file path, the file will be loaded into memory now,
instead of simply storing a file path. And will be dumped as base64 on SVG save.
In case of an SVG image, it will be loaded as a `Tree` and saved as base64 encoded XML on save.
- (usvg) `ImageData` replaced with `ImageKind`.
- (usvg) Fonts database is empty by default now and should be filled manually.
- (c-api) Almost a complete rewrite.
### Removed
- All backends except the Skia one.
- `Options` from all backends. We don't use it anymore.
- (usvg) `ImageFormat`.
- (c-api) Rendering on a backends canvas no longer supported. Was constantly misused.
## [0.10.0] - 2020-06-19
### Changed
- The `resvg` crate has been split into four: resvg-cairo, resvg-qt, resvg-skia and resvg-raqote.
So from now, instead of enabling a required backend via cargo features,
you should select a required backend-specific crate.
This allows us to have a better integration with a selected 2D library.
And we also have separated C API implementations now.
And each backend has its own vendored archive too.
- (qt-backend) Use `QImage` instead of Rust libraries for raster images loading.
### Removed
- The `resvg` crate. Use backend-specific crates.
- `tools/rendersvg`. Each backend has its own CLI tool now.
- `tools/usvg`. `usvg` implements CLI by default now.
- (c-api) `resvg_*_render_to_file` methods.
- (qt-backend) `jpeg-decoder` and `png` dependencies.
## [0.9.1] - 2020-06-03
### Fixed
- Stack overflow when `enable-background` and `filter` are set on the same element.
- Grayscale PNG loading.
- Allow building on BSD.
- (usvg) Font fallback when shaping produces a different amount of glyphs.
- (usvg) Ignore a space after the last character during `letter-spacing` processing.
- (usvg) `marker-end` rendering when the last segment is a curve with the second control point
that coincides with end point.
- (usvg) Accept embedded `image` data without mime.
- (usvg) Fonts search in a home directory on Linux.
- (usvg) `dy` calculation for `textPath` thanks to [Stoeoef](https://github.com/Stoeoef)
- (usvg) `textPath` resolving when a referenced path has a transform.
Thanks to [Stoeoef](https://github.com/Stoeoef).
- (usvg) Load user fonts on macOS too.
- (xmlparser) Parsing comment before DTD.
## [0.9.0] - 2020-01-18
### Added
- `feConvolveMatrix`, `feMorphology`, `feDisplacementMap`, `feTurbulence`,
`feDiffuseLighting` and `feSpecularLighting` support.
- `BackgroundImage`, `BackgroundAlpha`, `FillPaint` and `StrokePaint` support as a filter input.
- Load grayscale raster images.
- `enable-background` support.
- resvg/usvg can be built without text rendering support now.
- `OutputImage::make_vec` and `OutputImage::make_rgba_vec`.
- `feImage` with a reference to an internal element.
### Changed
- `feComposite` k1-4 coefficients can have any number now.
This matches browsers behaviour.
- Use `flate2` instead of `libflate` for GZip decoding.
- (usvg) `fill` and `stroke` attributes will always be set for `path` now.
- (usvg) `g`, `path` and `image` can now be set inside `defs`. Required by `feImage`.
- (c-api) Rename `resvg_*_render_to_image` into `resvg_*_render_to_file`.
### Fixed
- (usvg) Transform processing during text-to-path conversion.
- `feComposite` with fully transparent region was producing an invalid result.
- Fallback to `matrix` in `feColorMatrix` when `type` is not set or invalid.
- ID preserving for `use` elements.
- `feFlood` with subregion and `primitiveUnits=objectBoundingBox`.
- (harfbuzz_rs) Memory leak.
## [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 Rust crates 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*.
- (usvg) 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 `objectBoundingBox` 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 `objectBoundingBox` 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.
[Linebender]: https://github.com/linebender
[@DJMcNab]: https://github.com/DJMcNab
[@michabay05]: https://github.com/michabay05
[@Shnatsel]: https://github.com/Shnatsel
[@JosefKuchar]: https://github.com/JosefKuchar
[@tovrstra]: https://github.com/tovrstra
[@newinnovations]: https://github.com/newinnovations
[@LaurenzV]: https://github.com/LaurenzV
[@HaHa421]: https://github.com/HaHa421
[@Dabble63]: https://github.com/Dabble63
[@Daaiid]: https://github.com/Daaiid
[@arnaud-secondlayer]: https://github.com/arnaud-secondlayer
[@Its-Just-Nans]: https://github.com/Its-Just-Nans
[#897]: https://github.com/linebender/resvg/pull/897
[Unreleased]: https://github.com/linebender/resvg/compare/v0.47.0...HEAD
[0.47.0]: https://github.com/linebender/resvg/compare/v0.46.0...v0.47.0
[0.46.0]: https://github.com/linebender/resvg/compare/v0.45.1...v0.46.0
[0.45.1]: https://github.com/linebender/resvg/compare/v0.45.0...v0.45.1
[0.45.0]: https://github.com/linebender/resvg/compare/v0.44.0...v0.45.0
[0.44.0]: https://github.com/linebender/resvg/compare/v0.43.0...v0.44.0
[0.43.0]: https://github.com/linebender/resvg/compare/v0.42.0...v0.43.0
[0.42.0]: https://github.com/linebender/resvg/compare/v0.41.0...v0.42.0
[0.41.0]: https://github.com/linebender/resvg/compare/v0.40.0...v0.41.0
[0.40.0]: https://github.com/linebender/resvg/compare/v0.39.0...v0.40.0
[0.39.0]: https://github.com/linebender/resvg/compare/v0.38.0...v0.39.0
[0.38.0]: https://github.com/linebender/resvg/compare/v0.37.0...v0.38.0
[0.37.0]: https://github.com/linebender/resvg/compare/v0.36.0...v0.37.0
[0.36.0]: https://github.com/linebender/resvg/compare/v0.35.0...v0.36.0
[0.35.0]: https://github.com/linebender/resvg/compare/v0.34.1...v0.35.0
[0.34.1]: https://github.com/linebender/resvg/compare/v0.34.0...v0.34.1
[0.34.0]: https://github.com/linebender/resvg/compare/v0.33.0...v0.34.0
[0.33.0]: https://github.com/linebender/resvg/compare/v0.32.0...v0.33.0
[0.32.0]: https://github.com/linebender/resvg/compare/v0.31.1...v0.32.0
[0.31.1]: https://github.com/linebender/resvg/compare/v0.31.0...v0.31.1
[0.31.0]: https://github.com/linebender/resvg/compare/v0.30.0...v0.31.0
[0.30.0]: https://github.com/linebender/resvg/compare/v0.29.0...v0.30.0
[0.29.0]: https://github.com/linebender/resvg/compare/v0.28.0...v0.29.0
[0.28.0]: https://github.com/linebender/resvg/compare/v0.27.0...v0.28.0
[0.27.0]: https://github.com/linebender/resvg/compare/v0.26.1...v0.27.0
[0.26.1]: https://github.com/linebender/resvg/compare/v0.26.0...v0.26.1
[0.26.0]: https://github.com/linebender/resvg/compare/v0.25.0...v0.26.0
[0.25.0]: https://github.com/linebender/resvg/compare/v0.24.0...v0.25.0
[0.24.0]: https://github.com/linebender/resvg/compare/v0.23.0...v0.24.0
[0.23.0]: https://github.com/linebender/resvg/compare/v0.22.0...v0.23.0
[0.22.0]: https://github.com/linebender/resvg/compare/v0.21.0...v0.22.0
[0.21.0]: https://github.com/linebender/resvg/compare/v0.20.0...v0.21.0
[0.20.0]: https://github.com/linebender/resvg/compare/v0.19.0...v0.20.0
[0.19.0]: https://github.com/linebender/resvg/compare/v0.18.0...v0.19.0
[0.18.0]: https://github.com/linebender/resvg/compare/v0.17.0...v0.18.0
[0.17.0]: https://github.com/linebender/resvg/compare/v0.16.0...v0.17.0
[0.16.0]: https://github.com/linebender/resvg/compare/v0.15.0...v0.16.0
[0.15.0]: https://github.com/linebender/resvg/compare/v0.14.1...v0.15.0
[0.14.1]: https://github.com/linebender/resvg/compare/v0.14.0...v0.14.1
[0.14.0]: https://github.com/linebender/resvg/compare/v0.13.1...v0.14.0
[0.13.1]: https://github.com/linebender/resvg/compare/v0.13.0...v0.13.1
[0.13.0]: https://github.com/linebender/resvg/compare/v0.12.0...v0.13.0
[0.12.0]: https://github.com/linebender/resvg/compare/v0.11.0...v0.12.0
[0.11.0]: https://github.com/linebender/resvg/compare/v0.10.0...v0.11.0
[0.10.0]: https://github.com/linebender/resvg/compare/v0.9.1...v0.10.0
[0.9.1]: https://github.com/linebender/resvg/compare/v0.9.0...v0.9.1
[0.9.0]: https://github.com/linebender/resvg/compare/v0.8.0...v0.9.0
[0.8.0]: https://github.com/linebender/resvg/compare/v0.7.0...v0.8.0
[0.7.0]: https://github.com/linebender/resvg/compare/v0.6.1...v0.7.0
[0.6.1]: https://github.com/linebender/resvg/compare/v0.6.0...v0.6.1
[0.6.0]: https://github.com/linebender/resvg/compare/v0.5.0...v0.6.0
[0.5.0]: https://github.com/linebender/resvg/compare/v0.4.0...v0.5.0
[0.4.0]: https://github.com/linebender/resvg/compare/v0.3.0...v0.4.0
[0.3.0]: https://github.com/linebender/resvg/compare/v0.2.0...v0.3.0
[0.2.0]: https://github.com/linebender/resvg/compare/v0.1.0...v0.2.0
================================================
FILE: Cargo.toml
================================================
[workspace]
members = [
"crates/c-api",
"crates/resvg",
"crates/usvg",
"crates/usvg/codegen",
#"tools/explorer-thumbnailer",
]
default-members = ["crates/resvg"]
resolver = "2"
[workspace.package]
license = "Apache-2.0 OR MIT"
================================================
FILE: LICENSE-APACHE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
================================================
FILE: LICENSE-MIT
================================================
Copyright 2017 the Resvg Authors
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.
================================================
FILE: README.md
================================================
## resvg

[](https://crates.io/crates/resvg)
[](https://docs.rs/resvg)
[](https://www.rust-lang.org)
*resvg* is an [SVG](https://en.wikipedia.org/wiki/Scalable_Vector_Graphics) rendering library.
It can be used as a Rust library, as a C library, and as a CLI application to render static SVG files.
The core idea is to make a fast, small, portable SVG library with the goal to support the whole SVG spec.
## Features
### Designed for edge-cases
SVG is a very complicated format with a large specification (SVG 1.1 is almost 900 pages).
You basically need a web browser to handle all of it. But the truth is that even browsers
fail at this (see [SVG support](https://github.com/linebender/resvg#svg-support)).
Yes, unlike `resvg`, browsers do support dynamic SVG features like animations and scripting.
But using a browser to render SVG _correctly_ is sadly not an option.
To prove its correctness, `resvg` has a vast test suite that includes around 1600 tests.
And those are only SVG-to-PNG regression tests. This doesn't include tests in `resvg` dependencies.
And the best thing is that `resvg` test suite is available to everyone. It's not tied to `resvg`
in any way. Which should help people who plan to develop their own SVG libraries.
### Safety
It's hard not to mention safety when we talk about Rust and processing of a random input.
And we're talking not only about SVG/XML, but also about CSS, TTF, PNG, JPEG, GIF, and GZIP.
While `resvg` is not the only SVG library written in Rust, it's the only one that
is written completely in Rust. There is no non-Rust code in the final binary.
Moreover, there is almost no `unsafe` code either. Still, some dependencies have some `unsafe` code
and font memory-mapping is inherently `unsafe`, but it's best you can get in terms of memory safety.
However, this doesn't stop at memory safety. `resvg` has extensive checks to prevent endless loops (freezes)
and stack overflows (via recursion).
### Zero bloat
Right now, the `resvg` CLI application is less than 3MB in size and doesn't require any external dependencies.
The binary contains nothing that isn't needed for rendering SVG files.
### Portable
`resvg` is guaranteed to work everywhere where you can compile the Rust itself,
including WASM. There are some rough edges with obscure CPU architectures and
mobile OSs (mainly system fonts loading), but it should be pretty painless otherwise.
### SVG preprocessing
Another major difference from other SVG rendering libraries is that in `resvg`
SVG parsing and rendering are two completely separate steps.
Those steps are also split into two separate libraries: `resvg` and [usvg].
Meaning you can easily write your own renderer on top of `usvg` using any 2D library of your liking.
### Performance
Comparing performance between different SVG rendering libraries is like comparing apples and oranges.
Everyone has a very different set of supported features, languages, build flags, etc...
Anyhow, as `resvg` is written in Rust and uses [tiny-skia] for rendering - it's pretty fast.
There should also still be quite a lot of room for improvement.
### Reproducibility
Since `resvg` doesn't rely on any system libraries it allows us to have reproducible results
on all supported platforms. Meaning if you render an SVG file on x86 Windows and then render it
on ARM macOS - the produced image will be identical. Each pixel would have the same value.
## Limitations
- No animations
There are no plans on implementing them either.
- No native text rendering
`resvg` doesn't rely on any system libraries, which implies that we cannot use native text rendering.
Nevertheless, native text rendering is optimized for small horizontal text, which is not
that common in SVG.
- Unicode-only
It's the 21st century. Text files that aren't UTF-8 encoded are no longer relevant.
## SVG support
`resvg` aims to only support the [static](http://www.w3.org/TR/SVG11/feature#SVG-static)
SVG subset; i.e. no `a`, `script`, `view` or `cursor` elements, no events and no animations.
[SVG 2](https://www.w3.org/TR/SVG2/) support is being worked on.
You can search for relevant issues with the
[svg2 tag](https://github.com/linebender/resvg/issues?q=is%3Aissue+is%3Aopen+label%3Asvg2)
or our [SVG 2 changelog](https://github.com/linebender/resvg/blob/main/docs/svg2-changelog.md).
[SVG Tiny 1.2](https://www.w3.org/TR/SVGTiny12/) is not supported and support is also not planned.
Results of the [resvg test suite](https://github.com/linebender/resvg-test-suite):

SVG 2 only results:

You can find a complete table of supported features
[here](https://linebender.org/resvg-test-suite/svg-support-table.html).
It also includes some alternative libraries.
We're not testing against all SVG libraries since many of them are pretty bad.
Some libraries are not on the list because they don't pass the 25% mark.
Such libraries are: wxSvg, LunaSVG and nanosvg.
## resvg project
There is a subtle difference between resvg as a _library_ and resvg as a _project_.
While most users will interact only with the resvg library, it's just a tip of an iceberg.
There are a lot of libraries that I had to write to make resvg possible.
Here are some of them:
- resvg - the actual SVG renderer
- [usvg] - an SVG preprocessor/simplifier
- [tiny-skia] - a [Skia](https://github.com/google/skia) subset ported to Rust
- [rustybuzz] - a [harfbuzz](https://github.com/harfbuzz/harfbuzz) subset ported to Rust
- [ttf-parser] - a TrueType/OpenType font parser
- [fontdb] - a simple, in-memory font database with CSS-like queries
- [roxmltree] - an XML parsing library
- [simplecss] - a pretty decent CSS 2 parser and selector
- [pico-args] - an absolutely minimal, but surprisingly popular command-line arguments parser
So while the resvg _library_ is deceptively small (around 2500 LOC), the resvg _project_
is nearing 75'000 LOC. Which is not that much considering how much resvg does.
It's definitely the smallest option out there.
## License
Licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or )
- MIT license ([LICENSE-MIT](LICENSE-MIT) or )
at your option.
## Contribution
Contributions are welcome by pull request.
The [Rust code of conduct] applies.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be licensed as above, without any additional terms or conditions.
[usvg]: https://github.com/linebender/resvg/tree/main/crates/usvg
[rustybuzz]: https://github.com/harfbuzz/rustybuzz
[tiny-skia]: https://github.com/linebender/tiny-skia
[ttf-parser]: https://github.com/harfbuzz/ttf-parser
[roxmltree]: https://github.com/RazrFalcon/roxmltree
[simplecss]: https://github.com/linebender/simplecss
[fontdb]: https://github.com/RazrFalcon/fontdb
[pico-args]: https://github.com/RazrFalcon/pico-args
[Rust Code of Conduct]: https://www.rust-lang.org/policies/code-of-conduct
================================================
FILE: crates/c-api/Cargo.toml
================================================
[package]
name = "resvg-capi"
version = "0.47.0"
keywords = ["svg", "render", "raster", "c-api"]
license.workspace = true
edition = "2024"
rust-version = "1.87.0"
workspace = "../.."
[lib]
name = "resvg"
path = "lib.rs"
crate-type = ["cdylib", "staticlib"]
[dependencies]
log = "0.4"
resvg = { path = "../resvg", default-features = false }
[features]
default = ["text", "system-fonts", "memmap-fonts", "raster-images"]
# enables SVG Text support
# adds around 500KiB to your binary
text = ["resvg/text"]
# enables system fonts loading (only for `text`)
system-fonts = ["resvg/system-fonts"]
# enables font files memmaping for faster loading (only for `text`)
memmap-fonts = ["resvg/memmap-fonts"]
raster-images = ["resvg/raster-images"]
capi = []
[package.metadata.capi.header]
generation = false
[package.metadata.capi.install.include]
asset = [{ from="resvg.h" }]
================================================
FILE: crates/c-api/LICENSE-APACHE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
================================================
FILE: crates/c-api/LICENSE-MIT
================================================
Copyright 2017 the Resvg Authors
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.
================================================
FILE: crates/c-api/README.md
================================================
# C API for resvg
## Build
```sh
cargo build --release
```
This will produce dynamic and static C libraries that can be found at `../target/release`.
## Header generation
The `resvg.h` is generated via [cbindgen](https://github.com/eqrion/cbindgen)
and then manually edited a bit.
## License
Licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or )
- MIT license ([LICENSE-MIT](LICENSE-MIT) or )
at your option.
## Contribution
Contributions are welcome by pull request.
The [Rust code of conduct] applies.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be licensed as above, without any additional terms or conditions.
[Rust Code of Conduct]: https://www.rust-lang.org/policies/code-of-conduct
================================================
FILE: crates/c-api/ResvgQt.h
================================================
// Copyright 2018 the Resvg Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
/**
* @file ResvgQt.h
*
* An idiomatic Qt API for resvg.
*/
#ifndef RESVG_QT_H
#define RESVG_QT_H
#define RESVG_QT_MAJOR_VERSION 0
#define RESVG_QT_MINOR_VERSION 47
#define RESVG_QT_PATCH_VERSION 0
#define RESVG_QT_VERSION "0.47.0"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace ResvgPrivate {
class Data
{
public:
~Data()
{
clear();
}
void reset()
{
clear();
}
resvg_render_tree *tree = nullptr;
QSizeF size;
QString errMsg;
private:
void clear()
{
// No need to deallocate opt.font_family, because it is a constant.
if (tree) {
resvg_tree_destroy(tree);
tree = nullptr;
}
size = QSizeF();
errMsg = QString();
}
};
static QString errorToString(const int err)
{
switch (err) {
case RESVG_OK :
return QString();
case RESVG_ERROR_NOT_AN_UTF8_STR :
return QLatin1String("The SVG content has not an UTF-8 encoding.");
case RESVG_ERROR_FILE_OPEN_FAILED :
return QLatin1String("Failed to read the file.");
case RESVG_ERROR_MALFORMED_GZIP :
return QLatin1String("Not a GZip compressed data.");
case RESVG_ERROR_ELEMENTS_LIMIT_REACHED :
return QLatin1String("Too many elements.");
case RESVG_ERROR_INVALID_SIZE :
return QLatin1String("SVG doesn't have a valid size.");
case RESVG_ERROR_PARSING_FAILED :
return QLatin1String("Failed to parse an SVG data.");
}
Q_UNREACHABLE();
}
} //ResvgPrivate
/**
* @brief SVG parsing options.
*/
class ResvgOptions {
public:
/**
* @brief Constructs a new options set.
*/
ResvgOptions()
: d(resvg_options_create())
{
// Do not set the default font via QFont::family()
// because it will return a dummy one on Windows.
// See https://github.com/linebender/resvg/issues/159
setLanguages({ QLocale().bcp47Name() });
}
/**
* @brief Sets a directory that will be used during relative paths resolving.
*
* Expected to be the same as the directory that contains the SVG file,
* but can be set to any.
*
* Default: not set
*/
void setResourcesDir(const QString &path)
{
Q_ASSERT(QFileInfo(path).isDir());
if (path.isEmpty()) {
resvg_options_set_resources_dir(d, nullptr);
} else {
auto pathC = path.toUtf8();
pathC.append('\0');
resvg_options_set_resources_dir(d, pathC.constData());
}
}
/**
* @brief Sets the target DPI.
*
* Impact units conversion.
*
* Default: 96
*/
void setDpi(const float dpi)
{
resvg_options_set_dpi(d, dpi);
}
/**
* @brief Sets the default font family.
*
* Will be used when no `font-family` attribute is set in the SVG.
*
* Default: Times New Roman
*/
void setFontFamily(const QString &family)
{
if (family.isEmpty()) {
return;
}
auto familyC = family.toUtf8();
familyC.append('\0');
resvg_options_set_font_family(d, familyC.constData());
}
/**
* @brief Sets the default font size.
*
* Will be used when no `font-size` attribute is set in the SVG.
*
* Default: 12
*/
void setFontSize(const float size)
{
resvg_options_set_font_size(d, size);
}
/**
* @brief Sets a list of languages.
*
* Will be used to resolve a `systemLanguage` conditional attribute.
*
* Example: en, en-US.
*
* Default: en
*/
void setLanguages(const QStringList &languages)
{
if (languages.isEmpty()) {
resvg_options_set_languages(d, nullptr);
} else {
auto languagesC = languages.join(',').toUtf8();
languagesC.append('\0');
resvg_options_set_languages(d, languagesC.constData());
}
}
/**
* @brief Sets the default shape rendering method.
*
* Will be used when an SVG element's `shape-rendering` property is set to `auto`.
*
* Default: `RESVG_SHAPE_RENDERING_GEOMETRIC_PRECISION`
*/
void setShapeRenderingMode(const resvg_shape_rendering mode)
{
resvg_options_set_shape_rendering_mode(d, mode);
}
/**
* @brief Sets the default text rendering method.
*
* Will be used when an SVG element's `text-rendering` property is set to `auto`.
*
* Default: `RESVG_TEXT_RENDERING_OPTIMIZE_LEGIBILITY`
*/
void setTextRenderingMode(const resvg_text_rendering mode)
{
resvg_options_set_text_rendering_mode(d, mode);
}
/**
* @brief Sets the default image rendering method.
*
* Will be used when an SVG element's `image-rendering` property is set to `auto`.
*
* Default: `RESVG_IMAGE_RENDERING_OPTIMIZE_QUALITY`
*/
void setImageRenderingMode(const resvg_image_rendering mode)
{
resvg_options_set_image_rendering_mode(d, mode);
}
/**
* @brief Loads a font data into the internal fonts database.
*
* Prints a warning into the log when the data is not a valid TrueType font.
*/
void loadFontData(const QByteArray &data)
{
resvg_options_load_font_data(d, data.constData(), data.size());
}
/**
* @brief Loads a font file into the internal fonts database.
*
* Prints a warning into the log when the data is not a valid TrueType font.
*/
bool loadFontFile(const QString &path)
{
auto pathC = path.toUtf8();
pathC.append('\0');
return resvg_options_load_font_file(d, pathC.constData());
}
/**
* @brief Loads system fonts into the internal fonts database.
*
* This method is very IO intensive.
*
* This method should be executed only once per #resvg_options.
*
* The system scanning is not perfect, so some fonts may be omitted.
* Please send a bug report in this case.
*
* Prints warnings into the log.
*/
void loadSystemFonts()
{
resvg_options_load_system_fonts(d);
}
/**
* @brief Destructs options.
*/
~ResvgOptions()
{
resvg_options_destroy(d);
}
friend class ResvgRenderer;
private:
resvg_options * const d;
};
/**
* @brief QSvgRenderer-like wrapper for resvg.
*/
class ResvgRenderer {
public:
/**
* @brief Constructs a new renderer.
*/
ResvgRenderer()
: d(new ResvgPrivate::Data())
{
}
/**
* @brief Constructs a new renderer and loads the contents of the SVG(Z) file.
*/
ResvgRenderer(const QString &filePath, const ResvgOptions &opt)
: d(new ResvgPrivate::Data())
{
load(filePath, opt);
}
/**
* @brief Constructs a new renderer and loads the SVG data.
*/
ResvgRenderer(const QByteArray &data, const ResvgOptions &opt)
: d(new ResvgPrivate::Data())
{
load(data, opt);
}
/**
* @brief Loads the contents of the SVG(Z) file.
*/
bool load(const QString &filePath, const ResvgOptions &opt)
{
// Check for Qt resource path.
if (filePath.startsWith(QLatin1String(":/"))) {
QFile file(filePath);
if (file.open(QFile::ReadOnly))
return load(file.readAll(), opt);
else
return false;
}
d->reset();
auto filePathC = filePath.toUtf8();
filePathC.append('\0');
const auto err = resvg_parse_tree_from_file(filePathC.constData(), opt.d, &d->tree);
if (err != RESVG_OK) {
d->errMsg = ResvgPrivate::errorToString(err);
return false;
}
const auto s = resvg_get_image_size(d->tree);
d->size = QSizeF(s.width, s.height);
return true;
}
/**
* @brief Loads the SVG data.
*/
bool load(const QByteArray &data, const ResvgOptions &opt)
{
d->reset();
const auto err = resvg_parse_tree_from_data(data.constData(), data.size(), opt.d, &d->tree);
if (err != RESVG_OK) {
d->errMsg = ResvgPrivate::errorToString(err);
return false;
}
const auto s = resvg_get_image_size(d->tree);
d->size = QSizeF(s.width, s.height);
return true;
}
/**
* @brief Returns \b true if the file or data were loaded successful.
*/
bool isValid() const
{
return d->tree;
}
/**
* @brief Returns an underling error when #isValid is \b false.
*/
QString errorString() const
{
return d->errMsg;
}
/**
* @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 no nodes.
*/
bool isEmpty() const
{
if (d->tree)
return resvg_is_image_empty(d->tree);
else
return true;
}
/**
* @brief Returns an SVG size.
*
* The `width` and `height` attributes in SVG.
*/
QSize defaultSize() const
{
return defaultSizeF().toSize();
}
/**
* @brief Returns an SVG size.
*
* The `width` and `height` attributes in SVG.
*/
QSizeF defaultSizeF() const
{
if (d->tree)
return d->size.toSize();
else
return QSizeF();
}
/**
* @brief Returns an SVG viewbox.
*
* `resvg` flattens the `viewbox`, therefore this method returns
* the same value as \b size.
*/
QRect viewBox() const
{
return QRect(0, 0, d->size.width(), d->size.height());
}
/**
* @brief Returns an SVG viewbox.
*
* `resvg` flattens the `viewbox`, therefore this method returns
* the same value as \b size.
*/
QRectF viewBoxF() const
{
return QRectF(0, 0, d->size.width(), d->size.height());
}
/**
* @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
{
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.width, bbox.height);
return QRectF();
}
/**
* @brief Returns bounding rectangle of a whole image.
*/
QRectF boundingBox() const
{
if (!d->tree)
return QRectF();
resvg_rect bbox;
if (resvg_get_object_bbox(d->tree, &bbox))
return QRectF(bbox.x, bbox.y, bbox.width, bbox.height);
return QRectF();
}
/**
* @brief Returns \b true if element with such an ID exists.
*/
bool 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);
}
/**
* @brief Returns element's transform.
*/
QTransform 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();
}
// TODO: render node
/**
* @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
{
resvg_transform ts = resvg_transform_identity();
if (size.isValid()) {
// TODO: support height too.
auto sizef = defaultSizeF();
const auto newHeight = std::ceil(double(size.width()) * sizef.height() / sizef.width());
ts.a = double(size.width()) / sizef.width();
ts.d = newHeight / sizef.height();
}
auto svgSize = size;
if (svgSize.isEmpty())
svgSize = defaultSize();
QImage qImg(svgSize.width(), svgSize.height(), QImage::Format_ARGB32_Premultiplied);
qImg.fill(Qt::transparent);
resvg_render(d->tree, ts, qImg.width(), qImg.height(), (char*)qImg.bits());
// resvg renders onto the RGBA canvas, while QImage is ARGB.
// std::move is required to call inplace version of rgbSwapped().
return std::move(qImg).rgbSwapped();
}
/**
* @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()
{
resvg_init_log();
}
private:
QScopedPointer d;
};
#endif // RESVG_QT_H
================================================
FILE: crates/c-api/cbindgen.toml
================================================
language = "C"
include_guard = "RESVG_H"
braces = "SameLine"
tab_width = 4
documentation_style = "doxy"
header = """// Copyright 2021 the Resvg Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
/**
* @file resvg.h
*
* resvg C API
*/"""
cpp_compat = true
no_includes = true
sys_includes = ["stdbool.h", "stdint.h"]
style = "type"
[fn]
sort_by = "None"
[enum]
rename_variants = "ScreamingSnakeCase"
prefix_with_name = true
[export]
include = [
"resvg_error",
"resvg_shape_rendering",
"resvg_text_rendering",
"resvg_image_rendering",
]
================================================
FILE: crates/c-api/examples/cairo/Makefile
================================================
TARGET = example
LIBS = -lm -L../../../../target/debug -lresvg `pkg-config --libs cairo`
CC = gcc
CFLAGS = -g -Wall `pkg-config --cflags cairo` -I../../
.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)
================================================
FILE: crates/c-api/examples/cairo/README.md
================================================
A simple example that shows how to use *resvg* through C API to render on a Cairo context.
## Run
```bash
cargo build --manifest-path ../../Cargo.toml
make
LD_LIBRARY_PATH=../../../../target/debug ./example image.svg image.png
```
================================================
FILE: crates/c-api/examples/cairo/example.c
================================================
// Copyright 2020 the Resvg Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
if (argc != 3)
{
printf("Usage:\n\texample in.svg out.png");
abort();
}
// Initialize resvg's library logging system
resvg_init_log();
resvg_options *opt = resvg_options_create();
resvg_options_load_system_fonts(opt);
// Optionally, you can add some CSS to control the SVG rendering.
resvg_options_set_stylesheet(opt, "svg { fill: black; }");
resvg_render_tree *tree;
// Construct a tree from the svg file and pass in some options
int err = resvg_parse_tree_from_file(argv[1], opt, &tree);
resvg_options_destroy(opt);
if (err != RESVG_OK)
{
printf("Error id: %i\n", err);
abort();
}
resvg_size size = resvg_get_image_size(tree);
int width = (int)size.width;
int height = (int)size.height;
// Using the dimension info, allocate enough pixels to account for the entire image
cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
/* resvg doesn't support stride, so cairo_surface_t should have no padding */
assert(cairo_image_surface_get_stride(surface) == (int)size.width * 4);
unsigned char *surface_data = cairo_image_surface_get_data(surface);
resvg_render(tree, resvg_transform_identity(), width, height, (char*)surface_data);
/* RGBA -> BGRA */
for (int i = 0; i < width * height * 4; i += 4)
{
unsigned char r = surface_data[i + 0];
surface_data[i + 0] = surface_data[i + 2];
surface_data[i + 2] = r;
}
// Save image
cairo_surface_write_to_png(surface, argv[2]);
// De-initialize the allocated memory
cairo_surface_destroy(surface);
resvg_tree_destroy(tree);
return 0;
}
================================================
FILE: crates/c-api/lib.rs
================================================
// Copyright 2020 the Resvg Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
//! C bindings.
#![allow(non_camel_case_types)]
#![warn(missing_docs)]
#![warn(missing_copy_implementations)]
use std::ffi::CStr;
use std::os::raw::c_char;
use std::slice;
use resvg::tiny_skia;
use resvg::usvg;
/// @brief List of possible errors.
#[repr(C)]
#[derive(Copy, Clone)]
pub enum resvg_error {
/// Everything is ok.
OK = 0,
/// Only UTF-8 content are supported.
NOT_AN_UTF8_STR,
/// Failed to open the provided file.
FILE_OPEN_FAILED,
/// Compressed SVG must use the GZip algorithm.
MALFORMED_GZIP,
/// We do not allow SVG with more than 1_000_000 elements for security reasons.
ELEMENTS_LIMIT_REACHED,
/// 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.
INVALID_SIZE,
/// Failed to parse an SVG data.
PARSING_FAILED,
}
/// @brief A rectangle representation.
#[repr(C)]
#[allow(missing_docs)]
#[derive(Copy, Clone)]
pub struct resvg_rect {
pub x: f32,
pub y: f32,
pub width: f32,
pub height: f32,
}
/// @brief A size representation.
#[repr(C)]
#[allow(missing_docs)]
#[derive(Copy, Clone)]
pub struct resvg_size {
pub width: f32,
pub height: f32,
}
/// @brief A 2D transform representation.
#[repr(C)]
#[allow(missing_docs)]
#[derive(Copy, Clone)]
pub struct resvg_transform {
pub a: f32,
pub b: f32,
pub c: f32,
pub d: f32,
pub e: f32,
pub f: f32,
}
impl resvg_transform {
#[inline]
fn to_tiny_skia(&self) -> tiny_skia::Transform {
tiny_skia::Transform::from_row(self.a, self.b, self.c, self.d, self.e, self.f)
}
}
/// @brief Creates an identity transform.
#[unsafe(no_mangle)]
pub extern "C" fn resvg_transform_identity() -> resvg_transform {
resvg_transform {
a: 1.0,
b: 0.0,
c: 0.0,
d: 1.0,
e: 0.0,
f: 0.0,
}
}
/// @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 `stderr`.
#[unsafe(no_mangle)]
pub extern "C" fn resvg_init_log() {
if let Ok(()) = log::set_logger(&LOGGER) {
log::set_max_level(log::LevelFilter::Warn);
}
}
/// @brief An SVG to #resvg_render_tree conversion options.
///
/// Also, contains a fonts database used during text to path conversion.
/// The database is empty by default.
pub struct resvg_options {
options: usvg::Options<'static>,
}
/// @brief Creates a new #resvg_options object.
///
/// Should be destroyed via #resvg_options_destroy.
#[unsafe(no_mangle)]
pub extern "C" fn resvg_options_create() -> *mut resvg_options {
Box::into_raw(Box::new(resvg_options {
options: usvg::Options::default(),
}))
}
#[inline]
fn cast_opt(opt: *mut resvg_options) -> &'static mut usvg::Options<'static> {
unsafe {
assert!(!opt.is_null());
&mut (*opt).options
}
}
/// @brief Sets a directory that will be used during relative paths resolving.
///
/// Expected to be the same as the directory that contains the SVG file,
/// but can be set to any.
///
/// Must be UTF-8. Can be set to NULL.
///
/// Default: NULL
#[unsafe(no_mangle)]
pub extern "C" fn resvg_options_set_resources_dir(opt: *mut resvg_options, path: *const c_char) {
if path.is_null() {
cast_opt(opt).resources_dir = None;
} else {
cast_opt(opt).resources_dir = Some(cstr_to_str(path).unwrap().into());
}
}
/// @brief Sets the target DPI.
///
/// Impact units conversion.
///
/// Default: 96
#[unsafe(no_mangle)]
pub extern "C" fn resvg_options_set_dpi(opt: *mut resvg_options, dpi: f32) {
cast_opt(opt).dpi = dpi;
}
/// @brief Provides the content of a stylesheet that will be used when resolving CSS attributes.
///
/// Must be UTF-8. Can be set to NULL.
///
/// Default: NULL
#[unsafe(no_mangle)]
pub extern "C" fn resvg_options_set_stylesheet(opt: *mut resvg_options, content: *const c_char) {
if content.is_null() {
cast_opt(opt).style_sheet = None;
} else {
cast_opt(opt).style_sheet = Some(cstr_to_str(content).unwrap().into());
}
}
/// @brief Sets the default font family.
///
/// Will be used when no `font-family` attribute is set in the SVG.
///
/// Must be UTF-8. NULL is not allowed.
///
/// Default: Times New Roman
#[unsafe(no_mangle)]
pub extern "C" fn resvg_options_set_font_family(opt: *mut resvg_options, family: *const c_char) {
cast_opt(opt).font_family = cstr_to_str(family).unwrap().to_string();
}
/// @brief Sets the default font size.
///
/// Will be used when no `font-size` attribute is set in the SVG.
///
/// Default: 12
#[unsafe(no_mangle)]
pub extern "C" fn resvg_options_set_font_size(opt: *mut resvg_options, size: f32) {
cast_opt(opt).font_size = size;
}
/// @brief Sets the `serif` font family.
///
/// Must be UTF-8. NULL is not allowed.
///
/// Has no effect when the `text` feature is not enabled.
///
/// Default: Times New Roman
#[unsafe(no_mangle)]
#[allow(unused_variables)]
pub extern "C" fn resvg_options_set_serif_family(opt: *mut resvg_options, family: *const c_char) {
#[cfg(feature = "text")]
{
cast_opt(opt)
.fontdb_mut()
.set_serif_family(cstr_to_str(family).unwrap().to_string());
}
}
/// @brief Sets the `sans-serif` font family.
///
/// Must be UTF-8. NULL is not allowed.
///
/// Has no effect when the `text` feature is not enabled.
///
/// Default: Arial
#[unsafe(no_mangle)]
#[allow(unused_variables)]
pub extern "C" fn resvg_options_set_sans_serif_family(
opt: *mut resvg_options,
family: *const c_char,
) {
#[cfg(feature = "text")]
{
cast_opt(opt)
.fontdb_mut()
.set_sans_serif_family(cstr_to_str(family).unwrap().to_string());
}
}
/// @brief Sets the `cursive` font family.
///
/// Must be UTF-8. NULL is not allowed.
///
/// Has no effect when the `text` feature is not enabled.
///
/// Default: Comic Sans MS
#[unsafe(no_mangle)]
#[allow(unused_variables)]
pub extern "C" fn resvg_options_set_cursive_family(opt: *mut resvg_options, family: *const c_char) {
#[cfg(feature = "text")]
{
cast_opt(opt)
.fontdb_mut()
.set_cursive_family(cstr_to_str(family).unwrap().to_string());
}
}
/// @brief Sets the `fantasy` font family.
///
/// Must be UTF-8. NULL is not allowed.
///
/// Has no effect when the `text` feature is not enabled.
///
/// Default: Papyrus on macOS, Impact on other OS'es
#[unsafe(no_mangle)]
#[allow(unused_variables)]
pub extern "C" fn resvg_options_set_fantasy_family(opt: *mut resvg_options, family: *const c_char) {
#[cfg(feature = "text")]
{
cast_opt(opt)
.fontdb_mut()
.set_fantasy_family(cstr_to_str(family).unwrap().to_string());
}
}
/// @brief Sets the `monospace` font family.
///
/// Must be UTF-8. NULL is not allowed.
///
/// Has no effect when the `text` feature is not enabled.
///
/// Default: Courier New
#[unsafe(no_mangle)]
#[allow(unused_variables)]
pub extern "C" fn resvg_options_set_monospace_family(
opt: *mut resvg_options,
family: *const c_char,
) {
#[cfg(feature = "text")]
{
cast_opt(opt)
.fontdb_mut()
.set_monospace_family(cstr_to_str(family).unwrap().to_string());
}
}
/// @brief Sets a comma-separated list of languages.
///
/// Will be used to resolve a `systemLanguage` conditional attribute.
///
/// Example: en,en-US.
///
/// Must be UTF-8. Can be NULL.
///
/// Default: en
#[unsafe(no_mangle)]
pub extern "C" fn resvg_options_set_languages(opt: *mut resvg_options, languages: *const c_char) {
if languages.is_null() {
cast_opt(opt).languages = Vec::new();
return;
}
let languages_str = match cstr_to_str(languages) {
Some(v) => v,
None => return,
};
let mut languages = Vec::new();
for lang in languages_str.split(',') {
languages.push(lang.trim().to_string());
}
cast_opt(opt).languages = languages;
}
/// @brief A shape rendering method.
#[repr(C)]
#[allow(missing_docs)]
#[derive(Copy, Clone)]
pub enum resvg_shape_rendering {
OPTIMIZE_SPEED,
CRISP_EDGES,
GEOMETRIC_PRECISION,
}
/// @brief Sets the default shape rendering method.
///
/// Will be used when an SVG element's `shape-rendering` property is set to `auto`.
///
/// Default: `RESVG_SHAPE_RENDERING_GEOMETRIC_PRECISION`
#[unsafe(no_mangle)]
pub extern "C" fn resvg_options_set_shape_rendering_mode(
opt: *mut resvg_options,
mode: resvg_shape_rendering,
) {
cast_opt(opt).shape_rendering = match mode as i32 {
0 => usvg::ShapeRendering::OptimizeSpeed,
1 => usvg::ShapeRendering::CrispEdges,
2 => usvg::ShapeRendering::GeometricPrecision,
_ => return,
}
}
/// @brief A text rendering method.
#[repr(C)]
#[allow(missing_docs)]
#[derive(Copy, Clone)]
pub enum resvg_text_rendering {
OPTIMIZE_SPEED,
OPTIMIZE_LEGIBILITY,
GEOMETRIC_PRECISION,
}
/// @brief Sets the default text rendering method.
///
/// Will be used when an SVG element's `text-rendering` property is set to `auto`.
///
/// Default: `RESVG_TEXT_RENDERING_OPTIMIZE_LEGIBILITY`
#[unsafe(no_mangle)]
pub extern "C" fn resvg_options_set_text_rendering_mode(
opt: *mut resvg_options,
mode: resvg_text_rendering,
) {
cast_opt(opt).text_rendering = match mode as i32 {
0 => usvg::TextRendering::OptimizeSpeed,
1 => usvg::TextRendering::OptimizeLegibility,
2 => usvg::TextRendering::GeometricPrecision,
_ => return,
}
}
/// @brief A image rendering method.
#[repr(C)]
#[allow(missing_docs)]
#[derive(Copy, Clone)]
pub enum resvg_image_rendering {
OPTIMIZE_QUALITY,
OPTIMIZE_SPEED,
}
/// @brief Sets the default image rendering method.
///
/// Will be used when an SVG element's `image-rendering` property is set to `auto`.
///
/// Default: `RESVG_IMAGE_RENDERING_OPTIMIZE_QUALITY`
#[unsafe(no_mangle)]
pub extern "C" fn resvg_options_set_image_rendering_mode(
opt: *mut resvg_options,
mode: resvg_image_rendering,
) {
cast_opt(opt).image_rendering = match mode as i32 {
0 => usvg::ImageRendering::OptimizeQuality,
1 => usvg::ImageRendering::OptimizeSpeed,
_ => return,
}
}
/// @brief Loads a font data into the internal fonts database.
///
/// Prints a warning into the log when the data is not a valid TrueType font.
///
/// Has no effect when the `text` feature is not enabled.
#[unsafe(no_mangle)]
#[allow(unused_variables)]
pub extern "C" fn resvg_options_load_font_data(
opt: *mut resvg_options,
data: *const c_char,
len: usize,
) {
#[cfg(feature = "text")]
{
let data = unsafe { slice::from_raw_parts(data as *const u8, len) };
cast_opt(opt).fontdb_mut().load_font_data(data.to_vec())
}
}
/// @brief Loads a font file into the internal fonts database.
///
/// Prints a warning into the log when the data is not a valid TrueType font.
///
/// Has no effect when the `text` feature is not enabled.
///
/// @return #resvg_error with RESVG_OK, RESVG_ERROR_NOT_AN_UTF8_STR or RESVG_ERROR_FILE_OPEN_FAILED
#[unsafe(no_mangle)]
#[allow(unused_variables)]
pub extern "C" fn resvg_options_load_font_file(
opt: *mut resvg_options,
file_path: *const c_char,
) -> i32 {
#[cfg(feature = "text")]
{
let file_path = match cstr_to_str(file_path) {
Some(v) => v,
None => return resvg_error::NOT_AN_UTF8_STR as i32,
};
if cast_opt(opt).fontdb_mut().load_font_file(file_path).is_ok() {
resvg_error::OK as i32
} else {
resvg_error::FILE_OPEN_FAILED as i32
}
}
#[cfg(not(feature = "text"))]
{
resvg_error::OK as i32
}
}
/// @brief Loads system fonts into the internal fonts database.
///
/// This method is very IO intensive.
///
/// This method should be executed only once per #resvg_options.
///
/// The system scanning is not perfect, so some fonts may be omitted.
/// Please send a bug report in this case.
///
/// Prints warnings into the log.
///
/// Has no effect when the `text` feature is not enabled.
#[unsafe(no_mangle)]
#[allow(unused_variables)]
pub extern "C" fn resvg_options_load_system_fonts(opt: *mut resvg_options) {
#[cfg(feature = "text")]
{
cast_opt(opt).fontdb_mut().load_system_fonts();
}
}
/// @brief Destroys the #resvg_options.
#[unsafe(no_mangle)]
pub extern "C" fn resvg_options_destroy(opt: *mut resvg_options) {
unsafe {
assert!(!opt.is_null());
let _ = Box::from_raw(opt);
};
}
// TODO: use resvg::Tree
/// @brief An opaque pointer to the rendering tree.
pub struct resvg_render_tree(pub usvg::Tree);
/// @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. Must not be NULL.
/// @param tree Parsed render tree. Should be destroyed via #resvg_tree_destroy.
/// @return #resvg_error
#[unsafe(no_mangle)]
pub extern "C" fn resvg_parse_tree_from_file(
file_path: *const c_char,
opt: *const resvg_options,
tree: *mut *mut resvg_render_tree,
) -> i32 {
let file_path = match cstr_to_str(file_path) {
Some(v) => v,
None => return resvg_error::NOT_AN_UTF8_STR as i32,
};
let raw_opt = unsafe {
assert!(!opt.is_null());
&*opt
};
let file_data = match std::fs::read(file_path) {
Ok(tree) => tree,
Err(_) => return resvg_error::FILE_OPEN_FAILED as i32,
};
let utree = usvg::Tree::from_data(&file_data, &raw_opt.options);
let utree = match utree {
Ok(tree) => tree,
Err(e) => return convert_error(e) as i32,
};
let tree_box = Box::new(resvg_render_tree(utree));
unsafe {
*tree = Box::into_raw(tree_box);
}
resvg_error::OK as i32
}
/// @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. Must not be NULL.
/// @param len Data length.
/// @param opt Rendering options. Must not be NULL.
/// @param tree Parsed render tree. Should be destroyed via #resvg_tree_destroy.
/// @return #resvg_error
#[unsafe(no_mangle)]
pub extern "C" fn resvg_parse_tree_from_data(
data: *const c_char,
len: usize,
opt: *const resvg_options,
tree: *mut *mut resvg_render_tree,
) -> i32 {
let data = unsafe { slice::from_raw_parts(data as *const u8, len) };
let raw_opt = unsafe {
assert!(!opt.is_null());
&*opt
};
let utree = usvg::Tree::from_data(data, &raw_opt.options);
let utree = match utree {
Ok(tree) => tree,
Err(e) => return convert_error(e) as i32,
};
let tree_box = Box::new(resvg_render_tree(utree));
unsafe {
*tree = Box::into_raw(tree_box);
}
resvg_error::OK as i32
}
/// @brief Checks that tree has any nodes.
///
/// @param tree Render tree.
/// @return Returns `true` if tree has no nodes.
#[unsafe(no_mangle)]
pub extern "C" fn resvg_is_image_empty(tree: *const resvg_render_tree) -> bool {
let tree = unsafe {
assert!(!tree.is_null());
&*tree
};
!tree.0.root().has_children()
}
/// @brief Returns an image size.
///
/// The size of an image that is required to render this SVG.
///
/// Note that elements outside the viewbox will be clipped. This is by design.
/// If you want to render the whole SVG content, use #resvg_get_image_bbox instead.
///
/// @param tree Render tree.
/// @return Image size.
#[unsafe(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.size();
resvg_size {
width: size.width(),
height: size.height(),
}
}
/// @brief Returns an object bounding box.
///
/// This bounding box does not include objects stroke and filter regions.
/// This is what SVG calls "absolute object bonding box".
///
/// If you're looking for a "complete" bounding box see #resvg_get_image_bbox
///
/// @param tree Render tree.
/// @param bbox Image's object bounding box.
/// @return `false` if an image has no elements.
#[unsafe(no_mangle)]
pub extern "C" fn resvg_get_object_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().abs_bounding_box().to_non_zero_rect() {
unsafe {
*bbox = resvg_rect {
x: r.x(),
y: r.y(),
width: r.width(),
height: r.height(),
}
}
true
} else {
false
}
}
/// @brief Returns an image bounding box.
///
/// This bounding box contains the maximum SVG dimensions.
/// It's size can be bigger or smaller than #resvg_get_image_size
/// Use it when you want to avoid clipping of elements that are outside the SVG viewbox.
///
/// @param tree Render tree.
/// @param bbox Image's bounding box.
/// @return `false` if an image has no elements.
#[unsafe(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
};
// `abs_layer_bounding_box` returns 0x0x1x1 for empty groups, so we need additional checks.
if tree.0.root().has_children() || !tree.0.root().filters().is_empty() {
let r = tree.0.root().abs_layer_bounding_box();
unsafe {
*bbox = resvg_rect {
x: r.x(),
y: r.y(),
width: r.width(),
height: r.height(),
}
}
true
} else {
false
}
}
/// @brief Returns `true` if a renderable node with such an ID exists.
///
/// @param tree Render tree.
/// @param id Node's ID. UTF-8 string. Must not be NULL.
/// @return `true` if a node exists.
/// @return `false` if a node doesn't exist or ID isn't a UTF-8 string.
/// @return `false` if a node exists, but not renderable.
#[unsafe(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 => {
log::warn!("Provided ID is not a UTF-8 string.");
return false;
}
};
let tree = unsafe {
assert!(!tree.is_null());
&*tree
};
tree.0.node_by_id(id).is_some()
}
/// @brief Returns node's transform by ID.
///
/// @param tree Render tree.
/// @param id Node's ID. UTF-8 string. Must not be NULL.
/// @param transform Node's transform.
/// @return `true` if a node exists.
/// @return `false` if a node doesn't exist or ID isn't a UTF-8 string.
/// @return `false` if a node exists, but not renderable.
#[unsafe(no_mangle)]
pub extern "C" fn resvg_get_node_transform(
tree: *const resvg_render_tree,
id: *const c_char,
transform: *mut resvg_transform,
) -> bool {
let id = match cstr_to_str(id) {
Some(v) => v,
None => {
log::warn!("Provided ID is not a UTF-8 string.");
return false;
}
};
let tree = unsafe {
assert!(!tree.is_null());
&*tree
};
if let Some(node) = tree.0.node_by_id(id) {
let abs_ts = node.abs_transform();
unsafe {
*transform = resvg_transform {
a: abs_ts.sx,
b: abs_ts.ky,
c: abs_ts.kx,
d: abs_ts.sy,
e: abs_ts.tx,
f: abs_ts.ty,
}
}
return true;
}
false
}
/// @brief Returns node's bounding box in canvas coordinates by ID.
///
/// @param tree Render tree.
/// @param id Node's ID. Must not be NULL.
/// @param bbox Node's bounding box.
/// @return `false` if a node with such an ID does not exist
/// @return `false` if ID isn't a UTF-8 string.
/// @return `false` if ID is an empty string
#[unsafe(no_mangle)]
pub extern "C" fn resvg_get_node_bbox(
tree: *const resvg_render_tree,
id: *const c_char,
bbox: *mut resvg_rect,
) -> bool {
get_node_bbox(tree, id, bbox, &|node| node.abs_bounding_box())
}
/// @brief Returns node's bounding box, including stroke, in canvas coordinates by ID.
///
/// @param tree Render tree.
/// @param id Node's ID. Must not be NULL.
/// @param bbox Node's bounding box.
/// @return `false` if a node with such an ID does not exist
/// @return `false` if ID isn't a UTF-8 string.
/// @return `false` if ID is an empty string
#[unsafe(no_mangle)]
pub extern "C" fn resvg_get_node_stroke_bbox(
tree: *const resvg_render_tree,
id: *const c_char,
bbox: *mut resvg_rect,
) -> bool {
get_node_bbox(tree, id, bbox, &|node| node.abs_stroke_bounding_box())
}
fn get_node_bbox(
tree: *const resvg_render_tree,
id: *const c_char,
bbox: *mut resvg_rect,
f: &dyn Fn(&usvg::Node) -> usvg::Rect,
) -> bool {
let id = match cstr_to_str(id) {
Some(v) => v,
None => {
log::warn!("Provided ID is not a UTF-8 string.");
return false;
}
};
if id.is_empty() {
log::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) => {
let r = f(node);
unsafe {
*bbox = resvg_rect {
x: r.x(),
y: r.y(),
width: r.width(),
height: r.height(),
}
}
true
}
None => {
log::warn!("No node with '{}' ID is in the tree.", id);
false
}
}
}
/// @brief Destroys the #resvg_render_tree.
#[unsafe(no_mangle)]
pub extern "C" fn resvg_tree_destroy(tree: *mut resvg_render_tree) {
unsafe {
assert!(!tree.is_null());
let _ = Box::from_raw(tree);
};
}
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 convert_error(e: usvg::Error) -> resvg_error {
match e {
usvg::Error::NotAnUtf8Str => resvg_error::NOT_AN_UTF8_STR,
usvg::Error::MalformedGZip => resvg_error::MALFORMED_GZIP,
usvg::Error::ElementsLimitReached => resvg_error::ELEMENTS_LIMIT_REACHED,
usvg::Error::InvalidSize => resvg_error::INVALID_SIZE,
usvg::Error::ParsingFailed(_) => resvg_error::PARSING_FAILED,
}
}
/// @brief Renders the #resvg_render_tree onto the pixmap.
///
/// @param tree A render tree.
/// @param transform A root SVG transform. Can be used to position SVG inside the `pixmap`.
/// @param width Pixmap width.
/// @param height Pixmap height.
/// @param pixmap Pixmap data. Should have width*height*4 size and contain
/// premultiplied RGBA8888 pixels.
#[unsafe(no_mangle)]
pub extern "C" fn resvg_render(
tree: *const resvg_render_tree,
transform: resvg_transform,
width: u32,
height: u32,
pixmap: *mut c_char,
) {
let tree = unsafe {
assert!(!tree.is_null());
&*tree
};
let pixmap_len = width as usize * height as usize * tiny_skia::BYTES_PER_PIXEL;
let pixmap: &mut [u8] =
unsafe { std::slice::from_raw_parts_mut(pixmap as *mut u8, pixmap_len) };
let mut pixmap = tiny_skia::PixmapMut::from_bytes(pixmap, width, height).unwrap();
resvg::render(&tree.0, transform.to_tiny_skia(), &mut pixmap)
}
/// @brief Renders a Node by ID onto the image.
///
/// @param tree A render tree.
/// @param id Node's ID. Must not be NULL.
/// @param transform A root SVG transform. Can be used to position SVG inside the `pixmap`.
/// @param width Pixmap width.
/// @param height Pixmap height.
/// @param pixmap Pixmap data. Should have width*height*4 size and contain
/// premultiplied RGBA8888 pixels.
/// @return `false` when `id` is not a non-empty UTF-8 string.
/// @return `false` when the selected `id` is not present.
/// @return `false` when an element has a zero bbox.
#[unsafe(no_mangle)]
pub extern "C" fn resvg_render_node(
tree: *const resvg_render_tree,
id: *const c_char,
transform: resvg_transform,
width: u32,
height: u32,
pixmap: *mut c_char,
) -> bool {
let tree = unsafe {
assert!(!tree.is_null());
&*tree
};
let id = match cstr_to_str(id) {
Some(v) => v,
None => return false,
};
if id.is_empty() {
log::warn!("Node with an empty ID cannot be rendered.");
return false;
}
if let Some(node) = tree.0.node_by_id(id) {
let pixmap_len = width as usize * height as usize * tiny_skia::BYTES_PER_PIXEL;
let pixmap: &mut [u8] =
unsafe { std::slice::from_raw_parts_mut(pixmap as *mut u8, pixmap_len) };
let mut pixmap = tiny_skia::PixmapMut::from_bytes(pixmap, width, height).unwrap();
resvg::render_node(node, transform.to_tiny_skia(), &mut pixmap).is_some()
} else {
log::warn!("A node with '{}' ID wasn't found.", id);
false
}
}
/// A simple stderr logger.
static LOGGER: SimpleLogger = SimpleLogger;
struct SimpleLogger;
impl log::Log for SimpleLogger {
fn enabled(&self, metadata: &log::Metadata) -> bool {
metadata.level() <= log::LevelFilter::Warn
}
fn log(&self, record: &log::Record) {
if self.enabled(record.metadata()) {
let target = if record.target().len() > 0 {
record.target()
} else {
record.module_path().unwrap_or_default()
};
let line = record.line().unwrap_or(0);
let args = record.args();
match record.level() {
log::Level::Error => eprintln!("Error (in {}:{}): {}", target, line, args),
log::Level::Warn => eprintln!("Warning (in {}:{}): {}", target, line, args),
log::Level::Info => eprintln!("Info (in {}:{}): {}", target, line, args),
log::Level::Debug => eprintln!("Debug (in {}:{}): {}", target, line, args),
log::Level::Trace => eprintln!("Trace (in {}:{}): {}", target, line, args),
}
}
}
fn flush(&self) {}
}
================================================
FILE: crates/c-api/resvg.h
================================================
// Copyright 2021 the Resvg Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
/**
* @file resvg.h
*
* resvg C API
*/
#ifndef RESVG_H
#define RESVG_H
#include
#include
#define RESVG_MAJOR_VERSION 0
#define RESVG_MINOR_VERSION 47
#define RESVG_PATCH_VERSION 0
#define RESVG_VERSION "0.47.0"
/**
* @brief List of possible errors.
*/
typedef enum {
/**
* 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,
/**
* Compressed SVG must use the GZip algorithm.
*/
RESVG_ERROR_MALFORMED_GZIP,
/**
* We do not allow SVG with more than 1_000_000 elements for security reasons.
*/
RESVG_ERROR_ELEMENTS_LIMIT_REACHED,
/**
* 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.
*/
RESVG_ERROR_INVALID_SIZE,
/**
* Failed to parse an SVG data.
*/
RESVG_ERROR_PARSING_FAILED,
} resvg_error;
/**
* @brief A image rendering method.
*/
typedef enum {
RESVG_IMAGE_RENDERING_OPTIMIZE_QUALITY,
RESVG_IMAGE_RENDERING_OPTIMIZE_SPEED,
} resvg_image_rendering;
/**
* @brief A shape rendering method.
*/
typedef enum {
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_OPTIMIZE_SPEED,
RESVG_TEXT_RENDERING_OPTIMIZE_LEGIBILITY,
RESVG_TEXT_RENDERING_GEOMETRIC_PRECISION,
} resvg_text_rendering;
/**
* @brief An SVG to #resvg_render_tree conversion options.
*
* Also, contains a fonts database used during text to path conversion.
* The database is empty by default.
*/
typedef struct resvg_options resvg_options;
/**
* @brief An opaque pointer to the rendering tree.
*/
typedef struct resvg_render_tree resvg_render_tree;
/**
* @brief A 2D transform representation.
*/
typedef struct {
float a;
float b;
float c;
float d;
float e;
float f;
} resvg_transform;
/**
* @brief A size representation.
*/
typedef struct {
float width;
float height;
} resvg_size;
/**
* @brief A rectangle representation.
*/
typedef struct {
float x;
float y;
float width;
float height;
} resvg_rect;
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
/**
* @brief Creates an identity transform.
*/
resvg_transform resvg_transform_identity(void);
/**
* @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 `stderr`.
*/
void resvg_init_log(void);
/**
* @brief Creates a new #resvg_options object.
*
* Should be destroyed via #resvg_options_destroy.
*/
resvg_options *resvg_options_create(void);
/**
* @brief Sets a directory that will be used during relative paths resolving.
*
* Expected to be the same as the directory that contains the SVG file,
* but can be set to any.
*
* Must be UTF-8. Can be set to NULL.
*
* Default: NULL
*/
void resvg_options_set_resources_dir(resvg_options *opt, const char *path);
/**
* @brief Sets the target DPI.
*
* Impact units conversion.
*
* Default: 96
*/
void resvg_options_set_dpi(resvg_options *opt, float dpi);
/**
* @brief Provides the content of a stylesheet that will be used when resolving CSS attributes.
*
* Must be UTF-8. Can be set to NULL.
*
* Default: NULL
*/
void resvg_options_set_stylesheet(resvg_options *opt, const char *content);
/**
* @brief Sets the default font family.
*
* Will be used when no `font-family` attribute is set in the SVG.
*
* Must be UTF-8. NULL is not allowed.
*
* Default: Times New Roman
*/
void resvg_options_set_font_family(resvg_options *opt, const char *family);
/**
* @brief Sets the default font size.
*
* Will be used when no `font-size` attribute is set in the SVG.
*
* Default: 12
*/
void resvg_options_set_font_size(resvg_options *opt, float size);
/**
* @brief Sets the `serif` font family.
*
* Must be UTF-8. NULL is not allowed.
*
* Has no effect when the `text` feature is not enabled.
*
* Default: Times New Roman
*/
void resvg_options_set_serif_family(resvg_options *opt, const char *family);
/**
* @brief Sets the `sans-serif` font family.
*
* Must be UTF-8. NULL is not allowed.
*
* Has no effect when the `text` feature is not enabled.
*
* Default: Arial
*/
void resvg_options_set_sans_serif_family(resvg_options *opt, const char *family);
/**
* @brief Sets the `cursive` font family.
*
* Must be UTF-8. NULL is not allowed.
*
* Has no effect when the `text` feature is not enabled.
*
* Default: Comic Sans MS
*/
void resvg_options_set_cursive_family(resvg_options *opt, const char *family);
/**
* @brief Sets the `fantasy` font family.
*
* Must be UTF-8. NULL is not allowed.
*
* Has no effect when the `text` feature is not enabled.
*
* Default: Papyrus on macOS, Impact on other OS'es
*/
void resvg_options_set_fantasy_family(resvg_options *opt, const char *family);
/**
* @brief Sets the `monospace` font family.
*
* Must be UTF-8. NULL is not allowed.
*
* Has no effect when the `text` feature is not enabled.
*
* Default: Courier New
*/
void resvg_options_set_monospace_family(resvg_options *opt, const char *family);
/**
* @brief Sets a comma-separated list of languages.
*
* Will be used to resolve a `systemLanguage` conditional attribute.
*
* Example: en,en-US.
*
* Must be UTF-8. Can be NULL.
*
* Default: en
*/
void resvg_options_set_languages(resvg_options *opt, const char *languages);
/**
* @brief Sets the default shape rendering method.
*
* Will be used when an SVG element's `shape-rendering` property is set to `auto`.
*
* Default: `RESVG_SHAPE_RENDERING_GEOMETRIC_PRECISION`
*/
void resvg_options_set_shape_rendering_mode(resvg_options *opt, resvg_shape_rendering mode);
/**
* @brief Sets the default text rendering method.
*
* Will be used when an SVG element's `text-rendering` property is set to `auto`.
*
* Default: `RESVG_TEXT_RENDERING_OPTIMIZE_LEGIBILITY`
*/
void resvg_options_set_text_rendering_mode(resvg_options *opt, resvg_text_rendering mode);
/**
* @brief Sets the default image rendering method.
*
* Will be used when an SVG element's `image-rendering` property is set to `auto`.
*
* Default: `RESVG_IMAGE_RENDERING_OPTIMIZE_QUALITY`
*/
void resvg_options_set_image_rendering_mode(resvg_options *opt, resvg_image_rendering mode);
/**
* @brief Loads a font data into the internal fonts database.
*
* Prints a warning into the log when the data is not a valid TrueType font.
*
* Has no effect when the `text` feature is not enabled.
*/
void resvg_options_load_font_data(resvg_options *opt, const char *data, uintptr_t len);
/**
* @brief Loads a font file into the internal fonts database.
*
* Prints a warning into the log when the data is not a valid TrueType font.
*
* Has no effect when the `text` feature is not enabled.
*
* @return #resvg_error with RESVG_OK, RESVG_ERROR_NOT_AN_UTF8_STR or RESVG_ERROR_FILE_OPEN_FAILED
*/
int32_t resvg_options_load_font_file(resvg_options *opt, const char *file_path);
/**
* @brief Loads system fonts into the internal fonts database.
*
* This method is very IO intensive.
*
* This method should be executed only once per #resvg_options.
*
* The system scanning is not perfect, so some fonts may be omitted.
* Please send a bug report in this case.
*
* Prints warnings into the log.
*
* Has no effect when the `text` feature is not enabled.
*/
void resvg_options_load_system_fonts(resvg_options *opt);
/**
* @brief Destroys the #resvg_options.
*/
void resvg_options_destroy(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. Must not be NULL.
* @param tree Parsed render tree. Should be destroyed via #resvg_tree_destroy.
* @return #resvg_error
*/
int32_t 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. Must not be NULL.
* @param len Data length.
* @param opt Rendering options. Must not be NULL.
* @param tree Parsed render tree. Should be destroyed via #resvg_tree_destroy.
* @return #resvg_error
*/
int32_t resvg_parse_tree_from_data(const char *data,
uintptr_t len,
const resvg_options *opt,
resvg_render_tree **tree);
/**
* @brief Checks that tree has any nodes.
*
* @param tree Render tree.
* @return Returns `true` if tree has no nodes.
*/
bool resvg_is_image_empty(const resvg_render_tree *tree);
/**
* @brief Returns an image size.
*
* The size of an image that is required to render this SVG.
*
* Note that elements outside the viewbox will be clipped. This is by design.
* If you want to render the whole SVG content, use #resvg_get_image_bbox instead.
*
* @param tree Render tree.
* @return Image size.
*/
resvg_size resvg_get_image_size(const resvg_render_tree *tree);
/**
* @brief Returns an object bounding box.
*
* This bounding box does not include objects stroke and filter regions.
* This is what SVG calls "absolute object bonding box".
*
* If you're looking for a "complete" bounding box see #resvg_get_image_bbox
*
* @param tree Render tree.
* @param bbox Image's object bounding box.
* @return `false` if an image has no elements.
*/
bool resvg_get_object_bbox(const resvg_render_tree *tree, resvg_rect *bbox);
/**
* @brief Returns an image bounding box.
*
* This bounding box contains the maximum SVG dimensions.
* It's size can be bigger or smaller than #resvg_get_image_size
* Use it when you want to avoid clipping of elements that are outside the SVG viewbox.
*
* @param tree Render tree.
* @param bbox Image's bounding box.
* @return `false` if an image has no elements.
*/
bool resvg_get_image_bbox(const resvg_render_tree *tree, resvg_rect *bbox);
/**
* @brief Returns `true` if a renderable node with such an ID exists.
*
* @param tree Render tree.
* @param id Node's ID. UTF-8 string. Must not be NULL.
* @return `true` if a node exists.
* @return `false` if a node doesn't exist or ID isn't a UTF-8 string.
* @return `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. Must not be NULL.
* @param transform Node's transform.
* @return `true` if a node exists.
* @return `false` if a node doesn't exist or ID isn't a UTF-8 string.
* @return `false` if a node exists, but not renderable.
*/
bool resvg_get_node_transform(const resvg_render_tree *tree,
const char *id,
resvg_transform *transform);
/**
* @brief Returns node's bounding box in canvas coordinates by ID.
*
* @param tree Render tree.
* @param id Node's ID. Must not be NULL.
* @param bbox Node's bounding box.
* @return `false` if a node with such an ID does not exist
* @return `false` if ID isn't a UTF-8 string.
* @return `false` if ID is an empty string
*/
bool resvg_get_node_bbox(const resvg_render_tree *tree, const char *id, resvg_rect *bbox);
/**
* @brief Returns node's bounding box, including stroke, in canvas coordinates by ID.
*
* @param tree Render tree.
* @param id Node's ID. Must not be NULL.
* @param bbox Node's bounding box.
* @return `false` if a node with such an ID does not exist
* @return `false` if ID isn't a UTF-8 string.
* @return `false` if ID is an empty string
*/
bool resvg_get_node_stroke_bbox(const resvg_render_tree *tree, const char *id, resvg_rect *bbox);
/**
* @brief Destroys the #resvg_render_tree.
*/
void resvg_tree_destroy(resvg_render_tree *tree);
/**
* @brief Renders the #resvg_render_tree onto the pixmap.
*
* @param tree A render tree.
* @param transform A root SVG transform. Can be used to position SVG inside the `pixmap`.
* @param width Pixmap width.
* @param height Pixmap height.
* @param pixmap Pixmap data. Should have width*height*4 size and contain
* premultiplied RGBA8888 pixels.
*/
void resvg_render(const resvg_render_tree *tree,
resvg_transform transform,
uint32_t width,
uint32_t height,
char *pixmap);
/**
* @brief Renders a Node by ID onto the image.
*
* @param tree A render tree.
* @param id Node's ID. Must not be NULL.
* @param transform A root SVG transform. Can be used to position SVG inside the `pixmap`.
* @param width Pixmap width.
* @param height Pixmap height.
* @param pixmap Pixmap data. Should have width*height*4 size and contain
* premultiplied RGBA8888 pixels.
* @return `false` when `id` is not a non-empty UTF-8 string.
* @return `false` when the selected `id` is not present.
* @return `false` when an element has a zero bbox.
*/
bool resvg_render_node(const resvg_render_tree *tree,
const char *id,
resvg_transform transform,
uint32_t width,
uint32_t height,
char *pixmap);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
#endif /* RESVG_H */
================================================
FILE: crates/resvg/Cargo.toml
================================================
[package]
name = "resvg"
version = "0.47.0"
keywords = ["svg", "render", "raster"]
license.workspace = true
edition = "2024"
rust-version = "1.87.0"
description = "An SVG rendering library."
repository = "https://github.com/linebender/resvg"
exclude = ["tests"]
workspace = "../.."
[[bin]]
name = "resvg"
required-features = ["text", "system-fonts", "memmap-fonts"]
[dependencies]
gif = { version = "0.14.1", optional = true }
image-webp = { version = "0.2.4", optional = true }
log = "0.4"
pico-args = { version = "0.5", features = ["eq-separator"] }
rgb = "0.8"
svgtypes = "0.16.1"
tiny-skia = "0.12.0"
usvg = { path = "../usvg", version = "0.47.0", default-features = false }
zune-jpeg = { version = "0.5.8", optional = true }
[dev-dependencies]
once_cell = "1.21"
png = "0.18.0"
[features]
default = ["text", "system-fonts", "memmap-fonts", "raster-images"]
# Enables SVG Text support.
# Adds around 400KiB to your binary.
text = ["usvg/text"]
# Enables system fonts loading (only for `text`).
system-fonts = ["usvg/system-fonts"]
# Enables font files memmaping for faster loading (only for `text`).
memmap-fonts = ["usvg/memmap-fonts"]
# Enables decoding and rendering of raster images.
# When disabled, `image` elements with SVG data will still be rendered.
# Adds around 200KiB to your binary.
raster-images = ["gif", "image-webp", "dep:zune-jpeg"]
================================================
FILE: crates/resvg/LICENSE-APACHE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
================================================
FILE: crates/resvg/LICENSE-MIT
================================================
Copyright 2017 the Resvg Authors
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.
================================================
FILE: crates/resvg/examples/custom_href_resolver.rs
================================================
// Copyright 2022 the Resvg Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
fn main() {
let mut opt = usvg::Options::default();
let ferris_image = std::sync::Arc::new(std::fs::read("./examples/ferris.png").unwrap());
// We know that our SVG won't have DataUrl hrefs, just return None for such case.
let resolve_data = Box::new(|_: &str, _: std::sync::Arc>, _: &usvg::Options| None);
// Here we handle xlink:href attribute as string,
// let's use already loaded Ferris image to match that string.
let resolve_string = Box::new(move |href: &str, _: &usvg::Options| match href {
"ferris_image" => Some(usvg::ImageKind::PNG(ferris_image.clone())),
_ => None,
});
// Assign new ImageHrefResolver option using our closures.
opt.image_href_resolver = usvg::ImageHrefResolver {
resolve_data,
resolve_string,
};
let svg_data = std::fs::read("./examples/custom_href_resolver.svg").unwrap();
let tree = usvg::Tree::from_data(&svg_data, &opt).unwrap();
let pixmap_size = tree.size().to_int_size();
let mut pixmap = tiny_skia::Pixmap::new(pixmap_size.width(), pixmap_size.height()).unwrap();
resvg::render(&tree, tiny_skia::Transform::default(), &mut pixmap.as_mut());
pixmap.save_png("custom_href_resolver.png").unwrap();
}
================================================
FILE: crates/resvg/examples/draw_bboxes.rs
================================================
// Copyright 2017 the Resvg Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
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 = usvg::Options {
// Get file's absolute directory.
resources_dir: std::fs::canonicalize(&args[1])
.ok()
.and_then(|p| p.parent().map(|p| p.to_path_buf())),
..usvg::Options::default()
};
opt.fontdb_mut().load_system_fonts();
let svg_data = std::fs::read(&args[1]).unwrap();
let tree = usvg::Tree::from_data(&svg_data, &opt).unwrap();
let mut bboxes = Vec::new();
let mut stroke_bboxes = Vec::new();
collect_bboxes(tree.root(), &mut bboxes, &mut stroke_bboxes);
let pixmap_size = tree.size().to_int_size().scale_by(zoom).unwrap();
let mut pixmap = tiny_skia::Pixmap::new(pixmap_size.width(), pixmap_size.height()).unwrap();
let render_ts = tiny_skia::Transform::from_scale(zoom, zoom);
resvg::render(&tree, render_ts, &mut pixmap.as_mut());
let stroke = tiny_skia::Stroke {
width: 1.0 / zoom, // prevent stroke scaling as well
..tiny_skia::Stroke::default()
};
let mut paint1 = tiny_skia::Paint::default();
paint1.set_color_rgba8(255, 0, 0, 127);
let mut paint2 = tiny_skia::Paint::default();
paint2.set_color_rgba8(0, 200, 0, 127);
for bbox in bboxes {
let path = tiny_skia::PathBuilder::from_rect(bbox);
pixmap.stroke_path(&path, &paint1, &stroke, render_ts, None);
}
for bbox in stroke_bboxes {
let path = tiny_skia::PathBuilder::from_rect(bbox);
pixmap.stroke_path(&path, &paint2, &stroke, render_ts, None);
}
pixmap.save_png(&args[2]).unwrap();
}
fn collect_bboxes(
parent: &usvg::Group,
bboxes: &mut Vec,
stroke_bboxes: &mut Vec,
) {
for node in parent.children() {
if let usvg::Node::Group(group) = node {
collect_bboxes(group, bboxes, stroke_bboxes);
}
let bbox = node.abs_bounding_box();
bboxes.push(bbox);
let stroke_bbox = node.abs_stroke_bounding_box();
if bbox != stroke_bbox {
stroke_bboxes.push(stroke_bbox);
}
}
}
================================================
FILE: crates/resvg/examples/minimal.rs
================================================
// Copyright 2017 the Resvg Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
fn main() {
let args: Vec = std::env::args().collect();
if args.len() != 3 {
println!("Usage:\n\tminimal ");
return;
}
let tree = {
let mut opt = usvg::Options {
// Get file's absolute directory.
resources_dir: std::fs::canonicalize(&args[1])
.ok()
.and_then(|p| p.parent().map(|p| p.to_path_buf())),
..usvg::Options::default()
};
opt.fontdb_mut().load_system_fonts();
let svg_data = std::fs::read(&args[1]).unwrap();
usvg::Tree::from_data(&svg_data, &opt).unwrap()
};
let pixmap_size = tree.size().to_int_size();
let mut pixmap = tiny_skia::Pixmap::new(pixmap_size.width(), pixmap_size.height()).unwrap();
resvg::render(&tree, tiny_skia::Transform::default(), &mut pixmap.as_mut());
pixmap.save_png(&args[2]).unwrap();
}
================================================
FILE: crates/resvg/src/clip.rs
================================================
// Copyright 2019 the Resvg Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
use crate::render::Context;
pub fn apply(
clip: &usvg::ClipPath,
transform: tiny_skia::Transform,
pixmap: &mut tiny_skia::Pixmap,
) {
let mut clip_pixmap = tiny_skia::Pixmap::new(pixmap.width(), pixmap.height()).unwrap();
clip_pixmap.fill(tiny_skia::Color::BLACK);
draw_children(
clip.root(),
tiny_skia::BlendMode::Clear,
transform.pre_concat(clip.transform()),
&mut clip_pixmap.as_mut(),
);
if let Some(clip) = clip.clip_path() {
apply(clip, transform, pixmap);
}
let mut mask = tiny_skia::Mask::from_pixmap(clip_pixmap.as_ref(), tiny_skia::MaskType::Alpha);
mask.invert();
pixmap.apply_mask(&mask);
}
fn draw_children(
parent: &usvg::Group,
mode: tiny_skia::BlendMode,
transform: tiny_skia::Transform,
pixmap: &mut tiny_skia::PixmapMut,
) {
for child in parent.children() {
match child {
usvg::Node::Path(path) => {
if !path.is_visible() {
continue;
}
// We could use any values here. They will not be used anyway.
let ctx = Context {
max_bbox: tiny_skia::IntRect::from_xywh(0, 0, 1, 1).unwrap(),
};
crate::path::fill_path(path, mode, &ctx, transform, pixmap);
}
usvg::Node::Text(text) => {
draw_children(text.flattened(), mode, transform, pixmap);
}
usvg::Node::Group(group) => {
let transform = transform.pre_concat(group.transform());
if let Some(clip) = group.clip_path() {
// 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`.
clip_group(group, clip, transform, pixmap);
} else {
draw_children(group, mode, transform, pixmap);
}
}
_ => {}
}
}
}
fn clip_group(
children: &usvg::Group,
clip: &usvg::ClipPath,
transform: tiny_skia::Transform,
pixmap: &mut tiny_skia::PixmapMut,
) -> Option<()> {
let mut clip_pixmap = tiny_skia::Pixmap::new(pixmap.width(), pixmap.height()).unwrap();
draw_children(
children,
tiny_skia::BlendMode::SourceOver,
transform,
&mut clip_pixmap.as_mut(),
);
apply(clip, transform, &mut clip_pixmap);
let mut paint = tiny_skia::PixmapPaint::default();
paint.blend_mode = tiny_skia::BlendMode::Xor;
pixmap.draw_pixmap(
0,
0,
clip_pixmap.as_ref(),
&paint,
tiny_skia::Transform::identity(),
None,
);
Some(())
}
================================================
FILE: crates/resvg/src/filter/box_blur.rs
================================================
// Copyright 2020 the Resvg Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
// Based on https://github.com/fschutt/fastblur
#![allow(clippy::needless_range_loop)]
use super::ImageRefMut;
use rgb::RGBA8;
use std::cmp;
const STEPS: usize = 5;
/// Applies a box blur.
///
/// Input image pixels should have a **premultiplied alpha**.
///
/// A negative or zero `sigma_x`/`sigma_y` will disable the blur along that axis.
///
/// # Allocations
///
/// This method will allocate a copy of the `src` image as a back buffer.
pub fn apply(sigma_x: f64, sigma_y: f64, mut src: ImageRefMut) {
let boxes_horz = create_box_gauss(sigma_x as f32);
let boxes_vert = create_box_gauss(sigma_y as f32);
let mut backbuf = src.data.to_vec();
let mut backbuf = ImageRefMut::new(src.width, src.height, &mut backbuf);
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;
box_blur_impl(radius_horz, radius_vert, &mut backbuf, &mut src);
}
}
#[inline(never)]
fn create_box_gauss(sigma: f32) -> [i32; STEPS] {
if sigma > 0.0 {
let n_float = STEPS 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 = [0; STEPS];
for i in 0..STEPS {
if i < m {
sizes[i] = wl;
} else {
sizes[i] = wu;
}
}
sizes
} else {
[1; STEPS]
}
}
#[inline]
fn box_blur_impl(
blur_radius_horz: usize,
blur_radius_vert: usize,
backbuf: &mut ImageRefMut,
frontbuf: &mut ImageRefMut,
) {
box_blur_vert(blur_radius_vert, frontbuf, backbuf);
box_blur_horz(blur_radius_horz, backbuf, frontbuf);
}
#[inline]
fn box_blur_vert(blur_radius: usize, backbuf: &ImageRefMut, frontbuf: &mut ImageRefMut) {
if blur_radius == 0 {
frontbuf.data.copy_from_slice(backbuf.data);
return;
}
let width = backbuf.width as usize;
let height = backbuf.height as usize;
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::default();
let lv = RGBA8::default();
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.data[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.data[i] }
};
for j in 0..cmp::min(blur_radius, height) {
let bb = backbuf.data[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.data[ti] = RGBA8 {
r: round(val_r as f32 * iarr) as u8,
g: round(val_g as f32 * iarr) as u8,
b: round(val_b as f32 * iarr) as u8,
a: round(val_a as f32 * iarr) as u8,
};
ti += width;
}
if height <= blur_radius {
// otherwise `(height - blur_radius)` will underflow
continue;
}
for _ in (blur_radius + 1)..(height - blur_radius) {
let bb1 = backbuf.data[ri];
ri += width;
let bb2 = backbuf.data[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.data[ti] = RGBA8 {
r: round(val_r as f32 * iarr) as u8,
g: round(val_g as f32 * iarr) as u8,
b: round(val_b as f32 * iarr) as u8,
a: round(val_a as f32 * iarr) as u8,
};
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.data[ti] = RGBA8 {
r: round(val_r as f32 * iarr) as u8,
g: round(val_g as f32 * iarr) as u8,
b: round(val_b as f32 * iarr) as u8,
a: round(val_a as f32 * iarr) as u8,
};
ti += width;
}
}
}
#[inline]
fn box_blur_horz(blur_radius: usize, backbuf: &ImageRefMut, frontbuf: &mut ImageRefMut) {
if blur_radius == 0 {
frontbuf.data.copy_from_slice(backbuf.data);
return;
}
let width = backbuf.width as usize;
let height = backbuf.height as usize;
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::default();
let lv = RGBA8::default();
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.data[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.data[i] }
};
for j in 0..cmp::min(blur_radius, width) {
let bb = backbuf.data[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.data[ti] = RGBA8 {
r: round(val_r as f32 * iarr) as u8,
g: round(val_g as f32 * iarr) as u8,
b: round(val_b as f32 * iarr) as u8,
a: round(val_a as f32 * iarr) as u8,
};
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.data[ri];
ri += 1;
let bb2 = backbuf.data[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.data[ti] = RGBA8 {
r: round(val_r as f32 * iarr) as u8,
g: round(val_g as f32 * iarr) as u8,
b: round(val_b as f32 * iarr) as u8,
a: round(val_a as f32 * iarr) as u8,
};
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.data[ti] = RGBA8 {
r: round(val_r as f32 * iarr) as u8,
g: round(val_g as f32 * iarr) as u8,
b: round(val_b as f32 * iarr) as u8,
a: round(val_a as f32 * iarr) as u8,
};
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
}
================================================
FILE: crates/resvg/src/filter/color_matrix.rs
================================================
// Copyright 2020 the Resvg Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
use super::{ImageRefMut, f32_bound};
use rgb::RGBA8;
use usvg::filter::ColorMatrixKind as ColorMatrix;
/// Applies a color matrix filter.
///
/// Input image pixels should have an **unpremultiplied alpha**.
pub fn apply(matrix: &ColorMatrix, src: ImageRefMut) {
match matrix {
ColorMatrix::Matrix(m) => {
for pixel in src.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);
}
}
ColorMatrix::Saturate(v) => {
let v = v.get().max(0.0);
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 src.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);
}
}
ColorMatrix::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 src.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);
}
}
ColorMatrix::LuminanceToAlpha => {
for pixel in src.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);
}
}
}
}
#[inline]
fn to_normalized_components(pixel: RGBA8) -> (f32, f32, f32, f32) {
(
pixel.r as f32 / 255.0,
pixel.g as f32 / 255.0,
pixel.b as f32 / 255.0,
pixel.a as f32 / 255.0,
)
}
#[inline]
fn from_normalized(c: f32) -> u8 {
(f32_bound(0.0, c, 1.0) * 255.0) as u8
}
================================================
FILE: crates/resvg/src/filter/component_transfer.rs
================================================
// Copyright 2020 the Resvg Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
use super::{ImageRefMut, f32_bound};
use usvg::filter::{ComponentTransfer, TransferFunction};
/// Applies component transfer functions for each `src` image channel.
///
/// Input image pixels should have an **unpremultiplied alpha**.
pub fn apply(fe: &ComponentTransfer, src: ImageRefMut) {
for pixel in src.data {
if !is_dummy(fe.func_r()) {
pixel.r = transfer(fe.func_r(), pixel.r);
}
if !is_dummy(fe.func_b()) {
pixel.b = transfer(fe.func_b(), pixel.b);
}
if !is_dummy(fe.func_g()) {
pixel.g = transfer(fe.func_g(), pixel.g);
}
if !is_dummy(fe.func_a()) {
pixel.a = transfer(fe.func_a(), pixel.a);
}
}
}
fn is_dummy(func: &TransferFunction) -> bool {
match func {
TransferFunction::Identity => true,
TransferFunction::Table(values) => values.is_empty(),
TransferFunction::Discrete(values) => values.is_empty(),
TransferFunction::Linear { .. } => false,
TransferFunction::Gamma { .. } => false,
}
}
fn transfer(func: &TransferFunction, c: u8) -> u8 {
let c = c as f32 / 255.0;
let c = match func {
TransferFunction::Identity => c,
TransferFunction::Table(values) => {
let n = values.len() - 1;
let k = (c * (n as f32)).floor() as usize;
let k = std::cmp::min(k, n);
if k == n {
values[k]
} else {
let vk = values[k];
let vk1 = values[k + 1];
let k = k as f32;
let n = n as f32;
vk + (c - k / n) * n * (vk1 - vk)
}
}
TransferFunction::Discrete(values) => {
let n = values.len();
let k = (c * (n as f32)).floor() as usize;
values[std::cmp::min(k, n - 1)]
}
TransferFunction::Linear { slope, intercept } => slope * c + intercept,
TransferFunction::Gamma {
amplitude,
exponent,
offset,
} => amplitude * c.powf(*exponent) + offset,
};
(f32_bound(0.0, c, 1.0) * 255.0) as u8
}
================================================
FILE: crates/resvg/src/filter/composite.rs
================================================
// Copyright 2020 the Resvg Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
use super::{ImageRef, ImageRefMut, f32_bound};
use rgb::RGBA8;
use usvg::ApproxZeroUlps;
/// Performs an arithmetic composition.
///
/// - `src1` and `src2` image pixels should have a **premultiplied alpha**.
/// - `dest` image pixels will have a **premultiplied alpha**.
///
/// # Panics
///
/// When `src1`, `src2` and `dest` have different sizes.
pub fn arithmetic(
k1: f32,
k2: f32,
k3: f32,
k4: f32,
src1: ImageRef,
src2: ImageRef,
dest: ImageRefMut,
) {
assert!(src1.width == src2.width && src1.width == dest.width);
assert!(src1.height == src2.height && src1.height == dest.height);
let calc = |i1, i2, max| {
let i1 = i1 as f32 / 255.0;
let i2 = i2 as f32 / 255.0;
let result = k1 * i1 * i2 + k2 * i1 + k3 * i2 + k4;
f32_bound(0.0, result, max)
};
let mut i = 0;
for (c1, c2) in src1.data.iter().zip(src2.data.iter()) {
let a = calc(c1.a, c2.a, 1.0);
if a.approx_zero_ulps(4) {
i += 1;
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;
dest.data[i] = RGBA8 { r, g, b, a };
i += 1;
}
}
================================================
FILE: crates/resvg/src/filter/convolve_matrix.rs
================================================
// Copyright 2020 the Resvg Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
use super::{ImageRefMut, f32_bound};
use rgb::RGBA8;
use usvg::filter::{ConvolveMatrix, EdgeMode};
/// Applies a convolve matrix.
///
/// Input image pixels should have a **premultiplied alpha** when `preserve_alpha=false`.
///
/// # Allocations
///
/// This method will allocate a copy of the `src` image as a back buffer.
pub fn apply(matrix: &ConvolveMatrix, src: ImageRefMut) {
fn bound(min: i32, val: i32, max: i32) -> i32 {
core::cmp::max(min, core::cmp::min(max, val))
}
let width_max = src.width as i32 - 1;
let height_max = src.height as i32 - 1;
let mut buf = vec![RGBA8::default(); src.data.len()];
let mut buf = ImageRefMut::new(src.width, src.height, &mut buf);
let mut x = 0;
let mut y = 0;
for in_p in src.data.iter() {
let mut new_r = 0.0;
let mut new_g = 0.0;
let mut new_b = 0.0;
let mut new_a = 0.0;
for oy in 0..matrix.matrix().rows() {
for ox in 0..matrix.matrix().columns() {
let mut tx = x as i32 - matrix.matrix().target_x() as i32 + ox as i32;
let mut ty = y as i32 - matrix.matrix().target_y() as i32 + oy as i32;
match matrix.edge_mode() {
EdgeMode::None => {
if tx < 0 || tx > width_max || ty < 0 || ty > height_max {
continue;
}
}
EdgeMode::Duplicate => {
tx = bound(0, tx, width_max);
ty = bound(0, ty, height_max);
}
EdgeMode::Wrap => {
while tx < 0 {
tx += src.width as i32;
}
tx %= src.width as i32;
while ty < 0 {
ty += src.height as i32;
}
ty %= src.height as i32;
}
}
let k = matrix.matrix().get(
matrix.matrix().columns() - ox - 1,
matrix.matrix().rows() - oy - 1,
);
let p = src.pixel_at(tx as u32, ty as u32);
new_r += (p.r as f32) / 255.0 * k;
new_g += (p.g as f32) / 255.0 * k;
new_b += (p.b as f32) / 255.0 * k;
if !matrix.preserve_alpha() {
new_a += (p.a as f32) / 255.0 * k;
}
}
}
if matrix.preserve_alpha() {
new_a = in_p.a as f32 / 255.0;
} else {
new_a = new_a / matrix.divisor().get() + matrix.bias();
}
let bounded_new_a = f32_bound(0.0, new_a, 1.0);
let calc = |x| {
let x = x / matrix.divisor().get() + matrix.bias() * new_a;
let x = if matrix.preserve_alpha() {
f32_bound(0.0, x, 1.0) * bounded_new_a
} else {
f32_bound(0.0, x, bounded_new_a)
};
(x * 255.0 + 0.5) as u8
};
let out_p = buf.pixel_at_mut(x, y);
out_p.r = calc(new_r);
out_p.g = calc(new_g);
out_p.b = calc(new_b);
out_p.a = (bounded_new_a * 255.0 + 0.5) as u8;
x += 1;
if x == src.width {
x = 0;
y += 1;
}
}
// Do not use `mem::swap` because `data` referenced via FFI.
src.data.copy_from_slice(buf.data);
}
================================================
FILE: crates/resvg/src/filter/displacement_map.rs
================================================
// Copyright 2020 the Resvg Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
use super::{ImageRef, ImageRefMut};
use usvg::filter::{ColorChannel, DisplacementMap};
/// Applies a displacement map.
///
/// - `map` pixels should have a **unpremultiplied alpha**.
/// - `src` pixels can have any alpha method.
///
/// `sx` and `sy` indicate canvas scale.
///
/// # Panics
///
/// When `src`, `map` and `dest` have different sizes.
pub fn apply(
fe: &DisplacementMap,
sx: f32,
sy: f32,
src: ImageRef,
map: ImageRef,
dest: ImageRefMut,
) {
assert!(src.width == map.width && src.width == dest.width);
assert!(src.height == map.height && src.height == dest.height);
let w = src.width as i32;
let h = src.height as i32;
let mut x: u32 = 0;
let mut y: u32 = 0;
for pixel in map.data.iter() {
let calc_offset = |channel| {
let c = match channel {
ColorChannel::B => pixel.b,
ColorChannel::G => pixel.g,
ColorChannel::R => pixel.r,
ColorChannel::A => pixel.a,
};
c as f32 / 255.0 - 0.5
};
let dx = calc_offset(fe.x_channel_selector());
let dy = calc_offset(fe.y_channel_selector());
let ox = (x as f32 + dx * sx * fe.scale()).round() as i32;
let oy = (y as f32 + dy * sy * fe.scale()).round() as i32;
// TODO: we should use some kind of anti-aliasing when offset is on a pixel border
if x < w as u32 && y < h as u32 && ox >= 0 && ox < w && oy >= 0 && oy < h {
let idx = (oy * w + ox) as usize;
let idx1 = (y * w as u32 + x) as usize;
dest.data[idx1] = src.data[idx];
}
x += 1;
if x == src.width {
x = 0;
y += 1;
}
}
}
================================================
FILE: crates/resvg/src/filter/iir_blur.rs
================================================
// Copyright 2020 the Resvg Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
// 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.
// TODO: Blurs right and bottom sides twice for some reason.
use super::ImageRefMut;
use rgb::ComponentSlice;
struct BlurData {
width: usize,
height: usize,
sigma_x: f64,
sigma_y: f64,
steps: usize,
}
/// Applies an IIR blur.
///
/// Input image pixels should have a **premultiplied alpha**.
///
/// A negative or zero `sigma_x`/`sigma_y` will disable the blur along that axis.
///
/// # Allocations
///
/// This method will allocate a 2x `src` buffer.
pub fn apply(sigma_x: f64, sigma_y: f64, src: ImageRefMut) {
let buf_size = (src.width * src.height) as usize;
let mut buf = vec![0.0; buf_size];
let buf = &mut buf;
let d = BlurData {
width: src.width as usize,
height: src.height as usize,
sigma_x,
sigma_y,
steps: 4,
};
let data = src.data.as_mut_slice();
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 [f64]) {
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 [f64]) {
// Filter horizontally along each row.
let (lambda_x, dnu_x) = if d.sigma_x > 0.0 {
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;
// Filter rightwards.
for x in 1..d.width {
buf[idx + x] += dnu * buf[idx + x - 1];
}
let mut x = d.width - 1;
// 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) = gen_coefficients(d.sigma_y, d.steps);
for x in 0..d.width {
for _ in 0..d.steps {
let idx = x;
// 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;
// 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);
(lambda, dnu)
}
================================================
FILE: crates/resvg/src/filter/lighting.rs
================================================
// Copyright 2020 the Resvg Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
use super::{ImageRef, ImageRefMut, f32_bound};
use rgb::RGBA8;
use usvg::filter::{DiffuseLighting, LightSource, SpecularLighting};
use usvg::{ApproxEqUlps, ApproxZeroUlps, Color};
const FACTOR_1_2: f32 = 1.0 / 2.0;
const FACTOR_1_3: f32 = 1.0 / 3.0;
const FACTOR_1_4: f32 = 1.0 / 4.0;
const FACTOR_2_3: f32 = 2.0 / 3.0;
#[derive(Clone, Copy, Debug)]
struct Vector2 {
x: f32,
y: f32,
}
impl Vector2 {
#[inline]
fn new(x: f32, y: f32) -> Self {
Vector2 { x, y }
}
#[inline]
fn approx_zero(&self) -> bool {
self.x.approx_zero_ulps(4) && self.y.approx_zero_ulps(4)
}
}
impl core::ops::Mul for Vector2 {
type Output = Self;
#[inline]
fn mul(self, c: f32) -> Self::Output {
Vector2 {
x: self.x * c,
y: self.y * c,
}
}
}
#[derive(Clone, Copy, Debug)]
struct Vector3 {
x: f32,
y: f32,
z: f32,
}
impl Vector3 {
#[inline]
fn new(x: f32, y: f32, z: f32) -> Self {
Vector3 { x, y, z }
}
#[inline]
fn dot(&self, other: &Self) -> f32 {
self.x * other.x + self.y * other.y + self.z * other.z
}
#[inline]
fn length(&self) -> f32 {
(self.x * self.x + self.y * self.y + self.z * self.z).sqrt()
}
#[inline]
fn normalized(&self) -> Option {
let length = self.length();
if !length.approx_zero_ulps(4) {
Some(Vector3 {
x: self.x / length,
y: self.y / length,
z: self.z / length,
})
} else {
None
}
}
}
impl core::ops::Add for Vector3 {
type Output = Self;
#[inline]
fn add(self, rhs: Vector3) -> Self::Output {
Vector3 {
x: self.x + rhs.x,
y: self.y + rhs.y,
z: self.z + rhs.z,
}
}
}
impl core::ops::Sub for Vector3 {
type Output = Self;
#[inline]
fn sub(self, rhs: Vector3) -> Self::Output {
Vector3 {
x: self.x - rhs.x,
y: self.y - rhs.y,
z: self.z - rhs.z,
}
}
}
#[derive(Clone, Copy, Debug)]
struct Normal {
factor: Vector2,
normal: Vector2,
}
impl Normal {
#[inline]
fn new(factor_x: f32, factor_y: f32, nx: i16, ny: i16) -> Self {
Normal {
factor: Vector2::new(factor_x, factor_y),
normal: Vector2::new(-nx as f32, -ny as f32),
}
}
}
/// Renders a diffuse lighting.
///
/// - `src` pixels can have any alpha method, since only the alpha channel is used.
/// - `dest` will have an **unpremultiplied alpha**.
///
/// Does nothing when `src` is less than 3x3.
///
/// # Panics
///
/// - When `src` and `dest` have different sizes.
pub fn diffuse_lighting(
fe: &DiffuseLighting,
light_source: LightSource,
src: ImageRef,
dest: ImageRefMut,
) {
assert!(src.width == dest.width && src.height == dest.height);
let light_factor = |normal: Normal, light_vector: Vector3| {
let k = if normal.normal.approx_zero() {
light_vector.z
} else {
let mut n = normal.normal * (fe.surface_scale() / 255.0);
n.x *= normal.factor.x;
n.y *= normal.factor.y;
let normal = Vector3::new(n.x, n.y, 1.0);
normal.dot(&light_vector) / normal.length()
};
fe.diffuse_constant() * k
};
apply(
light_source,
fe.surface_scale(),
fe.lighting_color(),
&light_factor,
calc_diffuse_alpha,
src,
dest,
);
}
/// Renders a specular lighting.
///
/// - `src` pixels can have any alpha method, since only the alpha channel is used.
/// - `dest` will have a **premultiplied alpha**.
///
/// Does nothing when `src` is less than 3x3.
///
/// # Panics
///
/// - When `src` and `dest` have different sizes.
pub fn specular_lighting(
fe: &SpecularLighting,
light_source: LightSource,
src: ImageRef,
dest: ImageRefMut,
) {
assert!(src.width == dest.width && src.height == dest.height);
let light_factor = |normal: Normal, light_vector: Vector3| {
let h = light_vector + Vector3::new(0.0, 0.0, 1.0);
let h_length = h.length();
if h_length.approx_zero_ulps(4) {
return 0.0;
}
let k = if normal.normal.approx_zero() {
let n_dot_h = h.z / h_length;
if fe.specular_exponent().approx_eq_ulps(&1.0, 4) {
n_dot_h
} else {
n_dot_h.powf(fe.specular_exponent())
}
} else {
let mut n = normal.normal * (fe.surface_scale() / 255.0);
n.x *= normal.factor.x;
n.y *= normal.factor.y;
let normal = Vector3::new(n.x, n.y, 1.0);
let n_dot_h = normal.dot(&h) / normal.length() / h_length;
if fe.specular_exponent().approx_eq_ulps(&1.0, 4) {
n_dot_h
} else {
n_dot_h.powf(fe.specular_exponent())
}
};
fe.specular_constant() * k
};
apply(
light_source,
fe.surface_scale(),
fe.lighting_color(),
&light_factor,
calc_specular_alpha,
src,
dest,
);
}
fn apply(
light_source: LightSource,
surface_scale: f32,
lighting_color: Color,
light_factor: &dyn Fn(Normal, Vector3) -> f32,
calc_alpha: fn(u8, u8, u8) -> u8,
src: ImageRef,
mut dest: ImageRefMut,
) {
if src.width < 3 || src.height < 3 {
return;
}
let width = src.width;
let height = src.height;
// `feDistantLight` has a fixed vector, so calculate it beforehand.
let mut light_vector = match light_source {
LightSource::DistantLight(light) => {
let azimuth = light.azimuth.to_radians();
let elevation = light.elevation.to_radians();
Vector3::new(
azimuth.cos() * elevation.cos(),
azimuth.sin() * elevation.cos(),
elevation.sin(),
)
}
_ => Vector3::new(1.0, 1.0, 1.0),
};
let mut calc = |nx, ny, normal: Normal| {
match light_source {
LightSource::DistantLight(_) => {}
LightSource::PointLight(ref light) => {
let nz = src.alpha_at(nx, ny) as f32 / 255.0 * surface_scale;
let origin = Vector3::new(light.x, light.y, light.z);
let v = origin - Vector3::new(nx as f32, ny as f32, nz);
light_vector = v.normalized().unwrap_or(v);
}
LightSource::SpotLight(ref light) => {
let nz = src.alpha_at(nx, ny) as f32 / 255.0 * surface_scale;
let origin = Vector3::new(light.x, light.y, light.z);
let v = origin - Vector3::new(nx as f32, ny as f32, nz);
light_vector = v.normalized().unwrap_or(v);
}
}
let light_color = light_color(&light_source, lighting_color, light_vector);
let factor = light_factor(normal, light_vector);
let compute = |x| (f32_bound(0.0, x as f32 * factor, 255.0) + 0.5) as u8;
let r = compute(light_color.red);
let g = compute(light_color.green);
let b = compute(light_color.blue);
let a = calc_alpha(r, g, b);
*dest.pixel_at_mut(nx, ny) = RGBA8 { b, g, r, a };
};
calc(0, 0, top_left_normal(src));
calc(width - 1, 0, top_right_normal(src));
calc(0, height - 1, bottom_left_normal(src));
calc(width - 1, height - 1, bottom_right_normal(src));
for x in 1..width - 1 {
calc(x, 0, top_row_normal(src, x));
calc(x, height - 1, bottom_row_normal(src, x));
}
for y in 1..height - 1 {
calc(0, y, left_column_normal(src, y));
calc(width - 1, y, right_column_normal(src, y));
}
for y in 1..height - 1 {
for x in 1..width - 1 {
calc(x, y, interior_normal(src, x, y));
}
}
}
fn light_color(light: &LightSource, lighting_color: Color, light_vector: Vector3) -> Color {
match *light {
LightSource::DistantLight(_) | LightSource::PointLight(_) => lighting_color,
LightSource::SpotLight(ref light) => {
let origin = Vector3::new(light.x, light.y, light.z);
let direction = Vector3::new(light.points_at_x, light.points_at_y, light.points_at_z);
let direction = direction - origin;
let direction = direction.normalized().unwrap_or(direction);
let minus_l_dot_s = -light_vector.dot(&direction);
if minus_l_dot_s <= 0.0 {
return Color::black();
}
if let Some(limiting_cone_angle) = light.limiting_cone_angle {
if minus_l_dot_s < limiting_cone_angle.to_radians().cos() {
return Color::black();
}
}
let factor = minus_l_dot_s.powf(light.specular_exponent.get());
let compute = |x| (f32_bound(0.0, x as f32 * factor, 255.0) + 0.5) as u8;
Color::new_rgb(
compute(lighting_color.red),
compute(lighting_color.green),
compute(lighting_color.blue),
)
}
}
}
fn top_left_normal(img: ImageRef) -> Normal {
let center = img.alpha_at(0, 0);
let right = img.alpha_at(1, 0);
let bottom = img.alpha_at(0, 1);
let bottom_right = img.alpha_at(1, 1);
Normal::new(
FACTOR_2_3,
FACTOR_2_3,
-2 * center + 2 * right - bottom + bottom_right,
-2 * center - right + 2 * bottom + bottom_right,
)
}
fn top_right_normal(img: ImageRef) -> Normal {
let left = img.alpha_at(img.width - 2, 0);
let center = img.alpha_at(img.width - 1, 0);
let bottom_left = img.alpha_at(img.width - 2, 1);
let bottom = img.alpha_at(img.width - 1, 1);
Normal::new(
FACTOR_2_3,
FACTOR_2_3,
-2 * left + 2 * center - bottom_left + bottom,
-left - 2 * center + bottom_left + 2 * bottom,
)
}
fn bottom_left_normal(img: ImageRef) -> Normal {
let top = img.alpha_at(0, img.height - 2);
let top_right = img.alpha_at(1, img.height - 2);
let center = img.alpha_at(0, img.height - 1);
let right = img.alpha_at(1, img.height - 1);
Normal::new(
FACTOR_2_3,
FACTOR_2_3,
-top + top_right - 2 * center + 2 * right,
-2 * top - top_right + 2 * center + right,
)
}
fn bottom_right_normal(img: ImageRef) -> Normal {
let top_left = img.alpha_at(img.width - 2, img.height - 2);
let top = img.alpha_at(img.width - 1, img.height - 2);
let left = img.alpha_at(img.width - 2, img.height - 1);
let center = img.alpha_at(img.width - 1, img.height - 1);
Normal::new(
FACTOR_2_3,
FACTOR_2_3,
-top_left + top - 2 * left + 2 * center,
-top_left - 2 * top + left + 2 * center,
)
}
fn top_row_normal(img: ImageRef, x: u32) -> Normal {
let left = img.alpha_at(x - 1, 0);
let center = img.alpha_at(x, 0);
let right = img.alpha_at(x + 1, 0);
let bottom_left = img.alpha_at(x - 1, 1);
let bottom = img.alpha_at(x, 1);
let bottom_right = img.alpha_at(x + 1, 1);
Normal::new(
FACTOR_1_3,
FACTOR_1_2,
-2 * left + 2 * right - bottom_left + bottom_right,
-left - 2 * center - right + bottom_left + 2 * bottom + bottom_right,
)
}
fn bottom_row_normal(img: ImageRef, x: u32) -> Normal {
let top_left = img.alpha_at(x - 1, img.height - 2);
let top = img.alpha_at(x, img.height - 2);
let top_right = img.alpha_at(x + 1, img.height - 2);
let left = img.alpha_at(x - 1, img.height - 1);
let center = img.alpha_at(x, img.height - 1);
let right = img.alpha_at(x + 1, img.height - 1);
Normal::new(
FACTOR_1_3,
FACTOR_1_2,
-top_left + top_right - 2 * left + 2 * right,
-top_left - 2 * top - top_right + left + 2 * center + right,
)
}
fn left_column_normal(img: ImageRef, y: u32) -> Normal {
let top = img.alpha_at(0, y - 1);
let top_right = img.alpha_at(1, y - 1);
let center = img.alpha_at(0, y);
let right = img.alpha_at(1, y);
let bottom = img.alpha_at(0, y + 1);
let bottom_right = img.alpha_at(1, y + 1);
Normal::new(
FACTOR_1_2,
FACTOR_1_3,
-top + top_right - 2 * center + 2 * right - bottom + bottom_right,
-2 * top - top_right + 2 * bottom + bottom_right,
)
}
fn right_column_normal(img: ImageRef, y: u32) -> Normal {
let top_left = img.alpha_at(img.width - 2, y - 1);
let top = img.alpha_at(img.width - 1, y - 1);
let left = img.alpha_at(img.width - 2, y);
let center = img.alpha_at(img.width - 1, y);
let bottom_left = img.alpha_at(img.width - 2, y + 1);
let bottom = img.alpha_at(img.width - 1, y + 1);
Normal::new(
FACTOR_1_2,
FACTOR_1_3,
-top_left + top - 2 * left + 2 * center - bottom_left + bottom,
-top_left - 2 * top + bottom_left + 2 * bottom,
)
}
fn interior_normal(img: ImageRef, x: u32, y: u32) -> Normal {
let top_left = img.alpha_at(x - 1, y - 1);
let top = img.alpha_at(x, y - 1);
let top_right = img.alpha_at(x + 1, y - 1);
let left = img.alpha_at(x - 1, y);
let right = img.alpha_at(x + 1, y);
let bottom_left = img.alpha_at(x - 1, y + 1);
let bottom = img.alpha_at(x, y + 1);
let bottom_right = img.alpha_at(x + 1, y + 1);
Normal::new(
FACTOR_1_4,
FACTOR_1_4,
-top_left + top_right - 2 * left + 2 * right - bottom_left + bottom_right,
-top_left - 2 * top - top_right + bottom_left + 2 * bottom + bottom_right,
)
}
fn calc_diffuse_alpha(_: u8, _: u8, _: u8) -> u8 {
255
}
fn calc_specular_alpha(r: u8, g: u8, b: u8) -> u8 {
use core::cmp::max;
max(max(r, g), b)
}
================================================
FILE: crates/resvg/src/filter/mod.rs
================================================
// Copyright 2018 the Resvg Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
use std::rc::Rc;
use rgb::{FromSlice, RGBA8};
use tiny_skia::IntRect;
use usvg::{ApproxEqUlps, ApproxZeroUlps};
mod box_blur;
mod color_matrix;
mod component_transfer;
mod composite;
mod convolve_matrix;
mod displacement_map;
mod iir_blur;
mod lighting;
mod morphology;
mod turbulence;
// TODO: apply single primitive filters in-place
/// An image reference.
///
/// Image pixels should be stored in RGBA order.
///
/// Some filters will require premultiplied channels, some not.
/// See specific filter documentation for details.
#[derive(Clone, Copy)]
pub struct ImageRef<'a> {
data: &'a [RGBA8],
width: u32,
height: u32,
}
impl<'a> ImageRef<'a> {
/// Creates a new image reference.
///
/// Doesn't clone the provided data.
#[inline]
pub fn new(width: u32, height: u32, data: &'a [RGBA8]) -> Self {
ImageRef {
data,
width,
height,
}
}
#[inline]
fn alpha_at(&self, x: u32, y: u32) -> i16 {
self.data[(self.width * y + x) as usize].a as i16
}
}
/// A mutable `ImageRef` variant.
pub struct ImageRefMut<'a> {
data: &'a mut [RGBA8],
width: u32,
height: u32,
}
impl<'a> ImageRefMut<'a> {
/// Creates a new mutable image reference.
///
/// Doesn't clone the provided data.
#[inline]
pub fn new(width: u32, height: u32, data: &'a mut [RGBA8]) -> Self {
ImageRefMut {
data,
width,
height,
}
}
#[inline]
fn pixel_at(&self, x: u32, y: u32) -> RGBA8 {
self.data[(self.width * y + x) as usize]
}
#[inline]
fn pixel_at_mut(&mut self, x: u32, y: u32) -> &mut RGBA8 {
&mut self.data[(self.width * y + x) as usize]
}
}
#[derive(Debug)]
pub(crate) enum Error {
InvalidRegion,
NoResults,
}
trait PixmapExt: Sized {
fn try_create(width: u32, height: u32) -> Result;
fn copy_region(&self, region: IntRect) -> Result;
fn clear(&mut self);
fn into_srgb(&mut self);
fn into_linear_rgb(&mut self);
}
impl PixmapExt for tiny_skia::Pixmap {
fn try_create(width: u32, height: u32) -> Result {
tiny_skia::Pixmap::new(width, height).ok_or(Error::InvalidRegion)
}
fn copy_region(&self, region: IntRect) -> Result {
let rect = IntRect::from_xywh(region.x(), region.y(), region.width(), region.height())
.ok_or(Error::InvalidRegion)?;
self.clone_rect(rect).ok_or(Error::InvalidRegion)
}
fn clear(&mut self) {
self.fill(tiny_skia::Color::TRANSPARENT);
}
fn into_srgb(&mut self) {
demultiply_alpha(self.data_mut().as_rgba_mut());
from_linear_rgb(self.data_mut().as_rgba_mut());
multiply_alpha(self.data_mut().as_rgba_mut());
}
fn into_linear_rgb(&mut self) {
demultiply_alpha(self.data_mut().as_rgba_mut());
into_linear_rgb(self.data_mut().as_rgba_mut());
multiply_alpha(self.data_mut().as_rgba_mut());
}
}
/// Multiplies provided pixels alpha.
fn multiply_alpha(data: &mut [RGBA8]) {
for p in data {
let a = p.a as f32 / 255.0;
p.b = (p.b as f32 * a + 0.5) as u8;
p.g = (p.g as f32 * a + 0.5) as u8;
p.r = (p.r as f32 * a + 0.5) as u8;
}
}
/// Demultiplies provided pixels alpha.
fn demultiply_alpha(data: &mut [RGBA8]) {
for p in data {
let a = p.a as f32 / 255.0;
p.b = (p.b as f32 / a + 0.5) as u8;
p.g = (p.g as f32 / a + 0.5) as u8;
p.r = (p.r as f32 / a + 0.5) as u8;
}
}
/// 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.
#[rustfmt::skip]
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.
#[rustfmt::skip]
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,
];
/// Converts input pixel from sRGB into LinearRGB.
///
/// Provided pixels should have an **unpremultiplied alpha**.
///
/// RGB channels order of the input image doesn't matter, but alpha channel must be the last one.
fn into_linear_rgb(data: &mut [RGBA8]) {
for p in data {
p.r = SRGB_TO_LINEAR_RGB_TABLE[p.r as usize];
p.g = SRGB_TO_LINEAR_RGB_TABLE[p.g as usize];
p.b = SRGB_TO_LINEAR_RGB_TABLE[p.b as usize];
}
}
/// Converts input pixel from LinearRGB into sRGB.
///
/// Provided pixels should have an **unpremultiplied alpha**.
///
/// RGB channels order of the input image doesn't matter, but alpha channel must be the last one.
fn from_linear_rgb(data: &mut [RGBA8]) {
for p in data {
p.r = LINEAR_RGB_TO_SRGB_TABLE[p.r as usize];
p.g = LINEAR_RGB_TO_SRGB_TABLE[p.g as usize];
p.b = LINEAR_RGB_TO_SRGB_TABLE[p.b as usize];
}
}
// TODO: https://github.com/rust-lang/rust/issues/44095
#[inline]
fn f32_bound(min: f32, val: f32, max: f32) -> f32 {
debug_assert!(min.is_finite());
debug_assert!(val.is_finite());
debug_assert!(max.is_finite());
if val > max {
max
} else if val < min {
min
} else {
val
}
}
#[derive(Clone)]
struct Image {
/// Filter primitive result.
///
/// All images have the same size which is equal to the current filter region.
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`.
region: IntRect,
/// The current color space.
color_space: usvg::filter::ColorInterpolation,
}
impl Image {
fn from_image(image: tiny_skia::Pixmap, color_space: usvg::filter::ColorInterpolation) -> Self {
let (w, h) = (image.width(), image.height());
Image {
image: Rc::new(image),
region: IntRect::from_xywh(0, 0, w, h).unwrap(),
color_space,
}
}
fn into_color_space(
self,
color_space: usvg::filter::ColorInterpolation,
) -> Result {
if color_space != self.color_space {
let region = self.region;
let mut image = self.take()?;
match color_space {
usvg::filter::ColorInterpolation::SRGB => image.into_srgb(),
usvg::filter::ColorInterpolation::LinearRGB => image.into_linear_rgb(),
}
Ok(Image {
image: Rc::new(image),
region,
color_space,
})
} else {
Ok(self)
}
}
fn take(self) -> Result {
match Rc::try_unwrap(self.image) {
Ok(v) => Ok(v),
Err(v) => Ok((*v).clone()),
}
}
fn width(&self) -> u32 {
self.image.width()
}
fn height(&self) -> u32 {
self.image.height()
}
fn as_ref(&self) -> &tiny_skia::Pixmap {
&self.image
}
}
struct FilterResult {
name: String,
image: Image,
}
pub fn apply(
filter: &usvg::filter::Filter,
ts: tiny_skia::Transform,
source: &mut tiny_skia::Pixmap,
) {
let result = apply_inner(filter, ts, source);
let result = result.and_then(|image| apply_to_canvas(image, source));
// Clear on error.
if result.is_err() {
source.fill(tiny_skia::Color::TRANSPARENT);
}
match result {
Ok(_) => {}
Err(Error::InvalidRegion) => {
log::warn!("Filter has an invalid region.");
}
Err(Error::NoResults) => {}
}
}
fn apply_inner(
filter: &usvg::filter::Filter,
ts: usvg::Transform,
source: &mut tiny_skia::Pixmap,
) -> Result {
let region = filter
.rect()
.transform(ts)
.map(|r| r.to_int_rect())
.ok_or(Error::InvalidRegion)?;
let mut results: Vec = Vec::new();
for primitive in filter.primitives() {
let mut subregion = primitive
.rect()
.transform(ts)
.map(|r| r.to_int_rect())
.ok_or(Error::InvalidRegion)?;
// `feOffset` inherits its region from the input.
if let usvg::filter::Kind::Offset(fe) = primitive.kind() {
if let usvg::filter::Input::Reference(name) = fe.input() {
if let Some(res) = results.iter().rev().find(|v| v.name == *name) {
subregion = res.image.region;
}
}
}
let cs = primitive.color_interpolation();
let mut result = match primitive.kind() {
usvg::filter::Kind::Blend(fe) => {
let input1 = get_input(fe.input1(), region, source, &results)?;
let input2 = get_input(fe.input2(), region, source, &results)?;
apply_blend(fe, cs, region, input1, input2)
}
usvg::filter::Kind::DropShadow(fe) => {
let input = get_input(fe.input(), region, source, &results)?;
apply_drop_shadow(fe, cs, ts, input)
}
usvg::filter::Kind::Flood(fe) => apply_flood(fe, region),
usvg::filter::Kind::GaussianBlur(fe) => {
let input = get_input(fe.input(), region, source, &results)?;
apply_blur(fe, cs, ts, input)
}
usvg::filter::Kind::Offset(fe) => {
let input = get_input(fe.input(), region, source, &results)?;
apply_offset(fe, ts, input)
}
usvg::filter::Kind::Composite(fe) => {
let input1 = get_input(fe.input1(), region, source, &results)?;
let input2 = get_input(fe.input2(), region, source, &results)?;
apply_composite(fe, cs, region, input1, input2)
}
usvg::filter::Kind::Merge(fe) => apply_merge(fe, cs, region, source, &results),
usvg::filter::Kind::Tile(fe) => {
let input = get_input(fe.input(), region, source, &results)?;
apply_tile(input, region)
}
usvg::filter::Kind::Image(fe) => apply_image(fe, region, subregion, ts),
usvg::filter::Kind::ComponentTransfer(fe) => {
let input = get_input(fe.input(), region, source, &results)?;
apply_component_transfer(fe, cs, input)
}
usvg::filter::Kind::ColorMatrix(fe) => {
let input = get_input(fe.input(), region, source, &results)?;
apply_color_matrix(fe, cs, input)
}
usvg::filter::Kind::ConvolveMatrix(fe) => {
let input = get_input(fe.input(), region, source, &results)?;
apply_convolve_matrix(fe, cs, input)
}
usvg::filter::Kind::Morphology(fe) => {
let input = get_input(fe.input(), region, source, &results)?;
apply_morphology(fe, cs, ts, input)
}
usvg::filter::Kind::DisplacementMap(fe) => {
let input1 = get_input(fe.input1(), region, source, &results)?;
let input2 = get_input(fe.input2(), region, source, &results)?;
apply_displacement_map(fe, region, cs, ts, input1, input2)
}
usvg::filter::Kind::Turbulence(fe) => apply_turbulence(fe, region, cs, ts),
usvg::filter::Kind::DiffuseLighting(fe) => {
let input = get_input(fe.input(), region, source, &results)?;
apply_diffuse_lighting(fe, region, cs, ts, input)
}
usvg::filter::Kind::SpecularLighting(fe) => {
let input = get_input(fe.input(), region, source, &results)?;
apply_specular_lighting(fe, region, cs, ts, input)
}
}?;
if region != subregion {
// Clip result.
// TODO: explain
let subregion2 = if let usvg::filter::Kind::Offset(..) = primitive.kind() {
// We do not support clipping on feOffset.
region.translate_to(0, 0)
} else {
subregion.translate(-region.x(), -region.y())
}
.unwrap();
let color_space = result.color_space;
let pixmap = {
// This is cropping by clearing the pixels outside the region.
let mut paint = tiny_skia::Paint::default();
paint.set_color(tiny_skia::Color::BLACK);
paint.blend_mode = tiny_skia::BlendMode::Clear;
let mut pixmap = result.take()?;
let w = pixmap.width() as f32;
let h = pixmap.height() as f32;
if let Some(rect) = tiny_skia::Rect::from_xywh(0.0, 0.0, w, subregion2.y() as f32) {
pixmap.fill_rect(rect, &paint, tiny_skia::Transform::identity(), None);
}
if let Some(rect) = tiny_skia::Rect::from_xywh(0.0, 0.0, subregion2.x() as f32, h) {
pixmap.fill_rect(rect, &paint, tiny_skia::Transform::identity(), None);
}
if let Some(rect) = tiny_skia::Rect::from_xywh(subregion2.right() as f32, 0.0, w, h)
{
pixmap.fill_rect(rect, &paint, tiny_skia::Transform::identity(), None);
}
if let Some(rect) =
tiny_skia::Rect::from_xywh(0.0, subregion2.bottom() as f32, w, h)
{
pixmap.fill_rect(rect, &paint, tiny_skia::Transform::identity(), None);
}
pixmap
};
result = Image {
image: Rc::new(pixmap),
region: subregion,
color_space,
};
}
results.push(FilterResult {
name: primitive.result().to_string(),
image: result,
});
}
if let Some(res) = results.pop() {
Ok(res.image)
} else {
Err(Error::NoResults)
}
}
fn get_input(
input: &usvg::filter::Input,
region: IntRect,
source: &tiny_skia::Pixmap,
results: &[FilterResult],
) -> Result {
match input {
usvg::filter::Input::SourceGraphic => {
let image = source.clone();
Ok(Image {
image: Rc::new(image),
region,
color_space: usvg::filter::ColorInterpolation::SRGB,
})
}
usvg::filter::Input::SourceAlpha => {
let mut image = source.clone();
// Set RGB to black. Keep alpha as is.
for p in image.data_mut().as_rgba_mut() {
p.r = 0;
p.g = 0;
p.b = 0;
}
Ok(Image {
image: Rc::new(image),
region,
color_space: usvg::filter::ColorInterpolation::SRGB,
})
}
usvg::filter::Input::Reference(name) => {
if let Some(v) = results.iter().rev().find(|v| v.name == *name) {
Ok(v.image.clone())
} else {
// Technically unreachable.
log::warn!("Unknown filter primitive reference '{}'.", name);
get_input(&usvg::filter::Input::SourceGraphic, region, source, results)
}
}
}
}
trait PixmapToImageRef<'a> {
fn as_image_ref(&'a self) -> ImageRef<'a>;
fn as_image_ref_mut(&'a mut self) -> ImageRefMut<'a>;
}
impl<'a> PixmapToImageRef<'a> for tiny_skia::Pixmap {
fn as_image_ref(&'a self) -> ImageRef<'a> {
ImageRef::new(self.width(), self.height(), self.data().as_rgba())
}
fn as_image_ref_mut(&'a mut self) -> ImageRefMut<'a> {
ImageRefMut::new(self.width(), self.height(), self.data_mut().as_rgba_mut())
}
}
fn apply_drop_shadow(
fe: &usvg::filter::DropShadow,
cs: usvg::filter::ColorInterpolation,
ts: usvg::Transform,
input: Image,
) -> Result {
let (dx, dy) = match scale_coordinates(fe.dx(), fe.dy(), ts) {
Some(v) => v,
None => return Ok(input),
};
let mut pixmap = tiny_skia::Pixmap::try_create(input.width(), input.height())?;
let input_pixmap = input.into_color_space(cs)?.take()?;
let mut shadow_pixmap = input_pixmap.clone();
if let Some((std_dx, std_dy, use_box_blur)) =
resolve_std_dev(fe.std_dev_x().get(), fe.std_dev_y().get(), ts)
{
if use_box_blur {
box_blur::apply(std_dx, std_dy, shadow_pixmap.as_image_ref_mut());
} else {
iir_blur::apply(std_dx, std_dy, shadow_pixmap.as_image_ref_mut());
}
}
// flood
let color = tiny_skia::Color::from_rgba8(
fe.color().red,
fe.color().green,
fe.color().blue,
fe.opacity().to_u8(),
);
for p in shadow_pixmap.pixels_mut() {
let mut color = color;
color.apply_opacity(p.alpha() as f32 / 255.0);
*p = color.premultiply().to_color_u8();
}
match cs {
usvg::filter::ColorInterpolation::SRGB => shadow_pixmap.into_srgb(),
usvg::filter::ColorInterpolation::LinearRGB => shadow_pixmap.into_linear_rgb(),
}
pixmap.draw_pixmap(
dx as i32,
dy as i32,
shadow_pixmap.as_ref(),
&tiny_skia::PixmapPaint::default(),
tiny_skia::Transform::identity(),
None,
);
pixmap.draw_pixmap(
0,
0,
input_pixmap.as_ref(),
&tiny_skia::PixmapPaint::default(),
tiny_skia::Transform::identity(),
None,
);
Ok(Image::from_image(pixmap, cs))
}
fn apply_blur(
fe: &usvg::filter::GaussianBlur,
cs: usvg::filter::ColorInterpolation,
ts: usvg::Transform,
input: Image,
) -> Result {
let (std_dx, std_dy, use_box_blur) =
match resolve_std_dev(fe.std_dev_x().get(), fe.std_dev_y().get(), ts) {
Some(v) => v,
None => return Ok(input),
};
let mut pixmap = input.into_color_space(cs)?.take()?;
if use_box_blur {
box_blur::apply(std_dx, std_dy, pixmap.as_image_ref_mut());
} else {
iir_blur::apply(std_dx, std_dy, pixmap.as_image_ref_mut());
}
Ok(Image::from_image(pixmap, cs))
}
fn apply_offset(
fe: &usvg::filter::Offset,
ts: usvg::Transform,
input: Image,
) -> Result {
let (dx, dy) = match scale_coordinates(fe.dx(), fe.dy(), ts) {
Some(v) => v,
None => return Ok(input),
};
if dx.approx_zero_ulps(4) && dy.approx_zero_ulps(4) {
return Ok(input);
}
let mut pixmap = tiny_skia::Pixmap::try_create(input.width(), input.height())?;
pixmap.draw_pixmap(
dx as i32,
dy as i32,
input.as_ref().as_ref(),
&tiny_skia::PixmapPaint::default(),
tiny_skia::Transform::identity(),
None,
);
Ok(Image::from_image(pixmap, input.color_space))
}
fn apply_blend(
fe: &usvg::filter::Blend,
cs: usvg::filter::ColorInterpolation,
region: IntRect,
input1: Image,
input2: Image,
) -> Result {
let input1 = input1.into_color_space(cs)?;
let input2 = input2.into_color_space(cs)?;
let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?;
pixmap.draw_pixmap(
0,
0,
input2.as_ref().as_ref(),
&tiny_skia::PixmapPaint::default(),
tiny_skia::Transform::identity(),
None,
);
pixmap.draw_pixmap(
0,
0,
input1.as_ref().as_ref(),
&tiny_skia::PixmapPaint {
blend_mode: crate::render::convert_blend_mode(fe.mode()),
..tiny_skia::PixmapPaint::default()
},
tiny_skia::Transform::identity(),
None,
);
Ok(Image::from_image(pixmap, cs))
}
fn apply_composite(
fe: &usvg::filter::Composite,
cs: usvg::filter::ColorInterpolation,
region: IntRect,
input1: Image,
input2: Image,
) -> Result {
use usvg::filter::CompositeOperator as Operator;
let input1 = input1.into_color_space(cs)?;
let input2 = input2.into_color_space(cs)?;
let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?;
if let Operator::Arithmetic { k1, k2, k3, k4 } = fe.operator() {
let pixmap1 = input1.take()?;
let pixmap2 = input2.take()?;
composite::arithmetic(
k1,
k2,
k3,
k4,
pixmap1.as_image_ref(),
pixmap2.as_image_ref(),
pixmap.as_image_ref_mut(),
);
return Ok(Image::from_image(pixmap, cs));
}
pixmap.draw_pixmap(
0,
0,
input2.as_ref().as_ref(),
&tiny_skia::PixmapPaint::default(),
tiny_skia::Transform::identity(),
None,
);
let blend_mode = match fe.operator() {
Operator::Over => tiny_skia::BlendMode::SourceOver,
Operator::In => tiny_skia::BlendMode::SourceIn,
Operator::Out => tiny_skia::BlendMode::SourceOut,
Operator::Atop => tiny_skia::BlendMode::SourceAtop,
Operator::Xor => tiny_skia::BlendMode::Xor,
Operator::Arithmetic { .. } => tiny_skia::BlendMode::SourceOver,
};
pixmap.draw_pixmap(
0,
0,
input1.as_ref().as_ref(),
&tiny_skia::PixmapPaint {
blend_mode,
..tiny_skia::PixmapPaint::default()
},
tiny_skia::Transform::identity(),
None,
);
Ok(Image::from_image(pixmap, cs))
}
fn apply_merge(
fe: &usvg::filter::Merge,
cs: usvg::filter::ColorInterpolation,
region: IntRect,
source: &tiny_skia::Pixmap,
results: &[FilterResult],
) -> Result {
let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?;
for input in fe.inputs() {
let input = get_input(input, region, source, results)?;
let input = input.into_color_space(cs)?;
pixmap.draw_pixmap(
0,
0,
input.as_ref().as_ref(),
&tiny_skia::PixmapPaint::default(),
tiny_skia::Transform::identity(),
None,
);
}
Ok(Image::from_image(pixmap, cs))
}
fn apply_flood(fe: &usvg::filter::Flood, region: IntRect) -> Result {
let c = fe.color();
let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?;
pixmap.fill(tiny_skia::Color::from_rgba8(
c.red,
c.green,
c.blue,
fe.opacity().to_u8(),
));
Ok(Image::from_image(
pixmap,
usvg::filter::ColorInterpolation::SRGB,
))
}
fn apply_tile(input: Image, region: IntRect) -> Result {
let subregion = input.region.translate(-region.x(), -region.y()).unwrap();
let tile_pixmap = input.image.copy_region(subregion)?;
let mut paint = tiny_skia::Paint::default();
paint.shader = tiny_skia::Pattern::new(
tile_pixmap.as_ref(),
tiny_skia::SpreadMode::Repeat,
tiny_skia::FilterQuality::Bicubic,
1.0,
tiny_skia::Transform::from_translate(subregion.x() as f32, subregion.y() as f32),
);
let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?;
let rect = tiny_skia::Rect::from_xywh(0.0, 0.0, region.width() as f32, region.height() as f32)
.unwrap();
pixmap.fill_rect(rect, &paint, tiny_skia::Transform::identity(), None);
Ok(Image::from_image(
pixmap,
usvg::filter::ColorInterpolation::SRGB,
))
}
fn apply_image(
fe: &usvg::filter::Image,
region: IntRect,
subregion: IntRect,
ts: usvg::Transform,
) -> Result {
let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?;
let (sx, sy) = ts.get_scale();
let transform = tiny_skia::Transform::from_row(
sx,
0.0,
0.0,
sy,
subregion.x() as f32,
subregion.y() as f32,
);
let ctx = crate::render::Context {
max_bbox: tiny_skia::IntRect::from_xywh(0, 0, region.width(), region.height()).unwrap(),
};
crate::render::render_nodes(fe.root(), &ctx, transform, &mut pixmap.as_mut());
Ok(Image::from_image(
pixmap,
usvg::filter::ColorInterpolation::SRGB,
))
}
fn apply_component_transfer(
fe: &usvg::filter::ComponentTransfer,
cs: usvg::filter::ColorInterpolation,
input: Image,
) -> Result {
let mut pixmap = input.into_color_space(cs)?.take()?;
demultiply_alpha(pixmap.data_mut().as_rgba_mut());
component_transfer::apply(fe, pixmap.as_image_ref_mut());
multiply_alpha(pixmap.data_mut().as_rgba_mut());
Ok(Image::from_image(pixmap, cs))
}
fn apply_color_matrix(
fe: &usvg::filter::ColorMatrix,
cs: usvg::filter::ColorInterpolation,
input: Image,
) -> Result {
let mut pixmap = input.into_color_space(cs)?.take()?;
demultiply_alpha(pixmap.data_mut().as_rgba_mut());
color_matrix::apply(fe.kind(), pixmap.as_image_ref_mut());
multiply_alpha(pixmap.data_mut().as_rgba_mut());
Ok(Image::from_image(pixmap, cs))
}
fn apply_convolve_matrix(
fe: &usvg::filter::ConvolveMatrix,
cs: usvg::filter::ColorInterpolation,
input: Image,
) -> Result {
let mut pixmap = input.into_color_space(cs)?.take()?;
if fe.preserve_alpha() {
demultiply_alpha(pixmap.data_mut().as_rgba_mut());
}
convolve_matrix::apply(fe, pixmap.as_image_ref_mut());
Ok(Image::from_image(pixmap, cs))
}
fn apply_morphology(
fe: &usvg::filter::Morphology,
cs: usvg::filter::ColorInterpolation,
ts: usvg::Transform,
input: Image,
) -> Result {
let mut pixmap = input.into_color_space(cs)?.take()?;
let (rx, ry) = match scale_coordinates(fe.radius_x().get(), fe.radius_y().get(), ts) {
Some(v) => v,
None => return Ok(Image::from_image(pixmap, cs)),
};
if !(rx > 0.0 && ry > 0.0) {
pixmap.clear();
return Ok(Image::from_image(pixmap, cs));
}
morphology::apply(fe.operator(), rx, ry, pixmap.as_image_ref_mut());
Ok(Image::from_image(pixmap, cs))
}
fn apply_displacement_map(
fe: &usvg::filter::DisplacementMap,
region: IntRect,
cs: usvg::filter::ColorInterpolation,
ts: usvg::Transform,
input1: Image,
input2: Image,
) -> Result {
let pixmap1 = input1.into_color_space(cs)?.take()?;
let pixmap2 = input2.into_color_space(cs)?.take()?;
let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?;
let (sx, sy) = match scale_coordinates(fe.scale(), fe.scale(), ts) {
Some(v) => v,
None => return Ok(Image::from_image(pixmap1, cs)),
};
displacement_map::apply(
fe,
sx,
sy,
pixmap1.as_image_ref(),
pixmap2.as_image_ref(),
pixmap.as_image_ref_mut(),
);
Ok(Image::from_image(pixmap, cs))
}
fn apply_turbulence(
fe: &usvg::filter::Turbulence,
region: IntRect,
cs: usvg::filter::ColorInterpolation,
ts: usvg::Transform,
) -> Result {
let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?;
let (sx, sy) = ts.get_scale();
if sx.approx_zero_ulps(4) || sy.approx_zero_ulps(4) {
return Ok(Image::from_image(pixmap, cs));
}
turbulence::apply(
region.x() as f64 - ts.tx as f64,
region.y() as f64 - ts.ty as f64,
sx as f64,
sy as f64,
fe.base_frequency_x().get() as f64,
fe.base_frequency_y().get() as f64,
fe.num_octaves(),
fe.seed(),
fe.stitch_tiles(),
fe.kind() == usvg::filter::TurbulenceKind::FractalNoise,
pixmap.as_image_ref_mut(),
);
multiply_alpha(pixmap.data_mut().as_rgba_mut());
Ok(Image::from_image(pixmap, cs))
}
fn apply_diffuse_lighting(
fe: &usvg::filter::DiffuseLighting,
region: IntRect,
cs: usvg::filter::ColorInterpolation,
ts: usvg::Transform,
input: Image,
) -> Result {
let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?;
let light_source = transform_light_source(fe.light_source(), region, ts);
lighting::diffuse_lighting(
fe,
light_source,
input.as_ref().as_image_ref(),
pixmap.as_image_ref_mut(),
);
Ok(Image::from_image(pixmap, cs))
}
fn apply_specular_lighting(
fe: &usvg::filter::SpecularLighting,
region: IntRect,
cs: usvg::filter::ColorInterpolation,
ts: usvg::Transform,
input: Image,
) -> Result {
let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?;
let light_source = transform_light_source(fe.light_source(), region, ts);
lighting::specular_lighting(
fe,
light_source,
input.as_ref().as_image_ref(),
pixmap.as_image_ref_mut(),
);
Ok(Image::from_image(pixmap, cs))
}
// TODO: do not modify LightSource
fn transform_light_source(
mut source: usvg::filter::LightSource,
region: IntRect,
ts: usvg::Transform,
) -> usvg::filter::LightSource {
use std::f32::consts::SQRT_2;
use usvg::filter::LightSource;
match &mut source {
LightSource::DistantLight(..) => {}
LightSource::PointLight(light) => {
let mut point = tiny_skia::Point::from_xy(light.x, light.y);
ts.map_point(&mut point);
light.x = point.x - region.x() as f32;
light.y = point.y - region.y() as f32;
light.z = light.z * (ts.sx * ts.sx + ts.sy * ts.sy).sqrt() / SQRT_2;
}
LightSource::SpotLight(light) => {
let sz = (ts.sx * ts.sx + ts.sy * ts.sy).sqrt() / SQRT_2;
let mut point = tiny_skia::Point::from_xy(light.x, light.y);
ts.map_point(&mut point);
light.x = point.x - region.x() as f32;
light.y = point.y - region.x() as f32;
light.z *= sz;
let mut point = tiny_skia::Point::from_xy(light.points_at_x, light.points_at_y);
ts.map_point(&mut point);
light.points_at_x = point.x - region.x() as f32;
light.points_at_y = point.y - region.x() as f32;
light.points_at_z *= sz;
}
}
source
}
fn apply_to_canvas(input: Image, pixmap: &mut tiny_skia::Pixmap) -> Result<(), Error> {
let input = input.into_color_space(usvg::filter::ColorInterpolation::SRGB)?;
pixmap.fill(tiny_skia::Color::TRANSPARENT);
pixmap.draw_pixmap(
0,
0,
input.as_ref().as_ref(),
&tiny_skia::PixmapPaint::default(),
tiny_skia::Transform::identity(),
None,
);
Ok(())
}
/// Calculates Gaussian blur sigmas for the current world transform.
///
/// If the last flag is set, then a box blur should be used. Or IIR otherwise.
fn resolve_std_dev(std_dx: f32, std_dy: f32, ts: usvg::Transform) -> Option<(f64, f64, bool)> {
let (mut std_dx, mut std_dy) = scale_coordinates(std_dx, std_dy, ts)?;
// '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 std_dx.approx_eq_ulps(&0.0, 4) && std_dy.approx_eq_ulps(&0.0, 4) {
return None;
}
// Ignore tiny sigmas. In case of IIR blur it can lead to a transparent image.
if std_dx < 0.05 {
std_dx = 0.0;
}
if std_dy < 0.05 {
std_dy = 0.0;
}
const BLUR_SIGMA_THRESHOLD: f32 = 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 as f64, std_dy as f64, box_blur))
}
fn scale_coordinates(x: f32, y: f32, ts: usvg::Transform) -> Option<(f32, f32)> {
let (sx, sy) = ts.get_scale();
Some((x * sx, y * sy))
}
================================================
FILE: crates/resvg/src/filter/morphology.rs
================================================
// Copyright 2020 the Resvg Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
use super::ImageRefMut;
use rgb::RGBA8;
use usvg::filter::MorphologyOperator;
/// Applies a morphology filter.
///
/// `src` pixels should have a **premultiplied alpha**.
///
/// # Allocations
///
/// This method will allocate a copy of the `src` image as a back buffer.
pub fn apply(operator: MorphologyOperator, rx: f32, ry: f32, src: ImageRefMut) {
// No point in making matrix larger than image.
let columns = std::cmp::min(rx.ceil() as u32 * 2, src.width);
let rows = std::cmp::min(ry.ceil() as u32 * 2, src.height);
let target_x = (columns as f32 / 2.0).floor() as u32;
let target_y = (rows as f32 / 2.0).floor() as u32;
let width_max = src.width as i32 - 1;
let height_max = src.height as i32 - 1;
let mut buf = vec![RGBA8::default(); src.data.len()];
let mut buf = ImageRefMut::new(src.width, src.height, &mut buf);
let mut x = 0;
let mut y = 0;
for _ in src.data.iter() {
let mut new_p = RGBA8::default();
if operator == MorphologyOperator::Erode {
new_p.r = 255;
new_p.g = 255;
new_p.b = 255;
new_p.a = 255;
}
for oy in 0..rows {
for ox in 0..columns {
let tx = x as i32 - target_x as i32 + ox as i32;
let ty = y as i32 - target_y as i32 + oy as i32;
if tx < 0 || tx > width_max || ty < 0 || ty > height_max {
continue;
}
let p = src.pixel_at(tx as u32, ty as u32);
if operator == MorphologyOperator::Erode {
new_p.r = std::cmp::min(p.r, new_p.r);
new_p.g = std::cmp::min(p.g, new_p.g);
new_p.b = std::cmp::min(p.b, new_p.b);
new_p.a = std::cmp::min(p.a, new_p.a);
} else {
new_p.r = std::cmp::max(p.r, new_p.r);
new_p.g = std::cmp::max(p.g, new_p.g);
new_p.b = std::cmp::max(p.b, new_p.b);
new_p.a = std::cmp::max(p.a, new_p.a);
}
}
}
*buf.pixel_at_mut(x, y) = new_p;
x += 1;
if x == src.width {
x = 0;
y += 1;
}
}
// Do not use `mem::swap` because `data` referenced via FFI.
src.data.copy_from_slice(buf.data);
}
================================================
FILE: crates/resvg/src/filter/turbulence.rs
================================================
// Copyright 2020 the Resvg Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
#![allow(clippy::needless_range_loop)]
use super::{ImageRefMut, f32_bound};
use usvg::ApproxZeroUlps;
const RAND_M: i32 = 2147483647; // 2**31 - 1
const RAND_A: i32 = 16807; // 7**5; primitive root of m
const RAND_Q: i32 = 127773; // m / a
const RAND_R: i32 = 2836; // m % a
const B_SIZE: usize = 0x100;
const B_SIZE_32: i32 = 0x100;
const B_LEN: usize = B_SIZE + B_SIZE + 2;
const BM: i32 = 0xff;
const PERLIN_N: i32 = 0x1000;
#[derive(Clone, Copy)]
struct StitchInfo {
width: i32, // How much to subtract to wrap for stitching.
height: i32,
wrap_x: i32, // Minimum value to wrap.
wrap_y: i32,
}
/// Applies a turbulence filter.
///
/// `dest` image pixels will have an **unpremultiplied alpha**.
///
/// - `offset_x` and `offset_y` indicate filter region offset.
/// - `sx` and `sy` indicate canvas scale.
pub fn apply(
offset_x: f64,
offset_y: f64,
sx: f64,
sy: f64,
base_frequency_x: f64,
base_frequency_y: f64,
num_octaves: u32,
seed: i32,
stitch_tiles: bool,
fractal_noise: bool,
dest: ImageRefMut,
) {
let (lattice_selector, gradient) = init(seed);
let width = dest.width;
let height = dest.height;
let mut x = 0;
let mut y = 0;
for pixel in dest.data.iter_mut() {
let turb = |channel| {
let (tx, ty) = ((x as f64 + offset_x) / sx, (y as f64 + offset_y) / sy);
let n = turbulence(
channel,
tx,
ty,
x as f64,
y as f64,
width as f64,
height as f64,
base_frequency_x,
base_frequency_y,
num_octaves,
fractal_noise,
stitch_tiles,
&lattice_selector,
&gradient,
);
let n = if fractal_noise {
(n * 255.0 + 255.0) / 2.0
} else {
n * 255.0
};
(f32_bound(0.0, n as f32, 255.0) + 0.5) as u8
};
pixel.r = turb(0);
pixel.g = turb(1);
pixel.b = turb(2);
pixel.a = turb(3);
x += 1;
if x == dest.width {
x = 0;
y += 1;
}
}
}
fn init(mut seed: i32) -> (Vec, Vec>>) {
let mut lattice_selector = vec![0; B_LEN];
let mut gradient = vec![vec![vec![0.0; 2]; B_LEN]; 4];
if seed <= 0 {
seed = -seed % (RAND_M - 1) + 1;
}
if seed > RAND_M - 1 {
seed = RAND_M - 1;
}
for k in 0..4 {
for i in 0..B_SIZE {
lattice_selector[i] = i;
for j in 0..2 {
seed = random(seed);
gradient[k][i][j] =
((seed % (B_SIZE_32 + B_SIZE_32)) - B_SIZE_32) as f64 / B_SIZE_32 as f64;
}
let s = (gradient[k][i][0] * gradient[k][i][0] + gradient[k][i][1] * gradient[k][i][1])
.sqrt();
gradient[k][i][0] /= s;
gradient[k][i][1] /= s;
}
}
for i in (1..B_SIZE).rev() {
let k = lattice_selector[i];
seed = random(seed);
let j = (seed % B_SIZE_32) as usize;
lattice_selector[i] = lattice_selector[j];
lattice_selector[j] = k;
}
for i in 0..B_SIZE + 2 {
lattice_selector[B_SIZE + i] = lattice_selector[i];
for g in gradient.iter_mut().take(4) {
for j in 0..2 {
g[B_SIZE + i][j] = g[i][j];
}
}
}
(lattice_selector, gradient)
}
fn turbulence(
color_channel: usize,
mut x: f64,
mut y: f64,
tile_x: f64,
tile_y: f64,
tile_width: f64,
tile_height: f64,
mut base_freq_x: f64,
mut base_freq_y: f64,
num_octaves: u32,
fractal_sum: bool,
do_stitching: bool,
lattice_selector: &[usize],
gradient: &[Vec>],
) -> f64 {
// Adjust the base frequencies if necessary for stitching.
let mut stitch = if do_stitching {
// When stitching tiled turbulence, the frequencies must be adjusted
// so that the tile borders will be continuous.
if !base_freq_x.approx_zero_ulps(4) {
let lo_freq = (tile_width * base_freq_x).floor() / tile_width;
let hi_freq = (tile_width * base_freq_x).ceil() / tile_width;
if base_freq_x / lo_freq < hi_freq / base_freq_x {
base_freq_x = lo_freq;
} else {
base_freq_x = hi_freq;
}
}
if !base_freq_y.approx_zero_ulps(4) {
let lo_freq = (tile_height * base_freq_y).floor() / tile_height;
let hi_freq = (tile_height * base_freq_y).ceil() / tile_height;
if base_freq_y / lo_freq < hi_freq / base_freq_y {
base_freq_y = lo_freq;
} else {
base_freq_y = hi_freq;
}
}
// Set up initial stitch values.
let width = (tile_width * base_freq_x + 0.5) as i32;
let height = (tile_height * base_freq_y + 0.5) as i32;
let wrap_x = (tile_x * base_freq_x + PERLIN_N as f64 + width as f64) as i32;
let wrap_y = (tile_y * base_freq_y + PERLIN_N as f64 + height as f64) as i32;
Some(StitchInfo {
width,
height,
wrap_x,
wrap_y,
})
} else {
None
};
let mut sum = 0.0;
x *= base_freq_x;
y *= base_freq_y;
let mut ratio = 1.0;
for _ in 0..num_octaves {
if fractal_sum {
sum += noise2(color_channel, x, y, lattice_selector, gradient, stitch) / ratio;
} else {
sum += noise2(color_channel, x, y, lattice_selector, gradient, stitch).abs() / ratio;
}
x *= 2.0;
y *= 2.0;
ratio *= 2.0;
if let Some(ref mut stitch) = stitch {
// Update stitch values. Subtracting PerlinN before the multiplication and
// adding it afterward simplifies to subtracting it once.
stitch.width *= 2;
stitch.wrap_x = 2 * stitch.wrap_x - PERLIN_N;
stitch.height *= 2;
stitch.wrap_y = 2 * stitch.wrap_y - PERLIN_N;
}
}
sum
}
fn noise2(
color_channel: usize,
x: f64,
y: f64,
lattice_selector: &[usize],
gradient: &[Vec>],
stitch_info: Option,
) -> f64 {
let t = x + PERLIN_N as f64;
let mut bx0 = t as i32;
let mut bx1 = bx0 + 1;
let rx0 = t - t as i64 as f64;
let rx1 = rx0 - 1.0;
let t = y + PERLIN_N as f64;
let mut by0 = t as i32;
let mut by1 = by0 + 1;
let ry0 = t - t as i64 as f64;
let ry1 = ry0 - 1.0;
// If stitching, adjust lattice points accordingly.
if let Some(info) = stitch_info {
if bx0 >= info.wrap_x {
bx0 -= info.width;
}
if bx1 >= info.wrap_x {
bx1 -= info.width;
}
if by0 >= info.wrap_y {
by0 -= info.height;
}
if by1 >= info.wrap_y {
by1 -= info.height;
}
}
bx0 &= BM;
bx1 &= BM;
by0 &= BM;
by1 &= BM;
let i = lattice_selector[bx0 as usize];
let j = lattice_selector[bx1 as usize];
let b00 = lattice_selector[i + by0 as usize];
let b10 = lattice_selector[j + by0 as usize];
let b01 = lattice_selector[i + by1 as usize];
let b11 = lattice_selector[j + by1 as usize];
let sx = s_curve(rx0);
let sy = s_curve(ry0);
let q = &gradient[color_channel][b00];
let u = rx0 * q[0] + ry0 * q[1];
let q = &gradient[color_channel][b10];
let v = rx1 * q[0] + ry0 * q[1];
let a = lerp(sx, u, v);
let q = &gradient[color_channel][b01];
let u = rx0 * q[0] + ry1 * q[1];
let q = &gradient[color_channel][b11];
let v = rx1 * q[0] + ry1 * q[1];
let b = lerp(sx, u, v);
lerp(sy, a, b)
}
fn random(seed: i32) -> i32 {
let mut result = RAND_A * (seed % RAND_Q) - RAND_R * (seed / RAND_Q);
if result <= 0 {
result += RAND_M;
}
result
}
#[inline]
fn s_curve(t: f64) -> f64 {
t * t * (3.0 - 2.0 * t)
}
#[inline]
fn lerp(t: f64, a: f64, b: f64) -> f64 {
a + t * (b - a)
}
================================================
FILE: crates/resvg/src/geom.rs
================================================
// Copyright 2023 the Resvg Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
/// Fits the current rect into the specified bounds.
pub fn fit_to_rect(
r: tiny_skia::IntRect,
bounds: tiny_skia::IntRect,
) -> Option {
let mut left = r.left();
if left < bounds.left() {
left = bounds.left();
}
let mut top = r.top();
if top < bounds.top() {
top = bounds.top();
}
let mut right = r.right();
if right > bounds.right() {
right = bounds.right();
}
let mut bottom = r.bottom();
if bottom > bounds.bottom() {
bottom = bounds.bottom();
}
tiny_skia::IntRect::from_ltrb(left, top, right, bottom)
}
================================================
FILE: crates/resvg/src/image.rs
================================================
// Copyright 2018 the Resvg Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
pub fn render(
image: &usvg::Image,
transform: tiny_skia::Transform,
pixmap: &mut tiny_skia::PixmapMut,
) {
if !image.is_visible() {
return;
}
render_inner(image.kind(), transform, image.rendering_mode(), pixmap);
}
pub fn render_inner(
image_kind: &usvg::ImageKind,
transform: tiny_skia::Transform,
#[allow(unused_variables)] rendering_mode: usvg::ImageRendering,
pixmap: &mut tiny_skia::PixmapMut,
) {
match image_kind {
usvg::ImageKind::SVG(tree) => {
render_vector(tree, transform, pixmap);
}
#[cfg(feature = "raster-images")]
_ => {
raster_images::render_raster(image_kind, transform, rendering_mode, pixmap);
}
#[cfg(not(feature = "raster-images"))]
_ => {
log::warn!("Images decoding was disabled by a build feature.");
}
}
}
fn render_vector(
tree: &usvg::Tree,
transform: tiny_skia::Transform,
pixmap: &mut tiny_skia::PixmapMut,
) -> Option<()> {
let mut sub_pixmap = tiny_skia::Pixmap::new(pixmap.width(), pixmap.height()).unwrap();
crate::render(tree, transform, &mut sub_pixmap.as_mut());
pixmap.draw_pixmap(
0,
0,
sub_pixmap.as_ref(),
&tiny_skia::PixmapPaint::default(),
tiny_skia::Transform::default(),
None,
);
Some(())
}
#[cfg(feature = "raster-images")]
mod raster_images {
use crate::OptionLog;
use std::io::Cursor;
use usvg::ImageRendering;
fn decode_raster(image: &usvg::ImageKind) -> Option {
match image {
usvg::ImageKind::SVG(_) => None,
usvg::ImageKind::JPEG(data) => {
decode_jpeg(data).log_none(|| log::warn!("Failed to decode a JPEG image."))
}
usvg::ImageKind::PNG(data) => {
decode_png(data).log_none(|| log::warn!("Failed to decode a PNG image."))
}
usvg::ImageKind::GIF(data) => {
decode_gif(data).log_none(|| log::warn!("Failed to decode a GIF image."))
}
usvg::ImageKind::WEBP(data) => {
decode_webp(data).log_none(|| log::warn!("Failed to decode a WebP image."))
}
}
}
fn decode_png(data: &[u8]) -> Option {
tiny_skia::Pixmap::decode_png(data).ok()
}
fn decode_jpeg(data: &[u8]) -> Option {
use zune_jpeg::zune_core::colorspace::ColorSpace;
use zune_jpeg::zune_core::options::DecoderOptions;
let cursor = Cursor::new(data);
let options = DecoderOptions::default().jpeg_set_out_colorspace(ColorSpace::RGBA);
let mut decoder = zune_jpeg::JpegDecoder::new_with_options(cursor, options);
decoder.decode_headers().ok()?;
let output_cs = decoder.output_colorspace()?;
let img_data = {
let data = decoder.decode().ok()?;
match output_cs {
ColorSpace::RGBA => data,
_ => return None,
}
};
let info = decoder.info()?;
let size = tiny_skia::IntSize::from_wh(info.width as u32, info.height as u32)?;
tiny_skia::Pixmap::from_vec(img_data, size)
}
fn decode_gif(data: &[u8]) -> Option {
let mut decoder = gif::DecodeOptions::new();
decoder.set_color_output(gif::ColorOutput::RGBA);
let mut decoder = decoder.read_info(data).ok()?;
let first_frame = decoder.read_next_frame().ok()??;
let size = tiny_skia::IntSize::from_wh(
u32::from(first_frame.width),
u32::from(first_frame.height),
)?;
let (w, h) = size.dimensions();
let mut pixmap = tiny_skia::Pixmap::new(w, h)?;
rgba_to_pixmap(&first_frame.buffer, &mut pixmap);
Some(pixmap)
}
fn decode_webp(data: &[u8]) -> Option {
let mut decoder = image_webp::WebPDecoder::new(std::io::Cursor::new(data)).ok()?;
let mut first_frame = vec![0; decoder.output_buffer_size()?];
decoder.read_image(&mut first_frame).ok()?;
let (w, h) = decoder.dimensions();
let mut pixmap = tiny_skia::Pixmap::new(w, h)?;
if decoder.has_alpha() {
rgba_to_pixmap(&first_frame, &mut pixmap);
} else {
rgb_to_pixmap(&first_frame, &mut pixmap);
}
Some(pixmap)
}
fn rgb_to_pixmap(data: &[u8], pixmap: &mut tiny_skia::Pixmap) {
use rgb::FromSlice;
let mut i = 0;
let dst = pixmap.data_mut();
for p in data.as_rgb() {
dst[i + 0] = p.r;
dst[i + 1] = p.g;
dst[i + 2] = p.b;
dst[i + 3] = 255;
i += tiny_skia::BYTES_PER_PIXEL;
}
}
fn rgba_to_pixmap(data: &[u8], pixmap: &mut tiny_skia::Pixmap) {
use rgb::FromSlice;
let mut i = 0;
let dst = pixmap.data_mut();
for p in data.as_rgba() {
let a = p.a as f64 / 255.0;
dst[i + 0] = (p.r as f64 * a + 0.5) as u8;
dst[i + 1] = (p.g as f64 * a + 0.5) as u8;
dst[i + 2] = (p.b as f64 * a + 0.5) as u8;
dst[i + 3] = p.a;
i += tiny_skia::BYTES_PER_PIXEL;
}
}
pub(crate) fn render_raster(
image: &usvg::ImageKind,
transform: tiny_skia::Transform,
rendering_mode: usvg::ImageRendering,
pixmap: &mut tiny_skia::PixmapMut,
) -> Option<()> {
let raster = decode_raster(image)?;
let rect = tiny_skia::Size::from_wh(raster.width() as f32, raster.height() as f32)?
.to_rect(0.0, 0.0)?;
let quality = match rendering_mode {
ImageRendering::OptimizeQuality => tiny_skia::FilterQuality::Bicubic,
ImageRendering::OptimizeSpeed => tiny_skia::FilterQuality::Nearest,
ImageRendering::Smooth => tiny_skia::FilterQuality::Bilinear,
ImageRendering::HighQuality => tiny_skia::FilterQuality::Bicubic,
ImageRendering::CrispEdges => tiny_skia::FilterQuality::Nearest,
ImageRendering::Pixelated => tiny_skia::FilterQuality::Nearest,
};
let pattern = tiny_skia::Pattern::new(
raster.as_ref(),
tiny_skia::SpreadMode::Pad,
quality,
1.0,
tiny_skia::Transform::default(),
);
let mut paint = tiny_skia::Paint::default();
paint.shader = pattern;
pixmap.fill_rect(rect, &paint, transform, None);
Some(())
}
}
================================================
FILE: crates/resvg/src/lib.rs
================================================
// Copyright 2017 the Resvg Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
/*!
[resvg](https://github.com/linebender/resvg) is an SVG rendering library.
*/
#![forbid(unsafe_code)]
#![warn(missing_docs)]
#![allow(clippy::field_reassign_with_default)]
#![allow(clippy::identity_op)]
#![allow(clippy::too_many_arguments)]
#![allow(clippy::uninlined_format_args)]
#![allow(clippy::upper_case_acronyms)]
#![allow(clippy::wrong_self_convention)]
pub use tiny_skia;
pub use usvg;
mod clip;
mod filter;
mod geom;
mod image;
mod mask;
mod path;
mod render;
/// Renders a tree onto the pixmap.
///
/// `transform` will be used as a root transform.
/// Can be used to position SVG inside the `pixmap`.
///
/// The produced content is in the sRGB color space.
pub fn render(
tree: &usvg::Tree,
transform: tiny_skia::Transform,
pixmap: &mut tiny_skia::PixmapMut,
) {
let target_size = tiny_skia::IntSize::from_wh(pixmap.width(), pixmap.height()).unwrap();
let max_bbox = tiny_skia::IntRect::from_xywh(
-(target_size.width() as i32) * 2,
-(target_size.height() as i32) * 2,
target_size.width() * 5,
target_size.height() * 5,
)
.unwrap();
let ctx = render::Context { max_bbox };
render::render_nodes(tree.root(), &ctx, transform, pixmap);
}
/// Renders a node onto the pixmap.
///
/// `transform` will be used as a root transform.
/// Can be used to position SVG inside the `pixmap`.
///
/// The expected pixmap size can be retrieved from `usvg::Node::abs_layer_bounding_box()`.
///
/// Returns `None` when `node` has a zero size.
///
/// The produced content is in the sRGB color space.
pub fn render_node(
node: &usvg::Node,
mut transform: tiny_skia::Transform,
pixmap: &mut tiny_skia::PixmapMut,
) -> Option<()> {
let bbox = node.abs_layer_bounding_box()?;
let target_size = tiny_skia::IntSize::from_wh(pixmap.width(), pixmap.height()).unwrap();
let max_bbox = tiny_skia::IntRect::from_xywh(
-(target_size.width() as i32) * 2,
-(target_size.height() as i32) * 2,
target_size.width() * 5,
target_size.height() * 5,
)
.unwrap();
transform = transform.pre_translate(-bbox.x(), -bbox.y());
let ctx = render::Context { max_bbox };
render::render_node(node, &ctx, transform, pixmap);
Some(())
}
pub(crate) trait OptionLog {
fn log_none(self, f: F) -> Self;
}
impl OptionLog for Option {
#[inline]
fn log_none(self, f: F) -> Self {
self.or_else(|| {
f();
None
})
}
}
================================================
FILE: crates/resvg/src/main.rs
================================================
// Copyright 2020 the Resvg Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
#![allow(clippy::uninlined_format_args)]
use std::path;
use std::sync::Arc;
use usvg::fontdb;
fn main() {
if let Err(e) = process() {
eprintln!("Error: {}.", e);
std::process::exit(1);
}
}
fn timed(perf: bool, name: &str, mut f: F) -> T
where
F: FnMut() -> T,
{
let now = std::time::Instant::now();
let result = f();
if perf {
let elapsed = now.elapsed().as_micros() as f64 / 1000.0;
println!("{}: {:.2}ms", name, elapsed);
}
result
}
fn process() -> Result<(), String> {
let mut args = match parse_args() {
Ok(args) => args,
Err(e) => {
println!("{}", HELP);
return Err(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) {
if let Ok(()) = log::set_logger(&LOGGER) {
log::set_max_level(log::LevelFilter::Warn);
}
}
let mut svg_data = timed(args.perf, "Reading", || -> Result, &str> {
if let InputFrom::File(ref file) = args.in_svg {
std::fs::read(file).map_err(|_| "failed to open the provided file")
} else {
use std::io::Read;
let mut buf = Vec::new();
let stdin = std::io::stdin();
let mut handle = stdin.lock();
handle
.read_to_end(&mut buf)
.map_err(|_| "failed to read stdin")?;
Ok(buf)
}
})?;
if svg_data.starts_with(&[0x1f, 0x8b]) {
svg_data = timed(args.perf, "SVGZ Decoding", || {
usvg::decompress_svgz(&svg_data).map_err(|e| e.to_string())
})?;
};
let svg_string = std::str::from_utf8(&svg_data)
.map_err(|_| "provided data has not an UTF-8 encoding".to_string())?;
let xml_tree = timed(args.perf, "XML Parsing", || {
let xml_opt = usvg::roxmltree::ParsingOptions {
allow_dtd: true,
..Default::default()
};
usvg::roxmltree::Document::parse_with_options(svg_string, xml_opt)
.map_err(|e| e.to_string())
})?;
// fontdb initialization is pretty expensive, so perform it only when needed.
let has_text_nodes = xml_tree
.descendants()
.any(|n| n.has_tag_name(("http://www.w3.org/2000/svg", "text")));
if has_text_nodes {
timed(args.perf, "FontDB", || {
load_fonts(&args.raw_args, args.usvg.fontdb_mut());
});
}
let tree = timed(args.perf, "SVG Parsing", || {
usvg::Tree::from_xmltree(&xml_tree, &args.usvg).map_err(|e| e.to_string())
})?;
if args.query_all {
return query_all(&tree);
}
// Render.
let img = render_svg(&args, &tree)?;
match args.out_png.unwrap() {
OutputTo::Stdout => {
use std::io::Write;
let buf = img.encode_png().map_err(|e| e.to_string())?;
std::io::stdout().write_all(&buf).unwrap();
}
OutputTo::File(ref file) => {
timed(args.perf, "Saving", || {
img.save_png(file).map_err(|e| e.to_string())
})?;
}
};
Ok(())
}
const HELP: &str = "\
resvg is an SVG rendering application.
USAGE:
resvg [OPTIONS] # from file to file
resvg [OPTIONS] -c # from file to stdout
resvg [OPTIONS] - # from stdin to file
resvg [OPTIONS] - -c # from stdin to stdout
resvg in.svg out.png
resvg -z 4 in.svg out.png
resvg --query-all in.svg
OPTIONS:
--help Prints this help
-V, --version Prints version
-c Prints the output PNG to the stdout
-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 (inclusive)]
--background COLOR Sets the background color
Examples: red, #fff, #fff000
--stylesheet PATH Inject a stylesheet that should be used when resolving
CSS attributes.
--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, smooth, high-quality, crisp-edges, pixelated]
--resources-dir DIR Sets a directory that will be used during
relative paths resolving.
Expected to be the same as the directory that
contains the SVG file, but can be set to any.
[default: input file directory]
--font-family FAMILY Sets the default font family that will be
used when no 'font-family' is present
[default: Times New Roman]
--font-size SIZE Sets the default font size that will be
used when no 'font-size' is present
[default: 12] [possible values: 1..192 (inclusive)]
--serif-family FAMILY Sets the 'serif' font family
[default: Times New Roman]
--sans-serif-family FAMILY Sets the 'sans-serif' font family
[default: Arial]
--cursive-family FAMILY Sets the 'cursive' font family
[default: Comic Sans MS]
--fantasy-family FAMILY Sets the 'fantasy' font family
[default: Impact]
--monospace-family FAMILY Sets the 'monospace' font family
[default: Courier New]
--use-font-file PATH Load a specified font file into the fonts database.
Will be used during text to path conversion.
This option can be set multiple times
--use-fonts-dir PATH Loads all fonts from the specified directory
into the fonts database.
Will be used during text to path conversion.
This option can be set multiple times
--skip-system-fonts Disables system fonts loading.
You should add some fonts manually using
--use-font-file and/or --use-fonts-dir
Otherwise, text elements will not be processes
--list-fonts Lists successfully loaded font faces.
Useful for debugging
--query-all Queries all valid SVG ids with bounding boxes
--export-id ID Renders an object only with a specified ID
--export-area-page Use an image size instead of an object size during ID exporting
--export-area-drawing Use drawing's tight bounding box instead of image size.
Used during normal rendering and not during --export-id
--perf Prints performance stats
--quiet Disables warnings
ARGS:
Input file
Output file
";
#[derive(Debug)]
struct CliArgs {
width: Option,
height: Option,
zoom: Option,
dpi: u32,
background: Option,
languages: Vec,
shape_rendering: usvg::ShapeRendering,
text_rendering: usvg::TextRendering,
image_rendering: usvg::ImageRendering,
resources_dir: Option,
font_family: Option,
font_size: u32,
serif_family: Option,
sans_serif_family: Option,
cursive_family: Option,
fantasy_family: Option,
monospace_family: Option,
font_files: Vec,
font_dirs: Vec,
skip_system_fonts: bool,
list_fonts: bool,
style_sheet: Option,
query_all: bool,
export_id: Option,
export_area_page: bool,
export_area_drawing: bool,
perf: bool,
quiet: bool,
input: Option,
output: Option,
}
fn collect_args() -> Result {
let mut input = pico_args::Arguments::from_env();
if input.contains("--help") {
print!("{}", HELP);
std::process::exit(0);
}
if input.contains(["-V", "--version"]) {
println!("{}", env!("CARGO_PKG_VERSION"));
std::process::exit(0);
}
Ok(CliArgs {
width: input.opt_value_from_fn(["-w", "--width"], parse_length)?,
height: input.opt_value_from_fn(["-h", "--height"], parse_length)?,
zoom: input.opt_value_from_fn(["-z", "--zoom"], parse_zoom)?,
dpi: input.opt_value_from_fn("--dpi", parse_dpi)?.unwrap_or(96),
background: input.opt_value_from_str("--background")?,
languages: input
.opt_value_from_fn("--languages", parse_languages)?
.unwrap_or_else(|| vec!["en".to_string()]), // TODO: use system language
shape_rendering: input
.opt_value_from_str("--shape-rendering")?
.unwrap_or_default(),
text_rendering: input
.opt_value_from_str("--text-rendering")?
.unwrap_or_default(),
image_rendering: input
.opt_value_from_str("--image-rendering")?
.unwrap_or_default(),
resources_dir: input
.opt_value_from_str("--resources-dir")
.unwrap_or_default(),
font_family: input.opt_value_from_str("--font-family")?,
font_size: input
.opt_value_from_fn("--font-size", parse_font_size)?
.unwrap_or(12),
serif_family: input.opt_value_from_str("--serif-family")?,
sans_serif_family: input.opt_value_from_str("--sans-serif-family")?,
cursive_family: input.opt_value_from_str("--cursive-family")?,
fantasy_family: input.opt_value_from_str("--fantasy-family")?,
monospace_family: input.opt_value_from_str("--monospace-family")?,
font_files: input.values_from_str("--use-font-file")?,
font_dirs: input.values_from_str("--use-fonts-dir")?,
skip_system_fonts: input.contains("--skip-system-fonts"),
list_fonts: input.contains("--list-fonts"),
query_all: input.contains("--query-all"),
export_id: input.opt_value_from_str("--export-id")?,
export_area_page: input.contains("--export-area-page"),
export_area_drawing: input.contains("--export-area-drawing"),
style_sheet: input.opt_value_from_str("--stylesheet").unwrap_or_default(),
perf: input.contains("--perf"),
quiet: input.contains("--quiet"),
input: input.opt_free_from_str()?,
output: input.opt_free_from_str()?,
})
}
fn parse_dpi(s: &str) -> Result {
let n: u32 = s.parse().map_err(|_| "invalid number")?;
if (10..=4000).contains(&n) {
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)
}
#[derive(Clone, PartialEq, Debug)]
enum InputFrom {
Stdin,
File(path::PathBuf),
}
#[derive(Clone, PartialEq, Debug)]
enum OutputTo {
Stdout,
File(path::PathBuf),
}
#[derive(Clone, Copy, PartialEq, Debug)]
enum FitTo {
/// Keep original size.
Original,
/// Scale to width.
Width(u32),
/// Scale to height.
Height(u32),
/// Scale to size.
Size(u32, u32),
/// Zoom by factor.
Zoom(f32),
}
impl FitTo {
fn fit_to_size(&self, size: tiny_skia::IntSize) -> Option {
match *self {
FitTo::Original => Some(size),
FitTo::Width(w) => size.scale_to_width(w),
FitTo::Height(h) => size.scale_to_height(h),
FitTo::Size(w, h) => tiny_skia::IntSize::from_wh(w, h).map(|s| size.scale_to(s)),
FitTo::Zoom(z) => size.scale_by(z),
}
}
fn fit_to_transform(&self, size: tiny_skia::IntSize) -> tiny_skia::Transform {
let size1 = size.to_size();
let size2 = match self.fit_to_size(size) {
Some(v) => v.to_size(),
None => return tiny_skia::Transform::default(),
};
tiny_skia::Transform::from_scale(
size2.width() / size1.width(),
size2.height() / size1.height(),
)
}
}
fn list_fonts(args: &CliArgs) {
let mut fontdb = fontdb::Database::new();
load_fonts(args, &mut fontdb);
use fontdb::Family;
println!("serif: {}", fontdb.family_name(&Family::Serif));
println!("sans-serif: {}", fontdb.family_name(&Family::SansSerif));
println!("cursive: {}", fontdb.family_name(&Family::Cursive));
println!("fantasy: {}", fontdb.family_name(&Family::Fantasy));
println!("monospace: {}", fontdb.family_name(&Family::Monospace));
for face in fontdb.faces() {
if let fontdb::Source::File(path) = &face.source {
let families: Vec<_> = face
.families
.iter()
.map(|f| format!("{} ({}, {})", f.0, f.1.primary_language(), f.1.region()))
.collect();
println!(
"{}: '{}', {}, {:?}, {:?}, {:?}",
path.display(),
families.join("', '"),
face.index,
face.style,
face.weight.0,
face.stretch
);
}
}
}
struct Args {
in_svg: InputFrom,
out_png: Option,
query_all: bool,
export_id: Option,
export_area_page: bool,
export_area_drawing: bool,
perf: bool,
quiet: bool,
usvg: usvg::Options<'static>,
fit_to: FitTo,
background: Option,
raw_args: CliArgs, // TODO: find a better way
}
fn parse_args() -> Result {
let args = collect_args().map_err(|e| e.to_string())?;
if args.list_fonts {
list_fonts(&args);
std::process::exit(0);
}
let (in_svg, out_png) = {
let in_svg = match args.input {
Some(ref v) => v,
None => return Err("input file is missing".to_string()),
};
let svg_from = if in_svg == "-" {
InputFrom::Stdin
} else if in_svg == "-c" {
return Err("-c should be set after input".to_string());
} else {
InputFrom::File(in_svg.into())
};
let out_png = if let Some(ref out_png) = args.output {
if out_png == "-c" {
Some(OutputTo::Stdout)
} else {
Some(OutputTo::File(out_png.into()))
}
} else {
None
};
(svg_from, out_png)
};
if !args.query_all && out_png.is_none() {
return Err(" must be set".to_string());
}
if in_svg == InputFrom::Stdin && args.resources_dir.is_none() {
eprintln!("Warning: Make sure to set --resources-dir when reading SVG from stdin.");
}
if args.export_area_page && args.export_id.is_none() {
eprintln!("Warning: --export-area-page has no effect without --export-id.");
}
if args.export_area_drawing && args.export_id.is_some() {
eprintln!("Warning: --export-area-drawing has no effect when --export-id is set.");
}
let export_id = args.export_id.as_ref().map(|v| v.to_string());
let mut fit_to = FitTo::Original;
let mut default_size = usvg::Size::from_wh(100.0, 100.0).unwrap();
if let (Some(w), Some(h)) = (args.width, args.height) {
default_size = usvg::Size::from_wh(w as f32, h as f32).unwrap();
fit_to = FitTo::Size(w, h);
} else if let Some(w) = args.width {
default_size = usvg::Size::from_wh(w as f32, 100.0).unwrap();
fit_to = FitTo::Width(w);
} else if let Some(h) = args.height {
default_size = usvg::Size::from_wh(100.0, h as f32).unwrap();
fit_to = FitTo::Height(h);
} else if let Some(z) = args.zoom {
fit_to = FitTo::Zoom(z);
}
let resources_dir = match args.resources_dir {
Some(ref v) => Some(v.clone()),
None => {
if let InputFrom::File(ref input) = in_svg {
// Get input file absolute directory.
std::fs::canonicalize(input)
.ok()
.and_then(|p| p.parent().map(|p| p.to_path_buf()))
} else {
None
}
}
};
let style_sheet = match args.style_sheet.as_ref() {
Some(p) => Some(
std::fs::read(p)
.ok()
.and_then(|s| std::str::from_utf8(&s).ok().map(|s| s.to_string()))
.ok_or("failed to read stylesheet".to_string())?,
),
None => None,
};
let usvg = usvg::Options {
resources_dir,
dpi: args.dpi as f32,
font_family: args
.font_family
.clone()
.unwrap_or_else(|| "Times New Roman".to_string()),
font_size: args.font_size as f32,
languages: args.languages.clone(),
shape_rendering: args.shape_rendering,
text_rendering: args.text_rendering,
image_rendering: args.image_rendering,
default_size,
image_href_resolver: usvg::ImageHrefResolver::default(),
font_resolver: usvg::FontResolver::default(),
fontdb: Arc::new(fontdb::Database::new()),
style_sheet,
};
Ok(Args {
in_svg,
out_png,
query_all: args.query_all,
export_id,
export_area_page: args.export_area_page,
export_area_drawing: args.export_area_drawing,
perf: args.perf,
quiet: args.quiet,
usvg,
fit_to,
background: args.background,
raw_args: args,
})
}
fn load_fonts(args: &CliArgs, fontdb: &mut fontdb::Database) {
if !args.skip_system_fonts {
fontdb.load_system_fonts();
}
for path in &args.font_files {
if let Err(e) = fontdb.load_font_file(path) {
log::warn!("Failed to load '{}' cause {}.", path.display(), e);
}
}
for path in &args.font_dirs {
fontdb.load_fonts_dir(path);
}
fontdb.set_serif_family(args.serif_family.as_deref().unwrap_or("Times New Roman"));
fontdb.set_sans_serif_family(args.sans_serif_family.as_deref().unwrap_or("Arial"));
fontdb.set_cursive_family(args.cursive_family.as_deref().unwrap_or("Comic Sans MS"));
fontdb.set_fantasy_family(args.fantasy_family.as_deref().unwrap_or("Impact"));
fontdb.set_monospace_family(args.monospace_family.as_deref().unwrap_or("Courier New"));
}
fn query_all(tree: &usvg::Tree) -> Result<(), String> {
let count = query_all_impl(tree.root());
if count == 0 {
return Err("the file has no valid ID's".to_string());
}
Ok(())
}
fn query_all_impl(parent: &usvg::Group) -> usize {
let mut count = 0;
for node in parent.children() {
if node.id().is_empty() {
if let usvg::Node::Group(group) = node {
count += query_all_impl(group);
}
continue;
}
count += 1;
fn round_len(v: f32) -> f32 {
(v * 1000.0).round() / 1000.0
}
let bbox = node
.abs_layer_bounding_box()
.map(|r| r.to_rect())
.unwrap_or(node.abs_bounding_box());
println!(
"{},{},{},{},{}",
node.id(),
round_len(bbox.x()),
round_len(bbox.y()),
round_len(bbox.width()),
round_len(bbox.height())
);
if let usvg::Node::Group(group) = node {
count += query_all_impl(group);
}
}
count
}
fn render_svg(args: &Args, tree: &usvg::Tree) -> Result {
let now = std::time::Instant::now();
let img = if let Some(ref id) = args.export_id {
let node = match tree.node_by_id(id) {
Some(node) => node,
None => return Err(format!("SVG doesn't have '{}' ID", id)),
};
let bbox = node.abs_layer_bounding_box().ok_or("node has zero size")?;
let size = args
.fit_to
.fit_to_size(bbox.size().to_int_size())
.ok_or("target size is zero")?;
// Pixmap's width is limited by i32::MAX/4, we handle the creation error.
let mut pixmap =
tiny_skia::Pixmap::new(size.width(), size.height()).ok_or("cannot create pixmap")?;
if !args.export_area_page {
if let Some(background) = args.background {
pixmap.fill(svg_to_skia_color(background));
}
}
let ts = args.fit_to.fit_to_transform(tree.size().to_int_size());
resvg::render_node(node, ts, &mut pixmap.as_mut());
if args.export_area_page {
// TODO: add offset support to render_node() so we would not need an additional pixmap
let size = args
.fit_to
.fit_to_size(tree.size().to_int_size())
.ok_or("target size is zero")?;
// Pixmap's width is limited by i32::MAX/4, we handle the creation error.
let mut page_pixmap = tiny_skia::Pixmap::new(size.width(), size.height())
.ok_or("cannot create pixmap")?;
if let Some(background) = args.background {
page_pixmap.fill(svg_to_skia_color(background));
}
page_pixmap.draw_pixmap(
bbox.x() as i32,
bbox.y() as i32,
pixmap.as_ref(),
&tiny_skia::PixmapPaint::default(),
tiny_skia::Transform::default(),
None,
);
page_pixmap
} else {
pixmap
}
} else {
let size = args
.fit_to
.fit_to_size(tree.size().to_int_size())
.ok_or("target size is zero")?;
// Pixmap's width is limited by i32::MAX/4, we handle the creation error.
let mut pixmap =
tiny_skia::Pixmap::new(size.width(), size.height()).ok_or("cannot create pixmap")?;
if let Some(background) = args.background {
pixmap.fill(svg_to_skia_color(background));
}
let ts = args.fit_to.fit_to_transform(tree.size().to_int_size());
resvg::render(tree, ts, &mut pixmap.as_mut());
if args.export_area_drawing {
trim_pixmap(tree, ts, &pixmap).unwrap_or(pixmap)
} else {
pixmap
}
};
if args.perf {
let elapsed = now.elapsed().as_micros() as f64 / 1000.0;
println!("Rendering: {:.2}ms", elapsed);
}
Ok(img)
}
fn trim_pixmap(
tree: &usvg::Tree,
transform: tiny_skia::Transform,
pixmap: &tiny_skia::Pixmap,
) -> Option {
let content_area = tree.root().layer_bounding_box();
let limit = tiny_skia::IntRect::from_xywh(0, 0, pixmap.width(), pixmap.height()).unwrap();
let content_area = content_area.transform(transform)?.to_int_rect();
let content_area = fit_to_rect(content_area, limit);
let content_area = tiny_skia::IntRect::from_xywh(
content_area.x(),
content_area.y(),
content_area.width(),
content_area.height(),
)?;
pixmap.clone_rect(content_area)
}
/// Fits the current rect into the specified bounds.
fn fit_to_rect(r: tiny_skia::IntRect, bounds: tiny_skia::IntRect) -> tiny_skia::IntRect {
let mut left = r.left();
if left < bounds.left() {
left = bounds.left();
}
let mut top = r.top();
if top < bounds.top() {
top = bounds.top();
}
let mut right = r.right();
if right > bounds.right() {
right = bounds.right();
}
let mut bottom = r.bottom();
if bottom > bounds.bottom() {
bottom = bounds.bottom();
}
tiny_skia::IntRect::from_ltrb(left, top, right, bottom).unwrap()
}
fn svg_to_skia_color(color: svgtypes::Color) -> tiny_skia::Color {
tiny_skia::Color::from_rgba8(color.red, color.green, color.blue, color.alpha)
}
/// A simple stderr logger.
static LOGGER: SimpleLogger = SimpleLogger;
struct SimpleLogger;
impl log::Log for SimpleLogger {
fn enabled(&self, metadata: &log::Metadata) -> bool {
metadata.level() <= log::LevelFilter::Warn
}
fn log(&self, record: &log::Record) {
if self.enabled(record.metadata()) {
let target = if !record.target().is_empty() {
record.target()
} else {
record.module_path().unwrap_or_default()
};
let line = record.line().unwrap_or(0);
let args = record.args();
match record.level() {
log::Level::Error => eprintln!("Error (in {}:{}): {}", target, line, args),
log::Level::Warn => eprintln!("Warning (in {}:{}): {}", target, line, args),
log::Level::Info => eprintln!("Info (in {}:{}): {}", target, line, args),
log::Level::Debug => eprintln!("Debug (in {}:{}): {}", target, line, args),
log::Level::Trace => eprintln!("Trace (in {}:{}): {}", target, line, args),
}
}
}
fn flush(&self) {}
}
================================================
FILE: crates/resvg/src/mask.rs
================================================
// Copyright 2019 the Resvg Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
use crate::render::Context;
pub fn apply(
mask: &usvg::Mask,
ctx: &Context,
transform: tiny_skia::Transform,
pixmap: &mut tiny_skia::Pixmap,
) {
if mask.root().children().is_empty() {
pixmap.fill(tiny_skia::Color::TRANSPARENT);
return;
}
let mut mask_pixmap = tiny_skia::Pixmap::new(pixmap.width(), pixmap.height()).unwrap();
{
// TODO: only when needed
// Mask has to be clipped by mask.region
let mut alpha_mask = tiny_skia::Mask::new(pixmap.width(), pixmap.height()).unwrap();
alpha_mask.fill_path(
&tiny_skia::PathBuilder::from_rect(mask.rect().to_rect()),
tiny_skia::FillRule::Winding,
true,
transform,
);
crate::render::render_nodes(mask.root(), ctx, transform, &mut mask_pixmap.as_mut());
mask_pixmap.apply_mask(&alpha_mask);
}
if let Some(mask) = mask.mask() {
self::apply(mask, ctx, transform, pixmap);
}
let mask_type = match mask.kind() {
usvg::MaskType::Luminance => tiny_skia::MaskType::Luminance,
usvg::MaskType::Alpha => tiny_skia::MaskType::Alpha,
};
let mask = tiny_skia::Mask::from_pixmap(mask_pixmap.as_ref(), mask_type);
pixmap.apply_mask(&mask);
}
================================================
FILE: crates/resvg/src/path.rs
================================================
// Copyright 2019 the Resvg Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
use crate::render::Context;
pub fn render(
path: &usvg::Path,
blend_mode: tiny_skia::BlendMode,
ctx: &Context,
transform: tiny_skia::Transform,
pixmap: &mut tiny_skia::PixmapMut,
) {
if !path.is_visible() {
return;
}
if path.paint_order() == usvg::PaintOrder::FillAndStroke {
fill_path(path, blend_mode, ctx, transform, pixmap);
stroke_path(path, blend_mode, ctx, transform, pixmap);
} else {
stroke_path(path, blend_mode, ctx, transform, pixmap);
fill_path(path, blend_mode, ctx, transform, pixmap);
}
}
pub fn fill_path(
path: &usvg::Path,
blend_mode: tiny_skia::BlendMode,
ctx: &Context,
transform: tiny_skia::Transform,
pixmap: &mut tiny_skia::PixmapMut,
) -> Option<()> {
let fill = path.fill()?;
// Horizontal and vertical lines cannot be filled. Skip.
if path.data().bounds().width() == 0.0 || path.data().bounds().height() == 0.0 {
return None;
}
let rule = match fill.rule() {
usvg::FillRule::NonZero => tiny_skia::FillRule::Winding,
usvg::FillRule::EvenOdd => tiny_skia::FillRule::EvenOdd,
};
let pattern_pixmap;
let mut paint = tiny_skia::Paint::default();
match fill.paint() {
usvg::Paint::Color(c) => {
paint.set_color_rgba8(c.red, c.green, c.blue, fill.opacity().to_u8());
}
usvg::Paint::LinearGradient(lg) => {
paint.shader = convert_linear_gradient(lg, fill.opacity())?;
}
usvg::Paint::RadialGradient(rg) => {
paint.shader = convert_radial_gradient(rg, fill.opacity())?;
}
usvg::Paint::Pattern(pattern) => {
let (patt_pix, patt_ts) = render_pattern_pixmap(pattern, ctx, transform)?;
pattern_pixmap = patt_pix;
paint.shader = tiny_skia::Pattern::new(
pattern_pixmap.as_ref(),
tiny_skia::SpreadMode::Repeat,
tiny_skia::FilterQuality::Bicubic,
fill.opacity().get(),
patt_ts,
);
}
}
paint.anti_alias = path.rendering_mode().use_shape_antialiasing();
paint.blend_mode = blend_mode;
pixmap.fill_path(path.data(), &paint, rule, transform, None);
Some(())
}
fn stroke_path(
path: &usvg::Path,
blend_mode: tiny_skia::BlendMode,
ctx: &Context,
transform: tiny_skia::Transform,
pixmap: &mut tiny_skia::PixmapMut,
) -> Option<()> {
let stroke = path.stroke()?;
let pattern_pixmap;
let mut paint = tiny_skia::Paint::default();
match stroke.paint() {
usvg::Paint::Color(c) => {
paint.set_color_rgba8(c.red, c.green, c.blue, stroke.opacity().to_u8());
}
usvg::Paint::LinearGradient(lg) => {
paint.shader = convert_linear_gradient(lg, stroke.opacity())?;
}
usvg::Paint::RadialGradient(rg) => {
paint.shader = convert_radial_gradient(rg, stroke.opacity())?;
}
usvg::Paint::Pattern(pattern) => {
let (patt_pix, patt_ts) = render_pattern_pixmap(pattern, ctx, transform)?;
pattern_pixmap = patt_pix;
paint.shader = tiny_skia::Pattern::new(
pattern_pixmap.as_ref(),
tiny_skia::SpreadMode::Repeat,
tiny_skia::FilterQuality::Bicubic,
stroke.opacity().get(),
patt_ts,
);
}
}
paint.anti_alias = path.rendering_mode().use_shape_antialiasing();
paint.blend_mode = blend_mode;
pixmap.stroke_path(path.data(), &paint, &stroke.to_tiny_skia(), transform, None);
Some(())
}
fn convert_linear_gradient(
gradient: &usvg::LinearGradient,
opacity: usvg::Opacity,
) -> Option> {
let (mode, points) = convert_base_gradient(gradient, opacity)?;
let shader = tiny_skia::LinearGradient::new(
(gradient.x1(), gradient.y1()).into(),
(gradient.x2(), gradient.y2()).into(),
points,
mode,
gradient.transform(),
)?;
Some(shader)
}
fn convert_radial_gradient(
gradient: &usvg::RadialGradient,
opacity: usvg::Opacity,
) -> Option> {
let (mode, points) = convert_base_gradient(gradient, opacity)?;
let shader = tiny_skia::RadialGradient::new(
(gradient.fx(), gradient.fy()).into(),
gradient.fr().get(),
(gradient.cx(), gradient.cy()).into(),
gradient.r().get(),
points,
mode,
gradient.transform(),
)?;
Some(shader)
}
fn convert_base_gradient(
gradient: &usvg::BaseGradient,
opacity: usvg::Opacity,
) -> Option<(tiny_skia::SpreadMode, Vec)> {
let mode = match gradient.spread_method() {
usvg::SpreadMethod::Pad => tiny_skia::SpreadMode::Pad,
usvg::SpreadMethod::Reflect => tiny_skia::SpreadMode::Reflect,
usvg::SpreadMethod::Repeat => tiny_skia::SpreadMode::Repeat,
};
let mut points = Vec::with_capacity(gradient.stops().len());
for stop in gradient.stops() {
let alpha = stop.opacity() * opacity;
let color = tiny_skia::Color::from_rgba8(
stop.color().red,
stop.color().green,
stop.color().blue,
alpha.to_u8(),
);
points.push(tiny_skia::GradientStop::new(stop.offset().get(), color));
}
Some((mode, points))
}
fn render_pattern_pixmap(
pattern: &usvg::Pattern,
ctx: &Context,
transform: tiny_skia::Transform,
) -> Option<(tiny_skia::Pixmap, tiny_skia::Transform)> {
let (sx, sy) = {
let ts2 = transform.pre_concat(pattern.transform());
ts2.get_scale()
};
let rect = pattern.rect();
let img_size = tiny_skia::IntSize::from_wh(
(rect.width() * sx).round() as u32,
(rect.height() * sy).round() as u32,
)?;
let mut pixmap = tiny_skia::Pixmap::new(img_size.width(), img_size.height())?;
let transform = tiny_skia::Transform::from_scale(sx, sy);
crate::render::render_nodes(pattern.root(), ctx, transform, &mut pixmap.as_mut());
let mut ts = tiny_skia::Transform::default();
ts = ts.pre_concat(pattern.transform());
ts = ts.pre_translate(rect.x(), rect.y());
ts = ts.pre_scale(1.0 / sx, 1.0 / sy);
Some((pixmap, ts))
}
================================================
FILE: crates/resvg/src/render.rs
================================================
// Copyright 2018 the Resvg Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
use crate::OptionLog;
pub struct Context {
pub max_bbox: tiny_skia::IntRect,
}
pub fn render_nodes(
parent: &usvg::Group,
ctx: &Context,
transform: tiny_skia::Transform,
pixmap: &mut tiny_skia::PixmapMut,
) {
for node in parent.children() {
render_node(node, ctx, transform, pixmap);
}
}
pub fn render_node(
node: &usvg::Node,
ctx: &Context,
transform: tiny_skia::Transform,
pixmap: &mut tiny_skia::PixmapMut,
) {
match node {
usvg::Node::Group(group) => {
render_group(group, ctx, transform, pixmap);
}
usvg::Node::Path(path) => {
crate::path::render(
path,
tiny_skia::BlendMode::SourceOver,
ctx,
transform,
pixmap,
);
}
usvg::Node::Image(image) => {
crate::image::render(image, transform, pixmap);
}
usvg::Node::Text(text) => {
render_group(text.flattened(), ctx, transform, pixmap);
}
}
}
fn render_group(
group: &usvg::Group,
ctx: &Context,
transform: tiny_skia::Transform,
pixmap: &mut tiny_skia::PixmapMut,
) -> Option<()> {
let transform = transform.pre_concat(group.transform());
if !group.should_isolate() {
render_nodes(group, ctx, transform, pixmap);
return Some(());
}
let bbox = group.layer_bounding_box().transform(transform)?;
let mut ibbox = if group.filters().is_empty() {
// Convert group bbox into an integer one, expanding each side outwards by 2px
// to make sure that anti-aliased pixels would not be clipped.
tiny_skia::IntRect::from_xywh(
(bbox.x().floor() as i32).checked_sub(2)?,
(bbox.y().floor() as i32).checked_sub(2)?,
(bbox.width().ceil() as u32).checked_add(4)?,
(bbox.height().ceil() as u32).checked_add(4)?,
)?
} else {
// The bounding box for groups with filters is special and should not be expanded by 2px,
// because it's already acting as a clipping region.
let bbox = bbox.to_int_rect();
// Make sure our filter region is not bigger than 4x the canvas size.
// This is required mainly to prevent huge filter regions that would tank the performance.
// It should not affect the final result in any way.
crate::geom::fit_to_rect(bbox, ctx.max_bbox)?
};
// Make sure our layer is not bigger than 4x the canvas size.
// This is required to prevent huge layers.
if group.filters().is_empty() {
ibbox = crate::geom::fit_to_rect(ibbox, ctx.max_bbox)?;
}
let shift_ts = {
// Original shift.
let mut dx = bbox.x();
let mut dy = bbox.y();
// Account for subpixel positioned layers.
dx -= bbox.x() - ibbox.x() as f32;
dy -= bbox.y() - ibbox.y() as f32;
tiny_skia::Transform::from_translate(-dx, -dy)
};
let transform = shift_ts.pre_concat(transform);
let mut sub_pixmap = tiny_skia::Pixmap::new(ibbox.width(), ibbox.height())
.log_none(|| log::warn!("Failed to allocate a group layer for: {:?}.", ibbox))?;
render_nodes(group, ctx, transform, &mut sub_pixmap.as_mut());
if !group.filters().is_empty() {
for filter in group.filters() {
crate::filter::apply(filter, transform, &mut sub_pixmap);
}
}
if let Some(clip_path) = group.clip_path() {
crate::clip::apply(clip_path, transform, &mut sub_pixmap);
}
if let Some(mask) = group.mask() {
crate::mask::apply(mask, ctx, transform, &mut sub_pixmap);
}
let paint = tiny_skia::PixmapPaint {
opacity: group.opacity().get(),
blend_mode: convert_blend_mode(group.blend_mode()),
quality: tiny_skia::FilterQuality::Nearest,
};
pixmap.draw_pixmap(
ibbox.x(),
ibbox.y(),
sub_pixmap.as_ref(),
&paint,
tiny_skia::Transform::identity(),
None,
);
Some(())
}
pub fn convert_blend_mode(mode: usvg::BlendMode) -> tiny_skia::BlendMode {
match mode {
usvg::BlendMode::Normal => tiny_skia::BlendMode::SourceOver,
usvg::BlendMode::Multiply => tiny_skia::BlendMode::Multiply,
usvg::BlendMode::Screen => tiny_skia::BlendMode::Screen,
usvg::BlendMode::Overlay => tiny_skia::BlendMode::Overlay,
usvg::BlendMode::Darken => tiny_skia::BlendMode::Darken,
usvg::BlendMode::Lighten => tiny_skia::BlendMode::Lighten,
usvg::BlendMode::ColorDodge => tiny_skia::BlendMode::ColorDodge,
usvg::BlendMode::ColorBurn => tiny_skia::BlendMode::ColorBurn,
usvg::BlendMode::HardLight => tiny_skia::BlendMode::HardLight,
usvg::BlendMode::SoftLight => tiny_skia::BlendMode::SoftLight,
usvg::BlendMode::Difference => tiny_skia::BlendMode::Difference,
usvg::BlendMode::Exclusion => tiny_skia::BlendMode::Exclusion,
usvg::BlendMode::Hue => tiny_skia::BlendMode::Hue,
usvg::BlendMode::Saturation => tiny_skia::BlendMode::Saturation,
usvg::BlendMode::Color => tiny_skia::BlendMode::Color,
usvg::BlendMode::Luminosity => tiny_skia::BlendMode::Luminosity,
}
}
================================================
FILE: crates/resvg/tests/README.md
================================================
# SVG tests
This directory contains a collection of SVG files used during *resvg* regression testing.
## Adding a new test
### Create an SVG file
We are using SVG files with a fixed, 200x200 viewbox for all tests.
Here is a test file template:
```xml
```
General requirements:
1. Each test must test only a single issue.
1. Each element must have an `id` attribute.
1. The `title` value must be unique and shorter than 60 characters.
Newlines are not allowed.
1. Each line in an XML file should be less than 100 characters.
1. No trailing spaces.
1. A single trailing newline.
1. UTF-8 only.
You could use the `check.py` script to automatically check those requirements.
### Render PNG
After the SVG test is finished, you should render it using resvg:
```sh
cargo run --release -- \
--width 300 \
--skip-system-fonts \
--use-fonts-dir 'tests/fonts' \
--font-family 'Noto Sans' \
--serif-family 'Noto Serif' \
--sans-serif-family 'Noto Sans' \
--cursive-family 'Yellowtail' \
--fantasy-family 'Sedgwick Ave Display' \
--monospace-family 'Noto Mono' \
in.svg out.png
```
(we are using 300px width to test scaling)
After that, you should optimize the resulting PNG using oxipng:
```sh
cargo install oxipng
oxipng -o 6 -Z out.png
```
And then place it into the `png` dir.
## resvg tests vs resvg-test-suite tests
resvg tests are stored in two repos: this one and in
[resvg-test-suite](https://github.com/linebender/resvg-test-suite).
Which can be a bit confusing.
`resvg-test-suite` is the source of truth. It contains the latest version of the tests
and intended to help people with writing SVG processing apps.
`resvg/tests/svg` directory contains the exact copy of `resvg-test-suite/svg`,
maybe a bit outdated at times.
The major difference is `png` directories. `resvg-test-suite/png` contains reference image.
This is how the SVG files should be rendered.
While `resvg/tests/png` contains PNGs rendered by the resvg itself
and used only for regression testing.
================================================
FILE: crates/resvg/tests/fonts/Amiri-LICENSE-OFL.txt
================================================
Copyright (c) 2010-2016, Khaled Hosny ()
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
================================================
FILE: crates/resvg/tests/fonts/CFF-and-SBIX-LICENSE-APACHE.txt
================================================
Copyright 2019 Simon Cozens. All rights reserved.
Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
================================================
FILE: crates/resvg/tests/fonts/MPLUS1p-LICENSE-OFL.txt
================================================
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
================================================
FILE: crates/resvg/tests/fonts/Noto-LICENSE-OFL.txt
================================================
This Font Software is licensed under the SIL Open Font License,
Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font
creation efforts of academic and linguistic communities, and to
provide a free and open framework in which fonts may be shared and
improved in partnership with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply to
any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software
components as distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to,
deleting, or substituting -- in part or in whole -- any of the
components of the Original Version, by changing formats or by porting
the Font Software to a new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed,
modify, redistribute, and sell modified and unmodified copies of the
Font Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components, in
Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the
corresponding Copyright Holder. This restriction only applies to the
primary font name as presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created using
the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
================================================
FILE: crates/resvg/tests/fonts/NotoColorEmojiCBDT-LICENSE_APACHE.txt
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: crates/resvg/tests/fonts/NotoZnamennyMusicalNotation-OFL.txt
================================================
Copyright 2023 The Noto Project Authors (https://github.com/notofonts/znamenny)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
================================================
FILE: crates/resvg/tests/fonts/README.md
================================================
How fonts were subsetted:
Twitter Color Emoji
1. Download: https://github.com/13rac1/twemoji-color-font/releases/download/v14.0.2/TwitterColorEmoji-SVGinOT-14.0.2.zip
2. Run `fonttools subset TwitterColorEmoji-SVGinOT.ttf --unicodes="U+1F601,U+1F980,U+1F3F3,U+FE0F,U+200D,U+1F308,U+1F600,U+1F603,U+1F90C,U+1F90F" --output-file=TwitterColorEmoji.subset.ttf`
Noto Color Emoji (CBDT)
1. Download: https://github.com/googlefonts/noto-emoji/blob/main/fonts/NotoColorEmoji.ttf
2. Run `fonttools subset NotoColorEmoji.ttf --unicodes="U+1F600" --output-file=NotoColorEmojiCBDT.subset.ttf`
Noto COLOR Emoji (COLRv1)
1. Download: https://fonts.google.com/noto/specimen/Noto+Color+Emoji
2. Run `fonttools subset NotoColorEmoji-Regular.ttf --unicodes="U+1F436,U+1F41D,U+1F313,U+1F973" --output-file=NotoColorEmojiCOLR.subset.ttf`
3. Run `fonttools ttx NotoColorEmojiCOLR.subset.ttf`
4. Go to the section and rename all instances of "Noto Color Emoji" to "Noto Color Emoji COLR" (so that
we can distinguish them from CBDT in tests).
5. Run `fonttools ttx -f NotoColorEmojiCOLR.subset.ttx`
Roboto Flex (Variable Font)
1. Download: https://github.com/googlefonts/roboto-flex/raw/main/fonts/RobotoFlex%5BGRAD%2CXOPQ%2CXTRA%2CYOPQ%2CYTAS%2CYTDE%2CYTFI%2CYTLC%2CYTUC%2Copsz%2Cslnt%2Cwdth%2Cwght%5D.ttf
2. Run `pyftsubset RobotoFlex*.ttf --unicodes="U+0020-007E" --layout-features='*' --output-file=RobotoFlex.subset.ttf`
3. Copy OFL license from https://github.com/googlefonts/roboto-flex/blob/main/OFL.txt
================================================
FILE: crates/resvg/tests/fonts/RobotoFlex-LICENSE-OFL.txt
================================================
Copyright 2011 The Roboto Flex Project Authors (https://github.com/googlefonts/roboto-flex)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
================================================
FILE: crates/resvg/tests/fonts/SedgwickAveDisplay-LICENSE-OFL.txt
================================================
Copyright 2017 The Sedgwick Ave Project Authors (https://github.com/googlefonts/sedgwickave)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
================================================
FILE: crates/resvg/tests/fonts/SourceSansPro-LICENSE-OFL.md
================================================
Copyright 2010-2018 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe in the United States and/or other countries.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
================================================
FILE: crates/resvg/tests/fonts/TwitterColorEmoji-LICENSE-MIT.txt
================================================
Applies to "EmojiOne SVGinOT Font" code only
Copyright (c) 2022 Brad Erickson
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.
================================================
FILE: crates/resvg/tests/fonts/Yellowtail-LICENSE-Apache2.txt
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: crates/resvg/tests/gen-tests.py
================================================
#!/usr/bin/env python3
import os
from pathlib import Path
IGNORE = [
'tests/filters/feMorphology/huge-radius', # will timeout on CI
'tests/structure/svg/negative-size', # invalid size
'tests/structure/svg/no-size', # invalid size
'tests/structure/svg/zero-size', # invalid size
'tests/structure/svg/not-UTF-8-encoding', # invalid encoding
# Produces slightly different output on some hardware.
# Not a bug, just a SIMD rounding difference.
'tests/paint-servers/radialGradient/focal-point-correction',
]
print('// Copyright 2020 the Resvg Authors')
print('// SPDX-License-Identifier: Apache-2.0 OR MIT')
print()
print('// This file is auto-generated by gen-tests.py')
print()
print('#![allow(non_snake_case)]')
print()
print('use crate::render;')
print()
files = sorted(list(Path('tests').rglob('*.svg')))
for file in files:
file = str(file).replace('.svg', '')
if file in IGNORE:
continue
fn_name = file.replace('tests/', '')
fn_name = fn_name.replace('/', '_')
fn_name = fn_name.replace('-', '_')
fn_name = fn_name.replace('=', '_eq_')
fn_name = fn_name.replace('.', '_')
fn_name = fn_name.replace('#', '')
print(f'#[test] fn {fn_name}() {{ assert_eq!(render("{file}"), 0); }}')
================================================
FILE: crates/resvg/tests/integration/extra.rs
================================================
// Copyright 2023 the Resvg Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
use crate::{render_extra, render_extra_with_scale, render_node};
#[test]
fn group_with_only_transform() {
assert_eq!(render_extra("extra/group-with-only-transform"), 0);
}
#[test]
fn subpixel_rect_position() {
assert_eq!(render_extra("extra/subpixel-rect-position"), 0);
}
#[test]
fn transformed_rect() {
assert_eq!(render_extra("extra/transformed-rect"), 0);
}
#[test]
fn hidden_element() {
assert_eq!(render_extra("extra/hidden-element"), 0);
}
#[test]
fn simple_stroke() {
assert_eq!(render_extra("extra/simple-stroke"), 0);
}
#[test]
fn fill_and_stroke() {
assert_eq!(render_extra("extra/fill-and-stroke"), 0);
}
#[test]
fn paint_order_stroke() {
assert_eq!(render_extra("extra/paint-order=stroke"), 0);
}
#[test]
fn stroke_linecap_square() {
assert_eq!(render_extra("extra/stroke-linecap=square"), 0);
}
#[test]
fn miter_join_with_acute_angle() {
assert_eq!(render_extra("extra/miter-join-with-acute-angle"), 0);
}
#[test]
fn horizontal_line() {
assert_eq!(render_extra("extra/horizontal-line"), 0);
}
#[test]
fn horizontal_line_no_stroke() {
assert_eq!(render_extra("extra/horizontal-line-no-stroke"), 0);
}
#[test]
fn filter_region_precision() {
assert_eq!(
render_extra_with_scale("extra/filter-region-precision", 10.0),
0
);
}
#[test]
fn translate_outside_viewbox() {
assert_eq!(render_extra("extra/translate-outside-viewbox"), 0);
}
#[test]
fn render_node_filter_on_empty_group() {
assert_eq!(render_node("extra/filter-on-empty-group", "g1"), 0);
}
#[test]
fn render_node_filter_with_transform_on_shape() {
assert_eq!(render_node("extra/filter-with-transform-on-shape", "g1"), 0);
}
================================================
FILE: crates/resvg/tests/integration/main.rs
================================================
// Copyright 2020 the Resvg Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
use once_cell::sync::Lazy;
use png::{BitDepth, ColorType, Encoder};
use rgb::{FromSlice, RGBA8, Rgba};
use std::cmp::max;
use std::fs::File;
use std::io::{BufWriter, Cursor};
use std::process::Command;
use std::sync::Arc;
use usvg::fontdb;
#[rustfmt::skip]
mod render;
mod extra;
const IMAGE_SIZE: u32 = 300;
static GLOBAL_FONTDB: Lazy> = Lazy::new(|| {
if let Ok(()) = log::set_logger(&LOGGER) {
log::set_max_level(log::LevelFilter::Warn);
}
let mut fontdb = fontdb::Database::new();
fontdb.load_fonts_dir("tests/fonts");
fontdb.set_serif_family("Noto Serif");
fontdb.set_sans_serif_family("Noto Sans");
fontdb.set_cursive_family("Yellowtail");
fontdb.set_fantasy_family("Sedgwick Ave Display");
fontdb.set_monospace_family("Noto Mono");
Arc::new(fontdb)
});
pub fn render(name: &str) -> usize {
render_inner(name, TestMode::Normal)
}
pub fn render_extra_with_scale(name: &str, scale: f32) -> usize {
render_inner(name, TestMode::Extra(scale))
}
pub fn render_extra(name: &str) -> usize {
render_extra_with_scale(name, 1.0)
}
pub fn render_node(name: &str, id: &str) -> usize {
render_inner(name, TestMode::Node(id))
}
pub fn render_inner(name: &str, test_mode: TestMode) -> usize {
let svg_path = format!("tests/{}.svg", name);
let png_path = format!("tests/{}.png", name);
let make_ref = std::env::var("MAKE_REF").is_ok();
let opt = usvg::Options {
fontdb: GLOBAL_FONTDB.clone(),
resources_dir: Some(
std::path::PathBuf::from(&svg_path)
.parent()
.unwrap()
.to_owned(),
),
..usvg::Options::default()
};
let tree = {
let svg_data = std::fs::read(&svg_path).unwrap();
usvg::Tree::from_data(&svg_data, &opt).unwrap()
};
let size;
let mut pixmap;
match test_mode {
TestMode::Normal => {
size = tree
.size()
.to_int_size()
.scale_to_width(IMAGE_SIZE)
.unwrap();
pixmap = tiny_skia::Pixmap::new(size.width(), size.height()).unwrap();
let render_ts = tiny_skia::Transform::from_scale(
size.width() as f32 / tree.size().width() as f32,
size.height() as f32 / tree.size().height() as f32,
);
resvg::render(&tree, render_ts, &mut pixmap.as_mut());
}
TestMode::Node(id) => {
let node = tree.node_by_id(id).unwrap();
size = node.abs_layer_bounding_box().unwrap().size().to_int_size();
pixmap = tiny_skia::Pixmap::new(size.width(), size.height()).unwrap();
resvg::render_node(node, tiny_skia::Transform::identity(), &mut pixmap.as_mut());
}
TestMode::Extra(scale) => {
size = tree.size().to_int_size().scale_by(scale).unwrap();
pixmap = tiny_skia::Pixmap::new(size.width(), size.height()).unwrap();
let render_ts = tiny_skia::Transform::from_scale(scale, scale);
resvg::render(&tree, render_ts, &mut pixmap.as_mut());
}
}
let actual_image = {
let (width, height) = (pixmap.width(), pixmap.height());
let mut data = pixmap.clone().take();
demultiply_alpha(data.as_mut_slice().as_rgba_mut());
TestImage::new_with(data, width, height)
};
let make_ref_fn = || -> ! {
pixmap.save_png(&png_path).unwrap();
Command::new("oxipng")
.args([
"-o".to_owned(),
"6".to_owned(),
"-Z".to_owned(),
png_path.clone(),
])
.output()
.unwrap();
panic!("new reference image created");
};
let reference_image = if let Ok(image_data) = std::fs::read(&png_path) {
load_png(image_data)
} else {
if make_ref {
make_ref_fn();
} else {
panic!("missing reference image");
}
};
if let Some((diff_image, pixel_diff)) = get_diff(&reference_image, &actual_image) {
if make_ref {
make_ref_fn();
} else {
let _ = std::fs::create_dir_all("tests/diffs");
diff_image.save_png(&format!("tests/diffs/{}.png", name.replace("/", "_")));
pixel_diff
}
} else {
0
}
}
/// Returns `Some` if there is at least one different pixel, and `None` if the images match.
fn get_diff(expected_image: &TestImage, actual_image: &TestImage) -> Option<(TestImage, usize)> {
const DIFF_THRESHOLD: u8 = 1;
let width = max(expected_image.width, actual_image.width);
let height = max(expected_image.height, actual_image.height);
let mut diff_image = TestImage::new(3 * width, height);
let mut pixel_diff = 0;
for x in 0..width {
for y in 0..height {
let actual_pixel = actual_image.get_pixel(x, y);
let expected_pixel = expected_image.get_pixel(x, y);
match (actual_pixel, expected_pixel) {
(Some(actual), Some(expected)) => {
diff_image.set_pixel(x, y, expected);
diff_image.set_pixel(x + 2 * width, y, actual);
if is_pix_diff(&expected, &actual, DIFF_THRESHOLD) {
pixel_diff += 1;
diff_image.set_pixel(x + width, y, Rgba::new(255, 0, 0, 255));
} else {
diff_image.set_pixel(x + width, y, Rgba::new(0, 0, 0, 255));
}
}
(Some(actual), None) => {
pixel_diff += 1;
diff_image.set_pixel(x + 2 * width, y, actual);
diff_image.set_pixel(x + width, y, Rgba::new(255, 0, 0, 255));
}
(None, Some(expected)) => {
pixel_diff += 1;
diff_image.set_pixel(x, y, expected);
diff_image.set_pixel(x + width, y, Rgba::new(255, 0, 0, 255));
}
_ => {
pixel_diff += 1;
diff_image.set_pixel(x, y, Rgba::new(255, 0, 0, 255));
diff_image.set_pixel(x + width, y, Rgba::new(255, 0, 0, 255));
}
}
}
}
if pixel_diff > 0 {
Some((diff_image, pixel_diff))
} else {
None
}
}
/// Demultiplies provided pixels alpha.
fn demultiply_alpha(data: &mut [RGBA8]) {
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;
}
}
fn is_pix_diff(pixel1: &Rgba, pixel2: &Rgba, threshold: u8) -> bool {
if pixel1.a == 0 && pixel2.a == 0 {
return false;
}
let mut different = false;
different |= pixel1.r.abs_diff(pixel2.r) > threshold;
different |= pixel1.g.abs_diff(pixel2.g) > threshold;
different |= pixel1.b.abs_diff(pixel2.b) > threshold;
different |= pixel1.a.abs_diff(pixel2.a) > threshold;
different
}
fn load_png(data: Vec) -> TestImage {
let mut decoder = png::Decoder::new(Cursor::new(data.as_slice()));
decoder.set_transformations(png::Transformations::normalize_to_color8());
let mut reader = decoder.read_info().unwrap();
let mut img_data = vec![0; reader.output_buffer_size().unwrap()];
let info = reader.next_frame(&mut img_data).unwrap();
let data = match info.color_type {
png::ColorType::Rgb => {
panic!("RGB PNG is not supported.");
}
png::ColorType::Rgba => img_data,
png::ColorType::Grayscale => {
let mut rgba_data = Vec::with_capacity(img_data.len() * 4);
for gray in img_data {
rgba_data.push(gray);
rgba_data.push(gray);
rgba_data.push(gray);
rgba_data.push(255);
}
rgba_data
}
png::ColorType::GrayscaleAlpha => {
let mut rgba_data = Vec::with_capacity(img_data.len() * 2);
for slice in img_data.chunks(2) {
let gray = slice[0];
let alpha = slice[1];
rgba_data.push(gray);
rgba_data.push(gray);
rgba_data.push(gray);
rgba_data.push(alpha);
}
rgba_data
}
png::ColorType::Indexed => {
panic!("Indexed PNG is not supported.");
}
};
TestImage::new_with(data, info.width, info.height)
}
struct TestImage {
data: Vec,
width: u32,
height: u32,
}
impl TestImage {
fn new(width: u32, height: u32) -> Self {
Self {
data: vec![0; width as usize * height as usize * 4],
width,
height,
}
}
fn new_with(data: Vec, width: u32, height: u32) -> Self {
Self {
data,
width,
height,
}
}
fn get_pixel(&self, x: u32, y: u32) -> Option> {
if x >= self.width || y >= self.height {
return None;
}
let pos = self.width as usize * (y as usize) + x as usize;
Some(self.data.as_rgba()[pos])
}
fn set_pixel(&mut self, x: u32, y: u32, val: Rgba) {
let pos = self.width as usize * (y as usize) + x as usize;
self.data.as_rgba_mut()[pos] = val;
}
fn save_png(&self, path: &str) {
let file = File::create(path).unwrap();
let ref mut w = BufWriter::new(file);
let mut encoder = Encoder::new(w, self.width, self.height);
encoder.set_color(ColorType::Rgba);
encoder.set_depth(BitDepth::Eight);
let mut writer = encoder.write_header().unwrap();
writer.write_image_data(&self.data).unwrap();
writer.finish().unwrap();
}
}
#[derive(Copy, Clone)]
pub enum TestMode<'a> {
/// Render a node by its ID.
Node(&'a str),
/// Render an `extra` test with a specific scale.
Extra(f32),
/// Render a normal SVG test.
Normal,
}
/// A simple stderr logger.
static LOGGER: SimpleLogger = SimpleLogger;
struct SimpleLogger;
impl log::Log for SimpleLogger {
fn enabled(&self, metadata: &log::Metadata) -> bool {
metadata.level() <= log::LevelFilter::Warn
}
fn log(&self, record: &log::Record) {
if self.enabled(record.metadata()) {
let target = if !record.target().is_empty() {
record.target()
} else {
record.module_path().unwrap_or_default()
};
let line = record.line().unwrap_or(0);
let args = record.args();
match record.level() {
log::Level::Error => eprintln!("Error (in {}:{}): {}", target, line, args),
log::Level::Warn => eprintln!("Warning (in {}:{}): {}", target, line, args),
log::Level::Info => eprintln!("Info (in {}:{}): {}", target, line, args),
log::Level::Debug => eprintln!("Debug (in {}:{}): {}", target, line, args),
log::Level::Trace => eprintln!("Trace (in {}:{}): {}", target, line, args),
}
}
}
fn flush(&self) {}
}
================================================
FILE: crates/resvg/tests/integration/render.rs
================================================
// Copyright 2020 the Resvg Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
// This file is auto-generated by gen-tests.py
#![allow(non_snake_case)]
use crate::render;
#[test] fn filters_enable_background_accumulate_with_new() { assert_eq!(render("tests/filters/enable-background/accumulate-with-new"), 0); }
#[test] fn filters_enable_background_accumulate() { assert_eq!(render("tests/filters/enable-background/accumulate"), 0); }
#[test] fn filters_enable_background_filter_on_shape() { assert_eq!(render("tests/filters/enable-background/filter-on-shape"), 0); }
#[test] fn filters_enable_background_inherit() { assert_eq!(render("tests/filters/enable-background/inherit"), 0); }
#[test] fn filters_enable_background_new_with_invalid_region_1() { assert_eq!(render("tests/filters/enable-background/new-with-invalid-region-1"), 0); }
#[test] fn filters_enable_background_new_with_invalid_region_2() { assert_eq!(render("tests/filters/enable-background/new-with-invalid-region-2"), 0); }
#[test] fn filters_enable_background_new_with_invalid_region_3() { assert_eq!(render("tests/filters/enable-background/new-with-invalid-region-3"), 0); }
#[test] fn filters_enable_background_new_with_region() { assert_eq!(render("tests/filters/enable-background/new-with-region"), 0); }
#[test] fn filters_enable_background_new() { assert_eq!(render("tests/filters/enable-background/new"), 0); }
#[test] fn filters_enable_background_shapes_after_filter() { assert_eq!(render("tests/filters/enable-background/shapes-after-filter"), 0); }
#[test] fn filters_enable_background_stop_on_the_first_new_1() { assert_eq!(render("tests/filters/enable-background/stop-on-the-first-new-1"), 0); }
#[test] fn filters_enable_background_stop_on_the_first_new_2() { assert_eq!(render("tests/filters/enable-background/stop-on-the-first-new-2"), 0); }
#[test] fn filters_enable_background_with_clip_path() { assert_eq!(render("tests/filters/enable-background/with-clip-path"), 0); }
#[test] fn filters_enable_background_with_filter_on_the_same_element() { assert_eq!(render("tests/filters/enable-background/with-filter-on-the-same-element"), 0); }
#[test] fn filters_enable_background_with_filter() { assert_eq!(render("tests/filters/enable-background/with-filter"), 0); }
#[test] fn filters_enable_background_with_mask() { assert_eq!(render("tests/filters/enable-background/with-mask"), 0); }
#[test] fn filters_enable_background_with_opacity_1() { assert_eq!(render("tests/filters/enable-background/with-opacity-1"), 0); }
#[test] fn filters_enable_background_with_opacity_2() { assert_eq!(render("tests/filters/enable-background/with-opacity-2"), 0); }
#[test] fn filters_enable_background_with_opacity_3() { assert_eq!(render("tests/filters/enable-background/with-opacity-3"), 0); }
#[test] fn filters_enable_background_with_opacity_4() { assert_eq!(render("tests/filters/enable-background/with-opacity-4"), 0); }
#[test] fn filters_enable_background_with_transform() { assert_eq!(render("tests/filters/enable-background/with-transform"), 0); }
#[test] fn filters_feBlend_empty() { assert_eq!(render("tests/filters/feBlend/empty"), 0); }
#[test] fn filters_feBlend_mode_eq_color_burn() { assert_eq!(render("tests/filters/feBlend/mode=color-burn"), 0); }
#[test] fn filters_feBlend_mode_eq_darken() { assert_eq!(render("tests/filters/feBlend/mode=darken"), 0); }
#[test] fn filters_feBlend_mode_eq_hue() { assert_eq!(render("tests/filters/feBlend/mode=hue"), 0); }
#[test] fn filters_feBlend_mode_eq_lighten() { assert_eq!(render("tests/filters/feBlend/mode=lighten"), 0); }
#[test] fn filters_feBlend_mode_eq_multiply() { assert_eq!(render("tests/filters/feBlend/mode=multiply"), 0); }
#[test] fn filters_feBlend_mode_eq_normal() { assert_eq!(render("tests/filters/feBlend/mode=normal"), 0); }
#[test] fn filters_feBlend_mode_eq_screen() { assert_eq!(render("tests/filters/feBlend/mode=screen"), 0); }
#[test] fn filters_feBlend_with_subregion_on_input_1() { assert_eq!(render("tests/filters/feBlend/with-subregion-on-input-1"), 0); }
#[test] fn filters_feBlend_with_subregion_on_input_2() { assert_eq!(render("tests/filters/feBlend/with-subregion-on-input-2"), 0); }
#[test] fn filters_feColorMatrix_invalid_type() { assert_eq!(render("tests/filters/feColorMatrix/invalid-type"), 0); }
#[test] fn filters_feColorMatrix_type_eq_hueRotate_without_an_angle() { assert_eq!(render("tests/filters/feColorMatrix/type=hueRotate-without-an-angle"), 0); }
#[test] fn filters_feColorMatrix_type_eq_hueRotate() { assert_eq!(render("tests/filters/feColorMatrix/type=hueRotate"), 0); }
#[test] fn filters_feColorMatrix_type_eq_luminanceToAlpha() { assert_eq!(render("tests/filters/feColorMatrix/type=luminanceToAlpha"), 0); }
#[test] fn filters_feColorMatrix_type_eq_matrix_with_empty_values() { assert_eq!(render("tests/filters/feColorMatrix/type=matrix-with-empty-values"), 0); }
#[test] fn filters_feColorMatrix_type_eq_matrix_with_non_normalized_values() { assert_eq!(render("tests/filters/feColorMatrix/type=matrix-with-non-normalized-values"), 0); }
#[test] fn filters_feColorMatrix_type_eq_matrix_with_not_enough_values() { assert_eq!(render("tests/filters/feColorMatrix/type=matrix-with-not-enough-values"), 0); }
#[test] fn filters_feColorMatrix_type_eq_matrix_with_too_many_values() { assert_eq!(render("tests/filters/feColorMatrix/type=matrix-with-too-many-values"), 0); }
#[test] fn filters_feColorMatrix_type_eq_matrix_without_values() { assert_eq!(render("tests/filters/feColorMatrix/type=matrix-without-values"), 0); }
#[test] fn filters_feColorMatrix_type_eq_matrix() { assert_eq!(render("tests/filters/feColorMatrix/type=matrix"), 0); }
#[test] fn filters_feColorMatrix_type_eq_saturate_with_a_large_coefficient() { assert_eq!(render("tests/filters/feColorMatrix/type=saturate-with-a-large-coefficient"), 0); }
#[test] fn filters_feColorMatrix_type_eq_saturate_with_negative_coefficient() { assert_eq!(render("tests/filters/feColorMatrix/type=saturate-with-negative-coefficient"), 0); }
#[test] fn filters_feColorMatrix_type_eq_saturate_without_a_coefficient() { assert_eq!(render("tests/filters/feColorMatrix/type=saturate-without-a-coefficient"), 0); }
#[test] fn filters_feColorMatrix_type_eq_saturate() { assert_eq!(render("tests/filters/feColorMatrix/type=saturate"), 0); }
#[test] fn filters_feColorMatrix_without_a_type() { assert_eq!(render("tests/filters/feColorMatrix/without-a-type"), 0); }
#[test] fn filters_feColorMatrix_without_attributes() { assert_eq!(render("tests/filters/feColorMatrix/without-attributes"), 0); }
#[test] fn filters_feComponentTransfer_invalid_type() { assert_eq!(render("tests/filters/feComponentTransfer/invalid-type"), 0); }
#[test] fn filters_feComponentTransfer_mixed_types() { assert_eq!(render("tests/filters/feComponentTransfer/mixed-types"), 0); }
#[test] fn filters_feComponentTransfer_no_children() { assert_eq!(render("tests/filters/feComponentTransfer/no-children"), 0); }
#[test] fn filters_feComponentTransfer_type_eq_discrete_on_blue() { assert_eq!(render("tests/filters/feComponentTransfer/type=discrete-on-blue"), 0); }
#[test] fn filters_feComponentTransfer_type_eq_gamma_on_blue() { assert_eq!(render("tests/filters/feComponentTransfer/type=gamma-on-blue"), 0); }
#[test] fn filters_feComponentTransfer_type_eq_gamma_with_an_invalid_offset() { assert_eq!(render("tests/filters/feComponentTransfer/type=gamma-with-an-invalid-offset"), 0); }
#[test] fn filters_feComponentTransfer_type_eq_gamma_with_invalid_values() { assert_eq!(render("tests/filters/feComponentTransfer/type=gamma-with-invalid-values"), 0); }
#[test] fn filters_feComponentTransfer_type_eq_identity_on_all() { assert_eq!(render("tests/filters/feComponentTransfer/type=identity-on-all"), 0); }
#[test] fn filters_feComponentTransfer_type_eq_linear_on_blue() { assert_eq!(render("tests/filters/feComponentTransfer/type=linear-on-blue"), 0); }
#[test] fn filters_feComponentTransfer_type_eq_linear_with_invalid_values() { assert_eq!(render("tests/filters/feComponentTransfer/type=linear-with-invalid-values"), 0); }
#[test] fn filters_feComponentTransfer_type_eq_linear_with_large_values() { assert_eq!(render("tests/filters/feComponentTransfer/type=linear-with-large-values"), 0); }
#[test] fn filters_feComponentTransfer_type_eq_table_and_tableValues_eq_1_0_1() { assert_eq!(render("tests/filters/feComponentTransfer/type=table-and-tableValues=1-0-1"), 0); }
#[test] fn filters_feComponentTransfer_type_eq_table_and_tableValues_eq_1() { assert_eq!(render("tests/filters/feComponentTransfer/type=table-and-tableValues=1"), 0); }
#[test] fn filters_feComponentTransfer_type_eq_table_and_tableValues_eq_100__100() { assert_eq!(render("tests/filters/feComponentTransfer/type=table-and-tableValues=100--100"), 0); }
#[test] fn filters_feComponentTransfer_type_eq_table_and_tableValues_eq_1px() { assert_eq!(render("tests/filters/feComponentTransfer/type=table-and-tableValues=1px"), 0); }
#[test] fn filters_feComponentTransfer_type_eq_table_on_alpha() { assert_eq!(render("tests/filters/feComponentTransfer/type=table-on-alpha"), 0); }
#[test] fn filters_feComponentTransfer_type_eq_table_on_blue_twice() { assert_eq!(render("tests/filters/feComponentTransfer/type=table-on-blue-twice"), 0); }
#[test] fn filters_feComponentTransfer_type_eq_table_on_blue_with_sRGB_interpolation() { assert_eq!(render("tests/filters/feComponentTransfer/type=table-on-blue-with-sRGB-interpolation"), 0); }
#[test] fn filters_feComponentTransfer_type_eq_table_on_blue() { assert_eq!(render("tests/filters/feComponentTransfer/type=table-on-blue"), 0); }
#[test] fn filters_feComponentTransfer_type_eq_table_with_an_empty_tableValues() { assert_eq!(render("tests/filters/feComponentTransfer/type=table-with-an-empty-tableValues"), 0); }
#[test] fn filters_feComponentTransfer_type_eq_table_with_large_values() { assert_eq!(render("tests/filters/feComponentTransfer/type=table-with-large-values"), 0); }
#[test] fn filters_feComponentTransfer_type_eq_table_without_tableValues() { assert_eq!(render("tests/filters/feComponentTransfer/type=table-without-tableValues"), 0); }
#[test] fn filters_feComposite_default_operator() { assert_eq!(render("tests/filters/feComposite/default-operator"), 0); }
#[test] fn filters_feComposite_empty() { assert_eq!(render("tests/filters/feComposite/empty"), 0); }
#[test] fn filters_feComposite_invalid_operator() { assert_eq!(render("tests/filters/feComposite/invalid-operator"), 0); }
#[test] fn filters_feComposite_operator_eq_arithmetic_and_invalid_k1_4() { assert_eq!(render("tests/filters/feComposite/operator=arithmetic-and-invalid-k1-4"), 0); }
#[test] fn filters_feComposite_operator_eq_arithmetic_on_sRGB() { assert_eq!(render("tests/filters/feComposite/operator=arithmetic-on-sRGB"), 0); }
#[test] fn filters_feComposite_operator_eq_arithmetic_with_large_k1_4() { assert_eq!(render("tests/filters/feComposite/operator=arithmetic-with-large-k1-4"), 0); }
#[test] fn filters_feComposite_operator_eq_arithmetic_with_opacity_on_sRGB() { assert_eq!(render("tests/filters/feComposite/operator=arithmetic-with-opacity-on-sRGB"), 0); }
#[test] fn filters_feComposite_operator_eq_arithmetic_with_opacity() { assert_eq!(render("tests/filters/feComposite/operator=arithmetic-with-opacity"), 0); }
#[test] fn filters_feComposite_operator_eq_arithmetic_with_some_k1_4() { assert_eq!(render("tests/filters/feComposite/operator=arithmetic-with-some-k1-4"), 0); }
#[test] fn filters_feComposite_operator_eq_arithmetic_without_k1_4() { assert_eq!(render("tests/filters/feComposite/operator=arithmetic-without-k1-4"), 0); }
#[test] fn filters_feComposite_operator_eq_arithmetic() { assert_eq!(render("tests/filters/feComposite/operator=arithmetic"), 0); }
#[test] fn filters_feComposite_operator_eq_atop() { assert_eq!(render("tests/filters/feComposite/operator=atop"), 0); }
#[test] fn filters_feComposite_operator_eq_in() { assert_eq!(render("tests/filters/feComposite/operator=in"), 0); }
#[test] fn filters_feComposite_operator_eq_out() { assert_eq!(render("tests/filters/feComposite/operator=out"), 0); }
#[test] fn filters_feComposite_operator_eq_over() { assert_eq!(render("tests/filters/feComposite/operator=over"), 0); }
#[test] fn filters_feComposite_operator_eq_xor() { assert_eq!(render("tests/filters/feComposite/operator=xor"), 0); }
#[test] fn filters_feComposite_with_subregion_on_input_1() { assert_eq!(render("tests/filters/feComposite/with-subregion-on-input-1"), 0); }
#[test] fn filters_feComposite_with_subregion_on_input_2() { assert_eq!(render("tests/filters/feComposite/with-subregion-on-input-2"), 0); }
#[test] fn filters_feConvolveMatrix_bias_eq__0_5() { assert_eq!(render("tests/filters/feConvolveMatrix/bias=-0.5"), 0); }
#[test] fn filters_feConvolveMatrix_bias_eq_0_5() { assert_eq!(render("tests/filters/feConvolveMatrix/bias=0.5"), 0); }
#[test] fn filters_feConvolveMatrix_bias_eq_9999() { assert_eq!(render("tests/filters/feConvolveMatrix/bias=9999"), 0); }
#[test] fn filters_feConvolveMatrix_custom_divisor() { assert_eq!(render("tests/filters/feConvolveMatrix/custom-divisor"), 0); }
#[test] fn filters_feConvolveMatrix_divisor_eq_0() { assert_eq!(render("tests/filters/feConvolveMatrix/divisor=0"), 0); }
#[test] fn filters_feConvolveMatrix_edgeMode_eq_none() { assert_eq!(render("tests/filters/feConvolveMatrix/edgeMode=none"), 0); }
#[test] fn filters_feConvolveMatrix_edgeMode_eq_wrap_with_matrix_larger_than_target() { assert_eq!(render("tests/filters/feConvolveMatrix/edgeMode=wrap-with-matrix-larger-than-target"), 0); }
#[test] fn filters_feConvolveMatrix_edgeMode_eq_wrap() { assert_eq!(render("tests/filters/feConvolveMatrix/edgeMode=wrap"), 0); }
#[test] fn filters_feConvolveMatrix_empty_kernelMatrix() { assert_eq!(render("tests/filters/feConvolveMatrix/empty-kernelMatrix"), 0); }
#[test] fn filters_feConvolveMatrix_kernelMatrix_with_not_enough_values() { assert_eq!(render("tests/filters/feConvolveMatrix/kernelMatrix-with-not-enough-values"), 0); }
#[test] fn filters_feConvolveMatrix_kernelMatrix_with_too_many_values() { assert_eq!(render("tests/filters/feConvolveMatrix/kernelMatrix-with-too-many-values"), 0); }
#[test] fn filters_feConvolveMatrix_kernelMatrix_with_zero_sum_and_no_divisor() { assert_eq!(render("tests/filters/feConvolveMatrix/kernelMatrix-with-zero-sum-and-no-divisor"), 0); }
#[test] fn filters_feConvolveMatrix_no_kernelMatrix() { assert_eq!(render("tests/filters/feConvolveMatrix/no-kernelMatrix"), 0); }
#[test] fn filters_feConvolveMatrix_order_with_a_negative_value_1() { assert_eq!(render("tests/filters/feConvolveMatrix/order-with-a-negative-value-1"), 0); }
#[test] fn filters_feConvolveMatrix_order_with_a_negative_value_2() { assert_eq!(render("tests/filters/feConvolveMatrix/order-with-a-negative-value-2"), 0); }
#[test] fn filters_feConvolveMatrix_order_eq_0() { assert_eq!(render("tests/filters/feConvolveMatrix/order=0"), 0); }
#[test] fn filters_feConvolveMatrix_order_eq_4_2() { assert_eq!(render("tests/filters/feConvolveMatrix/order=4-2"), 0); }
#[test] fn filters_feConvolveMatrix_order_eq_4_4() { assert_eq!(render("tests/filters/feConvolveMatrix/order=4-4"), 0); }
#[test] fn filters_feConvolveMatrix_order_eq_4() { assert_eq!(render("tests/filters/feConvolveMatrix/order=4"), 0); }
#[test] fn filters_feConvolveMatrix_preserveAlpha_eq_true() { assert_eq!(render("tests/filters/feConvolveMatrix/preserveAlpha=true"), 0); }
#[test] fn filters_feConvolveMatrix_targetX_eq__1() { assert_eq!(render("tests/filters/feConvolveMatrix/targetX=-1"), 0); }
#[test] fn filters_feConvolveMatrix_targetX_eq_0() { assert_eq!(render("tests/filters/feConvolveMatrix/targetX=0"), 0); }
#[test] fn filters_feConvolveMatrix_targetX_eq_2() { assert_eq!(render("tests/filters/feConvolveMatrix/targetX=2"), 0); }
#[test] fn filters_feConvolveMatrix_targetX_eq_3() { assert_eq!(render("tests/filters/feConvolveMatrix/targetX=3"), 0); }
#[test] fn filters_feConvolveMatrix_unset_order() { assert_eq!(render("tests/filters/feConvolveMatrix/unset-order"), 0); }
#[test] fn filters_feDiffuseLighting_complex_transform() { assert_eq!(render("tests/filters/feDiffuseLighting/complex-transform"), 0); }
#[test] fn filters_feDiffuseLighting_diffuseConstant_eq__1() { assert_eq!(render("tests/filters/feDiffuseLighting/diffuseConstant=-1"), 0); }
#[test] fn filters_feDiffuseLighting_diffuseConstant_eq_0() { assert_eq!(render("tests/filters/feDiffuseLighting/diffuseConstant=0"), 0); }
#[test] fn filters_feDiffuseLighting_diffuseConstant_eq_5() { assert_eq!(render("tests/filters/feDiffuseLighting/diffuseConstant=5"), 0); }
#[test] fn filters_feDiffuseLighting_lighting_color_eq_currentColor_without_color() { assert_eq!(render("tests/filters/feDiffuseLighting/lighting-color=currentColor-without-color"), 0); }
#[test] fn filters_feDiffuseLighting_lighting_color_eq_currentColor() { assert_eq!(render("tests/filters/feDiffuseLighting/lighting-color=currentColor"), 0); }
#[test] fn filters_feDiffuseLighting_lighting_color_eq_hsla() { assert_eq!(render("tests/filters/feDiffuseLighting/lighting-color=hsla"), 0); }
#[test] fn filters_feDiffuseLighting_lighting_color_eq_inherit() { assert_eq!(render("tests/filters/feDiffuseLighting/lighting-color=inherit"), 0); }
#[test] fn filters_feDiffuseLighting_lighting_color_eq_seagreen() { assert_eq!(render("tests/filters/feDiffuseLighting/lighting-color=seagreen"), 0); }
#[test] fn filters_feDiffuseLighting_linearRGB_color_interpolation() { assert_eq!(render("tests/filters/feDiffuseLighting/linearRGB-color-interpolation"), 0); }
#[test] fn filters_feDiffuseLighting_multiple_light_sources() { assert_eq!(render("tests/filters/feDiffuseLighting/multiple-light-sources"), 0); }
#[test] fn filters_feDiffuseLighting_no_light_source() { assert_eq!(render("tests/filters/feDiffuseLighting/no-light-source"), 0); }
#[test] fn filters_feDiffuseLighting_single_light_source_with_comment() { assert_eq!(render("tests/filters/feDiffuseLighting/single-light-source-with-comment"), 0); }
#[test] fn filters_feDiffuseLighting_single_light_source_with_desc() { assert_eq!(render("tests/filters/feDiffuseLighting/single-light-source-with-desc"), 0); }
#[test] fn filters_feDiffuseLighting_single_light_source_with_invalid_child() { assert_eq!(render("tests/filters/feDiffuseLighting/single-light-source-with-invalid-child"), 0); }
#[test] fn filters_feDiffuseLighting_single_light_source_with_title_and_desc() { assert_eq!(render("tests/filters/feDiffuseLighting/single-light-source-with-title-and-desc"), 0); }
#[test] fn filters_feDiffuseLighting_single_light_source_with_title() { assert_eq!(render("tests/filters/feDiffuseLighting/single-light-source-with-title"), 0); }
#[test] fn filters_feDiffuseLighting_single_light_source() { assert_eq!(render("tests/filters/feDiffuseLighting/single-light-source"), 0); }
#[test] fn filters_feDiffuseLighting_surfaceScale_eq__10() { assert_eq!(render("tests/filters/feDiffuseLighting/surfaceScale=-10"), 0); }
#[test] fn filters_feDiffuseLighting_surfaceScale_eq_0() { assert_eq!(render("tests/filters/feDiffuseLighting/surfaceScale=0"), 0); }
#[test] fn filters_feDiffuseLighting_surfaceScale_eq_1_33() { assert_eq!(render("tests/filters/feDiffuseLighting/surfaceScale=1.33"), 0); }
#[test] fn filters_feDiffuseLighting_surfaceScale_eq_5() { assert_eq!(render("tests/filters/feDiffuseLighting/surfaceScale=5"), 0); }
#[test] fn filters_feDisplacementMap_simple_case() { assert_eq!(render("tests/filters/feDisplacementMap/simple-case"), 0); }
#[test] fn filters_feDistantLight_default_attributes() { assert_eq!(render("tests/filters/feDistantLight/default-attributes"), 0); }
#[test] fn filters_feDistantLight_negative_azimuth_and_elevation() { assert_eq!(render("tests/filters/feDistantLight/negative-azimuth-and-elevation"), 0); }
#[test] fn filters_feDistantLight_only_azimuth() { assert_eq!(render("tests/filters/feDistantLight/only-azimuth"), 0); }
#[test] fn filters_feDistantLight_only_elevation() { assert_eq!(render("tests/filters/feDistantLight/only-elevation"), 0); }
#[test] fn filters_feDropShadow_hsla_color() { assert_eq!(render("tests/filters/feDropShadow/hsla-color"), 0); }
#[test] fn filters_feDropShadow_only_stdDeviation() { assert_eq!(render("tests/filters/feDropShadow/only-stdDeviation"), 0); }
#[test] fn filters_feDropShadow_stdDeviation_eq_0() { assert_eq!(render("tests/filters/feDropShadow/stdDeviation=0"), 0); }
#[test] fn filters_feDropShadow_with_flood_color() { assert_eq!(render("tests/filters/feDropShadow/with-flood-color"), 0); }
#[test] fn filters_feDropShadow_with_flood_opacity() { assert_eq!(render("tests/filters/feDropShadow/with-flood-opacity"), 0); }
#[test] fn filters_feDropShadow_with_offset_clipped() { assert_eq!(render("tests/filters/feDropShadow/with-offset-clipped"), 0); }
#[test] fn filters_feDropShadow_with_offset() { assert_eq!(render("tests/filters/feDropShadow/with-offset"), 0); }
#[test] fn filters_feDropShadow_with_percent_offset() { assert_eq!(render("tests/filters/feDropShadow/with-percent-offset"), 0); }
#[test] fn filters_feFlood_complex_transform() { assert_eq!(render("tests/filters/feFlood/complex-transform"), 0); }
#[test] fn filters_feFlood_default_values() { assert_eq!(render("tests/filters/feFlood/default-values"), 0); }
#[test] fn filters_feFlood_partial_subregion() { assert_eq!(render("tests/filters/feFlood/partial-subregion"), 0); }
#[test] fn filters_feFlood_seagreen() { assert_eq!(render("tests/filters/feFlood/seagreen"), 0); }
#[test] fn filters_feFlood_subregion_inheritance() { assert_eq!(render("tests/filters/feFlood/subregion-inheritance"), 0); }
#[test] fn filters_feFlood_subregion_with_primitiveUnits_eq_objectBoundingBox() { assert_eq!(render("tests/filters/feFlood/subregion-with-primitiveUnits=objectBoundingBox"), 0); }
#[test] fn filters_feFlood_with_opacity_on_target_element() { assert_eq!(render("tests/filters/feFlood/with-opacity-on-target-element"), 0); }
#[test] fn filters_feFlood_with_opacity() { assert_eq!(render("tests/filters/feFlood/with-opacity"), 0); }
#[test] fn filters_feGaussianBlur_complex_transform() { assert_eq!(render("tests/filters/feGaussianBlur/complex-transform"), 0); }
#[test] fn filters_feGaussianBlur_empty_stdDeviation() { assert_eq!(render("tests/filters/feGaussianBlur/empty-stdDeviation"), 0); }
#[test] fn filters_feGaussianBlur_huge_stdDeviation() { assert_eq!(render("tests/filters/feGaussianBlur/huge-stdDeviation"), 0); }
#[test] fn filters_feGaussianBlur_negative_stdDeviation() { assert_eq!(render("tests/filters/feGaussianBlur/negative-stdDeviation"), 0); }
#[test] fn filters_feGaussianBlur_no_stdDeviation() { assert_eq!(render("tests/filters/feGaussianBlur/no-stdDeviation"), 0); }
#[test] fn filters_feGaussianBlur_simple_case() { assert_eq!(render("tests/filters/feGaussianBlur/simple-case"), 0); }
#[test] fn filters_feGaussianBlur_small_stdDeviation() { assert_eq!(render("tests/filters/feGaussianBlur/small-stdDeviation"), 0); }
#[test] fn filters_feGaussianBlur_stdDeviation_with_multiple_values() { assert_eq!(render("tests/filters/feGaussianBlur/stdDeviation-with-multiple-values"), 0); }
#[test] fn filters_feGaussianBlur_stdDeviation_with_two_different_values() { assert_eq!(render("tests/filters/feGaussianBlur/stdDeviation-with-two-different-values"), 0); }
#[test] fn filters_feGaussianBlur_stdDeviation_with_two_values() { assert_eq!(render("tests/filters/feGaussianBlur/stdDeviation-with-two-values"), 0); }
#[test] fn filters_feGaussianBlur_stdDeviation_eq_0_5() { assert_eq!(render("tests/filters/feGaussianBlur/stdDeviation=0-5"), 0); }
#[test] fn filters_feGaussianBlur_stdDeviation_eq_5_0() { assert_eq!(render("tests/filters/feGaussianBlur/stdDeviation=5-0"), 0); }
#[test] fn filters_feGaussianBlur_tiny_stdDeviation() { assert_eq!(render("tests/filters/feGaussianBlur/tiny-stdDeviation"), 0); }
#[test] fn filters_feImage_chained_feImage() { assert_eq!(render("tests/filters/feImage/chained-feImage"), 0); }
#[test] fn filters_feImage_embedded_png() { assert_eq!(render("tests/filters/feImage/embedded-png"), 0); }
#[test] fn filters_feImage_empty() { assert_eq!(render("tests/filters/feImage/empty"), 0); }
#[test] fn filters_feImage_link_on_an_element_with_complex_transform() { assert_eq!(render("tests/filters/feImage/link-on-an-element-with-complex-transform"), 0); }
#[test] fn filters_feImage_link_on_an_element_with_transform() { assert_eq!(render("tests/filters/feImage/link-on-an-element-with-transform"), 0); }
#[test] fn filters_feImage_link_to_an_element_outside_defs_1() { assert_eq!(render("tests/filters/feImage/link-to-an-element-outside-defs-1"), 0); }
#[test] fn filters_feImage_link_to_an_element_outside_defs_2() { assert_eq!(render("tests/filters/feImage/link-to-an-element-outside-defs-2"), 0); }
#[test] fn filters_feImage_link_to_an_element_with_opacity() { assert_eq!(render("tests/filters/feImage/link-to-an-element-with-opacity"), 0); }
#[test] fn filters_feImage_link_to_an_element_with_transform() { assert_eq!(render("tests/filters/feImage/link-to-an-element-with-transform"), 0); }
#[test] fn filters_feImage_link_to_an_element() { assert_eq!(render("tests/filters/feImage/link-to-an-element"), 0); }
#[test] fn filters_feImage_link_to_an_invalid_element() { assert_eq!(render("tests/filters/feImage/link-to-an-invalid-element"), 0); }
#[test] fn filters_feImage_link_to_g() { assert_eq!(render("tests/filters/feImage/link-to-g"), 0); }
#[test] fn filters_feImage_link_to_use() { assert_eq!(render("tests/filters/feImage/link-to-use"), 0); }
#[test] fn filters_feImage_preserveAspectRatio_eq_none() { assert_eq!(render("tests/filters/feImage/preserveAspectRatio=none"), 0); }
#[test] fn filters_feImage_recursive_links_1() { assert_eq!(render("tests/filters/feImage/recursive-links-1"), 0); }
#[test] fn filters_feImage_recursive_links_2() { assert_eq!(render("tests/filters/feImage/recursive-links-2"), 0); }
#[test] fn filters_feImage_self_recursive() { assert_eq!(render("tests/filters/feImage/self-recursive"), 0); }
#[test] fn filters_feImage_simple_case() { assert_eq!(render("tests/filters/feImage/simple-case"), 0); }
#[test] fn filters_feImage_svg() { assert_eq!(render("tests/filters/feImage/svg"), 0); }
#[test] fn filters_feImage_with_subregion_1() { assert_eq!(render("tests/filters/feImage/with-subregion-1"), 0); }
#[test] fn filters_feImage_with_subregion_2() { assert_eq!(render("tests/filters/feImage/with-subregion-2"), 0); }
#[test] fn filters_feImage_with_subregion_3() { assert_eq!(render("tests/filters/feImage/with-subregion-3"), 0); }
#[test] fn filters_feImage_with_subregion_4() { assert_eq!(render("tests/filters/feImage/with-subregion-4"), 0); }
#[test] fn filters_feImage_with_subregion_5() { assert_eq!(render("tests/filters/feImage/with-subregion-5"), 0); }
#[test] fn filters_feImage_with_x_y_and_protruding_subregion_1() { assert_eq!(render("tests/filters/feImage/with-x-y-and-protruding-subregion-1"), 0); }
#[test] fn filters_feImage_with_x_y_and_protruding_subregion_2() { assert_eq!(render("tests/filters/feImage/with-x-y-and-protruding-subregion-2"), 0); }
#[test] fn filters_feImage_with_x_y() { assert_eq!(render("tests/filters/feImage/with-x-y"), 0); }
#[test] fn filters_feMerge_color_interpolation_filters_eq_linearRGB() { assert_eq!(render("tests/filters/feMerge/color-interpolation-filters=linearRGB"), 0); }
#[test] fn filters_feMerge_color_interpolation_filters_eq_sRGB() { assert_eq!(render("tests/filters/feMerge/color-interpolation-filters=sRGB"), 0); }
#[test] fn filters_feMerge_complex_transform() { assert_eq!(render("tests/filters/feMerge/complex-transform"), 0); }
#[test] fn filters_feMorphology_empty_radius() { assert_eq!(render("tests/filters/feMorphology/empty-radius"), 0); }
#[test] fn filters_feMorphology_negative_radius() { assert_eq!(render("tests/filters/feMorphology/negative-radius"), 0); }
#[test] fn filters_feMorphology_no_radius() { assert_eq!(render("tests/filters/feMorphology/no-radius"), 0); }
#[test] fn filters_feMorphology_operator_eq_dilate() { assert_eq!(render("tests/filters/feMorphology/operator=dilate"), 0); }
#[test] fn filters_feMorphology_radius_with_too_many_values() { assert_eq!(render("tests/filters/feMorphology/radius-with-too-many-values"), 0); }
#[test] fn filters_feMorphology_radius_eq_0_5_with_objectBoundingBox() { assert_eq!(render("tests/filters/feMorphology/radius=0.5-with-objectBoundingBox"), 0); }
#[test] fn filters_feMorphology_radius_eq_0_5() { assert_eq!(render("tests/filters/feMorphology/radius=0.5"), 0); }
#[test] fn filters_feMorphology_radius_eq_1_10() { assert_eq!(render("tests/filters/feMorphology/radius=1-10"), 0); }
#[test] fn filters_feMorphology_radius_eq_10_0() { assert_eq!(render("tests/filters/feMorphology/radius=10-0"), 0); }
#[test] fn filters_feMorphology_radius_eq_10_1() { assert_eq!(render("tests/filters/feMorphology/radius=10-1"), 0); }
#[test] fn filters_feMorphology_simple_case() { assert_eq!(render("tests/filters/feMorphology/simple-case"), 0); }
#[test] fn filters_feMorphology_source_with_opacity() { assert_eq!(render("tests/filters/feMorphology/source-with-opacity"), 0); }
#[test] fn filters_feMorphology_zero_radius() { assert_eq!(render("tests/filters/feMorphology/zero-radius"), 0); }
#[test] fn filters_feOffset_complex_transform() { assert_eq!(render("tests/filters/feOffset/complex-transform"), 0); }
#[test] fn filters_feOffset_fractional_offset() { assert_eq!(render("tests/filters/feOffset/fractional-offset"), 0); }
#[test] fn filters_feOffset_negative_offset() { assert_eq!(render("tests/filters/feOffset/negative-offset"), 0); }
#[test] fn filters_feOffset_no_offset() { assert_eq!(render("tests/filters/feOffset/no-offset"), 0); }
#[test] fn filters_feOffset_only_dx() { assert_eq!(render("tests/filters/feOffset/only-dx"), 0); }
#[test] fn filters_feOffset_only_dy() { assert_eq!(render("tests/filters/feOffset/only-dy"), 0); }
#[test] fn filters_feOffset_percentage_values() { assert_eq!(render("tests/filters/feOffset/percentage-values"), 0); }
#[test] fn filters_feOffset_simple_case() { assert_eq!(render("tests/filters/feOffset/simple-case"), 0); }
#[test] fn filters_feOffset_with_primitiveUnits_eq_objectBoundingBox() { assert_eq!(render("tests/filters/feOffset/with-primitiveUnits=objectBoundingBox"), 0); }
#[test] fn filters_fePointLight_complex_transform() { assert_eq!(render("tests/filters/fePointLight/complex-transform"), 0); }
#[test] fn filters_fePointLight_custom_attributes() { assert_eq!(render("tests/filters/fePointLight/custom-attributes"), 0); }
#[test] fn filters_fePointLight_default_attributes() { assert_eq!(render("tests/filters/fePointLight/default-attributes"), 0); }
#[test] fn filters_fePointLight_primitiveUnits_eq_objectBoundingBox() { assert_eq!(render("tests/filters/fePointLight/primitiveUnits=objectBoundingBox"), 0); }
#[test] fn filters_feSpecularLighting_lighting_color_eq_hsla() { assert_eq!(render("tests/filters/feSpecularLighting/lighting-color=hsla"), 0); }
#[test] fn filters_feSpecularLighting_specularExponent_eq_0() { assert_eq!(render("tests/filters/feSpecularLighting/specularExponent=0"), 0); }
#[test] fn filters_feSpecularLighting_specularExponent_eq_256() { assert_eq!(render("tests/filters/feSpecularLighting/specularExponent=256"), 0); }
#[test] fn filters_feSpecularLighting_with_feDistantLight() { assert_eq!(render("tests/filters/feSpecularLighting/with-feDistantLight"), 0); }
#[test] fn filters_feSpecularLighting_with_fePointLight() { assert_eq!(render("tests/filters/feSpecularLighting/with-fePointLight"), 0); }
#[test] fn filters_feSpecularLighting_with_feSpotLight_and_specular_and_exponent() { assert_eq!(render("tests/filters/feSpecularLighting/with-feSpotLight-and-specular-and-exponent"), 0); }
#[test] fn filters_feSpecularLighting_with_feSpotLight_and_specularConstant_eq_5() { assert_eq!(render("tests/filters/feSpecularLighting/with-feSpotLight-and-specularConstant=5"), 0); }
#[test] fn filters_feSpecularLighting_with_feSpotLight() { assert_eq!(render("tests/filters/feSpecularLighting/with-feSpotLight"), 0); }
#[test] fn filters_feSpotLight_complex_transform() { assert_eq!(render("tests/filters/feSpotLight/complex-transform"), 0); }
#[test] fn filters_feSpotLight_custom_attributes() { assert_eq!(render("tests/filters/feSpotLight/custom-attributes"), 0); }
#[test] fn filters_feSpotLight_default_attributes() { assert_eq!(render("tests/filters/feSpotLight/default-attributes"), 0); }
#[test] fn filters_feSpotLight_limitingConeAngle_anti_aliasing() { assert_eq!(render("tests/filters/feSpotLight/limitingConeAngle-anti-aliasing"), 0); }
#[test] fn filters_feSpotLight_limitingConeAngle_eq__30() { assert_eq!(render("tests/filters/feSpotLight/limitingConeAngle=-30"), 0); }
#[test] fn filters_feSpotLight_limitingConeAngle_eq_0() { assert_eq!(render("tests/filters/feSpotLight/limitingConeAngle=0"), 0); }
#[test] fn filters_feSpotLight_limitingConeAngle_eq_30() { assert_eq!(render("tests/filters/feSpotLight/limitingConeAngle=30"), 0); }
#[test] fn filters_feSpotLight_primitiveUnits_eq_objectBoundingBox() { assert_eq!(render("tests/filters/feSpotLight/primitiveUnits=objectBoundingBox"), 0); }
#[test] fn filters_feSpotLight_specularExponent_eq__10() { assert_eq!(render("tests/filters/feSpotLight/specularExponent=-10"), 0); }
#[test] fn filters_feSpotLight_specularExponent_eq_0_5() { assert_eq!(render("tests/filters/feSpotLight/specularExponent=0.5"), 0); }
#[test] fn filters_feSpotLight_specularExponent_eq_10() { assert_eq!(render("tests/filters/feSpotLight/specularExponent=10"), 0); }
#[test] fn filters_feSpotLight_with_all_pointsAt() { assert_eq!(render("tests/filters/feSpotLight/with-all-pointsAt"), 0); }
#[test] fn filters_feTile_complex_transform() { assert_eq!(render("tests/filters/feTile/complex-transform"), 0); }
#[test] fn filters_feTile_empty_region() { assert_eq!(render("tests/filters/feTile/empty-region"), 0); }
#[test] fn filters_feTile_simple_case() { assert_eq!(render("tests/filters/feTile/simple-case"), 0); }
#[test] fn filters_feTile_with_region() { assert_eq!(render("tests/filters/feTile/with-region"), 0); }
#[test] fn filters_feTile_with_subregion_1() { assert_eq!(render("tests/filters/feTile/with-subregion-1"), 0); }
#[test] fn filters_feTile_with_subregion_2() { assert_eq!(render("tests/filters/feTile/with-subregion-2"), 0); }
#[test] fn filters_feTile_with_subregion_3() { assert_eq!(render("tests/filters/feTile/with-subregion-3"), 0); }
#[test] fn filters_feTurbulence_baseFrequency_eq__0_05() { assert_eq!(render("tests/filters/feTurbulence/baseFrequency=-0.05"), 0); }
#[test] fn filters_feTurbulence_baseFrequency_eq_0_01() { assert_eq!(render("tests/filters/feTurbulence/baseFrequency=0.01"), 0); }
#[test] fn filters_feTurbulence_baseFrequency_eq_0_05__0_01() { assert_eq!(render("tests/filters/feTurbulence/baseFrequency=0.05--0.01"), 0); }
#[test] fn filters_feTurbulence_baseFrequency_eq_0_05_0_01() { assert_eq!(render("tests/filters/feTurbulence/baseFrequency=0.05-0.01"), 0); }
#[test] fn filters_feTurbulence_baseFrequency_eq_0_05_0_05() { assert_eq!(render("tests/filters/feTurbulence/baseFrequency=0.05-0.05"), 0); }
#[test] fn filters_feTurbulence_baseFrequency_eq_0_05_0() { assert_eq!(render("tests/filters/feTurbulence/baseFrequency=0.05-0"), 0); }
#[test] fn filters_feTurbulence_color_interpolation_filters_eq_sRGB() { assert_eq!(render("tests/filters/feTurbulence/color-interpolation-filters=sRGB"), 0); }
#[test] fn filters_feTurbulence_complex_transform() { assert_eq!(render("tests/filters/feTurbulence/complex-transform"), 0); }
#[test] fn filters_feTurbulence_no_attributes() { assert_eq!(render("tests/filters/feTurbulence/no-attributes"), 0); }
#[test] fn filters_feTurbulence_numOctaves_eq__1() { assert_eq!(render("tests/filters/feTurbulence/numOctaves=-1"), 0); }
#[test] fn filters_feTurbulence_numOctaves_eq_0() { assert_eq!(render("tests/filters/feTurbulence/numOctaves=0"), 0); }
#[test] fn filters_feTurbulence_numOctaves_eq_5() { assert_eq!(render("tests/filters/feTurbulence/numOctaves=5"), 0); }
#[test] fn filters_feTurbulence_primitiveUnits_eq_objectBoundingBox() { assert_eq!(render("tests/filters/feTurbulence/primitiveUnits=objectBoundingBox"), 0); }
#[test] fn filters_feTurbulence_seed_eq__20() { assert_eq!(render("tests/filters/feTurbulence/seed=-20"), 0); }
#[test] fn filters_feTurbulence_seed_eq_1_5() { assert_eq!(render("tests/filters/feTurbulence/seed=1.5"), 0); }
#[test] fn filters_feTurbulence_seed_eq_20() { assert_eq!(render("tests/filters/feTurbulence/seed=20"), 0); }
#[test] fn filters_feTurbulence_stitchTiles_eq_stitch() { assert_eq!(render("tests/filters/feTurbulence/stitchTiles=stitch"), 0); }
#[test] fn filters_feTurbulence_type_eq_fractalNoise() { assert_eq!(render("tests/filters/feTurbulence/type=fractalNoise"), 0); }
#[test] fn filters_feTurbulence_type_eq_invalid() { assert_eq!(render("tests/filters/feTurbulence/type=invalid"), 0); }
#[test] fn filters_filter_color_interpolation_filters_eq_sRGB() { assert_eq!(render("tests/filters/filter/color-interpolation-filters=sRGB"), 0); }
#[test] fn filters_filter_complex_order_and_xlink_href() { assert_eq!(render("tests/filters/filter/complex-order-and-xlink-href"), 0); }
#[test] fn filters_filter_content_outside_the_canvas_2() { assert_eq!(render("tests/filters/filter/content-outside-the-canvas-2"), 0); }
#[test] fn filters_filter_content_outside_the_canvas() { assert_eq!(render("tests/filters/filter/content-outside-the-canvas"), 0); }
#[test] fn filters_filter_default_color_interpolation_filters() { assert_eq!(render("tests/filters/filter/default-color-interpolation-filters"), 0); }
#[test] fn filters_filter_everything_via_xlink_href() { assert_eq!(render("tests/filters/filter/everything-via-xlink-href"), 0); }
#[test] fn filters_filter_global_transform() { assert_eq!(render("tests/filters/filter/global-transform"), 0); }
#[test] fn filters_filter_huge_region() { assert_eq!(render("tests/filters/filter/huge-region"), 0); }
#[test] fn filters_filter_in_to_invalid_1() { assert_eq!(render("tests/filters/filter/in-to-invalid-1"), 0); }
#[test] fn filters_filter_in_to_invalid_2() { assert_eq!(render("tests/filters/filter/in-to-invalid-2"), 0); }
#[test] fn filters_filter_in_eq_BackgroundAlpha_with_enable_background() { assert_eq!(render("tests/filters/filter/in=BackgroundAlpha-with-enable-background"), 0); }
#[test] fn filters_filter_in_eq_BackgroundAlpha() { assert_eq!(render("tests/filters/filter/in=BackgroundAlpha"), 0); }
#[test] fn filters_filter_in_eq_BackgroundImage_with_enable_background() { assert_eq!(render("tests/filters/filter/in=BackgroundImage-with-enable-background"), 0); }
#[test] fn filters_filter_in_eq_BackgroundImage() { assert_eq!(render("tests/filters/filter/in=BackgroundImage"), 0); }
#[test] fn filters_filter_in_eq_FillPaint_on_g_without_children() { assert_eq!(render("tests/filters/filter/in=FillPaint-on-g-without-children"), 0); }
#[test] fn filters_filter_in_eq_FillPaint_with_gradient() { assert_eq!(render("tests/filters/filter/in=FillPaint-with-gradient"), 0); }
#[test] fn filters_filter_in_eq_FillPaint_with_pattern() { assert_eq!(render("tests/filters/filter/in=FillPaint-with-pattern"), 0); }
#[test] fn filters_filter_in_eq_FillPaint_with_target_on_g() { assert_eq!(render("tests/filters/filter/in=FillPaint-with-target-on-g"), 0); }
#[test] fn filters_filter_in_eq_FillPaint() { assert_eq!(render("tests/filters/filter/in=FillPaint"), 0); }
#[test] fn filters_filter_in_eq_SourceAlpha() { assert_eq!(render("tests/filters/filter/in=SourceAlpha"), 0); }
#[test] fn filters_filter_in_eq_StrokePaint() { assert_eq!(render("tests/filters/filter/in=StrokePaint"), 0); }
#[test] fn filters_filter_initial_transform() { assert_eq!(render("tests/filters/filter/initial-transform"), 0); }
#[test] fn filters_filter_invalid_FuncIRI() { assert_eq!(render("tests/filters/filter/invalid-FuncIRI"), 0); }
#[test] fn filters_filter_invalid_filterUnits() { assert_eq!(render("tests/filters/filter/invalid-filterUnits"), 0); }
#[test] fn filters_filter_invalid_primitive_1() { assert_eq!(render("tests/filters/filter/invalid-primitive-1"), 0); }
#[test] fn filters_filter_invalid_primitive_2() { assert_eq!(render("tests/filters/filter/invalid-primitive-2"), 0); }
#[test] fn filters_filter_invalid_region() { assert_eq!(render("tests/filters/filter/invalid-region"), 0); }
#[test] fn filters_filter_invalid_subregion() { assert_eq!(render("tests/filters/filter/invalid-subregion"), 0); }
#[test] fn filters_filter_invalid_xlink_href() { assert_eq!(render("tests/filters/filter/invalid-xlink-href"), 0); }
#[test] fn filters_filter_multiple_primitives_1() { assert_eq!(render("tests/filters/filter/multiple-primitives-1"), 0); }
#[test] fn filters_filter_multiple_primitives_2() { assert_eq!(render("tests/filters/filter/multiple-primitives-2"), 0); }
#[test] fn filters_filter_multiple_primitives_3() { assert_eq!(render("tests/filters/filter/multiple-primitives-3"), 0); }
#[test] fn filters_filter_multiple_primitives_4() { assert_eq!(render("tests/filters/filter/multiple-primitives-4"), 0); }
#[test] fn filters_filter_negative_subregion() { assert_eq!(render("tests/filters/filter/negative-subregion"), 0); }
#[test] fn filters_filter_no_children() { assert_eq!(render("tests/filters/filter/no-children"), 0); }
#[test] fn filters_filter_none() { assert_eq!(render("tests/filters/filter/none"), 0); }
#[test] fn filters_filter_on_a_thin_rect() { assert_eq!(render("tests/filters/filter/on-a-thin-rect"), 0); }
#[test] fn filters_filter_on_a_vertical_line() { assert_eq!(render("tests/filters/filter/on-a-vertical-line"), 0); }
#[test] fn filters_filter_on_an_empty_group_1() { assert_eq!(render("tests/filters/filter/on-an-empty-group-1"), 0); }
#[test] fn filters_filter_on_an_empty_group_2() { assert_eq!(render("tests/filters/filter/on-an-empty-group-2"), 0); }
#[test] fn filters_filter_on_group_with_child_outside_of_canvas() { assert_eq!(render("tests/filters/filter/on-group-with-child-outside-of-canvas"), 0); }
#[test] fn filters_filter_on_the_root_svg() { assert_eq!(render("tests/filters/filter/on-the-root-svg"), 0); }
#[test] fn filters_filter_on_zero_sized_shape() { assert_eq!(render("tests/filters/filter/on-zero-sized-shape"), 0); }
#[test] fn filters_filter_path_bbox() { assert_eq!(render("tests/filters/filter/path-bbox"), 0); }
#[test] fn filters_filter_primitiveUnits_eq_objectBoundingBox() { assert_eq!(render("tests/filters/filter/primitiveUnits=objectBoundingBox"), 0); }
#[test] fn filters_filter_recursive_xlink_href() { assert_eq!(render("tests/filters/filter/recursive-xlink-href"), 0); }
#[test] fn filters_filter_region_with_stroke() { assert_eq!(render("tests/filters/filter/region-with-stroke"), 0); }
#[test] fn filters_filter_self_recursive_xlink_href() { assert_eq!(render("tests/filters/filter/self-recursive-xlink-href"), 0); }
#[test] fn filters_filter_simple_case() { assert_eq!(render("tests/filters/filter/simple-case"), 0); }
#[test] fn filters_filter_some_attributes_via_xlink_href() { assert_eq!(render("tests/filters/filter/some-attributes-via-xlink-href"), 0); }
#[test] fn filters_filter_subregion_and_primitiveUnits_eq_objectBoundingBox_1() { assert_eq!(render("tests/filters/filter/subregion-and-primitiveUnits=objectBoundingBox-1"), 0); }
#[test] fn filters_filter_subregion_and_primitiveUnits_eq_objectBoundingBox_2() { assert_eq!(render("tests/filters/filter/subregion-and-primitiveUnits=objectBoundingBox-2"), 0); }
#[test] fn filters_filter_subregion_bigger_that_region() { assert_eq!(render("tests/filters/filter/subregion-bigger-that-region"), 0); }
#[test] fn filters_filter_transform_on_filter() { assert_eq!(render("tests/filters/filter/transform-on-filter"), 0); }
#[test] fn filters_filter_transform_on_shape_with_filter_region() { assert_eq!(render("tests/filters/filter/transform-on-shape-with-filter-region"), 0); }
#[test] fn filters_filter_transform_on_shape() { assert_eq!(render("tests/filters/filter/transform-on-shape"), 0); }
#[test] fn filters_filter_unresolved_xlink_href() { assert_eq!(render("tests/filters/filter/unresolved-xlink-href"), 0); }
#[test] fn filters_filter_with_clip_path_and_mask() { assert_eq!(render("tests/filters/filter/with-clip-path-and-mask"), 0); }
#[test] fn filters_filter_with_clip_path() { assert_eq!(render("tests/filters/filter/with-clip-path"), 0); }
#[test] fn filters_filter_with_mask_on_parent() { assert_eq!(render("tests/filters/filter/with-mask-on-parent"), 0); }
#[test] fn filters_filter_with_mask() { assert_eq!(render("tests/filters/filter/with-mask"), 0); }
#[test] fn filters_filter_with_multiple_transforms_1() { assert_eq!(render("tests/filters/filter/with-multiple-transforms-1"), 0); }
#[test] fn filters_filter_with_multiple_transforms_2() { assert_eq!(render("tests/filters/filter/with-multiple-transforms-2"), 0); }
#[test] fn filters_filter_with_region_and_filterUnits_eq_userSpaceOnUse() { assert_eq!(render("tests/filters/filter/with-region-and-filterUnits=userSpaceOnUse"), 0); }
#[test] fn filters_filter_with_region_and_subregion() { assert_eq!(render("tests/filters/filter/with-region-and-subregion"), 0); }
#[test] fn filters_filter_with_region_outside_the_canvas() { assert_eq!(render("tests/filters/filter/with-region-outside-the-canvas"), 0); }
#[test] fn filters_filter_with_region_outside_the_viewbox() { assert_eq!(render("tests/filters/filter/with-region-outside-the-viewbox"), 0); }
#[test] fn filters_filter_with_region() { assert_eq!(render("tests/filters/filter/with-region"), 0); }
#[test] fn filters_filter_with_subregion_1() { assert_eq!(render("tests/filters/filter/with-subregion-1"), 0); }
#[test] fn filters_filter_with_subregion_2() { assert_eq!(render("tests/filters/filter/with-subregion-2"), 0); }
#[test] fn filters_filter_with_subregion_3() { assert_eq!(render("tests/filters/filter/with-subregion-3"), 0); }
#[test] fn filters_filter_with_transform_outside_of_canvas() { assert_eq!(render("tests/filters/filter/with-transform-outside-of-canvas"), 0); }
#[test] fn filters_filter_without_region_and_filterUnits_eq_userSpaceOnUse() { assert_eq!(render("tests/filters/filter/without-region-and-filterUnits=userSpaceOnUse"), 0); }
#[test] fn filters_filter_zero_sized_subregion() { assert_eq!(render("tests/filters/filter/zero-sized-subregion"), 0); }
#[test] fn filters_filter_functions_blur_function_mm_value() { assert_eq!(render("tests/filters/filter-functions/blur-function-mm-value"), 0); }
#[test] fn filters_filter_functions_blur_function_negative_value() { assert_eq!(render("tests/filters/filter-functions/blur-function-negative-value"), 0); }
#[test] fn filters_filter_functions_blur_function_no_values() { assert_eq!(render("tests/filters/filter-functions/blur-function-no-values"), 0); }
#[test] fn filters_filter_functions_blur_function_percent_value() { assert_eq!(render("tests/filters/filter-functions/blur-function-percent-value"), 0); }
#[test] fn filters_filter_functions_blur_function_two_values() { assert_eq!(render("tests/filters/filter-functions/blur-function-two-values"), 0); }
#[test] fn filters_filter_functions_blur_function() { assert_eq!(render("tests/filters/filter-functions/blur-function"), 0); }
#[test] fn filters_filter_functions_color_adjust_functions_0percent() { assert_eq!(render("tests/filters/filter-functions/color-adjust-functions-0percent"), 0); }
#[test] fn filters_filter_functions_color_adjust_functions_100percent() { assert_eq!(render("tests/filters/filter-functions/color-adjust-functions-100percent"), 0); }
#[test] fn filters_filter_functions_color_adjust_functions_2() { assert_eq!(render("tests/filters/filter-functions/color-adjust-functions-2"), 0); }
#[test] fn filters_filter_functions_color_adjust_functions_200percent() { assert_eq!(render("tests/filters/filter-functions/color-adjust-functions-200percent"), 0); }
#[test] fn filters_filter_functions_color_adjust_functions_50percent() { assert_eq!(render("tests/filters/filter-functions/color-adjust-functions-50percent"), 0); }
#[test] fn filters_filter_functions_color_adjust_functions_default_value() { assert_eq!(render("tests/filters/filter-functions/color-adjust-functions-default-value"), 0); }
#[test] fn filters_filter_functions_color_adjust_functions_negative() { assert_eq!(render("tests/filters/filter-functions/color-adjust-functions-negative"), 0); }
#[test] fn filters_filter_functions_drop_shadow_function_color_as_attribute() { assert_eq!(render("tests/filters/filter-functions/drop-shadow-function-color-as-attribute"), 0); }
#[test] fn filters_filter_functions_drop_shadow_function_color_last() { assert_eq!(render("tests/filters/filter-functions/drop-shadow-function-color-last"), 0); }
#[test] fn filters_filter_functions_drop_shadow_function_comma_separated() { assert_eq!(render("tests/filters/filter-functions/drop-shadow-function-comma-separated"), 0); }
#[test] fn filters_filter_functions_drop_shadow_function_currentColor() { assert_eq!(render("tests/filters/filter-functions/drop-shadow-function-currentColor"), 0); }
#[test] fn filters_filter_functions_drop_shadow_function_em_values() { assert_eq!(render("tests/filters/filter-functions/drop-shadow-function-em-values"), 0); }
#[test] fn filters_filter_functions_drop_shadow_function_extra_value() { assert_eq!(render("tests/filters/filter-functions/drop-shadow-function-extra-value"), 0); }
#[test] fn filters_filter_functions_drop_shadow_function_filter_region() { assert_eq!(render("tests/filters/filter-functions/drop-shadow-function-filter-region"), 0); }
#[test] fn filters_filter_functions_drop_shadow_function_mm_values() { assert_eq!(render("tests/filters/filter-functions/drop-shadow-function-mm-values"), 0); }
#[test] fn filters_filter_functions_drop_shadow_function_no_color() { assert_eq!(render("tests/filters/filter-functions/drop-shadow-function-no-color"), 0); }
#[test] fn filters_filter_functions_drop_shadow_function_no_values() { assert_eq!(render("tests/filters/filter-functions/drop-shadow-function-no-values"), 0); }
#[test] fn filters_filter_functions_drop_shadow_function_only_X_offset() { assert_eq!(render("tests/filters/filter-functions/drop-shadow-function-only-X-offset"), 0); }
#[test] fn filters_filter_functions_drop_shadow_function_only_offset() { assert_eq!(render("tests/filters/filter-functions/drop-shadow-function-only-offset"), 0); }
#[test] fn filters_filter_functions_drop_shadow_function_percent_values() { assert_eq!(render("tests/filters/filter-functions/drop-shadow-function-percent-values"), 0); }
#[test] fn filters_filter_functions_drop_shadow_function() { assert_eq!(render("tests/filters/filter-functions/drop-shadow-function"), 0); }
#[test] fn filters_filter_functions_grayscale_and_opacity() { assert_eq!(render("tests/filters/filter-functions/grayscale-and-opacity"), 0); }
#[test] fn filters_filter_functions_hue_rotate_function_0_25turn() { assert_eq!(render("tests/filters/filter-functions/hue-rotate-function-0.25turn"), 0); }
#[test] fn filters_filter_functions_hue_rotate_function_45() { assert_eq!(render("tests/filters/filter-functions/hue-rotate-function-45"), 0); }
#[test] fn filters_filter_functions_hue_rotate_function_45deg() { assert_eq!(render("tests/filters/filter-functions/hue-rotate-function-45deg"), 0); }
#[test] fn filters_filter_functions_hue_rotate_function_45grad() { assert_eq!(render("tests/filters/filter-functions/hue-rotate-function-45grad"), 0); }
#[test] fn filters_filter_functions_hue_rotate_function_45rad() { assert_eq!(render("tests/filters/filter-functions/hue-rotate-function-45rad"), 0); }
#[test] fn filters_filter_functions_hue_rotate_function_999deg() { assert_eq!(render("tests/filters/filter-functions/hue-rotate-function-999deg"), 0); }
#[test] fn filters_filter_functions_hue_rotate_function_default_value() { assert_eq!(render("tests/filters/filter-functions/hue-rotate-function-default-value"), 0); }
#[test] fn filters_filter_functions_hue_rotate_function_zero() { assert_eq!(render("tests/filters/filter-functions/hue-rotate-function-zero"), 0); }
#[test] fn filters_filter_functions_nested_filters() { assert_eq!(render("tests/filters/filter-functions/nested-filters"), 0); }
#[test] fn filters_filter_functions_one_invalid_function_in_list() { assert_eq!(render("tests/filters/filter-functions/one-invalid-function-in-list"), 0); }
#[test] fn filters_filter_functions_one_invalid_url_in_list() { assert_eq!(render("tests/filters/filter-functions/one-invalid-url-in-list"), 0); }
#[test] fn filters_filter_functions_two_drop_shadow_function() { assert_eq!(render("tests/filters/filter-functions/two-drop-shadow-function"), 0); }
#[test] fn filters_filter_functions_two_exact_urls() { assert_eq!(render("tests/filters/filter-functions/two-exact-urls"), 0); }
#[test] fn filters_filter_functions_two_urls() { assert_eq!(render("tests/filters/filter-functions/two-urls"), 0); }
#[test] fn filters_filter_functions_url_and_grayscale() { assert_eq!(render("tests/filters/filter-functions/url-and-grayscale"), 0); }
#[test] fn filters_flood_color_hsla_color() { assert_eq!(render("tests/filters/flood-color/hsla-color"), 0); }
#[test] fn filters_flood_color_inheritance_1() { assert_eq!(render("tests/filters/flood-color/inheritance-1"), 0); }
#[test] fn filters_flood_color_inheritance_2() { assert_eq!(render("tests/filters/flood-color/inheritance-2"), 0); }
#[test] fn filters_flood_color_inheritance_3() { assert_eq!(render("tests/filters/flood-color/inheritance-3"), 0); }
#[test] fn filters_flood_color_inheritance_4() { assert_eq!(render("tests/filters/flood-color/inheritance-4"), 0); }
#[test] fn filters_flood_color_inheritance_5() { assert_eq!(render("tests/filters/flood-color/inheritance-5"), 0); }
#[test] fn filters_flood_color_simple_case() { assert_eq!(render("tests/filters/flood-color/simple-case"), 0); }
#[test] fn filters_flood_opacity_50percent() { assert_eq!(render("tests/filters/flood-opacity/50percent"), 0); }
#[test] fn filters_flood_opacity_simple_case() { assert_eq!(render("tests/filters/flood-opacity/simple-case"), 0); }
#[test] fn masking_clip_simple_case() { assert_eq!(render("tests/masking/clip/simple-case"), 0); }
#[test] fn masking_clip_rule_clip_rule_eq_evenodd() { assert_eq!(render("tests/masking/clip-rule/clip-rule=evenodd"), 0); }
#[test] fn masking_clipPath_circle_shorthand_with_stroke_box() { assert_eq!(render("tests/masking/clipPath/circle-shorthand-with-stroke-box"), 0); }
#[test] fn masking_clipPath_circle_shorthand_with_view_box() { assert_eq!(render("tests/masking/clipPath/circle-shorthand-with-view-box"), 0); }
#[test] fn masking_clipPath_circle_shorthand() { assert_eq!(render("tests/masking/clipPath/circle-shorthand"), 0); }
#[test] fn masking_clipPath_clip_path_on_child_with_transform() { assert_eq!(render("tests/masking/clipPath/clip-path-on-child-with-transform"), 0); }
#[test] fn masking_clipPath_clip_path_on_child() { assert_eq!(render("tests/masking/clipPath/clip-path-on-child"), 0); }
#[test] fn masking_clipPath_clip_path_on_children() { assert_eq!(render("tests/masking/clipPath/clip-path-on-children"), 0); }
#[test] fn masking_clipPath_clip_path_on_self_2() { assert_eq!(render("tests/masking/clipPath/clip-path-on-self-2"), 0); }
#[test] fn masking_clipPath_clip_path_on_self() { assert_eq!(render("tests/masking/clipPath/clip-path-on-self"), 0); }
#[test] fn masking_clipPath_clip_path_with_transform_on_text() { assert_eq!(render("tests/masking/clipPath/clip-path-with-transform-on-text"), 0); }
#[test] fn masking_clipPath_clip_path_with_transform() { assert_eq!(render("tests/masking/clipPath/clip-path-with-transform"), 0); }
#[test] fn masking_clipPath_clip_rule_from_parent_node() { assert_eq!(render("tests/masking/clipPath/clip-rule-from-parent-node"), 0); }
#[test] fn masking_clipPath_clip_rule_eq_evenodd() { assert_eq!(render("tests/masking/clipPath/clip-rule=evenodd"), 0); }
#[test] fn masking_clipPath_clipPathUnits_eq_objectBoundingBox() { assert_eq!(render("tests/masking/clipPath/clipPathUnits=objectBoundingBox"), 0); }
#[test] fn masking_clipPath_clipping_with_complex_text_1() { assert_eq!(render("tests/masking/clipPath/clipping-with-complex-text-1"), 0); }
#[test] fn masking_clipPath_clipping_with_complex_text_2() { assert_eq!(render("tests/masking/clipPath/clipping-with-complex-text-2"), 0); }
#[test] fn masking_clipPath_clipping_with_complex_text_and_clip_rule() { assert_eq!(render("tests/masking/clipPath/clipping-with-complex-text-and-clip-rule"), 0); }
#[test] fn masking_clipPath_clipping_with_text() { assert_eq!(render("tests/masking/clipPath/clipping-with-text"), 0); }
#[test] fn masking_clipPath_fill_has_no_effect() { assert_eq!(render("tests/masking/clipPath/fill-has-no-effect"), 0); }
#[test] fn masking_clipPath_filter_has_no_effect() { assert_eq!(render("tests/masking/clipPath/filter-has-no-effect"), 0); }
#[test] fn masking_clipPath_g_is_not_a_valid_child() { assert_eq!(render("tests/masking/clipPath/g-is-not-a-valid-child"), 0); }
#[test] fn masking_clipPath_image_is_not_a_valid_child() { assert_eq!(render("tests/masking/clipPath/image-is-not-a-valid-child"), 0); }
#[test] fn masking_clipPath_invalid_FuncIRI() { assert_eq!(render("tests/masking/clipPath/invalid-FuncIRI"), 0); }
#[test] fn masking_clipPath_invalid_clip_path_on_child() { assert_eq!(render("tests/masking/clipPath/invalid-clip-path-on-child"), 0); }
#[test] fn masking_clipPath_invalid_clip_path_on_self() { assert_eq!(render("tests/masking/clipPath/invalid-clip-path-on-self"), 0); }
#[test] fn masking_clipPath_invalid_transform_on_clipPath() { assert_eq!(render("tests/masking/clipPath/invalid-transform-on-clipPath"), 0); }
#[test] fn masking_clipPath_invisible_child_1() { assert_eq!(render("tests/masking/clipPath/invisible-child-1"), 0); }
#[test] fn masking_clipPath_invisible_child_2() { assert_eq!(render("tests/masking/clipPath/invisible-child-2"), 0); }
#[test] fn masking_clipPath_line_is_not_a_valid_child() { assert_eq!(render("tests/masking/clipPath/line-is-not-a-valid-child"), 0); }
#[test] fn masking_clipPath_malformed_path_child() { assert_eq!(render("tests/masking/clipPath/malformed-path-child"), 0); }
#[test] fn masking_clipPath_mask_has_no_effect() { assert_eq!(render("tests/masking/clipPath/mask-has-no-effect"), 0); }
#[test] fn masking_clipPath_mixed_clip_rule() { assert_eq!(render("tests/masking/clipPath/mixed-clip-rule"), 0); }
#[test] fn masking_clipPath_multiple_children() { assert_eq!(render("tests/masking/clipPath/multiple-children"), 0); }
#[test] fn masking_clipPath_nested_clip_path() { assert_eq!(render("tests/masking/clipPath/nested-clip-path"), 0); }
#[test] fn masking_clipPath_no_children() { assert_eq!(render("tests/masking/clipPath/no-children"), 0); }
#[test] fn masking_clipPath_none() { assert_eq!(render("tests/masking/clipPath/none"), 0); }
#[test] fn masking_clipPath_on_a_horizontal_line() { assert_eq!(render("tests/masking/clipPath/on-a-horizontal-line"), 0); }
#[test] fn masking_clipPath_on_the_root_svg_with_size() { assert_eq!(render("tests/masking/clipPath/on-the-root-svg-with-size"), 0); }
#[test] fn masking_clipPath_on_the_root_svg_without_size() { assert_eq!(render("tests/masking/clipPath/on-the-root-svg-without-size"), 0); }
#[test] fn masking_clipPath_opacity_has_no_effect() { assert_eq!(render("tests/masking/clipPath/opacity-has-no-effect"), 0); }
#[test] fn masking_clipPath_overlapped_shapes_with_evenodd() { assert_eq!(render("tests/masking/clipPath/overlapped-shapes-with-evenodd"), 0); }
#[test] fn masking_clipPath_recursive_on_child() { assert_eq!(render("tests/masking/clipPath/recursive-on-child"), 0); }
#[test] fn masking_clipPath_recursive_on_self() { assert_eq!(render("tests/masking/clipPath/recursive-on-self"), 0); }
#[test] fn masking_clipPath_recursive() { assert_eq!(render("tests/masking/clipPath/recursive"), 0); }
#[test] fn masking_clipPath_self_recursive() { assert_eq!(render("tests/masking/clipPath/self-recursive"), 0); }
#[test] fn masking_clipPath_simple_case() { assert_eq!(render("tests/masking/clipPath/simple-case"), 0); }
#[test] fn masking_clipPath_stroke_has_no_effect() { assert_eq!(render("tests/masking/clipPath/stroke-has-no-effect"), 0); }
#[test] fn masking_clipPath_switch_is_not_a_valid_child() { assert_eq!(render("tests/masking/clipPath/switch-is-not-a-valid-child"), 0); }
#[test] fn masking_clipPath_symbol_via_use_is_not_a_valid_child() { assert_eq!(render("tests/masking/clipPath/symbol-via-use-is-not-a-valid-child"), 0); }
#[test] fn masking_clipPath_transform_on_clipPath() { assert_eq!(render("tests/masking/clipPath/transform-on-clipPath"), 0); }
#[test] fn masking_clipPath_with_invalid_child_via_use() { assert_eq!(render("tests/masking/clipPath/with-invalid-child-via-use"), 0); }
#[test] fn masking_clipPath_with_marker_on_clip() { assert_eq!(render("tests/masking/clipPath/with-marker-on-clip"), 0); }
#[test] fn masking_clipPath_with_use_child() { assert_eq!(render("tests/masking/clipPath/with-use-child"), 0); }
#[test] fn masking_mask_color_interpolation_eq_linearRGB() { assert_eq!(render("tests/masking/mask/color-interpolation=linearRGB"), 0); }
#[test] fn masking_mask_half_width_region_with_rotation() { assert_eq!(render("tests/masking/mask/half-width-region-with-rotation"), 0); }
#[test] fn masking_mask_invalid_FuncIRI() { assert_eq!(render("tests/masking/mask/invalid-FuncIRI"), 0); }
#[test] fn masking_mask_invalid_child() { assert_eq!(render("tests/masking/mask/invalid-child"), 0); }
#[test] fn masking_mask_invisible_child_1() { assert_eq!(render("tests/masking/mask/invisible-child-1"), 0); }
#[test] fn masking_mask_invisible_child_2() { assert_eq!(render("tests/masking/mask/invisible-child-2"), 0); }
#[test] fn masking_mask_mask_on_child() { assert_eq!(render("tests/masking/mask/mask-on-child"), 0); }
#[test] fn masking_mask_mask_on_self_with_mask_type_eq_alpha() { assert_eq!(render("tests/masking/mask/mask-on-self-with-mask-type=alpha"), 0); }
#[test] fn masking_mask_mask_on_self_with_mixed_mask_type() { assert_eq!(render("tests/masking/mask/mask-on-self-with-mixed-mask-type"), 0); }
#[test] fn masking_mask_mask_on_self() { assert_eq!(render("tests/masking/mask/mask-on-self"), 0); }
#[test] fn masking_mask_mask_type_in_style() { assert_eq!(render("tests/masking/mask/mask-type-in-style"), 0); }
#[test] fn masking_mask_mask_type_eq_alpha() { assert_eq!(render("tests/masking/mask/mask-type=alpha"), 0); }
#[test] fn masking_mask_mask_type_eq_invalid() { assert_eq!(render("tests/masking/mask/mask-type=invalid"), 0); }
#[test] fn masking_mask_mask_type_eq_luminance() { assert_eq!(render("tests/masking/mask/mask-type=luminance"), 0); }
#[test] fn masking_mask_maskContentUnits_eq_objectBoundingBox() { assert_eq!(render("tests/masking/mask/maskContentUnits=objectBoundingBox"), 0); }
#[test] fn masking_mask_maskUnits_eq_objectBoundingBox_with_percent() { assert_eq!(render("tests/masking/mask/maskUnits=objectBoundingBox-with-percent"), 0); }
#[test] fn masking_mask_maskUnits_eq_userSpaceOnUse_with_percent() { assert_eq!(render("tests/masking/mask/maskUnits=userSpaceOnUse-with-percent"), 0); }
#[test] fn masking_mask_maskUnits_eq_userSpaceOnUse_with_rect() { assert_eq!(render("tests/masking/mask/maskUnits=userSpaceOnUse-with-rect"), 0); }
#[test] fn masking_mask_maskUnits_eq_userSpaceOnUse_with_width_only() { assert_eq!(render("tests/masking/mask/maskUnits=userSpaceOnUse-with-width-only"), 0); }
#[test] fn masking_mask_maskUnits_eq_userSpaceOnUse_without_rect() { assert_eq!(render("tests/masking/mask/maskUnits=userSpaceOnUse-without-rect"), 0); }
#[test] fn masking_mask_nested_objectBoundingBox() { assert_eq!(render("tests/masking/mask/nested-objectBoundingBox"), 0); }
#[test] fn masking_mask_no_children() { assert_eq!(render("tests/masking/mask/no-children"), 0); }
#[test] fn masking_mask_none() { assert_eq!(render("tests/masking/mask/none"), 0); }
#[test] fn masking_mask_on_a_horizontal_line() { assert_eq!(render("tests/masking/mask/on-a-horizontal-line"), 0); }
#[test] fn masking_mask_on_a_small_object() { assert_eq!(render("tests/masking/mask/on-a-small-object"), 0); }
#[test] fn masking_mask_on_group_with_transform() { assert_eq!(render("tests/masking/mask/on-group-with-transform"), 0); }
#[test] fn masking_mask_recursive_on_child() { assert_eq!(render("tests/masking/mask/recursive-on-child"), 0); }
#[test] fn masking_mask_recursive_on_self() { assert_eq!(render("tests/masking/mask/recursive-on-self"), 0); }
#[test] fn masking_mask_recursive() { assert_eq!(render("tests/masking/mask/recursive"), 0); }
#[test] fn masking_mask_self_recursive() { assert_eq!(render("tests/masking/mask/self-recursive"), 0); }
#[test] fn masking_mask_simple_case() { assert_eq!(render("tests/masking/mask/simple-case"), 0); }
#[test] fn masking_mask_transform_has_no_effect() { assert_eq!(render("tests/masking/mask/transform-has-no-effect"), 0); }
#[test] fn masking_mask_transform_on_shape() { assert_eq!(render("tests/masking/mask/transform-on-shape"), 0); }
#[test] fn masking_mask_with_clip_path() { assert_eq!(render("tests/masking/mask/with-clip-path"), 0); }
#[test] fn masking_mask_with_grayscale_image() { assert_eq!(render("tests/masking/mask/with-grayscale-image"), 0); }
#[test] fn masking_mask_with_image() { assert_eq!(render("tests/masking/mask/with-image"), 0); }
#[test] fn masking_mask_with_opacity_1() { assert_eq!(render("tests/masking/mask/with-opacity-1"), 0); }
#[test] fn masking_mask_with_opacity_2() { assert_eq!(render("tests/masking/mask/with-opacity-2"), 0); }
#[test] fn masking_mask_with_opacity_3() { assert_eq!(render("tests/masking/mask/with-opacity-3"), 0); }
#[test] fn paint_servers_linearGradient_attributes_via_xlink_href_complex_order() { assert_eq!(render("tests/paint-servers/linearGradient/attributes-via-xlink-href-complex-order"), 0); }
#[test] fn paint_servers_linearGradient_attributes_via_xlink_href_from_radialGradient() { assert_eq!(render("tests/paint-servers/linearGradient/attributes-via-xlink-href-from-radialGradient"), 0); }
#[test] fn paint_servers_linearGradient_attributes_via_xlink_href_from_rect() { assert_eq!(render("tests/paint-servers/linearGradient/attributes-via-xlink-href-from-rect"), 0); }
#[test] fn paint_servers_linearGradient_attributes_via_xlink_href_only_required() { assert_eq!(render("tests/paint-servers/linearGradient/attributes-via-xlink-href-only-required"), 0); }
#[test] fn paint_servers_linearGradient_attributes_via_xlink_href() { assert_eq!(render("tests/paint-servers/linearGradient/attributes-via-xlink-href"), 0); }
#[test] fn paint_servers_linearGradient_default_attributes() { assert_eq!(render("tests/paint-servers/linearGradient/default-attributes"), 0); }
#[test] fn paint_servers_linearGradient_gradientTransform_and_transform() { assert_eq!(render("tests/paint-servers/linearGradient/gradientTransform-and-transform"), 0); }
#[test] fn paint_servers_linearGradient_gradientTransform() { assert_eq!(render("tests/paint-servers/linearGradient/gradientTransform"), 0); }
#[test] fn paint_servers_linearGradient_gradientUnits_eq_objectBoundingBox_with_percent() { assert_eq!(render("tests/paint-servers/linearGradient/gradientUnits=objectBoundingBox-with-percent"), 0); }
#[test] fn paint_servers_linearGradient_gradientUnits_eq_userSpaceOnUse_with_percent() { assert_eq!(render("tests/paint-servers/linearGradient/gradientUnits=userSpaceOnUse-with-percent"), 0); }
#[test] fn paint_servers_linearGradient_gradientUnits_eq_userSpaceOnUse() { assert_eq!(render("tests/paint-servers/linearGradient/gradientUnits=userSpaceOnUse"), 0); }
#[test] fn paint_servers_linearGradient_hsla_color() { assert_eq!(render("tests/paint-servers/linearGradient/hsla-color"), 0); }
#[test] fn paint_servers_linearGradient_invalid_child_1() { assert_eq!(render("tests/paint-servers/linearGradient/invalid-child-1"), 0); }
#[test] fn paint_servers_linearGradient_invalid_child_2() { assert_eq!(render("tests/paint-servers/linearGradient/invalid-child-2"), 0); }
#[test] fn paint_servers_linearGradient_invalid_child_3() { assert_eq!(render("tests/paint-servers/linearGradient/invalid-child-3"), 0); }
#[test] fn paint_servers_linearGradient_invalid_gradientTransform() { assert_eq!(render("tests/paint-servers/linearGradient/invalid-gradientTransform"), 0); }
#[test] fn paint_servers_linearGradient_invalid_gradientUnits() { assert_eq!(render("tests/paint-servers/linearGradient/invalid-gradientUnits"), 0); }
#[test] fn paint_servers_linearGradient_invalid_spreadMethod() { assert_eq!(render("tests/paint-servers/linearGradient/invalid-spreadMethod"), 0); }
#[test] fn paint_servers_linearGradient_invalid_xlink_href() { assert_eq!(render("tests/paint-servers/linearGradient/invalid-xlink-href"), 0); }
#[test] fn paint_servers_linearGradient_many_stops() { assert_eq!(render("tests/paint-servers/linearGradient/many-stops"), 0); }
#[test] fn paint_servers_linearGradient_no_stops() { assert_eq!(render("tests/paint-servers/linearGradient/no-stops"), 0); }
#[test] fn paint_servers_linearGradient_recursive_xlink_href_1() { assert_eq!(render("tests/paint-servers/linearGradient/recursive-xlink-href-1"), 0); }
#[test] fn paint_servers_linearGradient_recursive_xlink_href_2() { assert_eq!(render("tests/paint-servers/linearGradient/recursive-xlink-href-2"), 0); }
#[test] fn paint_servers_linearGradient_recursive_xlink_href_3() { assert_eq!(render("tests/paint-servers/linearGradient/recursive-xlink-href-3"), 0); }
#[test] fn paint_servers_linearGradient_self_recursive_xlink_href() { assert_eq!(render("tests/paint-servers/linearGradient/self-recursive-xlink-href"), 0); }
#[test] fn paint_servers_linearGradient_single_stop_with_opacity_used_by_fill_and_stroke() { assert_eq!(render("tests/paint-servers/linearGradient/single-stop-with-opacity-used-by-fill-and-stroke"), 0); }
#[test] fn paint_servers_linearGradient_single_stop_with_opacity_used_by_fill() { assert_eq!(render("tests/paint-servers/linearGradient/single-stop-with-opacity-used-by-fill"), 0); }
#[test] fn paint_servers_linearGradient_single_stop_with_opacity_used_by_stroke() { assert_eq!(render("tests/paint-servers/linearGradient/single-stop-with-opacity-used-by-stroke"), 0); }
#[test] fn paint_servers_linearGradient_single_stop() { assert_eq!(render("tests/paint-servers/linearGradient/single-stop"), 0); }
#[test] fn paint_servers_linearGradient_spreadMethod_eq_pad() { assert_eq!(render("tests/paint-servers/linearGradient/spreadMethod=pad"), 0); }
#[test] fn paint_servers_linearGradient_spreadMethod_eq_reflect() { assert_eq!(render("tests/paint-servers/linearGradient/spreadMethod=reflect"), 0); }
#[test] fn paint_servers_linearGradient_spreadMethod_eq_repeat() { assert_eq!(render("tests/paint-servers/linearGradient/spreadMethod=repeat"), 0); }
#[test] fn paint_servers_linearGradient_stops_via_xlink_href_complex_order_1() { assert_eq!(render("tests/paint-servers/linearGradient/stops-via-xlink-href-complex-order-1"), 0); }
#[test] fn paint_servers_linearGradient_stops_via_xlink_href_complex_order_2() { assert_eq!(render("tests/paint-servers/linearGradient/stops-via-xlink-href-complex-order-2"), 0); }
#[test] fn paint_servers_linearGradient_stops_via_xlink_href_from_radialGradient() { assert_eq!(render("tests/paint-servers/linearGradient/stops-via-xlink-href-from-radialGradient"), 0); }
#[test] fn paint_servers_linearGradient_stops_via_xlink_href_from_rect() { assert_eq!(render("tests/paint-servers/linearGradient/stops-via-xlink-href-from-rect"), 0); }
#[test] fn paint_servers_linearGradient_stops_via_xlink_href() { assert_eq!(render("tests/paint-servers/linearGradient/stops-via-xlink-href"), 0); }
#[test] fn paint_servers_linearGradient_unresolved_xlink_href() { assert_eq!(render("tests/paint-servers/linearGradient/unresolved-xlink-href"), 0); }
#[test] fn paint_servers_pattern_attributes_via_xlink_href() { assert_eq!(render("tests/paint-servers/pattern/attributes-via-xlink-href"), 0); }
#[test] fn paint_servers_pattern_child_with_invalid_FuncIRI() { assert_eq!(render("tests/paint-servers/pattern/child-with-invalid-FuncIRI"), 0); }
#[test] fn paint_servers_pattern_children_via_xlink_href() { assert_eq!(render("tests/paint-servers/pattern/children-via-xlink-href"), 0); }
#[test] fn paint_servers_pattern_display_eq_none_on_child() { assert_eq!(render("tests/paint-servers/pattern/display=none-on-child"), 0); }
#[test] fn paint_servers_pattern_everything_via_xlink_href() { assert_eq!(render("tests/paint-servers/pattern/everything-via-xlink-href"), 0); }
#[test] fn paint_servers_pattern_invalid_patternTransform() { assert_eq!(render("tests/paint-servers/pattern/invalid-patternTransform"), 0); }
#[test] fn paint_servers_pattern_invalid_patternUnits_and_patternContentUnits() { assert_eq!(render("tests/paint-servers/pattern/invalid-patternUnits-and-patternContentUnits"), 0); }
#[test] fn paint_servers_pattern_missing_height() { assert_eq!(render("tests/paint-servers/pattern/missing-height"), 0); }
#[test] fn paint_servers_pattern_missing_width() { assert_eq!(render("tests/paint-servers/pattern/missing-width"), 0); }
#[test] fn paint_servers_pattern_nested_objectBoundingBox() { assert_eq!(render("tests/paint-servers/pattern/nested-objectBoundingBox"), 0); }
#[test] fn paint_servers_pattern_no_children() { assert_eq!(render("tests/paint-servers/pattern/no-children"), 0); }
#[test] fn paint_servers_pattern_out_of_order_referencing() { assert_eq!(render("tests/paint-servers/pattern/out-of-order-referencing"), 0); }
#[test] fn paint_servers_pattern_overflow_eq_visible() { assert_eq!(render("tests/paint-servers/pattern/overflow=visible"), 0); }
#[test] fn paint_servers_pattern_pattern_on_child() { assert_eq!(render("tests/paint-servers/pattern/pattern-on-child"), 0); }
#[test] fn paint_servers_pattern_patternContentUnits_with_viewBox() { assert_eq!(render("tests/paint-servers/pattern/patternContentUnits-with-viewBox"), 0); }
#[test] fn paint_servers_pattern_patternContentUnits_eq_objectBoundingBox() { assert_eq!(render("tests/paint-servers/pattern/patternContentUnits=objectBoundingBox"), 0); }
#[test] fn paint_servers_pattern_patternUnits_eq_objectBoundingBox_with_percent() { assert_eq!(render("tests/paint-servers/pattern/patternUnits=objectBoundingBox-with-percent"), 0); }
#[test] fn paint_servers_pattern_patternUnits_eq_objectBoundingBox() { assert_eq!(render("tests/paint-servers/pattern/patternUnits=objectBoundingBox"), 0); }
#[test] fn paint_servers_pattern_patternUnits_eq_userSpaceOnUse_with_percent() { assert_eq!(render("tests/paint-servers/pattern/patternUnits=userSpaceOnUse-with-percent"), 0); }
#[test] fn paint_servers_pattern_preserveAspectRatio() { assert_eq!(render("tests/paint-servers/pattern/preserveAspectRatio"), 0); }
#[test] fn paint_servers_pattern_recursive_on_child() { assert_eq!(render("tests/paint-servers/pattern/recursive-on-child"), 0); }
#[test] fn paint_servers_pattern_self_recursive_on_child() { assert_eq!(render("tests/paint-servers/pattern/self-recursive-on-child"), 0); }
#[test] fn paint_servers_pattern_self_recursive() { assert_eq!(render("tests/paint-servers/pattern/self-recursive"), 0); }
#[test] fn paint_servers_pattern_simple_case() { assert_eq!(render("tests/paint-servers/pattern/simple-case"), 0); }
#[test] fn paint_servers_pattern_text_child() { assert_eq!(render("tests/paint-servers/pattern/text-child"), 0); }
#[test] fn paint_servers_pattern_tiny_pattern_upscaled() { assert_eq!(render("tests/paint-servers/pattern/tiny-pattern-upscaled"), 0); }
#[test] fn paint_servers_pattern_transform_and_patternTransform() { assert_eq!(render("tests/paint-servers/pattern/transform-and-patternTransform"), 0); }
#[test] fn paint_servers_pattern_viewBox_via_xlink_href() { assert_eq!(render("tests/paint-servers/pattern/viewBox-via-xlink-href"), 0); }
#[test] fn paint_servers_pattern_with_patternTransform() { assert_eq!(render("tests/paint-servers/pattern/with-patternTransform"), 0); }
#[test] fn paint_servers_pattern_with_viewBox() { assert_eq!(render("tests/paint-servers/pattern/with-viewBox"), 0); }
#[test] fn paint_servers_pattern_with_x_and_y() { assert_eq!(render("tests/paint-servers/pattern/with-x-and-y"), 0); }
#[test] fn paint_servers_radialGradient_attributes_via_xlink_href_complex_order() { assert_eq!(render("tests/paint-servers/radialGradient/attributes-via-xlink-href-complex-order"), 0); }
#[test] fn paint_servers_radialGradient_attributes_via_xlink_href_from_linearGradient() { assert_eq!(render("tests/paint-servers/radialGradient/attributes-via-xlink-href-from-linearGradient"), 0); }
#[test] fn paint_servers_radialGradient_attributes_via_xlink_href_from_rect() { assert_eq!(render("tests/paint-servers/radialGradient/attributes-via-xlink-href-from-rect"), 0); }
#[test] fn paint_servers_radialGradient_attributes_via_xlink_href_only_required() { assert_eq!(render("tests/paint-servers/radialGradient/attributes-via-xlink-href-only-required"), 0); }
#[test] fn paint_servers_radialGradient_attributes_via_xlink_href() { assert_eq!(render("tests/paint-servers/radialGradient/attributes-via-xlink-href"), 0); }
#[test] fn paint_servers_radialGradient_default_attributes() { assert_eq!(render("tests/paint-servers/radialGradient/default-attributes"), 0); }
#[test] fn paint_servers_radialGradient_fr_eq__1() { assert_eq!(render("tests/paint-servers/radialGradient/fr=-1"), 0); }
#[test] fn paint_servers_radialGradient_fr_eq_0_2() { assert_eq!(render("tests/paint-servers/radialGradient/fr=0.2"), 0); }
#[test] fn paint_servers_radialGradient_fr_eq_0_5() { assert_eq!(render("tests/paint-servers/radialGradient/fr=0.5"), 0); }
#[test] fn paint_servers_radialGradient_fr_eq_0_7() { assert_eq!(render("tests/paint-servers/radialGradient/fr=0.7"), 0); }
#[test] fn paint_servers_radialGradient_fx_resolving_1() { assert_eq!(render("tests/paint-servers/radialGradient/fx-resolving-1"), 0); }
#[test] fn paint_servers_radialGradient_fx_resolving_2() { assert_eq!(render("tests/paint-servers/radialGradient/fx-resolving-2"), 0); }
#[test] fn paint_servers_radialGradient_fx_resolving_3() { assert_eq!(render("tests/paint-servers/radialGradient/fx-resolving-3"), 0); }
#[test] fn paint_servers_radialGradient_fy_resolving_1() { assert_eq!(render("tests/paint-servers/radialGradient/fy-resolving-1"), 0); }
#[test] fn paint_servers_radialGradient_fy_resolving_2() { assert_eq!(render("tests/paint-servers/radialGradient/fy-resolving-2"), 0); }
#[test] fn paint_servers_radialGradient_fy_resolving_3() { assert_eq!(render("tests/paint-servers/radialGradient/fy-resolving-3"), 0); }
#[test] fn paint_servers_radialGradient_gradientTransform_and_transform() { assert_eq!(render("tests/paint-servers/radialGradient/gradientTransform-and-transform"), 0); }
#[test] fn paint_servers_radialGradient_gradientTransform() { assert_eq!(render("tests/paint-servers/radialGradient/gradientTransform"), 0); }
#[test] fn paint_servers_radialGradient_gradientUnits_eq_objectBoundingBox_with_percent() { assert_eq!(render("tests/paint-servers/radialGradient/gradientUnits=objectBoundingBox-with-percent"), 0); }
#[test] fn paint_servers_radialGradient_gradientUnits_eq_userSpaceOnUse_with_percent() { assert_eq!(render("tests/paint-servers/radialGradient/gradientUnits=userSpaceOnUse-with-percent"), 0); }
#[test] fn paint_servers_radialGradient_gradientUnits_eq_userSpaceOnUse() { assert_eq!(render("tests/paint-servers/radialGradient/gradientUnits=userSpaceOnUse"), 0); }
#[test] fn paint_servers_radialGradient_hsla_color() { assert_eq!(render("tests/paint-servers/radialGradient/hsla-color"), 0); }
#[test] fn paint_servers_radialGradient_invalid_gradientTransform() { assert_eq!(render("tests/paint-servers/radialGradient/invalid-gradientTransform"), 0); }
#[test] fn paint_servers_radialGradient_invalid_gradientUnits() { assert_eq!(render("tests/paint-servers/radialGradient/invalid-gradientUnits"), 0); }
#[test] fn paint_servers_radialGradient_invalid_spreadMethod() { assert_eq!(render("tests/paint-servers/radialGradient/invalid-spreadMethod"), 0); }
#[test] fn paint_servers_radialGradient_invalid_xlink_href() { assert_eq!(render("tests/paint-servers/radialGradient/invalid-xlink-href"), 0); }
#[test] fn paint_servers_radialGradient_many_stops() { assert_eq!(render("tests/paint-servers/radialGradient/many-stops"), 0); }
#[test] fn paint_servers_radialGradient_negative_r() { assert_eq!(render("tests/paint-servers/radialGradient/negative-r"), 0); }
#[test] fn paint_servers_radialGradient_no_stops() { assert_eq!(render("tests/paint-servers/radialGradient/no-stops"), 0); }
#[test] fn paint_servers_radialGradient_recursive_xlink_href() { assert_eq!(render("tests/paint-servers/radialGradient/recursive-xlink-href"), 0); }
#[test] fn paint_servers_radialGradient_self_recursive_xlink_href() { assert_eq!(render("tests/paint-servers/radialGradient/self-recursive-xlink-href"), 0); }
#[test] fn paint_servers_radialGradient_single_stop() { assert_eq!(render("tests/paint-servers/radialGradient/single-stop"), 0); }
#[test] fn paint_servers_radialGradient_spreadMethod_eq_pad() { assert_eq!(render("tests/paint-servers/radialGradient/spreadMethod=pad"), 0); }
#[test] fn paint_servers_radialGradient_spreadMethod_eq_reflect() { assert_eq!(render("tests/paint-servers/radialGradient/spreadMethod=reflect"), 0); }
#[test] fn paint_servers_radialGradient_spreadMethod_eq_repeat() { assert_eq!(render("tests/paint-servers/radialGradient/spreadMethod=repeat"), 0); }
#[test] fn paint_servers_radialGradient_stops_via_xlink_href_complex_order() { assert_eq!(render("tests/paint-servers/radialGradient/stops-via-xlink-href-complex-order"), 0); }
#[test] fn paint_servers_radialGradient_stops_via_xlink_href_from_linearGradient() { assert_eq!(render("tests/paint-servers/radialGradient/stops-via-xlink-href-from-linearGradient"), 0); }
#[test] fn paint_servers_radialGradient_stops_via_xlink_href_from_rect() { assert_eq!(render("tests/paint-servers/radialGradient/stops-via-xlink-href-from-rect"), 0); }
#[test] fn paint_servers_radialGradient_stops_via_xlink_href() { assert_eq!(render("tests/paint-servers/radialGradient/stops-via-xlink-href"), 0); }
#[test] fn paint_servers_radialGradient_unresolved_xlink_href() { assert_eq!(render("tests/paint-servers/radialGradient/unresolved-xlink-href"), 0); }
#[test] fn paint_servers_radialGradient_xlink_href_not_to_gradient() { assert_eq!(render("tests/paint-servers/radialGradient/xlink-href-not-to-gradient"), 0); }
#[test] fn paint_servers_radialGradient_zero_r_with_stop_opacity_1() { assert_eq!(render("tests/paint-servers/radialGradient/zero-r-with-stop-opacity-1"), 0); }
#[test] fn paint_servers_radialGradient_zero_r_with_stop_opacity_2() { assert_eq!(render("tests/paint-servers/radialGradient/zero-r-with-stop-opacity-2"), 0); }
#[test] fn paint_servers_radialGradient_zero_r() { assert_eq!(render("tests/paint-servers/radialGradient/zero-r"), 0); }
#[test] fn paint_servers_stop_equal_stop_color() { assert_eq!(render("tests/paint-servers/stop/equal-stop-color"), 0); }
#[test] fn paint_servers_stop_hsla_color() { assert_eq!(render("tests/paint-servers/stop/hsla-color"), 0); }
#[test] fn paint_servers_stop_invalid_offset_1() { assert_eq!(render("tests/paint-servers/stop/invalid-offset-1"), 0); }
#[test] fn paint_servers_stop_invalid_offset_2() { assert_eq!(render("tests/paint-servers/stop/invalid-offset-2"), 0); }
#[test] fn paint_servers_stop_missing_offset_1() { assert_eq!(render("tests/paint-servers/stop/missing-offset-1"), 0); }
#[test] fn paint_servers_stop_missing_offset_2() { assert_eq!(render("tests/paint-servers/stop/missing-offset-2"), 0); }
#[test] fn paint_servers_stop_missing_offset_3() { assert_eq!(render("tests/paint-servers/stop/missing-offset-3"), 0); }
#[test] fn paint_servers_stop_missing_offset_4() { assert_eq!(render("tests/paint-servers/stop/missing-offset-4"), 0); }
#[test] fn paint_servers_stop_missing_offset_5() { assert_eq!(render("tests/paint-servers/stop/missing-offset-5"), 0); }
#[test] fn paint_servers_stop_missing_offset_6() { assert_eq!(render("tests/paint-servers/stop/missing-offset-6"), 0); }
#[test] fn paint_servers_stop_missing_offset_7() { assert_eq!(render("tests/paint-servers/stop/missing-offset-7"), 0); }
#[test] fn paint_servers_stop_no_stop_color() { assert_eq!(render("tests/paint-servers/stop/no-stop-color"), 0); }
#[test] fn paint_servers_stop_offset_clamping_with_percent() { assert_eq!(render("tests/paint-servers/stop/offset-clamping-with-percent"), 0); }
#[test] fn paint_servers_stop_offset_clamping() { assert_eq!(render("tests/paint-servers/stop/offset-clamping"), 0); }
#[test] fn paint_servers_stop_offset_with_percent() { assert_eq!(render("tests/paint-servers/stop/offset-with-percent"), 0); }
#[test] fn paint_servers_stop_stop_color_with_currentColor_1() { assert_eq!(render("tests/paint-servers/stop/stop-color-with-currentColor-1"), 0); }
#[test] fn paint_servers_stop_stop_color_with_currentColor_2() { assert_eq!(render("tests/paint-servers/stop/stop-color-with-currentColor-2"), 0); }
#[test] fn paint_servers_stop_stop_color_with_currentColor_3() { assert_eq!(render("tests/paint-servers/stop/stop-color-with-currentColor-3"), 0); }
#[test] fn paint_servers_stop_stop_color_with_currentColor_4() { assert_eq!(render("tests/paint-servers/stop/stop-color-with-currentColor-4"), 0); }
#[test] fn paint_servers_stop_stop_color_with_inherit_1() { assert_eq!(render("tests/paint-servers/stop/stop-color-with-inherit-1"), 0); }
#[test] fn paint_servers_stop_stop_color_with_inherit_2() { assert_eq!(render("tests/paint-servers/stop/stop-color-with-inherit-2"), 0); }
#[test] fn paint_servers_stop_stop_color_with_inherit_3() { assert_eq!(render("tests/paint-servers/stop/stop-color-with-inherit-3"), 0); }
#[test] fn paint_servers_stop_stop_color_with_inherit_4() { assert_eq!(render("tests/paint-servers/stop/stop-color-with-inherit-4"), 0); }
#[test] fn paint_servers_stop_stop_color_with_inherit_5() { assert_eq!(render("tests/paint-servers/stop/stop-color-with-inherit-5"), 0); }
#[test] fn paint_servers_stop_stop_with_smaller_offset() { assert_eq!(render("tests/paint-servers/stop/stop-with-smaller-offset"), 0); }
#[test] fn paint_servers_stop_stops_with_equal_offset_1() { assert_eq!(render("tests/paint-servers/stop/stops-with-equal-offset-1"), 0); }
#[test] fn paint_servers_stop_stops_with_equal_offset_2() { assert_eq!(render("tests/paint-servers/stop/stops-with-equal-offset-2"), 0); }
#[test] fn paint_servers_stop_stops_with_equal_offset_3() { assert_eq!(render("tests/paint-servers/stop/stops-with-equal-offset-3"), 0); }
#[test] fn paint_servers_stop_stops_with_equal_offset_4() { assert_eq!(render("tests/paint-servers/stop/stops-with-equal-offset-4"), 0); }
#[test] fn paint_servers_stop_stops_with_equal_offset_5() { assert_eq!(render("tests/paint-servers/stop/stops-with-equal-offset-5"), 0); }
#[test] fn paint_servers_stop_stops_with_equal_offset_6() { assert_eq!(render("tests/paint-servers/stop/stops-with-equal-offset-6"), 0); }
#[test] fn paint_servers_stop_zero_offset_in_the_middle() { assert_eq!(render("tests/paint-servers/stop/zero-offset-in-the-middle"), 0); }
#[test] fn paint_servers_stop_color_simple_case() { assert_eq!(render("tests/paint-servers/stop-color/simple-case"), 0); }
#[test] fn paint_servers_stop_opacity_50percent() { assert_eq!(render("tests/paint-servers/stop-opacity/50percent"), 0); }
#[test] fn paint_servers_stop_opacity_simple_case() { assert_eq!(render("tests/paint-servers/stop-opacity/simple-case"), 0); }
#[test] fn painting_color_inherit() { assert_eq!(render("tests/painting/color/inherit"), 0); }
#[test] fn painting_color_recursive_nested_context_without_color() { assert_eq!(render("tests/painting/color/recursive-nested-context-without-color"), 0); }
#[test] fn painting_color_recursive_nested_context() { assert_eq!(render("tests/painting/color/recursive-nested-context"), 0); }
#[test] fn painting_color_simple_case() { assert_eq!(render("tests/painting/color/simple-case"), 0); }
#[test] fn painting_context_in_marker() { assert_eq!(render("tests/painting/context/in-marker"), 0); }
#[test] fn painting_context_in_nested_marker() { assert_eq!(render("tests/painting/context/in-nested-marker"), 0); }
#[test] fn painting_context_in_nested_use_and_marker() { assert_eq!(render("tests/painting/context/in-nested-use-and-marker"), 0); }
#[test] fn painting_context_in_nested_use() { assert_eq!(render("tests/painting/context/in-nested-use"), 0); }
#[test] fn painting_context_in_use() { assert_eq!(render("tests/painting/context/in-use"), 0); }
#[test] fn painting_context_on_shape_with_zero_size_bbox() { assert_eq!(render("tests/painting/context/on-shape-with-zero-size-bbox"), 0); }
#[test] fn painting_context_with_gradient_and_gradient_transform() { assert_eq!(render("tests/painting/context/with-gradient-and-gradient-transform"), 0); }
#[test] fn painting_context_with_gradient_in_use() { assert_eq!(render("tests/painting/context/with-gradient-in-use"), 0); }
#[test] fn painting_context_with_gradient_on_marker() { assert_eq!(render("tests/painting/context/with-gradient-on-marker"), 0); }
#[test] fn painting_context_with_pattern_and_transform_in_use() { assert_eq!(render("tests/painting/context/with-pattern-and-transform-in-use"), 0); }
#[test] fn painting_context_with_pattern_in_use() { assert_eq!(render("tests/painting/context/with-pattern-in-use"), 0); }
#[test] fn painting_context_with_pattern_objectBoundingBox_in_use() { assert_eq!(render("tests/painting/context/with-pattern-objectBoundingBox-in-use"), 0); }
#[test] fn painting_context_with_pattern_on_marker() { assert_eq!(render("tests/painting/context/with-pattern-on-marker"), 0); }
#[test] fn painting_context_with_text() { assert_eq!(render("tests/painting/context/with-text"), 0); }
#[test] fn painting_context_with_viewbox() { assert_eq!(render("tests/painting/context/with-viewbox"), 0); }
#[test] fn painting_context_without_context_element() { assert_eq!(render("tests/painting/context/without-context-element"), 0); }
#[test] fn painting_display_bBox_impact() { assert_eq!(render("tests/painting/display/bBox-impact"), 0); }
#[test] fn painting_display_none_on_clipPath() { assert_eq!(render("tests/painting/display/none-on-clipPath"), 0); }
#[test] fn painting_display_none_on_defs() { assert_eq!(render("tests/painting/display/none-on-defs"), 0); }
#[test] fn painting_display_none_on_linearGradient() { assert_eq!(render("tests/painting/display/none-on-linearGradient"), 0); }
#[test] fn painting_display_none_on_rect() { assert_eq!(render("tests/painting/display/none-on-rect"), 0); }
#[test] fn painting_display_none_on_svg() { assert_eq!(render("tests/painting/display/none-on-svg"), 0); }
#[test] fn painting_display_none_on_tref() { assert_eq!(render("tests/painting/display/none-on-tref"), 0); }
#[test] fn painting_display_none_on_tspan_1() { assert_eq!(render("tests/painting/display/none-on-tspan-1"), 0); }
#[test] fn painting_display_none_on_tspan_2() { assert_eq!(render("tests/painting/display/none-on-tspan-2"), 0); }
#[test] fn painting_fill_RGB_color() { assert_eq!(render("tests/painting/fill/#RGB-color"), 0); }
#[test] fn painting_fill_RGBA() { assert_eq!(render("tests/painting/fill/#RGBA"), 0); }
#[test] fn painting_fill_RRGGBB_color() { assert_eq!(render("tests/painting/fill/#RRGGBB-color"), 0); }
#[test] fn painting_fill_RRGGBB_uppercase_color() { assert_eq!(render("tests/painting/fill/#RRGGBB-uppercase-color"), 0); }
#[test] fn painting_fill_RRGGBBAA() { assert_eq!(render("tests/painting/fill/#RRGGBBAA"), 0); }
#[test] fn painting_fill_currentColor_without_parent() { assert_eq!(render("tests/painting/fill/currentColor-without-parent"), 0); }
#[test] fn painting_fill_currentColor() { assert_eq!(render("tests/painting/fill/currentColor"), 0); }
#[test] fn painting_fill_double_inherit() { assert_eq!(render("tests/painting/fill/double-inherit"), 0); }
#[test] fn painting_fill_funcIRI_to_a_missing_element_with_a_fallback_color() { assert_eq!(render("tests/painting/fill/funcIRI-to-a-missing-element-with-a-fallback-color"), 0); }
#[test] fn painting_fill_funcIRI_to_a_missing_element_with_a_none_fallback() { assert_eq!(render("tests/painting/fill/funcIRI-to-a-missing-element-with-a-none-fallback"), 0); }
#[test] fn painting_fill_funcIRI_to_an_invalid_element_with_a_none_fallback() { assert_eq!(render("tests/painting/fill/funcIRI-to-an-invalid-element-with-a-none-fallback"), 0); }
#[test] fn painting_fill_funcIRI_to_an_unsupported_element() { assert_eq!(render("tests/painting/fill/funcIRI-to-an-unsupported-element"), 0); }
#[test] fn painting_fill_funcIRI_with_a_fallback_color() { assert_eq!(render("tests/painting/fill/funcIRI-with-a-fallback-color"), 0); }
#[test] fn painting_fill_hsl_120_100percent_25percent() { assert_eq!(render("tests/painting/fill/hsl-120-100percent-25percent"), 0); }
#[test] fn painting_fill_hsl_120_200percent_25percent() { assert_eq!(render("tests/painting/fill/hsl-120-200percent-25percent"), 0); }
#[test] fn painting_fill_hsl_360_100percent_25percent() { assert_eq!(render("tests/painting/fill/hsl-360-100percent-25percent"), 0); }
#[test] fn painting_fill_hsl_999_100percent_25percent() { assert_eq!(render("tests/painting/fill/hsl-999-100percent-25percent"), 0); }
#[test] fn painting_fill_hsl_with_alpha() { assert_eq!(render("tests/painting/fill/hsl-with-alpha"), 0); }
#[test] fn painting_fill_hsla_with_percentage_s_and_l_values() { assert_eq!(render("tests/painting/fill/hsla-with-percentage-s-and-l-values"), 0); }
#[test] fn painting_fill_icc_color() { assert_eq!(render("tests/painting/fill/icc-color"), 0); }
#[test] fn painting_fill_inherit_without_parent() { assert_eq!(render("tests/painting/fill/inherit-without-parent"), 0); }
#[test] fn painting_fill_inherit() { assert_eq!(render("tests/painting/fill/inherit"), 0); }
#[test] fn painting_fill_invalid_RRGGBB_1() { assert_eq!(render("tests/painting/fill/invalid-#RRGGBB-1"), 0); }
#[test] fn painting_fill_invalid_RRGGBB_2() { assert_eq!(render("tests/painting/fill/invalid-#RRGGBB-2"), 0); }
#[test] fn painting_fill_invalid_RRGGBB_3() { assert_eq!(render("tests/painting/fill/invalid-#RRGGBB-3"), 0); }
#[test] fn painting_fill_invalid_FuncIRI_with_a_currentColor_fallback() { assert_eq!(render("tests/painting/fill/invalid-FuncIRI-with-a-currentColor-fallback"), 0); }
#[test] fn painting_fill_invalid_FuncIRI_with_a_fallback_color() { assert_eq!(render("tests/painting/fill/invalid-FuncIRI-with-a-fallback-color"), 0); }
#[test] fn painting_fill_linear_gradient_on_shape() { assert_eq!(render("tests/painting/fill/linear-gradient-on-shape"), 0); }
#[test] fn painting_fill_linear_gradient_on_text() { assert_eq!(render("tests/painting/fill/linear-gradient-on-text"), 0); }
#[test] fn painting_fill_missing_FuncIRI_with_a_currentColor_fallback() { assert_eq!(render("tests/painting/fill/missing-FuncIRI-with-a-currentColor-fallback"), 0); }
#[test] fn painting_fill_named_color_in_mixedcase() { assert_eq!(render("tests/painting/fill/named-color-in-mixedcase"), 0); }
#[test] fn painting_fill_named_color_in_uppercase() { assert_eq!(render("tests/painting/fill/named-color-in-uppercase"), 0); }
#[test] fn painting_fill_named_color() { assert_eq!(render("tests/painting/fill/named-color"), 0); }
#[test] fn painting_fill_none() { assert_eq!(render("tests/painting/fill/none"), 0); }
#[test] fn painting_fill_not_trimmed_attribute_value() { assert_eq!(render("tests/painting/fill/not-trimmed-attribute-value"), 0); }
#[test] fn painting_fill_pattern_on_shape() { assert_eq!(render("tests/painting/fill/pattern-on-shape"), 0); }
#[test] fn painting_fill_pattern_on_text() { assert_eq!(render("tests/painting/fill/pattern-on-text"), 0); }
#[test] fn painting_fill_radial_gradient_on_shape() { assert_eq!(render("tests/painting/fill/radial-gradient-on-shape"), 0); }
#[test] fn painting_fill_radial_gradient_on_text() { assert_eq!(render("tests/painting/fill/radial-gradient-on-text"), 0); }
#[test] fn painting_fill_random_value() { assert_eq!(render("tests/painting/fill/random-value"), 0); }
#[test] fn painting_fill_rgb_0_127_0_0_5() { assert_eq!(render("tests/painting/fill/rgb-0-127-0-0.5"), 0); }
#[test] fn painting_fill_rgb_color_with_a_big_fraction_part() { assert_eq!(render("tests/painting/fill/rgb-color-with-a-big-fraction-part"), 0); }
#[test] fn painting_fill_rgb_color_with_extra_spaces() { assert_eq!(render("tests/painting/fill/rgb-color-with-extra-spaces"), 0); }
#[test] fn painting_fill_rgb_color_with_float_percentage_values() { assert_eq!(render("tests/painting/fill/rgb-color-with-float-percentage-values"), 0); }
#[test] fn painting_fill_rgb_color_with_floats() { assert_eq!(render("tests/painting/fill/rgb-color-with-floats"), 0); }
#[test] fn painting_fill_rgb_color_with_percentage_overflow() { assert_eq!(render("tests/painting/fill/rgb-color-with-percentage-overflow"), 0); }
#[test] fn painting_fill_rgb_color_with_percentage_values() { assert_eq!(render("tests/painting/fill/rgb-color-with-percentage-values"), 0); }
#[test] fn painting_fill_rgb_color() { assert_eq!(render("tests/painting/fill/rgb-color"), 0); }
#[test] fn painting_fill_rgb_int_int_int() { assert_eq!(render("tests/painting/fill/rgb-int-int-int"), 0); }
#[test] fn painting_fill_rgba_0_127_0__1() { assert_eq!(render("tests/painting/fill/rgba-0-127-0--1"), 0); }
#[test] fn painting_fill_rgba_0_127_0_0_5() { assert_eq!(render("tests/painting/fill/rgba-0-127-0-0.5"), 0); }
#[test] fn painting_fill_rgba_0_127_0_0() { assert_eq!(render("tests/painting/fill/rgba-0-127-0-0"), 0); }
#[test] fn painting_fill_rgba_0_127_0_1() { assert_eq!(render("tests/painting/fill/rgba-0-127-0-1"), 0); }
#[test] fn painting_fill_rgba_0_127_0_2() { assert_eq!(render("tests/painting/fill/rgba-0-127-0-2"), 0); }
#[test] fn painting_fill_rgba_0_127_0_50percent() { assert_eq!(render("tests/painting/fill/rgba-0-127-0-50percent"), 0); }
#[test] fn painting_fill_rgba_0_50percent_0_0_5() { assert_eq!(render("tests/painting/fill/rgba-0-50percent-0-0.5"), 0); }
#[test] fn painting_fill_rgba_0percent_50percent_0percent_0_5() { assert_eq!(render("tests/painting/fill/rgba-0percent-50percent-0percent-0.5"), 0); }
#[test] fn painting_fill_transparent() { assert_eq!(render("tests/painting/fill/transparent"), 0); }
#[test] fn painting_fill_uppercase_rgb_color() { assert_eq!(render("tests/painting/fill/uppercase-rgb-color"), 0); }
#[test] fn painting_fill_valid_FuncIRI_with_a_fallback_ICC_color() { assert_eq!(render("tests/painting/fill/valid-FuncIRI-with-a-fallback-ICC-color"), 0); }
#[test] fn painting_fill_opacity_50percent() { assert_eq!(render("tests/painting/fill-opacity/50percent"), 0); }
#[test] fn painting_fill_opacity_half_opacity() { assert_eq!(render("tests/painting/fill-opacity/half-opacity"), 0); }
#[test] fn painting_fill_opacity_nested() { assert_eq!(render("tests/painting/fill-opacity/nested"), 0); }
#[test] fn painting_fill_opacity_on_parent() { assert_eq!(render("tests/painting/fill-opacity/on-parent"), 0); }
#[test] fn painting_fill_opacity_on_text() { assert_eq!(render("tests/painting/fill-opacity/on-text"), 0); }
#[test] fn painting_fill_opacity_with_linearGradient() { assert_eq!(render("tests/painting/fill-opacity/with-linearGradient"), 0); }
#[test] fn painting_fill_opacity_with_opacity() { assert_eq!(render("tests/painting/fill-opacity/with-opacity"), 0); }
#[test] fn painting_fill_opacity_with_pattern() { assert_eq!(render("tests/painting/fill-opacity/with-pattern"), 0); }
#[test] fn painting_fill_rule_evenodd() { assert_eq!(render("tests/painting/fill-rule/evenodd"), 0); }
#[test] fn painting_fill_rule_nonzero() { assert_eq!(render("tests/painting/fill-rule/nonzero"), 0); }
#[test] fn painting_image_rendering_high_quality() { assert_eq!(render("tests/painting/image-rendering/high-quality"), 0); }
#[test] fn painting_image_rendering_on_feImage() { assert_eq!(render("tests/painting/image-rendering/on-feImage"), 0); }
#[test] fn painting_image_rendering_optimizeSpeed_on_SVG() { assert_eq!(render("tests/painting/image-rendering/optimizeSpeed-on-SVG"), 0); }
#[test] fn painting_image_rendering_optimizeSpeed() { assert_eq!(render("tests/painting/image-rendering/optimizeSpeed"), 0); }
#[test] fn painting_isolation_as_property() { assert_eq!(render("tests/painting/isolation/as-property"), 0); }
#[test] fn painting_isolation_isolate() { assert_eq!(render("tests/painting/isolation/isolate"), 0); }
#[test] fn painting_marker_default_clip() { assert_eq!(render("tests/painting/marker/default-clip"), 0); }
#[test] fn painting_marker_empty() { assert_eq!(render("tests/painting/marker/empty"), 0); }
#[test] fn painting_marker_inheritance_1() { assert_eq!(render("tests/painting/marker/inheritance-1"), 0); }
#[test] fn painting_marker_inheritance_2() { assert_eq!(render("tests/painting/marker/inheritance-2"), 0); }
#[test] fn painting_marker_invalid_child() { assert_eq!(render("tests/painting/marker/invalid-child"), 0); }
#[test] fn painting_marker_marker_on_circle() { assert_eq!(render("tests/painting/marker/marker-on-circle"), 0); }
#[test] fn painting_marker_marker_on_line() { assert_eq!(render("tests/painting/marker/marker-on-line"), 0); }
#[test] fn painting_marker_marker_on_polygon() { assert_eq!(render("tests/painting/marker/marker-on-polygon"), 0); }
#[test] fn painting_marker_marker_on_polyline() { assert_eq!(render("tests/painting/marker/marker-on-polyline"), 0); }
#[test] fn painting_marker_marker_on_rect() { assert_eq!(render("tests/painting/marker/marker-on-rect"), 0); }
#[test] fn painting_marker_marker_on_rounded_rect() { assert_eq!(render("tests/painting/marker/marker-on-rounded-rect"), 0); }
#[test] fn painting_marker_marker_on_text() { assert_eq!(render("tests/painting/marker/marker-on-text"), 0); }
#[test] fn painting_marker_marker_with_a_negative_size() { assert_eq!(render("tests/painting/marker/marker-with-a-negative-size"), 0); }
#[test] fn painting_marker_nested() { assert_eq!(render("tests/painting/marker/nested"), 0); }
#[test] fn painting_marker_no_stroke_on_target() { assert_eq!(render("tests/painting/marker/no-stroke-on-target"), 0); }
#[test] fn painting_marker_on_ArcTo() { assert_eq!(render("tests/painting/marker/on-ArcTo"), 0); }
#[test] fn painting_marker_only_marker_end() { assert_eq!(render("tests/painting/marker/only-marker-end"), 0); }
#[test] fn painting_marker_only_marker_mid() { assert_eq!(render("tests/painting/marker/only-marker-mid"), 0); }
#[test] fn painting_marker_only_marker_start() { assert_eq!(render("tests/painting/marker/only-marker-start"), 0); }
#[test] fn painting_marker_orient_eq__45() { assert_eq!(render("tests/painting/marker/orient=-45"), 0); }
#[test] fn painting_marker_orient_eq_0_25turn() { assert_eq!(render("tests/painting/marker/orient=0.25turn"), 0); }
#[test] fn painting_marker_orient_eq_1_5rad() { assert_eq!(render("tests/painting/marker/orient=1.5rad"), 0); }
#[test] fn painting_marker_orient_eq_30() { assert_eq!(render("tests/painting/marker/orient=30"), 0); }
#[test] fn painting_marker_orient_eq_40grad() { assert_eq!(render("tests/painting/marker/orient=40grad"), 0); }
#[test] fn painting_marker_orient_eq_9999() { assert_eq!(render("tests/painting/marker/orient=9999"), 0); }
#[test] fn painting_marker_orient_eq_auto_on_M_C_C_1() { assert_eq!(render("tests/painting/marker/orient=auto-on-M-C-C-1"), 0); }
#[test] fn painting_marker_orient_eq_auto_on_M_C_C_2() { assert_eq!(render("tests/painting/marker/orient=auto-on-M-C-C-2"), 0); }
#[test] fn painting_marker_orient_eq_auto_on_M_C_C_3() { assert_eq!(render("tests/painting/marker/orient=auto-on-M-C-C-3"), 0); }
#[test] fn painting_marker_orient_eq_auto_on_M_C_C_4() { assert_eq!(render("tests/painting/marker/orient=auto-on-M-C-C-4"), 0); }
#[test] fn painting_marker_orient_eq_auto_on_M_C_C_5() { assert_eq!(render("tests/painting/marker/orient=auto-on-M-C-C-5"), 0); }
#[test] fn painting_marker_orient_eq_auto_on_M_C_C_6() { assert_eq!(render("tests/painting/marker/orient=auto-on-M-C-C-6"), 0); }
#[test] fn painting_marker_orient_eq_auto_on_M_C_C_7() { assert_eq!(render("tests/painting/marker/orient=auto-on-M-C-C-7"), 0); }
#[test] fn painting_marker_orient_eq_auto_on_M_C_C_8() { assert_eq!(render("tests/painting/marker/orient=auto-on-M-C-C-8"), 0); }
#[test] fn painting_marker_orient_eq_auto_on_M_C_L() { assert_eq!(render("tests/painting/marker/orient=auto-on-M-C-L"), 0); }
#[test] fn painting_marker_orient_eq_auto_on_M_C_M_L() { assert_eq!(render("tests/painting/marker/orient=auto-on-M-C-M-L"), 0); }
#[test] fn painting_marker_orient_eq_auto_on_M_L_C() { assert_eq!(render("tests/painting/marker/orient=auto-on-M-L-C"), 0); }
#[test] fn painting_marker_orient_eq_auto_on_M_L_L_Z_Z_Z() { assert_eq!(render("tests/painting/marker/orient=auto-on-M-L-L-Z-Z-Z"), 0); }
#[test] fn painting_marker_orient_eq_auto_on_M_L_L() { assert_eq!(render("tests/painting/marker/orient=auto-on-M-L-L"), 0); }
#[test] fn painting_marker_orient_eq_auto_on_M_L_M_C() { assert_eq!(render("tests/painting/marker/orient=auto-on-M-L-M-C"), 0); }
#[test] fn painting_marker_orient_eq_auto_on_M_L_Z() { assert_eq!(render("tests/painting/marker/orient=auto-on-M-L-Z"), 0); }
#[test] fn painting_marker_orient_eq_auto_on_M_L() { assert_eq!(render("tests/painting/marker/orient=auto-on-M-L"), 0); }
#[test] fn painting_marker_orient_eq_auto_start_reverse() { assert_eq!(render("tests/painting/marker/orient=auto-start-reverse"), 0); }
#[test] fn painting_marker_percent_values() { assert_eq!(render("tests/painting/marker/percent-values"), 0); }
#[test] fn painting_marker_recursive_1() { assert_eq!(render("tests/painting/marker/recursive-1"), 0); }
#[test] fn painting_marker_recursive_2() { assert_eq!(render("tests/painting/marker/recursive-2"), 0); }
#[test] fn painting_marker_recursive_3() { assert_eq!(render("tests/painting/marker/recursive-3"), 0); }
#[test] fn painting_marker_recursive_4() { assert_eq!(render("tests/painting/marker/recursive-4"), 0); }
#[test] fn painting_marker_recursive_5() { assert_eq!(render("tests/painting/marker/recursive-5"), 0); }
#[test] fn painting_marker_target_with_subpaths_1() { assert_eq!(render("tests/painting/marker/target-with-subpaths-1"), 0); }
#[test] fn painting_marker_target_with_subpaths_2() { assert_eq!(render("tests/painting/marker/target-with-subpaths-2"), 0); }
#[test] fn painting_marker_the_marker_property_in_CSS() { assert_eq!(render("tests/painting/marker/the-marker-property-in-CSS"), 0); }
#[test] fn painting_marker_the_marker_property() { assert_eq!(render("tests/painting/marker/the-marker-property"), 0); }
#[test] fn painting_marker_with_a_large_stroke() { assert_eq!(render("tests/painting/marker/with-a-large-stroke"), 0); }
#[test] fn painting_marker_with_a_text_child() { assert_eq!(render("tests/painting/marker/with-a-text-child"), 0); }
#[test] fn painting_marker_with_an_image_child() { assert_eq!(render("tests/painting/marker/with-an-image-child"), 0); }
#[test] fn painting_marker_with_invalid_markerUnits() { assert_eq!(render("tests/painting/marker/with-invalid-markerUnits"), 0); }
#[test] fn painting_marker_with_markerUnits_eq_userSpaceOnUse() { assert_eq!(render("tests/painting/marker/with-markerUnits=userSpaceOnUse"), 0); }
#[test] fn painting_marker_with_viewBox_1() { assert_eq!(render("tests/painting/marker/with-viewBox-1"), 0); }
#[test] fn painting_marker_with_viewBox_2() { assert_eq!(render("tests/painting/marker/with-viewBox-2"), 0); }
#[test] fn painting_marker_zero_length_path_1() { assert_eq!(render("tests/painting/marker/zero-length-path-1"), 0); }
#[test] fn painting_marker_zero_length_path_2() { assert_eq!(render("tests/painting/marker/zero-length-path-2"), 0); }
#[test] fn painting_marker_zero_sized_stroke() { assert_eq!(render("tests/painting/marker/zero-sized-stroke"), 0); }
#[test] fn painting_marker_zero_sized() { assert_eq!(render("tests/painting/marker/zero-sized"), 0); }
#[test] fn painting_mix_blend_mode_as_property() { assert_eq!(render("tests/painting/mix-blend-mode/as-property"), 0); }
#[test] fn painting_mix_blend_mode_color_burn() { assert_eq!(render("tests/painting/mix-blend-mode/color-burn"), 0); }
#[test] fn painting_mix_blend_mode_color_dodge() { assert_eq!(render("tests/painting/mix-blend-mode/color-dodge"), 0); }
#[test] fn painting_mix_blend_mode_color() { assert_eq!(render("tests/painting/mix-blend-mode/color"), 0); }
#[test] fn painting_mix_blend_mode_darken() { assert_eq!(render("tests/painting/mix-blend-mode/darken"), 0); }
#[test] fn painting_mix_blend_mode_difference() { assert_eq!(render("tests/painting/mix-blend-mode/difference"), 0); }
#[test] fn painting_mix_blend_mode_exclusion() { assert_eq!(render("tests/painting/mix-blend-mode/exclusion"), 0); }
#[test] fn painting_mix_blend_mode_hard_light() { assert_eq!(render("tests/painting/mix-blend-mode/hard-light"), 0); }
#[test] fn painting_mix_blend_mode_hue() { assert_eq!(render("tests/painting/mix-blend-mode/hue"), 0); }
#[test] fn painting_mix_blend_mode_lighten() { assert_eq!(render("tests/painting/mix-blend-mode/lighten"), 0); }
#[test] fn painting_mix_blend_mode_luminosity() { assert_eq!(render("tests/painting/mix-blend-mode/luminosity"), 0); }
#[test] fn painting_mix_blend_mode_multiply() { assert_eq!(render("tests/painting/mix-blend-mode/multiply"), 0); }
#[test] fn painting_mix_blend_mode_normal() { assert_eq!(render("tests/painting/mix-blend-mode/normal"), 0); }
#[test] fn painting_mix_blend_mode_opacity_on_element() { assert_eq!(render("tests/painting/mix-blend-mode/opacity-on-element"), 0); }
#[test] fn painting_mix_blend_mode_opacity_on_group() { assert_eq!(render("tests/painting/mix-blend-mode/opacity-on-group"), 0); }
#[test] fn painting_mix_blend_mode_overlay() { assert_eq!(render("tests/painting/mix-blend-mode/overlay"), 0); }
#[test] fn painting_mix_blend_mode_saturation() { assert_eq!(render("tests/painting/mix-blend-mode/saturation"), 0); }
#[test] fn painting_mix_blend_mode_screen() { assert_eq!(render("tests/painting/mix-blend-mode/screen"), 0); }
#[test] fn painting_mix_blend_mode_soft_light() { assert_eq!(render("tests/painting/mix-blend-mode/soft-light"), 0); }
#[test] fn painting_mix_blend_mode_xor() { assert_eq!(render("tests/painting/mix-blend-mode/xor"), 0); }
#[test] fn painting_opacity_50percent() { assert_eq!(render("tests/painting/opacity/50percent"), 0); }
#[test] fn painting_opacity_bBox_impact() { assert_eq!(render("tests/painting/opacity/bBox-impact"), 0); }
#[test] fn painting_opacity_clamp_value_1() { assert_eq!(render("tests/painting/opacity/clamp-value-1"), 0); }
#[test] fn painting_opacity_clamp_value_2() { assert_eq!(render("tests/painting/opacity/clamp-value-2"), 0); }
#[test] fn painting_opacity_group_opacity() { assert_eq!(render("tests/painting/opacity/group-opacity"), 0); }
#[test] fn painting_opacity_invalid_value_2() { assert_eq!(render("tests/painting/opacity/invalid-value-2"), 0); }
#[test] fn painting_opacity_mixed_group_opacity() { assert_eq!(render("tests/painting/opacity/mixed-group-opacity"), 0); }
#[test] fn painting_opacity_on_an_invalid_element() { assert_eq!(render("tests/painting/opacity/on-an-invalid-element"), 0); }
#[test] fn painting_opacity_on_the_root_svg() { assert_eq!(render("tests/painting/opacity/on-the-root-svg"), 0); }
#[test] fn painting_overflow_auto_on_marker() { assert_eq!(render("tests/painting/overflow/auto-on-marker"), 0); }
#[test] fn painting_overflow_inherit_on_marker_without_parent() { assert_eq!(render("tests/painting/overflow/inherit-on-marker-without-parent"), 0); }
#[test] fn painting_overflow_inherit_on_marker() { assert_eq!(render("tests/painting/overflow/inherit-on-marker"), 0); }
#[test] fn painting_overflow_scroll_on_marker() { assert_eq!(render("tests/painting/overflow/scroll-on-marker"), 0); }
#[test] fn painting_overflow_visible_on_marker() { assert_eq!(render("tests/painting/overflow/visible-on-marker"), 0); }
#[test] fn painting_paint_order_duplicates() { assert_eq!(render("tests/painting/paint-order/duplicates"), 0); }
#[test] fn painting_paint_order_fill_markers_stroke() { assert_eq!(render("tests/painting/paint-order/fill-markers-stroke"), 0); }
#[test] fn painting_paint_order_fill() { assert_eq!(render("tests/painting/paint-order/fill"), 0); }
#[test] fn painting_paint_order_invalid() { assert_eq!(render("tests/painting/paint-order/invalid"), 0); }
#[test] fn painting_paint_order_markers_stroke() { assert_eq!(render("tests/painting/paint-order/markers-stroke"), 0); }
#[test] fn painting_paint_order_markers() { assert_eq!(render("tests/painting/paint-order/markers"), 0); }
#[test] fn painting_paint_order_normal() { assert_eq!(render("tests/painting/paint-order/normal"), 0); }
#[test] fn painting_paint_order_on_text() { assert_eq!(render("tests/painting/paint-order/on-text"), 0); }
#[test] fn painting_paint_order_on_tspan() { assert_eq!(render("tests/painting/paint-order/on-tspan"), 0); }
#[test] fn painting_paint_order_stroke_invalid() { assert_eq!(render("tests/painting/paint-order/stroke-invalid"), 0); }
#[test] fn painting_paint_order_stroke_markers_fill() { assert_eq!(render("tests/painting/paint-order/stroke-markers-fill"), 0); }
#[test] fn painting_paint_order_stroke_markers() { assert_eq!(render("tests/painting/paint-order/stroke-markers"), 0); }
#[test] fn painting_paint_order_stroke() { assert_eq!(render("tests/painting/paint-order/stroke"), 0); }
#[test] fn painting_paint_order_trailing_data() { assert_eq!(render("tests/painting/paint-order/trailing-data"), 0); }
#[test] fn painting_shape_rendering_auto_on_circle() { assert_eq!(render("tests/painting/shape-rendering/auto-on-circle"), 0); }
#[test] fn painting_shape_rendering_crispEdges_on_circle() { assert_eq!(render("tests/painting/shape-rendering/crispEdges-on-circle"), 0); }
#[test] fn painting_shape_rendering_geometricPrecision_on_circle() { assert_eq!(render("tests/painting/shape-rendering/geometricPrecision-on-circle"), 0); }
#[test] fn painting_shape_rendering_inheritance() { assert_eq!(render("tests/painting/shape-rendering/inheritance"), 0); }
#[test] fn painting_shape_rendering_on_horizontal_line() { assert_eq!(render("tests/painting/shape-rendering/on-horizontal-line"), 0); }
#[test] fn painting_shape_rendering_optimizeSpeed_on_circle() { assert_eq!(render("tests/painting/shape-rendering/optimizeSpeed-on-circle"), 0); }
#[test] fn painting_shape_rendering_optimizeSpeed_on_text() { assert_eq!(render("tests/painting/shape-rendering/optimizeSpeed-on-text"), 0); }
#[test] fn painting_shape_rendering_path_with_marker() { assert_eq!(render("tests/painting/shape-rendering/path-with-marker"), 0); }
#[test] fn painting_stroke_control_points_clamping_1() { assert_eq!(render("tests/painting/stroke/control-points-clamping-1"), 0); }
#[test] fn painting_stroke_control_points_clamping_2() { assert_eq!(render("tests/painting/stroke/control-points-clamping-2"), 0); }
#[test] fn painting_stroke_currentColor_without_a_parent() { assert_eq!(render("tests/painting/stroke/currentColor-without-a-parent"), 0); }
#[test] fn painting_stroke_funcIRI_to_unsupported_element() { assert_eq!(render("tests/painting/stroke/funcIRI-to-unsupported-element"), 0); }
#[test] fn painting_stroke_gradient_with_objectBoundingBox_and_fallback_on_lines() { assert_eq!(render("tests/painting/stroke/gradient-with-objectBoundingBox-and-fallback-on-lines"), 0); }
#[test] fn painting_stroke_gradient_with_objectBoundingBox_on_path_without_a_bbox_1() { assert_eq!(render("tests/painting/stroke/gradient-with-objectBoundingBox-on-path-without-a-bbox-1"), 0); }
#[test] fn painting_stroke_gradient_with_objectBoundingBox_on_path_without_a_bbox_2() { assert_eq!(render("tests/painting/stroke/gradient-with-objectBoundingBox-on-path-without-a-bbox-2"), 0); }
#[test] fn painting_stroke_gradient_with_objectBoundingBox_on_shape_without_a_bbox() { assert_eq!(render("tests/painting/stroke/gradient-with-objectBoundingBox-on-shape-without-a-bbox"), 0); }
#[test] fn painting_stroke_line_as_curve_1() { assert_eq!(render("tests/painting/stroke/line-as-curve-1"), 0); }
#[test] fn painting_stroke_line_as_curve_2() { assert_eq!(render("tests/painting/stroke/line-as-curve-2"), 0); }
#[test] fn painting_stroke_linear_gradient_on_text() { assert_eq!(render("tests/painting/stroke/linear-gradient-on-text"), 0); }
#[test] fn painting_stroke_linear_gradient() { assert_eq!(render("tests/painting/stroke/linear-gradient"), 0); }
#[test] fn painting_stroke_named_color() { assert_eq!(render("tests/painting/stroke/named-color"), 0); }
#[test] fn painting_stroke_none() { assert_eq!(render("tests/painting/stroke/none"), 0); }
#[test] fn painting_stroke_pattern_on_text() { assert_eq!(render("tests/painting/stroke/pattern-on-text"), 0); }
#[test] fn painting_stroke_pattern_with_objectBoundingBox_fallback_on_zero_bbox_shape() { assert_eq!(render("tests/painting/stroke/pattern-with-objectBoundingBox-fallback-on-zero-bbox-shape"), 0); }
#[test] fn painting_stroke_pattern_with_objectBoundingBox_on_zero_bbox_shape() { assert_eq!(render("tests/painting/stroke/pattern-with-objectBoundingBox-on-zero-bbox-shape"), 0); }
#[test] fn painting_stroke_pattern() { assert_eq!(render("tests/painting/stroke/pattern"), 0); }
#[test] fn painting_stroke_radial_gradient_on_text() { assert_eq!(render("tests/painting/stroke/radial-gradient-on-text"), 0); }
#[test] fn painting_stroke_radial_gradient() { assert_eq!(render("tests/painting/stroke/radial-gradient"), 0); }
#[test] fn painting_stroke_dasharray_0_n_with_butt_caps() { assert_eq!(render("tests/painting/stroke-dasharray/0-n-with-butt-caps"), 0); }
#[test] fn painting_stroke_dasharray_0_n_with_round_caps() { assert_eq!(render("tests/painting/stroke-dasharray/0-n-with-round-caps"), 0); }
#[test] fn painting_stroke_dasharray_0_n_with_square_caps() { assert_eq!(render("tests/painting/stroke-dasharray/0-n-with-square-caps"), 0); }
#[test] fn painting_stroke_dasharray_comma_ws_separator() { assert_eq!(render("tests/painting/stroke-dasharray/comma-ws-separator"), 0); }
#[test] fn painting_stroke_dasharray_em_units() { assert_eq!(render("tests/painting/stroke-dasharray/em-units"), 0); }
#[test] fn painting_stroke_dasharray_even_count() { assert_eq!(render("tests/painting/stroke-dasharray/even-count"), 0); }
#[test] fn painting_stroke_dasharray_mm_units() { assert_eq!(render("tests/painting/stroke-dasharray/mm-units"), 0); }
#[test] fn painting_stroke_dasharray_multiple_subpaths() { assert_eq!(render("tests/painting/stroke-dasharray/multiple-subpaths"), 0); }
#[test] fn painting_stroke_dasharray_n_0() { assert_eq!(render("tests/painting/stroke-dasharray/n-0"), 0); }
#[test] fn painting_stroke_dasharray_negative_sum() { assert_eq!(render("tests/painting/stroke-dasharray/negative-sum"), 0); }
#[test] fn painting_stroke_dasharray_negative_values() { assert_eq!(render("tests/painting/stroke-dasharray/negative-values"), 0); }
#[test] fn painting_stroke_dasharray_none() { assert_eq!(render("tests/painting/stroke-dasharray/none"), 0); }
#[test] fn painting_stroke_dasharray_odd_count() { assert_eq!(render("tests/painting/stroke-dasharray/odd-count"), 0); }
#[test] fn painting_stroke_dasharray_on_a_circle() { assert_eq!(render("tests/painting/stroke-dasharray/on-a-circle"), 0); }
#[test] fn painting_stroke_dasharray_percent_units() { assert_eq!(render("tests/painting/stroke-dasharray/percent-units"), 0); }
#[test] fn painting_stroke_dasharray_ws_separator() { assert_eq!(render("tests/painting/stroke-dasharray/ws-separator"), 0); }
#[test] fn painting_stroke_dasharray_zero_sum() { assert_eq!(render("tests/painting/stroke-dasharray/zero-sum"), 0); }
#[test] fn painting_stroke_dashoffset_default() { assert_eq!(render("tests/painting/stroke-dashoffset/default"), 0); }
#[test] fn painting_stroke_dashoffset_em_units() { assert_eq!(render("tests/painting/stroke-dashoffset/em-units"), 0); }
#[test] fn painting_stroke_dashoffset_mm_units() { assert_eq!(render("tests/painting/stroke-dashoffset/mm-units"), 0); }
#[test] fn painting_stroke_dashoffset_negative_value() { assert_eq!(render("tests/painting/stroke-dashoffset/negative-value"), 0); }
#[test] fn painting_stroke_dashoffset_percent_units() { assert_eq!(render("tests/painting/stroke-dashoffset/percent-units"), 0); }
#[test] fn painting_stroke_dashoffset_px_units() { assert_eq!(render("tests/painting/stroke-dashoffset/px-units"), 0); }
#[test] fn painting_stroke_linecap_butt() { assert_eq!(render("tests/painting/stroke-linecap/butt"), 0); }
#[test] fn painting_stroke_linecap_open_path_with_butt() { assert_eq!(render("tests/painting/stroke-linecap/open-path-with-butt"), 0); }
#[test] fn painting_stroke_linecap_open_path_with_round() { assert_eq!(render("tests/painting/stroke-linecap/open-path-with-round"), 0); }
#[test] fn painting_stroke_linecap_open_path_with_square() { assert_eq!(render("tests/painting/stroke-linecap/open-path-with-square"), 0); }
#[test] fn painting_stroke_linecap_round() { assert_eq!(render("tests/painting/stroke-linecap/round"), 0); }
#[test] fn painting_stroke_linecap_square() { assert_eq!(render("tests/painting/stroke-linecap/square"), 0); }
#[test] fn painting_stroke_linecap_zero_length_path_with_butt() { assert_eq!(render("tests/painting/stroke-linecap/zero-length-path-with-butt"), 0); }
#[test] fn painting_stroke_linecap_zero_length_path_with_round() { assert_eq!(render("tests/painting/stroke-linecap/zero-length-path-with-round"), 0); }
#[test] fn painting_stroke_linecap_zero_length_path_with_square() { assert_eq!(render("tests/painting/stroke-linecap/zero-length-path-with-square"), 0); }
#[test] fn painting_stroke_linejoin_arcs() { assert_eq!(render("tests/painting/stroke-linejoin/arcs"), 0); }
#[test] fn painting_stroke_linejoin_bevel() { assert_eq!(render("tests/painting/stroke-linejoin/bevel"), 0); }
#[test] fn painting_stroke_linejoin_miter_clip() { assert_eq!(render("tests/painting/stroke-linejoin/miter-clip"), 0); }
#[test] fn painting_stroke_linejoin_miter() { assert_eq!(render("tests/painting/stroke-linejoin/miter"), 0); }
#[test] fn painting_stroke_linejoin_round() { assert_eq!(render("tests/painting/stroke-linejoin/round"), 0); }
#[test] fn painting_stroke_miterlimit_default() { assert_eq!(render("tests/painting/stroke-miterlimit/default"), 0); }
#[test] fn painting_stroke_miterlimit_invalid_value() { assert_eq!(render("tests/painting/stroke-miterlimit/invalid-value"), 0); }
#[test] fn painting_stroke_miterlimit_valid_value() { assert_eq!(render("tests/painting/stroke-miterlimit/valid-value"), 0); }
#[test] fn painting_stroke_miterlimit_value_with_mm() { assert_eq!(render("tests/painting/stroke-miterlimit/value-with-mm"), 0); }
#[test] fn painting_stroke_miterlimit_value_with_percent() { assert_eq!(render("tests/painting/stroke-miterlimit/value-with-percent"), 0); }
#[test] fn painting_stroke_opacity_50percent() { assert_eq!(render("tests/painting/stroke-opacity/50percent"), 0); }
#[test] fn painting_stroke_opacity_half_opacity() { assert_eq!(render("tests/painting/stroke-opacity/half-opacity"), 0); }
#[test] fn painting_stroke_opacity_nested() { assert_eq!(render("tests/painting/stroke-opacity/nested"), 0); }
#[test] fn painting_stroke_opacity_on_parent() { assert_eq!(render("tests/painting/stroke-opacity/on-parent"), 0); }
#[test] fn painting_stroke_opacity_on_text() { assert_eq!(render("tests/painting/stroke-opacity/on-text"), 0); }
#[test] fn painting_stroke_opacity_with_linearGradient() { assert_eq!(render("tests/painting/stroke-opacity/with-linearGradient"), 0); }
#[test] fn painting_stroke_opacity_with_opacity() { assert_eq!(render("tests/painting/stroke-opacity/with-opacity"), 0); }
#[test] fn painting_stroke_opacity_with_pattern() { assert_eq!(render("tests/painting/stroke-opacity/with-pattern"), 0); }
#[test] fn painting_stroke_width_bold() { assert_eq!(render("tests/painting/stroke-width/bold"), 0); }
#[test] fn painting_stroke_width_default() { assert_eq!(render("tests/painting/stroke-width/default"), 0); }
#[test] fn painting_stroke_width_negative() { assert_eq!(render("tests/painting/stroke-width/negative"), 0); }
#[test] fn painting_stroke_width_percentage() { assert_eq!(render("tests/painting/stroke-width/percentage"), 0); }
#[test] fn painting_stroke_width_zero() { assert_eq!(render("tests/painting/stroke-width/zero"), 0); }
#[test] fn painting_visibility_bbox_impact_1() { assert_eq!(render("tests/painting/visibility/bbox-impact-1"), 0); }
#[test] fn painting_visibility_bbox_impact_2() { assert_eq!(render("tests/painting/visibility/bbox-impact-2"), 0); }
#[test] fn painting_visibility_bbox_impact_3() { assert_eq!(render("tests/painting/visibility/bbox-impact-3"), 0); }
#[test] fn painting_visibility_collapse_on_tspan() { assert_eq!(render("tests/painting/visibility/collapse-on-tspan"), 0); }
#[test] fn painting_visibility_hidden_on_group() { assert_eq!(render("tests/painting/visibility/hidden-on-group"), 0); }
#[test] fn painting_visibility_hidden_on_shape() { assert_eq!(render("tests/painting/visibility/hidden-on-shape"), 0); }
#[test] fn painting_visibility_hidden_on_tspan() { assert_eq!(render("tests/painting/visibility/hidden-on-tspan"), 0); }
#[test] fn shapes_circle_missing_cx_and_cy_attributes() { assert_eq!(render("tests/shapes/circle/missing-cx-and-cy-attributes"), 0); }
#[test] fn shapes_circle_missing_cx_attribute() { assert_eq!(render("tests/shapes/circle/missing-cx-attribute"), 0); }
#[test] fn shapes_circle_missing_cy_attribute() { assert_eq!(render("tests/shapes/circle/missing-cy-attribute"), 0); }
#[test] fn shapes_circle_missing_r_attribute() { assert_eq!(render("tests/shapes/circle/missing-r-attribute"), 0); }
#[test] fn shapes_circle_negative_r_attribute() { assert_eq!(render("tests/shapes/circle/negative-r-attribute"), 0); }
#[test] fn shapes_circle_simple_case() { assert_eq!(render("tests/shapes/circle/simple-case"), 0); }
#[test] fn shapes_ellipse_missing_cx_and_cy_attributes() { assert_eq!(render("tests/shapes/ellipse/missing-cx-and-cy-attributes"), 0); }
#[test] fn shapes_ellipse_missing_cx_attribute() { assert_eq!(render("tests/shapes/ellipse/missing-cx-attribute"), 0); }
#[test] fn shapes_ellipse_missing_cy_attribute() { assert_eq!(render("tests/shapes/ellipse/missing-cy-attribute"), 0); }
#[test] fn shapes_ellipse_missing_rx_and_ry_attributes() { assert_eq!(render("tests/shapes/ellipse/missing-rx-and-ry-attributes"), 0); }
#[test] fn shapes_ellipse_missing_rx_attribute() { assert_eq!(render("tests/shapes/ellipse/missing-rx-attribute"), 0); }
#[test] fn shapes_ellipse_missing_ry_attribute() { assert_eq!(render("tests/shapes/ellipse/missing-ry-attribute"), 0); }
#[test] fn shapes_ellipse_negative_rx_and_ry_attributes() { assert_eq!(render("tests/shapes/ellipse/negative-rx-and-ry-attributes"), 0); }
#[test] fn shapes_ellipse_negative_rx_attribute() { assert_eq!(render("tests/shapes/ellipse/negative-rx-attribute"), 0); }
#[test] fn shapes_ellipse_negative_ry_attribute() { assert_eq!(render("tests/shapes/ellipse/negative-ry-attribute"), 0); }
#[test] fn shapes_ellipse_percent_values_missing_ry() { assert_eq!(render("tests/shapes/ellipse/percent-values-missing-ry"), 0); }
#[test] fn shapes_ellipse_percent_values() { assert_eq!(render("tests/shapes/ellipse/percent-values"), 0); }
#[test] fn shapes_ellipse_simple_case() { assert_eq!(render("tests/shapes/ellipse/simple-case"), 0); }
#[test] fn shapes_line_no_coordinates() { assert_eq!(render("tests/shapes/line/no-coordinates"), 0); }
#[test] fn shapes_line_no_x1_and_y1_coordinates() { assert_eq!(render("tests/shapes/line/no-x1-and-y1-coordinates"), 0); }
#[test] fn shapes_line_no_x1_coordinate() { assert_eq!(render("tests/shapes/line/no-x1-coordinate"), 0); }
#[test] fn shapes_line_no_x2_and_y2_coordinates() { assert_eq!(render("tests/shapes/line/no-x2-and-y2-coordinates"), 0); }
#[test] fn shapes_line_no_x2_coordinate() { assert_eq!(render("tests/shapes/line/no-x2-coordinate"), 0); }
#[test] fn shapes_line_no_y1_coordinate() { assert_eq!(render("tests/shapes/line/no-y1-coordinate"), 0); }
#[test] fn shapes_line_no_y2_coordinate() { assert_eq!(render("tests/shapes/line/no-y2-coordinate"), 0); }
#[test] fn shapes_line_percent_units() { assert_eq!(render("tests/shapes/line/percent-units"), 0); }
#[test] fn shapes_line_simple_case() { assert_eq!(render("tests/shapes/line/simple-case"), 0); }
#[test] fn shapes_line_with_transform() { assert_eq!(render("tests/shapes/line/with-transform"), 0); }
#[test] fn shapes_path_A() { assert_eq!(render("tests/shapes/path/A"), 0); }
#[test] fn shapes_path_M_A_s() { assert_eq!(render("tests/shapes/path/M-A-s"), 0); }
#[test] fn shapes_path_M_A_t() { assert_eq!(render("tests/shapes/path/M-A-t"), 0); }
#[test] fn shapes_path_M_A_trimmed() { assert_eq!(render("tests/shapes/path/M-A-trimmed"), 0); }
#[test] fn shapes_path_M_A() { assert_eq!(render("tests/shapes/path/M-A"), 0); }
#[test] fn shapes_path_M_C_S() { assert_eq!(render("tests/shapes/path/M-C-S"), 0); }
#[test] fn shapes_path_M_C() { assert_eq!(render("tests/shapes/path/M-C"), 0); }
#[test] fn shapes_path_M_H_H_implicit() { assert_eq!(render("tests/shapes/path/M-H-H-implicit"), 0); }
#[test] fn shapes_path_M_H_H() { assert_eq!(render("tests/shapes/path/M-H-H"), 0); }
#[test] fn shapes_path_M_H() { assert_eq!(render("tests/shapes/path/M-H"), 0); }
#[test] fn shapes_path_M_L_L_Z_rel() { assert_eq!(render("tests/shapes/path/M-L-L-Z-rel"), 0); }
#[test] fn shapes_path_M_L_L_Z() { assert_eq!(render("tests/shapes/path/M-L-L-Z"), 0); }
#[test] fn shapes_path_M_L_L_implicit() { assert_eq!(render("tests/shapes/path/M-L-L-implicit"), 0); }
#[test] fn shapes_path_M_L_M_L() { assert_eq!(render("tests/shapes/path/M-L-M-L"), 0); }
#[test] fn shapes_path_M_L_M_Z() { assert_eq!(render("tests/shapes/path/M-L-M-Z"), 0); }
#[test] fn shapes_path_M_L_M() { assert_eq!(render("tests/shapes/path/M-L-M"), 0); }
#[test] fn shapes_path_M_L_Z_A() { assert_eq!(render("tests/shapes/path/M-L-Z-A"), 0); }
#[test] fn shapes_path_M_L_Z_L_L() { assert_eq!(render("tests/shapes/path/M-L-Z-L-L"), 0); }
#[test] fn shapes_path_M_L() { assert_eq!(render("tests/shapes/path/M-L"), 0); }
#[test] fn shapes_path_M_M_implicit_M_implicit() { assert_eq!(render("tests/shapes/path/M-M-implicit-M-implicit"), 0); }
#[test] fn shapes_path_M_M_rel() { assert_eq!(render("tests/shapes/path/M-M-rel"), 0); }
#[test] fn shapes_path_M_M() { assert_eq!(render("tests/shapes/path/M-M"), 0); }
#[test] fn shapes_path_M_Q_T_rel() { assert_eq!(render("tests/shapes/path/M-Q-T-rel"), 0); }
#[test] fn shapes_path_M_Q_T() { assert_eq!(render("tests/shapes/path/M-Q-T"), 0); }
#[test] fn shapes_path_M_Q_rel_T_rel() { assert_eq!(render("tests/shapes/path/M-Q-rel-T-rel"), 0); }
#[test] fn shapes_path_M_Q() { assert_eq!(render("tests/shapes/path/M-Q"), 0); }
#[test] fn shapes_path_M_S_S() { assert_eq!(render("tests/shapes/path/M-S-S"), 0); }
#[test] fn shapes_path_M_S() { assert_eq!(render("tests/shapes/path/M-S"), 0); }
#[test] fn shapes_path_M_T_Q_rel() { assert_eq!(render("tests/shapes/path/M-T-Q-rel"), 0); }
#[test] fn shapes_path_M_T_Q() { assert_eq!(render("tests/shapes/path/M-T-Q"), 0); }
#[test] fn shapes_path_M_T_S_rel() { assert_eq!(render("tests/shapes/path/M-T-S-rel"), 0); }
#[test] fn shapes_path_M_T_S() { assert_eq!(render("tests/shapes/path/M-T-S"), 0); }
#[test] fn shapes_path_M_T_T_rel() { assert_eq!(render("tests/shapes/path/M-T-T-rel"), 0); }
#[test] fn shapes_path_M_T_T() { assert_eq!(render("tests/shapes/path/M-T-T"), 0); }
#[test] fn shapes_path_M_T() { assert_eq!(render("tests/shapes/path/M-T"), 0); }
#[test] fn shapes_path_M_V_V_implicit() { assert_eq!(render("tests/shapes/path/M-V-V-implicit"), 0); }
#[test] fn shapes_path_M_V_V() { assert_eq!(render("tests/shapes/path/M-V-V"), 0); }
#[test] fn shapes_path_M_V() { assert_eq!(render("tests/shapes/path/M-V"), 0); }
#[test] fn shapes_path_M_Z() { assert_eq!(render("tests/shapes/path/M-Z"), 0); }
#[test] fn shapes_path_M_rel_M_rel_implicit_M_rel_implicit() { assert_eq!(render("tests/shapes/path/M-rel-M-rel-implicit-M-rel-implicit"), 0); }
#[test] fn shapes_path_M_rel_M() { assert_eq!(render("tests/shapes/path/M-rel-M"), 0); }
#[test] fn shapes_path_M() { assert_eq!(render("tests/shapes/path/M"), 0); }
#[test] fn shapes_path_empty() { assert_eq!(render("tests/shapes/path/empty"), 0); }
#[test] fn shapes_path_extra_spaces() { assert_eq!(render("tests/shapes/path/extra-spaces"), 0); }
#[test] fn shapes_path_invalid_data_in_L() { assert_eq!(render("tests/shapes/path/invalid-data-in-L"), 0); }
#[test] fn shapes_path_invalid_transform() { assert_eq!(render("tests/shapes/path/invalid-transform"), 0); }
#[test] fn shapes_path_missing_coordinate_in_L() { assert_eq!(render("tests/shapes/path/missing-coordinate-in-L"), 0); }
#[test] fn shapes_path_multi_line_data() { assert_eq!(render("tests/shapes/path/multi-line-data"), 0); }
#[test] fn shapes_path_negative_large_arc_flag_value() { assert_eq!(render("tests/shapes/path/negative-large-arc-flag-value"), 0); }
#[test] fn shapes_path_negative_sweep_flag_value() { assert_eq!(render("tests/shapes/path/negative-sweep-flag-value"), 0); }
#[test] fn shapes_path_no_commawsp_after_sweep_flag() { assert_eq!(render("tests/shapes/path/no-commawsp-after-sweep-flag"), 0); }
#[test] fn shapes_path_no_commawsp_before_arc_flags() { assert_eq!(render("tests/shapes/path/no-commawsp-before-arc-flags"), 0); }
#[test] fn shapes_path_no_commawsp_between_and_after_arc_flags() { assert_eq!(render("tests/shapes/path/no-commawsp-between-and-after-arc-flags"), 0); }
#[test] fn shapes_path_no_commawsp_between_arc_flags() { assert_eq!(render("tests/shapes/path/no-commawsp-between-arc-flags"), 0); }
#[test] fn shapes_path_numeric_character_references() { assert_eq!(render("tests/shapes/path/numeric-character-references"), 0); }
#[test] fn shapes_path_out_of_range_large_arc_flag_value() { assert_eq!(render("tests/shapes/path/out-of-range-large-arc-flag-value"), 0); }
#[test] fn shapes_path_out_of_range_sweep_flag_value() { assert_eq!(render("tests/shapes/path/out-of-range-sweep-flag-value"), 0); }
#[test] fn shapes_polygon_ignore_odd_points() { assert_eq!(render("tests/shapes/polygon/ignore-odd-points"), 0); }
#[test] fn shapes_polygon_missing_points_attribute() { assert_eq!(render("tests/shapes/polygon/missing-points-attribute"), 0); }
#[test] fn shapes_polygon_not_enough_points() { assert_eq!(render("tests/shapes/polygon/not-enough-points"), 0); }
#[test] fn shapes_polygon_simple_case() { assert_eq!(render("tests/shapes/polygon/simple-case"), 0); }
#[test] fn shapes_polygon_stop_processing_on_invalid_data() { assert_eq!(render("tests/shapes/polygon/stop-processing-on-invalid-data"), 0); }
#[test] fn shapes_polyline_ignore_odd_points() { assert_eq!(render("tests/shapes/polyline/ignore-odd-points"), 0); }
#[test] fn shapes_polyline_missing_points_attribute() { assert_eq!(render("tests/shapes/polyline/missing-points-attribute"), 0); }
#[test] fn shapes_polyline_not_enough_points() { assert_eq!(render("tests/shapes/polyline/not-enough-points"), 0); }
#[test] fn shapes_polyline_simple_case() { assert_eq!(render("tests/shapes/polyline/simple-case"), 0); }
#[test] fn shapes_polyline_stop_processing_on_invalid_data() { assert_eq!(render("tests/shapes/polyline/stop-processing-on-invalid-data"), 0); }
#[test] fn shapes_rect_cap_values() { assert_eq!(render("tests/shapes/rect/cap-values"), 0); }
#[test] fn shapes_rect_ch_values() { assert_eq!(render("tests/shapes/rect/ch-values"), 0); }
#[test] fn shapes_rect_em_values() { assert_eq!(render("tests/shapes/rect/em-values"), 0); }
#[test] fn shapes_rect_ex_values() { assert_eq!(render("tests/shapes/rect/ex-values"), 0); }
#[test] fn shapes_rect_ic_values() { assert_eq!(render("tests/shapes/rect/ic-values"), 0); }
#[test] fn shapes_rect_invalid_coordinates() { assert_eq!(render("tests/shapes/rect/invalid-coordinates"), 0); }
#[test] fn shapes_rect_invalid_length() { assert_eq!(render("tests/shapes/rect/invalid-length"), 0); }
#[test] fn shapes_rect_lh_values() { assert_eq!(render("tests/shapes/rect/lh-values"), 0); }
#[test] fn shapes_rect_missing_height_attribute_processing() { assert_eq!(render("tests/shapes/rect/missing-height-attribute-processing"), 0); }
#[test] fn shapes_rect_missing_width_attribute_processing() { assert_eq!(render("tests/shapes/rect/missing-width-attribute-processing"), 0); }
#[test] fn shapes_rect_mm_values() { assert_eq!(render("tests/shapes/rect/mm-values"), 0); }
#[test] fn shapes_rect_negative_height_attribute_processing() { assert_eq!(render("tests/shapes/rect/negative-height-attribute-processing"), 0); }
#[test] fn shapes_rect_negative_rx_and_ry_attributes_resolving() { assert_eq!(render("tests/shapes/rect/negative-rx-and-ry-attributes-resolving"), 0); }
#[test] fn shapes_rect_negative_rx_attribute_resolving() { assert_eq!(render("tests/shapes/rect/negative-rx-attribute-resolving"), 0); }
#[test] fn shapes_rect_negative_ry_attribute_resolving() { assert_eq!(render("tests/shapes/rect/negative-ry-attribute-resolving"), 0); }
#[test] fn shapes_rect_negative_width_attribute_processing() { assert_eq!(render("tests/shapes/rect/negative-width-attribute-processing"), 0); }
#[test] fn shapes_rect_percentage_values_1() { assert_eq!(render("tests/shapes/rect/percentage-values-1"), 0); }
#[test] fn shapes_rect_percentage_values_2() { assert_eq!(render("tests/shapes/rect/percentage-values-2"), 0); }
#[test] fn shapes_rect_q_values() { assert_eq!(render("tests/shapes/rect/q-values"), 0); }
#[test] fn shapes_rect_rem_values() { assert_eq!(render("tests/shapes/rect/rem-values"), 0); }
#[test] fn shapes_rect_rlh_values() { assert_eq!(render("tests/shapes/rect/rlh-values"), 0); }
#[test] fn shapes_rect_rounded_rect() { assert_eq!(render("tests/shapes/rect/rounded-rect"), 0); }
#[test] fn shapes_rect_rx_and_ry_attributes_clamping_order() { assert_eq!(render("tests/shapes/rect/rx-and-ry-attributes-clamping-order"), 0); }
#[test] fn shapes_rect_rx_attribute_clamping() { assert_eq!(render("tests/shapes/rect/rx-attribute-clamping"), 0); }
#[test] fn shapes_rect_rx_attribute_resolving() { assert_eq!(render("tests/shapes/rect/rx-attribute-resolving"), 0); }
#[test] fn shapes_rect_ry_attribute_clamping() { assert_eq!(render("tests/shapes/rect/ry-attribute-clamping"), 0); }
#[test] fn shapes_rect_ry_attribute_resolving() { assert_eq!(render("tests/shapes/rect/ry-attribute-resolving"), 0); }
#[test] fn shapes_rect_simple_case() { assert_eq!(render("tests/shapes/rect/simple-case"), 0); }
#[test] fn shapes_rect_vi_and_vb_values() { assert_eq!(render("tests/shapes/rect/vi-and-vb-values"), 0); }
#[test] fn shapes_rect_vmin_and_vmax_values() { assert_eq!(render("tests/shapes/rect/vmin-and-vmax-values"), 0); }
#[test] fn shapes_rect_vw_and_vh_values() { assert_eq!(render("tests/shapes/rect/vw-and-vh-values"), 0); }
#[test] fn shapes_rect_with_child() { assert_eq!(render("tests/shapes/rect/with-child"), 0); }
#[test] fn shapes_rect_x_attribute_resolving() { assert_eq!(render("tests/shapes/rect/x-attribute-resolving"), 0); }
#[test] fn shapes_rect_y_attribute_resolving() { assert_eq!(render("tests/shapes/rect/y-attribute-resolving"), 0); }
#[test] fn shapes_rect_zero_height_attribute_processing() { assert_eq!(render("tests/shapes/rect/zero-height-attribute-processing"), 0); }
#[test] fn shapes_rect_zero_rx_attribute_resolving() { assert_eq!(render("tests/shapes/rect/zero-rx-attribute-resolving"), 0); }
#[test] fn shapes_rect_zero_ry_attribute_resolving() { assert_eq!(render("tests/shapes/rect/zero-ry-attribute-resolving"), 0); }
#[test] fn shapes_rect_zero_width_attribute_processing() { assert_eq!(render("tests/shapes/rect/zero-width-attribute-processing"), 0); }
#[test] fn structure_a_inside_text() { assert_eq!(render("tests/structure/a/inside-text"), 0); }
#[test] fn structure_a_inside_tspan() { assert_eq!(render("tests/structure/a/inside-tspan"), 0); }
#[test] fn structure_a_on_shape() { assert_eq!(render("tests/structure/a/on-shape"), 0); }
#[test] fn structure_a_on_text() { assert_eq!(render("tests/structure/a/on-text"), 0); }
#[test] fn structure_a_on_tspan() { assert_eq!(render("tests/structure/a/on-tspan"), 0); }
#[test] fn structure_defs_ignore_shapes_inside_defs() { assert_eq!(render("tests/structure/defs/ignore-shapes-inside-defs"), 0); }
#[test] fn structure_defs_multiple_defs() { assert_eq!(render("tests/structure/defs/multiple-defs"), 0); }
#[test] fn structure_defs_nested_defs() { assert_eq!(render("tests/structure/defs/nested-defs"), 0); }
#[test] fn structure_defs_out_of_order() { assert_eq!(render("tests/structure/defs/out-of-order"), 0); }
#[test] fn structure_defs_simple_case() { assert_eq!(render("tests/structure/defs/simple-case"), 0); }
#[test] fn structure_defs_style_inheritance_on_text() { assert_eq!(render("tests/structure/defs/style-inheritance-on-text"), 0); }
#[test] fn structure_defs_style_inheritance() { assert_eq!(render("tests/structure/defs/style-inheritance"), 0); }
#[test] fn structure_g_deeply_nested_groups() { assert_eq!(render("tests/structure/g/deeply-nested-groups"), 0); }
#[test] fn structure_g_recursive_inheritance() { assert_eq!(render("tests/structure/g/recursive-inheritance"), 0); }
#[test] fn structure_image_embedded_16bit_png() { assert_eq!(render("tests/structure/image/embedded-16bit-png"), 0); }
#[test] fn structure_image_embedded_gif() { assert_eq!(render("tests/structure/image/embedded-gif"), 0); }
#[test] fn structure_image_embedded_jpeg_as_image_jpeg() { assert_eq!(render("tests/structure/image/embedded-jpeg-as-image-jpeg"), 0); }
#[test] fn structure_image_embedded_jpeg_as_image_jpg() { assert_eq!(render("tests/structure/image/embedded-jpeg-as-image-jpg"), 0); }
#[test] fn structure_image_embedded_jpeg_luma() { assert_eq!(render("tests/structure/image/embedded-jpeg-luma"), 0); }
#[test] fn structure_image_embedded_jpeg_without_mime() { assert_eq!(render("tests/structure/image/embedded-jpeg-without-mime"), 0); }
#[test] fn structure_image_embedded_png_luma() { assert_eq!(render("tests/structure/image/embedded-png-luma"), 0); }
#[test] fn structure_image_embedded_png() { assert_eq!(render("tests/structure/image/embedded-png"), 0); }
#[test] fn structure_image_embedded_svg_with_text() { assert_eq!(render("tests/structure/image/embedded-svg-with-text"), 0); }
#[test] fn structure_image_embedded_svg_without_mime() { assert_eq!(render("tests/structure/image/embedded-svg-without-mime"), 0); }
#[test] fn structure_image_embedded_svg() { assert_eq!(render("tests/structure/image/embedded-svg"), 0); }
#[test] fn structure_image_embedded_svgz() { assert_eq!(render("tests/structure/image/embedded-svgz"), 0); }
#[test] fn structure_image_embedded_webp() { assert_eq!(render("tests/structure/image/embedded-webp"), 0); }
#[test] fn structure_image_external_gif() { assert_eq!(render("tests/structure/image/external-gif"), 0); }
#[test] fn structure_image_external_jpeg() { assert_eq!(render("tests/structure/image/external-jpeg"), 0); }
#[test] fn structure_image_external_png() { assert_eq!(render("tests/structure/image/external-png"), 0); }
#[test] fn structure_image_external_svg_with_transform() { assert_eq!(render("tests/structure/image/external-svg-with-transform"), 0); }
#[test] fn structure_image_external_svg() { assert_eq!(render("tests/structure/image/external-svg"), 0); }
#[test] fn structure_image_external_svgz() { assert_eq!(render("tests/structure/image/external-svgz"), 0); }
#[test] fn structure_image_external_webp() { assert_eq!(render("tests/structure/image/external-webp"), 0); }
#[test] fn structure_image_float_size() { assert_eq!(render("tests/structure/image/float-size"), 0); }
#[test] fn structure_image_image_with_float_size_scaling() { assert_eq!(render("tests/structure/image/image-with-float-size-scaling"), 0); }
#[test] fn structure_image_nested_embedded_png() { assert_eq!(render("tests/structure/image/nested-embedded-png"), 0); }
#[test] fn structure_image_nested_external_png() { assert_eq!(render("tests/structure/image/nested-external-png"), 0); }
#[test] fn structure_image_no_height_non_square() { assert_eq!(render("tests/structure/image/no-height-non-square"), 0); }
#[test] fn structure_image_no_height_on_svg() { assert_eq!(render("tests/structure/image/no-height-on-svg"), 0); }
#[test] fn structure_image_no_height() { assert_eq!(render("tests/structure/image/no-height"), 0); }
#[test] fn structure_image_no_width_and_height_on_svg() { assert_eq!(render("tests/structure/image/no-width-and-height-on-svg"), 0); }
#[test] fn structure_image_no_width_and_height() { assert_eq!(render("tests/structure/image/no-width-and-height"), 0); }
#[test] fn structure_image_no_width_on_svg() { assert_eq!(render("tests/structure/image/no-width-on-svg"), 0); }
#[test] fn structure_image_no_width() { assert_eq!(render("tests/structure/image/no-width"), 0); }
#[test] fn structure_image_preserveAspectRatio_eq_none_on_svg() { assert_eq!(render("tests/structure/image/preserveAspectRatio=none-on-svg"), 0); }
#[test] fn structure_image_preserveAspectRatio_eq_none() { assert_eq!(render("tests/structure/image/preserveAspectRatio=none"), 0); }
#[test] fn structure_image_preserveAspectRatio_eq_xMaxYMax_meet_on_svg() { assert_eq!(render("tests/structure/image/preserveAspectRatio=xMaxYMax-meet-on-svg"), 0); }
#[test] fn structure_image_preserveAspectRatio_eq_xMaxYMax_meet() { assert_eq!(render("tests/structure/image/preserveAspectRatio=xMaxYMax-meet"), 0); }
#[test] fn structure_image_preserveAspectRatio_eq_xMaxYMax_slice_on_svg() { assert_eq!(render("tests/structure/image/preserveAspectRatio=xMaxYMax-slice-on-svg"), 0); }
#[test] fn structure_image_preserveAspectRatio_eq_xMaxYMax_slice() { assert_eq!(render("tests/structure/image/preserveAspectRatio=xMaxYMax-slice"), 0); }
#[test] fn structure_image_preserveAspectRatio_eq_xMidYMid_meet_on_svg() { assert_eq!(render("tests/structure/image/preserveAspectRatio=xMidYMid-meet-on-svg"), 0); }
#[test] fn structure_image_preserveAspectRatio_eq_xMidYMid_meet() { assert_eq!(render("tests/structure/image/preserveAspectRatio=xMidYMid-meet"), 0); }
#[test] fn structure_image_preserveAspectRatio_eq_xMidYMid_slice_on_svg() { assert_eq!(render("tests/structure/image/preserveAspectRatio=xMidYMid-slice-on-svg"), 0); }
#[test] fn structure_image_preserveAspectRatio_eq_xMidYMid_slice() { assert_eq!(render("tests/structure/image/preserveAspectRatio=xMidYMid-slice"), 0); }
#[test] fn structure_image_preserveAspectRatio_eq_xMinYMin_meet_on_svg() { assert_eq!(render("tests/structure/image/preserveAspectRatio=xMinYMin-meet-on-svg"), 0); }
#[test] fn structure_image_preserveAspectRatio_eq_xMinYMin_meet() { assert_eq!(render("tests/structure/image/preserveAspectRatio=xMinYMin-meet"), 0); }
#[test] fn structure_image_preserveAspectRatio_eq_xMinYMin_slice_on_svg() { assert_eq!(render("tests/structure/image/preserveAspectRatio=xMinYMin-slice-on-svg"), 0); }
#[test] fn structure_image_preserveAspectRatio_eq_xMinYMin_slice() { assert_eq!(render("tests/structure/image/preserveAspectRatio=xMinYMin-slice"), 0); }
#[test] fn structure_image_raster_image_and_size_with_odd_numbers() { assert_eq!(render("tests/structure/image/raster-image-and-size-with-odd-numbers"), 0); }
#[test] fn structure_image_recursive_1() { assert_eq!(render("tests/structure/image/recursive-1"), 0); }
#[test] fn structure_image_recursive_2() { assert_eq!(render("tests/structure/image/recursive-2"), 0); }
#[test] fn structure_image_url_to_png() { assert_eq!(render("tests/structure/image/url-to-png"), 0); }
#[test] fn structure_image_url_to_svg() { assert_eq!(render("tests/structure/image/url-to-svg"), 0); }
#[test] fn structure_image_width_and_height_set_to_auto() { assert_eq!(render("tests/structure/image/width-and-height-set-to-auto"), 0); }
#[test] fn structure_image_with_transform() { assert_eq!(render("tests/structure/image/with-transform"), 0); }
#[test] fn structure_image_with_zero_width_and_height() { assert_eq!(render("tests/structure/image/with-zero-width-and-height"), 0); }
#[test] fn structure_image_zero_height() { assert_eq!(render("tests/structure/image/zero-height"), 0); }
#[test] fn structure_image_zero_width() { assert_eq!(render("tests/structure/image/zero-width"), 0); }
#[test] fn structure_style_attribute_selector() { assert_eq!(render("tests/structure/style/attribute-selector"), 0); }
#[test] fn structure_style_class_selector() { assert_eq!(render("tests/structure/style/class-selector"), 0); }
#[test] fn structure_style_combined_selectors() { assert_eq!(render("tests/structure/style/combined-selectors"), 0); }
#[test] fn structure_style_current_color_fill_before_color() { assert_eq!(render("tests/structure/style/current-color-fill-before-color"), 0); }
#[test] fn structure_style_current_color_stroke_before_color() { assert_eq!(render("tests/structure/style/current-color-stroke-before-color"), 0); }
#[test] fn structure_style_external_CSS() { assert_eq!(render("tests/structure/style/external-CSS"), 0); }
#[test] fn structure_style_iD_selector() { assert_eq!(render("tests/structure/style/iD-selector"), 0); }
#[test] fn structure_style_important() { assert_eq!(render("tests/structure/style/important"), 0); }
#[test] fn structure_style_invalid_type() { assert_eq!(render("tests/structure/style/invalid-type"), 0); }
#[test] fn structure_style_non_presentational_attribute() { assert_eq!(render("tests/structure/style/non-presentational-attribute"), 0); }
#[test] fn structure_style_resolve_order() { assert_eq!(render("tests/structure/style/resolve-order"), 0); }
#[test] fn structure_style_rule_specificity() { assert_eq!(render("tests/structure/style/rule-specificity"), 0); }
#[test] fn structure_style_style_after_usage() { assert_eq!(render("tests/structure/style/style-after-usage"), 0); }
#[test] fn structure_style_style_inside_CDATA() { assert_eq!(render("tests/structure/style/style-inside-CDATA"), 0); }
#[test] fn structure_style_transform() { assert_eq!(render("tests/structure/style/transform"), 0); }
#[test] fn structure_style_type_selector() { assert_eq!(render("tests/structure/style/type-selector"), 0); }
#[test] fn structure_style_universal_selector() { assert_eq!(render("tests/structure/style/universal-selector"), 0); }
#[test] fn structure_style_unresolved_class_selector() { assert_eq!(render("tests/structure/style/unresolved-class-selector"), 0); }
#[test] fn structure_style_attribute_comments() { assert_eq!(render("tests/structure/style-attribute/comments"), 0); }
#[test] fn structure_style_attribute_non_presentational_attribute() { assert_eq!(render("tests/structure/style-attribute/non-presentational-attribute"), 0); }
#[test] fn structure_style_attribute_simple_case() { assert_eq!(render("tests/structure/style-attribute/simple-case"), 0); }
#[test] fn structure_style_attribute_transform() { assert_eq!(render("tests/structure/style-attribute/transform"), 0); }
#[test] fn structure_svg_attribute_value_via_ENTITY_reference() { assert_eq!(render("tests/structure/svg/attribute-value-via-ENTITY-reference"), 0); }
#[test] fn structure_svg_background_color_with_viewbox() { assert_eq!(render("tests/structure/svg/background-color-with-viewbox"), 0); }
#[test] fn structure_svg_background_color() { assert_eq!(render("tests/structure/svg/background-color"), 0); }
#[test] fn structure_svg_deeply_nested_svg() { assert_eq!(render("tests/structure/svg/deeply-nested-svg"), 0); }
#[test] fn structure_svg_elements_via_ENTITY_reference_1() { assert_eq!(render("tests/structure/svg/elements-via-ENTITY-reference-1"), 0); }
#[test] fn structure_svg_elements_via_ENTITY_reference_2() { assert_eq!(render("tests/structure/svg/elements-via-ENTITY-reference-2"), 0); }
#[test] fn structure_svg_elements_via_ENTITY_reference_3() { assert_eq!(render("tests/structure/svg/elements-via-ENTITY-reference-3"), 0); }
#[test] fn structure_svg_explicit_svg_namespace() { assert_eq!(render("tests/structure/svg/explicit-svg-namespace"), 0); }
#[test] fn structure_svg_funcIRI_parsing() { assert_eq!(render("tests/structure/svg/funcIRI-parsing"), 0); }
#[test] fn structure_svg_funcIRI_with_invalid_characters() { assert_eq!(render("tests/structure/svg/funcIRI-with-invalid-characters"), 0); }
#[test] fn structure_svg_funcIRI_with_quotes() { assert_eq!(render("tests/structure/svg/funcIRI-with-quotes"), 0); }
#[test] fn structure_svg_invalid_id_attribute_1() { assert_eq!(render("tests/structure/svg/invalid-id-attribute-1"), 0); }
#[test] fn structure_svg_invalid_id_attribute_2() { assert_eq!(render("tests/structure/svg/invalid-id-attribute-2"), 0); }
#[test] fn structure_svg_mixed_namespaces() { assert_eq!(render("tests/structure/svg/mixed-namespaces"), 0); }
#[test] fn structure_svg_nested_svg_one_with_rect_and_one_with_viewBox() { assert_eq!(render("tests/structure/svg/nested-svg-one-with-rect-and-one-with-viewBox"), 0); }
#[test] fn structure_svg_nested_svg_with_overflow_auto() { assert_eq!(render("tests/structure/svg/nested-svg-with-overflow-auto"), 0); }
#[test] fn structure_svg_nested_svg_with_overflow_visible() { assert_eq!(render("tests/structure/svg/nested-svg-with-overflow-visible"), 0); }
#[test] fn structure_svg_nested_svg_with_rect_and_percent_values() { assert_eq!(render("tests/structure/svg/nested-svg-with-rect-and-percent-values"), 0); }
#[test] fn structure_svg_nested_svg_with_rect_and_viewBox_1() { assert_eq!(render("tests/structure/svg/nested-svg-with-rect-and-viewBox-1"), 0); }
#[test] fn structure_svg_nested_svg_with_rect_and_viewBox_2() { assert_eq!(render("tests/structure/svg/nested-svg-with-rect-and-viewBox-2"), 0); }
#[test] fn structure_svg_nested_svg_with_rect_and_viewBox_3() { assert_eq!(render("tests/structure/svg/nested-svg-with-rect-and-viewBox-3"), 0); }
#[test] fn structure_svg_nested_svg_with_rect_and_viewBox_and_percent_values() { assert_eq!(render("tests/structure/svg/nested-svg-with-rect-and-viewBox-and-percent-values"), 0); }
#[test] fn structure_svg_nested_svg_with_rect() { assert_eq!(render("tests/structure/svg/nested-svg-with-rect"), 0); }
#[test] fn structure_svg_nested_svg_with_relative_width_and_height() { assert_eq!(render("tests/structure/svg/nested-svg-with-relative-width-and-height"), 0); }
#[test] fn structure_svg_nested_svg_with_viewBox_and_percent_values() { assert_eq!(render("tests/structure/svg/nested-svg-with-viewBox-and-percent-values"), 0); }
#[test] fn structure_svg_nested_svg_with_viewBox() { assert_eq!(render("tests/structure/svg/nested-svg-with-viewBox"), 0); }
#[test] fn structure_svg_nested_svg() { assert_eq!(render("tests/structure/svg/nested-svg"), 0); }
#[test] fn structure_svg_no_children() { assert_eq!(render("tests/structure/svg/no-children"), 0); }
#[test] fn structure_svg_preserveAspectRatio_with_viewBox_not_at_zero_pos() { assert_eq!(render("tests/structure/svg/preserveAspectRatio-with-viewBox-not-at-zero-pos"), 0); }
#[test] fn structure_svg_preserveAspectRatio_eq_none() { assert_eq!(render("tests/structure/svg/preserveAspectRatio=none"), 0); }
#[test] fn structure_svg_preserveAspectRatio_eq_xMaxYMax_slice() { assert_eq!(render("tests/structure/svg/preserveAspectRatio=xMaxYMax-slice"), 0); }
#[test] fn structure_svg_preserveAspectRatio_eq_xMaxYMax() { assert_eq!(render("tests/structure/svg/preserveAspectRatio=xMaxYMax"), 0); }
#[test] fn structure_svg_preserveAspectRatio_eq_xMidYMid_slice() { assert_eq!(render("tests/structure/svg/preserveAspectRatio=xMidYMid-slice"), 0); }
#[test] fn structure_svg_preserveAspectRatio_eq_xMidYMid() { assert_eq!(render("tests/structure/svg/preserveAspectRatio=xMidYMid"), 0); }
#[test] fn structure_svg_preserveAspectRatio_eq_xMinYMin_slice() { assert_eq!(render("tests/structure/svg/preserveAspectRatio=xMinYMin-slice"), 0); }
#[test] fn structure_svg_preserveAspectRatio_eq_xMinYMin() { assert_eq!(render("tests/structure/svg/preserveAspectRatio=xMinYMin"), 0); }
#[test] fn structure_svg_proportional_viewBox() { assert_eq!(render("tests/structure/svg/proportional-viewBox"), 0); }
#[test] fn structure_svg_rect_inside_a_non_SVG_element() { assert_eq!(render("tests/structure/svg/rect-inside-a-non-SVG-element"), 0); }
#[test] fn structure_svg_viewBox_not_at_zero_pos() { assert_eq!(render("tests/structure/svg/viewBox-not-at-zero-pos"), 0); }
#[test] fn structure_svg_xmlns_validation() { assert_eq!(render("tests/structure/svg/xmlns-validation"), 0); }
#[test] fn structure_switch_comment_as_first_child() { assert_eq!(render("tests/structure/switch/comment-as-first-child"), 0); }
#[test] fn structure_switch_display_none_on_child() { assert_eq!(render("tests/structure/switch/display-none-on-child"), 0); }
#[test] fn structure_switch_non_SVG_child() { assert_eq!(render("tests/structure/switch/non-SVG-child"), 0); }
#[test] fn structure_switch_requiredFeatures() { assert_eq!(render("tests/structure/switch/requiredFeatures"), 0); }
#[test] fn structure_switch_simple_case() { assert_eq!(render("tests/structure/switch/simple-case"), 0); }
#[test] fn structure_switch_single_child() { assert_eq!(render("tests/structure/switch/single-child"), 0); }
#[test] fn structure_switch_systemLanguage() { assert_eq!(render("tests/structure/switch/systemLanguage"), 0); }
#[test] fn structure_switch_systemLanguage_eq_en_GB() { assert_eq!(render("tests/structure/switch/systemLanguage=en-GB"), 0); }
#[test] fn structure_switch_systemLanguage_eq_en_US() { assert_eq!(render("tests/structure/switch/systemLanguage=en-US"), 0); }
#[test] fn structure_switch_systemLanguage_eq_en() { assert_eq!(render("tests/structure/switch/systemLanguage=en"), 0); }
#[test] fn structure_switch_systemLanguage_eq_ru_Ru() { assert_eq!(render("tests/structure/switch/systemLanguage=ru-Ru"), 0); }
#[test] fn structure_switch_systemLanguage_eq_ru_en() { assert_eq!(render("tests/structure/switch/systemLanguage=ru-en"), 0); }
#[test] fn structure_switch_with_attributes() { assert_eq!(render("tests/structure/switch/with-attributes"), 0); }
#[test] fn structure_symbol_content_outside_the_viewbox() { assert_eq!(render("tests/structure/symbol/content-outside-the-viewbox"), 0); }
#[test] fn structure_symbol_indirect_symbol_reference() { assert_eq!(render("tests/structure/symbol/indirect-symbol-reference"), 0); }
#[test] fn structure_symbol_opacity_on_symbol_with_viewBox() { assert_eq!(render("tests/structure/symbol/opacity-on-symbol-with-viewBox"), 0); }
#[test] fn structure_symbol_opacity_on_symbol() { assert_eq!(render("tests/structure/symbol/opacity-on-symbol"), 0); }
#[test] fn structure_symbol_opacity_on_use_and_symbol() { assert_eq!(render("tests/structure/symbol/opacity-on-use-and-symbol"), 0); }
#[test] fn structure_symbol_opacity_on_use() { assert_eq!(render("tests/structure/symbol/opacity-on-use"), 0); }
#[test] fn structure_symbol_simple_case() { assert_eq!(render("tests/structure/symbol/simple-case"), 0); }
#[test] fn structure_symbol_unused_symbol() { assert_eq!(render("tests/structure/symbol/unused-symbol"), 0); }
#[test] fn structure_symbol_with_custom_use_size() { assert_eq!(render("tests/structure/symbol/with-custom-use-size"), 0); }
#[test] fn structure_symbol_with_overflow_visible() { assert_eq!(render("tests/structure/symbol/with-overflow-visible"), 0); }
#[test] fn structure_symbol_with_size_on_use_and_relative_units() { assert_eq!(render("tests/structure/symbol/with-size-on-use-and-relative-units"), 0); }
#[test] fn structure_symbol_with_transform_on_use_no_size() { assert_eq!(render("tests/structure/symbol/with-transform-on-use-no-size"), 0); }
#[test] fn structure_symbol_with_transform_on_use() { assert_eq!(render("tests/structure/symbol/with-transform-on-use"), 0); }
#[test] fn structure_symbol_with_transform() { assert_eq!(render("tests/structure/symbol/with-transform"), 0); }
#[test] fn structure_symbol_with_viewBox_and_custom_use_rect() { assert_eq!(render("tests/structure/symbol/with-viewBox-and-custom-use-rect"), 0); }
#[test] fn structure_symbol_with_viewBox_and_custom_use_size() { assert_eq!(render("tests/structure/symbol/with-viewBox-and-custom-use-size"), 0); }
#[test] fn structure_symbol_with_viewBox() { assert_eq!(render("tests/structure/symbol/with-viewBox"), 0); }
#[test] fn structure_systemLanguage_en_GB() { assert_eq!(render("tests/structure/systemLanguage/en-GB"), 0); }
#[test] fn structure_systemLanguage_en_US() { assert_eq!(render("tests/structure/systemLanguage/en-US"), 0); }
#[test] fn structure_systemLanguage_en() { assert_eq!(render("tests/structure/systemLanguage/en"), 0); }
#[test] fn structure_systemLanguage_on_clipPath() { assert_eq!(render("tests/structure/systemLanguage/on-clipPath"), 0); }
#[test] fn structure_systemLanguage_on_defs() { assert_eq!(render("tests/structure/systemLanguage/on-defs"), 0); }
#[test] fn structure_systemLanguage_on_linearGradient() { assert_eq!(render("tests/structure/systemLanguage/on-linearGradient"), 0); }
#[test] fn structure_systemLanguage_on_svg() { assert_eq!(render("tests/structure/systemLanguage/on-svg"), 0); }
#[test] fn structure_systemLanguage_on_tspan() { assert_eq!(render("tests/structure/systemLanguage/on-tspan"), 0); }
#[test] fn structure_systemLanguage_ru_Ru() { assert_eq!(render("tests/structure/systemLanguage/ru-Ru"), 0); }
#[test] fn structure_systemLanguage_ru_en() { assert_eq!(render("tests/structure/systemLanguage/ru-en"), 0); }
#[test] fn structure_transform_default() { assert_eq!(render("tests/structure/transform/default"), 0); }
#[test] fn structure_transform_direct_transform() { assert_eq!(render("tests/structure/transform/direct-transform"), 0); }
#[test] fn structure_transform_empty() { assert_eq!(render("tests/structure/transform/empty"), 0); }
#[test] fn structure_transform_extra_spaces() { assert_eq!(render("tests/structure/transform/extra-spaces"), 0); }
#[test] fn structure_transform_matrix_no_commas() { assert_eq!(render("tests/structure/transform/matrix-no-commas"), 0); }
#[test] fn structure_transform_matrix() { assert_eq!(render("tests/structure/transform/matrix"), 0); }
#[test] fn structure_transform_nested_transforms_1() { assert_eq!(render("tests/structure/transform/nested-transforms-1"), 0); }
#[test] fn structure_transform_nested_transforms_2() { assert_eq!(render("tests/structure/transform/nested-transforms-2"), 0); }
#[test] fn structure_transform_numeric_character_references() { assert_eq!(render("tests/structure/transform/numeric-character-references"), 0); }
#[test] fn structure_transform_rotate_at_position() { assert_eq!(render("tests/structure/transform/rotate-at-position"), 0); }
#[test] fn structure_transform_rotate() { assert_eq!(render("tests/structure/transform/rotate"), 0); }
#[test] fn structure_transform_scale_without_Y() { assert_eq!(render("tests/structure/transform/scale-without-Y"), 0); }
#[test] fn structure_transform_scale() { assert_eq!(render("tests/structure/transform/scale"), 0); }
#[test] fn structure_transform_skewX() { assert_eq!(render("tests/structure/transform/skewX"), 0); }
#[test] fn structure_transform_skewY() { assert_eq!(render("tests/structure/transform/skewY"), 0); }
#[test] fn structure_transform_transform_list() { assert_eq!(render("tests/structure/transform/transform-list"), 0); }
#[test] fn structure_transform_translate_without_Y() { assert_eq!(render("tests/structure/transform/translate-without-Y"), 0); }
#[test] fn structure_transform_translate() { assert_eq!(render("tests/structure/transform/translate"), 0); }
#[test] fn structure_transform_zeroed_matrix() { assert_eq!(render("tests/structure/transform/zeroed-matrix"), 0); }
#[test] fn structure_transform_origin_bottom() { assert_eq!(render("tests/structure/transform-origin/bottom"), 0); }
#[test] fn structure_transform_origin_center() { assert_eq!(render("tests/structure/transform-origin/center"), 0); }
#[test] fn structure_transform_origin_keyword_length() { assert_eq!(render("tests/structure/transform-origin/keyword-length"), 0); }
#[test] fn structure_transform_origin_left() { assert_eq!(render("tests/structure/transform-origin/left"), 0); }
#[test] fn structure_transform_origin_length_percent() { assert_eq!(render("tests/structure/transform-origin/length-percent"), 0); }
#[test] fn structure_transform_origin_length_px() { assert_eq!(render("tests/structure/transform-origin/length-px"), 0); }
#[test] fn structure_transform_origin_no_transform() { assert_eq!(render("tests/structure/transform-origin/no-transform"), 0); }
#[test] fn structure_transform_origin_on_clippath_objectBoundingBox() { assert_eq!(render("tests/structure/transform-origin/on-clippath-objectBoundingBox"), 0); }
#[test] fn structure_transform_origin_on_clippath() { assert_eq!(render("tests/structure/transform-origin/on-clippath"), 0); }
#[test] fn structure_transform_origin_on_gradient_object_bounding_box() { assert_eq!(render("tests/structure/transform-origin/on-gradient-object-bounding-box"), 0); }
#[test] fn structure_transform_origin_on_gradient_user_space_on_use() { assert_eq!(render("tests/structure/transform-origin/on-gradient-user-space-on-use"), 0); }
#[test] fn structure_transform_origin_on_group() { assert_eq!(render("tests/structure/transform-origin/on-group"), 0); }
#[test] fn structure_transform_origin_on_image() { assert_eq!(render("tests/structure/transform-origin/on-image"), 0); }
#[test] fn structure_transform_origin_on_pattern_object_bounding_box() { assert_eq!(render("tests/structure/transform-origin/on-pattern-object-bounding-box"), 0); }
#[test] fn structure_transform_origin_on_pattern_user_space_on_use() { assert_eq!(render("tests/structure/transform-origin/on-pattern-user-space-on-use"), 0); }
#[test] fn structure_transform_origin_on_shape() { assert_eq!(render("tests/structure/transform-origin/on-shape"), 0); }
#[test] fn structure_transform_origin_on_text_path() { assert_eq!(render("tests/structure/transform-origin/on-text-path"), 0); }
#[test] fn structure_transform_origin_on_text() { assert_eq!(render("tests/structure/transform-origin/on-text"), 0); }
#[test] fn structure_transform_origin_right_bottom() { assert_eq!(render("tests/structure/transform-origin/right-bottom"), 0); }
#[test] fn structure_transform_origin_right() { assert_eq!(render("tests/structure/transform-origin/right"), 0); }
#[test] fn structure_transform_origin_top_left() { assert_eq!(render("tests/structure/transform-origin/top-left"), 0); }
#[test] fn structure_transform_origin_top() { assert_eq!(render("tests/structure/transform-origin/top"), 0); }
#[test] fn structure_transform_origin_transform_on_parent() { assert_eq!(render("tests/structure/transform-origin/transform-on-parent"), 0); }
#[test] fn structure_use_cSS_rules() { assert_eq!(render("tests/structure/use/cSS-rules"), 0); }
#[test] fn structure_use_complex_style_resolving_order() { assert_eq!(render("tests/structure/use/complex-style-resolving-order"), 0); }
#[test] fn structure_use_display_inheritance() { assert_eq!(render("tests/structure/use/display-inheritance"), 0); }
#[test] fn structure_use_duplicated_IDs() { assert_eq!(render("tests/structure/use/duplicated-IDs"), 0); }
#[test] fn structure_use_fill_opacity_inheritance() { assert_eq!(render("tests/structure/use/fill-opacity-inheritance"), 0); }
#[test] fn structure_use_from_defs() { assert_eq!(render("tests/structure/use/from-defs"), 0); }
#[test] fn structure_use_href_without_the_xlink_namespace() { assert_eq!(render("tests/structure/use/href-without-the-xlink-namespace"), 0); }
#[test] fn structure_use_indirect_recursive_1() { assert_eq!(render("tests/structure/use/indirect-recursive-1"), 0); }
#[test] fn structure_use_indirect_recursive_2() { assert_eq!(render("tests/structure/use/indirect-recursive-2"), 0); }
#[test] fn structure_use_indirect_recursive_3() { assert_eq!(render("tests/structure/use/indirect-recursive-3"), 0); }
#[test] fn structure_use_indirect() { assert_eq!(render("tests/structure/use/indirect"), 0); }
#[test] fn structure_use_nested_recursive_1() { assert_eq!(render("tests/structure/use/nested-recursive-1"), 0); }
#[test] fn structure_use_nested_recursive_2() { assert_eq!(render("tests/structure/use/nested-recursive-2"), 0); }
#[test] fn structure_use_nested_xlink_to_svg_element_with_rect_and_size() { assert_eq!(render("tests/structure/use/nested-xlink-to-svg-element-with-rect-and-size"), 0); }
#[test] fn structure_use_non_linear_order() { assert_eq!(render("tests/structure/use/non-linear-order"), 0); }
#[test] fn structure_use_opacity_inheritance() { assert_eq!(render("tests/structure/use/opacity-inheritance"), 0); }
#[test] fn structure_use_position_inheritance() { assert_eq!(render("tests/structure/use/position-inheritance"), 0); }
#[test] fn structure_use_recursive() { assert_eq!(render("tests/structure/use/recursive"), 0); }
#[test] fn structure_use_self_recursive() { assert_eq!(render("tests/structure/use/self-recursive"), 0); }
#[test] fn structure_use_simple_case() { assert_eq!(render("tests/structure/use/simple-case"), 0); }
#[test] fn structure_use_stroke_opacity_inheritance() { assert_eq!(render("tests/structure/use/stroke-opacity-inheritance"), 0); }
#[test] fn structure_use_style_inheritance_1() { assert_eq!(render("tests/structure/use/style-inheritance-1"), 0); }
#[test] fn structure_use_style_inheritance_2() { assert_eq!(render("tests/structure/use/style-inheritance-2"), 0); }
#[test] fn structure_use_style_inheritance_3() { assert_eq!(render("tests/structure/use/style-inheritance-3"), 0); }
#[test] fn structure_use_transform_attribute_1() { assert_eq!(render("tests/structure/use/transform-attribute-1"), 0); }
#[test] fn structure_use_transform_attribute_2() { assert_eq!(render("tests/structure/use/transform-attribute-2"), 0); }
#[test] fn structure_use_transform_inheritance() { assert_eq!(render("tests/structure/use/transform-inheritance"), 0); }
#[test] fn structure_use_with_currentColor() { assert_eq!(render("tests/structure/use/with-currentColor"), 0); }
#[test] fn structure_use_with_size() { assert_eq!(render("tests/structure/use/with-size"), 0); }
#[test] fn structure_use_xlink_to_a_child_of_a_non_SVG_element() { assert_eq!(render("tests/structure/use/xlink-to-a-child-of-a-non-SVG-element"), 0); }
#[test] fn structure_use_xlink_to_a_child_of_an_invalid_element() { assert_eq!(render("tests/structure/use/xlink-to-a-child-of-an-invalid-element"), 0); }
#[test] fn structure_use_xlink_to_an_external_file() { assert_eq!(render("tests/structure/use/xlink-to-an-external-file"), 0); }
#[test] fn structure_use_xlink_to_an_invalid_element_1() { assert_eq!(render("tests/structure/use/xlink-to-an-invalid-element-1"), 0); }
#[test] fn structure_use_xlink_to_an_invalid_element_2() { assert_eq!(render("tests/structure/use/xlink-to-an-invalid-element-2"), 0); }
#[test] fn structure_use_xlink_to_an_invalid_element_3() { assert_eq!(render("tests/structure/use/xlink-to-an-invalid-element-3"), 0); }
#[test] fn structure_use_xlink_to_svg_element_with_rect_only_width() { assert_eq!(render("tests/structure/use/xlink-to-svg-element-with-rect-only-width"), 0); }
#[test] fn structure_use_xlink_to_svg_element_with_rect() { assert_eq!(render("tests/structure/use/xlink-to-svg-element-with-rect"), 0); }
#[test] fn structure_use_xlink_to_svg_element_with_viewBox() { assert_eq!(render("tests/structure/use/xlink-to-svg-element-with-viewBox"), 0); }
#[test] fn structure_use_xlink_to_svg_element_with_width_height_on_use() { assert_eq!(render("tests/structure/use/xlink-to-svg-element-with-width-height-on-use"), 0); }
#[test] fn structure_use_xlink_to_svg_element_with_x_y_on_use() { assert_eq!(render("tests/structure/use/xlink-to-svg-element-with-x-y-on-use"), 0); }
#[test] fn structure_use_xlink_to_svg_element() { assert_eq!(render("tests/structure/use/xlink-to-svg-element"), 0); }
#[test] fn text_alignment_baseline_after_edge() { assert_eq!(render("tests/text/alignment-baseline/after-edge"), 0); }
#[test] fn text_alignment_baseline_alphabetic() { assert_eq!(render("tests/text/alignment-baseline/alphabetic"), 0); }
#[test] fn text_alignment_baseline_auto() { assert_eq!(render("tests/text/alignment-baseline/auto"), 0); }
#[test] fn text_alignment_baseline_baseline() { assert_eq!(render("tests/text/alignment-baseline/baseline"), 0); }
#[test] fn text_alignment_baseline_before_edge() { assert_eq!(render("tests/text/alignment-baseline/before-edge"), 0); }
#[test] fn text_alignment_baseline_central() { assert_eq!(render("tests/text/alignment-baseline/central"), 0); }
#[test] fn text_alignment_baseline_hanging_and_baseline_shift_eq_20_on_tspan() { assert_eq!(render("tests/text/alignment-baseline/hanging-and-baseline-shift-eq-20-on-tspan"), 0); }
#[test] fn text_alignment_baseline_hanging_on_tspan() { assert_eq!(render("tests/text/alignment-baseline/hanging-on-tspan"), 0); }
#[test] fn text_alignment_baseline_hanging_on_vertical() { assert_eq!(render("tests/text/alignment-baseline/hanging-on-vertical"), 0); }
#[test] fn text_alignment_baseline_hanging_with_underline() { assert_eq!(render("tests/text/alignment-baseline/hanging-with-underline"), 0); }
#[test] fn text_alignment_baseline_hanging() { assert_eq!(render("tests/text/alignment-baseline/hanging"), 0); }
#[test] fn text_alignment_baseline_ideographic() { assert_eq!(render("tests/text/alignment-baseline/ideographic"), 0); }
#[test] fn text_alignment_baseline_inherit() { assert_eq!(render("tests/text/alignment-baseline/inherit"), 0); }
#[test] fn text_alignment_baseline_mathematical() { assert_eq!(render("tests/text/alignment-baseline/mathematical"), 0); }
#[test] fn text_alignment_baseline_middle_on_textPath() { assert_eq!(render("tests/text/alignment-baseline/middle-on-textPath"), 0); }
#[test] fn text_alignment_baseline_middle() { assert_eq!(render("tests/text/alignment-baseline/middle"), 0); }
#[test] fn text_alignment_baseline_text_after_edge() { assert_eq!(render("tests/text/alignment-baseline/text-after-edge"), 0); }
#[test] fn text_alignment_baseline_text_before_edge() { assert_eq!(render("tests/text/alignment-baseline/text-before-edge"), 0); }
#[test] fn text_alignment_baseline_two_textPath_with_middle_on_first() { assert_eq!(render("tests/text/alignment-baseline/two-textPath-with-middle-on-first"), 0); }
#[test] fn text_baseline_shift__10() { assert_eq!(render("tests/text/baseline-shift/-10"), 0); }
#[test] fn text_baseline_shift__50percent() { assert_eq!(render("tests/text/baseline-shift/-50percent"), 0); }
#[test] fn text_baseline_shift_0() { assert_eq!(render("tests/text/baseline-shift/0"), 0); }
#[test] fn text_baseline_shift_10() { assert_eq!(render("tests/text/baseline-shift/10"), 0); }
#[test] fn text_baseline_shift_2mm() { assert_eq!(render("tests/text/baseline-shift/2mm"), 0); }
#[test] fn text_baseline_shift_50percent() { assert_eq!(render("tests/text/baseline-shift/50percent"), 0); }
#[test] fn text_baseline_shift_baseline() { assert_eq!(render("tests/text/baseline-shift/baseline"), 0); }
#[test] fn text_baseline_shift_deeply_nested_super() { assert_eq!(render("tests/text/baseline-shift/deeply-nested-super"), 0); }
#[test] fn text_baseline_shift_inheritance_1() { assert_eq!(render("tests/text/baseline-shift/inheritance-1"), 0); }
#[test] fn text_baseline_shift_inheritance_2() { assert_eq!(render("tests/text/baseline-shift/inheritance-2"), 0); }
#[test] fn text_baseline_shift_inheritance_3() { assert_eq!(render("tests/text/baseline-shift/inheritance-3"), 0); }
#[test] fn text_baseline_shift_inheritance_4() { assert_eq!(render("tests/text/baseline-shift/inheritance-4"), 0); }
#[test] fn text_baseline_shift_inheritance_5() { assert_eq!(render("tests/text/baseline-shift/inheritance-5"), 0); }
#[test] fn text_baseline_shift_invalid_value() { assert_eq!(render("tests/text/baseline-shift/invalid-value"), 0); }
#[test] fn text_baseline_shift_mixed_nested() { assert_eq!(render("tests/text/baseline-shift/mixed-nested"), 0); }
#[test] fn text_baseline_shift_nested_length() { assert_eq!(render("tests/text/baseline-shift/nested-length"), 0); }
#[test] fn text_baseline_shift_nested_super() { assert_eq!(render("tests/text/baseline-shift/nested-super"), 0); }
#[test] fn text_baseline_shift_nested_with_baseline_1() { assert_eq!(render("tests/text/baseline-shift/nested-with-baseline-1"), 0); }
#[test] fn text_baseline_shift_nested_with_baseline_2() { assert_eq!(render("tests/text/baseline-shift/nested-with-baseline-2"), 0); }
#[test] fn text_baseline_shift_sub() { assert_eq!(render("tests/text/baseline-shift/sub"), 0); }
#[test] fn text_baseline_shift_super() { assert_eq!(render("tests/text/baseline-shift/super"), 0); }
#[test] fn text_baseline_shift_with_rotate() { assert_eq!(render("tests/text/baseline-shift/with-rotate"), 0); }
#[test] fn text_color_font_cbdt() { assert_eq!(render("tests/text/color-font/cbdt"), 0); }
#[test] fn text_color_font_colrv0() { assert_eq!(render("tests/text/color-font/colrv0"), 0); }
#[test] fn text_color_font_colrv1() { assert_eq!(render("tests/text/color-font/colrv1"), 0); }
#[test] fn text_color_font_compound_emojis_and_coordinates_list() { assert_eq!(render("tests/text/color-font/compound-emojis-and-coordinates-list"), 0); }
#[test] fn text_color_font_compound_emojis() { assert_eq!(render("tests/text/color-font/compound-emojis"), 0); }
#[test] fn text_color_font_mixed_text_rtl() { assert_eq!(render("tests/text/color-font/mixed-text-rtl"), 0); }
#[test] fn text_color_font_mixed_text() { assert_eq!(render("tests/text/color-font/mixed-text"), 0); }
#[test] fn text_color_font_sbix() { assert_eq!(render("tests/text/color-font/sbix"), 0); }
#[test] fn text_color_font_svg() { assert_eq!(render("tests/text/color-font/svg"), 0); }
#[test] fn text_color_font_writing_mode_eq_tb() { assert_eq!(render("tests/text/color-font/writing-mode=tb"), 0); }
#[test] fn text_direction_rtl_with_vertical_writing_mode() { assert_eq!(render("tests/text/direction/rtl-with-vertical-writing-mode"), 0); }
#[test] fn text_direction_rtl() { assert_eq!(render("tests/text/direction/rtl"), 0); }
#[test] fn text_dominant_baseline_alignment_baseline_and_baseline_shift_on_tspans() { assert_eq!(render("tests/text/dominant-baseline/alignment-baseline-and-baseline-shift-on-tspans"), 0); }
#[test] fn text_dominant_baseline_alignment_baseline_eq_baseline_on_tspan() { assert_eq!(render("tests/text/dominant-baseline/alignment-baseline=baseline-on-tspan"), 0); }
#[test] fn text_dominant_baseline_alphabetic() { assert_eq!(render("tests/text/dominant-baseline/alphabetic"), 0); }
#[test] fn text_dominant_baseline_auto() { assert_eq!(render("tests/text/dominant-baseline/auto"), 0); }
#[test] fn text_dominant_baseline_central() { assert_eq!(render("tests/text/dominant-baseline/central"), 0); }
#[test] fn text_dominant_baseline_complex() { assert_eq!(render("tests/text/dominant-baseline/complex"), 0); }
#[test] fn text_dominant_baseline_different_alignment_baseline_on_tspan() { assert_eq!(render("tests/text/dominant-baseline/different-alignment-baseline-on-tspan"), 0); }
#[test] fn text_dominant_baseline_dummy_tspan() { assert_eq!(render("tests/text/dominant-baseline/dummy-tspan"), 0); }
#[test] fn text_dominant_baseline_equal_alignment_baseline_on_tspan() { assert_eq!(render("tests/text/dominant-baseline/equal-alignment-baseline-on-tspan"), 0); }
#[test] fn text_dominant_baseline_hanging() { assert_eq!(render("tests/text/dominant-baseline/hanging"), 0); }
#[test] fn text_dominant_baseline_ideographic() { assert_eq!(render("tests/text/dominant-baseline/ideographic"), 0); }
#[test] fn text_dominant_baseline_inherit() { assert_eq!(render("tests/text/dominant-baseline/inherit"), 0); }
#[test] fn text_dominant_baseline_mathematical() { assert_eq!(render("tests/text/dominant-baseline/mathematical"), 0); }
#[test] fn text_dominant_baseline_middle() { assert_eq!(render("tests/text/dominant-baseline/middle"), 0); }
#[test] fn text_dominant_baseline_nested() { assert_eq!(render("tests/text/dominant-baseline/nested"), 0); }
#[test] fn text_dominant_baseline_no_change() { assert_eq!(render("tests/text/dominant-baseline/no-change"), 0); }
#[test] fn text_dominant_baseline_reset_size() { assert_eq!(render("tests/text/dominant-baseline/reset-size"), 0); }
#[test] fn text_dominant_baseline_sequential() { assert_eq!(render("tests/text/dominant-baseline/sequential"), 0); }
#[test] fn text_dominant_baseline_text_after_edge() { assert_eq!(render("tests/text/dominant-baseline/text-after-edge"), 0); }
#[test] fn text_dominant_baseline_text_before_edge() { assert_eq!(render("tests/text/dominant-baseline/text-before-edge"), 0); }
#[test] fn text_dominant_baseline_use_script() { assert_eq!(render("tests/text/dominant-baseline/use-script"), 0); }
#[test] fn text_font_font_shorthand() { assert_eq!(render("tests/text/font/font-shorthand"), 0); }
#[test] fn text_font_simple_case() { assert_eq!(render("tests/text/font/simple-case"), 0); }
#[test] fn text_font_family_bold_sans_serif() { assert_eq!(render("tests/text/font-family/bold-sans-serif"), 0); }
#[test] fn text_font_family_cursive() { assert_eq!(render("tests/text/font-family/cursive"), 0); }
#[test] fn text_font_family_double_quoted() { assert_eq!(render("tests/text/font-family/double-quoted"), 0); }
#[test] fn text_font_family_fallback_1() { assert_eq!(render("tests/text/font-family/fallback-1"), 0); }
#[test] fn text_font_family_fallback_2() { assert_eq!(render("tests/text/font-family/fallback-2"), 0); }
#[test] fn text_font_family_fantasy() { assert_eq!(render("tests/text/font-family/fantasy"), 0); }
#[test] fn text_font_family_font_list() { assert_eq!(render("tests/text/font-family/font-list"), 0); }
#[test] fn text_font_family_monospace() { assert_eq!(render("tests/text/font-family/monospace"), 0); }
#[test] fn text_font_family_noto_sans() { assert_eq!(render("tests/text/font-family/noto-sans"), 0); }
#[test] fn text_font_family_sans_serif() { assert_eq!(render("tests/text/font-family/sans-serif"), 0); }
#[test] fn text_font_family_serif() { assert_eq!(render("tests/text/font-family/serif"), 0); }
#[test] fn text_font_family_source_sans_pro() { assert_eq!(render("tests/text/font-family/source-sans-pro"), 0); }
#[test] fn text_font_kerning_arabic_script() { assert_eq!(render("tests/text/font-kerning/arabic-script"), 0); }
#[test] fn text_font_kerning_as_property() { assert_eq!(render("tests/text/font-kerning/as-property"), 0); }
#[test] fn text_font_kerning_none() { assert_eq!(render("tests/text/font-kerning/none"), 0); }
#[test] fn text_font_size_em_nested_and_mixed() { assert_eq!(render("tests/text/font-size/em-nested-and-mixed"), 0); }
#[test] fn text_font_size_em_on_the_root_element() { assert_eq!(render("tests/text/font-size/em-on-the-root-element"), 0); }
#[test] fn text_font_size_em() { assert_eq!(render("tests/text/font-size/em"), 0); }
#[test] fn text_font_size_ex_nested_and_mixed() { assert_eq!(render("tests/text/font-size/ex-nested-and-mixed"), 0); }
#[test] fn text_font_size_ex_on_the_root_element() { assert_eq!(render("tests/text/font-size/ex-on-the-root-element"), 0); }
#[test] fn text_font_size_ex() { assert_eq!(render("tests/text/font-size/ex"), 0); }
#[test] fn text_font_size_inheritance() { assert_eq!(render("tests/text/font-size/inheritance"), 0); }
#[test] fn text_font_size_mixed_values() { assert_eq!(render("tests/text/font-size/mixed-values"), 0); }
#[test] fn text_font_size_named_value_without_a_parent() { assert_eq!(render("tests/text/font-size/named-value-without-a-parent"), 0); }
#[test] fn text_font_size_named_value() { assert_eq!(render("tests/text/font-size/named-value"), 0); }
#[test] fn text_font_size_negative_size() { assert_eq!(render("tests/text/font-size/negative-size"), 0); }
#[test] fn text_font_size_nested_percent_values_1() { assert_eq!(render("tests/text/font-size/nested-percent-values-1"), 0); }
#[test] fn text_font_size_nested_percent_values_2() { assert_eq!(render("tests/text/font-size/nested-percent-values-2"), 0); }
#[test] fn text_font_size_percent_value_without_a_parent() { assert_eq!(render("tests/text/font-size/percent-value-without-a-parent"), 0); }
#[test] fn text_font_size_percent_value() { assert_eq!(render("tests/text/font-size/percent-value"), 0); }
#[test] fn text_font_size_simple_case() { assert_eq!(render("tests/text/font-size/simple-case"), 0); }
#[test] fn text_font_size_zero_size_on_parent_1() { assert_eq!(render("tests/text/font-size/zero-size-on-parent-1"), 0); }
#[test] fn text_font_size_zero_size_on_parent_2() { assert_eq!(render("tests/text/font-size/zero-size-on-parent-2"), 0); }
#[test] fn text_font_size_zero_size_on_parent_3() { assert_eq!(render("tests/text/font-size/zero-size-on-parent-3"), 0); }
#[test] fn text_font_size_zero_size() { assert_eq!(render("tests/text/font-size/zero-size"), 0); }
#[test] fn text_font_size_adjust_simple_case() { assert_eq!(render("tests/text/font-size-adjust/simple-case"), 0); }
#[test] fn text_font_stretch_extra_condensed() { assert_eq!(render("tests/text/font-stretch/extra-condensed"), 0); }
#[test] fn text_font_stretch_inherit() { assert_eq!(render("tests/text/font-stretch/inherit"), 0); }
#[test] fn text_font_stretch_narrower() { assert_eq!(render("tests/text/font-stretch/narrower"), 0); }
#[test] fn text_font_style_inherit() { assert_eq!(render("tests/text/font-style/inherit"), 0); }
#[test] fn text_font_style_italic() { assert_eq!(render("tests/text/font-style/italic"), 0); }
#[test] fn text_font_style_oblique() { assert_eq!(render("tests/text/font-style/oblique"), 0); }
#[test] fn text_font_variant_inherit() { assert_eq!(render("tests/text/font-variant/inherit"), 0); }
#[test] fn text_font_variant_small_caps() { assert_eq!(render("tests/text/font-variant/small-caps"), 0); }
#[test] fn text_font_variation_settings_all_axes_combined() { assert_eq!(render("tests/text/font-variation-settings/all-axes-combined"), 0); }
#[test] fn text_font_variation_settings_auto_font_stretch_condensed() { assert_eq!(render("tests/text/font-variation-settings/auto-font-stretch-condensed"), 0); }
#[test] fn text_font_variation_settings_auto_font_style_oblique() { assert_eq!(render("tests/text/font-variation-settings/auto-font-style-oblique"), 0); }
#[test] fn text_font_variation_settings_auto_font_weight_700() { assert_eq!(render("tests/text/font-variation-settings/auto-font-weight-700"), 0); }
#[test] fn text_font_variation_settings_explicit_overrides_auto() { assert_eq!(render("tests/text/font-variation-settings/explicit-overrides-auto"), 0); }
#[test] fn text_font_variation_settings_grad_negative() { assert_eq!(render("tests/text/font-variation-settings/grad-negative"), 0); }
#[test] fn text_font_variation_settings_multiple_axes() { assert_eq!(render("tests/text/font-variation-settings/multiple-axes"), 0); }
#[test] fn text_font_variation_settings_opsz_144() { assert_eq!(render("tests/text/font-variation-settings/opsz-144"), 0); }
#[test] fn text_font_variation_settings_slnt_negative() { assert_eq!(render("tests/text/font-variation-settings/slnt-negative"), 0); }
#[test] fn text_font_variation_settings_wdth_151() { assert_eq!(render("tests/text/font-variation-settings/wdth-151"), 0); }
#[test] fn text_font_variation_settings_wdth_25() { assert_eq!(render("tests/text/font-variation-settings/wdth-25"), 0); }
#[test] fn text_font_variation_settings_wght_100() { assert_eq!(render("tests/text/font-variation-settings/wght-100"), 0); }
#[test] fn text_font_variation_settings_wght_700() { assert_eq!(render("tests/text/font-variation-settings/wght-700"), 0); }
#[test] fn text_font_variation_settings_xtra_extreme() { assert_eq!(render("tests/text/font-variation-settings/xtra-extreme"), 0); }
#[test] fn text_font_weight_650() { assert_eq!(render("tests/text/font-weight/650"), 0); }
#[test] fn text_font_weight_700() { assert_eq!(render("tests/text/font-weight/700"), 0); }
#[test] fn text_font_weight_bold() { assert_eq!(render("tests/text/font-weight/bold"), 0); }
#[test] fn text_font_weight_bolder_with_clamping() { assert_eq!(render("tests/text/font-weight/bolder-with-clamping"), 0); }
#[test] fn text_font_weight_bolder_without_parent() { assert_eq!(render("tests/text/font-weight/bolder-without-parent"), 0); }
#[test] fn text_font_weight_bolder() { assert_eq!(render("tests/text/font-weight/bolder"), 0); }
#[test] fn text_font_weight_inherit() { assert_eq!(render("tests/text/font-weight/inherit"), 0); }
#[test] fn text_font_weight_invalid_number_1() { assert_eq!(render("tests/text/font-weight/invalid-number-1"), 0); }
#[test] fn text_font_weight_lighter_with_clamping() { assert_eq!(render("tests/text/font-weight/lighter-with-clamping"), 0); }
#[test] fn text_font_weight_lighter_without_parent() { assert_eq!(render("tests/text/font-weight/lighter-without-parent"), 0); }
#[test] fn text_font_weight_lighter() { assert_eq!(render("tests/text/font-weight/lighter"), 0); }
#[test] fn text_font_weight_normal() { assert_eq!(render("tests/text/font-weight/normal"), 0); }
#[test] fn text_glyph_orientation_horizontal_simple_case() { assert_eq!(render("tests/text/glyph-orientation-horizontal/simple-case"), 0); }
#[test] fn text_glyph_orientation_vertical_simple_case() { assert_eq!(render("tests/text/glyph-orientation-vertical/simple-case"), 0); }
#[test] fn text_kerning_0() { assert_eq!(render("tests/text/kerning/0"), 0); }
#[test] fn text_kerning_10percent() { assert_eq!(render("tests/text/kerning/10percent"), 0); }
#[test] fn text_lengthAdjust_spacingAndGlyphs() { assert_eq!(render("tests/text/lengthAdjust/spacingAndGlyphs"), 0); }
#[test] fn text_lengthAdjust_text_on_path() { assert_eq!(render("tests/text/lengthAdjust/text-on-path"), 0); }
#[test] fn text_lengthAdjust_vertical() { assert_eq!(render("tests/text/lengthAdjust/vertical"), 0); }
#[test] fn text_lengthAdjust_with_underline() { assert_eq!(render("tests/text/lengthAdjust/with-underline"), 0); }
#[test] fn text_letter_spacing__3() { assert_eq!(render("tests/text/letter-spacing/-3"), 0); }
#[test] fn text_letter_spacing_0() { assert_eq!(render("tests/text/letter-spacing/0"), 0); }
#[test] fn text_letter_spacing_1mm() { assert_eq!(render("tests/text/letter-spacing/1mm"), 0); }
#[test] fn text_letter_spacing_3() { assert_eq!(render("tests/text/letter-spacing/3"), 0); }
#[test] fn text_letter_spacing_5percent() { assert_eq!(render("tests/text/letter-spacing/5percent"), 0); }
#[test] fn text_letter_spacing_filter_bbox() { assert_eq!(render("tests/text/letter-spacing/filter-bbox"), 0); }
#[test] fn text_letter_spacing_large_negative() { assert_eq!(render("tests/text/letter-spacing/large-negative"), 0); }
#[test] fn text_letter_spacing_mixed_scripts() { assert_eq!(render("tests/text/letter-spacing/mixed-scripts"), 0); }
#[test] fn text_letter_spacing_mixed_spacing() { assert_eq!(render("tests/text/letter-spacing/mixed-spacing"), 0); }
#[test] fn text_letter_spacing_non_ASCII_character() { assert_eq!(render("tests/text/letter-spacing/non-ASCII-character"), 0); }
#[test] fn text_letter_spacing_normal() { assert_eq!(render("tests/text/letter-spacing/normal"), 0); }
#[test] fn text_letter_spacing_on_Arabic() { assert_eq!(render("tests/text/letter-spacing/on-Arabic"), 0); }
#[test] fn text_text_bidi_reordering() { assert_eq!(render("tests/text/text/bidi-reordering"), 0); }
#[test] fn text_text_complex_grapheme_split_by_tspan() { assert_eq!(render("tests/text/text/complex-grapheme-split-by-tspan"), 0); }
#[test] fn text_text_complex_graphemes_and_coordinates_list() { assert_eq!(render("tests/text/text/complex-graphemes-and-coordinates-list"), 0); }
#[test] fn text_text_complex_graphemes() { assert_eq!(render("tests/text/text/complex-graphemes"), 0); }
#[test] fn text_text_dx_and_dy_instead_of_x_and_y() { assert_eq!(render("tests/text/text/dx-and-dy-instead-of-x-and-y"), 0); }
#[test] fn text_text_dx_and_dy_with_less_values_than_characters() { assert_eq!(render("tests/text/text/dx-and-dy-with-less-values-than-characters"), 0); }
#[test] fn text_text_dx_and_dy_with_more_values_than_characters() { assert_eq!(render("tests/text/text/dx-and-dy-with-more-values-than-characters"), 0); }
#[test] fn text_text_dx_and_dy_with_multiple_values() { assert_eq!(render("tests/text/text/dx-and-dy-with-multiple-values"), 0); }
#[test] fn text_text_em_and_ex_coordinates() { assert_eq!(render("tests/text/text/em-and-ex-coordinates"), 0); }
#[test] fn text_text_escaped_text_1() { assert_eq!(render("tests/text/text/escaped-text-1"), 0); }
#[test] fn text_text_escaped_text_2() { assert_eq!(render("tests/text/text/escaped-text-2"), 0); }
#[test] fn text_text_escaped_text_3() { assert_eq!(render("tests/text/text/escaped-text-3"), 0); }
#[test] fn text_text_escaped_text_4() { assert_eq!(render("tests/text/text/escaped-text-4"), 0); }
#[test] fn text_text_fill_rule_eq_evenodd() { assert_eq!(render("tests/text/text/fill-rule=evenodd"), 0); }
#[test] fn text_text_filter_bbox() { assert_eq!(render("tests/text/text/filter-bbox"), 0); }
#[test] fn text_text_glyph_splitting() { assert_eq!(render("tests/text/text/glyph-splitting"), 0); }
#[test] fn text_text_ligatures_handling_in_mixed_fonts_1() { assert_eq!(render("tests/text/text/ligatures-handling-in-mixed-fonts-1"), 0); }
#[test] fn text_text_ligatures_handling_in_mixed_fonts_2() { assert_eq!(render("tests/text/text/ligatures-handling-in-mixed-fonts-2"), 0); }
#[test] fn text_text_mm_coordinates() { assert_eq!(render("tests/text/text/mm-coordinates"), 0); }
#[test] fn text_text_nested() { assert_eq!(render("tests/text/text/nested"), 0); }
#[test] fn text_text_no_coordinates() { assert_eq!(render("tests/text/text/no-coordinates"), 0); }
#[test] fn text_text_percent_value_on_dx_and_dy() { assert_eq!(render("tests/text/text/percent-value-on-dx-and-dy"), 0); }
#[test] fn text_text_percent_value_on_x_and_y() { assert_eq!(render("tests/text/text/percent-value-on-x-and-y"), 0); }
#[test] fn text_text_real_text_height() { assert_eq!(render("tests/text/text/real-text-height"), 0); }
#[test] fn text_text_rotate_on_Arabic() { assert_eq!(render("tests/text/text/rotate-on-Arabic"), 0); }
#[test] fn text_text_rotate_with_an_invalid_angle() { assert_eq!(render("tests/text/text/rotate-with-an-invalid-angle"), 0); }
#[test] fn text_text_rotate_with_less_values_than_characters() { assert_eq!(render("tests/text/text/rotate-with-less-values-than-characters"), 0); }
#[test] fn text_text_rotate_with_more_values_than_characters() { assert_eq!(render("tests/text/text/rotate-with-more-values-than-characters"), 0); }
#[test] fn text_text_rotate_with_multiple_values_and_complex_text() { assert_eq!(render("tests/text/text/rotate-with-multiple-values-and-complex-text"), 0); }
#[test] fn text_text_rotate_with_multiple_values_underline_and_pattern() { assert_eq!(render("tests/text/text/rotate-with-multiple-values-underline-and-pattern"), 0); }
#[test] fn text_text_rotate_with_multiple_values() { assert_eq!(render("tests/text/text/rotate-with-multiple-values"), 0); }
#[test] fn text_text_rotate() { assert_eq!(render("tests/text/text/rotate"), 0); }
#[test] fn text_text_simple_case() { assert_eq!(render("tests/text/text/simple-case"), 0); }
#[test] fn text_text_transform() { assert_eq!(render("tests/text/text/transform"), 0); }
#[test] fn text_text_x_and_y_with_dx_and_dy_lists() { assert_eq!(render("tests/text/text/x-and-y-with-dx-and-dy-lists"), 0); }
#[test] fn text_text_x_and_y_with_dx_and_dy() { assert_eq!(render("tests/text/text/x-and-y-with-dx-and-dy"), 0); }
#[test] fn text_text_x_and_y_with_less_values_than_characters() { assert_eq!(render("tests/text/text/x-and-y-with-less-values-than-characters"), 0); }
#[test] fn text_text_x_and_y_with_more_values_than_characters() { assert_eq!(render("tests/text/text/x-and-y-with-more-values-than-characters"), 0); }
#[test] fn text_text_x_and_y_with_multiple_values_and_arabic_text() { assert_eq!(render("tests/text/text/x-and-y-with-multiple-values-and-arabic-text"), 0); }
#[test] fn text_text_x_and_y_with_multiple_values_and_tspan() { assert_eq!(render("tests/text/text/x-and-y-with-multiple-values-and-tspan"), 0); }
#[test] fn text_text_x_and_y_with_multiple_values() { assert_eq!(render("tests/text/text/x-and-y-with-multiple-values"), 0); }
#[test] fn text_text_xml_lang_eq_ja() { assert_eq!(render("tests/text/text/xml-lang=ja"), 0); }
#[test] fn text_text_xml_space() { assert_eq!(render("tests/text/text/xml-space"), 0); }
#[test] fn text_text_zalgo() { assert_eq!(render("tests/text/text/zalgo"), 0); }
#[test] fn text_text_anchor_coordinates_list() { assert_eq!(render("tests/text/text-anchor/coordinates-list"), 0); }
#[test] fn text_text_anchor_end_on_text() { assert_eq!(render("tests/text/text-anchor/end-on-text"), 0); }
#[test] fn text_text_anchor_end_with_letter_spacing() { assert_eq!(render("tests/text/text-anchor/end-with-letter-spacing"), 0); }
#[test] fn text_text_anchor_inheritance_1() { assert_eq!(render("tests/text/text-anchor/inheritance-1"), 0); }
#[test] fn text_text_anchor_inheritance_2() { assert_eq!(render("tests/text/text-anchor/inheritance-2"), 0); }
#[test] fn text_text_anchor_inheritance_3() { assert_eq!(render("tests/text/text-anchor/inheritance-3"), 0); }
#[test] fn text_text_anchor_invalid_value_on_text() { assert_eq!(render("tests/text/text-anchor/invalid-value-on-text"), 0); }
#[test] fn text_text_anchor_middle_on_text() { assert_eq!(render("tests/text/text-anchor/middle-on-text"), 0); }
#[test] fn text_text_anchor_on_the_first_tspan() { assert_eq!(render("tests/text/text-anchor/on-the-first-tspan"), 0); }
#[test] fn text_text_anchor_on_tspan_with_arabic() { assert_eq!(render("tests/text/text-anchor/on-tspan-with-arabic"), 0); }
#[test] fn text_text_anchor_on_tspan() { assert_eq!(render("tests/text/text-anchor/on-tspan"), 0); }
#[test] fn text_text_anchor_start_on_text() { assert_eq!(render("tests/text/text-anchor/start-on-text"), 0); }
#[test] fn text_text_anchor_text_anchor_not_on_text_chunk() { assert_eq!(render("tests/text/text-anchor/text-anchor-not-on-text-chunk"), 0); }
#[test] fn text_text_decoration_all_types_inline_comma_separated() { assert_eq!(render("tests/text/text-decoration/all-types-inline-comma-separated"), 0); }
#[test] fn text_text_decoration_all_types_inline_no_spaces() { assert_eq!(render("tests/text/text-decoration/all-types-inline-no-spaces"), 0); }
#[test] fn text_text_decoration_all_types_inline() { assert_eq!(render("tests/text/text-decoration/all-types-inline"), 0); }
#[test] fn text_text_decoration_all_types_nested() { assert_eq!(render("tests/text/text-decoration/all-types-nested"), 0); }
#[test] fn text_text_decoration_indirect_with_multiple_colors() { assert_eq!(render("tests/text/text-decoration/indirect-with-multiple-colors"), 0); }
#[test] fn text_text_decoration_indirect() { assert_eq!(render("tests/text/text-decoration/indirect"), 0); }
#[test] fn text_text_decoration_line_through() { assert_eq!(render("tests/text/text-decoration/line-through"), 0); }
#[test] fn text_text_decoration_outside_the_text_element() { assert_eq!(render("tests/text/text-decoration/outside-the-text-element"), 0); }
#[test] fn text_text_decoration_overline() { assert_eq!(render("tests/text/text-decoration/overline"), 0); }
#[test] fn text_text_decoration_style_resolving_1() { assert_eq!(render("tests/text/text-decoration/style-resolving-1"), 0); }
#[test] fn text_text_decoration_style_resolving_2() { assert_eq!(render("tests/text/text-decoration/style-resolving-2"), 0); }
#[test] fn text_text_decoration_style_resolving_3() { assert_eq!(render("tests/text/text-decoration/style-resolving-3"), 0); }
#[test] fn text_text_decoration_style_resolving_4() { assert_eq!(render("tests/text/text-decoration/style-resolving-4"), 0); }
#[test] fn text_text_decoration_tspan_decoration() { assert_eq!(render("tests/text/text-decoration/tspan-decoration"), 0); }
#[test] fn text_text_decoration_underline_with_dy_list_1() { assert_eq!(render("tests/text/text-decoration/underline-with-dy-list-1"), 0); }
#[test] fn text_text_decoration_underline_with_dy_list_2() { assert_eq!(render("tests/text/text-decoration/underline-with-dy-list-2"), 0); }
#[test] fn text_text_decoration_underline_with_rotate_list_3() { assert_eq!(render("tests/text/text-decoration/underline-with-rotate-list-3"), 0); }
#[test] fn text_text_decoration_underline_with_rotate_list_4() { assert_eq!(render("tests/text/text-decoration/underline-with-rotate-list-4"), 0); }
#[test] fn text_text_decoration_underline_with_y_list() { assert_eq!(render("tests/text/text-decoration/underline-with-y-list"), 0); }
#[test] fn text_text_decoration_underline() { assert_eq!(render("tests/text/text-decoration/underline"), 0); }
#[test] fn text_text_decoration_with_textLength_on_a_single_character() { assert_eq!(render("tests/text/text-decoration/with-textLength-on-a-single-character"), 0); }
#[test] fn text_text_rendering_geometricPrecision() { assert_eq!(render("tests/text/text-rendering/geometricPrecision"), 0); }
#[test] fn text_text_rendering_on_tspan() { assert_eq!(render("tests/text/text-rendering/on-tspan"), 0); }
#[test] fn text_text_rendering_optimizeLegibility() { assert_eq!(render("tests/text/text-rendering/optimizeLegibility"), 0); }
#[test] fn text_text_rendering_optimizeSpeed() { assert_eq!(render("tests/text/text-rendering/optimizeSpeed"), 0); }
#[test] fn text_text_rendering_with_underline() { assert_eq!(render("tests/text/text-rendering/with-underline"), 0); }
#[test] fn text_textLength_150_on_parent() { assert_eq!(render("tests/text/textLength/150-on-parent"), 0); }
#[test] fn text_textLength_150_on_tspan() { assert_eq!(render("tests/text/textLength/150-on-tspan"), 0); }
#[test] fn text_textLength_150() { assert_eq!(render("tests/text/textLength/150"), 0); }
#[test] fn text_textLength_40mm() { assert_eq!(render("tests/text/textLength/40mm"), 0); }
#[test] fn text_textLength_75percent() { assert_eq!(render("tests/text/textLength/75percent"), 0); }
#[test] fn text_textLength_arabic_with_lengthAdjust() { assert_eq!(render("tests/text/textLength/arabic-with-lengthAdjust"), 0); }
#[test] fn text_textLength_arabic() { assert_eq!(render("tests/text/textLength/arabic"), 0); }
#[test] fn text_textLength_inherit() { assert_eq!(render("tests/text/textLength/inherit"), 0); }
#[test] fn text_textLength_negative() { assert_eq!(render("tests/text/textLength/negative"), 0); }
#[test] fn text_textLength_on_a_single_tspan() { assert_eq!(render("tests/text/textLength/on-a-single-tspan"), 0); }
#[test] fn text_textLength_on_text_and_tspan() { assert_eq!(render("tests/text/textLength/on-text-and-tspan"), 0); }
#[test] fn text_textLength_zero() { assert_eq!(render("tests/text/textLength/zero"), 0); }
#[test] fn text_textPath_closed_path() { assert_eq!(render("tests/text/textPath/closed-path"), 0); }
#[test] fn text_textPath_complex() { assert_eq!(render("tests/text/textPath/complex"), 0); }
#[test] fn text_textPath_dy_with_tiny_coordinates() { assert_eq!(render("tests/text/textPath/dy-with-tiny-coordinates"), 0); }
#[test] fn text_textPath_invalid_link() { assert_eq!(render("tests/text/textPath/invalid-link"), 0); }
#[test] fn text_textPath_invalid_textPath_in_the_middle() { assert_eq!(render("tests/text/textPath/invalid-textPath-in-the-middle"), 0); }
#[test] fn text_textPath_link_to_rect() { assert_eq!(render("tests/text/textPath/link-to-rect"), 0); }
#[test] fn text_textPath_m_A_path() { assert_eq!(render("tests/text/textPath/m-A-path"), 0); }
#[test] fn text_textPath_m_L_Z_path() { assert_eq!(render("tests/text/textPath/m-L-Z-path"), 0); }
#[test] fn text_textPath_method_eq_stretch() { assert_eq!(render("tests/text/textPath/method=stretch"), 0); }
#[test] fn text_textPath_mixed_children_1() { assert_eq!(render("tests/text/textPath/mixed-children-1"), 0); }
#[test] fn text_textPath_mixed_children_2() { assert_eq!(render("tests/text/textPath/mixed-children-2"), 0); }
#[test] fn text_textPath_nested() { assert_eq!(render("tests/text/textPath/nested"), 0); }
#[test] fn text_textPath_no_link() { assert_eq!(render("tests/text/textPath/no-link"), 0); }
#[test] fn text_textPath_path_with_ClosePath() { assert_eq!(render("tests/text/textPath/path-with-ClosePath"), 0); }
#[test] fn text_textPath_path_with_subpaths_and_startOffset() { assert_eq!(render("tests/text/textPath/path-with-subpaths-and-startOffset"), 0); }
#[test] fn text_textPath_path_with_subpaths() { assert_eq!(render("tests/text/textPath/path-with-subpaths"), 0); }
#[test] fn text_textPath_side_eq_right() { assert_eq!(render("tests/text/textPath/side=right"), 0); }
#[test] fn text_textPath_simple_case() { assert_eq!(render("tests/text/textPath/simple-case"), 0); }
#[test] fn text_textPath_spacing_eq_auto() { assert_eq!(render("tests/text/textPath/spacing=auto"), 0); }
#[test] fn text_textPath_startOffset_eq__100() { assert_eq!(render("tests/text/textPath/startOffset=-100"), 0); }
#[test] fn text_textPath_startOffset_eq_10percent() { assert_eq!(render("tests/text/textPath/startOffset=10percent"), 0); }
#[test] fn text_textPath_startOffset_eq_30() { assert_eq!(render("tests/text/textPath/startOffset=30"), 0); }
#[test] fn text_textPath_startOffset_eq_5mm() { assert_eq!(render("tests/text/textPath/startOffset=5mm"), 0); }
#[test] fn text_textPath_startOffset_eq_9999() { assert_eq!(render("tests/text/textPath/startOffset=9999"), 0); }
#[test] fn text_textPath_tspan_with_absolute_position() { assert_eq!(render("tests/text/textPath/tspan-with-absolute-position"), 0); }
#[test] fn text_textPath_tspan_with_relative_position() { assert_eq!(render("tests/text/textPath/tspan-with-relative-position"), 0); }
#[test] fn text_textPath_two_paths() { assert_eq!(render("tests/text/textPath/two-paths"), 0); }
#[test] fn text_textPath_very_long_text() { assert_eq!(render("tests/text/textPath/very-long-text"), 0); }
#[test] fn text_textPath_with_baseline_shift_and_rotate() { assert_eq!(render("tests/text/textPath/with-baseline-shift-and-rotate"), 0); }
#[test] fn text_textPath_with_baseline_shift() { assert_eq!(render("tests/text/textPath/with-baseline-shift"), 0); }
#[test] fn text_textPath_with_big_letter_spacing() { assert_eq!(render("tests/text/textPath/with-big-letter-spacing"), 0); }
#[test] fn text_textPath_with_coordinates_on_text() { assert_eq!(render("tests/text/textPath/with-coordinates-on-text"), 0); }
#[test] fn text_textPath_with_coordinates_on_textPath() { assert_eq!(render("tests/text/textPath/with-coordinates-on-textPath"), 0); }
#[test] fn text_textPath_with_filter() { assert_eq!(render("tests/text/textPath/with-filter"), 0); }
#[test] fn text_textPath_with_invalid_path_and_xlink_href() { assert_eq!(render("tests/text/textPath/with-invalid-path-and-xlink-href"), 0); }
#[test] fn text_textPath_with_letter_spacing() { assert_eq!(render("tests/text/textPath/with-letter-spacing"), 0); }
#[test] fn text_textPath_with_path_and_xlink_href() { assert_eq!(render("tests/text/textPath/with-path-and-xlink-href"), 0); }
#[test] fn text_textPath_with_path() { assert_eq!(render("tests/text/textPath/with-path"), 0); }
#[test] fn text_textPath_with_rotate() { assert_eq!(render("tests/text/textPath/with-rotate"), 0); }
#[test] fn text_textPath_with_text_anchor() { assert_eq!(render("tests/text/textPath/with-text-anchor"), 0); }
#[test] fn text_textPath_with_transform_on_a_referenced_path() { assert_eq!(render("tests/text/textPath/with-transform-on-a-referenced-path"), 0); }
#[test] fn text_textPath_with_transform_outside_a_referenced_path() { assert_eq!(render("tests/text/textPath/with-transform-outside-a-referenced-path"), 0); }
#[test] fn text_textPath_with_underline() { assert_eq!(render("tests/text/textPath/with-underline"), 0); }
#[test] fn text_textPath_writing_mode_eq_tb() { assert_eq!(render("tests/text/textPath/writing-mode=tb"), 0); }
#[test] fn text_tref_link_to_a_complex_text() { assert_eq!(render("tests/text/tref/link-to-a-complex-text"), 0); }
#[test] fn text_tref_link_to_a_non_SVG_element() { assert_eq!(render("tests/text/tref/link-to-a-non-SVG-element"), 0); }
#[test] fn text_tref_link_to_a_non_text_element() { assert_eq!(render("tests/text/tref/link-to-a-non-text-element"), 0); }
#[test] fn text_tref_link_to_an_external_file_element() { assert_eq!(render("tests/text/tref/link-to-an-external-file-element"), 0); }
#[test] fn text_tref_link_to_text() { assert_eq!(render("tests/text/tref/link-to-text"), 0); }
#[test] fn text_tref_nested() { assert_eq!(render("tests/text/tref/nested"), 0); }
#[test] fn text_tref_position_attributes() { assert_eq!(render("tests/text/tref/position-attributes"), 0); }
#[test] fn text_tref_style_attributes() { assert_eq!(render("tests/text/tref/style-attributes"), 0); }
#[test] fn text_tref_with_a_title_child() { assert_eq!(render("tests/text/tref/with-a-title-child"), 0); }
#[test] fn text_tref_with_text() { assert_eq!(render("tests/text/tref/with-text"), 0); }
#[test] fn text_tref_xml_space() { assert_eq!(render("tests/text/tref/xml-space"), 0); }
#[test] fn text_tspan_bidi_reordering() { assert_eq!(render("tests/text/tspan/bidi-reordering"), 0); }
#[test] fn text_tspan_mixed_font_size() { assert_eq!(render("tests/text/tspan/mixed-font-size"), 0); }
#[test] fn text_tspan_mixed_xml_space_1() { assert_eq!(render("tests/text/tspan/mixed-xml-space-1"), 0); }
#[test] fn text_tspan_mixed_xml_space_2() { assert_eq!(render("tests/text/tspan/mixed-xml-space-2"), 0); }
#[test] fn text_tspan_mixed_xml_space_3() { assert_eq!(render("tests/text/tspan/mixed-xml-space-3"), 0); }
#[test] fn text_tspan_mixed() { assert_eq!(render("tests/text/tspan/mixed"), 0); }
#[test] fn text_tspan_multiple_coordinates() { assert_eq!(render("tests/text/tspan/multiple-coordinates"), 0); }
#[test] fn text_tspan_nested_rotate() { assert_eq!(render("tests/text/tspan/nested-rotate"), 0); }
#[test] fn text_tspan_nested_whitespaces() { assert_eq!(render("tests/text/tspan/nested-whitespaces"), 0); }
#[test] fn text_tspan_nested() { assert_eq!(render("tests/text/tspan/nested"), 0); }
#[test] fn text_tspan_only_with_y() { assert_eq!(render("tests/text/tspan/only-with-y"), 0); }
#[test] fn text_tspan_outside_the_text() { assert_eq!(render("tests/text/tspan/outside-the-text"), 0); }
#[test] fn text_tspan_pseudo_multi_line() { assert_eq!(render("tests/text/tspan/pseudo-multi-line"), 0); }
#[test] fn text_tspan_rotate_and_display_none() { assert_eq!(render("tests/text/tspan/rotate-and-display-none"), 0); }
#[test] fn text_tspan_rotate_on_child() { assert_eq!(render("tests/text/tspan/rotate-on-child"), 0); }
#[test] fn text_tspan_sequential() { assert_eq!(render("tests/text/tspan/sequential"), 0); }
#[test] fn text_tspan_style_override() { assert_eq!(render("tests/text/tspan/style-override"), 0); }
#[test] fn text_tspan_text_shaping_across_multiple_tspan_1() { assert_eq!(render("tests/text/tspan/text-shaping-across-multiple-tspan-1"), 0); }
#[test] fn text_tspan_text_shaping_across_multiple_tspan_2() { assert_eq!(render("tests/text/tspan/text-shaping-across-multiple-tspan-2"), 0); }
#[test] fn text_tspan_transform() { assert_eq!(render("tests/text/tspan/transform"), 0); }
#[test] fn text_tspan_tspan_bbox_1() { assert_eq!(render("tests/text/tspan/tspan-bbox-1"), 0); }
#[test] fn text_tspan_tspan_bbox_2() { assert_eq!(render("tests/text/tspan/tspan-bbox-2"), 0); }
#[test] fn text_tspan_with_clip_path() { assert_eq!(render("tests/text/tspan/with-clip-path"), 0); }
#[test] fn text_tspan_with_dy() { assert_eq!(render("tests/text/tspan/with-dy"), 0); }
#[test] fn text_tspan_with_filter() { assert_eq!(render("tests/text/tspan/with-filter"), 0); }
#[test] fn text_tspan_with_mask() { assert_eq!(render("tests/text/tspan/with-mask"), 0); }
#[test] fn text_tspan_with_opacity() { assert_eq!(render("tests/text/tspan/with-opacity"), 0); }
#[test] fn text_tspan_with_x_and_y() { assert_eq!(render("tests/text/tspan/with-x-and-y"), 0); }
#[test] fn text_tspan_without_attributes() { assert_eq!(render("tests/text/tspan/without-attributes"), 0); }
#[test] fn text_tspan_xml_space_1() { assert_eq!(render("tests/text/tspan/xml-space-1"), 0); }
#[test] fn text_tspan_xml_space_2() { assert_eq!(render("tests/text/tspan/xml-space-2"), 0); }
#[test] fn text_unicode_bidi_bidi_override() { assert_eq!(render("tests/text/unicode-bidi/bidi-override"), 0); }
#[test] fn text_word_spacing__5() { assert_eq!(render("tests/text/word-spacing/-5"), 0); }
#[test] fn text_word_spacing_0() { assert_eq!(render("tests/text/word-spacing/0"), 0); }
#[test] fn text_word_spacing_10() { assert_eq!(render("tests/text/word-spacing/10"), 0); }
#[test] fn text_word_spacing_2mm() { assert_eq!(render("tests/text/word-spacing/2mm"), 0); }
#[test] fn text_word_spacing_5percent() { assert_eq!(render("tests/text/word-spacing/5percent"), 0); }
#[test] fn text_word_spacing_large_negative() { assert_eq!(render("tests/text/word-spacing/large-negative"), 0); }
#[test] fn text_word_spacing_normal() { assert_eq!(render("tests/text/word-spacing/normal"), 0); }
#[test] fn text_writing_mode_arabic_with_rl() { assert_eq!(render("tests/text/writing-mode/arabic-with-rl"), 0); }
#[test] fn text_writing_mode_horizontal_tb() { assert_eq!(render("tests/text/writing-mode/horizontal-tb"), 0); }
#[test] fn text_writing_mode_inheritance() { assert_eq!(render("tests/text/writing-mode/inheritance"), 0); }
#[test] fn text_writing_mode_invalid_value() { assert_eq!(render("tests/text/writing-mode/invalid-value"), 0); }
#[test] fn text_writing_mode_japanese_with_tb() { assert_eq!(render("tests/text/writing-mode/japanese-with-tb"), 0); }
#[test] fn text_writing_mode_lr_tb() { assert_eq!(render("tests/text/writing-mode/lr-tb"), 0); }
#[test] fn text_writing_mode_lr() { assert_eq!(render("tests/text/writing-mode/lr"), 0); }
#[test] fn text_writing_mode_mixed_languages_with_tb_and_underline() { assert_eq!(render("tests/text/writing-mode/mixed-languages-with-tb-and-underline"), 0); }
#[test] fn text_writing_mode_mixed_languages_with_tb() { assert_eq!(render("tests/text/writing-mode/mixed-languages-with-tb"), 0); }
#[test] fn text_writing_mode_on_tspan() { assert_eq!(render("tests/text/writing-mode/on-tspan"), 0); }
#[test] fn text_writing_mode_rl_tb() { assert_eq!(render("tests/text/writing-mode/rl-tb"), 0); }
#[test] fn text_writing_mode_rl() { assert_eq!(render("tests/text/writing-mode/rl"), 0); }
#[test] fn text_writing_mode_tb_and_punctuation() { assert_eq!(render("tests/text/writing-mode/tb-and-punctuation"), 0); }
#[test] fn text_writing_mode_tb_rl() { assert_eq!(render("tests/text/writing-mode/tb-rl"), 0); }
#[test] fn text_writing_mode_tb_with_alignment() { assert_eq!(render("tests/text/writing-mode/tb-with-alignment"), 0); }
#[test] fn text_writing_mode_tb_with_dx_on_second_tspan() { assert_eq!(render("tests/text/writing-mode/tb-with-dx-on-second-tspan"), 0); }
#[test] fn text_writing_mode_tb_with_dx_on_tspan() { assert_eq!(render("tests/text/writing-mode/tb-with-dx-on-tspan"), 0); }
#[test] fn text_writing_mode_tb_with_dy_on_second_tspan() { assert_eq!(render("tests/text/writing-mode/tb-with-dy-on-second-tspan"), 0); }
#[test] fn text_writing_mode_tb_with_rotate_and_underline() { assert_eq!(render("tests/text/writing-mode/tb-with-rotate-and-underline"), 0); }
#[test] fn text_writing_mode_tb_with_rotate() { assert_eq!(render("tests/text/writing-mode/tb-with-rotate"), 0); }
#[test] fn text_writing_mode_tb() { assert_eq!(render("tests/text/writing-mode/tb"), 0); }
#[test] fn text_writing_mode_vertical_lr() { assert_eq!(render("tests/text/writing-mode/vertical-lr"), 0); }
#[test] fn text_writing_mode_vertical_rl() { assert_eq!(render("tests/text/writing-mode/vertical-rl"), 0); }
================================================
FILE: crates/resvg/tests/resources/green.css
================================================
#rect1 { fill:green; }
================================================
FILE: crates/usvg/Cargo.toml
================================================
[package]
name = "usvg"
version = "0.47.0"
keywords = ["svg"]
license.workspace = true
edition = "2024"
rust-version = "1.87.0"
description = "An SVG simplification library."
categories = ["multimedia::images"]
repository = "https://github.com/linebender/resvg"
documentation = "https://docs.rs/usvg/"
readme = "README.md"
exclude = ["tests"]
workspace = "../.."
[[bin]]
name = "usvg"
required-features = ["text", "system-fonts", "memmap-fonts"]
[dependencies]
base64 = "0.22" # for embedded images
log = "0.4"
pico-args = { version = "0.5", features = ["eq-separator"] }
strict-num = "0.1.1"
svgtypes = "0.16.1"
tiny-skia-path = "0.12.0"
xmlwriter = "0.1"
# parser
data-url = "0.3" # for href parsing
flate2 = { version = "1.1", default-features = false, features = ["rust_backend"] } # SVGZ decoding
imagesize = "0.14.0" # raster images size detection
kurbo = "0.13.0" # Bezier curves utils
roxmltree = "0.21.1"
simplecss = "0.2"
siphasher = "1.0" # perfect hash implementation
# text
fontdb = { version = "0.23.0", default-features = false, optional = true }
rustybuzz = { version = "0.20.1", optional = true }
# Note: ttf-parser is re-exported from rustybuzz, but we need gvar-alloc for variable fonts
# with many variation axes (like Roboto Flex which has 13 axes)
ttf-parser = { version = "0.25.1", features = ["gvar-alloc"], optional = true }
unicode-bidi = { version = "0.3", optional = true }
unicode-script = { version = "0.5", optional = true }
unicode-vo = { version = "0.1", optional = true }
[dev-dependencies]
once_cell = "1.21"
[features]
default = ["text", "system-fonts", "memmap-fonts"]
# Enables text-to-path conversion support.
# Adds around 400KiB to your binary.
text = ["fontdb", "rustybuzz", "ttf-parser", "unicode-bidi", "unicode-script", "unicode-vo"]
# Enables system fonts loading.
system-fonts = ["fontdb/fs", "fontdb/fontconfig"]
# Enables font files memmaping for faster loading.
memmap-fonts = ["fontdb/memmap"]
================================================
FILE: crates/usvg/LICENSE-APACHE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
================================================
FILE: crates/usvg/LICENSE-MIT
================================================
Copyright 2017 the Resvg Authors
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.
================================================
FILE: crates/usvg/README.md
================================================
# usvg
[](https://crates.io/crates/usvg)
[](https://docs.rs/usvg)
[](https://www.rust-lang.org)
`usvg` (micro SVG) is an [SVG] parser that tries to solve most of SVG complexity.
SVG is notoriously hard to parse. `usvg` presents a layer between an XML library and
a potential SVG rendering library. It will parse an input SVG into a strongly-typed tree structure
were all the elements, attributes, references and other SVG features are already resolved
and presented in the simplest way possible.
So a caller doesn't have to worry about most of the issues related to SVG parsing
and can focus just on the rendering part.
## Features
- All supported attributes are resolved.
No need to worry about inheritable, implicit and default attributes
- CSS will be applied
- Only simple paths
- Basic shapes (like `rect` and `circle`) will be converted into paths
- Paths contain only absolute *MoveTo*, *LineTo*, *QuadTo*, *CurveTo* and *ClosePath* segments.
ArcTo, implicit and relative segments will be converted
- `use` will be resolved and replaced with the reference content
- Nested `svg` will be resolved
- Invalid, malformed elements will be removed
- Relative length units (mm, em, etc.) will be converted into pixels/points
- External images will be loaded
- Internal, base64 images will be decoded
- All references (like `#elem` and `url(#elem)`) will be resolved
- `switch` will be resolved
- Text elements, which are probably the hardest part of SVG, will be completely resolved.
This includes all the attributes resolving, whitespaces preprocessing (`xml:space`),
text chunks and spans resolving
- Markers will be converted into regular elements. No need to place them manually
- All filters are supported. Including filter functions, like `filter="contrast(50%)"`
- Recursive elements will be detected and removed
- `objectBoundingBox` will be replaced with `userSpaceOnUse`
## Limitations
- Unsupported SVG features 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`, no events and no animations
## License
Licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or )
- MIT license ([LICENSE-MIT](LICENSE-MIT) or )
at your option.
## Contribution
Contributions are welcome by pull request.
The [Rust code of conduct] applies.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be licensed as above, without any additional terms or conditions.
[Rust Code of Conduct]: https://www.rust-lang.org/policies/code-of-conduct
[SVG]: https://en.wikipedia.org/wiki/Scalable_Vector_Graphics
================================================
FILE: crates/usvg/codegen/Cargo.toml
================================================
[package]
name = "codegen"
version = "0.1.0"
license.workspace = true
edition = "2021"
publish = false
[[bin]]
name = "codegen"
path = "main.rs"
[dependencies]
phf_codegen = "0.7.24"
itertools = "0.14"
================================================
FILE: crates/usvg/codegen/README.md
================================================
We don't use cargo build script, since this data will rarely be changed and
there is no point in regenerating it each time.
To regenerate files run:
```
cargo run
```
================================================
FILE: crates/usvg/codegen/attributes.txt
================================================
alignment-baseline
amplitude
azimuth
background-color
baseFrequency
baseline-shift
bias
class
clip
clip-path
clip-rule
clipPathUnits
color
color-interpolation
color-interpolation-filters
color-profile
color-rendering
cx
cy
d
diffuseConstant
direction
display
divisor
dominant-baseline
dx
dy
edgeMode
elevation
enable-background
exponent
fill
fill-opacity
fill-rule
filter
filterUnits
flood-color
flood-opacity
font
font-family
font-feature-settings
font-kerning
font-optical-sizing
font-size
font-size-adjust
font-stretch
font-style
font-synthesis
font-variant
font-variant-caps
font-variant-east-asian
font-variant-ligatures
font-variant-numeric
font-variant-position
font-variation-settings
font-weight
fr
fx
fy
glyph-orientation-horizontal
glyph-orientation-vertical
gradientTransform
gradientUnits
height
href
id
image-rendering
in
in2
inline-size
intercept
isolation
k1
k2
k3
k4
kernelMatrix
kernelUnitLength
kerning
lengthAdjust
letter-spacing
lighting-color
limitingConeAngle
line-height
marker-end
marker-mid
marker-start
markerHeight
markerUnits
markerWidth
mask
mask-border
mask-border-mode
mask-border-outset
mask-border-repeat
mask-border-slice
mask-border-source
mask-border-width
mask-clip
mask-composite
mask-image
mask-mode
mask-origin
mask-position
mask-size
mask-type
maskContentUnits
maskUnits
mix-blend-mode
mode
numOctaves
offset
opacity
operator
order
orient
overflow
paint-order
path
pathLength
patternContentUnits
patternTransform
patternUnits
points
pointsAtX
pointsAtY
pointsAtZ
preserveAlpha
preserveAspectRatio
primitiveUnits
r
radius
refX
refY
requiredExtensions
requiredFeatures
result
rotate
rx
ry
scale
seed
shape-image-threshold
shape-inside
shape-margin
shape-padding
shape-rendering
shape-subtract
side
slope
space
specularConstant
specularExponent
spreadMethod
startOffset
stdDeviation
stitchTiles
stop-color
stop-opacity
stroke
stroke-dasharray
stroke-dashoffset
stroke-linecap
stroke-linejoin
stroke-miterlimit
stroke-opacity
stroke-width
style
surfaceScale
systemLanguage
tableValues
targetX
targetY
text-align
text-align-last
text-anchor
text-decoration
text-decoration-color
text-decoration-fill
text-decoration-line
text-decoration-stroke
text-decoration-style
text-indent
text-orientation
text-overflow
text-rendering
text-underline-position
textLength
transform
transform-box
transform-origin
type
unicode-bidi
unicode-range
values
vector-effect
viewBox
visibility
white-space
width
word-spacing
writing-mode
x
x1
x2
xChannelSelector
y
y1
y2
yChannelSelector
z
================================================
FILE: crates/usvg/codegen/elements.txt
================================================
a
circle
clipPath
defs
ellipse
feBlend
feColorMatrix
feComponentTransfer
feComposite
feConvolveMatrix
feDiffuseLighting
feDisplacementMap
feDistantLight
feDropShadow
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
================================================
FILE: crates/usvg/codegen/main.rs
================================================
// Copyright 2019 the Resvg Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
use 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> {
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;
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/parser/svgtree/names.rs")?;
writeln!(f, "// Copyright 2019 the Resvg Authors")?;
writeln!(f, "// SPDX-License-Identifier: Apache-2.0 OR MIT")?;
writeln!(f, "")?;
writeln!(f, "// This file is autogenerated. Do not edit it!")?;
writeln!(f, "// See ./codegen for details.\n")?;
gen_map("elements.txt", "An element ID.", "EId", "ELEMENTS", f)?;
gen_map("attributes.txt", "An attribute ID.", "AId", "ATTRIBUTES", f)?;
writeln!(f, "{}", PHF_SRC)?;
Ok(())
}
fn gen_map(
spec_path: &str,
enum_docs: &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, "/// {}", enum_docs)?;
writeln!(f, "#[allow(missing_docs)]")?;
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(crate) fn from_str(text: &str) -> Option<{}> {{",
enum_name
)?;
writeln!(f, " {}.get(text).cloned()", map_name)?;
writeln!(f, " }}")?;
writeln!(f, "")?;
writeln!(f, " /// Returns the original string.")?;
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 std::fmt::Debug for {} {{", enum_name)?;
writeln!(
f,
" fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {{"
)?;
writeln!(f, " write!(f, \"{{}}\", self.to_str())")?;
writeln!(f, " }}")?;
writeln!(f, "}}\n")?;
writeln!(f, "impl std::fmt::Display for {} {{", enum_name)?;
writeln!(
f,
" fn fmt(&self, f: &mut std::fmt::Formatter) -> std::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
}
================================================
FILE: crates/usvg/docs/post-processing.md
================================================
# XML Post-processing Steps
## No namespaces
In an SVG tree all elements and attributes belong to the SVG namespace.
## No non-SVG elements and attributes
Only SVG elements and attributes are preserved.
And their names are stored as `enum`s and not strings.
This increases performance and makes typos impossible.
## Only elements and text nodes
XML can contain elements, text nodes, comments and processing instructions.
Our tree contains only elements and text nodes inside the `text` element.
## Whitespaces trimming
Not only text nodes can be present only inside the `text` element,
but they are also trimmed according to the SVG rules, including `xml:space`.
For example:
```xml
Text
```
becomes
```xml
Text
```
And
```xml
Text
Text
```
becomes
```xml
TextText
```
## `style` attribute splitting
The `style` attribute content will be converted into normal attributes.
```xml
```
will become
```xml
```
The produced SVG tree never has `style` attributes.
## CSS will be applied
All _supported_ CSS rules will be applied.
```xml
```
will become
```xml
```
The produced SVG tree never has `style` elements and `class` attributes.
## `inherit` will be resolved
SVG allows setting some attribute values to `inherit`,
in which case the actual value should be taken from a parent element.
Not only it applies only to some attributes.
But some attributes also allow `inherit` only from the direct parent.
`rosvgtree` handles this for us.
## Recursive links removal
SVG supports referencing other elements via IRI and FuncIRI value types.
IRI is `xlink:href="#id"` and FuncIRI is `url(#id)`.
As in any link-based system this could lead to recursive references,
which when handled incorrectly can crash your app.
We're trying to detect all common cases, but it's
not 100% guarantee that there will be no recursive links left, but we're pretty close.
This includes simple cases like
```xml
```
and more complex one like
```xml
```
## Remember all elements with an ID
As mentioned above, SVG supports references. And it can reference any element in the document.
Instead of checking each element in the tree each time, which would be pretty slow,
we have an ID<->Node HashMap to quickly retrieve a requested element.
## Links are groups
The `` element in SVG is just a `` with a URL.
Since we really support only the static SVG subset, we can replace `` with ``.
## `tref` resolving
[`tref`](https://www.w3.org/TR/SVG11/text.html#TRefElement) is a pretty weird SVG element.
It's basically a way to reference text nodes.
We resolve them automatically and replace them with `tspan`.
```xml
Text
```
will become
```xml
Text
```
## `use` will be resolved
This is probably the only breaking change to the SVG structure.
The way the `use` works, is that it creates a shadow tree of nodes
that it's referencing. This is a great way to save space,
but it makes style properties resolving way harder.
This is because when you want to get a parent element from inside the `use`,
the tree should return `use`'s parent and not the referenced element parent.
To illustrate:
```xml
```
If you simply call `node.parent().attribute("fill")` it will return `red`, not `green`.
Because the current node is `rect1`.
As you can imagine, this is pretty hard to handle using a typical DOM model.
So instead we're simply coping referenced elements inside
the `use` so it can be treated as a regular group.
```xml
```
will become
```xml
```
The main limitation of this approach, excluding the fact we're creating way more elements
that we had initially, is that copied elements must not have an `id` attribute,
otherwise we would end up with multiple duplicates.
================================================
FILE: crates/usvg/docs/spec.adoc
================================================
= 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 `objectBoundingBox` units.
- No `viewBox` and `preserveAspectRatio` attributes.
- No `style` attribute, except for `mix-blend-mode` and `isolation`
- Default attributes are implicit.
You can use
https://github.com/linebender/resvg/tree/main/crates/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.
[[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` = < >>
* `patternUnits` = `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
* `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` = < >>
* `mask-type` = `alpha`? +
Default: luminance
* `maskUnits` = `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`
[[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` = < >>?
* `style` = < >>? +
This is the only place where the `style` attribute is used.
For reasons unknown, `mix-blend-mode` and `isolation` properties must not be set as attributes,
only as part of the `style` attribute. +
The set attribute will look like `mix-blend-mode:screen;isolation:isolate`.
Both properties are always set. +
The attribute is not present only in case of `mix-blend-mode:norma;isolation:auto`
[[path-element]]
=== The `path` element
*Children:*
* none
*Attributes:*
* `id` = < >>? +
An optional, but never empty, element ID.
* `d` = < >> +
* `fill` = `none` | < >> | < >> +
If set to `none` than all fill-* attributes will not be set too. +
Default: black
* `fill-opacity` = < >>? +
Default: 1
* `fill-rule` = `evenodd`? +
Default: nonzero
* `stroke` = `none` | < >> | < >> +
If set to `none` 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
* `paint-order` = `normal | stroke`? +
Default: `normal` +
Only `stroke` will be written.
* `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`? +
Default: visible
* `transform` = < >>? +
Can only be set on paths inside of `clipPath`.
[[image-element]]
=== The `image` element
*Children:*
* none
*Attributes:*
* `id` = < >>? +
An optional, but never empty, element ID.
* `xlink:href` = < >> +
The IRI contains a base64 encoded image.
* `width` = < >>
* `height` = < >>
* `image-rendering` = `optimizeSpeed`? +
Default: optimizeQuality
* `visibility` = `hidden`? +
Default: visible
== 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 omitted.
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 | overlay | darken | lighten | color-dodge |color-burn |
hard-light | soft-light | difference | exclusion | hue | saturation | color | luminosity`
* <>
=== 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` = <