diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml deleted file mode 100644 index e76cc88..0000000 --- a/.github/workflows/build.yaml +++ /dev/null @@ -1,83 +0,0 @@ -name: Build - -on: - workflow_dispatch: - push: - branches: - - main - pull_request: - branches: - - main - -jobs: - build-macos: - runs-on: macos-latest - steps: - - uses: extractions/setup-just@v2 - - - name: Setup Rust - run: rustup toolchain install stable --profile minimal - - - name: install elan - run: | - set -o pipefail - curl -sSfL https://github.com/leanprover/elan/releases/download/v3.1.1/elan-aarch64-apple-darwin.tar.gz | tar xz - ./elan-init -y --no-modify-path --default-toolchain none - echo "$HOME/.elan/bin" >> $GITHUB_PATH - - - name: query clang - run: clang --version - - - uses: actions/checkout@v4 - - - name: fetch mathlib cache - run: lake exe cache get - - - name: build project - run: just build - - - name: create release tarball - run: | - tar zcf raylean_macos-aarch64.tar.gz -C .lake/build/bin raylean - - - uses: actions/upload-artifact@v4 - with: - name: macos-aarch64-binary - path: raylean_macos-aarch64.tar.gz - if-no-files-found: error - - build-linux: - runs-on: ubuntu-22.04 - steps: - - uses: extractions/setup-just@v2 - - - name: Setup Rust - run: rustup toolchain install stable --profile minimal - - - name: Install raylib deps - run: sudo apt-get install -y --no-install-recommends libglfw3 libglfw3-dev libx11-dev libxcursor-dev libxrandr-dev libxinerama-dev libxi-dev libxext-dev libxfixes-dev libwayland-dev libxkbcommon-dev - - - name: install elan - run: | - set -o pipefail - curl -sSfL https://github.com/leanprover/elan/releases/download/v3.1.1/elan-x86_64-unknown-linux-gnu.tar.gz | tar xz - ./elan-init -y --no-modify-path --default-toolchain none - echo "$HOME/.elan/bin" >> $GITHUB_PATH - - - uses: actions/checkout@v4 - - - name: fetch mathlib cache - run: lake exe cache get - - - name: build project - run: just build - - - name: create release tarball - run: | - tar zcf raylean_linux-x86_64.tar.gz -C .lake/build/bin raylean - - - uses: actions/upload-artifact@v4 - with: - name: linux_x86_64-binary - path: raylean_linux-x86_64.tar.gz - if-no-files-found: error diff --git a/.github/workflows/lean_action_ci.yml b/.github/workflows/lean_action_ci.yml new file mode 100644 index 0000000..d9245ed --- /dev/null +++ b/.github/workflows/lean_action_ci.yml @@ -0,0 +1,33 @@ +name: Lean Action CI + +on: + push: + pull_request: + workflow_dispatch: + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-22.04, macos-latest] + + steps: + - uses: actions/checkout@v4 + + - uses: extractions/setup-just@v2 + + - name: install lean + uses: leanprover/lean-action@v1 + with: + build: false + test: false + lint: false + + - name: Install raylib deps + if: runner.os == 'Linux' + run: sudo apt-get install -y --no-install-recommends libglfw3 libglfw3-dev libx11-dev libxcursor-dev libxrandr-dev libxinerama-dev libxi-dev libxext-dev libxfixes-dev libwayland-dev libxkbcommon-dev + + # inherits lake installation from the build step + - name: build + run: just build \ No newline at end of file diff --git a/.gitignore b/.gitignore index b92d942..f7179aa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,2 @@ /.lake -/lib -c/include/bundle.h -build/ +lib/ \ No newline at end of file diff --git a/README.md b/README.md index bf50089..95c6c5d 100644 --- a/README.md +++ b/README.md @@ -1,79 +1,5 @@ -# raylean +# Image Denotation -Lean4 bindings for [raylib](http://www.raylib.com/). - -## Community - -If you want to contribute to Raylean, you can find us on [Discord](https://discord.gg/mdgKuGAMQj) Discord - -## How build the demo executable target (MacOS only) - -The project comes with a demo executable target. To build and run this use the provided [justfile](./justfile). - -For now only macOS is supported. - -### Dependencies - -Raylean has several dependencies: - -* [Lean](https://lean-lang.org), which we use for development of games. -* [Just](https://github.com/casey/just), as a replacement for Make. -* XCode for macOS SDK frameworks like OpenGL. -* [Rust](https://www.rust-lang.org/) for building resvg, which provides SVG support. - -You need to install all four to build Raylean. Below follows instructions for each. - -#### [Lean](https://lean-lang.org) - -Use the [official documentation](https://lean-lang.org/lean4/doc/quickstart.html) to setup Lean. - -#### [Just command runner](https://github.com/casey/just) - -Install using [Homebrew](https://brew.sh) with: - -``` sh -brew install just -``` - -#### XCode Commandline Tools - -The macOS clang installation and macOS SDK frameworks like OpenGL are required when linking the executable. - -Install by running: - -``` sh -xcode-select --install -``` - -#### [Rust](https://www.rust-lang.org/) - -```sh -curl https://sh.rustup.rs -sSf | sh -``` - -Or use the [official documentation](https://www.rust-lang.org/tools/install) to setup Rust. - -### Build and Run - -To build the raylib static library and the demo executable, run the following command in the project: - -``` sh -just build -``` - -To run the demo, run the following command in the project: - -``` sh -just run -``` - -### Assets - -Assets used by the demo application are stored in the [resources](./resources) directory. - - -| Asset | Attribution | -|--------------------------------------|----------------------------------------------------------| -| [walter.png](./resources/walter.png) | [Liza Schulze](https://www.linkedin.com/in/lizaschulze/) | -| [Asset.svg](./resources/Asset.svg) | [Liza Schulze](https://www.linkedin.com/in/lizaschulze/) | +Proofs about Image Denotations including drawings via [raylean](https://github.com/funexists/raylean). +This work is inspired by [Functional Images - Conal Elliott](http://conal.net/papers/functional-images/) \ No newline at end of file diff --git a/build/makeBundle b/build/makeBundle new file mode 100755 index 0000000..84ad5d6 Binary files /dev/null and b/build/makeBundle differ diff --git a/build/makeBundle.c b/build/makeBundle.c new file mode 100644 index 0000000..69be3dc --- /dev/null +++ b/build/makeBundle.c @@ -0,0 +1,2085 @@ +// Lean compiler output +// Module: scripts.makeBundle +// Imports: Init +#include +#if defined(__clang__) +#pragma clang diagnostic ignored "-Wunused-parameter" +#pragma clang diagnostic ignored "-Wunused-label" +#elif defined(__GNUC__) && !defined(__CLANG__) +#pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wunused-label" +#pragma GCC diagnostic ignored "-Wunused-but-set-variable" +#endif +#ifdef __cplusplus +extern "C" { +#endif +static lean_object* l_assembleBundleFile___closed__8; +LEAN_EXPORT lean_object* _lean_main(lean_object*, lean_object*); +LEAN_EXPORT lean_object* l_List_repeatedly(lean_object*, lean_object*); +lean_object* l_System_FilePath_join(lean_object*, lean_object*); +static lean_object* l_List_chunksOf___rarg___closed__1; +LEAN_EXPORT lean_object* l_generateResourceInfoCode(lean_object*); +LEAN_EXPORT lean_object* l_generateDataCode(lean_object*); +static lean_object* l_assembleBundleFile___closed__11; +lean_object* lean_array_push(lean_object*, lean_object*); +LEAN_EXPORT lean_object* l_mkResourcesInfo(lean_object*); +static lean_object* l_generateResourceInfoSize___closed__2; +static lean_object* l_mkResourcesInfo___closed__2; +static lean_object* l_assembleBundleFile___closed__3; +uint8_t lean_uint8_land(uint8_t, uint8_t); +lean_object* l_IO_FS_DirEntry_path(lean_object*); +LEAN_EXPORT lean_object* l_Array_forIn_x27Unsafe_loop___at_mkResourcesInfo___spec__1(lean_object*, lean_object*, lean_object*, size_t, size_t, lean_object*); +static lean_object* l_generateDataCode___closed__2; +LEAN_EXPORT lean_object* l_List_dropPrefix___at_readFile___spec__1(lean_object*, lean_object*); +lean_object* lean_byte_array_push(lean_object*, uint8_t); +uint8_t lean_string_dec_eq(lean_object*, lean_object*); +lean_object* l_ByteArray_append(lean_object*, lean_object*); +lean_object* l_System_FilePath_pathExists(lean_object*, lean_object*); +lean_object* lean_string_push(lean_object*, uint32_t); +LEAN_EXPORT lean_object* l_mkResourcesInfo___boxed(lean_object*); +static lean_object* l_generateDataCode___closed__1; +static lean_object* l_Array_mapMUnsafe_map___at_generateResourceInfoCode___spec__1___closed__4; +static lean_object* l_hexEncodeByte___closed__2; +static lean_object* l_Array_mapMUnsafe_map___at_generateResourceInfoCode___spec__1___closed__1; +static lean_object* l_assembleBundleFile___closed__10; +lean_object* l_System_FilePath_isDir(lean_object*, lean_object*); +LEAN_EXPORT lean_object* l_List_chunksOf(lean_object*); +static lean_object* l_List_chunksOf___rarg___closed__2; +static lean_object* l_List_mapTR_loop___at_generateDataCode___spec__2___closed__1; +extern lean_object* l_ByteArray_empty; +static lean_object* l_Array_mapMUnsafe_map___at_generateResourceInfoCode___spec__1___closed__2; +static lean_object* l_assembleBundleFile___closed__6; +lean_object* l_List_splitAt___rarg(lean_object*, lean_object*); +static lean_object* l_main___closed__1; +LEAN_EXPORT lean_object* l_List_dropPrefix(lean_object*); +LEAN_EXPORT lean_object* l_readFilesFromDir(lean_object*, lean_object*, lean_object*); +lean_object* lean_array_to_list(lean_object*); +LEAN_EXPORT lean_object* l_List_mapTR_loop___at_generateDataCode___spec__2(lean_object*, lean_object*); +static lean_object* l_Array_mapMUnsafe_map___at_generateResourceInfoCode___spec__1___closed__3; +lean_object* l_System_FilePath_components(lean_object*); +LEAN_EXPORT lean_object* l_List_repeatedly___rarg(lean_object*, lean_object*); +LEAN_EXPORT lean_object* l_generateDataCode___boxed(lean_object*); +static lean_object* l_assembleBundleFile___closed__9; +static lean_object* l_generateResourceInfoCode___closed__2; +lean_object* l_IO_print___at_IO_println___spec__1(lean_object*, lean_object*); +lean_object* l_ByteArray_toList(lean_object*); +static lean_object* l_List_chunksOf___rarg___closed__4; +LEAN_EXPORT lean_object* l_panic___at_List_chunksOf___spec__1(lean_object*); +lean_object* l___private_Init_Util_0__mkPanicMessageWithDecl(lean_object*, lean_object*, lean_object*, lean_object*, lean_object*); +LEAN_EXPORT lean_object* l_Array_mapMUnsafe_map___at_generateResourceInfoCode___spec__1(size_t, size_t, lean_object*); +LEAN_EXPORT lean_object* l_List_dropPrefix___rarg(lean_object*, lean_object*, lean_object*); +static lean_object* l_assembleBundleFile___closed__4; +LEAN_EXPORT lean_object* l_List_chunksOf___rarg(lean_object*, lean_object*); +static lean_object* l_assembleBundleFile___closed__5; +LEAN_EXPORT lean_object* l_List_dropPrefix___at_readFile___spec__1___boxed(lean_object*, lean_object*); +LEAN_EXPORT lean_object* l_panic___at_List_chunksOf___spec__1___rarg(lean_object*); +LEAN_EXPORT lean_object* l_hexEncodeByte___boxed(lean_object*); +lean_object* l_System_mkFilePath(lean_object*); +LEAN_EXPORT lean_object* l_readFile(lean_object*, lean_object*, lean_object*); +LEAN_EXPORT lean_object* l_generateResourceInfoSize(lean_object*); +LEAN_EXPORT lean_object* l_readFilesFromDir___boxed(lean_object*, lean_object*, lean_object*); +LEAN_EXPORT lean_object* l_Array_forIn_x27Unsafe_loop___at_mkResourcesInfo___spec__1___boxed(lean_object*, lean_object*, lean_object*, lean_object*, lean_object*, lean_object*); +LEAN_EXPORT lean_object* l_IO_println___at_main___spec__1(lean_object*, lean_object*); +uint8_t lean_nat_dec_eq(lean_object*, lean_object*); +static lean_object* l_mkResourcesInfo___closed__1; +static lean_object* l_hexEncodeByte___closed__1; +static lean_object* l_assembleBundleFile___closed__7; +LEAN_EXPORT lean_object* l_Array_mapMUnsafe_map___at_generateResourceInfoCode___spec__1___boxed(lean_object*, lean_object*, lean_object*); +static lean_object* l_generateDataCode___closed__3; +LEAN_EXPORT lean_object* l_List_mapTR_loop___at_generateDataCode___spec__1(lean_object*, lean_object*); +LEAN_EXPORT lean_object* l_hexEncodeByte(uint8_t); +lean_object* lean_panic_fn(lean_object*, lean_object*); +static lean_object* l_assembleBundleFile___closed__12; +uint8_t lean_uint8_shift_right(uint8_t, uint8_t); +lean_object* l_List_reverse___rarg(lean_object*); +lean_object* l_String_intercalate(lean_object*, lean_object*); +static lean_object* l_generateResourceInfoCode___closed__1; +lean_object* lean_io_read_dir(lean_object*, lean_object*); +lean_object* lean_array_mk(lean_object*); +LEAN_EXPORT lean_object* l_Array_mapMUnsafe_map___at_readFilesFromDir___spec__1___boxed(lean_object*, lean_object*, lean_object*, lean_object*, lean_object*); +size_t lean_usize_add(size_t, size_t); +static lean_object* l_generateResourceInfoSize___closed__1; +lean_object* lean_array_uget(lean_object*, size_t); +size_t lean_array_size(lean_object*); +static lean_object* l_List_mapTR_loop___at_generateDataCode___spec__2___closed__3; +lean_object* l_IO_FS_writeFile(lean_object*, lean_object*, lean_object*); +static lean_object* l_assembleBundleFile___closed__2; +static lean_object* l_main___closed__2; +lean_object* lean_string_append(lean_object*, lean_object*); +static lean_object* l_List_chunksOf___rarg___closed__3; +static lean_object* l_assembleBundleFile___closed__1; +lean_object* lean_array_get_size(lean_object*); +uint8_t lean_usize_dec_lt(size_t, size_t); +lean_object* l_IO_FS_readBinFile(lean_object*, lean_object*); +lean_object* lean_byte_array_size(lean_object*); +uint8_t lean_uint8_dec_eq(uint8_t, uint8_t); +lean_object* lean_array_uset(lean_object*, size_t, lean_object*); +lean_object* l___private_Init_Data_Repr_0__Nat_reprFast(lean_object*); +static lean_object* l_List_mapTR_loop___at_generateDataCode___spec__2___closed__2; +LEAN_EXPORT lean_object* l_assembleBundleFile(lean_object*); +LEAN_EXPORT lean_object* l_Array_mapMUnsafe_map___at_readFilesFromDir___spec__1(size_t, size_t, lean_object*, lean_object*, lean_object*); +LEAN_EXPORT lean_object* l_List_dropPrefix___rarg(lean_object* x_1, lean_object* x_2, lean_object* x_3) { +_start: +{ +if (lean_obj_tag(x_2) == 0) +{ +lean_dec(x_1); +if (lean_obj_tag(x_3) == 0) +{ +return x_3; +} +else +{ +lean_dec(x_3); +return x_2; +} +} +else +{ +if (lean_obj_tag(x_3) == 0) +{ +lean_dec(x_1); +return x_2; +} +else +{ +lean_object* x_4; lean_object* x_5; lean_object* x_6; lean_object* x_7; lean_object* x_8; uint8_t x_9; +x_4 = lean_ctor_get(x_2, 0); +lean_inc(x_4); +x_5 = lean_ctor_get(x_2, 1); +lean_inc(x_5); +x_6 = lean_ctor_get(x_3, 0); +lean_inc(x_6); +x_7 = lean_ctor_get(x_3, 1); +lean_inc(x_7); +lean_dec(x_3); +lean_inc(x_1); +x_8 = lean_apply_2(x_1, x_4, x_6); +x_9 = lean_unbox(x_8); +lean_dec(x_8); +if (x_9 == 0) +{ +lean_dec(x_7); +lean_dec(x_5); +lean_dec(x_1); +return x_2; +} +else +{ +lean_dec(x_2); +x_2 = x_5; +x_3 = x_7; +goto _start; +} +} +} +} +} +LEAN_EXPORT lean_object* l_List_dropPrefix(lean_object* x_1) { +_start: +{ +lean_object* x_2; +x_2 = lean_alloc_closure((void*)(l_List_dropPrefix___rarg), 3, 0); +return x_2; +} +} +LEAN_EXPORT lean_object* l_List_dropPrefix___at_readFile___spec__1(lean_object* x_1, lean_object* x_2) { +_start: +{ +if (lean_obj_tag(x_1) == 0) +{ +if (lean_obj_tag(x_2) == 0) +{ +return x_2; +} +else +{ +return x_1; +} +} +else +{ +if (lean_obj_tag(x_2) == 0) +{ +lean_inc(x_1); +return x_1; +} +else +{ +lean_object* x_3; lean_object* x_4; lean_object* x_5; lean_object* x_6; uint8_t x_7; +x_3 = lean_ctor_get(x_1, 0); +x_4 = lean_ctor_get(x_1, 1); +x_5 = lean_ctor_get(x_2, 0); +x_6 = lean_ctor_get(x_2, 1); +x_7 = lean_string_dec_eq(x_3, x_5); +if (x_7 == 0) +{ +lean_inc(x_1); +return x_1; +} +else +{ +x_1 = x_4; +x_2 = x_6; +goto _start; +} +} +} +} +} +LEAN_EXPORT lean_object* l_readFile(lean_object* x_1, lean_object* x_2, lean_object* x_3) { +_start: +{ +lean_object* x_4; lean_object* x_5; lean_object* x_6; lean_object* x_7; lean_object* x_8; lean_object* x_9; +x_4 = l_IO_FS_DirEntry_path(x_1); +lean_inc(x_4); +x_5 = l_System_FilePath_components(x_4); +x_6 = l_System_FilePath_components(x_2); +x_7 = l_List_dropPrefix___at_readFile___spec__1(x_5, x_6); +lean_dec(x_6); +lean_dec(x_5); +x_8 = l_System_mkFilePath(x_7); +x_9 = l_IO_FS_readBinFile(x_4, x_3); +lean_dec(x_4); +if (lean_obj_tag(x_9) == 0) +{ +uint8_t x_10; +x_10 = !lean_is_exclusive(x_9); +if (x_10 == 0) +{ +lean_object* x_11; lean_object* x_12; +x_11 = lean_ctor_get(x_9, 0); +x_12 = lean_alloc_ctor(0, 2, 0); +lean_ctor_set(x_12, 0, x_8); +lean_ctor_set(x_12, 1, x_11); +lean_ctor_set(x_9, 0, x_12); +return x_9; +} +else +{ +lean_object* x_13; lean_object* x_14; lean_object* x_15; lean_object* x_16; +x_13 = lean_ctor_get(x_9, 0); +x_14 = lean_ctor_get(x_9, 1); +lean_inc(x_14); +lean_inc(x_13); +lean_dec(x_9); +x_15 = lean_alloc_ctor(0, 2, 0); +lean_ctor_set(x_15, 0, x_8); +lean_ctor_set(x_15, 1, x_13); +x_16 = lean_alloc_ctor(0, 2, 0); +lean_ctor_set(x_16, 0, x_15); +lean_ctor_set(x_16, 1, x_14); +return x_16; +} +} +else +{ +uint8_t x_17; +lean_dec(x_8); +x_17 = !lean_is_exclusive(x_9); +if (x_17 == 0) +{ +return x_9; +} +else +{ +lean_object* x_18; lean_object* x_19; lean_object* x_20; +x_18 = lean_ctor_get(x_9, 0); +x_19 = lean_ctor_get(x_9, 1); +lean_inc(x_19); +lean_inc(x_18); +lean_dec(x_9); +x_20 = lean_alloc_ctor(1, 2, 0); +lean_ctor_set(x_20, 0, x_18); +lean_ctor_set(x_20, 1, x_19); +return x_20; +} +} +} +} +LEAN_EXPORT lean_object* l_List_dropPrefix___at_readFile___spec__1___boxed(lean_object* x_1, lean_object* x_2) { +_start: +{ +lean_object* x_3; +x_3 = l_List_dropPrefix___at_readFile___spec__1(x_1, x_2); +lean_dec(x_2); +lean_dec(x_1); +return x_3; +} +} +LEAN_EXPORT lean_object* l_Array_mapMUnsafe_map___at_readFilesFromDir___spec__1(size_t x_1, size_t x_2, lean_object* x_3, lean_object* x_4, lean_object* x_5) { +_start: +{ +uint8_t x_6; +x_6 = lean_usize_dec_lt(x_2, x_1); +if (x_6 == 0) +{ +lean_object* x_7; +lean_dec(x_4); +x_7 = lean_alloc_ctor(0, 2, 0); +lean_ctor_set(x_7, 0, x_3); +lean_ctor_set(x_7, 1, x_5); +return x_7; +} +else +{ +lean_object* x_8; lean_object* x_9; lean_object* x_10; lean_object* x_11; +x_8 = lean_array_uget(x_3, x_2); +x_9 = lean_unsigned_to_nat(0u); +x_10 = lean_array_uset(x_3, x_2, x_9); +lean_inc(x_4); +x_11 = l_readFile(x_8, x_4, x_5); +if (lean_obj_tag(x_11) == 0) +{ +lean_object* x_12; lean_object* x_13; size_t x_14; size_t x_15; lean_object* x_16; +x_12 = lean_ctor_get(x_11, 0); +lean_inc(x_12); +x_13 = lean_ctor_get(x_11, 1); +lean_inc(x_13); +lean_dec(x_11); +x_14 = 1; +x_15 = lean_usize_add(x_2, x_14); +x_16 = lean_array_uset(x_10, x_2, x_12); +x_2 = x_15; +x_3 = x_16; +x_5 = x_13; +goto _start; +} +else +{ +uint8_t x_18; +lean_dec(x_10); +lean_dec(x_4); +x_18 = !lean_is_exclusive(x_11); +if (x_18 == 0) +{ +return x_11; +} +else +{ +lean_object* x_19; lean_object* x_20; lean_object* x_21; +x_19 = lean_ctor_get(x_11, 0); +x_20 = lean_ctor_get(x_11, 1); +lean_inc(x_20); +lean_inc(x_19); +lean_dec(x_11); +x_21 = lean_alloc_ctor(1, 2, 0); +lean_ctor_set(x_21, 0, x_19); +lean_ctor_set(x_21, 1, x_20); +return x_21; +} +} +} +} +} +LEAN_EXPORT lean_object* l_readFilesFromDir(lean_object* x_1, lean_object* x_2, lean_object* x_3) { +_start: +{ +lean_object* x_4; +x_4 = lean_io_read_dir(x_1, x_3); +if (lean_obj_tag(x_4) == 0) +{ +lean_object* x_5; lean_object* x_6; size_t x_7; size_t x_8; lean_object* x_9; +x_5 = lean_ctor_get(x_4, 0); +lean_inc(x_5); +x_6 = lean_ctor_get(x_4, 1); +lean_inc(x_6); +lean_dec(x_4); +x_7 = lean_array_size(x_5); +x_8 = 0; +x_9 = l_Array_mapMUnsafe_map___at_readFilesFromDir___spec__1(x_7, x_8, x_5, x_2, x_6); +return x_9; +} +else +{ +uint8_t x_10; +lean_dec(x_2); +x_10 = !lean_is_exclusive(x_4); +if (x_10 == 0) +{ +return x_4; +} +else +{ +lean_object* x_11; lean_object* x_12; lean_object* x_13; +x_11 = lean_ctor_get(x_4, 0); +x_12 = lean_ctor_get(x_4, 1); +lean_inc(x_12); +lean_inc(x_11); +lean_dec(x_4); +x_13 = lean_alloc_ctor(1, 2, 0); +lean_ctor_set(x_13, 0, x_11); +lean_ctor_set(x_13, 1, x_12); +return x_13; +} +} +} +} +LEAN_EXPORT lean_object* l_Array_mapMUnsafe_map___at_readFilesFromDir___spec__1___boxed(lean_object* x_1, lean_object* x_2, lean_object* x_3, lean_object* x_4, lean_object* x_5) { +_start: +{ +size_t x_6; size_t x_7; lean_object* x_8; +x_6 = lean_unbox_usize(x_1); +lean_dec(x_1); +x_7 = lean_unbox_usize(x_2); +lean_dec(x_2); +x_8 = l_Array_mapMUnsafe_map___at_readFilesFromDir___spec__1(x_6, x_7, x_3, x_4, x_5); +return x_8; +} +} +LEAN_EXPORT lean_object* l_readFilesFromDir___boxed(lean_object* x_1, lean_object* x_2, lean_object* x_3) { +_start: +{ +lean_object* x_4; +x_4 = l_readFilesFromDir(x_1, x_2, x_3); +lean_dec(x_1); +return x_4; +} +} +LEAN_EXPORT lean_object* l_Array_forIn_x27Unsafe_loop___at_mkResourcesInfo___spec__1(lean_object* x_1, lean_object* x_2, lean_object* x_3, size_t x_4, size_t x_5, lean_object* x_6) { +_start: +{ +uint8_t x_7; +x_7 = lean_usize_dec_lt(x_5, x_4); +if (x_7 == 0) +{ +return x_6; +} +else +{ +lean_object* x_8; lean_object* x_9; lean_object* x_10; uint8_t x_11; +x_8 = lean_array_uget(x_3, x_5); +x_9 = lean_ctor_get(x_6, 0); +lean_inc(x_9); +x_10 = lean_ctor_get(x_6, 1); +lean_inc(x_10); +lean_dec(x_6); +x_11 = !lean_is_exclusive(x_8); +if (x_11 == 0) +{ +lean_object* x_12; lean_object* x_13; lean_object* x_14; lean_object* x_15; lean_object* x_16; lean_object* x_17; lean_object* x_18; uint8_t x_19; lean_object* x_20; size_t x_21; size_t x_22; +x_12 = lean_ctor_get(x_8, 0); +x_13 = lean_ctor_get(x_8, 1); +x_14 = lean_byte_array_size(x_9); +x_15 = lean_byte_array_size(x_13); +x_16 = lean_alloc_ctor(0, 3, 0); +lean_ctor_set(x_16, 0, x_12); +lean_ctor_set(x_16, 1, x_14); +lean_ctor_set(x_16, 2, x_15); +x_17 = lean_array_push(x_10, x_16); +x_18 = l_ByteArray_append(x_9, x_13); +lean_dec(x_13); +x_19 = 0; +x_20 = lean_byte_array_push(x_18, x_19); +lean_ctor_set(x_8, 1, x_17); +lean_ctor_set(x_8, 0, x_20); +x_21 = 1; +x_22 = lean_usize_add(x_5, x_21); +x_5 = x_22; +x_6 = x_8; +goto _start; +} +else +{ +lean_object* x_24; lean_object* x_25; lean_object* x_26; lean_object* x_27; lean_object* x_28; lean_object* x_29; lean_object* x_30; uint8_t x_31; lean_object* x_32; lean_object* x_33; size_t x_34; size_t x_35; +x_24 = lean_ctor_get(x_8, 0); +x_25 = lean_ctor_get(x_8, 1); +lean_inc(x_25); +lean_inc(x_24); +lean_dec(x_8); +x_26 = lean_byte_array_size(x_9); +x_27 = lean_byte_array_size(x_25); +x_28 = lean_alloc_ctor(0, 3, 0); +lean_ctor_set(x_28, 0, x_24); +lean_ctor_set(x_28, 1, x_26); +lean_ctor_set(x_28, 2, x_27); +x_29 = lean_array_push(x_10, x_28); +x_30 = l_ByteArray_append(x_9, x_25); +lean_dec(x_25); +x_31 = 0; +x_32 = lean_byte_array_push(x_30, x_31); +x_33 = lean_alloc_ctor(0, 2, 0); +lean_ctor_set(x_33, 0, x_32); +lean_ctor_set(x_33, 1, x_29); +x_34 = 1; +x_35 = lean_usize_add(x_5, x_34); +x_5 = x_35; +x_6 = x_33; +goto _start; +} +} +} +} +static lean_object* _init_l_mkResourcesInfo___closed__1() { +_start: +{ +lean_object* x_1; lean_object* x_2; +x_1 = lean_box(0); +x_2 = lean_array_mk(x_1); +return x_2; +} +} +static lean_object* _init_l_mkResourcesInfo___closed__2() { +_start: +{ +lean_object* x_1; lean_object* x_2; lean_object* x_3; +x_1 = l_ByteArray_empty; +x_2 = l_mkResourcesInfo___closed__1; +x_3 = lean_alloc_ctor(0, 2, 0); +lean_ctor_set(x_3, 0, x_1); +lean_ctor_set(x_3, 1, x_2); +return x_3; +} +} +LEAN_EXPORT lean_object* l_mkResourcesInfo(lean_object* x_1) { +_start: +{ +lean_object* x_2; size_t x_3; size_t x_4; lean_object* x_5; lean_object* x_6; uint8_t x_7; +x_2 = lean_box(0); +x_3 = lean_array_size(x_1); +x_4 = 0; +x_5 = l_mkResourcesInfo___closed__2; +x_6 = l_Array_forIn_x27Unsafe_loop___at_mkResourcesInfo___spec__1(x_1, x_2, x_1, x_3, x_4, x_5); +x_7 = !lean_is_exclusive(x_6); +if (x_7 == 0) +{ +return x_6; +} +else +{ +lean_object* x_8; lean_object* x_9; lean_object* x_10; +x_8 = lean_ctor_get(x_6, 0); +x_9 = lean_ctor_get(x_6, 1); +lean_inc(x_9); +lean_inc(x_8); +lean_dec(x_6); +x_10 = lean_alloc_ctor(0, 2, 0); +lean_ctor_set(x_10, 0, x_8); +lean_ctor_set(x_10, 1, x_9); +return x_10; +} +} +} +LEAN_EXPORT lean_object* l_Array_forIn_x27Unsafe_loop___at_mkResourcesInfo___spec__1___boxed(lean_object* x_1, lean_object* x_2, lean_object* x_3, lean_object* x_4, lean_object* x_5, lean_object* x_6) { +_start: +{ +size_t x_7; size_t x_8; lean_object* x_9; +x_7 = lean_unbox_usize(x_4); +lean_dec(x_4); +x_8 = lean_unbox_usize(x_5); +lean_dec(x_5); +x_9 = l_Array_forIn_x27Unsafe_loop___at_mkResourcesInfo___spec__1(x_1, x_2, x_3, x_7, x_8, x_6); +lean_dec(x_3); +lean_dec(x_2); +lean_dec(x_1); +return x_9; +} +} +LEAN_EXPORT lean_object* l_mkResourcesInfo___boxed(lean_object* x_1) { +_start: +{ +lean_object* x_2; +x_2 = l_mkResourcesInfo(x_1); +lean_dec(x_1); +return x_2; +} +} +static lean_object* _init_l_hexEncodeByte___closed__1() { +_start: +{ +lean_object* x_1; +x_1 = lean_mk_string_unchecked("", 0, 0); +return x_1; +} +} +static lean_object* _init_l_hexEncodeByte___closed__2() { +_start: +{ +lean_object* x_1; +x_1 = lean_mk_string_unchecked("0x", 2, 2); +return x_1; +} +} +LEAN_EXPORT lean_object* l_hexEncodeByte(uint8_t x_1) { +_start: +{ +uint8_t x_2; uint8_t x_3; uint8_t x_4; uint8_t x_5; uint8_t x_6; uint8_t x_7; uint8_t x_8; uint32_t x_9; +x_2 = 4; +x_3 = lean_uint8_shift_right(x_1, x_2); +x_4 = 0; +x_5 = lean_uint8_dec_eq(x_3, x_4); +x_6 = 15; +x_7 = lean_uint8_land(x_1, x_6); +x_8 = lean_uint8_dec_eq(x_7, x_4); +if (x_5 == 0) +{ +uint8_t x_66; uint8_t x_67; +x_66 = 1; +x_67 = lean_uint8_dec_eq(x_3, x_66); +if (x_67 == 0) +{ +uint8_t x_68; uint8_t x_69; +x_68 = 2; +x_69 = lean_uint8_dec_eq(x_3, x_68); +if (x_69 == 0) +{ +uint8_t x_70; uint8_t x_71; +x_70 = 3; +x_71 = lean_uint8_dec_eq(x_3, x_70); +if (x_71 == 0) +{ +uint8_t x_72; +x_72 = lean_uint8_dec_eq(x_3, x_2); +if (x_72 == 0) +{ +uint8_t x_73; uint8_t x_74; +x_73 = 5; +x_74 = lean_uint8_dec_eq(x_3, x_73); +if (x_74 == 0) +{ +uint8_t x_75; uint8_t x_76; +x_75 = 6; +x_76 = lean_uint8_dec_eq(x_3, x_75); +if (x_76 == 0) +{ +uint8_t x_77; uint8_t x_78; +x_77 = 7; +x_78 = lean_uint8_dec_eq(x_3, x_77); +if (x_78 == 0) +{ +uint8_t x_79; uint8_t x_80; +x_79 = 8; +x_80 = lean_uint8_dec_eq(x_3, x_79); +if (x_80 == 0) +{ +uint8_t x_81; uint8_t x_82; +x_81 = 9; +x_82 = lean_uint8_dec_eq(x_3, x_81); +if (x_82 == 0) +{ +uint8_t x_83; uint8_t x_84; +x_83 = 10; +x_84 = lean_uint8_dec_eq(x_3, x_83); +if (x_84 == 0) +{ +uint8_t x_85; uint8_t x_86; +x_85 = 11; +x_86 = lean_uint8_dec_eq(x_3, x_85); +if (x_86 == 0) +{ +uint8_t x_87; uint8_t x_88; +x_87 = 12; +x_88 = lean_uint8_dec_eq(x_3, x_87); +if (x_88 == 0) +{ +uint8_t x_89; uint8_t x_90; +x_89 = 13; +x_90 = lean_uint8_dec_eq(x_3, x_89); +if (x_90 == 0) +{ +uint8_t x_91; uint8_t x_92; +x_91 = 14; +x_92 = lean_uint8_dec_eq(x_3, x_91); +if (x_92 == 0) +{ +uint8_t x_93; +x_93 = lean_uint8_dec_eq(x_3, x_6); +if (x_93 == 0) +{ +uint32_t x_94; +x_94 = 42; +x_9 = x_94; +goto block_65; +} +else +{ +uint32_t x_95; +x_95 = 70; +x_9 = x_95; +goto block_65; +} +} +else +{ +uint32_t x_96; +x_96 = 69; +x_9 = x_96; +goto block_65; +} +} +else +{ +uint32_t x_97; +x_97 = 68; +x_9 = x_97; +goto block_65; +} +} +else +{ +uint32_t x_98; +x_98 = 67; +x_9 = x_98; +goto block_65; +} +} +else +{ +uint32_t x_99; +x_99 = 66; +x_9 = x_99; +goto block_65; +} +} +else +{ +uint32_t x_100; +x_100 = 65; +x_9 = x_100; +goto block_65; +} +} +else +{ +uint32_t x_101; +x_101 = 57; +x_9 = x_101; +goto block_65; +} +} +else +{ +uint32_t x_102; +x_102 = 56; +x_9 = x_102; +goto block_65; +} +} +else +{ +uint32_t x_103; +x_103 = 55; +x_9 = x_103; +goto block_65; +} +} +else +{ +uint32_t x_104; +x_104 = 54; +x_9 = x_104; +goto block_65; +} +} +else +{ +uint32_t x_105; +x_105 = 53; +x_9 = x_105; +goto block_65; +} +} +else +{ +uint32_t x_106; +x_106 = 52; +x_9 = x_106; +goto block_65; +} +} +else +{ +uint32_t x_107; +x_107 = 51; +x_9 = x_107; +goto block_65; +} +} +else +{ +uint32_t x_108; +x_108 = 50; +x_9 = x_108; +goto block_65; +} +} +else +{ +uint32_t x_109; +x_109 = 49; +x_9 = x_109; +goto block_65; +} +} +else +{ +uint32_t x_110; +x_110 = 48; +x_9 = x_110; +goto block_65; +} +block_65: +{ +lean_object* x_10; lean_object* x_11; lean_object* x_12; lean_object* x_13; lean_object* x_14; uint32_t x_15; +x_10 = l_hexEncodeByte___closed__1; +x_11 = lean_string_push(x_10, x_9); +x_12 = l_hexEncodeByte___closed__2; +x_13 = lean_string_append(x_12, x_11); +lean_dec(x_11); +x_14 = lean_string_append(x_13, x_10); +if (x_8 == 0) +{ +uint8_t x_20; uint8_t x_21; +x_20 = 1; +x_21 = lean_uint8_dec_eq(x_7, x_20); +if (x_21 == 0) +{ +uint8_t x_22; uint8_t x_23; +x_22 = 2; +x_23 = lean_uint8_dec_eq(x_7, x_22); +if (x_23 == 0) +{ +uint8_t x_24; uint8_t x_25; +x_24 = 3; +x_25 = lean_uint8_dec_eq(x_7, x_24); +if (x_25 == 0) +{ +uint8_t x_26; +x_26 = lean_uint8_dec_eq(x_7, x_2); +if (x_26 == 0) +{ +uint8_t x_27; uint8_t x_28; +x_27 = 5; +x_28 = lean_uint8_dec_eq(x_7, x_27); +if (x_28 == 0) +{ +uint8_t x_29; uint8_t x_30; +x_29 = 6; +x_30 = lean_uint8_dec_eq(x_7, x_29); +if (x_30 == 0) +{ +uint8_t x_31; uint8_t x_32; +x_31 = 7; +x_32 = lean_uint8_dec_eq(x_7, x_31); +if (x_32 == 0) +{ +uint8_t x_33; uint8_t x_34; +x_33 = 8; +x_34 = lean_uint8_dec_eq(x_7, x_33); +if (x_34 == 0) +{ +uint8_t x_35; uint8_t x_36; +x_35 = 9; +x_36 = lean_uint8_dec_eq(x_7, x_35); +if (x_36 == 0) +{ +uint8_t x_37; uint8_t x_38; +x_37 = 10; +x_38 = lean_uint8_dec_eq(x_7, x_37); +if (x_38 == 0) +{ +uint8_t x_39; uint8_t x_40; +x_39 = 11; +x_40 = lean_uint8_dec_eq(x_7, x_39); +if (x_40 == 0) +{ +uint8_t x_41; uint8_t x_42; +x_41 = 12; +x_42 = lean_uint8_dec_eq(x_7, x_41); +if (x_42 == 0) +{ +uint8_t x_43; uint8_t x_44; +x_43 = 13; +x_44 = lean_uint8_dec_eq(x_7, x_43); +if (x_44 == 0) +{ +uint8_t x_45; uint8_t x_46; +x_45 = 14; +x_46 = lean_uint8_dec_eq(x_7, x_45); +if (x_46 == 0) +{ +uint8_t x_47; +x_47 = lean_uint8_dec_eq(x_7, x_6); +if (x_47 == 0) +{ +uint32_t x_48; +x_48 = 42; +x_15 = x_48; +goto block_19; +} +else +{ +uint32_t x_49; +x_49 = 70; +x_15 = x_49; +goto block_19; +} +} +else +{ +uint32_t x_50; +x_50 = 69; +x_15 = x_50; +goto block_19; +} +} +else +{ +uint32_t x_51; +x_51 = 68; +x_15 = x_51; +goto block_19; +} +} +else +{ +uint32_t x_52; +x_52 = 67; +x_15 = x_52; +goto block_19; +} +} +else +{ +uint32_t x_53; +x_53 = 66; +x_15 = x_53; +goto block_19; +} +} +else +{ +uint32_t x_54; +x_54 = 65; +x_15 = x_54; +goto block_19; +} +} +else +{ +uint32_t x_55; +x_55 = 57; +x_15 = x_55; +goto block_19; +} +} +else +{ +uint32_t x_56; +x_56 = 56; +x_15 = x_56; +goto block_19; +} +} +else +{ +uint32_t x_57; +x_57 = 55; +x_15 = x_57; +goto block_19; +} +} +else +{ +uint32_t x_58; +x_58 = 54; +x_15 = x_58; +goto block_19; +} +} +else +{ +uint32_t x_59; +x_59 = 53; +x_15 = x_59; +goto block_19; +} +} +else +{ +uint32_t x_60; +x_60 = 52; +x_15 = x_60; +goto block_19; +} +} +else +{ +uint32_t x_61; +x_61 = 51; +x_15 = x_61; +goto block_19; +} +} +else +{ +uint32_t x_62; +x_62 = 50; +x_15 = x_62; +goto block_19; +} +} +else +{ +uint32_t x_63; +x_63 = 49; +x_15 = x_63; +goto block_19; +} +} +else +{ +uint32_t x_64; +x_64 = 48; +x_15 = x_64; +goto block_19; +} +block_19: +{ +lean_object* x_16; lean_object* x_17; lean_object* x_18; +x_16 = lean_string_push(x_10, x_15); +x_17 = lean_string_append(x_14, x_16); +lean_dec(x_16); +x_18 = lean_string_append(x_17, x_10); +return x_18; +} +} +} +} +LEAN_EXPORT lean_object* l_hexEncodeByte___boxed(lean_object* x_1) { +_start: +{ +uint8_t x_2; lean_object* x_3; +x_2 = lean_unbox(x_1); +lean_dec(x_1); +x_3 = l_hexEncodeByte(x_2); +return x_3; +} +} +LEAN_EXPORT lean_object* l_List_repeatedly___rarg(lean_object* x_1, lean_object* x_2) { +_start: +{ +if (lean_obj_tag(x_2) == 0) +{ +lean_object* x_3; +lean_dec(x_1); +x_3 = lean_box(0); +return x_3; +} +else +{ +lean_object* x_4; uint8_t x_5; +lean_inc(x_1); +lean_inc(x_2); +x_4 = lean_apply_1(x_1, x_2); +x_5 = !lean_is_exclusive(x_2); +if (x_5 == 0) +{ +lean_object* x_6; lean_object* x_7; lean_object* x_8; lean_object* x_9; lean_object* x_10; +x_6 = lean_ctor_get(x_2, 1); +lean_dec(x_6); +x_7 = lean_ctor_get(x_2, 0); +lean_dec(x_7); +x_8 = lean_ctor_get(x_4, 0); +lean_inc(x_8); +x_9 = lean_ctor_get(x_4, 1); +lean_inc(x_9); +lean_dec(x_4); +x_10 = l_List_repeatedly___rarg(x_1, x_9); +lean_ctor_set(x_2, 1, x_10); +lean_ctor_set(x_2, 0, x_8); +return x_2; +} +else +{ +lean_object* x_11; lean_object* x_12; lean_object* x_13; lean_object* x_14; +lean_dec(x_2); +x_11 = lean_ctor_get(x_4, 0); +lean_inc(x_11); +x_12 = lean_ctor_get(x_4, 1); +lean_inc(x_12); +lean_dec(x_4); +x_13 = l_List_repeatedly___rarg(x_1, x_12); +x_14 = lean_alloc_ctor(1, 2, 0); +lean_ctor_set(x_14, 0, x_11); +lean_ctor_set(x_14, 1, x_13); +return x_14; +} +} +} +} +LEAN_EXPORT lean_object* l_List_repeatedly(lean_object* x_1, lean_object* x_2) { +_start: +{ +lean_object* x_3; +x_3 = lean_alloc_closure((void*)(l_List_repeatedly___rarg), 2, 0); +return x_3; +} +} +LEAN_EXPORT lean_object* l_panic___at_List_chunksOf___spec__1___rarg(lean_object* x_1) { +_start: +{ +lean_object* x_2; lean_object* x_3; +x_2 = lean_box(0); +x_3 = lean_panic_fn(x_2, x_1); +return x_3; +} +} +LEAN_EXPORT lean_object* l_panic___at_List_chunksOf___spec__1(lean_object* x_1) { +_start: +{ +lean_object* x_2; +x_2 = lean_alloc_closure((void*)(l_panic___at_List_chunksOf___spec__1___rarg), 1, 0); +return x_2; +} +} +static lean_object* _init_l_List_chunksOf___rarg___closed__1() { +_start: +{ +lean_object* x_1; +x_1 = lean_mk_string_unchecked("scripts.makeBundle", 18, 18); +return x_1; +} +} +static lean_object* _init_l_List_chunksOf___rarg___closed__2() { +_start: +{ +lean_object* x_1; +x_1 = lean_mk_string_unchecked("List.chunksOf", 13, 13); +return x_1; +} +} +static lean_object* _init_l_List_chunksOf___rarg___closed__3() { +_start: +{ +lean_object* x_1; +x_1 = lean_mk_string_unchecked("Cannot call chunksOf with zero", 30, 30); +return x_1; +} +} +static lean_object* _init_l_List_chunksOf___rarg___closed__4() { +_start: +{ +lean_object* x_1; lean_object* x_2; lean_object* x_3; lean_object* x_4; lean_object* x_5; lean_object* x_6; +x_1 = l_List_chunksOf___rarg___closed__1; +x_2 = l_List_chunksOf___rarg___closed__2; +x_3 = lean_unsigned_to_nat(77u); +x_4 = lean_unsigned_to_nat(9u); +x_5 = l_List_chunksOf___rarg___closed__3; +x_6 = l___private_Init_Util_0__mkPanicMessageWithDecl(x_1, x_2, x_3, x_4, x_5); +return x_6; +} +} +LEAN_EXPORT lean_object* l_List_chunksOf___rarg(lean_object* x_1, lean_object* x_2) { +_start: +{ +lean_object* x_3; uint8_t x_4; +x_3 = lean_unsigned_to_nat(0u); +x_4 = lean_nat_dec_eq(x_1, x_3); +if (x_4 == 0) +{ +lean_object* x_5; lean_object* x_6; +x_5 = lean_alloc_closure((void*)(l_List_splitAt___rarg), 2, 1); +lean_closure_set(x_5, 0, x_1); +x_6 = l_List_repeatedly___rarg(x_5, x_2); +return x_6; +} +else +{ +lean_object* x_7; lean_object* x_8; +lean_dec(x_2); +lean_dec(x_1); +x_7 = l_List_chunksOf___rarg___closed__4; +x_8 = l_panic___at_List_chunksOf___spec__1___rarg(x_7); +return x_8; +} +} +} +LEAN_EXPORT lean_object* l_List_chunksOf(lean_object* x_1) { +_start: +{ +lean_object* x_2; +x_2 = lean_alloc_closure((void*)(l_List_chunksOf___rarg), 2, 0); +return x_2; +} +} +LEAN_EXPORT lean_object* l_List_mapTR_loop___at_generateDataCode___spec__1(lean_object* x_1, lean_object* x_2) { +_start: +{ +if (lean_obj_tag(x_1) == 0) +{ +lean_object* x_3; +x_3 = l_List_reverse___rarg(x_2); +return x_3; +} +else +{ +uint8_t x_4; +x_4 = !lean_is_exclusive(x_1); +if (x_4 == 0) +{ +lean_object* x_5; lean_object* x_6; uint8_t x_7; lean_object* x_8; +x_5 = lean_ctor_get(x_1, 0); +x_6 = lean_ctor_get(x_1, 1); +x_7 = lean_unbox(x_5); +lean_dec(x_5); +x_8 = l_hexEncodeByte(x_7); +lean_ctor_set(x_1, 1, x_2); +lean_ctor_set(x_1, 0, x_8); +{ +lean_object* _tmp_0 = x_6; +lean_object* _tmp_1 = x_1; +x_1 = _tmp_0; +x_2 = _tmp_1; +} +goto _start; +} +else +{ +lean_object* x_10; lean_object* x_11; uint8_t x_12; lean_object* x_13; lean_object* x_14; +x_10 = lean_ctor_get(x_1, 0); +x_11 = lean_ctor_get(x_1, 1); +lean_inc(x_11); +lean_inc(x_10); +lean_dec(x_1); +x_12 = lean_unbox(x_10); +lean_dec(x_10); +x_13 = l_hexEncodeByte(x_12); +x_14 = lean_alloc_ctor(1, 2, 0); +lean_ctor_set(x_14, 0, x_13); +lean_ctor_set(x_14, 1, x_2); +x_1 = x_11; +x_2 = x_14; +goto _start; +} +} +} +} +static lean_object* _init_l_List_mapTR_loop___at_generateDataCode___spec__2___closed__1() { +_start: +{ +lean_object* x_1; +x_1 = lean_mk_string_unchecked(", ", 2, 2); +return x_1; +} +} +static lean_object* _init_l_List_mapTR_loop___at_generateDataCode___spec__2___closed__2() { +_start: +{ +lean_object* x_1; +x_1 = lean_mk_string_unchecked(" ", 4, 4); +return x_1; +} +} +static lean_object* _init_l_List_mapTR_loop___at_generateDataCode___spec__2___closed__3() { +_start: +{ +lean_object* x_1; +x_1 = lean_mk_string_unchecked(",", 1, 1); +return x_1; +} +} +LEAN_EXPORT lean_object* l_List_mapTR_loop___at_generateDataCode___spec__2(lean_object* x_1, lean_object* x_2) { +_start: +{ +if (lean_obj_tag(x_1) == 0) +{ +lean_object* x_3; +x_3 = l_List_reverse___rarg(x_2); +return x_3; +} +else +{ +uint8_t x_4; +x_4 = !lean_is_exclusive(x_1); +if (x_4 == 0) +{ +lean_object* x_5; lean_object* x_6; lean_object* x_7; lean_object* x_8; lean_object* x_9; lean_object* x_10; lean_object* x_11; lean_object* x_12; +x_5 = lean_ctor_get(x_1, 0); +x_6 = lean_ctor_get(x_1, 1); +x_7 = l_List_mapTR_loop___at_generateDataCode___spec__2___closed__1; +x_8 = l_String_intercalate(x_7, x_5); +x_9 = l_List_mapTR_loop___at_generateDataCode___spec__2___closed__2; +x_10 = lean_string_append(x_9, x_8); +lean_dec(x_8); +x_11 = l_List_mapTR_loop___at_generateDataCode___spec__2___closed__3; +x_12 = lean_string_append(x_10, x_11); +lean_ctor_set(x_1, 1, x_2); +lean_ctor_set(x_1, 0, x_12); +{ +lean_object* _tmp_0 = x_6; +lean_object* _tmp_1 = x_1; +x_1 = _tmp_0; +x_2 = _tmp_1; +} +goto _start; +} +else +{ +lean_object* x_14; lean_object* x_15; lean_object* x_16; lean_object* x_17; lean_object* x_18; lean_object* x_19; lean_object* x_20; lean_object* x_21; lean_object* x_22; +x_14 = lean_ctor_get(x_1, 0); +x_15 = lean_ctor_get(x_1, 1); +lean_inc(x_15); +lean_inc(x_14); +lean_dec(x_1); +x_16 = l_List_mapTR_loop___at_generateDataCode___spec__2___closed__1; +x_17 = l_String_intercalate(x_16, x_14); +x_18 = l_List_mapTR_loop___at_generateDataCode___spec__2___closed__2; +x_19 = lean_string_append(x_18, x_17); +lean_dec(x_17); +x_20 = l_List_mapTR_loop___at_generateDataCode___spec__2___closed__3; +x_21 = lean_string_append(x_19, x_20); +x_22 = lean_alloc_ctor(1, 2, 0); +lean_ctor_set(x_22, 0, x_21); +lean_ctor_set(x_22, 1, x_2); +x_1 = x_15; +x_2 = x_22; +goto _start; +} +} +} +} +static lean_object* _init_l_generateDataCode___closed__1() { +_start: +{ +lean_object* x_1; +x_1 = lean_mk_string_unchecked("\n", 1, 1); +return x_1; +} +} +static lean_object* _init_l_generateDataCode___closed__2() { +_start: +{ +lean_object* x_1; +x_1 = lean_mk_string_unchecked("static const unsigned char bundle_data[] = {\n", 45, 45); +return x_1; +} +} +static lean_object* _init_l_generateDataCode___closed__3() { +_start: +{ +lean_object* x_1; +x_1 = lean_mk_string_unchecked("\n};\n", 4, 4); +return x_1; +} +} +LEAN_EXPORT lean_object* l_generateDataCode(lean_object* x_1) { +_start: +{ +lean_object* x_2; lean_object* x_3; lean_object* x_4; lean_object* x_5; lean_object* x_6; lean_object* x_7; lean_object* x_8; lean_object* x_9; lean_object* x_10; lean_object* x_11; lean_object* x_12; lean_object* x_13; +x_2 = l_ByteArray_toList(x_1); +x_3 = lean_box(0); +x_4 = l_List_mapTR_loop___at_generateDataCode___spec__1(x_2, x_3); +x_5 = lean_unsigned_to_nat(20u); +x_6 = l_List_chunksOf___rarg(x_5, x_4); +x_7 = l_List_mapTR_loop___at_generateDataCode___spec__2(x_6, x_3); +x_8 = l_generateDataCode___closed__1; +x_9 = l_String_intercalate(x_8, x_7); +x_10 = l_generateDataCode___closed__2; +x_11 = lean_string_append(x_10, x_9); +lean_dec(x_9); +x_12 = l_generateDataCode___closed__3; +x_13 = lean_string_append(x_11, x_12); +return x_13; +} +} +LEAN_EXPORT lean_object* l_generateDataCode___boxed(lean_object* x_1) { +_start: +{ +lean_object* x_2; +x_2 = l_generateDataCode(x_1); +lean_dec(x_1); +return x_2; +} +} +static lean_object* _init_l_generateResourceInfoSize___closed__1() { +_start: +{ +lean_object* x_1; +x_1 = lean_mk_string_unchecked("size_t resources_size = ", 24, 24); +return x_1; +} +} +static lean_object* _init_l_generateResourceInfoSize___closed__2() { +_start: +{ +lean_object* x_1; +x_1 = lean_mk_string_unchecked(";\n", 2, 2); +return x_1; +} +} +LEAN_EXPORT lean_object* l_generateResourceInfoSize(lean_object* x_1) { +_start: +{ +lean_object* x_2; lean_object* x_3; lean_object* x_4; lean_object* x_5; lean_object* x_6; +x_2 = l___private_Init_Data_Repr_0__Nat_reprFast(x_1); +x_3 = l_generateResourceInfoSize___closed__1; +x_4 = lean_string_append(x_3, x_2); +lean_dec(x_2); +x_5 = l_generateResourceInfoSize___closed__2; +x_6 = lean_string_append(x_4, x_5); +return x_6; +} +} +static lean_object* _init_l_Array_mapMUnsafe_map___at_generateResourceInfoCode___spec__1___closed__1() { +_start: +{ +lean_object* x_1; +x_1 = lean_mk_string_unchecked(" {.filename = \"", 18, 18); +return x_1; +} +} +static lean_object* _init_l_Array_mapMUnsafe_map___at_generateResourceInfoCode___spec__1___closed__2() { +_start: +{ +lean_object* x_1; +x_1 = lean_mk_string_unchecked("\", .offset = ", 13, 13); +return x_1; +} +} +static lean_object* _init_l_Array_mapMUnsafe_map___at_generateResourceInfoCode___spec__1___closed__3() { +_start: +{ +lean_object* x_1; +x_1 = lean_mk_string_unchecked(", .size = ", 10, 10); +return x_1; +} +} +static lean_object* _init_l_Array_mapMUnsafe_map___at_generateResourceInfoCode___spec__1___closed__4() { +_start: +{ +lean_object* x_1; +x_1 = lean_mk_string_unchecked("},", 2, 2); +return x_1; +} +} +LEAN_EXPORT lean_object* l_Array_mapMUnsafe_map___at_generateResourceInfoCode___spec__1(size_t x_1, size_t x_2, lean_object* x_3) { +_start: +{ +uint8_t x_4; +x_4 = lean_usize_dec_lt(x_2, x_1); +if (x_4 == 0) +{ +return x_3; +} +else +{ +lean_object* x_5; lean_object* x_6; lean_object* x_7; lean_object* x_8; lean_object* x_9; lean_object* x_10; lean_object* x_11; lean_object* x_12; lean_object* x_13; lean_object* x_14; lean_object* x_15; lean_object* x_16; lean_object* x_17; lean_object* x_18; lean_object* x_19; lean_object* x_20; lean_object* x_21; lean_object* x_22; size_t x_23; size_t x_24; lean_object* x_25; +x_5 = lean_array_uget(x_3, x_2); +x_6 = lean_unsigned_to_nat(0u); +x_7 = lean_array_uset(x_3, x_2, x_6); +x_8 = lean_ctor_get(x_5, 0); +lean_inc(x_8); +x_9 = l_Array_mapMUnsafe_map___at_generateResourceInfoCode___spec__1___closed__1; +x_10 = lean_string_append(x_9, x_8); +lean_dec(x_8); +x_11 = l_Array_mapMUnsafe_map___at_generateResourceInfoCode___spec__1___closed__2; +x_12 = lean_string_append(x_10, x_11); +x_13 = lean_ctor_get(x_5, 1); +lean_inc(x_13); +x_14 = l___private_Init_Data_Repr_0__Nat_reprFast(x_13); +x_15 = lean_string_append(x_12, x_14); +lean_dec(x_14); +x_16 = l_Array_mapMUnsafe_map___at_generateResourceInfoCode___spec__1___closed__3; +x_17 = lean_string_append(x_15, x_16); +x_18 = lean_ctor_get(x_5, 2); +lean_inc(x_18); +lean_dec(x_5); +x_19 = l___private_Init_Data_Repr_0__Nat_reprFast(x_18); +x_20 = lean_string_append(x_17, x_19); +lean_dec(x_19); +x_21 = l_Array_mapMUnsafe_map___at_generateResourceInfoCode___spec__1___closed__4; +x_22 = lean_string_append(x_20, x_21); +x_23 = 1; +x_24 = lean_usize_add(x_2, x_23); +x_25 = lean_array_uset(x_7, x_2, x_22); +x_2 = x_24; +x_3 = x_25; +goto _start; +} +} +} +static lean_object* _init_l_generateResourceInfoCode___closed__1() { +_start: +{ +lean_object* x_1; +x_1 = lean_mk_string_unchecked("static const ResourceInfo resource_infos[", 41, 41); +return x_1; +} +} +static lean_object* _init_l_generateResourceInfoCode___closed__2() { +_start: +{ +lean_object* x_1; +x_1 = lean_mk_string_unchecked("] = {\n", 6, 6); +return x_1; +} +} +LEAN_EXPORT lean_object* l_generateResourceInfoCode(lean_object* x_1) { +_start: +{ +size_t x_2; size_t x_3; lean_object* x_4; lean_object* x_5; lean_object* x_6; lean_object* x_7; lean_object* x_8; lean_object* x_9; lean_object* x_10; lean_object* x_11; lean_object* x_12; lean_object* x_13; lean_object* x_14; lean_object* x_15; lean_object* x_16; +x_2 = lean_array_size(x_1); +x_3 = 0; +lean_inc(x_1); +x_4 = l_Array_mapMUnsafe_map___at_generateResourceInfoCode___spec__1(x_2, x_3, x_1); +x_5 = lean_array_get_size(x_1); +lean_dec(x_1); +x_6 = l___private_Init_Data_Repr_0__Nat_reprFast(x_5); +x_7 = l_generateResourceInfoCode___closed__1; +x_8 = lean_string_append(x_7, x_6); +lean_dec(x_6); +x_9 = l_generateResourceInfoCode___closed__2; +x_10 = lean_string_append(x_8, x_9); +x_11 = lean_array_to_list(x_4); +x_12 = l_generateDataCode___closed__1; +x_13 = l_String_intercalate(x_12, x_11); +x_14 = lean_string_append(x_10, x_13); +lean_dec(x_13); +x_15 = l_generateDataCode___closed__3; +x_16 = lean_string_append(x_14, x_15); +return x_16; +} +} +LEAN_EXPORT lean_object* l_Array_mapMUnsafe_map___at_generateResourceInfoCode___spec__1___boxed(lean_object* x_1, lean_object* x_2, lean_object* x_3) { +_start: +{ +size_t x_4; size_t x_5; lean_object* x_6; +x_4 = lean_unbox_usize(x_1); +lean_dec(x_1); +x_5 = lean_unbox_usize(x_2); +lean_dec(x_2); +x_6 = l_Array_mapMUnsafe_map___at_generateResourceInfoCode___spec__1(x_4, x_5, x_3); +return x_6; +} +} +static lean_object* _init_l_assembleBundleFile___closed__1() { +_start: +{ +lean_object* x_1; +x_1 = lean_mk_string_unchecked("#ifndef ", 8, 8); +return x_1; +} +} +static lean_object* _init_l_assembleBundleFile___closed__2() { +_start: +{ +lean_object* x_1; +x_1 = lean_mk_string_unchecked("RAYLEAN_BUNDLE_H", 16, 16); +return x_1; +} +} +static lean_object* _init_l_assembleBundleFile___closed__3() { +_start: +{ +lean_object* x_1; lean_object* x_2; lean_object* x_3; +x_1 = l_assembleBundleFile___closed__1; +x_2 = l_assembleBundleFile___closed__2; +x_3 = lean_string_append(x_1, x_2); +return x_3; +} +} +static lean_object* _init_l_assembleBundleFile___closed__4() { +_start: +{ +lean_object* x_1; +x_1 = lean_mk_string_unchecked("\n#define ", 9, 9); +return x_1; +} +} +static lean_object* _init_l_assembleBundleFile___closed__5() { +_start: +{ +lean_object* x_1; lean_object* x_2; lean_object* x_3; +x_1 = l_assembleBundleFile___closed__3; +x_2 = l_assembleBundleFile___closed__4; +x_3 = lean_string_append(x_1, x_2); +return x_3; +} +} +static lean_object* _init_l_assembleBundleFile___closed__6() { +_start: +{ +lean_object* x_1; lean_object* x_2; lean_object* x_3; +x_1 = l_assembleBundleFile___closed__5; +x_2 = l_assembleBundleFile___closed__2; +x_3 = lean_string_append(x_1, x_2); +return x_3; +} +} +static lean_object* _init_l_assembleBundleFile___closed__7() { +_start: +{ +lean_object* x_1; +x_1 = lean_mk_string_unchecked("\n\n#include \n\n", 23, 23); +return x_1; +} +} +static lean_object* _init_l_assembleBundleFile___closed__8() { +_start: +{ +lean_object* x_1; lean_object* x_2; lean_object* x_3; +x_1 = l_assembleBundleFile___closed__6; +x_2 = l_assembleBundleFile___closed__7; +x_3 = lean_string_append(x_1, x_2); +return x_3; +} +} +static lean_object* _init_l_assembleBundleFile___closed__9() { +_start: +{ +lean_object* x_1; +x_1 = lean_mk_string_unchecked("typedef struct {\n const char* filename;\n size_t offset;\n size_t size;\n} ResourceInfo;\n", 95, 95); +return x_1; +} +} +static lean_object* _init_l_assembleBundleFile___closed__10() { +_start: +{ +lean_object* x_1; lean_object* x_2; lean_object* x_3; +x_1 = l_assembleBundleFile___closed__8; +x_2 = l_assembleBundleFile___closed__9; +x_3 = lean_string_append(x_1, x_2); +return x_3; +} +} +static lean_object* _init_l_assembleBundleFile___closed__11() { +_start: +{ +lean_object* x_1; lean_object* x_2; lean_object* x_3; +x_1 = l_assembleBundleFile___closed__10; +x_2 = l_generateDataCode___closed__1; +x_3 = lean_string_append(x_1, x_2); +return x_3; +} +} +static lean_object* _init_l_assembleBundleFile___closed__12() { +_start: +{ +lean_object* x_1; +x_1 = lean_mk_string_unchecked("#endif // ", 10, 10); +return x_1; +} +} +LEAN_EXPORT lean_object* l_assembleBundleFile(lean_object* x_1) { +_start: +{ +lean_object* x_2; lean_object* x_3; lean_object* x_4; lean_object* x_5; lean_object* x_6; lean_object* x_7; lean_object* x_8; lean_object* x_9; lean_object* x_10; lean_object* x_11; lean_object* x_12; lean_object* x_13; lean_object* x_14; lean_object* x_15; +x_2 = lean_ctor_get(x_1, 1); +lean_inc(x_2); +x_3 = l_generateResourceInfoCode(x_2); +x_4 = lean_ctor_get(x_1, 0); +lean_inc(x_4); +lean_dec(x_1); +x_5 = l_generateDataCode(x_4); +lean_dec(x_4); +x_6 = l_assembleBundleFile___closed__11; +x_7 = lean_string_append(x_6, x_3); +lean_dec(x_3); +x_8 = l_generateDataCode___closed__1; +x_9 = lean_string_append(x_7, x_8); +x_10 = lean_string_append(x_9, x_5); +lean_dec(x_5); +x_11 = l_assembleBundleFile___closed__12; +x_12 = lean_string_append(x_10, x_11); +x_13 = l_assembleBundleFile___closed__2; +x_14 = lean_string_append(x_12, x_13); +x_15 = lean_string_append(x_14, x_8); +return x_15; +} +} +LEAN_EXPORT lean_object* l_IO_println___at_main___spec__1(lean_object* x_1, lean_object* x_2) { +_start: +{ +uint32_t x_3; lean_object* x_4; lean_object* x_5; +x_3 = 10; +x_4 = lean_string_push(x_1, x_3); +x_5 = l_IO_print___at_IO_println___spec__1(x_4, x_2); +return x_5; +} +} +static lean_object* _init_l_main___closed__1() { +_start: +{ +lean_object* x_1; +x_1 = lean_mk_string_unchecked("Usage: makeBundle ", 59, 59); +return x_1; +} +} +static lean_object* _init_l_main___closed__2() { +_start: +{ +lean_object* x_1; +x_1 = lean_mk_string_unchecked(" must be a subdirectory of ", 27, 27); +return x_1; +} +} +LEAN_EXPORT lean_object* _lean_main(lean_object* x_1, lean_object* x_2) { +_start: +{ +if (lean_obj_tag(x_1) == 0) +{ +lean_object* x_3; lean_object* x_4; +x_3 = l_main___closed__1; +x_4 = l_IO_println___at_main___spec__1(x_3, x_2); +return x_4; +} +else +{ +lean_object* x_5; +x_5 = lean_ctor_get(x_1, 1); +lean_inc(x_5); +if (lean_obj_tag(x_5) == 0) +{ +lean_object* x_6; lean_object* x_7; +lean_dec(x_1); +x_6 = l_main___closed__1; +x_7 = l_IO_println___at_main___spec__1(x_6, x_2); +return x_7; +} +else +{ +lean_object* x_8; +x_8 = lean_ctor_get(x_5, 1); +lean_inc(x_8); +if (lean_obj_tag(x_8) == 0) +{ +lean_object* x_9; lean_object* x_10; +lean_dec(x_5); +lean_dec(x_1); +x_9 = l_main___closed__1; +x_10 = l_IO_println___at_main___spec__1(x_9, x_2); +return x_10; +} +else +{ +lean_object* x_11; +x_11 = lean_ctor_get(x_8, 1); +lean_inc(x_11); +if (lean_obj_tag(x_11) == 0) +{ +lean_object* x_12; lean_object* x_13; lean_object* x_14; lean_object* x_15; lean_object* x_16; lean_object* x_17; lean_object* x_18; lean_object* x_19; uint8_t x_20; +x_12 = lean_ctor_get(x_1, 0); +lean_inc(x_12); +lean_dec(x_1); +x_13 = lean_ctor_get(x_5, 0); +lean_inc(x_13); +lean_dec(x_5); +x_14 = lean_ctor_get(x_8, 0); +lean_inc(x_14); +lean_dec(x_8); +lean_inc(x_12); +x_15 = l_System_FilePath_join(x_12, x_13); +x_16 = l_System_FilePath_pathExists(x_15, x_2); +x_17 = lean_ctor_get(x_16, 0); +lean_inc(x_17); +x_18 = lean_ctor_get(x_16, 1); +lean_inc(x_18); +lean_dec(x_16); +x_19 = l_System_FilePath_isDir(x_15, x_18); +x_20 = lean_unbox(x_17); +lean_dec(x_17); +if (x_20 == 0) +{ +lean_object* x_21; lean_object* x_22; lean_object* x_23; +lean_dec(x_15); +lean_dec(x_14); +x_21 = lean_ctor_get(x_19, 1); +lean_inc(x_21); +lean_dec(x_19); +x_22 = l_main___closed__1; +x_23 = l_IO_println___at_main___spec__1(x_22, x_21); +if (lean_obj_tag(x_23) == 0) +{ +lean_object* x_24; lean_object* x_25; lean_object* x_26; lean_object* x_27; lean_object* x_28; lean_object* x_29; lean_object* x_30; lean_object* x_31; +x_24 = lean_ctor_get(x_23, 1); +lean_inc(x_24); +lean_dec(x_23); +x_25 = l_hexEncodeByte___closed__1; +x_26 = lean_string_append(x_25, x_13); +lean_dec(x_13); +x_27 = l_main___closed__2; +x_28 = lean_string_append(x_26, x_27); +x_29 = lean_string_append(x_28, x_12); +lean_dec(x_12); +x_30 = lean_string_append(x_29, x_25); +x_31 = l_IO_println___at_main___spec__1(x_30, x_24); +return x_31; +} +else +{ +uint8_t x_32; +lean_dec(x_13); +lean_dec(x_12); +x_32 = !lean_is_exclusive(x_23); +if (x_32 == 0) +{ +return x_23; +} +else +{ +lean_object* x_33; lean_object* x_34; lean_object* x_35; +x_33 = lean_ctor_get(x_23, 0); +x_34 = lean_ctor_get(x_23, 1); +lean_inc(x_34); +lean_inc(x_33); +lean_dec(x_23); +x_35 = lean_alloc_ctor(1, 2, 0); +lean_ctor_set(x_35, 0, x_33); +lean_ctor_set(x_35, 1, x_34); +return x_35; +} +} +} +else +{ +lean_object* x_36; uint8_t x_37; +x_36 = lean_ctor_get(x_19, 0); +lean_inc(x_36); +x_37 = lean_unbox(x_36); +lean_dec(x_36); +if (x_37 == 0) +{ +lean_object* x_38; lean_object* x_39; lean_object* x_40; +lean_dec(x_15); +lean_dec(x_14); +x_38 = lean_ctor_get(x_19, 1); +lean_inc(x_38); +lean_dec(x_19); +x_39 = l_main___closed__1; +x_40 = l_IO_println___at_main___spec__1(x_39, x_38); +if (lean_obj_tag(x_40) == 0) +{ +lean_object* x_41; lean_object* x_42; lean_object* x_43; lean_object* x_44; lean_object* x_45; lean_object* x_46; lean_object* x_47; lean_object* x_48; +x_41 = lean_ctor_get(x_40, 1); +lean_inc(x_41); +lean_dec(x_40); +x_42 = l_hexEncodeByte___closed__1; +x_43 = lean_string_append(x_42, x_13); +lean_dec(x_13); +x_44 = l_main___closed__2; +x_45 = lean_string_append(x_43, x_44); +x_46 = lean_string_append(x_45, x_12); +lean_dec(x_12); +x_47 = lean_string_append(x_46, x_42); +x_48 = l_IO_println___at_main___spec__1(x_47, x_41); +return x_48; +} +else +{ +uint8_t x_49; +lean_dec(x_13); +lean_dec(x_12); +x_49 = !lean_is_exclusive(x_40); +if (x_49 == 0) +{ +return x_40; +} +else +{ +lean_object* x_50; lean_object* x_51; lean_object* x_52; +x_50 = lean_ctor_get(x_40, 0); +x_51 = lean_ctor_get(x_40, 1); +lean_inc(x_51); +lean_inc(x_50); +lean_dec(x_40); +x_52 = lean_alloc_ctor(1, 2, 0); +lean_ctor_set(x_52, 0, x_50); +lean_ctor_set(x_52, 1, x_51); +return x_52; +} +} +} +else +{ +lean_object* x_53; lean_object* x_54; +lean_dec(x_13); +x_53 = lean_ctor_get(x_19, 1); +lean_inc(x_53); +lean_dec(x_19); +x_54 = l_readFilesFromDir(x_15, x_12, x_53); +lean_dec(x_15); +if (lean_obj_tag(x_54) == 0) +{ +lean_object* x_55; lean_object* x_56; lean_object* x_57; lean_object* x_58; lean_object* x_59; +x_55 = lean_ctor_get(x_54, 0); +lean_inc(x_55); +x_56 = lean_ctor_get(x_54, 1); +lean_inc(x_56); +lean_dec(x_54); +x_57 = l_mkResourcesInfo(x_55); +lean_dec(x_55); +x_58 = l_assembleBundleFile(x_57); +x_59 = l_IO_FS_writeFile(x_14, x_58, x_56); +lean_dec(x_58); +lean_dec(x_14); +return x_59; +} +else +{ +uint8_t x_60; +lean_dec(x_14); +x_60 = !lean_is_exclusive(x_54); +if (x_60 == 0) +{ +return x_54; +} +else +{ +lean_object* x_61; lean_object* x_62; lean_object* x_63; +x_61 = lean_ctor_get(x_54, 0); +x_62 = lean_ctor_get(x_54, 1); +lean_inc(x_62); +lean_inc(x_61); +lean_dec(x_54); +x_63 = lean_alloc_ctor(1, 2, 0); +lean_ctor_set(x_63, 0, x_61); +lean_ctor_set(x_63, 1, x_62); +return x_63; +} +} +} +} +} +else +{ +lean_object* x_64; lean_object* x_65; +lean_dec(x_11); +lean_dec(x_8); +lean_dec(x_5); +lean_dec(x_1); +x_64 = l_main___closed__1; +x_65 = l_IO_println___at_main___spec__1(x_64, x_2); +return x_65; +} +} +} +} +} +} +lean_object* initialize_Init(uint8_t builtin, lean_object*); +static bool _G_initialized = false; +LEAN_EXPORT lean_object* initialize_scripts_makeBundle(uint8_t builtin, lean_object* w) { +lean_object * res; +if (_G_initialized) return lean_io_result_mk_ok(lean_box(0)); +_G_initialized = true; +res = initialize_Init(builtin, lean_io_mk_world()); +if (lean_io_result_is_error(res)) return res; +lean_dec_ref(res); +l_mkResourcesInfo___closed__1 = _init_l_mkResourcesInfo___closed__1(); +lean_mark_persistent(l_mkResourcesInfo___closed__1); +l_mkResourcesInfo___closed__2 = _init_l_mkResourcesInfo___closed__2(); +lean_mark_persistent(l_mkResourcesInfo___closed__2); +l_hexEncodeByte___closed__1 = _init_l_hexEncodeByte___closed__1(); +lean_mark_persistent(l_hexEncodeByte___closed__1); +l_hexEncodeByte___closed__2 = _init_l_hexEncodeByte___closed__2(); +lean_mark_persistent(l_hexEncodeByte___closed__2); +l_List_chunksOf___rarg___closed__1 = _init_l_List_chunksOf___rarg___closed__1(); +lean_mark_persistent(l_List_chunksOf___rarg___closed__1); +l_List_chunksOf___rarg___closed__2 = _init_l_List_chunksOf___rarg___closed__2(); +lean_mark_persistent(l_List_chunksOf___rarg___closed__2); +l_List_chunksOf___rarg___closed__3 = _init_l_List_chunksOf___rarg___closed__3(); +lean_mark_persistent(l_List_chunksOf___rarg___closed__3); +l_List_chunksOf___rarg___closed__4 = _init_l_List_chunksOf___rarg___closed__4(); +lean_mark_persistent(l_List_chunksOf___rarg___closed__4); +l_List_mapTR_loop___at_generateDataCode___spec__2___closed__1 = _init_l_List_mapTR_loop___at_generateDataCode___spec__2___closed__1(); +lean_mark_persistent(l_List_mapTR_loop___at_generateDataCode___spec__2___closed__1); +l_List_mapTR_loop___at_generateDataCode___spec__2___closed__2 = _init_l_List_mapTR_loop___at_generateDataCode___spec__2___closed__2(); +lean_mark_persistent(l_List_mapTR_loop___at_generateDataCode___spec__2___closed__2); +l_List_mapTR_loop___at_generateDataCode___spec__2___closed__3 = _init_l_List_mapTR_loop___at_generateDataCode___spec__2___closed__3(); +lean_mark_persistent(l_List_mapTR_loop___at_generateDataCode___spec__2___closed__3); +l_generateDataCode___closed__1 = _init_l_generateDataCode___closed__1(); +lean_mark_persistent(l_generateDataCode___closed__1); +l_generateDataCode___closed__2 = _init_l_generateDataCode___closed__2(); +lean_mark_persistent(l_generateDataCode___closed__2); +l_generateDataCode___closed__3 = _init_l_generateDataCode___closed__3(); +lean_mark_persistent(l_generateDataCode___closed__3); +l_generateResourceInfoSize___closed__1 = _init_l_generateResourceInfoSize___closed__1(); +lean_mark_persistent(l_generateResourceInfoSize___closed__1); +l_generateResourceInfoSize___closed__2 = _init_l_generateResourceInfoSize___closed__2(); +lean_mark_persistent(l_generateResourceInfoSize___closed__2); +l_Array_mapMUnsafe_map___at_generateResourceInfoCode___spec__1___closed__1 = _init_l_Array_mapMUnsafe_map___at_generateResourceInfoCode___spec__1___closed__1(); +lean_mark_persistent(l_Array_mapMUnsafe_map___at_generateResourceInfoCode___spec__1___closed__1); +l_Array_mapMUnsafe_map___at_generateResourceInfoCode___spec__1___closed__2 = _init_l_Array_mapMUnsafe_map___at_generateResourceInfoCode___spec__1___closed__2(); +lean_mark_persistent(l_Array_mapMUnsafe_map___at_generateResourceInfoCode___spec__1___closed__2); +l_Array_mapMUnsafe_map___at_generateResourceInfoCode___spec__1___closed__3 = _init_l_Array_mapMUnsafe_map___at_generateResourceInfoCode___spec__1___closed__3(); +lean_mark_persistent(l_Array_mapMUnsafe_map___at_generateResourceInfoCode___spec__1___closed__3); +l_Array_mapMUnsafe_map___at_generateResourceInfoCode___spec__1___closed__4 = _init_l_Array_mapMUnsafe_map___at_generateResourceInfoCode___spec__1___closed__4(); +lean_mark_persistent(l_Array_mapMUnsafe_map___at_generateResourceInfoCode___spec__1___closed__4); +l_generateResourceInfoCode___closed__1 = _init_l_generateResourceInfoCode___closed__1(); +lean_mark_persistent(l_generateResourceInfoCode___closed__1); +l_generateResourceInfoCode___closed__2 = _init_l_generateResourceInfoCode___closed__2(); +lean_mark_persistent(l_generateResourceInfoCode___closed__2); +l_assembleBundleFile___closed__1 = _init_l_assembleBundleFile___closed__1(); +lean_mark_persistent(l_assembleBundleFile___closed__1); +l_assembleBundleFile___closed__2 = _init_l_assembleBundleFile___closed__2(); +lean_mark_persistent(l_assembleBundleFile___closed__2); +l_assembleBundleFile___closed__3 = _init_l_assembleBundleFile___closed__3(); +lean_mark_persistent(l_assembleBundleFile___closed__3); +l_assembleBundleFile___closed__4 = _init_l_assembleBundleFile___closed__4(); +lean_mark_persistent(l_assembleBundleFile___closed__4); +l_assembleBundleFile___closed__5 = _init_l_assembleBundleFile___closed__5(); +lean_mark_persistent(l_assembleBundleFile___closed__5); +l_assembleBundleFile___closed__6 = _init_l_assembleBundleFile___closed__6(); +lean_mark_persistent(l_assembleBundleFile___closed__6); +l_assembleBundleFile___closed__7 = _init_l_assembleBundleFile___closed__7(); +lean_mark_persistent(l_assembleBundleFile___closed__7); +l_assembleBundleFile___closed__8 = _init_l_assembleBundleFile___closed__8(); +lean_mark_persistent(l_assembleBundleFile___closed__8); +l_assembleBundleFile___closed__9 = _init_l_assembleBundleFile___closed__9(); +lean_mark_persistent(l_assembleBundleFile___closed__9); +l_assembleBundleFile___closed__10 = _init_l_assembleBundleFile___closed__10(); +lean_mark_persistent(l_assembleBundleFile___closed__10); +l_assembleBundleFile___closed__11 = _init_l_assembleBundleFile___closed__11(); +lean_mark_persistent(l_assembleBundleFile___closed__11); +l_assembleBundleFile___closed__12 = _init_l_assembleBundleFile___closed__12(); +lean_mark_persistent(l_assembleBundleFile___closed__12); +l_main___closed__1 = _init_l_main___closed__1(); +lean_mark_persistent(l_main___closed__1); +l_main___closed__2 = _init_l_main___closed__2(); +lean_mark_persistent(l_main___closed__2); +return lean_io_result_mk_ok(lean_box(0)); +} +void lean_initialize_runtime_module(); + + #if defined(WIN32) || defined(_WIN32) + #include + #endif + + int main(int argc, char ** argv) { + #if defined(WIN32) || defined(_WIN32) + SetErrorMode(SEM_FAILCRITICALERRORS); + #endif + lean_object* in; lean_object* res; +lean_initialize_runtime_module(); +lean_set_panic_messages(false); +res = initialize_scripts_makeBundle(1 /* builtin */, lean_io_mk_world()); +lean_set_panic_messages(true); +lean_io_mark_end_initialization(); +if (lean_io_result_is_ok(res)) { +lean_dec_ref(res); +lean_init_task_manager(); +in = lean_box(0); +int i = argc; +while (i > 1) { + lean_object* n; + i--; + n = lean_alloc_ctor(1,2,0); lean_ctor_set(n, 0, lean_mk_string(argv[i])); lean_ctor_set(n, 1, in); + in = n; +} +res = _lean_main(in, lean_io_mk_world()); +} +lean_finalize_task_manager(); +if (lean_io_result_is_ok(res)) { + int ret = 0; + lean_dec_ref(res); + return ret; +} else { + lean_io_result_show_error(res); + lean_dec_ref(res); + return 1; +} +} +#ifdef __cplusplus +} +#endif diff --git a/c/raylib_bindings.c b/c/raylib_bindings.c deleted file mode 100644 index 41c579b..0000000 --- a/c/raylib_bindings.c +++ /dev/null @@ -1,537 +0,0 @@ -#include -#include -#include -#include - -#define IO_UNIT (lean_io_result_mk_ok(lean_box(0))) - -// leanc doesn't provide stdlib.h -void *memcpy(void *, const void *, size_t); -void *malloc(size_t); -void *calloc(size_t, size_t); - -// leanc doesn't provide string.h -int strcmp(const char *s1, const char *s2) { - while (*s1 && (*s1 == *s2)) { - s1++; - s2++; - } - return *(const unsigned char *)s1 - *(const unsigned char *)s2; -} - -#ifdef RAYLEAN_NO_BUNDLE - -void bundle_free_resource(void *data) { - UnloadFileData(data); -} - -void* bundle_load_resource(const char* filepath, size_t *size) { - int dataSize; - void* data = (void*) LoadFileData(filepath, &dataSize); - *size = dataSize; - return data; -} - -#else - -#include "bundle.h" - -// The number of resources stored in the bundle -size_t resourceInfoSize = sizeof(resource_infos) / sizeof(ResourceInfo); - -void bundle_free_resource(void *data) {} - -// Load data from from the bundle -void *bundle_load_resource(const char *filename, size_t *size) { - for (size_t i = 0; i < resourceInfoSize; i++) { - if (strcmp(resource_infos[i].filename, filename) == 0) { - *size = resource_infos[i].size; - return (void*) &bundle_data[resource_infos[i].offset]; - } - } - return NULL; -} - -#endif - -static inline lean_obj_res string_io_error(const char *msg) { - return lean_io_result_mk_error(lean_mk_io_user_error(lean_mk_string(msg))); -} - -/* TEXTURE */ - -static lean_external_class *raylib_texture2d_class = NULL; - -// The finalizer is run by the lean runtime when a Texture2D is garbage -// collected -static void raylib_texture2d_finalizer(void *texture2d) { - UnloadTexture(*(Texture2D *)texture2d); - lean_free_small(texture2d); -} - -static void raylib_texture2d_foreach(void *mod, b_lean_obj_arg fn) {} - -static lean_external_class *get_raylib_texture2d_class(void) { - if (raylib_texture2d_class == NULL) { - raylib_texture2d_class = lean_register_external_class( - &raylib_texture2d_finalizer, &raylib_texture2d_foreach); - } - return raylib_texture2d_class; -} - -static inline Texture2D *texture2d_of_arg(b_lean_obj_arg texture2d) { - return (Texture2D *)lean_get_external_data(texture2d); -} - -static lean_object *texture2d_obj_mk(Texture2D texture2d) { - // Allocate a pointer to the Texture2D struct on the heap - Texture2D *texture2d_ptr = (void *)lean_alloc_small_object(sizeof(Texture2D)); - if (texture2d_ptr == NULL) { - return string_io_error("texture2d_obj_mk: lean_alloc_small_object failed"); - } - *texture2d_ptr = texture2d; - // Register the Texture2D pointer in the Lean runtime - return lean_alloc_external(get_raylib_texture2d_class(), - (void *)texture2d_ptr); -} - -lean_obj_res texture2d_width(b_lean_obj_arg texture) { - return lean_uint32_to_nat(texture2d_of_arg(texture)->width); -} - -lean_obj_res texture2d_height(b_lean_obj_arg texture) { - return lean_uint32_to_nat(texture2d_of_arg(texture)->height); -} - -/* IMAGE */ - -static lean_external_class *raylib_image_class = NULL; - -// The finalizer is run by the lean runtime when an Image is garbage collected -static void raylib_image_finalizer(void *image) { - UnloadImage(*(Image *)image); - lean_free_small(image); -} - -static void raylib_image_foreach(void *mod, b_lean_obj_arg fn) {} - -static lean_external_class *get_raylib_image_class(void) { - if (raylib_image_class == NULL) { - raylib_image_class = lean_register_external_class(&raylib_image_finalizer, - &raylib_image_foreach); - } - return raylib_image_class; -} - -static inline Image *image_of_arg(b_lean_obj_arg image) { - return (Image *)lean_get_external_data(image); -} - -static lean_object *image_obj_mk(Image image) { - // Allocate a pointer to the Image struct on the heap - Image *image_ptr = (void *)lean_alloc_small_object(sizeof(Image)); - if (image_ptr == NULL) { - return string_io_error("image_obj_mk: lean_alloc_small_object failed"); - } - *image_ptr = image; - // Register the Image pointer in the Lean runtime - return lean_alloc_external(get_raylib_image_class(), (void *)image_ptr); -} - -lean_obj_res image_width(b_lean_obj_arg image) { - return lean_uint32_to_nat(image_of_arg(image)->width); -} - -lean_obj_res image_height(b_lean_obj_arg image) { - return lean_uint32_to_nat(image_of_arg(image)->height); -} - - -#ifdef RAYLEAN_NO_RESVG - -Image loadImageFromData(const char *ext, const char *data, size_t size) { - return LoadImageFromMemory(ext, (unsigned char *)data, size); -} - -#else - -Image loadImageFromData(const char *ext, const char *data, size_t size) { - if (strcmp(".svg", ext) == 0 || strcmp(".SVG", ext) == 0) { - Image image = {0}; - resvg_options *opt = resvg_options_create(); - resvg_render_tree *tree; - int err = resvg_parse_tree_from_data(data, size, opt, &tree); - resvg_options_destroy(opt); - - if (err != RESVG_OK) { - TraceLog(LOG_ERROR, "resvg error: %i", err); - resvg_tree_destroy(tree); - return image; - } - - resvg_size size = resvg_get_image_size(tree); - int width = (int)size.width; - int height = (int)size.height; - - TraceLog(LOG_INFO, "resvg calculated width: %i, height: %i", width, height); - - // Uses calloc here because the data should contain "premultiplied pixels" so - // perhaps it's assumed it's initialized memory. - // - // The size is specified in the resvg docs. - // - // We use RL_CALLOC instead of calloc because UnloadImage uses RL_FREE. - char *img = (char *) RL_CALLOC(width * height * 4, sizeof(char)); - resvg_render(tree, resvg_transform_identity(), width, height, img); - resvg_tree_destroy(tree); - - image.data = img; - image.width = width; - image.height = height; - image.mipmaps = 1; - image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; - return image; - } else { - return LoadImageFromMemory(ext, (unsigned char *)data, size); - } -} - - -#endif - -// Load an image from a resource -// Resources are loaded from the resources/ directory in the project -lean_obj_res loadImage(b_lean_obj_arg resource_name_arg) { - // Load the data associated with the resource from the bundle - const char *resource_name = lean_string_cstr(resource_name_arg); - size_t size; - const char *data = bundle_load_resource(resource_name, &size); - if (data == NULL) { - return string_io_error("loadImage: getFileData failed"); - } - - // Extract the extension from the resource_name - const char *ext = GetFileExtension(resource_name); - if (ext == NULL) { - return string_io_error("loadImage: GetFileExtension failed"); - } - - Image image = loadImageFromData(ext, data, size); - - bundle_free_resource((void*) data); - - if (!IsImageReady(image)) { - return string_io_error("loadImage: LoadImageFromMemory failed"); - } - - lean_object *image_lean = image_obj_mk(image); - if (image_lean == NULL) { - return string_io_error("loadImage: image_obj_mk failed"); - } - - return lean_io_result_mk_ok(image_lean); -} - -static inline Color color_of_arg(lean_obj_arg color) { - uint8_t r = lean_ctor_get_uint8(color, 0); - uint8_t g = lean_ctor_get_uint8(color, 1); - uint8_t b = lean_ctor_get_uint8(color, 2); - uint8_t a = lean_ctor_get_uint8(color, 3); - return (Color){r, g, b, a}; -} - -static inline Vector2 vector2_of_arg(lean_obj_arg vector2) { - double x = lean_ctor_get_float(vector2, 0); - double y = lean_ctor_get_float(vector2, sizeof(double)); - return (Vector2){x, y}; -} - -static inline lean_object *vector2_obj_mk(Vector2 vector2) { - lean_object *vector2_obj = lean_alloc_ctor(0, 0, sizeof(double) * 2); - lean_ctor_set_float(vector2_obj, 0, vector2.x); - lean_ctor_set_float(vector2_obj, sizeof(double), vector2.y); - return vector2_obj; -} - -static inline Vector3 vector3_of_arg(lean_obj_arg vector3) { - double x = lean_ctor_get_float(vector3, 0); - double y = lean_ctor_get_float(vector3, sizeof(double)); - double z = lean_ctor_get_float(vector3, sizeof(double) * 2); - return (Vector3){x, y, z}; -} - -static inline lean_object *vector3_obj_mk(Vector3 vector3) { - lean_object *vector3_obj = lean_alloc_ctor(0, 0, sizeof(double) * 3); - lean_ctor_set_float(vector3_obj, 0, vector3.x); - lean_ctor_set_float(vector3_obj, sizeof(double), vector3.y); - lean_ctor_set_float(vector3_obj, sizeof(double) * 2, vector3.z); - return vector3_obj; -} - -static inline Rectangle rectangle_of_arg(lean_obj_arg rectangle) { - double x = lean_ctor_get_float(rectangle, 0); - double y = lean_ctor_get_float(rectangle, sizeof(double)); - double width = lean_ctor_get_float(rectangle, sizeof(double) * 2); - double height = lean_ctor_get_float(rectangle, sizeof(double) * 3); - return (Rectangle){x, y, width, height}; -} - -static inline Camera3D camera3D_of_arg(lean_obj_arg camera) { - Vector3 position = vector3_of_arg(lean_ctor_get(camera, 0)); - Vector3 target = vector3_of_arg(lean_ctor_get(camera, 1)); - Vector3 up = vector3_of_arg(lean_ctor_get(camera, 2)); - float fovy = lean_ctor_get_float(camera, sizeof(void *) * 3); - CameraProjection projection = - lean_ctor_get_uint8(camera, sizeof(void *) * 3 + sizeof(double)); - return (Camera3D){position, target, up, fovy, projection}; -} - -void camera3D_obj_init(lean_obj_arg camera_arg, Camera3D camera) { - lean_ctor_set(camera_arg, 0, vector3_obj_mk(camera.position)); - lean_ctor_set(camera_arg, 1, vector3_obj_mk(camera.target)); - lean_ctor_set(camera_arg, 2, vector3_obj_mk(camera.up)); - lean_ctor_set_float(camera_arg, sizeof(void *) * 3, camera.fovy); - lean_ctor_set_uint8(camera_arg, sizeof(void *) * 3 + sizeof(double), - camera.projection); -} - -void camera3D_obj_update(lean_obj_arg camera_arg, Camera3D camera) { - lean_dec_ref(lean_ctor_get(camera_arg, 0)); - lean_dec_ref(lean_ctor_get(camera_arg, 1)); - lean_dec_ref(lean_ctor_get(camera_arg, 2)); - camera3D_obj_init(camera_arg, camera); -} - -static inline lean_object *camera3D_obj_mk(Camera3D camera) { - lean_object *camera_obj = lean_alloc_ctor(0, 3, sizeof(double) + 1); - camera3D_obj_init(camera_obj, camera); - return camera_obj; -} - -static inline Camera2D camera2D_of_arg(lean_obj_arg camera) { - Vector2 offset = vector2_of_arg(lean_ctor_get(camera, 0)); - Vector2 target = vector2_of_arg(lean_ctor_get(camera, 1)); - double rotation = lean_ctor_get_float(camera, sizeof(void *) * 2); - double zoom = - lean_ctor_get_float(camera, sizeof(void *) * 2 + sizeof(double)); - return (Camera2D){offset, target, rotation, zoom}; -} - -lean_obj_res getRandomValue(uint32_t min, uint32_t max) - __attribute__((optnone)) { - // BUG: This always seems to return `min` - return lean_io_result_mk_ok(lean_box_uint32(GetRandomValue(min, max))); -} - -lean_obj_res initWindow(lean_obj_arg width, lean_obj_arg height, - b_lean_obj_arg title) { - InitWindow(lean_uint32_of_nat_mk(width), lean_uint32_of_nat_mk(height), - lean_string_cstr(title)); - return IO_UNIT; -} - -lean_obj_res windowShouldClose(void) { - return lean_io_result_mk_ok(lean_box(WindowShouldClose())); -} - -lean_obj_res closeWindow(void) { - CloseWindow(); - return IO_UNIT; -} - -lean_obj_res beginDrawing(void) { - BeginDrawing(); - return IO_UNIT; -} - -lean_obj_res endDrawing(void) { - EndDrawing(); - return IO_UNIT; -} - -lean_obj_res clearBackground(lean_obj_arg color) { - ClearBackground(color_of_arg(color)); - return IO_UNIT; -} - -lean_obj_res setTargetFPS(lean_obj_arg fps) { - SetTargetFPS(lean_uint32_of_nat_mk(fps)); - return IO_UNIT; -} - -lean_obj_res drawFPS(lean_obj_arg posX, lean_obj_arg posY) { - DrawFPS(lean_uint32_of_nat_mk(posX), lean_uint32_of_nat_mk(posY)); - return IO_UNIT; -} - -lean_obj_res drawText(b_lean_obj_arg text, lean_obj_arg posX, lean_obj_arg posY, - lean_obj_arg fontSize, lean_obj_arg color) { - DrawText(lean_string_cstr(text), lean_uint32_of_nat_mk(posX), - lean_uint32_of_nat_mk(posY), lean_uint32_of_nat_mk(fontSize), - color_of_arg(color)); - return IO_UNIT; -} - -lean_obj_res drawCircleV(lean_obj_arg center, double radius, - lean_obj_arg color) { - Vector2 centerV = vector2_of_arg(center); - Color colorArg = color_of_arg(color); - DrawCircleV(centerV, radius, colorArg); - return IO_UNIT; -} - -lean_obj_res isKeyDown(lean_obj_arg key) { - bool res = IsKeyDown(lean_uint32_of_nat_mk(key)); - return lean_io_result_mk_ok(lean_box(res)); -} - -lean_obj_res endMode2D(void) { - EndMode2D(); - return IO_UNIT; -} - -lean_obj_res beginMode2D(lean_obj_arg camera) { - BeginMode2D(camera2D_of_arg(camera)); - return IO_UNIT; -} - -lean_obj_res endMode3D(void) { - EndMode3D(); - return IO_UNIT; -} - -lean_obj_res beginMode3D(lean_obj_arg camera) { - BeginMode3D(camera3D_of_arg(camera)); - return IO_UNIT; -} - -lean_obj_res drawCube(lean_obj_arg position, double width, double height, - double length, lean_obj_arg color) { - DrawCube(vector3_of_arg(position), width, height, length, - color_of_arg(color)); - return IO_UNIT; -} - -lean_obj_res drawCubeWires(lean_obj_arg position, double width, double height, - double length, lean_obj_arg color) { - DrawCubeWires(vector3_of_arg(position), width, height, length, - color_of_arg(color)); - return IO_UNIT; -} - -lean_obj_res drawGrid(lean_obj_arg slices, double spacing) { - DrawGrid(lean_uint32_of_nat_mk(slices), spacing); - return IO_UNIT; -} - -lean_obj_res disableCursor(void) { - DisableCursor(); - return IO_UNIT; -} - -lean_obj_res updateCamera(lean_obj_arg camera_arg, uint8_t mode) { - Camera3D camera = camera3D_of_arg(camera_arg); - UpdateCamera(&camera, mode); - if (lean_is_exclusive(camera_arg)) { - camera3D_obj_update(camera_arg, camera); - return lean_io_result_mk_ok(camera_arg); - } else { - lean_dec_ref(camera_arg); - return lean_io_result_mk_ok(camera3D_obj_mk(camera)); - } -} - -lean_obj_res drawRectangleRec(lean_obj_arg rectangle_arg, - lean_obj_arg color_arg) { - DrawRectangleRec(rectangle_of_arg(rectangle_arg), color_of_arg(color_arg)); - return IO_UNIT; -} - -lean_obj_res getScreenToWorld2D(lean_obj_arg vector2_arg, - lean_obj_arg camera_arg) { - Vector2 worldV = GetScreenToWorld2D(vector2_of_arg(vector2_arg), - camera2D_of_arg(camera_arg)); - return vector2_obj_mk(worldV); -} - -lean_obj_res getFrameTime(void) { - return lean_io_result_mk_ok(lean_box_float(GetFrameTime())); -} - -lean_obj_res getMouseWheelMove(void) { - return lean_io_result_mk_ok(lean_box_float(GetMouseWheelMove())); -} - -lean_obj_res checkCollisionPointRec(lean_obj_arg vector2_arg, - lean_obj_arg rectangle_arg) { - Vector2 v = vector2_of_arg(vector2_arg); - Rectangle r = rectangle_of_arg(rectangle_arg); - return lean_io_result_mk_ok(lean_box(CheckCollisionPointRec(v, r))); -} - -lean_obj_res isMouseButtonPressed(uint8_t button) { - return lean_io_result_mk_ok(lean_box(IsMouseButtonPressed(button))); -} - -lean_obj_res getMousePosition(void) { - return lean_io_result_mk_ok(vector2_obj_mk(GetMousePosition())); -} - -lean_obj_res loadTextureFromImage(b_lean_obj_arg image_arg) { - Image *image = image_of_arg(image_arg); - Texture2D texture = LoadTextureFromImage(*image); - lean_obj_res texture2d_lean = texture2d_obj_mk(texture); - if (texture2d_lean == NULL) { - return string_io_error("loadTextureFromImage: texture2d_obj_mk failed"); - } - return lean_io_result_mk_ok(texture2d_lean); -} - -lean_obj_res drawTexture(b_lean_obj_arg texture_arg, lean_obj_arg posX_arg, - lean_obj_arg posY_arg, lean_obj_arg tint_arg) { - Texture2D *texture = texture2d_of_arg(texture_arg); - int posX = lean_uint32_of_nat_mk(posX_arg); - int posY = lean_uint32_of_nat_mk(posY_arg); - Color tint = color_of_arg(tint_arg); - DrawTexture(*texture, posX, posY, tint); - return IO_UNIT; -} - -lean_obj_res drawTexturePro(b_lean_obj_arg texture_arg, - lean_obj_arg source_rect_arg, - lean_obj_arg dest_rect_arg, lean_obj_arg origin_arg, - double rotation, lean_obj_arg tint_arg) { - Texture2D *texture = texture2d_of_arg(texture_arg); - Rectangle source = rectangle_of_arg(source_rect_arg); - Rectangle dest = rectangle_of_arg(dest_rect_arg); - Vector2 origin = vector2_of_arg(origin_arg); - Color tint = color_of_arg(tint_arg); - DrawTexturePro(*texture, source, dest, origin, rotation, tint); - return IO_UNIT; -} - -lean_obj_res drawLineV(lean_obj_arg startPos_arg, lean_obj_arg endPos_arg, lean_obj_arg color_arg) { - Vector2 startPos = vector2_of_arg(startPos_arg); - Vector2 endPos = vector2_of_arg(endPos_arg); - Color color = color_of_arg(color_arg); - DrawLineV(startPos, endPos, color); - return IO_UNIT; -} - -lean_obj_res drawLineStrip(b_lean_obj_arg points_arg, lean_obj_arg color_arg) { - size_t pointCount = lean_array_size(points_arg); - Vector2* points = malloc(pointCount * sizeof(Vector2)); - for (size_t i = 0; i < pointCount; i++) { - points[i] = vector2_of_arg(lean_array_get_core(points_arg, i)); - } - DrawLineStrip(points, pointCount, color_of_arg(color_arg)); - free(points); - return IO_UNIT; -} - -lean_obj_res drawPixelV(lean_obj_arg position_arg, lean_obj_arg color_arg) { - Vector2 position = vector2_of_arg(position_arg); - Color color = color_of_arg(color_arg); - DrawPixelV(position, color); - return IO_UNIT; -} diff --git a/justfile b/justfile index 068369d..908d9b1 100644 --- a/justfile +++ b/justfile @@ -1,22 +1,3 @@ -# set to non-empty string to disable use of resvg for SVG support. -# -# e.g: -# just disableResvg=yes build -disableResvg := '' - -# set to non-empty string to disable use of the resource bundle. -# -# e.g: -# just disableBundle=yes build -disableBundle := '' - -lake_bundle_config_opt := if disableBundle == "" { "-K bundle=on" } else { "" } - -lake_resvg_config_opt := if disableResvg == "" { "" } else { "-K resvg=disable" } - -# Flags used to configure the lake build -lake_config_opts := lake_bundle_config_opt + " " + lake_resvg_config_opt - # Raylib CUSTOM_FLAGS tailed for the current os # # The macos differ from the raylib release workflow. @@ -47,38 +28,11 @@ raylib_extra_make_variables := "" static_lib_path := join(justfile_directory(), "lib") raylib_src_path := join(justfile_directory(), "raylib-5.0", "src") -resource_dir := join(justfile_directory(), "resources") -bundle_h_path := join(justfile_directory(), "c", "include", "bundle.h") -makebundle_src_path := join(justfile_directory(), "scripts", "makeBundle.lean") -makebundle_output_path := join(justfile_directory(), "build", "makeBundle") -resvg_c_api_path := join(justfile_directory(), "resvg-0.43.0", "crates", "c-api") [private] default: @just --list -check_cargo: - #!/usr/bin/env bash - set -euo pipefail - if [ -z "{{disableResvg}}" ]; then - if ! command -v cargo &> /dev/null - then - echo "cargo was not found. Please install rust: https://rustup.rs" - exit 1 - fi - fi - -# build only the resvg static library -build_resvg: check_cargo - #!/usr/bin/env bash - if [ -z "{{disableResvg}}" ]; then - set -euo pipefail - cd {{resvg_c_api_path}} - cargo build --release - mkdir -p {{static_lib_path}} - cp {{resvg_c_api_path}}/../../target/release/libresvg.a {{static_lib_path}} - fi - # build only the raylib static library build_raylib: #!/usr/bin/env bash @@ -95,8 +49,8 @@ build_raylib: fi # build both the raylib library and the Lake project -build: build_resvg build_raylib bundler - lake -R {{lake_config_opts}} build +build: build_raylib + lake build # clean only the Lake project clean: @@ -110,29 +64,9 @@ clean_raylib: make -C {{raylib_src_path}} clean rm -rf {{static_lib_path}}/libraylib.a -clean_bundler: - rm -rf {{parent_directory(bundle_h_path)}} - rm -rf {{parent_directory(makebundle_output_path)}} - -clean_resvg: - #!/usr/bin/env bash - set -euo pipefail - cd {{resvg_c_api_path}} - cargo clean - # clean both the raylib build and the Lake project -clean_all: clean clean_raylib clean_bundler clean_resvg clean_static_lib +clean_all: clean clean_raylib clean_static_lib # run the demo executable -run *demoName: build - .lake/build/bin/raylean {{demoName}} - -build-bundler: - mkdir -p {{parent_directory(makebundle_output_path)}} - lean -c {{makebundle_output_path}}.c {{makebundle_src_path}} - leanc {{makebundle_output_path}}.c -o {{makebundle_output_path}} - -# run the bundler -bundler: build-bundler - mkdir -p {{parent_directory(bundle_h_path)}} - {{makebundle_output_path}} {{justfile_directory()}} {{resource_dir}} {{bundle_h_path}} +run: build + .lake/build/bin/imagedenotation \ No newline at end of file diff --git a/lake-manifest.json b/lake-manifest.json index 9f7fecc..9a549e2 100644 --- a/lake-manifest.json +++ b/lake-manifest.json @@ -1,7 +1,27 @@ {"version": "1.1.0", "packagesDir": ".lake/packages", "packages": - [{"url": "https://github.com/leanprover-community/batteries.git", + [{"url": "https://github.com/funexists/raylean.git", + "type": "git", + "subDir": null, + "scope": "", + "rev": "3a60f54222e06767daa93e50a8fcbc68975eee4c", + "name": "raylean", + "manifestFile": "lake-manifest.json", + "inputRev": "3a60f54222e06767daa93e50a8fcbc68975eee4c", + "inherited": false, + "configFile": "lakefile.lean"}, + {"url": "https://github.com/leanprover-community/mathlib4.git", + "type": "git", + "subDir": null, + "scope": "", + "rev": "4bbdccd9c5f862bf90ff12f0a9e2c8be032b9a84", + "name": "mathlib", + "manifestFile": "lake-manifest.json", + "inputRev": "v4.14.0", + "inherited": false, + "configFile": "lakefile.lean"}, + {"url": "https://github.com/leanprover-community/batteries.git", "type": "git", "subDir": null, "scope": "", @@ -11,42 +31,32 @@ "inputRev": "v4.14.0", "inherited": false, "configFile": "lakefile.toml"}, - {"url": "https://github.com/leanprover-community/quote4", + {"url": "https://github.com/funexists/lens-demo.git", "type": "git", "subDir": null, - "scope": "leanprover-community", - "rev": "303b23fbcea94ac4f96e590c1cad6618fd4f5f41", - "name": "Qq", + "scope": "", + "rev": "c0121c73d85ae3539add5b69b3345123ac4606e1", + "name": "«lens-demo»", "manifestFile": "lake-manifest.json", - "inputRev": "master", + "inputRev": "c0121c73d85ae3539add5b69b3345123ac4606e1", "inherited": true, "configFile": "lakefile.lean"}, - {"url": "https://github.com/leanprover-community/aesop", + {"url": "https://github.com/leanprover-community/plausible", "type": "git", "subDir": null, "scope": "leanprover-community", - "rev": "5a0ec8588855265ade536f35bcdcf0fb24fd6030", - "name": "aesop", + "rev": "42dc02bdbc5d0c2f395718462a76c3d87318f7fa", + "name": "plausible", "manifestFile": "lake-manifest.json", - "inputRev": "v4.14.0", + "inputRev": "main", "inherited": true, "configFile": "lakefile.toml"}, - {"url": "https://github.com/leanprover-community/ProofWidgets4", + {"url": "https://github.com/leanprover-community/LeanSearchClient", "type": "git", "subDir": null, "scope": "leanprover-community", - "rev": "68280daef58803f68368eb2e53046dabcd270c9d", - "name": "proofwidgets", - "manifestFile": "lake-manifest.json", - "inputRev": "v0.0.47", - "inherited": true, - "configFile": "lakefile.lean"}, - {"url": "https://github.com/leanprover/lean4-cli", - "type": "git", - "subDir": null, - "scope": "leanprover", - "rev": "726b3c9ad13acca724d4651f14afc4804a7b0e4d", - "name": "Cli", + "rev": "d7caecce0d0f003fd5e9cce9a61f1dd6ba83142b", + "name": "LeanSearchClient", "manifestFile": "lake-manifest.json", "inputRev": "main", "inherited": true, @@ -61,35 +71,45 @@ "inputRev": "v4.14.0", "inherited": true, "configFile": "lakefile.toml"}, - {"url": "https://github.com/leanprover-community/LeanSearchClient", + {"url": "https://github.com/leanprover-community/ProofWidgets4", "type": "git", "subDir": null, "scope": "leanprover-community", - "rev": "d7caecce0d0f003fd5e9cce9a61f1dd6ba83142b", - "name": "LeanSearchClient", + "rev": "68280daef58803f68368eb2e53046dabcd270c9d", + "name": "proofwidgets", "manifestFile": "lake-manifest.json", - "inputRev": "main", + "inputRev": "v0.0.47", "inherited": true, - "configFile": "lakefile.toml"}, - {"url": "https://github.com/leanprover-community/plausible", + "configFile": "lakefile.lean"}, + {"url": "https://github.com/leanprover-community/aesop", "type": "git", "subDir": null, "scope": "leanprover-community", - "rev": "42dc02bdbc5d0c2f395718462a76c3d87318f7fa", - "name": "plausible", + "rev": "5a0ec8588855265ade536f35bcdcf0fb24fd6030", + "name": "aesop", "manifestFile": "lake-manifest.json", - "inputRev": "main", + "inputRev": "v4.14.0", "inherited": true, "configFile": "lakefile.toml"}, - {"url": "https://github.com/leanprover-community/mathlib4.git", + {"url": "https://github.com/leanprover-community/quote4", "type": "git", "subDir": null, - "scope": "", - "rev": "4bbdccd9c5f862bf90ff12f0a9e2c8be032b9a84", - "name": "mathlib", + "scope": "leanprover-community", + "rev": "303b23fbcea94ac4f96e590c1cad6618fd4f5f41", + "name": "Qq", "manifestFile": "lake-manifest.json", - "inputRev": "v4.14.0", - "inherited": false, - "configFile": "lakefile.lean"}], - "name": "raylean", + "inputRev": "master", + "inherited": true, + "configFile": "lakefile.lean"}, + {"url": "https://github.com/leanprover/lean4-cli", + "type": "git", + "subDir": null, + "scope": "leanprover", + "rev": "726b3c9ad13acca724d4651f14afc4804a7b0e4d", + "name": "Cli", + "manifestFile": "lake-manifest.json", + "inputRev": "main", + "inherited": true, + "configFile": "lakefile.toml"}], + "name": "imagedenotation", "lakeDir": ".lake"} diff --git a/lakefile.lean b/lakefile.lean index fa1a7bd..e47e0d0 100644 --- a/lakefile.lean +++ b/lakefile.lean @@ -1,46 +1,24 @@ import Lake -open System Lake DSL - -def optionUseBundle : Bool := get_config? bundle == some "on" - -def optionDisableResvg : Bool := get_config? resvg == some "disable" +open Lake DSL require batteries from git "https://github.com/leanprover-community/batteries.git" @ "v4.14.0" require mathlib from git "https://github.com/leanprover-community/mathlib4.git" @ "v4.14.0" -package «raylean» where - srcDir := "lean" - -lean_lib «Raylean» where - precompileModules := true - -lean_lib «Examples» where - precompileModules := true +require raylean from git "https://github.com/funexists/raylean.git" @ "3a60f54222e06767daa93e50a8fcbc68975eee4c" with + NameMap.empty + |>.insert `bundle "off" + |>.insert `resvg "disable" -lean_lib «ECS» +package "imagedenotation" where + srcDir := "src" -lean_lib «Lens» +lean_lib «ImageDenotation» where @[default_target] -lean_exe «raylean» where +lean_exe "imagedenotation" where root := `Main moreLinkArgs := Id.run do let mut args := #[ "lib/libraylib.a"] - if not optionDisableResvg then - args := args ++ #["lib/libresvg.a"] - if (← System.Platform.isOSX) then - args := args ++ - #[ "-isysroot", "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk" - , "-framework", "IOKit" - , "-framework", "Cocoa" - , "-framework", "OpenGL" - ] - args - -lean_exe «ecs-example» where - root := `Examples.ECS - moreLinkArgs := Id.run do - let mut args := #[ "lib/libraylib.a" , "lib/libresvg.a"] if (← System.Platform.isOSX) then args := args ++ #[ "-isysroot", "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk" @@ -49,22 +27,3 @@ lean_exe «ecs-example» where , "-framework", "OpenGL" ] args - -target raylib_bindings.o pkg : FilePath := do - let oFile := pkg.buildDir / "c" / "raylib_bindings.o" - let includes := pkg.dir / "c" / "include" - let srcJob ← inputTextFile <| pkg.dir / "c" / "raylib_bindings.c" - let raylibInclude := pkg.dir / "raylib-5.0" / "src" - let resvgInclude := pkg.dir / "resvg-0.43.0" / "crates" / "c-api" - let weakArgs := #["-I", s!"{raylibInclude}", "-I", s!"{includes}", "-I", s!"{resvgInclude}"] - let mut traceArgs := #["-fPIC"] - if optionDisableResvg then - traceArgs := traceArgs ++ #["-DRAYLEAN_NO_RESVG"] - if not (optionUseBundle) then - traceArgs := traceArgs ++ #["-DRAYLEAN_NO_BUNDLE"] - buildLeanO oFile srcJob weakArgs traceArgs - -extern_lib libleanffi pkg := do - let ffiO ← raylib_bindings.o.fetch - let name := nameToStaticLib "rayliblean" - buildStaticLib (pkg.nativeLibDir / name) #[ffiO] diff --git a/lean/ECS.lean b/lean/ECS.lean deleted file mode 100644 index 0d08843..0000000 --- a/lean/ECS.lean +++ /dev/null @@ -1,6 +0,0 @@ -import ECS.Basic -import ECS.Store -import ECS.EntityCounter -import ECS.System -import ECS.Elab -import ECS.Components diff --git a/lean/ECS/Basic.lean b/lean/ECS/Basic.lean deleted file mode 100644 index 8c77061..0000000 --- a/lean/ECS/Basic.lean +++ /dev/null @@ -1,36 +0,0 @@ -import ECS.Family - -namespace ECS - -structure Entity where - id : Nat - deriving BEq, Hashable, Repr - -abbrev System (w a : Type) := ReaderT w IO a - -opaque StorageFam (c : Type) : Type - -opaque ElemFam (s : Type) : Type - -class Component (c : Type) {s t : outParam Type} [FamilyDef StorageFam c s] [FamilyDef ElemFam s t] where - constraint : t = c - -class Has (w : Type) (c : Type) {s : outParam Type} [FamilyDef StorageFam c s] where - getStore : System w s - -class ExplInit (s : Type) where - explInit : IO s - -class ExplGet (s : Type) {t : outParam Type} [FamilyDef ElemFam s t] where - explGet : s → Entity → IO t - explExists : s → Entity → IO Bool - -class ExplSet (s : Type) {t : outParam Type} [FamilyDef ElemFam s t] where - explSet : s → Entity → t → IO Unit - -class ExplDestroy (s : Type) where - explDestroy : s → Entity → IO Unit - -class ExplMembers (s : Type) where - explMembers : s → IO (Array Entity) - diff --git a/lean/ECS/Components.lean b/lean/ECS/Components.lean deleted file mode 100644 index 0d70b42..0000000 --- a/lean/ECS/Components.lean +++ /dev/null @@ -1,334 +0,0 @@ -import ECS.Family -import ECS.Basic - -namespace ECS - -axiom ProdStorage - {α β sa sb : Type} - [FamilyDef StorageFam α sa] - [FamilyDef StorageFam β sb] - : StorageFam (α × β) = (sa × sb) -instance - [FamilyDef StorageFam α sa] - [FamilyDef StorageFam β sb] - : (FamilyDef StorageFam (α × β)) (sa × sb) := ⟨ProdStorage⟩ - -axiom ProdElem - {α β ea eb : Type} - [FamilyDef ElemFam α ea] - [FamilyDef ElemFam β eb] - : ElemFam (α × β) = (ea × eb) -instance - [FamilyDef ElemFam α ea] - [FamilyDef ElemFam β eb] - : (FamilyDef ElemFam (α × β)) (ea × eb) := ⟨ProdElem⟩ - -instance - [FamilyDef StorageFam α sa] - [FamilyDef StorageFam β sb] - [FamilyDef ElemFam sa ea] - [FamilyDef ElemFam sb eb] - [ca : @Component α sa ea _ _] - [cb : @Component β sb eb _ _] - : @Component (α × β) (sa × sb) (ea × eb) _ _ where - constraint := by - rw [ca.constraint] - rw [cb.constraint] - -instance - [FamilyDef ElemFam α ea] - [FamilyDef ElemFam β eb] - [@ExplGet α ea _] - [@ExplGet β eb _] - : @ExplGet (α × β) (ea × eb) _ where - explExists s ety := do - let (sa, sb) := s - pure ((← ExplGet.explExists sa ety) && (← ExplGet.explExists sb ety)) - - explGet s ety := do - let (sa, sb) := s - pure ((← ExplGet.explGet sa ety), (← ExplGet.explGet sb ety)) - -instance - [FamilyDef ElemFam α ea] - [FamilyDef ElemFam β eb] - [@ExplSet α ea _] - [@ExplSet β eb _] - : @ExplSet (α × β) (ea × eb) _ where - explSet s ety a := do - let (sa, sb) := s - let (aa, ab) := a - ExplSet.explSet sa ety aa - ExplSet.explSet sb ety ab - -instance [ExplDestroy α] [ExplDestroy β] : ExplDestroy (α × β) where - explDestroy s ety := do - let (sa, sb) := s - ExplDestroy.explDestroy sa ety - ExplDestroy.explDestroy sb ety - -instance [FamilyDef ElemFam β eb] [ExplMembers α] [@ExplGet β eb _] : ExplMembers (α × β) where - explMembers s := do - let (sa, sb) := s - let as ← ExplMembers.explMembers sa - as.filterM (ExplGet.explExists sb) - -instance - [FamilyDef StorageFam α sa] - [FamilyDef StorageFam β sb] - [@Has w α sa _] - [@Has w β sb _] - : @Has w (α × β) (sa × sb) _ where - getStore := do - let sta : sa ← Has.getStore α - let stb : sb ← Has.getStore β - pure (sta, stb) - - -/-- -A pseudocomponent indicating the absence of α - -Can be used as: - - cmap (fun (a, Not b) => c) - -to iterate over entities with an `a` but no `b` - -It can also be used as: - - cmap (fun a => Not : Not a) - -to delete every `a` component. ---/ -inductive Not (α : Type) where - | Not - - -/-- -A pseudostore used to produce values of `Not a`. It inverts `explExists` and destroys instead of `explSet` ---/ -structure NotStore (α : Type) where - val : α - -axiom ElemNotStore {s es : Type} [FamilyDef ElemFam s es] : ElemFam (NotStore s) = Not es -instance [FamilyDef ElemFam s es] : FamilyDef ElemFam (NotStore s) (Not es) := ⟨ElemNotStore⟩ - -axiom StorageNot {c s : Type} [FamilyDef StorageFam c s] : StorageFam (Not c) = NotStore s -instance [FamilyDef StorageFam c s] : FamilyDef StorageFam (Not c) (NotStore s) := ⟨StorageNot⟩ - -instance - [FamilyDef StorageFam c s] - [FamilyDef ElemFam s es] - [ca : @Component c s es _ _] - : @Component (Not c) (NotStore s) (Not es) _ _ where - constraint := congrArg Not ca.constraint - -instance - [FamilyDef StorageFam c s] - [@Has w c s _] - : @Has w (Not c) (NotStore s) _ where - getStore := NotStore.mk <$> Has.getStore c - -instance - [FamilyDef ElemFam s e] - [@ExplGet s e _] - : @ExplGet (NotStore s) (Not e) _ where - explGet _ _ := pure .Not - explExists sa ety := do - let (NotStore.mk st) := sa - not <$> ExplGet.explExists st ety - -instance - [FamilyDef ElemFam s e] - [ExplDestroy s] : @ExplSet (NotStore s) (Not e) _ where - explSet sa ety _ := do - let (NotStore.mk st) := sa - ExplDestroy.explDestroy st ety - -/-- A pseudostore used to produce values of type `Option a`. It will always return `true` for `explExists`. -Writing can both set and delete a component using `some` and `none` respectively. ---/ -structure OptionStore (α : Type) where - val : α - -axiom ElemOptionStore {s es : Type} [FamilyDef ElemFam s es] : ElemFam (OptionStore s) = Option es -instance [FamilyDef ElemFam s es] : FamilyDef ElemFam (OptionStore s) (Option es) := ⟨ElemOptionStore⟩ - -axiom StorageOption {c s : Type} [FamilyDef StorageFam c s] : StorageFam (Option c) = OptionStore s -instance [FamilyDef StorageFam c s] : FamilyDef StorageFam (Option c) (OptionStore s) := ⟨StorageOption⟩ - -instance - [FamilyDef StorageFam c s] - [FamilyDef ElemFam s es] - [ca : @Component c s es _ _] - : @Component (Option c) (OptionStore s) (Option es) _ _ where - constraint := congrArg Option ca.constraint - -instance - [FamilyDef StorageFam c s] - [@Has w c s _] - : @Has w (Option c) (OptionStore s) _ where - getStore := OptionStore.mk <$> Has.getStore c - -instance - [FamilyDef ElemFam s e] - [@ExplGet s e _] - : @ExplGet (OptionStore s) (Option e) _ where - explGet sa ety := do - let (OptionStore.mk st) := sa - if (← ExplGet.explExists st ety) - then some <$> ExplGet.explGet st ety - else return none - - explExists _ _ := return true - -instance - [FamilyDef ElemFam s e] - [ExplDestroy s] - [@ExplSet s e _] - : @ExplSet (OptionStore s) (Option e) _ where - explSet sa ety mv := do - let (OptionStore.mk st) := sa - match mv with - | none => ExplDestroy.explDestroy st ety - | some x => ExplSet.explSet st ety x - -/-- An `Sum` component, a logical disjunction between two components. --- Getting an `a ⊕ b` will first attempt to get a `b` and return it as `inr b`, or if it does not exist, get an `a` as `inl a`. --- Can also be used to set one of two things. ---/ -structure SumStore (sa sb : Type) where - sta : sa - stb : sb - -axiom ElemSumStore - {sa sb ea eb : Type} - [FamilyDef ElemFam sa ea] - [FamilyDef ElemFam sb eb] - : ElemFam (SumStore sa sb) = (ea ⊕ eb) -instance - {sa sb ea eb : Type} - [FamilyDef ElemFam sa ea] - [FamilyDef ElemFam sb eb] - : FamilyDef ElemFam (SumStore sa sb) (ea ⊕ eb) := ⟨ElemSumStore⟩ - -axiom StorageSum - {ca cb sa sb : Type} - [FamilyDef StorageFam ca sa] - [FamilyDef StorageFam cb sb] - : StorageFam (ca ⊕ cb) = SumStore sa sb -instance - {ca cb sa sb : Type} - [FamilyDef StorageFam ca sa] - [FamilyDef StorageFam cb sb] - : FamilyDef StorageFam (ca ⊕ cb) (SumStore sa sb) := ⟨StorageSum⟩ - -instance - [FamilyDef StorageFam ca sa] - [FamilyDef StorageFam cb sb] - [FamilyDef ElemFam sa ea] - [FamilyDef ElemFam sb eb] - [compA : @Component ca sa ea _ _] - [compB : @Component cb sb eb _ _] - : @Component (ca ⊕ cb) (SumStore sa sb) (ea ⊕ eb) _ _ where - constraint := by - rw [compA.constraint] - rw [compB.constraint] - -instance - [FamilyDef StorageFam ca sa] - [FamilyDef StorageFam cb sb] - [@Has w ca sa _] - [@Has w cb sb _] - : @Has w (ca ⊕ cb) (SumStore sa sb) _ where - getStore := do - let sta ← Has.getStore ca - let stb ← Has.getStore cb - return (SumStore.mk sta stb) - -instance - [FamilyDef ElemFam sa ea] - [FamilyDef ElemFam sb eb] - [@ExplGet sa ea _] - [@ExplGet sb eb _] - : @ExplGet (SumStore sa sb) (ea ⊕ eb) _ where - explGet s ety := do - let (SumStore.mk sta stb) := s - if (← ExplGet.explExists stb ety) - then .inr <$> ExplGet.explGet stb ety - else .inl <$> ExplGet.explGet sta ety - - explExists s ety := do - let (SumStore.mk sa sb) := s - if (← ExplGet.explExists sb ety) - then return true - else ExplGet.explExists sa ety - -instance - [FamilyDef ElemFam sa ea] - [FamilyDef ElemFam sb eb] - [@ExplSet sa ea _] - [@ExplSet sb eb _] - : @ExplSet (SumStore sa sb) (ea ⊕ eb) _ where - explSet s ety v := do - let (SumStore.mk sta stb) := s - match v with - | .inr b => ExplSet.explSet stb ety b - | .inl a => ExplSet.explSet sta ety a - -instance - [ExplDestroy sa] - [ExplDestroy sb] - : ExplDestroy (SumStore sa sb) where - explDestroy s ety := do - let (SumStore.mk sta stb) := s - ExplDestroy.explDestroy sta ety - ExplDestroy.explDestroy stb ety - -/-- Instances for Unit. -Useful when you want to 'do nothing' in a cmap ---/ -axiom ElemUnitStore : ElemFam Unit = Unit -instance : FamilyDef ElemFam Unit Unit := ⟨ElemUnitStore⟩ - -axiom StorageUnit : StorageFam Unit = Unit -instance : FamilyDef StorageFam Unit Unit := ⟨StorageUnit⟩ - -instance : @Component Unit Unit Unit _ _ where - constraint := rfl - -instance : @Has w Unit Unit _ where - getStore := return () - -instance : @ExplGet Unit Unit _ where - explGet _ _ := return () - explExists _ _ := return true - -instance : @ExplSet Unit Unit _ where - explSet _ _ _ := return () - -instance : ExplDestroy Unit where - explDestroy _ _ := return () - -/-- A pseudostore used to produce components of type `Entity`. -It always returns `true` for `explExists`, and echoes back the entity argument for `explGet`. -It can be used in e.g. `cmap $ fun (a, ety : Entity) -> b` to access the current entity. ---/ -inductive EntityStore where - | EntityStore - -axiom ElemEntityStore : ElemFam EntityStore = Entity -instance : FamilyDef ElemFam EntityStore Entity := ⟨ElemEntityStore⟩ - -axiom StorageEntity : StorageFam Entity = EntityStore -instance : FamilyDef StorageFam Entity EntityStore := ⟨StorageEntity⟩ - -instance : @Component Entity EntityStore Entity _ _ where - constraint := rfl - -instance : @Has w Entity EntityStore _ where - getStore := return .EntityStore - -instance : @ExplGet EntityStore Entity _ where - explGet _ ety := return ety - explExists _ _ := return true diff --git a/lean/ECS/Elab.lean b/lean/ECS/Elab.lean deleted file mode 100644 index 83e7164..0000000 --- a/lean/ECS/Elab.lean +++ /dev/null @@ -1,159 +0,0 @@ -import ECS.Basic -import ECS.Store -import ECS.EntityCounter - -import Lean - -namespace ECS - -open Lean Elab Command Term Meta - -def toSnd (f : α → β) (a : α) : α × β := (a , f a) - -def mapToSnd [Functor f] (g : α → β) : f α → f (α × β) := Functor.map (toSnd g) - -def mapMToSnd [Monad m] (g : α → m β) (fa : Array α) : m (Array (α × β)) := do - let mg (a : α) : m (α × β) := do - let mb ← g a - return (a , mb) - fa.mapM mg - -def const (a : α) : β → α := fun _ => a - --- TODO: There should be a way to do this with mkFreshUserName -def mkFreshName (basename : Name) := liftTermElabM <| do - let freshId ← mkFreshId - pure <| basename.appendAfter (toString freshId) - -def mkFreshIdent (basename : Name) := mkIdent <$> mkFreshName basename - -elab "makeMapComponent" elemIdent:ident : command => do - let elemName ← Lean.resolveGlobalConstNoOverload elemIdent - let axiomName : Ident ← liftCoreM (mkIdent <$> (mkFreshUserName (Name.mkSimple s!"Storage{elemIdent.getId}"))) - elabCommand (← `(axiom $axiomName : StorageFam $(mkIdent elemName) = MapStorage $(mkIdent elemName))) - elabCommand (← `(instance : FamilyDef StorageFam $(mkIdent elemName) (MapStorage $(mkIdent elemName)) := ⟨$axiomName⟩)) - elabCommand (← `( - instance : @Component $(mkIdent elemName) (MapStorage $(mkIdent elemName)) $(mkIdent elemName) _ _ where - constraint := rfl - )) - -elab "makeGlobalComponent" elemIdent:ident : command => do - let elemName ← Lean.resolveGlobalConstNoOverload elemIdent - let axiomName : Ident ← liftCoreM (mkIdent <$> (mkFreshUserName (Name.mkSimple s!"Storage{elemIdent.getId}"))) - elabCommand (← `(axiom $axiomName : StorageFam $(mkIdent elemName) = GlobalStorage $(mkIdent elemName))) - elabCommand (← `(instance : FamilyDef StorageFam $(mkIdent elemName) (GlobalStorage $(mkIdent elemName)) := ⟨$axiomName⟩)) - elabCommand (← `( - instance : @Component $(mkIdent elemName) (GlobalStorage $(mkIdent elemName)) $(mkIdent elemName) _ _ where - constraint := rfl - )) - -elab "makeUniqueComponent" elemIdent:ident : command => do - let elemName ← Lean.resolveGlobalConstNoOverload elemIdent - let axiomName : Ident ← liftCoreM (mkIdent <$> (mkFreshUserName (Name.mkSimple s!"Storage{elemIdent.getId}"))) - elabCommand (← `(axiom $axiomName : StorageFam $(mkIdent elemName) = UniqueStorage $(mkIdent elemName))) - elabCommand (← `(instance : FamilyDef StorageFam $(mkIdent elemName) (UniqueStorage $(mkIdent elemName)) := ⟨$axiomName⟩)) - elabCommand (← `( - instance : @Component $(mkIdent elemName) (UniqueStorage $(mkIdent elemName)) $(mkIdent elemName) _ _ where - constraint := rfl - )) - -elab "makeMapComponents" elemIdents:ident* : command => do - for t in (← elemIdents.mapM Lean.resolveGlobalConstNoOverload) do - elabCommand (← `(makeMapComponent $(mkIdent t))) - -elab "makeWorldAndComponents" "[" mapIdents:ident,* "]" "[" globalIdents:ident,* "]" "[" uniqueIdents:ident,* "]": command => do - -- identifiers exposed to the caller - let worldStructName := "World" - let world := mkIdent <| Name.mkSimple worldStructName - let initWorld := mkIdent <| Name.mkSimple "initWorld" - let worldmk := mkIdent <| Name.mkStr2 worldStructName "mk" - - -- resolve the type names of passed idenifiers - let mapNames := (← mapIdents.elemsAndSeps.getSepElems.mapM Lean.resolveGlobalConstNoOverload) - let globalNames := (← globalIdents.elemsAndSeps.getSepElems.mapM Lean.resolveGlobalConstNoOverload) - let uniqueNames := (← uniqueIdents.elemsAndSeps.getSepElems.mapM Lean.resolveGlobalConstNoOverload) - - -- register all components - for t in mapNames do - elabCommand (← `(makeMapComponent $(mkIdent t))) - - for t in globalNames do - elabCommand (← `(makeGlobalComponent $(mkIdent t))) - - for t in uniqueNames do - elabCommand (← `(makeUniqueComponent $(mkIdent t))) - - -- fresh names for world fields - let worldMapNames ← mapMToSnd (const <| mkFreshIdent `world) mapNames - let worldGlobalNames ← mapMToSnd (const <| mkFreshIdent `world) globalNames - let worldUniqueNames ← mapMToSnd (const <| mkFreshIdent `world) uniqueNames - let worldEntity ← mkFreshIdent `world - - let mut fields ← worldMapNames.mapM <| fun (n, w) => do - `(Lean.Parser.Command.structExplicitBinder| ( $w : MapStorage $(mkIdent n) )) - fields := fields.append (← worldGlobalNames.mapM <| fun (n, w) => do - `(Lean.Parser.Command.structExplicitBinder| ( $w : GlobalStorage $(mkIdent n) ))) - fields := fields.append (← worldUniqueNames.mapM <| fun (n, w) => do - `(Lean.Parser.Command.structExplicitBinder| ( $w : UniqueStorage $(mkIdent n) ))) - fields := fields.push (← `(Lean.Parser.Command.structExplicitBinder| ( $worldEntity : GlobalStorage EntityCounter ))) - - -- define thw World structure - elabCommand (← `( - structure $world where - $fields:structExplicitBinder* - )) - - -- register a Has instance for each component - for (t, w) in worldMapNames do - elabCommand (← `( - instance : @Has $world $(mkIdent t) (MapStorage $(mkIdent t)) _ where - getStore := (·.$w) <$> read - )) - for (t, w) in worldGlobalNames do - elabCommand (← `( - instance : @Has $world $(mkIdent t) (GlobalStorage $(mkIdent t)) _ where - getStore := (·.$w) <$> read - )) - for (t, w) in worldUniqueNames do - elabCommand (← `( - instance : @Has $world $(mkIdent t) (UniqueStorage $(mkIdent t)) _ where - getStore := (·.$w) <$> read - )) - elabCommand (← `( - instance : @Has $world EntityCounter (GlobalStorage EntityCounter) _ where - getStore := (·.$worldEntity) <$> read - )) - - -- fresh names for World.mk constructure arguments - let mapStoreNames ← mapMToSnd (const <| mkFreshIdent `store) mapNames - let globalStoreNames ← mapMToSnd (const <| mkFreshIdent `store) globalNames - let uniqueStoreNames ← mapMToSnd (const <| mkFreshIdent `store) uniqueNames - let storeEntity ← mkFreshIdent `store - - let mut storageInitLets := #[] - for (t, s) in mapStoreNames do - storageInitLets := storageInitLets.push - (← `(Lean.Parser.Term.doSeqItem| let $s : MapStorage $(mkIdent t) ← ExplInit.explInit)) - for (t, s) in globalStoreNames do - storageInitLets := storageInitLets.push - (← `(Lean.Parser.Term.doSeqItem| let $s : GlobalStorage $(mkIdent t) ← ExplInit.explInit)) - for (t, s) in uniqueStoreNames do - storageInitLets := storageInitLets.push - (← `(Lean.Parser.Term.doSeqItem| let $s : UniqueStorage $(mkIdent t) ← ExplInit.explInit)) - storageInitLets := storageInitLets.push - (← `(Lean.Parser.Term.doSeqItem| let $storeEntity : GlobalStorage EntityCounter ← ExplInit.explInit)) - - let mut worldmkArgs := #[] - for (_, s) in mapStoreNames ++ globalStoreNames ++ uniqueStoreNames do - worldmkArgs := worldmkArgs.push s - worldmkArgs := worldmkArgs.push storeEntity - - -- define initWorld - elabCommand (← `( - def $initWorld : IO $world := do - $storageInitLets:doSeqItem* - return ($worldmk $worldmkArgs*) -)) - -elab "makeWorldAndMapComponents" "[" elemIdents:ident,* "]": command => do - elabCommand (← `(makeWorldAndComponents [$elemIdents:ident,*] [] [])) diff --git a/lean/ECS/EntityCounter.lean b/lean/ECS/EntityCounter.lean deleted file mode 100644 index cee3605..0000000 --- a/lean/ECS/EntityCounter.lean +++ /dev/null @@ -1,57 +0,0 @@ -import ECS.Basic -import ECS.Store -import ECS.System - -namespace ECS - -structure EntityCounter where - getCounter : Nat - deriving Inhabited - -axiom StorageEntityCounter : StorageFam EntityCounter = GlobalStorage EntityCounter -instance : FamilyDef StorageFam EntityCounter (GlobalStorage EntityCounter) := ⟨StorageEntityCounter⟩ - -instance : @Component EntityCounter (GlobalStorage EntityCounter) EntityCounter _ _ where - constraint := rfl - -def nextEntity - [@Has w EntityCounter (GlobalStorage EntityCounter) _] - : System w Entity := do - let g : EntityCounter ← getGlobal - setGlobal (EntityCounter.mk (g.getCounter + 1)) - return ⟨g.getCounter⟩ - -def newEntity - [FamilyDef StorageFam c s] - [FamilyDef ElemFam s t] - [@Component c s t _ _] - [@Has w EntityCounter (GlobalStorage EntityCounter) _] - [@Has w c s _] - [@ExplSet s t _] - (x : c) : System w Entity := do - let ety ← nextEntity - set' ety x - pure ety - -def newEntity_ - [FamilyDef StorageFam c s] - [FamilyDef ElemFam s t] - [@Component c s t _ _] - [@Has w EntityCounter (GlobalStorage EntityCounter) _] - [@Has w c s _] - [@ExplSet s t _] - (x : c) : System w Unit := do - let ety ← nextEntity - set' ety x - -def newEntityAs_ - (c : Type) - [FamilyDef StorageFam c s] - [FamilyDef ElemFam s t] - [@Component c s t _ _] - [@Has w EntityCounter (GlobalStorage EntityCounter) _] - [@Has w c s _] - [@ExplSet s t _] - (x : c) : System w Unit := do - let ety ← nextEntity - set' ety x diff --git a/lean/ECS/Family.lean b/lean/ECS/Family.lean deleted file mode 100644 index fb1d801..0000000 --- a/lean/ECS/Family.lean +++ /dev/null @@ -1,4 +0,0 @@ -namespace ECS - -class FamilyDef {α : Type u} (Fam : α → Type v) (a : α) (β : semiOutParam $ Type v) : Prop where - family_key_eq_type : Fam a = β diff --git a/lean/ECS/Store.lean b/lean/ECS/Store.lean deleted file mode 100644 index 74b30d7..0000000 --- a/lean/ECS/Store.lean +++ /dev/null @@ -1,119 +0,0 @@ -import Std - -import ECS.Basic -import ECS.Family - -namespace ECS - -structure MapStorage (c : Type) where - ref : IO.Ref (Std.HashMap Entity c) - -namespace MapStorage - -def init : IO (MapStorage c) := MapStorage.mk <$> IO.mkRef (Std.HashMap.empty) - -def get (s : MapStorage c) (e : Entity) : IO c := do - let st <- s.ref.get - match Std.HashMap.get? st e with - | (some x) => pure x - | none => panic! "Reading non-existent component" - -def exists? (s : MapStorage c) (e : Entity) : IO Bool := - (Std.HashMap.contains · e) <$> s.ref.get - -def set (s : MapStorage c) (e : Entity) (x : c) : IO Unit := do - let m ← s.ref.get - Std.HashMap.insert m e x |> s.ref.set - -def destroy (s : MapStorage c) (e : Entity) : IO Unit := do - let m ← s.ref.get - Std.HashMap.erase m e |> s.ref.set - -def members (s : MapStorage c) : IO (Array Entity) := - Std.HashMap.keysArray <$> s.ref.get - -axiom ElemMapStorage : {c : Type} → ElemFam (MapStorage c) = c -instance : FamilyDef ElemFam (MapStorage c) c := ⟨ElemMapStorage⟩ - -instance : ExplInit (MapStorage c) where - explInit := init - -instance : @ExplGet (MapStorage c) c _ where - explGet := get - explExists := exists? - -instance : @ExplSet (MapStorage c) c _ where - explSet := set - -instance : ExplDestroy (MapStorage c) where - explDestroy := destroy - -instance : ExplMembers (MapStorage c) where - explMembers := members - -class StorageMapping - -end MapStorage - -structure GlobalStorage (α : Type) where - ref : IO.Ref α - -namespace GlobalStorage - -axiom ElemGlobalStorage : {c : Type} → ElemFam (GlobalStorage c) = c -instance : FamilyDef ElemFam (GlobalStorage c) c := ⟨ElemGlobalStorage⟩ - -instance [Inhabited α] : ExplInit (GlobalStorage α) where - explInit := GlobalStorage.mk <$> IO.mkRef default - -instance explGetGlobal : @ExplGet (GlobalStorage α) α _ where - explGet s _ := s.ref.get - explExists _ _ := pure true - -instance : @ExplSet (GlobalStorage α) α _ where - explSet s _ a := s.ref.set a - -end GlobalStorage - -structure UniqueStorage (α : Type) where - ref : IO.Ref (Option (Entity × α)) - -axiom ElemUniqueStorage : {c : Type} → ElemFam (UniqueStorage c) = c -instance : FamilyDef ElemFam (UniqueStorage c) c := ⟨ElemUniqueStorage⟩ - -namespace UniqueStorage - -instance : ExplInit (UniqueStorage α) where - explInit := UniqueStorage.mk <$> IO.mkRef none - -instance : @ExplGet (UniqueStorage α) α _ where - explGet s _ := do - let u ← s.ref.get - match u with - | (some (_, c)) => pure c - | none => panic! "Reading non-existent unique component" - - explExists s e := do - let u ← s.ref.get - pure <| match u with - | (some (ety, _)) => e == ety - | none => false - -instance : @ExplSet (UniqueStorage α) α _ where - explSet s ety c := s.ref.set (some (ety, c)) - -instance : ExplDestroy (UniqueStorage α) where - explDestroy s e := do - let u ← s.ref.get - match u with - | (some (ety, _)) => if (ety == e) then s.ref.set none else pure Unit.unit - | none => pure Unit.unit - -instance : ExplMembers (UniqueStorage α) where - explMembers s := do - let u ← s.ref.get - pure <| match u with - | none => #[] - | (some (ety, _)) => #[ety] - -end UniqueStorage diff --git a/lean/ECS/System.lean b/lean/ECS/System.lean deleted file mode 100644 index 721a382..0000000 --- a/lean/ECS/System.lean +++ /dev/null @@ -1,187 +0,0 @@ -import ECS.Basic -import ECS.Store - -namespace ECS - -/-- Run a system in a game world --/ -def runSystem (s : System w α) (world : w) : IO α := s.run world - -/-- The entity used in `getGlobal` and `setGlobal`.-/ -def global : Entity := ⟨0⟩ - -/-- Read a component --/ -def get - {w c s t : Type} - [FamilyDef StorageFam c s] - [FamilyDef ElemFam s t] - [comp : @Component c s t _ _] - [@Has w c s _] - [e : @ExplGet s t _] - (ety : Entity) : System w c := do - let s ← Has.getStore c - comp.constraint.mp <$> e.explGet s ety - -def getGlobal - {w c : Type} - [FamilyDef StorageFam c (GlobalStorage c)] - [@Component c (GlobalStorage c) c _ _] - [@Has w c (GlobalStorage c) _] - [@ExplGet (GlobalStorage c) c _] : System w c := get global - --- TODO: I want to call this `set` but it conflicts with a Prelude function -/-- Writes a component to the given entity. Will overwrite existing components --/ -def set' - {w c s t : Type} - [FamilyDef StorageFam c s] - [FamilyDef ElemFam s t] - [comp : @Component c s t _ _] - [@Has w c s _] - [e : @ExplSet s t _] - (ety : Entity) - (x : c) : System w Unit := do - let s ← Has.getStore c - e.explSet s ety (comp.constraint.symm.mp x) - -def setGlobal - {w c : Type} - [FamilyDef StorageFam c (GlobalStorage c)] - [@Component c (GlobalStorage c) c _ _] - [@Has w c (GlobalStorage c) _] - (x : c) : System w Unit := set' global x - -/-- Returns whether the given entity has component c --/ -def exists? - (c : Type) - [FamilyDef StorageFam c s] - [FamilyDef ElemFam s t] - [@Component c s t _ _] - [@Has w c s _] - [e : @ExplGet s t _] - (ety : Entity) : System w Bool := do - let s ← Has.getStore c - e.explExists s ety - -/-- Destroys component c for the given enitty --/ -def destroy - (c : Type) - [FamilyDef StorageFam c s] - [FamilyDef ElemFam s t] - [@Component c s t _ _] - [@Has w c s _] - [e : ExplDestroy s] - (ety : Entity) : System w Unit := do - let s ← Has.getStore c - e.explDestroy s ety - -/-- Applies a function if the given entity exists in the source component --/ -def modify' - [FamilyDef StorageFam cx sx] - [FamilyDef ElemFam sx tx] - [FamilyDef StorageFam cy sy] - [FamilyDef ElemFam sy ty] - [compX : @Component cx sx tx _ _] - [compY : @Component cy sy ty _ _] - [@Has w cx sx _] - [@Has w cy sy _] - [getX : @ExplGet sx tx _] - [setY : @ExplSet sy ty _] - (ety : Entity) - (f : cx → cy) : System w Unit := do - let sx ← Has.getStore cx - let sy ← Has.getStore cy - if (← getX.explExists sx ety) - then do - let x ← getX.explGet sx ety - setY.explSet sy ety (compY.constraint.symm.mp (f (compX.constraint.mp x))) - -/-- Returns an array of all components with type cx --/ -def members - [FamilyDef StorageFam cx sx] - [FamilyDef ElemFam sx tx] - [compX : @Component cx sx tx _ _] - [@Has w cx sx _] - [getX : @ExplGet sx tx _] - [mX : ExplMembers sx] : System w (Array cx) := do - let stx ← Has.getStore cx - let sl ← mX.explMembers stx - let res : Array tx ← sl.mapM (getX.explGet stx) |> monadLift - return (res.map compX.constraint.mp) - -/-- Maps a function over all entities with a cx component and writes their cy --/ -def cmap - [FamilyDef StorageFam cx sx] - [FamilyDef ElemFam sx tx] - [FamilyDef StorageFam cy sy] - [FamilyDef ElemFam sy ty] - [compX : @Component cx sx tx _ _] - [compY : @Component cy sy ty _ _] - [@Has w cx sx _] - [@Has w cy sy _] - [getX : @ExplGet sx tx _] - [setY : @ExplSet sy ty _] - [mX : ExplMembers sx] - (f : cx → cy) : System w Unit := do - let stx ← Has.getStore cx - let sty ← Has.getStore cy - let sl ← mX.explMembers stx - for ety in sl do - let x ← getX.explGet stx ety - setY.explSet sty ety (compY.constraint.symm.mp (f (compX.constraint.mp x))) - -def cmapM - [FamilyDef StorageFam cx sx] - [FamilyDef ElemFam sx tx] - [FamilyDef StorageFam cy sy] - [FamilyDef ElemFam sy ty] - [compX : @Component cx sx tx _ _] - [compY : @Component cy sy ty _ _] - [@Has w cx sx _] - [@Has w cy sy _] - [getX : @ExplGet sx tx _] - [setY : @ExplSet sy ty _] - [mX : ExplMembers sx] - (sys : cx → System w cy) : System w Unit := do - let stx ← Has.getStore cx - let sty ← Has.getStore cy - let sl ← mX.explMembers stx - for ety in sl do - let x ← getX.explGet stx ety - let fx ← sys (compX.constraint.mp x) - setY.explSet sty ety (compY.constraint.symm.mp fx) - -def cmapM_ - [FamilyDef StorageFam cx sx] - [FamilyDef ElemFam sx tx] - [compX : @Component cx sx tx _ _] - [@Has w cx sx _] - [getX : @ExplGet sx tx _] - [mX : ExplMembers sx] - (sys : cx → System w Unit) : System w Unit := do - let stx ← Has.getStore cx - let sl ← mX.explMembers stx - for ety in sl do - let x ← getX.explGet stx ety - sys (compX.constraint.mp x) - -def cfold - [FamilyDef StorageFam cx sx] - [FamilyDef ElemFam sx tx] - [compX : @Component cx sx tx _ _] - [@Has w cx sx _] - [getX : @ExplGet sx tx _] - [mX : ExplMembers sx] - (f : a → cx → a) (accInit : a) : System w a := do - let stx ← Has.getStore cx - let sl ← mX.explMembers stx - sl.foldlM (fun a e => (f a ∘ compX.constraint.mp) <$> getX.explGet stx e) accInit - -/-- collect matching components into an array using the specified test/process function -/ -def collect - [FamilyDef StorageFam cx sx] - [FamilyDef ElemFam sx tx] - [@Component cx sx tx _ _] - [@Has w cx sx _] - [@ExplGet sx tx _] - [ExplMembers sx] - (f : cx → Option a) : System w (Array a) := - cfold (fun acc e => f e |>.elim acc acc.push) #[] diff --git a/lean/Examples.lean b/lean/Examples.lean deleted file mode 100644 index 2d0eb78..0000000 --- a/lean/Examples.lean +++ /dev/null @@ -1,9 +0,0 @@ -import «Examples».Window -import «Examples».InputKeys -import «Examples».Camera3D -import «Examples».Camera2DPlatformer -import «Examples».JessicaCantSwim -import «Examples».BasicECS -import «Examples».Orbital -import «Examples».BouncingBall -import «Examples».ImageDenotation diff --git a/lean/Examples/BasicECS.lean b/lean/Examples/BasicECS.lean deleted file mode 100644 index 759e4df..0000000 --- a/lean/Examples/BasicECS.lean +++ /dev/null @@ -1,60 +0,0 @@ -import Raylean -import ECS - -open Raylean -open Raylean.Types -open ECS - -namespace BasicECS - -structure Camera where - camera : Camera3D - --- Brings `World` and `initWorld` into scope -makeWorldAndMapComponents [Camera] - -def init : System World Unit := do - let mut camera : Camera3D := { - position := ⟨5, 1, 0⟩, - target := ⟨0, 0, 0⟩, - up := ⟨0, 1, 0⟩, - fovy := 70 - projection := .perspective} - camera ← updateCamera camera CameraMode.firstPerson - - -- Create a global entity with the camera component - -- `set'` sets a component's state for a given entity - -- `global` refers to the unique global entity - set' global <| Camera.mk camera - - initWindow 1920 1080 "App" - setTargetFPS 60 - -def terminate : System World Unit := closeWindow - -def update : System World Unit := do - -- get the camera component from the global entity - let (Camera.mk c) ← get global - let c' ← updateCamera c CameraMode.firstPerson - - -- replace the global entity's camera with the updated Camera - set' global <| Camera.mk c' - -def render : System World Unit := do - let (Camera.mk c) ← get global - renderFrame do - clearBackground Color.white - renderWithCamera c do - drawCube ⟨0,0,0⟩ 2 2 2 Color.red - drawCubeWires ⟨0,0,0⟩ 2 2 2 Color.blue - drawGrid 10 1 - drawFPS 10 20 - drawText "Welcome to the third dimension" 10 40 20 Color.black - -def run : System World Unit := do - while not (← windowShouldClose) do - update - render - -def main : IO Unit := do - runSystem (init *> run *> terminate) (← initWorld) diff --git a/lean/Examples/BouncingBall.lean b/lean/Examples/BouncingBall.lean deleted file mode 100644 index 71b5f80..0000000 --- a/lean/Examples/BouncingBall.lean +++ /dev/null @@ -1,115 +0,0 @@ -import Raylean -import ECS - -open Raylean -open Raylean.Types -open ECS - -namespace BouncingBall - -structure Position where - val : Vector2 - -structure Velocity where - val : Vector2 - -structure Config where - shapeRadius : Float - screenWidth : Float - screenHeight : Float - velocity : Vector2 - deriving Inhabited - -inductive Circle where - | Circle - --- Brings `World` and `initWorld` into scope -makeWorldAndComponents [Position, Velocity, Circle] [Config] [] - -def screenWidth := 800 -def screenHeight := 600 - -def initConfig : Config := - { shapeRadius := 20 - , screenWidth := screenWidth.toFloat - , screenHeight := screenHeight.toFloat - , velocity := ⟨300, 250⟩ - : Config - } - -def init : System World Unit := do - initWindow screenWidth screenHeight "Bouncing ball" - setGlobal initConfig - setTargetFPS 120 - -def newBall (p : Vector2) : System World Unit := do - let c : Config ← getGlobal - newEntityAs_ (Position × Velocity × Circle) (⟨p⟩, ⟨c.velocity⟩, .Circle) - -def newSquare (p : Vector2) : System World Unit := do - let c : Config ← getGlobal - newEntityAs_ (Position × Velocity × Not Circle) (⟨p⟩, ⟨c.velocity.mul (-1)⟩, .Not) - -def renderBall : Position × Circle → System World Unit - | (⟨p⟩, _) => do - let c : Config ← getGlobal - drawCircleV p c.shapeRadius Color.Raylean.maroon - -def renderSquare : Position × Not Circle → System World Unit - | (⟨p⟩, _) => do - let c : Config ← getGlobal - drawRectangleRec ⟨p.x - c.shapeRadius, p.y - c.shapeRadius, 2 * c.shapeRadius, 2 * c.shapeRadius⟩ Color.Raylean.green - -def updateShape (dt : Float) (c : Config) : Position × Velocity → Position × Velocity - | (⟨p⟩, ⟨v⟩) => - let position := p.add (v.mul dt) - let velocity : Vector2 := - ⟨ - if position.x >= c.screenWidth - c.shapeRadius || position.x <= c.shapeRadius then v.x * (-1.0) else v.x, - if position.y >= c.screenHeight - c.shapeRadius || position.y <= c.shapeRadius then v.y * (-1.0) else v.y - ⟩ - (⟨position⟩, ⟨velocity⟩) - -def removeAll (_ : Position) : Not Position := .Not - -def deleteAt (pos : Vector2) (radius : Float) : Position → System World (Option Position) - | ⟨p⟩ => do if - (← checkCollisionPointRec pos {x := p.x - radius, y := p.y - radius, width := 2 * radius, height := 2 * radius : Rectangle}) - then return none else return (some ⟨p⟩) - -/-- An alternative to `deleteAt` that explicitly deletes an entity --/ -def deleteAt' (pos : Vector2) (radius : Float) : Position × Entity → System World Unit - | (⟨p⟩, e) => do if - (← checkCollisionPointRec pos {x := p.x - radius, y := p.y - radius, width := 2 * radius, height := 2 * radius : Rectangle}) - then destroy (Position × Velocity) e else return () - -/-- An alternative to `deleteAt` that uses a Sum to delete an entry --/ -def deleteAt'' (pos : Vector2) (radius : Float) : Position → System World (Unit ⊕ Not Position) - | ⟨p⟩ => do if - (← checkCollisionPointRec pos {x := p.x - radius, y := p.y - radius, width := 2 * radius, height := 2 * radius : Rectangle}) - then return .inr .Not else return .inl () - -def update : System World Unit := do - let c : Config ← getGlobal - if (← isMouseButtonPressed MouseButton.left) then - if (← isKeyDown Key.r) then cmapM (deleteAt (← getMousePosition) c.shapeRadius) - else newBall (← getMousePosition) - if (← isMouseButtonPressed MouseButton.right) then newSquare (← getMousePosition) - if (← isKeyDown Key.space) then cmap removeAll - cmap (updateShape (← getFrameTime) c) - -def render : System World Unit := do - renderFrame do - clearBackground Color.white - cmapM_ renderBall - cmapM_ renderSquare - -def run : System World Unit := do - while not (← windowShouldClose) do - update - render - -def terminate : System World Unit := closeWindow - -def main : IO Unit := do - runSystem (init *> run *> terminate) (← initWorld) diff --git a/lean/Examples/Camera2DPlatformer.lean b/lean/Examples/Camera2DPlatformer.lean deleted file mode 100644 index e99f3ff..0000000 --- a/lean/Examples/Camera2DPlatformer.lean +++ /dev/null @@ -1,131 +0,0 @@ -import «Raylean» - -import Examples.Camera2DPlatformer.Types -open Types - -namespace Camera2DPlatformer - -open Raylean -open Raylean.Types - -def screenWidth : Nat := 800 -def screenHeight : Nat := 450 -def fps : Nat := 60 - -def gravity : Nat := 500 -def player_jump_speed : Float := 350 -def player_horizontal_speed : Float := 200 -def player_width : Float := 40 -def player_height : Float := 1.38 * player_width -def backgroundColor : Color := Color.Raylean.lightgray - -def initPlayer : Player := { position := {x := 400, y := 200}, speed := 0, canJump := false } - -def initCamera : Camera2D := { - target := initPlayer.position, - offset := { x := screenWidth.toFloat / 2, y := screenHeight.toFloat / 2 } - rotation := 0, - zoom := 1 -} - -def initGameState : GameState := { player := initPlayer, camera := initCamera } - -def initGameEnv (playerTexture : Texture2D) : GameEnv := { - playerTexture, - items := [ - { rect := {x := 0, y := 0, width := 1000, height := 400 } - blocking := false, - color := Color.Raylean.lightgray }, - { rect := {x := 0, y := 400, width := 1000, height := 200 } - blocking := true, - color := Color.Raylean.gray }, - { rect := {x := 300, y := 200, width := 400, height := 10 } - blocking := true, - color := Color.Raylean.gray }, - { rect := {x := 250, y := 300, width := 100, height := 10 } - blocking := true, - color := Color.Raylean.gray }, - { rect := {x := 650, y := 300, width := 100, height := 10 } - blocking := true, - color := Color.Raylean.gray }, -] } - -def updatePlayer (delta : Float) : GameM Unit := do - if (← isKeyDown Key.left) then modifyPositionX (· - player_horizontal_speed * delta) - if (← isKeyDown Key.right) then modifyPositionX (· + player_horizontal_speed * delta) - if (← isKeyDown Key.space) && (← get).player.canJump then do - setSpeed player_jump_speed.neg - setCanJump false - - -- Resolve collisions with environment. Result is none if an abstacle is hit - let resolveCollisions : OptionT GameM Unit := do - forM (← read).items fun item => do - let player := (← get).player - let position := player.position - if item.blocking - && item.rect.x <= position.x - && item.rect.x + item.rect.width >= position.x - && item.rect.y >= position.y - && item.rect.y <= position.y + (player.speed * delta) then do - setSpeed 0 - setPositionY item.rect.y - failure - - let hitObstacle : Bool := (← resolveCollisions.run).isNone - if not hitObstacle then - modifyPositionY (· + (← get).player.speed * delta) - modifySpeed (· + gravity.toFloat * delta) - setCanJump false - else - setCanJump true - -def updateCameraCenter [Monad m] [MonadState GameState m] : m Unit := do - setTarget (← get).player.position - -def renderEnvironment : GameM Unit := do - forM (← read).items fun item => drawRectangleRec item.rect item.color - -def renderPlayer : GameM Unit := do - let p := (← get).player - let texture := (← read).playerTexture - let sourceRect : Rectangle := - { x := 0 - , y := 0 - , width := texture.width.toFloat - , height := texture.height.toFloat } - let destRect : Rectangle := - { x := p.position.x - player_width / 2 - , y := p.position.y - player_height - , width := player_width - , height := player_height - } - let origin : Vector2 := ⟨0,0⟩ - let rotation : Float := 0 - drawTexturePro texture sourceRect destRect origin rotation backgroundColor - -def updateZoom [Monad m] [MonadState GameState m] (offset: Float) : m Unit := do - modifyZoom (· + offset) - if (← get).camera.zoom > 3 then setZoom 3 - else if (← get).camera.zoom < 0.25 then setZoom 0.25 - -def doRender : GameM Unit := do - while not (← windowShouldClose) do - updatePlayer (← getFrameTime) - updateZoom ((← getMouseWheelMove) * 0.05) - updateCameraCenter - renderFrame do - clearBackground backgroundColor - renderWithCamera2D (← get).camera do - renderEnvironment - renderPlayer - closeWindow - -def main : IO Unit := do - initWindow screenWidth screenHeight "2d camera" - setTargetFPS fps - let walterTexture ← loadTextureFromImage (← loadImage "resources/walter.png") - doRender - |>.run' initGameState - |>.run (initGameEnv walterTexture) - -end Camera2DPlatformer diff --git a/lean/Examples/Camera2DPlatformer/Types.lean b/lean/Examples/Camera2DPlatformer/Types.lean deleted file mode 100644 index c466130..0000000 --- a/lean/Examples/Camera2DPlatformer/Types.lean +++ /dev/null @@ -1,66 +0,0 @@ -import Raylean.Types -import Lens - -namespace Types - -open Raylean.Types -open Lens - -structure Player where - position : Vector2 - speed : Float - canJump : Bool - -structure EnvItem where - rect : Rectangle - blocking : Bool - color : Color - -structure GameState where - player : Player - camera : Camera2D - -structure GameEnv where - items : List EnvItem - playerTexture : Texture2D - -makeLenses Player -makeLenses Camera2D -makeLenses GameState -makeLenses Vector2 - -abbrev GameM : Type -> Type := StateT GameState (ReaderT GameEnv IO) - -open GameState.Lens -open Player.Lens -open Vector2.Lens -open Camera2D.Lens - -def modifyPositionX [MonadState GameState m] (f : Float → Float) : m Unit := - modify (over (player ∘ position ∘ x) f) - -def modifyPositionY [MonadState GameState m] (f : Float → Float) : m Unit := - modify (over (player ∘ position ∘ y) f) - -def modifySpeed [MonadState GameState m] (f : Float → Float) : m Unit := - modify (over (player ∘ speed) f) - -def setPositionY [MonadState GameState m] (py : Float) : m Unit := - modify (set (player ∘ position ∘ y) py) - -def setCanJump [MonadState GameState m] (b : Bool) : m Unit := - modify (set (player ∘ canJump) b) - -def setSpeed [MonadState GameState m] (s : Float) : m Unit := - modify (set (player ∘ speed) s) - -def modifyZoom [MonadState GameState m] (f : Float -> Float) : m Unit := - modify (over (camera ∘ zoom) f) - -def setZoom [MonadState GameState m] (z : Float) : m Unit := - modify (set (camera ∘ zoom) z) - -def setTarget [MonadState GameState m] (v : Vector2) : m Unit := - modify (set (camera ∘ target) v) - -namespace Types diff --git a/lean/Examples/Camera3D.lean b/lean/Examples/Camera3D.lean deleted file mode 100644 index 259ef77..0000000 --- a/lean/Examples/Camera3D.lean +++ /dev/null @@ -1,41 +0,0 @@ -import «Raylean» - -namespace Camera3D - -open Raylean -open Raylean.Types - -private def screenWidth : Nat := 1000 -private def screenHeight : Nat := 600 -private def fps : Nat := 120 - -private def cubePosition : Vector3 := { x := 0, y := 0, z := 0 : Vector3 } - -private def doRender : IO Unit := do - let mut camera : Camera3D := { - position := { x := 10, y := 10, z := 10 }, - target := { x := 0, y := 0, z := 0 }, - up := { x := 0, y := 1, z := 0 }, - fovy := 45, - projection := CameraProjection.perspective - } - disableCursor - while not (← windowShouldClose) do - camera <- updateCamera camera CameraMode.thridPerson - if (<- isKeyDown Key.space) then - camera := { camera with target := cubePosition } - renderFrame do - clearBackground Color.white - renderWithCamera camera do - drawCube cubePosition 2 2 2 Color.red - drawCubeWires cubePosition 2 2 2 Color.blue - drawGrid 10 1 - drawFPS (screenWidth - 100) 10 - drawText "Welcome to the third dimension" 10 40 20 Color.black - -def camera3D : IO Unit := do - initWindow screenWidth screenHeight "Camera3D" - setTargetFPS fps - doRender - -end Camera3D diff --git a/lean/Examples/Elab.lean b/lean/Examples/Elab.lean deleted file mode 100644 index 3192890..0000000 --- a/lean/Examples/Elab.lean +++ /dev/null @@ -1,15 +0,0 @@ -import Lean - -open Lean Elab Command Term Meta - ---- Generate an array of all elements of an inductive type. ---- This only works when all constructors of the type are unary. -elab "allElements " indTy:ident : term => do - let indName ← resolveGlobalConstNoOverload indTy - let indVal ← getConstInfoInduct indName - for ctor in indVal.ctors do - if (← getConstInfoCtor ctor).numFields != 0 - then throwError "Types with non-unary constructors are not supported" - let ctorVals ← List.mapM mkConst indVal.ctors - mkArrayLit (mkConst indName) ctorVals - diff --git a/lean/Examples/InputKeys.lean b/lean/Examples/InputKeys.lean deleted file mode 100644 index 05ecb3d..0000000 --- a/lean/Examples/InputKeys.lean +++ /dev/null @@ -1,51 +0,0 @@ -import «Raylean» - -namespace InputKeys - -open Raylean -open Raylean.Types - -private def screenWidth : Nat := 800 -private def screenHeight : Nat := 450 - -private def initialBallPosition : Vector2 := { x := screenWidth.toFloat / 2, y := screenHeight.toFloat / 2 } - -private inductive Move where - | up - | down - | left - | right - | stay - -private def updateBallPosition (d : Move) (p : Vector2) : Vector2 := match d with - | Move.right => { p with x := p.x + 2.0 } - | Move.left => { p with x := p.x - 2.0 } - | Move.up => { p with y := p.y - 2.0 } - | Move.down => { p with y := p.y + 2.0 } - | Move.stay => p - -private def getMove : IO Move := do - if (← isKeyDown Key.right) then return Move.right - else if (← isKeyDown Key.left) then return Move.left - else if (← isKeyDown Key.up) then return Move.up - else if (← isKeyDown Key.down) then return Move.down - else return Move.stay - -private def doRender : IO Unit := do - let mut ballPosition := initialBallPosition - while not (← windowShouldClose) do - let d ← getMove - ballPosition := updateBallPosition d ballPosition - renderFrame do - drawFPS (screenWidth - 100) 10 - clearBackground Color.white - drawText "Move the ball with arrow keys" 10 10 20 Color.blue - drawCircleV ballPosition 50 Color.red - closeWindow - -def inputKeys : IO Unit := do - initWindow 800 450 "keyboard input" - setTargetFPS 60 - doRender - -end InputKeys diff --git a/lean/Examples/JessicaCantSwim.lean b/lean/Examples/JessicaCantSwim.lean deleted file mode 100644 index 56356e9..0000000 --- a/lean/Examples/JessicaCantSwim.lean +++ /dev/null @@ -1,61 +0,0 @@ -import «Raylean» - -import Examples.JessicaCantSwim.Rand -import Examples.JessicaCantSwim.Shape -import Examples.JessicaCantSwim.Types -import Examples.JessicaCantSwim.Draw -import Examples.JessicaCantSwim.Keys -import Examples.JessicaCantSwim.Game - -namespace JessicaCantSwim - -open Raylean -open Raylean.Types - -def getKeys: IO (List Keys.Keys) := do - let mut keys := #[] - if (← isKeyDown Key.down) - then keys := keys.push Keys.Keys.Down - if (← isKeyDown Key.up) - then keys := keys.push Keys.Keys.Up - if (← isKeyDown Key.left) - then keys := keys.push Keys.Keys.Left - if (← isKeyDown Key.right) - then keys := keys.push Keys.Keys.Right - return keys.toList - -def draw (draw: Draw.Draw): IO Unit := do - match draw with - | Draw.Draw.Text text x y size ⟨ r, g, b, a ⟩ => - drawText text x y size ⟨ r, g, b, a ⟩ - | Draw.Draw.Rectangle ⟨ x, y, width, height ⟩ ⟨ r, g, b, a ⟩ => - drawRectangleRec ⟨ x, y, width, height ⟩ ⟨ r, g, b, a ⟩ - | Draw.Draw.Circle ⟨ ⟨ x, y ⟩ , radius ⟩ ⟨ r, g, b, a ⟩ => - drawCircleV ⟨ x, y ⟩ radius ⟨ r, g, b, a ⟩ - -def draws (drawings: List Draw.Draw): IO Unit := do - for drawing in drawings do - draw drawing - -def main : IO Unit := do - let screenWidth: Nat := 800 - let screenHeight : Nat := 450 - let startPosition : Shape.Vector2 := { x := 200, y := 200 } - initWindow screenWidth screenHeight "Jessica Can't Swim" - setTargetFPS 60 - let rand ← Rand.init 123 - let mut game := Game.init rand startPosition screenWidth screenHeight - while not (← windowShouldClose) do - let delta ← getFrameTime - let keys ← getKeys - let emits := game.emit - let events: List Types.Msg := List.map (λ key => Types.Msg.Key key) keys - game := game.step delta (List.flatten [events, emits]) - let drawings := game.view - let ⟨ ⟨ ox, oy ⟩, ⟨ tx, ty ⟩, r, z ⟩ := game.camera - renderFrame do - clearBackground Color.Raylean.lightgray - renderWithCamera2D ⟨ ⟨ ox, oy ⟩, ⟨ tx, ty ⟩, r, z ⟩ (draws drawings) - closeWindow - -end JessicaCantSwim diff --git a/lean/Examples/JessicaCantSwim/Camera.lean b/lean/Examples/JessicaCantSwim/Camera.lean deleted file mode 100644 index 84a7ccf..0000000 --- a/lean/Examples/JessicaCantSwim/Camera.lean +++ /dev/null @@ -1,23 +0,0 @@ -import Examples.JessicaCantSwim.Shape - -namespace Camera - -structure Camera where - /-- Camera offset (displacement from target) -/ - offset : Shape.Vector2 - /-- Camera target (rotation and zoom origin) -/ - target : Shape.Vector2 - /-- Camera rotation in degrees -/ - rotation : Float - /-- Camera zoom (scaling), should be 1.0f by default -/ - zoom : Float - -def init (_player: Shape.Vector2) (_screenWidth: Nat) (_screenHeight: Nat): Camera := - { - target := {x := 0, y := 0}, - offset := {x := 0, y := 0}, - rotation := 0, - zoom := 1, - } - -end Camera diff --git a/lean/Examples/JessicaCantSwim/Collision.lean b/lean/Examples/JessicaCantSwim/Collision.lean deleted file mode 100644 index d4a8d71..0000000 --- a/lean/Examples/JessicaCantSwim/Collision.lean +++ /dev/null @@ -1,33 +0,0 @@ -import Examples.JessicaCantSwim.Types - -namespace Collision - --- detect if there is a collision between two rectangles -private def detect(rect1: Shape.Rectangle) (rect2: Shape.Rectangle): Bool := - rect1.x < rect2.x + rect2.width && - rect1.x + rect1.width > rect2.x && - rect1.y < rect2.y + rect2.height && - rect1.y + rect1.height > rect2.y - -def detects {ModelID: Type} (entities: List (ModelID × List Shape.Rectangle)): Id (List (ModelID × ModelID)) := do - let mut collisions := #[] - for src in entities do - for dst in entities do - for srcBound in src.2 do - for dstBound in dst.2 do - if detect srcBound dstBound then - collisions := collisions.push (src.1, dst.1) - return collisions.toList - -def detectCollisions (msgs: List Types.Msg) : (List Types.Msg) := - let idBoxPairs := List.filterMap (λ msg => - match msg with - | Types.Msg.Bounds id boxes => - Option.some (id, boxes) - | _otherwise => - Option.none - ) msgs - let collisions := detects idBoxPairs - List.map (λ collision => Types.Msg.Collision collision.1 collision.2) collisions - -end Collision diff --git a/lean/Examples/JessicaCantSwim/Colors.lean b/lean/Examples/JessicaCantSwim/Colors.lean deleted file mode 100644 index 5c41e5e..0000000 --- a/lean/Examples/JessicaCantSwim/Colors.lean +++ /dev/null @@ -1,23 +0,0 @@ -namespace Colors - -structure Color where - /-- Color red value -/ - r : UInt8 - /-- Color green value -/ - g : UInt8 - /-- Color blue value -/ - b : UInt8 - /-- Color alpha value -/ - a : UInt8 := 255 - deriving BEq - -def white := { r:=255, g:=255, b:=255, a:=255 : Color } -def red := { r:=255, g:=0, b:=0, a:=255 : Color } -def green := { r:=0, g:=255, b:=0, a:=255 : Color } -def blue := { r:=0, g:=0, b:=255, a:=255 : Color } -def yellow := { r:=0, g:=255, b:=255, a:=255 : Color } -def black := { r:=0, g:=0, b:=0, a:=255 : Color } -def magenta := { r := 255, g := 0, b := 255, a := 255 : Color } -def transparent := { r:=0, g:=0, b:=0, a:=0 : Color } - -end Colors diff --git a/lean/Examples/JessicaCantSwim/Contributing.md b/lean/Examples/JessicaCantSwim/Contributing.md deleted file mode 100644 index 5f63cbd..0000000 --- a/lean/Examples/JessicaCantSwim/Contributing.md +++ /dev/null @@ -1,18 +0,0 @@ -# Contributing Guidelines - -This is the contributing guidelines for Jessica Can't Swim. - -## Monads - -They are great, but they make it harder for others to understand. - -Limit monads to: - -* `IO Unit` for render functions. -* `Id ` for imperative code. - -## namespaces and imports - -* Every file should start with and end a namespace -* namespaces are not separated by dots, they are single words. -* Do not use `open`, only used qualified imports. diff --git a/lean/Examples/JessicaCantSwim/Draw.lean b/lean/Examples/JessicaCantSwim/Draw.lean deleted file mode 100644 index 6954a45..0000000 --- a/lean/Examples/JessicaCantSwim/Draw.lean +++ /dev/null @@ -1,11 +0,0 @@ -import Examples.JessicaCantSwim.Colors -import Examples.JessicaCantSwim.Shape - -namespace Draw - -inductive Draw where -| Text (text: String) (x: Nat) (y: Nat) (size: Nat) (color: Colors.Color) -| Rectangle (r: Shape.Rectangle) (color: Colors.Color) -| Circle (circle: Shape.Circle) (color: Colors.Color) - -end Draw diff --git a/lean/Examples/JessicaCantSwim/Game.lean b/lean/Examples/JessicaCantSwim/Game.lean deleted file mode 100644 index 8d88222..0000000 --- a/lean/Examples/JessicaCantSwim/Game.lean +++ /dev/null @@ -1,79 +0,0 @@ -import Examples.JessicaCantSwim.Rand -import Examples.JessicaCantSwim.Types -import Examples.JessicaCantSwim.Camera - -import Examples.JessicaCantSwim.Collision -import Examples.JessicaCantSwim.Player -import Examples.JessicaCantSwim.Scoreboard -import Examples.JessicaCantSwim.Ocean -import Examples.JessicaCantSwim.WetSand -import Examples.JessicaCantSwim.Shells - -namespace Game - -structure Game where - camera : Camera.Camera - -- Add your new Model here: - player: Player.Player - scoreboard: Scoreboard.Scoreboard - ocean: Ocean.Ocean - wetsand: WetSand.WetSand - shells: Shells.Shells - -def init (r: Rand.Generator) (position: Shape.Vector2) (screenWidth: Nat) (screenHeight: Nat): Game := - let camera := Camera.init position screenWidth screenHeight - let (oceanRand, shellsRand) := r.split - { - camera := camera, - -- Add your new Model here: - player := Player.init position, - scoreboard := Scoreboard.init, - ocean := Ocean.init screenWidth screenHeight oceanRand, - wetsand := WetSand.init screenWidth screenHeight, - shells := Shells.init screenWidth screenHeight shellsRand, - } - -private def Game.update (game: Game) (msg: Types.Msg): Game := - { - camera := game.camera, - -- Add your new Model here: - player := game.player.update msg - scoreboard := game.scoreboard.update msg - ocean := game.ocean.update msg - wetsand := game.wetsand.update msg - shells := game.shells.update msg - } - -def Game.view (game: Game): List Draw.Draw := - List.flatten [ - -- Add your new Model here: - game.wetsand.view, - game.shells.view, - game.ocean.view, - game.player.view, - game.scoreboard.view, - ] - -def Game.emit (game: Game): List Types.Msg := - List.flatten [ - -- Add your new Model here: - game.ocean.emit, - game.wetsand.emit, - game.shells.emit, - game.player.emit, - game.scoreboard.emit - ] - -private def Game.updates (game: Game) (events: List Types.Msg): Id Game := do - let mut game := game - for event in events do - game := game.update event - return game - -def Game.step (game: Game) (delta : Float) (externalEvents: List Types.Msg): Game := - let collisions := Collision.detectCollisions externalEvents - let deltaEvent := Types.Msg.Time delta - let allEvents := List.concat (List.append externalEvents collisions) deltaEvent - Game.updates game allEvents - -end Game diff --git a/lean/Examples/JessicaCantSwim/Keys.lean b/lean/Examples/JessicaCantSwim/Keys.lean deleted file mode 100644 index 4d3cd21..0000000 --- a/lean/Examples/JessicaCantSwim/Keys.lean +++ /dev/null @@ -1,10 +0,0 @@ -namespace Keys - -inductive Keys where - | Down: Keys - | Up: Keys - | Left: Keys - | Right: Keys - deriving BEq - -end Keys diff --git a/lean/Examples/JessicaCantSwim/Ocean.lean b/lean/Examples/JessicaCantSwim/Ocean.lean deleted file mode 100644 index 7b750d8..0000000 --- a/lean/Examples/JessicaCantSwim/Ocean.lean +++ /dev/null @@ -1,84 +0,0 @@ -import Examples.JessicaCantSwim.Types -import Examples.JessicaCantSwim.Rand - -namespace Ocean - -structure Ocean where - private maxWidth: Float - private height: Float - private gravity: Float - private width: Float - private speed: Float - private pulledback: Bool - private rand: Rand.Generator - -def init (maxWidth: Nat) (height: Nat) (r: Rand.Generator) : Ocean := - { - width := 0, - maxWidth := maxWidth.toFloat, - height := height.toFloat, - speed := 100, - gravity := 9.8, - pulledback := false - rand := r - } - -private def Ocean.id (_ocean: Ocean): Types.ID := - Types.ID.Ocean - -private def Ocean.box (ocean: Ocean): Shape.Rectangle := - { - x := ocean.maxWidth - ocean.width, - y := 0, - width := ocean.width, - height := ocean.height, - } - -def Ocean.emit (ocean: Ocean): List Types.Msg := - let boundsMsg := Types.Msg.Bounds ocean.id [ocean.box] - let pullingBackMsg := Types.Msg.OceanPullingBack ocean.width - if ocean.speed < 0 && !ocean.pulledback - then [ boundsMsg, pullingBackMsg] - else [ boundsMsg ] - --- Once the ocean is pulled back, reset it to a new speed -private def Ocean.reset (ocean: Ocean): Ocean := - let (newNum, newGen) := ocean.rand.next - let newSpeed := (newNum % 100).toFloat - { ocean with - width := 0, - speed := newSpeed, - pulledback := false, - rand := newGen, - } - --- Avoids sending Msg.OceanPullingBack twice -private def Ocean.alreadyPulledBack (ocean: Ocean): Ocean := - { ocean with pulledback := true } - -private def Ocean.move (ocean: Ocean) (delta: Float): Ocean := - if ocean.width < 0 - then ocean.reset - else - let width := ocean.width + ocean.speed * delta - let speed := ocean.speed - ocean.gravity * delta - { ocean with - width := width, - speed := speed, - } - -def Ocean.update (ocean: Ocean) (msg: Types.Msg): Ocean := - match msg with - | Types.Msg.OceanPullingBack _ => ocean.alreadyPulledBack - | Types.Msg.Time delta => ocean.move delta - | _otherwise => ocean - -def Ocean.view (ocean: Ocean): List Draw.Draw := - [Draw.Draw.Rectangle ocean.box Colors.blue] - -instance : Types.Model Ocean where - emit := Ocean.emit - update := Ocean.update - view := Ocean.view - -end Ocean diff --git a/lean/Examples/JessicaCantSwim/Player.lean b/lean/Examples/JessicaCantSwim/Player.lean deleted file mode 100644 index ca7de76..0000000 --- a/lean/Examples/JessicaCantSwim/Player.lean +++ /dev/null @@ -1,72 +0,0 @@ -import Examples.JessicaCantSwim.Types - -namespace Player - -structure Player where - private position : Shape.Vector2 - private speed: Float - private radius : Float - private direction : Shape.Vector2 - -def init (position: Shape.Vector2): Player := - { - position := position, - speed := 200, - radius := 10, - direction := ⟨0, 0⟩, - } - -private def Player.id (_player: Player): Types.ID := - Types.ID.Player - -def Player.bounds (p: Player): List Shape.Rectangle := - [{ - x := p.position.x - p.radius, - y := p.position.y + p.radius, - width := p.radius * 2, - height := p.radius * 2, - }] - -def Player.emit (player: Player): List Types.Msg := [ - Types.Msg.Bounds player.id player.bounds - ] - -private def Player.left (p: Player): Player := - { p with direction := ⟨ -1, p.direction.y ⟩ } - -private def Player.right (p: Player): Player := - { p with direction := ⟨ 1, p.direction.y ⟩ } - -private def Player.up (p: Player): Player := - { p with direction := ⟨ p.direction.x, -1 ⟩ } - -private def Player.down (p: Player): Player := - { p with direction := ⟨ p.direction.x, 1 ⟩ } - -private def Player.move (p: Player) (delta: Float): Player := - let factor := p.speed * delta - let newPosition := ⟨ p.position.x + factor * p.direction.x , p.position.y + factor * p.direction.y ⟩ - { p with - direction := ⟨0, 0⟩, - position := newPosition, - } - -def Player.update (p: Player) (msg: Types.Msg): Player := - match msg with - | Types.Msg.Key Keys.Keys.Left => p.left - | Types.Msg.Key Keys.Keys.Right => p.right - | Types.Msg.Key Keys.Keys.Up => p.up - | Types.Msg.Key Keys.Keys.Down => p.down - | Types.Msg.Time delta => p.move delta - | _otherwise => p - --- IO is required, since we are drawing -def Player.view (p: Player): List Draw.Draw := - [Draw.Draw.Circle ⟨ p.position, p.radius ⟩ Colors.green] - -instance : Types.Model Player where - emit := Player.emit - update := Player.update - view := Player.view - -end Player diff --git a/lean/Examples/JessicaCantSwim/Rand.lean b/lean/Examples/JessicaCantSwim/Rand.lean deleted file mode 100644 index b804296..0000000 --- a/lean/Examples/JessicaCantSwim/Rand.lean +++ /dev/null @@ -1,27 +0,0 @@ -namespace Rand - -structure Generator where - private stdgen : StdGen - -def init (seed: Nat): IO Generator := do - IO.setRandSeed seed - let gen: StdGen ← IO.stdGenRef.get - return ⟨ gen ⟩ - -def Generator.range (_g: Generator): Nat × Nat := - stdRange - -def Generator.next (g: Generator): Nat × Generator := - let (n, stdgen) := stdNext g.stdgen - (n, ⟨ stdgen ⟩ ) - -def Generator.split (g: Generator): (Generator × Generator) := - let (stdgen1, stdgen2) := stdSplit g.stdgen - (⟨ stdgen1 ⟩, ⟨ stdgen2 ⟩) - -instance : RandomGen Generator where - range := Generator.range - next := Generator.next - split := Generator.split - -end Rand diff --git a/lean/Examples/JessicaCantSwim/Scoreboard.lean b/lean/Examples/JessicaCantSwim/Scoreboard.lean deleted file mode 100644 index 0fc34fb..0000000 --- a/lean/Examples/JessicaCantSwim/Scoreboard.lean +++ /dev/null @@ -1,48 +0,0 @@ -import Examples.JessicaCantSwim.Types - -namespace Scoreboard - -structure Scoreboard where - private inOcean: Bool - private onWetsand: Bool - private score: Float - -def init: Scoreboard := - { - inOcean := False, - onWetsand := False, - score := 0, - } - -def Scoreboard.update (scoreboard: Scoreboard) (msg: Types.Msg) : Scoreboard := - match msg with - | Types.Msg.Collision Types.ID.Ocean Types.ID.Player => { scoreboard with inOcean := True } - | Types.Msg.Collision Types.ID.WetSand Types.ID.Player => { scoreboard with onWetsand := True } - | Types.Msg.Collision (Types.ID.Shell _) Types.ID.Player => - if !scoreboard.inOcean - then { scoreboard with - score := scoreboard.score + 10, - } else scoreboard - | Types.Msg.Time delta => - if !scoreboard.inOcean && scoreboard.onWetsand - then { scoreboard with - onWetsand := False, - score := scoreboard.score + delta, - } - else scoreboard - | _otherwise => scoreboard - -def Scoreboard.emit (_scoreboard: Scoreboard): List Types.Msg := [] - -def Scoreboard.view (scoreboard: Scoreboard): Id (List (Draw.Draw)) := do - let scoreText := reprStr (scoreboard.score.toUInt64) - if scoreboard.inOcean then - return [Draw.Draw.Text ("Game Over! Top Score: " ++ scoreText) 10 10 24 Colors.black] - return [Draw.Draw.Text scoreText 10 10 24 Colors.black] - -instance : Types.Model Scoreboard where - emit := Scoreboard.emit - update := Scoreboard.update - view := Scoreboard.view - -end Scoreboard diff --git a/lean/Examples/JessicaCantSwim/Shape.lean b/lean/Examples/JessicaCantSwim/Shape.lean deleted file mode 100644 index ce68526..0000000 --- a/lean/Examples/JessicaCantSwim/Shape.lean +++ /dev/null @@ -1,22 +0,0 @@ -namespace Shape - -structure Vector2 where - x : Float - y : Float - deriving Inhabited - -structure Rectangle where - /-- Rectangle top-left corner position x -/ - x : Float - /-- Rectangle top-left corner position y -/ - y : Float - /-- Rectangle width -/ - width : Float - /-- Rectangle height -/ - height : Float - -structure Circle where - position : Shape.Vector2 - radius : Float - -end Shape diff --git a/lean/Examples/JessicaCantSwim/Shells.lean b/lean/Examples/JessicaCantSwim/Shells.lean deleted file mode 100644 index 714cca9..0000000 --- a/lean/Examples/JessicaCantSwim/Shells.lean +++ /dev/null @@ -1,120 +0,0 @@ -import Std -import Examples.JessicaCantSwim.Rand -import Examples.JessicaCantSwim.Types - -namespace Shells - -structure Shell where - private id: Nat - private position : Shape.Vector2 - private radius: Float - -def Shell.init (id: Nat) (position: Shape.Vector2): Shell := - { - id := id, - position := position, - radius := 5, - } - -def Shell.bounds (s: Shell): List Shape.Rectangle := - [{ - x := s.position.x - s.radius, - y := s.position.y + s.radius, - width := s.radius * 2, - height := s.radius * 2, - }] - -def Shell.emit (shell: Shell): Types.Msg := - Types.Msg.Bounds (Types.ID.Shell shell.id) shell.bounds - -def Shell.view (s: Shell): Draw.Draw := - Draw.Draw.Circle ⟨ s.position, s.radius ⟩ Colors.yellow - -structure Shells where - private maxWidth : Float - private maxHeight : Float - private oceanWidth : Float - private oceanHeight : Float - private nextID : Nat - private shellsMap : Std.HashMap Nat Shell - private timeUntilSpawn: Float - private rand: Rand.Generator - -def init (maxWidth: Nat) (maxHeight: Nat) (r: Rand.Generator): Shells := - { - maxWidth := maxWidth.toFloat, - maxHeight := maxHeight.toFloat, - oceanWidth := 0, - oceanHeight := maxHeight.toFloat, - nextID := 0, - shellsMap := Std.HashMap.empty, - timeUntilSpawn := 1000, - rand := r, - } - -def Shells.emit (shells: Shells): List Types.Msg := - (Std.HashMap.map (λ _ shell => shell.emit) shells.shellsMap).values - -def Shells.delete (shells: Shells) (id: Nat): Shells := - let shellList := shells.shellsMap.toList - let filteredList := List.filter (λ (shellID, _) => shellID != id) shellList - { shells with - shellsMap := Std.HashMap.ofList filteredList, - } - -def Shells.add (shells: Shells): Shells := - let (newNum1, newGen1) := shells.rand.next - let (newNum2, newGen2) := newGen1.next - let maxWidth := shells.oceanWidth.toUInt64.toNat - let maxHeight := shells.oceanHeight.toUInt64.toNat - let location: Shape.Vector2 := ⟨ (newNum1 % maxWidth).toFloat, (newNum2 % maxHeight).toFloat ⟩ - let x := (shells.maxWidth - shells.oceanWidth) + location.x - let coords := ⟨x, location.y⟩ - let newShell := Shell.init shells.nextID coords - let shellsMap := shells.shellsMap.insert shells.nextID newShell - { shells with - timeUntilSpawn := shells.timeUntilSpawn + 3, - nextID := shells.nextID + 1, - shellsMap := shellsMap, - rand := newGen2, - } - -def Shells.decSpawnTime (shells: Shells) (delta: Float): Shells := - if shells.timeUntilSpawn < 0 - then shells.add - else { shells with - timeUntilSpawn := shells.timeUntilSpawn - delta, - } - -def Shells.resetSpawnTime (shells: Shells): Shells := - { shells with - timeUntilSpawn := 1, - } - -def Shells.update (shells: Shells) (msg: Types.Msg): Id Shells := do - match msg with - | Types.Msg.Bounds Types.ID.Ocean boxes => - let mut oceanWidth := shells.oceanWidth - for box in boxes do - oceanWidth := box.width - return { shells with - oceanWidth := oceanWidth, - } - | Types.Msg.Collision (Types.ID.Shell id) Types.ID.Player => - shells.delete id - | Types.Msg.OceanPullingBack _ => - shells.resetSpawnTime - | Types.Msg.Time delta => - shells.decSpawnTime delta - | _otherwise => - shells - -def Shells.view (shells: Shells): List Draw.Draw := - List.map (·.view) shells.shellsMap.values - -instance : Types.Model Shells where - emit := Shells.emit - update := Shells.update - view := Shells.view - -end Shells diff --git a/lean/Examples/JessicaCantSwim/Types.lean b/lean/Examples/JessicaCantSwim/Types.lean deleted file mode 100644 index 8b9bec5..0000000 --- a/lean/Examples/JessicaCantSwim/Types.lean +++ /dev/null @@ -1,30 +0,0 @@ -import Examples.JessicaCantSwim.Keys -import Examples.JessicaCantSwim.Draw - -namespace Types - -inductive ID where - | All - -- Add your new Model here: - | Player - | Scoreboard - | Ocean - | WetSand - | Shells - | Shell (n: Nat) - deriving BEq, Repr - -inductive Msg where - -- Add your new Message here: - | Bounds (id: ID) (boxes: List Shape.Rectangle): Msg - | Collision (src: ID) (dst: ID) : Msg - | Key (key: Keys.Keys) : Msg - | Time (delta: Float): Msg - | OceanPullingBack (max: Float): Msg - -class Model (M : Type u) where - emit (model: M): List Msg - update (model: M) (msg: Msg) : M - view (model: M): List Draw.Draw - -end Types diff --git a/lean/Examples/JessicaCantSwim/WetSand.lean b/lean/Examples/JessicaCantSwim/WetSand.lean deleted file mode 100644 index a22c0b2..0000000 --- a/lean/Examples/JessicaCantSwim/WetSand.lean +++ /dev/null @@ -1,81 +0,0 @@ -import Examples.JessicaCantSwim.Types - -namespace WetSand - -structure WetSand where - private maxWidth: Float - private height: Float - private gravity: Float - private countdown: Float - private width: Float - private speed: Float - -def init (maxWidth: Nat) (height: Nat) : WetSand := - { - maxWidth := maxWidth.toFloat, - height := height.toFloat, - gravity := 9.8, - width := 0, - speed := 0, - countdown := 0, - } - -def WetSand.id (_wetsand: WetSand): Types.ID := - Types.ID.WetSand - -private def WetSand.box (wetsand: WetSand): Shape.Rectangle := - { - x := wetsand.maxWidth - wetsand.width, - y := 0, - width := wetsand.width, - height := wetsand.height, - } - -def WetSand.emit (wetsand: WetSand): List Types.Msg := - [ Types.Msg.Bounds wetsand.id [wetsand.box] ] - -def WetSand.update (wetsand: WetSand) (msg: Types.Msg): Id WetSand := do - match msg with - | Types.Msg.OceanPullingBack max => - if max < wetsand.width then - -- Still pulling back from ocean that was out further - return wetsand - return { - maxWidth := wetsand.maxWidth, - height := wetsand.height, - gravity := wetsand.gravity, - countdown := 5, - width := max, - speed := 0, - } - | Types.Msg.Time delta => - if wetsand.width <= 0 then return wetsand - if wetsand.countdown > 0 - then - return { wetsand with - countdown := wetsand.countdown - delta - } - else - let move := wetsand.speed * delta - let width := wetsand.width + move - let speed := wetsand.speed - wetsand.gravity * delta - return { - maxWidth := wetsand.maxWidth, - height := wetsand.height, - gravity := wetsand.gravity, - countdown := 0, - width := width, - speed := speed, - } - | _otherwise => - return wetsand - -def WetSand.view (wetsand: WetSand): List Draw.Draw := - [Draw.Draw.Rectangle wetsand.box Colors.red] - -instance : Types.Model WetSand where - emit := WetSand.emit - update := WetSand.update - view := WetSand.view - -end WetSand diff --git a/lean/Examples/Orbital.lean b/lean/Examples/Orbital.lean deleted file mode 100644 index f4ec07f..0000000 --- a/lean/Examples/Orbital.lean +++ /dev/null @@ -1,117 +0,0 @@ -import Raylean -import ECS -import Examples.Orbital.Types -import Raylean.Graphics2D - -open Raylean -open Raylean.Types -open ECS -open Raylean.Graphics2D - -namespace Orbital - -def screenWidth : Nat := 1920 -def screenHeight : Nat := 1080 -def initPos : Vector2 := ⟨5,0⟩ -def initVel : Vector2 := ⟨0, 1.0 / initPos.length |>.sqrt |>.neg⟩ - -def mkOrbitingBody (initPosition initVelocity : Vector2) : System World Unit := - newEntityAs_ (Position × Velocity × OrbitPath) ⟨⟨initPosition⟩, ⟨initVelocity⟩, ⟨#[]⟩⟩ - -def mkPlayer (initPosition initVelocity : Vector2) : System World Unit := - newEntityAs_ (Position × Velocity × OrbitPath × Player) ⟨⟨initPosition⟩, ⟨initVelocity⟩, ⟨#[]⟩, .Player⟩ - -def mkStaticBody (mass : Float) (position : Vector2) : System World Unit := - newEntityAs_ (Mass × Position × Not Velocity) (⟨mass⟩, ⟨position⟩, .Not) - -def init : System World Unit := do - - mkStaticBody 1 ⟨0,0⟩ - mkStaticBody 0.5 ⟨3, -3⟩ - mkStaticBody 0.5 ⟨-1, -3⟩ - mkStaticBody 0.5 ⟨-4, 4⟩ - mkPlayer initPos initVel - mkOrbitingBody ⟨-4, 0⟩ (initVel.mul (-1)) - mkOrbitingBody ⟨-0.8, 0.6⟩ (initVel.mul (-0.95)) - mkOrbitingBody ⟨1, 1⟩ initVel - - initWindow screenWidth screenHeight "Orbital" - setTargetFPS 60 - -def updateWithStatic (dt : Float) (staticBodies : Array (Mass × Position)) : Position × Velocity × OrbitPath → Position × Velocity × OrbitPath - | ⟨⟨po⟩, ⟨v⟩, ⟨o⟩⟩ => Id.run do - -- compute the new velocity from contributions from each static body - let mut vNew := v - for (⟨m⟩, ⟨ps⟩) in staticBodies do - -- p is the vector pointing from the oribiting body (po) to the static body (ps) - let p := ps.sub po - let pMag := p.length - let softenedDistance := (pMag^2 + 0.25^2).sqrt - let a := p |>.mul (m / softenedDistance^3) - vNew := vNew.add (a.mul dt) - let pNew := po.add (vNew.mul dt) - let oNew := o.push pNew - (⟨pNew⟩, ⟨vNew⟩, ⟨oNew⟩) - -def updateOrbitingBody (dt : Float) : System World Unit := do - let static : Array (Mass × Position × Not Velocity) ← members - cmap (updateWithStatic dt (static.map (fun (m, p, _) => (m, p)))) - -def changeVelocity (dv : Float) : Velocity × Player → Velocity - | (⟨v⟩, _) => ⟨v.add <| v.mul (dv / v.length)⟩ - -def changePerpVelocity (dv : Float) : Velocity × Player → Velocity - | (⟨v⟩, _) => - let normV : Vector2 := ⟨-v.y, v.x⟩ - ⟨v.add <| normV.mul (dv / v.length)⟩ - -def resetOrbitPath (_ : OrbitPath) : OrbitPath := OrbitPath.mk #[] - -def resetPlayer : Position × Velocity × OrbitPath × Player → Position × Velocity × OrbitPath - | (_, _, _, _) => (⟨initPos⟩, ⟨initVel⟩, OrbitPath.mk #[]) - -def update : System World Unit := do - if (← isKeyDown Key.up) then cmap (changeVelocity 0.01) - if (← isKeyDown Key.down) then cmap (changeVelocity (-0.01)) - if (← isKeyDown Key.right) then cmap (changePerpVelocity (-0.01)) - if (← isKeyDown Key.left) then cmap (changePerpVelocity (0.01)) - if (← isKeyDown Key.space) then cmap resetOrbitPath - if (← isKeyDown Key.r) then cmap resetPlayer - updateOrbitingBody (← getFrameTime) - -def bodyScale (mass : Mass) : Float := mass.val * 0.3 - -def staticBody (mass : Mass) (p : Position) : Picture := - .circle (bodyScale mass) |> - .color .red |> - .translate p.val - -def orbitingBody (p : Position) (c : Color) : Picture := - .circle 0.1 |> - .color c |> - .translate p.val - -def orbitPath (o : OrbitPath) : Picture := - .line o.val |> .color .white - -def gamePicture : System World Picture := do - let staticBodies ← collect (cx := Mass × Position × Not Velocity) <| fun (m, p, _) => staticBody m p |> some - let playerOrbitingBody ← collect (cx := Player × Position × Velocity) <| fun (_, p, _) => orbitingBody p .green |> some - let orbitingBodies ← collect (cx := Position × Velocity × Not Player) <| fun (p, _, _) => orbitingBody p .blue |> some - let orbitPaths ← collect <| some ∘ orbitPath - return (.scale ⟨100, 100⟩ <| .pictures (staticBodies ++ playerOrbitingBody ++ orbitingBodies ++ orbitPaths)) - -def render : System World Unit := - renderFrame do - clearBackground Color.black - renderPicture screenWidth.toFloat screenHeight.toFloat (← gamePicture) - -def terminate : System World Unit := closeWindow - -def run : System World Unit := do - while not (← windowShouldClose) do - update - render - -def main : IO Unit := do - runSystem (init *> run *> terminate) (← initWorld) diff --git a/lean/Examples/Orbital/Types.lean b/lean/Examples/Orbital/Types.lean deleted file mode 100644 index 30251d0..0000000 --- a/lean/Examples/Orbital/Types.lean +++ /dev/null @@ -1,26 +0,0 @@ -import Raylean -import ECS - -open Raylean -open Raylean.Types -open ECS - -namespace Orbital - -structure Position where - val : Vector2 - -structure Velocity where - val : Vector2 - -structure OrbitPath where - val : Array Vector2 - -structure Mass where - val : Float - -inductive Player where - | Player - --- Brings `World` and `initWorld` into scope -makeWorldAndComponents [Position, Velocity, OrbitPath, Mass] [] [Player] diff --git a/lean/Examples/Selector.lean b/lean/Examples/Selector.lean deleted file mode 100644 index ca68ac7..0000000 --- a/lean/Examples/Selector.lean +++ /dev/null @@ -1,116 +0,0 @@ -import «Raylean» -import Examples - -import Examples.Elab - -namespace Selector - -open Raylean -open Raylean.Types - -/-- Demos supported in the selector --/ -inductive Demo where - | jessica - | window - | platformer2d - | cube3d - | inputKeys - | basicECS - | orbital - | bouncingBall - | imageDenotation - -def Demo.all := allElements Demo - -def stringToDemo (s : String) : Option Demo := - match s.trim.toLower with - | "jessica" => some .jessica - | "window" => some .window - | "platformer2d" => some .platformer2d - | "cube3d" => some .cube3d - | "inputkeys" => some .inputKeys - | "basicecs" => some .basicECS - | "orbital" => some .orbital - | "bouncingball" => some .bouncingBall - | "imagedenotation" => some .imageDenotation - | _ => none - -def screenWidth : Nat := 800 -def optionHeight : Nat := 80 -def screenHeight : Nat := Demo.all.size * optionHeight -def fps : Nat := 60 -def textSize : Nat := 20 -def lightSelectorColor : Color := Color.Raylean.blue -def darkSelectorColor : Color := Color.Raylean.darkblue -def selectorTextColor : Color := Color.black - -structure DemoInfo where - /-- The action that starts the demo --/ - start : IO Unit - /-- The title that identifies the demo --/ - title : String - -def mkDemoInfo : Demo -> DemoInfo - | .window => {start := Window.window, title := "Basic window"} - | .platformer2d => {start := Camera2DPlatformer.main, title := "2D Platformer"} - | .cube3d => {start := Camera3D.camera3D, title := "3D Cube"} - | .inputKeys => {start := InputKeys.inputKeys, title := "Input keys"} - | .jessica => {start := JessicaCantSwim.main, title := "Jessica can't swim"} - | .basicECS => {start := BasicECS.main, title := "Basic ECS"} - | .orbital => {start := Orbital.main, title := "Orbital"} - | .bouncingBall => {start := BouncingBall.main, title := "Bouncing Ball"} - | .imageDenotation => {start := ImageDenotation.main, title := "Image Denotation"} - -structure DemoRenderInfo where - /-- The action that starts the demo --/ - start : IO Unit - /-- The action that renders the demo selector --/ - render : IO Unit - /-- An action that determines if the position has clicked the demo selector --/ - isClicked : Vector2 -> IO Bool - -/-- Construct a DemoRenderInfo for each Demo --/ -def demoRenderInfos : Array DemoRenderInfo := - let f (idx : Nat) (demo : Demo) := - let demoInfo := mkDemoInfo demo - let yOffset := idx * optionHeight - let rect : Rectangle := - { x := 0 - , y := yOffset.toFloat - , width := screenWidth.toFloat - , height := optionHeight.toFloat } - let color := if idx % 2 == 0 then lightSelectorColor else darkSelectorColor - let render := do - drawRectangleRec rect color - drawText demoInfo.title (Nat.div screenWidth 3) (yOffset + Nat.div optionHeight 2) textSize selectorTextColor - let isClicked (pos : Vector2) := checkCollisionPointRec pos rect - {start := demoInfo.start, render, isClicked : DemoRenderInfo} - Demo.all.mapIdx f - -/-- Start the selector --/ -def start : IO Unit := do - let mut launchDemo : Option (IO Unit) := none - while not (← windowShouldClose) do - if (← isMouseButtonPressed MouseButton.left) then do - let pos ← getMousePosition - launchDemo := DemoRenderInfo.start <$> (← demoRenderInfos.findM? (·.isClicked pos)) - if launchDemo.isSome then break - - renderFrame do - clearBackground Color.green - for i in demoRenderInfos do i.render - closeWindow - if let some start := launchDemo then start - -def selector : IO Unit := do - initWindow Selector.screenWidth Selector.screenHeight "Select a demo" - setTargetFPS 60 - start - -/-- Directly launch a demo by name, otherwise start the selector --/ -def tryLaunchDemo (name : String) : IO Unit := - match stringToDemo name with - | none => selector - | some d => mkDemoInfo d |>.start - -end Selector diff --git a/lean/Examples/Window.lean b/lean/Examples/Window.lean deleted file mode 100644 index 44eadee..0000000 --- a/lean/Examples/Window.lean +++ /dev/null @@ -1,52 +0,0 @@ -import «Raylean» - -namespace Window - -open Raylean.Types -open Raylean.Graphics2D -open Raylean - -def screenWidth : Nat := 800 -def screenHeight : Nat := 600 - -def render : IO Unit := do - let texture ← loadTextureFromImage (← loadImage "resources/Asset.svg") - let sourceRect : Rectangle := - { x := 0 - , y := 0 - , width := texture.width.toFloat - , height := texture.height.toFloat - } - let destRect : Rectangle := - { x := screenWidth.toFloat / 2 - texture.width.toFloat / 2 - , y := screenHeight.toFloat / 2 - , width := texture.width.toFloat - , height := texture.height.toFloat - } - let origin : Vector2 := ⟨0, 0⟩ - let rotation : Float := 0 - let rectangle := .rectangle 10 10 |> .color Color.red |> .scale ⟨20,20⟩ - let circle := .circle 100 |> .color Color.blue |> .scale ⟨0.5, 0.5⟩ - let line := .line #[⟨100, 100⟩, ⟨200, 200⟩] |> .color Color.black - let p : Picture := line ++ (rectangle ++ circle |> .translate ⟨250, -40⟩ |> .scale ⟨1, 2⟩) - - while not (← windowShouldClose) do - renderFrame do - renderPicture screenWidth.toFloat screenHeight.toFloat p - clearBackground Color.Raylean.gold - drawFPS 100 100 - let c := match (← IO.rand 0 6) with - | 0 => Color.red - | 1 => Color.green - | 2 => Color.red - | _ => Color.black - drawText "Hello From Lean!" 190 200 50 c - drawTexturePro texture sourceRect destRect origin rotation Color.white - closeWindow - -def window : IO Unit := do - initWindow screenWidth screenHeight "Hello From Lean!" - setTargetFPS 60 - render - -end Window diff --git a/lean/Main.lean b/lean/Main.lean deleted file mode 100644 index 3675494..0000000 --- a/lean/Main.lean +++ /dev/null @@ -1,6 +0,0 @@ -import «Examples».Selector - -def main (args : List String) : IO Unit := do - match args with - | (demoName :: _) => Selector.tryLaunchDemo demoName - | _ => Selector.selector diff --git a/lean/Raylean.lean b/lean/Raylean.lean deleted file mode 100644 index 725d6fb..0000000 --- a/lean/Raylean.lean +++ /dev/null @@ -1,5 +0,0 @@ -import «Raylean».Core -import «Raylean».Math -import «Raylean».Types -import «Raylean».Frame -import «Raylean».Graphics2D diff --git a/lean/Raylean/Core.lean b/lean/Raylean/Core.lean deleted file mode 100644 index ef7313f..0000000 --- a/lean/Raylean/Core.lean +++ /dev/null @@ -1,144 +0,0 @@ -import «Raylean».Types - -namespace Raylean - -open Raylean.Types - -/- Window-related functions -/ - -@[extern "initWindow"] -opaque initWindow : (width : Nat) → (height : Nat) → (title : @& String) → IO Unit - -@[extern "closeWindow"] -opaque closeWindow : IO Unit - -@[extern "windowShouldClose"] -opaque windowShouldClose : IO Bool - -/- Cursor-related functions -/ - -@[extern "disableCursor"] -opaque disableCursor : IO Unit - -/- Drawing-related functions -/ - -@[extern "clearBackground"] -opaque clearBackground : (c : @& Color) → IO Unit - -@[extern "beginDrawing"] -opaque beginDrawing : IO Unit - -@[extern "endDrawing"] -opaque endDrawing : IO Unit - -@[extern "beginMode2D"] -opaque beginMode2D : (camera : @& Camera2D) → IO Unit - -@[extern "endMode2D"] -opaque endMode2D : IO Unit - -@[extern "beginMode3D"] -opaque beginMode3D : (camera : @& Camera3D) → IO Unit - -@[extern "endMode3D"] -opaque endMode3D : IO Unit - -/- Timing-related functions -/ - -@[extern "setTargetFPS"] -opaque setTargetFPS : (fps : Nat) → IO Unit - -@[extern "getFrameTime"] -opaque getFrameTime : IO Float - -/- Random values generation functions -/ - -@[extern "getRandomValue"] -opaque getRandomValue : UInt32 → UInt32 → IO UInt32 - -/- Input-related functions: keyboard -/ - -@[extern "isKeyDown"] -opaque isKeyDown : (key : Nat) → IO Bool - -/- Input-related functions: mouse -/ - -@[extern "isMouseButtonPressed"] -opaque isMouseButtonPressed : (button : MouseButton) → IO Bool - -@[extern "getMousePosition"] -opaque getMousePosition : IO Vector2 - -@[extern "getMouseWheelMove"] -opaque getMouseWheelMove : IO Float - -/- Camera System Functions -/ - -@[extern "updateCamera"] -opaque updateCamera : (camera : Camera3D) → (mode : CameraMode) → IO Camera3D - -/- Basic shapes drawing functions -/ - -@[extern "drawCircleV"] -opaque drawCircleV : (center : @& Vector2) → (radius : Float) → (color : @& Color) → IO Unit - -@[extern "drawLineV"] -opaque drawLineV : (startPos : @& Vector2) → (endPos : @& Vector2) → (color : @& Color) → IO Unit - -@[extern "drawLineStrip"] -opaque drawLineStrip : (points : @& Array Vector2) → (color : @& Color) → IO Unit - -@[extern "drawRectangleRec"] -opaque drawRectangleRec : (rectangle : @& Rectangle) → (color : @& Color) → IO Unit - -/- Basic shapes collision detection functions -/ - -@[extern "checkCollisionPointRec"] -opaque checkCollisionPointRec : (point : @& Vector2) → (rect : @& Rectangle) -> IO Bool - -/- Screen-space-related functions -/ - -/-- Get the world space position for a 2d camera screen space position -/ -@[extern "getScreenToWorld2D"] -opaque getScreenToWorld2D : (position : @& Vector2) → (camera : @& Camera2D) → Vector2 - -/- Text drawing functions -/ - -@[extern "drawFPS"] -opaque drawFPS : (posX : Nat) → (posY : Nat) → IO Unit - -@[extern "drawText"] -opaque drawText : (text : @& String) → (posX : Nat) → (posY : Nat) → (fontSize : Nat) → (color : @& Color) → IO Unit - -/- Basic geometric 3D shapes drawing functions -/ - -@[extern "drawCube"] -opaque drawCube : (position : @& Vector3) → (width : Float) → (height : Float) → (length : Float) → (color : @& Color) -> IO Unit - -@[extern "drawCubeWires"] -opaque drawCubeWires : (position : @& Vector3) → (width : Float) → (height : Float) → (length : Float) → (color : @& Color) -> IO Unit - -@[extern "drawGrid"] -opaque drawGrid : (slices : Nat) → (spacing : Float) → IO Unit - -@[extern "loadImage"] -opaque loadImage : (resourceName : @& String) -> IO Image - -@[extern "loadTextureFromImage"] -opaque loadTextureFromImage : (image : @& Image) -> IO Texture2D - -@[extern "drawTexture"] -opaque drawTexture : (texture : @& Texture2D) -> (posX : Nat) -> (posY : Nat) -> (color : @& Color) -> IO Unit - -/-- -Source rectangle (part of the texture to use for drawing) -source defines the part of the texture we use for drawing -dest defines the rectangle where our texture part will fit (scaling it to fit) -origin defines the point of the texture used as reference for rotation and scaling, it's relative to destination rectangle size -rotation defines the texture rotation (using origin as rotation point) --/ -@[extern "drawTexturePro"] -opaque drawTexturePro : (texture : @& Texture2D) -> (source : @& Rectangle) -> (dest : @& Rectangle) -> (origin : @& Vector2) -> (rotation : Float) -> (tint : @& Color) -> IO Unit - -@[extern "drawPixelV"] -opaque drawPixelV : (position : @& Vector2) -> (color : @& Color) -> IO Unit diff --git a/lean/Raylean/Frame.lean b/lean/Raylean/Frame.lean deleted file mode 100644 index 928e6a9..0000000 --- a/lean/Raylean/Frame.lean +++ /dev/null @@ -1,20 +0,0 @@ -import «Raylean».Core - -namespace Raylean - -open Raylean.Types - -def renderFrame [Monad m] [MonadLiftT IO m] (mkFrame : m Unit) : m Unit := do - beginDrawing - mkFrame - endDrawing - -def renderWithCamera [Monad m] [MonadLiftT IO m] (camera : Camera3D) (mkScene : m Unit) : m Unit := do - beginMode3D camera - mkScene - endMode3D - -def renderWithCamera2D [Monad m] [MonadLiftT IO m] (camera : Camera2D) (mkScene : m Unit) : m Unit := do - beginMode2D camera - mkScene - endMode2D diff --git a/lean/Raylean/Graphics2D.lean b/lean/Raylean/Graphics2D.lean deleted file mode 100644 index b2da8c9..0000000 --- a/lean/Raylean/Graphics2D.lean +++ /dev/null @@ -1,3 +0,0 @@ -import Raylean.Graphics2D.Basic -import Raylean.Graphics2D.Render -import Raylean.Graphics2D.Image diff --git a/lean/Raylean/Lean.lean b/lean/Raylean/Lean.lean deleted file mode 100644 index 265f6a1..0000000 --- a/lean/Raylean/Lean.lean +++ /dev/null @@ -1,3 +0,0 @@ - -/-- Execute a computation in a modified environment --/ -def ReaderT.local {ρ : Type u} (f : ρ → ρ) (r : ReaderT ρ m α) : ReaderT ρ m α := r.run ∘ f diff --git a/lean/Raylean/Math.lean b/lean/Raylean/Math.lean deleted file mode 100644 index ce5e654..0000000 --- a/lean/Raylean/Math.lean +++ /dev/null @@ -1,24 +0,0 @@ -import Raylean.Types - -namespace Raylean - -namespace Types - -namespace Vector2 - -open Raylean.Types - -def add (v1 : Vector2) (v2 : Vector2) : Vector2 := - { x := v1.x + v2.x, y := v1.y + v2.y : Vector2 } - -def length (v : Vector2) : Float := Float.sqrt (v.x ^ 2 + v.y ^ 2) - -def sub (v1 : Vector2) (v2 : Vector2) : Vector2 := - { x := v1.x - v2.x, y := v1.y - v2.y } - -def mul (v : Vector2) (s : Float) : Vector2 := - { x := s * v.x, y := s * v.y } - -def dot (v1 v2 : Vector2) : Vector2 := ⟨v1.x * v2.x, v1.y * v2.y⟩ - -end Vector2 diff --git a/lean/Raylean/Types.lean b/lean/Raylean/Types.lean deleted file mode 100644 index 0e55d6c..0000000 --- a/lean/Raylean/Types.lean +++ /dev/null @@ -1,157 +0,0 @@ -namespace Raylean - -namespace Types - -structure Vector2 where - x : Float - y : Float - deriving Inhabited - -structure Vector3 where - x : Float - y : Float - z : Float - -structure Rectangle where - /-- Rectangle top-left corner position x -/ - x : Float - /-- Rectangle top-left corner position y -/ - y : Float - /-- Rectangle width -/ - width : Float - /-- Rectangle height -/ - height : Float - -structure Camera2D where - /-- Camera offset (displacement from target) -/ - offset : Vector2 - /-- Camera target (rotation and zoom origin) -/ - target : Vector2 - /-- Camera rotation in degrees -/ - rotation : Float - /-- Camera zoom (scaling), should be 1.0f by default -/ - zoom : Float - -instance : Inhabited Camera2D where - default := ⟨⟨0,0⟩, ⟨0,0⟩, 0, 1⟩ - -inductive CameraProjection where - | perspective - | orthographic - -structure Camera3D where - /-- Camera position -/ - position : Vector3 - /-- Camera target it looks-at -/ - target : Vector3 - /-- Camera up vector (rotation over its axis) -/ - up : Vector3 - /-- Camera field-of-view aperture in Y (degrees) in perspective, used as near plane width in orthographic -/ - fovy : Float - /-- Camera projection: CAMERA_PERSPECTIVE or CAMERA_ORTHOGRAPHIC -/ - projection : CameraProjection - -inductive CameraMode where - | custom - | free - | orbital - | firstPerson - | thridPerson - -structure Color where - /-- Color red value -/ - r : UInt8 - /-- Color green value -/ - g : UInt8 - /-- Color blue value -/ - b : UInt8 - /-- Color alpha value -/ - a : UInt8 := 255 - deriving BEq - -namespace Color - -def white := { r:=255, g:=255, b:=255, a:=255 : Color } -def red := { r:=255, g:=0, b:=0, a:=255 : Color } -def green := { r:=0, g:=255, b:=0, a:=255 : Color } -def blue := { r:=0, g:=0, b:=255, a:=255 : Color } -def yellow := { r:=0, g:=255, b:=255, a:=255 : Color } -def black := { r:=0, g:=0, b:=0, a:=255 : Color } -def magenta := { r := 255, g := 0, b := 255, a := 255 : Color } -def transparent := { r:=0, g:=0, b:=0, a:=0 : Color } - -instance : Inhabited Color where - default := transparent - -namespace Raylean - -def lightgray := { r := 200, g := 200, b := 200, a := 255 : Color } -def gray := { r := 130, g := 130, b := 130, a := 255 : Color } -def darkgray := { r := 80, g := 80, b := 80, a := 255 : Color } -def yellow := { r := 253, g := 249, b := 0, a := 255 : Color } -def gold := { r := 255, g := 203, b := 0, a := 255 : Color } -def orange := { r := 255, g := 161, b := 0, a := 255 : Color } -def pink := { r := 255, g := 109, b := 194, a := 255 : Color } -def red := { r := 230, g := 41, b := 55, a := 255 : Color } -def maroon := { r := 190, g := 33, b := 55, a := 255 : Color } -def green := { r := 0, g := 228, b := 48, a := 255 : Color } -def lime := { r := 0, g := 158, b := 47, a := 255 : Color } -def darkgreen := { r := 0, g := 117, b := 44, a := 255 : Color } -def skyblue := { r := 102, g := 191, b := 255, a := 255 : Color } -def blue := { r := 0, g := 121, b := 241, a := 255 : Color } -def darkblue := { r := 0, g := 82, b := 172, a := 255 : Color } -def purple := { r := 200, g := 122, b := 255, a := 255 : Color } -def violet := { r := 135, g := 60, b := 190, a := 255 : Color } -def darkpurple := { r := 112, g := 31, b := 126, a := 255 : Color } -def beige := { r := 211, g := 176, b := 131, a := 255 : Color } -def brown := { r := 127, g := 106, b := 79, a := 255 : Color } -def darkbrown := { r := 76, g := 63, b := 47, a := 255 : Color } -def raywhite := { r := 245, g := 245, b := 245, a := 255 : Color } - -end Raylean - -end Color - -namespace Key - -def space : Nat := 32 -def right : Nat := 262 -def left : Nat := 263 -def down : Nat := 264 -def up : Nat := 265 -def r : Nat := 82 - -end Key - -inductive MouseButton where - | left - | right - | middle - | side - | extra - | forward - | back - -private opaque Texture2DP : NonemptyType -def Texture2D := Texture2DP.type -instance : Nonempty Texture2D := Texture2DP.property - --- fields of Texture2D defined directly using a namespace must be in the same --- namespace as Texture2D -@[extern "texture2d_width"] -opaque Texture2D.width (texture2d : @& Texture2D) : Nat - -@[extern "texture2d_height"] -opaque Texture2D.height (texture2d : @& Texture2D) : Nat - -private opaque ImageP : NonemptyType -def Image := ImageP.type -instance : Nonempty Image := ImageP.property - --- fields of Image defined directly using a namespace must be in the same --- namespace as Image -@[extern "image_width"] -opaque Image.width (image : @& Image) : Nat - -@[extern "image_height"] -opaque Image.height (image : @& Image) : Nat diff --git a/resources/Asset.svg b/resources/Asset.svg deleted file mode 100644 index 47393aa..0000000 --- a/resources/Asset.svg +++ /dev/null @@ -1,155 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/resources/discord.svg b/resources/discord.svg deleted file mode 100644 index c03e8e1..0000000 --- a/resources/discord.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/resources/walter.png b/resources/walter.png deleted file mode 100644 index 35e1a6c..0000000 Binary files a/resources/walter.png and /dev/null differ diff --git a/resvg-0.43.0/.gitignore b/resvg-0.43.0/.gitignore deleted file mode 100644 index 697690a..0000000 --- a/resvg-0.43.0/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -target -.directory -.DS_Store -.vscode -tools/build-* diff --git a/resvg-0.43.0/CHANGELOG.md b/resvg-0.43.0/CHANGELOG.md deleted file mode 100644 index f07bc51..0000000 --- a/resvg-0.43.0/CHANGELOG.md +++ /dev/null @@ -1,1269 +0,0 @@ -# Change Log -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](http://keepachangelog.com/) -and this project adheres to [Semantic Versioning](http://semver.org/). - -This changelog also contains important changes in dependencies. - -## [Unreleased] - -## [0.43.0] - 2024-08-10 -### Added -- Support WebP images. - Thanks to [@notjosh](https://github.com/notjosh). - -### Changed -- Use `zune-jpeg` instead of `jpeg-decoder`. - Thanks to [@mattfbacon](https://github.com/mattfbacon). -- Update dependencies. - -### Fixed -- Canvas size limits calculation. -- SVG fonts handling. - Thanks to [@LaurenzV](https://github.com/LaurenzV). -- Transforms in COLR fonts. - Thanks to [@LaurenzV](https://github.com/LaurenzV). - -## [0.42.0] - 2024-06-01 -### Added -- `resvg` can render color fonts now, aka Emojis.
- In TrueType terms, `COLRv0`, `COLRv1` (mostly), `sbix`, `CBDT` and `SVG` tables are supported.
- Thanks to [@LaurenzV](https://github.com/LaurenzV). -- Fonts matching and fallback can be controlled by the caller via `usvg::FontResolver` now. - Thanks to [@LaurenzV](https://github.com/LaurenzV). -- `usvg::Options::font_resolver`. Similar to `usvg::Options::image_href_resolver` we already had. -- `usvg::Options::fontdb` -- Support double-quoted FuncIRIs, aka `url("#id")`. -- `image` element viewbox flattening.
- Instead of having `usvg::Image::view_box` that the caller should handle themselves, - we instead replace it with `transform` and optional `clip-path`. - This greatly simplifies `image` rendering. -- `usvg::Image::size` -- Tree viewbox flattening.
- Similar to `image` above, but affects the root `svg` element instead. -- `pattern` viewbox flattening.
- Similar to `image` above, but for patterns. -- Improve vertical text rendering. - Thanks to [@LaurenzV](https://github.com/LaurenzV). - -### Changed -- `usvg::fontdb::Database` should be set in `usvg::Options` and not passed - to the parser separately now. -- `usvg::Options` and `usvg::ImageHrefResolver` have a lifetime now. -- Replace `usvg::Visibility` enum with just `bool`. -- `usvg::Path::visibility()` is replaced with `usvg::Path::is_visible()` -- `usvg::Image::visibility()` is replaced with `usvg::Image::is_visible()` -- `usvg::TextSpan::visibility()` is replaced with `usvg::TextSpan::is_visible()` -- Always represent `feImage` content as a link to an element.
- In SVG, `feImage` can contain a link to an element or a base64 image data, just like `image`. - From now, the inlined base64 data will always be represented by a link to an actual `image` element. - ```xml - - - - ``` - will be parsed as - ```xml - - - - - ``` - This simplifies `feImage` rendering, since we don't have to handle both cases now. -- The `--list-fonts` resvg argument can be used without providing an SVG file now. - Can simply call `resvg --list-fonts` now. -- The `--list-fonts` resvg argument includes generic font family names as well now. -- Make sure all warning and errors are printed to stderr. - Thanks to [@ahaoboy](https://github.com/ahaoboy). - -### Removed -- `usvg::ViewBox`, `usvg::AspectRatio`, `usvg::Align` types. Nol longer used. -- `usvg::filter::Image::aspect`. No longer needed. -- `usvg::filter::Image::rendering_mode`. No longer needed. -- `usvg::filter::Image::data`. Use `usvg::filter::Image::root` instead. -- `usvg::Tree::view_box`. No longer needed. -- `usvg::Image::view_box`. No longer needed. -- `usvg::Image::pattern`. No longer needed. -- `usvg::utils::align_pos`. No longer needed. -- `usvg::Visibility`. No longer needed. -- (c-api) `resvg_get_image_viewbox`. Use `resvg_get_image_size` instead. - -### Fixed -- `context-fill` handling. - Thanks to [@LaurenzV](https://github.com/LaurenzV). - -## [0.41.0] - 2024-04-03 -### Added -- `context-fill` and `context-stroke` support. - Thanks to [@LaurenzV](https://github.com/LaurenzV). -- `usvg::Text::layouted()`, which returns a list of glyph IDs. - It can be used to manually draw glyphs, unlike with `usvg::Text::flattened()`, which returns - just vector paths. - Thanks to [@LaurenzV](https://github.com/LaurenzV). - -### Fixed -- Missing text when a `text` element uses multiple fonts and one of them produces ligatures. -- Absolute transform propagation during `use` resolving. -- Absolute transform propagation during nested `svg` resolving. -- `Node::abs_transform` documentation. The current element's transform _is_ included. - -## [0.40.0] - 2024-02-17 -### Added -- `usvg::Tree` is `Send + Sync` compatible now. -- `usvg::WriteOptions::preserve_text` to control how `usvg` generates an SVG. -- `usvg::Image::abs_bounding_box` - -### Changed -- All types in `usvg` are immutable now. Meaning that `usvg::Tree` cannot be modified - after creation anymore. -- All struct fields in `usvg` are private now. Use getters instead. -- All `usvg::Tree` parsing methods require the `fontdb` argument now. -- All `defs` children like gradients, patterns, clipPaths, masks and filters are guarantee - to have a unique, non-empty ID. -- All `defs` children like gradients, patterns, clipPaths, masks and filters are guarantee - to have `userSpaceOnUse` units now. No `objectBoundingBox` units anymore. -- `usvg::Mask` is allowed to have no children now. -- Text nodes will not be parsed when the `text` build feature isn't enabled. -- `usvg::Tree::clip_paths`, `usvg::Tree::masks`, `usvg::Tree::filters` returns - a pre-collected slice of unique nodes now. - It's no longer a closure and you do not have to deduplicate nodes by yourself. -- `usvg::filter::Primitive::x`, `y`, `width` and `height` methods were replaced - with `usvg::filter::Primitive::rect`. -- Split `usvg::Tree::paint_servers` into `usvg::Tree::linear_gradients`, - `usvg::Tree::radial_gradients`, `usvg::Tree::patterns`. - All three returns pre-collected slices now. -- A `usvg::Path` no longer can have an invalid bbox. Paths with an invalid bbox will be - rejected during parsing. -- All `usvg` methods that return bounding boxes return non-optional `Rect` now. - No `NonZeroRect` as well. -- `usvg::Text::flattened` returns `&Group` and not `Option<&Group>` now. -- `usvg::ImageHrefDataResolverFn` and `usvg::ImageHrefStringResolverFn` - require `fontdb::Database` argument. -- All shared nodes are stored in `Arc` and not `Rc` now. -- `resvg::render_node` now includes filters bounding box. Meaning that a node with a blur filter - no longer be clipped. -- Replace `usvg::utils::view_box_to_transform` with `usvg::ViewBox::to_transform`. -- Rename `usvg::XmlOptions` into `usvg::WriteOptions` and embed `xmlwriter::Options`. - -### Removed -- `usvg::Tree::postprocess()` and `usvg::PostProcessingSteps`. No longer needed. -- `usvg::ClipPath::units()`, `usvg::Mask::units()`, `usvg::Mask::content_units()`, - `usvg::Filter::units()`, `usvg::Filter::content_units()`, `usvg::LinearGradient::units()`, - `usvg::RadialGradient::units()`, `usvg::Pattern::units()`, `usvg::Pattern::content_units()` - and `usvg::Paint::units()`. They are always `userSpaceOnUse` now. -- `usvg::Units`. No longer needed. - -### Fixed -- Text bounding box is accounted during SVG size resolving. - Previously, only paths and images were included. -- Font selection when an italic font isn't explicitly marked as one. -- Preserve `image` aspect ratio when only `width` or `height` are present. - Thanks to [@LaurenzV](https://github.com/LaurenzV). - -## [0.39.0] - 2024-02-06 -### Added -- `font` shorthand parsing. - Thanks to [@LaurenzV](https://github.com/LaurenzV). -- `usvg::Group::abs_bounding_box` -- `usvg::Group::abs_stroke_bounding_box` -- `usvg::Path::abs_bounding_box` -- `usvg::Path::abs_stroke_bounding_box` -- `usvg::Text::abs_bounding_box` -- `usvg::Text::abs_stroke_bounding_box` - -### Changed -- All `usvg-*` crates merged into one. There is just the `usvg` crate now, as before. - -### Removed -- `usvg::Group::abs_bounding_box()` method. It's a field now. -- `usvg::Group::abs_filters_bounding_box()` -- `usvg::TreeParsing`, `usvg::TreePostProc` and `usvg::TreeWriting` traits. - They are no longer needed. - -### Fixed -- `font-family` parsing. - Thanks to [@LaurenzV](https://github.com/LaurenzV). -- Absolute bounding box calculation for paths. - -## [0.38.0] - 2024-01-21 -### Added -- Each `usvg::Node` stores its absolute transform now. - `Node::abs_transform()` executes in constant time now. -- `usvg::Tree::calculate_bounding_boxes` to calculate all bounding boxes beforehand. -- `usvg::Node::bounding_box` which returns a precalculated node's bounding box in object coordinates. -- `usvg::Node::abs_bounding_box` which returns a precalculated node's bounding box in canvas coordinates. -- `usvg::Node::stroke_bounding_box` which returns a precalculated node's bounding box, - including stroke, in object coordinates. -- `usvg::Node::abs_stroke_bounding_box` which returns a precalculated node's bounding box, - including stroke, in canvas coordinates. -- (c-api) `resvg_get_node_stroke_bbox` -- `usvg::Node::filters_bounding_box` -- `usvg::Node::abs_filters_bounding_box` -- `usvg::Tree::postprocess` - -### Changed -- `resvg` renders `usvg::Tree` directly again. `resvg::Tree` is gone. -- `usvg` no longer uses `rctree` for the nodes tree implementation. - The tree is a regular `enum` now. - - A caller no longer need to use the awkward `*node.borrow()`. - - No more panics on incorrect mutable `Rc` access. - - Tree nodes respect tree's mutability rules. Before, one could mutate tree nodes when the tree - itself is not mutable. Because `Rc` provides a shared mutable access. -- Filters, clip paths, masks and patterns are stored as `Rc>` instead of `Rc`. - This is required for proper mutability since `Node` itself is no longer an `Rc`. -- Rename `usvg::NodeKind` into `usvg::Node`. -- Upgrade to Rust 2021 edition. - -### Removed -- `resvg::Tree`. No longer needed. `resvg` can render `usvg::Tree` directly once again. -- `rctree::Node` methods. The `Node` API is completely different now. -- `usvg::NodeExt`. No longer needed. -- `usvg::Node::calculate_bbox`. Use `usvg::Node::abs_bounding_box` instead. -- `usvg::Tree::convert_text`. Use `usvg::Tree::postprocess` instead. -- `usvg::TreeTextToPath` trait. No longer needed. - -### Fixed -- Mark `mask-type` as a presentation attribute. -- Do not show needless warnings when parsing some attributes. -- `feImage` rendering with a non-default position. - Thanks to [@LaurenzV](https://github.com/LaurenzV). - -## [0.37.0] - 2023-12-16 -### Added -- `usvg` can write text back to SVG now. - Thanks to [@LaurenzV](https://github.com/LaurenzV). -- `--preserve-text` flag to the `usvg` CLI tool. - Thanks to [@LaurenzV](https://github.com/LaurenzV). -- Support [`transform-origin`](https://drafts.csswg.org/css-transforms/#transform-origin-property) - property. - Thanks to [@LaurenzV](https://github.com/LaurenzV). -- Support non-default markers order via - [`paint-order`](https://svgwg.org/svg2-draft/painting.html#PaintOrder). - Previously, only fill and stroke could have been swapped. - Thanks to [@LaurenzV](https://github.com/LaurenzV). -- `usvg_tree::Text::flattened` that will contain a flattened/outlined text. -- `usvg_tree::Text::bounding_box`. Will be set only after text flattening. -- Optimize `usvg_tree::NodeExt::abs_transform` by storing absolute transforms in the tree - instead of calculating them each time. - -### Changed -- `usvg_tree::Text::positions` was replaced with `usvg_tree::Text::dx` and `usvg_tree::Text::dy`.
- `usvg_tree::CharacterPosition::x` and `usvg_tree::CharacterPosition::y` are gone. - They were redundant and you should use `usvg_tree::TextChunk::x` - and `usvg_tree::TextChunk::y` instead. -- `usvg_tree::LinearGradient::id` and `usvg_tree::RadialGradient::id` are moved to - `usvg_tree::BaseGradient::id`. -- Do not generate element IDs during parsing. Previously, some elements like `clipPath`s - and `filter`s could have generated IDs, but it wasn't very reliable and mostly unnecessary. - Renderer doesn't rely on them and usvg writer would generate them anyway. -- Text-to-paths conversion via `usvg_text_layout::Tree::convert_text` no longer replaces - original text elements with paths, but instead puts them into `usvg_tree::Text::flattened`. - -### Removed -- The `transform` field from `usvg_tree::Path`, `usvg_tree::Image` and `usvg_tree::Text`. - Only `usvg_tree::Group` can have it.
- It doesn't break anything, because those properties were never used before anyway.
- Thanks to [@LaurenzV](https://github.com/LaurenzV). -- `usvg_tree::CharacterPosition` -- `usvg_tree::Path::text_bbox`. Use `usvg_tree::Text::bounding_box` instead. -- `usvg_text_layout::TextToPath` trait for `Text` nodes. - Only the whole tree can be converted at once. - -### Fixed -- Path object bounding box calculation. We were using point bounds instead of tight contour bounds. - Was broken since v0.34 -- Convert text-to-paths in embedded SVGs as well. The one inside the `Image` node. - Thanks to [@LaurenzV](https://github.com/LaurenzV). -- Indirect `text-decoration` resolving in some cases. - Thanks to [@LaurenzV](https://github.com/LaurenzV). -- (usvg) Clip paths writing to SVG. - Thanks to [@LaurenzV](https://github.com/LaurenzV). - -## [0.36.0] - 2023-10-01 -### Added -- `stroke-linejoin=miter-clip` support. SVG2. - Thanks to [@torokati44](https://github.com/torokati44). -- Quoted FuncIRI support. Like `fill="url('#gradient')"`. SVG2. - Thanks to [@romanzes](https://github.com/romanzes). -- Allow float values in `rgb()` and `rgba()` colors. SVG2. - Thanks to [@yisibl](https://github.com/yisibl). -- `auto-start-reverse` variant support to `orient` in markers. SVG2. - Thanks to [@EpicEricEE](https://github.com/EpicEricEE). - -### Changed -- Update dependencies. - -### Fixed -- Increase precision of the zero-scale transform check. - Was rejecting some valid transforms before. -- Panic when rendering a very specific text. -- Greatly improve parsing performance when an SVG has a lot of references. - Thanks to [@wez](https://github.com/wez). -- (Qt API) Fix scaling factor calculation. - Thanks to [@missdeer](https://github.com/missdeer). - -## [0.35.0] - 2023-06-27 -### Fixed -- Panic when an element is completely outside the viewbox. - -### Removed -- `FillPaint` and `StrokePaint` filter inputs support. - It's a mostly undocumented SVG feature that no one supports and no one uses. - And it was adding a significant complexity to the codebase. -- `usvg::filter::Filter::fill_paint` and `usvg::filter::Filter::stroke_paint`. -- `BackgroundImage`, `BackgroundAlpha`, `FillPaint` and `StrokePaint` from `usvg::filter::Input`. -- `usvg::Group::filter_fill_paint` and `usvg::Group::filter_stroke_paint`. - -## [0.34.1] - 2023-05-28 -### Fixed -- Transform components order. Affects only `usvg` SVG output and C API. - -## [0.34.0] - 2023-05-27 -### Changed -- `usvg` uses `tiny-skia` geometry primitives now, including the `Path` container.
- The main difference compared to the old `usvg` primitives - is that `tiny-skia` uses `f32` instead of `f64`. - So while in theory we could loose some precision, in practice, `f32` is used mainly - as a storage type and precise math operations are still done using `f64`.
- `tiny-skia` primitives are move robust, strict and have a nicer API.
- More importantly, this change reduces the peak memory usages for SVGs with large paths - (in terms of the number of segments). - And removes the need to convert `usvg::PathData` into `tiny-skia::Path` before rendering. - Which was just a useless reallocation. -- All numbers are stored as `f32` instead of `f64` now. -- Because we use `tiny-skia::Path` now, we allow _quadratic curves_ as well. - This includes `usvg` CLI output. -- Because we allow _quadratic curves_ now, text might render slightly differently (better?). - This is because TrueType fonts contain only _quadratic curves_ - and we were converting them to cubic before. -- `usvg::Path` no longer implements `Default`. Use `usvg::Path::new` instead. -- Replace `usvg::Rect` with `tiny_skia::NonZeroRect`. -- Replace `usvg::PathBbox` with `tiny_skia::Rect`. -- Unlike the old `usvg::PathBbox`, `tiny_skia::Rect` allows both width and height to be zero. - This is not an error. -- `usvg::filter::Turbulence::base_frequency` was split into `base_frequency_x` and `base_frequency_y`. -- `usvg::NodeExt::calculate_bbox` no longer includes stroke bbox. -- (c-api) Use `float` instead of `double` everywhere. -- The `svgfilters` crate was merged into `resvg`. -- The `rosvgtree` crate was merged into `usvg-parser`. -- `usvg::Group::filter_fill` moved to `usvg::filter::Filter::fill_paint`. -- `usvg::Group::filter_stroke` moved to `usvg::filter::Filter::stroke_paint`. - -### Remove -- `usvg::Point`. Use `tiny_skia::Point` instead. -- `usvg::FuzzyEq`. Use `usvg::ApproxEqUlps` instead. -- `usvg::FuzzyZero`. Use `usvg::ApproxZeroUlps` instead. -- (c-api) `resvg_path_bbox`. Use `resvg_rect` instead. -- `svgfilters` crate. -- `rosvgtree` crate. - -### Fixed -- Write `transform` on `clipPath` children in `usvg` SVG output. -- Do not duplicate marker children IDs. - Previously, each element resolved for a marker would preserve its ID. - Affects only `usvg` SVG output and doesn't affect rendering. - -## [0.33.0] - 2023-05-17 -### Added -- A new rendering algorithm.
- When rendering [isolated groups](https://razrfalcon.github.io/notes-on-svg-parsing/isolated-groups.html), - aka layers, we have to know the layer bounding box beforehand, which is ridiculously hard in SVG.
- Previously, resvg would simply use the canvas size for all the layers. - Meaning that to render a 10x10px layer on a 1000x1000px canvas, we would have to allocate and then blend - a 1000x1000px layer, which is just a waste of CPU cycles.
- The new rendering algorithm is able to calculate layer bounding boxes, which dramatically improves - performance when rendering a lot of tiny layers on a large canvas.
- Moreover, it makes performance more linear with a canvas size increase.
- The [paris-30k.svg](https://github.com/google/forma/blob/681e8bfd348caa61aab47437e7d857764c2ce522/assets/svgs/paris-30k.svg) - sample from [google/forma](https://github.com/google/forma) is rendered _115 times_ faster on M1 Pro now. - From ~33760ms down to ~290ms. 5269x3593px canvas.
- If we restrict the canvas to 1000x1000px, which would contain only the actual `paris-30k.svg` content, - then we're _13 times_ faster. From ~3252ms down to ~253ms. -- `resvg::Tree`, aka a render tree, which is an even simpler version of `usvg::Tree`. - `usvg::Tree` had to be converted into `resvg::Tree` before rendering now. - -### Changed -- Restructure the root directory. All crates are in the `crates` directory now. -- Restructure tests. New directory structure and naming scheme. -- Use `resvg::Tree::render` instead of `resvg::render`. -- resvg's `--export-area-drawing` option uses calculated bounds instead of trimming - excessive alpha now. It's faster, but can lead to a slightly different output. -- (c-api) Removed `fit_to` argument from `resvg_render`. -- (c-api) Removed `fit_to` argument from `resvg_render_node`. -- `usvg::ScreenSize` moved to `resvg`. -- `usvg::ScreenRect` moved to `resvg`. -- Rename `resvg::ScreenSize` into `resvg::IntSize`. -- Rename `resvg::ScreenRect` into `resvg::IntRect`. - -### Removed -- `filter` build feature from `resvg`. Filters are always enabled now. -- `resvg::FitTo` -- `usvg::utils::view_box_to_transform_with_clip` -- `usvg::Size::to_screen_size`. Use `resvg::IntSize::from_usvg` instead. -- `usvg::Rect::to_screen_size`. Use `resvg::IntSize::from_usvg(rect.size())` instead. -- `usvg::Rect::to_screen_rect`. Use `resvg::IntRect::from_usvg` instead. -- (c-api) `resvg_fit_to` -- (c-api) `resvg_fit_to_type` - -### Fixed -- Double quotes parsing in `font-family`. - -## [0.32.0] - 2023-04-23 -### Added -- Clipping and masking is up to 20% faster. -- `mask-type` property support. SVG2 -- `usvg_tree::MaskType` -- `usvg_tree::Mask::kind` -- (rosvgtree) New SVG 2 mask attributes. - -### Changed -- `BackgroundImage` and `BackgroundAlpha` filter inputs will produce the same output - as `SourceGraphic` and `SourceAlpha` respectively. - -### Removed -- `enable-background` support. This feature was never supported by browsers - and was deprecated in SVG 2. To my knowledge, only Batik has a good support of it. - Also, it's a performance nightmare, which caused multiple issues in resvg already. -- `usvg_tree::EnableBackground` -- `usvg_tree::Group::enable_background` -- `usvg_tree::NodeExt::filter_background_start_node` - -### Fixed -- Improve rectangular clipping anti-aliasing quality. -- Mask's RGB to Luminance converter was ignoring premultiplied alpha. - -## [0.31.1] - 2023-04-22 -### Fixed -- Use the latest `tiny-skia` to fix SVGs with large masks rendering. - -## [0.31.0] - 2023-04-10 -### Added -- `usvg::Tree::paint_servers` -- `usvg::Tree::clip_paths` -- `usvg::Tree::masks` -- `usvg::Tree::filters` -- `usvg::Node::subroots` -- (usvg) `--coordinates-precision` and `--transforms-precision` writing options. - Thanks to [@flxzt](https://github.com/flxzt). - -### Fixed -- `fill-opacity` and `stroke-opacity` resolving. -- Double `transform` when resolving `symbol`. -- `symbol` clipping when its viewbox is the same as the document one. -- (usvg) Deeply nested gradients, patterns, clip paths, masks and filters - were ignored during SVG writing. -- Missing text in nested clip paths and mask, text decoration patterns, filter inputs and feImage. - -## [0.30.0] - 2023-03-25 -### Added -- Readd `usvg` CLI tool. Can be installed via cargo as before. - -### Changed -- Extract most `usvg` internals into new `usvg-tree` and `usvg-parser` crates. - `usvg-tree` contains just the SVG tree and all the types. - `usvg-parser` parsers SVG into `usvg-tree`. - And `usvg` is just an umbrella crate now. -- To use `usvg::Tree::from*` methods one should import the `usvg::TreeParsing` trait now. -- No need to import `usvg-text-layout` manually anymore. It is part of `usvg` now. -- `rosvgtree` no longer reexports `svgtypes`. -- `rosvgtree::Node::attribute` returns just a string now. -- `rosvgtree::Node::find_attribute` returns just a `rosvgtree::Node` now. -- Rename `usvg::Stretch` into `usvg::FontStretch`. -- Rename `usvg::Style` into `usvg::FontStyle`. -- `usvg::FitTo` moved to `resvg::FitTo`. -- `usvg::IsDefault` trait is private now. - -### Removed -- `rosvgtree::FromValue`. Due to Rust's orphan rules this trait is pretty useless. - -### Fixed -- Recursive markers detection. -- Skip malformed `transform` attributes without skipping the whole element. -- Clipping path rectangle calculation for nested `svg` elements. - Thanks to [@LaurenzV](https://github.com/LaurenzV). -- Panic when applying `text-decoration` on text with only one cluster. - Thanks to [@LaurenzV](https://github.com/LaurenzV). -- (Qt API) Image size wasn't initialized. Thanks to [@missdeer](https://github.com/missdeer). -- `resvg` CLI allows files with XML DTD again. -- (svgtypes) Handle implicit MoveTo after ClosePath segments. - -## [0.29.0] - 2023-02-04 -### Added -- `resvg` CLI loads system fonts only when an input SVG has text nodes now. - Fonts loading is an IO-heavy operation and by avoiding it we can speed up `resvg` execution. -- `usvg::Group::should_isolate` -- `usvg::Tree::has_text_nodes` - -### Changed -- Some `usvg` internals were moved into the new `rosvgtree` crate. -- Dummy groups are no longer removed. Use `usvg::Group::should_isolate` to check - if a group affects rendering. -- `usvg-text-layout::TreeTextToPath::convert_text` no longer has the `keep_named_groups` argument. -- MSRV bumped to 1.65 -- Update dependencies. - -### Removed -- `usvg::Options::keep_named_groups`. Dummy groups are no longer removed. -- (c-api) `resvg_options_set_keep_named_groups` -- (Qt API) `ResvgOptions::setKeepNamedGroups` - -### Fixed -- Missing `font-family` handling. -- `font-weight` resolving. - -## [0.28.0] - 2022-12-03 -### Added -- `usvg::Text` and `usvg::NodeKind::Text`. - -### Changed -- `usvg` isn't converting text to paths by default now. A caller must call - `usvg::Tree::convert_text` or `usvg::Text::convert` from `usvg-text-layout` crate on demand. -- `usvg` text layout implementation moved into `usvg-text-layout` crate. -- During SVG size recovery, when no `width`, `height` and `viewBox` attributes have been set, - text nodes are no longer taken into an account. This is because a text node has no bbox - before conversion into path(s), which we no longer doing during parsing. -- `usvg` is purely an SVG parser now. It doesn't convert text to paths - and doesn't write SVG anymore. -- `usvg::filter::ConvolveMatrixData` methods are fields now. - -### Removed -- `usvg` CLI binary. No alternatives for now. -- All `usvg` build features. - - `filter`. Filter elements are always parsed by `usvg` now. - - `text`. Text elements are always parsed by `usvg` now. - - `export`. `usvg` cannot write an SVG anymore. -- `usvg::Tree::to_string`. `usvg` cannot write an SVG anymore. -- `usvg::TransformFromBBox` trait. This is just a regular `usvg::Transform` method now. -- `usvg::OptionsRef`. `usvg::Options` is enough from now. -- `usvg::Options::fontdb`. Used only by `usvg-text-layout` now. -- `--dump-svg` from `resvg`. - -## [0.27.0] - 2022-11-27 -### Added -- `lengthAdjust` and `textLength` attributes support. -- Support automatic `image` size detection. - `width` and `height` attributes can be omitted or set to `auto` on `image` now. SVG2 - -### Fixed -- `--query-all` flag in `resvg` CLI. -- Percentage values resolving. - -## [0.26.1] - 2022-11-21 -### Fixed -- Allow `dominant-baseline` and `alignment-baseline` to be set via CSS. - -## [0.26.0] - 2022-11-20 -### Added -- Minimal `dominant-baseline` and `alignment-baseline` support. -- `mix-blend-mode` and `isolation` support. SVG2 -- Allow writing resvg output to stdout. -- Allow disabling text kerning using `kerning="0"` and `style="font-kerning:none"`. SVG2 -- Allow `` values for `opacity`, `fill-opacity`, `stroke-opacity`, - `flood-opacity` and `stop-opacity` attributes.
- You can write `opacity="50%"` now. SVG2 - -### Changed -- Disable focal point correction on radial gradients to conform with SVG 2. SVG2 -- Update `feMorphology` radius value resolving. - -### Fixed -- Do not clip nested `svg` when only the `viewBox` attribute is present. - -## [0.25.0] - 2022-10-30 -### Added -- Partial `paint-order` attribute support. - Markers can only be under or above the shape. - -### Fixed -- Compilation issues caused by `rustybuzz` update. - -## [0.24.0] - 2022-10-22 -### Added -- CSS3 `writing-mode` variants `vertical-rl` and `vertical-lr`. - Thanks to [yisibl](https://github.com/yisibl). -- (tiny-skia) AArch64 Neon SIMD support. Up to 3x faster on Apple M1. - -### Changed -- `usvg::Tree` stores only `Group`, `Path` and `Image` nodes now. - Instead of emulating an SVG file structure, where gradients, patterns, filters, clips and masks - are part of the nodes tree (usually inside the `defs` element), we reference them using `Rc` - from now. - This change makes `usvg` a bit simpler. Makes `usvg` API way easier, since instead of - looking for a node via `usvg::Tree::defs_by_id` the caller can access the type directly via `Rc`. - And makes creation of custom `usvg::Tree`s way easier. -- `clip_path`, `mask` and `filters` `usvg::Group` fields store `Rc` instead of `String` now. -- `usvg::NodeExt::units` was moved to `usvg::Paint::units`. -- `usvg::filter::ImageKind::Use` stores `usvg::Node` instead of `String`. -- `usvg::PathData` stores commands and points separately now to reduce overall memory usage. -- `usvg::PathData` segments should be accessed via `segments()` now. -- Most numeric types have been moved to the `strict-num` crate. -- Rename `NormalizedValue` into `NormalizedF64`. -- Rename `PositiveNumber` into `PositiveF64`. -- Raw number of numeric types should be accessed via `get()` method instead of `value()` now. -- `usvg::TextSpan::font_size` is `NonZeroPositiveF64` instead of `f64` now. -- Re-export `usvg` and `tiny-skia` dependencies in `resvg`. -- Re-export `roxmltree` dependency in `usvg`. -- (usvg) Output float precision is reduced from 11 to 8 digits. - -### Removed -- `usvg::Tree::create`. `usvg::Tree` is an open struct now. -- `usvg::Tree::root`. It's a public field now. -- `usvg::Tree::svg_node`. Replaced with `usvg::Tree` public fields. -- `defs`, `is_in_defs`, `append_to_defs` and `defs_by_id` from `usvg::Tree`. - We no longer emulate SVG structure. No alternative. -- `usvg::Tree::is_in_defs`. There are no `defs` anymore. -- `usvg::Paint::Link`. We store gradient and patterns directly in `usvg::Paint` now. -- `usvg::Svg`. No longer needed. `size` and `view_box` are `usvg::Tree` fields now. -- `usvg::SubPathIter` and `usvg::PathData::subpaths`. No longer used. - -### Fixed -- Path bbox calculation scales stroke width too. - Thanks to [growler](https://github.com/growler). -- (tiny-skia) Round caps roundness. -- (xmlparser) Stack overflow on specific files. -- (c-api) `resvg_is_image_empty` output was inverted. - -## [0.23.0] - 2022-06-11 -### Added -- `#RRGGBBAA` and `#RGBA` color notation support. - Thanks to [demurgos](https://github.com/demurgos). - -### Fixed -- Panic during recursive `pattern` resolving. - Thanks to [FylmTM](https://github.com/FylmTM). -- Spurious warning when using `--export-id`. - Thanks to [benoit-pierre](https://github.com/benoit-pierre). - -## [0.22.0] - 2022-02-20 -### Added -- Support `svg` referenced by `use`. External SVG files are still not supported. - -### Changed -- `ttf-parser`, `fontdb` and `rustybuzz` have been updated. - -## [0.21.0] - 2022-02-13 -### Added -- `usvg::ImageHrefResolver` that allows a custom `xlink:href` handling. - Thanks to [antmelnyk](https://github.com/antmelnyk). -- `usvg::Options::image_href_resolver` -- Support for GIF images inside the `` element. -- (fontdb) Support for loading user fonts on Windows. -- (fontdb) Support for parsing fontconfig config files on Linux. - For now, only to retrieve a list of font dirs. - -### Changed -- MSRV bumped to 1.51 -- `usvg::ImageKind` stores data as `Arc>` and not just `Vec` now. - -### Fixed -- Every nested `svg` element defines a new viewBox now. Previously, we were always using the root one. -- Correctly handle SVG size calculation when SVG doesn't have a size and any elements. -- Improve groups ungrouping speed. - -## [0.20.0] - 2021-12-29 -### Changed -- `resvg::render` and `resvg::render_node` accept a transform now. -- (c-api) `resvg_render` and `resvg_render_node` accept a transform now. -- `usvg::Color` is a custom type and not a `svgtypes::Color` reexport now. -- `usvg::Color` doesn't contain alpha anymore, which have been added in v0.16 - Alpha would be automatically flattened. - This makes [Micro SVG](https://github.com/RazrFalcon/resvg/blob/master/docs/usvg_spec.adoc) - compatible with SVG 1.1 again. -- (c-api) Rename `RESVG_FIT_TO_*` into `RESVG_FIT_TO_TYPE_*`. - -### Fixed -- The `--background` argument in `resvg` correctly handles alpha now. -- Fix building usvg without filter feature but with export. - -## [0.19.0] - 2021-10-04 -### Added -- Better text-on-path converter accuracy by accounting the current transform. - -### Changed -- `usvg::NodeExt::abs_transform` includes current node transform now. -- Improved turbulence filter performance. Thanks to [akindle](https://github.com/akindle). -- Multiple dependencies updated. - -## [0.18.0] - 2021-09-12 -### Added -- `filter` build feature. Enabled by default. -- `usvg::PathBbox` and `resvg_path_bbox` (to C API). - -### Changed -- (usvg) All filter related types are under the `filter` module now. -- (usvg) Remove `Fe` prefix from all filter types. -- (c-api) `resvg_get_node_bbox` returns `resvg_path_bbox` now. - -### Fixed -- Horizontal and vertical lines processing. -- C API building without the `text` feature. - -## [0.17.0] - 2021-09-04 -### Added -- `tiny-skia` updated with support of images larger than 8000x8000 pixels. -- `feDropShadow` support. SVG2 -- [``](https://www.w3.org/TR/filter-effects-1/#typedef-filter-value-list) support. - Meaning that the `filter` attribute can have multiple values now. - Like `url(#filter1) blur(2)`. SVG2 -- All [filter functions](https://www.w3.org/TR/filter-effects-1/#filter-functions). SVG2 -- Support all [new](https://www.w3.org/TR/compositing-1/#ltblendmodegt) `feBlend` modes. SVG2 -- Automatic SVG size detection when `width`/`height`/`viewBox` is not set. - Thanks to [reknih](https://github.com/reknih). -- `usvg::Options::default_size` -- `--default-width` and `--default-height` to usvg. - -### Changed -- `usvg::Group::filter` is a list of filter IDs now. -- `usvg::FeColorMatrixKind::Saturate` accepts any positive `f64` value now. -- `svgfilters::ColorMatrix::Saturate` accepts any positive `f64` value now. -- Fonts memory mapping was split into a separate build feature: `memmap-fonts`. - Now you can build resvg/usvg with `system-fonts`, but without `memmap-fonts`. - Enabled by default. -- The `--dump-svg` argument in resvg CLI tool should be enabled using `--features dump-svg` now. - No enabled by default. -- `usvg::Tree::to_string` is behind the `export` build feature now. - -### Fixed -- When writing SVG, `usvg` will use `rgba()` notations for colors instead of `#RRGGBB`. - -## [0.16.0] - 2021-08-22 -### Added -- CSS3 colors support. Specifically `rgba`, `hsl`, `hsla` and `transparent`. SVG2 -- Allow missing `rx`/`ry` attributes on `ellipse`. SVG2 -- Allow markers on all shapes. SVG2 -- `textPath` can reference basic shapes now. SVG2 -- `usvg::OptionsRef`, which is a non-owned `usvg::Options` variant. -- `simplecss` updated with CSS specificity support. -- `turn` angle unit support. SVG2 -- Basic `font-variant=small-caps` support. No font fallback. -- `--export-area-page` to resvg. -- `--export-area-drawing` to resvg. - -### Changed -- `resvg::render_node` requires `usvg::Tree` now. -- `usvg::Color` gained an `alpha` field. - -### Removed -- `usvg::Node::tree`. Cannot be implemented efficiently anymore. -- `usvg::SystemFontDB`. No longer needed. - -### Fixed -- `pattern` scaling. -- Greatly improve `symbol` resolving speed in `usvg`. -- Whitespaces trimming on nested `tspan`. - -## [0.15.0] - 2021-06-13 -### Added -- Allow reading SVG from stdin in `resvg` binary. -- `--id-prefix` to `usvg`. -- `FitTo::Size` -- `resvg` binary accepts `--width` and `--height` args together now. - Previously, only `--width` or `--height` were allowed. -- `usvg::Path::text_bbox` -- The maximum number of SVG elements is limited by 1_000_000 now. - Mainly to prevent a billion laugh style attacks. -- The maximum SVG elements nesting is limited by 1024 now. -- `usvg::Error::ElementsLimitReached` - -### Changed -- Improve clipping and masking performance on large images. -- Remove layers caching. This was a pointless optimization. -- Split _Preprocessing_ into _Reading_ and _Parsing_ in `resvg --perf`. -- `usvg::XmlOptions` rewritten. -- `usvg::Tree::to_string` requires a reference to `XmlOptions` now. - -### Removed -- `usvg::Tree::from_file`. Use `from_data` or `from_str` instead. -- `usvg::Error::InvalidFileSuffix` -- `usvg::Error::FileOpenFailed` -- (c-api) `RESVG_ERROR_INVALID_FILE_SUFFIX` - -### Fixed -- Ignore tiny blur values. It could lead to a transparent image. -- `use` style propagation when used with `symbol`. -- Vertical text layout with relative offsets. -- Text bbox calculation. `usvg` uses font metrics instead of path bbox now. - -## [0.14.1] - 2021-04-18 -### Added -- Allow `href` without the `xlink` namespace. - This feature is part of SVG 2 (which we do not support), - but there are more and more files like this in the wild. - -### Changed -- (usvg) Do not write `usvg:version` to the output SVG. - -### Fixed -- (usvg) `overflow='inherit'` resolving. -- (usvg) SVG Path length calculation that affects `startOffset` property in `textPath`. -- (usvg) Fix `feImage` resolving when the linked element has - `opacity`, `clip-path`, `mask` and/or `filter` attributes. -- (usvg) Fix chained `feImage` resolving. -- CLI arguments processing. - -## [0.14.0] - 2021-03-06 -### Fixed -- Multiple critical bugs in `tiny-skia`. - -## [0.13.1] - 2021-01-20 -### Fixed -- `image` with float size scaling. -- Critical bug in `tiny-skia`. - -## [0.13.0] - 2020-12-21 -### Added -- `--resources-dir` option to CLI tools. -- (usvg) `Tree::from_xmltree` - -### Changed -- Remove the `Image` struct. `render()` and `render_node()` methods now accept `tiny_skia::PixmapMut`. -- Update `fontdb`. -- Update `tiny-skia`. -- (c-api) `resvg_size` uses `double` instead of `uint32_t` now. -- (qt-api) `defaultSize()` and `defaultSizeF()` methods now return SVG size - and not SVG viewbox size. -- (usvg) `Options::path` changed to `Options::resources_dir` and requires a directory now. -- (c-api) `resvg_options_set_file_path` changed to `resvg_options_set_resources_dir` - and requires a directory now. -- (qt-api) `ResvgOptions::setFilePath` changed to `ResvgOptions::setResourcesDir` - and requires a directory now. - -### Fixed -- Support multiple values inside a `text-decoration` attribute. - -### Removed -- `Image`. Use `tiny_skia::PixmapMut` instead. -- (c-api) `resvg_image` struct and `resvg_image_*` methods. `resvg` renders onto - the provided buffer now. -- (c-api) `resvg_color`, because unused. - -## [0.12.0] - 2020-12-05 -### Changed -- resvg no longer requires a C++ compiler! -- `tiny-skia` was updated to a pure Rust version, which means that `resvg` no longer - depends on `clang` and should work on 32bit targets. -- `rustybuzz` was updated to a pure Rust version. -- `tools/explorer-thumbnailer` is back and written in Rust now. - Thanks to [gentoo90](https://github.com/gentoo90). - -### Fixed -- (usvg) Do not panic when a font has a zero-sized underline thickness. -- (usvg) Multiple `textPath` processing fixes by [chubei-oppen](https://github.com/chubei-oppen). -- (qt-api) `boundsOnElement` and `boundingBox` were returning transposed bounds. - -## [0.11.0] - 2020-07-04 -### Highlights -- All backends except Skia were removed. Skia is the only official one from now. -- New C API implementation. - -### Added -- Support for user-defined fonts in usvg, resvg and C API. -- `--serif-family`, `--sans-serif-family`, `--cursive-family`, `--fantasy-family` - `--monospace-family`, `--use-font-file`, `--use-fonts-dir`, `--skip-system-fonts` and `--list-fonts` - options to all CLI tools. -- New tests suite. Instead of testing against the previous build, now we're testing against - prerendered PNG images. Which is way faster.
- And you can test resvg without the internet connection now.
- And all you need is just `cargo test`. - -### Changed -- Library uses an embedded Skia by default now. -- Switch `harfbuzz_rs` with `rustybuzz`. -- Rendering doesn't require `usvg::Options` now. -- (usvg) The `fontdb` module moved into its own crate. -- (usvg) `fontconfig` is no longer used for matching - [generic fonts](https://www.w3.org/TR/2018/REC-css-fonts-3-20180920/#generic-family-value) - on Linux. Mainly because it's very slow. -- (usvg) When an `image` element contains a file path, the file will be loaded into memory now, - instead of simply storing a file path. And will be dumped as base64 on SVG save. - In case of an SVG image, it will be loaded as a `Tree` and saved as base64 encoded XML on save. -- (usvg) `ImageData` replaced with `ImageKind`. -- (usvg) Fonts database is empty by default now and should be filled manually. -- (c-api) Almost a complete rewrite. - -### Removed -- All backends except the Skia one. -- `Options` from all backends. We don't use it anymore. -- (usvg) `ImageFormat`. -- (c-api) Rendering on a backends canvas no longer supported. Was constantly misused. - -## [0.10.0] - 2020-06-19 - -### Changed -- The `resvg` crate has been split into four: resvg-cairo, resvg-qt, resvg-skia and resvg-raqote.
- So from now, instead of enabling a required backend via cargo features, - you should select a required backend-specific crate.
- This allows us to have a better integration with a selected 2D library.
- And we also have separated C API implementations now.
- And each backend has its own vendored archive too. -- (qt-backend) Use `QImage` instead of Rust libraries for raster images loading. - -### Removed -- The `resvg` crate. Use backend-specific crates. -- `tools/rendersvg`. Each backend has its own CLI tool now. -- `tools/usvg`. `usvg` implements CLI by default now. -- (c-api) `resvg_*_render_to_file` methods. -- (qt-backend) `jpeg-decoder` and `png` dependencies. - -## [0.9.1] - 2020-06-03 -### Fixed -- Stack overflow when `enable-background` and `filter` are set on the same element. -- Grayscale PNG loading. -- Allow building on BSD. -- (usvg) Font fallback when shaping produces a different amount of glyphs. -- (usvg) Ignore a space after the last character during `letter-spacing` processing. -- (usvg) `marker-end` rendering when the last segment is a curve with the second control point - that coincides with end point. -- (usvg) Accept embedded `image` data without mime. -- (usvg) Fonts search in a home directory on Linux. -- (usvg) `dy` calculation for `textPath` thanks to [Stoeoef](https://github.com/Stoeoef) -- (usvg) `textPath` resolving when a referenced path has a transform.
- Thanks to [Stoeoef](https://github.com/Stoeoef). -- (usvg) Load user fonts on macOS too. -- (xmlparser) Parsing comment before DTD. - -## [0.9.0] - 2020-01-18 -### Added -- `feConvolveMatrix`, `feMorphology`, `feDisplacementMap`, `feTurbulence`, - `feDiffuseLighting` and `feSpecularLighting` support. -- `BackgroundImage`, `BackgroundAlpha`, `FillPaint` and `StrokePaint` support as a filter input. -- Load grayscale raster images. -- `enable-background` support. -- resvg/usvg can be built without text rendering support now. -- `OutputImage::make_vec` and `OutputImage::make_rgba_vec`. -- `feImage` with a reference to an internal element. - -### Changed -- `feComposite` k1-4 coefficients can have any number now. - This matches browsers behaviour. -- Use `flate2` instead of `libflate` for GZip decoding. -- (usvg) `fill` and `stroke` attributes will always be set for `path` now. -- (usvg) `g`, `path` and `image` can now be set inside `defs`. Required by `feImage`. -- (c-api) Rename `resvg_*_render_to_image` into `resvg_*_render_to_file`. - -### Fixed -- (usvg) Transform processing during text-to-path conversion. -- `feComposite` with fully transparent region was producing an invalid result. -- Fallback to `matrix` in `feColorMatrix` when `type` is not set or invalid. -- ID preserving for `use` elements. -- `feFlood` with subregion and `primitiveUnits=objectBoundingBox`. -- (harfbuzz_rs) Memory leak. - -## [0.8.0] - 2019-08-17 -### Added -- A [Skia](https://skia.org/) backend thanks to - [JaFenix](https://github.com/JaFenix). -- `feComponentTransfer` support. -- `feColorMatrix` support. -- A better CSS support. -- An `*.otf` fonts support. -- (usvg) `dx`, `dy` are supported inside `textPath` now. -- Use a box blur for `feGaussianBlur` with `stdDeviation`>=2. - This is 4-8 times faster than IIR blur. - Thanks to [Shnatsel](https://github.com/Shnatsel). - -### Changed -- All backends are using Rust crates for raster images loading now. -- Use `pico-args` instead of `gumdrop` to reduced the build time of `tools/rendersvg` - and `tools/usvg`. -- (usvg) The `xmlwriter` is used for SVG generation now. - Almost 2x faster than generating an `svgdom`. -- (usvg) Optimize font database initialization. Almost 50% faster. -- Use a lower PNG compression ratio to speed up PNG generation. - Depending on a backend and image can be 2-4x faster. -- `OutputImage::save` -> `OutputImage::save_png`. -- (usvg) `Path::segments` -> `Path::data`. -- Cairo backend compilation is 2x faster now due to overall changes. -- Performance improvements (Oxygen Icon theme SVG-to-PNG): - - cairo-backend: 22% faster - - qt-backend: 20% faster - - raqote-backend: 34% faster - -### Fixed -- (qt-api) A default font resolving. -- (usvg) `baseline-shift` processing inside `textPath`. -- (usvg) Remove all `tref` element children. -- (usvg) `tref` with `xml:space` resolving. -- (usvg) Ignore nested `tref`. -- (usvg) Ignore invalid `clipPath` children that were referenced via `use`. -- (usvg) `currentColor` will always fallback to black now. - Previously, `stroke` was set to `none` which is incorrect. -- (usvg) `use` can reference an element inside a non-SVG element now. -- (usvg) Collect all styles for generic fonts and not only *Regular*. -- (usvg) Parse only presentation attributes from the `style` element and attribute. - -### Removed -- (cairo-backend) `gdk-pixbuf` dependency. -- (qt-backend) JPEG image format plugin dependency. -- `svgdom` dependency. - -## [0.7.0] - 2019-06-19 -### Added -- New text layout implementation: - - `textPath` support. - - `writing-mode` support, aka vertical text. - - [Text BIDI reordering](http://www.unicode.org/reports/tr9/). - - Better text shaping. - - `word-spacing` is supported for all backends now. - - [`harfbuzz`](https://github.com/harfbuzz/harfbuzz) dependency. - - Subscript, superscript offsets are extracted from font and not hardcoded now. -- `shape-rendering`, `text-rendering` and `image-rendering` support. -- The `arithmetic` operator for `feComposite`. -- (usvg) `--quiet` argument. -- (c-api) `resvg_get_image_bbox`. -- (qt-api) `ResvgRenderer::boundingBox`. -- (resvg) A [raqote](https://github.com/jrmuizel/raqote) backend thanks to - [jrmuizel](https://github.com/jrmuizel). Still experimental. - -### Changed -- Text will be converted into paths on the `usvg` side now. -- (resvg) Do not rescale images before rendering. This is faster and better. -- (usvg) An `image` element with a zero or negative size will be skipped now. - Previously, a linked image size was used, which is incorrect. -- Geometry primitives (`Rect`, `Size`, etc) are immutable and always valid now. -- (usvg) The default `color-interpolation-filters` attribute will not be exported now. - -### Removed -- (usvg) All text related structures and enums. Text will be converted into `Path` now. -- `InitObject` and `init()` because they are no longer needed. -- (c-api) `resvg_handle`, `resvg_init`, `resvg_destroy`. -- (c-api) `resvg_cairo_get_node_bbox` and `resvg_qt_get_node_bbox`. - Use backend-independent `resvg_get_node_bbox` instead. -- (cairo-backend) `pango` dependency. -- (resvg) `Backend::calc_node_bbox`. Use `Node::calculate_bbox()` instead. - -### Fixed -- `letter-spacing` on cursive scripts (like Arabic). -- (rctree) Prevent stack overflow on a huge, deeply nested SVG. -- (c-api) `resvg_is_image_empty` was always returning `false`. -- (resvg) Panic when `filter` with `objectBoudningBox` was set on an empty group. -- (usvg) `mask` with `objectBoundingBox` resolving. -- (usvg) `pattern`'s `viewBox` attribute resolving via `href`. -- (roxmltree) Namespace resolving. - -## [0.6.1] - 2019-03-16 -### Fixed -- (usvg) `transform` multiplication. -- (usvg) `use` inside `clipPath` resolving. - -## [0.6.0] - 2019-03-16 -### Added -- Nested `baseline-shift` support. -- (qt-api) `renderToImage`. -- (usvg) A better algorithm for unused defs (`defs` element children, like gradients) removal. -- (usvg) `Error::InvalidSize`. -- (c-api) `RESVG_ERROR_INVALID_SIZE`. - -### Changed -- (usvg) A major rewrite. -- `baseline-shift` with `sub`, `super` and percent values calculation. -- Marker resolving moved completely to `usvg`. -- If an SVG doesn't have a valid size than an error will occur. - Previously, an empty tree was produced. -- (qt-api) `render` methods are `const` now. -- (usvg) Disable default attributes exporting. - -### Removed -- (usvg) Marker element and attributes. Markers will be resolved just like `use` now. - -### Fixed -- (resvg) During the `tspan` rendering, the `text` bbox will be used instead - of the `tspan` bbox itself. This is the correct behaviour by the SVG spec. -- (cairo-backend) `font-family` parsing. -- (usvg) `filter:none` processing. -- (usvg) `text` inside `text` processing. -- (usvg) Endless loop during `use` resolving. -- (usvg) Endless loop when SVG has indirect recursive `xlink:href` links. -- (usvg) Endless loop when SVG has recursive `marker-*` links. -- (usvg) Panic during `use` resolving. -- (usvg) Panic during inherited attributes resolving. -- (usvg) Groups regrouping. -- (usvg) `dx`/`dy` processing on `text`. -- (usvg) `textAnchor` resolving. -- (usvg) Ignore `fill-rule` on `text`. -- (svgtypes) Style with comments parsing. -- (roxmltree) Namespaces resolving. - -## [0.5.0] - 2019-01-04 -### Added -- `marker` support. -- Partial `baseline-shift` support. -- `letter-spacing` support. -- (qt-backend) `word-spacing` support. - Does not work on the cairo backend. -- tools/explorer-thumbnailer -- tools/kde-dolphin-thumbnailer - -### Fixed -- Object bounding box calculation. -- Pattern scaling. -- Nested `objectBoundigBox` support. -- (usvg) `color` on `use` resolving. -- (usvg) `offset` attribute resolving inside the `stop` element. -- (usvg) Ungrouping of groups with non-inheritable attributes. -- (usvg) `rotate` attribute resolving. -- (usvg) Paths without stroke and fill will no longer be removed. - Required for a proper bbox resolving. -- (usvg) Coordinates resolving when units are `userSpaceOnUse`. -- (usvg) Groups regrouping. Caused an incorrect rendering of `clipPath` - that had `filter` on a child. -- (usvg) Style attributes resolving on the root `svg` element. -- (usvg) `SmoothCurveTo` and `SmoothQuadratic` conversion. -- (usvg) `symbol` resolving. -- (cairo-backend) Font ascent calculation. -- (qt-backend) Stroking of LineTo specified as CurveTo. -- (svgdom) `stroke-miterlimit` attribute parsing. -- (svgdom) `length` and `number` attribute types parsing. -- (svgdom) `offset` attribute parsing. -- (svgdom) IRI resolving order when SVG has duplicated ID's. - -## [0.4.0] - 2018-12-13 -### Added -- (resvg) Initial filters support. -- (resvg) Nested `clipPath` and `mask` support. -- (resvg) MSVC support. -- (rendersvg) `font-family`, `font-size` and `languages` to args. -- (usvg) `systemLanguage` attribute support. -- (usvg) Default font family and size is configurable now. -- (c-api) `RESVG_ERROR_PARSING_FAILED`. -- (c-api) `font_family`, `font_size` and `languages` to `resvg_options`. -- (qt-api) `ResvgRenderer::setDevicePixelRatio`. - -### Changed -- (rendersvg) Use `gumdrop` instead of `getopts`. -- (c-api) Qt wrapper is header-only now. - -### Fixed -- (cairo-backend) Text layout. -- (cairo-backend) Rendering of a zero length subpath with a square cap. -- (qt-backend) Transform retrieving via Qt bindings. -- (resvg) Recursive SVG images via `image` tag. -- (resvg) Bbox calculation of the text with rotate. -- (resvg) Invisible elements processing. -- (qt-api) SVG from QByteArray loading when data is invalid. -- (usvg) `display` attribute processing. -- (usvg) Recursive `mask` resolving. -- (usvg) `inherit` attribute value resolving. -- (svgdom) XML namespaces resolving. - -### Removed -- (rendersvg) `failure` dependency. - -## [0.3.0] - 2018-05-23 -### Added -- (c-api) `resvg_is_image_empty`. -- (c-api) `resvg_error` enum. -- (c-api) Qt wrapper. -- (resvg) Advanced text layout support (lists of x, y, dx, dy and rotate). -- (resvg) SVG support for `image` element. -- (usvg) `symbol` element support. -- (usvg) Nested `svg` elements support. -- (usvg) Paint fallback resolving. -- (usvg) Bbox validation for shapes that use painting servers. -- (svgdom) Elements from ENTITY resolving. - -### Changed -- (c-api) `resvg_parse_tree_from_file`, `resvg_parse_tree_from_data` - `resvg_cairo_render_to_image` and `resvg_qt_render_to_image` - will return an error code now. -- (cairo-backend) Use `gdk-pixbuf` crate instead of `image`. -- (resvg) `Render::render_to_image` and `Render::render_node_to_image` will return - `Option` and not `Result` now. -- (resvg) New geometry primitives implementation. -- (resvg) Rename `render_*` modules to `backend_`. -- (rendersvg) Use `getopts` instead of `clap` to reduce the executable size. -- (svgtypes) `StreamExt::parse_iri` and `StreamExt::parse_func_iri` will parse - not only well-formed data now. - -### Fixed -- (qt-backend) Gradient with `objectBoundingBox` rendering. -- (qt-backend) Text bounding box detection during the rendering. -- (cairo-backend) `image` element clipping. -- (cairo-backend) Layers management. -- (c-api) `resvg_get_node_transform` will return a correct transform now. -- (resvg) `text-decoration` thickness. -- (resvg) `pattern` scaling. -- (resvg) `image` without size rendering. -- (usvg) Panic during `visibility` resolving. -- (usvg) Gradients with one stop resolving. -- (usvg) `use` attributes resolving. -- (usvg) `clipPath` and `mask` attributes resolving. -- (usvg) `offset` attribute in `stop` element resolving. -- (usvg) Incorrect `font-size` attribute resolving. -- (usvg) Gradient stops resolving. -- (usvg) `switch` element resolving. -- (svgdom) Mixed `xml:space` processing. -- (svgtypes) `Paint::from_span` poor performance. - -### Removed -- (c-api) `resvg_error_msg_destroy`. -- (resvg) `parse_rtree_*` methods. Use `usvg::Tree::from_` instead. -- (resvg) `Error`. - -## [0.2.0] - 2018-04-24 -### Added -- (svg) Partial `clipPath` support. -- (svg) Partial `mask` support. -- (svg) Partial `pattern` support. -- (svg) `preserveAspectRatio` support. -- (svg) Check that an external image is PNG or JPEG. -- (rendersvg) Added `--query-all` and `--export-id` arguments to render SVG items by ID. -- (rendersvg) Added `--perf` argument for a simple performance stats. - -### Changed -- (resvg) API is completely new. - -### Fixed -- `font-size` attribute inheritance during `use` resolving. - -[Unreleased]: https://github.com/RazrFalcon/resvg/compare/v0.43.0...HEAD -[0.43.0]: https://github.com/RazrFalcon/resvg/compare/v0.42.0...v0.43.0 -[0.42.0]: https://github.com/RazrFalcon/resvg/compare/v0.41.0...v0.42.0 -[0.41.0]: https://github.com/RazrFalcon/resvg/compare/v0.40.0...v0.41.0 -[0.40.0]: https://github.com/RazrFalcon/resvg/compare/v0.39.0...v0.40.0 -[0.39.0]: https://github.com/RazrFalcon/resvg/compare/v0.38.0...v0.39.0 -[0.38.0]: https://github.com/RazrFalcon/resvg/compare/v0.37.0...v0.38.0 -[0.37.0]: https://github.com/RazrFalcon/resvg/compare/v0.36.0...v0.37.0 -[0.36.0]: https://github.com/RazrFalcon/resvg/compare/v0.35.0...v0.36.0 -[0.35.0]: https://github.com/RazrFalcon/resvg/compare/v0.34.1...v0.35.0 -[0.34.1]: https://github.com/RazrFalcon/resvg/compare/v0.34.0...v0.34.1 -[0.34.0]: https://github.com/RazrFalcon/resvg/compare/v0.33.0...v0.34.0 -[0.33.0]: https://github.com/RazrFalcon/resvg/compare/v0.32.0...v0.33.0 -[0.32.0]: https://github.com/RazrFalcon/resvg/compare/v0.31.1...v0.32.0 -[0.31.1]: https://github.com/RazrFalcon/resvg/compare/v0.31.0...v0.31.1 -[0.31.0]: https://github.com/RazrFalcon/resvg/compare/v0.30.0...v0.31.0 -[0.30.0]: https://github.com/RazrFalcon/resvg/compare/v0.29.0...v0.30.0 -[0.29.0]: https://github.com/RazrFalcon/resvg/compare/v0.28.0...v0.29.0 -[0.28.0]: https://github.com/RazrFalcon/resvg/compare/v0.27.0...v0.28.0 -[0.27.0]: https://github.com/RazrFalcon/resvg/compare/v0.26.1...v0.27.0 -[0.26.1]: https://github.com/RazrFalcon/resvg/compare/v0.26.0...v0.26.1 -[0.26.0]: https://github.com/RazrFalcon/resvg/compare/v0.25.0...v0.26.0 -[0.25.0]: https://github.com/RazrFalcon/resvg/compare/v0.24.0...v0.25.0 -[0.24.0]: https://github.com/RazrFalcon/resvg/compare/v0.23.0...v0.24.0 -[0.23.0]: https://github.com/RazrFalcon/resvg/compare/v0.22.0...v0.23.0 -[0.22.0]: https://github.com/RazrFalcon/resvg/compare/v0.21.0...v0.22.0 -[0.21.0]: https://github.com/RazrFalcon/resvg/compare/v0.20.0...v0.21.0 -[0.20.0]: https://github.com/RazrFalcon/resvg/compare/v0.19.0...v0.20.0 -[0.19.0]: https://github.com/RazrFalcon/resvg/compare/v0.18.0...v0.19.0 -[0.18.0]: https://github.com/RazrFalcon/resvg/compare/v0.17.0...v0.18.0 -[0.17.0]: https://github.com/RazrFalcon/resvg/compare/v0.16.0...v0.17.0 -[0.16.0]: https://github.com/RazrFalcon/resvg/compare/v0.15.0...v0.16.0 -[0.15.0]: https://github.com/RazrFalcon/resvg/compare/v0.14.1...v0.15.0 -[0.14.1]: https://github.com/RazrFalcon/resvg/compare/v0.14.0...v0.14.1 -[0.14.0]: https://github.com/RazrFalcon/resvg/compare/v0.13.1...v0.14.0 -[0.13.1]: https://github.com/RazrFalcon/resvg/compare/v0.13.0...v0.13.1 -[0.13.0]: https://github.com/RazrFalcon/resvg/compare/v0.12.0...v0.13.0 -[0.12.0]: https://github.com/RazrFalcon/resvg/compare/v0.11.0...v0.12.0 -[0.11.0]: https://github.com/RazrFalcon/resvg/compare/v0.10.0...v0.11.0 -[0.10.0]: https://github.com/RazrFalcon/resvg/compare/v0.9.1...v0.10.0 -[0.9.1]: https://github.com/RazrFalcon/resvg/compare/v0.9.0...v0.9.1 -[0.9.0]: https://github.com/RazrFalcon/resvg/compare/v0.8.0...v0.9.0 -[0.8.0]: https://github.com/RazrFalcon/resvg/compare/v0.7.0...v0.8.0 -[0.7.0]: https://github.com/RazrFalcon/resvg/compare/v0.6.1...v0.7.0 -[0.6.1]: https://github.com/RazrFalcon/resvg/compare/v0.6.0...v0.6.1 -[0.6.0]: https://github.com/RazrFalcon/resvg/compare/v0.5.0...v0.6.0 -[0.5.0]: https://github.com/RazrFalcon/resvg/compare/v0.4.0...v0.5.0 -[0.4.0]: https://github.com/RazrFalcon/resvg/compare/v0.3.0...v0.4.0 -[0.3.0]: https://github.com/RazrFalcon/resvg/compare/v0.2.0...v0.3.0 -[0.2.0]: https://github.com/RazrFalcon/resvg/compare/v0.1.0...v0.2.0 diff --git a/resvg-0.43.0/Cargo.lock b/resvg-0.43.0/Cargo.lock deleted file mode 100644 index e36587c..0000000 --- a/resvg-0.43.0/Cargo.lock +++ /dev/null @@ -1,497 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "arrayref" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" - -[[package]] -name = "arrayvec" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" - -[[package]] -name = "bytemuck" -version = "1.16.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "102087e286b4677862ea56cf8fc58bb2cdfa8725c40ffb80fe3a008eb7f2fc83" - -[[package]] -name = "byteorder-lite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "color_quant" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" - -[[package]] -name = "core_maths" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b02505ccb8c50b0aa21ace0fc08c3e53adebd4e58caa18a36152803c7709a3" -dependencies = [ - "libm", -] - -[[package]] -name = "crc32fast" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "data-url" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" - -[[package]] -name = "fdeflate" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" -dependencies = [ - "simd-adler32", -] - -[[package]] -name = "flate2" -version = "1.0.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f211bbe8e69bbd0cfdea405084f128ae8b4aaa6b0b522fc8f2b009084797920" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "float-cmp" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" - -[[package]] -name = "fontconfig-parser" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fcfcd44ca6e90c921fee9fa665d530b21ef1327a4c1a6c5250ea44b776ada7" -dependencies = [ - "roxmltree", -] - -[[package]] -name = "fontdb" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37be9fc20d966be438cd57a45767f73349477fb0f85ce86e000557f787298afb" -dependencies = [ - "fontconfig-parser", - "log", - "memmap2", - "slotmap", - "tinyvec", - "ttf-parser", -] - -[[package]] -name = "gif" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" -dependencies = [ - "color_quant", - "weezl", -] - -[[package]] -name = "image-webp" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79afb8cbee2ef20f59ccd477a218c12a93943d075b492015ecb1bb81f8ee904" -dependencies = [ - "byteorder-lite", - "quick-error", -] - -[[package]] -name = "imagesize" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" - -[[package]] -name = "kurbo" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e5aa9f0f96a938266bdb12928a67169e8d22c6a786fda8ed984b85e6ba93c3c" -dependencies = [ - "arrayvec", - "smallvec", -] - -[[package]] -name = "libc" -version = "0.2.155" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" - -[[package]] -name = "libm" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" - -[[package]] -name = "log" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" - -[[package]] -name = "memmap2" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" -dependencies = [ - "libc", -] - -[[package]] -name = "miniz_oxide" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" -dependencies = [ - "adler", - "simd-adler32", -] - -[[package]] -name = "once_cell" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" - -[[package]] -name = "pico-args" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" - -[[package]] -name = "png" -version = "0.17.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" -dependencies = [ - "bitflags 1.3.2", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", -] - -[[package]] -name = "quick-error" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" - -[[package]] -name = "resvg" -version = "0.43.0" -dependencies = [ - "gif", - "image-webp", - "log", - "once_cell", - "pico-args", - "png", - "rgb", - "svgtypes", - "tiny-skia", - "usvg", - "zune-jpeg", -] - -[[package]] -name = "resvg-capi" -version = "0.43.0" -dependencies = [ - "log", - "resvg", -] - -[[package]] -name = "rgb" -version = "0.8.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f86ae463694029097b846d8f99fd5536740602ae00022c0c50c5600720b2f71" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "roxmltree" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" - -[[package]] -name = "rustybuzz" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85d1ccd519e61834798eb52c4e886e8c2d7d698dd3d6ce0b1b47eb8557f1181" -dependencies = [ - "bitflags 2.6.0", - "bytemuck", - "core_maths", - "log", - "smallvec", - "ttf-parser", - "unicode-bidi-mirroring", - "unicode-ccc", - "unicode-properties", - "unicode-script", -] - -[[package]] -name = "simd-adler32" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" - -[[package]] -name = "simplecss" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d" -dependencies = [ - "log", -] - -[[package]] -name = "siphasher" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" - -[[package]] -name = "slotmap" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" -dependencies = [ - "version_check", -] - -[[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - -[[package]] -name = "strict-num" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" -dependencies = [ - "float-cmp", -] - -[[package]] -name = "svgtypes" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fae3064df9b89391c9a76a0425a69d124aee9c5c28455204709e72c39868a43c" -dependencies = [ - "kurbo", - "siphasher", -] - -[[package]] -name = "tiny-skia" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" -dependencies = [ - "arrayref", - "arrayvec", - "bytemuck", - "cfg-if", - "log", - "png", - "tiny-skia-path", -] - -[[package]] -name = "tiny-skia-path" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" -dependencies = [ - "arrayref", - "bytemuck", - "strict-num", -] - -[[package]] -name = "tinyvec" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "ttf-parser" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be21190ff5d38e8b4a2d3b6a3ae57f612cc39c96e83cedeaf7abc338a8bac4a" -dependencies = [ - "core_maths", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" - -[[package]] -name = "unicode-bidi-mirroring" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64af057ad7466495ca113126be61838d8af947f41d93a949980b2389a118082f" - -[[package]] -name = "unicode-ccc" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "260bc6647b3893a9a90668360803a15f96b85a5257b1c3a0c3daf6ae2496de42" - -[[package]] -name = "unicode-properties" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291" - -[[package]] -name = "unicode-script" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8d71f5726e5f285a935e9fe8edfd53f0491eb6e9a5774097fdabee7cd8c9cd" - -[[package]] -name = "unicode-vo" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" - -[[package]] -name = "usvg" -version = "0.43.0" -dependencies = [ - "base64", - "data-url", - "flate2", - "fontdb", - "imagesize", - "kurbo", - "log", - "once_cell", - "pico-args", - "roxmltree", - "rustybuzz", - "simplecss", - "siphasher", - "strict-num", - "svgtypes", - "tiny-skia-path", - "unicode-bidi", - "unicode-script", - "unicode-vo", - "xmlwriter", -] - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "weezl" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" - -[[package]] -name = "xmlwriter" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" - -[[package]] -name = "zune-core" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" - -[[package]] -name = "zune-jpeg" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16099418600b4d8f028622f73ff6e3deaabdff330fb9a2a131dea781ee8b0768" -dependencies = [ - "zune-core", -] diff --git a/resvg-0.43.0/Cargo.toml b/resvg-0.43.0/Cargo.toml deleted file mode 100644 index b2533dd..0000000 --- a/resvg-0.43.0/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[workspace] -members = [ - "crates/resvg", - "crates/usvg", - "crates/c-api", -] -default-members = ["crates/resvg"] -resolver = "2" diff --git a/resvg-0.43.0/LICENSE.txt b/resvg-0.43.0/LICENSE.txt deleted file mode 100644 index 14e2f77..0000000 --- a/resvg-0.43.0/LICENSE.txt +++ /dev/null @@ -1,373 +0,0 @@ -Mozilla Public License Version 2.0 -================================== - -1. Definitions --------------- - -1.1. "Contributor" - means each individual or legal entity that creates, contributes to - the creation of, or owns Covered Software. - -1.2. "Contributor Version" - means the combination of the Contributions of others (if any) used - by a Contributor and that particular Contributor's Contribution. - -1.3. "Contribution" - means Covered Software of a particular Contributor. - -1.4. "Covered Software" - means Source Code Form to which the initial Contributor has attached - the notice in Exhibit A, the Executable Form of such Source Code - Form, and Modifications of such Source Code Form, in each case - including portions thereof. - -1.5. "Incompatible With Secondary Licenses" - means - - (a) that the initial Contributor has attached the notice described - in Exhibit B to the Covered Software; or - - (b) that the Covered Software was made available under the terms of - version 1.1 or earlier of the License, but not also under the - terms of a Secondary License. - -1.6. "Executable Form" - means any form of the work other than Source Code Form. - -1.7. "Larger Work" - means a work that combines Covered Software with other material, in - a separate file or files, that is not Covered Software. - -1.8. "License" - means this document. - -1.9. "Licensable" - means having the right to grant, to the maximum extent possible, - whether at the time of the initial grant or subsequently, any and - all of the rights conveyed by this License. - -1.10. "Modifications" - means any of the following: - - (a) any file in Source Code Form that results from an addition to, - deletion from, or modification of the contents of Covered - Software; or - - (b) any new file in Source Code Form that contains any Covered - Software. - -1.11. "Patent Claims" of a Contributor - means any patent claim(s), including without limitation, method, - process, and apparatus claims, in any patent Licensable by such - Contributor that would be infringed, but for the grant of the - License, by the making, using, selling, offering for sale, having - made, import, or transfer of either its Contributions or its - Contributor Version. - -1.12. "Secondary License" - means either the GNU General Public License, Version 2.0, the GNU - Lesser General Public License, Version 2.1, the GNU Affero General - Public License, Version 3.0, or any later versions of those - licenses. - -1.13. "Source Code Form" - means the form of the work preferred for making modifications. - -1.14. "You" (or "Your") - means an individual or a legal entity exercising rights under this - License. For legal entities, "You" includes any entity that - controls, is controlled by, or is under common control with You. For - purposes of this definition, "control" means (a) the power, direct - or indirect, to cause the direction or management of such entity, - whether by contract or otherwise, or (b) ownership of more than - fifty percent (50%) of the outstanding shares or beneficial - ownership of such entity. - -2. License Grants and Conditions --------------------------------- - -2.1. Grants - -Each Contributor hereby grants You a world-wide, royalty-free, -non-exclusive license: - -(a) under intellectual property rights (other than patent or trademark) - Licensable by such Contributor to use, reproduce, make available, - modify, display, perform, distribute, and otherwise exploit its - Contributions, either on an unmodified basis, with Modifications, or - as part of a Larger Work; and - -(b) under Patent Claims of such Contributor to make, use, sell, offer - for sale, have made, import, and otherwise transfer either its - Contributions or its Contributor Version. - -2.2. Effective Date - -The licenses granted in Section 2.1 with respect to any Contribution -become effective for each Contribution on the date the Contributor first -distributes such Contribution. - -2.3. Limitations on Grant Scope - -The licenses granted in this Section 2 are the only rights granted under -this License. No additional rights or licenses will be implied from the -distribution or licensing of Covered Software under this License. -Notwithstanding Section 2.1(b) above, no patent license is granted by a -Contributor: - -(a) for any code that a Contributor has removed from Covered Software; - or - -(b) for infringements caused by: (i) Your and any other third party's - modifications of Covered Software, or (ii) the combination of its - Contributions with other software (except as part of its Contributor - Version); or - -(c) under Patent Claims infringed by Covered Software in the absence of - its Contributions. - -This License does not grant any rights in the trademarks, service marks, -or logos of any Contributor (except as may be necessary to comply with -the notice requirements in Section 3.4). - -2.4. Subsequent Licenses - -No Contributor makes additional grants as a result of Your choice to -distribute the Covered Software under a subsequent version of this -License (see Section 10.2) or under the terms of a Secondary License (if -permitted under the terms of Section 3.3). - -2.5. Representation - -Each Contributor represents that the Contributor believes its -Contributions are its original creation(s) or it has sufficient rights -to grant the rights to its Contributions conveyed by this License. - -2.6. Fair Use - -This License is not intended to limit any rights You have under -applicable copyright doctrines of fair use, fair dealing, or other -equivalents. - -2.7. Conditions - -Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted -in Section 2.1. - -3. Responsibilities -------------------- - -3.1. Distribution of Source Form - -All distribution of Covered Software in Source Code Form, including any -Modifications that You create or to which You contribute, must be under -the terms of this License. You must inform recipients that the Source -Code Form of the Covered Software is governed by the terms of this -License, and how they can obtain a copy of this License. You may not -attempt to alter or restrict the recipients' rights in the Source Code -Form. - -3.2. Distribution of Executable Form - -If You distribute Covered Software in Executable Form then: - -(a) such Covered Software must also be made available in Source Code - Form, as described in Section 3.1, and You must inform recipients of - the Executable Form how they can obtain a copy of such Source Code - Form by reasonable means in a timely manner, at a charge no more - than the cost of distribution to the recipient; and - -(b) You may distribute such Executable Form under the terms of this - License, or sublicense it under different terms, provided that the - license for the Executable Form does not attempt to limit or alter - the recipients' rights in the Source Code Form under this License. - -3.3. Distribution of a Larger Work - -You may create and distribute a Larger Work under terms of Your choice, -provided that You also comply with the requirements of this License for -the Covered Software. If the Larger Work is a combination of Covered -Software with a work governed by one or more Secondary Licenses, and the -Covered Software is not Incompatible With Secondary Licenses, this -License permits You to additionally distribute such Covered Software -under the terms of such Secondary License(s), so that the recipient of -the Larger Work may, at their option, further distribute the Covered -Software under the terms of either this License or such Secondary -License(s). - -3.4. Notices - -You may not remove or alter the substance of any license notices -(including copyright notices, patent notices, disclaimers of warranty, -or limitations of liability) contained within the Source Code Form of -the Covered Software, except that You may alter any license notices to -the extent required to remedy known factual inaccuracies. - -3.5. Application of Additional Terms - -You may choose to offer, and to charge a fee for, warranty, support, -indemnity or liability obligations to one or more recipients of Covered -Software. However, You may do so only on Your own behalf, and not on -behalf of any Contributor. You must make it absolutely clear that any -such warranty, support, indemnity, or liability obligation is offered by -You alone, and You hereby agree to indemnify every Contributor for any -liability incurred by such Contributor as a result of warranty, support, -indemnity or liability terms You offer. You may include additional -disclaimers of warranty and limitations of liability specific to any -jurisdiction. - -4. Inability to Comply Due to Statute or Regulation ---------------------------------------------------- - -If it is impossible for You to comply with any of the terms of this -License with respect to some or all of the Covered Software due to -statute, judicial order, or regulation then You must: (a) comply with -the terms of this License to the maximum extent possible; and (b) -describe the limitations and the code they affect. Such description must -be placed in a text file included with all distributions of the Covered -Software under this License. Except to the extent prohibited by statute -or regulation, such description must be sufficiently detailed for a -recipient of ordinary skill to be able to understand it. - -5. Termination --------------- - -5.1. The rights granted under this License will terminate automatically -if You fail to comply with any of its terms. However, if You become -compliant, then the rights granted under this License from a particular -Contributor are reinstated (a) provisionally, unless and until such -Contributor explicitly and finally terminates Your grants, and (b) on an -ongoing basis, if such Contributor fails to notify You of the -non-compliance by some reasonable means prior to 60 days after You have -come back into compliance. Moreover, Your grants from a particular -Contributor are reinstated on an ongoing basis if such Contributor -notifies You of the non-compliance by some reasonable means, this is the -first time You have received notice of non-compliance with this License -from such Contributor, and You become compliant prior to 30 days after -Your receipt of the notice. - -5.2. If You initiate litigation against any entity by asserting a patent -infringement claim (excluding declaratory judgment actions, -counter-claims, and cross-claims) alleging that a Contributor Version -directly or indirectly infringes any patent, then the rights granted to -You by any and all Contributors for the Covered Software under Section -2.1 of this License shall terminate. - -5.3. In the event of termination under Sections 5.1 or 5.2 above, all -end user license agreements (excluding distributors and resellers) which -have been validly granted by You or Your distributors under this License -prior to termination shall survive termination. - -************************************************************************ -* * -* 6. Disclaimer of Warranty * -* ------------------------- * -* * -* Covered Software is provided under this License on an "as is" * -* basis, without warranty of any kind, either expressed, implied, or * -* statutory, including, without limitation, warranties that the * -* Covered Software is free of defects, merchantable, fit for a * -* particular purpose or non-infringing. The entire risk as to the * -* quality and performance of the Covered Software is with You. * -* Should any Covered Software prove defective in any respect, You * -* (not any Contributor) assume the cost of any necessary servicing, * -* repair, or correction. This disclaimer of warranty constitutes an * -* essential part of this License. No use of any Covered Software is * -* authorized under this License except under this disclaimer. * -* * -************************************************************************ - -************************************************************************ -* * -* 7. Limitation of Liability * -* -------------------------- * -* * -* Under no circumstances and under no legal theory, whether tort * -* (including negligence), contract, or otherwise, shall any * -* Contributor, or anyone who distributes Covered Software as * -* permitted above, be liable to You for any direct, indirect, * -* special, incidental, or consequential damages of any character * -* including, without limitation, damages for lost profits, loss of * -* goodwill, work stoppage, computer failure or malfunction, or any * -* and all other commercial damages or losses, even if such party * -* shall have been informed of the possibility of such damages. This * -* limitation of liability shall not apply to liability for death or * -* personal injury resulting from such party's negligence to the * -* extent applicable law prohibits such limitation. Some * -* jurisdictions do not allow the exclusion or limitation of * -* incidental or consequential damages, so this exclusion and * -* limitation may not apply to You. * -* * -************************************************************************ - -8. Litigation -------------- - -Any litigation relating to this License may be brought only in the -courts of a jurisdiction where the defendant maintains its principal -place of business and such litigation shall be governed by laws of that -jurisdiction, without reference to its conflict-of-law provisions. -Nothing in this Section shall prevent a party's ability to bring -cross-claims or counter-claims. - -9. Miscellaneous ----------------- - -This License represents the complete agreement concerning the subject -matter hereof. If any provision of this License is held to be -unenforceable, such provision shall be reformed only to the extent -necessary to make it enforceable. Any law or regulation which provides -that the language of a contract shall be construed against the drafter -shall not be used to construe this License against a Contributor. - -10. Versions of the License ---------------------------- - -10.1. New Versions - -Mozilla Foundation is the license steward. Except as provided in Section -10.3, no one other than the license steward has the right to modify or -publish new versions of this License. Each version will be given a -distinguishing version number. - -10.2. Effect of New Versions - -You may distribute the Covered Software under the terms of the version -of the License under which You originally received the Covered Software, -or under the terms of any subsequent version published by the license -steward. - -10.3. Modified Versions - -If you create software not governed by this License, and you want to -create a new license for such software, you may create and use a -modified version of this License if you rename the license and remove -any references to the name of the license steward (except to note that -such modified license differs from this License). - -10.4. Distributing Source Code Form that is Incompatible With Secondary -Licenses - -If You choose to distribute Source Code Form that is Incompatible With -Secondary Licenses under the terms of this version of the License, the -notice described in Exhibit B of this License must be attached. - -Exhibit A - Source Code Form License Notice -------------------------------------------- - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. - -If it is not possible or desirable to put the notice in a particular -file, then You may include the notice in a location (such as a LICENSE -file in a relevant directory) where a recipient would be likely to look -for such a notice. - -You may add additional accurate notices of copyright ownership. - -Exhibit B - "Incompatible With Secondary Licenses" Notice ---------------------------------------------------------- - - This Source Code Form is "Incompatible With Secondary Licenses", as - defined by the Mozilla Public License, v. 2.0. diff --git a/resvg-0.43.0/NOTICE.txt b/resvg-0.43.0/NOTICE.txt deleted file mode 100644 index 636ec97..0000000 --- a/resvg-0.43.0/NOTICE.txt +++ /dev/null @@ -1 +0,0 @@ -Copyright (c) 2017 Yevhenii Reizner diff --git a/resvg-0.43.0/README.md b/resvg-0.43.0/README.md deleted file mode 100644 index ee6f888..0000000 --- a/resvg-0.43.0/README.md +++ /dev/null @@ -1,144 +0,0 @@ -## resvg -![Build Status](https://github.com/RazrFalcon/resvg/workflows/Build/badge.svg) -[![Crates.io](https://img.shields.io/crates/v/resvg.svg)](https://crates.io/crates/resvg) -[![Documentation](https://docs.rs/resvg/badge.svg)](https://docs.rs/resvg) -[![Rust 1.67.1+](https://img.shields.io/badge/rust-1.67.1+-orange.svg)](https://www.rust-lang.org) - -*resvg* is an [SVG](https://en.wikipedia.org/wiki/Scalable_Vector_Graphics) rendering library. - -It can be used as a Rust library, as a C library, and as a CLI application to render static SVG files. - -The core idea is to make a fast, small, portable SVG library with the goal to support the whole SVG spec. - -## Features - -### Designed for edge-cases - -SVG is a very complicated format with a large specification (SVG 1.1 is almost 900 pages). -You basically need a web browser to handle all of it. But the truth is that even browsers -fail at this (see [SVG support](https://github.com/RazrFalcon/resvg#svg-support)). -Yes, unlike `resvg`, browsers do support dynamic SVG features like animations and scripting. -But using a browser to render SVG _correctly_ is sadly not an option. - -To prove its correctness, `resvg` has a vast test suite that includes around 1600 tests. -And those are only SVG-to-PNG regression tests. This doesn't include tests in `resvg` dependencies. -And the best thing is that `resvg` test suite is available to everyone. It's not tied to `resvg` -in any way. Which should help people who plan to develop their own SVG libraries. - -### Safety - -It's hard not to mention safety when we talk about Rust and processing of a random input. -And we're talking not only about SVG/XML, but also about CSS, TTF, PNG, JPEG, GIF, and GZIP. - -While `resvg` is not the only SVG library written in Rust, it's the only one that -is written completely in Rust. There is no non-Rust code in the final binary. - -Moreover, there is almost no `unsafe` code either. Still, some dependencies have some `unsafe` code -and font memory-mapping is inherently `unsafe`, but it's best you can get in terms of memory safety. - -However, this doesn't stop at memory safety. `resvg` has extensive checks to prevent endless loops (freezes) -and stack overflows (via recursion). - -### Zero bloat - -Right now, the `resvg` CLI application is less than 3MB in size and doesn't require any external dependencies. -The binary contains nothing that isn't needed for rendering SVG files. - -### Portable - -`resvg` is guaranteed to work everywhere where you can compile the Rust itself, -including WASM. There are some rough edges with obscure CPU architectures and -mobile OSs (mainly system fonts loading), but it should be pretty painless otherwise. - -### SVG preprocessing - -Another major difference from other SVG rendering libraries is that in `resvg` -SVG parsing and rendering are two completely separate steps. -Those steps are also split into two separate libraries: `resvg` and [usvg]. -Meaning you can easily write your own renderer on top of `usvg` using any 2D library of your liking. - -### Performance - -Comparing performance between different SVG rendering libraries is like comparing apples and oranges. -Everyone has a very different set of supported features, languages, build flags, etc... -Anyhow, as `resvg` is written in Rust and uses [tiny-skia] for rendering - it's pretty fast. -There should also still be quite a lot of room for improvement. - -### Reproducibility - -Since `resvg` doesn't rely on any system libraries it allows us to have reproducible results -on all supported platforms. Meaning if you render an SVG file on x86 Windows and then render it -on ARM macOS - the produced image will be identical. Each pixel would have the same value. - -## Limitations - -- No animations
- There are no plans on implementing them either. -- No native text rendering
- `resvg` doesn't rely on any system libraries, which implies that we cannot use native text rendering. - Nevertheless, native text rendering is optimized for small horizontal text, which is not - that common in SVG. -- Unicode-only
- It's the 21st century. Text files that aren't UTF-8 encoded are no longer relevant. - -## SVG support - -`resvg` aims to only support the [static](http://www.w3.org/TR/SVG11/feature#SVG-static) -SVG subset; i.e. no `a`, `script`, `view` or `cursor` elements, no events and no animations. - -[SVG 2](https://www.w3.org/TR/SVG2/) support is being worked on. -You can search for relevant issues with the -[svg2 tag](https://github.com/RazrFalcon/resvg/issues?q=is%3Aissue+is%3Aopen+label%3Asvg2) -or our [SVG 2 changelog](https://github.com/RazrFalcon/resvg/blob/master/docs/svg2-changelog.md). - -[SVG Tiny 1.2](https://www.w3.org/TR/SVGTiny12/) is not supported and support is also not planned. - -Results of the [resvg test suite](https://github.com/RazrFalcon/resvg-test-suite): - -![](./.github/chart.svg) - -SVG 2 only results: - -![](./.github/chart-svg2.svg) - -You can find a complete table of supported features -[here](https://razrfalcon.github.io/resvg-test-suite/svg-support-table.html). -It also includes some alternative libraries. - -We're not testing against all SVG libraries since many of them are pretty bad. -Some libraries are not on the list because they don't pass the 25% mark. -Such libraries are: wxSvg, LunaSVG and nanosvg. - -## resvg project - -There is a subtle difference between resvg as a _library_ and resvg as a _project_. -While most users will interact only with the resvg library, it's just a tip of an iceberg. -There are a lot of libraries that I had to write to make resvg possible. -Here are some of them: - -- resvg - the actual SVG renderer -- [usvg] - an SVG preprocessor/simplifier -- [tiny-skia] - a [Skia](https://github.com/google/skia) subset ported to Rust -- [rustybuzz] - a [harfbuzz](https://github.com/harfbuzz/harfbuzz) subset ported to Rust -- [ttf-parser] - a TrueType/OpenType font parser -- [fontdb] - a simple, in-memory font database with CSS-like queries -- [roxmltree] - an XML parsing library -- [simplecss] - a pretty decent CSS 2 parser and selector -- [pico-args] - an absolutely minimal, but surprisingly popular command-line arguments parser - -So while the resvg _library_ is deceptively small (around 2500 LOC), the resvg _project_ -is nearing 75'000 LOC. Which is not that much considering how much resvg does. -It's definitely the smallest option out there. - -## License - -`resvg` project is licensed under the [MPLv2.0](https://www.mozilla.org/en-US/MPL/). - -[usvg]: https://github.com/RazrFalcon/resvg/tree/master/crates/usvg -[rustybuzz]: https://github.com/RazrFalcon/rustybuzz -[tiny-skia]: https://github.com/RazrFalcon/tiny-skia -[ttf-parser]: https://github.com/RazrFalcon/ttf-parser -[roxmltree]: https://github.com/RazrFalcon/roxmltree -[simplecss]: https://github.com/RazrFalcon/simplecss -[fontdb]: https://github.com/RazrFalcon/fontdb -[pico-args]: https://github.com/RazrFalcon/pico-args diff --git a/resvg-0.43.0/crates/c-api/Cargo.toml b/resvg-0.43.0/crates/c-api/Cargo.toml deleted file mode 100644 index 2345143..0000000 --- a/resvg-0.43.0/crates/c-api/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "resvg-capi" -version = "0.43.0" -authors = ["Yevhenii Reizner "] -keywords = ["svg", "render", "raster", "c-api"] -license = "MPL-2.0" -edition = "2021" -workspace = "../.." - -[lib] -name = "resvg" -path = "lib.rs" -crate-type = ["cdylib", "staticlib"] - -[dependencies] -log = "0.4" -resvg = { path = "../resvg", default-features = false } - -[features] -default = ["text", "system-fonts", "memmap-fonts", "raster-images"] -# enables SVG Text support -# adds around 500KiB to your binary -text = ["resvg/text"] -# enables system fonts loading (only for `text`) -system-fonts = ["resvg/system-fonts"] -# enables font files memmaping for faster loading (only for `text`) -memmap-fonts = ["resvg/memmap-fonts"] -raster-images = ["resvg/raster-images"] diff --git a/resvg-0.43.0/crates/c-api/README.md b/resvg-0.43.0/crates/c-api/README.md deleted file mode 100644 index 734845b..0000000 --- a/resvg-0.43.0/crates/c-api/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# C API for resvg - -## Build - -```sh -cargo build --release -``` - -This will produce dynamic and static C libraries that can be found at `../target/release`. - -## Header generation - -The `resvg.h` is generated via [cbindgen](https://github.com/eqrion/cbindgen) -and then manually edited a bit. diff --git a/resvg-0.43.0/crates/c-api/ResvgQt.h b/resvg-0.43.0/crates/c-api/ResvgQt.h deleted file mode 100644 index c7533eb..0000000 --- a/resvg-0.43.0/crates/c-api/ResvgQt.h +++ /dev/null @@ -1,555 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -/** - * @file ResvgQt.h - * - * An idiomatic Qt API for resvg. - */ - -#ifndef RESVG_QT_H -#define RESVG_QT_H - -#define RESVG_QT_MAJOR_VERSION 0 -#define RESVG_QT_MINOR_VERSION 43 -#define RESVG_QT_PATCH_VERSION 0 -#define RESVG_QT_VERSION "0.43.0" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -namespace ResvgPrivate { - -class Data -{ -public: - ~Data() - { - clear(); - } - - void reset() - { - clear(); - } - - resvg_render_tree *tree = nullptr; - QSizeF size; - QString errMsg; - -private: - void clear() - { - // No need to deallocate opt.font_family, because it is a constant. - - if (tree) { - resvg_tree_destroy(tree); - tree = nullptr; - } - - size = QSizeF(); - errMsg = QString(); - } -}; - -static QString errorToString(const int err) -{ - switch (err) { - case RESVG_OK : - return QString(); - case RESVG_ERROR_NOT_AN_UTF8_STR : - return QLatin1String("The SVG content has not an UTF-8 encoding."); - case RESVG_ERROR_FILE_OPEN_FAILED : - return QLatin1String("Failed to read the file."); - case RESVG_ERROR_MALFORMED_GZIP : - return QLatin1String("Not a GZip compressed data."); - case RESVG_ERROR_ELEMENTS_LIMIT_REACHED : - return QLatin1String("Too many elements."); - case RESVG_ERROR_INVALID_SIZE : - return QLatin1String("SVG doesn't have a valid size."); - case RESVG_ERROR_PARSING_FAILED : - return QLatin1String("Failed to parse an SVG data."); - } - - Q_UNREACHABLE(); -} - -} //ResvgPrivate - -/** - * @brief SVG parsing options. - */ -class ResvgOptions { -public: - /** - * @brief Constructs a new options set. - */ - ResvgOptions() - : d(resvg_options_create()) - { - // Do not set the default font via QFont::family() - // because it will return a dummy one on Windows. - // See https://github.com/RazrFalcon/resvg/issues/159 - - setLanguages({ QLocale().bcp47Name() }); - } - - /** - * @brief Sets a directory that will be used during relative paths resolving. - * - * Expected to be the same as the directory that contains the SVG file, - * but can be set to any. - * - * Default: not set - */ - void setResourcesDir(const QString &path) - { - Q_ASSERT(QFileInfo(path).isDir()); - if (path.isEmpty()) { - resvg_options_set_resources_dir(d, nullptr); - } else { - auto pathC = path.toUtf8(); - pathC.append('\0'); - resvg_options_set_resources_dir(d, pathC.constData()); - } - } - - /** - * @brief Sets the target DPI. - * - * Impact units conversion. - * - * Default: 96 - */ - void setDpi(const float dpi) - { - resvg_options_set_dpi(d, dpi); - } - - /** - * @brief Sets the default font family. - * - * Will be used when no `font-family` attribute is set in the SVG. - * - * Default: Times New Roman - */ - void setFontFamily(const QString &family) - { - if (family.isEmpty()) { - return; - } - - auto familyC = family.toUtf8(); - familyC.append('\0'); - resvg_options_set_font_family(d, familyC.constData()); - } - - /** - * @brief Sets the default font size. - * - * Will be used when no `font-size` attribute is set in the SVG. - * - * Default: 12 - */ - void setFontSize(const float size) - { - resvg_options_set_font_size(d, size); - } - - /** - * @brief Sets a list of languages. - * - * Will be used to resolve a `systemLanguage` conditional attribute. - * - * Example: en, en-US. - * - * Default: en - */ - void setLanguages(const QStringList &languages) - { - if (languages.isEmpty()) { - resvg_options_set_languages(d, nullptr); - } else { - auto languagesC = languages.join(',').toUtf8(); - languagesC.append('\0'); - resvg_options_set_languages(d, languagesC.constData()); - } - } - - /** - * @brief Sets the default shape rendering method. - * - * Will be used when an SVG element's `shape-rendering` property is set to `auto`. - * - * Default: `RESVG_SHAPE_RENDERING_GEOMETRIC_PRECISION` - */ - void setShapeRenderingMode(const resvg_shape_rendering mode) - { - resvg_options_set_shape_rendering_mode(d, mode); - } - - /** - * @brief Sets the default text rendering method. - * - * Will be used when an SVG element's `text-rendering` property is set to `auto`. - * - * Default: `RESVG_TEXT_RENDERING_OPTIMIZE_LEGIBILITY` - */ - void setTextRenderingMode(const resvg_text_rendering mode) - { - resvg_options_set_text_rendering_mode(d, mode); - } - - /** - * @brief Sets the default image rendering method. - * - * Will be used when an SVG element's `image-rendering` property is set to `auto`. - * - * Default: `RESVG_IMAGE_RENDERING_OPTIMIZE_QUALITY` - */ - void setImageRenderingMode(const resvg_image_rendering mode) - { - resvg_options_set_image_rendering_mode(d, mode); - } - - /** - * @brief Loads a font data into the internal fonts database. - * - * Prints a warning into the log when the data is not a valid TrueType font. - */ - void loadFontData(const QByteArray &data) - { - resvg_options_load_font_data(d, data.constData(), data.size()); - } - - /** - * @brief Loads a font file into the internal fonts database. - * - * Prints a warning into the log when the data is not a valid TrueType font. - */ - bool loadFontFile(const QString &path) - { - auto pathC = path.toUtf8(); - pathC.append('\0'); - return resvg_options_load_font_file(d, pathC.constData()); - } - - /** - * @brief Loads system fonts into the internal fonts database. - * - * This method is very IO intensive. - * - * This method should be executed only once per #resvg_options. - * - * The system scanning is not perfect, so some fonts may be omitted. - * Please send a bug report in this case. - * - * Prints warnings into the log. - */ - void loadSystemFonts() - { - resvg_options_load_system_fonts(d); - } - - /** - * @brief Destructs options. - */ - ~ResvgOptions() - { - resvg_options_destroy(d); - } - - friend class ResvgRenderer; - -private: - resvg_options * const d; -}; - -/** - * @brief QSvgRenderer-like wrapper for resvg. - */ -class ResvgRenderer { -public: - /** - * @brief Constructs a new renderer. - */ - ResvgRenderer() - : d(new ResvgPrivate::Data()) - { - } - - /** - * @brief Constructs a new renderer and loads the contents of the SVG(Z) file. - */ - ResvgRenderer(const QString &filePath, const ResvgOptions &opt) - : d(new ResvgPrivate::Data()) - { - load(filePath, opt); - } - - /** - * @brief Constructs a new renderer and loads the SVG data. - */ - ResvgRenderer(const QByteArray &data, const ResvgOptions &opt) - : d(new ResvgPrivate::Data()) - { - load(data, opt); - } - - /** - * @brief Loads the contents of the SVG(Z) file. - */ - bool load(const QString &filePath, const ResvgOptions &opt) - { - // Check for Qt resource path. - if (filePath.startsWith(QLatin1String(":/"))) { - QFile file(filePath); - if (file.open(QFile::ReadOnly)) - return load(file.readAll(), opt); - else - return false; - } - - d->reset(); - - auto filePathC = filePath.toUtf8(); - filePathC.append('\0'); - - const auto err = resvg_parse_tree_from_file(filePathC.constData(), opt.d, &d->tree); - if (err != RESVG_OK) { - d->errMsg = ResvgPrivate::errorToString(err); - return false; - } - - const auto s = resvg_get_image_size(d->tree); - d->size = QSizeF(s.width, s.height); - - return true; - } - - /** - * @brief Loads the SVG data. - */ - bool load(const QByteArray &data, const ResvgOptions &opt) - { - d->reset(); - - const auto err = resvg_parse_tree_from_data(data.constData(), data.size(), opt.d, &d->tree); - if (err != RESVG_OK) { - d->errMsg = ResvgPrivate::errorToString(err); - return false; - } - - const auto s = resvg_get_image_size(d->tree); - d->size = QSizeF(s.width, s.height); - - return true; - } - - /** - * @brief Returns \b true if the file or data were loaded successful. - */ - bool isValid() const - { - return d->tree; - } - - /** - * @brief Returns an underling error when #isValid is \b false. - */ - QString errorString() const - { - return d->errMsg; - } - - /** - * @brief Checks that underling tree has any nodes. - * - * #ResvgRenderer and #ResvgRenderer constructors - * will set an error only if a file does not exist or it has a non-UTF-8 encoding. - * All other errors will result in an empty tree with a 100x100px size. - * - * @return Returns \b true if tree has no nodes. - */ - bool isEmpty() const - { - if (d->tree) - return resvg_is_image_empty(d->tree); - else - return true; - } - - /** - * @brief Returns an SVG size. - * - * The `width` and `height` attributes in SVG. - */ - QSize defaultSize() const - { - return defaultSizeF().toSize(); - } - - /** - * @brief Returns an SVG size. - * - * The `width` and `height` attributes in SVG. - */ - QSizeF defaultSizeF() const - { - if (d->tree) - return d->size.toSize(); - else - return QSizeF(); - } - - /** - * @brief Returns an SVG viewbox. - * - * `resvg` flattens the `viewbox`, therefore this method returns - * the same value as \b size. - */ - QRect viewBox() const - { - return QRect(0, 0, d->size.width(), d->size.height()); - } - - /** - * @brief Returns an SVG viewbox. - * - * `resvg` flattens the `viewbox`, therefore this method returns - * the same value as \b size. - */ - QRectF viewBoxF() const - { - return QRectF(0, 0, d->size.width(), d->size.height()); - } - - /** - * @brief Returns bounding rectangle of the item with the given \b id. - * The transformation matrix of parent elements is not affecting - * the bounds of the element. - */ - QRectF boundsOnElement(const QString &id) const - { - if (!d->tree) - return QRectF(); - - const auto utf8Str = id.toUtf8(); - const auto rawId = utf8Str.constData(); - resvg_rect bbox; - if (resvg_get_node_bbox(d->tree, rawId, &bbox)) - return QRectF(bbox.x, bbox.y, bbox.width, bbox.height); - - return QRectF(); - } - - /** - * @brief Returns bounding rectangle of a whole image. - */ - QRectF boundingBox() const - { - if (!d->tree) - return QRectF(); - - resvg_rect bbox; - if (resvg_get_image_bbox(d->tree, &bbox)) - return QRectF(bbox.x, bbox.y, bbox.width, bbox.height); - - return QRectF(); - } - - /** - * @brief Returns \b true if element with such an ID exists. - */ - bool elementExists(const QString &id) const - { - if (!d->tree) - return false; - - const auto utf8Str = id.toUtf8(); - const auto rawId = utf8Str.constData(); - return resvg_node_exists(d->tree, rawId); - } - - /** - * @brief Returns element's transform. - */ - QTransform transformForElement(const QString &id) const - { - if (!d->tree) - return QTransform(); - - const auto utf8Str = id.toUtf8(); - const auto rawId = utf8Str.constData(); - resvg_transform ts; - if (resvg_get_node_transform(d->tree, rawId, &ts)) - return QTransform(ts.a, ts.b, ts.c, ts.d, ts.e, ts.f); - - return QTransform(); - } - - // TODO: render node - - /** - * @brief Renders the SVG data to \b QImage with a specified \b size. - * - * If \b size is not set, the \b defaultSize() will be used. - */ - QImage renderToImage(const QSize &size = QSize()) const - { - resvg_transform ts = resvg_transform_identity(); - if (size.isValid()) { - // TODO: support height too. - auto sizef = defaultSizeF(); - const auto newHeight = std::ceil(double(size.width()) * sizef.height() / sizef.width()); - ts.a = double(size.width()) / sizef.width(); - ts.d = newHeight / sizef.height(); - } - - auto svgSize = size; - if (svgSize.isEmpty()) - svgSize = defaultSize(); - - QImage qImg(svgSize.width(), svgSize.height(), QImage::Format_ARGB32_Premultiplied); - qImg.fill(Qt::transparent); - resvg_render(d->tree, ts, qImg.width(), qImg.height(), (char*)qImg.bits()); - - // resvg renders onto the RGBA canvas, while QImage is ARGB. - // std::move is required to call inplace version of rgbSwapped(). - return std::move(qImg).rgbSwapped(); - } - - /** - * @brief Initializes the library log. - * - * Use it if you want to see any warnings. - * - * Must be called only once. - * - * All warnings will be printed to the \b stderr. - */ - static void initLog() - { - resvg_init_log(); - } - -private: - QScopedPointer d; -}; - -#endif // RESVG_QT_H diff --git a/resvg-0.43.0/crates/c-api/cbindgen.toml b/resvg-0.43.0/crates/c-api/cbindgen.toml deleted file mode 100644 index 96794f9..0000000 --- a/resvg-0.43.0/crates/c-api/cbindgen.toml +++ /dev/null @@ -1,35 +0,0 @@ -language = "C" -include_guard = "RESVG_H" -braces = "SameLine" -tab_width = 4 -documentation_style = "doxy" -header = """/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -/** - * @file resvg.h - * - * resvg C API - */""" -cpp_compat = true -no_includes = true -sys_includes = ["stdbool.h", "stdint.h"] -style = "type" - -[fn] -sort_by = "None" - -[enum] -rename_variants = "ScreamingSnakeCase" -prefix_with_name = true - -[export] -include = [ - "resvg_error", - "resvg_shape_rendering", - "resvg_text_rendering", - "resvg_image_rendering", -] diff --git a/resvg-0.43.0/crates/c-api/lib.rs b/resvg-0.43.0/crates/c-api/lib.rs deleted file mode 100644 index 9ec66d7..0000000 --- a/resvg-0.43.0/crates/c-api/lib.rs +++ /dev/null @@ -1,917 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -//! C bindings. - -#![allow(non_camel_case_types)] -#![warn(missing_docs)] -#![warn(missing_copy_implementations)] - -use std::ffi::CStr; -use std::os::raw::c_char; -use std::slice; - -use resvg::tiny_skia; -use resvg::usvg; - -/// @brief List of possible errors. -#[repr(C)] -#[derive(Copy, Clone)] -pub enum resvg_error { - /// Everything is ok. - OK = 0, - /// Only UTF-8 content are supported. - NOT_AN_UTF8_STR, - /// Failed to open the provided file. - FILE_OPEN_FAILED, - /// Compressed SVG must use the GZip algorithm. - MALFORMED_GZIP, - /// We do not allow SVG with more than 1_000_000 elements for security reasons. - ELEMENTS_LIMIT_REACHED, - /// SVG doesn't have a valid size. - /// - /// Occurs when width and/or height are <= 0. - /// - /// Also occurs if width, height and viewBox are not set. - INVALID_SIZE, - /// Failed to parse an SVG data. - PARSING_FAILED, -} - -/// @brief A rectangle representation. -#[repr(C)] -#[allow(missing_docs)] -#[derive(Copy, Clone)] -pub struct resvg_rect { - pub x: f32, - pub y: f32, - pub width: f32, - pub height: f32, -} - -/// @brief A size representation. -#[repr(C)] -#[allow(missing_docs)] -#[derive(Copy, Clone)] -pub struct resvg_size { - pub width: f32, - pub height: f32, -} - -/// @brief A 2D transform representation. -#[repr(C)] -#[allow(missing_docs)] -#[derive(Copy, Clone)] -pub struct resvg_transform { - pub a: f32, - pub b: f32, - pub c: f32, - pub d: f32, - pub e: f32, - pub f: f32, -} - -impl resvg_transform { - #[inline] - fn to_tiny_skia(&self) -> tiny_skia::Transform { - tiny_skia::Transform::from_row(self.a, self.b, self.c, self.d, self.e, self.f) - } -} - -/// @brief Creates an identity transform. -#[no_mangle] -pub extern "C" fn resvg_transform_identity() -> resvg_transform { - resvg_transform { - a: 1.0, - b: 0.0, - c: 0.0, - d: 1.0, - e: 0.0, - f: 0.0, - } -} - -/// @brief Initializes the library log. -/// -/// Use it if you want to see any warnings. -/// -/// Must be called only once. -/// -/// All warnings will be printed to the `stderr`. -#[no_mangle] -pub extern "C" fn resvg_init_log() { - if let Ok(()) = log::set_logger(&LOGGER) { - log::set_max_level(log::LevelFilter::Warn); - } -} - -/// @brief An SVG to #resvg_render_tree conversion options. -/// -/// Also, contains a fonts database used during text to path conversion. -/// The database is empty by default. -pub struct resvg_options { - options: usvg::Options<'static>, -} - -/// @brief Creates a new #resvg_options object. -/// -/// Should be destroyed via #resvg_options_destroy. -#[no_mangle] -pub extern "C" fn resvg_options_create() -> *mut resvg_options { - Box::into_raw(Box::new(resvg_options { - options: usvg::Options::default(), - })) -} - -#[inline] -fn cast_opt(opt: *mut resvg_options) -> &'static mut usvg::Options<'static> { - unsafe { - assert!(!opt.is_null()); - &mut (*opt).options - } -} - -/// @brief Sets a directory that will be used during relative paths resolving. -/// -/// Expected to be the same as the directory that contains the SVG file, -/// but can be set to any. -/// -/// Must be UTF-8. Can be set to NULL. -/// -/// Default: NULL -#[no_mangle] -pub extern "C" fn resvg_options_set_resources_dir(opt: *mut resvg_options, path: *const c_char) { - if path.is_null() { - cast_opt(opt).resources_dir = None; - } else { - cast_opt(opt).resources_dir = Some(cstr_to_str(path).unwrap().into()); - } -} - -/// @brief Sets the target DPI. -/// -/// Impact units conversion. -/// -/// Default: 96 -#[no_mangle] -pub extern "C" fn resvg_options_set_dpi(opt: *mut resvg_options, dpi: f32) { - cast_opt(opt).dpi = dpi as f32; -} - -/// @brief Sets the default font family. -/// -/// Will be used when no `font-family` attribute is set in the SVG. -/// -/// Must be UTF-8. NULL is not allowed. -/// -/// Default: Times New Roman -#[no_mangle] -pub extern "C" fn resvg_options_set_font_family(opt: *mut resvg_options, family: *const c_char) { - cast_opt(opt).font_family = cstr_to_str(family).unwrap().to_string(); -} - -/// @brief Sets the default font size. -/// -/// Will be used when no `font-size` attribute is set in the SVG. -/// -/// Default: 12 -#[no_mangle] -pub extern "C" fn resvg_options_set_font_size(opt: *mut resvg_options, size: f32) { - cast_opt(opt).font_size = size; -} - -/// @brief Sets the `serif` font family. -/// -/// Must be UTF-8. NULL is not allowed. -/// -/// Has no effect when the `text` feature is not enabled. -/// -/// Default: Times New Roman -#[no_mangle] -#[allow(unused_variables)] -pub extern "C" fn resvg_options_set_serif_family(opt: *mut resvg_options, family: *const c_char) { - #[cfg(feature = "text")] - { - cast_opt(opt) - .fontdb_mut() - .set_serif_family(cstr_to_str(family).unwrap().to_string()); - } -} - -/// @brief Sets the `sans-serif` font family. -/// -/// Must be UTF-8. NULL is not allowed. -/// -/// Has no effect when the `text` feature is not enabled. -/// -/// Default: Arial -#[no_mangle] -#[allow(unused_variables)] -pub extern "C" fn resvg_options_set_sans_serif_family( - opt: *mut resvg_options, - family: *const c_char, -) { - #[cfg(feature = "text")] - { - cast_opt(opt) - .fontdb_mut() - .set_sans_serif_family(cstr_to_str(family).unwrap().to_string()); - } -} - -/// @brief Sets the `cursive` font family. -/// -/// Must be UTF-8. NULL is not allowed. -/// -/// Has no effect when the `text` feature is not enabled. -/// -/// Default: Comic Sans MS -#[no_mangle] -#[allow(unused_variables)] -pub extern "C" fn resvg_options_set_cursive_family(opt: *mut resvg_options, family: *const c_char) { - #[cfg(feature = "text")] - { - cast_opt(opt) - .fontdb_mut() - .set_cursive_family(cstr_to_str(family).unwrap().to_string()); - } -} - -/// @brief Sets the `fantasy` font family. -/// -/// Must be UTF-8. NULL is not allowed. -/// -/// Has no effect when the `text` feature is not enabled. -/// -/// Default: Papyrus on macOS, Impact on other OS'es -#[no_mangle] -#[allow(unused_variables)] -pub extern "C" fn resvg_options_set_fantasy_family(opt: *mut resvg_options, family: *const c_char) { - #[cfg(feature = "text")] - { - cast_opt(opt) - .fontdb_mut() - .set_fantasy_family(cstr_to_str(family).unwrap().to_string()); - } -} - -/// @brief Sets the `monospace` font family. -/// -/// Must be UTF-8. NULL is not allowed. -/// -/// Has no effect when the `text` feature is not enabled. -/// -/// Default: Courier New -#[no_mangle] -#[allow(unused_variables)] -pub extern "C" fn resvg_options_set_monospace_family( - opt: *mut resvg_options, - family: *const c_char, -) { - #[cfg(feature = "text")] - { - cast_opt(opt) - .fontdb_mut() - .set_monospace_family(cstr_to_str(family).unwrap().to_string()); - } -} - -/// @brief Sets a comma-separated list of languages. -/// -/// Will be used to resolve a `systemLanguage` conditional attribute. -/// -/// Example: en,en-US. -/// -/// Must be UTF-8. Can be NULL. -/// -/// Default: en -#[no_mangle] -pub extern "C" fn resvg_options_set_languages(opt: *mut resvg_options, languages: *const c_char) { - if languages.is_null() { - cast_opt(opt).languages = Vec::new(); - return; - } - - let languages_str = match cstr_to_str(languages) { - Some(v) => v, - None => return, - }; - - let mut languages = Vec::new(); - for lang in languages_str.split(',') { - languages.push(lang.trim().to_string()); - } - - cast_opt(opt).languages = languages; -} - -/// @brief A shape rendering method. -#[repr(C)] -#[allow(missing_docs)] -#[derive(Copy, Clone)] -pub enum resvg_shape_rendering { - OPTIMIZE_SPEED, - CRISP_EDGES, - GEOMETRIC_PRECISION, -} - -/// @brief Sets the default shape rendering method. -/// -/// Will be used when an SVG element's `shape-rendering` property is set to `auto`. -/// -/// Default: `RESVG_SHAPE_RENDERING_GEOMETRIC_PRECISION` -#[no_mangle] -pub extern "C" fn resvg_options_set_shape_rendering_mode( - opt: *mut resvg_options, - mode: resvg_shape_rendering, -) { - cast_opt(opt).shape_rendering = match mode as i32 { - 0 => usvg::ShapeRendering::OptimizeSpeed, - 1 => usvg::ShapeRendering::CrispEdges, - 2 => usvg::ShapeRendering::GeometricPrecision, - _ => return, - } -} - -/// @brief A text rendering method. -#[repr(C)] -#[allow(missing_docs)] -#[derive(Copy, Clone)] -pub enum resvg_text_rendering { - OPTIMIZE_SPEED, - OPTIMIZE_LEGIBILITY, - GEOMETRIC_PRECISION, -} - -/// @brief Sets the default text rendering method. -/// -/// Will be used when an SVG element's `text-rendering` property is set to `auto`. -/// -/// Default: `RESVG_TEXT_RENDERING_OPTIMIZE_LEGIBILITY` -#[no_mangle] -pub extern "C" fn resvg_options_set_text_rendering_mode( - opt: *mut resvg_options, - mode: resvg_text_rendering, -) { - cast_opt(opt).text_rendering = match mode as i32 { - 0 => usvg::TextRendering::OptimizeSpeed, - 1 => usvg::TextRendering::OptimizeLegibility, - 2 => usvg::TextRendering::GeometricPrecision, - _ => return, - } -} - -/// @brief A image rendering method. -#[repr(C)] -#[allow(missing_docs)] -#[derive(Copy, Clone)] -pub enum resvg_image_rendering { - OPTIMIZE_QUALITY, - OPTIMIZE_SPEED, -} - -/// @brief Sets the default image rendering method. -/// -/// Will be used when an SVG element's `image-rendering` property is set to `auto`. -/// -/// Default: `RESVG_IMAGE_RENDERING_OPTIMIZE_QUALITY` -#[no_mangle] -pub extern "C" fn resvg_options_set_image_rendering_mode( - opt: *mut resvg_options, - mode: resvg_image_rendering, -) { - cast_opt(opt).image_rendering = match mode as i32 { - 0 => usvg::ImageRendering::OptimizeQuality, - 1 => usvg::ImageRendering::OptimizeSpeed, - _ => return, - } -} - -/// @brief Loads a font data into the internal fonts database. -/// -/// Prints a warning into the log when the data is not a valid TrueType font. -/// -/// Has no effect when the `text` feature is not enabled. -#[no_mangle] -#[allow(unused_variables)] -pub extern "C" fn resvg_options_load_font_data( - opt: *mut resvg_options, - data: *const c_char, - len: usize, -) { - #[cfg(feature = "text")] - { - let data = unsafe { slice::from_raw_parts(data as *const u8, len) }; - cast_opt(opt).fontdb_mut().load_font_data(data.to_vec()) - } -} - -/// @brief Loads a font file into the internal fonts database. -/// -/// Prints a warning into the log when the data is not a valid TrueType font. -/// -/// Has no effect when the `text` feature is not enabled. -/// -/// @return #resvg_error with RESVG_OK, RESVG_ERROR_NOT_AN_UTF8_STR or RESVG_ERROR_FILE_OPEN_FAILED -#[no_mangle] -#[allow(unused_variables)] -pub extern "C" fn resvg_options_load_font_file( - opt: *mut resvg_options, - file_path: *const c_char, -) -> i32 { - #[cfg(feature = "text")] - { - let file_path = match cstr_to_str(file_path) { - Some(v) => v, - None => return resvg_error::NOT_AN_UTF8_STR as i32, - }; - - if cast_opt(opt).fontdb_mut().load_font_file(file_path).is_ok() { - resvg_error::OK as i32 - } else { - resvg_error::FILE_OPEN_FAILED as i32 - } - } - - #[cfg(not(feature = "text"))] - { - resvg_error::OK as i32 - } -} - -/// @brief Loads system fonts into the internal fonts database. -/// -/// This method is very IO intensive. -/// -/// This method should be executed only once per #resvg_options. -/// -/// The system scanning is not perfect, so some fonts may be omitted. -/// Please send a bug report in this case. -/// -/// Prints warnings into the log. -/// -/// Has no effect when the `text` feature is not enabled. -#[no_mangle] -#[allow(unused_variables)] -pub extern "C" fn resvg_options_load_system_fonts(opt: *mut resvg_options) { - #[cfg(feature = "text")] - { - cast_opt(opt).fontdb_mut().load_system_fonts(); - } -} - -/// @brief Destroys the #resvg_options. -#[no_mangle] -pub extern "C" fn resvg_options_destroy(opt: *mut resvg_options) { - unsafe { - assert!(!opt.is_null()); - let _ = Box::from_raw(opt); - }; -} - -// TODO: use resvg::Tree -/// @brief An opaque pointer to the rendering tree. -pub struct resvg_render_tree(pub usvg::Tree); - -/// @brief Creates #resvg_render_tree from file. -/// -/// .svg and .svgz files are supported. -/// -/// See #resvg_is_image_empty for details. -/// -/// @param file_path UTF-8 file path. -/// @param opt Rendering options. Must not be NULL. -/// @param tree Parsed render tree. Should be destroyed via #resvg_tree_destroy. -/// @return #resvg_error -#[no_mangle] -pub extern "C" fn resvg_parse_tree_from_file( - file_path: *const c_char, - opt: *const resvg_options, - tree: *mut *mut resvg_render_tree, -) -> i32 { - let file_path = match cstr_to_str(file_path) { - Some(v) => v, - None => return resvg_error::NOT_AN_UTF8_STR as i32, - }; - - let raw_opt = unsafe { - assert!(!opt.is_null()); - &*opt - }; - - let file_data = match std::fs::read(file_path) { - Ok(tree) => tree, - Err(_) => return resvg_error::FILE_OPEN_FAILED as i32, - }; - - let utree = usvg::Tree::from_data(&file_data, &raw_opt.options); - - let utree = match utree { - Ok(tree) => tree, - Err(e) => return convert_error(e) as i32, - }; - - let tree_box = Box::new(resvg_render_tree(utree)); - unsafe { - *tree = Box::into_raw(tree_box); - } - - resvg_error::OK as i32 -} - -/// @brief Creates #resvg_render_tree from data. -/// -/// See #resvg_is_image_empty for details. -/// -/// @param data SVG data. Can contain SVG string or gzip compressed data. Must not be NULL. -/// @param len Data length. -/// @param opt Rendering options. Must not be NULL. -/// @param tree Parsed render tree. Should be destroyed via #resvg_tree_destroy. -/// @return #resvg_error -#[no_mangle] -pub extern "C" fn resvg_parse_tree_from_data( - data: *const c_char, - len: usize, - opt: *const resvg_options, - tree: *mut *mut resvg_render_tree, -) -> i32 { - let data = unsafe { slice::from_raw_parts(data as *const u8, len) }; - - let raw_opt = unsafe { - assert!(!opt.is_null()); - &*opt - }; - - let utree = usvg::Tree::from_data(data, &raw_opt.options); - - let utree = match utree { - Ok(tree) => tree, - Err(e) => return convert_error(e) as i32, - }; - - let tree_box = Box::new(resvg_render_tree(utree)); - unsafe { - *tree = Box::into_raw(tree_box); - } - - resvg_error::OK as i32 -} - -/// @brief Checks that tree has any nodes. -/// -/// @param tree Render tree. -/// @return Returns `true` if tree has no nodes. -#[no_mangle] -pub extern "C" fn resvg_is_image_empty(tree: *const resvg_render_tree) -> bool { - let tree = unsafe { - assert!(!tree.is_null()); - &*tree - }; - - !tree.0.root().has_children() -} - -/// @brief Returns an image size. -/// -/// The size of a canvas that required to render this SVG. -/// -/// The `width` and `height` attributes in SVG. -/// -/// @param tree Render tree. -/// @return Image size. -#[no_mangle] -pub extern "C" fn resvg_get_image_size(tree: *const resvg_render_tree) -> resvg_size { - let tree = unsafe { - assert!(!tree.is_null()); - &*tree - }; - - let size = tree.0.size(); - - resvg_size { - width: size.width(), - height: size.height(), - } -} - -/// @brief Returns an image bounding box. -/// -/// Can be smaller or bigger than a `viewbox`. -/// -/// @param tree Render tree. -/// @param bbox Image's bounding box. -/// @return `false` if an image has no elements. -#[no_mangle] -pub extern "C" fn resvg_get_image_bbox( - tree: *const resvg_render_tree, - bbox: *mut resvg_rect, -) -> bool { - let tree = unsafe { - assert!(!tree.is_null()); - &*tree - }; - - if let Some(r) = tree.0.root().abs_bounding_box().to_non_zero_rect() { - unsafe { - *bbox = resvg_rect { - x: r.x(), - y: r.y(), - width: r.width(), - height: r.height(), - } - } - - true - } else { - false - } -} - -/// @brief Returns `true` if a renderable node with such an ID exists. -/// -/// @param tree Render tree. -/// @param id Node's ID. UTF-8 string. Must not be NULL. -/// @return `true` if a node exists. -/// @return `false` if a node doesn't exist or ID isn't a UTF-8 string. -/// @return `false` if a node exists, but not renderable. -#[no_mangle] -pub extern "C" fn resvg_node_exists(tree: *const resvg_render_tree, id: *const c_char) -> bool { - let id = match cstr_to_str(id) { - Some(v) => v, - None => { - log::warn!("Provided ID is no an UTF-8 string."); - return false; - } - }; - - let tree = unsafe { - assert!(!tree.is_null()); - &*tree - }; - - tree.0.node_by_id(id).is_some() -} - -/// @brief Returns node's transform by ID. -/// -/// @param tree Render tree. -/// @param id Node's ID. UTF-8 string. Must not be NULL. -/// @param transform Node's transform. -/// @return `true` if a node exists. -/// @return `false` if a node doesn't exist or ID isn't a UTF-8 string. -/// @return `false` if a node exists, but not renderable. -#[no_mangle] -pub extern "C" fn resvg_get_node_transform( - tree: *const resvg_render_tree, - id: *const c_char, - transform: *mut resvg_transform, -) -> bool { - let id = match cstr_to_str(id) { - Some(v) => v, - None => { - log::warn!("Provided ID is no an UTF-8 string."); - return false; - } - }; - - let tree = unsafe { - assert!(!tree.is_null()); - &*tree - }; - - if let Some(node) = tree.0.node_by_id(id) { - let abs_ts = node.abs_transform(); - - unsafe { - *transform = resvg_transform { - a: abs_ts.sx, - b: abs_ts.ky, - c: abs_ts.kx, - d: abs_ts.sy, - e: abs_ts.tx, - f: abs_ts.ty, - } - } - - return true; - } - - false -} - -/// @brief Returns node's bounding box in canvas coordinates by ID. -/// -/// @param tree Render tree. -/// @param id Node's ID. Must not be NULL. -/// @param bbox Node's bounding box. -/// @return `false` if a node with such an ID does not exist -/// @return `false` if ID isn't a UTF-8 string. -/// @return `false` if ID is an empty string -#[no_mangle] -pub extern "C" fn resvg_get_node_bbox( - tree: *const resvg_render_tree, - id: *const c_char, - bbox: *mut resvg_rect, -) -> bool { - get_node_bbox(tree, id, bbox, &|node| node.abs_bounding_box()) -} - -/// @brief Returns node's bounding box, including stroke, in canvas coordinates by ID. -/// -/// @param tree Render tree. -/// @param id Node's ID. Must not be NULL. -/// @param bbox Node's bounding box. -/// @return `false` if a node with such an ID does not exist -/// @return `false` if ID isn't a UTF-8 string. -/// @return `false` if ID is an empty string -#[no_mangle] -pub extern "C" fn resvg_get_node_stroke_bbox( - tree: *const resvg_render_tree, - id: *const c_char, - bbox: *mut resvg_rect, -) -> bool { - get_node_bbox(tree, id, bbox, &|node| node.abs_stroke_bounding_box()) -} - -fn get_node_bbox( - tree: *const resvg_render_tree, - id: *const c_char, - bbox: *mut resvg_rect, - f: &dyn Fn(&usvg::Node) -> usvg::Rect, -) -> bool { - let id = match cstr_to_str(id) { - Some(v) => v, - None => { - log::warn!("Provided ID is no an UTF-8 string."); - return false; - } - }; - - if id.is_empty() { - log::warn!("Node ID must not be empty."); - return false; - } - - let tree = unsafe { - assert!(!tree.is_null()); - &*tree - }; - - match tree.0.node_by_id(id) { - Some(node) => { - let r = f(node); - unsafe { - *bbox = resvg_rect { - x: r.x(), - y: r.y(), - width: r.width(), - height: r.height(), - } - } - true - } - None => { - log::warn!("No node with '{}' ID is in the tree.", id); - false - } - } -} - -/// @brief Destroys the #resvg_render_tree. -#[no_mangle] -pub extern "C" fn resvg_tree_destroy(tree: *mut resvg_render_tree) { - unsafe { - assert!(!tree.is_null()); - let _ = Box::from_raw(tree); - }; -} - -fn cstr_to_str(text: *const c_char) -> Option<&'static str> { - let text = unsafe { - assert!(!text.is_null()); - CStr::from_ptr(text) - }; - - text.to_str().ok() -} - -fn convert_error(e: usvg::Error) -> resvg_error { - match e { - usvg::Error::NotAnUtf8Str => resvg_error::NOT_AN_UTF8_STR, - usvg::Error::MalformedGZip => resvg_error::MALFORMED_GZIP, - usvg::Error::ElementsLimitReached => resvg_error::ELEMENTS_LIMIT_REACHED, - usvg::Error::InvalidSize => resvg_error::INVALID_SIZE, - usvg::Error::ParsingFailed(_) => resvg_error::PARSING_FAILED, - } -} - -/// @brief Renders the #resvg_render_tree onto the pixmap. -/// -/// @param tree A render tree. -/// @param transform A root SVG transform. Can be used to position SVG inside the `pixmap`. -/// @param width Pixmap width. -/// @param height Pixmap height. -/// @param pixmap Pixmap data. Should have width*height*4 size and contain -/// premultiplied RGBA8888 pixels. -#[no_mangle] -pub extern "C" fn resvg_render( - tree: *const resvg_render_tree, - transform: resvg_transform, - width: u32, - height: u32, - pixmap: *mut c_char, -) { - let tree = unsafe { - assert!(!tree.is_null()); - &*tree - }; - - let pixmap_len = width as usize * height as usize * tiny_skia::BYTES_PER_PIXEL; - let pixmap: &mut [u8] = - unsafe { std::slice::from_raw_parts_mut(pixmap as *mut u8, pixmap_len) }; - let mut pixmap = tiny_skia::PixmapMut::from_bytes(pixmap, width, height).unwrap(); - - resvg::render(&tree.0, transform.to_tiny_skia(), &mut pixmap) -} - -/// @brief Renders a Node by ID onto the image. -/// -/// @param tree A render tree. -/// @param id Node's ID. Must not be NULL. -/// @param transform A root SVG transform. Can be used to position SVG inside the `pixmap`. -/// @param width Pixmap width. -/// @param height Pixmap height. -/// @param pixmap Pixmap data. Should have width*height*4 size and contain -/// premultiplied RGBA8888 pixels. -/// @return `false` when `id` is not a non-empty UTF-8 string. -/// @return `false` when the selected `id` is not present. -/// @return `false` when an element has a zero bbox. -#[no_mangle] -pub extern "C" fn resvg_render_node( - tree: *const resvg_render_tree, - id: *const c_char, - transform: resvg_transform, - width: u32, - height: u32, - pixmap: *mut c_char, -) -> bool { - let tree = unsafe { - assert!(!tree.is_null()); - &*tree - }; - - let id = match cstr_to_str(id) { - Some(v) => v, - None => return false, - }; - - if id.is_empty() { - log::warn!("Node with an empty ID cannot be rendered."); - return false; - } - - if let Some(node) = tree.0.node_by_id(id) { - let pixmap_len = width as usize * height as usize * tiny_skia::BYTES_PER_PIXEL; - let pixmap: &mut [u8] = - unsafe { std::slice::from_raw_parts_mut(pixmap as *mut u8, pixmap_len) }; - let mut pixmap = tiny_skia::PixmapMut::from_bytes(pixmap, width, height).unwrap(); - - resvg::render_node(node, transform.to_tiny_skia(), &mut pixmap).is_some() - } else { - log::warn!("A node with '{}' ID wasn't found.", id); - false - } -} - -/// A simple stderr logger. -static LOGGER: SimpleLogger = SimpleLogger; -struct SimpleLogger; -impl log::Log for SimpleLogger { - fn enabled(&self, metadata: &log::Metadata) -> bool { - metadata.level() <= log::LevelFilter::Warn - } - - fn log(&self, record: &log::Record) { - if self.enabled(record.metadata()) { - let target = if record.target().len() > 0 { - record.target() - } else { - record.module_path().unwrap_or_default() - }; - - let line = record.line().unwrap_or(0); - let args = record.args(); - - match record.level() { - log::Level::Error => eprintln!("Error (in {}:{}): {}", target, line, args), - log::Level::Warn => eprintln!("Warning (in {}:{}): {}", target, line, args), - log::Level::Info => eprintln!("Info (in {}:{}): {}", target, line, args), - log::Level::Debug => eprintln!("Debug (in {}:{}): {}", target, line, args), - log::Level::Trace => eprintln!("Trace (in {}:{}): {}", target, line, args), - } - } - } - - fn flush(&self) {} -} diff --git a/resvg-0.43.0/crates/c-api/resvg.h b/resvg-0.43.0/crates/c-api/resvg.h deleted file mode 100644 index 54ce69c..0000000 --- a/resvg-0.43.0/crates/c-api/resvg.h +++ /dev/null @@ -1,493 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -/** - * @file resvg.h - * - * resvg C API - */ - -#ifndef RESVG_H -#define RESVG_H - -#include -#include - -#define RESVG_MAJOR_VERSION 0 -#define RESVG_MINOR_VERSION 43 -#define RESVG_PATCH_VERSION 0 -#define RESVG_VERSION "0.43.0" - -/** - * @brief List of possible errors. - */ -typedef enum { - /** - * Everything is ok. - */ - RESVG_OK = 0, - /** - * Only UTF-8 content are supported. - */ - RESVG_ERROR_NOT_AN_UTF8_STR, - /** - * Failed to open the provided file. - */ - RESVG_ERROR_FILE_OPEN_FAILED, - /** - * Compressed SVG must use the GZip algorithm. - */ - RESVG_ERROR_MALFORMED_GZIP, - /** - * We do not allow SVG with more than 1_000_000 elements for security reasons. - */ - RESVG_ERROR_ELEMENTS_LIMIT_REACHED, - /** - * SVG doesn't have a valid size. - * - * Occurs when width and/or height are <= 0. - * - * Also occurs if width, height and viewBox are not set. - */ - RESVG_ERROR_INVALID_SIZE, - /** - * Failed to parse an SVG data. - */ - RESVG_ERROR_PARSING_FAILED, -} resvg_error; - -/** - * @brief A image rendering method. - */ -typedef enum { - RESVG_IMAGE_RENDERING_OPTIMIZE_QUALITY, - RESVG_IMAGE_RENDERING_OPTIMIZE_SPEED, -} resvg_image_rendering; - -/** - * @brief A shape rendering method. - */ -typedef enum { - RESVG_SHAPE_RENDERING_OPTIMIZE_SPEED, - RESVG_SHAPE_RENDERING_CRISP_EDGES, - RESVG_SHAPE_RENDERING_GEOMETRIC_PRECISION, -} resvg_shape_rendering; - -/** - * @brief A text rendering method. - */ -typedef enum { - RESVG_TEXT_RENDERING_OPTIMIZE_SPEED, - RESVG_TEXT_RENDERING_OPTIMIZE_LEGIBILITY, - RESVG_TEXT_RENDERING_GEOMETRIC_PRECISION, -} resvg_text_rendering; - -/** - * @brief An SVG to #resvg_render_tree conversion options. - * - * Also, contains a fonts database used during text to path conversion. - * The database is empty by default. - */ -typedef struct resvg_options resvg_options; - -/** - * @brief An opaque pointer to the rendering tree. - */ -typedef struct resvg_render_tree resvg_render_tree; - -/** - * @brief A 2D transform representation. - */ -typedef struct { - float a; - float b; - float c; - float d; - float e; - float f; -} resvg_transform; - -/** - * @brief A size representation. - */ -typedef struct { - float width; - float height; -} resvg_size; - -/** - * @brief A rectangle representation. - */ -typedef struct { - float x; - float y; - float width; - float height; -} resvg_rect; - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -/** - * @brief Creates an identity transform. - */ -resvg_transform resvg_transform_identity(void); - -/** - * @brief Initializes the library log. - * - * Use it if you want to see any warnings. - * - * Must be called only once. - * - * All warnings will be printed to the `stderr`. - */ -void resvg_init_log(void); - -/** - * @brief Creates a new #resvg_options object. - * - * Should be destroyed via #resvg_options_destroy. - */ -resvg_options *resvg_options_create(void); - -/** - * @brief Sets a directory that will be used during relative paths resolving. - * - * Expected to be the same as the directory that contains the SVG file, - * but can be set to any. - * - * Must be UTF-8. Can be set to NULL. - * - * Default: NULL - */ -void resvg_options_set_resources_dir(resvg_options *opt, const char *path); - -/** - * @brief Sets the target DPI. - * - * Impact units conversion. - * - * Default: 96 - */ -void resvg_options_set_dpi(resvg_options *opt, float dpi); - -/** - * @brief Sets the default font family. - * - * Will be used when no `font-family` attribute is set in the SVG. - * - * Must be UTF-8. NULL is not allowed. - * - * Default: Times New Roman - */ -void resvg_options_set_font_family(resvg_options *opt, const char *family); - -/** - * @brief Sets the default font size. - * - * Will be used when no `font-size` attribute is set in the SVG. - * - * Default: 12 - */ -void resvg_options_set_font_size(resvg_options *opt, float size); - -/** - * @brief Sets the `serif` font family. - * - * Must be UTF-8. NULL is not allowed. - * - * Has no effect when the `text` feature is not enabled. - * - * Default: Times New Roman - */ -void resvg_options_set_serif_family(resvg_options *opt, const char *family); - -/** - * @brief Sets the `sans-serif` font family. - * - * Must be UTF-8. NULL is not allowed. - * - * Has no effect when the `text` feature is not enabled. - * - * Default: Arial - */ -void resvg_options_set_sans_serif_family(resvg_options *opt, const char *family); - -/** - * @brief Sets the `cursive` font family. - * - * Must be UTF-8. NULL is not allowed. - * - * Has no effect when the `text` feature is not enabled. - * - * Default: Comic Sans MS - */ -void resvg_options_set_cursive_family(resvg_options *opt, const char *family); - -/** - * @brief Sets the `fantasy` font family. - * - * Must be UTF-8. NULL is not allowed. - * - * Has no effect when the `text` feature is not enabled. - * - * Default: Papyrus on macOS, Impact on other OS'es - */ -void resvg_options_set_fantasy_family(resvg_options *opt, const char *family); - -/** - * @brief Sets the `monospace` font family. - * - * Must be UTF-8. NULL is not allowed. - * - * Has no effect when the `text` feature is not enabled. - * - * Default: Courier New - */ -void resvg_options_set_monospace_family(resvg_options *opt, const char *family); - -/** - * @brief Sets a comma-separated list of languages. - * - * Will be used to resolve a `systemLanguage` conditional attribute. - * - * Example: en,en-US. - * - * Must be UTF-8. Can be NULL. - * - * Default: en - */ -void resvg_options_set_languages(resvg_options *opt, const char *languages); - -/** - * @brief Sets the default shape rendering method. - * - * Will be used when an SVG element's `shape-rendering` property is set to `auto`. - * - * Default: `RESVG_SHAPE_RENDERING_GEOMETRIC_PRECISION` - */ -void resvg_options_set_shape_rendering_mode(resvg_options *opt, resvg_shape_rendering mode); - -/** - * @brief Sets the default text rendering method. - * - * Will be used when an SVG element's `text-rendering` property is set to `auto`. - * - * Default: `RESVG_TEXT_RENDERING_OPTIMIZE_LEGIBILITY` - */ -void resvg_options_set_text_rendering_mode(resvg_options *opt, resvg_text_rendering mode); - -/** - * @brief Sets the default image rendering method. - * - * Will be used when an SVG element's `image-rendering` property is set to `auto`. - * - * Default: `RESVG_IMAGE_RENDERING_OPTIMIZE_QUALITY` - */ -void resvg_options_set_image_rendering_mode(resvg_options *opt, resvg_image_rendering mode); - -/** - * @brief Loads a font data into the internal fonts database. - * - * Prints a warning into the log when the data is not a valid TrueType font. - * - * Has no effect when the `text` feature is not enabled. - */ -void resvg_options_load_font_data(resvg_options *opt, const char *data, uintptr_t len); - -/** - * @brief Loads a font file into the internal fonts database. - * - * Prints a warning into the log when the data is not a valid TrueType font. - * - * Has no effect when the `text` feature is not enabled. - * - * @return #resvg_error with RESVG_OK, RESVG_ERROR_NOT_AN_UTF8_STR or RESVG_ERROR_FILE_OPEN_FAILED - */ -int32_t resvg_options_load_font_file(resvg_options *opt, const char *file_path); - -/** - * @brief Loads system fonts into the internal fonts database. - * - * This method is very IO intensive. - * - * This method should be executed only once per #resvg_options. - * - * The system scanning is not perfect, so some fonts may be omitted. - * Please send a bug report in this case. - * - * Prints warnings into the log. - * - * Has no effect when the `text` feature is not enabled. - */ -void resvg_options_load_system_fonts(resvg_options *opt); - -/** - * @brief Destroys the #resvg_options. - */ -void resvg_options_destroy(resvg_options *opt); - -/** - * @brief Creates #resvg_render_tree from file. - * - * .svg and .svgz files are supported. - * - * See #resvg_is_image_empty for details. - * - * @param file_path UTF-8 file path. - * @param opt Rendering options. Must not be NULL. - * @param tree Parsed render tree. Should be destroyed via #resvg_tree_destroy. - * @return #resvg_error - */ -int32_t resvg_parse_tree_from_file(const char *file_path, - const resvg_options *opt, - resvg_render_tree **tree); - -/** - * @brief Creates #resvg_render_tree from data. - * - * See #resvg_is_image_empty for details. - * - * @param data SVG data. Can contain SVG string or gzip compressed data. Must not be NULL. - * @param len Data length. - * @param opt Rendering options. Must not be NULL. - * @param tree Parsed render tree. Should be destroyed via #resvg_tree_destroy. - * @return #resvg_error - */ -int32_t resvg_parse_tree_from_data(const char *data, - uintptr_t len, - const resvg_options *opt, - resvg_render_tree **tree); - -/** - * @brief Checks that tree has any nodes. - * - * @param tree Render tree. - * @return Returns `true` if tree has no nodes. - */ -bool resvg_is_image_empty(const resvg_render_tree *tree); - -/** - * @brief Returns an image size. - * - * The size of a canvas that required to render this SVG. - * - * The `width` and `height` attributes in SVG. - * - * @param tree Render tree. - * @return Image size. - */ -resvg_size resvg_get_image_size(const resvg_render_tree *tree); - -/** - * @brief Returns an image bounding box. - * - * Can be smaller or bigger than a `viewbox`. - * - * @param tree Render tree. - * @param bbox Image's bounding box. - * @return `false` if an image has no elements. - */ -bool resvg_get_image_bbox(const resvg_render_tree *tree, resvg_rect *bbox); - -/** - * @brief Returns `true` if a renderable node with such an ID exists. - * - * @param tree Render tree. - * @param id Node's ID. UTF-8 string. Must not be NULL. - * @return `true` if a node exists. - * @return `false` if a node doesn't exist or ID isn't a UTF-8 string. - * @return `false` if a node exists, but not renderable. - */ -bool resvg_node_exists(const resvg_render_tree *tree, const char *id); - -/** - * @brief Returns node's transform by ID. - * - * @param tree Render tree. - * @param id Node's ID. UTF-8 string. Must not be NULL. - * @param transform Node's transform. - * @return `true` if a node exists. - * @return `false` if a node doesn't exist or ID isn't a UTF-8 string. - * @return `false` if a node exists, but not renderable. - */ -bool resvg_get_node_transform(const resvg_render_tree *tree, - const char *id, - resvg_transform *transform); - -/** - * @brief Returns node's bounding box in canvas coordinates by ID. - * - * @param tree Render tree. - * @param id Node's ID. Must not be NULL. - * @param bbox Node's bounding box. - * @return `false` if a node with such an ID does not exist - * @return `false` if ID isn't a UTF-8 string. - * @return `false` if ID is an empty string - */ -bool resvg_get_node_bbox(const resvg_render_tree *tree, const char *id, resvg_rect *bbox); - -/** - * @brief Returns node's bounding box, including stroke, in canvas coordinates by ID. - * - * @param tree Render tree. - * @param id Node's ID. Must not be NULL. - * @param bbox Node's bounding box. - * @return `false` if a node with such an ID does not exist - * @return `false` if ID isn't a UTF-8 string. - * @return `false` if ID is an empty string - */ -bool resvg_get_node_stroke_bbox(const resvg_render_tree *tree, const char *id, resvg_rect *bbox); - -/** - * @brief Destroys the #resvg_render_tree. - */ -void resvg_tree_destroy(resvg_render_tree *tree); - -/** - * @brief Renders the #resvg_render_tree onto the pixmap. - * - * @param tree A render tree. - * @param transform A root SVG transform. Can be used to position SVG inside the `pixmap`. - * @param width Pixmap width. - * @param height Pixmap height. - * @param pixmap Pixmap data. Should have width*height*4 size and contain - * premultiplied RGBA8888 pixels. - */ -void resvg_render(const resvg_render_tree *tree, - resvg_transform transform, - uint32_t width, - uint32_t height, - char *pixmap); - -/** - * @brief Renders a Node by ID onto the image. - * - * @param tree A render tree. - * @param id Node's ID. Must not be NULL. - * @param transform A root SVG transform. Can be used to position SVG inside the `pixmap`. - * @param width Pixmap width. - * @param height Pixmap height. - * @param pixmap Pixmap data. Should have width*height*4 size and contain - * premultiplied RGBA8888 pixels. - * @return `false` when `id` is not a non-empty UTF-8 string. - * @return `false` when the selected `id` is not present. - * @return `false` when an element has a zero bbox. - */ -bool resvg_render_node(const resvg_render_tree *tree, - const char *id, - resvg_transform transform, - uint32_t width, - uint32_t height, - char *pixmap); - -#ifdef __cplusplus -} // extern "C" -#endif // __cplusplus - -#endif /* RESVG_H */ diff --git a/resvg-0.43.0/crates/resvg/Cargo.toml b/resvg-0.43.0/crates/resvg/Cargo.toml deleted file mode 100644 index f7a87dd..0000000 --- a/resvg-0.43.0/crates/resvg/Cargo.toml +++ /dev/null @@ -1,44 +0,0 @@ -[package] -name = "resvg" -version = "0.43.0" -authors = ["Yevhenii Reizner "] -keywords = ["svg", "render", "raster"] -license = "MPL-2.0" -edition = "2021" -description = "An SVG rendering library." -repository = "https://github.com/RazrFalcon/resvg" -exclude = ["tests"] -workspace = "../.." - -[[bin]] -name = "resvg" -required-features = ["text", "system-fonts", "memmap-fonts"] - -[dependencies] -gif = { version = "0.13", optional = true } -image-webp = { version = "0.1.3", optional = true } -log = "0.4" -pico-args = { version = "0.5", features = ["eq-separator"] } -rgb = "0.8" -svgtypes = "0.15.1" -tiny-skia = "0.11.4" -usvg = { path = "../usvg", version = "0.43.0", default-features = false } -zune-jpeg = { version = "0.4", optional = true } - -[dev-dependencies] -once_cell = "1.5" -png = "0.17" - -[features] -default = ["text", "system-fonts", "memmap-fonts", "raster-images"] -# Enables SVG Text support. -# Adds around 400KiB to your binary. -text = ["usvg/text"] -# Enables system fonts loading (only for `text`). -system-fonts = ["usvg/system-fonts"] -# Enables font files memmaping for faster loading (only for `text`). -memmap-fonts = ["usvg/memmap-fonts"] -# Enables decoding and rendering of raster images. -# When disabled, `image` elements with SVG data will still be rendered. -# Adds around 200KiB to your binary. -raster-images = ["gif", "image-webp", "dep:zune-jpeg"] diff --git a/resvg-0.43.0/crates/resvg/LICENSE.txt b/resvg-0.43.0/crates/resvg/LICENSE.txt deleted file mode 100644 index 14e2f77..0000000 --- a/resvg-0.43.0/crates/resvg/LICENSE.txt +++ /dev/null @@ -1,373 +0,0 @@ -Mozilla Public License Version 2.0 -================================== - -1. Definitions --------------- - -1.1. "Contributor" - means each individual or legal entity that creates, contributes to - the creation of, or owns Covered Software. - -1.2. "Contributor Version" - means the combination of the Contributions of others (if any) used - by a Contributor and that particular Contributor's Contribution. - -1.3. "Contribution" - means Covered Software of a particular Contributor. - -1.4. "Covered Software" - means Source Code Form to which the initial Contributor has attached - the notice in Exhibit A, the Executable Form of such Source Code - Form, and Modifications of such Source Code Form, in each case - including portions thereof. - -1.5. "Incompatible With Secondary Licenses" - means - - (a) that the initial Contributor has attached the notice described - in Exhibit B to the Covered Software; or - - (b) that the Covered Software was made available under the terms of - version 1.1 or earlier of the License, but not also under the - terms of a Secondary License. - -1.6. "Executable Form" - means any form of the work other than Source Code Form. - -1.7. "Larger Work" - means a work that combines Covered Software with other material, in - a separate file or files, that is not Covered Software. - -1.8. "License" - means this document. - -1.9. "Licensable" - means having the right to grant, to the maximum extent possible, - whether at the time of the initial grant or subsequently, any and - all of the rights conveyed by this License. - -1.10. "Modifications" - means any of the following: - - (a) any file in Source Code Form that results from an addition to, - deletion from, or modification of the contents of Covered - Software; or - - (b) any new file in Source Code Form that contains any Covered - Software. - -1.11. "Patent Claims" of a Contributor - means any patent claim(s), including without limitation, method, - process, and apparatus claims, in any patent Licensable by such - Contributor that would be infringed, but for the grant of the - License, by the making, using, selling, offering for sale, having - made, import, or transfer of either its Contributions or its - Contributor Version. - -1.12. "Secondary License" - means either the GNU General Public License, Version 2.0, the GNU - Lesser General Public License, Version 2.1, the GNU Affero General - Public License, Version 3.0, or any later versions of those - licenses. - -1.13. "Source Code Form" - means the form of the work preferred for making modifications. - -1.14. "You" (or "Your") - means an individual or a legal entity exercising rights under this - License. For legal entities, "You" includes any entity that - controls, is controlled by, or is under common control with You. For - purposes of this definition, "control" means (a) the power, direct - or indirect, to cause the direction or management of such entity, - whether by contract or otherwise, or (b) ownership of more than - fifty percent (50%) of the outstanding shares or beneficial - ownership of such entity. - -2. License Grants and Conditions --------------------------------- - -2.1. Grants - -Each Contributor hereby grants You a world-wide, royalty-free, -non-exclusive license: - -(a) under intellectual property rights (other than patent or trademark) - Licensable by such Contributor to use, reproduce, make available, - modify, display, perform, distribute, and otherwise exploit its - Contributions, either on an unmodified basis, with Modifications, or - as part of a Larger Work; and - -(b) under Patent Claims of such Contributor to make, use, sell, offer - for sale, have made, import, and otherwise transfer either its - Contributions or its Contributor Version. - -2.2. Effective Date - -The licenses granted in Section 2.1 with respect to any Contribution -become effective for each Contribution on the date the Contributor first -distributes such Contribution. - -2.3. Limitations on Grant Scope - -The licenses granted in this Section 2 are the only rights granted under -this License. No additional rights or licenses will be implied from the -distribution or licensing of Covered Software under this License. -Notwithstanding Section 2.1(b) above, no patent license is granted by a -Contributor: - -(a) for any code that a Contributor has removed from Covered Software; - or - -(b) for infringements caused by: (i) Your and any other third party's - modifications of Covered Software, or (ii) the combination of its - Contributions with other software (except as part of its Contributor - Version); or - -(c) under Patent Claims infringed by Covered Software in the absence of - its Contributions. - -This License does not grant any rights in the trademarks, service marks, -or logos of any Contributor (except as may be necessary to comply with -the notice requirements in Section 3.4). - -2.4. Subsequent Licenses - -No Contributor makes additional grants as a result of Your choice to -distribute the Covered Software under a subsequent version of this -License (see Section 10.2) or under the terms of a Secondary License (if -permitted under the terms of Section 3.3). - -2.5. Representation - -Each Contributor represents that the Contributor believes its -Contributions are its original creation(s) or it has sufficient rights -to grant the rights to its Contributions conveyed by this License. - -2.6. Fair Use - -This License is not intended to limit any rights You have under -applicable copyright doctrines of fair use, fair dealing, or other -equivalents. - -2.7. Conditions - -Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted -in Section 2.1. - -3. Responsibilities -------------------- - -3.1. Distribution of Source Form - -All distribution of Covered Software in Source Code Form, including any -Modifications that You create or to which You contribute, must be under -the terms of this License. You must inform recipients that the Source -Code Form of the Covered Software is governed by the terms of this -License, and how they can obtain a copy of this License. You may not -attempt to alter or restrict the recipients' rights in the Source Code -Form. - -3.2. Distribution of Executable Form - -If You distribute Covered Software in Executable Form then: - -(a) such Covered Software must also be made available in Source Code - Form, as described in Section 3.1, and You must inform recipients of - the Executable Form how they can obtain a copy of such Source Code - Form by reasonable means in a timely manner, at a charge no more - than the cost of distribution to the recipient; and - -(b) You may distribute such Executable Form under the terms of this - License, or sublicense it under different terms, provided that the - license for the Executable Form does not attempt to limit or alter - the recipients' rights in the Source Code Form under this License. - -3.3. Distribution of a Larger Work - -You may create and distribute a Larger Work under terms of Your choice, -provided that You also comply with the requirements of this License for -the Covered Software. If the Larger Work is a combination of Covered -Software with a work governed by one or more Secondary Licenses, and the -Covered Software is not Incompatible With Secondary Licenses, this -License permits You to additionally distribute such Covered Software -under the terms of such Secondary License(s), so that the recipient of -the Larger Work may, at their option, further distribute the Covered -Software under the terms of either this License or such Secondary -License(s). - -3.4. Notices - -You may not remove or alter the substance of any license notices -(including copyright notices, patent notices, disclaimers of warranty, -or limitations of liability) contained within the Source Code Form of -the Covered Software, except that You may alter any license notices to -the extent required to remedy known factual inaccuracies. - -3.5. Application of Additional Terms - -You may choose to offer, and to charge a fee for, warranty, support, -indemnity or liability obligations to one or more recipients of Covered -Software. However, You may do so only on Your own behalf, and not on -behalf of any Contributor. You must make it absolutely clear that any -such warranty, support, indemnity, or liability obligation is offered by -You alone, and You hereby agree to indemnify every Contributor for any -liability incurred by such Contributor as a result of warranty, support, -indemnity or liability terms You offer. You may include additional -disclaimers of warranty and limitations of liability specific to any -jurisdiction. - -4. Inability to Comply Due to Statute or Regulation ---------------------------------------------------- - -If it is impossible for You to comply with any of the terms of this -License with respect to some or all of the Covered Software due to -statute, judicial order, or regulation then You must: (a) comply with -the terms of this License to the maximum extent possible; and (b) -describe the limitations and the code they affect. Such description must -be placed in a text file included with all distributions of the Covered -Software under this License. Except to the extent prohibited by statute -or regulation, such description must be sufficiently detailed for a -recipient of ordinary skill to be able to understand it. - -5. Termination --------------- - -5.1. The rights granted under this License will terminate automatically -if You fail to comply with any of its terms. However, if You become -compliant, then the rights granted under this License from a particular -Contributor are reinstated (a) provisionally, unless and until such -Contributor explicitly and finally terminates Your grants, and (b) on an -ongoing basis, if such Contributor fails to notify You of the -non-compliance by some reasonable means prior to 60 days after You have -come back into compliance. Moreover, Your grants from a particular -Contributor are reinstated on an ongoing basis if such Contributor -notifies You of the non-compliance by some reasonable means, this is the -first time You have received notice of non-compliance with this License -from such Contributor, and You become compliant prior to 30 days after -Your receipt of the notice. - -5.2. If You initiate litigation against any entity by asserting a patent -infringement claim (excluding declaratory judgment actions, -counter-claims, and cross-claims) alleging that a Contributor Version -directly or indirectly infringes any patent, then the rights granted to -You by any and all Contributors for the Covered Software under Section -2.1 of this License shall terminate. - -5.3. In the event of termination under Sections 5.1 or 5.2 above, all -end user license agreements (excluding distributors and resellers) which -have been validly granted by You or Your distributors under this License -prior to termination shall survive termination. - -************************************************************************ -* * -* 6. Disclaimer of Warranty * -* ------------------------- * -* * -* Covered Software is provided under this License on an "as is" * -* basis, without warranty of any kind, either expressed, implied, or * -* statutory, including, without limitation, warranties that the * -* Covered Software is free of defects, merchantable, fit for a * -* particular purpose or non-infringing. The entire risk as to the * -* quality and performance of the Covered Software is with You. * -* Should any Covered Software prove defective in any respect, You * -* (not any Contributor) assume the cost of any necessary servicing, * -* repair, or correction. This disclaimer of warranty constitutes an * -* essential part of this License. No use of any Covered Software is * -* authorized under this License except under this disclaimer. * -* * -************************************************************************ - -************************************************************************ -* * -* 7. Limitation of Liability * -* -------------------------- * -* * -* Under no circumstances and under no legal theory, whether tort * -* (including negligence), contract, or otherwise, shall any * -* Contributor, or anyone who distributes Covered Software as * -* permitted above, be liable to You for any direct, indirect, * -* special, incidental, or consequential damages of any character * -* including, without limitation, damages for lost profits, loss of * -* goodwill, work stoppage, computer failure or malfunction, or any * -* and all other commercial damages or losses, even if such party * -* shall have been informed of the possibility of such damages. This * -* limitation of liability shall not apply to liability for death or * -* personal injury resulting from such party's negligence to the * -* extent applicable law prohibits such limitation. Some * -* jurisdictions do not allow the exclusion or limitation of * -* incidental or consequential damages, so this exclusion and * -* limitation may not apply to You. * -* * -************************************************************************ - -8. Litigation -------------- - -Any litigation relating to this License may be brought only in the -courts of a jurisdiction where the defendant maintains its principal -place of business and such litigation shall be governed by laws of that -jurisdiction, without reference to its conflict-of-law provisions. -Nothing in this Section shall prevent a party's ability to bring -cross-claims or counter-claims. - -9. Miscellaneous ----------------- - -This License represents the complete agreement concerning the subject -matter hereof. If any provision of this License is held to be -unenforceable, such provision shall be reformed only to the extent -necessary to make it enforceable. Any law or regulation which provides -that the language of a contract shall be construed against the drafter -shall not be used to construe this License against a Contributor. - -10. Versions of the License ---------------------------- - -10.1. New Versions - -Mozilla Foundation is the license steward. Except as provided in Section -10.3, no one other than the license steward has the right to modify or -publish new versions of this License. Each version will be given a -distinguishing version number. - -10.2. Effect of New Versions - -You may distribute the Covered Software under the terms of the version -of the License under which You originally received the Covered Software, -or under the terms of any subsequent version published by the license -steward. - -10.3. Modified Versions - -If you create software not governed by this License, and you want to -create a new license for such software, you may create and use a -modified version of this License if you rename the license and remove -any references to the name of the license steward (except to note that -such modified license differs from this License). - -10.4. Distributing Source Code Form that is Incompatible With Secondary -Licenses - -If You choose to distribute Source Code Form that is Incompatible With -Secondary Licenses under the terms of this version of the License, the -notice described in Exhibit B of this License must be attached. - -Exhibit A - Source Code Form License Notice -------------------------------------------- - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. - -If it is not possible or desirable to put the notice in a particular -file, then You may include the notice in a location (such as a LICENSE -file in a relevant directory) where a recipient would be likely to look -for such a notice. - -You may add additional accurate notices of copyright ownership. - -Exhibit B - "Incompatible With Secondary Licenses" Notice ---------------------------------------------------------- - - This Source Code Form is "Incompatible With Secondary Licenses", as - defined by the Mozilla Public License, v. 2.0. diff --git a/resvg-0.43.0/crates/resvg/README.md b/resvg-0.43.0/crates/resvg/README.md deleted file mode 100644 index ee6f888..0000000 --- a/resvg-0.43.0/crates/resvg/README.md +++ /dev/null @@ -1,144 +0,0 @@ -## resvg -![Build Status](https://github.com/RazrFalcon/resvg/workflows/Build/badge.svg) -[![Crates.io](https://img.shields.io/crates/v/resvg.svg)](https://crates.io/crates/resvg) -[![Documentation](https://docs.rs/resvg/badge.svg)](https://docs.rs/resvg) -[![Rust 1.67.1+](https://img.shields.io/badge/rust-1.67.1+-orange.svg)](https://www.rust-lang.org) - -*resvg* is an [SVG](https://en.wikipedia.org/wiki/Scalable_Vector_Graphics) rendering library. - -It can be used as a Rust library, as a C library, and as a CLI application to render static SVG files. - -The core idea is to make a fast, small, portable SVG library with the goal to support the whole SVG spec. - -## Features - -### Designed for edge-cases - -SVG is a very complicated format with a large specification (SVG 1.1 is almost 900 pages). -You basically need a web browser to handle all of it. But the truth is that even browsers -fail at this (see [SVG support](https://github.com/RazrFalcon/resvg#svg-support)). -Yes, unlike `resvg`, browsers do support dynamic SVG features like animations and scripting. -But using a browser to render SVG _correctly_ is sadly not an option. - -To prove its correctness, `resvg` has a vast test suite that includes around 1600 tests. -And those are only SVG-to-PNG regression tests. This doesn't include tests in `resvg` dependencies. -And the best thing is that `resvg` test suite is available to everyone. It's not tied to `resvg` -in any way. Which should help people who plan to develop their own SVG libraries. - -### Safety - -It's hard not to mention safety when we talk about Rust and processing of a random input. -And we're talking not only about SVG/XML, but also about CSS, TTF, PNG, JPEG, GIF, and GZIP. - -While `resvg` is not the only SVG library written in Rust, it's the only one that -is written completely in Rust. There is no non-Rust code in the final binary. - -Moreover, there is almost no `unsafe` code either. Still, some dependencies have some `unsafe` code -and font memory-mapping is inherently `unsafe`, but it's best you can get in terms of memory safety. - -However, this doesn't stop at memory safety. `resvg` has extensive checks to prevent endless loops (freezes) -and stack overflows (via recursion). - -### Zero bloat - -Right now, the `resvg` CLI application is less than 3MB in size and doesn't require any external dependencies. -The binary contains nothing that isn't needed for rendering SVG files. - -### Portable - -`resvg` is guaranteed to work everywhere where you can compile the Rust itself, -including WASM. There are some rough edges with obscure CPU architectures and -mobile OSs (mainly system fonts loading), but it should be pretty painless otherwise. - -### SVG preprocessing - -Another major difference from other SVG rendering libraries is that in `resvg` -SVG parsing and rendering are two completely separate steps. -Those steps are also split into two separate libraries: `resvg` and [usvg]. -Meaning you can easily write your own renderer on top of `usvg` using any 2D library of your liking. - -### Performance - -Comparing performance between different SVG rendering libraries is like comparing apples and oranges. -Everyone has a very different set of supported features, languages, build flags, etc... -Anyhow, as `resvg` is written in Rust and uses [tiny-skia] for rendering - it's pretty fast. -There should also still be quite a lot of room for improvement. - -### Reproducibility - -Since `resvg` doesn't rely on any system libraries it allows us to have reproducible results -on all supported platforms. Meaning if you render an SVG file on x86 Windows and then render it -on ARM macOS - the produced image will be identical. Each pixel would have the same value. - -## Limitations - -- No animations
- There are no plans on implementing them either. -- No native text rendering
- `resvg` doesn't rely on any system libraries, which implies that we cannot use native text rendering. - Nevertheless, native text rendering is optimized for small horizontal text, which is not - that common in SVG. -- Unicode-only
- It's the 21st century. Text files that aren't UTF-8 encoded are no longer relevant. - -## SVG support - -`resvg` aims to only support the [static](http://www.w3.org/TR/SVG11/feature#SVG-static) -SVG subset; i.e. no `a`, `script`, `view` or `cursor` elements, no events and no animations. - -[SVG 2](https://www.w3.org/TR/SVG2/) support is being worked on. -You can search for relevant issues with the -[svg2 tag](https://github.com/RazrFalcon/resvg/issues?q=is%3Aissue+is%3Aopen+label%3Asvg2) -or our [SVG 2 changelog](https://github.com/RazrFalcon/resvg/blob/master/docs/svg2-changelog.md). - -[SVG Tiny 1.2](https://www.w3.org/TR/SVGTiny12/) is not supported and support is also not planned. - -Results of the [resvg test suite](https://github.com/RazrFalcon/resvg-test-suite): - -![](./.github/chart.svg) - -SVG 2 only results: - -![](./.github/chart-svg2.svg) - -You can find a complete table of supported features -[here](https://razrfalcon.github.io/resvg-test-suite/svg-support-table.html). -It also includes some alternative libraries. - -We're not testing against all SVG libraries since many of them are pretty bad. -Some libraries are not on the list because they don't pass the 25% mark. -Such libraries are: wxSvg, LunaSVG and nanosvg. - -## resvg project - -There is a subtle difference between resvg as a _library_ and resvg as a _project_. -While most users will interact only with the resvg library, it's just a tip of an iceberg. -There are a lot of libraries that I had to write to make resvg possible. -Here are some of them: - -- resvg - the actual SVG renderer -- [usvg] - an SVG preprocessor/simplifier -- [tiny-skia] - a [Skia](https://github.com/google/skia) subset ported to Rust -- [rustybuzz] - a [harfbuzz](https://github.com/harfbuzz/harfbuzz) subset ported to Rust -- [ttf-parser] - a TrueType/OpenType font parser -- [fontdb] - a simple, in-memory font database with CSS-like queries -- [roxmltree] - an XML parsing library -- [simplecss] - a pretty decent CSS 2 parser and selector -- [pico-args] - an absolutely minimal, but surprisingly popular command-line arguments parser - -So while the resvg _library_ is deceptively small (around 2500 LOC), the resvg _project_ -is nearing 75'000 LOC. Which is not that much considering how much resvg does. -It's definitely the smallest option out there. - -## License - -`resvg` project is licensed under the [MPLv2.0](https://www.mozilla.org/en-US/MPL/). - -[usvg]: https://github.com/RazrFalcon/resvg/tree/master/crates/usvg -[rustybuzz]: https://github.com/RazrFalcon/rustybuzz -[tiny-skia]: https://github.com/RazrFalcon/tiny-skia -[ttf-parser]: https://github.com/RazrFalcon/ttf-parser -[roxmltree]: https://github.com/RazrFalcon/roxmltree -[simplecss]: https://github.com/RazrFalcon/simplecss -[fontdb]: https://github.com/RazrFalcon/fontdb -[pico-args]: https://github.com/RazrFalcon/pico-args diff --git a/resvg-0.43.0/crates/resvg/src/clip.rs b/resvg-0.43.0/crates/resvg/src/clip.rs deleted file mode 100644 index 39c9e42..0000000 --- a/resvg-0.43.0/crates/resvg/src/clip.rs +++ /dev/null @@ -1,99 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::render::Context; - -pub fn apply( - clip: &usvg::ClipPath, - transform: tiny_skia::Transform, - pixmap: &mut tiny_skia::Pixmap, -) { - let mut clip_pixmap = tiny_skia::Pixmap::new(pixmap.width(), pixmap.height()).unwrap(); - clip_pixmap.fill(tiny_skia::Color::BLACK); - - draw_children( - clip.root(), - tiny_skia::BlendMode::Clear, - transform.pre_concat(clip.transform()), - &mut clip_pixmap.as_mut(), - ); - - if let Some(clip) = clip.clip_path() { - apply(clip, transform, pixmap); - } - - let mut mask = tiny_skia::Mask::from_pixmap(clip_pixmap.as_ref(), tiny_skia::MaskType::Alpha); - mask.invert(); - pixmap.apply_mask(&mask); -} - -fn draw_children( - parent: &usvg::Group, - mode: tiny_skia::BlendMode, - transform: tiny_skia::Transform, - pixmap: &mut tiny_skia::PixmapMut, -) { - for child in parent.children() { - match child { - usvg::Node::Path(ref path) => { - if !path.is_visible() { - continue; - } - - // We could use any values here. They will not be used anyway. - let ctx = Context { - max_bbox: tiny_skia::IntRect::from_xywh(0, 0, 1, 1).unwrap(), - }; - - crate::path::fill_path(path, mode, &ctx, transform, pixmap); - } - usvg::Node::Text(ref text) => { - draw_children(text.flattened(), mode, transform, pixmap); - } - usvg::Node::Group(ref group) => { - let transform = transform.pre_concat(group.transform()); - - if let Some(clip) = group.clip_path() { - // If a `clipPath` child also has a `clip-path` - // then we should render this child on a new canvas, - // clip it, and only then draw it to the `clipPath`. - clip_group(group, clip, transform, pixmap); - } else { - draw_children(group, mode, transform, pixmap); - } - } - _ => {} - } - } -} - -fn clip_group( - children: &usvg::Group, - clip: &usvg::ClipPath, - transform: tiny_skia::Transform, - pixmap: &mut tiny_skia::PixmapMut, -) -> Option<()> { - let mut clip_pixmap = tiny_skia::Pixmap::new(pixmap.width(), pixmap.height()).unwrap(); - - draw_children( - children, - tiny_skia::BlendMode::SourceOver, - transform, - &mut clip_pixmap.as_mut(), - ); - apply(clip, transform, &mut clip_pixmap); - - let mut paint = tiny_skia::PixmapPaint::default(); - paint.blend_mode = tiny_skia::BlendMode::Xor; - pixmap.draw_pixmap( - 0, - 0, - clip_pixmap.as_ref(), - &paint, - tiny_skia::Transform::identity(), - None, - ); - - Some(()) -} diff --git a/resvg-0.43.0/crates/resvg/src/filter/box_blur.rs b/resvg-0.43.0/crates/resvg/src/filter/box_blur.rs deleted file mode 100644 index d6ebbba..0000000 --- a/resvg-0.43.0/crates/resvg/src/filter/box_blur.rs +++ /dev/null @@ -1,353 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -// Based on https://github.com/fschutt/fastblur - -#![allow(clippy::needless_range_loop)] - -use super::ImageRefMut; -use rgb::RGBA8; -use std::cmp; - -const STEPS: usize = 5; - -/// Applies a box blur. -/// -/// Input image pixels should have a **premultiplied alpha**. -/// -/// A negative or zero `sigma_x`/`sigma_y` will disable the blur along that axis. -/// -/// # Allocations -/// -/// This method will allocate a copy of the `src` image as a back buffer. -pub fn apply(sigma_x: f64, sigma_y: f64, mut src: ImageRefMut) { - let boxes_horz = create_box_gauss(sigma_x as f32); - let boxes_vert = create_box_gauss(sigma_y as f32); - let mut backbuf = src.data.to_vec(); - let mut backbuf = ImageRefMut::new(src.width, src.height, &mut backbuf); - - for (box_size_horz, box_size_vert) in boxes_horz.iter().zip(boxes_vert.iter()) { - let radius_horz = ((box_size_horz - 1) / 2) as usize; - let radius_vert = ((box_size_vert - 1) / 2) as usize; - box_blur_impl(radius_horz, radius_vert, &mut backbuf, &mut src); - } -} - -#[inline(never)] -fn create_box_gauss(sigma: f32) -> [i32; STEPS] { - if sigma > 0.0 { - let n_float = STEPS as f32; - - // Ideal averaging filter width - let w_ideal = (12.0 * sigma * sigma / n_float).sqrt() + 1.0; - let mut wl = w_ideal.floor() as i32; - if wl % 2 == 0 { - wl -= 1; - } - - let wu = wl + 2; - - let wl_float = wl as f32; - let m_ideal = (12.0 * sigma * sigma - - n_float * wl_float * wl_float - - 4.0 * n_float * wl_float - - 3.0 * n_float) - / (-4.0 * wl_float - 4.0); - let m = m_ideal.round() as usize; - - let mut sizes = [0; STEPS]; - for i in 0..STEPS { - if i < m { - sizes[i] = wl; - } else { - sizes[i] = wu; - } - } - - sizes - } else { - [1; STEPS] - } -} - -#[inline] -fn box_blur_impl( - blur_radius_horz: usize, - blur_radius_vert: usize, - backbuf: &mut ImageRefMut, - frontbuf: &mut ImageRefMut, -) { - box_blur_vert(blur_radius_vert, frontbuf, backbuf); - box_blur_horz(blur_radius_horz, backbuf, frontbuf); -} - -#[inline] -fn box_blur_vert(blur_radius: usize, backbuf: &ImageRefMut, frontbuf: &mut ImageRefMut) { - if blur_radius == 0 { - frontbuf.data.copy_from_slice(backbuf.data); - return; - } - - let width = backbuf.width as usize; - let height = backbuf.height as usize; - - let iarr = 1.0 / (blur_radius + blur_radius + 1) as f32; - let blur_radius_prev = blur_radius as isize - height as isize; - let blur_radius_next = blur_radius as isize + 1; - - for i in 0..width { - let col_start = i; //inclusive - let col_end = i + width * (height - 1); //inclusive - let mut ti = i; - let mut li = ti; - let mut ri = ti + blur_radius * width; - - let fv = RGBA8::default(); - let lv = RGBA8::default(); - - let mut val_r = blur_radius_next * (fv.r as isize); - let mut val_g = blur_radius_next * (fv.g as isize); - let mut val_b = blur_radius_next * (fv.b as isize); - let mut val_a = blur_radius_next * (fv.a as isize); - - // Get the pixel at the specified index, or the first pixel of the column - // if the index is beyond the top edge of the image - let get_top = |i| { - if i < col_start { - fv - } else { - backbuf.data[i] - } - }; - - // Get the pixel at the specified index, or the last pixel of the column - // if the index is beyond the bottom edge of the image - let get_bottom = |i| { - if i > col_end { - lv - } else { - backbuf.data[i] - } - }; - - for j in 0..cmp::min(blur_radius, height) { - let bb = backbuf.data[ti + j * width]; - val_r += bb.r as isize; - val_g += bb.g as isize; - val_b += bb.b as isize; - val_a += bb.a as isize; - } - if blur_radius > height { - val_r += blur_radius_prev * (lv.r as isize); - val_g += blur_radius_prev * (lv.g as isize); - val_b += blur_radius_prev * (lv.b as isize); - val_a += blur_radius_prev * (lv.a as isize); - } - - for _ in 0..cmp::min(height, blur_radius + 1) { - let bb = get_bottom(ri); - ri += width; - val_r += sub(bb.r, fv.r); - val_g += sub(bb.g, fv.g); - val_b += sub(bb.b, fv.b); - val_a += sub(bb.a, fv.a); - - frontbuf.data[ti] = RGBA8 { - r: round(val_r as f32 * iarr) as u8, - g: round(val_g as f32 * iarr) as u8, - b: round(val_b as f32 * iarr) as u8, - a: round(val_a as f32 * iarr) as u8, - }; - ti += width; - } - - if height <= blur_radius { - // otherwise `(height - blur_radius)` will underflow - continue; - } - - for _ in (blur_radius + 1)..(height - blur_radius) { - let bb1 = backbuf.data[ri]; - ri += width; - let bb2 = backbuf.data[li]; - li += width; - - val_r += sub(bb1.r, bb2.r); - val_g += sub(bb1.g, bb2.g); - val_b += sub(bb1.b, bb2.b); - val_a += sub(bb1.a, bb2.a); - - frontbuf.data[ti] = RGBA8 { - r: round(val_r as f32 * iarr) as u8, - g: round(val_g as f32 * iarr) as u8, - b: round(val_b as f32 * iarr) as u8, - a: round(val_a as f32 * iarr) as u8, - }; - ti += width; - } - - for _ in 0..cmp::min(height - blur_radius - 1, blur_radius) { - let bb = get_top(li); - li += width; - - val_r += sub(lv.r, bb.r); - val_g += sub(lv.g, bb.g); - val_b += sub(lv.b, bb.b); - val_a += sub(lv.a, bb.a); - - frontbuf.data[ti] = RGBA8 { - r: round(val_r as f32 * iarr) as u8, - g: round(val_g as f32 * iarr) as u8, - b: round(val_b as f32 * iarr) as u8, - a: round(val_a as f32 * iarr) as u8, - }; - ti += width; - } - } -} - -#[inline] -fn box_blur_horz(blur_radius: usize, backbuf: &ImageRefMut, frontbuf: &mut ImageRefMut) { - if blur_radius == 0 { - frontbuf.data.copy_from_slice(backbuf.data); - return; - } - - let width = backbuf.width as usize; - let height = backbuf.height as usize; - - let iarr = 1.0 / (blur_radius + blur_radius + 1) as f32; - let blur_radius_prev = blur_radius as isize - width as isize; - let blur_radius_next = blur_radius as isize + 1; - - for i in 0..height { - let row_start = i * width; // inclusive - let row_end = (i + 1) * width - 1; // inclusive - let mut ti = i * width; // VERTICAL: $i; - let mut li = ti; - let mut ri = ti + blur_radius; - - let fv = RGBA8::default(); - let lv = RGBA8::default(); - - let mut val_r = blur_radius_next * (fv.r as isize); - let mut val_g = blur_radius_next * (fv.g as isize); - let mut val_b = blur_radius_next * (fv.b as isize); - let mut val_a = blur_radius_next * (fv.a as isize); - - // Get the pixel at the specified index, or the first pixel of the row - // if the index is beyond the left edge of the image - let get_left = |i| { - if i < row_start { - fv - } else { - backbuf.data[i] - } - }; - - // Get the pixel at the specified index, or the last pixel of the row - // if the index is beyond the right edge of the image - let get_right = |i| { - if i > row_end { - lv - } else { - backbuf.data[i] - } - }; - - for j in 0..cmp::min(blur_radius, width) { - let bb = backbuf.data[ti + j]; // VERTICAL: ti + j * width - val_r += bb.r as isize; - val_g += bb.g as isize; - val_b += bb.b as isize; - val_a += bb.a as isize; - } - if blur_radius > width { - val_r += blur_radius_prev * (lv.r as isize); - val_g += blur_radius_prev * (lv.g as isize); - val_b += blur_radius_prev * (lv.b as isize); - val_a += blur_radius_prev * (lv.a as isize); - } - - // Process the left side where we need pixels from beyond the left edge - for _ in 0..cmp::min(width, blur_radius + 1) { - let bb = get_right(ri); - ri += 1; - val_r += sub(bb.r, fv.r); - val_g += sub(bb.g, fv.g); - val_b += sub(bb.b, fv.b); - val_a += sub(bb.a, fv.a); - - frontbuf.data[ti] = RGBA8 { - r: round(val_r as f32 * iarr) as u8, - g: round(val_g as f32 * iarr) as u8, - b: round(val_b as f32 * iarr) as u8, - a: round(val_a as f32 * iarr) as u8, - }; - ti += 1; // VERTICAL : ti += width, same with the other areas - } - - if width <= blur_radius { - // otherwise `(width - blur_radius)` will underflow - continue; - } - - // Process the middle where we know we won't bump into borders - // without the extra indirection of get_left/get_right. This is faster. - for _ in (blur_radius + 1)..(width - blur_radius) { - let bb1 = backbuf.data[ri]; - ri += 1; - let bb2 = backbuf.data[li]; - li += 1; - - val_r += sub(bb1.r, bb2.r); - val_g += sub(bb1.g, bb2.g); - val_b += sub(bb1.b, bb2.b); - val_a += sub(bb1.a, bb2.a); - - frontbuf.data[ti] = RGBA8 { - r: round(val_r as f32 * iarr) as u8, - g: round(val_g as f32 * iarr) as u8, - b: round(val_b as f32 * iarr) as u8, - a: round(val_a as f32 * iarr) as u8, - }; - ti += 1; - } - - // Process the right side where we need pixels from beyond the right edge - for _ in 0..cmp::min(width - blur_radius - 1, blur_radius) { - let bb = get_left(li); - li += 1; - - val_r += sub(lv.r, bb.r); - val_g += sub(lv.g, bb.g); - val_b += sub(lv.b, bb.b); - val_a += sub(lv.a, bb.a); - - frontbuf.data[ti] = RGBA8 { - r: round(val_r as f32 * iarr) as u8, - g: round(val_g as f32 * iarr) as u8, - b: round(val_b as f32 * iarr) as u8, - a: round(val_a as f32 * iarr) as u8, - }; - ti += 1; - } - } -} - -/// Fast rounding for x <= 2^23. -/// This is orders of magnitude faster than built-in rounding intrinsic. -/// -/// Source: https://stackoverflow.com/a/42386149/585725 -#[inline] -fn round(mut x: f32) -> f32 { - x += 12582912.0; - x -= 12582912.0; - x -} - -#[inline] -fn sub(c1: u8, c2: u8) -> isize { - c1 as isize - c2 as isize -} diff --git a/resvg-0.43.0/crates/resvg/src/filter/color_matrix.rs b/resvg-0.43.0/crates/resvg/src/filter/color_matrix.rs deleted file mode 100644 index 93b0d72..0000000 --- a/resvg-0.43.0/crates/resvg/src/filter/color_matrix.rs +++ /dev/null @@ -1,111 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use super::{f32_bound, ImageRefMut}; -use rgb::RGBA8; -use usvg::filter::ColorMatrixKind as ColorMatrix; - -/// Applies a color matrix filter. -/// -/// Input image pixels should have an **unpremultiplied alpha**. -pub fn apply(matrix: &ColorMatrix, src: ImageRefMut) { - match matrix { - ColorMatrix::Matrix(m) => { - for pixel in src.data { - let (r, g, b, a) = to_normalized_components(*pixel); - - let new_r = r * m[0] + g * m[1] + b * m[2] + a * m[3] + m[4]; - let new_g = r * m[5] + g * m[6] + b * m[7] + a * m[8] + m[9]; - let new_b = r * m[10] + g * m[11] + b * m[12] + a * m[13] + m[14]; - let new_a = r * m[15] + g * m[16] + b * m[17] + a * m[18] + m[19]; - - pixel.r = from_normalized(new_r); - pixel.g = from_normalized(new_g); - pixel.b = from_normalized(new_b); - pixel.a = from_normalized(new_a); - } - } - ColorMatrix::Saturate(v) => { - let v = v.get().max(0.0); - let m = [ - 0.213 + 0.787 * v, - 0.715 - 0.715 * v, - 0.072 - 0.072 * v, - 0.213 - 0.213 * v, - 0.715 + 0.285 * v, - 0.072 - 0.072 * v, - 0.213 - 0.213 * v, - 0.715 - 0.715 * v, - 0.072 + 0.928 * v, - ]; - - for pixel in src.data { - let (r, g, b, _) = to_normalized_components(*pixel); - - let new_r = r * m[0] + g * m[1] + b * m[2]; - let new_g = r * m[3] + g * m[4] + b * m[5]; - let new_b = r * m[6] + g * m[7] + b * m[8]; - - pixel.r = from_normalized(new_r); - pixel.g = from_normalized(new_g); - pixel.b = from_normalized(new_b); - } - } - ColorMatrix::HueRotate(angle) => { - let angle = angle.to_radians(); - let a1 = angle.cos(); - let a2 = angle.sin(); - let m = [ - 0.213 + 0.787 * a1 - 0.213 * a2, - 0.715 - 0.715 * a1 - 0.715 * a2, - 0.072 - 0.072 * a1 + 0.928 * a2, - 0.213 - 0.213 * a1 + 0.143 * a2, - 0.715 + 0.285 * a1 + 0.140 * a2, - 0.072 - 0.072 * a1 - 0.283 * a2, - 0.213 - 0.213 * a1 - 0.787 * a2, - 0.715 - 0.715 * a1 + 0.715 * a2, - 0.072 + 0.928 * a1 + 0.072 * a2, - ]; - - for pixel in src.data { - let (r, g, b, _) = to_normalized_components(*pixel); - - let new_r = r * m[0] + g * m[1] + b * m[2]; - let new_g = r * m[3] + g * m[4] + b * m[5]; - let new_b = r * m[6] + g * m[7] + b * m[8]; - - pixel.r = from_normalized(new_r); - pixel.g = from_normalized(new_g); - pixel.b = from_normalized(new_b); - } - } - ColorMatrix::LuminanceToAlpha => { - for pixel in src.data { - let (r, g, b, _) = to_normalized_components(*pixel); - - let new_a = r * 0.2125 + g * 0.7154 + b * 0.0721; - - pixel.r = 0; - pixel.g = 0; - pixel.b = 0; - pixel.a = from_normalized(new_a); - } - } - } -} - -#[inline] -fn to_normalized_components(pixel: RGBA8) -> (f32, f32, f32, f32) { - ( - pixel.r as f32 / 255.0, - pixel.g as f32 / 255.0, - pixel.b as f32 / 255.0, - pixel.a as f32 / 255.0, - ) -} - -#[inline] -fn from_normalized(c: f32) -> u8 { - (f32_bound(0.0, c, 1.0) * 255.0) as u8 -} diff --git a/resvg-0.43.0/crates/resvg/src/filter/component_transfer.rs b/resvg-0.43.0/crates/resvg/src/filter/component_transfer.rs deleted file mode 100644 index b56af64..0000000 --- a/resvg-0.43.0/crates/resvg/src/filter/component_transfer.rs +++ /dev/null @@ -1,73 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use super::{f32_bound, ImageRefMut}; -use usvg::filter::{ComponentTransfer, TransferFunction}; - -/// Applies component transfer functions for each `src` image channel. -/// -/// Input image pixels should have an **unpremultiplied alpha**. -pub fn apply(fe: &ComponentTransfer, src: ImageRefMut) { - for pixel in src.data { - if !is_dummy(fe.func_r()) { - pixel.r = transfer(fe.func_r(), pixel.r); - } - - if !is_dummy(fe.func_b()) { - pixel.b = transfer(fe.func_b(), pixel.b); - } - - if !is_dummy(fe.func_g()) { - pixel.g = transfer(fe.func_g(), pixel.g); - } - - if !is_dummy(fe.func_a()) { - pixel.a = transfer(fe.func_a(), pixel.a); - } - } -} - -fn is_dummy(func: &TransferFunction) -> bool { - match func { - TransferFunction::Identity => true, - TransferFunction::Table(values) => values.is_empty(), - TransferFunction::Discrete(values) => values.is_empty(), - TransferFunction::Linear { .. } => false, - TransferFunction::Gamma { .. } => false, - } -} - -fn transfer(func: &TransferFunction, c: u8) -> u8 { - let c = c as f32 / 255.0; - let c = match func { - TransferFunction::Identity => c, - TransferFunction::Table(values) => { - let n = values.len() - 1; - let k = (c * (n as f32)).floor() as usize; - let k = std::cmp::min(k, n); - if k == n { - values[k] - } else { - let vk = values[k]; - let vk1 = values[k + 1]; - let k = k as f32; - let n = n as f32; - vk + (c - k / n) * n * (vk1 - vk) - } - } - TransferFunction::Discrete(values) => { - let n = values.len(); - let k = (c * (n as f32)).floor() as usize; - values[std::cmp::min(k, n - 1)] - } - TransferFunction::Linear { slope, intercept } => slope * c + intercept, - TransferFunction::Gamma { - amplitude, - exponent, - offset, - } => amplitude * c.powf(*exponent) + offset, - }; - - (f32_bound(0.0, c, 1.0) * 255.0) as u8 -} diff --git a/resvg-0.43.0/crates/resvg/src/filter/composite.rs b/resvg-0.43.0/crates/resvg/src/filter/composite.rs deleted file mode 100644 index 7c4c561..0000000 --- a/resvg-0.43.0/crates/resvg/src/filter/composite.rs +++ /dev/null @@ -1,53 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use super::{f32_bound, ImageRef, ImageRefMut}; -use rgb::RGBA8; -use usvg::ApproxZeroUlps; - -/// Performs an arithmetic composition. -/// -/// - `src1` and `src2` image pixels should have a **premultiplied alpha**. -/// - `dest` image pixels will have a **premultiplied alpha**. -/// -/// # Panics -/// -/// When `src1`, `src2` and `dest` have different sizes. -pub fn arithmetic( - k1: f32, - k2: f32, - k3: f32, - k4: f32, - src1: ImageRef, - src2: ImageRef, - dest: ImageRefMut, -) { - assert!(src1.width == src2.width && src1.width == dest.width); - assert!(src1.height == src2.height && src1.height == dest.height); - - let calc = |i1, i2, max| { - let i1 = i1 as f32 / 255.0; - let i2 = i2 as f32 / 255.0; - let result = k1 * i1 * i2 + k2 * i1 + k3 * i2 + k4; - f32_bound(0.0, result, max) - }; - - let mut i = 0; - for (c1, c2) in src1.data.iter().zip(src2.data.iter()) { - let a = calc(c1.a, c2.a, 1.0); - if a.approx_zero_ulps(4) { - i += 1; - continue; - } - - let r = (calc(c1.r, c2.r, a) * 255.0) as u8; - let g = (calc(c1.g, c2.g, a) * 255.0) as u8; - let b = (calc(c1.b, c2.b, a) * 255.0) as u8; - let a = (a * 255.0) as u8; - - dest.data[i] = RGBA8 { r, g, b, a }; - - i += 1; - } -} diff --git a/resvg-0.43.0/crates/resvg/src/filter/convolve_matrix.rs b/resvg-0.43.0/crates/resvg/src/filter/convolve_matrix.rs deleted file mode 100644 index fca9e9c..0000000 --- a/resvg-0.43.0/crates/resvg/src/filter/convolve_matrix.rs +++ /dev/null @@ -1,112 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use super::{f32_bound, ImageRefMut}; -use rgb::RGBA8; -use usvg::filter::{ConvolveMatrix, EdgeMode}; - -/// Applies a convolve matrix. -/// -/// Input image pixels should have a **premultiplied alpha** when `preserve_alpha=false`. -/// -/// # Allocations -/// -/// This method will allocate a copy of the `src` image as a back buffer. -pub fn apply(matrix: &ConvolveMatrix, src: ImageRefMut) { - fn bound(min: i32, val: i32, max: i32) -> i32 { - core::cmp::max(min, core::cmp::min(max, val)) - } - - let width_max = src.width as i32 - 1; - let height_max = src.height as i32 - 1; - - let mut buf = vec![RGBA8::default(); src.data.len()]; - let mut buf = ImageRefMut::new(src.width, src.height, &mut buf); - let mut x = 0; - let mut y = 0; - for in_p in src.data.iter() { - let mut new_r = 0.0; - let mut new_g = 0.0; - let mut new_b = 0.0; - let mut new_a = 0.0; - for oy in 0..matrix.matrix().rows() { - for ox in 0..matrix.matrix().columns() { - let mut tx = x as i32 - matrix.matrix().target_x() as i32 + ox as i32; - let mut ty = y as i32 - matrix.matrix().target_y() as i32 + oy as i32; - - match matrix.edge_mode() { - EdgeMode::None => { - if tx < 0 || tx > width_max || ty < 0 || ty > height_max { - continue; - } - } - EdgeMode::Duplicate => { - tx = bound(0, tx, width_max); - ty = bound(0, ty, height_max); - } - EdgeMode::Wrap => { - while tx < 0 { - tx += src.width as i32; - } - tx %= src.width as i32; - - while ty < 0 { - ty += src.height as i32; - } - ty %= src.height as i32; - } - } - - let k = matrix.matrix().get( - matrix.matrix().columns() - ox - 1, - matrix.matrix().rows() - oy - 1, - ); - - let p = src.pixel_at(tx as u32, ty as u32); - new_r += (p.r as f32) / 255.0 * k; - new_g += (p.g as f32) / 255.0 * k; - new_b += (p.b as f32) / 255.0 * k; - - if !matrix.preserve_alpha() { - new_a += (p.a as f32) / 255.0 * k; - } - } - } - - if matrix.preserve_alpha() { - new_a = in_p.a as f32 / 255.0; - } else { - new_a = new_a / matrix.divisor().get() + matrix.bias(); - } - - let bounded_new_a = f32_bound(0.0, new_a, 1.0); - - let calc = |x| { - let x = x / matrix.divisor().get() + matrix.bias() * new_a; - - let x = if matrix.preserve_alpha() { - f32_bound(0.0, x, 1.0) * bounded_new_a - } else { - f32_bound(0.0, x, bounded_new_a) - }; - - (x * 255.0 + 0.5) as u8 - }; - - let out_p = buf.pixel_at_mut(x, y); - out_p.r = calc(new_r); - out_p.g = calc(new_g); - out_p.b = calc(new_b); - out_p.a = (bounded_new_a * 255.0 + 0.5) as u8; - - x += 1; - if x == src.width { - x = 0; - y += 1; - } - } - - // Do not use `mem::swap` because `data` referenced via FFI. - src.data.copy_from_slice(buf.data); -} diff --git a/resvg-0.43.0/crates/resvg/src/filter/displacement_map.rs b/resvg-0.43.0/crates/resvg/src/filter/displacement_map.rs deleted file mode 100644 index 545a544..0000000 --- a/resvg-0.43.0/crates/resvg/src/filter/displacement_map.rs +++ /dev/null @@ -1,65 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use super::{ImageRef, ImageRefMut}; -use usvg::filter::{ColorChannel, DisplacementMap}; - -/// Applies a displacement map. -/// -/// - `map` pixels should have a **unpremultiplied alpha**. -/// - `src` pixels can have any alpha method. -/// -/// `sx` and `sy` indicate canvas scale. -/// -/// # Panics -/// -/// When `src`, `map` and `dest` have different sizes. -pub fn apply( - fe: &DisplacementMap, - sx: f32, - sy: f32, - src: ImageRef, - map: ImageRef, - dest: ImageRefMut, -) { - assert!(src.width == map.width && src.width == dest.width); - assert!(src.height == map.height && src.height == dest.height); - - let w = src.width as i32; - let h = src.height as i32; - - let mut x: u32 = 0; - let mut y: u32 = 0; - for pixel in map.data.iter() { - let calc_offset = |channel| { - let c = match channel { - ColorChannel::B => pixel.b, - ColorChannel::G => pixel.g, - ColorChannel::R => pixel.r, - ColorChannel::A => pixel.a, - }; - - c as f32 / 255.0 - 0.5 - }; - - let dx = calc_offset(fe.x_channel_selector()); - let dy = calc_offset(fe.y_channel_selector()); - let ox = (x as f32 + dx * sx * fe.scale()).round() as i32; - let oy = (y as f32 + dy * sy * fe.scale()).round() as i32; - - // TODO: we should use some kind of anti-aliasing when offset is on a pixel border - - if x < w as u32 && y < h as u32 && ox >= 0 && ox < w && oy >= 0 && oy < h { - let idx = (oy * w + ox) as usize; - let idx1 = (y * w as u32 + x) as usize; - dest.data[idx1] = src.data[idx]; - } - - x += 1; - if x == src.width { - x = 0; - y += 1; - } - } -} diff --git a/resvg-0.43.0/crates/resvg/src/filter/iir_blur.rs b/resvg-0.43.0/crates/resvg/src/filter/iir_blur.rs deleted file mode 100644 index ff2c8e1..0000000 --- a/resvg-0.43.0/crates/resvg/src/filter/iir_blur.rs +++ /dev/null @@ -1,148 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -// An IIR blur. -// -// Based on http://www.getreuer.info/home/gaussianiir -// -// Licensed under 'Simplified BSD License'. -// -// -// Implements the fast Gaussian convolution algorithm of Alvarez and Mazorra, -// where the Gaussian is approximated by a cascade of first-order infinite -// impulsive response (IIR) filters. Boundaries are handled with half-sample -// symmetric extension. -// -// Gaussian convolution is approached as approximating the heat equation and -// each timestep is performed with an efficient recursive computation. Using -// more steps yields a more accurate approximation of the Gaussian. A -// reasonable default value for `numsteps` is 4. -// -// Reference: -// Alvarez, Mazorra, "Signal and Image Restoration using Shock Filters and -// Anisotropic Diffusion," SIAM J. on Numerical Analysis, vol. 31, no. 2, -// pp. 590-605, 1994. - -// TODO: Blurs right and bottom sides twice for some reason. - -use super::ImageRefMut; -use rgb::ComponentSlice; - -struct BlurData { - width: usize, - height: usize, - sigma_x: f64, - sigma_y: f64, - steps: usize, -} - -/// Applies an IIR blur. -/// -/// Input image pixels should have a **premultiplied alpha**. -/// -/// A negative or zero `sigma_x`/`sigma_y` will disable the blur along that axis. -/// -/// # Allocations -/// -/// This method will allocate a 2x `src` buffer. -pub fn apply(sigma_x: f64, sigma_y: f64, src: ImageRefMut) { - let buf_size = (src.width * src.height) as usize; - let mut buf = vec![0.0; buf_size]; - let buf = &mut buf; - - let d = BlurData { - width: src.width as usize, - height: src.height as usize, - sigma_x, - sigma_y, - steps: 4, - }; - - let data = src.data.as_mut_slice(); - gaussian_channel(data, &d, 0, buf); - gaussian_channel(data, &d, 1, buf); - gaussian_channel(data, &d, 2, buf); - gaussian_channel(data, &d, 3, buf); -} - -fn gaussian_channel(data: &mut [u8], d: &BlurData, channel: usize, buf: &mut Vec) { - for i in 0..data.len() / 4 { - buf[i] = data[i * 4 + channel] as f64 / 255.0; - } - - gaussianiir2d(d, buf); - - for i in 0..data.len() / 4 { - data[i * 4 + channel] = (buf[i] * 255.0) as u8; - } -} - -fn gaussianiir2d(d: &BlurData, buf: &mut Vec) { - // Filter horizontally along each row. - let (lambda_x, dnu_x) = if d.sigma_x > 0.0 { - let (lambda, dnu) = gen_coefficients(d.sigma_x, d.steps); - - for y in 0..d.height { - for _ in 0..d.steps { - let idx = d.width * y; - - // Filter rightwards. - for x in 1..d.width { - buf[idx + x] += dnu * buf[idx + x - 1]; - } - - let mut x = d.width - 1; - - // Filter leftwards. - while x > 0 { - buf[idx + x - 1] += dnu * buf[idx + x]; - x -= 1; - } - } - } - - (lambda, dnu) - } else { - (1.0, 1.0) - }; - - // Filter vertically along each column. - let (lambda_y, dnu_y) = if d.sigma_y > 0.0 { - let (lambda, dnu) = gen_coefficients(d.sigma_y, d.steps); - for x in 0..d.width { - for _ in 0..d.steps { - let idx = x; - - // Filter downwards. - let mut y = d.width; - while y < buf.len() { - buf[idx + y] += dnu * buf[idx + y - d.width]; - y += d.width; - } - - y = buf.len() - d.width; - - // Filter upwards. - while y > 0 { - buf[idx + y - d.width] += dnu * buf[idx + y]; - y -= d.width; - } - } - } - - (lambda, dnu) - } else { - (1.0, 1.0) - }; - - let post_scale = - ((dnu_x * dnu_y).sqrt() / (lambda_x * lambda_y).sqrt()).powi(2 * d.steps as i32); - buf.iter_mut().for_each(|v| *v *= post_scale); -} - -fn gen_coefficients(sigma: f64, steps: usize) -> (f64, f64) { - let lambda = (sigma * sigma) / (2.0 * steps as f64); - let dnu = (1.0 + 2.0 * lambda - (1.0 + 4.0 * lambda).sqrt()) / (2.0 * lambda); - (lambda, dnu) -} diff --git a/resvg-0.43.0/crates/resvg/src/filter/lighting.rs b/resvg-0.43.0/crates/resvg/src/filter/lighting.rs deleted file mode 100644 index d6be6b7..0000000 --- a/resvg-0.43.0/crates/resvg/src/filter/lighting.rs +++ /dev/null @@ -1,490 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use super::{f32_bound, ImageRef, ImageRefMut}; -use rgb::RGBA8; -use usvg::filter::{DiffuseLighting, LightSource, SpecularLighting}; -use usvg::{ApproxEqUlps, ApproxZeroUlps, Color}; - -const FACTOR_1_2: f32 = 1.0 / 2.0; -const FACTOR_1_3: f32 = 1.0 / 3.0; -const FACTOR_1_4: f32 = 1.0 / 4.0; -const FACTOR_2_3: f32 = 2.0 / 3.0; - -#[derive(Clone, Copy, Debug)] -struct Vector2 { - x: f32, - y: f32, -} - -impl Vector2 { - #[inline] - fn new(x: f32, y: f32) -> Self { - Vector2 { x, y } - } - - #[inline] - fn approx_zero(&self) -> bool { - self.x.approx_zero_ulps(4) && self.y.approx_zero_ulps(4) - } -} - -impl core::ops::Mul for Vector2 { - type Output = Self; - - #[inline] - fn mul(self, c: f32) -> Self::Output { - Vector2 { - x: self.x * c, - y: self.y * c, - } - } -} - -#[derive(Clone, Copy, Debug)] -struct Vector3 { - x: f32, - y: f32, - z: f32, -} - -impl Vector3 { - #[inline] - fn new(x: f32, y: f32, z: f32) -> Self { - Vector3 { x, y, z } - } - - #[inline] - fn dot(&self, other: &Self) -> f32 { - self.x * other.x + self.y * other.y + self.z * other.z - } - - #[inline] - fn length(&self) -> f32 { - (self.x * self.x + self.y * self.y + self.z * self.z).sqrt() - } - - #[inline] - fn normalized(&self) -> Option { - let length = self.length(); - if !length.approx_zero_ulps(4) { - Some(Vector3 { - x: self.x / length, - y: self.y / length, - z: self.z / length, - }) - } else { - None - } - } -} - -impl core::ops::Add for Vector3 { - type Output = Self; - - #[inline] - fn add(self, rhs: Vector3) -> Self::Output { - Vector3 { - x: self.x + rhs.x, - y: self.y + rhs.y, - z: self.z + rhs.z, - } - } -} - -impl core::ops::Sub for Vector3 { - type Output = Self; - - #[inline] - fn sub(self, rhs: Vector3) -> Self::Output { - Vector3 { - x: self.x - rhs.x, - y: self.y - rhs.y, - z: self.z - rhs.z, - } - } -} - -#[derive(Clone, Copy, Debug)] -struct Normal { - factor: Vector2, - normal: Vector2, -} - -impl Normal { - #[inline] - fn new(factor_x: f32, factor_y: f32, nx: i16, ny: i16) -> Self { - Normal { - factor: Vector2::new(factor_x, factor_y), - normal: Vector2::new(-nx as f32, -ny as f32), - } - } -} - -/// Renders a diffuse lighting. -/// -/// - `src` pixels can have any alpha method, since only the alpha channel is used. -/// - `dest` will have an **unpremultiplied alpha**. -/// -/// Does nothing when `src` is less than 3x3. -/// -/// # Panics -/// -/// - When `src` and `dest` have different sizes. -pub fn diffuse_lighting( - fe: &DiffuseLighting, - light_source: LightSource, - src: ImageRef, - dest: ImageRefMut, -) { - assert!(src.width == dest.width && src.height == dest.height); - - let light_factor = |normal: Normal, light_vector: Vector3| { - let k = if normal.normal.approx_zero() { - light_vector.z - } else { - let mut n = normal.normal * (fe.surface_scale() / 255.0); - n.x *= normal.factor.x; - n.y *= normal.factor.y; - - let normal = Vector3::new(n.x, n.y, 1.0); - - normal.dot(&light_vector) / normal.length() - }; - - fe.diffuse_constant() * k - }; - - apply( - light_source, - fe.surface_scale(), - fe.lighting_color(), - &light_factor, - calc_diffuse_alpha, - src, - dest, - ); -} - -/// Renders a specular lighting. -/// -/// - `src` pixels can have any alpha method, since only the alpha channel is used. -/// - `dest` will have a **premultiplied alpha**. -/// -/// Does nothing when `src` is less than 3x3. -/// -/// # Panics -/// -/// - When `src` and `dest` have different sizes. -pub fn specular_lighting( - fe: &SpecularLighting, - light_source: LightSource, - src: ImageRef, - dest: ImageRefMut, -) { - assert!(src.width == dest.width && src.height == dest.height); - - let light_factor = |normal: Normal, light_vector: Vector3| { - let h = light_vector + Vector3::new(0.0, 0.0, 1.0); - let h_length = h.length(); - - if h_length.approx_zero_ulps(4) { - return 0.0; - } - - let k = if normal.normal.approx_zero() { - let n_dot_h = h.z / h_length; - if fe.specular_exponent().approx_eq_ulps(&1.0, 4) { - n_dot_h - } else { - n_dot_h.powf(fe.specular_exponent()) - } - } else { - let mut n = normal.normal * (fe.surface_scale() / 255.0); - n.x *= normal.factor.x; - n.y *= normal.factor.y; - - let normal = Vector3::new(n.x, n.y, 1.0); - - let n_dot_h = normal.dot(&h) / normal.length() / h_length; - if fe.specular_exponent().approx_eq_ulps(&1.0, 4) { - n_dot_h - } else { - n_dot_h.powf(fe.specular_exponent()) - } - }; - - fe.specular_constant() * k - }; - - apply( - light_source, - fe.surface_scale(), - fe.lighting_color(), - &light_factor, - calc_specular_alpha, - src, - dest, - ); -} - -fn apply( - light_source: LightSource, - surface_scale: f32, - lighting_color: Color, - light_factor: &dyn Fn(Normal, Vector3) -> f32, - calc_alpha: fn(u8, u8, u8) -> u8, - src: ImageRef, - mut dest: ImageRefMut, -) { - if src.width < 3 || src.height < 3 { - return; - } - - let width = src.width; - let height = src.height; - - // `feDistantLight` has a fixed vector, so calculate it beforehand. - let mut light_vector = match light_source { - LightSource::DistantLight(light) => { - let azimuth = light.azimuth.to_radians(); - let elevation = light.elevation.to_radians(); - Vector3::new( - azimuth.cos() * elevation.cos(), - azimuth.sin() * elevation.cos(), - elevation.sin(), - ) - } - _ => Vector3::new(1.0, 1.0, 1.0), - }; - - let mut calc = |nx, ny, normal: Normal| { - match light_source { - LightSource::DistantLight(_) => {} - LightSource::PointLight(ref light) => { - let nz = src.alpha_at(nx, ny) as f32 / 255.0 * surface_scale; - let origin = Vector3::new(light.x, light.y, light.z); - let v = origin - Vector3::new(nx as f32, ny as f32, nz); - light_vector = v.normalized().unwrap_or(v); - } - LightSource::SpotLight(ref light) => { - let nz = src.alpha_at(nx, ny) as f32 / 255.0 * surface_scale; - let origin = Vector3::new(light.x, light.y, light.z); - let v = origin - Vector3::new(nx as f32, ny as f32, nz); - light_vector = v.normalized().unwrap_or(v); - } - } - - let light_color = light_color(&light_source, lighting_color, light_vector); - let factor = light_factor(normal, light_vector); - - let compute = |x| (f32_bound(0.0, x as f32 * factor, 255.0) + 0.5) as u8; - - let r = compute(light_color.red); - let g = compute(light_color.green); - let b = compute(light_color.blue); - let a = calc_alpha(r, g, b); - - *dest.pixel_at_mut(nx, ny) = RGBA8 { b, g, r, a }; - }; - - calc(0, 0, top_left_normal(src)); - calc(width - 1, 0, top_right_normal(src)); - calc(0, height - 1, bottom_left_normal(src)); - calc(width - 1, height - 1, bottom_right_normal(src)); - - for x in 1..width - 1 { - calc(x, 0, top_row_normal(src, x)); - calc(x, height - 1, bottom_row_normal(src, x)); - } - - for y in 1..height - 1 { - calc(0, y, left_column_normal(src, y)); - calc(width - 1, y, right_column_normal(src, y)); - } - - for y in 1..height - 1 { - for x in 1..width - 1 { - calc(x, y, interior_normal(src, x, y)); - } - } -} - -fn light_color(light: &LightSource, lighting_color: Color, light_vector: Vector3) -> Color { - match *light { - LightSource::DistantLight(_) | LightSource::PointLight(_) => lighting_color, - LightSource::SpotLight(ref light) => { - let origin = Vector3::new(light.x, light.y, light.z); - let direction = Vector3::new(light.points_at_x, light.points_at_y, light.points_at_z); - let direction = direction - origin; - let direction = direction.normalized().unwrap_or(direction); - let minus_l_dot_s = -light_vector.dot(&direction); - if minus_l_dot_s <= 0.0 { - return Color::black(); - } - - if let Some(limiting_cone_angle) = light.limiting_cone_angle { - if minus_l_dot_s < limiting_cone_angle.to_radians().cos() { - return Color::black(); - } - } - - let factor = minus_l_dot_s.powf(light.specular_exponent.get()); - let compute = |x| (f32_bound(0.0, x as f32 * factor, 255.0) + 0.5) as u8; - - Color::new_rgb( - compute(lighting_color.red), - compute(lighting_color.green), - compute(lighting_color.blue), - ) - } - } -} - -fn top_left_normal(img: ImageRef) -> Normal { - let center = img.alpha_at(0, 0); - let right = img.alpha_at(1, 0); - let bottom = img.alpha_at(0, 1); - let bottom_right = img.alpha_at(1, 1); - - Normal::new( - FACTOR_2_3, - FACTOR_2_3, - -2 * center + 2 * right - bottom + bottom_right, - -2 * center - right + 2 * bottom + bottom_right, - ) -} - -fn top_right_normal(img: ImageRef) -> Normal { - let left = img.alpha_at(img.width - 2, 0); - let center = img.alpha_at(img.width - 1, 0); - let bottom_left = img.alpha_at(img.width - 2, 1); - let bottom = img.alpha_at(img.width - 1, 1); - - Normal::new( - FACTOR_2_3, - FACTOR_2_3, - -2 * left + 2 * center - bottom_left + bottom, - -left - 2 * center + bottom_left + 2 * bottom, - ) -} - -fn bottom_left_normal(img: ImageRef) -> Normal { - let top = img.alpha_at(0, img.height - 2); - let top_right = img.alpha_at(1, img.height - 2); - let center = img.alpha_at(0, img.height - 1); - let right = img.alpha_at(1, img.height - 1); - - Normal::new( - FACTOR_2_3, - FACTOR_2_3, - -top + top_right - 2 * center + 2 * right, - -2 * top - top_right + 2 * center + right, - ) -} - -fn bottom_right_normal(img: ImageRef) -> Normal { - let top_left = img.alpha_at(img.width - 2, img.height - 2); - let top = img.alpha_at(img.width - 1, img.height - 2); - let left = img.alpha_at(img.width - 2, img.height - 1); - let center = img.alpha_at(img.width - 1, img.height - 1); - - Normal::new( - FACTOR_2_3, - FACTOR_2_3, - -top_left + top - 2 * left + 2 * center, - -top_left - 2 * top + left + 2 * center, - ) -} - -fn top_row_normal(img: ImageRef, x: u32) -> Normal { - let left = img.alpha_at(x - 1, 0); - let center = img.alpha_at(x, 0); - let right = img.alpha_at(x + 1, 0); - let bottom_left = img.alpha_at(x - 1, 1); - let bottom = img.alpha_at(x, 1); - let bottom_right = img.alpha_at(x + 1, 1); - - Normal::new( - FACTOR_1_3, - FACTOR_1_2, - -2 * left + 2 * right - bottom_left + bottom_right, - -left - 2 * center - right + bottom_left + 2 * bottom + bottom_right, - ) -} - -fn bottom_row_normal(img: ImageRef, x: u32) -> Normal { - let top_left = img.alpha_at(x - 1, img.height - 2); - let top = img.alpha_at(x, img.height - 2); - let top_right = img.alpha_at(x + 1, img.height - 2); - let left = img.alpha_at(x - 1, img.height - 1); - let center = img.alpha_at(x, img.height - 1); - let right = img.alpha_at(x + 1, img.height - 1); - - Normal::new( - FACTOR_1_3, - FACTOR_1_2, - -top_left + top_right - 2 * left + 2 * right, - -top_left - 2 * top - top_right + left + 2 * center + right, - ) -} - -fn left_column_normal(img: ImageRef, y: u32) -> Normal { - let top = img.alpha_at(0, y - 1); - let top_right = img.alpha_at(1, y - 1); - let center = img.alpha_at(0, y); - let right = img.alpha_at(1, y); - let bottom = img.alpha_at(0, y + 1); - let bottom_right = img.alpha_at(1, y + 1); - - Normal::new( - FACTOR_1_2, - FACTOR_1_3, - -top + top_right - 2 * center + 2 * right - bottom + bottom_right, - -2 * top - top_right + 2 * bottom + bottom_right, - ) -} - -fn right_column_normal(img: ImageRef, y: u32) -> Normal { - let top_left = img.alpha_at(img.width - 2, y - 1); - let top = img.alpha_at(img.width - 1, y - 1); - let left = img.alpha_at(img.width - 2, y); - let center = img.alpha_at(img.width - 1, y); - let bottom_left = img.alpha_at(img.width - 2, y + 1); - let bottom = img.alpha_at(img.width - 1, y + 1); - - Normal::new( - FACTOR_1_2, - FACTOR_1_3, - -top_left + top - 2 * left + 2 * center - bottom_left + bottom, - -top_left - 2 * top + bottom_left + 2 * bottom, - ) -} - -fn interior_normal(img: ImageRef, x: u32, y: u32) -> Normal { - let top_left = img.alpha_at(x - 1, y - 1); - let top = img.alpha_at(x, y - 1); - let top_right = img.alpha_at(x + 1, y - 1); - let left = img.alpha_at(x - 1, y); - let right = img.alpha_at(x + 1, y); - let bottom_left = img.alpha_at(x - 1, y + 1); - let bottom = img.alpha_at(x, y + 1); - let bottom_right = img.alpha_at(x + 1, y + 1); - - Normal::new( - FACTOR_1_4, - FACTOR_1_4, - -top_left + top_right - 2 * left + 2 * right - bottom_left + bottom_right, - -top_left - 2 * top - top_right + bottom_left + 2 * bottom + bottom_right, - ) -} - -fn calc_diffuse_alpha(_: u8, _: u8, _: u8) -> u8 { - 255 -} - -fn calc_specular_alpha(r: u8, g: u8, b: u8) -> u8 { - use core::cmp::max; - max(max(r, g), b) -} diff --git a/resvg-0.43.0/crates/resvg/src/filter/mod.rs b/resvg-0.43.0/crates/resvg/src/filter/mod.rs deleted file mode 100644 index 6aa5c27..0000000 --- a/resvg-0.43.0/crates/resvg/src/filter/mod.rs +++ /dev/null @@ -1,1141 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use std::rc::Rc; - -use rgb::{FromSlice, RGBA8}; -use tiny_skia::IntRect; -use usvg::{ApproxEqUlps, ApproxZeroUlps}; - -mod box_blur; -mod color_matrix; -mod component_transfer; -mod composite; -mod convolve_matrix; -mod displacement_map; -mod iir_blur; -mod lighting; -mod morphology; -mod turbulence; - -// TODO: apply single primitive filters in-place - -/// An image reference. -/// -/// Image pixels should be stored in RGBA order. -/// -/// Some filters will require premultipled channels, some not. -/// See specific filter documentation for details. -#[derive(Clone, Copy)] -pub struct ImageRef<'a> { - data: &'a [RGBA8], - width: u32, - height: u32, -} - -impl<'a> ImageRef<'a> { - /// Creates a new image reference. - /// - /// Doesn't clone the provided data. - #[inline] - pub fn new(width: u32, height: u32, data: &'a [RGBA8]) -> Self { - ImageRef { - data, - width, - height, - } - } - - #[inline] - fn alpha_at(&self, x: u32, y: u32) -> i16 { - self.data[(self.width * y + x) as usize].a as i16 - } -} - -/// A mutable `ImageRef` variant. -pub struct ImageRefMut<'a> { - data: &'a mut [RGBA8], - width: u32, - height: u32, -} - -impl<'a> ImageRefMut<'a> { - /// Creates a new mutable image reference. - /// - /// Doesn't clone the provided data. - #[inline] - pub fn new(width: u32, height: u32, data: &'a mut [RGBA8]) -> Self { - ImageRefMut { - data, - width, - height, - } - } - - #[inline] - fn pixel_at(&self, x: u32, y: u32) -> RGBA8 { - self.data[(self.width * y + x) as usize] - } - - #[inline] - fn pixel_at_mut(&mut self, x: u32, y: u32) -> &mut RGBA8 { - &mut self.data[(self.width * y + x) as usize] - } -} - -#[derive(Debug)] -pub(crate) enum Error { - InvalidRegion, - NoResults, -} - -trait PixmapExt: Sized { - fn try_create(width: u32, height: u32) -> Result; - fn copy_region(&self, region: IntRect) -> Result; - fn clear(&mut self); - fn into_srgb(&mut self); - fn into_linear_rgb(&mut self); -} - -impl PixmapExt for tiny_skia::Pixmap { - fn try_create(width: u32, height: u32) -> Result { - tiny_skia::Pixmap::new(width, height).ok_or(Error::InvalidRegion) - } - - fn copy_region(&self, region: IntRect) -> Result { - let rect = IntRect::from_xywh(region.x(), region.y(), region.width(), region.height()) - .ok_or(Error::InvalidRegion)?; - self.clone_rect(rect).ok_or(Error::InvalidRegion) - } - - fn clear(&mut self) { - self.fill(tiny_skia::Color::TRANSPARENT); - } - - fn into_srgb(&mut self) { - demultiply_alpha(self.data_mut().as_rgba_mut()); - from_linear_rgb(self.data_mut().as_rgba_mut()); - multiply_alpha(self.data_mut().as_rgba_mut()); - } - - fn into_linear_rgb(&mut self) { - demultiply_alpha(self.data_mut().as_rgba_mut()); - into_linear_rgb(self.data_mut().as_rgba_mut()); - multiply_alpha(self.data_mut().as_rgba_mut()); - } -} - -/// Multiplies provided pixels alpha. -fn multiply_alpha(data: &mut [RGBA8]) { - for p in data { - let a = p.a as f32 / 255.0; - p.b = (p.b as f32 * a + 0.5) as u8; - p.g = (p.g as f32 * a + 0.5) as u8; - p.r = (p.r as f32 * a + 0.5) as u8; - } -} - -/// Demultiplies provided pixels alpha. -fn demultiply_alpha(data: &mut [RGBA8]) { - for p in data { - let a = p.a as f32 / 255.0; - p.b = (p.b as f32 / a + 0.5) as u8; - p.g = (p.g as f32 / a + 0.5) as u8; - p.r = (p.r as f32 / a + 0.5) as u8; - } -} - -/// Precomputed sRGB to LinearRGB table. -/// -/// Since we are storing the result in `u8`, there is no need to compute those -/// values each time. Mainly because it's very expensive. -/// -/// ```text -/// if (C_srgb <= 0.04045) -/// C_lin = C_srgb / 12.92; -/// else -/// C_lin = pow((C_srgb + 0.055) / 1.055, 2.4); -/// ``` -/// -/// Thanks to librsvg for the idea. -#[rustfmt::skip] -const SRGB_TO_LINEAR_RGB_TABLE: &[u8; 256] = &[ - 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, - 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, - 8, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 12, 12, 12, 13, - 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 17, 18, 18, 19, 19, 20, - 20, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 27, 27, 28, 29, 29, - 30, 30, 31, 32, 32, 33, 34, 35, 35, 36, 37, 37, 38, 39, 40, 41, - 41, 42, 43, 44, 45, 45, 46, 47, 48, 49, 50, 51, 51, 52, 53, 54, - 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, - 71, 72, 73, 74, 76, 77, 78, 79, 80, 81, 82, 84, 85, 86, 87, 88, - 90, 91, 92, 93, 95, 96, 97, 99, 100, 101, 103, 104, 105, 107, 108, 109, - 111, 112, 114, 115, 116, 118, 119, 121, 122, 124, 125, 127, 128, 130, 131, 133, - 134, 136, 138, 139, 141, 142, 144, 146, 147, 149, 151, 152, 154, 156, 157, 159, - 161, 163, 164, 166, 168, 170, 171, 173, 175, 177, 179, 181, 183, 184, 186, 188, - 190, 192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, - 222, 224, 226, 229, 231, 233, 235, 237, 239, 242, 244, 246, 248, 250, 253, 255, -]; - -/// Precomputed LinearRGB to sRGB table. -/// -/// Since we are storing the result in `u8`, there is no need to compute those -/// values each time. Mainly because it's very expensive. -/// -/// ```text -/// if (C_lin <= 0.0031308) -/// C_srgb = C_lin * 12.92; -/// else -/// C_srgb = 1.055 * pow(C_lin, 1.0 / 2.4) - 0.055; -/// ``` -/// -/// Thanks to librsvg for the idea. -#[rustfmt::skip] -const LINEAR_RGB_TO_SRGB_TABLE: &[u8; 256] = &[ - 0, 13, 22, 28, 34, 38, 42, 46, 50, 53, 56, 59, 61, 64, 66, 69, - 71, 73, 75, 77, 79, 81, 83, 85, 86, 88, 90, 92, 93, 95, 96, 98, - 99, 101, 102, 104, 105, 106, 108, 109, 110, 112, 113, 114, 115, 117, 118, 119, - 120, 121, 122, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, - 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 148, 149, 150, 151, - 152, 153, 154, 155, 155, 156, 157, 158, 159, 159, 160, 161, 162, 163, 163, 164, - 165, 166, 167, 167, 168, 169, 170, 170, 171, 172, 173, 173, 174, 175, 175, 176, - 177, 178, 178, 179, 180, 180, 181, 182, 182, 183, 184, 185, 185, 186, 187, 187, - 188, 189, 189, 190, 190, 191, 192, 192, 193, 194, 194, 195, 196, 196, 197, 197, - 198, 199, 199, 200, 200, 201, 202, 202, 203, 203, 204, 205, 205, 206, 206, 207, - 208, 208, 209, 209, 210, 210, 211, 212, 212, 213, 213, 214, 214, 215, 215, 216, - 216, 217, 218, 218, 219, 219, 220, 220, 221, 221, 222, 222, 223, 223, 224, 224, - 225, 226, 226, 227, 227, 228, 228, 229, 229, 230, 230, 231, 231, 232, 232, 233, - 233, 234, 234, 235, 235, 236, 236, 237, 237, 238, 238, 238, 239, 239, 240, 240, - 241, 241, 242, 242, 243, 243, 244, 244, 245, 245, 246, 246, 246, 247, 247, 248, - 248, 249, 249, 250, 250, 251, 251, 251, 252, 252, 253, 253, 254, 254, 255, 255, -]; - -/// Converts input pixel from sRGB into LinearRGB. -/// -/// Provided pixels should have an **unpremultiplied alpha**. -/// -/// RGB channels order of the input image doesn't matter, but alpha channel must be the last one. -fn into_linear_rgb(data: &mut [RGBA8]) { - for p in data { - p.r = SRGB_TO_LINEAR_RGB_TABLE[p.r as usize]; - p.g = SRGB_TO_LINEAR_RGB_TABLE[p.g as usize]; - p.b = SRGB_TO_LINEAR_RGB_TABLE[p.b as usize]; - } -} - -/// Converts input pixel from LinearRGB into sRGB. -/// -/// Provided pixels should have an **unpremultiplied alpha**. -/// -/// RGB channels order of the input image doesn't matter, but alpha channel must be the last one. -fn from_linear_rgb(data: &mut [RGBA8]) { - for p in data { - p.r = LINEAR_RGB_TO_SRGB_TABLE[p.r as usize]; - p.g = LINEAR_RGB_TO_SRGB_TABLE[p.g as usize]; - p.b = LINEAR_RGB_TO_SRGB_TABLE[p.b as usize]; - } -} - -// TODO: https://github.com/rust-lang/rust/issues/44095 -#[inline] -fn f32_bound(min: f32, val: f32, max: f32) -> f32 { - debug_assert!(min.is_finite()); - debug_assert!(val.is_finite()); - debug_assert!(max.is_finite()); - - if val > max { - max - } else if val < min { - min - } else { - val - } -} - -#[derive(Clone)] -struct Image { - /// Filter primitive result. - /// - /// All images have the same size which is equal to the current filter region. - image: Rc, - - /// Image's region that has actual data. - /// - /// Region is in global coordinates and not in `image` one. - /// - /// Image's content outside this region will be transparent/cleared. - /// - /// Currently used only for `feTile`. - region: IntRect, - - /// The current color space. - color_space: usvg::filter::ColorInterpolation, -} - -impl Image { - fn from_image(image: tiny_skia::Pixmap, color_space: usvg::filter::ColorInterpolation) -> Self { - let (w, h) = (image.width(), image.height()); - Image { - image: Rc::new(image), - region: IntRect::from_xywh(0, 0, w, h).unwrap(), - color_space, - } - } - - fn into_color_space( - self, - color_space: usvg::filter::ColorInterpolation, - ) -> Result { - if color_space != self.color_space { - let region = self.region; - - let mut image = self.take()?; - - match color_space { - usvg::filter::ColorInterpolation::SRGB => image.into_srgb(), - usvg::filter::ColorInterpolation::LinearRGB => image.into_linear_rgb(), - } - - Ok(Image { - image: Rc::new(image), - region, - color_space, - }) - } else { - Ok(self) - } - } - - fn take(self) -> Result { - match Rc::try_unwrap(self.image) { - Ok(v) => Ok(v), - Err(v) => Ok((*v).clone()), - } - } - - fn width(&self) -> u32 { - self.image.width() - } - - fn height(&self) -> u32 { - self.image.height() - } - - fn as_ref(&self) -> &tiny_skia::Pixmap { - &self.image - } -} - -struct FilterResult { - name: String, - image: Image, -} - -pub fn apply( - filter: &usvg::filter::Filter, - ts: tiny_skia::Transform, - source: &mut tiny_skia::Pixmap, -) { - let result = apply_inner(filter, ts, source); - let result = result.and_then(|image| apply_to_canvas(image, source)); - - // Clear on error. - if result.is_err() { - source.fill(tiny_skia::Color::TRANSPARENT); - } - - match result { - Ok(_) => {} - Err(Error::InvalidRegion) => { - log::warn!("Filter has an invalid region."); - } - Err(Error::NoResults) => {} - } -} - -fn apply_inner( - filter: &usvg::filter::Filter, - ts: usvg::Transform, - source: &mut tiny_skia::Pixmap, -) -> Result { - let region = filter - .rect() - .transform(ts) - .map(|r| r.to_int_rect()) - .ok_or(Error::InvalidRegion)?; - - let mut results: Vec = Vec::new(); - - for primitive in filter.primitives() { - let mut subregion = primitive - .rect() - .transform(ts) - .map(|r| r.to_int_rect()) - .ok_or(Error::InvalidRegion)?; - - // `feOffset` inherits its region from the input. - if let usvg::filter::Kind::Offset(ref fe) = primitive.kind() { - if let usvg::filter::Input::Reference(ref name) = fe.input() { - if let Some(res) = results.iter().rev().find(|v| v.name == *name) { - subregion = res.image.region; - } - } - } - - let cs = primitive.color_interpolation(); - - let mut result = match primitive.kind() { - usvg::filter::Kind::Blend(ref fe) => { - let input1 = get_input(fe.input1(), region, source, &results)?; - let input2 = get_input(fe.input2(), region, source, &results)?; - apply_blend(fe, cs, region, input1, input2) - } - usvg::filter::Kind::DropShadow(ref fe) => { - let input = get_input(fe.input(), region, source, &results)?; - apply_drop_shadow(fe, cs, ts, input) - } - usvg::filter::Kind::Flood(ref fe) => apply_flood(fe, region), - usvg::filter::Kind::GaussianBlur(ref fe) => { - let input = get_input(fe.input(), region, source, &results)?; - apply_blur(fe, cs, ts, input) - } - usvg::filter::Kind::Offset(ref fe) => { - let input = get_input(fe.input(), region, source, &results)?; - apply_offset(fe, ts, input) - } - usvg::filter::Kind::Composite(ref fe) => { - let input1 = get_input(fe.input1(), region, source, &results)?; - let input2 = get_input(fe.input2(), region, source, &results)?; - apply_composite(fe, cs, region, input1, input2) - } - usvg::filter::Kind::Merge(ref fe) => apply_merge(fe, cs, region, source, &results), - usvg::filter::Kind::Tile(ref fe) => { - let input = get_input(fe.input(), region, source, &results)?; - apply_tile(input, region) - } - usvg::filter::Kind::Image(ref fe) => apply_image(fe, region, subregion, ts), - usvg::filter::Kind::ComponentTransfer(ref fe) => { - let input = get_input(fe.input(), region, source, &results)?; - apply_component_transfer(fe, cs, input) - } - usvg::filter::Kind::ColorMatrix(ref fe) => { - let input = get_input(fe.input(), region, source, &results)?; - apply_color_matrix(fe, cs, input) - } - usvg::filter::Kind::ConvolveMatrix(ref fe) => { - let input = get_input(fe.input(), region, source, &results)?; - apply_convolve_matrix(fe, cs, input) - } - usvg::filter::Kind::Morphology(ref fe) => { - let input = get_input(fe.input(), region, source, &results)?; - apply_morphology(fe, cs, ts, input) - } - usvg::filter::Kind::DisplacementMap(ref fe) => { - let input1 = get_input(fe.input1(), region, source, &results)?; - let input2 = get_input(fe.input2(), region, source, &results)?; - apply_displacement_map(fe, region, cs, ts, input1, input2) - } - usvg::filter::Kind::Turbulence(ref fe) => apply_turbulence(fe, region, cs, ts), - usvg::filter::Kind::DiffuseLighting(ref fe) => { - let input = get_input(fe.input(), region, source, &results)?; - apply_diffuse_lighting(fe, region, cs, ts, input) - } - usvg::filter::Kind::SpecularLighting(ref fe) => { - let input = get_input(fe.input(), region, source, &results)?; - apply_specular_lighting(fe, region, cs, ts, input) - } - }?; - - if region != subregion { - // Clip result. - - // TODO: explain - let subregion2 = if let usvg::filter::Kind::Offset(..) = primitive.kind() { - // We do not support clipping on feOffset. - region.translate_to(0, 0) - } else { - subregion.translate(-region.x(), -region.y()) - } - .unwrap(); - - let color_space = result.color_space; - - let pixmap = { - // This is cropping by clearing the pixels outside the region. - let mut paint = tiny_skia::Paint::default(); - paint.set_color(tiny_skia::Color::BLACK); - paint.blend_mode = tiny_skia::BlendMode::Clear; - - let mut pixmap = result.take()?; - let w = pixmap.width() as f32; - let h = pixmap.height() as f32; - - if let Some(rect) = tiny_skia::Rect::from_xywh(0.0, 0.0, w, subregion2.y() as f32) { - pixmap.fill_rect(rect, &paint, tiny_skia::Transform::identity(), None); - } - - if let Some(rect) = tiny_skia::Rect::from_xywh(0.0, 0.0, subregion2.x() as f32, h) { - pixmap.fill_rect(rect, &paint, tiny_skia::Transform::identity(), None); - } - - if let Some(rect) = tiny_skia::Rect::from_xywh(subregion2.right() as f32, 0.0, w, h) - { - pixmap.fill_rect(rect, &paint, tiny_skia::Transform::identity(), None); - } - - if let Some(rect) = - tiny_skia::Rect::from_xywh(0.0, subregion2.bottom() as f32, w, h) - { - pixmap.fill_rect(rect, &paint, tiny_skia::Transform::identity(), None); - } - - pixmap - }; - - result = Image { - image: Rc::new(pixmap), - region: subregion, - color_space, - }; - } - - results.push(FilterResult { - name: primitive.result().to_string(), - image: result, - }); - } - - if let Some(res) = results.pop() { - Ok(res.image) - } else { - Err(Error::NoResults) - } -} - -fn get_input( - input: &usvg::filter::Input, - region: IntRect, - source: &tiny_skia::Pixmap, - results: &[FilterResult], -) -> Result { - match input { - usvg::filter::Input::SourceGraphic => { - let image = source.clone(); - - Ok(Image { - image: Rc::new(image), - region, - color_space: usvg::filter::ColorInterpolation::SRGB, - }) - } - usvg::filter::Input::SourceAlpha => { - let mut image = source.clone(); - // Set RGB to black. Keep alpha as is. - for p in image.data_mut().as_rgba_mut() { - p.r = 0; - p.g = 0; - p.b = 0; - } - - Ok(Image { - image: Rc::new(image), - region, - color_space: usvg::filter::ColorInterpolation::SRGB, - }) - } - usvg::filter::Input::Reference(ref name) => { - if let Some(v) = results.iter().rev().find(|v| v.name == *name) { - Ok(v.image.clone()) - } else { - // Technically unreachable. - log::warn!("Unknown filter primitive reference '{}'.", name); - get_input(&usvg::filter::Input::SourceGraphic, region, source, results) - } - } - } -} - -trait PixmapToImageRef<'a> { - fn as_image_ref(&'a self) -> ImageRef<'a>; - fn as_image_ref_mut(&'a mut self) -> ImageRefMut<'a>; -} - -impl<'a> PixmapToImageRef<'a> for tiny_skia::Pixmap { - fn as_image_ref(&'a self) -> ImageRef<'a> { - ImageRef::new(self.width(), self.height(), self.data().as_rgba()) - } - - fn as_image_ref_mut(&'a mut self) -> ImageRefMut<'a> { - ImageRefMut::new(self.width(), self.height(), self.data_mut().as_rgba_mut()) - } -} - -fn apply_drop_shadow( - fe: &usvg::filter::DropShadow, - cs: usvg::filter::ColorInterpolation, - ts: usvg::Transform, - input: Image, -) -> Result { - let (dx, dy) = match scale_coordinates(fe.dx(), fe.dy(), ts) { - Some(v) => v, - None => return Ok(input), - }; - - let mut pixmap = tiny_skia::Pixmap::try_create(input.width(), input.height())?; - let input_pixmap = input.into_color_space(cs)?.take()?; - let mut shadow_pixmap = input_pixmap.clone(); - - if let Some((std_dx, std_dy, use_box_blur)) = - resolve_std_dev(fe.std_dev_x().get(), fe.std_dev_y().get(), ts) - { - if use_box_blur { - box_blur::apply(std_dx, std_dy, shadow_pixmap.as_image_ref_mut()); - } else { - iir_blur::apply(std_dx, std_dy, shadow_pixmap.as_image_ref_mut()); - } - } - - // flood - let color = tiny_skia::Color::from_rgba8( - fe.color().red, - fe.color().green, - fe.color().blue, - fe.opacity().to_u8(), - ); - for p in shadow_pixmap.pixels_mut() { - let mut color = color; - color.apply_opacity(p.alpha() as f32 / 255.0); - *p = color.premultiply().to_color_u8(); - } - - match cs { - usvg::filter::ColorInterpolation::SRGB => shadow_pixmap.into_srgb(), - usvg::filter::ColorInterpolation::LinearRGB => shadow_pixmap.into_linear_rgb(), - } - - pixmap.draw_pixmap( - dx as i32, - dy as i32, - shadow_pixmap.as_ref(), - &tiny_skia::PixmapPaint::default(), - tiny_skia::Transform::identity(), - None, - ); - - pixmap.draw_pixmap( - 0, - 0, - input_pixmap.as_ref(), - &tiny_skia::PixmapPaint::default(), - tiny_skia::Transform::identity(), - None, - ); - - Ok(Image::from_image(pixmap, cs)) -} - -fn apply_blur( - fe: &usvg::filter::GaussianBlur, - cs: usvg::filter::ColorInterpolation, - ts: usvg::Transform, - input: Image, -) -> Result { - let (std_dx, std_dy, use_box_blur) = - match resolve_std_dev(fe.std_dev_x().get(), fe.std_dev_y().get(), ts) { - Some(v) => v, - None => return Ok(input), - }; - - let mut pixmap = input.into_color_space(cs)?.take()?; - - if use_box_blur { - box_blur::apply(std_dx, std_dy, pixmap.as_image_ref_mut()); - } else { - iir_blur::apply(std_dx, std_dy, pixmap.as_image_ref_mut()); - } - - Ok(Image::from_image(pixmap, cs)) -} - -fn apply_offset( - fe: &usvg::filter::Offset, - ts: usvg::Transform, - input: Image, -) -> Result { - let (dx, dy) = match scale_coordinates(fe.dx(), fe.dy(), ts) { - Some(v) => v, - None => return Ok(input), - }; - - if dx.approx_zero_ulps(4) && dy.approx_zero_ulps(4) { - return Ok(input); - } - - let mut pixmap = tiny_skia::Pixmap::try_create(input.width(), input.height())?; - pixmap.draw_pixmap( - dx as i32, - dy as i32, - input.as_ref().as_ref(), - &tiny_skia::PixmapPaint::default(), - tiny_skia::Transform::identity(), - None, - ); - - Ok(Image::from_image(pixmap, input.color_space)) -} - -fn apply_blend( - fe: &usvg::filter::Blend, - cs: usvg::filter::ColorInterpolation, - region: IntRect, - input1: Image, - input2: Image, -) -> Result { - let input1 = input1.into_color_space(cs)?; - let input2 = input2.into_color_space(cs)?; - - let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?; - - pixmap.draw_pixmap( - 0, - 0, - input2.as_ref().as_ref(), - &tiny_skia::PixmapPaint::default(), - tiny_skia::Transform::identity(), - None, - ); - - pixmap.draw_pixmap( - 0, - 0, - input1.as_ref().as_ref(), - &tiny_skia::PixmapPaint { - blend_mode: crate::render::convert_blend_mode(fe.mode()), - ..tiny_skia::PixmapPaint::default() - }, - tiny_skia::Transform::identity(), - None, - ); - - Ok(Image::from_image(pixmap, cs)) -} - -fn apply_composite( - fe: &usvg::filter::Composite, - cs: usvg::filter::ColorInterpolation, - region: IntRect, - input1: Image, - input2: Image, -) -> Result { - use usvg::filter::CompositeOperator as Operator; - - let input1 = input1.into_color_space(cs)?; - let input2 = input2.into_color_space(cs)?; - - let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?; - - if let Operator::Arithmetic { k1, k2, k3, k4 } = fe.operator() { - let pixmap1 = input1.take()?; - let pixmap2 = input2.take()?; - - composite::arithmetic( - k1, - k2, - k3, - k4, - pixmap1.as_image_ref(), - pixmap2.as_image_ref(), - pixmap.as_image_ref_mut(), - ); - - return Ok(Image::from_image(pixmap, cs)); - } - - pixmap.draw_pixmap( - 0, - 0, - input2.as_ref().as_ref(), - &tiny_skia::PixmapPaint::default(), - tiny_skia::Transform::identity(), - None, - ); - - let blend_mode = match fe.operator() { - Operator::Over => tiny_skia::BlendMode::SourceOver, - Operator::In => tiny_skia::BlendMode::SourceIn, - Operator::Out => tiny_skia::BlendMode::SourceOut, - Operator::Atop => tiny_skia::BlendMode::SourceAtop, - Operator::Xor => tiny_skia::BlendMode::Xor, - Operator::Arithmetic { .. } => tiny_skia::BlendMode::SourceOver, - }; - - pixmap.draw_pixmap( - 0, - 0, - input1.as_ref().as_ref(), - &tiny_skia::PixmapPaint { - blend_mode, - ..tiny_skia::PixmapPaint::default() - }, - tiny_skia::Transform::identity(), - None, - ); - - Ok(Image::from_image(pixmap, cs)) -} - -fn apply_merge( - fe: &usvg::filter::Merge, - cs: usvg::filter::ColorInterpolation, - region: IntRect, - source: &tiny_skia::Pixmap, - results: &[FilterResult], -) -> Result { - let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?; - - for input in fe.inputs() { - let input = get_input(input, region, source, results)?; - let input = input.into_color_space(cs)?; - pixmap.draw_pixmap( - 0, - 0, - input.as_ref().as_ref(), - &tiny_skia::PixmapPaint::default(), - tiny_skia::Transform::identity(), - None, - ); - } - - Ok(Image::from_image(pixmap, cs)) -} - -fn apply_flood(fe: &usvg::filter::Flood, region: IntRect) -> Result { - let c = fe.color(); - - let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?; - pixmap.fill(tiny_skia::Color::from_rgba8( - c.red, - c.green, - c.blue, - fe.opacity().to_u8(), - )); - - Ok(Image::from_image( - pixmap, - usvg::filter::ColorInterpolation::SRGB, - )) -} - -fn apply_tile(input: Image, region: IntRect) -> Result { - let subregion = input.region.translate(-region.x(), -region.y()).unwrap(); - - let tile_pixmap = input.image.copy_region(subregion)?; - let mut paint = tiny_skia::Paint::default(); - paint.shader = tiny_skia::Pattern::new( - tile_pixmap.as_ref(), - tiny_skia::SpreadMode::Repeat, - tiny_skia::FilterQuality::Bicubic, - 1.0, - tiny_skia::Transform::from_translate(subregion.x() as f32, subregion.y() as f32), - ); - - let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?; - let rect = tiny_skia::Rect::from_xywh(0.0, 0.0, region.width() as f32, region.height() as f32) - .unwrap(); - pixmap.fill_rect(rect, &paint, tiny_skia::Transform::identity(), None); - - Ok(Image::from_image( - pixmap, - usvg::filter::ColorInterpolation::SRGB, - )) -} - -fn apply_image( - fe: &usvg::filter::Image, - region: IntRect, - subregion: IntRect, - ts: usvg::Transform, -) -> Result { - let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?; - - let (sx, sy) = ts.get_scale(); - let transform = tiny_skia::Transform::from_row( - sx, - 0.0, - 0.0, - sy, - subregion.x() as f32, - subregion.y() as f32, - ); - - let ctx = crate::render::Context { - max_bbox: tiny_skia::IntRect::from_xywh(0, 0, region.width(), region.height()).unwrap(), - }; - - crate::render::render_nodes(fe.root(), &ctx, transform, &mut pixmap.as_mut()); - - Ok(Image::from_image( - pixmap, - usvg::filter::ColorInterpolation::SRGB, - )) -} - -fn apply_component_transfer( - fe: &usvg::filter::ComponentTransfer, - cs: usvg::filter::ColorInterpolation, - input: Image, -) -> Result { - let mut pixmap = input.into_color_space(cs)?.take()?; - - demultiply_alpha(pixmap.data_mut().as_rgba_mut()); - component_transfer::apply(fe, pixmap.as_image_ref_mut()); - multiply_alpha(pixmap.data_mut().as_rgba_mut()); - - Ok(Image::from_image(pixmap, cs)) -} - -fn apply_color_matrix( - fe: &usvg::filter::ColorMatrix, - cs: usvg::filter::ColorInterpolation, - input: Image, -) -> Result { - let mut pixmap = input.into_color_space(cs)?.take()?; - - demultiply_alpha(pixmap.data_mut().as_rgba_mut()); - color_matrix::apply(fe.kind(), pixmap.as_image_ref_mut()); - multiply_alpha(pixmap.data_mut().as_rgba_mut()); - - Ok(Image::from_image(pixmap, cs)) -} - -fn apply_convolve_matrix( - fe: &usvg::filter::ConvolveMatrix, - cs: usvg::filter::ColorInterpolation, - input: Image, -) -> Result { - let mut pixmap = input.into_color_space(cs)?.take()?; - - if fe.preserve_alpha() { - demultiply_alpha(pixmap.data_mut().as_rgba_mut()); - } - - convolve_matrix::apply(fe, pixmap.as_image_ref_mut()); - - Ok(Image::from_image(pixmap, cs)) -} - -fn apply_morphology( - fe: &usvg::filter::Morphology, - cs: usvg::filter::ColorInterpolation, - ts: usvg::Transform, - input: Image, -) -> Result { - let mut pixmap = input.into_color_space(cs)?.take()?; - - let (rx, ry) = match scale_coordinates(fe.radius_x().get(), fe.radius_y().get(), ts) { - Some(v) => v, - None => return Ok(Image::from_image(pixmap, cs)), - }; - - if !(rx > 0.0 && ry > 0.0) { - pixmap.clear(); - return Ok(Image::from_image(pixmap, cs)); - } - - morphology::apply(fe.operator(), rx, ry, pixmap.as_image_ref_mut()); - - Ok(Image::from_image(pixmap, cs)) -} - -fn apply_displacement_map( - fe: &usvg::filter::DisplacementMap, - region: IntRect, - cs: usvg::filter::ColorInterpolation, - ts: usvg::Transform, - input1: Image, - input2: Image, -) -> Result { - let pixmap1 = input1.into_color_space(cs)?.take()?; - let pixmap2 = input2.into_color_space(cs)?.take()?; - - let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?; - - let (sx, sy) = match scale_coordinates(fe.scale(), fe.scale(), ts) { - Some(v) => v, - None => return Ok(Image::from_image(pixmap1, cs)), - }; - - displacement_map::apply( - fe, - sx, - sy, - pixmap1.as_image_ref(), - pixmap2.as_image_ref(), - pixmap.as_image_ref_mut(), - ); - - Ok(Image::from_image(pixmap, cs)) -} - -fn apply_turbulence( - fe: &usvg::filter::Turbulence, - region: IntRect, - cs: usvg::filter::ColorInterpolation, - ts: usvg::Transform, -) -> Result { - let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?; - - let (sx, sy) = ts.get_scale(); - if sx.approx_zero_ulps(4) || sy.approx_zero_ulps(4) { - return Ok(Image::from_image(pixmap, cs)); - } - - turbulence::apply( - region.x() as f64 - ts.tx as f64, - region.y() as f64 - ts.ty as f64, - sx as f64, - sy as f64, - fe.base_frequency_x().get() as f64, - fe.base_frequency_y().get() as f64, - fe.num_octaves(), - fe.seed(), - fe.stitch_tiles(), - fe.kind() == usvg::filter::TurbulenceKind::FractalNoise, - pixmap.as_image_ref_mut(), - ); - - multiply_alpha(pixmap.data_mut().as_rgba_mut()); - - Ok(Image::from_image(pixmap, cs)) -} - -fn apply_diffuse_lighting( - fe: &usvg::filter::DiffuseLighting, - region: IntRect, - cs: usvg::filter::ColorInterpolation, - ts: usvg::Transform, - input: Image, -) -> Result { - let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?; - - let light_source = transform_light_source(fe.light_source(), region, ts); - - lighting::diffuse_lighting( - fe, - light_source, - input.as_ref().as_image_ref(), - pixmap.as_image_ref_mut(), - ); - - Ok(Image::from_image(pixmap, cs)) -} - -fn apply_specular_lighting( - fe: &usvg::filter::SpecularLighting, - region: IntRect, - cs: usvg::filter::ColorInterpolation, - ts: usvg::Transform, - input: Image, -) -> Result { - let mut pixmap = tiny_skia::Pixmap::try_create(region.width(), region.height())?; - - let light_source = transform_light_source(fe.light_source(), region, ts); - - lighting::specular_lighting( - fe, - light_source, - input.as_ref().as_image_ref(), - pixmap.as_image_ref_mut(), - ); - - Ok(Image::from_image(pixmap, cs)) -} - -// TODO: do not modify LightSource -fn transform_light_source( - mut source: usvg::filter::LightSource, - region: IntRect, - ts: usvg::Transform, -) -> usvg::filter::LightSource { - use std::f32::consts::SQRT_2; - use usvg::filter::LightSource; - - match source { - LightSource::DistantLight(..) => {} - LightSource::PointLight(ref mut light) => { - let mut point = tiny_skia::Point::from_xy(light.x, light.y); - ts.map_point(&mut point); - light.x = point.x - region.x() as f32; - light.y = point.y - region.y() as f32; - light.z = light.z * (ts.sx * ts.sx + ts.sy * ts.sy).sqrt() / SQRT_2; - } - LightSource::SpotLight(ref mut light) => { - let sz = (ts.sx * ts.sx + ts.sy * ts.sy).sqrt() / SQRT_2; - - let mut point = tiny_skia::Point::from_xy(light.x, light.y); - ts.map_point(&mut point); - light.x = point.x - region.x() as f32; - light.y = point.y - region.x() as f32; - light.z *= sz; - - let mut point = tiny_skia::Point::from_xy(light.points_at_x, light.points_at_y); - ts.map_point(&mut point); - light.points_at_x = point.x - region.x() as f32; - light.points_at_y = point.y - region.x() as f32; - light.points_at_z *= sz; - } - } - - source -} - -fn apply_to_canvas(input: Image, pixmap: &mut tiny_skia::Pixmap) -> Result<(), Error> { - let input = input.into_color_space(usvg::filter::ColorInterpolation::SRGB)?; - - pixmap.fill(tiny_skia::Color::TRANSPARENT); - pixmap.draw_pixmap( - 0, - 0, - input.as_ref().as_ref(), - &tiny_skia::PixmapPaint::default(), - tiny_skia::Transform::identity(), - None, - ); - - Ok(()) -} - -/// Calculates Gaussian blur sigmas for the current world transform. -/// -/// If the last flag is set, then a box blur should be used. Or IIR otherwise. -fn resolve_std_dev(std_dx: f32, std_dy: f32, ts: usvg::Transform) -> Option<(f64, f64, bool)> { - let (mut std_dx, mut std_dy) = scale_coordinates(std_dx, std_dy, ts)?; - - // 'A negative value or a value of zero disables the effect of the given filter primitive - // (i.e., the result is the filter input image).' - if std_dx.approx_eq_ulps(&0.0, 4) && std_dy.approx_eq_ulps(&0.0, 4) { - return None; - } - - // Ignore tiny sigmas. In case of IIR blur it can lead to a transparent image. - if std_dx < 0.05 { - std_dx = 0.0; - } - - if std_dy < 0.05 { - std_dy = 0.0; - } - - const BLUR_SIGMA_THRESHOLD: f32 = 2.0; - // Check that the current feGaussianBlur filter can be applied using a box blur. - let box_blur = std_dx >= BLUR_SIGMA_THRESHOLD || std_dy >= BLUR_SIGMA_THRESHOLD; - - Some((std_dx as f64, std_dy as f64, box_blur)) -} - -fn scale_coordinates(x: f32, y: f32, ts: usvg::Transform) -> Option<(f32, f32)> { - let (sx, sy) = ts.get_scale(); - Some((x * sx, y * sy)) -} diff --git a/resvg-0.43.0/crates/resvg/src/filter/morphology.rs b/resvg-0.43.0/crates/resvg/src/filter/morphology.rs deleted file mode 100644 index 16134c3..0000000 --- a/resvg-0.43.0/crates/resvg/src/filter/morphology.rs +++ /dev/null @@ -1,74 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use super::ImageRefMut; -use rgb::RGBA8; -use usvg::filter::MorphologyOperator; - -/// Applies a morphology filter. -/// -/// `src` pixels should have a **premultiplied alpha**. -/// -/// # Allocations -/// -/// This method will allocate a copy of the `src` image as a back buffer. -pub fn apply(operator: MorphologyOperator, rx: f32, ry: f32, src: ImageRefMut) { - // No point in making matrix larger than image. - let columns = std::cmp::min(rx.ceil() as u32 * 2, src.width); - let rows = std::cmp::min(ry.ceil() as u32 * 2, src.height); - let target_x = (columns as f32 / 2.0).floor() as u32; - let target_y = (rows as f32 / 2.0).floor() as u32; - - let width_max = src.width as i32 - 1; - let height_max = src.height as i32 - 1; - - let mut buf = vec![RGBA8::default(); src.data.len()]; - let mut buf = ImageRefMut::new(src.width, src.height, &mut buf); - let mut x = 0; - let mut y = 0; - for _ in src.data.iter() { - let mut new_p = RGBA8::default(); - if operator == MorphologyOperator::Erode { - new_p.r = 255; - new_p.g = 255; - new_p.b = 255; - new_p.a = 255; - } - - for oy in 0..rows { - for ox in 0..columns { - let tx = x as i32 - target_x as i32 + ox as i32; - let ty = y as i32 - target_y as i32 + oy as i32; - - if tx < 0 || tx > width_max || ty < 0 || ty > height_max { - continue; - } - - let p = src.pixel_at(tx as u32, ty as u32); - if operator == MorphologyOperator::Erode { - new_p.r = std::cmp::min(p.r, new_p.r); - new_p.g = std::cmp::min(p.g, new_p.g); - new_p.b = std::cmp::min(p.b, new_p.b); - new_p.a = std::cmp::min(p.a, new_p.a); - } else { - new_p.r = std::cmp::max(p.r, new_p.r); - new_p.g = std::cmp::max(p.g, new_p.g); - new_p.b = std::cmp::max(p.b, new_p.b); - new_p.a = std::cmp::max(p.a, new_p.a); - } - } - } - - *buf.pixel_at_mut(x, y) = new_p; - - x += 1; - if x == src.width { - x = 0; - y += 1; - } - } - - // Do not use `mem::swap` because `data` referenced via FFI. - src.data.copy_from_slice(buf.data); -} diff --git a/resvg-0.43.0/crates/resvg/src/filter/turbulence.rs b/resvg-0.43.0/crates/resvg/src/filter/turbulence.rs deleted file mode 100644 index 3485abb..0000000 --- a/resvg-0.43.0/crates/resvg/src/filter/turbulence.rs +++ /dev/null @@ -1,305 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#![allow(clippy::needless_range_loop)] - -use super::{f32_bound, ImageRefMut}; -use usvg::ApproxZeroUlps; - -const RAND_M: i32 = 2147483647; // 2**31 - 1 -const RAND_A: i32 = 16807; // 7**5; primitive root of m -const RAND_Q: i32 = 127773; // m / a -const RAND_R: i32 = 2836; // m % a -const B_SIZE: usize = 0x100; -const B_SIZE_32: i32 = 0x100; -const B_LEN: usize = B_SIZE + B_SIZE + 2; -const BM: i32 = 0xff; -const PERLIN_N: i32 = 0x1000; - -#[derive(Clone, Copy)] -struct StitchInfo { - width: i32, // How much to subtract to wrap for stitching. - height: i32, - wrap_x: i32, // Minimum value to wrap. - wrap_y: i32, -} - -/// Applies a turbulence filter. -/// -/// `dest` image pixels will have an **unpremultiplied alpha**. -/// -/// - `offset_x` and `offset_y` indicate filter region offset. -/// - `sx` and `sy` indicate canvas scale. -pub fn apply( - offset_x: f64, - offset_y: f64, - sx: f64, - sy: f64, - base_frequency_x: f64, - base_frequency_y: f64, - num_octaves: u32, - seed: i32, - stitch_tiles: bool, - fractal_noise: bool, - dest: ImageRefMut, -) { - let (lattice_selector, gradient) = init(seed); - let width = dest.width; - let height = dest.height; - let mut x = 0; - let mut y = 0; - for pixel in dest.data.iter_mut() { - let turb = |channel| { - let (tx, ty) = ((x as f64 + offset_x) / sx, (y as f64 + offset_y) / sy); - let n = turbulence( - channel, - tx, - ty, - x as f64, - y as f64, - width as f64, - height as f64, - base_frequency_x, - base_frequency_y, - num_octaves, - fractal_noise, - stitch_tiles, - &lattice_selector, - &gradient, - ); - - let n = if fractal_noise { - (n * 255.0 + 255.0) / 2.0 - } else { - n * 255.0 - }; - - (f32_bound(0.0, n as f32, 255.0) + 0.5) as u8 - }; - - pixel.r = turb(0); - pixel.g = turb(1); - pixel.b = turb(2); - pixel.a = turb(3); - - x += 1; - if x == dest.width { - x = 0; - y += 1; - } - } -} - -fn init(mut seed: i32) -> (Vec, Vec>>) { - let mut lattice_selector = vec![0; B_LEN]; - let mut gradient = vec![vec![vec![0.0; 2]; B_LEN]; 4]; - - if seed <= 0 { - seed = -seed % (RAND_M - 1) + 1; - } - - if seed > RAND_M - 1 { - seed = RAND_M - 1; - } - - for k in 0..4 { - for i in 0..B_SIZE { - lattice_selector[i] = i; - for j in 0..2 { - seed = random(seed); - gradient[k][i][j] = - ((seed % (B_SIZE_32 + B_SIZE_32)) - B_SIZE_32) as f64 / B_SIZE_32 as f64; - } - - let s = (gradient[k][i][0] * gradient[k][i][0] + gradient[k][i][1] * gradient[k][i][1]) - .sqrt(); - - gradient[k][i][0] /= s; - gradient[k][i][1] /= s; - } - } - - for i in (1..B_SIZE).rev() { - let k = lattice_selector[i]; - seed = random(seed); - let j = (seed % B_SIZE_32) as usize; - lattice_selector[i] = lattice_selector[j]; - lattice_selector[j] = k; - } - - for i in 0..B_SIZE + 2 { - lattice_selector[B_SIZE + i] = lattice_selector[i]; - for g in gradient.iter_mut().take(4) { - for j in 0..2 { - g[B_SIZE + i][j] = g[i][j]; - } - } - } - - (lattice_selector, gradient) -} - -fn turbulence( - color_channel: usize, - mut x: f64, - mut y: f64, - tile_x: f64, - tile_y: f64, - tile_width: f64, - tile_height: f64, - mut base_freq_x: f64, - mut base_freq_y: f64, - num_octaves: u32, - fractal_sum: bool, - do_stitching: bool, - lattice_selector: &[usize], - gradient: &[Vec>], -) -> f64 { - // Adjust the base frequencies if necessary for stitching. - let mut stitch = if do_stitching { - // When stitching tiled turbulence, the frequencies must be adjusted - // so that the tile borders will be continuous. - if !base_freq_x.approx_zero_ulps(4) { - let lo_freq = (tile_width * base_freq_x).floor() / tile_width; - let hi_freq = (tile_width * base_freq_x).ceil() / tile_width; - if base_freq_x / lo_freq < hi_freq / base_freq_x { - base_freq_x = lo_freq; - } else { - base_freq_x = hi_freq; - } - } - - if !base_freq_y.approx_zero_ulps(4) { - let lo_freq = (tile_height * base_freq_y).floor() / tile_height; - let hi_freq = (tile_height * base_freq_y).ceil() / tile_height; - if base_freq_y / lo_freq < hi_freq / base_freq_y { - base_freq_y = lo_freq; - } else { - base_freq_y = hi_freq; - } - } - - // Set up initial stitch values. - let width = (tile_width * base_freq_x + 0.5) as i32; - let height = (tile_height * base_freq_y + 0.5) as i32; - let wrap_x = (tile_x * base_freq_x + PERLIN_N as f64 + width as f64) as i32; - let wrap_y = (tile_y * base_freq_y + PERLIN_N as f64 + height as f64) as i32; - Some(StitchInfo { - width, - height, - wrap_x, - wrap_y, - }) - } else { - None - }; - - let mut sum = 0.0; - x *= base_freq_x; - y *= base_freq_y; - let mut ratio = 1.0; - for _ in 0..num_octaves { - if fractal_sum { - sum += noise2(color_channel, x, y, lattice_selector, gradient, stitch) / ratio; - } else { - sum += noise2(color_channel, x, y, lattice_selector, gradient, stitch).abs() / ratio; - } - x *= 2.0; - y *= 2.0; - ratio *= 2.0; - - if let Some(ref mut stitch) = stitch { - // Update stitch values. Subtracting PerlinN before the multiplication and - // adding it afterward simplifies to subtracting it once. - stitch.width *= 2; - stitch.wrap_x = 2 * stitch.wrap_x - PERLIN_N; - stitch.height *= 2; - stitch.wrap_y = 2 * stitch.wrap_y - PERLIN_N; - } - } - - sum -} - -fn noise2( - color_channel: usize, - x: f64, - y: f64, - lattice_selector: &[usize], - gradient: &[Vec>], - stitch_info: Option, -) -> f64 { - let t = x + PERLIN_N as f64; - let mut bx0 = t as i32; - let mut bx1 = bx0 + 1; - let rx0 = t - t as i64 as f64; - let rx1 = rx0 - 1.0; - let t = y + PERLIN_N as f64; - let mut by0 = t as i32; - let mut by1 = by0 + 1; - let ry0 = t - t as i64 as f64; - let ry1 = ry0 - 1.0; - - // If stitching, adjust lattice points accordingly. - if let Some(info) = stitch_info { - if bx0 >= info.wrap_x { - bx0 -= info.width; - } - - if bx1 >= info.wrap_x { - bx1 -= info.width; - } - - if by0 >= info.wrap_y { - by0 -= info.height; - } - - if by1 >= info.wrap_y { - by1 -= info.height; - } - } - - bx0 &= BM; - bx1 &= BM; - by0 &= BM; - by1 &= BM; - let i = lattice_selector[bx0 as usize]; - let j = lattice_selector[bx1 as usize]; - let b00 = lattice_selector[i + by0 as usize]; - let b10 = lattice_selector[j + by0 as usize]; - let b01 = lattice_selector[i + by1 as usize]; - let b11 = lattice_selector[j + by1 as usize]; - let sx = s_curve(rx0); - let sy = s_curve(ry0); - let q = &gradient[color_channel][b00]; - let u = rx0 * q[0] + ry0 * q[1]; - let q = &gradient[color_channel][b10]; - let v = rx1 * q[0] + ry0 * q[1]; - let a = lerp(sx, u, v); - let q = &gradient[color_channel][b01]; - let u = rx0 * q[0] + ry1 * q[1]; - let q = &gradient[color_channel][b11]; - let v = rx1 * q[0] + ry1 * q[1]; - let b = lerp(sx, u, v); - lerp(sy, a, b) -} - -fn random(seed: i32) -> i32 { - let mut result = RAND_A * (seed % RAND_Q) - RAND_R * (seed / RAND_Q); - if result <= 0 { - result += RAND_M; - } - - result -} - -#[inline] -fn s_curve(t: f64) -> f64 { - t * t * (3.0 - 2.0 * t) -} - -#[inline] -fn lerp(t: f64, a: f64, b: f64) -> f64 { - a + t * (b - a) -} diff --git a/resvg-0.43.0/crates/resvg/src/geom.rs b/resvg-0.43.0/crates/resvg/src/geom.rs deleted file mode 100644 index db58edd..0000000 --- a/resvg-0.43.0/crates/resvg/src/geom.rs +++ /dev/null @@ -1,31 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -/// Fits the current rect into the specified bounds. -pub fn fit_to_rect( - r: tiny_skia::IntRect, - bounds: tiny_skia::IntRect, -) -> Option { - let mut left = r.left(); - if left < bounds.left() { - left = bounds.left(); - } - - let mut top = r.top(); - if top < bounds.top() { - top = bounds.top(); - } - - let mut right = r.right(); - if right > bounds.right() { - right = bounds.right(); - } - - let mut bottom = r.bottom(); - if bottom > bounds.bottom() { - bottom = bounds.bottom(); - } - - tiny_skia::IntRect::from_ltrb(left, top, right, bottom) -} diff --git a/resvg-0.43.0/crates/resvg/src/image.rs b/resvg-0.43.0/crates/resvg/src/image.rs deleted file mode 100644 index 3f4f349..0000000 --- a/resvg-0.43.0/crates/resvg/src/image.rs +++ /dev/null @@ -1,191 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -pub fn render( - image: &usvg::Image, - transform: tiny_skia::Transform, - pixmap: &mut tiny_skia::PixmapMut, -) { - if !image.is_visible() { - return; - } - - render_inner(image.kind(), transform, image.rendering_mode(), pixmap); -} - -pub fn render_inner( - image_kind: &usvg::ImageKind, - transform: tiny_skia::Transform, - #[allow(unused_variables)] rendering_mode: usvg::ImageRendering, - pixmap: &mut tiny_skia::PixmapMut, -) { - match image_kind { - usvg::ImageKind::SVG(ref tree) => { - render_vector(tree, transform, pixmap); - } - #[cfg(feature = "raster-images")] - _ => { - raster_images::render_raster(image_kind, transform, rendering_mode, pixmap); - } - #[cfg(not(feature = "raster-images"))] - _ => { - log::warn!("Images decoding was disabled by a build feature."); - } - } -} - -fn render_vector( - tree: &usvg::Tree, - transform: tiny_skia::Transform, - pixmap: &mut tiny_skia::PixmapMut, -) -> Option<()> { - let mut sub_pixmap = tiny_skia::Pixmap::new(pixmap.width(), pixmap.height()).unwrap(); - crate::render(tree, transform, &mut sub_pixmap.as_mut()); - pixmap.draw_pixmap( - 0, - 0, - sub_pixmap.as_ref(), - &tiny_skia::PixmapPaint::default(), - tiny_skia::Transform::default(), - None, - ); - - Some(()) -} - -#[cfg(feature = "raster-images")] -mod raster_images { - use crate::OptionLog; - - fn decode_raster(image: &usvg::ImageKind) -> Option { - match image { - usvg::ImageKind::SVG(_) => None, - usvg::ImageKind::JPEG(ref data) => { - decode_jpeg(data).log_none(|| log::warn!("Failed to decode a JPEG image.")) - } - usvg::ImageKind::PNG(ref data) => { - decode_png(data).log_none(|| log::warn!("Failed to decode a PNG image.")) - } - usvg::ImageKind::GIF(ref data) => { - decode_gif(data).log_none(|| log::warn!("Failed to decode a GIF image.")) - } - usvg::ImageKind::WEBP(ref data) => { - decode_webp(data).log_none(|| log::warn!("Failed to decode a WebP image.")) - } - } - } - - fn decode_png(data: &[u8]) -> Option { - tiny_skia::Pixmap::decode_png(data).ok() - } - - fn decode_jpeg(data: &[u8]) -> Option { - use zune_jpeg::zune_core::colorspace::ColorSpace; - use zune_jpeg::zune_core::options::DecoderOptions; - - let options = DecoderOptions::default().jpeg_set_out_colorspace(ColorSpace::RGBA); - let mut decoder = zune_jpeg::JpegDecoder::new_with_options(data, options); - let img_data = decoder.decode().ok()?; - let info = decoder.info()?; - - let size = tiny_skia::IntSize::from_wh(info.width as u32, info.height as u32)?; - tiny_skia::Pixmap::from_vec(img_data, size) - } - - fn decode_gif(data: &[u8]) -> Option { - let mut decoder = gif::DecodeOptions::new(); - decoder.set_color_output(gif::ColorOutput::RGBA); - let mut decoder = decoder.read_info(data).ok()?; - let first_frame = decoder.read_next_frame().ok()??; - - let size = tiny_skia::IntSize::from_wh( - u32::from(first_frame.width), - u32::from(first_frame.height), - )?; - - let (w, h) = size.dimensions(); - let mut pixmap = tiny_skia::Pixmap::new(w, h)?; - rgba_to_pixmap(&first_frame.buffer, &mut pixmap); - Some(pixmap) - } - - fn decode_webp(data: &[u8]) -> Option { - let mut decoder = image_webp::WebPDecoder::new(std::io::Cursor::new(data)).ok()?; - let mut first_frame = vec![0; decoder.output_buffer_size()?]; - decoder.read_image(&mut first_frame).ok()?; - - let (w, h) = decoder.dimensions(); - let mut pixmap = tiny_skia::Pixmap::new(w, h)?; - - if decoder.has_alpha() { - rgba_to_pixmap(&first_frame, &mut pixmap); - } else { - rgb_to_pixmap(&first_frame, &mut pixmap); - } - - Some(pixmap) - } - - fn rgb_to_pixmap(data: &[u8], pixmap: &mut tiny_skia::Pixmap) { - use rgb::FromSlice; - - let mut i = 0; - let dst = pixmap.data_mut(); - for p in data.as_rgb() { - dst[i + 0] = p.r; - dst[i + 1] = p.g; - dst[i + 2] = p.b; - dst[i + 3] = 255; - - i += tiny_skia::BYTES_PER_PIXEL; - } - } - - fn rgba_to_pixmap(data: &[u8], pixmap: &mut tiny_skia::Pixmap) { - use rgb::FromSlice; - - let mut i = 0; - let dst = pixmap.data_mut(); - for p in data.as_rgba() { - let a = p.a as f64 / 255.0; - dst[i + 0] = (p.r as f64 * a + 0.5) as u8; - dst[i + 1] = (p.g as f64 * a + 0.5) as u8; - dst[i + 2] = (p.b as f64 * a + 0.5) as u8; - dst[i + 3] = p.a; - - i += tiny_skia::BYTES_PER_PIXEL; - } - } - - pub(crate) fn render_raster( - image: &usvg::ImageKind, - transform: tiny_skia::Transform, - rendering_mode: usvg::ImageRendering, - pixmap: &mut tiny_skia::PixmapMut, - ) -> Option<()> { - let raster = decode_raster(image)?; - - let rect = tiny_skia::Size::from_wh(raster.width() as f32, raster.height() as f32)? - .to_rect(0.0, 0.0)?; - - let mut quality = tiny_skia::FilterQuality::Bicubic; - if rendering_mode == usvg::ImageRendering::OptimizeSpeed { - quality = tiny_skia::FilterQuality::Nearest; - } - - let pattern = tiny_skia::Pattern::new( - raster.as_ref(), - tiny_skia::SpreadMode::Pad, - quality, - 1.0, - tiny_skia::Transform::default(), - ); - let mut paint = tiny_skia::Paint::default(); - paint.shader = pattern; - - pixmap.fill_rect(rect, &paint, transform, None); - - Some(()) - } -} diff --git a/resvg-0.43.0/crates/resvg/src/lib.rs b/resvg-0.43.0/crates/resvg/src/lib.rs deleted file mode 100644 index 1fbed3a..0000000 --- a/resvg-0.43.0/crates/resvg/src/lib.rs +++ /dev/null @@ -1,99 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -/*! -[resvg](https://github.com/RazrFalcon/resvg) is an SVG rendering library. -*/ - -#![forbid(unsafe_code)] -#![warn(missing_docs)] -#![allow(clippy::field_reassign_with_default)] -#![allow(clippy::identity_op)] -#![allow(clippy::too_many_arguments)] -#![allow(clippy::uninlined_format_args)] -#![allow(clippy::upper_case_acronyms)] -#![allow(clippy::wrong_self_convention)] - -pub use tiny_skia; -pub use usvg; - -mod clip; -mod filter; -mod geom; -mod image; -mod mask; -mod path; -mod render; - -/// Renders a tree onto the pixmap. -/// -/// `transform` will be used as a root transform. -/// Can be used to position SVG inside the `pixmap`. -/// -/// The produced content is in the sRGB color space. -pub fn render( - tree: &usvg::Tree, - transform: tiny_skia::Transform, - pixmap: &mut tiny_skia::PixmapMut, -) { - let target_size = tiny_skia::IntSize::from_wh(pixmap.width(), pixmap.height()).unwrap(); - let max_bbox = tiny_skia::IntRect::from_xywh( - -(target_size.width() as i32) * 2, - -(target_size.height() as i32) * 2, - target_size.width() * 5, - target_size.height() * 5, - ) - .unwrap(); - - let ctx = render::Context { max_bbox }; - render::render_nodes(tree.root(), &ctx, transform, pixmap); -} - -/// Renders a node onto the pixmap. -/// -/// `transform` will be used as a root transform. -/// Can be used to position SVG inside the `pixmap`. -/// -/// The expected pixmap size can be retrieved from `usvg::Node::abs_layer_bounding_box()`. -/// -/// Returns `None` when `node` has a zero size. -/// -/// The produced content is in the sRGB color space. -pub fn render_node( - node: &usvg::Node, - mut transform: tiny_skia::Transform, - pixmap: &mut tiny_skia::PixmapMut, -) -> Option<()> { - let bbox = node.abs_layer_bounding_box()?; - - let target_size = tiny_skia::IntSize::from_wh(pixmap.width(), pixmap.height()).unwrap(); - let max_bbox = tiny_skia::IntRect::from_xywh( - -(target_size.width() as i32) * 2, - -(target_size.height() as i32) * 2, - target_size.width() * 5, - target_size.height() * 5, - ) - .unwrap(); - - transform = transform.pre_translate(-bbox.x(), -bbox.y()); - - let ctx = render::Context { max_bbox }; - render::render_node(node, &ctx, transform, pixmap); - - Some(()) -} - -pub(crate) trait OptionLog { - fn log_none(self, f: F) -> Self; -} - -impl OptionLog for Option { - #[inline] - fn log_none(self, f: F) -> Self { - self.or_else(|| { - f(); - None - }) - } -} diff --git a/resvg-0.43.0/crates/resvg/src/main.rs b/resvg-0.43.0/crates/resvg/src/main.rs deleted file mode 100644 index be72773..0000000 --- a/resvg-0.43.0/crates/resvg/src/main.rs +++ /dev/null @@ -1,825 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#![allow(clippy::uninlined_format_args)] - -use std::path; -use std::sync::Arc; - -use usvg::fontdb; - -fn main() { - if let Err(e) = process() { - eprintln!("Error: {}.", e); - std::process::exit(1); - } -} - -fn timed(perf: bool, name: &str, mut f: F) -> T -where - F: FnMut() -> T, -{ - let now = std::time::Instant::now(); - let result = f(); - if perf { - let elapsed = now.elapsed().as_micros() as f64 / 1000.0; - println!("{}: {:.2}ms", name, elapsed); - } - - result -} - -fn process() -> Result<(), String> { - let mut args = match parse_args() { - Ok(args) => args, - Err(e) => { - println!("{}", HELP); - return Err(e); - } - }; - - // Do not print warning during the ID querying. - // - // Some crates still can print to stdout/stderr, but we can't do anything about it. - if !(args.query_all || args.quiet) { - if let Ok(()) = log::set_logger(&LOGGER) { - log::set_max_level(log::LevelFilter::Warn); - } - } - - let mut svg_data = timed(args.perf, "Reading", || -> Result, &str> { - if let InputFrom::File(ref file) = args.in_svg { - std::fs::read(file).map_err(|_| "failed to open the provided file") - } else { - use std::io::Read; - let mut buf = Vec::new(); - let stdin = std::io::stdin(); - let mut handle = stdin.lock(); - handle - .read_to_end(&mut buf) - .map_err(|_| "failed to read stdin")?; - Ok(buf) - } - })?; - - if svg_data.starts_with(&[0x1f, 0x8b]) { - svg_data = timed(args.perf, "SVGZ Decoding", || { - usvg::decompress_svgz(&svg_data).map_err(|e| e.to_string()) - })?; - }; - - let svg_string = std::str::from_utf8(&svg_data) - .map_err(|_| "provided data has not an UTF-8 encoding".to_string())?; - - let xml_tree = timed(args.perf, "XML Parsing", || { - let xml_opt = usvg::roxmltree::ParsingOptions { - allow_dtd: true, - ..Default::default() - }; - usvg::roxmltree::Document::parse_with_options(svg_string, xml_opt) - .map_err(|e| e.to_string()) - })?; - - // fontdb initialization is pretty expensive, so perform it only when needed. - let has_text_nodes = xml_tree - .descendants() - .any(|n| n.has_tag_name(("http://www.w3.org/2000/svg", "text"))); - - if has_text_nodes { - timed(args.perf, "FontDB", || { - load_fonts(&args.raw_args, args.usvg.fontdb_mut()) - }); - } - - let tree = timed(args.perf, "SVG Parsing", || { - usvg::Tree::from_xmltree(&xml_tree, &args.usvg).map_err(|e| e.to_string()) - })?; - - if args.query_all { - return query_all(&tree); - } - - // Render. - let img = render_svg(&args, &tree)?; - - match args.out_png.unwrap() { - OutputTo::Stdout => { - use std::io::Write; - let buf = img.encode_png().map_err(|e| e.to_string())?; - std::io::stdout().write_all(&buf).unwrap(); - } - OutputTo::File(ref file) => { - timed(args.perf, "Saving", || { - img.save_png(file).map_err(|e| e.to_string()) - })?; - } - }; - - Ok(()) -} - -const HELP: &str = "\ -resvg is an SVG rendering application. - -USAGE: - resvg [OPTIONS] # from file to file - resvg [OPTIONS] -c # from file to stdout - resvg [OPTIONS] - # from stdin to file - resvg [OPTIONS] - -c # from stdin to stdout - - resvg in.svg out.png - resvg -z 4 in.svg out.png - resvg --query-all in.svg - -OPTIONS: - --help Prints this help - -V, --version Prints version - -c Prints the output PNG to the stdout - - -w, --width LENGTH Sets the width in pixels - -h, --height LENGTH Sets the height in pixels - -z, --zoom FACTOR Zooms the image by a factor - --dpi DPI Sets the resolution - [default: 96] [possible values: 10..4000 (inclusive)] - --background COLOR Sets the background color - Examples: red, #fff, #fff000 - - --languages LANG Sets a comma-separated list of languages that - will be used during the 'systemLanguage' - attribute resolving - Examples: 'en-US', 'en-US, ru-RU', 'en, ru' - [default: en] - --shape-rendering HINT Selects the default shape rendering method - [default: geometricPrecision] - [possible values: optimizeSpeed, crispEdges, - geometricPrecision] - --text-rendering HINT Selects the default text rendering method - [default: optimizeLegibility] - [possible values: optimizeSpeed, optimizeLegibility, - geometricPrecision] - --image-rendering HINT Selects the default image rendering method - [default: optimizeQuality] - [possible values: optimizeQuality, optimizeSpeed] - --resources-dir DIR Sets a directory that will be used during - relative paths resolving. - Expected to be the same as the directory that - contains the SVG file, but can be set to any. - [default: input file directory] - - --font-family FAMILY Sets the default font family that will be - used when no 'font-family' is present - [default: Times New Roman] - --font-size SIZE Sets the default font size that will be - used when no 'font-size' is present - [default: 12] [possible values: 1..192 (inclusive)] - --serif-family FAMILY Sets the 'serif' font family - [default: Times New Roman] - --sans-serif-family FAMILY Sets the 'sans-serif' font family - [default: Arial] - --cursive-family FAMILY Sets the 'cursive' font family - [default: Comic Sans MS] - --fantasy-family FAMILY Sets the 'fantasy' font family - [default: Impact] - --monospace-family FAMILY Sets the 'monospace' font family - [default: Courier New] - --use-font-file PATH Load a specified font file into the fonts database. - Will be used during text to path conversion. - This option can be set multiple times - --use-fonts-dir PATH Loads all fonts from the specified directory - into the fonts database. - Will be used during text to path conversion. - This option can be set multiple times - --skip-system-fonts Disables system fonts loading. - You should add some fonts manually using - --use-font-file and/or --use-fonts-dir - Otherwise, text elements will not be processes - --list-fonts Lists successfully loaded font faces. - Useful for debugging - - - --query-all Queries all valid SVG ids with bounding boxes - --export-id ID Renders an object only with a specified ID - --export-area-page Use an image size instead of an object size during ID exporting - - --export-area-drawing Use drawing's tight bounding box instead of image size. - Used during normal rendering and not during --export-id - - --perf Prints performance stats - --quiet Disables warnings - -ARGS: - Input file - Output file -"; - -#[derive(Debug)] -struct CliArgs { - width: Option, - height: Option, - zoom: Option, - dpi: u32, - background: Option, - - languages: Vec, - shape_rendering: usvg::ShapeRendering, - text_rendering: usvg::TextRendering, - image_rendering: usvg::ImageRendering, - resources_dir: Option, - - font_family: Option, - font_size: u32, - serif_family: Option, - sans_serif_family: Option, - cursive_family: Option, - fantasy_family: Option, - monospace_family: Option, - font_files: Vec, - font_dirs: Vec, - skip_system_fonts: bool, - list_fonts: bool, - - query_all: bool, - export_id: Option, - export_area_page: bool, - - export_area_drawing: bool, - - perf: bool, - quiet: bool, - - input: Option, - output: Option, -} - -fn collect_args() -> Result { - let mut input = pico_args::Arguments::from_env(); - - if input.contains("--help") { - print!("{}", HELP); - std::process::exit(0); - } - - if input.contains(["-V", "--version"]) { - println!("{}", env!("CARGO_PKG_VERSION")); - std::process::exit(0); - } - - Ok(CliArgs { - width: input.opt_value_from_fn(["-w", "--width"], parse_length)?, - height: input.opt_value_from_fn(["-h", "--height"], parse_length)?, - zoom: input.opt_value_from_fn(["-z", "--zoom"], parse_zoom)?, - dpi: input.opt_value_from_fn("--dpi", parse_dpi)?.unwrap_or(96), - background: input.opt_value_from_str("--background")?, - - languages: input - .opt_value_from_fn("--languages", parse_languages)? - .unwrap_or_else(|| vec!["en".to_string()]), // TODO: use system language - shape_rendering: input - .opt_value_from_str("--shape-rendering")? - .unwrap_or_default(), - text_rendering: input - .opt_value_from_str("--text-rendering")? - .unwrap_or_default(), - image_rendering: input - .opt_value_from_str("--image-rendering")? - .unwrap_or_default(), - resources_dir: input - .opt_value_from_str("--resources-dir") - .unwrap_or_default(), - - font_family: input.opt_value_from_str("--font-family")?, - font_size: input - .opt_value_from_fn("--font-size", parse_font_size)? - .unwrap_or(12), - serif_family: input.opt_value_from_str("--serif-family")?, - sans_serif_family: input.opt_value_from_str("--sans-serif-family")?, - cursive_family: input.opt_value_from_str("--cursive-family")?, - fantasy_family: input.opt_value_from_str("--fantasy-family")?, - monospace_family: input.opt_value_from_str("--monospace-family")?, - font_files: input.values_from_str("--use-font-file")?, - font_dirs: input.values_from_str("--use-fonts-dir")?, - skip_system_fonts: input.contains("--skip-system-fonts"), - list_fonts: input.contains("--list-fonts"), - - query_all: input.contains("--query-all"), - export_id: input.opt_value_from_str("--export-id")?, - export_area_page: input.contains("--export-area-page"), - - export_area_drawing: input.contains("--export-area-drawing"), - - perf: input.contains("--perf"), - quiet: input.contains("--quiet"), - - input: input.opt_free_from_str()?, - output: input.opt_free_from_str()?, - }) -} - -fn parse_dpi(s: &str) -> Result { - let n: u32 = s.parse().map_err(|_| "invalid number")?; - - if (10..=4000).contains(&n) { - Ok(n) - } else { - Err("DPI out of bounds".to_string()) - } -} - -fn parse_length(s: &str) -> Result { - let n: u32 = s.parse().map_err(|_| "invalid length")?; - - if n > 0 { - Ok(n) - } else { - Err("LENGTH cannot be zero".to_string()) - } -} - -fn parse_zoom(s: &str) -> Result { - let n: f32 = s.parse().map_err(|_| "invalid zoom factor")?; - - if n > 0.0 { - Ok(n) - } else { - Err("ZOOM should be positive".to_string()) - } -} - -fn parse_font_size(s: &str) -> Result { - let n: u32 = s.parse().map_err(|_| "invalid number")?; - - if n > 0 && n <= 192 { - Ok(n) - } else { - Err("font size out of bounds".to_string()) - } -} - -fn parse_languages(s: &str) -> Result, String> { - let mut langs = Vec::new(); - for lang in s.split(',') { - langs.push(lang.trim().to_string()); - } - - if langs.is_empty() { - return Err("languages list cannot be empty".to_string()); - } - - Ok(langs) -} - -#[derive(Clone, PartialEq, Debug)] -enum InputFrom { - Stdin, - File(path::PathBuf), -} - -#[derive(Clone, PartialEq, Debug)] -enum OutputTo { - Stdout, - File(path::PathBuf), -} - -#[derive(Clone, Copy, PartialEq, Debug)] -enum FitTo { - /// Keep original size. - Original, - /// Scale to width. - Width(u32), - /// Scale to height. - Height(u32), - /// Scale to size. - Size(u32, u32), - /// Zoom by factor. - Zoom(f32), -} - -impl FitTo { - fn fit_to_size(&self, size: tiny_skia::IntSize) -> Option { - match *self { - FitTo::Original => Some(size), - FitTo::Width(w) => size.scale_to_width(w), - FitTo::Height(h) => size.scale_to_height(h), - FitTo::Size(w, h) => tiny_skia::IntSize::from_wh(w, h).map(|s| size.scale_to(s)), - FitTo::Zoom(z) => size.scale_by(z), - } - } - - fn fit_to_transform(&self, size: tiny_skia::IntSize) -> tiny_skia::Transform { - let size1 = size.to_size(); - let size2 = match self.fit_to_size(size) { - Some(v) => v.to_size(), - None => return tiny_skia::Transform::default(), - }; - tiny_skia::Transform::from_scale( - size2.width() / size1.width(), - size2.height() / size1.height(), - ) - } -} - -fn list_fonts(args: &CliArgs) { - let mut fontdb = fontdb::Database::new(); - load_fonts(&args, &mut fontdb); - - use fontdb::Family; - println!("serif: {}", fontdb.family_name(&Family::Serif)); - println!("sans-serif: {}", fontdb.family_name(&Family::SansSerif)); - println!("cursive: {}", fontdb.family_name(&Family::Cursive)); - println!("fantasy: {}", fontdb.family_name(&Family::Fantasy)); - println!("monospace: {}", fontdb.family_name(&Family::Monospace)); - - for face in fontdb.faces() { - if let fontdb::Source::File(ref path) = &face.source { - let families: Vec<_> = face - .families - .iter() - .map(|f| format!("{} ({}, {})", f.0, f.1.primary_language(), f.1.region())) - .collect(); - - println!( - "{}: '{}', {}, {:?}, {:?}, {:?}", - path.display(), - families.join("', '"), - face.index, - face.style, - face.weight.0, - face.stretch - ); - } - } -} - -struct Args { - in_svg: InputFrom, - out_png: Option, - query_all: bool, - export_id: Option, - export_area_page: bool, - export_area_drawing: bool, - perf: bool, - quiet: bool, - usvg: usvg::Options<'static>, - fit_to: FitTo, - background: Option, - raw_args: CliArgs, // TODO: find a better way -} - -fn parse_args() -> Result { - let args = collect_args().map_err(|e| e.to_string())?; - - if args.list_fonts { - list_fonts(&args); - std::process::exit(0); - } - - let (in_svg, out_png) = { - let in_svg = match args.input { - Some(ref v) => v, - None => return Err("input file is missing".to_string()), - }; - - let svg_from = if in_svg == "-" { - InputFrom::Stdin - } else if in_svg == "-c" { - return Err("-c should be set after input".to_string()); - } else { - InputFrom::File(in_svg.into()) - }; - - let out_png = if let Some(ref out_png) = args.output { - if out_png == "-c" { - Some(OutputTo::Stdout) - } else { - Some(OutputTo::File(out_png.into())) - } - } else { - None - }; - - (svg_from, out_png) - }; - - if !args.query_all && out_png.is_none() { - return Err(" must be set".to_string()); - } - - if in_svg == InputFrom::Stdin && args.resources_dir.is_none() { - eprintln!("Warning: Make sure to set --resources-dir when reading SVG from stdin."); - } - - if args.export_area_page && args.export_id.is_none() { - eprintln!("Warning: --export-area-page has no effect without --export-id."); - } - - if args.export_area_drawing && args.export_id.is_some() { - eprintln!("Warning: --export-area-drawing has no effect when --export-id is set."); - } - - let export_id = args.export_id.as_ref().map(|v| v.to_string()); - - let mut fit_to = FitTo::Original; - let mut default_size = usvg::Size::from_wh(100.0, 100.0).unwrap(); - if let (Some(w), Some(h)) = (args.width, args.height) { - default_size = usvg::Size::from_wh(w as f32, h as f32).unwrap(); - fit_to = FitTo::Size(w, h); - } else if let Some(w) = args.width { - default_size = usvg::Size::from_wh(w as f32, 100.0).unwrap(); - fit_to = FitTo::Width(w); - } else if let Some(h) = args.height { - default_size = usvg::Size::from_wh(100.0, h as f32).unwrap(); - fit_to = FitTo::Height(h); - } else if let Some(z) = args.zoom { - fit_to = FitTo::Zoom(z); - } - - let resources_dir = match args.resources_dir { - Some(ref v) => Some(v.clone()), - None => { - if let InputFrom::File(ref input) = in_svg { - // Get input file absolute directory. - std::fs::canonicalize(input) - .ok() - .and_then(|p| p.parent().map(|p| p.to_path_buf())) - } else { - None - } - } - }; - - let usvg = usvg::Options { - resources_dir, - dpi: args.dpi as f32, - font_family: args - .font_family - .clone() - .unwrap_or_else(|| "Times New Roman".to_string()), - font_size: args.font_size as f32, - languages: args.languages.clone(), - shape_rendering: args.shape_rendering, - text_rendering: args.text_rendering, - image_rendering: args.image_rendering, - default_size, - image_href_resolver: usvg::ImageHrefResolver::default(), - font_resolver: usvg::FontResolver::default(), - fontdb: Arc::new(fontdb::Database::new()), - }; - - Ok(Args { - in_svg, - out_png, - query_all: args.query_all, - export_id, - export_area_page: args.export_area_page, - export_area_drawing: args.export_area_drawing, - perf: args.perf, - quiet: args.quiet, - usvg, - fit_to, - background: args.background, - raw_args: args, - }) -} - -fn load_fonts(args: &CliArgs, fontdb: &mut fontdb::Database) { - if !args.skip_system_fonts { - fontdb.load_system_fonts(); - } - - for path in &args.font_files { - if let Err(e) = fontdb.load_font_file(path) { - log::warn!("Failed to load '{}' cause {}.", path.display(), e); - } - } - - for path in &args.font_dirs { - fontdb.load_fonts_dir(path); - } - - fontdb.set_serif_family(args.serif_family.as_deref().unwrap_or("Times New Roman")); - fontdb.set_sans_serif_family(args.sans_serif_family.as_deref().unwrap_or("Arial")); - fontdb.set_cursive_family(args.cursive_family.as_deref().unwrap_or("Comic Sans MS")); - fontdb.set_fantasy_family(args.fantasy_family.as_deref().unwrap_or("Impact")); - fontdb.set_monospace_family(args.monospace_family.as_deref().unwrap_or("Courier New")); -} - -fn query_all(tree: &usvg::Tree) -> Result<(), String> { - let count = query_all_impl(tree.root()); - - if count == 0 { - return Err("the file has no valid ID's".to_string()); - } - - Ok(()) -} - -fn query_all_impl(parent: &usvg::Group) -> usize { - let mut count = 0; - for node in parent.children() { - if node.id().is_empty() { - if let usvg::Node::Group(ref group) = node { - count += query_all_impl(group); - } - continue; - } - - count += 1; - - fn round_len(v: f32) -> f32 { - (v * 1000.0).round() / 1000.0 - } - - let bbox = node - .abs_layer_bounding_box() - .map(|r| r.to_rect()) - .unwrap_or(node.abs_bounding_box()); - - println!( - "{},{},{},{},{}", - node.id(), - round_len(bbox.x()), - round_len(bbox.y()), - round_len(bbox.width()), - round_len(bbox.height()) - ); - - if let usvg::Node::Group(ref group) = node { - count += query_all_impl(group); - } - } - - count -} - -fn render_svg(args: &Args, tree: &usvg::Tree) -> Result { - let now = std::time::Instant::now(); - - let img = if let Some(ref id) = args.export_id { - let node = match tree.node_by_id(id) { - Some(node) => node, - None => return Err(format!("SVG doesn't have '{}' ID", id)), - }; - - let bbox = node - .abs_layer_bounding_box() - .ok_or_else(|| "node has zero size".to_string())?; - - let size = args - .fit_to - .fit_to_size(bbox.size().to_int_size()) - .ok_or_else(|| "target size is zero".to_string())?; - - // Unwrap is safe, because `size` is already valid. - let mut pixmap = tiny_skia::Pixmap::new(size.width(), size.height()).unwrap(); - - if !args.export_area_page { - if let Some(background) = args.background { - pixmap.fill(svg_to_skia_color(background)); - } - } - - let ts = args.fit_to.fit_to_transform(tree.size().to_int_size()); - - resvg::render_node(node, ts, &mut pixmap.as_mut()); - - if args.export_area_page { - // TODO: add offset support to render_node() so we would not need an additional pixmap - - let size = args - .fit_to - .fit_to_size(tree.size().to_int_size()) - .ok_or_else(|| "target size is zero".to_string())?; - - // Unwrap is safe, because `size` is already valid. - let mut page_pixmap = tiny_skia::Pixmap::new(size.width(), size.height()).unwrap(); - - if let Some(background) = args.background { - page_pixmap.fill(svg_to_skia_color(background)); - } - - page_pixmap.draw_pixmap( - bbox.x() as i32, - bbox.y() as i32, - pixmap.as_ref(), - &tiny_skia::PixmapPaint::default(), - tiny_skia::Transform::default(), - None, - ); - page_pixmap - } else { - pixmap - } - } else { - let size = args - .fit_to - .fit_to_size(tree.size().to_int_size()) - .ok_or_else(|| "target size is zero".to_string())?; - - // Unwrap is safe, because `size` is already valid. - let mut pixmap = tiny_skia::Pixmap::new(size.width(), size.height()).unwrap(); - - if let Some(background) = args.background { - pixmap.fill(svg_to_skia_color(background)); - } - - let ts = args.fit_to.fit_to_transform(tree.size().to_int_size()); - - resvg::render(tree, ts, &mut pixmap.as_mut()); - - if args.export_area_drawing { - trim_pixmap(tree, ts, &pixmap).unwrap_or(pixmap) - } else { - pixmap - } - }; - - if args.perf { - let elapsed = now.elapsed().as_micros() as f64 / 1000.0; - println!("Rendering: {:.2}ms", elapsed); - } - - Ok(img) -} - -fn trim_pixmap( - tree: &usvg::Tree, - transform: tiny_skia::Transform, - pixmap: &tiny_skia::Pixmap, -) -> Option { - let content_area = tree.root().layer_bounding_box(); - - let limit = tiny_skia::IntRect::from_xywh(0, 0, pixmap.width(), pixmap.height()).unwrap(); - - let content_area = content_area.transform(transform)?.to_int_rect(); - let content_area = fit_to_rect(content_area, limit); - let content_area = tiny_skia::IntRect::from_xywh( - content_area.x(), - content_area.y(), - content_area.width(), - content_area.height(), - )?; - - pixmap.clone_rect(content_area) -} - -/// Fits the current rect into the specified bounds. -fn fit_to_rect(r: tiny_skia::IntRect, bounds: tiny_skia::IntRect) -> tiny_skia::IntRect { - let mut left = r.left(); - if left < bounds.left() { - left = bounds.left(); - } - - let mut top = r.top(); - if top < bounds.top() { - top = bounds.top(); - } - - let mut right = r.right(); - if right > bounds.right() { - right = bounds.right(); - } - - let mut bottom = r.bottom(); - if bottom > bounds.bottom() { - bottom = bounds.bottom(); - } - - tiny_skia::IntRect::from_ltrb(left, top, right, bottom).unwrap() -} - -fn svg_to_skia_color(color: svgtypes::Color) -> tiny_skia::Color { - tiny_skia::Color::from_rgba8(color.red, color.green, color.blue, color.alpha) -} - -/// A simple stderr logger. -static LOGGER: SimpleLogger = SimpleLogger; -struct SimpleLogger; -impl log::Log for SimpleLogger { - fn enabled(&self, metadata: &log::Metadata) -> bool { - metadata.level() <= log::LevelFilter::Warn - } - - fn log(&self, record: &log::Record) { - if self.enabled(record.metadata()) { - let target = if !record.target().is_empty() { - record.target() - } else { - record.module_path().unwrap_or_default() - }; - - let line = record.line().unwrap_or(0); - let args = record.args(); - - match record.level() { - log::Level::Error => eprintln!("Error (in {}:{}): {}", target, line, args), - log::Level::Warn => eprintln!("Warning (in {}:{}): {}", target, line, args), - log::Level::Info => eprintln!("Info (in {}:{}): {}", target, line, args), - log::Level::Debug => eprintln!("Debug (in {}:{}): {}", target, line, args), - log::Level::Trace => eprintln!("Trace (in {}:{}): {}", target, line, args), - } - } - } - - fn flush(&self) {} -} diff --git a/resvg-0.43.0/crates/resvg/src/mask.rs b/resvg-0.43.0/crates/resvg/src/mask.rs deleted file mode 100644 index 4cd9dd3..0000000 --- a/resvg-0.43.0/crates/resvg/src/mask.rs +++ /dev/null @@ -1,47 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::render::Context; - -pub fn apply( - mask: &usvg::Mask, - ctx: &Context, - transform: tiny_skia::Transform, - pixmap: &mut tiny_skia::Pixmap, -) { - if mask.root().children().is_empty() { - pixmap.fill(tiny_skia::Color::TRANSPARENT); - return; - } - - let mut mask_pixmap = tiny_skia::Pixmap::new(pixmap.width(), pixmap.height()).unwrap(); - - { - // TODO: only when needed - // Mask has to be clipped by mask.region - let mut alpha_mask = tiny_skia::Mask::new(pixmap.width(), pixmap.height()).unwrap(); - alpha_mask.fill_path( - &tiny_skia::PathBuilder::from_rect(mask.rect().to_rect()), - tiny_skia::FillRule::Winding, - true, - transform, - ); - - crate::render::render_nodes(mask.root(), ctx, transform, &mut mask_pixmap.as_mut()); - - mask_pixmap.apply_mask(&alpha_mask); - } - - if let Some(mask) = mask.mask() { - self::apply(mask, ctx, transform, pixmap); - } - - let mask_type = match mask.kind() { - usvg::MaskType::Luminance => tiny_skia::MaskType::Luminance, - usvg::MaskType::Alpha => tiny_skia::MaskType::Alpha, - }; - - let mask = tiny_skia::Mask::from_pixmap(mask_pixmap.as_ref(), mask_type); - pixmap.apply_mask(&mask); -} diff --git a/resvg-0.43.0/crates/resvg/src/path.rs b/resvg-0.43.0/crates/resvg/src/path.rs deleted file mode 100644 index 333e029..0000000 --- a/resvg-0.43.0/crates/resvg/src/path.rs +++ /dev/null @@ -1,205 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::render::Context; - -pub fn render( - path: &usvg::Path, - blend_mode: tiny_skia::BlendMode, - ctx: &Context, - transform: tiny_skia::Transform, - pixmap: &mut tiny_skia::PixmapMut, -) { - if !path.is_visible() { - return; - } - - if path.paint_order() == usvg::PaintOrder::FillAndStroke { - fill_path(path, blend_mode, ctx, transform, pixmap); - stroke_path(path, blend_mode, ctx, transform, pixmap); - } else { - stroke_path(path, blend_mode, ctx, transform, pixmap); - fill_path(path, blend_mode, ctx, transform, pixmap); - } -} - -pub fn fill_path( - path: &usvg::Path, - blend_mode: tiny_skia::BlendMode, - ctx: &Context, - transform: tiny_skia::Transform, - pixmap: &mut tiny_skia::PixmapMut, -) -> Option<()> { - let fill = path.fill()?; - - // Horizontal and vertical lines cannot be filled. Skip. - if path.data().bounds().width() == 0.0 || path.data().bounds().height() == 0.0 { - return None; - } - - let rule = match fill.rule() { - usvg::FillRule::NonZero => tiny_skia::FillRule::Winding, - usvg::FillRule::EvenOdd => tiny_skia::FillRule::EvenOdd, - }; - - let pattern_pixmap; - let mut paint = tiny_skia::Paint::default(); - match fill.paint() { - usvg::Paint::Color(c) => { - paint.set_color_rgba8(c.red, c.green, c.blue, fill.opacity().to_u8()); - } - usvg::Paint::LinearGradient(ref lg) => { - paint.shader = convert_linear_gradient(lg, fill.opacity())?; - } - usvg::Paint::RadialGradient(ref rg) => { - paint.shader = convert_radial_gradient(rg, fill.opacity())?; - } - usvg::Paint::Pattern(ref pattern) => { - let (patt_pix, patt_ts) = render_pattern_pixmap(pattern, ctx, transform)?; - - pattern_pixmap = patt_pix; - paint.shader = tiny_skia::Pattern::new( - pattern_pixmap.as_ref(), - tiny_skia::SpreadMode::Repeat, - tiny_skia::FilterQuality::Bicubic, - fill.opacity().get(), - patt_ts, - ) - } - } - paint.anti_alias = path.rendering_mode().use_shape_antialiasing(); - paint.blend_mode = blend_mode; - - pixmap.fill_path(path.data(), &paint, rule, transform, None); - Some(()) -} - -fn stroke_path( - path: &usvg::Path, - blend_mode: tiny_skia::BlendMode, - ctx: &Context, - transform: tiny_skia::Transform, - pixmap: &mut tiny_skia::PixmapMut, -) -> Option<()> { - let stroke = path.stroke()?; - let pattern_pixmap; - let mut paint = tiny_skia::Paint::default(); - match stroke.paint() { - usvg::Paint::Color(c) => { - paint.set_color_rgba8(c.red, c.green, c.blue, stroke.opacity().to_u8()); - } - usvg::Paint::LinearGradient(ref lg) => { - paint.shader = convert_linear_gradient(lg, stroke.opacity())?; - } - usvg::Paint::RadialGradient(ref rg) => { - paint.shader = convert_radial_gradient(rg, stroke.opacity())?; - } - usvg::Paint::Pattern(ref pattern) => { - let (patt_pix, patt_ts) = render_pattern_pixmap(pattern, ctx, transform)?; - - pattern_pixmap = patt_pix; - paint.shader = tiny_skia::Pattern::new( - pattern_pixmap.as_ref(), - tiny_skia::SpreadMode::Repeat, - tiny_skia::FilterQuality::Bicubic, - stroke.opacity().get(), - patt_ts, - ) - } - } - paint.anti_alias = path.rendering_mode().use_shape_antialiasing(); - paint.blend_mode = blend_mode; - - pixmap.stroke_path(path.data(), &paint, &stroke.to_tiny_skia(), transform, None); - - Some(()) -} - -fn convert_linear_gradient( - gradient: &usvg::LinearGradient, - opacity: usvg::Opacity, -) -> Option { - let (mode, points) = convert_base_gradient(gradient, opacity)?; - - let shader = tiny_skia::LinearGradient::new( - (gradient.x1(), gradient.y1()).into(), - (gradient.x2(), gradient.y2()).into(), - points, - mode, - gradient.transform(), - )?; - - Some(shader) -} - -fn convert_radial_gradient( - gradient: &usvg::RadialGradient, - opacity: usvg::Opacity, -) -> Option { - let (mode, points) = convert_base_gradient(gradient, opacity)?; - - let shader = tiny_skia::RadialGradient::new( - (gradient.fx(), gradient.fy()).into(), - (gradient.cx(), gradient.cy()).into(), - gradient.r().get(), - points, - mode, - gradient.transform(), - )?; - - Some(shader) -} - -fn convert_base_gradient( - gradient: &usvg::BaseGradient, - opacity: usvg::Opacity, -) -> Option<(tiny_skia::SpreadMode, Vec)> { - let mode = match gradient.spread_method() { - usvg::SpreadMethod::Pad => tiny_skia::SpreadMode::Pad, - usvg::SpreadMethod::Reflect => tiny_skia::SpreadMode::Reflect, - usvg::SpreadMethod::Repeat => tiny_skia::SpreadMode::Repeat, - }; - - let mut points = Vec::with_capacity(gradient.stops().len()); - for stop in gradient.stops() { - let alpha = stop.opacity() * opacity; - let color = tiny_skia::Color::from_rgba8( - stop.color().red, - stop.color().green, - stop.color().blue, - alpha.to_u8(), - ); - points.push(tiny_skia::GradientStop::new(stop.offset().get(), color)) - } - - Some((mode, points)) -} - -fn render_pattern_pixmap( - pattern: &usvg::Pattern, - ctx: &Context, - transform: tiny_skia::Transform, -) -> Option<(tiny_skia::Pixmap, tiny_skia::Transform)> { - let (sx, sy) = { - let ts2 = transform.pre_concat(pattern.transform()); - ts2.get_scale() - }; - - let rect = pattern.rect(); - let img_size = tiny_skia::IntSize::from_wh( - (rect.width() * sx).round() as u32, - (rect.height() * sy).round() as u32, - )?; - let mut pixmap = tiny_skia::Pixmap::new(img_size.width(), img_size.height())?; - - let transform = tiny_skia::Transform::from_scale(sx, sy); - crate::render::render_nodes(pattern.root(), ctx, transform, &mut pixmap.as_mut()); - - let mut ts = tiny_skia::Transform::default(); - ts = ts.pre_concat(pattern.transform()); - ts = ts.pre_translate(rect.x(), rect.y()); - ts = ts.pre_scale(1.0 / sx, 1.0 / sy); - - Some((pixmap, ts)) -} diff --git a/resvg-0.43.0/crates/resvg/src/render.rs b/resvg-0.43.0/crates/resvg/src/render.rs deleted file mode 100644 index 1e2908d..0000000 --- a/resvg-0.43.0/crates/resvg/src/render.rs +++ /dev/null @@ -1,160 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use crate::OptionLog; - -pub struct Context { - pub max_bbox: tiny_skia::IntRect, -} - -pub fn render_nodes( - parent: &usvg::Group, - ctx: &Context, - transform: tiny_skia::Transform, - pixmap: &mut tiny_skia::PixmapMut, -) { - for node in parent.children() { - render_node(node, ctx, transform, pixmap); - } -} - -pub fn render_node( - node: &usvg::Node, - ctx: &Context, - transform: tiny_skia::Transform, - pixmap: &mut tiny_skia::PixmapMut, -) { - match node { - usvg::Node::Group(ref group) => { - render_group(group, ctx, transform, pixmap); - } - usvg::Node::Path(ref path) => { - crate::path::render( - path, - tiny_skia::BlendMode::SourceOver, - ctx, - transform, - pixmap, - ); - } - usvg::Node::Image(ref image) => { - crate::image::render(image, transform, pixmap); - } - usvg::Node::Text(ref text) => { - render_group(text.flattened(), ctx, transform, pixmap); - } - } -} - -fn render_group( - group: &usvg::Group, - ctx: &Context, - transform: tiny_skia::Transform, - pixmap: &mut tiny_skia::PixmapMut, -) -> Option<()> { - let transform = transform.pre_concat(group.transform()); - - if !group.should_isolate() { - render_nodes(group, ctx, transform, pixmap); - return Some(()); - } - - let bbox = group.layer_bounding_box().transform(transform)?; - - let mut ibbox = if group.filters().is_empty() { - // Convert group bbox into an integer one, expanding each side outwards by 2px - // to make sure that anti-aliased pixels would not be clipped. - tiny_skia::IntRect::from_xywh( - bbox.x().floor() as i32 - 2, - bbox.y().floor() as i32 - 2, - bbox.width().ceil() as u32 + 4, - bbox.height().ceil() as u32 + 4, - )? - } else { - // The bounding box for groups with filters is special and should not be expanded by 2px, - // because it's already acting as a clipping region. - let bbox = bbox.to_int_rect(); - // Make sure our filter region is not bigger than 4x the canvas size. - // This is required mainly to prevent huge filter regions that would tank the performance. - // It should not affect the final result in any way. - crate::geom::fit_to_rect(bbox, ctx.max_bbox)? - }; - - // Make sure our layer is not bigger than 4x the canvas size. - // This is required to prevent huge layers. - if group.filters().is_empty() { - ibbox = crate::geom::fit_to_rect(ibbox, ctx.max_bbox)?; - } - - let shift_ts = { - // Original shift. - let mut dx = bbox.x(); - let mut dy = bbox.y(); - - // Account for subpixel positioned layers. - dx -= bbox.x() - ibbox.x() as f32; - dy -= bbox.y() - ibbox.y() as f32; - - tiny_skia::Transform::from_translate(-dx, -dy) - }; - - let transform = shift_ts.pre_concat(transform); - - let mut sub_pixmap = tiny_skia::Pixmap::new(ibbox.width(), ibbox.height()) - .log_none(|| log::warn!("Failed to allocate a group layer for: {:?}.", ibbox))?; - - render_nodes(group, ctx, transform, &mut sub_pixmap.as_mut()); - - if !group.filters().is_empty() { - for filter in group.filters() { - crate::filter::apply(filter, transform, &mut sub_pixmap); - } - } - - if let Some(clip_path) = group.clip_path() { - crate::clip::apply(clip_path, transform, &mut sub_pixmap); - } - - if let Some(mask) = group.mask() { - crate::mask::apply(mask, ctx, transform, &mut sub_pixmap); - } - - let paint = tiny_skia::PixmapPaint { - opacity: group.opacity().get(), - blend_mode: convert_blend_mode(group.blend_mode()), - quality: tiny_skia::FilterQuality::Nearest, - }; - - pixmap.draw_pixmap( - ibbox.x(), - ibbox.y(), - sub_pixmap.as_ref(), - &paint, - tiny_skia::Transform::identity(), - None, - ); - - Some(()) -} - -pub fn convert_blend_mode(mode: usvg::BlendMode) -> tiny_skia::BlendMode { - match mode { - usvg::BlendMode::Normal => tiny_skia::BlendMode::SourceOver, - usvg::BlendMode::Multiply => tiny_skia::BlendMode::Multiply, - usvg::BlendMode::Screen => tiny_skia::BlendMode::Screen, - usvg::BlendMode::Overlay => tiny_skia::BlendMode::Overlay, - usvg::BlendMode::Darken => tiny_skia::BlendMode::Darken, - usvg::BlendMode::Lighten => tiny_skia::BlendMode::Lighten, - usvg::BlendMode::ColorDodge => tiny_skia::BlendMode::ColorDodge, - usvg::BlendMode::ColorBurn => tiny_skia::BlendMode::ColorBurn, - usvg::BlendMode::HardLight => tiny_skia::BlendMode::HardLight, - usvg::BlendMode::SoftLight => tiny_skia::BlendMode::SoftLight, - usvg::BlendMode::Difference => tiny_skia::BlendMode::Difference, - usvg::BlendMode::Exclusion => tiny_skia::BlendMode::Exclusion, - usvg::BlendMode::Hue => tiny_skia::BlendMode::Hue, - usvg::BlendMode::Saturation => tiny_skia::BlendMode::Saturation, - usvg::BlendMode::Color => tiny_skia::BlendMode::Color, - usvg::BlendMode::Luminosity => tiny_skia::BlendMode::Luminosity, - } -} diff --git a/resvg-0.43.0/crates/usvg/Cargo.toml b/resvg-0.43.0/crates/usvg/Cargo.toml deleted file mode 100644 index 8e17129..0000000 --- a/resvg-0.43.0/crates/usvg/Cargo.toml +++ /dev/null @@ -1,56 +0,0 @@ -[package] -name = "usvg" -version = "0.43.0" -authors = ["Yevhenii Reizner "] -keywords = ["svg"] -license = "MPL-2.0" -edition = "2021" -description = "An SVG simplification library." -categories = ["multimedia::images"] -repository = "https://github.com/RazrFalcon/resvg" -documentation = "https://docs.rs/usvg/" -readme = "README.md" -exclude = ["tests"] -workspace = "../.." - -[[bin]] -name = "usvg" -required-features = ["text", "system-fonts", "memmap-fonts"] - -[dependencies] -base64 = "0.22" # for embedded images -log = "0.4" -pico-args = { version = "0.5", features = ["eq-separator"] } -strict-num = "0.1.1" -svgtypes = "0.15.1" -tiny-skia-path = "0.11.4" -xmlwriter = "0.1" - -# parser -data-url = "0.3" # for href parsing -flate2 = { version = "1.0", default-features = false, features = ["rust_backend"] } # SVGZ decoding -imagesize = "0.13" # raster images size detection -kurbo = "0.11" # Bezier curves utils -roxmltree = "0.20" -simplecss = "0.2" -siphasher = "1.0" # perfect hash implementation - -# text -fontdb = { version = "0.21.0", default-features = false, optional = true } -rustybuzz = { version = "0.18.0", optional = true } -unicode-bidi = { version = "0.3", optional = true } -unicode-script = { version = "0.5", optional = true } -unicode-vo = { version = "0.1", optional = true } - -[dev-dependencies] -once_cell = "1.5" - -[features] -default = ["text", "system-fonts", "memmap-fonts"] -# Enables text-to-path conversion support. -# Adds around 400KiB to your binary. -text = ["fontdb", "rustybuzz", "unicode-bidi", "unicode-script", "unicode-vo"] -# Enables system fonts loading. -system-fonts = ["fontdb/fs", "fontdb/fontconfig"] -# Enables font files memmaping for faster loading. -memmap-fonts = ["fontdb/memmap"] diff --git a/resvg-0.43.0/crates/usvg/LICENSE.txt b/resvg-0.43.0/crates/usvg/LICENSE.txt deleted file mode 100644 index 14e2f77..0000000 --- a/resvg-0.43.0/crates/usvg/LICENSE.txt +++ /dev/null @@ -1,373 +0,0 @@ -Mozilla Public License Version 2.0 -================================== - -1. Definitions --------------- - -1.1. "Contributor" - means each individual or legal entity that creates, contributes to - the creation of, or owns Covered Software. - -1.2. "Contributor Version" - means the combination of the Contributions of others (if any) used - by a Contributor and that particular Contributor's Contribution. - -1.3. "Contribution" - means Covered Software of a particular Contributor. - -1.4. "Covered Software" - means Source Code Form to which the initial Contributor has attached - the notice in Exhibit A, the Executable Form of such Source Code - Form, and Modifications of such Source Code Form, in each case - including portions thereof. - -1.5. "Incompatible With Secondary Licenses" - means - - (a) that the initial Contributor has attached the notice described - in Exhibit B to the Covered Software; or - - (b) that the Covered Software was made available under the terms of - version 1.1 or earlier of the License, but not also under the - terms of a Secondary License. - -1.6. "Executable Form" - means any form of the work other than Source Code Form. - -1.7. "Larger Work" - means a work that combines Covered Software with other material, in - a separate file or files, that is not Covered Software. - -1.8. "License" - means this document. - -1.9. "Licensable" - means having the right to grant, to the maximum extent possible, - whether at the time of the initial grant or subsequently, any and - all of the rights conveyed by this License. - -1.10. "Modifications" - means any of the following: - - (a) any file in Source Code Form that results from an addition to, - deletion from, or modification of the contents of Covered - Software; or - - (b) any new file in Source Code Form that contains any Covered - Software. - -1.11. "Patent Claims" of a Contributor - means any patent claim(s), including without limitation, method, - process, and apparatus claims, in any patent Licensable by such - Contributor that would be infringed, but for the grant of the - License, by the making, using, selling, offering for sale, having - made, import, or transfer of either its Contributions or its - Contributor Version. - -1.12. "Secondary License" - means either the GNU General Public License, Version 2.0, the GNU - Lesser General Public License, Version 2.1, the GNU Affero General - Public License, Version 3.0, or any later versions of those - licenses. - -1.13. "Source Code Form" - means the form of the work preferred for making modifications. - -1.14. "You" (or "Your") - means an individual or a legal entity exercising rights under this - License. For legal entities, "You" includes any entity that - controls, is controlled by, or is under common control with You. For - purposes of this definition, "control" means (a) the power, direct - or indirect, to cause the direction or management of such entity, - whether by contract or otherwise, or (b) ownership of more than - fifty percent (50%) of the outstanding shares or beneficial - ownership of such entity. - -2. License Grants and Conditions --------------------------------- - -2.1. Grants - -Each Contributor hereby grants You a world-wide, royalty-free, -non-exclusive license: - -(a) under intellectual property rights (other than patent or trademark) - Licensable by such Contributor to use, reproduce, make available, - modify, display, perform, distribute, and otherwise exploit its - Contributions, either on an unmodified basis, with Modifications, or - as part of a Larger Work; and - -(b) under Patent Claims of such Contributor to make, use, sell, offer - for sale, have made, import, and otherwise transfer either its - Contributions or its Contributor Version. - -2.2. Effective Date - -The licenses granted in Section 2.1 with respect to any Contribution -become effective for each Contribution on the date the Contributor first -distributes such Contribution. - -2.3. Limitations on Grant Scope - -The licenses granted in this Section 2 are the only rights granted under -this License. No additional rights or licenses will be implied from the -distribution or licensing of Covered Software under this License. -Notwithstanding Section 2.1(b) above, no patent license is granted by a -Contributor: - -(a) for any code that a Contributor has removed from Covered Software; - or - -(b) for infringements caused by: (i) Your and any other third party's - modifications of Covered Software, or (ii) the combination of its - Contributions with other software (except as part of its Contributor - Version); or - -(c) under Patent Claims infringed by Covered Software in the absence of - its Contributions. - -This License does not grant any rights in the trademarks, service marks, -or logos of any Contributor (except as may be necessary to comply with -the notice requirements in Section 3.4). - -2.4. Subsequent Licenses - -No Contributor makes additional grants as a result of Your choice to -distribute the Covered Software under a subsequent version of this -License (see Section 10.2) or under the terms of a Secondary License (if -permitted under the terms of Section 3.3). - -2.5. Representation - -Each Contributor represents that the Contributor believes its -Contributions are its original creation(s) or it has sufficient rights -to grant the rights to its Contributions conveyed by this License. - -2.6. Fair Use - -This License is not intended to limit any rights You have under -applicable copyright doctrines of fair use, fair dealing, or other -equivalents. - -2.7. Conditions - -Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted -in Section 2.1. - -3. Responsibilities -------------------- - -3.1. Distribution of Source Form - -All distribution of Covered Software in Source Code Form, including any -Modifications that You create or to which You contribute, must be under -the terms of this License. You must inform recipients that the Source -Code Form of the Covered Software is governed by the terms of this -License, and how they can obtain a copy of this License. You may not -attempt to alter or restrict the recipients' rights in the Source Code -Form. - -3.2. Distribution of Executable Form - -If You distribute Covered Software in Executable Form then: - -(a) such Covered Software must also be made available in Source Code - Form, as described in Section 3.1, and You must inform recipients of - the Executable Form how they can obtain a copy of such Source Code - Form by reasonable means in a timely manner, at a charge no more - than the cost of distribution to the recipient; and - -(b) You may distribute such Executable Form under the terms of this - License, or sublicense it under different terms, provided that the - license for the Executable Form does not attempt to limit or alter - the recipients' rights in the Source Code Form under this License. - -3.3. Distribution of a Larger Work - -You may create and distribute a Larger Work under terms of Your choice, -provided that You also comply with the requirements of this License for -the Covered Software. If the Larger Work is a combination of Covered -Software with a work governed by one or more Secondary Licenses, and the -Covered Software is not Incompatible With Secondary Licenses, this -License permits You to additionally distribute such Covered Software -under the terms of such Secondary License(s), so that the recipient of -the Larger Work may, at their option, further distribute the Covered -Software under the terms of either this License or such Secondary -License(s). - -3.4. Notices - -You may not remove or alter the substance of any license notices -(including copyright notices, patent notices, disclaimers of warranty, -or limitations of liability) contained within the Source Code Form of -the Covered Software, except that You may alter any license notices to -the extent required to remedy known factual inaccuracies. - -3.5. Application of Additional Terms - -You may choose to offer, and to charge a fee for, warranty, support, -indemnity or liability obligations to one or more recipients of Covered -Software. However, You may do so only on Your own behalf, and not on -behalf of any Contributor. You must make it absolutely clear that any -such warranty, support, indemnity, or liability obligation is offered by -You alone, and You hereby agree to indemnify every Contributor for any -liability incurred by such Contributor as a result of warranty, support, -indemnity or liability terms You offer. You may include additional -disclaimers of warranty and limitations of liability specific to any -jurisdiction. - -4. Inability to Comply Due to Statute or Regulation ---------------------------------------------------- - -If it is impossible for You to comply with any of the terms of this -License with respect to some or all of the Covered Software due to -statute, judicial order, or regulation then You must: (a) comply with -the terms of this License to the maximum extent possible; and (b) -describe the limitations and the code they affect. Such description must -be placed in a text file included with all distributions of the Covered -Software under this License. Except to the extent prohibited by statute -or regulation, such description must be sufficiently detailed for a -recipient of ordinary skill to be able to understand it. - -5. Termination --------------- - -5.1. The rights granted under this License will terminate automatically -if You fail to comply with any of its terms. However, if You become -compliant, then the rights granted under this License from a particular -Contributor are reinstated (a) provisionally, unless and until such -Contributor explicitly and finally terminates Your grants, and (b) on an -ongoing basis, if such Contributor fails to notify You of the -non-compliance by some reasonable means prior to 60 days after You have -come back into compliance. Moreover, Your grants from a particular -Contributor are reinstated on an ongoing basis if such Contributor -notifies You of the non-compliance by some reasonable means, this is the -first time You have received notice of non-compliance with this License -from such Contributor, and You become compliant prior to 30 days after -Your receipt of the notice. - -5.2. If You initiate litigation against any entity by asserting a patent -infringement claim (excluding declaratory judgment actions, -counter-claims, and cross-claims) alleging that a Contributor Version -directly or indirectly infringes any patent, then the rights granted to -You by any and all Contributors for the Covered Software under Section -2.1 of this License shall terminate. - -5.3. In the event of termination under Sections 5.1 or 5.2 above, all -end user license agreements (excluding distributors and resellers) which -have been validly granted by You or Your distributors under this License -prior to termination shall survive termination. - -************************************************************************ -* * -* 6. Disclaimer of Warranty * -* ------------------------- * -* * -* Covered Software is provided under this License on an "as is" * -* basis, without warranty of any kind, either expressed, implied, or * -* statutory, including, without limitation, warranties that the * -* Covered Software is free of defects, merchantable, fit for a * -* particular purpose or non-infringing. The entire risk as to the * -* quality and performance of the Covered Software is with You. * -* Should any Covered Software prove defective in any respect, You * -* (not any Contributor) assume the cost of any necessary servicing, * -* repair, or correction. This disclaimer of warranty constitutes an * -* essential part of this License. No use of any Covered Software is * -* authorized under this License except under this disclaimer. * -* * -************************************************************************ - -************************************************************************ -* * -* 7. Limitation of Liability * -* -------------------------- * -* * -* Under no circumstances and under no legal theory, whether tort * -* (including negligence), contract, or otherwise, shall any * -* Contributor, or anyone who distributes Covered Software as * -* permitted above, be liable to You for any direct, indirect, * -* special, incidental, or consequential damages of any character * -* including, without limitation, damages for lost profits, loss of * -* goodwill, work stoppage, computer failure or malfunction, or any * -* and all other commercial damages or losses, even if such party * -* shall have been informed of the possibility of such damages. This * -* limitation of liability shall not apply to liability for death or * -* personal injury resulting from such party's negligence to the * -* extent applicable law prohibits such limitation. Some * -* jurisdictions do not allow the exclusion or limitation of * -* incidental or consequential damages, so this exclusion and * -* limitation may not apply to You. * -* * -************************************************************************ - -8. Litigation -------------- - -Any litigation relating to this License may be brought only in the -courts of a jurisdiction where the defendant maintains its principal -place of business and such litigation shall be governed by laws of that -jurisdiction, without reference to its conflict-of-law provisions. -Nothing in this Section shall prevent a party's ability to bring -cross-claims or counter-claims. - -9. Miscellaneous ----------------- - -This License represents the complete agreement concerning the subject -matter hereof. If any provision of this License is held to be -unenforceable, such provision shall be reformed only to the extent -necessary to make it enforceable. Any law or regulation which provides -that the language of a contract shall be construed against the drafter -shall not be used to construe this License against a Contributor. - -10. Versions of the License ---------------------------- - -10.1. New Versions - -Mozilla Foundation is the license steward. Except as provided in Section -10.3, no one other than the license steward has the right to modify or -publish new versions of this License. Each version will be given a -distinguishing version number. - -10.2. Effect of New Versions - -You may distribute the Covered Software under the terms of the version -of the License under which You originally received the Covered Software, -or under the terms of any subsequent version published by the license -steward. - -10.3. Modified Versions - -If you create software not governed by this License, and you want to -create a new license for such software, you may create and use a -modified version of this License if you rename the license and remove -any references to the name of the license steward (except to note that -such modified license differs from this License). - -10.4. Distributing Source Code Form that is Incompatible With Secondary -Licenses - -If You choose to distribute Source Code Form that is Incompatible With -Secondary Licenses under the terms of this version of the License, the -notice described in Exhibit B of this License must be attached. - -Exhibit A - Source Code Form License Notice -------------------------------------------- - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. - -If it is not possible or desirable to put the notice in a particular -file, then You may include the notice in a location (such as a LICENSE -file in a relevant directory) where a recipient would be likely to look -for such a notice. - -You may add additional accurate notices of copyright ownership. - -Exhibit B - "Incompatible With Secondary Licenses" Notice ---------------------------------------------------------- - - This Source Code Form is "Incompatible With Secondary Licenses", as - defined by the Mozilla Public License, v. 2.0. diff --git a/resvg-0.43.0/crates/usvg/README.md b/resvg-0.43.0/crates/usvg/README.md deleted file mode 100644 index a1907ae..0000000 --- a/resvg-0.43.0/crates/usvg/README.md +++ /dev/null @@ -1,51 +0,0 @@ -# usvg -[![Crates.io](https://img.shields.io/crates/v/usvg.svg)](https://crates.io/crates/usvg) -[![Documentation](https://docs.rs/usvg/badge.svg)](https://docs.rs/usvg) -[![Rust 1.65+](https://img.shields.io/badge/rust-1.65+-orange.svg)](https://www.rust-lang.org) - -`usvg` (micro SVG) is an [SVG] parser that tries to solve most of SVG complexity. - -SVG is notoriously hard to parse. `usvg` presents a layer between an XML library and -a potential SVG rendering library. It will parse an input SVG into a strongly-typed tree structure -were all the elements, attributes, references and other SVG features are already resolved -and presented in the simplest way possible. -So a caller doesn't have to worry about most of the issues related to SVG parsing -and can focus just on the rendering part. - -## Features - -- All supported attributes are resolved. - No need to worry about inheritable, implicit and default attributes -- CSS will be applied -- Only simple paths - - Basic shapes (like `rect` and `circle`) will be converted into paths - - Paths contain only absolute *MoveTo*, *LineTo*, *QuadTo*, *CurveTo* and *ClosePath* segments. - ArcTo, implicit and relative segments will be converted -- `use` will be resolved and replaced with the reference content -- Nested `svg` will be resolved -- Invalid, malformed elements will be removed -- Relative length units (mm, em, etc.) will be converted into pixels/points -- External images will be loaded -- Internal, base64 images will be decoded -- All references (like `#elem` and `url(#elem)`) will be resolved -- `switch` will be resolved -- Text elements, which are probably the hardest part of SVG, will be completely resolved. - This includes all the attributes resolving, whitespaces preprocessing (`xml:space`), - text chunks and spans resolving -- Markers will be converted into regular elements. No need to place them manually -- All filters are supported. Including filter functions, like `filter="contrast(50%)"` -- Recursive elements will be detected and removed -- `objectBoundingBox` will be replaced with `userSpaceOnUse` - -## Limitations - -- Unsupported SVG features will be ignored -- CSS support is minimal -- Only [static](http://www.w3.org/TR/SVG11/feature#SVG-static) SVG features, - e.g. no `a`, `view`, `cursor`, `script`, no events and no animations - -## License - -*usvg* is licensed under the [MPLv2.0](https://www.mozilla.org/en-US/MPL/). - -[SVG]: https://en.wikipedia.org/wiki/Scalable_Vector_Graphics diff --git a/resvg-0.43.0/crates/usvg/codegen/Cargo.toml b/resvg-0.43.0/crates/usvg/codegen/Cargo.toml deleted file mode 100644 index 56fbb70..0000000 --- a/resvg-0.43.0/crates/usvg/codegen/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "codegen" -version = "0.1.0" -authors = ["Yevhenii Reizner "] -license = "MIT" -edition = "2021" - -[workspace] - -[[bin]] -name = "codegen" -path = "main.rs" - -[dependencies] -phf = "0.7.22" -phf_codegen = "0.7.22" -itertools = "0.7" diff --git a/resvg-0.43.0/crates/usvg/codegen/README.md b/resvg-0.43.0/crates/usvg/codegen/README.md deleted file mode 100644 index 4a71b27..0000000 --- a/resvg-0.43.0/crates/usvg/codegen/README.md +++ /dev/null @@ -1,8 +0,0 @@ -We don't use cargo build script, since this data will rarely be changed and -there is no point in regenerating it each time. - -To regenerate files run: - -``` -cargo run -``` diff --git a/resvg-0.43.0/crates/usvg/codegen/attributes.txt b/resvg-0.43.0/crates/usvg/codegen/attributes.txt deleted file mode 100644 index 97598a8..0000000 --- a/resvg-0.43.0/crates/usvg/codegen/attributes.txt +++ /dev/null @@ -1,208 +0,0 @@ -alignment-baseline -amplitude -azimuth -baseFrequency -baseline-shift -bias -class -clip -clip-path -clip-rule -clipPathUnits -color -color-interpolation -color-interpolation-filters -color-profile -color-rendering -cx -cy -d -diffuseConstant -direction -display -divisor -dominant-baseline -dx -dy -edgeMode -elevation -enable-background -exponent -fill -fill-opacity -fill-rule -filter -filterUnits -flood-color -flood-opacity -font -font-family -font-feature-settings -font-kerning -font-size -font-size-adjust -font-stretch -font-style -font-synthesis -font-variant -font-variant-caps -font-variant-east-asian -font-variant-ligatures -font-variant-numeric -font-variant-position -font-weight -fr -fx -fy -glyph-orientation-horizontal -glyph-orientation-vertical -gradientTransform -gradientUnits -height -href -id -image-rendering -in -in2 -inline-size -intercept -isolation -k1 -k2 -k3 -k4 -kernelMatrix -kernelUnitLength -kerning -lengthAdjust -letter-spacing -lighting-color -limitingConeAngle -line-height -marker-end -marker-mid -marker-start -markerHeight -markerUnits -markerWidth -mask -mask-border -mask-border-mode -mask-border-outset -mask-border-repeat -mask-border-slice -mask-border-source -mask-border-width -mask-clip -mask-composite -mask-image -mask-mode -mask-origin -mask-position -mask-size -mask-type -maskContentUnits -maskUnits -mix-blend-mode -mode -numOctaves -offset -opacity -operator -order -orient -overflow -paint-order -path -pathLength -patternContentUnits -patternTransform -patternUnits -points -pointsAtX -pointsAtY -pointsAtZ -preserveAlpha -preserveAspectRatio -primitiveUnits -r -radius -refX -refY -requiredExtensions -requiredFeatures -result -rotate -rx -ry -scale -seed -shape-image-threshold -shape-inside -shape-margin -shape-padding -shape-rendering -shape-subtract -side -slope -space -specularConstant -specularExponent -spreadMethod -startOffset -stdDeviation -stitchTiles -stop-color -stop-opacity -stroke -stroke-dasharray -stroke-dashoffset -stroke-linecap -stroke-linejoin -stroke-miterlimit -stroke-opacity -stroke-width -style -surfaceScale -systemLanguage -tableValues -targetX -targetY -text-align -text-align-last -text-anchor -text-decoration -text-decoration-color -text-decoration-fill -text-decoration-line -text-decoration-stroke -text-decoration-style -text-indent -text-orientation -text-overflow -text-rendering -text-underline-position -textLength -transform -transform-box -transform-origin -type -unicode-bidi -unicode-range -values -vector-effect -viewBox -visibility -white-space -width -word-spacing -writing-mode -x -x1 -x2 -xChannelSelector -y -y1 -y2 -yChannelSelector -z diff --git a/resvg-0.43.0/crates/usvg/codegen/elements.txt b/resvg-0.43.0/crates/usvg/codegen/elements.txt deleted file mode 100644 index bff9ac0..0000000 --- a/resvg-0.43.0/crates/usvg/codegen/elements.txt +++ /dev/null @@ -1,53 +0,0 @@ -a -circle -clipPath -defs -ellipse -feBlend -feColorMatrix -feComponentTransfer -feComposite -feConvolveMatrix -feDiffuseLighting -feDisplacementMap -feDistantLight -feDropShadow -feFlood -feFuncA -feFuncB -feFuncG -feFuncR -feGaussianBlur -feImage -feMerge -feMergeNode -feMorphology -feOffset -fePointLight -feSpecularLighting -feSpotLight -feTile -feTurbulence -filter -g -image -line -linearGradient -marker -mask -path -pattern -polygon -polyline -radialGradient -rect -stop -style -svg -switch -symbol -text -textPath -tref -tspan -use diff --git a/resvg-0.43.0/crates/usvg/codegen/main.rs b/resvg-0.43.0/crates/usvg/codegen/main.rs deleted file mode 100644 index fb77ade..0000000 --- a/resvg-0.43.0/crates/usvg/codegen/main.rs +++ /dev/null @@ -1,201 +0,0 @@ -use itertools::Itertools; - -use std::fs; -use std::io::{Read, Write}; -use std::str; - -const PHF_SRC: &str = "\ -// A stripped down `phf` crate fork. -// -// https://github.com/sfackler/rust-phf - -struct Map { - pub key: u64, - pub disps: &'static [(u32, u32)], - pub entries: &'static[(&'static str, V)], -} - -impl Map { - fn get(&self, key: &str) -> Option<&V> { - use std::borrow::Borrow; - - let hash = hash(key, self.key); - let index = get_index(hash, self.disps, self.entries.len()); - let entry = &self.entries[index as usize]; - let b = entry.0.borrow(); - if b == key { - Some(&entry.1) - } else { - None - } - } - - fn key(&self, value: &V) -> &'static str { - self.entries.iter().find(|kv| kv.1 == *value).unwrap().0 - } -} - -#[inline] -fn hash(x: &str, key: u64) -> u64 { - use std::hash::Hasher; - - let mut hasher = siphasher::sip::SipHasher13::new_with_keys(0, key); - hasher.write(x.as_bytes()); - hasher.finish() -} - -#[inline] -fn get_index(hash: u64, disps: &[(u32, u32)], len: usize) -> u32 { - let (g, f1, f2) = split(hash); - let (d1, d2) = disps[(g % (disps.len() as u32)) as usize]; - displace(f1, f2, d1, d2) % (len as u32) -} - -#[inline] -fn split(hash: u64) -> (u32, u32, u32) { - const BITS: u32 = 21; - const MASK: u64 = (1 << BITS) - 1; - - ((hash & MASK) as u32, - ((hash >> BITS) & MASK) as u32, - ((hash >> (2 * BITS)) & MASK) as u32) -} - -#[inline] -fn displace(f1: u32, f2: u32, d1: u32, d2: u32) -> u32 { - d2 + f1 * d1 + f2 -}"; - -fn main() { - if let Err(e) = gen() { - println!("{:?}", e); - std::process::exit(1); - } -} - -fn gen() -> Result<(), Box> { - let f = &mut fs::File::create("../src/parser/svgtree/names.rs")?; - - writeln!(f, "// This file is autogenerated. Do not edit it!")?; - writeln!(f, "// See ./codegen for details.\n")?; - - gen_map("elements.txt", "An element ID.", "EId", "ELEMENTS", f)?; - - gen_map("attributes.txt", "An attribute ID.", "AId", "ATTRIBUTES", f)?; - - writeln!(f, "{}", PHF_SRC)?; - - Ok(()) -} - -fn gen_map( - spec_path: &str, - enum_docs: &str, - enum_name: &str, - map_name: &str, - f: &mut fs::File, -) -> Result<(), Box> { - let mut spec = String::new(); - fs::File::open(spec_path)?.read_to_string(&mut spec)?; - - let names: Vec<&str> = spec.split('\n').filter(|s| !s.is_empty()).collect(); - - let joined_names = names.iter().map(|n| to_enum_name(n)).join(",\n "); - - let mut map = phf_codegen::Map::new(); - for name in &names { - map.entry(*name, &format!("{}::{}", enum_name, to_enum_name(name))); - } - - let mut map_data = Vec::new(); - map.build(&mut map_data)?; - let map_data = String::from_utf8(map_data)?; - let map_data = map_data.replace("::phf::Map", "Map"); - let map_data = map_data.replace("::phf::Slice::Static(", ""); - let map_data = map_data.replace("]),", "],"); - - writeln!(f, "/// {}", enum_docs)?; - writeln!(f, "#[allow(missing_docs)]")?; - writeln!(f, "#[derive(Clone, Copy, PartialEq)]")?; - writeln!(f, "pub enum {} {{", enum_name)?; - writeln!(f, " {}", joined_names)?; - writeln!(f, "}}\n")?; - - writeln!( - f, - "static {}: Map<{}> = {};\n", - map_name, enum_name, map_data - )?; - - writeln!(f, "impl {} {{", enum_name)?; - writeln!( - f, - " pub(crate) fn from_str(text: &str) -> Option<{}> {{", - enum_name - )?; - writeln!(f, " {}.get(text).cloned()", map_name)?; - writeln!(f, " }}")?; - writeln!(f, "")?; - writeln!(f, " /// Returns the original string.")?; - writeln!(f, " #[inline(never)]")?; - writeln!(f, " pub fn to_str(self) -> &'static str {{")?; - writeln!(f, " {}.key(&self)", map_name)?; - writeln!(f, " }}")?; - writeln!(f, "}}\n")?; - - writeln!(f, "impl std::fmt::Debug for {} {{", enum_name)?; - writeln!( - f, - " fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {{" - )?; - writeln!(f, " write!(f, \"{{}}\", self.to_str())")?; - writeln!(f, " }}")?; - writeln!(f, "}}\n")?; - - writeln!(f, "impl std::fmt::Display for {} {{", enum_name)?; - writeln!( - f, - " fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {{" - )?; - writeln!(f, " write!(f, \"{{:?}}\", self)")?; - writeln!(f, " }}")?; - writeln!(f, "}}")?; - writeln!(f, "")?; - - Ok(()) -} - -// some-string -> SomeString -// some_string -> SomeString -// some:string -> SomeString -// 100 -> N100 -fn to_enum_name(name: &str) -> String { - let mut change_case = false; - let mut s = String::with_capacity(name.len()); - for (idx, c) in name.chars().enumerate() { - if idx == 0 { - if c.is_digit(10) { - s.push('N'); - s.push(c); - } else { - s.push(c.to_uppercase().next().unwrap()); - } - - continue; - } - - if c == '-' || c == '_' || c == ':' { - change_case = true; - continue; - } - - if change_case { - s.push(c.to_uppercase().next().unwrap()); - change_case = false; - } else { - s.push(c); - } - } - - s -} diff --git a/resvg-0.43.0/crates/usvg/src/lib.rs b/resvg-0.43.0/crates/usvg/src/lib.rs deleted file mode 100644 index 553c732..0000000 --- a/resvg-0.43.0/crates/usvg/src/lib.rs +++ /dev/null @@ -1,72 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -/*! -`usvg` (micro SVG) is an [SVG] parser that tries to solve most of SVG complexity. - -SVG is notoriously hard to parse. `usvg` presents a layer between an XML library and -a potential SVG rendering library. It will parse an input SVG into a strongly-typed tree structure -were all the elements, attributes, references and other SVG features are already resolved -and presented in the simplest way possible. -So a caller doesn't have to worry about most of the issues related to SVG parsing -and can focus just on the rendering part. - -## Features - -- All supported attributes are resolved. - No need to worry about inheritable, implicit and default attributes -- CSS will be applied -- Only simple paths - - Basic shapes (like `rect` and `circle`) will be converted into paths - - Paths contain only absolute *MoveTo*, *LineTo*, *QuadTo*, *CurveTo* and *ClosePath* segments. - ArcTo, implicit and relative segments will be converted -- `use` will be resolved and replaced with the reference content -- Nested `svg` will be resolved -- Invalid, malformed elements will be removed -- Relative length units (mm, em, etc.) will be converted into pixels/points -- External images will be loaded -- Internal, base64 images will be decoded -- All references (like `#elem` and `url(#elem)`) will be resolved -- `switch` will be resolved -- Text elements, which are probably the hardest part of SVG, will be completely resolved. - This includes all the attributes resolving, whitespaces preprocessing (`xml:space`), - text chunks and spans resolving -- Markers will be converted into regular elements. No need to place them manually -- All filters are supported. Including filter functions, like `filter="contrast(50%)"` -- Recursive elements will be detected and removed -- `objectBoundingBox` will be replaced with `userSpaceOnUse` - -## Limitations - -- Unsupported SVG features will be ignored -- CSS support is minimal -- Only [static](http://www.w3.org/TR/SVG11/feature#SVG-static) SVG features, - e.g. no `a`, `view`, `cursor`, `script`, no events and no animations - -[SVG]: https://en.wikipedia.org/wiki/Scalable_Vector_Graphics -*/ - -#![forbid(unsafe_code)] -#![warn(missing_docs)] -#![warn(missing_debug_implementations)] -#![warn(missing_copy_implementations)] - -mod parser; -#[cfg(feature = "text")] -mod text; -mod tree; -mod writer; - -pub use parser::*; -#[cfg(feature = "text")] -pub use text::*; -pub use tree::*; - -pub use roxmltree; - -#[cfg(feature = "text")] -pub use fontdb; - -pub use writer::WriteOptions; -pub use xmlwriter::Indent; diff --git a/resvg-0.43.0/crates/usvg/src/main.rs b/resvg-0.43.0/crates/usvg/src/main.rs deleted file mode 100644 index b85ba15..0000000 --- a/resvg-0.43.0/crates/usvg/src/main.rs +++ /dev/null @@ -1,500 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use std::fs::File; -use std::io::{self, Read, Write}; -use std::path::PathBuf; -use std::process; -use std::sync::Arc; - -use pico_args::Arguments; - -const HELP: &str = "\ -usvg (micro SVG) is an SVG simplification tool. - -USAGE: - usvg [OPTIONS] # from file to file - usvg [OPTIONS] -c # from file to stdout - usvg [OPTIONS] - # from stdin to file - usvg [OPTIONS] - -c # from stdin to stdout - -OPTIONS: - -h, --help Prints help information - -V, --version Prints version information - -c Prints the output SVG to the stdout - - --dpi DPI Sets the resolution - [default: 96] [possible values: 10..4000 (inclusive)] - --languages LANG Sets a comma-separated list of languages that - will be used during the 'systemLanguage' - attribute resolving - Examples: 'en-US', 'en-US, ru-RU', 'en, ru' - [default: en] - --shape-rendering HINT Selects the default shape rendering method - [default: geometricPrecision] - [possible values: optimizeSpeed, crispEdges, - geometricPrecision] - --text-rendering HINT Selects the default text rendering method - [default: optimizeLegibility] - [possible values: optimizeSpeed, optimizeLegibility, - geometricPrecision] - --image-rendering HINT Selects the default image rendering method - [default: optimizeQuality] - [possible values: optimizeQuality, optimizeSpeed] - --resources-dir DIR Sets a directory that will be used during - relative paths resolving. - Expected to be the same as the directory that - contains the SVG file, but can be set to any. - [default: input file directory - or none when reading from stdin] - - --font-family FAMILY Sets the default font family that will be - used when no 'font-family' is present - [default: Times New Roman] - --font-size SIZE Sets the default font size that will be - used when no 'font-size' is present - [default: 12] [possible values: 1..192 (inclusive)] - --serif-family FAMILY Sets the 'serif' font family. - Will be used when no 'font-family' is present - [default: Times New Roman] - --sans-serif-family FAMILY Sets the 'sans-serif' font family - [default: Arial] - --cursive-family FAMILY Sets the 'cursive' font family - [default: Comic Sans MS] - --fantasy-family FAMILY Sets the 'fantasy' font family - [default: Impact] - --monospace-family FAMILY Sets the 'monospace' font family - [default: Courier New] - --use-font-file PATH Load a specified font file into the fonts database. - Will be used during text to path conversion. - This option can be set multiple times - --use-fonts-dir PATH Loads all fonts from the specified directory - into the fonts database. - Will be used during text to path conversion. - This option can be set multiple times - --skip-system-fonts Disables system fonts loading. - You should add some fonts manually using - --use-font-file and/or --use-fonts-dir - Otherwise, text elements will not be processes - --list-fonts Lists successfully loaded font faces. - Useful for debugging - --default-width LENGTH Sets the default width of the SVG viewport. Like - the '--default-height' option, this option - controls what size relative units in the document - will use as a base if there is no viewBox and - document width or height are relative. - [values: 1..4294967295 (inclusive)] [default: 100] - --default-height LENGTH Sets the default height of the SVG viewport. - Refer to the explanation of the '--default-width' - option. [values: 1..4294967295 (inclusive)] [default: 100] - - --preserve-text Do not convert text into paths. - --id-prefix Adds a prefix to each ID attribute - --indent INDENT Sets the XML nodes indent - [values: none, 0, 1, 2, 3, 4, tabs] [default: 4] - --attrs-indent INDENT Sets the XML attributes indent - [values: none, 0, 1, 2, 3, 4, tabs] [default: none] - --coordinates-precision NUM Set the coordinates numeric precision. - Smaller precision can lead to a malformed output in some cases - [values: 2..8 (inclusive)] [default: 8] - --transforms-precision NUM Set the transform values numeric precision. - Smaller precision can lead to a malformed output in some cases - [values: 2..8 (inclusive)] [default: 8] - --quiet Disables warnings - -ARGS: - Input file - Output file -"; - -#[derive(Debug)] -struct Args { - dpi: u32, - languages: Vec, - shape_rendering: usvg::ShapeRendering, - text_rendering: usvg::TextRendering, - image_rendering: usvg::ImageRendering, - resources_dir: Option, - - font_family: Option, - font_size: u32, - serif_family: Option, - sans_serif_family: Option, - cursive_family: Option, - fantasy_family: Option, - monospace_family: Option, - font_files: Vec, - font_dirs: Vec, - skip_system_fonts: bool, - preserve_text: bool, - list_fonts: bool, - default_width: u32, - default_height: u32, - - id_prefix: Option, - indent: xmlwriter::Indent, - attrs_indent: xmlwriter::Indent, - coordinates_precision: Option, - transforms_precision: Option, - - quiet: bool, - - input: String, - output: String, -} - -fn collect_args() -> Result { - let mut input = Arguments::from_env(); - - if input.contains(["-h", "--help"]) { - print!("{}", HELP); - process::exit(0); - } - - if input.contains(["-V", "--version"]) { - println!("{}", env!("CARGO_PKG_VERSION")); - process::exit(0); - } - - Ok(Args { - dpi: input.opt_value_from_fn("--dpi", parse_dpi)?.unwrap_or(96), - languages: input - .opt_value_from_fn("--languages", parse_languages)? - .unwrap_or(vec!["en".to_string()]), // TODO: use system language - shape_rendering: input - .opt_value_from_str("--shape-rendering")? - .unwrap_or_default(), - text_rendering: input - .opt_value_from_str("--text-rendering")? - .unwrap_or_default(), - image_rendering: input - .opt_value_from_str("--image-rendering")? - .unwrap_or_default(), - resources_dir: input - .opt_value_from_str("--resources-dir") - .unwrap_or_default(), - - font_family: input.opt_value_from_str("--font-family")?, - font_size: input - .opt_value_from_fn("--font-size", parse_font_size)? - .unwrap_or(12), - serif_family: input.opt_value_from_str("--serif-family")?, - sans_serif_family: input.opt_value_from_str("--sans-serif-family")?, - cursive_family: input.opt_value_from_str("--cursive-family")?, - fantasy_family: input.opt_value_from_str("--fantasy-family")?, - monospace_family: input.opt_value_from_str("--monospace-family")?, - font_files: input.values_from_str("--use-font-file")?, - font_dirs: input.values_from_str("--use-fonts-dir")?, - skip_system_fonts: input.contains("--skip-system-fonts"), - preserve_text: input.contains("--preserve-text"), - list_fonts: input.contains("--list-fonts"), - default_width: input - .opt_value_from_fn("--default-width", parse_length)? - .unwrap_or(100), - default_height: input - .opt_value_from_fn("--default-height", parse_length)? - .unwrap_or(100), - - id_prefix: input.opt_value_from_str("--id-prefix")?, - indent: input - .opt_value_from_fn("--indent", parse_indent)? - .unwrap_or(xmlwriter::Indent::Spaces(4)), - attrs_indent: input - .opt_value_from_fn("--attrs-indent", parse_indent)? - .unwrap_or(xmlwriter::Indent::None), - coordinates_precision: input - .opt_value_from_fn("--coordinates-precision", parse_precision)?, - transforms_precision: input.opt_value_from_fn("--transforms-precision", parse_precision)?, - - quiet: input.contains("--quiet"), - - input: input.free_from_str()?, - output: input.free_from_str()?, - }) -} - -fn parse_dpi(s: &str) -> Result { - let n: u32 = s.parse().map_err(|_| "invalid number")?; - - if n >= 10 && n <= 4000 { - Ok(n) - } else { - Err("DPI out of bounds".to_string()) - } -} - -fn parse_font_size(s: &str) -> Result { - let n: u32 = s.parse().map_err(|_| "invalid number")?; - - if n > 0 && n <= 192 { - Ok(n) - } else { - Err("font size out of bounds".to_string()) - } -} - -fn parse_languages(s: &str) -> Result, String> { - let mut langs = Vec::new(); - for lang in s.split(',') { - langs.push(lang.trim().to_string()); - } - - if langs.is_empty() { - return Err("languages list cannot be empty".to_string()); - } - - Ok(langs) -} - -fn parse_indent(s: &str) -> Result { - let indent = match s { - "none" => xmlwriter::Indent::None, - "0" => xmlwriter::Indent::Spaces(0), - "1" => xmlwriter::Indent::Spaces(1), - "2" => xmlwriter::Indent::Spaces(2), - "3" => xmlwriter::Indent::Spaces(3), - "4" => xmlwriter::Indent::Spaces(4), - "tabs" => xmlwriter::Indent::Tabs, - _ => return Err("invalid INDENT value".to_string()), - }; - - Ok(indent) -} - -fn parse_length(s: &str) -> Result { - let n: u32 = s.parse().map_err(|_| "invalid length")?; - - if n > 0 { - Ok(n) - } else { - Err("LENGTH cannot be zero".to_string()) - } -} - -fn parse_precision(s: &str) -> Result { - let n: u8 = s.parse().map_err(|_| "invalid precision NUM value")?; - - if (2..=8).contains(&n) { - Ok(n) - } else { - Err("precision NUM cannot be smaller than 2 or larger than 8".to_string()) - } -} - -#[derive(Clone, PartialEq, Debug)] -enum InputFrom<'a> { - Stdin, - File(&'a str), -} - -#[derive(Clone, PartialEq, Debug)] -enum OutputTo<'a> { - Stdout, - File(&'a str), -} - -fn main() { - let args = match collect_args() { - Ok(v) => v, - Err(e) => { - eprintln!("Error: {}.", e); - process::exit(1); - } - }; - - if !args.quiet { - if let Ok(()) = log::set_logger(&LOGGER) { - log::set_max_level(log::LevelFilter::Warn); - } - } - - if let Err(e) = process(args) { - eprintln!("Error: {}.", e.to_string()); - process::exit(1); - } -} - -fn process(args: Args) -> Result<(), String> { - let (in_svg, out_svg) = { - let in_svg = args.input.as_str(); - let out_svg = args.output.as_str(); - - let svg_from = if in_svg == "-" { - InputFrom::Stdin - } else if in_svg == "-c" { - return Err(format!("-c should be set after input")); - } else { - InputFrom::File(in_svg) - }; - - let svg_to = if out_svg == "-c" { - OutputTo::Stdout - } else { - OutputTo::File(out_svg) - }; - - (svg_from, svg_to) - }; - - let mut fontdb = usvg::fontdb::Database::new(); - if !args.skip_system_fonts { - // TODO: only when needed - fontdb.load_system_fonts(); - } - - for path in &args.font_files { - if let Err(e) = fontdb.load_font_file(path) { - log::warn!("Failed to load '{}' cause {}.", path.display(), e); - } - } - - for path in &args.font_dirs { - fontdb.load_fonts_dir(path); - } - - let take_or = |mut family: Option, fallback: &str| { - family.take().unwrap_or_else(|| fallback.to_string()) - }; - - fontdb.set_serif_family(take_or(args.serif_family, "Times New Roman")); - fontdb.set_sans_serif_family(take_or(args.sans_serif_family, "Arial")); - fontdb.set_cursive_family(take_or(args.cursive_family, "Comic Sans MS")); - fontdb.set_fantasy_family(take_or(args.fantasy_family, "Impact")); - fontdb.set_monospace_family(take_or(args.monospace_family, "Courier New")); - - if args.list_fonts { - for face in fontdb.faces() { - if let usvg::fontdb::Source::File(ref path) = &face.source { - let families: Vec<_> = face - .families - .iter() - .map(|f| format!("{} ({}, {})", f.0, f.1.primary_language(), f.1.region())) - .collect(); - - println!( - "{}: '{}', {}, {:?}, {:?}, {:?}", - path.display(), - families.join("', '"), - face.index, - face.style, - face.weight.0, - face.stretch - ); - } - } - } - - let resources_dir = match args.resources_dir { - Some(v) => Some(v), - None => { - match in_svg { - InputFrom::Stdin => None, - InputFrom::File(ref f) => { - // Get input file absolute directory. - std::fs::canonicalize(f) - .ok() - .and_then(|p| p.parent().map(|p| p.to_path_buf())) - } - } - } - }; - - let re_opt = usvg::Options { - resources_dir, - dpi: args.dpi as f32, - font_family: args - .font_family - .as_deref() - .unwrap_or("Times New Roman") - .to_string(), - font_size: args.font_size as f32, - languages: args.languages, - shape_rendering: args.shape_rendering, - text_rendering: args.text_rendering, - image_rendering: args.image_rendering, - default_size: usvg::Size::from_wh(args.default_width as f32, args.default_height as f32) - .unwrap(), - image_href_resolver: usvg::ImageHrefResolver::default(), - font_resolver: usvg::FontResolver::default(), - fontdb: Arc::new(fontdb), - }; - - let input_svg = match in_svg { - InputFrom::Stdin => load_stdin(), - InputFrom::File(ref path) => std::fs::read(path).map_err(|e| e.to_string()), - }?; - - let tree = usvg::Tree::from_data(&input_svg, &re_opt).map_err(|e| format!("{}", e))?; - - let xml_opt = usvg::WriteOptions { - id_prefix: args.id_prefix, - preserve_text: args.preserve_text, - coordinates_precision: args.coordinates_precision.unwrap_or(8), - transforms_precision: args.transforms_precision.unwrap_or(8), - use_single_quote: false, - indent: args.indent, - attributes_indent: args.attrs_indent, - }; - - let s = tree.to_string(&xml_opt); - match out_svg { - OutputTo::Stdout => { - io::stdout() - .write_all(s.as_bytes()) - .map_err(|_| format!("failed to write to the stdout"))?; - } - OutputTo::File(path) => { - let mut f = - File::create(path).map_err(|_| format!("failed to create the output file"))?; - f.write_all(s.as_bytes()) - .map_err(|_| format!("failed to write to the output file"))?; - } - } - - Ok(()) -} - -fn load_stdin() -> Result, String> { - let mut buf = Vec::new(); - let stdin = io::stdin(); - let mut handle = stdin.lock(); - - handle - .read_to_end(&mut buf) - .map_err(|_| format!("failed to read from stdin"))?; - - Ok(buf) -} - -/// A simple stderr logger. -static LOGGER: SimpleLogger = SimpleLogger; -struct SimpleLogger; -impl log::Log for SimpleLogger { - fn enabled(&self, metadata: &log::Metadata) -> bool { - metadata.level() <= log::LevelFilter::Warn - } - - fn log(&self, record: &log::Record) { - if self.enabled(record.metadata()) { - let target = if record.target().len() > 0 { - record.target() - } else { - record.module_path().unwrap_or_default() - }; - - let line = record.line().unwrap_or(0); - let args = record.args(); - - match record.level() { - log::Level::Error => eprintln!("Error (in {}:{}): {}", target, line, args), - log::Level::Warn => eprintln!("Warning (in {}:{}): {}", target, line, args), - log::Level::Info => eprintln!("Info (in {}:{}): {}", target, line, args), - log::Level::Debug => eprintln!("Debug (in {}:{}): {}", target, line, args), - log::Level::Trace => eprintln!("Trace (in {}:{}): {}", target, line, args), - } - } - } - - fn flush(&self) {} -} diff --git a/resvg-0.43.0/crates/usvg/src/parser/clippath.rs b/resvg-0.43.0/crates/usvg/src/parser/clippath.rs deleted file mode 100644 index b95caaf..0000000 --- a/resvg-0.43.0/crates/usvg/src/parser/clippath.rs +++ /dev/null @@ -1,126 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use std::str::FromStr; -use std::sync::Arc; - -use super::converter; -use super::svgtree::{AId, EId, SvgNode}; -use crate::{ClipPath, Group, NonEmptyString, NonZeroRect, Transform, Units}; - -pub(crate) fn convert( - node: SvgNode, - state: &converter::State, - object_bbox: Option, - cache: &mut converter::Cache, -) -> Option> { - // A `clip-path` attribute must reference a `clipPath` element. - if node.tag_name() != Some(EId::ClipPath) { - return None; - } - - // The whole clip path should be ignored when a transform is invalid. - let mut transform = resolve_clip_path_transform(node, state)?; - - let units = node - .attribute(AId::ClipPathUnits) - .unwrap_or(Units::UserSpaceOnUse); - - // Check if this element was already converted. - // - // Only `userSpaceOnUse` clipPaths can be shared, - // because `objectBoundingBox` one will be converted into user one - // and will become node-specific. - let cacheable = units == Units::UserSpaceOnUse; - if cacheable { - if let Some(clip) = cache.clip_paths.get(node.element_id()) { - return Some(clip.clone()); - } - } - - if units == Units::ObjectBoundingBox { - let object_bbox = match object_bbox { - Some(v) => v, - None => { - log::warn!("Clipping of zero-sized shapes is not allowed."); - return None; - } - }; - - let ts = Transform::from_bbox(object_bbox); - transform = transform.pre_concat(ts); - } - - // Resolve linked clip path. - let mut clip_path = None; - if let Some(link) = node.attribute::(AId::ClipPath) { - clip_path = convert(link, state, object_bbox, cache); - - // Linked `clipPath` must be valid. - if clip_path.is_none() { - return None; - } - } - - let mut id = NonEmptyString::new(node.element_id().to_string())?; - // Generate ID only when we're parsing `objectBoundingBox` clip for the second time. - if !cacheable && cache.clip_paths.contains_key(id.get()) { - id = cache.gen_clip_path_id(); - } - let id_copy = id.get().to_string(); - - let mut clip = ClipPath { - id, - transform, - clip_path, - root: Group::empty(), - }; - - let mut clip_state = state.clone(); - clip_state.parent_clip_path = Some(node); - converter::convert_clip_path_elements(node, &clip_state, cache, &mut clip.root); - - if clip.root.has_children() { - clip.root.calculate_bounding_boxes(); - let clip = Arc::new(clip); - cache.clip_paths.insert(id_copy, clip.clone()); - Some(clip) - } else { - // A clip path without children is invalid. - None - } -} - -fn resolve_clip_path_transform(node: SvgNode, state: &converter::State) -> Option { - // Do not use Node::attribute::, because it will always - // return a valid transform. - - let value: &str = match node.attribute(AId::Transform) { - Some(v) => v, - None => return Some(Transform::default()), - }; - - let ts = match svgtypes::Transform::from_str(value) { - Ok(v) => v, - Err(_) => { - log::warn!("Failed to parse {} value: '{}'.", AId::Transform, value); - return None; - } - }; - - let ts = Transform::from_row( - ts.a as f32, - ts.b as f32, - ts.c as f32, - ts.d as f32, - ts.e as f32, - ts.f as f32, - ); - - if ts.is_valid() { - Some(node.resolve_transform(AId::Transform, state)) - } else { - None - } -} diff --git a/resvg-0.43.0/crates/usvg/src/parser/converter.rs b/resvg-0.43.0/crates/usvg/src/parser/converter.rs deleted file mode 100644 index 55584db..0000000 --- a/resvg-0.43.0/crates/usvg/src/parser/converter.rs +++ /dev/null @@ -1,987 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use std::collections::{HashMap, HashSet}; -use std::hash::{Hash, Hasher}; -use std::str::FromStr; -use std::sync::Arc; - -#[cfg(feature = "text")] -use fontdb::Database; -use svgtypes::{Length, LengthUnit as Unit, PaintOrderKind, TransformOrigin}; - -use super::svgtree::{self, AId, EId, FromValue, SvgNode}; -use super::units::{self, convert_length}; -use super::{marker, Error, Options}; -use crate::parser::paint_server::process_paint; -use crate::*; - -#[derive(Clone)] -pub struct State<'a> { - pub(crate) parent_clip_path: Option>, - pub(crate) parent_markers: Vec>, - /// Stores the resolved fill and stroke of a use node - /// or a path element (for markers) - pub(crate) context_element: Option<(Option, Option)>, - pub(crate) fe_image_link: bool, - /// A viewBox of the parent SVG element. - pub(crate) view_box: NonZeroRect, - /// A size of the parent `use` element. - /// Used only during nested `svg` size resolving. - /// Width and height can be set independently. - pub(crate) use_size: (Option, Option), - pub(crate) opt: &'a Options<'a>, -} - -#[derive(Clone)] -pub struct Cache { - /// This fontdb is initialized from [`Options::fontdb`] and then populated - /// over the course of conversion. - #[cfg(feature = "text")] - pub fontdb: Arc, - - pub clip_paths: HashMap>, - pub masks: HashMap>, - pub filters: HashMap>, - pub paint: HashMap, - - // used for ID generation - all_ids: HashSet, - linear_gradient_index: usize, - radial_gradient_index: usize, - pattern_index: usize, - clip_path_index: usize, - mask_index: usize, - filter_index: usize, - image_index: usize, -} - -impl Cache { - pub(crate) fn new(#[cfg(feature = "text")] fontdb: Arc) -> Self { - Self { - #[cfg(feature = "text")] - fontdb, - - clip_paths: HashMap::new(), - masks: HashMap::new(), - filters: HashMap::new(), - paint: HashMap::new(), - - all_ids: HashSet::new(), - linear_gradient_index: 0, - radial_gradient_index: 0, - pattern_index: 0, - clip_path_index: 0, - mask_index: 0, - filter_index: 0, - image_index: 0, - } - } - - // TODO: macros? - pub(crate) fn gen_linear_gradient_id(&mut self) -> NonEmptyString { - loop { - self.linear_gradient_index += 1; - let new_id = format!("linearGradient{}", self.linear_gradient_index); - let new_hash = string_hash(&new_id); - if !self.all_ids.contains(&new_hash) { - return NonEmptyString::new(new_id).unwrap(); - } - } - } - - pub(crate) fn gen_radial_gradient_id(&mut self) -> NonEmptyString { - loop { - self.radial_gradient_index += 1; - let new_id = format!("radialGradient{}", self.radial_gradient_index); - let new_hash = string_hash(&new_id); - if !self.all_ids.contains(&new_hash) { - return NonEmptyString::new(new_id).unwrap(); - } - } - } - - pub(crate) fn gen_pattern_id(&mut self) -> NonEmptyString { - loop { - self.pattern_index += 1; - let new_id = format!("pattern{}", self.pattern_index); - let new_hash = string_hash(&new_id); - if !self.all_ids.contains(&new_hash) { - return NonEmptyString::new(new_id).unwrap(); - } - } - } - - pub(crate) fn gen_clip_path_id(&mut self) -> NonEmptyString { - loop { - self.clip_path_index += 1; - let new_id = format!("clipPath{}", self.clip_path_index); - let new_hash = string_hash(&new_id); - if !self.all_ids.contains(&new_hash) { - return NonEmptyString::new(new_id).unwrap(); - } - } - } - - pub(crate) fn gen_mask_id(&mut self) -> NonEmptyString { - loop { - self.mask_index += 1; - let new_id = format!("mask{}", self.mask_index); - let new_hash = string_hash(&new_id); - if !self.all_ids.contains(&new_hash) { - return NonEmptyString::new(new_id).unwrap(); - } - } - } - - pub(crate) fn gen_filter_id(&mut self) -> NonEmptyString { - loop { - self.filter_index += 1; - let new_id = format!("filter{}", self.filter_index); - let new_hash = string_hash(&new_id); - if !self.all_ids.contains(&new_hash) { - return NonEmptyString::new(new_id).unwrap(); - } - } - } - - pub(crate) fn gen_image_id(&mut self) -> NonEmptyString { - loop { - self.image_index += 1; - let new_id = format!("image{}", self.image_index); - let new_hash = string_hash(&new_id); - if !self.all_ids.contains(&new_hash) { - return NonEmptyString::new(new_id).unwrap(); - } - } - } -} - -// TODO: is there a simpler way? -fn string_hash(s: &str) -> u64 { - let mut h = std::collections::hash_map::DefaultHasher::new(); - s.hash(&mut h); - h.finish() -} - -impl<'a, 'input: 'a> SvgNode<'a, 'input> { - pub(crate) fn convert_length( - &self, - aid: AId, - object_units: Units, - state: &State, - def: Length, - ) -> f32 { - units::convert_length( - self.attribute(aid).unwrap_or(def), - *self, - aid, - object_units, - state, - ) - } - - pub fn convert_user_length(&self, aid: AId, state: &State, def: Length) -> f32 { - self.convert_length(aid, Units::UserSpaceOnUse, state, def) - } - - pub fn parse_viewbox(&self) -> Option { - let vb: svgtypes::ViewBox = self.attribute(AId::ViewBox)?; - NonZeroRect::from_xywh(vb.x as f32, vb.y as f32, vb.w as f32, vb.h as f32) - } - - pub fn resolve_length(&self, aid: AId, state: &State, def: f32) -> f32 { - debug_assert!( - !matches!(aid, AId::BaselineShift | AId::FontSize), - "{} cannot be resolved via this function", - aid - ); - - if let Some(n) = self.ancestors().find(|n| n.has_attribute(aid)) { - if let Some(length) = n.attribute(aid) { - return units::convert_user_length(length, n, aid, state); - } - } - - def - } - - pub fn resolve_valid_length( - &self, - aid: AId, - state: &State, - def: f32, - ) -> Option { - let n = self.resolve_length(aid, state, def); - NonZeroPositiveF32::new(n) - } - - pub(crate) fn try_convert_length( - &self, - aid: AId, - object_units: Units, - state: &State, - ) -> Option { - Some(units::convert_length( - self.attribute(aid)?, - *self, - aid, - object_units, - state, - )) - } - - pub fn has_valid_transform(&self, aid: AId) -> bool { - // Do not use Node::attribute::, because it will always - // return a valid transform. - - let attr = match self.attribute(aid) { - Some(attr) => attr, - None => return true, - }; - - let ts = match svgtypes::Transform::from_str(attr) { - Ok(v) => v, - Err(_) => return true, - }; - - let ts = Transform::from_row( - ts.a as f32, - ts.b as f32, - ts.c as f32, - ts.d as f32, - ts.e as f32, - ts.f as f32, - ); - ts.is_valid() - } - - pub fn is_visible_element(&self, opt: &crate::Options) -> bool { - self.attribute(AId::Display) != Some("none") - && self.has_valid_transform(AId::Transform) - && super::switch::is_condition_passed(*self, opt) - } -} - -pub trait SvgColorExt { - fn split_alpha(self) -> (Color, Opacity); -} - -impl SvgColorExt for svgtypes::Color { - fn split_alpha(self) -> (Color, Opacity) { - ( - Color::new_rgb(self.red, self.green, self.blue), - Opacity::new_u8(self.alpha), - ) - } -} - -/// Converts an input `Document` into a `Tree`. -/// -/// # Errors -/// -/// - If `Document` doesn't have an SVG node - returns an empty tree. -/// - If `Document` doesn't have a valid size - returns `Error::InvalidSize`. -pub(crate) fn convert_doc(svg_doc: &svgtree::Document, opt: &Options) -> Result { - let svg = svg_doc.root_element(); - let (size, restore_viewbox) = resolve_svg_size(&svg, opt); - let size = size?; - let view_box = ViewBox { - rect: svg - .parse_viewbox() - .unwrap_or_else(|| size.to_non_zero_rect(0.0, 0.0)), - aspect: svg.attribute(AId::PreserveAspectRatio).unwrap_or_default(), - }; - - let mut tree = Tree { - size, - root: Group::empty(), - linear_gradients: Vec::new(), - radial_gradients: Vec::new(), - patterns: Vec::new(), - clip_paths: Vec::new(), - masks: Vec::new(), - filters: Vec::new(), - #[cfg(feature = "text")] - fontdb: opt.fontdb.clone(), - }; - - if !svg.is_visible_element(opt) { - return Ok(tree); - } - - let state = State { - parent_clip_path: None, - context_element: None, - parent_markers: Vec::new(), - fe_image_link: false, - view_box: view_box.rect, - use_size: (None, None), - opt, - }; - - let mut cache = Cache::new( - #[cfg(feature = "text")] - opt.fontdb.clone(), - ); - - for node in svg_doc.descendants() { - if let Some(tag) = node.tag_name() { - if matches!( - tag, - EId::ClipPath - | EId::Filter - | EId::LinearGradient - | EId::Mask - | EId::Pattern - | EId::RadialGradient - | EId::Image - ) { - if !node.element_id().is_empty() { - cache.all_ids.insert(string_hash(node.element_id())); - } - } - } - } - - let root_ts = view_box.to_transform(tree.size()); - if root_ts.is_identity() { - convert_children(svg_doc.root(), &state, &mut cache, &mut tree.root); - } else { - let mut g = Group::empty(); - g.transform = root_ts; - g.abs_transform = root_ts; - convert_children(svg_doc.root(), &state, &mut cache, &mut g); - tree.root.children.push(Node::Group(Box::new(g))); - } - - // Clear cache to make sure that all `Arc` objects have a single strong reference. - cache.clip_paths.clear(); - cache.masks.clear(); - cache.filters.clear(); - cache.paint.clear(); - - super::paint_server::update_paint_servers( - &mut tree.root, - Transform::default(), - None, - None, - &mut cache, - ); - tree.collect_paint_servers(); - tree.root.collect_clip_paths(&mut tree.clip_paths); - tree.root.collect_masks(&mut tree.masks); - tree.root.collect_filters(&mut tree.filters); - tree.root.calculate_bounding_boxes(); - - // The fontdb might have been mutated and we want to apply these changes to - // the tree's fontdb. - #[cfg(feature = "text")] - { - tree.fontdb = cache.fontdb; - } - - if restore_viewbox { - calculate_svg_bbox(&mut tree); - } - - Ok(tree) -} - -fn resolve_svg_size(svg: &SvgNode, opt: &Options) -> (Result, bool) { - let mut state = State { - parent_clip_path: None, - context_element: None, - parent_markers: Vec::new(), - fe_image_link: false, - view_box: NonZeroRect::from_xywh(0.0, 0.0, 100.0, 100.0).unwrap(), - use_size: (None, None), - opt, - }; - - let def = Length::new(100.0, Unit::Percent); - let mut width: Length = svg.attribute(AId::Width).unwrap_or(def); - let mut height: Length = svg.attribute(AId::Height).unwrap_or(def); - - let view_box = svg.parse_viewbox(); - - let restore_viewbox = - if (width.unit == Unit::Percent || height.unit == Unit::Percent) && view_box.is_none() { - // Apply the percentages to the fallback size. - if width.unit == Unit::Percent { - width = Length::new( - (width.number / 100.0) * state.opt.default_size.width() as f64, - Unit::None, - ); - } - - if height.unit == Unit::Percent { - height = Length::new( - (height.number / 100.0) * state.opt.default_size.height() as f64, - Unit::None, - ); - } - - true - } else { - false - }; - - let size = if let Some(vbox) = view_box { - state.view_box = vbox; - - let w = if width.unit == Unit::Percent { - vbox.width() * (width.number as f32 / 100.0) - } else { - svg.convert_user_length(AId::Width, &state, def) - }; - - let h = if height.unit == Unit::Percent { - vbox.height() * (height.number as f32 / 100.0) - } else { - svg.convert_user_length(AId::Height, &state, def) - }; - - Size::from_wh(w, h) - } else { - Size::from_wh( - svg.convert_user_length(AId::Width, &state, def), - svg.convert_user_length(AId::Height, &state, def), - ) - }; - - (size.ok_or(Error::InvalidSize), restore_viewbox) -} - -/// Calculates SVG's size and viewBox in case there were not set. -/// -/// Simply iterates over all nodes and calculates a bounding box. -fn calculate_svg_bbox(tree: &mut Tree) { - let bbox = tree.root.abs_bounding_box(); - if let Some(size) = Size::from_wh(bbox.right(), bbox.bottom()) { - tree.size = size; - } -} - -#[inline(never)] -pub(crate) fn convert_children( - parent_node: SvgNode, - state: &State, - cache: &mut Cache, - parent: &mut Group, -) { - for node in parent_node.children() { - convert_element(node, state, cache, parent); - } -} - -#[inline(never)] -pub(crate) fn convert_element(node: SvgNode, state: &State, cache: &mut Cache, parent: &mut Group) { - let tag_name = match node.tag_name() { - Some(v) => v, - None => return, - }; - - if !tag_name.is_graphic() && !matches!(tag_name, EId::G | EId::Switch | EId::Svg) { - return; - } - - if !node.is_visible_element(state.opt) { - return; - } - - if tag_name == EId::Use { - super::use_node::convert(node, state, cache, parent); - return; - } - - if tag_name == EId::Switch { - super::switch::convert(node, state, cache, parent); - return; - } - - if let Some(g) = convert_group(node, state, false, cache, parent, &|cache, g| { - convert_element_impl(tag_name, node, state, cache, g); - }) { - parent.children.push(Node::Group(Box::new(g))); - } -} - -#[inline(never)] -fn convert_element_impl( - tag_name: EId, - node: SvgNode, - state: &State, - cache: &mut Cache, - parent: &mut Group, -) { - match tag_name { - EId::Rect - | EId::Circle - | EId::Ellipse - | EId::Line - | EId::Polyline - | EId::Polygon - | EId::Path => { - if let Some(path) = super::shapes::convert(node, state) { - convert_path(node, path, state, cache, parent); - } - } - EId::Image => { - super::image::convert(node, state, cache, parent); - } - EId::Text => { - #[cfg(feature = "text")] - { - super::text::convert(node, state, cache, parent); - } - } - EId::Svg => { - if node.parent_element().is_some() { - super::use_node::convert_svg(node, state, cache, parent); - } else { - // Skip root `svg`. - convert_children(node, state, cache, parent); - } - } - EId::G => { - convert_children(node, state, cache, parent); - } - _ => {} - } -} - -// `clipPath` can have only shape and `text` children. -// -// `line` doesn't impact rendering because stroke is always disabled -// for `clipPath` children. -#[inline(never)] -pub(crate) fn convert_clip_path_elements( - clip_node: SvgNode, - state: &State, - cache: &mut Cache, - parent: &mut Group, -) { - for node in clip_node.children() { - let tag_name = match node.tag_name() { - Some(v) => v, - None => continue, - }; - - if !tag_name.is_graphic() { - continue; - } - - if !node.is_visible_element(state.opt) { - continue; - } - - if tag_name == EId::Use { - super::use_node::convert(node, state, cache, parent); - continue; - } - - if let Some(g) = convert_group(node, state, false, cache, parent, &|cache, g| { - convert_clip_path_elements_impl(tag_name, node, state, cache, g); - }) { - parent.children.push(Node::Group(Box::new(g))); - } - } -} - -#[inline(never)] -fn convert_clip_path_elements_impl( - tag_name: EId, - node: SvgNode, - state: &State, - cache: &mut Cache, - parent: &mut Group, -) { - match tag_name { - EId::Rect | EId::Circle | EId::Ellipse | EId::Polyline | EId::Polygon | EId::Path => { - if let Some(path) = super::shapes::convert(node, state) { - convert_path(node, path, state, cache, parent); - } - } - EId::Text => { - #[cfg(feature = "text")] - { - super::text::convert(node, state, cache, parent); - } - } - _ => { - log::warn!("'{}' is no a valid 'clip-path' child.", tag_name); - } - } -} - -#[derive(Clone, Copy, PartialEq, Debug)] -enum Isolation { - Auto, - Isolate, -} - -impl Default for Isolation { - fn default() -> Self { - Self::Auto - } -} - -impl<'a, 'input: 'a> FromValue<'a, 'input> for Isolation { - fn parse(_: SvgNode, _: AId, value: &str) -> Option { - match value { - "auto" => Some(Isolation::Auto), - "isolate" => Some(Isolation::Isolate), - _ => None, - } - } -} - -// TODO: explain -pub(crate) fn convert_group( - node: SvgNode, - state: &State, - force: bool, - cache: &mut Cache, - parent: &mut Group, - collect_children: &dyn Fn(&mut Cache, &mut Group), -) -> Option { - // A `clipPath` child cannot have an opacity. - let opacity = if state.parent_clip_path.is_none() { - node.attribute::(AId::Opacity) - .unwrap_or(Opacity::ONE) - } else { - Opacity::ONE - }; - - let transform = node.resolve_transform(AId::Transform, state); - let blend_mode: BlendMode = node.attribute(AId::MixBlendMode).unwrap_or_default(); - let isolation: Isolation = node.attribute(AId::Isolation).unwrap_or_default(); - let isolate = isolation == Isolation::Isolate; - - // Nodes generated by markers must not have an ID. Otherwise we would have duplicates. - let is_g_or_use = matches!(node.tag_name(), Some(EId::G) | Some(EId::Use)); - let id = if is_g_or_use && state.parent_markers.is_empty() { - node.element_id().to_string() - } else { - String::new() - }; - - let abs_transform = parent.abs_transform.pre_concat(transform); - let dummy = Rect::from_xywh(0.0, 0.0, 0.0, 0.0).unwrap(); - let mut g = Group { - id, - transform, - abs_transform, - opacity, - blend_mode, - isolate, - clip_path: None, - mask: None, - filters: Vec::new(), - is_context_element: false, - bounding_box: dummy, - abs_bounding_box: dummy, - stroke_bounding_box: dummy, - abs_stroke_bounding_box: dummy, - layer_bounding_box: NonZeroRect::from_xywh(0.0, 0.0, 1.0, 1.0).unwrap(), - abs_layer_bounding_box: NonZeroRect::from_xywh(0.0, 0.0, 1.0, 1.0).unwrap(), - children: Vec::new(), - }; - collect_children(cache, &mut g); - - // We need to know group's bounding box before converting - // clipPaths, masks and filters. - let object_bbox = g.calculate_object_bbox(); - - // `mask` and `filter` cannot be set on `clipPath` children. - // But `clip-path` can. - - let mut clip_path = None; - if let Some(link) = node.attribute::(AId::ClipPath) { - clip_path = super::clippath::convert(link, state, object_bbox, cache); - if clip_path.is_none() { - return None; - } - } - - let mut mask = None; - if state.parent_clip_path.is_none() { - if let Some(link) = node.attribute::(AId::Mask) { - mask = super::mask::convert(link, state, object_bbox, cache); - if mask.is_none() { - return None; - } - } - } - - let filters = { - let mut filters = Vec::new(); - if state.parent_clip_path.is_none() { - if node.attribute(AId::Filter) == Some("none") { - // Do nothing. - } else if node.has_attribute(AId::Filter) { - if let Ok(f) = super::filter::convert(node, state, object_bbox, cache) { - filters = f; - } else { - // A filter that not a link or a filter with a link to a non existing element. - // - // Unlike `clip-path` and `mask`, when a `filter` link is invalid - // then the whole element should be ignored. - // - // This is kinda an undefined behaviour. - // In most cases, Chrome, Firefox and rsvg will ignore such elements, - // but in some cases Chrome allows it. Not sure why. - // Inkscape (0.92) simply ignores such attributes, rendering element as is. - // Batik (1.12) crashes. - // - // Test file: e-filter-051.svg - return None; - } - } - } - - filters - }; - - let required = opacity.get().approx_ne_ulps(&1.0, 4) - || clip_path.is_some() - || mask.is_some() - || !filters.is_empty() - || !transform.is_identity() - || blend_mode != BlendMode::Normal - || isolate - || is_g_or_use - || force; - - if !required { - parent.children.append(&mut g.children); - return None; - } - - g.clip_path = clip_path; - g.mask = mask; - g.filters = filters; - - // Must be called after we set Group::filters - g.calculate_bounding_boxes(); - - Some(g) -} - -fn convert_path( - node: SvgNode, - tiny_skia_path: Arc, - state: &State, - cache: &mut Cache, - parent: &mut Group, -) { - debug_assert!(tiny_skia_path.len() >= 2); - if tiny_skia_path.len() < 2 { - return; - } - - let has_bbox = tiny_skia_path.bounds().width() > 0.0 && tiny_skia_path.bounds().height() > 0.0; - let mut fill = super::style::resolve_fill(node, has_bbox, state, cache); - let mut stroke = super::style::resolve_stroke(node, has_bbox, state, cache); - let visibility: Visibility = node.find_attribute(AId::Visibility).unwrap_or_default(); - let mut visible = visibility == Visibility::Visible; - let rendering_mode: ShapeRendering = node - .find_attribute(AId::ShapeRendering) - .unwrap_or(state.opt.shape_rendering); - - // TODO: handle `markers` before `stroke` - let raw_paint_order: svgtypes::PaintOrder = - node.find_attribute(AId::PaintOrder).unwrap_or_default(); - let paint_order = svg_paint_order_to_usvg(raw_paint_order); - let path_transform = parent.abs_transform; - - // If a path doesn't have a fill or a stroke then it's invisible. - // By setting `visibility` to `hidden` we are disabling rendering of this path. - if fill.is_none() && stroke.is_none() { - visible = false; - } - - if let Some(fill) = fill.as_mut() { - if let Some(ContextElement::PathNode(context_transform, context_bbox)) = - fill.context_element - { - process_paint( - &mut fill.paint, - true, - context_transform, - context_bbox.map(|r| r.to_rect()), - path_transform, - tiny_skia_path.bounds(), - cache, - ); - fill.context_element = None; - } - } - - if let Some(stroke) = stroke.as_mut() { - if let Some(ContextElement::PathNode(context_transform, context_bbox)) = - stroke.context_element - { - process_paint( - &mut stroke.paint, - true, - context_transform, - context_bbox.map(|r| r.to_rect()), - path_transform, - tiny_skia_path.bounds(), - cache, - ); - stroke.context_element = None; - } - } - - let mut marker = None; - if marker::is_valid(node) && visibility == Visibility::Visible { - let mut marker_group = Group { - abs_transform: parent.abs_transform, - ..Group::empty() - }; - - let mut marker_state = state.clone(); - - let bbox = tiny_skia_path - .compute_tight_bounds() - .and_then(|r| r.to_non_zero_rect()); - - let fill = fill.clone().map(|mut f| { - f.context_element = Some(ContextElement::PathNode(path_transform, bbox)); - f - }); - - let stroke = stroke.clone().map(|mut s| { - s.context_element = Some(ContextElement::PathNode(path_transform, bbox)); - s - }); - - marker_state.context_element = Some((fill, stroke)); - - marker::convert( - node, - &tiny_skia_path, - &marker_state, - cache, - &mut marker_group, - ); - marker_group.calculate_bounding_boxes(); - marker = Some(marker_group); - } - - // Nodes generated by markers must not have an ID. Otherwise we would have duplicates. - let id = if state.parent_markers.is_empty() { - node.element_id().to_string() - } else { - String::new() - }; - - let path = Path::new( - id, - visible, - fill, - stroke, - paint_order, - rendering_mode, - tiny_skia_path, - path_transform, - ); - - let path = match path { - Some(v) => v, - None => return, - }; - - match raw_paint_order.order { - [PaintOrderKind::Markers, _, _] => { - if let Some(markers_node) = marker { - parent.children.push(Node::Group(Box::new(markers_node))); - } - - parent.children.push(Node::Path(Box::new(path.clone()))); - } - [first, PaintOrderKind::Markers, last] => { - append_single_paint_path(first, &path, parent); - - if let Some(markers_node) = marker { - parent.children.push(Node::Group(Box::new(markers_node))); - } - - append_single_paint_path(last, &path, parent); - } - [_, _, PaintOrderKind::Markers] => { - parent.children.push(Node::Path(Box::new(path.clone()))); - - if let Some(markers_node) = marker { - parent.children.push(Node::Group(Box::new(markers_node))); - } - } - _ => parent.children.push(Node::Path(Box::new(path.clone()))), - } -} - -fn append_single_paint_path(paint_order_kind: PaintOrderKind, path: &Path, parent: &mut Group) { - match paint_order_kind { - PaintOrderKind::Fill => { - if path.fill.is_some() { - let mut fill_path = path.clone(); - fill_path.stroke = None; - fill_path.id = String::new(); - parent.children.push(Node::Path(Box::new(fill_path))); - } - } - PaintOrderKind::Stroke => { - if path.stroke.is_some() { - let mut stroke_path = path.clone(); - stroke_path.fill = None; - stroke_path.id = String::new(); - parent.children.push(Node::Path(Box::new(stroke_path))); - } - } - _ => {} - } -} - -pub fn svg_paint_order_to_usvg(order: svgtypes::PaintOrder) -> PaintOrder { - match (order.order[0], order.order[1]) { - (svgtypes::PaintOrderKind::Stroke, _) => PaintOrder::StrokeAndFill, - (svgtypes::PaintOrderKind::Markers, svgtypes::PaintOrderKind::Stroke) => { - PaintOrder::StrokeAndFill - } - _ => PaintOrder::FillAndStroke, - } -} - -impl SvgNode<'_, '_> { - pub(crate) fn resolve_transform(&self, transform_aid: AId, state: &State) -> Transform { - let mut transform: Transform = self.attribute(transform_aid).unwrap_or_default(); - let transform_origin: Option = self.attribute(AId::TransformOrigin); - - if let Some(transform_origin) = transform_origin { - let dx = convert_length( - transform_origin.x_offset, - *self, - AId::Width, - Units::UserSpaceOnUse, - state, - ); - let dy = convert_length( - transform_origin.y_offset, - *self, - AId::Height, - Units::UserSpaceOnUse, - state, - ); - transform = Transform::default() - .pre_translate(dx, dy) - .pre_concat(transform) - .pre_translate(-dx, -dy); - } - - transform - } -} diff --git a/resvg-0.43.0/crates/usvg/src/parser/filter.rs b/resvg-0.43.0/crates/usvg/src/parser/filter.rs deleted file mode 100644 index 5dd47df..0000000 --- a/resvg-0.43.0/crates/usvg/src/parser/filter.rs +++ /dev/null @@ -1,1280 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -//! A collection of SVG filters. - -use std::collections::HashSet; -use std::str::FromStr; -use std::sync::Arc; - -use strict_num::PositiveF32; -use svgtypes::{AspectRatio, Length, LengthUnit as Unit}; - -use crate::{ - filter::{self, *}, - ApproxZeroUlps, Color, Group, Node, NonEmptyString, NonZeroF32, NonZeroRect, Opacity, Size, - Units, -}; - -use super::converter::{self, SvgColorExt}; -use super::paint_server::{convert_units, resolve_number}; -use super::svgtree::{AId, EId, FromValue, SvgNode}; -use super::OptionLog; - -impl<'a, 'input: 'a> FromValue<'a, 'input> for filter::ColorInterpolation { - fn parse(_: SvgNode, _: AId, value: &str) -> Option { - match value { - "sRGB" => Some(filter::ColorInterpolation::SRGB), - "linearRGB" => Some(filter::ColorInterpolation::LinearRGB), - _ => None, - } - } -} - -pub(crate) fn convert( - node: SvgNode, - state: &converter::State, - object_bbox: Option, - cache: &mut converter::Cache, -) -> Result>, ()> { - let value = match node.attribute::<&str>(AId::Filter) { - Some(v) => v, - None => return Ok(Vec::new()), - }; - - let mut has_invalid_urls = false; - let mut filters = Vec::new(); - - let create_base_filter_func = - |kind, filters: &mut Vec>, cache: &mut converter::Cache| { - // Filter functions, unlike `filter` elements, do not have a filter region. - // We're currently do not support an unlimited region, so we simply use a fairly large one. - // This if far from ideal, but good for now. - // TODO: Should be fixed eventually. - let mut rect = match kind { - Kind::DropShadow(_) | Kind::GaussianBlur(_) => { - NonZeroRect::from_xywh(-0.5, -0.5, 2.0, 2.0).unwrap() - } - _ => NonZeroRect::from_xywh(-0.1, -0.1, 1.2, 1.2).unwrap(), - }; - - let object_bbox = match object_bbox { - Some(v) => v, - None => { - log::warn!( - "Filter '{}' has an invalid region. Skipped.", - node.element_id() - ); - return; - } - }; - - rect = rect.bbox_transform(object_bbox); - - filters.push(Arc::new(Filter { - id: cache.gen_filter_id(), - rect, - primitives: vec![Primitive { - rect: rect, - // Unlike `filter` elements, filter functions use sRGB colors by default. - color_interpolation: ColorInterpolation::SRGB, - result: "result".to_string(), - kind, - }], - })); - }; - - for func in svgtypes::FilterValueListParser::from(value) { - let func = match func { - Ok(v) => v, - Err(e) => { - // Skip the whole attribute list on error. - log::warn!("Failed to parse a filter value cause {}. Skipping.", e); - return Ok(Vec::new()); - } - }; - - match func { - svgtypes::FilterValue::Blur(std_dev) => create_base_filter_func( - convert_blur_function(node, std_dev, state), - &mut filters, - cache, - ), - svgtypes::FilterValue::DropShadow { - color, - dx, - dy, - std_dev, - } => create_base_filter_func( - convert_drop_shadow_function(node, color, dx, dy, std_dev, state), - &mut filters, - cache, - ), - svgtypes::FilterValue::Brightness(amount) => { - create_base_filter_func(convert_brightness_function(amount), &mut filters, cache) - } - svgtypes::FilterValue::Contrast(amount) => { - create_base_filter_func(convert_contrast_function(amount), &mut filters, cache) - } - svgtypes::FilterValue::Grayscale(amount) => { - create_base_filter_func(convert_grayscale_function(amount), &mut filters, cache) - } - svgtypes::FilterValue::HueRotate(angle) => { - create_base_filter_func(convert_hue_rotate_function(angle), &mut filters, cache) - } - svgtypes::FilterValue::Invert(amount) => { - create_base_filter_func(convert_invert_function(amount), &mut filters, cache) - } - svgtypes::FilterValue::Opacity(amount) => { - create_base_filter_func(convert_opacity_function(amount), &mut filters, cache) - } - svgtypes::FilterValue::Sepia(amount) => { - create_base_filter_func(convert_sepia_function(amount), &mut filters, cache) - } - svgtypes::FilterValue::Saturate(amount) => { - create_base_filter_func(convert_saturate_function(amount), &mut filters, cache) - } - svgtypes::FilterValue::Url(url) => { - if let Some(link) = node.document().element_by_id(url) { - if let Ok(res) = convert_url(link, state, object_bbox, cache) { - if let Some(f) = res { - filters.push(f); - } - } else { - has_invalid_urls = true; - } - } else { - has_invalid_urls = true; - } - } - } - } - - // If a `filter` attribute had urls pointing to a missing elements - // and there are no valid filters at all - this is an error. - // - // Note that an invalid url is not an error in general. - if filters.is_empty() && has_invalid_urls { - return Err(()); - } - - Ok(filters) -} - -fn convert_url( - node: SvgNode, - state: &converter::State, - object_bbox: Option, - cache: &mut converter::Cache, -) -> Result>, ()> { - let units = convert_units(node, AId::FilterUnits, Units::ObjectBoundingBox); - let primitive_units = convert_units(node, AId::PrimitiveUnits, Units::UserSpaceOnUse); - - // Check if this element was already converted. - // - // Only `userSpaceOnUse` clipPaths can be shared, - // because `objectBoundingBox` one will be converted into user one - // and will become node-specific. - let cacheable = units == Units::UserSpaceOnUse && primitive_units == Units::UserSpaceOnUse; - if cacheable { - if let Some(filter) = cache.filters.get(node.element_id()) { - return Ok(Some(filter.clone())); - } - } - - let rect = NonZeroRect::from_xywh( - resolve_number( - node, - AId::X, - units, - state, - Length::new(-10.0, Unit::Percent), - ), - resolve_number( - node, - AId::Y, - units, - state, - Length::new(-10.0, Unit::Percent), - ), - resolve_number( - node, - AId::Width, - units, - state, - Length::new(120.0, Unit::Percent), - ), - resolve_number( - node, - AId::Height, - units, - state, - Length::new(120.0, Unit::Percent), - ), - ); - - let mut rect = rect - .log_none(|| { - log::warn!( - "Filter '{}' has an invalid region. Skipped.", - node.element_id() - ) - }) - .ok_or(())?; - - if units == Units::ObjectBoundingBox { - if let Some(object_bbox) = object_bbox { - rect = rect.bbox_transform(object_bbox); - } else { - log::warn!("Filters on zero-sized shapes are not allowed."); - return Err(()); - } - } - - let node_with_primitives = match find_filter_with_primitives(node) { - Some(v) => v, - None => return Err(()), - }; - let primitives = collect_children( - &node_with_primitives, - primitive_units, - state, - object_bbox, - rect, - cache, - ); - if primitives.is_empty() { - return Err(()); - } - - let mut id = NonEmptyString::new(node.element_id().to_string()).ok_or(())?; - // Generate ID only when we're parsing `objectBoundingBox` filter for the second time. - if !cacheable && cache.filters.contains_key(id.get()) { - id = cache.gen_filter_id(); - } - let id_copy = id.get().to_string(); - - let filter = Arc::new(Filter { - id, - rect, - primitives, - }); - - cache.filters.insert(id_copy, filter.clone()); - - Ok(Some(filter)) -} - -fn find_filter_with_primitives<'a>(node: SvgNode<'a, 'a>) -> Option> { - for link in node.href_iter() { - if link.tag_name() != Some(EId::Filter) { - log::warn!( - "Filter '{}' cannot reference '{}' via 'xlink:href'.", - node.element_id(), - link.tag_name().unwrap() - ); - return None; - } - - if link.has_children() { - return Some(link); - } - } - - None -} - -struct FilterResults { - names: HashSet, - idx: usize, -} - -fn collect_children( - filter: &SvgNode, - units: Units, - state: &converter::State, - object_bbox: Option, - filter_region: NonZeroRect, - cache: &mut converter::Cache, -) -> Vec { - let mut primitives = Vec::new(); - - let mut results = FilterResults { - names: HashSet::new(), - idx: 1, - }; - - let scale = if units == Units::ObjectBoundingBox { - if let Some(object_bbox) = object_bbox { - object_bbox.size() - } else { - // No need to warn. Already checked. - return Vec::new(); - } - } else { - Size::from_wh(1.0, 1.0).unwrap() - }; - - for child in filter.children() { - let tag_name = match child.tag_name() { - Some(v) => v, - None => continue, - }; - - let filter_subregion = match resolve_primitive_region( - child, - tag_name, - units, - state, - object_bbox, - filter_region, - ) { - Some(v) => v, - None => break, - }; - - let kind = - match tag_name { - EId::FeDropShadow => convert_drop_shadow(child, scale, &primitives), - EId::FeGaussianBlur => convert_gaussian_blur(child, scale, &primitives), - EId::FeOffset => convert_offset(child, scale, &primitives), - EId::FeBlend => convert_blend(child, &primitives), - EId::FeFlood => convert_flood(child), - EId::FeComposite => convert_composite(child, &primitives), - EId::FeMerge => convert_merge(child, &primitives), - EId::FeTile => convert_tile(child, &primitives), - EId::FeImage => convert_image(child, filter_subregion, state, cache), - EId::FeComponentTransfer => convert_component_transfer(child, &primitives), - EId::FeColorMatrix => convert_color_matrix(child, &primitives), - EId::FeConvolveMatrix => convert_convolve_matrix(child, &primitives) - .unwrap_or_else(create_dummy_primitive), - EId::FeMorphology => convert_morphology(child, scale, &primitives), - EId::FeDisplacementMap => convert_displacement_map(child, scale, &primitives), - EId::FeTurbulence => convert_turbulence(child), - EId::FeDiffuseLighting => convert_diffuse_lighting(child, &primitives) - .unwrap_or_else(create_dummy_primitive), - EId::FeSpecularLighting => convert_specular_lighting(child, &primitives) - .unwrap_or_else(create_dummy_primitive), - tag_name => { - log::warn!("'{}' is not a valid filter primitive. Skipped.", tag_name); - continue; - } - }; - - let color_interpolation = child - .find_attribute(AId::ColorInterpolationFilters) - .unwrap_or_default(); - - primitives.push(Primitive { - rect: filter_subregion, - color_interpolation, - result: gen_result(child, &mut results), - kind, - }); - } - - // TODO: remove primitives which results are not used - - primitives -} - -// TODO: rewrite/simplify/explain/whatever -fn resolve_primitive_region( - fe: SvgNode, - kind: EId, - units: Units, - state: &converter::State, - bbox: Option, - filter_region: NonZeroRect, -) -> Option { - let x = fe.try_convert_length(AId::X, units, state); - let y = fe.try_convert_length(AId::Y, units, state); - let width = fe.try_convert_length(AId::Width, units, state); - let height = fe.try_convert_length(AId::Height, units, state); - - let region = match kind { - EId::FeFlood | EId::FeImage => { - // `feImage` uses the object bbox. - if units == Units::ObjectBoundingBox { - let bbox = bbox?; - - // TODO: wrong - // let ts_bbox = tiny_skia::Rect::new(ts.e, ts.f, ts.a, ts.d).unwrap(); - - let r = NonZeroRect::from_xywh( - x.unwrap_or(0.0), - y.unwrap_or(0.0), - width.unwrap_or(1.0), - height.unwrap_or(1.0), - )?; - - return Some(r.bbox_transform(bbox)); - } else { - filter_region - } - } - _ => filter_region, - }; - - // TODO: Wrong! Does not account rotate and skew. - if units == Units::ObjectBoundingBox { - let subregion_bbox = NonZeroRect::from_xywh( - x.unwrap_or(0.0), - y.unwrap_or(0.0), - width.unwrap_or(1.0), - height.unwrap_or(1.0), - )?; - - Some(region.bbox_transform(subregion_bbox)) - } else { - NonZeroRect::from_xywh( - x.unwrap_or(region.x()), - y.unwrap_or(region.y()), - width.unwrap_or(region.width()), - height.unwrap_or(region.height()), - ) - } -} - -// A malformed filter primitive usually should produce a transparent image. -// But since `FilterKind` structs are designed to always be valid, -// we are using `FeFlood` as fallback. -#[inline(never)] -pub(crate) fn create_dummy_primitive() -> Kind { - Kind::Flood(Flood { - color: Color::black(), - opacity: Opacity::ZERO, - }) -} - -#[inline(never)] -fn resolve_input(node: SvgNode, aid: AId, primitives: &[Primitive]) -> Input { - match node.attribute(aid) { - Some(s) => { - let input = parse_in(s); - - // If `in` references an unknown `result` than fallback - // to previous result or `SourceGraphic`. - if let Input::Reference(ref name) = input { - if !primitives.iter().any(|p| p.result == *name) { - return if let Some(prev) = primitives.last() { - Input::Reference(prev.result.clone()) - } else { - Input::SourceGraphic - }; - } - } - - input - } - None => { - if let Some(prev) = primitives.last() { - // If `in` is not set and this is not the first primitive - // than the input is a result of the previous primitive. - Input::Reference(prev.result.clone()) - } else { - // If `in` is not set and this is the first primitive - // than the input is `SourceGraphic`. - Input::SourceGraphic - } - } - } -} - -fn parse_in(s: &str) -> Input { - match s { - "SourceGraphic" => Input::SourceGraphic, - "SourceAlpha" => Input::SourceAlpha, - "BackgroundImage" | "BackgroundAlpha" | "FillPaint" | "StrokePaint" => { - log::warn!("{} filter input isn't supported and not planed.", s); - Input::SourceGraphic - } - _ => Input::Reference(s.to_string()), - } -} - -fn gen_result(node: SvgNode, results: &mut FilterResults) -> String { - match node.attribute::<&str>(AId::Result) { - Some(s) => { - // Remember predefined result. - results.names.insert(s.to_string()); - results.idx += 1; - - s.to_string() - } - None => { - // Generate an unique name for `result`. - loop { - let name = format!("result{}", results.idx); - results.idx += 1; - - if !results.names.contains(&name) { - return name; - } - } - } - } -} - -fn convert_blend(fe: SvgNode, primitives: &[Primitive]) -> Kind { - let mode = fe.attribute(AId::Mode).unwrap_or_default(); - let input1 = resolve_input(fe, AId::In, primitives); - let input2 = resolve_input(fe, AId::In2, primitives); - Kind::Blend(Blend { - mode, - input1, - input2, - }) -} - -fn convert_color_matrix(fe: SvgNode, primitives: &[Primitive]) -> Kind { - let kind = convert_color_matrix_kind(fe).unwrap_or_default(); - Kind::ColorMatrix(ColorMatrix { - input: resolve_input(fe, AId::In, primitives), - kind, - }) -} - -fn convert_color_matrix_kind(fe: SvgNode) -> Option { - match fe.attribute(AId::Type) { - Some("saturate") => { - if let Some(list) = fe.attribute::>(AId::Values) { - if !list.is_empty() { - let n = crate::f32_bound(0.0, list[0], 1.0); - return Some(ColorMatrixKind::Saturate(PositiveF32::new(n).unwrap())); - } else { - return Some(ColorMatrixKind::Saturate(PositiveF32::new(1.0).unwrap())); - } - } - } - Some("hueRotate") => { - if let Some(list) = fe.attribute::>(AId::Values) { - if !list.is_empty() { - return Some(ColorMatrixKind::HueRotate(list[0])); - } else { - return Some(ColorMatrixKind::HueRotate(0.0)); - } - } - } - Some("luminanceToAlpha") => { - return Some(ColorMatrixKind::LuminanceToAlpha); - } - _ => { - // Fallback to `matrix`. - if let Some(list) = fe.attribute::>(AId::Values) { - if list.len() == 20 { - return Some(ColorMatrixKind::Matrix(list)); - } - } - } - } - - None -} - -fn convert_component_transfer(fe: SvgNode, primitives: &[Primitive]) -> Kind { - let mut kind = ComponentTransfer { - input: resolve_input(fe, AId::In, primitives), - func_r: TransferFunction::Identity, - func_g: TransferFunction::Identity, - func_b: TransferFunction::Identity, - func_a: TransferFunction::Identity, - }; - - for child in fe.children().filter(|n| n.is_element()) { - if let Some(func) = convert_transfer_function(child) { - match child.tag_name().unwrap() { - EId::FeFuncR => kind.func_r = func, - EId::FeFuncG => kind.func_g = func, - EId::FeFuncB => kind.func_b = func, - EId::FeFuncA => kind.func_a = func, - _ => {} - } - } - } - - Kind::ComponentTransfer(kind) -} - -fn convert_transfer_function(node: SvgNode) -> Option { - match node.attribute(AId::Type)? { - "identity" => Some(TransferFunction::Identity), - "table" => match node.attribute::>(AId::TableValues) { - Some(values) => Some(TransferFunction::Table(values)), - None => Some(TransferFunction::Table(Vec::new())), - }, - "discrete" => match node.attribute::>(AId::TableValues) { - Some(values) => Some(TransferFunction::Discrete(values)), - None => Some(TransferFunction::Discrete(Vec::new())), - }, - "linear" => Some(TransferFunction::Linear { - slope: node.attribute(AId::Slope).unwrap_or(1.0), - intercept: node.attribute(AId::Intercept).unwrap_or(0.0), - }), - "gamma" => Some(TransferFunction::Gamma { - amplitude: node.attribute(AId::Amplitude).unwrap_or(1.0), - exponent: node.attribute(AId::Exponent).unwrap_or(1.0), - offset: node.attribute(AId::Offset).unwrap_or(0.0), - }), - _ => None, - } -} - -fn convert_composite(fe: SvgNode, primitives: &[Primitive]) -> Kind { - let operator = match fe.attribute(AId::Operator).unwrap_or("over") { - "in" => CompositeOperator::In, - "out" => CompositeOperator::Out, - "atop" => CompositeOperator::Atop, - "xor" => CompositeOperator::Xor, - "arithmetic" => CompositeOperator::Arithmetic { - k1: fe.attribute(AId::K1).unwrap_or(0.0), - k2: fe.attribute(AId::K2).unwrap_or(0.0), - k3: fe.attribute(AId::K3).unwrap_or(0.0), - k4: fe.attribute(AId::K4).unwrap_or(0.0), - }, - _ => CompositeOperator::Over, - }; - - let input1 = resolve_input(fe, AId::In, primitives); - let input2 = resolve_input(fe, AId::In2, primitives); - - Kind::Composite(Composite { - operator, - input1, - input2, - }) -} - -fn convert_convolve_matrix(fe: SvgNode, primitives: &[Primitive]) -> Option { - fn parse_target(target: Option, order: u32) -> Option { - let default_target = (order as f32 / 2.0).floor() as u32; - let target = target.unwrap_or(default_target as f32) as i32; - if target < 0 || target >= order as i32 { - None - } else { - Some(target as u32) - } - } - - let mut order_x = 3; - let mut order_y = 3; - if let Some(value) = fe.attribute::<&str>(AId::Order) { - let mut s = svgtypes::NumberListParser::from(value); - let x = s.next().and_then(|a| a.ok()).map(|n| n as i32).unwrap_or(3); - let y = s.next().and_then(|a| a.ok()).map(|n| n as i32).unwrap_or(x); - if x > 0 && y > 0 { - order_x = x as u32; - order_y = y as u32; - } - } - - let mut matrix = Vec::new(); - if let Some(list) = fe.attribute::>(AId::KernelMatrix) { - if list.len() == (order_x * order_y) as usize { - matrix = list; - } - } - - let mut kernel_sum: f32 = matrix.iter().sum(); - // Round up to prevent float precision issues. - kernel_sum = (kernel_sum * 1_000_000.0).round() / 1_000_000.0; - if kernel_sum.approx_zero_ulps(4) { - kernel_sum = 1.0; - } - - let divisor = fe.attribute(AId::Divisor).unwrap_or(kernel_sum); - if divisor.approx_zero_ulps(4) { - return None; - } - - let bias = fe.attribute(AId::Bias).unwrap_or(0.0); - - let target_x = parse_target(fe.attribute(AId::TargetX), order_x)?; - let target_y = parse_target(fe.attribute(AId::TargetY), order_y)?; - - let kernel_matrix = ConvolveMatrixData::new(target_x, target_y, order_x, order_y, matrix)?; - - let edge_mode = match fe.attribute(AId::EdgeMode).unwrap_or("duplicate") { - "none" => EdgeMode::None, - "wrap" => EdgeMode::Wrap, - _ => EdgeMode::Duplicate, - }; - - let preserve_alpha = fe.attribute(AId::PreserveAlpha).unwrap_or("false") == "true"; - - Some(Kind::ConvolveMatrix(ConvolveMatrix { - input: resolve_input(fe, AId::In, primitives), - matrix: kernel_matrix, - divisor: NonZeroF32::new(divisor).unwrap(), - bias, - edge_mode, - preserve_alpha, - })) -} - -fn convert_displacement_map(fe: SvgNode, scale: Size, primitives: &[Primitive]) -> Kind { - let parse_channel = |aid| match fe.attribute(aid).unwrap_or("A") { - "R" => ColorChannel::R, - "G" => ColorChannel::G, - "B" => ColorChannel::B, - _ => ColorChannel::A, - }; - - // TODO: should probably split scale to scale_x and scale_y, - // but resvg doesn't support displacement map anyway... - let scale = (scale.width() + scale.height()) / 2.0; - - Kind::DisplacementMap(DisplacementMap { - input1: resolve_input(fe, AId::In, primitives), - input2: resolve_input(fe, AId::In2, primitives), - scale: fe.attribute(AId::Scale).unwrap_or(0.0) * scale, - x_channel_selector: parse_channel(AId::XChannelSelector), - y_channel_selector: parse_channel(AId::YChannelSelector), - }) -} - -fn convert_drop_shadow(fe: SvgNode, scale: Size, primitives: &[Primitive]) -> Kind { - let (std_dev_x, std_dev_y) = convert_std_dev_attr(fe, scale, "2 2"); - - let (color, opacity) = fe - .attribute(AId::FloodColor) - .unwrap_or_else(svgtypes::Color::black) - .split_alpha(); - - let flood_opacity = fe - .attribute::(AId::FloodOpacity) - .unwrap_or(Opacity::ONE); - - Kind::DropShadow(DropShadow { - input: resolve_input(fe, AId::In, primitives), - dx: fe.attribute(AId::Dx).unwrap_or(2.0) * scale.width(), - dy: fe.attribute(AId::Dy).unwrap_or(2.0) * scale.height(), - std_dev_x, - std_dev_y, - color, - opacity: opacity * flood_opacity, - }) -} - -fn convert_flood(fe: SvgNode) -> Kind { - let (color, opacity) = fe - .attribute(AId::FloodColor) - .unwrap_or_else(svgtypes::Color::black) - .split_alpha(); - - let flood_opacity = fe - .attribute::(AId::FloodOpacity) - .unwrap_or(Opacity::ONE); - - Kind::Flood(Flood { - color, - opacity: opacity * flood_opacity, - }) -} - -fn convert_gaussian_blur(fe: SvgNode, scale: Size, primitives: &[Primitive]) -> Kind { - let (std_dev_x, std_dev_y) = convert_std_dev_attr(fe, scale, "0 0"); - Kind::GaussianBlur(GaussianBlur { - input: resolve_input(fe, AId::In, primitives), - std_dev_x, - std_dev_y, - }) -} - -fn convert_std_dev_attr(fe: SvgNode, scale: Size, default: &str) -> (PositiveF32, PositiveF32) { - let text = fe.attribute(AId::StdDeviation).unwrap_or(default); - let mut parser = svgtypes::NumberListParser::from(text); - - let n1 = parser.next().and_then(|n| n.ok()); - let n2 = parser.next().and_then(|n| n.ok()); - // `stdDeviation` must have no more than two values. - // Otherwise we should fallback to `0 0`. - let n3 = parser.next().and_then(|n| n.ok()); - - let (std_dev_x, std_dev_y) = match (n1, n2, n3) { - (Some(n1), Some(n2), None) => (n1, n2), - (Some(n1), None, None) => (n1, n1), - _ => (0.0, 0.0), - }; - - let std_dev_x = (std_dev_x as f32) * scale.width(); - let std_dev_y = (std_dev_y as f32) * scale.height(); - - let std_dev_x = PositiveF32::new(std_dev_x as f32).unwrap_or(PositiveF32::ZERO); - let std_dev_y = PositiveF32::new(std_dev_y as f32).unwrap_or(PositiveF32::ZERO); - - (std_dev_x, std_dev_y) -} - -fn convert_image( - fe: SvgNode, - filter_subregion: NonZeroRect, - state: &converter::State, - cache: &mut converter::Cache, -) -> Kind { - match convert_image_inner(fe, filter_subregion, state, cache) { - Some(kind) => kind, - None => create_dummy_primitive(), - } -} - -fn convert_image_inner( - fe: SvgNode, - filter_subregion: NonZeroRect, - state: &converter::State, - cache: &mut converter::Cache, -) -> Option { - let rendering_mode = fe - .find_attribute(AId::ImageRendering) - .unwrap_or(state.opt.image_rendering); - - if let Some(node) = fe.try_attribute::(AId::Href) { - let mut state = state.clone(); - state.fe_image_link = true; - let mut root = Group::empty(); - super::converter::convert_element(node, &state, cache, &mut root); - return if root.has_children() { - root.calculate_bounding_boxes(); - // Transfer node id from group's child to the group itself if needed. - if let Some(Node::Group(ref mut g)) = root.children.first_mut() { - if let Some(child2) = g.children.first_mut() { - g.id = child2.id().to_string(); - match child2 { - Node::Group(ref mut g2) => g2.id.clear(), - Node::Path(ref mut path) => path.id.clear(), - Node::Image(ref mut image) => image.id.clear(), - Node::Text(ref mut text) => text.id.clear(), - } - } - } - - Some(Kind::Image(Image { root })) - } else { - None - }; - } - - let href = fe.try_attribute(AId::Href).log_none(|| { - log::warn!("The 'feImage' element lacks the 'xlink:href' attribute. Skipped.") - })?; - let img_data = super::image::get_href_data(href, state)?; - let actual_size = img_data.actual_size()?; - - let aspect: AspectRatio = fe.attribute(AId::PreserveAspectRatio).unwrap_or_default(); - - let mut root = Group::empty(); - super::image::convert_inner( - img_data, - cache.gen_image_id().take(), - true, - rendering_mode, - aspect, - actual_size, - filter_subregion.translate_to(0.0, 0.0)?, - cache, - &mut root, - ); - root.calculate_bounding_boxes(); - - Some(Kind::Image(Image { root })) -} - -fn convert_diffuse_lighting(fe: SvgNode, primitives: &[Primitive]) -> Option { - let light_source = convert_light_source(fe)?; - Some(Kind::DiffuseLighting(DiffuseLighting { - input: resolve_input(fe, AId::In, primitives), - surface_scale: fe.attribute(AId::SurfaceScale).unwrap_or(1.0), - diffuse_constant: fe.attribute(AId::DiffuseConstant).unwrap_or(1.0), - lighting_color: convert_lighting_color(fe), - light_source, - })) -} - -fn convert_specular_lighting(fe: SvgNode, primitives: &[Primitive]) -> Option { - let light_source = convert_light_source(fe)?; - - let specular_exponent = fe.attribute(AId::SpecularExponent).unwrap_or(1.0); - if !(1.0..=128.0).contains(&specular_exponent) { - // When exponent is out of range, the whole filter primitive should be ignored. - return None; - } - - let specular_exponent = crate::f32_bound(1.0, specular_exponent, 128.0); - - Some(Kind::SpecularLighting(SpecularLighting { - input: resolve_input(fe, AId::In, primitives), - surface_scale: fe.attribute(AId::SurfaceScale).unwrap_or(1.0), - specular_constant: fe.attribute(AId::SpecularConstant).unwrap_or(1.0), - specular_exponent, - lighting_color: convert_lighting_color(fe), - light_source, - })) -} - -#[inline(never)] -fn convert_lighting_color(node: SvgNode) -> Color { - // Color's alpha doesn't affect lighting-color. Simply skip it. - match node.attribute(AId::LightingColor) { - Some("currentColor") => { - node.find_attribute(AId::Color) - // Yes, a missing `currentColor` resolves to black and not white. - .unwrap_or(svgtypes::Color::black()) - .split_alpha() - .0 - } - Some(value) => { - if let Ok(c) = svgtypes::Color::from_str(value) { - c.split_alpha().0 - } else { - log::warn!("Failed to parse lighting-color value: '{}'.", value); - Color::white() - } - } - _ => Color::white(), - } -} - -#[inline(never)] -fn convert_light_source(parent: SvgNode) -> Option { - let child = parent.children().find(|n| { - matches!( - n.tag_name(), - Some(EId::FeDistantLight) | Some(EId::FePointLight) | Some(EId::FeSpotLight) - ) - })?; - - match child.tag_name() { - Some(EId::FeDistantLight) => Some(LightSource::DistantLight(DistantLight { - azimuth: child.attribute(AId::Azimuth).unwrap_or(0.0), - elevation: child.attribute(AId::Elevation).unwrap_or(0.0), - })), - Some(EId::FePointLight) => Some(LightSource::PointLight(PointLight { - x: child.attribute(AId::X).unwrap_or(0.0), - y: child.attribute(AId::Y).unwrap_or(0.0), - z: child.attribute(AId::Z).unwrap_or(0.0), - })), - Some(EId::FeSpotLight) => { - let specular_exponent = child.attribute(AId::SpecularExponent).unwrap_or(1.0); - let specular_exponent = PositiveF32::new(specular_exponent) - .unwrap_or_else(|| PositiveF32::new(1.0).unwrap()); - - Some(LightSource::SpotLight(SpotLight { - x: child.attribute(AId::X).unwrap_or(0.0), - y: child.attribute(AId::Y).unwrap_or(0.0), - z: child.attribute(AId::Z).unwrap_or(0.0), - points_at_x: child.attribute(AId::PointsAtX).unwrap_or(0.0), - points_at_y: child.attribute(AId::PointsAtY).unwrap_or(0.0), - points_at_z: child.attribute(AId::PointsAtZ).unwrap_or(0.0), - specular_exponent, - limiting_cone_angle: child.attribute(AId::LimitingConeAngle), - })) - } - _ => None, - } -} - -fn convert_merge(fe: SvgNode, primitives: &[Primitive]) -> Kind { - let mut inputs = Vec::new(); - for child in fe.children() { - inputs.push(resolve_input(child, AId::In, primitives)); - } - - Kind::Merge(Merge { inputs }) -} - -fn convert_morphology(fe: SvgNode, scale: Size, primitives: &[Primitive]) -> Kind { - let operator = match fe.attribute(AId::Operator).unwrap_or("erode") { - "dilate" => MorphologyOperator::Dilate, - _ => MorphologyOperator::Erode, - }; - - let mut radius_x = PositiveF32::new(scale.width()).unwrap(); - let mut radius_y = PositiveF32::new(scale.height()).unwrap(); - if let Some(list) = fe.attribute::>(AId::Radius) { - let mut rx = 0.0; - let mut ry = 0.0; - if list.len() == 2 { - rx = list[0]; - ry = list[1]; - } else if list.len() == 1 { - rx = list[0]; - ry = list[0]; // The same as `rx`. - } - - if rx.approx_zero_ulps(4) && ry.approx_zero_ulps(4) { - rx = 1.0; - ry = 1.0; - } - - // If only one of the values is zero, reset it to 1.0 - // This is not specified in the spec, but this is how Chrome and Safari work. - if rx.approx_zero_ulps(4) && !ry.approx_zero_ulps(4) { - rx = 1.0; - } - if !rx.approx_zero_ulps(4) && ry.approx_zero_ulps(4) { - ry = 1.0; - } - - // Both values must be positive. - if rx.is_sign_positive() && ry.is_sign_positive() { - radius_x = PositiveF32::new(rx * scale.width()).unwrap(); - radius_y = PositiveF32::new(ry * scale.height()).unwrap(); - } - } - - Kind::Morphology(Morphology { - input: resolve_input(fe, AId::In, primitives), - operator, - radius_x, - radius_y, - }) -} - -fn convert_offset(fe: SvgNode, scale: Size, primitives: &[Primitive]) -> Kind { - Kind::Offset(Offset { - input: resolve_input(fe, AId::In, primitives), - dx: fe.attribute(AId::Dx).unwrap_or(0.0) * scale.width(), - dy: fe.attribute(AId::Dy).unwrap_or(0.0) * scale.height(), - }) -} - -fn convert_tile(fe: SvgNode, primitives: &[Primitive]) -> Kind { - Kind::Tile(Tile { - input: resolve_input(fe, AId::In, primitives), - }) -} - -fn convert_turbulence(fe: SvgNode) -> Kind { - let mut base_frequency_x = PositiveF32::ZERO; - let mut base_frequency_y = PositiveF32::ZERO; - if let Some(list) = fe.attribute::>(AId::BaseFrequency) { - let mut x = 0.0; - let mut y = 0.0; - if list.len() == 2 { - x = list[0]; - y = list[1]; - } else if list.len() == 1 { - x = list[0]; - y = list[0]; // The same as `x`. - } - - if x.is_sign_positive() && y.is_sign_positive() { - base_frequency_x = PositiveF32::new(x).unwrap(); - base_frequency_y = PositiveF32::new(y).unwrap(); - } - } - - let mut num_octaves = fe.attribute(AId::NumOctaves).unwrap_or(1.0); - if num_octaves.is_sign_negative() { - num_octaves = 0.0; - } - - let kind = match fe.attribute(AId::Type).unwrap_or("turbulence") { - "fractalNoise" => TurbulenceKind::FractalNoise, - _ => TurbulenceKind::Turbulence, - }; - - Kind::Turbulence(Turbulence { - base_frequency_x, - base_frequency_y, - num_octaves: num_octaves.round() as u32, - seed: fe.attribute::(AId::Seed).unwrap_or(0.0).trunc() as i32, - stitch_tiles: fe.attribute(AId::StitchTiles) == Some("stitch"), - kind, - }) -} - -#[inline(never)] -fn convert_grayscale_function(amount: f64) -> Kind { - let amount = amount.min(1.0) as f32; - Kind::ColorMatrix(ColorMatrix { - input: Input::SourceGraphic, - kind: ColorMatrixKind::Matrix(vec![ - (0.2126 + 0.7874 * (1.0 - amount)), - (0.7152 - 0.7152 * (1.0 - amount)), - (0.0722 - 0.0722 * (1.0 - amount)), - 0.0, - 0.0, - (0.2126 - 0.2126 * (1.0 - amount)), - (0.7152 + 0.2848 * (1.0 - amount)), - (0.0722 - 0.0722 * (1.0 - amount)), - 0.0, - 0.0, - (0.2126 - 0.2126 * (1.0 - amount)), - (0.7152 - 0.7152 * (1.0 - amount)), - (0.0722 + 0.9278 * (1.0 - amount)), - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 1.0, - 0.0, - ]), - }) -} - -#[inline(never)] -fn convert_sepia_function(amount: f64) -> Kind { - let amount = amount.min(1.0) as f32; - Kind::ColorMatrix(ColorMatrix { - input: Input::SourceGraphic, - kind: ColorMatrixKind::Matrix(vec![ - (0.393 + 0.607 * (1.0 - amount)), - (0.769 - 0.769 * (1.0 - amount)), - (0.189 - 0.189 * (1.0 - amount)), - 0.0, - 0.0, - (0.349 - 0.349 * (1.0 - amount)), - (0.686 + 0.314 * (1.0 - amount)), - (0.168 - 0.168 * (1.0 - amount)), - 0.0, - 0.0, - (0.272 - 0.272 * (1.0 - amount)), - (0.534 - 0.534 * (1.0 - amount)), - (0.131 + 0.869 * (1.0 - amount)), - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 1.0, - 0.0, - ]), - }) -} - -#[inline(never)] -fn convert_saturate_function(amount: f64) -> Kind { - let amount = PositiveF32::new(amount as f32).unwrap_or(PositiveF32::ZERO); - Kind::ColorMatrix(ColorMatrix { - input: Input::SourceGraphic, - kind: ColorMatrixKind::Saturate(amount), - }) -} - -#[inline(never)] -fn convert_hue_rotate_function(amount: svgtypes::Angle) -> Kind { - Kind::ColorMatrix(ColorMatrix { - input: Input::SourceGraphic, - kind: ColorMatrixKind::HueRotate(amount.to_degrees() as f32), - }) -} - -#[inline(never)] -fn convert_invert_function(amount: f64) -> Kind { - let amount = amount.min(1.0) as f32; - Kind::ComponentTransfer(ComponentTransfer { - input: Input::SourceGraphic, - func_r: TransferFunction::Table(vec![amount, 1.0 - amount]), - func_g: TransferFunction::Table(vec![amount, 1.0 - amount]), - func_b: TransferFunction::Table(vec![amount, 1.0 - amount]), - func_a: TransferFunction::Identity, - }) -} - -#[inline(never)] -fn convert_opacity_function(amount: f64) -> Kind { - let amount = amount.min(1.0) as f32; - Kind::ComponentTransfer(ComponentTransfer { - input: Input::SourceGraphic, - func_r: TransferFunction::Identity, - func_g: TransferFunction::Identity, - func_b: TransferFunction::Identity, - func_a: TransferFunction::Table(vec![0.0, amount]), - }) -} - -#[inline(never)] -fn convert_brightness_function(amount: f64) -> Kind { - let amount = amount as f32; - Kind::ComponentTransfer(ComponentTransfer { - input: Input::SourceGraphic, - func_r: TransferFunction::Linear { - slope: amount, - intercept: 0.0, - }, - func_g: TransferFunction::Linear { - slope: amount, - intercept: 0.0, - }, - func_b: TransferFunction::Linear { - slope: amount, - intercept: 0.0, - }, - func_a: TransferFunction::Identity, - }) -} - -#[inline(never)] -fn convert_contrast_function(amount: f64) -> Kind { - let amount = amount as f32; - Kind::ComponentTransfer(ComponentTransfer { - input: Input::SourceGraphic, - func_r: TransferFunction::Linear { - slope: amount, - intercept: -(0.5 * amount) + 0.5, - }, - func_g: TransferFunction::Linear { - slope: amount, - intercept: -(0.5 * amount) + 0.5, - }, - func_b: TransferFunction::Linear { - slope: amount, - intercept: -(0.5 * amount) + 0.5, - }, - func_a: TransferFunction::Identity, - }) -} - -#[inline(never)] -fn convert_blur_function(node: SvgNode, std_dev: Length, state: &converter::State) -> Kind { - let std_dev = PositiveF32::new(super::units::convert_user_length( - std_dev, - node, - AId::Dx, - state, - )) - .unwrap_or(PositiveF32::ZERO); - Kind::GaussianBlur(GaussianBlur { - input: Input::SourceGraphic, - std_dev_x: std_dev, - std_dev_y: std_dev, - }) -} - -#[inline(never)] -fn convert_drop_shadow_function( - node: SvgNode, - color: Option, - dx: Length, - dy: Length, - std_dev: Length, - state: &converter::State, -) -> Kind { - let std_dev = PositiveF32::new(super::units::convert_user_length( - std_dev, - node, - AId::Dx, - state, - )) - .unwrap_or(PositiveF32::ZERO); - - let (color, opacity) = color - .unwrap_or_else(|| { - node.find_attribute(AId::Color) - .unwrap_or_else(svgtypes::Color::black) - }) - .split_alpha(); - - Kind::DropShadow(DropShadow { - input: Input::SourceGraphic, - dx: super::units::convert_user_length(dx, node, AId::Dx, state), - dy: super::units::convert_user_length(dy, node, AId::Dy, state), - std_dev_x: std_dev, - std_dev_y: std_dev, - color, - opacity, - }) -} diff --git a/resvg-0.43.0/crates/usvg/src/parser/image.rs b/resvg-0.43.0/crates/usvg/src/parser/image.rs deleted file mode 100644 index e622e20..0000000 --- a/resvg-0.43.0/crates/usvg/src/parser/image.rs +++ /dev/null @@ -1,387 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use std::sync::Arc; - -use svgtypes::{AspectRatio, Length}; - -use super::svgtree::{AId, SvgNode}; -use super::{converter, OptionLog, Options}; -use crate::{ - ClipPath, Group, Image, ImageKind, ImageRendering, Node, NonZeroRect, Path, Size, Transform, - Tree, Visibility, -}; - -/// A shorthand for [ImageHrefResolver]'s data function. -pub type ImageHrefDataResolverFn<'a> = - Box>, &Options) -> Option + Send + Sync + 'a>; - -/// A shorthand for [ImageHrefResolver]'s string function. -pub type ImageHrefStringResolverFn<'a> = - Box Option + Send + Sync + 'a>; - -/// An `xlink:href` resolver for `` elements. -/// -/// This type can be useful if you want to have an alternative `xlink:href` handling -/// to the default one. For example, you can forbid access to local files (which is allowed by default) -/// or add support for resolving actual URLs (usvg doesn't do any network requests). -pub struct ImageHrefResolver<'a> { - /// Resolver function that will be used when `xlink:href` contains a - /// [Data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs). - /// - /// A function would be called with mime, decoded base64 data and parsing options. - pub resolve_data: ImageHrefDataResolverFn<'a>, - - /// Resolver function that will be used to handle an arbitrary string in `xlink:href`. - pub resolve_string: ImageHrefStringResolverFn<'a>, -} - -impl Default for ImageHrefResolver<'_> { - fn default() -> Self { - ImageHrefResolver { - resolve_data: ImageHrefResolver::default_data_resolver(), - resolve_string: ImageHrefResolver::default_string_resolver(), - } - } -} - -impl ImageHrefResolver<'_> { - /// Creates a default - /// [Data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) - /// resolver closure. - /// - /// base64 encoded data is already decoded. - /// - /// The default implementation would try to load JPEG, PNG, GIF, WebP, SVG and SVGZ types. - /// Note that it will simply match the `mime` or data's magic. - /// The actual images would not be decoded. It's up to the renderer. - pub fn default_data_resolver() -> ImageHrefDataResolverFn<'static> { - Box::new( - move |mime: &str, data: Arc>, opts: &Options| match mime { - "image/jpg" | "image/jpeg" => Some(ImageKind::JPEG(data)), - "image/png" => Some(ImageKind::PNG(data)), - "image/gif" => Some(ImageKind::GIF(data)), - "image/webp" => Some(ImageKind::WEBP(data)), - "image/svg+xml" => load_sub_svg(&data, opts), - "text/plain" => match get_image_data_format(&data) { - Some(ImageFormat::JPEG) => Some(ImageKind::JPEG(data)), - Some(ImageFormat::PNG) => Some(ImageKind::PNG(data)), - Some(ImageFormat::GIF) => Some(ImageKind::GIF(data)), - Some(ImageFormat::WEBP) => Some(ImageKind::WEBP(data)), - _ => load_sub_svg(&data, opts), - }, - _ => None, - }, - ) - } - - /// Creates a default string resolver. - /// - /// The default implementation treats an input string as a file path and tries to open. - /// If a string is an URL or something else it would be ignored. - /// - /// Paths have to be absolute or relative to the input SVG file or relative to - /// [Options::resources_dir](crate::Options::resources_dir). - pub fn default_string_resolver() -> ImageHrefStringResolverFn<'static> { - Box::new(move |href: &str, opts: &Options| { - let path = opts.get_abs_path(std::path::Path::new(href)); - - if path.exists() { - let data = match std::fs::read(&path) { - Ok(data) => data, - Err(_) => { - log::warn!("Failed to load '{}'. Skipped.", href); - return None; - } - }; - - match get_image_file_format(&path, &data) { - Some(ImageFormat::JPEG) => Some(ImageKind::JPEG(Arc::new(data))), - Some(ImageFormat::PNG) => Some(ImageKind::PNG(Arc::new(data))), - Some(ImageFormat::GIF) => Some(ImageKind::GIF(Arc::new(data))), - Some(ImageFormat::WEBP) => Some(ImageKind::WEBP(Arc::new(data))), - Some(ImageFormat::SVG) => load_sub_svg(&data, opts), - _ => { - log::warn!("'{}' is not a PNG, JPEG, GIF, WebP or SVG(Z) image.", href); - None - } - } - } else { - log::warn!("'{}' is not a path to an image.", href); - None - } - }) - } -} - -impl std::fmt::Debug for ImageHrefResolver<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("ImageHrefResolver { .. }") - } -} - -#[derive(Clone, Copy, PartialEq, Debug)] -enum ImageFormat { - PNG, - JPEG, - GIF, - WEBP, - SVG, -} - -pub(crate) fn convert( - node: SvgNode, - state: &converter::State, - cache: &mut converter::Cache, - parent: &mut Group, -) -> Option<()> { - let href = node - .try_attribute(AId::Href) - .log_none(|| log::warn!("Image lacks the 'xlink:href' attribute. Skipped."))?; - - let kind = get_href_data(href, state)?; - - let visibility: Visibility = node.find_attribute(AId::Visibility).unwrap_or_default(); - let visible = visibility == Visibility::Visible; - - let rendering_mode = node - .find_attribute(AId::ImageRendering) - .unwrap_or(state.opt.image_rendering); - - // Nodes generated by markers must not have an ID. Otherwise we would have duplicates. - let id = if state.parent_markers.is_empty() { - node.element_id().to_string() - } else { - String::new() - }; - - let actual_size = kind.actual_size()?; - - let x = node.convert_user_length(AId::X, state, Length::zero()); - let y = node.convert_user_length(AId::Y, state, Length::zero()); - let mut width = node.convert_user_length( - AId::Width, - state, - Length::new_number(actual_size.width() as f64), - ); - let mut height = node.convert_user_length( - AId::Height, - state, - Length::new_number(actual_size.height() as f64), - ); - - match ( - node.attribute::(AId::Width), - node.attribute::(AId::Height), - ) { - (Some(_), None) => { - // Only width was defined, so we need to scale height accordingly. - height = actual_size.height() * (width / actual_size.width()); - } - (None, Some(_)) => { - // Only height was defined, so we need to scale width accordingly. - width = actual_size.width() * (height / actual_size.height()); - } - _ => {} - }; - - let aspect: AspectRatio = node.attribute(AId::PreserveAspectRatio).unwrap_or_default(); - - let rect = NonZeroRect::from_xywh(x, y, width, height); - let rect = rect.log_none(|| log::warn!("Image has an invalid size. Skipped."))?; - - convert_inner( - kind, - id, - visible, - rendering_mode, - aspect, - actual_size, - rect, - cache, - parent, - ) -} - -pub(crate) fn convert_inner( - kind: ImageKind, - id: String, - visible: bool, - rendering_mode: ImageRendering, - aspect: AspectRatio, - actual_size: Size, - rect: NonZeroRect, - cache: &mut converter::Cache, - parent: &mut Group, -) -> Option<()> { - let aligned_size = fit_view_box(actual_size, rect, aspect); - let (aligned_x, aligned_y) = crate::aligned_pos( - aspect.align, - rect.x(), - rect.y(), - rect.width() - aligned_size.width(), - rect.height() - aligned_size.height(), - ); - let view_box = aligned_size.to_non_zero_rect(aligned_x, aligned_y); - - let image_ts = Transform::from_row( - view_box.width() / actual_size.width(), - 0.0, - 0.0, - view_box.height() / actual_size.height(), - view_box.x(), - view_box.y(), - ); - - let abs_transform = parent.abs_transform.pre_concat(image_ts); - let abs_bounding_box = rect.transform(abs_transform)?; - - let mut g = Group::empty(); - g.id = id; - g.children.push(Node::Image(Box::new(Image { - id: String::new(), - visible, - size: actual_size, - rendering_mode, - kind, - abs_transform, - abs_bounding_box, - }))); - g.transform = image_ts; - g.abs_transform = abs_transform; - g.calculate_bounding_boxes(); - - if aspect.slice { - // Image slice acts like a rectangular clip. - let mut path = Path::new_simple(Arc::new(tiny_skia_path::PathBuilder::from_rect( - rect.to_rect(), - ))) - .unwrap(); - path.fill = Some(crate::Fill::default()); - - let mut clip = ClipPath::empty(cache.gen_clip_path_id()); - clip.root.children.push(Node::Path(Box::new(path))); - - // Clip path should not be affected by the image viewbox transform. - // The final structure should look like: - // - // - // - // - // - - let mut g2 = Group::empty(); - std::mem::swap(&mut g.id, &mut g2.id); - g2.abs_transform = parent.abs_transform; - g2.clip_path = Some(Arc::new(clip)); - g2.children.push(Node::Group(Box::new(g))); - g2.calculate_bounding_boxes(); - - parent.children.push(Node::Group(Box::new(g2))); - } else { - parent.children.push(Node::Group(Box::new(g))); - } - - Some(()) -} - -pub(crate) fn get_href_data(href: &str, state: &converter::State) -> Option { - if let Ok(url) = data_url::DataUrl::process(href) { - let (data, _) = url.decode_to_vec().ok()?; - - let mime = format!( - "{}/{}", - url.mime_type().type_.as_str(), - url.mime_type().subtype.as_str() - ); - - (state.opt.image_href_resolver.resolve_data)(&mime, Arc::new(data), state.opt) - } else { - (state.opt.image_href_resolver.resolve_string)(href, state.opt) - } -} - -/// Checks that file has a PNG, a GIF, a JPEG or a WebP magic bytes. -/// Or an SVG(Z) extension. -fn get_image_file_format(path: &std::path::Path, data: &[u8]) -> Option { - let ext = path.extension().and_then(|e| e.to_str())?.to_lowercase(); - if ext == "svg" || ext == "svgz" { - return Some(ImageFormat::SVG); - } - - get_image_data_format(data) -} - -/// Checks that file has a PNG, a GIF, a JPEG or a WebP magic bytes. -fn get_image_data_format(data: &[u8]) -> Option { - match imagesize::image_type(data).ok()? { - imagesize::ImageType::Gif => Some(ImageFormat::GIF), - imagesize::ImageType::Jpeg => Some(ImageFormat::JPEG), - imagesize::ImageType::Png => Some(ImageFormat::PNG), - imagesize::ImageType::Webp => Some(ImageFormat::WEBP), - _ => None, - } -} - -/// Tries to load the `ImageData` content as an SVG image. -/// -/// Unlike `Tree::from_*` methods, this one will also remove all `image` elements -/// from the loaded SVG, as required by the spec. -pub(crate) fn load_sub_svg(data: &[u8], opt: &Options) -> Option { - let mut sub_opt = Options::default(); - sub_opt.resources_dir = None; - sub_opt.dpi = opt.dpi; - sub_opt.font_size = opt.font_size; - sub_opt.languages = opt.languages.clone(); - sub_opt.shape_rendering = opt.shape_rendering; - sub_opt.text_rendering = opt.text_rendering; - sub_opt.image_rendering = opt.image_rendering; - sub_opt.default_size = opt.default_size; - - // The referenced SVG image cannot have any 'image' elements by itself. - // Not only recursive. Any. Don't know why. - sub_opt.image_href_resolver = ImageHrefResolver { - resolve_data: Box::new(|_, _, _| None), - resolve_string: Box::new(|_, _| None), - }; - - #[cfg(feature = "text")] - { - // In the referenced SVG, we start with the unmodified user-provided - // fontdb, not the one from the cache. - sub_opt.fontdb = opt.fontdb.clone(); - - // Can't clone the resolver, so we create a new one that forwards to it. - sub_opt.font_resolver = crate::FontResolver { - select_font: Box::new(|font, db| (opt.font_resolver.select_font)(font, db)), - select_fallback: Box::new(|c, used_fonts, db| { - (opt.font_resolver.select_fallback)(c, used_fonts, db) - }), - }; - } - - let tree = Tree::from_data(data, &sub_opt); - let tree = match tree { - Ok(tree) => tree, - Err(_) => { - log::warn!("Failed to load subsvg image."); - return None; - } - }; - - Some(ImageKind::SVG(tree)) -} - -/// Fits size into a viewbox. -fn fit_view_box(size: Size, rect: NonZeroRect, aspect: AspectRatio) -> Size { - let s = rect.size(); - - if aspect.align == svgtypes::Align::None { - s - } else if aspect.slice { - size.expand_to(s) - } else { - size.scale_to(s) - } -} diff --git a/resvg-0.43.0/crates/usvg/src/parser/marker.rs b/resvg-0.43.0/crates/usvg/src/parser/marker.rs deleted file mode 100644 index 07259a4..0000000 --- a/resvg-0.43.0/crates/usvg/src/parser/marker.rs +++ /dev/null @@ -1,498 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use std::sync::Arc; - -use strict_num::NonZeroPositiveF32; -use svgtypes::Length; -use tiny_skia_path::Point; - -use super::converter; -use super::svgtree::{AId, EId, SvgNode}; -use crate::{ - ApproxEqUlps, ApproxZeroUlps, ClipPath, Fill, Group, Node, NonZeroRect, Path, Size, Transform, - ViewBox, -}; - -// Similar to `tiny_skia_path::PathSegment`, but without the `QuadTo`. -#[derive(Copy, Clone, Debug)] -enum Segment { - MoveTo(Point), - LineTo(Point), - CubicTo(Point, Point, Point), - Close, -} - -pub(crate) fn is_valid(node: SvgNode) -> bool { - // `marker-*` attributes cannot be set on shapes inside a `clipPath`. - if node - .ancestors() - .any(|n| n.tag_name() == Some(EId::ClipPath)) - { - return false; - } - - let start = node.find_attribute::(AId::MarkerStart); - let mid = node.find_attribute::(AId::MarkerMid); - let end = node.find_attribute::(AId::MarkerEnd); - start.is_some() || mid.is_some() || end.is_some() -} - -pub(crate) fn convert( - node: SvgNode, - path: &tiny_skia_path::Path, - state: &converter::State, - cache: &mut converter::Cache, - parent: &mut Group, -) { - let list = [ - (AId::MarkerStart, MarkerKind::Start), - (AId::MarkerMid, MarkerKind::Middle), - (AId::MarkerEnd, MarkerKind::End), - ]; - - for (aid, kind) in &list { - let mut marker = None; - if let Some(link) = node.find_attribute::(*aid) { - if link.tag_name() == Some(EId::Marker) { - marker = Some(link); - } - } - - if let Some(marker) = marker { - // TODO: move to svgtree - // Check for recursive marker. - if state.parent_markers.contains(&marker) { - log::warn!("Recursive marker detected: {}", marker.element_id()); - continue; - } - - resolve(node, path, marker, *kind, state, cache, parent); - } - } -} - -#[derive(Clone, Copy)] -enum MarkerKind { - Start, - Middle, - End, -} - -enum MarkerOrientation { - Auto, - AutoStartReverse, - Angle(f32), -} - -fn resolve( - shape_node: SvgNode, - path: &tiny_skia_path::Path, - marker_node: SvgNode, - marker_kind: MarkerKind, - state: &converter::State, - cache: &mut converter::Cache, - parent: &mut Group, -) -> Option<()> { - let stroke_scale = stroke_scale(shape_node, marker_node, state)?.get(); - - let r = convert_rect(marker_node, state)?; - - let view_box = marker_node.parse_viewbox().map(|vb| ViewBox { - rect: vb, - aspect: marker_node - .attribute(AId::PreserveAspectRatio) - .unwrap_or_default(), - }); - - let has_overflow = { - let overflow = marker_node.attribute(AId::Overflow); - // `overflow` is `hidden` by default. - overflow.is_none() || overflow == Some("hidden") || overflow == Some("scroll") - }; - - let clip_path = if has_overflow { - let clip_rect = if let Some(vbox) = view_box { - vbox.rect - } else { - r.size().to_non_zero_rect(0.0, 0.0) - }; - - let mut clip_path = ClipPath::empty(cache.gen_clip_path_id()); - - let mut path = Path::new_simple(Arc::new(tiny_skia_path::PathBuilder::from_rect( - clip_rect.to_rect(), - )))?; - path.fill = Some(Fill::default()); - - clip_path.root.children.push(Node::Path(Box::new(path))); - - Some(Arc::new(clip_path)) - } else { - None - }; - - // TODO: avoid allocation - let mut segments: Vec = Vec::with_capacity(path.len()); - let mut prev = Point::zero(); - let mut prev_move = Point::zero(); - for seg in path.segments() { - match seg { - tiny_skia_path::PathSegment::MoveTo(p) => { - segments.push(Segment::MoveTo(p)); - prev = p; - prev_move = p; - } - tiny_skia_path::PathSegment::LineTo(p) => { - segments.push(Segment::LineTo(p)); - prev = p; - } - tiny_skia_path::PathSegment::QuadTo(p1, p) => { - let (p1, p2, p) = quad_to_curve(prev, p1, p); - segments.push(Segment::CubicTo(p1, p2, p)); - prev = p; - } - tiny_skia_path::PathSegment::CubicTo(p1, p2, p) => { - segments.push(Segment::CubicTo(p1, p2, p)); - prev = p; - } - tiny_skia_path::PathSegment::Close => { - segments.push(Segment::Close); - prev = prev_move; - } - } - } - - let draw_marker = |p: tiny_skia_path::Point, idx: usize| { - let mut ts = Transform::from_translate(p.x, p.y); - - let angle = match convert_orientation(marker_node) { - MarkerOrientation::AutoStartReverse if idx == 0 => { - (calc_vertex_angle(&segments, idx) + 180.0) % 360.0 - } - MarkerOrientation::Auto | MarkerOrientation::AutoStartReverse => { - calc_vertex_angle(&segments, idx) - } - MarkerOrientation::Angle(angle) => angle, - }; - - if !angle.approx_zero_ulps(4) { - ts = ts.pre_rotate(angle); - } - - if let Some(vbox) = view_box { - let size = Size::from_wh(r.width() * stroke_scale, r.height() * stroke_scale).unwrap(); - let vbox_ts = vbox.to_transform(size); - let (sx, sy) = vbox_ts.get_scale(); - ts = ts.pre_scale(sx, sy); - } else { - ts = ts.pre_scale(stroke_scale, stroke_scale); - } - - ts = ts.pre_translate(-r.x(), -r.y()); - - // TODO: do not create a group when no clipPath - let mut g = Group { - transform: ts, - abs_transform: parent.abs_transform.pre_concat(ts), - clip_path: clip_path.clone(), - ..Group::empty() - }; - - let mut marker_state = state.clone(); - marker_state.parent_markers.push(marker_node); - converter::convert_children(marker_node, &marker_state, cache, &mut g); - g.calculate_bounding_boxes(); - - if g.has_children() { - parent.children.push(Node::Group(Box::new(g))); - } - }; - - draw_markers(&segments, marker_kind, draw_marker); - - Some(()) -} - -fn stroke_scale( - path_node: SvgNode, - marker_node: SvgNode, - state: &converter::State, -) -> Option { - match marker_node.attribute(AId::MarkerUnits) { - Some("userSpaceOnUse") => NonZeroPositiveF32::new(1.0), - _ => path_node.resolve_valid_length(AId::StrokeWidth, state, 1.0), - } -} - -fn draw_markers

(path: &[Segment], kind: MarkerKind, mut draw_marker: P) -where - P: FnMut(tiny_skia_path::Point, usize), -{ - match kind { - MarkerKind::Start => { - if let Some(Segment::MoveTo(p)) = path.first().cloned() { - draw_marker(p, 0); - } - } - MarkerKind::Middle => { - let total = path.len() - 1; - let mut i = 1; - while i < total { - let p = match path[i] { - Segment::MoveTo(p) => p, - Segment::LineTo(p) => p, - Segment::CubicTo(_, _, p) => p, - _ => { - i += 1; - continue; - } - }; - - draw_marker(p, i); - - i += 1; - } - } - MarkerKind::End => { - let idx = path.len() - 1; - match path.last().cloned() { - Some(Segment::LineTo(p)) => { - draw_marker(p, idx); - } - Some(Segment::CubicTo(_, _, p)) => { - draw_marker(p, idx); - } - Some(Segment::Close) => { - let p = get_subpath_start(path, idx); - draw_marker(p, idx); - } - _ => {} - } - } - } -} - -fn calc_vertex_angle(path: &[Segment], idx: usize) -> f32 { - if idx == 0 { - // First segment. - - debug_assert!(path.len() > 1); - - let seg1 = path[0]; - let seg2 = path[1]; - - match (seg1, seg2) { - (Segment::MoveTo(pm), Segment::LineTo(p)) => calc_line_angle(pm.x, pm.y, p.x, p.y), - (Segment::MoveTo(pm), Segment::CubicTo(p1, _, p)) => { - if pm.x.approx_eq_ulps(&p1.x, 4) && pm.y.approx_eq_ulps(&p1.y, 4) { - calc_line_angle(pm.x, pm.y, p.x, p.y) - } else { - calc_line_angle(pm.x, pm.y, p1.x, p1.y) - } - } - _ => 0.0, - } - } else if idx == path.len() - 1 { - // Last segment. - - let seg1 = path[idx - 1]; - let seg2 = path[idx]; - - match (seg1, seg2) { - (_, Segment::MoveTo(_)) => 0.0, // unreachable - (_, Segment::LineTo(p)) => { - let prev = get_prev_vertex(path, idx); - calc_line_angle(prev.x, prev.y, p.x, p.y) - } - (_, Segment::CubicTo(p1, p2, p)) => { - if p2.x.approx_eq_ulps(&p.x, 4) && p2.y.approx_eq_ulps(&p.y, 4) { - calc_line_angle(p1.x, p1.y, p.x, p.y) - } else { - calc_line_angle(p2.x, p2.y, p.x, p.y) - } - } - (Segment::LineTo(p), Segment::Close) => { - let next = get_subpath_start(path, idx); - calc_line_angle(p.x, p.y, next.x, next.y) - } - (Segment::CubicTo(_, p2, p), Segment::Close) => { - let prev = get_prev_vertex(path, idx); - let next = get_subpath_start(path, idx); - calc_curves_angle( - prev.x, prev.y, p2.x, p2.y, p.x, p.y, next.x, next.y, next.x, next.y, - ) - } - (_, Segment::Close) => 0.0, - } - } else { - // Middle segments. - - let seg1 = path[idx]; - let seg2 = path[idx + 1]; - - // TODO: Not sure if there is a better way. - match (seg1, seg2) { - (Segment::MoveTo(pm), Segment::LineTo(p)) => calc_line_angle(pm.x, pm.y, p.x, p.y), - (Segment::MoveTo(pm), Segment::CubicTo(p1, _, _)) => { - calc_line_angle(pm.x, pm.y, p1.x, p1.y) - } - (Segment::LineTo(p1), Segment::LineTo(p2)) => { - let prev = get_prev_vertex(path, idx); - calc_angle(prev.x, prev.y, p1.x, p1.y, p1.x, p1.y, p2.x, p2.y) - } - (Segment::CubicTo(_, c1_p2, c1_p), Segment::CubicTo(c2_p1, _, c2_p)) => { - let prev = get_prev_vertex(path, idx); - calc_curves_angle( - 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, - c2_p.y, - ) - } - (Segment::LineTo(pl), Segment::CubicTo(p1, _, p)) => { - let prev = get_prev_vertex(path, idx); - calc_curves_angle( - prev.x, prev.y, prev.x, prev.y, pl.x, pl.y, p1.x, p1.y, p.x, p.y, - ) - } - (Segment::CubicTo(_, p2, p), Segment::LineTo(pl)) => { - let prev = get_prev_vertex(path, idx); - calc_curves_angle(prev.x, prev.y, p2.x, p2.y, p.x, p.y, pl.x, pl.y, pl.x, pl.y) - } - (Segment::LineTo(p), Segment::MoveTo(_)) => { - let prev = get_prev_vertex(path, idx); - calc_line_angle(prev.x, prev.y, p.x, p.y) - } - (Segment::CubicTo(_, p2, p), Segment::MoveTo(_)) => { - if p.x.approx_eq_ulps(&p2.x, 4) && p.y.approx_eq_ulps(&p2.y, 4) { - let prev = get_prev_vertex(path, idx); - calc_line_angle(prev.x, prev.y, p.x, p.y) - } else { - calc_line_angle(p2.x, p2.y, p.x, p.y) - } - } - (Segment::LineTo(p), Segment::Close) => { - let prev = get_prev_vertex(path, idx); - let next = get_subpath_start(path, idx); - calc_angle(prev.x, prev.y, p.x, p.y, p.x, p.y, next.x, next.y) - } - (_, Segment::Close) => { - let prev = get_prev_vertex(path, idx); - let next = get_subpath_start(path, idx); - calc_line_angle(prev.x, prev.y, next.x, next.y) - } - (_, Segment::MoveTo(_)) | (Segment::Close, _) => 0.0, - } - } -} - -fn calc_line_angle(x1: f32, y1: f32, x2: f32, y2: f32) -> f32 { - calc_angle(x1, y1, x2, y2, x1, y1, x2, y2) -} - -fn calc_curves_angle( - px: f32, - py: f32, // previous vertex - cx1: f32, - cy1: f32, // previous control point - x: f32, - y: f32, // current vertex - cx2: f32, - cy2: f32, // next control point - nx: f32, - ny: f32, // next vertex -) -> f32 { - if cx1.approx_eq_ulps(&x, 4) && cy1.approx_eq_ulps(&y, 4) { - calc_angle(px, py, x, y, x, y, cx2, cy2) - } else if x.approx_eq_ulps(&cx2, 4) && y.approx_eq_ulps(&cy2, 4) { - calc_angle(cx1, cy1, x, y, x, y, nx, ny) - } else { - calc_angle(cx1, cy1, x, y, x, y, cx2, cy2) - } -} - -fn calc_angle(x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32, x4: f32, y4: f32) -> f32 { - use std::f32::consts::*; - - fn normalize(rad: f32) -> f32 { - let v = rad % (PI * 2.0); - if v < 0.0 { - v + PI * 2.0 - } else { - v - } - } - - fn vector_angle(vx: f32, vy: f32) -> f32 { - let rad = vy.atan2(vx); - if rad.is_nan() { - 0.0 - } else { - normalize(rad) - } - } - - let in_a = vector_angle(x2 - x1, y2 - y1); - let out_a = vector_angle(x4 - x3, y4 - y3); - let d = (out_a - in_a) * 0.5; - - let mut angle = in_a + d; - if FRAC_PI_2 < d.abs() { - angle -= PI; - } - - normalize(angle).to_degrees() -} - -fn get_subpath_start(segments: &[Segment], idx: usize) -> tiny_skia_path::Point { - let offset = segments.len() - idx; - for seg in segments.iter().rev().skip(offset) { - if let Segment::MoveTo(p) = *seg { - return p; - } - } - - tiny_skia_path::Point::zero() -} - -fn get_prev_vertex(segments: &[Segment], idx: usize) -> tiny_skia_path::Point { - match segments[idx - 1] { - Segment::MoveTo(p) => p, - Segment::LineTo(p) => p, - Segment::CubicTo(_, _, p) => p, - Segment::Close => get_subpath_start(segments, idx), - } -} - -fn convert_rect(node: SvgNode, state: &converter::State) -> Option { - NonZeroRect::from_xywh( - node.convert_user_length(AId::RefX, state, Length::zero()), - node.convert_user_length(AId::RefY, state, Length::zero()), - node.convert_user_length(AId::MarkerWidth, state, Length::new_number(3.0)), - node.convert_user_length(AId::MarkerHeight, state, Length::new_number(3.0)), - ) -} - -fn convert_orientation(node: SvgNode) -> MarkerOrientation { - match node.attribute(AId::Orient) { - Some("auto") => MarkerOrientation::Auto, - Some("auto-start-reverse") => MarkerOrientation::AutoStartReverse, - _ => match node.attribute::(AId::Orient) { - Some(angle) => MarkerOrientation::Angle(angle.to_degrees() as f32), - None => MarkerOrientation::Angle(0.0), - }, - } -} - -fn quad_to_curve(prev: Point, p1: Point, p: Point) -> (Point, Point, Point) { - #[inline] - fn calc(n1: f32, n2: f32) -> f32 { - (n1 + n2 * 2.0) / 3.0 - } - - ( - Point::from_xy(calc(prev.x, p1.x), calc(prev.y, p1.y)), - Point::from_xy(calc(p.x, p1.x), calc(p.y, p1.y)), - p, - ) -} diff --git a/resvg-0.43.0/crates/usvg/src/parser/mask.rs b/resvg-0.43.0/crates/usvg/src/parser/mask.rs deleted file mode 100644 index cdc88e9..0000000 --- a/resvg-0.43.0/crates/usvg/src/parser/mask.rs +++ /dev/null @@ -1,151 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use std::sync::Arc; - -use svgtypes::{Length, LengthUnit as Unit}; - -use super::svgtree::{AId, EId, SvgNode}; -use super::{converter, OptionLog}; -use crate::{Group, Mask, MaskType, Node, NonEmptyString, NonZeroRect, Transform, Units}; - -pub(crate) fn convert( - node: SvgNode, - state: &converter::State, - object_bbox: Option, - cache: &mut converter::Cache, -) -> Option> { - // A `mask` attribute must reference a `mask` element. - if node.tag_name() != Some(EId::Mask) { - return None; - } - - let units = node - .attribute(AId::MaskUnits) - .unwrap_or(Units::ObjectBoundingBox); - - let content_units = node - .attribute(AId::MaskContentUnits) - .unwrap_or(Units::UserSpaceOnUse); - - // Check if this element was already converted. - // - // Only `userSpaceOnUse` masks can be shared, - // because `objectBoundingBox` one will be converted into user one - // and will become node-specific. - let cacheable = units == Units::UserSpaceOnUse && content_units == Units::UserSpaceOnUse; - if cacheable { - if let Some(mask) = cache.masks.get(node.element_id()) { - return Some(mask.clone()); - } - } - - let rect = NonZeroRect::from_xywh( - node.convert_length(AId::X, units, state, Length::new(-10.0, Unit::Percent)), - node.convert_length(AId::Y, units, state, Length::new(-10.0, Unit::Percent)), - node.convert_length(AId::Width, units, state, Length::new(120.0, Unit::Percent)), - node.convert_length(AId::Height, units, state, Length::new(120.0, Unit::Percent)), - ); - let mut rect = - rect.log_none(|| log::warn!("Mask '{}' has an invalid size. Skipped.", node.element_id()))?; - - let mut mask_all = false; - if units == Units::ObjectBoundingBox { - if let Some(bbox) = object_bbox { - rect = rect.bbox_transform(bbox) - } else { - // When mask units are `objectBoundingBox` and bbox is zero-sized - the whole - // element should be masked. - // Technically an UB, but this is what Chrome and Firefox do. - mask_all = true; - } - } - - let mut id = NonEmptyString::new(node.element_id().to_string())?; - // Generate ID only when we're parsing `objectBoundingBox` mask for the second time. - if !cacheable && cache.masks.contains_key(id.get()) { - id = cache.gen_mask_id(); - } - let id_copy = id.get().to_string(); - - if mask_all { - let mask = Arc::new(Mask { - id, - rect, - kind: MaskType::Luminance, - mask: None, - root: Group::empty(), - }); - cache.masks.insert(id_copy, mask.clone()); - return Some(mask); - } - - // Resolve linked mask. - let mut mask = None; - if let Some(link) = node.attribute::(AId::Mask) { - mask = convert(link, state, object_bbox, cache); - - // Linked `mask` must be valid. - if mask.is_none() { - return None; - } - } - - let kind = if node.attribute(AId::MaskType) == Some("alpha") { - MaskType::Alpha - } else { - MaskType::Luminance - }; - - let mut mask = Mask { - id, - rect, - kind, - mask, - root: Group::empty(), - }; - - // To emulate content `objectBoundingBox` units we have to put - // mask children into a group with a transform. - let mut subroot = None; - if content_units == Units::ObjectBoundingBox { - let object_bbox = match object_bbox { - Some(v) => v, - None => { - log::warn!("Masking of zero-sized shapes is not allowed."); - return None; - } - }; - - let mut g = Group::empty(); - g.transform = Transform::from_bbox(object_bbox); - // Make sure to set `abs_transform`, because it must propagate to all children. - g.abs_transform = g.transform; - - subroot = Some(g); - } - - { - // Prefer `subroot` to `mask.root`. - let real_root = subroot.as_mut().unwrap_or(&mut mask.root); - converter::convert_children(node, state, cache, real_root); - - // A mask without children at this point is invalid. - // Only masks with zero bbox and `objectBoundingBox` can be empty. - if !real_root.has_children() { - return None; - } - } - - if let Some(mut subroot) = subroot { - subroot.calculate_bounding_boxes(); - mask.root.children.push(Node::Group(Box::new(subroot))); - } - - mask.root.calculate_bounding_boxes(); - - let mask = Arc::new(mask); - cache.masks.insert(id_copy, mask.clone()); - Some(mask) -} diff --git a/resvg-0.43.0/crates/usvg/src/parser/mod.rs b/resvg-0.43.0/crates/usvg/src/parser/mod.rs deleted file mode 100644 index 1f52c3b..0000000 --- a/resvg-0.43.0/crates/usvg/src/parser/mod.rs +++ /dev/null @@ -1,154 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -mod clippath; -mod converter; -mod filter; -mod image; -mod marker; -mod mask; -mod options; -mod paint_server; -mod shapes; -mod style; -mod svgtree; -mod switch; -mod units; -mod use_node; - -#[cfg(feature = "text")] -mod text; - -pub use image::{ImageHrefDataResolverFn, ImageHrefResolver, ImageHrefStringResolverFn}; -pub use options::Options; -pub(crate) use svgtree::{AId, EId}; - -/// List of all errors. -#[derive(Debug)] -pub enum Error { - /// Only UTF-8 content are supported. - NotAnUtf8Str, - - /// Compressed SVG must use the GZip algorithm. - MalformedGZip, - - /// We do not allow SVG with more than 1_000_000 elements for security reasons. - ElementsLimitReached, - - /// SVG doesn't have a valid size. - /// - /// Occurs when width and/or height are <= 0. - /// - /// Also occurs if width, height and viewBox are not set. - InvalidSize, - - /// Failed to parse an SVG data. - ParsingFailed(roxmltree::Error), -} - -impl From for Error { - fn from(e: roxmltree::Error) -> Self { - Error::ParsingFailed(e) - } -} - -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match *self { - Error::NotAnUtf8Str => { - write!(f, "provided data has not an UTF-8 encoding") - } - Error::MalformedGZip => { - write!(f, "provided data has a malformed GZip content") - } - Error::ElementsLimitReached => { - write!(f, "the maximum number of SVG elements has been reached") - } - Error::InvalidSize => { - write!(f, "SVG has an invalid size") - } - Error::ParsingFailed(ref e) => { - write!(f, "SVG data parsing failed cause {}", e) - } - } - } -} - -impl std::error::Error for Error {} - -pub(crate) trait OptionLog { - fn log_none(self, f: F) -> Self; -} - -impl OptionLog for Option { - #[inline] - fn log_none(self, f: F) -> Self { - self.or_else(|| { - f(); - None - }) - } -} - -impl crate::Tree { - /// Parses `Tree` from an SVG data. - /// - /// Can contain an SVG string or a gzip compressed data. - pub fn from_data(data: &[u8], opt: &Options) -> Result { - if data.starts_with(&[0x1f, 0x8b]) { - let data = decompress_svgz(data)?; - let text = std::str::from_utf8(&data).map_err(|_| Error::NotAnUtf8Str)?; - Self::from_str(text, opt) - } else { - let text = std::str::from_utf8(data).map_err(|_| Error::NotAnUtf8Str)?; - Self::from_str(text, opt) - } - } - - /// Parses `Tree` from an SVG string. - pub fn from_str(text: &str, opt: &Options) -> Result { - let xml_opt = roxmltree::ParsingOptions { - allow_dtd: true, - ..Default::default() - }; - - let doc = - roxmltree::Document::parse_with_options(text, xml_opt).map_err(Error::ParsingFailed)?; - - Self::from_xmltree(&doc, opt) - } - - /// Parses `Tree` from `roxmltree::Document`. - pub fn from_xmltree(doc: &roxmltree::Document, opt: &Options) -> Result { - let doc = svgtree::Document::parse_tree(doc)?; - self::converter::convert_doc(&doc, opt) - } -} - -/// Decompresses an SVGZ file. -pub fn decompress_svgz(data: &[u8]) -> Result, Error> { - use std::io::Read; - - let mut decoder = flate2::read::GzDecoder::new(data); - let mut decoded = Vec::with_capacity(data.len() * 2); - decoder - .read_to_end(&mut decoded) - .map_err(|_| Error::MalformedGZip)?; - Ok(decoded) -} - -#[inline] -pub(crate) fn f32_bound(min: f32, val: f32, max: f32) -> f32 { - debug_assert!(min.is_finite()); - debug_assert!(val.is_finite()); - debug_assert!(max.is_finite()); - - if val > max { - max - } else if val < min { - min - } else { - val - } -} diff --git a/resvg-0.43.0/crates/usvg/src/parser/options.rs b/resvg-0.43.0/crates/usvg/src/parser/options.rs deleted file mode 100644 index 7b3c51a..0000000 --- a/resvg-0.43.0/crates/usvg/src/parser/options.rs +++ /dev/null @@ -1,141 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#[cfg(feature = "text")] -use std::sync::Arc; - -#[cfg(feature = "text")] -use crate::FontResolver; -use crate::{ImageHrefResolver, ImageRendering, ShapeRendering, Size, TextRendering}; - -/// Processing options. -#[derive(Debug)] -pub struct Options<'a> { - /// Directory that will be used during relative paths resolving. - /// - /// Expected to be the same as the directory that contains the SVG file, - /// but can be set to any. - /// - /// Default: `None` - pub resources_dir: Option, - - /// Target DPI. - /// - /// Impacts units conversion. - /// - /// Default: 96.0 - pub dpi: f32, - - /// A default font family. - /// - /// Will be used when no `font-family` attribute is set in the SVG. - /// - /// Default: Times New Roman - pub font_family: String, - - /// A default font size. - /// - /// Will be used when no `font-size` attribute is set in the SVG. - /// - /// Default: 12 - pub font_size: f32, - - /// A list of languages. - /// - /// Will be used to resolve a `systemLanguage` conditional attribute. - /// - /// Format: en, en-US. - /// - /// Default: `[en]` - pub languages: Vec, - - /// Specifies the default shape rendering method. - /// - /// Will be used when an SVG element's `shape-rendering` property is set to `auto`. - /// - /// Default: GeometricPrecision - pub shape_rendering: ShapeRendering, - - /// Specifies the default text rendering method. - /// - /// Will be used when an SVG element's `text-rendering` property is set to `auto`. - /// - /// Default: OptimizeLegibility - pub text_rendering: TextRendering, - - /// Specifies the default image rendering method. - /// - /// Will be used when an SVG element's `image-rendering` property is set to `auto`. - /// - /// Default: OptimizeQuality - pub image_rendering: ImageRendering, - - /// Default viewport size to assume if there is no `viewBox` attribute and - /// the `width` or `height` attributes are relative. - /// - /// Default: `(100, 100)` - pub default_size: Size, - - /// Specifies the way `xlink:href` in `` elements should be handled. - /// - /// Default: see type's documentation for details - pub image_href_resolver: ImageHrefResolver<'a>, - - /// Specifies how fonts should be resolved and loaded. - #[cfg(feature = "text")] - pub font_resolver: FontResolver<'a>, - - /// A database of fonts usable by text. - /// - /// This is a base database. If a custom `font_resolver` is specified, - /// additional fonts can be loaded during parsing. Those will be added to a - /// copy of this database. The full database containing all fonts referenced - /// in a `Tree` becomes available as [`Tree::fontdb`](crate::Tree::fontdb) - /// after parsing. If no fonts were loaded dynamically, that database will - /// be the same as this one. - #[cfg(feature = "text")] - pub fontdb: Arc, -} - -impl Default for Options<'_> { - fn default() -> Options<'static> { - Options { - resources_dir: None, - dpi: 96.0, - // Default font is user-agent dependent so we can use whichever we like. - font_family: "Times New Roman".to_owned(), - font_size: 12.0, - languages: vec!["en".to_string()], - shape_rendering: ShapeRendering::default(), - text_rendering: TextRendering::default(), - image_rendering: ImageRendering::default(), - default_size: Size::from_wh(100.0, 100.0).unwrap(), - image_href_resolver: ImageHrefResolver::default(), - #[cfg(feature = "text")] - font_resolver: FontResolver::default(), - #[cfg(feature = "text")] - fontdb: Arc::new(fontdb::Database::new()), - } - } -} - -impl Options<'_> { - /// Converts a relative path into absolute relative to the SVG file itself. - /// - /// If `Options::resources_dir` is not set, returns itself. - pub fn get_abs_path(&self, rel_path: &std::path::Path) -> std::path::PathBuf { - match self.resources_dir { - Some(ref dir) => dir.join(rel_path), - None => rel_path.into(), - } - } - - /// Mutably acquires the database. - /// - /// This clones the database if it is currently shared. - #[cfg(feature = "text")] - pub fn fontdb_mut(&mut self) -> &mut fontdb::Database { - Arc::make_mut(&mut self.fontdb) - } -} diff --git a/resvg-0.43.0/crates/usvg/src/parser/paint_server.rs b/resvg-0.43.0/crates/usvg/src/parser/paint_server.rs deleted file mode 100644 index 3aee39e..0000000 --- a/resvg-0.43.0/crates/usvg/src/parser/paint_server.rs +++ /dev/null @@ -1,1088 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use std::str::FromStr; -use std::sync::Arc; - -use strict_num::PositiveF32; -use svgtypes::{Length, LengthUnit as Unit}; - -use super::converter::{self, Cache, SvgColorExt}; -use super::svgtree::{AId, EId, SvgNode}; -use super::OptionLog; -use crate::*; - -pub(crate) enum ServerOrColor { - Server(Paint), - Color { color: Color, opacity: Opacity }, -} - -pub(crate) fn convert( - node: SvgNode, - state: &converter::State, - cache: &mut converter::Cache, -) -> Option { - // Check for existing. - if let Some(paint) = cache.paint.get(node.element_id()) { - return Some(ServerOrColor::Server(paint.clone())); - } - - // Unwrap is safe, because we already checked for is_paint_server(). - let paint = match node.tag_name().unwrap() { - EId::LinearGradient => convert_linear(node, state), - EId::RadialGradient => convert_radial(node, state), - EId::Pattern => convert_pattern(node, state, cache), - _ => unreachable!(), - }; - - if let Some(ServerOrColor::Server(ref paint)) = paint { - cache - .paint - .insert(node.element_id().to_string(), paint.clone()); - } - - paint -} - -#[inline(never)] -fn convert_linear(node: SvgNode, state: &converter::State) -> Option { - let id = NonEmptyString::new(node.element_id().to_string())?; - - let stops = convert_stops(find_gradient_with_stops(node)?); - if stops.len() < 2 { - return stops_to_color(&stops); - } - - let units = convert_units(node, AId::GradientUnits, Units::ObjectBoundingBox); - let transform = node.resolve_transform(AId::GradientTransform, state); - - let gradient = LinearGradient { - x1: resolve_number(node, AId::X1, units, state, Length::zero()), - y1: resolve_number(node, AId::Y1, units, state, Length::zero()), - x2: resolve_number( - node, - AId::X2, - units, - state, - Length::new(100.0, Unit::Percent), - ), - y2: resolve_number(node, AId::Y2, units, state, Length::zero()), - base: BaseGradient { - id, - units, - transform, - spread_method: convert_spread_method(node), - stops, - }, - }; - - Some(ServerOrColor::Server(Paint::LinearGradient(Arc::new( - gradient, - )))) -} - -#[inline(never)] -fn convert_radial(node: SvgNode, state: &converter::State) -> Option { - let id = NonEmptyString::new(node.element_id().to_string())?; - - let stops = convert_stops(find_gradient_with_stops(node)?); - if stops.len() < 2 { - return stops_to_color(&stops); - } - - let units = convert_units(node, AId::GradientUnits, Units::ObjectBoundingBox); - let r = resolve_number(node, AId::R, units, state, Length::new(50.0, Unit::Percent)); - - // 'A value of zero will cause the area to be painted as a single color - // using the color and opacity of the last gradient stop.' - // - // https://www.w3.org/TR/SVG11/pservers.html#RadialGradientElementRAttribute - if !r.is_valid_length() { - let stop = stops.last().unwrap(); - return Some(ServerOrColor::Color { - color: stop.color, - opacity: stop.opacity, - }); - } - - let spread_method = convert_spread_method(node); - let cx = resolve_number( - node, - AId::Cx, - units, - state, - Length::new(50.0, Unit::Percent), - ); - let cy = resolve_number( - node, - AId::Cy, - units, - state, - Length::new(50.0, Unit::Percent), - ); - let fx = resolve_number(node, AId::Fx, units, state, Length::new_number(cx as f64)); - let fy = resolve_number(node, AId::Fy, units, state, Length::new_number(cy as f64)); - let transform = node.resolve_transform(AId::GradientTransform, state); - - let gradient = RadialGradient { - cx, - cy, - r: PositiveF32::new(r).unwrap(), - fx, - fy, - base: BaseGradient { - id, - units, - transform, - spread_method, - stops, - }, - }; - - Some(ServerOrColor::Server(Paint::RadialGradient(Arc::new( - gradient, - )))) -} - -#[inline(never)] -fn convert_pattern( - node: SvgNode, - state: &converter::State, - cache: &mut converter::Cache, -) -> Option { - let node_with_children = find_pattern_with_children(node)?; - - let id = NonEmptyString::new(node.element_id().to_string())?; - - let view_box = { - let n1 = resolve_attr(node, AId::ViewBox); - let n2 = resolve_attr(node, AId::PreserveAspectRatio); - n1.parse_viewbox().map(|vb| ViewBox { - rect: vb, - aspect: n2.attribute(AId::PreserveAspectRatio).unwrap_or_default(), - }) - }; - - let units = convert_units(node, AId::PatternUnits, Units::ObjectBoundingBox); - let content_units = convert_units(node, AId::PatternContentUnits, Units::UserSpaceOnUse); - - let transform = node.resolve_transform(AId::PatternTransform, state); - - let rect = NonZeroRect::from_xywh( - resolve_number(node, AId::X, units, state, Length::zero()), - resolve_number(node, AId::Y, units, state, Length::zero()), - resolve_number(node, AId::Width, units, state, Length::zero()), - resolve_number(node, AId::Height, units, state, Length::zero()), - ); - let rect = rect.log_none(|| { - log::warn!( - "Pattern '{}' has an invalid size. Skipped.", - node.element_id() - ) - })?; - - let mut patt = Pattern { - id, - units, - content_units, - transform, - rect, - view_box, - root: Group::empty(), - }; - - // We can apply viewbox transform only for user space coordinates. - // Otherwise we need a bounding box, which is unknown at this point. - if patt.view_box.is_some() - && patt.units == Units::UserSpaceOnUse - && patt.content_units == Units::UserSpaceOnUse - { - let mut g = Group::empty(); - g.transform = view_box.unwrap().to_transform(rect.size()); - g.abs_transform = g.transform; - - converter::convert_children(node_with_children, state, cache, &mut g); - if !g.has_children() { - return None; - } - - g.calculate_bounding_boxes(); - patt.root.children.push(Node::Group(Box::new(g))); - } else { - converter::convert_children(node_with_children, state, cache, &mut patt.root); - if !patt.root.has_children() { - return None; - } - } - - patt.root.calculate_bounding_boxes(); - - Some(ServerOrColor::Server(Paint::Pattern(Arc::new(patt)))) -} - -fn convert_spread_method(node: SvgNode) -> SpreadMethod { - let node = resolve_attr(node, AId::SpreadMethod); - node.attribute(AId::SpreadMethod).unwrap_or_default() -} - -pub(crate) fn convert_units(node: SvgNode, name: AId, def: Units) -> Units { - let node = resolve_attr(node, name); - node.attribute(name).unwrap_or(def) -} - -fn find_gradient_with_stops<'a, 'input: 'a>( - node: SvgNode<'a, 'input>, -) -> Option> { - for link in node.href_iter() { - if !link.tag_name().unwrap().is_gradient() { - log::warn!( - "Gradient '{}' cannot reference '{}' via 'xlink:href'.", - node.element_id(), - link.tag_name().unwrap() - ); - return None; - } - - if link.children().any(|n| n.tag_name() == Some(EId::Stop)) { - return Some(link); - } - } - - None -} - -fn find_pattern_with_children<'a, 'input: 'a>( - node: SvgNode<'a, 'input>, -) -> Option> { - for link in node.href_iter() { - if link.tag_name() != Some(EId::Pattern) { - log::warn!( - "Pattern '{}' cannot reference '{}' via 'xlink:href'.", - node.element_id(), - link.tag_name().unwrap() - ); - return None; - } - - if link.has_children() { - return Some(link); - } - } - - None -} - -fn convert_stops(grad: SvgNode) -> Vec { - let mut stops = Vec::new(); - - { - let mut prev_offset = Length::zero(); - for stop in grad.children() { - if stop.tag_name() != Some(EId::Stop) { - log::warn!("Invalid gradient child: '{:?}'.", stop.tag_name().unwrap()); - continue; - } - - // `number` can be either a number or a percentage. - let offset = stop.attribute(AId::Offset).unwrap_or(prev_offset); - let offset = match offset.unit { - Unit::None => offset.number, - Unit::Percent => offset.number / 100.0, - _ => prev_offset.number, - }; - prev_offset = Length::new_number(offset); - let offset = crate::f32_bound(0.0, offset as f32, 1.0); - - let (color, opacity) = match stop.attribute(AId::StopColor) { - Some("currentColor") => stop - .find_attribute(AId::Color) - .unwrap_or_else(svgtypes::Color::black), - Some(value) => { - if let Ok(c) = svgtypes::Color::from_str(value) { - c - } else { - log::warn!("Failed to parse stop-color value: '{}'.", value); - svgtypes::Color::black() - } - } - _ => svgtypes::Color::black(), - } - .split_alpha(); - - let stop_opacity = stop - .attribute::(AId::StopOpacity) - .unwrap_or(Opacity::ONE); - stops.push(Stop { - offset: StopOffset::new_clamped(offset), - color, - opacity: opacity * stop_opacity, - }); - } - } - - // Remove stops with equal offset. - // - // Example: - // offset="0.5" - // offset="0.7" - // offset="0.7" <-- this one should be removed - // offset="0.7" - // offset="0.9" - if stops.len() >= 3 { - let mut i = 0; - while i < stops.len() - 2 { - let offset1 = stops[i + 0].offset.get(); - let offset2 = stops[i + 1].offset.get(); - let offset3 = stops[i + 2].offset.get(); - - if offset1.approx_eq_ulps(&offset2, 4) && offset2.approx_eq_ulps(&offset3, 4) { - // Remove offset in the middle. - stops.remove(i + 1); - } else { - i += 1; - } - } - } - - // Remove zeros. - // - // From: - // offset="0.0" - // offset="0.0" - // offset="0.7" - // - // To: - // offset="0.0" - // offset="0.00000001" - // offset="0.7" - if stops.len() >= 2 { - let mut i = 0; - while i < stops.len() - 1 { - let offset1 = stops[i + 0].offset.get(); - let offset2 = stops[i + 1].offset.get(); - - if offset1.approx_eq_ulps(&0.0, 4) && offset2.approx_eq_ulps(&0.0, 4) { - stops[i + 1].offset = StopOffset::new_clamped(offset1 + f32::EPSILON); - } - - i += 1; - } - } - - // Shift equal offsets. - // - // From: - // offset="0.5" - // offset="0.7" - // offset="0.7" - // - // To: - // offset="0.5" - // offset="0.699999999" - // offset="0.7" - { - let mut i = 1; - while i < stops.len() { - let offset1 = stops[i - 1].offset.get(); - let offset2 = stops[i - 0].offset.get(); - - // Next offset must be smaller then previous. - if offset1 > offset2 || offset1.approx_eq_ulps(&offset2, 4) { - // Make previous offset a bit smaller. - let new_offset = offset1 - f32::EPSILON; - stops[i - 1].offset = StopOffset::new_clamped(new_offset); - stops[i - 0].offset = StopOffset::new_clamped(offset1); - } - - i += 1; - } - } - - stops -} - -#[inline(never)] -pub(crate) fn resolve_number( - node: SvgNode, - name: AId, - units: Units, - state: &converter::State, - def: Length, -) -> f32 { - resolve_attr(node, name).convert_length(name, units, state, def) -} - -fn resolve_attr<'a, 'input: 'a>(node: SvgNode<'a, 'input>, name: AId) -> SvgNode<'a, 'input> { - if node.has_attribute(name) { - return node; - } - - match node.tag_name().unwrap() { - EId::LinearGradient => resolve_lg_attr(node, name), - EId::RadialGradient => resolve_rg_attr(node, name), - EId::Pattern => resolve_pattern_attr(node, name), - EId::Filter => resolve_filter_attr(node, name), - _ => node, - } -} - -fn resolve_lg_attr<'a, 'input: 'a>(node: SvgNode<'a, 'input>, name: AId) -> SvgNode<'a, 'input> { - for link in node.href_iter() { - let tag_name = match link.tag_name() { - Some(v) => v, - None => return node, - }; - - match (name, tag_name) { - // Coordinates can be resolved only from - // ref element with the same type. - (AId::X1, EId::LinearGradient) - | (AId::Y1, EId::LinearGradient) - | (AId::X2, EId::LinearGradient) - | (AId::Y2, EId::LinearGradient) - // Other attributes can be resolved - // from any kind of gradient. - | (AId::GradientUnits, EId::LinearGradient) - | (AId::GradientUnits, EId::RadialGradient) - | (AId::SpreadMethod, EId::LinearGradient) - | (AId::SpreadMethod, EId::RadialGradient) - | (AId::GradientTransform, EId::LinearGradient) - | (AId::GradientTransform, EId::RadialGradient) => { - if link.has_attribute(name) { - return link; - } - } - _ => break, - } - } - - node -} - -fn resolve_rg_attr<'a, 'input>(node: SvgNode<'a, 'input>, name: AId) -> SvgNode<'a, 'input> { - for link in node.href_iter() { - let tag_name = match link.tag_name() { - Some(v) => v, - None => return node, - }; - - match (name, tag_name) { - // Coordinates can be resolved only from - // ref element with the same type. - (AId::Cx, EId::RadialGradient) - | (AId::Cy, EId::RadialGradient) - | (AId::R, EId::RadialGradient) - | (AId::Fx, EId::RadialGradient) - | (AId::Fy, EId::RadialGradient) - // Other attributes can be resolved - // from any kind of gradient. - | (AId::GradientUnits, EId::LinearGradient) - | (AId::GradientUnits, EId::RadialGradient) - | (AId::SpreadMethod, EId::LinearGradient) - | (AId::SpreadMethod, EId::RadialGradient) - | (AId::GradientTransform, EId::LinearGradient) - | (AId::GradientTransform, EId::RadialGradient) => { - if link.has_attribute(name) { - return link; - } - } - _ => break, - } - } - - node -} - -fn resolve_pattern_attr<'a, 'input: 'a>( - node: SvgNode<'a, 'input>, - name: AId, -) -> SvgNode<'a, 'input> { - for link in node.href_iter() { - let tag_name = match link.tag_name() { - Some(v) => v, - None => return node, - }; - - if tag_name != EId::Pattern { - break; - } - - if link.has_attribute(name) { - return link; - } - } - - node -} - -fn resolve_filter_attr<'a, 'input: 'a>(node: SvgNode<'a, 'input>, aid: AId) -> SvgNode<'a, 'input> { - for link in node.href_iter() { - let tag_name = match link.tag_name() { - Some(v) => v, - None => return node, - }; - - if tag_name != EId::Filter { - break; - } - - if link.has_attribute(aid) { - return link; - } - } - - node -} - -fn stops_to_color(stops: &[Stop]) -> Option { - if stops.is_empty() { - None - } else { - Some(ServerOrColor::Color { - color: stops[0].color, - opacity: stops[0].opacity, - }) - } -} - -// Update paints servers by doing the following: -// 1. Replace context fills/strokes that are linked to -// a use node with their actual values. -// 2. Convert all object units to UserSpaceOnUse -pub fn update_paint_servers( - group: &mut Group, - context_transform: Transform, - context_bbox: Option, - text_bbox: Option, - cache: &mut Cache, -) { - for child in &mut group.children { - // Set context transform and bbox if applicable if the - // current group is a use node. - let (context_transform, context_bbox) = if group.is_context_element { - (group.abs_transform, Some(group.bounding_box)) - } else { - (context_transform, context_bbox) - }; - - node_to_user_coordinates(child, context_transform, context_bbox, text_bbox, cache); - } -} - -// When parsing clipPaths, masks and filters we already know group's bounding box. -// But with gradients and patterns we don't, because we have to know text bounding box -// before we even parsed it. Which is impossible. -// Therefore our only choice is to parse gradients and patterns preserving their units -// and then replace them with `userSpaceOnUse` after the whole tree parsing is finished. -// So while gradients and patterns do still store their units, -// they are not exposed in the public API and for the caller they are always `userSpaceOnUse`. -fn node_to_user_coordinates( - node: &mut Node, - context_transform: Transform, - context_bbox: Option, - text_bbox: Option, - cache: &mut Cache, -) { - match node { - Node::Group(ref mut g) => { - // No need to check clip paths, because they cannot have paint servers. - if let Some(ref mut mask) = g.mask { - if let Some(ref mut mask) = Arc::get_mut(mask) { - update_paint_servers( - &mut mask.root, - context_transform, - context_bbox, - None, - cache, - ); - - if let Some(ref mut sub_mask) = mask.mask { - if let Some(ref mut sub_mask) = Arc::get_mut(sub_mask) { - update_paint_servers( - &mut sub_mask.root, - context_transform, - context_bbox, - None, - cache, - ); - } - } - } - } - - for filter in &mut g.filters { - if let Some(ref mut filter) = Arc::get_mut(filter) { - for primitive in &mut filter.primitives { - if let filter::Kind::Image(ref mut image) = primitive.kind { - update_paint_servers( - &mut image.root, - context_transform, - context_bbox, - None, - cache, - ); - } - } - } - } - - update_paint_servers(g, context_transform, context_bbox, text_bbox, cache); - } - Node::Path(ref mut path) => { - // Paths inside `Text::flattened` are special and must use text's bounding box - // instead of their own. - let bbox = text_bbox.unwrap_or(path.bounding_box); - - process_fill( - &mut path.fill, - path.abs_transform, - context_transform, - context_bbox, - bbox, - cache, - ); - process_stroke( - &mut path.stroke, - path.abs_transform, - context_transform, - context_bbox, - bbox, - cache, - ); - } - Node::Image(ref mut image) => { - if let ImageKind::SVG(ref mut tree) = image.kind { - update_paint_servers(&mut tree.root, context_transform, context_bbox, None, cache); - } - } - Node::Text(ref mut text) => { - // By the SVG spec, `tspan` doesn't have a bbox and uses the parent `text` bbox. - // Therefore we have to use text's bbox when converting tspan and flatted text - // paint servers. - let bbox = text.bounding_box; - - // We need to update three things: - // 1. The fills/strokes of the original elements in the usvg tree. - // 2. The fills/strokes of the layouted elements of the text. - // 3. The fills/strokes of the outlined text. - - // 1. - for chunk in &mut text.chunks { - for span in &mut chunk.spans { - process_fill( - &mut span.fill, - text.abs_transform, - context_transform, - context_bbox, - bbox, - cache, - ); - process_stroke( - &mut span.stroke, - text.abs_transform, - context_transform, - context_bbox, - bbox, - cache, - ); - process_text_decoration(&mut span.decoration.underline, bbox, cache); - process_text_decoration(&mut span.decoration.overline, bbox, cache); - process_text_decoration(&mut span.decoration.line_through, bbox, cache); - } - } - - // 2. - #[cfg(feature = "text")] - for span in &mut text.layouted { - process_fill( - &mut span.fill, - text.abs_transform, - context_transform, - context_bbox, - bbox, - cache, - ); - process_stroke( - &mut span.stroke, - text.abs_transform, - context_transform, - context_bbox, - bbox, - cache, - ); - - let mut process_decoration = |path: &mut Path| { - process_fill( - &mut path.fill, - text.abs_transform, - context_transform, - context_bbox, - bbox, - cache, - ); - process_stroke( - &mut path.stroke, - text.abs_transform, - context_transform, - context_bbox, - bbox, - cache, - ); - }; - - if let Some(ref mut path) = span.overline { - process_decoration(path); - } - - if let Some(ref mut path) = span.underline { - process_decoration(path); - } - - if let Some(ref mut path) = span.line_through { - process_decoration(path); - } - } - - // 3. - update_paint_servers( - &mut text.flattened, - context_transform, - context_bbox, - Some(bbox), - cache, - ); - } - } -} - -fn process_fill( - fill: &mut Option, - path_transform: Transform, - context_transform: Transform, - context_bbox: Option, - bbox: Rect, - cache: &mut Cache, -) { - let mut ok = false; - if let Some(ref mut fill) = fill { - // Path context elements (i.e. for markers) have already been resolved, - // so we only care about use nodes. - ok = process_paint( - &mut fill.paint, - matches!(fill.context_element, Some(ContextElement::UseNode)), - context_transform, - context_bbox, - path_transform, - bbox, - cache, - ); - } - if !ok { - *fill = None; - } -} - -fn process_stroke( - stroke: &mut Option, - path_transform: Transform, - context_transform: Transform, - context_bbox: Option, - bbox: Rect, - cache: &mut Cache, -) { - let mut ok = false; - if let Some(ref mut stroke) = stroke { - // Path context elements (i.e. for markers) have already been resolved, - // so we only care about use nodes. - ok = process_paint( - &mut stroke.paint, - matches!(stroke.context_element, Some(ContextElement::UseNode)), - context_transform, - context_bbox, - path_transform, - bbox, - cache, - ); - } - if !ok { - *stroke = None; - } -} - -fn process_context_paint( - paint: &mut Paint, - context_transform: Transform, - path_transform: Transform, - cache: &mut Cache, -) -> Option<()> { - // The idea is the following: We have a certain context element that has - // a transform A, and further below in the tree we have for example a path - // whose paint has a transform C. In order to get from A to C, there is some - // transformation matrix B such that A x B = C. We now need to figure out - // a way to get from C back to A, so that the transformation of the paint - // matches the one from the context element, even if B was applied. How - // do we do that? We calculate CxB^(-1), which will overall then have - // the same effect as A. How do we calculate B^(-1)? - // --> (A^(-1)xC)^(-1) - let rev_transform = context_transform - .invert()? - .pre_concat(path_transform) - .invert()?; - - match paint { - Paint::Color(_) => {} - Paint::LinearGradient(ref lg) => { - let transform = lg.transform.post_concat(rev_transform); - *paint = Paint::LinearGradient(Arc::new(LinearGradient { - x1: lg.x1, - y1: lg.y1, - x2: lg.x2, - y2: lg.y2, - base: BaseGradient { - id: cache.gen_linear_gradient_id(), - units: lg.units, - transform, - spread_method: lg.spread_method, - stops: lg.stops.clone(), - }, - })); - } - Paint::RadialGradient(ref rg) => { - let transform = rg.transform.post_concat(rev_transform); - *paint = Paint::RadialGradient(Arc::new(RadialGradient { - cx: rg.cx, - cy: rg.cy, - r: rg.r, - fx: rg.fx, - fy: rg.fy, - base: BaseGradient { - id: cache.gen_radial_gradient_id(), - units: rg.units, - transform, - spread_method: rg.spread_method, - stops: rg.stops.clone(), - }, - })) - } - Paint::Pattern(ref pat) => { - let transform = pat.transform.post_concat(rev_transform); - *paint = Paint::Pattern(Arc::new(Pattern { - id: cache.gen_pattern_id(), - units: pat.units, - content_units: pat.content_units, - transform, - rect: pat.rect, - view_box: pat.view_box, - root: pat.root.clone(), - })) - } - } - - Some(()) -} - -pub(crate) fn process_paint( - paint: &mut Paint, - has_context: bool, - context_transform: Transform, - context_bbox: Option, - path_transform: Transform, - bbox: Rect, - cache: &mut Cache, -) -> bool { - if paint.units() == Units::ObjectBoundingBox - || paint.content_units() == Units::ObjectBoundingBox - { - let bbox = if has_context { - let Some(bbox) = context_bbox else { - return false; - }; - bbox - } else { - bbox - }; - - if paint.to_user_coordinates(bbox, cache).is_none() { - return false; - } - } - - if let Paint::Pattern(ref mut patt) = paint { - if let Some(ref mut patt) = Arc::get_mut(patt) { - update_paint_servers(&mut patt.root, Transform::default(), None, None, cache); - } - } - - if has_context { - process_context_paint(paint, context_transform, path_transform, cache); - } - - true -} - -fn process_text_decoration(style: &mut Option, bbox: Rect, cache: &mut Cache) { - if let Some(ref mut style) = style { - process_fill( - &mut style.fill, - Transform::default(), - Transform::default(), - None, - bbox, - cache, - ); - process_stroke( - &mut style.stroke, - Transform::default(), - Transform::default(), - None, - bbox, - cache, - ); - } -} - -impl Paint { - fn to_user_coordinates(&mut self, bbox: Rect, cache: &mut Cache) -> Option<()> { - let name = if matches!(self, Paint::Pattern(_)) { - "Pattern" - } else { - "Gradient" - }; - let bbox = bbox - .to_non_zero_rect() - .log_none(|| log::warn!("{} on zero-sized shapes is not allowed.", name))?; - - // `Arc::get_mut()` allow us to modify some paint servers in-place. - // This reduces the amount of cloning and preserves the original ID as well. - match self { - Paint::Color(_) => {} // unreachable - Paint::LinearGradient(ref mut lg) => { - let transform = lg.transform.post_concat(Transform::from_bbox(bbox)); - if let Some(ref mut lg) = Arc::get_mut(lg) { - lg.base.transform = transform; - lg.base.units = Units::UserSpaceOnUse; - } else { - *lg = Arc::new(LinearGradient { - x1: lg.x1, - y1: lg.y1, - x2: lg.x2, - y2: lg.y2, - base: BaseGradient { - id: cache.gen_linear_gradient_id(), - units: Units::UserSpaceOnUse, - transform, - spread_method: lg.spread_method, - stops: lg.stops.clone(), - }, - }); - } - } - Paint::RadialGradient(ref mut rg) => { - let transform = rg.transform.post_concat(Transform::from_bbox(bbox)); - if let Some(ref mut rg) = Arc::get_mut(rg) { - rg.base.transform = transform; - rg.base.units = Units::UserSpaceOnUse; - } else { - *rg = Arc::new(RadialGradient { - cx: rg.cx, - cy: rg.cy, - r: rg.r, - fx: rg.fx, - fy: rg.fy, - base: BaseGradient { - id: cache.gen_radial_gradient_id(), - units: Units::UserSpaceOnUse, - transform, - spread_method: rg.spread_method, - stops: rg.stops.clone(), - }, - }); - } - } - Paint::Pattern(ref mut patt) => { - let rect = if patt.units == Units::ObjectBoundingBox { - patt.rect.bbox_transform(bbox) - } else { - patt.rect - }; - - if let Some(ref mut patt) = Arc::get_mut(patt) { - patt.rect = rect; - patt.units = Units::UserSpaceOnUse; - - if patt.content_units == Units::ObjectBoundingBox && patt.view_box.is_none() { - // No need to shift patterns. - let transform = Transform::from_scale(bbox.width(), bbox.height()); - push_pattern_transform(&mut patt.root, transform); - } - - if let Some(view_box) = patt.view_box { - push_pattern_transform(&mut patt.root, view_box.to_transform(rect.size())); - } - - patt.content_units = Units::UserSpaceOnUse; - } else { - let mut root = if patt.content_units == Units::ObjectBoundingBox - && patt.view_box.is_none() - { - // No need to shift patterns. - let transform = Transform::from_scale(bbox.width(), bbox.height()); - - let mut g = patt.root.clone(); - push_pattern_transform(&mut g, transform); - g - } else { - patt.root.clone() - }; - - if let Some(view_box) = patt.view_box { - push_pattern_transform(&mut root, view_box.to_transform(rect.size())); - } - - *patt = Arc::new(Pattern { - id: cache.gen_pattern_id(), - units: Units::UserSpaceOnUse, - content_units: Units::UserSpaceOnUse, - transform: patt.transform, - rect, - view_box: patt.view_box, - root, - }) - } - } - } - - Some(()) - } -} - -fn push_pattern_transform(root: &mut Group, transform: Transform) { - // TODO: we should update abs_transform in all descendants as well - let mut g = std::mem::replace(root, Group::empty()); - g.transform = transform; - g.abs_transform = transform; - - root.children.push(Node::Group(Box::new(g))); - root.calculate_bounding_boxes(); -} - -impl Paint { - #[inline] - pub(crate) fn units(&self) -> Units { - match self { - Self::Color(_) => Units::UserSpaceOnUse, - Self::LinearGradient(ref lg) => lg.units, - Self::RadialGradient(ref rg) => rg.units, - Self::Pattern(ref patt) => patt.units, - } - } - - #[inline] - pub(crate) fn content_units(&self) -> Units { - match self { - Self::Pattern(ref patt) => patt.content_units, - _ => Units::UserSpaceOnUse, - } - } -} diff --git a/resvg-0.43.0/crates/usvg/src/parser/shapes.rs b/resvg-0.43.0/crates/usvg/src/parser/shapes.rs deleted file mode 100644 index 57e574b..0000000 --- a/resvg-0.43.0/crates/usvg/src/parser/shapes.rs +++ /dev/null @@ -1,330 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use std::sync::Arc; - -use svgtypes::Length; -use tiny_skia_path::Path; - -use super::svgtree::{AId, EId, SvgNode}; -use super::{converter, units}; -use crate::{ApproxEqUlps, IsValidLength, Rect}; - -pub(crate) fn convert(node: SvgNode, state: &converter::State) -> Option> { - match node.tag_name()? { - EId::Rect => convert_rect(node, state), - EId::Circle => convert_circle(node, state), - EId::Ellipse => convert_ellipse(node, state), - EId::Line => convert_line(node, state), - EId::Polyline => convert_polyline(node), - EId::Polygon => convert_polygon(node), - EId::Path => convert_path(node), - _ => None, - } -} - -pub(crate) fn convert_path(node: SvgNode) -> Option> { - let value: &str = node.attribute(AId::D)?; - let mut builder = tiny_skia_path::PathBuilder::new(); - for segment in svgtypes::SimplifyingPathParser::from(value) { - let segment = match segment { - Ok(v) => v, - Err(_) => break, - }; - - match segment { - svgtypes::SimplePathSegment::MoveTo { x, y } => { - builder.move_to(x as f32, y as f32); - } - svgtypes::SimplePathSegment::LineTo { x, y } => { - builder.line_to(x as f32, y as f32); - } - svgtypes::SimplePathSegment::Quadratic { x1, y1, x, y } => { - builder.quad_to(x1 as f32, y1 as f32, x as f32, y as f32); - } - svgtypes::SimplePathSegment::CurveTo { - x1, - y1, - x2, - y2, - x, - y, - } => { - builder.cubic_to( - x1 as f32, y1 as f32, x2 as f32, y2 as f32, x as f32, y as f32, - ); - } - svgtypes::SimplePathSegment::ClosePath => { - builder.close(); - } - } - } - - builder.finish().map(Arc::new) -} - -fn convert_rect(node: SvgNode, state: &converter::State) -> Option> { - // 'width' and 'height' attributes must be positive and non-zero. - let width = node.convert_user_length(AId::Width, state, Length::zero()); - let height = node.convert_user_length(AId::Height, state, Length::zero()); - if !width.is_valid_length() { - log::warn!( - "Rect '{}' has an invalid 'width' value. Skipped.", - node.element_id() - ); - return None; - } - if !height.is_valid_length() { - log::warn!( - "Rect '{}' has an invalid 'height' value. Skipped.", - node.element_id() - ); - return None; - } - - let x = node.convert_user_length(AId::X, state, Length::zero()); - let y = node.convert_user_length(AId::Y, state, Length::zero()); - - let (mut rx, mut ry) = resolve_rx_ry(node, state); - - // Clamp rx/ry to the half of the width/height. - // - // Should be done only after resolving. - if rx > width / 2.0 { - rx = width / 2.0; - } - if ry > height / 2.0 { - ry = height / 2.0; - } - - // Conversion according to https://www.w3.org/TR/SVG11/shapes.html#RectElement - let path = if rx.approx_eq_ulps(&0.0, 4) { - tiny_skia_path::PathBuilder::from_rect(Rect::from_xywh(x, y, width, height)?) - } else { - let mut builder = tiny_skia_path::PathBuilder::new(); - builder.move_to(x + rx, y); - - builder.line_to(x + width - rx, y); - builder.arc_to(rx, ry, 0.0, false, true, x + width, y + ry); - - builder.line_to(x + width, y + height - ry); - builder.arc_to(rx, ry, 0.0, false, true, x + width - rx, y + height); - - builder.line_to(x + rx, y + height); - builder.arc_to(rx, ry, 0.0, false, true, x, y + height - ry); - - builder.line_to(x, y + ry); - builder.arc_to(rx, ry, 0.0, false, true, x + rx, y); - - builder.close(); - - builder.finish()? - }; - - Some(Arc::new(path)) -} - -fn resolve_rx_ry(node: SvgNode, state: &converter::State) -> (f32, f32) { - let mut rx_opt = node.attribute::(AId::Rx); - let mut ry_opt = node.attribute::(AId::Ry); - - // Remove negative values first. - if let Some(v) = rx_opt { - if v.number.is_sign_negative() { - rx_opt = None; - } - } - if let Some(v) = ry_opt { - if v.number.is_sign_negative() { - ry_opt = None; - } - } - - // Resolve. - match (rx_opt, ry_opt) { - (None, None) => (0.0, 0.0), - (Some(rx), None) => { - let rx = units::convert_user_length(rx, node, AId::Rx, state); - (rx, rx) - } - (None, Some(ry)) => { - let ry = units::convert_user_length(ry, node, AId::Ry, state); - (ry, ry) - } - (Some(rx), Some(ry)) => { - let rx = units::convert_user_length(rx, node, AId::Rx, state); - let ry = units::convert_user_length(ry, node, AId::Ry, state); - (rx, ry) - } - } -} - -fn convert_line(node: SvgNode, state: &converter::State) -> Option> { - let x1 = node.convert_user_length(AId::X1, state, Length::zero()); - let y1 = node.convert_user_length(AId::Y1, state, Length::zero()); - let x2 = node.convert_user_length(AId::X2, state, Length::zero()); - let y2 = node.convert_user_length(AId::Y2, state, Length::zero()); - - let mut builder = tiny_skia_path::PathBuilder::new(); - builder.move_to(x1, y1); - builder.line_to(x2, y2); - builder.finish().map(Arc::new) -} - -fn convert_polyline(node: SvgNode) -> Option> { - let builder = points_to_path(node, "Polyline")?; - builder.finish().map(Arc::new) -} - -fn convert_polygon(node: SvgNode) -> Option> { - let mut builder = points_to_path(node, "Polygon")?; - builder.close(); - builder.finish().map(Arc::new) -} - -fn points_to_path(node: SvgNode, eid: &str) -> Option { - use svgtypes::PointsParser; - - let mut builder = tiny_skia_path::PathBuilder::new(); - match node.attribute::<&str>(AId::Points) { - Some(text) => { - for (x, y) in PointsParser::from(text) { - if builder.is_empty() { - builder.move_to(x as f32, y as f32); - } else { - builder.line_to(x as f32, y as f32); - } - } - } - _ => { - log::warn!( - "{} '{}' has an invalid 'points' value. Skipped.", - eid, - node.element_id() - ); - return None; - } - }; - - // 'polyline' and 'polygon' elements must contain at least 2 points. - if builder.len() < 2 { - log::warn!( - "{} '{}' has less than 2 points. Skipped.", - eid, - node.element_id() - ); - return None; - } - - Some(builder) -} - -fn convert_circle(node: SvgNode, state: &converter::State) -> Option> { - let cx = node.convert_user_length(AId::Cx, state, Length::zero()); - let cy = node.convert_user_length(AId::Cy, state, Length::zero()); - let r = node.convert_user_length(AId::R, state, Length::zero()); - - if !r.is_valid_length() { - log::warn!( - "Circle '{}' has an invalid 'r' value. Skipped.", - node.element_id() - ); - return None; - } - - ellipse_to_path(cx, cy, r, r) -} - -fn convert_ellipse(node: SvgNode, state: &converter::State) -> Option> { - let cx = node.convert_user_length(AId::Cx, state, Length::zero()); - let cy = node.convert_user_length(AId::Cy, state, Length::zero()); - let (rx, ry) = resolve_rx_ry(node, state); - - if !rx.is_valid_length() { - log::warn!( - "Ellipse '{}' has an invalid 'rx' value. Skipped.", - node.element_id() - ); - return None; - } - - if !ry.is_valid_length() { - log::warn!( - "Ellipse '{}' has an invalid 'ry' value. Skipped.", - node.element_id() - ); - return None; - } - - ellipse_to_path(cx, cy, rx, ry) -} - -fn ellipse_to_path(cx: f32, cy: f32, rx: f32, ry: f32) -> Option> { - let mut builder = tiny_skia_path::PathBuilder::new(); - builder.move_to(cx + rx, cy); - builder.arc_to(rx, ry, 0.0, false, true, cx, cy + ry); - builder.arc_to(rx, ry, 0.0, false, true, cx - rx, cy); - builder.arc_to(rx, ry, 0.0, false, true, cx, cy - ry); - builder.arc_to(rx, ry, 0.0, false, true, cx + rx, cy); - builder.close(); - builder.finish().map(Arc::new) -} - -trait PathBuilderExt { - fn arc_to( - &mut self, - rx: f32, - ry: f32, - x_axis_rotation: f32, - large_arc: bool, - sweep: bool, - x: f32, - y: f32, - ); -} - -impl PathBuilderExt for tiny_skia_path::PathBuilder { - fn arc_to( - &mut self, - rx: f32, - ry: f32, - x_axis_rotation: f32, - large_arc: bool, - sweep: bool, - x: f32, - y: f32, - ) { - let prev = match self.last_point() { - Some(v) => v, - None => return, - }; - - let svg_arc = kurbo::SvgArc { - from: kurbo::Point::new(prev.x as f64, prev.y as f64), - to: kurbo::Point::new(x as f64, y as f64), - radii: kurbo::Vec2::new(rx as f64, ry as f64), - x_rotation: (x_axis_rotation as f64).to_radians(), - large_arc, - sweep, - }; - - match kurbo::Arc::from_svg_arc(&svg_arc) { - Some(arc) => { - arc.to_cubic_beziers(0.1, |p1, p2, p| { - self.cubic_to( - p1.x as f32, - p1.y as f32, - p2.x as f32, - p2.y as f32, - p.x as f32, - p.y as f32, - ); - }); - } - None => { - self.line_to(x, y); - } - } - } -} diff --git a/resvg-0.43.0/crates/usvg/src/parser/style.rs b/resvg-0.43.0/crates/usvg/src/parser/style.rs deleted file mode 100644 index 47af77e..0000000 --- a/resvg-0.43.0/crates/usvg/src/parser/style.rs +++ /dev/null @@ -1,280 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use super::converter::{self, SvgColorExt}; -use super::paint_server; -use super::svgtree::{AId, FromValue, SvgNode}; -use crate::tree::ContextElement; -use crate::{ - ApproxEqUlps, Color, Fill, FillRule, LineCap, LineJoin, Opacity, Paint, Stroke, - StrokeMiterlimit, Units, -}; - -impl<'a, 'input: 'a> FromValue<'a, 'input> for LineCap { - fn parse(_: SvgNode, _: AId, value: &str) -> Option { - match value { - "butt" => Some(LineCap::Butt), - "round" => Some(LineCap::Round), - "square" => Some(LineCap::Square), - _ => None, - } - } -} - -impl<'a, 'input: 'a> FromValue<'a, 'input> for LineJoin { - fn parse(_: SvgNode, _: AId, value: &str) -> Option { - match value { - "miter" => Some(LineJoin::Miter), - "miter-clip" => Some(LineJoin::MiterClip), - "round" => Some(LineJoin::Round), - "bevel" => Some(LineJoin::Bevel), - _ => None, - } - } -} - -impl<'a, 'input: 'a> FromValue<'a, 'input> for FillRule { - fn parse(_: SvgNode, _: AId, value: &str) -> Option { - match value { - "nonzero" => Some(FillRule::NonZero), - "evenodd" => Some(FillRule::EvenOdd), - _ => None, - } - } -} - -pub(crate) fn resolve_fill( - node: SvgNode, - has_bbox: bool, - state: &converter::State, - cache: &mut converter::Cache, -) -> Option { - if state.parent_clip_path.is_some() { - // A `clipPath` child can be filled only with a black color. - return Some(Fill { - paint: Paint::Color(Color::black()), - opacity: Opacity::ONE, - rule: node.find_attribute(AId::ClipRule).unwrap_or_default(), - context_element: None, - }); - } - - let mut sub_opacity = Opacity::ONE; - let (paint, context_element) = - if let Some(n) = node.ancestors().find(|n| n.has_attribute(AId::Fill)) { - convert_paint(n, AId::Fill, has_bbox, state, &mut sub_opacity, cache)? - } else { - (Paint::Color(Color::black()), None) - }; - - let fill_opacity = node - .find_attribute::(AId::FillOpacity) - .unwrap_or(Opacity::ONE); - - Some(Fill { - paint, - opacity: sub_opacity * fill_opacity, - rule: node.find_attribute(AId::FillRule).unwrap_or_default(), - context_element, - }) -} - -pub(crate) fn resolve_stroke( - node: SvgNode, - has_bbox: bool, - state: &converter::State, - cache: &mut converter::Cache, -) -> Option { - if state.parent_clip_path.is_some() { - // A `clipPath` child cannot be stroked. - return None; - } - - let mut sub_opacity = Opacity::ONE; - let (paint, context_element) = - if let Some(n) = node.ancestors().find(|n| n.has_attribute(AId::Stroke)) { - convert_paint(n, AId::Stroke, has_bbox, state, &mut sub_opacity, cache)? - } else { - return None; - }; - - let width = node.resolve_valid_length(AId::StrokeWidth, state, 1.0)?; - - // Must be bigger than 1. - let miterlimit = node.find_attribute(AId::StrokeMiterlimit).unwrap_or(4.0); - let miterlimit = if miterlimit < 1.0 { 1.0 } else { miterlimit }; - let miterlimit = StrokeMiterlimit::new(miterlimit); - - let stroke_opacity = node - .find_attribute::(AId::StrokeOpacity) - .unwrap_or(Opacity::ONE); - - let stroke = Stroke { - paint, - dasharray: conv_dasharray(node, state), - dashoffset: node.resolve_length(AId::StrokeDashoffset, state, 0.0), - miterlimit, - opacity: sub_opacity * stroke_opacity, - width, - linecap: node.find_attribute(AId::StrokeLinecap).unwrap_or_default(), - linejoin: node.find_attribute(AId::StrokeLinejoin).unwrap_or_default(), - context_element, - }; - - Some(stroke) -} - -fn convert_paint( - node: SvgNode, - aid: AId, - has_bbox: bool, - state: &converter::State, - opacity: &mut Opacity, - cache: &mut converter::Cache, -) -> Option<(Paint, Option)> { - let value: &str = node.attribute(aid)?; - let paint = match svgtypes::Paint::from_str(value) { - Ok(v) => v, - Err(_) => { - if aid == AId::Fill { - log::warn!( - "Failed to parse fill value: '{}'. Fallback to black.", - value - ); - svgtypes::Paint::Color(svgtypes::Color::black()) - } else if aid == AId::Stroke { - log::warn!( - "Failed to parse stroke value: '{}'. Fallback to no stroke.", - value - ); - return None; - } else { - return None; - } - } - }; - - match paint { - svgtypes::Paint::None => None, - svgtypes::Paint::Inherit => None, // already resolved by svgtree - svgtypes::Paint::ContextFill => state - .context_element - .clone() - .map(|(f, _)| f) - .flatten() - .map(|f| (f.paint, f.context_element)), - svgtypes::Paint::ContextStroke => state - .context_element - .clone() - .map(|(_, s)| s) - .flatten() - .map(|s| (s.paint, s.context_element)), - svgtypes::Paint::CurrentColor => { - let svg_color: svgtypes::Color = node - .find_attribute(AId::Color) - .unwrap_or_else(svgtypes::Color::black); - let (color, alpha) = svg_color.split_alpha(); - *opacity = alpha; - Some((Paint::Color(color), None)) - } - svgtypes::Paint::Color(svg_color) => { - let (color, alpha) = svg_color.split_alpha(); - *opacity = alpha; - Some((Paint::Color(color), None)) - } - svgtypes::Paint::FuncIRI(func_iri, fallback) => { - if let Some(link) = node.document().element_by_id(func_iri) { - let tag_name = link.tag_name().unwrap(); - if tag_name.is_paint_server() { - match paint_server::convert(link, state, cache) { - Some(paint_server::ServerOrColor::Server(paint)) => { - // We can use a paint server node with ObjectBoundingBox units - // for painting only when the shape itself has a bbox. - // - // See SVG spec 7.11 for details. - - if !has_bbox && paint.units() == Units::ObjectBoundingBox { - from_fallback(node, fallback, opacity).map(|p| (p, None)) - } else { - Some((paint, None)) - } - } - Some(paint_server::ServerOrColor::Color { color, opacity: so }) => { - *opacity = so; - Some((Paint::Color(color), None)) - } - None => from_fallback(node, fallback, opacity).map(|p| (p, None)), - } - } else { - log::warn!("'{}' cannot be used to {} a shape.", tag_name, aid); - None - } - } else { - from_fallback(node, fallback, opacity).map(|p| (p, None)) - } - } - } -} - -fn from_fallback( - node: SvgNode, - fallback: Option, - opacity: &mut Opacity, -) -> Option { - match fallback? { - svgtypes::PaintFallback::None => None, - svgtypes::PaintFallback::CurrentColor => { - let svg_color: svgtypes::Color = node - .find_attribute(AId::Color) - .unwrap_or_else(svgtypes::Color::black); - let (color, alpha) = svg_color.split_alpha(); - *opacity = alpha; - Some(Paint::Color(color)) - } - svgtypes::PaintFallback::Color(svg_color) => { - let (color, alpha) = svg_color.split_alpha(); - *opacity = alpha; - Some(Paint::Color(color)) - } - } -} - -// Prepare the 'stroke-dasharray' according to: -// https://www.w3.org/TR/SVG11/painting.html#StrokeDasharrayProperty -fn conv_dasharray(node: SvgNode, state: &converter::State) -> Option> { - let node = node - .ancestors() - .find(|n| n.has_attribute(AId::StrokeDasharray))?; - let list = super::units::convert_list(node, AId::StrokeDasharray, state)?; - - // `A negative value is an error` - if list.iter().any(|n| n.is_sign_negative()) { - return None; - } - - // `If the sum of the values is zero, then the stroke is rendered - // as if a value of none were specified.` - { - // no Iter::sum(), because of f64 - - let mut sum: f32 = 0.0; - for n in list.iter() { - sum += *n; - } - - if sum.approx_eq_ulps(&0.0, 4) { - return None; - } - } - - // `If an odd number of values is provided, then the list of values - // is repeated to yield an even number of values.` - if list.len() % 2 != 0 { - let mut tmp_list = list.clone(); - tmp_list.extend_from_slice(&list); - return Some(tmp_list); - } - - Some(list) -} diff --git a/resvg-0.43.0/crates/usvg/src/parser/svgtree/mod.rs b/resvg-0.43.0/crates/usvg/src/parser/svgtree/mod.rs deleted file mode 100644 index e0e6a1f..0000000 --- a/resvg-0.43.0/crates/usvg/src/parser/svgtree/mod.rs +++ /dev/null @@ -1,1061 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use std::collections::HashMap; -use std::num::NonZeroU32; -use std::str::FromStr; - -#[rustfmt::skip] mod names; -mod parse; -mod text; - -use tiny_skia_path::Transform; - -use crate::{ - BlendMode, ImageRendering, Opacity, ShapeRendering, SpreadMethod, TextRendering, Units, - Visibility, -}; -pub use names::{AId, EId}; - -/// An SVG tree container. -/// -/// Contains only element and text nodes. -/// Text nodes are present only inside the `text` element. -pub struct Document<'input> { - nodes: Vec, - attrs: Vec>, - links: HashMap, -} - -impl<'input> Document<'input> { - /// Returns the root node. - #[inline] - pub fn root<'a>(&'a self) -> SvgNode<'a, 'input> { - SvgNode { - id: NodeId::new(0), - d: &self.nodes[0], - doc: self, - } - } - - /// Returns the root element. - #[inline] - pub fn root_element<'a>(&'a self) -> SvgNode<'a, 'input> { - // `unwrap` is safe, because `Document` is guarantee to have at least one element. - self.root().first_element_child().unwrap() - } - - /// Returns an iterator over document's descendant nodes. - /// - /// Shorthand for `doc.root().descendants()`. - #[inline] - pub fn descendants<'a>(&'a self) -> Descendants<'a, 'input> { - self.root().descendants() - } - - /// Returns an element by ID. - /// - /// Unlike the [`Descendants`] iterator, this is just a HashMap lookup. - /// Meaning it's way faster. - #[inline] - pub fn element_by_id<'a>(&'a self, id: &str) -> Option> { - let node_id = self.links.get(id)?; - Some(self.get(*node_id)) - } - - #[inline] - fn get<'a>(&'a self, id: NodeId) -> SvgNode<'a, 'input> { - SvgNode { - id, - d: &self.nodes[id.get_usize()], - doc: self, - } - } -} - -impl std::fmt::Debug for Document<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { - if !self.root().has_children() { - return write!(f, "Document []"); - } - - macro_rules! writeln_indented { - ($depth:expr, $f:expr, $fmt:expr) => { - for _ in 0..$depth { write!($f, " ")?; } - writeln!($f, $fmt)?; - }; - ($depth:expr, $f:expr, $fmt:expr, $($arg:tt)*) => { - for _ in 0..$depth { write!($f, " ")?; } - writeln!($f, $fmt, $($arg)*)?; - }; - } - - fn print_children( - parent: SvgNode, - depth: usize, - f: &mut std::fmt::Formatter, - ) -> Result<(), std::fmt::Error> { - for child in parent.children() { - if child.is_element() { - writeln_indented!(depth, f, "Element {{"); - writeln_indented!(depth, f, " tag_name: {:?}", child.tag_name()); - - if !child.attributes().is_empty() { - writeln_indented!(depth + 1, f, "attributes: ["); - for attr in child.attributes() { - writeln_indented!(depth + 2, f, "{:?}", attr); - } - writeln_indented!(depth + 1, f, "]"); - } - - if child.has_children() { - writeln_indented!(depth, f, " children: ["); - print_children(child, depth + 2, f)?; - writeln_indented!(depth, f, " ]"); - } - - writeln_indented!(depth, f, "}}"); - } else { - writeln_indented!(depth, f, "{:?}", child); - } - } - - Ok(()) - } - - writeln!(f, "Document [")?; - print_children(self.root(), 1, f)?; - writeln!(f, "]")?; - - Ok(()) - } -} - -#[derive(Clone, Copy, Debug)] -pub(crate) struct ShortRange { - start: u32, - end: u32, -} - -impl ShortRange { - #[inline] - fn new(start: u32, end: u32) -> Self { - ShortRange { start, end } - } - - #[inline] - fn to_urange(self) -> std::ops::Range { - self.start as usize..self.end as usize - } -} - -#[derive(Clone, Copy, PartialEq, Debug)] -pub(crate) struct NodeId(NonZeroU32); - -impl NodeId { - #[inline] - fn new(id: u32) -> Self { - debug_assert!(id < core::u32::MAX); - - // We are using `NonZeroU32` to reduce overhead of `Option`. - NodeId(NonZeroU32::new(id + 1).unwrap()) - } - - #[inline] - fn get(self) -> u32 { - self.0.get() - 1 - } - - #[inline] - fn get_usize(self) -> usize { - self.get() as usize - } -} - -impl From for NodeId { - #[inline] - fn from(id: usize) -> Self { - // We already checked that `id` is limited by u32::MAX. - debug_assert!(id <= core::u32::MAX as usize); - NodeId::new(id as u32) - } -} - -pub(crate) enum NodeKind { - Root, - Element { - tag_name: EId, - attributes: ShortRange, - }, - Text(String), -} - -struct NodeData { - parent: Option, - next_sibling: Option, - children: Option<(NodeId, NodeId)>, - kind: NodeKind, -} - -/// An attribute. -#[derive(Clone)] -pub struct Attribute<'input> { - /// Attribute's name. - pub name: AId, - /// Attribute's value. - pub value: roxmltree::StringStorage<'input>, -} - -impl std::fmt::Debug for Attribute<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { - write!( - f, - "Attribute {{ name: {:?}, value: {} }}", - self.name, self.value - ) - } -} - -/// An SVG node. -#[derive(Clone, Copy)] -pub struct SvgNode<'a, 'input: 'a> { - id: NodeId, - doc: &'a Document<'input>, - d: &'a NodeData, -} - -impl Eq for SvgNode<'_, '_> {} - -impl PartialEq for SvgNode<'_, '_> { - #[inline] - fn eq(&self, other: &Self) -> bool { - self.id == other.id && std::ptr::eq(self.doc, other.doc) && std::ptr::eq(self.d, other.d) - } -} - -impl<'a, 'input: 'a> SvgNode<'a, 'input> { - #[inline] - fn id(&self) -> NodeId { - self.id - } - - /// Checks if the current node is an element. - #[inline] - pub fn is_element(&self) -> bool { - matches!(self.d.kind, NodeKind::Element { .. }) - } - - /// Checks if the current node is a text. - #[inline] - pub fn is_text(&self) -> bool { - matches!(self.d.kind, NodeKind::Text(_)) - } - - /// Returns node's document. - #[inline] - pub fn document(&self) -> &'a Document<'input> { - self.doc - } - - /// Returns element's tag name, unless the current node is text. - #[inline] - pub fn tag_name(&self) -> Option { - match self.d.kind { - NodeKind::Element { tag_name, .. } => Some(tag_name), - _ => None, - } - } - /// Returns element's `id` attribute value. - /// - /// Returns an empty string otherwise. - #[inline] - pub fn element_id(&self) -> &'a str { - self.attribute(AId::Id).unwrap_or("") - } - - /// Returns an attribute value. - pub fn attribute>(&self, aid: AId) -> Option { - let value = self - .attributes() - .iter() - .find(|a| a.name == aid) - .map(|a| a.value.as_str())?; - match T::parse(*self, aid, value) { - Some(v) => Some(v), - None => { - // TODO: show position in XML - log::warn!("Failed to parse {} value: '{}'.", aid, value); - None - } - } - } - - /// Returns an attribute value. - /// - /// Same as `SvgNode::attribute`, but doesn't show a warning. - pub fn try_attribute>(&self, aid: AId) -> Option { - let value = self - .attributes() - .iter() - .find(|a| a.name == aid) - .map(|a| a.value.as_str())?; - T::parse(*self, aid, value) - } - - #[inline] - fn node_attribute(&self, aid: AId) -> Option> { - let value = self.attribute(aid)?; - let id = if aid == AId::Href { - svgtypes::IRI::from_str(value).ok().map(|v| v.0) - } else { - svgtypes::FuncIRI::from_str(value).ok().map(|v| v.0) - }?; - - self.document().element_by_id(id) - } - - /// Checks if an attribute is present. - #[inline] - pub fn has_attribute(&self, aid: AId) -> bool { - self.attributes().iter().any(|a| a.name == aid) - } - - /// Returns a list of all element's attributes. - #[inline] - pub fn attributes(&self) -> &'a [Attribute<'input>] { - match self.d.kind { - NodeKind::Element { ref attributes, .. } => &self.doc.attrs[attributes.to_urange()], - _ => &[], - } - } - - #[inline] - fn attribute_id(&self, aid: AId) -> Option { - match self.d.kind { - NodeKind::Element { ref attributes, .. } => { - let idx = self.attributes().iter().position(|attr| attr.name == aid)?; - Some(attributes.start as usize + idx) - } - _ => None, - } - } - - /// Finds a [`Node`] that contains the required attribute. - /// - /// For inheritable attributes walks over ancestors until a node with - /// the specified attribute is found. - /// - /// For non-inheritable attributes checks only the current node and the parent one. - /// As per SVG spec. - pub fn find_attribute>(&self, aid: AId) -> Option { - self.find_attribute_impl(aid)?.attribute(aid) - } - - fn find_attribute_impl(&self, aid: AId) -> Option> { - if aid.is_inheritable() { - for n in self.ancestors() { - if n.has_attribute(aid) { - return Some(n); - } - } - - None - } else { - if self.has_attribute(aid) { - Some(*self) - } else { - // Non-inheritable attributes can inherit a value only from a direct parent. - let n = self.parent_element()?; - if n.has_attribute(aid) { - Some(n) - } else { - None - } - } - } - } - - /// Returns node's text data. - /// - /// For text nodes returns its content. For elements returns the first child node text. - #[inline] - pub fn text(&self) -> &'a str { - match self.d.kind { - NodeKind::Element { .. } => match self.first_child() { - Some(child) if child.is_text() => match self.doc.nodes[child.id.get_usize()].kind { - NodeKind::Text(ref text) => text, - _ => "", - }, - _ => "", - }, - NodeKind::Text(ref text) => text, - _ => "", - } - } - - /// Returns a parent node. - #[inline] - pub fn parent(&self) -> Option { - self.d.parent.map(|id| self.doc.get(id)) - } - - /// Returns the parent element. - #[inline] - pub fn parent_element(&self) -> Option { - self.ancestors().skip(1).find(|n| n.is_element()) - } - - /// Returns the next sibling. - #[inline] - pub fn next_sibling(&self) -> Option { - self.d.next_sibling.map(|id| self.doc.get(id)) - } - - /// Returns the first child. - #[inline] - pub fn first_child(&self) -> Option { - self.d.children.map(|(id, _)| self.doc.get(id)) - } - - /// Returns the first child element. - #[inline] - pub fn first_element_child(&self) -> Option { - self.children().find(|n| n.is_element()) - } - - /// Returns the last child. - #[inline] - pub fn last_child(&self) -> Option { - self.d.children.map(|(_, id)| self.doc.get(id)) - } - - /// Checks if the node has child nodes. - #[inline] - pub fn has_children(&self) -> bool { - self.d.children.is_some() - } - - /// Returns an iterator over ancestor nodes starting at this node. - #[inline] - pub fn ancestors(&self) -> Ancestors<'a, 'input> { - Ancestors(Some(*self)) - } - - /// Returns an iterator over children nodes. - #[inline] - pub fn children(&self) -> Children<'a, 'input> { - Children { - front: self.first_child(), - back: self.last_child(), - } - } - - /// Returns an iterator which traverses the subtree starting at this node. - #[inline] - fn traverse(&self) -> Traverse<'a, 'input> { - Traverse { - root: *self, - edge: None, - } - } - - /// Returns an iterator over this node and its descendants. - #[inline] - pub fn descendants(&self) -> Descendants<'a, 'input> { - Descendants(self.traverse()) - } - - /// Returns an iterator over elements linked via `xlink:href`. - #[inline] - pub fn href_iter(&self) -> HrefIter<'a, 'input> { - HrefIter { - doc: self.document(), - origin: self.id(), - curr: self.id(), - is_first: true, - is_finished: false, - } - } -} - -impl std::fmt::Debug for SvgNode<'_, '_> { - fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { - match self.d.kind { - NodeKind::Root => write!(f, "Root"), - NodeKind::Element { .. } => { - write!( - f, - "Element {{ tag_name: {:?}, attributes: {:?} }}", - self.tag_name(), - self.attributes() - ) - } - NodeKind::Text(ref text) => write!(f, "Text({:?})", text), - } - } -} - -/// An iterator over ancestor nodes. -#[derive(Clone, Debug)] -pub struct Ancestors<'a, 'input: 'a>(Option>); - -impl<'a, 'input: 'a> Iterator for Ancestors<'a, 'input> { - type Item = SvgNode<'a, 'input>; - - #[inline] - fn next(&mut self) -> Option { - let node = self.0.take(); - self.0 = node.as_ref().and_then(SvgNode::parent); - node - } -} - -/// An iterator over children nodes. -#[derive(Clone, Debug)] -pub struct Children<'a, 'input: 'a> { - front: Option>, - back: Option>, -} - -impl<'a, 'input: 'a> Iterator for Children<'a, 'input> { - type Item = SvgNode<'a, 'input>; - - fn next(&mut self) -> Option { - let node = self.front.take(); - if self.front == self.back { - self.back = None; - } else { - self.front = node.as_ref().and_then(SvgNode::next_sibling); - } - node - } -} - -#[derive(Clone, Copy, PartialEq, Debug)] -enum Edge<'a, 'input: 'a> { - Open(SvgNode<'a, 'input>), - Close(SvgNode<'a, 'input>), -} - -#[derive(Clone, Debug)] -struct Traverse<'a, 'input: 'a> { - root: SvgNode<'a, 'input>, - edge: Option>, -} - -impl<'a, 'input: 'a> Iterator for Traverse<'a, 'input> { - type Item = Edge<'a, 'input>; - - fn next(&mut self) -> Option { - match self.edge { - Some(Edge::Open(node)) => { - self.edge = Some(match node.first_child() { - Some(first_child) => Edge::Open(first_child), - None => Edge::Close(node), - }); - } - Some(Edge::Close(node)) => { - if node == self.root { - self.edge = None; - } else if let Some(next_sibling) = node.next_sibling() { - self.edge = Some(Edge::Open(next_sibling)); - } else { - self.edge = node.parent().map(Edge::Close); - } - } - None => { - self.edge = Some(Edge::Open(self.root)); - } - } - - self.edge - } -} - -/// A descendants iterator. -#[derive(Clone, Debug)] -pub struct Descendants<'a, 'input: 'a>(Traverse<'a, 'input>); - -impl<'a, 'input: 'a> Iterator for Descendants<'a, 'input> { - type Item = SvgNode<'a, 'input>; - - #[inline] - fn next(&mut self) -> Option { - for edge in &mut self.0 { - if let Edge::Open(node) = edge { - return Some(node); - } - } - - None - } -} - -/// An iterator over `xlink:href` references. -#[derive(Clone, Debug)] -pub struct HrefIter<'a, 'input: 'a> { - doc: &'a Document<'input>, - origin: NodeId, - curr: NodeId, - is_first: bool, - is_finished: bool, -} - -impl<'a, 'input: 'a> Iterator for HrefIter<'a, 'input> { - type Item = SvgNode<'a, 'input>; - - fn next(&mut self) -> Option { - if self.is_finished { - return None; - } - - if self.is_first { - self.is_first = false; - return Some(self.doc.get(self.curr)); - } - - if let Some(link) = self.doc.get(self.curr).node_attribute(AId::Href) { - if link.id() == self.curr || link.id() == self.origin { - log::warn!( - "Element '#{}' cannot reference itself via 'xlink:href'.", - self.doc.get(self.origin).element_id() - ); - self.is_finished = true; - return None; - } - - self.curr = link.id(); - Some(self.doc.get(self.curr)) - } else { - None - } - } -} - -impl EId { - /// Checks if this is a - /// [graphics element](https://www.w3.org/TR/SVG11/intro.html#TermGraphicsElement). - pub fn is_graphic(&self) -> bool { - matches!( - self, - EId::Circle - | EId::Ellipse - | EId::Image - | EId::Line - | EId::Path - | EId::Polygon - | EId::Polyline - | EId::Rect - | EId::Text - | EId::Use - ) - } - - /// Checks if this is a - /// [gradient element](https://www.w3.org/TR/SVG11/intro.html#TermGradientElement). - pub fn is_gradient(&self) -> bool { - matches!(self, EId::LinearGradient | EId::RadialGradient) - } - - /// Checks if this is a - /// [paint server element](https://www.w3.org/TR/SVG11/intro.html#TermPaint). - pub fn is_paint_server(&self) -> bool { - matches!( - self, - EId::LinearGradient | EId::RadialGradient | EId::Pattern - ) - } -} - -impl AId { - fn is_presentation(&self) -> bool { - matches!( - self, - AId::AlignmentBaseline - | AId::BaselineShift - | AId::ClipPath - | AId::ClipRule - | AId::Color - | AId::ColorInterpolation - | AId::ColorInterpolationFilters - | AId::ColorRendering - | AId::Direction - | AId::Display - | AId::DominantBaseline - | AId::Fill - | AId::FillOpacity - | AId::FillRule - | AId::Filter - | AId::FloodColor - | AId::FloodOpacity - | AId::FontFamily - | AId::FontKerning // technically not presentation - | AId::FontSize - | AId::FontSizeAdjust - | AId::FontStretch - | AId::FontStyle - | AId::FontVariant - | AId::FontWeight - | AId::GlyphOrientationHorizontal - | AId::GlyphOrientationVertical - | AId::ImageRendering - | AId::Isolation // technically not presentation - | AId::LetterSpacing - | AId::LightingColor - | AId::MarkerEnd - | AId::MarkerMid - | AId::MarkerStart - | AId::Mask - | AId::MaskType - | AId::MixBlendMode // technically not presentation - | AId::Opacity - | AId::Overflow - | AId::PaintOrder - | AId::ShapeRendering - | AId::StopColor - | AId::StopOpacity - | AId::Stroke - | AId::StrokeDasharray - | AId::StrokeDashoffset - | AId::StrokeLinecap - | AId::StrokeLinejoin - | AId::StrokeMiterlimit - | AId::StrokeOpacity - | AId::StrokeWidth - | AId::TextAnchor - | AId::TextDecoration - | AId::TextOverflow - | AId::TextRendering - | AId::Transform - | AId::TransformOrigin - | AId::UnicodeBidi - | AId::VectorEffect - | AId::Visibility - | AId::WhiteSpace - | AId::WordSpacing - | AId::WritingMode - ) - } - - /// Checks if the current attribute is inheritable. - fn is_inheritable(&self) -> bool { - if self.is_presentation() { - !is_non_inheritable(*self) - } else { - false - } - } - - fn allows_inherit_value(&self) -> bool { - matches!( - self, - AId::AlignmentBaseline - | AId::BaselineShift - | AId::ClipPath - | AId::ClipRule - | AId::Color - | AId::ColorInterpolationFilters - | AId::Direction - | AId::Display - | AId::DominantBaseline - | AId::Fill - | AId::FillOpacity - | AId::FillRule - | AId::Filter - | AId::FloodColor - | AId::FloodOpacity - | AId::FontFamily - | AId::FontKerning - | AId::FontSize - | AId::FontStretch - | AId::FontStyle - | AId::FontVariant - | AId::FontWeight - | AId::ImageRendering - | AId::Kerning - | AId::LetterSpacing - | AId::MarkerEnd - | AId::MarkerMid - | AId::MarkerStart - | AId::Mask - | AId::Opacity - | AId::Overflow - | AId::ShapeRendering - | AId::StopColor - | AId::StopOpacity - | AId::Stroke - | AId::StrokeDasharray - | AId::StrokeDashoffset - | AId::StrokeLinecap - | AId::StrokeLinejoin - | AId::StrokeMiterlimit - | AId::StrokeOpacity - | AId::StrokeWidth - | AId::TextAnchor - | AId::TextDecoration - | AId::TextRendering - | AId::Visibility - | AId::WordSpacing - | AId::WritingMode - ) - } -} - -fn is_non_inheritable(id: AId) -> bool { - matches!( - id, - AId::AlignmentBaseline - | AId::BaselineShift - | AId::ClipPath - | AId::Display - | AId::DominantBaseline - | AId::Filter - | AId::FloodColor - | AId::FloodOpacity - | AId::Mask - | AId::Opacity - | AId::Overflow - | AId::LightingColor - | AId::StopColor - | AId::StopOpacity - | AId::TextDecoration - | AId::Transform - | AId::TransformOrigin - ) -} - -// TODO: is there a way yo make it less ugly? Too many lifetimes. -/// A trait for parsing attribute values. -pub trait FromValue<'a, 'input: 'a>: Sized { - /// Parses an attribute value. - /// - /// When `None` is returned, the attribute value will be logged as a parsing failure. - fn parse(node: SvgNode<'a, 'input>, aid: AId, value: &'a str) -> Option; -} - -impl<'a, 'input: 'a> FromValue<'a, 'input> for &'a str { - fn parse(_: SvgNode<'a, 'input>, _: AId, value: &'a str) -> Option { - Some(value) - } -} - -impl<'a, 'input: 'a> FromValue<'a, 'input> for f32 { - fn parse(_: SvgNode, _: AId, value: &str) -> Option { - svgtypes::Number::from_str(value).ok().map(|v| v.0 as f32) - } -} - -impl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::Length { - fn parse(_: SvgNode, _: AId, value: &str) -> Option { - svgtypes::Length::from_str(value).ok() - } -} - -// TODO: to svgtypes? -impl<'a, 'input: 'a> FromValue<'a, 'input> for Opacity { - fn parse(_: SvgNode, _: AId, value: &str) -> Option { - let length = svgtypes::Length::from_str(value).ok()?; - if length.unit == svgtypes::LengthUnit::Percent { - Some(Opacity::new_clamped(length.number as f32 / 100.0)) - } else if length.unit == svgtypes::LengthUnit::None { - Some(Opacity::new_clamped(length.number as f32)) - } else { - None - } - } -} - -impl<'a, 'input: 'a> FromValue<'a, 'input> for Transform { - fn parse(_: SvgNode, _: AId, value: &str) -> Option { - let ts = match svgtypes::Transform::from_str(value) { - Ok(v) => v, - Err(_) => return None, - }; - - let ts = Transform::from_row( - ts.a as f32, - ts.b as f32, - ts.c as f32, - ts.d as f32, - ts.e as f32, - ts.f as f32, - ); - - if ts.is_valid() { - Some(ts) - } else { - Some(Transform::default()) - } - } -} - -impl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::TransformOrigin { - fn parse(_: SvgNode, _: AId, value: &str) -> Option { - Self::from_str(value).ok() - } -} - -impl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::ViewBox { - fn parse(_: SvgNode, _: AId, value: &str) -> Option { - Self::from_str(value).ok() - } -} - -impl<'a, 'input: 'a> FromValue<'a, 'input> for Units { - fn parse(_: SvgNode, _: AId, value: &str) -> Option { - match value { - "userSpaceOnUse" => Some(Units::UserSpaceOnUse), - "objectBoundingBox" => Some(Units::ObjectBoundingBox), - _ => None, - } - } -} - -impl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::AspectRatio { - fn parse(_: SvgNode, _: AId, value: &str) -> Option { - Self::from_str(value).ok() - } -} - -impl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::PaintOrder { - fn parse(_: SvgNode, _: AId, value: &str) -> Option { - Self::from_str(value).ok() - } -} - -impl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::Color { - fn parse(_: SvgNode, _: AId, value: &str) -> Option { - Self::from_str(value).ok() - } -} - -impl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::Angle { - fn parse(_: SvgNode, _: AId, value: &str) -> Option { - Self::from_str(value).ok() - } -} - -impl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::EnableBackground { - fn parse(_: SvgNode, _: AId, value: &str) -> Option { - Self::from_str(value).ok() - } -} - -impl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::Paint<'a> { - fn parse(_: SvgNode, _: AId, value: &'a str) -> Option { - Self::from_str(value).ok() - } -} - -impl<'a, 'input: 'a> FromValue<'a, 'input> for Vec { - fn parse(_: SvgNode, _: AId, value: &str) -> Option { - let mut list = Vec::new(); - for n in svgtypes::NumberListParser::from(value) { - list.push(n.ok()? as f32); - } - - Some(list) - } -} - -impl<'a, 'input: 'a> FromValue<'a, 'input> for Vec { - fn parse(_: SvgNode, _: AId, value: &str) -> Option { - let mut list = Vec::new(); - for n in svgtypes::LengthListParser::from(value) { - list.push(n.ok()?); - } - - Some(list) - } -} - -impl<'a, 'input: 'a> FromValue<'a, 'input> for Visibility { - fn parse(_: SvgNode, _: AId, value: &str) -> Option { - match value { - "visible" => Some(Visibility::Visible), - "hidden" => Some(Visibility::Hidden), - "collapse" => Some(Visibility::Collapse), - _ => None, - } - } -} - -impl<'a, 'input: 'a> FromValue<'a, 'input> for SpreadMethod { - fn parse(_: SvgNode, _: AId, value: &str) -> Option { - match value { - "pad" => Some(SpreadMethod::Pad), - "reflect" => Some(SpreadMethod::Reflect), - "repeat" => Some(SpreadMethod::Repeat), - _ => None, - } - } -} - -impl<'a, 'input: 'a> FromValue<'a, 'input> for ShapeRendering { - fn parse(_: SvgNode, _: AId, value: &str) -> Option { - match value { - "optimizeSpeed" => Some(ShapeRendering::OptimizeSpeed), - "crispEdges" => Some(ShapeRendering::CrispEdges), - "auto" | "geometricPrecision" => Some(ShapeRendering::GeometricPrecision), - _ => None, - } - } -} - -impl<'a, 'input: 'a> FromValue<'a, 'input> for TextRendering { - fn parse(_: SvgNode, _: AId, value: &str) -> Option { - match value { - "optimizeSpeed" => Some(TextRendering::OptimizeSpeed), - "auto" | "optimizeLegibility" => Some(TextRendering::OptimizeLegibility), - "geometricPrecision" => Some(TextRendering::GeometricPrecision), - _ => None, - } - } -} - -impl<'a, 'input: 'a> FromValue<'a, 'input> for ImageRendering { - fn parse(_: SvgNode, _: AId, value: &str) -> Option { - match value { - "auto" | "optimizeQuality" => Some(ImageRendering::OptimizeQuality), - "optimizeSpeed" => Some(ImageRendering::OptimizeSpeed), - _ => None, - } - } -} - -impl<'a, 'input: 'a> FromValue<'a, 'input> for BlendMode { - fn parse(_: SvgNode, _: AId, value: &str) -> Option { - match value { - "normal" => Some(BlendMode::Normal), - "multiply" => Some(BlendMode::Multiply), - "screen" => Some(BlendMode::Screen), - "overlay" => Some(BlendMode::Overlay), - "darken" => Some(BlendMode::Darken), - "lighten" => Some(BlendMode::Lighten), - "color-dodge" => Some(BlendMode::ColorDodge), - "color-burn" => Some(BlendMode::ColorBurn), - "hard-light" => Some(BlendMode::HardLight), - "soft-light" => Some(BlendMode::SoftLight), - "difference" => Some(BlendMode::Difference), - "exclusion" => Some(BlendMode::Exclusion), - "hue" => Some(BlendMode::Hue), - "saturation" => Some(BlendMode::Saturation), - "color" => Some(BlendMode::Color), - "luminosity" => Some(BlendMode::Luminosity), - _ => None, - } - } -} - -impl<'a, 'input: 'a> FromValue<'a, 'input> for SvgNode<'a, 'input> { - fn parse(node: SvgNode<'a, 'input>, aid: AId, value: &str) -> Option { - let id = if aid == AId::Href { - svgtypes::IRI::from_str(value).ok().map(|v| v.0) - } else { - svgtypes::FuncIRI::from_str(value).ok().map(|v| v.0) - }?; - - node.document().element_by_id(id) - } -} diff --git a/resvg-0.43.0/crates/usvg/src/parser/svgtree/names.rs b/resvg-0.43.0/crates/usvg/src/parser/svgtree/names.rs deleted file mode 100644 index bc8e8f0..0000000 --- a/resvg-0.43.0/crates/usvg/src/parser/svgtree/names.rs +++ /dev/null @@ -1,719 +0,0 @@ -// This file is autogenerated. Do not edit it! -// See ./codegen for details. - -/// An element ID. -#[allow(missing_docs)] -#[derive(Clone, Copy, PartialEq)] -pub enum EId { - A, - Circle, - ClipPath, - Defs, - Ellipse, - FeBlend, - FeColorMatrix, - FeComponentTransfer, - FeComposite, - FeConvolveMatrix, - FeDiffuseLighting, - FeDisplacementMap, - FeDistantLight, - FeDropShadow, - FeFlood, - FeFuncA, - FeFuncB, - FeFuncG, - FeFuncR, - FeGaussianBlur, - FeImage, - FeMerge, - FeMergeNode, - FeMorphology, - FeOffset, - FePointLight, - FeSpecularLighting, - FeSpotLight, - FeTile, - FeTurbulence, - Filter, - G, - Image, - Line, - LinearGradient, - Marker, - Mask, - Path, - Pattern, - Polygon, - Polyline, - RadialGradient, - Rect, - Stop, - Style, - Svg, - Switch, - Symbol, - Text, - TextPath, - Tref, - Tspan, - Use, -} - -static ELEMENTS: Map = Map { - key: 732231254413039614, - disps: &[ - (0, 12), - (1, 11), - (10, 26), - (2, 42), - (1, 19), - (0, 5), - (1, 13), - (8, 50), - (0, 0), - (1, 0), - (7, 45), - ], - entries: &[ - ("feFlood", EId::FeFlood), - ("radialGradient", EId::RadialGradient), - ("feImage", EId::FeImage), - ("stop", EId::Stop), - ("fePointLight", EId::FePointLight), - ("feConvolveMatrix", EId::FeConvolveMatrix), - ("feComposite", EId::FeComposite), - ("clipPath", EId::ClipPath), - ("feMerge", EId::FeMerge), - ("defs", EId::Defs), - ("mask", EId::Mask), - ("svg", EId::Svg), - ("symbol", EId::Symbol), - ("linearGradient", EId::LinearGradient), - ("feSpecularLighting", EId::FeSpecularLighting), - ("feFuncB", EId::FeFuncB), - ("filter", EId::Filter), - ("feFuncG", EId::FeFuncG), - ("circle", EId::Circle), - ("g", EId::G), - ("tref", EId::Tref), - ("feFuncA", EId::FeFuncA), - ("image", EId::Image), - ("text", EId::Text), - ("line", EId::Line), - ("pattern", EId::Pattern), - ("use", EId::Use), - ("feDropShadow", EId::FeDropShadow), - ("feSpotLight", EId::FeSpotLight), - ("marker", EId::Marker), - ("style", EId::Style), - ("switch", EId::Switch), - ("tspan", EId::Tspan), - ("feColorMatrix", EId::FeColorMatrix), - ("feOffset", EId::FeOffset), - ("path", EId::Path), - ("feGaussianBlur", EId::FeGaussianBlur), - ("feTile", EId::FeTile), - ("feTurbulence", EId::FeTurbulence), - ("feMergeNode", EId::FeMergeNode), - ("feMorphology", EId::FeMorphology), - ("a", EId::A), - ("textPath", EId::TextPath), - ("ellipse", EId::Ellipse), - ("feComponentTransfer", EId::FeComponentTransfer), - ("feDistantLight", EId::FeDistantLight), - ("polyline", EId::Polyline), - ("polygon", EId::Polygon), - ("feBlend", EId::FeBlend), - ("feDisplacementMap", EId::FeDisplacementMap), - ("feDiffuseLighting", EId::FeDiffuseLighting), - ("rect", EId::Rect), - ("feFuncR", EId::FeFuncR), - ], -}; - -impl EId { - pub(crate) fn from_str(text: &str) -> Option { - ELEMENTS.get(text).cloned() - } - - /// Returns the original string. - #[inline(never)] - pub fn to_str(self) -> &'static str { - ELEMENTS.key(&self) - } -} - -impl std::fmt::Debug for EId { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", self.to_str()) - } -} - -impl std::fmt::Display for EId { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{:?}", self) - } -} - -/// An attribute ID. -#[allow(missing_docs)] -#[derive(Clone, Copy, PartialEq)] -pub enum AId { - AlignmentBaseline, - Amplitude, - Azimuth, - BaseFrequency, - BaselineShift, - Bias, - Class, - Clip, - ClipPath, - ClipRule, - ClipPathUnits, - Color, - ColorInterpolation, - ColorInterpolationFilters, - ColorProfile, - ColorRendering, - Cx, - Cy, - D, - DiffuseConstant, - Direction, - Display, - Divisor, - DominantBaseline, - Dx, - Dy, - EdgeMode, - Elevation, - EnableBackground, - Exponent, - Fill, - FillOpacity, - FillRule, - Filter, - FilterUnits, - FloodColor, - FloodOpacity, - Font, - FontFamily, - FontFeatureSettings, - FontKerning, - FontSize, - FontSizeAdjust, - FontStretch, - FontStyle, - FontSynthesis, - FontVariant, - FontVariantCaps, - FontVariantEastAsian, - FontVariantLigatures, - FontVariantNumeric, - FontVariantPosition, - FontWeight, - Fr, - Fx, - Fy, - GlyphOrientationHorizontal, - GlyphOrientationVertical, - GradientTransform, - GradientUnits, - Height, - Href, - Id, - ImageRendering, - In, - In2, - InlineSize, - Intercept, - Isolation, - K1, - K2, - K3, - K4, - KernelMatrix, - KernelUnitLength, - Kerning, - LengthAdjust, - LetterSpacing, - LightingColor, - LimitingConeAngle, - LineHeight, - MarkerEnd, - MarkerMid, - MarkerStart, - MarkerHeight, - MarkerUnits, - MarkerWidth, - Mask, - MaskBorder, - MaskBorderMode, - MaskBorderOutset, - MaskBorderRepeat, - MaskBorderSlice, - MaskBorderSource, - MaskBorderWidth, - MaskClip, - MaskComposite, - MaskImage, - MaskMode, - MaskOrigin, - MaskPosition, - MaskSize, - MaskType, - MaskContentUnits, - MaskUnits, - MixBlendMode, - Mode, - NumOctaves, - Offset, - Opacity, - Operator, - Order, - Orient, - Overflow, - PaintOrder, - Path, - PathLength, - PatternContentUnits, - PatternTransform, - PatternUnits, - Points, - PointsAtX, - PointsAtY, - PointsAtZ, - PreserveAlpha, - PreserveAspectRatio, - PrimitiveUnits, - R, - Radius, - RefX, - RefY, - RequiredExtensions, - RequiredFeatures, - Result, - Rotate, - Rx, - Ry, - Scale, - Seed, - ShapeImageThreshold, - ShapeInside, - ShapeMargin, - ShapePadding, - ShapeRendering, - ShapeSubtract, - Side, - Slope, - Space, - SpecularConstant, - SpecularExponent, - SpreadMethod, - StartOffset, - StdDeviation, - StitchTiles, - StopColor, - StopOpacity, - Stroke, - StrokeDasharray, - StrokeDashoffset, - StrokeLinecap, - StrokeLinejoin, - StrokeMiterlimit, - StrokeOpacity, - StrokeWidth, - Style, - SurfaceScale, - SystemLanguage, - TableValues, - TargetX, - TargetY, - TextAlign, - TextAlignLast, - TextAnchor, - TextDecoration, - TextDecorationColor, - TextDecorationFill, - TextDecorationLine, - TextDecorationStroke, - TextDecorationStyle, - TextIndent, - TextOrientation, - TextOverflow, - TextRendering, - TextUnderlinePosition, - TextLength, - Transform, - TransformBox, - TransformOrigin, - Type, - UnicodeBidi, - UnicodeRange, - Values, - VectorEffect, - ViewBox, - Visibility, - WhiteSpace, - Width, - WordSpacing, - WritingMode, - X, - X1, - X2, - XChannelSelector, - Y, - Y1, - Y2, - YChannelSelector, - Z, -} - -static ATTRIBUTES: Map = Map { - key: 732231254413039614, - disps: &[ - (63, 17), - (0, 1), - (0, 113), - (0, 21), - (0, 8), - (0, 23), - (0, 15), - (0, 3), - (40, 203), - (0, 3), - (0, 6), - (1, 31), - (0, 68), - (11, 161), - (0, 41), - (2, 58), - (7, 10), - (0, 0), - (0, 39), - (7, 73), - (0, 5), - (65, 30), - (0, 71), - (0, 28), - (0, 7), - (2, 99), - (0, 168), - (1, 79), - (0, 0), - (0, 3), - (0, 21), - (1, 9), - (0, 0), - (5, 171), - (0, 45), - (0, 18), - (99, 110), - (31, 206), - (5, 125), - (0, 14), - (0, 4), - (0, 83), - ], - entries: &[ - ("mask-type", AId::MaskType), - ("markerWidth", AId::MarkerWidth), - ("textLength", AId::TextLength), - ("alignment-baseline", AId::AlignmentBaseline), - ("unicode-bidi", AId::UnicodeBidi), - ("dominant-baseline", AId::DominantBaseline), - ("orient", AId::Orient), - ("x", AId::X), - ("font-style", AId::FontStyle), - ("color-interpolation", AId::ColorInterpolation), - ("style", AId::Style), - ("clip-rule", AId::ClipRule), - ("bias", AId::Bias), - ("font-weight", AId::FontWeight), - ("fill", AId::Fill), - ("mask-border-outset", AId::MaskBorderOutset), - ("x1", AId::X1), - ("mask", AId::Mask), - ("height", AId::Height), - ("stroke", AId::Stroke), - ("transform", AId::Transform), - ("isolation", AId::Isolation), - ("fy", AId::Fy), - ("text-orientation", AId::TextOrientation), - ("stop-opacity", AId::StopOpacity), - ("fx", AId::Fx), - ("font-kerning", AId::FontKerning), - ("cx", AId::Cx), - ("maskContentUnits", AId::MaskContentUnits), - ("opacity", AId::Opacity), - ("filterUnits", AId::FilterUnits), - ("lengthAdjust", AId::LengthAdjust), - ("text-anchor", AId::TextAnchor), - ("stroke-miterlimit", AId::StrokeMiterlimit), - ("viewBox", AId::ViewBox), - ("visibility", AId::Visibility), - ("ry", AId::Ry), - ( - "glyph-orientation-horizontal", - AId::GlyphOrientationHorizontal, - ), - ("gradientTransform", AId::GradientTransform), - ("markerUnits", AId::MarkerUnits), - ("shape-inside", AId::ShapeInside), - ("font-size", AId::FontSize), - ("yChannelSelector", AId::YChannelSelector), - ("clip-path", AId::ClipPath), - ("flood-color", AId::FloodColor), - ("marker-mid", AId::MarkerMid), - ("surfaceScale", AId::SurfaceScale), - ("color-profile", AId::ColorProfile), - ("requiredFeatures", AId::RequiredFeatures), - ("class", AId::Class), - ("startOffset", AId::StartOffset), - ("mask-origin", AId::MaskOrigin), - ("stdDeviation", AId::StdDeviation), - ("mode", AId::Mode), - ("overflow", AId::Overflow), - ("text-decoration-fill", AId::TextDecorationFill), - ("mask-border-mode", AId::MaskBorderMode), - ("divisor", AId::Divisor), - ("marker-start", AId::MarkerStart), - ("text-decoration-line", AId::TextDecorationLine), - ("preserveAlpha", AId::PreserveAlpha), - ("primitiveUnits", AId::PrimitiveUnits), - ("in", AId::In), - ("points", AId::Points), - ("pointsAtY", AId::PointsAtY), - ("specularExponent", AId::SpecularExponent), - ("shape-rendering", AId::ShapeRendering), - ("refY", AId::RefY), - ("clip", AId::Clip), - ("white-space", AId::WhiteSpace), - ("vector-effect", AId::VectorEffect), - ("image-rendering", AId::ImageRendering), - ("stitchTiles", AId::StitchTiles), - ("stroke-opacity", AId::StrokeOpacity), - ("shape-margin", AId::ShapeMargin), - ("y2", AId::Y2), - ("operator", AId::Operator), - ("pathLength", AId::PathLength), - ("order", AId::Order), - ("text-rendering", AId::TextRendering), - ("mask-border", AId::MaskBorder), - ("exponent", AId::Exponent), - ( - "color-interpolation-filters", - AId::ColorInterpolationFilters, - ), - ("diffuseConstant", AId::DiffuseConstant), - ("space", AId::Space), - ("font-synthesis", AId::FontSynthesis), - ("direction", AId::Direction), - ("font-size-adjust", AId::FontSizeAdjust), - ("kerning", AId::Kerning), - ("flood-opacity", AId::FloodOpacity), - ("mask-border-source", AId::MaskBorderSource), - ("line-height", AId::LineHeight), - ("mix-blend-mode", AId::MixBlendMode), - ("scale", AId::Scale), - ("mask-image", AId::MaskImage), - ("dy", AId::Dy), - ("xChannelSelector", AId::XChannelSelector), - ("unicode-range", AId::UnicodeRange), - ("y", AId::Y), - ("fill-opacity", AId::FillOpacity), - ("mask-composite", AId::MaskComposite), - ("targetY", AId::TargetY), - ("writing-mode", AId::WritingMode), - ("transform-origin", AId::TransformOrigin), - ("font-variant-caps", AId::FontVariantCaps), - ("k2", AId::K2), - ("transform-box", AId::TransformBox), - ("text-decoration", AId::TextDecoration), - ("filter", AId::Filter), - ("enable-background", AId::EnableBackground), - ("word-spacing", AId::WordSpacing), - ("gradientUnits", AId::GradientUnits), - ("font-feature-settings", AId::FontFeatureSettings), - ("stroke-linecap", AId::StrokeLinecap), - ("rx", AId::Rx), - ("kernelUnitLength", AId::KernelUnitLength), - ("shape-image-threshold", AId::ShapeImageThreshold), - ("inline-size", AId::InlineSize), - ("edgeMode", AId::EdgeMode), - ("text-decoration-style", AId::TextDecorationStyle), - ("pointsAtZ", AId::PointsAtZ), - ("elevation", AId::Elevation), - ("tableValues", AId::TableValues), - ("kernelMatrix", AId::KernelMatrix), - ("patternTransform", AId::PatternTransform), - ("fill-rule", AId::FillRule), - ("color", AId::Color), - ("lighting-color", AId::LightingColor), - ("cy", AId::Cy), - ("fr", AId::Fr), - ("systemLanguage", AId::SystemLanguage), - ("text-indent", AId::TextIndent), - ("slope", AId::Slope), - ("font", AId::Font), - ("requiredExtensions", AId::RequiredExtensions), - ("href", AId::Href), - ("baseFrequency", AId::BaseFrequency), - ("stroke-dashoffset", AId::StrokeDashoffset), - ("text-decoration-stroke", AId::TextDecorationStroke), - ("display", AId::Display), - ("amplitude", AId::Amplitude), - ("mask-size", AId::MaskSize), - ("font-variant-ligatures", AId::FontVariantLigatures), - ("in2", AId::In2), - ("maskUnits", AId::MaskUnits), - ("k4", AId::K4), - ("shape-padding", AId::ShapePadding), - ("r", AId::R), - ("text-underline-position", AId::TextUnderlinePosition), - ("font-variant-east-asian", AId::FontVariantEastAsian), - ("z", AId::Z), - ("text-decoration-color", AId::TextDecorationColor), - ("intercept", AId::Intercept), - ("side", AId::Side), - ("x2", AId::X2), - ("mask-position", AId::MaskPosition), - ("pointsAtX", AId::PointsAtX), - ("patternUnits", AId::PatternUnits), - ("result", AId::Result), - ("baseline-shift", AId::BaselineShift), - ("stop-color", AId::StopColor), - ("k3", AId::K3), - ("stroke-dasharray", AId::StrokeDasharray), - ("y1", AId::Y1), - ("targetX", AId::TargetX), - ("stroke-width", AId::StrokeWidth), - ("limitingConeAngle", AId::LimitingConeAngle), - ("id", AId::Id), - ("mask-border-width", AId::MaskBorderWidth), - ("shape-subtract", AId::ShapeSubtract), - ("font-variant", AId::FontVariant), - ("glyph-orientation-vertical", AId::GlyphOrientationVertical), - ("offset", AId::Offset), - ("type", AId::Type), - ("numOctaves", AId::NumOctaves), - ("font-stretch", AId::FontStretch), - ("mask-clip", AId::MaskClip), - ("clipPathUnits", AId::ClipPathUnits), - ("path", AId::Path), - ("seed", AId::Seed), - ("font-variant-position", AId::FontVariantPosition), - ("rotate", AId::Rotate), - ("text-overflow", AId::TextOverflow), - ("mask-border-repeat", AId::MaskBorderRepeat), - ("stroke-linejoin", AId::StrokeLinejoin), - ("mask-mode", AId::MaskMode), - ("specularConstant", AId::SpecularConstant), - ("spreadMethod", AId::SpreadMethod), - ("letter-spacing", AId::LetterSpacing), - ("font-family", AId::FontFamily), - ("azimuth", AId::Azimuth), - ("refX", AId::RefX), - ("color-rendering", AId::ColorRendering), - ("values", AId::Values), - ("marker-end", AId::MarkerEnd), - ("font-variant-numeric", AId::FontVariantNumeric), - ("markerHeight", AId::MarkerHeight), - ("radius", AId::Radius), - ("width", AId::Width), - ("text-align-last", AId::TextAlignLast), - ("preserveAspectRatio", AId::PreserveAspectRatio), - ("patternContentUnits", AId::PatternContentUnits), - ("k1", AId::K1), - ("paint-order", AId::PaintOrder), - ("d", AId::D), - ("mask-border-slice", AId::MaskBorderSlice), - ("dx", AId::Dx), - ("text-align", AId::TextAlign), - ], -}; - -impl AId { - pub(crate) fn from_str(text: &str) -> Option { - ATTRIBUTES.get(text).cloned() - } - - /// Returns the original string. - #[inline(never)] - pub fn to_str(self) -> &'static str { - ATTRIBUTES.key(&self) - } -} - -impl std::fmt::Debug for AId { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", self.to_str()) - } -} - -impl std::fmt::Display for AId { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{:?}", self) - } -} - -// A stripped down `phf` crate fork. -// -// https://github.com/sfackler/rust-phf - -struct Map { - pub key: u64, - pub disps: &'static [(u32, u32)], - pub entries: &'static [(&'static str, V)], -} - -impl Map { - fn get(&self, key: &str) -> Option<&V> { - let hash = hash(key, self.key); - let index = get_index(hash, self.disps, self.entries.len()); - let entry = &self.entries[index as usize]; - if entry.0 == key { - Some(&entry.1) - } else { - None - } - } - - fn key(&self, value: &V) -> &'static str { - self.entries.iter().find(|kv| kv.1 == *value).unwrap().0 - } -} - -#[inline] -fn hash(x: &str, key: u64) -> u64 { - use std::hash::Hasher; - - let mut hasher = siphasher::sip::SipHasher13::new_with_keys(0, key); - hasher.write(x.as_bytes()); - hasher.finish() -} - -#[inline] -fn get_index(hash: u64, disps: &[(u32, u32)], len: usize) -> u32 { - let (g, f1, f2) = split(hash); - let (d1, d2) = disps[(g % (disps.len() as u32)) as usize]; - displace(f1, f2, d1, d2) % (len as u32) -} - -#[inline] -fn split(hash: u64) -> (u32, u32, u32) { - const BITS: u32 = 21; - const MASK: u64 = (1 << BITS) - 1; - - ( - (hash & MASK) as u32, - ((hash >> BITS) & MASK) as u32, - ((hash >> (2 * BITS)) & MASK) as u32, - ) -} - -#[inline] -fn displace(f1: u32, f2: u32, d1: u32, d2: u32) -> u32 { - d2 + f1 * d1 + f2 -} diff --git a/resvg-0.43.0/crates/usvg/src/parser/svgtree/parse.rs b/resvg-0.43.0/crates/usvg/src/parser/svgtree/parse.rs deleted file mode 100644 index 785b25a..0000000 --- a/resvg-0.43.0/crates/usvg/src/parser/svgtree/parse.rs +++ /dev/null @@ -1,746 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use std::collections::HashMap; - -use roxmltree::Error; -use simplecss::Declaration; -use svgtypes::FontShorthand; - -use super::{AId, Attribute, Document, EId, NodeData, NodeId, NodeKind, ShortRange}; - -const SVG_NS: &str = "http://www.w3.org/2000/svg"; -const XLINK_NS: &str = "http://www.w3.org/1999/xlink"; -const XML_NAMESPACE_NS: &str = "http://www.w3.org/XML/1998/namespace"; - -impl<'input> Document<'input> { - /// Parses a [`Document`] from a [`roxmltree::Document`]. - pub fn parse_tree(xml: &roxmltree::Document<'input>) -> Result, Error> { - parse(xml) - } - - pub(crate) fn append(&mut self, parent_id: NodeId, kind: NodeKind) -> NodeId { - let new_child_id = NodeId::from(self.nodes.len()); - self.nodes.push(NodeData { - parent: Some(parent_id), - next_sibling: None, - children: None, - kind, - }); - - let last_child_id = self.nodes[parent_id.get_usize()].children.map(|(_, id)| id); - - if let Some(id) = last_child_id { - self.nodes[id.get_usize()].next_sibling = Some(new_child_id); - } - - self.nodes[parent_id.get_usize()].children = Some( - if let Some((first_child_id, _)) = self.nodes[parent_id.get_usize()].children { - (first_child_id, new_child_id) - } else { - (new_child_id, new_child_id) - }, - ); - - new_child_id - } - - fn append_attribute(&mut self, name: AId, value: roxmltree::StringStorage<'input>) { - self.attrs.push(Attribute { name, value }); - } -} - -fn parse<'input>(xml: &roxmltree::Document<'input>) -> Result, Error> { - let mut doc = Document { - nodes: Vec::new(), - attrs: Vec::new(), - links: HashMap::new(), - }; - - // build a map of id -> node for resolve_href - let mut id_map = HashMap::new(); - for node in xml.descendants() { - if let Some(id) = node.attribute("id") { - if !id_map.contains_key(id) { - id_map.insert(id, node); - } - } - } - - // Add a root node. - doc.nodes.push(NodeData { - parent: None, - next_sibling: None, - children: None, - kind: NodeKind::Root, - }); - - let style_sheet = resolve_css(xml); - - parse_xml_node_children( - xml.root(), - xml.root(), - doc.root().id, - &style_sheet, - false, - 0, - &mut doc, - &id_map, - )?; - - // Check that the root element is `svg`. - match doc.root().first_element_child() { - Some(child) => { - if child.tag_name() != Some(EId::Svg) { - return Err(roxmltree::Error::NoRootNode); - } - } - None => return Err(roxmltree::Error::NoRootNode), - } - - // Collect all elements with `id` attribute. - let mut links = HashMap::new(); - for node in doc.descendants() { - if let Some(id) = node.attribute::<&str>(AId::Id) { - links.insert(id.to_string(), node.id); - } - } - doc.links = links; - - fix_recursive_patterns(&mut doc); - fix_recursive_links(EId::ClipPath, AId::ClipPath, &mut doc); - fix_recursive_links(EId::Mask, AId::Mask, &mut doc); - fix_recursive_links(EId::Filter, AId::Filter, &mut doc); - fix_recursive_fe_image(&mut doc); - - Ok(doc) -} - -pub(crate) fn parse_tag_name(node: roxmltree::Node) -> Option { - if !node.is_element() { - return None; - } - - if node.tag_name().namespace() != Some(SVG_NS) { - return None; - } - - EId::from_str(node.tag_name().name()) -} - -fn parse_xml_node_children<'input>( - parent: roxmltree::Node<'_, 'input>, - origin: roxmltree::Node, - parent_id: NodeId, - style_sheet: &simplecss::StyleSheet, - ignore_ids: bool, - depth: u32, - doc: &mut Document<'input>, - id_map: &HashMap<&str, roxmltree::Node<'_, 'input>>, -) -> Result<(), Error> { - for node in parent.children() { - parse_xml_node( - node, - origin, - parent_id, - style_sheet, - ignore_ids, - depth, - doc, - id_map, - )?; - } - - Ok(()) -} - -fn parse_xml_node<'input>( - node: roxmltree::Node<'_, 'input>, - origin: roxmltree::Node, - parent_id: NodeId, - style_sheet: &simplecss::StyleSheet, - ignore_ids: bool, - depth: u32, - doc: &mut Document<'input>, - id_map: &HashMap<&str, roxmltree::Node<'_, 'input>>, -) -> Result<(), Error> { - if depth > 1024 { - return Err(Error::NodesLimitReached); - } - - let mut tag_name = match parse_tag_name(node) { - Some(id) => id, - None => return Ok(()), - }; - - if tag_name == EId::Style { - return Ok(()); - } - - // TODO: remove? - // Treat links as groups. - if tag_name == EId::A { - tag_name = EId::G; - } - - let node_id = parse_svg_element(node, parent_id, tag_name, style_sheet, ignore_ids, doc)?; - if tag_name == EId::Text { - super::text::parse_svg_text_element(node, node_id, style_sheet, doc)?; - } else if tag_name == EId::Use { - parse_svg_use_element(node, origin, node_id, style_sheet, depth + 1, doc, id_map)?; - } else { - parse_xml_node_children( - node, - origin, - node_id, - style_sheet, - ignore_ids, - depth + 1, - doc, - id_map, - )?; - } - - Ok(()) -} - -pub(crate) fn parse_svg_element<'input>( - xml_node: roxmltree::Node<'_, 'input>, - parent_id: NodeId, - tag_name: EId, - style_sheet: &simplecss::StyleSheet, - ignore_ids: bool, - doc: &mut Document<'input>, -) -> Result { - let attrs_start_idx = doc.attrs.len(); - - // Copy presentational attributes first. - for attr in xml_node.attributes() { - match attr.namespace() { - None | Some(SVG_NS) | Some(XLINK_NS) | Some(XML_NAMESPACE_NS) => {} - _ => continue, - } - - let aid = match AId::from_str(attr.name()) { - Some(v) => v, - None => continue, - }; - - // During a `use` resolving, all `id` attributes must be ignored. - // Otherwise we will get elements with duplicated id's. - if ignore_ids && aid == AId::Id { - continue; - } - - // For some reason those properties are allowed only inside a `style` attribute and CSS. - if matches!(aid, AId::MixBlendMode | AId::Isolation | AId::FontKerning) { - continue; - } - - append_attribute(parent_id, tag_name, aid, attr.value_storage().clone(), doc); - } - - let mut insert_attribute = |aid, value: &str| { - // Check that attribute already exists. - let idx = doc.attrs[attrs_start_idx..] - .iter_mut() - .position(|a| a.name == aid); - - // Append an attribute as usual. - let added = append_attribute( - parent_id, - tag_name, - aid, - roxmltree::StringStorage::new_owned(value), - doc, - ); - - // Check that attribute was actually added, because it could be skipped. - if added { - if let Some(idx) = idx { - // Swap the last attribute with an existing one. - let last_idx = doc.attrs.len() - 1; - doc.attrs.swap(attrs_start_idx + idx, last_idx); - // Remove last. - doc.attrs.pop(); - } - } - }; - - let mut write_declaration = |declaration: &Declaration| { - // TODO: perform XML attribute normalization - if declaration.name == "marker" { - insert_attribute(AId::MarkerStart, declaration.value); - insert_attribute(AId::MarkerMid, declaration.value); - insert_attribute(AId::MarkerEnd, declaration.value); - } else if declaration.name == "font" { - if let Ok(shorthand) = FontShorthand::from_str(declaration.value) { - // First we need to reset all values to their default. - insert_attribute(AId::FontStyle, "normal"); - insert_attribute(AId::FontVariant, "normal"); - insert_attribute(AId::FontWeight, "normal"); - insert_attribute(AId::FontStretch, "normal"); - insert_attribute(AId::LineHeight, "normal"); - insert_attribute(AId::FontSizeAdjust, "none"); - insert_attribute(AId::FontKerning, "auto"); - insert_attribute(AId::FontVariantCaps, "normal"); - insert_attribute(AId::FontVariantLigatures, "normal"); - insert_attribute(AId::FontVariantNumeric, "normal"); - insert_attribute(AId::FontVariantEastAsian, "normal"); - insert_attribute(AId::FontVariantPosition, "normal"); - - // Then, we set the properties that have been declared. - shorthand - .font_stretch - .map(|s| insert_attribute(AId::FontStretch, s)); - shorthand - .font_weight - .map(|s| insert_attribute(AId::FontWeight, s)); - shorthand - .font_variant - .map(|s| insert_attribute(AId::FontVariant, s)); - shorthand - .font_style - .map(|s| insert_attribute(AId::FontStyle, s)); - insert_attribute(AId::FontSize, shorthand.font_size); - insert_attribute(AId::FontFamily, shorthand.font_family); - } else { - log::warn!( - "Failed to parse {} value: '{}'", - AId::Font, - declaration.value - ); - } - } else if let Some(aid) = AId::from_str(declaration.name) { - // Parse only the presentation attributes. - if aid.is_presentation() { - insert_attribute(aid, declaration.value); - } - } - }; - - // Apply CSS. - for rule in &style_sheet.rules { - if rule.selector.matches(&XmlNode(xml_node)) { - for declaration in &rule.declarations { - write_declaration(declaration); - } - } - } - - // Split a `style` attribute. - if let Some(value) = xml_node.attribute("style") { - for declaration in simplecss::DeclarationTokenizer::from(value) { - write_declaration(&declaration); - } - } - - if doc.nodes.len() > 1_000_000 { - return Err(Error::NodesLimitReached); - } - - let node_id = doc.append( - parent_id, - NodeKind::Element { - tag_name, - attributes: ShortRange::new(attrs_start_idx as u32, doc.attrs.len() as u32), - }, - ); - - Ok(node_id) -} - -fn append_attribute<'input>( - parent_id: NodeId, - tag_name: EId, - aid: AId, - value: roxmltree::StringStorage<'input>, - doc: &mut Document<'input>, -) -> bool { - match aid { - // The `style` attribute will be split into attributes, so we don't need it. - AId::Style | - // No need to copy a `class` attribute since CSS were already resolved. - AId::Class => return false, - _ => {} - } - - // Ignore `xlink:href` on `tspan` (which was originally `tref` or `a`), - // because we will convert `tref` into `tspan` anyway. - if tag_name == EId::Tspan && aid == AId::Href { - return false; - } - - if aid.allows_inherit_value() && &*value == "inherit" { - return resolve_inherit(parent_id, aid, doc); - } - - doc.append_attribute(aid, value); - true -} - -fn resolve_inherit(parent_id: NodeId, aid: AId, doc: &mut Document) -> bool { - if aid.is_inheritable() { - // Inheritable attributes can inherit a value from an any ancestor. - let node_id = doc - .get(parent_id) - .ancestors() - .find(|n| n.has_attribute(aid)) - .map(|n| n.id); - if let Some(node_id) = node_id { - if let Some(attr) = doc - .get(node_id) - .attributes() - .iter() - .find(|a| a.name == aid) - .cloned() - { - doc.attrs.push(Attribute { - name: aid, - value: attr.value, - }); - - return true; - } - } - } else { - // Non-inheritable attributes can inherit a value only from a direct parent. - if let Some(attr) = doc - .get(parent_id) - .attributes() - .iter() - .find(|a| a.name == aid) - .cloned() - { - doc.attrs.push(Attribute { - name: aid, - value: attr.value, - }); - - return true; - } - } - - // Fallback to a default value if possible. - let value = match aid { - AId::ImageRendering | AId::ShapeRendering | AId::TextRendering => "auto", - - AId::ClipPath - | AId::Filter - | AId::MarkerEnd - | AId::MarkerMid - | AId::MarkerStart - | AId::Mask - | AId::Stroke - | AId::StrokeDasharray - | AId::TextDecoration => "none", - - AId::FontStretch - | AId::FontStyle - | AId::FontVariant - | AId::FontWeight - | AId::LetterSpacing - | AId::WordSpacing => "normal", - - AId::Fill | AId::FloodColor | AId::StopColor => "black", - - AId::FillOpacity - | AId::FloodOpacity - | AId::Opacity - | AId::StopOpacity - | AId::StrokeOpacity => "1", - - AId::ClipRule | AId::FillRule => "nonzero", - - AId::BaselineShift => "baseline", - AId::ColorInterpolationFilters => "linearRGB", - AId::Direction => "ltr", - AId::Display => "inline", - AId::FontSize => "medium", - AId::Overflow => "visible", - AId::StrokeDashoffset => "0", - AId::StrokeLinecap => "butt", - AId::StrokeLinejoin => "miter", - AId::StrokeMiterlimit => "4", - AId::StrokeWidth => "1", - AId::TextAnchor => "start", - AId::Visibility => "visible", - AId::WritingMode => "lr-tb", - _ => return false, - }; - - doc.append_attribute(aid, roxmltree::StringStorage::Borrowed(value)); - true -} - -fn resolve_href<'a, 'input: 'a>( - node: roxmltree::Node<'a, 'input>, - id_map: &HashMap<&str, roxmltree::Node<'a, 'input>>, -) -> Option> { - let link_value = node - .attribute((XLINK_NS, "href")) - .or_else(|| node.attribute("href"))?; - - let link_id = svgtypes::IRI::from_str(link_value).ok()?.0; - - id_map.get(link_id).copied() -} - -fn parse_svg_use_element<'input>( - node: roxmltree::Node<'_, 'input>, - origin: roxmltree::Node, - parent_id: NodeId, - style_sheet: &simplecss::StyleSheet, - depth: u32, - doc: &mut Document<'input>, - id_map: &HashMap<&str, roxmltree::Node<'_, 'input>>, -) -> Result<(), Error> { - let link = match resolve_href(node, id_map) { - Some(v) => v, - None => return Ok(()), - }; - - if link == node || link == origin { - log::warn!( - "Recursive 'use' detected. '{}' will be skipped.", - node.attribute((SVG_NS, "id")).unwrap_or_default() - ); - return Ok(()); - } - - // Make sure we're linked to an SVG element. - if parse_tag_name(link).is_none() { - return Ok(()); - } - - // Check that none of the linked node's children reference current `use` node - // via other `use` node. - // - // Example: - // - // - // - // - // - // `use2` should be removed. - // - // Also, child should not reference its parent: - // - // - // - // - // `use1` should be removed. - let mut is_recursive = false; - for link_child in link - .descendants() - .skip(1) - .filter(|n| n.has_tag_name((SVG_NS, "use"))) - { - if let Some(link2) = resolve_href(link_child, id_map) { - if link2 == node || link2 == link { - is_recursive = true; - break; - } - } - } - - if is_recursive { - log::warn!( - "Recursive 'use' detected. '{}' will be skipped.", - node.attribute((SVG_NS, "id")).unwrap_or_default() - ); - return Ok(()); - } - - parse_xml_node( - link, - node, - parent_id, - style_sheet, - true, - depth + 1, - doc, - id_map, - ) -} - -fn resolve_css<'a>(xml: &'a roxmltree::Document<'a>) -> simplecss::StyleSheet<'a> { - let mut sheet = simplecss::StyleSheet::new(); - - for node in xml.descendants().filter(|n| n.has_tag_name("style")) { - match node.attribute("type") { - Some("text/css") => {} - Some(_) => continue, - None => {} - } - - let text = match node.text() { - Some(v) => v, - None => continue, - }; - - sheet.parse_more(text); - } - - sheet -} - -struct XmlNode<'a, 'input: 'a>(roxmltree::Node<'a, 'input>); - -impl simplecss::Element for XmlNode<'_, '_> { - fn parent_element(&self) -> Option { - self.0.parent_element().map(XmlNode) - } - - fn prev_sibling_element(&self) -> Option { - self.0.prev_sibling_element().map(XmlNode) - } - - fn has_local_name(&self, local_name: &str) -> bool { - self.0.tag_name().name() == local_name - } - - fn attribute_matches(&self, local_name: &str, operator: simplecss::AttributeOperator) -> bool { - match self.0.attribute(local_name) { - Some(value) => operator.matches(value), - None => false, - } - } - - fn pseudo_class_matches(&self, class: simplecss::PseudoClass) -> bool { - match class { - simplecss::PseudoClass::FirstChild => self.prev_sibling_element().is_none(), - // TODO: lang - _ => false, // Since we are querying a static SVG we can ignore other pseudo-classes. - } - } -} - -fn fix_recursive_patterns(doc: &mut Document) { - while let Some(node_id) = find_recursive_pattern(AId::Fill, doc) { - let idx = doc.get(node_id).attribute_id(AId::Fill).unwrap(); - doc.attrs[idx].value = roxmltree::StringStorage::Borrowed("none"); - } - - while let Some(node_id) = find_recursive_pattern(AId::Stroke, doc) { - let idx = doc.get(node_id).attribute_id(AId::Stroke).unwrap(); - doc.attrs[idx].value = roxmltree::StringStorage::Borrowed("none"); - } -} - -fn find_recursive_pattern(aid: AId, doc: &mut Document) -> Option { - for pattern_node in doc - .root() - .descendants() - .filter(|n| n.tag_name() == Some(EId::Pattern)) - { - for node in pattern_node.descendants() { - let value = match node.attribute(aid) { - Some(v) => v, - None => continue, - }; - - if let Ok(svgtypes::Paint::FuncIRI(link_id, _)) = svgtypes::Paint::from_str(value) { - if link_id == pattern_node.element_id() { - // If a pattern child has a link to the pattern itself - // then we have to replace it with `none`. - // Otherwise we will get endless loop/recursion and stack overflow. - return Some(node.id); - } else { - // Check that linked node children doesn't link this pattern. - if let Some(linked_node) = doc.element_by_id(link_id) { - for node2 in linked_node.descendants() { - let value2 = match node2.attribute(aid) { - Some(v) => v, - None => continue, - }; - - if let Ok(svgtypes::Paint::FuncIRI(link_id2, _)) = - svgtypes::Paint::from_str(value2) - { - if link_id2 == pattern_node.element_id() { - return Some(node2.id); - } - } - } - } - } - } - } - } - - None -} - -fn fix_recursive_links(eid: EId, aid: AId, doc: &mut Document) { - while let Some(node_id) = find_recursive_link(eid, aid, doc) { - let idx = doc.get(node_id).attribute_id(aid).unwrap(); - doc.attrs[idx].value = roxmltree::StringStorage::Borrowed("none"); - } -} - -fn find_recursive_link(eid: EId, aid: AId, doc: &Document) -> Option { - for node in doc - .root() - .descendants() - .filter(|n| n.tag_name() == Some(eid)) - { - for child in node.descendants() { - if let Some(link) = child.node_attribute(aid) { - if link == node { - // If an element child has a link to the element itself - // then we have to replace it with `none`. - // Otherwise we will get endless loop/recursion and stack overflow. - return Some(child.id); - } else { - // Check that linked node children doesn't link this element. - for node2 in link.descendants() { - if let Some(link2) = node2.node_attribute(aid) { - if link2 == node { - return Some(node2.id); - } - } - } - } - } - } - } - - None -} - -/// Detects cases like: -/// -/// ```xml -/// -/// -/// -/// -/// ``` -fn fix_recursive_fe_image(doc: &mut Document) { - let mut ids = Vec::new(); - for fe_node in doc - .root() - .descendants() - .filter(|n| n.tag_name() == Some(EId::FeImage)) - { - if let Some(link) = fe_node.node_attribute(AId::Href) { - if let Some(filter_uri) = link.attribute::<&str>(AId::Filter) { - let filter_id = fe_node.parent().unwrap().element_id(); - for func in svgtypes::FilterValueListParser::from(filter_uri).flatten() { - if let svgtypes::FilterValue::Url(url) = func { - if url == filter_id { - ids.push(link.id); - } - } - } - } - } - } - - for id in ids { - let idx = doc.get(id).attribute_id(AId::Filter).unwrap(); - doc.attrs[idx].value = roxmltree::StringStorage::Borrowed("none"); - } -} diff --git a/resvg-0.43.0/crates/usvg/src/parser/svgtree/text.rs b/resvg-0.43.0/crates/usvg/src/parser/svgtree/text.rs deleted file mode 100644 index fab2a9b..0000000 --- a/resvg-0.43.0/crates/usvg/src/parser/svgtree/text.rs +++ /dev/null @@ -1,367 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#![allow(clippy::comparison_chain)] - -use roxmltree::Error; - -use super::{AId, Document, EId, NodeId, NodeKind, SvgNode}; - -const XLINK_NS: &str = "http://www.w3.org/1999/xlink"; - -pub(crate) fn parse_svg_text_element<'input>( - parent: roxmltree::Node<'_, 'input>, - parent_id: NodeId, - style_sheet: &simplecss::StyleSheet, - doc: &mut Document<'input>, -) -> Result<(), Error> { - debug_assert_eq!(parent.tag_name().name(), "text"); - - let space = if doc.get(parent_id).has_attribute(AId::Space) { - get_xmlspace(doc, parent_id, XmlSpace::Default) - } else { - if let Some(node) = doc - .get(parent_id) - .ancestors() - .find(|n| n.has_attribute(AId::Space)) - { - get_xmlspace(doc, node.id, XmlSpace::Default) - } else { - XmlSpace::Default - } - }; - - parse_svg_text_element_impl(parent, parent_id, style_sheet, space, doc)?; - - trim_text_nodes(parent_id, space, doc); - Ok(()) -} - -fn parse_svg_text_element_impl<'input>( - parent: roxmltree::Node<'_, 'input>, - parent_id: NodeId, - style_sheet: &simplecss::StyleSheet, - space: XmlSpace, - doc: &mut Document<'input>, -) -> Result<(), Error> { - for node in parent.children() { - if node.is_text() { - let text = trim_text(node.text().unwrap(), space); - doc.append(parent_id, NodeKind::Text(text)); - continue; - } - - let mut tag_name = match super::parse::parse_tag_name(node) { - Some(v) => v, - None => continue, - }; - - if tag_name == EId::A { - // Treat links as simple text. - tag_name = EId::Tspan; - } - - if !matches!(tag_name, EId::Tspan | EId::Tref | EId::TextPath) { - continue; - } - - // `textPath` must be a direct `text` child. - if tag_name == EId::TextPath && parent.tag_name().name() != "text" { - continue; - } - - // We are converting `tref` into `tspan` to simplify later use. - let mut is_tref = false; - if tag_name == EId::Tref { - tag_name = EId::Tspan; - is_tref = true; - } - - let node_id = - super::parse::parse_svg_element(node, parent_id, tag_name, style_sheet, false, doc)?; - let space = get_xmlspace(doc, node_id, space); - - if is_tref { - let link_value = node - .attribute((XLINK_NS, "href")) - .or_else(|| node.attribute("href")); - - if let Some(href) = link_value { - if let Some(text) = resolve_tref_text(node.document(), href) { - let text = trim_text(&text, space); - doc.append(node_id, NodeKind::Text(text)); - } - } - } else { - parse_svg_text_element_impl(node, node_id, style_sheet, space, doc)?; - } - } - - Ok(()) -} - -fn resolve_tref_text(xml: &roxmltree::Document, href: &str) -> Option { - let id = svgtypes::IRI::from_str(href).ok()?.0; - - // Find linked element in the original tree. - let node = xml.descendants().find(|n| n.attribute("id") == Some(id))?; - - // `tref` should be linked to an SVG element. - super::parse::parse_tag_name(node)?; - - // 'All character data within the referenced element, including character data enclosed - // within additional markup, will be rendered.' - // - // So we don't care about attributes and everything. Just collecting text nodes data. - // - // Note: we have to filter nodes by `is_text()` first since `text()` will look up - // for text nodes in element children therefore we will get duplicates. - let text: String = node - .descendants() - .filter(|n| n.is_text()) - .filter_map(|n| n.text()) - .collect(); - if text.is_empty() { - None - } else { - Some(text) - } -} - -#[derive(Clone, Copy, PartialEq, Debug)] -enum XmlSpace { - Default, - Preserve, -} - -fn get_xmlspace(doc: &Document, node_id: NodeId, default: XmlSpace) -> XmlSpace { - match doc.get(node_id).attribute(AId::Space) { - Some("preserve") => XmlSpace::Preserve, - Some(_) => XmlSpace::Default, - _ => default, - } -} - -trait StrTrim { - fn remove_first_space(&mut self); - fn remove_last_space(&mut self); -} - -impl StrTrim for String { - fn remove_first_space(&mut self) { - debug_assert_eq!(self.chars().next().unwrap(), ' '); - self.drain(0..1); - } - - fn remove_last_space(&mut self) { - debug_assert_eq!(self.chars().next_back().unwrap(), ' '); - self.pop(); - } -} - -/// Prepares text nodes according to the spec: https://www.w3.org/TR/SVG11/text.html#WhiteSpace -/// -/// This function handles: -/// - 'xml:space' processing -/// - tabs and newlines removing/replacing -/// - spaces trimming -fn trim_text_nodes(text_elem_id: NodeId, xmlspace: XmlSpace, doc: &mut Document) { - let mut nodes = Vec::new(); // TODO: allocate only once - collect_text_nodes(doc.get(text_elem_id), 0, &mut nodes); - - // `trim` method has already collapsed all spaces into a single one, - // so we have to check only for one leading or trailing space. - - if nodes.len() == 1 { - // Process element with a single text node child. - - let node_id = nodes[0].0; - - if xmlspace == XmlSpace::Default { - if let NodeKind::Text(ref mut text) = doc.nodes[node_id.get_usize()].kind { - match text.len() { - 0 => {} // An empty string. Do nothing. - 1 => { - // If string has only one character and it's a space - clear this string. - if text.as_bytes()[0] == b' ' { - text.clear(); - } - } - _ => { - // 'text' has at least 2 bytes, so indexing is safe. - let c1 = text.as_bytes()[0]; - let c2 = text.as_bytes()[text.len() - 1]; - - if c1 == b' ' { - text.remove_first_space(); - } - - if c2 == b' ' { - text.remove_last_space(); - } - } - } - } - } else { - // Do nothing when xml:space=preserve. - } - } else if nodes.len() > 1 { - // Process element with many text node children. - - // We manage all text nodes as a single text node - // and trying to remove duplicated spaces across nodes. - // - // For example 'Text text text' - // is the same is 'Text text text' - - let mut i = 0; - let len = nodes.len() - 1; - let mut last_non_empty: Option = None; - while i < len { - // Process pairs. - let (mut node1_id, depth1) = nodes[i]; - let (node2_id, depth2) = nodes[i + 1]; - - if doc.get(node1_id).text().is_empty() { - if let Some(n) = last_non_empty { - node1_id = n; - } - } - - // Parent of the text node is always an element node and always exist, - // so unwrap is safe. - let xmlspace1 = get_xmlspace(doc, doc.get(node1_id).parent().unwrap().id, xmlspace); - let xmlspace2 = get_xmlspace(doc, doc.get(node2_id).parent().unwrap().id, xmlspace); - - // >text<..>text< - // 1 2 3 4 - let (c1, c2, c3, c4) = { - let text1 = doc.get(node1_id).text(); - let text2 = doc.get(node2_id).text(); - - let bytes1 = text1.as_bytes(); - let bytes2 = text2.as_bytes(); - - let c1 = bytes1.first().cloned(); - let c2 = bytes1.last().cloned(); - let c3 = bytes2.first().cloned(); - let c4 = bytes2.last().cloned(); - - (c1, c2, c3, c4) - }; - - // NOTE: xml:space processing is mostly an undefined behavior, - // because everyone do it differently. - // We're mimicking the Chrome behavior. - - // Remove space from the second text node if both nodes has bound spaces. - // From: 'Text text' - // To: 'Text text' - // - // See text-tspan-02-b.svg for details. - if depth1 < depth2 { - if c3 == Some(b' ') { - if xmlspace2 == XmlSpace::Default { - if let NodeKind::Text(ref mut text) = doc.nodes[node2_id.get_usize()].kind { - text.remove_first_space(); - } - } - } - } else { - if c2 == Some(b' ') && c2 == c3 { - if xmlspace1 == XmlSpace::Default && xmlspace2 == XmlSpace::Default { - if let NodeKind::Text(ref mut text) = doc.nodes[node1_id.get_usize()].kind { - text.remove_last_space(); - } - } else { - if xmlspace1 == XmlSpace::Preserve && xmlspace2 == XmlSpace::Default { - if let NodeKind::Text(ref mut text) = - doc.nodes[node2_id.get_usize()].kind - { - text.remove_first_space(); - } - } - } - } - } - - let is_first = i == 0; - let is_last = i == len - 1; - - if is_first - && c1 == Some(b' ') - && xmlspace1 == XmlSpace::Default - && !doc.get(node1_id).text().is_empty() - { - // Remove a leading space from a first text node. - if let NodeKind::Text(ref mut text) = doc.nodes[node1_id.get_usize()].kind { - text.remove_first_space(); - } - } else if is_last - && c4 == Some(b' ') - && !doc.get(node2_id).text().is_empty() - && xmlspace2 == XmlSpace::Default - { - // Remove a trailing space from a last text node. - // Also check that 'text2' is not empty already. - if let NodeKind::Text(ref mut text) = doc.nodes[node2_id.get_usize()].kind { - text.remove_last_space(); - } - } - - if is_last - && c2 == Some(b' ') - && !doc.get(node1_id).text().is_empty() - && doc.get(node2_id).text().is_empty() - && doc.get(node1_id).text().ends_with(' ') - { - if let NodeKind::Text(ref mut text) = doc.nodes[node1_id.get_usize()].kind { - text.remove_last_space(); - } - } - - if !doc.get(node1_id).text().trim().is_empty() { - last_non_empty = Some(node1_id); - } - - i += 1; - } - } - - // TODO: find a way to remove all empty text nodes -} - -fn collect_text_nodes(parent: SvgNode, depth: usize, nodes: &mut Vec<(NodeId, usize)>) { - for child in parent.children() { - if child.is_text() { - nodes.push((child.id, depth)); - } else if child.is_element() { - collect_text_nodes(child, depth + 1, nodes); - } - } -} - -fn trim_text(text: &str, space: XmlSpace) -> String { - let mut s = String::with_capacity(text.len()); - - let mut prev = '0'; - for c in text.chars() { - // \r, \n and \t should be converted into spaces. - let c = match c { - '\r' | '\n' | '\t' => ' ', - _ => c, - }; - - // Skip continuous spaces. - if space == XmlSpace::Default && c == ' ' && c == prev { - continue; - } - - prev = c; - - s.push(c); - } - - s -} diff --git a/resvg-0.43.0/crates/usvg/src/parser/switch.rs b/resvg-0.43.0/crates/usvg/src/parser/switch.rs deleted file mode 100644 index 794bf6d..0000000 --- a/resvg-0.43.0/crates/usvg/src/parser/switch.rs +++ /dev/null @@ -1,126 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use super::svgtree::{AId, SvgNode}; -use super::{converter, Options}; -use crate::{Group, Node}; - -// Full list can be found here: https://www.w3.org/TR/SVG11/feature.html -static FEATURES: &[&str] = &[ - "http://www.w3.org/TR/SVG11/feature#SVGDOM-static", - "http://www.w3.org/TR/SVG11/feature#SVG-static", - "http://www.w3.org/TR/SVG11/feature#CoreAttribute", // no xml:base and xml:lang - "http://www.w3.org/TR/SVG11/feature#Structure", - "http://www.w3.org/TR/SVG11/feature#BasicStructure", - "http://www.w3.org/TR/SVG11/feature#ContainerAttribute", // `enable-background` - "http://www.w3.org/TR/SVG11/feature#ConditionalProcessing", - "http://www.w3.org/TR/SVG11/feature#Image", - "http://www.w3.org/TR/SVG11/feature#Style", - // "http://www.w3.org/TR/SVG11/feature#ViewportAttribute", // `clip` and `overflow`, not yet - "http://www.w3.org/TR/SVG11/feature#Shape", - "http://www.w3.org/TR/SVG11/feature#Text", - "http://www.w3.org/TR/SVG11/feature#BasicText", - "http://www.w3.org/TR/SVG11/feature#PaintAttribute", // no color-interpolation and color-rendering - "http://www.w3.org/TR/SVG11/feature#BasicPaintAttribute", // no color-interpolation - "http://www.w3.org/TR/SVG11/feature#OpacityAttribute", - "http://www.w3.org/TR/SVG11/feature#GraphicsAttribute", - "http://www.w3.org/TR/SVG11/feature#BasicGraphicsAttribute", - "http://www.w3.org/TR/SVG11/feature#Marker", - // "http://www.w3.org/TR/SVG11/feature#ColorProfile", // not yet - "http://www.w3.org/TR/SVG11/feature#Gradient", - "http://www.w3.org/TR/SVG11/feature#Pattern", - "http://www.w3.org/TR/SVG11/feature#Clip", - "http://www.w3.org/TR/SVG11/feature#BasicClip", - "http://www.w3.org/TR/SVG11/feature#Mask", - "http://www.w3.org/TR/SVG11/feature#Filter", - "http://www.w3.org/TR/SVG11/feature#BasicFilter", - // only xlink:href - "http://www.w3.org/TR/SVG11/feature#XlinkAttribute", - // "http://www.w3.org/TR/SVG11/feature#Font", - // "http://www.w3.org/TR/SVG11/feature#BasicFont", -]; - -pub(crate) fn convert( - node: SvgNode, - state: &converter::State, - cache: &mut converter::Cache, - parent: &mut Group, -) -> Option<()> { - let child = node - .children() - .find(|n| is_condition_passed(*n, state.opt))?; - if let Some(g) = converter::convert_group(node, state, false, cache, parent, &|cache, g| { - converter::convert_element(child, state, cache, g); - }) { - parent.children.push(Node::Group(Box::new(g))); - } - - Some(()) -} - -pub(crate) fn is_condition_passed(node: SvgNode, opt: &Options) -> bool { - if !node.is_element() { - return false; - } - - if node.has_attribute(AId::RequiredExtensions) { - return false; - } - - // 'The value is a list of feature strings, with the individual values separated by white space. - // Determines whether all of the named features are supported by the user agent. - // Only feature strings defined in the Feature String appendix are allowed. - // If all of the given features are supported, then the attribute evaluates to true; - // otherwise, the current element and its children are skipped and thus will not be rendered.' - if let Some(features) = node.attribute::<&str>(AId::RequiredFeatures) { - for feature in features.split(' ') { - if !FEATURES.contains(&feature) { - return false; - } - } - } - - if !is_valid_sys_lang(node, opt) { - return false; - } - - true -} - -/// SVG spec 5.8.5 -fn is_valid_sys_lang(node: SvgNode, opt: &Options) -> bool { - // 'The attribute value is a comma-separated list of language names - // as defined in BCP 47.' - // - // But we support only simple cases like `en` or `en-US`. - // No one really uses this, especially with complex BCP 47 values. - if let Some(langs) = node.attribute::<&str>(AId::SystemLanguage) { - let mut has_match = false; - for lang in langs.split(',') { - let lang = lang.trim(); - - // 'Evaluates to `true` if one of the languages indicated by user preferences exactly - // equals one of the languages given in the value of this parameter.' - if opt.languages.iter().any(|v| v == lang) { - has_match = true; - break; - } - - // 'If one of the languages indicated by user preferences exactly equals a prefix - // of one of the languages given in the value of this parameter such that - // the first tag character following the prefix is `-`.' - if let Some(idx) = lang.bytes().position(|c| c == b'-') { - let lang_prefix = &lang[..idx]; - if opt.languages.iter().any(|v| v == lang_prefix) { - has_match = true; - break; - } - } - } - - has_match - } else { - true - } -} diff --git a/resvg-0.43.0/crates/usvg/src/parser/text.rs b/resvg-0.43.0/crates/usvg/src/parser/text.rs deleted file mode 100644 index 2d62657..0000000 --- a/resvg-0.43.0/crates/usvg/src/parser/text.rs +++ /dev/null @@ -1,826 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use std::sync::Arc; - -use kurbo::{ParamCurve, ParamCurveArclen}; -use svgtypes::{parse_font_families, FontFamily, Length, LengthUnit}; - -use super::svgtree::{AId, EId, FromValue, SvgNode}; -use super::{converter, style, OptionLog}; -use crate::*; - -impl<'a, 'input: 'a> FromValue<'a, 'input> for TextAnchor { - fn parse(_: SvgNode, _: AId, value: &str) -> Option { - match value { - "start" => Some(TextAnchor::Start), - "middle" => Some(TextAnchor::Middle), - "end" => Some(TextAnchor::End), - _ => None, - } - } -} - -impl<'a, 'input: 'a> FromValue<'a, 'input> for AlignmentBaseline { - fn parse(_: SvgNode, _: AId, value: &str) -> Option { - match value { - "auto" => Some(AlignmentBaseline::Auto), - "baseline" => Some(AlignmentBaseline::Baseline), - "before-edge" => Some(AlignmentBaseline::BeforeEdge), - "text-before-edge" => Some(AlignmentBaseline::TextBeforeEdge), - "middle" => Some(AlignmentBaseline::Middle), - "central" => Some(AlignmentBaseline::Central), - "after-edge" => Some(AlignmentBaseline::AfterEdge), - "text-after-edge" => Some(AlignmentBaseline::TextAfterEdge), - "ideographic" => Some(AlignmentBaseline::Ideographic), - "alphabetic" => Some(AlignmentBaseline::Alphabetic), - "hanging" => Some(AlignmentBaseline::Hanging), - "mathematical" => Some(AlignmentBaseline::Mathematical), - _ => None, - } - } -} - -impl<'a, 'input: 'a> FromValue<'a, 'input> for DominantBaseline { - fn parse(_: SvgNode, _: AId, value: &str) -> Option { - match value { - "auto" => Some(DominantBaseline::Auto), - "use-script" => Some(DominantBaseline::UseScript), - "no-change" => Some(DominantBaseline::NoChange), - "reset-size" => Some(DominantBaseline::ResetSize), - "ideographic" => Some(DominantBaseline::Ideographic), - "alphabetic" => Some(DominantBaseline::Alphabetic), - "hanging" => Some(DominantBaseline::Hanging), - "mathematical" => Some(DominantBaseline::Mathematical), - "central" => Some(DominantBaseline::Central), - "middle" => Some(DominantBaseline::Middle), - "text-after-edge" => Some(DominantBaseline::TextAfterEdge), - "text-before-edge" => Some(DominantBaseline::TextBeforeEdge), - _ => None, - } - } -} - -impl<'a, 'input: 'a> FromValue<'a, 'input> for LengthAdjust { - fn parse(_: SvgNode, _: AId, value: &str) -> Option { - match value { - "spacing" => Some(LengthAdjust::Spacing), - "spacingAndGlyphs" => Some(LengthAdjust::SpacingAndGlyphs), - _ => None, - } - } -} - -impl<'a, 'input: 'a> FromValue<'a, 'input> for FontStyle { - fn parse(_: SvgNode, _: AId, value: &str) -> Option { - match value { - "normal" => Some(FontStyle::Normal), - "italic" => Some(FontStyle::Italic), - "oblique" => Some(FontStyle::Oblique), - _ => None, - } - } -} - -/// A text character position. -/// -/// _Character_ is a Unicode codepoint. -#[derive(Clone, Copy, Debug)] -struct CharacterPosition { - /// An absolute X axis position. - x: Option, - /// An absolute Y axis position. - y: Option, - /// A relative X axis offset. - dx: Option, - /// A relative Y axis offset. - dy: Option, -} - -pub(crate) fn convert( - text_node: SvgNode, - state: &converter::State, - cache: &mut converter::Cache, - parent: &mut Group, -) { - let pos_list = resolve_positions_list(text_node, state); - let rotate_list = resolve_rotate_list(text_node); - let writing_mode = convert_writing_mode(text_node); - - let chunks = collect_text_chunks(text_node, &pos_list, state, cache); - - let rendering_mode: TextRendering = text_node - .find_attribute(AId::TextRendering) - .unwrap_or(state.opt.text_rendering); - - // Nodes generated by markers must not have an ID. Otherwise we would have duplicates. - let id = if state.parent_markers.is_empty() { - text_node.element_id().to_string() - } else { - String::new() - }; - - let dummy = Rect::from_xywh(0.0, 0.0, 0.0, 0.0).unwrap(); - - let mut text = Text { - id, - rendering_mode, - dx: pos_list.iter().map(|v| v.dx.unwrap_or(0.0)).collect(), - dy: pos_list.iter().map(|v| v.dy.unwrap_or(0.0)).collect(), - rotate: rotate_list, - writing_mode, - chunks, - abs_transform: parent.abs_transform, - // All fields below will be reset by `text_to_paths`. - bounding_box: dummy, - abs_bounding_box: dummy, - stroke_bounding_box: dummy, - abs_stroke_bounding_box: dummy, - flattened: Box::new(Group::empty()), - layouted: vec![], - }; - - if text::convert(&mut text, &state.opt.font_resolver, &mut cache.fontdb).is_none() { - return; - } - - parent.children.push(Node::Text(Box::new(text))); -} - -struct IterState { - chars_count: usize, - chunk_bytes_count: usize, - split_chunk: bool, - text_flow: TextFlow, - chunks: Vec, -} - -fn collect_text_chunks( - text_node: SvgNode, - pos_list: &[CharacterPosition], - state: &converter::State, - cache: &mut converter::Cache, -) -> Vec { - let mut iter_state = IterState { - chars_count: 0, - chunk_bytes_count: 0, - split_chunk: false, - text_flow: TextFlow::Linear, - chunks: Vec::new(), - }; - - collect_text_chunks_impl(text_node, pos_list, state, cache, &mut iter_state); - - iter_state.chunks -} - -fn collect_text_chunks_impl( - parent: SvgNode, - pos_list: &[CharacterPosition], - state: &converter::State, - cache: &mut converter::Cache, - iter_state: &mut IterState, -) { - for child in parent.children() { - if child.is_element() { - if child.tag_name() == Some(EId::TextPath) { - if parent.tag_name() != Some(EId::Text) { - // `textPath` can be set only as a direct `text` element child. - iter_state.chars_count += count_chars(child); - continue; - } - - match resolve_text_flow(child, state) { - Some(v) => { - iter_state.text_flow = v; - } - None => { - // Skip an invalid text path and all it's children. - // We have to update the chars count, - // because `pos_list` was calculated including this text path. - iter_state.chars_count += count_chars(child); - continue; - } - } - - iter_state.split_chunk = true; - } - - collect_text_chunks_impl(child, pos_list, state, cache, iter_state); - - iter_state.text_flow = TextFlow::Linear; - - // Next char after `textPath` should be split too. - if child.tag_name() == Some(EId::TextPath) { - iter_state.split_chunk = true; - } - - continue; - } - - if !parent.is_visible_element(state.opt) { - iter_state.chars_count += child.text().chars().count(); - continue; - } - - let anchor = parent.find_attribute(AId::TextAnchor).unwrap_or_default(); - - // TODO: what to do when <= 0? UB? - let font_size = super::units::resolve_font_size(parent, state); - let font_size = match NonZeroPositiveF32::new(font_size) { - Some(n) => n, - None => { - // Skip this span. - iter_state.chars_count += child.text().chars().count(); - continue; - } - }; - - let font = convert_font(parent, state); - - let raw_paint_order: svgtypes::PaintOrder = - parent.find_attribute(AId::PaintOrder).unwrap_or_default(); - let paint_order = super::converter::svg_paint_order_to_usvg(raw_paint_order); - - let mut dominant_baseline = parent - .find_attribute(AId::DominantBaseline) - .unwrap_or_default(); - - // `no-change` means "use parent". - if dominant_baseline == DominantBaseline::NoChange { - dominant_baseline = parent - .parent_element() - .unwrap() - .find_attribute(AId::DominantBaseline) - .unwrap_or_default(); - } - - let mut apply_kerning = true; - #[allow(clippy::if_same_then_else)] - if parent.resolve_length(AId::Kerning, state, -1.0) == 0.0 { - apply_kerning = false; - } else if parent.find_attribute::<&str>(AId::FontKerning) == Some("none") { - apply_kerning = false; - } - - let mut text_length = - parent.try_convert_length(AId::TextLength, Units::UserSpaceOnUse, state); - // Negative values should be ignored. - if let Some(n) = text_length { - if n < 0.0 { - text_length = None; - } - } - - let visibility: Visibility = parent.find_attribute(AId::Visibility).unwrap_or_default(); - - let span = TextSpan { - start: 0, - end: 0, - fill: style::resolve_fill(parent, true, state, cache), - stroke: style::resolve_stroke(parent, true, state, cache), - paint_order, - font, - font_size, - small_caps: parent.find_attribute::<&str>(AId::FontVariant) == Some("small-caps"), - apply_kerning, - decoration: resolve_decoration(parent, state, cache), - visible: visibility == Visibility::Visible, - dominant_baseline, - alignment_baseline: parent - .find_attribute(AId::AlignmentBaseline) - .unwrap_or_default(), - baseline_shift: convert_baseline_shift(parent, state), - letter_spacing: parent.resolve_length(AId::LetterSpacing, state, 0.0), - word_spacing: parent.resolve_length(AId::WordSpacing, state, 0.0), - text_length, - length_adjust: parent.find_attribute(AId::LengthAdjust).unwrap_or_default(), - }; - - let mut is_new_span = true; - for c in child.text().chars() { - let char_len = c.len_utf8(); - - // Create a new chunk if: - // - this is the first span (yes, position can be None) - // - text character has an absolute coordinate assigned to it (via x/y attribute) - // - `c` is the first char of the `textPath` - // - `c` is the first char after `textPath` - let is_new_chunk = pos_list[iter_state.chars_count].x.is_some() - || pos_list[iter_state.chars_count].y.is_some() - || iter_state.split_chunk - || iter_state.chunks.is_empty(); - - iter_state.split_chunk = false; - - if is_new_chunk { - iter_state.chunk_bytes_count = 0; - - let mut span2 = span.clone(); - span2.start = 0; - span2.end = char_len; - - iter_state.chunks.push(TextChunk { - x: pos_list[iter_state.chars_count].x, - y: pos_list[iter_state.chars_count].y, - anchor, - spans: vec![span2], - text_flow: iter_state.text_flow.clone(), - text: c.to_string(), - }); - } else if is_new_span { - // Add this span to the last text chunk. - let mut span2 = span.clone(); - span2.start = iter_state.chunk_bytes_count; - span2.end = iter_state.chunk_bytes_count + char_len; - - if let Some(chunk) = iter_state.chunks.last_mut() { - chunk.text.push(c); - chunk.spans.push(span2); - } - } else { - // Extend the last span. - if let Some(chunk) = iter_state.chunks.last_mut() { - chunk.text.push(c); - if let Some(span) = chunk.spans.last_mut() { - debug_assert_ne!(span.end, 0); - span.end += char_len; - } - } - } - - is_new_span = false; - iter_state.chars_count += 1; - iter_state.chunk_bytes_count += char_len; - } - } -} - -fn resolve_text_flow(node: SvgNode, state: &converter::State) -> Option { - let linked_node = node.attribute::(AId::Href)?; - let path = super::shapes::convert(linked_node, state)?; - - // The reference path's transform needs to be applied - let transform = linked_node.resolve_transform(AId::Transform, state); - let path = if !transform.is_identity() { - let mut path_copy = path.as_ref().clone(); - path_copy = path_copy.transform(transform)?; - Arc::new(path_copy) - } else { - path - }; - - let start_offset: Length = node.attribute(AId::StartOffset).unwrap_or_default(); - let start_offset = if start_offset.unit == LengthUnit::Percent { - // 'If a percentage is given, then the `startOffset` represents - // a percentage distance along the entire path.' - let path_len = path_length(&path); - (path_len * (start_offset.number / 100.0)) as f32 - } else { - node.resolve_length(AId::StartOffset, state, 0.0) - }; - - let id = NonEmptyString::new(linked_node.element_id().to_string())?; - Some(TextFlow::Path(Arc::new(TextPath { - id, - start_offset, - path, - }))) -} - -fn convert_font(node: SvgNode, state: &converter::State) -> Font { - let style: FontStyle = node.find_attribute(AId::FontStyle).unwrap_or_default(); - let stretch = conv_font_stretch(node); - let weight = resolve_font_weight(node); - - let font_families = if let Some(n) = node.ancestors().find(|n| n.has_attribute(AId::FontFamily)) - { - n.attribute(AId::FontFamily).unwrap_or("") - } else { - "" - }; - - let mut families = parse_font_families(font_families) - .ok() - .log_none(|| { - log::warn!( - "Failed to parse {} value: '{}'. Falling back to {}.", - AId::FontFamily, - font_families, - state.opt.font_family - ) - }) - .unwrap_or_default(); - - if families.is_empty() { - families.push(FontFamily::Named(state.opt.font_family.clone())) - } - - Font { - families, - style, - stretch, - weight, - } -} - -// TODO: properly resolve narrower/wider -fn conv_font_stretch(node: SvgNode) -> FontStretch { - if let Some(n) = node.ancestors().find(|n| n.has_attribute(AId::FontStretch)) { - match n.attribute(AId::FontStretch).unwrap_or("") { - "narrower" | "condensed" => FontStretch::Condensed, - "ultra-condensed" => FontStretch::UltraCondensed, - "extra-condensed" => FontStretch::ExtraCondensed, - "semi-condensed" => FontStretch::SemiCondensed, - "semi-expanded" => FontStretch::SemiExpanded, - "wider" | "expanded" => FontStretch::Expanded, - "extra-expanded" => FontStretch::ExtraExpanded, - "ultra-expanded" => FontStretch::UltraExpanded, - _ => FontStretch::Normal, - } - } else { - FontStretch::Normal - } -} - -fn resolve_font_weight(node: SvgNode) -> u16 { - fn bound(min: usize, val: usize, max: usize) -> usize { - std::cmp::max(min, std::cmp::min(max, val)) - } - - let nodes: Vec<_> = node.ancestors().collect(); - let mut weight = 400; - for n in nodes.iter().rev().skip(1) { - // skip Root - weight = match n.attribute(AId::FontWeight).unwrap_or("") { - "normal" => 400, - "bold" => 700, - "100" => 100, - "200" => 200, - "300" => 300, - "400" => 400, - "500" => 500, - "600" => 600, - "700" => 700, - "800" => 800, - "900" => 900, - "bolder" => { - // By the CSS2 spec the default value should be 400 - // so `bolder` will result in 500. - // But Chrome and Inkscape will give us 700. - // Have no idea is it a bug or something, but - // we will follow such behavior for now. - let step = if weight == 400 { 300 } else { 100 }; - - bound(100, weight + step, 900) - } - "lighter" => { - // By the CSS2 spec the default value should be 400 - // so `lighter` will result in 300. - // But Chrome and Inkscape will give us 200. - // Have no idea is it a bug or something, but - // we will follow such behavior for now. - let step = if weight == 400 { 200 } else { 100 }; - - bound(100, weight - step, 900) - } - _ => weight, - }; - } - - weight as u16 -} - -/// Resolves text's character positions. -/// -/// This includes: x, y, dx, dy. -/// -/// # The character -/// -/// The first problem with this task is that the *character* itself -/// is basically undefined in the SVG spec. Sometimes it's an *XML character*, -/// sometimes a *glyph*, and sometimes just a *character*. -/// -/// There is an ongoing [discussion](https://github.com/w3c/svgwg/issues/537) -/// on the SVG working group that addresses this by stating that a character -/// is a Unicode code point. But it's not final. -/// -/// Also, according to the SVG 2 spec, *character* is *a Unicode code point*. -/// -/// Anyway, we treat a character as a Unicode code point. -/// -/// # Algorithm -/// -/// To resolve positions, we have to iterate over descendant nodes and -/// if the current node is a `tspan` and has x/y/dx/dy attribute, -/// than the positions from this attribute should be assigned to the characters -/// of this `tspan` and it's descendants. -/// -/// Positions list can have more values than characters in the `tspan`, -/// so we have to clamp it, because values should not overlap, e.g.: -/// -/// (we ignore whitespaces for example purposes, -/// so the `text` content is `Text` and not `T ex t`) -/// -/// ```text -/// -/// a -/// -/// bc -/// -/// d -/// -/// ``` -/// -/// In this example, the `d` position should not be set to `30`. -/// And the result should be: `[None, 10, 20, None]` -/// -/// Another example: -/// -/// ```text -/// -/// -/// a -/// -/// bc -/// -/// -/// d -/// -/// ``` -/// -/// The result should be: `[100, 50, 120, None]` -fn resolve_positions_list(text_node: SvgNode, state: &converter::State) -> Vec { - // Allocate a list that has all characters positions set to `None`. - let total_chars = count_chars(text_node); - let mut list = vec![ - CharacterPosition { - x: None, - y: None, - dx: None, - dy: None, - }; - total_chars - ]; - - let mut offset = 0; - for child in text_node.descendants() { - if child.is_element() { - // We must ignore text positions on `textPath`. - if !matches!(child.tag_name(), Some(EId::Text) | Some(EId::Tspan)) { - continue; - } - - let child_chars = count_chars(child); - macro_rules! push_list { - ($aid:expr, $field:ident) => { - if let Some(num_list) = super::units::convert_list(child, $aid, state) { - // Note that we are using not the total count, - // but the amount of characters in the current `tspan` (with children). - let len = std::cmp::min(num_list.len(), child_chars); - for i in 0..len { - list[offset + i].$field = Some(num_list[i]); - } - } - }; - } - - push_list!(AId::X, x); - push_list!(AId::Y, y); - push_list!(AId::Dx, dx); - push_list!(AId::Dy, dy); - } else if child.is_text() { - // Advance the offset. - offset += child.text().chars().count(); - } - } - - list -} - -/// Resolves characters rotation. -/// -/// The algorithm is well explained -/// [in the SVG spec](https://www.w3.org/TR/SVG11/text.html#TSpanElement) (scroll down a bit). -/// -/// ![](https://www.w3.org/TR/SVG11/images/text/tspan05-diagram.png) -/// -/// Note: this algorithm differs from the position resolving one. -fn resolve_rotate_list(text_node: SvgNode) -> Vec { - // Allocate a list that has all characters angles set to `0.0`. - let mut list = vec![0.0; count_chars(text_node)]; - let mut last = 0.0; - let mut offset = 0; - for child in text_node.descendants() { - if child.is_element() { - if let Some(rotate) = child.attribute::>(AId::Rotate) { - for i in 0..count_chars(child) { - if let Some(a) = rotate.get(i).cloned() { - list[offset + i] = a; - last = a; - } else { - // If the rotate list doesn't specify the rotation for - // this character - use the last one. - list[offset + i] = last; - } - } - } - } else if child.is_text() { - // Advance the offset. - offset += child.text().chars().count(); - } - } - - list -} - -/// Resolves node's `text-decoration` property. -fn resolve_decoration( - tspan: SvgNode, - state: &converter::State, - cache: &mut converter::Cache, -) -> TextDecoration { - // Checks if a decoration is present in a single node. - fn find_decoration(node: SvgNode, value: &str) -> bool { - if let Some(str_value) = node.attribute::<&str>(AId::TextDecoration) { - str_value.split(' ').any(|v| v == value) - } else { - false - } - } - - // The algorithm is as follows: First, we check whether the given text decoration appears in ANY - // ancestor, i.e. it can also appear in ancestors outside of the element. If the text - // decoration is declared somewhere, it means that this tspan will have it. However, we still - // need to find the corresponding fill/stroke for it. To do this, we iterate through all - // ancestors (i.e. tspans) until we find the text decoration declared. If not, we will - // stop at latest at the text node, and use its fill/stroke. - let mut gen_style = |text_decoration: &str| { - if !tspan - .ancestors() - .any(|n| find_decoration(n, text_decoration)) - { - return None; - } - - let mut fill_node = None; - let mut stroke_node = None; - - for node in tspan.ancestors() { - if find_decoration(node, text_decoration) || node.tag_name() == Some(EId::Text) { - fill_node = fill_node.map_or(Some(node), Some); - stroke_node = stroke_node.map_or(Some(node), Some); - break; - } - } - - Some(TextDecorationStyle { - fill: fill_node.and_then(|node| style::resolve_fill(node, true, state, cache)), - stroke: stroke_node.and_then(|node| style::resolve_stroke(node, true, state, cache)), - }) - }; - - TextDecoration { - underline: gen_style("underline"), - overline: gen_style("overline"), - line_through: gen_style("line-through"), - } -} - -fn convert_baseline_shift(node: SvgNode, state: &converter::State) -> Vec { - let mut shift = Vec::new(); - let nodes: Vec<_> = node - .ancestors() - .take_while(|n| n.tag_name() != Some(EId::Text)) - .collect(); - for n in nodes { - if let Some(len) = n.try_attribute::(AId::BaselineShift) { - if len.unit == LengthUnit::Percent { - let n = super::units::resolve_font_size(n, state) * (len.number as f32 / 100.0); - shift.push(BaselineShift::Number(n)); - } else { - let n = super::units::convert_length( - len, - n, - AId::BaselineShift, - Units::ObjectBoundingBox, - state, - ); - shift.push(BaselineShift::Number(n)); - } - } else if let Some(s) = n.attribute(AId::BaselineShift) { - match s { - "sub" => shift.push(BaselineShift::Subscript), - "super" => shift.push(BaselineShift::Superscript), - _ => shift.push(BaselineShift::Baseline), - } - } - } - - if shift - .iter() - .all(|base| matches!(base, BaselineShift::Baseline)) - { - shift.clear(); - } - - shift -} - -fn count_chars(node: SvgNode) -> usize { - node.descendants() - .filter(|n| n.is_text()) - .fold(0, |w, n| w + n.text().chars().count()) -} - -/// Converts the writing mode. -/// -/// [SVG 2] references [CSS Writing Modes Level 3] for the definition of the -/// 'writing-mode' property, there are only two writing modes: -/// horizontal left-to-right and vertical right-to-left. -/// -/// That specification introduces new values for the property. The SVG 1.1 -/// values are obsolete but must still be supported by converting the specified -/// values to computed values as follows: -/// -/// - `lr`, `lr-tb`, `rl`, `rl-tb` => `horizontal-tb` -/// - `tb`, `tb-rl` => `vertical-rl` -/// -/// The current `vertical-lr` behaves exactly the same as `vertical-rl`. -/// -/// Also, looks like no one really supports the `rl` and `rl-tb`, except `Batik`. -/// And I'm not sure if its behaviour is correct. -/// -/// So we will ignore it as well, mainly because I have no idea how exactly -/// it should affect the rendering. -/// -/// [SVG 2]: https://www.w3.org/TR/SVG2/text.html#WritingModeProperty -/// [CSS Writing Modes Level 3]: https://www.w3.org/TR/css-writing-modes-3/#svg-writing-mode-css -fn convert_writing_mode(text_node: SvgNode) -> WritingMode { - if let Some(n) = text_node - .ancestors() - .find(|n| n.has_attribute(AId::WritingMode)) - { - match n.attribute(AId::WritingMode).unwrap_or("lr-tb") { - "tb" | "tb-rl" | "vertical-rl" | "vertical-lr" => WritingMode::TopToBottom, - _ => WritingMode::LeftToRight, - } - } else { - WritingMode::LeftToRight - } -} - -fn path_length(path: &tiny_skia_path::Path) -> f64 { - let mut prev_mx = path.points()[0].x; - let mut prev_my = path.points()[0].y; - let mut prev_x = prev_mx; - let mut prev_y = prev_my; - - fn create_curve_from_line(px: f32, py: f32, x: f32, y: f32) -> kurbo::CubicBez { - let line = kurbo::Line::new( - kurbo::Point::new(px as f64, py as f64), - kurbo::Point::new(x as f64, y as f64), - ); - let p1 = line.eval(0.33); - let p2 = line.eval(0.66); - kurbo::CubicBez::new(line.p0, p1, p2, line.p1) - } - - let mut length = 0.0; - for seg in path.segments() { - let curve = match seg { - tiny_skia_path::PathSegment::MoveTo(p) => { - prev_mx = p.x; - prev_my = p.y; - prev_x = p.x; - prev_y = p.y; - continue; - } - tiny_skia_path::PathSegment::LineTo(p) => { - create_curve_from_line(prev_x, prev_y, p.x, p.y) - } - tiny_skia_path::PathSegment::QuadTo(p1, p) => kurbo::QuadBez::new( - kurbo::Point::new(prev_x as f64, prev_y as f64), - kurbo::Point::new(p1.x as f64, p1.y as f64), - kurbo::Point::new(p.x as f64, p.y as f64), - ) - .raise(), - tiny_skia_path::PathSegment::CubicTo(p1, p2, p) => kurbo::CubicBez::new( - kurbo::Point::new(prev_x as f64, prev_y as f64), - kurbo::Point::new(p1.x as f64, p1.y as f64), - kurbo::Point::new(p2.x as f64, p2.y as f64), - kurbo::Point::new(p.x as f64, p.y as f64), - ), - tiny_skia_path::PathSegment::Close => { - create_curve_from_line(prev_x, prev_y, prev_mx, prev_my) - } - }; - - length += curve.arclen(0.5); - prev_x = curve.p3.x as f32; - prev_y = curve.p3.y as f32; - } - - length -} diff --git a/resvg-0.43.0/crates/usvg/src/parser/units.rs b/resvg-0.43.0/crates/usvg/src/parser/units.rs deleted file mode 100644 index f15abb2..0000000 --- a/resvg-0.43.0/crates/usvg/src/parser/units.rs +++ /dev/null @@ -1,146 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use svgtypes::{Length, LengthUnit as Unit}; - -use super::converter; -use super::svgtree::{AId, SvgNode}; -use crate::Units; - -#[inline(never)] -pub(crate) fn convert_length( - length: Length, - node: SvgNode, - aid: AId, - object_units: Units, - state: &converter::State, -) -> f32 { - let dpi = state.opt.dpi; - let n = length.number as f32; - match length.unit { - Unit::None | Unit::Px => n, - Unit::Em => n * resolve_font_size(node, state), - Unit::Ex => n * resolve_font_size(node, state) / 2.0, - Unit::In => n * dpi, - Unit::Cm => n * dpi / 2.54, - Unit::Mm => n * dpi / 25.4, - Unit::Pt => n * dpi / 72.0, - Unit::Pc => n * dpi / 6.0, - Unit::Percent => { - if object_units == Units::ObjectBoundingBox { - n / 100.0 - } else { - let view_box = state.view_box; - - match aid { - AId::Cx - | AId::Dx - | AId::Fx - | AId::MarkerWidth - | AId::RefX - | AId::Rx - | AId::Width - | AId::X - | AId::X1 - | AId::X2 => convert_percent(length, view_box.width()), - AId::Cy - | AId::Dy - | AId::Fy - | AId::Height - | AId::MarkerHeight - | AId::RefY - | AId::Ry - | AId::Y - | AId::Y1 - | AId::Y2 => convert_percent(length, view_box.height()), - _ => { - let mut vb_len = view_box.width().powi(2) + view_box.height().powi(2); - vb_len = (vb_len / 2.0).sqrt(); - convert_percent(length, vb_len) - } - } - } - } - } -} - -pub(crate) fn convert_user_length( - length: Length, - node: SvgNode, - aid: AId, - state: &converter::State, -) -> f32 { - convert_length(length, node, aid, Units::UserSpaceOnUse, state) -} - -#[inline(never)] -pub(crate) fn convert_list(node: SvgNode, aid: AId, state: &converter::State) -> Option> { - if let Some(text) = node.attribute::<&str>(aid) { - let mut num_list = Vec::new(); - for length in svgtypes::LengthListParser::from(text).flatten() { - num_list.push(convert_user_length(length, node, aid, state)); - } - - Some(num_list) - } else { - None - } -} - -fn convert_percent(length: Length, base: f32) -> f32 { - base * (length.number as f32) / 100.0 -} - -#[inline(never)] -pub(crate) fn resolve_font_size(node: SvgNode, state: &converter::State) -> f32 { - let nodes: Vec<_> = node.ancestors().collect(); - let mut font_size = state.opt.font_size; - for n in nodes.iter().rev().skip(1) { - // skip Root - if let Some(length) = n.try_attribute::(AId::FontSize) { - let dpi = state.opt.dpi; - let n = length.number as f32; - font_size = match length.unit { - Unit::None | Unit::Px => n, - Unit::Em => n * font_size, - Unit::Ex => n * font_size / 2.0, - Unit::In => n * dpi, - Unit::Cm => n * dpi / 2.54, - Unit::Mm => n * dpi / 25.4, - Unit::Pt => n * dpi / 72.0, - Unit::Pc => n * dpi / 6.0, - Unit::Percent => { - // If `font-size` has percent units that it's value - // is relative to the parent node `font-size`. - length.number as f32 * font_size * 0.01 - } - } - } else if let Some(name) = n.attribute(AId::FontSize) { - font_size = convert_named_font_size(name, font_size); - } - } - - font_size -} - -fn convert_named_font_size(name: &str, parent_font_size: f32) -> f32 { - let factor = match name { - "xx-small" => -3, - "x-small" => -2, - "small" => -1, - "medium" => 0, - "large" => 1, - "x-large" => 2, - "xx-large" => 3, - "smaller" => -1, - "larger" => 1, - _ => { - log::warn!("Invalid 'font-size' value: '{}'.", name); - 0 - } - }; - - // 'On a computer screen a scaling factor of 1.2 is suggested between adjacent indexes.' - parent_font_size * 1.2f32.powi(factor) -} diff --git a/resvg-0.43.0/crates/usvg/src/parser/use_node.rs b/resvg-0.43.0/crates/usvg/src/parser/use_node.rs deleted file mode 100644 index 20dc17f..0000000 --- a/resvg-0.43.0/crates/usvg/src/parser/use_node.rs +++ /dev/null @@ -1,345 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use std::sync::Arc; - -use svgtypes::{Length, LengthUnit}; - -use super::svgtree::{AId, EId, SvgNode}; -use super::{converter, style}; -use crate::tree::ContextElement; -use crate::{Group, IsValidLength, Node, NonZeroRect, Path, Size, Transform, ViewBox}; - -pub(crate) fn convert( - node: SvgNode, - state: &converter::State, - cache: &mut converter::Cache, - parent: &mut Group, -) { - let child = match node.first_child() { - Some(v) => v, - None => return, - }; - - if state.parent_clip_path.is_some() && child.tag_name() == Some(EId::Symbol) { - // Ignore `symbol` referenced by `use` inside a `clipPath`. - // It will be ignored later anyway, but this will prevent - // a redundant `clipPath` creation (which is required for `symbol`). - return; - } - - let mut use_state = state.clone(); - use_state.context_element = Some(( - style::resolve_fill(node, true, state, cache).map(|mut f| { - f.context_element = Some(ContextElement::UseNode); - f - }), - style::resolve_stroke(node, true, state, cache).map(|mut s| { - s.context_element = Some(ContextElement::UseNode); - s - }), - )); - - // We require an original transformation to setup 'clipPath'. - let mut orig_ts = node.resolve_transform(AId::Transform, state); - let mut new_ts = Transform::default(); - - { - let x = node.convert_user_length(AId::X, &use_state, Length::zero()); - let y = node.convert_user_length(AId::Y, &use_state, Length::zero()); - new_ts = new_ts.pre_translate(x, y); - } - - let linked_to_symbol = child.tag_name() == Some(EId::Symbol); - - if linked_to_symbol { - if let Some(ts) = viewbox_transform(node, child, &use_state) { - new_ts = new_ts.pre_concat(ts); - } - - if let Some(clip_rect) = get_clip_rect(node, child, &use_state) { - let mut g = clip_element(node, clip_rect, orig_ts, &use_state, cache); - g.abs_transform = parent.abs_transform; - - // Make group for `use`. - if let Some(mut g2) = - converter::convert_group(node, &use_state, true, cache, &mut g, &|cache, g2| { - convert_children(child, new_ts, &use_state, cache, false, g2); - }) - { - // We must reset transform, because it was already set - // to the group with clip-path. - g.is_context_element = true; - g2.id = String::new(); // Prevent ID duplication. - g2.transform = Transform::default(); - g.children.push(Node::Group(Box::new(g2))); - } - - if g.children.is_empty() { - return; - } - - g.calculate_bounding_boxes(); - parent.children.push(Node::Group(Box::new(g))); - return; - } - } - - orig_ts = orig_ts.pre_concat(new_ts); - - if linked_to_symbol { - // Make group for `use`. - if let Some(mut g) = - converter::convert_group(node, &use_state, false, cache, parent, &|cache, g| { - convert_children(child, orig_ts, &use_state, cache, false, g); - }) - { - g.is_context_element = true; - g.transform = Transform::default(); - parent.children.push(Node::Group(Box::new(g))); - } - } else { - let linked_to_svg = child.tag_name() == Some(EId::Svg); - if linked_to_svg { - // When a `use` element references a `svg` element, - // we have to remember `use` element size and use it - // instead of `svg` element size. - - let def = Length::new(100.0, LengthUnit::Percent); - // As per usual, the SVG spec doesn't clarify this edge case, - // but it seems like `use` size has to be reset by each `use`. - // Meaning if we have two nested `use` elements, where one had set `width` and - // other set `height`, we have to ignore the first `width`. - // - // Example: - // - // - // - // - // In this case `svg2` size is 80x100 and not 100x100. - use_state.use_size = (None, None); - - // Width and height can be set independently. - if node.has_attribute(AId::Width) { - use_state.use_size.0 = Some(node.convert_user_length(AId::Width, &use_state, def)); - } - if node.has_attribute(AId::Height) { - use_state.use_size.1 = Some(node.convert_user_length(AId::Height, &use_state, def)); - } - - convert_children(node, orig_ts, &use_state, cache, true, parent); - } else { - convert_children(node, orig_ts, &use_state, cache, true, parent); - } - } -} - -pub(crate) fn convert_svg( - node: SvgNode, - state: &converter::State, - cache: &mut converter::Cache, - parent: &mut Group, -) { - // We require original transformation to setup 'clipPath'. - let mut orig_ts = node.resolve_transform(AId::Transform, state); - let mut new_ts = Transform::default(); - - let x = node.convert_user_length(AId::X, state, Length::zero()); - let y = node.convert_user_length(AId::Y, state, Length::zero()); - new_ts = new_ts.pre_translate(x, y); - - if let Some(ts) = viewbox_transform(node, node, state) { - new_ts = new_ts.pre_concat(ts); - } - - // We have to create a new state which would have its viewBox set to the current SVG element. - // Note that we're not updating State::size - it's a completely different property. - let mut new_state = state.clone(); - new_state.view_box = { - if let Some(vb) = node.parse_viewbox() { - vb - } else { - // No `viewBox` attribute? Then use `x`, `y`, `width` and `height` instead. - let (mut w, mut h) = use_node_size(node, &state); - - // If attributes `width` and/or `height` are provided on the `use` element, - // then these values will override the corresponding attributes - // on the `svg` in the generated tree. - w = state.use_size.0.unwrap_or(w); - h = state.use_size.1.unwrap_or(h); - - NonZeroRect::from_xywh(x, y, w, h).unwrap_or(state.view_box) - } - }; - - if let Some(clip_rect) = get_clip_rect(node, node, state) { - let mut g = clip_element(node, clip_rect, orig_ts, state, cache); - g.abs_transform = parent.abs_transform; - convert_children(node, new_ts, &new_state, cache, false, &mut g); - g.calculate_bounding_boxes(); - parent.children.push(Node::Group(Box::new(g))); - } else { - orig_ts = orig_ts.pre_concat(new_ts); - convert_children(node, orig_ts, &new_state, cache, false, parent); - } -} - -fn clip_element( - node: SvgNode, - clip_rect: NonZeroRect, - transform: Transform, - state: &converter::State, - cache: &mut converter::Cache, -) -> Group { - // We can't set `clip-path` on the element itself, - // because it will be affected by a possible transform. - // So we have to create an additional group. - - // Emulate a new viewport via clipPath. - // - // From: - // - // - // - // To: - // - // - // - // - // - // - // - // - - let mut clip_path = crate::ClipPath::empty(cache.gen_clip_path_id()); - - let mut path = Path::new_simple(Arc::new(tiny_skia_path::PathBuilder::from_rect( - clip_rect.to_rect(), - ))) - .unwrap(); - path.fill = Some(crate::Fill::default()); - clip_path.root.children.push(Node::Path(Box::new(path))); - - // Nodes generated by markers must not have an ID. Otherwise we would have duplicates. - let id = if state.parent_markers.is_empty() { - node.element_id().to_string() - } else { - String::new() - }; - - Group { - id, - transform, - clip_path: Some(Arc::new(clip_path)), - ..Group::empty() - } -} - -fn convert_children( - node: SvgNode, - transform: Transform, - state: &converter::State, - cache: &mut converter::Cache, - is_context_element: bool, - parent: &mut Group, -) { - // Temporarily adjust absolute transform so `convert_group` would account for `transform`. - let old_abs_transform = parent.abs_transform; - parent.abs_transform = parent.abs_transform.pre_concat(transform); - - let required = !transform.is_identity(); - if let Some(mut g) = - converter::convert_group(node, state, required, cache, parent, &|cache, g| { - if state.parent_clip_path.is_some() { - converter::convert_clip_path_elements(node, state, cache, g); - } else { - converter::convert_children(node, state, cache, g); - } - }) - { - g.is_context_element = is_context_element; - g.transform = transform; - parent.children.push(Node::Group(Box::new(g))); - } - - parent.abs_transform = old_abs_transform; -} - -fn get_clip_rect( - use_node: SvgNode, - symbol_node: SvgNode, - state: &converter::State, -) -> Option { - // No need to clip elements with overflow:visible. - if matches!( - symbol_node.attribute(AId::Overflow), - Some("visible") | Some("auto") - ) { - return None; - } - - // A nested `svg` with only the `viewBox` attribute and no "rectangle" (x, y, width, height) - // should not be clipped. - if use_node.tag_name() == Some(EId::Svg) { - // Nested `svg` referenced by `use` still should be clipped, but by `use` bounds. - if state.use_size.0.is_none() && state.use_size.1.is_none() { - if !(use_node.has_attribute(AId::Width) && use_node.has_attribute(AId::Height)) { - return None; - } - } - } - - let (x, y, mut w, mut h) = { - let x = use_node.convert_user_length(AId::X, state, Length::zero()); - let y = use_node.convert_user_length(AId::Y, state, Length::zero()); - let (w, h) = use_node_size(use_node, state); - (x, y, w, h) - }; - - if use_node.tag_name() == Some(EId::Svg) { - // If attributes `width` and/or `height` are provided on the `use` element, - // then these values will override the corresponding attributes - // on the `svg` in the generated tree. - w = state.use_size.0.unwrap_or(w); - h = state.use_size.1.unwrap_or(h); - } - - if !w.is_valid_length() || !h.is_valid_length() { - return None; - } - - NonZeroRect::from_xywh(x, y, w, h) -} - -fn use_node_size(node: SvgNode, state: &converter::State) -> (f32, f32) { - let def = Length::new(100.0, LengthUnit::Percent); - let w = node.convert_user_length(AId::Width, state, def); - let h = node.convert_user_length(AId::Height, state, def); - (w, h) -} - -fn viewbox_transform( - node: SvgNode, - linked: SvgNode, - state: &converter::State, -) -> Option { - let (mut w, mut h) = use_node_size(node, state); - - if node.tag_name() == Some(EId::Svg) { - // If attributes `width` and/or `height` are provided on the `use` element, - // then these values will override the corresponding attributes - // on the `svg` in the generated tree. - w = state.use_size.0.unwrap_or(w); - h = state.use_size.1.unwrap_or(h); - } - - let size = Size::from_wh(w, h)?; - let rect = linked.parse_viewbox()?; - let aspect = linked - .attribute(AId::PreserveAspectRatio) - .unwrap_or_default(); - let view_box = ViewBox { rect, aspect }; - - Some(view_box.to_transform(size)) -} diff --git a/resvg-0.43.0/crates/usvg/src/text/colr.rs b/resvg-0.43.0/crates/usvg/src/text/colr.rs deleted file mode 100644 index 602ac3d..0000000 --- a/resvg-0.43.0/crates/usvg/src/text/colr.rs +++ /dev/null @@ -1,340 +0,0 @@ -use crate::parser::OptionLog; -use rustybuzz::ttf_parser; - -struct Builder<'a>(&'a mut String); - -impl Builder<'_> { - fn finish(&mut self) { - if !self.0.is_empty() { - self.0.pop(); // remove trailing space - } - } -} - -impl ttf_parser::OutlineBuilder for Builder<'_> { - fn move_to(&mut self, x: f32, y: f32) { - use std::fmt::Write; - write!(self.0, "M {} {} ", x, y).unwrap() - } - - fn line_to(&mut self, x: f32, y: f32) { - use std::fmt::Write; - write!(self.0, "L {} {} ", x, y).unwrap() - } - - fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) { - use std::fmt::Write; - write!(self.0, "Q {} {} {} {} ", x1, y1, x, y).unwrap() - } - - fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) { - use std::fmt::Write; - write!(self.0, "C {} {} {} {} {} {} ", x1, y1, x2, y2, x, y).unwrap() - } - - fn close(&mut self) { - self.0.push_str("Z ") - } -} - -trait XmlWriterExt { - fn write_color_attribute(&mut self, name: &str, ts: ttf_parser::RgbaColor); - fn write_transform_attribute(&mut self, name: &str, ts: ttf_parser::Transform); - fn write_spread_method_attribute(&mut self, method: ttf_parser::colr::GradientExtend); -} - -impl XmlWriterExt for xmlwriter::XmlWriter { - fn write_color_attribute(&mut self, name: &str, color: ttf_parser::RgbaColor) { - self.write_attribute_fmt( - name, - format_args!("rgb({}, {}, {})", color.red, color.green, color.blue), - ); - } - - fn write_transform_attribute(&mut self, name: &str, ts: ttf_parser::Transform) { - if ts.is_default() { - return; - } - - self.write_attribute_fmt( - name, - format_args!( - "matrix({} {} {} {} {} {})", - ts.a, ts.b, ts.c, ts.d, ts.e, ts.f - ), - ); - } - - fn write_spread_method_attribute(&mut self, extend: ttf_parser::colr::GradientExtend) { - self.write_attribute( - "spreadMethod", - match extend { - ttf_parser::colr::GradientExtend::Pad => &"pad", - ttf_parser::colr::GradientExtend::Repeat => &"repeat", - ttf_parser::colr::GradientExtend::Reflect => &"reflect", - }, - ); - } -} - -// NOTE: This is only a best-effort translation of COLR into SVG. -pub(crate) struct GlyphPainter<'a> { - pub(crate) face: &'a ttf_parser::Face<'a>, - pub(crate) svg: &'a mut xmlwriter::XmlWriter, - pub(crate) path_buf: &'a mut String, - pub(crate) gradient_index: usize, - pub(crate) clip_path_index: usize, - pub(crate) palette_index: u16, - pub(crate) transform: ttf_parser::Transform, - pub(crate) outline_transform: ttf_parser::Transform, - pub(crate) transforms_stack: Vec, -} - -impl<'a> GlyphPainter<'a> { - fn write_gradient_stops(&mut self, stops: ttf_parser::colr::GradientStopsIter) { - for stop in stops { - self.svg.start_element("stop"); - self.svg.write_attribute("offset", &stop.stop_offset); - self.svg.write_color_attribute("stop-color", stop.color); - let opacity = f32::from(stop.color.alpha) / 255.0; - self.svg.write_attribute("stop-opacity", &opacity); - self.svg.end_element(); - } - } - - fn paint_solid(&mut self, color: ttf_parser::RgbaColor) { - self.svg.start_element("path"); - self.svg.write_color_attribute("fill", color); - let opacity = f32::from(color.alpha) / 255.0; - self.svg.write_attribute("fill-opacity", &opacity); - self.svg - .write_transform_attribute("transform", self.outline_transform); - self.svg.write_attribute("d", self.path_buf); - self.svg.end_element(); - } - - fn paint_linear_gradient(&mut self, gradient: ttf_parser::colr::LinearGradient<'a>) { - let gradient_id = format!("lg{}", self.gradient_index); - self.gradient_index += 1; - - let gradient_transform = paint_transform(self.outline_transform, self.transform); - - // TODO: We ignore x2, y2. Have to apply them somehow. - // TODO: The way spreadMode works in ttf and svg is a bit different. In SVG, the spreadMode - // will always be applied based on x1/y1 and x2/y2. However, in TTF the spreadMode will - // be applied from the first/last stop. So if we have a gradient with x1=0 x2=1, and - // a stop at x=0.4 and x=0.6, then in SVG we will always see a padding, while in ttf - // we will see the actual spreadMode. We need to account for that somehow. - self.svg.start_element("linearGradient"); - self.svg.write_attribute("id", &gradient_id); - self.svg.write_attribute("x1", &gradient.x0); - self.svg.write_attribute("y1", &gradient.y0); - self.svg.write_attribute("x2", &gradient.x1); - self.svg.write_attribute("y2", &gradient.y1); - self.svg.write_attribute("gradientUnits", &"userSpaceOnUse"); - self.svg.write_spread_method_attribute(gradient.extend); - self.svg - .write_transform_attribute("gradientTransform", gradient_transform); - self.write_gradient_stops( - gradient.stops(self.palette_index, self.face.variation_coordinates()), - ); - self.svg.end_element(); - - self.svg.start_element("path"); - self.svg - .write_attribute_fmt("fill", format_args!("url(#{})", gradient_id)); - self.svg - .write_transform_attribute("transform", self.outline_transform); - self.svg.write_attribute("d", self.path_buf); - self.svg.end_element(); - } - - fn paint_radial_gradient(&mut self, gradient: ttf_parser::colr::RadialGradient<'a>) { - let gradient_id = format!("rg{}", self.gradient_index); - self.gradient_index += 1; - - let gradient_transform = paint_transform(self.outline_transform, self.transform); - - self.svg.start_element("radialGradient"); - self.svg.write_attribute("id", &gradient_id); - self.svg.write_attribute("cx", &gradient.x1); - self.svg.write_attribute("cy", &gradient.y1); - self.svg.write_attribute("r", &gradient.r1); - self.svg.write_attribute("fr", &gradient.r0); - self.svg.write_attribute("fx", &gradient.x0); - self.svg.write_attribute("fy", &gradient.y0); - self.svg.write_attribute("gradientUnits", &"userSpaceOnUse"); - self.svg.write_spread_method_attribute(gradient.extend); - self.svg - .write_transform_attribute("gradientTransform", gradient_transform); - self.write_gradient_stops( - gradient.stops(self.palette_index, self.face.variation_coordinates()), - ); - self.svg.end_element(); - - self.svg.start_element("path"); - self.svg - .write_attribute_fmt("fill", format_args!("url(#{})", gradient_id)); - self.svg - .write_transform_attribute("transform", self.outline_transform); - self.svg.write_attribute("d", self.path_buf); - self.svg.end_element(); - } - - fn paint_sweep_gradient(&mut self, _: ttf_parser::colr::SweepGradient<'a>) { - println!("Warning: sweep gradients are not supported.") - } -} - -fn paint_transform( - outline_transform: ttf_parser::Transform, - transform: ttf_parser::Transform, -) -> ttf_parser::Transform { - let outline_transform = tiny_skia_path::Transform::from_row( - outline_transform.a, - outline_transform.b, - outline_transform.c, - outline_transform.d, - outline_transform.e, - outline_transform.f, - ); - - let gradient_transform = tiny_skia_path::Transform::from_row( - transform.a, - transform.b, - transform.c, - transform.d, - transform.e, - transform.f, - ); - - let gradient_transform = outline_transform - .invert() - .log_none(|| log::warn!("Failed to calculate transform for gradient in glyph.")) - .unwrap_or_default() - .pre_concat(gradient_transform); - - ttf_parser::Transform { - a: gradient_transform.sx, - b: gradient_transform.ky, - c: gradient_transform.kx, - d: gradient_transform.sy, - e: gradient_transform.tx, - f: gradient_transform.ty, - } -} - -impl GlyphPainter<'_> { - fn clip_with_path(&mut self, path: &str) { - let clip_id = format!("cp{}", self.clip_path_index); - self.clip_path_index += 1; - - self.svg.start_element("clipPath"); - self.svg.write_attribute("id", &clip_id); - self.svg.start_element("path"); - self.svg - .write_transform_attribute("transform", self.outline_transform); - self.svg.write_attribute("d", &path); - self.svg.end_element(); - self.svg.end_element(); - - self.svg.start_element("g"); - self.svg - .write_attribute_fmt("clip-path", format_args!("url(#{})", clip_id)); - } -} - -impl<'a> ttf_parser::colr::Painter<'a> for GlyphPainter<'a> { - fn outline_glyph(&mut self, glyph_id: ttf_parser::GlyphId) { - self.path_buf.clear(); - let mut builder = Builder(self.path_buf); - match self.face.outline_glyph(glyph_id, &mut builder) { - Some(v) => v, - None => return, - }; - builder.finish(); - - // We have to write outline using the current transform. - self.outline_transform = self.transform; - } - - fn push_layer(&mut self, mode: ttf_parser::colr::CompositeMode) { - self.svg.start_element("g"); - - use ttf_parser::colr::CompositeMode; - // TODO: Need to figure out how to represent the other blend modes - // in SVG. - let mode = match mode { - CompositeMode::SourceOver => "normal", - CompositeMode::Screen => "screen", - CompositeMode::Overlay => "overlay", - CompositeMode::Darken => "darken", - CompositeMode::Lighten => "lighten", - CompositeMode::ColorDodge => "color-dodge", - CompositeMode::ColorBurn => "color-burn", - CompositeMode::HardLight => "hard-light", - CompositeMode::SoftLight => "soft-light", - CompositeMode::Difference => "difference", - CompositeMode::Exclusion => "exclusion", - CompositeMode::Multiply => "multiply", - CompositeMode::Hue => "hue", - CompositeMode::Saturation => "saturation", - CompositeMode::Color => "color", - CompositeMode::Luminosity => "luminosity", - _ => { - println!("Warning: unsupported blend mode: {:?}", mode); - "normal" - } - }; - self.svg.write_attribute_fmt( - "style", - format_args!("mix-blend-mode: {}; isolation: isolate", mode), - ); - } - - fn pop_layer(&mut self) { - self.svg.end_element(); // g - } - - fn push_transform(&mut self, transform: ttf_parser::Transform) { - self.transforms_stack.push(self.transform); - self.transform = ttf_parser::Transform::combine(self.transform, transform); - } - - fn paint(&mut self, paint: ttf_parser::colr::Paint<'a>) { - match paint { - ttf_parser::colr::Paint::Solid(color) => self.paint_solid(color), - ttf_parser::colr::Paint::LinearGradient(lg) => self.paint_linear_gradient(lg), - ttf_parser::colr::Paint::RadialGradient(rg) => self.paint_radial_gradient(rg), - ttf_parser::colr::Paint::SweepGradient(sg) => self.paint_sweep_gradient(sg), - } - } - - fn pop_transform(&mut self) { - if let Some(ts) = self.transforms_stack.pop() { - self.transform = ts - } - } - - fn push_clip(&mut self) { - self.clip_with_path(&self.path_buf.clone()); - } - - fn pop_clip(&mut self) { - self.svg.end_element(); - } - - fn push_clip_box(&mut self, clipbox: ttf_parser::colr::ClipBox) { - let x_min = clipbox.x_min; - let x_max = clipbox.x_max; - let y_min = clipbox.y_min; - let y_max = clipbox.y_max; - - let clip_path = format!( - "M {} {} L {} {} L {} {} L {} {} Z", - x_min, y_min, x_max, y_min, x_max, y_max, x_min, y_max - ); - - self.clip_with_path(&clip_path); - } -} diff --git a/resvg-0.43.0/crates/usvg/src/text/flatten.rs b/resvg-0.43.0/crates/usvg/src/text/flatten.rs deleted file mode 100644 index 502f674..0000000 --- a/resvg-0.43.0/crates/usvg/src/text/flatten.rs +++ /dev/null @@ -1,324 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use std::mem; -use std::sync::Arc; - -use fontdb::{Database, ID}; -use rustybuzz::ttf_parser; -use rustybuzz::ttf_parser::{GlyphId, RasterImageFormat, RgbaColor}; -use tiny_skia_path::{NonZeroRect, Size, Transform}; -use xmlwriter::XmlWriter; - -use crate::text::colr::GlyphPainter; -use crate::*; - -fn resolve_rendering_mode(text: &Text) -> ShapeRendering { - match text.rendering_mode { - TextRendering::OptimizeSpeed => ShapeRendering::CrispEdges, - TextRendering::OptimizeLegibility => ShapeRendering::GeometricPrecision, - TextRendering::GeometricPrecision => ShapeRendering::GeometricPrecision, - } -} - -fn push_outline_paths( - span: &layout::Span, - builder: &mut tiny_skia_path::PathBuilder, - new_children: &mut Vec, - rendering_mode: ShapeRendering, -) { - let builder = mem::replace(builder, tiny_skia_path::PathBuilder::new()); - - if let Some(path) = builder.finish().and_then(|p| { - Path::new( - String::new(), - span.visible, - span.fill.clone(), - span.stroke.clone(), - span.paint_order, - rendering_mode, - Arc::new(p), - Transform::default(), - ) - }) { - new_children.push(Node::Path(Box::new(path))); - } -} - -pub(crate) fn flatten(text: &mut Text, fontdb: &fontdb::Database) -> Option<(Group, NonZeroRect)> { - let mut new_children = vec![]; - - let rendering_mode = resolve_rendering_mode(text); - - for span in &text.layouted { - if let Some(path) = span.overline.as_ref() { - let mut path = path.clone(); - path.rendering_mode = rendering_mode; - new_children.push(Node::Path(Box::new(path))); - } - - if let Some(path) = span.underline.as_ref() { - let mut path = path.clone(); - path.rendering_mode = rendering_mode; - new_children.push(Node::Path(Box::new(path))); - } - - // Instead of always processing each glyph separately, we always collect - // as many outline glyphs as possible by pushing them into the span_builder - // and only if we encounter a different glyph, or we reach the very end of the - // span to we push the actual outline paths into new_children. This way, we don't need - // to create a new path for every glyph if we have many consecutive glyphs - // with just outlines (which is the most common case). - let mut span_builder = tiny_skia_path::PathBuilder::new(); - - for glyph in &span.positioned_glyphs { - // A (best-effort conversion of a) COLR glyph. - if let Some(tree) = fontdb.colr(glyph.font, glyph.id) { - let mut group = Group { - transform: glyph.colr_transform(), - ..Group::empty() - }; - // TODO: Probably need to update abs_transform of children? - group.children.push(Node::Group(Box::new(tree.root))); - group.calculate_bounding_boxes(); - - new_children.push(Node::Group(Box::new(group))); - } - // An SVG glyph. Will return the usvg node containing the glyph descriptions. - else if let Some(node) = fontdb.svg(glyph.font, glyph.id) { - push_outline_paths(span, &mut span_builder, &mut new_children, rendering_mode); - - let mut group = Group { - transform: glyph.svg_transform(), - ..Group::empty() - }; - // TODO: Probably need to update abs_transform of children? - group.children.push(node); - group.calculate_bounding_boxes(); - - new_children.push(Node::Group(Box::new(group))); - } - // A bitmap glyph. - else if let Some(img) = fontdb.raster(glyph.font, glyph.id) { - push_outline_paths(span, &mut span_builder, &mut new_children, rendering_mode); - - let transform = if img.is_sbix { - glyph.sbix_transform( - img.x as f32, - img.y as f32, - img.glyph_bbox.map(|bbox| bbox.x_min).unwrap_or(0) as f32, - img.glyph_bbox.map(|bbox| bbox.y_min).unwrap_or(0) as f32, - img.pixels_per_em as f32, - img.image.size.height(), - ) - } else { - glyph.cbdt_transform( - img.x as f32, - img.y as f32, - img.pixels_per_em as f32, - img.image.size.height(), - ) - }; - - let mut group = Group { - transform, - ..Group::empty() - }; - group.children.push(Node::Image(Box::new(img.image))); - group.calculate_bounding_boxes(); - - new_children.push(Node::Group(Box::new(group))); - } else if let Some(outline) = fontdb - .outline(glyph.font, glyph.id) - .and_then(|p| p.transform(glyph.outline_transform())) - { - span_builder.push_path(&outline); - } - } - - push_outline_paths(span, &mut span_builder, &mut new_children, rendering_mode); - - if let Some(path) = span.line_through.as_ref() { - let mut path = path.clone(); - path.rendering_mode = rendering_mode; - new_children.push(Node::Path(Box::new(path))); - } - } - - let mut group = Group { - id: text.id.clone(), - ..Group::empty() - }; - - for child in new_children { - group.children.push(child); - } - - group.calculate_bounding_boxes(); - let stroke_bbox = group.stroke_bounding_box().to_non_zero_rect()?; - Some((group, stroke_bbox)) -} - -struct PathBuilder { - builder: tiny_skia_path::PathBuilder, -} - -impl ttf_parser::OutlineBuilder for PathBuilder { - fn move_to(&mut self, x: f32, y: f32) { - self.builder.move_to(x, y); - } - - fn line_to(&mut self, x: f32, y: f32) { - self.builder.line_to(x, y); - } - - fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) { - self.builder.quad_to(x1, y1, x, y); - } - - fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) { - self.builder.cubic_to(x1, y1, x2, y2, x, y); - } - - fn close(&mut self) { - self.builder.close(); - } -} - -pub(crate) trait DatabaseExt { - fn outline(&self, id: ID, glyph_id: GlyphId) -> Option; - fn raster(&self, id: ID, glyph_id: GlyphId) -> Option; - fn svg(&self, id: ID, glyph_id: GlyphId) -> Option; - fn colr(&self, id: ID, glyph_id: GlyphId) -> Option; -} - -pub(crate) struct BitmapImage { - image: Image, - x: i16, - y: i16, - pixels_per_em: u16, - glyph_bbox: Option, - is_sbix: bool, -} - -impl DatabaseExt for Database { - #[inline(never)] - fn outline(&self, id: ID, glyph_id: GlyphId) -> Option { - self.with_face_data(id, |data, face_index| -> Option { - let font = ttf_parser::Face::parse(data, face_index).ok()?; - - let mut builder = PathBuilder { - builder: tiny_skia_path::PathBuilder::new(), - }; - - font.outline_glyph(glyph_id, &mut builder)?; - builder.builder.finish() - })? - } - - fn raster(&self, id: ID, glyph_id: GlyphId) -> Option { - self.with_face_data(id, |data, face_index| -> Option { - let font = ttf_parser::Face::parse(data, face_index).ok()?; - let image = font.glyph_raster_image(glyph_id, u16::MAX)?; - - if image.format == RasterImageFormat::PNG { - let bitmap_image = BitmapImage { - image: Image { - id: String::new(), - visible: true, - size: Size::from_wh(image.width as f32, image.height as f32)?, - rendering_mode: ImageRendering::OptimizeQuality, - kind: ImageKind::PNG(Arc::new(image.data.into())), - abs_transform: Transform::default(), - abs_bounding_box: NonZeroRect::from_xywh( - 0.0, - 0.0, - image.width as f32, - image.height as f32, - )?, - }, - x: image.x, - y: image.y, - pixels_per_em: image.pixels_per_em, - glyph_bbox: font.glyph_bounding_box(glyph_id), - // ttf-parser always checks sbix first, so if this table exists, it was used. - is_sbix: font.tables().sbix.is_some(), - }; - - return Some(bitmap_image); - } - - None - })? - } - - fn svg(&self, id: ID, glyph_id: GlyphId) -> Option { - // TODO: Technically not 100% accurate because the SVG format in a OTF font - // is actually a subset/superset of a normal SVG, but it seems to work fine - // for Twitter Color Emoji, so might as well use what we already have. - - // TODO: Glyph records can contain the data for multiple glyphs. We should - // add a cache so we don't need to reparse the data every time. - self.with_face_data(id, |data, face_index| -> Option { - let font = ttf_parser::Face::parse(data, face_index).ok()?; - let image = font.glyph_svg_image(glyph_id)?; - let tree = Tree::from_data(image.data, &Options::default()).ok()?; - - // Twitter Color Emoji seems to always have one SVG record per glyph, - // while Noto Color Emoji sometimes contains multiple ones. It's kind of hacky, - // but the best we have for now. - let node = if image.start_glyph_id == image.end_glyph_id { - Node::Group(Box::new(tree.root)) - } else { - tree.node_by_id(&format!("glyph{}", glyph_id.0)) - .log_none(|| { - log::warn!("Failed to find SVG glyph node for glyph {}", glyph_id.0) - }) - .cloned()? - }; - - Some(node) - })? - } - - fn colr(&self, id: ID, glyph_id: GlyphId) -> Option { - self.with_face_data(id, |data, face_index| -> Option { - let face = ttf_parser::Face::parse(data, face_index).ok()?; - - let mut svg = XmlWriter::new(xmlwriter::Options::default()); - - svg.start_element("svg"); - svg.write_attribute("xmlns", "http://www.w3.org/2000/svg"); - svg.write_attribute("xmlns:xlink", "http://www.w3.org/1999/xlink"); - - let mut path_buf = String::with_capacity(256); - let gradient_index = 1; - let clip_path_index = 1; - - svg.start_element("g"); - - let mut glyph_painter = GlyphPainter { - face: &face, - svg: &mut svg, - path_buf: &mut path_buf, - gradient_index, - clip_path_index, - palette_index: 0, - transform: ttf_parser::Transform::default(), - outline_transform: ttf_parser::Transform::default(), - transforms_stack: vec![ttf_parser::Transform::default()], - }; - - face.paint_color_glyph( - glyph_id, - 0, - RgbaColor::new(0, 0, 0, 255), - &mut glyph_painter, - )?; - svg.end_element(); - - Tree::from_data(&svg.end_document().as_bytes(), &Options::default()).ok() - })? - } -} diff --git a/resvg-0.43.0/crates/usvg/src/text/layout.rs b/resvg-0.43.0/crates/usvg/src/text/layout.rs deleted file mode 100644 index 27db780..0000000 --- a/resvg-0.43.0/crates/usvg/src/text/layout.rs +++ /dev/null @@ -1,1734 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use std::collections::HashMap; -use std::num::NonZeroU16; -use std::sync::Arc; - -use fontdb::{Database, ID}; -use kurbo::{ParamCurve, ParamCurveArclen, ParamCurveDeriv}; -use rustybuzz::ttf_parser; -use rustybuzz::ttf_parser::{GlyphId, Tag}; -use strict_num::NonZeroPositiveF32; -use tiny_skia_path::{NonZeroRect, Transform}; -use unicode_script::UnicodeScript; - -use crate::tree::{BBox, IsValidLength}; -use crate::{ - AlignmentBaseline, ApproxZeroUlps, BaselineShift, DominantBaseline, Fill, FillRule, Font, - FontResolver, LengthAdjust, PaintOrder, Path, ShapeRendering, Stroke, Text, TextAnchor, - TextChunk, TextDecorationStyle, TextFlow, TextPath, TextSpan, WritingMode, -}; - -/// A glyph that has already been positioned correctly. -/// -/// Note that the transform already takes the font size into consideration, so applying the -/// transform to the outline of the glyphs is all that is necessary to display it correctly. -#[derive(Clone, Debug)] -pub struct PositionedGlyph { - /// Returns the transform of the glyph itself within the cluster. For example, - /// for zalgo text, it contains the transform to position the glyphs above/below - /// the main glyph. - glyph_ts: Transform, - /// Returns the transform of the whole cluster that the glyph is part of. - cluster_ts: Transform, - /// Returns the transform of the span that the glyph is a part of. - span_ts: Transform, - /// The units per em of the font the glyph belongs to. - units_per_em: u16, - /// The font size the glyph should be scaled to. - font_size: f32, - /// The ID of the glyph. - pub id: GlyphId, - /// The text from the original string that corresponds to that glyph. - pub text: String, - /// The ID of the font the glyph should be taken from. Can be used with the - /// [font database of the tree](crate::Tree::fontdb) this glyph is part of. - pub font: ID, -} - -impl PositionedGlyph { - /// Returns the transform of glyph. - pub fn transform(&self) -> Transform { - let sx = self.font_size / self.units_per_em as f32; - - self.span_ts - .pre_concat(self.cluster_ts) - .pre_concat(Transform::from_scale(sx, sx)) - .pre_concat(self.glyph_ts) - } - - /// Returns the transform of glyph, assuming that an outline - /// glyph is being used (i.e. from the `glyf` or `CFF/CFF2` table). - pub fn outline_transform(&self) -> Transform { - // Outlines are mirrored by default. - self.transform() - .pre_concat(Transform::from_scale(1.0, -1.0)) - } - - /// Returns the transform for the glyph, assuming that a CBTD-based raster glyph - /// is being used. - pub fn cbdt_transform(&self, x: f32, y: f32, pixels_per_em: f32, height: f32) -> Transform { - self.transform() - .pre_concat(Transform::from_scale( - self.units_per_em as f32 / pixels_per_em, - self.units_per_em as f32 / pixels_per_em, - )) - // Right now, the top-left corner of the image would be placed in - // on the "text cursor", but we want the bottom-left corner to be there, - // so we need to shift it up and also apply the x/y offset. - .pre_translate(x, -height - y) - } - - /// Returns the transform for the glyph, assuming that a sbix-based raster glyph - /// is being used. - pub fn sbix_transform( - &self, - x: f32, - y: f32, - x_min: f32, - y_min: f32, - pixels_per_em: f32, - height: f32, - ) -> Transform { - // In contrast to CBDT, we also need to look at the outline bbox of the glyph and add a shift if necessary. - let bbox_x_shift = -x_min; - - let bbox_y_shift = if y_min.approx_zero_ulps(4) { - // For unknown reasons, using Apple Color Emoji will lead to a vertical shift on MacOS, but this shift - // doesn't seem to be coming from the font and most likely is somehow hardcoded. On Windows, - // this shift will not be applied. However, if this shift is not applied the emojis are a bit - // too high up when being together with other text, so we try to imitate this. - // See also https://github.com/harfbuzz/harfbuzz/issues/2679#issuecomment-1345595425 - // So whenever the y-shift is 0, we approximate this vertical shift that seems to be produced by it. - // This value seems to be pretty close to what is happening on MacOS. - // We can still remove this if it turns out to be a problem, but Apple Color Emoji is pretty - // much the only `sbix` font out there and they all seem to have a y-shift of 0, so it - // makes sense to keep it. - 0.128 * self.units_per_em as f32 - } else { - -y_min - }; - - self.transform() - .pre_concat(Transform::from_translate(bbox_x_shift, bbox_y_shift)) - .pre_concat(Transform::from_scale( - self.units_per_em as f32 / pixels_per_em, - self.units_per_em as f32 / pixels_per_em, - )) - // Right now, the top-left corner of the image would be placed in - // on the "text cursor", but we want the bottom-left corner to be there, - // so we need to shift it up and also apply the x/y offset. - .pre_translate(x, -height - y) - } - - /// Returns the transform for the glyph, assuming that an SVG glyph is - /// being used. - pub fn svg_transform(&self) -> Transform { - self.transform() - } - - /// Returns the transform for the glyph, assuming that a COLR glyph is - /// being used. - pub fn colr_transform(&self) -> Transform { - self.outline_transform() - } -} - -/// A span contains a number of layouted glyphs that share the same fill, stroke, paint order and -/// visibility. -#[derive(Clone, Debug)] -pub struct Span { - /// The fill of the span. - pub fill: Option, - /// The stroke of the span. - pub stroke: Option, - /// The paint order of the span. - pub paint_order: PaintOrder, - /// The font size of the span. - pub font_size: NonZeroPositiveF32, - /// The visibility of the span. - pub visible: bool, - /// The glyphs that make up the span. - pub positioned_glyphs: Vec, - /// An underline text decoration of the span. - /// Needs to be rendered before all glyphs. - pub underline: Option, - /// An overline text decoration of the span. - /// Needs to be rendered before all glyphs. - pub overline: Option, - /// A line-through text decoration of the span. - /// Needs to be rendered after all glyphs. - pub line_through: Option, -} - -#[derive(Clone, Debug)] -struct GlyphCluster { - byte_idx: ByteIndex, - codepoint: char, - width: f32, - advance: f32, - ascent: f32, - descent: f32, - has_relative_shift: bool, - glyphs: Vec, - transform: Transform, - path_transform: Transform, - visible: bool, -} - -impl GlyphCluster { - pub(crate) fn height(&self) -> f32 { - self.ascent - self.descent - } - - pub(crate) fn transform(&self) -> Transform { - self.path_transform.post_concat(self.transform) - } -} - -pub(crate) fn layout_text( - text_node: &Text, - resolver: &FontResolver, - fontdb: &mut Arc, -) -> Option<(Vec, NonZeroRect)> { - let mut fonts_cache: FontsCache = HashMap::new(); - - for chunk in &text_node.chunks { - for span in &chunk.spans { - if !fonts_cache.contains_key(&span.font) { - if let Some(font) = - (resolver.select_font)(&span.font, fontdb).and_then(|id| fontdb.load_font(id)) - { - fonts_cache.insert(span.font.clone(), Arc::new(font)); - } - } - } - } - - let mut spans = vec![]; - let mut char_offset = 0; - let mut last_x = 0.0; - let mut last_y = 0.0; - let mut bbox = BBox::default(); - for chunk in &text_node.chunks { - let (x, y) = match chunk.text_flow { - TextFlow::Linear => (chunk.x.unwrap_or(last_x), chunk.y.unwrap_or(last_y)), - TextFlow::Path(_) => (0.0, 0.0), - }; - - let mut clusters = process_chunk(chunk, &fonts_cache, resolver, fontdb); - if clusters.is_empty() { - char_offset += chunk.text.chars().count(); - continue; - } - - apply_writing_mode(text_node.writing_mode, &mut clusters); - apply_letter_spacing(chunk, &mut clusters); - apply_word_spacing(chunk, &mut clusters); - - apply_length_adjust(chunk, &mut clusters); - let mut curr_pos = resolve_clusters_positions( - text_node, - chunk, - char_offset, - text_node.writing_mode, - &fonts_cache, - &mut clusters, - ); - - let mut text_ts = Transform::default(); - if text_node.writing_mode == WritingMode::TopToBottom { - if let TextFlow::Linear = chunk.text_flow { - text_ts = text_ts.pre_rotate_at(90.0, x, y); - } - } - - for span in &chunk.spans { - let font = match fonts_cache.get(&span.font) { - Some(v) => v, - None => continue, - }; - - let decoration_spans = collect_decoration_spans(span, &clusters); - - let mut span_ts = text_ts; - span_ts = span_ts.pre_translate(x, y); - if let TextFlow::Linear = chunk.text_flow { - let shift = resolve_baseline(span, font, text_node.writing_mode); - - // In case of a horizontal flow, shift transform and not clusters, - // because clusters can be rotated and an additional shift will lead - // to invalid results. - span_ts = span_ts.pre_translate(0.0, shift); - } - - let mut underline = None; - let mut overline = None; - let mut line_through = None; - - if let Some(decoration) = span.decoration.underline.clone() { - // TODO: No idea what offset should be used for top-to-bottom layout. - // There is - // https://www.w3.org/TR/css-text-decor-3/#text-underline-position-property - // but it doesn't go into details. - let offset = match text_node.writing_mode { - WritingMode::LeftToRight => -font.underline_position(span.font_size.get()), - WritingMode::TopToBottom => font.height(span.font_size.get()) / 2.0, - }; - - if let Some(path) = - convert_decoration(offset, span, font, decoration, &decoration_spans, span_ts) - { - bbox = bbox.expand(path.data.bounds()); - underline = Some(path); - } - } - - if let Some(decoration) = span.decoration.overline.clone() { - let offset = match text_node.writing_mode { - WritingMode::LeftToRight => -font.ascent(span.font_size.get()), - WritingMode::TopToBottom => -font.height(span.font_size.get()) / 2.0, - }; - - if let Some(path) = - convert_decoration(offset, span, font, decoration, &decoration_spans, span_ts) - { - bbox = bbox.expand(path.data.bounds()); - overline = Some(path); - } - } - - if let Some(decoration) = span.decoration.line_through.clone() { - let offset = match text_node.writing_mode { - WritingMode::LeftToRight => -font.line_through_position(span.font_size.get()), - WritingMode::TopToBottom => 0.0, - }; - - if let Some(path) = - convert_decoration(offset, span, font, decoration, &decoration_spans, span_ts) - { - bbox = bbox.expand(path.data.bounds()); - line_through = Some(path); - } - } - - let mut fill = span.fill.clone(); - if let Some(ref mut fill) = fill { - // The `fill-rule` should be ignored. - // https://www.w3.org/TR/SVG2/text.html#TextRenderingOrder - // - // 'Since the fill-rule property does not apply to SVG text elements, - // the specific order of the subpaths within the equivalent path does not matter.' - fill.rule = FillRule::NonZero; - } - - if let Some((span_fragments, span_bbox)) = convert_span(span, &clusters, span_ts) { - bbox = bbox.expand(span_bbox); - - let positioned_glyphs = span_fragments - .into_iter() - .flat_map(|mut gc| { - let cluster_ts = gc.transform(); - gc.glyphs.iter_mut().for_each(|pg| { - pg.cluster_ts = cluster_ts; - pg.span_ts = span_ts; - }); - gc.glyphs - }) - .collect(); - - spans.push(Span { - fill, - stroke: span.stroke.clone(), - paint_order: span.paint_order, - font_size: span.font_size, - visible: span.visible, - positioned_glyphs, - underline, - overline, - line_through, - }); - } - } - - char_offset += chunk.text.chars().count(); - - if text_node.writing_mode == WritingMode::TopToBottom { - if let TextFlow::Linear = chunk.text_flow { - std::mem::swap(&mut curr_pos.0, &mut curr_pos.1); - } - } - - last_x = x + curr_pos.0; - last_y = y + curr_pos.1; - } - - let bbox = bbox.to_non_zero_rect()?; - - Some((spans, bbox)) -} - -fn convert_span( - span: &TextSpan, - clusters: &[GlyphCluster], - text_ts: Transform, -) -> Option<(Vec, NonZeroRect)> { - let mut span_clusters = vec![]; - let mut bboxes_builder = tiny_skia_path::PathBuilder::new(); - - for cluster in clusters { - if !cluster.visible { - continue; - } - - if span_contains(span, cluster.byte_idx) { - span_clusters.push(cluster.clone()); - } - - let mut advance = cluster.advance; - if advance <= 0.0 { - advance = 1.0; - } - - // We have to calculate text bbox using font metrics and not glyph shape. - if let Some(r) = NonZeroRect::from_xywh(0.0, -cluster.ascent, advance, cluster.height()) { - if let Some(r) = r.transform(cluster.transform()) { - bboxes_builder.push_rect(r.to_rect()); - } - } - } - - let mut bboxes = bboxes_builder.finish()?; - bboxes = bboxes.transform(text_ts)?; - let bbox = bboxes.compute_tight_bounds()?.to_non_zero_rect()?; - - Some((span_clusters, bbox)) -} - -fn collect_decoration_spans(span: &TextSpan, clusters: &[GlyphCluster]) -> Vec { - let mut spans = Vec::new(); - - let mut started = false; - let mut width = 0.0; - let mut transform = Transform::default(); - - for cluster in clusters { - if span_contains(span, cluster.byte_idx) { - if started && cluster.has_relative_shift { - started = false; - spans.push(DecorationSpan { width, transform }); - } - - if !started { - width = cluster.advance; - started = true; - transform = cluster.transform; - } else { - width += cluster.advance; - } - } else if started { - spans.push(DecorationSpan { width, transform }); - started = false; - } - } - - if started { - spans.push(DecorationSpan { width, transform }); - } - - spans -} - -pub(crate) fn convert_decoration( - dy: f32, - span: &TextSpan, - font: &ResolvedFont, - mut decoration: TextDecorationStyle, - decoration_spans: &[DecorationSpan], - transform: Transform, -) -> Option { - debug_assert!(!decoration_spans.is_empty()); - - let thickness = font.underline_thickness(span.font_size.get()); - - let mut builder = tiny_skia_path::PathBuilder::new(); - for dec_span in decoration_spans { - let rect = match NonZeroRect::from_xywh(0.0, -thickness / 2.0, dec_span.width, thickness) { - Some(v) => v, - None => { - log::warn!("a decoration span has a malformed bbox"); - continue; - } - }; - - let ts = dec_span.transform.pre_translate(0.0, dy); - - let mut path = tiny_skia_path::PathBuilder::from_rect(rect.to_rect()); - path = match path.transform(ts) { - Some(v) => v, - None => continue, - }; - - builder.push_path(&path); - } - - let mut path_data = builder.finish()?; - path_data = path_data.transform(transform)?; - - Path::new( - String::new(), - span.visible, - decoration.fill.take(), - decoration.stroke.take(), - PaintOrder::default(), - ShapeRendering::default(), - Arc::new(path_data), - Transform::default(), - ) -} - -/// A text decoration span. -/// -/// Basically a horizontal line, that will be used for underline, overline and line-through. -/// It doesn't have a height, since it depends on the Font metrics. -#[derive(Clone, Copy)] -pub(crate) struct DecorationSpan { - pub(crate) width: f32, - pub(crate) transform: Transform, -} - -/// Resolves clusters positions. -/// -/// Mainly sets the `transform` property. -/// -/// Returns the last text position. The next text chunk should start from that position. -fn resolve_clusters_positions( - text: &Text, - chunk: &TextChunk, - char_offset: usize, - writing_mode: WritingMode, - fonts_cache: &FontsCache, - clusters: &mut [GlyphCluster], -) -> (f32, f32) { - match chunk.text_flow { - TextFlow::Linear => { - resolve_clusters_positions_horizontal(text, chunk, char_offset, writing_mode, clusters) - } - TextFlow::Path(ref path) => resolve_clusters_positions_path( - text, - chunk, - char_offset, - path, - writing_mode, - fonts_cache, - clusters, - ), - } -} - -fn clusters_length(clusters: &[GlyphCluster]) -> f32 { - clusters.iter().fold(0.0, |w, cluster| w + cluster.advance) -} - -fn resolve_clusters_positions_horizontal( - text: &Text, - chunk: &TextChunk, - offset: usize, - writing_mode: WritingMode, - clusters: &mut [GlyphCluster], -) -> (f32, f32) { - let mut x = process_anchor(chunk.anchor, clusters_length(clusters)); - let mut y = 0.0; - - for cluster in clusters { - let cp = offset + cluster.byte_idx.code_point_at(&chunk.text); - if let (Some(dx), Some(dy)) = (text.dx.get(cp), text.dy.get(cp)) { - if writing_mode == WritingMode::LeftToRight { - x += dx; - y += dy; - } else { - y -= dx; - x += dy; - } - cluster.has_relative_shift = !dx.approx_zero_ulps(4) || !dy.approx_zero_ulps(4); - } - - cluster.transform = cluster.transform.pre_translate(x, y); - - if let Some(angle) = text.rotate.get(cp).cloned() { - if !angle.approx_zero_ulps(4) { - cluster.transform = cluster.transform.pre_rotate(angle); - cluster.has_relative_shift = true; - } - } - - x += cluster.advance; - } - - (x, y) -} - -// Baseline resolving in SVG is a mess. -// Not only it's poorly documented, but as soon as you start mixing -// `dominant-baseline` and `alignment-baseline` each application/browser will produce -// different results. -// -// For now, resvg simply tries to match Chrome's output and not the mythical SVG spec output. -// -// See `alignment_baseline_shift` method comment for more details. -pub(crate) fn resolve_baseline( - span: &TextSpan, - font: &ResolvedFont, - writing_mode: WritingMode, -) -> f32 { - let mut shift = -resolve_baseline_shift(&span.baseline_shift, font, span.font_size.get()); - - // TODO: support vertical layout as well - if writing_mode == WritingMode::LeftToRight { - if span.alignment_baseline == AlignmentBaseline::Auto - || span.alignment_baseline == AlignmentBaseline::Baseline - { - shift += font.dominant_baseline_shift(span.dominant_baseline, span.font_size.get()); - } else { - shift += font.alignment_baseline_shift(span.alignment_baseline, span.font_size.get()); - } - } - - shift -} - -fn resolve_baseline_shift(baselines: &[BaselineShift], font: &ResolvedFont, font_size: f32) -> f32 { - let mut shift = 0.0; - for baseline in baselines.iter().rev() { - match baseline { - BaselineShift::Baseline => {} - BaselineShift::Subscript => shift -= font.subscript_offset(font_size), - BaselineShift::Superscript => shift += font.superscript_offset(font_size), - BaselineShift::Number(n) => shift += n, - } - } - - shift -} - -fn resolve_clusters_positions_path( - text: &Text, - chunk: &TextChunk, - char_offset: usize, - path: &TextPath, - writing_mode: WritingMode, - fonts_cache: &FontsCache, - clusters: &mut [GlyphCluster], -) -> (f32, f32) { - let mut last_x = 0.0; - let mut last_y = 0.0; - - let mut dy = 0.0; - - // In the text path mode, chunk's x/y coordinates provide an additional offset along the path. - // The X coordinate is used in a horizontal mode, and Y in vertical. - let chunk_offset = match writing_mode { - WritingMode::LeftToRight => chunk.x.unwrap_or(0.0), - WritingMode::TopToBottom => chunk.y.unwrap_or(0.0), - }; - - let start_offset = - chunk_offset + path.start_offset + process_anchor(chunk.anchor, clusters_length(clusters)); - - let normals = collect_normals(text, chunk, clusters, &path.path, char_offset, start_offset); - for (cluster, normal) in clusters.iter_mut().zip(normals) { - let (x, y, angle) = match normal { - Some(normal) => (normal.x, normal.y, normal.angle), - None => { - // Hide clusters that are outside the text path. - cluster.visible = false; - continue; - } - }; - - // We have to break a decoration line for each cluster during text-on-path. - cluster.has_relative_shift = true; - - let orig_ts = cluster.transform; - - // Clusters should be rotated by the x-midpoint x baseline position. - let half_width = cluster.width / 2.0; - cluster.transform = Transform::default(); - cluster.transform = cluster.transform.pre_translate(x - half_width, y); - cluster.transform = cluster.transform.pre_rotate_at(angle, half_width, 0.0); - - let cp = char_offset + cluster.byte_idx.code_point_at(&chunk.text); - dy += text.dy.get(cp).cloned().unwrap_or(0.0); - - let baseline_shift = chunk_span_at(chunk, cluster.byte_idx) - .map(|span| { - let font = match fonts_cache.get(&span.font) { - Some(v) => v, - None => return 0.0, - }; - -resolve_baseline(span, font, writing_mode) - }) - .unwrap_or(0.0); - - // Shift only by `dy` since we already applied `dx` - // during offset along the path calculation. - if !dy.approx_zero_ulps(4) || !baseline_shift.approx_zero_ulps(4) { - let shift = kurbo::Vec2::new(0.0, (dy - baseline_shift) as f64); - cluster.transform = cluster - .transform - .pre_translate(shift.x as f32, shift.y as f32); - } - - if let Some(angle) = text.rotate.get(cp).cloned() { - if !angle.approx_zero_ulps(4) { - cluster.transform = cluster.transform.pre_rotate(angle); - } - } - - // The possible `lengthAdjust` transform should be applied after text-on-path positioning. - cluster.transform = cluster.transform.pre_concat(orig_ts); - - last_x = x + cluster.advance; - last_y = y; - } - - (last_x, last_y) -} - -pub(crate) fn process_anchor(a: TextAnchor, text_width: f32) -> f32 { - match a { - TextAnchor::Start => 0.0, // Nothing. - TextAnchor::Middle => -text_width / 2.0, - TextAnchor::End => -text_width, - } -} - -pub(crate) struct PathNormal { - pub(crate) x: f32, - pub(crate) y: f32, - pub(crate) angle: f32, -} - -fn collect_normals( - text: &Text, - chunk: &TextChunk, - clusters: &[GlyphCluster], - path: &tiny_skia_path::Path, - char_offset: usize, - offset: f32, -) -> Vec> { - let mut offsets = Vec::with_capacity(clusters.len()); - let mut normals = Vec::with_capacity(clusters.len()); - { - let mut advance = offset; - for cluster in clusters { - // Clusters should be rotated by the x-midpoint x baseline position. - let half_width = cluster.width / 2.0; - - // Include relative position. - let cp = char_offset + cluster.byte_idx.code_point_at(&chunk.text); - advance += text.dx.get(cp).cloned().unwrap_or(0.0); - - let offset = advance + half_width; - - // Clusters outside the path have no normals. - if offset < 0.0 { - normals.push(None); - } - - offsets.push(offset as f64); - advance += cluster.advance; - } - } - - let mut prev_mx = path.points()[0].x; - let mut prev_my = path.points()[0].y; - let mut prev_x = prev_mx; - let mut prev_y = prev_my; - - fn create_curve_from_line(px: f32, py: f32, x: f32, y: f32) -> kurbo::CubicBez { - let line = kurbo::Line::new( - kurbo::Point::new(px as f64, py as f64), - kurbo::Point::new(x as f64, y as f64), - ); - let p1 = line.eval(0.33); - let p2 = line.eval(0.66); - kurbo::CubicBez { - p0: line.p0, - p1, - p2, - p3: line.p1, - } - } - - let mut length: f64 = 0.0; - for seg in path.segments() { - let curve = match seg { - tiny_skia_path::PathSegment::MoveTo(p) => { - prev_mx = p.x; - prev_my = p.y; - prev_x = p.x; - prev_y = p.y; - continue; - } - tiny_skia_path::PathSegment::LineTo(p) => { - create_curve_from_line(prev_x, prev_y, p.x, p.y) - } - tiny_skia_path::PathSegment::QuadTo(p1, p) => kurbo::QuadBez { - p0: kurbo::Point::new(prev_x as f64, prev_y as f64), - p1: kurbo::Point::new(p1.x as f64, p1.y as f64), - p2: kurbo::Point::new(p.x as f64, p.y as f64), - } - .raise(), - tiny_skia_path::PathSegment::CubicTo(p1, p2, p) => kurbo::CubicBez { - p0: kurbo::Point::new(prev_x as f64, prev_y as f64), - p1: kurbo::Point::new(p1.x as f64, p1.y as f64), - p2: kurbo::Point::new(p2.x as f64, p2.y as f64), - p3: kurbo::Point::new(p.x as f64, p.y as f64), - }, - tiny_skia_path::PathSegment::Close => { - create_curve_from_line(prev_x, prev_y, prev_mx, prev_my) - } - }; - - let arclen_accuracy = { - let base_arclen_accuracy = 0.5; - // Accuracy depends on a current scale. - // When we have a tiny path scaled by a large value, - // we have to increase out accuracy accordingly. - let (sx, sy) = text.abs_transform.get_scale(); - // 1.0 acts as a threshold to prevent division by 0 and/or low accuracy. - base_arclen_accuracy / (sx * sy).sqrt().max(1.0) - }; - - let curve_len = curve.arclen(arclen_accuracy as f64); - - for offset in &offsets[normals.len()..] { - if *offset >= length && *offset <= length + curve_len { - let mut offset = curve.inv_arclen(offset - length, arclen_accuracy as f64); - // some rounding error may occur, so we give offset a little tolerance - debug_assert!((-1.0e-3..=1.0 + 1.0e-3).contains(&offset)); - offset = offset.min(1.0).max(0.0); - - let pos = curve.eval(offset); - let d = curve.deriv().eval(offset); - let d = kurbo::Vec2::new(-d.y, d.x); // tangent - let angle = d.atan2().to_degrees() - 90.0; - - normals.push(Some(PathNormal { - x: pos.x as f32, - y: pos.y as f32, - angle: angle as f32, - })); - - if normals.len() == offsets.len() { - break; - } - } - } - - length += curve_len; - prev_x = curve.p3.x as f32; - prev_y = curve.p3.y as f32; - } - - // If path ended and we still have unresolved normals - set them to `None`. - for _ in 0..(offsets.len() - normals.len()) { - normals.push(None); - } - - normals -} - -/// Converts a text chunk into a list of outlined clusters. -/// -/// This function will do the BIDI reordering, text shaping and glyphs outlining, -/// but not the text layouting. So all clusters are in the 0x0 position. -fn process_chunk( - chunk: &TextChunk, - fonts_cache: &FontsCache, - resolver: &FontResolver, - fontdb: &mut Arc, -) -> Vec { - // The way this function works is a bit tricky. - // - // The first problem is BIDI reordering. - // We cannot shape text span-by-span, because glyph clusters are not guarantee to be continuous. - // - // For example: - // Hello שלום. - // - // Would be shaped as: - // H e l l o ש ל ו ם . (characters) - // 0 1 2 3 4 5 12 10 8 6 14 (cluster indices in UTF-8) - // --- --- (green span) - // - // As you can see, our continuous `lo של` span was split into two separated one. - // So our 3 spans: black - green - black, become 5 spans: black - green - black - green - black. - // If we shape `Hel`, then `lo של` an then `ום` separately - we would get an incorrect output. - // To properly handle this we simply shape the whole chunk. - // - // But this introduces another issue - what to do when we have multiple fonts? - // The easy solution would be to simply shape text with each font, - // where the first font output is used as a base one and all others overwrite it. - // This way in case of: - // Hello world - // we would replace Arial glyphs for `world` with Helvetica one. Pretty simple. - // - // Well, it would work most of the time, but not always. - // This is because different fonts can produce different amount of glyphs for the same text. - // The most common example are ligatures. Some fonts can shape `fi` as two glyphs `f` and `i`, - // but some can use `fi` (U+FB01) instead. - // Meaning that during merging we have to overwrite not individual glyphs, but clusters. - - let mut glyphs = Vec::new(); - for span in &chunk.spans { - let font = match fonts_cache.get(&span.font) { - Some(v) => v.clone(), - None => continue, - }; - - let tmp_glyphs = shape_text( - &chunk.text, - font, - span.small_caps, - span.apply_kerning, - resolver, - fontdb, - ); - - // Do nothing with the first run. - if glyphs.is_empty() { - glyphs = tmp_glyphs; - continue; - } - - // Overwrite span's glyphs. - let mut iter = tmp_glyphs.into_iter(); - while let Some(new_glyph) = iter.next() { - if !span_contains(span, new_glyph.byte_idx) { - continue; - } - - let Some(idx) = glyphs.iter().position(|g| g.byte_idx == new_glyph.byte_idx) else { - continue; - }; - - let prev_cluster_len = glyphs[idx].cluster_len; - if prev_cluster_len < new_glyph.cluster_len { - // If the new font represents the same cluster with fewer glyphs - // then remove remaining glyphs. - for _ in 1..new_glyph.cluster_len { - glyphs.remove(idx + 1); - } - } else if prev_cluster_len > new_glyph.cluster_len { - // If the new font represents the same cluster with more glyphs - // then insert them after the current one. - for j in 1..prev_cluster_len { - if let Some(g) = iter.next() { - glyphs.insert(idx + j, g); - } - } - } - - glyphs[idx] = new_glyph; - } - } - - // Convert glyphs to clusters. - let mut clusters = Vec::new(); - for (range, byte_idx) in GlyphClusters::new(&glyphs) { - if let Some(span) = chunk_span_at(chunk, byte_idx) { - clusters.push(form_glyph_clusters( - &glyphs[range], - &chunk.text, - span.font_size.get(), - )); - } - } - - clusters -} - -fn apply_length_adjust(chunk: &TextChunk, clusters: &mut [GlyphCluster]) { - let is_horizontal = matches!(chunk.text_flow, TextFlow::Linear); - - for span in &chunk.spans { - let target_width = match span.text_length { - Some(v) => v, - None => continue, - }; - - let mut width = 0.0; - let mut cluster_indexes = Vec::new(); - for i in span.start..span.end { - if let Some(index) = clusters.iter().position(|c| c.byte_idx.value() == i) { - cluster_indexes.push(index); - } - } - // Complex scripts can have mutli-codepoint clusters therefore we have to remove duplicates. - cluster_indexes.sort(); - cluster_indexes.dedup(); - - for i in &cluster_indexes { - // Use the original cluster `width` and not `advance`. - // This method essentially discards any `word-spacing` and `letter-spacing`. - width += clusters[*i].width; - } - - if cluster_indexes.is_empty() { - continue; - } - - if span.length_adjust == LengthAdjust::Spacing { - let factor = if cluster_indexes.len() > 1 { - (target_width - width) / (cluster_indexes.len() - 1) as f32 - } else { - 0.0 - }; - - for i in cluster_indexes { - clusters[i].advance = clusters[i].width + factor; - } - } else { - let factor = target_width / width; - // Prevent multiplying by zero. - if factor < 0.001 { - continue; - } - - for i in cluster_indexes { - clusters[i].transform = clusters[i].transform.pre_scale(factor, 1.0); - - // Technically just a hack to support the current text-on-path algorithm. - if !is_horizontal { - clusters[i].advance *= factor; - clusters[i].width *= factor; - } - } - } - } -} - -/// Rotates clusters according to -/// [Unicode Vertical_Orientation Property](https://www.unicode.org/reports/tr50/tr50-19.html). -fn apply_writing_mode(writing_mode: WritingMode, clusters: &mut [GlyphCluster]) { - if writing_mode != WritingMode::TopToBottom { - return; - } - - for cluster in clusters { - let orientation = unicode_vo::char_orientation(cluster.codepoint); - if orientation == unicode_vo::Orientation::Upright { - let mut ts = Transform::default(); - // Position glyph in the center of vertical axis. - ts = ts.pre_translate(0.0, (cluster.ascent + cluster.descent) / 2.0); - // Rotate by 90 degrees in the center. - ts = ts.pre_rotate_at( - -90.0, - cluster.width / 2.0, - -(cluster.ascent + cluster.descent) / 2.0, - ); - - cluster.path_transform = ts; - - // Move "baseline" to the middle and make height equal to width. - cluster.ascent = cluster.width / 2.0; - cluster.descent = -cluster.width / 2.0; - } else { - // Could not find a spec that explains this, - // but this is how other applications are shifting the "rotated" characters - // in the top-to-bottom mode. - cluster.transform = cluster - .transform - .pre_translate(0.0, (cluster.ascent + cluster.descent) / 2.0); - } - } -} - -/// Applies the `letter-spacing` property to a text chunk clusters. -/// -/// [In the CSS spec](https://www.w3.org/TR/css-text-3/#letter-spacing-property). -fn apply_letter_spacing(chunk: &TextChunk, clusters: &mut [GlyphCluster]) { - // At least one span should have a non-zero spacing. - if !chunk - .spans - .iter() - .any(|span| !span.letter_spacing.approx_zero_ulps(4)) - { - return; - } - - let num_clusters = clusters.len(); - for (i, cluster) in clusters.iter_mut().enumerate() { - // Spacing must be applied only to characters that belongs to the script - // that supports spacing. - // We are checking only the first code point, since it should be enough. - // https://www.w3.org/TR/css-text-3/#cursive-tracking - let script = cluster.codepoint.script(); - if script_supports_letter_spacing(script) { - if let Some(span) = chunk_span_at(chunk, cluster.byte_idx) { - // A space after the last cluster should be ignored, - // since it affects the bbox and text alignment. - if i != num_clusters - 1 { - cluster.advance += span.letter_spacing; - } - - // If the cluster advance became negative - clear it. - // This is an UB so we can do whatever we want, and we mimic Chrome's behavior. - if !cluster.advance.is_valid_length() { - cluster.width = 0.0; - cluster.advance = 0.0; - cluster.glyphs = vec![]; - } - } - } - } -} - -/// Applies the `word-spacing` property to a text chunk clusters. -/// -/// [In the CSS spec](https://www.w3.org/TR/css-text-3/#propdef-word-spacing). -fn apply_word_spacing(chunk: &TextChunk, clusters: &mut [GlyphCluster]) { - // At least one span should have a non-zero spacing. - if !chunk - .spans - .iter() - .any(|span| !span.word_spacing.approx_zero_ulps(4)) - { - return; - } - - for cluster in clusters { - if is_word_separator_characters(cluster.codepoint) { - if let Some(span) = chunk_span_at(chunk, cluster.byte_idx) { - // Technically, word spacing 'should be applied half on each - // side of the character', but it doesn't affect us in any way, - // so we are ignoring this. - cluster.advance += span.word_spacing; - - // After word spacing, `advance` can be negative. - } - } - } -} - -fn form_glyph_clusters(glyphs: &[Glyph], text: &str, font_size: f32) -> GlyphCluster { - debug_assert!(!glyphs.is_empty()); - - let mut width = 0.0; - let mut x: f32 = 0.0; - - let mut positioned_glyphs = vec![]; - - for glyph in glyphs { - let sx = glyph.font.scale(font_size); - - // Apply offset. - // - // The first glyph in the cluster will have an offset from 0x0, - // but the later one will have an offset from the "current position". - // So we have to keep an advance. - // TODO: should be done only inside a single text span - let ts = Transform::from_translate(x + glyph.dx as f32, -glyph.dy as f32); - - positioned_glyphs.push(PositionedGlyph { - glyph_ts: ts, - // Will be set later. - cluster_ts: Transform::default(), - // Will be set later. - span_ts: Transform::default(), - units_per_em: glyph.font.units_per_em.get(), - font_size, - font: glyph.font.id, - text: glyph.text.clone(), - id: glyph.id, - }); - - x += glyph.width as f32; - - let glyph_width = glyph.width as f32 * sx; - if glyph_width > width { - width = glyph_width; - } - } - - let byte_idx = glyphs[0].byte_idx; - let font = glyphs[0].font.clone(); - GlyphCluster { - byte_idx, - codepoint: byte_idx.char_from(text), - width, - advance: width, - ascent: font.ascent(font_size), - descent: font.descent(font_size), - has_relative_shift: false, - transform: Transform::default(), - path_transform: Transform::default(), - glyphs: positioned_glyphs, - visible: true, - } -} - -pub(crate) trait DatabaseExt { - fn load_font(&self, id: ID) -> Option; - fn has_char(&self, id: ID, c: char) -> bool; -} - -impl DatabaseExt for Database { - #[inline(never)] - fn load_font(&self, id: ID) -> Option { - self.with_face_data(id, |data, face_index| -> Option { - let font = ttf_parser::Face::parse(data, face_index).ok()?; - - let units_per_em = NonZeroU16::new(font.units_per_em())?; - - let ascent = font.ascender(); - let descent = font.descender(); - - let x_height = font - .x_height() - .and_then(|x| u16::try_from(x).ok()) - .and_then(NonZeroU16::new); - let x_height = match x_height { - Some(height) => height, - None => { - // If not set - fallback to height * 45%. - // 45% is what Firefox uses. - u16::try_from((f32::from(ascent - descent) * 0.45) as i32) - .ok() - .and_then(NonZeroU16::new)? - } - }; - - let line_through = font.strikeout_metrics(); - let line_through_position = match line_through { - Some(metrics) => metrics.position, - None => x_height.get() as i16 / 2, - }; - - let (underline_position, underline_thickness) = match font.underline_metrics() { - Some(metrics) => { - let thickness = u16::try_from(metrics.thickness) - .ok() - .and_then(NonZeroU16::new) - // `ttf_parser` guarantees that units_per_em is >= 16 - .unwrap_or_else(|| NonZeroU16::new(units_per_em.get() / 12).unwrap()); - - (metrics.position, thickness) - } - None => ( - -(units_per_em.get() as i16) / 9, - NonZeroU16::new(units_per_em.get() / 12).unwrap(), - ), - }; - - // 0.2 and 0.4 are generic offsets used by some applications (Inkscape/librsvg). - let mut subscript_offset = (units_per_em.get() as f32 / 0.2).round() as i16; - let mut superscript_offset = (units_per_em.get() as f32 / 0.4).round() as i16; - if let Some(metrics) = font.subscript_metrics() { - subscript_offset = metrics.y_offset; - } - - if let Some(metrics) = font.superscript_metrics() { - superscript_offset = metrics.y_offset; - } - - Some(ResolvedFont { - id, - units_per_em, - ascent, - descent, - x_height, - underline_position, - underline_thickness, - line_through_position, - subscript_offset, - superscript_offset, - }) - })? - } - - #[inline(never)] - fn has_char(&self, id: ID, c: char) -> bool { - let res = self.with_face_data(id, |font_data, face_index| -> Option { - let font = ttf_parser::Face::parse(font_data, face_index).ok()?; - font.glyph_index(c)?; - Some(true) - }); - - res == Some(Some(true)) - } -} - -/// Text shaping with font fallback. -pub(crate) fn shape_text( - text: &str, - font: Arc, - small_caps: bool, - apply_kerning: bool, - resolver: &FontResolver, - fontdb: &mut Arc, -) -> Vec { - let mut glyphs = shape_text_with_font(text, font.clone(), small_caps, apply_kerning, fontdb) - .unwrap_or_default(); - - // Remember all fonts used for shaping. - let mut used_fonts = vec![font.id]; - - // Loop until all glyphs become resolved or until no more fonts are left. - 'outer: loop { - let mut missing = None; - for glyph in &glyphs { - if glyph.is_missing() { - missing = Some(glyph.byte_idx.char_from(text)); - break; - } - } - - if let Some(c) = missing { - let fallback_font = match (resolver.select_fallback)(c, &used_fonts, fontdb) - .and_then(|id| fontdb.load_font(id)) - { - Some(v) => Arc::new(v), - None => break 'outer, - }; - - // Shape again, using a new font. - let fallback_glyphs = shape_text_with_font( - text, - fallback_font.clone(), - small_caps, - apply_kerning, - fontdb, - ) - .unwrap_or_default(); - - let all_matched = fallback_glyphs.iter().all(|g| !g.is_missing()); - if all_matched { - // Replace all glyphs when all of them were matched. - glyphs = fallback_glyphs; - break 'outer; - } - - // We assume, that shaping with an any font will produce the same amount of glyphs. - // This is incorrect, but good enough for now. - if glyphs.len() != fallback_glyphs.len() { - break 'outer; - } - - // TODO: Replace clusters and not glyphs. This should be more accurate. - - // Copy new glyphs. - for i in 0..glyphs.len() { - if glyphs[i].is_missing() && !fallback_glyphs[i].is_missing() { - glyphs[i] = fallback_glyphs[i].clone(); - } - } - - // Remember this font. - used_fonts.push(fallback_font.id); - } else { - break 'outer; - } - } - - // Warn about missing glyphs. - for glyph in &glyphs { - if glyph.is_missing() { - let c = glyph.byte_idx.char_from(text); - // TODO: print a full grapheme - log::warn!( - "No fonts with a {}/U+{:X} character were found.", - c, - c as u32 - ); - } - } - - glyphs -} - -/// Converts a text into a list of glyph IDs. -/// -/// This function will do the BIDI reordering and text shaping. -fn shape_text_with_font( - text: &str, - font: Arc, - small_caps: bool, - apply_kerning: bool, - fontdb: &fontdb::Database, -) -> Option> { - fontdb.with_face_data(font.id, |font_data, face_index| -> Option> { - let rb_font = rustybuzz::Face::from_slice(font_data, face_index)?; - - let bidi_info = unicode_bidi::BidiInfo::new(text, Some(unicode_bidi::Level::ltr())); - let paragraph = &bidi_info.paragraphs[0]; - let line = paragraph.range.clone(); - - let mut glyphs = Vec::new(); - - let (levels, runs) = bidi_info.visual_runs(paragraph, line); - for run in runs.iter() { - let sub_text = &text[run.clone()]; - if sub_text.is_empty() { - continue; - } - - let ltr = levels[run.start].is_ltr(); - let hb_direction = if ltr { - rustybuzz::Direction::LeftToRight - } else { - rustybuzz::Direction::RightToLeft - }; - - let mut buffer = rustybuzz::UnicodeBuffer::new(); - buffer.push_str(sub_text); - buffer.set_direction(hb_direction); - - let mut features = Vec::new(); - if small_caps { - features.push(rustybuzz::Feature::new(Tag::from_bytes(b"smcp"), 1, ..)); - } - - if !apply_kerning { - features.push(rustybuzz::Feature::new(Tag::from_bytes(b"kern"), 0, ..)); - } - - let output = rustybuzz::shape(&rb_font, &features, buffer); - - let positions = output.glyph_positions(); - let infos = output.glyph_infos(); - - for i in 0..output.len() { - let pos = positions[i]; - let info = infos[i]; - let idx = run.start + info.cluster as usize; - - let start = info.cluster as usize; - - let end = if ltr { - i.checked_add(1) - } else { - i.checked_sub(1) - } - .and_then(|last| infos.get(last)) - .map_or(sub_text.len(), |info| info.cluster as usize); - - glyphs.push(Glyph { - byte_idx: ByteIndex::new(idx), - cluster_len: end.checked_sub(start).unwrap_or(0), // TODO: can fail? - text: sub_text[start..end].to_string(), - id: GlyphId(info.glyph_id as u16), - dx: pos.x_offset, - dy: pos.y_offset, - width: pos.x_advance, - font: font.clone(), - }); - } - } - - Some(glyphs) - })? -} - -/// An iterator over glyph clusters. -/// -/// Input: 0 2 2 2 3 4 4 5 5 -/// Result: 0 1 4 5 7 -pub(crate) struct GlyphClusters<'a> { - data: &'a [Glyph], - idx: usize, -} - -impl<'a> GlyphClusters<'a> { - pub(crate) fn new(data: &'a [Glyph]) -> Self { - GlyphClusters { data, idx: 0 } - } -} - -impl<'a> Iterator for GlyphClusters<'a> { - type Item = (std::ops::Range, ByteIndex); - - fn next(&mut self) -> Option { - if self.idx == self.data.len() { - return None; - } - - let start = self.idx; - let cluster = self.data[self.idx].byte_idx; - for g in &self.data[self.idx..] { - if g.byte_idx != cluster { - break; - } - - self.idx += 1; - } - - Some((start..self.idx, cluster)) - } -} - -/// Checks that selected script supports letter spacing. -/// -/// [In the CSS spec](https://www.w3.org/TR/css-text-3/#cursive-tracking). -/// -/// The list itself is from: https://github.com/harfbuzz/harfbuzz/issues/64 -pub(crate) fn script_supports_letter_spacing(script: unicode_script::Script) -> bool { - use unicode_script::Script; - - !matches!( - script, - Script::Arabic - | Script::Syriac - | Script::Nko - | Script::Manichaean - | Script::Psalter_Pahlavi - | Script::Mandaic - | Script::Mongolian - | Script::Phags_Pa - | Script::Devanagari - | Script::Bengali - | Script::Gurmukhi - | Script::Modi - | Script::Sharada - | Script::Syloti_Nagri - | Script::Tirhuta - | Script::Ogham - ) -} - -/// A glyph. -/// -/// Basically, a glyph ID and it's metrics. -#[derive(Clone)] -pub(crate) struct Glyph { - /// The glyph ID in the font. - pub(crate) id: GlyphId, - - /// Position in bytes in the original string. - /// - /// We use it to match a glyph with a character in the text chunk and therefore with the style. - pub(crate) byte_idx: ByteIndex, - - // The length of the cluster in bytes. - pub(crate) cluster_len: usize, - - /// The text from the original string that corresponds to that glyph. - pub(crate) text: String, - - /// The glyph offset in font units. - pub(crate) dx: i32, - - /// The glyph offset in font units. - pub(crate) dy: i32, - - /// The glyph width / X-advance in font units. - pub(crate) width: i32, - - /// Reference to the source font. - /// - /// Each glyph can have it's own source font. - pub(crate) font: Arc, -} - -impl Glyph { - fn is_missing(&self) -> bool { - self.id.0 == 0 - } -} - -#[derive(Clone, Copy, Debug)] -pub(crate) struct ResolvedFont { - pub(crate) id: ID, - - units_per_em: NonZeroU16, - - // All values below are in font units. - ascent: i16, - descent: i16, - x_height: NonZeroU16, - - underline_position: i16, - underline_thickness: NonZeroU16, - - // line-through thickness should be the the same as underline thickness - // according to the TrueType spec: - // https://docs.microsoft.com/en-us/typography/opentype/spec/os2#ystrikeoutsize - line_through_position: i16, - - subscript_offset: i16, - superscript_offset: i16, -} - -pub(crate) fn chunk_span_at(chunk: &TextChunk, byte_offset: ByteIndex) -> Option<&TextSpan> { - chunk - .spans - .iter() - .find(|&span| span_contains(span, byte_offset)) -} - -pub(crate) fn span_contains(span: &TextSpan, byte_offset: ByteIndex) -> bool { - byte_offset.value() >= span.start && byte_offset.value() < span.end -} - -/// Checks that the selected character is a word separator. -/// -/// According to: https://www.w3.org/TR/css-text-3/#word-separator -pub(crate) fn is_word_separator_characters(c: char) -> bool { - matches!( - c as u32, - 0x0020 | 0x00A0 | 0x1361 | 0x010100 | 0x010101 | 0x01039F | 0x01091F - ) -} - -impl ResolvedFont { - #[inline] - pub(crate) fn scale(&self, font_size: f32) -> f32 { - font_size / self.units_per_em.get() as f32 - } - - #[inline] - pub(crate) fn ascent(&self, font_size: f32) -> f32 { - self.ascent as f32 * self.scale(font_size) - } - - #[inline] - pub(crate) fn descent(&self, font_size: f32) -> f32 { - self.descent as f32 * self.scale(font_size) - } - - #[inline] - pub(crate) fn height(&self, font_size: f32) -> f32 { - self.ascent(font_size) - self.descent(font_size) - } - - #[inline] - pub(crate) fn x_height(&self, font_size: f32) -> f32 { - self.x_height.get() as f32 * self.scale(font_size) - } - - #[inline] - pub(crate) fn underline_position(&self, font_size: f32) -> f32 { - self.underline_position as f32 * self.scale(font_size) - } - - #[inline] - fn underline_thickness(&self, font_size: f32) -> f32 { - self.underline_thickness.get() as f32 * self.scale(font_size) - } - - #[inline] - pub(crate) fn line_through_position(&self, font_size: f32) -> f32 { - self.line_through_position as f32 * self.scale(font_size) - } - - #[inline] - fn subscript_offset(&self, font_size: f32) -> f32 { - self.subscript_offset as f32 * self.scale(font_size) - } - - #[inline] - fn superscript_offset(&self, font_size: f32) -> f32 { - self.superscript_offset as f32 * self.scale(font_size) - } - - fn dominant_baseline_shift(&self, baseline: DominantBaseline, font_size: f32) -> f32 { - let alignment = match baseline { - DominantBaseline::Auto => AlignmentBaseline::Auto, - DominantBaseline::UseScript => AlignmentBaseline::Auto, // unsupported - DominantBaseline::NoChange => AlignmentBaseline::Auto, // already resolved - DominantBaseline::ResetSize => AlignmentBaseline::Auto, // unsupported - DominantBaseline::Ideographic => AlignmentBaseline::Ideographic, - DominantBaseline::Alphabetic => AlignmentBaseline::Alphabetic, - DominantBaseline::Hanging => AlignmentBaseline::Hanging, - DominantBaseline::Mathematical => AlignmentBaseline::Mathematical, - DominantBaseline::Central => AlignmentBaseline::Central, - DominantBaseline::Middle => AlignmentBaseline::Middle, - DominantBaseline::TextAfterEdge => AlignmentBaseline::TextAfterEdge, - DominantBaseline::TextBeforeEdge => AlignmentBaseline::TextBeforeEdge, - }; - - self.alignment_baseline_shift(alignment, font_size) - } - - // The `alignment-baseline` property is a mess. - // - // The SVG 1.1 spec (https://www.w3.org/TR/SVG11/text.html#BaselineAlignmentProperties) - // goes on and on about what this property suppose to do, but doesn't actually explain - // how it should be implemented. It's just a very verbose overview. - // - // As of Nov 2022, only Chrome and Safari support `alignment-baseline`. Firefox isn't. - // Same goes for basically every SVG library in existence. - // Meaning we have no idea how exactly it should be implemented. - // - // And even Chrome and Safari cannot agree on how to handle `baseline`, `after-edge`, - // `text-after-edge` and `ideographic` variants. Producing vastly different output. - // - // As per spec, a proper implementation should get baseline values from the font itself, - // using `BASE` and `bsln` TrueType tables. If those tables are not present, - // we have to synthesize them (https://drafts.csswg.org/css-inline/#baseline-synthesis-fonts). - // And in the worst case scenario simply fallback to hardcoded values. - // - // Also, most fonts do not provide `BASE` and `bsln` tables to begin with. - // - // Again, as of Nov 2022, Chrome does only the latter: - // https://github.com/chromium/chromium/blob/main/third_party/blink/renderer/platform/fonts/font_metrics.cc#L153 - // - // Since baseline TrueType tables parsing and baseline synthesis are pretty hard, - // we do what Chrome does - use hardcoded values. And it seems like Safari does the same. - // - // - // But that's not all! SVG 2 and CSS Inline Layout 3 did a baseline handling overhaul, - // and it's far more complex now. Not sure if anyone actually supports it. - fn alignment_baseline_shift(&self, alignment: AlignmentBaseline, font_size: f32) -> f32 { - match alignment { - AlignmentBaseline::Auto => 0.0, - AlignmentBaseline::Baseline => 0.0, - AlignmentBaseline::BeforeEdge | AlignmentBaseline::TextBeforeEdge => { - self.ascent(font_size) - } - AlignmentBaseline::Middle => self.x_height(font_size) * 0.5, - AlignmentBaseline::Central => self.ascent(font_size) - self.height(font_size) * 0.5, - AlignmentBaseline::AfterEdge | AlignmentBaseline::TextAfterEdge => { - self.descent(font_size) - } - AlignmentBaseline::Ideographic => self.descent(font_size), - AlignmentBaseline::Alphabetic => 0.0, - AlignmentBaseline::Hanging => self.ascent(font_size) * 0.8, - AlignmentBaseline::Mathematical => self.ascent(font_size) * 0.5, - } - } -} - -pub(crate) type FontsCache = HashMap>; - -/// A read-only text index in bytes. -/// -/// Guarantee to be on a char boundary and in text bounds. -#[derive(Clone, Copy, PartialEq, Debug)] -pub(crate) struct ByteIndex(usize); - -impl ByteIndex { - fn new(i: usize) -> Self { - ByteIndex(i) - } - - pub(crate) fn value(&self) -> usize { - self.0 - } - - /// Converts byte position into a code point position. - pub(crate) fn code_point_at(&self, text: &str) -> usize { - text.char_indices() - .take_while(|(i, _)| *i != self.0) - .count() - } - - /// Converts byte position into a character. - pub(crate) fn char_from(&self, text: &str) -> char { - text[self.0..].chars().next().unwrap() - } -} diff --git a/resvg-0.43.0/crates/usvg/src/text/mod.rs b/resvg-0.43.0/crates/usvg/src/text/mod.rs deleted file mode 100644 index 10c8a92..0000000 --- a/resvg-0.43.0/crates/usvg/src/text/mod.rs +++ /dev/null @@ -1,220 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use std::sync::Arc; - -use fontdb::{Database, ID}; -use svgtypes::FontFamily; - -use self::layout::DatabaseExt; -use crate::{Font, FontStretch, FontStyle, Text}; - -mod flatten; - -mod colr; -/// Provides access to the layout of a text node. -pub mod layout; - -/// A shorthand for [FontResolver]'s font selection function. -/// -/// This function receives a font specification (families + a style, weight, -/// stretch triple) and a font database and should return the ID of the font -/// that shall be used (if any). -/// -/// In the basic case, the function will search the existing fonts in the -/// database to find a good match, e.g. via -/// [`Database::query`](fontdb::Database::query). This is what the [default -/// implementation](FontResolver::default_font_selector) does. -/// -/// Users with more complex requirements can mutate the database to load -/// additional fonts dynamically. To perform mutation, it is recommended to call -/// `Arc::make_mut` on the provided database. (This call is not done outside of -/// the callback to not needless clone an underlying shared database if no -/// mutation will be performed.) It is important that the database is only -/// mutated additively. Removing fonts or replacing the entire database will -/// break things. -pub type FontSelectionFn<'a> = - Box) -> Option + Send + Sync + 'a>; - -/// A shorthand for [FontResolver]'s fallback selection function. -/// -/// This function receives a specific character, a list of already used fonts, -/// and a font database. It should return the ID of a font that -/// - is not any of the already used fonts -/// - is as close as possible to the first already used font (if any) -/// - supports the given character -/// -/// The function can search the existing database, but can also load additional -/// fonts dynamically. See the documentation of [`FontSelectionFn`] for more -/// details. -pub type FallbackSelectionFn<'a> = - Box) -> Option + Send + Sync + 'a>; - -/// A font resolver for `` elements. -/// -/// This type can be useful if you want to have an alternative font handling to -/// the default one. By default, only fonts specified upfront in -/// [`Options::fontdb`](crate::Options::fontdb) will be used. This type allows -/// you to load additional fonts on-demand and customize the font selection -/// process. -pub struct FontResolver<'a> { - /// Resolver function that will be used when selecting a specific font - /// for a generic [`Font`] specification. - pub select_font: FontSelectionFn<'a>, - - /// Resolver function that will be used when selecting a fallback font for a - /// character. - pub select_fallback: FallbackSelectionFn<'a>, -} - -impl Default for FontResolver<'_> { - fn default() -> Self { - FontResolver { - select_font: FontResolver::default_font_selector(), - select_fallback: FontResolver::default_fallback_selector(), - } - } -} - -impl FontResolver<'_> { - /// Creates a default font selection resolver. - /// - /// The default implementation forwards to - /// [`query`](fontdb::Database::query) on the font database specified in the - /// [`Options`](crate::Options). - pub fn default_font_selector() -> FontSelectionFn<'static> { - Box::new(move |font, fontdb| { - let mut name_list = Vec::new(); - for family in &font.families { - name_list.push(match family { - FontFamily::Serif => fontdb::Family::Serif, - FontFamily::SansSerif => fontdb::Family::SansSerif, - FontFamily::Cursive => fontdb::Family::Cursive, - FontFamily::Fantasy => fontdb::Family::Fantasy, - FontFamily::Monospace => fontdb::Family::Monospace, - FontFamily::Named(s) => fontdb::Family::Name(s), - }); - } - - // Use the default font as fallback. - name_list.push(fontdb::Family::Serif); - - let stretch = match font.stretch { - FontStretch::UltraCondensed => fontdb::Stretch::UltraCondensed, - FontStretch::ExtraCondensed => fontdb::Stretch::ExtraCondensed, - FontStretch::Condensed => fontdb::Stretch::Condensed, - FontStretch::SemiCondensed => fontdb::Stretch::SemiCondensed, - FontStretch::Normal => fontdb::Stretch::Normal, - FontStretch::SemiExpanded => fontdb::Stretch::SemiExpanded, - FontStretch::Expanded => fontdb::Stretch::Expanded, - FontStretch::ExtraExpanded => fontdb::Stretch::ExtraExpanded, - FontStretch::UltraExpanded => fontdb::Stretch::UltraExpanded, - }; - - let style = match font.style { - FontStyle::Normal => fontdb::Style::Normal, - FontStyle::Italic => fontdb::Style::Italic, - FontStyle::Oblique => fontdb::Style::Oblique, - }; - - let query = fontdb::Query { - families: &name_list, - weight: fontdb::Weight(font.weight), - stretch, - style, - }; - - let id = fontdb.query(&query); - if id.is_none() { - log::warn!( - "No match for '{}' font-family.", - font.families - .iter() - .map(|f| f.to_string()) - .collect::>() - .join(", ") - ); - } - - id - }) - } - - /// Creates a default font fallback selection resolver. - /// - /// The default implementation searches through the entire `fontdb` - /// to find a font that has the correct style and supports the character. - pub fn default_fallback_selector() -> FallbackSelectionFn<'static> { - Box::new(|c, exclude_fonts, fontdb| { - let base_font_id = exclude_fonts[0]; - - // Iterate over fonts and check if any of them support the specified char. - for face in fontdb.faces() { - // Ignore fonts, that were used for shaping already. - if exclude_fonts.contains(&face.id) { - continue; - } - - // Check that the new face has the same style. - let base_face = fontdb.face(base_font_id)?; - if base_face.style != face.style - && base_face.weight != face.weight - && base_face.stretch != face.stretch - { - continue; - } - - if !fontdb.has_char(face.id, c) { - continue; - } - - let base_family = base_face - .families - .iter() - .find(|f| f.1 == fontdb::Language::English_UnitedStates) - .unwrap_or(&base_face.families[0]); - - let new_family = face - .families - .iter() - .find(|f| f.1 == fontdb::Language::English_UnitedStates) - .unwrap_or(&base_face.families[0]); - - log::warn!("Fallback from {} to {}.", base_family.0, new_family.0); - return Some(face.id); - } - - None - }) - } -} - -impl std::fmt::Debug for FontResolver<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("FontResolver { .. }") - } -} - -/// Convert a text into its paths. This is done in two steps: -/// 1. We convert the text into glyphs and position them according to the rules specified in the -/// SVG specifiation. While doing so, we also calculate the text bbox (which is not based on the -/// outlines of a glyph, but instead the glyph metrics as well as decoration spans). -/// 2. We convert all of the positioned glyphs into outlines. -pub(crate) fn convert( - text: &mut Text, - resolver: &FontResolver, - fontdb: &mut Arc, -) -> Option<()> { - let (text_fragments, bbox) = layout::layout_text(text, resolver, fontdb)?; - text.layouted = text_fragments; - text.bounding_box = bbox.to_rect(); - text.abs_bounding_box = bbox.transform(text.abs_transform)?.to_rect(); - - let (group, stroke_bbox) = flatten::flatten(text, fontdb)?; - text.flattened = Box::new(group); - text.stroke_bounding_box = stroke_bbox.to_rect(); - text.abs_stroke_bounding_box = stroke_bbox.transform(text.abs_transform)?.to_rect(); - - Some(()) -} diff --git a/resvg-0.43.0/crates/usvg/src/tree/filter.rs b/resvg-0.43.0/crates/usvg/src/tree/filter.rs deleted file mode 100644 index 4ec6b7c..0000000 --- a/resvg-0.43.0/crates/usvg/src/tree/filter.rs +++ /dev/null @@ -1,1065 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -//! SVG filter types. - -use strict_num::PositiveF32; - -use crate::{BlendMode, Color, Group, NonEmptyString, NonZeroF32, NonZeroRect, Opacity}; - -/// A filter element. -/// -/// `filter` element in the SVG. -#[derive(Debug)] -pub struct Filter { - pub(crate) id: NonEmptyString, - pub(crate) rect: NonZeroRect, - pub(crate) primitives: Vec, -} - -impl Filter { - /// Element's ID. - /// - /// Taken from the SVG itself. - /// Used only during SVG writing. `resvg` doesn't rely on this property. - pub fn id(&self) -> &str { - self.id.get() - } - - /// Filter region. - /// - /// `x`, `y`, `width` and `height` in the SVG. - pub fn rect(&self) -> NonZeroRect { - self.rect - } - - /// A list of filter primitives. - pub fn primitives(&self) -> &[Primitive] { - &self.primitives - } -} - -/// A filter primitive element. -#[derive(Clone, Debug)] -pub struct Primitive { - pub(crate) rect: NonZeroRect, - pub(crate) color_interpolation: ColorInterpolation, - pub(crate) result: String, - pub(crate) kind: Kind, -} - -impl Primitive { - /// Filter subregion. - /// - /// `x`, `y`, `width` and `height` in the SVG. - pub fn rect(&self) -> NonZeroRect { - self.rect - } - - /// Color interpolation mode. - /// - /// `color-interpolation-filters` in the SVG. - pub fn color_interpolation(&self) -> ColorInterpolation { - self.color_interpolation - } - - /// Assigned name for this filter primitive. - /// - /// `result` in the SVG. - pub fn result(&self) -> &str { - &self.result - } - - /// Filter primitive kind. - pub fn kind(&self) -> &Kind { - &self.kind - } -} - -/// A filter kind. -#[allow(missing_docs)] -#[derive(Clone, Debug)] -pub enum Kind { - Blend(Blend), - ColorMatrix(ColorMatrix), - ComponentTransfer(ComponentTransfer), - Composite(Composite), - ConvolveMatrix(ConvolveMatrix), - DiffuseLighting(DiffuseLighting), - DisplacementMap(DisplacementMap), - DropShadow(DropShadow), - Flood(Flood), - GaussianBlur(GaussianBlur), - Image(Image), - Merge(Merge), - Morphology(Morphology), - Offset(Offset), - SpecularLighting(SpecularLighting), - Tile(Tile), - Turbulence(Turbulence), -} - -impl Kind { - /// Checks that `FilterKind` has a specific input. - pub fn has_input(&self, input: &Input) -> bool { - match self { - Kind::Blend(ref fe) => fe.input1 == *input || fe.input2 == *input, - Kind::ColorMatrix(ref fe) => fe.input == *input, - Kind::ComponentTransfer(ref fe) => fe.input == *input, - Kind::Composite(ref fe) => fe.input1 == *input || fe.input2 == *input, - Kind::ConvolveMatrix(ref fe) => fe.input == *input, - Kind::DiffuseLighting(ref fe) => fe.input == *input, - Kind::DisplacementMap(ref fe) => fe.input1 == *input || fe.input2 == *input, - Kind::DropShadow(ref fe) => fe.input == *input, - Kind::Flood(_) => false, - Kind::GaussianBlur(ref fe) => fe.input == *input, - Kind::Image(_) => false, - Kind::Merge(ref fe) => fe.inputs.iter().any(|i| i == input), - Kind::Morphology(ref fe) => fe.input == *input, - Kind::Offset(ref fe) => fe.input == *input, - Kind::SpecularLighting(ref fe) => fe.input == *input, - Kind::Tile(ref fe) => fe.input == *input, - Kind::Turbulence(_) => false, - } - } -} - -/// Identifies input for a filter primitive. -#[allow(missing_docs)] -#[derive(Clone, PartialEq, Debug)] -pub enum Input { - SourceGraphic, - SourceAlpha, - Reference(String), -} - -/// A color interpolation mode. -#[allow(missing_docs)] -#[derive(Clone, Copy, PartialEq, Debug)] -pub enum ColorInterpolation { - SRGB, - LinearRGB, -} - -impl Default for ColorInterpolation { - fn default() -> Self { - ColorInterpolation::LinearRGB - } -} - -/// A blend filter primitive. -/// -/// `feBlend` element in the SVG. -#[derive(Clone, Debug)] -pub struct Blend { - pub(crate) input1: Input, - pub(crate) input2: Input, - pub(crate) mode: BlendMode, -} - -impl Blend { - /// Identifies input for the given filter primitive. - /// - /// `in` in the SVG. - pub fn input1(&self) -> &Input { - &self.input1 - } - - /// Identifies input for the given filter primitive. - /// - /// `in2` in the SVG. - pub fn input2(&self) -> &Input { - &self.input2 - } - - /// A blending mode. - /// - /// `mode` in the SVG. - pub fn mode(&self) -> BlendMode { - self.mode - } -} - -/// A color matrix filter primitive. -/// -/// `feColorMatrix` element in the SVG. -#[derive(Clone, Debug)] -pub struct ColorMatrix { - pub(crate) input: Input, - pub(crate) kind: ColorMatrixKind, -} - -impl ColorMatrix { - /// Identifies input for the given filter primitive. - /// - /// `in` in the SVG. - pub fn input(&self) -> &Input { - &self.input - } - - /// A matrix kind. - /// - /// `type` in the SVG. - pub fn kind(&self) -> &ColorMatrixKind { - &self.kind - } -} - -/// A color matrix filter primitive kind. -#[derive(Clone, Debug)] -#[allow(missing_docs)] -pub enum ColorMatrixKind { - Matrix(Vec), // Guarantee to have 20 numbers. - Saturate(PositiveF32), - HueRotate(f32), - LuminanceToAlpha, -} - -impl Default for ColorMatrixKind { - fn default() -> Self { - ColorMatrixKind::Matrix(vec![ - 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, - 0.0, 1.0, 0.0, - ]) - } -} - -/// A component-wise remapping filter primitive. -/// -/// `feComponentTransfer` element in the SVG. -#[derive(Clone, Debug)] -pub struct ComponentTransfer { - pub(crate) input: Input, - pub(crate) func_r: TransferFunction, - pub(crate) func_g: TransferFunction, - pub(crate) func_b: TransferFunction, - pub(crate) func_a: TransferFunction, -} - -impl ComponentTransfer { - /// Identifies input for the given filter primitive. - /// - /// `in` in the SVG. - pub fn input(&self) -> &Input { - &self.input - } - - /// `feFuncR` in the SVG. - pub fn func_r(&self) -> &TransferFunction { - &self.func_r - } - - /// `feFuncG` in the SVG. - pub fn func_g(&self) -> &TransferFunction { - &self.func_g - } - - /// `feFuncB` in the SVG. - pub fn func_b(&self) -> &TransferFunction { - &self.func_b - } - - /// `feFuncA` in the SVG. - pub fn func_a(&self) -> &TransferFunction { - &self.func_a - } -} - -/// A transfer function used by `FeComponentTransfer`. -/// -/// -#[derive(Clone, Debug)] -pub enum TransferFunction { - /// Keeps a component as is. - Identity, - - /// Applies a linear interpolation to a component. - /// - /// The number list can be empty. - Table(Vec), - - /// Applies a step function to a component. - /// - /// The number list can be empty. - Discrete(Vec), - - /// Applies a linear shift to a component. - #[allow(missing_docs)] - Linear { slope: f32, intercept: f32 }, - - /// Applies an exponential shift to a component. - #[allow(missing_docs)] - Gamma { - amplitude: f32, - exponent: f32, - offset: f32, - }, -} - -/// A composite filter primitive. -/// -/// `feComposite` element in the SVG. -#[derive(Clone, Debug)] -pub struct Composite { - pub(crate) input1: Input, - pub(crate) input2: Input, - pub(crate) operator: CompositeOperator, -} - -impl Composite { - /// Identifies input for the given filter primitive. - /// - /// `in` in the SVG. - pub fn input1(&self) -> &Input { - &self.input1 - } - - /// Identifies input for the given filter primitive. - /// - /// `in2` in the SVG. - pub fn input2(&self) -> &Input { - &self.input2 - } - - /// A compositing operation. - /// - /// `operator` in the SVG. - pub fn operator(&self) -> CompositeOperator { - self.operator - } -} - -/// An images compositing operation. -#[allow(missing_docs)] -#[derive(Clone, Copy, PartialEq, Debug)] -pub enum CompositeOperator { - Over, - In, - Out, - Atop, - Xor, - Arithmetic { k1: f32, k2: f32, k3: f32, k4: f32 }, -} - -/// A matrix convolution filter primitive. -/// -/// `feConvolveMatrix` element in the SVG. -#[derive(Clone, Debug)] -pub struct ConvolveMatrix { - pub(crate) input: Input, - pub(crate) matrix: ConvolveMatrixData, - pub(crate) divisor: NonZeroF32, - pub(crate) bias: f32, - pub(crate) edge_mode: EdgeMode, - pub(crate) preserve_alpha: bool, -} - -impl ConvolveMatrix { - /// Identifies input for the given filter primitive. - /// - /// `in` in the SVG. - pub fn input(&self) -> &Input { - &self.input - } - - /// A convolve matrix. - pub fn matrix(&self) -> &ConvolveMatrixData { - &self.matrix - } - - /// A matrix divisor. - /// - /// `divisor` in the SVG. - pub fn divisor(&self) -> NonZeroF32 { - self.divisor - } - - /// A kernel matrix bias. - /// - /// `bias` in the SVG. - pub fn bias(&self) -> f32 { - self.bias - } - - /// An edges processing mode. - /// - /// `edgeMode` in the SVG. - pub fn edge_mode(&self) -> EdgeMode { - self.edge_mode - } - - /// An alpha preserving flag. - /// - /// `preserveAlpha` in the SVG. - pub fn preserve_alpha(&self) -> bool { - self.preserve_alpha - } -} - -/// A convolve matrix representation. -/// -/// Used primarily by [`ConvolveMatrix`]. -#[derive(Clone, Debug)] -pub struct ConvolveMatrixData { - pub(crate) target_x: u32, - pub(crate) target_y: u32, - pub(crate) columns: u32, - pub(crate) rows: u32, - pub(crate) data: Vec, -} - -impl ConvolveMatrixData { - /// Returns a matrix's X target. - /// - /// `targetX` in the SVG. - pub fn target_x(&self) -> u32 { - self.target_x - } - - /// Returns a matrix's Y target. - /// - /// `targetY` in the SVG. - pub fn target_y(&self) -> u32 { - self.target_y - } - - /// Returns a number of columns in the matrix. - /// - /// Part of the `order` attribute in the SVG. - pub fn columns(&self) -> u32 { - self.columns - } - - /// Returns a number of rows in the matrix. - /// - /// Part of the `order` attribute in the SVG. - pub fn rows(&self) -> u32 { - self.rows - } - - /// The actual matrix. - pub fn data(&self) -> &[f32] { - &self.data - } -} - -impl ConvolveMatrixData { - /// Creates a new `ConvolveMatrixData`. - /// - /// Returns `None` when: - /// - /// - `columns` * `rows` != `data.len()` - /// - `target_x` >= `columns` - /// - `target_y` >= `rows` - pub(crate) fn new( - target_x: u32, - target_y: u32, - columns: u32, - rows: u32, - data: Vec, - ) -> Option { - if (columns * rows) as usize != data.len() || target_x >= columns || target_y >= rows { - return None; - } - - Some(ConvolveMatrixData { - target_x, - target_y, - columns, - rows, - data, - }) - } - - /// Returns a matrix value at the specified position. - /// - /// # Panics - /// - /// - When position is out of bounds. - pub fn get(&self, x: u32, y: u32) -> f32 { - self.data[(y * self.columns + x) as usize] - } -} - -/// An edges processing mode. -#[allow(missing_docs)] -#[derive(Clone, Copy, PartialEq, Debug)] -pub enum EdgeMode { - None, - Duplicate, - Wrap, -} - -/// A displacement map filter primitive. -/// -/// `feDisplacementMap` element in the SVG. -#[derive(Clone, Debug)] -pub struct DisplacementMap { - pub(crate) input1: Input, - pub(crate) input2: Input, - pub(crate) scale: f32, - pub(crate) x_channel_selector: ColorChannel, - pub(crate) y_channel_selector: ColorChannel, -} - -impl DisplacementMap { - /// Identifies input for the given filter primitive. - /// - /// `in` in the SVG. - pub fn input1(&self) -> &Input { - &self.input1 - } - - /// Identifies input for the given filter primitive. - /// - /// `in2` in the SVG. - pub fn input2(&self) -> &Input { - &self.input2 - } - - /// Scale factor. - /// - /// `scale` in the SVG. - pub fn scale(&self) -> f32 { - self.scale - } - - /// Indicates a source color channel along the X-axis. - /// - /// `xChannelSelector` in the SVG. - pub fn x_channel_selector(&self) -> ColorChannel { - self.x_channel_selector - } - - /// Indicates a source color channel along the Y-axis. - /// - /// `yChannelSelector` in the SVG. - pub fn y_channel_selector(&self) -> ColorChannel { - self.y_channel_selector - } -} - -/// A color channel. -#[allow(missing_docs)] -#[derive(Clone, Copy, PartialEq, Debug)] -pub enum ColorChannel { - R, - G, - B, - A, -} - -/// A drop shadow filter primitive. -/// -/// This is essentially `feGaussianBlur`, `feOffset` and `feFlood` joined together. -/// -/// `feDropShadow` element in the SVG. -#[derive(Clone, Debug)] -pub struct DropShadow { - pub(crate) input: Input, - pub(crate) dx: f32, - pub(crate) dy: f32, - pub(crate) std_dev_x: PositiveF32, - pub(crate) std_dev_y: PositiveF32, - pub(crate) color: Color, - pub(crate) opacity: Opacity, -} - -impl DropShadow { - /// Identifies input for the given filter primitive. - /// - /// `in` in the SVG. - pub fn input(&self) -> &Input { - &self.input - } - - /// The amount to offset the input graphic along the X-axis. - pub fn dx(&self) -> f32 { - self.dx - } - - /// The amount to offset the input graphic along the Y-axis. - pub fn dy(&self) -> f32 { - self.dy - } - - /// A standard deviation along the X-axis. - /// - /// `stdDeviation` in the SVG. - pub fn std_dev_x(&self) -> PositiveF32 { - self.std_dev_x - } - - /// A standard deviation along the Y-axis. - /// - /// `stdDeviation` in the SVG. - pub fn std_dev_y(&self) -> PositiveF32 { - self.std_dev_y - } - - /// A flood color. - /// - /// `flood-color` in the SVG. - pub fn color(&self) -> Color { - self.color - } - - /// A flood opacity. - /// - /// `flood-opacity` in the SVG. - pub fn opacity(&self) -> Opacity { - self.opacity - } -} - -/// A flood filter primitive. -/// -/// `feFlood` element in the SVG. -#[derive(Clone, Copy, Debug)] -pub struct Flood { - pub(crate) color: Color, - pub(crate) opacity: Opacity, -} - -impl Flood { - /// A flood color. - /// - /// `flood-color` in the SVG. - pub fn color(&self) -> Color { - self.color - } - - /// A flood opacity. - /// - /// `flood-opacity` in the SVG. - pub fn opacity(&self) -> Opacity { - self.opacity - } -} - -/// A Gaussian blur filter primitive. -/// -/// `feGaussianBlur` element in the SVG. -#[derive(Clone, Debug)] -pub struct GaussianBlur { - pub(crate) input: Input, - pub(crate) std_dev_x: PositiveF32, - pub(crate) std_dev_y: PositiveF32, -} - -impl GaussianBlur { - /// Identifies input for the given filter primitive. - /// - /// `in` in the SVG. - pub fn input(&self) -> &Input { - &self.input - } - - /// A standard deviation along the X-axis. - /// - /// `stdDeviation` in the SVG. - pub fn std_dev_x(&self) -> PositiveF32 { - self.std_dev_x - } - - /// A standard deviation along the Y-axis. - /// - /// `stdDeviation` in the SVG. - pub fn std_dev_y(&self) -> PositiveF32 { - self.std_dev_y - } -} - -/// An image filter primitive. -/// -/// `feImage` element in the SVG. -#[derive(Clone, Debug)] -pub struct Image { - pub(crate) root: Group, -} - -impl Image { - /// `feImage` children. - pub fn root(&self) -> &Group { - &self.root - } -} - -/// A diffuse lighting filter primitive. -/// -/// `feDiffuseLighting` element in the SVG. -#[derive(Clone, Debug)] -pub struct DiffuseLighting { - pub(crate) input: Input, - pub(crate) surface_scale: f32, - pub(crate) diffuse_constant: f32, - pub(crate) lighting_color: Color, - pub(crate) light_source: LightSource, -} - -impl DiffuseLighting { - /// Identifies input for the given filter primitive. - /// - /// `in` in the SVG. - pub fn input(&self) -> &Input { - &self.input - } - - /// A surface scale. - /// - /// `surfaceScale` in the SVG. - pub fn surface_scale(&self) -> f32 { - self.surface_scale - } - - /// A diffuse constant. - /// - /// `diffuseConstant` in the SVG. - pub fn diffuse_constant(&self) -> f32 { - self.diffuse_constant - } - - /// A lighting color. - /// - /// `lighting-color` in the SVG. - pub fn lighting_color(&self) -> Color { - self.lighting_color - } - - /// A light source. - pub fn light_source(&self) -> LightSource { - self.light_source - } -} - -/// A specular lighting filter primitive. -/// -/// `feSpecularLighting` element in the SVG. -#[derive(Clone, Debug)] -pub struct SpecularLighting { - pub(crate) input: Input, - pub(crate) surface_scale: f32, - pub(crate) specular_constant: f32, - pub(crate) specular_exponent: f32, - pub(crate) lighting_color: Color, - pub(crate) light_source: LightSource, -} - -impl SpecularLighting { - /// Identifies input for the given filter primitive. - /// - /// `in` in the SVG. - pub fn input(&self) -> &Input { - &self.input - } - - /// A surface scale. - /// - /// `surfaceScale` in the SVG. - pub fn surface_scale(&self) -> f32 { - self.surface_scale - } - - /// A specular constant. - /// - /// `specularConstant` in the SVG. - pub fn specular_constant(&self) -> f32 { - self.specular_constant - } - - /// A specular exponent. - /// - /// Should be in 1..128 range. - /// - /// `specularExponent` in the SVG. - pub fn specular_exponent(&self) -> f32 { - self.specular_exponent - } - - /// A lighting color. - /// - /// `lighting-color` in the SVG. - pub fn lighting_color(&self) -> Color { - self.lighting_color - } - - /// A light source. - pub fn light_source(&self) -> LightSource { - self.light_source - } -} - -/// A light source kind. -#[allow(missing_docs)] -#[derive(Clone, Copy, Debug)] -pub enum LightSource { - DistantLight(DistantLight), - PointLight(PointLight), - SpotLight(SpotLight), -} - -/// A distant light source. -/// -/// `feDistantLight` element in the SVG. -#[derive(Clone, Copy, Debug)] -pub struct DistantLight { - /// Direction angle for the light source on the XY plane (clockwise), - /// in degrees from the x axis. - /// - /// `azimuth` in the SVG. - pub azimuth: f32, - - /// Direction angle for the light source from the XY plane towards the z axis, in degrees. - /// - /// `elevation` in the SVG. - pub elevation: f32, -} - -/// A point light source. -/// -/// `fePointLight` element in the SVG. -#[derive(Clone, Copy, Debug)] -pub struct PointLight { - /// X location for the light source. - /// - /// `x` in the SVG. - pub x: f32, - - /// Y location for the light source. - /// - /// `y` in the SVG. - pub y: f32, - - /// Z location for the light source. - /// - /// `z` in the SVG. - pub z: f32, -} - -/// A spot light source. -/// -/// `feSpotLight` element in the SVG. -#[derive(Clone, Copy, Debug)] -pub struct SpotLight { - /// X location for the light source. - /// - /// `x` in the SVG. - pub x: f32, - - /// Y location for the light source. - /// - /// `y` in the SVG. - pub y: f32, - - /// Z location for the light source. - /// - /// `z` in the SVG. - pub z: f32, - - /// X point at which the light source is pointing. - /// - /// `pointsAtX` in the SVG. - pub points_at_x: f32, - - /// Y point at which the light source is pointing. - /// - /// `pointsAtY` in the SVG. - pub points_at_y: f32, - - /// Z point at which the light source is pointing. - /// - /// `pointsAtZ` in the SVG. - pub points_at_z: f32, - - /// Exponent value controlling the focus for the light source. - /// - /// `specularExponent` in the SVG. - pub specular_exponent: PositiveF32, - - /// A limiting cone which restricts the region where the light is projected. - /// - /// `limitingConeAngle` in the SVG. - pub limiting_cone_angle: Option, -} - -/// A merge filter primitive. -/// -/// `feMerge` element in the SVG. -#[derive(Clone, Debug)] -pub struct Merge { - pub(crate) inputs: Vec, -} - -impl Merge { - /// List of input layers that should be merged. - /// - /// List of `feMergeNode`'s in the SVG. - pub fn inputs(&self) -> &[Input] { - &self.inputs - } -} - -/// A morphology filter primitive. -/// -/// `feMorphology` element in the SVG. -#[derive(Clone, Debug)] -pub struct Morphology { - pub(crate) input: Input, - pub(crate) operator: MorphologyOperator, - pub(crate) radius_x: PositiveF32, - pub(crate) radius_y: PositiveF32, -} - -impl Morphology { - /// Identifies input for the given filter primitive. - /// - /// `in` in the SVG. - pub fn input(&self) -> &Input { - &self.input - } - - /// A filter operator. - /// - /// `operator` in the SVG. - pub fn operator(&self) -> MorphologyOperator { - self.operator - } - - /// A filter radius along the X-axis. - /// - /// A value of zero disables the effect of the given filter primitive. - /// - /// `radius` in the SVG. - pub fn radius_x(&self) -> PositiveF32 { - self.radius_x - } - - /// A filter radius along the Y-axis. - /// - /// A value of zero disables the effect of the given filter primitive. - /// - /// `radius` in the SVG. - pub fn radius_y(&self) -> PositiveF32 { - self.radius_y - } -} - -/// A morphology operation. -#[allow(missing_docs)] -#[derive(Clone, Copy, PartialEq, Debug)] -pub enum MorphologyOperator { - Erode, - Dilate, -} - -/// An offset filter primitive. -/// -/// `feOffset` element in the SVG. -#[derive(Clone, Debug)] -pub struct Offset { - pub(crate) input: Input, - pub(crate) dx: f32, - pub(crate) dy: f32, -} - -impl Offset { - /// Identifies input for the given filter primitive. - /// - /// `in` in the SVG. - pub fn input(&self) -> &Input { - &self.input - } - - /// The amount to offset the input graphic along the X-axis. - pub fn dx(&self) -> f32 { - self.dx - } - - /// The amount to offset the input graphic along the Y-axis. - pub fn dy(&self) -> f32 { - self.dy - } -} - -/// A tile filter primitive. -/// -/// `feTile` element in the SVG. -#[derive(Clone, Debug)] -pub struct Tile { - pub(crate) input: Input, -} - -impl Tile { - /// Identifies input for the given filter primitive. - /// - /// `in` in the SVG. - pub fn input(&self) -> &Input { - &self.input - } -} - -/// A turbulence generation filter primitive. -/// -/// `feTurbulence` element in the SVG. -#[derive(Clone, Copy, Debug)] -pub struct Turbulence { - pub(crate) base_frequency_x: PositiveF32, - pub(crate) base_frequency_y: PositiveF32, - pub(crate) num_octaves: u32, - pub(crate) seed: i32, - pub(crate) stitch_tiles: bool, - pub(crate) kind: TurbulenceKind, -} - -impl Turbulence { - /// Identifies the base frequency for the noise function. - /// - /// `baseFrequency` in the SVG. - pub fn base_frequency_x(&self) -> PositiveF32 { - self.base_frequency_x - } - - /// Identifies the base frequency for the noise function. - /// - /// `baseFrequency` in the SVG. - pub fn base_frequency_y(&self) -> PositiveF32 { - self.base_frequency_y - } - - /// Identifies the number of octaves for the noise function. - /// - /// `numOctaves` in the SVG. - pub fn num_octaves(&self) -> u32 { - self.num_octaves - } - - /// The starting number for the pseudo random number generator. - /// - /// `seed` in the SVG. - pub fn seed(&self) -> i32 { - self.seed - } - - /// Smooth transitions at the border of tiles. - /// - /// `stitchTiles` in the SVG. - pub fn stitch_tiles(&self) -> bool { - self.stitch_tiles - } - - /// Indicates whether the filter primitive should perform a noise or turbulence function. - /// - /// `type` in the SVG. - pub fn kind(&self) -> TurbulenceKind { - self.kind - } -} - -/// A turbulence kind for the `feTurbulence` filter. -#[allow(missing_docs)] -#[derive(Clone, Copy, PartialEq, Debug)] -pub enum TurbulenceKind { - FractalNoise, - Turbulence, -} diff --git a/resvg-0.43.0/crates/usvg/src/tree/geom.rs b/resvg-0.43.0/crates/usvg/src/tree/geom.rs deleted file mode 100644 index c159437..0000000 --- a/resvg-0.43.0/crates/usvg/src/tree/geom.rs +++ /dev/null @@ -1,194 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use strict_num::ApproxEqUlps; -use svgtypes::{Align, AspectRatio}; -pub use tiny_skia_path::{NonZeroRect, Rect, Size, Transform}; - -/// Approximate zero equality comparisons. -pub trait ApproxZeroUlps: ApproxEqUlps { - /// Checks if the number is approximately zero. - fn approx_zero_ulps(&self, ulps: ::U) -> bool; -} - -impl ApproxZeroUlps for f32 { - fn approx_zero_ulps(&self, ulps: i32) -> bool { - self.approx_eq_ulps(&0.0, ulps) - } -} - -impl ApproxZeroUlps for f64 { - fn approx_zero_ulps(&self, ulps: i64) -> bool { - self.approx_eq_ulps(&0.0, ulps) - } -} - -/// Checks that the current number is > 0. -pub(crate) trait IsValidLength { - /// Checks that the current number is > 0. - fn is_valid_length(&self) -> bool; -} - -impl IsValidLength for f32 { - #[inline] - fn is_valid_length(&self) -> bool { - *self > 0.0 && self.is_finite() - } -} - -impl IsValidLength for f64 { - #[inline] - fn is_valid_length(&self) -> bool { - *self > 0.0 && self.is_finite() - } -} - -/// View box. -#[derive(Clone, Copy, Debug)] -pub(crate) struct ViewBox { - /// Value of the `viewBox` attribute. - pub rect: NonZeroRect, - - /// Value of the `preserveAspectRatio` attribute. - pub aspect: AspectRatio, -} - -impl ViewBox { - /// Converts `viewBox` into `Transform`. - pub fn to_transform(&self, img_size: Size) -> Transform { - let vr = self.rect; - - let sx = img_size.width() / vr.width(); - let sy = img_size.height() / vr.height(); - - let (sx, sy) = if self.aspect.align == Align::None { - (sx, sy) - } else { - let s = if self.aspect.slice { - if sx < sy { - sy - } else { - sx - } - } else { - if sx > sy { - sy - } else { - sx - } - }; - - (s, s) - }; - - let x = -vr.x() * sx; - let y = -vr.y() * sy; - let w = img_size.width() - vr.width() * sx; - let h = img_size.height() - vr.height() * sy; - - let (tx, ty) = aligned_pos(self.aspect.align, x, y, w, h); - Transform::from_row(sx, 0.0, 0.0, sy, tx, ty) - } -} - -/// A bounding box calculator. -#[derive(Clone, Copy, Debug)] -pub(crate) struct BBox { - left: f32, - top: f32, - right: f32, - bottom: f32, -} - -impl From for BBox { - fn from(r: Rect) -> Self { - Self { - left: r.left(), - top: r.top(), - right: r.right(), - bottom: r.bottom(), - } - } -} - -impl From for BBox { - fn from(r: NonZeroRect) -> Self { - Self { - left: r.left(), - top: r.top(), - right: r.right(), - bottom: r.bottom(), - } - } -} - -impl Default for BBox { - fn default() -> Self { - Self { - left: f32::MAX, - top: f32::MAX, - right: f32::MIN, - bottom: f32::MIN, - } - } -} - -impl BBox { - /// Checks if the bounding box is default, i.e. invalid. - pub fn is_default(&self) -> bool { - self.left == f32::MAX - && self.top == f32::MAX - && self.right == f32::MIN - && self.bottom == f32::MIN - } - - /// Expand the bounding box to the specified bounds. - #[must_use] - pub fn expand(&self, r: impl Into) -> Self { - self.expand_impl(r.into()) - } - - fn expand_impl(&self, r: Self) -> Self { - Self { - left: self.left.min(r.left), - top: self.top.min(r.top), - right: self.right.max(r.right), - bottom: self.bottom.max(r.bottom), - } - } - - /// Converts a bounding box into [`Rect`]. - pub fn to_rect(&self) -> Option { - if !self.is_default() { - Rect::from_ltrb(self.left, self.top, self.right, self.bottom) - } else { - None - } - } - - /// Converts a bounding box into [`NonZeroRect`]. - pub fn to_non_zero_rect(&self) -> Option { - if !self.is_default() { - NonZeroRect::from_ltrb(self.left, self.top, self.right, self.bottom) - } else { - None - } - } -} - -/// Returns object aligned position. -pub(crate) fn aligned_pos(align: Align, x: f32, y: f32, w: f32, h: f32) -> (f32, f32) { - match align { - Align::None => (x, y), - Align::XMinYMin => (x, y), - Align::XMidYMin => (x + w / 2.0, y), - Align::XMaxYMin => (x + w, y), - Align::XMinYMid => (x, y + h / 2.0), - Align::XMidYMid => (x + w / 2.0, y + h / 2.0), - Align::XMaxYMid => (x + w, y + h / 2.0), - Align::XMinYMax => (x, y + h), - Align::XMidYMax => (x + w / 2.0, y + h), - Align::XMaxYMax => (x + w, y + h), - } -} diff --git a/resvg-0.43.0/crates/usvg/src/tree/mod.rs b/resvg-0.43.0/crates/usvg/src/tree/mod.rs deleted file mode 100644 index 73f8fad..0000000 --- a/resvg-0.43.0/crates/usvg/src/tree/mod.rs +++ /dev/null @@ -1,1865 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -pub mod filter; -mod geom; -mod text; - -use std::sync::Arc; - -pub use strict_num::{self, ApproxEqUlps, NonZeroPositiveF32, NormalizedF32, PositiveF32}; - -pub use tiny_skia_path; - -pub use self::geom::*; -pub use self::text::*; - -use crate::OptionLog; - -/// An alias to `NormalizedF32`. -pub type Opacity = NormalizedF32; - -// Must not be clone-able to preserve ID uniqueness. -#[derive(Debug)] -pub(crate) struct NonEmptyString(String); - -impl NonEmptyString { - pub(crate) fn new(string: String) -> Option { - if string.trim().is_empty() { - return None; - } - - Some(NonEmptyString(string)) - } - - pub(crate) fn get(&self) -> &str { - &self.0 - } - - pub(crate) fn take(self) -> String { - self.0 - } -} - -/// A non-zero `f32`. -/// -/// Just like `f32` but immutable and guarantee to never be zero. -#[derive(Clone, Copy, Debug)] -pub struct NonZeroF32(f32); - -impl NonZeroF32 { - /// Creates a new `NonZeroF32` value. - #[inline] - pub fn new(n: f32) -> Option { - if n.approx_eq_ulps(&0.0, 4) { - None - } else { - Some(NonZeroF32(n)) - } - } - - /// Returns an underlying value. - #[inline] - pub fn get(&self) -> f32 { - self.0 - } -} - -#[derive(Clone, Copy, PartialEq, Debug)] -pub(crate) enum Units { - UserSpaceOnUse, - ObjectBoundingBox, -} - -// `Units` cannot have a default value, because it changes depending on an element. - -/// A visibility property. -/// -/// `visibility` attribute in the SVG. -#[allow(missing_docs)] -#[derive(Clone, Copy, PartialEq, Debug)] -pub(crate) enum Visibility { - Visible, - Hidden, - Collapse, -} - -impl Default for Visibility { - fn default() -> Self { - Self::Visible - } -} - -/// A shape rendering method. -/// -/// `shape-rendering` attribute in the SVG. -#[derive(Clone, Copy, PartialEq, Debug)] -#[allow(missing_docs)] -pub enum ShapeRendering { - OptimizeSpeed, - CrispEdges, - GeometricPrecision, -} - -impl ShapeRendering { - /// Checks if anti-aliasing should be enabled. - pub fn use_shape_antialiasing(self) -> bool { - match self { - ShapeRendering::OptimizeSpeed => false, - ShapeRendering::CrispEdges => false, - ShapeRendering::GeometricPrecision => true, - } - } -} - -impl Default for ShapeRendering { - fn default() -> Self { - Self::GeometricPrecision - } -} - -impl std::str::FromStr for ShapeRendering { - type Err = &'static str; - - fn from_str(s: &str) -> Result { - match s { - "optimizeSpeed" => Ok(ShapeRendering::OptimizeSpeed), - "crispEdges" => Ok(ShapeRendering::CrispEdges), - "geometricPrecision" => Ok(ShapeRendering::GeometricPrecision), - _ => Err("invalid"), - } - } -} - -/// A text rendering method. -/// -/// `text-rendering` attribute in the SVG. -#[allow(missing_docs)] -#[derive(Clone, Copy, PartialEq, Debug)] -pub enum TextRendering { - OptimizeSpeed, - OptimizeLegibility, - GeometricPrecision, -} - -impl Default for TextRendering { - fn default() -> Self { - Self::OptimizeLegibility - } -} - -impl std::str::FromStr for TextRendering { - type Err = &'static str; - - fn from_str(s: &str) -> Result { - match s { - "optimizeSpeed" => Ok(TextRendering::OptimizeSpeed), - "optimizeLegibility" => Ok(TextRendering::OptimizeLegibility), - "geometricPrecision" => Ok(TextRendering::GeometricPrecision), - _ => Err("invalid"), - } - } -} - -/// An image rendering method. -/// -/// `image-rendering` attribute in the SVG. -#[allow(missing_docs)] -#[derive(Clone, Copy, PartialEq, Debug)] -pub enum ImageRendering { - OptimizeQuality, - OptimizeSpeed, -} - -impl Default for ImageRendering { - fn default() -> Self { - Self::OptimizeQuality - } -} - -impl std::str::FromStr for ImageRendering { - type Err = &'static str; - - fn from_str(s: &str) -> Result { - match s { - "optimizeQuality" => Ok(ImageRendering::OptimizeQuality), - "optimizeSpeed" => Ok(ImageRendering::OptimizeSpeed), - _ => Err("invalid"), - } - } -} - -/// A blending mode property. -/// -/// `mix-blend-mode` attribute in the SVG. -#[allow(missing_docs)] -#[derive(Clone, Copy, PartialEq, Debug)] -pub enum BlendMode { - Normal, - Multiply, - Screen, - Overlay, - Darken, - Lighten, - ColorDodge, - ColorBurn, - HardLight, - SoftLight, - Difference, - Exclusion, - Hue, - Saturation, - Color, - Luminosity, -} - -impl Default for BlendMode { - fn default() -> Self { - Self::Normal - } -} - -/// A spread method. -/// -/// `spreadMethod` attribute in the SVG. -#[allow(missing_docs)] -#[derive(Clone, Copy, PartialEq, Debug)] -pub enum SpreadMethod { - Pad, - Reflect, - Repeat, -} - -impl Default for SpreadMethod { - fn default() -> Self { - Self::Pad - } -} - -/// A generic gradient. -#[derive(Debug)] -pub struct BaseGradient { - pub(crate) id: NonEmptyString, - pub(crate) units: Units, // used only during parsing - pub(crate) transform: Transform, - pub(crate) spread_method: SpreadMethod, - pub(crate) stops: Vec, -} - -impl BaseGradient { - /// Element's ID. - /// - /// Taken from the SVG itself. - /// Used only during SVG writing. `resvg` doesn't rely on this property. - pub fn id(&self) -> &str { - self.id.get() - } - - /// Gradient transform. - /// - /// `gradientTransform` in SVG. - pub fn transform(&self) -> Transform { - self.transform - } - - /// Gradient spreading method. - /// - /// `spreadMethod` in SVG. - pub fn spread_method(&self) -> SpreadMethod { - self.spread_method - } - - /// A list of `stop` elements. - pub fn stops(&self) -> &[Stop] { - &self.stops - } -} - -/// A linear gradient. -/// -/// `linearGradient` element in SVG. -#[derive(Debug)] -pub struct LinearGradient { - pub(crate) base: BaseGradient, - pub(crate) x1: f32, - pub(crate) y1: f32, - pub(crate) x2: f32, - pub(crate) y2: f32, -} - -impl LinearGradient { - /// `x1` coordinate. - pub fn x1(&self) -> f32 { - self.x1 - } - - /// `y1` coordinate. - pub fn y1(&self) -> f32 { - self.y1 - } - - /// `x2` coordinate. - pub fn x2(&self) -> f32 { - self.x2 - } - - /// `y2` coordinate. - pub fn y2(&self) -> f32 { - self.y2 - } -} - -impl std::ops::Deref for LinearGradient { - type Target = BaseGradient; - - fn deref(&self) -> &Self::Target { - &self.base - } -} - -/// A radial gradient. -/// -/// `radialGradient` element in SVG. -#[derive(Debug)] -pub struct RadialGradient { - pub(crate) base: BaseGradient, - pub(crate) cx: f32, - pub(crate) cy: f32, - pub(crate) r: PositiveF32, - pub(crate) fx: f32, - pub(crate) fy: f32, -} - -impl RadialGradient { - /// `cx` coordinate. - pub fn cx(&self) -> f32 { - self.cx - } - - /// `cy` coordinate. - pub fn cy(&self) -> f32 { - self.cy - } - - /// Gradient radius. - pub fn r(&self) -> PositiveF32 { - self.r - } - - /// `fx` coordinate. - pub fn fx(&self) -> f32 { - self.fx - } - - /// `fy` coordinate. - pub fn fy(&self) -> f32 { - self.fy - } -} - -impl std::ops::Deref for RadialGradient { - type Target = BaseGradient; - - fn deref(&self) -> &Self::Target { - &self.base - } -} - -/// An alias to `NormalizedF32`. -pub type StopOffset = NormalizedF32; - -/// Gradient's stop element. -/// -/// `stop` element in SVG. -#[derive(Clone, Copy, Debug)] -pub struct Stop { - pub(crate) offset: StopOffset, - pub(crate) color: Color, - pub(crate) opacity: Opacity, -} - -impl Stop { - /// Gradient stop offset. - /// - /// `offset` in SVG. - pub fn offset(&self) -> StopOffset { - self.offset - } - - /// Gradient stop color. - /// - /// `stop-color` in SVG. - pub fn color(&self) -> Color { - self.color - } - - /// Gradient stop opacity. - /// - /// `stop-opacity` in SVG. - pub fn opacity(&self) -> Opacity { - self.opacity - } -} - -/// A pattern element. -/// -/// `pattern` element in SVG. -#[derive(Debug)] -pub struct Pattern { - pub(crate) id: NonEmptyString, - pub(crate) units: Units, // used only during parsing - pub(crate) content_units: Units, // used only during parsing - pub(crate) transform: Transform, - pub(crate) rect: NonZeroRect, - pub(crate) view_box: Option, - pub(crate) root: Group, -} - -impl Pattern { - /// Element's ID. - /// - /// Taken from the SVG itself. - /// Used only during SVG writing. `resvg` doesn't rely on this property. - pub fn id(&self) -> &str { - self.id.get() - } - - /// Pattern transform. - /// - /// `patternTransform` in SVG. - pub fn transform(&self) -> Transform { - self.transform - } - - /// Pattern rectangle. - /// - /// `x`, `y`, `width` and `height` in SVG. - pub fn rect(&self) -> NonZeroRect { - self.rect - } - - /// Pattern children. - pub fn root(&self) -> &Group { - &self.root - } -} - -/// An alias to `NonZeroPositiveF32`. -pub type StrokeWidth = NonZeroPositiveF32; - -/// A `stroke-miterlimit` value. -/// -/// Just like `f32` but immutable and guarantee to be >=1.0. -#[derive(Clone, Copy, Debug)] -pub struct StrokeMiterlimit(f32); - -impl StrokeMiterlimit { - /// Creates a new `StrokeMiterlimit` value. - #[inline] - pub fn new(n: f32) -> Self { - debug_assert!(n.is_finite()); - debug_assert!(n >= 1.0); - - let n = if !(n >= 1.0) { 1.0 } else { n }; - - StrokeMiterlimit(n) - } - - /// Returns an underlying value. - #[inline] - pub fn get(&self) -> f32 { - self.0 - } -} - -impl Default for StrokeMiterlimit { - #[inline] - fn default() -> Self { - StrokeMiterlimit::new(4.0) - } -} - -impl From for StrokeMiterlimit { - #[inline] - fn from(n: f32) -> Self { - Self::new(n) - } -} - -impl PartialEq for StrokeMiterlimit { - #[inline] - fn eq(&self, other: &Self) -> bool { - self.0.approx_eq_ulps(&other.0, 4) - } -} - -/// A line cap. -/// -/// `stroke-linecap` attribute in the SVG. -#[allow(missing_docs)] -#[derive(Clone, Copy, PartialEq, Debug)] -pub enum LineCap { - Butt, - Round, - Square, -} - -impl Default for LineCap { - fn default() -> Self { - Self::Butt - } -} - -/// A line join. -/// -/// `stroke-linejoin` attribute in the SVG. -#[allow(missing_docs)] -#[derive(Clone, Copy, PartialEq, Debug)] -pub enum LineJoin { - Miter, - MiterClip, - Round, - Bevel, -} - -impl Default for LineJoin { - fn default() -> Self { - Self::Miter - } -} - -/// A stroke style. -#[derive(Clone, Debug)] -pub struct Stroke { - pub(crate) paint: Paint, - pub(crate) dasharray: Option>, - pub(crate) dashoffset: f32, - pub(crate) miterlimit: StrokeMiterlimit, - pub(crate) opacity: Opacity, - pub(crate) width: StrokeWidth, - pub(crate) linecap: LineCap, - pub(crate) linejoin: LineJoin, - // Whether the current stroke needs to be resolved relative - // to a context element. - pub(crate) context_element: Option, -} - -impl Stroke { - /// Stroke paint. - pub fn paint(&self) -> &Paint { - &self.paint - } - - /// Stroke dash array. - pub fn dasharray(&self) -> Option<&[f32]> { - self.dasharray.as_deref() - } - - /// Stroke dash offset. - pub fn dashoffset(&self) -> f32 { - self.dashoffset - } - - /// Stroke miter limit. - pub fn miterlimit(&self) -> StrokeMiterlimit { - self.miterlimit - } - - /// Stroke opacity. - pub fn opacity(&self) -> Opacity { - self.opacity - } - - /// Stroke width. - pub fn width(&self) -> StrokeWidth { - self.width - } - - /// Stroke linecap. - pub fn linecap(&self) -> LineCap { - self.linecap - } - - /// Stroke linejoin. - pub fn linejoin(&self) -> LineJoin { - self.linejoin - } - - /// Converts into a `tiny_skia_path::Stroke` type. - pub fn to_tiny_skia(&self) -> tiny_skia_path::Stroke { - let mut stroke = tiny_skia_path::Stroke { - width: self.width.get(), - miter_limit: self.miterlimit.get(), - line_cap: match self.linecap { - LineCap::Butt => tiny_skia_path::LineCap::Butt, - LineCap::Round => tiny_skia_path::LineCap::Round, - LineCap::Square => tiny_skia_path::LineCap::Square, - }, - line_join: match self.linejoin { - LineJoin::Miter => tiny_skia_path::LineJoin::Miter, - LineJoin::MiterClip => tiny_skia_path::LineJoin::MiterClip, - LineJoin::Round => tiny_skia_path::LineJoin::Round, - LineJoin::Bevel => tiny_skia_path::LineJoin::Bevel, - }, - // According to the spec, dash should not be accounted during - // bbox calculation. - dash: None, - }; - - if let Some(ref list) = self.dasharray { - stroke.dash = tiny_skia_path::StrokeDash::new(list.clone(), self.dashoffset); - } - - stroke - } -} - -/// A fill rule. -/// -/// `fill-rule` attribute in the SVG. -#[allow(missing_docs)] -#[derive(Clone, Copy, PartialEq, Debug)] -pub enum FillRule { - NonZero, - EvenOdd, -} - -impl Default for FillRule { - fn default() -> Self { - Self::NonZero - } -} - -#[derive(Clone, Copy, Debug)] -pub(crate) enum ContextElement { - /// The current context element is a use node. Since we can get - /// the bounding box of a use node only once we have converted - /// all elements, we need to fix the transform and units of - /// the stroke/fill after converting the whole tree. - UseNode, - /// The current context element is a path node (i.e. only applicable - /// if we draw the marker of a path). Since we already know the bounding - /// box of the path when rendering the markers, we can convert them directly, - /// so we do it while parsing. - PathNode(Transform, Option), -} - -/// A fill style. -#[derive(Clone, Debug)] -pub struct Fill { - pub(crate) paint: Paint, - pub(crate) opacity: Opacity, - pub(crate) rule: FillRule, - // Whether the current fill needs to be resolved relative - // to a context element. - pub(crate) context_element: Option, -} - -impl Fill { - /// Fill paint. - pub fn paint(&self) -> &Paint { - &self.paint - } - - /// Fill opacity. - pub fn opacity(&self) -> Opacity { - self.opacity - } - - /// Fill rule. - pub fn rule(&self) -> FillRule { - self.rule - } -} - -impl Default for Fill { - fn default() -> Self { - Fill { - paint: Paint::Color(Color::black()), - opacity: Opacity::ONE, - rule: FillRule::default(), - context_element: None, - } - } -} - -/// A 8-bit RGB color. -#[derive(Clone, Copy, PartialEq, Debug)] -#[allow(missing_docs)] -pub struct Color { - pub red: u8, - pub green: u8, - pub blue: u8, -} - -impl Color { - /// Constructs a new `Color` from RGB values. - #[inline] - pub fn new_rgb(red: u8, green: u8, blue: u8) -> Color { - Color { red, green, blue } - } - - /// Constructs a new `Color` set to black. - #[inline] - pub fn black() -> Color { - Color::new_rgb(0, 0, 0) - } - - /// Constructs a new `Color` set to white. - #[inline] - pub fn white() -> Color { - Color::new_rgb(255, 255, 255) - } -} - -/// A paint style. -/// -/// `paint` value type in the SVG. -#[allow(missing_docs)] -#[derive(Clone, Debug)] -pub enum Paint { - Color(Color), - LinearGradient(Arc), - RadialGradient(Arc), - Pattern(Arc), -} - -impl PartialEq for Paint { - #[inline] - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::Color(lc), Self::Color(rc)) => lc == rc, - (Self::LinearGradient(ref lg1), Self::LinearGradient(ref lg2)) => Arc::ptr_eq(lg1, lg2), - (Self::RadialGradient(ref rg1), Self::RadialGradient(ref rg2)) => Arc::ptr_eq(rg1, rg2), - (Self::Pattern(ref p1), Self::Pattern(ref p2)) => Arc::ptr_eq(p1, p2), - _ => false, - } - } -} - -/// A clip-path element. -/// -/// `clipPath` element in SVG. -#[derive(Debug)] -pub struct ClipPath { - pub(crate) id: NonEmptyString, - pub(crate) transform: Transform, - pub(crate) clip_path: Option>, - pub(crate) root: Group, -} - -impl ClipPath { - pub(crate) fn empty(id: NonEmptyString) -> Self { - ClipPath { - id, - transform: Transform::default(), - clip_path: None, - root: Group::empty(), - } - } - - /// Element's ID. - /// - /// Taken from the SVG itself. - /// Used only during SVG writing. `resvg` doesn't rely on this property. - pub fn id(&self) -> &str { - self.id.get() - } - - /// Clip path transform. - /// - /// `transform` in SVG. - pub fn transform(&self) -> Transform { - self.transform - } - - /// Additional clip path. - /// - /// `clip-path` in SVG. - pub fn clip_path(&self) -> Option<&ClipPath> { - self.clip_path.as_deref() - } - - /// Clip path children. - pub fn root(&self) -> &Group { - &self.root - } -} - -/// A mask type. -#[derive(Clone, Copy, PartialEq, Debug)] -pub enum MaskType { - /// Indicates that the luminance values of the mask should be used. - Luminance, - /// Indicates that the alpha values of the mask should be used. - Alpha, -} - -impl Default for MaskType { - fn default() -> Self { - Self::Luminance - } -} - -/// A mask element. -/// -/// `mask` element in SVG. -#[derive(Debug)] -pub struct Mask { - pub(crate) id: NonEmptyString, - pub(crate) rect: NonZeroRect, - pub(crate) kind: MaskType, - pub(crate) mask: Option>, - pub(crate) root: Group, -} - -impl Mask { - /// Element's ID. - /// - /// Taken from the SVG itself. - /// Used only during SVG writing. `resvg` doesn't rely on this property. - pub fn id(&self) -> &str { - self.id.get() - } - - /// Mask rectangle. - /// - /// `x`, `y`, `width` and `height` in SVG. - pub fn rect(&self) -> NonZeroRect { - self.rect - } - - /// Mask type. - /// - /// `mask-type` in SVG. - pub fn kind(&self) -> MaskType { - self.kind - } - - /// Additional mask. - /// - /// `mask` in SVG. - pub fn mask(&self) -> Option<&Mask> { - self.mask.as_deref() - } - - /// Mask children. - /// - /// A mask can have no children, in which case the whole element should be masked out. - pub fn root(&self) -> &Group { - &self.root - } -} - -/// Node's kind. -#[allow(missing_docs)] -#[derive(Clone, Debug)] -pub enum Node { - Group(Box), - Path(Box), - Image(Box), - Text(Box), -} - -impl Node { - /// Returns node's ID. - pub fn id(&self) -> &str { - match self { - Node::Group(ref e) => e.id.as_str(), - Node::Path(ref e) => e.id.as_str(), - Node::Image(ref e) => e.id.as_str(), - Node::Text(ref e) => e.id.as_str(), - } - } - - /// Returns node's absolute transform. - /// - /// This method is cheap since absolute transforms are already resolved. - pub fn abs_transform(&self) -> Transform { - match self { - Node::Group(ref group) => group.abs_transform(), - Node::Path(ref path) => path.abs_transform(), - Node::Image(ref image) => image.abs_transform(), - Node::Text(ref text) => text.abs_transform(), - } - } - - /// Returns node's bounding box in object coordinates, if any. - pub fn bounding_box(&self) -> Rect { - match self { - Node::Group(ref group) => group.bounding_box(), - Node::Path(ref path) => path.bounding_box(), - Node::Image(ref image) => image.bounding_box(), - Node::Text(ref text) => text.bounding_box(), - } - } - - /// Returns node's bounding box in canvas coordinates, if any. - pub fn abs_bounding_box(&self) -> Rect { - match self { - Node::Group(ref group) => group.abs_bounding_box(), - Node::Path(ref path) => path.abs_bounding_box(), - Node::Image(ref image) => image.abs_bounding_box(), - Node::Text(ref text) => text.abs_bounding_box(), - } - } - - /// Returns node's bounding box, including stroke, in object coordinates, if any. - pub fn stroke_bounding_box(&self) -> Rect { - match self { - Node::Group(ref group) => group.stroke_bounding_box(), - Node::Path(ref path) => path.stroke_bounding_box(), - // Image cannot be stroked. - Node::Image(ref image) => image.bounding_box(), - Node::Text(ref text) => text.stroke_bounding_box(), - } - } - - /// Returns node's bounding box, including stroke, in canvas coordinates, if any. - pub fn abs_stroke_bounding_box(&self) -> Rect { - match self { - Node::Group(ref group) => group.abs_stroke_bounding_box(), - Node::Path(ref path) => path.abs_stroke_bounding_box(), - // Image cannot be stroked. - Node::Image(ref image) => image.abs_bounding_box(), - Node::Text(ref text) => text.abs_stroke_bounding_box(), - } - } - - /// Element's "layer" bounding box in canvas units, if any. - /// - /// For most nodes this is just `abs_bounding_box`, - /// but for groups this is `abs_layer_bounding_box`. - /// - /// See [`Group::layer_bounding_box`] for details. - pub fn abs_layer_bounding_box(&self) -> Option { - match self { - Node::Group(ref group) => Some(group.abs_layer_bounding_box()), - // Hor/ver path without stroke can return None. This is expected. - Node::Path(ref path) => path.abs_bounding_box().to_non_zero_rect(), - Node::Image(ref image) => image.abs_bounding_box().to_non_zero_rect(), - Node::Text(ref text) => text.abs_bounding_box().to_non_zero_rect(), - } - } - - /// Calls a closure for each subroot this `Node` has. - /// - /// The [`Tree::root`](Tree::root) field contain only render-able SVG elements. - /// But some elements, specifically clip paths, masks, patterns and feImage - /// can store their own SVG subtrees. - /// And while one can access them manually, it's pretty verbose. - /// This methods allows looping over _all_ SVG elements present in the `Tree`. - /// - /// # Example - /// - /// ```no_run - /// fn all_nodes(parent: &usvg::Group) { - /// for node in parent.children() { - /// // do stuff... - /// - /// if let usvg::Node::Group(ref g) = node { - /// all_nodes(g); - /// } - /// - /// // handle subroots as well - /// node.subroots(|subroot| all_nodes(subroot)); - /// } - /// } - /// ``` - pub fn subroots(&self, mut f: F) { - match self { - Node::Group(ref group) => group.subroots(&mut f), - Node::Path(ref path) => path.subroots(&mut f), - Node::Image(ref image) => image.subroots(&mut f), - Node::Text(ref text) => text.subroots(&mut f), - } - } -} - -/// A group container. -/// -/// The preprocessor will remove all groups that don't impact rendering. -/// Those that left is just an indicator that a new canvas should be created. -/// -/// `g` element in SVG. -#[derive(Clone, Debug)] -pub struct Group { - pub(crate) id: String, - pub(crate) transform: Transform, - pub(crate) abs_transform: Transform, - pub(crate) opacity: Opacity, - pub(crate) blend_mode: BlendMode, - pub(crate) isolate: bool, - pub(crate) clip_path: Option>, - /// Whether the group is a context element (i.e. a use node) - pub(crate) is_context_element: bool, - pub(crate) mask: Option>, - pub(crate) filters: Vec>, - pub(crate) bounding_box: Rect, - pub(crate) abs_bounding_box: Rect, - pub(crate) stroke_bounding_box: Rect, - pub(crate) abs_stroke_bounding_box: Rect, - pub(crate) layer_bounding_box: NonZeroRect, - pub(crate) abs_layer_bounding_box: NonZeroRect, - pub(crate) children: Vec, -} - -impl Group { - pub(crate) fn empty() -> Self { - let dummy = Rect::from_xywh(0.0, 0.0, 0.0, 0.0).unwrap(); - Group { - id: String::new(), - transform: Transform::default(), - abs_transform: Transform::default(), - opacity: Opacity::ONE, - blend_mode: BlendMode::Normal, - isolate: false, - clip_path: None, - mask: None, - filters: Vec::new(), - is_context_element: false, - bounding_box: dummy, - abs_bounding_box: dummy, - stroke_bounding_box: dummy, - abs_stroke_bounding_box: dummy, - layer_bounding_box: NonZeroRect::from_xywh(0.0, 0.0, 1.0, 1.0).unwrap(), - abs_layer_bounding_box: NonZeroRect::from_xywh(0.0, 0.0, 1.0, 1.0).unwrap(), - children: Vec::new(), - } - } - - /// Element's ID. - /// - /// Taken from the SVG itself. - /// Isn't automatically generated. - /// Can be empty. - pub fn id(&self) -> &str { - &self.id - } - - /// Element's transform. - /// - /// This is a relative transform. The one that is set via the `transform` attribute in SVG. - pub fn transform(&self) -> Transform { - self.transform - } - - /// Element's absolute transform. - /// - /// Contains all ancestors transforms including group's transform. - /// - /// Note that subroots, like clipPaths, masks and patterns, have their own root transform, - /// which isn't affected by the node that references this subroot. - pub fn abs_transform(&self) -> Transform { - self.abs_transform - } - - /// Group opacity. - /// - /// After the group is rendered we should combine - /// it with a parent group using the specified opacity. - pub fn opacity(&self) -> Opacity { - self.opacity - } - - /// Group blend mode. - /// - /// `mix-blend-mode` in SVG. - pub fn blend_mode(&self) -> BlendMode { - self.blend_mode - } - - /// Group isolation. - /// - /// `isolation` in SVG. - pub fn isolate(&self) -> bool { - self.isolate - } - - /// Element's clip path. - pub fn clip_path(&self) -> Option<&ClipPath> { - self.clip_path.as_deref() - } - - /// Element's mask. - pub fn mask(&self) -> Option<&Mask> { - self.mask.as_deref() - } - - /// Element's filters. - pub fn filters(&self) -> &[Arc] { - &self.filters - } - - /// Element's object bounding box. - /// - /// `objectBoundingBox` in SVG terms. Meaning it doesn't affected by parent transforms. - /// - /// Can be set to `None` in case of an empty group. - pub fn bounding_box(&self) -> Rect { - self.bounding_box - } - - /// Element's bounding box in canvas coordinates. - /// - /// `userSpaceOnUse` in SVG terms. - pub fn abs_bounding_box(&self) -> Rect { - self.abs_bounding_box - } - - /// Element's object bounding box including stroke. - /// - /// Similar to `bounding_box`, but includes stroke. - pub fn stroke_bounding_box(&self) -> Rect { - self.stroke_bounding_box - } - - /// Element's bounding box including stroke in user coordinates. - /// - /// Similar to `abs_bounding_box`, but includes stroke. - pub fn abs_stroke_bounding_box(&self) -> Rect { - self.abs_stroke_bounding_box - } - - /// Element's "layer" bounding box in object units. - /// - /// Conceptually, this is `stroke_bounding_box` expanded and/or clipped - /// by `filters_bounding_box`, but also including all the children. - /// This is the bounding box `resvg` will later use to allocate layers/pixmaps - /// during isolated groups rendering. - /// - /// Only groups have it, because only groups can have filters. - /// For other nodes layer bounding box is the same as stroke bounding box. - /// - /// Unlike other bounding boxes, cannot have zero size. - pub fn layer_bounding_box(&self) -> NonZeroRect { - self.layer_bounding_box - } - - /// Element's "layer" bounding box in canvas units. - pub fn abs_layer_bounding_box(&self) -> NonZeroRect { - self.abs_layer_bounding_box - } - - /// Group's children. - pub fn children(&self) -> &[Node] { - &self.children - } - - /// Checks if this group should be isolated during rendering. - pub fn should_isolate(&self) -> bool { - self.isolate - || self.opacity != Opacity::ONE - || self.clip_path.is_some() - || self.mask.is_some() - || !self.filters.is_empty() - || self.blend_mode != BlendMode::Normal // TODO: probably not needed? - } - - /// Returns `true` if the group has any children. - pub fn has_children(&self) -> bool { - !self.children.is_empty() - } - - /// Calculates a node's filter bounding box. - /// - /// Filters with `objectBoundingBox` and missing or zero `bounding_box` would be ignored. - /// - /// Note that a filter region can act like a clipping rectangle, - /// therefore this function can produce a bounding box smaller than `bounding_box`. - /// - /// Returns `None` when then group has no filters. - /// - /// This function is very fast, that's why we do not store this bbox as a `Group` field. - pub fn filters_bounding_box(&self) -> Option { - let mut full_region = BBox::default(); - for filter in &self.filters { - full_region = full_region.expand(filter.rect); - } - - full_region.to_non_zero_rect() - } - - fn subroots(&self, f: &mut dyn FnMut(&Group)) { - if let Some(ref clip) = self.clip_path { - f(&clip.root); - - if let Some(ref sub_clip) = clip.clip_path { - f(&sub_clip.root); - } - } - - if let Some(ref mask) = self.mask { - f(&mask.root); - - if let Some(ref sub_mask) = mask.mask { - f(&sub_mask.root); - } - } - - for filter in &self.filters { - for primitive in &filter.primitives { - if let filter::Kind::Image(ref image) = primitive.kind { - f(image.root()); - } - } - } - } -} - -/// Representation of the [`paint-order`] property. -/// -/// `usvg` will handle `markers` automatically, -/// therefore we provide only `fill` and `stroke` variants. -/// -/// [`paint-order`]: https://www.w3.org/TR/SVG2/painting.html#PaintOrder -#[derive(Clone, Copy, PartialEq, Debug)] -#[allow(missing_docs)] -pub enum PaintOrder { - FillAndStroke, - StrokeAndFill, -} - -impl Default for PaintOrder { - fn default() -> Self { - Self::FillAndStroke - } -} - -/// A path element. -#[derive(Clone, Debug)] -pub struct Path { - pub(crate) id: String, - pub(crate) visible: bool, - pub(crate) fill: Option, - pub(crate) stroke: Option, - pub(crate) paint_order: PaintOrder, - pub(crate) rendering_mode: ShapeRendering, - pub(crate) data: Arc, - pub(crate) abs_transform: Transform, - pub(crate) bounding_box: Rect, - pub(crate) abs_bounding_box: Rect, - pub(crate) stroke_bounding_box: Rect, - pub(crate) abs_stroke_bounding_box: Rect, -} - -impl Path { - pub(crate) fn new_simple(data: Arc) -> Option { - Self::new( - String::new(), - true, - None, - None, - PaintOrder::default(), - ShapeRendering::default(), - data, - Transform::default(), - ) - } - - pub(crate) fn new( - id: String, - visible: bool, - fill: Option, - stroke: Option, - paint_order: PaintOrder, - rendering_mode: ShapeRendering, - data: Arc, - abs_transform: Transform, - ) -> Option { - let bounding_box = data.compute_tight_bounds()?; - let stroke_bounding_box = - Path::calculate_stroke_bbox(stroke.as_ref(), &data).unwrap_or(bounding_box); - - let abs_bounding_box: Rect; - let abs_stroke_bounding_box: Rect; - if abs_transform.has_skew() { - // TODO: avoid re-alloc - let path2 = data.as_ref().clone(); - let path2 = path2.transform(abs_transform)?; - abs_bounding_box = path2.compute_tight_bounds()?; - abs_stroke_bounding_box = - Path::calculate_stroke_bbox(stroke.as_ref(), &path2).unwrap_or(abs_bounding_box); - } else { - // A transform without a skew can be performed just on a bbox. - abs_bounding_box = bounding_box.transform(abs_transform)?; - abs_stroke_bounding_box = stroke_bounding_box.transform(abs_transform)?; - } - - Some(Path { - id, - visible, - fill, - stroke, - paint_order, - rendering_mode, - data, - abs_transform, - bounding_box, - abs_bounding_box, - stroke_bounding_box, - abs_stroke_bounding_box, - }) - } - - /// Element's ID. - /// - /// Taken from the SVG itself. - /// Isn't automatically generated. - /// Can be empty. - pub fn id(&self) -> &str { - &self.id - } - - /// Element visibility. - pub fn is_visible(&self) -> bool { - self.visible - } - - /// Fill style. - pub fn fill(&self) -> Option<&Fill> { - self.fill.as_ref() - } - - /// Stroke style. - pub fn stroke(&self) -> Option<&Stroke> { - self.stroke.as_ref() - } - - /// Fill and stroke paint order. - /// - /// Since markers will be replaced with regular nodes automatically, - /// `usvg` doesn't provide the `markers` order type. It's was already done. - /// - /// `paint-order` in SVG. - pub fn paint_order(&self) -> PaintOrder { - self.paint_order - } - - /// Rendering mode. - /// - /// `shape-rendering` in SVG. - pub fn rendering_mode(&self) -> ShapeRendering { - self.rendering_mode - } - - // TODO: find a better name - /// Segments list. - /// - /// All segments are in absolute coordinates. - pub fn data(&self) -> &tiny_skia_path::Path { - self.data.as_ref() - } - - /// Element's absolute transform. - /// - /// Contains all ancestors transforms including elements's transform. - /// - /// Note that this is not the relative transform present in SVG. - /// The SVG one would be set only on groups. - pub fn abs_transform(&self) -> Transform { - self.abs_transform - } - - /// Element's object bounding box. - /// - /// `objectBoundingBox` in SVG terms. Meaning it doesn't affected by parent transforms. - pub fn bounding_box(&self) -> Rect { - self.bounding_box - } - - /// Element's bounding box in canvas coordinates. - /// - /// `userSpaceOnUse` in SVG terms. - pub fn abs_bounding_box(&self) -> Rect { - self.abs_bounding_box - } - - /// Element's object bounding box including stroke. - /// - /// Will have the same value as `bounding_box` when path has no stroke. - pub fn stroke_bounding_box(&self) -> Rect { - self.stroke_bounding_box - } - - /// Element's bounding box including stroke in canvas coordinates. - /// - /// Will have the same value as `abs_bounding_box` when path has no stroke. - pub fn abs_stroke_bounding_box(&self) -> Rect { - self.abs_stroke_bounding_box - } - - fn calculate_stroke_bbox(stroke: Option<&Stroke>, path: &tiny_skia_path::Path) -> Option { - let mut stroke = stroke?.to_tiny_skia(); - // According to the spec, dash should not be accounted during bbox calculation. - stroke.dash = None; - - // TODO: avoid for round and bevel caps - - // Expensive, but there is not much we can do about it. - if let Some(stroked_path) = path.stroke(&stroke, 1.0) { - return stroked_path.compute_tight_bounds(); - } - - None - } - - fn subroots(&self, f: &mut dyn FnMut(&Group)) { - if let Some(Paint::Pattern(ref patt)) = self.fill.as_ref().map(|f| &f.paint) { - f(patt.root()) - } - if let Some(Paint::Pattern(ref patt)) = self.stroke.as_ref().map(|f| &f.paint) { - f(patt.root()) - } - } -} - -/// An embedded image kind. -#[derive(Clone)] -pub enum ImageKind { - /// A reference to raw JPEG data. Should be decoded by the caller. - JPEG(Arc>), - /// A reference to raw PNG data. Should be decoded by the caller. - PNG(Arc>), - /// A reference to raw GIF data. Should be decoded by the caller. - GIF(Arc>), - /// A reference to raw WebP data. Should be decoded by the caller. - WEBP(Arc>), - /// A preprocessed SVG tree. Can be rendered as is. - SVG(Tree), -} - -impl ImageKind { - pub(crate) fn actual_size(&self) -> Option { - match self { - ImageKind::JPEG(ref data) - | ImageKind::PNG(ref data) - | ImageKind::GIF(ref data) - | ImageKind::WEBP(ref data) => imagesize::blob_size(data) - .ok() - .and_then(|size| Size::from_wh(size.width as f32, size.height as f32)) - .log_none(|| log::warn!("Image has an invalid size. Skipped.")), - ImageKind::SVG(ref svg) => Some(svg.size), - } - } -} - -impl std::fmt::Debug for ImageKind { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - ImageKind::JPEG(_) => f.write_str("ImageKind::JPEG(..)"), - ImageKind::PNG(_) => f.write_str("ImageKind::PNG(..)"), - ImageKind::GIF(_) => f.write_str("ImageKind::GIF(..)"), - ImageKind::WEBP(_) => f.write_str("ImageKind::WEBP(..)"), - ImageKind::SVG(_) => f.write_str("ImageKind::SVG(..)"), - } - } -} - -/// A raster image element. -/// -/// `image` element in SVG. -#[derive(Clone, Debug)] -pub struct Image { - pub(crate) id: String, - pub(crate) visible: bool, - pub(crate) size: Size, - pub(crate) rendering_mode: ImageRendering, - pub(crate) kind: ImageKind, - pub(crate) abs_transform: Transform, - pub(crate) abs_bounding_box: NonZeroRect, -} - -impl Image { - /// Element's ID. - /// - /// Taken from the SVG itself. - /// Isn't automatically generated. - /// Can be empty. - pub fn id(&self) -> &str { - &self.id - } - - /// Element visibility. - pub fn is_visible(&self) -> bool { - self.visible - } - - /// The actual image size. - /// - /// This is not `width` and `height` attributes, - /// but rather the actual PNG/JPEG/GIF/SVG image size. - pub fn size(&self) -> Size { - self.size - } - - /// Rendering mode. - /// - /// `image-rendering` in SVG. - pub fn rendering_mode(&self) -> ImageRendering { - self.rendering_mode - } - - /// Image data. - pub fn kind(&self) -> &ImageKind { - &self.kind - } - - /// Element's absolute transform. - /// - /// Contains all ancestors transforms including elements's transform. - /// - /// Note that this is not the relative transform present in SVG. - /// The SVG one would be set only on groups. - pub fn abs_transform(&self) -> Transform { - self.abs_transform - } - - /// Element's object bounding box. - /// - /// `objectBoundingBox` in SVG terms. Meaning it doesn't affected by parent transforms. - pub fn bounding_box(&self) -> Rect { - self.size.to_rect(0.0, 0.0).unwrap() - } - - /// Element's bounding box in canvas coordinates. - /// - /// `userSpaceOnUse` in SVG terms. - pub fn abs_bounding_box(&self) -> Rect { - self.abs_bounding_box.to_rect() - } - - fn subroots(&self, f: &mut dyn FnMut(&Group)) { - if let ImageKind::SVG(ref tree) = self.kind { - f(&tree.root) - } - } -} - -/// A nodes tree container. -#[allow(missing_debug_implementations)] -#[derive(Clone, Debug)] -pub struct Tree { - pub(crate) size: Size, - pub(crate) root: Group, - pub(crate) linear_gradients: Vec>, - pub(crate) radial_gradients: Vec>, - pub(crate) patterns: Vec>, - pub(crate) clip_paths: Vec>, - pub(crate) masks: Vec>, - pub(crate) filters: Vec>, - #[cfg(feature = "text")] - pub(crate) fontdb: Arc, -} - -impl Tree { - /// Image size. - /// - /// Size of an image that should be created to fit the SVG. - /// - /// `width` and `height` in SVG. - pub fn size(&self) -> Size { - self.size - } - - /// The root element of the SVG tree. - pub fn root(&self) -> &Group { - &self.root - } - - /// Returns a renderable node by ID. - /// - /// If an empty ID is provided, than this method will always return `None`. - pub fn node_by_id(&self, id: &str) -> Option<&Node> { - if id.is_empty() { - return None; - } - - node_by_id(&self.root, id) - } - - /// Checks if the current tree has any text nodes. - pub fn has_text_nodes(&self) -> bool { - has_text_nodes(&self.root) - } - - /// Returns a list of all unique [`LinearGradient`]s in the tree. - pub fn linear_gradients(&self) -> &[Arc] { - &self.linear_gradients - } - - /// Returns a list of all unique [`RadialGradient`]s in the tree. - pub fn radial_gradients(&self) -> &[Arc] { - &self.radial_gradients - } - - /// Returns a list of all unique [`Pattern`]s in the tree. - pub fn patterns(&self) -> &[Arc] { - &self.patterns - } - - /// Returns a list of all unique [`ClipPath`]s in the tree. - pub fn clip_paths(&self) -> &[Arc] { - &self.clip_paths - } - - /// Returns a list of all unique [`Mask`]s in the tree. - pub fn masks(&self) -> &[Arc] { - &self.masks - } - - /// Returns a list of all unique [`Filter`](filter::Filter)s in the tree. - pub fn filters(&self) -> &[Arc] { - &self.filters - } - - /// Returns the font database that applies to all text nodes in the tree. - #[cfg(feature = "text")] - pub fn fontdb(&self) -> &Arc { - &self.fontdb - } - - pub(crate) fn collect_paint_servers(&mut self) { - loop_over_paint_servers(&self.root, &mut |paint| match paint { - Paint::Color(_) => {} - Paint::LinearGradient(lg) => { - if !self - .linear_gradients - .iter() - .any(|other| Arc::ptr_eq(&lg, other)) - { - self.linear_gradients.push(lg.clone()); - } - } - Paint::RadialGradient(rg) => { - if !self - .radial_gradients - .iter() - .any(|other| Arc::ptr_eq(&rg, other)) - { - self.radial_gradients.push(rg.clone()); - } - } - Paint::Pattern(patt) => { - if !self.patterns.iter().any(|other| Arc::ptr_eq(&patt, other)) { - self.patterns.push(patt.clone()); - } - } - }); - } -} - -fn node_by_id<'a>(parent: &'a Group, id: &str) -> Option<&'a Node> { - for child in &parent.children { - if child.id() == id { - return Some(child); - } - - if let Node::Group(ref g) = child { - if let Some(n) = node_by_id(g, id) { - return Some(n); - } - } - } - - None -} - -fn has_text_nodes(root: &Group) -> bool { - for node in &root.children { - if let Node::Text(_) = node { - return true; - } - - let mut has_text = false; - - if let Node::Image(ref image) = node { - if let ImageKind::SVG(ref tree) = image.kind { - if has_text_nodes(&tree.root) { - has_text = true; - } - } - } - - node.subroots(|subroot| has_text |= has_text_nodes(subroot)); - - if has_text { - return true; - } - } - - true -} - -fn loop_over_paint_servers(parent: &Group, f: &mut dyn FnMut(&Paint)) { - fn push(paint: Option<&Paint>, f: &mut dyn FnMut(&Paint)) { - if let Some(paint) = paint { - f(paint); - } - } - - for node in &parent.children { - match node { - Node::Group(ref group) => loop_over_paint_servers(group, f), - Node::Path(ref path) => { - push(path.fill.as_ref().map(|f| &f.paint), f); - push(path.stroke.as_ref().map(|f| &f.paint), f); - } - Node::Image(_) => {} - // Flattened text would be used instead. - Node::Text(_) => {} - } - - node.subroots(|subroot| loop_over_paint_servers(subroot, f)); - } -} - -impl Group { - pub(crate) fn collect_clip_paths(&self, clip_paths: &mut Vec>) { - for node in self.children() { - if let Node::Group(ref g) = node { - if let Some(ref clip) = g.clip_path { - if !clip_paths.iter().any(|other| Arc::ptr_eq(&clip, other)) { - clip_paths.push(clip.clone()); - } - - if let Some(ref sub_clip) = clip.clip_path { - if !clip_paths.iter().any(|other| Arc::ptr_eq(&sub_clip, other)) { - clip_paths.push(sub_clip.clone()); - } - } - } - } - - node.subroots(|subroot| subroot.collect_clip_paths(clip_paths)); - - if let Node::Group(ref g) = node { - g.collect_clip_paths(clip_paths); - } - } - } - - pub(crate) fn collect_masks(&self, masks: &mut Vec>) { - for node in self.children() { - if let Node::Group(ref g) = node { - if let Some(ref mask) = g.mask { - if !masks.iter().any(|other| Arc::ptr_eq(&mask, other)) { - masks.push(mask.clone()); - } - - if let Some(ref sub_mask) = mask.mask { - if !masks.iter().any(|other| Arc::ptr_eq(&sub_mask, other)) { - masks.push(sub_mask.clone()); - } - } - } - } - - node.subroots(|subroot| subroot.collect_masks(masks)); - - if let Node::Group(ref g) = node { - g.collect_masks(masks); - } - } - } - - pub(crate) fn collect_filters(&self, filters: &mut Vec>) { - for node in self.children() { - if let Node::Group(ref g) = node { - for filter in g.filters() { - if !filters.iter().any(|other| Arc::ptr_eq(&filter, other)) { - filters.push(filter.clone()); - } - } - } - - node.subroots(|subroot| subroot.collect_filters(filters)); - - if let Node::Group(ref g) = node { - g.collect_filters(filters); - } - } - } - - pub(crate) fn calculate_object_bbox(&mut self) -> Option { - let mut bbox = BBox::default(); - for child in &self.children { - let mut c_bbox = child.bounding_box(); - if let Node::Group(ref group) = child { - if let Some(r) = c_bbox.transform(group.transform) { - c_bbox = r; - } - } - - bbox = bbox.expand(c_bbox); - } - - bbox.to_non_zero_rect() - } - - pub(crate) fn calculate_bounding_boxes(&mut self) -> Option<()> { - let mut bbox = BBox::default(); - let mut abs_bbox = BBox::default(); - let mut stroke_bbox = BBox::default(); - let mut abs_stroke_bbox = BBox::default(); - let mut layer_bbox = BBox::default(); - for child in &self.children { - { - let mut c_bbox = child.bounding_box(); - if let Node::Group(ref group) = child { - if let Some(r) = c_bbox.transform(group.transform) { - c_bbox = r; - } - } - - bbox = bbox.expand(c_bbox); - } - - abs_bbox = abs_bbox.expand(child.abs_bounding_box()); - - { - let mut c_bbox = child.stroke_bounding_box(); - if let Node::Group(ref group) = child { - if let Some(r) = c_bbox.transform(group.transform) { - c_bbox = r; - } - } - - stroke_bbox = stroke_bbox.expand(c_bbox); - } - - abs_stroke_bbox = abs_stroke_bbox.expand(child.abs_stroke_bounding_box()); - - if let Node::Group(ref group) = child { - let r = group.layer_bounding_box; - if let Some(r) = r.transform(group.transform) { - layer_bbox = layer_bbox.expand(r); - } - } else { - // Not a group - no need to transform. - layer_bbox = layer_bbox.expand(child.stroke_bounding_box()); - } - } - - // `bbox` can be None for empty groups, but we still have to - // calculate `layer_bounding_box after` it. - if let Some(bbox) = bbox.to_rect() { - self.bounding_box = bbox; - self.abs_bounding_box = abs_bbox.to_rect()?; - self.stroke_bounding_box = stroke_bbox.to_rect()?; - self.abs_stroke_bounding_box = abs_stroke_bbox.to_rect()?; - } - - // Filter bbox has a higher priority than layers bbox. - if let Some(filter_bbox) = self.filters_bounding_box() { - self.layer_bounding_box = filter_bbox; - } else { - self.layer_bounding_box = layer_bbox.to_non_zero_rect()?; - } - - self.abs_layer_bounding_box = self.layer_bounding_box.transform(self.abs_transform)?; - - Some(()) - } -} diff --git a/resvg-0.43.0/crates/usvg/src/tree/text.rs b/resvg-0.43.0/crates/usvg/src/tree/text.rs deleted file mode 100644 index 64903fa..0000000 --- a/resvg-0.43.0/crates/usvg/src/tree/text.rs +++ /dev/null @@ -1,599 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use std::sync::Arc; - -use strict_num::NonZeroPositiveF32; -pub use svgtypes::FontFamily; - -#[cfg(feature = "text")] -use crate::layout::Span; -use crate::{Fill, Group, NonEmptyString, PaintOrder, Rect, Stroke, TextRendering, Transform}; - -/// A font stretch property. -#[allow(missing_docs)] -#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)] -pub enum FontStretch { - UltraCondensed, - ExtraCondensed, - Condensed, - SemiCondensed, - Normal, - SemiExpanded, - Expanded, - ExtraExpanded, - UltraExpanded, -} - -impl Default for FontStretch { - #[inline] - fn default() -> Self { - Self::Normal - } -} - -/// A font style property. -#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] -pub enum FontStyle { - /// A face that is neither italic not obliqued. - Normal, - /// A form that is generally cursive in nature. - Italic, - /// A typically-sloped version of the regular face. - Oblique, -} - -impl Default for FontStyle { - #[inline] - fn default() -> FontStyle { - Self::Normal - } -} - -/// Text font properties. -#[derive(Clone, Eq, PartialEq, Hash, Debug)] -pub struct Font { - pub(crate) families: Vec, - pub(crate) style: FontStyle, - pub(crate) stretch: FontStretch, - pub(crate) weight: u16, -} - -impl Font { - /// A list of family names. - /// - /// Never empty. Uses `usvg::Options::font_family` as fallback. - pub fn families(&self) -> &[FontFamily] { - &self.families - } - - /// A font style. - pub fn style(&self) -> FontStyle { - self.style - } - - /// A font stretch. - pub fn stretch(&self) -> FontStretch { - self.stretch - } - - /// A font width. - pub fn weight(&self) -> u16 { - self.weight - } -} - -/// A dominant baseline property. -#[allow(missing_docs)] -#[derive(Clone, Copy, PartialEq, Debug)] -pub enum DominantBaseline { - Auto, - UseScript, - NoChange, - ResetSize, - Ideographic, - Alphabetic, - Hanging, - Mathematical, - Central, - Middle, - TextAfterEdge, - TextBeforeEdge, -} - -impl Default for DominantBaseline { - fn default() -> Self { - Self::Auto - } -} - -/// An alignment baseline property. -#[allow(missing_docs)] -#[derive(Clone, Copy, PartialEq, Debug)] -pub enum AlignmentBaseline { - Auto, - Baseline, - BeforeEdge, - TextBeforeEdge, - Middle, - Central, - AfterEdge, - TextAfterEdge, - Ideographic, - Alphabetic, - Hanging, - Mathematical, -} - -impl Default for AlignmentBaseline { - fn default() -> Self { - Self::Auto - } -} - -/// A baseline shift property. -#[allow(missing_docs)] -#[derive(Clone, Copy, PartialEq, Debug)] -pub enum BaselineShift { - Baseline, - Subscript, - Superscript, - Number(f32), -} - -impl Default for BaselineShift { - #[inline] - fn default() -> BaselineShift { - BaselineShift::Baseline - } -} - -/// A length adjust property. -#[allow(missing_docs)] -#[derive(Clone, Copy, PartialEq, Debug)] -pub enum LengthAdjust { - Spacing, - SpacingAndGlyphs, -} - -impl Default for LengthAdjust { - fn default() -> Self { - Self::Spacing - } -} - -/// A text span decoration style. -/// -/// In SVG, text decoration and text it's applied to can have different styles. -/// So you can have black text and green underline. -/// -/// Also, in SVG you can specify text decoration stroking. -#[derive(Clone, Debug)] -pub struct TextDecorationStyle { - pub(crate) fill: Option, - pub(crate) stroke: Option, -} - -impl TextDecorationStyle { - /// A fill style. - pub fn fill(&self) -> Option<&Fill> { - self.fill.as_ref() - } - - /// A stroke style. - pub fn stroke(&self) -> Option<&Stroke> { - self.stroke.as_ref() - } -} - -/// A text span decoration. -#[derive(Clone, Debug)] -pub struct TextDecoration { - pub(crate) underline: Option, - pub(crate) overline: Option, - pub(crate) line_through: Option, -} - -impl TextDecoration { - /// An optional underline and its style. - pub fn underline(&self) -> Option<&TextDecorationStyle> { - self.underline.as_ref() - } - - /// An optional overline and its style. - pub fn overline(&self) -> Option<&TextDecorationStyle> { - self.overline.as_ref() - } - - /// An optional line-through and its style. - pub fn line_through(&self) -> Option<&TextDecorationStyle> { - self.line_through.as_ref() - } -} - -/// A text style span. -/// -/// Spans do not overlap inside a text chunk. -#[derive(Clone, Debug)] -pub struct TextSpan { - pub(crate) start: usize, - pub(crate) end: usize, - pub(crate) fill: Option, - pub(crate) stroke: Option, - pub(crate) paint_order: PaintOrder, - pub(crate) font: Font, - pub(crate) font_size: NonZeroPositiveF32, - pub(crate) small_caps: bool, - pub(crate) apply_kerning: bool, - pub(crate) decoration: TextDecoration, - pub(crate) dominant_baseline: DominantBaseline, - pub(crate) alignment_baseline: AlignmentBaseline, - pub(crate) baseline_shift: Vec, - pub(crate) visible: bool, - pub(crate) letter_spacing: f32, - pub(crate) word_spacing: f32, - pub(crate) text_length: Option, - pub(crate) length_adjust: LengthAdjust, -} - -impl TextSpan { - /// A span start in bytes. - /// - /// Offset is relative to the parent text chunk and not the parent text element. - pub fn start(&self) -> usize { - self.start - } - - /// A span end in bytes. - /// - /// Offset is relative to the parent text chunk and not the parent text element. - pub fn end(&self) -> usize { - self.end - } - - /// A fill style. - pub fn fill(&self) -> Option<&Fill> { - self.fill.as_ref() - } - - /// A stroke style. - pub fn stroke(&self) -> Option<&Stroke> { - self.stroke.as_ref() - } - - /// A paint order style. - pub fn paint_order(&self) -> PaintOrder { - self.paint_order - } - - /// A font. - pub fn font(&self) -> &Font { - &self.font - } - - /// A font size. - pub fn font_size(&self) -> NonZeroPositiveF32 { - self.font_size - } - - /// Indicates that small caps should be used. - /// - /// Set by `font-variant="small-caps"` - pub fn small_caps(&self) -> bool { - self.small_caps - } - - /// Indicates that a kerning should be applied. - /// - /// Supports both `kerning` and `font-kerning` properties. - pub fn apply_kerning(&self) -> bool { - self.apply_kerning - } - - /// A span decorations. - pub fn decoration(&self) -> &TextDecoration { - &self.decoration - } - - /// A span dominant baseline. - pub fn dominant_baseline(&self) -> DominantBaseline { - self.dominant_baseline - } - - /// A span alignment baseline. - pub fn alignment_baseline(&self) -> AlignmentBaseline { - self.alignment_baseline - } - - /// A list of all baseline shift that should be applied to this span. - /// - /// Ordered from `text` element down to the actual `span` element. - pub fn baseline_shift(&self) -> &[BaselineShift] { - &self.baseline_shift - } - - /// A visibility property. - pub fn is_visible(&self) -> bool { - self.visible - } - - /// A letter spacing property. - pub fn letter_spacing(&self) -> f32 { - self.letter_spacing - } - - /// A word spacing property. - pub fn word_spacing(&self) -> f32 { - self.word_spacing - } - - /// A text length property. - pub fn text_length(&self) -> Option { - self.text_length - } - - /// A length adjust property. - pub fn length_adjust(&self) -> LengthAdjust { - self.length_adjust - } -} - -/// A text chunk anchor property. -#[allow(missing_docs)] -#[derive(Clone, Copy, PartialEq, Debug)] -pub enum TextAnchor { - Start, - Middle, - End, -} - -impl Default for TextAnchor { - fn default() -> Self { - Self::Start - } -} - -/// A path used by text-on-path. -#[derive(Debug)] -pub struct TextPath { - pub(crate) id: NonEmptyString, - pub(crate) start_offset: f32, - pub(crate) path: Arc, -} - -impl TextPath { - /// Element's ID. - /// - /// Taken from the SVG itself. - pub fn id(&self) -> &str { - self.id.get() - } - - /// A text offset in SVG coordinates. - /// - /// Percentage values already resolved. - pub fn start_offset(&self) -> f32 { - self.start_offset - } - - /// A path. - pub fn path(&self) -> &tiny_skia_path::Path { - &self.path - } -} - -/// A text chunk flow property. -#[derive(Clone, Debug)] -pub enum TextFlow { - /// A linear layout. - /// - /// Includes left-to-right, right-to-left and top-to-bottom. - Linear, - /// A text-on-path layout. - Path(Arc), -} - -/// A text chunk. -/// -/// Text alignment and BIDI reordering can only be done inside a text chunk. -#[derive(Clone, Debug)] -pub struct TextChunk { - pub(crate) x: Option, - pub(crate) y: Option, - pub(crate) anchor: TextAnchor, - pub(crate) spans: Vec, - pub(crate) text_flow: TextFlow, - pub(crate) text: String, -} - -impl TextChunk { - /// An absolute X axis offset. - pub fn x(&self) -> Option { - self.x - } - - /// An absolute Y axis offset. - pub fn y(&self) -> Option { - self.y - } - - /// A text anchor. - pub fn anchor(&self) -> TextAnchor { - self.anchor - } - - /// A list of text chunk style spans. - pub fn spans(&self) -> &[TextSpan] { - &self.spans - } - - /// A text chunk flow. - pub fn text_flow(&self) -> TextFlow { - self.text_flow.clone() - } - - /// A text chunk actual text. - pub fn text(&self) -> &str { - &self.text - } -} - -/// A writing mode. -#[allow(missing_docs)] -#[derive(Clone, Copy, PartialEq, Debug)] -pub enum WritingMode { - LeftToRight, - TopToBottom, -} - -/// A text element. -/// -/// `text` element in SVG. -#[derive(Clone, Debug)] -pub struct Text { - pub(crate) id: String, - pub(crate) rendering_mode: TextRendering, - pub(crate) dx: Vec, - pub(crate) dy: Vec, - pub(crate) rotate: Vec, - pub(crate) writing_mode: WritingMode, - pub(crate) chunks: Vec, - pub(crate) abs_transform: Transform, - pub(crate) bounding_box: Rect, - pub(crate) abs_bounding_box: Rect, - pub(crate) stroke_bounding_box: Rect, - pub(crate) abs_stroke_bounding_box: Rect, - pub(crate) flattened: Box, - #[cfg(feature = "text")] - pub(crate) layouted: Vec, -} - -impl Text { - /// Element's ID. - /// - /// Taken from the SVG itself. - /// Isn't automatically generated. - /// Can be empty. - pub fn id(&self) -> &str { - &self.id - } - - /// Rendering mode. - /// - /// `text-rendering` in SVG. - pub fn rendering_mode(&self) -> TextRendering { - self.rendering_mode - } - - /// A relative X axis offsets. - /// - /// One offset for each Unicode codepoint. Aka `char` in Rust. - pub fn dx(&self) -> &[f32] { - &self.dx - } - - /// A relative Y axis offsets. - /// - /// One offset for each Unicode codepoint. Aka `char` in Rust. - pub fn dy(&self) -> &[f32] { - &self.dy - } - - /// A list of rotation angles. - /// - /// One angle for each Unicode codepoint. Aka `char` in Rust. - pub fn rotate(&self) -> &[f32] { - &self.rotate - } - - /// A writing mode. - pub fn writing_mode(&self) -> WritingMode { - self.writing_mode - } - - /// A list of text chunks. - pub fn chunks(&self) -> &[TextChunk] { - &self.chunks - } - - /// Element's absolute transform. - /// - /// Contains all ancestors transforms including elements's transform. - /// - /// Note that this is not the relative transform present in SVG. - /// The SVG one would be set only on groups. - pub fn abs_transform(&self) -> Transform { - self.abs_transform - } - - /// Element's text bounding box. - /// - /// Text bounding box is special in SVG and doesn't represent - /// tight bounds of the element's content. - /// You can find more about it - /// [here](https://razrfalcon.github.io/notes-on-svg-parsing/text/bbox.html). - /// - /// `objectBoundingBox` in SVG terms. Meaning it doesn't affected by parent transforms. - /// - /// Returns `None` when the `text` build feature was disabled. - /// This is because we have to perform a text layout before calculating a bounding box. - pub fn bounding_box(&self) -> Rect { - self.bounding_box - } - - /// Element's text bounding box in canvas coordinates. - /// - /// `userSpaceOnUse` in SVG terms. - pub fn abs_bounding_box(&self) -> Rect { - self.abs_bounding_box - } - - /// Element's object bounding box including stroke. - /// - /// Similar to `bounding_box`, but includes stroke. - /// - /// Will have the same value as `bounding_box` when path has no stroke. - pub fn stroke_bounding_box(&self) -> Rect { - self.stroke_bounding_box - } - - /// Element's bounding box including stroke in canvas coordinates. - pub fn abs_stroke_bounding_box(&self) -> Rect { - self.abs_stroke_bounding_box - } - - /// Text converted into paths, ready to render. - /// - /// Note that this is only a - /// "best-effort" attempt: The text will be converted into group/paths/image - /// primitives, so that they can be rendered with the existing infrastructure. - /// This process is in general lossless and should lead to correct output, with - /// two notable exceptions: - /// 1. For glyphs based on the `SVG` table, only glyphs that are pure SVG 1.1/2.0 - /// are supported. Glyphs that make use of features in the OpenType specification - /// that are not part of the original SVG specification are not supported. - /// 2. For glyphs based on the `COLR` table, there are a certain number of features - /// that are not (correctly) supported, such as conical - /// gradients, certain gradient transforms and some blend modes. But this shouldn't - /// cause any issues in 95% of the cases, as most of those are edge cases. - /// If the two above are not acceptable, then you will need to implement your own - /// glyph rendering logic based on the layouted glyphs (see the `layouted` method). - pub fn flattened(&self) -> &Group { - &self.flattened - } - - /// The positioned glyphs and decoration spans of the text. - /// - /// This should only be used if you need more low-level access - /// to the glyphs that make up the text. If you just need the - /// outlines of the text, you should use `flattened` instead. - #[cfg(feature = "text")] - pub fn layouted(&self) -> &[Span] { - &self.layouted - } - - pub(crate) fn subroots(&self, f: &mut dyn FnMut(&Group)) { - f(&self.flattened); - } -} diff --git a/resvg-0.43.0/crates/usvg/src/writer.rs b/resvg-0.43.0/crates/usvg/src/writer.rs deleted file mode 100644 index c36045f..0000000 --- a/resvg-0.43.0/crates/usvg/src/writer.rs +++ /dev/null @@ -1,1571 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use std::fmt::Display; -use std::io::Write; - -use svgtypes::{parse_font_families, FontFamily}; -use xmlwriter::XmlWriter; - -use crate::parser::{AId, EId}; -use crate::*; - -impl Tree { - /// Writes `usvg::Tree` back to SVG. - pub fn to_string(&self, opt: &WriteOptions) -> String { - convert(self, opt) - } -} - -/// Checks that type has a default value. -trait IsDefault: Default { - /// Checks that type has a default value. - fn is_default(&self) -> bool; -} - -impl IsDefault for T { - #[inline] - fn is_default(&self) -> bool { - *self == Self::default() - } -} - -/// XML writing options. -#[derive(Clone, Debug)] -pub struct WriteOptions { - /// Used to add a custom prefix to each element ID during writing. - pub id_prefix: Option, - - /// Do not convert text into paths. - /// - /// Default: false - pub preserve_text: bool, - - /// Set the coordinates numeric precision. - /// - /// Smaller precision can lead to a malformed output in some cases. - /// - /// Default: 8 - pub coordinates_precision: u8, - - /// Set the transform values numeric precision. - /// - /// Smaller precision can lead to a malformed output in some cases. - /// - /// Default: 8 - pub transforms_precision: u8, - - /// Use single quote marks instead of double quote. - /// - /// # Examples - /// - /// Before: - /// - /// ```text - /// - /// ``` - /// - /// After: - /// - /// ```text - /// - /// ``` - /// - /// Default: disabled - pub use_single_quote: bool, - - /// Set XML nodes indention. - /// - /// # Examples - /// - /// `Indent::None` - /// Before: - /// - /// ```text - /// - /// - /// - /// ``` - /// - /// After: - /// - /// ```text - /// - /// ``` - /// - /// Default: 4 spaces - pub indent: Indent, - - /// Set XML attributes indention. - /// - /// # Examples - /// - /// `Indent::Spaces(2)` - /// - /// Before: - /// - /// ```text - /// - /// - /// - /// ``` - /// - /// After: - /// - /// ```text - /// - /// - /// - /// ``` - /// - /// Default: `None` - pub attributes_indent: Indent, -} - -impl Default for WriteOptions { - fn default() -> Self { - Self { - id_prefix: Default::default(), - preserve_text: false, - coordinates_precision: 8, - transforms_precision: 8, - use_single_quote: false, - indent: Indent::Spaces(4), - attributes_indent: Indent::None, - } - } -} - -pub(crate) fn convert(tree: &Tree, opt: &WriteOptions) -> String { - let mut xml = XmlWriter::new(xmlwriter::Options { - use_single_quote: opt.use_single_quote, - indent: opt.indent, - attributes_indent: opt.attributes_indent, - }); - - xml.start_svg_element(EId::Svg); - xml.write_svg_attribute(AId::Width, &tree.size.width()); - xml.write_svg_attribute(AId::Height, &tree.size.height()); - xml.write_attribute("xmlns", "http://www.w3.org/2000/svg"); - if has_xlink(&tree.root) { - xml.write_attribute("xmlns:xlink", "http://www.w3.org/1999/xlink"); - } - - xml.start_svg_element(EId::Defs); - write_defs(tree, opt, &mut xml); - xml.end_element(); - - write_elements(&tree.root, false, opt, &mut xml); - - xml.end_document() -} - -fn write_filters(tree: &Tree, opt: &WriteOptions, xml: &mut XmlWriter) { - let mut written_fe_image_nodes: Vec = Vec::new(); - for filter in tree.filters() { - for fe in &filter.primitives { - if let filter::Kind::Image(ref img) = fe.kind { - if let Some(child) = img.root().children.first() { - if !written_fe_image_nodes.iter().any(|id| id == child.id()) { - write_element(child, false, opt, xml); - written_fe_image_nodes.push(child.id().to_string()); - } - } - } - } - - xml.start_svg_element(EId::Filter); - xml.write_id_attribute(filter.id(), opt); - xml.write_rect_attrs(filter.rect); - xml.write_units( - AId::FilterUnits, - Units::UserSpaceOnUse, - Units::ObjectBoundingBox, - ); - - for fe in &filter.primitives { - match fe.kind { - filter::Kind::DropShadow(ref shadow) => { - xml.start_svg_element(EId::FeDropShadow); - xml.write_filter_primitive_attrs(filter.rect(), fe); - xml.write_filter_input(AId::In, &shadow.input); - xml.write_attribute_fmt( - AId::StdDeviation.to_str(), - format_args!("{} {}", shadow.std_dev_x.get(), shadow.std_dev_y.get()), - ); - xml.write_svg_attribute(AId::Dx, &shadow.dx); - xml.write_svg_attribute(AId::Dy, &shadow.dy); - xml.write_color(AId::FloodColor, shadow.color); - xml.write_svg_attribute(AId::FloodOpacity, &shadow.opacity.get()); - xml.write_svg_attribute(AId::Result, &fe.result); - xml.end_element(); - } - filter::Kind::GaussianBlur(ref blur) => { - xml.start_svg_element(EId::FeGaussianBlur); - xml.write_filter_primitive_attrs(filter.rect(), fe); - xml.write_filter_input(AId::In, &blur.input); - xml.write_attribute_fmt( - AId::StdDeviation.to_str(), - format_args!("{} {}", blur.std_dev_x.get(), blur.std_dev_y.get()), - ); - xml.write_svg_attribute(AId::Result, &fe.result); - xml.end_element(); - } - filter::Kind::Offset(ref offset) => { - xml.start_svg_element(EId::FeOffset); - xml.write_filter_primitive_attrs(filter.rect(), fe); - xml.write_filter_input(AId::In, &offset.input); - xml.write_svg_attribute(AId::Dx, &offset.dx); - xml.write_svg_attribute(AId::Dy, &offset.dy); - xml.write_svg_attribute(AId::Result, &fe.result); - xml.end_element(); - } - filter::Kind::Blend(ref blend) => { - xml.start_svg_element(EId::FeBlend); - xml.write_filter_primitive_attrs(filter.rect(), fe); - xml.write_filter_input(AId::In, &blend.input1); - xml.write_filter_input(AId::In2, &blend.input2); - xml.write_svg_attribute( - AId::Mode, - match blend.mode { - BlendMode::Normal => "normal", - BlendMode::Multiply => "multiply", - BlendMode::Screen => "screen", - BlendMode::Overlay => "overlay", - BlendMode::Darken => "darken", - BlendMode::Lighten => "lighten", - BlendMode::ColorDodge => "color-dodge", - BlendMode::ColorBurn => "color-burn", - BlendMode::HardLight => "hard-light", - BlendMode::SoftLight => "soft-light", - BlendMode::Difference => "difference", - BlendMode::Exclusion => "exclusion", - BlendMode::Hue => "hue", - BlendMode::Saturation => "saturation", - BlendMode::Color => "color", - BlendMode::Luminosity => "luminosity", - }, - ); - xml.write_svg_attribute(AId::Result, &fe.result); - xml.end_element(); - } - filter::Kind::Flood(ref flood) => { - xml.start_svg_element(EId::FeFlood); - xml.write_filter_primitive_attrs(filter.rect(), fe); - xml.write_color(AId::FloodColor, flood.color); - xml.write_svg_attribute(AId::FloodOpacity, &flood.opacity.get()); - xml.write_svg_attribute(AId::Result, &fe.result); - xml.end_element(); - } - filter::Kind::Composite(ref composite) => { - xml.start_svg_element(EId::FeComposite); - xml.write_filter_primitive_attrs(filter.rect(), fe); - xml.write_filter_input(AId::In, &composite.input1); - xml.write_filter_input(AId::In2, &composite.input2); - xml.write_svg_attribute( - AId::Operator, - match composite.operator { - filter::CompositeOperator::Over => "over", - filter::CompositeOperator::In => "in", - filter::CompositeOperator::Out => "out", - filter::CompositeOperator::Atop => "atop", - filter::CompositeOperator::Xor => "xor", - filter::CompositeOperator::Arithmetic { .. } => "arithmetic", - }, - ); - - if let filter::CompositeOperator::Arithmetic { k1, k2, k3, k4 } = - composite.operator - { - xml.write_svg_attribute(AId::K1, &k1); - xml.write_svg_attribute(AId::K2, &k2); - xml.write_svg_attribute(AId::K3, &k3); - xml.write_svg_attribute(AId::K4, &k4); - } - - xml.write_svg_attribute(AId::Result, &fe.result); - xml.end_element(); - } - filter::Kind::Merge(ref merge) => { - xml.start_svg_element(EId::FeMerge); - xml.write_filter_primitive_attrs(filter.rect(), fe); - xml.write_svg_attribute(AId::Result, &fe.result); - for input in &merge.inputs { - xml.start_svg_element(EId::FeMergeNode); - xml.write_filter_input(AId::In, input); - xml.end_element(); - } - - xml.end_element(); - } - filter::Kind::Tile(ref tile) => { - xml.start_svg_element(EId::FeTile); - xml.write_filter_primitive_attrs(filter.rect(), fe); - xml.write_filter_input(AId::In, &tile.input); - xml.write_svg_attribute(AId::Result, &fe.result); - xml.end_element(); - } - filter::Kind::Image(ref img) => { - xml.start_svg_element(EId::FeImage); - xml.write_filter_primitive_attrs(filter.rect(), fe); - if let Some(child) = img.root.children.first() { - let prefix = opt.id_prefix.as_deref().unwrap_or_default(); - xml.write_attribute_fmt( - "xlink:href", - format_args!("#{}{}", prefix, child.id()), - ); - } - - xml.write_svg_attribute(AId::Result, &fe.result); - xml.end_element(); - } - filter::Kind::ComponentTransfer(ref transfer) => { - xml.start_svg_element(EId::FeComponentTransfer); - xml.write_filter_primitive_attrs(filter.rect(), fe); - xml.write_filter_input(AId::In, &transfer.input); - xml.write_svg_attribute(AId::Result, &fe.result); - - xml.write_filter_transfer_function(EId::FeFuncR, &transfer.func_r); - xml.write_filter_transfer_function(EId::FeFuncG, &transfer.func_g); - xml.write_filter_transfer_function(EId::FeFuncB, &transfer.func_b); - xml.write_filter_transfer_function(EId::FeFuncA, &transfer.func_a); - - xml.end_element(); - } - filter::Kind::ColorMatrix(ref matrix) => { - xml.start_svg_element(EId::FeColorMatrix); - xml.write_filter_primitive_attrs(filter.rect(), fe); - xml.write_filter_input(AId::In, &matrix.input); - xml.write_svg_attribute(AId::Result, &fe.result); - - match matrix.kind { - filter::ColorMatrixKind::Matrix(ref values) => { - xml.write_svg_attribute(AId::Type, "matrix"); - xml.write_numbers(AId::Values, values); - } - filter::ColorMatrixKind::Saturate(value) => { - xml.write_svg_attribute(AId::Type, "saturate"); - xml.write_svg_attribute(AId::Values, &value.get()); - } - filter::ColorMatrixKind::HueRotate(angle) => { - xml.write_svg_attribute(AId::Type, "hueRotate"); - xml.write_svg_attribute(AId::Values, &angle); - } - filter::ColorMatrixKind::LuminanceToAlpha => { - xml.write_svg_attribute(AId::Type, "luminanceToAlpha"); - } - } - - xml.end_element(); - } - filter::Kind::ConvolveMatrix(ref matrix) => { - xml.start_svg_element(EId::FeConvolveMatrix); - xml.write_filter_primitive_attrs(filter.rect(), fe); - xml.write_filter_input(AId::In, &matrix.input); - xml.write_svg_attribute(AId::Result, &fe.result); - - xml.write_attribute_fmt( - AId::Order.to_str(), - format_args!("{} {}", matrix.matrix.columns, matrix.matrix.rows), - ); - xml.write_numbers(AId::KernelMatrix, &matrix.matrix.data); - xml.write_svg_attribute(AId::Divisor, &matrix.divisor.get()); - xml.write_svg_attribute(AId::Bias, &matrix.bias); - xml.write_svg_attribute(AId::TargetX, &matrix.matrix.target_x); - xml.write_svg_attribute(AId::TargetY, &matrix.matrix.target_y); - xml.write_svg_attribute( - AId::EdgeMode, - match matrix.edge_mode { - filter::EdgeMode::None => "none", - filter::EdgeMode::Duplicate => "duplicate", - filter::EdgeMode::Wrap => "wrap", - }, - ); - xml.write_svg_attribute( - AId::PreserveAlpha, - if matrix.preserve_alpha { - "true" - } else { - "false" - }, - ); - - xml.end_element(); - } - filter::Kind::Morphology(ref morphology) => { - xml.start_svg_element(EId::FeMorphology); - xml.write_filter_primitive_attrs(filter.rect(), fe); - xml.write_filter_input(AId::In, &morphology.input); - xml.write_svg_attribute(AId::Result, &fe.result); - - xml.write_svg_attribute( - AId::Operator, - match morphology.operator { - filter::MorphologyOperator::Erode => "erode", - filter::MorphologyOperator::Dilate => "dilate", - }, - ); - xml.write_attribute_fmt( - AId::Radius.to_str(), - format_args!( - "{} {}", - morphology.radius_x.get(), - morphology.radius_y.get() - ), - ); - - xml.end_element(); - } - filter::Kind::DisplacementMap(ref map) => { - xml.start_svg_element(EId::FeDisplacementMap); - xml.write_filter_primitive_attrs(filter.rect(), fe); - xml.write_filter_input(AId::In, &map.input1); - xml.write_filter_input(AId::In2, &map.input2); - xml.write_svg_attribute(AId::Result, &fe.result); - - xml.write_svg_attribute(AId::Scale, &map.scale); - - let mut write_channel = |c, aid| { - xml.write_svg_attribute( - aid, - match c { - filter::ColorChannel::R => "R", - filter::ColorChannel::G => "G", - filter::ColorChannel::B => "B", - filter::ColorChannel::A => "A", - }, - ); - }; - write_channel(map.x_channel_selector, AId::XChannelSelector); - write_channel(map.y_channel_selector, AId::YChannelSelector); - - xml.end_element(); - } - filter::Kind::Turbulence(ref turbulence) => { - xml.start_svg_element(EId::FeTurbulence); - xml.write_filter_primitive_attrs(filter.rect(), fe); - xml.write_svg_attribute(AId::Result, &fe.result); - - xml.write_attribute_fmt( - AId::BaseFrequency.to_str(), - format_args!( - "{} {}", - turbulence.base_frequency_x.get(), - turbulence.base_frequency_y.get() - ), - ); - xml.write_svg_attribute(AId::NumOctaves, &turbulence.num_octaves); - xml.write_svg_attribute(AId::Seed, &turbulence.seed); - xml.write_svg_attribute( - AId::StitchTiles, - match turbulence.stitch_tiles { - true => "stitch", - false => "noStitch", - }, - ); - xml.write_svg_attribute( - AId::Type, - match turbulence.kind { - filter::TurbulenceKind::FractalNoise => "fractalNoise", - filter::TurbulenceKind::Turbulence => "turbulence", - }, - ); - - xml.end_element(); - } - filter::Kind::DiffuseLighting(ref light) => { - xml.start_svg_element(EId::FeDiffuseLighting); - xml.write_filter_primitive_attrs(filter.rect(), fe); - xml.write_svg_attribute(AId::Result, &fe.result); - - xml.write_svg_attribute(AId::SurfaceScale, &light.surface_scale); - xml.write_svg_attribute(AId::DiffuseConstant, &light.diffuse_constant); - xml.write_color(AId::LightingColor, light.lighting_color); - write_light_source(&light.light_source, xml); - - xml.end_element(); - } - filter::Kind::SpecularLighting(ref light) => { - xml.start_svg_element(EId::FeSpecularLighting); - xml.write_filter_primitive_attrs(filter.rect(), fe); - xml.write_svg_attribute(AId::Result, &fe.result); - - xml.write_svg_attribute(AId::SurfaceScale, &light.surface_scale); - xml.write_svg_attribute(AId::SpecularConstant, &light.specular_constant); - xml.write_svg_attribute(AId::SpecularExponent, &light.specular_exponent); - xml.write_color(AId::LightingColor, light.lighting_color); - write_light_source(&light.light_source, xml); - - xml.end_element(); - } - }; - } - - xml.end_element(); - } -} - -fn write_defs(tree: &Tree, opt: &WriteOptions, xml: &mut XmlWriter) { - for lg in tree.linear_gradients() { - xml.start_svg_element(EId::LinearGradient); - xml.write_id_attribute(lg.id(), opt); - xml.write_svg_attribute(AId::X1, &lg.x1); - xml.write_svg_attribute(AId::Y1, &lg.y1); - xml.write_svg_attribute(AId::X2, &lg.x2); - xml.write_svg_attribute(AId::Y2, &lg.y2); - write_base_grad(&lg.base, opt, xml); - xml.end_element(); - } - - for rg in tree.radial_gradients() { - xml.start_svg_element(EId::RadialGradient); - xml.write_id_attribute(rg.id(), opt); - xml.write_svg_attribute(AId::Cx, &rg.cx); - xml.write_svg_attribute(AId::Cy, &rg.cy); - xml.write_svg_attribute(AId::R, &rg.r.get()); - xml.write_svg_attribute(AId::Fx, &rg.fx); - xml.write_svg_attribute(AId::Fy, &rg.fy); - write_base_grad(&rg.base, opt, xml); - xml.end_element(); - } - - for pattern in tree.patterns() { - xml.start_svg_element(EId::Pattern); - xml.write_id_attribute(pattern.id(), opt); - xml.write_rect_attrs(pattern.rect); - xml.write_units(AId::PatternUnits, pattern.units, Units::ObjectBoundingBox); - xml.write_units( - AId::PatternContentUnits, - pattern.content_units, - Units::UserSpaceOnUse, - ); - xml.write_transform(AId::PatternTransform, pattern.transform, opt); - - write_elements(&pattern.root, false, opt, xml); - - xml.end_element(); - } - - if tree.has_text_nodes() { - write_text_path_paths(&tree.root, opt, xml); - } - - write_filters(tree, opt, xml); - - for clip in tree.clip_paths() { - xml.start_svg_element(EId::ClipPath); - xml.write_id_attribute(clip.id(), opt); - xml.write_transform(AId::Transform, clip.transform, opt); - - if let Some(ref clip) = clip.clip_path { - xml.write_func_iri(AId::ClipPath, clip.id(), opt); - } - - write_elements(&clip.root, true, opt, xml); - - xml.end_element(); - } - - for mask in tree.masks() { - xml.start_svg_element(EId::Mask); - xml.write_id_attribute(mask.id(), opt); - if mask.kind == MaskType::Alpha { - xml.write_svg_attribute(AId::MaskType, "alpha"); - } - xml.write_units( - AId::MaskUnits, - Units::UserSpaceOnUse, - Units::ObjectBoundingBox, - ); - xml.write_rect_attrs(mask.rect); - - if let Some(ref mask) = mask.mask { - xml.write_func_iri(AId::Mask, mask.id(), opt); - } - - write_elements(&mask.root, false, opt, xml); - - xml.end_element(); - } -} - -fn write_text_path_paths(parent: &Group, opt: &WriteOptions, xml: &mut XmlWriter) { - for node in &parent.children { - if let Node::Group(ref group) = node { - write_text_path_paths(group, opt, xml); - } else if let Node::Text(ref text) = node { - for chunk in &text.chunks { - if let TextFlow::Path(ref text_path) = chunk.text_flow { - let path = Path::new( - text_path.id().to_string(), - true, - None, - None, - PaintOrder::default(), - ShapeRendering::default(), - text_path.path.clone(), - Transform::default(), - ); - if let Some(ref path) = path { - write_path(path, false, Transform::default(), None, opt, xml); - } - } - } - } - - node.subroots(|subroot| write_text_path_paths(subroot, opt, xml)); - } -} - -fn write_elements(parent: &Group, is_clip_path: bool, opt: &WriteOptions, xml: &mut XmlWriter) { - for n in &parent.children { - write_element(n, is_clip_path, opt, xml); - } -} - -fn write_element(node: &Node, is_clip_path: bool, opt: &WriteOptions, xml: &mut XmlWriter) { - match node { - Node::Path(ref p) => { - write_path(p, is_clip_path, Transform::default(), None, opt, xml); - } - Node::Image(ref img) => { - xml.start_svg_element(EId::Image); - if !img.id.is_empty() { - xml.write_id_attribute(&img.id, opt); - } - - xml.write_svg_attribute(AId::Width, &img.size().width()); - xml.write_svg_attribute(AId::Height, &img.size().height()); - - xml.write_visibility(img.visible); - - match img.rendering_mode { - ImageRendering::OptimizeQuality => {} - ImageRendering::OptimizeSpeed => { - xml.write_svg_attribute(AId::ImageRendering, "optimizeSpeed"); - } - } - - xml.write_image_data(&img.kind); - - xml.end_element(); - } - Node::Group(ref g) => { - write_group_element(g, is_clip_path, opt, xml); - } - Node::Text(ref text) => { - if opt.preserve_text { - xml.start_svg_element(EId::Text); - - if !text.id.is_empty() { - xml.write_id_attribute(&text.id, opt); - } - - xml.write_attribute("xml:space", "preserve"); - - match text.writing_mode { - WritingMode::LeftToRight => {} - WritingMode::TopToBottom => xml.write_svg_attribute(AId::WritingMode, "tb"), - } - - match text.rendering_mode { - TextRendering::OptimizeSpeed => { - xml.write_svg_attribute(AId::TextRendering, "optimizeSpeed") - } - TextRendering::GeometricPrecision => { - xml.write_svg_attribute(AId::TextRendering, "geometricPrecision") - } - TextRendering::OptimizeLegibility => {} - } - - if text.rotate.iter().any(|r| *r != 0.0) { - xml.write_numbers(AId::Rotate, &text.rotate); - } - - if text.dx.iter().any(|dx| *dx != 0.0) { - xml.write_numbers(AId::Dx, &text.dx); - } - - if text.dy.iter().any(|dy| *dy != 0.0) { - xml.write_numbers(AId::Dy, &text.dy); - } - - xml.set_preserve_whitespaces(true); - - for chunk in &text.chunks { - if let TextFlow::Path(text_path) = &chunk.text_flow { - xml.start_svg_element(EId::TextPath); - - let prefix = opt.id_prefix.as_deref().unwrap_or_default(); - xml.write_attribute_fmt( - "xlink:href", - format_args!("#{}{}", prefix, text_path.id()), - ); - - if text_path.start_offset != 0.0 { - xml.write_svg_attribute(AId::StartOffset, &text_path.start_offset); - } - } - - xml.start_svg_element(EId::Tspan); - - if let Some(x) = chunk.x { - xml.write_svg_attribute(AId::X, &x); - } - - if let Some(y) = chunk.y { - xml.write_svg_attribute(AId::Y, &y); - } - - match chunk.anchor { - TextAnchor::Start => {} - TextAnchor::Middle => xml.write_svg_attribute(AId::TextAnchor, "middle"), - TextAnchor::End => xml.write_svg_attribute(AId::TextAnchor, "end"), - } - - for span in &chunk.spans { - let decorations: Vec<_> = [ - ("underline", &span.decoration.underline), - ("line-through", &span.decoration.line_through), - ("overline", &span.decoration.overline), - ] - .iter() - .filter_map(|&(key, option_value)| { - option_value.as_ref().map(|value| (key, value)) - }) - .collect(); - - // Decorations need to be dumped BEFORE we write the actual span data - // (so that for example stroke color of span doesn't affect the text - // itself while baseline shifts need to be written after (since they are - // affected by the font size) - for (deco_name, deco) in &decorations { - xml.start_svg_element(EId::Tspan); - xml.write_svg_attribute(AId::TextDecoration, deco_name); - write_fill(&deco.fill, false, opt, xml); - write_stroke(&deco.stroke, opt, xml); - } - - write_span(is_clip_path, opt, xml, chunk, span); - - // End for each tspan we needed to create for decorations - for _ in &decorations { - xml.end_element(); - } - } - xml.end_element(); - - // End textPath element - if matches!(&chunk.text_flow, TextFlow::Path(_)) { - xml.end_element(); - } - } - - xml.end_element(); - xml.set_preserve_whitespaces(false); - } else { - write_group_element(text.flattened(), is_clip_path, opt, xml); - } - } - } -} - -fn write_group_element(g: &Group, is_clip_path: bool, opt: &WriteOptions, xml: &mut XmlWriter) { - if is_clip_path { - // The `clipPath` element in SVG doesn't allow groups, only shapes and text. - // The problem is that in `usvg` we can set a `clip-path` only on groups. - // So in cases when a `clipPath` child has a `clip-path` as well, - // it would be inside a group. And we have to skip this group during writing. - // - // Basically, the following SVG: - // - // - // - // - // - // will be represented in usvg as: - // - // - // - // - // - // - // - // - // Same with text. Text elements will be converted into groups, - // but only the group's children should be written. - for child in &g.children { - if let Node::Path(ref path) = child { - let clip_id = g.clip_path.as_ref().map(|cp| cp.id().to_string()); - write_path( - path, - is_clip_path, - g.transform, - clip_id.as_deref(), - opt, - xml, - ); - } - } - return; - } - - xml.start_svg_element(EId::G); - if !g.id.is_empty() { - xml.write_id_attribute(&g.id, opt); - }; - - if let Some(ref clip) = g.clip_path { - xml.write_func_iri(AId::ClipPath, clip.id(), opt); - } - - if let Some(ref mask) = g.mask { - xml.write_func_iri(AId::Mask, mask.id(), opt); - } - - if !g.filters.is_empty() { - let prefix = opt.id_prefix.as_deref().unwrap_or_default(); - let ids: Vec<_> = g - .filters - .iter() - .map(|filter| format!("url(#{}{})", prefix, filter.id())) - .collect(); - xml.write_svg_attribute(AId::Filter, &ids.join(" ")); - } - - if g.opacity != Opacity::ONE { - xml.write_svg_attribute(AId::Opacity, &g.opacity.get()); - } - - xml.write_transform(AId::Transform, g.transform, opt); - - if g.blend_mode != BlendMode::Normal || g.isolate { - let blend_mode = match g.blend_mode { - BlendMode::Normal => "normal", - BlendMode::Multiply => "multiply", - BlendMode::Screen => "screen", - BlendMode::Overlay => "overlay", - BlendMode::Darken => "darken", - BlendMode::Lighten => "lighten", - BlendMode::ColorDodge => "color-dodge", - BlendMode::ColorBurn => "color-burn", - BlendMode::HardLight => "hard-light", - BlendMode::SoftLight => "soft-light", - BlendMode::Difference => "difference", - BlendMode::Exclusion => "exclusion", - BlendMode::Hue => "hue", - BlendMode::Saturation => "saturation", - BlendMode::Color => "color", - BlendMode::Luminosity => "luminosity", - }; - - // For reasons unknown, `mix-blend-mode` and `isolation` must be written - // as `style` attribute. - let isolation = if g.isolate { "isolate" } else { "auto" }; - xml.write_attribute_fmt( - AId::Style.to_str(), - format_args!("mix-blend-mode:{};isolation:{}", blend_mode, isolation), - ); - } - - write_elements(g, false, opt, xml); - - xml.end_element(); -} - -trait XmlWriterExt { - fn start_svg_element(&mut self, id: EId); - fn write_svg_attribute(&mut self, id: AId, value: &V); - fn write_id_attribute(&mut self, id: &str, opt: &WriteOptions); - fn write_color(&mut self, id: AId, color: Color); - fn write_units(&mut self, id: AId, units: Units, def: Units); - fn write_transform(&mut self, id: AId, units: Transform, opt: &WriteOptions); - fn write_visibility(&mut self, value: bool); - fn write_func_iri(&mut self, aid: AId, id: &str, opt: &WriteOptions); - fn write_rect_attrs(&mut self, r: NonZeroRect); - fn write_numbers(&mut self, aid: AId, list: &[f32]); - fn write_image_data(&mut self, kind: &ImageKind); - fn write_filter_input(&mut self, id: AId, input: &filter::Input); - fn write_filter_primitive_attrs(&mut self, parent_rect: NonZeroRect, fe: &filter::Primitive); - fn write_filter_transfer_function(&mut self, eid: EId, fe: &filter::TransferFunction); -} - -impl XmlWriterExt for XmlWriter { - #[inline(never)] - fn start_svg_element(&mut self, id: EId) { - self.start_element(id.to_str()); - } - - #[inline(never)] - fn write_svg_attribute(&mut self, id: AId, value: &V) { - self.write_attribute(id.to_str(), value) - } - - #[inline(never)] - fn write_id_attribute(&mut self, id: &str, opt: &WriteOptions) { - debug_assert!(!id.is_empty()); - - if let Some(ref prefix) = opt.id_prefix { - let full_id = format!("{}{}", prefix, id); - self.write_attribute("id", &full_id); - } else { - self.write_attribute("id", id); - } - } - - #[inline(never)] - fn write_color(&mut self, id: AId, c: Color) { - static CHARS: &[u8] = b"0123456789abcdef"; - - #[inline] - fn int2hex(n: u8) -> (u8, u8) { - (CHARS[(n >> 4) as usize], CHARS[(n & 0xf) as usize]) - } - - let (r1, r2) = int2hex(c.red); - let (g1, g2) = int2hex(c.green); - let (b1, b2) = int2hex(c.blue); - - self.write_attribute_raw(id.to_str(), |buf| { - buf.extend_from_slice(&[b'#', r1, r2, g1, g2, b1, b2]) - }); - } - - // TODO: simplify - fn write_units(&mut self, id: AId, units: Units, def: Units) { - if units != def { - self.write_attribute( - id.to_str(), - match units { - Units::UserSpaceOnUse => "userSpaceOnUse", - Units::ObjectBoundingBox => "objectBoundingBox", - }, - ); - } - } - - fn write_transform(&mut self, id: AId, ts: Transform, opt: &WriteOptions) { - if !ts.is_default() { - self.write_attribute_raw(id.to_str(), |buf| { - buf.extend_from_slice(b"matrix("); - write_num(ts.sx, buf, opt.transforms_precision); - buf.push(b' '); - write_num(ts.ky, buf, opt.transforms_precision); - buf.push(b' '); - write_num(ts.kx, buf, opt.transforms_precision); - buf.push(b' '); - write_num(ts.sy, buf, opt.transforms_precision); - buf.push(b' '); - write_num(ts.tx, buf, opt.transforms_precision); - buf.push(b' '); - write_num(ts.ty, buf, opt.transforms_precision); - buf.extend_from_slice(b")"); - }); - } - } - - fn write_visibility(&mut self, value: bool) { - if !value { - self.write_attribute(AId::Visibility.to_str(), "hidden"); - } - } - - fn write_func_iri(&mut self, aid: AId, id: &str, opt: &WriteOptions) { - debug_assert!(!id.is_empty()); - let prefix = opt.id_prefix.as_deref().unwrap_or_default(); - self.write_attribute_fmt(aid.to_str(), format_args!("url(#{}{})", prefix, id)); - } - - fn write_rect_attrs(&mut self, r: NonZeroRect) { - self.write_svg_attribute(AId::X, &r.x()); - self.write_svg_attribute(AId::Y, &r.y()); - self.write_svg_attribute(AId::Width, &r.width()); - self.write_svg_attribute(AId::Height, &r.height()); - } - - fn write_numbers(&mut self, aid: AId, list: &[f32]) { - self.write_attribute_raw(aid.to_str(), |buf| { - for n in list { - buf.write_fmt(format_args!("{} ", n)).unwrap(); - } - - if !list.is_empty() { - buf.pop(); - } - }); - } - - fn write_filter_input(&mut self, id: AId, input: &filter::Input) { - self.write_attribute( - id.to_str(), - match input { - filter::Input::SourceGraphic => "SourceGraphic", - filter::Input::SourceAlpha => "SourceAlpha", - filter::Input::Reference(ref s) => s, - }, - ); - } - - fn write_filter_primitive_attrs(&mut self, parent_rect: NonZeroRect, fe: &filter::Primitive) { - if parent_rect.x() != fe.rect().x() { - self.write_svg_attribute(AId::X, &fe.rect().x()); - } - if parent_rect.y() != fe.rect().y() { - self.write_svg_attribute(AId::Y, &fe.rect().y()); - } - if parent_rect.width() != fe.rect().width() { - self.write_svg_attribute(AId::Width, &fe.rect().width()); - } - if parent_rect.height() != fe.rect().height() { - self.write_svg_attribute(AId::Height, &fe.rect().height()); - } - - self.write_attribute( - AId::ColorInterpolationFilters.to_str(), - match fe.color_interpolation { - filter::ColorInterpolation::SRGB => "sRGB", - filter::ColorInterpolation::LinearRGB => "linearRGB", - }, - ); - } - - fn write_filter_transfer_function(&mut self, eid: EId, fe: &filter::TransferFunction) { - self.start_svg_element(eid); - - match fe { - filter::TransferFunction::Identity => { - self.write_svg_attribute(AId::Type, "identity"); - } - filter::TransferFunction::Table(ref values) => { - self.write_svg_attribute(AId::Type, "table"); - self.write_numbers(AId::TableValues, values); - } - filter::TransferFunction::Discrete(ref values) => { - self.write_svg_attribute(AId::Type, "discrete"); - self.write_numbers(AId::TableValues, values); - } - filter::TransferFunction::Linear { slope, intercept } => { - self.write_svg_attribute(AId::Type, "linear"); - self.write_svg_attribute(AId::Slope, &slope); - self.write_svg_attribute(AId::Intercept, &intercept); - } - filter::TransferFunction::Gamma { - amplitude, - exponent, - offset, - } => { - self.write_svg_attribute(AId::Type, "gamma"); - self.write_svg_attribute(AId::Amplitude, &litude); - self.write_svg_attribute(AId::Exponent, &exponent); - self.write_svg_attribute(AId::Offset, &offset); - } - } - - self.end_element(); - } - - fn write_image_data(&mut self, kind: &ImageKind) { - let svg_string; - let (mime, data) = match kind { - ImageKind::JPEG(ref data) => ("jpeg", data.as_slice()), - ImageKind::PNG(ref data) => ("png", data.as_slice()), - ImageKind::GIF(ref data) => ("gif", data.as_slice()), - ImageKind::WEBP(ref data) => ("webp", data.as_slice()), - ImageKind::SVG(ref tree) => { - svg_string = tree.to_string(&WriteOptions::default()); - ("svg+xml", svg_string.as_bytes()) - } - }; - - self.write_attribute_raw("xlink:href", |buf| { - buf.extend_from_slice(b"data:image/"); - buf.extend_from_slice(mime.as_bytes()); - buf.extend_from_slice(b";base64, "); - - let mut enc = - base64::write::EncoderWriter::new(buf, &base64::engine::general_purpose::STANDARD); - enc.write_all(data).unwrap(); - enc.finish().unwrap(); - }); - } -} - -fn has_xlink(parent: &Group) -> bool { - for node in &parent.children { - match node { - Node::Group(ref g) => { - for filter in &g.filters { - if filter - .primitives - .iter() - .any(|p| matches!(p.kind, filter::Kind::Image(_))) - { - return true; - } - } - - if let Some(ref mask) = g.mask { - if has_xlink(mask.root()) { - return true; - } - - if let Some(ref sub_mask) = mask.mask { - if has_xlink(&sub_mask.root) { - return true; - } - } - } - - if has_xlink(g) { - return true; - } - } - Node::Image(_) => { - return true; - } - Node::Text(ref text) => { - if text - .chunks - .iter() - .any(|t| matches!(t.text_flow, TextFlow::Path(_))) - { - return true; - } - } - _ => {} - } - - let mut present = false; - node.subroots(|root| present |= has_xlink(root)); - if present { - return true; - } - } - - false -} - -fn write_base_grad(g: &BaseGradient, opt: &WriteOptions, xml: &mut XmlWriter) { - xml.write_units(AId::GradientUnits, g.units, Units::ObjectBoundingBox); - xml.write_transform(AId::GradientTransform, g.transform, opt); - - match g.spread_method { - SpreadMethod::Pad => {} - SpreadMethod::Reflect => xml.write_svg_attribute(AId::SpreadMethod, "reflect"), - SpreadMethod::Repeat => xml.write_svg_attribute(AId::SpreadMethod, "repeat"), - } - - for s in &g.stops { - xml.start_svg_element(EId::Stop); - xml.write_svg_attribute(AId::Offset, &s.offset.get()); - xml.write_color(AId::StopColor, s.color); - if s.opacity != Opacity::ONE { - xml.write_svg_attribute(AId::StopOpacity, &s.opacity.get()); - } - - xml.end_element(); - } -} - -fn write_path( - path: &Path, - is_clip_path: bool, - path_transform: Transform, - clip_path: Option<&str>, - opt: &WriteOptions, - xml: &mut XmlWriter, -) { - xml.start_svg_element(EId::Path); - if !path.id.is_empty() { - xml.write_id_attribute(&path.id, opt); - } - - write_fill(&path.fill, is_clip_path, opt, xml); - write_stroke(&path.stroke, opt, xml); - - xml.write_visibility(path.visible); - - if path.paint_order == PaintOrder::StrokeAndFill { - xml.write_svg_attribute(AId::PaintOrder, "stroke"); - } - - match path.rendering_mode { - ShapeRendering::OptimizeSpeed => { - xml.write_svg_attribute(AId::ShapeRendering, "optimizeSpeed"); - } - ShapeRendering::CrispEdges => xml.write_svg_attribute(AId::ShapeRendering, "crispEdges"), - ShapeRendering::GeometricPrecision => {} - } - - if let Some(id) = clip_path { - xml.write_func_iri(AId::ClipPath, id, opt); - } - - xml.write_transform(AId::Transform, path_transform, opt); - - xml.write_attribute_raw("d", |buf| { - use tiny_skia_path::PathSegment; - - for seg in path.data.segments() { - match seg { - PathSegment::MoveTo(p) => { - buf.extend_from_slice(b"M "); - write_num(p.x, buf, opt.coordinates_precision); - buf.push(b' '); - write_num(p.y, buf, opt.coordinates_precision); - buf.push(b' '); - } - PathSegment::LineTo(p) => { - buf.extend_from_slice(b"L "); - write_num(p.x, buf, opt.coordinates_precision); - buf.push(b' '); - write_num(p.y, buf, opt.coordinates_precision); - buf.push(b' '); - } - PathSegment::QuadTo(p1, p) => { - buf.extend_from_slice(b"Q "); - write_num(p1.x, buf, opt.coordinates_precision); - buf.push(b' '); - write_num(p1.y, buf, opt.coordinates_precision); - buf.push(b' '); - write_num(p.x, buf, opt.coordinates_precision); - buf.push(b' '); - write_num(p.y, buf, opt.coordinates_precision); - buf.push(b' '); - } - PathSegment::CubicTo(p1, p2, p) => { - buf.extend_from_slice(b"C "); - write_num(p1.x, buf, opt.coordinates_precision); - buf.push(b' '); - write_num(p1.y, buf, opt.coordinates_precision); - buf.push(b' '); - write_num(p2.x, buf, opt.coordinates_precision); - buf.push(b' '); - write_num(p2.y, buf, opt.coordinates_precision); - buf.push(b' '); - write_num(p.x, buf, opt.coordinates_precision); - buf.push(b' '); - write_num(p.y, buf, opt.coordinates_precision); - buf.push(b' '); - } - PathSegment::Close => { - buf.extend_from_slice(b"Z "); - } - } - } - - buf.pop(); - }); - - xml.end_element(); -} - -fn write_fill(fill: &Option, is_clip_path: bool, opt: &WriteOptions, xml: &mut XmlWriter) { - if let Some(ref fill) = fill { - write_paint(AId::Fill, &fill.paint, opt, xml); - - if fill.opacity != Opacity::ONE { - xml.write_svg_attribute(AId::FillOpacity, &fill.opacity.get()); - } - - if !fill.rule.is_default() { - let name = if is_clip_path { - AId::ClipRule - } else { - AId::FillRule - }; - - xml.write_svg_attribute(name, "evenodd"); - } - } else { - xml.write_svg_attribute(AId::Fill, "none"); - } -} - -fn write_stroke(stroke: &Option, opt: &WriteOptions, xml: &mut XmlWriter) { - if let Some(ref stroke) = stroke { - write_paint(AId::Stroke, &stroke.paint, opt, xml); - - if stroke.opacity != Opacity::ONE { - xml.write_svg_attribute(AId::StrokeOpacity, &stroke.opacity.get()); - } - - if !stroke.dashoffset.approx_zero_ulps(4) { - xml.write_svg_attribute(AId::StrokeDashoffset, &stroke.dashoffset) - } - - if !stroke.miterlimit.is_default() { - xml.write_svg_attribute(AId::StrokeMiterlimit, &stroke.miterlimit.get()); - } - - if stroke.width.get() != 1.0 { - xml.write_svg_attribute(AId::StrokeWidth, &stroke.width.get()); - } - - match stroke.linecap { - LineCap::Butt => {} - LineCap::Round => xml.write_svg_attribute(AId::StrokeLinecap, "round"), - LineCap::Square => xml.write_svg_attribute(AId::StrokeLinecap, "square"), - } - - match stroke.linejoin { - LineJoin::Miter => {} - LineJoin::MiterClip => xml.write_svg_attribute(AId::StrokeLinejoin, "miter-clip"), - LineJoin::Round => xml.write_svg_attribute(AId::StrokeLinejoin, "round"), - LineJoin::Bevel => xml.write_svg_attribute(AId::StrokeLinejoin, "bevel"), - } - - if let Some(ref array) = stroke.dasharray { - xml.write_numbers(AId::StrokeDasharray, array); - } - } else { - // Always set `stroke` to `none` to override the parent value. - // In 99.9% of the cases it's redundant, but a group with `filter` with `StrokePaint` - // will set `stroke`, which will interfere with children nodes. - xml.write_svg_attribute(AId::Stroke, "none"); - } -} - -fn write_paint(aid: AId, paint: &Paint, opt: &WriteOptions, xml: &mut XmlWriter) { - match paint { - Paint::Color(c) => xml.write_color(aid, *c), - Paint::LinearGradient(ref lg) => { - xml.write_func_iri(aid, lg.id(), opt); - } - Paint::RadialGradient(ref rg) => { - xml.write_func_iri(aid, rg.id(), opt); - } - Paint::Pattern(ref patt) => { - xml.write_func_iri(aid, patt.id(), opt); - } - } -} - -fn write_light_source(light: &filter::LightSource, xml: &mut XmlWriter) { - match light { - filter::LightSource::DistantLight(ref light) => { - xml.start_svg_element(EId::FeDistantLight); - xml.write_svg_attribute(AId::Azimuth, &light.azimuth); - xml.write_svg_attribute(AId::Elevation, &light.elevation); - } - filter::LightSource::PointLight(ref light) => { - xml.start_svg_element(EId::FePointLight); - xml.write_svg_attribute(AId::X, &light.x); - xml.write_svg_attribute(AId::Y, &light.y); - xml.write_svg_attribute(AId::Z, &light.z); - } - filter::LightSource::SpotLight(ref light) => { - xml.start_svg_element(EId::FeSpotLight); - xml.write_svg_attribute(AId::X, &light.x); - xml.write_svg_attribute(AId::Y, &light.y); - xml.write_svg_attribute(AId::Z, &light.z); - xml.write_svg_attribute(AId::PointsAtX, &light.points_at_x); - xml.write_svg_attribute(AId::PointsAtY, &light.points_at_y); - xml.write_svg_attribute(AId::PointsAtZ, &light.points_at_z); - xml.write_svg_attribute(AId::SpecularExponent, &light.specular_exponent); - if let Some(ref n) = light.limiting_cone_angle { - xml.write_svg_attribute(AId::LimitingConeAngle, n); - } - } - } - - xml.end_element(); -} - -static POW_VEC: &[f32] = &[ - 1.0, - 10.0, - 100.0, - 1_000.0, - 10_000.0, - 100_000.0, - 1_000_000.0, - 10_000_000.0, - 100_000_000.0, - 1_000_000_000.0, - 10_000_000_000.0, - 100_000_000_000.0, - 1_000_000_000_000.0, -]; - -fn write_num(num: f32, buf: &mut Vec, precision: u8) { - // If number is an integer, it's faster to write it as i32. - if num.fract().approx_zero_ulps(4) { - write!(buf, "{}", num as i32).unwrap(); - return; - } - - // Round numbers up to the specified precision to prevent writing - // ugly numbers like 29.999999999999996. - // It's not 100% correct, but differences are insignificant. - // - // Note that at least in Rust 1.64 the number formatting in debug and release modes - // can be slightly different. So having a lower precision makes - // our output and tests reproducible. - let v = (num * POW_VEC[precision as usize]).round() / POW_VEC[precision as usize]; - - write!(buf, "{}", v).unwrap(); -} - -/// Write all of the tspan attributes except for decorations. -fn write_span( - is_clip_path: bool, - opt: &WriteOptions, - xml: &mut XmlWriter, - chunk: &TextChunk, - span: &TextSpan, -) { - xml.start_svg_element(EId::Tspan); - - let font_family_to_str = |font_family: &FontFamily| match font_family { - FontFamily::Monospace => "monospace".to_string(), - FontFamily::Serif => "serif".to_string(), - FontFamily::SansSerif => "sans-serif".to_string(), - FontFamily::Cursive => "cursive".to_string(), - FontFamily::Fantasy => "fantasy".to_string(), - FontFamily::Named(s) => { - // Only quote if absolutely necessary - match parse_font_families(s) { - Ok(_) => s.clone(), - Err(_) => { - if opt.use_single_quote { - format!("\"{}\"", s) - } else { - format!("'{}'", s) - } - } - } - } - }; - - if !span.font.families.is_empty() { - let families = span - .font - .families - .iter() - .map(font_family_to_str) - .collect::>() - .join(", "); - xml.write_svg_attribute(AId::FontFamily, &families); - } - - match span.font.style { - FontStyle::Normal => {} - FontStyle::Italic => xml.write_svg_attribute(AId::FontStyle, "italic"), - FontStyle::Oblique => xml.write_svg_attribute(AId::FontStyle, "oblique"), - } - - if span.font.weight != 400 { - xml.write_svg_attribute(AId::FontWeight, &span.font.weight); - } - - if span.font.stretch != FontStretch::Normal { - let name = match span.font.stretch { - FontStretch::Condensed => "condensed", - FontStretch::ExtraCondensed => "extra-condensed", - FontStretch::UltraCondensed => "ultra-condensed", - FontStretch::SemiCondensed => "semi-condensed", - FontStretch::Expanded => "expanded", - FontStretch::SemiExpanded => "semi-expanded", - FontStretch::ExtraExpanded => "extra-expanded", - FontStretch::UltraExpanded => "ultra-expanded", - FontStretch::Normal => unreachable!(), - }; - xml.write_svg_attribute(AId::FontStretch, name); - } - - xml.write_svg_attribute(AId::FontSize, &span.font_size); - - xml.write_visibility(span.visible); - - if span.letter_spacing != 0.0 { - xml.write_svg_attribute(AId::LetterSpacing, &span.letter_spacing); - } - - if span.word_spacing != 0.0 { - xml.write_svg_attribute(AId::WordSpacing, &span.word_spacing); - } - - if let Some(text_length) = span.text_length { - xml.write_svg_attribute(AId::TextLength, &text_length); - } - - if span.length_adjust == LengthAdjust::SpacingAndGlyphs { - xml.write_svg_attribute(AId::LengthAdjust, "spacingAndGlyphs"); - } - - if span.small_caps { - xml.write_svg_attribute(AId::FontVariant, "small-caps"); - } - - if span.paint_order == PaintOrder::StrokeAndFill { - xml.write_svg_attribute(AId::PaintOrder, "stroke fill"); - } - - if !span.apply_kerning { - xml.write_attribute("style", "font-kerning:none") - } - - if span.dominant_baseline != DominantBaseline::Auto { - let name = match span.dominant_baseline { - DominantBaseline::UseScript => "use-script", - DominantBaseline::NoChange => "no-change", - DominantBaseline::ResetSize => "reset-size", - DominantBaseline::TextBeforeEdge => "text-before-edge", - DominantBaseline::Middle => "middle", - DominantBaseline::Central => "central", - DominantBaseline::TextAfterEdge => "text-after-edge", - DominantBaseline::Ideographic => "ideographic", - DominantBaseline::Alphabetic => "alphabetic", - DominantBaseline::Hanging => "hanging", - DominantBaseline::Mathematical => "mathematical", - DominantBaseline::Auto => unreachable!(), - }; - xml.write_svg_attribute(AId::DominantBaseline, name); - } - - if span.alignment_baseline != AlignmentBaseline::Auto { - let name = match span.alignment_baseline { - AlignmentBaseline::Baseline => "baseline", - AlignmentBaseline::BeforeEdge => "before-edge", - AlignmentBaseline::TextBeforeEdge => "text-before-edge", - AlignmentBaseline::Middle => "middle", - AlignmentBaseline::Central => "central", - AlignmentBaseline::AfterEdge => "after-edge", - AlignmentBaseline::TextAfterEdge => "text-after-edge", - AlignmentBaseline::Ideographic => "ideographic", - AlignmentBaseline::Alphabetic => "alphabetic", - AlignmentBaseline::Hanging => "hanging", - AlignmentBaseline::Mathematical => "mathematical", - AlignmentBaseline::Auto => unreachable!(), - }; - xml.write_svg_attribute(AId::AlignmentBaseline, name); - } - - write_fill(&span.fill, is_clip_path, opt, xml); - write_stroke(&span.stroke, opt, xml); - - for baseline_shift in &span.baseline_shift { - xml.start_svg_element(EId::Tspan); - match baseline_shift { - BaselineShift::Baseline => {} - BaselineShift::Number(num) => xml.write_svg_attribute(AId::BaselineShift, num), - BaselineShift::Subscript => xml.write_svg_attribute(AId::BaselineShift, "sub"), - BaselineShift::Superscript => xml.write_svg_attribute(AId::BaselineShift, "super"), - } - } - - let cur_text = &chunk.text[span.start..span.end]; - - xml.write_text(&cur_text.replace('&', "&")); - - // End for each tspan we needed to create for baseline_shift - for _ in &span.baseline_shift { - xml.end_element(); - } - - xml.end_element(); -} diff --git a/resvg-0.43.0/version-bump.md b/resvg-0.43.0/version-bump.md deleted file mode 100644 index 6ce9f82..0000000 --- a/resvg-0.43.0/version-bump.md +++ /dev/null @@ -1,10 +0,0 @@ -- .github/chart.svg -- .github/chart-svg2.svg -- CHANGELOG.md -- crates/usvg/Cargo.toml -- crates/resvg/Cargo.toml -- crates/c-api/Cargo.toml -- crates/c-api/resvg.h -- crates/c-api/ResvgQt.h -- tools/explorer-thumbnailer/install/installer.iss -- tools/explorer-thumbnailer/Cargo.toml diff --git a/scripts/makeBundle.lean b/scripts/makeBundle.lean deleted file mode 100644 index 5e6af98..0000000 --- a/scripts/makeBundle.lean +++ /dev/null @@ -1,115 +0,0 @@ -structure FileData where - filename : String - data : ByteArray - -structure ResourceInfo where - filename : String - offset : Nat - size : Nat - -structure ResourcesInfo where - allData : ByteArray - resources : Array ResourceInfo - -/-- Drops the given prefix from a list. It returns the original sequence if the -sequence doesn't start with the given prefix. --/ -def List.dropPrefix [BEq α] (ls lsPrefix : List α) : List α := - match ls, lsPrefix with - | xs, [] => xs - | (x :: xs), (y :: ys) => if x == y then dropPrefix xs ys else ls - | _, _ => ls - -open IO.FS - -def readFile (entry : DirEntry) : ReaderT System.FilePath IO FileData := do - let rootDir ← read - let relFilePath := System.mkFilePath <| entry.path.components.dropPrefix rootDir.components - return {filename := relFilePath.toString, data := (← readBinFile entry.path)} - -def readFilesFromDir (dir : System.FilePath) : ReaderT System.FilePath IO (Array FileData) := do - (← System.FilePath.readDir dir).mapM readFile - -def mkResourcesInfo (ds : Array FileData) : ResourcesInfo := Id.run do - let mut allData := ByteArray.empty - let mut resources := #[] - for d in ds do - resources := resources.push {filename := d.filename, offset := allData.size, size := d.data.size} - -- Push an extra 0 so that a pointer at the offset can be used as a C string. - -- This is useful for text resources like SVG. - allData := (allData.append d.data).push 0 - return {allData, resources} - -def hexEncodeByte (b : UInt8) : String := - let go (b : UInt8) : Char := - if b = 0 then '0' else - if b = 1 then '1' else - if b = 2 then '2' else - if b = 3 then '3' else - if b = 4 then '4' else - if b = 5 then '5' else - if b = 6 then '6' else - if b = 7 then '7' else - if b = 8 then '8' else - if b = 9 then '9' else - if b = 0xa then 'A' else - if b = 0xb then 'B' else - if b = 0xc then 'C' else - if b = 0xd then 'D' else - if b = 0xe then 'E' else - if b = 0xf then 'F' else - '*' - let hi := go <| b >>> 4 - let low := go <| b &&& 0xF - s!"0x{hi}{low}" - -unsafe -def List.repeatedly (f : List α → (β × List α)) (ls : List α) : List β := - match ls with - | [] => [] - | xs => - let (b, xs') := f xs - b :: repeatedly f xs' - -unsafe -def List.chunksOf (n : Nat) (ls : List α) : List (List α) := - match n with - | 0 => panic! "Cannot call chunksOf with zero" - | n => ls.repeatedly (List.splitAt n) - -unsafe -def generateDataCode (ba : ByteArray) : String := - let byteStrings := ba.toList.map hexEncodeByte - let lines := byteStrings.chunksOf 20 - let joinedLines := lines.map (fun line => " " ++ (String.intercalate ", " line) ++ ",") - "static const unsigned char bundle_data[] = {\n" ++ String.intercalate "\n" joinedLines ++ "\n};\n" - -def generateResourceInfoSize (s : Nat) := - s!"size_t resources_size = {s};\n" - -def generateResourceInfoCode (rs : Array ResourceInfo) : String := - let lines := rs.map (fun info => " {.filename = \"" ++ info.filename ++ "\", .offset = " ++ info.offset.repr ++ ", .size = " ++ info.size.repr ++ "},") - "static const ResourceInfo resource_infos[" ++ rs.size.repr ++ "] = {\n" ++ String.intercalate "\n" lines.toList ++ "\n};\n" - -unsafe -def assembleBundleFile (r : ResourcesInfo) : String := - let struct := "typedef struct {\n const char* filename;\n size_t offset;\n size_t size;\n} ResourceInfo;\n" - let infos := generateResourceInfoCode r.resources - let data := generateDataCode r.allData - let rayleanBundleH := "RAYLEAN_BUNDLE_H" - s!"#ifndef {rayleanBundleH}\n#define {rayleanBundleH}\n\n#include \n\n{struct}\n{infos}\n{data}#endif // {rayleanBundleH}\n" - -unsafe -def main (args : List String) : IO Unit := do - let [rootDir, relFromDir, output] := args - | IO.println "Usage: makeBundle " - let resourcePath := System.FilePath.join rootDir relFromDir - if - (← System.FilePath.pathExists resourcePath) - && (← System.FilePath.isDir resourcePath) - then - let ri := mkResourcesInfo (← readFilesFromDir resourcePath |>.run rootDir) - writeFile output (assembleBundleFile ri) - else do - IO.println "Usage: makeBundle " - IO.println s!"{relFromDir} must be a subdirectory of {rootDir}" diff --git a/lean/Examples/ImageDenotation.lean b/src/ImageDenotation.lean similarity index 59% rename from lean/Examples/ImageDenotation.lean rename to src/ImageDenotation.lean index 8545de2..72d13c3 100644 --- a/lean/Examples/ImageDenotation.lean +++ b/src/ImageDenotation.lean @@ -1,4 +1,7 @@ import Raylean +import ImageDenotation.Basic +import ImageDenotation.Image +import ImageDenotation.Render open Raylean open Raylean.Types @@ -13,9 +16,9 @@ def main : IO Unit := do setTargetFPS 10 while not (← windowShouldClose) do renderFrame do - Image.render screenWidth screenHeight - (Image.Image.sover - (Image.Image.monochrome Image.bluish) - (Image.Image.monochrome Image.redish)) + render screenWidth screenHeight + (Image.sover + (Image.monochrome bluish) + (Image.monochrome redish)) drawFPS 100 100 closeWindow diff --git a/lean/Raylean/Graphics2D/Basic.lean b/src/ImageDenotation/Basic.lean similarity index 96% rename from lean/Raylean/Graphics2D/Basic.lean rename to src/ImageDenotation/Basic.lean index e28e796..75caafd 100644 --- a/lean/Raylean/Graphics2D/Basic.lean +++ b/src/ImageDenotation/Basic.lean @@ -2,8 +2,6 @@ import Raylean.Types open Raylean.Types -namespace Raylean.Graphics2D - inductive Picture : Type where | blank : Picture | line (path : Array Vector2) : Picture diff --git a/lean/Raylean/Graphics2D/Image.lean b/src/ImageDenotation/Image.lean similarity index 99% rename from lean/Raylean/Graphics2D/Image.lean rename to src/ImageDenotation/Image.lean index 6a02f4f..f785cfb 100644 --- a/lean/Raylean/Graphics2D/Image.lean +++ b/src/ImageDenotation/Image.lean @@ -3,8 +3,6 @@ import Raylean.Core import Batteries import Mathlib.Tactic.Linarith -namespace Raylean.Image - class LawfulMonoid (α : Type _) [Append α] [Inhabited α]: Prop where assoc {x y z : α} : (x ++ (y ++ z)) = ((x ++ y) ++ z) left_id {x: α} : default ++ x = x diff --git a/lean/Raylean/Graphics2D/Render.lean b/src/ImageDenotation/Render.lean similarity index 89% rename from lean/Raylean/Graphics2D/Render.lean rename to src/ImageDenotation/Render.lean index 6c72fcc..785310c 100644 --- a/lean/Raylean/Graphics2D/Render.lean +++ b/src/ImageDenotation/Render.lean @@ -1,15 +1,13 @@ import Raylean.Core import Raylean.Types import Raylean.Math -import Raylean.Graphics2D.Basic +import ImageDenotation.Basic import Lens import Raylean.Lean open Raylean.Types open Lens -namespace Raylean.Graphics2D - structure RenderContext where scale : Vector2 color : Color @@ -30,18 +28,18 @@ open RenderContext.Lens def renderLine (points : Array Vector2) : ReaderT RenderContext IO Unit := do let ctx ← getContext - drawLineStrip (points.map ctx.toScreen) ctx.color + Raylean.drawLineStrip (points.map ctx.toScreen) ctx.color def renderCircle (radius : Float) : ReaderT RenderContext IO Unit := do let ctx ← getContext - drawCircleV (ctx.toScreen ⟨0, 0⟩) (radius * (max 0 (max ctx.scale.x ctx.scale.y))) ctx.color + Raylean.drawCircleV (ctx.toScreen ⟨0, 0⟩) (radius * (max 0 (max ctx.scale.x ctx.scale.y))) ctx.color def renderRectangle (width height : Float) : ReaderT RenderContext IO Unit := do let ctx ← getContext let topLeft : Vector2 := ⟨-width / 2, height / 2⟩ let p := ctx.toScreen topLeft let r : Rectangle := {x := p.x, y := p.y, width := ctx.scale.x * width, height := ctx.scale.y * height} - drawRectangleRec r ctx.color + Raylean.drawRectangleRec r ctx.color -- This function is partial because lean cannot prove termination partial def renderPicture' : (picture : Picture) → ReaderT RenderContext IO Unit diff --git a/lean/Lens.lean b/src/Lens.lean similarity index 100% rename from lean/Lens.lean rename to src/Lens.lean diff --git a/lean/Lens/Basic.lean b/src/Lens/Basic.lean similarity index 100% rename from lean/Lens/Basic.lean rename to src/Lens/Basic.lean diff --git a/lean/Lens/Const.lean b/src/Lens/Const.lean similarity index 100% rename from lean/Lens/Const.lean rename to src/Lens/Const.lean diff --git a/lean/Lens/Elab.lean b/src/Lens/Elab.lean similarity index 100% rename from lean/Lens/Elab.lean rename to src/Lens/Elab.lean diff --git a/src/Main.lean b/src/Main.lean new file mode 100644 index 0000000..23cb22d --- /dev/null +++ b/src/Main.lean @@ -0,0 +1,3 @@ +import ImageDenotation + +def main (args : List String) : IO Unit := ImageDenotation.main