[
  {
    "path": ".github/copyright.sh",
    "content": "#!/bin/bash\n\n# If there are new files with headers that can't match the conditions here,\n# then the files can be ignored by an additional glob argument via the -g flag.\n# For example:\n#   -g \"!src/special_file.rs\"\n#   -g \"!src/special_directory\"\n\n# Check all the standard Rust source files\noutput=$(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}\" .)\n\nif [ -n \"$output\" ]; then\n\techo -e \"The following files lack the correct copyright header:\\n\"\n\techo $output\n\techo -e \"\\n\\nPlease add the following header:\\n\"\n\techo \"// Copyright $(date +%Y) the Resvg Authors\"\n\techo \"// SPDX-License-Identifier: Apache-2.0 OR MIT\"\n\techo -e \"\\n... rest of the file ...\\n\"\n\texit 1\nfi\n\necho \"All files have correct copyright headers.\"\nexit 0\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "Pull requests that include:\n\n- dependencies updates\n- code formatting fixes\n- clippy fixes\n- compiler warnings fixes\n\nwill not be accepted.\n\nThe only exception are spellchecking and grammar fixes.\n\nA pull request must contain a meaningful improvement to the project.\n"
  },
  {
    "path": ".github/workflows/main.yml",
    "content": "name: Build\n\non: [push, pull_request]\n\nenv:\n  CARGO_TERM_COLOR: always\n\njobs:\n  fmt:\n    name: formatting\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      # TODO: Enable this when more of the Linebender CI has been applied.\n      #- name: install stable toolchain\n      #  uses: dtolnay/rust-toolchain@master\n      #  with:\n      #    toolchain: ${{ env.RUST_STABLE_VER }}\n      #    components: rustfmt\n\n      - name: cargo fmt\n        run: cargo fmt --all --check\n\n      - name: install ripgrep\n        run: |\n          sudo apt update\n          sudo apt install ripgrep\n\n      - name: check copyright headers\n        run: bash .github/copyright.sh\n\n  build:\n    runs-on: ubuntu-latest\n    steps:\n    - name: Checkout\n      uses: actions/checkout@v3\n\n    # We have to use the Release mode, otherwise it would take forever.\n    - name: Test\n      run: cargo test --all --release\n\n    - name: Build C API\n      working-directory: crates/c-api\n      run: cargo build\n\n    - name: Build C API without default features\n      working-directory: crates/c-api\n      run: cargo build --no-default-features\n\n    - name: Build resvg without default support\n      working-directory: crates/resvg\n      run: cargo check --no-default-features\n\n    - name: Build usvg without default support\n      working-directory: crates/usvg\n      run: cargo check --no-default-features\n\n  msrv:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v3\n\n      - name: Install toolchain\n        uses: dtolnay/rust-toolchain@stable\n        with:\n          toolchain: 1.87.0\n\n      - name: Build\n        run: cargo build\n\n  # We have some Windows specific code that we should check on each commit.\n  windows:\n    runs-on: windows-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v3\n\n      # Toolchain is stable-x86_64-pc-windows-msvc by default. No need to change it.\n\n      - name: Build thumbnailer\n        working-directory: tools/explorer-thumbnailer\n        env:\n          RUSTFLAGS: -Ctarget-feature=+crt-static # make sure it's static\n        run: cargo build\n\n      # Unlike other binaries, viewsvg isn't built with crt-static\n      - name: Build C API\n        working-directory: crates/c-api\n        run: cargo build --release\n\n  # If this fails, consider changing your text or adding something to .typos.toml.\n  typos:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: check typos\n        uses: crate-ci/typos@v1.28.4\n"
  },
  {
    "path": ".github/workflows/tagged-release.yml",
    "content": "name: \"Tagged Release\"\n\non:\n  push:\n    tags:\n      - \"v*\"\n\nenv:\n  CARGO_TERM_COLOR: always\n\njobs:\n  create-release:\n    name: Create Release\n    runs-on: ubuntu-latest\n    steps:\n      - name: Create Release\n        id: create_release\n        uses: softprops/action-gh-release@v2\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          tag_name: ${{ github.ref }}\n          name: ${{ github.ref_name }}\n          body: |\n            - `resvg-0.*.0.tar.xz` is a sources archive with vendored Rust dependencies\n            - `resvg-explorer-extension.exe` is an SVG thumbnailer for Windows Explorer\n\n            Check [CHANGELOG](https://github.com/linebender/resvg/blob/${{ github.ref }}/CHANGELOG.md).\n          draft: false\n          prerelease: false\n    outputs:\n      upload_url: ${{ steps.create_release.outputs.upload_url }}\n\n  release-linux:\n    name: Release Linux\n    runs-on: ubuntu-latest\n    needs: [\"create-release\"]\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v2\n\n      - name: Build resvg\n        run: cargo build --release\n\n      - name: Build usvg\n        working-directory: crates/usvg\n        run: cargo build --release\n\n      - name: Collect\n        working-directory: target/release\n        run: |\n          strip -s resvg\n          strip -s usvg\n          tar czf resvg-linux-x86_64.tar.gz resvg\n          tar czf usvg-linux-x86_64.tar.gz usvg\n          mkdir ../../bin\n          cp resvg-linux-x86_64.tar.gz ../../bin/\n          cp usvg-linux-x86_64.tar.gz ../../bin/\n\n      - name: Get version\n        id: get_version\n        uses: battila7/get-version-action@v2\n\n      - name: Make vendored archive\n        run: |\n          VERSION=${{ steps.get_version.outputs.version-without-v }}\n          echo $VERSION\n          git clone https://github.com/linebender/resvg resvg-$VERSION\n          cd resvg-\"$VERSION\"\n          mkdir -p .cargo\n          cargo vendor > .cargo/config\n          cd ..\n          env XZ_OPT=\"-9e\" tar \\\n              --exclude=\".git\" \\\n              --exclude=\"resvg-$VERSION/.github\" \\\n              --exclude=\"resvg-$VERSION/version-bump.md\" \\\n              --exclude=\"resvg-$VERSION/docs\" \\\n              -cJf resvg-\"$VERSION\".tar.xz resvg-\"$VERSION\"\n          cp resvg-\"$VERSION\".tar.xz bin/\n\n      - name: Upload binaries\n        uses: alexellis/upload-assets@0.2.2\n        env:\n          GITHUB_TOKEN: ${{ github.token }}\n        with:\n          asset_paths: '[\"bin/*\"]'\n\n  release-windows:\n    name: Release Windows\n    runs-on: windows-2019\n    needs: [\"create-release\"]\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v2\n\n      # Toolchain is stable-x86_64-pc-windows-msvc by default. No need to change it.\n\n      - name: Build resvg\n        env:\n          RUSTFLAGS: -Ctarget-feature=+crt-static # make sure it's static\n        run: cargo build --release\n\n      - name: Build usvg\n        working-directory: crates/usvg\n        env:\n          RUSTFLAGS: -Ctarget-feature=+crt-static # make sure it's static\n        run: cargo build --release\n\n      - name: Compress\n        working-directory: target/release\n        shell: cmd\n        run: |\n          7z a -tzip -mx9 resvg-win64.zip resvg.exe\n          7z a -tzip -mx9 usvg-win64.zip usvg.exe\n\n      - name: Build thumbnailer\n        working-directory: tools/explorer-thumbnailer\n        env:\n          RUSTFLAGS: -Ctarget-feature=+crt-static # make sure it's static\n        run: cargo build --release\n\n      - name: Build thumbnailer installer\n        working-directory: tools/explorer-thumbnailer/install\n        shell: cmd\n        run: |\n          \"%programfiles(x86)%\\Inno Setup 6\\iscc.exe\" \"installer.iss\"\n\n      # Unlike other binaries, viewsvg isn't built with crt-static\n      - name: Build C API\n        working-directory: crates/c-api\n        run: cargo build --release\n\n      - name: Prepare Developer Command Prompt for MSVC\n        uses: ilammy/msvc-dev-cmd@v1\n\n      - name: Collect\n        run: |\n          mkdir bin\n          cp target/release/resvg-win64.zip bin/\n          cp target/release/usvg-win64.zip bin/\n          cp tools/explorer-thumbnailer/install/resvg-explorer-extension.exe bin/\n\n      - name: Upload binaries\n        uses: alexellis/upload-assets@0.2.2\n        env:\n          GITHUB_TOKEN: ${{ github.token }}\n        with:\n          asset_paths: '[\"bin/*\"]'\n\n  release-macos-aarch64:\n    name: Release macOS (aarch64)\n    runs-on: macos-latest\n    needs: [\"create-release\"]\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v2\n\n      # Some weird CI glitch. Make sure we have the latest Rust.\n      - name: Install latest stable toolchain\n        uses: dtolnay/rust-toolchain@stable\n\n      - name: Build resvg\n        run: cargo build --release\n\n      - name: Build usvg\n        working-directory: crates/usvg\n        run: cargo build --release\n\n      - name: Compress\n        working-directory: target/release\n        run: |\n          7z a -tzip -mx9 resvg-macos-aarch64.zip resvg\n          7z a -tzip -mx9 usvg-macos-aarch64.zip usvg\n\n      - name: Build C API\n        working-directory: crates/c-api\n        run: cargo build --release\n\n      - name: Collect\n        run: |\n          mkdir bin\n          cp target/release/resvg-macos-aarch64.zip bin/\n          cp target/release/usvg-macos-aarch64.zip bin/\n\n      - name: Upload binaries\n        uses: alexellis/upload-assets@0.2.2\n        env:\n          GITHUB_TOKEN: ${{ github.token }}\n        with:\n          asset_paths: '[\"bin/*\"]'\n\n  release-macos-intel:\n    name: Release macOS (intel)\n    runs-on: macos-15-intel\n    needs: [\"create-release\"]\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v2\n\n      # Some weird CI glitch. Make sure we have the latest Rust.\n      - name: Install latest stable toolchain\n        uses: dtolnay/rust-toolchain@stable\n\n      - name: Build resvg\n        run: cargo build --release\n\n      - name: Build usvg\n        working-directory: crates/usvg\n        run: cargo build --release\n\n      - name: Compress\n        working-directory: target/release\n        run: |\n          7z a -tzip -mx9 resvg-macos-x86_64.zip resvg\n          7z a -tzip -mx9 usvg-macos-x86_64.zip usvg\n\n      - name: Build C API\n        working-directory: crates/c-api\n        run: cargo build --release\n\n      - name: Collect\n        run: |\n          mkdir bin\n          cp target/release/resvg-macos-x86_64.zip bin/\n          cp target/release/usvg-macos-x86_64.zip bin/\n\n      - name: Upload binaries\n        uses: alexellis/upload-assets@0.2.2\n        env:\n          GITHUB_TOKEN: ${{ github.token }}\n        with:\n          asset_paths: '[\"bin/*\"]'\n"
  },
  {
    "path": ".gitignore",
    "content": "target\n.directory\n.DS_Store\n.vscode\ntools/build-*\n**/diffs\n"
  },
  {
    "path": ".typos.toml",
    "content": "# See the configuration reference at\n# https://github.com/crate-ci/typos/blob/master/docs/reference.md\n\n# Corrections take the form of a key/value pair. The key is the incorrect word\n# and the value is the correct word. If the key and value are the same, the\n# word is treated as always correct. If the value is an empty string, the word\n# is treated as always incorrect.\n\n# Match Identifier - Case Sensitive\n[default.extend-identifiers]\nba = \"ba\"\nflate2 = \"flate2\"\nHel = \"Hel\"\nPNGs = \"PNGs\"\nSVGinOT = \"SVGinOT\"\n\n# Match Inside a Word - Case Insensitive\n[default.extend-words]\nwdth = \"wdth\"\n\n[files]\n# Include .github, .cargo, etc.\nignore-hidden = false\nextend-exclude = [\n    # /.git isn't in .gitignore, because git never tracks it.\n    # Typos doesn't know that, though.\n    \"/.git\",\n    \"*.svg\",\n]\n"
  },
  {
    "path": "AUTHORS",
    "content": "# This is the list of Resvg's significant contributors.\n#\n# This does not necessarily list everyone who has contributed code,\n# especially since many employees of one corporation may be contributing.\n# To see the full list of contributors, see the revision history in\n# source control.\nYevhenii Reizner\nLaurenz Stampfl\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Change Log\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](http://keepachangelog.com/)\nand this project adheres to [Semantic Versioning](http://semver.org/).\n\nThis changelog also contains important changes in dependencies.\n\n## [Unreleased]\n\nThis release has an MSRV of 1.87.0 for `usvg` and `resvg` and the C API.\n\n## [0.47.0] 2026-02-05\n\nThis release has an MSRV of 1.87.0 for `usvg` and `resvg` and the C API.\n\n### Added\n- Focal radius (`fr`) supported for Radial Gradients. (#1014 by @wmedrano)\n- Support for variable fonts based on font-variation-settings CSS property. (#997 by @oetiker)\n\n### Changed\n- `tiny-skia` has a major version bump from 0.11 to 0.12.\n\n## [0.46.0]\n\nThis release has an MSRV of 1.87.0 for `usvg` and `resvg` and the C API.\n\n### Added\n\n- Support SVGs without the xmlns attribute on the root. Thanks to [@JosefKuchar][].\n- Add a from_data_nested method to usvg::Tree (#955 by @tovrstra)\n- Use cache for glyph outlining (#957 by @newinnovations)\n- Support loading nested embedded images (#958 by @LaurenzV)\n\n### Changed\n\n- Bump dependencies, bump MSRV to 1.87, Upgrade to edition 2024 (#1002 and #1003 @LaurenzV)\n- Upgraded kurbo to 0.13 and svgtypes to 0.16.1. Thanks to [@HaHa421][].\n- Bump zune_jpeg (#964 by @LaurenzV)\n- Fix abs_bounding_box calculation for image (#924 by @Dabble63)\n- Fix crash caused by glyph splitting (#929 by @arnaud-secondlayer)\n- Fix a bug with incorrect resolving of fill/stroke color (#953 by @LaurenzV)\n- Fix inverted condition in has_text_nodes method (#967 by @Daaiid)\n- Do not write empty defs nodes (#980 by @Its-Just-Nans)\n- Check if text paths need to be written out (#981 by @Its-Just-Nans)\n- Use checked arithmetic when computing bounding box (#987 by @Its-Just-Nans)\n- Fix bug in rewriting of clip paths with transformed path (#988  by @Its-Just-Nans)\n\n\n### Removed\n\n- Remove unused phf dependency (#920)\n\n## [0.45.1] - 2025-04-16\n\n### Changed\n\n- Support SVGs without the xmlns attribute on the root (#892)\n- Add optimization for paths with markers in paint-order but no actual markers (#887)\n\n### Removed\n\n- tools/kde-dolphin-thumbnailer. This was never a released tool, and it doesn't support current versions of KDE/dolphin ([#897][] by [@DJMcNab][])\n\n\n## [0.45.0] - 2025-02-26\nThis is the first release under the stewardship of [Linebender][], who is now responsible for maintenance of this crate.\nMany thanks to Yevhenii Reizner for the years of hard work that he has poured into this and other crates.\n\nPlease note that the license of this project changed from `MPL-2.0` to `Apache-2.0 OR MIT`.\nSee [resvg#838](https://github.com/linebender/resvg/issues/838) for more information.\n\nThis release has an MSRV of 1.65 for `usvg` and 1.67.1 for `resvg` and the C API.\n\n### Added\n- Support for the `background-color` attribute.\n- Support for additional `image-rendering` attributes.\n- Support for the `!important` CSS flag.\n- Support for Luma JPEG images.\n- (c-api) `resvg_options_set_stylesheet`.\n  Thanks to [@michabay05][].\n- (svgtypes) Support for floating point hue in `hsl()` and `hsla()`.\n\n### Changed\n- License to `Apache-2.0 OR MIT`.\n  See [resvg#838](https://github.com/linebender/resvg/issues/838) for more information.\n- Updated WebP decoder for bug fixes and improved performance.\n  Thanks to [@Shnatsel][].\n- MSRV of resvg and c-api bumped to 1.67.1.\n- `fontdb` and `rustybuzz` have been updated.\n- Updated other dependencies.\n- (svgtypes) Simplified color component rounding and bounds checking.\n- Improved handling of paths with paint order `markers` but no actual markers.\n\n### Fixed\n- Relative unit handling when `use` references `symbol`.\n- (svgtypes) Rounding of hues in HSL to RGB conversion.\n- (svgtypes) Rounding of alpha.\n\n## [0.44.0] - 2024-09-28\n### Added\n- Stylesheet injection support.\n  Thanks to [@LaurenzV](https://github.com/LaurenzV).\n- (c-api) `resvg_get_object_bbox`\n- (c-api) `cargo-c` metadata.\n  Thanks to [@lu-zero](https://github.com/lu-zero).\n- Implement `From` for `fontdb` and `usvg` font types.\n  Thanks to [@dhardy](https://github.com/dhardy).\n\n### Changed\n- (c-api) `resvg_get_image_bbox` returns a _layer_ and not _object_ bounding box now.\n  Use `resvg_get_object_bbox` to preserve the old behavior.\n\n### Fixed\n- (svgtypes) Path parsing with `S` or `T` segments after `A`.\n- Bounding box calculation for the root group used for `viewBox` flattening.\n\n## [0.43.0] - 2024-08-10\n### Added\n- Support WebP images.\n  Thanks to [@notjosh](https://github.com/notjosh).\n\n### Changed\n- Use `zune-jpeg` instead of `jpeg-decoder`.\n  Thanks to [@mattfbacon](https://github.com/mattfbacon).\n- Update dependencies.\n\n### Fixed\n- Canvas size limits calculation.\n- SVG fonts handling.\n  Thanks to [@LaurenzV](https://github.com/LaurenzV).\n- Transforms in COLR fonts.\n  Thanks to [@LaurenzV](https://github.com/LaurenzV).\n\n## [0.42.0] - 2024-06-01\n### Added\n- `resvg` can render color fonts now, aka Emojis.<br>\n  In TrueType terms, `COLRv0`, `COLRv1` (mostly), `sbix`, `CBDT` and `SVG` tables are supported.<br>\n  Thanks to [@LaurenzV](https://github.com/LaurenzV).\n- Fonts matching and fallback can be controlled by the caller via `usvg::FontResolver` now.\n  Thanks to [@LaurenzV](https://github.com/LaurenzV).\n- `usvg::Options::font_resolver`. Similar to `usvg::Options::image_href_resolver` we already had.\n- `usvg::Options::fontdb`\n- Support double-quoted FuncIRIs, aka `url(\"#id\")`.\n- `image` element viewbox flattening.<br>\n  Instead of having `usvg::Image::view_box` that the caller should handle themselves,\n  we instead replace it with `transform` and optional `clip-path`.\n  This greatly simplifies `image` rendering.\n- `usvg::Image::size`\n- Tree viewbox flattening.<br>\n  Similar to `image` above, but affects the root `svg` element instead.\n- `pattern` viewbox flattening.<br>\n  Similar to `image` above, but for patterns.\n- Improve vertical text rendering.\n  Thanks to [@LaurenzV](https://github.com/LaurenzV).\n\n### Changed\n- `usvg::fontdb::Database` should be set in `usvg::Options` and not passed\n  to the parser separately now.\n- `usvg::Options` and `usvg::ImageHrefResolver` have a lifetime now.\n- Replace `usvg::Visibility` enum with just `bool`.\n- `usvg::Path::visibility()` is replaced with `usvg::Path::is_visible()`\n- `usvg::Image::visibility()` is replaced with `usvg::Image::is_visible()`\n- `usvg::TextSpan::visibility()` is replaced with `usvg::TextSpan::is_visible()`\n- Always represent `feImage` content as a link to an element.<br>\n  In SVG, `feImage` can contain a link to an element or a base64 image data, just like `image`.\n  From now, the inlined base64 data will always be represented by a link to an actual `image` element.\n  ```xml\n  <filter>\n    <feImage xlink:href=\"data:image/png;base64,...\"/>\n  </filter>\n  ```\n  will be parsed as\n  ```xml\n  <image id=\"image1\" xlink:href=\"data:image/png;base64,...\"/>\n  <filter>\n    <feImage xlink:href=\"#image1\"/>\n  </filter>\n  ```\n  This simplifies `feImage` rendering, since we don't have to handle both cases now.\n- The `--list-fonts` resvg argument can be used without providing an SVG file now.\n  Can simply call `resvg --list-fonts` now.\n- The `--list-fonts` resvg argument includes generic font family names as well now.\n- Make sure all warning and errors are printed to stderr.\n  Thanks to [@ahaoboy](https://github.com/ahaoboy).\n\n### Removed\n- `usvg::ViewBox`, `usvg::AspectRatio`, `usvg::Align` types. Nol longer used.\n- `usvg::filter::Image::aspect`. No longer needed.\n- `usvg::filter::Image::rendering_mode`. No longer needed.\n- `usvg::filter::Image::data`. Use `usvg::filter::Image::root` instead.\n- `usvg::Tree::view_box`. No longer needed.\n- `usvg::Image::view_box`. No longer needed.\n- `usvg::Image::pattern`. No longer needed.\n- `usvg::utils::align_pos`. No longer needed.\n- `usvg::Visibility`. No longer needed.\n- (c-api) `resvg_get_image_viewbox`. Use `resvg_get_image_size` instead.\n\n### Fixed\n- `context-fill` handling.\n  Thanks to [@LaurenzV](https://github.com/LaurenzV).\n\n## [0.41.0] - 2024-04-03\n### Added\n- `context-fill` and `context-stroke` support.\n  Thanks to [@LaurenzV](https://github.com/LaurenzV).\n- `usvg::Text::layouted()`, which returns a list of glyph IDs.\n  It can be used to manually draw glyphs, unlike with `usvg::Text::flattened()`, which returns\n  just vector paths.\n  Thanks to [@LaurenzV](https://github.com/LaurenzV).\n\n### Fixed\n- Missing text when a `text` element uses multiple fonts and one of them produces ligatures.\n- Absolute transform propagation during `use` resolving.\n- Absolute transform propagation during nested `svg` resolving.\n- `Node::abs_transform` documentation. The current element's transform _is_ included.\n\n## [0.40.0] - 2024-02-17\n### Added\n- `usvg::Tree` is `Send + Sync` compatible now.\n- `usvg::WriteOptions::preserve_text` to control how `usvg` generates an SVG.\n- `usvg::Image::abs_bounding_box`\n\n### Changed\n- All types in `usvg` are immutable now. Meaning that `usvg::Tree` cannot be modified\n  after creation anymore.\n- All struct fields in `usvg` are private now. Use getters instead.\n- All `usvg::Tree` parsing methods require the `fontdb` argument now.\n- All `defs` children like gradients, patterns, clipPaths, masks and filters are guarantee\n  to have a unique, non-empty ID.\n- All `defs` children like gradients, patterns, clipPaths, masks and filters are guarantee\n  to have `userSpaceOnUse` units now. No `objectBoundingBox` units anymore.\n- `usvg::Mask` is allowed to have no children now.\n- Text nodes will not be parsed when the `text` build feature isn't enabled.\n- `usvg::Tree::clip_paths`, `usvg::Tree::masks`, `usvg::Tree::filters` returns\n  a pre-collected slice of unique nodes now.\n  It's no longer a closure and you do not have to deduplicate nodes by yourself.\n- `usvg::filter::Primitive::x`, `y`, `width` and `height` methods were replaced\n  with `usvg::filter::Primitive::rect`.\n- Split `usvg::Tree::paint_servers` into `usvg::Tree::linear_gradients`,\n  `usvg::Tree::radial_gradients`, `usvg::Tree::patterns`.\n  All three returns pre-collected slices now.\n- A `usvg::Path` no longer can have an invalid bbox. Paths with an invalid bbox will be\n  rejected during parsing.\n- All `usvg` methods that return bounding boxes return non-optional `Rect` now.\n  No `NonZeroRect` as well.\n- `usvg::Text::flattened` returns `&Group` and not `Option<&Group>` now.\n- `usvg::ImageHrefDataResolverFn` and `usvg::ImageHrefStringResolverFn`\n  require `fontdb::Database` argument.\n- All shared nodes are stored in `Arc` and not `Rc<RefCell>` now.\n- `resvg::render_node` now includes filters bounding box. Meaning that a node with a blur filter\n  no longer be clipped.\n- Replace `usvg::utils::view_box_to_transform` with `usvg::ViewBox::to_transform`.\n- Rename `usvg::XmlOptions` into `usvg::WriteOptions` and embed `xmlwriter::Options`.\n\n### Removed\n- `usvg::Tree::postprocess()` and `usvg::PostProcessingSteps`. No longer needed.\n- `usvg::ClipPath::units()`, `usvg::Mask::units()`, `usvg::Mask::content_units()`,\n  `usvg::Filter::units()`, `usvg::Filter::content_units()`, `usvg::LinearGradient::units()`,\n  `usvg::RadialGradient::units()`, `usvg::Pattern::units()`, `usvg::Pattern::content_units()`\n  and `usvg::Paint::units()`. They are always `userSpaceOnUse` now.\n- `usvg::Units`. No longer needed.\n\n### Fixed\n- Text bounding box is accounted during SVG size resolving.\n  Previously, only paths and images were included.\n- Font selection when an italic font isn't explicitly marked as one.\n- Preserve `image` aspect ratio when only `width` or `height` are present.\n  Thanks to [@LaurenzV](https://github.com/LaurenzV).\n\n## [0.39.0] - 2024-02-06\n### Added\n- `font` shorthand parsing.\n  Thanks to [@LaurenzV](https://github.com/LaurenzV).\n- `usvg::Group::abs_bounding_box`\n- `usvg::Group::abs_stroke_bounding_box`\n- `usvg::Path::abs_bounding_box`\n- `usvg::Path::abs_stroke_bounding_box`\n- `usvg::Text::abs_bounding_box`\n- `usvg::Text::abs_stroke_bounding_box`\n\n### Changed\n- All `usvg-*` crates merged into one. There is just the `usvg` crate now, as before.\n\n### Removed\n- `usvg::Group::abs_bounding_box()` method. It's a field now.\n- `usvg::Group::abs_filters_bounding_box()`\n- `usvg::TreeParsing`, `usvg::TreePostProc` and `usvg::TreeWriting` traits.\n  They are no longer needed.\n\n### Fixed\n- `font-family` parsing.\n  Thanks to [@LaurenzV](https://github.com/LaurenzV).\n- Absolute bounding box calculation for paths.\n\n## [0.38.0] - 2024-01-21\n### Added\n- Each `usvg::Node` stores its absolute transform now.\n  `Node::abs_transform()` executes in constant time now.\n- `usvg::Tree::calculate_bounding_boxes` to calculate all bounding boxes beforehand.\n- `usvg::Node::bounding_box` which returns a precalculated node's bounding box in object coordinates.\n- `usvg::Node::abs_bounding_box` which returns a precalculated node's bounding box in canvas coordinates.\n- `usvg::Node::stroke_bounding_box` which returns a precalculated node's bounding box,\n  including stroke, in object coordinates.\n- `usvg::Node::abs_stroke_bounding_box` which returns a precalculated node's bounding box,\n  including stroke, in canvas coordinates.\n- (c-api) `resvg_get_node_stroke_bbox`\n- `usvg::Node::filters_bounding_box`\n- `usvg::Node::abs_filters_bounding_box`\n- `usvg::Tree::postprocess`\n\n### Changed\n- `resvg` renders `usvg::Tree` directly again. `resvg::Tree` is gone.\n- `usvg` no longer uses `rctree` for the nodes tree implementation.\n  The tree is a regular `enum` now.\n  - A caller no longer need to use the awkward `*node.borrow()`.\n  - No more panics on incorrect mutable `Rc<RefCell>` access.\n  - Tree nodes respect tree's mutability rules. Before, one could mutate tree nodes when the tree\n    itself is not mutable. Because `Rc<RefCell>` provides a shared mutable access.\n- Filters, clip paths, masks and patterns are stored as `Rc<RefCell<T>>` instead of `Rc<T>`.\n  This is required for proper mutability since `Node` itself is no longer an `Rc`.\n- Rename `usvg::NodeKind` into `usvg::Node`.\n- Upgrade to Rust 2021 edition.\n\n### Removed\n- `resvg::Tree`. No longer needed. `resvg` can render `usvg::Tree` directly once again.\n- `rctree::Node` methods. The `Node` API is completely different now.\n- `usvg::NodeExt`. No longer needed.\n- `usvg::Node::calculate_bbox`. Use `usvg::Node::abs_bounding_box` instead.\n- `usvg::Tree::convert_text`. Use `usvg::Tree::postprocess` instead.\n- `usvg::TreeTextToPath` trait. No longer needed.\n\n### Fixed\n- Mark `mask-type` as a presentation attribute.\n- Do not show needless warnings when parsing some attributes.\n- `feImage` rendering with a non-default position.\n  Thanks to [@LaurenzV](https://github.com/LaurenzV).\n\n## [0.37.0] - 2023-12-16\n### Added\n- `usvg` can write text back to SVG now.\n  Thanks to [@LaurenzV](https://github.com/LaurenzV).\n- `--preserve-text` flag to the `usvg` CLI tool.\n  Thanks to [@LaurenzV](https://github.com/LaurenzV).\n- Support [`transform-origin`](https://drafts.csswg.org/css-transforms/#transform-origin-property)\n  property.\n  Thanks to [@LaurenzV](https://github.com/LaurenzV).\n- Support non-default markers order via\n  [`paint-order`](https://svgwg.org/svg2-draft/painting.html#PaintOrder).\n  Previously, only fill and stroke could have been swapped.\n  Thanks to [@LaurenzV](https://github.com/LaurenzV).\n- `usvg_tree::Text::flattened` that will contain a flattened/outlined text.\n- `usvg_tree::Text::bounding_box`. Will be set only after text flattening.\n- Optimize `usvg_tree::NodeExt::abs_transform` by storing absolute transforms in the tree\n  instead of calculating them each time.\n\n### Changed\n- `usvg_tree::Text::positions` was replaced with `usvg_tree::Text::dx` and `usvg_tree::Text::dy`.<br>\n  `usvg_tree::CharacterPosition::x` and `usvg_tree::CharacterPosition::y` are gone.\n  They were redundant and you should use `usvg_tree::TextChunk::x`\n  and `usvg_tree::TextChunk::y` instead.\n- `usvg_tree::LinearGradient::id` and `usvg_tree::RadialGradient::id` are moved to\n  `usvg_tree::BaseGradient::id`.\n- Do not generate element IDs during parsing. Previously, some elements like `clipPath`s\n  and `filter`s could have generated IDs, but it wasn't very reliable and mostly unnecessary.\n  Renderer doesn't rely on them and usvg writer would generate them anyway.\n- Text-to-paths conversion via `usvg_text_layout::Tree::convert_text` no longer replaces\n  original text elements with paths, but instead puts them into `usvg_tree::Text::flattened`.\n\n### Removed\n- The `transform` field from `usvg_tree::Path`, `usvg_tree::Image` and `usvg_tree::Text`.\n  Only `usvg_tree::Group` can have it.<br>\n  It doesn't break anything, because those properties were never used before anyway.<br>\n  Thanks to [@LaurenzV](https://github.com/LaurenzV).\n- `usvg_tree::CharacterPosition`\n- `usvg_tree::Path::text_bbox`. Use `usvg_tree::Text::bounding_box` instead.\n- `usvg_text_layout::TextToPath` trait for `Text` nodes.\n  Only the whole tree can be converted at once.\n\n### Fixed\n- Path object bounding box calculation. We were using point bounds instead of tight contour bounds.\n  Was broken since v0.34\n- Convert text-to-paths in embedded SVGs as well. The one inside the `Image` node.\n  Thanks to [@LaurenzV](https://github.com/LaurenzV).\n- Indirect `text-decoration` resolving in some cases.\n  Thanks to [@LaurenzV](https://github.com/LaurenzV).\n- (usvg) Clip paths writing to SVG.\n  Thanks to [@LaurenzV](https://github.com/LaurenzV).\n\n## [0.36.0] - 2023-10-01\n### Added\n- `stroke-linejoin=miter-clip` support. SVG2.\n  Thanks to [@torokati44](https://github.com/torokati44).\n- Quoted FuncIRI support. Like `fill=\"url('#gradient')\"`. SVG2.\n  Thanks to [@romanzes](https://github.com/romanzes).\n- Allow float values in `rgb()` and `rgba()` colors. SVG2.\n  Thanks to [@yisibl](https://github.com/yisibl).\n- `auto-start-reverse` variant support to `orient` in markers. SVG2.\n  Thanks to [@EpicEricEE](https://github.com/EpicEricEE).\n\n### Changed\n- Update dependencies.\n\n### Fixed\n- Increase precision of the zero-scale transform check.\n  Was rejecting some valid transforms before.\n- Panic when rendering a very specific text.\n- Greatly improve parsing performance when an SVG has a lot of references.\n  Thanks to [@wez](https://github.com/wez).\n- (Qt API) Fix scaling factor calculation.\n  Thanks to [@missdeer](https://github.com/missdeer).\n\n## [0.35.0] - 2023-06-27\n### Fixed\n- Panic when an element is completely outside the viewbox.\n\n### Removed\n- `FillPaint` and `StrokePaint` filter inputs support.\n  It's a mostly undocumented SVG feature that no one supports and no one uses.\n  And it was adding a significant complexity to the codebase.\n- `usvg::filter::Filter::fill_paint` and `usvg::filter::Filter::stroke_paint`.\n- `BackgroundImage`, `BackgroundAlpha`, `FillPaint` and `StrokePaint` from `usvg::filter::Input`.\n- `usvg::Group::filter_fill_paint` and `usvg::Group::filter_stroke_paint`.\n\n## [0.34.1] - 2023-05-28\n### Fixed\n- Transform components order. Affects only `usvg` SVG output and C API.\n\n## [0.34.0] - 2023-05-27\n### Changed\n- `usvg` uses `tiny-skia` geometry primitives now, including the `Path` container.<br>\n  The main difference compared to the old `usvg` primitives\n  is that `tiny-skia` uses `f32` instead of `f64`.\n  So while in theory we could loose some precision, in practice, `f32` is used mainly\n  as a storage type and precise math operations are still done using `f64`.<br>\n  `tiny-skia` primitives are move robust, strict and have a nicer API.<br>\n  More importantly, this change reduces the peak memory usages for SVGs with large paths\n  (in terms of the number of segments).\n  And removes the need to convert `usvg::PathData` into `tiny-skia::Path` before rendering.\n  Which was just a useless reallocation.\n- All numbers are stored as `f32` instead of `f64` now.\n- Because we use `tiny-skia::Path` now, we allow _quadratic curves_ as well.\n  This includes `usvg` CLI output.\n- Because we allow _quadratic curves_ now, text might render slightly differently (better?).\n  This is because TrueType fonts contain only _quadratic curves_\n  and we were converting them to cubic before.\n- `usvg::Path` no longer implements `Default`. Use `usvg::Path::new` instead.\n- Replace `usvg::Rect` with `tiny_skia::NonZeroRect`.\n- Replace `usvg::PathBbox` with `tiny_skia::Rect`.\n- Unlike the old `usvg::PathBbox`, `tiny_skia::Rect` allows both width and height to be zero.\n  This is not an error.\n- `usvg::filter::Turbulence::base_frequency` was split into `base_frequency_x` and `base_frequency_y`.\n- `usvg::NodeExt::calculate_bbox` no longer includes stroke bbox.\n- (c-api) Use `float` instead of `double` everywhere.\n- The `svgfilters` crate was merged into `resvg`.\n- The `rosvgtree` crate was merged into `usvg-parser`.\n- `usvg::Group::filter_fill` moved to `usvg::filter::Filter::fill_paint`.\n- `usvg::Group::filter_stroke` moved to `usvg::filter::Filter::stroke_paint`.\n\n### Remove\n- `usvg::Point`. Use `tiny_skia::Point` instead.\n- `usvg::FuzzyEq`. Use `usvg::ApproxEqUlps` instead.\n- `usvg::FuzzyZero`. Use `usvg::ApproxZeroUlps` instead.\n- (c-api) `resvg_path_bbox`. Use `resvg_rect` instead.\n- `svgfilters` crate.\n- `rosvgtree` crate.\n\n### Fixed\n- Write `transform` on `clipPath` children in `usvg` SVG output.\n- Do not duplicate marker children IDs.\n  Previously, each element resolved for a marker would preserve its ID.\n  Affects only `usvg` SVG output and doesn't affect rendering.\n\n## [0.33.0] - 2023-05-17\n### Added\n- A new rendering algorithm.<br>\n  When rendering [isolated groups](https://razrfalcon.github.io/notes-on-svg-parsing/isolated-groups.html),\n  aka layers, we have to know the layer bounding box beforehand, which is ridiculously hard in SVG.<br>\n  Previously, resvg would simply use the canvas size for all the layers.\n  Meaning that to render a 10x10px layer on a 1000x1000px canvas, we would have to allocate and then blend\n  a 1000x1000px layer, which is just a waste of CPU cycles.<br>\n  The new rendering algorithm is able to calculate layer bounding boxes, which dramatically improves\n  performance when rendering a lot of tiny layers on a large canvas.<br>\n  Moreover, it makes performance more linear with a canvas size increase.<br>\n  The [paris-30k.svg](https://github.com/google/forma/blob/681e8bfd348caa61aab47437e7d857764c2ce522/assets/svgs/paris-30k.svg)\n  sample from [google/forma](https://github.com/google/forma) is rendered _115 times_ faster on M1 Pro now.\n  From ~33760ms down to ~290ms. 5269x3593px canvas.<br>\n  If we restrict the canvas to 1000x1000px, which would contain only the actual `paris-30k.svg` content,\n  then we're _13 times_ faster. From ~3252ms down to ~253ms.\n- `resvg::Tree`, aka a render tree, which is an even simpler version of `usvg::Tree`.\n  `usvg::Tree` had to be converted into `resvg::Tree` before rendering now.\n\n### Changed\n- Restructure the root directory. All crates are in the `crates` directory now.\n- Restructure tests. New directory structure and naming scheme.\n- Use `resvg::Tree::render` instead of `resvg::render`.\n- resvg's `--export-area-drawing` option uses calculated bounds instead of trimming\n  excessive alpha now. It's faster, but can lead to a slightly different output.\n- (c-api) Removed `fit_to` argument from `resvg_render`.\n- (c-api) Removed `fit_to` argument from `resvg_render_node`.\n- `usvg::ScreenSize` moved to `resvg`.\n- `usvg::ScreenRect` moved to `resvg`.\n- Rename `resvg::ScreenSize` into `resvg::IntSize`.\n- Rename `resvg::ScreenRect` into `resvg::IntRect`.\n\n### Removed\n- `filter` build feature from `resvg`. Filters are always enabled now.\n- `resvg::FitTo`\n- `usvg::utils::view_box_to_transform_with_clip`\n- `usvg::Size::to_screen_size`. Use `resvg::IntSize::from_usvg` instead.\n- `usvg::Rect::to_screen_size`. Use `resvg::IntSize::from_usvg(rect.size())` instead.\n- `usvg::Rect::to_screen_rect`. Use `resvg::IntRect::from_usvg` instead.\n- (c-api) `resvg_fit_to`\n- (c-api) `resvg_fit_to_type`\n\n### Fixed\n- Double quotes parsing in `font-family`.\n\n## [0.32.0] - 2023-04-23\n### Added\n- Clipping and masking is up to 20% faster.\n- `mask-type` property support. SVG2\n- `usvg_tree::MaskType`\n- `usvg_tree::Mask::kind`\n- (rosvgtree) New SVG 2 mask attributes.\n\n### Changed\n- `BackgroundImage` and `BackgroundAlpha` filter inputs will produce the same output\n  as `SourceGraphic` and `SourceAlpha` respectively.\n\n### Removed\n- `enable-background` support. This feature was never supported by browsers\n  and was deprecated in SVG 2. To my knowledge, only Batik has a good support of it.\n  Also, it's a performance nightmare, which caused multiple issues in resvg already.\n- `usvg_tree::EnableBackground`\n- `usvg_tree::Group::enable_background`\n- `usvg_tree::NodeExt::filter_background_start_node`\n\n### Fixed\n- Improve rectangular clipping anti-aliasing quality.\n- Mask's RGB to Luminance converter was ignoring premultiplied alpha.\n\n## [0.31.1] - 2023-04-22\n### Fixed\n- Use the latest `tiny-skia` to fix SVGs with large masks rendering.\n\n## [0.31.0] - 2023-04-10\n### Added\n- `usvg::Tree::paint_servers`\n- `usvg::Tree::clip_paths`\n- `usvg::Tree::masks`\n- `usvg::Tree::filters`\n- `usvg::Node::subroots`\n- (usvg) `--coordinates-precision` and `--transforms-precision` writing options.\n  Thanks to [@flxzt](https://github.com/flxzt).\n\n### Fixed\n- `fill-opacity` and `stroke-opacity` resolving.\n- Double `transform` when resolving `symbol`.\n- `symbol` clipping when its viewbox is the same as the document one.\n- (usvg) Deeply nested gradients, patterns, clip paths, masks and filters\n  were ignored during SVG writing.\n- Missing text in nested clip paths and mask, text decoration patterns, filter inputs and feImage.\n\n## [0.30.0] - 2023-03-25\n### Added\n- Readd `usvg` CLI tool. Can be installed via cargo as before.\n\n### Changed\n- Extract most `usvg` internals into new `usvg-tree` and `usvg-parser` crates.\n  `usvg-tree` contains just the SVG tree and all the types.\n  `usvg-parser` parsers SVG into `usvg-tree`.\n  And `usvg` is just an umbrella crate now.\n- To use `usvg::Tree::from*` methods one should import the `usvg::TreeParsing` trait now.\n- No need to import `usvg-text-layout` manually anymore. It is part of `usvg` now.\n- `rosvgtree` no longer reexports `svgtypes`.\n- `rosvgtree::Node::attribute` returns just a string now.\n- `rosvgtree::Node::find_attribute` returns just a `rosvgtree::Node` now.\n- Rename `usvg::Stretch` into `usvg::FontStretch`.\n- Rename `usvg::Style` into `usvg::FontStyle`.\n- `usvg::FitTo` moved to `resvg::FitTo`.\n- `usvg::IsDefault` trait is private now.\n\n### Removed\n- `rosvgtree::FromValue`. Due to Rust's orphan rules this trait is pretty useless.\n\n### Fixed\n- Recursive markers detection.\n- Skip malformed `transform` attributes without skipping the whole element.\n- Clipping path rectangle calculation for nested `svg` elements.\n  Thanks to [@LaurenzV](https://github.com/LaurenzV).\n- Panic when applying `text-decoration` on text with only one cluster.\n  Thanks to [@LaurenzV](https://github.com/LaurenzV).\n- (Qt API) Image size wasn't initialized. Thanks to [@missdeer](https://github.com/missdeer).\n- `resvg` CLI allows files with XML DTD again.\n- (svgtypes) Handle implicit MoveTo after ClosePath segments.\n\n## [0.29.0] - 2023-02-04\n### Added\n- `resvg` CLI loads system fonts only when an input SVG has text nodes now.\n  Fonts loading is an IO-heavy operation and by avoiding it we can speed up `resvg` execution.\n- `usvg::Group::should_isolate`\n- `usvg::Tree::has_text_nodes`\n\n### Changed\n- Some `usvg` internals were moved into the new `rosvgtree` crate.\n- Dummy groups are no longer removed. Use `usvg::Group::should_isolate` to check\n  if a group affects rendering.\n- `usvg-text-layout::TreeTextToPath::convert_text` no longer has the `keep_named_groups` argument.\n- MSRV bumped to 1.65\n- Update dependencies.\n\n### Removed\n- `usvg::Options::keep_named_groups`. Dummy groups are no longer removed.\n- (c-api) `resvg_options_set_keep_named_groups`\n- (Qt API) `ResvgOptions::setKeepNamedGroups`\n\n### Fixed\n- Missing `font-family` handling.\n- `font-weight` resolving.\n\n## [0.28.0] - 2022-12-03\n### Added\n- `usvg::Text` and `usvg::NodeKind::Text`.\n\n### Changed\n- `usvg` isn't converting text to paths by default now. A caller must call\n  `usvg::Tree::convert_text` or `usvg::Text::convert` from `usvg-text-layout` crate on demand.\n- `usvg` text layout implementation moved into `usvg-text-layout` crate.\n- During SVG size recovery, when no `width`, `height` and `viewBox` attributes have been set,\n  text nodes are no longer taken into an account. This is because a text node has no bbox\n  before conversion into path(s), which we no longer doing during parsing.\n- `usvg` is purely an SVG parser now. It doesn't convert text to paths\n  and doesn't write SVG anymore.\n- `usvg::filter::ConvolveMatrixData` methods are fields now.\n\n### Removed\n- `usvg` CLI binary. No alternatives for now.\n- All `usvg` build features.\n  - `filter`. Filter elements are always parsed by `usvg` now.\n  - `text`. Text elements are always parsed by `usvg` now.\n  - `export`. `usvg` cannot write an SVG anymore.\n- `usvg::Tree::to_string`. `usvg` cannot write an SVG anymore.\n- `usvg::TransformFromBBox` trait. This is just a regular `usvg::Transform` method now.\n- `usvg::OptionsRef`. `usvg::Options` is enough from now.\n- `usvg::Options::fontdb`. Used only by `usvg-text-layout` now.\n- `--dump-svg` from `resvg`.\n\n## [0.27.0] - 2022-11-27\n### Added\n- `lengthAdjust` and `textLength` attributes support.\n- Support automatic `image` size detection.\n  `width` and `height` attributes can be omitted or set to `auto` on `image` now. SVG2\n\n### Fixed\n- `--query-all` flag in `resvg` CLI.\n- Percentage values resolving.\n\n## [0.26.1] - 2022-11-21\n### Fixed\n- Allow `dominant-baseline` and `alignment-baseline` to be set via CSS.\n\n## [0.26.0] - 2022-11-20\n### Added\n- Minimal `dominant-baseline` and `alignment-baseline` support.\n- `mix-blend-mode` and `isolation` support. SVG2\n- Allow writing resvg output to stdout.\n- Allow disabling text kerning using `kerning=\"0\"` and `style=\"font-kerning:none\"`. SVG2\n- Allow `<percentage>` values for `opacity`, `fill-opacity`, `stroke-opacity`,\n  `flood-opacity` and `stop-opacity` attributes.<br>\n  You can write `opacity=\"50%\"` now. SVG2\n\n### Changed\n- Disable focal point correction on radial gradients to conform with SVG 2. SVG2\n- Update `feMorphology` radius value resolving.\n\n### Fixed\n- Do not clip nested `svg` when only the `viewBox` attribute is present.\n\n## [0.25.0] - 2022-10-30\n### Added\n- Partial `paint-order` attribute support.\n  Markers can only be under or above the shape.\n\n### Fixed\n- Compilation issues caused by `rustybuzz` update.\n\n## [0.24.0] - 2022-10-22\n### Added\n- CSS3 `writing-mode` variants `vertical-rl` and `vertical-lr`.\n  Thanks to [yisibl](https://github.com/yisibl).\n- (tiny-skia) AArch64 Neon SIMD support. Up to 3x faster on Apple M1.\n\n### Changed\n- `usvg::Tree` stores only `Group`, `Path` and `Image` nodes now.\n  Instead of emulating an SVG file structure, where gradients, patterns, filters, clips and masks\n  are part of the nodes tree (usually inside the `defs` element), we reference them using `Rc`\n  from now.\n  This change makes `usvg` a bit simpler. Makes `usvg` API way easier, since instead of\n  looking for a node via `usvg::Tree::defs_by_id` the caller can access the type directly via `Rc`.\n  And makes creation of custom `usvg::Tree`s way easier.\n- `clip_path`, `mask` and `filters` `usvg::Group` fields store `Rc` instead of `String` now.\n- `usvg::NodeExt::units` was moved to `usvg::Paint::units`.\n- `usvg::filter::ImageKind::Use` stores `usvg::Node` instead of `String`.\n- `usvg::PathData` stores commands and points separately now to reduce overall memory usage.\n- `usvg::PathData` segments should be accessed via `segments()` now.\n- Most numeric types have been moved to the `strict-num` crate.\n- Rename `NormalizedValue` into `NormalizedF64`.\n- Rename `PositiveNumber` into `PositiveF64`.\n- Raw number of numeric types should be accessed via `get()` method instead of `value()` now.\n- `usvg::TextSpan::font_size` is `NonZeroPositiveF64` instead of `f64` now.\n- Re-export `usvg` and `tiny-skia` dependencies in `resvg`.\n- Re-export `roxmltree` dependency in `usvg`.\n- (usvg) Output float precision is reduced from 11 to 8 digits.\n\n### Removed\n- `usvg::Tree::create`. `usvg::Tree` is an open struct now.\n- `usvg::Tree::root`. It's a public field now.\n- `usvg::Tree::svg_node`. Replaced with `usvg::Tree` public fields.\n- `defs`, `is_in_defs`, `append_to_defs` and `defs_by_id` from `usvg::Tree`.\n  We no longer emulate SVG structure. No alternative.\n- `usvg::Tree::is_in_defs`. There are no `defs` anymore.\n- `usvg::Paint::Link`. We store gradient and patterns directly in `usvg::Paint` now.\n- `usvg::Svg`. No longer needed. `size` and `view_box` are `usvg::Tree` fields now.\n- `usvg::SubPathIter` and `usvg::PathData::subpaths`. No longer used.\n\n### Fixed\n- Path bbox calculation scales stroke width too.\n  Thanks to [growler](https://github.com/growler).\n- (tiny-skia) Round caps roundness.\n- (xmlparser) Stack overflow on specific files.\n- (c-api) `resvg_is_image_empty` output was inverted.\n\n## [0.23.0] - 2022-06-11\n### Added\n- `#RRGGBBAA` and `#RGBA` color notation support.\n  Thanks to [demurgos](https://github.com/demurgos).\n\n### Fixed\n- Panic during recursive `pattern` resolving.\n  Thanks to [FylmTM](https://github.com/FylmTM).\n- Spurious warning when using `--export-id`.\n  Thanks to [benoit-pierre](https://github.com/benoit-pierre).\n\n## [0.22.0] - 2022-02-20\n### Added\n- Support `svg` referenced by `use`. External SVG files are still not supported.\n\n### Changed\n- `ttf-parser`, `fontdb` and `rustybuzz` have been updated.\n\n## [0.21.0] - 2022-02-13\n### Added\n- `usvg::ImageHrefResolver` that allows a custom `xlink:href` handling.\n  Thanks to [antmelnyk](https://github.com/antmelnyk).\n- `usvg::Options::image_href_resolver`\n- Support for GIF images inside the `<image>` element.\n- (fontdb) Support for loading user fonts on Windows.\n- (fontdb) Support for parsing fontconfig config files on Linux.\n  For now, only to retrieve a list of font dirs.\n\n### Changed\n- MSRV bumped to 1.51\n- `usvg::ImageKind` stores data as `Arc<Vec<u8>>` and not just `Vec<u8>` now.\n\n### Fixed\n- Every nested `svg` element defines a new viewBox now. Previously, we were always using the root one.\n- Correctly handle SVG size calculation when SVG doesn't have a size and any elements.\n- Improve groups ungrouping speed.\n\n## [0.20.0] - 2021-12-29\n### Changed\n- `resvg::render` and `resvg::render_node` accept a transform now.\n- (c-api) `resvg_render` and `resvg_render_node` accept a transform now.\n- `usvg::Color` is a custom type and not a `svgtypes::Color` reexport now.\n- `usvg::Color` doesn't contain alpha anymore, which have been added in v0.16\n  Alpha would be automatically flattened.\n  This makes [Micro SVG](https://github.com/linebender/resvg/blob/main/crates/usvg/docs/spec.adoc)\n  compatible with SVG 1.1 again.\n- (c-api) Rename `RESVG_FIT_TO_*` into `RESVG_FIT_TO_TYPE_*`.\n\n### Fixed\n- The `--background` argument in `resvg` correctly handles alpha now.\n- Fix building usvg without filter feature but with export.\n\n## [0.19.0] - 2021-10-04\n### Added\n- Better text-on-path converter accuracy by accounting the current transform.\n\n### Changed\n- `usvg::NodeExt::abs_transform` includes current node transform now.\n- Improved turbulence filter performance. Thanks to [akindle](https://github.com/akindle).\n- Multiple dependencies updated.\n\n## [0.18.0] - 2021-09-12\n### Added\n- `filter` build feature. Enabled by default.\n- `usvg::PathBbox` and `resvg_path_bbox` (to C API).\n\n### Changed\n- (usvg) All filter related types are under the `filter` module now.\n- (usvg) Remove `Fe` prefix from all filter types.\n- (c-api) `resvg_get_node_bbox` returns `resvg_path_bbox` now.\n\n### Fixed\n- Horizontal and vertical lines processing.\n- C API building without the `text` feature.\n\n## [0.17.0] - 2021-09-04\n### Added\n- `tiny-skia` updated with support of images larger than 8000x8000 pixels.\n- `feDropShadow` support. SVG2\n- [`<filter-value-list>`](https://www.w3.org/TR/filter-effects-1/#typedef-filter-value-list) support.\n  Meaning that the `filter` attribute can have multiple values now.\n  Like `url(#filter1) blur(2)`. SVG2\n- All [filter functions](https://www.w3.org/TR/filter-effects-1/#filter-functions). SVG2\n- Support all [new](https://www.w3.org/TR/compositing-1/#ltblendmodegt) `feBlend` modes. SVG2\n- Automatic SVG size detection when `width`/`height`/`viewBox` is not set.\n  Thanks to [reknih](https://github.com/reknih).\n- `usvg::Options::default_size`\n- `--default-width` and `--default-height` to usvg.\n\n### Changed\n- `usvg::Group::filter` is a list of filter IDs now.\n- `usvg::FeColorMatrixKind::Saturate` accepts any positive `f64` value now.\n- `svgfilters::ColorMatrix::Saturate` accepts any positive `f64` value now.\n- Fonts memory mapping was split into a separate build feature: `memmap-fonts`.\n  Now you can build resvg/usvg with `system-fonts`, but without `memmap-fonts`.\n  Enabled by default.\n- The `--dump-svg` argument in resvg CLI tool should be enabled using `--features dump-svg` now.\n  No enabled by default.\n- `usvg::Tree::to_string` is behind the `export` build feature now.\n\n### Fixed\n- When writing SVG, `usvg` will use `rgba()` notations for colors instead of `#RRGGBB`.\n\n## [0.16.0] - 2021-08-22\n### Added\n- CSS3 colors support. Specifically `rgba`, `hsl`, `hsla` and `transparent`. SVG2\n- Allow missing `rx`/`ry` attributes on `ellipse`. SVG2\n- Allow markers on all shapes. SVG2\n- `textPath` can reference basic shapes now. SVG2\n- `usvg::OptionsRef`, which is a non-owned `usvg::Options` variant.\n- `simplecss` updated with CSS specificity support.\n- `turn` angle unit support. SVG2\n- Basic `font-variant=small-caps` support. No font fallback.\n- `--export-area-page` to resvg.\n- `--export-area-drawing` to resvg.\n\n### Changed\n- `resvg::render_node` requires `usvg::Tree` now.\n- `usvg::Color` gained an `alpha` field.\n\n### Removed\n- `usvg::Node::tree`. Cannot be implemented efficiently anymore.\n- `usvg::SystemFontDB`. No longer needed.\n\n### Fixed\n- `pattern` scaling.\n- Greatly improve `symbol` resolving speed in `usvg`.\n- Whitespaces trimming on nested `tspan`.\n\n## [0.15.0] - 2021-06-13\n### Added\n- Allow reading SVG from stdin in `resvg` binary.\n- `--id-prefix` to `usvg`.\n- `FitTo::Size`\n- `resvg` binary accepts `--width` and `--height` args together now.\n  Previously, only `--width` or `--height` were allowed.\n- `usvg::Path::text_bbox`\n- The maximum number of SVG elements is limited by 1_000_000 now.\n  Mainly to prevent a billion laugh style attacks.\n- The maximum SVG elements nesting is limited by 1024 now.\n- `usvg::Error::ElementsLimitReached`\n\n### Changed\n- Improve clipping and masking performance on large images.\n- Remove layers caching. This was a pointless optimization.\n- Split _Preprocessing_ into _Reading_ and _Parsing_ in `resvg --perf`.\n- `usvg::XmlOptions` rewritten.\n- `usvg::Tree::to_string` requires a reference to `XmlOptions` now.\n\n### Removed\n- `usvg::Tree::from_file`. Use `from_data` or `from_str` instead.\n- `usvg::Error::InvalidFileSuffix`\n- `usvg::Error::FileOpenFailed`\n- (c-api) `RESVG_ERROR_INVALID_FILE_SUFFIX`\n\n### Fixed\n- Ignore tiny blur values. It could lead to a transparent image.\n- `use` style propagation when used with `symbol`.\n- Vertical text layout with relative offsets.\n- Text bbox calculation. `usvg` uses font metrics instead of path bbox now.\n\n## [0.14.1] - 2021-04-18\n### Added\n- Allow `href` without the `xlink` namespace.\n  This feature is part of SVG 2 (which we do not support),\n  but there are more and more files like this in the wild.\n\n### Changed\n- (usvg) Do not write `usvg:version` to the output SVG.\n\n### Fixed\n- (usvg) `overflow='inherit'` resolving.\n- (usvg) SVG Path length calculation that affects `startOffset` property in `textPath`.\n- (usvg) Fix `feImage` resolving when the linked element has\n  `opacity`, `clip-path`, `mask` and/or `filter` attributes.\n- (usvg) Fix chained `feImage` resolving.\n- CLI arguments processing.\n\n## [0.14.0] - 2021-03-06\n### Fixed\n- Multiple critical bugs in `tiny-skia`.\n\n## [0.13.1] - 2021-01-20\n### Fixed\n- `image` with float size scaling.\n- Critical bug in `tiny-skia`.\n\n## [0.13.0] - 2020-12-21\n### Added\n- `--resources-dir` option to CLI tools.\n- (usvg) `Tree::from_xmltree`\n\n### Changed\n- Remove the `Image` struct. `render()` and `render_node()` methods now accept `tiny_skia::PixmapMut`.\n- Update `fontdb`.\n- Update `tiny-skia`.\n- (c-api) `resvg_size` uses `double` instead of `uint32_t` now.\n- (qt-api) `defaultSize()` and `defaultSizeF()` methods now return SVG size\n  and not SVG viewbox size.\n- (usvg) `Options::path` changed to `Options::resources_dir` and requires a directory now.\n- (c-api) `resvg_options_set_file_path` changed to `resvg_options_set_resources_dir`\n  and requires a directory now.\n- (qt-api) `ResvgOptions::setFilePath` changed to `ResvgOptions::setResourcesDir`\n  and requires a directory now.\n\n### Fixed\n- Support multiple values inside a `text-decoration` attribute.\n\n### Removed\n- `Image`. Use `tiny_skia::PixmapMut` instead.\n- (c-api) `resvg_image` struct and `resvg_image_*` methods. `resvg` renders onto\n  the provided buffer now.\n- (c-api) `resvg_color`, because unused.\n\n## [0.12.0] - 2020-12-05\n### Changed\n- resvg no longer requires a C++ compiler!\n- `tiny-skia` was updated to a pure Rust version, which means that `resvg` no longer\n  depends on `clang` and should work on 32bit targets.\n- `rustybuzz` was updated to a pure Rust version.\n- `tools/explorer-thumbnailer` is back and written in Rust now.\n  Thanks to [gentoo90](https://github.com/gentoo90).\n\n### Fixed\n- (usvg) Do not panic when a font has a zero-sized underline thickness.\n- (usvg) Multiple `textPath` processing fixes by [chubei-oppen](https://github.com/chubei-oppen).\n- (qt-api) `boundsOnElement` and `boundingBox` were returning transposed bounds.\n\n## [0.11.0] - 2020-07-04\n### Highlights\n- All backends except Skia were removed. Skia is the only official one from now.\n- New C API implementation.\n\n### Added\n- Support for user-defined fonts in usvg, resvg and C API.\n- `--serif-family`, `--sans-serif-family`, `--cursive-family`, `--fantasy-family`\n  `--monospace-family`, `--use-font-file`, `--use-fonts-dir`, `--skip-system-fonts` and `--list-fonts`\n  options to all CLI tools.\n- New tests suite. Instead of testing against the previous build, now we're testing against\n  prerendered PNG images. Which is way faster.<br>\n  And you can test resvg without the internet connection now.<br>\n  And all you need is just `cargo test`.\n\n### Changed\n- Library uses an embedded Skia by default now.\n- Switch `harfbuzz_rs` with `rustybuzz`.\n- Rendering doesn't require `usvg::Options` now.\n- (usvg) The `fontdb` module moved into its own crate.\n- (usvg) `fontconfig` is no longer used for matching\n  [generic fonts](https://www.w3.org/TR/2018/REC-css-fonts-3-20180920/#generic-family-value)\n  on Linux. Mainly because it's very slow.\n- (usvg) When an `image` element contains a file path, the file will be loaded into memory now,\n  instead of simply storing a file path. And will be dumped as base64 on SVG save.\n  In case of an SVG image, it will be loaded as a `Tree` and saved as base64 encoded XML on save.\n- (usvg) `ImageData` replaced with `ImageKind`.\n- (usvg) Fonts database is empty by default now and should be filled manually.\n- (c-api) Almost a complete rewrite.\n\n### Removed\n- All backends except the Skia one.\n- `Options` from all backends. We don't use it anymore.\n- (usvg) `ImageFormat`.\n- (c-api) Rendering on a backends canvas no longer supported. Was constantly misused.\n\n## [0.10.0] - 2020-06-19\n\n### Changed\n- The `resvg` crate has been split into four: resvg-cairo, resvg-qt, resvg-skia and resvg-raqote.<br/>\n  So from now, instead of enabling a required backend via cargo features,\n  you should select a required backend-specific crate.<br/>\n  This allows us to have a better integration with a selected 2D library.<br/>\n  And we also have separated C API implementations now.<br/>\n  And each backend has its own vendored archive too.\n- (qt-backend) Use `QImage` instead of Rust libraries for raster images loading.\n\n### Removed\n- The `resvg` crate. Use backend-specific crates.\n- `tools/rendersvg`. Each backend has its own CLI tool now.\n- `tools/usvg`. `usvg` implements CLI by default now.\n- (c-api) `resvg_*_render_to_file` methods.\n- (qt-backend) `jpeg-decoder` and `png` dependencies.\n\n## [0.9.1] - 2020-06-03\n### Fixed\n- Stack overflow when `enable-background` and `filter` are set on the same element.\n- Grayscale PNG loading.\n- Allow building on BSD.\n- (usvg) Font fallback when shaping produces a different amount of glyphs.\n- (usvg) Ignore a space after the last character during `letter-spacing` processing.\n- (usvg) `marker-end` rendering when the last segment is a curve with the second control point\n  that coincides with end point.\n- (usvg) Accept embedded `image` data without mime.\n- (usvg) Fonts search in a home directory on Linux.\n- (usvg) `dy` calculation for `textPath` thanks to [Stoeoef](https://github.com/Stoeoef)\n- (usvg) `textPath` resolving when a referenced path has a transform.<br/>\n  Thanks to [Stoeoef](https://github.com/Stoeoef).\n- (usvg) Load user fonts on macOS too.\n- (xmlparser) Parsing comment before DTD.\n\n## [0.9.0] - 2020-01-18\n### Added\n- `feConvolveMatrix`, `feMorphology`, `feDisplacementMap`, `feTurbulence`,\n  `feDiffuseLighting` and `feSpecularLighting` support.\n- `BackgroundImage`, `BackgroundAlpha`, `FillPaint` and `StrokePaint` support as a filter input.\n- Load grayscale raster images.\n- `enable-background` support.\n- resvg/usvg can be built without text rendering support now.\n- `OutputImage::make_vec` and `OutputImage::make_rgba_vec`.\n- `feImage` with a reference to an internal element.\n\n### Changed\n- `feComposite` k1-4 coefficients can have any number now.\n  This matches browsers behaviour.\n- Use `flate2` instead of `libflate` for GZip decoding.\n- (usvg) `fill` and `stroke` attributes will always be set for `path` now.\n- (usvg) `g`, `path` and `image` can now be set inside `defs`. Required by `feImage`.\n- (c-api) Rename `resvg_*_render_to_image` into `resvg_*_render_to_file`.\n\n### Fixed\n- (usvg) Transform processing during text-to-path conversion.\n- `feComposite` with fully transparent region was producing an invalid result.\n- Fallback to `matrix` in `feColorMatrix` when `type` is not set or invalid.\n- ID preserving for `use` elements.\n- `feFlood` with subregion and `primitiveUnits=objectBoundingBox`.\n- (harfbuzz_rs) Memory leak.\n\n## [0.8.0] - 2019-08-17\n### Added\n- A [Skia](https://skia.org/) backend thanks to\n  [JaFenix](https://github.com/JaFenix).\n- `feComponentTransfer` support.\n- `feColorMatrix` support.\n- A better CSS support.\n- An `*.otf` fonts support.\n- (usvg) `dx`, `dy` are supported inside `textPath` now.\n- Use a box blur for `feGaussianBlur` with `stdDeviation`>=2.\n  This is 4-8 times faster than IIR blur.\n  Thanks to [Shnatsel](https://github.com/Shnatsel).\n\n### Changed\n- All backends are using Rust crates for raster images loading now.\n- Use `pico-args` instead of `gumdrop` to reduced the build time of `tools/rendersvg`\n  and `tools/usvg`.\n- (usvg) The `xmlwriter` is used for SVG generation now.\n  Almost 2x faster than generating an `svgdom`.\n- (usvg) Optimize font database initialization. Almost 50% faster.\n- Use a lower PNG compression ratio to speed up PNG generation.\n  Depending on a backend and image can be 2-4x faster.\n- `OutputImage::save` -> `OutputImage::save_png`.\n- (usvg) `Path::segments` -> `Path::data`.\n- Cairo backend compilation is 2x faster now due to overall changes.\n- Performance improvements (Oxygen Icon theme SVG-to-PNG):\n  - cairo-backend: 22% faster\n  - qt-backend: 20% faster\n  - raqote-backend: 34% faster\n\n### Fixed\n- (qt-api) A default font resolving.\n- (usvg) `baseline-shift` processing inside `textPath`.\n- (usvg) Remove all `tref` element children.\n- (usvg) `tref` with `xml:space` resolving.\n- (usvg) Ignore nested `tref`.\n- (usvg) Ignore invalid `clipPath` children that were referenced via `use`.\n- (usvg) `currentColor` will always fallback to black now.\n  Previously, `stroke` was set to `none` which is incorrect.\n- (usvg) `use` can reference an element inside a non-SVG element now.\n- (usvg) Collect all styles for generic fonts and not only *Regular*.\n- (usvg) Parse only presentation attributes from the `style` element and attribute.\n\n### Removed\n- (cairo-backend) `gdk-pixbuf` dependency.\n- (qt-backend) JPEG image format plugin dependency.\n- `svgdom` dependency.\n\n## [0.7.0] - 2019-06-19\n### Added\n- New text layout implementation:\n  - `textPath` support.\n  - `writing-mode` support, aka vertical text.\n  - [Text BIDI reordering](http://www.unicode.org/reports/tr9/).\n  - Better text shaping.\n  - `word-spacing` is supported for all backends now.\n  - [`harfbuzz`](https://github.com/harfbuzz/harfbuzz) dependency.\n  - Subscript, superscript offsets are extracted from font and not hardcoded now.\n- `shape-rendering`, `text-rendering` and `image-rendering` support.\n- The `arithmetic` operator for `feComposite`.\n- (usvg) `--quiet` argument.\n- (c-api) `resvg_get_image_bbox`.\n- (qt-api) `ResvgRenderer::boundingBox`.\n- (resvg) A [raqote](https://github.com/jrmuizel/raqote) backend thanks to\n  [jrmuizel](https://github.com/jrmuizel). Still experimental.\n\n### Changed\n- Text will be converted into paths on the `usvg` side now.\n- (resvg) Do not rescale images before rendering. This is faster and better.\n- (usvg) An `image` element with a zero or negative size will be skipped now.\n  Previously, a linked image size was used, which is incorrect.\n- Geometry primitives (`Rect`, `Size`, etc) are immutable and always valid now.\n- (usvg) The default `color-interpolation-filters` attribute will not be exported now.\n\n### Removed\n- (usvg) All text related structures and enums. Text will be converted into `Path` now.\n- `InitObject` and `init()` because they are no longer needed.\n- (c-api) `resvg_handle`, `resvg_init`, `resvg_destroy`.\n- (c-api) `resvg_cairo_get_node_bbox` and `resvg_qt_get_node_bbox`.\n  Use backend-independent `resvg_get_node_bbox` instead.\n- (cairo-backend) `pango` dependency.\n- (resvg) `Backend::calc_node_bbox`. Use `Node::calculate_bbox()` instead.\n\n### Fixed\n- `letter-spacing` on cursive scripts (like Arabic).\n- (rctree) Prevent stack overflow on a huge, deeply nested SVG.\n- (c-api) `resvg_is_image_empty` was always returning `false`.\n- (resvg) Panic when `filter` with `objectBoundingBox` was set on an empty group.\n- (usvg) `mask` with `objectBoundingBox` resolving.\n- (usvg) `pattern`'s `viewBox` attribute resolving via `href`.\n- (roxmltree) Namespace resolving.\n\n## [0.6.1] - 2019-03-16\n### Fixed\n- (usvg) `transform` multiplication.\n- (usvg) `use` inside `clipPath` resolving.\n\n## [0.6.0] - 2019-03-16\n### Added\n- Nested `baseline-shift` support.\n- (qt-api) `renderToImage`.\n- (usvg) A better algorithm for unused defs (`defs` element children, like gradients) removal.\n- (usvg) `Error::InvalidSize`.\n- (c-api) `RESVG_ERROR_INVALID_SIZE`.\n\n### Changed\n- (usvg) A major rewrite.\n- `baseline-shift` with `sub`, `super` and percent values calculation.\n- Marker resolving moved completely to `usvg`.\n- If an SVG doesn't have a valid size than an error will occur.\n  Previously, an empty tree was produced.\n- (qt-api) `render` methods are `const` now.\n- (usvg) Disable default attributes exporting.\n\n### Removed\n- (usvg) Marker element and attributes. Markers will be resolved just like `use` now.\n\n### Fixed\n- (resvg) During the `tspan` rendering, the `text` bbox will be used instead\n  of the `tspan` bbox itself. This is the correct behaviour by the SVG spec.\n- (cairo-backend) `font-family` parsing.\n- (usvg) `filter:none` processing.\n- (usvg) `text` inside `text` processing.\n- (usvg) Endless loop during `use` resolving.\n- (usvg) Endless loop when SVG has indirect recursive `xlink:href` links.\n- (usvg) Endless loop when SVG has recursive `marker-*` links.\n- (usvg) Panic during `use` resolving.\n- (usvg) Panic during inherited attributes resolving.\n- (usvg) Groups regrouping.\n- (usvg) `dx`/`dy` processing on `text`.\n- (usvg) `textAnchor` resolving.\n- (usvg) Ignore `fill-rule` on `text`.\n- (svgtypes) Style with comments parsing.\n- (roxmltree) Namespaces resolving.\n\n## [0.5.0] - 2019-01-04\n### Added\n- `marker` support.\n- Partial `baseline-shift` support.\n- `letter-spacing` support.\n- (qt-backend) `word-spacing` support.\n  Does not work on the cairo backend.\n- tools/explorer-thumbnailer\n- tools/kde-dolphin-thumbnailer\n\n### Fixed\n- Object bounding box calculation.\n- Pattern scaling.\n- Nested `objectBoundingBox` support.\n- (usvg) `color` on `use` resolving.\n- (usvg) `offset` attribute resolving inside the `stop` element.\n- (usvg) Ungrouping of groups with non-inheritable attributes.\n- (usvg) `rotate` attribute resolving.\n- (usvg) Paths without stroke and fill will no longer be removed.\n  Required for a proper bbox resolving.\n- (usvg) Coordinates resolving when units are `userSpaceOnUse`.\n- (usvg) Groups regrouping. Caused an incorrect rendering of `clipPath`\n  that had `filter` on a child.\n- (usvg) Style attributes resolving on the root `svg` element.\n- (usvg) `SmoothCurveTo` and `SmoothQuadratic` conversion.\n- (usvg) `symbol` resolving.\n- (cairo-backend) Font ascent calculation.\n- (qt-backend) Stroking of LineTo specified as CurveTo.\n- (svgdom) `stroke-miterlimit` attribute parsing.\n- (svgdom) `length` and `number` attribute types parsing.\n- (svgdom) `offset` attribute parsing.\n- (svgdom) IRI resolving order when SVG has duplicated ID's.\n\n## [0.4.0] - 2018-12-13\n### Added\n- (resvg) Initial filters support.\n- (resvg) Nested `clipPath` and `mask` support.\n- (resvg) MSVC support.\n- (rendersvg) `font-family`, `font-size` and `languages` to args.\n- (usvg) `systemLanguage` attribute support.\n- (usvg) Default font family and size is configurable now.\n- (c-api) `RESVG_ERROR_PARSING_FAILED`.\n- (c-api) `font_family`, `font_size` and `languages` to `resvg_options`.\n- (qt-api) `ResvgRenderer::setDevicePixelRatio`.\n\n### Changed\n- (rendersvg) Use `gumdrop` instead of `getopts`.\n- (c-api) Qt wrapper is header-only now.\n\n### Fixed\n- (cairo-backend) Text layout.\n- (cairo-backend) Rendering of a zero length subpath with a square cap.\n- (qt-backend) Transform retrieving via Qt bindings.\n- (resvg) Recursive SVG images via `image` tag.\n- (resvg) Bbox calculation of the text with rotate.\n- (resvg) Invisible elements processing.\n- (qt-api) SVG from QByteArray loading when data is invalid.\n- (usvg) `display` attribute processing.\n- (usvg) Recursive `mask` resolving.\n- (usvg) `inherit` attribute value resolving.\n- (svgdom) XML namespaces resolving.\n\n### Removed\n- (rendersvg) `failure` dependency.\n\n## [0.3.0] - 2018-05-23\n### Added\n- (c-api) `resvg_is_image_empty`.\n- (c-api) `resvg_error` enum.\n- (c-api) Qt wrapper.\n- (resvg) Advanced text layout support (lists of x, y, dx, dy and rotate).\n- (resvg) SVG support for `image` element.\n- (usvg) `symbol` element support.\n- (usvg) Nested `svg` elements support.\n- (usvg) Paint fallback resolving.\n- (usvg) Bbox validation for shapes that use painting servers.\n- (svgdom) Elements from ENTITY resolving.\n\n### Changed\n- (c-api) `resvg_parse_tree_from_file`, `resvg_parse_tree_from_data`\n  `resvg_cairo_render_to_image` and `resvg_qt_render_to_image`\n  will return an error code now.\n- (cairo-backend) Use `gdk-pixbuf` crate instead of `image`.\n- (resvg) `Render::render_to_image` and `Render::render_node_to_image` will return\n  `Option` and not `Result` now.\n- (resvg) New geometry primitives implementation.\n- (resvg) Rename `render_*` modules to `backend_`.\n- (rendersvg) Use `getopts` instead of `clap` to reduce the executable size.\n- (svgtypes) `StreamExt::parse_iri` and `StreamExt::parse_func_iri` will parse\n  not only well-formed data now.\n\n### Fixed\n- (qt-backend) Gradient with `objectBoundingBox` rendering.\n- (qt-backend) Text bounding box detection during the rendering.\n- (cairo-backend) `image` element clipping.\n- (cairo-backend) Layers management.\n- (c-api) `resvg_get_node_transform` will return a correct transform now.\n- (resvg) `text-decoration` thickness.\n- (resvg) `pattern` scaling.\n- (resvg) `image` without size rendering.\n- (usvg) Panic during `visibility` resolving.\n- (usvg) Gradients with one stop resolving.\n- (usvg) `use` attributes resolving.\n- (usvg) `clipPath` and `mask` attributes resolving.\n- (usvg) `offset` attribute in `stop` element resolving.\n- (usvg) Incorrect `font-size` attribute resolving.\n- (usvg) Gradient stops resolving.\n- (usvg) `switch` element resolving.\n- (svgdom) Mixed `xml:space` processing.\n- (svgtypes) `Paint::from_span` poor performance.\n\n### Removed\n- (c-api) `resvg_error_msg_destroy`.\n- (resvg) `parse_rtree_*` methods. Use `usvg::Tree::from_` instead.\n- (resvg) `Error`.\n\n## [0.2.0] - 2018-04-24\n### Added\n- (svg) Partial `clipPath` support.\n- (svg) Partial `mask` support.\n- (svg) Partial `pattern` support.\n- (svg) `preserveAspectRatio` support.\n- (svg) Check that an external image is PNG or JPEG.\n- (rendersvg) Added `--query-all` and `--export-id` arguments to render SVG items by ID.\n- (rendersvg) Added `--perf` argument for a simple performance stats.\n\n### Changed\n- (resvg) API is completely new.\n\n### Fixed\n- `font-size` attribute inheritance during `use` resolving.\n\n[Linebender]: https://github.com/linebender\n[@DJMcNab]: https://github.com/DJMcNab\n[@michabay05]: https://github.com/michabay05\n[@Shnatsel]: https://github.com/Shnatsel\n[@JosefKuchar]: https://github.com/JosefKuchar\n[@tovrstra]: https://github.com/tovrstra\n[@newinnovations]: https://github.com/newinnovations\n[@LaurenzV]: https://github.com/LaurenzV\n[@HaHa421]: https://github.com/HaHa421\n[@Dabble63]: https://github.com/Dabble63\n[@Daaiid]: https://github.com/Daaiid\n[@arnaud-secondlayer]: https://github.com/arnaud-secondlayer\n[@Its-Just-Nans]: https://github.com/Its-Just-Nans\n\n[#897]: https://github.com/linebender/resvg/pull/897\n\n[Unreleased]: https://github.com/linebender/resvg/compare/v0.47.0...HEAD\n[0.47.0]: https://github.com/linebender/resvg/compare/v0.46.0...v0.47.0\n[0.46.0]: https://github.com/linebender/resvg/compare/v0.45.1...v0.46.0\n[0.45.1]: https://github.com/linebender/resvg/compare/v0.45.0...v0.45.1\n[0.45.0]: https://github.com/linebender/resvg/compare/v0.44.0...v0.45.0\n[0.44.0]: https://github.com/linebender/resvg/compare/v0.43.0...v0.44.0\n[0.43.0]: https://github.com/linebender/resvg/compare/v0.42.0...v0.43.0\n[0.42.0]: https://github.com/linebender/resvg/compare/v0.41.0...v0.42.0\n[0.41.0]: https://github.com/linebender/resvg/compare/v0.40.0...v0.41.0\n[0.40.0]: https://github.com/linebender/resvg/compare/v0.39.0...v0.40.0\n[0.39.0]: https://github.com/linebender/resvg/compare/v0.38.0...v0.39.0\n[0.38.0]: https://github.com/linebender/resvg/compare/v0.37.0...v0.38.0\n[0.37.0]: https://github.com/linebender/resvg/compare/v0.36.0...v0.37.0\n[0.36.0]: https://github.com/linebender/resvg/compare/v0.35.0...v0.36.0\n[0.35.0]: https://github.com/linebender/resvg/compare/v0.34.1...v0.35.0\n[0.34.1]: https://github.com/linebender/resvg/compare/v0.34.0...v0.34.1\n[0.34.0]: https://github.com/linebender/resvg/compare/v0.33.0...v0.34.0\n[0.33.0]: https://github.com/linebender/resvg/compare/v0.32.0...v0.33.0\n[0.32.0]: https://github.com/linebender/resvg/compare/v0.31.1...v0.32.0\n[0.31.1]: https://github.com/linebender/resvg/compare/v0.31.0...v0.31.1\n[0.31.0]: https://github.com/linebender/resvg/compare/v0.30.0...v0.31.0\n[0.30.0]: https://github.com/linebender/resvg/compare/v0.29.0...v0.30.0\n[0.29.0]: https://github.com/linebender/resvg/compare/v0.28.0...v0.29.0\n[0.28.0]: https://github.com/linebender/resvg/compare/v0.27.0...v0.28.0\n[0.27.0]: https://github.com/linebender/resvg/compare/v0.26.1...v0.27.0\n[0.26.1]: https://github.com/linebender/resvg/compare/v0.26.0...v0.26.1\n[0.26.0]: https://github.com/linebender/resvg/compare/v0.25.0...v0.26.0\n[0.25.0]: https://github.com/linebender/resvg/compare/v0.24.0...v0.25.0\n[0.24.0]: https://github.com/linebender/resvg/compare/v0.23.0...v0.24.0\n[0.23.0]: https://github.com/linebender/resvg/compare/v0.22.0...v0.23.0\n[0.22.0]: https://github.com/linebender/resvg/compare/v0.21.0...v0.22.0\n[0.21.0]: https://github.com/linebender/resvg/compare/v0.20.0...v0.21.0\n[0.20.0]: https://github.com/linebender/resvg/compare/v0.19.0...v0.20.0\n[0.19.0]: https://github.com/linebender/resvg/compare/v0.18.0...v0.19.0\n[0.18.0]: https://github.com/linebender/resvg/compare/v0.17.0...v0.18.0\n[0.17.0]: https://github.com/linebender/resvg/compare/v0.16.0...v0.17.0\n[0.16.0]: https://github.com/linebender/resvg/compare/v0.15.0...v0.16.0\n[0.15.0]: https://github.com/linebender/resvg/compare/v0.14.1...v0.15.0\n[0.14.1]: https://github.com/linebender/resvg/compare/v0.14.0...v0.14.1\n[0.14.0]: https://github.com/linebender/resvg/compare/v0.13.1...v0.14.0\n[0.13.1]: https://github.com/linebender/resvg/compare/v0.13.0...v0.13.1\n[0.13.0]: https://github.com/linebender/resvg/compare/v0.12.0...v0.13.0\n[0.12.0]: https://github.com/linebender/resvg/compare/v0.11.0...v0.12.0\n[0.11.0]: https://github.com/linebender/resvg/compare/v0.10.0...v0.11.0\n[0.10.0]: https://github.com/linebender/resvg/compare/v0.9.1...v0.10.0\n[0.9.1]: https://github.com/linebender/resvg/compare/v0.9.0...v0.9.1\n[0.9.0]: https://github.com/linebender/resvg/compare/v0.8.0...v0.9.0\n[0.8.0]: https://github.com/linebender/resvg/compare/v0.7.0...v0.8.0\n[0.7.0]: https://github.com/linebender/resvg/compare/v0.6.1...v0.7.0\n[0.6.1]: https://github.com/linebender/resvg/compare/v0.6.0...v0.6.1\n[0.6.0]: https://github.com/linebender/resvg/compare/v0.5.0...v0.6.0\n[0.5.0]: https://github.com/linebender/resvg/compare/v0.4.0...v0.5.0\n[0.4.0]: https://github.com/linebender/resvg/compare/v0.3.0...v0.4.0\n[0.3.0]: https://github.com/linebender/resvg/compare/v0.2.0...v0.3.0\n[0.2.0]: https://github.com/linebender/resvg/compare/v0.1.0...v0.2.0\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[workspace]\nmembers = [\n    \"crates/c-api\",\n    \"crates/resvg\",\n    \"crates/usvg\",\n    \"crates/usvg/codegen\",\n    #\"tools/explorer-thumbnailer\",\n]\ndefault-members = [\"crates/resvg\"]\nresolver = \"2\"\n\n[workspace.package]\nlicense = \"Apache-2.0 OR MIT\"\n"
  },
  {
    "path": "LICENSE-APACHE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "LICENSE-MIT",
    "content": "Copyright 2017 the Resvg Authors\n\nPermission is hereby granted, free of charge, to any\nperson obtaining a copy of this software and associated\ndocumentation files (the \"Software\"), to deal in the\nSoftware without restriction, including without\nlimitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software\nis furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice\nshall be included in all copies or substantial portions\nof the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF\nANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED\nTO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\nPARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT\nSHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR\nIN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "## resvg\n![Build Status](https://github.com/linebender/resvg/workflows/Build/badge.svg)\n[![Crates.io](https://img.shields.io/crates/v/resvg.svg)](https://crates.io/crates/resvg)\n[![Documentation](https://docs.rs/resvg/badge.svg)](https://docs.rs/resvg)\n[![Rust 1.87.0+](https://img.shields.io/badge/rust-1.87.0+-orange.svg)](https://www.rust-lang.org)\n\n*resvg* is an [SVG](https://en.wikipedia.org/wiki/Scalable_Vector_Graphics) rendering library.\n\nIt can be used as a Rust library, as a C library, and as a CLI application to render static SVG files.\n\nThe core idea is to make a fast, small, portable SVG library with the goal to support the whole SVG spec.\n\n## Features\n\n### Designed for edge-cases\n\nSVG is a very complicated format with a large specification (SVG 1.1 is almost 900 pages).\nYou basically need a web browser to handle all of it. But the truth is that even browsers\nfail at this (see [SVG support](https://github.com/linebender/resvg#svg-support)).\nYes, unlike `resvg`, browsers do support dynamic SVG features like animations and scripting.\nBut using a browser to render SVG _correctly_ is sadly not an option.\n\nTo prove its correctness, `resvg` has a vast test suite that includes around 1600 tests.\nAnd those are only SVG-to-PNG regression tests. This doesn't include tests in `resvg` dependencies.\nAnd the best thing is that `resvg` test suite is available to everyone. It's not tied to `resvg`\nin any way. Which should help people who plan to develop their own SVG libraries.\n\n### Safety\n\nIt's hard not to mention safety when we talk about Rust and processing of a random input.\nAnd we're talking not only about SVG/XML, but also about CSS, TTF, PNG, JPEG, GIF, and GZIP.\n\nWhile `resvg` is not the only SVG library written in Rust, it's the only one that\nis written completely in Rust. There is no non-Rust code in the final binary.\n\nMoreover, there is almost no `unsafe` code either. Still, some dependencies have some `unsafe` code\nand font memory-mapping is inherently `unsafe`, but it's best you can get in terms of memory safety.\n\nHowever, this doesn't stop at memory safety. `resvg` has extensive checks to prevent endless loops (freezes)\nand stack overflows (via recursion).\n\n### Zero bloat\n\nRight now, the `resvg` CLI application is less than 3MB in size and doesn't require any external dependencies.\nThe binary contains nothing that isn't needed for rendering SVG files.\n\n### Portable\n\n`resvg` is guaranteed to work everywhere where you can compile the Rust itself,\nincluding WASM. There are some rough edges with obscure CPU architectures and\nmobile OSs (mainly system fonts loading), but it should be pretty painless otherwise.\n\n### SVG preprocessing\n\nAnother major difference from other SVG rendering libraries is that in `resvg`\nSVG parsing and rendering are two completely separate steps.\nThose steps are also split into two separate libraries: `resvg` and [usvg].\nMeaning you can easily write your own renderer on top of `usvg` using any 2D library of your liking.\n\n### Performance\n\nComparing performance between different SVG rendering libraries is like comparing apples and oranges.\nEveryone has a very different set of supported features, languages, build flags, etc...\nAnyhow, as `resvg` is written in Rust and uses [tiny-skia] for rendering - it's pretty fast.\nThere should also still be quite a lot of room for improvement.\n\n### Reproducibility\n\nSince `resvg` doesn't rely on any system libraries it allows us to have reproducible results\non all supported platforms. Meaning if you render an SVG file on x86 Windows and then render it\non ARM macOS - the produced image will be identical. Each pixel would have the same value.\n\n## Limitations\n\n- No animations<br>\n  There are no plans on implementing them either.\n- No native text rendering<br>\n  `resvg` doesn't rely on any system libraries, which implies that we cannot use native text rendering.\n  Nevertheless, native text rendering is optimized for small horizontal text, which is not\n  that common in SVG.\n- Unicode-only<br>\n  It's the 21st century. Text files that aren't UTF-8 encoded are no longer relevant.\n\n## SVG support\n\n`resvg` aims to only support the [static](http://www.w3.org/TR/SVG11/feature#SVG-static)\nSVG subset; i.e. no `a`, `script`, `view` or `cursor` elements, no events and no animations.\n\n[SVG 2](https://www.w3.org/TR/SVG2/) support is being worked on.\nYou can search for relevant issues with the\n[svg2 tag](https://github.com/linebender/resvg/issues?q=is%3Aissue+is%3Aopen+label%3Asvg2)\nor our [SVG 2 changelog](https://github.com/linebender/resvg/blob/main/docs/svg2-changelog.md).\n\n[SVG Tiny 1.2](https://www.w3.org/TR/SVGTiny12/) is not supported and support is also not planned.\n\nResults of the [resvg test suite](https://github.com/linebender/resvg-test-suite):\n\n![](./.github/chart.svg)\n\nSVG 2 only results:\n\n![](./.github/chart-svg2.svg)\n\nYou can find a complete table of supported features\n[here](https://linebender.org/resvg-test-suite/svg-support-table.html).\nIt also includes some alternative libraries.\n\nWe're not testing against all SVG libraries since many of them are pretty bad.\nSome libraries are not on the list because they don't pass the 25% mark.\nSuch libraries are: wxSvg, LunaSVG and nanosvg.\n\n## resvg project\n\nThere is a subtle difference between resvg as a _library_ and resvg as a _project_.\nWhile most users will interact only with the resvg library, it's just a tip of an iceberg.\nThere are a lot of libraries that I had to write to make resvg possible.\nHere are some of them:\n\n- resvg - the actual SVG renderer\n- [usvg] - an SVG preprocessor/simplifier\n- [tiny-skia] - a [Skia](https://github.com/google/skia) subset ported to Rust\n- [rustybuzz] - a [harfbuzz](https://github.com/harfbuzz/harfbuzz) subset ported to Rust\n- [ttf-parser] - a TrueType/OpenType font parser\n- [fontdb] - a simple, in-memory font database with CSS-like queries\n- [roxmltree] - an XML parsing library\n- [simplecss] - a pretty decent CSS 2 parser and selector\n- [pico-args] - an absolutely minimal, but surprisingly popular command-line arguments parser\n\nSo while the resvg _library_ is deceptively small (around 2500 LOC), the resvg _project_\nis nearing 75'000 LOC. Which is not that much considering how much resvg does.\nIt's definitely the smallest option out there.\n\n## License\n\nLicensed under either of\n\n- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or <http://www.apache.org/licenses/LICENSE-2.0>)\n- MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)\n\nat your option.\n\n## Contribution\n\nContributions are welcome by pull request.\nThe [Rust code of conduct] applies.\n\nUnless 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.\n\n[usvg]: https://github.com/linebender/resvg/tree/main/crates/usvg\n[rustybuzz]: https://github.com/harfbuzz/rustybuzz\n[tiny-skia]: https://github.com/linebender/tiny-skia\n[ttf-parser]: https://github.com/harfbuzz/ttf-parser\n[roxmltree]: https://github.com/RazrFalcon/roxmltree\n[simplecss]: https://github.com/linebender/simplecss\n[fontdb]: https://github.com/RazrFalcon/fontdb\n[pico-args]: https://github.com/RazrFalcon/pico-args\n[Rust Code of Conduct]: https://www.rust-lang.org/policies/code-of-conduct\n"
  },
  {
    "path": "crates/c-api/Cargo.toml",
    "content": "[package]\nname = \"resvg-capi\"\nversion = \"0.47.0\"\nkeywords = [\"svg\", \"render\", \"raster\", \"c-api\"]\nlicense.workspace = true\nedition = \"2024\"\nrust-version = \"1.87.0\"\nworkspace = \"../..\"\n\n[lib]\nname = \"resvg\"\npath = \"lib.rs\"\ncrate-type = [\"cdylib\", \"staticlib\"]\n\n[dependencies]\nlog = \"0.4\"\nresvg = { path = \"../resvg\", default-features = false }\n\n[features]\ndefault = [\"text\", \"system-fonts\", \"memmap-fonts\", \"raster-images\"]\n# enables SVG Text support\n# adds around 500KiB to your binary\ntext = [\"resvg/text\"]\n# enables system fonts loading (only for `text`)\nsystem-fonts = [\"resvg/system-fonts\"]\n# enables font files memmaping for faster loading (only for `text`)\nmemmap-fonts = [\"resvg/memmap-fonts\"]\nraster-images = [\"resvg/raster-images\"]\ncapi = []\n\n[package.metadata.capi.header]\ngeneration = false\n\n[package.metadata.capi.install.include]\nasset = [{ from=\"resvg.h\" }]\n"
  },
  {
    "path": "crates/c-api/LICENSE-APACHE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "crates/c-api/LICENSE-MIT",
    "content": "Copyright 2017 the Resvg Authors\n\nPermission is hereby granted, free of charge, to any\nperson obtaining a copy of this software and associated\ndocumentation files (the \"Software\"), to deal in the\nSoftware without restriction, including without\nlimitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software\nis furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice\nshall be included in all copies or substantial portions\nof the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF\nANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED\nTO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\nPARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT\nSHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR\nIN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "crates/c-api/README.md",
    "content": "# C API for resvg\n\n## Build\n\n```sh\ncargo build --release\n```\n\nThis will produce dynamic and static C libraries that can be found at `../target/release`.\n\n## Header generation\n\nThe `resvg.h` is generated via [cbindgen](https://github.com/eqrion/cbindgen)\nand then manually edited a bit.\n\n## License\n\nLicensed under either of\n\n- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or <http://www.apache.org/licenses/LICENSE-2.0>)\n- MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)\n\nat your option.\n\n## Contribution\n\nContributions are welcome by pull request.\nThe [Rust code of conduct] applies.\n\nUnless 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.\n\n[Rust Code of Conduct]: https://www.rust-lang.org/policies/code-of-conduct\n"
  },
  {
    "path": "crates/c-api/ResvgQt.h",
    "content": "// Copyright 2018 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\n/**\n * @file ResvgQt.h\n *\n * An idiomatic Qt API for resvg.\n */\n\n#ifndef RESVG_QT_H\n#define RESVG_QT_H\n\n#define RESVG_QT_MAJOR_VERSION 0\n#define RESVG_QT_MINOR_VERSION 47\n#define RESVG_QT_PATCH_VERSION 0\n#define RESVG_QT_VERSION \"0.47.0\"\n\n#include <cmath>\n\n#include <QDebug>\n#include <QFile>\n#include <QFileInfo>\n#include <QGuiApplication>\n#include <QImage>\n#include <QRectF>\n#include <QScopedPointer>\n#include <QScreen>\n#include <QString>\n#include <QTransform>\n\n#include <resvg.h>\n\nnamespace ResvgPrivate {\n\nclass Data\n{\npublic:\n    ~Data()\n    {\n        clear();\n    }\n\n    void reset()\n    {\n        clear();\n    }\n\n    resvg_render_tree *tree = nullptr;\n    QSizeF size;\n    QString errMsg;\n\nprivate:\n    void clear()\n    {\n        // No need to deallocate opt.font_family, because it is a constant.\n\n        if (tree) {\n            resvg_tree_destroy(tree);\n            tree = nullptr;\n        }\n\n        size = QSizeF();\n        errMsg = QString();\n    }\n};\n\nstatic QString errorToString(const int err)\n{\n    switch (err) {\n        case RESVG_OK :\n            return QString();\n        case RESVG_ERROR_NOT_AN_UTF8_STR :\n            return QLatin1String(\"The SVG content has not an UTF-8 encoding.\");\n        case RESVG_ERROR_FILE_OPEN_FAILED :\n            return QLatin1String(\"Failed to read the file.\");\n        case RESVG_ERROR_MALFORMED_GZIP :\n            return QLatin1String(\"Not a GZip compressed data.\");\n        case RESVG_ERROR_ELEMENTS_LIMIT_REACHED :\n            return QLatin1String(\"Too many elements.\");\n        case RESVG_ERROR_INVALID_SIZE :\n            return QLatin1String(\"SVG doesn't have a valid size.\");\n        case RESVG_ERROR_PARSING_FAILED :\n            return QLatin1String(\"Failed to parse an SVG data.\");\n    }\n\n    Q_UNREACHABLE();\n}\n\n} //ResvgPrivate\n\n/**\n * @brief SVG parsing options.\n */\nclass ResvgOptions {\npublic:\n    /**\n     * @brief Constructs a new options set.\n     */\n    ResvgOptions()\n        : d(resvg_options_create())\n    {\n        // Do not set the default font via QFont::family()\n        // because it will return a dummy one on Windows.\n        // See https://github.com/linebender/resvg/issues/159\n\n        setLanguages({ QLocale().bcp47Name() });\n    }\n\n    /**\n     * @brief Sets a directory that will be used during relative paths resolving.\n     *\n     * Expected to be the same as the directory that contains the SVG file,\n     * but can be set to any.\n     *\n     * Default: not set\n     */\n    void setResourcesDir(const QString &path)\n    {\n        Q_ASSERT(QFileInfo(path).isDir());\n        if (path.isEmpty()) {\n            resvg_options_set_resources_dir(d, nullptr);\n        } else {\n            auto pathC = path.toUtf8();\n            pathC.append('\\0');\n            resvg_options_set_resources_dir(d, pathC.constData());\n        }\n    }\n\n    /**\n     * @brief Sets the target DPI.\n     *\n     * Impact units conversion.\n     *\n     * Default: 96\n     */\n    void setDpi(const float dpi)\n    {\n        resvg_options_set_dpi(d, dpi);\n    }\n\n    /**\n     * @brief Sets the default font family.\n     *\n     * Will be used when no `font-family` attribute is set in the SVG.\n     *\n     * Default: Times New Roman\n     */\n    void setFontFamily(const QString &family)\n    {\n        if (family.isEmpty()) {\n            return;\n        }\n\n        auto familyC = family.toUtf8();\n        familyC.append('\\0');\n        resvg_options_set_font_family(d, familyC.constData());\n    }\n\n    /**\n     * @brief Sets the default font size.\n     *\n     * Will be used when no `font-size` attribute is set in the SVG.\n     *\n     * Default: 12\n     */\n    void setFontSize(const float size)\n    {\n        resvg_options_set_font_size(d, size);\n    }\n\n    /**\n     * @brief Sets a list of languages.\n     *\n     * Will be used to resolve a `systemLanguage` conditional attribute.\n     *\n     * Example: en, en-US.\n     *\n     * Default: en\n     */\n    void setLanguages(const QStringList &languages)\n    {\n        if (languages.isEmpty()) {\n            resvg_options_set_languages(d, nullptr);\n        } else {\n            auto languagesC = languages.join(',').toUtf8();\n            languagesC.append('\\0');\n            resvg_options_set_languages(d, languagesC.constData());\n        }\n    }\n\n    /**\n     * @brief Sets the default shape rendering method.\n     *\n     * Will be used when an SVG element's `shape-rendering` property is set to `auto`.\n     *\n     * Default: `RESVG_SHAPE_RENDERING_GEOMETRIC_PRECISION`\n     */\n    void setShapeRenderingMode(const resvg_shape_rendering mode)\n    {\n        resvg_options_set_shape_rendering_mode(d, mode);\n    }\n\n    /**\n     * @brief Sets the default text rendering method.\n     *\n     * Will be used when an SVG element's `text-rendering` property is set to `auto`.\n     *\n     * Default: `RESVG_TEXT_RENDERING_OPTIMIZE_LEGIBILITY`\n     */\n    void setTextRenderingMode(const resvg_text_rendering mode)\n    {\n        resvg_options_set_text_rendering_mode(d, mode);\n    }\n\n    /**\n     * @brief Sets the default image rendering method.\n     *\n     * Will be used when an SVG element's `image-rendering` property is set to `auto`.\n     *\n     * Default: `RESVG_IMAGE_RENDERING_OPTIMIZE_QUALITY`\n     */\n    void setImageRenderingMode(const resvg_image_rendering mode)\n    {\n        resvg_options_set_image_rendering_mode(d, mode);\n    }\n\n    /**\n     * @brief Loads a font data into the internal fonts database.\n     *\n     * Prints a warning into the log when the data is not a valid TrueType font.\n     */\n    void loadFontData(const QByteArray &data)\n    {\n        resvg_options_load_font_data(d, data.constData(), data.size());\n    }\n\n    /**\n     * @brief Loads a font file into the internal fonts database.\n     *\n     * Prints a warning into the log when the data is not a valid TrueType font.\n     */\n    bool loadFontFile(const QString &path)\n    {\n        auto pathC = path.toUtf8();\n        pathC.append('\\0');\n        return resvg_options_load_font_file(d, pathC.constData());\n    }\n\n    /**\n     * @brief Loads system fonts into the internal fonts database.\n     *\n     * This method is very IO intensive.\n     *\n     * This method should be executed only once per #resvg_options.\n     *\n     * The system scanning is not perfect, so some fonts may be omitted.\n     * Please send a bug report in this case.\n     *\n     * Prints warnings into the log.\n     */\n    void loadSystemFonts()\n    {\n        resvg_options_load_system_fonts(d);\n    }\n\n    /**\n     * @brief Destructs options.\n     */\n    ~ResvgOptions()\n    {\n        resvg_options_destroy(d);\n    }\n\n    friend class ResvgRenderer;\n\nprivate:\n    resvg_options * const d;\n};\n\n/**\n * @brief QSvgRenderer-like wrapper for resvg.\n */\nclass ResvgRenderer {\npublic:\n    /**\n     * @brief Constructs a new renderer.\n     */\n    ResvgRenderer()\n        : d(new ResvgPrivate::Data())\n    {\n    }\n\n    /**\n     * @brief Constructs a new renderer and loads the contents of the SVG(Z) file.\n     */\n    ResvgRenderer(const QString &filePath, const ResvgOptions &opt)\n        : d(new ResvgPrivate::Data())\n    {\n        load(filePath, opt);\n    }\n\n    /**\n     * @brief Constructs a new renderer and loads the SVG data.\n     */\n    ResvgRenderer(const QByteArray &data, const ResvgOptions &opt)\n        : d(new ResvgPrivate::Data())\n    {\n        load(data, opt);\n    }\n\n    /**\n     * @brief Loads the contents of the SVG(Z) file.\n     */\n    bool load(const QString &filePath, const ResvgOptions &opt)\n    {\n        // Check for Qt resource path.\n        if (filePath.startsWith(QLatin1String(\":/\"))) {\n            QFile file(filePath);\n            if (file.open(QFile::ReadOnly))\n                return load(file.readAll(), opt);\n            else\n                return false;\n        }\n\n        d->reset();\n\n        auto filePathC = filePath.toUtf8();\n        filePathC.append('\\0');\n\n        const auto err = resvg_parse_tree_from_file(filePathC.constData(), opt.d, &d->tree);\n        if (err != RESVG_OK) {\n            d->errMsg = ResvgPrivate::errorToString(err);\n            return false;\n        }\n\n        const auto s = resvg_get_image_size(d->tree);\n        d->size = QSizeF(s.width, s.height);\n\n        return true;\n    }\n\n    /**\n     * @brief Loads the SVG data.\n     */\n    bool load(const QByteArray &data, const ResvgOptions &opt)\n    {\n        d->reset();\n\n        const auto err = resvg_parse_tree_from_data(data.constData(), data.size(), opt.d, &d->tree);\n        if (err != RESVG_OK) {\n            d->errMsg = ResvgPrivate::errorToString(err);\n            return false;\n        }\n\n        const auto s = resvg_get_image_size(d->tree);\n        d->size = QSizeF(s.width, s.height);\n\n        return true;\n    }\n\n    /**\n     * @brief Returns \\b true if the file or data were loaded successful.\n     */\n    bool isValid() const\n    {\n        return d->tree;\n    }\n\n    /**\n     * @brief Returns an underling error when #isValid is \\b false.\n     */\n    QString errorString() const\n    {\n        return d->errMsg;\n    }\n\n    /**\n     * @brief Checks that underling tree has any nodes.\n     *\n     * #ResvgRenderer and #ResvgRenderer constructors\n     * will set an error only if a file does not exist or it has a non-UTF-8 encoding.\n     * All other errors will result in an empty tree with a 100x100px size.\n     *\n     * @return Returns \\b true if tree has no nodes.\n     */\n    bool isEmpty() const\n    {\n        if (d->tree)\n            return resvg_is_image_empty(d->tree);\n        else\n            return true;\n    }\n\n    /**\n     * @brief Returns an SVG size.\n     *\n     * The `width` and `height` attributes in SVG.\n     */\n    QSize defaultSize() const\n    {\n        return defaultSizeF().toSize();\n    }\n\n    /**\n     * @brief Returns an SVG size.\n     *\n     * The `width` and `height` attributes in SVG.\n     */\n    QSizeF defaultSizeF() const\n    {\n        if (d->tree)\n            return d->size.toSize();\n        else\n            return QSizeF();\n    }\n\n    /**\n     * @brief Returns an SVG viewbox.\n     *\n     * `resvg` flattens the `viewbox`, therefore this method returns\n     * the same value as \\b size.\n     */\n    QRect viewBox() const\n    {\n        return QRect(0, 0, d->size.width(), d->size.height());\n    }\n\n    /**\n     * @brief Returns an SVG viewbox.\n     *\n     * `resvg` flattens the `viewbox`, therefore this method returns\n     * the same value as \\b size.\n     */\n    QRectF viewBoxF() const\n    {\n        return QRectF(0, 0, d->size.width(), d->size.height());\n    }\n\n    /**\n     * @brief Returns bounding rectangle of the item with the given \\b id.\n     *        The transformation matrix of parent elements is not affecting\n     *        the bounds of the element.\n     */\n    QRectF boundsOnElement(const QString &id) const\n    {\n        if (!d->tree)\n            return QRectF();\n\n        const auto utf8Str = id.toUtf8();\n        const auto rawId = utf8Str.constData();\n        resvg_rect bbox;\n        if (resvg_get_node_bbox(d->tree, rawId, &bbox))\n            return QRectF(bbox.x, bbox.y, bbox.width, bbox.height);\n\n        return QRectF();\n    }\n\n    /**\n     * @brief Returns bounding rectangle of a whole image.\n     */\n    QRectF boundingBox() const\n    {\n        if (!d->tree)\n            return QRectF();\n\n        resvg_rect bbox;\n        if (resvg_get_object_bbox(d->tree, &bbox))\n            return QRectF(bbox.x, bbox.y, bbox.width, bbox.height);\n\n        return QRectF();\n    }\n\n    /**\n     * @brief Returns \\b true if element with such an ID exists.\n     */\n    bool elementExists(const QString &id) const\n    {\n        if (!d->tree)\n            return false;\n\n        const auto utf8Str = id.toUtf8();\n        const auto rawId = utf8Str.constData();\n        return resvg_node_exists(d->tree, rawId);\n    }\n\n    /**\n     * @brief Returns element's transform.\n     */\n    QTransform transformForElement(const QString &id) const\n    {\n        if (!d->tree)\n            return QTransform();\n\n        const auto utf8Str = id.toUtf8();\n        const auto rawId = utf8Str.constData();\n        resvg_transform ts;\n        if (resvg_get_node_transform(d->tree, rawId, &ts))\n            return QTransform(ts.a, ts.b, ts.c, ts.d, ts.e, ts.f);\n\n        return QTransform();\n    }\n\n    // TODO: render node\n\n    /**\n     * @brief Renders the SVG data to \\b QImage with a specified \\b size.\n     *\n     * If \\b size is not set, the \\b defaultSize() will be used.\n     */\n    QImage renderToImage(const QSize &size = QSize()) const\n    {\n        resvg_transform ts = resvg_transform_identity();\n        if (size.isValid()) {\n            // TODO: support height too.\n            auto sizef = defaultSizeF();\n            const auto newHeight = std::ceil(double(size.width()) * sizef.height() / sizef.width());\n            ts.a = double(size.width()) / sizef.width();\n            ts.d = newHeight / sizef.height();\n        }\n\n        auto svgSize = size;\n        if (svgSize.isEmpty())\n            svgSize = defaultSize();\n\n        QImage qImg(svgSize.width(), svgSize.height(), QImage::Format_ARGB32_Premultiplied);\n        qImg.fill(Qt::transparent);\n        resvg_render(d->tree, ts, qImg.width(), qImg.height(), (char*)qImg.bits());\n\n        // resvg renders onto the RGBA canvas, while QImage is ARGB.\n        // std::move is required to call inplace version of rgbSwapped().\n        return std::move(qImg).rgbSwapped();\n    }\n\n    /**\n     * @brief Initializes the library log.\n     *\n     * Use it if you want to see any warnings.\n     *\n     * Must be called only once.\n     *\n     * All warnings will be printed to the \\b stderr.\n     */\n    static void initLog()\n    {\n        resvg_init_log();\n    }\n\nprivate:\n    QScopedPointer<ResvgPrivate::Data> d;\n};\n\n#endif // RESVG_QT_H\n"
  },
  {
    "path": "crates/c-api/cbindgen.toml",
    "content": "language = \"C\"\ninclude_guard = \"RESVG_H\"\nbraces = \"SameLine\"\ntab_width = 4\ndocumentation_style = \"doxy\"\nheader = \"\"\"// Copyright 2021 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\n/**\n * @file resvg.h\n *\n * resvg C API\n */\"\"\"\ncpp_compat = true\nno_includes = true\nsys_includes = [\"stdbool.h\", \"stdint.h\"]\nstyle = \"type\"\n\n[fn]\nsort_by = \"None\"\n\n[enum]\nrename_variants = \"ScreamingSnakeCase\"\nprefix_with_name = true\n\n[export]\ninclude = [\n    \"resvg_error\",\n    \"resvg_shape_rendering\",\n    \"resvg_text_rendering\",\n    \"resvg_image_rendering\",\n]\n"
  },
  {
    "path": "crates/c-api/examples/cairo/Makefile",
    "content": "TARGET = example\nLIBS = -lm -L../../../../target/debug -lresvg `pkg-config --libs cairo`\nCC = gcc\nCFLAGS = -g -Wall `pkg-config --cflags cairo` -I../../\n\n.PHONY: default all clean\n\ndefault: $(TARGET)\nall: default\n\nOBJECTS = $(patsubst %.c, %.o, $(wildcard *.c))\n\n%.o: %.c $(CC) $(CFLAGS) -c $< -o $@\n\n.PRECIOUS: $(TARGET) $(OBJECTS)\n\n$(TARGET): $(OBJECTS)\n\t$(CC) $(OBJECTS) -Wall $(LIBS) -o $@\n\nclean:\n\t-rm -f *.o\n\t-rm -f $(TARGET)\n"
  },
  {
    "path": "crates/c-api/examples/cairo/README.md",
    "content": "A simple example that shows how to use *resvg* through C API to render on a Cairo context.\n\n## Run\n\n```bash\ncargo build --manifest-path ../../Cargo.toml\nmake\nLD_LIBRARY_PATH=../../../../target/debug ./example image.svg image.png\n```\n"
  },
  {
    "path": "crates/c-api/examples/cairo/example.c",
    "content": "// Copyright 2020 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <assert.h>\n#include <cairo.h>\n#include <resvg.h>\n\nint main(int argc, char **argv)\n{\n    if (argc != 3)\n    {\n        printf(\"Usage:\\n\\texample in.svg out.png\");\n        abort();\n    }\n\n    // Initialize resvg's library logging system\n    resvg_init_log();\n\n    resvg_options *opt = resvg_options_create();\n    resvg_options_load_system_fonts(opt);\n\n    // Optionally, you can add some CSS to control the SVG rendering.\n    resvg_options_set_stylesheet(opt, \"svg { fill: black; }\");\n\n    resvg_render_tree *tree;\n    // Construct a tree from the svg file and pass in some options\n    int err = resvg_parse_tree_from_file(argv[1], opt, &tree);\n    resvg_options_destroy(opt);\n    if (err != RESVG_OK)\n    {\n        printf(\"Error id: %i\\n\", err);\n        abort();\n    }\n\n    resvg_size size = resvg_get_image_size(tree);\n    int width = (int)size.width;\n    int height = (int)size.height;\n\n    // Using the dimension info, allocate enough pixels to account for the entire image\n    cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);\n\n    /* resvg doesn't support stride, so cairo_surface_t should have no padding */\n    assert(cairo_image_surface_get_stride(surface) == (int)size.width * 4);\n\n    unsigned char *surface_data = cairo_image_surface_get_data(surface);\n\n    resvg_render(tree, resvg_transform_identity(), width, height, (char*)surface_data);\n\n    /* RGBA -> BGRA */\n    for (int i = 0; i < width * height * 4; i += 4)\n    {\n        unsigned char r = surface_data[i + 0];\n        surface_data[i + 0] = surface_data[i + 2];\n        surface_data[i + 2] = r;\n    }\n\n    // Save image\n    cairo_surface_write_to_png(surface, argv[2]);\n\n    // De-initialize the allocated memory\n    cairo_surface_destroy(surface);\n    resvg_tree_destroy(tree);\n\n    return 0;\n}\n"
  },
  {
    "path": "crates/c-api/lib.rs",
    "content": "// Copyright 2020 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\n//! C bindings.\n\n#![allow(non_camel_case_types)]\n#![warn(missing_docs)]\n#![warn(missing_copy_implementations)]\n\nuse std::ffi::CStr;\nuse std::os::raw::c_char;\nuse std::slice;\n\nuse resvg::tiny_skia;\nuse resvg::usvg;\n\n/// @brief List of possible errors.\n#[repr(C)]\n#[derive(Copy, Clone)]\npub enum resvg_error {\n    /// Everything is ok.\n    OK = 0,\n    /// Only UTF-8 content are supported.\n    NOT_AN_UTF8_STR,\n    /// Failed to open the provided file.\n    FILE_OPEN_FAILED,\n    /// Compressed SVG must use the GZip algorithm.\n    MALFORMED_GZIP,\n    /// We do not allow SVG with more than 1_000_000 elements for security reasons.\n    ELEMENTS_LIMIT_REACHED,\n    /// SVG doesn't have a valid size.\n    ///\n    /// Occurs when width and/or height are <= 0.\n    ///\n    /// Also occurs if width, height and viewBox are not set.\n    INVALID_SIZE,\n    /// Failed to parse an SVG data.\n    PARSING_FAILED,\n}\n\n/// @brief A rectangle representation.\n#[repr(C)]\n#[allow(missing_docs)]\n#[derive(Copy, Clone)]\npub struct resvg_rect {\n    pub x: f32,\n    pub y: f32,\n    pub width: f32,\n    pub height: f32,\n}\n\n/// @brief A size representation.\n#[repr(C)]\n#[allow(missing_docs)]\n#[derive(Copy, Clone)]\npub struct resvg_size {\n    pub width: f32,\n    pub height: f32,\n}\n\n/// @brief A 2D transform representation.\n#[repr(C)]\n#[allow(missing_docs)]\n#[derive(Copy, Clone)]\npub struct resvg_transform {\n    pub a: f32,\n    pub b: f32,\n    pub c: f32,\n    pub d: f32,\n    pub e: f32,\n    pub f: f32,\n}\n\nimpl resvg_transform {\n    #[inline]\n    fn to_tiny_skia(&self) -> tiny_skia::Transform {\n        tiny_skia::Transform::from_row(self.a, self.b, self.c, self.d, self.e, self.f)\n    }\n}\n\n/// @brief Creates an identity transform.\n#[unsafe(no_mangle)]\npub extern \"C\" fn resvg_transform_identity() -> resvg_transform {\n    resvg_transform {\n        a: 1.0,\n        b: 0.0,\n        c: 0.0,\n        d: 1.0,\n        e: 0.0,\n        f: 0.0,\n    }\n}\n\n/// @brief Initializes the library log.\n///\n/// Use it if you want to see any warnings.\n///\n/// Must be called only once.\n///\n/// All warnings will be printed to the `stderr`.\n#[unsafe(no_mangle)]\npub extern \"C\" fn resvg_init_log() {\n    if let Ok(()) = log::set_logger(&LOGGER) {\n        log::set_max_level(log::LevelFilter::Warn);\n    }\n}\n\n/// @brief An SVG to #resvg_render_tree conversion options.\n///\n/// Also, contains a fonts database used during text to path conversion.\n/// The database is empty by default.\npub struct resvg_options {\n    options: usvg::Options<'static>,\n}\n\n/// @brief Creates a new #resvg_options object.\n///\n/// Should be destroyed via #resvg_options_destroy.\n#[unsafe(no_mangle)]\npub extern \"C\" fn resvg_options_create() -> *mut resvg_options {\n    Box::into_raw(Box::new(resvg_options {\n        options: usvg::Options::default(),\n    }))\n}\n\n#[inline]\nfn cast_opt(opt: *mut resvg_options) -> &'static mut usvg::Options<'static> {\n    unsafe {\n        assert!(!opt.is_null());\n        &mut (*opt).options\n    }\n}\n\n/// @brief Sets a directory that will be used during relative paths resolving.\n///\n/// Expected to be the same as the directory that contains the SVG file,\n/// but can be set to any.\n///\n/// Must be UTF-8. Can be set to NULL.\n///\n/// Default: NULL\n#[unsafe(no_mangle)]\npub extern \"C\" fn resvg_options_set_resources_dir(opt: *mut resvg_options, path: *const c_char) {\n    if path.is_null() {\n        cast_opt(opt).resources_dir = None;\n    } else {\n        cast_opt(opt).resources_dir = Some(cstr_to_str(path).unwrap().into());\n    }\n}\n\n/// @brief Sets the target DPI.\n///\n/// Impact units conversion.\n///\n/// Default: 96\n#[unsafe(no_mangle)]\npub extern \"C\" fn resvg_options_set_dpi(opt: *mut resvg_options, dpi: f32) {\n    cast_opt(opt).dpi = dpi;\n}\n\n/// @brief Provides the content of a stylesheet that will be used when resolving CSS attributes.\n///\n/// Must be UTF-8. Can be set to NULL.\n///\n/// Default: NULL\n#[unsafe(no_mangle)]\npub extern \"C\" fn resvg_options_set_stylesheet(opt: *mut resvg_options, content: *const c_char) {\n    if content.is_null() {\n        cast_opt(opt).style_sheet = None;\n    } else {\n        cast_opt(opt).style_sheet = Some(cstr_to_str(content).unwrap().into());\n    }\n}\n\n/// @brief Sets the default font family.\n///\n/// Will be used when no `font-family` attribute is set in the SVG.\n///\n/// Must be UTF-8. NULL is not allowed.\n///\n/// Default: Times New Roman\n#[unsafe(no_mangle)]\npub extern \"C\" fn resvg_options_set_font_family(opt: *mut resvg_options, family: *const c_char) {\n    cast_opt(opt).font_family = cstr_to_str(family).unwrap().to_string();\n}\n\n/// @brief Sets the default font size.\n///\n/// Will be used when no `font-size` attribute is set in the SVG.\n///\n/// Default: 12\n#[unsafe(no_mangle)]\npub extern \"C\" fn resvg_options_set_font_size(opt: *mut resvg_options, size: f32) {\n    cast_opt(opt).font_size = size;\n}\n\n/// @brief Sets the `serif` font family.\n///\n/// Must be UTF-8. NULL is not allowed.\n///\n/// Has no effect when the `text` feature is not enabled.\n///\n/// Default: Times New Roman\n#[unsafe(no_mangle)]\n#[allow(unused_variables)]\npub extern \"C\" fn resvg_options_set_serif_family(opt: *mut resvg_options, family: *const c_char) {\n    #[cfg(feature = \"text\")]\n    {\n        cast_opt(opt)\n            .fontdb_mut()\n            .set_serif_family(cstr_to_str(family).unwrap().to_string());\n    }\n}\n\n/// @brief Sets the `sans-serif` font family.\n///\n/// Must be UTF-8. NULL is not allowed.\n///\n/// Has no effect when the `text` feature is not enabled.\n///\n/// Default: Arial\n#[unsafe(no_mangle)]\n#[allow(unused_variables)]\npub extern \"C\" fn resvg_options_set_sans_serif_family(\n    opt: *mut resvg_options,\n    family: *const c_char,\n) {\n    #[cfg(feature = \"text\")]\n    {\n        cast_opt(opt)\n            .fontdb_mut()\n            .set_sans_serif_family(cstr_to_str(family).unwrap().to_string());\n    }\n}\n\n/// @brief Sets the `cursive` font family.\n///\n/// Must be UTF-8. NULL is not allowed.\n///\n/// Has no effect when the `text` feature is not enabled.\n///\n/// Default: Comic Sans MS\n#[unsafe(no_mangle)]\n#[allow(unused_variables)]\npub extern \"C\" fn resvg_options_set_cursive_family(opt: *mut resvg_options, family: *const c_char) {\n    #[cfg(feature = \"text\")]\n    {\n        cast_opt(opt)\n            .fontdb_mut()\n            .set_cursive_family(cstr_to_str(family).unwrap().to_string());\n    }\n}\n\n/// @brief Sets the `fantasy` font family.\n///\n/// Must be UTF-8. NULL is not allowed.\n///\n/// Has no effect when the `text` feature is not enabled.\n///\n/// Default: Papyrus on macOS, Impact on other OS'es\n#[unsafe(no_mangle)]\n#[allow(unused_variables)]\npub extern \"C\" fn resvg_options_set_fantasy_family(opt: *mut resvg_options, family: *const c_char) {\n    #[cfg(feature = \"text\")]\n    {\n        cast_opt(opt)\n            .fontdb_mut()\n            .set_fantasy_family(cstr_to_str(family).unwrap().to_string());\n    }\n}\n\n/// @brief Sets the `monospace` font family.\n///\n/// Must be UTF-8. NULL is not allowed.\n///\n/// Has no effect when the `text` feature is not enabled.\n///\n/// Default: Courier New\n#[unsafe(no_mangle)]\n#[allow(unused_variables)]\npub extern \"C\" fn resvg_options_set_monospace_family(\n    opt: *mut resvg_options,\n    family: *const c_char,\n) {\n    #[cfg(feature = \"text\")]\n    {\n        cast_opt(opt)\n            .fontdb_mut()\n            .set_monospace_family(cstr_to_str(family).unwrap().to_string());\n    }\n}\n\n/// @brief Sets a comma-separated list of languages.\n///\n/// Will be used to resolve a `systemLanguage` conditional attribute.\n///\n/// Example: en,en-US.\n///\n/// Must be UTF-8. Can be NULL.\n///\n/// Default: en\n#[unsafe(no_mangle)]\npub extern \"C\" fn resvg_options_set_languages(opt: *mut resvg_options, languages: *const c_char) {\n    if languages.is_null() {\n        cast_opt(opt).languages = Vec::new();\n        return;\n    }\n\n    let languages_str = match cstr_to_str(languages) {\n        Some(v) => v,\n        None => return,\n    };\n\n    let mut languages = Vec::new();\n    for lang in languages_str.split(',') {\n        languages.push(lang.trim().to_string());\n    }\n\n    cast_opt(opt).languages = languages;\n}\n\n/// @brief A shape rendering method.\n#[repr(C)]\n#[allow(missing_docs)]\n#[derive(Copy, Clone)]\npub enum resvg_shape_rendering {\n    OPTIMIZE_SPEED,\n    CRISP_EDGES,\n    GEOMETRIC_PRECISION,\n}\n\n/// @brief Sets the default shape rendering method.\n///\n/// Will be used when an SVG element's `shape-rendering` property is set to `auto`.\n///\n/// Default: `RESVG_SHAPE_RENDERING_GEOMETRIC_PRECISION`\n#[unsafe(no_mangle)]\npub extern \"C\" fn resvg_options_set_shape_rendering_mode(\n    opt: *mut resvg_options,\n    mode: resvg_shape_rendering,\n) {\n    cast_opt(opt).shape_rendering = match mode as i32 {\n        0 => usvg::ShapeRendering::OptimizeSpeed,\n        1 => usvg::ShapeRendering::CrispEdges,\n        2 => usvg::ShapeRendering::GeometricPrecision,\n        _ => return,\n    }\n}\n\n/// @brief A text rendering method.\n#[repr(C)]\n#[allow(missing_docs)]\n#[derive(Copy, Clone)]\npub enum resvg_text_rendering {\n    OPTIMIZE_SPEED,\n    OPTIMIZE_LEGIBILITY,\n    GEOMETRIC_PRECISION,\n}\n\n/// @brief Sets the default text rendering method.\n///\n/// Will be used when an SVG element's `text-rendering` property is set to `auto`.\n///\n/// Default: `RESVG_TEXT_RENDERING_OPTIMIZE_LEGIBILITY`\n#[unsafe(no_mangle)]\npub extern \"C\" fn resvg_options_set_text_rendering_mode(\n    opt: *mut resvg_options,\n    mode: resvg_text_rendering,\n) {\n    cast_opt(opt).text_rendering = match mode as i32 {\n        0 => usvg::TextRendering::OptimizeSpeed,\n        1 => usvg::TextRendering::OptimizeLegibility,\n        2 => usvg::TextRendering::GeometricPrecision,\n        _ => return,\n    }\n}\n\n/// @brief A image rendering method.\n#[repr(C)]\n#[allow(missing_docs)]\n#[derive(Copy, Clone)]\npub enum resvg_image_rendering {\n    OPTIMIZE_QUALITY,\n    OPTIMIZE_SPEED,\n}\n\n/// @brief Sets the default image rendering method.\n///\n/// Will be used when an SVG element's `image-rendering` property is set to `auto`.\n///\n/// Default: `RESVG_IMAGE_RENDERING_OPTIMIZE_QUALITY`\n#[unsafe(no_mangle)]\npub extern \"C\" fn resvg_options_set_image_rendering_mode(\n    opt: *mut resvg_options,\n    mode: resvg_image_rendering,\n) {\n    cast_opt(opt).image_rendering = match mode as i32 {\n        0 => usvg::ImageRendering::OptimizeQuality,\n        1 => usvg::ImageRendering::OptimizeSpeed,\n        _ => return,\n    }\n}\n\n/// @brief Loads a font data into the internal fonts database.\n///\n/// Prints a warning into the log when the data is not a valid TrueType font.\n///\n/// Has no effect when the `text` feature is not enabled.\n#[unsafe(no_mangle)]\n#[allow(unused_variables)]\npub extern \"C\" fn resvg_options_load_font_data(\n    opt: *mut resvg_options,\n    data: *const c_char,\n    len: usize,\n) {\n    #[cfg(feature = \"text\")]\n    {\n        let data = unsafe { slice::from_raw_parts(data as *const u8, len) };\n        cast_opt(opt).fontdb_mut().load_font_data(data.to_vec())\n    }\n}\n\n/// @brief Loads a font file into the internal fonts database.\n///\n/// Prints a warning into the log when the data is not a valid TrueType font.\n///\n/// Has no effect when the `text` feature is not enabled.\n///\n/// @return #resvg_error with RESVG_OK, RESVG_ERROR_NOT_AN_UTF8_STR or RESVG_ERROR_FILE_OPEN_FAILED\n#[unsafe(no_mangle)]\n#[allow(unused_variables)]\npub extern \"C\" fn resvg_options_load_font_file(\n    opt: *mut resvg_options,\n    file_path: *const c_char,\n) -> i32 {\n    #[cfg(feature = \"text\")]\n    {\n        let file_path = match cstr_to_str(file_path) {\n            Some(v) => v,\n            None => return resvg_error::NOT_AN_UTF8_STR as i32,\n        };\n\n        if cast_opt(opt).fontdb_mut().load_font_file(file_path).is_ok() {\n            resvg_error::OK as i32\n        } else {\n            resvg_error::FILE_OPEN_FAILED as i32\n        }\n    }\n\n    #[cfg(not(feature = \"text\"))]\n    {\n        resvg_error::OK as i32\n    }\n}\n\n/// @brief Loads system fonts into the internal fonts database.\n///\n/// This method is very IO intensive.\n///\n/// This method should be executed only once per #resvg_options.\n///\n/// The system scanning is not perfect, so some fonts may be omitted.\n/// Please send a bug report in this case.\n///\n/// Prints warnings into the log.\n///\n/// Has no effect when the `text` feature is not enabled.\n#[unsafe(no_mangle)]\n#[allow(unused_variables)]\npub extern \"C\" fn resvg_options_load_system_fonts(opt: *mut resvg_options) {\n    #[cfg(feature = \"text\")]\n    {\n        cast_opt(opt).fontdb_mut().load_system_fonts();\n    }\n}\n\n/// @brief Destroys the #resvg_options.\n#[unsafe(no_mangle)]\npub extern \"C\" fn resvg_options_destroy(opt: *mut resvg_options) {\n    unsafe {\n        assert!(!opt.is_null());\n        let _ = Box::from_raw(opt);\n    };\n}\n\n// TODO: use resvg::Tree\n/// @brief An opaque pointer to the rendering tree.\npub struct resvg_render_tree(pub usvg::Tree);\n\n/// @brief Creates #resvg_render_tree from file.\n///\n/// .svg and .svgz files are supported.\n///\n/// See #resvg_is_image_empty for details.\n///\n/// @param file_path UTF-8 file path.\n/// @param opt Rendering options. Must not be NULL.\n/// @param tree Parsed render tree. Should be destroyed via #resvg_tree_destroy.\n/// @return #resvg_error\n#[unsafe(no_mangle)]\npub extern \"C\" fn resvg_parse_tree_from_file(\n    file_path: *const c_char,\n    opt: *const resvg_options,\n    tree: *mut *mut resvg_render_tree,\n) -> i32 {\n    let file_path = match cstr_to_str(file_path) {\n        Some(v) => v,\n        None => return resvg_error::NOT_AN_UTF8_STR as i32,\n    };\n\n    let raw_opt = unsafe {\n        assert!(!opt.is_null());\n        &*opt\n    };\n\n    let file_data = match std::fs::read(file_path) {\n        Ok(tree) => tree,\n        Err(_) => return resvg_error::FILE_OPEN_FAILED as i32,\n    };\n\n    let utree = usvg::Tree::from_data(&file_data, &raw_opt.options);\n\n    let utree = match utree {\n        Ok(tree) => tree,\n        Err(e) => return convert_error(e) as i32,\n    };\n\n    let tree_box = Box::new(resvg_render_tree(utree));\n    unsafe {\n        *tree = Box::into_raw(tree_box);\n    }\n\n    resvg_error::OK as i32\n}\n\n/// @brief Creates #resvg_render_tree from data.\n///\n/// See #resvg_is_image_empty for details.\n///\n/// @param data SVG data. Can contain SVG string or gzip compressed data. Must not be NULL.\n/// @param len Data length.\n/// @param opt Rendering options. Must not be NULL.\n/// @param tree Parsed render tree. Should be destroyed via #resvg_tree_destroy.\n/// @return #resvg_error\n#[unsafe(no_mangle)]\npub extern \"C\" fn resvg_parse_tree_from_data(\n    data: *const c_char,\n    len: usize,\n    opt: *const resvg_options,\n    tree: *mut *mut resvg_render_tree,\n) -> i32 {\n    let data = unsafe { slice::from_raw_parts(data as *const u8, len) };\n\n    let raw_opt = unsafe {\n        assert!(!opt.is_null());\n        &*opt\n    };\n\n    let utree = usvg::Tree::from_data(data, &raw_opt.options);\n\n    let utree = match utree {\n        Ok(tree) => tree,\n        Err(e) => return convert_error(e) as i32,\n    };\n\n    let tree_box = Box::new(resvg_render_tree(utree));\n    unsafe {\n        *tree = Box::into_raw(tree_box);\n    }\n\n    resvg_error::OK as i32\n}\n\n/// @brief Checks that tree has any nodes.\n///\n/// @param tree Render tree.\n/// @return Returns `true` if tree has no nodes.\n#[unsafe(no_mangle)]\npub extern \"C\" fn resvg_is_image_empty(tree: *const resvg_render_tree) -> bool {\n    let tree = unsafe {\n        assert!(!tree.is_null());\n        &*tree\n    };\n\n    !tree.0.root().has_children()\n}\n\n/// @brief Returns an image size.\n///\n/// The size of an image that is required to render this SVG.\n///\n/// Note that elements outside the viewbox will be clipped. This is by design.\n/// If you want to render the whole SVG content, use #resvg_get_image_bbox instead.\n///\n/// @param tree Render tree.\n/// @return Image size.\n#[unsafe(no_mangle)]\npub extern \"C\" fn resvg_get_image_size(tree: *const resvg_render_tree) -> resvg_size {\n    let tree = unsafe {\n        assert!(!tree.is_null());\n        &*tree\n    };\n\n    let size = tree.0.size();\n\n    resvg_size {\n        width: size.width(),\n        height: size.height(),\n    }\n}\n\n/// @brief Returns an object bounding box.\n///\n/// This bounding box does not include objects stroke and filter regions.\n/// This is what SVG calls \"absolute object bonding box\".\n///\n/// If you're looking for a \"complete\" bounding box see #resvg_get_image_bbox\n///\n/// @param tree Render tree.\n/// @param bbox Image's object bounding box.\n/// @return `false` if an image has no elements.\n#[unsafe(no_mangle)]\npub extern \"C\" fn resvg_get_object_bbox(\n    tree: *const resvg_render_tree,\n    bbox: *mut resvg_rect,\n) -> bool {\n    let tree = unsafe {\n        assert!(!tree.is_null());\n        &*tree\n    };\n\n    if let Some(r) = tree.0.root().abs_bounding_box().to_non_zero_rect() {\n        unsafe {\n            *bbox = resvg_rect {\n                x: r.x(),\n                y: r.y(),\n                width: r.width(),\n                height: r.height(),\n            }\n        }\n\n        true\n    } else {\n        false\n    }\n}\n\n/// @brief Returns an image bounding box.\n///\n/// This bounding box contains the maximum SVG dimensions.\n/// It's size can be bigger or smaller than #resvg_get_image_size\n/// Use it when you want to avoid clipping of elements that are outside the SVG viewbox.\n///\n/// @param tree Render tree.\n/// @param bbox Image's bounding box.\n/// @return `false` if an image has no elements.\n#[unsafe(no_mangle)]\npub extern \"C\" fn resvg_get_image_bbox(\n    tree: *const resvg_render_tree,\n    bbox: *mut resvg_rect,\n) -> bool {\n    let tree = unsafe {\n        assert!(!tree.is_null());\n        &*tree\n    };\n\n    // `abs_layer_bounding_box` returns 0x0x1x1 for empty groups, so we need additional checks.\n    if tree.0.root().has_children() || !tree.0.root().filters().is_empty() {\n        let r = tree.0.root().abs_layer_bounding_box();\n        unsafe {\n            *bbox = resvg_rect {\n                x: r.x(),\n                y: r.y(),\n                width: r.width(),\n                height: r.height(),\n            }\n        }\n\n        true\n    } else {\n        false\n    }\n}\n\n/// @brief Returns `true` if a renderable node with such an ID exists.\n///\n/// @param tree Render tree.\n/// @param id Node's ID. UTF-8 string. Must not be NULL.\n/// @return `true` if a node exists.\n/// @return `false` if a node doesn't exist or ID isn't a UTF-8 string.\n/// @return `false` if a node exists, but not renderable.\n#[unsafe(no_mangle)]\npub extern \"C\" fn resvg_node_exists(tree: *const resvg_render_tree, id: *const c_char) -> bool {\n    let id = match cstr_to_str(id) {\n        Some(v) => v,\n        None => {\n            log::warn!(\"Provided ID is not a UTF-8 string.\");\n            return false;\n        }\n    };\n\n    let tree = unsafe {\n        assert!(!tree.is_null());\n        &*tree\n    };\n\n    tree.0.node_by_id(id).is_some()\n}\n\n/// @brief Returns node's transform by ID.\n///\n/// @param tree Render tree.\n/// @param id Node's ID. UTF-8 string. Must not be NULL.\n/// @param transform Node's transform.\n/// @return `true` if a node exists.\n/// @return `false` if a node doesn't exist or ID isn't a UTF-8 string.\n/// @return `false` if a node exists, but not renderable.\n#[unsafe(no_mangle)]\npub extern \"C\" fn resvg_get_node_transform(\n    tree: *const resvg_render_tree,\n    id: *const c_char,\n    transform: *mut resvg_transform,\n) -> bool {\n    let id = match cstr_to_str(id) {\n        Some(v) => v,\n        None => {\n            log::warn!(\"Provided ID is not a UTF-8 string.\");\n            return false;\n        }\n    };\n\n    let tree = unsafe {\n        assert!(!tree.is_null());\n        &*tree\n    };\n\n    if let Some(node) = tree.0.node_by_id(id) {\n        let abs_ts = node.abs_transform();\n\n        unsafe {\n            *transform = resvg_transform {\n                a: abs_ts.sx,\n                b: abs_ts.ky,\n                c: abs_ts.kx,\n                d: abs_ts.sy,\n                e: abs_ts.tx,\n                f: abs_ts.ty,\n            }\n        }\n\n        return true;\n    }\n\n    false\n}\n\n/// @brief Returns node's bounding box in canvas coordinates by ID.\n///\n/// @param tree Render tree.\n/// @param id Node's ID. Must not be NULL.\n/// @param bbox Node's bounding box.\n/// @return `false` if a node with such an ID does not exist\n/// @return `false` if ID isn't a UTF-8 string.\n/// @return `false` if ID is an empty string\n#[unsafe(no_mangle)]\npub extern \"C\" fn resvg_get_node_bbox(\n    tree: *const resvg_render_tree,\n    id: *const c_char,\n    bbox: *mut resvg_rect,\n) -> bool {\n    get_node_bbox(tree, id, bbox, &|node| node.abs_bounding_box())\n}\n\n/// @brief Returns node's bounding box, including stroke, in canvas coordinates by ID.\n///\n/// @param tree Render tree.\n/// @param id Node's ID. Must not be NULL.\n/// @param bbox Node's bounding box.\n/// @return `false` if a node with such an ID does not exist\n/// @return `false` if ID isn't a UTF-8 string.\n/// @return `false` if ID is an empty string\n#[unsafe(no_mangle)]\npub extern \"C\" fn resvg_get_node_stroke_bbox(\n    tree: *const resvg_render_tree,\n    id: *const c_char,\n    bbox: *mut resvg_rect,\n) -> bool {\n    get_node_bbox(tree, id, bbox, &|node| node.abs_stroke_bounding_box())\n}\n\nfn get_node_bbox(\n    tree: *const resvg_render_tree,\n    id: *const c_char,\n    bbox: *mut resvg_rect,\n    f: &dyn Fn(&usvg::Node) -> usvg::Rect,\n) -> bool {\n    let id = match cstr_to_str(id) {\n        Some(v) => v,\n        None => {\n            log::warn!(\"Provided ID is not a UTF-8 string.\");\n            return false;\n        }\n    };\n\n    if id.is_empty() {\n        log::warn!(\"Node ID must not be empty.\");\n        return false;\n    }\n\n    let tree = unsafe {\n        assert!(!tree.is_null());\n        &*tree\n    };\n\n    match tree.0.node_by_id(id) {\n        Some(node) => {\n            let r = f(node);\n            unsafe {\n                *bbox = resvg_rect {\n                    x: r.x(),\n                    y: r.y(),\n                    width: r.width(),\n                    height: r.height(),\n                }\n            }\n            true\n        }\n        None => {\n            log::warn!(\"No node with '{}' ID is in the tree.\", id);\n            false\n        }\n    }\n}\n\n/// @brief Destroys the #resvg_render_tree.\n#[unsafe(no_mangle)]\npub extern \"C\" fn resvg_tree_destroy(tree: *mut resvg_render_tree) {\n    unsafe {\n        assert!(!tree.is_null());\n        let _ = Box::from_raw(tree);\n    };\n}\n\nfn cstr_to_str(text: *const c_char) -> Option<&'static str> {\n    let text = unsafe {\n        assert!(!text.is_null());\n        CStr::from_ptr(text)\n    };\n\n    text.to_str().ok()\n}\n\nfn convert_error(e: usvg::Error) -> resvg_error {\n    match e {\n        usvg::Error::NotAnUtf8Str => resvg_error::NOT_AN_UTF8_STR,\n        usvg::Error::MalformedGZip => resvg_error::MALFORMED_GZIP,\n        usvg::Error::ElementsLimitReached => resvg_error::ELEMENTS_LIMIT_REACHED,\n        usvg::Error::InvalidSize => resvg_error::INVALID_SIZE,\n        usvg::Error::ParsingFailed(_) => resvg_error::PARSING_FAILED,\n    }\n}\n\n/// @brief Renders the #resvg_render_tree onto the pixmap.\n///\n/// @param tree A render tree.\n/// @param transform A root SVG transform. Can be used to position SVG inside the `pixmap`.\n/// @param width Pixmap width.\n/// @param height Pixmap height.\n/// @param pixmap Pixmap data. Should have width*height*4 size and contain\n///               premultiplied RGBA8888 pixels.\n#[unsafe(no_mangle)]\npub extern \"C\" fn resvg_render(\n    tree: *const resvg_render_tree,\n    transform: resvg_transform,\n    width: u32,\n    height: u32,\n    pixmap: *mut c_char,\n) {\n    let tree = unsafe {\n        assert!(!tree.is_null());\n        &*tree\n    };\n\n    let pixmap_len = width as usize * height as usize * tiny_skia::BYTES_PER_PIXEL;\n    let pixmap: &mut [u8] =\n        unsafe { std::slice::from_raw_parts_mut(pixmap as *mut u8, pixmap_len) };\n    let mut pixmap = tiny_skia::PixmapMut::from_bytes(pixmap, width, height).unwrap();\n\n    resvg::render(&tree.0, transform.to_tiny_skia(), &mut pixmap)\n}\n\n/// @brief Renders a Node by ID onto the image.\n///\n/// @param tree A render tree.\n/// @param id Node's ID. Must not be NULL.\n/// @param transform A root SVG transform. Can be used to position SVG inside the `pixmap`.\n/// @param width Pixmap width.\n/// @param height Pixmap height.\n/// @param pixmap Pixmap data. Should have width*height*4 size and contain\n///               premultiplied RGBA8888 pixels.\n/// @return `false` when `id` is not a non-empty UTF-8 string.\n/// @return `false` when the selected `id` is not present.\n/// @return `false` when an element has a zero bbox.\n#[unsafe(no_mangle)]\npub extern \"C\" fn resvg_render_node(\n    tree: *const resvg_render_tree,\n    id: *const c_char,\n    transform: resvg_transform,\n    width: u32,\n    height: u32,\n    pixmap: *mut c_char,\n) -> bool {\n    let tree = unsafe {\n        assert!(!tree.is_null());\n        &*tree\n    };\n\n    let id = match cstr_to_str(id) {\n        Some(v) => v,\n        None => return false,\n    };\n\n    if id.is_empty() {\n        log::warn!(\"Node with an empty ID cannot be rendered.\");\n        return false;\n    }\n\n    if let Some(node) = tree.0.node_by_id(id) {\n        let pixmap_len = width as usize * height as usize * tiny_skia::BYTES_PER_PIXEL;\n        let pixmap: &mut [u8] =\n            unsafe { std::slice::from_raw_parts_mut(pixmap as *mut u8, pixmap_len) };\n        let mut pixmap = tiny_skia::PixmapMut::from_bytes(pixmap, width, height).unwrap();\n\n        resvg::render_node(node, transform.to_tiny_skia(), &mut pixmap).is_some()\n    } else {\n        log::warn!(\"A node with '{}' ID wasn't found.\", id);\n        false\n    }\n}\n\n/// A simple stderr logger.\nstatic LOGGER: SimpleLogger = SimpleLogger;\nstruct SimpleLogger;\nimpl log::Log for SimpleLogger {\n    fn enabled(&self, metadata: &log::Metadata) -> bool {\n        metadata.level() <= log::LevelFilter::Warn\n    }\n\n    fn log(&self, record: &log::Record) {\n        if self.enabled(record.metadata()) {\n            let target = if record.target().len() > 0 {\n                record.target()\n            } else {\n                record.module_path().unwrap_or_default()\n            };\n\n            let line = record.line().unwrap_or(0);\n            let args = record.args();\n\n            match record.level() {\n                log::Level::Error => eprintln!(\"Error (in {}:{}): {}\", target, line, args),\n                log::Level::Warn => eprintln!(\"Warning (in {}:{}): {}\", target, line, args),\n                log::Level::Info => eprintln!(\"Info (in {}:{}): {}\", target, line, args),\n                log::Level::Debug => eprintln!(\"Debug (in {}:{}): {}\", target, line, args),\n                log::Level::Trace => eprintln!(\"Trace (in {}:{}): {}\", target, line, args),\n            }\n        }\n    }\n\n    fn flush(&self) {}\n}\n"
  },
  {
    "path": "crates/c-api/resvg.h",
    "content": "// Copyright 2021 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\n/**\n * @file resvg.h\n *\n * resvg C API\n */\n\n#ifndef RESVG_H\n#define RESVG_H\n\n#include <stdbool.h>\n#include <stdint.h>\n\n#define RESVG_MAJOR_VERSION 0\n#define RESVG_MINOR_VERSION 47\n#define RESVG_PATCH_VERSION 0\n#define RESVG_VERSION \"0.47.0\"\n\n/**\n * @brief List of possible errors.\n */\ntypedef enum {\n    /**\n     * Everything is ok.\n     */\n    RESVG_OK = 0,\n    /**\n     * Only UTF-8 content are supported.\n     */\n    RESVG_ERROR_NOT_AN_UTF8_STR,\n    /**\n     * Failed to open the provided file.\n     */\n    RESVG_ERROR_FILE_OPEN_FAILED,\n    /**\n     * Compressed SVG must use the GZip algorithm.\n     */\n    RESVG_ERROR_MALFORMED_GZIP,\n    /**\n     * We do not allow SVG with more than 1_000_000 elements for security reasons.\n     */\n    RESVG_ERROR_ELEMENTS_LIMIT_REACHED,\n    /**\n     * SVG doesn't have a valid size.\n     *\n     * Occurs when width and/or height are <= 0.\n     *\n     * Also occurs if width, height and viewBox are not set.\n     */\n    RESVG_ERROR_INVALID_SIZE,\n    /**\n     * Failed to parse an SVG data.\n     */\n    RESVG_ERROR_PARSING_FAILED,\n} resvg_error;\n\n/**\n * @brief A image rendering method.\n */\ntypedef enum {\n    RESVG_IMAGE_RENDERING_OPTIMIZE_QUALITY,\n    RESVG_IMAGE_RENDERING_OPTIMIZE_SPEED,\n} resvg_image_rendering;\n\n/**\n * @brief A shape rendering method.\n */\ntypedef enum {\n    RESVG_SHAPE_RENDERING_OPTIMIZE_SPEED,\n    RESVG_SHAPE_RENDERING_CRISP_EDGES,\n    RESVG_SHAPE_RENDERING_GEOMETRIC_PRECISION,\n} resvg_shape_rendering;\n\n/**\n * @brief A text rendering method.\n */\ntypedef enum {\n    RESVG_TEXT_RENDERING_OPTIMIZE_SPEED,\n    RESVG_TEXT_RENDERING_OPTIMIZE_LEGIBILITY,\n    RESVG_TEXT_RENDERING_GEOMETRIC_PRECISION,\n} resvg_text_rendering;\n\n/**\n * @brief An SVG to #resvg_render_tree conversion options.\n *\n * Also, contains a fonts database used during text to path conversion.\n * The database is empty by default.\n */\ntypedef struct resvg_options resvg_options;\n\n/**\n * @brief An opaque pointer to the rendering tree.\n */\ntypedef struct resvg_render_tree resvg_render_tree;\n\n/**\n * @brief A 2D transform representation.\n */\ntypedef struct {\n    float a;\n    float b;\n    float c;\n    float d;\n    float e;\n    float f;\n} resvg_transform;\n\n/**\n * @brief A size representation.\n */\ntypedef struct {\n    float width;\n    float height;\n} resvg_size;\n\n/**\n * @brief A rectangle representation.\n */\ntypedef struct {\n    float x;\n    float y;\n    float width;\n    float height;\n} resvg_rect;\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif // __cplusplus\n\n/**\n * @brief Creates an identity transform.\n */\nresvg_transform resvg_transform_identity(void);\n\n/**\n * @brief Initializes the library log.\n *\n * Use it if you want to see any warnings.\n *\n * Must be called only once.\n *\n * All warnings will be printed to the `stderr`.\n */\nvoid resvg_init_log(void);\n\n/**\n * @brief Creates a new #resvg_options object.\n *\n * Should be destroyed via #resvg_options_destroy.\n */\nresvg_options *resvg_options_create(void);\n\n/**\n * @brief Sets a directory that will be used during relative paths resolving.\n *\n * Expected to be the same as the directory that contains the SVG file,\n * but can be set to any.\n *\n * Must be UTF-8. Can be set to NULL.\n *\n * Default: NULL\n */\nvoid resvg_options_set_resources_dir(resvg_options *opt, const char *path);\n\n/**\n * @brief Sets the target DPI.\n *\n * Impact units conversion.\n *\n * Default: 96\n */\nvoid resvg_options_set_dpi(resvg_options *opt, float dpi);\n\n/**\n * @brief Provides the content of a stylesheet that will be used when resolving CSS attributes.\n *\n * Must be UTF-8. Can be set to NULL.\n *\n * Default: NULL\n */\nvoid resvg_options_set_stylesheet(resvg_options *opt, const char *content);\n\n/**\n * @brief Sets the default font family.\n *\n * Will be used when no `font-family` attribute is set in the SVG.\n *\n * Must be UTF-8. NULL is not allowed.\n *\n * Default: Times New Roman\n */\nvoid resvg_options_set_font_family(resvg_options *opt, const char *family);\n\n/**\n * @brief Sets the default font size.\n *\n * Will be used when no `font-size` attribute is set in the SVG.\n *\n * Default: 12\n */\nvoid resvg_options_set_font_size(resvg_options *opt, float size);\n\n/**\n * @brief Sets the `serif` font family.\n *\n * Must be UTF-8. NULL is not allowed.\n *\n * Has no effect when the `text` feature is not enabled.\n *\n * Default: Times New Roman\n */\nvoid resvg_options_set_serif_family(resvg_options *opt, const char *family);\n\n/**\n * @brief Sets the `sans-serif` font family.\n *\n * Must be UTF-8. NULL is not allowed.\n *\n * Has no effect when the `text` feature is not enabled.\n *\n * Default: Arial\n */\nvoid resvg_options_set_sans_serif_family(resvg_options *opt, const char *family);\n\n/**\n * @brief Sets the `cursive` font family.\n *\n * Must be UTF-8. NULL is not allowed.\n *\n * Has no effect when the `text` feature is not enabled.\n *\n * Default: Comic Sans MS\n */\nvoid resvg_options_set_cursive_family(resvg_options *opt, const char *family);\n\n/**\n * @brief Sets the `fantasy` font family.\n *\n * Must be UTF-8. NULL is not allowed.\n *\n * Has no effect when the `text` feature is not enabled.\n *\n * Default: Papyrus on macOS, Impact on other OS'es\n */\nvoid resvg_options_set_fantasy_family(resvg_options *opt, const char *family);\n\n/**\n * @brief Sets the `monospace` font family.\n *\n * Must be UTF-8. NULL is not allowed.\n *\n * Has no effect when the `text` feature is not enabled.\n *\n * Default: Courier New\n */\nvoid resvg_options_set_monospace_family(resvg_options *opt, const char *family);\n\n/**\n * @brief Sets a comma-separated list of languages.\n *\n * Will be used to resolve a `systemLanguage` conditional attribute.\n *\n * Example: en,en-US.\n *\n * Must be UTF-8. Can be NULL.\n *\n * Default: en\n */\nvoid resvg_options_set_languages(resvg_options *opt, const char *languages);\n\n/**\n * @brief Sets the default shape rendering method.\n *\n * Will be used when an SVG element's `shape-rendering` property is set to `auto`.\n *\n * Default: `RESVG_SHAPE_RENDERING_GEOMETRIC_PRECISION`\n */\nvoid resvg_options_set_shape_rendering_mode(resvg_options *opt, resvg_shape_rendering mode);\n\n/**\n * @brief Sets the default text rendering method.\n *\n * Will be used when an SVG element's `text-rendering` property is set to `auto`.\n *\n * Default: `RESVG_TEXT_RENDERING_OPTIMIZE_LEGIBILITY`\n */\nvoid resvg_options_set_text_rendering_mode(resvg_options *opt, resvg_text_rendering mode);\n\n/**\n * @brief Sets the default image rendering method.\n *\n * Will be used when an SVG element's `image-rendering` property is set to `auto`.\n *\n * Default: `RESVG_IMAGE_RENDERING_OPTIMIZE_QUALITY`\n */\nvoid resvg_options_set_image_rendering_mode(resvg_options *opt, resvg_image_rendering mode);\n\n/**\n * @brief Loads a font data into the internal fonts database.\n *\n * Prints a warning into the log when the data is not a valid TrueType font.\n *\n * Has no effect when the `text` feature is not enabled.\n */\nvoid resvg_options_load_font_data(resvg_options *opt, const char *data, uintptr_t len);\n\n/**\n * @brief Loads a font file into the internal fonts database.\n *\n * Prints a warning into the log when the data is not a valid TrueType font.\n *\n * Has no effect when the `text` feature is not enabled.\n *\n * @return #resvg_error with RESVG_OK, RESVG_ERROR_NOT_AN_UTF8_STR or RESVG_ERROR_FILE_OPEN_FAILED\n */\nint32_t resvg_options_load_font_file(resvg_options *opt, const char *file_path);\n\n/**\n * @brief Loads system fonts into the internal fonts database.\n *\n * This method is very IO intensive.\n *\n * This method should be executed only once per #resvg_options.\n *\n * The system scanning is not perfect, so some fonts may be omitted.\n * Please send a bug report in this case.\n *\n * Prints warnings into the log.\n *\n * Has no effect when the `text` feature is not enabled.\n */\nvoid resvg_options_load_system_fonts(resvg_options *opt);\n\n/**\n * @brief Destroys the #resvg_options.\n */\nvoid resvg_options_destroy(resvg_options *opt);\n\n/**\n * @brief Creates #resvg_render_tree from file.\n *\n * .svg and .svgz files are supported.\n *\n * See #resvg_is_image_empty for details.\n *\n * @param file_path UTF-8 file path.\n * @param opt Rendering options. Must not be NULL.\n * @param tree Parsed render tree. Should be destroyed via #resvg_tree_destroy.\n * @return #resvg_error\n */\nint32_t resvg_parse_tree_from_file(const char *file_path,\n                                   const resvg_options *opt,\n                                   resvg_render_tree **tree);\n\n/**\n * @brief Creates #resvg_render_tree from data.\n *\n * See #resvg_is_image_empty for details.\n *\n * @param data SVG data. Can contain SVG string or gzip compressed data. Must not be NULL.\n * @param len Data length.\n * @param opt Rendering options. Must not be NULL.\n * @param tree Parsed render tree. Should be destroyed via #resvg_tree_destroy.\n * @return #resvg_error\n */\nint32_t resvg_parse_tree_from_data(const char *data,\n                                   uintptr_t len,\n                                   const resvg_options *opt,\n                                   resvg_render_tree **tree);\n\n/**\n * @brief Checks that tree has any nodes.\n *\n * @param tree Render tree.\n * @return Returns `true` if tree has no nodes.\n */\nbool resvg_is_image_empty(const resvg_render_tree *tree);\n\n/**\n * @brief Returns an image size.\n *\n * The size of an image that is required to render this SVG.\n *\n * Note that elements outside the viewbox will be clipped. This is by design.\n * If you want to render the whole SVG content, use #resvg_get_image_bbox instead.\n *\n * @param tree Render tree.\n * @return Image size.\n */\nresvg_size resvg_get_image_size(const resvg_render_tree *tree);\n\n/**\n * @brief Returns an object bounding box.\n *\n * This bounding box does not include objects stroke and filter regions.\n * This is what SVG calls \"absolute object bonding box\".\n *\n * If you're looking for a \"complete\" bounding box see #resvg_get_image_bbox\n *\n * @param tree Render tree.\n * @param bbox Image's object bounding box.\n * @return `false` if an image has no elements.\n */\nbool resvg_get_object_bbox(const resvg_render_tree *tree, resvg_rect *bbox);\n\n/**\n * @brief Returns an image bounding box.\n *\n * This bounding box contains the maximum SVG dimensions.\n * It's size can be bigger or smaller than #resvg_get_image_size\n * Use it when you want to avoid clipping of elements that are outside the SVG viewbox.\n *\n * @param tree Render tree.\n * @param bbox Image's bounding box.\n * @return `false` if an image has no elements.\n */\nbool resvg_get_image_bbox(const resvg_render_tree *tree, resvg_rect *bbox);\n\n/**\n * @brief Returns `true` if a renderable node with such an ID exists.\n *\n * @param tree Render tree.\n * @param id Node's ID. UTF-8 string. Must not be NULL.\n * @return `true` if a node exists.\n * @return `false` if a node doesn't exist or ID isn't a UTF-8 string.\n * @return `false` if a node exists, but not renderable.\n */\nbool resvg_node_exists(const resvg_render_tree *tree, const char *id);\n\n/**\n * @brief Returns node's transform by ID.\n *\n * @param tree Render tree.\n * @param id Node's ID. UTF-8 string. Must not be NULL.\n * @param transform Node's transform.\n * @return `true` if a node exists.\n * @return `false` if a node doesn't exist or ID isn't a UTF-8 string.\n * @return `false` if a node exists, but not renderable.\n */\nbool resvg_get_node_transform(const resvg_render_tree *tree,\n                              const char *id,\n                              resvg_transform *transform);\n\n/**\n * @brief Returns node's bounding box in canvas coordinates by ID.\n *\n * @param tree Render tree.\n * @param id Node's ID. Must not be NULL.\n * @param bbox Node's bounding box.\n * @return `false` if a node with such an ID does not exist\n * @return `false` if ID isn't a UTF-8 string.\n * @return `false` if ID is an empty string\n */\nbool resvg_get_node_bbox(const resvg_render_tree *tree, const char *id, resvg_rect *bbox);\n\n/**\n * @brief Returns node's bounding box, including stroke, in canvas coordinates by ID.\n *\n * @param tree Render tree.\n * @param id Node's ID. Must not be NULL.\n * @param bbox Node's bounding box.\n * @return `false` if a node with such an ID does not exist\n * @return `false` if ID isn't a UTF-8 string.\n * @return `false` if ID is an empty string\n */\nbool resvg_get_node_stroke_bbox(const resvg_render_tree *tree, const char *id, resvg_rect *bbox);\n\n/**\n * @brief Destroys the #resvg_render_tree.\n */\nvoid resvg_tree_destroy(resvg_render_tree *tree);\n\n/**\n * @brief Renders the #resvg_render_tree onto the pixmap.\n *\n * @param tree A render tree.\n * @param transform A root SVG transform. Can be used to position SVG inside the `pixmap`.\n * @param width Pixmap width.\n * @param height Pixmap height.\n * @param pixmap Pixmap data. Should have width*height*4 size and contain\n *               premultiplied RGBA8888 pixels.\n */\nvoid resvg_render(const resvg_render_tree *tree,\n                  resvg_transform transform,\n                  uint32_t width,\n                  uint32_t height,\n                  char *pixmap);\n\n/**\n * @brief Renders a Node by ID onto the image.\n *\n * @param tree A render tree.\n * @param id Node's ID. Must not be NULL.\n * @param transform A root SVG transform. Can be used to position SVG inside the `pixmap`.\n * @param width Pixmap width.\n * @param height Pixmap height.\n * @param pixmap Pixmap data. Should have width*height*4 size and contain\n *               premultiplied RGBA8888 pixels.\n * @return `false` when `id` is not a non-empty UTF-8 string.\n * @return `false` when the selected `id` is not present.\n * @return `false` when an element has a zero bbox.\n */\nbool resvg_render_node(const resvg_render_tree *tree,\n                       const char *id,\n                       resvg_transform transform,\n                       uint32_t width,\n                       uint32_t height,\n                       char *pixmap);\n\n#ifdef __cplusplus\n} // extern \"C\"\n#endif // __cplusplus\n\n#endif /* RESVG_H */\n"
  },
  {
    "path": "crates/resvg/Cargo.toml",
    "content": "[package]\nname = \"resvg\"\nversion = \"0.47.0\"\nkeywords = [\"svg\", \"render\", \"raster\"]\nlicense.workspace = true\nedition = \"2024\"\nrust-version = \"1.87.0\"\ndescription = \"An SVG rendering library.\"\nrepository = \"https://github.com/linebender/resvg\"\nexclude = [\"tests\"]\nworkspace = \"../..\"\n\n[[bin]]\nname = \"resvg\"\nrequired-features = [\"text\", \"system-fonts\", \"memmap-fonts\"]\n\n[dependencies]\ngif = { version = \"0.14.1\", optional = true }\nimage-webp = { version = \"0.2.4\", optional = true }\nlog = \"0.4\"\npico-args = { version = \"0.5\", features = [\"eq-separator\"] }\nrgb = \"0.8\"\nsvgtypes = \"0.16.1\"\ntiny-skia = \"0.12.0\"\nusvg = { path = \"../usvg\", version = \"0.47.0\", default-features = false }\nzune-jpeg = { version = \"0.5.8\", optional = true }\n\n[dev-dependencies]\nonce_cell = \"1.21\"\npng = \"0.18.0\"\n\n[features]\ndefault = [\"text\", \"system-fonts\", \"memmap-fonts\", \"raster-images\"]\n# Enables SVG Text support.\n# Adds around 400KiB to your binary.\ntext = [\"usvg/text\"]\n# Enables system fonts loading (only for `text`).\nsystem-fonts = [\"usvg/system-fonts\"]\n# Enables font files memmaping for faster loading (only for `text`).\nmemmap-fonts = [\"usvg/memmap-fonts\"]\n# Enables decoding and rendering of raster images.\n# When disabled, `image` elements with SVG data will still be rendered.\n# Adds around 200KiB to your binary.\nraster-images = [\"gif\", \"image-webp\", \"dep:zune-jpeg\"]\n"
  },
  {
    "path": "crates/resvg/LICENSE-APACHE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "crates/resvg/LICENSE-MIT",
    "content": "Copyright 2017 the Resvg Authors\n\nPermission is hereby granted, free of charge, to any\nperson obtaining a copy of this software and associated\ndocumentation files (the \"Software\"), to deal in the\nSoftware without restriction, including without\nlimitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software\nis furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice\nshall be included in all copies or substantial portions\nof the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF\nANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED\nTO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\nPARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT\nSHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR\nIN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "crates/resvg/examples/custom_href_resolver.rs",
    "content": "// Copyright 2022 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nfn main() {\n    let mut opt = usvg::Options::default();\n\n    let ferris_image = std::sync::Arc::new(std::fs::read(\"./examples/ferris.png\").unwrap());\n\n    // We know that our SVG won't have DataUrl hrefs, just return None for such case.\n    let resolve_data = Box::new(|_: &str, _: std::sync::Arc<Vec<u8>>, _: &usvg::Options| None);\n\n    // Here we handle xlink:href attribute as string,\n    // let's use already loaded Ferris image to match that string.\n    let resolve_string = Box::new(move |href: &str, _: &usvg::Options| match href {\n        \"ferris_image\" => Some(usvg::ImageKind::PNG(ferris_image.clone())),\n        _ => None,\n    });\n\n    // Assign new ImageHrefResolver option using our closures.\n    opt.image_href_resolver = usvg::ImageHrefResolver {\n        resolve_data,\n        resolve_string,\n    };\n\n    let svg_data = std::fs::read(\"./examples/custom_href_resolver.svg\").unwrap();\n    let tree = usvg::Tree::from_data(&svg_data, &opt).unwrap();\n\n    let pixmap_size = tree.size().to_int_size();\n    let mut pixmap = tiny_skia::Pixmap::new(pixmap_size.width(), pixmap_size.height()).unwrap();\n\n    resvg::render(&tree, tiny_skia::Transform::default(), &mut pixmap.as_mut());\n\n    pixmap.save_png(\"custom_href_resolver.png\").unwrap();\n}\n"
  },
  {
    "path": "crates/resvg/examples/draw_bboxes.rs",
    "content": "// Copyright 2017 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nfn main() {\n    let args: Vec<String> = std::env::args().collect();\n    if !(args.len() == 3 || args.len() == 5) {\n        println!(\n            \"Usage:\\n\\\n             \\tdraw_bboxes <in-svg> <out-png>\\n\\\n             \\tdraw_bboxes <in-svg> <out-png> -z ZOOM\"\n        );\n        return;\n    }\n\n    let zoom = if args.len() == 5 {\n        args[4].parse::<f32>().expect(\"not a float\")\n    } else {\n        1.0\n    };\n\n    let mut opt = usvg::Options {\n        // Get file's absolute directory.\n        resources_dir: std::fs::canonicalize(&args[1])\n            .ok()\n            .and_then(|p| p.parent().map(|p| p.to_path_buf())),\n        ..usvg::Options::default()\n    };\n\n    opt.fontdb_mut().load_system_fonts();\n\n    let svg_data = std::fs::read(&args[1]).unwrap();\n    let tree = usvg::Tree::from_data(&svg_data, &opt).unwrap();\n\n    let mut bboxes = Vec::new();\n    let mut stroke_bboxes = Vec::new();\n    collect_bboxes(tree.root(), &mut bboxes, &mut stroke_bboxes);\n\n    let pixmap_size = tree.size().to_int_size().scale_by(zoom).unwrap();\n    let mut pixmap = tiny_skia::Pixmap::new(pixmap_size.width(), pixmap_size.height()).unwrap();\n    let render_ts = tiny_skia::Transform::from_scale(zoom, zoom);\n    resvg::render(&tree, render_ts, &mut pixmap.as_mut());\n\n    let stroke = tiny_skia::Stroke {\n        width: 1.0 / zoom, // prevent stroke scaling as well\n        ..tiny_skia::Stroke::default()\n    };\n\n    let mut paint1 = tiny_skia::Paint::default();\n    paint1.set_color_rgba8(255, 0, 0, 127);\n\n    let mut paint2 = tiny_skia::Paint::default();\n    paint2.set_color_rgba8(0, 200, 0, 127);\n\n    for bbox in bboxes {\n        let path = tiny_skia::PathBuilder::from_rect(bbox);\n        pixmap.stroke_path(&path, &paint1, &stroke, render_ts, None);\n    }\n\n    for bbox in stroke_bboxes {\n        let path = tiny_skia::PathBuilder::from_rect(bbox);\n        pixmap.stroke_path(&path, &paint2, &stroke, render_ts, None);\n    }\n\n    pixmap.save_png(&args[2]).unwrap();\n}\n\nfn collect_bboxes(\n    parent: &usvg::Group,\n    bboxes: &mut Vec<usvg::Rect>,\n    stroke_bboxes: &mut Vec<usvg::Rect>,\n) {\n    for node in parent.children() {\n        if let usvg::Node::Group(group) = node {\n            collect_bboxes(group, bboxes, stroke_bboxes);\n        }\n\n        let bbox = node.abs_bounding_box();\n        bboxes.push(bbox);\n\n        let stroke_bbox = node.abs_stroke_bounding_box();\n        if bbox != stroke_bbox {\n            stroke_bboxes.push(stroke_bbox);\n        }\n    }\n}\n"
  },
  {
    "path": "crates/resvg/examples/minimal.rs",
    "content": "// Copyright 2017 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nfn main() {\n    let args: Vec<String> = std::env::args().collect();\n    if args.len() != 3 {\n        println!(\"Usage:\\n\\tminimal <in-svg> <out-png>\");\n        return;\n    }\n\n    let tree = {\n        let mut opt = usvg::Options {\n            // Get file's absolute directory.\n            resources_dir: std::fs::canonicalize(&args[1])\n                .ok()\n                .and_then(|p| p.parent().map(|p| p.to_path_buf())),\n            ..usvg::Options::default()\n        };\n        opt.fontdb_mut().load_system_fonts();\n\n        let svg_data = std::fs::read(&args[1]).unwrap();\n        usvg::Tree::from_data(&svg_data, &opt).unwrap()\n    };\n\n    let pixmap_size = tree.size().to_int_size();\n    let mut pixmap = tiny_skia::Pixmap::new(pixmap_size.width(), pixmap_size.height()).unwrap();\n    resvg::render(&tree, tiny_skia::Transform::default(), &mut pixmap.as_mut());\n    pixmap.save_png(&args[2]).unwrap();\n}\n"
  },
  {
    "path": "crates/resvg/src/clip.rs",
    "content": "// Copyright 2019 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse crate::render::Context;\n\npub fn apply(\n    clip: &usvg::ClipPath,\n    transform: tiny_skia::Transform,\n    pixmap: &mut tiny_skia::Pixmap,\n) {\n    let mut clip_pixmap = tiny_skia::Pixmap::new(pixmap.width(), pixmap.height()).unwrap();\n    clip_pixmap.fill(tiny_skia::Color::BLACK);\n\n    draw_children(\n        clip.root(),\n        tiny_skia::BlendMode::Clear,\n        transform.pre_concat(clip.transform()),\n        &mut clip_pixmap.as_mut(),\n    );\n\n    if let Some(clip) = clip.clip_path() {\n        apply(clip, transform, pixmap);\n    }\n\n    let mut mask = tiny_skia::Mask::from_pixmap(clip_pixmap.as_ref(), tiny_skia::MaskType::Alpha);\n    mask.invert();\n    pixmap.apply_mask(&mask);\n}\n\nfn draw_children(\n    parent: &usvg::Group,\n    mode: tiny_skia::BlendMode,\n    transform: tiny_skia::Transform,\n    pixmap: &mut tiny_skia::PixmapMut,\n) {\n    for child in parent.children() {\n        match child {\n            usvg::Node::Path(path) => {\n                if !path.is_visible() {\n                    continue;\n                }\n\n                // We could use any values here. They will not be used anyway.\n                let ctx = Context {\n                    max_bbox: tiny_skia::IntRect::from_xywh(0, 0, 1, 1).unwrap(),\n                };\n\n                crate::path::fill_path(path, mode, &ctx, transform, pixmap);\n            }\n            usvg::Node::Text(text) => {\n                draw_children(text.flattened(), mode, transform, pixmap);\n            }\n            usvg::Node::Group(group) => {\n                let transform = transform.pre_concat(group.transform());\n\n                if let Some(clip) = group.clip_path() {\n                    // If a `clipPath` child also has a `clip-path`\n                    // then we should render this child on a new canvas,\n                    // clip it, and only then draw it to the `clipPath`.\n                    clip_group(group, clip, transform, pixmap);\n                } else {\n                    draw_children(group, mode, transform, pixmap);\n                }\n            }\n            _ => {}\n        }\n    }\n}\n\nfn clip_group(\n    children: &usvg::Group,\n    clip: &usvg::ClipPath,\n    transform: tiny_skia::Transform,\n    pixmap: &mut tiny_skia::PixmapMut,\n) -> Option<()> {\n    let mut clip_pixmap = tiny_skia::Pixmap::new(pixmap.width(), pixmap.height()).unwrap();\n\n    draw_children(\n        children,\n        tiny_skia::BlendMode::SourceOver,\n        transform,\n        &mut clip_pixmap.as_mut(),\n    );\n    apply(clip, transform, &mut clip_pixmap);\n\n    let mut paint = tiny_skia::PixmapPaint::default();\n    paint.blend_mode = tiny_skia::BlendMode::Xor;\n    pixmap.draw_pixmap(\n        0,\n        0,\n        clip_pixmap.as_ref(),\n        &paint,\n        tiny_skia::Transform::identity(),\n        None,\n    );\n\n    Some(())\n}\n"
  },
  {
    "path": "crates/resvg/src/filter/box_blur.rs",
    "content": "// Copyright 2020 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\n// Based on https://github.com/fschutt/fastblur\n\n#![allow(clippy::needless_range_loop)]\n\nuse super::ImageRefMut;\nuse rgb::RGBA8;\nuse std::cmp;\n\nconst STEPS: usize = 5;\n\n/// Applies a box blur.\n///\n/// Input image pixels should have a **premultiplied alpha**.\n///\n/// A negative or zero `sigma_x`/`sigma_y` will disable the blur along that axis.\n///\n/// # Allocations\n///\n/// This method will allocate a copy of the `src` image as a back buffer.\npub fn apply(sigma_x: f64, sigma_y: f64, mut src: ImageRefMut) {\n    let boxes_horz = create_box_gauss(sigma_x as f32);\n    let boxes_vert = create_box_gauss(sigma_y as f32);\n    let mut backbuf = src.data.to_vec();\n    let mut backbuf = ImageRefMut::new(src.width, src.height, &mut backbuf);\n\n    for (box_size_horz, box_size_vert) in boxes_horz.iter().zip(boxes_vert.iter()) {\n        let radius_horz = ((box_size_horz - 1) / 2) as usize;\n        let radius_vert = ((box_size_vert - 1) / 2) as usize;\n        box_blur_impl(radius_horz, radius_vert, &mut backbuf, &mut src);\n    }\n}\n\n#[inline(never)]\nfn create_box_gauss(sigma: f32) -> [i32; STEPS] {\n    if sigma > 0.0 {\n        let n_float = STEPS as f32;\n\n        // Ideal averaging filter width\n        let w_ideal = (12.0 * sigma * sigma / n_float).sqrt() + 1.0;\n        let mut wl = w_ideal.floor() as i32;\n        if wl % 2 == 0 {\n            wl -= 1;\n        }\n\n        let wu = wl + 2;\n\n        let wl_float = wl as f32;\n        let m_ideal = (12.0 * sigma * sigma\n            - n_float * wl_float * wl_float\n            - 4.0 * n_float * wl_float\n            - 3.0 * n_float)\n            / (-4.0 * wl_float - 4.0);\n        let m = m_ideal.round() as usize;\n\n        let mut sizes = [0; STEPS];\n        for i in 0..STEPS {\n            if i < m {\n                sizes[i] = wl;\n            } else {\n                sizes[i] = wu;\n            }\n        }\n\n        sizes\n    } else {\n        [1; STEPS]\n    }\n}\n\n#[inline]\nfn box_blur_impl(\n    blur_radius_horz: usize,\n    blur_radius_vert: usize,\n    backbuf: &mut ImageRefMut,\n    frontbuf: &mut ImageRefMut,\n) {\n    box_blur_vert(blur_radius_vert, frontbuf, backbuf);\n    box_blur_horz(blur_radius_horz, backbuf, frontbuf);\n}\n\n#[inline]\nfn box_blur_vert(blur_radius: usize, backbuf: &ImageRefMut, frontbuf: &mut ImageRefMut) {\n    if blur_radius == 0 {\n        frontbuf.data.copy_from_slice(backbuf.data);\n        return;\n    }\n\n    let width = backbuf.width as usize;\n    let height = backbuf.height as usize;\n\n    let iarr = 1.0 / (blur_radius + blur_radius + 1) as f32;\n    let blur_radius_prev = blur_radius as isize - height as isize;\n    let blur_radius_next = blur_radius as isize + 1;\n\n    for i in 0..width {\n        let col_start = i; //inclusive\n        let col_end = i + width * (height - 1); //inclusive\n        let mut ti = i;\n        let mut li = ti;\n        let mut ri = ti + blur_radius * width;\n\n        let fv = RGBA8::default();\n        let lv = RGBA8::default();\n\n        let mut val_r = blur_radius_next * (fv.r as isize);\n        let mut val_g = blur_radius_next * (fv.g as isize);\n        let mut val_b = blur_radius_next * (fv.b as isize);\n        let mut val_a = blur_radius_next * (fv.a as isize);\n\n        // Get the pixel at the specified index, or the first pixel of the column\n        // if the index is beyond the top edge of the image\n        let get_top = |i| {\n            if i < col_start { fv } else { backbuf.data[i] }\n        };\n\n        // Get the pixel at the specified index, or the last pixel of the column\n        // if the index is beyond the bottom edge of the image\n        let get_bottom = |i| {\n            if i > col_end { lv } else { backbuf.data[i] }\n        };\n\n        for j in 0..cmp::min(blur_radius, height) {\n            let bb = backbuf.data[ti + j * width];\n            val_r += bb.r as isize;\n            val_g += bb.g as isize;\n            val_b += bb.b as isize;\n            val_a += bb.a as isize;\n        }\n        if blur_radius > height {\n            val_r += blur_radius_prev * (lv.r as isize);\n            val_g += blur_radius_prev * (lv.g as isize);\n            val_b += blur_radius_prev * (lv.b as isize);\n            val_a += blur_radius_prev * (lv.a as isize);\n        }\n\n        for _ in 0..cmp::min(height, blur_radius + 1) {\n            let bb = get_bottom(ri);\n            ri += width;\n            val_r += sub(bb.r, fv.r);\n            val_g += sub(bb.g, fv.g);\n            val_b += sub(bb.b, fv.b);\n            val_a += sub(bb.a, fv.a);\n\n            frontbuf.data[ti] = RGBA8 {\n                r: round(val_r as f32 * iarr) as u8,\n                g: round(val_g as f32 * iarr) as u8,\n                b: round(val_b as f32 * iarr) as u8,\n                a: round(val_a as f32 * iarr) as u8,\n            };\n            ti += width;\n        }\n\n        if height <= blur_radius {\n            // otherwise `(height - blur_radius)` will underflow\n            continue;\n        }\n\n        for _ in (blur_radius + 1)..(height - blur_radius) {\n            let bb1 = backbuf.data[ri];\n            ri += width;\n            let bb2 = backbuf.data[li];\n            li += width;\n\n            val_r += sub(bb1.r, bb2.r);\n            val_g += sub(bb1.g, bb2.g);\n            val_b += sub(bb1.b, bb2.b);\n            val_a += sub(bb1.a, bb2.a);\n\n            frontbuf.data[ti] = RGBA8 {\n                r: round(val_r as f32 * iarr) as u8,\n                g: round(val_g as f32 * iarr) as u8,\n                b: round(val_b as f32 * iarr) as u8,\n                a: round(val_a as f32 * iarr) as u8,\n            };\n            ti += width;\n        }\n\n        for _ in 0..cmp::min(height - blur_radius - 1, blur_radius) {\n            let bb = get_top(li);\n            li += width;\n\n            val_r += sub(lv.r, bb.r);\n            val_g += sub(lv.g, bb.g);\n            val_b += sub(lv.b, bb.b);\n            val_a += sub(lv.a, bb.a);\n\n            frontbuf.data[ti] = RGBA8 {\n                r: round(val_r as f32 * iarr) as u8,\n                g: round(val_g as f32 * iarr) as u8,\n                b: round(val_b as f32 * iarr) as u8,\n                a: round(val_a as f32 * iarr) as u8,\n            };\n            ti += width;\n        }\n    }\n}\n\n#[inline]\nfn box_blur_horz(blur_radius: usize, backbuf: &ImageRefMut, frontbuf: &mut ImageRefMut) {\n    if blur_radius == 0 {\n        frontbuf.data.copy_from_slice(backbuf.data);\n        return;\n    }\n\n    let width = backbuf.width as usize;\n    let height = backbuf.height as usize;\n\n    let iarr = 1.0 / (blur_radius + blur_radius + 1) as f32;\n    let blur_radius_prev = blur_radius as isize - width as isize;\n    let blur_radius_next = blur_radius as isize + 1;\n\n    for i in 0..height {\n        let row_start = i * width; // inclusive\n        let row_end = (i + 1) * width - 1; // inclusive\n        let mut ti = i * width; // VERTICAL: $i;\n        let mut li = ti;\n        let mut ri = ti + blur_radius;\n\n        let fv = RGBA8::default();\n        let lv = RGBA8::default();\n\n        let mut val_r = blur_radius_next * (fv.r as isize);\n        let mut val_g = blur_radius_next * (fv.g as isize);\n        let mut val_b = blur_radius_next * (fv.b as isize);\n        let mut val_a = blur_radius_next * (fv.a as isize);\n\n        // Get the pixel at the specified index, or the first pixel of the row\n        // if the index is beyond the left edge of the image\n        let get_left = |i| {\n            if i < row_start { fv } else { backbuf.data[i] }\n        };\n\n        // Get the pixel at the specified index, or the last pixel of the row\n        // if the index is beyond the right edge of the image\n        let get_right = |i| {\n            if i > row_end { lv } else { backbuf.data[i] }\n        };\n\n        for j in 0..cmp::min(blur_radius, width) {\n            let bb = backbuf.data[ti + j]; // VERTICAL: ti + j * width\n            val_r += bb.r as isize;\n            val_g += bb.g as isize;\n            val_b += bb.b as isize;\n            val_a += bb.a as isize;\n        }\n        if blur_radius > width {\n            val_r += blur_radius_prev * (lv.r as isize);\n            val_g += blur_radius_prev * (lv.g as isize);\n            val_b += blur_radius_prev * (lv.b as isize);\n            val_a += blur_radius_prev * (lv.a as isize);\n        }\n\n        // Process the left side where we need pixels from beyond the left edge\n        for _ in 0..cmp::min(width, blur_radius + 1) {\n            let bb = get_right(ri);\n            ri += 1;\n            val_r += sub(bb.r, fv.r);\n            val_g += sub(bb.g, fv.g);\n            val_b += sub(bb.b, fv.b);\n            val_a += sub(bb.a, fv.a);\n\n            frontbuf.data[ti] = RGBA8 {\n                r: round(val_r as f32 * iarr) as u8,\n                g: round(val_g as f32 * iarr) as u8,\n                b: round(val_b as f32 * iarr) as u8,\n                a: round(val_a as f32 * iarr) as u8,\n            };\n            ti += 1; // VERTICAL : ti += width, same with the other areas\n        }\n\n        if width <= blur_radius {\n            // otherwise `(width - blur_radius)` will underflow\n            continue;\n        }\n\n        // Process the middle where we know we won't bump into borders\n        // without the extra indirection of get_left/get_right. This is faster.\n        for _ in (blur_radius + 1)..(width - blur_radius) {\n            let bb1 = backbuf.data[ri];\n            ri += 1;\n            let bb2 = backbuf.data[li];\n            li += 1;\n\n            val_r += sub(bb1.r, bb2.r);\n            val_g += sub(bb1.g, bb2.g);\n            val_b += sub(bb1.b, bb2.b);\n            val_a += sub(bb1.a, bb2.a);\n\n            frontbuf.data[ti] = RGBA8 {\n                r: round(val_r as f32 * iarr) as u8,\n                g: round(val_g as f32 * iarr) as u8,\n                b: round(val_b as f32 * iarr) as u8,\n                a: round(val_a as f32 * iarr) as u8,\n            };\n            ti += 1;\n        }\n\n        // Process the right side where we need pixels from beyond the right edge\n        for _ in 0..cmp::min(width - blur_radius - 1, blur_radius) {\n            let bb = get_left(li);\n            li += 1;\n\n            val_r += sub(lv.r, bb.r);\n            val_g += sub(lv.g, bb.g);\n            val_b += sub(lv.b, bb.b);\n            val_a += sub(lv.a, bb.a);\n\n            frontbuf.data[ti] = RGBA8 {\n                r: round(val_r as f32 * iarr) as u8,\n                g: round(val_g as f32 * iarr) as u8,\n                b: round(val_b as f32 * iarr) as u8,\n                a: round(val_a as f32 * iarr) as u8,\n            };\n            ti += 1;\n        }\n    }\n}\n\n/// Fast rounding for x <= 2^23.\n/// This is orders of magnitude faster than built-in rounding intrinsic.\n///\n/// Source: https://stackoverflow.com/a/42386149/585725\n#[inline]\nfn round(mut x: f32) -> f32 {\n    x += 12582912.0;\n    x -= 12582912.0;\n    x\n}\n\n#[inline]\nfn sub(c1: u8, c2: u8) -> isize {\n    c1 as isize - c2 as isize\n}\n"
  },
  {
    "path": "crates/resvg/src/filter/color_matrix.rs",
    "content": "// Copyright 2020 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse super::{ImageRefMut, f32_bound};\nuse rgb::RGBA8;\nuse usvg::filter::ColorMatrixKind as ColorMatrix;\n\n/// Applies a color matrix filter.\n///\n/// Input image pixels should have an **unpremultiplied alpha**.\npub fn apply(matrix: &ColorMatrix, src: ImageRefMut) {\n    match matrix {\n        ColorMatrix::Matrix(m) => {\n            for pixel in src.data {\n                let (r, g, b, a) = to_normalized_components(*pixel);\n\n                let new_r = r * m[0] + g * m[1] + b * m[2] + a * m[3] + m[4];\n                let new_g = r * m[5] + g * m[6] + b * m[7] + a * m[8] + m[9];\n                let new_b = r * m[10] + g * m[11] + b * m[12] + a * m[13] + m[14];\n                let new_a = r * m[15] + g * m[16] + b * m[17] + a * m[18] + m[19];\n\n                pixel.r = from_normalized(new_r);\n                pixel.g = from_normalized(new_g);\n                pixel.b = from_normalized(new_b);\n                pixel.a = from_normalized(new_a);\n            }\n        }\n        ColorMatrix::Saturate(v) => {\n            let v = v.get().max(0.0);\n            let m = [\n                0.213 + 0.787 * v,\n                0.715 - 0.715 * v,\n                0.072 - 0.072 * v,\n                0.213 - 0.213 * v,\n                0.715 + 0.285 * v,\n                0.072 - 0.072 * v,\n                0.213 - 0.213 * v,\n                0.715 - 0.715 * v,\n                0.072 + 0.928 * v,\n            ];\n\n            for pixel in src.data {\n                let (r, g, b, _) = to_normalized_components(*pixel);\n\n                let new_r = r * m[0] + g * m[1] + b * m[2];\n                let new_g = r * m[3] + g * m[4] + b * m[5];\n                let new_b = r * m[6] + g * m[7] + b * m[8];\n\n                pixel.r = from_normalized(new_r);\n                pixel.g = from_normalized(new_g);\n                pixel.b = from_normalized(new_b);\n            }\n        }\n        ColorMatrix::HueRotate(angle) => {\n            let angle = angle.to_radians();\n            let a1 = angle.cos();\n            let a2 = angle.sin();\n            let m = [\n                0.213 + 0.787 * a1 - 0.213 * a2,\n                0.715 - 0.715 * a1 - 0.715 * a2,\n                0.072 - 0.072 * a1 + 0.928 * a2,\n                0.213 - 0.213 * a1 + 0.143 * a2,\n                0.715 + 0.285 * a1 + 0.140 * a2,\n                0.072 - 0.072 * a1 - 0.283 * a2,\n                0.213 - 0.213 * a1 - 0.787 * a2,\n                0.715 - 0.715 * a1 + 0.715 * a2,\n                0.072 + 0.928 * a1 + 0.072 * a2,\n            ];\n\n            for pixel in src.data {\n                let (r, g, b, _) = to_normalized_components(*pixel);\n\n                let new_r = r * m[0] + g * m[1] + b * m[2];\n                let new_g = r * m[3] + g * m[4] + b * m[5];\n                let new_b = r * m[6] + g * m[7] + b * m[8];\n\n                pixel.r = from_normalized(new_r);\n                pixel.g = from_normalized(new_g);\n                pixel.b = from_normalized(new_b);\n            }\n        }\n        ColorMatrix::LuminanceToAlpha => {\n            for pixel in src.data {\n                let (r, g, b, _) = to_normalized_components(*pixel);\n\n                let new_a = r * 0.2125 + g * 0.7154 + b * 0.0721;\n\n                pixel.r = 0;\n                pixel.g = 0;\n                pixel.b = 0;\n                pixel.a = from_normalized(new_a);\n            }\n        }\n    }\n}\n\n#[inline]\nfn to_normalized_components(pixel: RGBA8) -> (f32, f32, f32, f32) {\n    (\n        pixel.r as f32 / 255.0,\n        pixel.g as f32 / 255.0,\n        pixel.b as f32 / 255.0,\n        pixel.a as f32 / 255.0,\n    )\n}\n\n#[inline]\nfn from_normalized(c: f32) -> u8 {\n    (f32_bound(0.0, c, 1.0) * 255.0) as u8\n}\n"
  },
  {
    "path": "crates/resvg/src/filter/component_transfer.rs",
    "content": "// Copyright 2020 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse super::{ImageRefMut, f32_bound};\nuse usvg::filter::{ComponentTransfer, TransferFunction};\n\n/// Applies component transfer functions for each `src` image channel.\n///\n/// Input image pixels should have an **unpremultiplied alpha**.\npub fn apply(fe: &ComponentTransfer, src: ImageRefMut) {\n    for pixel in src.data {\n        if !is_dummy(fe.func_r()) {\n            pixel.r = transfer(fe.func_r(), pixel.r);\n        }\n\n        if !is_dummy(fe.func_b()) {\n            pixel.b = transfer(fe.func_b(), pixel.b);\n        }\n\n        if !is_dummy(fe.func_g()) {\n            pixel.g = transfer(fe.func_g(), pixel.g);\n        }\n\n        if !is_dummy(fe.func_a()) {\n            pixel.a = transfer(fe.func_a(), pixel.a);\n        }\n    }\n}\n\nfn is_dummy(func: &TransferFunction) -> bool {\n    match func {\n        TransferFunction::Identity => true,\n        TransferFunction::Table(values) => values.is_empty(),\n        TransferFunction::Discrete(values) => values.is_empty(),\n        TransferFunction::Linear { .. } => false,\n        TransferFunction::Gamma { .. } => false,\n    }\n}\n\nfn transfer(func: &TransferFunction, c: u8) -> u8 {\n    let c = c as f32 / 255.0;\n    let c = match func {\n        TransferFunction::Identity => c,\n        TransferFunction::Table(values) => {\n            let n = values.len() - 1;\n            let k = (c * (n as f32)).floor() as usize;\n            let k = std::cmp::min(k, n);\n            if k == n {\n                values[k]\n            } else {\n                let vk = values[k];\n                let vk1 = values[k + 1];\n                let k = k as f32;\n                let n = n as f32;\n                vk + (c - k / n) * n * (vk1 - vk)\n            }\n        }\n        TransferFunction::Discrete(values) => {\n            let n = values.len();\n            let k = (c * (n as f32)).floor() as usize;\n            values[std::cmp::min(k, n - 1)]\n        }\n        TransferFunction::Linear { slope, intercept } => slope * c + intercept,\n        TransferFunction::Gamma {\n            amplitude,\n            exponent,\n            offset,\n        } => amplitude * c.powf(*exponent) + offset,\n    };\n\n    (f32_bound(0.0, c, 1.0) * 255.0) as u8\n}\n"
  },
  {
    "path": "crates/resvg/src/filter/composite.rs",
    "content": "// Copyright 2020 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse super::{ImageRef, ImageRefMut, f32_bound};\nuse rgb::RGBA8;\nuse usvg::ApproxZeroUlps;\n\n/// Performs an arithmetic composition.\n///\n/// - `src1` and `src2` image pixels should have a **premultiplied alpha**.\n/// - `dest` image pixels will have a **premultiplied alpha**.\n///\n/// # Panics\n///\n/// When `src1`, `src2` and `dest` have different sizes.\npub fn arithmetic(\n    k1: f32,\n    k2: f32,\n    k3: f32,\n    k4: f32,\n    src1: ImageRef,\n    src2: ImageRef,\n    dest: ImageRefMut,\n) {\n    assert!(src1.width == src2.width && src1.width == dest.width);\n    assert!(src1.height == src2.height && src1.height == dest.height);\n\n    let calc = |i1, i2, max| {\n        let i1 = i1 as f32 / 255.0;\n        let i2 = i2 as f32 / 255.0;\n        let result = k1 * i1 * i2 + k2 * i1 + k3 * i2 + k4;\n        f32_bound(0.0, result, max)\n    };\n\n    let mut i = 0;\n    for (c1, c2) in src1.data.iter().zip(src2.data.iter()) {\n        let a = calc(c1.a, c2.a, 1.0);\n        if a.approx_zero_ulps(4) {\n            i += 1;\n            continue;\n        }\n\n        let r = (calc(c1.r, c2.r, a) * 255.0) as u8;\n        let g = (calc(c1.g, c2.g, a) * 255.0) as u8;\n        let b = (calc(c1.b, c2.b, a) * 255.0) as u8;\n        let a = (a * 255.0) as u8;\n\n        dest.data[i] = RGBA8 { r, g, b, a };\n\n        i += 1;\n    }\n}\n"
  },
  {
    "path": "crates/resvg/src/filter/convolve_matrix.rs",
    "content": "// Copyright 2020 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse super::{ImageRefMut, f32_bound};\nuse rgb::RGBA8;\nuse usvg::filter::{ConvolveMatrix, EdgeMode};\n\n/// Applies a convolve matrix.\n///\n/// Input image pixels should have a **premultiplied alpha** when `preserve_alpha=false`.\n///\n/// # Allocations\n///\n/// This method will allocate a copy of the `src` image as a back buffer.\npub fn apply(matrix: &ConvolveMatrix, src: ImageRefMut) {\n    fn bound(min: i32, val: i32, max: i32) -> i32 {\n        core::cmp::max(min, core::cmp::min(max, val))\n    }\n\n    let width_max = src.width as i32 - 1;\n    let height_max = src.height as i32 - 1;\n\n    let mut buf = vec![RGBA8::default(); src.data.len()];\n    let mut buf = ImageRefMut::new(src.width, src.height, &mut buf);\n    let mut x = 0;\n    let mut y = 0;\n    for in_p in src.data.iter() {\n        let mut new_r = 0.0;\n        let mut new_g = 0.0;\n        let mut new_b = 0.0;\n        let mut new_a = 0.0;\n        for oy in 0..matrix.matrix().rows() {\n            for ox in 0..matrix.matrix().columns() {\n                let mut tx = x as i32 - matrix.matrix().target_x() as i32 + ox as i32;\n                let mut ty = y as i32 - matrix.matrix().target_y() as i32 + oy as i32;\n\n                match matrix.edge_mode() {\n                    EdgeMode::None => {\n                        if tx < 0 || tx > width_max || ty < 0 || ty > height_max {\n                            continue;\n                        }\n                    }\n                    EdgeMode::Duplicate => {\n                        tx = bound(0, tx, width_max);\n                        ty = bound(0, ty, height_max);\n                    }\n                    EdgeMode::Wrap => {\n                        while tx < 0 {\n                            tx += src.width as i32;\n                        }\n                        tx %= src.width as i32;\n\n                        while ty < 0 {\n                            ty += src.height as i32;\n                        }\n                        ty %= src.height as i32;\n                    }\n                }\n\n                let k = matrix.matrix().get(\n                    matrix.matrix().columns() - ox - 1,\n                    matrix.matrix().rows() - oy - 1,\n                );\n\n                let p = src.pixel_at(tx as u32, ty as u32);\n                new_r += (p.r as f32) / 255.0 * k;\n                new_g += (p.g as f32) / 255.0 * k;\n                new_b += (p.b as f32) / 255.0 * k;\n\n                if !matrix.preserve_alpha() {\n                    new_a += (p.a as f32) / 255.0 * k;\n                }\n            }\n        }\n\n        if matrix.preserve_alpha() {\n            new_a = in_p.a as f32 / 255.0;\n        } else {\n            new_a = new_a / matrix.divisor().get() + matrix.bias();\n        }\n\n        let bounded_new_a = f32_bound(0.0, new_a, 1.0);\n\n        let calc = |x| {\n            let x = x / matrix.divisor().get() + matrix.bias() * new_a;\n\n            let x = if matrix.preserve_alpha() {\n                f32_bound(0.0, x, 1.0) * bounded_new_a\n            } else {\n                f32_bound(0.0, x, bounded_new_a)\n            };\n\n            (x * 255.0 + 0.5) as u8\n        };\n\n        let out_p = buf.pixel_at_mut(x, y);\n        out_p.r = calc(new_r);\n        out_p.g = calc(new_g);\n        out_p.b = calc(new_b);\n        out_p.a = (bounded_new_a * 255.0 + 0.5) as u8;\n\n        x += 1;\n        if x == src.width {\n            x = 0;\n            y += 1;\n        }\n    }\n\n    // Do not use `mem::swap` because `data` referenced via FFI.\n    src.data.copy_from_slice(buf.data);\n}\n"
  },
  {
    "path": "crates/resvg/src/filter/displacement_map.rs",
    "content": "// Copyright 2020 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse super::{ImageRef, ImageRefMut};\nuse usvg::filter::{ColorChannel, DisplacementMap};\n\n/// Applies a displacement map.\n///\n/// - `map` pixels should have a **unpremultiplied alpha**.\n/// - `src` pixels can have any alpha method.\n///\n/// `sx` and `sy` indicate canvas scale.\n///\n/// # Panics\n///\n/// When `src`, `map` and `dest` have different sizes.\npub fn apply(\n    fe: &DisplacementMap,\n    sx: f32,\n    sy: f32,\n    src: ImageRef,\n    map: ImageRef,\n    dest: ImageRefMut,\n) {\n    assert!(src.width == map.width && src.width == dest.width);\n    assert!(src.height == map.height && src.height == dest.height);\n\n    let w = src.width as i32;\n    let h = src.height as i32;\n\n    let mut x: u32 = 0;\n    let mut y: u32 = 0;\n    for pixel in map.data.iter() {\n        let calc_offset = |channel| {\n            let c = match channel {\n                ColorChannel::B => pixel.b,\n                ColorChannel::G => pixel.g,\n                ColorChannel::R => pixel.r,\n                ColorChannel::A => pixel.a,\n            };\n\n            c as f32 / 255.0 - 0.5\n        };\n\n        let dx = calc_offset(fe.x_channel_selector());\n        let dy = calc_offset(fe.y_channel_selector());\n        let ox = (x as f32 + dx * sx * fe.scale()).round() as i32;\n        let oy = (y as f32 + dy * sy * fe.scale()).round() as i32;\n\n        // TODO: we should use some kind of anti-aliasing when offset is on a pixel border\n\n        if x < w as u32 && y < h as u32 && ox >= 0 && ox < w && oy >= 0 && oy < h {\n            let idx = (oy * w + ox) as usize;\n            let idx1 = (y * w as u32 + x) as usize;\n            dest.data[idx1] = src.data[idx];\n        }\n\n        x += 1;\n        if x == src.width {\n            x = 0;\n            y += 1;\n        }\n    }\n}\n"
  },
  {
    "path": "crates/resvg/src/filter/iir_blur.rs",
    "content": "// Copyright 2020 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\n// An IIR blur.\n//\n// Based on http://www.getreuer.info/home/gaussianiir\n//\n// Licensed under 'Simplified BSD License'.\n//\n//\n// Implements the fast Gaussian convolution algorithm of Alvarez and Mazorra,\n// where the Gaussian is approximated by a cascade of first-order infinite\n// impulsive response (IIR) filters.  Boundaries are handled with half-sample\n// symmetric extension.\n//\n// Gaussian convolution is approached as approximating the heat equation and\n// each timestep is performed with an efficient recursive computation.  Using\n// more steps yields a more accurate approximation of the Gaussian.  A\n// reasonable default value for `numsteps` is 4.\n//\n// Reference:\n// Alvarez, Mazorra, \"Signal and Image Restoration using Shock Filters and\n// Anisotropic Diffusion,\" SIAM J. on Numerical Analysis, vol. 31, no. 2,\n// pp. 590-605, 1994.\n\n// TODO: Blurs right and bottom sides twice for some reason.\n\nuse super::ImageRefMut;\nuse rgb::ComponentSlice;\n\nstruct BlurData {\n    width: usize,\n    height: usize,\n    sigma_x: f64,\n    sigma_y: f64,\n    steps: usize,\n}\n\n/// Applies an IIR blur.\n///\n/// Input image pixels should have a **premultiplied alpha**.\n///\n/// A negative or zero `sigma_x`/`sigma_y` will disable the blur along that axis.\n///\n/// # Allocations\n///\n/// This method will allocate a 2x `src` buffer.\npub fn apply(sigma_x: f64, sigma_y: f64, src: ImageRefMut) {\n    let buf_size = (src.width * src.height) as usize;\n    let mut buf = vec![0.0; buf_size];\n    let buf = &mut buf;\n\n    let d = BlurData {\n        width: src.width as usize,\n        height: src.height as usize,\n        sigma_x,\n        sigma_y,\n        steps: 4,\n    };\n\n    let data = src.data.as_mut_slice();\n    gaussian_channel(data, &d, 0, buf);\n    gaussian_channel(data, &d, 1, buf);\n    gaussian_channel(data, &d, 2, buf);\n    gaussian_channel(data, &d, 3, buf);\n}\n\nfn gaussian_channel(data: &mut [u8], d: &BlurData, channel: usize, buf: &mut [f64]) {\n    for i in 0..data.len() / 4 {\n        buf[i] = data[i * 4 + channel] as f64 / 255.0;\n    }\n\n    gaussianiir2d(d, buf);\n\n    for i in 0..data.len() / 4 {\n        data[i * 4 + channel] = (buf[i] * 255.0) as u8;\n    }\n}\n\nfn gaussianiir2d(d: &BlurData, buf: &mut [f64]) {\n    // Filter horizontally along each row.\n    let (lambda_x, dnu_x) = if d.sigma_x > 0.0 {\n        let (lambda, dnu) = gen_coefficients(d.sigma_x, d.steps);\n\n        for y in 0..d.height {\n            for _ in 0..d.steps {\n                let idx = d.width * y;\n\n                // Filter rightwards.\n                for x in 1..d.width {\n                    buf[idx + x] += dnu * buf[idx + x - 1];\n                }\n\n                let mut x = d.width - 1;\n\n                // Filter leftwards.\n                while x > 0 {\n                    buf[idx + x - 1] += dnu * buf[idx + x];\n                    x -= 1;\n                }\n            }\n        }\n\n        (lambda, dnu)\n    } else {\n        (1.0, 1.0)\n    };\n\n    // Filter vertically along each column.\n    let (lambda_y, dnu_y) = if d.sigma_y > 0.0 {\n        let (lambda, dnu) = gen_coefficients(d.sigma_y, d.steps);\n        for x in 0..d.width {\n            for _ in 0..d.steps {\n                let idx = x;\n\n                // Filter downwards.\n                let mut y = d.width;\n                while y < buf.len() {\n                    buf[idx + y] += dnu * buf[idx + y - d.width];\n                    y += d.width;\n                }\n\n                y = buf.len() - d.width;\n\n                // Filter upwards.\n                while y > 0 {\n                    buf[idx + y - d.width] += dnu * buf[idx + y];\n                    y -= d.width;\n                }\n            }\n        }\n\n        (lambda, dnu)\n    } else {\n        (1.0, 1.0)\n    };\n\n    let post_scale =\n        ((dnu_x * dnu_y).sqrt() / (lambda_x * lambda_y).sqrt()).powi(2 * d.steps as i32);\n    buf.iter_mut().for_each(|v| *v *= post_scale);\n}\n\nfn gen_coefficients(sigma: f64, steps: usize) -> (f64, f64) {\n    let lambda = (sigma * sigma) / (2.0 * steps as f64);\n    let dnu = (1.0 + 2.0 * lambda - (1.0 + 4.0 * lambda).sqrt()) / (2.0 * lambda);\n    (lambda, dnu)\n}\n"
  },
  {
    "path": "crates/resvg/src/filter/lighting.rs",
    "content": "// Copyright 2020 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse super::{ImageRef, ImageRefMut, f32_bound};\nuse rgb::RGBA8;\nuse usvg::filter::{DiffuseLighting, LightSource, SpecularLighting};\nuse usvg::{ApproxEqUlps, ApproxZeroUlps, Color};\n\nconst FACTOR_1_2: f32 = 1.0 / 2.0;\nconst FACTOR_1_3: f32 = 1.0 / 3.0;\nconst FACTOR_1_4: f32 = 1.0 / 4.0;\nconst FACTOR_2_3: f32 = 2.0 / 3.0;\n\n#[derive(Clone, Copy, Debug)]\nstruct Vector2 {\n    x: f32,\n    y: f32,\n}\n\nimpl Vector2 {\n    #[inline]\n    fn new(x: f32, y: f32) -> Self {\n        Vector2 { x, y }\n    }\n\n    #[inline]\n    fn approx_zero(&self) -> bool {\n        self.x.approx_zero_ulps(4) && self.y.approx_zero_ulps(4)\n    }\n}\n\nimpl core::ops::Mul<f32> for Vector2 {\n    type Output = Self;\n\n    #[inline]\n    fn mul(self, c: f32) -> Self::Output {\n        Vector2 {\n            x: self.x * c,\n            y: self.y * c,\n        }\n    }\n}\n\n#[derive(Clone, Copy, Debug)]\nstruct Vector3 {\n    x: f32,\n    y: f32,\n    z: f32,\n}\n\nimpl Vector3 {\n    #[inline]\n    fn new(x: f32, y: f32, z: f32) -> Self {\n        Vector3 { x, y, z }\n    }\n\n    #[inline]\n    fn dot(&self, other: &Self) -> f32 {\n        self.x * other.x + self.y * other.y + self.z * other.z\n    }\n\n    #[inline]\n    fn length(&self) -> f32 {\n        (self.x * self.x + self.y * self.y + self.z * self.z).sqrt()\n    }\n\n    #[inline]\n    fn normalized(&self) -> Option<Self> {\n        let length = self.length();\n        if !length.approx_zero_ulps(4) {\n            Some(Vector3 {\n                x: self.x / length,\n                y: self.y / length,\n                z: self.z / length,\n            })\n        } else {\n            None\n        }\n    }\n}\n\nimpl core::ops::Add<Vector3> for Vector3 {\n    type Output = Self;\n\n    #[inline]\n    fn add(self, rhs: Vector3) -> Self::Output {\n        Vector3 {\n            x: self.x + rhs.x,\n            y: self.y + rhs.y,\n            z: self.z + rhs.z,\n        }\n    }\n}\n\nimpl core::ops::Sub<Vector3> for Vector3 {\n    type Output = Self;\n\n    #[inline]\n    fn sub(self, rhs: Vector3) -> Self::Output {\n        Vector3 {\n            x: self.x - rhs.x,\n            y: self.y - rhs.y,\n            z: self.z - rhs.z,\n        }\n    }\n}\n\n#[derive(Clone, Copy, Debug)]\nstruct Normal {\n    factor: Vector2,\n    normal: Vector2,\n}\n\nimpl Normal {\n    #[inline]\n    fn new(factor_x: f32, factor_y: f32, nx: i16, ny: i16) -> Self {\n        Normal {\n            factor: Vector2::new(factor_x, factor_y),\n            normal: Vector2::new(-nx as f32, -ny as f32),\n        }\n    }\n}\n\n/// Renders a diffuse lighting.\n///\n/// - `src` pixels can have any alpha method, since only the alpha channel is used.\n/// - `dest` will have an **unpremultiplied alpha**.\n///\n/// Does nothing when `src` is less than 3x3.\n///\n/// # Panics\n///\n/// - When `src` and `dest` have different sizes.\npub fn diffuse_lighting(\n    fe: &DiffuseLighting,\n    light_source: LightSource,\n    src: ImageRef,\n    dest: ImageRefMut,\n) {\n    assert!(src.width == dest.width && src.height == dest.height);\n\n    let light_factor = |normal: Normal, light_vector: Vector3| {\n        let k = if normal.normal.approx_zero() {\n            light_vector.z\n        } else {\n            let mut n = normal.normal * (fe.surface_scale() / 255.0);\n            n.x *= normal.factor.x;\n            n.y *= normal.factor.y;\n\n            let normal = Vector3::new(n.x, n.y, 1.0);\n\n            normal.dot(&light_vector) / normal.length()\n        };\n\n        fe.diffuse_constant() * k\n    };\n\n    apply(\n        light_source,\n        fe.surface_scale(),\n        fe.lighting_color(),\n        &light_factor,\n        calc_diffuse_alpha,\n        src,\n        dest,\n    );\n}\n\n/// Renders a specular lighting.\n///\n/// - `src` pixels can have any alpha method, since only the alpha channel is used.\n/// - `dest` will have a **premultiplied alpha**.\n///\n/// Does nothing when `src` is less than 3x3.\n///\n/// # Panics\n///\n/// - When `src` and `dest` have different sizes.\npub fn specular_lighting(\n    fe: &SpecularLighting,\n    light_source: LightSource,\n    src: ImageRef,\n    dest: ImageRefMut,\n) {\n    assert!(src.width == dest.width && src.height == dest.height);\n\n    let light_factor = |normal: Normal, light_vector: Vector3| {\n        let h = light_vector + Vector3::new(0.0, 0.0, 1.0);\n        let h_length = h.length();\n\n        if h_length.approx_zero_ulps(4) {\n            return 0.0;\n        }\n\n        let k = if normal.normal.approx_zero() {\n            let n_dot_h = h.z / h_length;\n            if fe.specular_exponent().approx_eq_ulps(&1.0, 4) {\n                n_dot_h\n            } else {\n                n_dot_h.powf(fe.specular_exponent())\n            }\n        } else {\n            let mut n = normal.normal * (fe.surface_scale() / 255.0);\n            n.x *= normal.factor.x;\n            n.y *= normal.factor.y;\n\n            let normal = Vector3::new(n.x, n.y, 1.0);\n\n            let n_dot_h = normal.dot(&h) / normal.length() / h_length;\n            if fe.specular_exponent().approx_eq_ulps(&1.0, 4) {\n                n_dot_h\n            } else {\n                n_dot_h.powf(fe.specular_exponent())\n            }\n        };\n\n        fe.specular_constant() * k\n    };\n\n    apply(\n        light_source,\n        fe.surface_scale(),\n        fe.lighting_color(),\n        &light_factor,\n        calc_specular_alpha,\n        src,\n        dest,\n    );\n}\n\nfn apply(\n    light_source: LightSource,\n    surface_scale: f32,\n    lighting_color: Color,\n    light_factor: &dyn Fn(Normal, Vector3) -> f32,\n    calc_alpha: fn(u8, u8, u8) -> u8,\n    src: ImageRef,\n    mut dest: ImageRefMut,\n) {\n    if src.width < 3 || src.height < 3 {\n        return;\n    }\n\n    let width = src.width;\n    let height = src.height;\n\n    // `feDistantLight` has a fixed vector, so calculate it beforehand.\n    let mut light_vector = match light_source {\n        LightSource::DistantLight(light) => {\n            let azimuth = light.azimuth.to_radians();\n            let elevation = light.elevation.to_radians();\n            Vector3::new(\n                azimuth.cos() * elevation.cos(),\n                azimuth.sin() * elevation.cos(),\n                elevation.sin(),\n            )\n        }\n        _ => Vector3::new(1.0, 1.0, 1.0),\n    };\n\n    let mut calc = |nx, ny, normal: Normal| {\n        match light_source {\n            LightSource::DistantLight(_) => {}\n            LightSource::PointLight(ref light) => {\n                let nz = src.alpha_at(nx, ny) as f32 / 255.0 * surface_scale;\n                let origin = Vector3::new(light.x, light.y, light.z);\n                let v = origin - Vector3::new(nx as f32, ny as f32, nz);\n                light_vector = v.normalized().unwrap_or(v);\n            }\n            LightSource::SpotLight(ref light) => {\n                let nz = src.alpha_at(nx, ny) as f32 / 255.0 * surface_scale;\n                let origin = Vector3::new(light.x, light.y, light.z);\n                let v = origin - Vector3::new(nx as f32, ny as f32, nz);\n                light_vector = v.normalized().unwrap_or(v);\n            }\n        }\n\n        let light_color = light_color(&light_source, lighting_color, light_vector);\n        let factor = light_factor(normal, light_vector);\n\n        let compute = |x| (f32_bound(0.0, x as f32 * factor, 255.0) + 0.5) as u8;\n\n        let r = compute(light_color.red);\n        let g = compute(light_color.green);\n        let b = compute(light_color.blue);\n        let a = calc_alpha(r, g, b);\n\n        *dest.pixel_at_mut(nx, ny) = RGBA8 { b, g, r, a };\n    };\n\n    calc(0, 0, top_left_normal(src));\n    calc(width - 1, 0, top_right_normal(src));\n    calc(0, height - 1, bottom_left_normal(src));\n    calc(width - 1, height - 1, bottom_right_normal(src));\n\n    for x in 1..width - 1 {\n        calc(x, 0, top_row_normal(src, x));\n        calc(x, height - 1, bottom_row_normal(src, x));\n    }\n\n    for y in 1..height - 1 {\n        calc(0, y, left_column_normal(src, y));\n        calc(width - 1, y, right_column_normal(src, y));\n    }\n\n    for y in 1..height - 1 {\n        for x in 1..width - 1 {\n            calc(x, y, interior_normal(src, x, y));\n        }\n    }\n}\n\nfn light_color(light: &LightSource, lighting_color: Color, light_vector: Vector3) -> Color {\n    match *light {\n        LightSource::DistantLight(_) | LightSource::PointLight(_) => lighting_color,\n        LightSource::SpotLight(ref light) => {\n            let origin = Vector3::new(light.x, light.y, light.z);\n            let direction = Vector3::new(light.points_at_x, light.points_at_y, light.points_at_z);\n            let direction = direction - origin;\n            let direction = direction.normalized().unwrap_or(direction);\n            let minus_l_dot_s = -light_vector.dot(&direction);\n            if minus_l_dot_s <= 0.0 {\n                return Color::black();\n            }\n\n            if let Some(limiting_cone_angle) = light.limiting_cone_angle {\n                if minus_l_dot_s < limiting_cone_angle.to_radians().cos() {\n                    return Color::black();\n                }\n            }\n\n            let factor = minus_l_dot_s.powf(light.specular_exponent.get());\n            let compute = |x| (f32_bound(0.0, x as f32 * factor, 255.0) + 0.5) as u8;\n\n            Color::new_rgb(\n                compute(lighting_color.red),\n                compute(lighting_color.green),\n                compute(lighting_color.blue),\n            )\n        }\n    }\n}\n\nfn top_left_normal(img: ImageRef) -> Normal {\n    let center = img.alpha_at(0, 0);\n    let right = img.alpha_at(1, 0);\n    let bottom = img.alpha_at(0, 1);\n    let bottom_right = img.alpha_at(1, 1);\n\n    Normal::new(\n        FACTOR_2_3,\n        FACTOR_2_3,\n        -2 * center + 2 * right - bottom + bottom_right,\n        -2 * center - right + 2 * bottom + bottom_right,\n    )\n}\n\nfn top_right_normal(img: ImageRef) -> Normal {\n    let left = img.alpha_at(img.width - 2, 0);\n    let center = img.alpha_at(img.width - 1, 0);\n    let bottom_left = img.alpha_at(img.width - 2, 1);\n    let bottom = img.alpha_at(img.width - 1, 1);\n\n    Normal::new(\n        FACTOR_2_3,\n        FACTOR_2_3,\n        -2 * left + 2 * center - bottom_left + bottom,\n        -left - 2 * center + bottom_left + 2 * bottom,\n    )\n}\n\nfn bottom_left_normal(img: ImageRef) -> Normal {\n    let top = img.alpha_at(0, img.height - 2);\n    let top_right = img.alpha_at(1, img.height - 2);\n    let center = img.alpha_at(0, img.height - 1);\n    let right = img.alpha_at(1, img.height - 1);\n\n    Normal::new(\n        FACTOR_2_3,\n        FACTOR_2_3,\n        -top + top_right - 2 * center + 2 * right,\n        -2 * top - top_right + 2 * center + right,\n    )\n}\n\nfn bottom_right_normal(img: ImageRef) -> Normal {\n    let top_left = img.alpha_at(img.width - 2, img.height - 2);\n    let top = img.alpha_at(img.width - 1, img.height - 2);\n    let left = img.alpha_at(img.width - 2, img.height - 1);\n    let center = img.alpha_at(img.width - 1, img.height - 1);\n\n    Normal::new(\n        FACTOR_2_3,\n        FACTOR_2_3,\n        -top_left + top - 2 * left + 2 * center,\n        -top_left - 2 * top + left + 2 * center,\n    )\n}\n\nfn top_row_normal(img: ImageRef, x: u32) -> Normal {\n    let left = img.alpha_at(x - 1, 0);\n    let center = img.alpha_at(x, 0);\n    let right = img.alpha_at(x + 1, 0);\n    let bottom_left = img.alpha_at(x - 1, 1);\n    let bottom = img.alpha_at(x, 1);\n    let bottom_right = img.alpha_at(x + 1, 1);\n\n    Normal::new(\n        FACTOR_1_3,\n        FACTOR_1_2,\n        -2 * left + 2 * right - bottom_left + bottom_right,\n        -left - 2 * center - right + bottom_left + 2 * bottom + bottom_right,\n    )\n}\n\nfn bottom_row_normal(img: ImageRef, x: u32) -> Normal {\n    let top_left = img.alpha_at(x - 1, img.height - 2);\n    let top = img.alpha_at(x, img.height - 2);\n    let top_right = img.alpha_at(x + 1, img.height - 2);\n    let left = img.alpha_at(x - 1, img.height - 1);\n    let center = img.alpha_at(x, img.height - 1);\n    let right = img.alpha_at(x + 1, img.height - 1);\n\n    Normal::new(\n        FACTOR_1_3,\n        FACTOR_1_2,\n        -top_left + top_right - 2 * left + 2 * right,\n        -top_left - 2 * top - top_right + left + 2 * center + right,\n    )\n}\n\nfn left_column_normal(img: ImageRef, y: u32) -> Normal {\n    let top = img.alpha_at(0, y - 1);\n    let top_right = img.alpha_at(1, y - 1);\n    let center = img.alpha_at(0, y);\n    let right = img.alpha_at(1, y);\n    let bottom = img.alpha_at(0, y + 1);\n    let bottom_right = img.alpha_at(1, y + 1);\n\n    Normal::new(\n        FACTOR_1_2,\n        FACTOR_1_3,\n        -top + top_right - 2 * center + 2 * right - bottom + bottom_right,\n        -2 * top - top_right + 2 * bottom + bottom_right,\n    )\n}\n\nfn right_column_normal(img: ImageRef, y: u32) -> Normal {\n    let top_left = img.alpha_at(img.width - 2, y - 1);\n    let top = img.alpha_at(img.width - 1, y - 1);\n    let left = img.alpha_at(img.width - 2, y);\n    let center = img.alpha_at(img.width - 1, y);\n    let bottom_left = img.alpha_at(img.width - 2, y + 1);\n    let bottom = img.alpha_at(img.width - 1, y + 1);\n\n    Normal::new(\n        FACTOR_1_2,\n        FACTOR_1_3,\n        -top_left + top - 2 * left + 2 * center - bottom_left + bottom,\n        -top_left - 2 * top + bottom_left + 2 * bottom,\n    )\n}\n\nfn interior_normal(img: ImageRef, x: u32, y: u32) -> Normal {\n    let top_left = img.alpha_at(x - 1, y - 1);\n    let top = img.alpha_at(x, y - 1);\n    let top_right = img.alpha_at(x + 1, y - 1);\n    let left = img.alpha_at(x - 1, y);\n    let right = img.alpha_at(x + 1, y);\n    let bottom_left = img.alpha_at(x - 1, y + 1);\n    let bottom = img.alpha_at(x, y + 1);\n    let bottom_right = img.alpha_at(x + 1, y + 1);\n\n    Normal::new(\n        FACTOR_1_4,\n        FACTOR_1_4,\n        -top_left + top_right - 2 * left + 2 * right - bottom_left + bottom_right,\n        -top_left - 2 * top - top_right + bottom_left + 2 * bottom + bottom_right,\n    )\n}\n\nfn calc_diffuse_alpha(_: u8, _: u8, _: u8) -> u8 {\n    255\n}\n\nfn calc_specular_alpha(r: u8, g: u8, b: u8) -> u8 {\n    use core::cmp::max;\n    max(max(r, g), b)\n}\n"
  },
  {
    "path": "crates/resvg/src/filter/mod.rs",
    "content": "// Copyright 2018 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse std::rc::Rc;\n\nuse rgb::{FromSlice, RGBA8};\nuse tiny_skia::IntRect;\nuse usvg::{ApproxEqUlps, ApproxZeroUlps};\n\nmod box_blur;\nmod color_matrix;\nmod component_transfer;\nmod composite;\nmod convolve_matrix;\nmod displacement_map;\nmod iir_blur;\nmod lighting;\nmod morphology;\nmod turbulence;\n\n// TODO: apply single primitive filters in-place\n\n/// An image reference.\n///\n/// Image pixels should be stored in RGBA order.\n///\n/// Some filters will require premultiplied channels, some not.\n/// See specific filter documentation for details.\n#[derive(Clone, Copy)]\npub struct ImageRef<'a> {\n    data: &'a [RGBA8],\n    width: u32,\n    height: u32,\n}\n\nimpl<'a> ImageRef<'a> {\n    /// Creates a new image reference.\n    ///\n    /// Doesn't clone the provided data.\n    #[inline]\n    pub fn new(width: u32, height: u32, data: &'a [RGBA8]) -> Self {\n        ImageRef {\n            data,\n            width,\n            height,\n        }\n    }\n\n    #[inline]\n    fn alpha_at(&self, x: u32, y: u32) -> i16 {\n        self.data[(self.width * y + x) as usize].a as i16\n    }\n}\n\n/// A mutable `ImageRef` variant.\npub struct ImageRefMut<'a> {\n    data: &'a mut [RGBA8],\n    width: u32,\n    height: u32,\n}\n\nimpl<'a> ImageRefMut<'a> {\n    /// Creates a new mutable image reference.\n    ///\n    /// Doesn't clone the provided data.\n    #[inline]\n    pub fn new(width: u32, height: u32, data: &'a mut [RGBA8]) -> Self {\n        ImageRefMut {\n            data,\n            width,\n            height,\n        }\n    }\n\n    #[inline]\n    fn pixel_at(&self, x: u32, y: u32) -> RGBA8 {\n        self.data[(self.width * y + x) as usize]\n    }\n\n    #[inline]\n    fn pixel_at_mut(&mut self, x: u32, y: u32) -> &mut RGBA8 {\n        &mut self.data[(self.width * y + x) as usize]\n    }\n}\n\n#[derive(Debug)]\npub(crate) enum Error {\n    InvalidRegion,\n    NoResults,\n}\n\ntrait PixmapExt: Sized {\n    fn try_create(width: u32, height: u32) -> Result<tiny_skia::Pixmap, Error>;\n    fn copy_region(&self, region: IntRect) -> Result<tiny_skia::Pixmap, Error>;\n    fn clear(&mut self);\n    fn into_srgb(&mut self);\n    fn into_linear_rgb(&mut self);\n}\n\nimpl PixmapExt for tiny_skia::Pixmap {\n    fn try_create(width: u32, height: u32) -> Result<tiny_skia::Pixmap, Error> {\n        tiny_skia::Pixmap::new(width, height).ok_or(Error::InvalidRegion)\n    }\n\n    fn copy_region(&self, region: IntRect) -> Result<tiny_skia::Pixmap, Error> {\n        let rect = IntRect::from_xywh(region.x(), region.y(), region.width(), region.height())\n            .ok_or(Error::InvalidRegion)?;\n        self.clone_rect(rect).ok_or(Error::InvalidRegion)\n    }\n\n    fn clear(&mut self) {\n        self.fill(tiny_skia::Color::TRANSPARENT);\n    }\n\n    fn into_srgb(&mut self) {\n        demultiply_alpha(self.data_mut().as_rgba_mut());\n        from_linear_rgb(self.data_mut().as_rgba_mut());\n        multiply_alpha(self.data_mut().as_rgba_mut());\n    }\n\n    fn into_linear_rgb(&mut self) {\n        demultiply_alpha(self.data_mut().as_rgba_mut());\n        into_linear_rgb(self.data_mut().as_rgba_mut());\n        multiply_alpha(self.data_mut().as_rgba_mut());\n    }\n}\n\n/// Multiplies provided pixels alpha.\nfn multiply_alpha(data: &mut [RGBA8]) {\n    for p in data {\n        let a = p.a as f32 / 255.0;\n        p.b = (p.b as f32 * a + 0.5) as u8;\n        p.g = (p.g as f32 * a + 0.5) as u8;\n        p.r = (p.r as f32 * a + 0.5) as u8;\n    }\n}\n\n/// Demultiplies provided pixels alpha.\nfn demultiply_alpha(data: &mut [RGBA8]) {\n    for p in data {\n        let a = p.a as f32 / 255.0;\n        p.b = (p.b as f32 / a + 0.5) as u8;\n        p.g = (p.g as f32 / a + 0.5) as u8;\n        p.r = (p.r as f32 / a + 0.5) as u8;\n    }\n}\n\n/// Precomputed sRGB to LinearRGB table.\n///\n/// Since we are storing the result in `u8`, there is no need to compute those\n/// values each time. Mainly because it's very expensive.\n///\n/// ```text\n/// if (C_srgb <= 0.04045)\n///     C_lin = C_srgb / 12.92;\n///  else\n///     C_lin = pow((C_srgb + 0.055) / 1.055, 2.4);\n/// ```\n///\n/// Thanks to librsvg for the idea.\n#[rustfmt::skip]\nconst SRGB_TO_LINEAR_RGB_TABLE: &[u8; 256] = &[\n    0,   0,   0,   0,   0,   0,  0,    1,   1,   1,   1,   1,   1,   1,   1,   1,\n    1,   1,   2,   2,   2,   2,  2,    2,   2,   2,   3,   3,   3,   3,   3,   3,\n    4,   4,   4,   4,   4,   5,  5,    5,   5,   6,   6,   6,   6,   7,   7,   7,\n    8,   8,   8,   8,   9,   9,  9,   10,  10,  10,  11,  11,  12,  12,  12,  13,\n    13,  13,  14,  14,  15,  15,  16,  16,  17,  17,  17,  18,  18,  19,  19,  20,\n    20,  21,  22,  22,  23,  23,  24,  24,  25,  25,  26,  27,  27,  28,  29,  29,\n    30,  30,  31,  32,  32,  33,  34,  35,  35,  36,  37,  37,  38,  39,  40,  41,\n    41,  42,  43,  44,  45,  45,  46,  47,  48,  49,  50,  51,  51,  52,  53,  54,\n    55,  56,  57,  58,  59,  60,  61,  62,  63,  64,  65,  66,  67,  68,  69,  70,\n    71,  72,  73,  74,  76,  77,  78,  79,  80,  81,  82,  84,  85,  86,  87,  88,\n    90,  91,  92,  93,  95,  96,  97,  99, 100, 101, 103, 104, 105, 107, 108, 109,\n    111, 112, 114, 115, 116, 118, 119, 121, 122, 124, 125, 127, 128, 130, 131, 133,\n    134, 136, 138, 139, 141, 142, 144, 146, 147, 149, 151, 152, 154, 156, 157, 159,\n    161, 163, 164, 166, 168, 170, 171, 173, 175, 177, 179, 181, 183, 184, 186, 188,\n    190, 192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220,\n    222, 224, 226, 229, 231, 233, 235, 237, 239, 242, 244, 246, 248, 250, 253, 255,\n];\n\n/// Precomputed LinearRGB to sRGB table.\n///\n/// Since we are storing the result in `u8`, there is no need to compute those\n/// values each time. Mainly because it's very expensive.\n///\n/// ```text\n/// if (C_lin <= 0.0031308)\n///     C_srgb = C_lin * 12.92;\n/// else\n///     C_srgb = 1.055 * pow(C_lin, 1.0 / 2.4) - 0.055;\n/// ```\n///\n/// Thanks to librsvg for the idea.\n#[rustfmt::skip]\nconst LINEAR_RGB_TO_SRGB_TABLE: &[u8; 256] = &[\n    0,  13,  22,  28,  34,  38,  42,  46,  50,  53,  56,  59,  61,  64,  66,  69,\n    71,  73,  75,  77,  79,  81,  83,  85,  86,  88,  90,  92,  93,  95,  96,  98,\n    99, 101, 102, 104, 105, 106, 108, 109, 110, 112, 113, 114, 115, 117, 118, 119,\n    120, 121, 122, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136,\n    137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 148, 149, 150, 151,\n    152, 153, 154, 155, 155, 156, 157, 158, 159, 159, 160, 161, 162, 163, 163, 164,\n    165, 166, 167, 167, 168, 169, 170, 170, 171, 172, 173, 173, 174, 175, 175, 176,\n    177, 178, 178, 179, 180, 180, 181, 182, 182, 183, 184, 185, 185, 186, 187, 187,\n    188, 189, 189, 190, 190, 191, 192, 192, 193, 194, 194, 195, 196, 196, 197, 197,\n    198, 199, 199, 200, 200, 201, 202, 202, 203, 203, 204, 205, 205, 206, 206, 207,\n    208, 208, 209, 209, 210, 210, 211, 212, 212, 213, 213, 214, 214, 215, 215, 216,\n    216, 217, 218, 218, 219, 219, 220, 220, 221, 221, 222, 222, 223, 223, 224, 224,\n    225, 226, 226, 227, 227, 228, 228, 229, 229, 230, 230, 231, 231, 232, 232, 233,\n    233, 234, 234, 235, 235, 236, 236, 237, 237, 238, 238, 238, 239, 239, 240, 240,\n    241, 241, 242, 242, 243, 243, 244, 244, 245, 245, 246, 246, 246, 247, 247, 248,\n    248, 249, 249, 250, 250, 251, 251, 251, 252, 252, 253, 253, 254, 254, 255, 255,\n];\n\n/// Converts input pixel from sRGB into LinearRGB.\n///\n/// Provided pixels should have an **unpremultiplied alpha**.\n///\n/// RGB channels order of the input image doesn't matter, but alpha channel must be the last one.\nfn into_linear_rgb(data: &mut [RGBA8]) {\n    for p in data {\n        p.r = SRGB_TO_LINEAR_RGB_TABLE[p.r as usize];\n        p.g = SRGB_TO_LINEAR_RGB_TABLE[p.g as usize];\n        p.b = SRGB_TO_LINEAR_RGB_TABLE[p.b as usize];\n    }\n}\n\n/// Converts input pixel from LinearRGB into sRGB.\n///\n/// Provided pixels should have an **unpremultiplied alpha**.\n///\n/// RGB channels order of the input image doesn't matter, but alpha channel must be the last one.\nfn from_linear_rgb(data: &mut [RGBA8]) {\n    for p in data {\n        p.r = LINEAR_RGB_TO_SRGB_TABLE[p.r as usize];\n        p.g = LINEAR_RGB_TO_SRGB_TABLE[p.g as usize];\n        p.b = LINEAR_RGB_TO_SRGB_TABLE[p.b as usize];\n    }\n}\n\n// TODO: https://github.com/rust-lang/rust/issues/44095\n#[inline]\nfn f32_bound(min: f32, val: f32, max: f32) -> f32 {\n    debug_assert!(min.is_finite());\n    debug_assert!(val.is_finite());\n    debug_assert!(max.is_finite());\n\n    if val > max {\n        max\n    } else if val < min {\n        min\n    } else {\n        val\n    }\n}\n\n#[derive(Clone)]\nstruct Image {\n    /// Filter primitive result.\n    ///\n    /// All images have the same size which is equal to the current filter region.\n    image: Rc<tiny_skia::Pixmap>,\n\n    /// Image's region that has actual data.\n    ///\n    /// Region is in global coordinates and not in `image` one.\n    ///\n    /// Image's content outside this region will be transparent/cleared.\n    ///\n    /// Currently used only for `feTile`.\n    region: IntRect,\n\n    /// The current color space.\n    color_space: usvg::filter::ColorInterpolation,\n}\n\nimpl Image {\n    fn from_image(image: tiny_skia::Pixmap, color_space: usvg::filter::ColorInterpolation) -> Self {\n        let (w, h) = (image.width(), image.height());\n        Image {\n            image: Rc::new(image),\n            region: IntRect::from_xywh(0, 0, w, h).unwrap(),\n            color_space,\n        }\n    }\n\n    fn into_color_space(\n        self,\n        color_space: usvg::filter::ColorInterpolation,\n    ) -> Result<Self, Error> {\n        if color_space != self.color_space {\n            let region = self.region;\n\n            let mut image = self.take()?;\n\n            match color_space {\n                usvg::filter::ColorInterpolation::SRGB => image.into_srgb(),\n                usvg::filter::ColorInterpolation::LinearRGB => image.into_linear_rgb(),\n            }\n\n            Ok(Image {\n                image: Rc::new(image),\n                region,\n                color_space,\n            })\n        } else {\n            Ok(self)\n        }\n    }\n\n    fn take(self) -> Result<tiny_skia::Pixmap, Error> {\n        match Rc::try_unwrap(self.image) {\n            Ok(v) => Ok(v),\n            Err(v) => Ok((*v).clone()),\n        }\n    }\n\n    fn width(&self) -> u32 {\n        self.image.width()\n    }\n\n    fn height(&self) -> u32 {\n        self.image.height()\n    }\n\n    fn as_ref(&self) -> &tiny_skia::Pixmap {\n        &self.image\n    }\n}\n\nstruct FilterResult {\n    name: String,\n    image: Image,\n}\n\npub fn apply(\n    filter: &usvg::filter::Filter,\n    ts: tiny_skia::Transform,\n    source: &mut tiny_skia::Pixmap,\n) {\n    let result = apply_inner(filter, ts, source);\n    let result = result.and_then(|image| apply_to_canvas(image, source));\n\n    // Clear on error.\n    if result.is_err() {\n        source.fill(tiny_skia::Color::TRANSPARENT);\n    }\n\n    match result {\n        Ok(_) => {}\n        Err(Error::InvalidRegion) => {\n            log::warn!(\"Filter has an invalid region.\");\n        }\n        Err(Error::NoResults) => {}\n    }\n}\n\nfn apply_inner(\n    filter: &usvg::filter::Filter,\n    ts: usvg::Transform,\n    source: &mut tiny_skia::Pixmap,\n) -> Result<Image, Error> {\n    let region = filter\n        .rect()\n        .transform(ts)\n        .map(|r| r.to_int_rect())\n        .ok_or(Error::InvalidRegion)?;\n\n    let mut results: Vec<FilterResult> = Vec::new();\n\n    for primitive in filter.primitives() {\n        let mut subregion = primitive\n            .rect()\n            .transform(ts)\n            .map(|r| r.to_int_rect())\n            .ok_or(Error::InvalidRegion)?;\n\n        // `feOffset` inherits its region from the input.\n        if let usvg::filter::Kind::Offset(fe) = primitive.kind() {\n            if let usvg::filter::Input::Reference(name) = fe.input() {\n                if let Some(res) = results.iter().rev().find(|v| v.name == *name) {\n                    subregion = res.image.region;\n                }\n            }\n        }\n\n        let cs = primitive.color_interpolation();\n\n        let mut result = match primitive.kind() {\n            usvg::filter::Kind::Blend(fe) => {\n                let input1 = get_input(fe.input1(), region, source, &results)?;\n                let input2 = get_input(fe.input2(), region, source, &results)?;\n                apply_blend(fe, cs, region, input1, input2)\n            }\n            usvg::filter::Kind::DropShadow(fe) => {\n                let input = get_input(fe.input(), region, source, &results)?;\n                apply_drop_shadow(fe, cs, ts, input)\n            }\n            usvg::filter::Kind::Flood(fe) => apply_flood(fe, region),\n            usvg::filter::Kind::GaussianBlur(fe) => {\n                let input = get_input(fe.input(), region, source, &results)?;\n                apply_blur(fe, cs, ts, input)\n            }\n            usvg::filter::Kind::Offset(fe) => {\n                let input = get_input(fe.input(), region, source, &results)?;\n                apply_offset(fe, ts, input)\n            }\n            usvg::filter::Kind::Composite(fe) => {\n                let input1 = get_input(fe.input1(), region, source, &results)?;\n                let input2 = get_input(fe.input2(), region, source, &results)?;\n                apply_composite(fe, cs, region, input1, input2)\n            }\n            usvg::filter::Kind::Merge(fe) => apply_merge(fe, cs, region, source, &results),\n            usvg::filter::Kind::Tile(fe) => {\n                let input = get_input(fe.input(), region, source, &results)?;\n                apply_tile(input, region)\n            }\n            usvg::filter::Kind::Image(fe) => apply_image(fe, region, subregion, ts),\n            usvg::filter::Kind::ComponentTransfer(fe) => {\n                let input = get_input(fe.input(), region, source, &results)?;\n                apply_component_transfer(fe, cs, input)\n            }\n            usvg::filter::Kind::ColorMatrix(fe) => {\n                let input = get_input(fe.input(), region, source, &results)?;\n                apply_color_matrix(fe, cs, input)\n            }\n            usvg::filter::Kind::ConvolveMatrix(fe) => {\n                let input = get_input(fe.input(), region, source, &results)?;\n                apply_convolve_matrix(fe, cs, input)\n            }\n            usvg::filter::Kind::Morphology(fe) => {\n                let input = get_input(fe.input(), region, source, &results)?;\n                apply_morphology(fe, cs, ts, input)\n            }\n            usvg::filter::Kind::DisplacementMap(fe) => {\n                let input1 = get_input(fe.input1(), region, source, &results)?;\n                let input2 = get_input(fe.input2(), region, source, &results)?;\n                apply_displacement_map(fe, region, cs, ts, input1, input2)\n            }\n            usvg::filter::Kind::Turbulence(fe) => apply_turbulence(fe, region, cs, ts),\n            usvg::filter::Kind::DiffuseLighting(fe) => {\n                let input = get_input(fe.input(), region, source, &results)?;\n                apply_diffuse_lighting(fe, region, cs, ts, input)\n            }\n            usvg::filter::Kind::SpecularLighting(fe) => {\n                let input = get_input(fe.input(), region, source, &results)?;\n                apply_specular_lighting(fe, region, cs, ts, input)\n            }\n        }?;\n\n        if region != subregion {\n            // Clip result.\n\n            // TODO: explain\n            let subregion2 = if let usvg::filter::Kind::Offset(..) = primitive.kind() {\n                // We do not support clipping on feOffset.\n                region.translate_to(0, 0)\n            } else {\n                subregion.translate(-region.x(), -region.y())\n            }\n            .unwrap();\n\n            let color_space = result.color_space;\n\n            let pixmap = {\n                // This is cropping by clearing the pixels outside the region.\n                let mut paint = tiny_skia::Paint::default();\n                paint.set_color(tiny_skia::Color::BLACK);\n                paint.blend_mode = tiny_skia::BlendMode::Clear;\n\n                let mut pixmap = result.take()?;\n                let w = pixmap.width() as f32;\n                let h = pixmap.height() as f32;\n\n                if let Some(rect) = tiny_skia::Rect::from_xywh(0.0, 0.0, w, subregion2.y() as f32) {\n                    pixmap.fill_rect(rect, &paint, tiny_skia::Transform::identity(), None);\n                }\n\n                if let Some(rect) = tiny_skia::Rect::from_xywh(0.0, 0.0, subregion2.x() as f32, h) {\n                    pixmap.fill_rect(rect, &paint, tiny_skia::Transform::identity(), None);\n                }\n\n                if let Some(rect) = tiny_skia::Rect::from_xywh(subregion2.right() as f32, 0.0, w, h)\n                {\n                    pixmap.fill_rect(rect, &paint, tiny_skia::Transform::identity(), None);\n                }\n\n                if let Some(rect) =\n                    tiny_skia::Rect::from_xywh(0.0, subregion2.bottom() as f32, w, h)\n                {\n                    pixmap.fill_rect(rect, &paint, tiny_skia::Transform::identity(), None);\n                }\n\n                pixmap\n            };\n\n            result = Image {\n                image: Rc::new(pixmap),\n                region: subregion,\n                color_space,\n            };\n        }\n\n        results.push(FilterResult {\n            name: primitive.result().to_string(),\n            image: result,\n        });\n    }\n\n    if let Some(res) = results.pop() {\n        Ok(res.image)\n    } else {\n        Err(Error::NoResults)\n    }\n}\n\nfn get_input(\n    input: &usvg::filter::Input,\n    region: IntRect,\n    source: &tiny_skia::Pixmap,\n    results: &[FilterResult],\n) -> Result<Image, Error> {\n    match input {\n        usvg::filter::Input::SourceGraphic => {\n            let image = source.clone();\n\n            Ok(Image {\n                image: Rc::new(image),\n                region,\n                color_space: usvg::filter::ColorInterpolation::SRGB,\n            })\n        }\n        usvg::filter::Input::SourceAlpha => {\n            let mut image = source.clone();\n            // Set RGB to black. Keep alpha as is.\n            for p in image.data_mut().as_rgba_mut() {\n                p.r = 0;\n                p.g = 0;\n                p.b = 0;\n            }\n\n            Ok(Image {\n                image: Rc::new(image),\n                region,\n                color_space: usvg::filter::ColorInterpolation::SRGB,\n            })\n        }\n        usvg::filter::Input::Reference(name) => {\n            if let Some(v) = results.iter().rev().find(|v| v.name == *name) {\n                Ok(v.image.clone())\n            } else {\n                // Technically unreachable.\n                log::warn!(\"Unknown filter primitive reference '{}'.\", name);\n                get_input(&usvg::filter::Input::SourceGraphic, region, source, results)\n            }\n        }\n    }\n}\n\ntrait PixmapToImageRef<'a> {\n    fn as_image_ref(&'a self) -> ImageRef<'a>;\n    fn as_image_ref_mut(&'a mut self) -> ImageRefMut<'a>;\n}\n\nimpl<'a> PixmapToImageRef<'a> for tiny_skia::Pixmap {\n    fn as_image_ref(&'a self) -> ImageRef<'a> {\n        ImageRef::new(self.width(), self.height(), self.data().as_rgba())\n    }\n\n    fn as_image_ref_mut(&'a mut self) -> ImageRefMut<'a> {\n        ImageRefMut::new(self.width(), self.height(), self.data_mut().as_rgba_mut())\n    }\n}\n\nfn apply_drop_shadow(\n    fe: &usvg::filter::DropShadow,\n    cs: usvg::filter::ColorInterpolation,\n    ts: usvg::Transform,\n    input: Image,\n) -> Result<Image, Error> {\n    let (dx, dy) = match scale_coordinates(fe.dx(), fe.dy(), ts) {\n        Some(v) => v,\n        None => return Ok(input),\n    };\n\n    let mut pixmap = tiny_skia::Pixmap::try_create(input.width(), input.height())?;\n    let input_pixmap = input.into_color_space(cs)?.take()?;\n    let mut shadow_pixmap = input_pixmap.clone();\n\n    if let Some((std_dx, std_dy, use_box_blur)) =\n        resolve_std_dev(fe.std_dev_x().get(), fe.std_dev_y().get(), ts)\n    {\n        if use_box_blur {\n            box_blur::apply(std_dx, std_dy, shadow_pixmap.as_image_ref_mut());\n        } else {\n            iir_blur::apply(std_dx, std_dy, shadow_pixmap.as_image_ref_mut());\n        }\n    }\n\n    // flood\n    let color = tiny_skia::Color::from_rgba8(\n        fe.color().red,\n        fe.color().green,\n        fe.color().blue,\n        fe.opacity().to_u8(),\n    );\n    for p in shadow_pixmap.pixels_mut() {\n        let mut color = color;\n        color.apply_opacity(p.alpha() as f32 / 255.0);\n        *p = color.premultiply().to_color_u8();\n    }\n\n    match cs {\n        usvg::filter::ColorInterpolation::SRGB => shadow_pixmap.into_srgb(),\n        usvg::filter::ColorInterpolation::LinearRGB => shadow_pixmap.into_linear_rgb(),\n    }\n\n    pixmap.draw_pixmap(\n        dx as i32,\n        dy as i32,\n        shadow_pixmap.as_ref(),\n        &tiny_skia::PixmapPaint::default(),\n        tiny_skia::Transform::identity(),\n        None,\n    );\n\n    pixmap.draw_pixmap(\n        0,\n        0,\n        input_pixmap.as_ref(),\n        &tiny_skia::PixmapPaint::default(),\n        tiny_skia::Transform::identity(),\n        None,\n    );\n\n    Ok(Image::from_image(pixmap, cs))\n}\n\nfn apply_blur(\n    fe: &usvg::filter::GaussianBlur,\n    cs: usvg::filter::ColorInterpolation,\n    ts: usvg::Transform,\n    input: Image,\n) -> Result<Image, Error> {\n    let (std_dx, std_dy, use_box_blur) =\n        match resolve_std_dev(fe.std_dev_x().get(), fe.std_dev_y().get(), ts) {\n            Some(v) => v,\n            None => return Ok(input),\n        };\n\n    let mut pixmap = input.into_color_space(cs)?.take()?;\n\n    if use_box_blur {\n        box_blur::apply(std_dx, std_dy, pixmap.as_image_ref_mut());\n    } else {\n        iir_blur::apply(std_dx, std_dy, pixmap.as_image_ref_mut());\n    }\n\n    Ok(Image::from_image(pixmap, cs))\n}\n\nfn apply_offset(\n    fe: &usvg::filter::Offset,\n    ts: usvg::Transform,\n    input: Image,\n) -> Result<Image, Error> {\n    let (dx, dy) = match scale_coordinates(fe.dx(), fe.dy(), ts) {\n        Some(v) => v,\n        None => return Ok(input),\n    };\n\n    if dx.approx_zero_ulps(4) && dy.approx_zero_ulps(4) {\n        return Ok(input);\n    }\n\n    let mut pixmap = tiny_skia::Pixmap::try_create(input.width(), input.height())?;\n    pixmap.draw_pixmap(\n        dx as i32,\n        dy as i32,\n        input.as_ref().as_ref(),\n        &tiny_skia::PixmapPaint::default(),\n        tiny_skia::Transform::identity(),\n        None,\n    );\n\n    Ok(Image::from_image(pixmap, input.color_space))\n}\n\nfn apply_blend(\n    fe: &usvg::filter::Blend,\n    cs: usvg::filter::ColorInterpolation,\n    region: IntRect,\n    input1: Image,\n    input2: Image,\n) -> Result<Image, Error> {\n    let input1 = input1.into_color_space(cs)?;\n    let input2 = input2.into_color_space(cs)?;\n\n    let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?;\n\n    pixmap.draw_pixmap(\n        0,\n        0,\n        input2.as_ref().as_ref(),\n        &tiny_skia::PixmapPaint::default(),\n        tiny_skia::Transform::identity(),\n        None,\n    );\n\n    pixmap.draw_pixmap(\n        0,\n        0,\n        input1.as_ref().as_ref(),\n        &tiny_skia::PixmapPaint {\n            blend_mode: crate::render::convert_blend_mode(fe.mode()),\n            ..tiny_skia::PixmapPaint::default()\n        },\n        tiny_skia::Transform::identity(),\n        None,\n    );\n\n    Ok(Image::from_image(pixmap, cs))\n}\n\nfn apply_composite(\n    fe: &usvg::filter::Composite,\n    cs: usvg::filter::ColorInterpolation,\n    region: IntRect,\n    input1: Image,\n    input2: Image,\n) -> Result<Image, Error> {\n    use usvg::filter::CompositeOperator as Operator;\n\n    let input1 = input1.into_color_space(cs)?;\n    let input2 = input2.into_color_space(cs)?;\n\n    let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?;\n\n    if let Operator::Arithmetic { k1, k2, k3, k4 } = fe.operator() {\n        let pixmap1 = input1.take()?;\n        let pixmap2 = input2.take()?;\n\n        composite::arithmetic(\n            k1,\n            k2,\n            k3,\n            k4,\n            pixmap1.as_image_ref(),\n            pixmap2.as_image_ref(),\n            pixmap.as_image_ref_mut(),\n        );\n\n        return Ok(Image::from_image(pixmap, cs));\n    }\n\n    pixmap.draw_pixmap(\n        0,\n        0,\n        input2.as_ref().as_ref(),\n        &tiny_skia::PixmapPaint::default(),\n        tiny_skia::Transform::identity(),\n        None,\n    );\n\n    let blend_mode = match fe.operator() {\n        Operator::Over => tiny_skia::BlendMode::SourceOver,\n        Operator::In => tiny_skia::BlendMode::SourceIn,\n        Operator::Out => tiny_skia::BlendMode::SourceOut,\n        Operator::Atop => tiny_skia::BlendMode::SourceAtop,\n        Operator::Xor => tiny_skia::BlendMode::Xor,\n        Operator::Arithmetic { .. } => tiny_skia::BlendMode::SourceOver,\n    };\n\n    pixmap.draw_pixmap(\n        0,\n        0,\n        input1.as_ref().as_ref(),\n        &tiny_skia::PixmapPaint {\n            blend_mode,\n            ..tiny_skia::PixmapPaint::default()\n        },\n        tiny_skia::Transform::identity(),\n        None,\n    );\n\n    Ok(Image::from_image(pixmap, cs))\n}\n\nfn apply_merge(\n    fe: &usvg::filter::Merge,\n    cs: usvg::filter::ColorInterpolation,\n    region: IntRect,\n    source: &tiny_skia::Pixmap,\n    results: &[FilterResult],\n) -> Result<Image, Error> {\n    let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?;\n\n    for input in fe.inputs() {\n        let input = get_input(input, region, source, results)?;\n        let input = input.into_color_space(cs)?;\n        pixmap.draw_pixmap(\n            0,\n            0,\n            input.as_ref().as_ref(),\n            &tiny_skia::PixmapPaint::default(),\n            tiny_skia::Transform::identity(),\n            None,\n        );\n    }\n\n    Ok(Image::from_image(pixmap, cs))\n}\n\nfn apply_flood(fe: &usvg::filter::Flood, region: IntRect) -> Result<Image, Error> {\n    let c = fe.color();\n\n    let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?;\n    pixmap.fill(tiny_skia::Color::from_rgba8(\n        c.red,\n        c.green,\n        c.blue,\n        fe.opacity().to_u8(),\n    ));\n\n    Ok(Image::from_image(\n        pixmap,\n        usvg::filter::ColorInterpolation::SRGB,\n    ))\n}\n\nfn apply_tile(input: Image, region: IntRect) -> Result<Image, Error> {\n    let subregion = input.region.translate(-region.x(), -region.y()).unwrap();\n\n    let tile_pixmap = input.image.copy_region(subregion)?;\n    let mut paint = tiny_skia::Paint::default();\n    paint.shader = tiny_skia::Pattern::new(\n        tile_pixmap.as_ref(),\n        tiny_skia::SpreadMode::Repeat,\n        tiny_skia::FilterQuality::Bicubic,\n        1.0,\n        tiny_skia::Transform::from_translate(subregion.x() as f32, subregion.y() as f32),\n    );\n\n    let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?;\n    let rect = tiny_skia::Rect::from_xywh(0.0, 0.0, region.width() as f32, region.height() as f32)\n        .unwrap();\n    pixmap.fill_rect(rect, &paint, tiny_skia::Transform::identity(), None);\n\n    Ok(Image::from_image(\n        pixmap,\n        usvg::filter::ColorInterpolation::SRGB,\n    ))\n}\n\nfn apply_image(\n    fe: &usvg::filter::Image,\n    region: IntRect,\n    subregion: IntRect,\n    ts: usvg::Transform,\n) -> Result<Image, Error> {\n    let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?;\n\n    let (sx, sy) = ts.get_scale();\n    let transform = tiny_skia::Transform::from_row(\n        sx,\n        0.0,\n        0.0,\n        sy,\n        subregion.x() as f32,\n        subregion.y() as f32,\n    );\n\n    let ctx = crate::render::Context {\n        max_bbox: tiny_skia::IntRect::from_xywh(0, 0, region.width(), region.height()).unwrap(),\n    };\n\n    crate::render::render_nodes(fe.root(), &ctx, transform, &mut pixmap.as_mut());\n\n    Ok(Image::from_image(\n        pixmap,\n        usvg::filter::ColorInterpolation::SRGB,\n    ))\n}\n\nfn apply_component_transfer(\n    fe: &usvg::filter::ComponentTransfer,\n    cs: usvg::filter::ColorInterpolation,\n    input: Image,\n) -> Result<Image, Error> {\n    let mut pixmap = input.into_color_space(cs)?.take()?;\n\n    demultiply_alpha(pixmap.data_mut().as_rgba_mut());\n    component_transfer::apply(fe, pixmap.as_image_ref_mut());\n    multiply_alpha(pixmap.data_mut().as_rgba_mut());\n\n    Ok(Image::from_image(pixmap, cs))\n}\n\nfn apply_color_matrix(\n    fe: &usvg::filter::ColorMatrix,\n    cs: usvg::filter::ColorInterpolation,\n    input: Image,\n) -> Result<Image, Error> {\n    let mut pixmap = input.into_color_space(cs)?.take()?;\n\n    demultiply_alpha(pixmap.data_mut().as_rgba_mut());\n    color_matrix::apply(fe.kind(), pixmap.as_image_ref_mut());\n    multiply_alpha(pixmap.data_mut().as_rgba_mut());\n\n    Ok(Image::from_image(pixmap, cs))\n}\n\nfn apply_convolve_matrix(\n    fe: &usvg::filter::ConvolveMatrix,\n    cs: usvg::filter::ColorInterpolation,\n    input: Image,\n) -> Result<Image, Error> {\n    let mut pixmap = input.into_color_space(cs)?.take()?;\n\n    if fe.preserve_alpha() {\n        demultiply_alpha(pixmap.data_mut().as_rgba_mut());\n    }\n\n    convolve_matrix::apply(fe, pixmap.as_image_ref_mut());\n\n    Ok(Image::from_image(pixmap, cs))\n}\n\nfn apply_morphology(\n    fe: &usvg::filter::Morphology,\n    cs: usvg::filter::ColorInterpolation,\n    ts: usvg::Transform,\n    input: Image,\n) -> Result<Image, Error> {\n    let mut pixmap = input.into_color_space(cs)?.take()?;\n\n    let (rx, ry) = match scale_coordinates(fe.radius_x().get(), fe.radius_y().get(), ts) {\n        Some(v) => v,\n        None => return Ok(Image::from_image(pixmap, cs)),\n    };\n\n    if !(rx > 0.0 && ry > 0.0) {\n        pixmap.clear();\n        return Ok(Image::from_image(pixmap, cs));\n    }\n\n    morphology::apply(fe.operator(), rx, ry, pixmap.as_image_ref_mut());\n\n    Ok(Image::from_image(pixmap, cs))\n}\n\nfn apply_displacement_map(\n    fe: &usvg::filter::DisplacementMap,\n    region: IntRect,\n    cs: usvg::filter::ColorInterpolation,\n    ts: usvg::Transform,\n    input1: Image,\n    input2: Image,\n) -> Result<Image, Error> {\n    let pixmap1 = input1.into_color_space(cs)?.take()?;\n    let pixmap2 = input2.into_color_space(cs)?.take()?;\n\n    let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?;\n\n    let (sx, sy) = match scale_coordinates(fe.scale(), fe.scale(), ts) {\n        Some(v) => v,\n        None => return Ok(Image::from_image(pixmap1, cs)),\n    };\n\n    displacement_map::apply(\n        fe,\n        sx,\n        sy,\n        pixmap1.as_image_ref(),\n        pixmap2.as_image_ref(),\n        pixmap.as_image_ref_mut(),\n    );\n\n    Ok(Image::from_image(pixmap, cs))\n}\n\nfn apply_turbulence(\n    fe: &usvg::filter::Turbulence,\n    region: IntRect,\n    cs: usvg::filter::ColorInterpolation,\n    ts: usvg::Transform,\n) -> Result<Image, Error> {\n    let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?;\n\n    let (sx, sy) = ts.get_scale();\n    if sx.approx_zero_ulps(4) || sy.approx_zero_ulps(4) {\n        return Ok(Image::from_image(pixmap, cs));\n    }\n\n    turbulence::apply(\n        region.x() as f64 - ts.tx as f64,\n        region.y() as f64 - ts.ty as f64,\n        sx as f64,\n        sy as f64,\n        fe.base_frequency_x().get() as f64,\n        fe.base_frequency_y().get() as f64,\n        fe.num_octaves(),\n        fe.seed(),\n        fe.stitch_tiles(),\n        fe.kind() == usvg::filter::TurbulenceKind::FractalNoise,\n        pixmap.as_image_ref_mut(),\n    );\n\n    multiply_alpha(pixmap.data_mut().as_rgba_mut());\n\n    Ok(Image::from_image(pixmap, cs))\n}\n\nfn apply_diffuse_lighting(\n    fe: &usvg::filter::DiffuseLighting,\n    region: IntRect,\n    cs: usvg::filter::ColorInterpolation,\n    ts: usvg::Transform,\n    input: Image,\n) -> Result<Image, Error> {\n    let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?;\n\n    let light_source = transform_light_source(fe.light_source(), region, ts);\n\n    lighting::diffuse_lighting(\n        fe,\n        light_source,\n        input.as_ref().as_image_ref(),\n        pixmap.as_image_ref_mut(),\n    );\n\n    Ok(Image::from_image(pixmap, cs))\n}\n\nfn apply_specular_lighting(\n    fe: &usvg::filter::SpecularLighting,\n    region: IntRect,\n    cs: usvg::filter::ColorInterpolation,\n    ts: usvg::Transform,\n    input: Image,\n) -> Result<Image, Error> {\n    let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?;\n\n    let light_source = transform_light_source(fe.light_source(), region, ts);\n\n    lighting::specular_lighting(\n        fe,\n        light_source,\n        input.as_ref().as_image_ref(),\n        pixmap.as_image_ref_mut(),\n    );\n\n    Ok(Image::from_image(pixmap, cs))\n}\n\n// TODO: do not modify LightSource\nfn transform_light_source(\n    mut source: usvg::filter::LightSource,\n    region: IntRect,\n    ts: usvg::Transform,\n) -> usvg::filter::LightSource {\n    use std::f32::consts::SQRT_2;\n    use usvg::filter::LightSource;\n\n    match &mut source {\n        LightSource::DistantLight(..) => {}\n        LightSource::PointLight(light) => {\n            let mut point = tiny_skia::Point::from_xy(light.x, light.y);\n            ts.map_point(&mut point);\n            light.x = point.x - region.x() as f32;\n            light.y = point.y - region.y() as f32;\n            light.z = light.z * (ts.sx * ts.sx + ts.sy * ts.sy).sqrt() / SQRT_2;\n        }\n        LightSource::SpotLight(light) => {\n            let sz = (ts.sx * ts.sx + ts.sy * ts.sy).sqrt() / SQRT_2;\n\n            let mut point = tiny_skia::Point::from_xy(light.x, light.y);\n            ts.map_point(&mut point);\n            light.x = point.x - region.x() as f32;\n            light.y = point.y - region.x() as f32;\n            light.z *= sz;\n\n            let mut point = tiny_skia::Point::from_xy(light.points_at_x, light.points_at_y);\n            ts.map_point(&mut point);\n            light.points_at_x = point.x - region.x() as f32;\n            light.points_at_y = point.y - region.x() as f32;\n            light.points_at_z *= sz;\n        }\n    }\n\n    source\n}\n\nfn apply_to_canvas(input: Image, pixmap: &mut tiny_skia::Pixmap) -> Result<(), Error> {\n    let input = input.into_color_space(usvg::filter::ColorInterpolation::SRGB)?;\n\n    pixmap.fill(tiny_skia::Color::TRANSPARENT);\n    pixmap.draw_pixmap(\n        0,\n        0,\n        input.as_ref().as_ref(),\n        &tiny_skia::PixmapPaint::default(),\n        tiny_skia::Transform::identity(),\n        None,\n    );\n\n    Ok(())\n}\n\n/// Calculates Gaussian blur sigmas for the current world transform.\n///\n/// If the last flag is set, then a box blur should be used. Or IIR otherwise.\nfn resolve_std_dev(std_dx: f32, std_dy: f32, ts: usvg::Transform) -> Option<(f64, f64, bool)> {\n    let (mut std_dx, mut std_dy) = scale_coordinates(std_dx, std_dy, ts)?;\n\n    // 'A negative value or a value of zero disables the effect of the given filter primitive\n    // (i.e., the result is the filter input image).'\n    if std_dx.approx_eq_ulps(&0.0, 4) && std_dy.approx_eq_ulps(&0.0, 4) {\n        return None;\n    }\n\n    // Ignore tiny sigmas. In case of IIR blur it can lead to a transparent image.\n    if std_dx < 0.05 {\n        std_dx = 0.0;\n    }\n\n    if std_dy < 0.05 {\n        std_dy = 0.0;\n    }\n\n    const BLUR_SIGMA_THRESHOLD: f32 = 2.0;\n    // Check that the current feGaussianBlur filter can be applied using a box blur.\n    let box_blur = std_dx >= BLUR_SIGMA_THRESHOLD || std_dy >= BLUR_SIGMA_THRESHOLD;\n\n    Some((std_dx as f64, std_dy as f64, box_blur))\n}\n\nfn scale_coordinates(x: f32, y: f32, ts: usvg::Transform) -> Option<(f32, f32)> {\n    let (sx, sy) = ts.get_scale();\n    Some((x * sx, y * sy))\n}\n"
  },
  {
    "path": "crates/resvg/src/filter/morphology.rs",
    "content": "// Copyright 2020 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse super::ImageRefMut;\nuse rgb::RGBA8;\nuse usvg::filter::MorphologyOperator;\n\n/// Applies a morphology filter.\n///\n/// `src` pixels should have a **premultiplied alpha**.\n///\n/// # Allocations\n///\n/// This method will allocate a copy of the `src` image as a back buffer.\npub fn apply(operator: MorphologyOperator, rx: f32, ry: f32, src: ImageRefMut) {\n    // No point in making matrix larger than image.\n    let columns = std::cmp::min(rx.ceil() as u32 * 2, src.width);\n    let rows = std::cmp::min(ry.ceil() as u32 * 2, src.height);\n    let target_x = (columns as f32 / 2.0).floor() as u32;\n    let target_y = (rows as f32 / 2.0).floor() as u32;\n\n    let width_max = src.width as i32 - 1;\n    let height_max = src.height as i32 - 1;\n\n    let mut buf = vec![RGBA8::default(); src.data.len()];\n    let mut buf = ImageRefMut::new(src.width, src.height, &mut buf);\n    let mut x = 0;\n    let mut y = 0;\n    for _ in src.data.iter() {\n        let mut new_p = RGBA8::default();\n        if operator == MorphologyOperator::Erode {\n            new_p.r = 255;\n            new_p.g = 255;\n            new_p.b = 255;\n            new_p.a = 255;\n        }\n\n        for oy in 0..rows {\n            for ox in 0..columns {\n                let tx = x as i32 - target_x as i32 + ox as i32;\n                let ty = y as i32 - target_y as i32 + oy as i32;\n\n                if tx < 0 || tx > width_max || ty < 0 || ty > height_max {\n                    continue;\n                }\n\n                let p = src.pixel_at(tx as u32, ty as u32);\n                if operator == MorphologyOperator::Erode {\n                    new_p.r = std::cmp::min(p.r, new_p.r);\n                    new_p.g = std::cmp::min(p.g, new_p.g);\n                    new_p.b = std::cmp::min(p.b, new_p.b);\n                    new_p.a = std::cmp::min(p.a, new_p.a);\n                } else {\n                    new_p.r = std::cmp::max(p.r, new_p.r);\n                    new_p.g = std::cmp::max(p.g, new_p.g);\n                    new_p.b = std::cmp::max(p.b, new_p.b);\n                    new_p.a = std::cmp::max(p.a, new_p.a);\n                }\n            }\n        }\n\n        *buf.pixel_at_mut(x, y) = new_p;\n\n        x += 1;\n        if x == src.width {\n            x = 0;\n            y += 1;\n        }\n    }\n\n    // Do not use `mem::swap` because `data` referenced via FFI.\n    src.data.copy_from_slice(buf.data);\n}\n"
  },
  {
    "path": "crates/resvg/src/filter/turbulence.rs",
    "content": "// Copyright 2020 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\n#![allow(clippy::needless_range_loop)]\n\nuse super::{ImageRefMut, f32_bound};\nuse usvg::ApproxZeroUlps;\n\nconst RAND_M: i32 = 2147483647; // 2**31 - 1\nconst RAND_A: i32 = 16807; // 7**5; primitive root of m\nconst RAND_Q: i32 = 127773; // m / a\nconst RAND_R: i32 = 2836; // m % a\nconst B_SIZE: usize = 0x100;\nconst B_SIZE_32: i32 = 0x100;\nconst B_LEN: usize = B_SIZE + B_SIZE + 2;\nconst BM: i32 = 0xff;\nconst PERLIN_N: i32 = 0x1000;\n\n#[derive(Clone, Copy)]\nstruct StitchInfo {\n    width: i32, // How much to subtract to wrap for stitching.\n    height: i32,\n    wrap_x: i32, // Minimum value to wrap.\n    wrap_y: i32,\n}\n\n/// Applies a turbulence filter.\n///\n/// `dest` image pixels will have an **unpremultiplied alpha**.\n///\n/// - `offset_x` and `offset_y` indicate filter region offset.\n/// - `sx` and `sy` indicate canvas scale.\npub fn apply(\n    offset_x: f64,\n    offset_y: f64,\n    sx: f64,\n    sy: f64,\n    base_frequency_x: f64,\n    base_frequency_y: f64,\n    num_octaves: u32,\n    seed: i32,\n    stitch_tiles: bool,\n    fractal_noise: bool,\n    dest: ImageRefMut,\n) {\n    let (lattice_selector, gradient) = init(seed);\n    let width = dest.width;\n    let height = dest.height;\n    let mut x = 0;\n    let mut y = 0;\n    for pixel in dest.data.iter_mut() {\n        let turb = |channel| {\n            let (tx, ty) = ((x as f64 + offset_x) / sx, (y as f64 + offset_y) / sy);\n            let n = turbulence(\n                channel,\n                tx,\n                ty,\n                x as f64,\n                y as f64,\n                width as f64,\n                height as f64,\n                base_frequency_x,\n                base_frequency_y,\n                num_octaves,\n                fractal_noise,\n                stitch_tiles,\n                &lattice_selector,\n                &gradient,\n            );\n\n            let n = if fractal_noise {\n                (n * 255.0 + 255.0) / 2.0\n            } else {\n                n * 255.0\n            };\n\n            (f32_bound(0.0, n as f32, 255.0) + 0.5) as u8\n        };\n\n        pixel.r = turb(0);\n        pixel.g = turb(1);\n        pixel.b = turb(2);\n        pixel.a = turb(3);\n\n        x += 1;\n        if x == dest.width {\n            x = 0;\n            y += 1;\n        }\n    }\n}\n\nfn init(mut seed: i32) -> (Vec<usize>, Vec<Vec<Vec<f64>>>) {\n    let mut lattice_selector = vec![0; B_LEN];\n    let mut gradient = vec![vec![vec![0.0; 2]; B_LEN]; 4];\n\n    if seed <= 0 {\n        seed = -seed % (RAND_M - 1) + 1;\n    }\n\n    if seed > RAND_M - 1 {\n        seed = RAND_M - 1;\n    }\n\n    for k in 0..4 {\n        for i in 0..B_SIZE {\n            lattice_selector[i] = i;\n            for j in 0..2 {\n                seed = random(seed);\n                gradient[k][i][j] =\n                    ((seed % (B_SIZE_32 + B_SIZE_32)) - B_SIZE_32) as f64 / B_SIZE_32 as f64;\n            }\n\n            let s = (gradient[k][i][0] * gradient[k][i][0] + gradient[k][i][1] * gradient[k][i][1])\n                .sqrt();\n\n            gradient[k][i][0] /= s;\n            gradient[k][i][1] /= s;\n        }\n    }\n\n    for i in (1..B_SIZE).rev() {\n        let k = lattice_selector[i];\n        seed = random(seed);\n        let j = (seed % B_SIZE_32) as usize;\n        lattice_selector[i] = lattice_selector[j];\n        lattice_selector[j] = k;\n    }\n\n    for i in 0..B_SIZE + 2 {\n        lattice_selector[B_SIZE + i] = lattice_selector[i];\n        for g in gradient.iter_mut().take(4) {\n            for j in 0..2 {\n                g[B_SIZE + i][j] = g[i][j];\n            }\n        }\n    }\n\n    (lattice_selector, gradient)\n}\n\nfn turbulence(\n    color_channel: usize,\n    mut x: f64,\n    mut y: f64,\n    tile_x: f64,\n    tile_y: f64,\n    tile_width: f64,\n    tile_height: f64,\n    mut base_freq_x: f64,\n    mut base_freq_y: f64,\n    num_octaves: u32,\n    fractal_sum: bool,\n    do_stitching: bool,\n    lattice_selector: &[usize],\n    gradient: &[Vec<Vec<f64>>],\n) -> f64 {\n    // Adjust the base frequencies if necessary for stitching.\n    let mut stitch = if do_stitching {\n        // When stitching tiled turbulence, the frequencies must be adjusted\n        // so that the tile borders will be continuous.\n        if !base_freq_x.approx_zero_ulps(4) {\n            let lo_freq = (tile_width * base_freq_x).floor() / tile_width;\n            let hi_freq = (tile_width * base_freq_x).ceil() / tile_width;\n            if base_freq_x / lo_freq < hi_freq / base_freq_x {\n                base_freq_x = lo_freq;\n            } else {\n                base_freq_x = hi_freq;\n            }\n        }\n\n        if !base_freq_y.approx_zero_ulps(4) {\n            let lo_freq = (tile_height * base_freq_y).floor() / tile_height;\n            let hi_freq = (tile_height * base_freq_y).ceil() / tile_height;\n            if base_freq_y / lo_freq < hi_freq / base_freq_y {\n                base_freq_y = lo_freq;\n            } else {\n                base_freq_y = hi_freq;\n            }\n        }\n\n        // Set up initial stitch values.\n        let width = (tile_width * base_freq_x + 0.5) as i32;\n        let height = (tile_height * base_freq_y + 0.5) as i32;\n        let wrap_x = (tile_x * base_freq_x + PERLIN_N as f64 + width as f64) as i32;\n        let wrap_y = (tile_y * base_freq_y + PERLIN_N as f64 + height as f64) as i32;\n        Some(StitchInfo {\n            width,\n            height,\n            wrap_x,\n            wrap_y,\n        })\n    } else {\n        None\n    };\n\n    let mut sum = 0.0;\n    x *= base_freq_x;\n    y *= base_freq_y;\n    let mut ratio = 1.0;\n    for _ in 0..num_octaves {\n        if fractal_sum {\n            sum += noise2(color_channel, x, y, lattice_selector, gradient, stitch) / ratio;\n        } else {\n            sum += noise2(color_channel, x, y, lattice_selector, gradient, stitch).abs() / ratio;\n        }\n        x *= 2.0;\n        y *= 2.0;\n        ratio *= 2.0;\n\n        if let Some(ref mut stitch) = stitch {\n            // Update stitch values. Subtracting PerlinN before the multiplication and\n            // adding it afterward simplifies to subtracting it once.\n            stitch.width *= 2;\n            stitch.wrap_x = 2 * stitch.wrap_x - PERLIN_N;\n            stitch.height *= 2;\n            stitch.wrap_y = 2 * stitch.wrap_y - PERLIN_N;\n        }\n    }\n\n    sum\n}\n\nfn noise2(\n    color_channel: usize,\n    x: f64,\n    y: f64,\n    lattice_selector: &[usize],\n    gradient: &[Vec<Vec<f64>>],\n    stitch_info: Option<StitchInfo>,\n) -> f64 {\n    let t = x + PERLIN_N as f64;\n    let mut bx0 = t as i32;\n    let mut bx1 = bx0 + 1;\n    let rx0 = t - t as i64 as f64;\n    let rx1 = rx0 - 1.0;\n    let t = y + PERLIN_N as f64;\n    let mut by0 = t as i32;\n    let mut by1 = by0 + 1;\n    let ry0 = t - t as i64 as f64;\n    let ry1 = ry0 - 1.0;\n\n    // If stitching, adjust lattice points accordingly.\n    if let Some(info) = stitch_info {\n        if bx0 >= info.wrap_x {\n            bx0 -= info.width;\n        }\n\n        if bx1 >= info.wrap_x {\n            bx1 -= info.width;\n        }\n\n        if by0 >= info.wrap_y {\n            by0 -= info.height;\n        }\n\n        if by1 >= info.wrap_y {\n            by1 -= info.height;\n        }\n    }\n\n    bx0 &= BM;\n    bx1 &= BM;\n    by0 &= BM;\n    by1 &= BM;\n    let i = lattice_selector[bx0 as usize];\n    let j = lattice_selector[bx1 as usize];\n    let b00 = lattice_selector[i + by0 as usize];\n    let b10 = lattice_selector[j + by0 as usize];\n    let b01 = lattice_selector[i + by1 as usize];\n    let b11 = lattice_selector[j + by1 as usize];\n    let sx = s_curve(rx0);\n    let sy = s_curve(ry0);\n    let q = &gradient[color_channel][b00];\n    let u = rx0 * q[0] + ry0 * q[1];\n    let q = &gradient[color_channel][b10];\n    let v = rx1 * q[0] + ry0 * q[1];\n    let a = lerp(sx, u, v);\n    let q = &gradient[color_channel][b01];\n    let u = rx0 * q[0] + ry1 * q[1];\n    let q = &gradient[color_channel][b11];\n    let v = rx1 * q[0] + ry1 * q[1];\n    let b = lerp(sx, u, v);\n    lerp(sy, a, b)\n}\n\nfn random(seed: i32) -> i32 {\n    let mut result = RAND_A * (seed % RAND_Q) - RAND_R * (seed / RAND_Q);\n    if result <= 0 {\n        result += RAND_M;\n    }\n\n    result\n}\n\n#[inline]\nfn s_curve(t: f64) -> f64 {\n    t * t * (3.0 - 2.0 * t)\n}\n\n#[inline]\nfn lerp(t: f64, a: f64, b: f64) -> f64 {\n    a + t * (b - a)\n}\n"
  },
  {
    "path": "crates/resvg/src/geom.rs",
    "content": "// Copyright 2023 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\n/// Fits the current rect into the specified bounds.\npub fn fit_to_rect(\n    r: tiny_skia::IntRect,\n    bounds: tiny_skia::IntRect,\n) -> Option<tiny_skia::IntRect> {\n    let mut left = r.left();\n    if left < bounds.left() {\n        left = bounds.left();\n    }\n\n    let mut top = r.top();\n    if top < bounds.top() {\n        top = bounds.top();\n    }\n\n    let mut right = r.right();\n    if right > bounds.right() {\n        right = bounds.right();\n    }\n\n    let mut bottom = r.bottom();\n    if bottom > bounds.bottom() {\n        bottom = bounds.bottom();\n    }\n\n    tiny_skia::IntRect::from_ltrb(left, top, right, bottom)\n}\n"
  },
  {
    "path": "crates/resvg/src/image.rs",
    "content": "// Copyright 2018 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\npub fn render(\n    image: &usvg::Image,\n    transform: tiny_skia::Transform,\n    pixmap: &mut tiny_skia::PixmapMut,\n) {\n    if !image.is_visible() {\n        return;\n    }\n\n    render_inner(image.kind(), transform, image.rendering_mode(), pixmap);\n}\n\npub fn render_inner(\n    image_kind: &usvg::ImageKind,\n    transform: tiny_skia::Transform,\n    #[allow(unused_variables)] rendering_mode: usvg::ImageRendering,\n    pixmap: &mut tiny_skia::PixmapMut,\n) {\n    match image_kind {\n        usvg::ImageKind::SVG(tree) => {\n            render_vector(tree, transform, pixmap);\n        }\n        #[cfg(feature = \"raster-images\")]\n        _ => {\n            raster_images::render_raster(image_kind, transform, rendering_mode, pixmap);\n        }\n        #[cfg(not(feature = \"raster-images\"))]\n        _ => {\n            log::warn!(\"Images decoding was disabled by a build feature.\");\n        }\n    }\n}\n\nfn render_vector(\n    tree: &usvg::Tree,\n    transform: tiny_skia::Transform,\n    pixmap: &mut tiny_skia::PixmapMut,\n) -> Option<()> {\n    let mut sub_pixmap = tiny_skia::Pixmap::new(pixmap.width(), pixmap.height()).unwrap();\n    crate::render(tree, transform, &mut sub_pixmap.as_mut());\n    pixmap.draw_pixmap(\n        0,\n        0,\n        sub_pixmap.as_ref(),\n        &tiny_skia::PixmapPaint::default(),\n        tiny_skia::Transform::default(),\n        None,\n    );\n\n    Some(())\n}\n\n#[cfg(feature = \"raster-images\")]\nmod raster_images {\n    use crate::OptionLog;\n    use std::io::Cursor;\n    use usvg::ImageRendering;\n\n    fn decode_raster(image: &usvg::ImageKind) -> Option<tiny_skia::Pixmap> {\n        match image {\n            usvg::ImageKind::SVG(_) => None,\n            usvg::ImageKind::JPEG(data) => {\n                decode_jpeg(data).log_none(|| log::warn!(\"Failed to decode a JPEG image.\"))\n            }\n            usvg::ImageKind::PNG(data) => {\n                decode_png(data).log_none(|| log::warn!(\"Failed to decode a PNG image.\"))\n            }\n            usvg::ImageKind::GIF(data) => {\n                decode_gif(data).log_none(|| log::warn!(\"Failed to decode a GIF image.\"))\n            }\n            usvg::ImageKind::WEBP(data) => {\n                decode_webp(data).log_none(|| log::warn!(\"Failed to decode a WebP image.\"))\n            }\n        }\n    }\n\n    fn decode_png(data: &[u8]) -> Option<tiny_skia::Pixmap> {\n        tiny_skia::Pixmap::decode_png(data).ok()\n    }\n\n    fn decode_jpeg(data: &[u8]) -> Option<tiny_skia::Pixmap> {\n        use zune_jpeg::zune_core::colorspace::ColorSpace;\n        use zune_jpeg::zune_core::options::DecoderOptions;\n\n        let cursor = Cursor::new(data);\n        let options = DecoderOptions::default().jpeg_set_out_colorspace(ColorSpace::RGBA);\n        let mut decoder = zune_jpeg::JpegDecoder::new_with_options(cursor, options);\n        decoder.decode_headers().ok()?;\n        let output_cs = decoder.output_colorspace()?;\n\n        let img_data = {\n            let data = decoder.decode().ok()?;\n            match output_cs {\n                ColorSpace::RGBA => data,\n                _ => return None,\n            }\n        };\n\n        let info = decoder.info()?;\n\n        let size = tiny_skia::IntSize::from_wh(info.width as u32, info.height as u32)?;\n        tiny_skia::Pixmap::from_vec(img_data, size)\n    }\n\n    fn decode_gif(data: &[u8]) -> Option<tiny_skia::Pixmap> {\n        let mut decoder = gif::DecodeOptions::new();\n        decoder.set_color_output(gif::ColorOutput::RGBA);\n        let mut decoder = decoder.read_info(data).ok()?;\n        let first_frame = decoder.read_next_frame().ok()??;\n\n        let size = tiny_skia::IntSize::from_wh(\n            u32::from(first_frame.width),\n            u32::from(first_frame.height),\n        )?;\n\n        let (w, h) = size.dimensions();\n        let mut pixmap = tiny_skia::Pixmap::new(w, h)?;\n        rgba_to_pixmap(&first_frame.buffer, &mut pixmap);\n        Some(pixmap)\n    }\n\n    fn decode_webp(data: &[u8]) -> Option<tiny_skia::Pixmap> {\n        let mut decoder = image_webp::WebPDecoder::new(std::io::Cursor::new(data)).ok()?;\n        let mut first_frame = vec![0; decoder.output_buffer_size()?];\n        decoder.read_image(&mut first_frame).ok()?;\n\n        let (w, h) = decoder.dimensions();\n        let mut pixmap = tiny_skia::Pixmap::new(w, h)?;\n\n        if decoder.has_alpha() {\n            rgba_to_pixmap(&first_frame, &mut pixmap);\n        } else {\n            rgb_to_pixmap(&first_frame, &mut pixmap);\n        }\n\n        Some(pixmap)\n    }\n\n    fn rgb_to_pixmap(data: &[u8], pixmap: &mut tiny_skia::Pixmap) {\n        use rgb::FromSlice;\n\n        let mut i = 0;\n        let dst = pixmap.data_mut();\n        for p in data.as_rgb() {\n            dst[i + 0] = p.r;\n            dst[i + 1] = p.g;\n            dst[i + 2] = p.b;\n            dst[i + 3] = 255;\n\n            i += tiny_skia::BYTES_PER_PIXEL;\n        }\n    }\n\n    fn rgba_to_pixmap(data: &[u8], pixmap: &mut tiny_skia::Pixmap) {\n        use rgb::FromSlice;\n\n        let mut i = 0;\n        let dst = pixmap.data_mut();\n        for p in data.as_rgba() {\n            let a = p.a as f64 / 255.0;\n            dst[i + 0] = (p.r as f64 * a + 0.5) as u8;\n            dst[i + 1] = (p.g as f64 * a + 0.5) as u8;\n            dst[i + 2] = (p.b as f64 * a + 0.5) as u8;\n            dst[i + 3] = p.a;\n\n            i += tiny_skia::BYTES_PER_PIXEL;\n        }\n    }\n\n    pub(crate) fn render_raster(\n        image: &usvg::ImageKind,\n        transform: tiny_skia::Transform,\n        rendering_mode: usvg::ImageRendering,\n        pixmap: &mut tiny_skia::PixmapMut,\n    ) -> Option<()> {\n        let raster = decode_raster(image)?;\n\n        let rect = tiny_skia::Size::from_wh(raster.width() as f32, raster.height() as f32)?\n            .to_rect(0.0, 0.0)?;\n\n        let quality = match rendering_mode {\n            ImageRendering::OptimizeQuality => tiny_skia::FilterQuality::Bicubic,\n            ImageRendering::OptimizeSpeed => tiny_skia::FilterQuality::Nearest,\n            ImageRendering::Smooth => tiny_skia::FilterQuality::Bilinear,\n            ImageRendering::HighQuality => tiny_skia::FilterQuality::Bicubic,\n            ImageRendering::CrispEdges => tiny_skia::FilterQuality::Nearest,\n            ImageRendering::Pixelated => tiny_skia::FilterQuality::Nearest,\n        };\n\n        let pattern = tiny_skia::Pattern::new(\n            raster.as_ref(),\n            tiny_skia::SpreadMode::Pad,\n            quality,\n            1.0,\n            tiny_skia::Transform::default(),\n        );\n        let mut paint = tiny_skia::Paint::default();\n        paint.shader = pattern;\n\n        pixmap.fill_rect(rect, &paint, transform, None);\n\n        Some(())\n    }\n}\n"
  },
  {
    "path": "crates/resvg/src/lib.rs",
    "content": "// Copyright 2017 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\n/*!\n[resvg](https://github.com/linebender/resvg) is an SVG rendering library.\n*/\n\n#![forbid(unsafe_code)]\n#![warn(missing_docs)]\n#![allow(clippy::field_reassign_with_default)]\n#![allow(clippy::identity_op)]\n#![allow(clippy::too_many_arguments)]\n#![allow(clippy::uninlined_format_args)]\n#![allow(clippy::upper_case_acronyms)]\n#![allow(clippy::wrong_self_convention)]\n\npub use tiny_skia;\npub use usvg;\n\nmod clip;\nmod filter;\nmod geom;\nmod image;\nmod mask;\nmod path;\nmod render;\n\n/// Renders a tree onto the pixmap.\n///\n/// `transform` will be used as a root transform.\n/// Can be used to position SVG inside the `pixmap`.\n///\n/// The produced content is in the sRGB color space.\npub fn render(\n    tree: &usvg::Tree,\n    transform: tiny_skia::Transform,\n    pixmap: &mut tiny_skia::PixmapMut,\n) {\n    let target_size = tiny_skia::IntSize::from_wh(pixmap.width(), pixmap.height()).unwrap();\n    let max_bbox = tiny_skia::IntRect::from_xywh(\n        -(target_size.width() as i32) * 2,\n        -(target_size.height() as i32) * 2,\n        target_size.width() * 5,\n        target_size.height() * 5,\n    )\n    .unwrap();\n\n    let ctx = render::Context { max_bbox };\n    render::render_nodes(tree.root(), &ctx, transform, pixmap);\n}\n\n/// Renders a node onto the pixmap.\n///\n/// `transform` will be used as a root transform.\n/// Can be used to position SVG inside the `pixmap`.\n///\n/// The expected pixmap size can be retrieved from `usvg::Node::abs_layer_bounding_box()`.\n///\n/// Returns `None` when `node` has a zero size.\n///\n/// The produced content is in the sRGB color space.\npub fn render_node(\n    node: &usvg::Node,\n    mut transform: tiny_skia::Transform,\n    pixmap: &mut tiny_skia::PixmapMut,\n) -> Option<()> {\n    let bbox = node.abs_layer_bounding_box()?;\n\n    let target_size = tiny_skia::IntSize::from_wh(pixmap.width(), pixmap.height()).unwrap();\n    let max_bbox = tiny_skia::IntRect::from_xywh(\n        -(target_size.width() as i32) * 2,\n        -(target_size.height() as i32) * 2,\n        target_size.width() * 5,\n        target_size.height() * 5,\n    )\n    .unwrap();\n\n    transform = transform.pre_translate(-bbox.x(), -bbox.y());\n\n    let ctx = render::Context { max_bbox };\n    render::render_node(node, &ctx, transform, pixmap);\n\n    Some(())\n}\n\npub(crate) trait OptionLog {\n    fn log_none<F: FnOnce()>(self, f: F) -> Self;\n}\n\nimpl<T> OptionLog for Option<T> {\n    #[inline]\n    fn log_none<F: FnOnce()>(self, f: F) -> Self {\n        self.or_else(|| {\n            f();\n            None\n        })\n    }\n}\n"
  },
  {
    "path": "crates/resvg/src/main.rs",
    "content": "// Copyright 2020 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\n#![allow(clippy::uninlined_format_args)]\n\nuse std::path;\nuse std::sync::Arc;\n\nuse usvg::fontdb;\n\nfn main() {\n    if let Err(e) = process() {\n        eprintln!(\"Error: {}.\", e);\n        std::process::exit(1);\n    }\n}\n\nfn timed<F, T>(perf: bool, name: &str, mut f: F) -> T\nwhere\n    F: FnMut() -> T,\n{\n    let now = std::time::Instant::now();\n    let result = f();\n    if perf {\n        let elapsed = now.elapsed().as_micros() as f64 / 1000.0;\n        println!(\"{}: {:.2}ms\", name, elapsed);\n    }\n\n    result\n}\n\nfn process() -> Result<(), String> {\n    let mut args = match parse_args() {\n        Ok(args) => args,\n        Err(e) => {\n            println!(\"{}\", HELP);\n            return Err(e);\n        }\n    };\n\n    // Do not print warning during the ID querying.\n    //\n    // Some crates still can print to stdout/stderr, but we can't do anything about it.\n    if !(args.query_all || args.quiet) {\n        if let Ok(()) = log::set_logger(&LOGGER) {\n            log::set_max_level(log::LevelFilter::Warn);\n        }\n    }\n\n    let mut svg_data = timed(args.perf, \"Reading\", || -> Result<Vec<u8>, &str> {\n        if let InputFrom::File(ref file) = args.in_svg {\n            std::fs::read(file).map_err(|_| \"failed to open the provided file\")\n        } else {\n            use std::io::Read;\n            let mut buf = Vec::new();\n            let stdin = std::io::stdin();\n            let mut handle = stdin.lock();\n            handle\n                .read_to_end(&mut buf)\n                .map_err(|_| \"failed to read stdin\")?;\n            Ok(buf)\n        }\n    })?;\n\n    if svg_data.starts_with(&[0x1f, 0x8b]) {\n        svg_data = timed(args.perf, \"SVGZ Decoding\", || {\n            usvg::decompress_svgz(&svg_data).map_err(|e| e.to_string())\n        })?;\n    };\n\n    let svg_string = std::str::from_utf8(&svg_data)\n        .map_err(|_| \"provided data has not an UTF-8 encoding\".to_string())?;\n\n    let xml_tree = timed(args.perf, \"XML Parsing\", || {\n        let xml_opt = usvg::roxmltree::ParsingOptions {\n            allow_dtd: true,\n            ..Default::default()\n        };\n        usvg::roxmltree::Document::parse_with_options(svg_string, xml_opt)\n            .map_err(|e| e.to_string())\n    })?;\n\n    // fontdb initialization is pretty expensive, so perform it only when needed.\n    let has_text_nodes = xml_tree\n        .descendants()\n        .any(|n| n.has_tag_name((\"http://www.w3.org/2000/svg\", \"text\")));\n\n    if has_text_nodes {\n        timed(args.perf, \"FontDB\", || {\n            load_fonts(&args.raw_args, args.usvg.fontdb_mut());\n        });\n    }\n\n    let tree = timed(args.perf, \"SVG Parsing\", || {\n        usvg::Tree::from_xmltree(&xml_tree, &args.usvg).map_err(|e| e.to_string())\n    })?;\n\n    if args.query_all {\n        return query_all(&tree);\n    }\n\n    // Render.\n    let img = render_svg(&args, &tree)?;\n\n    match args.out_png.unwrap() {\n        OutputTo::Stdout => {\n            use std::io::Write;\n            let buf = img.encode_png().map_err(|e| e.to_string())?;\n            std::io::stdout().write_all(&buf).unwrap();\n        }\n        OutputTo::File(ref file) => {\n            timed(args.perf, \"Saving\", || {\n                img.save_png(file).map_err(|e| e.to_string())\n            })?;\n        }\n    };\n\n    Ok(())\n}\n\nconst HELP: &str = \"\\\nresvg is an SVG rendering application.\n\nUSAGE:\n  resvg [OPTIONS] <in-svg> <out-png>  # from file to file\n  resvg [OPTIONS] <in-svg> -c         # from file to stdout\n  resvg [OPTIONS] - <out-png>         # from stdin to file\n  resvg [OPTIONS] - -c                # from stdin to stdout\n\n  resvg in.svg out.png\n  resvg -z 4 in.svg out.png\n  resvg --query-all in.svg\n\nOPTIONS:\n      --help                    Prints this help\n  -V, --version                 Prints version\n  -c                            Prints the output PNG to the stdout\n\n  -w, --width LENGTH            Sets the width in pixels\n  -h, --height LENGTH           Sets the height in pixels\n  -z, --zoom FACTOR             Zooms the image by a factor\n      --dpi DPI                 Sets the resolution\n                                [default: 96] [possible values: 10..4000 (inclusive)]\n  --background COLOR            Sets the background color\n                                Examples: red, #fff, #fff000\n  --stylesheet PATH             Inject a stylesheet that should be used when resolving\n                                CSS attributes.\n\n  --languages LANG              Sets a comma-separated list of languages that\n                                will be used during the 'systemLanguage'\n                                attribute resolving\n                                Examples: 'en-US', 'en-US, ru-RU', 'en, ru'\n                                [default: en]\n  --shape-rendering HINT        Selects the default shape rendering method\n                                [default: geometricPrecision]\n                                [possible values: optimizeSpeed, crispEdges,\n                                geometricPrecision]\n  --text-rendering HINT         Selects the default text rendering method\n                                [default: optimizeLegibility]\n                                [possible values: optimizeSpeed, optimizeLegibility,\n                                geometricPrecision]\n  --image-rendering HINT        Selects the default image rendering method\n                                [default: optimizeQuality]\n                                [possible values: optimizeQuality, optimizeSpeed, smooth, high-quality, crisp-edges, pixelated]\n  --resources-dir DIR           Sets a directory that will be used during\n                                relative paths resolving.\n                                Expected to be the same as the directory that\n                                contains the SVG file, but can be set to any.\n                                [default: input file directory]\n\n  --font-family FAMILY          Sets the default font family that will be\n                                used when no 'font-family' is present\n                                [default: Times New Roman]\n  --font-size SIZE              Sets the default font size that will be\n                                used when no 'font-size' is present\n                                [default: 12] [possible values: 1..192 (inclusive)]\n  --serif-family FAMILY         Sets the 'serif' font family\n                                [default: Times New Roman]\n  --sans-serif-family FAMILY    Sets the 'sans-serif' font family\n                                [default: Arial]\n  --cursive-family FAMILY       Sets the 'cursive' font family\n                                [default: Comic Sans MS]\n  --fantasy-family FAMILY       Sets the 'fantasy' font family\n                                [default: Impact]\n  --monospace-family FAMILY     Sets the 'monospace' font family\n                                [default: Courier New]\n  --use-font-file PATH          Load a specified font file into the fonts database.\n                                Will be used during text to path conversion.\n                                This option can be set multiple times\n  --use-fonts-dir PATH          Loads all fonts from the specified directory\n                                into the fonts database.\n                                Will be used during text to path conversion.\n                                This option can be set multiple times\n  --skip-system-fonts           Disables system fonts loading.\n                                You should add some fonts manually using\n                                --use-font-file and/or --use-fonts-dir\n                                Otherwise, text elements will not be processes\n  --list-fonts                  Lists successfully loaded font faces.\n                                Useful for debugging\n\n\n  --query-all                   Queries all valid SVG ids with bounding boxes\n  --export-id ID                Renders an object only with a specified ID\n  --export-area-page            Use an image size instead of an object size during ID exporting\n\n  --export-area-drawing         Use drawing's tight bounding box instead of image size.\n                                Used during normal rendering and not during --export-id\n\n  --perf                        Prints performance stats\n  --quiet                       Disables warnings\n\nARGS:\n  <in-svg>                      Input file\n  <out-png>                     Output file\n\";\n\n#[derive(Debug)]\nstruct CliArgs {\n    width: Option<u32>,\n    height: Option<u32>,\n    zoom: Option<f32>,\n    dpi: u32,\n    background: Option<svgtypes::Color>,\n\n    languages: Vec<String>,\n    shape_rendering: usvg::ShapeRendering,\n    text_rendering: usvg::TextRendering,\n    image_rendering: usvg::ImageRendering,\n    resources_dir: Option<path::PathBuf>,\n\n    font_family: Option<String>,\n    font_size: u32,\n    serif_family: Option<String>,\n    sans_serif_family: Option<String>,\n    cursive_family: Option<String>,\n    fantasy_family: Option<String>,\n    monospace_family: Option<String>,\n    font_files: Vec<path::PathBuf>,\n    font_dirs: Vec<path::PathBuf>,\n    skip_system_fonts: bool,\n    list_fonts: bool,\n    style_sheet: Option<path::PathBuf>,\n\n    query_all: bool,\n    export_id: Option<String>,\n    export_area_page: bool,\n\n    export_area_drawing: bool,\n\n    perf: bool,\n    quiet: bool,\n\n    input: Option<String>,\n    output: Option<String>,\n}\n\nfn collect_args() -> Result<CliArgs, pico_args::Error> {\n    let mut input = pico_args::Arguments::from_env();\n\n    if input.contains(\"--help\") {\n        print!(\"{}\", HELP);\n        std::process::exit(0);\n    }\n\n    if input.contains([\"-V\", \"--version\"]) {\n        println!(\"{}\", env!(\"CARGO_PKG_VERSION\"));\n        std::process::exit(0);\n    }\n\n    Ok(CliArgs {\n        width: input.opt_value_from_fn([\"-w\", \"--width\"], parse_length)?,\n        height: input.opt_value_from_fn([\"-h\", \"--height\"], parse_length)?,\n        zoom: input.opt_value_from_fn([\"-z\", \"--zoom\"], parse_zoom)?,\n        dpi: input.opt_value_from_fn(\"--dpi\", parse_dpi)?.unwrap_or(96),\n        background: input.opt_value_from_str(\"--background\")?,\n\n        languages: input\n            .opt_value_from_fn(\"--languages\", parse_languages)?\n            .unwrap_or_else(|| vec![\"en\".to_string()]), // TODO: use system language\n        shape_rendering: input\n            .opt_value_from_str(\"--shape-rendering\")?\n            .unwrap_or_default(),\n        text_rendering: input\n            .opt_value_from_str(\"--text-rendering\")?\n            .unwrap_or_default(),\n        image_rendering: input\n            .opt_value_from_str(\"--image-rendering\")?\n            .unwrap_or_default(),\n        resources_dir: input\n            .opt_value_from_str(\"--resources-dir\")\n            .unwrap_or_default(),\n\n        font_family: input.opt_value_from_str(\"--font-family\")?,\n        font_size: input\n            .opt_value_from_fn(\"--font-size\", parse_font_size)?\n            .unwrap_or(12),\n        serif_family: input.opt_value_from_str(\"--serif-family\")?,\n        sans_serif_family: input.opt_value_from_str(\"--sans-serif-family\")?,\n        cursive_family: input.opt_value_from_str(\"--cursive-family\")?,\n        fantasy_family: input.opt_value_from_str(\"--fantasy-family\")?,\n        monospace_family: input.opt_value_from_str(\"--monospace-family\")?,\n        font_files: input.values_from_str(\"--use-font-file\")?,\n        font_dirs: input.values_from_str(\"--use-fonts-dir\")?,\n        skip_system_fonts: input.contains(\"--skip-system-fonts\"),\n        list_fonts: input.contains(\"--list-fonts\"),\n\n        query_all: input.contains(\"--query-all\"),\n        export_id: input.opt_value_from_str(\"--export-id\")?,\n        export_area_page: input.contains(\"--export-area-page\"),\n\n        export_area_drawing: input.contains(\"--export-area-drawing\"),\n        style_sheet: input.opt_value_from_str(\"--stylesheet\").unwrap_or_default(),\n\n        perf: input.contains(\"--perf\"),\n        quiet: input.contains(\"--quiet\"),\n\n        input: input.opt_free_from_str()?,\n        output: input.opt_free_from_str()?,\n    })\n}\n\nfn parse_dpi(s: &str) -> Result<u32, String> {\n    let n: u32 = s.parse().map_err(|_| \"invalid number\")?;\n\n    if (10..=4000).contains(&n) {\n        Ok(n)\n    } else {\n        Err(\"DPI out of bounds\".to_string())\n    }\n}\n\nfn parse_length(s: &str) -> Result<u32, String> {\n    let n: u32 = s.parse().map_err(|_| \"invalid length\")?;\n\n    if n > 0 {\n        Ok(n)\n    } else {\n        Err(\"LENGTH cannot be zero\".to_string())\n    }\n}\n\nfn parse_zoom(s: &str) -> Result<f32, String> {\n    let n: f32 = s.parse().map_err(|_| \"invalid zoom factor\")?;\n\n    if n > 0.0 {\n        Ok(n)\n    } else {\n        Err(\"ZOOM should be positive\".to_string())\n    }\n}\n\nfn parse_font_size(s: &str) -> Result<u32, String> {\n    let n: u32 = s.parse().map_err(|_| \"invalid number\")?;\n\n    if n > 0 && n <= 192 {\n        Ok(n)\n    } else {\n        Err(\"font size out of bounds\".to_string())\n    }\n}\n\nfn parse_languages(s: &str) -> Result<Vec<String>, String> {\n    let mut langs = Vec::new();\n    for lang in s.split(',') {\n        langs.push(lang.trim().to_string());\n    }\n\n    if langs.is_empty() {\n        return Err(\"languages list cannot be empty\".to_string());\n    }\n\n    Ok(langs)\n}\n\n#[derive(Clone, PartialEq, Debug)]\nenum InputFrom {\n    Stdin,\n    File(path::PathBuf),\n}\n\n#[derive(Clone, PartialEq, Debug)]\nenum OutputTo {\n    Stdout,\n    File(path::PathBuf),\n}\n\n#[derive(Clone, Copy, PartialEq, Debug)]\nenum FitTo {\n    /// Keep original size.\n    Original,\n    /// Scale to width.\n    Width(u32),\n    /// Scale to height.\n    Height(u32),\n    /// Scale to size.\n    Size(u32, u32),\n    /// Zoom by factor.\n    Zoom(f32),\n}\n\nimpl FitTo {\n    fn fit_to_size(&self, size: tiny_skia::IntSize) -> Option<tiny_skia::IntSize> {\n        match *self {\n            FitTo::Original => Some(size),\n            FitTo::Width(w) => size.scale_to_width(w),\n            FitTo::Height(h) => size.scale_to_height(h),\n            FitTo::Size(w, h) => tiny_skia::IntSize::from_wh(w, h).map(|s| size.scale_to(s)),\n            FitTo::Zoom(z) => size.scale_by(z),\n        }\n    }\n\n    fn fit_to_transform(&self, size: tiny_skia::IntSize) -> tiny_skia::Transform {\n        let size1 = size.to_size();\n        let size2 = match self.fit_to_size(size) {\n            Some(v) => v.to_size(),\n            None => return tiny_skia::Transform::default(),\n        };\n        tiny_skia::Transform::from_scale(\n            size2.width() / size1.width(),\n            size2.height() / size1.height(),\n        )\n    }\n}\n\nfn list_fonts(args: &CliArgs) {\n    let mut fontdb = fontdb::Database::new();\n    load_fonts(args, &mut fontdb);\n\n    use fontdb::Family;\n    println!(\"serif: {}\", fontdb.family_name(&Family::Serif));\n    println!(\"sans-serif: {}\", fontdb.family_name(&Family::SansSerif));\n    println!(\"cursive: {}\", fontdb.family_name(&Family::Cursive));\n    println!(\"fantasy: {}\", fontdb.family_name(&Family::Fantasy));\n    println!(\"monospace: {}\", fontdb.family_name(&Family::Monospace));\n\n    for face in fontdb.faces() {\n        if let fontdb::Source::File(path) = &face.source {\n            let families: Vec<_> = face\n                .families\n                .iter()\n                .map(|f| format!(\"{} ({}, {})\", f.0, f.1.primary_language(), f.1.region()))\n                .collect();\n\n            println!(\n                \"{}: '{}', {}, {:?}, {:?}, {:?}\",\n                path.display(),\n                families.join(\"', '\"),\n                face.index,\n                face.style,\n                face.weight.0,\n                face.stretch\n            );\n        }\n    }\n}\n\nstruct Args {\n    in_svg: InputFrom,\n    out_png: Option<OutputTo>,\n    query_all: bool,\n    export_id: Option<String>,\n    export_area_page: bool,\n    export_area_drawing: bool,\n    perf: bool,\n    quiet: bool,\n    usvg: usvg::Options<'static>,\n    fit_to: FitTo,\n    background: Option<svgtypes::Color>,\n    raw_args: CliArgs, // TODO: find a better way\n}\n\nfn parse_args() -> Result<Args, String> {\n    let args = collect_args().map_err(|e| e.to_string())?;\n\n    if args.list_fonts {\n        list_fonts(&args);\n        std::process::exit(0);\n    }\n\n    let (in_svg, out_png) = {\n        let in_svg = match args.input {\n            Some(ref v) => v,\n            None => return Err(\"input file is missing\".to_string()),\n        };\n\n        let svg_from = if in_svg == \"-\" {\n            InputFrom::Stdin\n        } else if in_svg == \"-c\" {\n            return Err(\"-c should be set after input\".to_string());\n        } else {\n            InputFrom::File(in_svg.into())\n        };\n\n        let out_png = if let Some(ref out_png) = args.output {\n            if out_png == \"-c\" {\n                Some(OutputTo::Stdout)\n            } else {\n                Some(OutputTo::File(out_png.into()))\n            }\n        } else {\n            None\n        };\n\n        (svg_from, out_png)\n    };\n\n    if !args.query_all && out_png.is_none() {\n        return Err(\"<out-png> must be set\".to_string());\n    }\n\n    if in_svg == InputFrom::Stdin && args.resources_dir.is_none() {\n        eprintln!(\"Warning: Make sure to set --resources-dir when reading SVG from stdin.\");\n    }\n\n    if args.export_area_page && args.export_id.is_none() {\n        eprintln!(\"Warning: --export-area-page has no effect without --export-id.\");\n    }\n\n    if args.export_area_drawing && args.export_id.is_some() {\n        eprintln!(\"Warning: --export-area-drawing has no effect when --export-id is set.\");\n    }\n\n    let export_id = args.export_id.as_ref().map(|v| v.to_string());\n\n    let mut fit_to = FitTo::Original;\n    let mut default_size = usvg::Size::from_wh(100.0, 100.0).unwrap();\n    if let (Some(w), Some(h)) = (args.width, args.height) {\n        default_size = usvg::Size::from_wh(w as f32, h as f32).unwrap();\n        fit_to = FitTo::Size(w, h);\n    } else if let Some(w) = args.width {\n        default_size = usvg::Size::from_wh(w as f32, 100.0).unwrap();\n        fit_to = FitTo::Width(w);\n    } else if let Some(h) = args.height {\n        default_size = usvg::Size::from_wh(100.0, h as f32).unwrap();\n        fit_to = FitTo::Height(h);\n    } else if let Some(z) = args.zoom {\n        fit_to = FitTo::Zoom(z);\n    }\n\n    let resources_dir = match args.resources_dir {\n        Some(ref v) => Some(v.clone()),\n        None => {\n            if let InputFrom::File(ref input) = in_svg {\n                // Get input file absolute directory.\n                std::fs::canonicalize(input)\n                    .ok()\n                    .and_then(|p| p.parent().map(|p| p.to_path_buf()))\n            } else {\n                None\n            }\n        }\n    };\n\n    let style_sheet = match args.style_sheet.as_ref() {\n        Some(p) => Some(\n            std::fs::read(p)\n                .ok()\n                .and_then(|s| std::str::from_utf8(&s).ok().map(|s| s.to_string()))\n                .ok_or(\"failed to read stylesheet\".to_string())?,\n        ),\n        None => None,\n    };\n\n    let usvg = usvg::Options {\n        resources_dir,\n        dpi: args.dpi as f32,\n        font_family: args\n            .font_family\n            .clone()\n            .unwrap_or_else(|| \"Times New Roman\".to_string()),\n        font_size: args.font_size as f32,\n        languages: args.languages.clone(),\n        shape_rendering: args.shape_rendering,\n        text_rendering: args.text_rendering,\n        image_rendering: args.image_rendering,\n        default_size,\n        image_href_resolver: usvg::ImageHrefResolver::default(),\n        font_resolver: usvg::FontResolver::default(),\n        fontdb: Arc::new(fontdb::Database::new()),\n        style_sheet,\n    };\n\n    Ok(Args {\n        in_svg,\n        out_png,\n        query_all: args.query_all,\n        export_id,\n        export_area_page: args.export_area_page,\n        export_area_drawing: args.export_area_drawing,\n        perf: args.perf,\n        quiet: args.quiet,\n        usvg,\n        fit_to,\n        background: args.background,\n        raw_args: args,\n    })\n}\n\nfn load_fonts(args: &CliArgs, fontdb: &mut fontdb::Database) {\n    if !args.skip_system_fonts {\n        fontdb.load_system_fonts();\n    }\n\n    for path in &args.font_files {\n        if let Err(e) = fontdb.load_font_file(path) {\n            log::warn!(\"Failed to load '{}' cause {}.\", path.display(), e);\n        }\n    }\n\n    for path in &args.font_dirs {\n        fontdb.load_fonts_dir(path);\n    }\n\n    fontdb.set_serif_family(args.serif_family.as_deref().unwrap_or(\"Times New Roman\"));\n    fontdb.set_sans_serif_family(args.sans_serif_family.as_deref().unwrap_or(\"Arial\"));\n    fontdb.set_cursive_family(args.cursive_family.as_deref().unwrap_or(\"Comic Sans MS\"));\n    fontdb.set_fantasy_family(args.fantasy_family.as_deref().unwrap_or(\"Impact\"));\n    fontdb.set_monospace_family(args.monospace_family.as_deref().unwrap_or(\"Courier New\"));\n}\n\nfn query_all(tree: &usvg::Tree) -> Result<(), String> {\n    let count = query_all_impl(tree.root());\n\n    if count == 0 {\n        return Err(\"the file has no valid ID's\".to_string());\n    }\n\n    Ok(())\n}\n\nfn query_all_impl(parent: &usvg::Group) -> usize {\n    let mut count = 0;\n    for node in parent.children() {\n        if node.id().is_empty() {\n            if let usvg::Node::Group(group) = node {\n                count += query_all_impl(group);\n            }\n            continue;\n        }\n\n        count += 1;\n\n        fn round_len(v: f32) -> f32 {\n            (v * 1000.0).round() / 1000.0\n        }\n\n        let bbox = node\n            .abs_layer_bounding_box()\n            .map(|r| r.to_rect())\n            .unwrap_or(node.abs_bounding_box());\n\n        println!(\n            \"{},{},{},{},{}\",\n            node.id(),\n            round_len(bbox.x()),\n            round_len(bbox.y()),\n            round_len(bbox.width()),\n            round_len(bbox.height())\n        );\n\n        if let usvg::Node::Group(group) = node {\n            count += query_all_impl(group);\n        }\n    }\n\n    count\n}\n\nfn render_svg(args: &Args, tree: &usvg::Tree) -> Result<tiny_skia::Pixmap, String> {\n    let now = std::time::Instant::now();\n\n    let img = if let Some(ref id) = args.export_id {\n        let node = match tree.node_by_id(id) {\n            Some(node) => node,\n            None => return Err(format!(\"SVG doesn't have '{}' ID\", id)),\n        };\n\n        let bbox = node.abs_layer_bounding_box().ok_or(\"node has zero size\")?;\n\n        let size = args\n            .fit_to\n            .fit_to_size(bbox.size().to_int_size())\n            .ok_or(\"target size is zero\")?;\n\n        // Pixmap's width is limited by i32::MAX/4, we handle the creation error.\n        let mut pixmap =\n            tiny_skia::Pixmap::new(size.width(), size.height()).ok_or(\"cannot create pixmap\")?;\n\n        if !args.export_area_page {\n            if let Some(background) = args.background {\n                pixmap.fill(svg_to_skia_color(background));\n            }\n        }\n\n        let ts = args.fit_to.fit_to_transform(tree.size().to_int_size());\n\n        resvg::render_node(node, ts, &mut pixmap.as_mut());\n\n        if args.export_area_page {\n            // TODO: add offset support to render_node() so we would not need an additional pixmap\n\n            let size = args\n                .fit_to\n                .fit_to_size(tree.size().to_int_size())\n                .ok_or(\"target size is zero\")?;\n\n            // Pixmap's width is limited by i32::MAX/4, we handle the creation error.\n            let mut page_pixmap = tiny_skia::Pixmap::new(size.width(), size.height())\n                .ok_or(\"cannot create pixmap\")?;\n\n            if let Some(background) = args.background {\n                page_pixmap.fill(svg_to_skia_color(background));\n            }\n\n            page_pixmap.draw_pixmap(\n                bbox.x() as i32,\n                bbox.y() as i32,\n                pixmap.as_ref(),\n                &tiny_skia::PixmapPaint::default(),\n                tiny_skia::Transform::default(),\n                None,\n            );\n            page_pixmap\n        } else {\n            pixmap\n        }\n    } else {\n        let size = args\n            .fit_to\n            .fit_to_size(tree.size().to_int_size())\n            .ok_or(\"target size is zero\")?;\n\n        // Pixmap's width is limited by i32::MAX/4, we handle the creation error.\n        let mut pixmap =\n            tiny_skia::Pixmap::new(size.width(), size.height()).ok_or(\"cannot create pixmap\")?;\n\n        if let Some(background) = args.background {\n            pixmap.fill(svg_to_skia_color(background));\n        }\n\n        let ts = args.fit_to.fit_to_transform(tree.size().to_int_size());\n\n        resvg::render(tree, ts, &mut pixmap.as_mut());\n\n        if args.export_area_drawing {\n            trim_pixmap(tree, ts, &pixmap).unwrap_or(pixmap)\n        } else {\n            pixmap\n        }\n    };\n\n    if args.perf {\n        let elapsed = now.elapsed().as_micros() as f64 / 1000.0;\n        println!(\"Rendering: {:.2}ms\", elapsed);\n    }\n\n    Ok(img)\n}\n\nfn trim_pixmap(\n    tree: &usvg::Tree,\n    transform: tiny_skia::Transform,\n    pixmap: &tiny_skia::Pixmap,\n) -> Option<tiny_skia::Pixmap> {\n    let content_area = tree.root().layer_bounding_box();\n\n    let limit = tiny_skia::IntRect::from_xywh(0, 0, pixmap.width(), pixmap.height()).unwrap();\n\n    let content_area = content_area.transform(transform)?.to_int_rect();\n    let content_area = fit_to_rect(content_area, limit);\n    let content_area = tiny_skia::IntRect::from_xywh(\n        content_area.x(),\n        content_area.y(),\n        content_area.width(),\n        content_area.height(),\n    )?;\n\n    pixmap.clone_rect(content_area)\n}\n\n/// Fits the current rect into the specified bounds.\nfn fit_to_rect(r: tiny_skia::IntRect, bounds: tiny_skia::IntRect) -> tiny_skia::IntRect {\n    let mut left = r.left();\n    if left < bounds.left() {\n        left = bounds.left();\n    }\n\n    let mut top = r.top();\n    if top < bounds.top() {\n        top = bounds.top();\n    }\n\n    let mut right = r.right();\n    if right > bounds.right() {\n        right = bounds.right();\n    }\n\n    let mut bottom = r.bottom();\n    if bottom > bounds.bottom() {\n        bottom = bounds.bottom();\n    }\n\n    tiny_skia::IntRect::from_ltrb(left, top, right, bottom).unwrap()\n}\n\nfn svg_to_skia_color(color: svgtypes::Color) -> tiny_skia::Color {\n    tiny_skia::Color::from_rgba8(color.red, color.green, color.blue, color.alpha)\n}\n\n/// A simple stderr logger.\nstatic LOGGER: SimpleLogger = SimpleLogger;\nstruct SimpleLogger;\nimpl log::Log for SimpleLogger {\n    fn enabled(&self, metadata: &log::Metadata) -> bool {\n        metadata.level() <= log::LevelFilter::Warn\n    }\n\n    fn log(&self, record: &log::Record) {\n        if self.enabled(record.metadata()) {\n            let target = if !record.target().is_empty() {\n                record.target()\n            } else {\n                record.module_path().unwrap_or_default()\n            };\n\n            let line = record.line().unwrap_or(0);\n            let args = record.args();\n\n            match record.level() {\n                log::Level::Error => eprintln!(\"Error (in {}:{}): {}\", target, line, args),\n                log::Level::Warn => eprintln!(\"Warning (in {}:{}): {}\", target, line, args),\n                log::Level::Info => eprintln!(\"Info (in {}:{}): {}\", target, line, args),\n                log::Level::Debug => eprintln!(\"Debug (in {}:{}): {}\", target, line, args),\n                log::Level::Trace => eprintln!(\"Trace (in {}:{}): {}\", target, line, args),\n            }\n        }\n    }\n\n    fn flush(&self) {}\n}\n"
  },
  {
    "path": "crates/resvg/src/mask.rs",
    "content": "// Copyright 2019 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse crate::render::Context;\n\npub fn apply(\n    mask: &usvg::Mask,\n    ctx: &Context,\n    transform: tiny_skia::Transform,\n    pixmap: &mut tiny_skia::Pixmap,\n) {\n    if mask.root().children().is_empty() {\n        pixmap.fill(tiny_skia::Color::TRANSPARENT);\n        return;\n    }\n\n    let mut mask_pixmap = tiny_skia::Pixmap::new(pixmap.width(), pixmap.height()).unwrap();\n\n    {\n        // TODO: only when needed\n        // Mask has to be clipped by mask.region\n        let mut alpha_mask = tiny_skia::Mask::new(pixmap.width(), pixmap.height()).unwrap();\n        alpha_mask.fill_path(\n            &tiny_skia::PathBuilder::from_rect(mask.rect().to_rect()),\n            tiny_skia::FillRule::Winding,\n            true,\n            transform,\n        );\n\n        crate::render::render_nodes(mask.root(), ctx, transform, &mut mask_pixmap.as_mut());\n\n        mask_pixmap.apply_mask(&alpha_mask);\n    }\n\n    if let Some(mask) = mask.mask() {\n        self::apply(mask, ctx, transform, pixmap);\n    }\n\n    let mask_type = match mask.kind() {\n        usvg::MaskType::Luminance => tiny_skia::MaskType::Luminance,\n        usvg::MaskType::Alpha => tiny_skia::MaskType::Alpha,\n    };\n\n    let mask = tiny_skia::Mask::from_pixmap(mask_pixmap.as_ref(), mask_type);\n    pixmap.apply_mask(&mask);\n}\n"
  },
  {
    "path": "crates/resvg/src/path.rs",
    "content": "// Copyright 2019 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse crate::render::Context;\n\npub fn render(\n    path: &usvg::Path,\n    blend_mode: tiny_skia::BlendMode,\n    ctx: &Context,\n    transform: tiny_skia::Transform,\n    pixmap: &mut tiny_skia::PixmapMut,\n) {\n    if !path.is_visible() {\n        return;\n    }\n\n    if path.paint_order() == usvg::PaintOrder::FillAndStroke {\n        fill_path(path, blend_mode, ctx, transform, pixmap);\n        stroke_path(path, blend_mode, ctx, transform, pixmap);\n    } else {\n        stroke_path(path, blend_mode, ctx, transform, pixmap);\n        fill_path(path, blend_mode, ctx, transform, pixmap);\n    }\n}\n\npub fn fill_path(\n    path: &usvg::Path,\n    blend_mode: tiny_skia::BlendMode,\n    ctx: &Context,\n    transform: tiny_skia::Transform,\n    pixmap: &mut tiny_skia::PixmapMut,\n) -> Option<()> {\n    let fill = path.fill()?;\n\n    // Horizontal and vertical lines cannot be filled. Skip.\n    if path.data().bounds().width() == 0.0 || path.data().bounds().height() == 0.0 {\n        return None;\n    }\n\n    let rule = match fill.rule() {\n        usvg::FillRule::NonZero => tiny_skia::FillRule::Winding,\n        usvg::FillRule::EvenOdd => tiny_skia::FillRule::EvenOdd,\n    };\n\n    let pattern_pixmap;\n    let mut paint = tiny_skia::Paint::default();\n    match fill.paint() {\n        usvg::Paint::Color(c) => {\n            paint.set_color_rgba8(c.red, c.green, c.blue, fill.opacity().to_u8());\n        }\n        usvg::Paint::LinearGradient(lg) => {\n            paint.shader = convert_linear_gradient(lg, fill.opacity())?;\n        }\n        usvg::Paint::RadialGradient(rg) => {\n            paint.shader = convert_radial_gradient(rg, fill.opacity())?;\n        }\n        usvg::Paint::Pattern(pattern) => {\n            let (patt_pix, patt_ts) = render_pattern_pixmap(pattern, ctx, transform)?;\n\n            pattern_pixmap = patt_pix;\n            paint.shader = tiny_skia::Pattern::new(\n                pattern_pixmap.as_ref(),\n                tiny_skia::SpreadMode::Repeat,\n                tiny_skia::FilterQuality::Bicubic,\n                fill.opacity().get(),\n                patt_ts,\n            );\n        }\n    }\n    paint.anti_alias = path.rendering_mode().use_shape_antialiasing();\n    paint.blend_mode = blend_mode;\n\n    pixmap.fill_path(path.data(), &paint, rule, transform, None);\n    Some(())\n}\n\nfn stroke_path(\n    path: &usvg::Path,\n    blend_mode: tiny_skia::BlendMode,\n    ctx: &Context,\n    transform: tiny_skia::Transform,\n    pixmap: &mut tiny_skia::PixmapMut,\n) -> Option<()> {\n    let stroke = path.stroke()?;\n    let pattern_pixmap;\n    let mut paint = tiny_skia::Paint::default();\n    match stroke.paint() {\n        usvg::Paint::Color(c) => {\n            paint.set_color_rgba8(c.red, c.green, c.blue, stroke.opacity().to_u8());\n        }\n        usvg::Paint::LinearGradient(lg) => {\n            paint.shader = convert_linear_gradient(lg, stroke.opacity())?;\n        }\n        usvg::Paint::RadialGradient(rg) => {\n            paint.shader = convert_radial_gradient(rg, stroke.opacity())?;\n        }\n        usvg::Paint::Pattern(pattern) => {\n            let (patt_pix, patt_ts) = render_pattern_pixmap(pattern, ctx, transform)?;\n\n            pattern_pixmap = patt_pix;\n            paint.shader = tiny_skia::Pattern::new(\n                pattern_pixmap.as_ref(),\n                tiny_skia::SpreadMode::Repeat,\n                tiny_skia::FilterQuality::Bicubic,\n                stroke.opacity().get(),\n                patt_ts,\n            );\n        }\n    }\n    paint.anti_alias = path.rendering_mode().use_shape_antialiasing();\n    paint.blend_mode = blend_mode;\n\n    pixmap.stroke_path(path.data(), &paint, &stroke.to_tiny_skia(), transform, None);\n\n    Some(())\n}\n\nfn convert_linear_gradient(\n    gradient: &usvg::LinearGradient,\n    opacity: usvg::Opacity,\n) -> Option<tiny_skia::Shader<'_>> {\n    let (mode, points) = convert_base_gradient(gradient, opacity)?;\n\n    let shader = tiny_skia::LinearGradient::new(\n        (gradient.x1(), gradient.y1()).into(),\n        (gradient.x2(), gradient.y2()).into(),\n        points,\n        mode,\n        gradient.transform(),\n    )?;\n\n    Some(shader)\n}\n\nfn convert_radial_gradient(\n    gradient: &usvg::RadialGradient,\n    opacity: usvg::Opacity,\n) -> Option<tiny_skia::Shader<'_>> {\n    let (mode, points) = convert_base_gradient(gradient, opacity)?;\n\n    let shader = tiny_skia::RadialGradient::new(\n        (gradient.fx(), gradient.fy()).into(),\n        gradient.fr().get(),\n        (gradient.cx(), gradient.cy()).into(),\n        gradient.r().get(),\n        points,\n        mode,\n        gradient.transform(),\n    )?;\n\n    Some(shader)\n}\n\nfn convert_base_gradient(\n    gradient: &usvg::BaseGradient,\n    opacity: usvg::Opacity,\n) -> Option<(tiny_skia::SpreadMode, Vec<tiny_skia::GradientStop>)> {\n    let mode = match gradient.spread_method() {\n        usvg::SpreadMethod::Pad => tiny_skia::SpreadMode::Pad,\n        usvg::SpreadMethod::Reflect => tiny_skia::SpreadMode::Reflect,\n        usvg::SpreadMethod::Repeat => tiny_skia::SpreadMode::Repeat,\n    };\n\n    let mut points = Vec::with_capacity(gradient.stops().len());\n    for stop in gradient.stops() {\n        let alpha = stop.opacity() * opacity;\n        let color = tiny_skia::Color::from_rgba8(\n            stop.color().red,\n            stop.color().green,\n            stop.color().blue,\n            alpha.to_u8(),\n        );\n        points.push(tiny_skia::GradientStop::new(stop.offset().get(), color));\n    }\n\n    Some((mode, points))\n}\n\nfn render_pattern_pixmap(\n    pattern: &usvg::Pattern,\n    ctx: &Context,\n    transform: tiny_skia::Transform,\n) -> Option<(tiny_skia::Pixmap, tiny_skia::Transform)> {\n    let (sx, sy) = {\n        let ts2 = transform.pre_concat(pattern.transform());\n        ts2.get_scale()\n    };\n\n    let rect = pattern.rect();\n    let img_size = tiny_skia::IntSize::from_wh(\n        (rect.width() * sx).round() as u32,\n        (rect.height() * sy).round() as u32,\n    )?;\n    let mut pixmap = tiny_skia::Pixmap::new(img_size.width(), img_size.height())?;\n\n    let transform = tiny_skia::Transform::from_scale(sx, sy);\n    crate::render::render_nodes(pattern.root(), ctx, transform, &mut pixmap.as_mut());\n\n    let mut ts = tiny_skia::Transform::default();\n    ts = ts.pre_concat(pattern.transform());\n    ts = ts.pre_translate(rect.x(), rect.y());\n    ts = ts.pre_scale(1.0 / sx, 1.0 / sy);\n\n    Some((pixmap, ts))\n}\n"
  },
  {
    "path": "crates/resvg/src/render.rs",
    "content": "// Copyright 2018 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse crate::OptionLog;\n\npub struct Context {\n    pub max_bbox: tiny_skia::IntRect,\n}\n\npub fn render_nodes(\n    parent: &usvg::Group,\n    ctx: &Context,\n    transform: tiny_skia::Transform,\n    pixmap: &mut tiny_skia::PixmapMut,\n) {\n    for node in parent.children() {\n        render_node(node, ctx, transform, pixmap);\n    }\n}\n\npub fn render_node(\n    node: &usvg::Node,\n    ctx: &Context,\n    transform: tiny_skia::Transform,\n    pixmap: &mut tiny_skia::PixmapMut,\n) {\n    match node {\n        usvg::Node::Group(group) => {\n            render_group(group, ctx, transform, pixmap);\n        }\n        usvg::Node::Path(path) => {\n            crate::path::render(\n                path,\n                tiny_skia::BlendMode::SourceOver,\n                ctx,\n                transform,\n                pixmap,\n            );\n        }\n        usvg::Node::Image(image) => {\n            crate::image::render(image, transform, pixmap);\n        }\n        usvg::Node::Text(text) => {\n            render_group(text.flattened(), ctx, transform, pixmap);\n        }\n    }\n}\n\nfn render_group(\n    group: &usvg::Group,\n    ctx: &Context,\n    transform: tiny_skia::Transform,\n    pixmap: &mut tiny_skia::PixmapMut,\n) -> Option<()> {\n    let transform = transform.pre_concat(group.transform());\n\n    if !group.should_isolate() {\n        render_nodes(group, ctx, transform, pixmap);\n        return Some(());\n    }\n\n    let bbox = group.layer_bounding_box().transform(transform)?;\n\n    let mut ibbox = if group.filters().is_empty() {\n        // Convert group bbox into an integer one, expanding each side outwards by 2px\n        // to make sure that anti-aliased pixels would not be clipped.\n        tiny_skia::IntRect::from_xywh(\n            (bbox.x().floor() as i32).checked_sub(2)?,\n            (bbox.y().floor() as i32).checked_sub(2)?,\n            (bbox.width().ceil() as u32).checked_add(4)?,\n            (bbox.height().ceil() as u32).checked_add(4)?,\n        )?\n    } else {\n        // The bounding box for groups with filters is special and should not be expanded by 2px,\n        // because it's already acting as a clipping region.\n        let bbox = bbox.to_int_rect();\n        // Make sure our filter region is not bigger than 4x the canvas size.\n        // This is required mainly to prevent huge filter regions that would tank the performance.\n        // It should not affect the final result in any way.\n        crate::geom::fit_to_rect(bbox, ctx.max_bbox)?\n    };\n\n    // Make sure our layer is not bigger than 4x the canvas size.\n    // This is required to prevent huge layers.\n    if group.filters().is_empty() {\n        ibbox = crate::geom::fit_to_rect(ibbox, ctx.max_bbox)?;\n    }\n\n    let shift_ts = {\n        // Original shift.\n        let mut dx = bbox.x();\n        let mut dy = bbox.y();\n\n        // Account for subpixel positioned layers.\n        dx -= bbox.x() - ibbox.x() as f32;\n        dy -= bbox.y() - ibbox.y() as f32;\n\n        tiny_skia::Transform::from_translate(-dx, -dy)\n    };\n\n    let transform = shift_ts.pre_concat(transform);\n\n    let mut sub_pixmap = tiny_skia::Pixmap::new(ibbox.width(), ibbox.height())\n        .log_none(|| log::warn!(\"Failed to allocate a group layer for: {:?}.\", ibbox))?;\n\n    render_nodes(group, ctx, transform, &mut sub_pixmap.as_mut());\n\n    if !group.filters().is_empty() {\n        for filter in group.filters() {\n            crate::filter::apply(filter, transform, &mut sub_pixmap);\n        }\n    }\n\n    if let Some(clip_path) = group.clip_path() {\n        crate::clip::apply(clip_path, transform, &mut sub_pixmap);\n    }\n\n    if let Some(mask) = group.mask() {\n        crate::mask::apply(mask, ctx, transform, &mut sub_pixmap);\n    }\n\n    let paint = tiny_skia::PixmapPaint {\n        opacity: group.opacity().get(),\n        blend_mode: convert_blend_mode(group.blend_mode()),\n        quality: tiny_skia::FilterQuality::Nearest,\n    };\n\n    pixmap.draw_pixmap(\n        ibbox.x(),\n        ibbox.y(),\n        sub_pixmap.as_ref(),\n        &paint,\n        tiny_skia::Transform::identity(),\n        None,\n    );\n\n    Some(())\n}\n\npub fn convert_blend_mode(mode: usvg::BlendMode) -> tiny_skia::BlendMode {\n    match mode {\n        usvg::BlendMode::Normal => tiny_skia::BlendMode::SourceOver,\n        usvg::BlendMode::Multiply => tiny_skia::BlendMode::Multiply,\n        usvg::BlendMode::Screen => tiny_skia::BlendMode::Screen,\n        usvg::BlendMode::Overlay => tiny_skia::BlendMode::Overlay,\n        usvg::BlendMode::Darken => tiny_skia::BlendMode::Darken,\n        usvg::BlendMode::Lighten => tiny_skia::BlendMode::Lighten,\n        usvg::BlendMode::ColorDodge => tiny_skia::BlendMode::ColorDodge,\n        usvg::BlendMode::ColorBurn => tiny_skia::BlendMode::ColorBurn,\n        usvg::BlendMode::HardLight => tiny_skia::BlendMode::HardLight,\n        usvg::BlendMode::SoftLight => tiny_skia::BlendMode::SoftLight,\n        usvg::BlendMode::Difference => tiny_skia::BlendMode::Difference,\n        usvg::BlendMode::Exclusion => tiny_skia::BlendMode::Exclusion,\n        usvg::BlendMode::Hue => tiny_skia::BlendMode::Hue,\n        usvg::BlendMode::Saturation => tiny_skia::BlendMode::Saturation,\n        usvg::BlendMode::Color => tiny_skia::BlendMode::Color,\n        usvg::BlendMode::Luminosity => tiny_skia::BlendMode::Luminosity,\n    }\n}\n"
  },
  {
    "path": "crates/resvg/tests/README.md",
    "content": "# SVG tests\n\nThis directory contains a collection of SVG files used during *resvg* regression testing.\n\n## Adding a new test\n\n### Create an SVG file\n\nWe are using SVG files with a fixed, 200x200 viewbox for all tests.\n\nHere is a test file template:\n\n```xml\n<svg id=\"svg1\" viewBox=\"0 0 200 200\" xmlns=\"http://www.w3.org/2000/svg\">\n    <title>My new test</title>\n\n    <!-- replace with an actual SVG data -->\n\n    <!-- image frame -->\n    <rect id=\"frame\" x=\"1\" y=\"1\" width=\"198\" height=\"198\" fill=\"none\" stroke=\"black\"/>\n</svg>\n\n```\n\nGeneral requirements:\n\n1. Each test must test only a single issue.\n1. Each element must have an `id` attribute.\n1. The `title` value must be unique and shorter than 60 characters.<br/>\n   Newlines are not allowed.\n1. Each line in an XML file should be less than 100 characters.\n1. No trailing spaces.\n1. A single trailing newline.\n1. UTF-8 only.\n\nYou could use the `check.py` script to automatically check those requirements.\n\n### Render PNG\n\nAfter the SVG test is finished, you should render it using resvg:\n\n```sh\ncargo run --release -- \\\n    --width 300 \\\n    --skip-system-fonts \\\n    --use-fonts-dir 'tests/fonts' \\\n    --font-family 'Noto Sans' \\\n    --serif-family 'Noto Serif' \\\n    --sans-serif-family 'Noto Sans' \\\n    --cursive-family 'Yellowtail' \\\n    --fantasy-family 'Sedgwick Ave Display' \\\n    --monospace-family 'Noto Mono' \\\n    in.svg out.png\n```\n\n(we are using 300px width to test scaling)\n\nAfter that, you should optimize the resulting PNG using oxipng:\n\n```sh\ncargo install oxipng\noxipng -o 6 -Z out.png\n```\n\nAnd then place it into the `png` dir.\n\n## resvg tests vs resvg-test-suite tests\n\nresvg tests are stored in two repos: this one and in\n[resvg-test-suite](https://github.com/linebender/resvg-test-suite).\nWhich can be a bit confusing.\n\n`resvg-test-suite` is the source of truth. It contains the latest version of the tests\nand intended to help people with writing SVG processing apps.\n`resvg/tests/svg` directory contains the exact copy of `resvg-test-suite/svg`,\nmaybe a bit outdated at times.\nThe major difference is `png` directories. `resvg-test-suite/png` contains reference image.\nThis is how the SVG files should be rendered.\nWhile `resvg/tests/png` contains PNGs rendered by the resvg itself\nand used only for regression testing.\n"
  },
  {
    "path": "crates/resvg/tests/fonts/Amiri-LICENSE-OFL.txt",
    "content": "Copyright (c) 2010-2016, Khaled Hosny (<khaledhosny@eglug.org>)\r\n\r\nThis Font Software is licensed under the SIL Open Font License, Version 1.1.\r\nThis license is copied below, and is also available with a FAQ at:\r\nhttp://scripts.sil.org/OFL\r\n\r\n\r\n-----------------------------------------------------------\r\nSIL OPEN FONT LICENSE Version 1.1 - 26 February 2007\r\n-----------------------------------------------------------\r\n\r\nPREAMBLE\r\nThe goals of the Open Font License (OFL) are to stimulate worldwide\r\ndevelopment of collaborative font projects, to support the font creation\r\nefforts of academic and linguistic communities, and to provide a free and\r\nopen framework in which fonts may be shared and improved in partnership\r\nwith others.\r\n\r\nThe OFL allows the licensed fonts to be used, studied, modified and\r\nredistributed freely as long as they are not sold by themselves. The\r\nfonts, including any derivative works, can be bundled, embedded, \r\nredistributed and/or sold with any software provided that any reserved\r\nnames are not used by derivative works. The fonts and derivatives,\r\nhowever, cannot be released under any other type of license. The\r\nrequirement for fonts to remain under this license does not apply\r\nto any document created using the fonts or their derivatives.\r\n\r\nDEFINITIONS\r\n\"Font Software\" refers to the set of files released by the Copyright\r\nHolder(s) under this license and clearly marked as such. This may\r\ninclude source files, build scripts and documentation.\r\n\r\n\"Reserved Font Name\" refers to any names specified as such after the\r\ncopyright statement(s).\r\n\r\n\"Original Version\" refers to the collection of Font Software components as\r\ndistributed by the Copyright Holder(s).\r\n\r\n\"Modified Version\" refers to any derivative made by adding to, deleting,\r\nor substituting -- in part or in whole -- any of the components of the\r\nOriginal Version, by changing formats or by porting the Font Software to a\r\nnew environment.\r\n\r\n\"Author\" refers to any designer, engineer, programmer, technical\r\nwriter or other person who contributed to the Font Software.\r\n\r\nPERMISSION & CONDITIONS\r\nPermission is hereby granted, free of charge, to any person obtaining\r\na copy of the Font Software, to use, study, copy, merge, embed, modify,\r\nredistribute, and sell modified and unmodified copies of the Font\r\nSoftware, subject to the following conditions:\r\n\r\n1) Neither the Font Software nor any of its individual components,\r\nin Original or Modified Versions, may be sold by itself.\r\n\r\n2) Original or Modified Versions of the Font Software may be bundled,\r\nredistributed and/or sold with any software, provided that each copy\r\ncontains the above copyright notice and this license. These can be\r\nincluded either as stand-alone text files, human-readable headers or\r\nin the appropriate machine-readable metadata fields within text or\r\nbinary files as long as those fields can be easily viewed by the user.\r\n\r\n3) No Modified Version of the Font Software may use the Reserved Font\r\nName(s) unless explicit written permission is granted by the corresponding\r\nCopyright Holder. This restriction only applies to the primary font name as\r\npresented to the users.\r\n\r\n4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font\r\nSoftware shall not be used to promote, endorse or advertise any\r\nModified Version, except to acknowledge the contribution(s) of the\r\nCopyright Holder(s) and the Author(s) or with their explicit written\r\npermission.\r\n\r\n5) The Font Software, modified or unmodified, in part or in whole,\r\nmust be distributed entirely under this license, and must not be\r\ndistributed under any other license. The requirement for fonts to\r\nremain under this license does not apply to any document created\r\nusing the Font Software.\r\n\r\nTERMINATION\r\nThis license becomes null and void if any of the above conditions are\r\nnot met.\r\n\r\nDISCLAIMER\r\nTHE FONT SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF\r\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT\r\nOF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE\r\nCOPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\nINCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL\r\nDAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\nFROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM\r\nOTHER DEALINGS IN THE FONT SOFTWARE.\r\n"
  },
  {
    "path": "crates/resvg/tests/fonts/CFF-and-SBIX-LICENSE-APACHE.txt",
    "content": "Copyright 2019 Simon Cozens. All rights reserved.\r\rLicensed 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\r\r http://www.apache.org/licenses/LICENSE-2.0\r\rUnless 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."
  },
  {
    "path": "crates/resvg/tests/fonts/MPLUS1p-LICENSE-OFL.txt",
    "content": "This Font Software is licensed under the SIL Open Font License, Version 1.1.\r\nThis license is copied below, and is also available with a FAQ at:\r\nhttp://scripts.sil.org/OFL\r\n\r\n\r\n-----------------------------------------------------------\r\nSIL OPEN FONT LICENSE Version 1.1 - 26 February 2007\r\n-----------------------------------------------------------\r\n\r\nPREAMBLE\r\nThe goals of the Open Font License (OFL) are to stimulate worldwide\r\ndevelopment of collaborative font projects, to support the font creation\r\nefforts of academic and linguistic communities, and to provide a free and\r\nopen framework in which fonts may be shared and improved in partnership\r\nwith others.\r\n\r\nThe OFL allows the licensed fonts to be used, studied, modified and\r\nredistributed freely as long as they are not sold by themselves. The\r\nfonts, including any derivative works, can be bundled, embedded, \r\nredistributed and/or sold with any software provided that any reserved\r\nnames are not used by derivative works. The fonts and derivatives,\r\nhowever, cannot be released under any other type of license. The\r\nrequirement for fonts to remain under this license does not apply\r\nto any document created using the fonts or their derivatives.\r\n\r\nDEFINITIONS\r\n\"Font Software\" refers to the set of files released by the Copyright\r\nHolder(s) under this license and clearly marked as such. This may\r\ninclude source files, build scripts and documentation.\r\n\r\n\"Reserved Font Name\" refers to any names specified as such after the\r\ncopyright statement(s).\r\n\r\n\"Original Version\" refers to the collection of Font Software components as\r\ndistributed by the Copyright Holder(s).\r\n\r\n\"Modified Version\" refers to any derivative made by adding to, deleting,\r\nor substituting -- in part or in whole -- any of the components of the\r\nOriginal Version, by changing formats or by porting the Font Software to a\r\nnew environment.\r\n\r\n\"Author\" refers to any designer, engineer, programmer, technical\r\nwriter or other person who contributed to the Font Software.\r\n\r\nPERMISSION & CONDITIONS\r\nPermission is hereby granted, free of charge, to any person obtaining\r\na copy of the Font Software, to use, study, copy, merge, embed, modify,\r\nredistribute, and sell modified and unmodified copies of the Font\r\nSoftware, subject to the following conditions:\r\n\r\n1) Neither the Font Software nor any of its individual components,\r\nin Original or Modified Versions, may be sold by itself.\r\n\r\n2) Original or Modified Versions of the Font Software may be bundled,\r\nredistributed and/or sold with any software, provided that each copy\r\ncontains the above copyright notice and this license. These can be\r\nincluded either as stand-alone text files, human-readable headers or\r\nin the appropriate machine-readable metadata fields within text or\r\nbinary files as long as those fields can be easily viewed by the user.\r\n\r\n3) No Modified Version of the Font Software may use the Reserved Font\r\nName(s) unless explicit written permission is granted by the corresponding\r\nCopyright Holder. This restriction only applies to the primary font name as\r\npresented to the users.\r\n\r\n4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font\r\nSoftware shall not be used to promote, endorse or advertise any\r\nModified Version, except to acknowledge the contribution(s) of the\r\nCopyright Holder(s) and the Author(s) or with their explicit written\r\npermission.\r\n\r\n5) The Font Software, modified or unmodified, in part or in whole,\r\nmust be distributed entirely under this license, and must not be\r\ndistributed under any other license. The requirement for fonts to\r\nremain under this license does not apply to any document created\r\nusing the Font Software.\r\n\r\nTERMINATION\r\nThis license becomes null and void if any of the above conditions are\r\nnot met.\r\n\r\nDISCLAIMER\r\nTHE FONT SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF\r\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT\r\nOF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE\r\nCOPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\nINCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL\r\nDAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\nFROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM\r\nOTHER DEALINGS IN THE FONT SOFTWARE.\r\n"
  },
  {
    "path": "crates/resvg/tests/fonts/Noto-LICENSE-OFL.txt",
    "content": "This Font Software is licensed under the SIL Open Font License,\nVersion 1.1.\n\nThis license is copied below, and is also available with a FAQ at:\nhttp://scripts.sil.org/OFL\n\n-----------------------------------------------------------\nSIL OPEN FONT LICENSE Version 1.1 - 26 February 2007\n-----------------------------------------------------------\n\nPREAMBLE\nThe goals of the Open Font License (OFL) are to stimulate worldwide\ndevelopment of collaborative font projects, to support the font\ncreation efforts of academic and linguistic communities, and to\nprovide a free and open framework in which fonts may be shared and\nimproved in partnership with others.\n\nThe OFL allows the licensed fonts to be used, studied, modified and\nredistributed freely as long as they are not sold by themselves. The\nfonts, including any derivative works, can be bundled, embedded,\nredistributed and/or sold with any software provided that any reserved\nnames are not used by derivative works. The fonts and derivatives,\nhowever, cannot be released under any other type of license. The\nrequirement for fonts to remain under this license does not apply to\nany document created using the fonts or their derivatives.\n\nDEFINITIONS\n\"Font Software\" refers to the set of files released by the Copyright\nHolder(s) under this license and clearly marked as such. This may\ninclude source files, build scripts and documentation.\n\n\"Reserved Font Name\" refers to any names specified as such after the\ncopyright statement(s).\n\n\"Original Version\" refers to the collection of Font Software\ncomponents as distributed by the Copyright Holder(s).\n\n\"Modified Version\" refers to any derivative made by adding to,\ndeleting, or substituting -- in part or in whole -- any of the\ncomponents of the Original Version, by changing formats or by porting\nthe Font Software to a new environment.\n\n\"Author\" refers to any designer, engineer, programmer, technical\nwriter or other person who contributed to the Font Software.\n\nPERMISSION & CONDITIONS\nPermission is hereby granted, free of charge, to any person obtaining\na copy of the Font Software, to use, study, copy, merge, embed,\nmodify, redistribute, and sell modified and unmodified copies of the\nFont Software, subject to the following conditions:\n\n1) Neither the Font Software nor any of its individual components, in\nOriginal or Modified Versions, may be sold by itself.\n\n2) Original or Modified Versions of the Font Software may be bundled,\nredistributed and/or sold with any software, provided that each copy\ncontains the above copyright notice and this license. These can be\nincluded either as stand-alone text files, human-readable headers or\nin the appropriate machine-readable metadata fields within text or\nbinary files as long as those fields can be easily viewed by the user.\n\n3) No Modified Version of the Font Software may use the Reserved Font\nName(s) unless explicit written permission is granted by the\ncorresponding Copyright Holder. This restriction only applies to the\nprimary font name as presented to the users.\n\n4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font\nSoftware shall not be used to promote, endorse or advertise any\nModified Version, except to acknowledge the contribution(s) of the\nCopyright Holder(s) and the Author(s) or with their explicit written\npermission.\n\n5) The Font Software, modified or unmodified, in part or in whole,\nmust be distributed entirely under this license, and must not be\ndistributed under any other license. The requirement for fonts to\nremain under this license does not apply to any document created using\nthe Font Software.\n\nTERMINATION\nThis license becomes null and void if any of the above conditions are\nnot met.\n\nDISCLAIMER\nTHE FONT SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT\nOF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE\nCOPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\nINCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL\nDAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM\nOTHER DEALINGS IN THE FONT SOFTWARE.\n"
  },
  {
    "path": "crates/resvg/tests/fonts/NotoColorEmojiCBDT-LICENSE_APACHE.txt",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License."
  },
  {
    "path": "crates/resvg/tests/fonts/NotoZnamennyMusicalNotation-OFL.txt",
    "content": "Copyright 2023 The Noto Project Authors (https://github.com/notofonts/znamenny)\n\nThis Font Software is licensed under the SIL Open Font License, Version 1.1.\nThis license is copied below, and is also available with a FAQ at:\nhttps://openfontlicense.org\n\n\n-----------------------------------------------------------\nSIL OPEN FONT LICENSE Version 1.1 - 26 February 2007\n-----------------------------------------------------------\n\nPREAMBLE\nThe goals of the Open Font License (OFL) are to stimulate worldwide\ndevelopment of collaborative font projects, to support the font creation\nefforts of academic and linguistic communities, and to provide a free and\nopen framework in which fonts may be shared and improved in partnership\nwith others.\n\nThe OFL allows the licensed fonts to be used, studied, modified and\nredistributed freely as long as they are not sold by themselves. The\nfonts, including any derivative works, can be bundled, embedded, \nredistributed and/or sold with any software provided that any reserved\nnames are not used by derivative works. The fonts and derivatives,\nhowever, cannot be released under any other type of license. The\nrequirement for fonts to remain under this license does not apply\nto any document created using the fonts or their derivatives.\n\nDEFINITIONS\n\"Font Software\" refers to the set of files released by the Copyright\nHolder(s) under this license and clearly marked as such. This may\ninclude source files, build scripts and documentation.\n\n\"Reserved Font Name\" refers to any names specified as such after the\ncopyright statement(s).\n\n\"Original Version\" refers to the collection of Font Software components as\ndistributed by the Copyright Holder(s).\n\n\"Modified Version\" refers to any derivative made by adding to, deleting,\nor substituting -- in part or in whole -- any of the components of the\nOriginal Version, by changing formats or by porting the Font Software to a\nnew environment.\n\n\"Author\" refers to any designer, engineer, programmer, technical\nwriter or other person who contributed to the Font Software.\n\nPERMISSION & CONDITIONS\nPermission is hereby granted, free of charge, to any person obtaining\na copy of the Font Software, to use, study, copy, merge, embed, modify,\nredistribute, and sell modified and unmodified copies of the Font\nSoftware, subject to the following conditions:\n\n1) Neither the Font Software nor any of its individual components,\nin Original or Modified Versions, may be sold by itself.\n\n2) Original or Modified Versions of the Font Software may be bundled,\nredistributed and/or sold with any software, provided that each copy\ncontains the above copyright notice and this license. These can be\nincluded either as stand-alone text files, human-readable headers or\nin the appropriate machine-readable metadata fields within text or\nbinary files as long as those fields can be easily viewed by the user.\n\n3) No Modified Version of the Font Software may use the Reserved Font\nName(s) unless explicit written permission is granted by the corresponding\nCopyright Holder. This restriction only applies to the primary font name as\npresented to the users.\n\n4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font\nSoftware shall not be used to promote, endorse or advertise any\nModified Version, except to acknowledge the contribution(s) of the\nCopyright Holder(s) and the Author(s) or with their explicit written\npermission.\n\n5) The Font Software, modified or unmodified, in part or in whole,\nmust be distributed entirely under this license, and must not be\ndistributed under any other license. The requirement for fonts to\nremain under this license does not apply to any document created\nusing the Font Software.\n\nTERMINATION\nThis license becomes null and void if any of the above conditions are\nnot met.\n\nDISCLAIMER\nTHE FONT SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT\nOF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE\nCOPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\nINCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL\nDAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM\nOTHER DEALINGS IN THE FONT SOFTWARE.\n"
  },
  {
    "path": "crates/resvg/tests/fonts/README.md",
    "content": "How fonts were subsetted:\n\nTwitter Color Emoji\n1. Download: https://github.com/13rac1/twemoji-color-font/releases/download/v14.0.2/TwitterColorEmoji-SVGinOT-14.0.2.zip\n2. 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`\n\nNoto Color Emoji (CBDT)\n1. Download: https://github.com/googlefonts/noto-emoji/blob/main/fonts/NotoColorEmoji.ttf\n2. Run `fonttools subset NotoColorEmoji.ttf --unicodes=\"U+1F600\" --output-file=NotoColorEmojiCBDT.subset.ttf`\n\nNoto COLOR Emoji (COLRv1)\n1. Download: https://fonts.google.com/noto/specimen/Noto+Color+Emoji\n2. Run `fonttools subset NotoColorEmoji-Regular.ttf --unicodes=\"U+1F436,U+1F41D,U+1F313,U+1F973\" --output-file=NotoColorEmojiCOLR.subset.ttf`\n3. Run `fonttools ttx NotoColorEmojiCOLR.subset.ttf`\n4. Go to the <name> section and rename all instances of \"Noto Color Emoji\" to \"Noto Color Emoji COLR\" (so that\nwe can distinguish them from CBDT in tests).\n5. Run `fonttools ttx -f NotoColorEmojiCOLR.subset.ttx`\n\nRoboto Flex (Variable Font)\n1. 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\n2. Run `pyftsubset RobotoFlex*.ttf --unicodes=\"U+0020-007E\" --layout-features='*' --output-file=RobotoFlex.subset.ttf`\n3. Copy OFL license from https://github.com/googlefonts/roboto-flex/blob/main/OFL.txt\n"
  },
  {
    "path": "crates/resvg/tests/fonts/RobotoFlex-LICENSE-OFL.txt",
    "content": "Copyright 2011 The Roboto Flex Project Authors (https://github.com/googlefonts/roboto-flex)\n\nThis Font Software is licensed under the SIL Open Font License, Version 1.1.\nThis license is copied below, and is also available with a FAQ at:\nhttp://scripts.sil.org/OFL\n\n\n-----------------------------------------------------------\nSIL OPEN FONT LICENSE Version 1.1 - 26 February 2007\n-----------------------------------------------------------\n\nPREAMBLE\nThe goals of the Open Font License (OFL) are to stimulate worldwide\ndevelopment of collaborative font projects, to support the font creation\nefforts of academic and linguistic communities, and to provide a free and\nopen framework in which fonts may be shared and improved in partnership\nwith others.\n\nThe OFL allows the licensed fonts to be used, studied, modified and\nredistributed freely as long as they are not sold by themselves. The\nfonts, including any derivative works, can be bundled, embedded, \nredistributed and/or sold with any software provided that any reserved\nnames are not used by derivative works. The fonts and derivatives,\nhowever, cannot be released under any other type of license. The\nrequirement for fonts to remain under this license does not apply\nto any document created using the fonts or their derivatives.\n\nDEFINITIONS\n\"Font Software\" refers to the set of files released by the Copyright\nHolder(s) under this license and clearly marked as such. This may\ninclude source files, build scripts and documentation.\n\n\"Reserved Font Name\" refers to any names specified as such after the\ncopyright statement(s).\n\n\"Original Version\" refers to the collection of Font Software components as\ndistributed by the Copyright Holder(s).\n\n\"Modified Version\" refers to any derivative made by adding to, deleting,\nor substituting -- in part or in whole -- any of the components of the\nOriginal Version, by changing formats or by porting the Font Software to a\nnew environment.\n\n\"Author\" refers to any designer, engineer, programmer, technical\nwriter or other person who contributed to the Font Software.\n\nPERMISSION & CONDITIONS\nPermission is hereby granted, free of charge, to any person obtaining\na copy of the Font Software, to use, study, copy, merge, embed, modify,\nredistribute, and sell modified and unmodified copies of the Font\nSoftware, subject to the following conditions:\n\n1) Neither the Font Software nor any of its individual components,\nin Original or Modified Versions, may be sold by itself.\n\n2) Original or Modified Versions of the Font Software may be bundled,\nredistributed and/or sold with any software, provided that each copy\ncontains the above copyright notice and this license. These can be\nincluded either as stand-alone text files, human-readable headers or\nin the appropriate machine-readable metadata fields within text or\nbinary files as long as those fields can be easily viewed by the user.\n\n3) No Modified Version of the Font Software may use the Reserved Font\nName(s) unless explicit written permission is granted by the corresponding\nCopyright Holder. This restriction only applies to the primary font name as\npresented to the users.\n\n4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font\nSoftware shall not be used to promote, endorse or advertise any\nModified Version, except to acknowledge the contribution(s) of the\nCopyright Holder(s) and the Author(s) or with their explicit written\npermission.\n\n5) The Font Software, modified or unmodified, in part or in whole,\nmust be distributed entirely under this license, and must not be\ndistributed under any other license. The requirement for fonts to\nremain under this license does not apply to any document created\nusing the Font Software.\n\nTERMINATION\nThis license becomes null and void if any of the above conditions are\nnot met.\n\nDISCLAIMER\nTHE FONT SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT\nOF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE\nCOPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\nINCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL\nDAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM\nOTHER DEALINGS IN THE FONT SOFTWARE.\n"
  },
  {
    "path": "crates/resvg/tests/fonts/SedgwickAveDisplay-LICENSE-OFL.txt",
    "content": "Copyright 2017 The Sedgwick Ave Project Authors (https://github.com/googlefonts/sedgwickave)\r\n\r\nThis Font Software is licensed under the SIL Open Font License, Version 1.1.\r\nThis license is copied below, and is also available with a FAQ at:\r\nhttp://scripts.sil.org/OFL\r\n\r\n\r\n-----------------------------------------------------------\r\nSIL OPEN FONT LICENSE Version 1.1 - 26 February 2007\r\n-----------------------------------------------------------\r\n\r\nPREAMBLE\r\nThe goals of the Open Font License (OFL) are to stimulate worldwide\r\ndevelopment of collaborative font projects, to support the font creation\r\nefforts of academic and linguistic communities, and to provide a free and\r\nopen framework in which fonts may be shared and improved in partnership\r\nwith others.\r\n\r\nThe OFL allows the licensed fonts to be used, studied, modified and\r\nredistributed freely as long as they are not sold by themselves. The\r\nfonts, including any derivative works, can be bundled, embedded, \r\nredistributed and/or sold with any software provided that any reserved\r\nnames are not used by derivative works. The fonts and derivatives,\r\nhowever, cannot be released under any other type of license. The\r\nrequirement for fonts to remain under this license does not apply\r\nto any document created using the fonts or their derivatives.\r\n\r\nDEFINITIONS\r\n\"Font Software\" refers to the set of files released by the Copyright\r\nHolder(s) under this license and clearly marked as such. This may\r\ninclude source files, build scripts and documentation.\r\n\r\n\"Reserved Font Name\" refers to any names specified as such after the\r\ncopyright statement(s).\r\n\r\n\"Original Version\" refers to the collection of Font Software components as\r\ndistributed by the Copyright Holder(s).\r\n\r\n\"Modified Version\" refers to any derivative made by adding to, deleting,\r\nor substituting -- in part or in whole -- any of the components of the\r\nOriginal Version, by changing formats or by porting the Font Software to a\r\nnew environment.\r\n\r\n\"Author\" refers to any designer, engineer, programmer, technical\r\nwriter or other person who contributed to the Font Software.\r\n\r\nPERMISSION & CONDITIONS\r\nPermission is hereby granted, free of charge, to any person obtaining\r\na copy of the Font Software, to use, study, copy, merge, embed, modify,\r\nredistribute, and sell modified and unmodified copies of the Font\r\nSoftware, subject to the following conditions:\r\n\r\n1) Neither the Font Software nor any of its individual components,\r\nin Original or Modified Versions, may be sold by itself.\r\n\r\n2) Original or Modified Versions of the Font Software may be bundled,\r\nredistributed and/or sold with any software, provided that each copy\r\ncontains the above copyright notice and this license. These can be\r\nincluded either as stand-alone text files, human-readable headers or\r\nin the appropriate machine-readable metadata fields within text or\r\nbinary files as long as those fields can be easily viewed by the user.\r\n\r\n3) No Modified Version of the Font Software may use the Reserved Font\r\nName(s) unless explicit written permission is granted by the corresponding\r\nCopyright Holder. This restriction only applies to the primary font name as\r\npresented to the users.\r\n\r\n4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font\r\nSoftware shall not be used to promote, endorse or advertise any\r\nModified Version, except to acknowledge the contribution(s) of the\r\nCopyright Holder(s) and the Author(s) or with their explicit written\r\npermission.\r\n\r\n5) The Font Software, modified or unmodified, in part or in whole,\r\nmust be distributed entirely under this license, and must not be\r\ndistributed under any other license. The requirement for fonts to\r\nremain under this license does not apply to any document created\r\nusing the Font Software.\r\n\r\nTERMINATION\r\nThis license becomes null and void if any of the above conditions are\r\nnot met.\r\n\r\nDISCLAIMER\r\nTHE FONT SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF\r\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT\r\nOF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE\r\nCOPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\nINCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL\r\nDAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\nFROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM\r\nOTHER DEALINGS IN THE FONT SOFTWARE.\r\n"
  },
  {
    "path": "crates/resvg/tests/fonts/SourceSansPro-LICENSE-OFL.md",
    "content": "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.\r\n\r\nThis Font Software is licensed under the SIL Open Font License, Version 1.1.\r\n\r\nThis license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL\r\n\r\n\r\n-----------------------------------------------------------\r\nSIL OPEN FONT LICENSE Version 1.1 - 26 February 2007\r\n-----------------------------------------------------------\r\n\r\nPREAMBLE\r\nThe goals of the Open Font License (OFL) are to stimulate worldwide\r\ndevelopment of collaborative font projects, to support the font creation\r\nefforts of academic and linguistic communities, and to provide a free and\r\nopen framework in which fonts may be shared and improved in partnership\r\nwith others.\r\n\r\nThe OFL allows the licensed fonts to be used, studied, modified and\r\nredistributed freely as long as they are not sold by themselves. The\r\nfonts, including any derivative works, can be bundled, embedded, \r\nredistributed and/or sold with any software provided that any reserved\r\nnames are not used by derivative works. The fonts and derivatives,\r\nhowever, cannot be released under any other type of license. The\r\nrequirement for fonts to remain under this license does not apply\r\nto any document created using the fonts or their derivatives.\r\n\r\nDEFINITIONS\r\n\"Font Software\" refers to the set of files released by the Copyright\r\nHolder(s) under this license and clearly marked as such. This may\r\ninclude source files, build scripts and documentation.\r\n\r\n\"Reserved Font Name\" refers to any names specified as such after the\r\ncopyright statement(s).\r\n\r\n\"Original Version\" refers to the collection of Font Software components as\r\ndistributed by the Copyright Holder(s).\r\n\r\n\"Modified Version\" refers to any derivative made by adding to, deleting,\r\nor substituting -- in part or in whole -- any of the components of the\r\nOriginal Version, by changing formats or by porting the Font Software to a\r\nnew environment.\r\n\r\n\"Author\" refers to any designer, engineer, programmer, technical\r\nwriter or other person who contributed to the Font Software.\r\n\r\nPERMISSION & CONDITIONS\r\nPermission is hereby granted, free of charge, to any person obtaining\r\na copy of the Font Software, to use, study, copy, merge, embed, modify,\r\nredistribute, and sell modified and unmodified copies of the Font\r\nSoftware, subject to the following conditions:\r\n\r\n1) Neither the Font Software nor any of its individual components,\r\nin Original or Modified Versions, may be sold by itself.\r\n\r\n2) Original or Modified Versions of the Font Software may be bundled,\r\nredistributed and/or sold with any software, provided that each copy\r\ncontains the above copyright notice and this license. These can be\r\nincluded either as stand-alone text files, human-readable headers or\r\nin the appropriate machine-readable metadata fields within text or\r\nbinary files as long as those fields can be easily viewed by the user.\r\n\r\n3) No Modified Version of the Font Software may use the Reserved Font\r\nName(s) unless explicit written permission is granted by the corresponding\r\nCopyright Holder. This restriction only applies to the primary font name as\r\npresented to the users.\r\n\r\n4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font\r\nSoftware shall not be used to promote, endorse or advertise any\r\nModified Version, except to acknowledge the contribution(s) of the\r\nCopyright Holder(s) and the Author(s) or with their explicit written\r\npermission.\r\n\r\n5) The Font Software, modified or unmodified, in part or in whole,\r\nmust be distributed entirely under this license, and must not be\r\ndistributed under any other license. The requirement for fonts to\r\nremain under this license does not apply to any document created\r\nusing the Font Software.\r\n\r\nTERMINATION\r\nThis license becomes null and void if any of the above conditions are\r\nnot met.\r\n\r\nDISCLAIMER\r\nTHE FONT SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF\r\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT\r\nOF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE\r\nCOPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\nINCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL\r\nDAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\nFROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM\r\nOTHER DEALINGS IN THE FONT SOFTWARE.\r\n"
  },
  {
    "path": "crates/resvg/tests/fonts/TwitterColorEmoji-LICENSE-MIT.txt",
    "content": "Applies to \"EmojiOne SVGinOT Font\" code only\nCopyright (c) 2022 Brad Erickson\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\nDAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\nOTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE\nOR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "crates/resvg/tests/fonts/Yellowtail-LICENSE-Apache2.txt",
    "content": "\r\n                                 Apache License\r\n                           Version 2.0, January 2004\r\n                        http://www.apache.org/licenses/\r\n\r\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\r\n\r\n   1. Definitions.\r\n\r\n      \"License\" shall mean the terms and conditions for use, reproduction,\r\n      and distribution as defined by Sections 1 through 9 of this document.\r\n\r\n      \"Licensor\" shall mean the copyright owner or entity authorized by\r\n      the copyright owner that is granting the License.\r\n\r\n      \"Legal Entity\" shall mean the union of the acting entity and all\r\n      other entities that control, are controlled by, or are under common\r\n      control with that entity. For the purposes of this definition,\r\n      \"control\" means (i) the power, direct or indirect, to cause the\r\n      direction or management of such entity, whether by contract or\r\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\r\n      outstanding shares, or (iii) beneficial ownership of such entity.\r\n\r\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\r\n      exercising permissions granted by this License.\r\n\r\n      \"Source\" form shall mean the preferred form for making modifications,\r\n      including but not limited to software source code, documentation\r\n      source, and configuration files.\r\n\r\n      \"Object\" form shall mean any form resulting from mechanical\r\n      transformation or translation of a Source form, including but\r\n      not limited to compiled object code, generated documentation,\r\n      and conversions to other media types.\r\n\r\n      \"Work\" shall mean the work of authorship, whether in Source or\r\n      Object form, made available under the License, as indicated by a\r\n      copyright notice that is included in or attached to the work\r\n      (an example is provided in the Appendix below).\r\n\r\n      \"Derivative Works\" shall mean any work, whether in Source or Object\r\n      form, that is based on (or derived from) the Work and for which the\r\n      editorial revisions, annotations, elaborations, or other modifications\r\n      represent, as a whole, an original work of authorship. For the purposes\r\n      of this License, Derivative Works shall not include works that remain\r\n      separable from, or merely link (or bind by name) to the interfaces of,\r\n      the Work and Derivative Works thereof.\r\n\r\n      \"Contribution\" shall mean any work of authorship, including\r\n      the original version of the Work and any modifications or additions\r\n      to that Work or Derivative Works thereof, that is intentionally\r\n      submitted to Licensor for inclusion in the Work by the copyright owner\r\n      or by an individual or Legal Entity authorized to submit on behalf of\r\n      the copyright owner. For the purposes of this definition, \"submitted\"\r\n      means any form of electronic, verbal, or written communication sent\r\n      to the Licensor or its representatives, including but not limited to\r\n      communication on electronic mailing lists, source code control systems,\r\n      and issue tracking systems that are managed by, or on behalf of, the\r\n      Licensor for the purpose of discussing and improving the Work, but\r\n      excluding communication that is conspicuously marked or otherwise\r\n      designated in writing by the copyright owner as \"Not a Contribution.\"\r\n\r\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\r\n      on behalf of whom a Contribution has been received by Licensor and\r\n      subsequently incorporated within the Work.\r\n\r\n   2. Grant of Copyright License. Subject to the terms and conditions of\r\n      this License, each Contributor hereby grants to You a perpetual,\r\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\r\n      copyright license to reproduce, prepare Derivative Works of,\r\n      publicly display, publicly perform, sublicense, and distribute the\r\n      Work and such Derivative Works in Source or Object form.\r\n\r\n   3. Grant of Patent License. Subject to the terms and conditions of\r\n      this License, each Contributor hereby grants to You a perpetual,\r\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\r\n      (except as stated in this section) patent license to make, have made,\r\n      use, offer to sell, sell, import, and otherwise transfer the Work,\r\n      where such license applies only to those patent claims licensable\r\n      by such Contributor that are necessarily infringed by their\r\n      Contribution(s) alone or by combination of their Contribution(s)\r\n      with the Work to which such Contribution(s) was submitted. If You\r\n      institute patent litigation against any entity (including a\r\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\r\n      or a Contribution incorporated within the Work constitutes direct\r\n      or contributory patent infringement, then any patent licenses\r\n      granted to You under this License for that Work shall terminate\r\n      as of the date such litigation is filed.\r\n\r\n   4. Redistribution. You may reproduce and distribute copies of the\r\n      Work or Derivative Works thereof in any medium, with or without\r\n      modifications, and in Source or Object form, provided that You\r\n      meet the following conditions:\r\n\r\n      (a) You must give any other recipients of the Work or\r\n          Derivative Works a copy of this License; and\r\n\r\n      (b) You must cause any modified files to carry prominent notices\r\n          stating that You changed the files; and\r\n\r\n      (c) You must retain, in the Source form of any Derivative Works\r\n          that You distribute, all copyright, patent, trademark, and\r\n          attribution notices from the Source form of the Work,\r\n          excluding those notices that do not pertain to any part of\r\n          the Derivative Works; and\r\n\r\n      (d) If the Work includes a \"NOTICE\" text file as part of its\r\n          distribution, then any Derivative Works that You distribute must\r\n          include a readable copy of the attribution notices contained\r\n          within such NOTICE file, excluding those notices that do not\r\n          pertain to any part of the Derivative Works, in at least one\r\n          of the following places: within a NOTICE text file distributed\r\n          as part of the Derivative Works; within the Source form or\r\n          documentation, if provided along with the Derivative Works; or,\r\n          within a display generated by the Derivative Works, if and\r\n          wherever such third-party notices normally appear. The contents\r\n          of the NOTICE file are for informational purposes only and\r\n          do not modify the License. You may add Your own attribution\r\n          notices within Derivative Works that You distribute, alongside\r\n          or as an addendum to the NOTICE text from the Work, provided\r\n          that such additional attribution notices cannot be construed\r\n          as modifying the License.\r\n\r\n      You may add Your own copyright statement to Your modifications and\r\n      may provide additional or different license terms and conditions\r\n      for use, reproduction, or distribution of Your modifications, or\r\n      for any such Derivative Works as a whole, provided Your use,\r\n      reproduction, and distribution of the Work otherwise complies with\r\n      the conditions stated in this License.\r\n\r\n   5. Submission of Contributions. Unless You explicitly state otherwise,\r\n      any Contribution intentionally submitted for inclusion in the Work\r\n      by You to the Licensor shall be under the terms and conditions of\r\n      this License, without any additional terms or conditions.\r\n      Notwithstanding the above, nothing herein shall supersede or modify\r\n      the terms of any separate license agreement you may have executed\r\n      with Licensor regarding such Contributions.\r\n\r\n   6. Trademarks. This License does not grant permission to use the trade\r\n      names, trademarks, service marks, or product names of the Licensor,\r\n      except as required for reasonable and customary use in describing the\r\n      origin of the Work and reproducing the content of the NOTICE file.\r\n\r\n   7. Disclaimer of Warranty. Unless required by applicable law or\r\n      agreed to in writing, Licensor provides the Work (and each\r\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\r\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\r\n      implied, including, without limitation, any warranties or conditions\r\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\r\n      PARTICULAR PURPOSE. You are solely responsible for determining the\r\n      appropriateness of using or redistributing the Work and assume any\r\n      risks associated with Your exercise of permissions under this License.\r\n\r\n   8. Limitation of Liability. In no event and under no legal theory,\r\n      whether in tort (including negligence), contract, or otherwise,\r\n      unless required by applicable law (such as deliberate and grossly\r\n      negligent acts) or agreed to in writing, shall any Contributor be\r\n      liable to You for damages, including any direct, indirect, special,\r\n      incidental, or consequential damages of any character arising as a\r\n      result of this License or out of the use or inability to use the\r\n      Work (including but not limited to damages for loss of goodwill,\r\n      work stoppage, computer failure or malfunction, or any and all\r\n      other commercial damages or losses), even if such Contributor\r\n      has been advised of the possibility of such damages.\r\n\r\n   9. Accepting Warranty or Additional Liability. While redistributing\r\n      the Work or Derivative Works thereof, You may choose to offer,\r\n      and charge a fee for, acceptance of support, warranty, indemnity,\r\n      or other liability obligations and/or rights consistent with this\r\n      License. However, in accepting such obligations, You may act only\r\n      on Your own behalf and on Your sole responsibility, not on behalf\r\n      of any other Contributor, and only if You agree to indemnify,\r\n      defend, and hold each Contributor harmless for any liability\r\n      incurred by, or claims asserted against, such Contributor by reason\r\n      of your accepting any such warranty or additional liability.\r\n\r\n   END OF TERMS AND CONDITIONS\r\n\r\n   APPENDIX: How to apply the Apache License to your work.\r\n\r\n      To apply the Apache License to your work, attach the following\r\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\r\n      replaced with your own identifying information. (Don't include\r\n      the brackets!)  The text should be enclosed in the appropriate\r\n      comment syntax for the file format. We also recommend that a\r\n      file or class name and description of purpose be included on the\r\n      same \"printed page\" as the copyright notice for easier\r\n      identification within third-party archives.\r\n\r\n   Copyright [yyyy] [name of copyright owner]\r\n\r\n   Licensed under the Apache License, Version 2.0 (the \"License\");\r\n   you may not use this file except in compliance with the License.\r\n   You may obtain a copy of the License at\r\n\r\n       http://www.apache.org/licenses/LICENSE-2.0\r\n\r\n   Unless required by applicable law or agreed to in writing, software\r\n   distributed under the License is distributed on an \"AS IS\" BASIS,\r\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n   See the License for the specific language governing permissions and\r\n   limitations under the License.\r\n"
  },
  {
    "path": "crates/resvg/tests/gen-tests.py",
    "content": "#!/usr/bin/env python3\n\nimport os\nfrom pathlib import Path\n\nIGNORE = [\n    'tests/filters/feMorphology/huge-radius', # will timeout on CI\n    'tests/structure/svg/negative-size', # invalid size\n    'tests/structure/svg/no-size', # invalid size\n    'tests/structure/svg/zero-size', # invalid size\n    'tests/structure/svg/not-UTF-8-encoding', # invalid encoding\n    # Produces slightly different output on some hardware.\n    # Not a bug, just a SIMD rounding difference.\n    'tests/paint-servers/radialGradient/focal-point-correction',\n]\n\nprint('// Copyright 2020 the Resvg Authors')\nprint('// SPDX-License-Identifier: Apache-2.0 OR MIT')\nprint()\nprint('// This file is auto-generated by gen-tests.py')\nprint()\nprint('#![allow(non_snake_case)]')\nprint()\nprint('use crate::render;')\nprint()\n\nfiles = sorted(list(Path('tests').rglob('*.svg')))\nfor file in files:\n    file = str(file).replace('.svg', '')\n\n    if file in IGNORE:\n        continue\n\n    fn_name = file.replace('tests/', '')\n    fn_name = fn_name.replace('/', '_')\n    fn_name = fn_name.replace('-', '_')\n    fn_name = fn_name.replace('=', '_eq_')\n    fn_name = fn_name.replace('.', '_')\n    fn_name = fn_name.replace('#', '')\n\n    print(f'#[test] fn {fn_name}() {{ assert_eq!(render(\"{file}\"), 0); }}')\n"
  },
  {
    "path": "crates/resvg/tests/integration/extra.rs",
    "content": "// Copyright 2023 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse crate::{render_extra, render_extra_with_scale, render_node};\n\n#[test]\nfn group_with_only_transform() {\n    assert_eq!(render_extra(\"extra/group-with-only-transform\"), 0);\n}\n\n#[test]\nfn subpixel_rect_position() {\n    assert_eq!(render_extra(\"extra/subpixel-rect-position\"), 0);\n}\n\n#[test]\nfn transformed_rect() {\n    assert_eq!(render_extra(\"extra/transformed-rect\"), 0);\n}\n\n#[test]\nfn hidden_element() {\n    assert_eq!(render_extra(\"extra/hidden-element\"), 0);\n}\n\n#[test]\nfn simple_stroke() {\n    assert_eq!(render_extra(\"extra/simple-stroke\"), 0);\n}\n\n#[test]\nfn fill_and_stroke() {\n    assert_eq!(render_extra(\"extra/fill-and-stroke\"), 0);\n}\n\n#[test]\nfn paint_order_stroke() {\n    assert_eq!(render_extra(\"extra/paint-order=stroke\"), 0);\n}\n\n#[test]\nfn stroke_linecap_square() {\n    assert_eq!(render_extra(\"extra/stroke-linecap=square\"), 0);\n}\n\n#[test]\nfn miter_join_with_acute_angle() {\n    assert_eq!(render_extra(\"extra/miter-join-with-acute-angle\"), 0);\n}\n\n#[test]\nfn horizontal_line() {\n    assert_eq!(render_extra(\"extra/horizontal-line\"), 0);\n}\n\n#[test]\nfn horizontal_line_no_stroke() {\n    assert_eq!(render_extra(\"extra/horizontal-line-no-stroke\"), 0);\n}\n\n#[test]\nfn filter_region_precision() {\n    assert_eq!(\n        render_extra_with_scale(\"extra/filter-region-precision\", 10.0),\n        0\n    );\n}\n\n#[test]\nfn translate_outside_viewbox() {\n    assert_eq!(render_extra(\"extra/translate-outside-viewbox\"), 0);\n}\n\n#[test]\nfn render_node_filter_on_empty_group() {\n    assert_eq!(render_node(\"extra/filter-on-empty-group\", \"g1\"), 0);\n}\n\n#[test]\nfn render_node_filter_with_transform_on_shape() {\n    assert_eq!(render_node(\"extra/filter-with-transform-on-shape\", \"g1\"), 0);\n}\n"
  },
  {
    "path": "crates/resvg/tests/integration/main.rs",
    "content": "// Copyright 2020 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse once_cell::sync::Lazy;\nuse png::{BitDepth, ColorType, Encoder};\nuse rgb::{FromSlice, RGBA8, Rgba};\nuse std::cmp::max;\nuse std::fs::File;\nuse std::io::{BufWriter, Cursor};\nuse std::process::Command;\nuse std::sync::Arc;\nuse usvg::fontdb;\n\n#[rustfmt::skip]\nmod render;\n\nmod extra;\n\nconst IMAGE_SIZE: u32 = 300;\n\nstatic GLOBAL_FONTDB: Lazy<Arc<fontdb::Database>> = Lazy::new(|| {\n    if let Ok(()) = log::set_logger(&LOGGER) {\n        log::set_max_level(log::LevelFilter::Warn);\n    }\n\n    let mut fontdb = fontdb::Database::new();\n    fontdb.load_fonts_dir(\"tests/fonts\");\n    fontdb.set_serif_family(\"Noto Serif\");\n    fontdb.set_sans_serif_family(\"Noto Sans\");\n    fontdb.set_cursive_family(\"Yellowtail\");\n    fontdb.set_fantasy_family(\"Sedgwick Ave Display\");\n    fontdb.set_monospace_family(\"Noto Mono\");\n    Arc::new(fontdb)\n});\n\npub fn render(name: &str) -> usize {\n    render_inner(name, TestMode::Normal)\n}\n\npub fn render_extra_with_scale(name: &str, scale: f32) -> usize {\n    render_inner(name, TestMode::Extra(scale))\n}\n\npub fn render_extra(name: &str) -> usize {\n    render_extra_with_scale(name, 1.0)\n}\n\npub fn render_node(name: &str, id: &str) -> usize {\n    render_inner(name, TestMode::Node(id))\n}\n\npub fn render_inner(name: &str, test_mode: TestMode) -> usize {\n    let svg_path = format!(\"tests/{}.svg\", name);\n    let png_path = format!(\"tests/{}.png\", name);\n    let make_ref = std::env::var(\"MAKE_REF\").is_ok();\n\n    let opt = usvg::Options {\n        fontdb: GLOBAL_FONTDB.clone(),\n        resources_dir: Some(\n            std::path::PathBuf::from(&svg_path)\n                .parent()\n                .unwrap()\n                .to_owned(),\n        ),\n        ..usvg::Options::default()\n    };\n\n    let tree = {\n        let svg_data = std::fs::read(&svg_path).unwrap();\n        usvg::Tree::from_data(&svg_data, &opt).unwrap()\n    };\n\n    let size;\n    let mut pixmap;\n\n    match test_mode {\n        TestMode::Normal => {\n            size = tree\n                .size()\n                .to_int_size()\n                .scale_to_width(IMAGE_SIZE)\n                .unwrap();\n            pixmap = tiny_skia::Pixmap::new(size.width(), size.height()).unwrap();\n            let render_ts = tiny_skia::Transform::from_scale(\n                size.width() as f32 / tree.size().width() as f32,\n                size.height() as f32 / tree.size().height() as f32,\n            );\n            resvg::render(&tree, render_ts, &mut pixmap.as_mut());\n        }\n        TestMode::Node(id) => {\n            let node = tree.node_by_id(id).unwrap();\n            size = node.abs_layer_bounding_box().unwrap().size().to_int_size();\n            pixmap = tiny_skia::Pixmap::new(size.width(), size.height()).unwrap();\n            resvg::render_node(node, tiny_skia::Transform::identity(), &mut pixmap.as_mut());\n        }\n        TestMode::Extra(scale) => {\n            size = tree.size().to_int_size().scale_by(scale).unwrap();\n            pixmap = tiny_skia::Pixmap::new(size.width(), size.height()).unwrap();\n            let render_ts = tiny_skia::Transform::from_scale(scale, scale);\n            resvg::render(&tree, render_ts, &mut pixmap.as_mut());\n        }\n    }\n\n    let actual_image = {\n        let (width, height) = (pixmap.width(), pixmap.height());\n        let mut data = pixmap.clone().take();\n        demultiply_alpha(data.as_mut_slice().as_rgba_mut());\n\n        TestImage::new_with(data, width, height)\n    };\n\n    let make_ref_fn = || -> ! {\n        pixmap.save_png(&png_path).unwrap();\n        Command::new(\"oxipng\")\n            .args([\n                \"-o\".to_owned(),\n                \"6\".to_owned(),\n                \"-Z\".to_owned(),\n                png_path.clone(),\n            ])\n            .output()\n            .unwrap();\n        panic!(\"new reference image created\");\n    };\n\n    let reference_image = if let Ok(image_data) = std::fs::read(&png_path) {\n        load_png(image_data)\n    } else {\n        if make_ref {\n            make_ref_fn();\n        } else {\n            panic!(\"missing reference image\");\n        }\n    };\n\n    if let Some((diff_image, pixel_diff)) = get_diff(&reference_image, &actual_image) {\n        if make_ref {\n            make_ref_fn();\n        } else {\n            let _ = std::fs::create_dir_all(\"tests/diffs\");\n            diff_image.save_png(&format!(\"tests/diffs/{}.png\", name.replace(\"/\", \"_\")));\n\n            pixel_diff\n        }\n    } else {\n        0\n    }\n}\n\n/// Returns `Some` if there is at least one different pixel, and `None` if the images match.\nfn get_diff(expected_image: &TestImage, actual_image: &TestImage) -> Option<(TestImage, usize)> {\n    const DIFF_THRESHOLD: u8 = 1;\n\n    let width = max(expected_image.width, actual_image.width);\n    let height = max(expected_image.height, actual_image.height);\n\n    let mut diff_image = TestImage::new(3 * width, height);\n\n    let mut pixel_diff = 0;\n\n    for x in 0..width {\n        for y in 0..height {\n            let actual_pixel = actual_image.get_pixel(x, y);\n            let expected_pixel = expected_image.get_pixel(x, y);\n\n            match (actual_pixel, expected_pixel) {\n                (Some(actual), Some(expected)) => {\n                    diff_image.set_pixel(x, y, expected);\n                    diff_image.set_pixel(x + 2 * width, y, actual);\n                    if is_pix_diff(&expected, &actual, DIFF_THRESHOLD) {\n                        pixel_diff += 1;\n                        diff_image.set_pixel(x + width, y, Rgba::new(255, 0, 0, 255));\n                    } else {\n                        diff_image.set_pixel(x + width, y, Rgba::new(0, 0, 0, 255));\n                    }\n                }\n                (Some(actual), None) => {\n                    pixel_diff += 1;\n                    diff_image.set_pixel(x + 2 * width, y, actual);\n                    diff_image.set_pixel(x + width, y, Rgba::new(255, 0, 0, 255));\n                }\n                (None, Some(expected)) => {\n                    pixel_diff += 1;\n                    diff_image.set_pixel(x, y, expected);\n                    diff_image.set_pixel(x + width, y, Rgba::new(255, 0, 0, 255));\n                }\n                _ => {\n                    pixel_diff += 1;\n                    diff_image.set_pixel(x, y, Rgba::new(255, 0, 0, 255));\n                    diff_image.set_pixel(x + width, y, Rgba::new(255, 0, 0, 255));\n                }\n            }\n        }\n    }\n\n    if pixel_diff > 0 {\n        Some((diff_image, pixel_diff))\n    } else {\n        None\n    }\n}\n\n/// Demultiplies provided pixels alpha.\nfn demultiply_alpha(data: &mut [RGBA8]) {\n    for p in data {\n        let a = p.a as f64 / 255.0;\n        p.b = (p.b as f64 / a + 0.5) as u8;\n        p.g = (p.g as f64 / a + 0.5) as u8;\n        p.r = (p.r as f64 / a + 0.5) as u8;\n    }\n}\n\nfn is_pix_diff(pixel1: &Rgba<u8>, pixel2: &Rgba<u8>, threshold: u8) -> bool {\n    if pixel1.a == 0 && pixel2.a == 0 {\n        return false;\n    }\n\n    let mut different = false;\n\n    different |= pixel1.r.abs_diff(pixel2.r) > threshold;\n    different |= pixel1.g.abs_diff(pixel2.g) > threshold;\n    different |= pixel1.b.abs_diff(pixel2.b) > threshold;\n    different |= pixel1.a.abs_diff(pixel2.a) > threshold;\n\n    different\n}\n\nfn load_png(data: Vec<u8>) -> TestImage {\n    let mut decoder = png::Decoder::new(Cursor::new(data.as_slice()));\n    decoder.set_transformations(png::Transformations::normalize_to_color8());\n    let mut reader = decoder.read_info().unwrap();\n    let mut img_data = vec![0; reader.output_buffer_size().unwrap()];\n    let info = reader.next_frame(&mut img_data).unwrap();\n\n    let data = match info.color_type {\n        png::ColorType::Rgb => {\n            panic!(\"RGB PNG is not supported.\");\n        }\n        png::ColorType::Rgba => img_data,\n        png::ColorType::Grayscale => {\n            let mut rgba_data = Vec::with_capacity(img_data.len() * 4);\n            for gray in img_data {\n                rgba_data.push(gray);\n                rgba_data.push(gray);\n                rgba_data.push(gray);\n                rgba_data.push(255);\n            }\n\n            rgba_data\n        }\n        png::ColorType::GrayscaleAlpha => {\n            let mut rgba_data = Vec::with_capacity(img_data.len() * 2);\n            for slice in img_data.chunks(2) {\n                let gray = slice[0];\n                let alpha = slice[1];\n                rgba_data.push(gray);\n                rgba_data.push(gray);\n                rgba_data.push(gray);\n                rgba_data.push(alpha);\n            }\n\n            rgba_data\n        }\n        png::ColorType::Indexed => {\n            panic!(\"Indexed PNG is not supported.\");\n        }\n    };\n\n    TestImage::new_with(data, info.width, info.height)\n}\n\nstruct TestImage {\n    data: Vec<u8>,\n    width: u32,\n    height: u32,\n}\n\nimpl TestImage {\n    fn new(width: u32, height: u32) -> Self {\n        Self {\n            data: vec![0; width as usize * height as usize * 4],\n            width,\n            height,\n        }\n    }\n\n    fn new_with(data: Vec<u8>, width: u32, height: u32) -> Self {\n        Self {\n            data,\n            width,\n            height,\n        }\n    }\n\n    fn get_pixel(&self, x: u32, y: u32) -> Option<Rgba<u8>> {\n        if x >= self.width || y >= self.height {\n            return None;\n        }\n\n        let pos = self.width as usize * (y as usize) + x as usize;\n\n        Some(self.data.as_rgba()[pos])\n    }\n\n    fn set_pixel(&mut self, x: u32, y: u32, val: Rgba<u8>) {\n        let pos = self.width as usize * (y as usize) + x as usize;\n\n        self.data.as_rgba_mut()[pos] = val;\n    }\n\n    fn save_png(&self, path: &str) {\n        let file = File::create(path).unwrap();\n        let ref mut w = BufWriter::new(file);\n\n        let mut encoder = Encoder::new(w, self.width, self.height);\n        encoder.set_color(ColorType::Rgba);\n        encoder.set_depth(BitDepth::Eight);\n\n        let mut writer = encoder.write_header().unwrap();\n        writer.write_image_data(&self.data).unwrap();\n        writer.finish().unwrap();\n    }\n}\n\n#[derive(Copy, Clone)]\npub enum TestMode<'a> {\n    /// Render a node by its ID.\n    Node(&'a str),\n    /// Render an `extra` test with a specific scale.\n    Extra(f32),\n    /// Render a normal SVG test.\n    Normal,\n}\n\n/// A simple stderr logger.\nstatic LOGGER: SimpleLogger = SimpleLogger;\nstruct SimpleLogger;\nimpl log::Log for SimpleLogger {\n    fn enabled(&self, metadata: &log::Metadata) -> bool {\n        metadata.level() <= log::LevelFilter::Warn\n    }\n\n    fn log(&self, record: &log::Record) {\n        if self.enabled(record.metadata()) {\n            let target = if !record.target().is_empty() {\n                record.target()\n            } else {\n                record.module_path().unwrap_or_default()\n            };\n\n            let line = record.line().unwrap_or(0);\n            let args = record.args();\n\n            match record.level() {\n                log::Level::Error => eprintln!(\"Error (in {}:{}): {}\", target, line, args),\n                log::Level::Warn => eprintln!(\"Warning (in {}:{}): {}\", target, line, args),\n                log::Level::Info => eprintln!(\"Info (in {}:{}): {}\", target, line, args),\n                log::Level::Debug => eprintln!(\"Debug (in {}:{}): {}\", target, line, args),\n                log::Level::Trace => eprintln!(\"Trace (in {}:{}): {}\", target, line, args),\n            }\n        }\n    }\n\n    fn flush(&self) {}\n}\n"
  },
  {
    "path": "crates/resvg/tests/integration/render.rs",
    "content": "// Copyright 2020 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\n// This file is auto-generated by gen-tests.py\n\n#![allow(non_snake_case)]\n\nuse crate::render;\n\n#[test] fn filters_enable_background_accumulate_with_new() { assert_eq!(render(\"tests/filters/enable-background/accumulate-with-new\"), 0); }\n#[test] fn filters_enable_background_accumulate() { assert_eq!(render(\"tests/filters/enable-background/accumulate\"), 0); }\n#[test] fn filters_enable_background_filter_on_shape() { assert_eq!(render(\"tests/filters/enable-background/filter-on-shape\"), 0); }\n#[test] fn filters_enable_background_inherit() { assert_eq!(render(\"tests/filters/enable-background/inherit\"), 0); }\n#[test] fn filters_enable_background_new_with_invalid_region_1() { assert_eq!(render(\"tests/filters/enable-background/new-with-invalid-region-1\"), 0); }\n#[test] fn filters_enable_background_new_with_invalid_region_2() { assert_eq!(render(\"tests/filters/enable-background/new-with-invalid-region-2\"), 0); }\n#[test] fn filters_enable_background_new_with_invalid_region_3() { assert_eq!(render(\"tests/filters/enable-background/new-with-invalid-region-3\"), 0); }\n#[test] fn filters_enable_background_new_with_region() { assert_eq!(render(\"tests/filters/enable-background/new-with-region\"), 0); }\n#[test] fn filters_enable_background_new() { assert_eq!(render(\"tests/filters/enable-background/new\"), 0); }\n#[test] fn filters_enable_background_shapes_after_filter() { assert_eq!(render(\"tests/filters/enable-background/shapes-after-filter\"), 0); }\n#[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); }\n#[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); }\n#[test] fn filters_enable_background_with_clip_path() { assert_eq!(render(\"tests/filters/enable-background/with-clip-path\"), 0); }\n#[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); }\n#[test] fn filters_enable_background_with_filter() { assert_eq!(render(\"tests/filters/enable-background/with-filter\"), 0); }\n#[test] fn filters_enable_background_with_mask() { assert_eq!(render(\"tests/filters/enable-background/with-mask\"), 0); }\n#[test] fn filters_enable_background_with_opacity_1() { assert_eq!(render(\"tests/filters/enable-background/with-opacity-1\"), 0); }\n#[test] fn filters_enable_background_with_opacity_2() { assert_eq!(render(\"tests/filters/enable-background/with-opacity-2\"), 0); }\n#[test] fn filters_enable_background_with_opacity_3() { assert_eq!(render(\"tests/filters/enable-background/with-opacity-3\"), 0); }\n#[test] fn filters_enable_background_with_opacity_4() { assert_eq!(render(\"tests/filters/enable-background/with-opacity-4\"), 0); }\n#[test] fn filters_enable_background_with_transform() { assert_eq!(render(\"tests/filters/enable-background/with-transform\"), 0); }\n#[test] fn filters_feBlend_empty() { assert_eq!(render(\"tests/filters/feBlend/empty\"), 0); }\n#[test] fn filters_feBlend_mode_eq_color_burn() { assert_eq!(render(\"tests/filters/feBlend/mode=color-burn\"), 0); }\n#[test] fn filters_feBlend_mode_eq_darken() { assert_eq!(render(\"tests/filters/feBlend/mode=darken\"), 0); }\n#[test] fn filters_feBlend_mode_eq_hue() { assert_eq!(render(\"tests/filters/feBlend/mode=hue\"), 0); }\n#[test] fn filters_feBlend_mode_eq_lighten() { assert_eq!(render(\"tests/filters/feBlend/mode=lighten\"), 0); }\n#[test] fn filters_feBlend_mode_eq_multiply() { assert_eq!(render(\"tests/filters/feBlend/mode=multiply\"), 0); }\n#[test] fn filters_feBlend_mode_eq_normal() { assert_eq!(render(\"tests/filters/feBlend/mode=normal\"), 0); }\n#[test] fn filters_feBlend_mode_eq_screen() { assert_eq!(render(\"tests/filters/feBlend/mode=screen\"), 0); }\n#[test] fn filters_feBlend_with_subregion_on_input_1() { assert_eq!(render(\"tests/filters/feBlend/with-subregion-on-input-1\"), 0); }\n#[test] fn filters_feBlend_with_subregion_on_input_2() { assert_eq!(render(\"tests/filters/feBlend/with-subregion-on-input-2\"), 0); }\n#[test] fn filters_feColorMatrix_invalid_type() { assert_eq!(render(\"tests/filters/feColorMatrix/invalid-type\"), 0); }\n#[test] fn filters_feColorMatrix_type_eq_hueRotate_without_an_angle() { assert_eq!(render(\"tests/filters/feColorMatrix/type=hueRotate-without-an-angle\"), 0); }\n#[test] fn filters_feColorMatrix_type_eq_hueRotate() { assert_eq!(render(\"tests/filters/feColorMatrix/type=hueRotate\"), 0); }\n#[test] fn filters_feColorMatrix_type_eq_luminanceToAlpha() { assert_eq!(render(\"tests/filters/feColorMatrix/type=luminanceToAlpha\"), 0); }\n#[test] fn filters_feColorMatrix_type_eq_matrix_with_empty_values() { assert_eq!(render(\"tests/filters/feColorMatrix/type=matrix-with-empty-values\"), 0); }\n#[test] fn filters_feColorMatrix_type_eq_matrix_with_non_normalized_values() { assert_eq!(render(\"tests/filters/feColorMatrix/type=matrix-with-non-normalized-values\"), 0); }\n#[test] fn filters_feColorMatrix_type_eq_matrix_with_not_enough_values() { assert_eq!(render(\"tests/filters/feColorMatrix/type=matrix-with-not-enough-values\"), 0); }\n#[test] fn filters_feColorMatrix_type_eq_matrix_with_too_many_values() { assert_eq!(render(\"tests/filters/feColorMatrix/type=matrix-with-too-many-values\"), 0); }\n#[test] fn filters_feColorMatrix_type_eq_matrix_without_values() { assert_eq!(render(\"tests/filters/feColorMatrix/type=matrix-without-values\"), 0); }\n#[test] fn filters_feColorMatrix_type_eq_matrix() { assert_eq!(render(\"tests/filters/feColorMatrix/type=matrix\"), 0); }\n#[test] fn filters_feColorMatrix_type_eq_saturate_with_a_large_coefficient() { assert_eq!(render(\"tests/filters/feColorMatrix/type=saturate-with-a-large-coefficient\"), 0); }\n#[test] fn filters_feColorMatrix_type_eq_saturate_with_negative_coefficient() { assert_eq!(render(\"tests/filters/feColorMatrix/type=saturate-with-negative-coefficient\"), 0); }\n#[test] fn filters_feColorMatrix_type_eq_saturate_without_a_coefficient() { assert_eq!(render(\"tests/filters/feColorMatrix/type=saturate-without-a-coefficient\"), 0); }\n#[test] fn filters_feColorMatrix_type_eq_saturate() { assert_eq!(render(\"tests/filters/feColorMatrix/type=saturate\"), 0); }\n#[test] fn filters_feColorMatrix_without_a_type() { assert_eq!(render(\"tests/filters/feColorMatrix/without-a-type\"), 0); }\n#[test] fn filters_feColorMatrix_without_attributes() { assert_eq!(render(\"tests/filters/feColorMatrix/without-attributes\"), 0); }\n#[test] fn filters_feComponentTransfer_invalid_type() { assert_eq!(render(\"tests/filters/feComponentTransfer/invalid-type\"), 0); }\n#[test] fn filters_feComponentTransfer_mixed_types() { assert_eq!(render(\"tests/filters/feComponentTransfer/mixed-types\"), 0); }\n#[test] fn filters_feComponentTransfer_no_children() { assert_eq!(render(\"tests/filters/feComponentTransfer/no-children\"), 0); }\n#[test] fn filters_feComponentTransfer_type_eq_discrete_on_blue() { assert_eq!(render(\"tests/filters/feComponentTransfer/type=discrete-on-blue\"), 0); }\n#[test] fn filters_feComponentTransfer_type_eq_gamma_on_blue() { assert_eq!(render(\"tests/filters/feComponentTransfer/type=gamma-on-blue\"), 0); }\n#[test] fn filters_feComponentTransfer_type_eq_gamma_with_an_invalid_offset() { assert_eq!(render(\"tests/filters/feComponentTransfer/type=gamma-with-an-invalid-offset\"), 0); }\n#[test] fn filters_feComponentTransfer_type_eq_gamma_with_invalid_values() { assert_eq!(render(\"tests/filters/feComponentTransfer/type=gamma-with-invalid-values\"), 0); }\n#[test] fn filters_feComponentTransfer_type_eq_identity_on_all() { assert_eq!(render(\"tests/filters/feComponentTransfer/type=identity-on-all\"), 0); }\n#[test] fn filters_feComponentTransfer_type_eq_linear_on_blue() { assert_eq!(render(\"tests/filters/feComponentTransfer/type=linear-on-blue\"), 0); }\n#[test] fn filters_feComponentTransfer_type_eq_linear_with_invalid_values() { assert_eq!(render(\"tests/filters/feComponentTransfer/type=linear-with-invalid-values\"), 0); }\n#[test] fn filters_feComponentTransfer_type_eq_linear_with_large_values() { assert_eq!(render(\"tests/filters/feComponentTransfer/type=linear-with-large-values\"), 0); }\n#[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); }\n#[test] fn filters_feComponentTransfer_type_eq_table_and_tableValues_eq_1() { assert_eq!(render(\"tests/filters/feComponentTransfer/type=table-and-tableValues=1\"), 0); }\n#[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); }\n#[test] fn filters_feComponentTransfer_type_eq_table_and_tableValues_eq_1px() { assert_eq!(render(\"tests/filters/feComponentTransfer/type=table-and-tableValues=1px\"), 0); }\n#[test] fn filters_feComponentTransfer_type_eq_table_on_alpha() { assert_eq!(render(\"tests/filters/feComponentTransfer/type=table-on-alpha\"), 0); }\n#[test] fn filters_feComponentTransfer_type_eq_table_on_blue_twice() { assert_eq!(render(\"tests/filters/feComponentTransfer/type=table-on-blue-twice\"), 0); }\n#[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); }\n#[test] fn filters_feComponentTransfer_type_eq_table_on_blue() { assert_eq!(render(\"tests/filters/feComponentTransfer/type=table-on-blue\"), 0); }\n#[test] fn filters_feComponentTransfer_type_eq_table_with_an_empty_tableValues() { assert_eq!(render(\"tests/filters/feComponentTransfer/type=table-with-an-empty-tableValues\"), 0); }\n#[test] fn filters_feComponentTransfer_type_eq_table_with_large_values() { assert_eq!(render(\"tests/filters/feComponentTransfer/type=table-with-large-values\"), 0); }\n#[test] fn filters_feComponentTransfer_type_eq_table_without_tableValues() { assert_eq!(render(\"tests/filters/feComponentTransfer/type=table-without-tableValues\"), 0); }\n#[test] fn filters_feComposite_default_operator() { assert_eq!(render(\"tests/filters/feComposite/default-operator\"), 0); }\n#[test] fn filters_feComposite_empty() { assert_eq!(render(\"tests/filters/feComposite/empty\"), 0); }\n#[test] fn filters_feComposite_invalid_operator() { assert_eq!(render(\"tests/filters/feComposite/invalid-operator\"), 0); }\n#[test] fn filters_feComposite_operator_eq_arithmetic_and_invalid_k1_4() { assert_eq!(render(\"tests/filters/feComposite/operator=arithmetic-and-invalid-k1-4\"), 0); }\n#[test] fn filters_feComposite_operator_eq_arithmetic_on_sRGB() { assert_eq!(render(\"tests/filters/feComposite/operator=arithmetic-on-sRGB\"), 0); }\n#[test] fn filters_feComposite_operator_eq_arithmetic_with_large_k1_4() { assert_eq!(render(\"tests/filters/feComposite/operator=arithmetic-with-large-k1-4\"), 0); }\n#[test] fn filters_feComposite_operator_eq_arithmetic_with_opacity_on_sRGB() { assert_eq!(render(\"tests/filters/feComposite/operator=arithmetic-with-opacity-on-sRGB\"), 0); }\n#[test] fn filters_feComposite_operator_eq_arithmetic_with_opacity() { assert_eq!(render(\"tests/filters/feComposite/operator=arithmetic-with-opacity\"), 0); }\n#[test] fn filters_feComposite_operator_eq_arithmetic_with_some_k1_4() { assert_eq!(render(\"tests/filters/feComposite/operator=arithmetic-with-some-k1-4\"), 0); }\n#[test] fn filters_feComposite_operator_eq_arithmetic_without_k1_4() { assert_eq!(render(\"tests/filters/feComposite/operator=arithmetic-without-k1-4\"), 0); }\n#[test] fn filters_feComposite_operator_eq_arithmetic() { assert_eq!(render(\"tests/filters/feComposite/operator=arithmetic\"), 0); }\n#[test] fn filters_feComposite_operator_eq_atop() { assert_eq!(render(\"tests/filters/feComposite/operator=atop\"), 0); }\n#[test] fn filters_feComposite_operator_eq_in() { assert_eq!(render(\"tests/filters/feComposite/operator=in\"), 0); }\n#[test] fn filters_feComposite_operator_eq_out() { assert_eq!(render(\"tests/filters/feComposite/operator=out\"), 0); }\n#[test] fn filters_feComposite_operator_eq_over() { assert_eq!(render(\"tests/filters/feComposite/operator=over\"), 0); }\n#[test] fn filters_feComposite_operator_eq_xor() { assert_eq!(render(\"tests/filters/feComposite/operator=xor\"), 0); }\n#[test] fn filters_feComposite_with_subregion_on_input_1() { assert_eq!(render(\"tests/filters/feComposite/with-subregion-on-input-1\"), 0); }\n#[test] fn filters_feComposite_with_subregion_on_input_2() { assert_eq!(render(\"tests/filters/feComposite/with-subregion-on-input-2\"), 0); }\n#[test] fn filters_feConvolveMatrix_bias_eq__0_5() { assert_eq!(render(\"tests/filters/feConvolveMatrix/bias=-0.5\"), 0); }\n#[test] fn filters_feConvolveMatrix_bias_eq_0_5() { assert_eq!(render(\"tests/filters/feConvolveMatrix/bias=0.5\"), 0); }\n#[test] fn filters_feConvolveMatrix_bias_eq_9999() { assert_eq!(render(\"tests/filters/feConvolveMatrix/bias=9999\"), 0); }\n#[test] fn filters_feConvolveMatrix_custom_divisor() { assert_eq!(render(\"tests/filters/feConvolveMatrix/custom-divisor\"), 0); }\n#[test] fn filters_feConvolveMatrix_divisor_eq_0() { assert_eq!(render(\"tests/filters/feConvolveMatrix/divisor=0\"), 0); }\n#[test] fn filters_feConvolveMatrix_edgeMode_eq_none() { assert_eq!(render(\"tests/filters/feConvolveMatrix/edgeMode=none\"), 0); }\n#[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); }\n#[test] fn filters_feConvolveMatrix_edgeMode_eq_wrap() { assert_eq!(render(\"tests/filters/feConvolveMatrix/edgeMode=wrap\"), 0); }\n#[test] fn filters_feConvolveMatrix_empty_kernelMatrix() { assert_eq!(render(\"tests/filters/feConvolveMatrix/empty-kernelMatrix\"), 0); }\n#[test] fn filters_feConvolveMatrix_kernelMatrix_with_not_enough_values() { assert_eq!(render(\"tests/filters/feConvolveMatrix/kernelMatrix-with-not-enough-values\"), 0); }\n#[test] fn filters_feConvolveMatrix_kernelMatrix_with_too_many_values() { assert_eq!(render(\"tests/filters/feConvolveMatrix/kernelMatrix-with-too-many-values\"), 0); }\n#[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); }\n#[test] fn filters_feConvolveMatrix_no_kernelMatrix() { assert_eq!(render(\"tests/filters/feConvolveMatrix/no-kernelMatrix\"), 0); }\n#[test] fn filters_feConvolveMatrix_order_with_a_negative_value_1() { assert_eq!(render(\"tests/filters/feConvolveMatrix/order-with-a-negative-value-1\"), 0); }\n#[test] fn filters_feConvolveMatrix_order_with_a_negative_value_2() { assert_eq!(render(\"tests/filters/feConvolveMatrix/order-with-a-negative-value-2\"), 0); }\n#[test] fn filters_feConvolveMatrix_order_eq_0() { assert_eq!(render(\"tests/filters/feConvolveMatrix/order=0\"), 0); }\n#[test] fn filters_feConvolveMatrix_order_eq_4_2() { assert_eq!(render(\"tests/filters/feConvolveMatrix/order=4-2\"), 0); }\n#[test] fn filters_feConvolveMatrix_order_eq_4_4() { assert_eq!(render(\"tests/filters/feConvolveMatrix/order=4-4\"), 0); }\n#[test] fn filters_feConvolveMatrix_order_eq_4() { assert_eq!(render(\"tests/filters/feConvolveMatrix/order=4\"), 0); }\n#[test] fn filters_feConvolveMatrix_preserveAlpha_eq_true() { assert_eq!(render(\"tests/filters/feConvolveMatrix/preserveAlpha=true\"), 0); }\n#[test] fn filters_feConvolveMatrix_targetX_eq__1() { assert_eq!(render(\"tests/filters/feConvolveMatrix/targetX=-1\"), 0); }\n#[test] fn filters_feConvolveMatrix_targetX_eq_0() { assert_eq!(render(\"tests/filters/feConvolveMatrix/targetX=0\"), 0); }\n#[test] fn filters_feConvolveMatrix_targetX_eq_2() { assert_eq!(render(\"tests/filters/feConvolveMatrix/targetX=2\"), 0); }\n#[test] fn filters_feConvolveMatrix_targetX_eq_3() { assert_eq!(render(\"tests/filters/feConvolveMatrix/targetX=3\"), 0); }\n#[test] fn filters_feConvolveMatrix_unset_order() { assert_eq!(render(\"tests/filters/feConvolveMatrix/unset-order\"), 0); }\n#[test] fn filters_feDiffuseLighting_complex_transform() { assert_eq!(render(\"tests/filters/feDiffuseLighting/complex-transform\"), 0); }\n#[test] fn filters_feDiffuseLighting_diffuseConstant_eq__1() { assert_eq!(render(\"tests/filters/feDiffuseLighting/diffuseConstant=-1\"), 0); }\n#[test] fn filters_feDiffuseLighting_diffuseConstant_eq_0() { assert_eq!(render(\"tests/filters/feDiffuseLighting/diffuseConstant=0\"), 0); }\n#[test] fn filters_feDiffuseLighting_diffuseConstant_eq_5() { assert_eq!(render(\"tests/filters/feDiffuseLighting/diffuseConstant=5\"), 0); }\n#[test] fn filters_feDiffuseLighting_lighting_color_eq_currentColor_without_color() { assert_eq!(render(\"tests/filters/feDiffuseLighting/lighting-color=currentColor-without-color\"), 0); }\n#[test] fn filters_feDiffuseLighting_lighting_color_eq_currentColor() { assert_eq!(render(\"tests/filters/feDiffuseLighting/lighting-color=currentColor\"), 0); }\n#[test] fn filters_feDiffuseLighting_lighting_color_eq_hsla() { assert_eq!(render(\"tests/filters/feDiffuseLighting/lighting-color=hsla\"), 0); }\n#[test] fn filters_feDiffuseLighting_lighting_color_eq_inherit() { assert_eq!(render(\"tests/filters/feDiffuseLighting/lighting-color=inherit\"), 0); }\n#[test] fn filters_feDiffuseLighting_lighting_color_eq_seagreen() { assert_eq!(render(\"tests/filters/feDiffuseLighting/lighting-color=seagreen\"), 0); }\n#[test] fn filters_feDiffuseLighting_linearRGB_color_interpolation() { assert_eq!(render(\"tests/filters/feDiffuseLighting/linearRGB-color-interpolation\"), 0); }\n#[test] fn filters_feDiffuseLighting_multiple_light_sources() { assert_eq!(render(\"tests/filters/feDiffuseLighting/multiple-light-sources\"), 0); }\n#[test] fn filters_feDiffuseLighting_no_light_source() { assert_eq!(render(\"tests/filters/feDiffuseLighting/no-light-source\"), 0); }\n#[test] fn filters_feDiffuseLighting_single_light_source_with_comment() { assert_eq!(render(\"tests/filters/feDiffuseLighting/single-light-source-with-comment\"), 0); }\n#[test] fn filters_feDiffuseLighting_single_light_source_with_desc() { assert_eq!(render(\"tests/filters/feDiffuseLighting/single-light-source-with-desc\"), 0); }\n#[test] fn filters_feDiffuseLighting_single_light_source_with_invalid_child() { assert_eq!(render(\"tests/filters/feDiffuseLighting/single-light-source-with-invalid-child\"), 0); }\n#[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); }\n#[test] fn filters_feDiffuseLighting_single_light_source_with_title() { assert_eq!(render(\"tests/filters/feDiffuseLighting/single-light-source-with-title\"), 0); }\n#[test] fn filters_feDiffuseLighting_single_light_source() { assert_eq!(render(\"tests/filters/feDiffuseLighting/single-light-source\"), 0); }\n#[test] fn filters_feDiffuseLighting_surfaceScale_eq__10() { assert_eq!(render(\"tests/filters/feDiffuseLighting/surfaceScale=-10\"), 0); }\n#[test] fn filters_feDiffuseLighting_surfaceScale_eq_0() { assert_eq!(render(\"tests/filters/feDiffuseLighting/surfaceScale=0\"), 0); }\n#[test] fn filters_feDiffuseLighting_surfaceScale_eq_1_33() { assert_eq!(render(\"tests/filters/feDiffuseLighting/surfaceScale=1.33\"), 0); }\n#[test] fn filters_feDiffuseLighting_surfaceScale_eq_5() { assert_eq!(render(\"tests/filters/feDiffuseLighting/surfaceScale=5\"), 0); }\n#[test] fn filters_feDisplacementMap_simple_case() { assert_eq!(render(\"tests/filters/feDisplacementMap/simple-case\"), 0); }\n#[test] fn filters_feDistantLight_default_attributes() { assert_eq!(render(\"tests/filters/feDistantLight/default-attributes\"), 0); }\n#[test] fn filters_feDistantLight_negative_azimuth_and_elevation() { assert_eq!(render(\"tests/filters/feDistantLight/negative-azimuth-and-elevation\"), 0); }\n#[test] fn filters_feDistantLight_only_azimuth() { assert_eq!(render(\"tests/filters/feDistantLight/only-azimuth\"), 0); }\n#[test] fn filters_feDistantLight_only_elevation() { assert_eq!(render(\"tests/filters/feDistantLight/only-elevation\"), 0); }\n#[test] fn filters_feDropShadow_hsla_color() { assert_eq!(render(\"tests/filters/feDropShadow/hsla-color\"), 0); }\n#[test] fn filters_feDropShadow_only_stdDeviation() { assert_eq!(render(\"tests/filters/feDropShadow/only-stdDeviation\"), 0); }\n#[test] fn filters_feDropShadow_stdDeviation_eq_0() { assert_eq!(render(\"tests/filters/feDropShadow/stdDeviation=0\"), 0); }\n#[test] fn filters_feDropShadow_with_flood_color() { assert_eq!(render(\"tests/filters/feDropShadow/with-flood-color\"), 0); }\n#[test] fn filters_feDropShadow_with_flood_opacity() { assert_eq!(render(\"tests/filters/feDropShadow/with-flood-opacity\"), 0); }\n#[test] fn filters_feDropShadow_with_offset_clipped() { assert_eq!(render(\"tests/filters/feDropShadow/with-offset-clipped\"), 0); }\n#[test] fn filters_feDropShadow_with_offset() { assert_eq!(render(\"tests/filters/feDropShadow/with-offset\"), 0); }\n#[test] fn filters_feDropShadow_with_percent_offset() { assert_eq!(render(\"tests/filters/feDropShadow/with-percent-offset\"), 0); }\n#[test] fn filters_feFlood_complex_transform() { assert_eq!(render(\"tests/filters/feFlood/complex-transform\"), 0); }\n#[test] fn filters_feFlood_default_values() { assert_eq!(render(\"tests/filters/feFlood/default-values\"), 0); }\n#[test] fn filters_feFlood_partial_subregion() { assert_eq!(render(\"tests/filters/feFlood/partial-subregion\"), 0); }\n#[test] fn filters_feFlood_seagreen() { assert_eq!(render(\"tests/filters/feFlood/seagreen\"), 0); }\n#[test] fn filters_feFlood_subregion_inheritance() { assert_eq!(render(\"tests/filters/feFlood/subregion-inheritance\"), 0); }\n#[test] fn filters_feFlood_subregion_with_primitiveUnits_eq_objectBoundingBox() { assert_eq!(render(\"tests/filters/feFlood/subregion-with-primitiveUnits=objectBoundingBox\"), 0); }\n#[test] fn filters_feFlood_with_opacity_on_target_element() { assert_eq!(render(\"tests/filters/feFlood/with-opacity-on-target-element\"), 0); }\n#[test] fn filters_feFlood_with_opacity() { assert_eq!(render(\"tests/filters/feFlood/with-opacity\"), 0); }\n#[test] fn filters_feGaussianBlur_complex_transform() { assert_eq!(render(\"tests/filters/feGaussianBlur/complex-transform\"), 0); }\n#[test] fn filters_feGaussianBlur_empty_stdDeviation() { assert_eq!(render(\"tests/filters/feGaussianBlur/empty-stdDeviation\"), 0); }\n#[test] fn filters_feGaussianBlur_huge_stdDeviation() { assert_eq!(render(\"tests/filters/feGaussianBlur/huge-stdDeviation\"), 0); }\n#[test] fn filters_feGaussianBlur_negative_stdDeviation() { assert_eq!(render(\"tests/filters/feGaussianBlur/negative-stdDeviation\"), 0); }\n#[test] fn filters_feGaussianBlur_no_stdDeviation() { assert_eq!(render(\"tests/filters/feGaussianBlur/no-stdDeviation\"), 0); }\n#[test] fn filters_feGaussianBlur_simple_case() { assert_eq!(render(\"tests/filters/feGaussianBlur/simple-case\"), 0); }\n#[test] fn filters_feGaussianBlur_small_stdDeviation() { assert_eq!(render(\"tests/filters/feGaussianBlur/small-stdDeviation\"), 0); }\n#[test] fn filters_feGaussianBlur_stdDeviation_with_multiple_values() { assert_eq!(render(\"tests/filters/feGaussianBlur/stdDeviation-with-multiple-values\"), 0); }\n#[test] fn filters_feGaussianBlur_stdDeviation_with_two_different_values() { assert_eq!(render(\"tests/filters/feGaussianBlur/stdDeviation-with-two-different-values\"), 0); }\n#[test] fn filters_feGaussianBlur_stdDeviation_with_two_values() { assert_eq!(render(\"tests/filters/feGaussianBlur/stdDeviation-with-two-values\"), 0); }\n#[test] fn filters_feGaussianBlur_stdDeviation_eq_0_5() { assert_eq!(render(\"tests/filters/feGaussianBlur/stdDeviation=0-5\"), 0); }\n#[test] fn filters_feGaussianBlur_stdDeviation_eq_5_0() { assert_eq!(render(\"tests/filters/feGaussianBlur/stdDeviation=5-0\"), 0); }\n#[test] fn filters_feGaussianBlur_tiny_stdDeviation() { assert_eq!(render(\"tests/filters/feGaussianBlur/tiny-stdDeviation\"), 0); }\n#[test] fn filters_feImage_chained_feImage() { assert_eq!(render(\"tests/filters/feImage/chained-feImage\"), 0); }\n#[test] fn filters_feImage_embedded_png() { assert_eq!(render(\"tests/filters/feImage/embedded-png\"), 0); }\n#[test] fn filters_feImage_empty() { assert_eq!(render(\"tests/filters/feImage/empty\"), 0); }\n#[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); }\n#[test] fn filters_feImage_link_on_an_element_with_transform() { assert_eq!(render(\"tests/filters/feImage/link-on-an-element-with-transform\"), 0); }\n#[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); }\n#[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); }\n#[test] fn filters_feImage_link_to_an_element_with_opacity() { assert_eq!(render(\"tests/filters/feImage/link-to-an-element-with-opacity\"), 0); }\n#[test] fn filters_feImage_link_to_an_element_with_transform() { assert_eq!(render(\"tests/filters/feImage/link-to-an-element-with-transform\"), 0); }\n#[test] fn filters_feImage_link_to_an_element() { assert_eq!(render(\"tests/filters/feImage/link-to-an-element\"), 0); }\n#[test] fn filters_feImage_link_to_an_invalid_element() { assert_eq!(render(\"tests/filters/feImage/link-to-an-invalid-element\"), 0); }\n#[test] fn filters_feImage_link_to_g() { assert_eq!(render(\"tests/filters/feImage/link-to-g\"), 0); }\n#[test] fn filters_feImage_link_to_use() { assert_eq!(render(\"tests/filters/feImage/link-to-use\"), 0); }\n#[test] fn filters_feImage_preserveAspectRatio_eq_none() { assert_eq!(render(\"tests/filters/feImage/preserveAspectRatio=none\"), 0); }\n#[test] fn filters_feImage_recursive_links_1() { assert_eq!(render(\"tests/filters/feImage/recursive-links-1\"), 0); }\n#[test] fn filters_feImage_recursive_links_2() { assert_eq!(render(\"tests/filters/feImage/recursive-links-2\"), 0); }\n#[test] fn filters_feImage_self_recursive() { assert_eq!(render(\"tests/filters/feImage/self-recursive\"), 0); }\n#[test] fn filters_feImage_simple_case() { assert_eq!(render(\"tests/filters/feImage/simple-case\"), 0); }\n#[test] fn filters_feImage_svg() { assert_eq!(render(\"tests/filters/feImage/svg\"), 0); }\n#[test] fn filters_feImage_with_subregion_1() { assert_eq!(render(\"tests/filters/feImage/with-subregion-1\"), 0); }\n#[test] fn filters_feImage_with_subregion_2() { assert_eq!(render(\"tests/filters/feImage/with-subregion-2\"), 0); }\n#[test] fn filters_feImage_with_subregion_3() { assert_eq!(render(\"tests/filters/feImage/with-subregion-3\"), 0); }\n#[test] fn filters_feImage_with_subregion_4() { assert_eq!(render(\"tests/filters/feImage/with-subregion-4\"), 0); }\n#[test] fn filters_feImage_with_subregion_5() { assert_eq!(render(\"tests/filters/feImage/with-subregion-5\"), 0); }\n#[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); }\n#[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); }\n#[test] fn filters_feImage_with_x_y() { assert_eq!(render(\"tests/filters/feImage/with-x-y\"), 0); }\n#[test] fn filters_feMerge_color_interpolation_filters_eq_linearRGB() { assert_eq!(render(\"tests/filters/feMerge/color-interpolation-filters=linearRGB\"), 0); }\n#[test] fn filters_feMerge_color_interpolation_filters_eq_sRGB() { assert_eq!(render(\"tests/filters/feMerge/color-interpolation-filters=sRGB\"), 0); }\n#[test] fn filters_feMerge_complex_transform() { assert_eq!(render(\"tests/filters/feMerge/complex-transform\"), 0); }\n#[test] fn filters_feMorphology_empty_radius() { assert_eq!(render(\"tests/filters/feMorphology/empty-radius\"), 0); }\n#[test] fn filters_feMorphology_negative_radius() { assert_eq!(render(\"tests/filters/feMorphology/negative-radius\"), 0); }\n#[test] fn filters_feMorphology_no_radius() { assert_eq!(render(\"tests/filters/feMorphology/no-radius\"), 0); }\n#[test] fn filters_feMorphology_operator_eq_dilate() { assert_eq!(render(\"tests/filters/feMorphology/operator=dilate\"), 0); }\n#[test] fn filters_feMorphology_radius_with_too_many_values() { assert_eq!(render(\"tests/filters/feMorphology/radius-with-too-many-values\"), 0); }\n#[test] fn filters_feMorphology_radius_eq_0_5_with_objectBoundingBox() { assert_eq!(render(\"tests/filters/feMorphology/radius=0.5-with-objectBoundingBox\"), 0); }\n#[test] fn filters_feMorphology_radius_eq_0_5() { assert_eq!(render(\"tests/filters/feMorphology/radius=0.5\"), 0); }\n#[test] fn filters_feMorphology_radius_eq_1_10() { assert_eq!(render(\"tests/filters/feMorphology/radius=1-10\"), 0); }\n#[test] fn filters_feMorphology_radius_eq_10_0() { assert_eq!(render(\"tests/filters/feMorphology/radius=10-0\"), 0); }\n#[test] fn filters_feMorphology_radius_eq_10_1() { assert_eq!(render(\"tests/filters/feMorphology/radius=10-1\"), 0); }\n#[test] fn filters_feMorphology_simple_case() { assert_eq!(render(\"tests/filters/feMorphology/simple-case\"), 0); }\n#[test] fn filters_feMorphology_source_with_opacity() { assert_eq!(render(\"tests/filters/feMorphology/source-with-opacity\"), 0); }\n#[test] fn filters_feMorphology_zero_radius() { assert_eq!(render(\"tests/filters/feMorphology/zero-radius\"), 0); }\n#[test] fn filters_feOffset_complex_transform() { assert_eq!(render(\"tests/filters/feOffset/complex-transform\"), 0); }\n#[test] fn filters_feOffset_fractional_offset() { assert_eq!(render(\"tests/filters/feOffset/fractional-offset\"), 0); }\n#[test] fn filters_feOffset_negative_offset() { assert_eq!(render(\"tests/filters/feOffset/negative-offset\"), 0); }\n#[test] fn filters_feOffset_no_offset() { assert_eq!(render(\"tests/filters/feOffset/no-offset\"), 0); }\n#[test] fn filters_feOffset_only_dx() { assert_eq!(render(\"tests/filters/feOffset/only-dx\"), 0); }\n#[test] fn filters_feOffset_only_dy() { assert_eq!(render(\"tests/filters/feOffset/only-dy\"), 0); }\n#[test] fn filters_feOffset_percentage_values() { assert_eq!(render(\"tests/filters/feOffset/percentage-values\"), 0); }\n#[test] fn filters_feOffset_simple_case() { assert_eq!(render(\"tests/filters/feOffset/simple-case\"), 0); }\n#[test] fn filters_feOffset_with_primitiveUnits_eq_objectBoundingBox() { assert_eq!(render(\"tests/filters/feOffset/with-primitiveUnits=objectBoundingBox\"), 0); }\n#[test] fn filters_fePointLight_complex_transform() { assert_eq!(render(\"tests/filters/fePointLight/complex-transform\"), 0); }\n#[test] fn filters_fePointLight_custom_attributes() { assert_eq!(render(\"tests/filters/fePointLight/custom-attributes\"), 0); }\n#[test] fn filters_fePointLight_default_attributes() { assert_eq!(render(\"tests/filters/fePointLight/default-attributes\"), 0); }\n#[test] fn filters_fePointLight_primitiveUnits_eq_objectBoundingBox() { assert_eq!(render(\"tests/filters/fePointLight/primitiveUnits=objectBoundingBox\"), 0); }\n#[test] fn filters_feSpecularLighting_lighting_color_eq_hsla() { assert_eq!(render(\"tests/filters/feSpecularLighting/lighting-color=hsla\"), 0); }\n#[test] fn filters_feSpecularLighting_specularExponent_eq_0() { assert_eq!(render(\"tests/filters/feSpecularLighting/specularExponent=0\"), 0); }\n#[test] fn filters_feSpecularLighting_specularExponent_eq_256() { assert_eq!(render(\"tests/filters/feSpecularLighting/specularExponent=256\"), 0); }\n#[test] fn filters_feSpecularLighting_with_feDistantLight() { assert_eq!(render(\"tests/filters/feSpecularLighting/with-feDistantLight\"), 0); }\n#[test] fn filters_feSpecularLighting_with_fePointLight() { assert_eq!(render(\"tests/filters/feSpecularLighting/with-fePointLight\"), 0); }\n#[test] fn filters_feSpecularLighting_with_feSpotLight_and_specular_and_exponent() { assert_eq!(render(\"tests/filters/feSpecularLighting/with-feSpotLight-and-specular-and-exponent\"), 0); }\n#[test] fn filters_feSpecularLighting_with_feSpotLight_and_specularConstant_eq_5() { assert_eq!(render(\"tests/filters/feSpecularLighting/with-feSpotLight-and-specularConstant=5\"), 0); }\n#[test] fn filters_feSpecularLighting_with_feSpotLight() { assert_eq!(render(\"tests/filters/feSpecularLighting/with-feSpotLight\"), 0); }\n#[test] fn filters_feSpotLight_complex_transform() { assert_eq!(render(\"tests/filters/feSpotLight/complex-transform\"), 0); }\n#[test] fn filters_feSpotLight_custom_attributes() { assert_eq!(render(\"tests/filters/feSpotLight/custom-attributes\"), 0); }\n#[test] fn filters_feSpotLight_default_attributes() { assert_eq!(render(\"tests/filters/feSpotLight/default-attributes\"), 0); }\n#[test] fn filters_feSpotLight_limitingConeAngle_anti_aliasing() { assert_eq!(render(\"tests/filters/feSpotLight/limitingConeAngle-anti-aliasing\"), 0); }\n#[test] fn filters_feSpotLight_limitingConeAngle_eq__30() { assert_eq!(render(\"tests/filters/feSpotLight/limitingConeAngle=-30\"), 0); }\n#[test] fn filters_feSpotLight_limitingConeAngle_eq_0() { assert_eq!(render(\"tests/filters/feSpotLight/limitingConeAngle=0\"), 0); }\n#[test] fn filters_feSpotLight_limitingConeAngle_eq_30() { assert_eq!(render(\"tests/filters/feSpotLight/limitingConeAngle=30\"), 0); }\n#[test] fn filters_feSpotLight_primitiveUnits_eq_objectBoundingBox() { assert_eq!(render(\"tests/filters/feSpotLight/primitiveUnits=objectBoundingBox\"), 0); }\n#[test] fn filters_feSpotLight_specularExponent_eq__10() { assert_eq!(render(\"tests/filters/feSpotLight/specularExponent=-10\"), 0); }\n#[test] fn filters_feSpotLight_specularExponent_eq_0_5() { assert_eq!(render(\"tests/filters/feSpotLight/specularExponent=0.5\"), 0); }\n#[test] fn filters_feSpotLight_specularExponent_eq_10() { assert_eq!(render(\"tests/filters/feSpotLight/specularExponent=10\"), 0); }\n#[test] fn filters_feSpotLight_with_all_pointsAt() { assert_eq!(render(\"tests/filters/feSpotLight/with-all-pointsAt\"), 0); }\n#[test] fn filters_feTile_complex_transform() { assert_eq!(render(\"tests/filters/feTile/complex-transform\"), 0); }\n#[test] fn filters_feTile_empty_region() { assert_eq!(render(\"tests/filters/feTile/empty-region\"), 0); }\n#[test] fn filters_feTile_simple_case() { assert_eq!(render(\"tests/filters/feTile/simple-case\"), 0); }\n#[test] fn filters_feTile_with_region() { assert_eq!(render(\"tests/filters/feTile/with-region\"), 0); }\n#[test] fn filters_feTile_with_subregion_1() { assert_eq!(render(\"tests/filters/feTile/with-subregion-1\"), 0); }\n#[test] fn filters_feTile_with_subregion_2() { assert_eq!(render(\"tests/filters/feTile/with-subregion-2\"), 0); }\n#[test] fn filters_feTile_with_subregion_3() { assert_eq!(render(\"tests/filters/feTile/with-subregion-3\"), 0); }\n#[test] fn filters_feTurbulence_baseFrequency_eq__0_05() { assert_eq!(render(\"tests/filters/feTurbulence/baseFrequency=-0.05\"), 0); }\n#[test] fn filters_feTurbulence_baseFrequency_eq_0_01() { assert_eq!(render(\"tests/filters/feTurbulence/baseFrequency=0.01\"), 0); }\n#[test] fn filters_feTurbulence_baseFrequency_eq_0_05__0_01() { assert_eq!(render(\"tests/filters/feTurbulence/baseFrequency=0.05--0.01\"), 0); }\n#[test] fn filters_feTurbulence_baseFrequency_eq_0_05_0_01() { assert_eq!(render(\"tests/filters/feTurbulence/baseFrequency=0.05-0.01\"), 0); }\n#[test] fn filters_feTurbulence_baseFrequency_eq_0_05_0_05() { assert_eq!(render(\"tests/filters/feTurbulence/baseFrequency=0.05-0.05\"), 0); }\n#[test] fn filters_feTurbulence_baseFrequency_eq_0_05_0() { assert_eq!(render(\"tests/filters/feTurbulence/baseFrequency=0.05-0\"), 0); }\n#[test] fn filters_feTurbulence_color_interpolation_filters_eq_sRGB() { assert_eq!(render(\"tests/filters/feTurbulence/color-interpolation-filters=sRGB\"), 0); }\n#[test] fn filters_feTurbulence_complex_transform() { assert_eq!(render(\"tests/filters/feTurbulence/complex-transform\"), 0); }\n#[test] fn filters_feTurbulence_no_attributes() { assert_eq!(render(\"tests/filters/feTurbulence/no-attributes\"), 0); }\n#[test] fn filters_feTurbulence_numOctaves_eq__1() { assert_eq!(render(\"tests/filters/feTurbulence/numOctaves=-1\"), 0); }\n#[test] fn filters_feTurbulence_numOctaves_eq_0() { assert_eq!(render(\"tests/filters/feTurbulence/numOctaves=0\"), 0); }\n#[test] fn filters_feTurbulence_numOctaves_eq_5() { assert_eq!(render(\"tests/filters/feTurbulence/numOctaves=5\"), 0); }\n#[test] fn filters_feTurbulence_primitiveUnits_eq_objectBoundingBox() { assert_eq!(render(\"tests/filters/feTurbulence/primitiveUnits=objectBoundingBox\"), 0); }\n#[test] fn filters_feTurbulence_seed_eq__20() { assert_eq!(render(\"tests/filters/feTurbulence/seed=-20\"), 0); }\n#[test] fn filters_feTurbulence_seed_eq_1_5() { assert_eq!(render(\"tests/filters/feTurbulence/seed=1.5\"), 0); }\n#[test] fn filters_feTurbulence_seed_eq_20() { assert_eq!(render(\"tests/filters/feTurbulence/seed=20\"), 0); }\n#[test] fn filters_feTurbulence_stitchTiles_eq_stitch() { assert_eq!(render(\"tests/filters/feTurbulence/stitchTiles=stitch\"), 0); }\n#[test] fn filters_feTurbulence_type_eq_fractalNoise() { assert_eq!(render(\"tests/filters/feTurbulence/type=fractalNoise\"), 0); }\n#[test] fn filters_feTurbulence_type_eq_invalid() { assert_eq!(render(\"tests/filters/feTurbulence/type=invalid\"), 0); }\n#[test] fn filters_filter_color_interpolation_filters_eq_sRGB() { assert_eq!(render(\"tests/filters/filter/color-interpolation-filters=sRGB\"), 0); }\n#[test] fn filters_filter_complex_order_and_xlink_href() { assert_eq!(render(\"tests/filters/filter/complex-order-and-xlink-href\"), 0); }\n#[test] fn filters_filter_content_outside_the_canvas_2() { assert_eq!(render(\"tests/filters/filter/content-outside-the-canvas-2\"), 0); }\n#[test] fn filters_filter_content_outside_the_canvas() { assert_eq!(render(\"tests/filters/filter/content-outside-the-canvas\"), 0); }\n#[test] fn filters_filter_default_color_interpolation_filters() { assert_eq!(render(\"tests/filters/filter/default-color-interpolation-filters\"), 0); }\n#[test] fn filters_filter_everything_via_xlink_href() { assert_eq!(render(\"tests/filters/filter/everything-via-xlink-href\"), 0); }\n#[test] fn filters_filter_global_transform() { assert_eq!(render(\"tests/filters/filter/global-transform\"), 0); }\n#[test] fn filters_filter_huge_region() { assert_eq!(render(\"tests/filters/filter/huge-region\"), 0); }\n#[test] fn filters_filter_in_to_invalid_1() { assert_eq!(render(\"tests/filters/filter/in-to-invalid-1\"), 0); }\n#[test] fn filters_filter_in_to_invalid_2() { assert_eq!(render(\"tests/filters/filter/in-to-invalid-2\"), 0); }\n#[test] fn filters_filter_in_eq_BackgroundAlpha_with_enable_background() { assert_eq!(render(\"tests/filters/filter/in=BackgroundAlpha-with-enable-background\"), 0); }\n#[test] fn filters_filter_in_eq_BackgroundAlpha() { assert_eq!(render(\"tests/filters/filter/in=BackgroundAlpha\"), 0); }\n#[test] fn filters_filter_in_eq_BackgroundImage_with_enable_background() { assert_eq!(render(\"tests/filters/filter/in=BackgroundImage-with-enable-background\"), 0); }\n#[test] fn filters_filter_in_eq_BackgroundImage() { assert_eq!(render(\"tests/filters/filter/in=BackgroundImage\"), 0); }\n#[test] fn filters_filter_in_eq_FillPaint_on_g_without_children() { assert_eq!(render(\"tests/filters/filter/in=FillPaint-on-g-without-children\"), 0); }\n#[test] fn filters_filter_in_eq_FillPaint_with_gradient() { assert_eq!(render(\"tests/filters/filter/in=FillPaint-with-gradient\"), 0); }\n#[test] fn filters_filter_in_eq_FillPaint_with_pattern() { assert_eq!(render(\"tests/filters/filter/in=FillPaint-with-pattern\"), 0); }\n#[test] fn filters_filter_in_eq_FillPaint_with_target_on_g() { assert_eq!(render(\"tests/filters/filter/in=FillPaint-with-target-on-g\"), 0); }\n#[test] fn filters_filter_in_eq_FillPaint() { assert_eq!(render(\"tests/filters/filter/in=FillPaint\"), 0); }\n#[test] fn filters_filter_in_eq_SourceAlpha() { assert_eq!(render(\"tests/filters/filter/in=SourceAlpha\"), 0); }\n#[test] fn filters_filter_in_eq_StrokePaint() { assert_eq!(render(\"tests/filters/filter/in=StrokePaint\"), 0); }\n#[test] fn filters_filter_initial_transform() { assert_eq!(render(\"tests/filters/filter/initial-transform\"), 0); }\n#[test] fn filters_filter_invalid_FuncIRI() { assert_eq!(render(\"tests/filters/filter/invalid-FuncIRI\"), 0); }\n#[test] fn filters_filter_invalid_filterUnits() { assert_eq!(render(\"tests/filters/filter/invalid-filterUnits\"), 0); }\n#[test] fn filters_filter_invalid_primitive_1() { assert_eq!(render(\"tests/filters/filter/invalid-primitive-1\"), 0); }\n#[test] fn filters_filter_invalid_primitive_2() { assert_eq!(render(\"tests/filters/filter/invalid-primitive-2\"), 0); }\n#[test] fn filters_filter_invalid_region() { assert_eq!(render(\"tests/filters/filter/invalid-region\"), 0); }\n#[test] fn filters_filter_invalid_subregion() { assert_eq!(render(\"tests/filters/filter/invalid-subregion\"), 0); }\n#[test] fn filters_filter_invalid_xlink_href() { assert_eq!(render(\"tests/filters/filter/invalid-xlink-href\"), 0); }\n#[test] fn filters_filter_multiple_primitives_1() { assert_eq!(render(\"tests/filters/filter/multiple-primitives-1\"), 0); }\n#[test] fn filters_filter_multiple_primitives_2() { assert_eq!(render(\"tests/filters/filter/multiple-primitives-2\"), 0); }\n#[test] fn filters_filter_multiple_primitives_3() { assert_eq!(render(\"tests/filters/filter/multiple-primitives-3\"), 0); }\n#[test] fn filters_filter_multiple_primitives_4() { assert_eq!(render(\"tests/filters/filter/multiple-primitives-4\"), 0); }\n#[test] fn filters_filter_negative_subregion() { assert_eq!(render(\"tests/filters/filter/negative-subregion\"), 0); }\n#[test] fn filters_filter_no_children() { assert_eq!(render(\"tests/filters/filter/no-children\"), 0); }\n#[test] fn filters_filter_none() { assert_eq!(render(\"tests/filters/filter/none\"), 0); }\n#[test] fn filters_filter_on_a_thin_rect() { assert_eq!(render(\"tests/filters/filter/on-a-thin-rect\"), 0); }\n#[test] fn filters_filter_on_a_vertical_line() { assert_eq!(render(\"tests/filters/filter/on-a-vertical-line\"), 0); }\n#[test] fn filters_filter_on_an_empty_group_1() { assert_eq!(render(\"tests/filters/filter/on-an-empty-group-1\"), 0); }\n#[test] fn filters_filter_on_an_empty_group_2() { assert_eq!(render(\"tests/filters/filter/on-an-empty-group-2\"), 0); }\n#[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); }\n#[test] fn filters_filter_on_the_root_svg() { assert_eq!(render(\"tests/filters/filter/on-the-root-svg\"), 0); }\n#[test] fn filters_filter_on_zero_sized_shape() { assert_eq!(render(\"tests/filters/filter/on-zero-sized-shape\"), 0); }\n#[test] fn filters_filter_path_bbox() { assert_eq!(render(\"tests/filters/filter/path-bbox\"), 0); }\n#[test] fn filters_filter_primitiveUnits_eq_objectBoundingBox() { assert_eq!(render(\"tests/filters/filter/primitiveUnits=objectBoundingBox\"), 0); }\n#[test] fn filters_filter_recursive_xlink_href() { assert_eq!(render(\"tests/filters/filter/recursive-xlink-href\"), 0); }\n#[test] fn filters_filter_region_with_stroke() { assert_eq!(render(\"tests/filters/filter/region-with-stroke\"), 0); }\n#[test] fn filters_filter_self_recursive_xlink_href() { assert_eq!(render(\"tests/filters/filter/self-recursive-xlink-href\"), 0); }\n#[test] fn filters_filter_simple_case() { assert_eq!(render(\"tests/filters/filter/simple-case\"), 0); }\n#[test] fn filters_filter_some_attributes_via_xlink_href() { assert_eq!(render(\"tests/filters/filter/some-attributes-via-xlink-href\"), 0); }\n#[test] fn filters_filter_subregion_and_primitiveUnits_eq_objectBoundingBox_1() { assert_eq!(render(\"tests/filters/filter/subregion-and-primitiveUnits=objectBoundingBox-1\"), 0); }\n#[test] fn filters_filter_subregion_and_primitiveUnits_eq_objectBoundingBox_2() { assert_eq!(render(\"tests/filters/filter/subregion-and-primitiveUnits=objectBoundingBox-2\"), 0); }\n#[test] fn filters_filter_subregion_bigger_that_region() { assert_eq!(render(\"tests/filters/filter/subregion-bigger-that-region\"), 0); }\n#[test] fn filters_filter_transform_on_filter() { assert_eq!(render(\"tests/filters/filter/transform-on-filter\"), 0); }\n#[test] fn filters_filter_transform_on_shape_with_filter_region() { assert_eq!(render(\"tests/filters/filter/transform-on-shape-with-filter-region\"), 0); }\n#[test] fn filters_filter_transform_on_shape() { assert_eq!(render(\"tests/filters/filter/transform-on-shape\"), 0); }\n#[test] fn filters_filter_unresolved_xlink_href() { assert_eq!(render(\"tests/filters/filter/unresolved-xlink-href\"), 0); }\n#[test] fn filters_filter_with_clip_path_and_mask() { assert_eq!(render(\"tests/filters/filter/with-clip-path-and-mask\"), 0); }\n#[test] fn filters_filter_with_clip_path() { assert_eq!(render(\"tests/filters/filter/with-clip-path\"), 0); }\n#[test] fn filters_filter_with_mask_on_parent() { assert_eq!(render(\"tests/filters/filter/with-mask-on-parent\"), 0); }\n#[test] fn filters_filter_with_mask() { assert_eq!(render(\"tests/filters/filter/with-mask\"), 0); }\n#[test] fn filters_filter_with_multiple_transforms_1() { assert_eq!(render(\"tests/filters/filter/with-multiple-transforms-1\"), 0); }\n#[test] fn filters_filter_with_multiple_transforms_2() { assert_eq!(render(\"tests/filters/filter/with-multiple-transforms-2\"), 0); }\n#[test] fn filters_filter_with_region_and_filterUnits_eq_userSpaceOnUse() { assert_eq!(render(\"tests/filters/filter/with-region-and-filterUnits=userSpaceOnUse\"), 0); }\n#[test] fn filters_filter_with_region_and_subregion() { assert_eq!(render(\"tests/filters/filter/with-region-and-subregion\"), 0); }\n#[test] fn filters_filter_with_region_outside_the_canvas() { assert_eq!(render(\"tests/filters/filter/with-region-outside-the-canvas\"), 0); }\n#[test] fn filters_filter_with_region_outside_the_viewbox() { assert_eq!(render(\"tests/filters/filter/with-region-outside-the-viewbox\"), 0); }\n#[test] fn filters_filter_with_region() { assert_eq!(render(\"tests/filters/filter/with-region\"), 0); }\n#[test] fn filters_filter_with_subregion_1() { assert_eq!(render(\"tests/filters/filter/with-subregion-1\"), 0); }\n#[test] fn filters_filter_with_subregion_2() { assert_eq!(render(\"tests/filters/filter/with-subregion-2\"), 0); }\n#[test] fn filters_filter_with_subregion_3() { assert_eq!(render(\"tests/filters/filter/with-subregion-3\"), 0); }\n#[test] fn filters_filter_with_transform_outside_of_canvas() { assert_eq!(render(\"tests/filters/filter/with-transform-outside-of-canvas\"), 0); }\n#[test] fn filters_filter_without_region_and_filterUnits_eq_userSpaceOnUse() { assert_eq!(render(\"tests/filters/filter/without-region-and-filterUnits=userSpaceOnUse\"), 0); }\n#[test] fn filters_filter_zero_sized_subregion() { assert_eq!(render(\"tests/filters/filter/zero-sized-subregion\"), 0); }\n#[test] fn filters_filter_functions_blur_function_mm_value() { assert_eq!(render(\"tests/filters/filter-functions/blur-function-mm-value\"), 0); }\n#[test] fn filters_filter_functions_blur_function_negative_value() { assert_eq!(render(\"tests/filters/filter-functions/blur-function-negative-value\"), 0); }\n#[test] fn filters_filter_functions_blur_function_no_values() { assert_eq!(render(\"tests/filters/filter-functions/blur-function-no-values\"), 0); }\n#[test] fn filters_filter_functions_blur_function_percent_value() { assert_eq!(render(\"tests/filters/filter-functions/blur-function-percent-value\"), 0); }\n#[test] fn filters_filter_functions_blur_function_two_values() { assert_eq!(render(\"tests/filters/filter-functions/blur-function-two-values\"), 0); }\n#[test] fn filters_filter_functions_blur_function() { assert_eq!(render(\"tests/filters/filter-functions/blur-function\"), 0); }\n#[test] fn filters_filter_functions_color_adjust_functions_0percent() { assert_eq!(render(\"tests/filters/filter-functions/color-adjust-functions-0percent\"), 0); }\n#[test] fn filters_filter_functions_color_adjust_functions_100percent() { assert_eq!(render(\"tests/filters/filter-functions/color-adjust-functions-100percent\"), 0); }\n#[test] fn filters_filter_functions_color_adjust_functions_2() { assert_eq!(render(\"tests/filters/filter-functions/color-adjust-functions-2\"), 0); }\n#[test] fn filters_filter_functions_color_adjust_functions_200percent() { assert_eq!(render(\"tests/filters/filter-functions/color-adjust-functions-200percent\"), 0); }\n#[test] fn filters_filter_functions_color_adjust_functions_50percent() { assert_eq!(render(\"tests/filters/filter-functions/color-adjust-functions-50percent\"), 0); }\n#[test] fn filters_filter_functions_color_adjust_functions_default_value() { assert_eq!(render(\"tests/filters/filter-functions/color-adjust-functions-default-value\"), 0); }\n#[test] fn filters_filter_functions_color_adjust_functions_negative() { assert_eq!(render(\"tests/filters/filter-functions/color-adjust-functions-negative\"), 0); }\n#[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); }\n#[test] fn filters_filter_functions_drop_shadow_function_color_last() { assert_eq!(render(\"tests/filters/filter-functions/drop-shadow-function-color-last\"), 0); }\n#[test] fn filters_filter_functions_drop_shadow_function_comma_separated() { assert_eq!(render(\"tests/filters/filter-functions/drop-shadow-function-comma-separated\"), 0); }\n#[test] fn filters_filter_functions_drop_shadow_function_currentColor() { assert_eq!(render(\"tests/filters/filter-functions/drop-shadow-function-currentColor\"), 0); }\n#[test] fn filters_filter_functions_drop_shadow_function_em_values() { assert_eq!(render(\"tests/filters/filter-functions/drop-shadow-function-em-values\"), 0); }\n#[test] fn filters_filter_functions_drop_shadow_function_extra_value() { assert_eq!(render(\"tests/filters/filter-functions/drop-shadow-function-extra-value\"), 0); }\n#[test] fn filters_filter_functions_drop_shadow_function_filter_region() { assert_eq!(render(\"tests/filters/filter-functions/drop-shadow-function-filter-region\"), 0); }\n#[test] fn filters_filter_functions_drop_shadow_function_mm_values() { assert_eq!(render(\"tests/filters/filter-functions/drop-shadow-function-mm-values\"), 0); }\n#[test] fn filters_filter_functions_drop_shadow_function_no_color() { assert_eq!(render(\"tests/filters/filter-functions/drop-shadow-function-no-color\"), 0); }\n#[test] fn filters_filter_functions_drop_shadow_function_no_values() { assert_eq!(render(\"tests/filters/filter-functions/drop-shadow-function-no-values\"), 0); }\n#[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); }\n#[test] fn filters_filter_functions_drop_shadow_function_only_offset() { assert_eq!(render(\"tests/filters/filter-functions/drop-shadow-function-only-offset\"), 0); }\n#[test] fn filters_filter_functions_drop_shadow_function_percent_values() { assert_eq!(render(\"tests/filters/filter-functions/drop-shadow-function-percent-values\"), 0); }\n#[test] fn filters_filter_functions_drop_shadow_function() { assert_eq!(render(\"tests/filters/filter-functions/drop-shadow-function\"), 0); }\n#[test] fn filters_filter_functions_grayscale_and_opacity() { assert_eq!(render(\"tests/filters/filter-functions/grayscale-and-opacity\"), 0); }\n#[test] fn filters_filter_functions_hue_rotate_function_0_25turn() { assert_eq!(render(\"tests/filters/filter-functions/hue-rotate-function-0.25turn\"), 0); }\n#[test] fn filters_filter_functions_hue_rotate_function_45() { assert_eq!(render(\"tests/filters/filter-functions/hue-rotate-function-45\"), 0); }\n#[test] fn filters_filter_functions_hue_rotate_function_45deg() { assert_eq!(render(\"tests/filters/filter-functions/hue-rotate-function-45deg\"), 0); }\n#[test] fn filters_filter_functions_hue_rotate_function_45grad() { assert_eq!(render(\"tests/filters/filter-functions/hue-rotate-function-45grad\"), 0); }\n#[test] fn filters_filter_functions_hue_rotate_function_45rad() { assert_eq!(render(\"tests/filters/filter-functions/hue-rotate-function-45rad\"), 0); }\n#[test] fn filters_filter_functions_hue_rotate_function_999deg() { assert_eq!(render(\"tests/filters/filter-functions/hue-rotate-function-999deg\"), 0); }\n#[test] fn filters_filter_functions_hue_rotate_function_default_value() { assert_eq!(render(\"tests/filters/filter-functions/hue-rotate-function-default-value\"), 0); }\n#[test] fn filters_filter_functions_hue_rotate_function_zero() { assert_eq!(render(\"tests/filters/filter-functions/hue-rotate-function-zero\"), 0); }\n#[test] fn filters_filter_functions_nested_filters() { assert_eq!(render(\"tests/filters/filter-functions/nested-filters\"), 0); }\n#[test] fn filters_filter_functions_one_invalid_function_in_list() { assert_eq!(render(\"tests/filters/filter-functions/one-invalid-function-in-list\"), 0); }\n#[test] fn filters_filter_functions_one_invalid_url_in_list() { assert_eq!(render(\"tests/filters/filter-functions/one-invalid-url-in-list\"), 0); }\n#[test] fn filters_filter_functions_two_drop_shadow_function() { assert_eq!(render(\"tests/filters/filter-functions/two-drop-shadow-function\"), 0); }\n#[test] fn filters_filter_functions_two_exact_urls() { assert_eq!(render(\"tests/filters/filter-functions/two-exact-urls\"), 0); }\n#[test] fn filters_filter_functions_two_urls() { assert_eq!(render(\"tests/filters/filter-functions/two-urls\"), 0); }\n#[test] fn filters_filter_functions_url_and_grayscale() { assert_eq!(render(\"tests/filters/filter-functions/url-and-grayscale\"), 0); }\n#[test] fn filters_flood_color_hsla_color() { assert_eq!(render(\"tests/filters/flood-color/hsla-color\"), 0); }\n#[test] fn filters_flood_color_inheritance_1() { assert_eq!(render(\"tests/filters/flood-color/inheritance-1\"), 0); }\n#[test] fn filters_flood_color_inheritance_2() { assert_eq!(render(\"tests/filters/flood-color/inheritance-2\"), 0); }\n#[test] fn filters_flood_color_inheritance_3() { assert_eq!(render(\"tests/filters/flood-color/inheritance-3\"), 0); }\n#[test] fn filters_flood_color_inheritance_4() { assert_eq!(render(\"tests/filters/flood-color/inheritance-4\"), 0); }\n#[test] fn filters_flood_color_inheritance_5() { assert_eq!(render(\"tests/filters/flood-color/inheritance-5\"), 0); }\n#[test] fn filters_flood_color_simple_case() { assert_eq!(render(\"tests/filters/flood-color/simple-case\"), 0); }\n#[test] fn filters_flood_opacity_50percent() { assert_eq!(render(\"tests/filters/flood-opacity/50percent\"), 0); }\n#[test] fn filters_flood_opacity_simple_case() { assert_eq!(render(\"tests/filters/flood-opacity/simple-case\"), 0); }\n#[test] fn masking_clip_simple_case() { assert_eq!(render(\"tests/masking/clip/simple-case\"), 0); }\n#[test] fn masking_clip_rule_clip_rule_eq_evenodd() { assert_eq!(render(\"tests/masking/clip-rule/clip-rule=evenodd\"), 0); }\n#[test] fn masking_clipPath_circle_shorthand_with_stroke_box() { assert_eq!(render(\"tests/masking/clipPath/circle-shorthand-with-stroke-box\"), 0); }\n#[test] fn masking_clipPath_circle_shorthand_with_view_box() { assert_eq!(render(\"tests/masking/clipPath/circle-shorthand-with-view-box\"), 0); }\n#[test] fn masking_clipPath_circle_shorthand() { assert_eq!(render(\"tests/masking/clipPath/circle-shorthand\"), 0); }\n#[test] fn masking_clipPath_clip_path_on_child_with_transform() { assert_eq!(render(\"tests/masking/clipPath/clip-path-on-child-with-transform\"), 0); }\n#[test] fn masking_clipPath_clip_path_on_child() { assert_eq!(render(\"tests/masking/clipPath/clip-path-on-child\"), 0); }\n#[test] fn masking_clipPath_clip_path_on_children() { assert_eq!(render(\"tests/masking/clipPath/clip-path-on-children\"), 0); }\n#[test] fn masking_clipPath_clip_path_on_self_2() { assert_eq!(render(\"tests/masking/clipPath/clip-path-on-self-2\"), 0); }\n#[test] fn masking_clipPath_clip_path_on_self() { assert_eq!(render(\"tests/masking/clipPath/clip-path-on-self\"), 0); }\n#[test] fn masking_clipPath_clip_path_with_transform_on_text() { assert_eq!(render(\"tests/masking/clipPath/clip-path-with-transform-on-text\"), 0); }\n#[test] fn masking_clipPath_clip_path_with_transform() { assert_eq!(render(\"tests/masking/clipPath/clip-path-with-transform\"), 0); }\n#[test] fn masking_clipPath_clip_rule_from_parent_node() { assert_eq!(render(\"tests/masking/clipPath/clip-rule-from-parent-node\"), 0); }\n#[test] fn masking_clipPath_clip_rule_eq_evenodd() { assert_eq!(render(\"tests/masking/clipPath/clip-rule=evenodd\"), 0); }\n#[test] fn masking_clipPath_clipPathUnits_eq_objectBoundingBox() { assert_eq!(render(\"tests/masking/clipPath/clipPathUnits=objectBoundingBox\"), 0); }\n#[test] fn masking_clipPath_clipping_with_complex_text_1() { assert_eq!(render(\"tests/masking/clipPath/clipping-with-complex-text-1\"), 0); }\n#[test] fn masking_clipPath_clipping_with_complex_text_2() { assert_eq!(render(\"tests/masking/clipPath/clipping-with-complex-text-2\"), 0); }\n#[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); }\n#[test] fn masking_clipPath_clipping_with_text() { assert_eq!(render(\"tests/masking/clipPath/clipping-with-text\"), 0); }\n#[test] fn masking_clipPath_fill_has_no_effect() { assert_eq!(render(\"tests/masking/clipPath/fill-has-no-effect\"), 0); }\n#[test] fn masking_clipPath_filter_has_no_effect() { assert_eq!(render(\"tests/masking/clipPath/filter-has-no-effect\"), 0); }\n#[test] fn masking_clipPath_g_is_not_a_valid_child() { assert_eq!(render(\"tests/masking/clipPath/g-is-not-a-valid-child\"), 0); }\n#[test] fn masking_clipPath_image_is_not_a_valid_child() { assert_eq!(render(\"tests/masking/clipPath/image-is-not-a-valid-child\"), 0); }\n#[test] fn masking_clipPath_invalid_FuncIRI() { assert_eq!(render(\"tests/masking/clipPath/invalid-FuncIRI\"), 0); }\n#[test] fn masking_clipPath_invalid_clip_path_on_child() { assert_eq!(render(\"tests/masking/clipPath/invalid-clip-path-on-child\"), 0); }\n#[test] fn masking_clipPath_invalid_clip_path_on_self() { assert_eq!(render(\"tests/masking/clipPath/invalid-clip-path-on-self\"), 0); }\n#[test] fn masking_clipPath_invalid_transform_on_clipPath() { assert_eq!(render(\"tests/masking/clipPath/invalid-transform-on-clipPath\"), 0); }\n#[test] fn masking_clipPath_invisible_child_1() { assert_eq!(render(\"tests/masking/clipPath/invisible-child-1\"), 0); }\n#[test] fn masking_clipPath_invisible_child_2() { assert_eq!(render(\"tests/masking/clipPath/invisible-child-2\"), 0); }\n#[test] fn masking_clipPath_line_is_not_a_valid_child() { assert_eq!(render(\"tests/masking/clipPath/line-is-not-a-valid-child\"), 0); }\n#[test] fn masking_clipPath_malformed_path_child() { assert_eq!(render(\"tests/masking/clipPath/malformed-path-child\"), 0); }\n#[test] fn masking_clipPath_mask_has_no_effect() { assert_eq!(render(\"tests/masking/clipPath/mask-has-no-effect\"), 0); }\n#[test] fn masking_clipPath_mixed_clip_rule() { assert_eq!(render(\"tests/masking/clipPath/mixed-clip-rule\"), 0); }\n#[test] fn masking_clipPath_multiple_children() { assert_eq!(render(\"tests/masking/clipPath/multiple-children\"), 0); }\n#[test] fn masking_clipPath_nested_clip_path() { assert_eq!(render(\"tests/masking/clipPath/nested-clip-path\"), 0); }\n#[test] fn masking_clipPath_no_children() { assert_eq!(render(\"tests/masking/clipPath/no-children\"), 0); }\n#[test] fn masking_clipPath_none() { assert_eq!(render(\"tests/masking/clipPath/none\"), 0); }\n#[test] fn masking_clipPath_on_a_horizontal_line() { assert_eq!(render(\"tests/masking/clipPath/on-a-horizontal-line\"), 0); }\n#[test] fn masking_clipPath_on_the_root_svg_with_size() { assert_eq!(render(\"tests/masking/clipPath/on-the-root-svg-with-size\"), 0); }\n#[test] fn masking_clipPath_on_the_root_svg_without_size() { assert_eq!(render(\"tests/masking/clipPath/on-the-root-svg-without-size\"), 0); }\n#[test] fn masking_clipPath_opacity_has_no_effect() { assert_eq!(render(\"tests/masking/clipPath/opacity-has-no-effect\"), 0); }\n#[test] fn masking_clipPath_overlapped_shapes_with_evenodd() { assert_eq!(render(\"tests/masking/clipPath/overlapped-shapes-with-evenodd\"), 0); }\n#[test] fn masking_clipPath_recursive_on_child() { assert_eq!(render(\"tests/masking/clipPath/recursive-on-child\"), 0); }\n#[test] fn masking_clipPath_recursive_on_self() { assert_eq!(render(\"tests/masking/clipPath/recursive-on-self\"), 0); }\n#[test] fn masking_clipPath_recursive() { assert_eq!(render(\"tests/masking/clipPath/recursive\"), 0); }\n#[test] fn masking_clipPath_self_recursive() { assert_eq!(render(\"tests/masking/clipPath/self-recursive\"), 0); }\n#[test] fn masking_clipPath_simple_case() { assert_eq!(render(\"tests/masking/clipPath/simple-case\"), 0); }\n#[test] fn masking_clipPath_stroke_has_no_effect() { assert_eq!(render(\"tests/masking/clipPath/stroke-has-no-effect\"), 0); }\n#[test] fn masking_clipPath_switch_is_not_a_valid_child() { assert_eq!(render(\"tests/masking/clipPath/switch-is-not-a-valid-child\"), 0); }\n#[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); }\n#[test] fn masking_clipPath_transform_on_clipPath() { assert_eq!(render(\"tests/masking/clipPath/transform-on-clipPath\"), 0); }\n#[test] fn masking_clipPath_with_invalid_child_via_use() { assert_eq!(render(\"tests/masking/clipPath/with-invalid-child-via-use\"), 0); }\n#[test] fn masking_clipPath_with_marker_on_clip() { assert_eq!(render(\"tests/masking/clipPath/with-marker-on-clip\"), 0); }\n#[test] fn masking_clipPath_with_use_child() { assert_eq!(render(\"tests/masking/clipPath/with-use-child\"), 0); }\n#[test] fn masking_mask_color_interpolation_eq_linearRGB() { assert_eq!(render(\"tests/masking/mask/color-interpolation=linearRGB\"), 0); }\n#[test] fn masking_mask_half_width_region_with_rotation() { assert_eq!(render(\"tests/masking/mask/half-width-region-with-rotation\"), 0); }\n#[test] fn masking_mask_invalid_FuncIRI() { assert_eq!(render(\"tests/masking/mask/invalid-FuncIRI\"), 0); }\n#[test] fn masking_mask_invalid_child() { assert_eq!(render(\"tests/masking/mask/invalid-child\"), 0); }\n#[test] fn masking_mask_invisible_child_1() { assert_eq!(render(\"tests/masking/mask/invisible-child-1\"), 0); }\n#[test] fn masking_mask_invisible_child_2() { assert_eq!(render(\"tests/masking/mask/invisible-child-2\"), 0); }\n#[test] fn masking_mask_mask_on_child() { assert_eq!(render(\"tests/masking/mask/mask-on-child\"), 0); }\n#[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); }\n#[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); }\n#[test] fn masking_mask_mask_on_self() { assert_eq!(render(\"tests/masking/mask/mask-on-self\"), 0); }\n#[test] fn masking_mask_mask_type_in_style() { assert_eq!(render(\"tests/masking/mask/mask-type-in-style\"), 0); }\n#[test] fn masking_mask_mask_type_eq_alpha() { assert_eq!(render(\"tests/masking/mask/mask-type=alpha\"), 0); }\n#[test] fn masking_mask_mask_type_eq_invalid() { assert_eq!(render(\"tests/masking/mask/mask-type=invalid\"), 0); }\n#[test] fn masking_mask_mask_type_eq_luminance() { assert_eq!(render(\"tests/masking/mask/mask-type=luminance\"), 0); }\n#[test] fn masking_mask_maskContentUnits_eq_objectBoundingBox() { assert_eq!(render(\"tests/masking/mask/maskContentUnits=objectBoundingBox\"), 0); }\n#[test] fn masking_mask_maskUnits_eq_objectBoundingBox_with_percent() { assert_eq!(render(\"tests/masking/mask/maskUnits=objectBoundingBox-with-percent\"), 0); }\n#[test] fn masking_mask_maskUnits_eq_userSpaceOnUse_with_percent() { assert_eq!(render(\"tests/masking/mask/maskUnits=userSpaceOnUse-with-percent\"), 0); }\n#[test] fn masking_mask_maskUnits_eq_userSpaceOnUse_with_rect() { assert_eq!(render(\"tests/masking/mask/maskUnits=userSpaceOnUse-with-rect\"), 0); }\n#[test] fn masking_mask_maskUnits_eq_userSpaceOnUse_with_width_only() { assert_eq!(render(\"tests/masking/mask/maskUnits=userSpaceOnUse-with-width-only\"), 0); }\n#[test] fn masking_mask_maskUnits_eq_userSpaceOnUse_without_rect() { assert_eq!(render(\"tests/masking/mask/maskUnits=userSpaceOnUse-without-rect\"), 0); }\n#[test] fn masking_mask_nested_objectBoundingBox() { assert_eq!(render(\"tests/masking/mask/nested-objectBoundingBox\"), 0); }\n#[test] fn masking_mask_no_children() { assert_eq!(render(\"tests/masking/mask/no-children\"), 0); }\n#[test] fn masking_mask_none() { assert_eq!(render(\"tests/masking/mask/none\"), 0); }\n#[test] fn masking_mask_on_a_horizontal_line() { assert_eq!(render(\"tests/masking/mask/on-a-horizontal-line\"), 0); }\n#[test] fn masking_mask_on_a_small_object() { assert_eq!(render(\"tests/masking/mask/on-a-small-object\"), 0); }\n#[test] fn masking_mask_on_group_with_transform() { assert_eq!(render(\"tests/masking/mask/on-group-with-transform\"), 0); }\n#[test] fn masking_mask_recursive_on_child() { assert_eq!(render(\"tests/masking/mask/recursive-on-child\"), 0); }\n#[test] fn masking_mask_recursive_on_self() { assert_eq!(render(\"tests/masking/mask/recursive-on-self\"), 0); }\n#[test] fn masking_mask_recursive() { assert_eq!(render(\"tests/masking/mask/recursive\"), 0); }\n#[test] fn masking_mask_self_recursive() { assert_eq!(render(\"tests/masking/mask/self-recursive\"), 0); }\n#[test] fn masking_mask_simple_case() { assert_eq!(render(\"tests/masking/mask/simple-case\"), 0); }\n#[test] fn masking_mask_transform_has_no_effect() { assert_eq!(render(\"tests/masking/mask/transform-has-no-effect\"), 0); }\n#[test] fn masking_mask_transform_on_shape() { assert_eq!(render(\"tests/masking/mask/transform-on-shape\"), 0); }\n#[test] fn masking_mask_with_clip_path() { assert_eq!(render(\"tests/masking/mask/with-clip-path\"), 0); }\n#[test] fn masking_mask_with_grayscale_image() { assert_eq!(render(\"tests/masking/mask/with-grayscale-image\"), 0); }\n#[test] fn masking_mask_with_image() { assert_eq!(render(\"tests/masking/mask/with-image\"), 0); }\n#[test] fn masking_mask_with_opacity_1() { assert_eq!(render(\"tests/masking/mask/with-opacity-1\"), 0); }\n#[test] fn masking_mask_with_opacity_2() { assert_eq!(render(\"tests/masking/mask/with-opacity-2\"), 0); }\n#[test] fn masking_mask_with_opacity_3() { assert_eq!(render(\"tests/masking/mask/with-opacity-3\"), 0); }\n#[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); }\n#[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); }\n#[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); }\n#[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); }\n#[test] fn paint_servers_linearGradient_attributes_via_xlink_href() { assert_eq!(render(\"tests/paint-servers/linearGradient/attributes-via-xlink-href\"), 0); }\n#[test] fn paint_servers_linearGradient_default_attributes() { assert_eq!(render(\"tests/paint-servers/linearGradient/default-attributes\"), 0); }\n#[test] fn paint_servers_linearGradient_gradientTransform_and_transform() { assert_eq!(render(\"tests/paint-servers/linearGradient/gradientTransform-and-transform\"), 0); }\n#[test] fn paint_servers_linearGradient_gradientTransform() { assert_eq!(render(\"tests/paint-servers/linearGradient/gradientTransform\"), 0); }\n#[test] fn paint_servers_linearGradient_gradientUnits_eq_objectBoundingBox_with_percent() { assert_eq!(render(\"tests/paint-servers/linearGradient/gradientUnits=objectBoundingBox-with-percent\"), 0); }\n#[test] fn paint_servers_linearGradient_gradientUnits_eq_userSpaceOnUse_with_percent() { assert_eq!(render(\"tests/paint-servers/linearGradient/gradientUnits=userSpaceOnUse-with-percent\"), 0); }\n#[test] fn paint_servers_linearGradient_gradientUnits_eq_userSpaceOnUse() { assert_eq!(render(\"tests/paint-servers/linearGradient/gradientUnits=userSpaceOnUse\"), 0); }\n#[test] fn paint_servers_linearGradient_hsla_color() { assert_eq!(render(\"tests/paint-servers/linearGradient/hsla-color\"), 0); }\n#[test] fn paint_servers_linearGradient_invalid_child_1() { assert_eq!(render(\"tests/paint-servers/linearGradient/invalid-child-1\"), 0); }\n#[test] fn paint_servers_linearGradient_invalid_child_2() { assert_eq!(render(\"tests/paint-servers/linearGradient/invalid-child-2\"), 0); }\n#[test] fn paint_servers_linearGradient_invalid_child_3() { assert_eq!(render(\"tests/paint-servers/linearGradient/invalid-child-3\"), 0); }\n#[test] fn paint_servers_linearGradient_invalid_gradientTransform() { assert_eq!(render(\"tests/paint-servers/linearGradient/invalid-gradientTransform\"), 0); }\n#[test] fn paint_servers_linearGradient_invalid_gradientUnits() { assert_eq!(render(\"tests/paint-servers/linearGradient/invalid-gradientUnits\"), 0); }\n#[test] fn paint_servers_linearGradient_invalid_spreadMethod() { assert_eq!(render(\"tests/paint-servers/linearGradient/invalid-spreadMethod\"), 0); }\n#[test] fn paint_servers_linearGradient_invalid_xlink_href() { assert_eq!(render(\"tests/paint-servers/linearGradient/invalid-xlink-href\"), 0); }\n#[test] fn paint_servers_linearGradient_many_stops() { assert_eq!(render(\"tests/paint-servers/linearGradient/many-stops\"), 0); }\n#[test] fn paint_servers_linearGradient_no_stops() { assert_eq!(render(\"tests/paint-servers/linearGradient/no-stops\"), 0); }\n#[test] fn paint_servers_linearGradient_recursive_xlink_href_1() { assert_eq!(render(\"tests/paint-servers/linearGradient/recursive-xlink-href-1\"), 0); }\n#[test] fn paint_servers_linearGradient_recursive_xlink_href_2() { assert_eq!(render(\"tests/paint-servers/linearGradient/recursive-xlink-href-2\"), 0); }\n#[test] fn paint_servers_linearGradient_recursive_xlink_href_3() { assert_eq!(render(\"tests/paint-servers/linearGradient/recursive-xlink-href-3\"), 0); }\n#[test] fn paint_servers_linearGradient_self_recursive_xlink_href() { assert_eq!(render(\"tests/paint-servers/linearGradient/self-recursive-xlink-href\"), 0); }\n#[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); }\n#[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); }\n#[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); }\n#[test] fn paint_servers_linearGradient_single_stop() { assert_eq!(render(\"tests/paint-servers/linearGradient/single-stop\"), 0); }\n#[test] fn paint_servers_linearGradient_spreadMethod_eq_pad() { assert_eq!(render(\"tests/paint-servers/linearGradient/spreadMethod=pad\"), 0); }\n#[test] fn paint_servers_linearGradient_spreadMethod_eq_reflect() { assert_eq!(render(\"tests/paint-servers/linearGradient/spreadMethod=reflect\"), 0); }\n#[test] fn paint_servers_linearGradient_spreadMethod_eq_repeat() { assert_eq!(render(\"tests/paint-servers/linearGradient/spreadMethod=repeat\"), 0); }\n#[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); }\n#[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); }\n#[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); }\n#[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); }\n#[test] fn paint_servers_linearGradient_stops_via_xlink_href() { assert_eq!(render(\"tests/paint-servers/linearGradient/stops-via-xlink-href\"), 0); }\n#[test] fn paint_servers_linearGradient_unresolved_xlink_href() { assert_eq!(render(\"tests/paint-servers/linearGradient/unresolved-xlink-href\"), 0); }\n#[test] fn paint_servers_pattern_attributes_via_xlink_href() { assert_eq!(render(\"tests/paint-servers/pattern/attributes-via-xlink-href\"), 0); }\n#[test] fn paint_servers_pattern_child_with_invalid_FuncIRI() { assert_eq!(render(\"tests/paint-servers/pattern/child-with-invalid-FuncIRI\"), 0); }\n#[test] fn paint_servers_pattern_children_via_xlink_href() { assert_eq!(render(\"tests/paint-servers/pattern/children-via-xlink-href\"), 0); }\n#[test] fn paint_servers_pattern_display_eq_none_on_child() { assert_eq!(render(\"tests/paint-servers/pattern/display=none-on-child\"), 0); }\n#[test] fn paint_servers_pattern_everything_via_xlink_href() { assert_eq!(render(\"tests/paint-servers/pattern/everything-via-xlink-href\"), 0); }\n#[test] fn paint_servers_pattern_invalid_patternTransform() { assert_eq!(render(\"tests/paint-servers/pattern/invalid-patternTransform\"), 0); }\n#[test] fn paint_servers_pattern_invalid_patternUnits_and_patternContentUnits() { assert_eq!(render(\"tests/paint-servers/pattern/invalid-patternUnits-and-patternContentUnits\"), 0); }\n#[test] fn paint_servers_pattern_missing_height() { assert_eq!(render(\"tests/paint-servers/pattern/missing-height\"), 0); }\n#[test] fn paint_servers_pattern_missing_width() { assert_eq!(render(\"tests/paint-servers/pattern/missing-width\"), 0); }\n#[test] fn paint_servers_pattern_nested_objectBoundingBox() { assert_eq!(render(\"tests/paint-servers/pattern/nested-objectBoundingBox\"), 0); }\n#[test] fn paint_servers_pattern_no_children() { assert_eq!(render(\"tests/paint-servers/pattern/no-children\"), 0); }\n#[test] fn paint_servers_pattern_out_of_order_referencing() { assert_eq!(render(\"tests/paint-servers/pattern/out-of-order-referencing\"), 0); }\n#[test] fn paint_servers_pattern_overflow_eq_visible() { assert_eq!(render(\"tests/paint-servers/pattern/overflow=visible\"), 0); }\n#[test] fn paint_servers_pattern_pattern_on_child() { assert_eq!(render(\"tests/paint-servers/pattern/pattern-on-child\"), 0); }\n#[test] fn paint_servers_pattern_patternContentUnits_with_viewBox() { assert_eq!(render(\"tests/paint-servers/pattern/patternContentUnits-with-viewBox\"), 0); }\n#[test] fn paint_servers_pattern_patternContentUnits_eq_objectBoundingBox() { assert_eq!(render(\"tests/paint-servers/pattern/patternContentUnits=objectBoundingBox\"), 0); }\n#[test] fn paint_servers_pattern_patternUnits_eq_objectBoundingBox_with_percent() { assert_eq!(render(\"tests/paint-servers/pattern/patternUnits=objectBoundingBox-with-percent\"), 0); }\n#[test] fn paint_servers_pattern_patternUnits_eq_objectBoundingBox() { assert_eq!(render(\"tests/paint-servers/pattern/patternUnits=objectBoundingBox\"), 0); }\n#[test] fn paint_servers_pattern_patternUnits_eq_userSpaceOnUse_with_percent() { assert_eq!(render(\"tests/paint-servers/pattern/patternUnits=userSpaceOnUse-with-percent\"), 0); }\n#[test] fn paint_servers_pattern_preserveAspectRatio() { assert_eq!(render(\"tests/paint-servers/pattern/preserveAspectRatio\"), 0); }\n#[test] fn paint_servers_pattern_recursive_on_child() { assert_eq!(render(\"tests/paint-servers/pattern/recursive-on-child\"), 0); }\n#[test] fn paint_servers_pattern_self_recursive_on_child() { assert_eq!(render(\"tests/paint-servers/pattern/self-recursive-on-child\"), 0); }\n#[test] fn paint_servers_pattern_self_recursive() { assert_eq!(render(\"tests/paint-servers/pattern/self-recursive\"), 0); }\n#[test] fn paint_servers_pattern_simple_case() { assert_eq!(render(\"tests/paint-servers/pattern/simple-case\"), 0); }\n#[test] fn paint_servers_pattern_text_child() { assert_eq!(render(\"tests/paint-servers/pattern/text-child\"), 0); }\n#[test] fn paint_servers_pattern_tiny_pattern_upscaled() { assert_eq!(render(\"tests/paint-servers/pattern/tiny-pattern-upscaled\"), 0); }\n#[test] fn paint_servers_pattern_transform_and_patternTransform() { assert_eq!(render(\"tests/paint-servers/pattern/transform-and-patternTransform\"), 0); }\n#[test] fn paint_servers_pattern_viewBox_via_xlink_href() { assert_eq!(render(\"tests/paint-servers/pattern/viewBox-via-xlink-href\"), 0); }\n#[test] fn paint_servers_pattern_with_patternTransform() { assert_eq!(render(\"tests/paint-servers/pattern/with-patternTransform\"), 0); }\n#[test] fn paint_servers_pattern_with_viewBox() { assert_eq!(render(\"tests/paint-servers/pattern/with-viewBox\"), 0); }\n#[test] fn paint_servers_pattern_with_x_and_y() { assert_eq!(render(\"tests/paint-servers/pattern/with-x-and-y\"), 0); }\n#[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); }\n#[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); }\n#[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); }\n#[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); }\n#[test] fn paint_servers_radialGradient_attributes_via_xlink_href() { assert_eq!(render(\"tests/paint-servers/radialGradient/attributes-via-xlink-href\"), 0); }\n#[test] fn paint_servers_radialGradient_default_attributes() { assert_eq!(render(\"tests/paint-servers/radialGradient/default-attributes\"), 0); }\n#[test] fn paint_servers_radialGradient_fr_eq__1() { assert_eq!(render(\"tests/paint-servers/radialGradient/fr=-1\"), 0); }\n#[test] fn paint_servers_radialGradient_fr_eq_0_2() { assert_eq!(render(\"tests/paint-servers/radialGradient/fr=0.2\"), 0); }\n#[test] fn paint_servers_radialGradient_fr_eq_0_5() { assert_eq!(render(\"tests/paint-servers/radialGradient/fr=0.5\"), 0); }\n#[test] fn paint_servers_radialGradient_fr_eq_0_7() { assert_eq!(render(\"tests/paint-servers/radialGradient/fr=0.7\"), 0); }\n#[test] fn paint_servers_radialGradient_fx_resolving_1() { assert_eq!(render(\"tests/paint-servers/radialGradient/fx-resolving-1\"), 0); }\n#[test] fn paint_servers_radialGradient_fx_resolving_2() { assert_eq!(render(\"tests/paint-servers/radialGradient/fx-resolving-2\"), 0); }\n#[test] fn paint_servers_radialGradient_fx_resolving_3() { assert_eq!(render(\"tests/paint-servers/radialGradient/fx-resolving-3\"), 0); }\n#[test] fn paint_servers_radialGradient_fy_resolving_1() { assert_eq!(render(\"tests/paint-servers/radialGradient/fy-resolving-1\"), 0); }\n#[test] fn paint_servers_radialGradient_fy_resolving_2() { assert_eq!(render(\"tests/paint-servers/radialGradient/fy-resolving-2\"), 0); }\n#[test] fn paint_servers_radialGradient_fy_resolving_3() { assert_eq!(render(\"tests/paint-servers/radialGradient/fy-resolving-3\"), 0); }\n#[test] fn paint_servers_radialGradient_gradientTransform_and_transform() { assert_eq!(render(\"tests/paint-servers/radialGradient/gradientTransform-and-transform\"), 0); }\n#[test] fn paint_servers_radialGradient_gradientTransform() { assert_eq!(render(\"tests/paint-servers/radialGradient/gradientTransform\"), 0); }\n#[test] fn paint_servers_radialGradient_gradientUnits_eq_objectBoundingBox_with_percent() { assert_eq!(render(\"tests/paint-servers/radialGradient/gradientUnits=objectBoundingBox-with-percent\"), 0); }\n#[test] fn paint_servers_radialGradient_gradientUnits_eq_userSpaceOnUse_with_percent() { assert_eq!(render(\"tests/paint-servers/radialGradient/gradientUnits=userSpaceOnUse-with-percent\"), 0); }\n#[test] fn paint_servers_radialGradient_gradientUnits_eq_userSpaceOnUse() { assert_eq!(render(\"tests/paint-servers/radialGradient/gradientUnits=userSpaceOnUse\"), 0); }\n#[test] fn paint_servers_radialGradient_hsla_color() { assert_eq!(render(\"tests/paint-servers/radialGradient/hsla-color\"), 0); }\n#[test] fn paint_servers_radialGradient_invalid_gradientTransform() { assert_eq!(render(\"tests/paint-servers/radialGradient/invalid-gradientTransform\"), 0); }\n#[test] fn paint_servers_radialGradient_invalid_gradientUnits() { assert_eq!(render(\"tests/paint-servers/radialGradient/invalid-gradientUnits\"), 0); }\n#[test] fn paint_servers_radialGradient_invalid_spreadMethod() { assert_eq!(render(\"tests/paint-servers/radialGradient/invalid-spreadMethod\"), 0); }\n#[test] fn paint_servers_radialGradient_invalid_xlink_href() { assert_eq!(render(\"tests/paint-servers/radialGradient/invalid-xlink-href\"), 0); }\n#[test] fn paint_servers_radialGradient_many_stops() { assert_eq!(render(\"tests/paint-servers/radialGradient/many-stops\"), 0); }\n#[test] fn paint_servers_radialGradient_negative_r() { assert_eq!(render(\"tests/paint-servers/radialGradient/negative-r\"), 0); }\n#[test] fn paint_servers_radialGradient_no_stops() { assert_eq!(render(\"tests/paint-servers/radialGradient/no-stops\"), 0); }\n#[test] fn paint_servers_radialGradient_recursive_xlink_href() { assert_eq!(render(\"tests/paint-servers/radialGradient/recursive-xlink-href\"), 0); }\n#[test] fn paint_servers_radialGradient_self_recursive_xlink_href() { assert_eq!(render(\"tests/paint-servers/radialGradient/self-recursive-xlink-href\"), 0); }\n#[test] fn paint_servers_radialGradient_single_stop() { assert_eq!(render(\"tests/paint-servers/radialGradient/single-stop\"), 0); }\n#[test] fn paint_servers_radialGradient_spreadMethod_eq_pad() { assert_eq!(render(\"tests/paint-servers/radialGradient/spreadMethod=pad\"), 0); }\n#[test] fn paint_servers_radialGradient_spreadMethod_eq_reflect() { assert_eq!(render(\"tests/paint-servers/radialGradient/spreadMethod=reflect\"), 0); }\n#[test] fn paint_servers_radialGradient_spreadMethod_eq_repeat() { assert_eq!(render(\"tests/paint-servers/radialGradient/spreadMethod=repeat\"), 0); }\n#[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); }\n#[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); }\n#[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); }\n#[test] fn paint_servers_radialGradient_stops_via_xlink_href() { assert_eq!(render(\"tests/paint-servers/radialGradient/stops-via-xlink-href\"), 0); }\n#[test] fn paint_servers_radialGradient_unresolved_xlink_href() { assert_eq!(render(\"tests/paint-servers/radialGradient/unresolved-xlink-href\"), 0); }\n#[test] fn paint_servers_radialGradient_xlink_href_not_to_gradient() { assert_eq!(render(\"tests/paint-servers/radialGradient/xlink-href-not-to-gradient\"), 0); }\n#[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); }\n#[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); }\n#[test] fn paint_servers_radialGradient_zero_r() { assert_eq!(render(\"tests/paint-servers/radialGradient/zero-r\"), 0); }\n#[test] fn paint_servers_stop_equal_stop_color() { assert_eq!(render(\"tests/paint-servers/stop/equal-stop-color\"), 0); }\n#[test] fn paint_servers_stop_hsla_color() { assert_eq!(render(\"tests/paint-servers/stop/hsla-color\"), 0); }\n#[test] fn paint_servers_stop_invalid_offset_1() { assert_eq!(render(\"tests/paint-servers/stop/invalid-offset-1\"), 0); }\n#[test] fn paint_servers_stop_invalid_offset_2() { assert_eq!(render(\"tests/paint-servers/stop/invalid-offset-2\"), 0); }\n#[test] fn paint_servers_stop_missing_offset_1() { assert_eq!(render(\"tests/paint-servers/stop/missing-offset-1\"), 0); }\n#[test] fn paint_servers_stop_missing_offset_2() { assert_eq!(render(\"tests/paint-servers/stop/missing-offset-2\"), 0); }\n#[test] fn paint_servers_stop_missing_offset_3() { assert_eq!(render(\"tests/paint-servers/stop/missing-offset-3\"), 0); }\n#[test] fn paint_servers_stop_missing_offset_4() { assert_eq!(render(\"tests/paint-servers/stop/missing-offset-4\"), 0); }\n#[test] fn paint_servers_stop_missing_offset_5() { assert_eq!(render(\"tests/paint-servers/stop/missing-offset-5\"), 0); }\n#[test] fn paint_servers_stop_missing_offset_6() { assert_eq!(render(\"tests/paint-servers/stop/missing-offset-6\"), 0); }\n#[test] fn paint_servers_stop_missing_offset_7() { assert_eq!(render(\"tests/paint-servers/stop/missing-offset-7\"), 0); }\n#[test] fn paint_servers_stop_no_stop_color() { assert_eq!(render(\"tests/paint-servers/stop/no-stop-color\"), 0); }\n#[test] fn paint_servers_stop_offset_clamping_with_percent() { assert_eq!(render(\"tests/paint-servers/stop/offset-clamping-with-percent\"), 0); }\n#[test] fn paint_servers_stop_offset_clamping() { assert_eq!(render(\"tests/paint-servers/stop/offset-clamping\"), 0); }\n#[test] fn paint_servers_stop_offset_with_percent() { assert_eq!(render(\"tests/paint-servers/stop/offset-with-percent\"), 0); }\n#[test] fn paint_servers_stop_stop_color_with_currentColor_1() { assert_eq!(render(\"tests/paint-servers/stop/stop-color-with-currentColor-1\"), 0); }\n#[test] fn paint_servers_stop_stop_color_with_currentColor_2() { assert_eq!(render(\"tests/paint-servers/stop/stop-color-with-currentColor-2\"), 0); }\n#[test] fn paint_servers_stop_stop_color_with_currentColor_3() { assert_eq!(render(\"tests/paint-servers/stop/stop-color-with-currentColor-3\"), 0); }\n#[test] fn paint_servers_stop_stop_color_with_currentColor_4() { assert_eq!(render(\"tests/paint-servers/stop/stop-color-with-currentColor-4\"), 0); }\n#[test] fn paint_servers_stop_stop_color_with_inherit_1() { assert_eq!(render(\"tests/paint-servers/stop/stop-color-with-inherit-1\"), 0); }\n#[test] fn paint_servers_stop_stop_color_with_inherit_2() { assert_eq!(render(\"tests/paint-servers/stop/stop-color-with-inherit-2\"), 0); }\n#[test] fn paint_servers_stop_stop_color_with_inherit_3() { assert_eq!(render(\"tests/paint-servers/stop/stop-color-with-inherit-3\"), 0); }\n#[test] fn paint_servers_stop_stop_color_with_inherit_4() { assert_eq!(render(\"tests/paint-servers/stop/stop-color-with-inherit-4\"), 0); }\n#[test] fn paint_servers_stop_stop_color_with_inherit_5() { assert_eq!(render(\"tests/paint-servers/stop/stop-color-with-inherit-5\"), 0); }\n#[test] fn paint_servers_stop_stop_with_smaller_offset() { assert_eq!(render(\"tests/paint-servers/stop/stop-with-smaller-offset\"), 0); }\n#[test] fn paint_servers_stop_stops_with_equal_offset_1() { assert_eq!(render(\"tests/paint-servers/stop/stops-with-equal-offset-1\"), 0); }\n#[test] fn paint_servers_stop_stops_with_equal_offset_2() { assert_eq!(render(\"tests/paint-servers/stop/stops-with-equal-offset-2\"), 0); }\n#[test] fn paint_servers_stop_stops_with_equal_offset_3() { assert_eq!(render(\"tests/paint-servers/stop/stops-with-equal-offset-3\"), 0); }\n#[test] fn paint_servers_stop_stops_with_equal_offset_4() { assert_eq!(render(\"tests/paint-servers/stop/stops-with-equal-offset-4\"), 0); }\n#[test] fn paint_servers_stop_stops_with_equal_offset_5() { assert_eq!(render(\"tests/paint-servers/stop/stops-with-equal-offset-5\"), 0); }\n#[test] fn paint_servers_stop_stops_with_equal_offset_6() { assert_eq!(render(\"tests/paint-servers/stop/stops-with-equal-offset-6\"), 0); }\n#[test] fn paint_servers_stop_zero_offset_in_the_middle() { assert_eq!(render(\"tests/paint-servers/stop/zero-offset-in-the-middle\"), 0); }\n#[test] fn paint_servers_stop_color_simple_case() { assert_eq!(render(\"tests/paint-servers/stop-color/simple-case\"), 0); }\n#[test] fn paint_servers_stop_opacity_50percent() { assert_eq!(render(\"tests/paint-servers/stop-opacity/50percent\"), 0); }\n#[test] fn paint_servers_stop_opacity_simple_case() { assert_eq!(render(\"tests/paint-servers/stop-opacity/simple-case\"), 0); }\n#[test] fn painting_color_inherit() { assert_eq!(render(\"tests/painting/color/inherit\"), 0); }\n#[test] fn painting_color_recursive_nested_context_without_color() { assert_eq!(render(\"tests/painting/color/recursive-nested-context-without-color\"), 0); }\n#[test] fn painting_color_recursive_nested_context() { assert_eq!(render(\"tests/painting/color/recursive-nested-context\"), 0); }\n#[test] fn painting_color_simple_case() { assert_eq!(render(\"tests/painting/color/simple-case\"), 0); }\n#[test] fn painting_context_in_marker() { assert_eq!(render(\"tests/painting/context/in-marker\"), 0); }\n#[test] fn painting_context_in_nested_marker() { assert_eq!(render(\"tests/painting/context/in-nested-marker\"), 0); }\n#[test] fn painting_context_in_nested_use_and_marker() { assert_eq!(render(\"tests/painting/context/in-nested-use-and-marker\"), 0); }\n#[test] fn painting_context_in_nested_use() { assert_eq!(render(\"tests/painting/context/in-nested-use\"), 0); }\n#[test] fn painting_context_in_use() { assert_eq!(render(\"tests/painting/context/in-use\"), 0); }\n#[test] fn painting_context_on_shape_with_zero_size_bbox() { assert_eq!(render(\"tests/painting/context/on-shape-with-zero-size-bbox\"), 0); }\n#[test] fn painting_context_with_gradient_and_gradient_transform() { assert_eq!(render(\"tests/painting/context/with-gradient-and-gradient-transform\"), 0); }\n#[test] fn painting_context_with_gradient_in_use() { assert_eq!(render(\"tests/painting/context/with-gradient-in-use\"), 0); }\n#[test] fn painting_context_with_gradient_on_marker() { assert_eq!(render(\"tests/painting/context/with-gradient-on-marker\"), 0); }\n#[test] fn painting_context_with_pattern_and_transform_in_use() { assert_eq!(render(\"tests/painting/context/with-pattern-and-transform-in-use\"), 0); }\n#[test] fn painting_context_with_pattern_in_use() { assert_eq!(render(\"tests/painting/context/with-pattern-in-use\"), 0); }\n#[test] fn painting_context_with_pattern_objectBoundingBox_in_use() { assert_eq!(render(\"tests/painting/context/with-pattern-objectBoundingBox-in-use\"), 0); }\n#[test] fn painting_context_with_pattern_on_marker() { assert_eq!(render(\"tests/painting/context/with-pattern-on-marker\"), 0); }\n#[test] fn painting_context_with_text() { assert_eq!(render(\"tests/painting/context/with-text\"), 0); }\n#[test] fn painting_context_with_viewbox() { assert_eq!(render(\"tests/painting/context/with-viewbox\"), 0); }\n#[test] fn painting_context_without_context_element() { assert_eq!(render(\"tests/painting/context/without-context-element\"), 0); }\n#[test] fn painting_display_bBox_impact() { assert_eq!(render(\"tests/painting/display/bBox-impact\"), 0); }\n#[test] fn painting_display_none_on_clipPath() { assert_eq!(render(\"tests/painting/display/none-on-clipPath\"), 0); }\n#[test] fn painting_display_none_on_defs() { assert_eq!(render(\"tests/painting/display/none-on-defs\"), 0); }\n#[test] fn painting_display_none_on_linearGradient() { assert_eq!(render(\"tests/painting/display/none-on-linearGradient\"), 0); }\n#[test] fn painting_display_none_on_rect() { assert_eq!(render(\"tests/painting/display/none-on-rect\"), 0); }\n#[test] fn painting_display_none_on_svg() { assert_eq!(render(\"tests/painting/display/none-on-svg\"), 0); }\n#[test] fn painting_display_none_on_tref() { assert_eq!(render(\"tests/painting/display/none-on-tref\"), 0); }\n#[test] fn painting_display_none_on_tspan_1() { assert_eq!(render(\"tests/painting/display/none-on-tspan-1\"), 0); }\n#[test] fn painting_display_none_on_tspan_2() { assert_eq!(render(\"tests/painting/display/none-on-tspan-2\"), 0); }\n#[test] fn painting_fill_RGB_color() { assert_eq!(render(\"tests/painting/fill/#RGB-color\"), 0); }\n#[test] fn painting_fill_RGBA() { assert_eq!(render(\"tests/painting/fill/#RGBA\"), 0); }\n#[test] fn painting_fill_RRGGBB_color() { assert_eq!(render(\"tests/painting/fill/#RRGGBB-color\"), 0); }\n#[test] fn painting_fill_RRGGBB_uppercase_color() { assert_eq!(render(\"tests/painting/fill/#RRGGBB-uppercase-color\"), 0); }\n#[test] fn painting_fill_RRGGBBAA() { assert_eq!(render(\"tests/painting/fill/#RRGGBBAA\"), 0); }\n#[test] fn painting_fill_currentColor_without_parent() { assert_eq!(render(\"tests/painting/fill/currentColor-without-parent\"), 0); }\n#[test] fn painting_fill_currentColor() { assert_eq!(render(\"tests/painting/fill/currentColor\"), 0); }\n#[test] fn painting_fill_double_inherit() { assert_eq!(render(\"tests/painting/fill/double-inherit\"), 0); }\n#[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); }\n#[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); }\n#[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); }\n#[test] fn painting_fill_funcIRI_to_an_unsupported_element() { assert_eq!(render(\"tests/painting/fill/funcIRI-to-an-unsupported-element\"), 0); }\n#[test] fn painting_fill_funcIRI_with_a_fallback_color() { assert_eq!(render(\"tests/painting/fill/funcIRI-with-a-fallback-color\"), 0); }\n#[test] fn painting_fill_hsl_120_100percent_25percent() { assert_eq!(render(\"tests/painting/fill/hsl-120-100percent-25percent\"), 0); }\n#[test] fn painting_fill_hsl_120_200percent_25percent() { assert_eq!(render(\"tests/painting/fill/hsl-120-200percent-25percent\"), 0); }\n#[test] fn painting_fill_hsl_360_100percent_25percent() { assert_eq!(render(\"tests/painting/fill/hsl-360-100percent-25percent\"), 0); }\n#[test] fn painting_fill_hsl_999_100percent_25percent() { assert_eq!(render(\"tests/painting/fill/hsl-999-100percent-25percent\"), 0); }\n#[test] fn painting_fill_hsl_with_alpha() { assert_eq!(render(\"tests/painting/fill/hsl-with-alpha\"), 0); }\n#[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); }\n#[test] fn painting_fill_icc_color() { assert_eq!(render(\"tests/painting/fill/icc-color\"), 0); }\n#[test] fn painting_fill_inherit_without_parent() { assert_eq!(render(\"tests/painting/fill/inherit-without-parent\"), 0); }\n#[test] fn painting_fill_inherit() { assert_eq!(render(\"tests/painting/fill/inherit\"), 0); }\n#[test] fn painting_fill_invalid_RRGGBB_1() { assert_eq!(render(\"tests/painting/fill/invalid-#RRGGBB-1\"), 0); }\n#[test] fn painting_fill_invalid_RRGGBB_2() { assert_eq!(render(\"tests/painting/fill/invalid-#RRGGBB-2\"), 0); }\n#[test] fn painting_fill_invalid_RRGGBB_3() { assert_eq!(render(\"tests/painting/fill/invalid-#RRGGBB-3\"), 0); }\n#[test] fn painting_fill_invalid_FuncIRI_with_a_currentColor_fallback() { assert_eq!(render(\"tests/painting/fill/invalid-FuncIRI-with-a-currentColor-fallback\"), 0); }\n#[test] fn painting_fill_invalid_FuncIRI_with_a_fallback_color() { assert_eq!(render(\"tests/painting/fill/invalid-FuncIRI-with-a-fallback-color\"), 0); }\n#[test] fn painting_fill_linear_gradient_on_shape() { assert_eq!(render(\"tests/painting/fill/linear-gradient-on-shape\"), 0); }\n#[test] fn painting_fill_linear_gradient_on_text() { assert_eq!(render(\"tests/painting/fill/linear-gradient-on-text\"), 0); }\n#[test] fn painting_fill_missing_FuncIRI_with_a_currentColor_fallback() { assert_eq!(render(\"tests/painting/fill/missing-FuncIRI-with-a-currentColor-fallback\"), 0); }\n#[test] fn painting_fill_named_color_in_mixedcase() { assert_eq!(render(\"tests/painting/fill/named-color-in-mixedcase\"), 0); }\n#[test] fn painting_fill_named_color_in_uppercase() { assert_eq!(render(\"tests/painting/fill/named-color-in-uppercase\"), 0); }\n#[test] fn painting_fill_named_color() { assert_eq!(render(\"tests/painting/fill/named-color\"), 0); }\n#[test] fn painting_fill_none() { assert_eq!(render(\"tests/painting/fill/none\"), 0); }\n#[test] fn painting_fill_not_trimmed_attribute_value() { assert_eq!(render(\"tests/painting/fill/not-trimmed-attribute-value\"), 0); }\n#[test] fn painting_fill_pattern_on_shape() { assert_eq!(render(\"tests/painting/fill/pattern-on-shape\"), 0); }\n#[test] fn painting_fill_pattern_on_text() { assert_eq!(render(\"tests/painting/fill/pattern-on-text\"), 0); }\n#[test] fn painting_fill_radial_gradient_on_shape() { assert_eq!(render(\"tests/painting/fill/radial-gradient-on-shape\"), 0); }\n#[test] fn painting_fill_radial_gradient_on_text() { assert_eq!(render(\"tests/painting/fill/radial-gradient-on-text\"), 0); }\n#[test] fn painting_fill_random_value() { assert_eq!(render(\"tests/painting/fill/random-value\"), 0); }\n#[test] fn painting_fill_rgb_0_127_0_0_5() { assert_eq!(render(\"tests/painting/fill/rgb-0-127-0-0.5\"), 0); }\n#[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); }\n#[test] fn painting_fill_rgb_color_with_extra_spaces() { assert_eq!(render(\"tests/painting/fill/rgb-color-with-extra-spaces\"), 0); }\n#[test] fn painting_fill_rgb_color_with_float_percentage_values() { assert_eq!(render(\"tests/painting/fill/rgb-color-with-float-percentage-values\"), 0); }\n#[test] fn painting_fill_rgb_color_with_floats() { assert_eq!(render(\"tests/painting/fill/rgb-color-with-floats\"), 0); }\n#[test] fn painting_fill_rgb_color_with_percentage_overflow() { assert_eq!(render(\"tests/painting/fill/rgb-color-with-percentage-overflow\"), 0); }\n#[test] fn painting_fill_rgb_color_with_percentage_values() { assert_eq!(render(\"tests/painting/fill/rgb-color-with-percentage-values\"), 0); }\n#[test] fn painting_fill_rgb_color() { assert_eq!(render(\"tests/painting/fill/rgb-color\"), 0); }\n#[test] fn painting_fill_rgb_int_int_int() { assert_eq!(render(\"tests/painting/fill/rgb-int-int-int\"), 0); }\n#[test] fn painting_fill_rgba_0_127_0__1() { assert_eq!(render(\"tests/painting/fill/rgba-0-127-0--1\"), 0); }\n#[test] fn painting_fill_rgba_0_127_0_0_5() { assert_eq!(render(\"tests/painting/fill/rgba-0-127-0-0.5\"), 0); }\n#[test] fn painting_fill_rgba_0_127_0_0() { assert_eq!(render(\"tests/painting/fill/rgba-0-127-0-0\"), 0); }\n#[test] fn painting_fill_rgba_0_127_0_1() { assert_eq!(render(\"tests/painting/fill/rgba-0-127-0-1\"), 0); }\n#[test] fn painting_fill_rgba_0_127_0_2() { assert_eq!(render(\"tests/painting/fill/rgba-0-127-0-2\"), 0); }\n#[test] fn painting_fill_rgba_0_127_0_50percent() { assert_eq!(render(\"tests/painting/fill/rgba-0-127-0-50percent\"), 0); }\n#[test] fn painting_fill_rgba_0_50percent_0_0_5() { assert_eq!(render(\"tests/painting/fill/rgba-0-50percent-0-0.5\"), 0); }\n#[test] fn painting_fill_rgba_0percent_50percent_0percent_0_5() { assert_eq!(render(\"tests/painting/fill/rgba-0percent-50percent-0percent-0.5\"), 0); }\n#[test] fn painting_fill_transparent() { assert_eq!(render(\"tests/painting/fill/transparent\"), 0); }\n#[test] fn painting_fill_uppercase_rgb_color() { assert_eq!(render(\"tests/painting/fill/uppercase-rgb-color\"), 0); }\n#[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); }\n#[test] fn painting_fill_opacity_50percent() { assert_eq!(render(\"tests/painting/fill-opacity/50percent\"), 0); }\n#[test] fn painting_fill_opacity_half_opacity() { assert_eq!(render(\"tests/painting/fill-opacity/half-opacity\"), 0); }\n#[test] fn painting_fill_opacity_nested() { assert_eq!(render(\"tests/painting/fill-opacity/nested\"), 0); }\n#[test] fn painting_fill_opacity_on_parent() { assert_eq!(render(\"tests/painting/fill-opacity/on-parent\"), 0); }\n#[test] fn painting_fill_opacity_on_text() { assert_eq!(render(\"tests/painting/fill-opacity/on-text\"), 0); }\n#[test] fn painting_fill_opacity_with_linearGradient() { assert_eq!(render(\"tests/painting/fill-opacity/with-linearGradient\"), 0); }\n#[test] fn painting_fill_opacity_with_opacity() { assert_eq!(render(\"tests/painting/fill-opacity/with-opacity\"), 0); }\n#[test] fn painting_fill_opacity_with_pattern() { assert_eq!(render(\"tests/painting/fill-opacity/with-pattern\"), 0); }\n#[test] fn painting_fill_rule_evenodd() { assert_eq!(render(\"tests/painting/fill-rule/evenodd\"), 0); }\n#[test] fn painting_fill_rule_nonzero() { assert_eq!(render(\"tests/painting/fill-rule/nonzero\"), 0); }\n#[test] fn painting_image_rendering_high_quality() { assert_eq!(render(\"tests/painting/image-rendering/high-quality\"), 0); }\n#[test] fn painting_image_rendering_on_feImage() { assert_eq!(render(\"tests/painting/image-rendering/on-feImage\"), 0); }\n#[test] fn painting_image_rendering_optimizeSpeed_on_SVG() { assert_eq!(render(\"tests/painting/image-rendering/optimizeSpeed-on-SVG\"), 0); }\n#[test] fn painting_image_rendering_optimizeSpeed() { assert_eq!(render(\"tests/painting/image-rendering/optimizeSpeed\"), 0); }\n#[test] fn painting_isolation_as_property() { assert_eq!(render(\"tests/painting/isolation/as-property\"), 0); }\n#[test] fn painting_isolation_isolate() { assert_eq!(render(\"tests/painting/isolation/isolate\"), 0); }\n#[test] fn painting_marker_default_clip() { assert_eq!(render(\"tests/painting/marker/default-clip\"), 0); }\n#[test] fn painting_marker_empty() { assert_eq!(render(\"tests/painting/marker/empty\"), 0); }\n#[test] fn painting_marker_inheritance_1() { assert_eq!(render(\"tests/painting/marker/inheritance-1\"), 0); }\n#[test] fn painting_marker_inheritance_2() { assert_eq!(render(\"tests/painting/marker/inheritance-2\"), 0); }\n#[test] fn painting_marker_invalid_child() { assert_eq!(render(\"tests/painting/marker/invalid-child\"), 0); }\n#[test] fn painting_marker_marker_on_circle() { assert_eq!(render(\"tests/painting/marker/marker-on-circle\"), 0); }\n#[test] fn painting_marker_marker_on_line() { assert_eq!(render(\"tests/painting/marker/marker-on-line\"), 0); }\n#[test] fn painting_marker_marker_on_polygon() { assert_eq!(render(\"tests/painting/marker/marker-on-polygon\"), 0); }\n#[test] fn painting_marker_marker_on_polyline() { assert_eq!(render(\"tests/painting/marker/marker-on-polyline\"), 0); }\n#[test] fn painting_marker_marker_on_rect() { assert_eq!(render(\"tests/painting/marker/marker-on-rect\"), 0); }\n#[test] fn painting_marker_marker_on_rounded_rect() { assert_eq!(render(\"tests/painting/marker/marker-on-rounded-rect\"), 0); }\n#[test] fn painting_marker_marker_on_text() { assert_eq!(render(\"tests/painting/marker/marker-on-text\"), 0); }\n#[test] fn painting_marker_marker_with_a_negative_size() { assert_eq!(render(\"tests/painting/marker/marker-with-a-negative-size\"), 0); }\n#[test] fn painting_marker_nested() { assert_eq!(render(\"tests/painting/marker/nested\"), 0); }\n#[test] fn painting_marker_no_stroke_on_target() { assert_eq!(render(\"tests/painting/marker/no-stroke-on-target\"), 0); }\n#[test] fn painting_marker_on_ArcTo() { assert_eq!(render(\"tests/painting/marker/on-ArcTo\"), 0); }\n#[test] fn painting_marker_only_marker_end() { assert_eq!(render(\"tests/painting/marker/only-marker-end\"), 0); }\n#[test] fn painting_marker_only_marker_mid() { assert_eq!(render(\"tests/painting/marker/only-marker-mid\"), 0); }\n#[test] fn painting_marker_only_marker_start() { assert_eq!(render(\"tests/painting/marker/only-marker-start\"), 0); }\n#[test] fn painting_marker_orient_eq__45() { assert_eq!(render(\"tests/painting/marker/orient=-45\"), 0); }\n#[test] fn painting_marker_orient_eq_0_25turn() { assert_eq!(render(\"tests/painting/marker/orient=0.25turn\"), 0); }\n#[test] fn painting_marker_orient_eq_1_5rad() { assert_eq!(render(\"tests/painting/marker/orient=1.5rad\"), 0); }\n#[test] fn painting_marker_orient_eq_30() { assert_eq!(render(\"tests/painting/marker/orient=30\"), 0); }\n#[test] fn painting_marker_orient_eq_40grad() { assert_eq!(render(\"tests/painting/marker/orient=40grad\"), 0); }\n#[test] fn painting_marker_orient_eq_9999() { assert_eq!(render(\"tests/painting/marker/orient=9999\"), 0); }\n#[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); }\n#[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); }\n#[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); }\n#[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); }\n#[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); }\n#[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); }\n#[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); }\n#[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); }\n#[test] fn painting_marker_orient_eq_auto_on_M_C_L() { assert_eq!(render(\"tests/painting/marker/orient=auto-on-M-C-L\"), 0); }\n#[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); }\n#[test] fn painting_marker_orient_eq_auto_on_M_L_C() { assert_eq!(render(\"tests/painting/marker/orient=auto-on-M-L-C\"), 0); }\n#[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); }\n#[test] fn painting_marker_orient_eq_auto_on_M_L_L() { assert_eq!(render(\"tests/painting/marker/orient=auto-on-M-L-L\"), 0); }\n#[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); }\n#[test] fn painting_marker_orient_eq_auto_on_M_L_Z() { assert_eq!(render(\"tests/painting/marker/orient=auto-on-M-L-Z\"), 0); }\n#[test] fn painting_marker_orient_eq_auto_on_M_L() { assert_eq!(render(\"tests/painting/marker/orient=auto-on-M-L\"), 0); }\n#[test] fn painting_marker_orient_eq_auto_start_reverse() { assert_eq!(render(\"tests/painting/marker/orient=auto-start-reverse\"), 0); }\n#[test] fn painting_marker_percent_values() { assert_eq!(render(\"tests/painting/marker/percent-values\"), 0); }\n#[test] fn painting_marker_recursive_1() { assert_eq!(render(\"tests/painting/marker/recursive-1\"), 0); }\n#[test] fn painting_marker_recursive_2() { assert_eq!(render(\"tests/painting/marker/recursive-2\"), 0); }\n#[test] fn painting_marker_recursive_3() { assert_eq!(render(\"tests/painting/marker/recursive-3\"), 0); }\n#[test] fn painting_marker_recursive_4() { assert_eq!(render(\"tests/painting/marker/recursive-4\"), 0); }\n#[test] fn painting_marker_recursive_5() { assert_eq!(render(\"tests/painting/marker/recursive-5\"), 0); }\n#[test] fn painting_marker_target_with_subpaths_1() { assert_eq!(render(\"tests/painting/marker/target-with-subpaths-1\"), 0); }\n#[test] fn painting_marker_target_with_subpaths_2() { assert_eq!(render(\"tests/painting/marker/target-with-subpaths-2\"), 0); }\n#[test] fn painting_marker_the_marker_property_in_CSS() { assert_eq!(render(\"tests/painting/marker/the-marker-property-in-CSS\"), 0); }\n#[test] fn painting_marker_the_marker_property() { assert_eq!(render(\"tests/painting/marker/the-marker-property\"), 0); }\n#[test] fn painting_marker_with_a_large_stroke() { assert_eq!(render(\"tests/painting/marker/with-a-large-stroke\"), 0); }\n#[test] fn painting_marker_with_a_text_child() { assert_eq!(render(\"tests/painting/marker/with-a-text-child\"), 0); }\n#[test] fn painting_marker_with_an_image_child() { assert_eq!(render(\"tests/painting/marker/with-an-image-child\"), 0); }\n#[test] fn painting_marker_with_invalid_markerUnits() { assert_eq!(render(\"tests/painting/marker/with-invalid-markerUnits\"), 0); }\n#[test] fn painting_marker_with_markerUnits_eq_userSpaceOnUse() { assert_eq!(render(\"tests/painting/marker/with-markerUnits=userSpaceOnUse\"), 0); }\n#[test] fn painting_marker_with_viewBox_1() { assert_eq!(render(\"tests/painting/marker/with-viewBox-1\"), 0); }\n#[test] fn painting_marker_with_viewBox_2() { assert_eq!(render(\"tests/painting/marker/with-viewBox-2\"), 0); }\n#[test] fn painting_marker_zero_length_path_1() { assert_eq!(render(\"tests/painting/marker/zero-length-path-1\"), 0); }\n#[test] fn painting_marker_zero_length_path_2() { assert_eq!(render(\"tests/painting/marker/zero-length-path-2\"), 0); }\n#[test] fn painting_marker_zero_sized_stroke() { assert_eq!(render(\"tests/painting/marker/zero-sized-stroke\"), 0); }\n#[test] fn painting_marker_zero_sized() { assert_eq!(render(\"tests/painting/marker/zero-sized\"), 0); }\n#[test] fn painting_mix_blend_mode_as_property() { assert_eq!(render(\"tests/painting/mix-blend-mode/as-property\"), 0); }\n#[test] fn painting_mix_blend_mode_color_burn() { assert_eq!(render(\"tests/painting/mix-blend-mode/color-burn\"), 0); }\n#[test] fn painting_mix_blend_mode_color_dodge() { assert_eq!(render(\"tests/painting/mix-blend-mode/color-dodge\"), 0); }\n#[test] fn painting_mix_blend_mode_color() { assert_eq!(render(\"tests/painting/mix-blend-mode/color\"), 0); }\n#[test] fn painting_mix_blend_mode_darken() { assert_eq!(render(\"tests/painting/mix-blend-mode/darken\"), 0); }\n#[test] fn painting_mix_blend_mode_difference() { assert_eq!(render(\"tests/painting/mix-blend-mode/difference\"), 0); }\n#[test] fn painting_mix_blend_mode_exclusion() { assert_eq!(render(\"tests/painting/mix-blend-mode/exclusion\"), 0); }\n#[test] fn painting_mix_blend_mode_hard_light() { assert_eq!(render(\"tests/painting/mix-blend-mode/hard-light\"), 0); }\n#[test] fn painting_mix_blend_mode_hue() { assert_eq!(render(\"tests/painting/mix-blend-mode/hue\"), 0); }\n#[test] fn painting_mix_blend_mode_lighten() { assert_eq!(render(\"tests/painting/mix-blend-mode/lighten\"), 0); }\n#[test] fn painting_mix_blend_mode_luminosity() { assert_eq!(render(\"tests/painting/mix-blend-mode/luminosity\"), 0); }\n#[test] fn painting_mix_blend_mode_multiply() { assert_eq!(render(\"tests/painting/mix-blend-mode/multiply\"), 0); }\n#[test] fn painting_mix_blend_mode_normal() { assert_eq!(render(\"tests/painting/mix-blend-mode/normal\"), 0); }\n#[test] fn painting_mix_blend_mode_opacity_on_element() { assert_eq!(render(\"tests/painting/mix-blend-mode/opacity-on-element\"), 0); }\n#[test] fn painting_mix_blend_mode_opacity_on_group() { assert_eq!(render(\"tests/painting/mix-blend-mode/opacity-on-group\"), 0); }\n#[test] fn painting_mix_blend_mode_overlay() { assert_eq!(render(\"tests/painting/mix-blend-mode/overlay\"), 0); }\n#[test] fn painting_mix_blend_mode_saturation() { assert_eq!(render(\"tests/painting/mix-blend-mode/saturation\"), 0); }\n#[test] fn painting_mix_blend_mode_screen() { assert_eq!(render(\"tests/painting/mix-blend-mode/screen\"), 0); }\n#[test] fn painting_mix_blend_mode_soft_light() { assert_eq!(render(\"tests/painting/mix-blend-mode/soft-light\"), 0); }\n#[test] fn painting_mix_blend_mode_xor() { assert_eq!(render(\"tests/painting/mix-blend-mode/xor\"), 0); }\n#[test] fn painting_opacity_50percent() { assert_eq!(render(\"tests/painting/opacity/50percent\"), 0); }\n#[test] fn painting_opacity_bBox_impact() { assert_eq!(render(\"tests/painting/opacity/bBox-impact\"), 0); }\n#[test] fn painting_opacity_clamp_value_1() { assert_eq!(render(\"tests/painting/opacity/clamp-value-1\"), 0); }\n#[test] fn painting_opacity_clamp_value_2() { assert_eq!(render(\"tests/painting/opacity/clamp-value-2\"), 0); }\n#[test] fn painting_opacity_group_opacity() { assert_eq!(render(\"tests/painting/opacity/group-opacity\"), 0); }\n#[test] fn painting_opacity_invalid_value_2() { assert_eq!(render(\"tests/painting/opacity/invalid-value-2\"), 0); }\n#[test] fn painting_opacity_mixed_group_opacity() { assert_eq!(render(\"tests/painting/opacity/mixed-group-opacity\"), 0); }\n#[test] fn painting_opacity_on_an_invalid_element() { assert_eq!(render(\"tests/painting/opacity/on-an-invalid-element\"), 0); }\n#[test] fn painting_opacity_on_the_root_svg() { assert_eq!(render(\"tests/painting/opacity/on-the-root-svg\"), 0); }\n#[test] fn painting_overflow_auto_on_marker() { assert_eq!(render(\"tests/painting/overflow/auto-on-marker\"), 0); }\n#[test] fn painting_overflow_inherit_on_marker_without_parent() { assert_eq!(render(\"tests/painting/overflow/inherit-on-marker-without-parent\"), 0); }\n#[test] fn painting_overflow_inherit_on_marker() { assert_eq!(render(\"tests/painting/overflow/inherit-on-marker\"), 0); }\n#[test] fn painting_overflow_scroll_on_marker() { assert_eq!(render(\"tests/painting/overflow/scroll-on-marker\"), 0); }\n#[test] fn painting_overflow_visible_on_marker() { assert_eq!(render(\"tests/painting/overflow/visible-on-marker\"), 0); }\n#[test] fn painting_paint_order_duplicates() { assert_eq!(render(\"tests/painting/paint-order/duplicates\"), 0); }\n#[test] fn painting_paint_order_fill_markers_stroke() { assert_eq!(render(\"tests/painting/paint-order/fill-markers-stroke\"), 0); }\n#[test] fn painting_paint_order_fill() { assert_eq!(render(\"tests/painting/paint-order/fill\"), 0); }\n#[test] fn painting_paint_order_invalid() { assert_eq!(render(\"tests/painting/paint-order/invalid\"), 0); }\n#[test] fn painting_paint_order_markers_stroke() { assert_eq!(render(\"tests/painting/paint-order/markers-stroke\"), 0); }\n#[test] fn painting_paint_order_markers() { assert_eq!(render(\"tests/painting/paint-order/markers\"), 0); }\n#[test] fn painting_paint_order_normal() { assert_eq!(render(\"tests/painting/paint-order/normal\"), 0); }\n#[test] fn painting_paint_order_on_text() { assert_eq!(render(\"tests/painting/paint-order/on-text\"), 0); }\n#[test] fn painting_paint_order_on_tspan() { assert_eq!(render(\"tests/painting/paint-order/on-tspan\"), 0); }\n#[test] fn painting_paint_order_stroke_invalid() { assert_eq!(render(\"tests/painting/paint-order/stroke-invalid\"), 0); }\n#[test] fn painting_paint_order_stroke_markers_fill() { assert_eq!(render(\"tests/painting/paint-order/stroke-markers-fill\"), 0); }\n#[test] fn painting_paint_order_stroke_markers() { assert_eq!(render(\"tests/painting/paint-order/stroke-markers\"), 0); }\n#[test] fn painting_paint_order_stroke() { assert_eq!(render(\"tests/painting/paint-order/stroke\"), 0); }\n#[test] fn painting_paint_order_trailing_data() { assert_eq!(render(\"tests/painting/paint-order/trailing-data\"), 0); }\n#[test] fn painting_shape_rendering_auto_on_circle() { assert_eq!(render(\"tests/painting/shape-rendering/auto-on-circle\"), 0); }\n#[test] fn painting_shape_rendering_crispEdges_on_circle() { assert_eq!(render(\"tests/painting/shape-rendering/crispEdges-on-circle\"), 0); }\n#[test] fn painting_shape_rendering_geometricPrecision_on_circle() { assert_eq!(render(\"tests/painting/shape-rendering/geometricPrecision-on-circle\"), 0); }\n#[test] fn painting_shape_rendering_inheritance() { assert_eq!(render(\"tests/painting/shape-rendering/inheritance\"), 0); }\n#[test] fn painting_shape_rendering_on_horizontal_line() { assert_eq!(render(\"tests/painting/shape-rendering/on-horizontal-line\"), 0); }\n#[test] fn painting_shape_rendering_optimizeSpeed_on_circle() { assert_eq!(render(\"tests/painting/shape-rendering/optimizeSpeed-on-circle\"), 0); }\n#[test] fn painting_shape_rendering_optimizeSpeed_on_text() { assert_eq!(render(\"tests/painting/shape-rendering/optimizeSpeed-on-text\"), 0); }\n#[test] fn painting_shape_rendering_path_with_marker() { assert_eq!(render(\"tests/painting/shape-rendering/path-with-marker\"), 0); }\n#[test] fn painting_stroke_control_points_clamping_1() { assert_eq!(render(\"tests/painting/stroke/control-points-clamping-1\"), 0); }\n#[test] fn painting_stroke_control_points_clamping_2() { assert_eq!(render(\"tests/painting/stroke/control-points-clamping-2\"), 0); }\n#[test] fn painting_stroke_currentColor_without_a_parent() { assert_eq!(render(\"tests/painting/stroke/currentColor-without-a-parent\"), 0); }\n#[test] fn painting_stroke_funcIRI_to_unsupported_element() { assert_eq!(render(\"tests/painting/stroke/funcIRI-to-unsupported-element\"), 0); }\n#[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); }\n#[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); }\n#[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); }\n#[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); }\n#[test] fn painting_stroke_line_as_curve_1() { assert_eq!(render(\"tests/painting/stroke/line-as-curve-1\"), 0); }\n#[test] fn painting_stroke_line_as_curve_2() { assert_eq!(render(\"tests/painting/stroke/line-as-curve-2\"), 0); }\n#[test] fn painting_stroke_linear_gradient_on_text() { assert_eq!(render(\"tests/painting/stroke/linear-gradient-on-text\"), 0); }\n#[test] fn painting_stroke_linear_gradient() { assert_eq!(render(\"tests/painting/stroke/linear-gradient\"), 0); }\n#[test] fn painting_stroke_named_color() { assert_eq!(render(\"tests/painting/stroke/named-color\"), 0); }\n#[test] fn painting_stroke_none() { assert_eq!(render(\"tests/painting/stroke/none\"), 0); }\n#[test] fn painting_stroke_pattern_on_text() { assert_eq!(render(\"tests/painting/stroke/pattern-on-text\"), 0); }\n#[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); }\n#[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); }\n#[test] fn painting_stroke_pattern() { assert_eq!(render(\"tests/painting/stroke/pattern\"), 0); }\n#[test] fn painting_stroke_radial_gradient_on_text() { assert_eq!(render(\"tests/painting/stroke/radial-gradient-on-text\"), 0); }\n#[test] fn painting_stroke_radial_gradient() { assert_eq!(render(\"tests/painting/stroke/radial-gradient\"), 0); }\n#[test] fn painting_stroke_dasharray_0_n_with_butt_caps() { assert_eq!(render(\"tests/painting/stroke-dasharray/0-n-with-butt-caps\"), 0); }\n#[test] fn painting_stroke_dasharray_0_n_with_round_caps() { assert_eq!(render(\"tests/painting/stroke-dasharray/0-n-with-round-caps\"), 0); }\n#[test] fn painting_stroke_dasharray_0_n_with_square_caps() { assert_eq!(render(\"tests/painting/stroke-dasharray/0-n-with-square-caps\"), 0); }\n#[test] fn painting_stroke_dasharray_comma_ws_separator() { assert_eq!(render(\"tests/painting/stroke-dasharray/comma-ws-separator\"), 0); }\n#[test] fn painting_stroke_dasharray_em_units() { assert_eq!(render(\"tests/painting/stroke-dasharray/em-units\"), 0); }\n#[test] fn painting_stroke_dasharray_even_count() { assert_eq!(render(\"tests/painting/stroke-dasharray/even-count\"), 0); }\n#[test] fn painting_stroke_dasharray_mm_units() { assert_eq!(render(\"tests/painting/stroke-dasharray/mm-units\"), 0); }\n#[test] fn painting_stroke_dasharray_multiple_subpaths() { assert_eq!(render(\"tests/painting/stroke-dasharray/multiple-subpaths\"), 0); }\n#[test] fn painting_stroke_dasharray_n_0() { assert_eq!(render(\"tests/painting/stroke-dasharray/n-0\"), 0); }\n#[test] fn painting_stroke_dasharray_negative_sum() { assert_eq!(render(\"tests/painting/stroke-dasharray/negative-sum\"), 0); }\n#[test] fn painting_stroke_dasharray_negative_values() { assert_eq!(render(\"tests/painting/stroke-dasharray/negative-values\"), 0); }\n#[test] fn painting_stroke_dasharray_none() { assert_eq!(render(\"tests/painting/stroke-dasharray/none\"), 0); }\n#[test] fn painting_stroke_dasharray_odd_count() { assert_eq!(render(\"tests/painting/stroke-dasharray/odd-count\"), 0); }\n#[test] fn painting_stroke_dasharray_on_a_circle() { assert_eq!(render(\"tests/painting/stroke-dasharray/on-a-circle\"), 0); }\n#[test] fn painting_stroke_dasharray_percent_units() { assert_eq!(render(\"tests/painting/stroke-dasharray/percent-units\"), 0); }\n#[test] fn painting_stroke_dasharray_ws_separator() { assert_eq!(render(\"tests/painting/stroke-dasharray/ws-separator\"), 0); }\n#[test] fn painting_stroke_dasharray_zero_sum() { assert_eq!(render(\"tests/painting/stroke-dasharray/zero-sum\"), 0); }\n#[test] fn painting_stroke_dashoffset_default() { assert_eq!(render(\"tests/painting/stroke-dashoffset/default\"), 0); }\n#[test] fn painting_stroke_dashoffset_em_units() { assert_eq!(render(\"tests/painting/stroke-dashoffset/em-units\"), 0); }\n#[test] fn painting_stroke_dashoffset_mm_units() { assert_eq!(render(\"tests/painting/stroke-dashoffset/mm-units\"), 0); }\n#[test] fn painting_stroke_dashoffset_negative_value() { assert_eq!(render(\"tests/painting/stroke-dashoffset/negative-value\"), 0); }\n#[test] fn painting_stroke_dashoffset_percent_units() { assert_eq!(render(\"tests/painting/stroke-dashoffset/percent-units\"), 0); }\n#[test] fn painting_stroke_dashoffset_px_units() { assert_eq!(render(\"tests/painting/stroke-dashoffset/px-units\"), 0); }\n#[test] fn painting_stroke_linecap_butt() { assert_eq!(render(\"tests/painting/stroke-linecap/butt\"), 0); }\n#[test] fn painting_stroke_linecap_open_path_with_butt() { assert_eq!(render(\"tests/painting/stroke-linecap/open-path-with-butt\"), 0); }\n#[test] fn painting_stroke_linecap_open_path_with_round() { assert_eq!(render(\"tests/painting/stroke-linecap/open-path-with-round\"), 0); }\n#[test] fn painting_stroke_linecap_open_path_with_square() { assert_eq!(render(\"tests/painting/stroke-linecap/open-path-with-square\"), 0); }\n#[test] fn painting_stroke_linecap_round() { assert_eq!(render(\"tests/painting/stroke-linecap/round\"), 0); }\n#[test] fn painting_stroke_linecap_square() { assert_eq!(render(\"tests/painting/stroke-linecap/square\"), 0); }\n#[test] fn painting_stroke_linecap_zero_length_path_with_butt() { assert_eq!(render(\"tests/painting/stroke-linecap/zero-length-path-with-butt\"), 0); }\n#[test] fn painting_stroke_linecap_zero_length_path_with_round() { assert_eq!(render(\"tests/painting/stroke-linecap/zero-length-path-with-round\"), 0); }\n#[test] fn painting_stroke_linecap_zero_length_path_with_square() { assert_eq!(render(\"tests/painting/stroke-linecap/zero-length-path-with-square\"), 0); }\n#[test] fn painting_stroke_linejoin_arcs() { assert_eq!(render(\"tests/painting/stroke-linejoin/arcs\"), 0); }\n#[test] fn painting_stroke_linejoin_bevel() { assert_eq!(render(\"tests/painting/stroke-linejoin/bevel\"), 0); }\n#[test] fn painting_stroke_linejoin_miter_clip() { assert_eq!(render(\"tests/painting/stroke-linejoin/miter-clip\"), 0); }\n#[test] fn painting_stroke_linejoin_miter() { assert_eq!(render(\"tests/painting/stroke-linejoin/miter\"), 0); }\n#[test] fn painting_stroke_linejoin_round() { assert_eq!(render(\"tests/painting/stroke-linejoin/round\"), 0); }\n#[test] fn painting_stroke_miterlimit_default() { assert_eq!(render(\"tests/painting/stroke-miterlimit/default\"), 0); }\n#[test] fn painting_stroke_miterlimit_invalid_value() { assert_eq!(render(\"tests/painting/stroke-miterlimit/invalid-value\"), 0); }\n#[test] fn painting_stroke_miterlimit_valid_value() { assert_eq!(render(\"tests/painting/stroke-miterlimit/valid-value\"), 0); }\n#[test] fn painting_stroke_miterlimit_value_with_mm() { assert_eq!(render(\"tests/painting/stroke-miterlimit/value-with-mm\"), 0); }\n#[test] fn painting_stroke_miterlimit_value_with_percent() { assert_eq!(render(\"tests/painting/stroke-miterlimit/value-with-percent\"), 0); }\n#[test] fn painting_stroke_opacity_50percent() { assert_eq!(render(\"tests/painting/stroke-opacity/50percent\"), 0); }\n#[test] fn painting_stroke_opacity_half_opacity() { assert_eq!(render(\"tests/painting/stroke-opacity/half-opacity\"), 0); }\n#[test] fn painting_stroke_opacity_nested() { assert_eq!(render(\"tests/painting/stroke-opacity/nested\"), 0); }\n#[test] fn painting_stroke_opacity_on_parent() { assert_eq!(render(\"tests/painting/stroke-opacity/on-parent\"), 0); }\n#[test] fn painting_stroke_opacity_on_text() { assert_eq!(render(\"tests/painting/stroke-opacity/on-text\"), 0); }\n#[test] fn painting_stroke_opacity_with_linearGradient() { assert_eq!(render(\"tests/painting/stroke-opacity/with-linearGradient\"), 0); }\n#[test] fn painting_stroke_opacity_with_opacity() { assert_eq!(render(\"tests/painting/stroke-opacity/with-opacity\"), 0); }\n#[test] fn painting_stroke_opacity_with_pattern() { assert_eq!(render(\"tests/painting/stroke-opacity/with-pattern\"), 0); }\n#[test] fn painting_stroke_width_bold() { assert_eq!(render(\"tests/painting/stroke-width/bold\"), 0); }\n#[test] fn painting_stroke_width_default() { assert_eq!(render(\"tests/painting/stroke-width/default\"), 0); }\n#[test] fn painting_stroke_width_negative() { assert_eq!(render(\"tests/painting/stroke-width/negative\"), 0); }\n#[test] fn painting_stroke_width_percentage() { assert_eq!(render(\"tests/painting/stroke-width/percentage\"), 0); }\n#[test] fn painting_stroke_width_zero() { assert_eq!(render(\"tests/painting/stroke-width/zero\"), 0); }\n#[test] fn painting_visibility_bbox_impact_1() { assert_eq!(render(\"tests/painting/visibility/bbox-impact-1\"), 0); }\n#[test] fn painting_visibility_bbox_impact_2() { assert_eq!(render(\"tests/painting/visibility/bbox-impact-2\"), 0); }\n#[test] fn painting_visibility_bbox_impact_3() { assert_eq!(render(\"tests/painting/visibility/bbox-impact-3\"), 0); }\n#[test] fn painting_visibility_collapse_on_tspan() { assert_eq!(render(\"tests/painting/visibility/collapse-on-tspan\"), 0); }\n#[test] fn painting_visibility_hidden_on_group() { assert_eq!(render(\"tests/painting/visibility/hidden-on-group\"), 0); }\n#[test] fn painting_visibility_hidden_on_shape() { assert_eq!(render(\"tests/painting/visibility/hidden-on-shape\"), 0); }\n#[test] fn painting_visibility_hidden_on_tspan() { assert_eq!(render(\"tests/painting/visibility/hidden-on-tspan\"), 0); }\n#[test] fn shapes_circle_missing_cx_and_cy_attributes() { assert_eq!(render(\"tests/shapes/circle/missing-cx-and-cy-attributes\"), 0); }\n#[test] fn shapes_circle_missing_cx_attribute() { assert_eq!(render(\"tests/shapes/circle/missing-cx-attribute\"), 0); }\n#[test] fn shapes_circle_missing_cy_attribute() { assert_eq!(render(\"tests/shapes/circle/missing-cy-attribute\"), 0); }\n#[test] fn shapes_circle_missing_r_attribute() { assert_eq!(render(\"tests/shapes/circle/missing-r-attribute\"), 0); }\n#[test] fn shapes_circle_negative_r_attribute() { assert_eq!(render(\"tests/shapes/circle/negative-r-attribute\"), 0); }\n#[test] fn shapes_circle_simple_case() { assert_eq!(render(\"tests/shapes/circle/simple-case\"), 0); }\n#[test] fn shapes_ellipse_missing_cx_and_cy_attributes() { assert_eq!(render(\"tests/shapes/ellipse/missing-cx-and-cy-attributes\"), 0); }\n#[test] fn shapes_ellipse_missing_cx_attribute() { assert_eq!(render(\"tests/shapes/ellipse/missing-cx-attribute\"), 0); }\n#[test] fn shapes_ellipse_missing_cy_attribute() { assert_eq!(render(\"tests/shapes/ellipse/missing-cy-attribute\"), 0); }\n#[test] fn shapes_ellipse_missing_rx_and_ry_attributes() { assert_eq!(render(\"tests/shapes/ellipse/missing-rx-and-ry-attributes\"), 0); }\n#[test] fn shapes_ellipse_missing_rx_attribute() { assert_eq!(render(\"tests/shapes/ellipse/missing-rx-attribute\"), 0); }\n#[test] fn shapes_ellipse_missing_ry_attribute() { assert_eq!(render(\"tests/shapes/ellipse/missing-ry-attribute\"), 0); }\n#[test] fn shapes_ellipse_negative_rx_and_ry_attributes() { assert_eq!(render(\"tests/shapes/ellipse/negative-rx-and-ry-attributes\"), 0); }\n#[test] fn shapes_ellipse_negative_rx_attribute() { assert_eq!(render(\"tests/shapes/ellipse/negative-rx-attribute\"), 0); }\n#[test] fn shapes_ellipse_negative_ry_attribute() { assert_eq!(render(\"tests/shapes/ellipse/negative-ry-attribute\"), 0); }\n#[test] fn shapes_ellipse_percent_values_missing_ry() { assert_eq!(render(\"tests/shapes/ellipse/percent-values-missing-ry\"), 0); }\n#[test] fn shapes_ellipse_percent_values() { assert_eq!(render(\"tests/shapes/ellipse/percent-values\"), 0); }\n#[test] fn shapes_ellipse_simple_case() { assert_eq!(render(\"tests/shapes/ellipse/simple-case\"), 0); }\n#[test] fn shapes_line_no_coordinates() { assert_eq!(render(\"tests/shapes/line/no-coordinates\"), 0); }\n#[test] fn shapes_line_no_x1_and_y1_coordinates() { assert_eq!(render(\"tests/shapes/line/no-x1-and-y1-coordinates\"), 0); }\n#[test] fn shapes_line_no_x1_coordinate() { assert_eq!(render(\"tests/shapes/line/no-x1-coordinate\"), 0); }\n#[test] fn shapes_line_no_x2_and_y2_coordinates() { assert_eq!(render(\"tests/shapes/line/no-x2-and-y2-coordinates\"), 0); }\n#[test] fn shapes_line_no_x2_coordinate() { assert_eq!(render(\"tests/shapes/line/no-x2-coordinate\"), 0); }\n#[test] fn shapes_line_no_y1_coordinate() { assert_eq!(render(\"tests/shapes/line/no-y1-coordinate\"), 0); }\n#[test] fn shapes_line_no_y2_coordinate() { assert_eq!(render(\"tests/shapes/line/no-y2-coordinate\"), 0); }\n#[test] fn shapes_line_percent_units() { assert_eq!(render(\"tests/shapes/line/percent-units\"), 0); }\n#[test] fn shapes_line_simple_case() { assert_eq!(render(\"tests/shapes/line/simple-case\"), 0); }\n#[test] fn shapes_line_with_transform() { assert_eq!(render(\"tests/shapes/line/with-transform\"), 0); }\n#[test] fn shapes_path_A() { assert_eq!(render(\"tests/shapes/path/A\"), 0); }\n#[test] fn shapes_path_M_A_s() { assert_eq!(render(\"tests/shapes/path/M-A-s\"), 0); }\n#[test] fn shapes_path_M_A_t() { assert_eq!(render(\"tests/shapes/path/M-A-t\"), 0); }\n#[test] fn shapes_path_M_A_trimmed() { assert_eq!(render(\"tests/shapes/path/M-A-trimmed\"), 0); }\n#[test] fn shapes_path_M_A() { assert_eq!(render(\"tests/shapes/path/M-A\"), 0); }\n#[test] fn shapes_path_M_C_S() { assert_eq!(render(\"tests/shapes/path/M-C-S\"), 0); }\n#[test] fn shapes_path_M_C() { assert_eq!(render(\"tests/shapes/path/M-C\"), 0); }\n#[test] fn shapes_path_M_H_H_implicit() { assert_eq!(render(\"tests/shapes/path/M-H-H-implicit\"), 0); }\n#[test] fn shapes_path_M_H_H() { assert_eq!(render(\"tests/shapes/path/M-H-H\"), 0); }\n#[test] fn shapes_path_M_H() { assert_eq!(render(\"tests/shapes/path/M-H\"), 0); }\n#[test] fn shapes_path_M_L_L_Z_rel() { assert_eq!(render(\"tests/shapes/path/M-L-L-Z-rel\"), 0); }\n#[test] fn shapes_path_M_L_L_Z() { assert_eq!(render(\"tests/shapes/path/M-L-L-Z\"), 0); }\n#[test] fn shapes_path_M_L_L_implicit() { assert_eq!(render(\"tests/shapes/path/M-L-L-implicit\"), 0); }\n#[test] fn shapes_path_M_L_M_L() { assert_eq!(render(\"tests/shapes/path/M-L-M-L\"), 0); }\n#[test] fn shapes_path_M_L_M_Z() { assert_eq!(render(\"tests/shapes/path/M-L-M-Z\"), 0); }\n#[test] fn shapes_path_M_L_M() { assert_eq!(render(\"tests/shapes/path/M-L-M\"), 0); }\n#[test] fn shapes_path_M_L_Z_A() { assert_eq!(render(\"tests/shapes/path/M-L-Z-A\"), 0); }\n#[test] fn shapes_path_M_L_Z_L_L() { assert_eq!(render(\"tests/shapes/path/M-L-Z-L-L\"), 0); }\n#[test] fn shapes_path_M_L() { assert_eq!(render(\"tests/shapes/path/M-L\"), 0); }\n#[test] fn shapes_path_M_M_implicit_M_implicit() { assert_eq!(render(\"tests/shapes/path/M-M-implicit-M-implicit\"), 0); }\n#[test] fn shapes_path_M_M_rel() { assert_eq!(render(\"tests/shapes/path/M-M-rel\"), 0); }\n#[test] fn shapes_path_M_M() { assert_eq!(render(\"tests/shapes/path/M-M\"), 0); }\n#[test] fn shapes_path_M_Q_T_rel() { assert_eq!(render(\"tests/shapes/path/M-Q-T-rel\"), 0); }\n#[test] fn shapes_path_M_Q_T() { assert_eq!(render(\"tests/shapes/path/M-Q-T\"), 0); }\n#[test] fn shapes_path_M_Q_rel_T_rel() { assert_eq!(render(\"tests/shapes/path/M-Q-rel-T-rel\"), 0); }\n#[test] fn shapes_path_M_Q() { assert_eq!(render(\"tests/shapes/path/M-Q\"), 0); }\n#[test] fn shapes_path_M_S_S() { assert_eq!(render(\"tests/shapes/path/M-S-S\"), 0); }\n#[test] fn shapes_path_M_S() { assert_eq!(render(\"tests/shapes/path/M-S\"), 0); }\n#[test] fn shapes_path_M_T_Q_rel() { assert_eq!(render(\"tests/shapes/path/M-T-Q-rel\"), 0); }\n#[test] fn shapes_path_M_T_Q() { assert_eq!(render(\"tests/shapes/path/M-T-Q\"), 0); }\n#[test] fn shapes_path_M_T_S_rel() { assert_eq!(render(\"tests/shapes/path/M-T-S-rel\"), 0); }\n#[test] fn shapes_path_M_T_S() { assert_eq!(render(\"tests/shapes/path/M-T-S\"), 0); }\n#[test] fn shapes_path_M_T_T_rel() { assert_eq!(render(\"tests/shapes/path/M-T-T-rel\"), 0); }\n#[test] fn shapes_path_M_T_T() { assert_eq!(render(\"tests/shapes/path/M-T-T\"), 0); }\n#[test] fn shapes_path_M_T() { assert_eq!(render(\"tests/shapes/path/M-T\"), 0); }\n#[test] fn shapes_path_M_V_V_implicit() { assert_eq!(render(\"tests/shapes/path/M-V-V-implicit\"), 0); }\n#[test] fn shapes_path_M_V_V() { assert_eq!(render(\"tests/shapes/path/M-V-V\"), 0); }\n#[test] fn shapes_path_M_V() { assert_eq!(render(\"tests/shapes/path/M-V\"), 0); }\n#[test] fn shapes_path_M_Z() { assert_eq!(render(\"tests/shapes/path/M-Z\"), 0); }\n#[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); }\n#[test] fn shapes_path_M_rel_M() { assert_eq!(render(\"tests/shapes/path/M-rel-M\"), 0); }\n#[test] fn shapes_path_M() { assert_eq!(render(\"tests/shapes/path/M\"), 0); }\n#[test] fn shapes_path_empty() { assert_eq!(render(\"tests/shapes/path/empty\"), 0); }\n#[test] fn shapes_path_extra_spaces() { assert_eq!(render(\"tests/shapes/path/extra-spaces\"), 0); }\n#[test] fn shapes_path_invalid_data_in_L() { assert_eq!(render(\"tests/shapes/path/invalid-data-in-L\"), 0); }\n#[test] fn shapes_path_invalid_transform() { assert_eq!(render(\"tests/shapes/path/invalid-transform\"), 0); }\n#[test] fn shapes_path_missing_coordinate_in_L() { assert_eq!(render(\"tests/shapes/path/missing-coordinate-in-L\"), 0); }\n#[test] fn shapes_path_multi_line_data() { assert_eq!(render(\"tests/shapes/path/multi-line-data\"), 0); }\n#[test] fn shapes_path_negative_large_arc_flag_value() { assert_eq!(render(\"tests/shapes/path/negative-large-arc-flag-value\"), 0); }\n#[test] fn shapes_path_negative_sweep_flag_value() { assert_eq!(render(\"tests/shapes/path/negative-sweep-flag-value\"), 0); }\n#[test] fn shapes_path_no_commawsp_after_sweep_flag() { assert_eq!(render(\"tests/shapes/path/no-commawsp-after-sweep-flag\"), 0); }\n#[test] fn shapes_path_no_commawsp_before_arc_flags() { assert_eq!(render(\"tests/shapes/path/no-commawsp-before-arc-flags\"), 0); }\n#[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); }\n#[test] fn shapes_path_no_commawsp_between_arc_flags() { assert_eq!(render(\"tests/shapes/path/no-commawsp-between-arc-flags\"), 0); }\n#[test] fn shapes_path_numeric_character_references() { assert_eq!(render(\"tests/shapes/path/numeric-character-references\"), 0); }\n#[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); }\n#[test] fn shapes_path_out_of_range_sweep_flag_value() { assert_eq!(render(\"tests/shapes/path/out-of-range-sweep-flag-value\"), 0); }\n#[test] fn shapes_polygon_ignore_odd_points() { assert_eq!(render(\"tests/shapes/polygon/ignore-odd-points\"), 0); }\n#[test] fn shapes_polygon_missing_points_attribute() { assert_eq!(render(\"tests/shapes/polygon/missing-points-attribute\"), 0); }\n#[test] fn shapes_polygon_not_enough_points() { assert_eq!(render(\"tests/shapes/polygon/not-enough-points\"), 0); }\n#[test] fn shapes_polygon_simple_case() { assert_eq!(render(\"tests/shapes/polygon/simple-case\"), 0); }\n#[test] fn shapes_polygon_stop_processing_on_invalid_data() { assert_eq!(render(\"tests/shapes/polygon/stop-processing-on-invalid-data\"), 0); }\n#[test] fn shapes_polyline_ignore_odd_points() { assert_eq!(render(\"tests/shapes/polyline/ignore-odd-points\"), 0); }\n#[test] fn shapes_polyline_missing_points_attribute() { assert_eq!(render(\"tests/shapes/polyline/missing-points-attribute\"), 0); }\n#[test] fn shapes_polyline_not_enough_points() { assert_eq!(render(\"tests/shapes/polyline/not-enough-points\"), 0); }\n#[test] fn shapes_polyline_simple_case() { assert_eq!(render(\"tests/shapes/polyline/simple-case\"), 0); }\n#[test] fn shapes_polyline_stop_processing_on_invalid_data() { assert_eq!(render(\"tests/shapes/polyline/stop-processing-on-invalid-data\"), 0); }\n#[test] fn shapes_rect_cap_values() { assert_eq!(render(\"tests/shapes/rect/cap-values\"), 0); }\n#[test] fn shapes_rect_ch_values() { assert_eq!(render(\"tests/shapes/rect/ch-values\"), 0); }\n#[test] fn shapes_rect_em_values() { assert_eq!(render(\"tests/shapes/rect/em-values\"), 0); }\n#[test] fn shapes_rect_ex_values() { assert_eq!(render(\"tests/shapes/rect/ex-values\"), 0); }\n#[test] fn shapes_rect_ic_values() { assert_eq!(render(\"tests/shapes/rect/ic-values\"), 0); }\n#[test] fn shapes_rect_invalid_coordinates() { assert_eq!(render(\"tests/shapes/rect/invalid-coordinates\"), 0); }\n#[test] fn shapes_rect_invalid_length() { assert_eq!(render(\"tests/shapes/rect/invalid-length\"), 0); }\n#[test] fn shapes_rect_lh_values() { assert_eq!(render(\"tests/shapes/rect/lh-values\"), 0); }\n#[test] fn shapes_rect_missing_height_attribute_processing() { assert_eq!(render(\"tests/shapes/rect/missing-height-attribute-processing\"), 0); }\n#[test] fn shapes_rect_missing_width_attribute_processing() { assert_eq!(render(\"tests/shapes/rect/missing-width-attribute-processing\"), 0); }\n#[test] fn shapes_rect_mm_values() { assert_eq!(render(\"tests/shapes/rect/mm-values\"), 0); }\n#[test] fn shapes_rect_negative_height_attribute_processing() { assert_eq!(render(\"tests/shapes/rect/negative-height-attribute-processing\"), 0); }\n#[test] fn shapes_rect_negative_rx_and_ry_attributes_resolving() { assert_eq!(render(\"tests/shapes/rect/negative-rx-and-ry-attributes-resolving\"), 0); }\n#[test] fn shapes_rect_negative_rx_attribute_resolving() { assert_eq!(render(\"tests/shapes/rect/negative-rx-attribute-resolving\"), 0); }\n#[test] fn shapes_rect_negative_ry_attribute_resolving() { assert_eq!(render(\"tests/shapes/rect/negative-ry-attribute-resolving\"), 0); }\n#[test] fn shapes_rect_negative_width_attribute_processing() { assert_eq!(render(\"tests/shapes/rect/negative-width-attribute-processing\"), 0); }\n#[test] fn shapes_rect_percentage_values_1() { assert_eq!(render(\"tests/shapes/rect/percentage-values-1\"), 0); }\n#[test] fn shapes_rect_percentage_values_2() { assert_eq!(render(\"tests/shapes/rect/percentage-values-2\"), 0); }\n#[test] fn shapes_rect_q_values() { assert_eq!(render(\"tests/shapes/rect/q-values\"), 0); }\n#[test] fn shapes_rect_rem_values() { assert_eq!(render(\"tests/shapes/rect/rem-values\"), 0); }\n#[test] fn shapes_rect_rlh_values() { assert_eq!(render(\"tests/shapes/rect/rlh-values\"), 0); }\n#[test] fn shapes_rect_rounded_rect() { assert_eq!(render(\"tests/shapes/rect/rounded-rect\"), 0); }\n#[test] fn shapes_rect_rx_and_ry_attributes_clamping_order() { assert_eq!(render(\"tests/shapes/rect/rx-and-ry-attributes-clamping-order\"), 0); }\n#[test] fn shapes_rect_rx_attribute_clamping() { assert_eq!(render(\"tests/shapes/rect/rx-attribute-clamping\"), 0); }\n#[test] fn shapes_rect_rx_attribute_resolving() { assert_eq!(render(\"tests/shapes/rect/rx-attribute-resolving\"), 0); }\n#[test] fn shapes_rect_ry_attribute_clamping() { assert_eq!(render(\"tests/shapes/rect/ry-attribute-clamping\"), 0); }\n#[test] fn shapes_rect_ry_attribute_resolving() { assert_eq!(render(\"tests/shapes/rect/ry-attribute-resolving\"), 0); }\n#[test] fn shapes_rect_simple_case() { assert_eq!(render(\"tests/shapes/rect/simple-case\"), 0); }\n#[test] fn shapes_rect_vi_and_vb_values() { assert_eq!(render(\"tests/shapes/rect/vi-and-vb-values\"), 0); }\n#[test] fn shapes_rect_vmin_and_vmax_values() { assert_eq!(render(\"tests/shapes/rect/vmin-and-vmax-values\"), 0); }\n#[test] fn shapes_rect_vw_and_vh_values() { assert_eq!(render(\"tests/shapes/rect/vw-and-vh-values\"), 0); }\n#[test] fn shapes_rect_with_child() { assert_eq!(render(\"tests/shapes/rect/with-child\"), 0); }\n#[test] fn shapes_rect_x_attribute_resolving() { assert_eq!(render(\"tests/shapes/rect/x-attribute-resolving\"), 0); }\n#[test] fn shapes_rect_y_attribute_resolving() { assert_eq!(render(\"tests/shapes/rect/y-attribute-resolving\"), 0); }\n#[test] fn shapes_rect_zero_height_attribute_processing() { assert_eq!(render(\"tests/shapes/rect/zero-height-attribute-processing\"), 0); }\n#[test] fn shapes_rect_zero_rx_attribute_resolving() { assert_eq!(render(\"tests/shapes/rect/zero-rx-attribute-resolving\"), 0); }\n#[test] fn shapes_rect_zero_ry_attribute_resolving() { assert_eq!(render(\"tests/shapes/rect/zero-ry-attribute-resolving\"), 0); }\n#[test] fn shapes_rect_zero_width_attribute_processing() { assert_eq!(render(\"tests/shapes/rect/zero-width-attribute-processing\"), 0); }\n#[test] fn structure_a_inside_text() { assert_eq!(render(\"tests/structure/a/inside-text\"), 0); }\n#[test] fn structure_a_inside_tspan() { assert_eq!(render(\"tests/structure/a/inside-tspan\"), 0); }\n#[test] fn structure_a_on_shape() { assert_eq!(render(\"tests/structure/a/on-shape\"), 0); }\n#[test] fn structure_a_on_text() { assert_eq!(render(\"tests/structure/a/on-text\"), 0); }\n#[test] fn structure_a_on_tspan() { assert_eq!(render(\"tests/structure/a/on-tspan\"), 0); }\n#[test] fn structure_defs_ignore_shapes_inside_defs() { assert_eq!(render(\"tests/structure/defs/ignore-shapes-inside-defs\"), 0); }\n#[test] fn structure_defs_multiple_defs() { assert_eq!(render(\"tests/structure/defs/multiple-defs\"), 0); }\n#[test] fn structure_defs_nested_defs() { assert_eq!(render(\"tests/structure/defs/nested-defs\"), 0); }\n#[test] fn structure_defs_out_of_order() { assert_eq!(render(\"tests/structure/defs/out-of-order\"), 0); }\n#[test] fn structure_defs_simple_case() { assert_eq!(render(\"tests/structure/defs/simple-case\"), 0); }\n#[test] fn structure_defs_style_inheritance_on_text() { assert_eq!(render(\"tests/structure/defs/style-inheritance-on-text\"), 0); }\n#[test] fn structure_defs_style_inheritance() { assert_eq!(render(\"tests/structure/defs/style-inheritance\"), 0); }\n#[test] fn structure_g_deeply_nested_groups() { assert_eq!(render(\"tests/structure/g/deeply-nested-groups\"), 0); }\n#[test] fn structure_g_recursive_inheritance() { assert_eq!(render(\"tests/structure/g/recursive-inheritance\"), 0); }\n#[test] fn structure_image_embedded_16bit_png() { assert_eq!(render(\"tests/structure/image/embedded-16bit-png\"), 0); }\n#[test] fn structure_image_embedded_gif() { assert_eq!(render(\"tests/structure/image/embedded-gif\"), 0); }\n#[test] fn structure_image_embedded_jpeg_as_image_jpeg() { assert_eq!(render(\"tests/structure/image/embedded-jpeg-as-image-jpeg\"), 0); }\n#[test] fn structure_image_embedded_jpeg_as_image_jpg() { assert_eq!(render(\"tests/structure/image/embedded-jpeg-as-image-jpg\"), 0); }\n#[test] fn structure_image_embedded_jpeg_luma() { assert_eq!(render(\"tests/structure/image/embedded-jpeg-luma\"), 0); }\n#[test] fn structure_image_embedded_jpeg_without_mime() { assert_eq!(render(\"tests/structure/image/embedded-jpeg-without-mime\"), 0); }\n#[test] fn structure_image_embedded_png_luma() { assert_eq!(render(\"tests/structure/image/embedded-png-luma\"), 0); }\n#[test] fn structure_image_embedded_png() { assert_eq!(render(\"tests/structure/image/embedded-png\"), 0); }\n#[test] fn structure_image_embedded_svg_with_text() { assert_eq!(render(\"tests/structure/image/embedded-svg-with-text\"), 0); }\n#[test] fn structure_image_embedded_svg_without_mime() { assert_eq!(render(\"tests/structure/image/embedded-svg-without-mime\"), 0); }\n#[test] fn structure_image_embedded_svg() { assert_eq!(render(\"tests/structure/image/embedded-svg\"), 0); }\n#[test] fn structure_image_embedded_svgz() { assert_eq!(render(\"tests/structure/image/embedded-svgz\"), 0); }\n#[test] fn structure_image_embedded_webp() { assert_eq!(render(\"tests/structure/image/embedded-webp\"), 0); }\n#[test] fn structure_image_external_gif() { assert_eq!(render(\"tests/structure/image/external-gif\"), 0); }\n#[test] fn structure_image_external_jpeg() { assert_eq!(render(\"tests/structure/image/external-jpeg\"), 0); }\n#[test] fn structure_image_external_png() { assert_eq!(render(\"tests/structure/image/external-png\"), 0); }\n#[test] fn structure_image_external_svg_with_transform() { assert_eq!(render(\"tests/structure/image/external-svg-with-transform\"), 0); }\n#[test] fn structure_image_external_svg() { assert_eq!(render(\"tests/structure/image/external-svg\"), 0); }\n#[test] fn structure_image_external_svgz() { assert_eq!(render(\"tests/structure/image/external-svgz\"), 0); }\n#[test] fn structure_image_external_webp() { assert_eq!(render(\"tests/structure/image/external-webp\"), 0); }\n#[test] fn structure_image_float_size() { assert_eq!(render(\"tests/structure/image/float-size\"), 0); }\n#[test] fn structure_image_image_with_float_size_scaling() { assert_eq!(render(\"tests/structure/image/image-with-float-size-scaling\"), 0); }\n#[test] fn structure_image_nested_embedded_png() { assert_eq!(render(\"tests/structure/image/nested-embedded-png\"), 0); }\n#[test] fn structure_image_nested_external_png() { assert_eq!(render(\"tests/structure/image/nested-external-png\"), 0); }\n#[test] fn structure_image_no_height_non_square() { assert_eq!(render(\"tests/structure/image/no-height-non-square\"), 0); }\n#[test] fn structure_image_no_height_on_svg() { assert_eq!(render(\"tests/structure/image/no-height-on-svg\"), 0); }\n#[test] fn structure_image_no_height() { assert_eq!(render(\"tests/structure/image/no-height\"), 0); }\n#[test] fn structure_image_no_width_and_height_on_svg() { assert_eq!(render(\"tests/structure/image/no-width-and-height-on-svg\"), 0); }\n#[test] fn structure_image_no_width_and_height() { assert_eq!(render(\"tests/structure/image/no-width-and-height\"), 0); }\n#[test] fn structure_image_no_width_on_svg() { assert_eq!(render(\"tests/structure/image/no-width-on-svg\"), 0); }\n#[test] fn structure_image_no_width() { assert_eq!(render(\"tests/structure/image/no-width\"), 0); }\n#[test] fn structure_image_preserveAspectRatio_eq_none_on_svg() { assert_eq!(render(\"tests/structure/image/preserveAspectRatio=none-on-svg\"), 0); }\n#[test] fn structure_image_preserveAspectRatio_eq_none() { assert_eq!(render(\"tests/structure/image/preserveAspectRatio=none\"), 0); }\n#[test] fn structure_image_preserveAspectRatio_eq_xMaxYMax_meet_on_svg() { assert_eq!(render(\"tests/structure/image/preserveAspectRatio=xMaxYMax-meet-on-svg\"), 0); }\n#[test] fn structure_image_preserveAspectRatio_eq_xMaxYMax_meet() { assert_eq!(render(\"tests/structure/image/preserveAspectRatio=xMaxYMax-meet\"), 0); }\n#[test] fn structure_image_preserveAspectRatio_eq_xMaxYMax_slice_on_svg() { assert_eq!(render(\"tests/structure/image/preserveAspectRatio=xMaxYMax-slice-on-svg\"), 0); }\n#[test] fn structure_image_preserveAspectRatio_eq_xMaxYMax_slice() { assert_eq!(render(\"tests/structure/image/preserveAspectRatio=xMaxYMax-slice\"), 0); }\n#[test] fn structure_image_preserveAspectRatio_eq_xMidYMid_meet_on_svg() { assert_eq!(render(\"tests/structure/image/preserveAspectRatio=xMidYMid-meet-on-svg\"), 0); }\n#[test] fn structure_image_preserveAspectRatio_eq_xMidYMid_meet() { assert_eq!(render(\"tests/structure/image/preserveAspectRatio=xMidYMid-meet\"), 0); }\n#[test] fn structure_image_preserveAspectRatio_eq_xMidYMid_slice_on_svg() { assert_eq!(render(\"tests/structure/image/preserveAspectRatio=xMidYMid-slice-on-svg\"), 0); }\n#[test] fn structure_image_preserveAspectRatio_eq_xMidYMid_slice() { assert_eq!(render(\"tests/structure/image/preserveAspectRatio=xMidYMid-slice\"), 0); }\n#[test] fn structure_image_preserveAspectRatio_eq_xMinYMin_meet_on_svg() { assert_eq!(render(\"tests/structure/image/preserveAspectRatio=xMinYMin-meet-on-svg\"), 0); }\n#[test] fn structure_image_preserveAspectRatio_eq_xMinYMin_meet() { assert_eq!(render(\"tests/structure/image/preserveAspectRatio=xMinYMin-meet\"), 0); }\n#[test] fn structure_image_preserveAspectRatio_eq_xMinYMin_slice_on_svg() { assert_eq!(render(\"tests/structure/image/preserveAspectRatio=xMinYMin-slice-on-svg\"), 0); }\n#[test] fn structure_image_preserveAspectRatio_eq_xMinYMin_slice() { assert_eq!(render(\"tests/structure/image/preserveAspectRatio=xMinYMin-slice\"), 0); }\n#[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); }\n#[test] fn structure_image_recursive_1() { assert_eq!(render(\"tests/structure/image/recursive-1\"), 0); }\n#[test] fn structure_image_recursive_2() { assert_eq!(render(\"tests/structure/image/recursive-2\"), 0); }\n#[test] fn structure_image_url_to_png() { assert_eq!(render(\"tests/structure/image/url-to-png\"), 0); }\n#[test] fn structure_image_url_to_svg() { assert_eq!(render(\"tests/structure/image/url-to-svg\"), 0); }\n#[test] fn structure_image_width_and_height_set_to_auto() { assert_eq!(render(\"tests/structure/image/width-and-height-set-to-auto\"), 0); }\n#[test] fn structure_image_with_transform() { assert_eq!(render(\"tests/structure/image/with-transform\"), 0); }\n#[test] fn structure_image_with_zero_width_and_height() { assert_eq!(render(\"tests/structure/image/with-zero-width-and-height\"), 0); }\n#[test] fn structure_image_zero_height() { assert_eq!(render(\"tests/structure/image/zero-height\"), 0); }\n#[test] fn structure_image_zero_width() { assert_eq!(render(\"tests/structure/image/zero-width\"), 0); }\n#[test] fn structure_style_attribute_selector() { assert_eq!(render(\"tests/structure/style/attribute-selector\"), 0); }\n#[test] fn structure_style_class_selector() { assert_eq!(render(\"tests/structure/style/class-selector\"), 0); }\n#[test] fn structure_style_combined_selectors() { assert_eq!(render(\"tests/structure/style/combined-selectors\"), 0); }\n#[test] fn structure_style_current_color_fill_before_color() { assert_eq!(render(\"tests/structure/style/current-color-fill-before-color\"), 0); }\n#[test] fn structure_style_current_color_stroke_before_color() { assert_eq!(render(\"tests/structure/style/current-color-stroke-before-color\"), 0); }\n#[test] fn structure_style_external_CSS() { assert_eq!(render(\"tests/structure/style/external-CSS\"), 0); }\n#[test] fn structure_style_iD_selector() { assert_eq!(render(\"tests/structure/style/iD-selector\"), 0); }\n#[test] fn structure_style_important() { assert_eq!(render(\"tests/structure/style/important\"), 0); }\n#[test] fn structure_style_invalid_type() { assert_eq!(render(\"tests/structure/style/invalid-type\"), 0); }\n#[test] fn structure_style_non_presentational_attribute() { assert_eq!(render(\"tests/structure/style/non-presentational-attribute\"), 0); }\n#[test] fn structure_style_resolve_order() { assert_eq!(render(\"tests/structure/style/resolve-order\"), 0); }\n#[test] fn structure_style_rule_specificity() { assert_eq!(render(\"tests/structure/style/rule-specificity\"), 0); }\n#[test] fn structure_style_style_after_usage() { assert_eq!(render(\"tests/structure/style/style-after-usage\"), 0); }\n#[test] fn structure_style_style_inside_CDATA() { assert_eq!(render(\"tests/structure/style/style-inside-CDATA\"), 0); }\n#[test] fn structure_style_transform() { assert_eq!(render(\"tests/structure/style/transform\"), 0); }\n#[test] fn structure_style_type_selector() { assert_eq!(render(\"tests/structure/style/type-selector\"), 0); }\n#[test] fn structure_style_universal_selector() { assert_eq!(render(\"tests/structure/style/universal-selector\"), 0); }\n#[test] fn structure_style_unresolved_class_selector() { assert_eq!(render(\"tests/structure/style/unresolved-class-selector\"), 0); }\n#[test] fn structure_style_attribute_comments() { assert_eq!(render(\"tests/structure/style-attribute/comments\"), 0); }\n#[test] fn structure_style_attribute_non_presentational_attribute() { assert_eq!(render(\"tests/structure/style-attribute/non-presentational-attribute\"), 0); }\n#[test] fn structure_style_attribute_simple_case() { assert_eq!(render(\"tests/structure/style-attribute/simple-case\"), 0); }\n#[test] fn structure_style_attribute_transform() { assert_eq!(render(\"tests/structure/style-attribute/transform\"), 0); }\n#[test] fn structure_svg_attribute_value_via_ENTITY_reference() { assert_eq!(render(\"tests/structure/svg/attribute-value-via-ENTITY-reference\"), 0); }\n#[test] fn structure_svg_background_color_with_viewbox() { assert_eq!(render(\"tests/structure/svg/background-color-with-viewbox\"), 0); }\n#[test] fn structure_svg_background_color() { assert_eq!(render(\"tests/structure/svg/background-color\"), 0); }\n#[test] fn structure_svg_deeply_nested_svg() { assert_eq!(render(\"tests/structure/svg/deeply-nested-svg\"), 0); }\n#[test] fn structure_svg_elements_via_ENTITY_reference_1() { assert_eq!(render(\"tests/structure/svg/elements-via-ENTITY-reference-1\"), 0); }\n#[test] fn structure_svg_elements_via_ENTITY_reference_2() { assert_eq!(render(\"tests/structure/svg/elements-via-ENTITY-reference-2\"), 0); }\n#[test] fn structure_svg_elements_via_ENTITY_reference_3() { assert_eq!(render(\"tests/structure/svg/elements-via-ENTITY-reference-3\"), 0); }\n#[test] fn structure_svg_explicit_svg_namespace() { assert_eq!(render(\"tests/structure/svg/explicit-svg-namespace\"), 0); }\n#[test] fn structure_svg_funcIRI_parsing() { assert_eq!(render(\"tests/structure/svg/funcIRI-parsing\"), 0); }\n#[test] fn structure_svg_funcIRI_with_invalid_characters() { assert_eq!(render(\"tests/structure/svg/funcIRI-with-invalid-characters\"), 0); }\n#[test] fn structure_svg_funcIRI_with_quotes() { assert_eq!(render(\"tests/structure/svg/funcIRI-with-quotes\"), 0); }\n#[test] fn structure_svg_invalid_id_attribute_1() { assert_eq!(render(\"tests/structure/svg/invalid-id-attribute-1\"), 0); }\n#[test] fn structure_svg_invalid_id_attribute_2() { assert_eq!(render(\"tests/structure/svg/invalid-id-attribute-2\"), 0); }\n#[test] fn structure_svg_mixed_namespaces() { assert_eq!(render(\"tests/structure/svg/mixed-namespaces\"), 0); }\n#[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); }\n#[test] fn structure_svg_nested_svg_with_overflow_auto() { assert_eq!(render(\"tests/structure/svg/nested-svg-with-overflow-auto\"), 0); }\n#[test] fn structure_svg_nested_svg_with_overflow_visible() { assert_eq!(render(\"tests/structure/svg/nested-svg-with-overflow-visible\"), 0); }\n#[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); }\n#[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); }\n#[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); }\n#[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); }\n#[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); }\n#[test] fn structure_svg_nested_svg_with_rect() { assert_eq!(render(\"tests/structure/svg/nested-svg-with-rect\"), 0); }\n#[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); }\n#[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); }\n#[test] fn structure_svg_nested_svg_with_viewBox() { assert_eq!(render(\"tests/structure/svg/nested-svg-with-viewBox\"), 0); }\n#[test] fn structure_svg_nested_svg() { assert_eq!(render(\"tests/structure/svg/nested-svg\"), 0); }\n#[test] fn structure_svg_no_children() { assert_eq!(render(\"tests/structure/svg/no-children\"), 0); }\n#[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); }\n#[test] fn structure_svg_preserveAspectRatio_eq_none() { assert_eq!(render(\"tests/structure/svg/preserveAspectRatio=none\"), 0); }\n#[test] fn structure_svg_preserveAspectRatio_eq_xMaxYMax_slice() { assert_eq!(render(\"tests/structure/svg/preserveAspectRatio=xMaxYMax-slice\"), 0); }\n#[test] fn structure_svg_preserveAspectRatio_eq_xMaxYMax() { assert_eq!(render(\"tests/structure/svg/preserveAspectRatio=xMaxYMax\"), 0); }\n#[test] fn structure_svg_preserveAspectRatio_eq_xMidYMid_slice() { assert_eq!(render(\"tests/structure/svg/preserveAspectRatio=xMidYMid-slice\"), 0); }\n#[test] fn structure_svg_preserveAspectRatio_eq_xMidYMid() { assert_eq!(render(\"tests/structure/svg/preserveAspectRatio=xMidYMid\"), 0); }\n#[test] fn structure_svg_preserveAspectRatio_eq_xMinYMin_slice() { assert_eq!(render(\"tests/structure/svg/preserveAspectRatio=xMinYMin-slice\"), 0); }\n#[test] fn structure_svg_preserveAspectRatio_eq_xMinYMin() { assert_eq!(render(\"tests/structure/svg/preserveAspectRatio=xMinYMin\"), 0); }\n#[test] fn structure_svg_proportional_viewBox() { assert_eq!(render(\"tests/structure/svg/proportional-viewBox\"), 0); }\n#[test] fn structure_svg_rect_inside_a_non_SVG_element() { assert_eq!(render(\"tests/structure/svg/rect-inside-a-non-SVG-element\"), 0); }\n#[test] fn structure_svg_viewBox_not_at_zero_pos() { assert_eq!(render(\"tests/structure/svg/viewBox-not-at-zero-pos\"), 0); }\n#[test] fn structure_svg_xmlns_validation() { assert_eq!(render(\"tests/structure/svg/xmlns-validation\"), 0); }\n#[test] fn structure_switch_comment_as_first_child() { assert_eq!(render(\"tests/structure/switch/comment-as-first-child\"), 0); }\n#[test] fn structure_switch_display_none_on_child() { assert_eq!(render(\"tests/structure/switch/display-none-on-child\"), 0); }\n#[test] fn structure_switch_non_SVG_child() { assert_eq!(render(\"tests/structure/switch/non-SVG-child\"), 0); }\n#[test] fn structure_switch_requiredFeatures() { assert_eq!(render(\"tests/structure/switch/requiredFeatures\"), 0); }\n#[test] fn structure_switch_simple_case() { assert_eq!(render(\"tests/structure/switch/simple-case\"), 0); }\n#[test] fn structure_switch_single_child() { assert_eq!(render(\"tests/structure/switch/single-child\"), 0); }\n#[test] fn structure_switch_systemLanguage() { assert_eq!(render(\"tests/structure/switch/systemLanguage\"), 0); }\n#[test] fn structure_switch_systemLanguage_eq_en_GB() { assert_eq!(render(\"tests/structure/switch/systemLanguage=en-GB\"), 0); }\n#[test] fn structure_switch_systemLanguage_eq_en_US() { assert_eq!(render(\"tests/structure/switch/systemLanguage=en-US\"), 0); }\n#[test] fn structure_switch_systemLanguage_eq_en() { assert_eq!(render(\"tests/structure/switch/systemLanguage=en\"), 0); }\n#[test] fn structure_switch_systemLanguage_eq_ru_Ru() { assert_eq!(render(\"tests/structure/switch/systemLanguage=ru-Ru\"), 0); }\n#[test] fn structure_switch_systemLanguage_eq_ru_en() { assert_eq!(render(\"tests/structure/switch/systemLanguage=ru-en\"), 0); }\n#[test] fn structure_switch_with_attributes() { assert_eq!(render(\"tests/structure/switch/with-attributes\"), 0); }\n#[test] fn structure_symbol_content_outside_the_viewbox() { assert_eq!(render(\"tests/structure/symbol/content-outside-the-viewbox\"), 0); }\n#[test] fn structure_symbol_indirect_symbol_reference() { assert_eq!(render(\"tests/structure/symbol/indirect-symbol-reference\"), 0); }\n#[test] fn structure_symbol_opacity_on_symbol_with_viewBox() { assert_eq!(render(\"tests/structure/symbol/opacity-on-symbol-with-viewBox\"), 0); }\n#[test] fn structure_symbol_opacity_on_symbol() { assert_eq!(render(\"tests/structure/symbol/opacity-on-symbol\"), 0); }\n#[test] fn structure_symbol_opacity_on_use_and_symbol() { assert_eq!(render(\"tests/structure/symbol/opacity-on-use-and-symbol\"), 0); }\n#[test] fn structure_symbol_opacity_on_use() { assert_eq!(render(\"tests/structure/symbol/opacity-on-use\"), 0); }\n#[test] fn structure_symbol_simple_case() { assert_eq!(render(\"tests/structure/symbol/simple-case\"), 0); }\n#[test] fn structure_symbol_unused_symbol() { assert_eq!(render(\"tests/structure/symbol/unused-symbol\"), 0); }\n#[test] fn structure_symbol_with_custom_use_size() { assert_eq!(render(\"tests/structure/symbol/with-custom-use-size\"), 0); }\n#[test] fn structure_symbol_with_overflow_visible() { assert_eq!(render(\"tests/structure/symbol/with-overflow-visible\"), 0); }\n#[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); }\n#[test] fn structure_symbol_with_transform_on_use_no_size() { assert_eq!(render(\"tests/structure/symbol/with-transform-on-use-no-size\"), 0); }\n#[test] fn structure_symbol_with_transform_on_use() { assert_eq!(render(\"tests/structure/symbol/with-transform-on-use\"), 0); }\n#[test] fn structure_symbol_with_transform() { assert_eq!(render(\"tests/structure/symbol/with-transform\"), 0); }\n#[test] fn structure_symbol_with_viewBox_and_custom_use_rect() { assert_eq!(render(\"tests/structure/symbol/with-viewBox-and-custom-use-rect\"), 0); }\n#[test] fn structure_symbol_with_viewBox_and_custom_use_size() { assert_eq!(render(\"tests/structure/symbol/with-viewBox-and-custom-use-size\"), 0); }\n#[test] fn structure_symbol_with_viewBox() { assert_eq!(render(\"tests/structure/symbol/with-viewBox\"), 0); }\n#[test] fn structure_systemLanguage_en_GB() { assert_eq!(render(\"tests/structure/systemLanguage/en-GB\"), 0); }\n#[test] fn structure_systemLanguage_en_US() { assert_eq!(render(\"tests/structure/systemLanguage/en-US\"), 0); }\n#[test] fn structure_systemLanguage_en() { assert_eq!(render(\"tests/structure/systemLanguage/en\"), 0); }\n#[test] fn structure_systemLanguage_on_clipPath() { assert_eq!(render(\"tests/structure/systemLanguage/on-clipPath\"), 0); }\n#[test] fn structure_systemLanguage_on_defs() { assert_eq!(render(\"tests/structure/systemLanguage/on-defs\"), 0); }\n#[test] fn structure_systemLanguage_on_linearGradient() { assert_eq!(render(\"tests/structure/systemLanguage/on-linearGradient\"), 0); }\n#[test] fn structure_systemLanguage_on_svg() { assert_eq!(render(\"tests/structure/systemLanguage/on-svg\"), 0); }\n#[test] fn structure_systemLanguage_on_tspan() { assert_eq!(render(\"tests/structure/systemLanguage/on-tspan\"), 0); }\n#[test] fn structure_systemLanguage_ru_Ru() { assert_eq!(render(\"tests/structure/systemLanguage/ru-Ru\"), 0); }\n#[test] fn structure_systemLanguage_ru_en() { assert_eq!(render(\"tests/structure/systemLanguage/ru-en\"), 0); }\n#[test] fn structure_transform_default() { assert_eq!(render(\"tests/structure/transform/default\"), 0); }\n#[test] fn structure_transform_direct_transform() { assert_eq!(render(\"tests/structure/transform/direct-transform\"), 0); }\n#[test] fn structure_transform_empty() { assert_eq!(render(\"tests/structure/transform/empty\"), 0); }\n#[test] fn structure_transform_extra_spaces() { assert_eq!(render(\"tests/structure/transform/extra-spaces\"), 0); }\n#[test] fn structure_transform_matrix_no_commas() { assert_eq!(render(\"tests/structure/transform/matrix-no-commas\"), 0); }\n#[test] fn structure_transform_matrix() { assert_eq!(render(\"tests/structure/transform/matrix\"), 0); }\n#[test] fn structure_transform_nested_transforms_1() { assert_eq!(render(\"tests/structure/transform/nested-transforms-1\"), 0); }\n#[test] fn structure_transform_nested_transforms_2() { assert_eq!(render(\"tests/structure/transform/nested-transforms-2\"), 0); }\n#[test] fn structure_transform_numeric_character_references() { assert_eq!(render(\"tests/structure/transform/numeric-character-references\"), 0); }\n#[test] fn structure_transform_rotate_at_position() { assert_eq!(render(\"tests/structure/transform/rotate-at-position\"), 0); }\n#[test] fn structure_transform_rotate() { assert_eq!(render(\"tests/structure/transform/rotate\"), 0); }\n#[test] fn structure_transform_scale_without_Y() { assert_eq!(render(\"tests/structure/transform/scale-without-Y\"), 0); }\n#[test] fn structure_transform_scale() { assert_eq!(render(\"tests/structure/transform/scale\"), 0); }\n#[test] fn structure_transform_skewX() { assert_eq!(render(\"tests/structure/transform/skewX\"), 0); }\n#[test] fn structure_transform_skewY() { assert_eq!(render(\"tests/structure/transform/skewY\"), 0); }\n#[test] fn structure_transform_transform_list() { assert_eq!(render(\"tests/structure/transform/transform-list\"), 0); }\n#[test] fn structure_transform_translate_without_Y() { assert_eq!(render(\"tests/structure/transform/translate-without-Y\"), 0); }\n#[test] fn structure_transform_translate() { assert_eq!(render(\"tests/structure/transform/translate\"), 0); }\n#[test] fn structure_transform_zeroed_matrix() { assert_eq!(render(\"tests/structure/transform/zeroed-matrix\"), 0); }\n#[test] fn structure_transform_origin_bottom() { assert_eq!(render(\"tests/structure/transform-origin/bottom\"), 0); }\n#[test] fn structure_transform_origin_center() { assert_eq!(render(\"tests/structure/transform-origin/center\"), 0); }\n#[test] fn structure_transform_origin_keyword_length() { assert_eq!(render(\"tests/structure/transform-origin/keyword-length\"), 0); }\n#[test] fn structure_transform_origin_left() { assert_eq!(render(\"tests/structure/transform-origin/left\"), 0); }\n#[test] fn structure_transform_origin_length_percent() { assert_eq!(render(\"tests/structure/transform-origin/length-percent\"), 0); }\n#[test] fn structure_transform_origin_length_px() { assert_eq!(render(\"tests/structure/transform-origin/length-px\"), 0); }\n#[test] fn structure_transform_origin_no_transform() { assert_eq!(render(\"tests/structure/transform-origin/no-transform\"), 0); }\n#[test] fn structure_transform_origin_on_clippath_objectBoundingBox() { assert_eq!(render(\"tests/structure/transform-origin/on-clippath-objectBoundingBox\"), 0); }\n#[test] fn structure_transform_origin_on_clippath() { assert_eq!(render(\"tests/structure/transform-origin/on-clippath\"), 0); }\n#[test] fn structure_transform_origin_on_gradient_object_bounding_box() { assert_eq!(render(\"tests/structure/transform-origin/on-gradient-object-bounding-box\"), 0); }\n#[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); }\n#[test] fn structure_transform_origin_on_group() { assert_eq!(render(\"tests/structure/transform-origin/on-group\"), 0); }\n#[test] fn structure_transform_origin_on_image() { assert_eq!(render(\"tests/structure/transform-origin/on-image\"), 0); }\n#[test] fn structure_transform_origin_on_pattern_object_bounding_box() { assert_eq!(render(\"tests/structure/transform-origin/on-pattern-object-bounding-box\"), 0); }\n#[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); }\n#[test] fn structure_transform_origin_on_shape() { assert_eq!(render(\"tests/structure/transform-origin/on-shape\"), 0); }\n#[test] fn structure_transform_origin_on_text_path() { assert_eq!(render(\"tests/structure/transform-origin/on-text-path\"), 0); }\n#[test] fn structure_transform_origin_on_text() { assert_eq!(render(\"tests/structure/transform-origin/on-text\"), 0); }\n#[test] fn structure_transform_origin_right_bottom() { assert_eq!(render(\"tests/structure/transform-origin/right-bottom\"), 0); }\n#[test] fn structure_transform_origin_right() { assert_eq!(render(\"tests/structure/transform-origin/right\"), 0); }\n#[test] fn structure_transform_origin_top_left() { assert_eq!(render(\"tests/structure/transform-origin/top-left\"), 0); }\n#[test] fn structure_transform_origin_top() { assert_eq!(render(\"tests/structure/transform-origin/top\"), 0); }\n#[test] fn structure_transform_origin_transform_on_parent() { assert_eq!(render(\"tests/structure/transform-origin/transform-on-parent\"), 0); }\n#[test] fn structure_use_cSS_rules() { assert_eq!(render(\"tests/structure/use/cSS-rules\"), 0); }\n#[test] fn structure_use_complex_style_resolving_order() { assert_eq!(render(\"tests/structure/use/complex-style-resolving-order\"), 0); }\n#[test] fn structure_use_display_inheritance() { assert_eq!(render(\"tests/structure/use/display-inheritance\"), 0); }\n#[test] fn structure_use_duplicated_IDs() { assert_eq!(render(\"tests/structure/use/duplicated-IDs\"), 0); }\n#[test] fn structure_use_fill_opacity_inheritance() { assert_eq!(render(\"tests/structure/use/fill-opacity-inheritance\"), 0); }\n#[test] fn structure_use_from_defs() { assert_eq!(render(\"tests/structure/use/from-defs\"), 0); }\n#[test] fn structure_use_href_without_the_xlink_namespace() { assert_eq!(render(\"tests/structure/use/href-without-the-xlink-namespace\"), 0); }\n#[test] fn structure_use_indirect_recursive_1() { assert_eq!(render(\"tests/structure/use/indirect-recursive-1\"), 0); }\n#[test] fn structure_use_indirect_recursive_2() { assert_eq!(render(\"tests/structure/use/indirect-recursive-2\"), 0); }\n#[test] fn structure_use_indirect_recursive_3() { assert_eq!(render(\"tests/structure/use/indirect-recursive-3\"), 0); }\n#[test] fn structure_use_indirect() { assert_eq!(render(\"tests/structure/use/indirect\"), 0); }\n#[test] fn structure_use_nested_recursive_1() { assert_eq!(render(\"tests/structure/use/nested-recursive-1\"), 0); }\n#[test] fn structure_use_nested_recursive_2() { assert_eq!(render(\"tests/structure/use/nested-recursive-2\"), 0); }\n#[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); }\n#[test] fn structure_use_non_linear_order() { assert_eq!(render(\"tests/structure/use/non-linear-order\"), 0); }\n#[test] fn structure_use_opacity_inheritance() { assert_eq!(render(\"tests/structure/use/opacity-inheritance\"), 0); }\n#[test] fn structure_use_position_inheritance() { assert_eq!(render(\"tests/structure/use/position-inheritance\"), 0); }\n#[test] fn structure_use_recursive() { assert_eq!(render(\"tests/structure/use/recursive\"), 0); }\n#[test] fn structure_use_self_recursive() { assert_eq!(render(\"tests/structure/use/self-recursive\"), 0); }\n#[test] fn structure_use_simple_case() { assert_eq!(render(\"tests/structure/use/simple-case\"), 0); }\n#[test] fn structure_use_stroke_opacity_inheritance() { assert_eq!(render(\"tests/structure/use/stroke-opacity-inheritance\"), 0); }\n#[test] fn structure_use_style_inheritance_1() { assert_eq!(render(\"tests/structure/use/style-inheritance-1\"), 0); }\n#[test] fn structure_use_style_inheritance_2() { assert_eq!(render(\"tests/structure/use/style-inheritance-2\"), 0); }\n#[test] fn structure_use_style_inheritance_3() { assert_eq!(render(\"tests/structure/use/style-inheritance-3\"), 0); }\n#[test] fn structure_use_transform_attribute_1() { assert_eq!(render(\"tests/structure/use/transform-attribute-1\"), 0); }\n#[test] fn structure_use_transform_attribute_2() { assert_eq!(render(\"tests/structure/use/transform-attribute-2\"), 0); }\n#[test] fn structure_use_transform_inheritance() { assert_eq!(render(\"tests/structure/use/transform-inheritance\"), 0); }\n#[test] fn structure_use_with_currentColor() { assert_eq!(render(\"tests/structure/use/with-currentColor\"), 0); }\n#[test] fn structure_use_with_size() { assert_eq!(render(\"tests/structure/use/with-size\"), 0); }\n#[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); }\n#[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); }\n#[test] fn structure_use_xlink_to_an_external_file() { assert_eq!(render(\"tests/structure/use/xlink-to-an-external-file\"), 0); }\n#[test] fn structure_use_xlink_to_an_invalid_element_1() { assert_eq!(render(\"tests/structure/use/xlink-to-an-invalid-element-1\"), 0); }\n#[test] fn structure_use_xlink_to_an_invalid_element_2() { assert_eq!(render(\"tests/structure/use/xlink-to-an-invalid-element-2\"), 0); }\n#[test] fn structure_use_xlink_to_an_invalid_element_3() { assert_eq!(render(\"tests/structure/use/xlink-to-an-invalid-element-3\"), 0); }\n#[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); }\n#[test] fn structure_use_xlink_to_svg_element_with_rect() { assert_eq!(render(\"tests/structure/use/xlink-to-svg-element-with-rect\"), 0); }\n#[test] fn structure_use_xlink_to_svg_element_with_viewBox() { assert_eq!(render(\"tests/structure/use/xlink-to-svg-element-with-viewBox\"), 0); }\n#[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); }\n#[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); }\n#[test] fn structure_use_xlink_to_svg_element() { assert_eq!(render(\"tests/structure/use/xlink-to-svg-element\"), 0); }\n#[test] fn text_alignment_baseline_after_edge() { assert_eq!(render(\"tests/text/alignment-baseline/after-edge\"), 0); }\n#[test] fn text_alignment_baseline_alphabetic() { assert_eq!(render(\"tests/text/alignment-baseline/alphabetic\"), 0); }\n#[test] fn text_alignment_baseline_auto() { assert_eq!(render(\"tests/text/alignment-baseline/auto\"), 0); }\n#[test] fn text_alignment_baseline_baseline() { assert_eq!(render(\"tests/text/alignment-baseline/baseline\"), 0); }\n#[test] fn text_alignment_baseline_before_edge() { assert_eq!(render(\"tests/text/alignment-baseline/before-edge\"), 0); }\n#[test] fn text_alignment_baseline_central() { assert_eq!(render(\"tests/text/alignment-baseline/central\"), 0); }\n#[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); }\n#[test] fn text_alignment_baseline_hanging_on_tspan() { assert_eq!(render(\"tests/text/alignment-baseline/hanging-on-tspan\"), 0); }\n#[test] fn text_alignment_baseline_hanging_on_vertical() { assert_eq!(render(\"tests/text/alignment-baseline/hanging-on-vertical\"), 0); }\n#[test] fn text_alignment_baseline_hanging_with_underline() { assert_eq!(render(\"tests/text/alignment-baseline/hanging-with-underline\"), 0); }\n#[test] fn text_alignment_baseline_hanging() { assert_eq!(render(\"tests/text/alignment-baseline/hanging\"), 0); }\n#[test] fn text_alignment_baseline_ideographic() { assert_eq!(render(\"tests/text/alignment-baseline/ideographic\"), 0); }\n#[test] fn text_alignment_baseline_inherit() { assert_eq!(render(\"tests/text/alignment-baseline/inherit\"), 0); }\n#[test] fn text_alignment_baseline_mathematical() { assert_eq!(render(\"tests/text/alignment-baseline/mathematical\"), 0); }\n#[test] fn text_alignment_baseline_middle_on_textPath() { assert_eq!(render(\"tests/text/alignment-baseline/middle-on-textPath\"), 0); }\n#[test] fn text_alignment_baseline_middle() { assert_eq!(render(\"tests/text/alignment-baseline/middle\"), 0); }\n#[test] fn text_alignment_baseline_text_after_edge() { assert_eq!(render(\"tests/text/alignment-baseline/text-after-edge\"), 0); }\n#[test] fn text_alignment_baseline_text_before_edge() { assert_eq!(render(\"tests/text/alignment-baseline/text-before-edge\"), 0); }\n#[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); }\n#[test] fn text_baseline_shift__10() { assert_eq!(render(\"tests/text/baseline-shift/-10\"), 0); }\n#[test] fn text_baseline_shift__50percent() { assert_eq!(render(\"tests/text/baseline-shift/-50percent\"), 0); }\n#[test] fn text_baseline_shift_0() { assert_eq!(render(\"tests/text/baseline-shift/0\"), 0); }\n#[test] fn text_baseline_shift_10() { assert_eq!(render(\"tests/text/baseline-shift/10\"), 0); }\n#[test] fn text_baseline_shift_2mm() { assert_eq!(render(\"tests/text/baseline-shift/2mm\"), 0); }\n#[test] fn text_baseline_shift_50percent() { assert_eq!(render(\"tests/text/baseline-shift/50percent\"), 0); }\n#[test] fn text_baseline_shift_baseline() { assert_eq!(render(\"tests/text/baseline-shift/baseline\"), 0); }\n#[test] fn text_baseline_shift_deeply_nested_super() { assert_eq!(render(\"tests/text/baseline-shift/deeply-nested-super\"), 0); }\n#[test] fn text_baseline_shift_inheritance_1() { assert_eq!(render(\"tests/text/baseline-shift/inheritance-1\"), 0); }\n#[test] fn text_baseline_shift_inheritance_2() { assert_eq!(render(\"tests/text/baseline-shift/inheritance-2\"), 0); }\n#[test] fn text_baseline_shift_inheritance_3() { assert_eq!(render(\"tests/text/baseline-shift/inheritance-3\"), 0); }\n#[test] fn text_baseline_shift_inheritance_4() { assert_eq!(render(\"tests/text/baseline-shift/inheritance-4\"), 0); }\n#[test] fn text_baseline_shift_inheritance_5() { assert_eq!(render(\"tests/text/baseline-shift/inheritance-5\"), 0); }\n#[test] fn text_baseline_shift_invalid_value() { assert_eq!(render(\"tests/text/baseline-shift/invalid-value\"), 0); }\n#[test] fn text_baseline_shift_mixed_nested() { assert_eq!(render(\"tests/text/baseline-shift/mixed-nested\"), 0); }\n#[test] fn text_baseline_shift_nested_length() { assert_eq!(render(\"tests/text/baseline-shift/nested-length\"), 0); }\n#[test] fn text_baseline_shift_nested_super() { assert_eq!(render(\"tests/text/baseline-shift/nested-super\"), 0); }\n#[test] fn text_baseline_shift_nested_with_baseline_1() { assert_eq!(render(\"tests/text/baseline-shift/nested-with-baseline-1\"), 0); }\n#[test] fn text_baseline_shift_nested_with_baseline_2() { assert_eq!(render(\"tests/text/baseline-shift/nested-with-baseline-2\"), 0); }\n#[test] fn text_baseline_shift_sub() { assert_eq!(render(\"tests/text/baseline-shift/sub\"), 0); }\n#[test] fn text_baseline_shift_super() { assert_eq!(render(\"tests/text/baseline-shift/super\"), 0); }\n#[test] fn text_baseline_shift_with_rotate() { assert_eq!(render(\"tests/text/baseline-shift/with-rotate\"), 0); }\n#[test] fn text_color_font_cbdt() { assert_eq!(render(\"tests/text/color-font/cbdt\"), 0); }\n#[test] fn text_color_font_colrv0() { assert_eq!(render(\"tests/text/color-font/colrv0\"), 0); }\n#[test] fn text_color_font_colrv1() { assert_eq!(render(\"tests/text/color-font/colrv1\"), 0); }\n#[test] fn text_color_font_compound_emojis_and_coordinates_list() { assert_eq!(render(\"tests/text/color-font/compound-emojis-and-coordinates-list\"), 0); }\n#[test] fn text_color_font_compound_emojis() { assert_eq!(render(\"tests/text/color-font/compound-emojis\"), 0); }\n#[test] fn text_color_font_mixed_text_rtl() { assert_eq!(render(\"tests/text/color-font/mixed-text-rtl\"), 0); }\n#[test] fn text_color_font_mixed_text() { assert_eq!(render(\"tests/text/color-font/mixed-text\"), 0); }\n#[test] fn text_color_font_sbix() { assert_eq!(render(\"tests/text/color-font/sbix\"), 0); }\n#[test] fn text_color_font_svg() { assert_eq!(render(\"tests/text/color-font/svg\"), 0); }\n#[test] fn text_color_font_writing_mode_eq_tb() { assert_eq!(render(\"tests/text/color-font/writing-mode=tb\"), 0); }\n#[test] fn text_direction_rtl_with_vertical_writing_mode() { assert_eq!(render(\"tests/text/direction/rtl-with-vertical-writing-mode\"), 0); }\n#[test] fn text_direction_rtl() { assert_eq!(render(\"tests/text/direction/rtl\"), 0); }\n#[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); }\n#[test] fn text_dominant_baseline_alignment_baseline_eq_baseline_on_tspan() { assert_eq!(render(\"tests/text/dominant-baseline/alignment-baseline=baseline-on-tspan\"), 0); }\n#[test] fn text_dominant_baseline_alphabetic() { assert_eq!(render(\"tests/text/dominant-baseline/alphabetic\"), 0); }\n#[test] fn text_dominant_baseline_auto() { assert_eq!(render(\"tests/text/dominant-baseline/auto\"), 0); }\n#[test] fn text_dominant_baseline_central() { assert_eq!(render(\"tests/text/dominant-baseline/central\"), 0); }\n#[test] fn text_dominant_baseline_complex() { assert_eq!(render(\"tests/text/dominant-baseline/complex\"), 0); }\n#[test] fn text_dominant_baseline_different_alignment_baseline_on_tspan() { assert_eq!(render(\"tests/text/dominant-baseline/different-alignment-baseline-on-tspan\"), 0); }\n#[test] fn text_dominant_baseline_dummy_tspan() { assert_eq!(render(\"tests/text/dominant-baseline/dummy-tspan\"), 0); }\n#[test] fn text_dominant_baseline_equal_alignment_baseline_on_tspan() { assert_eq!(render(\"tests/text/dominant-baseline/equal-alignment-baseline-on-tspan\"), 0); }\n#[test] fn text_dominant_baseline_hanging() { assert_eq!(render(\"tests/text/dominant-baseline/hanging\"), 0); }\n#[test] fn text_dominant_baseline_ideographic() { assert_eq!(render(\"tests/text/dominant-baseline/ideographic\"), 0); }\n#[test] fn text_dominant_baseline_inherit() { assert_eq!(render(\"tests/text/dominant-baseline/inherit\"), 0); }\n#[test] fn text_dominant_baseline_mathematical() { assert_eq!(render(\"tests/text/dominant-baseline/mathematical\"), 0); }\n#[test] fn text_dominant_baseline_middle() { assert_eq!(render(\"tests/text/dominant-baseline/middle\"), 0); }\n#[test] fn text_dominant_baseline_nested() { assert_eq!(render(\"tests/text/dominant-baseline/nested\"), 0); }\n#[test] fn text_dominant_baseline_no_change() { assert_eq!(render(\"tests/text/dominant-baseline/no-change\"), 0); }\n#[test] fn text_dominant_baseline_reset_size() { assert_eq!(render(\"tests/text/dominant-baseline/reset-size\"), 0); }\n#[test] fn text_dominant_baseline_sequential() { assert_eq!(render(\"tests/text/dominant-baseline/sequential\"), 0); }\n#[test] fn text_dominant_baseline_text_after_edge() { assert_eq!(render(\"tests/text/dominant-baseline/text-after-edge\"), 0); }\n#[test] fn text_dominant_baseline_text_before_edge() { assert_eq!(render(\"tests/text/dominant-baseline/text-before-edge\"), 0); }\n#[test] fn text_dominant_baseline_use_script() { assert_eq!(render(\"tests/text/dominant-baseline/use-script\"), 0); }\n#[test] fn text_font_font_shorthand() { assert_eq!(render(\"tests/text/font/font-shorthand\"), 0); }\n#[test] fn text_font_simple_case() { assert_eq!(render(\"tests/text/font/simple-case\"), 0); }\n#[test] fn text_font_family_bold_sans_serif() { assert_eq!(render(\"tests/text/font-family/bold-sans-serif\"), 0); }\n#[test] fn text_font_family_cursive() { assert_eq!(render(\"tests/text/font-family/cursive\"), 0); }\n#[test] fn text_font_family_double_quoted() { assert_eq!(render(\"tests/text/font-family/double-quoted\"), 0); }\n#[test] fn text_font_family_fallback_1() { assert_eq!(render(\"tests/text/font-family/fallback-1\"), 0); }\n#[test] fn text_font_family_fallback_2() { assert_eq!(render(\"tests/text/font-family/fallback-2\"), 0); }\n#[test] fn text_font_family_fantasy() { assert_eq!(render(\"tests/text/font-family/fantasy\"), 0); }\n#[test] fn text_font_family_font_list() { assert_eq!(render(\"tests/text/font-family/font-list\"), 0); }\n#[test] fn text_font_family_monospace() { assert_eq!(render(\"tests/text/font-family/monospace\"), 0); }\n#[test] fn text_font_family_noto_sans() { assert_eq!(render(\"tests/text/font-family/noto-sans\"), 0); }\n#[test] fn text_font_family_sans_serif() { assert_eq!(render(\"tests/text/font-family/sans-serif\"), 0); }\n#[test] fn text_font_family_serif() { assert_eq!(render(\"tests/text/font-family/serif\"), 0); }\n#[test] fn text_font_family_source_sans_pro() { assert_eq!(render(\"tests/text/font-family/source-sans-pro\"), 0); }\n#[test] fn text_font_kerning_arabic_script() { assert_eq!(render(\"tests/text/font-kerning/arabic-script\"), 0); }\n#[test] fn text_font_kerning_as_property() { assert_eq!(render(\"tests/text/font-kerning/as-property\"), 0); }\n#[test] fn text_font_kerning_none() { assert_eq!(render(\"tests/text/font-kerning/none\"), 0); }\n#[test] fn text_font_size_em_nested_and_mixed() { assert_eq!(render(\"tests/text/font-size/em-nested-and-mixed\"), 0); }\n#[test] fn text_font_size_em_on_the_root_element() { assert_eq!(render(\"tests/text/font-size/em-on-the-root-element\"), 0); }\n#[test] fn text_font_size_em() { assert_eq!(render(\"tests/text/font-size/em\"), 0); }\n#[test] fn text_font_size_ex_nested_and_mixed() { assert_eq!(render(\"tests/text/font-size/ex-nested-and-mixed\"), 0); }\n#[test] fn text_font_size_ex_on_the_root_element() { assert_eq!(render(\"tests/text/font-size/ex-on-the-root-element\"), 0); }\n#[test] fn text_font_size_ex() { assert_eq!(render(\"tests/text/font-size/ex\"), 0); }\n#[test] fn text_font_size_inheritance() { assert_eq!(render(\"tests/text/font-size/inheritance\"), 0); }\n#[test] fn text_font_size_mixed_values() { assert_eq!(render(\"tests/text/font-size/mixed-values\"), 0); }\n#[test] fn text_font_size_named_value_without_a_parent() { assert_eq!(render(\"tests/text/font-size/named-value-without-a-parent\"), 0); }\n#[test] fn text_font_size_named_value() { assert_eq!(render(\"tests/text/font-size/named-value\"), 0); }\n#[test] fn text_font_size_negative_size() { assert_eq!(render(\"tests/text/font-size/negative-size\"), 0); }\n#[test] fn text_font_size_nested_percent_values_1() { assert_eq!(render(\"tests/text/font-size/nested-percent-values-1\"), 0); }\n#[test] fn text_font_size_nested_percent_values_2() { assert_eq!(render(\"tests/text/font-size/nested-percent-values-2\"), 0); }\n#[test] fn text_font_size_percent_value_without_a_parent() { assert_eq!(render(\"tests/text/font-size/percent-value-without-a-parent\"), 0); }\n#[test] fn text_font_size_percent_value() { assert_eq!(render(\"tests/text/font-size/percent-value\"), 0); }\n#[test] fn text_font_size_simple_case() { assert_eq!(render(\"tests/text/font-size/simple-case\"), 0); }\n#[test] fn text_font_size_zero_size_on_parent_1() { assert_eq!(render(\"tests/text/font-size/zero-size-on-parent-1\"), 0); }\n#[test] fn text_font_size_zero_size_on_parent_2() { assert_eq!(render(\"tests/text/font-size/zero-size-on-parent-2\"), 0); }\n#[test] fn text_font_size_zero_size_on_parent_3() { assert_eq!(render(\"tests/text/font-size/zero-size-on-parent-3\"), 0); }\n#[test] fn text_font_size_zero_size() { assert_eq!(render(\"tests/text/font-size/zero-size\"), 0); }\n#[test] fn text_font_size_adjust_simple_case() { assert_eq!(render(\"tests/text/font-size-adjust/simple-case\"), 0); }\n#[test] fn text_font_stretch_extra_condensed() { assert_eq!(render(\"tests/text/font-stretch/extra-condensed\"), 0); }\n#[test] fn text_font_stretch_inherit() { assert_eq!(render(\"tests/text/font-stretch/inherit\"), 0); }\n#[test] fn text_font_stretch_narrower() { assert_eq!(render(\"tests/text/font-stretch/narrower\"), 0); }\n#[test] fn text_font_style_inherit() { assert_eq!(render(\"tests/text/font-style/inherit\"), 0); }\n#[test] fn text_font_style_italic() { assert_eq!(render(\"tests/text/font-style/italic\"), 0); }\n#[test] fn text_font_style_oblique() { assert_eq!(render(\"tests/text/font-style/oblique\"), 0); }\n#[test] fn text_font_variant_inherit() { assert_eq!(render(\"tests/text/font-variant/inherit\"), 0); }\n#[test] fn text_font_variant_small_caps() { assert_eq!(render(\"tests/text/font-variant/small-caps\"), 0); }\n#[test] fn text_font_variation_settings_all_axes_combined() { assert_eq!(render(\"tests/text/font-variation-settings/all-axes-combined\"), 0); }\n#[test] fn text_font_variation_settings_auto_font_stretch_condensed() { assert_eq!(render(\"tests/text/font-variation-settings/auto-font-stretch-condensed\"), 0); }\n#[test] fn text_font_variation_settings_auto_font_style_oblique() { assert_eq!(render(\"tests/text/font-variation-settings/auto-font-style-oblique\"), 0); }\n#[test] fn text_font_variation_settings_auto_font_weight_700() { assert_eq!(render(\"tests/text/font-variation-settings/auto-font-weight-700\"), 0); }\n#[test] fn text_font_variation_settings_explicit_overrides_auto() { assert_eq!(render(\"tests/text/font-variation-settings/explicit-overrides-auto\"), 0); }\n#[test] fn text_font_variation_settings_grad_negative() { assert_eq!(render(\"tests/text/font-variation-settings/grad-negative\"), 0); }\n#[test] fn text_font_variation_settings_multiple_axes() { assert_eq!(render(\"tests/text/font-variation-settings/multiple-axes\"), 0); }\n#[test] fn text_font_variation_settings_opsz_144() { assert_eq!(render(\"tests/text/font-variation-settings/opsz-144\"), 0); }\n#[test] fn text_font_variation_settings_slnt_negative() { assert_eq!(render(\"tests/text/font-variation-settings/slnt-negative\"), 0); }\n#[test] fn text_font_variation_settings_wdth_151() { assert_eq!(render(\"tests/text/font-variation-settings/wdth-151\"), 0); }\n#[test] fn text_font_variation_settings_wdth_25() { assert_eq!(render(\"tests/text/font-variation-settings/wdth-25\"), 0); }\n#[test] fn text_font_variation_settings_wght_100() { assert_eq!(render(\"tests/text/font-variation-settings/wght-100\"), 0); }\n#[test] fn text_font_variation_settings_wght_700() { assert_eq!(render(\"tests/text/font-variation-settings/wght-700\"), 0); }\n#[test] fn text_font_variation_settings_xtra_extreme() { assert_eq!(render(\"tests/text/font-variation-settings/xtra-extreme\"), 0); }\n#[test] fn text_font_weight_650() { assert_eq!(render(\"tests/text/font-weight/650\"), 0); }\n#[test] fn text_font_weight_700() { assert_eq!(render(\"tests/text/font-weight/700\"), 0); }\n#[test] fn text_font_weight_bold() { assert_eq!(render(\"tests/text/font-weight/bold\"), 0); }\n#[test] fn text_font_weight_bolder_with_clamping() { assert_eq!(render(\"tests/text/font-weight/bolder-with-clamping\"), 0); }\n#[test] fn text_font_weight_bolder_without_parent() { assert_eq!(render(\"tests/text/font-weight/bolder-without-parent\"), 0); }\n#[test] fn text_font_weight_bolder() { assert_eq!(render(\"tests/text/font-weight/bolder\"), 0); }\n#[test] fn text_font_weight_inherit() { assert_eq!(render(\"tests/text/font-weight/inherit\"), 0); }\n#[test] fn text_font_weight_invalid_number_1() { assert_eq!(render(\"tests/text/font-weight/invalid-number-1\"), 0); }\n#[test] fn text_font_weight_lighter_with_clamping() { assert_eq!(render(\"tests/text/font-weight/lighter-with-clamping\"), 0); }\n#[test] fn text_font_weight_lighter_without_parent() { assert_eq!(render(\"tests/text/font-weight/lighter-without-parent\"), 0); }\n#[test] fn text_font_weight_lighter() { assert_eq!(render(\"tests/text/font-weight/lighter\"), 0); }\n#[test] fn text_font_weight_normal() { assert_eq!(render(\"tests/text/font-weight/normal\"), 0); }\n#[test] fn text_glyph_orientation_horizontal_simple_case() { assert_eq!(render(\"tests/text/glyph-orientation-horizontal/simple-case\"), 0); }\n#[test] fn text_glyph_orientation_vertical_simple_case() { assert_eq!(render(\"tests/text/glyph-orientation-vertical/simple-case\"), 0); }\n#[test] fn text_kerning_0() { assert_eq!(render(\"tests/text/kerning/0\"), 0); }\n#[test] fn text_kerning_10percent() { assert_eq!(render(\"tests/text/kerning/10percent\"), 0); }\n#[test] fn text_lengthAdjust_spacingAndGlyphs() { assert_eq!(render(\"tests/text/lengthAdjust/spacingAndGlyphs\"), 0); }\n#[test] fn text_lengthAdjust_text_on_path() { assert_eq!(render(\"tests/text/lengthAdjust/text-on-path\"), 0); }\n#[test] fn text_lengthAdjust_vertical() { assert_eq!(render(\"tests/text/lengthAdjust/vertical\"), 0); }\n#[test] fn text_lengthAdjust_with_underline() { assert_eq!(render(\"tests/text/lengthAdjust/with-underline\"), 0); }\n#[test] fn text_letter_spacing__3() { assert_eq!(render(\"tests/text/letter-spacing/-3\"), 0); }\n#[test] fn text_letter_spacing_0() { assert_eq!(render(\"tests/text/letter-spacing/0\"), 0); }\n#[test] fn text_letter_spacing_1mm() { assert_eq!(render(\"tests/text/letter-spacing/1mm\"), 0); }\n#[test] fn text_letter_spacing_3() { assert_eq!(render(\"tests/text/letter-spacing/3\"), 0); }\n#[test] fn text_letter_spacing_5percent() { assert_eq!(render(\"tests/text/letter-spacing/5percent\"), 0); }\n#[test] fn text_letter_spacing_filter_bbox() { assert_eq!(render(\"tests/text/letter-spacing/filter-bbox\"), 0); }\n#[test] fn text_letter_spacing_large_negative() { assert_eq!(render(\"tests/text/letter-spacing/large-negative\"), 0); }\n#[test] fn text_letter_spacing_mixed_scripts() { assert_eq!(render(\"tests/text/letter-spacing/mixed-scripts\"), 0); }\n#[test] fn text_letter_spacing_mixed_spacing() { assert_eq!(render(\"tests/text/letter-spacing/mixed-spacing\"), 0); }\n#[test] fn text_letter_spacing_non_ASCII_character() { assert_eq!(render(\"tests/text/letter-spacing/non-ASCII-character\"), 0); }\n#[test] fn text_letter_spacing_normal() { assert_eq!(render(\"tests/text/letter-spacing/normal\"), 0); }\n#[test] fn text_letter_spacing_on_Arabic() { assert_eq!(render(\"tests/text/letter-spacing/on-Arabic\"), 0); }\n#[test] fn text_text_bidi_reordering() { assert_eq!(render(\"tests/text/text/bidi-reordering\"), 0); }\n#[test] fn text_text_complex_grapheme_split_by_tspan() { assert_eq!(render(\"tests/text/text/complex-grapheme-split-by-tspan\"), 0); }\n#[test] fn text_text_complex_graphemes_and_coordinates_list() { assert_eq!(render(\"tests/text/text/complex-graphemes-and-coordinates-list\"), 0); }\n#[test] fn text_text_complex_graphemes() { assert_eq!(render(\"tests/text/text/complex-graphemes\"), 0); }\n#[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); }\n#[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); }\n#[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); }\n#[test] fn text_text_dx_and_dy_with_multiple_values() { assert_eq!(render(\"tests/text/text/dx-and-dy-with-multiple-values\"), 0); }\n#[test] fn text_text_em_and_ex_coordinates() { assert_eq!(render(\"tests/text/text/em-and-ex-coordinates\"), 0); }\n#[test] fn text_text_escaped_text_1() { assert_eq!(render(\"tests/text/text/escaped-text-1\"), 0); }\n#[test] fn text_text_escaped_text_2() { assert_eq!(render(\"tests/text/text/escaped-text-2\"), 0); }\n#[test] fn text_text_escaped_text_3() { assert_eq!(render(\"tests/text/text/escaped-text-3\"), 0); }\n#[test] fn text_text_escaped_text_4() { assert_eq!(render(\"tests/text/text/escaped-text-4\"), 0); }\n#[test] fn text_text_fill_rule_eq_evenodd() { assert_eq!(render(\"tests/text/text/fill-rule=evenodd\"), 0); }\n#[test] fn text_text_filter_bbox() { assert_eq!(render(\"tests/text/text/filter-bbox\"), 0); }\n#[test] fn text_text_glyph_splitting() { assert_eq!(render(\"tests/text/text/glyph-splitting\"), 0); }\n#[test] fn text_text_ligatures_handling_in_mixed_fonts_1() { assert_eq!(render(\"tests/text/text/ligatures-handling-in-mixed-fonts-1\"), 0); }\n#[test] fn text_text_ligatures_handling_in_mixed_fonts_2() { assert_eq!(render(\"tests/text/text/ligatures-handling-in-mixed-fonts-2\"), 0); }\n#[test] fn text_text_mm_coordinates() { assert_eq!(render(\"tests/text/text/mm-coordinates\"), 0); }\n#[test] fn text_text_nested() { assert_eq!(render(\"tests/text/text/nested\"), 0); }\n#[test] fn text_text_no_coordinates() { assert_eq!(render(\"tests/text/text/no-coordinates\"), 0); }\n#[test] fn text_text_percent_value_on_dx_and_dy() { assert_eq!(render(\"tests/text/text/percent-value-on-dx-and-dy\"), 0); }\n#[test] fn text_text_percent_value_on_x_and_y() { assert_eq!(render(\"tests/text/text/percent-value-on-x-and-y\"), 0); }\n#[test] fn text_text_real_text_height() { assert_eq!(render(\"tests/text/text/real-text-height\"), 0); }\n#[test] fn text_text_rotate_on_Arabic() { assert_eq!(render(\"tests/text/text/rotate-on-Arabic\"), 0); }\n#[test] fn text_text_rotate_with_an_invalid_angle() { assert_eq!(render(\"tests/text/text/rotate-with-an-invalid-angle\"), 0); }\n#[test] fn text_text_rotate_with_less_values_than_characters() { assert_eq!(render(\"tests/text/text/rotate-with-less-values-than-characters\"), 0); }\n#[test] fn text_text_rotate_with_more_values_than_characters() { assert_eq!(render(\"tests/text/text/rotate-with-more-values-than-characters\"), 0); }\n#[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); }\n#[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); }\n#[test] fn text_text_rotate_with_multiple_values() { assert_eq!(render(\"tests/text/text/rotate-with-multiple-values\"), 0); }\n#[test] fn text_text_rotate() { assert_eq!(render(\"tests/text/text/rotate\"), 0); }\n#[test] fn text_text_simple_case() { assert_eq!(render(\"tests/text/text/simple-case\"), 0); }\n#[test] fn text_text_transform() { assert_eq!(render(\"tests/text/text/transform\"), 0); }\n#[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); }\n#[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); }\n#[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); }\n#[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); }\n#[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); }\n#[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); }\n#[test] fn text_text_x_and_y_with_multiple_values() { assert_eq!(render(\"tests/text/text/x-and-y-with-multiple-values\"), 0); }\n#[test] fn text_text_xml_lang_eq_ja() { assert_eq!(render(\"tests/text/text/xml-lang=ja\"), 0); }\n#[test] fn text_text_xml_space() { assert_eq!(render(\"tests/text/text/xml-space\"), 0); }\n#[test] fn text_text_zalgo() { assert_eq!(render(\"tests/text/text/zalgo\"), 0); }\n#[test] fn text_text_anchor_coordinates_list() { assert_eq!(render(\"tests/text/text-anchor/coordinates-list\"), 0); }\n#[test] fn text_text_anchor_end_on_text() { assert_eq!(render(\"tests/text/text-anchor/end-on-text\"), 0); }\n#[test] fn text_text_anchor_end_with_letter_spacing() { assert_eq!(render(\"tests/text/text-anchor/end-with-letter-spacing\"), 0); }\n#[test] fn text_text_anchor_inheritance_1() { assert_eq!(render(\"tests/text/text-anchor/inheritance-1\"), 0); }\n#[test] fn text_text_anchor_inheritance_2() { assert_eq!(render(\"tests/text/text-anchor/inheritance-2\"), 0); }\n#[test] fn text_text_anchor_inheritance_3() { assert_eq!(render(\"tests/text/text-anchor/inheritance-3\"), 0); }\n#[test] fn text_text_anchor_invalid_value_on_text() { assert_eq!(render(\"tests/text/text-anchor/invalid-value-on-text\"), 0); }\n#[test] fn text_text_anchor_middle_on_text() { assert_eq!(render(\"tests/text/text-anchor/middle-on-text\"), 0); }\n#[test] fn text_text_anchor_on_the_first_tspan() { assert_eq!(render(\"tests/text/text-anchor/on-the-first-tspan\"), 0); }\n#[test] fn text_text_anchor_on_tspan_with_arabic() { assert_eq!(render(\"tests/text/text-anchor/on-tspan-with-arabic\"), 0); }\n#[test] fn text_text_anchor_on_tspan() { assert_eq!(render(\"tests/text/text-anchor/on-tspan\"), 0); }\n#[test] fn text_text_anchor_start_on_text() { assert_eq!(render(\"tests/text/text-anchor/start-on-text\"), 0); }\n#[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); }\n#[test] fn text_text_decoration_all_types_inline_comma_separated() { assert_eq!(render(\"tests/text/text-decoration/all-types-inline-comma-separated\"), 0); }\n#[test] fn text_text_decoration_all_types_inline_no_spaces() { assert_eq!(render(\"tests/text/text-decoration/all-types-inline-no-spaces\"), 0); }\n#[test] fn text_text_decoration_all_types_inline() { assert_eq!(render(\"tests/text/text-decoration/all-types-inline\"), 0); }\n#[test] fn text_text_decoration_all_types_nested() { assert_eq!(render(\"tests/text/text-decoration/all-types-nested\"), 0); }\n#[test] fn text_text_decoration_indirect_with_multiple_colors() { assert_eq!(render(\"tests/text/text-decoration/indirect-with-multiple-colors\"), 0); }\n#[test] fn text_text_decoration_indirect() { assert_eq!(render(\"tests/text/text-decoration/indirect\"), 0); }\n#[test] fn text_text_decoration_line_through() { assert_eq!(render(\"tests/text/text-decoration/line-through\"), 0); }\n#[test] fn text_text_decoration_outside_the_text_element() { assert_eq!(render(\"tests/text/text-decoration/outside-the-text-element\"), 0); }\n#[test] fn text_text_decoration_overline() { assert_eq!(render(\"tests/text/text-decoration/overline\"), 0); }\n#[test] fn text_text_decoration_style_resolving_1() { assert_eq!(render(\"tests/text/text-decoration/style-resolving-1\"), 0); }\n#[test] fn text_text_decoration_style_resolving_2() { assert_eq!(render(\"tests/text/text-decoration/style-resolving-2\"), 0); }\n#[test] fn text_text_decoration_style_resolving_3() { assert_eq!(render(\"tests/text/text-decoration/style-resolving-3\"), 0); }\n#[test] fn text_text_decoration_style_resolving_4() { assert_eq!(render(\"tests/text/text-decoration/style-resolving-4\"), 0); }\n#[test] fn text_text_decoration_tspan_decoration() { assert_eq!(render(\"tests/text/text-decoration/tspan-decoration\"), 0); }\n#[test] fn text_text_decoration_underline_with_dy_list_1() { assert_eq!(render(\"tests/text/text-decoration/underline-with-dy-list-1\"), 0); }\n#[test] fn text_text_decoration_underline_with_dy_list_2() { assert_eq!(render(\"tests/text/text-decoration/underline-with-dy-list-2\"), 0); }\n#[test] fn text_text_decoration_underline_with_rotate_list_3() { assert_eq!(render(\"tests/text/text-decoration/underline-with-rotate-list-3\"), 0); }\n#[test] fn text_text_decoration_underline_with_rotate_list_4() { assert_eq!(render(\"tests/text/text-decoration/underline-with-rotate-list-4\"), 0); }\n#[test] fn text_text_decoration_underline_with_y_list() { assert_eq!(render(\"tests/text/text-decoration/underline-with-y-list\"), 0); }\n#[test] fn text_text_decoration_underline() { assert_eq!(render(\"tests/text/text-decoration/underline\"), 0); }\n#[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); }\n#[test] fn text_text_rendering_geometricPrecision() { assert_eq!(render(\"tests/text/text-rendering/geometricPrecision\"), 0); }\n#[test] fn text_text_rendering_on_tspan() { assert_eq!(render(\"tests/text/text-rendering/on-tspan\"), 0); }\n#[test] fn text_text_rendering_optimizeLegibility() { assert_eq!(render(\"tests/text/text-rendering/optimizeLegibility\"), 0); }\n#[test] fn text_text_rendering_optimizeSpeed() { assert_eq!(render(\"tests/text/text-rendering/optimizeSpeed\"), 0); }\n#[test] fn text_text_rendering_with_underline() { assert_eq!(render(\"tests/text/text-rendering/with-underline\"), 0); }\n#[test] fn text_textLength_150_on_parent() { assert_eq!(render(\"tests/text/textLength/150-on-parent\"), 0); }\n#[test] fn text_textLength_150_on_tspan() { assert_eq!(render(\"tests/text/textLength/150-on-tspan\"), 0); }\n#[test] fn text_textLength_150() { assert_eq!(render(\"tests/text/textLength/150\"), 0); }\n#[test] fn text_textLength_40mm() { assert_eq!(render(\"tests/text/textLength/40mm\"), 0); }\n#[test] fn text_textLength_75percent() { assert_eq!(render(\"tests/text/textLength/75percent\"), 0); }\n#[test] fn text_textLength_arabic_with_lengthAdjust() { assert_eq!(render(\"tests/text/textLength/arabic-with-lengthAdjust\"), 0); }\n#[test] fn text_textLength_arabic() { assert_eq!(render(\"tests/text/textLength/arabic\"), 0); }\n#[test] fn text_textLength_inherit() { assert_eq!(render(\"tests/text/textLength/inherit\"), 0); }\n#[test] fn text_textLength_negative() { assert_eq!(render(\"tests/text/textLength/negative\"), 0); }\n#[test] fn text_textLength_on_a_single_tspan() { assert_eq!(render(\"tests/text/textLength/on-a-single-tspan\"), 0); }\n#[test] fn text_textLength_on_text_and_tspan() { assert_eq!(render(\"tests/text/textLength/on-text-and-tspan\"), 0); }\n#[test] fn text_textLength_zero() { assert_eq!(render(\"tests/text/textLength/zero\"), 0); }\n#[test] fn text_textPath_closed_path() { assert_eq!(render(\"tests/text/textPath/closed-path\"), 0); }\n#[test] fn text_textPath_complex() { assert_eq!(render(\"tests/text/textPath/complex\"), 0); }\n#[test] fn text_textPath_dy_with_tiny_coordinates() { assert_eq!(render(\"tests/text/textPath/dy-with-tiny-coordinates\"), 0); }\n#[test] fn text_textPath_invalid_link() { assert_eq!(render(\"tests/text/textPath/invalid-link\"), 0); }\n#[test] fn text_textPath_invalid_textPath_in_the_middle() { assert_eq!(render(\"tests/text/textPath/invalid-textPath-in-the-middle\"), 0); }\n#[test] fn text_textPath_link_to_rect() { assert_eq!(render(\"tests/text/textPath/link-to-rect\"), 0); }\n#[test] fn text_textPath_m_A_path() { assert_eq!(render(\"tests/text/textPath/m-A-path\"), 0); }\n#[test] fn text_textPath_m_L_Z_path() { assert_eq!(render(\"tests/text/textPath/m-L-Z-path\"), 0); }\n#[test] fn text_textPath_method_eq_stretch() { assert_eq!(render(\"tests/text/textPath/method=stretch\"), 0); }\n#[test] fn text_textPath_mixed_children_1() { assert_eq!(render(\"tests/text/textPath/mixed-children-1\"), 0); }\n#[test] fn text_textPath_mixed_children_2() { assert_eq!(render(\"tests/text/textPath/mixed-children-2\"), 0); }\n#[test] fn text_textPath_nested() { assert_eq!(render(\"tests/text/textPath/nested\"), 0); }\n#[test] fn text_textPath_no_link() { assert_eq!(render(\"tests/text/textPath/no-link\"), 0); }\n#[test] fn text_textPath_path_with_ClosePath() { assert_eq!(render(\"tests/text/textPath/path-with-ClosePath\"), 0); }\n#[test] fn text_textPath_path_with_subpaths_and_startOffset() { assert_eq!(render(\"tests/text/textPath/path-with-subpaths-and-startOffset\"), 0); }\n#[test] fn text_textPath_path_with_subpaths() { assert_eq!(render(\"tests/text/textPath/path-with-subpaths\"), 0); }\n#[test] fn text_textPath_side_eq_right() { assert_eq!(render(\"tests/text/textPath/side=right\"), 0); }\n#[test] fn text_textPath_simple_case() { assert_eq!(render(\"tests/text/textPath/simple-case\"), 0); }\n#[test] fn text_textPath_spacing_eq_auto() { assert_eq!(render(\"tests/text/textPath/spacing=auto\"), 0); }\n#[test] fn text_textPath_startOffset_eq__100() { assert_eq!(render(\"tests/text/textPath/startOffset=-100\"), 0); }\n#[test] fn text_textPath_startOffset_eq_10percent() { assert_eq!(render(\"tests/text/textPath/startOffset=10percent\"), 0); }\n#[test] fn text_textPath_startOffset_eq_30() { assert_eq!(render(\"tests/text/textPath/startOffset=30\"), 0); }\n#[test] fn text_textPath_startOffset_eq_5mm() { assert_eq!(render(\"tests/text/textPath/startOffset=5mm\"), 0); }\n#[test] fn text_textPath_startOffset_eq_9999() { assert_eq!(render(\"tests/text/textPath/startOffset=9999\"), 0); }\n#[test] fn text_textPath_tspan_with_absolute_position() { assert_eq!(render(\"tests/text/textPath/tspan-with-absolute-position\"), 0); }\n#[test] fn text_textPath_tspan_with_relative_position() { assert_eq!(render(\"tests/text/textPath/tspan-with-relative-position\"), 0); }\n#[test] fn text_textPath_two_paths() { assert_eq!(render(\"tests/text/textPath/two-paths\"), 0); }\n#[test] fn text_textPath_very_long_text() { assert_eq!(render(\"tests/text/textPath/very-long-text\"), 0); }\n#[test] fn text_textPath_with_baseline_shift_and_rotate() { assert_eq!(render(\"tests/text/textPath/with-baseline-shift-and-rotate\"), 0); }\n#[test] fn text_textPath_with_baseline_shift() { assert_eq!(render(\"tests/text/textPath/with-baseline-shift\"), 0); }\n#[test] fn text_textPath_with_big_letter_spacing() { assert_eq!(render(\"tests/text/textPath/with-big-letter-spacing\"), 0); }\n#[test] fn text_textPath_with_coordinates_on_text() { assert_eq!(render(\"tests/text/textPath/with-coordinates-on-text\"), 0); }\n#[test] fn text_textPath_with_coordinates_on_textPath() { assert_eq!(render(\"tests/text/textPath/with-coordinates-on-textPath\"), 0); }\n#[test] fn text_textPath_with_filter() { assert_eq!(render(\"tests/text/textPath/with-filter\"), 0); }\n#[test] fn text_textPath_with_invalid_path_and_xlink_href() { assert_eq!(render(\"tests/text/textPath/with-invalid-path-and-xlink-href\"), 0); }\n#[test] fn text_textPath_with_letter_spacing() { assert_eq!(render(\"tests/text/textPath/with-letter-spacing\"), 0); }\n#[test] fn text_textPath_with_path_and_xlink_href() { assert_eq!(render(\"tests/text/textPath/with-path-and-xlink-href\"), 0); }\n#[test] fn text_textPath_with_path() { assert_eq!(render(\"tests/text/textPath/with-path\"), 0); }\n#[test] fn text_textPath_with_rotate() { assert_eq!(render(\"tests/text/textPath/with-rotate\"), 0); }\n#[test] fn text_textPath_with_text_anchor() { assert_eq!(render(\"tests/text/textPath/with-text-anchor\"), 0); }\n#[test] fn text_textPath_with_transform_on_a_referenced_path() { assert_eq!(render(\"tests/text/textPath/with-transform-on-a-referenced-path\"), 0); }\n#[test] fn text_textPath_with_transform_outside_a_referenced_path() { assert_eq!(render(\"tests/text/textPath/with-transform-outside-a-referenced-path\"), 0); }\n#[test] fn text_textPath_with_underline() { assert_eq!(render(\"tests/text/textPath/with-underline\"), 0); }\n#[test] fn text_textPath_writing_mode_eq_tb() { assert_eq!(render(\"tests/text/textPath/writing-mode=tb\"), 0); }\n#[test] fn text_tref_link_to_a_complex_text() { assert_eq!(render(\"tests/text/tref/link-to-a-complex-text\"), 0); }\n#[test] fn text_tref_link_to_a_non_SVG_element() { assert_eq!(render(\"tests/text/tref/link-to-a-non-SVG-element\"), 0); }\n#[test] fn text_tref_link_to_a_non_text_element() { assert_eq!(render(\"tests/text/tref/link-to-a-non-text-element\"), 0); }\n#[test] fn text_tref_link_to_an_external_file_element() { assert_eq!(render(\"tests/text/tref/link-to-an-external-file-element\"), 0); }\n#[test] fn text_tref_link_to_text() { assert_eq!(render(\"tests/text/tref/link-to-text\"), 0); }\n#[test] fn text_tref_nested() { assert_eq!(render(\"tests/text/tref/nested\"), 0); }\n#[test] fn text_tref_position_attributes() { assert_eq!(render(\"tests/text/tref/position-attributes\"), 0); }\n#[test] fn text_tref_style_attributes() { assert_eq!(render(\"tests/text/tref/style-attributes\"), 0); }\n#[test] fn text_tref_with_a_title_child() { assert_eq!(render(\"tests/text/tref/with-a-title-child\"), 0); }\n#[test] fn text_tref_with_text() { assert_eq!(render(\"tests/text/tref/with-text\"), 0); }\n#[test] fn text_tref_xml_space() { assert_eq!(render(\"tests/text/tref/xml-space\"), 0); }\n#[test] fn text_tspan_bidi_reordering() { assert_eq!(render(\"tests/text/tspan/bidi-reordering\"), 0); }\n#[test] fn text_tspan_mixed_font_size() { assert_eq!(render(\"tests/text/tspan/mixed-font-size\"), 0); }\n#[test] fn text_tspan_mixed_xml_space_1() { assert_eq!(render(\"tests/text/tspan/mixed-xml-space-1\"), 0); }\n#[test] fn text_tspan_mixed_xml_space_2() { assert_eq!(render(\"tests/text/tspan/mixed-xml-space-2\"), 0); }\n#[test] fn text_tspan_mixed_xml_space_3() { assert_eq!(render(\"tests/text/tspan/mixed-xml-space-3\"), 0); }\n#[test] fn text_tspan_mixed() { assert_eq!(render(\"tests/text/tspan/mixed\"), 0); }\n#[test] fn text_tspan_multiple_coordinates() { assert_eq!(render(\"tests/text/tspan/multiple-coordinates\"), 0); }\n#[test] fn text_tspan_nested_rotate() { assert_eq!(render(\"tests/text/tspan/nested-rotate\"), 0); }\n#[test] fn text_tspan_nested_whitespaces() { assert_eq!(render(\"tests/text/tspan/nested-whitespaces\"), 0); }\n#[test] fn text_tspan_nested() { assert_eq!(render(\"tests/text/tspan/nested\"), 0); }\n#[test] fn text_tspan_only_with_y() { assert_eq!(render(\"tests/text/tspan/only-with-y\"), 0); }\n#[test] fn text_tspan_outside_the_text() { assert_eq!(render(\"tests/text/tspan/outside-the-text\"), 0); }\n#[test] fn text_tspan_pseudo_multi_line() { assert_eq!(render(\"tests/text/tspan/pseudo-multi-line\"), 0); }\n#[test] fn text_tspan_rotate_and_display_none() { assert_eq!(render(\"tests/text/tspan/rotate-and-display-none\"), 0); }\n#[test] fn text_tspan_rotate_on_child() { assert_eq!(render(\"tests/text/tspan/rotate-on-child\"), 0); }\n#[test] fn text_tspan_sequential() { assert_eq!(render(\"tests/text/tspan/sequential\"), 0); }\n#[test] fn text_tspan_style_override() { assert_eq!(render(\"tests/text/tspan/style-override\"), 0); }\n#[test] fn text_tspan_text_shaping_across_multiple_tspan_1() { assert_eq!(render(\"tests/text/tspan/text-shaping-across-multiple-tspan-1\"), 0); }\n#[test] fn text_tspan_text_shaping_across_multiple_tspan_2() { assert_eq!(render(\"tests/text/tspan/text-shaping-across-multiple-tspan-2\"), 0); }\n#[test] fn text_tspan_transform() { assert_eq!(render(\"tests/text/tspan/transform\"), 0); }\n#[test] fn text_tspan_tspan_bbox_1() { assert_eq!(render(\"tests/text/tspan/tspan-bbox-1\"), 0); }\n#[test] fn text_tspan_tspan_bbox_2() { assert_eq!(render(\"tests/text/tspan/tspan-bbox-2\"), 0); }\n#[test] fn text_tspan_with_clip_path() { assert_eq!(render(\"tests/text/tspan/with-clip-path\"), 0); }\n#[test] fn text_tspan_with_dy() { assert_eq!(render(\"tests/text/tspan/with-dy\"), 0); }\n#[test] fn text_tspan_with_filter() { assert_eq!(render(\"tests/text/tspan/with-filter\"), 0); }\n#[test] fn text_tspan_with_mask() { assert_eq!(render(\"tests/text/tspan/with-mask\"), 0); }\n#[test] fn text_tspan_with_opacity() { assert_eq!(render(\"tests/text/tspan/with-opacity\"), 0); }\n#[test] fn text_tspan_with_x_and_y() { assert_eq!(render(\"tests/text/tspan/with-x-and-y\"), 0); }\n#[test] fn text_tspan_without_attributes() { assert_eq!(render(\"tests/text/tspan/without-attributes\"), 0); }\n#[test] fn text_tspan_xml_space_1() { assert_eq!(render(\"tests/text/tspan/xml-space-1\"), 0); }\n#[test] fn text_tspan_xml_space_2() { assert_eq!(render(\"tests/text/tspan/xml-space-2\"), 0); }\n#[test] fn text_unicode_bidi_bidi_override() { assert_eq!(render(\"tests/text/unicode-bidi/bidi-override\"), 0); }\n#[test] fn text_word_spacing__5() { assert_eq!(render(\"tests/text/word-spacing/-5\"), 0); }\n#[test] fn text_word_spacing_0() { assert_eq!(render(\"tests/text/word-spacing/0\"), 0); }\n#[test] fn text_word_spacing_10() { assert_eq!(render(\"tests/text/word-spacing/10\"), 0); }\n#[test] fn text_word_spacing_2mm() { assert_eq!(render(\"tests/text/word-spacing/2mm\"), 0); }\n#[test] fn text_word_spacing_5percent() { assert_eq!(render(\"tests/text/word-spacing/5percent\"), 0); }\n#[test] fn text_word_spacing_large_negative() { assert_eq!(render(\"tests/text/word-spacing/large-negative\"), 0); }\n#[test] fn text_word_spacing_normal() { assert_eq!(render(\"tests/text/word-spacing/normal\"), 0); }\n#[test] fn text_writing_mode_arabic_with_rl() { assert_eq!(render(\"tests/text/writing-mode/arabic-with-rl\"), 0); }\n#[test] fn text_writing_mode_horizontal_tb() { assert_eq!(render(\"tests/text/writing-mode/horizontal-tb\"), 0); }\n#[test] fn text_writing_mode_inheritance() { assert_eq!(render(\"tests/text/writing-mode/inheritance\"), 0); }\n#[test] fn text_writing_mode_invalid_value() { assert_eq!(render(\"tests/text/writing-mode/invalid-value\"), 0); }\n#[test] fn text_writing_mode_japanese_with_tb() { assert_eq!(render(\"tests/text/writing-mode/japanese-with-tb\"), 0); }\n#[test] fn text_writing_mode_lr_tb() { assert_eq!(render(\"tests/text/writing-mode/lr-tb\"), 0); }\n#[test] fn text_writing_mode_lr() { assert_eq!(render(\"tests/text/writing-mode/lr\"), 0); }\n#[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); }\n#[test] fn text_writing_mode_mixed_languages_with_tb() { assert_eq!(render(\"tests/text/writing-mode/mixed-languages-with-tb\"), 0); }\n#[test] fn text_writing_mode_on_tspan() { assert_eq!(render(\"tests/text/writing-mode/on-tspan\"), 0); }\n#[test] fn text_writing_mode_rl_tb() { assert_eq!(render(\"tests/text/writing-mode/rl-tb\"), 0); }\n#[test] fn text_writing_mode_rl() { assert_eq!(render(\"tests/text/writing-mode/rl\"), 0); }\n#[test] fn text_writing_mode_tb_and_punctuation() { assert_eq!(render(\"tests/text/writing-mode/tb-and-punctuation\"), 0); }\n#[test] fn text_writing_mode_tb_rl() { assert_eq!(render(\"tests/text/writing-mode/tb-rl\"), 0); }\n#[test] fn text_writing_mode_tb_with_alignment() { assert_eq!(render(\"tests/text/writing-mode/tb-with-alignment\"), 0); }\n#[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); }\n#[test] fn text_writing_mode_tb_with_dx_on_tspan() { assert_eq!(render(\"tests/text/writing-mode/tb-with-dx-on-tspan\"), 0); }\n#[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); }\n#[test] fn text_writing_mode_tb_with_rotate_and_underline() { assert_eq!(render(\"tests/text/writing-mode/tb-with-rotate-and-underline\"), 0); }\n#[test] fn text_writing_mode_tb_with_rotate() { assert_eq!(render(\"tests/text/writing-mode/tb-with-rotate\"), 0); }\n#[test] fn text_writing_mode_tb() { assert_eq!(render(\"tests/text/writing-mode/tb\"), 0); }\n#[test] fn text_writing_mode_vertical_lr() { assert_eq!(render(\"tests/text/writing-mode/vertical-lr\"), 0); }\n#[test] fn text_writing_mode_vertical_rl() { assert_eq!(render(\"tests/text/writing-mode/vertical-rl\"), 0); }\n"
  },
  {
    "path": "crates/resvg/tests/resources/green.css",
    "content": "#rect1 { fill:green; }\n"
  },
  {
    "path": "crates/usvg/Cargo.toml",
    "content": "[package]\nname = \"usvg\"\nversion = \"0.47.0\"\nkeywords = [\"svg\"]\nlicense.workspace = true\nedition = \"2024\"\nrust-version = \"1.87.0\"\ndescription = \"An SVG simplification library.\"\ncategories = [\"multimedia::images\"]\nrepository = \"https://github.com/linebender/resvg\"\ndocumentation = \"https://docs.rs/usvg/\"\nreadme = \"README.md\"\nexclude = [\"tests\"]\nworkspace = \"../..\"\n\n[[bin]]\nname = \"usvg\"\nrequired-features = [\"text\", \"system-fonts\", \"memmap-fonts\"]\n\n[dependencies]\nbase64 = \"0.22\" # for embedded images\nlog = \"0.4\"\npico-args = { version = \"0.5\", features = [\"eq-separator\"] }\nstrict-num = \"0.1.1\"\nsvgtypes = \"0.16.1\"\ntiny-skia-path = \"0.12.0\"\nxmlwriter = \"0.1\"\n\n# parser\ndata-url = \"0.3\" # for href parsing\nflate2 = { version = \"1.1\", default-features = false, features = [\"rust_backend\"] } # SVGZ decoding\nimagesize = \"0.14.0\" # raster images size detection\nkurbo = \"0.13.0\" # Bezier curves utils\nroxmltree = \"0.21.1\"\nsimplecss = \"0.2\"\nsiphasher = \"1.0\" # perfect hash implementation\n\n# text\nfontdb = { version = \"0.23.0\", default-features = false, optional = true }\nrustybuzz = { version = \"0.20.1\", optional = true }\n# Note: ttf-parser is re-exported from rustybuzz, but we need gvar-alloc for variable fonts\n# with many variation axes (like Roboto Flex which has 13 axes)\nttf-parser = { version = \"0.25.1\", features = [\"gvar-alloc\"], optional = true }\nunicode-bidi = { version = \"0.3\", optional = true }\nunicode-script = { version = \"0.5\", optional = true }\nunicode-vo = { version = \"0.1\", optional = true }\n\n[dev-dependencies]\nonce_cell = \"1.21\"\n\n[features]\ndefault = [\"text\", \"system-fonts\", \"memmap-fonts\"]\n# Enables text-to-path conversion support.\n# Adds around 400KiB to your binary.\ntext = [\"fontdb\", \"rustybuzz\", \"ttf-parser\", \"unicode-bidi\", \"unicode-script\", \"unicode-vo\"]\n# Enables system fonts loading.\nsystem-fonts = [\"fontdb/fs\", \"fontdb/fontconfig\"]\n# Enables font files memmaping for faster loading.\nmemmap-fonts = [\"fontdb/memmap\"]\n"
  },
  {
    "path": "crates/usvg/LICENSE-APACHE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "crates/usvg/LICENSE-MIT",
    "content": "Copyright 2017 the Resvg Authors\n\nPermission is hereby granted, free of charge, to any\nperson obtaining a copy of this software and associated\ndocumentation files (the \"Software\"), to deal in the\nSoftware without restriction, including without\nlimitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software\nis furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice\nshall be included in all copies or substantial portions\nof the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF\nANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED\nTO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\nPARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT\nSHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR\nIN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "crates/usvg/README.md",
    "content": "# usvg\n[![Crates.io](https://img.shields.io/crates/v/usvg.svg)](https://crates.io/crates/usvg)\n[![Documentation](https://docs.rs/usvg/badge.svg)](https://docs.rs/usvg)\n[![Rust 1.65+](https://img.shields.io/badge/rust-1.65+-orange.svg)](https://www.rust-lang.org)\n\n`usvg` (micro SVG) is an [SVG] parser that tries to solve most of SVG complexity.\n\nSVG is notoriously hard to parse. `usvg` presents a layer between an XML library and\na potential SVG rendering library. It will parse an input SVG into a strongly-typed tree structure\nwere all the elements, attributes, references and other SVG features are already resolved\nand presented in the simplest way possible.\nSo a caller doesn't have to worry about most of the issues related to SVG parsing\nand can focus just on the rendering part.\n\n## Features\n\n- All supported attributes are resolved.\n  No need to worry about inheritable, implicit and default attributes\n- CSS will be applied\n- Only simple paths\n  - Basic shapes (like `rect` and `circle`) will be converted into paths\n  - Paths contain only absolute *MoveTo*, *LineTo*, *QuadTo*, *CurveTo* and *ClosePath* segments.\n    ArcTo, implicit and relative segments will be converted\n- `use` will be resolved and replaced with the reference content\n- Nested `svg` will be resolved\n- Invalid, malformed elements will be removed\n- Relative length units (mm, em, etc.) will be converted into pixels/points\n- External images will be loaded\n- Internal, base64 images will be decoded\n- All references (like `#elem` and `url(#elem)`) will be resolved\n- `switch` will be resolved\n- Text elements, which are probably the hardest part of SVG, will be completely resolved.\n  This includes all the attributes resolving, whitespaces preprocessing (`xml:space`),\n  text chunks and spans resolving\n- Markers will be converted into regular elements. No need to place them manually\n- All filters are supported. Including filter functions, like `filter=\"contrast(50%)\"`\n- Recursive elements will be detected and removed\n- `objectBoundingBox` will be replaced with `userSpaceOnUse`\n\n## Limitations\n\n- Unsupported SVG features will be ignored\n- CSS support is minimal\n- Only [static](http://www.w3.org/TR/SVG11/feature#SVG-static) SVG features,\n  e.g. no `a`, `view`, `cursor`, `script`, no events and no animations\n\n## License\n\nLicensed under either of\n\n- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or <http://www.apache.org/licenses/LICENSE-2.0>)\n- MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)\n\nat your option.\n\n## Contribution\n\nContributions are welcome by pull request.\nThe [Rust code of conduct] applies.\n\nUnless 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.\n\n[Rust Code of Conduct]: https://www.rust-lang.org/policies/code-of-conduct\n[SVG]: https://en.wikipedia.org/wiki/Scalable_Vector_Graphics\n"
  },
  {
    "path": "crates/usvg/codegen/Cargo.toml",
    "content": "[package]\nname = \"codegen\"\nversion = \"0.1.0\"\nlicense.workspace = true\nedition = \"2021\"\npublish = false\n\n[[bin]]\nname = \"codegen\"\npath = \"main.rs\"\n\n[dependencies]\nphf_codegen = \"0.7.24\"\nitertools = \"0.14\"\n"
  },
  {
    "path": "crates/usvg/codegen/README.md",
    "content": "We don't use cargo build script, since this data will rarely be changed and\nthere is no point in regenerating it each time.\n\nTo regenerate files run:\n\n```\ncargo run\n```\n"
  },
  {
    "path": "crates/usvg/codegen/attributes.txt",
    "content": "alignment-baseline\namplitude\nazimuth\nbackground-color\nbaseFrequency\nbaseline-shift\nbias\nclass\nclip\nclip-path\nclip-rule\nclipPathUnits\ncolor\ncolor-interpolation\ncolor-interpolation-filters\ncolor-profile\ncolor-rendering\ncx\ncy\nd\ndiffuseConstant\ndirection\ndisplay\ndivisor\ndominant-baseline\ndx\ndy\nedgeMode\nelevation\nenable-background\nexponent\nfill\nfill-opacity\nfill-rule\nfilter\nfilterUnits\nflood-color\nflood-opacity\nfont\nfont-family\nfont-feature-settings\nfont-kerning\nfont-optical-sizing\nfont-size\nfont-size-adjust\nfont-stretch\nfont-style\nfont-synthesis\nfont-variant\nfont-variant-caps\nfont-variant-east-asian\nfont-variant-ligatures\nfont-variant-numeric\nfont-variant-position\nfont-variation-settings\nfont-weight\nfr\nfx\nfy\nglyph-orientation-horizontal\nglyph-orientation-vertical\ngradientTransform\ngradientUnits\nheight\nhref\nid\nimage-rendering\nin\nin2\ninline-size\nintercept\nisolation\nk1\nk2\nk3\nk4\nkernelMatrix\nkernelUnitLength\nkerning\nlengthAdjust\nletter-spacing\nlighting-color\nlimitingConeAngle\nline-height\nmarker-end\nmarker-mid\nmarker-start\nmarkerHeight\nmarkerUnits\nmarkerWidth\nmask\nmask-border\nmask-border-mode\nmask-border-outset\nmask-border-repeat\nmask-border-slice\nmask-border-source\nmask-border-width\nmask-clip\nmask-composite\nmask-image\nmask-mode\nmask-origin\nmask-position\nmask-size\nmask-type\nmaskContentUnits\nmaskUnits\nmix-blend-mode\nmode\nnumOctaves\noffset\nopacity\noperator\norder\norient\noverflow\npaint-order\npath\npathLength\npatternContentUnits\npatternTransform\npatternUnits\npoints\npointsAtX\npointsAtY\npointsAtZ\npreserveAlpha\npreserveAspectRatio\nprimitiveUnits\nr\nradius\nrefX\nrefY\nrequiredExtensions\nrequiredFeatures\nresult\nrotate\nrx\nry\nscale\nseed\nshape-image-threshold\nshape-inside\nshape-margin\nshape-padding\nshape-rendering\nshape-subtract\nside\nslope\nspace\nspecularConstant\nspecularExponent\nspreadMethod\nstartOffset\nstdDeviation\nstitchTiles\nstop-color\nstop-opacity\nstroke\nstroke-dasharray\nstroke-dashoffset\nstroke-linecap\nstroke-linejoin\nstroke-miterlimit\nstroke-opacity\nstroke-width\nstyle\nsurfaceScale\nsystemLanguage\ntableValues\ntargetX\ntargetY\ntext-align\ntext-align-last\ntext-anchor\ntext-decoration\ntext-decoration-color\ntext-decoration-fill\ntext-decoration-line\ntext-decoration-stroke\ntext-decoration-style\ntext-indent\ntext-orientation\ntext-overflow\ntext-rendering\ntext-underline-position\ntextLength\ntransform\ntransform-box\ntransform-origin\ntype\nunicode-bidi\nunicode-range\nvalues\nvector-effect\nviewBox\nvisibility\nwhite-space\nwidth\nword-spacing\nwriting-mode\nx\nx1\nx2\nxChannelSelector\ny\ny1\ny2\nyChannelSelector\nz\n"
  },
  {
    "path": "crates/usvg/codegen/elements.txt",
    "content": "a\ncircle\nclipPath\ndefs\nellipse\nfeBlend\nfeColorMatrix\nfeComponentTransfer\nfeComposite\nfeConvolveMatrix\nfeDiffuseLighting\nfeDisplacementMap\nfeDistantLight\nfeDropShadow\nfeFlood\nfeFuncA\nfeFuncB\nfeFuncG\nfeFuncR\nfeGaussianBlur\nfeImage\nfeMerge\nfeMergeNode\nfeMorphology\nfeOffset\nfePointLight\nfeSpecularLighting\nfeSpotLight\nfeTile\nfeTurbulence\nfilter\ng\nimage\nline\nlinearGradient\nmarker\nmask\npath\npattern\npolygon\npolyline\nradialGradient\nrect\nstop\nstyle\nsvg\nswitch\nsymbol\ntext\ntextPath\ntref\ntspan\nuse\n"
  },
  {
    "path": "crates/usvg/codegen/main.rs",
    "content": "// Copyright 2019 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse itertools::Itertools;\n\nuse std::fs;\nuse std::io::{Read, Write};\nuse std::str;\n\nconst PHF_SRC: &str = \"\\\n// A stripped down `phf` crate fork.\n//\n// https://github.com/sfackler/rust-phf\n\nstruct Map<V: 'static> {\n    pub key: u64,\n    pub disps: &'static [(u32, u32)],\n    pub entries: &'static [(&'static str, V)],\n}\n\nimpl<V: PartialEq> Map<V> {\n    fn get(&self, key: &str) -> Option<&V> {\n        let hash = hash(key, self.key);\n        let index = get_index(hash, self.disps, self.entries.len());\n        let entry = &self.entries[index as usize];\n        let b = entry.0;\n        if b == key {\n            Some(&entry.1)\n        } else {\n            None\n        }\n    }\n\n    fn key(&self, value: &V) -> &'static str {\n        self.entries.iter().find(|kv| kv.1 == *value).unwrap().0\n    }\n}\n\n#[inline]\nfn hash(x: &str, key: u64) -> u64 {\n    use std::hash::Hasher;\n\n    let mut hasher = siphasher::sip::SipHasher13::new_with_keys(0, key);\n    hasher.write(x.as_bytes());\n    hasher.finish()\n}\n\n#[inline]\nfn get_index(hash: u64, disps: &[(u32, u32)], len: usize) -> u32 {\n    let (g, f1, f2) = split(hash);\n    let (d1, d2) = disps[(g % (disps.len() as u32)) as usize];\n    displace(f1, f2, d1, d2) % (len as u32)\n}\n\n#[inline]\nfn split(hash: u64) -> (u32, u32, u32) {\n    const BITS: u32 = 21;\n    const MASK: u64 = (1 << BITS) - 1;\n\n    ((hash & MASK) as u32,\n     ((hash >> BITS) & MASK) as u32,\n     ((hash >> (2 * BITS)) & MASK) as u32)\n}\n\n#[inline]\nfn displace(f1: u32, f2: u32, d1: u32, d2: u32) -> u32 {\n    d2 + f1 * d1 + f2\n}\";\n\nfn main() {\n    if let Err(e) = gen() {\n        println!(\"{:?}\", e);\n        std::process::exit(1);\n    }\n}\n\nfn gen() -> Result<(), Box<dyn std::error::Error>> {\n    let f = &mut fs::File::create(\"../src/parser/svgtree/names.rs\")?;\n\n    writeln!(f, \"// Copyright 2019 the Resvg Authors\")?;\n    writeln!(f, \"// SPDX-License-Identifier: Apache-2.0 OR MIT\")?;\n    writeln!(f, \"\")?;\n    writeln!(f, \"// This file is autogenerated. Do not edit it!\")?;\n    writeln!(f, \"// See ./codegen for details.\\n\")?;\n\n    gen_map(\"elements.txt\", \"An element ID.\", \"EId\", \"ELEMENTS\", f)?;\n\n    gen_map(\"attributes.txt\", \"An attribute ID.\", \"AId\", \"ATTRIBUTES\", f)?;\n\n    writeln!(f, \"{}\", PHF_SRC)?;\n\n    Ok(())\n}\n\nfn gen_map(\n    spec_path: &str,\n    enum_docs: &str,\n    enum_name: &str,\n    map_name: &str,\n    f: &mut fs::File,\n) -> Result<(), Box<dyn std::error::Error>> {\n    let mut spec = String::new();\n    fs::File::open(spec_path)?.read_to_string(&mut spec)?;\n\n    let names: Vec<&str> = spec.split('\\n').filter(|s| !s.is_empty()).collect();\n\n    let joined_names = names.iter().map(|n| to_enum_name(n)).join(\",\\n    \");\n\n    let mut map = phf_codegen::Map::new();\n    for name in &names {\n        map.entry(*name, &format!(\"{}::{}\", enum_name, to_enum_name(name)));\n    }\n\n    let mut map_data = Vec::new();\n    map.build(&mut map_data)?;\n    let map_data = String::from_utf8(map_data)?;\n    let map_data = map_data.replace(\"::phf::Map\", \"Map\");\n    let map_data = map_data.replace(\"::phf::Slice::Static(\", \"\");\n    let map_data = map_data.replace(\"]),\", \"],\");\n\n    writeln!(f, \"/// {}\", enum_docs)?;\n    writeln!(f, \"#[allow(missing_docs)]\")?;\n    writeln!(f, \"#[derive(Clone, Copy, PartialEq)]\")?;\n    writeln!(f, \"pub enum {} {{\", enum_name)?;\n    writeln!(f, \"    {}\", joined_names)?;\n    writeln!(f, \"}}\\n\")?;\n\n    writeln!(\n        f,\n        \"static {}: Map<{}> = {};\\n\",\n        map_name, enum_name, map_data\n    )?;\n\n    writeln!(f, \"impl {} {{\", enum_name)?;\n    writeln!(\n        f,\n        \"    pub(crate) fn from_str(text: &str) -> Option<{}> {{\",\n        enum_name\n    )?;\n    writeln!(f, \"        {}.get(text).cloned()\", map_name)?;\n    writeln!(f, \"    }}\")?;\n    writeln!(f, \"\")?;\n    writeln!(f, \"    /// Returns the original string.\")?;\n    writeln!(f, \"    #[inline(never)]\")?;\n    writeln!(f, \"    pub fn to_str(self) -> &'static str {{\")?;\n    writeln!(f, \"        {}.key(&self)\", map_name)?;\n    writeln!(f, \"    }}\")?;\n    writeln!(f, \"}}\\n\")?;\n\n    writeln!(f, \"impl std::fmt::Debug for {} {{\", enum_name)?;\n    writeln!(\n        f,\n        \"    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {{\"\n    )?;\n    writeln!(f, \"        write!(f, \\\"{{}}\\\", self.to_str())\")?;\n    writeln!(f, \"    }}\")?;\n    writeln!(f, \"}}\\n\")?;\n\n    writeln!(f, \"impl std::fmt::Display for {} {{\", enum_name)?;\n    writeln!(\n        f,\n        \"    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {{\"\n    )?;\n    writeln!(f, \"        write!(f, \\\"{{:?}}\\\", self)\")?;\n    writeln!(f, \"    }}\")?;\n    writeln!(f, \"}}\")?;\n    writeln!(f, \"\")?;\n\n    Ok(())\n}\n\n// some-string -> SomeString\n// some_string -> SomeString\n// some:string -> SomeString\n// 100 -> N100\nfn to_enum_name(name: &str) -> String {\n    let mut change_case = false;\n    let mut s = String::with_capacity(name.len());\n    for (idx, c) in name.chars().enumerate() {\n        if idx == 0 {\n            if c.is_digit(10) {\n                s.push('N');\n                s.push(c);\n            } else {\n                s.push(c.to_uppercase().next().unwrap());\n            }\n\n            continue;\n        }\n\n        if c == '-' || c == '_' || c == ':' {\n            change_case = true;\n            continue;\n        }\n\n        if change_case {\n            s.push(c.to_uppercase().next().unwrap());\n            change_case = false;\n        } else {\n            s.push(c);\n        }\n    }\n\n    s\n}\n"
  },
  {
    "path": "crates/usvg/docs/post-processing.md",
    "content": "# XML Post-processing Steps\n\n## No namespaces\n\nIn an SVG tree all elements and attributes belong to the SVG namespace.\n\n## No non-SVG elements and attributes\n\nOnly SVG elements and attributes are preserved.\n\nAnd their names are stored as `enum`s and not strings.\nThis increases performance and makes typos impossible.\n\n## Only elements and text nodes\n\nXML can contain elements, text nodes, comments and processing instructions.\nOur tree contains only elements and text nodes inside the `text` element.\n\n## Whitespaces trimming\n\nNot only text nodes can be present only inside the `text` element,\nbut they are also trimmed according to the SVG rules, including `xml:space`.\n\nFor example:\n\n```xml\n<text>\n    Text\n</text>\n```\n\nbecomes\n\n```xml\n<text>Text</text>\n```\n\nAnd\n\n```xml\n<text>\n    <tspan>\n        Text\n    </tspan>\n    <tspan>\n        Text\n    </tspan>\n</text>\n```\n\nbecomes\n\n```xml\n<text><tspan>Text</tspan> <tspan>Text</tspan></text>\n```\n\n## `style` attribute splitting\n\nThe `style` attribute content will be converted into normal attributes.\n\n```xml\n<rect style=\"fill:green\"/>\n```\n\nwill become\n\n```xml\n<rect fill=\"green\"/>\n```\n\nThe produced SVG tree never has `style` attributes.\n\n## CSS will be applied\n\nAll _supported_ CSS rules will be applied.\n\n```xml\n<style>rect { fill:green } </style>\n<rect/>\n```\n\nwill become\n\n```xml\n<rect fill=\"green\"/>\n```\n\nThe produced SVG tree never has `style` elements and `class` attributes.\n\n## `inherit` will be resolved\n\nSVG allows setting some attribute values to `inherit`,\nin which case the actual value should be taken from a parent element.\n\nNot only it applies only to some attributes.\nBut some attributes also allow `inherit` only from the direct parent.\n\n`rosvgtree` handles this for us.\n\n## Recursive links removal\n\nSVG supports referencing other elements via IRI and FuncIRI value types.\nIRI is `xlink:href=\"#id\"` and FuncIRI is `url(#id)`.\n\nAs in any link-based system this could lead to recursive references,\nwhich when handled incorrectly can crash your app.\n\nWe're trying to detect all common cases, but it's\nnot 100% guarantee that there will be no recursive links left, but we're pretty close.\n\nThis includes simple cases like\n\n```xml\n<use id=\"use1\" xlink:href=\"#use1\"/>\n```\n\nand more complex one like\n\n```xml\n<clipPath id=\"clip1\">\n    <rect clip-path=\"url(#clip2)\"/>\n</clipPath>\n<clipPath id=\"clip2\">\n    <rect clip-path=\"url(#clip1)\"/>\n</clipPath>\n```\n\n## Remember all elements with an ID\n\nAs mentioned above, SVG supports references. And it can reference any element in the document.<br>\nInstead of checking each element in the tree each time, which would be pretty slow,\nwe have an ID<->Node HashMap to quickly retrieve a requested element.\n\n## Links are groups\n\nThe `<a>` element in SVG is just a `<g>` with a URL.<br>\nSince we really support only the static SVG subset, we can replace `<a>` with `<g>`.\n\n## `tref` resolving\n\n[`tref`](https://www.w3.org/TR/SVG11/text.html#TRefElement) is a pretty weird SVG element.\nIt's basically a way to reference text nodes.\n\nWe resolve them automatically and replace them with `tspan`.\n\n```xml\n<defs>\n    <text id=\"text1\">Text</text>\n</defs>\n<text><tref xlink:href=\"#text1\"/></text>\n```\n\nwill become\n\n```xml\n<text><tspan>Text</tspan></text>\n```\n\n## `use` will be resolved\n\nThis is probably the only breaking change to the SVG structure.\n\nThe way the `use` works, is that it creates a shadow tree of nodes\nthat it's referencing. This is a great way to save space,\nbut it makes style properties resolving way harder.\n\nThis is because when you want to get a parent element from inside the `use`,\nthe tree should return `use`'s parent and not the referenced element parent.\n\nTo illustrate:\n\n```xml\n<g fill=\"red\">\n    <rect id=\"rect1\"/>\n</g>\n<g fill=\"green\">\n    <!-- rect's fill should be resolved to green -->\n    <use href=\"#rect\"/>\n</g>\n```\n\nIf you simply call `node.parent().attribute(\"fill\")` it will return `red`, not `green`.\nBecause the current node is `rect1`.\n\nAs you can imagine, this is pretty hard to handle using a typical DOM model.\nSo instead we're simply coping referenced elements inside\nthe `use` so it can be treated as a regular group.\n\n```xml\n<rect id=\"rect1\"/>\n<use href=\"#rect\"/>\n```\n\nwill become\n\n```xml\n<rect id=\"rect1\"/>\n<use href=\"#rect\">\n    <rect/>\n</use>\n```\n\n<br>\n\nThe main limitation of this approach, excluding the fact we're creating way more elements\nthat we had initially, is that copied elements must not have an `id` attribute,\notherwise we would end up with multiple duplicates.\n"
  },
  {
    "path": "crates/usvg/docs/spec.adoc",
    "content": "= Micro SVG Document Structure\n:toc:\n\n== Intro\n\nSVG Micro represents a strip down SVG Full 1.1 subset.\n\nHere is the main differences between SVG Full and SVG Micro.\n\n- No XML DTD.\n- No CSS.\n- `use`, `marker` and nested `svg` will be resolved.\n- Simplified path notation. Only absolute MoveTo, LineTo, CurveTo\n  and ClosePath segments are allowed.\n- No inheritable attributes.\n- No `xlink:href`, except the `image` element.\n- No recursive references.\n- Only valid elements and attributes.\n- No unused elements.\n- No redundant groups.\n- No units.\n- No `objectBoundingBox` units.\n- No `viewBox` and `preserveAspectRatio` attributes.\n- No `style` attribute, except for `mix-blend-mode` and `isolation`\n- Default attributes are implicit.\n\nYou can use\nhttps://github.com/linebender/resvg/tree/main/crates/usvg[usvg]\nto convert a random SVG into a SVG Micro almost losslessly.\n\n== Elements\n\n[[svg-element]]\n\n=== The `svg` element\n\nThe `svg` element is the root element of the document.\nIt's defined only once and can't be nested, unlike by the SVG spec.\n\n*Children:*\n\n* <<defs-element,defs>>\n* <<g-element,g>>\n* <<path-element,path>>\n* <<image-element,image>>\n\n*Attributes:*\n\n* `width` = <<positive-number-type,<positive-number> >> +\n  The width of the rectangular region into which the referenced document is placed.\n* `height` = <<positive-number-type,<positive-number> >> +\n  The height of the rectangular region into which the referenced document is placed.\n\n[[defs-element]]\n\n=== The `defs` element\n\nAlways present. Always the first `svg` child. Can be empty.\n\n*Children:*\n\n* <<linearGradient-element,linearGradient>>\n* <<radialGradient-element,radialGradient>>\n* <<clipPath-element,clipPath>>\n* <<mask-element,mask>>\n* <<pattern-element,pattern>>\n* <<filter-element,filter>>\n* <<g-element,g>>\n* <<path-element,path>>\n* <<image-element,image>>\n\n*Attributes:*\n\n* none\n\n[[linearGradient-element]]\n\n=== The `linearGradient` element\n\nDoesn't have a `xlink:href` attribute because all attributes and `stop`\nchildren will be resolved.\n\n*Children:*\n\n* At least two <<stop-element,stop>>\n\n*Attributes:*\n\n* `id` = <<string-type,<string> >> +\n  The element ID. Always set. Guarantee to be unique.\n* `x1` = <<number-type,<number> >>\n* `y1` = <<number-type,<number> >>\n* `x2` = <<number-type,<number> >>\n* `y2` = <<number-type,<number> >>\n* `gradientUnits` = `userSpaceOnUse`?\n* `spreadMethod` = `reflect | repeat`?\n* `gradientTransform` = <<transform-type,<transform> >>?\n\n[[radialGradient-element]]\n\n=== The `radialGradient` element\n\nDoesn't have a `xlink:href` attribute because all attributes and `stop`\nchildren will be resolved.\n\n*Children:*\n\n* At least two <<stop-element,stop>>\n\n*Attributes:*\n\n* `id` = <<string-type,<string> >> +\n  The element ID. Always set. Guarantee to be unique.\n* `cx` = <<number-type,<number> >>\n* `cy` = <<number-type,<number> >>\n* `fx` = <<number-type,<number> >> +\n  Guarantee to be the circle defined by `cx`, `cy` and `r`.\n* `fy` = <<number-type,<number> >> +\n  Guarantee to be inside the circle defined by `cx`, `cy` and `r`.\n* `r` = <<positive-number-type,<positive-number> >>\n* `gradientUnits` = `userSpaceOnUse`\n* `spreadMethod` = `reflect | repeat`?\n* `gradientTransform` = <<transform-type,<transform> >>?\n\n[[stop-element]]\n\n=== The `stop` element\n\nGradient's `stop` children will always have unique, ordered `offset` values\nin the 0..1 range.\n\n*Children:*\n\n* none\n\n*Attributes:*\n\n* `offset` = <<offset-type,<offset> >>\n* `stop-color` = <<color-type,<color> >>\n* `stop-opacity` = <<opacity-type,<opacity> >>? +\n  Default: 1\n\n[[pattern-element]]\n\n=== The `pattern` element\n\nDoesn't have a `xlink:href` attribute because all attributes and children will be resolved.\n\n*Children:*\n\n* `g`\n* `path`\n* `image`\n\n*Attributes:*\n\n* `id` = <<string-type,<string> >> +\n  The element ID. Always set. Guarantee to be unique.\n* `x` = <<number-type,<number> >>\n* `y` = <<number-type,<number> >>\n* `width` = <<positive-number-type,<positive-number> >>\n* `height` = <<positive-number-type,<positive-number> >>\n* `patternUnits` = `userSpaceOnUse`\n* `patternTransform` = <<transform-type,<transform> >>?\n\n[[clipPath-element]]\n\n=== The `clipPath` element\n\n*Children:*\n\n* `path`\n\n*Attributes:*\n\n* `id` = <<string-type,<string> >> +\n  The element ID. Always set. Guarantee to be unique.\n* `clip-path` = <<func-iri-type,<FuncIRI> >>? +\n  An optional reference to a supplemental `clipPath`. +\n  Default: none\n* `transform` = <<transform-type,<transform> >>?\n\n[[mask-element]]\n\n=== The `mask` element\n\n*Children:*\n\n* `g`\n* `path`\n* `image`\n\n*Attributes:*\n\n* `id` = <<string-type,<string> >> +\n  The element ID. Always set. Guarantee to be unique.\n* `mask` = <<func-iri-type,<FuncIRI> >>? +\n  An optional reference to a supplemental `mask`. +\n  Default: none\n* `x` = <<number-type,<number> >>\n* `y` = <<number-type,<number> >>\n* `width` = <<positive-number-type,<positive-number> >>\n* `height` = <<positive-number-type,<positive-number> >>\n* `mask-type` = `alpha`? +\n  Default: luminance\n* `maskUnits` = `userSpaceOnUse`\n\n[[filter-element]]\n\n=== The `filter` element\n\nDoesn't have a `xlink:href` attribute because all attributes and children will be resolved.\n\n*Children:*\n\n* <<Filter primitives>>\n\n*Attributes:*\n\n* `id` = <<string-type,<string> >> +\n  The element ID. Always set. Guarantee to be unique.\n* `x` = <<number-type,<number> >>\n* `y` = <<number-type,<number> >>\n* `width` = <<positive-number-type,<positive-number> >>\n* `height` = <<positive-number-type,<positive-number> >>\n* `filterUnits` = `userSpaceOnUse`\n\n[[g-element]]\n\n=== The `g` element\n\nThe group element indicates that a new canvas should be created.\nAll group's children elements will be rendered on it and then merged into\nthe parent canvas.\n\nSince it's pretty expensive, especially memory wise, _usvg_\nwill remove as many groups as possible.\nAnd all the remaining one will indicate that a new canvas must be created.\n\nA group can have no children when it has a `filter` attribute.\n\nA group will have at least one of the attributes present.\n\n*Children:*\n\n* <<g-element,g>>\n* <<path-element,path>>\n* <<image-element,image>>\n\n*Attributes:*\n\n* `id` = <<string-type,<string> >>? +\n  An optional, but never empty, element ID.\n* `opacity` = <<opacity-type,<opacity> >>?\n* `clip-path` = <<func-iri-type,<FuncIRI> >>? +\n  Cannot be set to `none`.\n* `mask` = <<func-iri-type,<FuncIRI> >>? +\n  Cannot be set to `none`.\n* `filter` = <<func-iri-type,<FuncIRI> >>+ +\n  Cannot be set to `none`.\n* `transform` = <<transform-type,<transform> >>?\n* `style` = <<string-type,<string> >>? +\n  This is the only place where the `style` attribute is used.\n  For reasons unknown, `mix-blend-mode` and `isolation` properties must not be set as attributes,\n  only as part of the `style` attribute. +\n  The set attribute will look like `mix-blend-mode:screen;isolation:isolate`.\n  Both properties are always set. +\n  The attribute is not present only in case of `mix-blend-mode:norma;isolation:auto`\n\n[[path-element]]\n\n=== The `path` element\n\n*Children:*\n\n* none\n\n*Attributes:*\n\n* `id` = <<string-type,<string> >>? +\n  An optional, but never empty, element ID.\n* `d` = <<path-data-type,<path-data> >> +\n* `fill` = `none` | <<color-type,<color> >> | <<func-iri-type,<FuncIRI> >> +\n  If set to `none` than all fill-* attributes will not be set too. +\n  Default: black\n* `fill-opacity` = <<opacity-type,<opacity> >>? +\n  Default: 1\n* `fill-rule` = `evenodd`? +\n  Default: nonzero\n* `stroke` = `none` | <<color-type,<color> >> | <<func-iri-type,<FuncIRI> >> +\n  If set to `none` than all stroke-* attributes will not be set too. +\n  Default: none\n* `stroke-width` = <<positive-number-type,<positive-number> >>? +\n  Default: 1\n* `stroke-linecap` = `round | square`? +\n  Default: butt\n* `stroke-linejoin` = `round | bevel`? +\n  Default: miter\n* `stroke-miterlimit` = <<positive-number-type,<positive-number> >>? +\n  Guarantee to be > 1. +\n  Default: 4\n* `stroke-dasharray` = `<list-of-numbers>`? +\n  Guarantee to have even amount of numbers. +\n  Default: none\n* `stroke-dashoffset` = <<number-type,<number> >>?\n* `stroke-opacity` = <<opacity-type,<opacity> >>? +\n  Default: 1\n* `paint-order` = `normal | stroke`? +\n  Default: `normal` +\n  Only `stroke` will be written.\n* `clip-rule` = `evenodd`? +\n  Will be set only inside the <<clipPath-element,clipPath>>, instead of `fill-rule`.\n* `clip-path` = <<func-iri-type,<FuncIRI> >>? +\n  Available only inside the <<clipPath-element,clipPath>>.\n* `shape-rendering` = `optimizeSpeed | crispEdges`? +\n  Default: geometricPrecision\n* `visibility` = `hidden`? +\n  Default: visible\n* `transform` = <<transform-type,<transform> >>? +\n  Can only be set on paths inside of `clipPath`.\n\n[[image-element]]\n\n=== The `image` element\n\n*Children:*\n\n* none\n\n*Attributes:*\n\n* `id` = <<string-type,<string> >>? +\n  An optional, but never empty, element ID.\n* `xlink:href` = <<iri-type,<IRI> >> +\n  The IRI contains a base64 encoded image.\n* `width` = <<positive-number-type,<positive-number> >>\n* `height` = <<positive-number-type,<positive-number> >>\n* `image-rendering` = `optimizeSpeed`? +\n  Default: optimizeQuality\n* `visibility` = `hidden`? +\n  Default: visible\n\n== Filter primitives\n\n=== Filter primitive attributes\n\nThe attributes below are the same for all filter primitives.\n\n* `color-interpolation-filters` = `sRGB`? +\n  Default: linearRGB\n* `x` = <<number-type,<number> >>?\n* `y` = <<number-type,<number> >>?\n* `width` = <<number-type,<number> >>?\n* `height` = <<number-type,<number> >>?\n* `result` = <<string-type,<string> >>\n\nThe `x`, `y`, `width` and `height` attributes can be omitted.\nSVG has a pretty complex\nhttps://www.w3.org/TR/SVG11/filters.html#FilterPrimitiveSubRegion[rules of resolving them]\nand I don't fully understand them yet.\nNeither do others, because they are pretty poorly implemented.\n\n=== Filter primitive `feBlend`\n\n*Attributes:*\n\n* `in` = <<filter-input-type,<filter-input> >>\n* `in2` = <<filter-input-type,<filter-input> >>\n* `mode` = `normal | multiply | screen | overlay | darken | lighten | color-dodge |color-burn |\nhard-light | soft-light | difference | exclusion | hue | saturation | color | luminosity`\n* <<Filter primitive attributes>>\n\n=== Filter primitive `feColorMatrix`\n\n*Attributes:*\n\n* `in` = <<filter-input-type,<filter-input> >>\n* `type` = `matrix | saturate | hueRotate | luminanceToAlpha`\n* `values` = `<list-of-numbers>`? +\n** For `type=matrix`, contains 20 numbers.\n** For `type=saturate`, contains a single number in a 0..1 range.\n** For `type=hueRotate`, contains a single number.\n** Not present for `type=luminanceToAlpha`.\n* <<Filter primitive attributes>>\n\n=== Filter primitive `feComponentTransfer`\n\n*Children:*\n\n* `feFuncR`\n* `feFuncG`\n* `feFuncB`\n* `feFuncA`\n\nThe all four will always be present.\n\n*Attributes:*\n\n* `in` = <<filter-input-type,<filter-input> >>\n* <<Filter primitive attributes>>\n\n*`feFunc(R|G|B|A)` attributes:*\n\n* `type` = `identity | table | discrete | linear | gamma`\n* `tableValues` = `<list-of-numbers>`? +\n  Present only when `type=table | discrete`. Can be empty.\n* `slope` = <<number-type,<number> >>? +\n  Present only when `type=linear`.\n* `intercept` = <<number-type,<number> >>? +\n  Present only when `type=linear`.\n* `amplitude` = <<number-type,<number> >>? +\n  Present only when `type=gamma`.\n* `exponent` = <<number-type,<number> >>? +\n  Present only when `type=gamma`.\n* `offset` = <<number-type,<number> >>? +\n  Present only when `type=gamma`.\n\n=== Filter primitive `feComposite`\n\n*Attributes:*\n\n* `in` = <<filter-input-type,<filter-input> >>\n* `in2` = <<filter-input-type,<filter-input> >>\n* `operator` = `over | in | out | atop | xor | arithmetic`\n* `k1` = <<number-type,<number> >>? +\n  Present only when `operator=arithmetic`.\n* `k2` = <<number-type,<number> >>? +\n  Present only when `operator=arithmetic`.\n* `k3` = <<number-type,<number> >>? +\n  Present only when `operator=arithmetic`.\n* `k4` = <<number-type,<number> >>? +\n  Present only when `operator=arithmetic`.\n* <<Filter primitive attributes>>\n\n=== Filter primitive `feConvolveMatrix`\n\n*Attributes:*\n\n* `in` = <<filter-input-type,<filter-input> >>\n* `order` = <<positive-integer-type,<positive-integer> >> \" \" <<positive-integer-type,<positive-integer> >> +\n  Both numbers are never 0.\n* `kernelMatrix` = `<list-of-numbers>`\n* `divisor` = <<number-type,<number> >> +\n  Never 0.\n* `bias` = <<number-type,<number> >>\n* `targetX` = <<positive-integer-type,<positive-integer> >> +\n  Always smaller than the number of columns in the matrix.\n* `targetY` = <<positive-integer-type,<positive-integer> >> +\n  Always smaller than the number of rows in the matrix.\n* `edgeMode` = `none | duplicate | wrap`\n* `preserveAlpha` = `true | false`\n* <<Filter primitive attributes>>\n\n=== Filter primitive `feDiffuseLighting`\n\n*Children:*\n\nOnly one of:\n\n* `feDistantLight`\n* `fePointLight`\n* `feSpotLight`\n\n*Attributes:*\n\n* `in` = <<filter-input-type,<filter-input> >>\n* `surfaceScale` = <<number-type,<number> >>\n* `diffuseConstant` = <<number-type,<number> >>\n* `lighting-color` = <<color-type,<color> >>\n* <<Filter primitive attributes>>\n\n`feDistantLight` *attributes:*\n\n* `azimuth` = <<number-type,<number> >>\n* `elevation` = <<number-type,<number> >>\n\n`fePointLight` *attributes:*\n\n* `x` = <<number-type,<number> >>\n* `y` = <<number-type,<number> >>\n* `z` = <<number-type,<number> >>\n\n`feSpotLight` *attributes:*\n\n* `x` = <<number-type,<number> >>\n* `y` = <<number-type,<number> >>\n* `z` = <<number-type,<number> >>\n* `pointsAtX` = <<number-type,<number> >>\n* `pointsAtY` = <<number-type,<number> >>\n* `pointsAtZ` = <<number-type,<number> >>\n* `specularExponent` = <<positive-number-type,<positive-number> >>\n* `limitingConeAngle` = <<number-type,<number> >>?\n\n=== Filter primitive `feDisplacementMap`\n\n*Attributes:*\n\n* `in` = <<filter-input-type,<filter-input> >>\n* `in2` = <<filter-input-type,<filter-input> >>\n* `scale` = <<number-type,<number> >>\n* `xChannelSelector` = `R | G | B | A`\n* `yChannelSelector` = `R | G | B | A`\n* <<Filter primitive attributes>>\n\n=== Filter primitive `feDropShadow`\n\n*Attributes:*\n\n* `in` = <<filter-input-type,<filter-input> >>\n* `stdDeviation` = <<positive-number-type,<positive-number> >> \" \" <<positive-number-type,<positive-number> >>\n* `dx` = <<number-type,<number> >>\n* `dy` = <<number-type,<number> >>\n* `flood-color` = <<color-type,<color> >>\n* `flood-opacity` = <<opacity-type,<opacity> >>\n* <<Filter primitive attributes>>\n\n=== Filter primitive `feFlood`\n\n*Attributes:*\n\n* `flood-color` = <<color-type,<color> >>\n* `flood-opacity` = <<opacity-type,<opacity> >>\n* <<Filter primitive attributes>>\n\n=== Filter primitive `feGaussianBlur`\n\n*Attributes:*\n\n* `in` = <<filter-input-type,<filter-input> >>\n* `stdDeviation` = <<positive-number-type,<positive-number> >> \" \" <<positive-number-type,<positive-number> >>\n* <<Filter primitive attributes>>\n\n=== Filter primitive `feImage`\n\n*Attributes:*\n\n* `xlink:href` = <<iri-type,<IRI> >> +\n  The IRI contains a link to an element (like `use`).\n  base64 encoded is not allowed and will be represented as a link to an `image`.\n* <<Filter primitive attributes>>\n\n=== Filter primitive `feMerge`\n\n*Children:*\n\n* `feMergeNode`\n\n*Attributes:*\n\n* <<Filter primitive attributes>>\n\n*`feMergeNode` attributes:*\n\n* `in` = <<filter-input-type,<filter-input> >>\n\n=== Filter primitive `feMorphology`\n\n*Attributes:*\n\n* `in` = <<filter-input-type,<filter-input> >>\n* `operator` = `erode | dilate`\n* `radius` = <<positive-number-type,<positive-number> >> \" \" <<positive-number-type,<positive-number> >>\n* <<Filter primitive attributes>>\n\n=== Filter primitive `feOffset`\n\n*Attributes:*\n\n* `in` = <<filter-input-type,<filter-input> >>\n* `dx` = <<number-type,<number> >>\n* `dy` = <<number-type,<number> >>\n* <<Filter primitive attributes>>\n\n=== Filter primitive `feSpecularLighting`\n\n*Children:*\n\nOnly one of:\n\n* `feDistantLight`\n* `fePointLight`\n* `feSpotLight`\n\n*Attributes:*\n\n* `in` = <<filter-input-type,<filter-input> >>\n* `surfaceScale` = <<number-type,<number> >>\n* `specularConstant` = <<number-type,<number> >>\n* `specularExponent` = <<number-type,<number> >> +\n  Number in a 1..128 range.\n* `lighting-color` = <<color-type,<color> >>\n* <<Filter primitive attributes>>\n\n`feDistantLight` *attributes:*\n\n* `azimuth` = <<number-type,<number> >>\n* `elevation` = <<number-type,<number> >>\n\n`fePointLight` *attributes:*\n\n* `x` = <<number-type,<number> >>\n* `y` = <<number-type,<number> >>\n* `z` = <<number-type,<number> >>\n\n`feSpotLight` *attributes:*\n\n* `x` = <<number-type,<number> >>\n* `y` = <<number-type,<number> >>\n* `z` = <<number-type,<number> >>\n* `pointsAtX` = <<number-type,<number> >>\n* `pointsAtY` = <<number-type,<number> >>\n* `pointsAtZ` = <<number-type,<number> >>\n* `specularExponent` = <<positive-number-type,<positive-number> >>\n* `limitingConeAngle` = <<number-type,<number> >>?\n\n=== Filter primitive `feTile`\n\n*Attributes:*\n\n* `in` = <<filter-input-type,<filter-input> >>\n* <<Filter primitive attributes>>\n\n=== Filter primitive `feTurbulence`\n\n*Attributes:*\n\n* `baseFrequency` = <<positive-number-type,<positive-number> >> \" \" <<positive-number-type,<positive-number> >>\n* `numOctaves` = <<positive-integer-type,<positive-integer> >>\n* `seed` = <<integer-type,<integer> >>\n* `stitchTiles` = `stitch | noStitch`\n* `type` = `fractalNoise | turbulence`\n* <<Filter primitive attributes>>\n\n== Data types\n\nIf an attribute has the `?` symbol after the type that's mean that\nthat this attribute is optional.\n\n[[string-type]]\n\n*<string>* - A Unicode (UTF-8) string.\n\n\n[[number-type]]\n\n*<number>* - A real number. +\n`number ::= [-]? [0-9]+ \".\" [0-9]+`\n\n\n[[positive-number-type]]\n\n*<positive-number>* - A positive real <<number-type,number>>. +\n`positive-number ::= [0-9]+ \".\" [0-9]+`\n\n\n[[integer-type]]\n\n*<integer>* - An integer. +\n`integer ::= [-]? [0-9]+`\n\n\n[[positive-integer-type]]\n\n*<positive-integer>* - A positive integer. +\n`positive-integer ::= [0-9]+`\n\n\n[[opacity-type]]\n\n*<opacity>* - A real <<number-type,number>> in a 0..1 range. +\n`opacity ::= positive-number`\n\n\n[[offset-type]]\n\n*<offset>* - A real <<number-type,number>> in a 0..1 range. +\n`offset ::= positive-number`\n\n\n[[color-type]]\n\n*<color>* - A hex-encoded RGB color.\n```\ncolor    ::= \"#\" hexdigit hexdigit hexdigit hexdigit hexdigit hexdigit\nhexdigit ::= [0-9a-f]\n```\n\n\n[[iri-type]]\n\n*<IRI>* - An Internationalized Resource Identifier.\nAlways a valid, local reference. +\n`IRI ::= string`\n\n\n[[func-iri-type]]\n\n*<FuncIRI>* - Functional notation for an <<iri-type,IRI>>.\nAlways a valid, local reference. +\n`FuncIRI ::= url( <IRI> )`\n\n\n[[filter-input-type]]\n\n*<filter-input>* - A filter source. A reference to a _result_ guarantee to be valid.\n\n```\nfilter-input ::= SourceGraphic | SourceAlpha | <string>\n```\n\nWe do not support `FillPaint`, `StrokePaint`, `BackgroundImage` and `BackgroundAlpha`.\n\n\n[[transform-type]]\n\n*<transform>* - A transformation matrix.\nAlways a `matrix` and not `translate`, `scale`, etc.\nNumbers are space-separated. +\n`transform ::= matrix( <number> \" \" <number> \" \" <number> \" \" <number> \" \" <number> \" \" <number> )`\n\n\n[[path-data-type]]\n\n*<path-data>* - A path data.\n\n* Contains only absolute MoveTo, LineTo, CurveTo and ClosePath segments.\n* All segments are explicit.\n* The first segment is guarantee to be MoveTo.\n* Segments, commands and coordinates are separated only by space.\n* Path and all subpaths are guarantee to have at least two segments.\n\nGrammar:\n\n```\nsvg-path:\n    moveto-drawto-command-groups\nmoveto-drawto-command-groups:\n    moveto-drawto-command-group\n    | moveto-drawto-command-group \" \" moveto-drawto-command-groups\nmoveto-drawto-command-group:\n    moveto \" \" drawto-commands\ndrawto-commands:\n    drawto-command\n    | drawto-command \" \" drawto-commands\ndrawto-command:\n    closepath\n    | lineto\n    | curveto\nmoveto:\n    \"M \" coordinate-pair\nlineto:\n    \"L \" coordinate-pair\ncurveto:\n    \"C \" coordinate-pair \" \" coordinate-pair \" \" coordinate-pair\nclosepath:\n    \"Z\"\ncoordinate-pair:\n    coordinate \" \" coordinate\ncoordinate:\n    sign? digit-sequence \".\" digit-sequence\nsign:\n    \"-\"\ndigit-sequence:\n    digit\n    | digit digit-sequence\ndigit:\n    \"0\" | \"1\" | \"2\" | \"3\" | \"4\" | \"5\" | \"6\" | \"7\" | \"8\" | \"9\"\n```\n\nBasically, a path looks like this: `M 10.5 20 L 30 40`.\nCommands and numbers are separated by a space.\nNumbers with an exponent are not allowed.\nTrimmed numbers like `-.5` are not allowed.\n"
  },
  {
    "path": "crates/usvg/src/lib.rs",
    "content": "// Copyright 2018 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\n/*!\n`usvg` (micro SVG) is an [SVG] parser that tries to solve most of SVG complexity.\n\nSVG is notoriously hard to parse. `usvg` presents a layer between an XML library and\na potential SVG rendering library. It will parse an input SVG into a strongly-typed tree structure\nwere all the elements, attributes, references and other SVG features are already resolved\nand presented in the simplest way possible.\nSo a caller doesn't have to worry about most of the issues related to SVG parsing\nand can focus just on the rendering part.\n\n## Features\n\n- All supported attributes are resolved.\n  No need to worry about inheritable, implicit and default attributes\n- CSS will be applied\n- Only simple paths\n  - Basic shapes (like `rect` and `circle`) will be converted into paths\n  - Paths contain only absolute *MoveTo*, *LineTo*, *QuadTo*, *CurveTo* and *ClosePath* segments.\n    ArcTo, implicit and relative segments will be converted\n- `use` will be resolved and replaced with the reference content\n- Nested `svg` will be resolved\n- Invalid, malformed elements will be removed\n- Relative length units (mm, em, etc.) will be converted into pixels/points\n- External images will be loaded\n- Internal, base64 images will be decoded\n- All references (like `#elem` and `url(#elem)`) will be resolved\n- `switch` will be resolved\n- Text elements, which are probably the hardest part of SVG, will be completely resolved.\n  This includes all the attributes resolving, whitespaces preprocessing (`xml:space`),\n  text chunks and spans resolving\n- Markers will be converted into regular elements. No need to place them manually\n- All filters are supported. Including filter functions, like `filter=\"contrast(50%)\"`\n- Recursive elements will be detected and removed\n- `objectBoundingBox` will be replaced with `userSpaceOnUse`\n\n## Limitations\n\n- Unsupported SVG features will be ignored\n- CSS support is minimal\n- Only [static](http://www.w3.org/TR/SVG11/feature#SVG-static) SVG features,\n  e.g. no `a`, `view`, `cursor`, `script`, no events and no animations\n\n[SVG]: https://en.wikipedia.org/wiki/Scalable_Vector_Graphics\n*/\n\n#![forbid(unsafe_code)]\n#![warn(missing_docs)]\n#![warn(missing_debug_implementations)]\n#![warn(missing_copy_implementations)]\n\nmod parser;\n#[cfg(feature = \"text\")]\nmod text;\nmod tree;\nmod writer;\n\npub use parser::*;\n#[cfg(feature = \"text\")]\npub use text::*;\npub use tree::*;\n\npub use roxmltree;\n\n#[cfg(feature = \"text\")]\npub use fontdb;\n\npub use writer::WriteOptions;\npub use xmlwriter::Indent;\n"
  },
  {
    "path": "crates/usvg/src/main.rs",
    "content": "// Copyright 2018 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse std::fs::File;\nuse std::io::{self, Read, Write};\nuse std::path::PathBuf;\nuse std::process;\nuse std::sync::Arc;\n\nuse pico_args::Arguments;\n\nconst HELP: &str = \"\\\nusvg (micro SVG) is an SVG simplification tool.\n\nUSAGE:\n  usvg [OPTIONS] <in-svg> <out-svg>  # from file to file\n  usvg [OPTIONS] <in-svg> -c         # from file to stdout\n  usvg [OPTIONS] - <out-svg>         # from stdin to file\n  usvg [OPTIONS] - -c                # from stdin to stdout\n\nOPTIONS:\n  -h, --help                        Prints help information\n  -V, --version                     Prints version information\n  -c                                Prints the output SVG to the stdout\n\n  --dpi DPI                         Sets the resolution\n                                    [default: 96] [possible values: 10..4000 (inclusive)]\n  --stylesheet PATH                 Inject a stylesheet that should be used when resolving\n                                    CSS attributes.\n  --languages LANG                  Sets a comma-separated list of languages that\n                                    will be used during the 'systemLanguage'\n                                    attribute resolving\n                                    Examples: 'en-US', 'en-US, ru-RU', 'en, ru'\n                                    [default: en]\n  --shape-rendering HINT            Selects the default shape rendering method\n                                    [default: geometricPrecision]\n                                    [possible values: optimizeSpeed, crispEdges,\n                                    geometricPrecision]\n  --text-rendering HINT             Selects the default text rendering method\n                                    [default: optimizeLegibility]\n                                    [possible values: optimizeSpeed, optimizeLegibility,\n                                    geometricPrecision]\n  --image-rendering HINT            Selects the default image rendering method\n                                    [default: optimizeQuality]\n                                    [possible values: optimizeQuality, optimizeSpeed, smooth, high-quality, crisp-edges, pixelated]\n  --resources-dir DIR               Sets a directory that will be used during\n                                    relative paths resolving.\n                                    Expected to be the same as the directory that\n                                    contains the SVG file, but can be set to any.\n                                    [default: input file directory\n                                    or none when reading from stdin]\n\n  --font-family FAMILY              Sets the default font family that will be\n                                    used when no 'font-family' is present\n                                    [default: Times New Roman]\n  --font-size SIZE                  Sets the default font size that will be\n                                    used when no 'font-size' is present\n                                    [default: 12] [possible values: 1..192 (inclusive)]\n  --serif-family FAMILY             Sets the 'serif' font family.\n                                    Will be used when no 'font-family' is present\n                                    [default: Times New Roman]\n  --sans-serif-family FAMILY        Sets the 'sans-serif' font family\n                                    [default: Arial]\n  --cursive-family FAMILY           Sets the 'cursive' font family\n                                    [default: Comic Sans MS]\n  --fantasy-family FAMILY           Sets the 'fantasy' font family\n                                    [default: Impact]\n  --monospace-family FAMILY         Sets the 'monospace' font family\n                                    [default: Courier New]\n  --use-font-file PATH              Load a specified font file into the fonts database.\n                                    Will be used during text to path conversion.\n                                    This option can be set multiple times\n  --use-fonts-dir PATH              Loads all fonts from the specified directory\n                                    into the fonts database.\n                                    Will be used during text to path conversion.\n                                    This option can be set multiple times\n  --skip-system-fonts               Disables system fonts loading.\n                                    You should add some fonts manually using\n                                    --use-font-file and/or --use-fonts-dir\n                                    Otherwise, text elements will not be processes\n  --list-fonts                      Lists successfully loaded font faces.\n                                    Useful for debugging\n  --default-width LENGTH            Sets the default width of the SVG viewport. Like\n                                    the '--default-height' option, this option\n                                    controls what size relative units in the document\n                                    will use as a base if there is no viewBox and\n                                    document width or height are relative.\n                                    [values: 1..4294967295 (inclusive)] [default: 100]\n  --default-height LENGTH           Sets the default height of the SVG viewport.\n                                    Refer to the explanation of the '--default-width'\n                                    option. [values: 1..4294967295 (inclusive)] [default: 100]\n\n  --preserve-text                   Do not convert text into paths.\n  --id-prefix                       Adds a prefix to each ID attribute\n  --indent INDENT                   Sets the XML nodes indent\n                                    [values: none, 0, 1, 2, 3, 4, tabs] [default: 4]\n  --attrs-indent INDENT             Sets the XML attributes indent\n                                    [values: none, 0, 1, 2, 3, 4, tabs] [default: none]\n  --coordinates-precision NUM       Set the coordinates numeric precision.\n                                    Smaller precision can lead to a malformed output in some cases\n                                    [values: 2..8 (inclusive)] [default: 8]\n  --transforms-precision NUM        Set the transform values numeric precision.\n                                    Smaller precision can lead to a malformed output in some cases\n                                    [values: 2..8 (inclusive)] [default: 8]\n  --quiet                           Disables warnings\n\nARGS:\n  <in-svg>                          Input file\n  <out-svg>                         Output file\n\";\n\n#[derive(Debug)]\nstruct Args {\n    dpi: u32,\n    languages: Vec<String>,\n    shape_rendering: usvg::ShapeRendering,\n    text_rendering: usvg::TextRendering,\n    image_rendering: usvg::ImageRendering,\n    resources_dir: Option<PathBuf>,\n\n    font_family: Option<String>,\n    font_size: u32,\n    serif_family: Option<String>,\n    sans_serif_family: Option<String>,\n    cursive_family: Option<String>,\n    fantasy_family: Option<String>,\n    monospace_family: Option<String>,\n    font_files: Vec<PathBuf>,\n    font_dirs: Vec<PathBuf>,\n    skip_system_fonts: bool,\n    preserve_text: bool,\n    list_fonts: bool,\n    default_width: u32,\n    default_height: u32,\n\n    id_prefix: Option<String>,\n    indent: xmlwriter::Indent,\n    attrs_indent: xmlwriter::Indent,\n    coordinates_precision: Option<u8>,\n    transforms_precision: Option<u8>,\n    style_sheet: Option<PathBuf>,\n\n    quiet: bool,\n\n    input: String,\n    output: String,\n}\n\nfn collect_args() -> Result<Args, pico_args::Error> {\n    let mut input = Arguments::from_env();\n\n    if input.contains([\"-h\", \"--help\"]) {\n        print!(\"{}\", HELP);\n        process::exit(0);\n    }\n\n    if input.contains([\"-V\", \"--version\"]) {\n        println!(\"{}\", env!(\"CARGO_PKG_VERSION\"));\n        process::exit(0);\n    }\n\n    Ok(Args {\n        dpi: input.opt_value_from_fn(\"--dpi\", parse_dpi)?.unwrap_or(96),\n        languages: input\n            .opt_value_from_fn(\"--languages\", parse_languages)?\n            .unwrap_or(vec![\"en\".to_string()]), // TODO: use system language\n        shape_rendering: input\n            .opt_value_from_str(\"--shape-rendering\")?\n            .unwrap_or_default(),\n        text_rendering: input\n            .opt_value_from_str(\"--text-rendering\")?\n            .unwrap_or_default(),\n        image_rendering: input\n            .opt_value_from_str(\"--image-rendering\")?\n            .unwrap_or_default(),\n        resources_dir: input\n            .opt_value_from_str(\"--resources-dir\")\n            .unwrap_or_default(),\n\n        font_family: input.opt_value_from_str(\"--font-family\")?,\n        font_size: input\n            .opt_value_from_fn(\"--font-size\", parse_font_size)?\n            .unwrap_or(12),\n        serif_family: input.opt_value_from_str(\"--serif-family\")?,\n        sans_serif_family: input.opt_value_from_str(\"--sans-serif-family\")?,\n        cursive_family: input.opt_value_from_str(\"--cursive-family\")?,\n        fantasy_family: input.opt_value_from_str(\"--fantasy-family\")?,\n        monospace_family: input.opt_value_from_str(\"--monospace-family\")?,\n        font_files: input.values_from_str(\"--use-font-file\")?,\n        font_dirs: input.values_from_str(\"--use-fonts-dir\")?,\n        skip_system_fonts: input.contains(\"--skip-system-fonts\"),\n        preserve_text: input.contains(\"--preserve-text\"),\n        list_fonts: input.contains(\"--list-fonts\"),\n        default_width: input\n            .opt_value_from_fn(\"--default-width\", parse_length)?\n            .unwrap_or(100),\n        default_height: input\n            .opt_value_from_fn(\"--default-height\", parse_length)?\n            .unwrap_or(100),\n\n        id_prefix: input.opt_value_from_str(\"--id-prefix\")?,\n        indent: input\n            .opt_value_from_fn(\"--indent\", parse_indent)?\n            .unwrap_or(xmlwriter::Indent::Spaces(4)),\n        attrs_indent: input\n            .opt_value_from_fn(\"--attrs-indent\", parse_indent)?\n            .unwrap_or(xmlwriter::Indent::None),\n        coordinates_precision: input\n            .opt_value_from_fn(\"--coordinates-precision\", parse_precision)?,\n        transforms_precision: input.opt_value_from_fn(\"--transforms-precision\", parse_precision)?,\n        style_sheet: input.opt_value_from_str(\"--stylesheet\").unwrap_or_default(),\n\n        quiet: input.contains(\"--quiet\"),\n\n        input: input.free_from_str()?,\n        output: input.free_from_str()?,\n    })\n}\n\nfn parse_dpi(s: &str) -> Result<u32, String> {\n    let n: u32 = s.parse().map_err(|_| \"invalid number\")?;\n\n    if n >= 10 && n <= 4000 {\n        Ok(n)\n    } else {\n        Err(\"DPI out of bounds\".to_string())\n    }\n}\n\nfn parse_font_size(s: &str) -> Result<u32, String> {\n    let n: u32 = s.parse().map_err(|_| \"invalid number\")?;\n\n    if n > 0 && n <= 192 {\n        Ok(n)\n    } else {\n        Err(\"font size out of bounds\".to_string())\n    }\n}\n\nfn parse_languages(s: &str) -> Result<Vec<String>, String> {\n    let mut langs = Vec::new();\n    for lang in s.split(',') {\n        langs.push(lang.trim().to_string());\n    }\n\n    if langs.is_empty() {\n        return Err(\"languages list cannot be empty\".to_string());\n    }\n\n    Ok(langs)\n}\n\nfn parse_indent(s: &str) -> Result<xmlwriter::Indent, String> {\n    let indent = match s {\n        \"none\" => xmlwriter::Indent::None,\n        \"0\" => xmlwriter::Indent::Spaces(0),\n        \"1\" => xmlwriter::Indent::Spaces(1),\n        \"2\" => xmlwriter::Indent::Spaces(2),\n        \"3\" => xmlwriter::Indent::Spaces(3),\n        \"4\" => xmlwriter::Indent::Spaces(4),\n        \"tabs\" => xmlwriter::Indent::Tabs,\n        _ => return Err(\"invalid INDENT value\".to_string()),\n    };\n\n    Ok(indent)\n}\n\nfn parse_length(s: &str) -> Result<u32, String> {\n    let n: u32 = s.parse().map_err(|_| \"invalid length\")?;\n\n    if n > 0 {\n        Ok(n)\n    } else {\n        Err(\"LENGTH cannot be zero\".to_string())\n    }\n}\n\nfn parse_precision(s: &str) -> Result<u8, String> {\n    let n: u8 = s.parse().map_err(|_| \"invalid precision NUM value\")?;\n\n    if (2..=8).contains(&n) {\n        Ok(n)\n    } else {\n        Err(\"precision NUM cannot be smaller than 2 or larger than 8\".to_string())\n    }\n}\n\n#[derive(Clone, PartialEq, Debug)]\nenum InputFrom<'a> {\n    Stdin,\n    File(&'a str),\n}\n\n#[derive(Clone, PartialEq, Debug)]\nenum OutputTo<'a> {\n    Stdout,\n    File(&'a str),\n}\n\nfn main() {\n    let args = match collect_args() {\n        Ok(v) => v,\n        Err(e) => {\n            eprintln!(\"Error: {}.\", e);\n            process::exit(1);\n        }\n    };\n\n    if !args.quiet {\n        if let Ok(()) = log::set_logger(&LOGGER) {\n            log::set_max_level(log::LevelFilter::Warn);\n        }\n    }\n\n    if let Err(e) = process(args) {\n        eprintln!(\"Error: {}.\", e);\n        process::exit(1);\n    }\n}\n\nfn process(args: Args) -> Result<(), String> {\n    let (in_svg, out_svg) = {\n        let in_svg = args.input.as_str();\n        let out_svg = args.output.as_str();\n\n        let svg_from = if in_svg == \"-\" {\n            InputFrom::Stdin\n        } else if in_svg == \"-c\" {\n            return Err(\"-c should be set after input\".to_string());\n        } else {\n            InputFrom::File(in_svg)\n        };\n\n        let svg_to = if out_svg == \"-c\" {\n            OutputTo::Stdout\n        } else {\n            OutputTo::File(out_svg)\n        };\n\n        (svg_from, svg_to)\n    };\n\n    let mut fontdb = usvg::fontdb::Database::new();\n    if !args.skip_system_fonts {\n        // TODO: only when needed\n        fontdb.load_system_fonts();\n    }\n\n    for path in &args.font_files {\n        if let Err(e) = fontdb.load_font_file(path) {\n            log::warn!(\"Failed to load '{}' cause {}.\", path.display(), e);\n        }\n    }\n\n    for path in &args.font_dirs {\n        fontdb.load_fonts_dir(path);\n    }\n\n    let take_or = |mut family: Option<String>, fallback: &str| {\n        family.take().unwrap_or_else(|| fallback.to_string())\n    };\n\n    fontdb.set_serif_family(take_or(args.serif_family, \"Times New Roman\"));\n    fontdb.set_sans_serif_family(take_or(args.sans_serif_family, \"Arial\"));\n    fontdb.set_cursive_family(take_or(args.cursive_family, \"Comic Sans MS\"));\n    fontdb.set_fantasy_family(take_or(args.fantasy_family, \"Impact\"));\n    fontdb.set_monospace_family(take_or(args.monospace_family, \"Courier New\"));\n\n    if args.list_fonts {\n        for face in fontdb.faces() {\n            if let usvg::fontdb::Source::File(path) = &face.source {\n                let families: Vec<_> = face\n                    .families\n                    .iter()\n                    .map(|f| format!(\"{} ({}, {})\", f.0, f.1.primary_language(), f.1.region()))\n                    .collect();\n\n                println!(\n                    \"{}: '{}', {}, {:?}, {:?}, {:?}\",\n                    path.display(),\n                    families.join(\"', '\"),\n                    face.index,\n                    face.style,\n                    face.weight.0,\n                    face.stretch\n                );\n            }\n        }\n    }\n\n    let resources_dir = match args.resources_dir {\n        Some(v) => Some(v),\n        None => {\n            match in_svg {\n                InputFrom::Stdin => None,\n                InputFrom::File(ref f) => {\n                    // Get input file absolute directory.\n                    std::fs::canonicalize(f)\n                        .ok()\n                        .and_then(|p| p.parent().map(|p| p.to_path_buf()))\n                }\n            }\n        }\n    };\n\n    let style_sheet = match args.style_sheet.as_ref() {\n        Some(p) => Some(\n            std::fs::read(&p)\n                .ok()\n                .and_then(|s| std::str::from_utf8(&s).ok().map(|s| s.to_string()))\n                .ok_or(\"failed to read stylesheet\".to_string())?,\n        ),\n        None => None,\n    };\n\n    let re_opt = usvg::Options {\n        resources_dir,\n        dpi: args.dpi as f32,\n        font_family: args\n            .font_family\n            .as_deref()\n            .unwrap_or(\"Times New Roman\")\n            .to_string(),\n        font_size: args.font_size as f32,\n        languages: args.languages,\n        shape_rendering: args.shape_rendering,\n        text_rendering: args.text_rendering,\n        image_rendering: args.image_rendering,\n        default_size: usvg::Size::from_wh(args.default_width as f32, args.default_height as f32)\n            .unwrap(),\n        image_href_resolver: usvg::ImageHrefResolver::default(),\n        font_resolver: usvg::FontResolver::default(),\n        fontdb: Arc::new(fontdb),\n        style_sheet,\n    };\n\n    let input_svg = match in_svg {\n        InputFrom::Stdin => load_stdin(),\n        InputFrom::File(ref path) => std::fs::read(path).map_err(|e| e.to_string()),\n    }?;\n\n    let tree = usvg::Tree::from_data(&input_svg, &re_opt).map_err(|e| format!(\"{}\", e))?;\n\n    let xml_opt = usvg::WriteOptions {\n        id_prefix: args.id_prefix,\n        preserve_text: args.preserve_text,\n        coordinates_precision: args.coordinates_precision.unwrap_or(8),\n        transforms_precision: args.transforms_precision.unwrap_or(8),\n        use_single_quote: false,\n        indent: args.indent,\n        attributes_indent: args.attrs_indent,\n    };\n\n    let s = tree.to_string(&xml_opt);\n    match out_svg {\n        OutputTo::Stdout => {\n            io::stdout()\n                .write_all(s.as_bytes())\n                .map_err(|_| \"failed to write to the stdout\".to_string())?;\n        }\n        OutputTo::File(path) => {\n            let mut f =\n                File::create(path).map_err(|_| \"failed to create the output file\".to_string())?;\n            f.write_all(s.as_bytes())\n                .map_err(|_| \"failed to write to the output file\".to_string())?;\n        }\n    }\n\n    Ok(())\n}\n\nfn load_stdin() -> Result<Vec<u8>, String> {\n    let mut buf = Vec::new();\n    let stdin = io::stdin();\n    let mut handle = stdin.lock();\n\n    handle\n        .read_to_end(&mut buf)\n        .map_err(|_| \"failed to read from stdin\".to_string())?;\n\n    Ok(buf)\n}\n\n/// A simple stderr logger.\nstatic LOGGER: SimpleLogger = SimpleLogger;\nstruct SimpleLogger;\nimpl log::Log for SimpleLogger {\n    fn enabled(&self, metadata: &log::Metadata) -> bool {\n        metadata.level() <= log::LevelFilter::Warn\n    }\n\n    fn log(&self, record: &log::Record) {\n        if self.enabled(record.metadata()) {\n            let target = if record.target().len() > 0 {\n                record.target()\n            } else {\n                record.module_path().unwrap_or_default()\n            };\n\n            let line = record.line().unwrap_or(0);\n            let args = record.args();\n\n            match record.level() {\n                log::Level::Error => eprintln!(\"Error (in {}:{}): {}\", target, line, args),\n                log::Level::Warn => eprintln!(\"Warning (in {}:{}): {}\", target, line, args),\n                log::Level::Info => eprintln!(\"Info (in {}:{}): {}\", target, line, args),\n                log::Level::Debug => eprintln!(\"Debug (in {}:{}): {}\", target, line, args),\n                log::Level::Trace => eprintln!(\"Trace (in {}:{}): {}\", target, line, args),\n            }\n        }\n    }\n\n    fn flush(&self) {}\n}\n"
  },
  {
    "path": "crates/usvg/src/parser/clippath.rs",
    "content": "// Copyright 2018 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse std::str::FromStr;\nuse std::sync::Arc;\n\nuse super::converter;\nuse super::svgtree::{AId, EId, SvgNode};\nuse crate::{ClipPath, Group, NonEmptyString, NonZeroRect, Transform, Units};\n\npub(crate) fn convert(\n    node: SvgNode,\n    state: &converter::State,\n    object_bbox: Option<NonZeroRect>,\n    cache: &mut converter::Cache,\n) -> Option<Arc<ClipPath>> {\n    // A `clip-path` attribute must reference a `clipPath` element.\n    if node.tag_name() != Some(EId::ClipPath) {\n        return None;\n    }\n\n    // The whole clip path should be ignored when a transform is invalid.\n    let mut transform = resolve_clip_path_transform(node, state)?;\n\n    let units = node\n        .attribute(AId::ClipPathUnits)\n        .unwrap_or(Units::UserSpaceOnUse);\n\n    // Check if this element was already converted.\n    //\n    // Only `userSpaceOnUse` clipPaths can be shared,\n    // because `objectBoundingBox` one will be converted into user one\n    // and will become node-specific.\n    let cacheable = units == Units::UserSpaceOnUse;\n    if cacheable {\n        if let Some(clip) = cache.clip_paths.get(node.element_id()) {\n            return Some(clip.clone());\n        }\n    }\n\n    if units == Units::ObjectBoundingBox {\n        let object_bbox = match object_bbox {\n            Some(v) => v,\n            None => {\n                log::warn!(\"Clipping of zero-sized shapes is not allowed.\");\n                return None;\n            }\n        };\n\n        let ts = Transform::from_bbox(object_bbox);\n        transform = transform.pre_concat(ts);\n    }\n\n    // Resolve linked clip path.\n    let mut clip_path = None;\n    if let Some(link) = node.attribute::<SvgNode>(AId::ClipPath) {\n        clip_path = convert(link, state, object_bbox, cache);\n\n        // Linked `clipPath` must be valid.\n        if clip_path.is_none() {\n            return None;\n        }\n    }\n\n    let mut id = NonEmptyString::new(node.element_id().to_string())?;\n    // Generate ID only when we're parsing `objectBoundingBox` clip for the second time.\n    if !cacheable && cache.clip_paths.contains_key(id.get()) {\n        id = cache.gen_clip_path_id();\n    }\n    let id_copy = id.get().to_string();\n\n    let mut clip = ClipPath {\n        id,\n        transform,\n        clip_path,\n        root: Group::empty(),\n    };\n\n    let mut clip_state = state.clone();\n    clip_state.parent_clip_path = Some(node);\n    converter::convert_clip_path_elements(node, &clip_state, cache, &mut clip.root);\n\n    if clip.root.has_children() {\n        clip.root.calculate_bounding_boxes();\n        let clip = Arc::new(clip);\n        cache.clip_paths.insert(id_copy, clip.clone());\n        Some(clip)\n    } else {\n        // A clip path without children is invalid.\n        None\n    }\n}\n\nfn resolve_clip_path_transform(node: SvgNode, state: &converter::State) -> Option<Transform> {\n    // Do not use Node::attribute::<Transform>, because it will always\n    // return a valid transform.\n\n    let value: &str = match node.attribute(AId::Transform) {\n        Some(v) => v,\n        None => return Some(Transform::default()),\n    };\n\n    let ts = match svgtypes::Transform::from_str(value) {\n        Ok(v) => v,\n        Err(_) => {\n            log::warn!(\"Failed to parse {} value: '{}'.\", AId::Transform, value);\n            return None;\n        }\n    };\n\n    let ts = Transform::from_row(\n        ts.a as f32,\n        ts.b as f32,\n        ts.c as f32,\n        ts.d as f32,\n        ts.e as f32,\n        ts.f as f32,\n    );\n\n    if ts.is_valid() {\n        Some(node.resolve_transform(AId::Transform, state))\n    } else {\n        None\n    }\n}\n"
  },
  {
    "path": "crates/usvg/src/parser/converter.rs",
    "content": "// Copyright 2018 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse std::collections::{HashMap, HashSet};\nuse std::hash::{Hash, Hasher};\nuse std::str::FromStr;\nuse std::sync::Arc;\n\n#[cfg(feature = \"text\")]\nuse fontdb::Database;\n#[cfg(feature = \"text\")]\nuse fontdb::ID;\n#[cfg(feature = \"text\")]\nuse rustybuzz::ttf_parser::GlyphId;\nuse svgtypes::{Length, LengthUnit as Unit, PaintOrderKind, TransformOrigin};\nuse tiny_skia_path::PathBuilder;\n\nuse super::svgtree::{self, AId, EId, FromValue, SvgNode};\nuse super::units::{self, convert_length};\nuse super::{Error, Options, marker};\n#[cfg(feature = \"text\")]\nuse crate::flatten::BitmapImage;\nuse crate::parser::paint_server::process_paint;\n#[cfg(feature = \"text\")]\nuse crate::text::flatten::DatabaseExt;\nuse crate::*;\n\n#[derive(Clone)]\npub struct State<'a> {\n    pub(crate) parent_clip_path: Option<SvgNode<'a, 'a>>,\n    pub(crate) parent_markers: Vec<SvgNode<'a, 'a>>,\n    /// Stores the resolved fill and stroke of a use node\n    /// or a path element (for markers)\n    pub(crate) context_element: Option<(Option<Fill>, Option<Stroke>)>,\n    pub(crate) fe_image_link: bool,\n    /// A viewBox of the parent SVG element.\n    pub(crate) view_box: NonZeroRect,\n    /// A size of the parent `use` element.\n    /// Used only during nested `svg` size resolving.\n    /// Width and height can be set independently.\n    pub(crate) use_size: (Option<f32>, Option<f32>),\n    pub(crate) opt: &'a Options<'a>,\n}\n\n#[derive(Clone)]\npub struct Cache {\n    /// This fontdb is initialized from [`Options::fontdb`] and then populated\n    /// over the course of conversion.\n    #[cfg(feature = \"text\")]\n    pub fontdb: Arc<Database>,\n\n    #[cfg(feature = \"text\")]\n    cache_outline: HashMap<(ID, GlyphId), Option<tiny_skia_path::Path>>,\n    #[cfg(feature = \"text\")]\n    cache_colr: HashMap<(ID, GlyphId), Option<Tree>>,\n    #[cfg(feature = \"text\")]\n    cache_svg: HashMap<(ID, GlyphId), Option<Node>>,\n    #[cfg(feature = \"text\")]\n    cache_raster: HashMap<(ID, GlyphId), Option<BitmapImage>>,\n    #[cfg(feature = \"text\")]\n    cache_has_opsz: HashMap<ID, bool>,\n\n    pub clip_paths: HashMap<String, Arc<ClipPath>>,\n    pub masks: HashMap<String, Arc<Mask>>,\n    pub filters: HashMap<String, Arc<filter::Filter>>,\n    pub paint: HashMap<String, Paint>,\n\n    // used for ID generation\n    all_ids: HashSet<u64>,\n    linear_gradient_index: usize,\n    radial_gradient_index: usize,\n    pattern_index: usize,\n    clip_path_index: usize,\n    mask_index: usize,\n    filter_index: usize,\n    image_index: usize,\n}\n\nmacro_rules! font_lookup {\n    ($method_name:ident, $cache_map:ident, $font_variant:ident, $return_type:ty) => {\n        #[cfg(feature = \"text\")]\n        pub(crate) fn $method_name(&mut self, font: ID, glyph: GlyphId) -> Option<$return_type> {\n            let key = (font, glyph);\n            match self.$cache_map.get(&key) {\n                Some(cache_hit) => cache_hit.clone(),\n                None => {\n                    let lookup = self.fontdb.$font_variant(font, glyph);\n                    self.$cache_map.insert(key, lookup.clone());\n                    lookup\n                }\n            }\n        }\n    };\n}\n\nimpl Cache {\n    pub(crate) fn new(#[cfg(feature = \"text\")] fontdb: Arc<Database>) -> Self {\n        Self {\n            #[cfg(feature = \"text\")]\n            fontdb,\n\n            #[cfg(feature = \"text\")]\n            cache_outline: HashMap::new(),\n            #[cfg(feature = \"text\")]\n            cache_colr: HashMap::new(),\n            #[cfg(feature = \"text\")]\n            cache_svg: HashMap::new(),\n            #[cfg(feature = \"text\")]\n            cache_raster: HashMap::new(),\n            #[cfg(feature = \"text\")]\n            cache_has_opsz: HashMap::new(),\n\n            clip_paths: HashMap::new(),\n            masks: HashMap::new(),\n            filters: HashMap::new(),\n            paint: HashMap::new(),\n\n            all_ids: HashSet::new(),\n            linear_gradient_index: 0,\n            radial_gradient_index: 0,\n            pattern_index: 0,\n            clip_path_index: 0,\n            mask_index: 0,\n            filter_index: 0,\n            image_index: 0,\n        }\n    }\n\n    // TODO: macros?\n    pub(crate) fn gen_linear_gradient_id(&mut self) -> NonEmptyString {\n        loop {\n            self.linear_gradient_index += 1;\n            let new_id = format!(\"linearGradient{}\", self.linear_gradient_index);\n            let new_hash = string_hash(&new_id);\n            if !self.all_ids.contains(&new_hash) {\n                return NonEmptyString::new(new_id).unwrap();\n            }\n        }\n    }\n\n    pub(crate) fn gen_radial_gradient_id(&mut self) -> NonEmptyString {\n        loop {\n            self.radial_gradient_index += 1;\n            let new_id = format!(\"radialGradient{}\", self.radial_gradient_index);\n            let new_hash = string_hash(&new_id);\n            if !self.all_ids.contains(&new_hash) {\n                return NonEmptyString::new(new_id).unwrap();\n            }\n        }\n    }\n\n    pub(crate) fn gen_pattern_id(&mut self) -> NonEmptyString {\n        loop {\n            self.pattern_index += 1;\n            let new_id = format!(\"pattern{}\", self.pattern_index);\n            let new_hash = string_hash(&new_id);\n            if !self.all_ids.contains(&new_hash) {\n                return NonEmptyString::new(new_id).unwrap();\n            }\n        }\n    }\n\n    pub(crate) fn gen_clip_path_id(&mut self) -> NonEmptyString {\n        loop {\n            self.clip_path_index += 1;\n            let new_id = format!(\"clipPath{}\", self.clip_path_index);\n            let new_hash = string_hash(&new_id);\n            if !self.all_ids.contains(&new_hash) {\n                return NonEmptyString::new(new_id).unwrap();\n            }\n        }\n    }\n\n    pub(crate) fn gen_mask_id(&mut self) -> NonEmptyString {\n        loop {\n            self.mask_index += 1;\n            let new_id = format!(\"mask{}\", self.mask_index);\n            let new_hash = string_hash(&new_id);\n            if !self.all_ids.contains(&new_hash) {\n                return NonEmptyString::new(new_id).unwrap();\n            }\n        }\n    }\n\n    pub(crate) fn gen_filter_id(&mut self) -> NonEmptyString {\n        loop {\n            self.filter_index += 1;\n            let new_id = format!(\"filter{}\", self.filter_index);\n            let new_hash = string_hash(&new_id);\n            if !self.all_ids.contains(&new_hash) {\n                return NonEmptyString::new(new_id).unwrap();\n            }\n        }\n    }\n\n    pub(crate) fn gen_image_id(&mut self) -> NonEmptyString {\n        loop {\n            self.image_index += 1;\n            let new_id = format!(\"image{}\", self.image_index);\n            let new_hash = string_hash(&new_id);\n            if !self.all_ids.contains(&new_hash) {\n                return NonEmptyString::new(new_id).unwrap();\n            }\n        }\n    }\n\n    font_lookup!(fontdb_outline, cache_outline, outline, tiny_skia_path::Path);\n    font_lookup!(fontdb_colr, cache_colr, colr, Tree);\n    font_lookup!(fontdb_svg, cache_svg, svg, Node);\n    font_lookup!(fontdb_raster, cache_raster, raster, BitmapImage);\n\n    #[cfg(feature = \"text\")]\n    pub(crate) fn has_opsz_axis(&mut self, font: ID) -> bool {\n        if let Some(&cached) = self.cache_has_opsz.get(&font) {\n            return cached;\n        }\n        let has_opsz = self.fontdb.has_opsz_axis(font);\n        self.cache_has_opsz.insert(font, has_opsz);\n        has_opsz\n    }\n}\n\n// TODO: is there a simpler way?\nfn string_hash(s: &str) -> u64 {\n    let mut h = std::collections::hash_map::DefaultHasher::new();\n    s.hash(&mut h);\n    h.finish()\n}\n\nimpl<'a, 'input: 'a> SvgNode<'a, 'input> {\n    pub(crate) fn convert_length(\n        &self,\n        aid: AId,\n        object_units: Units,\n        state: &State,\n        def: Length,\n    ) -> f32 {\n        units::convert_length(\n            self.attribute(aid).unwrap_or(def),\n            *self,\n            aid,\n            object_units,\n            state,\n        )\n    }\n\n    pub fn convert_user_length(&self, aid: AId, state: &State, def: Length) -> f32 {\n        self.convert_length(aid, Units::UserSpaceOnUse, state, def)\n    }\n\n    pub fn parse_viewbox(&self) -> Option<NonZeroRect> {\n        let vb: svgtypes::ViewBox = self.attribute(AId::ViewBox)?;\n        NonZeroRect::from_xywh(vb.x as f32, vb.y as f32, vb.w as f32, vb.h as f32)\n    }\n\n    pub fn resolve_length(&self, aid: AId, state: &State, def: f32) -> f32 {\n        debug_assert!(\n            !matches!(aid, AId::BaselineShift | AId::FontSize),\n            \"{} cannot be resolved via this function\",\n            aid\n        );\n\n        if let Some(n) = self.ancestors().find(|n| n.has_attribute(aid)) {\n            if let Some(length) = n.attribute(aid) {\n                return units::convert_user_length(length, n, aid, state);\n            }\n        }\n\n        def\n    }\n\n    pub fn resolve_valid_length(\n        &self,\n        aid: AId,\n        state: &State,\n        def: f32,\n    ) -> Option<NonZeroPositiveF32> {\n        let n = self.resolve_length(aid, state, def);\n        NonZeroPositiveF32::new(n)\n    }\n\n    pub(crate) fn try_convert_length(\n        &self,\n        aid: AId,\n        object_units: Units,\n        state: &State,\n    ) -> Option<f32> {\n        Some(units::convert_length(\n            self.attribute(aid)?,\n            *self,\n            aid,\n            object_units,\n            state,\n        ))\n    }\n\n    pub fn has_valid_transform(&self, aid: AId) -> bool {\n        // Do not use Node::attribute::<Transform>, because it will always\n        // return a valid transform.\n\n        let attr = match self.attribute(aid) {\n            Some(attr) => attr,\n            None => return true,\n        };\n\n        let ts = match svgtypes::Transform::from_str(attr) {\n            Ok(v) => v,\n            Err(_) => return true,\n        };\n\n        let ts = Transform::from_row(\n            ts.a as f32,\n            ts.b as f32,\n            ts.c as f32,\n            ts.d as f32,\n            ts.e as f32,\n            ts.f as f32,\n        );\n        ts.is_valid()\n    }\n\n    pub fn is_visible_element(&self, opt: &crate::Options) -> bool {\n        self.attribute(AId::Display) != Some(\"none\")\n            && self.has_valid_transform(AId::Transform)\n            && super::switch::is_condition_passed(*self, opt)\n    }\n}\n\npub trait SvgColorExt {\n    fn split_alpha(self) -> (Color, Opacity);\n}\n\nimpl SvgColorExt for svgtypes::Color {\n    fn split_alpha(self) -> (Color, Opacity) {\n        (\n            Color::new_rgb(self.red, self.green, self.blue),\n            Opacity::new_u8(self.alpha),\n        )\n    }\n}\n\n/// Converts an input `Document` into a `Tree`.\n///\n/// # Errors\n///\n/// - If `Document` doesn't have an SVG node - returns an empty tree.\n/// - If `Document` doesn't have a valid size - returns `Error::InvalidSize`.\npub(crate) fn convert_doc(svg_doc: &svgtree::Document, opt: &Options) -> Result<Tree, Error> {\n    let svg = svg_doc.root_element();\n    let (size, restore_viewbox) = resolve_svg_size(&svg, opt);\n    let size = size?;\n    let view_box = ViewBox {\n        rect: svg\n            .parse_viewbox()\n            .unwrap_or_else(|| size.to_non_zero_rect(0.0, 0.0)),\n        aspect: svg.attribute(AId::PreserveAspectRatio).unwrap_or_default(),\n    };\n\n    let background_color = svg\n        .attribute::<&str>(AId::BackgroundColor)\n        .and_then(|s| svgtypes::Paint::from_str(s).ok())\n        .and_then(|paint| match paint {\n            svgtypes::Paint::Color(c) => Some(c),\n            _ => None,\n        });\n\n    let mut tree = Tree {\n        size,\n        root: Group::empty(),\n        linear_gradients: Vec::new(),\n        radial_gradients: Vec::new(),\n        patterns: Vec::new(),\n        clip_paths: Vec::new(),\n        masks: Vec::new(),\n        filters: Vec::new(),\n        #[cfg(feature = \"text\")]\n        fontdb: opt.fontdb.clone(),\n    };\n\n    if !svg.is_visible_element(opt) {\n        return Ok(tree);\n    }\n\n    let state = State {\n        parent_clip_path: None,\n        context_element: None,\n        parent_markers: Vec::new(),\n        fe_image_link: false,\n        view_box: view_box.rect,\n        use_size: (None, None),\n        opt,\n    };\n\n    let mut cache = Cache::new(\n        #[cfg(feature = \"text\")]\n        opt.fontdb.clone(),\n    );\n\n    for node in svg_doc.descendants() {\n        if let Some(tag) = node.tag_name() {\n            if matches!(\n                tag,\n                EId::ClipPath\n                    | EId::Filter\n                    | EId::LinearGradient\n                    | EId::Mask\n                    | EId::Pattern\n                    | EId::RadialGradient\n                    | EId::Image\n            ) {\n                if !node.element_id().is_empty() {\n                    cache.all_ids.insert(string_hash(node.element_id()));\n                }\n            }\n        }\n    }\n\n    let root_ts = view_box.to_transform(tree.size());\n    if root_ts.is_identity() && background_color.is_none() {\n        convert_children(svg_doc.root(), &state, &mut cache, &mut tree.root);\n    } else {\n        let mut g = Group::empty();\n\n        if let Some(background_color) = background_color {\n            if let Some(path) = background_path(background_color, view_box.rect.to_rect()) {\n                g.children.push(Node::Path(Box::new(path)));\n            }\n        }\n\n        g.transform = root_ts;\n        g.abs_transform = root_ts;\n        convert_children(svg_doc.root(), &state, &mut cache, &mut g);\n        g.calculate_bounding_boxes();\n        tree.root.children.push(Node::Group(Box::new(g)));\n    }\n\n    // Clear cache to make sure that all `Arc<T>` objects have a single strong reference.\n    cache.clip_paths.clear();\n    cache.masks.clear();\n    cache.filters.clear();\n    cache.paint.clear();\n\n    super::paint_server::update_paint_servers(\n        &mut tree.root,\n        Transform::default(),\n        None,\n        None,\n        &mut cache,\n    );\n    tree.collect_paint_servers();\n    tree.root.collect_clip_paths(&mut tree.clip_paths);\n    tree.root.collect_masks(&mut tree.masks);\n    tree.root.collect_filters(&mut tree.filters);\n    tree.root.calculate_bounding_boxes();\n\n    // The fontdb might have been mutated and we want to apply these changes to\n    // the tree's fontdb.\n    #[cfg(feature = \"text\")]\n    {\n        tree.fontdb = cache.fontdb;\n    }\n\n    if restore_viewbox {\n        calculate_svg_bbox(&mut tree);\n    }\n\n    Ok(tree)\n}\n\nfn background_path(background_color: svgtypes::Color, area: Rect) -> Option<Path> {\n    let path = PathBuilder::from_rect(area);\n\n    let fill = Fill {\n        paint: Paint::Color(Color::new_rgb(\n            background_color.red,\n            background_color.green,\n            background_color.blue,\n        )),\n        opacity: NormalizedF32::new(background_color.alpha as f32 / 255.0)?,\n        ..Default::default()\n    };\n\n    let mut path = Path::new_simple(Arc::new(path))?;\n    path.fill = Some(fill);\n\n    Some(path)\n}\n\nfn resolve_svg_size(svg: &SvgNode, opt: &Options) -> (Result<Size, Error>, bool) {\n    let mut state = State {\n        parent_clip_path: None,\n        context_element: None,\n        parent_markers: Vec::new(),\n        fe_image_link: false,\n        view_box: NonZeroRect::from_xywh(0.0, 0.0, 100.0, 100.0).unwrap(),\n        use_size: (None, None),\n        opt,\n    };\n\n    let def = Length::new(100.0, Unit::Percent);\n    let mut width: Length = svg.attribute(AId::Width).unwrap_or(def);\n    let mut height: Length = svg.attribute(AId::Height).unwrap_or(def);\n\n    let view_box = svg.parse_viewbox();\n\n    let restore_viewbox =\n        if (width.unit == Unit::Percent || height.unit == Unit::Percent) && view_box.is_none() {\n            // Apply the percentages to the fallback size.\n            if width.unit == Unit::Percent {\n                width = Length::new(\n                    (width.number / 100.0) * state.opt.default_size.width() as f64,\n                    Unit::None,\n                );\n            }\n\n            if height.unit == Unit::Percent {\n                height = Length::new(\n                    (height.number / 100.0) * state.opt.default_size.height() as f64,\n                    Unit::None,\n                );\n            }\n\n            true\n        } else {\n            false\n        };\n\n    let size = if let Some(vbox) = view_box {\n        state.view_box = vbox;\n\n        let w = if width.unit == Unit::Percent {\n            vbox.width() * (width.number as f32 / 100.0)\n        } else {\n            svg.convert_user_length(AId::Width, &state, def)\n        };\n\n        let h = if height.unit == Unit::Percent {\n            vbox.height() * (height.number as f32 / 100.0)\n        } else {\n            svg.convert_user_length(AId::Height, &state, def)\n        };\n\n        Size::from_wh(w, h)\n    } else {\n        Size::from_wh(\n            svg.convert_user_length(AId::Width, &state, def),\n            svg.convert_user_length(AId::Height, &state, def),\n        )\n    };\n\n    (size.ok_or(Error::InvalidSize), restore_viewbox)\n}\n\n/// Calculates SVG's size and viewBox in case there were not set.\n///\n/// Simply iterates over all nodes and calculates a bounding box.\nfn calculate_svg_bbox(tree: &mut Tree) {\n    let bbox = tree.root.abs_bounding_box();\n    if let Some(size) = Size::from_wh(bbox.right(), bbox.bottom()) {\n        tree.size = size;\n    }\n}\n\n#[inline(never)]\npub(crate) fn convert_children(\n    parent_node: SvgNode,\n    state: &State,\n    cache: &mut Cache,\n    parent: &mut Group,\n) {\n    for node in parent_node.children() {\n        convert_element(node, state, cache, parent);\n    }\n}\n\n#[inline(never)]\npub(crate) fn convert_element(node: SvgNode, state: &State, cache: &mut Cache, parent: &mut Group) {\n    let tag_name = match node.tag_name() {\n        Some(v) => v,\n        None => return,\n    };\n\n    if !tag_name.is_graphic() && !matches!(tag_name, EId::G | EId::Switch | EId::Svg) {\n        return;\n    }\n\n    if !node.is_visible_element(state.opt) {\n        return;\n    }\n\n    if tag_name == EId::Use {\n        super::use_node::convert(node, state, cache, parent);\n        return;\n    }\n\n    if tag_name == EId::Switch {\n        super::switch::convert(node, state, cache, parent);\n        return;\n    }\n\n    if let Some(g) = convert_group(node, state, false, cache, parent, &|cache, g| {\n        convert_element_impl(tag_name, node, state, cache, g);\n    }) {\n        parent.children.push(Node::Group(Box::new(g)));\n    }\n}\n\n#[inline(never)]\nfn convert_element_impl(\n    tag_name: EId,\n    node: SvgNode,\n    state: &State,\n    cache: &mut Cache,\n    parent: &mut Group,\n) {\n    match tag_name {\n        EId::Rect\n        | EId::Circle\n        | EId::Ellipse\n        | EId::Line\n        | EId::Polyline\n        | EId::Polygon\n        | EId::Path => {\n            if let Some(path) = super::shapes::convert(node, state) {\n                convert_path(node, path, state, cache, parent);\n            }\n        }\n        EId::Image => {\n            super::image::convert(node, state, cache, parent);\n        }\n        EId::Text => {\n            #[cfg(feature = \"text\")]\n            {\n                super::text::convert(node, state, cache, parent);\n            }\n        }\n        EId::Svg => {\n            if node.parent_element().is_some() {\n                super::use_node::convert_svg(node, state, cache, parent);\n            } else {\n                // Skip root `svg`.\n                convert_children(node, state, cache, parent);\n            }\n        }\n        EId::G => {\n            convert_children(node, state, cache, parent);\n        }\n        _ => {}\n    }\n}\n\n// `clipPath` can have only shape and `text` children.\n//\n// `line` doesn't impact rendering because stroke is always disabled\n// for `clipPath` children.\n#[inline(never)]\npub(crate) fn convert_clip_path_elements(\n    clip_node: SvgNode,\n    state: &State,\n    cache: &mut Cache,\n    parent: &mut Group,\n) {\n    for node in clip_node.children() {\n        let tag_name = match node.tag_name() {\n            Some(v) => v,\n            None => continue,\n        };\n\n        if !tag_name.is_graphic() {\n            continue;\n        }\n\n        if !node.is_visible_element(state.opt) {\n            continue;\n        }\n\n        if tag_name == EId::Use {\n            super::use_node::convert(node, state, cache, parent);\n            continue;\n        }\n\n        if let Some(g) = convert_group(node, state, false, cache, parent, &|cache, g| {\n            convert_clip_path_elements_impl(tag_name, node, state, cache, g);\n        }) {\n            parent.children.push(Node::Group(Box::new(g)));\n        }\n    }\n}\n\n#[inline(never)]\nfn convert_clip_path_elements_impl(\n    tag_name: EId,\n    node: SvgNode,\n    state: &State,\n    cache: &mut Cache,\n    parent: &mut Group,\n) {\n    match tag_name {\n        EId::Rect | EId::Circle | EId::Ellipse | EId::Polyline | EId::Polygon | EId::Path => {\n            if let Some(path) = super::shapes::convert(node, state) {\n                convert_path(node, path, state, cache, parent);\n            }\n        }\n        EId::Text => {\n            #[cfg(feature = \"text\")]\n            {\n                super::text::convert(node, state, cache, parent);\n            }\n        }\n        _ => {\n            log::warn!(\"'{}' is no a valid 'clip-path' child.\", tag_name);\n        }\n    }\n}\n\n#[derive(Clone, Copy, PartialEq, Debug)]\nenum Isolation {\n    Auto,\n    Isolate,\n}\n\nimpl Default for Isolation {\n    fn default() -> Self {\n        Self::Auto\n    }\n}\n\nimpl<'a, 'input: 'a> FromValue<'a, 'input> for Isolation {\n    fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {\n        match value {\n            \"auto\" => Some(Isolation::Auto),\n            \"isolate\" => Some(Isolation::Isolate),\n            _ => None,\n        }\n    }\n}\n\n// TODO: explain\npub(crate) fn convert_group(\n    node: SvgNode,\n    state: &State,\n    force: bool,\n    cache: &mut Cache,\n    parent: &mut Group,\n    collect_children: &dyn Fn(&mut Cache, &mut Group),\n) -> Option<Group> {\n    // A `clipPath` child cannot have an opacity.\n    let opacity = if state.parent_clip_path.is_none() {\n        node.attribute::<Opacity>(AId::Opacity)\n            .unwrap_or(Opacity::ONE)\n    } else {\n        Opacity::ONE\n    };\n\n    let transform = node.resolve_transform(AId::Transform, state);\n    let blend_mode: BlendMode = node.attribute(AId::MixBlendMode).unwrap_or_default();\n    let isolation: Isolation = node.attribute(AId::Isolation).unwrap_or_default();\n    let isolate = isolation == Isolation::Isolate;\n\n    // Nodes generated by markers must not have an ID. Otherwise we would have duplicates.\n    let is_g_or_use = matches!(node.tag_name(), Some(EId::G) | Some(EId::Use));\n    let id = if is_g_or_use && state.parent_markers.is_empty() {\n        node.element_id().to_string()\n    } else {\n        String::new()\n    };\n\n    let abs_transform = parent.abs_transform.pre_concat(transform);\n    let dummy = Rect::from_xywh(0.0, 0.0, 0.0, 0.0).unwrap();\n    let mut g = Group {\n        id,\n        transform,\n        abs_transform,\n        opacity,\n        blend_mode,\n        isolate,\n        clip_path: None,\n        mask: None,\n        filters: Vec::new(),\n        is_context_element: false,\n        bounding_box: dummy,\n        abs_bounding_box: dummy,\n        stroke_bounding_box: dummy,\n        abs_stroke_bounding_box: dummy,\n        layer_bounding_box: NonZeroRect::from_xywh(0.0, 0.0, 1.0, 1.0).unwrap(),\n        abs_layer_bounding_box: NonZeroRect::from_xywh(0.0, 0.0, 1.0, 1.0).unwrap(),\n        children: Vec::new(),\n    };\n    collect_children(cache, &mut g);\n\n    // We need to know group's bounding box before converting\n    // clipPaths, masks and filters.\n    let object_bbox = g.calculate_object_bbox();\n\n    // `mask` and `filter` cannot be set on `clipPath` children.\n    // But `clip-path` can.\n\n    let mut clip_path = None;\n    if let Some(link) = node.attribute::<SvgNode>(AId::ClipPath) {\n        clip_path = super::clippath::convert(link, state, object_bbox, cache);\n        if clip_path.is_none() {\n            return None;\n        }\n    }\n\n    let mut mask = None;\n    if state.parent_clip_path.is_none() {\n        if let Some(link) = node.attribute::<SvgNode>(AId::Mask) {\n            mask = super::mask::convert(link, state, object_bbox, cache);\n            if mask.is_none() {\n                return None;\n            }\n        }\n    }\n\n    let filters = {\n        let mut filters = Vec::new();\n        if state.parent_clip_path.is_none() {\n            if node.attribute(AId::Filter) == Some(\"none\") {\n                // Do nothing.\n            } else if node.has_attribute(AId::Filter) {\n                if let Ok(f) = super::filter::convert(node, state, object_bbox, cache) {\n                    filters = f;\n                } else {\n                    // A filter that not a link or a filter with a link to a non existing element.\n                    //\n                    // Unlike `clip-path` and `mask`, when a `filter` link is invalid\n                    // then the whole element should be ignored.\n                    //\n                    // This is kinda an undefined behaviour.\n                    // In most cases, Chrome, Firefox and rsvg will ignore such elements,\n                    // but in some cases Chrome allows it. Not sure why.\n                    // Inkscape (0.92) simply ignores such attributes, rendering element as is.\n                    // Batik (1.12) crashes.\n                    //\n                    // Test file: e-filter-051.svg\n                    return None;\n                }\n            }\n        }\n\n        filters\n    };\n\n    let required = opacity.get().approx_ne_ulps(&1.0, 4)\n        || clip_path.is_some()\n        || mask.is_some()\n        || !filters.is_empty()\n        || !transform.is_identity()\n        || blend_mode != BlendMode::Normal\n        || isolate\n        || is_g_or_use\n        || force;\n\n    if !required {\n        parent.children.append(&mut g.children);\n        return None;\n    }\n\n    g.clip_path = clip_path;\n    g.mask = mask;\n    g.filters = filters;\n\n    // Must be called after we set Group::filters\n    g.calculate_bounding_boxes();\n\n    Some(g)\n}\n\nfn convert_path(\n    node: SvgNode,\n    tiny_skia_path: Arc<tiny_skia_path::Path>,\n    state: &State,\n    cache: &mut Cache,\n    parent: &mut Group,\n) {\n    debug_assert!(tiny_skia_path.len() >= 2);\n    if tiny_skia_path.len() < 2 {\n        return;\n    }\n\n    let has_bbox = tiny_skia_path.bounds().width() > 0.0 && tiny_skia_path.bounds().height() > 0.0;\n    let mut fill = super::style::resolve_fill(node, has_bbox, state, cache);\n    let mut stroke = super::style::resolve_stroke(node, has_bbox, state, cache);\n    let visibility: Visibility = node.find_attribute(AId::Visibility).unwrap_or_default();\n    let mut visible = visibility == Visibility::Visible;\n    let rendering_mode: ShapeRendering = node\n        .find_attribute(AId::ShapeRendering)\n        .unwrap_or(state.opt.shape_rendering);\n\n    // TODO: handle `markers` before `stroke`\n    let raw_paint_order: svgtypes::PaintOrder =\n        node.find_attribute(AId::PaintOrder).unwrap_or_default();\n    let paint_order = svg_paint_order_to_usvg(raw_paint_order);\n    let path_transform = parent.abs_transform;\n\n    // If a path doesn't have a fill or a stroke then it's invisible.\n    // By setting `visibility` to `hidden` we are disabling rendering of this path.\n    if fill.is_none() && stroke.is_none() {\n        visible = false;\n    }\n\n    if let Some(fill) = fill.as_mut() {\n        if let Some(ContextElement::PathNode(context_transform, context_bbox)) =\n            fill.context_element\n        {\n            process_paint(\n                &mut fill.paint,\n                true,\n                context_transform,\n                context_bbox.map(|r| r.to_rect()),\n                path_transform,\n                tiny_skia_path.bounds(),\n                cache,\n            );\n            fill.context_element = None;\n        }\n    }\n\n    if let Some(stroke) = stroke.as_mut() {\n        if let Some(ContextElement::PathNode(context_transform, context_bbox)) =\n            stroke.context_element\n        {\n            process_paint(\n                &mut stroke.paint,\n                true,\n                context_transform,\n                context_bbox.map(|r| r.to_rect()),\n                path_transform,\n                tiny_skia_path.bounds(),\n                cache,\n            );\n            stroke.context_element = None;\n        }\n    }\n\n    let mut marker = None;\n    if marker::is_valid(node) && visibility == Visibility::Visible {\n        let mut marker_group = Group {\n            abs_transform: parent.abs_transform,\n            ..Group::empty()\n        };\n\n        let mut marker_state = state.clone();\n\n        let bbox = tiny_skia_path\n            .compute_tight_bounds()\n            .and_then(|r| r.to_non_zero_rect());\n\n        let fill = fill.clone().map(|mut f| {\n            f.context_element = Some(ContextElement::PathNode(path_transform, bbox));\n            f\n        });\n\n        let stroke = stroke.clone().map(|mut s| {\n            s.context_element = Some(ContextElement::PathNode(path_transform, bbox));\n            s\n        });\n\n        marker_state.context_element = Some((fill, stroke));\n\n        marker::convert(\n            node,\n            &tiny_skia_path,\n            &marker_state,\n            cache,\n            &mut marker_group,\n        );\n        marker_group.calculate_bounding_boxes();\n        marker = Some(marker_group);\n    }\n\n    // Nodes generated by markers must not have an ID. Otherwise we would have duplicates.\n    let id = if state.parent_markers.is_empty() {\n        node.element_id().to_string()\n    } else {\n        String::new()\n    };\n\n    let path = Path::new(\n        id,\n        visible,\n        fill,\n        stroke,\n        paint_order,\n        rendering_mode,\n        tiny_skia_path,\n        path_transform,\n    );\n\n    let path = match path {\n        Some(v) => v,\n        None => return,\n    };\n\n    match (raw_paint_order.order, marker) {\n        ([PaintOrderKind::Markers, _, _], Some(markers_node)) => {\n            parent.children.push(Node::Group(Box::new(markers_node)));\n            parent.children.push(Node::Path(Box::new(path.clone())));\n        }\n        ([first, PaintOrderKind::Markers, last], Some(markers_node)) => {\n            append_single_paint_path(first, &path, parent);\n            parent.children.push(Node::Group(Box::new(markers_node)));\n            append_single_paint_path(last, &path, parent);\n        }\n        ([_, _, PaintOrderKind::Markers], Some(markers_node)) => {\n            parent.children.push(Node::Path(Box::new(path.clone())));\n            parent.children.push(Node::Group(Box::new(markers_node)));\n        }\n        _ => parent.children.push(Node::Path(Box::new(path.clone()))),\n    }\n}\n\nfn append_single_paint_path(paint_order_kind: PaintOrderKind, path: &Path, parent: &mut Group) {\n    match paint_order_kind {\n        PaintOrderKind::Fill => {\n            if path.fill.is_some() {\n                let mut fill_path = path.clone();\n                fill_path.stroke = None;\n                fill_path.id = String::new();\n                parent.children.push(Node::Path(Box::new(fill_path)));\n            }\n        }\n        PaintOrderKind::Stroke => {\n            if path.stroke.is_some() {\n                let mut stroke_path = path.clone();\n                stroke_path.fill = None;\n                stroke_path.id = String::new();\n                parent.children.push(Node::Path(Box::new(stroke_path)));\n            }\n        }\n        _ => {}\n    }\n}\n\npub fn svg_paint_order_to_usvg(order: svgtypes::PaintOrder) -> PaintOrder {\n    match (order.order[0], order.order[1]) {\n        (svgtypes::PaintOrderKind::Stroke, _) => PaintOrder::StrokeAndFill,\n        (svgtypes::PaintOrderKind::Markers, svgtypes::PaintOrderKind::Stroke) => {\n            PaintOrder::StrokeAndFill\n        }\n        _ => PaintOrder::FillAndStroke,\n    }\n}\n\nimpl SvgNode<'_, '_> {\n    pub(crate) fn resolve_transform(&self, transform_aid: AId, state: &State) -> Transform {\n        let mut transform: Transform = self.attribute(transform_aid).unwrap_or_default();\n        let transform_origin: Option<TransformOrigin> = self.attribute(AId::TransformOrigin);\n\n        if let Some(transform_origin) = transform_origin {\n            let dx = convert_length(\n                transform_origin.x_offset,\n                *self,\n                AId::Width,\n                Units::UserSpaceOnUse,\n                state,\n            );\n            let dy = convert_length(\n                transform_origin.y_offset,\n                *self,\n                AId::Height,\n                Units::UserSpaceOnUse,\n                state,\n            );\n            transform = Transform::default()\n                .pre_translate(dx, dy)\n                .pre_concat(transform)\n                .pre_translate(-dx, -dy);\n        }\n\n        transform\n    }\n}\n"
  },
  {
    "path": "crates/usvg/src/parser/filter.rs",
    "content": "// Copyright 2022 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\n//! A collection of SVG filters.\n\nuse std::collections::HashSet;\nuse std::str::FromStr;\nuse std::sync::Arc;\n\nuse strict_num::PositiveF32;\nuse svgtypes::{AspectRatio, Length, LengthUnit as Unit};\n\nuse crate::{\n    ApproxZeroUlps, Color, Group, Node, NonEmptyString, NonZeroF32, NonZeroRect, Opacity, Size,\n    Units,\n    filter::{self, *},\n};\n\nuse super::OptionLog;\nuse super::converter::{self, SvgColorExt};\nuse super::paint_server::{convert_units, resolve_number};\nuse super::svgtree::{AId, EId, FromValue, SvgNode};\n\nimpl<'a, 'input: 'a> FromValue<'a, 'input> for filter::ColorInterpolation {\n    fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {\n        match value {\n            \"sRGB\" => Some(filter::ColorInterpolation::SRGB),\n            \"linearRGB\" => Some(filter::ColorInterpolation::LinearRGB),\n            _ => None,\n        }\n    }\n}\n\npub(crate) fn convert(\n    node: SvgNode,\n    state: &converter::State,\n    object_bbox: Option<NonZeroRect>,\n    cache: &mut converter::Cache,\n) -> Result<Vec<Arc<Filter>>, ()> {\n    let value = match node.attribute::<&str>(AId::Filter) {\n        Some(v) => v,\n        None => return Ok(Vec::new()),\n    };\n\n    let mut has_invalid_urls = false;\n    let mut filters = Vec::new();\n\n    let create_base_filter_func =\n        |kind, filters: &mut Vec<Arc<Filter>>, cache: &mut converter::Cache| {\n            // Filter functions, unlike `filter` elements, do not have a filter region.\n            // We're currently do not support an unlimited region, so we simply use a fairly large one.\n            // This if far from ideal, but good for now.\n            // TODO: Should be fixed eventually.\n            let mut rect = match kind {\n                Kind::DropShadow(_) | Kind::GaussianBlur(_) => {\n                    NonZeroRect::from_xywh(-0.5, -0.5, 2.0, 2.0).unwrap()\n                }\n                _ => NonZeroRect::from_xywh(-0.1, -0.1, 1.2, 1.2).unwrap(),\n            };\n\n            let object_bbox = match object_bbox {\n                Some(v) => v,\n                None => {\n                    log::warn!(\n                        \"Filter '{}' has an invalid region. Skipped.\",\n                        node.element_id()\n                    );\n                    return;\n                }\n            };\n\n            rect = rect.bbox_transform(object_bbox);\n\n            filters.push(Arc::new(Filter {\n                id: cache.gen_filter_id(),\n                rect,\n                primitives: vec![Primitive {\n                    rect,\n                    // Unlike `filter` elements, filter functions use sRGB colors by default.\n                    color_interpolation: ColorInterpolation::SRGB,\n                    result: \"result\".to_string(),\n                    kind,\n                }],\n            }));\n        };\n\n    for func in svgtypes::FilterValueListParser::from(value) {\n        let func = match func {\n            Ok(v) => v,\n            Err(e) => {\n                // Skip the whole attribute list on error.\n                log::warn!(\"Failed to parse a filter value cause {}. Skipping.\", e);\n                return Ok(Vec::new());\n            }\n        };\n\n        match func {\n            svgtypes::FilterValue::Blur(std_dev) => create_base_filter_func(\n                convert_blur_function(node, std_dev, state),\n                &mut filters,\n                cache,\n            ),\n            svgtypes::FilterValue::DropShadow {\n                color,\n                dx,\n                dy,\n                std_dev,\n            } => create_base_filter_func(\n                convert_drop_shadow_function(node, color, dx, dy, std_dev, state),\n                &mut filters,\n                cache,\n            ),\n            svgtypes::FilterValue::Brightness(amount) => {\n                create_base_filter_func(convert_brightness_function(amount), &mut filters, cache);\n            }\n            svgtypes::FilterValue::Contrast(amount) => {\n                create_base_filter_func(convert_contrast_function(amount), &mut filters, cache);\n            }\n            svgtypes::FilterValue::Grayscale(amount) => {\n                create_base_filter_func(convert_grayscale_function(amount), &mut filters, cache);\n            }\n            svgtypes::FilterValue::HueRotate(angle) => {\n                create_base_filter_func(convert_hue_rotate_function(angle), &mut filters, cache);\n            }\n            svgtypes::FilterValue::Invert(amount) => {\n                create_base_filter_func(convert_invert_function(amount), &mut filters, cache);\n            }\n            svgtypes::FilterValue::Opacity(amount) => {\n                create_base_filter_func(convert_opacity_function(amount), &mut filters, cache);\n            }\n            svgtypes::FilterValue::Sepia(amount) => {\n                create_base_filter_func(convert_sepia_function(amount), &mut filters, cache);\n            }\n            svgtypes::FilterValue::Saturate(amount) => {\n                create_base_filter_func(convert_saturate_function(amount), &mut filters, cache);\n            }\n            svgtypes::FilterValue::Url(url) => {\n                if let Some(link) = node.document().element_by_id(url) {\n                    if let Ok(res) = convert_url(link, state, object_bbox, cache) {\n                        if let Some(f) = res {\n                            filters.push(f);\n                        }\n                    } else {\n                        has_invalid_urls = true;\n                    }\n                } else {\n                    has_invalid_urls = true;\n                }\n            }\n        }\n    }\n\n    // If a `filter` attribute had urls pointing to a missing elements\n    // and there are no valid filters at all - this is an error.\n    //\n    // Note that an invalid url is not an error in general.\n    if filters.is_empty() && has_invalid_urls {\n        return Err(());\n    }\n\n    Ok(filters)\n}\n\nfn convert_url(\n    node: SvgNode,\n    state: &converter::State,\n    object_bbox: Option<NonZeroRect>,\n    cache: &mut converter::Cache,\n) -> Result<Option<Arc<Filter>>, ()> {\n    let units = convert_units(node, AId::FilterUnits, Units::ObjectBoundingBox);\n    let primitive_units = convert_units(node, AId::PrimitiveUnits, Units::UserSpaceOnUse);\n\n    // Check if this element was already converted.\n    //\n    // Only `userSpaceOnUse` clipPaths can be shared,\n    // because `objectBoundingBox` one will be converted into user one\n    // and will become node-specific.\n    let cacheable = units == Units::UserSpaceOnUse && primitive_units == Units::UserSpaceOnUse;\n    if cacheable {\n        if let Some(filter) = cache.filters.get(node.element_id()) {\n            return Ok(Some(filter.clone()));\n        }\n    }\n\n    let rect = NonZeroRect::from_xywh(\n        resolve_number(\n            node,\n            AId::X,\n            units,\n            state,\n            Length::new(-10.0, Unit::Percent),\n        ),\n        resolve_number(\n            node,\n            AId::Y,\n            units,\n            state,\n            Length::new(-10.0, Unit::Percent),\n        ),\n        resolve_number(\n            node,\n            AId::Width,\n            units,\n            state,\n            Length::new(120.0, Unit::Percent),\n        ),\n        resolve_number(\n            node,\n            AId::Height,\n            units,\n            state,\n            Length::new(120.0, Unit::Percent),\n        ),\n    );\n\n    let mut rect = rect\n        .log_none(|| {\n            log::warn!(\n                \"Filter '{}' has an invalid region. Skipped.\",\n                node.element_id()\n            );\n        })\n        .ok_or(())?;\n\n    if units == Units::ObjectBoundingBox {\n        if let Some(object_bbox) = object_bbox {\n            rect = rect.bbox_transform(object_bbox);\n        } else {\n            log::warn!(\"Filters on zero-sized shapes are not allowed.\");\n            return Err(());\n        }\n    }\n\n    let node_with_primitives = match find_filter_with_primitives(node) {\n        Some(v) => v,\n        None => return Err(()),\n    };\n    let primitives = collect_children(\n        &node_with_primitives,\n        primitive_units,\n        state,\n        object_bbox,\n        rect,\n        cache,\n    );\n    if primitives.is_empty() {\n        return Err(());\n    }\n\n    let mut id = NonEmptyString::new(node.element_id().to_string()).ok_or(())?;\n    // Generate ID only when we're parsing `objectBoundingBox` filter for the second time.\n    if !cacheable && cache.filters.contains_key(id.get()) {\n        id = cache.gen_filter_id();\n    }\n    let id_copy = id.get().to_string();\n\n    let filter = Arc::new(Filter {\n        id,\n        rect,\n        primitives,\n    });\n\n    cache.filters.insert(id_copy, filter.clone());\n\n    Ok(Some(filter))\n}\n\nfn find_filter_with_primitives<'a>(node: SvgNode<'a, 'a>) -> Option<SvgNode<'a, 'a>> {\n    for link in node.href_iter() {\n        if link.tag_name() != Some(EId::Filter) {\n            log::warn!(\n                \"Filter '{}' cannot reference '{}' via 'xlink:href'.\",\n                node.element_id(),\n                link.tag_name().unwrap()\n            );\n            return None;\n        }\n\n        if link.has_children() {\n            return Some(link);\n        }\n    }\n\n    None\n}\n\nstruct FilterResults {\n    names: HashSet<String>,\n    idx: usize,\n}\n\nfn collect_children(\n    filter: &SvgNode,\n    units: Units,\n    state: &converter::State,\n    object_bbox: Option<NonZeroRect>,\n    filter_region: NonZeroRect,\n    cache: &mut converter::Cache,\n) -> Vec<Primitive> {\n    let mut primitives = Vec::new();\n\n    let mut results = FilterResults {\n        names: HashSet::new(),\n        idx: 1,\n    };\n\n    let scale = if units == Units::ObjectBoundingBox {\n        if let Some(object_bbox) = object_bbox {\n            object_bbox.size()\n        } else {\n            // No need to warn. Already checked.\n            return Vec::new();\n        }\n    } else {\n        Size::from_wh(1.0, 1.0).unwrap()\n    };\n\n    for child in filter.children() {\n        let tag_name = match child.tag_name() {\n            Some(v) => v,\n            None => continue,\n        };\n\n        let filter_subregion = match resolve_primitive_region(\n            child,\n            tag_name,\n            units,\n            state,\n            object_bbox,\n            filter_region,\n        ) {\n            Some(v) => v,\n            None => break,\n        };\n\n        let kind =\n            match tag_name {\n                EId::FeDropShadow => convert_drop_shadow(child, scale, &primitives),\n                EId::FeGaussianBlur => convert_gaussian_blur(child, scale, &primitives),\n                EId::FeOffset => convert_offset(child, scale, &primitives),\n                EId::FeBlend => convert_blend(child, &primitives),\n                EId::FeFlood => convert_flood(child),\n                EId::FeComposite => convert_composite(child, &primitives),\n                EId::FeMerge => convert_merge(child, &primitives),\n                EId::FeTile => convert_tile(child, &primitives),\n                EId::FeImage => convert_image(child, filter_subregion, state, cache),\n                EId::FeComponentTransfer => convert_component_transfer(child, &primitives),\n                EId::FeColorMatrix => convert_color_matrix(child, &primitives),\n                EId::FeConvolveMatrix => convert_convolve_matrix(child, &primitives)\n                    .unwrap_or_else(create_dummy_primitive),\n                EId::FeMorphology => convert_morphology(child, scale, &primitives),\n                EId::FeDisplacementMap => convert_displacement_map(child, scale, &primitives),\n                EId::FeTurbulence => convert_turbulence(child),\n                EId::FeDiffuseLighting => convert_diffuse_lighting(child, &primitives)\n                    .unwrap_or_else(create_dummy_primitive),\n                EId::FeSpecularLighting => convert_specular_lighting(child, &primitives)\n                    .unwrap_or_else(create_dummy_primitive),\n                tag_name => {\n                    log::warn!(\"'{}' is not a valid filter primitive. Skipped.\", tag_name);\n                    continue;\n                }\n            };\n\n        let color_interpolation = child\n            .find_attribute(AId::ColorInterpolationFilters)\n            .unwrap_or_default();\n\n        primitives.push(Primitive {\n            rect: filter_subregion,\n            color_interpolation,\n            result: gen_result(child, &mut results),\n            kind,\n        });\n    }\n\n    // TODO: remove primitives which results are not used\n\n    primitives\n}\n\n// TODO: rewrite/simplify/explain/whatever\nfn resolve_primitive_region(\n    fe: SvgNode,\n    kind: EId,\n    units: Units,\n    state: &converter::State,\n    bbox: Option<NonZeroRect>,\n    filter_region: NonZeroRect,\n) -> Option<NonZeroRect> {\n    let x = fe.try_convert_length(AId::X, units, state);\n    let y = fe.try_convert_length(AId::Y, units, state);\n    let width = fe.try_convert_length(AId::Width, units, state);\n    let height = fe.try_convert_length(AId::Height, units, state);\n\n    let region = match kind {\n        EId::FeFlood | EId::FeImage => {\n            // `feImage` uses the object bbox.\n            if units == Units::ObjectBoundingBox {\n                let bbox = bbox?;\n\n                // TODO: wrong\n                // let ts_bbox = tiny_skia::Rect::new(ts.e, ts.f, ts.a, ts.d).unwrap();\n\n                let r = NonZeroRect::from_xywh(\n                    x.unwrap_or(0.0),\n                    y.unwrap_or(0.0),\n                    width.unwrap_or(1.0),\n                    height.unwrap_or(1.0),\n                )?;\n\n                return Some(r.bbox_transform(bbox));\n            } else {\n                filter_region\n            }\n        }\n        _ => filter_region,\n    };\n\n    // TODO: Wrong! Does not account rotate and skew.\n    if units == Units::ObjectBoundingBox {\n        let subregion_bbox = NonZeroRect::from_xywh(\n            x.unwrap_or(0.0),\n            y.unwrap_or(0.0),\n            width.unwrap_or(1.0),\n            height.unwrap_or(1.0),\n        )?;\n\n        Some(region.bbox_transform(subregion_bbox))\n    } else {\n        NonZeroRect::from_xywh(\n            x.unwrap_or(region.x()),\n            y.unwrap_or(region.y()),\n            width.unwrap_or(region.width()),\n            height.unwrap_or(region.height()),\n        )\n    }\n}\n\n// A malformed filter primitive usually should produce a transparent image.\n// But since `FilterKind` structs are designed to always be valid,\n// we are using `FeFlood` as fallback.\n#[inline(never)]\npub(crate) fn create_dummy_primitive() -> Kind {\n    Kind::Flood(Flood {\n        color: Color::black(),\n        opacity: Opacity::ZERO,\n    })\n}\n\n#[inline(never)]\nfn resolve_input(node: SvgNode, aid: AId, primitives: &[Primitive]) -> Input {\n    match node.attribute(aid) {\n        Some(s) => {\n            let input = parse_in(s);\n\n            // If `in` references an unknown `result` than fallback\n            // to previous result or `SourceGraphic`.\n            if let Input::Reference(ref name) = input {\n                if !primitives.iter().any(|p| p.result == *name) {\n                    return if let Some(prev) = primitives.last() {\n                        Input::Reference(prev.result.clone())\n                    } else {\n                        Input::SourceGraphic\n                    };\n                }\n            }\n\n            input\n        }\n        None => {\n            if let Some(prev) = primitives.last() {\n                // If `in` is not set and this is not the first primitive\n                // than the input is a result of the previous primitive.\n                Input::Reference(prev.result.clone())\n            } else {\n                // If `in` is not set and this is the first primitive\n                // than the input is `SourceGraphic`.\n                Input::SourceGraphic\n            }\n        }\n    }\n}\n\nfn parse_in(s: &str) -> Input {\n    match s {\n        \"SourceGraphic\" => Input::SourceGraphic,\n        \"SourceAlpha\" => Input::SourceAlpha,\n        \"BackgroundImage\" | \"BackgroundAlpha\" | \"FillPaint\" | \"StrokePaint\" => {\n            log::warn!(\"{} filter input isn't supported and not planed.\", s);\n            Input::SourceGraphic\n        }\n        _ => Input::Reference(s.to_string()),\n    }\n}\n\nfn gen_result(node: SvgNode, results: &mut FilterResults) -> String {\n    match node.attribute::<&str>(AId::Result) {\n        Some(s) => {\n            // Remember predefined result.\n            results.names.insert(s.to_string());\n            results.idx += 1;\n\n            s.to_string()\n        }\n        None => {\n            // Generate an unique name for `result`.\n            loop {\n                let name = format!(\"result{}\", results.idx);\n                results.idx += 1;\n\n                if !results.names.contains(&name) {\n                    return name;\n                }\n            }\n        }\n    }\n}\n\nfn convert_blend(fe: SvgNode, primitives: &[Primitive]) -> Kind {\n    let mode = fe.attribute(AId::Mode).unwrap_or_default();\n    let input1 = resolve_input(fe, AId::In, primitives);\n    let input2 = resolve_input(fe, AId::In2, primitives);\n    Kind::Blend(Blend {\n        mode,\n        input1,\n        input2,\n    })\n}\n\nfn convert_color_matrix(fe: SvgNode, primitives: &[Primitive]) -> Kind {\n    let kind = convert_color_matrix_kind(fe).unwrap_or_default();\n    Kind::ColorMatrix(ColorMatrix {\n        input: resolve_input(fe, AId::In, primitives),\n        kind,\n    })\n}\n\nfn convert_color_matrix_kind(fe: SvgNode) -> Option<ColorMatrixKind> {\n    match fe.attribute(AId::Type) {\n        Some(\"saturate\") => {\n            if let Some(list) = fe.attribute::<Vec<f32>>(AId::Values) {\n                if !list.is_empty() {\n                    let n = crate::f32_bound(0.0, list[0], 1.0);\n                    return Some(ColorMatrixKind::Saturate(PositiveF32::new(n).unwrap()));\n                } else {\n                    return Some(ColorMatrixKind::Saturate(PositiveF32::new(1.0).unwrap()));\n                }\n            }\n        }\n        Some(\"hueRotate\") => {\n            if let Some(list) = fe.attribute::<Vec<f32>>(AId::Values) {\n                if !list.is_empty() {\n                    return Some(ColorMatrixKind::HueRotate(list[0]));\n                } else {\n                    return Some(ColorMatrixKind::HueRotate(0.0));\n                }\n            }\n        }\n        Some(\"luminanceToAlpha\") => {\n            return Some(ColorMatrixKind::LuminanceToAlpha);\n        }\n        _ => {\n            // Fallback to `matrix`.\n            if let Some(list) = fe.attribute::<Vec<f32>>(AId::Values) {\n                if list.len() == 20 {\n                    return Some(ColorMatrixKind::Matrix(list));\n                }\n            }\n        }\n    }\n\n    None\n}\n\nfn convert_component_transfer(fe: SvgNode, primitives: &[Primitive]) -> Kind {\n    let mut kind = ComponentTransfer {\n        input: resolve_input(fe, AId::In, primitives),\n        func_r: TransferFunction::Identity,\n        func_g: TransferFunction::Identity,\n        func_b: TransferFunction::Identity,\n        func_a: TransferFunction::Identity,\n    };\n\n    for child in fe.children().filter(|n| n.is_element()) {\n        if let Some(func) = convert_transfer_function(child) {\n            match child.tag_name().unwrap() {\n                EId::FeFuncR => kind.func_r = func,\n                EId::FeFuncG => kind.func_g = func,\n                EId::FeFuncB => kind.func_b = func,\n                EId::FeFuncA => kind.func_a = func,\n                _ => {}\n            }\n        }\n    }\n\n    Kind::ComponentTransfer(kind)\n}\n\nfn convert_transfer_function(node: SvgNode) -> Option<TransferFunction> {\n    match node.attribute(AId::Type)? {\n        \"identity\" => Some(TransferFunction::Identity),\n        \"table\" => match node.attribute::<Vec<f32>>(AId::TableValues) {\n            Some(values) => Some(TransferFunction::Table(values)),\n            None => Some(TransferFunction::Table(Vec::new())),\n        },\n        \"discrete\" => match node.attribute::<Vec<f32>>(AId::TableValues) {\n            Some(values) => Some(TransferFunction::Discrete(values)),\n            None => Some(TransferFunction::Discrete(Vec::new())),\n        },\n        \"linear\" => Some(TransferFunction::Linear {\n            slope: node.attribute(AId::Slope).unwrap_or(1.0),\n            intercept: node.attribute(AId::Intercept).unwrap_or(0.0),\n        }),\n        \"gamma\" => Some(TransferFunction::Gamma {\n            amplitude: node.attribute(AId::Amplitude).unwrap_or(1.0),\n            exponent: node.attribute(AId::Exponent).unwrap_or(1.0),\n            offset: node.attribute(AId::Offset).unwrap_or(0.0),\n        }),\n        _ => None,\n    }\n}\n\nfn convert_composite(fe: SvgNode, primitives: &[Primitive]) -> Kind {\n    let operator = match fe.attribute(AId::Operator).unwrap_or(\"over\") {\n        \"in\" => CompositeOperator::In,\n        \"out\" => CompositeOperator::Out,\n        \"atop\" => CompositeOperator::Atop,\n        \"xor\" => CompositeOperator::Xor,\n        \"arithmetic\" => CompositeOperator::Arithmetic {\n            k1: fe.attribute(AId::K1).unwrap_or(0.0),\n            k2: fe.attribute(AId::K2).unwrap_or(0.0),\n            k3: fe.attribute(AId::K3).unwrap_or(0.0),\n            k4: fe.attribute(AId::K4).unwrap_or(0.0),\n        },\n        _ => CompositeOperator::Over,\n    };\n\n    let input1 = resolve_input(fe, AId::In, primitives);\n    let input2 = resolve_input(fe, AId::In2, primitives);\n\n    Kind::Composite(Composite {\n        operator,\n        input1,\n        input2,\n    })\n}\n\nfn convert_convolve_matrix(fe: SvgNode, primitives: &[Primitive]) -> Option<Kind> {\n    fn parse_target(target: Option<f32>, order: u32) -> Option<u32> {\n        let default_target = (order as f32 / 2.0).floor() as u32;\n        let target = target.unwrap_or(default_target as f32) as i32;\n        if target < 0 || target >= order as i32 {\n            None\n        } else {\n            Some(target as u32)\n        }\n    }\n\n    let mut order_x = 3;\n    let mut order_y = 3;\n    if let Some(value) = fe.attribute::<&str>(AId::Order) {\n        let mut s = svgtypes::NumberListParser::from(value);\n        let x = s.next().and_then(|a| a.ok()).map(|n| n as i32).unwrap_or(3);\n        let y = s.next().and_then(|a| a.ok()).map(|n| n as i32).unwrap_or(x);\n        if x > 0 && y > 0 {\n            order_x = x as u32;\n            order_y = y as u32;\n        }\n    }\n\n    let mut matrix = Vec::new();\n    if let Some(list) = fe.attribute::<Vec<f32>>(AId::KernelMatrix) {\n        if list.len() == (order_x * order_y) as usize {\n            matrix = list;\n        }\n    }\n\n    let mut kernel_sum: f32 = matrix.iter().sum();\n    // Round up to prevent float precision issues.\n    kernel_sum = (kernel_sum * 1_000_000.0).round() / 1_000_000.0;\n    if kernel_sum.approx_zero_ulps(4) {\n        kernel_sum = 1.0;\n    }\n\n    let divisor = fe.attribute(AId::Divisor).unwrap_or(kernel_sum);\n    if divisor.approx_zero_ulps(4) {\n        return None;\n    }\n\n    let bias = fe.attribute(AId::Bias).unwrap_or(0.0);\n\n    let target_x = parse_target(fe.attribute(AId::TargetX), order_x)?;\n    let target_y = parse_target(fe.attribute(AId::TargetY), order_y)?;\n\n    let kernel_matrix = ConvolveMatrixData::new(target_x, target_y, order_x, order_y, matrix)?;\n\n    let edge_mode = match fe.attribute(AId::EdgeMode).unwrap_or(\"duplicate\") {\n        \"none\" => EdgeMode::None,\n        \"wrap\" => EdgeMode::Wrap,\n        _ => EdgeMode::Duplicate,\n    };\n\n    let preserve_alpha = fe.attribute(AId::PreserveAlpha).unwrap_or(\"false\") == \"true\";\n\n    Some(Kind::ConvolveMatrix(ConvolveMatrix {\n        input: resolve_input(fe, AId::In, primitives),\n        matrix: kernel_matrix,\n        divisor: NonZeroF32::new(divisor).unwrap(),\n        bias,\n        edge_mode,\n        preserve_alpha,\n    }))\n}\n\nfn convert_displacement_map(fe: SvgNode, scale: Size, primitives: &[Primitive]) -> Kind {\n    let parse_channel = |aid| match fe.attribute(aid).unwrap_or(\"A\") {\n        \"R\" => ColorChannel::R,\n        \"G\" => ColorChannel::G,\n        \"B\" => ColorChannel::B,\n        _ => ColorChannel::A,\n    };\n\n    // TODO: should probably split scale to scale_x and scale_y,\n    //       but resvg doesn't support displacement map anyway...\n    let scale = (scale.width() + scale.height()) / 2.0;\n\n    Kind::DisplacementMap(DisplacementMap {\n        input1: resolve_input(fe, AId::In, primitives),\n        input2: resolve_input(fe, AId::In2, primitives),\n        scale: fe.attribute(AId::Scale).unwrap_or(0.0) * scale,\n        x_channel_selector: parse_channel(AId::XChannelSelector),\n        y_channel_selector: parse_channel(AId::YChannelSelector),\n    })\n}\n\nfn convert_drop_shadow(fe: SvgNode, scale: Size, primitives: &[Primitive]) -> Kind {\n    let (std_dev_x, std_dev_y) = convert_std_dev_attr(fe, scale, \"2 2\");\n\n    let (color, opacity) = fe\n        .attribute(AId::FloodColor)\n        .unwrap_or_else(svgtypes::Color::black)\n        .split_alpha();\n\n    let flood_opacity = fe\n        .attribute::<Opacity>(AId::FloodOpacity)\n        .unwrap_or(Opacity::ONE);\n\n    Kind::DropShadow(DropShadow {\n        input: resolve_input(fe, AId::In, primitives),\n        dx: fe.attribute(AId::Dx).unwrap_or(2.0) * scale.width(),\n        dy: fe.attribute(AId::Dy).unwrap_or(2.0) * scale.height(),\n        std_dev_x,\n        std_dev_y,\n        color,\n        opacity: opacity * flood_opacity,\n    })\n}\n\nfn convert_flood(fe: SvgNode) -> Kind {\n    let (color, opacity) = fe\n        .attribute(AId::FloodColor)\n        .unwrap_or_else(svgtypes::Color::black)\n        .split_alpha();\n\n    let flood_opacity = fe\n        .attribute::<Opacity>(AId::FloodOpacity)\n        .unwrap_or(Opacity::ONE);\n\n    Kind::Flood(Flood {\n        color,\n        opacity: opacity * flood_opacity,\n    })\n}\n\nfn convert_gaussian_blur(fe: SvgNode, scale: Size, primitives: &[Primitive]) -> Kind {\n    let (std_dev_x, std_dev_y) = convert_std_dev_attr(fe, scale, \"0 0\");\n    Kind::GaussianBlur(GaussianBlur {\n        input: resolve_input(fe, AId::In, primitives),\n        std_dev_x,\n        std_dev_y,\n    })\n}\n\nfn convert_std_dev_attr(fe: SvgNode, scale: Size, default: &str) -> (PositiveF32, PositiveF32) {\n    let text = fe.attribute(AId::StdDeviation).unwrap_or(default);\n    let mut parser = svgtypes::NumberListParser::from(text);\n\n    let n1 = parser.next().and_then(|n| n.ok());\n    let n2 = parser.next().and_then(|n| n.ok());\n    // `stdDeviation` must have no more than two values.\n    // Otherwise we should fallback to `0 0`.\n    let n3 = parser.next().and_then(|n| n.ok());\n\n    let (std_dev_x, std_dev_y) = match (n1, n2, n3) {\n        (Some(n1), Some(n2), None) => (n1, n2),\n        (Some(n1), None, None) => (n1, n1),\n        _ => (0.0, 0.0),\n    };\n\n    let std_dev_x = (std_dev_x as f32) * scale.width();\n    let std_dev_y = (std_dev_y as f32) * scale.height();\n\n    let std_dev_x = PositiveF32::new(std_dev_x).unwrap_or(PositiveF32::ZERO);\n    let std_dev_y = PositiveF32::new(std_dev_y).unwrap_or(PositiveF32::ZERO);\n\n    (std_dev_x, std_dev_y)\n}\n\nfn convert_image(\n    fe: SvgNode,\n    filter_subregion: NonZeroRect,\n    state: &converter::State,\n    cache: &mut converter::Cache,\n) -> Kind {\n    match convert_image_inner(fe, filter_subregion, state, cache) {\n        Some(kind) => kind,\n        None => create_dummy_primitive(),\n    }\n}\n\nfn convert_image_inner(\n    fe: SvgNode,\n    filter_subregion: NonZeroRect,\n    state: &converter::State,\n    cache: &mut converter::Cache,\n) -> Option<Kind> {\n    let rendering_mode = fe\n        .find_attribute(AId::ImageRendering)\n        .unwrap_or(state.opt.image_rendering);\n\n    if let Some(node) = fe.try_attribute::<SvgNode>(AId::Href) {\n        let mut state = state.clone();\n        state.fe_image_link = true;\n        let mut root = Group::empty();\n        super::converter::convert_element(node, &state, cache, &mut root);\n        return if root.has_children() {\n            root.calculate_bounding_boxes();\n            // Transfer node id from group's child to the group itself if needed.\n            if let Some(Node::Group(g)) = root.children.first_mut() {\n                if let Some(child2) = g.children.first_mut() {\n                    g.id = child2.id().to_string();\n                    match child2 {\n                        Node::Group(g2) => g2.id.clear(),\n                        Node::Path(path) => path.id.clear(),\n                        Node::Image(image) => image.id.clear(),\n                        Node::Text(text) => text.id.clear(),\n                    }\n                }\n            }\n\n            Some(Kind::Image(Image { root }))\n        } else {\n            None\n        };\n    }\n\n    let href = fe.try_attribute(AId::Href).log_none(|| {\n        log::warn!(\"The 'feImage' element lacks the 'xlink:href' attribute. Skipped.\");\n    })?;\n    let img_data = super::image::get_href_data(href, state)?;\n    let actual_size = img_data.actual_size()?;\n\n    let aspect: AspectRatio = fe.attribute(AId::PreserveAspectRatio).unwrap_or_default();\n\n    let mut root = Group::empty();\n    super::image::convert_inner(\n        img_data,\n        cache.gen_image_id().take(),\n        true,\n        rendering_mode,\n        aspect,\n        actual_size,\n        filter_subregion.translate_to(0.0, 0.0)?,\n        cache,\n        &mut root,\n    );\n    root.calculate_bounding_boxes();\n\n    Some(Kind::Image(Image { root }))\n}\n\nfn convert_diffuse_lighting(fe: SvgNode, primitives: &[Primitive]) -> Option<Kind> {\n    let light_source = convert_light_source(fe)?;\n    Some(Kind::DiffuseLighting(DiffuseLighting {\n        input: resolve_input(fe, AId::In, primitives),\n        surface_scale: fe.attribute(AId::SurfaceScale).unwrap_or(1.0),\n        diffuse_constant: fe.attribute(AId::DiffuseConstant).unwrap_or(1.0),\n        lighting_color: convert_lighting_color(fe),\n        light_source,\n    }))\n}\n\nfn convert_specular_lighting(fe: SvgNode, primitives: &[Primitive]) -> Option<Kind> {\n    let light_source = convert_light_source(fe)?;\n\n    let specular_exponent = fe.attribute(AId::SpecularExponent).unwrap_or(1.0);\n    if !(1.0..=128.0).contains(&specular_exponent) {\n        // When exponent is out of range, the whole filter primitive should be ignored.\n        return None;\n    }\n\n    let specular_exponent = crate::f32_bound(1.0, specular_exponent, 128.0);\n\n    Some(Kind::SpecularLighting(SpecularLighting {\n        input: resolve_input(fe, AId::In, primitives),\n        surface_scale: fe.attribute(AId::SurfaceScale).unwrap_or(1.0),\n        specular_constant: fe.attribute(AId::SpecularConstant).unwrap_or(1.0),\n        specular_exponent,\n        lighting_color: convert_lighting_color(fe),\n        light_source,\n    }))\n}\n\n#[inline(never)]\nfn convert_lighting_color(node: SvgNode) -> Color {\n    // Color's alpha doesn't affect lighting-color. Simply skip it.\n    match node.attribute(AId::LightingColor) {\n        Some(\"currentColor\") => {\n            node.find_attribute(AId::Color)\n                // Yes, a missing `currentColor` resolves to black and not white.\n                .unwrap_or(svgtypes::Color::black())\n                .split_alpha()\n                .0\n        }\n        Some(value) => {\n            if let Ok(c) = svgtypes::Color::from_str(value) {\n                c.split_alpha().0\n            } else {\n                log::warn!(\"Failed to parse lighting-color value: '{}'.\", value);\n                Color::white()\n            }\n        }\n        _ => Color::white(),\n    }\n}\n\n#[inline(never)]\nfn convert_light_source(parent: SvgNode) -> Option<LightSource> {\n    let child = parent.children().find(|n| {\n        matches!(\n            n.tag_name(),\n            Some(EId::FeDistantLight) | Some(EId::FePointLight) | Some(EId::FeSpotLight)\n        )\n    })?;\n\n    match child.tag_name() {\n        Some(EId::FeDistantLight) => Some(LightSource::DistantLight(DistantLight {\n            azimuth: child.attribute(AId::Azimuth).unwrap_or(0.0),\n            elevation: child.attribute(AId::Elevation).unwrap_or(0.0),\n        })),\n        Some(EId::FePointLight) => Some(LightSource::PointLight(PointLight {\n            x: child.attribute(AId::X).unwrap_or(0.0),\n            y: child.attribute(AId::Y).unwrap_or(0.0),\n            z: child.attribute(AId::Z).unwrap_or(0.0),\n        })),\n        Some(EId::FeSpotLight) => {\n            let specular_exponent = child.attribute(AId::SpecularExponent).unwrap_or(1.0);\n            let specular_exponent = PositiveF32::new(specular_exponent)\n                .unwrap_or_else(|| PositiveF32::new(1.0).unwrap());\n\n            Some(LightSource::SpotLight(SpotLight {\n                x: child.attribute(AId::X).unwrap_or(0.0),\n                y: child.attribute(AId::Y).unwrap_or(0.0),\n                z: child.attribute(AId::Z).unwrap_or(0.0),\n                points_at_x: child.attribute(AId::PointsAtX).unwrap_or(0.0),\n                points_at_y: child.attribute(AId::PointsAtY).unwrap_or(0.0),\n                points_at_z: child.attribute(AId::PointsAtZ).unwrap_or(0.0),\n                specular_exponent,\n                limiting_cone_angle: child.attribute(AId::LimitingConeAngle),\n            }))\n        }\n        _ => None,\n    }\n}\n\nfn convert_merge(fe: SvgNode, primitives: &[Primitive]) -> Kind {\n    let mut inputs = Vec::new();\n    for child in fe.children() {\n        inputs.push(resolve_input(child, AId::In, primitives));\n    }\n\n    Kind::Merge(Merge { inputs })\n}\n\nfn convert_morphology(fe: SvgNode, scale: Size, primitives: &[Primitive]) -> Kind {\n    let operator = match fe.attribute(AId::Operator).unwrap_or(\"erode\") {\n        \"dilate\" => MorphologyOperator::Dilate,\n        _ => MorphologyOperator::Erode,\n    };\n\n    let mut radius_x = PositiveF32::new(scale.width()).unwrap();\n    let mut radius_y = PositiveF32::new(scale.height()).unwrap();\n    if let Some(list) = fe.attribute::<Vec<f32>>(AId::Radius) {\n        let mut rx = 0.0;\n        let mut ry = 0.0;\n        if list.len() == 2 {\n            rx = list[0];\n            ry = list[1];\n        } else if list.len() == 1 {\n            rx = list[0];\n            ry = list[0]; // The same as `rx`.\n        }\n\n        if rx.approx_zero_ulps(4) && ry.approx_zero_ulps(4) {\n            rx = 1.0;\n            ry = 1.0;\n        }\n\n        // If only one of the values is zero, reset it to 1.0\n        // This is not specified in the spec, but this is how Chrome and Safari work.\n        if rx.approx_zero_ulps(4) && !ry.approx_zero_ulps(4) {\n            rx = 1.0;\n        }\n        if !rx.approx_zero_ulps(4) && ry.approx_zero_ulps(4) {\n            ry = 1.0;\n        }\n\n        // Both values must be positive.\n        if rx.is_sign_positive() && ry.is_sign_positive() {\n            radius_x = PositiveF32::new(rx * scale.width()).unwrap();\n            radius_y = PositiveF32::new(ry * scale.height()).unwrap();\n        }\n    }\n\n    Kind::Morphology(Morphology {\n        input: resolve_input(fe, AId::In, primitives),\n        operator,\n        radius_x,\n        radius_y,\n    })\n}\n\nfn convert_offset(fe: SvgNode, scale: Size, primitives: &[Primitive]) -> Kind {\n    Kind::Offset(Offset {\n        input: resolve_input(fe, AId::In, primitives),\n        dx: fe.attribute(AId::Dx).unwrap_or(0.0) * scale.width(),\n        dy: fe.attribute(AId::Dy).unwrap_or(0.0) * scale.height(),\n    })\n}\n\nfn convert_tile(fe: SvgNode, primitives: &[Primitive]) -> Kind {\n    Kind::Tile(Tile {\n        input: resolve_input(fe, AId::In, primitives),\n    })\n}\n\nfn convert_turbulence(fe: SvgNode) -> Kind {\n    let mut base_frequency_x = PositiveF32::ZERO;\n    let mut base_frequency_y = PositiveF32::ZERO;\n    if let Some(list) = fe.attribute::<Vec<f32>>(AId::BaseFrequency) {\n        let mut x = 0.0;\n        let mut y = 0.0;\n        if list.len() == 2 {\n            x = list[0];\n            y = list[1];\n        } else if list.len() == 1 {\n            x = list[0];\n            y = list[0]; // The same as `x`.\n        }\n\n        if x.is_sign_positive() && y.is_sign_positive() {\n            base_frequency_x = PositiveF32::new(x).unwrap();\n            base_frequency_y = PositiveF32::new(y).unwrap();\n        }\n    }\n\n    let mut num_octaves = fe.attribute(AId::NumOctaves).unwrap_or(1.0);\n    if num_octaves.is_sign_negative() {\n        num_octaves = 0.0;\n    }\n\n    let kind = match fe.attribute(AId::Type).unwrap_or(\"turbulence\") {\n        \"fractalNoise\" => TurbulenceKind::FractalNoise,\n        _ => TurbulenceKind::Turbulence,\n    };\n\n    Kind::Turbulence(Turbulence {\n        base_frequency_x,\n        base_frequency_y,\n        num_octaves: num_octaves.round() as u32,\n        seed: fe.attribute::<f32>(AId::Seed).unwrap_or(0.0).trunc() as i32,\n        stitch_tiles: fe.attribute(AId::StitchTiles) == Some(\"stitch\"),\n        kind,\n    })\n}\n\n#[inline(never)]\nfn convert_grayscale_function(amount: f64) -> Kind {\n    let amount = amount.min(1.0) as f32;\n    Kind::ColorMatrix(ColorMatrix {\n        input: Input::SourceGraphic,\n        kind: ColorMatrixKind::Matrix(vec![\n            (0.2126 + 0.7874 * (1.0 - amount)),\n            (0.7152 - 0.7152 * (1.0 - amount)),\n            (0.0722 - 0.0722 * (1.0 - amount)),\n            0.0,\n            0.0,\n            (0.2126 - 0.2126 * (1.0 - amount)),\n            (0.7152 + 0.2848 * (1.0 - amount)),\n            (0.0722 - 0.0722 * (1.0 - amount)),\n            0.0,\n            0.0,\n            (0.2126 - 0.2126 * (1.0 - amount)),\n            (0.7152 - 0.7152 * (1.0 - amount)),\n            (0.0722 + 0.9278 * (1.0 - amount)),\n            0.0,\n            0.0,\n            0.0,\n            0.0,\n            0.0,\n            1.0,\n            0.0,\n        ]),\n    })\n}\n\n#[inline(never)]\nfn convert_sepia_function(amount: f64) -> Kind {\n    let amount = amount.min(1.0) as f32;\n    Kind::ColorMatrix(ColorMatrix {\n        input: Input::SourceGraphic,\n        kind: ColorMatrixKind::Matrix(vec![\n            (0.393 + 0.607 * (1.0 - amount)),\n            (0.769 - 0.769 * (1.0 - amount)),\n            (0.189 - 0.189 * (1.0 - amount)),\n            0.0,\n            0.0,\n            (0.349 - 0.349 * (1.0 - amount)),\n            (0.686 + 0.314 * (1.0 - amount)),\n            (0.168 - 0.168 * (1.0 - amount)),\n            0.0,\n            0.0,\n            (0.272 - 0.272 * (1.0 - amount)),\n            (0.534 - 0.534 * (1.0 - amount)),\n            (0.131 + 0.869 * (1.0 - amount)),\n            0.0,\n            0.0,\n            0.0,\n            0.0,\n            0.0,\n            1.0,\n            0.0,\n        ]),\n    })\n}\n\n#[inline(never)]\nfn convert_saturate_function(amount: f64) -> Kind {\n    let amount = PositiveF32::new(amount as f32).unwrap_or(PositiveF32::ZERO);\n    Kind::ColorMatrix(ColorMatrix {\n        input: Input::SourceGraphic,\n        kind: ColorMatrixKind::Saturate(amount),\n    })\n}\n\n#[inline(never)]\nfn convert_hue_rotate_function(amount: svgtypes::Angle) -> Kind {\n    Kind::ColorMatrix(ColorMatrix {\n        input: Input::SourceGraphic,\n        kind: ColorMatrixKind::HueRotate(amount.to_degrees() as f32),\n    })\n}\n\n#[inline(never)]\nfn convert_invert_function(amount: f64) -> Kind {\n    let amount = amount.min(1.0) as f32;\n    Kind::ComponentTransfer(ComponentTransfer {\n        input: Input::SourceGraphic,\n        func_r: TransferFunction::Table(vec![amount, 1.0 - amount]),\n        func_g: TransferFunction::Table(vec![amount, 1.0 - amount]),\n        func_b: TransferFunction::Table(vec![amount, 1.0 - amount]),\n        func_a: TransferFunction::Identity,\n    })\n}\n\n#[inline(never)]\nfn convert_opacity_function(amount: f64) -> Kind {\n    let amount = amount.min(1.0) as f32;\n    Kind::ComponentTransfer(ComponentTransfer {\n        input: Input::SourceGraphic,\n        func_r: TransferFunction::Identity,\n        func_g: TransferFunction::Identity,\n        func_b: TransferFunction::Identity,\n        func_a: TransferFunction::Table(vec![0.0, amount]),\n    })\n}\n\n#[inline(never)]\nfn convert_brightness_function(amount: f64) -> Kind {\n    let amount = amount as f32;\n    Kind::ComponentTransfer(ComponentTransfer {\n        input: Input::SourceGraphic,\n        func_r: TransferFunction::Linear {\n            slope: amount,\n            intercept: 0.0,\n        },\n        func_g: TransferFunction::Linear {\n            slope: amount,\n            intercept: 0.0,\n        },\n        func_b: TransferFunction::Linear {\n            slope: amount,\n            intercept: 0.0,\n        },\n        func_a: TransferFunction::Identity,\n    })\n}\n\n#[inline(never)]\nfn convert_contrast_function(amount: f64) -> Kind {\n    let amount = amount as f32;\n    Kind::ComponentTransfer(ComponentTransfer {\n        input: Input::SourceGraphic,\n        func_r: TransferFunction::Linear {\n            slope: amount,\n            intercept: -(0.5 * amount) + 0.5,\n        },\n        func_g: TransferFunction::Linear {\n            slope: amount,\n            intercept: -(0.5 * amount) + 0.5,\n        },\n        func_b: TransferFunction::Linear {\n            slope: amount,\n            intercept: -(0.5 * amount) + 0.5,\n        },\n        func_a: TransferFunction::Identity,\n    })\n}\n\n#[inline(never)]\nfn convert_blur_function(node: SvgNode, std_dev: Length, state: &converter::State) -> Kind {\n    let std_dev = PositiveF32::new(super::units::convert_user_length(\n        std_dev,\n        node,\n        AId::Dx,\n        state,\n    ))\n    .unwrap_or(PositiveF32::ZERO);\n    Kind::GaussianBlur(GaussianBlur {\n        input: Input::SourceGraphic,\n        std_dev_x: std_dev,\n        std_dev_y: std_dev,\n    })\n}\n\n#[inline(never)]\nfn convert_drop_shadow_function(\n    node: SvgNode,\n    color: Option<svgtypes::Color>,\n    dx: Length,\n    dy: Length,\n    std_dev: Length,\n    state: &converter::State,\n) -> Kind {\n    let std_dev = PositiveF32::new(super::units::convert_user_length(\n        std_dev,\n        node,\n        AId::Dx,\n        state,\n    ))\n    .unwrap_or(PositiveF32::ZERO);\n\n    let (color, opacity) = color\n        .unwrap_or_else(|| {\n            node.find_attribute(AId::Color)\n                .unwrap_or_else(svgtypes::Color::black)\n        })\n        .split_alpha();\n\n    Kind::DropShadow(DropShadow {\n        input: Input::SourceGraphic,\n        dx: super::units::convert_user_length(dx, node, AId::Dx, state),\n        dy: super::units::convert_user_length(dy, node, AId::Dy, state),\n        std_dev_x: std_dev,\n        std_dev_y: std_dev,\n        color,\n        opacity,\n    })\n}\n"
  },
  {
    "path": "crates/usvg/src/parser/image.rs",
    "content": "// Copyright 2018 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse std::sync::Arc;\n\nuse svgtypes::{AspectRatio, Length};\n\nuse super::svgtree::{AId, SvgNode};\nuse super::{OptionLog, Options, converter};\nuse crate::{\n    ClipPath, Group, Image, ImageKind, ImageRendering, Node, NonZeroRect, Path, Size, Transform,\n    Tree, Visibility,\n};\n\n/// A shorthand for [ImageHrefResolver]'s data function.\npub type ImageHrefDataResolverFn<'a> =\n    Box<dyn Fn(&str, Arc<Vec<u8>>, &Options) -> Option<ImageKind> + Send + Sync + 'a>;\n\n/// A shorthand for [ImageHrefResolver]'s string function.\npub type ImageHrefStringResolverFn<'a> =\n    Box<dyn Fn(&str, &Options) -> Option<ImageKind> + Send + Sync + 'a>;\n\n/// An `xlink:href` resolver for `<image>` elements.\n///\n/// This type can be useful if you want to have an alternative `xlink:href` handling\n/// to the default one. For example, you can forbid access to local files (which is allowed by default)\n/// or add support for resolving actual URLs (usvg doesn't do any network requests).\npub struct ImageHrefResolver<'a> {\n    /// Resolver function that will be used when `xlink:href` contains a\n    /// [Data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs).\n    ///\n    /// A function would be called with mime, decoded base64 data and parsing options.\n    pub resolve_data: ImageHrefDataResolverFn<'a>,\n\n    /// Resolver function that will be used to handle an arbitrary string in `xlink:href`.\n    pub resolve_string: ImageHrefStringResolverFn<'a>,\n}\n\nimpl Default for ImageHrefResolver<'_> {\n    fn default() -> Self {\n        ImageHrefResolver {\n            resolve_data: ImageHrefResolver::default_data_resolver(),\n            resolve_string: ImageHrefResolver::default_string_resolver(),\n        }\n    }\n}\n\nimpl ImageHrefResolver<'_> {\n    /// Creates a default\n    /// [Data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs)\n    /// resolver closure.\n    ///\n    /// base64 encoded data is already decoded.\n    ///\n    /// The default implementation would try to load JPEG, PNG, GIF, WebP, SVG and SVGZ types.\n    /// Note that it will simply match the `mime` or data's magic.\n    /// The actual images would not be decoded. It's up to the renderer.\n    pub fn default_data_resolver() -> ImageHrefDataResolverFn<'static> {\n        Box::new(\n            move |mime: &str, data: Arc<Vec<u8>>, opts: &Options| match mime {\n                \"image/jpg\" | \"image/jpeg\" => Some(ImageKind::JPEG(data)),\n                \"image/png\" => Some(ImageKind::PNG(data)),\n                \"image/gif\" => Some(ImageKind::GIF(data)),\n                \"image/webp\" => Some(ImageKind::WEBP(data)),\n                \"image/svg+xml\" => load_sub_svg(&data, opts),\n                \"text/plain\" => match get_image_data_format(&data) {\n                    Some(ImageFormat::JPEG) => Some(ImageKind::JPEG(data)),\n                    Some(ImageFormat::PNG) => Some(ImageKind::PNG(data)),\n                    Some(ImageFormat::GIF) => Some(ImageKind::GIF(data)),\n                    Some(ImageFormat::WEBP) => Some(ImageKind::WEBP(data)),\n                    _ => load_sub_svg(&data, opts),\n                },\n                _ => None,\n            },\n        )\n    }\n\n    /// Creates a default string resolver.\n    ///\n    /// The default implementation treats an input string as a file path and tries to open.\n    /// If a string is an URL or something else it would be ignored.\n    ///\n    /// Paths have to be absolute or relative to the input SVG file or relative to\n    /// [Options::resources_dir](crate::Options::resources_dir).\n    pub fn default_string_resolver() -> ImageHrefStringResolverFn<'static> {\n        Box::new(move |href: &str, opts: &Options| {\n            let path = opts.get_abs_path(std::path::Path::new(href));\n\n            if path.exists() {\n                let data = match std::fs::read(&path) {\n                    Ok(data) => data,\n                    Err(_) => {\n                        log::warn!(\"Failed to load '{}'. Skipped.\", href);\n                        return None;\n                    }\n                };\n\n                match get_image_file_format(&path, &data) {\n                    Some(ImageFormat::JPEG) => Some(ImageKind::JPEG(Arc::new(data))),\n                    Some(ImageFormat::PNG) => Some(ImageKind::PNG(Arc::new(data))),\n                    Some(ImageFormat::GIF) => Some(ImageKind::GIF(Arc::new(data))),\n                    Some(ImageFormat::WEBP) => Some(ImageKind::WEBP(Arc::new(data))),\n                    Some(ImageFormat::SVG) => load_sub_svg(&data, opts),\n                    _ => {\n                        log::warn!(\"'{}' is not a PNG, JPEG, GIF, WebP or SVG(Z) image.\", href);\n                        None\n                    }\n                }\n            } else {\n                log::warn!(\"'{}' is not a path to an image.\", href);\n                None\n            }\n        })\n    }\n}\n\nimpl std::fmt::Debug for ImageHrefResolver<'_> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.write_str(\"ImageHrefResolver { .. }\")\n    }\n}\n\n#[derive(Clone, Copy, PartialEq, Debug)]\nenum ImageFormat {\n    PNG,\n    JPEG,\n    GIF,\n    WEBP,\n    SVG,\n}\n\npub(crate) fn convert(\n    node: SvgNode,\n    state: &converter::State,\n    cache: &mut converter::Cache,\n    parent: &mut Group,\n) -> Option<()> {\n    let href = node\n        .try_attribute(AId::Href)\n        .log_none(|| log::warn!(\"Image lacks the 'xlink:href' attribute. Skipped.\"))?;\n\n    let kind = get_href_data(href, state)?;\n\n    let visibility: Visibility = node.find_attribute(AId::Visibility).unwrap_or_default();\n    let visible = visibility == Visibility::Visible;\n\n    let rendering_mode = node\n        .find_attribute(AId::ImageRendering)\n        .unwrap_or(state.opt.image_rendering);\n\n    // Nodes generated by markers must not have an ID. Otherwise we would have duplicates.\n    let id = if state.parent_markers.is_empty() {\n        node.element_id().to_string()\n    } else {\n        String::new()\n    };\n\n    let actual_size = kind.actual_size()?;\n\n    let x = node.convert_user_length(AId::X, state, Length::zero());\n    let y = node.convert_user_length(AId::Y, state, Length::zero());\n    let mut width = node.convert_user_length(\n        AId::Width,\n        state,\n        Length::new_number(actual_size.width() as f64),\n    );\n    let mut height = node.convert_user_length(\n        AId::Height,\n        state,\n        Length::new_number(actual_size.height() as f64),\n    );\n\n    match (\n        node.attribute::<Length>(AId::Width),\n        node.attribute::<Length>(AId::Height),\n    ) {\n        (Some(_), None) => {\n            // Only width was defined, so we need to scale height accordingly.\n            height = actual_size.height() * (width / actual_size.width());\n        }\n        (None, Some(_)) => {\n            // Only height was defined, so we need to scale width accordingly.\n            width = actual_size.width() * (height / actual_size.height());\n        }\n        _ => {}\n    };\n\n    let aspect: AspectRatio = node.attribute(AId::PreserveAspectRatio).unwrap_or_default();\n\n    let rect = NonZeroRect::from_xywh(x, y, width, height);\n    let rect = rect.log_none(|| log::warn!(\"Image has an invalid size. Skipped.\"))?;\n\n    convert_inner(\n        kind,\n        id,\n        visible,\n        rendering_mode,\n        aspect,\n        actual_size,\n        rect,\n        cache,\n        parent,\n    )\n}\n\npub(crate) fn convert_inner(\n    kind: ImageKind,\n    id: String,\n    visible: bool,\n    rendering_mode: ImageRendering,\n    aspect: AspectRatio,\n    actual_size: Size,\n    rect: NonZeroRect,\n    cache: &mut converter::Cache,\n    parent: &mut Group,\n) -> Option<()> {\n    let aligned_size = fit_view_box(actual_size, rect, aspect);\n    let (aligned_x, aligned_y) = crate::aligned_pos(\n        aspect.align,\n        rect.x(),\n        rect.y(),\n        rect.width() - aligned_size.width(),\n        rect.height() - aligned_size.height(),\n    );\n    let view_box = aligned_size.to_non_zero_rect(aligned_x, aligned_y);\n\n    let image_ts = Transform::from_row(\n        view_box.width() / actual_size.width(),\n        0.0,\n        0.0,\n        view_box.height() / actual_size.height(),\n        view_box.x(),\n        view_box.y(),\n    );\n\n    let abs_transform = parent.abs_transform.pre_concat(image_ts);\n    let abs_bounding_box = view_box.transform(parent.abs_transform)?;\n\n    let mut g = Group::empty();\n    g.id = id;\n    g.children.push(Node::Image(Box::new(Image {\n        id: String::new(),\n        visible,\n        size: actual_size,\n        rendering_mode,\n        kind,\n        abs_transform,\n        abs_bounding_box,\n    })));\n    g.transform = image_ts;\n    g.abs_transform = abs_transform;\n    g.calculate_bounding_boxes();\n\n    if aspect.slice {\n        // Image slice acts like a rectangular clip.\n        let mut path = Path::new_simple(Arc::new(tiny_skia_path::PathBuilder::from_rect(\n            rect.to_rect(),\n        )))\n        .unwrap();\n        path.fill = Some(crate::Fill::default());\n\n        let mut clip = ClipPath::empty(cache.gen_clip_path_id());\n        clip.root.children.push(Node::Path(Box::new(path)));\n\n        // Clip path should not be affected by the image viewbox transform.\n        // The final structure should look like:\n        // <g clip-path=\"url(#clipPath1)\">\n        //     <g transform=\"matrix(1 0 0 1 10 20)\">\n        //         <image/>\n        //     </g>\n        // </g>\n\n        let mut g2 = Group::empty();\n        std::mem::swap(&mut g.id, &mut g2.id);\n        g2.abs_transform = parent.abs_transform;\n        g2.clip_path = Some(Arc::new(clip));\n        g2.children.push(Node::Group(Box::new(g)));\n        g2.calculate_bounding_boxes();\n\n        parent.children.push(Node::Group(Box::new(g2)));\n    } else {\n        parent.children.push(Node::Group(Box::new(g)));\n    }\n\n    Some(())\n}\n\npub(crate) fn get_href_data(href: &str, state: &converter::State) -> Option<ImageKind> {\n    if let Ok(url) = data_url::DataUrl::process(href) {\n        let (data, _) = url.decode_to_vec().ok()?;\n\n        let mime = format!(\n            \"{}/{}\",\n            url.mime_type().type_.as_str(),\n            url.mime_type().subtype.as_str()\n        );\n\n        (state.opt.image_href_resolver.resolve_data)(&mime, Arc::new(data), state.opt)\n    } else {\n        (state.opt.image_href_resolver.resolve_string)(href, state.opt)\n    }\n}\n\n/// Checks that file has a PNG, a GIF, a JPEG or a WebP magic bytes.\n/// Or an SVG(Z) extension.\nfn get_image_file_format(path: &std::path::Path, data: &[u8]) -> Option<ImageFormat> {\n    let ext = path.extension().and_then(|e| e.to_str())?.to_lowercase();\n    if ext == \"svg\" || ext == \"svgz\" {\n        return Some(ImageFormat::SVG);\n    }\n\n    get_image_data_format(data)\n}\n\n/// Checks that file has a PNG, a GIF, a JPEG or a WebP magic bytes.\nfn get_image_data_format(data: &[u8]) -> Option<ImageFormat> {\n    match imagesize::image_type(data).ok()? {\n        imagesize::ImageType::Gif => Some(ImageFormat::GIF),\n        imagesize::ImageType::Jpeg => Some(ImageFormat::JPEG),\n        imagesize::ImageType::Png => Some(ImageFormat::PNG),\n        imagesize::ImageType::Webp => Some(ImageFormat::WEBP),\n        _ => None,\n    }\n}\n\n/// Tries to load the `ImageData` content as an SVG image or emits a warning and returns `None`.\npub(crate) fn load_sub_svg(data: &[u8], opt: &Options) -> Option<ImageKind> {\n    match Tree::from_data_nested(data, opt) {\n        Ok(tree) => Some(ImageKind::SVG(tree)),\n        Err(_) => {\n            log::warn!(\"Failed to load nested SVG image.\");\n            None\n        }\n    }\n}\n\n/// Fits size into a viewbox.\nfn fit_view_box(size: Size, rect: NonZeroRect, aspect: AspectRatio) -> Size {\n    let s = rect.size();\n\n    if aspect.align == svgtypes::Align::None {\n        s\n    } else if aspect.slice {\n        size.expand_to(s)\n    } else {\n        size.scale_to(s)\n    }\n}\n"
  },
  {
    "path": "crates/usvg/src/parser/marker.rs",
    "content": "// Copyright 2018 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse std::sync::Arc;\n\nuse strict_num::NonZeroPositiveF32;\nuse svgtypes::Length;\nuse tiny_skia_path::Point;\n\nuse super::converter;\nuse super::svgtree::{AId, EId, SvgNode};\nuse crate::{\n    ApproxEqUlps, ApproxZeroUlps, ClipPath, Fill, Group, Node, NonZeroRect, Path, Size, Transform,\n    ViewBox,\n};\n\n// Similar to `tiny_skia_path::PathSegment`, but without the `QuadTo`.\n#[derive(Copy, Clone, Debug)]\nenum Segment {\n    MoveTo(Point),\n    LineTo(Point),\n    CubicTo(Point, Point, Point),\n    Close,\n}\n\npub(crate) fn is_valid(node: SvgNode) -> bool {\n    // `marker-*` attributes cannot be set on shapes inside a `clipPath`.\n    if node\n        .ancestors()\n        .any(|n| n.tag_name() == Some(EId::ClipPath))\n    {\n        return false;\n    }\n\n    let start = node.find_attribute::<SvgNode>(AId::MarkerStart);\n    let mid = node.find_attribute::<SvgNode>(AId::MarkerMid);\n    let end = node.find_attribute::<SvgNode>(AId::MarkerEnd);\n    start.is_some() || mid.is_some() || end.is_some()\n}\n\npub(crate) fn convert(\n    node: SvgNode,\n    path: &tiny_skia_path::Path,\n    state: &converter::State,\n    cache: &mut converter::Cache,\n    parent: &mut Group,\n) {\n    let list = [\n        (AId::MarkerStart, MarkerKind::Start),\n        (AId::MarkerMid, MarkerKind::Middle),\n        (AId::MarkerEnd, MarkerKind::End),\n    ];\n\n    for (aid, kind) in &list {\n        let mut marker = None;\n        if let Some(link) = node.find_attribute::<SvgNode>(*aid) {\n            if link.tag_name() == Some(EId::Marker) {\n                marker = Some(link);\n            }\n        }\n\n        if let Some(marker) = marker {\n            // TODO: move to svgtree\n            // Check for recursive marker.\n            if state.parent_markers.contains(&marker) {\n                log::warn!(\"Recursive marker detected: {}\", marker.element_id());\n                continue;\n            }\n\n            resolve(node, path, marker, *kind, state, cache, parent);\n        }\n    }\n}\n\n#[derive(Clone, Copy)]\nenum MarkerKind {\n    Start,\n    Middle,\n    End,\n}\n\nenum MarkerOrientation {\n    Auto,\n    AutoStartReverse,\n    Angle(f32),\n}\n\nfn resolve(\n    shape_node: SvgNode,\n    path: &tiny_skia_path::Path,\n    marker_node: SvgNode,\n    marker_kind: MarkerKind,\n    state: &converter::State,\n    cache: &mut converter::Cache,\n    parent: &mut Group,\n) -> Option<()> {\n    let stroke_scale = stroke_scale(shape_node, marker_node, state)?.get();\n\n    let r = convert_rect(marker_node, state)?;\n\n    let view_box = marker_node.parse_viewbox().map(|vb| ViewBox {\n        rect: vb,\n        aspect: marker_node\n            .attribute(AId::PreserveAspectRatio)\n            .unwrap_or_default(),\n    });\n\n    let has_overflow = {\n        let overflow = marker_node.attribute(AId::Overflow);\n        // `overflow` is `hidden` by default.\n        overflow.is_none() || overflow == Some(\"hidden\") || overflow == Some(\"scroll\")\n    };\n\n    let clip_path = if has_overflow {\n        let clip_rect = if let Some(vbox) = view_box {\n            vbox.rect\n        } else {\n            r.size().to_non_zero_rect(0.0, 0.0)\n        };\n\n        let mut clip_path = ClipPath::empty(cache.gen_clip_path_id());\n\n        let mut path = Path::new_simple(Arc::new(tiny_skia_path::PathBuilder::from_rect(\n            clip_rect.to_rect(),\n        )))?;\n        path.fill = Some(Fill::default());\n\n        clip_path.root.children.push(Node::Path(Box::new(path)));\n\n        Some(Arc::new(clip_path))\n    } else {\n        None\n    };\n\n    // TODO: avoid allocation\n    let mut segments: Vec<Segment> = Vec::with_capacity(path.len());\n    let mut prev = Point::zero();\n    let mut prev_move = Point::zero();\n    for seg in path.segments() {\n        match seg {\n            tiny_skia_path::PathSegment::MoveTo(p) => {\n                segments.push(Segment::MoveTo(p));\n                prev = p;\n                prev_move = p;\n            }\n            tiny_skia_path::PathSegment::LineTo(p) => {\n                segments.push(Segment::LineTo(p));\n                prev = p;\n            }\n            tiny_skia_path::PathSegment::QuadTo(p1, p) => {\n                let (p1, p2, p) = quad_to_curve(prev, p1, p);\n                segments.push(Segment::CubicTo(p1, p2, p));\n                prev = p;\n            }\n            tiny_skia_path::PathSegment::CubicTo(p1, p2, p) => {\n                segments.push(Segment::CubicTo(p1, p2, p));\n                prev = p;\n            }\n            tiny_skia_path::PathSegment::Close => {\n                segments.push(Segment::Close);\n                prev = prev_move;\n            }\n        }\n    }\n\n    let draw_marker = |p: tiny_skia_path::Point, idx: usize| {\n        let mut ts = Transform::from_translate(p.x, p.y);\n\n        let angle = match convert_orientation(marker_node) {\n            MarkerOrientation::AutoStartReverse if idx == 0 => {\n                (calc_vertex_angle(&segments, idx) + 180.0) % 360.0\n            }\n            MarkerOrientation::Auto | MarkerOrientation::AutoStartReverse => {\n                calc_vertex_angle(&segments, idx)\n            }\n            MarkerOrientation::Angle(angle) => angle,\n        };\n\n        if !angle.approx_zero_ulps(4) {\n            ts = ts.pre_rotate(angle);\n        }\n\n        if let Some(vbox) = view_box {\n            let size = Size::from_wh(r.width() * stroke_scale, r.height() * stroke_scale).unwrap();\n            let vbox_ts = vbox.to_transform(size);\n            let (sx, sy) = vbox_ts.get_scale();\n            ts = ts.pre_scale(sx, sy);\n        } else {\n            ts = ts.pre_scale(stroke_scale, stroke_scale);\n        }\n\n        ts = ts.pre_translate(-r.x(), -r.y());\n\n        // TODO: do not create a group when no clipPath\n        let mut g = Group {\n            transform: ts,\n            abs_transform: parent.abs_transform.pre_concat(ts),\n            clip_path: clip_path.clone(),\n            ..Group::empty()\n        };\n\n        let mut marker_state = state.clone();\n        marker_state.parent_markers.push(marker_node);\n        converter::convert_children(marker_node, &marker_state, cache, &mut g);\n        g.calculate_bounding_boxes();\n\n        if g.has_children() {\n            parent.children.push(Node::Group(Box::new(g)));\n        }\n    };\n\n    draw_markers(&segments, marker_kind, draw_marker);\n\n    Some(())\n}\n\nfn stroke_scale(\n    path_node: SvgNode,\n    marker_node: SvgNode,\n    state: &converter::State,\n) -> Option<NonZeroPositiveF32> {\n    match marker_node.attribute(AId::MarkerUnits) {\n        Some(\"userSpaceOnUse\") => NonZeroPositiveF32::new(1.0),\n        _ => path_node.resolve_valid_length(AId::StrokeWidth, state, 1.0),\n    }\n}\n\nfn draw_markers<P>(path: &[Segment], kind: MarkerKind, mut draw_marker: P)\nwhere\n    P: FnMut(tiny_skia_path::Point, usize),\n{\n    match kind {\n        MarkerKind::Start => {\n            if let Some(Segment::MoveTo(p)) = path.first().cloned() {\n                draw_marker(p, 0);\n            }\n        }\n        MarkerKind::Middle => {\n            let total = path.len() - 1;\n            let mut i = 1;\n            while i < total {\n                let p = match path[i] {\n                    Segment::MoveTo(p) => p,\n                    Segment::LineTo(p) => p,\n                    Segment::CubicTo(_, _, p) => p,\n                    _ => {\n                        i += 1;\n                        continue;\n                    }\n                };\n\n                draw_marker(p, i);\n\n                i += 1;\n            }\n        }\n        MarkerKind::End => {\n            let idx = path.len() - 1;\n            match path.last().cloned() {\n                Some(Segment::LineTo(p)) => {\n                    draw_marker(p, idx);\n                }\n                Some(Segment::CubicTo(_, _, p)) => {\n                    draw_marker(p, idx);\n                }\n                Some(Segment::Close) => {\n                    let p = get_subpath_start(path, idx);\n                    draw_marker(p, idx);\n                }\n                _ => {}\n            }\n        }\n    }\n}\n\nfn calc_vertex_angle(path: &[Segment], idx: usize) -> f32 {\n    if idx == 0 {\n        // First segment.\n\n        debug_assert!(path.len() > 1);\n\n        let seg1 = path[0];\n        let seg2 = path[1];\n\n        match (seg1, seg2) {\n            (Segment::MoveTo(pm), Segment::LineTo(p)) => calc_line_angle(pm.x, pm.y, p.x, p.y),\n            (Segment::MoveTo(pm), Segment::CubicTo(p1, _, p)) => {\n                if pm.x.approx_eq_ulps(&p1.x, 4) && pm.y.approx_eq_ulps(&p1.y, 4) {\n                    calc_line_angle(pm.x, pm.y, p.x, p.y)\n                } else {\n                    calc_line_angle(pm.x, pm.y, p1.x, p1.y)\n                }\n            }\n            _ => 0.0,\n        }\n    } else if idx == path.len() - 1 {\n        // Last segment.\n\n        let seg1 = path[idx - 1];\n        let seg2 = path[idx];\n\n        match (seg1, seg2) {\n            (_, Segment::MoveTo(_)) => 0.0, // unreachable\n            (_, Segment::LineTo(p)) => {\n                let prev = get_prev_vertex(path, idx);\n                calc_line_angle(prev.x, prev.y, p.x, p.y)\n            }\n            (_, Segment::CubicTo(p1, p2, p)) => {\n                if p2.x.approx_eq_ulps(&p.x, 4) && p2.y.approx_eq_ulps(&p.y, 4) {\n                    calc_line_angle(p1.x, p1.y, p.x, p.y)\n                } else {\n                    calc_line_angle(p2.x, p2.y, p.x, p.y)\n                }\n            }\n            (Segment::LineTo(p), Segment::Close) => {\n                let next = get_subpath_start(path, idx);\n                calc_line_angle(p.x, p.y, next.x, next.y)\n            }\n            (Segment::CubicTo(_, p2, p), Segment::Close) => {\n                let prev = get_prev_vertex(path, idx);\n                let next = get_subpath_start(path, idx);\n                calc_curves_angle(\n                    prev.x, prev.y, p2.x, p2.y, p.x, p.y, next.x, next.y, next.x, next.y,\n                )\n            }\n            (_, Segment::Close) => 0.0,\n        }\n    } else {\n        // Middle segments.\n\n        let seg1 = path[idx];\n        let seg2 = path[idx + 1];\n\n        // TODO: Not sure if there is a better way.\n        match (seg1, seg2) {\n            (Segment::MoveTo(pm), Segment::LineTo(p)) => calc_line_angle(pm.x, pm.y, p.x, p.y),\n            (Segment::MoveTo(pm), Segment::CubicTo(p1, _, _)) => {\n                calc_line_angle(pm.x, pm.y, p1.x, p1.y)\n            }\n            (Segment::LineTo(p1), Segment::LineTo(p2)) => {\n                let prev = get_prev_vertex(path, idx);\n                calc_angle(prev.x, prev.y, p1.x, p1.y, p1.x, p1.y, p2.x, p2.y)\n            }\n            (Segment::CubicTo(_, c1_p2, c1_p), Segment::CubicTo(c2_p1, _, c2_p)) => {\n                let prev = get_prev_vertex(path, idx);\n                calc_curves_angle(\n                    prev.x, prev.y, c1_p2.x, c1_p2.y, c1_p.x, c1_p.y, c2_p1.x, c2_p1.y, c2_p.x,\n                    c2_p.y,\n                )\n            }\n            (Segment::LineTo(pl), Segment::CubicTo(p1, _, p)) => {\n                let prev = get_prev_vertex(path, idx);\n                calc_curves_angle(\n                    prev.x, prev.y, prev.x, prev.y, pl.x, pl.y, p1.x, p1.y, p.x, p.y,\n                )\n            }\n            (Segment::CubicTo(_, p2, p), Segment::LineTo(pl)) => {\n                let prev = get_prev_vertex(path, idx);\n                calc_curves_angle(prev.x, prev.y, p2.x, p2.y, p.x, p.y, pl.x, pl.y, pl.x, pl.y)\n            }\n            (Segment::LineTo(p), Segment::MoveTo(_)) => {\n                let prev = get_prev_vertex(path, idx);\n                calc_line_angle(prev.x, prev.y, p.x, p.y)\n            }\n            (Segment::CubicTo(_, p2, p), Segment::MoveTo(_)) => {\n                if p.x.approx_eq_ulps(&p2.x, 4) && p.y.approx_eq_ulps(&p2.y, 4) {\n                    let prev = get_prev_vertex(path, idx);\n                    calc_line_angle(prev.x, prev.y, p.x, p.y)\n                } else {\n                    calc_line_angle(p2.x, p2.y, p.x, p.y)\n                }\n            }\n            (Segment::LineTo(p), Segment::Close) => {\n                let prev = get_prev_vertex(path, idx);\n                let next = get_subpath_start(path, idx);\n                calc_angle(prev.x, prev.y, p.x, p.y, p.x, p.y, next.x, next.y)\n            }\n            (_, Segment::Close) => {\n                let prev = get_prev_vertex(path, idx);\n                let next = get_subpath_start(path, idx);\n                calc_line_angle(prev.x, prev.y, next.x, next.y)\n            }\n            (_, Segment::MoveTo(_)) | (Segment::Close, _) => 0.0,\n        }\n    }\n}\n\nfn calc_line_angle(x1: f32, y1: f32, x2: f32, y2: f32) -> f32 {\n    calc_angle(x1, y1, x2, y2, x1, y1, x2, y2)\n}\n\nfn calc_curves_angle(\n    px: f32,\n    py: f32, // previous vertex\n    cx1: f32,\n    cy1: f32, // previous control point\n    x: f32,\n    y: f32, // current vertex\n    cx2: f32,\n    cy2: f32, // next control point\n    nx: f32,\n    ny: f32, // next vertex\n) -> f32 {\n    if cx1.approx_eq_ulps(&x, 4) && cy1.approx_eq_ulps(&y, 4) {\n        calc_angle(px, py, x, y, x, y, cx2, cy2)\n    } else if x.approx_eq_ulps(&cx2, 4) && y.approx_eq_ulps(&cy2, 4) {\n        calc_angle(cx1, cy1, x, y, x, y, nx, ny)\n    } else {\n        calc_angle(cx1, cy1, x, y, x, y, cx2, cy2)\n    }\n}\n\nfn calc_angle(x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32, x4: f32, y4: f32) -> f32 {\n    use std::f32::consts::*;\n\n    fn normalize(rad: f32) -> f32 {\n        let v = rad % (PI * 2.0);\n        if v < 0.0 { v + PI * 2.0 } else { v }\n    }\n\n    fn vector_angle(vx: f32, vy: f32) -> f32 {\n        let rad = vy.atan2(vx);\n        if rad.is_nan() { 0.0 } else { normalize(rad) }\n    }\n\n    let in_a = vector_angle(x2 - x1, y2 - y1);\n    let out_a = vector_angle(x4 - x3, y4 - y3);\n    let d = (out_a - in_a) * 0.5;\n\n    let mut angle = in_a + d;\n    if FRAC_PI_2 < d.abs() {\n        angle -= PI;\n    }\n\n    normalize(angle).to_degrees()\n}\n\nfn get_subpath_start(segments: &[Segment], idx: usize) -> tiny_skia_path::Point {\n    let offset = segments.len() - idx;\n    for seg in segments.iter().rev().skip(offset) {\n        if let Segment::MoveTo(p) = *seg {\n            return p;\n        }\n    }\n\n    tiny_skia_path::Point::zero()\n}\n\nfn get_prev_vertex(segments: &[Segment], idx: usize) -> tiny_skia_path::Point {\n    match segments[idx - 1] {\n        Segment::MoveTo(p) => p,\n        Segment::LineTo(p) => p,\n        Segment::CubicTo(_, _, p) => p,\n        Segment::Close => get_subpath_start(segments, idx),\n    }\n}\n\nfn convert_rect(node: SvgNode, state: &converter::State) -> Option<NonZeroRect> {\n    NonZeroRect::from_xywh(\n        node.convert_user_length(AId::RefX, state, Length::zero()),\n        node.convert_user_length(AId::RefY, state, Length::zero()),\n        node.convert_user_length(AId::MarkerWidth, state, Length::new_number(3.0)),\n        node.convert_user_length(AId::MarkerHeight, state, Length::new_number(3.0)),\n    )\n}\n\nfn convert_orientation(node: SvgNode) -> MarkerOrientation {\n    match node.attribute(AId::Orient) {\n        Some(\"auto\") => MarkerOrientation::Auto,\n        Some(\"auto-start-reverse\") => MarkerOrientation::AutoStartReverse,\n        _ => match node.attribute::<svgtypes::Angle>(AId::Orient) {\n            Some(angle) => MarkerOrientation::Angle(angle.to_degrees() as f32),\n            None => MarkerOrientation::Angle(0.0),\n        },\n    }\n}\n\nfn quad_to_curve(prev: Point, p1: Point, p: Point) -> (Point, Point, Point) {\n    #[inline]\n    fn calc(n1: f32, n2: f32) -> f32 {\n        (n1 + n2 * 2.0) / 3.0\n    }\n\n    (\n        Point::from_xy(calc(prev.x, p1.x), calc(prev.y, p1.y)),\n        Point::from_xy(calc(p.x, p1.x), calc(p.y, p1.y)),\n        p,\n    )\n}\n"
  },
  {
    "path": "crates/usvg/src/parser/mask.rs",
    "content": "// Copyright 2018 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse std::sync::Arc;\n\nuse svgtypes::{Length, LengthUnit as Unit};\n\nuse super::svgtree::{AId, EId, SvgNode};\nuse super::{OptionLog, converter};\nuse crate::{Group, Mask, MaskType, Node, NonEmptyString, NonZeroRect, Transform, Units};\n\npub(crate) fn convert(\n    node: SvgNode,\n    state: &converter::State,\n    object_bbox: Option<NonZeroRect>,\n    cache: &mut converter::Cache,\n) -> Option<Arc<Mask>> {\n    // A `mask` attribute must reference a `mask` element.\n    if node.tag_name() != Some(EId::Mask) {\n        return None;\n    }\n\n    let units = node\n        .attribute(AId::MaskUnits)\n        .unwrap_or(Units::ObjectBoundingBox);\n\n    let content_units = node\n        .attribute(AId::MaskContentUnits)\n        .unwrap_or(Units::UserSpaceOnUse);\n\n    // Check if this element was already converted.\n    //\n    // Only `userSpaceOnUse` masks can be shared,\n    // because `objectBoundingBox` one will be converted into user one\n    // and will become node-specific.\n    let cacheable = units == Units::UserSpaceOnUse && content_units == Units::UserSpaceOnUse;\n    if cacheable {\n        if let Some(mask) = cache.masks.get(node.element_id()) {\n            return Some(mask.clone());\n        }\n    }\n\n    let rect = NonZeroRect::from_xywh(\n        node.convert_length(AId::X, units, state, Length::new(-10.0, Unit::Percent)),\n        node.convert_length(AId::Y, units, state, Length::new(-10.0, Unit::Percent)),\n        node.convert_length(AId::Width, units, state, Length::new(120.0, Unit::Percent)),\n        node.convert_length(AId::Height, units, state, Length::new(120.0, Unit::Percent)),\n    );\n    let mut rect =\n        rect.log_none(|| log::warn!(\"Mask '{}' has an invalid size. Skipped.\", node.element_id()))?;\n\n    let mut mask_all = false;\n    if units == Units::ObjectBoundingBox {\n        if let Some(bbox) = object_bbox {\n            rect = rect.bbox_transform(bbox);\n        } else {\n            // When mask units are `objectBoundingBox` and bbox is zero-sized - the whole\n            // element should be masked.\n            // Technically an UB, but this is what Chrome and Firefox do.\n            mask_all = true;\n        }\n    }\n\n    let mut id = NonEmptyString::new(node.element_id().to_string())?;\n    // Generate ID only when we're parsing `objectBoundingBox` mask for the second time.\n    if !cacheable && cache.masks.contains_key(id.get()) {\n        id = cache.gen_mask_id();\n    }\n    let id_copy = id.get().to_string();\n\n    if mask_all {\n        let mask = Arc::new(Mask {\n            id,\n            rect,\n            kind: MaskType::Luminance,\n            mask: None,\n            root: Group::empty(),\n        });\n        cache.masks.insert(id_copy, mask.clone());\n        return Some(mask);\n    }\n\n    // Resolve linked mask.\n    let mut mask = None;\n    if let Some(link) = node.attribute::<SvgNode>(AId::Mask) {\n        mask = convert(link, state, object_bbox, cache);\n\n        // Linked `mask` must be valid.\n        if mask.is_none() {\n            return None;\n        }\n    }\n\n    let kind = if node.attribute(AId::MaskType) == Some(\"alpha\") {\n        MaskType::Alpha\n    } else {\n        MaskType::Luminance\n    };\n\n    let mut mask = Mask {\n        id,\n        rect,\n        kind,\n        mask,\n        root: Group::empty(),\n    };\n\n    // To emulate content `objectBoundingBox` units we have to put\n    // mask children into a group with a transform.\n    let mut subroot = None;\n    if content_units == Units::ObjectBoundingBox {\n        let object_bbox = match object_bbox {\n            Some(v) => v,\n            None => {\n                log::warn!(\"Masking of zero-sized shapes is not allowed.\");\n                return None;\n            }\n        };\n\n        let mut g = Group::empty();\n        g.transform = Transform::from_bbox(object_bbox);\n        // Make sure to set `abs_transform`, because it must propagate to all children.\n        g.abs_transform = g.transform;\n\n        subroot = Some(g);\n    }\n\n    {\n        // Prefer `subroot` to `mask.root`.\n        let real_root = subroot.as_mut().unwrap_or(&mut mask.root);\n        converter::convert_children(node, state, cache, real_root);\n\n        // A mask without children at this point is invalid.\n        // Only masks with zero bbox and `objectBoundingBox` can be empty.\n        if !real_root.has_children() {\n            return None;\n        }\n    }\n\n    if let Some(mut subroot) = subroot {\n        subroot.calculate_bounding_boxes();\n        mask.root.children.push(Node::Group(Box::new(subroot)));\n    }\n\n    mask.root.calculate_bounding_boxes();\n\n    let mask = Arc::new(mask);\n    cache.masks.insert(id_copy, mask.clone());\n    Some(mask)\n}\n"
  },
  {
    "path": "crates/usvg/src/parser/mod.rs",
    "content": "// Copyright 2018 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nmod clippath;\nmod converter;\nmod filter;\nmod image;\nmod marker;\nmod mask;\nmod options;\nmod paint_server;\nmod shapes;\nmod style;\nmod svgtree;\nmod switch;\nmod units;\nmod use_node;\n\n#[cfg(feature = \"text\")]\nmod text;\n#[cfg(feature = \"text\")]\npub(crate) use converter::Cache;\npub use image::{ImageHrefDataResolverFn, ImageHrefResolver, ImageHrefStringResolverFn};\npub use options::Options;\npub(crate) use svgtree::{AId, EId};\n\n/// List of all errors.\n#[derive(Debug)]\npub enum Error {\n    /// Only UTF-8 content are supported.\n    NotAnUtf8Str,\n\n    /// Compressed SVG must use the GZip algorithm.\n    MalformedGZip,\n\n    /// We do not allow SVG with more than 1_000_000 elements for security reasons.\n    ElementsLimitReached,\n\n    /// SVG doesn't have a valid size.\n    ///\n    /// Occurs when width and/or height are <= 0.\n    ///\n    /// Also occurs if width, height and viewBox are not set.\n    InvalidSize,\n\n    /// Failed to parse an SVG data.\n    ParsingFailed(roxmltree::Error),\n}\n\nimpl From<roxmltree::Error> for Error {\n    fn from(e: roxmltree::Error) -> Self {\n        Error::ParsingFailed(e)\n    }\n}\n\nimpl std::fmt::Display for Error {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        match *self {\n            Error::NotAnUtf8Str => {\n                write!(f, \"provided data has not an UTF-8 encoding\")\n            }\n            Error::MalformedGZip => {\n                write!(f, \"provided data has a malformed GZip content\")\n            }\n            Error::ElementsLimitReached => {\n                write!(f, \"the maximum number of SVG elements has been reached\")\n            }\n            Error::InvalidSize => {\n                write!(f, \"SVG has an invalid size\")\n            }\n            Error::ParsingFailed(ref e) => {\n                write!(f, \"SVG data parsing failed cause {}\", e)\n            }\n        }\n    }\n}\n\nimpl std::error::Error for Error {}\n\npub(crate) trait OptionLog {\n    fn log_none<F: FnOnce()>(self, f: F) -> Self;\n}\n\nimpl<T> OptionLog for Option<T> {\n    #[inline]\n    fn log_none<F: FnOnce()>(self, f: F) -> Self {\n        self.or_else(|| {\n            f();\n            None\n        })\n    }\n}\n\nimpl crate::Tree {\n    /// Parses `Tree` from an SVG data.\n    ///\n    /// Can contain an SVG string or a gzip compressed data.\n    pub fn from_data(data: &[u8], opt: &Options) -> Result<Self, Error> {\n        if data.starts_with(&[0x1f, 0x8b]) {\n            let data = decompress_svgz(data)?;\n            let text = std::str::from_utf8(&data).map_err(|_| Error::NotAnUtf8Str)?;\n            Self::from_str(text, opt)\n        } else {\n            let text = std::str::from_utf8(data).map_err(|_| Error::NotAnUtf8Str)?;\n            Self::from_str(text, opt)\n        }\n    }\n\n    /// Similar to the `from_data` method, except that it ignores all `image` elements linking to\n    /// external files, as required by the SVG specification when SVG files are loaded\n    /// for `<image href=\"...\" />` tags.\n    pub fn from_data_nested(data: &[u8], opt: &Options) -> Result<Self, Error> {\n        let nested_opt = Options {\n            resources_dir: None,\n            dpi: opt.dpi,\n            font_size: opt.font_size,\n            languages: opt.languages.clone(),\n            shape_rendering: opt.shape_rendering,\n            text_rendering: opt.text_rendering,\n            image_rendering: opt.image_rendering,\n            default_size: opt.default_size,\n            image_href_resolver: ImageHrefResolver {\n                resolve_data: Box::new(|a, b, c| (opt.image_href_resolver.resolve_data)(a, b, c)),\n                // External images should be ignored.\n                resolve_string: Box::new(|_, _| None),\n            },\n            // In the referenced SVG, we start with the unmodified user-provided\n            // fontdb, not the one from the cache.\n            #[cfg(feature = \"text\")]\n            fontdb: opt.fontdb.clone(),\n            // Can't clone the resolver, so we create a new one that forwards to it.\n            #[cfg(feature = \"text\")]\n            font_resolver: crate::FontResolver {\n                select_font: Box::new(|font, db| (opt.font_resolver.select_font)(font, db)),\n                select_fallback: Box::new(|c, used_fonts, db| {\n                    (opt.font_resolver.select_fallback)(c, used_fonts, db)\n                }),\n            },\n            ..Options::default()\n        };\n\n        Self::from_data(data, &nested_opt)\n    }\n\n    /// Parses `Tree` from an SVG string.\n    pub fn from_str(text: &str, opt: &Options) -> Result<Self, Error> {\n        let xml_opt = roxmltree::ParsingOptions {\n            allow_dtd: true,\n            ..Default::default()\n        };\n\n        let doc =\n            roxmltree::Document::parse_with_options(text, xml_opt).map_err(Error::ParsingFailed)?;\n\n        Self::from_xmltree(&doc, opt)\n    }\n\n    /// Parses `Tree` from `roxmltree::Document`.\n    pub fn from_xmltree(doc: &roxmltree::Document, opt: &Options) -> Result<Self, Error> {\n        let doc = svgtree::Document::parse_tree(doc, opt.style_sheet.as_deref())?;\n        self::converter::convert_doc(&doc, opt)\n    }\n}\n\n/// Decompresses an SVGZ file.\npub fn decompress_svgz(data: &[u8]) -> Result<Vec<u8>, Error> {\n    use std::io::Read;\n\n    let mut decoder = flate2::read::GzDecoder::new(data);\n    let mut decoded = Vec::with_capacity(data.len() * 2);\n    decoder\n        .read_to_end(&mut decoded)\n        .map_err(|_| Error::MalformedGZip)?;\n    Ok(decoded)\n}\n\n#[inline]\npub(crate) fn f32_bound(min: f32, val: f32, max: f32) -> f32 {\n    debug_assert!(min.is_finite());\n    debug_assert!(val.is_finite());\n    debug_assert!(max.is_finite());\n\n    if val > max {\n        max\n    } else if val < min {\n        min\n    } else {\n        val\n    }\n}\n"
  },
  {
    "path": "crates/usvg/src/parser/options.rs",
    "content": "// Copyright 2018 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\n#[cfg(feature = \"text\")]\nuse std::sync::Arc;\n\n#[cfg(feature = \"text\")]\nuse crate::FontResolver;\nuse crate::{ImageHrefResolver, ImageRendering, ShapeRendering, Size, TextRendering};\n\n/// Processing options.\n#[derive(Debug)]\npub struct Options<'a> {\n    /// Directory that will be used during relative paths resolving.\n    ///\n    /// Expected to be the same as the directory that contains the SVG file,\n    /// but can be set to any.\n    ///\n    /// Default: `None`\n    pub resources_dir: Option<std::path::PathBuf>,\n\n    /// Target DPI.\n    ///\n    /// Impacts units conversion.\n    ///\n    /// Default: 96.0\n    pub dpi: f32,\n\n    /// A default font family.\n    ///\n    /// Will be used when no `font-family` attribute is set in the SVG.\n    ///\n    /// Default: Times New Roman\n    pub font_family: String,\n\n    /// A default font size.\n    ///\n    /// Will be used when no `font-size` attribute is set in the SVG.\n    ///\n    /// Default: 12\n    pub font_size: f32,\n\n    /// A list of languages.\n    ///\n    /// Will be used to resolve a `systemLanguage` conditional attribute.\n    ///\n    /// Format: en, en-US.\n    ///\n    /// Default: `[en]`\n    pub languages: Vec<String>,\n\n    /// Specifies the default shape rendering method.\n    ///\n    /// Will be used when an SVG element's `shape-rendering` property is set to `auto`.\n    ///\n    /// Default: GeometricPrecision\n    pub shape_rendering: ShapeRendering,\n\n    /// Specifies the default text rendering method.\n    ///\n    /// Will be used when an SVG element's `text-rendering` property is set to `auto`.\n    ///\n    /// Default: OptimizeLegibility\n    pub text_rendering: TextRendering,\n\n    /// Specifies the default image rendering method.\n    ///\n    /// Will be used when an SVG element's `image-rendering` property is set to `auto`.\n    ///\n    /// Default: OptimizeQuality\n    pub image_rendering: ImageRendering,\n\n    /// Default viewport size to assume if there is no `viewBox` attribute and\n    /// the `width` or `height` attributes are relative.\n    ///\n    /// Default: `(100, 100)`\n    pub default_size: Size,\n\n    /// Specifies the way `xlink:href` in `<image>` elements should be handled.\n    ///\n    /// Default: see type's documentation for details\n    pub image_href_resolver: ImageHrefResolver<'a>,\n\n    /// Specifies how fonts should be resolved and loaded.\n    #[cfg(feature = \"text\")]\n    pub font_resolver: FontResolver<'a>,\n\n    /// A database of fonts usable by text.\n    ///\n    /// This is a base database. If a custom `font_resolver` is specified,\n    /// additional fonts can be loaded during parsing. Those will be added to a\n    /// copy of this database. The full database containing all fonts referenced\n    /// in a `Tree` becomes available as [`Tree::fontdb`](crate::Tree::fontdb)\n    /// after parsing. If no fonts were loaded dynamically, that database will\n    /// be the same as this one.\n    #[cfg(feature = \"text\")]\n    pub fontdb: Arc<fontdb::Database>,\n    /// A CSS stylesheet that should be injected into the SVG. Can be used to overwrite\n    /// certain attributes.\n    pub style_sheet: Option<String>,\n}\n\nimpl Default for Options<'_> {\n    fn default() -> Options<'static> {\n        Options {\n            resources_dir: None,\n            dpi: 96.0,\n            // Default font is user-agent dependent so we can use whichever we like.\n            font_family: \"Times New Roman\".to_owned(),\n            font_size: 12.0,\n            languages: vec![\"en\".to_string()],\n            shape_rendering: ShapeRendering::default(),\n            text_rendering: TextRendering::default(),\n            image_rendering: ImageRendering::default(),\n            default_size: Size::from_wh(100.0, 100.0).unwrap(),\n            image_href_resolver: ImageHrefResolver::default(),\n            #[cfg(feature = \"text\")]\n            font_resolver: FontResolver::default(),\n            #[cfg(feature = \"text\")]\n            fontdb: Arc::new(fontdb::Database::new()),\n            style_sheet: None,\n        }\n    }\n}\n\nimpl Options<'_> {\n    /// Converts a relative path into absolute relative to the SVG file itself.\n    ///\n    /// If `Options::resources_dir` is not set, returns itself.\n    pub fn get_abs_path(&self, rel_path: &std::path::Path) -> std::path::PathBuf {\n        match self.resources_dir {\n            Some(ref dir) => dir.join(rel_path),\n            None => rel_path.into(),\n        }\n    }\n\n    /// Mutably acquires the database.\n    ///\n    /// This clones the database if it is currently shared.\n    #[cfg(feature = \"text\")]\n    pub fn fontdb_mut(&mut self) -> &mut fontdb::Database {\n        Arc::make_mut(&mut self.fontdb)\n    }\n}\n"
  },
  {
    "path": "crates/usvg/src/parser/paint_server.rs",
    "content": "// Copyright 2018 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse std::str::FromStr;\nuse std::sync::Arc;\n\nuse strict_num::PositiveF32;\nuse svgtypes::{Length, LengthUnit as Unit};\n\nuse super::OptionLog;\nuse super::converter::{self, Cache, SvgColorExt};\nuse super::svgtree::{AId, EId, SvgNode};\nuse crate::*;\n\npub(crate) enum ServerOrColor {\n    Server(Paint),\n    Color { color: Color, opacity: Opacity },\n}\n\npub(crate) fn convert(\n    node: SvgNode,\n    state: &converter::State,\n    cache: &mut converter::Cache,\n) -> Option<ServerOrColor> {\n    // Check for existing.\n    if let Some(paint) = cache.paint.get(node.element_id()) {\n        return Some(ServerOrColor::Server(paint.clone()));\n    }\n\n    // Unwrap is safe, because we already checked for is_paint_server().\n    let paint = match node.tag_name().unwrap() {\n        EId::LinearGradient => convert_linear(node, state),\n        EId::RadialGradient => convert_radial(node, state),\n        EId::Pattern => convert_pattern(node, state, cache),\n        _ => unreachable!(),\n    };\n\n    if let Some(ServerOrColor::Server(paint)) = &paint {\n        cache\n            .paint\n            .insert(node.element_id().to_string(), paint.clone());\n    }\n\n    paint\n}\n\n#[inline(never)]\nfn convert_linear(node: SvgNode, state: &converter::State) -> Option<ServerOrColor> {\n    let id = NonEmptyString::new(node.element_id().to_string())?;\n\n    let stops = convert_stops(find_gradient_with_stops(node)?);\n    if stops.len() < 2 {\n        return stops_to_color(&stops);\n    }\n\n    let units = convert_units(node, AId::GradientUnits, Units::ObjectBoundingBox);\n    let transform = node.resolve_transform(AId::GradientTransform, state);\n\n    let gradient = LinearGradient {\n        x1: resolve_number(node, AId::X1, units, state, Length::zero()),\n        y1: resolve_number(node, AId::Y1, units, state, Length::zero()),\n        x2: resolve_number(\n            node,\n            AId::X2,\n            units,\n            state,\n            Length::new(100.0, Unit::Percent),\n        ),\n        y2: resolve_number(node, AId::Y2, units, state, Length::zero()),\n        base: BaseGradient {\n            id,\n            units,\n            transform,\n            spread_method: convert_spread_method(node),\n            stops,\n        },\n    };\n\n    Some(ServerOrColor::Server(Paint::LinearGradient(Arc::new(\n        gradient,\n    ))))\n}\n\n#[inline(never)]\nfn convert_radial(node: SvgNode, state: &converter::State) -> Option<ServerOrColor> {\n    let id = NonEmptyString::new(node.element_id().to_string())?;\n\n    let stops = convert_stops(find_gradient_with_stops(node)?);\n    if stops.len() < 2 {\n        return stops_to_color(&stops);\n    }\n\n    let units = convert_units(node, AId::GradientUnits, Units::ObjectBoundingBox);\n    let r = resolve_number(node, AId::R, units, state, Length::new(50.0, Unit::Percent));\n    let fr = resolve_number(node, AId::Fr, units, state, Length::zero());\n\n    // 'A value of zero will cause the area to be painted as a single color\n    // using the color and opacity of the last gradient stop.'\n    //\n    // https://www.w3.org/TR/SVG11/pservers.html#RadialGradientElementRAttribute\n    if !r.is_valid_length() {\n        let stop = stops.last().unwrap();\n        return Some(ServerOrColor::Color {\n            color: stop.color,\n            opacity: stop.opacity,\n        });\n    }\n\n    let spread_method = convert_spread_method(node);\n    let cx = resolve_number(\n        node,\n        AId::Cx,\n        units,\n        state,\n        Length::new(50.0, Unit::Percent),\n    );\n    let cy = resolve_number(\n        node,\n        AId::Cy,\n        units,\n        state,\n        Length::new(50.0, Unit::Percent),\n    );\n    let fx = resolve_number(node, AId::Fx, units, state, Length::new_number(cx as f64));\n    let fy = resolve_number(node, AId::Fy, units, state, Length::new_number(cy as f64));\n    let transform = node.resolve_transform(AId::GradientTransform, state);\n\n    let gradient = RadialGradient {\n        cx,\n        cy,\n        r: PositiveF32::new(r).unwrap(),\n        fx,\n        fy,\n        fr: PositiveF32::new(fr).unwrap_or(PositiveF32::ZERO),\n        base: BaseGradient {\n            id,\n            units,\n            transform,\n            spread_method,\n            stops,\n        },\n    };\n\n    Some(ServerOrColor::Server(Paint::RadialGradient(Arc::new(\n        gradient,\n    ))))\n}\n\n#[inline(never)]\nfn convert_pattern(\n    node: SvgNode,\n    state: &converter::State,\n    cache: &mut converter::Cache,\n) -> Option<ServerOrColor> {\n    let node_with_children = find_pattern_with_children(node)?;\n\n    let id = NonEmptyString::new(node.element_id().to_string())?;\n\n    let view_box = {\n        let n1 = resolve_attr(node, AId::ViewBox);\n        let n2 = resolve_attr(node, AId::PreserveAspectRatio);\n        n1.parse_viewbox().map(|vb| ViewBox {\n            rect: vb,\n            aspect: n2.attribute(AId::PreserveAspectRatio).unwrap_or_default(),\n        })\n    };\n\n    let units = convert_units(node, AId::PatternUnits, Units::ObjectBoundingBox);\n    let content_units = convert_units(node, AId::PatternContentUnits, Units::UserSpaceOnUse);\n\n    let transform = node.resolve_transform(AId::PatternTransform, state);\n\n    let rect = NonZeroRect::from_xywh(\n        resolve_number(node, AId::X, units, state, Length::zero()),\n        resolve_number(node, AId::Y, units, state, Length::zero()),\n        resolve_number(node, AId::Width, units, state, Length::zero()),\n        resolve_number(node, AId::Height, units, state, Length::zero()),\n    );\n    let rect = rect.log_none(|| {\n        log::warn!(\n            \"Pattern '{}' has an invalid size. Skipped.\",\n            node.element_id()\n        );\n    })?;\n\n    let mut patt = Pattern {\n        id,\n        units,\n        content_units,\n        transform,\n        rect,\n        view_box,\n        root: Group::empty(),\n    };\n\n    // We can apply viewbox transform only for user space coordinates.\n    // Otherwise we need a bounding box, which is unknown at this point.\n    if patt.view_box.is_some()\n        && patt.units == Units::UserSpaceOnUse\n        && patt.content_units == Units::UserSpaceOnUse\n    {\n        let mut g = Group::empty();\n        g.transform = view_box.unwrap().to_transform(rect.size());\n        g.abs_transform = g.transform;\n\n        converter::convert_children(node_with_children, state, cache, &mut g);\n        if !g.has_children() {\n            return None;\n        }\n\n        g.calculate_bounding_boxes();\n        patt.root.children.push(Node::Group(Box::new(g)));\n    } else {\n        converter::convert_children(node_with_children, state, cache, &mut patt.root);\n        if !patt.root.has_children() {\n            return None;\n        }\n    }\n\n    patt.root.calculate_bounding_boxes();\n\n    Some(ServerOrColor::Server(Paint::Pattern(Arc::new(patt))))\n}\n\nfn convert_spread_method(node: SvgNode) -> SpreadMethod {\n    let node = resolve_attr(node, AId::SpreadMethod);\n    node.attribute(AId::SpreadMethod).unwrap_or_default()\n}\n\npub(crate) fn convert_units(node: SvgNode, name: AId, def: Units) -> Units {\n    let node = resolve_attr(node, name);\n    node.attribute(name).unwrap_or(def)\n}\n\nfn find_gradient_with_stops<'a, 'input: 'a>(\n    node: SvgNode<'a, 'input>,\n) -> Option<SvgNode<'a, 'input>> {\n    for link in node.href_iter() {\n        if !link.tag_name().unwrap().is_gradient() {\n            log::warn!(\n                \"Gradient '{}' cannot reference '{}' via 'xlink:href'.\",\n                node.element_id(),\n                link.tag_name().unwrap()\n            );\n            return None;\n        }\n\n        if link.children().any(|n| n.tag_name() == Some(EId::Stop)) {\n            return Some(link);\n        }\n    }\n\n    None\n}\n\nfn find_pattern_with_children<'a, 'input: 'a>(\n    node: SvgNode<'a, 'input>,\n) -> Option<SvgNode<'a, 'input>> {\n    for link in node.href_iter() {\n        if link.tag_name() != Some(EId::Pattern) {\n            log::warn!(\n                \"Pattern '{}' cannot reference '{}' via 'xlink:href'.\",\n                node.element_id(),\n                link.tag_name().unwrap()\n            );\n            return None;\n        }\n\n        if link.has_children() {\n            return Some(link);\n        }\n    }\n\n    None\n}\n\nfn convert_stops(grad: SvgNode) -> Vec<Stop> {\n    let mut stops = Vec::new();\n\n    {\n        let mut prev_offset = Length::zero();\n        for stop in grad.children() {\n            if stop.tag_name() != Some(EId::Stop) {\n                log::warn!(\"Invalid gradient child: '{:?}'.\", stop.tag_name().unwrap());\n                continue;\n            }\n\n            // `number` can be either a number or a percentage.\n            let offset = stop.attribute(AId::Offset).unwrap_or(prev_offset);\n            let offset = match offset.unit {\n                Unit::None => offset.number,\n                Unit::Percent => offset.number / 100.0,\n                _ => prev_offset.number,\n            };\n            prev_offset = Length::new_number(offset);\n            let offset = crate::f32_bound(0.0, offset as f32, 1.0);\n\n            let (color, opacity) = match stop.attribute(AId::StopColor) {\n                Some(\"currentColor\") => stop\n                    .find_attribute(AId::Color)\n                    .unwrap_or_else(svgtypes::Color::black),\n                Some(value) => {\n                    if let Ok(c) = svgtypes::Color::from_str(value) {\n                        c\n                    } else {\n                        log::warn!(\"Failed to parse stop-color value: '{}'.\", value);\n                        svgtypes::Color::black()\n                    }\n                }\n                _ => svgtypes::Color::black(),\n            }\n            .split_alpha();\n\n            let stop_opacity = stop\n                .attribute::<Opacity>(AId::StopOpacity)\n                .unwrap_or(Opacity::ONE);\n            stops.push(Stop {\n                offset: StopOffset::new_clamped(offset),\n                color,\n                opacity: opacity * stop_opacity,\n            });\n        }\n    }\n\n    // Remove stops with equal offset.\n    //\n    // Example:\n    // offset=\"0.5\"\n    // offset=\"0.7\"\n    // offset=\"0.7\" <-- this one should be removed\n    // offset=\"0.7\"\n    // offset=\"0.9\"\n    if stops.len() >= 3 {\n        let mut i = 0;\n        while i < stops.len() - 2 {\n            let offset1 = stops[i + 0].offset.get();\n            let offset2 = stops[i + 1].offset.get();\n            let offset3 = stops[i + 2].offset.get();\n\n            if offset1.approx_eq_ulps(&offset2, 4) && offset2.approx_eq_ulps(&offset3, 4) {\n                // Remove offset in the middle.\n                stops.remove(i + 1);\n            } else {\n                i += 1;\n            }\n        }\n    }\n\n    // Remove zeros.\n    //\n    // From:\n    // offset=\"0.0\"\n    // offset=\"0.0\"\n    // offset=\"0.7\"\n    //\n    // To:\n    // offset=\"0.0\"\n    // offset=\"0.00000001\"\n    // offset=\"0.7\"\n    if stops.len() >= 2 {\n        let mut i = 0;\n        while i < stops.len() - 1 {\n            let offset1 = stops[i + 0].offset.get();\n            let offset2 = stops[i + 1].offset.get();\n\n            if offset1.approx_eq_ulps(&0.0, 4) && offset2.approx_eq_ulps(&0.0, 4) {\n                stops[i + 1].offset = StopOffset::new_clamped(offset1 + f32::EPSILON);\n            }\n\n            i += 1;\n        }\n    }\n\n    // Shift equal offsets.\n    //\n    // From:\n    // offset=\"0.5\"\n    // offset=\"0.7\"\n    // offset=\"0.7\"\n    //\n    // To:\n    // offset=\"0.5\"\n    // offset=\"0.699999999\"\n    // offset=\"0.7\"\n    {\n        let mut i = 1;\n        while i < stops.len() {\n            let offset1 = stops[i - 1].offset.get();\n            let offset2 = stops[i - 0].offset.get();\n\n            // Next offset must be smaller then previous.\n            if offset1 > offset2 || offset1.approx_eq_ulps(&offset2, 4) {\n                // Make previous offset a bit smaller.\n                let new_offset = offset1 - f32::EPSILON;\n                stops[i - 1].offset = StopOffset::new_clamped(new_offset);\n                stops[i - 0].offset = StopOffset::new_clamped(offset1);\n            }\n\n            i += 1;\n        }\n    }\n\n    stops\n}\n\n#[inline(never)]\npub(crate) fn resolve_number(\n    node: SvgNode,\n    name: AId,\n    units: Units,\n    state: &converter::State,\n    def: Length,\n) -> f32 {\n    resolve_attr(node, name).convert_length(name, units, state, def)\n}\n\nfn resolve_attr<'a, 'input: 'a>(node: SvgNode<'a, 'input>, name: AId) -> SvgNode<'a, 'input> {\n    if node.has_attribute(name) {\n        return node;\n    }\n\n    match node.tag_name().unwrap() {\n        EId::LinearGradient => resolve_lg_attr(node, name),\n        EId::RadialGradient => resolve_rg_attr(node, name),\n        EId::Pattern => resolve_pattern_attr(node, name),\n        EId::Filter => resolve_filter_attr(node, name),\n        _ => node,\n    }\n}\n\nfn resolve_lg_attr<'a, 'input: 'a>(node: SvgNode<'a, 'input>, name: AId) -> SvgNode<'a, 'input> {\n    for link in node.href_iter() {\n        let tag_name = match link.tag_name() {\n            Some(v) => v,\n            None => return node,\n        };\n\n        match (name, tag_name) {\n            // Coordinates can be resolved only from\n            // ref element with the same type.\n              (AId::X1, EId::LinearGradient)\n            | (AId::Y1, EId::LinearGradient)\n            | (AId::X2, EId::LinearGradient)\n            | (AId::Y2, EId::LinearGradient)\n            // Other attributes can be resolved\n            // from any kind of gradient.\n            | (AId::GradientUnits, EId::LinearGradient)\n            | (AId::GradientUnits, EId::RadialGradient)\n            | (AId::SpreadMethod, EId::LinearGradient)\n            | (AId::SpreadMethod, EId::RadialGradient)\n            | (AId::GradientTransform, EId::LinearGradient)\n            | (AId::GradientTransform, EId::RadialGradient) => {\n                if link.has_attribute(name) {\n                    return link;\n                }\n            }\n            _ => break,\n        }\n    }\n\n    node\n}\n\nfn resolve_rg_attr<'a, 'input>(node: SvgNode<'a, 'input>, name: AId) -> SvgNode<'a, 'input> {\n    for link in node.href_iter() {\n        let tag_name = match link.tag_name() {\n            Some(v) => v,\n            None => return node,\n        };\n\n        match (name, tag_name) {\n            // Coordinates can be resolved only from\n            // ref element with the same type.\n              (AId::Cx, EId::RadialGradient)\n            | (AId::Cy, EId::RadialGradient)\n            | (AId::R,  EId::RadialGradient)\n            | (AId::Fx, EId::RadialGradient)\n            | (AId::Fy, EId::RadialGradient)\n            // Other attributes can be resolved\n            // from any kind of gradient.\n            | (AId::GradientUnits, EId::LinearGradient)\n            | (AId::GradientUnits, EId::RadialGradient)\n            | (AId::SpreadMethod, EId::LinearGradient)\n            | (AId::SpreadMethod, EId::RadialGradient)\n            | (AId::GradientTransform, EId::LinearGradient)\n            | (AId::GradientTransform, EId::RadialGradient) => {\n                if link.has_attribute(name) {\n                    return link;\n                }\n            }\n            _ => break,\n        }\n    }\n\n    node\n}\n\nfn resolve_pattern_attr<'a, 'input: 'a>(\n    node: SvgNode<'a, 'input>,\n    name: AId,\n) -> SvgNode<'a, 'input> {\n    for link in node.href_iter() {\n        let tag_name = match link.tag_name() {\n            Some(v) => v,\n            None => return node,\n        };\n\n        if tag_name != EId::Pattern {\n            break;\n        }\n\n        if link.has_attribute(name) {\n            return link;\n        }\n    }\n\n    node\n}\n\nfn resolve_filter_attr<'a, 'input: 'a>(node: SvgNode<'a, 'input>, aid: AId) -> SvgNode<'a, 'input> {\n    for link in node.href_iter() {\n        let tag_name = match link.tag_name() {\n            Some(v) => v,\n            None => return node,\n        };\n\n        if tag_name != EId::Filter {\n            break;\n        }\n\n        if link.has_attribute(aid) {\n            return link;\n        }\n    }\n\n    node\n}\n\nfn stops_to_color(stops: &[Stop]) -> Option<ServerOrColor> {\n    if stops.is_empty() {\n        None\n    } else {\n        Some(ServerOrColor::Color {\n            color: stops[0].color,\n            opacity: stops[0].opacity,\n        })\n    }\n}\n\n// Update paints servers by doing the following:\n// 1. Replace context fills/strokes that are linked to\n// a use node with their actual values.\n// 2. Convert all object units to UserSpaceOnUse\npub fn update_paint_servers(\n    group: &mut Group,\n    context_transform: Transform,\n    context_bbox: Option<Rect>,\n    text_bbox: Option<Rect>,\n    cache: &mut Cache,\n) {\n    for child in &mut group.children {\n        // Set context transform and bbox if applicable if the\n        // current group is a use node.\n        let (context_transform, context_bbox) = if group.is_context_element {\n            (group.abs_transform, Some(group.bounding_box))\n        } else {\n            (context_transform, context_bbox)\n        };\n\n        node_to_user_coordinates(child, context_transform, context_bbox, text_bbox, cache);\n    }\n}\n\n// When parsing clipPaths, masks and filters we already know group's bounding box.\n// But with gradients and patterns we don't, because we have to know text bounding box\n// before we even parsed it. Which is impossible.\n// Therefore our only choice is to parse gradients and patterns preserving their units\n// and then replace them with `userSpaceOnUse` after the whole tree parsing is finished.\n// So while gradients and patterns do still store their units,\n// they are not exposed in the public API and for the caller they are always `userSpaceOnUse`.\nfn node_to_user_coordinates(\n    node: &mut Node,\n    context_transform: Transform,\n    context_bbox: Option<Rect>,\n    text_bbox: Option<Rect>,\n    cache: &mut Cache,\n) {\n    match node {\n        Node::Group(g) => {\n            // No need to check clip paths, because they cannot have paint servers.\n            if let Some(mask) = &mut g.mask {\n                if let Some(mask) = Arc::get_mut(mask) {\n                    update_paint_servers(\n                        &mut mask.root,\n                        context_transform,\n                        context_bbox,\n                        None,\n                        cache,\n                    );\n\n                    if let Some(sub_mask) = &mut mask.mask {\n                        if let Some(sub_mask) = Arc::get_mut(sub_mask) {\n                            update_paint_servers(\n                                &mut sub_mask.root,\n                                context_transform,\n                                context_bbox,\n                                None,\n                                cache,\n                            );\n                        }\n                    }\n                }\n            }\n\n            for filter in &mut g.filters {\n                if let Some(filter) = Arc::get_mut(filter) {\n                    for primitive in &mut filter.primitives {\n                        if let filter::Kind::Image(image) = &mut primitive.kind {\n                            update_paint_servers(\n                                &mut image.root,\n                                context_transform,\n                                context_bbox,\n                                None,\n                                cache,\n                            );\n                        }\n                    }\n                }\n            }\n\n            update_paint_servers(g, context_transform, context_bbox, text_bbox, cache);\n        }\n        Node::Path(path) => {\n            // Paths inside `Text::flattened` are special and must use text's bounding box\n            // instead of their own.\n            let bbox = text_bbox.unwrap_or(path.bounding_box);\n\n            process_fill(\n                &mut path.fill,\n                path.abs_transform,\n                context_transform,\n                context_bbox,\n                bbox,\n                cache,\n            );\n            process_stroke(\n                &mut path.stroke,\n                path.abs_transform,\n                context_transform,\n                context_bbox,\n                bbox,\n                cache,\n            );\n        }\n        Node::Image(image) => {\n            if let ImageKind::SVG(tree) = &mut image.kind {\n                update_paint_servers(&mut tree.root, context_transform, context_bbox, None, cache);\n            }\n        }\n        Node::Text(text) => {\n            // By the SVG spec, `tspan` doesn't have a bbox and uses the parent `text` bbox.\n            // Therefore we have to use text's bbox when converting tspan and flatted text\n            // paint servers.\n            let bbox = text.bounding_box;\n\n            // We need to update three things:\n            // 1. The fills/strokes of the original elements in the usvg tree.\n            // 2. The fills/strokes of the layouted elements of the text.\n            // 3. The fills/strokes of the outlined text.\n\n            // 1.\n            for chunk in &mut text.chunks {\n                for span in &mut chunk.spans {\n                    process_fill(\n                        &mut span.fill,\n                        text.abs_transform,\n                        context_transform,\n                        context_bbox,\n                        bbox,\n                        cache,\n                    );\n                    process_stroke(\n                        &mut span.stroke,\n                        text.abs_transform,\n                        context_transform,\n                        context_bbox,\n                        bbox,\n                        cache,\n                    );\n                    process_text_decoration(&mut span.decoration.underline, bbox, cache);\n                    process_text_decoration(&mut span.decoration.overline, bbox, cache);\n                    process_text_decoration(&mut span.decoration.line_through, bbox, cache);\n                }\n            }\n\n            // 2.\n            #[cfg(feature = \"text\")]\n            for span in &mut text.layouted {\n                process_fill(\n                    &mut span.fill,\n                    text.abs_transform,\n                    context_transform,\n                    context_bbox,\n                    bbox,\n                    cache,\n                );\n                process_stroke(\n                    &mut span.stroke,\n                    text.abs_transform,\n                    context_transform,\n                    context_bbox,\n                    bbox,\n                    cache,\n                );\n\n                let mut process_decoration = |path: &mut Path| {\n                    process_fill(\n                        &mut path.fill,\n                        text.abs_transform,\n                        context_transform,\n                        context_bbox,\n                        bbox,\n                        cache,\n                    );\n                    process_stroke(\n                        &mut path.stroke,\n                        text.abs_transform,\n                        context_transform,\n                        context_bbox,\n                        bbox,\n                        cache,\n                    );\n                };\n\n                if let Some(path) = &mut span.overline {\n                    process_decoration(path);\n                }\n\n                if let Some(path) = &mut span.underline {\n                    process_decoration(path);\n                }\n\n                if let Some(path) = &mut span.line_through {\n                    process_decoration(path);\n                }\n            }\n\n            // 3.\n            update_paint_servers(\n                &mut text.flattened,\n                context_transform,\n                context_bbox,\n                Some(bbox),\n                cache,\n            );\n        }\n    }\n}\n\nfn process_fill(\n    fill: &mut Option<Fill>,\n    path_transform: Transform,\n    context_transform: Transform,\n    context_bbox: Option<Rect>,\n    bbox: Rect,\n    cache: &mut Cache,\n) {\n    let mut ok = false;\n    if let Some(fill) = fill.as_mut() {\n        // Path context elements (i.e. for  markers) have already been resolved,\n        // so we only care about use nodes.\n        ok = process_paint(\n            &mut fill.paint,\n            matches!(fill.context_element, Some(ContextElement::UseNode)),\n            context_transform,\n            context_bbox,\n            path_transform,\n            bbox,\n            cache,\n        );\n    }\n    if !ok {\n        *fill = None;\n    }\n}\n\nfn process_stroke(\n    stroke: &mut Option<Stroke>,\n    path_transform: Transform,\n    context_transform: Transform,\n    context_bbox: Option<Rect>,\n    bbox: Rect,\n    cache: &mut Cache,\n) {\n    let mut ok = false;\n    if let Some(stroke) = stroke.as_mut() {\n        // Path context elements (i.e. for  markers) have already been resolved,\n        // so we only care about use nodes.\n        ok = process_paint(\n            &mut stroke.paint,\n            matches!(stroke.context_element, Some(ContextElement::UseNode)),\n            context_transform,\n            context_bbox,\n            path_transform,\n            bbox,\n            cache,\n        );\n    }\n    if !ok {\n        *stroke = None;\n    }\n}\n\nfn process_context_paint(\n    paint: &mut Paint,\n    context_transform: Transform,\n    path_transform: Transform,\n    cache: &mut Cache,\n) -> Option<()> {\n    // The idea is the following: We have a certain context element that has\n    // a transform A, and further below in the tree we have for example a path\n    // whose paint has a transform C. In order to get from A to C, there is some\n    // transformation matrix B such that A x B = C. We now need to figure out\n    // a way to get from C back to A, so that the transformation of the paint\n    // matches the one from the context element, even if B was applied. How\n    // do we do that? We calculate CxB^(-1), which will overall then have\n    // the same effect as A. How do we calculate B^(-1)?\n    // --> (A^(-1)xC)^(-1)\n    let rev_transform = context_transform\n        .invert()?\n        .pre_concat(path_transform)\n        .invert()?;\n\n    match paint {\n        Paint::Color(_) => {}\n        Paint::LinearGradient(lg) => {\n            let transform = lg.transform.post_concat(rev_transform);\n            *paint = Paint::LinearGradient(Arc::new(LinearGradient {\n                x1: lg.x1,\n                y1: lg.y1,\n                x2: lg.x2,\n                y2: lg.y2,\n                base: BaseGradient {\n                    id: cache.gen_linear_gradient_id(),\n                    units: lg.units,\n                    transform,\n                    spread_method: lg.spread_method,\n                    stops: lg.stops.clone(),\n                },\n            }));\n        }\n        Paint::RadialGradient(rg) => {\n            let transform = rg.transform.post_concat(rev_transform);\n            *paint = Paint::RadialGradient(Arc::new(RadialGradient {\n                cx: rg.cx,\n                cy: rg.cy,\n                r: rg.r,\n                fx: rg.fx,\n                fy: rg.fy,\n                fr: rg.fr,\n                base: BaseGradient {\n                    id: cache.gen_radial_gradient_id(),\n                    units: rg.units,\n                    transform,\n                    spread_method: rg.spread_method,\n                    stops: rg.stops.clone(),\n                },\n            }));\n        }\n        Paint::Pattern(pat) => {\n            let transform = pat.transform.post_concat(rev_transform);\n            *paint = Paint::Pattern(Arc::new(Pattern {\n                id: cache.gen_pattern_id(),\n                units: pat.units,\n                content_units: pat.content_units,\n                transform,\n                rect: pat.rect,\n                view_box: pat.view_box,\n                root: pat.root.clone(),\n            }));\n        }\n    }\n\n    Some(())\n}\n\npub(crate) fn process_paint(\n    paint: &mut Paint,\n    has_context: bool,\n    context_transform: Transform,\n    context_bbox: Option<Rect>,\n    path_transform: Transform,\n    bbox: Rect,\n    cache: &mut Cache,\n) -> bool {\n    if paint.units() == Units::ObjectBoundingBox\n        || paint.content_units() == Units::ObjectBoundingBox\n    {\n        let bbox = if has_context {\n            let Some(bbox) = context_bbox else {\n                return false;\n            };\n            bbox\n        } else {\n            bbox\n        };\n\n        if paint.to_user_coordinates(bbox, cache).is_none() {\n            return false;\n        }\n    }\n\n    if let Paint::Pattern(patt) = paint {\n        if let Some(patt) = Arc::get_mut(patt) {\n            update_paint_servers(&mut patt.root, Transform::default(), None, None, cache);\n        }\n    }\n\n    if has_context {\n        process_context_paint(paint, context_transform, path_transform, cache);\n    }\n\n    true\n}\n\nfn process_text_decoration(style: &mut Option<TextDecorationStyle>, bbox: Rect, cache: &mut Cache) {\n    if let Some(style) = style.as_mut() {\n        process_fill(\n            &mut style.fill,\n            Transform::default(),\n            Transform::default(),\n            None,\n            bbox,\n            cache,\n        );\n        process_stroke(\n            &mut style.stroke,\n            Transform::default(),\n            Transform::default(),\n            None,\n            bbox,\n            cache,\n        );\n    }\n}\n\nimpl Paint {\n    fn to_user_coordinates(&mut self, bbox: Rect, cache: &mut Cache) -> Option<()> {\n        let name = if matches!(self, Paint::Pattern(_)) {\n            \"Pattern\"\n        } else {\n            \"Gradient\"\n        };\n        let bbox = bbox\n            .to_non_zero_rect()\n            .log_none(|| log::warn!(\"{} on zero-sized shapes is not allowed.\", name))?;\n\n        // `Arc::get_mut()` allow us to modify some paint servers in-place.\n        // This reduces the amount of cloning and preserves the original ID as well.\n        match self {\n            Paint::Color(_) => {} // unreachable\n            Paint::LinearGradient(lg) => {\n                let transform = lg.transform.post_concat(Transform::from_bbox(bbox));\n                if let Some(lg) = Arc::get_mut(lg) {\n                    lg.base.transform = transform;\n                    lg.base.units = Units::UserSpaceOnUse;\n                } else {\n                    *lg = Arc::new(LinearGradient {\n                        x1: lg.x1,\n                        y1: lg.y1,\n                        x2: lg.x2,\n                        y2: lg.y2,\n                        base: BaseGradient {\n                            id: cache.gen_linear_gradient_id(),\n                            units: Units::UserSpaceOnUse,\n                            transform,\n                            spread_method: lg.spread_method,\n                            stops: lg.stops.clone(),\n                        },\n                    });\n                }\n            }\n            Paint::RadialGradient(rg) => {\n                let transform = rg.transform.post_concat(Transform::from_bbox(bbox));\n                if let Some(rg) = Arc::get_mut(rg) {\n                    rg.base.transform = transform;\n                    rg.base.units = Units::UserSpaceOnUse;\n                } else {\n                    *rg = Arc::new(RadialGradient {\n                        cx: rg.cx,\n                        cy: rg.cy,\n                        r: rg.r,\n                        fx: rg.fx,\n                        fy: rg.fy,\n                        fr: rg.fr,\n                        base: BaseGradient {\n                            id: cache.gen_radial_gradient_id(),\n                            units: Units::UserSpaceOnUse,\n                            transform,\n                            spread_method: rg.spread_method,\n                            stops: rg.stops.clone(),\n                        },\n                    });\n                }\n            }\n            Paint::Pattern(patt) => {\n                let rect = if patt.units == Units::ObjectBoundingBox {\n                    patt.rect.bbox_transform(bbox)\n                } else {\n                    patt.rect\n                };\n\n                if let Some(patt) = Arc::get_mut(patt) {\n                    patt.rect = rect;\n                    patt.units = Units::UserSpaceOnUse;\n\n                    if patt.content_units == Units::ObjectBoundingBox && patt.view_box.is_none() {\n                        // No need to shift patterns.\n                        let transform = Transform::from_scale(bbox.width(), bbox.height());\n                        push_pattern_transform(&mut patt.root, transform);\n                    }\n\n                    if let Some(view_box) = patt.view_box {\n                        push_pattern_transform(&mut patt.root, view_box.to_transform(rect.size()));\n                    }\n\n                    patt.content_units = Units::UserSpaceOnUse;\n                } else {\n                    let mut root = if patt.content_units == Units::ObjectBoundingBox\n                        && patt.view_box.is_none()\n                    {\n                        // No need to shift patterns.\n                        let transform = Transform::from_scale(bbox.width(), bbox.height());\n\n                        let mut g = patt.root.clone();\n                        push_pattern_transform(&mut g, transform);\n                        g\n                    } else {\n                        patt.root.clone()\n                    };\n\n                    if let Some(view_box) = patt.view_box {\n                        push_pattern_transform(&mut root, view_box.to_transform(rect.size()));\n                    }\n\n                    *patt = Arc::new(Pattern {\n                        id: cache.gen_pattern_id(),\n                        units: Units::UserSpaceOnUse,\n                        content_units: Units::UserSpaceOnUse,\n                        transform: patt.transform,\n                        rect,\n                        view_box: patt.view_box,\n                        root,\n                    });\n                }\n            }\n        }\n\n        Some(())\n    }\n}\n\nfn push_pattern_transform(root: &mut Group, transform: Transform) {\n    // TODO: we should update abs_transform in all descendants as well\n    let mut g = std::mem::replace(root, Group::empty());\n    g.transform = transform;\n    g.abs_transform = transform;\n\n    root.children.push(Node::Group(Box::new(g)));\n    root.calculate_bounding_boxes();\n}\n\nimpl Paint {\n    #[inline]\n    pub(crate) fn units(&self) -> Units {\n        match self {\n            Self::Color(_) => Units::UserSpaceOnUse,\n            Self::LinearGradient(lg) => lg.units,\n            Self::RadialGradient(rg) => rg.units,\n            Self::Pattern(patt) => patt.units,\n        }\n    }\n\n    #[inline]\n    pub(crate) fn content_units(&self) -> Units {\n        match self {\n            Self::Pattern(patt) => patt.content_units,\n            _ => Units::UserSpaceOnUse,\n        }\n    }\n}\n"
  },
  {
    "path": "crates/usvg/src/parser/shapes.rs",
    "content": "// Copyright 2018 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse std::sync::Arc;\n\nuse svgtypes::Length;\nuse tiny_skia_path::Path;\n\nuse super::svgtree::{AId, EId, SvgNode};\nuse super::{converter, units};\nuse crate::{ApproxEqUlps, IsValidLength, Rect};\n\npub(crate) fn convert(node: SvgNode, state: &converter::State) -> Option<Arc<Path>> {\n    match node.tag_name()? {\n        EId::Rect => convert_rect(node, state),\n        EId::Circle => convert_circle(node, state),\n        EId::Ellipse => convert_ellipse(node, state),\n        EId::Line => convert_line(node, state),\n        EId::Polyline => convert_polyline(node),\n        EId::Polygon => convert_polygon(node),\n        EId::Path => convert_path(node),\n        _ => None,\n    }\n}\n\npub(crate) fn convert_path(node: SvgNode) -> Option<Arc<Path>> {\n    let value: &str = node.attribute(AId::D)?;\n    let mut builder = tiny_skia_path::PathBuilder::new();\n    for segment in svgtypes::SimplifyingPathParser::from(value) {\n        let segment = match segment {\n            Ok(v) => v,\n            Err(_) => break,\n        };\n\n        match segment {\n            svgtypes::SimplePathSegment::MoveTo { x, y } => {\n                builder.move_to(x as f32, y as f32);\n            }\n            svgtypes::SimplePathSegment::LineTo { x, y } => {\n                builder.line_to(x as f32, y as f32);\n            }\n            svgtypes::SimplePathSegment::Quadratic { x1, y1, x, y } => {\n                builder.quad_to(x1 as f32, y1 as f32, x as f32, y as f32);\n            }\n            svgtypes::SimplePathSegment::CurveTo {\n                x1,\n                y1,\n                x2,\n                y2,\n                x,\n                y,\n            } => {\n                builder.cubic_to(\n                    x1 as f32, y1 as f32, x2 as f32, y2 as f32, x as f32, y as f32,\n                );\n            }\n            svgtypes::SimplePathSegment::ClosePath => {\n                builder.close();\n            }\n        }\n    }\n\n    builder.finish().map(Arc::new)\n}\n\nfn convert_rect(node: SvgNode, state: &converter::State) -> Option<Arc<Path>> {\n    // 'width' and 'height' attributes must be positive and non-zero.\n    let width = node.convert_user_length(AId::Width, state, Length::zero());\n    let height = node.convert_user_length(AId::Height, state, Length::zero());\n    if !width.is_valid_length() {\n        log::warn!(\n            \"Rect '{}' has an invalid 'width' value. Skipped.\",\n            node.element_id()\n        );\n        return None;\n    }\n    if !height.is_valid_length() {\n        log::warn!(\n            \"Rect '{}' has an invalid 'height' value. Skipped.\",\n            node.element_id()\n        );\n        return None;\n    }\n\n    let x = node.convert_user_length(AId::X, state, Length::zero());\n    let y = node.convert_user_length(AId::Y, state, Length::zero());\n\n    let (mut rx, mut ry) = resolve_rx_ry(node, state);\n\n    // Clamp rx/ry to the half of the width/height.\n    //\n    // Should be done only after resolving.\n    if rx > width / 2.0 {\n        rx = width / 2.0;\n    }\n    if ry > height / 2.0 {\n        ry = height / 2.0;\n    }\n\n    // Conversion according to https://www.w3.org/TR/SVG11/shapes.html#RectElement\n    let path = if rx.approx_eq_ulps(&0.0, 4) {\n        tiny_skia_path::PathBuilder::from_rect(Rect::from_xywh(x, y, width, height)?)\n    } else {\n        let mut builder = tiny_skia_path::PathBuilder::new();\n        builder.move_to(x + rx, y);\n\n        builder.line_to(x + width - rx, y);\n        builder.arc_to(rx, ry, 0.0, false, true, x + width, y + ry);\n\n        builder.line_to(x + width, y + height - ry);\n        builder.arc_to(rx, ry, 0.0, false, true, x + width - rx, y + height);\n\n        builder.line_to(x + rx, y + height);\n        builder.arc_to(rx, ry, 0.0, false, true, x, y + height - ry);\n\n        builder.line_to(x, y + ry);\n        builder.arc_to(rx, ry, 0.0, false, true, x + rx, y);\n\n        builder.close();\n\n        builder.finish()?\n    };\n\n    Some(Arc::new(path))\n}\n\nfn resolve_rx_ry(node: SvgNode, state: &converter::State) -> (f32, f32) {\n    let mut rx_opt = node.attribute::<Length>(AId::Rx);\n    let mut ry_opt = node.attribute::<Length>(AId::Ry);\n\n    // Remove negative values first.\n    if let Some(v) = rx_opt {\n        if v.number.is_sign_negative() {\n            rx_opt = None;\n        }\n    }\n    if let Some(v) = ry_opt {\n        if v.number.is_sign_negative() {\n            ry_opt = None;\n        }\n    }\n\n    // Resolve.\n    match (rx_opt, ry_opt) {\n        (None, None) => (0.0, 0.0),\n        (Some(rx), None) => {\n            let rx = units::convert_user_length(rx, node, AId::Rx, state);\n            (rx, rx)\n        }\n        (None, Some(ry)) => {\n            let ry = units::convert_user_length(ry, node, AId::Ry, state);\n            (ry, ry)\n        }\n        (Some(rx), Some(ry)) => {\n            let rx = units::convert_user_length(rx, node, AId::Rx, state);\n            let ry = units::convert_user_length(ry, node, AId::Ry, state);\n            (rx, ry)\n        }\n    }\n}\n\nfn convert_line(node: SvgNode, state: &converter::State) -> Option<Arc<Path>> {\n    let x1 = node.convert_user_length(AId::X1, state, Length::zero());\n    let y1 = node.convert_user_length(AId::Y1, state, Length::zero());\n    let x2 = node.convert_user_length(AId::X2, state, Length::zero());\n    let y2 = node.convert_user_length(AId::Y2, state, Length::zero());\n\n    let mut builder = tiny_skia_path::PathBuilder::new();\n    builder.move_to(x1, y1);\n    builder.line_to(x2, y2);\n    builder.finish().map(Arc::new)\n}\n\nfn convert_polyline(node: SvgNode) -> Option<Arc<Path>> {\n    let builder = points_to_path(node, \"Polyline\")?;\n    builder.finish().map(Arc::new)\n}\n\nfn convert_polygon(node: SvgNode) -> Option<Arc<Path>> {\n    let mut builder = points_to_path(node, \"Polygon\")?;\n    builder.close();\n    builder.finish().map(Arc::new)\n}\n\nfn points_to_path(node: SvgNode, eid: &str) -> Option<tiny_skia_path::PathBuilder> {\n    use svgtypes::PointsParser;\n\n    let mut builder = tiny_skia_path::PathBuilder::new();\n    match node.attribute::<&str>(AId::Points) {\n        Some(text) => {\n            for (x, y) in PointsParser::from(text) {\n                if builder.is_empty() {\n                    builder.move_to(x as f32, y as f32);\n                } else {\n                    builder.line_to(x as f32, y as f32);\n                }\n            }\n        }\n        _ => {\n            log::warn!(\n                \"{} '{}' has an invalid 'points' value. Skipped.\",\n                eid,\n                node.element_id()\n            );\n            return None;\n        }\n    };\n\n    // 'polyline' and 'polygon' elements must contain at least 2 points.\n    if builder.len() < 2 {\n        log::warn!(\n            \"{} '{}' has less than 2 points. Skipped.\",\n            eid,\n            node.element_id()\n        );\n        return None;\n    }\n\n    Some(builder)\n}\n\nfn convert_circle(node: SvgNode, state: &converter::State) -> Option<Arc<Path>> {\n    let cx = node.convert_user_length(AId::Cx, state, Length::zero());\n    let cy = node.convert_user_length(AId::Cy, state, Length::zero());\n    let r = node.convert_user_length(AId::R, state, Length::zero());\n\n    if !r.is_valid_length() {\n        log::warn!(\n            \"Circle '{}' has an invalid 'r' value. Skipped.\",\n            node.element_id()\n        );\n        return None;\n    }\n\n    ellipse_to_path(cx, cy, r, r)\n}\n\nfn convert_ellipse(node: SvgNode, state: &converter::State) -> Option<Arc<Path>> {\n    let cx = node.convert_user_length(AId::Cx, state, Length::zero());\n    let cy = node.convert_user_length(AId::Cy, state, Length::zero());\n    let (rx, ry) = resolve_rx_ry(node, state);\n\n    if !rx.is_valid_length() {\n        log::warn!(\n            \"Ellipse '{}' has an invalid 'rx' value. Skipped.\",\n            node.element_id()\n        );\n        return None;\n    }\n\n    if !ry.is_valid_length() {\n        log::warn!(\n            \"Ellipse '{}' has an invalid 'ry' value. Skipped.\",\n            node.element_id()\n        );\n        return None;\n    }\n\n    ellipse_to_path(cx, cy, rx, ry)\n}\n\nfn ellipse_to_path(cx: f32, cy: f32, rx: f32, ry: f32) -> Option<Arc<Path>> {\n    let mut builder = tiny_skia_path::PathBuilder::new();\n    builder.move_to(cx + rx, cy);\n    builder.arc_to(rx, ry, 0.0, false, true, cx, cy + ry);\n    builder.arc_to(rx, ry, 0.0, false, true, cx - rx, cy);\n    builder.arc_to(rx, ry, 0.0, false, true, cx, cy - ry);\n    builder.arc_to(rx, ry, 0.0, false, true, cx + rx, cy);\n    builder.close();\n    builder.finish().map(Arc::new)\n}\n\ntrait PathBuilderExt {\n    fn arc_to(\n        &mut self,\n        rx: f32,\n        ry: f32,\n        x_axis_rotation: f32,\n        large_arc: bool,\n        sweep: bool,\n        x: f32,\n        y: f32,\n    );\n}\n\nimpl PathBuilderExt for tiny_skia_path::PathBuilder {\n    fn arc_to(\n        &mut self,\n        rx: f32,\n        ry: f32,\n        x_axis_rotation: f32,\n        large_arc: bool,\n        sweep: bool,\n        x: f32,\n        y: f32,\n    ) {\n        let prev = match self.last_point() {\n            Some(v) => v,\n            None => return,\n        };\n\n        let svg_arc = kurbo::SvgArc {\n            from: kurbo::Point::new(prev.x as f64, prev.y as f64),\n            to: kurbo::Point::new(x as f64, y as f64),\n            radii: kurbo::Vec2::new(rx as f64, ry as f64),\n            x_rotation: (x_axis_rotation as f64).to_radians(),\n            large_arc,\n            sweep,\n        };\n\n        match kurbo::Arc::from_svg_arc(&svg_arc) {\n            Some(arc) => {\n                arc.to_cubic_beziers(0.1, |p1, p2, p| {\n                    self.cubic_to(\n                        p1.x as f32,\n                        p1.y as f32,\n                        p2.x as f32,\n                        p2.y as f32,\n                        p.x as f32,\n                        p.y as f32,\n                    );\n                });\n            }\n            None => {\n                self.line_to(x, y);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "crates/usvg/src/parser/style.rs",
    "content": "// Copyright 2018 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse super::converter::{self, SvgColorExt};\nuse super::paint_server;\nuse super::svgtree::{AId, FromValue, SvgNode};\nuse crate::tree::ContextElement;\nuse crate::{\n    ApproxEqUlps, Color, Fill, FillRule, LineCap, LineJoin, Opacity, Paint, Stroke,\n    StrokeMiterlimit, Units,\n};\n\nimpl<'a, 'input: 'a> FromValue<'a, 'input> for LineCap {\n    fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {\n        match value {\n            \"butt\" => Some(LineCap::Butt),\n            \"round\" => Some(LineCap::Round),\n            \"square\" => Some(LineCap::Square),\n            _ => None,\n        }\n    }\n}\n\nimpl<'a, 'input: 'a> FromValue<'a, 'input> for LineJoin {\n    fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {\n        match value {\n            \"miter\" => Some(LineJoin::Miter),\n            \"miter-clip\" => Some(LineJoin::MiterClip),\n            \"round\" => Some(LineJoin::Round),\n            \"bevel\" => Some(LineJoin::Bevel),\n            _ => None,\n        }\n    }\n}\n\nimpl<'a, 'input: 'a> FromValue<'a, 'input> for FillRule {\n    fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {\n        match value {\n            \"nonzero\" => Some(FillRule::NonZero),\n            \"evenodd\" => Some(FillRule::EvenOdd),\n            _ => None,\n        }\n    }\n}\n\npub(crate) fn resolve_fill(\n    node: SvgNode,\n    has_bbox: bool,\n    state: &converter::State,\n    cache: &mut converter::Cache,\n) -> Option<Fill> {\n    if state.parent_clip_path.is_some() {\n        // A `clipPath` child can be filled only with a black color.\n        return Some(Fill {\n            paint: Paint::Color(Color::black()),\n            opacity: Opacity::ONE,\n            rule: node.find_attribute(AId::ClipRule).unwrap_or_default(),\n            context_element: None,\n        });\n    }\n\n    let mut sub_opacity = Opacity::ONE;\n    let (paint, context_element) =\n        if let Some(n) = node.ancestors().find(|n| n.has_attribute(AId::Fill)) {\n            let value: &str = n.attribute(AId::Fill)?;\n            convert_paint(\n                node,\n                value,\n                AId::Fill,\n                has_bbox,\n                state,\n                &mut sub_opacity,\n                cache,\n            )?\n        } else {\n            (Paint::Color(Color::black()), None)\n        };\n\n    let fill_opacity = node\n        .find_attribute::<Opacity>(AId::FillOpacity)\n        .unwrap_or(Opacity::ONE);\n\n    Some(Fill {\n        paint,\n        opacity: sub_opacity * fill_opacity,\n        rule: node.find_attribute(AId::FillRule).unwrap_or_default(),\n        context_element,\n    })\n}\n\npub(crate) fn resolve_stroke(\n    node: SvgNode,\n    has_bbox: bool,\n    state: &converter::State,\n    cache: &mut converter::Cache,\n) -> Option<Stroke> {\n    if state.parent_clip_path.is_some() {\n        // A `clipPath` child cannot be stroked.\n        return None;\n    }\n\n    let mut sub_opacity = Opacity::ONE;\n    let (paint, context_element) =\n        if let Some(n) = node.ancestors().find(|n| n.has_attribute(AId::Stroke)) {\n            let value: &str = n.attribute(AId::Stroke)?;\n\n            convert_paint(\n                node,\n                value,\n                AId::Stroke,\n                has_bbox,\n                state,\n                &mut sub_opacity,\n                cache,\n            )?\n        } else {\n            return None;\n        };\n\n    let width = node.resolve_valid_length(AId::StrokeWidth, state, 1.0)?;\n\n    // Must be bigger than 1.\n    let miterlimit = node.find_attribute(AId::StrokeMiterlimit).unwrap_or(4.0);\n    let miterlimit = if miterlimit < 1.0 { 1.0 } else { miterlimit };\n    let miterlimit = StrokeMiterlimit::new(miterlimit);\n\n    let stroke_opacity = node\n        .find_attribute::<Opacity>(AId::StrokeOpacity)\n        .unwrap_or(Opacity::ONE);\n\n    let stroke = Stroke {\n        paint,\n        dasharray: conv_dasharray(node, state),\n        dashoffset: node.resolve_length(AId::StrokeDashoffset, state, 0.0),\n        miterlimit,\n        opacity: sub_opacity * stroke_opacity,\n        width,\n        linecap: node.find_attribute(AId::StrokeLinecap).unwrap_or_default(),\n        linejoin: node.find_attribute(AId::StrokeLinejoin).unwrap_or_default(),\n        context_element,\n    };\n\n    Some(stroke)\n}\n\nfn convert_paint(\n    node: SvgNode,\n    value: &str,\n    aid: AId,\n    has_bbox: bool,\n    state: &converter::State,\n    opacity: &mut Opacity,\n    cache: &mut converter::Cache,\n) -> Option<(Paint, Option<ContextElement>)> {\n    let paint = match svgtypes::Paint::from_str(value) {\n        Ok(v) => v,\n        Err(_) => {\n            if aid == AId::Fill {\n                log::warn!(\n                    \"Failed to parse fill value: '{}'. Fallback to black.\",\n                    value\n                );\n                svgtypes::Paint::Color(svgtypes::Color::black())\n            } else if aid == AId::Stroke {\n                log::warn!(\n                    \"Failed to parse stroke value: '{}'. Fallback to no stroke.\",\n                    value\n                );\n                return None;\n            } else {\n                return None;\n            }\n        }\n    };\n\n    match paint {\n        svgtypes::Paint::None => None,\n        svgtypes::Paint::Inherit => None, // already resolved by svgtree\n        svgtypes::Paint::ContextFill => state\n            .context_element\n            .clone()\n            .and_then(|(f, _)| f)\n            .map(|f| (f.paint, f.context_element)),\n        svgtypes::Paint::ContextStroke => state\n            .context_element\n            .clone()\n            .and_then(|(_, s)| s)\n            .map(|s| (s.paint, s.context_element)),\n        svgtypes::Paint::CurrentColor => {\n            let svg_color: svgtypes::Color = node\n                .find_attribute(AId::Color)\n                .unwrap_or_else(svgtypes::Color::black);\n            let (color, alpha) = svg_color.split_alpha();\n            *opacity = alpha;\n            Some((Paint::Color(color), None))\n        }\n        svgtypes::Paint::Color(svg_color) => {\n            let (color, alpha) = svg_color.split_alpha();\n            *opacity = alpha;\n            Some((Paint::Color(color), None))\n        }\n        svgtypes::Paint::FuncIRI(func_iri, fallback) => {\n            if let Some(link) = node.document().element_by_id(func_iri) {\n                let tag_name = link.tag_name().unwrap();\n                if tag_name.is_paint_server() {\n                    match paint_server::convert(link, state, cache) {\n                        Some(paint_server::ServerOrColor::Server(paint)) => {\n                            // We can use a paint server node with ObjectBoundingBox units\n                            // for painting only when the shape itself has a bbox.\n                            //\n                            // See SVG spec 7.11 for details.\n\n                            if !has_bbox && paint.units() == Units::ObjectBoundingBox {\n                                from_fallback(node, fallback, opacity).map(|p| (p, None))\n                            } else {\n                                Some((paint, None))\n                            }\n                        }\n                        Some(paint_server::ServerOrColor::Color { color, opacity: so }) => {\n                            *opacity = so;\n                            Some((Paint::Color(color), None))\n                        }\n                        None => from_fallback(node, fallback, opacity).map(|p| (p, None)),\n                    }\n                } else {\n                    log::warn!(\"'{}' cannot be used to {} a shape.\", tag_name, aid);\n                    None\n                }\n            } else {\n                from_fallback(node, fallback, opacity).map(|p| (p, None))\n            }\n        }\n    }\n}\n\nfn from_fallback(\n    node: SvgNode,\n    fallback: Option<svgtypes::PaintFallback>,\n    opacity: &mut Opacity,\n) -> Option<Paint> {\n    match fallback? {\n        svgtypes::PaintFallback::None => None,\n        svgtypes::PaintFallback::CurrentColor => {\n            let svg_color: svgtypes::Color = node\n                .find_attribute(AId::Color)\n                .unwrap_or_else(svgtypes::Color::black);\n            let (color, alpha) = svg_color.split_alpha();\n            *opacity = alpha;\n            Some(Paint::Color(color))\n        }\n        svgtypes::PaintFallback::Color(svg_color) => {\n            let (color, alpha) = svg_color.split_alpha();\n            *opacity = alpha;\n            Some(Paint::Color(color))\n        }\n    }\n}\n\n// Prepare the 'stroke-dasharray' according to:\n// https://www.w3.org/TR/SVG11/painting.html#StrokeDasharrayProperty\nfn conv_dasharray(node: SvgNode, state: &converter::State) -> Option<Vec<f32>> {\n    let node = node\n        .ancestors()\n        .find(|n| n.has_attribute(AId::StrokeDasharray))?;\n    let list = super::units::convert_list(node, AId::StrokeDasharray, state)?;\n\n    // `A negative value is an error`\n    if list.iter().any(|n| n.is_sign_negative()) {\n        return None;\n    }\n\n    // `If the sum of the values is zero, then the stroke is rendered\n    // as if a value of none were specified.`\n    {\n        // no Iter::sum(), because of f64\n\n        let mut sum: f32 = 0.0;\n        for n in list.iter() {\n            sum += *n;\n        }\n\n        if sum.approx_eq_ulps(&0.0, 4) {\n            return None;\n        }\n    }\n\n    // `If an odd number of values is provided, then the list of values\n    // is repeated to yield an even number of values.`\n    if list.len() % 2 != 0 {\n        let mut tmp_list = list.clone();\n        tmp_list.extend_from_slice(&list);\n        return Some(tmp_list);\n    }\n\n    Some(list)\n}\n"
  },
  {
    "path": "crates/usvg/src/parser/svgtree/mod.rs",
    "content": "// Copyright 2021 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse std::collections::HashMap;\nuse std::num::NonZeroU32;\nuse std::str::FromStr;\n\n#[rustfmt::skip] mod names;\nmod parse;\nmod text;\n\nuse tiny_skia_path::Transform;\n\nuse crate::{\n    BlendMode, ImageRendering, Opacity, ShapeRendering, SpreadMethod, TextRendering, Units,\n    Visibility,\n};\npub use names::{AId, EId};\n\n/// An SVG tree container.\n///\n/// Contains only element and text nodes.\n/// Text nodes are present only inside the `text` element.\npub struct Document<'input> {\n    nodes: Vec<NodeData>,\n    attrs: Vec<Attribute<'input>>,\n    links: HashMap<String, NodeId>,\n}\n\nimpl<'input> Document<'input> {\n    /// Returns the root node.\n    #[inline]\n    pub fn root<'a>(&'a self) -> SvgNode<'a, 'input> {\n        SvgNode {\n            id: NodeId::new(0),\n            d: &self.nodes[0],\n            doc: self,\n        }\n    }\n\n    /// Returns the root element.\n    #[inline]\n    pub fn root_element<'a>(&'a self) -> SvgNode<'a, 'input> {\n        // `unwrap` is safe, because `Document` is guarantee to have at least one element.\n        self.root().first_element_child().unwrap()\n    }\n\n    /// Returns an iterator over document's descendant nodes.\n    ///\n    /// Shorthand for `doc.root().descendants()`.\n    #[inline]\n    pub fn descendants<'a>(&'a self) -> Descendants<'a, 'input> {\n        self.root().descendants()\n    }\n\n    /// Returns an element by ID.\n    ///\n    /// Unlike the [`Descendants`] iterator, this is just a HashMap lookup.\n    /// Meaning it's way faster.\n    #[inline]\n    pub fn element_by_id<'a>(&'a self, id: &str) -> Option<SvgNode<'a, 'input>> {\n        let node_id = self.links.get(id)?;\n        Some(self.get(*node_id))\n    }\n\n    #[inline]\n    fn get<'a>(&'a self, id: NodeId) -> SvgNode<'a, 'input> {\n        SvgNode {\n            id,\n            d: &self.nodes[id.get_usize()],\n            doc: self,\n        }\n    }\n}\n\nimpl std::fmt::Debug for Document<'_> {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {\n        if !self.root().has_children() {\n            return write!(f, \"Document []\");\n        }\n\n        macro_rules! writeln_indented {\n            ($depth:expr, $f:expr, $fmt:expr) => {\n                for _ in 0..$depth { write!($f, \"    \")?; }\n                writeln!($f, $fmt)?;\n            };\n            ($depth:expr, $f:expr, $fmt:expr, $($arg:tt)*) => {\n                for _ in 0..$depth { write!($f, \"    \")?; }\n                writeln!($f, $fmt, $($arg)*)?;\n            };\n        }\n\n        fn print_children(\n            parent: SvgNode,\n            depth: usize,\n            f: &mut std::fmt::Formatter,\n        ) -> Result<(), std::fmt::Error> {\n            for child in parent.children() {\n                if child.is_element() {\n                    writeln_indented!(depth, f, \"Element {{\");\n                    writeln_indented!(depth, f, \"    tag_name: {:?}\", child.tag_name());\n\n                    if !child.attributes().is_empty() {\n                        writeln_indented!(depth + 1, f, \"attributes: [\");\n                        for attr in child.attributes() {\n                            writeln_indented!(depth + 2, f, \"{:?}\", attr);\n                        }\n                        writeln_indented!(depth + 1, f, \"]\");\n                    }\n\n                    if child.has_children() {\n                        writeln_indented!(depth, f, \"    children: [\");\n                        print_children(child, depth + 2, f)?;\n                        writeln_indented!(depth, f, \"    ]\");\n                    }\n\n                    writeln_indented!(depth, f, \"}}\");\n                } else {\n                    writeln_indented!(depth, f, \"{:?}\", child);\n                }\n            }\n\n            Ok(())\n        }\n\n        writeln!(f, \"Document [\")?;\n        print_children(self.root(), 1, f)?;\n        writeln!(f, \"]\")?;\n\n        Ok(())\n    }\n}\n\n#[derive(Clone, Copy, Debug)]\npub(crate) struct ShortRange {\n    start: u32,\n    end: u32,\n}\n\nimpl ShortRange {\n    #[inline]\n    fn new(start: u32, end: u32) -> Self {\n        ShortRange { start, end }\n    }\n\n    #[inline]\n    fn to_urange(self) -> std::ops::Range<usize> {\n        self.start as usize..self.end as usize\n    }\n}\n\n#[derive(Clone, Copy, PartialEq, Debug)]\npub(crate) struct NodeId(NonZeroU32);\n\nimpl NodeId {\n    #[inline]\n    fn new(id: u32) -> Self {\n        debug_assert!(id < u32::MAX);\n\n        // We are using `NonZeroU32` to reduce overhead of `Option<NodeId>`.\n        NodeId(NonZeroU32::new(id + 1).unwrap())\n    }\n\n    #[inline]\n    fn get(self) -> u32 {\n        self.0.get() - 1\n    }\n\n    #[inline]\n    fn get_usize(self) -> usize {\n        self.get() as usize\n    }\n}\n\nimpl From<usize> for NodeId {\n    #[inline]\n    fn from(id: usize) -> Self {\n        // We already checked that `id` is limited by u32::MAX.\n        debug_assert!(id <= u32::MAX as usize);\n        NodeId::new(id as u32)\n    }\n}\n\npub(crate) enum NodeKind {\n    Root,\n    Element {\n        tag_name: EId,\n        attributes: ShortRange,\n    },\n    Text(String),\n}\n\nstruct NodeData {\n    parent: Option<NodeId>,\n    next_sibling: Option<NodeId>,\n    children: Option<(NodeId, NodeId)>,\n    kind: NodeKind,\n}\n\n/// An attribute.\n#[derive(Clone)]\npub struct Attribute<'input> {\n    /// Attribute's name.\n    pub name: AId,\n    /// Attribute's value.\n    pub value: roxmltree::StringStorage<'input>,\n    /// Attribute's importance\n    pub important: bool,\n}\n\nimpl std::fmt::Debug for Attribute<'_> {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {\n        write!(\n            f,\n            \"Attribute {{ name: {:?}, value: {}, important: {} }}\",\n            self.name, self.value, self.important\n        )\n    }\n}\n\n/// An SVG node.\n#[derive(Clone, Copy)]\npub struct SvgNode<'a, 'input: 'a> {\n    id: NodeId,\n    doc: &'a Document<'input>,\n    d: &'a NodeData,\n}\n\nimpl Eq for SvgNode<'_, '_> {}\n\nimpl PartialEq for SvgNode<'_, '_> {\n    #[inline]\n    fn eq(&self, other: &Self) -> bool {\n        self.id == other.id && std::ptr::eq(self.doc, other.doc) && std::ptr::eq(self.d, other.d)\n    }\n}\n\nimpl<'a, 'input: 'a> SvgNode<'a, 'input> {\n    #[inline]\n    fn id(&self) -> NodeId {\n        self.id\n    }\n\n    /// Checks if the current node is an element.\n    #[inline]\n    pub fn is_element(&self) -> bool {\n        matches!(self.d.kind, NodeKind::Element { .. })\n    }\n\n    /// Checks if the current node is a text.\n    #[inline]\n    pub fn is_text(&self) -> bool {\n        matches!(self.d.kind, NodeKind::Text(_))\n    }\n\n    /// Returns node's document.\n    #[inline]\n    pub fn document(&self) -> &'a Document<'input> {\n        self.doc\n    }\n\n    /// Returns element's tag name, unless the current node is text.\n    #[inline]\n    pub fn tag_name(&self) -> Option<EId> {\n        match self.d.kind {\n            NodeKind::Element { tag_name, .. } => Some(tag_name),\n            _ => None,\n        }\n    }\n    /// Returns element's `id` attribute value.\n    ///\n    /// Returns an empty string otherwise.\n    #[inline]\n    pub fn element_id(&self) -> &'a str {\n        self.attribute(AId::Id).unwrap_or(\"\")\n    }\n\n    /// Returns an attribute value.\n    pub fn attribute<T: FromValue<'a, 'input>>(&self, aid: AId) -> Option<T> {\n        let value = self\n            .attributes()\n            .iter()\n            .find(|a| a.name == aid)\n            .map(|a| a.value.as_str())?;\n        // These AId have an initial value of none\n        let is_possible_none = matches!(\n            aid,\n            AId::Mask\n                | AId::MarkerStart\n                | AId::MarkerMid\n                | AId::MarkerEnd\n                | AId::ClipPath\n                | AId::Filter\n                | AId::FontSizeAdjust\n                | AId::TextDecoration\n                | AId::Stroke\n                | AId::StrokeDasharray\n        );\n        if is_possible_none && value == \"none\" {\n            return None;\n        }\n        match T::parse(*self, aid, value) {\n            Some(v) => Some(v),\n            None => {\n                // TODO: show position in XML\n                log::warn!(\"Failed to parse {} value: '{}'.\", aid, value);\n                None\n            }\n        }\n    }\n\n    /// Returns an attribute value.\n    ///\n    /// Same as `SvgNode::attribute`, but doesn't show a warning.\n    pub fn try_attribute<T: FromValue<'a, 'input>>(&self, aid: AId) -> Option<T> {\n        let value = self\n            .attributes()\n            .iter()\n            .find(|a| a.name == aid)\n            .map(|a| a.value.as_str())?;\n        T::parse(*self, aid, value)\n    }\n\n    #[inline]\n    fn node_attribute(&self, aid: AId) -> Option<SvgNode<'a, 'input>> {\n        let value = self.attribute(aid)?;\n        let id = if aid == AId::Href {\n            svgtypes::IRI::from_str(value).ok().map(|v| v.0)\n        } else {\n            svgtypes::FuncIRI::from_str(value).ok().map(|v| v.0)\n        }?;\n\n        self.document().element_by_id(id)\n    }\n\n    /// Checks if an attribute is present.\n    #[inline]\n    pub fn has_attribute(&self, aid: AId) -> bool {\n        self.attributes().iter().any(|a| a.name == aid)\n    }\n\n    /// Returns a list of all element's attributes.\n    #[inline]\n    pub fn attributes(&self) -> &'a [Attribute<'input>] {\n        match self.d.kind {\n            NodeKind::Element { ref attributes, .. } => &self.doc.attrs[attributes.to_urange()],\n            _ => &[],\n        }\n    }\n\n    #[inline]\n    fn attribute_id(&self, aid: AId) -> Option<usize> {\n        match self.d.kind {\n            NodeKind::Element { ref attributes, .. } => {\n                let idx = self.attributes().iter().position(|attr| attr.name == aid)?;\n                Some(attributes.start as usize + idx)\n            }\n            _ => None,\n        }\n    }\n\n    /// Finds a [`Node`] that contains the required attribute.\n    ///\n    /// For inheritable attributes walks over ancestors until a node with\n    /// the specified attribute is found.\n    ///\n    /// For non-inheritable attributes checks only the current node and the parent one.\n    /// As per SVG spec.\n    pub fn find_attribute<T: FromValue<'a, 'input>>(&self, aid: AId) -> Option<T> {\n        self.find_attribute_impl(aid)?.attribute(aid)\n    }\n\n    fn find_attribute_impl(&self, aid: AId) -> Option<SvgNode<'a, 'input>> {\n        if aid.is_inheritable() {\n            for n in self.ancestors() {\n                if n.has_attribute(aid) {\n                    return Some(n);\n                }\n            }\n\n            None\n        } else {\n            if self.has_attribute(aid) {\n                Some(*self)\n            } else {\n                // Non-inheritable attributes can inherit a value only from a direct parent.\n                let n = self.parent_element()?;\n                if n.has_attribute(aid) { Some(n) } else { None }\n            }\n        }\n    }\n\n    /// Returns node's text data.\n    ///\n    /// For text nodes returns its content. For elements returns the first child node text.\n    #[inline]\n    pub fn text(&self) -> &'a str {\n        match self.d.kind {\n            NodeKind::Element { .. } => match self.first_child() {\n                Some(child) if child.is_text() => match self.doc.nodes[child.id.get_usize()].kind {\n                    NodeKind::Text(ref text) => text,\n                    _ => \"\",\n                },\n                _ => \"\",\n            },\n            NodeKind::Text(ref text) => text,\n            _ => \"\",\n        }\n    }\n\n    /// Returns a parent node.\n    #[inline]\n    pub fn parent(&self) -> Option<Self> {\n        self.d.parent.map(|id| self.doc.get(id))\n    }\n\n    /// Returns the parent element.\n    #[inline]\n    pub fn parent_element(&self) -> Option<Self> {\n        self.ancestors().skip(1).find(|n| n.is_element())\n    }\n\n    /// Returns the next sibling.\n    #[inline]\n    pub fn next_sibling(&self) -> Option<Self> {\n        self.d.next_sibling.map(|id| self.doc.get(id))\n    }\n\n    /// Returns the first child.\n    #[inline]\n    pub fn first_child(&self) -> Option<Self> {\n        self.d.children.map(|(id, _)| self.doc.get(id))\n    }\n\n    /// Returns the first child element.\n    #[inline]\n    pub fn first_element_child(&self) -> Option<Self> {\n        self.children().find(|n| n.is_element())\n    }\n\n    /// Returns the last child.\n    #[inline]\n    pub fn last_child(&self) -> Option<Self> {\n        self.d.children.map(|(_, id)| self.doc.get(id))\n    }\n\n    /// Checks if the node has child nodes.\n    #[inline]\n    pub fn has_children(&self) -> bool {\n        self.d.children.is_some()\n    }\n\n    /// Returns an iterator over ancestor nodes starting at this node.\n    #[inline]\n    pub fn ancestors(&self) -> Ancestors<'a, 'input> {\n        Ancestors(Some(*self))\n    }\n\n    /// Returns an iterator over children nodes.\n    #[inline]\n    pub fn children(&self) -> Children<'a, 'input> {\n        Children {\n            front: self.first_child(),\n            back: self.last_child(),\n        }\n    }\n\n    /// Returns an iterator which traverses the subtree starting at this node.\n    #[inline]\n    fn traverse(&self) -> Traverse<'a, 'input> {\n        Traverse {\n            root: *self,\n            edge: None,\n        }\n    }\n\n    /// Returns an iterator over this node and its descendants.\n    #[inline]\n    pub fn descendants(&self) -> Descendants<'a, 'input> {\n        Descendants(self.traverse())\n    }\n\n    /// Returns an iterator over elements linked via `xlink:href`.\n    #[inline]\n    pub fn href_iter(&self) -> HrefIter<'a, 'input> {\n        HrefIter {\n            doc: self.document(),\n            origin: self.id(),\n            curr: self.id(),\n            is_first: true,\n            is_finished: false,\n        }\n    }\n}\n\nimpl std::fmt::Debug for SvgNode<'_, '_> {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {\n        match self.d.kind {\n            NodeKind::Root => write!(f, \"Root\"),\n            NodeKind::Element { .. } => {\n                write!(\n                    f,\n                    \"Element {{ tag_name: {:?}, attributes: {:?} }}\",\n                    self.tag_name(),\n                    self.attributes()\n                )\n            }\n            NodeKind::Text(ref text) => write!(f, \"Text({:?})\", text),\n        }\n    }\n}\n\n/// An iterator over ancestor nodes.\n#[derive(Clone, Debug)]\npub struct Ancestors<'a, 'input: 'a>(Option<SvgNode<'a, 'input>>);\n\nimpl<'a, 'input: 'a> Iterator for Ancestors<'a, 'input> {\n    type Item = SvgNode<'a, 'input>;\n\n    #[inline]\n    fn next(&mut self) -> Option<Self::Item> {\n        let node = self.0.take();\n        self.0 = node.as_ref().and_then(SvgNode::parent);\n        node\n    }\n}\n\n/// An iterator over children nodes.\n#[derive(Clone, Debug)]\npub struct Children<'a, 'input: 'a> {\n    front: Option<SvgNode<'a, 'input>>,\n    back: Option<SvgNode<'a, 'input>>,\n}\n\nimpl<'a, 'input: 'a> Iterator for Children<'a, 'input> {\n    type Item = SvgNode<'a, 'input>;\n\n    fn next(&mut self) -> Option<Self::Item> {\n        let node = self.front.take();\n        if self.front == self.back {\n            self.back = None;\n        } else {\n            self.front = node.as_ref().and_then(SvgNode::next_sibling);\n        }\n        node\n    }\n}\n\n#[derive(Clone, Copy, PartialEq, Debug)]\nenum Edge<'a, 'input: 'a> {\n    Open(SvgNode<'a, 'input>),\n    Close(SvgNode<'a, 'input>),\n}\n\n#[derive(Clone, Debug)]\nstruct Traverse<'a, 'input: 'a> {\n    root: SvgNode<'a, 'input>,\n    edge: Option<Edge<'a, 'input>>,\n}\n\nimpl<'a, 'input: 'a> Iterator for Traverse<'a, 'input> {\n    type Item = Edge<'a, 'input>;\n\n    fn next(&mut self) -> Option<Self::Item> {\n        match self.edge {\n            Some(Edge::Open(node)) => {\n                self.edge = Some(match node.first_child() {\n                    Some(first_child) => Edge::Open(first_child),\n                    None => Edge::Close(node),\n                });\n            }\n            Some(Edge::Close(node)) => {\n                if node == self.root {\n                    self.edge = None;\n                } else if let Some(next_sibling) = node.next_sibling() {\n                    self.edge = Some(Edge::Open(next_sibling));\n                } else {\n                    self.edge = node.parent().map(Edge::Close);\n                }\n            }\n            None => {\n                self.edge = Some(Edge::Open(self.root));\n            }\n        }\n\n        self.edge\n    }\n}\n\n/// A descendants iterator.\n#[derive(Clone, Debug)]\npub struct Descendants<'a, 'input: 'a>(Traverse<'a, 'input>);\n\nimpl<'a, 'input: 'a> Iterator for Descendants<'a, 'input> {\n    type Item = SvgNode<'a, 'input>;\n\n    #[inline]\n    fn next(&mut self) -> Option<Self::Item> {\n        for edge in &mut self.0 {\n            if let Edge::Open(node) = edge {\n                return Some(node);\n            }\n        }\n\n        None\n    }\n}\n\n/// An iterator over `xlink:href` references.\n#[derive(Clone, Debug)]\npub struct HrefIter<'a, 'input: 'a> {\n    doc: &'a Document<'input>,\n    origin: NodeId,\n    curr: NodeId,\n    is_first: bool,\n    is_finished: bool,\n}\n\nimpl<'a, 'input: 'a> Iterator for HrefIter<'a, 'input> {\n    type Item = SvgNode<'a, 'input>;\n\n    fn next(&mut self) -> Option<Self::Item> {\n        if self.is_finished {\n            return None;\n        }\n\n        if self.is_first {\n            self.is_first = false;\n            return Some(self.doc.get(self.curr));\n        }\n\n        if let Some(link) = self.doc.get(self.curr).node_attribute(AId::Href) {\n            if link.id() == self.curr || link.id() == self.origin {\n                log::warn!(\n                    \"Element '#{}' cannot reference itself via 'xlink:href'.\",\n                    self.doc.get(self.origin).element_id()\n                );\n                self.is_finished = true;\n                return None;\n            }\n\n            self.curr = link.id();\n            Some(self.doc.get(self.curr))\n        } else {\n            None\n        }\n    }\n}\n\nimpl EId {\n    /// Checks if this is a\n    /// [graphics element](https://www.w3.org/TR/SVG11/intro.html#TermGraphicsElement).\n    pub fn is_graphic(&self) -> bool {\n        matches!(\n            self,\n            EId::Circle\n                | EId::Ellipse\n                | EId::Image\n                | EId::Line\n                | EId::Path\n                | EId::Polygon\n                | EId::Polyline\n                | EId::Rect\n                | EId::Text\n                | EId::Use\n        )\n    }\n\n    /// Checks if this is a\n    /// [gradient element](https://www.w3.org/TR/SVG11/intro.html#TermGradientElement).\n    pub fn is_gradient(&self) -> bool {\n        matches!(self, EId::LinearGradient | EId::RadialGradient)\n    }\n\n    /// Checks if this is a\n    /// [paint server element](https://www.w3.org/TR/SVG11/intro.html#TermPaint).\n    pub fn is_paint_server(&self) -> bool {\n        matches!(\n            self,\n            EId::LinearGradient | EId::RadialGradient | EId::Pattern\n        )\n    }\n}\n\nimpl AId {\n    fn is_presentation(&self) -> bool {\n        matches!(\n            self,\n            AId::AlignmentBaseline\n                | AId::BaselineShift\n                | AId::BackgroundColor // non-standard SVG attribute\n                | AId::ClipPath\n                | AId::ClipRule\n                | AId::Color\n                | AId::ColorInterpolation\n                | AId::ColorInterpolationFilters\n                | AId::ColorRendering\n                | AId::Direction\n                | AId::Display\n                | AId::DominantBaseline\n                | AId::Fill\n                | AId::FillOpacity\n                | AId::FillRule\n                | AId::Filter\n                | AId::FloodColor\n                | AId::FloodOpacity\n                | AId::FontFamily\n                | AId::FontKerning // technically not presentation\n                | AId::FontOpticalSizing // technically not presentation\n                | AId::FontSize\n                | AId::FontSizeAdjust\n                | AId::FontStretch\n                | AId::FontStyle\n                | AId::FontVariant\n                | AId::FontWeight\n                | AId::FontVariationSettings\n                | AId::GlyphOrientationHorizontal\n                | AId::GlyphOrientationVertical\n                | AId::ImageRendering\n                | AId::Isolation // technically not presentation\n                | AId::LetterSpacing\n                | AId::LightingColor\n                | AId::MarkerEnd\n                | AId::MarkerMid\n                | AId::MarkerStart\n                | AId::Mask\n                | AId::MaskType\n                | AId::MixBlendMode // technically not presentation\n                | AId::Opacity\n                | AId::Overflow\n                | AId::PaintOrder\n                | AId::ShapeRendering\n                | AId::StopColor\n                | AId::StopOpacity\n                | AId::Stroke\n                | AId::StrokeDasharray\n                | AId::StrokeDashoffset\n                | AId::StrokeLinecap\n                | AId::StrokeLinejoin\n                | AId::StrokeMiterlimit\n                | AId::StrokeOpacity\n                | AId::StrokeWidth\n                | AId::TextAnchor\n                | AId::TextDecoration\n                | AId::TextOverflow\n                | AId::TextRendering\n                | AId::Transform\n                | AId::TransformOrigin\n                | AId::UnicodeBidi\n                | AId::VectorEffect\n                | AId::Visibility\n                | AId::WhiteSpace\n                | AId::WordSpacing\n                | AId::WritingMode\n        )\n    }\n\n    /// Checks if the current attribute is inheritable.\n    fn is_inheritable(&self) -> bool {\n        if self.is_presentation() {\n            !is_non_inheritable(*self)\n        } else {\n            false\n        }\n    }\n\n    fn allows_inherit_value(&self) -> bool {\n        matches!(\n            self,\n            AId::AlignmentBaseline\n                | AId::BaselineShift\n                | AId::ClipPath\n                | AId::ClipRule\n                | AId::Color\n                | AId::ColorInterpolationFilters\n                | AId::Direction\n                | AId::Display\n                | AId::DominantBaseline\n                | AId::Fill\n                | AId::FillOpacity\n                | AId::FillRule\n                | AId::Filter\n                | AId::FloodColor\n                | AId::FloodOpacity\n                | AId::FontFamily\n                | AId::FontKerning\n                | AId::FontOpticalSizing\n                | AId::FontSize\n                | AId::FontStretch\n                | AId::FontStyle\n                | AId::FontVariant\n                | AId::FontWeight\n                | AId::ImageRendering\n                | AId::Kerning\n                | AId::LetterSpacing\n                | AId::MarkerEnd\n                | AId::MarkerMid\n                | AId::MarkerStart\n                | AId::Mask\n                | AId::Opacity\n                | AId::Overflow\n                | AId::ShapeRendering\n                | AId::StopColor\n                | AId::StopOpacity\n                | AId::Stroke\n                | AId::StrokeDasharray\n                | AId::StrokeDashoffset\n                | AId::StrokeLinecap\n                | AId::StrokeLinejoin\n                | AId::StrokeMiterlimit\n                | AId::StrokeOpacity\n                | AId::StrokeWidth\n                | AId::TextAnchor\n                | AId::TextDecoration\n                | AId::TextRendering\n                | AId::Visibility\n                | AId::WordSpacing\n                | AId::WritingMode\n        )\n    }\n}\n\nfn is_non_inheritable(id: AId) -> bool {\n    matches!(\n        id,\n        AId::AlignmentBaseline\n            | AId::BaselineShift\n            | AId::ClipPath\n            | AId::Display\n            | AId::DominantBaseline\n            | AId::Filter\n            | AId::FloodColor\n            | AId::FloodOpacity\n            | AId::Mask\n            | AId::Opacity\n            | AId::Overflow\n            | AId::LightingColor\n            | AId::StopColor\n            | AId::StopOpacity\n            | AId::TextDecoration\n            | AId::Transform\n            | AId::TransformOrigin\n    )\n}\n\n// TODO: is there a way yo make it less ugly? Too many lifetimes.\n/// A trait for parsing attribute values.\npub trait FromValue<'a, 'input: 'a>: Sized {\n    /// Parses an attribute value.\n    ///\n    /// When `None` is returned, the attribute value will be logged as a parsing failure.\n    fn parse(node: SvgNode<'a, 'input>, aid: AId, value: &'a str) -> Option<Self>;\n}\n\nimpl<'a, 'input: 'a> FromValue<'a, 'input> for &'a str {\n    fn parse(_: SvgNode<'a, 'input>, _: AId, value: &'a str) -> Option<Self> {\n        Some(value)\n    }\n}\n\nimpl<'a, 'input: 'a> FromValue<'a, 'input> for f32 {\n    fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {\n        svgtypes::Number::from_str(value).ok().map(|v| v.0 as f32)\n    }\n}\n\nimpl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::Length {\n    fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {\n        svgtypes::Length::from_str(value).ok()\n    }\n}\n\n// TODO: to svgtypes?\nimpl<'a, 'input: 'a> FromValue<'a, 'input> for Opacity {\n    fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {\n        let length = svgtypes::Length::from_str(value).ok()?;\n        if length.unit == svgtypes::LengthUnit::Percent {\n            Some(Opacity::new_clamped(length.number as f32 / 100.0))\n        } else if length.unit == svgtypes::LengthUnit::None {\n            Some(Opacity::new_clamped(length.number as f32))\n        } else {\n            None\n        }\n    }\n}\n\nimpl<'a, 'input: 'a> FromValue<'a, 'input> for Transform {\n    fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {\n        let ts = match svgtypes::Transform::from_str(value) {\n            Ok(v) => v,\n            Err(_) => return None,\n        };\n\n        let ts = Transform::from_row(\n            ts.a as f32,\n            ts.b as f32,\n            ts.c as f32,\n            ts.d as f32,\n            ts.e as f32,\n            ts.f as f32,\n        );\n\n        if ts.is_valid() {\n            Some(ts)\n        } else {\n            Some(Transform::default())\n        }\n    }\n}\n\nimpl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::TransformOrigin {\n    fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {\n        Self::from_str(value).ok()\n    }\n}\n\nimpl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::ViewBox {\n    fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {\n        Self::from_str(value).ok()\n    }\n}\n\nimpl<'a, 'input: 'a> FromValue<'a, 'input> for Units {\n    fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {\n        match value {\n            \"userSpaceOnUse\" => Some(Units::UserSpaceOnUse),\n            \"objectBoundingBox\" => Some(Units::ObjectBoundingBox),\n            _ => None,\n        }\n    }\n}\n\nimpl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::AspectRatio {\n    fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {\n        Self::from_str(value).ok()\n    }\n}\n\nimpl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::PaintOrder {\n    fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {\n        Self::from_str(value).ok()\n    }\n}\n\nimpl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::Color {\n    fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {\n        Self::from_str(value).ok()\n    }\n}\n\nimpl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::Angle {\n    fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {\n        Self::from_str(value).ok()\n    }\n}\n\nimpl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::EnableBackground {\n    fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {\n        Self::from_str(value).ok()\n    }\n}\n\nimpl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::Paint<'a> {\n    fn parse(_: SvgNode, _: AId, value: &'a str) -> Option<Self> {\n        Self::from_str(value).ok()\n    }\n}\n\nimpl<'a, 'input: 'a> FromValue<'a, 'input> for Vec<f32> {\n    fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {\n        let mut list = Vec::new();\n        for n in svgtypes::NumberListParser::from(value) {\n            list.push(n.ok()? as f32);\n        }\n\n        Some(list)\n    }\n}\n\nimpl<'a, 'input: 'a> FromValue<'a, 'input> for Vec<svgtypes::Length> {\n    fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {\n        let mut list = Vec::new();\n        for n in svgtypes::LengthListParser::from(value) {\n            list.push(n.ok()?);\n        }\n\n        Some(list)\n    }\n}\n\nimpl<'a, 'input: 'a> FromValue<'a, 'input> for Visibility {\n    fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {\n        match value {\n            \"visible\" => Some(Visibility::Visible),\n            \"hidden\" => Some(Visibility::Hidden),\n            \"collapse\" => Some(Visibility::Collapse),\n            _ => None,\n        }\n    }\n}\n\nimpl<'a, 'input: 'a> FromValue<'a, 'input> for SpreadMethod {\n    fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {\n        match value {\n            \"pad\" => Some(SpreadMethod::Pad),\n            \"reflect\" => Some(SpreadMethod::Reflect),\n            \"repeat\" => Some(SpreadMethod::Repeat),\n            _ => None,\n        }\n    }\n}\n\nimpl<'a, 'input: 'a> FromValue<'a, 'input> for ShapeRendering {\n    fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {\n        match value {\n            \"optimizeSpeed\" => Some(ShapeRendering::OptimizeSpeed),\n            \"crispEdges\" => Some(ShapeRendering::CrispEdges),\n            \"auto\" | \"geometricPrecision\" => Some(ShapeRendering::GeometricPrecision),\n            _ => None,\n        }\n    }\n}\n\nimpl<'a, 'input: 'a> FromValue<'a, 'input> for TextRendering {\n    fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {\n        match value {\n            \"optimizeSpeed\" => Some(TextRendering::OptimizeSpeed),\n            \"auto\" | \"optimizeLegibility\" => Some(TextRendering::OptimizeLegibility),\n            \"geometricPrecision\" => Some(TextRendering::GeometricPrecision),\n            _ => None,\n        }\n    }\n}\n\nimpl<'a, 'input: 'a> FromValue<'a, 'input> for ImageRendering {\n    fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {\n        match value {\n            \"auto\" | \"optimizeQuality\" => Some(ImageRendering::OptimizeQuality),\n            \"optimizeSpeed\" => Some(ImageRendering::OptimizeSpeed),\n            \"smooth\" => Some(ImageRendering::Smooth),\n            \"high-quality\" => Some(ImageRendering::HighQuality),\n            \"crisp-edges\" => Some(ImageRendering::CrispEdges),\n            \"pixelated\" => Some(ImageRendering::Pixelated),\n            _ => None,\n        }\n    }\n}\n\nimpl<'a, 'input: 'a> FromValue<'a, 'input> for BlendMode {\n    fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {\n        match value {\n            \"normal\" => Some(BlendMode::Normal),\n            \"multiply\" => Some(BlendMode::Multiply),\n            \"screen\" => Some(BlendMode::Screen),\n            \"overlay\" => Some(BlendMode::Overlay),\n            \"darken\" => Some(BlendMode::Darken),\n            \"lighten\" => Some(BlendMode::Lighten),\n            \"color-dodge\" => Some(BlendMode::ColorDodge),\n            \"color-burn\" => Some(BlendMode::ColorBurn),\n            \"hard-light\" => Some(BlendMode::HardLight),\n            \"soft-light\" => Some(BlendMode::SoftLight),\n            \"difference\" => Some(BlendMode::Difference),\n            \"exclusion\" => Some(BlendMode::Exclusion),\n            \"hue\" => Some(BlendMode::Hue),\n            \"saturation\" => Some(BlendMode::Saturation),\n            \"color\" => Some(BlendMode::Color),\n            \"luminosity\" => Some(BlendMode::Luminosity),\n            _ => None,\n        }\n    }\n}\n\nimpl<'a, 'input: 'a> FromValue<'a, 'input> for SvgNode<'a, 'input> {\n    fn parse(node: SvgNode<'a, 'input>, aid: AId, value: &str) -> Option<Self> {\n        let id = if aid == AId::Href {\n            svgtypes::IRI::from_str(value).ok().map(|v| v.0)\n        } else {\n            svgtypes::FuncIRI::from_str(value).ok().map(|v| v.0)\n        }?;\n\n        node.document().element_by_id(id)\n    }\n}\n"
  },
  {
    "path": "crates/usvg/src/parser/svgtree/names.rs",
    "content": "// Copyright 2019 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\n// This file is autogenerated. Do not edit it!\n// See ./codegen for details.\n\n/// An element ID.\n#[allow(missing_docs)]\n#[derive(Clone, Copy, PartialEq)]\npub enum EId {\n    A,\n    Circle,\n    ClipPath,\n    Defs,\n    Ellipse,\n    FeBlend,\n    FeColorMatrix,\n    FeComponentTransfer,\n    FeComposite,\n    FeConvolveMatrix,\n    FeDiffuseLighting,\n    FeDisplacementMap,\n    FeDistantLight,\n    FeDropShadow,\n    FeFlood,\n    FeFuncA,\n    FeFuncB,\n    FeFuncG,\n    FeFuncR,\n    FeGaussianBlur,\n    FeImage,\n    FeMerge,\n    FeMergeNode,\n    FeMorphology,\n    FeOffset,\n    FePointLight,\n    FeSpecularLighting,\n    FeSpotLight,\n    FeTile,\n    FeTurbulence,\n    Filter,\n    G,\n    Image,\n    Line,\n    LinearGradient,\n    Marker,\n    Mask,\n    Path,\n    Pattern,\n    Polygon,\n    Polyline,\n    RadialGradient,\n    Rect,\n    Stop,\n    Style,\n    Svg,\n    Switch,\n    Symbol,\n    Text,\n    TextPath,\n    Tref,\n    Tspan,\n    Use\n}\n\nstatic ELEMENTS: Map<EId> = Map {\n    key: 732231254413039614,\n    disps: &[\n        (0, 12),\n        (1, 11),\n        (10, 26),\n        (2, 42),\n        (1, 19),\n        (0, 5),\n        (1, 13),\n        (8, 50),\n        (0, 0),\n        (1, 0),\n        (7, 45),\n    ],\n    entries: &[\n        (\"feFlood\", EId::FeFlood),\n        (\"radialGradient\", EId::RadialGradient),\n        (\"feImage\", EId::FeImage),\n        (\"stop\", EId::Stop),\n        (\"fePointLight\", EId::FePointLight),\n        (\"feConvolveMatrix\", EId::FeConvolveMatrix),\n        (\"feComposite\", EId::FeComposite),\n        (\"clipPath\", EId::ClipPath),\n        (\"feMerge\", EId::FeMerge),\n        (\"defs\", EId::Defs),\n        (\"mask\", EId::Mask),\n        (\"svg\", EId::Svg),\n        (\"symbol\", EId::Symbol),\n        (\"linearGradient\", EId::LinearGradient),\n        (\"feSpecularLighting\", EId::FeSpecularLighting),\n        (\"feFuncB\", EId::FeFuncB),\n        (\"filter\", EId::Filter),\n        (\"feFuncG\", EId::FeFuncG),\n        (\"circle\", EId::Circle),\n        (\"g\", EId::G),\n        (\"tref\", EId::Tref),\n        (\"feFuncA\", EId::FeFuncA),\n        (\"image\", EId::Image),\n        (\"text\", EId::Text),\n        (\"line\", EId::Line),\n        (\"pattern\", EId::Pattern),\n        (\"use\", EId::Use),\n        (\"feDropShadow\", EId::FeDropShadow),\n        (\"feSpotLight\", EId::FeSpotLight),\n        (\"marker\", EId::Marker),\n        (\"style\", EId::Style),\n        (\"switch\", EId::Switch),\n        (\"tspan\", EId::Tspan),\n        (\"feColorMatrix\", EId::FeColorMatrix),\n        (\"feOffset\", EId::FeOffset),\n        (\"path\", EId::Path),\n        (\"feGaussianBlur\", EId::FeGaussianBlur),\n        (\"feTile\", EId::FeTile),\n        (\"feTurbulence\", EId::FeTurbulence),\n        (\"feMergeNode\", EId::FeMergeNode),\n        (\"feMorphology\", EId::FeMorphology),\n        (\"a\", EId::A),\n        (\"textPath\", EId::TextPath),\n        (\"ellipse\", EId::Ellipse),\n        (\"feComponentTransfer\", EId::FeComponentTransfer),\n        (\"feDistantLight\", EId::FeDistantLight),\n        (\"polyline\", EId::Polyline),\n        (\"polygon\", EId::Polygon),\n        (\"feBlend\", EId::FeBlend),\n        (\"feDisplacementMap\", EId::FeDisplacementMap),\n        (\"feDiffuseLighting\", EId::FeDiffuseLighting),\n        (\"rect\", EId::Rect),\n        (\"feFuncR\", EId::FeFuncR),\n    ],\n};\n\nimpl EId {\n    pub(crate) fn from_str(text: &str) -> Option<EId> {\n        ELEMENTS.get(text).cloned()\n    }\n\n    /// Returns the original string.\n    #[inline(never)]\n    pub fn to_str(self) -> &'static str {\n        ELEMENTS.key(&self)\n    }\n}\n\nimpl std::fmt::Debug for EId {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        write!(f, \"{}\", self.to_str())\n    }\n}\n\nimpl std::fmt::Display for EId {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        write!(f, \"{:?}\", self)\n    }\n}\n\n/// An attribute ID.\n#[allow(missing_docs)]\n#[derive(Clone, Copy, PartialEq)]\npub enum AId {\n    AlignmentBaseline,\n    Amplitude,\n    Azimuth,\n    BackgroundColor,\n    BaseFrequency,\n    BaselineShift,\n    Bias,\n    Class,\n    Clip,\n    ClipPath,\n    ClipRule,\n    ClipPathUnits,\n    Color,\n    ColorInterpolation,\n    ColorInterpolationFilters,\n    ColorProfile,\n    ColorRendering,\n    Cx,\n    Cy,\n    D,\n    DiffuseConstant,\n    Direction,\n    Display,\n    Divisor,\n    DominantBaseline,\n    Dx,\n    Dy,\n    EdgeMode,\n    Elevation,\n    EnableBackground,\n    Exponent,\n    Fill,\n    FillOpacity,\n    FillRule,\n    Filter,\n    FilterUnits,\n    FloodColor,\n    FloodOpacity,\n    Font,\n    FontFamily,\n    FontFeatureSettings,\n    FontKerning,\n    FontOpticalSizing,\n    FontSize,\n    FontSizeAdjust,\n    FontStretch,\n    FontStyle,\n    FontSynthesis,\n    FontVariant,\n    FontVariantCaps,\n    FontVariantEastAsian,\n    FontVariantLigatures,\n    FontVariantNumeric,\n    FontVariantPosition,\n    FontVariationSettings,\n    FontWeight,\n    Fr,\n    Fx,\n    Fy,\n    GlyphOrientationHorizontal,\n    GlyphOrientationVertical,\n    GradientTransform,\n    GradientUnits,\n    Height,\n    Href,\n    Id,\n    ImageRendering,\n    In,\n    In2,\n    InlineSize,\n    Intercept,\n    Isolation,\n    K1,\n    K2,\n    K3,\n    K4,\n    KernelMatrix,\n    KernelUnitLength,\n    Kerning,\n    LengthAdjust,\n    LetterSpacing,\n    LightingColor,\n    LimitingConeAngle,\n    LineHeight,\n    MarkerEnd,\n    MarkerMid,\n    MarkerStart,\n    MarkerHeight,\n    MarkerUnits,\n    MarkerWidth,\n    Mask,\n    MaskBorder,\n    MaskBorderMode,\n    MaskBorderOutset,\n    MaskBorderRepeat,\n    MaskBorderSlice,\n    MaskBorderSource,\n    MaskBorderWidth,\n    MaskClip,\n    MaskComposite,\n    MaskImage,\n    MaskMode,\n    MaskOrigin,\n    MaskPosition,\n    MaskSize,\n    MaskType,\n    MaskContentUnits,\n    MaskUnits,\n    MixBlendMode,\n    Mode,\n    NumOctaves,\n    Offset,\n    Opacity,\n    Operator,\n    Order,\n    Orient,\n    Overflow,\n    PaintOrder,\n    Path,\n    PathLength,\n    PatternContentUnits,\n    PatternTransform,\n    PatternUnits,\n    Points,\n    PointsAtX,\n    PointsAtY,\n    PointsAtZ,\n    PreserveAlpha,\n    PreserveAspectRatio,\n    PrimitiveUnits,\n    R,\n    Radius,\n    RefX,\n    RefY,\n    RequiredExtensions,\n    RequiredFeatures,\n    Result,\n    Rotate,\n    Rx,\n    Ry,\n    Scale,\n    Seed,\n    ShapeImageThreshold,\n    ShapeInside,\n    ShapeMargin,\n    ShapePadding,\n    ShapeRendering,\n    ShapeSubtract,\n    Side,\n    Slope,\n    Space,\n    SpecularConstant,\n    SpecularExponent,\n    SpreadMethod,\n    StartOffset,\n    StdDeviation,\n    StitchTiles,\n    StopColor,\n    StopOpacity,\n    Stroke,\n    StrokeDasharray,\n    StrokeDashoffset,\n    StrokeLinecap,\n    StrokeLinejoin,\n    StrokeMiterlimit,\n    StrokeOpacity,\n    StrokeWidth,\n    Style,\n    SurfaceScale,\n    SystemLanguage,\n    TableValues,\n    TargetX,\n    TargetY,\n    TextAlign,\n    TextAlignLast,\n    TextAnchor,\n    TextDecoration,\n    TextDecorationColor,\n    TextDecorationFill,\n    TextDecorationLine,\n    TextDecorationStroke,\n    TextDecorationStyle,\n    TextIndent,\n    TextOrientation,\n    TextOverflow,\n    TextRendering,\n    TextUnderlinePosition,\n    TextLength,\n    Transform,\n    TransformBox,\n    TransformOrigin,\n    Type,\n    UnicodeBidi,\n    UnicodeRange,\n    Values,\n    VectorEffect,\n    ViewBox,\n    Visibility,\n    WhiteSpace,\n    Width,\n    WordSpacing,\n    WritingMode,\n    X,\n    X1,\n    X2,\n    XChannelSelector,\n    Y,\n    Y1,\n    Y2,\n    YChannelSelector,\n    Z\n}\n\nstatic ATTRIBUTES: Map<AId> = Map {\n    key: 3213172566270843353,\n    disps: &[\n        (0, 63),\n        (4, 146),\n        (0, 0),\n        (3, 42),\n        (2, 197),\n        (0, 0),\n        (0, 1),\n        (0, 0),\n        (0, 0),\n        (0, 18),\n        (0, 11),\n        (1, 20),\n        (0, 8),\n        (17, 110),\n        (1, 112),\n        (1, 108),\n        (5, 94),\n        (2, 128),\n        (4, 95),\n        (0, 63),\n        (0, 96),\n        (0, 0),\n        (1, 110),\n        (0, 1),\n        (40, 30),\n        (17, 157),\n        (0, 61),\n        (0, 16),\n        (7, 16),\n        (0, 80),\n        (0, 107),\n        (6, 111),\n        (0, 153),\n        (6, 202),\n        (18, 86),\n        (0, 194),\n        (0, 0),\n        (0, 7),\n        (0, 69),\n        (0, 5),\n        (0, 19),\n        (0, 0),\n        (4, 65),\n    ],\n    entries: &[\n        (\"alignment-baseline\", AId::AlignmentBaseline),\n        (\"fx\", AId::Fx),\n        (\"targetY\", AId::TargetY),\n        (\"clip-path\", AId::ClipPath),\n        (\"lengthAdjust\", AId::LengthAdjust),\n        (\"mask-size\", AId::MaskSize),\n        (\"unicode-bidi\", AId::UnicodeBidi),\n        (\"z\", AId::Z),\n        (\"font-variant-numeric\", AId::FontVariantNumeric),\n        (\"clip-rule\", AId::ClipRule),\n        (\"font\", AId::Font),\n        (\"gradientUnits\", AId::GradientUnits),\n        (\"style\", AId::Style),\n        (\"font-stretch\", AId::FontStretch),\n        (\"intercept\", AId::Intercept),\n        (\"mask-border-slice\", AId::MaskBorderSlice),\n        (\"y\", AId::Y),\n        (\"xChannelSelector\", AId::XChannelSelector),\n        (\"numOctaves\", AId::NumOctaves),\n        (\"x1\", AId::X1),\n        (\"fill-rule\", AId::FillRule),\n        (\"image-rendering\", AId::ImageRendering),\n        (\"surfaceScale\", AId::SurfaceScale),\n        (\"seed\", AId::Seed),\n        (\"mix-blend-mode\", AId::MixBlendMode),\n        (\"path\", AId::Path),\n        (\"mask-border-repeat\", AId::MaskBorderRepeat),\n        (\"transform\", AId::Transform),\n        (\"stroke\", AId::Stroke),\n        (\"refX\", AId::RefX),\n        (\"text-orientation\", AId::TextOrientation),\n        (\"line-height\", AId::LineHeight),\n        (\"display\", AId::Display),\n        (\"kerning\", AId::Kerning),\n        (\"transform-origin\", AId::TransformOrigin),\n        (\"shape-subtract\", AId::ShapeSubtract),\n        (\"width\", AId::Width),\n        (\"stroke-miterlimit\", AId::StrokeMiterlimit),\n        (\"dy\", AId::Dy),\n        (\"text-decoration-color\", AId::TextDecorationColor),\n        (\"white-space\", AId::WhiteSpace),\n        (\"diffuseConstant\", AId::DiffuseConstant),\n        (\"text-decoration-stroke\", AId::TextDecorationStroke),\n        (\"values\", AId::Values),\n        (\"font-size\", AId::FontSize),\n        (\"shape-image-threshold\", AId::ShapeImageThreshold),\n        (\"href\", AId::Href),\n        (\"cy\", AId::Cy),\n        (\"mask-image\", AId::MaskImage),\n        (\"unicode-range\", AId::UnicodeRange),\n        (\"specularConstant\", AId::SpecularConstant),\n        (\"baseline-shift\", AId::BaselineShift),\n        (\"k3\", AId::K3),\n        (\"text-anchor\", AId::TextAnchor),\n        (\"mask-border-mode\", AId::MaskBorderMode),\n        (\"requiredFeatures\", AId::RequiredFeatures),\n        (\"color-rendering\", AId::ColorRendering),\n        (\"amplitude\", AId::Amplitude),\n        (\"mask-border-width\", AId::MaskBorderWidth),\n        (\"stroke-linecap\", AId::StrokeLinecap),\n        (\"paint-order\", AId::PaintOrder),\n        (\"lighting-color\", AId::LightingColor),\n        (\"dx\", AId::Dx),\n        (\"markerWidth\", AId::MarkerWidth),\n        (\"scale\", AId::Scale),\n        (\"id\", AId::Id),\n        (\"color\", AId::Color),\n        (\"in2\", AId::In2),\n        (\"targetX\", AId::TargetX),\n        (\"direction\", AId::Direction),\n        (\"pointsAtX\", AId::PointsAtX),\n        (\"stitchTiles\", AId::StitchTiles),\n        (\"patternUnits\", AId::PatternUnits),\n        (\"shape-padding\", AId::ShapePadding),\n        (\"k2\", AId::K2),\n        (\"font-optical-sizing\", AId::FontOpticalSizing),\n        (\"k4\", AId::K4),\n        (\"vector-effect\", AId::VectorEffect),\n        (\"mask-composite\", AId::MaskComposite),\n        (\"stroke-width\", AId::StrokeWidth),\n        (\"font-variation-settings\", AId::FontVariationSettings),\n        (\"mask-border-outset\", AId::MaskBorderOutset),\n        (\"in\", AId::In),\n        (\"stroke-linejoin\", AId::StrokeLinejoin),\n        (\"stop-opacity\", AId::StopOpacity),\n        (\"inline-size\", AId::InlineSize),\n        (\"mask-type\", AId::MaskType),\n        (\"filterUnits\", AId::FilterUnits),\n        (\"color-profile\", AId::ColorProfile),\n        (\"space\", AId::Space),\n        (\"text-decoration-fill\", AId::TextDecorationFill),\n        (\"font-kerning\", AId::FontKerning),\n        (\"offset\", AId::Offset),\n        (\"pointsAtZ\", AId::PointsAtZ),\n        (\"text-align\", AId::TextAlign),\n        (\"clip\", AId::Clip),\n        (\"y1\", AId::Y1),\n        (\"mask-origin\", AId::MaskOrigin),\n        (\"mask-mode\", AId::MaskMode),\n        (\"yChannelSelector\", AId::YChannelSelector),\n        (\"font-variant-caps\", AId::FontVariantCaps),\n        (\"marker-mid\", AId::MarkerMid),\n        (\"shape-rendering\", AId::ShapeRendering),\n        (\"text-rendering\", AId::TextRendering),\n        (\"fill-opacity\", AId::FillOpacity),\n        (\"word-spacing\", AId::WordSpacing),\n        (\"fill\", AId::Fill),\n        (\"mask-clip\", AId::MaskClip),\n        (\"font-feature-settings\", AId::FontFeatureSettings),\n        (\"radius\", AId::Radius),\n        (\"kernelMatrix\", AId::KernelMatrix),\n        (\"kernelUnitLength\", AId::KernelUnitLength),\n        (\"mask-border-source\", AId::MaskBorderSource),\n        (\"k1\", AId::K1),\n        (\"mask\", AId::Mask),\n        (\"opacity\", AId::Opacity),\n        (\"markerUnits\", AId::MarkerUnits),\n        (\"visibility\", AId::Visibility),\n        (\"spreadMethod\", AId::SpreadMethod),\n        (\"pointsAtY\", AId::PointsAtY),\n        (\"d\", AId::D),\n        (\"slope\", AId::Slope),\n        (\"side\", AId::Side),\n        (\"tableValues\", AId::TableValues),\n        (\"order\", AId::Order),\n        (\"text-align-last\", AId::TextAlignLast),\n        (\"font-size-adjust\", AId::FontSizeAdjust),\n        (\"rotate\", AId::Rotate),\n        (\"shape-margin\", AId::ShapeMargin),\n        (\"limitingConeAngle\", AId::LimitingConeAngle),\n        (\"font-weight\", AId::FontWeight),\n        (\"text-decoration-line\", AId::TextDecorationLine),\n        (\"stop-color\", AId::StopColor),\n        (\"requiredExtensions\", AId::RequiredExtensions),\n        (\"enable-background\", AId::EnableBackground),\n        (\"systemLanguage\", AId::SystemLanguage),\n        (\"clipPathUnits\", AId::ClipPathUnits),\n        (\"stroke-dashoffset\", AId::StrokeDashoffset),\n        (\"ry\", AId::Ry),\n        (\"overflow\", AId::Overflow),\n        (\"class\", AId::Class),\n        (\"mask-border\", AId::MaskBorder),\n        (\"specularExponent\", AId::SpecularExponent),\n        (\"text-decoration\", AId::TextDecoration),\n        (\"startOffset\", AId::StartOffset),\n        (\"stroke-dasharray\", AId::StrokeDasharray),\n        (\"fr\", AId::Fr),\n        (\"mask-position\", AId::MaskPosition),\n        (\"writing-mode\", AId::WritingMode),\n        (\"font-synthesis\", AId::FontSynthesis),\n        (\"isolation\", AId::Isolation),\n        (\"rx\", AId::Rx),\n        (\"bias\", AId::Bias),\n        (\"markerHeight\", AId::MarkerHeight),\n        (\"edgeMode\", AId::EdgeMode),\n        (\"r\", AId::R),\n        (\"stroke-opacity\", AId::StrokeOpacity),\n        (\"maskContentUnits\", AId::MaskContentUnits),\n        (\"height\", AId::Height),\n        (\"font-variant-position\", AId::FontVariantPosition),\n        (\"operator\", AId::Operator),\n        (\"font-family\", AId::FontFamily),\n        (\"fy\", AId::Fy),\n        (\"dominant-baseline\", AId::DominantBaseline),\n        (\"y2\", AId::Y2),\n        (\"shape-inside\", AId::ShapeInside),\n        (\"letter-spacing\", AId::LetterSpacing),\n        (\"azimuth\", AId::Azimuth),\n        (\"stdDeviation\", AId::StdDeviation),\n        (\"flood-color\", AId::FloodColor),\n        (\"flood-opacity\", AId::FloodOpacity),\n        (\"type\", AId::Type),\n        (\"font-variant-east-asian\", AId::FontVariantEastAsian),\n        (\"points\", AId::Points),\n        (\"refY\", AId::RefY),\n        (\"text-underline-position\", AId::TextUnderlinePosition),\n        (\"patternContentUnits\", AId::PatternContentUnits),\n        (\"baseFrequency\", AId::BaseFrequency),\n        (\"color-interpolation\", AId::ColorInterpolation),\n        (\"font-variant-ligatures\", AId::FontVariantLigatures),\n        (\"font-style\", AId::FontStyle),\n        (\"filter\", AId::Filter),\n        (\"text-decoration-style\", AId::TextDecorationStyle),\n        (\"preserveAlpha\", AId::PreserveAlpha),\n        (\"mode\", AId::Mode),\n        (\"divisor\", AId::Divisor),\n        (\"cx\", AId::Cx),\n        (\"patternTransform\", AId::PatternTransform),\n        (\"background-color\", AId::BackgroundColor),\n        (\"preserveAspectRatio\", AId::PreserveAspectRatio),\n        (\"gradientTransform\", AId::GradientTransform),\n        (\"x2\", AId::X2),\n        (\"pathLength\", AId::PathLength),\n        (\"marker-start\", AId::MarkerStart),\n        (\"glyph-orientation-horizontal\", AId::GlyphOrientationHorizontal),\n        (\"maskUnits\", AId::MaskUnits),\n        (\"textLength\", AId::TextLength),\n        (\"viewBox\", AId::ViewBox),\n        (\"text-overflow\", AId::TextOverflow),\n        (\"glyph-orientation-vertical\", AId::GlyphOrientationVertical),\n        (\"result\", AId::Result),\n        (\"primitiveUnits\", AId::PrimitiveUnits),\n        (\"exponent\", AId::Exponent),\n        (\"x\", AId::X),\n        (\"font-variant\", AId::FontVariant),\n        (\"elevation\", AId::Elevation),\n        (\"color-interpolation-filters\", AId::ColorInterpolationFilters),\n        (\"text-indent\", AId::TextIndent),\n        (\"marker-end\", AId::MarkerEnd),\n        (\"transform-box\", AId::TransformBox),\n        (\"orient\", AId::Orient),\n    ],\n};\n\nimpl AId {\n    pub(crate) fn from_str(text: &str) -> Option<AId> {\n        ATTRIBUTES.get(text).cloned()\n    }\n\n    /// Returns the original string.\n    #[inline(never)]\n    pub fn to_str(self) -> &'static str {\n        ATTRIBUTES.key(&self)\n    }\n}\n\nimpl std::fmt::Debug for AId {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        write!(f, \"{}\", self.to_str())\n    }\n}\n\nimpl std::fmt::Display for AId {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        write!(f, \"{:?}\", self)\n    }\n}\n\n// A stripped down `phf` crate fork.\n//\n// https://github.com/sfackler/rust-phf\n\nstruct Map<V: 'static> {\n    pub key: u64,\n    pub disps: &'static [(u32, u32)],\n    pub entries: &'static [(&'static str, V)],\n}\n\nimpl<V: PartialEq> Map<V> {\n    fn get(&self, key: &str) -> Option<&V> {\n        let hash = hash(key, self.key);\n        let index = get_index(hash, self.disps, self.entries.len());\n        let entry = &self.entries[index as usize];\n        let b = entry.0;\n        if b == key {\n            Some(&entry.1)\n        } else {\n            None\n        }\n    }\n\n    fn key(&self, value: &V) -> &'static str {\n        self.entries.iter().find(|kv| kv.1 == *value).unwrap().0\n    }\n}\n\n#[inline]\nfn hash(x: &str, key: u64) -> u64 {\n    use std::hash::Hasher;\n\n    let mut hasher = siphasher::sip::SipHasher13::new_with_keys(0, key);\n    hasher.write(x.as_bytes());\n    hasher.finish()\n}\n\n#[inline]\nfn get_index(hash: u64, disps: &[(u32, u32)], len: usize) -> u32 {\n    let (g, f1, f2) = split(hash);\n    let (d1, d2) = disps[(g % (disps.len() as u32)) as usize];\n    displace(f1, f2, d1, d2) % (len as u32)\n}\n\n#[inline]\nfn split(hash: u64) -> (u32, u32, u32) {\n    const BITS: u32 = 21;\n    const MASK: u64 = (1 << BITS) - 1;\n\n    ((hash & MASK) as u32,\n     ((hash >> BITS) & MASK) as u32,\n     ((hash >> (2 * BITS)) & MASK) as u32)\n}\n\n#[inline]\nfn displace(f1: u32, f2: u32, d1: u32, d2: u32) -> u32 {\n    d2 + f1 * d1 + f2\n}\n"
  },
  {
    "path": "crates/usvg/src/parser/svgtree/parse.rs",
    "content": "// Copyright 2021 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse std::collections::HashMap;\n\nuse roxmltree::Error;\nuse simplecss::Declaration;\nuse svgtypes::FontShorthand;\n\nuse super::{AId, Attribute, Document, EId, NodeData, NodeId, NodeKind, ShortRange};\n\nconst SVG_NS: &str = \"http://www.w3.org/2000/svg\";\nconst XLINK_NS: &str = \"http://www.w3.org/1999/xlink\";\nconst XML_NAMESPACE_NS: &str = \"http://www.w3.org/XML/1998/namespace\";\n\nimpl<'input> Document<'input> {\n    /// Parses a [`Document`] from a [`roxmltree::Document`].\n    pub fn parse_tree(\n        xml: &roxmltree::Document<'input>,\n        injected_stylesheet: Option<&'input str>,\n    ) -> Result<Document<'input>, Error> {\n        parse(xml, injected_stylesheet)\n    }\n\n    pub(crate) fn append(&mut self, parent_id: NodeId, kind: NodeKind) -> NodeId {\n        let new_child_id = NodeId::from(self.nodes.len());\n        self.nodes.push(NodeData {\n            parent: Some(parent_id),\n            next_sibling: None,\n            children: None,\n            kind,\n        });\n\n        let last_child_id = self.nodes[parent_id.get_usize()].children.map(|(_, id)| id);\n\n        if let Some(id) = last_child_id {\n            self.nodes[id.get_usize()].next_sibling = Some(new_child_id);\n        }\n\n        self.nodes[parent_id.get_usize()].children = Some(\n            if let Some((first_child_id, _)) = self.nodes[parent_id.get_usize()].children {\n                (first_child_id, new_child_id)\n            } else {\n                (new_child_id, new_child_id)\n            },\n        );\n\n        new_child_id\n    }\n\n    fn append_attribute(\n        &mut self,\n        name: AId,\n        value: roxmltree::StringStorage<'input>,\n        important: bool,\n    ) {\n        self.attrs.push(Attribute {\n            name,\n            value,\n            important,\n        });\n    }\n}\n\nfn parse<'input>(\n    xml: &roxmltree::Document<'input>,\n    injected_stylesheet: Option<&'input str>,\n) -> Result<Document<'input>, Error> {\n    let mut doc = Document {\n        nodes: Vec::new(),\n        attrs: Vec::new(),\n        links: HashMap::new(),\n    };\n\n    // build a map of id -> node for resolve_href\n    let mut id_map = HashMap::new();\n    for node in xml.descendants() {\n        if let Some(id) = node.attribute(\"id\") {\n            if !id_map.contains_key(id) {\n                id_map.insert(id, node);\n            }\n        }\n    }\n\n    // Add a root node.\n    doc.nodes.push(NodeData {\n        parent: None,\n        next_sibling: None,\n        children: None,\n        kind: NodeKind::Root,\n    });\n\n    let style_sheet = resolve_css(xml, injected_stylesheet);\n\n    parse_xml_node_children(\n        xml.root(),\n        xml.root(),\n        doc.root().id,\n        &style_sheet,\n        false,\n        0,\n        &mut doc,\n        &id_map,\n    )?;\n\n    // Check that the root element is `svg`.\n    match doc.root().first_element_child() {\n        Some(child) => {\n            if child.tag_name() != Some(EId::Svg) {\n                return Err(roxmltree::Error::NoRootNode);\n            }\n        }\n        None => return Err(roxmltree::Error::NoRootNode),\n    }\n\n    // Collect all elements with `id` attribute.\n    let mut links = HashMap::new();\n    for node in doc.descendants() {\n        if let Some(id) = node.attribute::<&str>(AId::Id) {\n            links.insert(id.to_string(), node.id);\n        }\n    }\n    doc.links = links;\n\n    fix_recursive_patterns(&mut doc);\n    fix_recursive_links(EId::ClipPath, AId::ClipPath, &mut doc);\n    fix_recursive_links(EId::Mask, AId::Mask, &mut doc);\n    fix_recursive_links(EId::Filter, AId::Filter, &mut doc);\n    fix_recursive_fe_image(&mut doc);\n\n    Ok(doc)\n}\n\npub(crate) fn parse_tag_name(node: roxmltree::Node) -> Option<EId> {\n    if !node.is_element() {\n        return None;\n    }\n\n    if !matches!(node.tag_name().namespace(), None | Some(SVG_NS)) {\n        return None;\n    }\n\n    EId::from_str(node.tag_name().name())\n}\n\nfn parse_xml_node_children<'input>(\n    parent: roxmltree::Node<'_, 'input>,\n    origin: roxmltree::Node,\n    parent_id: NodeId,\n    style_sheet: &simplecss::StyleSheet,\n    ignore_ids: bool,\n    depth: u32,\n    doc: &mut Document<'input>,\n    id_map: &HashMap<&str, roxmltree::Node<'_, 'input>>,\n) -> Result<(), Error> {\n    for node in parent.children() {\n        parse_xml_node(\n            node,\n            origin,\n            parent_id,\n            style_sheet,\n            ignore_ids,\n            depth,\n            doc,\n            id_map,\n        )?;\n    }\n\n    Ok(())\n}\n\nfn parse_xml_node<'input>(\n    node: roxmltree::Node<'_, 'input>,\n    origin: roxmltree::Node,\n    parent_id: NodeId,\n    style_sheet: &simplecss::StyleSheet,\n    ignore_ids: bool,\n    depth: u32,\n    doc: &mut Document<'input>,\n    id_map: &HashMap<&str, roxmltree::Node<'_, 'input>>,\n) -> Result<(), Error> {\n    if depth > 1024 {\n        return Err(Error::NodesLimitReached);\n    }\n\n    let mut tag_name = match parse_tag_name(node) {\n        Some(id) => id,\n        None => return Ok(()),\n    };\n\n    if tag_name == EId::Style {\n        return Ok(());\n    }\n\n    // TODO: remove?\n    // Treat links as groups.\n    if tag_name == EId::A {\n        tag_name = EId::G;\n    }\n\n    let node_id = parse_svg_element(node, parent_id, tag_name, style_sheet, ignore_ids, doc)?;\n    if tag_name == EId::Text {\n        super::text::parse_svg_text_element(node, node_id, style_sheet, doc)?;\n    } else if tag_name == EId::Use {\n        parse_svg_use_element(node, origin, node_id, style_sheet, depth + 1, doc, id_map)?;\n    } else {\n        parse_xml_node_children(\n            node,\n            origin,\n            node_id,\n            style_sheet,\n            ignore_ids,\n            depth + 1,\n            doc,\n            id_map,\n        )?;\n    }\n\n    Ok(())\n}\n\npub(crate) fn parse_svg_element<'input>(\n    xml_node: roxmltree::Node<'_, 'input>,\n    parent_id: NodeId,\n    tag_name: EId,\n    style_sheet: &simplecss::StyleSheet,\n    ignore_ids: bool,\n    doc: &mut Document<'input>,\n) -> Result<NodeId, Error> {\n    let attrs_start_idx = doc.attrs.len();\n\n    // Copy presentational attributes first.\n    for attr in xml_node.attributes() {\n        match attr.namespace() {\n            None | Some(SVG_NS) | Some(XLINK_NS) | Some(XML_NAMESPACE_NS) => {}\n            _ => continue,\n        }\n\n        let aid = match AId::from_str(attr.name()) {\n            Some(v) => v,\n            None => continue,\n        };\n\n        // During a `use` resolving, all `id` attributes must be ignored.\n        // Otherwise we will get elements with duplicated id's.\n        if ignore_ids && aid == AId::Id {\n            continue;\n        }\n\n        // For some reason those properties are allowed only inside a `style` attribute and CSS.\n        if matches!(aid, AId::MixBlendMode | AId::Isolation | AId::FontKerning) {\n            continue;\n        } else if aid == AId::ImageRendering\n            && matches!(\n                attr.value(),\n                \"smooth\" | \"high-quality\" | \"crisp-edges\" | \"pixelated\"\n            )\n        {\n            continue;\n        }\n\n        append_attribute(\n            parent_id,\n            tag_name,\n            aid,\n            attr.value_storage().clone(),\n            false,\n            doc,\n        );\n    }\n\n    let mut insert_attribute = |aid, value: &str, important: bool| {\n        // Check that attribute already exists.\n        let idx = doc.attrs[attrs_start_idx..]\n            .iter_mut()\n            .position(|a| a.name == aid);\n\n        // Append an attribute as usual.\n        let added = append_attribute(\n            parent_id,\n            tag_name,\n            aid,\n            roxmltree::StringStorage::new_owned(value),\n            important,\n            doc,\n        );\n\n        // Check that attribute was actually added, because it could be skipped.\n        if added {\n            if let Some(idx) = idx {\n                let last_idx = doc.attrs.len() - 1;\n                let existing_idx = attrs_start_idx + idx;\n\n                // See https://developer.mozilla.org/en-US/docs/Web/CSS/important\n                // When a declaration is important, the order of precedence is reversed.\n                // Declarations marked as important in the user-agent style sheets override\n                // all important declarations in the user style sheets. Similarly, all important\n                // declarations in the user style sheets override all important declarations in the\n                // author's style sheets. Finally, all important declarations take precedence over\n                // all animations.\n                //\n                // Which means:\n                // 1) Existing is not important, new is not important -> swap\n                // 2) Existing is important, new is not important -> don't swap\n                // 3) Existing is not important, new is important -> swap\n                // 4) Existing is important, new is important -> don't swap (since the order\n                // is reversed, so existing important attributes take precedence over new\n                // important attributes)\n                let has_precedence = !doc.attrs[existing_idx].important;\n\n                if has_precedence {\n                    doc.attrs.swap(existing_idx, last_idx);\n                }\n\n                // Remove last.\n                doc.attrs.pop();\n            }\n        }\n    };\n\n    let mut write_declaration = |declaration: &Declaration| {\n        // TODO: perform XML attribute normalization\n        let imp = declaration.important;\n        let val = declaration.value;\n\n        if declaration.name == \"marker\" {\n            insert_attribute(AId::MarkerStart, val, imp);\n            insert_attribute(AId::MarkerMid, val, imp);\n            insert_attribute(AId::MarkerEnd, val, imp);\n        } else if declaration.name == \"font\" {\n            if let Ok(shorthand) = FontShorthand::from_str(val) {\n                // First we need to reset all values to their default.\n                insert_attribute(AId::FontStyle, \"normal\", imp);\n                insert_attribute(AId::FontVariant, \"normal\", imp);\n                insert_attribute(AId::FontWeight, \"normal\", imp);\n                insert_attribute(AId::FontStretch, \"normal\", imp);\n                insert_attribute(AId::LineHeight, \"normal\", imp);\n                insert_attribute(AId::FontSizeAdjust, \"none\", imp);\n                insert_attribute(AId::FontKerning, \"auto\", imp);\n                insert_attribute(AId::FontVariantCaps, \"normal\", imp);\n                insert_attribute(AId::FontVariantLigatures, \"normal\", imp);\n                insert_attribute(AId::FontVariantNumeric, \"normal\", imp);\n                insert_attribute(AId::FontVariantEastAsian, \"normal\", imp);\n                insert_attribute(AId::FontVariantPosition, \"normal\", imp);\n\n                // Then, we set the properties that have been declared.\n                shorthand\n                    .font_stretch\n                    .map(|s| insert_attribute(AId::FontStretch, s, imp));\n                shorthand\n                    .font_weight\n                    .map(|s| insert_attribute(AId::FontWeight, s, imp));\n                shorthand\n                    .font_variant\n                    .map(|s| insert_attribute(AId::FontVariant, s, imp));\n                shorthand\n                    .font_style\n                    .map(|s| insert_attribute(AId::FontStyle, s, imp));\n                insert_attribute(AId::FontSize, shorthand.font_size, imp);\n                insert_attribute(AId::FontFamily, shorthand.font_family, imp);\n            } else {\n                log::warn!(\n                    \"Failed to parse {} value: '{}'\",\n                    AId::Font,\n                    declaration.value\n                );\n            }\n        } else if let Some(aid) = AId::from_str(declaration.name) {\n            // Parse only the presentation attributes.\n            if aid.is_presentation() {\n                insert_attribute(aid, val, imp);\n            }\n        }\n    };\n\n    // Apply CSS.\n    for rule in &style_sheet.rules {\n        if rule.selector.matches(&XmlNode(xml_node)) {\n            for declaration in &rule.declarations {\n                write_declaration(declaration);\n            }\n        }\n    }\n\n    // Split a `style` attribute.\n    if let Some(value) = xml_node.attribute(\"style\") {\n        for declaration in simplecss::DeclarationTokenizer::from(value) {\n            write_declaration(&declaration);\n        }\n    }\n\n    if doc.nodes.len() > 1_000_000 {\n        return Err(Error::NodesLimitReached);\n    }\n\n    let node_id = doc.append(\n        parent_id,\n        NodeKind::Element {\n            tag_name,\n            attributes: ShortRange::new(attrs_start_idx as u32, doc.attrs.len() as u32),\n        },\n    );\n\n    Ok(node_id)\n}\n\nfn append_attribute<'input>(\n    parent_id: NodeId,\n    tag_name: EId,\n    aid: AId,\n    value: roxmltree::StringStorage<'input>,\n    important: bool,\n    doc: &mut Document<'input>,\n) -> bool {\n    match aid {\n        // The `style` attribute will be split into attributes, so we don't need it.\n        AId::Style |\n        // No need to copy a `class` attribute since CSS were already resolved.\n        AId::Class => return false,\n        _ => {}\n    }\n\n    // Ignore `xlink:href` on `tspan` (which was originally `tref` or `a`),\n    // because we will convert `tref` into `tspan` anyway.\n    if tag_name == EId::Tspan && aid == AId::Href {\n        return false;\n    }\n\n    if aid.allows_inherit_value() && &*value == \"inherit\" {\n        return resolve_inherit(parent_id, aid, doc);\n    }\n\n    doc.append_attribute(aid, value, important);\n    true\n}\n\nfn resolve_inherit(parent_id: NodeId, aid: AId, doc: &mut Document) -> bool {\n    if aid.is_inheritable() {\n        // Inheritable attributes can inherit a value from an any ancestor.\n        let node_id = doc\n            .get(parent_id)\n            .ancestors()\n            .find(|n| n.has_attribute(aid))\n            .map(|n| n.id);\n        if let Some(node_id) = node_id {\n            if let Some(attr) = doc\n                .get(node_id)\n                .attributes()\n                .iter()\n                .find(|a| a.name == aid)\n                .cloned()\n            {\n                doc.attrs.push(Attribute {\n                    name: aid,\n                    value: attr.value,\n                    important: attr.important,\n                });\n\n                return true;\n            }\n        }\n    } else {\n        // Non-inheritable attributes can inherit a value only from a direct parent.\n        if let Some(attr) = doc\n            .get(parent_id)\n            .attributes()\n            .iter()\n            .find(|a| a.name == aid)\n            .cloned()\n        {\n            doc.attrs.push(Attribute {\n                name: aid,\n                value: attr.value,\n                important: attr.important,\n            });\n\n            return true;\n        }\n    }\n\n    // Fallback to a default value if possible.\n    let value = match aid {\n        AId::ImageRendering | AId::ShapeRendering | AId::TextRendering => \"auto\",\n\n        AId::ClipPath\n        | AId::Filter\n        | AId::MarkerEnd\n        | AId::MarkerMid\n        | AId::MarkerStart\n        | AId::Mask\n        | AId::Stroke\n        | AId::StrokeDasharray\n        | AId::TextDecoration => \"none\",\n\n        AId::FontStretch\n        | AId::FontStyle\n        | AId::FontVariant\n        | AId::FontWeight\n        | AId::LetterSpacing\n        | AId::WordSpacing => \"normal\",\n\n        AId::Fill | AId::FloodColor | AId::StopColor => \"black\",\n\n        AId::FillOpacity\n        | AId::FloodOpacity\n        | AId::Opacity\n        | AId::StopOpacity\n        | AId::StrokeOpacity => \"1\",\n\n        AId::ClipRule | AId::FillRule => \"nonzero\",\n\n        AId::BaselineShift => \"baseline\",\n        AId::ColorInterpolationFilters => \"linearRGB\",\n        AId::Direction => \"ltr\",\n        AId::Display => \"inline\",\n        AId::FontSize => \"medium\",\n        AId::Overflow => \"visible\",\n        AId::StrokeDashoffset => \"0\",\n        AId::StrokeLinecap => \"butt\",\n        AId::StrokeLinejoin => \"miter\",\n        AId::StrokeMiterlimit => \"4\",\n        AId::StrokeWidth => \"1\",\n        AId::TextAnchor => \"start\",\n        AId::Visibility => \"visible\",\n        AId::WritingMode => \"lr-tb\",\n        _ => return false,\n    };\n\n    doc.append_attribute(aid, roxmltree::StringStorage::Borrowed(value), false);\n    true\n}\n\nfn resolve_href<'a, 'input: 'a>(\n    node: roxmltree::Node<'a, 'input>,\n    id_map: &HashMap<&str, roxmltree::Node<'a, 'input>>,\n) -> Option<roxmltree::Node<'a, 'input>> {\n    let link_value = node\n        .attribute((XLINK_NS, \"href\"))\n        .or_else(|| node.attribute(\"href\"))?;\n\n    let link_id = svgtypes::IRI::from_str(link_value).ok()?.0;\n\n    id_map.get(link_id).copied()\n}\n\nfn parse_svg_use_element<'input>(\n    node: roxmltree::Node<'_, 'input>,\n    origin: roxmltree::Node,\n    parent_id: NodeId,\n    style_sheet: &simplecss::StyleSheet,\n    depth: u32,\n    doc: &mut Document<'input>,\n    id_map: &HashMap<&str, roxmltree::Node<'_, 'input>>,\n) -> Result<(), Error> {\n    let link = match resolve_href(node, id_map) {\n        Some(v) => v,\n        None => return Ok(()),\n    };\n\n    if link == node || link == origin {\n        log::warn!(\n            \"Recursive 'use' detected. '{}' will be skipped.\",\n            node.attribute((SVG_NS, \"id\")).unwrap_or_default()\n        );\n        return Ok(());\n    }\n\n    // Make sure we're linked to an SVG element.\n    if parse_tag_name(link).is_none() {\n        return Ok(());\n    }\n\n    // Check that none of the linked node's children reference current `use` node\n    // via other `use` node.\n    //\n    // Example:\n    // <g id=\"g1\">\n    //     <use xlink:href=\"#use1\" id=\"use2\"/>\n    // </g>\n    // <use xlink:href=\"#g1\" id=\"use1\"/>\n    //\n    // `use2` should be removed.\n    //\n    // Also, child should not reference its parent:\n    // <g id=\"g1\">\n    //     <use xlink:href=\"#g1\" id=\"use1\"/>\n    // </g>\n    //\n    // `use1` should be removed.\n    let mut is_recursive = false;\n    for link_child in link\n        .descendants()\n        .skip(1)\n        .filter(|n| n.has_tag_name((SVG_NS, \"use\")))\n    {\n        if let Some(link2) = resolve_href(link_child, id_map) {\n            if link2 == node || link2 == link {\n                is_recursive = true;\n                break;\n            }\n        }\n    }\n\n    if is_recursive {\n        log::warn!(\n            \"Recursive 'use' detected. '{}' will be skipped.\",\n            node.attribute((SVG_NS, \"id\")).unwrap_or_default()\n        );\n        return Ok(());\n    }\n\n    parse_xml_node(\n        link,\n        node,\n        parent_id,\n        style_sheet,\n        true,\n        depth + 1,\n        doc,\n        id_map,\n    )\n}\n\nfn resolve_css<'a>(\n    xml: &'a roxmltree::Document<'a>,\n    style_sheet: Option<&'a str>,\n) -> simplecss::StyleSheet<'a> {\n    let mut sheet = simplecss::StyleSheet::new();\n\n    // Injected style sheets do not override internal ones (we mimic the logic of rsvg-convert),\n    // so we need to parse it first.\n    if let Some(style_sheet) = style_sheet {\n        sheet.parse_more(style_sheet);\n    }\n\n    for node in xml.descendants().filter(|n| n.has_tag_name(\"style\")) {\n        match node.attribute(\"type\") {\n            Some(\"text/css\") => {}\n            Some(_) => continue,\n            None => {}\n        }\n\n        let text = match node.text() {\n            Some(v) => v,\n            None => continue,\n        };\n\n        sheet.parse_more(text);\n    }\n\n    sheet\n}\n\nstruct XmlNode<'a, 'input: 'a>(roxmltree::Node<'a, 'input>);\n\nimpl simplecss::Element for XmlNode<'_, '_> {\n    fn parent_element(&self) -> Option<Self> {\n        self.0.parent_element().map(XmlNode)\n    }\n\n    fn prev_sibling_element(&self) -> Option<Self> {\n        self.0.prev_sibling_element().map(XmlNode)\n    }\n\n    fn has_local_name(&self, local_name: &str) -> bool {\n        self.0.tag_name().name() == local_name\n    }\n\n    fn attribute_matches(&self, local_name: &str, operator: simplecss::AttributeOperator) -> bool {\n        match self.0.attribute(local_name) {\n            Some(value) => operator.matches(value),\n            None => false,\n        }\n    }\n\n    fn pseudo_class_matches(&self, class: simplecss::PseudoClass) -> bool {\n        match class {\n            simplecss::PseudoClass::FirstChild => self.prev_sibling_element().is_none(),\n            // TODO: lang\n            _ => false, // Since we are querying a static SVG we can ignore other pseudo-classes.\n        }\n    }\n}\n\nfn fix_recursive_patterns(doc: &mut Document) {\n    while let Some(node_id) = find_recursive_pattern(AId::Fill, doc) {\n        let idx = doc.get(node_id).attribute_id(AId::Fill).unwrap();\n        doc.attrs[idx].value = roxmltree::StringStorage::Borrowed(\"none\");\n    }\n\n    while let Some(node_id) = find_recursive_pattern(AId::Stroke, doc) {\n        let idx = doc.get(node_id).attribute_id(AId::Stroke).unwrap();\n        doc.attrs[idx].value = roxmltree::StringStorage::Borrowed(\"none\");\n    }\n}\n\nfn find_recursive_pattern(aid: AId, doc: &mut Document) -> Option<NodeId> {\n    for pattern_node in doc\n        .root()\n        .descendants()\n        .filter(|n| n.tag_name() == Some(EId::Pattern))\n    {\n        for node in pattern_node.descendants() {\n            let value = match node.attribute(aid) {\n                Some(v) => v,\n                None => continue,\n            };\n\n            if let Ok(svgtypes::Paint::FuncIRI(link_id, _)) = svgtypes::Paint::from_str(value) {\n                if link_id == pattern_node.element_id() {\n                    // If a pattern child has a link to the pattern itself\n                    // then we have to replace it with `none`.\n                    // Otherwise we will get endless loop/recursion and stack overflow.\n                    return Some(node.id);\n                } else {\n                    // Check that linked node children doesn't link this pattern.\n                    if let Some(linked_node) = doc.element_by_id(link_id) {\n                        for node2 in linked_node.descendants() {\n                            let value2 = match node2.attribute(aid) {\n                                Some(v) => v,\n                                None => continue,\n                            };\n\n                            if let Ok(svgtypes::Paint::FuncIRI(link_id2, _)) =\n                                svgtypes::Paint::from_str(value2)\n                            {\n                                if link_id2 == pattern_node.element_id() {\n                                    return Some(node2.id);\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    None\n}\n\nfn fix_recursive_links(eid: EId, aid: AId, doc: &mut Document) {\n    while let Some(node_id) = find_recursive_link(eid, aid, doc) {\n        let idx = doc.get(node_id).attribute_id(aid).unwrap();\n        doc.attrs[idx].value = roxmltree::StringStorage::Borrowed(\"none\");\n    }\n}\n\nfn find_recursive_link(eid: EId, aid: AId, doc: &Document) -> Option<NodeId> {\n    for node in doc\n        .root()\n        .descendants()\n        .filter(|n| n.tag_name() == Some(eid))\n    {\n        for child in node.descendants() {\n            if let Some(link) = child.node_attribute(aid) {\n                if link == node {\n                    // If an element child has a link to the element itself\n                    // then we have to replace it with `none`.\n                    // Otherwise we will get endless loop/recursion and stack overflow.\n                    return Some(child.id);\n                } else {\n                    // Check that linked node children doesn't link this element.\n                    for node2 in link.descendants() {\n                        if let Some(link2) = node2.node_attribute(aid) {\n                            if link2 == node {\n                                return Some(node2.id);\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    None\n}\n\n/// Detects cases like:\n///\n/// ```xml\n/// <filter id=\"filter1\">\n///   <feImage xlink:href=\"#rect1\"/>\n/// </filter>\n/// <rect id=\"rect1\" x=\"36\" y=\"36\" width=\"120\" height=\"120\" fill=\"green\" filter=\"url(#filter1)\"/>\n/// ```\nfn fix_recursive_fe_image(doc: &mut Document) {\n    let mut ids = Vec::new();\n    for fe_node in doc\n        .root()\n        .descendants()\n        .filter(|n| n.tag_name() == Some(EId::FeImage))\n    {\n        if let Some(link) = fe_node.node_attribute(AId::Href) {\n            if let Some(filter_uri) = link.attribute::<&str>(AId::Filter) {\n                let filter_id = fe_node.parent().unwrap().element_id();\n                for func in svgtypes::FilterValueListParser::from(filter_uri).flatten() {\n                    if let svgtypes::FilterValue::Url(url) = func {\n                        if url == filter_id {\n                            ids.push(link.id);\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    for id in ids {\n        let idx = doc.get(id).attribute_id(AId::Filter).unwrap();\n        doc.attrs[idx].value = roxmltree::StringStorage::Borrowed(\"none\");\n    }\n}\n"
  },
  {
    "path": "crates/usvg/src/parser/svgtree/text.rs",
    "content": "// Copyright 2021 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\n#![allow(clippy::comparison_chain)]\n\nuse roxmltree::Error;\n\nuse super::{AId, Document, EId, NodeId, NodeKind, SvgNode};\n\nconst XLINK_NS: &str = \"http://www.w3.org/1999/xlink\";\n\npub(crate) fn parse_svg_text_element<'input>(\n    parent: roxmltree::Node<'_, 'input>,\n    parent_id: NodeId,\n    style_sheet: &simplecss::StyleSheet,\n    doc: &mut Document<'input>,\n) -> Result<(), Error> {\n    debug_assert_eq!(parent.tag_name().name(), \"text\");\n\n    let space = if doc.get(parent_id).has_attribute(AId::Space) {\n        get_xmlspace(doc, parent_id, XmlSpace::Default)\n    } else {\n        if let Some(node) = doc\n            .get(parent_id)\n            .ancestors()\n            .find(|n| n.has_attribute(AId::Space))\n        {\n            get_xmlspace(doc, node.id, XmlSpace::Default)\n        } else {\n            XmlSpace::Default\n        }\n    };\n\n    parse_svg_text_element_impl(parent, parent_id, style_sheet, space, doc)?;\n\n    trim_text_nodes(parent_id, space, doc);\n    Ok(())\n}\n\nfn parse_svg_text_element_impl<'input>(\n    parent: roxmltree::Node<'_, 'input>,\n    parent_id: NodeId,\n    style_sheet: &simplecss::StyleSheet,\n    space: XmlSpace,\n    doc: &mut Document<'input>,\n) -> Result<(), Error> {\n    for node in parent.children() {\n        if node.is_text() {\n            let text = trim_text(node.text().unwrap(), space);\n            doc.append(parent_id, NodeKind::Text(text));\n            continue;\n        }\n\n        let mut tag_name = match super::parse::parse_tag_name(node) {\n            Some(v) => v,\n            None => continue,\n        };\n\n        if tag_name == EId::A {\n            // Treat links as simple text.\n            tag_name = EId::Tspan;\n        }\n\n        if !matches!(tag_name, EId::Tspan | EId::Tref | EId::TextPath) {\n            continue;\n        }\n\n        // `textPath` must be a direct `text` child.\n        if tag_name == EId::TextPath && parent.tag_name().name() != \"text\" {\n            continue;\n        }\n\n        // We are converting `tref` into `tspan` to simplify later use.\n        let mut is_tref = false;\n        if tag_name == EId::Tref {\n            tag_name = EId::Tspan;\n            is_tref = true;\n        }\n\n        let node_id =\n            super::parse::parse_svg_element(node, parent_id, tag_name, style_sheet, false, doc)?;\n        let space = get_xmlspace(doc, node_id, space);\n\n        if is_tref {\n            let link_value = node\n                .attribute((XLINK_NS, \"href\"))\n                .or_else(|| node.attribute(\"href\"));\n\n            if let Some(href) = link_value {\n                if let Some(text) = resolve_tref_text(node.document(), href) {\n                    let text = trim_text(&text, space);\n                    doc.append(node_id, NodeKind::Text(text));\n                }\n            }\n        } else {\n            parse_svg_text_element_impl(node, node_id, style_sheet, space, doc)?;\n        }\n    }\n\n    Ok(())\n}\n\nfn resolve_tref_text(xml: &roxmltree::Document, href: &str) -> Option<String> {\n    let id = svgtypes::IRI::from_str(href).ok()?.0;\n\n    // Find linked element in the original tree.\n    let node = xml.descendants().find(|n| n.attribute(\"id\") == Some(id))?;\n\n    // `tref` should be linked to an SVG element.\n    super::parse::parse_tag_name(node)?;\n\n    // 'All character data within the referenced element, including character data enclosed\n    // within additional markup, will be rendered.'\n    //\n    // So we don't care about attributes and everything. Just collecting text nodes data.\n    //\n    // Note: we have to filter nodes by `is_text()` first since `text()` will look up\n    // for text nodes in element children therefore we will get duplicates.\n    let text: String = node\n        .descendants()\n        .filter(|n| n.is_text())\n        .filter_map(|n| n.text())\n        .collect();\n    if text.is_empty() { None } else { Some(text) }\n}\n\n#[derive(Clone, Copy, PartialEq, Debug)]\nenum XmlSpace {\n    Default,\n    Preserve,\n}\n\nfn get_xmlspace(doc: &Document, node_id: NodeId, default: XmlSpace) -> XmlSpace {\n    match doc.get(node_id).attribute(AId::Space) {\n        Some(\"preserve\") => XmlSpace::Preserve,\n        Some(_) => XmlSpace::Default,\n        _ => default,\n    }\n}\n\ntrait StrTrim {\n    fn remove_first_space(&mut self);\n    fn remove_last_space(&mut self);\n}\n\nimpl StrTrim for String {\n    fn remove_first_space(&mut self) {\n        debug_assert_eq!(self.chars().next().unwrap(), ' ');\n        self.drain(0..1);\n    }\n\n    fn remove_last_space(&mut self) {\n        debug_assert_eq!(self.chars().next_back().unwrap(), ' ');\n        self.pop();\n    }\n}\n\n/// Prepares text nodes according to the spec: https://www.w3.org/TR/SVG11/text.html#WhiteSpace\n///\n/// This function handles:\n/// - 'xml:space' processing\n/// - tabs and newlines removing/replacing\n/// - spaces trimming\nfn trim_text_nodes(text_elem_id: NodeId, xmlspace: XmlSpace, doc: &mut Document) {\n    let mut nodes = Vec::new(); // TODO: allocate only once\n    collect_text_nodes(doc.get(text_elem_id), 0, &mut nodes);\n\n    // `trim` method has already collapsed all spaces into a single one,\n    // so we have to check only for one leading or trailing space.\n\n    if nodes.len() == 1 {\n        // Process element with a single text node child.\n\n        let node_id = nodes[0].0;\n\n        if xmlspace == XmlSpace::Default {\n            if let NodeKind::Text(ref mut text) = doc.nodes[node_id.get_usize()].kind {\n                match text.len() {\n                    0 => {} // An empty string. Do nothing.\n                    1 => {\n                        // If string has only one character and it's a space - clear this string.\n                        if text.as_bytes()[0] == b' ' {\n                            text.clear();\n                        }\n                    }\n                    _ => {\n                        // 'text' has at least 2 bytes, so indexing is safe.\n                        let c1 = text.as_bytes()[0];\n                        let c2 = text.as_bytes()[text.len() - 1];\n\n                        if c1 == b' ' {\n                            text.remove_first_space();\n                        }\n\n                        if c2 == b' ' {\n                            text.remove_last_space();\n                        }\n                    }\n                }\n            }\n        } else {\n            // Do nothing when xml:space=preserve.\n        }\n    } else if nodes.len() > 1 {\n        // Process element with many text node children.\n\n        // We manage all text nodes as a single text node\n        // and trying to remove duplicated spaces across nodes.\n        //\n        // For example    '<text>Text <tspan> text </tspan> text</text>'\n        // is the same is '<text>Text <tspan>text</tspan> text</text>'\n\n        let mut i = 0;\n        let len = nodes.len() - 1;\n        let mut last_non_empty: Option<NodeId> = None;\n        while i < len {\n            // Process pairs.\n            let (mut node1_id, depth1) = nodes[i];\n            let (node2_id, depth2) = nodes[i + 1];\n\n            if doc.get(node1_id).text().is_empty() {\n                if let Some(n) = last_non_empty {\n                    node1_id = n;\n                }\n            }\n\n            // Parent of the text node is always an element node and always exist,\n            // so unwrap is safe.\n            let xmlspace1 = get_xmlspace(doc, doc.get(node1_id).parent().unwrap().id, xmlspace);\n            let xmlspace2 = get_xmlspace(doc, doc.get(node2_id).parent().unwrap().id, xmlspace);\n\n            // >text<..>text<\n            //  1  2    3  4\n            let (c1, c2, c3, c4) = {\n                let text1 = doc.get(node1_id).text();\n                let text2 = doc.get(node2_id).text();\n\n                let bytes1 = text1.as_bytes();\n                let bytes2 = text2.as_bytes();\n\n                let c1 = bytes1.first().cloned();\n                let c2 = bytes1.last().cloned();\n                let c3 = bytes2.first().cloned();\n                let c4 = bytes2.last().cloned();\n\n                (c1, c2, c3, c4)\n            };\n\n            // NOTE: xml:space processing is mostly an undefined behavior,\n            // because everyone do it differently.\n            // We're mimicking the Chrome behavior.\n\n            // Remove space from the second text node if both nodes has bound spaces.\n            // From: '<text>Text <tspan> text</tspan></text>'\n            // To:   '<text>Text <tspan>text</tspan></text>'\n            //\n            // See text-tspan-02-b.svg for details.\n            if depth1 < depth2 {\n                if c3 == Some(b' ') {\n                    if xmlspace2 == XmlSpace::Default {\n                        if let NodeKind::Text(ref mut text) = doc.nodes[node2_id.get_usize()].kind {\n                            text.remove_first_space();\n                        }\n                    }\n                }\n            } else {\n                if c2 == Some(b' ') && c2 == c3 {\n                    if xmlspace1 == XmlSpace::Default && xmlspace2 == XmlSpace::Default {\n                        if let NodeKind::Text(ref mut text) = doc.nodes[node1_id.get_usize()].kind {\n                            text.remove_last_space();\n                        }\n                    } else {\n                        if xmlspace1 == XmlSpace::Preserve && xmlspace2 == XmlSpace::Default {\n                            if let NodeKind::Text(ref mut text) =\n                                doc.nodes[node2_id.get_usize()].kind\n                            {\n                                text.remove_first_space();\n                            }\n                        }\n                    }\n                }\n            }\n\n            let is_first = i == 0;\n            let is_last = i == len - 1;\n\n            if is_first\n                && c1 == Some(b' ')\n                && xmlspace1 == XmlSpace::Default\n                && !doc.get(node1_id).text().is_empty()\n            {\n                // Remove a leading space from a first text node.\n                if let NodeKind::Text(ref mut text) = doc.nodes[node1_id.get_usize()].kind {\n                    text.remove_first_space();\n                }\n            } else if is_last\n                && c4 == Some(b' ')\n                && !doc.get(node2_id).text().is_empty()\n                && xmlspace2 == XmlSpace::Default\n            {\n                // Remove a trailing space from a last text node.\n                // Also check that 'text2' is not empty already.\n                if let NodeKind::Text(ref mut text) = doc.nodes[node2_id.get_usize()].kind {\n                    text.remove_last_space();\n                }\n            }\n\n            if is_last\n                && c2 == Some(b' ')\n                && !doc.get(node1_id).text().is_empty()\n                && doc.get(node2_id).text().is_empty()\n                && doc.get(node1_id).text().ends_with(' ')\n            {\n                if let NodeKind::Text(ref mut text) = doc.nodes[node1_id.get_usize()].kind {\n                    text.remove_last_space();\n                }\n            }\n\n            if !doc.get(node1_id).text().trim().is_empty() {\n                last_non_empty = Some(node1_id);\n            }\n\n            i += 1;\n        }\n    }\n\n    // TODO: find a way to remove all empty text nodes\n}\n\nfn collect_text_nodes(parent: SvgNode, depth: usize, nodes: &mut Vec<(NodeId, usize)>) {\n    for child in parent.children() {\n        if child.is_text() {\n            nodes.push((child.id, depth));\n        } else if child.is_element() {\n            collect_text_nodes(child, depth + 1, nodes);\n        }\n    }\n}\n\nfn trim_text(text: &str, space: XmlSpace) -> String {\n    let mut s = String::with_capacity(text.len());\n\n    let mut prev = '0';\n    for c in text.chars() {\n        // \\r, \\n and \\t should be converted into spaces.\n        let c = match c {\n            '\\r' | '\\n' | '\\t' => ' ',\n            _ => c,\n        };\n\n        // Skip continuous spaces.\n        if space == XmlSpace::Default && c == ' ' && c == prev {\n            continue;\n        }\n\n        prev = c;\n\n        s.push(c);\n    }\n\n    s\n}\n"
  },
  {
    "path": "crates/usvg/src/parser/switch.rs",
    "content": "// Copyright 2018 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse super::svgtree::{AId, SvgNode};\nuse super::{Options, converter};\nuse crate::{Group, Node};\n\n// Full list can be found here: https://www.w3.org/TR/SVG11/feature.html\nstatic FEATURES: &[&str] = &[\n    \"http://www.w3.org/TR/SVG11/feature#SVGDOM-static\",\n    \"http://www.w3.org/TR/SVG11/feature#SVG-static\",\n    \"http://www.w3.org/TR/SVG11/feature#CoreAttribute\", // no xml:base and xml:lang\n    \"http://www.w3.org/TR/SVG11/feature#Structure\",\n    \"http://www.w3.org/TR/SVG11/feature#BasicStructure\",\n    \"http://www.w3.org/TR/SVG11/feature#ContainerAttribute\", // `enable-background`\n    \"http://www.w3.org/TR/SVG11/feature#ConditionalProcessing\",\n    \"http://www.w3.org/TR/SVG11/feature#Image\",\n    \"http://www.w3.org/TR/SVG11/feature#Style\",\n    // \"http://www.w3.org/TR/SVG11/feature#ViewportAttribute\", // `clip` and `overflow`, not yet\n    \"http://www.w3.org/TR/SVG11/feature#Shape\",\n    \"http://www.w3.org/TR/SVG11/feature#Text\",\n    \"http://www.w3.org/TR/SVG11/feature#BasicText\",\n    \"http://www.w3.org/TR/SVG11/feature#PaintAttribute\", // no color-interpolation and color-rendering\n    \"http://www.w3.org/TR/SVG11/feature#BasicPaintAttribute\", // no color-interpolation\n    \"http://www.w3.org/TR/SVG11/feature#OpacityAttribute\",\n    \"http://www.w3.org/TR/SVG11/feature#GraphicsAttribute\",\n    \"http://www.w3.org/TR/SVG11/feature#BasicGraphicsAttribute\",\n    \"http://www.w3.org/TR/SVG11/feature#Marker\",\n    // \"http://www.w3.org/TR/SVG11/feature#ColorProfile\", // not yet\n    \"http://www.w3.org/TR/SVG11/feature#Gradient\",\n    \"http://www.w3.org/TR/SVG11/feature#Pattern\",\n    \"http://www.w3.org/TR/SVG11/feature#Clip\",\n    \"http://www.w3.org/TR/SVG11/feature#BasicClip\",\n    \"http://www.w3.org/TR/SVG11/feature#Mask\",\n    \"http://www.w3.org/TR/SVG11/feature#Filter\",\n    \"http://www.w3.org/TR/SVG11/feature#BasicFilter\",\n    // only xlink:href\n    \"http://www.w3.org/TR/SVG11/feature#XlinkAttribute\",\n    // \"http://www.w3.org/TR/SVG11/feature#Font\",\n    // \"http://www.w3.org/TR/SVG11/feature#BasicFont\",\n];\n\npub(crate) fn convert(\n    node: SvgNode,\n    state: &converter::State,\n    cache: &mut converter::Cache,\n    parent: &mut Group,\n) -> Option<()> {\n    let child = node\n        .children()\n        .find(|n| is_condition_passed(*n, state.opt))?;\n    if let Some(g) = converter::convert_group(node, state, false, cache, parent, &|cache, g| {\n        converter::convert_element(child, state, cache, g);\n    }) {\n        parent.children.push(Node::Group(Box::new(g)));\n    }\n\n    Some(())\n}\n\npub(crate) fn is_condition_passed(node: SvgNode, opt: &Options) -> bool {\n    if !node.is_element() {\n        return false;\n    }\n\n    if node.has_attribute(AId::RequiredExtensions) {\n        return false;\n    }\n\n    // 'The value is a list of feature strings, with the individual values separated by white space.\n    // Determines whether all of the named features are supported by the user agent.\n    // Only feature strings defined in the Feature String appendix are allowed.\n    // If all of the given features are supported, then the attribute evaluates to true;\n    // otherwise, the current element and its children are skipped and thus will not be rendered.'\n    if let Some(features) = node.attribute::<&str>(AId::RequiredFeatures) {\n        for feature in features.split(' ') {\n            if !FEATURES.contains(&feature) {\n                return false;\n            }\n        }\n    }\n\n    if !is_valid_sys_lang(node, opt) {\n        return false;\n    }\n\n    true\n}\n\n/// SVG spec 5.8.5\nfn is_valid_sys_lang(node: SvgNode, opt: &Options) -> bool {\n    // 'The attribute value is a comma-separated list of language names\n    // as defined in BCP 47.'\n    //\n    // But we support only simple cases like `en` or `en-US`.\n    // No one really uses this, especially with complex BCP 47 values.\n    if let Some(langs) = node.attribute::<&str>(AId::SystemLanguage) {\n        let mut has_match = false;\n        for lang in langs.split(',') {\n            let lang = lang.trim();\n\n            // 'Evaluates to `true` if one of the languages indicated by user preferences exactly\n            // equals one of the languages given in the value of this parameter.'\n            if opt.languages.iter().any(|v| v == lang) {\n                has_match = true;\n                break;\n            }\n\n            // 'If one of the languages indicated by user preferences exactly equals a prefix\n            // of one of the languages given in the value of this parameter such that\n            // the first tag character following the prefix is `-`.'\n            if let Some(idx) = lang.bytes().position(|c| c == b'-') {\n                let lang_prefix = &lang[..idx];\n                if opt.languages.iter().any(|v| v == lang_prefix) {\n                    has_match = true;\n                    break;\n                }\n            }\n        }\n\n        has_match\n    } else {\n        true\n    }\n}\n"
  },
  {
    "path": "crates/usvg/src/parser/text.rs",
    "content": "// Copyright 2019 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse std::sync::Arc;\n\nuse kurbo::{ParamCurve, ParamCurveArclen};\nuse svgtypes::{FontFamily, Length, LengthUnit, parse_font_families};\n\nuse super::svgtree::{AId, EId, FromValue, SvgNode};\nuse super::{OptionLog, converter, style};\nuse crate::*;\n\nimpl<'a, 'input: 'a> FromValue<'a, 'input> for TextAnchor {\n    fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {\n        match value {\n            \"start\" => Some(TextAnchor::Start),\n            \"middle\" => Some(TextAnchor::Middle),\n            \"end\" => Some(TextAnchor::End),\n            _ => None,\n        }\n    }\n}\n\nimpl<'a, 'input: 'a> FromValue<'a, 'input> for AlignmentBaseline {\n    fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {\n        match value {\n            \"auto\" => Some(AlignmentBaseline::Auto),\n            \"baseline\" => Some(AlignmentBaseline::Baseline),\n            \"before-edge\" => Some(AlignmentBaseline::BeforeEdge),\n            \"text-before-edge\" => Some(AlignmentBaseline::TextBeforeEdge),\n            \"middle\" => Some(AlignmentBaseline::Middle),\n            \"central\" => Some(AlignmentBaseline::Central),\n            \"after-edge\" => Some(AlignmentBaseline::AfterEdge),\n            \"text-after-edge\" => Some(AlignmentBaseline::TextAfterEdge),\n            \"ideographic\" => Some(AlignmentBaseline::Ideographic),\n            \"alphabetic\" => Some(AlignmentBaseline::Alphabetic),\n            \"hanging\" => Some(AlignmentBaseline::Hanging),\n            \"mathematical\" => Some(AlignmentBaseline::Mathematical),\n            _ => None,\n        }\n    }\n}\n\nimpl<'a, 'input: 'a> FromValue<'a, 'input> for DominantBaseline {\n    fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {\n        match value {\n            \"auto\" => Some(DominantBaseline::Auto),\n            \"use-script\" => Some(DominantBaseline::UseScript),\n            \"no-change\" => Some(DominantBaseline::NoChange),\n            \"reset-size\" => Some(DominantBaseline::ResetSize),\n            \"ideographic\" => Some(DominantBaseline::Ideographic),\n            \"alphabetic\" => Some(DominantBaseline::Alphabetic),\n            \"hanging\" => Some(DominantBaseline::Hanging),\n            \"mathematical\" => Some(DominantBaseline::Mathematical),\n            \"central\" => Some(DominantBaseline::Central),\n            \"middle\" => Some(DominantBaseline::Middle),\n            \"text-after-edge\" => Some(DominantBaseline::TextAfterEdge),\n            \"text-before-edge\" => Some(DominantBaseline::TextBeforeEdge),\n            _ => None,\n        }\n    }\n}\n\nimpl<'a, 'input: 'a> FromValue<'a, 'input> for LengthAdjust {\n    fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {\n        match value {\n            \"spacing\" => Some(LengthAdjust::Spacing),\n            \"spacingAndGlyphs\" => Some(LengthAdjust::SpacingAndGlyphs),\n            _ => None,\n        }\n    }\n}\n\nimpl<'a, 'input: 'a> FromValue<'a, 'input> for FontStyle {\n    fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {\n        match value {\n            \"normal\" => Some(FontStyle::Normal),\n            \"italic\" => Some(FontStyle::Italic),\n            \"oblique\" => Some(FontStyle::Oblique),\n            _ => None,\n        }\n    }\n}\n\n/// A text character position.\n///\n/// _Character_ is a Unicode codepoint.\n#[derive(Clone, Copy, Debug)]\nstruct CharacterPosition {\n    /// An absolute X axis position.\n    x: Option<f32>,\n    /// An absolute Y axis position.\n    y: Option<f32>,\n    /// A relative X axis offset.\n    dx: Option<f32>,\n    /// A relative Y axis offset.\n    dy: Option<f32>,\n}\n\npub(crate) fn convert(\n    text_node: SvgNode,\n    state: &converter::State,\n    cache: &mut converter::Cache,\n    parent: &mut Group,\n) {\n    let pos_list = resolve_positions_list(text_node, state);\n    let rotate_list = resolve_rotate_list(text_node);\n    let writing_mode = convert_writing_mode(text_node);\n\n    let chunks = collect_text_chunks(text_node, &pos_list, state, cache);\n\n    let rendering_mode: TextRendering = text_node\n        .find_attribute(AId::TextRendering)\n        .unwrap_or(state.opt.text_rendering);\n\n    // Nodes generated by markers must not have an ID. Otherwise we would have duplicates.\n    let id = if state.parent_markers.is_empty() {\n        text_node.element_id().to_string()\n    } else {\n        String::new()\n    };\n\n    let dummy = Rect::from_xywh(0.0, 0.0, 0.0, 0.0).unwrap();\n\n    let mut text = Text {\n        id,\n        rendering_mode,\n        dx: pos_list.iter().map(|v| v.dx.unwrap_or(0.0)).collect(),\n        dy: pos_list.iter().map(|v| v.dy.unwrap_or(0.0)).collect(),\n        rotate: rotate_list,\n        writing_mode,\n        chunks,\n        abs_transform: parent.abs_transform,\n        // All fields below will be reset by `text_to_paths`.\n        bounding_box: dummy,\n        abs_bounding_box: dummy,\n        stroke_bounding_box: dummy,\n        abs_stroke_bounding_box: dummy,\n        flattened: Box::new(Group::empty()),\n        layouted: vec![],\n    };\n\n    if text::convert(&mut text, &state.opt.font_resolver, cache).is_none() {\n        return;\n    }\n\n    parent.children.push(Node::Text(Box::new(text)));\n}\n\nstruct IterState {\n    chars_count: usize,\n    chunk_bytes_count: usize,\n    split_chunk: bool,\n    text_flow: TextFlow,\n    chunks: Vec<TextChunk>,\n}\n\nfn collect_text_chunks(\n    text_node: SvgNode,\n    pos_list: &[CharacterPosition],\n    state: &converter::State,\n    cache: &mut converter::Cache,\n) -> Vec<TextChunk> {\n    let mut iter_state = IterState {\n        chars_count: 0,\n        chunk_bytes_count: 0,\n        split_chunk: false,\n        text_flow: TextFlow::Linear,\n        chunks: Vec::new(),\n    };\n\n    collect_text_chunks_impl(text_node, pos_list, state, cache, &mut iter_state);\n\n    iter_state.chunks\n}\n\nfn collect_text_chunks_impl(\n    parent: SvgNode,\n    pos_list: &[CharacterPosition],\n    state: &converter::State,\n    cache: &mut converter::Cache,\n    iter_state: &mut IterState,\n) {\n    for child in parent.children() {\n        if child.is_element() {\n            if child.tag_name() == Some(EId::TextPath) {\n                if parent.tag_name() != Some(EId::Text) {\n                    // `textPath` can be set only as a direct `text` element child.\n                    iter_state.chars_count += count_chars(child);\n                    continue;\n                }\n\n                match resolve_text_flow(child, state) {\n                    Some(v) => {\n                        iter_state.text_flow = v;\n                    }\n                    None => {\n                        // Skip an invalid text path and all it's children.\n                        // We have to update the chars count,\n                        // because `pos_list` was calculated including this text path.\n                        iter_state.chars_count += count_chars(child);\n                        continue;\n                    }\n                }\n\n                iter_state.split_chunk = true;\n            }\n\n            collect_text_chunks_impl(child, pos_list, state, cache, iter_state);\n\n            iter_state.text_flow = TextFlow::Linear;\n\n            // Next char after `textPath` should be split too.\n            if child.tag_name() == Some(EId::TextPath) {\n                iter_state.split_chunk = true;\n            }\n\n            continue;\n        }\n\n        if !parent.is_visible_element(state.opt) {\n            iter_state.chars_count += child.text().chars().count();\n            continue;\n        }\n\n        let anchor = parent.find_attribute(AId::TextAnchor).unwrap_or_default();\n\n        // TODO: what to do when <= 0? UB?\n        let font_size = super::units::resolve_font_size(parent, state);\n        let font_size = match NonZeroPositiveF32::new(font_size) {\n            Some(n) => n,\n            None => {\n                // Skip this span.\n                iter_state.chars_count += child.text().chars().count();\n                continue;\n            }\n        };\n\n        let font = convert_font(parent, state);\n\n        let raw_paint_order: svgtypes::PaintOrder =\n            parent.find_attribute(AId::PaintOrder).unwrap_or_default();\n        let paint_order = super::converter::svg_paint_order_to_usvg(raw_paint_order);\n\n        let mut dominant_baseline = parent\n            .find_attribute(AId::DominantBaseline)\n            .unwrap_or_default();\n\n        // `no-change` means \"use parent\".\n        if dominant_baseline == DominantBaseline::NoChange {\n            dominant_baseline = parent\n                .parent_element()\n                .unwrap()\n                .find_attribute(AId::DominantBaseline)\n                .unwrap_or_default();\n        }\n\n        let mut apply_kerning = true;\n        #[allow(clippy::if_same_then_else)]\n        if parent.resolve_length(AId::Kerning, state, -1.0) == 0.0 {\n            apply_kerning = false;\n        } else if parent.find_attribute::<&str>(AId::FontKerning) == Some(\"none\") {\n            apply_kerning = false;\n        }\n\n        let font_optical_sizing = match parent.find_attribute::<&str>(AId::FontOpticalSizing) {\n            Some(\"none\") => crate::FontOpticalSizing::None,\n            _ => crate::FontOpticalSizing::Auto, // \"auto\" or missing (browser default)\n        };\n\n        let mut text_length =\n            parent.try_convert_length(AId::TextLength, Units::UserSpaceOnUse, state);\n        // Negative values should be ignored.\n        if let Some(n) = text_length {\n            if n < 0.0 {\n                text_length = None;\n            }\n        }\n\n        let visibility: Visibility = parent.find_attribute(AId::Visibility).unwrap_or_default();\n\n        let span = TextSpan {\n            start: 0,\n            end: 0,\n            fill: style::resolve_fill(parent, true, state, cache),\n            stroke: style::resolve_stroke(parent, true, state, cache),\n            paint_order,\n            font,\n            font_size,\n            small_caps: parent.find_attribute::<&str>(AId::FontVariant) == Some(\"small-caps\"),\n            apply_kerning,\n            font_optical_sizing,\n            decoration: resolve_decoration(parent, state, cache),\n            visible: visibility == Visibility::Visible,\n            dominant_baseline,\n            alignment_baseline: parent\n                .find_attribute(AId::AlignmentBaseline)\n                .unwrap_or_default(),\n            baseline_shift: convert_baseline_shift(parent, state),\n            letter_spacing: parent.resolve_length(AId::LetterSpacing, state, 0.0),\n            word_spacing: parent.resolve_length(AId::WordSpacing, state, 0.0),\n            text_length,\n            length_adjust: parent.find_attribute(AId::LengthAdjust).unwrap_or_default(),\n        };\n\n        let mut is_new_span = true;\n        for c in child.text().chars() {\n            let char_len = c.len_utf8();\n\n            // Create a new chunk if:\n            // - this is the first span (yes, position can be None)\n            // - text character has an absolute coordinate assigned to it (via x/y attribute)\n            // - `c` is the first char of the `textPath`\n            // - `c` is the first char after `textPath`\n            let is_new_chunk = pos_list[iter_state.chars_count].x.is_some()\n                || pos_list[iter_state.chars_count].y.is_some()\n                || iter_state.split_chunk\n                || iter_state.chunks.is_empty();\n\n            iter_state.split_chunk = false;\n\n            if is_new_chunk {\n                iter_state.chunk_bytes_count = 0;\n\n                let mut span2 = span.clone();\n                span2.start = 0;\n                span2.end = char_len;\n\n                iter_state.chunks.push(TextChunk {\n                    x: pos_list[iter_state.chars_count].x,\n                    y: pos_list[iter_state.chars_count].y,\n                    anchor,\n                    spans: vec![span2],\n                    text_flow: iter_state.text_flow.clone(),\n                    text: c.to_string(),\n                });\n            } else if is_new_span {\n                // Add this span to the last text chunk.\n                let mut span2 = span.clone();\n                span2.start = iter_state.chunk_bytes_count;\n                span2.end = iter_state.chunk_bytes_count + char_len;\n\n                if let Some(chunk) = iter_state.chunks.last_mut() {\n                    chunk.text.push(c);\n                    chunk.spans.push(span2);\n                }\n            } else {\n                // Extend the last span.\n                if let Some(chunk) = iter_state.chunks.last_mut() {\n                    chunk.text.push(c);\n                    if let Some(span) = chunk.spans.last_mut() {\n                        debug_assert_ne!(span.end, 0);\n                        span.end += char_len;\n                    }\n                }\n            }\n\n            is_new_span = false;\n            iter_state.chars_count += 1;\n            iter_state.chunk_bytes_count += char_len;\n        }\n    }\n}\n\nfn resolve_text_flow(node: SvgNode, state: &converter::State) -> Option<TextFlow> {\n    let linked_node = node.attribute::<SvgNode>(AId::Href)?;\n    let path = super::shapes::convert(linked_node, state)?;\n\n    // The reference path's transform needs to be applied\n    let transform = linked_node.resolve_transform(AId::Transform, state);\n    let path = if !transform.is_identity() {\n        let mut path_copy = path.as_ref().clone();\n        path_copy = path_copy.transform(transform)?;\n        Arc::new(path_copy)\n    } else {\n        path\n    };\n\n    let start_offset: Length = node.attribute(AId::StartOffset).unwrap_or_default();\n    let start_offset = if start_offset.unit == LengthUnit::Percent {\n        // 'If a percentage is given, then the `startOffset` represents\n        // a percentage distance along the entire path.'\n        let path_len = path_length(&path);\n        (path_len * (start_offset.number / 100.0)) as f32\n    } else {\n        node.resolve_length(AId::StartOffset, state, 0.0)\n    };\n\n    let id = NonEmptyString::new(linked_node.element_id().to_string())?;\n    Some(TextFlow::Path(Arc::new(TextPath {\n        id,\n        start_offset,\n        path,\n    })))\n}\n\nfn convert_font(node: SvgNode, state: &converter::State) -> Font {\n    let style: FontStyle = node.find_attribute(AId::FontStyle).unwrap_or_default();\n    let stretch = conv_font_stretch(node);\n    let weight = resolve_font_weight(node);\n    let mut variations = parse_font_variation_settings(node);\n\n    // Auto-map standard font properties to variation axes if not explicitly set.\n    // This allows variable fonts to work with regular font-weight/font-stretch properties.\n    let has_wght = variations.iter().any(|v| &v.tag == b\"wght\");\n    let has_wdth = variations.iter().any(|v| &v.tag == b\"wdth\");\n    let has_ital = variations.iter().any(|v| &v.tag == b\"ital\");\n    let has_slnt = variations.iter().any(|v| &v.tag == b\"slnt\");\n\n    // Map font-weight to wght axis (if not already set)\n    if !has_wght && weight != 400 {\n        variations.push(FontVariation::new(*b\"wght\", weight as f32));\n    }\n\n    // Map font-stretch to wdth axis (if not already set)\n    // CSS font-stretch percentages: ultra-condensed=50%, condensed=75%, normal=100%, expanded=125%, ultra-expanded=200%\n    if !has_wdth {\n        let wdth = match stretch {\n            FontStretch::UltraCondensed => 50.0,\n            FontStretch::ExtraCondensed => 62.5,\n            FontStretch::Condensed => 75.0,\n            FontStretch::SemiCondensed => 87.5,\n            FontStretch::Normal => 100.0,\n            FontStretch::SemiExpanded => 112.5,\n            FontStretch::Expanded => 125.0,\n            FontStretch::ExtraExpanded => 150.0,\n            FontStretch::UltraExpanded => 200.0,\n        };\n        if wdth != 100.0 {\n            variations.push(FontVariation::new(*b\"wdth\", wdth));\n        }\n    }\n\n    // Map font-style: italic to ital axis (if not already set)\n    if !has_ital && style == FontStyle::Italic {\n        variations.push(FontVariation::new(*b\"ital\", 1.0));\n    }\n\n    // Map font-style: oblique to slnt axis (if not already set)\n    // Default oblique angle is typically 12-14 degrees\n    if !has_slnt && style == FontStyle::Oblique {\n        variations.push(FontVariation::new(*b\"slnt\", -12.0));\n    }\n\n    let font_families = if let Some(n) = node.ancestors().find(|n| n.has_attribute(AId::FontFamily))\n    {\n        n.attribute(AId::FontFamily).unwrap_or(\"\")\n    } else {\n        \"\"\n    };\n\n    let mut families = parse_font_families(font_families)\n        .ok()\n        .log_none(|| {\n            log::warn!(\n                \"Failed to parse {} value: '{}'. Falling back to {}.\",\n                AId::FontFamily,\n                font_families,\n                state.opt.font_family\n            );\n        })\n        .unwrap_or_default();\n\n    if families.is_empty() {\n        families.push(FontFamily::Named(state.opt.font_family.clone()));\n    }\n\n    Font {\n        families,\n        style,\n        stretch,\n        weight,\n        variations,\n    }\n}\n\n/// Parses the `font-variation-settings` CSS property.\n///\n/// Syntax: `normal | [ <string> <number> ]#`\n/// Example: `\"wght\" 700, \"wdth\" 50`\nfn parse_font_variation_settings(node: SvgNode) -> Vec<FontVariation> {\n    let value = if let Some(n) = node\n        .ancestors()\n        .find(|n| n.has_attribute(AId::FontVariationSettings))\n    {\n        let v = n.attribute(AId::FontVariationSettings).unwrap_or(\"\");\n        log::debug!(\"Found font-variation-settings: '{}'\", v);\n        v\n    } else {\n        return Vec::new();\n    };\n\n    // \"normal\" means no variations\n    if value.eq_ignore_ascii_case(\"normal\") || value.is_empty() {\n        return Vec::new();\n    }\n\n    let mut variations = Vec::new();\n\n    // Parse comma-separated list of \"tag\" value pairs\n    for part in value.split(',') {\n        let part = part.trim();\n        if part.is_empty() {\n            continue;\n        }\n\n        // Find the tag (quoted string) and value\n        // Format: \"wght\" 700 or 'wght' 700\n        let mut chars = part.chars().peekable();\n\n        // Skip whitespace\n        while chars.peek().map_or(false, |c| c.is_whitespace()) {\n            chars.next();\n        }\n\n        // Parse quoted tag\n        let quote = match chars.next() {\n            Some('\"') => '\"',\n            Some('\\'') => '\\'',\n            _ => continue, // Invalid format\n        };\n\n        let mut tag_str = String::new();\n        for c in chars.by_ref() {\n            if c == quote {\n                break;\n            }\n            tag_str.push(c);\n        }\n\n        // Tag must be exactly 4 characters\n        if tag_str.len() != 4 {\n            log::warn!(\n                \"Invalid font-variation-settings tag: '{}' (must be 4 characters)\",\n                tag_str\n            );\n            continue;\n        }\n\n        // Skip whitespace before value\n        while chars.peek().map_or(false, |c| c.is_whitespace()) {\n            chars.next();\n        }\n\n        // Parse the numeric value\n        let value_str: String = chars.collect();\n        let value_str = value_str.trim();\n\n        let value = match value_str.parse::<f32>() {\n            Ok(v) => v,\n            Err(_) => {\n                log::warn!(\"Invalid font-variation-settings value: '{}'\", value_str);\n                continue;\n            }\n        };\n\n        let tag_bytes = tag_str.as_bytes();\n        let tag = [tag_bytes[0], tag_bytes[1], tag_bytes[2], tag_bytes[3]];\n\n        variations.push(FontVariation::new(tag, value));\n    }\n\n    variations\n}\n\n// TODO: properly resolve narrower/wider\nfn conv_font_stretch(node: SvgNode) -> FontStretch {\n    if let Some(n) = node.ancestors().find(|n| n.has_attribute(AId::FontStretch)) {\n        match n.attribute(AId::FontStretch).unwrap_or(\"\") {\n            \"narrower\" | \"condensed\" => FontStretch::Condensed,\n            \"ultra-condensed\" => FontStretch::UltraCondensed,\n            \"extra-condensed\" => FontStretch::ExtraCondensed,\n            \"semi-condensed\" => FontStretch::SemiCondensed,\n            \"semi-expanded\" => FontStretch::SemiExpanded,\n            \"wider\" | \"expanded\" => FontStretch::Expanded,\n            \"extra-expanded\" => FontStretch::ExtraExpanded,\n            \"ultra-expanded\" => FontStretch::UltraExpanded,\n            _ => FontStretch::Normal,\n        }\n    } else {\n        FontStretch::Normal\n    }\n}\n\nfn resolve_font_weight(node: SvgNode) -> u16 {\n    fn bound(min: usize, val: usize, max: usize) -> usize {\n        std::cmp::max(min, std::cmp::min(max, val))\n    }\n\n    let nodes: Vec<_> = node.ancestors().collect();\n    let mut weight = 400;\n    for n in nodes.iter().rev().skip(1) {\n        // skip Root\n        weight = match n.attribute(AId::FontWeight).unwrap_or(\"\") {\n            \"normal\" => 400,\n            \"bold\" => 700,\n            \"100\" => 100,\n            \"200\" => 200,\n            \"300\" => 300,\n            \"400\" => 400,\n            \"500\" => 500,\n            \"600\" => 600,\n            \"700\" => 700,\n            \"800\" => 800,\n            \"900\" => 900,\n            \"bolder\" => {\n                // By the CSS2 spec the default value should be 400\n                // so `bolder` will result in 500.\n                // But Chrome and Inkscape will give us 700.\n                // Have no idea is it a bug or something, but\n                // we will follow such behavior for now.\n                let step = if weight == 400 { 300 } else { 100 };\n\n                bound(100, weight + step, 900)\n            }\n            \"lighter\" => {\n                // By the CSS2 spec the default value should be 400\n                // so `lighter` will result in 300.\n                // But Chrome and Inkscape will give us 200.\n                // Have no idea is it a bug or something, but\n                // we will follow such behavior for now.\n                let step = if weight == 400 { 200 } else { 100 };\n\n                bound(100, weight - step, 900)\n            }\n            _ => weight,\n        };\n    }\n\n    weight as u16\n}\n\n/// Resolves text's character positions.\n///\n/// This includes: x, y, dx, dy.\n///\n/// # The character\n///\n/// The first problem with this task is that the *character* itself\n/// is basically undefined in the SVG spec. Sometimes it's an *XML character*,\n/// sometimes a *glyph*, and sometimes just a *character*.\n///\n/// There is an ongoing [discussion](https://github.com/w3c/svgwg/issues/537)\n/// on the SVG working group that addresses this by stating that a character\n/// is a Unicode code point. But it's not final.\n///\n/// Also, according to the SVG 2 spec, *character* is *a Unicode code point*.\n///\n/// Anyway, we treat a character as a Unicode code point.\n///\n/// # Algorithm\n///\n/// To resolve positions, we have to iterate over descendant nodes and\n/// if the current node is a `tspan` and has x/y/dx/dy attribute,\n/// than the positions from this attribute should be assigned to the characters\n/// of this `tspan` and it's descendants.\n///\n/// Positions list can have more values than characters in the `tspan`,\n/// so we have to clamp it, because values should not overlap, e.g.:\n///\n/// (we ignore whitespaces for example purposes,\n/// so the `text` content is `Text` and not `T ex t`)\n///\n/// ```text\n/// <text>\n///   a\n///   <tspan x=\"10 20 30\">\n///     bc\n///   </tspan>\n///   d\n/// </text>\n/// ```\n///\n/// In this example, the `d` position should not be set to `30`.\n/// And the result should be: `[None, 10, 20, None]`\n///\n/// Another example:\n///\n/// ```text\n/// <text>\n///   <tspan x=\"100 110 120 130\">\n///     a\n///     <tspan x=\"50\">\n///       bc\n///     </tspan>\n///   </tspan>\n///   d\n/// </text>\n/// ```\n///\n/// The result should be: `[100, 50, 120, None]`\nfn resolve_positions_list(text_node: SvgNode, state: &converter::State) -> Vec<CharacterPosition> {\n    // Allocate a list that has all characters positions set to `None`.\n    let total_chars = count_chars(text_node);\n    let mut list = vec![\n        CharacterPosition {\n            x: None,\n            y: None,\n            dx: None,\n            dy: None,\n        };\n        total_chars\n    ];\n\n    let mut offset = 0;\n    for child in text_node.descendants() {\n        if child.is_element() {\n            // We must ignore text positions on `textPath`.\n            if !matches!(child.tag_name(), Some(EId::Text) | Some(EId::Tspan)) {\n                continue;\n            }\n\n            let child_chars = count_chars(child);\n            macro_rules! push_list {\n                ($aid:expr, $field:ident) => {\n                    if let Some(num_list) = super::units::convert_list(child, $aid, state) {\n                        // Note that we are using not the total count,\n                        // but the amount of characters in the current `tspan` (with children).\n                        let len = std::cmp::min(num_list.len(), child_chars);\n                        for i in 0..len {\n                            list[offset + i].$field = Some(num_list[i]);\n                        }\n                    }\n                };\n            }\n\n            push_list!(AId::X, x);\n            push_list!(AId::Y, y);\n            push_list!(AId::Dx, dx);\n            push_list!(AId::Dy, dy);\n        } else if child.is_text() {\n            // Advance the offset.\n            offset += child.text().chars().count();\n        }\n    }\n\n    list\n}\n\n/// Resolves characters rotation.\n///\n/// The algorithm is well explained\n/// [in the SVG spec](https://www.w3.org/TR/SVG11/text.html#TSpanElement) (scroll down a bit).\n///\n/// ![](https://www.w3.org/TR/SVG11/images/text/tspan05-diagram.png)\n///\n/// Note: this algorithm differs from the position resolving one.\nfn resolve_rotate_list(text_node: SvgNode) -> Vec<f32> {\n    // Allocate a list that has all characters angles set to `0.0`.\n    let mut list = vec![0.0; count_chars(text_node)];\n    let mut last = 0.0;\n    let mut offset = 0;\n    for child in text_node.descendants() {\n        if child.is_element() {\n            if let Some(rotate) = child.attribute::<Vec<f32>>(AId::Rotate) {\n                for i in 0..count_chars(child) {\n                    if let Some(a) = rotate.get(i).cloned() {\n                        list[offset + i] = a;\n                        last = a;\n                    } else {\n                        // If the rotate list doesn't specify the rotation for\n                        // this character - use the last one.\n                        list[offset + i] = last;\n                    }\n                }\n            }\n        } else if child.is_text() {\n            // Advance the offset.\n            offset += child.text().chars().count();\n        }\n    }\n\n    list\n}\n\n/// Resolves node's `text-decoration` property.\nfn resolve_decoration(\n    tspan: SvgNode,\n    state: &converter::State,\n    cache: &mut converter::Cache,\n) -> TextDecoration {\n    // Checks if a decoration is present in a single node.\n    fn find_decoration(node: SvgNode, value: &str) -> bool {\n        if let Some(str_value) = node.attribute::<&str>(AId::TextDecoration) {\n            str_value.split(' ').any(|v| v == value)\n        } else {\n            false\n        }\n    }\n\n    // The algorithm is as follows: First, we check whether the given text decoration appears in ANY\n    // ancestor, i.e. it can also appear in ancestors outside of the <text> element. If the text\n    // decoration is declared somewhere, it means that this tspan will have it. However, we still\n    // need to find the corresponding fill/stroke for it. To do this, we iterate through all\n    // ancestors (i.e. tspans) until we find the text decoration declared. If not, we will\n    // stop at latest at the text node, and use its fill/stroke.\n    let mut gen_style = |text_decoration: &str| {\n        if !tspan\n            .ancestors()\n            .any(|n| find_decoration(n, text_decoration))\n        {\n            return None;\n        }\n\n        let mut fill_node = None;\n        let mut stroke_node = None;\n\n        for node in tspan.ancestors() {\n            if find_decoration(node, text_decoration) || node.tag_name() == Some(EId::Text) {\n                fill_node = fill_node.map_or(Some(node), Some);\n                stroke_node = stroke_node.map_or(Some(node), Some);\n                break;\n            }\n        }\n\n        Some(TextDecorationStyle {\n            fill: fill_node.and_then(|node| style::resolve_fill(node, true, state, cache)),\n            stroke: stroke_node.and_then(|node| style::resolve_stroke(node, true, state, cache)),\n        })\n    };\n\n    TextDecoration {\n        underline: gen_style(\"underline\"),\n        overline: gen_style(\"overline\"),\n        line_through: gen_style(\"line-through\"),\n    }\n}\n\nfn convert_baseline_shift(node: SvgNode, state: &converter::State) -> Vec<BaselineShift> {\n    let mut shift = Vec::new();\n    let nodes: Vec<_> = node\n        .ancestors()\n        .take_while(|n| n.tag_name() != Some(EId::Text))\n        .collect();\n    for n in nodes {\n        if let Some(len) = n.try_attribute::<Length>(AId::BaselineShift) {\n            if len.unit == LengthUnit::Percent {\n                let n = super::units::resolve_font_size(n, state) * (len.number as f32 / 100.0);\n                shift.push(BaselineShift::Number(n));\n            } else {\n                let n = super::units::convert_length(\n                    len,\n                    n,\n                    AId::BaselineShift,\n                    Units::ObjectBoundingBox,\n                    state,\n                );\n                shift.push(BaselineShift::Number(n));\n            }\n        } else if let Some(s) = n.attribute(AId::BaselineShift) {\n            match s {\n                \"sub\" => shift.push(BaselineShift::Subscript),\n                \"super\" => shift.push(BaselineShift::Superscript),\n                _ => shift.push(BaselineShift::Baseline),\n            }\n        }\n    }\n\n    if shift\n        .iter()\n        .all(|base| matches!(base, BaselineShift::Baseline))\n    {\n        shift.clear();\n    }\n\n    shift\n}\n\nfn count_chars(node: SvgNode) -> usize {\n    node.descendants()\n        .filter(|n| n.is_text())\n        .fold(0, |w, n| w + n.text().chars().count())\n}\n\n/// Converts the writing mode.\n///\n/// [SVG 2] references [CSS Writing Modes Level 3] for the definition of the\n/// 'writing-mode' property, there are only two writing modes:\n/// horizontal left-to-right and vertical right-to-left.\n///\n/// That specification introduces new values for the property. The SVG 1.1\n/// values are obsolete but must still be supported by converting the specified\n/// values to computed values as follows:\n///\n/// - `lr`, `lr-tb`, `rl`, `rl-tb` => `horizontal-tb`\n/// - `tb`, `tb-rl` => `vertical-rl`\n///\n/// The current `vertical-lr` behaves exactly the same as `vertical-rl`.\n///\n/// Also, looks like no one really supports the `rl` and `rl-tb`, except `Batik`.\n/// And I'm not sure if its behaviour is correct.\n///\n/// So we will ignore it as well, mainly because I have no idea how exactly\n/// it should affect the rendering.\n///\n/// [SVG 2]: https://www.w3.org/TR/SVG2/text.html#WritingModeProperty\n/// [CSS Writing Modes Level 3]: https://www.w3.org/TR/css-writing-modes-3/#svg-writing-mode-css\nfn convert_writing_mode(text_node: SvgNode) -> WritingMode {\n    if let Some(n) = text_node\n        .ancestors()\n        .find(|n| n.has_attribute(AId::WritingMode))\n    {\n        match n.attribute(AId::WritingMode).unwrap_or(\"lr-tb\") {\n            \"tb\" | \"tb-rl\" | \"vertical-rl\" | \"vertical-lr\" => WritingMode::TopToBottom,\n            _ => WritingMode::LeftToRight,\n        }\n    } else {\n        WritingMode::LeftToRight\n    }\n}\n\nfn path_length(path: &tiny_skia_path::Path) -> f64 {\n    let mut prev_mx = path.points()[0].x;\n    let mut prev_my = path.points()[0].y;\n    let mut prev_x = prev_mx;\n    let mut prev_y = prev_my;\n\n    fn create_curve_from_line(px: f32, py: f32, x: f32, y: f32) -> kurbo::CubicBez {\n        let line = kurbo::Line::new(\n            kurbo::Point::new(px as f64, py as f64),\n            kurbo::Point::new(x as f64, y as f64),\n        );\n        let p1 = line.eval(0.33);\n        let p2 = line.eval(0.66);\n        kurbo::CubicBez::new(line.p0, p1, p2, line.p1)\n    }\n\n    let mut length = 0.0;\n    for seg in path.segments() {\n        let curve = match seg {\n            tiny_skia_path::PathSegment::MoveTo(p) => {\n                prev_mx = p.x;\n                prev_my = p.y;\n                prev_x = p.x;\n                prev_y = p.y;\n                continue;\n            }\n            tiny_skia_path::PathSegment::LineTo(p) => {\n                create_curve_from_line(prev_x, prev_y, p.x, p.y)\n            }\n            tiny_skia_path::PathSegment::QuadTo(p1, p) => kurbo::QuadBez::new(\n                kurbo::Point::new(prev_x as f64, prev_y as f64),\n                kurbo::Point::new(p1.x as f64, p1.y as f64),\n                kurbo::Point::new(p.x as f64, p.y as f64),\n            )\n            .raise(),\n            tiny_skia_path::PathSegment::CubicTo(p1, p2, p) => kurbo::CubicBez::new(\n                kurbo::Point::new(prev_x as f64, prev_y as f64),\n                kurbo::Point::new(p1.x as f64, p1.y as f64),\n                kurbo::Point::new(p2.x as f64, p2.y as f64),\n                kurbo::Point::new(p.x as f64, p.y as f64),\n            ),\n            tiny_skia_path::PathSegment::Close => {\n                create_curve_from_line(prev_x, prev_y, prev_mx, prev_my)\n            }\n        };\n\n        length += curve.arclen(0.5);\n        prev_x = curve.p3.x as f32;\n        prev_y = curve.p3.y as f32;\n    }\n\n    length\n}\n"
  },
  {
    "path": "crates/usvg/src/parser/units.rs",
    "content": "// Copyright 2019 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse svgtypes::{Length, LengthUnit as Unit};\n\nuse super::converter;\nuse super::svgtree::{AId, SvgNode};\nuse crate::Units;\n\n#[inline(never)]\npub(crate) fn convert_length(\n    length: Length,\n    node: SvgNode,\n    aid: AId,\n    object_units: Units,\n    state: &converter::State,\n) -> f32 {\n    let dpi = state.opt.dpi;\n    let n = length.number as f32;\n    match length.unit {\n        Unit::None | Unit::Px => n,\n        Unit::Em => n * resolve_font_size(node, state),\n        Unit::Ex => n * resolve_font_size(node, state) / 2.0,\n        Unit::In => n * dpi,\n        Unit::Cm => n * dpi / 2.54,\n        Unit::Mm => n * dpi / 25.4,\n        Unit::Pt => n * dpi / 72.0,\n        Unit::Pc => n * dpi / 6.0,\n        Unit::Percent => {\n            if object_units == Units::ObjectBoundingBox {\n                n / 100.0\n            } else {\n                let view_box = state.view_box;\n\n                match aid {\n                    AId::Cx\n                    | AId::Dx\n                    | AId::Fx\n                    | AId::MarkerWidth\n                    | AId::RefX\n                    | AId::Rx\n                    | AId::Width\n                    | AId::X\n                    | AId::X1\n                    | AId::X2 => convert_percent(length, view_box.width()),\n                    AId::Cy\n                    | AId::Dy\n                    | AId::Fy\n                    | AId::Height\n                    | AId::MarkerHeight\n                    | AId::RefY\n                    | AId::Ry\n                    | AId::Y\n                    | AId::Y1\n                    | AId::Y2 => convert_percent(length, view_box.height()),\n                    _ => {\n                        let mut vb_len = view_box.width().powi(2) + view_box.height().powi(2);\n                        vb_len = (vb_len / 2.0).sqrt();\n                        convert_percent(length, vb_len)\n                    }\n                }\n            }\n        }\n    }\n}\n\npub(crate) fn convert_user_length(\n    length: Length,\n    node: SvgNode,\n    aid: AId,\n    state: &converter::State,\n) -> f32 {\n    convert_length(length, node, aid, Units::UserSpaceOnUse, state)\n}\n\n#[inline(never)]\npub(crate) fn convert_list(node: SvgNode, aid: AId, state: &converter::State) -> Option<Vec<f32>> {\n    if let Some(text) = node.attribute::<&str>(aid) {\n        let mut num_list = Vec::new();\n        for length in svgtypes::LengthListParser::from(text).flatten() {\n            num_list.push(convert_user_length(length, node, aid, state));\n        }\n\n        Some(num_list)\n    } else {\n        None\n    }\n}\n\nfn convert_percent(length: Length, base: f32) -> f32 {\n    base * (length.number as f32) / 100.0\n}\n\n#[inline(never)]\npub(crate) fn resolve_font_size(node: SvgNode, state: &converter::State) -> f32 {\n    let nodes: Vec<_> = node.ancestors().collect();\n    let mut font_size = state.opt.font_size;\n    for n in nodes.iter().rev().skip(1) {\n        // skip Root\n        if let Some(length) = n.try_attribute::<Length>(AId::FontSize) {\n            let dpi = state.opt.dpi;\n            let n = length.number as f32;\n            font_size = match length.unit {\n                Unit::None | Unit::Px => n,\n                Unit::Em => n * font_size,\n                Unit::Ex => n * font_size / 2.0,\n                Unit::In => n * dpi,\n                Unit::Cm => n * dpi / 2.54,\n                Unit::Mm => n * dpi / 25.4,\n                Unit::Pt => n * dpi / 72.0,\n                Unit::Pc => n * dpi / 6.0,\n                Unit::Percent => {\n                    // If `font-size` has percent units that it's value\n                    // is relative to the parent node `font-size`.\n                    length.number as f32 * font_size * 0.01\n                }\n            }\n        } else if let Some(name) = n.attribute(AId::FontSize) {\n            font_size = convert_named_font_size(name, font_size);\n        }\n    }\n\n    font_size\n}\n\nfn convert_named_font_size(name: &str, parent_font_size: f32) -> f32 {\n    let factor = match name {\n        \"xx-small\" => -3,\n        \"x-small\" => -2,\n        \"small\" => -1,\n        \"medium\" => 0,\n        \"large\" => 1,\n        \"x-large\" => 2,\n        \"xx-large\" => 3,\n        \"smaller\" => -1,\n        \"larger\" => 1,\n        _ => {\n            log::warn!(\"Invalid 'font-size' value: '{}'.\", name);\n            0\n        }\n    };\n\n    // 'On a computer screen a scaling factor of 1.2 is suggested between adjacent indexes.'\n    parent_font_size * 1.2f32.powi(factor)\n}\n"
  },
  {
    "path": "crates/usvg/src/parser/use_node.rs",
    "content": "// Copyright 2019 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse std::sync::Arc;\n\nuse svgtypes::{Length, LengthUnit};\n\nuse super::svgtree::{AId, EId, SvgNode};\nuse super::{converter, style};\nuse crate::tree::ContextElement;\nuse crate::{Group, IsValidLength, Node, NonZeroRect, Path, Size, Transform, ViewBox};\n\npub(crate) fn convert(\n    node: SvgNode,\n    state: &converter::State,\n    cache: &mut converter::Cache,\n    parent: &mut Group,\n) {\n    let child = match node.first_child() {\n        Some(v) => v,\n        None => return,\n    };\n\n    if state.parent_clip_path.is_some() && child.tag_name() == Some(EId::Symbol) {\n        // Ignore `symbol` referenced by `use` inside a `clipPath`.\n        // It will be ignored later anyway, but this will prevent\n        // a redundant `clipPath` creation (which is required for `symbol`).\n        return;\n    }\n\n    let mut use_state = state.clone();\n    use_state.context_element = Some((\n        style::resolve_fill(node, true, state, cache).map(|mut f| {\n            f.context_element = Some(ContextElement::UseNode);\n            f\n        }),\n        style::resolve_stroke(node, true, state, cache).map(|mut s| {\n            s.context_element = Some(ContextElement::UseNode);\n            s\n        }),\n    ));\n\n    // We require an original transformation to setup 'clipPath'.\n    let mut orig_ts = node.resolve_transform(AId::Transform, state);\n    let mut new_ts = Transform::default();\n\n    {\n        let x = node.convert_user_length(AId::X, &use_state, Length::zero());\n        let y = node.convert_user_length(AId::Y, &use_state, Length::zero());\n        new_ts = new_ts.pre_translate(x, y);\n    }\n\n    let linked_to_symbol = child.tag_name() == Some(EId::Symbol);\n\n    if linked_to_symbol {\n        // If a `use` element has a width/height attribute and references a symbol\n        // then relative units (like percentages) should be resolved relative\n        // to the width/height of the `use` element, and not the original SVG.\n        // This is why we need to (potentially) adapt the view box here.\n        use_state.view_box = {\n            let def = Length::new(100.0, LengthUnit::Percent);\n            let x = use_state.view_box.x();\n            let y = use_state.view_box.y();\n\n            let width = if node.has_attribute(AId::Width) {\n                node.convert_user_length(AId::Width, &use_state, def)\n            } else {\n                use_state.view_box.width()\n            };\n\n            let height = if node.has_attribute(AId::Height) {\n                node.convert_user_length(AId::Height, &use_state, def)\n            } else {\n                use_state.view_box.height()\n            };\n\n            NonZeroRect::from_xywh(x, y, width, height)\n                // Fail silently if the rect is not valid.\n                .unwrap_or(use_state.view_box)\n        };\n\n        if let Some(ts) = viewbox_transform(node, child, &use_state) {\n            new_ts = new_ts.pre_concat(ts);\n        }\n\n        if let Some(clip_rect) = get_clip_rect(node, child, &use_state) {\n            let mut g = clip_element(node, clip_rect, orig_ts, &use_state, cache);\n            g.abs_transform = parent.abs_transform;\n\n            // Make group for `use`.\n            if let Some(mut g2) =\n                converter::convert_group(node, &use_state, true, cache, &mut g, &|cache, g2| {\n                    convert_children(child, new_ts, &use_state, cache, false, g2);\n                })\n            {\n                // We must reset transform, because it was already set\n                // to the group with clip-path.\n                g.is_context_element = true;\n                g2.id = String::new(); // Prevent ID duplication.\n                g2.transform = Transform::default();\n                g.children.push(Node::Group(Box::new(g2)));\n            }\n\n            if g.children.is_empty() {\n                return;\n            }\n\n            g.calculate_bounding_boxes();\n            parent.children.push(Node::Group(Box::new(g)));\n            return;\n        }\n    }\n\n    orig_ts = orig_ts.pre_concat(new_ts);\n\n    if linked_to_symbol {\n        // Make group for `use`.\n        if let Some(mut g) =\n            converter::convert_group(node, &use_state, false, cache, parent, &|cache, g| {\n                convert_children(child, orig_ts, &use_state, cache, false, g);\n            })\n        {\n            g.is_context_element = true;\n            g.transform = Transform::default();\n            parent.children.push(Node::Group(Box::new(g)));\n        }\n    } else {\n        let linked_to_svg = child.tag_name() == Some(EId::Svg);\n        if linked_to_svg {\n            // When a `use` element references a `svg` element,\n            // we have to remember `use` element size and use it\n            // instead of `svg` element size.\n\n            let def = Length::new(100.0, LengthUnit::Percent);\n            // As per usual, the SVG spec doesn't clarify this edge case,\n            // but it seems like `use` size has to be reset by each `use`.\n            // Meaning if we have two nested `use` elements, where one had set `width` and\n            // other set `height`, we have to ignore the first `width`.\n            //\n            // Example:\n            // <use id=\"use1\" xlink:href=\"#use2\" width=\"100\"/>\n            // <use id=\"use2\" xlink:href=\"#svg2\" height=\"100\"/>\n            // <svg id=\"svg2\" x=\"40\" y=\"40\" width=\"80\" height=\"80\" xmlns=\"http://www.w3.org/2000/svg\"/>\n            //\n            // In this case `svg2` size is 80x100 and not 100x100.\n            use_state.use_size = (None, None);\n\n            // Width and height can be set independently.\n            if node.has_attribute(AId::Width) {\n                use_state.use_size.0 = Some(node.convert_user_length(AId::Width, &use_state, def));\n            }\n            if node.has_attribute(AId::Height) {\n                use_state.use_size.1 = Some(node.convert_user_length(AId::Height, &use_state, def));\n            }\n\n            convert_children(node, orig_ts, &use_state, cache, true, parent);\n        } else {\n            convert_children(node, orig_ts, &use_state, cache, true, parent);\n        }\n    }\n}\n\npub(crate) fn convert_svg(\n    node: SvgNode,\n    state: &converter::State,\n    cache: &mut converter::Cache,\n    parent: &mut Group,\n) {\n    // We require original transformation to setup 'clipPath'.\n    let mut orig_ts = node.resolve_transform(AId::Transform, state);\n    let mut new_ts = Transform::default();\n\n    let x = node.convert_user_length(AId::X, state, Length::zero());\n    let y = node.convert_user_length(AId::Y, state, Length::zero());\n    new_ts = new_ts.pre_translate(x, y);\n\n    if let Some(ts) = viewbox_transform(node, node, state) {\n        new_ts = new_ts.pre_concat(ts);\n    }\n\n    // We have to create a new state which would have its viewBox set to the current SVG element.\n    // Note that we're not updating State::size - it's a completely different property.\n    let mut new_state = state.clone();\n    new_state.view_box = {\n        if let Some(vb) = node.parse_viewbox() {\n            vb\n        } else {\n            // No `viewBox` attribute? Then use `x`, `y`, `width` and `height` instead.\n            let (mut w, mut h) = use_node_size(node, state);\n\n            // If attributes `width` and/or `height` are provided on the `use` element,\n            // then these values will override the corresponding attributes\n            // on the `svg` in the generated tree.\n            w = state.use_size.0.unwrap_or(w);\n            h = state.use_size.1.unwrap_or(h);\n\n            NonZeroRect::from_xywh(x, y, w, h).unwrap_or(state.view_box)\n        }\n    };\n\n    if let Some(clip_rect) = get_clip_rect(node, node, state) {\n        let mut g = clip_element(node, clip_rect, orig_ts, state, cache);\n        g.abs_transform = parent.abs_transform;\n        convert_children(node, new_ts, &new_state, cache, false, &mut g);\n        g.calculate_bounding_boxes();\n        parent.children.push(Node::Group(Box::new(g)));\n    } else {\n        orig_ts = orig_ts.pre_concat(new_ts);\n        convert_children(node, orig_ts, &new_state, cache, false, parent);\n    }\n}\n\nfn clip_element(\n    node: SvgNode,\n    clip_rect: NonZeroRect,\n    transform: Transform,\n    state: &converter::State,\n    cache: &mut converter::Cache,\n) -> Group {\n    // We can't set `clip-path` on the element itself,\n    // because it will be affected by a possible transform.\n    // So we have to create an additional group.\n\n    // Emulate a new viewport via clipPath.\n    //\n    // From:\n    // <defs/>\n    // <elem/>\n    //\n    // To:\n    // <defs>\n    //   <clipPath id=\"clipPath1\">\n    //     <rect/>\n    //   </clipPath>\n    // </defs>\n    // <g clip-path=\"ulr(#clipPath1)\">\n    //   <elem/>\n    // </g>\n\n    let mut clip_path = crate::ClipPath::empty(cache.gen_clip_path_id());\n\n    let mut path = Path::new_simple(Arc::new(tiny_skia_path::PathBuilder::from_rect(\n        clip_rect.to_rect(),\n    )))\n    .unwrap();\n    path.fill = Some(crate::Fill::default());\n    clip_path.root.children.push(Node::Path(Box::new(path)));\n\n    // Nodes generated by markers must not have an ID. Otherwise we would have duplicates.\n    let id = if state.parent_markers.is_empty() {\n        node.element_id().to_string()\n    } else {\n        String::new()\n    };\n\n    Group {\n        id,\n        transform,\n        clip_path: Some(Arc::new(clip_path)),\n        ..Group::empty()\n    }\n}\n\nfn convert_children(\n    node: SvgNode,\n    transform: Transform,\n    state: &converter::State,\n    cache: &mut converter::Cache,\n    is_context_element: bool,\n    parent: &mut Group,\n) {\n    // Temporarily adjust absolute transform so `convert_group` would account for `transform`.\n    let old_abs_transform = parent.abs_transform;\n    parent.abs_transform = parent.abs_transform.pre_concat(transform);\n\n    let required = !transform.is_identity();\n    if let Some(mut g) =\n        converter::convert_group(node, state, required, cache, parent, &|cache, g| {\n            if state.parent_clip_path.is_some() {\n                converter::convert_clip_path_elements(node, state, cache, g);\n            } else {\n                converter::convert_children(node, state, cache, g);\n            }\n        })\n    {\n        g.is_context_element = is_context_element;\n        g.transform = transform;\n        parent.children.push(Node::Group(Box::new(g)));\n    }\n\n    parent.abs_transform = old_abs_transform;\n}\n\nfn get_clip_rect(\n    use_node: SvgNode,\n    symbol_node: SvgNode,\n    state: &converter::State,\n) -> Option<NonZeroRect> {\n    // No need to clip elements with overflow:visible.\n    if matches!(\n        symbol_node.attribute(AId::Overflow),\n        Some(\"visible\") | Some(\"auto\")\n    ) {\n        return None;\n    }\n\n    // A nested `svg` with only the `viewBox` attribute and no \"rectangle\" (x, y, width, height)\n    // should not be clipped.\n    if use_node.tag_name() == Some(EId::Svg) {\n        // Nested `svg` referenced by `use` still should be clipped, but by `use` bounds.\n        if state.use_size.0.is_none() && state.use_size.1.is_none() {\n            if !(use_node.has_attribute(AId::Width) && use_node.has_attribute(AId::Height)) {\n                return None;\n            }\n        }\n    }\n\n    let (x, y, mut w, mut h) = {\n        let x = use_node.convert_user_length(AId::X, state, Length::zero());\n        let y = use_node.convert_user_length(AId::Y, state, Length::zero());\n        let (w, h) = use_node_size(use_node, state);\n        (x, y, w, h)\n    };\n\n    if use_node.tag_name() == Some(EId::Svg) {\n        // If attributes `width` and/or `height` are provided on the `use` element,\n        // then these values will override the corresponding attributes\n        // on the `svg` in the generated tree.\n        w = state.use_size.0.unwrap_or(w);\n        h = state.use_size.1.unwrap_or(h);\n    }\n\n    if !w.is_valid_length() || !h.is_valid_length() {\n        return None;\n    }\n\n    NonZeroRect::from_xywh(x, y, w, h)\n}\n\nfn use_node_size(node: SvgNode, state: &converter::State) -> (f32, f32) {\n    let def = Length::new(100.0, LengthUnit::Percent);\n    let w = node.convert_user_length(AId::Width, state, def);\n    let h = node.convert_user_length(AId::Height, state, def);\n    (w, h)\n}\n\nfn viewbox_transform(\n    node: SvgNode,\n    linked: SvgNode,\n    state: &converter::State,\n) -> Option<Transform> {\n    let (mut w, mut h) = use_node_size(node, state);\n\n    if node.tag_name() == Some(EId::Svg) {\n        // If attributes `width` and/or `height` are provided on the `use` element,\n        // then these values will override the corresponding attributes\n        // on the `svg` in the generated tree.\n        w = state.use_size.0.unwrap_or(w);\n        h = state.use_size.1.unwrap_or(h);\n    }\n\n    let size = Size::from_wh(w, h)?;\n    let rect = linked.parse_viewbox()?;\n    let aspect = linked\n        .attribute(AId::PreserveAspectRatio)\n        .unwrap_or_default();\n    let view_box = ViewBox { rect, aspect };\n\n    Some(view_box.to_transform(size))\n}\n"
  },
  {
    "path": "crates/usvg/src/text/colr.rs",
    "content": "// Copyright 2024 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse crate::parser::OptionLog;\nuse rustybuzz::ttf_parser;\n\nstruct Builder<'a>(&'a mut String);\n\nimpl Builder<'_> {\n    fn finish(&mut self) {\n        if !self.0.is_empty() {\n            self.0.pop(); // remove trailing space\n        }\n    }\n}\n\nimpl ttf_parser::OutlineBuilder for Builder<'_> {\n    fn move_to(&mut self, x: f32, y: f32) {\n        use std::fmt::Write;\n        write!(self.0, \"M {} {} \", x, y).unwrap();\n    }\n\n    fn line_to(&mut self, x: f32, y: f32) {\n        use std::fmt::Write;\n        write!(self.0, \"L {} {} \", x, y).unwrap();\n    }\n\n    fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {\n        use std::fmt::Write;\n        write!(self.0, \"Q {} {} {} {} \", x1, y1, x, y).unwrap();\n    }\n\n    fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {\n        use std::fmt::Write;\n        write!(self.0, \"C {} {} {} {} {} {} \", x1, y1, x2, y2, x, y).unwrap();\n    }\n\n    fn close(&mut self) {\n        self.0.push_str(\"Z \");\n    }\n}\n\ntrait XmlWriterExt {\n    fn write_color_attribute(&mut self, name: &str, ts: ttf_parser::RgbaColor);\n    fn write_transform_attribute(&mut self, name: &str, ts: ttf_parser::Transform);\n    fn write_spread_method_attribute(&mut self, method: ttf_parser::colr::GradientExtend);\n}\n\nimpl XmlWriterExt for xmlwriter::XmlWriter {\n    fn write_color_attribute(&mut self, name: &str, color: ttf_parser::RgbaColor) {\n        self.write_attribute_fmt(\n            name,\n            format_args!(\"rgb({}, {}, {})\", color.red, color.green, color.blue),\n        );\n    }\n\n    fn write_transform_attribute(&mut self, name: &str, ts: ttf_parser::Transform) {\n        if ts.is_default() {\n            return;\n        }\n\n        self.write_attribute_fmt(\n            name,\n            format_args!(\n                \"matrix({} {} {} {} {} {})\",\n                ts.a, ts.b, ts.c, ts.d, ts.e, ts.f\n            ),\n        );\n    }\n\n    fn write_spread_method_attribute(&mut self, extend: ttf_parser::colr::GradientExtend) {\n        self.write_attribute(\n            \"spreadMethod\",\n            match extend {\n                ttf_parser::colr::GradientExtend::Pad => &\"pad\",\n                ttf_parser::colr::GradientExtend::Repeat => &\"repeat\",\n                ttf_parser::colr::GradientExtend::Reflect => &\"reflect\",\n            },\n        );\n    }\n}\n\n// NOTE: This is only a best-effort translation of COLR into SVG.\npub(crate) struct GlyphPainter<'a> {\n    pub(crate) face: &'a ttf_parser::Face<'a>,\n    pub(crate) svg: &'a mut xmlwriter::XmlWriter,\n    pub(crate) path_buf: &'a mut String,\n    pub(crate) gradient_index: usize,\n    pub(crate) clip_path_index: usize,\n    pub(crate) palette_index: u16,\n    pub(crate) transform: ttf_parser::Transform,\n    pub(crate) outline_transform: ttf_parser::Transform,\n    pub(crate) transforms_stack: Vec<ttf_parser::Transform>,\n}\n\nimpl<'a> GlyphPainter<'a> {\n    fn write_gradient_stops(&mut self, stops: ttf_parser::colr::GradientStopsIter) {\n        for stop in stops {\n            self.svg.start_element(\"stop\");\n            self.svg.write_attribute(\"offset\", &stop.stop_offset);\n            self.svg.write_color_attribute(\"stop-color\", stop.color);\n            let opacity = f32::from(stop.color.alpha) / 255.0;\n            self.svg.write_attribute(\"stop-opacity\", &opacity);\n            self.svg.end_element();\n        }\n    }\n\n    fn paint_solid(&mut self, color: ttf_parser::RgbaColor) {\n        self.svg.start_element(\"path\");\n        self.svg.write_color_attribute(\"fill\", color);\n        let opacity = f32::from(color.alpha) / 255.0;\n        self.svg.write_attribute(\"fill-opacity\", &opacity);\n        self.svg\n            .write_transform_attribute(\"transform\", self.outline_transform);\n        self.svg.write_attribute(\"d\", self.path_buf);\n        self.svg.end_element();\n    }\n\n    fn paint_linear_gradient(&mut self, gradient: ttf_parser::colr::LinearGradient<'a>) {\n        let gradient_id = format!(\"lg{}\", self.gradient_index);\n        self.gradient_index += 1;\n\n        let gradient_transform = paint_transform(self.outline_transform, self.transform);\n\n        // TODO: We ignore x2, y2. Have to apply them somehow.\n        // TODO: The way spreadMode works in ttf and svg is a bit different. In SVG, the spreadMode\n        // will always be applied based on x1/y1 and x2/y2. However, in TTF the spreadMode will\n        // be applied from the first/last stop. So if we have a gradient with x1=0 x2=1, and\n        // a stop at x=0.4 and x=0.6, then in SVG we will always see a padding, while in ttf\n        // we will see the actual spreadMode. We need to account for that somehow.\n        self.svg.start_element(\"linearGradient\");\n        self.svg.write_attribute(\"id\", &gradient_id);\n        self.svg.write_attribute(\"x1\", &gradient.x0);\n        self.svg.write_attribute(\"y1\", &gradient.y0);\n        self.svg.write_attribute(\"x2\", &gradient.x1);\n        self.svg.write_attribute(\"y2\", &gradient.y1);\n        self.svg.write_attribute(\"gradientUnits\", &\"userSpaceOnUse\");\n        self.svg.write_spread_method_attribute(gradient.extend);\n        self.svg\n            .write_transform_attribute(\"gradientTransform\", gradient_transform);\n        self.write_gradient_stops(\n            gradient.stops(self.palette_index, self.face.variation_coordinates()),\n        );\n        self.svg.end_element();\n\n        self.svg.start_element(\"path\");\n        self.svg\n            .write_attribute_fmt(\"fill\", format_args!(\"url(#{})\", gradient_id));\n        self.svg\n            .write_transform_attribute(\"transform\", self.outline_transform);\n        self.svg.write_attribute(\"d\", self.path_buf);\n        self.svg.end_element();\n    }\n\n    fn paint_radial_gradient(&mut self, gradient: ttf_parser::colr::RadialGradient<'a>) {\n        let gradient_id = format!(\"rg{}\", self.gradient_index);\n        self.gradient_index += 1;\n\n        let gradient_transform = paint_transform(self.outline_transform, self.transform);\n\n        self.svg.start_element(\"radialGradient\");\n        self.svg.write_attribute(\"id\", &gradient_id);\n        self.svg.write_attribute(\"cx\", &gradient.x1);\n        self.svg.write_attribute(\"cy\", &gradient.y1);\n        self.svg.write_attribute(\"r\", &gradient.r1);\n        self.svg.write_attribute(\"fr\", &gradient.r0);\n        self.svg.write_attribute(\"fx\", &gradient.x0);\n        self.svg.write_attribute(\"fy\", &gradient.y0);\n        self.svg.write_attribute(\"gradientUnits\", &\"userSpaceOnUse\");\n        self.svg.write_spread_method_attribute(gradient.extend);\n        self.svg\n            .write_transform_attribute(\"gradientTransform\", gradient_transform);\n        self.write_gradient_stops(\n            gradient.stops(self.palette_index, self.face.variation_coordinates()),\n        );\n        self.svg.end_element();\n\n        self.svg.start_element(\"path\");\n        self.svg\n            .write_attribute_fmt(\"fill\", format_args!(\"url(#{})\", gradient_id));\n        self.svg\n            .write_transform_attribute(\"transform\", self.outline_transform);\n        self.svg.write_attribute(\"d\", self.path_buf);\n        self.svg.end_element();\n    }\n\n    fn paint_sweep_gradient(&mut self, _: ttf_parser::colr::SweepGradient<'a>) {\n        println!(\"Warning: sweep gradients are not supported.\");\n    }\n}\n\nfn paint_transform(\n    outline_transform: ttf_parser::Transform,\n    transform: ttf_parser::Transform,\n) -> ttf_parser::Transform {\n    let outline_transform = tiny_skia_path::Transform::from_row(\n        outline_transform.a,\n        outline_transform.b,\n        outline_transform.c,\n        outline_transform.d,\n        outline_transform.e,\n        outline_transform.f,\n    );\n\n    let gradient_transform = tiny_skia_path::Transform::from_row(\n        transform.a,\n        transform.b,\n        transform.c,\n        transform.d,\n        transform.e,\n        transform.f,\n    );\n\n    let gradient_transform = outline_transform\n        .invert()\n        .log_none(|| log::warn!(\"Failed to calculate transform for gradient in glyph.\"))\n        .unwrap_or_default()\n        .pre_concat(gradient_transform);\n\n    ttf_parser::Transform {\n        a: gradient_transform.sx,\n        b: gradient_transform.ky,\n        c: gradient_transform.kx,\n        d: gradient_transform.sy,\n        e: gradient_transform.tx,\n        f: gradient_transform.ty,\n    }\n}\n\nimpl GlyphPainter<'_> {\n    fn clip_with_path(&mut self, path: &str) {\n        let clip_id = format!(\"cp{}\", self.clip_path_index);\n        self.clip_path_index += 1;\n\n        self.svg.start_element(\"clipPath\");\n        self.svg.write_attribute(\"id\", &clip_id);\n        self.svg.start_element(\"path\");\n        self.svg\n            .write_transform_attribute(\"transform\", self.outline_transform);\n        self.svg.write_attribute(\"d\", &path);\n        self.svg.end_element();\n        self.svg.end_element();\n\n        self.svg.start_element(\"g\");\n        self.svg\n            .write_attribute_fmt(\"clip-path\", format_args!(\"url(#{})\", clip_id));\n    }\n}\n\nimpl<'a> ttf_parser::colr::Painter<'a> for GlyphPainter<'a> {\n    fn outline_glyph(&mut self, glyph_id: ttf_parser::GlyphId) {\n        self.path_buf.clear();\n        let mut builder = Builder(self.path_buf);\n        match self.face.outline_glyph(glyph_id, &mut builder) {\n            Some(v) => v,\n            None => return,\n        };\n        builder.finish();\n\n        // We have to write outline using the current transform.\n        self.outline_transform = self.transform;\n    }\n\n    fn push_layer(&mut self, mode: ttf_parser::colr::CompositeMode) {\n        self.svg.start_element(\"g\");\n\n        use ttf_parser::colr::CompositeMode;\n        // TODO: Need to figure out how to represent the other blend modes\n        // in SVG.\n        let mode = match mode {\n            CompositeMode::SourceOver => \"normal\",\n            CompositeMode::Screen => \"screen\",\n            CompositeMode::Overlay => \"overlay\",\n            CompositeMode::Darken => \"darken\",\n            CompositeMode::Lighten => \"lighten\",\n            CompositeMode::ColorDodge => \"color-dodge\",\n            CompositeMode::ColorBurn => \"color-burn\",\n            CompositeMode::HardLight => \"hard-light\",\n            CompositeMode::SoftLight => \"soft-light\",\n            CompositeMode::Difference => \"difference\",\n            CompositeMode::Exclusion => \"exclusion\",\n            CompositeMode::Multiply => \"multiply\",\n            CompositeMode::Hue => \"hue\",\n            CompositeMode::Saturation => \"saturation\",\n            CompositeMode::Color => \"color\",\n            CompositeMode::Luminosity => \"luminosity\",\n            _ => {\n                println!(\"Warning: unsupported blend mode: {:?}\", mode);\n                \"normal\"\n            }\n        };\n        self.svg.write_attribute_fmt(\n            \"style\",\n            format_args!(\"mix-blend-mode: {}; isolation: isolate\", mode),\n        );\n    }\n\n    fn pop_layer(&mut self) {\n        self.svg.end_element(); // g\n    }\n\n    fn push_transform(&mut self, transform: ttf_parser::Transform) {\n        self.transforms_stack.push(self.transform);\n        self.transform = ttf_parser::Transform::combine(self.transform, transform);\n    }\n\n    fn paint(&mut self, paint: ttf_parser::colr::Paint<'a>) {\n        match paint {\n            ttf_parser::colr::Paint::Solid(color) => self.paint_solid(color),\n            ttf_parser::colr::Paint::LinearGradient(lg) => self.paint_linear_gradient(lg),\n            ttf_parser::colr::Paint::RadialGradient(rg) => self.paint_radial_gradient(rg),\n            ttf_parser::colr::Paint::SweepGradient(sg) => self.paint_sweep_gradient(sg),\n        }\n    }\n\n    fn pop_transform(&mut self) {\n        if let Some(ts) = self.transforms_stack.pop() {\n            self.transform = ts;\n        }\n    }\n\n    fn push_clip(&mut self) {\n        self.clip_with_path(&self.path_buf.clone());\n    }\n\n    fn pop_clip(&mut self) {\n        self.svg.end_element();\n    }\n\n    fn push_clip_box(&mut self, clipbox: ttf_parser::colr::ClipBox) {\n        let x_min = clipbox.x_min;\n        let x_max = clipbox.x_max;\n        let y_min = clipbox.y_min;\n        let y_max = clipbox.y_max;\n\n        let clip_path = format!(\n            \"M {} {} L {} {} L {} {} L {} {} Z\",\n            x_min, y_min, x_max, y_min, x_max, y_max, x_min, y_max\n        );\n\n        self.clip_with_path(&clip_path);\n    }\n}\n"
  },
  {
    "path": "crates/usvg/src/text/flatten.rs",
    "content": "// Copyright 2022 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse std::mem;\nuse std::sync::Arc;\n\nuse fontdb::{Database, ID};\nuse rustybuzz::ttf_parser;\nuse rustybuzz::ttf_parser::{GlyphId, RasterImageFormat, RgbaColor};\nuse tiny_skia_path::{NonZeroRect, Size, Transform};\nuse xmlwriter::XmlWriter;\n\nuse crate::text::colr::GlyphPainter;\nuse crate::*;\n\nfn resolve_rendering_mode(text: &Text) -> ShapeRendering {\n    match text.rendering_mode {\n        TextRendering::OptimizeSpeed => ShapeRendering::CrispEdges,\n        TextRendering::OptimizeLegibility => ShapeRendering::GeometricPrecision,\n        TextRendering::GeometricPrecision => ShapeRendering::GeometricPrecision,\n    }\n}\n\nfn push_outline_paths(\n    span: &layout::Span,\n    builder: &mut tiny_skia_path::PathBuilder,\n    new_children: &mut Vec<Node>,\n    rendering_mode: ShapeRendering,\n) {\n    let builder = mem::replace(builder, tiny_skia_path::PathBuilder::new());\n\n    if let Some(path) = builder.finish().and_then(|p| {\n        Path::new(\n            String::new(),\n            span.visible,\n            span.fill.clone(),\n            span.stroke.clone(),\n            span.paint_order,\n            rendering_mode,\n            Arc::new(p),\n            Transform::default(),\n        )\n    }) {\n        new_children.push(Node::Path(Box::new(path)));\n    }\n}\n\npub(crate) fn flatten(text: &mut Text, cache: &mut Cache) -> Option<(Group, NonZeroRect)> {\n    let mut new_children = vec![];\n\n    let rendering_mode = resolve_rendering_mode(text);\n\n    for span in &text.layouted {\n        if let Some(path) = span.overline.as_ref() {\n            let mut path = path.clone();\n            path.rendering_mode = rendering_mode;\n            new_children.push(Node::Path(Box::new(path)));\n        }\n\n        if let Some(path) = span.underline.as_ref() {\n            let mut path = path.clone();\n            path.rendering_mode = rendering_mode;\n            new_children.push(Node::Path(Box::new(path)));\n        }\n\n        // Instead of always processing each glyph separately, we always collect\n        // as many outline glyphs as possible by pushing them into the span_builder\n        // and only if we encounter a different glyph, or we reach the very end of the\n        // span to we push the actual outline paths into new_children. This way, we don't need\n        // to create a new path for every glyph if we have many consecutive glyphs\n        // with just outlines (which is the most common case).\n        let mut span_builder = tiny_skia_path::PathBuilder::new();\n\n        // For variable fonts, we need to extract the outline with variations applied.\n        // We can't use the cache here since the outline depends on variation values.\n        let has_explicit_variations = !span.variations.is_empty();\n\n        for glyph in &span.positioned_glyphs {\n            // A (best-effort conversion of a) COLR glyph.\n            if let Some(tree) = cache.fontdb_colr(glyph.font, glyph.id) {\n                let mut group = Group {\n                    transform: glyph.colr_transform(),\n                    ..Group::empty()\n                };\n                // TODO: Probably need to update abs_transform of children?\n                group.children.push(Node::Group(Box::new(tree.root)));\n                group.calculate_bounding_boxes();\n\n                new_children.push(Node::Group(Box::new(group)));\n            }\n            // An SVG glyph. Will return the usvg node containing the glyph descriptions.\n            else if let Some(node) = cache.fontdb_svg(glyph.font, glyph.id) {\n                push_outline_paths(span, &mut span_builder, &mut new_children, rendering_mode);\n\n                let mut group = Group {\n                    transform: glyph.svg_transform(),\n                    ..Group::empty()\n                };\n                // TODO: Probably need to update abs_transform of children?\n                group.children.push(node);\n                group.calculate_bounding_boxes();\n\n                new_children.push(Node::Group(Box::new(group)));\n            }\n            // A bitmap glyph.\n            else if let Some(img) = cache.fontdb_raster(glyph.font, glyph.id) {\n                push_outline_paths(span, &mut span_builder, &mut new_children, rendering_mode);\n\n                let transform = if img.is_sbix {\n                    glyph.sbix_transform(\n                        img.x as f32,\n                        img.y as f32,\n                        img.glyph_bbox.map(|bbox| bbox.x_min).unwrap_or(0) as f32,\n                        img.glyph_bbox.map(|bbox| bbox.y_min).unwrap_or(0) as f32,\n                        img.pixels_per_em as f32,\n                        img.image.size.height(),\n                    )\n                } else {\n                    glyph.cbdt_transform(\n                        img.x as f32,\n                        img.y as f32,\n                        img.pixels_per_em as f32,\n                        img.image.size.height(),\n                    )\n                };\n\n                let mut group = Group {\n                    transform,\n                    ..Group::empty()\n                };\n                group.children.push(Node::Image(Box::new(img.image)));\n                group.calculate_bounding_boxes();\n\n                new_children.push(Node::Group(Box::new(group)));\n            } else {\n                // Only bypass cache if: explicit variations OR (auto opsz AND font has opsz axis)\n                let needs_variations = has_explicit_variations\n                    || (span.font_optical_sizing == crate::FontOpticalSizing::Auto\n                        && cache.has_opsz_axis(glyph.font));\n\n                let outline = if needs_variations {\n                    cache.fontdb.outline_with_variations(\n                        glyph.font,\n                        glyph.id,\n                        &span.variations,\n                        glyph.font_size(),\n                        span.font_optical_sizing,\n                    )\n                } else {\n                    cache.fontdb_outline(glyph.font, glyph.id)\n                };\n\n                if let Some(outline) = outline.and_then(|p| p.transform(glyph.outline_transform()))\n                {\n                    span_builder.push_path(&outline);\n                }\n            }\n        }\n\n        push_outline_paths(span, &mut span_builder, &mut new_children, rendering_mode);\n\n        if let Some(path) = span.line_through.as_ref() {\n            let mut path = path.clone();\n            path.rendering_mode = rendering_mode;\n            new_children.push(Node::Path(Box::new(path)));\n        }\n    }\n\n    let mut group = Group {\n        id: text.id.clone(),\n        ..Group::empty()\n    };\n\n    for child in new_children {\n        group.children.push(child);\n    }\n\n    group.calculate_bounding_boxes();\n    let stroke_bbox = group.stroke_bounding_box().to_non_zero_rect()?;\n    Some((group, stroke_bbox))\n}\n\nstruct PathBuilder {\n    builder: tiny_skia_path::PathBuilder,\n}\n\nimpl ttf_parser::OutlineBuilder for PathBuilder {\n    fn move_to(&mut self, x: f32, y: f32) {\n        self.builder.move_to(x, y);\n    }\n\n    fn line_to(&mut self, x: f32, y: f32) {\n        self.builder.line_to(x, y);\n    }\n\n    fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {\n        self.builder.quad_to(x1, y1, x, y);\n    }\n\n    fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {\n        self.builder.cubic_to(x1, y1, x2, y2, x, y);\n    }\n\n    fn close(&mut self) {\n        self.builder.close();\n    }\n}\n\npub(crate) trait DatabaseExt {\n    fn outline(&self, id: ID, glyph_id: GlyphId) -> Option<tiny_skia_path::Path>;\n    fn outline_with_variations(\n        &self,\n        id: ID,\n        glyph_id: GlyphId,\n        variations: &[crate::FontVariation],\n        font_size: f32,\n        font_optical_sizing: crate::FontOpticalSizing,\n    ) -> Option<tiny_skia_path::Path>;\n    fn has_opsz_axis(&self, id: ID) -> bool;\n    fn raster(&self, id: ID, glyph_id: GlyphId) -> Option<BitmapImage>;\n    fn svg(&self, id: ID, glyph_id: GlyphId) -> Option<Node>;\n    fn colr(&self, id: ID, glyph_id: GlyphId) -> Option<Tree>;\n}\n\n#[derive(Clone)]\npub(crate) struct BitmapImage {\n    image: Image,\n    x: i16,\n    y: i16,\n    pixels_per_em: u16,\n    glyph_bbox: Option<ttf_parser::Rect>,\n    is_sbix: bool,\n}\n\nimpl DatabaseExt for Database {\n    #[inline(never)]\n    fn outline(&self, id: ID, glyph_id: GlyphId) -> Option<tiny_skia_path::Path> {\n        self.with_face_data(id, |data, face_index| -> Option<tiny_skia_path::Path> {\n            let mut font = ttf_parser::Face::parse(data, face_index).ok()?;\n\n            // For variable fonts, we need to set default variation values to get proper outlines\n            if font.is_variable() {\n                for axis in font.variation_axes() {\n                    font.set_variation(axis.tag, axis.def_value);\n                }\n            }\n\n            let mut builder = PathBuilder {\n                builder: tiny_skia_path::PathBuilder::new(),\n            };\n\n            font.outline_glyph(glyph_id, &mut builder)?;\n            builder.builder.finish()\n        })?\n    }\n\n    #[inline(never)]\n    fn outline_with_variations(\n        &self,\n        id: ID,\n        glyph_id: GlyphId,\n        variations: &[crate::FontVariation],\n        font_size: f32,\n        font_optical_sizing: crate::FontOpticalSizing,\n    ) -> Option<tiny_skia_path::Path> {\n        self.with_face_data(id, |data, face_index| -> Option<tiny_skia_path::Path> {\n            let mut font = ttf_parser::Face::parse(data, face_index).ok()?;\n\n            for v in variations {\n                font.set_variation(ttf_parser::Tag::from_bytes(&v.tag), v.value);\n            }\n\n            // Auto-set opsz if font-optical-sizing is auto and not explicitly set\n            if font_optical_sizing == crate::FontOpticalSizing::Auto {\n                let has_explicit_opsz = variations.iter().any(|v| v.tag == *b\"opsz\");\n                if !has_explicit_opsz {\n                    // Check if font has opsz axis\n                    if let Some(axes) = font.tables().fvar {\n                        let has_opsz_axis = axes\n                            .axes\n                            .into_iter()\n                            .any(|axis| axis.tag == ttf_parser::Tag::from_bytes(b\"opsz\"));\n                        if has_opsz_axis {\n                            font.set_variation(ttf_parser::Tag::from_bytes(b\"opsz\"), font_size);\n                        }\n                    }\n                }\n            }\n\n            let mut builder = PathBuilder {\n                builder: tiny_skia_path::PathBuilder::new(),\n            };\n\n            font.outline_glyph(glyph_id, &mut builder)?;\n            builder.builder.finish()\n        })?\n    }\n\n    fn has_opsz_axis(&self, id: ID) -> bool {\n        self.with_face_data(id, |data, face_index| -> Option<bool> {\n            let font = ttf_parser::Face::parse(data, face_index).ok()?;\n            let has_opsz = font.tables().fvar.map_or(false, |axes| {\n                axes.axes\n                    .into_iter()\n                    .any(|axis| axis.tag == ttf_parser::Tag::from_bytes(b\"opsz\"))\n            });\n            Some(has_opsz)\n        })\n        .flatten()\n        .unwrap_or(false)\n    }\n\n    fn raster(&self, id: ID, glyph_id: GlyphId) -> Option<BitmapImage> {\n        self.with_face_data(id, |data, face_index| -> Option<BitmapImage> {\n            let font = ttf_parser::Face::parse(data, face_index).ok()?;\n            let image = font.glyph_raster_image(glyph_id, u16::MAX)?;\n\n            if image.format == RasterImageFormat::PNG {\n                let bitmap_image = BitmapImage {\n                    image: Image {\n                        id: String::new(),\n                        visible: true,\n                        size: Size::from_wh(image.width as f32, image.height as f32)?,\n                        rendering_mode: ImageRendering::OptimizeQuality,\n                        kind: ImageKind::PNG(Arc::new(image.data.into())),\n                        abs_transform: Transform::default(),\n                        abs_bounding_box: NonZeroRect::from_xywh(\n                            0.0,\n                            0.0,\n                            image.width as f32,\n                            image.height as f32,\n                        )?,\n                    },\n                    x: image.x,\n                    y: image.y,\n                    pixels_per_em: image.pixels_per_em,\n                    glyph_bbox: font.glyph_bounding_box(glyph_id),\n                    // ttf-parser always checks sbix first, so if this table exists, it was used.\n                    is_sbix: font.tables().sbix.is_some(),\n                };\n\n                return Some(bitmap_image);\n            }\n\n            None\n        })?\n    }\n\n    fn svg(&self, id: ID, glyph_id: GlyphId) -> Option<Node> {\n        // TODO: Technically not 100% accurate because the SVG format in a OTF font\n        // is actually a subset/superset of a normal SVG, but it seems to work fine\n        // for Twitter Color Emoji, so might as well use what we already have.\n\n        // TODO: Glyph records can contain the data for multiple glyphs. We should\n        // add a cache so we don't need to reparse the data every time.\n        self.with_face_data(id, |data, face_index| -> Option<Node> {\n            let font = ttf_parser::Face::parse(data, face_index).ok()?;\n            let image = font.glyph_svg_image(glyph_id)?;\n            let tree = Tree::from_data(image.data, &Options::default()).ok()?;\n\n            // Twitter Color Emoji seems to always have one SVG record per glyph,\n            // while Noto Color Emoji sometimes contains multiple ones. It's kind of hacky,\n            // but the best we have for now.\n            let node = if image.start_glyph_id == image.end_glyph_id {\n                Node::Group(Box::new(tree.root))\n            } else {\n                tree.node_by_id(&format!(\"glyph{}\", glyph_id.0))\n                    .log_none(|| {\n                        log::warn!(\"Failed to find SVG glyph node for glyph {}\", glyph_id.0);\n                    })\n                    .cloned()?\n            };\n\n            Some(node)\n        })?\n    }\n\n    fn colr(&self, id: ID, glyph_id: GlyphId) -> Option<Tree> {\n        self.with_face_data(id, |data, face_index| -> Option<Tree> {\n            let face = ttf_parser::Face::parse(data, face_index).ok()?;\n\n            let mut svg = XmlWriter::new(xmlwriter::Options::default());\n\n            svg.start_element(\"svg\");\n            svg.write_attribute(\"xmlns\", \"http://www.w3.org/2000/svg\");\n            svg.write_attribute(\"xmlns:xlink\", \"http://www.w3.org/1999/xlink\");\n\n            let mut path_buf = String::with_capacity(256);\n            let gradient_index = 1;\n            let clip_path_index = 1;\n\n            svg.start_element(\"g\");\n\n            let mut glyph_painter = GlyphPainter {\n                face: &face,\n                svg: &mut svg,\n                path_buf: &mut path_buf,\n                gradient_index,\n                clip_path_index,\n                palette_index: 0,\n                transform: ttf_parser::Transform::default(),\n                outline_transform: ttf_parser::Transform::default(),\n                transforms_stack: vec![ttf_parser::Transform::default()],\n            };\n\n            face.paint_color_glyph(\n                glyph_id,\n                0,\n                RgbaColor::new(0, 0, 0, 255),\n                &mut glyph_painter,\n            )?;\n            svg.end_element();\n\n            Tree::from_data(svg.end_document().as_bytes(), &Options::default()).ok()\n        })?\n    }\n}\n"
  },
  {
    "path": "crates/usvg/src/text/layout.rs",
    "content": "// Copyright 2022 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse std::collections::{HashMap, HashSet};\nuse std::num::NonZeroU16;\nuse std::sync::Arc;\n\nuse fontdb::{Database, ID};\nuse kurbo::{ParamCurve, ParamCurveArclen, ParamCurveDeriv};\nuse rustybuzz::ttf_parser;\nuse rustybuzz::ttf_parser::{GlyphId, Tag};\nuse strict_num::NonZeroPositiveF32;\nuse tiny_skia_path::{NonZeroRect, Transform};\nuse unicode_script::UnicodeScript;\n\nuse crate::tree::{BBox, IsValidLength};\nuse crate::{\n    AlignmentBaseline, ApproxZeroUlps, BaselineShift, DominantBaseline, Fill, FillRule, Font,\n    FontResolver, LengthAdjust, PaintOrder, Path, ShapeRendering, Stroke, Text, TextAnchor,\n    TextChunk, TextDecorationStyle, TextFlow, TextPath, TextSpan, WritingMode,\n};\n\n/// A glyph that has already been positioned correctly.\n///\n/// Note that the transform already takes the font size into consideration, so applying the\n/// transform to the outline of the glyphs is all that is necessary to display it correctly.\n#[derive(Clone, Debug)]\npub struct PositionedGlyph {\n    /// Returns the transform of the glyph itself within the cluster. For example,\n    /// for zalgo text, it contains the transform to position the glyphs above/below\n    /// the main glyph.\n    glyph_ts: Transform,\n    /// Returns the transform of the whole cluster that the glyph is part of.\n    cluster_ts: Transform,\n    /// Returns the transform of the span that the glyph is a part of.\n    span_ts: Transform,\n    /// The units per em of the font the glyph belongs to.\n    units_per_em: u16,\n    /// The font size the glyph should be scaled to.\n    font_size: f32,\n    /// The ID of the glyph.\n    pub id: GlyphId,\n    /// The text from the original string that corresponds to that glyph.\n    pub text: String,\n    /// The ID of the font the glyph should be taken from. Can be used with the\n    /// [font database of the tree](crate::Tree::fontdb) this glyph is part of.\n    pub font: ID,\n}\n\nimpl PositionedGlyph {\n    /// Returns the font size for this glyph.\n    pub fn font_size(&self) -> f32 {\n        self.font_size\n    }\n\n    /// Returns the transform of glyph.\n    pub fn transform(&self) -> Transform {\n        let sx = self.font_size / self.units_per_em as f32;\n\n        self.span_ts\n            .pre_concat(self.cluster_ts)\n            .pre_concat(Transform::from_scale(sx, sx))\n            .pre_concat(self.glyph_ts)\n    }\n\n    /// Returns the transform of glyph, assuming that an outline\n    /// glyph is being used (i.e. from the `glyf` or `CFF/CFF2` table).\n    pub fn outline_transform(&self) -> Transform {\n        // Outlines are mirrored by default.\n        self.transform()\n            .pre_concat(Transform::from_scale(1.0, -1.0))\n    }\n\n    /// Returns the transform for the glyph, assuming that a CBTD-based raster glyph\n    /// is being used.\n    pub fn cbdt_transform(&self, x: f32, y: f32, pixels_per_em: f32, height: f32) -> Transform {\n        self.transform()\n            .pre_concat(Transform::from_scale(\n                self.units_per_em as f32 / pixels_per_em,\n                self.units_per_em as f32 / pixels_per_em,\n            ))\n            // Right now, the top-left corner of the image would be placed in\n            // on the \"text cursor\", but we want the bottom-left corner to be there,\n            // so we need to shift it up and also apply the x/y offset.\n            .pre_translate(x, -height - y)\n    }\n\n    /// Returns the transform for the glyph, assuming that a sbix-based raster glyph\n    /// is being used.\n    pub fn sbix_transform(\n        &self,\n        x: f32,\n        y: f32,\n        x_min: f32,\n        y_min: f32,\n        pixels_per_em: f32,\n        height: f32,\n    ) -> Transform {\n        // In contrast to CBDT, we also need to look at the outline bbox of the glyph and add a shift if necessary.\n        let bbox_x_shift = -x_min;\n\n        let bbox_y_shift = if y_min.approx_zero_ulps(4) {\n            // For unknown reasons, using Apple Color Emoji will lead to a vertical shift on MacOS, but this shift\n            // doesn't seem to be coming from the font and most likely is somehow hardcoded. On Windows,\n            // this shift will not be applied. However, if this shift is not applied the emojis are a bit\n            // too high up when being together with other text, so we try to imitate this.\n            // See also https://github.com/harfbuzz/harfbuzz/issues/2679#issuecomment-1345595425\n            // So whenever the y-shift is 0, we approximate this vertical shift that seems to be produced by it.\n            // This value seems to be pretty close to what is happening on MacOS.\n            // We can still remove this if it turns out to be a problem, but Apple Color Emoji is pretty\n            // much the only `sbix` font out there and they all seem to have a y-shift of 0, so it\n            // makes sense to keep it.\n            0.128 * self.units_per_em as f32\n        } else {\n            -y_min\n        };\n\n        self.transform()\n            .pre_concat(Transform::from_translate(bbox_x_shift, bbox_y_shift))\n            .pre_concat(Transform::from_scale(\n                self.units_per_em as f32 / pixels_per_em,\n                self.units_per_em as f32 / pixels_per_em,\n            ))\n            // Right now, the top-left corner of the image would be placed in\n            // on the \"text cursor\", but we want the bottom-left corner to be there,\n            // so we need to shift it up and also apply the x/y offset.\n            .pre_translate(x, -height - y)\n    }\n\n    /// Returns the transform for the glyph, assuming that an SVG glyph is\n    /// being used.\n    pub fn svg_transform(&self) -> Transform {\n        self.transform()\n    }\n\n    /// Returns the transform for the glyph, assuming that a COLR glyph is\n    /// being used.\n    pub fn colr_transform(&self) -> Transform {\n        self.outline_transform()\n    }\n}\n\n/// A span contains a number of layouted glyphs that share the same fill, stroke, paint order and\n/// visibility.\n#[derive(Clone, Debug)]\npub struct Span {\n    /// The fill of the span.\n    pub fill: Option<Fill>,\n    /// The stroke of the span.\n    pub stroke: Option<Stroke>,\n    /// The paint order of the span.\n    pub paint_order: PaintOrder,\n    /// The font size of the span.\n    pub font_size: NonZeroPositiveF32,\n    /// Font variation settings for variable fonts.\n    pub variations: Vec<crate::FontVariation>,\n    /// Font optical sizing mode.\n    pub font_optical_sizing: crate::FontOpticalSizing,\n    /// The visibility of the span.\n    pub visible: bool,\n    /// The glyphs that make up the span.\n    pub positioned_glyphs: Vec<PositionedGlyph>,\n    /// An underline text decoration of the span.\n    /// Needs to be rendered before all glyphs.\n    pub underline: Option<Path>,\n    /// An overline text decoration of the span.\n    /// Needs to be rendered before all glyphs.\n    pub overline: Option<Path>,\n    /// A line-through text decoration of the span.\n    /// Needs to be rendered after all glyphs.\n    pub line_through: Option<Path>,\n}\n\n#[derive(Clone, Debug)]\nstruct GlyphCluster {\n    byte_idx: ByteIndex,\n    codepoint: char,\n    width: f32,\n    advance: f32,\n    ascent: f32,\n    descent: f32,\n    has_relative_shift: bool,\n    glyphs: Vec<PositionedGlyph>,\n    transform: Transform,\n    path_transform: Transform,\n    visible: bool,\n}\n\nimpl GlyphCluster {\n    pub(crate) fn height(&self) -> f32 {\n        self.ascent - self.descent\n    }\n\n    pub(crate) fn transform(&self) -> Transform {\n        self.path_transform.post_concat(self.transform)\n    }\n}\n\npub(crate) fn layout_text(\n    text_node: &Text,\n    resolver: &FontResolver,\n    fontdb: &mut Arc<fontdb::Database>,\n) -> Option<(Vec<Span>, NonZeroRect)> {\n    let mut fonts_cache: FontsCache = HashMap::new();\n\n    for chunk in &text_node.chunks {\n        for span in &chunk.spans {\n            if !fonts_cache.contains_key(&span.font) {\n                if let Some(font) =\n                    (resolver.select_font)(&span.font, fontdb).and_then(|id| fontdb.load_font(id))\n                {\n                    fonts_cache.insert(span.font.clone(), Arc::new(font));\n                }\n            }\n        }\n    }\n\n    let mut spans = vec![];\n    let mut char_offset = 0;\n    let mut last_x = 0.0;\n    let mut last_y = 0.0;\n    let mut bbox = BBox::default();\n    for chunk in &text_node.chunks {\n        let (x, y) = match chunk.text_flow {\n            TextFlow::Linear => (chunk.x.unwrap_or(last_x), chunk.y.unwrap_or(last_y)),\n            TextFlow::Path(_) => (0.0, 0.0),\n        };\n\n        let mut clusters = process_chunk(chunk, &fonts_cache, resolver, fontdb);\n        if clusters.is_empty() {\n            char_offset += chunk.text.chars().count();\n            continue;\n        }\n\n        apply_writing_mode(text_node.writing_mode, &mut clusters);\n        apply_letter_spacing(chunk, &mut clusters);\n        apply_word_spacing(chunk, &mut clusters);\n\n        apply_length_adjust(chunk, &mut clusters);\n        let mut curr_pos = resolve_clusters_positions(\n            text_node,\n            chunk,\n            char_offset,\n            text_node.writing_mode,\n            &fonts_cache,\n            &mut clusters,\n        );\n\n        let mut text_ts = Transform::default();\n        if text_node.writing_mode == WritingMode::TopToBottom {\n            if let TextFlow::Linear = chunk.text_flow {\n                text_ts = text_ts.pre_rotate_at(90.0, x, y);\n            }\n        }\n\n        for span in &chunk.spans {\n            let font = match fonts_cache.get(&span.font) {\n                Some(v) => v,\n                None => continue,\n            };\n\n            let decoration_spans = collect_decoration_spans(span, &clusters);\n\n            let mut span_ts = text_ts;\n            span_ts = span_ts.pre_translate(x, y);\n            if let TextFlow::Linear = chunk.text_flow {\n                let shift = resolve_baseline(span, font, text_node.writing_mode);\n\n                // In case of a horizontal flow, shift transform and not clusters,\n                // because clusters can be rotated and an additional shift will lead\n                // to invalid results.\n                span_ts = span_ts.pre_translate(0.0, shift);\n            }\n\n            let mut underline = None;\n            let mut overline = None;\n            let mut line_through = None;\n\n            if let Some(decoration) = span.decoration.underline.clone() {\n                // TODO: No idea what offset should be used for top-to-bottom layout.\n                // There is\n                // https://www.w3.org/TR/css-text-decor-3/#text-underline-position-property\n                // but it doesn't go into details.\n                let offset = match text_node.writing_mode {\n                    WritingMode::LeftToRight => -font.underline_position(span.font_size.get()),\n                    WritingMode::TopToBottom => font.height(span.font_size.get()) / 2.0,\n                };\n\n                if let Some(path) =\n                    convert_decoration(offset, span, font, decoration, &decoration_spans, span_ts)\n                {\n                    bbox = bbox.expand(path.data.bounds());\n                    underline = Some(path);\n                }\n            }\n\n            if let Some(decoration) = span.decoration.overline.clone() {\n                let offset = match text_node.writing_mode {\n                    WritingMode::LeftToRight => -font.ascent(span.font_size.get()),\n                    WritingMode::TopToBottom => -font.height(span.font_size.get()) / 2.0,\n                };\n\n                if let Some(path) =\n                    convert_decoration(offset, span, font, decoration, &decoration_spans, span_ts)\n                {\n                    bbox = bbox.expand(path.data.bounds());\n                    overline = Some(path);\n                }\n            }\n\n            if let Some(decoration) = span.decoration.line_through.clone() {\n                let offset = match text_node.writing_mode {\n                    WritingMode::LeftToRight => -font.line_through_position(span.font_size.get()),\n                    WritingMode::TopToBottom => 0.0,\n                };\n\n                if let Some(path) =\n                    convert_decoration(offset, span, font, decoration, &decoration_spans, span_ts)\n                {\n                    bbox = bbox.expand(path.data.bounds());\n                    line_through = Some(path);\n                }\n            }\n\n            let mut fill = span.fill.clone();\n            if let Some(ref mut fill) = fill {\n                // The `fill-rule` should be ignored.\n                // https://www.w3.org/TR/SVG2/text.html#TextRenderingOrder\n                //\n                // 'Since the fill-rule property does not apply to SVG text elements,\n                // the specific order of the subpaths within the equivalent path does not matter.'\n                fill.rule = FillRule::NonZero;\n            }\n\n            if let Some((span_fragments, span_bbox)) = convert_span(span, &clusters, span_ts) {\n                bbox = bbox.expand(span_bbox);\n\n                let positioned_glyphs = span_fragments\n                    .into_iter()\n                    .flat_map(|mut gc| {\n                        let cluster_ts = gc.transform();\n                        gc.glyphs.iter_mut().for_each(|pg| {\n                            pg.cluster_ts = cluster_ts;\n                            pg.span_ts = span_ts;\n                        });\n                        gc.glyphs\n                    })\n                    .collect();\n\n                spans.push(Span {\n                    fill,\n                    stroke: span.stroke.clone(),\n                    paint_order: span.paint_order,\n                    font_size: span.font_size,\n                    variations: span.font.variations.clone(),\n                    font_optical_sizing: span.font_optical_sizing,\n                    visible: span.visible,\n                    positioned_glyphs,\n                    underline,\n                    overline,\n                    line_through,\n                });\n            }\n        }\n\n        char_offset += chunk.text.chars().count();\n\n        if text_node.writing_mode == WritingMode::TopToBottom {\n            if let TextFlow::Linear = chunk.text_flow {\n                std::mem::swap(&mut curr_pos.0, &mut curr_pos.1);\n            }\n        }\n\n        last_x = x + curr_pos.0;\n        last_y = y + curr_pos.1;\n    }\n\n    let bbox = bbox.to_non_zero_rect()?;\n\n    Some((spans, bbox))\n}\n\nfn convert_span(\n    span: &TextSpan,\n    clusters: &[GlyphCluster],\n    text_ts: Transform,\n) -> Option<(Vec<GlyphCluster>, NonZeroRect)> {\n    let mut span_clusters = vec![];\n    let mut bboxes_builder = tiny_skia_path::PathBuilder::new();\n\n    for cluster in clusters {\n        if !cluster.visible {\n            continue;\n        }\n\n        if span_contains(span, cluster.byte_idx) {\n            span_clusters.push(cluster.clone());\n        }\n\n        let mut advance = cluster.advance;\n        if advance <= 0.0 {\n            advance = 1.0;\n        }\n\n        // We have to calculate text bbox using font metrics and not glyph shape.\n        if let Some(r) = NonZeroRect::from_xywh(0.0, -cluster.ascent, advance, cluster.height()) {\n            if let Some(r) = r.transform(cluster.transform()) {\n                bboxes_builder.push_rect(r.to_rect());\n            }\n        }\n    }\n\n    let mut bboxes = bboxes_builder.finish()?;\n    bboxes = bboxes.transform(text_ts)?;\n    let bbox = bboxes.compute_tight_bounds()?.to_non_zero_rect()?;\n\n    Some((span_clusters, bbox))\n}\n\nfn collect_decoration_spans(span: &TextSpan, clusters: &[GlyphCluster]) -> Vec<DecorationSpan> {\n    let mut spans = Vec::new();\n\n    let mut started = false;\n    let mut width = 0.0;\n    let mut transform = Transform::default();\n\n    for cluster in clusters {\n        if span_contains(span, cluster.byte_idx) {\n            if started && cluster.has_relative_shift {\n                started = false;\n                spans.push(DecorationSpan { width, transform });\n            }\n\n            if !started {\n                width = cluster.advance;\n                started = true;\n                transform = cluster.transform;\n            } else {\n                width += cluster.advance;\n            }\n        } else if started {\n            spans.push(DecorationSpan { width, transform });\n            started = false;\n        }\n    }\n\n    if started {\n        spans.push(DecorationSpan { width, transform });\n    }\n\n    spans\n}\n\npub(crate) fn convert_decoration(\n    dy: f32,\n    span: &TextSpan,\n    font: &ResolvedFont,\n    mut decoration: TextDecorationStyle,\n    decoration_spans: &[DecorationSpan],\n    transform: Transform,\n) -> Option<Path> {\n    debug_assert!(!decoration_spans.is_empty());\n\n    let thickness = font.underline_thickness(span.font_size.get());\n\n    let mut builder = tiny_skia_path::PathBuilder::new();\n    for dec_span in decoration_spans {\n        let rect = match NonZeroRect::from_xywh(0.0, -thickness / 2.0, dec_span.width, thickness) {\n            Some(v) => v,\n            None => {\n                log::warn!(\"a decoration span has a malformed bbox\");\n                continue;\n            }\n        };\n\n        let ts = dec_span.transform.pre_translate(0.0, dy);\n\n        let mut path = tiny_skia_path::PathBuilder::from_rect(rect.to_rect());\n        path = match path.transform(ts) {\n            Some(v) => v,\n            None => continue,\n        };\n\n        builder.push_path(&path);\n    }\n\n    let mut path_data = builder.finish()?;\n    path_data = path_data.transform(transform)?;\n\n    Path::new(\n        String::new(),\n        span.visible,\n        decoration.fill.take(),\n        decoration.stroke.take(),\n        PaintOrder::default(),\n        ShapeRendering::default(),\n        Arc::new(path_data),\n        Transform::default(),\n    )\n}\n\n/// A text decoration span.\n///\n/// Basically a horizontal line, that will be used for underline, overline and line-through.\n/// It doesn't have a height, since it depends on the Font metrics.\n#[derive(Clone, Copy)]\npub(crate) struct DecorationSpan {\n    pub(crate) width: f32,\n    pub(crate) transform: Transform,\n}\n\n/// Resolves clusters positions.\n///\n/// Mainly sets the `transform` property.\n///\n/// Returns the last text position. The next text chunk should start from that position.\nfn resolve_clusters_positions(\n    text: &Text,\n    chunk: &TextChunk,\n    char_offset: usize,\n    writing_mode: WritingMode,\n    fonts_cache: &FontsCache,\n    clusters: &mut [GlyphCluster],\n) -> (f32, f32) {\n    match chunk.text_flow {\n        TextFlow::Linear => {\n            resolve_clusters_positions_horizontal(text, chunk, char_offset, writing_mode, clusters)\n        }\n        TextFlow::Path(ref path) => resolve_clusters_positions_path(\n            text,\n            chunk,\n            char_offset,\n            path,\n            writing_mode,\n            fonts_cache,\n            clusters,\n        ),\n    }\n}\n\nfn clusters_length(clusters: &[GlyphCluster]) -> f32 {\n    clusters.iter().fold(0.0, |w, cluster| w + cluster.advance)\n}\n\nfn resolve_clusters_positions_horizontal(\n    text: &Text,\n    chunk: &TextChunk,\n    offset: usize,\n    writing_mode: WritingMode,\n    clusters: &mut [GlyphCluster],\n) -> (f32, f32) {\n    let mut x = process_anchor(chunk.anchor, clusters_length(clusters));\n    let mut y = 0.0;\n\n    for cluster in clusters {\n        let cp = offset + cluster.byte_idx.code_point_at(&chunk.text);\n        if let (Some(dx), Some(dy)) = (text.dx.get(cp), text.dy.get(cp)) {\n            if writing_mode == WritingMode::LeftToRight {\n                x += dx;\n                y += dy;\n            } else {\n                y -= dx;\n                x += dy;\n            }\n            cluster.has_relative_shift = !dx.approx_zero_ulps(4) || !dy.approx_zero_ulps(4);\n        }\n\n        cluster.transform = cluster.transform.pre_translate(x, y);\n\n        if let Some(angle) = text.rotate.get(cp).cloned() {\n            if !angle.approx_zero_ulps(4) {\n                cluster.transform = cluster.transform.pre_rotate(angle);\n                cluster.has_relative_shift = true;\n            }\n        }\n\n        x += cluster.advance;\n    }\n\n    (x, y)\n}\n\n// Baseline resolving in SVG is a mess.\n// Not only it's poorly documented, but as soon as you start mixing\n// `dominant-baseline` and `alignment-baseline` each application/browser will produce\n// different results.\n//\n// For now, resvg simply tries to match Chrome's output and not the mythical SVG spec output.\n//\n// See `alignment_baseline_shift` method comment for more details.\npub(crate) fn resolve_baseline(\n    span: &TextSpan,\n    font: &ResolvedFont,\n    writing_mode: WritingMode,\n) -> f32 {\n    let mut shift = -resolve_baseline_shift(&span.baseline_shift, font, span.font_size.get());\n\n    // TODO: support vertical layout as well\n    if writing_mode == WritingMode::LeftToRight {\n        if span.alignment_baseline == AlignmentBaseline::Auto\n            || span.alignment_baseline == AlignmentBaseline::Baseline\n        {\n            shift += font.dominant_baseline_shift(span.dominant_baseline, span.font_size.get());\n        } else {\n            shift += font.alignment_baseline_shift(span.alignment_baseline, span.font_size.get());\n        }\n    }\n\n    shift\n}\n\nfn resolve_baseline_shift(baselines: &[BaselineShift], font: &ResolvedFont, font_size: f32) -> f32 {\n    let mut shift = 0.0;\n    for baseline in baselines.iter().rev() {\n        match baseline {\n            BaselineShift::Baseline => {}\n            BaselineShift::Subscript => shift -= font.subscript_offset(font_size),\n            BaselineShift::Superscript => shift += font.superscript_offset(font_size),\n            BaselineShift::Number(n) => shift += n,\n        }\n    }\n\n    shift\n}\n\nfn resolve_clusters_positions_path(\n    text: &Text,\n    chunk: &TextChunk,\n    char_offset: usize,\n    path: &TextPath,\n    writing_mode: WritingMode,\n    fonts_cache: &FontsCache,\n    clusters: &mut [GlyphCluster],\n) -> (f32, f32) {\n    let mut last_x = 0.0;\n    let mut last_y = 0.0;\n\n    let mut dy = 0.0;\n\n    // In the text path mode, chunk's x/y coordinates provide an additional offset along the path.\n    // The X coordinate is used in a horizontal mode, and Y in vertical.\n    let chunk_offset = match writing_mode {\n        WritingMode::LeftToRight => chunk.x.unwrap_or(0.0),\n        WritingMode::TopToBottom => chunk.y.unwrap_or(0.0),\n    };\n\n    let start_offset =\n        chunk_offset + path.start_offset + process_anchor(chunk.anchor, clusters_length(clusters));\n\n    let normals = collect_normals(text, chunk, clusters, &path.path, char_offset, start_offset);\n    for (cluster, normal) in clusters.iter_mut().zip(normals) {\n        let (x, y, angle) = match normal {\n            Some(normal) => (normal.x, normal.y, normal.angle),\n            None => {\n                // Hide clusters that are outside the text path.\n                cluster.visible = false;\n                continue;\n            }\n        };\n\n        // We have to break a decoration line for each cluster during text-on-path.\n        cluster.has_relative_shift = true;\n\n        let orig_ts = cluster.transform;\n\n        // Clusters should be rotated by the x-midpoint x baseline position.\n        let half_width = cluster.width / 2.0;\n        cluster.transform = Transform::default();\n        cluster.transform = cluster.transform.pre_translate(x - half_width, y);\n        cluster.transform = cluster.transform.pre_rotate_at(angle, half_width, 0.0);\n\n        let cp = char_offset + cluster.byte_idx.code_point_at(&chunk.text);\n        dy += text.dy.get(cp).cloned().unwrap_or(0.0);\n\n        let baseline_shift = chunk_span_at(chunk, cluster.byte_idx)\n            .map(|span| {\n                let font = match fonts_cache.get(&span.font) {\n                    Some(v) => v,\n                    None => return 0.0,\n                };\n                -resolve_baseline(span, font, writing_mode)\n            })\n            .unwrap_or(0.0);\n\n        // Shift only by `dy` since we already applied `dx`\n        // during offset along the path calculation.\n        if !dy.approx_zero_ulps(4) || !baseline_shift.approx_zero_ulps(4) {\n            let shift = kurbo::Vec2::new(0.0, (dy - baseline_shift) as f64);\n            cluster.transform = cluster\n                .transform\n                .pre_translate(shift.x as f32, shift.y as f32);\n        }\n\n        if let Some(angle) = text.rotate.get(cp).cloned() {\n            if !angle.approx_zero_ulps(4) {\n                cluster.transform = cluster.transform.pre_rotate(angle);\n            }\n        }\n\n        // The possible `lengthAdjust` transform should be applied after text-on-path positioning.\n        cluster.transform = cluster.transform.pre_concat(orig_ts);\n\n        last_x = x + cluster.advance;\n        last_y = y;\n    }\n\n    (last_x, last_y)\n}\n\npub(crate) fn process_anchor(a: TextAnchor, text_width: f32) -> f32 {\n    match a {\n        TextAnchor::Start => 0.0, // Nothing.\n        TextAnchor::Middle => -text_width / 2.0,\n        TextAnchor::End => -text_width,\n    }\n}\n\npub(crate) struct PathNormal {\n    pub(crate) x: f32,\n    pub(crate) y: f32,\n    pub(crate) angle: f32,\n}\n\nfn collect_normals(\n    text: &Text,\n    chunk: &TextChunk,\n    clusters: &[GlyphCluster],\n    path: &tiny_skia_path::Path,\n    char_offset: usize,\n    offset: f32,\n) -> Vec<Option<PathNormal>> {\n    let mut offsets = Vec::with_capacity(clusters.len());\n    let mut normals = Vec::with_capacity(clusters.len());\n    {\n        let mut advance = offset;\n        for cluster in clusters {\n            // Clusters should be rotated by the x-midpoint x baseline position.\n            let half_width = cluster.width / 2.0;\n\n            // Include relative position.\n            let cp = char_offset + cluster.byte_idx.code_point_at(&chunk.text);\n            advance += text.dx.get(cp).cloned().unwrap_or(0.0);\n\n            let offset = advance + half_width;\n\n            // Clusters outside the path have no normals.\n            if offset < 0.0 {\n                normals.push(None);\n            }\n\n            offsets.push(offset as f64);\n            advance += cluster.advance;\n        }\n    }\n\n    let mut prev_mx = path.points()[0].x;\n    let mut prev_my = path.points()[0].y;\n    let mut prev_x = prev_mx;\n    let mut prev_y = prev_my;\n\n    fn create_curve_from_line(px: f32, py: f32, x: f32, y: f32) -> kurbo::CubicBez {\n        let line = kurbo::Line::new(\n            kurbo::Point::new(px as f64, py as f64),\n            kurbo::Point::new(x as f64, y as f64),\n        );\n        let p1 = line.eval(0.33);\n        let p2 = line.eval(0.66);\n        kurbo::CubicBez {\n            p0: line.p0,\n            p1,\n            p2,\n            p3: line.p1,\n        }\n    }\n\n    let mut length: f64 = 0.0;\n    for seg in path.segments() {\n        let curve = match seg {\n            tiny_skia_path::PathSegment::MoveTo(p) => {\n                prev_mx = p.x;\n                prev_my = p.y;\n                prev_x = p.x;\n                prev_y = p.y;\n                continue;\n            }\n            tiny_skia_path::PathSegment::LineTo(p) => {\n                create_curve_from_line(prev_x, prev_y, p.x, p.y)\n            }\n            tiny_skia_path::PathSegment::QuadTo(p1, p) => kurbo::QuadBez {\n                p0: kurbo::Point::new(prev_x as f64, prev_y as f64),\n                p1: kurbo::Point::new(p1.x as f64, p1.y as f64),\n                p2: kurbo::Point::new(p.x as f64, p.y as f64),\n            }\n            .raise(),\n            tiny_skia_path::PathSegment::CubicTo(p1, p2, p) => kurbo::CubicBez {\n                p0: kurbo::Point::new(prev_x as f64, prev_y as f64),\n                p1: kurbo::Point::new(p1.x as f64, p1.y as f64),\n                p2: kurbo::Point::new(p2.x as f64, p2.y as f64),\n                p3: kurbo::Point::new(p.x as f64, p.y as f64),\n            },\n            tiny_skia_path::PathSegment::Close => {\n                create_curve_from_line(prev_x, prev_y, prev_mx, prev_my)\n            }\n        };\n\n        let arclen_accuracy = {\n            let base_arclen_accuracy = 0.5;\n            // Accuracy depends on a current scale.\n            // When we have a tiny path scaled by a large value,\n            // we have to increase out accuracy accordingly.\n            let (sx, sy) = text.abs_transform.get_scale();\n            // 1.0 acts as a threshold to prevent division by 0 and/or low accuracy.\n            base_arclen_accuracy / (sx * sy).sqrt().max(1.0)\n        };\n\n        let curve_len = curve.arclen(arclen_accuracy as f64);\n\n        for offset in &offsets[normals.len()..] {\n            if *offset >= length && *offset <= length + curve_len {\n                let mut offset = curve.inv_arclen(offset - length, arclen_accuracy as f64);\n                // some rounding error may occur, so we give offset a little tolerance\n                debug_assert!((-1.0e-3..=1.0 + 1.0e-3).contains(&offset));\n                offset = offset.clamp(0.0, 1.0);\n\n                let pos = curve.eval(offset);\n                let d = curve.deriv().eval(offset);\n                let d = kurbo::Vec2::new(-d.y, d.x); // tangent\n                let angle = d.atan2().to_degrees() - 90.0;\n\n                normals.push(Some(PathNormal {\n                    x: pos.x as f32,\n                    y: pos.y as f32,\n                    angle: angle as f32,\n                }));\n\n                if normals.len() == offsets.len() {\n                    break;\n                }\n            }\n        }\n\n        length += curve_len;\n        prev_x = curve.p3.x as f32;\n        prev_y = curve.p3.y as f32;\n    }\n\n    // If path ended and we still have unresolved normals - set them to `None`.\n    for _ in 0..(offsets.len() - normals.len()) {\n        normals.push(None);\n    }\n\n    normals\n}\n\n/// Converts a text chunk into a list of outlined clusters.\n///\n/// This function will do the BIDI reordering, text shaping and glyphs outlining,\n/// but not the text layouting. So all clusters are in the 0x0 position.\nfn process_chunk(\n    chunk: &TextChunk,\n    fonts_cache: &FontsCache,\n    resolver: &FontResolver,\n    fontdb: &mut Arc<fontdb::Database>,\n) -> Vec<GlyphCluster> {\n    // The way this function works is a bit tricky.\n    //\n    // The first problem is BIDI reordering.\n    // We cannot shape text span-by-span, because glyph clusters are not guarantee to be continuous.\n    //\n    // For example:\n    // <text>Hel<tspan fill=\"url(#lg1)\">lo של</tspan>ום.</text>\n    //\n    // Would be shaped as:\n    // H e l l o   ש ל  ו  ם .   (characters)\n    // 0 1 2 3 4 5 12 10 8 6 14  (cluster indices in UTF-8)\n    //       ---         ---     (green span)\n    //\n    // As you can see, our continuous `lo של` span was split into two separated one.\n    // So our 3 spans: black - green - black, become 5 spans: black - green - black - green - black.\n    // If we shape `Hel`, then `lo של` an then `ום` separately - we would get an incorrect output.\n    // To properly handle this we simply shape the whole chunk.\n    //\n    // But this introduces another issue - what to do when we have multiple fonts?\n    // The easy solution would be to simply shape text with each font,\n    // where the first font output is used as a base one and all others overwrite it.\n    // This way in case of:\n    // <text font-family=\"Arial\">Hello <tspan font-family=\"Helvetica\">world</tspan></text>\n    // we would replace Arial glyphs for `world` with Helvetica one. Pretty simple.\n    //\n    // Well, it would work most of the time, but not always.\n    // This is because different fonts can produce different amount of glyphs for the same text.\n    // The most common example are ligatures. Some fonts can shape `fi` as two glyphs `f` and `i`,\n    // but some can use `ﬁ` (U+FB01) instead.\n    // Meaning that during merging we have to overwrite not individual glyphs, but clusters.\n\n    // Glyph splitting assigns distinct glyphs to the same index in the original text, we need to\n    // store previously used indices to make sure we do not re-use the same index while overwriting\n    // span glyphs.\n    let mut positions = HashSet::new();\n\n    let mut glyphs = Vec::new();\n    for span in &chunk.spans {\n        let font = match fonts_cache.get(&span.font) {\n            Some(v) => v.clone(),\n            None => continue,\n        };\n\n        let tmp_glyphs = shape_text(\n            &chunk.text,\n            font,\n            span.small_caps,\n            span.apply_kerning,\n            &span.font.variations,\n            span.font_size.get(),\n            span.font_optical_sizing,\n            resolver,\n            fontdb,\n        );\n\n        // Do nothing with the first run.\n        if glyphs.is_empty() {\n            glyphs = tmp_glyphs;\n            continue;\n        }\n\n        positions.clear();\n\n        // Overwrite span's glyphs.\n        let mut iter = tmp_glyphs.into_iter();\n        while let Some(new_glyph) = iter.next() {\n            if !span_contains(span, new_glyph.byte_idx) {\n                continue;\n            }\n\n            let Some(idx) = glyphs\n                .iter()\n                .position(|g| g.byte_idx == new_glyph.byte_idx)\n                .filter(|pos| !positions.contains(pos))\n            else {\n                continue;\n            };\n\n            positions.insert(idx);\n\n            let prev_cluster_len = glyphs[idx].cluster_len;\n            if prev_cluster_len < new_glyph.cluster_len {\n                // If the new font represents the same cluster with fewer glyphs\n                // then remove remaining glyphs.\n                for _ in 1..new_glyph.cluster_len {\n                    glyphs.remove(idx + 1);\n                }\n            } else if prev_cluster_len > new_glyph.cluster_len {\n                // If the new font represents the same cluster with more glyphs\n                // then insert them after the current one.\n                for j in 1..prev_cluster_len {\n                    if let Some(g) = iter.next() {\n                        glyphs.insert(idx + j, g);\n                    }\n                }\n            }\n\n            glyphs[idx] = new_glyph;\n        }\n    }\n\n    // Convert glyphs to clusters.\n    let mut clusters = Vec::new();\n    for (range, byte_idx) in GlyphClusters::new(&glyphs) {\n        if let Some(span) = chunk_span_at(chunk, byte_idx) {\n            clusters.push(form_glyph_clusters(\n                &glyphs[range],\n                &chunk.text,\n                span.font_size.get(),\n            ));\n        }\n    }\n\n    clusters\n}\n\nfn apply_length_adjust(chunk: &TextChunk, clusters: &mut [GlyphCluster]) {\n    let is_horizontal = matches!(chunk.text_flow, TextFlow::Linear);\n\n    for span in &chunk.spans {\n        let target_width = match span.text_length {\n            Some(v) => v,\n            None => continue,\n        };\n\n        let mut width = 0.0;\n        let mut cluster_indexes = Vec::new();\n        for i in span.start..span.end {\n            if let Some(index) = clusters.iter().position(|c| c.byte_idx.value() == i) {\n                cluster_indexes.push(index);\n            }\n        }\n        // Complex scripts can have multi-codepoint clusters therefore we have to remove duplicates.\n        cluster_indexes.sort();\n        cluster_indexes.dedup();\n\n        for i in &cluster_indexes {\n            // Use the original cluster `width` and not `advance`.\n            // This method essentially discards any `word-spacing` and `letter-spacing`.\n            width += clusters[*i].width;\n        }\n\n        if cluster_indexes.is_empty() {\n            continue;\n        }\n\n        if span.length_adjust == LengthAdjust::Spacing {\n            let factor = if cluster_indexes.len() > 1 {\n                (target_width - width) / (cluster_indexes.len() - 1) as f32\n            } else {\n                0.0\n            };\n\n            for i in cluster_indexes {\n                clusters[i].advance = clusters[i].width + factor;\n            }\n        } else {\n            let factor = target_width / width;\n            // Prevent multiplying by zero.\n            if factor < 0.001 {\n                continue;\n            }\n\n            for i in cluster_indexes {\n                clusters[i].transform = clusters[i].transform.pre_scale(factor, 1.0);\n\n                // Technically just a hack to support the current text-on-path algorithm.\n                if !is_horizontal {\n                    clusters[i].advance *= factor;\n                    clusters[i].width *= factor;\n                }\n            }\n        }\n    }\n}\n\n/// Rotates clusters according to\n/// [Unicode Vertical_Orientation Property](https://www.unicode.org/reports/tr50/tr50-19.html).\nfn apply_writing_mode(writing_mode: WritingMode, clusters: &mut [GlyphCluster]) {\n    if writing_mode != WritingMode::TopToBottom {\n        return;\n    }\n\n    for cluster in clusters {\n        let orientation = unicode_vo::char_orientation(cluster.codepoint);\n        if orientation == unicode_vo::Orientation::Upright {\n            let mut ts = Transform::default();\n            // Position glyph in the center of vertical axis.\n            ts = ts.pre_translate(0.0, (cluster.ascent + cluster.descent) / 2.0);\n            // Rotate by 90 degrees in the center.\n            ts = ts.pre_rotate_at(\n                -90.0,\n                cluster.width / 2.0,\n                -(cluster.ascent + cluster.descent) / 2.0,\n            );\n\n            cluster.path_transform = ts;\n\n            // Move \"baseline\" to the middle and make height equal to width.\n            cluster.ascent = cluster.width / 2.0;\n            cluster.descent = -cluster.width / 2.0;\n        } else {\n            // Could not find a spec that explains this,\n            // but this is how other applications are shifting the \"rotated\" characters\n            // in the top-to-bottom mode.\n            cluster.transform = cluster\n                .transform\n                .pre_translate(0.0, (cluster.ascent + cluster.descent) / 2.0);\n        }\n    }\n}\n\n/// Applies the `letter-spacing` property to a text chunk clusters.\n///\n/// [In the CSS spec](https://www.w3.org/TR/css-text-3/#letter-spacing-property).\nfn apply_letter_spacing(chunk: &TextChunk, clusters: &mut [GlyphCluster]) {\n    // At least one span should have a non-zero spacing.\n    if !chunk\n        .spans\n        .iter()\n        .any(|span| !span.letter_spacing.approx_zero_ulps(4))\n    {\n        return;\n    }\n\n    let num_clusters = clusters.len();\n    for (i, cluster) in clusters.iter_mut().enumerate() {\n        // Spacing must be applied only to characters that belongs to the script\n        // that supports spacing.\n        // We are checking only the first code point, since it should be enough.\n        // https://www.w3.org/TR/css-text-3/#cursive-tracking\n        let script = cluster.codepoint.script();\n        if script_supports_letter_spacing(script) {\n            if let Some(span) = chunk_span_at(chunk, cluster.byte_idx) {\n                // A space after the last cluster should be ignored,\n                // since it affects the bbox and text alignment.\n                if i != num_clusters - 1 {\n                    cluster.advance += span.letter_spacing;\n                }\n\n                // If the cluster advance became negative - clear it.\n                // This is an UB so we can do whatever we want, and we mimic Chrome's behavior.\n                if !cluster.advance.is_valid_length() {\n                    cluster.width = 0.0;\n                    cluster.advance = 0.0;\n                    cluster.glyphs = vec![];\n                }\n            }\n        }\n    }\n}\n\n/// Applies the `word-spacing` property to a text chunk clusters.\n///\n/// [In the CSS spec](https://www.w3.org/TR/css-text-3/#propdef-word-spacing).\nfn apply_word_spacing(chunk: &TextChunk, clusters: &mut [GlyphCluster]) {\n    // At least one span should have a non-zero spacing.\n    if !chunk\n        .spans\n        .iter()\n        .any(|span| !span.word_spacing.approx_zero_ulps(4))\n    {\n        return;\n    }\n\n    for cluster in clusters {\n        if is_word_separator_characters(cluster.codepoint) {\n            if let Some(span) = chunk_span_at(chunk, cluster.byte_idx) {\n                // Technically, word spacing 'should be applied half on each\n                // side of the character', but it doesn't affect us in any way,\n                // so we are ignoring this.\n                cluster.advance += span.word_spacing;\n\n                // After word spacing, `advance` can be negative.\n            }\n        }\n    }\n}\n\nfn form_glyph_clusters(glyphs: &[Glyph], text: &str, font_size: f32) -> GlyphCluster {\n    debug_assert!(!glyphs.is_empty());\n\n    let mut width = 0.0;\n    let mut x: f32 = 0.0;\n\n    let mut positioned_glyphs = vec![];\n\n    for glyph in glyphs {\n        let sx = glyph.font.scale(font_size);\n\n        // Apply offset.\n        //\n        // The first glyph in the cluster will have an offset from 0x0,\n        // but the later one will have an offset from the \"current position\".\n        // So we have to keep an advance.\n        // TODO: should be done only inside a single text span\n        let ts = Transform::from_translate(x + glyph.dx as f32, -glyph.dy as f32);\n\n        positioned_glyphs.push(PositionedGlyph {\n            glyph_ts: ts,\n            // Will be set later.\n            cluster_ts: Transform::default(),\n            // Will be set later.\n            span_ts: Transform::default(),\n            units_per_em: glyph.font.units_per_em.get(),\n            font_size,\n            font: glyph.font.id,\n            text: glyph.text.clone(),\n            id: glyph.id,\n        });\n\n        x += glyph.width as f32;\n\n        let glyph_width = glyph.width as f32 * sx;\n        if glyph_width > width {\n            width = glyph_width;\n        }\n    }\n\n    let byte_idx = glyphs[0].byte_idx;\n    let font = glyphs[0].font.clone();\n    GlyphCluster {\n        byte_idx,\n        codepoint: byte_idx.char_from(text),\n        width,\n        advance: width,\n        ascent: font.ascent(font_size),\n        descent: font.descent(font_size),\n        has_relative_shift: false,\n        transform: Transform::default(),\n        path_transform: Transform::default(),\n        glyphs: positioned_glyphs,\n        visible: true,\n    }\n}\n\npub(crate) trait DatabaseExt {\n    fn load_font(&self, id: ID) -> Option<ResolvedFont>;\n    fn has_char(&self, id: ID, c: char) -> bool;\n}\n\nimpl DatabaseExt for Database {\n    #[inline(never)]\n    fn load_font(&self, id: ID) -> Option<ResolvedFont> {\n        self.with_face_data(id, |data, face_index| -> Option<ResolvedFont> {\n            let font = ttf_parser::Face::parse(data, face_index).ok()?;\n\n            let units_per_em = NonZeroU16::new(font.units_per_em())?;\n\n            let ascent = font.ascender();\n            let descent = font.descender();\n\n            let x_height = font\n                .x_height()\n                .and_then(|x| u16::try_from(x).ok())\n                .and_then(NonZeroU16::new);\n            let x_height = match x_height {\n                Some(height) => height,\n                None => {\n                    // If not set - fallback to height * 45%.\n                    // 45% is what Firefox uses.\n                    u16::try_from((f32::from(ascent - descent) * 0.45) as i32)\n                        .ok()\n                        .and_then(NonZeroU16::new)?\n                }\n            };\n\n            let line_through = font.strikeout_metrics();\n            let line_through_position = match line_through {\n                Some(metrics) => metrics.position,\n                None => x_height.get() as i16 / 2,\n            };\n\n            let (underline_position, underline_thickness) = match font.underline_metrics() {\n                Some(metrics) => {\n                    let thickness = u16::try_from(metrics.thickness)\n                        .ok()\n                        .and_then(NonZeroU16::new)\n                        // `ttf_parser` guarantees that units_per_em is >= 16\n                        .unwrap_or_else(|| NonZeroU16::new(units_per_em.get() / 12).unwrap());\n\n                    (metrics.position, thickness)\n                }\n                None => (\n                    -(units_per_em.get() as i16) / 9,\n                    NonZeroU16::new(units_per_em.get() / 12).unwrap(),\n                ),\n            };\n\n            // 0.2 and 0.4 are generic offsets used by some applications (Inkscape/librsvg).\n            let mut subscript_offset = (units_per_em.get() as f32 / 0.2).round() as i16;\n            let mut superscript_offset = (units_per_em.get() as f32 / 0.4).round() as i16;\n            if let Some(metrics) = font.subscript_metrics() {\n                subscript_offset = metrics.y_offset;\n            }\n\n            if let Some(metrics) = font.superscript_metrics() {\n                superscript_offset = metrics.y_offset;\n            }\n\n            Some(ResolvedFont {\n                id,\n                units_per_em,\n                ascent,\n                descent,\n                x_height,\n                underline_position,\n                underline_thickness,\n                line_through_position,\n                subscript_offset,\n                superscript_offset,\n            })\n        })?\n    }\n\n    #[inline(never)]\n    fn has_char(&self, id: ID, c: char) -> bool {\n        let res = self.with_face_data(id, |font_data, face_index| -> Option<bool> {\n            let font = ttf_parser::Face::parse(font_data, face_index).ok()?;\n            font.glyph_index(c)?;\n            Some(true)\n        });\n\n        res == Some(Some(true))\n    }\n}\n\n/// Text shaping with font fallback.\npub(crate) fn shape_text(\n    text: &str,\n    font: Arc<ResolvedFont>,\n    small_caps: bool,\n    apply_kerning: bool,\n    variations: &[crate::FontVariation],\n    font_size: f32,\n    font_optical_sizing: crate::FontOpticalSizing,\n    resolver: &FontResolver,\n    fontdb: &mut Arc<fontdb::Database>,\n) -> Vec<Glyph> {\n    let mut glyphs = shape_text_with_font(\n        text,\n        font.clone(),\n        small_caps,\n        apply_kerning,\n        variations,\n        font_size,\n        font_optical_sizing,\n        fontdb,\n    )\n    .unwrap_or_default();\n\n    // Remember all fonts used for shaping.\n    let mut used_fonts = vec![font.id];\n\n    // Loop until all glyphs become resolved or until no more fonts are left.\n    'outer: loop {\n        let mut missing = None;\n        for glyph in &glyphs {\n            if glyph.is_missing() {\n                missing = Some(glyph.byte_idx.char_from(text));\n                break;\n            }\n        }\n\n        if let Some(c) = missing {\n            let fallback_font = match (resolver.select_fallback)(c, &used_fonts, fontdb)\n                .and_then(|id| fontdb.load_font(id))\n            {\n                Some(v) => Arc::new(v),\n                None => break 'outer,\n            };\n\n            // Shape again, using a new font.\n            let fallback_glyphs = shape_text_with_font(\n                text,\n                fallback_font.clone(),\n                small_caps,\n                apply_kerning,\n                variations,\n                font_size,\n                font_optical_sizing,\n                fontdb,\n            )\n            .unwrap_or_default();\n\n            let all_matched = fallback_glyphs.iter().all(|g| !g.is_missing());\n            if all_matched {\n                // Replace all glyphs when all of them were matched.\n                glyphs = fallback_glyphs;\n                break 'outer;\n            }\n\n            // We assume, that shaping with an any font will produce the same amount of glyphs.\n            // This is incorrect, but good enough for now.\n            if glyphs.len() != fallback_glyphs.len() {\n                break 'outer;\n            }\n\n            // TODO: Replace clusters and not glyphs. This should be more accurate.\n\n            // Copy new glyphs.\n            for i in 0..glyphs.len() {\n                if glyphs[i].is_missing() && !fallback_glyphs[i].is_missing() {\n                    glyphs[i] = fallback_glyphs[i].clone();\n                }\n            }\n\n            // Remember this font.\n            used_fonts.push(fallback_font.id);\n        } else {\n            break 'outer;\n        }\n    }\n\n    // Warn about missing glyphs.\n    for glyph in &glyphs {\n        if glyph.is_missing() {\n            let c = glyph.byte_idx.char_from(text);\n            // TODO: print a full grapheme\n            log::warn!(\n                \"No fonts with a {}/U+{:X} character were found.\",\n                c,\n                c as u32\n            );\n        }\n    }\n\n    glyphs\n}\n\n/// Converts a text into a list of glyph IDs.\n///\n/// This function will do the BIDI reordering and text shaping.\nfn shape_text_with_font(\n    text: &str,\n    font: Arc<ResolvedFont>,\n    small_caps: bool,\n    apply_kerning: bool,\n    variations: &[crate::FontVariation],\n    font_size: f32,\n    font_optical_sizing: crate::FontOpticalSizing,\n    fontdb: &fontdb::Database,\n) -> Option<Vec<Glyph>> {\n    fontdb.with_face_data(font.id, |font_data, face_index| -> Option<Vec<Glyph>> {\n        let mut rb_font = rustybuzz::Face::from_slice(font_data, face_index)?;\n\n        // Build the list of variations to apply\n        let mut final_variations: Vec<rustybuzz::Variation> = variations\n            .iter()\n            .map(|v| rustybuzz::Variation {\n                tag: Tag::from_bytes(&v.tag),\n                value: v.value,\n            })\n            .collect();\n\n        // Automatic optical sizing: if font-optical-sizing is auto and the font has\n        // an 'opsz' axis that isn't explicitly set, auto-set it to match font size.\n        // This matches browser behavior (CSS font-optical-sizing: auto).\n        if font_optical_sizing == crate::FontOpticalSizing::Auto {\n            let has_explicit_opsz = variations.iter().any(|v| v.tag == *b\"opsz\");\n            if !has_explicit_opsz {\n                // Check if font has opsz axis using the already parsed rb_font\n                if let Some(axes) = rb_font.tables().fvar {\n                    let has_opsz_axis = axes\n                        .axes\n                        .into_iter()\n                        .any(|axis| axis.tag == ttf_parser::Tag::from_bytes(b\"opsz\"));\n                    if has_opsz_axis {\n                        final_variations.push(rustybuzz::Variation {\n                            tag: Tag::from_bytes(b\"opsz\"),\n                            value: font_size,\n                        });\n                    }\n                }\n            }\n        }\n\n        // Apply font variations for variable fonts\n        if !final_variations.is_empty() {\n            rb_font.set_variations(&final_variations);\n        }\n\n        let bidi_info = unicode_bidi::BidiInfo::new(text, Some(unicode_bidi::Level::ltr()));\n        let paragraph = &bidi_info.paragraphs[0];\n        let line = paragraph.range.clone();\n\n        let mut glyphs = Vec::new();\n\n        let (levels, runs) = bidi_info.visual_runs(paragraph, line);\n        for run in runs.iter() {\n            let sub_text = &text[run.clone()];\n            if sub_text.is_empty() {\n                continue;\n            }\n\n            let ltr = levels[run.start].is_ltr();\n            let hb_direction = if ltr {\n                rustybuzz::Direction::LeftToRight\n            } else {\n                rustybuzz::Direction::RightToLeft\n            };\n\n            let mut buffer = rustybuzz::UnicodeBuffer::new();\n            buffer.push_str(sub_text);\n            buffer.set_direction(hb_direction);\n\n            let mut features = Vec::new();\n            if small_caps {\n                features.push(rustybuzz::Feature::new(Tag::from_bytes(b\"smcp\"), 1, ..));\n            }\n\n            if !apply_kerning {\n                features.push(rustybuzz::Feature::new(Tag::from_bytes(b\"kern\"), 0, ..));\n            }\n\n            let output = rustybuzz::shape(&rb_font, &features, buffer);\n\n            let positions = output.glyph_positions();\n            let infos = output.glyph_infos();\n\n            for i in 0..output.len() {\n                let pos = positions[i];\n                let info = infos[i];\n                let idx = run.start + info.cluster as usize;\n\n                let start = info.cluster as usize;\n\n                let end = if ltr {\n                    i.checked_add(1)\n                } else {\n                    i.checked_sub(1)\n                }\n                .and_then(|last| infos.get(last))\n                .map_or(sub_text.len(), |info| info.cluster as usize);\n\n                glyphs.push(Glyph {\n                    byte_idx: ByteIndex::new(idx),\n                    cluster_len: end.checked_sub(start).unwrap_or(0), // TODO: can fail?\n                    text: sub_text[start..end].to_string(),\n                    id: GlyphId(info.glyph_id as u16),\n                    dx: pos.x_offset,\n                    dy: pos.y_offset,\n                    width: pos.x_advance,\n                    font: font.clone(),\n                });\n            }\n        }\n\n        Some(glyphs)\n    })?\n}\n\n/// An iterator over glyph clusters.\n///\n/// Input:  0 2 2 2 3 4 4 5 5\n/// Result: 0 1     4 5   7\npub(crate) struct GlyphClusters<'a> {\n    data: &'a [Glyph],\n    idx: usize,\n}\n\nimpl<'a> GlyphClusters<'a> {\n    pub(crate) fn new(data: &'a [Glyph]) -> Self {\n        GlyphClusters { data, idx: 0 }\n    }\n}\n\nimpl Iterator for GlyphClusters<'_> {\n    type Item = (std::ops::Range<usize>, ByteIndex);\n\n    fn next(&mut self) -> Option<Self::Item> {\n        if self.idx == self.data.len() {\n            return None;\n        }\n\n        let start = self.idx;\n        let cluster = self.data[self.idx].byte_idx;\n        for g in &self.data[self.idx..] {\n            if g.byte_idx != cluster {\n                break;\n            }\n\n            self.idx += 1;\n        }\n\n        Some((start..self.idx, cluster))\n    }\n}\n\n/// Checks that selected script supports letter spacing.\n///\n/// [In the CSS spec](https://www.w3.org/TR/css-text-3/#cursive-tracking).\n///\n/// The list itself is from: https://github.com/harfbuzz/harfbuzz/issues/64\npub(crate) fn script_supports_letter_spacing(script: unicode_script::Script) -> bool {\n    use unicode_script::Script;\n\n    !matches!(\n        script,\n        Script::Arabic\n            | Script::Syriac\n            | Script::Nko\n            | Script::Manichaean\n            | Script::Psalter_Pahlavi\n            | Script::Mandaic\n            | Script::Mongolian\n            | Script::Phags_Pa\n            | Script::Devanagari\n            | Script::Bengali\n            | Script::Gurmukhi\n            | Script::Modi\n            | Script::Sharada\n            | Script::Syloti_Nagri\n            | Script::Tirhuta\n            | Script::Ogham\n    )\n}\n\n/// A glyph.\n///\n/// Basically, a glyph ID and it's metrics.\n#[derive(Clone)]\npub(crate) struct Glyph {\n    /// The glyph ID in the font.\n    pub(crate) id: GlyphId,\n\n    /// Position in bytes in the original string.\n    ///\n    /// We use it to match a glyph with a character in the text chunk and therefore with the style.\n    pub(crate) byte_idx: ByteIndex,\n\n    // The length of the cluster in bytes.\n    pub(crate) cluster_len: usize,\n\n    /// The text from the original string that corresponds to that glyph.\n    pub(crate) text: String,\n\n    /// The glyph offset in font units.\n    pub(crate) dx: i32,\n\n    /// The glyph offset in font units.\n    pub(crate) dy: i32,\n\n    /// The glyph width / X-advance in font units.\n    pub(crate) width: i32,\n\n    /// Reference to the source font.\n    ///\n    /// Each glyph can have it's own source font.\n    pub(crate) font: Arc<ResolvedFont>,\n}\n\nimpl Glyph {\n    fn is_missing(&self) -> bool {\n        self.id.0 == 0\n    }\n}\n\n#[derive(Clone, Copy, Debug)]\npub(crate) struct ResolvedFont {\n    pub(crate) id: ID,\n\n    units_per_em: NonZeroU16,\n\n    // All values below are in font units.\n    ascent: i16,\n    descent: i16,\n    x_height: NonZeroU16,\n\n    underline_position: i16,\n    underline_thickness: NonZeroU16,\n\n    // line-through thickness should be the the same as underline thickness\n    // according to the TrueType spec:\n    // https://docs.microsoft.com/en-us/typography/opentype/spec/os2#ystrikeoutsize\n    line_through_position: i16,\n\n    subscript_offset: i16,\n    superscript_offset: i16,\n}\n\npub(crate) fn chunk_span_at(chunk: &TextChunk, byte_offset: ByteIndex) -> Option<&TextSpan> {\n    chunk\n        .spans\n        .iter()\n        .find(|&span| span_contains(span, byte_offset))\n}\n\npub(crate) fn span_contains(span: &TextSpan, byte_offset: ByteIndex) -> bool {\n    byte_offset.value() >= span.start && byte_offset.value() < span.end\n}\n\n/// Checks that the selected character is a word separator.\n///\n/// According to: https://www.w3.org/TR/css-text-3/#word-separator\npub(crate) fn is_word_separator_characters(c: char) -> bool {\n    matches!(\n        c as u32,\n        0x0020 | 0x00A0 | 0x1361 | 0x010100 | 0x010101 | 0x01039F | 0x01091F\n    )\n}\n\nimpl ResolvedFont {\n    #[inline]\n    pub(crate) fn scale(&self, font_size: f32) -> f32 {\n        font_size / self.units_per_em.get() as f32\n    }\n\n    #[inline]\n    pub(crate) fn ascent(&self, font_size: f32) -> f32 {\n        self.ascent as f32 * self.scale(font_size)\n    }\n\n    #[inline]\n    pub(crate) fn descent(&self, font_size: f32) -> f32 {\n        self.descent as f32 * self.scale(font_size)\n    }\n\n    #[inline]\n    pub(crate) fn height(&self, font_size: f32) -> f32 {\n        self.ascent(font_size) - self.descent(font_size)\n    }\n\n    #[inline]\n    pub(crate) fn x_height(&self, font_size: f32) -> f32 {\n        self.x_height.get() as f32 * self.scale(font_size)\n    }\n\n    #[inline]\n    pub(crate) fn underline_position(&self, font_size: f32) -> f32 {\n        self.underline_position as f32 * self.scale(font_size)\n    }\n\n    #[inline]\n    fn underline_thickness(&self, font_size: f32) -> f32 {\n        self.underline_thickness.get() as f32 * self.scale(font_size)\n    }\n\n    #[inline]\n    pub(crate) fn line_through_position(&self, font_size: f32) -> f32 {\n        self.line_through_position as f32 * self.scale(font_size)\n    }\n\n    #[inline]\n    fn subscript_offset(&self, font_size: f32) -> f32 {\n        self.subscript_offset as f32 * self.scale(font_size)\n    }\n\n    #[inline]\n    fn superscript_offset(&self, font_size: f32) -> f32 {\n        self.superscript_offset as f32 * self.scale(font_size)\n    }\n\n    fn dominant_baseline_shift(&self, baseline: DominantBaseline, font_size: f32) -> f32 {\n        let alignment = match baseline {\n            DominantBaseline::Auto => AlignmentBaseline::Auto,\n            DominantBaseline::UseScript => AlignmentBaseline::Auto, // unsupported\n            DominantBaseline::NoChange => AlignmentBaseline::Auto,  // already resolved\n            DominantBaseline::ResetSize => AlignmentBaseline::Auto, // unsupported\n            DominantBaseline::Ideographic => AlignmentBaseline::Ideographic,\n            DominantBaseline::Alphabetic => AlignmentBaseline::Alphabetic,\n            DominantBaseline::Hanging => AlignmentBaseline::Hanging,\n            DominantBaseline::Mathematical => AlignmentBaseline::Mathematical,\n            DominantBaseline::Central => AlignmentBaseline::Central,\n            DominantBaseline::Middle => AlignmentBaseline::Middle,\n            DominantBaseline::TextAfterEdge => AlignmentBaseline::TextAfterEdge,\n            DominantBaseline::TextBeforeEdge => AlignmentBaseline::TextBeforeEdge,\n        };\n\n        self.alignment_baseline_shift(alignment, font_size)\n    }\n\n    // The `alignment-baseline` property is a mess.\n    //\n    // The SVG 1.1 spec (https://www.w3.org/TR/SVG11/text.html#BaselineAlignmentProperties)\n    // goes on and on about what this property suppose to do, but doesn't actually explain\n    // how it should be implemented. It's just a very verbose overview.\n    //\n    // As of Nov 2022, only Chrome and Safari support `alignment-baseline`. Firefox isn't.\n    // Same goes for basically every SVG library in existence.\n    // Meaning we have no idea how exactly it should be implemented.\n    //\n    // And even Chrome and Safari cannot agree on how to handle `baseline`, `after-edge`,\n    // `text-after-edge` and `ideographic` variants. Producing vastly different output.\n    //\n    // As per spec, a proper implementation should get baseline values from the font itself,\n    // using `BASE` and `bsln` TrueType tables. If those tables are not present,\n    // we have to synthesize them (https://drafts.csswg.org/css-inline/#baseline-synthesis-fonts).\n    // And in the worst case scenario simply fallback to hardcoded values.\n    //\n    // Also, most fonts do not provide `BASE` and `bsln` tables to begin with.\n    //\n    // Again, as of Nov 2022, Chrome does only the latter:\n    // https://github.com/chromium/chromium/blob/main/third_party/blink/renderer/platform/fonts/font_metrics.cc#L153\n    //\n    // Since baseline TrueType tables parsing and baseline synthesis are pretty hard,\n    // we do what Chrome does - use hardcoded values. And it seems like Safari does the same.\n    //\n    //\n    // But that's not all! SVG 2 and CSS Inline Layout 3 did a baseline handling overhaul,\n    // and it's far more complex now. Not sure if anyone actually supports it.\n    fn alignment_baseline_shift(&self, alignment: AlignmentBaseline, font_size: f32) -> f32 {\n        match alignment {\n            AlignmentBaseline::Auto => 0.0,\n            AlignmentBaseline::Baseline => 0.0,\n            AlignmentBaseline::BeforeEdge | AlignmentBaseline::TextBeforeEdge => {\n                self.ascent(font_size)\n            }\n            AlignmentBaseline::Middle => self.x_height(font_size) * 0.5,\n            AlignmentBaseline::Central => self.ascent(font_size) - self.height(font_size) * 0.5,\n            AlignmentBaseline::AfterEdge | AlignmentBaseline::TextAfterEdge => {\n                self.descent(font_size)\n            }\n            AlignmentBaseline::Ideographic => self.descent(font_size),\n            AlignmentBaseline::Alphabetic => 0.0,\n            AlignmentBaseline::Hanging => self.ascent(font_size) * 0.8,\n            AlignmentBaseline::Mathematical => self.ascent(font_size) * 0.5,\n        }\n    }\n}\n\npub(crate) type FontsCache = HashMap<Font, Arc<ResolvedFont>>;\n\n/// A read-only text index in bytes.\n///\n/// Guarantee to be on a char boundary and in text bounds.\n#[derive(Clone, Copy, PartialEq, Debug)]\npub(crate) struct ByteIndex(usize);\n\nimpl ByteIndex {\n    fn new(i: usize) -> Self {\n        ByteIndex(i)\n    }\n\n    pub(crate) fn value(&self) -> usize {\n        self.0\n    }\n\n    /// Converts byte position into a code point position.\n    pub(crate) fn code_point_at(&self, text: &str) -> usize {\n        text.char_indices()\n            .take_while(|(i, _)| *i != self.0)\n            .count()\n    }\n\n    /// Converts byte position into a character.\n    pub(crate) fn char_from(&self, text: &str) -> char {\n        text[self.0..].chars().next().unwrap()\n    }\n}\n"
  },
  {
    "path": "crates/usvg/src/text/mod.rs",
    "content": "// Copyright 2024 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse std::sync::Arc;\n\nuse fontdb::{Database, ID};\nuse svgtypes::FontFamily;\n\nuse self::layout::DatabaseExt;\nuse crate::{Cache, Font, FontStretch, FontStyle, Text};\n\npub(crate) mod flatten;\n\nmod colr;\n/// Provides access to the layout of a text node.\npub mod layout;\n\n/// A shorthand for [FontResolver]'s font selection function.\n///\n/// This function receives a font specification (families + a style, weight,\n/// stretch triple) and a font database and should return the ID of the font\n/// that shall be used (if any).\n///\n/// In the basic case, the function will search the existing fonts in the\n/// database to find a good match, e.g. via\n/// [`Database::query`](fontdb::Database::query). This is what the [default\n/// implementation](FontResolver::default_font_selector) does.\n///\n/// Users with more complex requirements can mutate the database to load\n/// additional fonts dynamically. To perform mutation, it is recommended to call\n/// `Arc::make_mut` on the provided database. (This call is not done outside of\n/// the callback to not needless clone an underlying shared database if no\n/// mutation will be performed.) It is important that the database is only\n/// mutated additively. Removing fonts or replacing the entire database will\n/// break things.\npub type FontSelectionFn<'a> =\n    Box<dyn Fn(&Font, &mut Arc<Database>) -> Option<ID> + Send + Sync + 'a>;\n\n/// A shorthand for [FontResolver]'s fallback selection function.\n///\n/// This function receives a specific character, a list of already used fonts,\n/// and a font database. It should return the ID of a font that\n/// - is not any of the already used fonts\n/// - is as close as possible to the first already used font (if any)\n/// - supports the given character\n///\n/// The function can search the existing database, but can also load additional\n/// fonts dynamically. See the documentation of [`FontSelectionFn`] for more\n/// details.\npub type FallbackSelectionFn<'a> =\n    Box<dyn Fn(char, &[ID], &mut Arc<Database>) -> Option<ID> + Send + Sync + 'a>;\n\n/// A font resolver for `<text>` elements.\n///\n/// This type can be useful if you want to have an alternative font handling to\n/// the default one. By default, only fonts specified upfront in\n/// [`Options::fontdb`](crate::Options::fontdb) will be used. This type allows\n/// you to load additional fonts on-demand and customize the font selection\n/// process.\npub struct FontResolver<'a> {\n    /// Resolver function that will be used when selecting a specific font\n    /// for a generic [`Font`] specification.\n    pub select_font: FontSelectionFn<'a>,\n\n    /// Resolver function that will be used when selecting a fallback font for a\n    /// character.\n    pub select_fallback: FallbackSelectionFn<'a>,\n}\n\nimpl Default for FontResolver<'_> {\n    fn default() -> Self {\n        FontResolver {\n            select_font: FontResolver::default_font_selector(),\n            select_fallback: FontResolver::default_fallback_selector(),\n        }\n    }\n}\n\nimpl FontResolver<'_> {\n    /// Creates a default font selection resolver.\n    ///\n    /// The default implementation forwards to\n    /// [`query`](fontdb::Database::query) on the font database specified in the\n    /// [`Options`](crate::Options).\n    pub fn default_font_selector() -> FontSelectionFn<'static> {\n        Box::new(move |font, fontdb| {\n            let mut name_list = Vec::new();\n            for family in &font.families {\n                name_list.push(match family {\n                    FontFamily::Serif => fontdb::Family::Serif,\n                    FontFamily::SansSerif => fontdb::Family::SansSerif,\n                    FontFamily::Cursive => fontdb::Family::Cursive,\n                    FontFamily::Fantasy => fontdb::Family::Fantasy,\n                    FontFamily::Monospace => fontdb::Family::Monospace,\n                    FontFamily::Named(s) => fontdb::Family::Name(s),\n                });\n            }\n\n            // Use the default font as fallback.\n            name_list.push(fontdb::Family::Serif);\n\n            let stretch = match font.stretch {\n                FontStretch::UltraCondensed => fontdb::Stretch::UltraCondensed,\n                FontStretch::ExtraCondensed => fontdb::Stretch::ExtraCondensed,\n                FontStretch::Condensed => fontdb::Stretch::Condensed,\n                FontStretch::SemiCondensed => fontdb::Stretch::SemiCondensed,\n                FontStretch::Normal => fontdb::Stretch::Normal,\n                FontStretch::SemiExpanded => fontdb::Stretch::SemiExpanded,\n                FontStretch::Expanded => fontdb::Stretch::Expanded,\n                FontStretch::ExtraExpanded => fontdb::Stretch::ExtraExpanded,\n                FontStretch::UltraExpanded => fontdb::Stretch::UltraExpanded,\n            };\n\n            let style = match font.style {\n                FontStyle::Normal => fontdb::Style::Normal,\n                FontStyle::Italic => fontdb::Style::Italic,\n                FontStyle::Oblique => fontdb::Style::Oblique,\n            };\n\n            let query = fontdb::Query {\n                families: &name_list,\n                weight: fontdb::Weight(font.weight),\n                stretch,\n                style,\n            };\n\n            let id = fontdb.query(&query);\n            if id.is_none() {\n                log::warn!(\n                    \"No match for '{}' font-family.\",\n                    font.families\n                        .iter()\n                        .map(|f| f.to_string())\n                        .collect::<Vec<_>>()\n                        .join(\", \")\n                );\n            }\n\n            id\n        })\n    }\n\n    /// Creates a default font fallback selection resolver.\n    ///\n    /// The default implementation searches through the entire `fontdb`\n    /// to find a font that has the correct style and supports the character.\n    pub fn default_fallback_selector() -> FallbackSelectionFn<'static> {\n        Box::new(|c, exclude_fonts, fontdb| {\n            let base_font_id = exclude_fonts[0];\n\n            // Iterate over fonts and check if any of them support the specified char.\n            for face in fontdb.faces() {\n                // Ignore fonts, that were used for shaping already.\n                if exclude_fonts.contains(&face.id) {\n                    continue;\n                }\n\n                // Check that the new face has the same style.\n                let base_face = fontdb.face(base_font_id)?;\n                if base_face.style != face.style\n                    && base_face.weight != face.weight\n                    && base_face.stretch != face.stretch\n                {\n                    continue;\n                }\n\n                if !fontdb.has_char(face.id, c) {\n                    continue;\n                }\n\n                let base_family = base_face\n                    .families\n                    .iter()\n                    .find(|f| f.1 == fontdb::Language::English_UnitedStates)\n                    .unwrap_or(&base_face.families[0]);\n\n                let new_family = face\n                    .families\n                    .iter()\n                    .find(|f| f.1 == fontdb::Language::English_UnitedStates)\n                    .unwrap_or(&base_face.families[0]);\n\n                log::warn!(\"Fallback from {} to {}.\", base_family.0, new_family.0);\n                return Some(face.id);\n            }\n\n            None\n        })\n    }\n}\n\nimpl std::fmt::Debug for FontResolver<'_> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.write_str(\"FontResolver { .. }\")\n    }\n}\n\n/// Convert a text into its paths. This is done in two steps:\n/// 1. We convert the text into glyphs and position them according to the rules specified\n///    in the SVG specification. While doing so, we also calculate the text bbox (which\n///    is not based on the outlines of a glyph, but instead the glyph metrics as well\n///    as decoration spans).\n/// 2. We convert all of the positioned glyphs into outlines.\npub(crate) fn convert(text: &mut Text, resolver: &FontResolver, cache: &mut Cache) -> Option<()> {\n    let (text_fragments, bbox) = layout::layout_text(text, resolver, &mut cache.fontdb)?;\n    text.layouted = text_fragments;\n    text.bounding_box = bbox.to_rect();\n    text.abs_bounding_box = bbox.transform(text.abs_transform)?.to_rect();\n\n    let (group, stroke_bbox) = flatten::flatten(text, cache)?;\n    text.flattened = Box::new(group);\n    text.stroke_bounding_box = stroke_bbox.to_rect();\n    text.abs_stroke_bounding_box = stroke_bbox.transform(text.abs_transform)?.to_rect();\n\n    Some(())\n}\n"
  },
  {
    "path": "crates/usvg/src/tree/filter.rs",
    "content": "// Copyright 2018 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\n//! SVG filter types.\n\nuse strict_num::PositiveF32;\n\nuse crate::{BlendMode, Color, Group, NonEmptyString, NonZeroF32, NonZeroRect, Opacity};\n\n/// A filter element.\n///\n/// `filter` element in the SVG.\n#[derive(Debug)]\npub struct Filter {\n    pub(crate) id: NonEmptyString,\n    pub(crate) rect: NonZeroRect,\n    pub(crate) primitives: Vec<Primitive>,\n}\n\nimpl Filter {\n    /// Element's ID.\n    ///\n    /// Taken from the SVG itself.\n    /// Used only during SVG writing. `resvg` doesn't rely on this property.\n    pub fn id(&self) -> &str {\n        self.id.get()\n    }\n\n    /// Filter region.\n    ///\n    /// `x`, `y`, `width` and `height` in the SVG.\n    pub fn rect(&self) -> NonZeroRect {\n        self.rect\n    }\n\n    /// A list of filter primitives.\n    pub fn primitives(&self) -> &[Primitive] {\n        &self.primitives\n    }\n}\n\n/// A filter primitive element.\n#[derive(Clone, Debug)]\npub struct Primitive {\n    pub(crate) rect: NonZeroRect,\n    pub(crate) color_interpolation: ColorInterpolation,\n    pub(crate) result: String,\n    pub(crate) kind: Kind,\n}\n\nimpl Primitive {\n    /// Filter subregion.\n    ///\n    /// `x`, `y`, `width` and `height` in the SVG.\n    pub fn rect(&self) -> NonZeroRect {\n        self.rect\n    }\n\n    /// Color interpolation mode.\n    ///\n    /// `color-interpolation-filters` in the SVG.\n    pub fn color_interpolation(&self) -> ColorInterpolation {\n        self.color_interpolation\n    }\n\n    /// Assigned name for this filter primitive.\n    ///\n    /// `result` in the SVG.\n    pub fn result(&self) -> &str {\n        &self.result\n    }\n\n    /// Filter primitive kind.\n    pub fn kind(&self) -> &Kind {\n        &self.kind\n    }\n}\n\n/// A filter kind.\n#[allow(missing_docs)]\n#[derive(Clone, Debug)]\npub enum Kind {\n    Blend(Blend),\n    ColorMatrix(ColorMatrix),\n    ComponentTransfer(ComponentTransfer),\n    Composite(Composite),\n    ConvolveMatrix(ConvolveMatrix),\n    DiffuseLighting(DiffuseLighting),\n    DisplacementMap(DisplacementMap),\n    DropShadow(DropShadow),\n    Flood(Flood),\n    GaussianBlur(GaussianBlur),\n    Image(Image),\n    Merge(Merge),\n    Morphology(Morphology),\n    Offset(Offset),\n    SpecularLighting(SpecularLighting),\n    Tile(Tile),\n    Turbulence(Turbulence),\n}\n\nimpl Kind {\n    /// Checks that `FilterKind` has a specific input.\n    pub fn has_input(&self, input: &Input) -> bool {\n        match self {\n            Kind::Blend(fe) => fe.input1 == *input || fe.input2 == *input,\n            Kind::ColorMatrix(fe) => fe.input == *input,\n            Kind::ComponentTransfer(fe) => fe.input == *input,\n            Kind::Composite(fe) => fe.input1 == *input || fe.input2 == *input,\n            Kind::ConvolveMatrix(fe) => fe.input == *input,\n            Kind::DiffuseLighting(fe) => fe.input == *input,\n            Kind::DisplacementMap(fe) => fe.input1 == *input || fe.input2 == *input,\n            Kind::DropShadow(fe) => fe.input == *input,\n            Kind::Flood(_) => false,\n            Kind::GaussianBlur(fe) => fe.input == *input,\n            Kind::Image(_) => false,\n            Kind::Merge(fe) => fe.inputs.iter().any(|i| i == input),\n            Kind::Morphology(fe) => fe.input == *input,\n            Kind::Offset(fe) => fe.input == *input,\n            Kind::SpecularLighting(fe) => fe.input == *input,\n            Kind::Tile(fe) => fe.input == *input,\n            Kind::Turbulence(_) => false,\n        }\n    }\n}\n\n/// Identifies input for a filter primitive.\n#[allow(missing_docs)]\n#[derive(Clone, PartialEq, Debug)]\npub enum Input {\n    SourceGraphic,\n    SourceAlpha,\n    Reference(String),\n}\n\n/// A color interpolation mode.\n///\n/// The default is `ColorInterpolation::LinearRGB`.\n#[allow(missing_docs)]\n#[derive(Clone, Copy, PartialEq, Debug, Default)]\npub enum ColorInterpolation {\n    SRGB,\n    #[default]\n    LinearRGB,\n}\n\n/// A blend filter primitive.\n///\n/// `feBlend` element in the SVG.\n#[derive(Clone, Debug)]\npub struct Blend {\n    pub(crate) input1: Input,\n    pub(crate) input2: Input,\n    pub(crate) mode: BlendMode,\n}\n\nimpl Blend {\n    /// Identifies input for the given filter primitive.\n    ///\n    /// `in` in the SVG.\n    pub fn input1(&self) -> &Input {\n        &self.input1\n    }\n\n    /// Identifies input for the given filter primitive.\n    ///\n    /// `in2` in the SVG.\n    pub fn input2(&self) -> &Input {\n        &self.input2\n    }\n\n    /// A blending mode.\n    ///\n    /// `mode` in the SVG.\n    pub fn mode(&self) -> BlendMode {\n        self.mode\n    }\n}\n\n/// A color matrix filter primitive.\n///\n/// `feColorMatrix` element in the SVG.\n#[derive(Clone, Debug)]\npub struct ColorMatrix {\n    pub(crate) input: Input,\n    pub(crate) kind: ColorMatrixKind,\n}\n\nimpl ColorMatrix {\n    /// Identifies input for the given filter primitive.\n    ///\n    /// `in` in the SVG.\n    pub fn input(&self) -> &Input {\n        &self.input\n    }\n\n    /// A matrix kind.\n    ///\n    /// `type` in the SVG.\n    pub fn kind(&self) -> &ColorMatrixKind {\n        &self.kind\n    }\n}\n\n/// A color matrix filter primitive kind.\n#[derive(Clone, Debug)]\n#[allow(missing_docs)]\npub enum ColorMatrixKind {\n    Matrix(Vec<f32>), // Guarantee to have 20 numbers.\n    Saturate(PositiveF32),\n    HueRotate(f32),\n    LuminanceToAlpha,\n}\n\nimpl Default for ColorMatrixKind {\n    fn default() -> Self {\n        ColorMatrixKind::Matrix(vec![\n            1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0,\n            0.0, 1.0, 0.0,\n        ])\n    }\n}\n\n/// A component-wise remapping filter primitive.\n///\n/// `feComponentTransfer` element in the SVG.\n#[derive(Clone, Debug)]\npub struct ComponentTransfer {\n    pub(crate) input: Input,\n    pub(crate) func_r: TransferFunction,\n    pub(crate) func_g: TransferFunction,\n    pub(crate) func_b: TransferFunction,\n    pub(crate) func_a: TransferFunction,\n}\n\nimpl ComponentTransfer {\n    /// Identifies input for the given filter primitive.\n    ///\n    /// `in` in the SVG.\n    pub fn input(&self) -> &Input {\n        &self.input\n    }\n\n    /// `feFuncR` in the SVG.\n    pub fn func_r(&self) -> &TransferFunction {\n        &self.func_r\n    }\n\n    /// `feFuncG` in the SVG.\n    pub fn func_g(&self) -> &TransferFunction {\n        &self.func_g\n    }\n\n    /// `feFuncB` in the SVG.\n    pub fn func_b(&self) -> &TransferFunction {\n        &self.func_b\n    }\n\n    /// `feFuncA` in the SVG.\n    pub fn func_a(&self) -> &TransferFunction {\n        &self.func_a\n    }\n}\n\n/// A transfer function used by `FeComponentTransfer`.\n///\n/// <https://www.w3.org/TR/SVG11/filters.html#transferFuncElements>\n#[derive(Clone, Debug)]\npub enum TransferFunction {\n    /// Keeps a component as is.\n    Identity,\n\n    /// Applies a linear interpolation to a component.\n    ///\n    /// The number list can be empty.\n    Table(Vec<f32>),\n\n    /// Applies a step function to a component.\n    ///\n    /// The number list can be empty.\n    Discrete(Vec<f32>),\n\n    /// Applies a linear shift to a component.\n    #[allow(missing_docs)]\n    Linear { slope: f32, intercept: f32 },\n\n    /// Applies an exponential shift to a component.\n    #[allow(missing_docs)]\n    Gamma {\n        amplitude: f32,\n        exponent: f32,\n        offset: f32,\n    },\n}\n\n/// A composite filter primitive.\n///\n/// `feComposite` element in the SVG.\n#[derive(Clone, Debug)]\npub struct Composite {\n    pub(crate) input1: Input,\n    pub(crate) input2: Input,\n    pub(crate) operator: CompositeOperator,\n}\n\nimpl Composite {\n    /// Identifies input for the given filter primitive.\n    ///\n    /// `in` in the SVG.\n    pub fn input1(&self) -> &Input {\n        &self.input1\n    }\n\n    /// Identifies input for the given filter primitive.\n    ///\n    /// `in2` in the SVG.\n    pub fn input2(&self) -> &Input {\n        &self.input2\n    }\n\n    /// A compositing operation.\n    ///\n    /// `operator` in the SVG.\n    pub fn operator(&self) -> CompositeOperator {\n        self.operator\n    }\n}\n\n/// An images compositing operation.\n#[allow(missing_docs)]\n#[derive(Clone, Copy, PartialEq, Debug)]\npub enum CompositeOperator {\n    Over,\n    In,\n    Out,\n    Atop,\n    Xor,\n    Arithmetic { k1: f32, k2: f32, k3: f32, k4: f32 },\n}\n\n/// A matrix convolution filter primitive.\n///\n/// `feConvolveMatrix` element in the SVG.\n#[derive(Clone, Debug)]\npub struct ConvolveMatrix {\n    pub(crate) input: Input,\n    pub(crate) matrix: ConvolveMatrixData,\n    pub(crate) divisor: NonZeroF32,\n    pub(crate) bias: f32,\n    pub(crate) edge_mode: EdgeMode,\n    pub(crate) preserve_alpha: bool,\n}\n\nimpl ConvolveMatrix {\n    /// Identifies input for the given filter primitive.\n    ///\n    /// `in` in the SVG.\n    pub fn input(&self) -> &Input {\n        &self.input\n    }\n\n    /// A convolve matrix.\n    pub fn matrix(&self) -> &ConvolveMatrixData {\n        &self.matrix\n    }\n\n    /// A matrix divisor.\n    ///\n    /// `divisor` in the SVG.\n    pub fn divisor(&self) -> NonZeroF32 {\n        self.divisor\n    }\n\n    /// A kernel matrix bias.\n    ///\n    /// `bias` in the SVG.\n    pub fn bias(&self) -> f32 {\n        self.bias\n    }\n\n    /// An edges processing mode.\n    ///\n    /// `edgeMode` in the SVG.\n    pub fn edge_mode(&self) -> EdgeMode {\n        self.edge_mode\n    }\n\n    /// An alpha preserving flag.\n    ///\n    /// `preserveAlpha` in the SVG.\n    pub fn preserve_alpha(&self) -> bool {\n        self.preserve_alpha\n    }\n}\n\n/// A convolve matrix representation.\n///\n/// Used primarily by [`ConvolveMatrix`].\n#[derive(Clone, Debug)]\npub struct ConvolveMatrixData {\n    pub(crate) target_x: u32,\n    pub(crate) target_y: u32,\n    pub(crate) columns: u32,\n    pub(crate) rows: u32,\n    pub(crate) data: Vec<f32>,\n}\n\nimpl ConvolveMatrixData {\n    /// Returns a matrix's X target.\n    ///\n    /// `targetX` in the SVG.\n    pub fn target_x(&self) -> u32 {\n        self.target_x\n    }\n\n    /// Returns a matrix's Y target.\n    ///\n    /// `targetY` in the SVG.\n    pub fn target_y(&self) -> u32 {\n        self.target_y\n    }\n\n    /// Returns a number of columns in the matrix.\n    ///\n    /// Part of the `order` attribute in the SVG.\n    pub fn columns(&self) -> u32 {\n        self.columns\n    }\n\n    /// Returns a number of rows in the matrix.\n    ///\n    /// Part of the `order` attribute in the SVG.\n    pub fn rows(&self) -> u32 {\n        self.rows\n    }\n\n    /// The actual matrix.\n    pub fn data(&self) -> &[f32] {\n        &self.data\n    }\n}\n\nimpl ConvolveMatrixData {\n    /// Creates a new `ConvolveMatrixData`.\n    ///\n    /// Returns `None` when:\n    ///\n    /// - `columns` * `rows` != `data.len()`\n    /// - `target_x` >= `columns`\n    /// - `target_y` >= `rows`\n    pub(crate) fn new(\n        target_x: u32,\n        target_y: u32,\n        columns: u32,\n        rows: u32,\n        data: Vec<f32>,\n    ) -> Option<Self> {\n        if (columns * rows) as usize != data.len() || target_x >= columns || target_y >= rows {\n            return None;\n        }\n\n        Some(ConvolveMatrixData {\n            target_x,\n            target_y,\n            columns,\n            rows,\n            data,\n        })\n    }\n\n    /// Returns a matrix value at the specified position.\n    ///\n    /// # Panics\n    ///\n    /// - When position is out of bounds.\n    pub fn get(&self, x: u32, y: u32) -> f32 {\n        self.data[(y * self.columns + x) as usize]\n    }\n}\n\n/// An edges processing mode.\n#[allow(missing_docs)]\n#[derive(Clone, Copy, PartialEq, Debug)]\npub enum EdgeMode {\n    None,\n    Duplicate,\n    Wrap,\n}\n\n/// A displacement map filter primitive.\n///\n/// `feDisplacementMap` element in the SVG.\n#[derive(Clone, Debug)]\npub struct DisplacementMap {\n    pub(crate) input1: Input,\n    pub(crate) input2: Input,\n    pub(crate) scale: f32,\n    pub(crate) x_channel_selector: ColorChannel,\n    pub(crate) y_channel_selector: ColorChannel,\n}\n\nimpl DisplacementMap {\n    /// Identifies input for the given filter primitive.\n    ///\n    /// `in` in the SVG.\n    pub fn input1(&self) -> &Input {\n        &self.input1\n    }\n\n    /// Identifies input for the given filter primitive.\n    ///\n    /// `in2` in the SVG.\n    pub fn input2(&self) -> &Input {\n        &self.input2\n    }\n\n    /// Scale factor.\n    ///\n    /// `scale` in the SVG.\n    pub fn scale(&self) -> f32 {\n        self.scale\n    }\n\n    /// Indicates a source color channel along the X-axis.\n    ///\n    /// `xChannelSelector` in the SVG.\n    pub fn x_channel_selector(&self) -> ColorChannel {\n        self.x_channel_selector\n    }\n\n    /// Indicates a source color channel along the Y-axis.\n    ///\n    /// `yChannelSelector` in the SVG.\n    pub fn y_channel_selector(&self) -> ColorChannel {\n        self.y_channel_selector\n    }\n}\n\n/// A color channel.\n#[allow(missing_docs)]\n#[derive(Clone, Copy, PartialEq, Debug)]\npub enum ColorChannel {\n    R,\n    G,\n    B,\n    A,\n}\n\n/// A drop shadow filter primitive.\n///\n/// This is essentially `feGaussianBlur`, `feOffset` and `feFlood` joined together.\n///\n/// `feDropShadow` element in the SVG.\n#[derive(Clone, Debug)]\npub struct DropShadow {\n    pub(crate) input: Input,\n    pub(crate) dx: f32,\n    pub(crate) dy: f32,\n    pub(crate) std_dev_x: PositiveF32,\n    pub(crate) std_dev_y: PositiveF32,\n    pub(crate) color: Color,\n    pub(crate) opacity: Opacity,\n}\n\nimpl DropShadow {\n    /// Identifies input for the given filter primitive.\n    ///\n    /// `in` in the SVG.\n    pub fn input(&self) -> &Input {\n        &self.input\n    }\n\n    /// The amount to offset the input graphic along the X-axis.\n    pub fn dx(&self) -> f32 {\n        self.dx\n    }\n\n    /// The amount to offset the input graphic along the Y-axis.\n    pub fn dy(&self) -> f32 {\n        self.dy\n    }\n\n    /// A standard deviation along the X-axis.\n    ///\n    /// `stdDeviation` in the SVG.\n    pub fn std_dev_x(&self) -> PositiveF32 {\n        self.std_dev_x\n    }\n\n    /// A standard deviation along the Y-axis.\n    ///\n    /// `stdDeviation` in the SVG.\n    pub fn std_dev_y(&self) -> PositiveF32 {\n        self.std_dev_y\n    }\n\n    /// A flood color.\n    ///\n    /// `flood-color` in the SVG.\n    pub fn color(&self) -> Color {\n        self.color\n    }\n\n    /// A flood opacity.\n    ///\n    /// `flood-opacity` in the SVG.\n    pub fn opacity(&self) -> Opacity {\n        self.opacity\n    }\n}\n\n/// A flood filter primitive.\n///\n/// `feFlood` element in the SVG.\n#[derive(Clone, Copy, Debug)]\npub struct Flood {\n    pub(crate) color: Color,\n    pub(crate) opacity: Opacity,\n}\n\nimpl Flood {\n    /// A flood color.\n    ///\n    /// `flood-color` in the SVG.\n    pub fn color(&self) -> Color {\n        self.color\n    }\n\n    /// A flood opacity.\n    ///\n    /// `flood-opacity` in the SVG.\n    pub fn opacity(&self) -> Opacity {\n        self.opacity\n    }\n}\n\n/// A Gaussian blur filter primitive.\n///\n/// `feGaussianBlur` element in the SVG.\n#[derive(Clone, Debug)]\npub struct GaussianBlur {\n    pub(crate) input: Input,\n    pub(crate) std_dev_x: PositiveF32,\n    pub(crate) std_dev_y: PositiveF32,\n}\n\nimpl GaussianBlur {\n    /// Identifies input for the given filter primitive.\n    ///\n    /// `in` in the SVG.\n    pub fn input(&self) -> &Input {\n        &self.input\n    }\n\n    /// A standard deviation along the X-axis.\n    ///\n    /// `stdDeviation` in the SVG.\n    pub fn std_dev_x(&self) -> PositiveF32 {\n        self.std_dev_x\n    }\n\n    /// A standard deviation along the Y-axis.\n    ///\n    /// `stdDeviation` in the SVG.\n    pub fn std_dev_y(&self) -> PositiveF32 {\n        self.std_dev_y\n    }\n}\n\n/// An image filter primitive.\n///\n/// `feImage` element in the SVG.\n#[derive(Clone, Debug)]\npub struct Image {\n    pub(crate) root: Group,\n}\n\nimpl Image {\n    /// `feImage` children.\n    pub fn root(&self) -> &Group {\n        &self.root\n    }\n}\n\n/// A diffuse lighting filter primitive.\n///\n/// `feDiffuseLighting` element in the SVG.\n#[derive(Clone, Debug)]\npub struct DiffuseLighting {\n    pub(crate) input: Input,\n    pub(crate) surface_scale: f32,\n    pub(crate) diffuse_constant: f32,\n    pub(crate) lighting_color: Color,\n    pub(crate) light_source: LightSource,\n}\n\nimpl DiffuseLighting {\n    /// Identifies input for the given filter primitive.\n    ///\n    /// `in` in the SVG.\n    pub fn input(&self) -> &Input {\n        &self.input\n    }\n\n    /// A surface scale.\n    ///\n    /// `surfaceScale` in the SVG.\n    pub fn surface_scale(&self) -> f32 {\n        self.surface_scale\n    }\n\n    /// A diffuse constant.\n    ///\n    /// `diffuseConstant` in the SVG.\n    pub fn diffuse_constant(&self) -> f32 {\n        self.diffuse_constant\n    }\n\n    /// A lighting color.\n    ///\n    /// `lighting-color` in the SVG.\n    pub fn lighting_color(&self) -> Color {\n        self.lighting_color\n    }\n\n    /// A light source.\n    pub fn light_source(&self) -> LightSource {\n        self.light_source\n    }\n}\n\n/// A specular lighting filter primitive.\n///\n/// `feSpecularLighting` element in the SVG.\n#[derive(Clone, Debug)]\npub struct SpecularLighting {\n    pub(crate) input: Input,\n    pub(crate) surface_scale: f32,\n    pub(crate) specular_constant: f32,\n    pub(crate) specular_exponent: f32,\n    pub(crate) lighting_color: Color,\n    pub(crate) light_source: LightSource,\n}\n\nimpl SpecularLighting {\n    /// Identifies input for the given filter primitive.\n    ///\n    /// `in` in the SVG.\n    pub fn input(&self) -> &Input {\n        &self.input\n    }\n\n    /// A surface scale.\n    ///\n    /// `surfaceScale` in the SVG.\n    pub fn surface_scale(&self) -> f32 {\n        self.surface_scale\n    }\n\n    /// A specular constant.\n    ///\n    /// `specularConstant` in the SVG.\n    pub fn specular_constant(&self) -> f32 {\n        self.specular_constant\n    }\n\n    /// A specular exponent.\n    ///\n    /// Should be in 1..128 range.\n    ///\n    /// `specularExponent` in the SVG.\n    pub fn specular_exponent(&self) -> f32 {\n        self.specular_exponent\n    }\n\n    /// A lighting color.\n    ///\n    /// `lighting-color` in the SVG.\n    pub fn lighting_color(&self) -> Color {\n        self.lighting_color\n    }\n\n    /// A light source.\n    pub fn light_source(&self) -> LightSource {\n        self.light_source\n    }\n}\n\n/// A light source kind.\n#[allow(missing_docs)]\n#[derive(Clone, Copy, Debug)]\npub enum LightSource {\n    DistantLight(DistantLight),\n    PointLight(PointLight),\n    SpotLight(SpotLight),\n}\n\n/// A distant light source.\n///\n/// `feDistantLight` element in the SVG.\n#[derive(Clone, Copy, Debug)]\npub struct DistantLight {\n    /// Direction angle for the light source on the XY plane (clockwise),\n    /// in degrees from the x axis.\n    ///\n    /// `azimuth` in the SVG.\n    pub azimuth: f32,\n\n    /// Direction angle for the light source from the XY plane towards the z axis, in degrees.\n    ///\n    /// `elevation` in the SVG.\n    pub elevation: f32,\n}\n\n/// A point light source.\n///\n/// `fePointLight` element in the SVG.\n#[derive(Clone, Copy, Debug)]\npub struct PointLight {\n    /// X location for the light source.\n    ///\n    /// `x` in the SVG.\n    pub x: f32,\n\n    /// Y location for the light source.\n    ///\n    /// `y` in the SVG.\n    pub y: f32,\n\n    /// Z location for the light source.\n    ///\n    /// `z` in the SVG.\n    pub z: f32,\n}\n\n/// A spot light source.\n///\n/// `feSpotLight` element in the SVG.\n#[derive(Clone, Copy, Debug)]\npub struct SpotLight {\n    /// X location for the light source.\n    ///\n    /// `x` in the SVG.\n    pub x: f32,\n\n    /// Y location for the light source.\n    ///\n    /// `y` in the SVG.\n    pub y: f32,\n\n    /// Z location for the light source.\n    ///\n    /// `z` in the SVG.\n    pub z: f32,\n\n    /// X point at which the light source is pointing.\n    ///\n    /// `pointsAtX` in the SVG.\n    pub points_at_x: f32,\n\n    /// Y point at which the light source is pointing.\n    ///\n    /// `pointsAtY` in the SVG.\n    pub points_at_y: f32,\n\n    /// Z point at which the light source is pointing.\n    ///\n    /// `pointsAtZ` in the SVG.\n    pub points_at_z: f32,\n\n    /// Exponent value controlling the focus for the light source.\n    ///\n    /// `specularExponent` in the SVG.\n    pub specular_exponent: PositiveF32,\n\n    /// A limiting cone which restricts the region where the light is projected.\n    ///\n    /// `limitingConeAngle` in the SVG.\n    pub limiting_cone_angle: Option<f32>,\n}\n\n/// A merge filter primitive.\n///\n/// `feMerge` element in the SVG.\n#[derive(Clone, Debug)]\npub struct Merge {\n    pub(crate) inputs: Vec<Input>,\n}\n\nimpl Merge {\n    /// List of input layers that should be merged.\n    ///\n    /// List of `feMergeNode`'s in the SVG.\n    pub fn inputs(&self) -> &[Input] {\n        &self.inputs\n    }\n}\n\n/// A morphology filter primitive.\n///\n/// `feMorphology` element in the SVG.\n#[derive(Clone, Debug)]\npub struct Morphology {\n    pub(crate) input: Input,\n    pub(crate) operator: MorphologyOperator,\n    pub(crate) radius_x: PositiveF32,\n    pub(crate) radius_y: PositiveF32,\n}\n\nimpl Morphology {\n    /// Identifies input for the given filter primitive.\n    ///\n    /// `in` in the SVG.\n    pub fn input(&self) -> &Input {\n        &self.input\n    }\n\n    /// A filter operator.\n    ///\n    /// `operator` in the SVG.\n    pub fn operator(&self) -> MorphologyOperator {\n        self.operator\n    }\n\n    /// A filter radius along the X-axis.\n    ///\n    /// A value of zero disables the effect of the given filter primitive.\n    ///\n    /// `radius` in the SVG.\n    pub fn radius_x(&self) -> PositiveF32 {\n        self.radius_x\n    }\n\n    /// A filter radius along the Y-axis.\n    ///\n    /// A value of zero disables the effect of the given filter primitive.\n    ///\n    /// `radius` in the SVG.\n    pub fn radius_y(&self) -> PositiveF32 {\n        self.radius_y\n    }\n}\n\n/// A morphology operation.\n#[allow(missing_docs)]\n#[derive(Clone, Copy, PartialEq, Debug)]\npub enum MorphologyOperator {\n    Erode,\n    Dilate,\n}\n\n/// An offset filter primitive.\n///\n/// `feOffset` element in the SVG.\n#[derive(Clone, Debug)]\npub struct Offset {\n    pub(crate) input: Input,\n    pub(crate) dx: f32,\n    pub(crate) dy: f32,\n}\n\nimpl Offset {\n    /// Identifies input for the given filter primitive.\n    ///\n    /// `in` in the SVG.\n    pub fn input(&self) -> &Input {\n        &self.input\n    }\n\n    /// The amount to offset the input graphic along the X-axis.\n    pub fn dx(&self) -> f32 {\n        self.dx\n    }\n\n    /// The amount to offset the input graphic along the Y-axis.\n    pub fn dy(&self) -> f32 {\n        self.dy\n    }\n}\n\n/// A tile filter primitive.\n///\n/// `feTile` element in the SVG.\n#[derive(Clone, Debug)]\npub struct Tile {\n    pub(crate) input: Input,\n}\n\nimpl Tile {\n    /// Identifies input for the given filter primitive.\n    ///\n    /// `in` in the SVG.\n    pub fn input(&self) -> &Input {\n        &self.input\n    }\n}\n\n/// A turbulence generation filter primitive.\n///\n/// `feTurbulence` element in the SVG.\n#[derive(Clone, Copy, Debug)]\npub struct Turbulence {\n    pub(crate) base_frequency_x: PositiveF32,\n    pub(crate) base_frequency_y: PositiveF32,\n    pub(crate) num_octaves: u32,\n    pub(crate) seed: i32,\n    pub(crate) stitch_tiles: bool,\n    pub(crate) kind: TurbulenceKind,\n}\n\nimpl Turbulence {\n    /// Identifies the base frequency for the noise function.\n    ///\n    /// `baseFrequency` in the SVG.\n    pub fn base_frequency_x(&self) -> PositiveF32 {\n        self.base_frequency_x\n    }\n\n    /// Identifies the base frequency for the noise function.\n    ///\n    /// `baseFrequency` in the SVG.\n    pub fn base_frequency_y(&self) -> PositiveF32 {\n        self.base_frequency_y\n    }\n\n    /// Identifies the number of octaves for the noise function.\n    ///\n    /// `numOctaves` in the SVG.\n    pub fn num_octaves(&self) -> u32 {\n        self.num_octaves\n    }\n\n    /// The starting number for the pseudo random number generator.\n    ///\n    /// `seed` in the SVG.\n    pub fn seed(&self) -> i32 {\n        self.seed\n    }\n\n    /// Smooth transitions at the border of tiles.\n    ///\n    /// `stitchTiles` in the SVG.\n    pub fn stitch_tiles(&self) -> bool {\n        self.stitch_tiles\n    }\n\n    /// Indicates whether the filter primitive should perform a noise or turbulence function.\n    ///\n    /// `type` in the SVG.\n    pub fn kind(&self) -> TurbulenceKind {\n        self.kind\n    }\n}\n\n/// A turbulence kind for the `feTurbulence` filter.\n#[allow(missing_docs)]\n#[derive(Clone, Copy, PartialEq, Debug)]\npub enum TurbulenceKind {\n    FractalNoise,\n    Turbulence,\n}\n"
  },
  {
    "path": "crates/usvg/src/tree/geom.rs",
    "content": "// Copyright 2018 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse strict_num::ApproxEqUlps;\nuse svgtypes::{Align, AspectRatio};\npub use tiny_skia_path::{NonZeroRect, Rect, Size, Transform};\n\n/// Approximate zero equality comparisons.\npub trait ApproxZeroUlps: ApproxEqUlps {\n    /// Checks if the number is approximately zero.\n    fn approx_zero_ulps(&self, ulps: <Self::Flt as strict_num::Ulps>::U) -> bool;\n}\n\nimpl ApproxZeroUlps for f32 {\n    fn approx_zero_ulps(&self, ulps: i32) -> bool {\n        self.approx_eq_ulps(&0.0, ulps)\n    }\n}\n\nimpl ApproxZeroUlps for f64 {\n    fn approx_zero_ulps(&self, ulps: i64) -> bool {\n        self.approx_eq_ulps(&0.0, ulps)\n    }\n}\n\n/// Checks that the current number is > 0.\npub(crate) trait IsValidLength {\n    /// Checks that the current number is > 0.\n    fn is_valid_length(&self) -> bool;\n}\n\nimpl IsValidLength for f32 {\n    #[inline]\n    fn is_valid_length(&self) -> bool {\n        *self > 0.0 && self.is_finite()\n    }\n}\n\nimpl IsValidLength for f64 {\n    #[inline]\n    fn is_valid_length(&self) -> bool {\n        *self > 0.0 && self.is_finite()\n    }\n}\n\n/// View box.\n#[derive(Clone, Copy, Debug)]\npub(crate) struct ViewBox {\n    /// Value of the `viewBox` attribute.\n    pub rect: NonZeroRect,\n\n    /// Value of the `preserveAspectRatio` attribute.\n    pub aspect: AspectRatio,\n}\n\nimpl ViewBox {\n    /// Converts `viewBox` into `Transform`.\n    pub fn to_transform(&self, img_size: Size) -> Transform {\n        let vr = self.rect;\n\n        let sx = img_size.width() / vr.width();\n        let sy = img_size.height() / vr.height();\n\n        let (sx, sy) = if self.aspect.align == Align::None {\n            (sx, sy)\n        } else {\n            let s = if self.aspect.slice {\n                if sx < sy { sy } else { sx }\n            } else {\n                if sx > sy { sy } else { sx }\n            };\n\n            (s, s)\n        };\n\n        let x = -vr.x() * sx;\n        let y = -vr.y() * sy;\n        let w = img_size.width() - vr.width() * sx;\n        let h = img_size.height() - vr.height() * sy;\n\n        let (tx, ty) = aligned_pos(self.aspect.align, x, y, w, h);\n        Transform::from_row(sx, 0.0, 0.0, sy, tx, ty)\n    }\n}\n\n/// A bounding box calculator.\n#[derive(Clone, Copy, Debug)]\npub(crate) struct BBox {\n    left: f32,\n    top: f32,\n    right: f32,\n    bottom: f32,\n}\n\nimpl From<Rect> for BBox {\n    fn from(r: Rect) -> Self {\n        Self {\n            left: r.left(),\n            top: r.top(),\n            right: r.right(),\n            bottom: r.bottom(),\n        }\n    }\n}\n\nimpl From<NonZeroRect> for BBox {\n    fn from(r: NonZeroRect) -> Self {\n        Self {\n            left: r.left(),\n            top: r.top(),\n            right: r.right(),\n            bottom: r.bottom(),\n        }\n    }\n}\n\nimpl Default for BBox {\n    fn default() -> Self {\n        Self {\n            left: f32::MAX,\n            top: f32::MAX,\n            right: f32::MIN,\n            bottom: f32::MIN,\n        }\n    }\n}\n\nimpl BBox {\n    /// Checks if the bounding box is default, i.e. invalid.\n    pub fn is_default(&self) -> bool {\n        self.left == f32::MAX\n            && self.top == f32::MAX\n            && self.right == f32::MIN\n            && self.bottom == f32::MIN\n    }\n\n    /// Expand the bounding box to the specified bounds.\n    #[must_use]\n    pub fn expand(&self, r: impl Into<Self>) -> Self {\n        self.expand_impl(r.into())\n    }\n\n    fn expand_impl(&self, r: Self) -> Self {\n        Self {\n            left: self.left.min(r.left),\n            top: self.top.min(r.top),\n            right: self.right.max(r.right),\n            bottom: self.bottom.max(r.bottom),\n        }\n    }\n\n    /// Converts a bounding box into [`Rect`].\n    pub fn to_rect(&self) -> Option<Rect> {\n        if !self.is_default() {\n            Rect::from_ltrb(self.left, self.top, self.right, self.bottom)\n        } else {\n            None\n        }\n    }\n\n    /// Converts a bounding box into [`NonZeroRect`].\n    pub fn to_non_zero_rect(&self) -> Option<NonZeroRect> {\n        if !self.is_default() {\n            NonZeroRect::from_ltrb(self.left, self.top, self.right, self.bottom)\n        } else {\n            None\n        }\n    }\n}\n\n/// Returns object aligned position.\npub(crate) fn aligned_pos(align: Align, x: f32, y: f32, w: f32, h: f32) -> (f32, f32) {\n    match align {\n        Align::None => (x, y),\n        Align::XMinYMin => (x, y),\n        Align::XMidYMin => (x + w / 2.0, y),\n        Align::XMaxYMin => (x + w, y),\n        Align::XMinYMid => (x, y + h / 2.0),\n        Align::XMidYMid => (x + w / 2.0, y + h / 2.0),\n        Align::XMaxYMid => (x + w, y + h / 2.0),\n        Align::XMinYMax => (x, y + h),\n        Align::XMidYMax => (x + w / 2.0, y + h),\n        Align::XMaxYMax => (x + w, y + h),\n    }\n}\n"
  },
  {
    "path": "crates/usvg/src/tree/mod.rs",
    "content": "// Copyright 2019 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\npub mod filter;\nmod geom;\nmod text;\n\nuse std::fmt::Display;\nuse std::sync::Arc;\n\npub use strict_num::{self, ApproxEqUlps, NonZeroPositiveF32, NormalizedF32, PositiveF32};\n\npub use tiny_skia_path;\n\npub use self::geom::*;\npub use self::text::*;\n\nuse crate::OptionLog;\n\n/// An alias to `NormalizedF32`.\npub type Opacity = NormalizedF32;\n\n// Must not be clone-able to preserve ID uniqueness.\n#[derive(Debug)]\npub(crate) struct NonEmptyString(String);\n\nimpl NonEmptyString {\n    pub(crate) fn new(string: String) -> Option<Self> {\n        if string.trim().is_empty() {\n            return None;\n        }\n\n        Some(NonEmptyString(string))\n    }\n\n    pub(crate) fn get(&self) -> &str {\n        &self.0\n    }\n\n    pub(crate) fn take(self) -> String {\n        self.0\n    }\n}\n\n/// A non-zero `f32`.\n///\n/// Just like `f32` but immutable and guarantee to never be zero.\n#[derive(Clone, Copy, Debug)]\npub struct NonZeroF32(f32);\n\nimpl NonZeroF32 {\n    /// Creates a new `NonZeroF32` value.\n    #[inline]\n    pub fn new(n: f32) -> Option<Self> {\n        if n.approx_eq_ulps(&0.0, 4) {\n            None\n        } else {\n            Some(NonZeroF32(n))\n        }\n    }\n\n    /// Returns an underlying value.\n    #[inline]\n    pub fn get(&self) -> f32 {\n        self.0\n    }\n}\n\n#[derive(Clone, Copy, PartialEq, Debug)]\npub(crate) enum Units {\n    UserSpaceOnUse,\n    ObjectBoundingBox,\n}\n\n// `Units` cannot have a default value, because it changes depending on an element.\n\n/// A visibility property.\n///\n/// `visibility` attribute in the SVG.\n#[allow(missing_docs)]\n#[derive(Clone, Copy, PartialEq, Debug)]\npub(crate) enum Visibility {\n    Visible,\n    Hidden,\n    Collapse,\n}\n\nimpl Default for Visibility {\n    fn default() -> Self {\n        Self::Visible\n    }\n}\n\n/// A shape rendering method.\n///\n/// `shape-rendering` attribute in the SVG.\n#[derive(Clone, Copy, PartialEq, Debug)]\n#[allow(missing_docs)]\npub enum ShapeRendering {\n    OptimizeSpeed,\n    CrispEdges,\n    GeometricPrecision,\n}\n\nimpl ShapeRendering {\n    /// Checks if anti-aliasing should be enabled.\n    pub fn use_shape_antialiasing(self) -> bool {\n        match self {\n            ShapeRendering::OptimizeSpeed => false,\n            ShapeRendering::CrispEdges => false,\n            ShapeRendering::GeometricPrecision => true,\n        }\n    }\n}\n\nimpl Default for ShapeRendering {\n    fn default() -> Self {\n        Self::GeometricPrecision\n    }\n}\n\nimpl std::str::FromStr for ShapeRendering {\n    type Err = &'static str;\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        match s {\n            \"optimizeSpeed\" => Ok(ShapeRendering::OptimizeSpeed),\n            \"crispEdges\" => Ok(ShapeRendering::CrispEdges),\n            \"geometricPrecision\" => Ok(ShapeRendering::GeometricPrecision),\n            _ => Err(\"invalid\"),\n        }\n    }\n}\n\n/// A text rendering method.\n///\n/// `text-rendering` attribute in the SVG.\n#[allow(missing_docs)]\n#[derive(Clone, Copy, PartialEq, Debug)]\npub enum TextRendering {\n    OptimizeSpeed,\n    OptimizeLegibility,\n    GeometricPrecision,\n}\n\nimpl Default for TextRendering {\n    fn default() -> Self {\n        Self::OptimizeLegibility\n    }\n}\n\nimpl std::str::FromStr for TextRendering {\n    type Err = &'static str;\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        match s {\n            \"optimizeSpeed\" => Ok(TextRendering::OptimizeSpeed),\n            \"optimizeLegibility\" => Ok(TextRendering::OptimizeLegibility),\n            \"geometricPrecision\" => Ok(TextRendering::GeometricPrecision),\n            _ => Err(\"invalid\"),\n        }\n    }\n}\n\n/// An image rendering method.\n///\n/// `image-rendering` attribute in the SVG.\n#[allow(missing_docs)]\n#[derive(Clone, Copy, PartialEq, Debug)]\npub enum ImageRendering {\n    OptimizeQuality,\n    OptimizeSpeed,\n    // The following can only appear as presentation attributes.\n    Smooth,\n    HighQuality,\n    CrispEdges,\n    Pixelated,\n}\n\nimpl Default for ImageRendering {\n    fn default() -> Self {\n        Self::OptimizeQuality\n    }\n}\n\nimpl std::str::FromStr for ImageRendering {\n    type Err = &'static str;\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        match s {\n            \"optimizeQuality\" => Ok(ImageRendering::OptimizeQuality),\n            \"optimizeSpeed\" => Ok(ImageRendering::OptimizeSpeed),\n            \"smooth\" => Ok(ImageRendering::Smooth),\n            \"high-quality\" => Ok(ImageRendering::HighQuality),\n            \"crisp-edges\" => Ok(ImageRendering::CrispEdges),\n            \"pixelated\" => Ok(ImageRendering::Pixelated),\n            _ => Err(\"invalid\"),\n        }\n    }\n}\n\n/// A blending mode property.\n///\n/// `mix-blend-mode` attribute in the SVG.\n#[allow(missing_docs)]\n#[derive(Clone, Copy, PartialEq, Debug)]\npub enum BlendMode {\n    Normal,\n    Multiply,\n    Screen,\n    Overlay,\n    Darken,\n    Lighten,\n    ColorDodge,\n    ColorBurn,\n    HardLight,\n    SoftLight,\n    Difference,\n    Exclusion,\n    Hue,\n    Saturation,\n    Color,\n    Luminosity,\n}\n\nimpl Default for BlendMode {\n    fn default() -> Self {\n        Self::Normal\n    }\n}\n\nimpl Display for BlendMode {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let blend_mode = match self {\n            BlendMode::Normal => \"normal\",\n            BlendMode::Multiply => \"multiply\",\n            BlendMode::Screen => \"screen\",\n            BlendMode::Overlay => \"overlay\",\n            BlendMode::Darken => \"darken\",\n            BlendMode::Lighten => \"lighten\",\n            BlendMode::ColorDodge => \"color-dodge\",\n            BlendMode::ColorBurn => \"color-burn\",\n            BlendMode::HardLight => \"hard-light\",\n            BlendMode::SoftLight => \"soft-light\",\n            BlendMode::Difference => \"difference\",\n            BlendMode::Exclusion => \"exclusion\",\n            BlendMode::Hue => \"hue\",\n            BlendMode::Saturation => \"saturation\",\n            BlendMode::Color => \"color\",\n            BlendMode::Luminosity => \"luminosity\",\n        };\n        write!(f, \"{blend_mode}\")\n    }\n}\n\n/// A spread method.\n///\n/// `spreadMethod` attribute in the SVG.\n#[allow(missing_docs)]\n#[derive(Clone, Copy, PartialEq, Debug)]\npub enum SpreadMethod {\n    Pad,\n    Reflect,\n    Repeat,\n}\n\nimpl Default for SpreadMethod {\n    fn default() -> Self {\n        Self::Pad\n    }\n}\n\n/// A generic gradient.\n#[derive(Debug)]\npub struct BaseGradient {\n    pub(crate) id: NonEmptyString,\n    pub(crate) units: Units, // used only during parsing\n    pub(crate) transform: Transform,\n    pub(crate) spread_method: SpreadMethod,\n    pub(crate) stops: Vec<Stop>,\n}\n\nimpl BaseGradient {\n    /// Element's ID.\n    ///\n    /// Taken from the SVG itself.\n    /// Used only during SVG writing. `resvg` doesn't rely on this property.\n    pub fn id(&self) -> &str {\n        self.id.get()\n    }\n\n    /// Gradient transform.\n    ///\n    /// `gradientTransform` in SVG.\n    pub fn transform(&self) -> Transform {\n        self.transform\n    }\n\n    /// Gradient spreading method.\n    ///\n    /// `spreadMethod` in SVG.\n    pub fn spread_method(&self) -> SpreadMethod {\n        self.spread_method\n    }\n\n    /// A list of `stop` elements.\n    pub fn stops(&self) -> &[Stop] {\n        &self.stops\n    }\n}\n\n/// A linear gradient.\n///\n/// `linearGradient` element in SVG.\n#[derive(Debug)]\npub struct LinearGradient {\n    pub(crate) base: BaseGradient,\n    pub(crate) x1: f32,\n    pub(crate) y1: f32,\n    pub(crate) x2: f32,\n    pub(crate) y2: f32,\n}\n\nimpl LinearGradient {\n    /// `x1` coordinate.\n    pub fn x1(&self) -> f32 {\n        self.x1\n    }\n\n    /// `y1` coordinate.\n    pub fn y1(&self) -> f32 {\n        self.y1\n    }\n\n    /// `x2` coordinate.\n    pub fn x2(&self) -> f32 {\n        self.x2\n    }\n\n    /// `y2` coordinate.\n    pub fn y2(&self) -> f32 {\n        self.y2\n    }\n}\n\nimpl std::ops::Deref for LinearGradient {\n    type Target = BaseGradient;\n\n    fn deref(&self) -> &Self::Target {\n        &self.base\n    }\n}\n\n/// A radial gradient.\n///\n/// `radialGradient` element in SVG.\n#[derive(Debug)]\npub struct RadialGradient {\n    pub(crate) base: BaseGradient,\n    pub(crate) cx: f32,\n    pub(crate) cy: f32,\n    pub(crate) r: PositiveF32,\n    pub(crate) fx: f32,\n    pub(crate) fy: f32,\n    pub(crate) fr: PositiveF32,\n}\n\nimpl RadialGradient {\n    /// `cx` coordinate.\n    pub fn cx(&self) -> f32 {\n        self.cx\n    }\n\n    /// `cy` coordinate.\n    pub fn cy(&self) -> f32 {\n        self.cy\n    }\n\n    /// Gradient radius.\n    pub fn r(&self) -> PositiveF32 {\n        self.r\n    }\n\n    /// `fx` coordinate.\n    pub fn fx(&self) -> f32 {\n        self.fx\n    }\n\n    /// `fy` coordinate.\n    pub fn fy(&self) -> f32 {\n        self.fy\n    }\n\n    /// Focal radius.\n    pub fn fr(&self) -> PositiveF32 {\n        self.fr\n    }\n}\n\nimpl std::ops::Deref for RadialGradient {\n    type Target = BaseGradient;\n\n    fn deref(&self) -> &Self::Target {\n        &self.base\n    }\n}\n\n/// An alias to `NormalizedF32`.\npub type StopOffset = NormalizedF32;\n\n/// Gradient's stop element.\n///\n/// `stop` element in SVG.\n#[derive(Clone, Copy, Debug)]\npub struct Stop {\n    pub(crate) offset: StopOffset,\n    pub(crate) color: Color,\n    pub(crate) opacity: Opacity,\n}\n\nimpl Stop {\n    /// Gradient stop offset.\n    ///\n    /// `offset` in SVG.\n    pub fn offset(&self) -> StopOffset {\n        self.offset\n    }\n\n    /// Gradient stop color.\n    ///\n    /// `stop-color` in SVG.\n    pub fn color(&self) -> Color {\n        self.color\n    }\n\n    /// Gradient stop opacity.\n    ///\n    /// `stop-opacity` in SVG.\n    pub fn opacity(&self) -> Opacity {\n        self.opacity\n    }\n}\n\n/// A pattern element.\n///\n/// `pattern` element in SVG.\n#[derive(Debug)]\npub struct Pattern {\n    pub(crate) id: NonEmptyString,\n    pub(crate) units: Units,         // used only during parsing\n    pub(crate) content_units: Units, // used only during parsing\n    pub(crate) transform: Transform,\n    pub(crate) rect: NonZeroRect,\n    pub(crate) view_box: Option<ViewBox>,\n    pub(crate) root: Group,\n}\n\nimpl Pattern {\n    /// Element's ID.\n    ///\n    /// Taken from the SVG itself.\n    /// Used only during SVG writing. `resvg` doesn't rely on this property.\n    pub fn id(&self) -> &str {\n        self.id.get()\n    }\n\n    /// Pattern transform.\n    ///\n    /// `patternTransform` in SVG.\n    pub fn transform(&self) -> Transform {\n        self.transform\n    }\n\n    /// Pattern rectangle.\n    ///\n    /// `x`, `y`, `width` and `height` in SVG.\n    pub fn rect(&self) -> NonZeroRect {\n        self.rect\n    }\n\n    /// Pattern children.\n    pub fn root(&self) -> &Group {\n        &self.root\n    }\n}\n\n/// An alias to `NonZeroPositiveF32`.\npub type StrokeWidth = NonZeroPositiveF32;\n\n/// A `stroke-miterlimit` value.\n///\n/// Just like `f32` but immutable and guarantee to be >=1.0.\n#[derive(Clone, Copy, Debug)]\npub struct StrokeMiterlimit(f32);\n\nimpl StrokeMiterlimit {\n    /// Creates a new `StrokeMiterlimit` value.\n    #[inline]\n    pub fn new(n: f32) -> Self {\n        debug_assert!(n.is_finite());\n        debug_assert!(n >= 1.0);\n\n        let n = if !(n >= 1.0) { 1.0 } else { n };\n\n        StrokeMiterlimit(n)\n    }\n\n    /// Returns an underlying value.\n    #[inline]\n    pub fn get(&self) -> f32 {\n        self.0\n    }\n}\n\nimpl Default for StrokeMiterlimit {\n    #[inline]\n    fn default() -> Self {\n        StrokeMiterlimit::new(4.0)\n    }\n}\n\nimpl From<f32> for StrokeMiterlimit {\n    #[inline]\n    fn from(n: f32) -> Self {\n        Self::new(n)\n    }\n}\n\nimpl PartialEq for StrokeMiterlimit {\n    #[inline]\n    fn eq(&self, other: &Self) -> bool {\n        self.0.approx_eq_ulps(&other.0, 4)\n    }\n}\n\n/// A line cap.\n///\n/// `stroke-linecap` attribute in the SVG.\n#[allow(missing_docs)]\n#[derive(Clone, Copy, PartialEq, Debug)]\npub enum LineCap {\n    Butt,\n    Round,\n    Square,\n}\n\nimpl Default for LineCap {\n    fn default() -> Self {\n        Self::Butt\n    }\n}\n\n/// A line join.\n///\n/// `stroke-linejoin` attribute in the SVG.\n#[allow(missing_docs)]\n#[derive(Clone, Copy, PartialEq, Debug)]\npub enum LineJoin {\n    Miter,\n    MiterClip,\n    Round,\n    Bevel,\n}\n\nimpl Default for LineJoin {\n    fn default() -> Self {\n        Self::Miter\n    }\n}\n\n/// A stroke style.\n#[derive(Clone, Debug)]\npub struct Stroke {\n    pub(crate) paint: Paint,\n    pub(crate) dasharray: Option<Vec<f32>>,\n    pub(crate) dashoffset: f32,\n    pub(crate) miterlimit: StrokeMiterlimit,\n    pub(crate) opacity: Opacity,\n    pub(crate) width: StrokeWidth,\n    pub(crate) linecap: LineCap,\n    pub(crate) linejoin: LineJoin,\n    // Whether the current stroke needs to be resolved relative\n    // to a context element.\n    pub(crate) context_element: Option<ContextElement>,\n}\n\nimpl Stroke {\n    /// Stroke paint.\n    pub fn paint(&self) -> &Paint {\n        &self.paint\n    }\n\n    /// Stroke dash array.\n    pub fn dasharray(&self) -> Option<&[f32]> {\n        self.dasharray.as_deref()\n    }\n\n    /// Stroke dash offset.\n    pub fn dashoffset(&self) -> f32 {\n        self.dashoffset\n    }\n\n    /// Stroke miter limit.\n    pub fn miterlimit(&self) -> StrokeMiterlimit {\n        self.miterlimit\n    }\n\n    /// Stroke opacity.\n    pub fn opacity(&self) -> Opacity {\n        self.opacity\n    }\n\n    /// Stroke width.\n    pub fn width(&self) -> StrokeWidth {\n        self.width\n    }\n\n    /// Stroke linecap.\n    pub fn linecap(&self) -> LineCap {\n        self.linecap\n    }\n\n    /// Stroke linejoin.\n    pub fn linejoin(&self) -> LineJoin {\n        self.linejoin\n    }\n\n    /// Converts into a `tiny_skia_path::Stroke` type.\n    pub fn to_tiny_skia(&self) -> tiny_skia_path::Stroke {\n        let mut stroke = tiny_skia_path::Stroke {\n            width: self.width.get(),\n            miter_limit: self.miterlimit.get(),\n            line_cap: match self.linecap {\n                LineCap::Butt => tiny_skia_path::LineCap::Butt,\n                LineCap::Round => tiny_skia_path::LineCap::Round,\n                LineCap::Square => tiny_skia_path::LineCap::Square,\n            },\n            line_join: match self.linejoin {\n                LineJoin::Miter => tiny_skia_path::LineJoin::Miter,\n                LineJoin::MiterClip => tiny_skia_path::LineJoin::MiterClip,\n                LineJoin::Round => tiny_skia_path::LineJoin::Round,\n                LineJoin::Bevel => tiny_skia_path::LineJoin::Bevel,\n            },\n            // According to the spec, dash should not be accounted during\n            // bbox calculation.\n            dash: None,\n        };\n\n        if let Some(ref list) = self.dasharray {\n            stroke.dash = tiny_skia_path::StrokeDash::new(list.clone(), self.dashoffset);\n        }\n\n        stroke\n    }\n}\n\n/// A fill rule.\n///\n/// `fill-rule` attribute in the SVG.\n#[allow(missing_docs)]\n#[derive(Clone, Copy, PartialEq, Debug)]\npub enum FillRule {\n    NonZero,\n    EvenOdd,\n}\n\nimpl Default for FillRule {\n    fn default() -> Self {\n        Self::NonZero\n    }\n}\n\n#[derive(Clone, Copy, Debug)]\npub(crate) enum ContextElement {\n    /// The current context element is a use node. Since we can get\n    /// the bounding box of a use node only once we have converted\n    /// all elements, we need to fix the transform and units of\n    /// the stroke/fill after converting the whole tree.\n    UseNode,\n    /// The current context element is a path node (i.e. only applicable\n    /// if we draw the marker of a path). Since we already know the bounding\n    /// box of the path when rendering the markers, we can convert them directly,\n    /// so we do it while parsing.\n    PathNode(Transform, Option<NonZeroRect>),\n}\n\n/// A fill style.\n#[derive(Clone, Debug)]\npub struct Fill {\n    pub(crate) paint: Paint,\n    pub(crate) opacity: Opacity,\n    pub(crate) rule: FillRule,\n    // Whether the current fill needs to be resolved relative\n    // to a context element.\n    pub(crate) context_element: Option<ContextElement>,\n}\n\nimpl Fill {\n    /// Fill paint.\n    pub fn paint(&self) -> &Paint {\n        &self.paint\n    }\n\n    /// Fill opacity.\n    pub fn opacity(&self) -> Opacity {\n        self.opacity\n    }\n\n    /// Fill rule.\n    pub fn rule(&self) -> FillRule {\n        self.rule\n    }\n}\n\nimpl Default for Fill {\n    fn default() -> Self {\n        Fill {\n            paint: Paint::Color(Color::black()),\n            opacity: Opacity::ONE,\n            rule: FillRule::default(),\n            context_element: None,\n        }\n    }\n}\n\n/// A 8-bit RGB color.\n#[derive(Clone, Copy, PartialEq, Debug)]\n#[allow(missing_docs)]\npub struct Color {\n    pub red: u8,\n    pub green: u8,\n    pub blue: u8,\n}\n\nimpl Color {\n    /// Constructs a new `Color` from RGB values.\n    #[inline]\n    pub fn new_rgb(red: u8, green: u8, blue: u8) -> Color {\n        Color { red, green, blue }\n    }\n\n    /// Constructs a new `Color` set to black.\n    #[inline]\n    pub fn black() -> Color {\n        Color::new_rgb(0, 0, 0)\n    }\n\n    /// Constructs a new `Color` set to white.\n    #[inline]\n    pub fn white() -> Color {\n        Color::new_rgb(255, 255, 255)\n    }\n}\n\n/// A paint style.\n///\n/// `paint` value type in the SVG.\n#[allow(missing_docs)]\n#[derive(Clone, Debug)]\npub enum Paint {\n    Color(Color),\n    LinearGradient(Arc<LinearGradient>),\n    RadialGradient(Arc<RadialGradient>),\n    Pattern(Arc<Pattern>),\n}\n\nimpl PartialEq for Paint {\n    #[inline]\n    fn eq(&self, other: &Self) -> bool {\n        match (self, other) {\n            (Self::Color(lc), Self::Color(rc)) => lc == rc,\n            (Self::LinearGradient(lg1), Self::LinearGradient(lg2)) => Arc::ptr_eq(lg1, lg2),\n            (Self::RadialGradient(rg1), Self::RadialGradient(rg2)) => Arc::ptr_eq(rg1, rg2),\n            (Self::Pattern(p1), Self::Pattern(p2)) => Arc::ptr_eq(p1, p2),\n            _ => false,\n        }\n    }\n}\n\n/// A clip-path element.\n///\n/// `clipPath` element in SVG.\n#[derive(Debug)]\npub struct ClipPath {\n    pub(crate) id: NonEmptyString,\n    pub(crate) transform: Transform,\n    pub(crate) clip_path: Option<Arc<ClipPath>>,\n    pub(crate) root: Group,\n}\n\nimpl ClipPath {\n    pub(crate) fn empty(id: NonEmptyString) -> Self {\n        ClipPath {\n            id,\n            transform: Transform::default(),\n            clip_path: None,\n            root: Group::empty(),\n        }\n    }\n\n    /// Element's ID.\n    ///\n    /// Taken from the SVG itself.\n    /// Used only during SVG writing. `resvg` doesn't rely on this property.\n    pub fn id(&self) -> &str {\n        self.id.get()\n    }\n\n    /// Clip path transform.\n    ///\n    /// `transform` in SVG.\n    pub fn transform(&self) -> Transform {\n        self.transform\n    }\n\n    /// Additional clip path.\n    ///\n    /// `clip-path` in SVG.\n    pub fn clip_path(&self) -> Option<&ClipPath> {\n        self.clip_path.as_deref()\n    }\n\n    /// Clip path children.\n    pub fn root(&self) -> &Group {\n        &self.root\n    }\n}\n\n/// A mask type.\n#[derive(Clone, Copy, PartialEq, Debug)]\npub enum MaskType {\n    /// Indicates that the luminance values of the mask should be used.\n    Luminance,\n    /// Indicates that the alpha values of the mask should be used.\n    Alpha,\n}\n\nimpl Default for MaskType {\n    fn default() -> Self {\n        Self::Luminance\n    }\n}\n\n/// A mask element.\n///\n/// `mask` element in SVG.\n#[derive(Debug)]\npub struct Mask {\n    pub(crate) id: NonEmptyString,\n    pub(crate) rect: NonZeroRect,\n    pub(crate) kind: MaskType,\n    pub(crate) mask: Option<Arc<Mask>>,\n    pub(crate) root: Group,\n}\n\nimpl Mask {\n    /// Element's ID.\n    ///\n    /// Taken from the SVG itself.\n    /// Used only during SVG writing. `resvg` doesn't rely on this property.\n    pub fn id(&self) -> &str {\n        self.id.get()\n    }\n\n    /// Mask rectangle.\n    ///\n    /// `x`, `y`, `width` and `height` in SVG.\n    pub fn rect(&self) -> NonZeroRect {\n        self.rect\n    }\n\n    /// Mask type.\n    ///\n    /// `mask-type` in SVG.\n    pub fn kind(&self) -> MaskType {\n        self.kind\n    }\n\n    /// Additional mask.\n    ///\n    /// `mask` in SVG.\n    pub fn mask(&self) -> Option<&Mask> {\n        self.mask.as_deref()\n    }\n\n    /// Mask children.\n    ///\n    /// A mask can have no children, in which case the whole element should be masked out.\n    pub fn root(&self) -> &Group {\n        &self.root\n    }\n}\n\n/// Node's kind.\n#[allow(missing_docs)]\n#[derive(Clone, Debug)]\npub enum Node {\n    Group(Box<Group>),\n    Path(Box<Path>),\n    Image(Box<Image>),\n    Text(Box<Text>),\n}\n\nimpl Node {\n    /// Returns node's ID.\n    pub fn id(&self) -> &str {\n        match self {\n            Node::Group(e) => e.id.as_str(),\n            Node::Path(e) => e.id.as_str(),\n            Node::Image(e) => e.id.as_str(),\n            Node::Text(e) => e.id.as_str(),\n        }\n    }\n\n    /// Returns node's absolute transform.\n    ///\n    /// This method is cheap since absolute transforms are already resolved.\n    pub fn abs_transform(&self) -> Transform {\n        match self {\n            Node::Group(group) => group.abs_transform(),\n            Node::Path(path) => path.abs_transform(),\n            Node::Image(image) => image.abs_transform(),\n            Node::Text(text) => text.abs_transform(),\n        }\n    }\n\n    /// Returns node's bounding box in object coordinates, if any.\n    pub fn bounding_box(&self) -> Rect {\n        match self {\n            Node::Group(group) => group.bounding_box(),\n            Node::Path(path) => path.bounding_box(),\n            Node::Image(image) => image.bounding_box(),\n            Node::Text(text) => text.bounding_box(),\n        }\n    }\n\n    /// Returns node's bounding box in canvas coordinates, if any.\n    pub fn abs_bounding_box(&self) -> Rect {\n        match self {\n            Node::Group(group) => group.abs_bounding_box(),\n            Node::Path(path) => path.abs_bounding_box(),\n            Node::Image(image) => image.abs_bounding_box(),\n            Node::Text(text) => text.abs_bounding_box(),\n        }\n    }\n\n    /// Returns node's bounding box, including stroke, in object coordinates, if any.\n    pub fn stroke_bounding_box(&self) -> Rect {\n        match self {\n            Node::Group(group) => group.stroke_bounding_box(),\n            Node::Path(path) => path.stroke_bounding_box(),\n            // Image cannot be stroked.\n            Node::Image(image) => image.bounding_box(),\n            Node::Text(text) => text.stroke_bounding_box(),\n        }\n    }\n\n    /// Returns node's bounding box, including stroke, in canvas coordinates, if any.\n    pub fn abs_stroke_bounding_box(&self) -> Rect {\n        match self {\n            Node::Group(group) => group.abs_stroke_bounding_box(),\n            Node::Path(path) => path.abs_stroke_bounding_box(),\n            // Image cannot be stroked.\n            Node::Image(image) => image.abs_bounding_box(),\n            Node::Text(text) => text.abs_stroke_bounding_box(),\n        }\n    }\n\n    /// Element's \"layer\" bounding box in canvas units, if any.\n    ///\n    /// For most nodes this is just `abs_bounding_box`,\n    /// but for groups this is `abs_layer_bounding_box`.\n    ///\n    /// See [`Group::layer_bounding_box`] for details.\n    pub fn abs_layer_bounding_box(&self) -> Option<NonZeroRect> {\n        match self {\n            Node::Group(group) => Some(group.abs_layer_bounding_box()),\n            // Hor/ver path without stroke can return None. This is expected.\n            Node::Path(path) => path.abs_bounding_box().to_non_zero_rect(),\n            Node::Image(image) => image.abs_bounding_box().to_non_zero_rect(),\n            Node::Text(text) => text.abs_bounding_box().to_non_zero_rect(),\n        }\n    }\n\n    /// Calls a closure for each subroot this `Node` has.\n    ///\n    /// The [`Tree::root`](Tree::root) field contain only render-able SVG elements.\n    /// But some elements, specifically clip paths, masks, patterns and feImage\n    /// can store their own SVG subtrees.\n    /// And while one can access them manually, it's pretty verbose.\n    /// This methods allows looping over _all_ SVG elements present in the `Tree`.\n    ///\n    /// # Example\n    ///\n    /// ```no_run\n    /// fn all_nodes(parent: &usvg::Group) {\n    ///     for node in parent.children() {\n    ///         // do stuff...\n    ///\n    ///         if let usvg::Node::Group(g) = node {\n    ///             all_nodes(g);\n    ///         }\n    ///\n    ///         // handle subroots as well\n    ///         node.subroots(|subroot| all_nodes(subroot));\n    ///     }\n    /// }\n    /// ```\n    pub fn subroots<F: FnMut(&Group)>(&self, mut f: F) {\n        match self {\n            Node::Group(group) => group.subroots(&mut f),\n            Node::Path(path) => path.subroots(&mut f),\n            Node::Image(image) => image.subroots(&mut f),\n            Node::Text(text) => text.subroots(&mut f),\n        }\n    }\n}\n\n/// A group container.\n///\n/// The preprocessor will remove all groups that don't impact rendering.\n/// Those that left is just an indicator that a new canvas should be created.\n///\n/// `g` element in SVG.\n#[derive(Clone, Debug)]\npub struct Group {\n    pub(crate) id: String,\n    pub(crate) transform: Transform,\n    pub(crate) abs_transform: Transform,\n    pub(crate) opacity: Opacity,\n    pub(crate) blend_mode: BlendMode,\n    pub(crate) isolate: bool,\n    pub(crate) clip_path: Option<Arc<ClipPath>>,\n    /// Whether the group is a context element (i.e. a use node)\n    pub(crate) is_context_element: bool,\n    pub(crate) mask: Option<Arc<Mask>>,\n    pub(crate) filters: Vec<Arc<filter::Filter>>,\n    pub(crate) bounding_box: Rect,\n    pub(crate) abs_bounding_box: Rect,\n    pub(crate) stroke_bounding_box: Rect,\n    pub(crate) abs_stroke_bounding_box: Rect,\n    pub(crate) layer_bounding_box: NonZeroRect,\n    pub(crate) abs_layer_bounding_box: NonZeroRect,\n    pub(crate) children: Vec<Node>,\n}\n\nimpl Group {\n    pub(crate) fn empty() -> Self {\n        let dummy = Rect::from_xywh(0.0, 0.0, 0.0, 0.0).unwrap();\n        Group {\n            id: String::new(),\n            transform: Transform::default(),\n            abs_transform: Transform::default(),\n            opacity: Opacity::ONE,\n            blend_mode: BlendMode::Normal,\n            isolate: false,\n            clip_path: None,\n            mask: None,\n            filters: Vec::new(),\n            is_context_element: false,\n            bounding_box: dummy,\n            abs_bounding_box: dummy,\n            stroke_bounding_box: dummy,\n            abs_stroke_bounding_box: dummy,\n            layer_bounding_box: NonZeroRect::from_xywh(0.0, 0.0, 1.0, 1.0).unwrap(),\n            abs_layer_bounding_box: NonZeroRect::from_xywh(0.0, 0.0, 1.0, 1.0).unwrap(),\n            children: Vec::new(),\n        }\n    }\n\n    /// Element's ID.\n    ///\n    /// Taken from the SVG itself.\n    /// Isn't automatically generated.\n    /// Can be empty.\n    pub fn id(&self) -> &str {\n        &self.id\n    }\n\n    /// Element's transform.\n    ///\n    /// This is a relative transform. The one that is set via the `transform` attribute in SVG.\n    pub fn transform(&self) -> Transform {\n        self.transform\n    }\n\n    /// Element's absolute transform.\n    ///\n    /// Contains all ancestors transforms including group's transform.\n    ///\n    /// Note that subroots, like clipPaths, masks and patterns, have their own root transform,\n    /// which isn't affected by the node that references this subroot.\n    pub fn abs_transform(&self) -> Transform {\n        self.abs_transform\n    }\n\n    /// Group opacity.\n    ///\n    /// After the group is rendered we should combine\n    /// it with a parent group using the specified opacity.\n    pub fn opacity(&self) -> Opacity {\n        self.opacity\n    }\n\n    /// Group blend mode.\n    ///\n    /// `mix-blend-mode` in SVG.\n    pub fn blend_mode(&self) -> BlendMode {\n        self.blend_mode\n    }\n\n    /// Group isolation.\n    ///\n    /// `isolation` in SVG.\n    pub fn isolate(&self) -> bool {\n        self.isolate\n    }\n\n    /// Element's clip path.\n    pub fn clip_path(&self) -> Option<&ClipPath> {\n        self.clip_path.as_deref()\n    }\n\n    /// Element's mask.\n    pub fn mask(&self) -> Option<&Mask> {\n        self.mask.as_deref()\n    }\n\n    /// Element's filters.\n    pub fn filters(&self) -> &[Arc<filter::Filter>] {\n        &self.filters\n    }\n\n    /// Element's object bounding box.\n    ///\n    /// `objectBoundingBox` in SVG terms. Meaning it doesn't affected by parent transforms.\n    ///\n    /// Can be set to `None` in case of an empty group.\n    pub fn bounding_box(&self) -> Rect {\n        self.bounding_box\n    }\n\n    /// Element's bounding box in canvas coordinates.\n    ///\n    /// `userSpaceOnUse` in SVG terms.\n    pub fn abs_bounding_box(&self) -> Rect {\n        self.abs_bounding_box\n    }\n\n    /// Element's object bounding box including stroke.\n    ///\n    /// Similar to `bounding_box`, but includes stroke.\n    pub fn stroke_bounding_box(&self) -> Rect {\n        self.stroke_bounding_box\n    }\n\n    /// Element's bounding box including stroke in user coordinates.\n    ///\n    /// Similar to `abs_bounding_box`, but includes stroke.\n    pub fn abs_stroke_bounding_box(&self) -> Rect {\n        self.abs_stroke_bounding_box\n    }\n\n    /// Element's \"layer\" bounding box in object units.\n    ///\n    /// Conceptually, this is `stroke_bounding_box` expanded and/or clipped\n    /// by `filters_bounding_box`, but also including all the children.\n    /// This is the bounding box `resvg` will later use to allocate layers/pixmaps\n    /// during isolated groups rendering.\n    ///\n    /// Only groups have it, because only groups can have filters.\n    /// For other nodes layer bounding box is the same as stroke bounding box.\n    ///\n    /// Unlike other bounding boxes, cannot have zero size.\n    ///\n    /// Returns 0x0x1x1 for empty groups.\n    pub fn layer_bounding_box(&self) -> NonZeroRect {\n        self.layer_bounding_box\n    }\n\n    /// Element's \"layer\" bounding box in canvas units.\n    pub fn abs_layer_bounding_box(&self) -> NonZeroRect {\n        self.abs_layer_bounding_box\n    }\n\n    /// Group's children.\n    pub fn children(&self) -> &[Node] {\n        &self.children\n    }\n\n    /// Checks if this group should be isolated during rendering.\n    pub fn should_isolate(&self) -> bool {\n        self.isolate\n            || self.opacity != Opacity::ONE\n            || self.clip_path.is_some()\n            || self.mask.is_some()\n            || !self.filters.is_empty()\n            || self.blend_mode != BlendMode::Normal // TODO: probably not needed?\n    }\n\n    /// Returns `true` if the group has any children.\n    pub fn has_children(&self) -> bool {\n        !self.children.is_empty()\n    }\n\n    /// Calculates a node's filter bounding box.\n    ///\n    /// Filters with `objectBoundingBox` and missing or zero `bounding_box` would be ignored.\n    ///\n    /// Note that a filter region can act like a clipping rectangle,\n    /// therefore this function can produce a bounding box smaller than `bounding_box`.\n    ///\n    /// Returns `None` when then group has no filters.\n    ///\n    /// This function is very fast, that's why we do not store this bbox as a `Group` field.\n    pub fn filters_bounding_box(&self) -> Option<NonZeroRect> {\n        let mut full_region = BBox::default();\n        for filter in &self.filters {\n            full_region = full_region.expand(filter.rect);\n        }\n\n        full_region.to_non_zero_rect()\n    }\n\n    fn subroots(&self, f: &mut dyn FnMut(&Group)) {\n        if let Some(ref clip) = self.clip_path {\n            f(&clip.root);\n\n            if let Some(ref sub_clip) = clip.clip_path {\n                f(&sub_clip.root);\n            }\n        }\n\n        if let Some(ref mask) = self.mask {\n            f(&mask.root);\n\n            if let Some(ref sub_mask) = mask.mask {\n                f(&sub_mask.root);\n            }\n        }\n\n        for filter in &self.filters {\n            for primitive in &filter.primitives {\n                if let filter::Kind::Image(ref image) = primitive.kind {\n                    f(image.root());\n                }\n            }\n        }\n    }\n}\n\n/// Representation of the [`paint-order`] property.\n///\n/// `usvg` will handle `markers` automatically,\n/// therefore we provide only `fill` and `stroke` variants.\n///\n/// [`paint-order`]: https://www.w3.org/TR/SVG2/painting.html#PaintOrder\n#[derive(Clone, Copy, PartialEq, Debug)]\n#[allow(missing_docs)]\npub enum PaintOrder {\n    FillAndStroke,\n    StrokeAndFill,\n}\n\nimpl Default for PaintOrder {\n    fn default() -> Self {\n        Self::FillAndStroke\n    }\n}\n\n/// A path element.\n#[derive(Clone, Debug)]\npub struct Path {\n    pub(crate) id: String,\n    pub(crate) visible: bool,\n    pub(crate) fill: Option<Fill>,\n    pub(crate) stroke: Option<Stroke>,\n    pub(crate) paint_order: PaintOrder,\n    pub(crate) rendering_mode: ShapeRendering,\n    pub(crate) data: Arc<tiny_skia_path::Path>,\n    pub(crate) abs_transform: Transform,\n    pub(crate) bounding_box: Rect,\n    pub(crate) abs_bounding_box: Rect,\n    pub(crate) stroke_bounding_box: Rect,\n    pub(crate) abs_stroke_bounding_box: Rect,\n}\n\nimpl Path {\n    pub(crate) fn new_simple(data: Arc<tiny_skia_path::Path>) -> Option<Self> {\n        Self::new(\n            String::new(),\n            true,\n            None,\n            None,\n            PaintOrder::default(),\n            ShapeRendering::default(),\n            data,\n            Transform::default(),\n        )\n    }\n\n    pub(crate) fn new(\n        id: String,\n        visible: bool,\n        fill: Option<Fill>,\n        stroke: Option<Stroke>,\n        paint_order: PaintOrder,\n        rendering_mode: ShapeRendering,\n        data: Arc<tiny_skia_path::Path>,\n        abs_transform: Transform,\n    ) -> Option<Self> {\n        let bounding_box = data.compute_tight_bounds()?;\n        let stroke_bounding_box =\n            Path::calculate_stroke_bbox(stroke.as_ref(), &data).unwrap_or(bounding_box);\n\n        let abs_bounding_box: Rect;\n        let abs_stroke_bounding_box: Rect;\n        if abs_transform.has_skew() {\n            // TODO: avoid re-alloc\n            let path2 = data.as_ref().clone();\n            let path2 = path2.transform(abs_transform)?;\n            abs_bounding_box = path2.compute_tight_bounds()?;\n            abs_stroke_bounding_box =\n                Path::calculate_stroke_bbox(stroke.as_ref(), &path2).unwrap_or(abs_bounding_box);\n        } else {\n            // A transform without a skew can be performed just on a bbox.\n            abs_bounding_box = bounding_box.transform(abs_transform)?;\n            abs_stroke_bounding_box = stroke_bounding_box.transform(abs_transform)?;\n        }\n\n        Some(Path {\n            id,\n            visible,\n            fill,\n            stroke,\n            paint_order,\n            rendering_mode,\n            data,\n            abs_transform,\n            bounding_box,\n            abs_bounding_box,\n            stroke_bounding_box,\n            abs_stroke_bounding_box,\n        })\n    }\n\n    /// Element's ID.\n    ///\n    /// Taken from the SVG itself.\n    /// Isn't automatically generated.\n    /// Can be empty.\n    pub fn id(&self) -> &str {\n        &self.id\n    }\n\n    /// Element visibility.\n    pub fn is_visible(&self) -> bool {\n        self.visible\n    }\n\n    /// Fill style.\n    pub fn fill(&self) -> Option<&Fill> {\n        self.fill.as_ref()\n    }\n\n    /// Stroke style.\n    pub fn stroke(&self) -> Option<&Stroke> {\n        self.stroke.as_ref()\n    }\n\n    /// Fill and stroke paint order.\n    ///\n    /// Since markers will be replaced with regular nodes automatically,\n    /// `usvg` doesn't provide the `markers` order type. It's was already done.\n    ///\n    /// `paint-order` in SVG.\n    pub fn paint_order(&self) -> PaintOrder {\n        self.paint_order\n    }\n\n    /// Rendering mode.\n    ///\n    /// `shape-rendering` in SVG.\n    pub fn rendering_mode(&self) -> ShapeRendering {\n        self.rendering_mode\n    }\n\n    // TODO: find a better name\n    /// Segments list.\n    ///\n    /// All segments are in absolute coordinates.\n    pub fn data(&self) -> &tiny_skia_path::Path {\n        self.data.as_ref()\n    }\n\n    /// Element's absolute transform.\n    ///\n    /// Contains all ancestors transforms including elements's transform.\n    ///\n    /// Note that this is not the relative transform present in SVG.\n    /// The SVG one would be set only on groups.\n    pub fn abs_transform(&self) -> Transform {\n        self.abs_transform\n    }\n\n    /// Element's object bounding box.\n    ///\n    /// `objectBoundingBox` in SVG terms. Meaning it doesn't affected by parent transforms.\n    pub fn bounding_box(&self) -> Rect {\n        self.bounding_box\n    }\n\n    /// Element's bounding box in canvas coordinates.\n    ///\n    /// `userSpaceOnUse` in SVG terms.\n    pub fn abs_bounding_box(&self) -> Rect {\n        self.abs_bounding_box\n    }\n\n    /// Element's object bounding box including stroke.\n    ///\n    /// Will have the same value as `bounding_box` when path has no stroke.\n    pub fn stroke_bounding_box(&self) -> Rect {\n        self.stroke_bounding_box\n    }\n\n    /// Element's bounding box including stroke in canvas coordinates.\n    ///\n    /// Will have the same value as `abs_bounding_box` when path has no stroke.\n    pub fn abs_stroke_bounding_box(&self) -> Rect {\n        self.abs_stroke_bounding_box\n    }\n\n    fn calculate_stroke_bbox(stroke: Option<&Stroke>, path: &tiny_skia_path::Path) -> Option<Rect> {\n        let mut stroke = stroke?.to_tiny_skia();\n        // According to the spec, dash should not be accounted during bbox calculation.\n        stroke.dash = None;\n\n        // TODO: avoid for round and bevel caps\n\n        // Expensive, but there is not much we can do about it.\n        if let Some(stroked_path) = path.stroke(&stroke, 1.0) {\n            return stroked_path.compute_tight_bounds();\n        }\n\n        None\n    }\n\n    fn subroots(&self, f: &mut dyn FnMut(&Group)) {\n        if let Some(Paint::Pattern(patt)) = self.fill.as_ref().map(|f| &f.paint) {\n            f(patt.root());\n        }\n        if let Some(Paint::Pattern(patt)) = self.stroke.as_ref().map(|f| &f.paint) {\n            f(patt.root());\n        }\n    }\n}\n\n/// An embedded image kind.\n#[derive(Clone)]\npub enum ImageKind {\n    /// A reference to raw JPEG data. Should be decoded by the caller.\n    JPEG(Arc<Vec<u8>>),\n    /// A reference to raw PNG data. Should be decoded by the caller.\n    PNG(Arc<Vec<u8>>),\n    /// A reference to raw GIF data. Should be decoded by the caller.\n    GIF(Arc<Vec<u8>>),\n    /// A reference to raw WebP data. Should be decoded by the caller.\n    WEBP(Arc<Vec<u8>>),\n    /// A preprocessed SVG tree. Can be rendered as is.\n    SVG(Tree),\n}\n\nimpl ImageKind {\n    pub(crate) fn actual_size(&self) -> Option<Size> {\n        match self {\n            ImageKind::JPEG(data)\n            | ImageKind::PNG(data)\n            | ImageKind::GIF(data)\n            | ImageKind::WEBP(data) => imagesize::blob_size(data)\n                .ok()\n                .and_then(|size| Size::from_wh(size.width as f32, size.height as f32))\n                .log_none(|| log::warn!(\"Image has an invalid size. Skipped.\")),\n            ImageKind::SVG(svg) => Some(svg.size),\n        }\n    }\n}\n\nimpl std::fmt::Debug for ImageKind {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        match self {\n            ImageKind::JPEG(_) => f.write_str(\"ImageKind::JPEG(..)\"),\n            ImageKind::PNG(_) => f.write_str(\"ImageKind::PNG(..)\"),\n            ImageKind::GIF(_) => f.write_str(\"ImageKind::GIF(..)\"),\n            ImageKind::WEBP(_) => f.write_str(\"ImageKind::WEBP(..)\"),\n            ImageKind::SVG(_) => f.write_str(\"ImageKind::SVG(..)\"),\n        }\n    }\n}\n\n/// A raster image element.\n///\n/// `image` element in SVG.\n#[derive(Clone, Debug)]\npub struct Image {\n    pub(crate) id: String,\n    pub(crate) visible: bool,\n    pub(crate) size: Size,\n    pub(crate) rendering_mode: ImageRendering,\n    pub(crate) kind: ImageKind,\n    pub(crate) abs_transform: Transform,\n    pub(crate) abs_bounding_box: NonZeroRect,\n}\n\nimpl Image {\n    /// Element's ID.\n    ///\n    /// Taken from the SVG itself.\n    /// Isn't automatically generated.\n    /// Can be empty.\n    pub fn id(&self) -> &str {\n        &self.id\n    }\n\n    /// Element visibility.\n    pub fn is_visible(&self) -> bool {\n        self.visible\n    }\n\n    /// The actual image size.\n    ///\n    /// This is not `width` and `height` attributes,\n    /// but rather the actual PNG/JPEG/GIF/SVG image size.\n    pub fn size(&self) -> Size {\n        self.size\n    }\n\n    /// Rendering mode.\n    ///\n    /// `image-rendering` in SVG.\n    pub fn rendering_mode(&self) -> ImageRendering {\n        self.rendering_mode\n    }\n\n    /// Image data.\n    pub fn kind(&self) -> &ImageKind {\n        &self.kind\n    }\n\n    /// Element's absolute transform.\n    ///\n    /// Contains all ancestors transforms including elements's transform.\n    ///\n    /// Note that this is not the relative transform present in SVG.\n    /// The SVG one would be set only on groups.\n    pub fn abs_transform(&self) -> Transform {\n        self.abs_transform\n    }\n\n    /// Element's object bounding box.\n    ///\n    /// `objectBoundingBox` in SVG terms. Meaning it doesn't affected by parent transforms.\n    pub fn bounding_box(&self) -> Rect {\n        self.size.to_rect(0.0, 0.0).unwrap()\n    }\n\n    /// Element's bounding box in canvas coordinates.\n    ///\n    /// `userSpaceOnUse` in SVG terms.\n    pub fn abs_bounding_box(&self) -> Rect {\n        self.abs_bounding_box.to_rect()\n    }\n\n    fn subroots(&self, f: &mut dyn FnMut(&Group)) {\n        if let ImageKind::SVG(ref tree) = self.kind {\n            f(&tree.root);\n        }\n    }\n}\n\n/// A nodes tree container.\n#[allow(missing_debug_implementations)]\n#[derive(Clone, Debug)]\npub struct Tree {\n    pub(crate) size: Size,\n    pub(crate) root: Group,\n    pub(crate) linear_gradients: Vec<Arc<LinearGradient>>,\n    pub(crate) radial_gradients: Vec<Arc<RadialGradient>>,\n    pub(crate) patterns: Vec<Arc<Pattern>>,\n    pub(crate) clip_paths: Vec<Arc<ClipPath>>,\n    pub(crate) masks: Vec<Arc<Mask>>,\n    pub(crate) filters: Vec<Arc<filter::Filter>>,\n    #[cfg(feature = \"text\")]\n    pub(crate) fontdb: Arc<fontdb::Database>,\n}\n\nimpl Tree {\n    /// Image size.\n    ///\n    /// Size of an image that should be created to fit the SVG.\n    ///\n    /// `width` and `height` in SVG.\n    pub fn size(&self) -> Size {\n        self.size\n    }\n\n    /// The root element of the SVG tree.\n    pub fn root(&self) -> &Group {\n        &self.root\n    }\n\n    /// Returns a renderable node by ID.\n    ///\n    /// If an empty ID is provided, than this method will always return `None`.\n    pub fn node_by_id(&self, id: &str) -> Option<&Node> {\n        if id.is_empty() {\n            return None;\n        }\n\n        node_by_id(&self.root, id)\n    }\n\n    /// Checks if the current tree has any text nodes.\n    pub fn has_text_nodes(&self) -> bool {\n        has_text_nodes(&self.root)\n    }\n\n    /// Checks if the current tree has any `defs` nodes.\n    pub fn has_defs_nodes(&self) -> bool {\n        !self.linear_gradients().is_empty()\n            || !self.radial_gradients().is_empty()\n            || !self.patterns().is_empty()\n            || !self.filters().is_empty()\n            || !self.clip_paths().is_empty()\n            || !self.masks().is_empty()\n    }\n\n    /// Returns a list of all unique [`LinearGradient`]s in the tree.\n    pub fn linear_gradients(&self) -> &[Arc<LinearGradient>] {\n        &self.linear_gradients\n    }\n\n    /// Returns a list of all unique [`RadialGradient`]s in the tree.\n    pub fn radial_gradients(&self) -> &[Arc<RadialGradient>] {\n        &self.radial_gradients\n    }\n\n    /// Returns a list of all unique [`Pattern`]s in the tree.\n    pub fn patterns(&self) -> &[Arc<Pattern>] {\n        &self.patterns\n    }\n\n    /// Returns a list of all unique [`ClipPath`]s in the tree.\n    pub fn clip_paths(&self) -> &[Arc<ClipPath>] {\n        &self.clip_paths\n    }\n\n    /// Returns a list of all unique [`Mask`]s in the tree.\n    pub fn masks(&self) -> &[Arc<Mask>] {\n        &self.masks\n    }\n\n    /// Returns a list of all unique [`Filter`](filter::Filter)s in the tree.\n    pub fn filters(&self) -> &[Arc<filter::Filter>] {\n        &self.filters\n    }\n\n    /// Returns the font database that applies to all text nodes in the tree.\n    #[cfg(feature = \"text\")]\n    pub fn fontdb(&self) -> &Arc<fontdb::Database> {\n        &self.fontdb\n    }\n\n    pub(crate) fn collect_paint_servers(&mut self) {\n        loop_over_paint_servers(&self.root, &mut |paint| match paint {\n            Paint::Color(_) => {}\n            Paint::LinearGradient(lg) => {\n                if !self\n                    .linear_gradients\n                    .iter()\n                    .any(|other| Arc::ptr_eq(lg, other))\n                {\n                    self.linear_gradients.push(lg.clone());\n                }\n            }\n            Paint::RadialGradient(rg) => {\n                if !self\n                    .radial_gradients\n                    .iter()\n                    .any(|other| Arc::ptr_eq(rg, other))\n                {\n                    self.radial_gradients.push(rg.clone());\n                }\n            }\n            Paint::Pattern(patt) => {\n                if !self.patterns.iter().any(|other| Arc::ptr_eq(patt, other)) {\n                    self.patterns.push(patt.clone());\n                }\n            }\n        });\n    }\n}\n\nfn node_by_id<'a>(parent: &'a Group, id: &str) -> Option<&'a Node> {\n    for child in &parent.children {\n        if child.id() == id {\n            return Some(child);\n        }\n\n        if let Node::Group(g) = child {\n            if let Some(n) = node_by_id(g, id) {\n                return Some(n);\n            }\n        }\n    }\n\n    None\n}\n\nfn has_text_nodes(root: &Group) -> bool {\n    for node in &root.children {\n        if let Node::Text(_) = node {\n            return true;\n        }\n\n        let mut has_text = false;\n\n        if let Node::Image(image) = node {\n            if let ImageKind::SVG(tree) = &image.kind {\n                if has_text_nodes(&tree.root) {\n                    has_text = true;\n                }\n            }\n        }\n\n        node.subroots(|subroot| has_text |= has_text_nodes(subroot));\n\n        if has_text {\n            return true;\n        }\n    }\n\n    false\n}\n\nfn loop_over_paint_servers(parent: &Group, f: &mut dyn FnMut(&Paint)) {\n    fn push(paint: Option<&Paint>, f: &mut dyn FnMut(&Paint)) {\n        if let Some(paint) = paint {\n            f(paint);\n        }\n    }\n\n    for node in &parent.children {\n        match node {\n            Node::Group(group) => loop_over_paint_servers(group, f),\n            Node::Path(path) => {\n                push(path.fill.as_ref().map(|f| &f.paint), f);\n                push(path.stroke.as_ref().map(|f| &f.paint), f);\n            }\n            Node::Image(_) => {}\n            // Flattened text would be used instead.\n            Node::Text(_) => {}\n        }\n\n        node.subroots(|subroot| loop_over_paint_servers(subroot, f));\n    }\n}\n\nimpl Group {\n    pub(crate) fn collect_clip_paths(&self, clip_paths: &mut Vec<Arc<ClipPath>>) {\n        for node in self.children() {\n            if let Node::Group(g) = node {\n                if let Some(clip) = &g.clip_path {\n                    if !clip_paths.iter().any(|other| Arc::ptr_eq(clip, other)) {\n                        clip_paths.push(clip.clone());\n                    }\n\n                    if let Some(sub_clip) = &clip.clip_path {\n                        if !clip_paths.iter().any(|other| Arc::ptr_eq(sub_clip, other)) {\n                            clip_paths.push(sub_clip.clone());\n                        }\n                    }\n                }\n            }\n\n            node.subroots(|subroot| subroot.collect_clip_paths(clip_paths));\n\n            if let Node::Group(g) = node {\n                g.collect_clip_paths(clip_paths);\n            }\n        }\n    }\n\n    pub(crate) fn collect_masks(&self, masks: &mut Vec<Arc<Mask>>) {\n        for node in self.children() {\n            if let Node::Group(g) = node {\n                if let Some(mask) = &g.mask {\n                    if !masks.iter().any(|other| Arc::ptr_eq(mask, other)) {\n                        masks.push(mask.clone());\n                    }\n\n                    if let Some(sub_mask) = &mask.mask {\n                        if !masks.iter().any(|other| Arc::ptr_eq(sub_mask, other)) {\n                            masks.push(sub_mask.clone());\n                        }\n                    }\n                }\n            }\n\n            node.subroots(|subroot| subroot.collect_masks(masks));\n\n            if let Node::Group(g) = node {\n                g.collect_masks(masks);\n            }\n        }\n    }\n\n    pub(crate) fn collect_filters(&self, filters: &mut Vec<Arc<filter::Filter>>) {\n        for node in self.children() {\n            if let Node::Group(g) = node {\n                for filter in g.filters() {\n                    if !filters.iter().any(|other| Arc::ptr_eq(filter, other)) {\n                        filters.push(filter.clone());\n                    }\n                }\n            }\n\n            node.subroots(|subroot| subroot.collect_filters(filters));\n\n            if let Node::Group(g) = node {\n                g.collect_filters(filters);\n            }\n        }\n    }\n\n    pub(crate) fn calculate_object_bbox(&mut self) -> Option<NonZeroRect> {\n        let mut bbox = BBox::default();\n        for child in &self.children {\n            let mut c_bbox = child.bounding_box();\n            if let Node::Group(group) = child {\n                if let Some(r) = c_bbox.transform(group.transform) {\n                    c_bbox = r;\n                }\n            }\n\n            bbox = bbox.expand(c_bbox);\n        }\n\n        bbox.to_non_zero_rect()\n    }\n\n    pub(crate) fn calculate_bounding_boxes(&mut self) -> Option<()> {\n        let mut bbox = BBox::default();\n        let mut abs_bbox = BBox::default();\n        let mut stroke_bbox = BBox::default();\n        let mut abs_stroke_bbox = BBox::default();\n        let mut layer_bbox = BBox::default();\n        for child in &self.children {\n            {\n                let mut c_bbox = child.bounding_box();\n                if let Node::Group(group) = child {\n                    if let Some(r) = c_bbox.transform(group.transform) {\n                        c_bbox = r;\n                    }\n                }\n\n                bbox = bbox.expand(c_bbox);\n            }\n\n            abs_bbox = abs_bbox.expand(child.abs_bounding_box());\n\n            {\n                let mut c_bbox = child.stroke_bounding_box();\n                if let Node::Group(group) = child {\n                    if let Some(r) = c_bbox.transform(group.transform) {\n                        c_bbox = r;\n                    }\n                }\n\n                stroke_bbox = stroke_bbox.expand(c_bbox);\n            }\n\n            abs_stroke_bbox = abs_stroke_bbox.expand(child.abs_stroke_bounding_box());\n\n            if let Node::Group(group) = child {\n                let r = group.layer_bounding_box;\n                if let Some(r) = r.transform(group.transform) {\n                    layer_bbox = layer_bbox.expand(r);\n                }\n            } else {\n                // Not a group - no need to transform.\n                layer_bbox = layer_bbox.expand(child.stroke_bounding_box());\n            }\n        }\n\n        // `bbox` can be None for empty groups, but we still have to\n        // calculate `layer_bounding_box after` it.\n        if let Some(bbox) = bbox.to_rect() {\n            self.bounding_box = bbox;\n            self.abs_bounding_box = abs_bbox.to_rect()?;\n            self.stroke_bounding_box = stroke_bbox.to_rect()?;\n            self.abs_stroke_bounding_box = abs_stroke_bbox.to_rect()?;\n        }\n\n        // Filter bbox has a higher priority than layers bbox.\n        if let Some(filter_bbox) = self.filters_bounding_box() {\n            self.layer_bounding_box = filter_bbox;\n        } else {\n            self.layer_bounding_box = layer_bbox.to_non_zero_rect()?;\n        }\n\n        self.abs_layer_bounding_box = self.layer_bounding_box.transform(self.abs_transform)?;\n\n        Some(())\n    }\n}\n"
  },
  {
    "path": "crates/usvg/src/tree/text.rs",
    "content": "// Copyright 2018 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse std::sync::Arc;\n\nuse strict_num::NonZeroPositiveF32;\npub use svgtypes::FontFamily;\n\n#[cfg(feature = \"text\")]\nuse crate::layout::Span;\nuse crate::{Fill, Group, NonEmptyString, PaintOrder, Rect, Stroke, TextRendering, Transform};\n\n/// A font stretch property.\n#[allow(missing_docs)]\n#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)]\npub enum FontStretch {\n    UltraCondensed,\n    ExtraCondensed,\n    Condensed,\n    SemiCondensed,\n    Normal,\n    SemiExpanded,\n    Expanded,\n    ExtraExpanded,\n    UltraExpanded,\n}\n\nimpl Default for FontStretch {\n    #[inline]\n    fn default() -> Self {\n        Self::Normal\n    }\n}\n\n#[cfg(feature = \"text\")]\nimpl From<fontdb::Stretch> for FontStretch {\n    fn from(stretch: fontdb::Stretch) -> Self {\n        match stretch {\n            fontdb::Stretch::UltraCondensed => FontStretch::UltraCondensed,\n            fontdb::Stretch::ExtraCondensed => FontStretch::ExtraCondensed,\n            fontdb::Stretch::Condensed => FontStretch::Condensed,\n            fontdb::Stretch::SemiCondensed => FontStretch::SemiCondensed,\n            fontdb::Stretch::Normal => FontStretch::Normal,\n            fontdb::Stretch::SemiExpanded => FontStretch::SemiExpanded,\n            fontdb::Stretch::Expanded => FontStretch::Expanded,\n            fontdb::Stretch::ExtraExpanded => FontStretch::ExtraExpanded,\n            fontdb::Stretch::UltraExpanded => FontStretch::UltraExpanded,\n        }\n    }\n}\n\n#[cfg(feature = \"text\")]\nimpl From<FontStretch> for fontdb::Stretch {\n    fn from(stretch: FontStretch) -> Self {\n        match stretch {\n            FontStretch::UltraCondensed => fontdb::Stretch::UltraCondensed,\n            FontStretch::ExtraCondensed => fontdb::Stretch::ExtraCondensed,\n            FontStretch::Condensed => fontdb::Stretch::Condensed,\n            FontStretch::SemiCondensed => fontdb::Stretch::SemiCondensed,\n            FontStretch::Normal => fontdb::Stretch::Normal,\n            FontStretch::SemiExpanded => fontdb::Stretch::SemiExpanded,\n            FontStretch::Expanded => fontdb::Stretch::Expanded,\n            FontStretch::ExtraExpanded => fontdb::Stretch::ExtraExpanded,\n            FontStretch::UltraExpanded => fontdb::Stretch::UltraExpanded,\n        }\n    }\n}\n\n/// A font variation axis setting.\n///\n/// Used for variable fonts to specify axis values like weight, width, etc.\n#[derive(Clone, Copy, Debug)]\npub struct FontVariation {\n    /// The 4-byte axis tag (e.g., b\"wght\" for weight).\n    pub tag: [u8; 4],\n    /// The axis value.\n    pub value: f32,\n}\n\nimpl FontVariation {\n    /// Creates a new font variation.\n    pub fn new(tag: [u8; 4], value: f32) -> Self {\n        Self { tag, value }\n    }\n}\n\nimpl PartialEq for FontVariation {\n    fn eq(&self, other: &Self) -> bool {\n        self.tag == other.tag && self.value.to_bits() == other.value.to_bits()\n    }\n}\n\nimpl Eq for FontVariation {}\n\nimpl std::hash::Hash for FontVariation {\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        self.tag.hash(state);\n        self.value.to_bits().hash(state);\n    }\n}\n\n/// A font style property.\n#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]\npub enum FontStyle {\n    /// A face that is neither italic not obliqued.\n    Normal,\n    /// A form that is generally cursive in nature.\n    Italic,\n    /// A typically-sloped version of the regular face.\n    Oblique,\n}\n\nimpl Default for FontStyle {\n    #[inline]\n    fn default() -> FontStyle {\n        Self::Normal\n    }\n}\n\n#[cfg(feature = \"text\")]\nimpl From<fontdb::Style> for FontStyle {\n    fn from(style: fontdb::Style) -> Self {\n        match style {\n            fontdb::Style::Normal => FontStyle::Normal,\n            fontdb::Style::Italic => FontStyle::Italic,\n            fontdb::Style::Oblique => FontStyle::Oblique,\n        }\n    }\n}\n\n#[cfg(feature = \"text\")]\nimpl From<FontStyle> for fontdb::Style {\n    fn from(style: FontStyle) -> Self {\n        match style {\n            FontStyle::Normal => fontdb::Style::Normal,\n            FontStyle::Italic => fontdb::Style::Italic,\n            FontStyle::Oblique => fontdb::Style::Oblique,\n        }\n    }\n}\n\n/// Text font properties.\n#[derive(Clone, Eq, PartialEq, Hash, Debug)]\npub struct Font {\n    pub(crate) families: Vec<FontFamily>,\n    pub(crate) style: FontStyle,\n    pub(crate) stretch: FontStretch,\n    pub(crate) weight: u16,\n    pub(crate) variations: Vec<FontVariation>,\n}\n\nimpl Font {\n    /// A list of family names.\n    ///\n    /// Never empty. Uses `usvg::Options::font_family` as fallback.\n    pub fn families(&self) -> &[FontFamily] {\n        &self.families\n    }\n\n    /// A font style.\n    pub fn style(&self) -> FontStyle {\n        self.style\n    }\n\n    /// A font stretch.\n    pub fn stretch(&self) -> FontStretch {\n        self.stretch\n    }\n\n    /// A font width.\n    pub fn weight(&self) -> u16 {\n        self.weight\n    }\n\n    /// Font variation settings for variable fonts.\n    pub fn variations(&self) -> &[FontVariation] {\n        &self.variations\n    }\n}\n\n/// A dominant baseline property.\n#[allow(missing_docs)]\n#[derive(Clone, Copy, PartialEq, Debug)]\npub enum DominantBaseline {\n    Auto,\n    UseScript,\n    NoChange,\n    ResetSize,\n    Ideographic,\n    Alphabetic,\n    Hanging,\n    Mathematical,\n    Central,\n    Middle,\n    TextAfterEdge,\n    TextBeforeEdge,\n}\n\nimpl Default for DominantBaseline {\n    fn default() -> Self {\n        Self::Auto\n    }\n}\n\n/// An alignment baseline property.\n#[allow(missing_docs)]\n#[derive(Clone, Copy, PartialEq, Debug)]\npub enum AlignmentBaseline {\n    Auto,\n    Baseline,\n    BeforeEdge,\n    TextBeforeEdge,\n    Middle,\n    Central,\n    AfterEdge,\n    TextAfterEdge,\n    Ideographic,\n    Alphabetic,\n    Hanging,\n    Mathematical,\n}\n\nimpl Default for AlignmentBaseline {\n    fn default() -> Self {\n        Self::Auto\n    }\n}\n\n/// A baseline shift property.\n#[allow(missing_docs)]\n#[derive(Clone, Copy, PartialEq, Debug)]\npub enum BaselineShift {\n    Baseline,\n    Subscript,\n    Superscript,\n    Number(f32),\n}\n\nimpl Default for BaselineShift {\n    #[inline]\n    fn default() -> BaselineShift {\n        BaselineShift::Baseline\n    }\n}\n\n/// A length adjust property.\n#[allow(missing_docs)]\n#[derive(Clone, Copy, PartialEq, Debug)]\npub enum LengthAdjust {\n    Spacing,\n    SpacingAndGlyphs,\n}\n\nimpl Default for LengthAdjust {\n    fn default() -> Self {\n        Self::Spacing\n    }\n}\n\n/// A font optical sizing property.\n///\n/// Controls automatic adjustment of the `opsz` axis in variable fonts\n/// based on font size. Matches CSS `font-optical-sizing`.\n#[derive(Clone, Copy, PartialEq, Debug)]\npub enum FontOpticalSizing {\n    /// Automatically set `opsz` to match font size (browser default).\n    Auto,\n    /// Do not automatically adjust `opsz`.\n    None,\n}\n\nimpl Default for FontOpticalSizing {\n    fn default() -> Self {\n        Self::Auto\n    }\n}\n\n/// A text span decoration style.\n///\n/// In SVG, text decoration and text it's applied to can have different styles.\n/// So you can have black text and green underline.\n///\n/// Also, in SVG you can specify text decoration stroking.\n#[derive(Clone, Debug)]\npub struct TextDecorationStyle {\n    pub(crate) fill: Option<Fill>,\n    pub(crate) stroke: Option<Stroke>,\n}\n\nimpl TextDecorationStyle {\n    /// A fill style.\n    pub fn fill(&self) -> Option<&Fill> {\n        self.fill.as_ref()\n    }\n\n    /// A stroke style.\n    pub fn stroke(&self) -> Option<&Stroke> {\n        self.stroke.as_ref()\n    }\n}\n\n/// A text span decoration.\n#[derive(Clone, Debug)]\npub struct TextDecoration {\n    pub(crate) underline: Option<TextDecorationStyle>,\n    pub(crate) overline: Option<TextDecorationStyle>,\n    pub(crate) line_through: Option<TextDecorationStyle>,\n}\n\nimpl TextDecoration {\n    /// An optional underline and its style.\n    pub fn underline(&self) -> Option<&TextDecorationStyle> {\n        self.underline.as_ref()\n    }\n\n    /// An optional overline and its style.\n    pub fn overline(&self) -> Option<&TextDecorationStyle> {\n        self.overline.as_ref()\n    }\n\n    /// An optional line-through and its style.\n    pub fn line_through(&self) -> Option<&TextDecorationStyle> {\n        self.line_through.as_ref()\n    }\n}\n\n/// A text style span.\n///\n/// Spans do not overlap inside a text chunk.\n#[derive(Clone, Debug)]\npub struct TextSpan {\n    pub(crate) start: usize,\n    pub(crate) end: usize,\n    pub(crate) fill: Option<Fill>,\n    pub(crate) stroke: Option<Stroke>,\n    pub(crate) paint_order: PaintOrder,\n    pub(crate) font: Font,\n    pub(crate) font_size: NonZeroPositiveF32,\n    pub(crate) small_caps: bool,\n    pub(crate) apply_kerning: bool,\n    pub(crate) font_optical_sizing: FontOpticalSizing,\n    pub(crate) decoration: TextDecoration,\n    pub(crate) dominant_baseline: DominantBaseline,\n    pub(crate) alignment_baseline: AlignmentBaseline,\n    pub(crate) baseline_shift: Vec<BaselineShift>,\n    pub(crate) visible: bool,\n    pub(crate) letter_spacing: f32,\n    pub(crate) word_spacing: f32,\n    pub(crate) text_length: Option<f32>,\n    pub(crate) length_adjust: LengthAdjust,\n}\n\nimpl TextSpan {\n    /// A span start in bytes.\n    ///\n    /// Offset is relative to the parent text chunk and not the parent text element.\n    pub fn start(&self) -> usize {\n        self.start\n    }\n\n    /// A span end in bytes.\n    ///\n    /// Offset is relative to the parent text chunk and not the parent text element.\n    pub fn end(&self) -> usize {\n        self.end\n    }\n\n    /// A fill style.\n    pub fn fill(&self) -> Option<&Fill> {\n        self.fill.as_ref()\n    }\n\n    /// A stroke style.\n    pub fn stroke(&self) -> Option<&Stroke> {\n        self.stroke.as_ref()\n    }\n\n    /// A paint order style.\n    pub fn paint_order(&self) -> PaintOrder {\n        self.paint_order\n    }\n\n    /// A font.\n    pub fn font(&self) -> &Font {\n        &self.font\n    }\n\n    /// A font size.\n    pub fn font_size(&self) -> NonZeroPositiveF32 {\n        self.font_size\n    }\n\n    /// Indicates that small caps should be used.\n    ///\n    /// Set by `font-variant=\"small-caps\"`\n    pub fn small_caps(&self) -> bool {\n        self.small_caps\n    }\n\n    /// Indicates that a kerning should be applied.\n    ///\n    /// Supports both `kerning` and `font-kerning` properties.\n    pub fn apply_kerning(&self) -> bool {\n        self.apply_kerning\n    }\n\n    /// Font optical sizing mode.\n    ///\n    /// When `Auto` (default), the `opsz` axis will be automatically set\n    /// to match the font size for variable fonts that support it.\n    /// This matches the CSS `font-optical-sizing: auto` behavior.\n    pub fn font_optical_sizing(&self) -> FontOpticalSizing {\n        self.font_optical_sizing\n    }\n\n    /// A span decorations.\n    pub fn decoration(&self) -> &TextDecoration {\n        &self.decoration\n    }\n\n    /// A span dominant baseline.\n    pub fn dominant_baseline(&self) -> DominantBaseline {\n        self.dominant_baseline\n    }\n\n    /// A span alignment baseline.\n    pub fn alignment_baseline(&self) -> AlignmentBaseline {\n        self.alignment_baseline\n    }\n\n    /// A list of all baseline shift that should be applied to this span.\n    ///\n    /// Ordered from `text` element down to the actual `span` element.\n    pub fn baseline_shift(&self) -> &[BaselineShift] {\n        &self.baseline_shift\n    }\n\n    /// A visibility property.\n    pub fn is_visible(&self) -> bool {\n        self.visible\n    }\n\n    /// A letter spacing property.\n    pub fn letter_spacing(&self) -> f32 {\n        self.letter_spacing\n    }\n\n    /// A word spacing property.\n    pub fn word_spacing(&self) -> f32 {\n        self.word_spacing\n    }\n\n    /// A text length property.\n    pub fn text_length(&self) -> Option<f32> {\n        self.text_length\n    }\n\n    /// A length adjust property.\n    pub fn length_adjust(&self) -> LengthAdjust {\n        self.length_adjust\n    }\n}\n\n/// A text chunk anchor property.\n#[allow(missing_docs)]\n#[derive(Clone, Copy, PartialEq, Debug)]\npub enum TextAnchor {\n    Start,\n    Middle,\n    End,\n}\n\nimpl Default for TextAnchor {\n    fn default() -> Self {\n        Self::Start\n    }\n}\n\n/// A path used by text-on-path.\n#[derive(Debug)]\npub struct TextPath {\n    pub(crate) id: NonEmptyString,\n    pub(crate) start_offset: f32,\n    pub(crate) path: Arc<tiny_skia_path::Path>,\n}\n\nimpl TextPath {\n    /// Element's ID.\n    ///\n    /// Taken from the SVG itself.\n    pub fn id(&self) -> &str {\n        self.id.get()\n    }\n\n    /// A text offset in SVG coordinates.\n    ///\n    /// Percentage values already resolved.\n    pub fn start_offset(&self) -> f32 {\n        self.start_offset\n    }\n\n    /// A path.\n    pub fn path(&self) -> &tiny_skia_path::Path {\n        &self.path\n    }\n}\n\n/// A text chunk flow property.\n#[derive(Clone, Debug)]\npub enum TextFlow {\n    /// A linear layout.\n    ///\n    /// Includes left-to-right, right-to-left and top-to-bottom.\n    Linear,\n    /// A text-on-path layout.\n    Path(Arc<TextPath>),\n}\n\n/// A text chunk.\n///\n/// Text alignment and BIDI reordering can only be done inside a text chunk.\n#[derive(Clone, Debug)]\npub struct TextChunk {\n    pub(crate) x: Option<f32>,\n    pub(crate) y: Option<f32>,\n    pub(crate) anchor: TextAnchor,\n    pub(crate) spans: Vec<TextSpan>,\n    pub(crate) text_flow: TextFlow,\n    pub(crate) text: String,\n}\n\nimpl TextChunk {\n    /// An absolute X axis offset.\n    pub fn x(&self) -> Option<f32> {\n        self.x\n    }\n\n    /// An absolute Y axis offset.\n    pub fn y(&self) -> Option<f32> {\n        self.y\n    }\n\n    /// A text anchor.\n    pub fn anchor(&self) -> TextAnchor {\n        self.anchor\n    }\n\n    /// A list of text chunk style spans.\n    pub fn spans(&self) -> &[TextSpan] {\n        &self.spans\n    }\n\n    /// A text chunk flow.\n    pub fn text_flow(&self) -> TextFlow {\n        self.text_flow.clone()\n    }\n\n    /// A text chunk actual text.\n    pub fn text(&self) -> &str {\n        &self.text\n    }\n}\n\n/// A writing mode.\n#[allow(missing_docs)]\n#[derive(Clone, Copy, PartialEq, Debug)]\npub enum WritingMode {\n    LeftToRight,\n    TopToBottom,\n}\n\n/// A text element.\n///\n/// `text` element in SVG.\n#[derive(Clone, Debug)]\npub struct Text {\n    pub(crate) id: String,\n    pub(crate) rendering_mode: TextRendering,\n    pub(crate) dx: Vec<f32>,\n    pub(crate) dy: Vec<f32>,\n    pub(crate) rotate: Vec<f32>,\n    pub(crate) writing_mode: WritingMode,\n    pub(crate) chunks: Vec<TextChunk>,\n    pub(crate) abs_transform: Transform,\n    pub(crate) bounding_box: Rect,\n    pub(crate) abs_bounding_box: Rect,\n    pub(crate) stroke_bounding_box: Rect,\n    pub(crate) abs_stroke_bounding_box: Rect,\n    pub(crate) flattened: Box<Group>,\n    #[cfg(feature = \"text\")]\n    pub(crate) layouted: Vec<Span>,\n}\n\nimpl Text {\n    /// Element's ID.\n    ///\n    /// Taken from the SVG itself.\n    /// Isn't automatically generated.\n    /// Can be empty.\n    pub fn id(&self) -> &str {\n        &self.id\n    }\n\n    /// Rendering mode.\n    ///\n    /// `text-rendering` in SVG.\n    pub fn rendering_mode(&self) -> TextRendering {\n        self.rendering_mode\n    }\n\n    /// A relative X axis offsets.\n    ///\n    /// One offset for each Unicode codepoint. Aka `char` in Rust.\n    pub fn dx(&self) -> &[f32] {\n        &self.dx\n    }\n\n    /// A relative Y axis offsets.\n    ///\n    /// One offset for each Unicode codepoint. Aka `char` in Rust.\n    pub fn dy(&self) -> &[f32] {\n        &self.dy\n    }\n\n    /// A list of rotation angles.\n    ///\n    /// One angle for each Unicode codepoint. Aka `char` in Rust.\n    pub fn rotate(&self) -> &[f32] {\n        &self.rotate\n    }\n\n    /// A writing mode.\n    pub fn writing_mode(&self) -> WritingMode {\n        self.writing_mode\n    }\n\n    /// A list of text chunks.\n    pub fn chunks(&self) -> &[TextChunk] {\n        &self.chunks\n    }\n\n    /// Element's absolute transform.\n    ///\n    /// Contains all ancestors transforms including elements's transform.\n    ///\n    /// Note that this is not the relative transform present in SVG.\n    /// The SVG one would be set only on groups.\n    pub fn abs_transform(&self) -> Transform {\n        self.abs_transform\n    }\n\n    /// Element's text bounding box.\n    ///\n    /// Text bounding box is special in SVG and doesn't represent\n    /// tight bounds of the element's content.\n    /// You can find more about it\n    /// [here](https://razrfalcon.github.io/notes-on-svg-parsing/text/bbox.html).\n    ///\n    /// `objectBoundingBox` in SVG terms. Meaning it isn't affected by parent transforms.\n    ///\n    /// Returns `None` when the `text` build feature was disabled.\n    /// This is because we have to perform a text layout before calculating a bounding box.\n    pub fn bounding_box(&self) -> Rect {\n        self.bounding_box\n    }\n\n    /// Element's text bounding box in canvas coordinates.\n    ///\n    /// `userSpaceOnUse` in SVG terms.\n    pub fn abs_bounding_box(&self) -> Rect {\n        self.abs_bounding_box\n    }\n\n    /// Element's object bounding box including stroke.\n    ///\n    /// Similar to `bounding_box`, but includes stroke.\n    ///\n    /// Will have the same value as `bounding_box` when path has no stroke.\n    pub fn stroke_bounding_box(&self) -> Rect {\n        self.stroke_bounding_box\n    }\n\n    /// Element's bounding box including stroke in canvas coordinates.\n    pub fn abs_stroke_bounding_box(&self) -> Rect {\n        self.abs_stroke_bounding_box\n    }\n\n    /// Text converted into paths, ready to render.\n    ///\n    /// Note that this is only a\n    /// \"best-effort\" attempt: The text will be converted into group/paths/image\n    /// primitives, so that they can be rendered with the existing infrastructure.\n    /// This process is in general lossless and should lead to correct output, with\n    /// two notable exceptions:\n    /// 1. For glyphs based on the `SVG` table, only glyphs that are pure SVG 1.1/2.0\n    ///    are supported. Glyphs that make use of features in the OpenType specification\n    ///    that are not part of the original SVG specification are not supported.\n    /// 2. For glyphs based on the `COLR` table, there are a certain number of features\n    ///    that are not (correctly) supported, such as conical\n    ///    gradients, certain gradient transforms and some blend modes. But this shouldn't\n    ///    cause any issues in 95% of the cases, as most of those are edge cases.\n    ///    If the two above are not acceptable, then you will need to implement your own\n    ///    glyph rendering logic based on the layouted glyphs (see the `layouted` method).\n    pub fn flattened(&self) -> &Group {\n        &self.flattened\n    }\n\n    /// The positioned glyphs and decoration spans of the text.\n    ///\n    /// This should only be used if you need more low-level access\n    /// to the glyphs that make up the text. If you just need the\n    /// outlines of the text, you should use `flattened` instead.\n    #[cfg(feature = \"text\")]\n    pub fn layouted(&self) -> &[Span] {\n        &self.layouted\n    }\n\n    pub(crate) fn subroots(&self, f: &mut dyn FnMut(&Group)) {\n        f(&self.flattened);\n    }\n}\n"
  },
  {
    "path": "crates/usvg/src/writer.rs",
    "content": "// Copyright 2023 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse std::fmt::Display;\nuse std::io::Write;\n\nuse svgtypes::{FontFamily, parse_font_families};\nuse xmlwriter::XmlWriter;\n\nuse crate::parser::{AId, EId};\nuse crate::*;\n\nimpl Tree {\n    /// Writes `usvg::Tree` back to SVG.\n    pub fn to_string(&self, opt: &WriteOptions) -> String {\n        convert(self, opt)\n    }\n}\n\n/// Checks that type has a default value.\ntrait IsDefault: Default {\n    /// Checks that type has a default value.\n    fn is_default(&self) -> bool;\n}\n\nimpl<T: Default + PartialEq + Copy> IsDefault for T {\n    #[inline]\n    fn is_default(&self) -> bool {\n        *self == Self::default()\n    }\n}\n\n/// XML writing options.\n#[derive(Clone, Debug)]\npub struct WriteOptions {\n    /// Used to add a custom prefix to each element ID during writing.\n    pub id_prefix: Option<String>,\n\n    /// Do not convert text into paths.\n    ///\n    /// Default: false\n    pub preserve_text: bool,\n\n    /// Set the coordinates numeric precision.\n    ///\n    /// Smaller precision can lead to a malformed output in some cases.\n    ///\n    /// Default: 8\n    pub coordinates_precision: u8,\n\n    /// Set the transform values numeric precision.\n    ///\n    /// Smaller precision can lead to a malformed output in some cases.\n    ///\n    /// Default: 8\n    pub transforms_precision: u8,\n\n    /// Use single quote marks instead of double quote.\n    ///\n    /// # Examples\n    ///\n    /// Before:\n    ///\n    /// ```text\n    /// <rect fill=\"red\"/>\n    /// ```\n    ///\n    /// After:\n    ///\n    /// ```text\n    /// <rect fill='red'/>\n    /// ```\n    ///\n    /// Default: disabled\n    pub use_single_quote: bool,\n\n    /// Set XML nodes indention.\n    ///\n    /// # Examples\n    ///\n    /// `Indent::None`\n    /// Before:\n    ///\n    /// ```text\n    /// <svg>\n    ///     <rect fill=\"red\"/>\n    /// </svg>\n    /// ```\n    ///\n    /// After:\n    ///\n    /// ```text\n    /// <svg><rect fill=\"red\"/></svg>\n    /// ```\n    ///\n    /// Default: 4 spaces\n    pub indent: Indent,\n\n    /// Set XML attributes indention.\n    ///\n    /// # Examples\n    ///\n    /// `Indent::Spaces(2)`\n    ///\n    /// Before:\n    ///\n    /// ```text\n    /// <svg>\n    ///     <rect fill=\"red\" stroke=\"black\"/>\n    /// </svg>\n    /// ```\n    ///\n    /// After:\n    ///\n    /// ```text\n    /// <svg>\n    ///     <rect\n    ///       fill=\"red\"\n    ///       stroke=\"black\"/>\n    /// </svg>\n    /// ```\n    ///\n    /// Default: `None`\n    pub attributes_indent: Indent,\n}\n\nimpl Default for WriteOptions {\n    fn default() -> Self {\n        Self {\n            id_prefix: Default::default(),\n            preserve_text: false,\n            coordinates_precision: 8,\n            transforms_precision: 8,\n            use_single_quote: false,\n            indent: Indent::Spaces(4),\n            attributes_indent: Indent::None,\n        }\n    }\n}\n\npub(crate) fn convert(tree: &Tree, opt: &WriteOptions) -> String {\n    let mut xml = XmlWriter::new(xmlwriter::Options {\n        use_single_quote: opt.use_single_quote,\n        indent: opt.indent,\n        attributes_indent: opt.attributes_indent,\n    });\n\n    xml.start_svg_element(EId::Svg);\n    xml.write_svg_attribute(AId::Width, &tree.size.width());\n    xml.write_svg_attribute(AId::Height, &tree.size.height());\n    xml.write_attribute(\"xmlns\", \"http://www.w3.org/2000/svg\");\n    if has_xlink(&tree.root) {\n        xml.write_attribute(\"xmlns:xlink\", \"http://www.w3.org/1999/xlink\");\n    }\n\n    let has_text_paths = has_text_paths(&tree.root);\n    if tree.has_defs_nodes() || has_text_paths {\n        write_defs(tree, opt, &mut xml, has_text_paths);\n    }\n\n    write_elements(&tree.root, false, opt, &mut xml);\n\n    xml.end_document()\n}\n\nfn write_filters(tree: &Tree, opt: &WriteOptions, xml: &mut XmlWriter) {\n    let mut written_fe_image_nodes: Vec<String> = Vec::new();\n    for filter in tree.filters() {\n        for fe in &filter.primitives {\n            if let filter::Kind::Image(ref img) = fe.kind {\n                if let Some(child) = img.root().children.first() {\n                    if !written_fe_image_nodes.iter().any(|id| id == child.id()) {\n                        write_element(child, false, opt, xml);\n                        written_fe_image_nodes.push(child.id().to_string());\n                    }\n                }\n            }\n        }\n\n        xml.start_svg_element(EId::Filter);\n        xml.write_id_attribute(filter.id(), opt);\n        xml.write_rect_attrs(filter.rect);\n        xml.write_units(\n            AId::FilterUnits,\n            Units::UserSpaceOnUse,\n            Units::ObjectBoundingBox,\n        );\n\n        for fe in &filter.primitives {\n            match fe.kind {\n                filter::Kind::DropShadow(ref shadow) => {\n                    xml.start_svg_element(EId::FeDropShadow);\n                    xml.write_filter_primitive_attrs(filter.rect(), fe);\n                    xml.write_filter_input(AId::In, &shadow.input);\n                    xml.write_attribute_fmt(\n                        AId::StdDeviation.to_str(),\n                        format_args!(\"{} {}\", shadow.std_dev_x.get(), shadow.std_dev_y.get()),\n                    );\n                    xml.write_svg_attribute(AId::Dx, &shadow.dx);\n                    xml.write_svg_attribute(AId::Dy, &shadow.dy);\n                    xml.write_color(AId::FloodColor, shadow.color);\n                    xml.write_svg_attribute(AId::FloodOpacity, &shadow.opacity.get());\n                    xml.write_svg_attribute(AId::Result, &fe.result);\n                    xml.end_element();\n                }\n                filter::Kind::GaussianBlur(ref blur) => {\n                    xml.start_svg_element(EId::FeGaussianBlur);\n                    xml.write_filter_primitive_attrs(filter.rect(), fe);\n                    xml.write_filter_input(AId::In, &blur.input);\n                    xml.write_attribute_fmt(\n                        AId::StdDeviation.to_str(),\n                        format_args!(\"{} {}\", blur.std_dev_x.get(), blur.std_dev_y.get()),\n                    );\n                    xml.write_svg_attribute(AId::Result, &fe.result);\n                    xml.end_element();\n                }\n                filter::Kind::Offset(ref offset) => {\n                    xml.start_svg_element(EId::FeOffset);\n                    xml.write_filter_primitive_attrs(filter.rect(), fe);\n                    xml.write_filter_input(AId::In, &offset.input);\n                    xml.write_svg_attribute(AId::Dx, &offset.dx);\n                    xml.write_svg_attribute(AId::Dy, &offset.dy);\n                    xml.write_svg_attribute(AId::Result, &fe.result);\n                    xml.end_element();\n                }\n                filter::Kind::Blend(ref blend) => {\n                    xml.start_svg_element(EId::FeBlend);\n                    xml.write_filter_primitive_attrs(filter.rect(), fe);\n                    xml.write_filter_input(AId::In, &blend.input1);\n                    xml.write_filter_input(AId::In2, &blend.input2);\n                    xml.write_svg_attribute(AId::Mode, &blend.mode.to_string());\n                    xml.write_svg_attribute(AId::Result, &fe.result);\n                    xml.end_element();\n                }\n                filter::Kind::Flood(ref flood) => {\n                    xml.start_svg_element(EId::FeFlood);\n                    xml.write_filter_primitive_attrs(filter.rect(), fe);\n                    xml.write_color(AId::FloodColor, flood.color);\n                    xml.write_svg_attribute(AId::FloodOpacity, &flood.opacity.get());\n                    xml.write_svg_attribute(AId::Result, &fe.result);\n                    xml.end_element();\n                }\n                filter::Kind::Composite(ref composite) => {\n                    xml.start_svg_element(EId::FeComposite);\n                    xml.write_filter_primitive_attrs(filter.rect(), fe);\n                    xml.write_filter_input(AId::In, &composite.input1);\n                    xml.write_filter_input(AId::In2, &composite.input2);\n                    xml.write_svg_attribute(\n                        AId::Operator,\n                        match composite.operator {\n                            filter::CompositeOperator::Over => \"over\",\n                            filter::CompositeOperator::In => \"in\",\n                            filter::CompositeOperator::Out => \"out\",\n                            filter::CompositeOperator::Atop => \"atop\",\n                            filter::CompositeOperator::Xor => \"xor\",\n                            filter::CompositeOperator::Arithmetic { .. } => \"arithmetic\",\n                        },\n                    );\n\n                    if let filter::CompositeOperator::Arithmetic { k1, k2, k3, k4 } =\n                        composite.operator\n                    {\n                        xml.write_svg_attribute(AId::K1, &k1);\n                        xml.write_svg_attribute(AId::K2, &k2);\n                        xml.write_svg_attribute(AId::K3, &k3);\n                        xml.write_svg_attribute(AId::K4, &k4);\n                    }\n\n                    xml.write_svg_attribute(AId::Result, &fe.result);\n                    xml.end_element();\n                }\n                filter::Kind::Merge(ref merge) => {\n                    xml.start_svg_element(EId::FeMerge);\n                    xml.write_filter_primitive_attrs(filter.rect(), fe);\n                    xml.write_svg_attribute(AId::Result, &fe.result);\n                    for input in &merge.inputs {\n                        xml.start_svg_element(EId::FeMergeNode);\n                        xml.write_filter_input(AId::In, input);\n                        xml.end_element();\n                    }\n\n                    xml.end_element();\n                }\n                filter::Kind::Tile(ref tile) => {\n                    xml.start_svg_element(EId::FeTile);\n                    xml.write_filter_primitive_attrs(filter.rect(), fe);\n                    xml.write_filter_input(AId::In, &tile.input);\n                    xml.write_svg_attribute(AId::Result, &fe.result);\n                    xml.end_element();\n                }\n                filter::Kind::Image(ref img) => {\n                    xml.start_svg_element(EId::FeImage);\n                    xml.write_filter_primitive_attrs(filter.rect(), fe);\n                    if let Some(child) = img.root.children.first() {\n                        let prefix = opt.id_prefix.as_deref().unwrap_or_default();\n                        xml.write_attribute_fmt(\n                            \"xlink:href\",\n                            format_args!(\"#{}{}\", prefix, child.id()),\n                        );\n                    }\n\n                    xml.write_svg_attribute(AId::Result, &fe.result);\n                    xml.end_element();\n                }\n                filter::Kind::ComponentTransfer(ref transfer) => {\n                    xml.start_svg_element(EId::FeComponentTransfer);\n                    xml.write_filter_primitive_attrs(filter.rect(), fe);\n                    xml.write_filter_input(AId::In, &transfer.input);\n                    xml.write_svg_attribute(AId::Result, &fe.result);\n\n                    xml.write_filter_transfer_function(EId::FeFuncR, &transfer.func_r);\n                    xml.write_filter_transfer_function(EId::FeFuncG, &transfer.func_g);\n                    xml.write_filter_transfer_function(EId::FeFuncB, &transfer.func_b);\n                    xml.write_filter_transfer_function(EId::FeFuncA, &transfer.func_a);\n\n                    xml.end_element();\n                }\n                filter::Kind::ColorMatrix(ref matrix) => {\n                    xml.start_svg_element(EId::FeColorMatrix);\n                    xml.write_filter_primitive_attrs(filter.rect(), fe);\n                    xml.write_filter_input(AId::In, &matrix.input);\n                    xml.write_svg_attribute(AId::Result, &fe.result);\n\n                    match matrix.kind {\n                        filter::ColorMatrixKind::Matrix(ref values) => {\n                            xml.write_svg_attribute(AId::Type, \"matrix\");\n                            xml.write_numbers(AId::Values, values);\n                        }\n                        filter::ColorMatrixKind::Saturate(value) => {\n                            xml.write_svg_attribute(AId::Type, \"saturate\");\n                            xml.write_svg_attribute(AId::Values, &value.get());\n                        }\n                        filter::ColorMatrixKind::HueRotate(angle) => {\n                            xml.write_svg_attribute(AId::Type, \"hueRotate\");\n                            xml.write_svg_attribute(AId::Values, &angle);\n                        }\n                        filter::ColorMatrixKind::LuminanceToAlpha => {\n                            xml.write_svg_attribute(AId::Type, \"luminanceToAlpha\");\n                        }\n                    }\n\n                    xml.end_element();\n                }\n                filter::Kind::ConvolveMatrix(ref matrix) => {\n                    xml.start_svg_element(EId::FeConvolveMatrix);\n                    xml.write_filter_primitive_attrs(filter.rect(), fe);\n                    xml.write_filter_input(AId::In, &matrix.input);\n                    xml.write_svg_attribute(AId::Result, &fe.result);\n\n                    xml.write_attribute_fmt(\n                        AId::Order.to_str(),\n                        format_args!(\"{} {}\", matrix.matrix.columns, matrix.matrix.rows),\n                    );\n                    xml.write_numbers(AId::KernelMatrix, &matrix.matrix.data);\n                    xml.write_svg_attribute(AId::Divisor, &matrix.divisor.get());\n                    xml.write_svg_attribute(AId::Bias, &matrix.bias);\n                    xml.write_svg_attribute(AId::TargetX, &matrix.matrix.target_x);\n                    xml.write_svg_attribute(AId::TargetY, &matrix.matrix.target_y);\n                    xml.write_svg_attribute(\n                        AId::EdgeMode,\n                        match matrix.edge_mode {\n                            filter::EdgeMode::None => \"none\",\n                            filter::EdgeMode::Duplicate => \"duplicate\",\n                            filter::EdgeMode::Wrap => \"wrap\",\n                        },\n                    );\n                    xml.write_svg_attribute(\n                        AId::PreserveAlpha,\n                        if matrix.preserve_alpha {\n                            \"true\"\n                        } else {\n                            \"false\"\n                        },\n                    );\n\n                    xml.end_element();\n                }\n                filter::Kind::Morphology(ref morphology) => {\n                    xml.start_svg_element(EId::FeMorphology);\n                    xml.write_filter_primitive_attrs(filter.rect(), fe);\n                    xml.write_filter_input(AId::In, &morphology.input);\n                    xml.write_svg_attribute(AId::Result, &fe.result);\n\n                    xml.write_svg_attribute(\n                        AId::Operator,\n                        match morphology.operator {\n                            filter::MorphologyOperator::Erode => \"erode\",\n                            filter::MorphologyOperator::Dilate => \"dilate\",\n                        },\n                    );\n                    xml.write_attribute_fmt(\n                        AId::Radius.to_str(),\n                        format_args!(\n                            \"{} {}\",\n                            morphology.radius_x.get(),\n                            morphology.radius_y.get()\n                        ),\n                    );\n\n                    xml.end_element();\n                }\n                filter::Kind::DisplacementMap(ref map) => {\n                    xml.start_svg_element(EId::FeDisplacementMap);\n                    xml.write_filter_primitive_attrs(filter.rect(), fe);\n                    xml.write_filter_input(AId::In, &map.input1);\n                    xml.write_filter_input(AId::In2, &map.input2);\n                    xml.write_svg_attribute(AId::Result, &fe.result);\n\n                    xml.write_svg_attribute(AId::Scale, &map.scale);\n\n                    let mut write_channel = |c, aid| {\n                        xml.write_svg_attribute(\n                            aid,\n                            match c {\n                                filter::ColorChannel::R => \"R\",\n                                filter::ColorChannel::G => \"G\",\n                                filter::ColorChannel::B => \"B\",\n                                filter::ColorChannel::A => \"A\",\n                            },\n                        );\n                    };\n                    write_channel(map.x_channel_selector, AId::XChannelSelector);\n                    write_channel(map.y_channel_selector, AId::YChannelSelector);\n\n                    xml.end_element();\n                }\n                filter::Kind::Turbulence(ref turbulence) => {\n                    xml.start_svg_element(EId::FeTurbulence);\n                    xml.write_filter_primitive_attrs(filter.rect(), fe);\n                    xml.write_svg_attribute(AId::Result, &fe.result);\n\n                    xml.write_attribute_fmt(\n                        AId::BaseFrequency.to_str(),\n                        format_args!(\n                            \"{} {}\",\n                            turbulence.base_frequency_x.get(),\n                            turbulence.base_frequency_y.get()\n                        ),\n                    );\n                    xml.write_svg_attribute(AId::NumOctaves, &turbulence.num_octaves);\n                    xml.write_svg_attribute(AId::Seed, &turbulence.seed);\n                    xml.write_svg_attribute(\n                        AId::StitchTiles,\n                        match turbulence.stitch_tiles {\n                            true => \"stitch\",\n                            false => \"noStitch\",\n                        },\n                    );\n                    xml.write_svg_attribute(\n                        AId::Type,\n                        match turbulence.kind {\n                            filter::TurbulenceKind::FractalNoise => \"fractalNoise\",\n                            filter::TurbulenceKind::Turbulence => \"turbulence\",\n                        },\n                    );\n\n                    xml.end_element();\n                }\n                filter::Kind::DiffuseLighting(ref light) => {\n                    xml.start_svg_element(EId::FeDiffuseLighting);\n                    xml.write_filter_primitive_attrs(filter.rect(), fe);\n                    xml.write_svg_attribute(AId::Result, &fe.result);\n\n                    xml.write_svg_attribute(AId::SurfaceScale, &light.surface_scale);\n                    xml.write_svg_attribute(AId::DiffuseConstant, &light.diffuse_constant);\n                    xml.write_color(AId::LightingColor, light.lighting_color);\n                    write_light_source(&light.light_source, xml);\n\n                    xml.end_element();\n                }\n                filter::Kind::SpecularLighting(ref light) => {\n                    xml.start_svg_element(EId::FeSpecularLighting);\n                    xml.write_filter_primitive_attrs(filter.rect(), fe);\n                    xml.write_svg_attribute(AId::Result, &fe.result);\n\n                    xml.write_svg_attribute(AId::SurfaceScale, &light.surface_scale);\n                    xml.write_svg_attribute(AId::SpecularConstant, &light.specular_constant);\n                    xml.write_svg_attribute(AId::SpecularExponent, &light.specular_exponent);\n                    xml.write_color(AId::LightingColor, light.lighting_color);\n                    write_light_source(&light.light_source, xml);\n\n                    xml.end_element();\n                }\n            };\n        }\n\n        xml.end_element();\n    }\n}\n\nfn write_defs(tree: &Tree, opt: &WriteOptions, xml: &mut XmlWriter, write_text_paths: bool) {\n    xml.start_svg_element(EId::Defs);\n    for lg in tree.linear_gradients() {\n        xml.start_svg_element(EId::LinearGradient);\n        xml.write_id_attribute(lg.id(), opt);\n        xml.write_svg_attribute(AId::X1, &lg.x1);\n        xml.write_svg_attribute(AId::Y1, &lg.y1);\n        xml.write_svg_attribute(AId::X2, &lg.x2);\n        xml.write_svg_attribute(AId::Y2, &lg.y2);\n        write_base_grad(&lg.base, opt, xml);\n        xml.end_element();\n    }\n\n    for rg in tree.radial_gradients() {\n        xml.start_svg_element(EId::RadialGradient);\n        xml.write_id_attribute(rg.id(), opt);\n        xml.write_svg_attribute(AId::Cx, &rg.cx);\n        xml.write_svg_attribute(AId::Cy, &rg.cy);\n        xml.write_svg_attribute(AId::R, &rg.r.get());\n        xml.write_svg_attribute(AId::Fx, &rg.fx);\n        xml.write_svg_attribute(AId::Fy, &rg.fy);\n        write_base_grad(&rg.base, opt, xml);\n        xml.end_element();\n    }\n\n    for pattern in tree.patterns() {\n        xml.start_svg_element(EId::Pattern);\n        xml.write_id_attribute(pattern.id(), opt);\n        xml.write_rect_attrs(pattern.rect);\n        xml.write_units(AId::PatternUnits, pattern.units, Units::ObjectBoundingBox);\n        xml.write_units(\n            AId::PatternContentUnits,\n            pattern.content_units,\n            Units::UserSpaceOnUse,\n        );\n        xml.write_transform(AId::PatternTransform, pattern.transform, opt);\n\n        write_elements(&pattern.root, false, opt, xml);\n\n        xml.end_element();\n    }\n\n    if write_text_paths {\n        write_text_path_paths(&tree.root, opt, xml);\n    }\n\n    write_filters(tree, opt, xml);\n\n    for clip in tree.clip_paths() {\n        xml.start_svg_element(EId::ClipPath);\n        xml.write_id_attribute(clip.id(), opt);\n        xml.write_transform(AId::Transform, clip.transform, opt);\n\n        if let Some(ref clip) = clip.clip_path {\n            xml.write_func_iri(AId::ClipPath, clip.id(), opt);\n        }\n\n        write_elements(&clip.root, true, opt, xml);\n\n        xml.end_element();\n    }\n\n    for mask in tree.masks() {\n        xml.start_svg_element(EId::Mask);\n        xml.write_id_attribute(mask.id(), opt);\n        if mask.kind == MaskType::Alpha {\n            xml.write_svg_attribute(AId::MaskType, \"alpha\");\n        }\n        xml.write_units(\n            AId::MaskUnits,\n            Units::UserSpaceOnUse,\n            Units::ObjectBoundingBox,\n        );\n        xml.write_rect_attrs(mask.rect);\n\n        if let Some(ref mask) = mask.mask {\n            xml.write_func_iri(AId::Mask, mask.id(), opt);\n        }\n\n        write_elements(&mask.root, false, opt, xml);\n\n        xml.end_element();\n    }\n    xml.end_element(); // end EId::Defs\n}\n\nfn has_text_paths(parent: &Group) -> bool {\n    for node in &parent.children {\n        if let Node::Group(group) = node {\n            if has_text_paths(group) {\n                return true;\n            }\n        } else if let Node::Text(text) = node {\n            for chunk in &text.chunks {\n                if let TextFlow::Path(text_path) = &chunk.text_flow {\n                    let path = Path::new(\n                        text_path.id().to_string(),\n                        true,\n                        None,\n                        None,\n                        PaintOrder::default(),\n                        ShapeRendering::default(),\n                        text_path.path.clone(),\n                        Transform::default(),\n                    );\n                    if path.is_some() {\n                        return true;\n                    }\n                }\n            }\n        }\n        let mut need_path = false;\n        node.subroots(|subroot| {\n            if !need_path && has_text_paths(subroot) {\n                need_path = true;\n            }\n        });\n        if need_path {\n            return true;\n        }\n    }\n    false\n}\n\n/// Write the `path` elements for text paths.\nfn write_text_path_paths(parent: &Group, opt: &WriteOptions, xml: &mut XmlWriter) {\n    for node in &parent.children {\n        if let Node::Group(group) = node {\n            write_text_path_paths(group, opt, xml);\n        } else if let Node::Text(text) = node {\n            for chunk in &text.chunks {\n                if let TextFlow::Path(text_path) = &chunk.text_flow {\n                    let path = Path::new(\n                        text_path.id().to_string(),\n                        true,\n                        None,\n                        None,\n                        PaintOrder::default(),\n                        ShapeRendering::default(),\n                        text_path.path.clone(),\n                        Transform::default(),\n                    );\n                    if let Some(path) = &path {\n                        write_path(path, false, Transform::default(), None, opt, xml);\n                    }\n                }\n            }\n        }\n\n        node.subroots(|subroot| write_text_path_paths(subroot, opt, xml));\n    }\n}\n\nfn write_elements(parent: &Group, is_clip_path: bool, opt: &WriteOptions, xml: &mut XmlWriter) {\n    for n in &parent.children {\n        write_element(n, is_clip_path, opt, xml);\n    }\n}\n\nfn write_element(node: &Node, is_clip_path: bool, opt: &WriteOptions, xml: &mut XmlWriter) {\n    match node {\n        Node::Path(p) => {\n            write_path(p, is_clip_path, Transform::default(), None, opt, xml);\n        }\n        Node::Image(img) => {\n            xml.start_svg_element(EId::Image);\n            if !img.id.is_empty() {\n                xml.write_id_attribute(&img.id, opt);\n            }\n\n            xml.write_svg_attribute(AId::Width, &img.size().width());\n            xml.write_svg_attribute(AId::Height, &img.size().height());\n\n            xml.write_visibility(img.visible);\n\n            match img.rendering_mode {\n                ImageRendering::OptimizeQuality => {}\n                ImageRendering::OptimizeSpeed => {\n                    xml.write_svg_attribute(AId::ImageRendering, \"optimizeSpeed\");\n                }\n                ImageRendering::Smooth => {\n                    xml.write_attribute(AId::Style.to_str(), \"image-rendering:smooth\");\n                }\n                ImageRendering::HighQuality => {\n                    xml.write_attribute(AId::Style.to_str(), \"image-rendering:high-quality\");\n                }\n                ImageRendering::CrispEdges => {\n                    xml.write_attribute(AId::Style.to_str(), \"image-rendering:crisp-edges\");\n                }\n                ImageRendering::Pixelated => {\n                    xml.write_attribute(AId::Style.to_str(), \"image-rendering:pixelated\");\n                }\n            }\n\n            xml.write_image_data(&img.kind);\n\n            xml.end_element();\n        }\n        Node::Group(g) => {\n            write_group_element(g, is_clip_path, opt, xml);\n        }\n        Node::Text(text) => {\n            if opt.preserve_text {\n                xml.start_svg_element(EId::Text);\n\n                if !text.id.is_empty() {\n                    xml.write_id_attribute(&text.id, opt);\n                }\n\n                xml.write_attribute(\"xml:space\", \"preserve\");\n\n                match text.writing_mode {\n                    WritingMode::LeftToRight => {}\n                    WritingMode::TopToBottom => xml.write_svg_attribute(AId::WritingMode, \"tb\"),\n                }\n\n                match text.rendering_mode {\n                    TextRendering::OptimizeSpeed => {\n                        xml.write_svg_attribute(AId::TextRendering, \"optimizeSpeed\");\n                    }\n                    TextRendering::GeometricPrecision => {\n                        xml.write_svg_attribute(AId::TextRendering, \"geometricPrecision\");\n                    }\n                    TextRendering::OptimizeLegibility => {}\n                }\n\n                if text.rotate.iter().any(|r| *r != 0.0) {\n                    xml.write_numbers(AId::Rotate, &text.rotate);\n                }\n\n                if text.dx.iter().any(|dx| *dx != 0.0) {\n                    xml.write_numbers(AId::Dx, &text.dx);\n                }\n\n                if text.dy.iter().any(|dy| *dy != 0.0) {\n                    xml.write_numbers(AId::Dy, &text.dy);\n                }\n\n                xml.set_preserve_whitespaces(true);\n\n                for chunk in &text.chunks {\n                    if let TextFlow::Path(text_path) = &chunk.text_flow {\n                        xml.start_svg_element(EId::TextPath);\n\n                        let prefix = opt.id_prefix.as_deref().unwrap_or_default();\n                        xml.write_attribute_fmt(\n                            \"xlink:href\",\n                            format_args!(\"#{}{}\", prefix, text_path.id()),\n                        );\n\n                        if text_path.start_offset != 0.0 {\n                            xml.write_svg_attribute(AId::StartOffset, &text_path.start_offset);\n                        }\n                    }\n\n                    xml.start_svg_element(EId::Tspan);\n\n                    if let Some(x) = chunk.x {\n                        xml.write_svg_attribute(AId::X, &x);\n                    }\n\n                    if let Some(y) = chunk.y {\n                        xml.write_svg_attribute(AId::Y, &y);\n                    }\n\n                    match chunk.anchor {\n                        TextAnchor::Start => {}\n                        TextAnchor::Middle => xml.write_svg_attribute(AId::TextAnchor, \"middle\"),\n                        TextAnchor::End => xml.write_svg_attribute(AId::TextAnchor, \"end\"),\n                    }\n\n                    for span in &chunk.spans {\n                        let decorations: Vec<_> = [\n                            (\"underline\", &span.decoration.underline),\n                            (\"line-through\", &span.decoration.line_through),\n                            (\"overline\", &span.decoration.overline),\n                        ]\n                        .iter()\n                        .filter_map(|&(key, option_value)| {\n                            option_value.as_ref().map(|value| (key, value))\n                        })\n                        .collect();\n\n                        // Decorations need to be dumped BEFORE we write the actual span data\n                        // (so that for example stroke color of span doesn't affect the text\n                        // itself while baseline shifts need to be written after (since they are\n                        // affected by the font size)\n                        for (deco_name, deco) in &decorations {\n                            xml.start_svg_element(EId::Tspan);\n                            xml.write_svg_attribute(AId::TextDecoration, deco_name);\n                            write_fill(&deco.fill, false, opt, xml);\n                            write_stroke(&deco.stroke, opt, xml);\n                        }\n\n                        write_span(is_clip_path, opt, xml, chunk, span);\n\n                        // End for each tspan we needed to create for decorations\n                        for _ in &decorations {\n                            xml.end_element();\n                        }\n                    }\n                    xml.end_element();\n\n                    // End textPath element\n                    if matches!(&chunk.text_flow, TextFlow::Path(_)) {\n                        xml.end_element();\n                    }\n                }\n\n                xml.end_element();\n                xml.set_preserve_whitespaces(false);\n            } else {\n                write_group_element(text.flattened(), is_clip_path, opt, xml);\n            }\n        }\n    }\n}\n\nfn write_group_element(g: &Group, is_clip_path: bool, opt: &WriteOptions, xml: &mut XmlWriter) {\n    if is_clip_path {\n        // The `clipPath` element in SVG doesn't allow groups, only shapes and text.\n        // The problem is that in `usvg` we can set a `clip-path` only on groups.\n        // So in cases when a `clipPath` child has a `clip-path` as well,\n        // it would be inside a group. And we have to skip this group during writing.\n        //\n        // Basically, the following SVG:\n        //\n        // <clipPath id=\"clip\">\n        //   <path clip-path=\"url(#clip-nested)\"/>\n        // </clipPath>\n        //\n        // will be represented in usvg as:\n        //\n        // <clipPath id=\"clip\">\n        //   <g clip-path=\"url(#clip-nested)\">\n        //      <path/>\n        //   </g>\n        // </clipPath>\n        //\n        //\n        // Same with text. Text elements will be converted into groups,\n        // but only the group's children should be written.\n        for child in &g.children {\n            match child {\n                Node::Group(child_group) => {\n                    write_group_element(child_group, is_clip_path, opt, xml);\n                }\n                Node::Path(child_path) => {\n                    let clip_id = g.clip_path.as_ref().map(|cp| cp.id().to_string());\n                    write_path(\n                        child_path,\n                        is_clip_path,\n                        g.transform,\n                        clip_id.as_deref(),\n                        opt,\n                        xml,\n                    );\n                }\n                _ => {}\n            }\n        }\n        return;\n    }\n\n    xml.start_svg_element(EId::G);\n    if !g.id.is_empty() {\n        xml.write_id_attribute(&g.id, opt);\n    };\n\n    if let Some(ref clip) = g.clip_path {\n        xml.write_func_iri(AId::ClipPath, clip.id(), opt);\n    }\n\n    if let Some(ref mask) = g.mask {\n        xml.write_func_iri(AId::Mask, mask.id(), opt);\n    }\n\n    if !g.filters.is_empty() {\n        let prefix = opt.id_prefix.as_deref().unwrap_or_default();\n        let ids: Vec<_> = g\n            .filters\n            .iter()\n            .map(|filter| format!(\"url(#{}{})\", prefix, filter.id()))\n            .collect();\n        xml.write_svg_attribute(AId::Filter, &ids.join(\" \"));\n    }\n\n    if g.opacity != Opacity::ONE {\n        xml.write_svg_attribute(AId::Opacity, &g.opacity.get());\n    }\n\n    xml.write_transform(AId::Transform, g.transform, opt);\n\n    if g.blend_mode != BlendMode::Normal || g.isolate {\n        // For reasons unknown, `mix-blend-mode` and `isolation` must be written\n        // as `style` attribute.\n        let isolation = if g.isolate { \"isolate\" } else { \"auto\" };\n        xml.write_attribute_fmt(\n            AId::Style.to_str(),\n            format_args!(\"mix-blend-mode:{};isolation:{}\", g.blend_mode, isolation),\n        );\n    }\n\n    write_elements(g, false, opt, xml);\n\n    xml.end_element();\n}\n\ntrait XmlWriterExt {\n    fn start_svg_element(&mut self, id: EId);\n    fn write_svg_attribute<V: Display + ?Sized>(&mut self, id: AId, value: &V);\n    fn write_id_attribute(&mut self, id: &str, opt: &WriteOptions);\n    fn write_color(&mut self, id: AId, color: Color);\n    fn write_units(&mut self, id: AId, units: Units, def: Units);\n    fn write_transform(&mut self, id: AId, units: Transform, opt: &WriteOptions);\n    fn write_visibility(&mut self, value: bool);\n    fn write_func_iri(&mut self, aid: AId, id: &str, opt: &WriteOptions);\n    fn write_rect_attrs(&mut self, r: NonZeroRect);\n    fn write_numbers(&mut self, aid: AId, list: &[f32]);\n    fn write_image_data(&mut self, kind: &ImageKind);\n    fn write_filter_input(&mut self, id: AId, input: &filter::Input);\n    fn write_filter_primitive_attrs(&mut self, parent_rect: NonZeroRect, fe: &filter::Primitive);\n    fn write_filter_transfer_function(&mut self, eid: EId, fe: &filter::TransferFunction);\n}\n\nimpl XmlWriterExt for XmlWriter {\n    #[inline(never)]\n    fn start_svg_element(&mut self, id: EId) {\n        self.start_element(id.to_str());\n    }\n\n    #[inline(never)]\n    fn write_svg_attribute<V: Display + ?Sized>(&mut self, id: AId, value: &V) {\n        self.write_attribute(id.to_str(), value);\n    }\n\n    #[inline(never)]\n    fn write_id_attribute(&mut self, id: &str, opt: &WriteOptions) {\n        debug_assert!(!id.is_empty());\n\n        if let Some(ref prefix) = opt.id_prefix {\n            let full_id = format!(\"{}{}\", prefix, id);\n            self.write_attribute(\"id\", &full_id);\n        } else {\n            self.write_attribute(\"id\", id);\n        }\n    }\n\n    #[inline(never)]\n    fn write_color(&mut self, id: AId, c: Color) {\n        static CHARS: &[u8] = b\"0123456789abcdef\";\n\n        #[inline]\n        fn int2hex(n: u8) -> (u8, u8) {\n            (CHARS[(n >> 4) as usize], CHARS[(n & 0xf) as usize])\n        }\n\n        let (r1, r2) = int2hex(c.red);\n        let (g1, g2) = int2hex(c.green);\n        let (b1, b2) = int2hex(c.blue);\n\n        self.write_attribute_raw(id.to_str(), |buf| {\n            buf.extend_from_slice(&[b'#', r1, r2, g1, g2, b1, b2]);\n        });\n    }\n\n    // TODO: simplify\n    fn write_units(&mut self, id: AId, units: Units, def: Units) {\n        if units != def {\n            self.write_attribute(\n                id.to_str(),\n                match units {\n                    Units::UserSpaceOnUse => \"userSpaceOnUse\",\n                    Units::ObjectBoundingBox => \"objectBoundingBox\",\n                },\n            );\n        }\n    }\n\n    fn write_transform(&mut self, id: AId, ts: Transform, opt: &WriteOptions) {\n        if !ts.is_default() {\n            self.write_attribute_raw(id.to_str(), |buf| {\n                buf.extend_from_slice(b\"matrix(\");\n                write_num(ts.sx, buf, opt.transforms_precision);\n                buf.push(b' ');\n                write_num(ts.ky, buf, opt.transforms_precision);\n                buf.push(b' ');\n                write_num(ts.kx, buf, opt.transforms_precision);\n                buf.push(b' ');\n                write_num(ts.sy, buf, opt.transforms_precision);\n                buf.push(b' ');\n                write_num(ts.tx, buf, opt.transforms_precision);\n                buf.push(b' ');\n                write_num(ts.ty, buf, opt.transforms_precision);\n                buf.extend_from_slice(b\")\");\n            });\n        }\n    }\n\n    fn write_visibility(&mut self, value: bool) {\n        if !value {\n            self.write_attribute(AId::Visibility.to_str(), \"hidden\");\n        }\n    }\n\n    fn write_func_iri(&mut self, aid: AId, id: &str, opt: &WriteOptions) {\n        debug_assert!(!id.is_empty());\n        let prefix = opt.id_prefix.as_deref().unwrap_or_default();\n        self.write_attribute_fmt(aid.to_str(), format_args!(\"url(#{}{})\", prefix, id));\n    }\n\n    fn write_rect_attrs(&mut self, r: NonZeroRect) {\n        self.write_svg_attribute(AId::X, &r.x());\n        self.write_svg_attribute(AId::Y, &r.y());\n        self.write_svg_attribute(AId::Width, &r.width());\n        self.write_svg_attribute(AId::Height, &r.height());\n    }\n\n    fn write_numbers(&mut self, aid: AId, list: &[f32]) {\n        self.write_attribute_raw(aid.to_str(), |buf| {\n            for n in list {\n                buf.write_fmt(format_args!(\"{} \", n)).unwrap();\n            }\n\n            if !list.is_empty() {\n                buf.pop();\n            }\n        });\n    }\n\n    fn write_filter_input(&mut self, id: AId, input: &filter::Input) {\n        self.write_attribute(\n            id.to_str(),\n            match input {\n                filter::Input::SourceGraphic => \"SourceGraphic\",\n                filter::Input::SourceAlpha => \"SourceAlpha\",\n                filter::Input::Reference(s) => s,\n            },\n        );\n    }\n\n    fn write_filter_primitive_attrs(&mut self, parent_rect: NonZeroRect, fe: &filter::Primitive) {\n        if parent_rect.x() != fe.rect().x() {\n            self.write_svg_attribute(AId::X, &fe.rect().x());\n        }\n        if parent_rect.y() != fe.rect().y() {\n            self.write_svg_attribute(AId::Y, &fe.rect().y());\n        }\n        if parent_rect.width() != fe.rect().width() {\n            self.write_svg_attribute(AId::Width, &fe.rect().width());\n        }\n        if parent_rect.height() != fe.rect().height() {\n            self.write_svg_attribute(AId::Height, &fe.rect().height());\n        }\n\n        self.write_attribute(\n            AId::ColorInterpolationFilters.to_str(),\n            match fe.color_interpolation {\n                filter::ColorInterpolation::SRGB => \"sRGB\",\n                filter::ColorInterpolation::LinearRGB => \"linearRGB\",\n            },\n        );\n    }\n\n    fn write_filter_transfer_function(&mut self, eid: EId, fe: &filter::TransferFunction) {\n        self.start_svg_element(eid);\n\n        match fe {\n            filter::TransferFunction::Identity => {\n                self.write_svg_attribute(AId::Type, \"identity\");\n            }\n            filter::TransferFunction::Table(values) => {\n                self.write_svg_attribute(AId::Type, \"table\");\n                self.write_numbers(AId::TableValues, values);\n            }\n            filter::TransferFunction::Discrete(values) => {\n                self.write_svg_attribute(AId::Type, \"discrete\");\n                self.write_numbers(AId::TableValues, values);\n            }\n            filter::TransferFunction::Linear { slope, intercept } => {\n                self.write_svg_attribute(AId::Type, \"linear\");\n                self.write_svg_attribute(AId::Slope, &slope);\n                self.write_svg_attribute(AId::Intercept, &intercept);\n            }\n            filter::TransferFunction::Gamma {\n                amplitude,\n                exponent,\n                offset,\n            } => {\n                self.write_svg_attribute(AId::Type, \"gamma\");\n                self.write_svg_attribute(AId::Amplitude, &amplitude);\n                self.write_svg_attribute(AId::Exponent, &exponent);\n                self.write_svg_attribute(AId::Offset, &offset);\n            }\n        }\n\n        self.end_element();\n    }\n\n    fn write_image_data(&mut self, kind: &ImageKind) {\n        let svg_string;\n        let (mime, data) = match kind {\n            ImageKind::JPEG(data) => (\"jpeg\", data.as_slice()),\n            ImageKind::PNG(data) => (\"png\", data.as_slice()),\n            ImageKind::GIF(data) => (\"gif\", data.as_slice()),\n            ImageKind::WEBP(data) => (\"webp\", data.as_slice()),\n            ImageKind::SVG(tree) => {\n                svg_string = tree.to_string(&WriteOptions::default());\n                (\"svg+xml\", svg_string.as_bytes())\n            }\n        };\n\n        self.write_attribute_raw(\"xlink:href\", |buf| {\n            buf.extend_from_slice(b\"data:image/\");\n            buf.extend_from_slice(mime.as_bytes());\n            buf.extend_from_slice(b\";base64, \");\n\n            let mut enc =\n                base64::write::EncoderWriter::new(buf, &base64::engine::general_purpose::STANDARD);\n            enc.write_all(data).unwrap();\n            enc.finish().unwrap();\n        });\n    }\n}\n\nfn has_xlink(parent: &Group) -> bool {\n    for node in &parent.children {\n        match node {\n            Node::Group(g) => {\n                for filter in &g.filters {\n                    if filter\n                        .primitives\n                        .iter()\n                        .any(|p| matches!(p.kind, filter::Kind::Image(_)))\n                    {\n                        return true;\n                    }\n                }\n\n                if let Some(mask) = &g.mask {\n                    if has_xlink(mask.root()) {\n                        return true;\n                    }\n\n                    if let Some(sub_mask) = &mask.mask {\n                        if has_xlink(&sub_mask.root) {\n                            return true;\n                        }\n                    }\n                }\n\n                if has_xlink(g) {\n                    return true;\n                }\n            }\n            Node::Image(_) => {\n                return true;\n            }\n            Node::Text(text) => {\n                if text\n                    .chunks\n                    .iter()\n                    .any(|t| matches!(t.text_flow, TextFlow::Path(_)))\n                {\n                    return true;\n                }\n            }\n            _ => {}\n        }\n\n        let mut present = false;\n        node.subroots(|root| present |= has_xlink(root));\n        if present {\n            return true;\n        }\n    }\n\n    false\n}\n\nfn write_base_grad(g: &BaseGradient, opt: &WriteOptions, xml: &mut XmlWriter) {\n    xml.write_units(AId::GradientUnits, g.units, Units::ObjectBoundingBox);\n    xml.write_transform(AId::GradientTransform, g.transform, opt);\n\n    match g.spread_method {\n        SpreadMethod::Pad => {}\n        SpreadMethod::Reflect => xml.write_svg_attribute(AId::SpreadMethod, \"reflect\"),\n        SpreadMethod::Repeat => xml.write_svg_attribute(AId::SpreadMethod, \"repeat\"),\n    }\n\n    for s in &g.stops {\n        xml.start_svg_element(EId::Stop);\n        xml.write_svg_attribute(AId::Offset, &s.offset.get());\n        xml.write_color(AId::StopColor, s.color);\n        if s.opacity != Opacity::ONE {\n            xml.write_svg_attribute(AId::StopOpacity, &s.opacity.get());\n        }\n\n        xml.end_element();\n    }\n}\n\nfn write_path(\n    path: &Path,\n    is_clip_path: bool,\n    path_transform: Transform,\n    clip_path: Option<&str>,\n    opt: &WriteOptions,\n    xml: &mut XmlWriter,\n) {\n    xml.start_svg_element(EId::Path);\n    if !path.id.is_empty() {\n        xml.write_id_attribute(&path.id, opt);\n    }\n\n    write_fill(&path.fill, is_clip_path, opt, xml);\n    write_stroke(&path.stroke, opt, xml);\n\n    xml.write_visibility(path.visible);\n\n    if path.paint_order == PaintOrder::StrokeAndFill {\n        xml.write_svg_attribute(AId::PaintOrder, \"stroke\");\n    }\n\n    match path.rendering_mode {\n        ShapeRendering::OptimizeSpeed => {\n            xml.write_svg_attribute(AId::ShapeRendering, \"optimizeSpeed\");\n        }\n        ShapeRendering::CrispEdges => xml.write_svg_attribute(AId::ShapeRendering, \"crispEdges\"),\n        ShapeRendering::GeometricPrecision => {}\n    }\n\n    if let Some(id) = clip_path {\n        xml.write_func_iri(AId::ClipPath, id, opt);\n    }\n\n    xml.write_transform(AId::Transform, path_transform, opt);\n\n    xml.write_attribute_raw(\"d\", |buf| {\n        use tiny_skia_path::PathSegment;\n\n        for seg in path.data.segments() {\n            match seg {\n                PathSegment::MoveTo(p) => {\n                    buf.extend_from_slice(b\"M \");\n                    write_num(p.x, buf, opt.coordinates_precision);\n                    buf.push(b' ');\n                    write_num(p.y, buf, opt.coordinates_precision);\n                    buf.push(b' ');\n                }\n                PathSegment::LineTo(p) => {\n                    buf.extend_from_slice(b\"L \");\n                    write_num(p.x, buf, opt.coordinates_precision);\n                    buf.push(b' ');\n                    write_num(p.y, buf, opt.coordinates_precision);\n                    buf.push(b' ');\n                }\n                PathSegment::QuadTo(p1, p) => {\n                    buf.extend_from_slice(b\"Q \");\n                    write_num(p1.x, buf, opt.coordinates_precision);\n                    buf.push(b' ');\n                    write_num(p1.y, buf, opt.coordinates_precision);\n                    buf.push(b' ');\n                    write_num(p.x, buf, opt.coordinates_precision);\n                    buf.push(b' ');\n                    write_num(p.y, buf, opt.coordinates_precision);\n                    buf.push(b' ');\n                }\n                PathSegment::CubicTo(p1, p2, p) => {\n                    buf.extend_from_slice(b\"C \");\n                    write_num(p1.x, buf, opt.coordinates_precision);\n                    buf.push(b' ');\n                    write_num(p1.y, buf, opt.coordinates_precision);\n                    buf.push(b' ');\n                    write_num(p2.x, buf, opt.coordinates_precision);\n                    buf.push(b' ');\n                    write_num(p2.y, buf, opt.coordinates_precision);\n                    buf.push(b' ');\n                    write_num(p.x, buf, opt.coordinates_precision);\n                    buf.push(b' ');\n                    write_num(p.y, buf, opt.coordinates_precision);\n                    buf.push(b' ');\n                }\n                PathSegment::Close => {\n                    buf.extend_from_slice(b\"Z \");\n                }\n            }\n        }\n\n        buf.pop();\n    });\n\n    xml.end_element();\n}\n\nfn write_fill(fill: &Option<Fill>, is_clip_path: bool, opt: &WriteOptions, xml: &mut XmlWriter) {\n    if let Some(fill) = fill {\n        write_paint(AId::Fill, &fill.paint, opt, xml);\n\n        if fill.opacity != Opacity::ONE {\n            xml.write_svg_attribute(AId::FillOpacity, &fill.opacity.get());\n        }\n\n        if !fill.rule.is_default() {\n            let name = if is_clip_path {\n                AId::ClipRule\n            } else {\n                AId::FillRule\n            };\n\n            xml.write_svg_attribute(name, \"evenodd\");\n        }\n    } else {\n        xml.write_svg_attribute(AId::Fill, \"none\");\n    }\n}\n\nfn write_stroke(stroke: &Option<Stroke>, opt: &WriteOptions, xml: &mut XmlWriter) {\n    if let Some(stroke) = stroke {\n        write_paint(AId::Stroke, &stroke.paint, opt, xml);\n\n        if stroke.opacity != Opacity::ONE {\n            xml.write_svg_attribute(AId::StrokeOpacity, &stroke.opacity.get());\n        }\n\n        if !stroke.dashoffset.approx_zero_ulps(4) {\n            xml.write_svg_attribute(AId::StrokeDashoffset, &stroke.dashoffset);\n        }\n\n        if !stroke.miterlimit.is_default() {\n            xml.write_svg_attribute(AId::StrokeMiterlimit, &stroke.miterlimit.get());\n        }\n\n        if stroke.width.get() != 1.0 {\n            xml.write_svg_attribute(AId::StrokeWidth, &stroke.width.get());\n        }\n\n        match stroke.linecap {\n            LineCap::Butt => {}\n            LineCap::Round => xml.write_svg_attribute(AId::StrokeLinecap, \"round\"),\n            LineCap::Square => xml.write_svg_attribute(AId::StrokeLinecap, \"square\"),\n        }\n\n        match stroke.linejoin {\n            LineJoin::Miter => {}\n            LineJoin::MiterClip => xml.write_svg_attribute(AId::StrokeLinejoin, \"miter-clip\"),\n            LineJoin::Round => xml.write_svg_attribute(AId::StrokeLinejoin, \"round\"),\n            LineJoin::Bevel => xml.write_svg_attribute(AId::StrokeLinejoin, \"bevel\"),\n        }\n\n        if let Some(ref array) = stroke.dasharray {\n            xml.write_numbers(AId::StrokeDasharray, array);\n        }\n    } else {\n        // Always set `stroke` to `none` to override the parent value.\n        // In 99.9% of the cases it's redundant, but a group with `filter` with `StrokePaint`\n        // will set `stroke`, which will interfere with children nodes.\n        xml.write_svg_attribute(AId::Stroke, \"none\");\n    }\n}\n\nfn write_paint(aid: AId, paint: &Paint, opt: &WriteOptions, xml: &mut XmlWriter) {\n    match paint {\n        Paint::Color(c) => xml.write_color(aid, *c),\n        Paint::LinearGradient(lg) => {\n            xml.write_func_iri(aid, lg.id(), opt);\n        }\n        Paint::RadialGradient(rg) => {\n            xml.write_func_iri(aid, rg.id(), opt);\n        }\n        Paint::Pattern(patt) => {\n            xml.write_func_iri(aid, patt.id(), opt);\n        }\n    }\n}\n\nfn write_light_source(light: &filter::LightSource, xml: &mut XmlWriter) {\n    match light {\n        filter::LightSource::DistantLight(light) => {\n            xml.start_svg_element(EId::FeDistantLight);\n            xml.write_svg_attribute(AId::Azimuth, &light.azimuth);\n            xml.write_svg_attribute(AId::Elevation, &light.elevation);\n        }\n        filter::LightSource::PointLight(light) => {\n            xml.start_svg_element(EId::FePointLight);\n            xml.write_svg_attribute(AId::X, &light.x);\n            xml.write_svg_attribute(AId::Y, &light.y);\n            xml.write_svg_attribute(AId::Z, &light.z);\n        }\n        filter::LightSource::SpotLight(light) => {\n            xml.start_svg_element(EId::FeSpotLight);\n            xml.write_svg_attribute(AId::X, &light.x);\n            xml.write_svg_attribute(AId::Y, &light.y);\n            xml.write_svg_attribute(AId::Z, &light.z);\n            xml.write_svg_attribute(AId::PointsAtX, &light.points_at_x);\n            xml.write_svg_attribute(AId::PointsAtY, &light.points_at_y);\n            xml.write_svg_attribute(AId::PointsAtZ, &light.points_at_z);\n            xml.write_svg_attribute(AId::SpecularExponent, &light.specular_exponent);\n            if let Some(n) = light.limiting_cone_angle {\n                xml.write_svg_attribute(AId::LimitingConeAngle, &n);\n            }\n        }\n    }\n\n    xml.end_element();\n}\n\nstatic POW_VEC: &[f32] = &[\n    1.0,\n    10.0,\n    100.0,\n    1_000.0,\n    10_000.0,\n    100_000.0,\n    1_000_000.0,\n    10_000_000.0,\n    100_000_000.0,\n    1_000_000_000.0,\n    10_000_000_000.0,\n    100_000_000_000.0,\n    1_000_000_000_000.0,\n];\n\nfn write_num(num: f32, buf: &mut Vec<u8>, precision: u8) {\n    // If number is an integer, it's faster to write it as i32.\n    if num.fract().approx_zero_ulps(4) {\n        write!(buf, \"{}\", num as i32).unwrap();\n        return;\n    }\n\n    // Round numbers up to the specified precision to prevent writing\n    // ugly numbers like 29.999999999999996.\n    // It's not 100% correct, but differences are insignificant.\n    //\n    // Note that at least in Rust 1.64 the number formatting in debug and release modes\n    // can be slightly different. So having a lower precision makes\n    // our output and tests reproducible.\n    let v = (num * POW_VEC[precision as usize]).round() / POW_VEC[precision as usize];\n\n    write!(buf, \"{}\", v).unwrap();\n}\n\n/// Write all of the tspan attributes except for decorations.\nfn write_span(\n    is_clip_path: bool,\n    opt: &WriteOptions,\n    xml: &mut XmlWriter,\n    chunk: &TextChunk,\n    span: &TextSpan,\n) {\n    xml.start_svg_element(EId::Tspan);\n\n    let font_family_to_str = |font_family: &FontFamily| match font_family {\n        FontFamily::Monospace => \"monospace\".to_string(),\n        FontFamily::Serif => \"serif\".to_string(),\n        FontFamily::SansSerif => \"sans-serif\".to_string(),\n        FontFamily::Cursive => \"cursive\".to_string(),\n        FontFamily::Fantasy => \"fantasy\".to_string(),\n        FontFamily::Named(s) => {\n            // Only quote if absolutely necessary\n            match parse_font_families(s) {\n                Ok(_) => s.clone(),\n                Err(_) => {\n                    if opt.use_single_quote {\n                        format!(\"\\\"{}\\\"\", s)\n                    } else {\n                        format!(\"'{}'\", s)\n                    }\n                }\n            }\n        }\n    };\n\n    if !span.font.families.is_empty() {\n        let families = span\n            .font\n            .families\n            .iter()\n            .map(font_family_to_str)\n            .collect::<Vec<_>>()\n            .join(\", \");\n        xml.write_svg_attribute(AId::FontFamily, &families);\n    }\n\n    match span.font.style {\n        FontStyle::Normal => {}\n        FontStyle::Italic => xml.write_svg_attribute(AId::FontStyle, \"italic\"),\n        FontStyle::Oblique => xml.write_svg_attribute(AId::FontStyle, \"oblique\"),\n    }\n\n    if span.font.weight != 400 {\n        xml.write_svg_attribute(AId::FontWeight, &span.font.weight);\n    }\n\n    if span.font.stretch != FontStretch::Normal {\n        let name = match span.font.stretch {\n            FontStretch::Condensed => \"condensed\",\n            FontStretch::ExtraCondensed => \"extra-condensed\",\n            FontStretch::UltraCondensed => \"ultra-condensed\",\n            FontStretch::SemiCondensed => \"semi-condensed\",\n            FontStretch::Expanded => \"expanded\",\n            FontStretch::SemiExpanded => \"semi-expanded\",\n            FontStretch::ExtraExpanded => \"extra-expanded\",\n            FontStretch::UltraExpanded => \"ultra-expanded\",\n            FontStretch::Normal => unreachable!(),\n        };\n        xml.write_svg_attribute(AId::FontStretch, name);\n    }\n\n    xml.write_svg_attribute(AId::FontSize, &span.font_size);\n\n    xml.write_visibility(span.visible);\n\n    if span.letter_spacing != 0.0 {\n        xml.write_svg_attribute(AId::LetterSpacing, &span.letter_spacing);\n    }\n\n    if span.word_spacing != 0.0 {\n        xml.write_svg_attribute(AId::WordSpacing, &span.word_spacing);\n    }\n\n    if let Some(text_length) = span.text_length {\n        xml.write_svg_attribute(AId::TextLength, &text_length);\n    }\n\n    if span.length_adjust == LengthAdjust::SpacingAndGlyphs {\n        xml.write_svg_attribute(AId::LengthAdjust, \"spacingAndGlyphs\");\n    }\n\n    if span.small_caps {\n        xml.write_svg_attribute(AId::FontVariant, \"small-caps\");\n    }\n\n    if span.paint_order == PaintOrder::StrokeAndFill {\n        xml.write_svg_attribute(AId::PaintOrder, \"stroke fill\");\n    }\n\n    if !span.apply_kerning {\n        xml.write_attribute(\"style\", \"font-kerning:none\");\n    }\n\n    if span.dominant_baseline != DominantBaseline::Auto {\n        let name = match span.dominant_baseline {\n            DominantBaseline::UseScript => \"use-script\",\n            DominantBaseline::NoChange => \"no-change\",\n            DominantBaseline::ResetSize => \"reset-size\",\n            DominantBaseline::TextBeforeEdge => \"text-before-edge\",\n            DominantBaseline::Middle => \"middle\",\n            DominantBaseline::Central => \"central\",\n            DominantBaseline::TextAfterEdge => \"text-after-edge\",\n            DominantBaseline::Ideographic => \"ideographic\",\n            DominantBaseline::Alphabetic => \"alphabetic\",\n            DominantBaseline::Hanging => \"hanging\",\n            DominantBaseline::Mathematical => \"mathematical\",\n            DominantBaseline::Auto => unreachable!(),\n        };\n        xml.write_svg_attribute(AId::DominantBaseline, name);\n    }\n\n    if span.alignment_baseline != AlignmentBaseline::Auto {\n        let name = match span.alignment_baseline {\n            AlignmentBaseline::Baseline => \"baseline\",\n            AlignmentBaseline::BeforeEdge => \"before-edge\",\n            AlignmentBaseline::TextBeforeEdge => \"text-before-edge\",\n            AlignmentBaseline::Middle => \"middle\",\n            AlignmentBaseline::Central => \"central\",\n            AlignmentBaseline::AfterEdge => \"after-edge\",\n            AlignmentBaseline::TextAfterEdge => \"text-after-edge\",\n            AlignmentBaseline::Ideographic => \"ideographic\",\n            AlignmentBaseline::Alphabetic => \"alphabetic\",\n            AlignmentBaseline::Hanging => \"hanging\",\n            AlignmentBaseline::Mathematical => \"mathematical\",\n            AlignmentBaseline::Auto => unreachable!(),\n        };\n        xml.write_svg_attribute(AId::AlignmentBaseline, name);\n    }\n\n    write_fill(&span.fill, is_clip_path, opt, xml);\n    write_stroke(&span.stroke, opt, xml);\n\n    for baseline_shift in &span.baseline_shift {\n        xml.start_svg_element(EId::Tspan);\n        match baseline_shift {\n            BaselineShift::Baseline => {}\n            BaselineShift::Number(num) => xml.write_svg_attribute(AId::BaselineShift, num),\n            BaselineShift::Subscript => xml.write_svg_attribute(AId::BaselineShift, \"sub\"),\n            BaselineShift::Superscript => xml.write_svg_attribute(AId::BaselineShift, \"super\"),\n        }\n    }\n\n    let cur_text = &chunk.text[span.start..span.end];\n\n    xml.write_text(&cur_text.replace('&', \"&amp;\"));\n\n    // End for each tspan we needed to create for baseline_shift\n    for _ in &span.baseline_shift {\n        xml.end_element();\n    }\n\n    xml.end_element();\n}\n"
  },
  {
    "path": "crates/usvg/tests/parser.rs",
    "content": "// Copyright 2018 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse tiny_skia_path::Rect;\nuse usvg::Color;\n\n#[test]\nfn clippath_with_invalid_child() {\n    let svg = \"\n    <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'>\n        <clipPath id='clip1'>\n            <rect/>\n        </clipPath>\n        <rect clip-path='url(#clip1)' width='10' height='10'/>\n    </svg>\n    \";\n\n    let tree = usvg::Tree::from_str(&svg, &usvg::Options::default()).unwrap();\n    // clipPath is invalid and should be removed together with rect.\n    assert!(!tree.root().has_children());\n}\n\n#[test]\nfn stylesheet_injection() {\n    let svg = \"<svg id='svg1' viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'>\n    <style>\n        #rect4 {\n            fill: green\n        }\n    </style>\n    <rect id='rect1' x='20' y='20' width='60' height='60'/>\n    <rect id='rect2' x='120' y='20' width='60' height='60' fill='green'/>\n    <rect id='rect3' x='20' y='120' width='60' height='60' style='fill: green'/>\n    <rect id='rect4' x='120' y='120' width='60' height='60'/>\n    <rect id='rect5' x='70' y='70' width='60' height='60' style='fill: green !important'/>\n</svg>\n\";\n\n    let stylesheet = \"rect { fill: red }\".to_string();\n\n    let options = usvg::Options {\n        style_sheet: Some(stylesheet),\n        ..usvg::Options::default()\n    };\n\n    let tree = usvg::Tree::from_str(&svg, &options).unwrap();\n\n    let usvg::Node::Path(first) = &tree.root().children()[0] else {\n        unreachable!()\n    };\n\n    // Only the rects with no CSS attributes should be overridden.\n    assert_eq!(\n        first.fill().unwrap().paint(),\n        &usvg::Paint::Color(Color::new_rgb(255, 0, 0))\n    );\n\n    let usvg::Node::Path(second) = &tree.root().children()[1] else {\n        unreachable!()\n    };\n    assert_eq!(\n        second.fill().unwrap().paint(),\n        &usvg::Paint::Color(Color::new_rgb(255, 0, 0))\n    );\n\n    let usvg::Node::Path(third) = &tree.root().children()[2] else {\n        unreachable!()\n    };\n    assert_eq!(\n        third.fill().unwrap().paint(),\n        &usvg::Paint::Color(Color::new_rgb(0, 128, 0))\n    );\n\n    let usvg::Node::Path(third) = &tree.root().children()[3] else {\n        unreachable!()\n    };\n    assert_eq!(\n        third.fill().unwrap().paint(),\n        &usvg::Paint::Color(Color::new_rgb(0, 128, 0))\n    );\n\n    let usvg::Node::Path(third) = &tree.root().children()[3] else {\n        unreachable!()\n    };\n    assert_eq!(\n        third.fill().unwrap().paint(),\n        &usvg::Paint::Color(Color::new_rgb(0, 128, 0))\n    );\n}\n\n#[test]\nfn stylesheet_injection_with_important() {\n    let svg = \"<svg id='svg1' viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'>\n    <style>\n        #rect4 {\n            fill: green\n        }\n    </style>\n    <rect id='rect1' x='20' y='20' width='60' height='60'/>\n    <rect id='rect2' x='120' y='20' width='60' height='60' fill='green'/>\n    <rect id='rect3' x='20' y='120' width='60' height='60' style='fill: green'/>\n    <rect id='rect4' x='120' y='120' width='60' height='60'/>\n    <rect id='rect5' x='70' y='70' width='60' height='60' style='fill: green !important'/>\n</svg>\n\";\n\n    let stylesheet = \"rect { fill: red !important }\".to_string();\n\n    let options = usvg::Options {\n        style_sheet: Some(stylesheet),\n        ..usvg::Options::default()\n    };\n\n    let tree = usvg::Tree::from_str(&svg, &options).unwrap();\n\n    let usvg::Node::Path(first) = &tree.root().children()[0] else {\n        unreachable!()\n    };\n\n    // All rects should be overridden, since we use `important`.\n    assert_eq!(\n        first.fill().unwrap().paint(),\n        &usvg::Paint::Color(Color::new_rgb(255, 0, 0))\n    );\n\n    let usvg::Node::Path(second) = &tree.root().children()[1] else {\n        unreachable!()\n    };\n    assert_eq!(\n        second.fill().unwrap().paint(),\n        &usvg::Paint::Color(Color::new_rgb(255, 0, 0))\n    );\n\n    let usvg::Node::Path(third) = &tree.root().children()[2] else {\n        unreachable!()\n    };\n    assert_eq!(\n        third.fill().unwrap().paint(),\n        &usvg::Paint::Color(Color::new_rgb(255, 0, 0))\n    );\n\n    let usvg::Node::Path(third) = &tree.root().children()[3] else {\n        unreachable!()\n    };\n    assert_eq!(\n        third.fill().unwrap().paint(),\n        &usvg::Paint::Color(Color::new_rgb(255, 0, 0))\n    );\n\n    let usvg::Node::Path(third) = &tree.root().children()[4] else {\n        unreachable!()\n    };\n    assert_eq!(\n        third.fill().unwrap().paint(),\n        &usvg::Paint::Color(Color::new_rgb(255, 0, 0))\n    );\n}\n\n#[test]\nfn simplify_paths() {\n    let svg = \"\n    <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'>\n        <path d='M 10 20 L 10 30 Z Z Z'/>\n    </svg>\n    \";\n\n    let tree = usvg::Tree::from_str(&svg, &usvg::Options::default()).unwrap();\n    let path = &tree.root().children()[0];\n    match path {\n        usvg::Node::Path(path) => {\n            // Make sure we have MLZ and not MLZZZ\n            assert_eq!(path.data().verbs().len(), 3);\n        }\n        _ => unreachable!(),\n    };\n}\n\n#[test]\nfn size_detection_1() {\n    let svg = \"<svg viewBox='0 0 10 20' xmlns='http://www.w3.org/2000/svg'/>\";\n    let tree = usvg::Tree::from_str(&svg, &usvg::Options::default()).unwrap();\n    assert_eq!(tree.size(), usvg::Size::from_wh(10.0, 20.0).unwrap());\n}\n\n#[test]\nfn size_detection_2() {\n    let svg =\n        \"<svg width='30' height='40' viewBox='0 0 10 20' xmlns='http://www.w3.org/2000/svg'/>\";\n    let tree = usvg::Tree::from_str(&svg, &usvg::Options::default()).unwrap();\n    assert_eq!(tree.size(), usvg::Size::from_wh(30.0, 40.0).unwrap());\n}\n\n#[test]\nfn size_detection_3() {\n    let svg =\n        \"<svg width='50%' height='100%' viewBox='0 0 10 20' xmlns='http://www.w3.org/2000/svg'/>\";\n    let tree = usvg::Tree::from_str(&svg, &usvg::Options::default()).unwrap();\n    assert_eq!(tree.size(), usvg::Size::from_wh(5.0, 20.0).unwrap());\n}\n\n#[test]\nfn size_detection_4() {\n    let svg = \"\n    <svg xmlns='http://www.w3.org/2000/svg'>\n        <circle cx='18' cy='18' r='18'/>\n    </svg>\n    \";\n    let tree = usvg::Tree::from_str(&svg, &usvg::Options::default()).unwrap();\n    assert_eq!(tree.size(), usvg::Size::from_wh(36.0, 36.0).unwrap());\n    assert_eq!(tree.size(), usvg::Size::from_wh(36.0, 36.0).unwrap());\n}\n\n#[test]\nfn size_detection_5() {\n    let svg = \"<svg xmlns='http://www.w3.org/2000/svg'/>\";\n    let tree = usvg::Tree::from_str(&svg, &usvg::Options::default()).unwrap();\n    assert_eq!(tree.size(), usvg::Size::from_wh(100.0, 100.0).unwrap());\n}\n\n#[test]\nfn invalid_size_1() {\n    let svg = \"<svg width='0' height='0' viewBox='0 0 10 20' xmlns='http://www.w3.org/2000/svg'/>\";\n    let result = usvg::Tree::from_str(&svg, &usvg::Options::default());\n    assert!(result.is_err());\n}\n\n#[test]\nfn tree_is_send_and_sync() {\n    fn ensure_send_and_sync<T: Send + Sync>() {}\n    ensure_send_and_sync::<usvg::Tree>();\n}\n\n#[test]\nfn path_transform() {\n    let svg = \"\n    <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'>\n        <path transform='translate(10)' d='M 0 0 L 10 10'/>\n    </svg>\n    \";\n\n    let tree = usvg::Tree::from_str(&svg, &usvg::Options::default()).unwrap();\n    assert_eq!(tree.root().children().len(), 1);\n\n    let group_node = &tree.root().children()[0];\n    assert!(matches!(group_node, usvg::Node::Group(_)));\n    assert_eq!(\n        group_node.abs_transform(),\n        usvg::Transform::from_translate(10.0, 0.0)\n    );\n\n    let group = match group_node {\n        usvg::Node::Group(g) => g,\n        _ => unreachable!(),\n    };\n\n    let path = &group.children()[0];\n    assert!(matches!(path, usvg::Node::Path(_)));\n    assert_eq!(\n        path.abs_transform(),\n        usvg::Transform::from_translate(10.0, 0.0)\n    );\n}\n\n#[test]\nfn path_transform_nested() {\n    let svg = \"\n    <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'>\n        <g transform='translate(20)'>\n            <path transform='translate(10)' d='M 0 0 L 10 10'/>\n        </g>\n    </svg>\n    \";\n\n    let tree = usvg::Tree::from_str(&svg, &usvg::Options::default()).unwrap();\n    assert_eq!(tree.root().children().len(), 1);\n\n    let group_node1 = &tree.root().children()[0];\n    assert!(matches!(group_node1, usvg::Node::Group(_)));\n    assert_eq!(\n        group_node1.abs_transform(),\n        usvg::Transform::from_translate(20.0, 0.0)\n    );\n\n    let group1 = match group_node1 {\n        usvg::Node::Group(g) => g,\n        _ => unreachable!(),\n    };\n\n    let group_node2 = &group1.children()[0];\n    assert!(matches!(group_node2, usvg::Node::Group(_)));\n    assert_eq!(\n        group_node2.abs_transform(),\n        usvg::Transform::from_translate(30.0, 0.0)\n    );\n\n    let group2 = match group_node2 {\n        usvg::Node::Group(g) => g,\n        _ => unreachable!(),\n    };\n\n    let path = &group2.children()[0];\n    assert!(matches!(path, usvg::Node::Path(_)));\n    assert_eq!(\n        path.abs_transform(),\n        usvg::Transform::from_translate(30.0, 0.0)\n    );\n}\n\n#[test]\nfn path_transform_in_symbol_no_clip() {\n    let svg = \"\n    <svg viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'>\n        <defs>\n            <symbol id='symbol1' overflow='visible'>\n                <rect id='rect1' x='0' y='0' width='10' height='10'/>\n            </symbol>\n        </defs>\n        <use id='use1' xlink:href='#symbol1' x='20'/>\n    </svg>\n    \";\n\n    // Will be parsed as:\n    // <svg width=\"100\" height=\"100\" viewBox=\"0 0 100 100\" xmlns=\"http://www.w3.org/2000/svg\">\n    //     <g id=\"use1\">\n    //         <g transform=\"matrix(1 0 0 1 20 0)\">\n    //             <path fill=\"#000000\" stroke=\"none\" d=\"M 0 0 L 10 0 L 10 10 L 0 10 Z\"/>\n    //         </g>\n    //     </g>\n    // </svg>\n\n    let tree = usvg::Tree::from_str(&svg, &usvg::Options::default()).unwrap();\n\n    let group_node1 = &tree.root().children()[0];\n    assert!(matches!(group_node1, usvg::Node::Group(_)));\n    assert_eq!(group_node1.id(), \"use1\");\n    assert_eq!(group_node1.abs_transform(), usvg::Transform::default());\n\n    let group1 = match group_node1 {\n        usvg::Node::Group(g) => g,\n        _ => unreachable!(),\n    };\n\n    let group_node2 = &group1.children()[0];\n    assert!(matches!(group_node2, usvg::Node::Group(_)));\n    assert_eq!(\n        group_node2.abs_transform(),\n        usvg::Transform::from_translate(20.0, 0.0)\n    );\n\n    let group2 = match group_node2 {\n        usvg::Node::Group(g) => g,\n        _ => unreachable!(),\n    };\n\n    let path = &group2.children()[0];\n    assert!(matches!(path, usvg::Node::Path(_)));\n    assert_eq!(\n        path.abs_transform(),\n        usvg::Transform::from_translate(20.0, 0.0)\n    );\n}\n\n#[test]\nfn path_transform_in_symbol_with_clip() {\n    let svg = \"\n    <svg viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'>\n        <defs>\n            <symbol id='symbol1' overflow='hidden'>\n                <rect id='rect1' x='0' y='0' width='10' height='10'/>\n            </symbol>\n        </defs>\n        <use id='use1' xlink:href='#symbol1' x='20'/>\n    </svg>\n    \";\n\n    // Will be parsed as:\n    // <svg width=\"100\" height=\"100\" viewBox=\"0 0 100 100\" xmlns=\"http://www.w3.org/2000/svg\">\n    //     <defs>\n    //         <clipPath id=\"clipPath1\">\n    //             <path fill=\"#000000\" stroke=\"none\" d=\"M 20 0 L 120 0 L 120 100 L 20 100 Z\"/>\n    //         </clipPath>\n    //     </defs>\n    //     <g id=\"use1\" clip-path=\"url(#clipPath1)\">\n    //         <g>\n    //             <g transform=\"matrix(1 0 0 1 20 0)\">\n    //                 <path fill=\"#000000\" stroke=\"none\" d=\"M 0 0 L 10 0 L 10 10 L 0 10 Z\"/>\n    //             </g>\n    //         </g>\n    //     </g>\n    // </svg>\n\n    let tree = usvg::Tree::from_str(&svg, &usvg::Options::default()).unwrap();\n\n    let group_node1 = &tree.root().children()[0];\n    assert!(matches!(group_node1, usvg::Node::Group(_)));\n    assert_eq!(group_node1.id(), \"use1\");\n    assert_eq!(group_node1.abs_transform(), usvg::Transform::default());\n\n    let group1 = match group_node1 {\n        usvg::Node::Group(g) => g,\n        _ => unreachable!(),\n    };\n\n    let group_node2 = &group1.children()[0];\n    assert!(matches!(group_node2, usvg::Node::Group(_)));\n    assert_eq!(group_node2.abs_transform(), usvg::Transform::default());\n\n    let group2 = match group_node2 {\n        usvg::Node::Group(g) => g,\n        _ => unreachable!(),\n    };\n\n    let group_node3 = &group2.children()[0];\n    assert!(matches!(group_node3, usvg::Node::Group(_)));\n    assert_eq!(\n        group_node3.abs_transform(),\n        usvg::Transform::from_translate(20.0, 0.0)\n    );\n\n    let group3 = match group_node3 {\n        usvg::Node::Group(g) => g,\n        _ => unreachable!(),\n    };\n\n    let path = &group3.children()[0];\n    assert!(matches!(path, usvg::Node::Path(_)));\n    assert_eq!(\n        path.abs_transform(),\n        usvg::Transform::from_translate(20.0, 0.0)\n    );\n}\n\n#[test]\nfn path_transform_in_svg() {\n    let svg = \"\n    <svg viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'>\n        <g id='g1' transform='translate(100 150)'>\n            <svg id='svg1' width='100' height='50'>\n                <rect id='rect1' width='10' height='10'/>\n            </svg>\n        </g>\n    </svg>\n    \";\n\n    // Will be parsed as:\n    // <svg width=\"100\" height=\"100\" viewBox=\"0 0 100 100\" xmlns=\"http://www.w3.org/2000/svg\">\n    //     <defs>\n    //         <clipPath id=\"clipPath1\">\n    //             <path fill=\"#000000\" stroke=\"none\" d=\"M 0 0 L 100 0 L 100 50 L 0 50 Z\"/>\n    //         </clipPath>\n    //     </defs>\n    //     <g id=\"g1\" transform=\"matrix(1 0 0 1 100 150)\">\n    //         <g id=\"svg1\" clip-path=\"url(#clipPath1)\">\n    //             <path id=\"rect1\" fill=\"#000000\" stroke=\"none\" d=\"M 0 0 L 10 0 L 10 10 L 0 10 Z\"/>\n    //         </g>\n    //     </g>\n    // </svg>\n\n    let tree = usvg::Tree::from_str(&svg, &usvg::Options::default()).unwrap();\n\n    let group_node1 = &tree.root().children()[0];\n    assert!(matches!(group_node1, usvg::Node::Group(_)));\n    assert_eq!(group_node1.id(), \"g1\");\n    assert_eq!(\n        group_node1.abs_transform(),\n        usvg::Transform::from_translate(100.0, 150.0)\n    );\n\n    let group1 = match group_node1 {\n        usvg::Node::Group(g) => g,\n        _ => unreachable!(),\n    };\n\n    let group_node2 = &group1.children()[0];\n    assert!(matches!(group_node2, usvg::Node::Group(_)));\n    assert_eq!(group_node2.id(), \"svg1\");\n    assert_eq!(\n        group_node2.abs_transform(),\n        usvg::Transform::from_translate(100.0, 150.0)\n    );\n\n    let group2 = match group_node2 {\n        usvg::Node::Group(g) => g,\n        _ => unreachable!(),\n    };\n\n    let path = &group2.children()[0];\n    assert!(matches!(path, usvg::Node::Path(_)));\n    assert_eq!(\n        path.abs_transform(),\n        usvg::Transform::from_translate(100.0, 150.0)\n    );\n}\n\n#[test]\nfn svg_without_xmlns() {\n    let svg = \"\n    <svg viewBox='0 0 100 100'>\n        <rect x='0' y='0' width='10' height='10'/>\n    </svg>\n    \";\n\n    let tree = usvg::Tree::from_str(&svg, &usvg::Options::default()).unwrap();\n    assert_eq!(tree.size(), usvg::Size::from_wh(100.0, 100.0).unwrap());\n}\n\n#[test]\nfn image_bbox_with_parent_transform() {\n    let svg = \"\n    <svg viewBox='0 0 200 200' \n         xmlns='http://www.w3.org/2000/svg'\n         xmlns:xlink='http://www.w3.org/1999/xlink'>\n        <g transform='translate(25 25)'>\n            <image id='image1' x='10' y='10' width='50' height='50' xlink:href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAABb0lEQVR4Xu3VUQ0AIAzEUOZfA87wAgkq+vGmoGlz2exz73IZAyNIpsUHEaTVQ5BYD0EEqRmI8fghgsQMxHAsRJCYgRiOhQgSMxDDsRBBYgZiOBYiSMxADMdCBIkZiOFYiCAxAzEcCxEkZiCGYyGCxAzEcCxEkJiBGI6FCBIzEMOxEEFiBmI4FiJIzEAMx0IEiRmI4ViIIDEDMRwLESRmIIZjIYLEDMRwLESQmIEYjoUIEjMQw7EQQWIGYjgWIkjMQAzHQgSJGYjhWIggMQMxHAsRJGYghmMhgsQMxHAsRJCYgRiOhQgSMxDDsRBBYgZiOBYiSMxADMdCBIkZiOFYiCAxAzEcCxEkZiCGYyGCxAzEcCxEkJiBGI6FCBIzEMOxEEFiBmI4FiJIzEAMx0IEiRmI4ViIIDEDMRwLESRmIIZjIYLEDMRwLESQmIEYjoUIEjMQw7EQQWIGYjgWIkjMQAzHQgSJGYjhWIggMQMxnAdKSlrwlejIDgAAAABJRU5ErkJggg=='/>\n        </g>\n    </svg>\n    \";\n\n    let tree = usvg::Tree::from_str(&svg, &usvg::Options::default()).unwrap();\n\n    let usvg::Node::Group(group_node1) = &tree.root().children()[0] else {\n        unreachable!()\n    };\n    let usvg::Node::Group(group_node2) = &group_node1.children()[0] else {\n        unreachable!()\n    };\n    let usvg::Node::Image(image_node) = &group_node2.children()[0] else {\n        unreachable!()\n    };\n\n    assert_eq!(\n        image_node.abs_bounding_box(),\n        Rect::from_xywh(35.0, 35.0, 50.0, 50.0).unwrap()\n    );\n}\n\n#[test]\nfn no_text_nodes() {\n    let svg = \"\n    <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'>\n        <g transform='translate(20)'>\n            <path transform='translate(10)' d='M 0 0 L 10 10'/>\n        </g>\n    </svg>\n    \";\n\n    let tree = usvg::Tree::from_str(&svg, &usvg::Options::default()).unwrap();\n    assert!(!tree.has_text_nodes());\n}\n"
  },
  {
    "path": "crates/usvg/tests/write.rs",
    "content": "// Copyright 2023 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse std::sync::Arc;\n\nuse once_cell::sync::Lazy;\n\nstatic GLOBAL_FONTDB: Lazy<Arc<usvg::fontdb::Database>> = Lazy::new(|| {\n    let mut fontdb = usvg::fontdb::Database::new();\n    fontdb.load_fonts_dir(\"../resvg/tests/fonts\");\n    fontdb.set_serif_family(\"Noto Serif\");\n    fontdb.set_sans_serif_family(\"Noto Sans\");\n    fontdb.set_cursive_family(\"Yellowtail\");\n    fontdb.set_fantasy_family(\"Sedgwick Ave Display\");\n    fontdb.set_monospace_family(\"Noto Mono\");\n    Arc::new(fontdb)\n});\n\nfn resave(name: &str) {\n    resave_impl(name, None, false);\n}\n\nfn resave_with_text(name: &str) {\n    resave_impl(name, None, true);\n}\n\nfn resave_with_prefix(name: &str, id_prefix: &str) {\n    resave_impl(name, Some(id_prefix.to_string()), false);\n}\n\nfn resave_impl(name: &str, id_prefix: Option<String>, preserve_text: bool) {\n    let input_svg = std::fs::read_to_string(format!(\"tests/files/{}.svg\", name)).unwrap();\n\n    let tree = {\n        let opt = usvg::Options {\n            fontdb: GLOBAL_FONTDB.clone(),\n            ..Default::default()\n        };\n        usvg::Tree::from_str(&input_svg, &opt).unwrap()\n    };\n    let xml_opt = usvg::WriteOptions {\n        id_prefix,\n        preserve_text,\n        coordinates_precision: 4, // Reduce noise and file size.\n        transforms_precision: 4,\n        ..usvg::WriteOptions::default()\n    };\n    let output_svg = tree.to_string(&xml_opt);\n\n    // std::fs::write(\n    //     format!(\"tests/files/{}-expected.svg\", name),\n    //     output_svg.clone(),\n    // )\n    // .unwrap();\n\n    let expected_svg =\n        std::fs::read_to_string(format!(\"tests/files/{}-expected.svg\", name)).unwrap();\n    // Do not use `assert_eq` because it produces an unreadable output.\n    let is_correct = output_svg == expected_svg;\n    // uncomment next line to apply correction\n    // std::fs::write(format!(\"tests/files/{}-expected.svg\", name), output_svg).unwrap();\n    assert!(is_correct);\n}\n\n#[test]\nfn path_simple_case() {\n    resave(\"path-simple-case\");\n}\n\n#[test]\nfn ellipse_simple_case() {\n    resave(\"ellipse-simple-case\");\n}\n\n#[test]\nfn text_simple_case() {\n    resave(\"text-simple-case\");\n}\n\n#[test]\nfn clip_path_with_transform() {\n    resave(\"clip-path-with-transform\");\n}\n\n#[test]\nfn preserve_id_filter() {\n    resave(\"preserve-id-filter\");\n}\n\n#[test]\nfn preserve_id_fe_image() {\n    resave(\"preserve-id-fe-image\");\n}\n\n#[test]\nfn preserve_id_fe_image_with_opacity() {\n    resave(\"preserve-id-fe-image-with-opacity\");\n}\n\n#[test]\nfn generate_filter_id_function_v1() {\n    resave(\"generate-id-filter-function-v1\");\n}\n\n#[test]\nfn generate_filter_id_function_v2() {\n    resave(\"generate-id-filter-function-v2\");\n}\n\n#[test]\nfn filter_id_with_prefix() {\n    resave_with_prefix(\"filter-id-with-prefix\", \"prefix-\");\n}\n\n#[test]\nfn filter_with_object_units_multi_use() {\n    resave(\"filter-with-object-units-multi-use\");\n}\n\n#[test]\nfn preserve_id_clip_path_v1() {\n    resave(\"preserve-id-clip-path-v1\");\n}\n\n#[test]\nfn preserve_id_clip_path_v2() {\n    resave(\"preserve-id-clip-path-v2\");\n}\n\n#[test]\nfn preserve_id_for_clip_path_in_pattern() {\n    resave(\"preserve-id-for-clip-path-in-pattern\");\n}\n\n#[test]\nfn generate_id_clip_path_for_symbol() {\n    resave(\"generate-id-clip-path-for-symbol\");\n}\n\n#[test]\nfn clip_path_with_text() {\n    resave(\"clip-path-with-text\");\n}\n\n#[test]\nfn clip_path_with_complex_text() {\n    resave(\"clip-path-with-complex-text\");\n}\n\n#[test]\nfn clip_path_with_object_units_multi_use() {\n    resave(\"clip-path-with-object-units-multi-use\");\n}\n\n#[test]\nfn mask_with_object_units_multi_use() {\n    resave(\"mask-with-object-units-multi-use\");\n}\n\n#[test]\nfn text_with_generated_gradients() {\n    resave(\"text-with-generated-gradients\");\n}\n\n#[test]\nfn preserve_text_multiple_font_families() {\n    resave_with_text(\"preserve-text-multiple-font-families\");\n}\n\n#[test]\nfn preserve_text_on_path() {\n    resave_with_text(\"preserve-text-on-path\");\n}\n\n#[test]\nfn preserve_text_in_clip_path() {\n    resave_with_text(\"preserve-text-in-clip-path\");\n}\n\n#[test]\nfn preserve_text_in_mask() {\n    resave_with_text(\"preserve-text-in-mask\");\n}\n\n#[test]\nfn preserve_text_in_pattern() {\n    resave_with_text(\"preserve-text-in-pattern\");\n}\n\n#[test]\nfn preserve_text_simple_case() {\n    resave(\"preserve-text-simple-case\");\n}\n\n#[test]\nfn preserve_text_with_dx_and_dy() {\n    resave_with_text(\"preserve-text-with-dx-and-dy\");\n}\n\n#[test]\nfn preserve_text_with_rotate() {\n    resave_with_text(\"preserve-text-with-rotate\");\n}\n\n#[test]\nfn preserve_text_with_complex_text_decoration() {\n    resave_with_text(\"preserve-text-with-complex-text-decoration\");\n}\n\n#[test]\nfn preserve_text_with_nested_baseline_shift() {\n    resave_with_text(\"preserve-text-with-nested-baseline-shift\");\n}\n\n#[test]\nfn optimize_paths_without_markers() {\n    resave(\"optimize-paths-without-markers\");\n}\n"
  },
  {
    "path": "docs/svg2-changelog.md",
    "content": "# SVG 2 changelog\n\nAn attempt to list all changes between SVG 1.1 and SVG 2.\n\nSomewhat similar to [Changes from SVG 1.1](https://www.w3.org/TR/SVG2/changes.html) from the SVG 2 spec, but actually lists all changes and not just changes to the spec itself. For example, that page doesn't list filter related changes and most of the text related changes are either omitted or scattered around the spec.\n\nThis document contains changes only to the static SVG subset. No animations, events and scripting.\n\nA checkbox indicates that the related feature is implemented in `resvg`.\n\nNOTE: This list is not final. This just things I was able to find so far. Patches are welcome.\n\n## Data Types\n\n### Added\n\n- [x] A `turn` unit to [`<angle>`](https://www.w3.org/TR/css-values-3/#angles).\n- [ ] Following units: `ch`, `rem`, `vw`, `vh`, `vmin`, `vmax` and `Q` to [`<length>`](https://www.w3.org/TR/css3-values/#lengths).\n- [x] [`rgba()`](https://www.w3.org/TR/css-color-3/#rgba-color), [`hsl()`](https://www.w3.org/TR/css-color-3/#hsl-color) and [`hsla()`](https://www.w3.org/TR/css-color-3/#hsla-color) notations to [`<color>`](https://www.w3.org/TR/css-color-3/#colorunits).\n- [x] A [`transparent`](https://www.w3.org/TR/css-color-3/#transparent) keyword to [`<color>`](https://www.w3.org/TR/css-color-3/#colorunits).\n- [x] A `#RRGGBBAA` and `#RGBA` notation for colors. Part of [CSS Color 4](https://www.w3.org/TR/css-color-4/#hex-notation).\n- [x] A [`opacity`](https://www.w3.org/TR/css-color-4/#transparency) property allows `<percentage>` now.\n\n### Changed\n\n- [x] [`<length>`](https://www.w3.org/TR/css3-values/#lengths) no longer includes the `%` unit. This variant was moved into a separate type: [`<length-percentage>`](https://www.w3.org/TR/css3-values/#typedef-length-percentage).\n- [x] [`<FuncIRI>`](https://www.w3.org/TR/SVG11/filters.html#FilterProperty) was replaced with an [`<url>`](https://www.w3.org/TR/css3-values/#url-value). The main change here is that `<url>` allows quoted strings.\n\n### Deprecated\n\n- [CSS2 system colors](https://www.w3.org/TR/css-color-3/#css2-system).\n\n### Quirks\n\n- [`<color>`](https://www.w3.org/TR/css-color-3/#colorunits) includes an alpha value now, which should be accounted by `fill`, `stroke`, `flood-color` and `stop-color` properties. But not by `lighting-color` property. At least Chrome 92 and Firefox 91 doesn't do this.\n\n<!-- ----------------------------------- -->\n\n## Document Structure\n\n### Added\n\n- [ ] `refX` and `refY` [properties](https://www.w3.org/TR/SVG2/struct.html#SymbolAttributes) to the [`symbol`](https://www.w3.org/TR/SVG2/struct.html#SymbolElement) element.\n- [x] An [`auto`](https://www.w3.org/TR/SVG2/geometry.html#Sizing) variant to [`image`](https://www.w3.org/TR/SVG2/embedded.html#ImageElement) element's `width` and `height` properties.\n- [ ] A `lang` attribute. The same as `xml:lang`, but without the namespace.\n\n### Changed\n\n- [ ] `width` and `height` properties of the [`svg`](https://www.w3.org/TR/SVG2/struct.html#SVGElement) element are set to `auto` by default.\n\n### Removed\n\n- A `baseProfile` attribute from the [`svg`](https://www.w3.org/TR/SVG2/struct.html#SVGElement) element.\n- A `version` attribute from the [`svg`](https://www.w3.org/TR/SVG2/struct.html#SVGElement) element.\n- A `externalResourcesRequired` attribute.\n- A `requiredFeatures` attribute.\n- A `xml:base` attribute.\n\n<!-- min-width and max-width ? -->\n\n<!-- ----------------------------------- -->\n\n## Styling\n\n### Deprecated\n\n- A [`clip`](https://www.w3.org/TR/css-masking-1/#clip-property) property.\n\n<!-- ----------------------------------- -->\n\n## Coordinate Systems, Transformations and Units\n\n### Added\n\n- [ ] A [`transform-box`](https://www.w3.org/TR/css-transforms-1/#transform-box) property.\n- [x] A [`transform-origin`](https://www.w3.org/TR/css-transforms-1/#transform-origin-property) property.\n- [ ] A [`vector-effect`](https://www.w3.org/TR/SVG2/coords.html#VectorEffects) property.\n\n### Changed\n\n- [ ] `transform`, `patternTransform` and `gradientTransform` are presentation attributes now. Which means that they can be resolved from CSS now.\n\n### Removed\n\n- A `defer` keyword from the [`preserveAspectRatio`](https://www.w3.org/TR/SVG2/coords.html#PreserveAspectRatioAttribute) attribute.\n\n### Quirks\n\n- CSS `transform` and SVG `transform` [have different syntax](https://www.w3.org/TR/css-transforms-1/#svg-syntax).\n\n<!-- ----------------------------------- -->\n\n## Basic Shapes\n\n### Added\n\n- [ ] A [`pathLength`](https://www.w3.org/TR/SVG2/paths.html#PathLengthAttribute) attribute to all [basic shapes](https://www.w3.org/TR/SVG2/shapes.html).\n\n### Changed\n\n- [x] `rx`/`ry` attributes on [`ellipse`](https://www.w3.org/TR/SVG2/shapes.html#EllipseElement) should be resolved using the same logic as [`rect`](https://www.w3.org/TR/SVG2/shapes.html#RectElement) uses.\n\n<!-- ----------------------------------- -->\n\n## Text\n\n### Added\n\nBasically everything from [CSS Text Module Level 3](https://www.w3.org/TR/css-text-3/).\n\n- [ ] WOFF font support is required now.\n- [ ] A [`path`](https://www.w3.org/TR/SVG2/text.html#TextPathElementPathAttribute) property to [`textPath`](https://www.w3.org/TR/SVG2/text.html#TextPathElement).\n- [ ] A [`side`](https://www.w3.org/TR/SVG2/text.html#TextPathElementSideAttribute) property to [`textPath`](https://www.w3.org/TR/SVG2/text.html#TextPathElement).\n- [ ] A [`font-feature-settings`](https://www.w3.org/TR/css-fonts-3/#propdef-font-feature-settings) property.\n- [x] A [`font-kerning`](https://www.w3.org/TR/css-fonts-3/#propdef-font-kerning) property.\n- [ ] A [`font-synthesis`](https://www.w3.org/TR/css-fonts-3/#propdef-font-synthesis) property.\n- [ ] A [`font-variant-caps`](https://www.w3.org/TR/css-fonts-3/#propdef-font-variant-caps) property.\n- [ ] A [`font-variant-east-asian`](https://www.w3.org/TR/css-fonts-3/#propdef-font-variant-east-asian) property.\n- [ ] A [`font-variant-ligatures`](https://www.w3.org/TR/css-fonts-3/#propdef-font-variant-ligatures) property.\n- [ ] A [`font-variant-numeric`](https://www.w3.org/TR/css-fonts-3/#propdef-font-variant-numeric) property.\n- [ ] A [`font-variant-position`](https://www.w3.org/TR/css-fonts-3/#propdef-font-variant-position) property.\n- [ ] A [`line-height`](https://www.w3.org/TR/SVG2/text.html#LineHeightProperty) property.\n- [ ] A [`text-align-last`](https://www.w3.org/TR/css-text-3/#propdef-text-align-last) property.\n- [ ] A [`text-align`](https://www.w3.org/TR/css-text-3/#propdef-text-align) property.\n- [ ] A [`text-indent`](https://www.w3.org/TR/css-text-3/#propdef-text-indent) property.\n- [ ] A [`text-orientation`](https://www.w3.org/TR/css-writing-modes-3/#text-orientation) property.\n- [ ] A [`text-overflow`](https://www.w3.org/TR/SVG2/text.html#TextOverflowProperty) property.\n- [ ] A [`text-transform`](https://www.w3.org/TR/css-text-3/#text-transform-property) property.\n- [ ] A [`unicode-range`](https://www.w3.org/TR/css-fonts-3/#descdef-unicode-range) property.\n- [ ] A [`white-space`](https://www.w3.org/TR/SVG2/text.html#WhiteSpace) property.\n- [ ] A [`text-decoration-line`](https://www.w3.org/TR/css-text-decor-3/#propdef-text-decoration-line) property.\n- [ ] A [`text-decoration-style`](https://www.w3.org/TR/css-text-decor-3/#propdef-text-decoration-style) property.\n- [ ] A [`text-decoration-color`](https://www.w3.org/TR/css-text-decor-3/#propdef-text-decoration-color) property.\n- [ ] A [`text-underline-position`](https://www.w3.org/TR/css-text-decor-3/#propdef-text-underline-position) property.\n- [ ] A [`text-decoration-fill`](https://www.w3.org/TR/SVG2/text.html#TextDecorationFillStroke) property.\n- [ ] A [`text-decoration-stroke`](https://www.w3.org/TR/SVG2/text.html#TextDecorationFillStroke) property.\n- [ ] A [`inline-size`](https://www.w3.org/TR/SVG2/text.html#InlineSize) property.\n- [ ] A [`shape-inside`](https://www.w3.org/TR/SVG2/text.html#TextShapeInside) property.\n- [ ] A [`shape-subtract`](https://www.w3.org/TR/SVG2/text.html#TextShapeSubtract) property.\n- [ ] A [`shape-image-threshold`](https://www.w3.org/TR/SVG2/text.html#TextShapeImageThreshold) property.\n- [ ] A [`shape-margin`](https://www.w3.org/TR/SVG2/text.html#TextShapeMargin) property.\n- [ ] A [`shape-padding`](https://www.w3.org/TR/SVG2/text.html#TextShapePadding) property.\n- [ ] New variants to [`font-variant`](https://drafts.csswg.org/css-fonts-3/#font-variant-prop) property. Previously it allowed only `small-caps`.\n- [x] A `font-variant-css21` value to [`font`](https://www.w3.org/TR/css-fonts-3/#propdef-font) property.\n\n<!-- text-emphasis ? -->\n<!-- text-shadow ? -->\n\n### Changed\n\n- [x] [`textPath`](https://www.w3.org/TR/SVG2/text.html#TextPathElement) can reference [basic shapes](https://www.w3.org/TR/SVG2/shapes.html) now.\n- [ ] Since CSS Fonts Module Level 4, the [`font-weight`](https://www.w3.org/TR/css-fonts-4/#font-weight-prop) property allows any value in a 1..1000 range.\n- [x] A [`writing-mode`](https://www.w3.org/TR/SVG2/text.html#WritingModeProperty) property introduces the `horizontal-tb` and `vertical-lr` values from [CSS Writing Modes Level 3](https://www.w3.org/TR/css-writing-modes-3/#svg-writing-mode-css).\n- [ ] [`dominant-baseline`](https://www.w3.org/TR/css-inline-3/#propdef-dominant-baseline) is inherited now.\n- [ ] [`baseline-shift`](https://www.w3.org/TR/css-inline-3/#propdef-baseline-shift) is `0` by default, instead of `baseline`.\n- [ ] Percentage values in a [`word-spacing`](https://www.w3.org/TR/css-text-3/#word-spacing-property) relate to a percentage of the affected character's width and not to viewport size now.\n- [ ] `filter`, `clip-path`, `mask` and `opacity` properties can be set on `tspan` and `textPath` elements.\n- [ ] A [`text-decoration`](https://www.w3.org/TR/css-text-decor-3/#propdef-text-decoration) property has a new, but backward compatible syntax.\n\n### Removed\n\n- A [`tref`](https://www.w3.org/TR/SVG11/text.html#TRefElement) element.\n- A [`kerning`](https://www.w3.org/TR/SVG11/text.html#KerningProperty) property. Use [`font-kerning`](https://www.w3.org/TR/css-fonts-3/#font-kerning-prop) instead.\n- A [`glyph-orientation-horizontal`](https://www.w3.org/TR/SVG11/text.html#GlyphOrientationHorizontalProperty) property.\n- A [`altGlyph`](https://www.w3.org/TR/SVG11/text.html#AltGlyphElement) element.\n- A [`altGlyphDef`](https://www.w3.org/TR/SVG11/text.html#AltGlyphDefElement) element.\n- A [`altGlyphItem`](https://www.w3.org/TR/SVG11/text.html#AltGlyphItemElement) element.\n- A [`glyphRef`](https://www.w3.org/TR/SVG11/text.html#GlyphRefElement) element.\n- `reset-size`, `use-script` and `no-change` variants from [`dominant-baseline`](https://www.w3.org/TR/css-inline-3/#propdef-dominant-baseline).\n- `auto`, `before-edge`, and `after-edge` variants from [`alignment-baseline`](https://www.w3.org/TR/css-inline-3/#propdef-alignment-baseline).\n- `baseline` variant from [`baseline-shift`](https://www.w3.org/TR/css-inline-3/#propdef-baseline-shift).\n- Percentage values from [`letter-spacing`](https://www.w3.org/TR/css-text-3/#letter-spacing-property).\n\n### Deprecated\n\n- A [`xml:space`](https://www.w3.org/TR/SVG11/struct.html#XMLSpaceAttribute) property.\n- A [`glyph-orientation-vertical`](https://www.w3.org/TR/SVG2/text.html#GlyphOrientationVerticalProperty) property.\n- A [`baseline-shift`](https://www.w3.org/TR/SVG2/text.html#BaselineShiftProperty) property. Use [`vertical-align`](https://drafts.csswg.org/css-inline/#transverse-alignment) instead.\n\n### Quirks\n\n- As of 2021, only Inkscape has [Text layout – Content Area](https://www.w3.org/TR/SVG2/text.html#TextLayoutContentArea) support, but still a very minimal one.\n- `text-transform` is technically a CSS 2.1 property, but was not allowed in SVG 1.1\n\n<!-- ----------------------------------- -->\n\n## Painting\n\n### Added\n\n- [ ] An `arcs` variant to the [`stroke-linejoin`](https://www.w3.org/TR/SVG2/painting.html#LineJoin) property.\n- [x] A `miter-clip` variant to the [`stroke-linejoin`](https://www.w3.org/TR/SVG2/painting.html#LineJoin) property.\n- [x] (partial support) A [`paint-order`](https://www.w3.org/TR/SVG2/painting.html#PaintOrder) property.\n- [x] `context-fill` and `context-stroke` variants to the [`<paint>`](https://www.w3.org/TR/SVG2/painting.html#SpecifyingPaint) type.\n- [x] A [`mix-blend-mode`](https://www.w3.org/TR/compositing-1/#mix-blend-mode) property.\n- [x] An [`isolation`](https://www.w3.org/TR/compositing-1/#isolation) property.\n- [ ] `left`, `center` and `right` variants to `refX` and `refY` properties of the [`marker`](https://www.w3.org/TR/SVG2/painting.html#MarkerElement) element.\n- [x] An `auto-start-reverse` variant to [`orient`](https://www.w3.org/TR/SVG2/painting.html#OrientAttribute) property of the [`marker`](https://www.w3.org/TR/SVG2/painting.html#MarkerElement) element\n- [x] The `image-rendering` can appear as a presentation attribute with additional possible values. Currently, there is only best-effort support for \"pixelated\".\n### Changed\n\n- [x] Markers can be set on all shapes and not only on `path`.\n\n### Quirks\n\n- As of 2021, no one supports `stroke-linejoin:arcs`.\n\n<!-- ----------------------------------- -->\n\n## Gradients and Patterns\n\n### Added\n\n- [ ] A [`fr`](https://www.w3.org/TR/SVG2/pservers.html#RadialGradientElementFRAttribute) attribute to the `radialGradient` element\n\n<!-- ----------------------------------- -->\n\n## Clipping, Masking and Compositing\n\n### Added\n\n- [ ] [`<basic-shape>`](https://www.w3.org/TR/css-shapes-1/#typedef-basic-shape) and [`<geometry-box>`](https://www.w3.org/TR/css-masking-1/#typedef-geometry-box) variants to the [`clip-path`](https://www.w3.org/TR/css-masking-1/#the-clip-path) property.\n- [ ] A [`mask-image`](https://www.w3.org/TR/css-masking-1/#the-mask-image) property.\n- [ ] A [`mask-mode`](https://www.w3.org/TR/css-masking-1/#the-mask-mode) property.\n- [ ] A [`mask-position`](https://www.w3.org/TR/css-masking-1/#the-mask-position) property.\n- [ ] A [`mask-clip`](https://www.w3.org/TR/css-masking-1/#the-mask-clip) property.\n- [ ] A [`mask-origin`](https://www.w3.org/TR/css-masking-1/#the-mask-origin) property.\n- [ ] A [`mask-size`](https://www.w3.org/TR/css-masking-1/#the-mask-size) property.\n- [ ] A [`mask-composite`](https://www.w3.org/TR/css-masking-1/#the-mask-composite) property.\n- [x] A [`mask-type`](https://www.w3.org/TR/css-masking-1/#the-mask-type) property.\n- [ ] A [`mask-border-source`](https://www.w3.org/TR/css-masking-1/#the-mask-border-source) property.\n- [ ] A [`mask-border-mode`](https://www.w3.org/TR/css-masking-1/#the-mask-border-mode) property.\n- [ ] A [`mask-border-slice`](https://www.w3.org/TR/css-masking-1/#the-mask-border-slice) property.\n- [ ] A [`mask-border-width`](https://www.w3.org/TR/css-masking-1/#the-mask-border-width) property.\n- [ ] A [`mask-border-outset`](https://www.w3.org/TR/css-masking-1/#the-mask-border-outset) property.\n- [ ] A [`mask-border-repeat`](https://www.w3.org/TR/css-masking-1/#the-mask-border-repeat) property.\n- [ ] A [`mask-border`](https://www.w3.org/TR/css-masking-1/#the-mask-border) property.\n\n### Changed\n\n- [ ] A [`mask`](https://www.w3.org/TR/css-masking-1/#the-mask) property has [a new grammar](https://www.w3.org/TR/css-masking-1/#typedef-mask-layer), backward compatible with SVG 1.1 one.\n- [ ] An element can have multiple masks now.\n\n<!-- ----------------------------------- -->\n\n## Filter Effects\n\n### Added\n\n- [x] A [`feDropShadow`](https://www.w3.org/TR/filter-effects-1/#feDropShadowElement) element.\n- [ ] An [`edgeMode`](https://www.w3.org/TR/filter-effects-1/#element-attrdef-fegaussianblur-edgemode) attribute to `feGaussianBlur` element.\n- [x] [Filter functions](https://www.w3.org/TR/filter-effects-1/#filter-functions).\n- [x] New [blend modes](https://www.w3.org/TR/compositing-1/#ltblendmodegt) to [`feBlend`](https://www.w3.org/TR/filter-effects-1/#feBlendElement) element.\n- [ ] A [`no-composite`](https://www.w3.org/TR/filter-effects-1/#element-attrdef-feblend-no-composite) property to [`feBlend`](https://www.w3.org/TR/filter-effects-1/#feBlendElement) element.\n\n### Changed\n\n- [x] A `filter` property type changed from [`<FuncIRI>`](https://www.w3.org/TR/SVG11/filters.html#FilterProperty) to [`<filter-value-list>`](https://www.w3.org/TR/filter-effects-1/#typedef-filter-value-list).\n- [ ] The [`saturate`](https://www.w3.org/TR/filter-effects-1/#element-attrdef-fecolormatrix-values) type in `feColorMatrix` can be larger than 1 now.\n\n### Deprecated\n\n- [x] An [`enable-background`](https://www.w3.org/TR/filter-effects-1/#AccessBackgroundImage) property.\n\n### Quirks\n\n- [Filter functions](https://www.w3.org/TR/filter-effects-1/#filter-functions) doesn't have a [filter region](https://www.w3.org/TR/filter-effects-1/#filter-region). Which means `blur()` and `drop-shadow()` cannot be losslessly converted to `filter` element. We have to manually calculate a new region (somehow).\n- [Filter functions](https://www.w3.org/TR/filter-effects-1/#filter-functions) are always in sRGB color space, unlike a `filter` element, which is in linearRGB by default.\n\n<!-- ----------------------------------- -->\n\n## Linking\n\n### Deprecated\n\n- `xlink:href` in favor of `href`.\n\n<!-- ----------------------------------- -->\n\n## Fonts\n\n### Removed\n\n- A `font` element.\n- A `glyph` element.\n- A `missing-glyph` element.\n- A `hkern` element.\n- A `vkern` element.\n- A `font-face` element.\n- A `font-face-src` element.\n- A `font-face-uri` element.\n- A `font-face-format` element.\n- A `font-face-name` element.\n"
  },
  {
    "path": "docs/unsupported.md",
    "content": "## A list of unsupported SVG 1.1 features\n\nFor the list of unsupported SVG 2 features see: [svg2-changelog.md](./svg2-changelog.md)\n\n### Elements\n\n- Font based\n  - `altGlyph`\n  - `altGlyphDef`\n  - `altGlyphItem`\n  - `font-face-format`\n  - `font-face-name`\n  - `font-face-src`\n  - `font-face-uri`\n  - `font-face`\n  - `font`\n  - `glyph`\n  - `glyphRef`\n  - `hkern`\n  - `missing-glyph`\n  - `vkern`\n- `color-profile`\n- `use` with a reference to an external SVG file\n\n### Attributes\n\n- `clip` (deprecated in the SVG 2)\n- `color-interpolation`\n- `color-profile`\n- `color-rendering`\n- `direction`\n- `font-size-adjust`\n- `font-stretch`\n- `glyph-orientation-horizontal` (removed in the SVG 2)\n- `glyph-orientation-vertical` (deprecated in the SVG 2)\n- `kerning` (removed in the SVG 2)\n- `unicode-bidi`\n\n**Note:** this list does not include elements and attributes outside the\n[static SVG](http://www.w3.org/TR/SVG11/feature#SVG-static) subset.\n"
  },
  {
    "path": "tools/explorer-thumbnailer/Cargo.toml",
    "content": "[package]\nname = \"explorer-thumbnailer\"\nversion = \"0.47.0\"\nlicense.workspace = true\nedition = \"2021\"\npublish = false\n\n# TODO: Remove this workspace.package section when this package is part of the repo's workspace.\n[workspace.package]\nlicense = \"Apache-2.0 OR MIT\"\n\n[dependencies]\ncom = \"0.2\"\nwinapi = { version = \"0.3.9\", features = [\"impl-default\", \"objidlbase\", \"windef\", \"wingdi\"] }\nlog = \"0.4\"\nwinlog = \"0.2\"\nresvg = { path = \"../../crates/resvg\" }\n\n[lib]\n# name can only be \"server.dll\", see https://github.com/microsoft/com-rs/issues/157\nname = \"server\"\ncrate-type = [\"rlib\", \"cdylib\"]\n"
  },
  {
    "path": "tools/explorer-thumbnailer/LICENSE-APACHE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "tools/explorer-thumbnailer/LICENSE-MIT",
    "content": "Copyright 2017 the Resvg Authors\n\nPermission is hereby granted, free of charge, to any\nperson obtaining a copy of this software and associated\ndocumentation files (the \"Software\"), to deal in the\nSoftware without restriction, including without\nlimitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software\nis furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice\nshall be included in all copies or substantial portions\nof the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF\nANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED\nTO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\nPARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT\nSHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR\nIN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "tools/explorer-thumbnailer/LICENSE-SUMMARY.txt",
    "content": "Licensed under either of\n\n- Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)\n- MIT license (http://opensource.org/licenses/MIT)\n\nat your option.\n\nYou can also find the text of these licenses in the installation target directory.\n"
  },
  {
    "path": "tools/explorer-thumbnailer/install/installer.iss",
    "content": "[Setup]\nAppName=\"resvg Explorer Extension\"\nAppVersion=\"0.47.0\"\nVersionInfoVersion=\"0.0.47.0\"\nAppVerName=\"resvg Explorer Extension 0.47.0\"\nAppPublisher=\"The Resvg Authors\"\nAppPublisherURL=https://github.com/linebender/resvg\nDefaultDirName=\"{pf}\\resvg Explorer Extension\"\nCompression=lzma\nSolidCompression=yes\nChangesAssociations=yes\nDisableDirPage=yes\nDisableProgramGroupPage=yes\nArchitecturesAllowed=x64\nArchitecturesInstallIn64BitMode=x64\nOutputBaseFilename=\"resvg-explorer-extension\"\nOutputDir=.\n\n[Languages]\nName: \"en\"; MessagesFile: \"compiler:Default.isl\"; LicenseFile: \"..\\LICENSE-SUMMARY.txt\"\n\n[Files]\nSource: \"..\\target\\release\\server.dll\"; DestDir: \"{app}\"\nSource: \"..\\LICENSE-APACHE\"; DestDir: \"{app}\";\nSource: \"..\\LICENSE-MIT\"; DestDir: \"{app}\";\n\n[Registry]\nRoot: HKCR; Subkey: \"CLSID\\{{4432C229-DFD0-4B18-8C4D-F58932AF6105}\"; Flags: uninsdeletekey\nRoot: HKCR; Subkey: \"CLSID\\{{4432C229-DFD0-4B18-8C4D-F58932AF6105}\"; ValueType: string; ValueData: \"ThumbnailProvider\"\nRoot: HKCR; Subkey: \"CLSID\\{{4432C229-DFD0-4B18-8C4D-F58932AF6105}\\InprocServer32\"; ValueType: string; ValueData: \"{app}\\server.dll\"\nRoot: HKCR; Subkey: \".svg\\shellex\"; Flags: uninsdeletekeyifempty\nRoot: HKCR; Subkey: \".svg\\shellex\\{{E357FCCD-A995-4576-B01F-234630154E96}\"; Flags: uninsdeletekey\nRoot: HKCR; Subkey: \".svg\\shellex\\{{E357FCCD-A995-4576-B01F-234630154E96}\"; ValueType: string; ValueData: \"{{4432C229-DFD0-4B18-8C4D-F58932AF6105}\"\nRoot: HKLM; Subkey: \"SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\resvg Thumbnailer\"; Flags: uninsdeletekey\nRoot: HKLM; Subkey: \"SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\resvg Thumbnailer\"; ValueType: string; ValueName: \"EventMessageFile\"; ValueData: \"{app}\\server.dll\"\n"
  },
  {
    "path": "tools/explorer-thumbnailer/src/error.rs",
    "content": "// Copyright 2020 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse com::sys::{E_POINTER, HRESULT, S_FALSE};\nuse resvg::usvg;\nuse Error::*;\n\n#[derive(Debug)]\npub enum Error {\n    IStreamStat(HRESULT),\n    IStreamRead(HRESULT),\n    TreeError(usvg::Error),\n    TreeEmpty,\n    CreateDIBSectionError,\n    RenderError,\n}\n\nimpl std::fmt::Display for Error {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {\n        match self {\n            IStreamStat(code) => write!(f, \"IStream::stat failed with error {}\", code),\n            IStreamRead(code) => write!(f, \"IStream::read failed with error {}\", code),\n            TreeError(err) => write!(f, \"Tree::from_data failed with error \\\"{}\\\"\", err),\n            TreeEmpty => write!(f, \"SVG tree was not initialized\"),\n            CreateDIBSectionError => write!(f, \"CreateDIBSection failed\"),\n            RenderError => write!(f, \"resvg::render returned None\"),\n        }\n    }\n}\n\nimpl std::error::Error for Error {\n    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {\n        match &*self {\n            IStreamStat(_) | IStreamRead(_) | TreeEmpty | CreateDIBSectionError | RenderError => {\n                None\n            }\n            TreeError(source) => Some(source),\n        }\n    }\n}\n\nimpl From<Error> for HRESULT {\n    fn from(err: Error) -> Self {\n        match err {\n            IStreamStat(code) | IStreamRead(code) => code,\n            TreeError(_) | TreeEmpty | RenderError => S_FALSE,\n            CreateDIBSectionError => E_POINTER,\n        }\n    }\n}\n"
  },
  {
    "path": "tools/explorer-thumbnailer/src/interfaces/iinitialize_with_stream.rs",
    "content": "// Copyright 2020 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse com::{com_interface, interfaces::iunknown::IUnknown, sys::HRESULT};\nuse winapi::shared::minwindef::DWORD;\nuse winapi::um::objidlbase::LPSTREAM;\n\n#[com_interface(\"B824B49D-22AC-4161-AC8A-9916E8FA3F7F\")]\npub trait IInitializeWithStream: IUnknown {\n    unsafe fn read(&self, pstream: LPSTREAM, grf_mode: DWORD) -> HRESULT;\n}\n"
  },
  {
    "path": "tools/explorer-thumbnailer/src/interfaces/ithumbnail_provider.rs",
    "content": "// Copyright 2020 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse com::com_interface;\nuse com::interfaces::iunknown::IUnknown;\nuse com::sys::HRESULT;\nuse winapi::shared::minwindef::UINT;\nuse winapi::shared::windef::HBITMAP;\n\n#[com_interface(\"E357FCCD-A995-4576-B01F-234630154E96\")]\npub trait IThumbnailProvider: IUnknown {\n    unsafe fn get_thumbnail(&self, cx: UINT, phbmp: *mut HBITMAP, pdw_alpha: *mut UINT) -> HRESULT;\n}\n"
  },
  {
    "path": "tools/explorer-thumbnailer/src/interfaces/mod.rs",
    "content": "// Copyright 2020 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\npub mod iinitialize_with_stream;\npub mod ithumbnail_provider;\n\npub use iinitialize_with_stream::IInitializeWithStream;\npub use ithumbnail_provider::IThumbnailProvider;\n"
  },
  {
    "path": "tools/explorer-thumbnailer/src/lib.rs",
    "content": "// Copyright 2020 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse com::registration::{dll_register_server, dll_unregister_server, RegistryKeyInfo};\nuse thumbnail_provider::{ThumbnailProvider, CLSID_THUMBNAIL_PROVIDER_CLASS};\n\nmod error;\nmod interfaces;\nmod thumbnail_provider;\nmod utils;\n\n// we replace the com::registration::inproc_dll_module macro in order to be able\n// to modify DllRegisterServer and DllUnregisterServer functions\nmacro_rules! inproc_dll_module {\n    (($class_id_one:ident, $class_type_one:ty), $(($class_id:ident, $class_type:ty)),*) => {\n        #[no_mangle]\n        extern \"stdcall\" fn DllGetClassObject(class_id: *const com::sys::CLSID, iid: *const com::sys::IID, result: *mut *mut std::ffi::c_void) -> com::sys::HRESULT {\n            use com::registration::initialize_class_object;\n            assert!(!class_id.is_null(), \"class id passed to DllGetClassObject should never be null\");\n\n            let class_id = unsafe { &*class_id };\n            if class_id == &$class_id_one {\n                let instance = <$class_type_one>::get_class_object();\n                initialize_class_object(instance, iid, result)\n            } $(else if class_id == &$class_id {\n                let instance = <$class_type>::get_class_object();\n                initialize_class_object(instance, iid, result)\n            })* else {\n                com::sys::CLASS_E_CLASSNOTAVAILABLE\n            }\n        }\n\n        fn get_relevant_registry_keys() -> Vec<com::registration::RegistryKeyInfo> {\n            use com::registration::RegistryKeyInfo;\n            let file_path = com::registration::get_dll_file_path();\n            vec![\n                RegistryKeyInfo::new(\n                    &com::registration::class_key_path($class_id_one),\n                    \"\",\n                    stringify!($class_type_one),\n                ),\n                RegistryKeyInfo::new(\n                    &com::registration::class_inproc_key_path($class_id_one),\n                    \"\",\n                    &file_path,\n                ),\n                $(RegistryKeyInfo::new(\n                    &com::registration::class_key_path($class_id),\n                    \"\",\n                    stringify!($class_type),\n                ),\n                RegistryKeyInfo::new(\n                    &com::registration::class_inproc_key_path($class_id),\n                    \"\",\n                    &file_path,\n                )),*\n            ]\n        }\n    };\n}\n\nstatic WINLOG_SOURCE: &'static str = \"reSVG Thumbnailer\";\n\ninproc_dll_module![(CLSID_THUMBNAIL_PROVIDER_CLASS, ThumbnailProvider),];\n\nfn get_all_relevant_registry_keys() -> Vec<RegistryKeyInfo> {\n    let mut res = get_relevant_registry_keys();\n    res.extend(vec![RegistryKeyInfo::new(\n        \".SVG\\\\shellex\\\\{E357FCCD-A995-4576-B01F-234630154E96}\",\n        \"\",\n        \"{4432C229-DFD0-4B18-8C4D-F58932AF6105}\",\n    )]);\n\n    res\n}\n\n#[no_mangle]\nextern \"stdcall\" fn DllRegisterServer() -> com::sys::HRESULT {\n    if winlog::try_register(WINLOG_SOURCE).is_err() {\n        return com::sys::SELFREG_E_CLASS;\n    }\n    dll_register_server(&mut get_all_relevant_registry_keys())\n}\n\n#[no_mangle]\nextern \"stdcall\" fn DllUnregisterServer() -> com::sys::HRESULT {\n    if winlog::try_deregister(WINLOG_SOURCE).is_err() {\n        return com::sys::SELFREG_E_CLASS;\n    }\n    dll_unregister_server(&mut get_all_relevant_registry_keys())\n}\n"
  },
  {
    "path": "tools/explorer-thumbnailer/src/thumbnail_provider.rs",
    "content": "// Copyright 2020 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse crate::interfaces::{IInitializeWithStream, IThumbnailProvider};\nuse crate::utils::{img_to_hbitmap, render_thumbnail, tree_from_istream};\nuse crate::WINLOG_SOURCE;\nuse com::co_class;\nuse com::sys::{HRESULT, IID, S_OK};\nuse log::error;\nuse resvg::usvg;\nuse std::cell::RefCell;\nuse winapi::shared::minwindef::{DWORD, UINT};\nuse winapi::shared::windef::HBITMAP;\nuse winapi::um::objidlbase::LPSTREAM;\n\n// {4432C229-DFD0-4B18-8C4D-F58932AF6105}\npub const CLSID_THUMBNAIL_PROVIDER_CLASS: IID = IID {\n    data1: 0x4432c229,\n    data2: 0xdfd0,\n    data3: 0x4b18,\n    data4: [0x8c, 0x4d, 0xf5, 0x89, 0x32, 0xaf, 0x61, 0x5],\n};\n\n#[co_class(implements(IThumbnailProvider, IInitializeWithStream))]\npub struct ThumbnailProvider {\n    tree: RefCell<Option<usvg::Tree>>,\n}\n\nimpl IInitializeWithStream for ThumbnailProvider {\n    unsafe fn read(&self, pstream: LPSTREAM, _grf_mode: DWORD) -> HRESULT {\n        tree_from_istream(pstream).map_or_else(\n            |err| {\n                error!(\"{}\", err);\n                err.into()\n            },\n            |tree| {\n                self.tree.replace(Some(tree));\n                S_OK\n            },\n        )\n    }\n}\n\nimpl IThumbnailProvider for ThumbnailProvider {\n    unsafe fn get_thumbnail(&self, cx: UINT, phbmp: *mut HBITMAP, pdw_alpha: *mut UINT) -> HRESULT {\n        render_thumbnail(&*self.tree.borrow(), cx)\n            .and_then(|img| img_to_hbitmap(&img))\n            .map_or_else(\n                |err| {\n                    error!(\"{}\", err);\n                    err.into()\n                },\n                |hbmp| {\n                    *phbmp = hbmp;\n                    *pdw_alpha = 2;\n                    S_OK\n                },\n            )\n    }\n}\n\nimpl ThumbnailProvider {\n    pub(crate) fn new() -> Box<ThumbnailProvider> {\n        // winlog::init fails sometimes but logging still works\n        #[allow(unused_must_use)]\n        {\n            winlog::init(WINLOG_SOURCE);\n        }\n        ThumbnailProvider::allocate(RefCell::new(None))\n    }\n}\n"
  },
  {
    "path": "tools/explorer-thumbnailer/src/utils.rs",
    "content": "// Copyright 2020 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\nuse crate::error::Error;\nuse com::sys::S_OK;\nuse resvg::{tiny_skia, usvg};\nuse std::mem;\nuse std::ptr;\nuse usvg::fontdb;\nuse winapi::ctypes::c_void;\nuse winapi::shared::minwindef::ULONG;\nuse winapi::shared::windef::{HBITMAP, HDC};\nuse winapi::um::objidlbase::{LPSTREAM, STATSTG};\nuse winapi::um::wingdi::{CreateDIBSection, BITMAPINFO, BITMAPINFOHEADER, BI_RGB, DIB_RGB_COLORS};\n\npub unsafe fn tree_from_istream(pstream: LPSTREAM) -> Result<usvg::Tree, Error> {\n    let mut stat: STATSTG = Default::default();\n    let stat_res = (*pstream).Stat(&mut stat, 0);\n    if stat_res != S_OK {\n        return Err(Error::IStreamStat(stat_res));\n    }\n\n    let size = *stat.cbSize.QuadPart();\n    let mut svg_data = Vec::with_capacity(size as usize);\n    let mut len: ULONG = 0;\n    let read_res = (*pstream).Read(svg_data.as_mut_ptr() as *mut c_void, size as u32, &mut len);\n    if read_res != S_OK {\n        return Err(Error::IStreamRead(read_res));\n    }\n    svg_data.set_len(len as usize);\n\n    let mut opt = usvg::Options::default();\n    opt.fontdb_mut().load_system_fonts();\n\n    let tree = usvg::Tree::from_data(&svg_data, &opt).map_err(|e| Error::TreeError(e))?;\n    Ok(tree)\n}\n\npub fn render_thumbnail(tree: &Option<usvg::Tree>, cx: u32) -> Result<tiny_skia::Pixmap, Error> {\n    if cx == 0 {\n        return Err(Error::RenderError);\n    }\n\n    let tree = tree.as_ref().ok_or(Error::TreeEmpty)?;\n\n    let size = if tree.size().width() > tree.size().height() {\n        tree.size().to_int_size().scale_to_width(cx)\n    } else {\n        tree.size().to_int_size().scale_to_height(cx)\n    }\n    .ok_or(Error::RenderError)?;\n\n    let transform = tiny_skia::Transform::from_scale(\n        size.width() as f32 / tree.size().width() as f32,\n        size.height() as f32 / tree.size().height() as f32,\n    );\n\n    let mut pixmap = tiny_skia::Pixmap::new(size.width(), size.height()).unwrap();\n    resvg::render(&tree, transform, &mut pixmap.as_mut());\n    Ok(pixmap)\n}\n\npub unsafe fn img_to_hbitmap(img: &tiny_skia::Pixmap) -> Result<HBITMAP, Error> {\n    let hdc: HDC = ptr::null_mut();\n    let mut bmi: BITMAPINFO = Default::default();\n    bmi.bmiHeader.biSize = mem::size_of::<BITMAPINFOHEADER>() as u32;\n    bmi.bmiHeader.biPlanes = 1;\n    bmi.bmiHeader.biBitCount = 32;\n    bmi.bmiHeader.biCompression = BI_RGB;\n    bmi.bmiHeader.biWidth = img.width() as i32;\n    bmi.bmiHeader.biHeight = -(img.height() as i32);\n    let mut ppv_bits = ptr::null_mut();\n\n    let hbitmap = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, &mut ppv_bits, ptr::null_mut(), 0);\n    if hbitmap as *const c_void == ptr::null() {\n        return Err(Error::CreateDIBSectionError);\n    }\n\n    let mut i = 0;\n    let ppv_bits = ppv_bits as *mut u8;\n    for px in img.pixels() {\n        let px = px.demultiply();\n        ptr::write(ppv_bits.offset(i + 0), px.blue());\n        ptr::write(ppv_bits.offset(i + 1), px.green());\n        ptr::write(ppv_bits.offset(i + 2), px.red());\n        ptr::write(ppv_bits.offset(i + 3), px.alpha());\n        i += 4;\n    }\n    Ok(hbitmap)\n}\n"
  },
  {
    "path": "tools/viewsvg/.gitignore",
    "content": "*.pro.user\n"
  },
  {
    "path": "tools/viewsvg/README.md",
    "content": "# viewsvg\n\nA simple SVG viewer using resvg-qt.\n\n## Dependencies\n\n- Qt >= 5.6\n\n## Build\n\nNote: make sure you have read the parent readme.\n\n```sh\n# build C-API first\ncargo build --release --manifest-path ../../crates/c-api/Cargo.toml\n# build viewsvg\nqmake\nmake\n# run\n./viewsvg\n```\n"
  },
  {
    "path": "tools/viewsvg/main.cpp",
    "content": "// Copyright 2017 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\n#include <QApplication>\n\n#include \"mainwindow.h\"\n\nint main(int argc, char *argv[])\n{\n    QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);\n\n    QApplication a(argc, argv);\n\n    MainWindow w;\n    w.show();\n\n    return a.exec();\n}\n"
  },
  {
    "path": "tools/viewsvg/mainwindow.cpp",
    "content": "// Copyright 2017 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\n#include <QMessageBox>\n#include <QTimer>\n\n#include \"mainwindow.h\"\n#include \"ui_mainwindow.h\"\n\nMainWindow::MainWindow(QWidget *parent)\n    : QMainWindow(parent)\n    , ui(new Ui::MainWindow)\n{\n    ui->setupUi(this);\n\n    SvgView::init();\n\n    ui->cmbBoxSize->setCurrentIndex(1);\n    ui->cmbBoxBackground->setCurrentIndex(1);\n\n    ui->svgView->setFitToView(true);\n    ui->svgView->setBackground(SvgView::Background::White);\n\n    connect(ui->svgView, &SvgView::loadError, this, [this](const QString &msg){\n        QMessageBox::critical(this, \"Error\", msg);\n    });\n\n    QTimer::singleShot(5, this, &MainWindow::onStart);\n}\n\nMainWindow::~MainWindow()\n{\n    delete ui;\n}\n\nvoid MainWindow::onStart()\n{\n    ui->svgView->setFocus();\n\n    const auto args = QCoreApplication::arguments();\n    if (args.size() != 2) {\n        return;\n    }\n\n    ui->svgView->loadFile(args.at(1));\n}\n\nvoid MainWindow::on_cmbBoxSize_activated(int index)\n{\n    ui->svgView->setFitToView(index == 1);\n}\n\nvoid MainWindow::on_cmbBoxBackground_activated(int index)\n{\n    ui->svgView->setBackground(SvgView::Background(index));\n}\n\nvoid MainWindow::on_chBoxDrawBorder_toggled(bool checked)\n{\n    ui->svgView->setDrawImageBorder(checked);\n}\n"
  },
  {
    "path": "tools/viewsvg/mainwindow.h",
    "content": "// Copyright 2017 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\n#pragma once\n\n#include <QMainWindow>\n\nnamespace Ui { class MainWindow; }\n\nclass MainWindow : public QMainWindow\n{\n    Q_OBJECT\n\npublic:\n    explicit MainWindow(QWidget *parent = nullptr);\n    ~MainWindow();\n\nprivate slots:\n    void onStart();\n    void on_cmbBoxBackground_activated(int index);\n    void on_chBoxDrawBorder_toggled(bool checked);\n    void on_cmbBoxSize_activated(int index);\n\nprivate:\n    Ui::MainWindow *ui;\n};\n"
  },
  {
    "path": "tools/viewsvg/mainwindow.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>MainWindow</class>\n <widget class=\"QMainWindow\" name=\"MainWindow\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>787</width>\n    <height>424</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string>viewsvg</string>\n  </property>\n  <widget class=\"QWidget\" name=\"centralWidget\">\n   <layout class=\"QVBoxLayout\" name=\"verticalLayout_2\">\n    <property name=\"spacing\">\n     <number>6</number>\n    </property>\n    <property name=\"leftMargin\">\n     <number>0</number>\n    </property>\n    <property name=\"topMargin\">\n     <number>6</number>\n    </property>\n    <property name=\"rightMargin\">\n     <number>0</number>\n    </property>\n    <property name=\"bottomMargin\">\n     <number>0</number>\n    </property>\n    <item>\n     <layout class=\"QHBoxLayout\" name=\"horizontalLayout\">\n      <property name=\"leftMargin\">\n       <number>2</number>\n      </property>\n      <item>\n       <widget class=\"QLabel\" name=\"label_3\">\n        <property name=\"sizePolicy\">\n         <sizepolicy hsizetype=\"Fixed\" vsizetype=\"Preferred\">\n          <horstretch>0</horstretch>\n          <verstretch>0</verstretch>\n         </sizepolicy>\n        </property>\n        <property name=\"text\">\n         <string>Size:</string>\n        </property>\n       </widget>\n      </item>\n      <item>\n       <widget class=\"QComboBox\" name=\"cmbBoxSize\">\n        <item>\n         <property name=\"text\">\n          <string>Original</string>\n         </property>\n        </item>\n        <item>\n         <property name=\"text\">\n          <string>Fit to View</string>\n         </property>\n        </item>\n       </widget>\n      </item>\n      <item>\n       <spacer name=\"horizontalSpacer\">\n        <property name=\"orientation\">\n         <enum>Qt::Horizontal</enum>\n        </property>\n        <property name=\"sizeType\">\n         <enum>QSizePolicy::Fixed</enum>\n        </property>\n        <property name=\"sizeHint\" stdset=\"0\">\n         <size>\n          <width>20</width>\n          <height>1</height>\n         </size>\n        </property>\n       </spacer>\n      </item>\n      <item>\n       <widget class=\"QLabel\" name=\"label_5\">\n        <property name=\"text\">\n         <string>Background:</string>\n        </property>\n       </widget>\n      </item>\n      <item>\n       <widget class=\"QComboBox\" name=\"cmbBoxBackground\">\n        <item>\n         <property name=\"text\">\n          <string>None</string>\n         </property>\n        </item>\n        <item>\n         <property name=\"text\">\n          <string>White</string>\n         </property>\n        </item>\n        <item>\n         <property name=\"text\">\n          <string>Check board</string>\n         </property>\n        </item>\n       </widget>\n      </item>\n      <item>\n       <spacer name=\"horizontalSpacer_3\">\n        <property name=\"orientation\">\n         <enum>Qt::Horizontal</enum>\n        </property>\n        <property name=\"sizeType\">\n         <enum>QSizePolicy::Fixed</enum>\n        </property>\n        <property name=\"sizeHint\" stdset=\"0\">\n         <size>\n          <width>20</width>\n          <height>1</height>\n         </size>\n        </property>\n       </spacer>\n      </item>\n      <item>\n       <widget class=\"QCheckBox\" name=\"chBoxDrawBorder\">\n        <property name=\"text\">\n         <string>Draw image border</string>\n        </property>\n       </widget>\n      </item>\n      <item>\n       <spacer name=\"horizontalSpacer_2\">\n        <property name=\"orientation\">\n         <enum>Qt::Horizontal</enum>\n        </property>\n        <property name=\"sizeHint\" stdset=\"0\">\n         <size>\n          <width>40</width>\n          <height>20</height>\n         </size>\n        </property>\n       </spacer>\n      </item>\n     </layout>\n    </item>\n    <item>\n     <widget class=\"SvgView\" name=\"svgView\" native=\"true\">\n      <property name=\"sizePolicy\">\n       <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Expanding\">\n        <horstretch>0</horstretch>\n        <verstretch>0</verstretch>\n       </sizepolicy>\n      </property>\n     </widget>\n    </item>\n   </layout>\n  </widget>\n </widget>\n <layoutdefault spacing=\"6\" margin=\"11\"/>\n <customwidgets>\n  <customwidget>\n   <class>SvgView</class>\n   <extends>QWidget</extends>\n   <header>svgview.h</header>\n   <container>1</container>\n  </customwidget>\n </customwidgets>\n <resources/>\n <connections/>\n</ui>\n"
  },
  {
    "path": "tools/viewsvg/svgview.cpp",
    "content": "// Copyright 2017 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\n#include <QDropEvent>\n#include <QElapsedTimer>\n#include <QFileInfo>\n#include <QMessageBox>\n#include <QMimeData>\n#include <QPainter>\n#include <QThread>\n#include <QTimer>\n\n#include \"svgview.h\"\n\nSvgViewWorker::SvgViewWorker(QObject *parent)\n    : QObject(parent)\n    , m_dpiRatio(qApp->screens().first()->devicePixelRatio())\n{\n    m_opt.loadSystemFonts();\n}\n\nQRect SvgViewWorker::viewBox() const\n{\n    QMutexLocker lock(&m_mutex);\n    return m_renderer.viewBox();\n}\n\nQString SvgViewWorker::loadData(const QByteArray &data)\n{\n    QMutexLocker lock(&m_mutex);\n\n    m_renderer.load(data, m_opt);\n    if (!m_renderer.isValid()) {\n        return m_renderer.errorString();\n    }\n\n    return QString();\n}\n\nQString SvgViewWorker::loadFile(const QString &path)\n{\n    QMutexLocker lock(&m_mutex);\n\n    m_opt.setResourcesDir(QFileInfo(path).absolutePath());\n    m_renderer.load(path, m_opt);\n    if (!m_renderer.isValid()) {\n        return m_renderer.errorString();\n    }\n\n    return QString();\n}\n\nvoid SvgViewWorker::render(const QSize &viewSize)\n{\n    Q_ASSERT(QThread::currentThread() != qApp->thread());\n\n    QMutexLocker lock(&m_mutex);\n\n    if (m_renderer.isEmpty()) {\n        return;\n    }\n\n    QElapsedTimer timer;\n    timer.start();\n\n    const auto s = m_renderer.defaultSize().scaled(viewSize, Qt::KeepAspectRatio);\n    auto img = m_renderer.renderToImage(s * m_dpiRatio);\n    img.setDevicePixelRatio(m_dpiRatio);\n\n    qDebug() << QString(\"Render in %1ms\").arg(timer.elapsed());\n\n    emit rendered(img);\n}\n\nstatic QImage genCheckedTexture()\n{\n    int l = 20;\n\n    QImage pix = QImage(l, l, QImage::Format_RGB32);\n    int b = pix.width() / 2.0;\n    pix.fill(QColor(\"#c0c0c0\"));\n\n    QPainter p;\n    p.begin(&pix);\n    p.fillRect(QRect(0,0,b,b), QColor(\"#808080\"));\n    p.fillRect(QRect(b,b,b,b), QColor(\"#808080\"));\n    p.end();\n\n    return pix;\n}\n\nSvgView::SvgView(QWidget *parent)\n    : QWidget(parent)\n    , m_checkboardImg(genCheckedTexture())\n    , m_worker(new SvgViewWorker())\n    , m_resizeTimer(new QTimer(this))\n{\n    setAcceptDrops(true);\n    setMinimumSize(10, 10);\n\n    QThread *th = new QThread(this);\n    m_worker->moveToThread(th);\n    th->start();\n\n    const auto *screen = qApp->screens().first();\n    m_dpiRatio = screen->devicePixelRatio();\n\n    connect(m_worker, &SvgViewWorker::rendered, this, &SvgView::onRendered);\n\n    m_resizeTimer->setSingleShot(true);\n    connect(m_resizeTimer, &QTimer::timeout, this, &SvgView::requestUpdate);\n}\n\nSvgView::~SvgView()\n{\n    QThread *th = m_worker->thread();\n    th->quit();\n    th->wait(10000);\n    delete m_worker;\n}\n\nvoid SvgView::init()\n{\n    ResvgRenderer::initLog();\n}\n\nvoid SvgView::setFitToView(bool flag)\n{\n    m_isFitToView = flag;\n    requestUpdate();\n}\n\nvoid SvgView::setBackground(SvgView::Background background)\n{\n    m_background = background;\n    update();\n}\n\nvoid SvgView::setDrawImageBorder(bool flag)\n{\n    m_isDrawImageBorder = flag;\n    update();\n}\n\nvoid SvgView::loadData(const QByteArray &ba)\n{\n    const QString errMsg = m_worker->loadData(ba);\n    afterLoad(errMsg);\n}\n\nvoid SvgView::loadFile(const QString &path)\n{\n    const QString errMsg = m_worker->loadFile(path);\n    afterLoad(errMsg);\n}\n\nvoid SvgView::afterLoad(const QString &errMsg)\n{\n    m_img = QImage();\n\n    if (errMsg.isEmpty()) {\n        m_isHasImage = true;\n        requestUpdate();\n    } else {\n        emit loadError(errMsg);\n        m_isHasImage = false;\n        update();\n    }\n}\n\nvoid SvgView::drawSpinner(QPainter &p)\n{\n    const int outerRadius = 20;\n    const int innerRadius = outerRadius * 0.45;\n\n    const int capsuleHeight = outerRadius - innerRadius;\n    const int capsuleWidth  = capsuleHeight * 0.35;\n    const int capsuleRadius = capsuleWidth / 2;\n\n    for (int i = 0; i < 12; ++i) {\n        QColor color = Qt::black;\n        color.setAlphaF(1.0f - (i / 12.0f));\n        p.setRenderHint(QPainter::Antialiasing);\n        p.setPen(Qt::NoPen);\n        p.setBrush(color);\n        p.save();\n        p.translate(width()/2, height()/2);\n        p.rotate(m_angle - i * 30.0f);\n        p.drawRoundedRect(-capsuleWidth * 0.5, -(innerRadius + capsuleHeight), capsuleWidth,\n                           capsuleHeight, capsuleRadius, capsuleRadius);\n        p.restore();\n    }\n}\n\nvoid SvgView::paintEvent(QPaintEvent *e)\n{\n    QPainter p(this);\n    const auto r = contentsRect();\n    p.setClipRect(r);\n\n    switch (m_background) {\n        case Background::None : break;\n        case Background::White : {\n            p.fillRect(contentsRect(), Qt::white);\n        } break;\n        case Background::CheckBoard : {\n            p.fillRect(contentsRect(), QBrush(m_checkboardImg));\n        } break;\n    }\n\n    if (m_img.isNull() && !m_timer.isActive()) {\n        p.setPen(Qt::black);\n        p.drawText(rect(), Qt::AlignCenter, \"Drop an SVG image here.\");\n    } else if (m_timer.isActive()) {\n        drawSpinner(p);\n    } else {\n        const QRect imgRect(0, 0, m_img.width() / m_dpiRatio, m_img.height() / m_dpiRatio);\n\n        p.translate(r.x() + (r.width() - imgRect.width())/ 2,\n                    r.y() + (r.height() - imgRect.height()) / 2);\n\n        p.drawImage(0, 0, m_img);\n\n        if (m_isDrawImageBorder) {\n            p.setRenderHint(QPainter::Antialiasing, false);\n            p.setPen(Qt::green);\n            p.setBrush(Qt::NoBrush);\n            p.drawRect(imgRect);\n        }\n    }\n\n    QWidget::paintEvent(e);\n}\n\nvoid SvgView::dragEnterEvent(QDragEnterEvent *event)\n{\n    event->accept();\n}\n\nvoid SvgView::dragMoveEvent(QDragMoveEvent *event)\n{\n    event->accept();\n}\n\nvoid SvgView::dropEvent(QDropEvent *event)\n{\n    const QMimeData *mime = event->mimeData();\n    if (!mime->hasUrls()) {\n        event->ignore();\n        return;\n    }\n\n    for (const QUrl &url : mime->urls()) {\n        if (!url.isLocalFile()) {\n            continue;\n        }\n\n        QString path = url.toLocalFile();\n        QFileInfo fi = QFileInfo(path);\n\n        if (fi.isFile()) {\n            QString suffix = fi.suffix().toLower();\n            if (suffix == \"svg\" || suffix == \"svgz\") {\n                loadFile(path);\n            } else {\n                QMessageBox::warning(this, tr(\"Warning\"),\n                                     tr(\"You can drop only SVG and SVGZ files.\"));\n            }\n        }\n    }\n\n    event->acceptProposedAction();\n}\n\nvoid SvgView::resizeEvent(QResizeEvent *)\n{\n    m_resizeTimer->start(200);\n}\n\nvoid SvgView::timerEvent(QTimerEvent *event)\n{\n    if (event->timerId() == m_timer.timerId()) {\n        m_angle = (m_angle + 30) % 360;\n        update();\n    } else {\n        QWidget::timerEvent(event);\n    }\n}\n\nvoid SvgView::requestUpdate()\n{\n    if (!m_isHasImage) {\n        return;\n    }\n\n    const auto s = m_isFitToView ? size() : m_worker->viewBox().size();\n\n    if (s * m_dpiRatio == m_img.size()) {\n        return;\n    }\n\n    m_timer.start(100, this);\n\n    // Run method in the m_worker thread scope.\n    QTimer::singleShot(1, m_worker, [=](){\n        m_worker->render(s);\n    });\n}\n\nvoid SvgView::onRendered(const QImage &img)\n{\n    m_timer.stop();\n\n    m_img = img;\n    update();\n}\n\n"
  },
  {
    "path": "tools/viewsvg/svgview.h",
    "content": "// Copyright 2017 the Resvg Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\n#pragma once\n\n#include <QWidget>\n#include <QMutex>\n#include <QBasicTimer>\n\n#include <ResvgQt.h>\n\nclass SvgViewWorker : public QObject\n{\n    Q_OBJECT\n\npublic:\n    SvgViewWorker(QObject *parent = nullptr);\n\n    QRect viewBox() const;\n\npublic slots:\n    QString loadData(const QByteArray &data);\n    QString loadFile(const QString &path);\n    void render(const QSize &viewSize);\n\nsignals:\n    void rendered(QImage);\n\nprivate:\n    const float m_dpiRatio;\n    mutable QMutex m_mutex;\n    ResvgOptions m_opt;\n    ResvgRenderer m_renderer;\n};\n\nclass SvgView : public QWidget\n{\n    Q_OBJECT\n\npublic:\n    enum class Background\n    {\n        None,\n        White,\n        CheckBoard,\n    };\n\n    explicit SvgView(QWidget *parent = nullptr);\n    ~SvgView();\n\n    static void init();\n\n    void setFitToView(bool flag);\n    void setBackground(Background background);\n    void setDrawImageBorder(bool flag);\n\n    void loadData(const QByteArray &data);\n    void loadFile(const QString &path);\n\nsignals:\n    void loadError(QString);\n\nprotected:\n    void paintEvent(QPaintEvent *);\n    void dragEnterEvent(QDragEnterEvent *event);\n    void dragMoveEvent(QDragMoveEvent *event);\n    void dropEvent(QDropEvent *event);\n    void resizeEvent(QResizeEvent *);\n    void timerEvent(QTimerEvent *);\n\nprivate:\n    void requestUpdate();\n    void afterLoad(const QString &errMsg);\n    void drawSpinner(QPainter &p);\n\nprivate slots:\n    void onRendered(const QImage &img);\n\nprivate:\n    const QImage m_checkboardImg;\n    SvgViewWorker * const m_worker;\n    QTimer * const m_resizeTimer;\n\n    QString m_path;\n    float m_dpiRatio = 1.0;\n    bool m_isFitToView = true;\n    Background m_background = Background::CheckBoard;\n    bool m_isDrawImageBorder = false;\n    bool m_isHasImage = false;\n    QImage m_img;\n\n    QBasicTimer m_timer;\n    int m_angle = 0;\n};\n"
  },
  {
    "path": "tools/viewsvg/viewsvg.pro",
    "content": "QT += core gui widgets\n\nTARGET = viewsvg\nTEMPLATE = app\nCONFIG += c++11\n\nSOURCES += \\\n    main.cpp \\\n    mainwindow.cpp \\\n    svgview.cpp\n\nHEADERS += \\\n    mainwindow.h \\\n    svgview.h\n\nFORMS += \\\n    mainwindow.ui\n\nCONFIG(release, debug|release): LIBS += -L$$PWD/../../target/release/ -lresvg\nelse:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../target/debug/ -lresvg\n\nwindows:LIBS += -lWs2_32 -lAdvapi32 -lBcrypt -lUserenv -lNtdll\n\nINCLUDEPATH += $$PWD/../../crates/c-api\nDEPENDPATH += $$PWD/../../crates/c-api\n"
  },
  {
    "path": "version-bump.md",
    "content": "- .github/chart.svg\n- .github/chart-svg2.svg\n- CHANGELOG.md\n- crates/usvg/Cargo.toml\n- crates/resvg/Cargo.toml\n- crates/c-api/Cargo.toml\n- crates/c-api/resvg.h\n- crates/c-api/ResvgQt.h\n- tools/explorer-thumbnailer/install/installer.iss\n- tools/explorer-thumbnailer/Cargo.toml\n"
  }
]