Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

1
vendor/zeno/.cargo-checksum.json vendored Normal file
View File

@@ -0,0 +1 @@
{"files":{"Cargo.lock":"bae5faa6624e9287b5a66f04ca71014e305b1195753f04939fdbcec5bcd44099","Cargo.toml":"e3b0ec0e9f3cb01d3d28f2f057fd9da3b6665037e936bb13ebc1d9de2b043ca0","LICENSE-APACHE":"7cfd738c53d61c79f07e348f622bf7707c9084237054d37fbe07788a75f5881c","LICENSE-MIT":"34dff8202d63e6df022f4f0ce88dab24f254c35ee0d5958b74dcf74d9eae21cc","README.md":"6555ad61119137207be77d590174345b55d4922e78f1eab86d69ae538a17ded9","benches/render.rs":"a4fc61d179df928df8695fd806e489d8218d7984bb5419322974e057850442bd","src/command.rs":"6915031c0d06221dab8e7c4c793d13e9c30dea5a7952a57274a311e6caaf8b29","src/geometry.rs":"e0dd8ff44fd4317e9b8395c7ae36552040293b88e3bc8b6101aedad9f394461a","src/hit_test.rs":"7a33031affed47daf5419e454d342d8ec95a414ae48f475685d420f53ab19197","src/lib.rs":"c45c6080b26fa05f9ffdbe111ecbc41215f18e8c7374a76954610e86be1e31b2","src/mask.rs":"9b5639e99cf74b98338ebd96a39c246a3276bd0d2bf990fef198c97d25bd6fc4","src/path_builder.rs":"872a98b229bd10ebd85aca196c29da8670ec9951a3f8b54baf625e00d00eeb28","src/path_data.rs":"a41e4e571e14c9df8f6669ac7ab53c40a0af7c58354f907f319aa5bd1d95837b","src/raster.rs":"2c9dbf11c217e3c84c703d5d7b4d5fa5dee31f57a6d06bd942edc086be91fe26","src/scratch.rs":"9dcc2d0c07aa30ff4da04ef910743f5f2cff03e582aa4177a511c0dc0f1b9443","src/segment.rs":"d6b1730fb61917975f45db1f87517f0f8bb2de48f3038c6937f5e5af261b9387","src/stroke.rs":"70a2290da08b8e0e54f99157ebcdb289c2261ccaeea1230986cc57c013f9a584","src/style.rs":"c83be959d69a57e48a9ebcc5d9fd474dddd43ebd3cbb9a04975c0a667ccdc772","src/svg_parser.rs":"014660b499e413eea82568562af1082f33f44d56967e763412c24318c270308d","src/traversal.rs":"45357d2dc637aba22f624c7931befd7059baf1b62da989d957f27722f2c9033d"},"package":"6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524"}

458
vendor/zeno/Cargo.lock generated vendored Normal file
View File

@@ -0,0 +1,458 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "anes"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
[[package]]
name = "anstyle"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "cast"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "ciborium"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
dependencies = [
"ciborium-io",
"ciborium-ll",
"serde",
]
[[package]]
name = "ciborium-io"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
[[package]]
name = "ciborium-ll"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
dependencies = [
"ciborium-io",
"half",
]
[[package]]
name = "clap"
version = "4.5.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.5.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838"
dependencies = [
"anstyle",
"clap_lex",
]
[[package]]
name = "clap_lex"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
[[package]]
name = "criterion"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f"
dependencies = [
"anes",
"cast",
"ciborium",
"clap",
"criterion-plot",
"is-terminal",
"itertools",
"num-traits",
"once_cell",
"oorandom",
"regex",
"serde",
"serde_derive",
"serde_json",
"tinytemplate",
"walkdir",
]
[[package]]
name = "criterion-plot"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
dependencies = [
"cast",
"itertools",
]
[[package]]
name = "crunchy"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "either"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "half"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888"
dependencies = [
"cfg-if",
"crunchy",
]
[[package]]
name = "hermit-abi"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
[[package]]
name = "is-terminal"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b"
dependencies = [
"hermit-abi",
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
[[package]]
name = "libc"
version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]]
name = "libm"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
name = "oorandom"
version = "11.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9"
[[package]]
name = "proc-macro2"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "ryu"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "serde"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.134"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "syn"
version = "2.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "987bc0be1cdea8b10216bd06e2ca407d40b9543468fafd3ddfb02f36e77f71f3"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tinytemplate"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "unicode-ident"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "winapi-util"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "zeno"
version = "0.3.3"
dependencies = [
"criterion",
"fastrand",
"libm",
]

65
vendor/zeno/Cargo.toml vendored Normal file
View File

@@ -0,0 +1,65 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
name = "zeno"
version = "0.3.3"
authors = ["Chad Brokaw <cbrokaw@gmail.com>"]
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "High performance, low level 2D path rasterization."
homepage = "https://github.com/dfrg/zeno"
readme = "README.md"
keywords = [
"path",
"rasterizer",
"svg",
]
categories = ["graphics"]
license = "Apache-2.0 OR MIT"
repository = "https://github.com/dfrg/zeno"
[features]
default = [
"eval",
"std",
]
eval = []
libm = ["dep:libm"]
std = []
[lib]
name = "zeno"
path = "src/lib.rs"
[[bench]]
name = "render"
path = "benches/render.rs"
harness = false
[dependencies.libm]
version = "0.2.7"
optional = true
default-features = false
[dev-dependencies.criterion]
version = "0.5.1"
features = ["cargo_bench_support"]
default-features = false
[dev-dependencies.fastrand]
version = "2.0.1"
default-features = false

201
vendor/zeno/LICENSE-APACHE vendored Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

25
vendor/zeno/LICENSE-MIT vendored Normal file
View File

@@ -0,0 +1,25 @@
Copyright (c) 2020 Chad Brokaw
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

84
vendor/zeno/README.md vendored Normal file
View File

@@ -0,0 +1,84 @@
# zeno
Zeno is a pure Rust crate that provides a high performance, low level 2D
rasterization library with support for rendering paths of various styles
into alpha or subpixel masks.
[![Crates.io][crates-badge]][crates-url]
[![Docs.rs][docs-badge]][docs-url]
[![Apache 2.0 or MIT license.][license-badge]][license-url]
[crates-badge]: https://img.shields.io/crates/v/zeno.svg
[crates-url]: https://crates.io/crates/zeno
[docs-badge]: https://docs.rs/zeno/badge.svg
[docs-url]: https://docs.rs/zeno
[license-badge]: https://img.shields.io/badge/license-Apache--2.0_OR_MIT-blue.svg
[license-url]: #license
## Features
- 256x anti-aliased rasterization (8-bit alpha or 32-bit RGBA subpixel alpha)
- Pixel perfect hit testing with customizable coverage threshold
- Non-zero and even-odd fills
- Stroking with the standard set of joins and caps
(separate start and end caps are possible)
- Numerically stable dashing for smooth dash offset animation
- Vertex traversal for marker placement
- Stepped distance traversal for animation or text-on-path support
- Abstract representation of path data that imposes no policy on storage
## Usage
Rendering a dashed stroke of a triangle:
```rust
use zeno::{Cap, Join, Mask, PathData, Stroke};
// Buffer to store the mask
let mut mask = [0u8; 64 * 64];
/// Create a mask builder with some path data
Mask::new("M 8,56 32,8 56,56 Z")
.style(
// Stroke style with a width of 4
Stroke::new(4.0)
// Round line joins
.join(Join::Round)
// And round line caps
.cap(Cap::Round)
// Dash pattern followed by a dash offset
.dash(&[10.0, 12.0, 0.0], 0.0),
)
// Set the target dimensions
.size(64, 64)
// Render into the target buffer
.render_into(&mut mask, None);
```
Resulting in the following mask:
![Dashed Triangle](https://muddl.com/zeno/tri_dash.png)
For detail on additional features and more advanced usage,
see the full API [documentation](https://docs.rs/zeno).
## License
Licensed under either of
- Apache License, Version 2.0
([LICENSE-APACHE](LICENSE-APACHE) or <http://www.apache.org/licenses/LICENSE-2.0>)
- MIT license
([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
at your option.
## Contribution
Contributions are welcome by pull request. The [Rust code of conduct] applies.
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
licensed as above, without any additional terms or conditions.
[Rust Code of Conduct]: https://www.rust-lang.org/policies/code-of-conduct

74
vendor/zeno/benches/render.rs vendored Normal file
View File

@@ -0,0 +1,74 @@
//! Rendering benchmarks.
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use fastrand::Rng;
use zeno::{Command, Mask, PathBuilder, Scratch, Style};
fn drawing(c: &mut Criterion) {
// Set up buffers for rendering.
let mut buffer = Box::new([0u8; 1024 * 1024]);
let mut scratch = Scratch::new();
let mut rng = Rng::with_seed(0x12345678);
c.bench_function("fill_square", |b| {
let path = {
let mut path = Vec::<Command>::new();
path.add_rect((5.0, 5.0), 1000.0, 1000.0);
path
};
b.iter(|| {
Mask::with_scratch(&path, &mut scratch)
.style(Style::Fill(zeno::Fill::EvenOdd))
.render_into(&mut *buffer, None);
black_box((&mut scratch, &mut buffer));
});
});
c.bench_function("complicated_shape", |b| {
// Create a weird, jagged circle.
let path = {
let (center_x, center_y) = (500.0, 500.0);
let radius = 450.0;
let mut path = Vec::<Command>::new();
path.move_to((center_x, center_y));
for i in 0..500 {
let angle = core::f32::consts::PI * 2.0 * (i as f32) / 500.0;
let pt_x = center_x + (angle.cos() * radius) + rng.f32();
let pt_y = center_y + (angle.sin() * radius) + rng.f32();
path.line_to((pt_x, pt_y));
}
path.close();
path
};
b.iter(|| {
Mask::with_scratch(&path, &mut scratch)
.style(Style::Fill(zeno::Fill::EvenOdd))
.render_into(&mut *buffer, None);
black_box((&mut scratch, &mut buffer));
})
});
c.bench_function("circle", |b| {
let path = {
let mut path = Vec::<Command>::new();
path.add_circle((500.0, 500.0), 450.0);
path
};
b.iter(|| {
Mask::with_scratch(&path, &mut scratch)
.style(Style::Fill(zeno::Fill::EvenOdd))
.render_into(&mut *buffer, None);
black_box((&mut scratch, &mut buffer));
});
});
}
criterion_group!(benches, drawing);
criterion_main!(benches);

177
vendor/zeno/src/command.rs vendored Normal file
View File

@@ -0,0 +1,177 @@
//! Path commands.
use super::geometry::{Point, Transform};
use super::path_builder::PathBuilder;
use core::borrow::Borrow;
/// Path command.
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum Command {
/// Begins a new subpath at the specified point.
MoveTo(Point),
/// A straight line from the previous point to the specified point.
LineTo(Point),
/// A cubic bezier curve from the previous point to the final point with
/// two intermediate control points.
CurveTo(Point, Point, Point),
/// A quadratic curve from the previous point to the final point with one
/// intermediate control point.
QuadTo(Point, Point),
/// Closes a subpath, connecting the final point to the initial point.
Close,
}
impl Command {
/// Returns the associated verb for the command.
pub fn verb(&self) -> Verb {
use Command::*;
match self {
MoveTo(..) => Verb::MoveTo,
LineTo(..) => Verb::LineTo,
QuadTo(..) => Verb::QuadTo,
CurveTo(..) => Verb::CurveTo,
Close => Verb::CurveTo,
}
}
/// Returns the result of a transformation matrix applied to the command.
#[inline]
pub fn transform(&self, transform: &Transform) -> Self {
use Command::*;
let t = transform;
match self {
MoveTo(p) => MoveTo(t.transform_point(*p)),
LineTo(p) => LineTo(t.transform_point(*p)),
QuadTo(c, p) => QuadTo(t.transform_point(*c), t.transform_point(*p)),
CurveTo(c1, c2, p) => CurveTo(
t.transform_point(*c1),
t.transform_point(*c2),
t.transform_point(*p),
),
Close => Close,
}
}
}
/// Action of a path command.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum Verb {
MoveTo,
LineTo,
CurveTo,
QuadTo,
Close,
}
#[derive(Clone)]
pub struct PointsCommands<'a> {
points: &'a [Point],
verbs: &'a [Verb],
point: usize,
verb: usize,
}
impl<'a> PointsCommands<'a> {
pub(super) fn new(points: &'a [Point], verbs: &'a [Verb]) -> Self {
Self {
points,
verbs,
point: 0,
verb: 0,
}
}
#[inline(always)]
pub(super) fn copy_to(&self, sink: &mut impl PathBuilder) {
self.copy_to_inner(sink);
}
#[inline(always)]
fn copy_to_inner(&self, sink: &mut impl PathBuilder) -> Option<()> {
let mut i = 0;
for verb in self.verbs {
match verb {
Verb::MoveTo => {
let p = self.points.get(i)?;
i += 1;
sink.move_to(*p);
}
Verb::LineTo => {
let p = self.points.get(i)?;
i += 1;
sink.line_to(*p);
}
Verb::QuadTo => {
let p = self.points.get(i + 1)?;
let c = self.points.get(i)?;
i += 2;
sink.quad_to(*c, *p);
}
Verb::CurveTo => {
let p = self.points.get(i + 2)?;
let c2 = self.points.get(i + 1)?;
let c1 = self.points.get(i)?;
i += 3;
sink.curve_to(*c1, *c2, *p);
}
Verb::Close => {
sink.close();
}
}
}
Some(())
}
}
impl Iterator for PointsCommands<'_> {
type Item = Command;
#[inline(always)]
fn next(&mut self) -> Option<Self::Item> {
use Command::*;
let verb = self.verbs.get(self.verb)?;
self.verb += 1;
Some(match verb {
Verb::MoveTo => {
let p = self.points.get(self.point)?;
self.point += 1;
MoveTo(*p)
}
Verb::LineTo => {
let p = self.points.get(self.point)?;
self.point += 1;
LineTo(*p)
}
Verb::QuadTo => {
let p = self.points.get(self.point..self.point + 2)?;
self.point += 2;
QuadTo(p[0], p[1])
}
Verb::CurveTo => {
let p = self.points.get(self.point..self.point + 3)?;
self.point += 3;
CurveTo(p[0], p[1], p[2])
}
Verb::Close => Close,
})
}
}
#[derive(Clone)]
pub struct TransformCommands<D> {
pub data: D,
pub transform: Transform,
}
impl<D> Iterator for TransformCommands<D>
where
D: Iterator + Clone,
D::Item: Borrow<Command>,
{
type Item = Command;
fn next(&mut self) -> Option<Self::Item> {
Some(self.data.next()?.borrow().transform(&self.transform))
}
}

602
vendor/zeno/src/geometry.rs vendored Normal file
View File

@@ -0,0 +1,602 @@
//! Geometric primitives.
#[allow(unused)]
use crate::F32Ext;
use core::borrow::Borrow;
use core::ops::{Add, Div, Mul, Sub};
/// Represents an angle in degrees or radians.
#[derive(Copy, Clone, PartialEq, Default, Debug)]
pub struct Angle(f32);
impl Angle {
/// Angle of zero degrees.
pub const ZERO: Self = Self(0.);
/// Creates a new angle from degrees.
pub fn from_degrees(degrees: f32) -> Self {
Self(degrees * core::f32::consts::PI / 180.)
}
/// Creates a new angle from radians.
pub fn from_radians(radians: f32) -> Self {
Self(radians)
}
/// Creates a new angle from gradians.
pub fn from_gradians(gradians: f32) -> Self {
Self::from_degrees(gradians / 400. * 360.)
}
/// Creates a new angle from turns.
pub fn from_turns(turns: f32) -> Self {
Self::from_degrees(turns * 360.)
}
/// Returns the angle in radians.
pub fn to_radians(self) -> f32 {
self.0
}
/// Returns the angle in degrees.
pub fn to_degrees(self) -> f32 {
self.0 * 180. / core::f32::consts::PI
}
}
/// Two dimensional vector.
#[derive(Copy, Clone, PartialEq, Default, Debug)]
pub struct Vector {
pub x: f32,
pub y: f32,
}
impl Vector {
/// Vector with both components set to zero.
pub const ZERO: Self = Self { x: 0., y: 0. };
/// Creates a new vector with the specified coordinates.
#[inline]
pub const fn new(x: f32, y: f32) -> Self {
Self { x, y }
}
/// Returns the length of the vector.
#[inline]
pub fn length(self) -> f32 {
(self.x * self.x + self.y * self.y).sqrt()
}
/// Returns the squared length of the vector.
#[inline]
pub fn length_squared(self) -> f32 {
self.x * self.x + self.y * self.y
}
/// Returns the distance between two points.
#[inline]
pub fn distance_to(self, other: Self) -> f32 {
(self - other).length()
}
/// Computes the dot product of two vectors.
#[inline]
pub fn dot(self, other: Self) -> f32 {
self.x * other.x + self.y * other.y
}
/// Computes the cross product of two vectors.
#[inline]
pub fn cross(self, other: Self) -> f32 {
self.x * other.y - self.y * other.x
}
/// Returns a normalized copy of the vector.
#[inline]
pub fn normalize(self) -> Self {
let length = self.length();
if length == 0. {
return Self::new(0., 0.);
}
let inverse = 1. / length;
Self::new(self.x * inverse, self.y * inverse)
}
/// Returns a new vector containing the smallest integer values greater than
/// or equal to each component.
pub fn ceil(self) -> Self {
Self::new(self.x.ceil(), self.y.ceil())
}
/// Returns a new vector containing the largest integer values less than
/// or equal to each component.
pub fn floor(self) -> Self {
Self::new(self.x.floor(), self.y.floor())
}
/// Returns the angle to the specified vector.
pub fn angle_to(self, other: Self) -> Angle {
Angle::from_radians(self.cross(other).atan2(self.dot(other)))
}
/// Returns true if this vector is approximately equal to other using a
/// standard single precision epsilon value.
#[inline]
pub fn nearly_eq(self, other: Vector) -> bool {
self.nearly_eq_by(other, f32::EPSILON)
}
/// Returns true if this vector is approximately equal to other using
/// the specified epsilon value.
#[inline]
pub fn nearly_eq_by(self, other: Vector, epsilon: f32) -> bool {
(self.x - other.x).abs() < epsilon && (self.y - other.y).abs() < epsilon
}
}
impl Add for Vector {
type Output = Self;
#[inline]
fn add(self, rhs: Self) -> Self {
Self::new(self.x + rhs.x, self.y + rhs.y)
}
}
impl Sub for Vector {
type Output = Self;
#[inline]
fn sub(self, rhs: Self) -> Self {
Self::new(self.x - rhs.x, self.y - rhs.y)
}
}
impl Mul for Vector {
type Output = Self;
#[inline]
fn mul(self, rhs: Self) -> Self {
Self::new(self.x * rhs.x, self.y * rhs.y)
}
}
impl Mul<f32> for Vector {
type Output = Self;
#[inline]
fn mul(self, rhs: f32) -> Self {
Self::new(self.x * rhs, self.y * rhs)
}
}
impl Div for Vector {
type Output = Self;
#[inline]
fn div(self, rhs: Self) -> Self {
Self::new(self.x / rhs.x, self.y / rhs.y)
}
}
impl Div<f32> for Vector {
type Output = Self;
#[inline]
fn div(self, rhs: f32) -> Self {
let s = 1. / rhs;
Self::new(self.x * s, self.y * s)
}
}
impl From<[f32; 2]> for Vector {
fn from(v: [f32; 2]) -> Self {
Self::new(v[0], v[1])
}
}
impl From<[i32; 2]> for Vector {
fn from(v: [i32; 2]) -> Self {
Self::new(v[0] as f32, v[1] as f32)
}
}
impl From<(f32, f32)> for Vector {
fn from(v: (f32, f32)) -> Self {
Self::new(v.0, v.1)
}
}
impl From<(i32, i32)> for Vector {
fn from(v: (i32, i32)) -> Self {
Self::new(v.0 as f32, v.1 as f32)
}
}
impl From<(f32, i32)> for Vector {
fn from(v: (f32, i32)) -> Self {
Self::new(v.0, v.1 as f32)
}
}
impl From<(i32, f32)> for Vector {
fn from(v: (i32, f32)) -> Self {
Self::new(v.0 as f32, v.1)
}
}
impl From<f32> for Vector {
fn from(x: f32) -> Self {
Self::new(x, x)
}
}
impl From<i32> for Vector {
fn from(x: i32) -> Self {
let x = x as f32;
Self::new(x, x)
}
}
impl From<Vector> for [f32; 2] {
fn from(v: Vector) -> Self {
[v.x, v.y]
}
}
impl From<Vector> for (f32, f32) {
fn from(v: Vector) -> Self {
(v.x, v.y)
}
}
/// Alias for vector to distinguish intended use.
pub type Point = Vector;
#[inline(always)]
pub(super) fn normal(start: Vector, end: Vector) -> Vector {
Vector::new(end.y - start.y, -(end.x - start.x)).normalize()
}
/// Two dimensional transformation matrix.
#[derive(Copy, Clone, Default, Debug)]
pub struct Transform {
pub xx: f32,
pub xy: f32,
pub yx: f32,
pub yy: f32,
pub x: f32,
pub y: f32,
}
impl Transform {
/// Identity matrix.
pub const IDENTITY: Self = Self {
xx: 1.,
xy: 0.,
yy: 1.,
yx: 0.,
x: 0.,
y: 0.,
};
/// Creates a new transform.
pub fn new(xx: f32, xy: f32, yx: f32, yy: f32, x: f32, y: f32) -> Self {
Self {
xx,
xy,
yx,
yy,
x,
y,
}
}
/// Creates a translation transform.
pub fn translation(x: f32, y: f32) -> Self {
Self::new(1., 0., 0., 1., x, y)
}
/// Creates a rotation transform.
pub fn rotation(angle: Angle) -> Self {
let (sin, cos) = angle.0.sin_cos();
Self {
xx: cos,
xy: sin,
yx: -sin,
yy: cos,
x: 0.,
y: 0.,
}
}
/// Creates a rotation transform around a point.
pub fn rotation_about(point: impl Into<Point>, angle: Angle) -> Self {
let p = point.into();
Self::translation(p.x, p.y)
.then_rotate(angle)
.then_translate(-p.x, -p.y)
}
/// Creates a scale transform.
pub fn scale(x: f32, y: f32) -> Self {
Self::new(x, 0., 0., y, 0., 0.)
}
/// Creates a skew transform.
pub fn skew(x: Angle, y: Angle) -> Self {
Self {
xx: 1.,
xy: y.0.tan(),
yx: x.0.tan(),
yy: 1.,
x: 0.,
y: 0.,
}
}
fn combine(a: &Transform, b: &Transform) -> Self {
let xx = a.xx * b.xx + a.yx * b.xy;
let yx = a.xx * b.yx + a.yx * b.yy;
let xy = a.xy * b.xx + a.yy * b.xy;
let yy = a.xy * b.yx + a.yy * b.yy;
let x = a.x * b.xx + a.y * b.xy + b.x;
let y = a.x * b.yx + a.y * b.yy + b.y;
Self {
xx,
yx,
xy,
yy,
x,
y,
}
}
/// Returns a new transform that represents the application of this transform
/// followed by other.
pub fn then(&self, other: &Transform) -> Self {
Self::combine(self, other)
}
/// Returns a new transform that represents a translation followed by this
/// transform.
pub fn pre_translate(&self, x: f32, y: f32) -> Self {
Self::combine(&Self::translation(x, y), self)
}
/// Returns a new transform that represents this transform followed by a
/// translation.
pub fn then_translate(&self, x: f32, y: f32) -> Self {
let mut t = *self;
t.x += x;
t.y += y;
t
}
/// Returns a new transform that represents a rotation followed by this
/// transform.
pub fn pre_rotate(&self, angle: Angle) -> Self {
Self::combine(&Self::rotation(angle), self)
}
/// Returns a new transform that represents this transform followed by a
/// rotation.
pub fn then_rotate(&self, angle: Angle) -> Self {
Self::combine(self, &Self::rotation(angle))
}
/// Returns a new transform that represents a scale followed by this
/// transform.
pub fn pre_scale(&self, x: f32, y: f32) -> Self {
Self::combine(&Self::scale(x, y), self)
}
/// Returns a new transform that represents this transform followed by a
/// scale.
pub fn then_scale(&self, x: f32, y: f32) -> Self {
Self::combine(self, &Self::scale(x, y))
}
/// Returns the determinant of the transform.
pub fn determinant(&self) -> f32 {
self.xx * self.yy - self.yx * self.xy
}
/// Returns the inverse of the transform, if any.
pub fn invert(&self) -> Option<Transform> {
let det = self.determinant();
if !det.is_finite() || det == 0. {
return None;
}
let s = 1. / det;
let a = self.xx;
let b = self.xy;
let c = self.yx;
let d = self.yy;
let x = self.x;
let y = self.y;
Some(Transform {
xx: d * s,
xy: -b * s,
yx: -c * s,
yy: a * s,
x: (b * y - d * x) * s,
y: (c * x - a * y) * s,
})
}
/// Returns the result of applying this transform to a point.
#[inline(always)]
pub fn transform_point(&self, point: Point) -> Point {
Vector {
x: (point.x * self.xx + point.y * self.yx) + self.x,
y: (point.x * self.xy + point.y * self.yy) + self.y,
}
}
/// Returns the result of applying this transform to a vector.
#[inline(always)]
pub fn transform_vector(&self, vector: Vector) -> Vector {
Vector {
x: (vector.x * self.xx + vector.y * self.yx),
y: (vector.x * self.xy + vector.y * self.yy),
}
}
}
/// The origin of the coordinate system for rendering.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum Origin {
/// Origin (0, 0) at the top left of the image.
TopLeft,
/// Origin (0, 0) at the bottom left of the image.
BottomLeft,
}
impl Default for Origin {
fn default() -> Self {
Self::TopLeft
}
}
/// Describes the offset and dimensions of a rendered mask.
#[derive(Copy, Clone, Debug, Default)]
pub struct Placement {
/// Horizontal offset with respect to the origin specified when computing
/// the placement.
pub left: i32,
/// Vertical offset with respect to the origin specified when computing
/// the placement.
pub top: i32,
/// Width in pixels.
pub width: u32,
/// Height in pixels.
pub height: u32,
}
impl Placement {
/// Given an origin, offset and bounding box, computes the resulting offset
/// and placement for a tightly bounded mask.
pub fn compute(
origin: Origin,
offset: impl Into<Vector>,
bounds: &Bounds,
) -> (Vector, Placement) {
let offset = offset.into();
let mut bounds = *bounds;
bounds.min = (bounds.min + offset).floor();
bounds.max = (bounds.max + offset).ceil();
let offset = Vector::new(-bounds.min.x, -bounds.min.y);
let width = bounds.width() as u32;
let height = bounds.height() as u32;
let left = -offset.x as i32;
let top = if origin == Origin::BottomLeft {
(-offset.y).floor() + height as f32
} else {
-offset.y
} as i32;
(
offset,
Placement {
left,
top,
width,
height,
},
)
}
}
/// Axis-aligned bounding box.
#[derive(Copy, Clone, Default, Debug)]
pub struct Bounds {
pub min: Point,
pub max: Point,
}
impl Bounds {
/// Creates a new bounding box from minimum and maximum points.
pub fn new(min: Point, max: Point) -> Self {
Self { min, max }
}
/// Creates a new bounding box from a sequence of points.
pub fn from_points<I>(points: I) -> Self
where
I: IntoIterator,
I::Item: Borrow<Point>,
{
let mut b = BoundsBuilder::new();
for p in points {
b.add(*p.borrow());
}
b.build()
}
/// Returns true if the bounding box is empty.
pub fn is_empty(&self) -> bool {
self.min.x >= self.max.x || self.min.y >= self.max.y
}
/// Returns the width of the bounding box.
pub fn width(&self) -> f32 {
self.max.x - self.min.x
}
/// Returns the height of the bounding box.
pub fn height(&self) -> f32 {
self.max.y - self.min.y
}
/// Returns true if the box contains the specified point.
pub fn contains(&self, point: impl Into<Point>) -> bool {
let p = point.into();
p.x > self.min.x && p.x < self.max.x && p.y > self.min.y && p.y < self.max.y
}
}
pub(super) struct BoundsBuilder {
pub count: usize,
#[allow(dead_code)]
pub start: Point,
pub current: Point,
pub min: Point,
pub max: Point,
}
impl BoundsBuilder {
pub fn new() -> Self {
Self {
count: 0,
start: Point::ZERO,
current: Point::ZERO,
min: Point::new(f32::MAX, f32::MAX),
max: Point::new(f32::MIN, f32::MIN),
}
}
pub fn add(&mut self, p: Point) -> &mut Self {
let x = p.x;
let y = p.y;
if x < self.min.x {
self.min.x = x;
}
if x > self.max.x {
self.max.x = x;
}
if y < self.min.y {
self.min.y = y;
}
if y > self.max.y {
self.max.y = y;
}
self.count += 1;
self
}
pub fn build(&self) -> Bounds {
if self.count != 0 {
Bounds {
min: self.min,
max: self.max,
}
} else {
Bounds::default()
}
}
}

90
vendor/zeno/src/hit_test.rs vendored Normal file
View File

@@ -0,0 +1,90 @@
//! Hit testing.
use super::geometry::{Point, Transform};
use super::mask::Mask;
use super::path_data::PathData;
use super::scratch::Scratch;
use super::style::{Fill, Style};
use core::cell::RefCell;
/// Builder for configuring and executing a hit test.
pub struct HitTest<'a, 's, D> {
data: D,
style: Style<'a>,
transform: Option<Transform>,
threshold: u8,
scratch: RefCell<Option<&'s mut Scratch>>,
}
impl<'a, 's, D> HitTest<'a, 's, D>
where
D: PathData,
{
/// Creates a new hit test builder for the specified path data.
pub fn new(data: D) -> Self {
Self {
data,
style: Style::Fill(Fill::NonZero),
transform: None,
threshold: 0,
scratch: RefCell::new(None),
}
}
/// Creates a new hit test builder for the specified path data and scratch memory.
pub fn with_scratch(data: D, scratch: &'s mut Scratch) -> Self {
Self {
data,
style: Style::Fill(Fill::NonZero),
transform: None,
threshold: 0,
scratch: RefCell::new(Some(scratch)),
}
}
/// Sets the style of the path.
pub fn style(&mut self, style: impl Into<Style<'a>>) -> &mut Self {
self.style = style.into();
self
}
/// Sets the transformation matrix of the path.
pub fn transform(&mut self, transform: Option<Transform>) -> &mut Self {
self.transform = transform;
self
}
/// Sets the threshold value for determining whether a hit test registers.
pub fn threshold(&mut self, threshold: u8) -> &mut Self {
self.threshold = threshold;
self
}
/// Returns true if the specified point is painted by the path.
pub fn test(&self, point: impl Into<Point>) -> bool {
let mut scratch = self.scratch.borrow_mut();
let mut buf = [0u8; 1];
let p = point.into() * -1.;
if let Some(scratch) = scratch.as_mut() {
Mask::with_scratch(&self.data, scratch)
.style(self.style)
.offset(p)
.transform(self.transform)
.size(1, 1)
.render_into(&mut buf, None);
} else {
Mask::new(&self.data)
.style(self.style)
.offset(p)
.transform(self.transform)
.size(1, 1)
.render_into(&mut buf, None);
}
if self.threshold == 0xFF {
buf[0] >= self.threshold
} else {
buf[0] > self.threshold
}
}
}

382
vendor/zeno/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,382 @@
/*!
This crate provides a high performance, low level 2D rasterization library
with support for rendering paths of various styles into alpha or subpixel
masks.
Broadly speaking, support is provided for the following:
- 256x anti-aliased rasterization (8-bit alpha or 32-bit RGBA subpixel alpha)
- Pixel perfect hit testing with customizable coverage threshold
- Non-zero and even-odd fills
- Stroking with the standard set of joins and caps
(separate start and end caps are possible)
- Numerically stable dashing for smooth dash offset animation
- Vertex traversal for marker placement
- Stepped distance traversal for animation or text-on-path support
- Abstract representation of path data that imposes no policy on storage
While this crate is general purpose, in the interest of interoperability and
familiarity, the feature set was chosen specifically to accommodate the
requirements of the
[SVG path specification](https://www.w3.org/TR/SVG/paths.html).
Furthermore, the rasterized masks are nearly identical to those generated by
Skia (sans slight AA differences) and as such, should yield images that are
equivalent to those produced by modern web browsers.
# Rendering
Due to the large configuration space for styling and rendering paths, the
builder pattern is used pervasively. The [`Mask`] struct is the builder
used for rasterization. For example, to render a simple triangle into a
64x64 8-bit alpha mask:
```rust
use zeno::{Mask, PathData};
// The target buffer that will contain the mask
let mut mask = [0u8; 64 * 64];
// Create a new mask with some path data
Mask::new("M 8,56 32,8 56,56 Z")
// Choose an explicit size for the target
.size(64, 64)
// Finally, render the path into the target
.render_into(&mut mask, None);
```
Note that, in this case, the path itself is supplied as a string in SVG path
data format. This crate provides several different kinds of path data by
default along with support for custom implementations. See the
[`PathData`] trait for more detail.
The previous example did not provide a style, so a non-zero
[`Fill`] was chosen by default. Let's render the same path with
a 4 pixel wide stroke and a round line join:
```rust
use zeno::{Join, Mask, PathData, Stroke};
let mut mask = [0u8; 64 * 64];
Mask::new("M 8,56 32,8 56,56 Z")
.size(64, 64)
.style(Stroke::new(4.0).join(Join::Round))
.render_into(&mut mask, None);
```
Or to make it a bit more dashing:
```rust
use zeno::{Cap, Join, Mask, PathData, Stroke};
let mut mask = [0u8; 64 * 64];
Mask::new("M 8,56 32,8 56,56 Z")
.style(
Stroke::new(4.0)
.join(Join::Round)
.cap(Cap::Round)
// dash accepts a slice of dash lengths and an initial dash offset
.dash(&[10.0, 12.0, 0.0], 0.0),
)
.size(64, 64)
.render_into(&mut mask, None);
```
See the [`Stroke`] builder struct for all available options.
So far, we've generated our masks into fixed buffers with explicit sizes. It is
often the case that it is preferred to ignore all empty space and render a path
into a tightly bound mask of dynamic size. This can be done by eliding the call
for the size method:
```rust
use zeno::{Mask, PathData};
// Dynamic buffer that will contain the mask
let mut mask = Vec::new();
let placement = Mask::new("M 8,56 32,8 56,56 Z")
// Insert an inspect call here to access the computed dimensions
.inspect(|format, width, height| {
// Make sure our buffer is the correct size
mask.resize(format.buffer_size(width, height), 0);
})
.render_into(&mut mask, None);
```
The call to size has been replaced with a call to inspect which injects a
closure into the call chain giving us the opportunity to extend our buffer to
the appropriate size. Note also that the render method has a return value that
has been captured here. This [`Placement`] struct describes the dimensions of
the resulting mask along with an offset that should be applied during
composition to compensate for the removal of any empty space.
Finally, it is possible to render without a target buffer, in which case the
rasterizer will allocate and return a new `Vec<u8>` containing the mask:
```rust
use zeno::{Mask, PathData};
// mask is a Vec<u8>
let (mask, placement) = Mask::new("M 8,56 32,8 56,56 Z")
// Calling render() instead of render_into() will allocate a buffer
// for you that is returned along with the placement
.render();
```
Both [`Mask`] and [`Stroke`] offer large sets of options for fine-grained
control of styling and rasterization including offsets, scaling,
transformations, formats, coordinate spaces and more. See
their respective documentation for more detail.
# Hit testing
Hit testing is the process of determining if a point is within the region that
would be painted by the path. A typical use case is to determine if a user's
cursor is hovering over a particular path. The process generally follows the
same form as rendering:
```rust
use zeno::{HitTest, PathData};
// A 20x10 region with the right half covered by the path
let hit_test = HitTest::new("M10,0 10,10 20,10 20,0 Z");
assert_eq!(hit_test.test([15, 5]), true);
assert_eq!(hit_test.test([5, 5]), false);
```
Due to the fact that paths are anti-aliased, the hit test builder offers a
threshold option that determines how much "coverage" is required for a hit test
to pass at a particular point.
```rust
use zeno::{HitTest, PathData};
let mut hit_test = HitTest::new("M2.5,0 2.5,2 5,2 5,0 Z");
// Require full coverage for a successful hit test
hit_test.threshold(255);
assert_eq!(hit_test.test([2, 0]), false);
// Succeed for any non-zero coverage
hit_test.threshold(0);
assert_eq!(hit_test.test([2, 0]), true);
```
See the [`HitTest`] type for more detail.
# Path building
While SVG paths are a reasonable choice for static storage, there sometimes
arise cases where paths must be built dynamically at runtime:
```rust
use zeno::{Command, Mask, PathBuilder, PathData};
// Create a vector to store the path commands
let mut path: Vec<Command> = Vec::new();
// Construct the path with chained method calls
path.move_to([8, 56]).line_to([32, 8]).line_to([56, 56]).close();
// Ensure it is equal to the equivalent SVG path
assert!((&path).commands().eq("M 8,56 32,8 56,56 Z".commands()));
// &Vec<Command> is also valid path data
Mask::new(&path).render(); // ...
```
Here, a vector of [`Command`]s is used to store the path data and the
[`PathBuilder`] trait provides the extension methods necessary for
building a path.
Beyond the four basic path commands, the path builder trait also provides
arcs (and position relative versions of all previous commands) along with
rectangles, round rectangles, ellipses and circles:
```rust
use zeno::{Angle, ArcSize, ArcSweep, Command, PathBuilder, PathData};
let mut path: Vec<Command> = Vec::new();
path.move_to([1, 2]).rel_arc_to(
8.0,
4.0,
Angle::from_degrees(30.0),
ArcSize::Small,
ArcSweep::Positive,
[10, 4],
);
assert!((&path).commands().eq("M1,2 a8,4,30,0,1,10,4".commands()));
```
Along with incremental building of paths, path builder can also be used as a
"sink" for capturing the result of the application of a style and transform
to some path data. For example, it is possible to store the output of a stroke
style to avoid the cost of stroke evaluation for future rendering or hit test
operations with the use of the [`apply`] function:
```rust
use zeno::{apply, Cap, Command, PathBuilder, PathData, Stroke};
let mut stroke: Vec<Command> = Vec::new();
apply("L10,0", Stroke::new(4.0).cap(Cap::Round), None, &mut stroke);
```
[`PathBuilder`] is only implemented for `Vec<Command>` by default, but
custom implementations are possible to support capturing and building
paths into other data structures.
# Traversal
Path traversal involves incremental evaluation of a path by some metric. This
crate currently provides two methods of traversal.
The [`Vertices`] iterator yields a variant of the [`Vertex`] enum at the
beginning and end of each subpath and between each path command. Each variant
provides all the geometric information necessary to place SVG style markers.
The [`Walk`] type is an iterator-like type that allows for
stepping along the path by arbitrary distances. Each step yields the position
on the path at the next distance along with a vector describing the
left-ward direction from the path at that point. This is useful for animating
objects along a path, or for rendering text attached to a path.
# Transient memory allocations
The algorithms in this crate make a concerted effort to avoid dynamic
allocations where possible, but paths of significant size or complexity
may cause spills into temporary heap memory. Specifically, stroke evaluation
and rasterization may cause heap allocations.
To amortize the cost of these, the appropriately named
[`Scratch`] struct is available. This type contains internal
heap allocated storage and provides replacement methods for functions that may
allocate. In addition, the [`Mask::with_scratch`] and [`HitTest::with_scratch`]
constructors are provided which take a scratch instance as an argument and
redirect all transient allocations to the reusable storage.
*/
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(not(any(feature = "std", feature = "libm")))]
compile_error! { "Either the std or libm feature must be enabled" }
extern crate alloc;
mod command;
mod geometry;
#[cfg(feature = "eval")]
mod hit_test;
#[cfg(feature = "eval")]
mod mask;
mod path_builder;
mod path_data;
#[cfg(feature = "eval")]
mod raster;
#[cfg(feature = "eval")]
mod scratch;
mod segment;
#[cfg(feature = "eval")]
mod stroke;
mod style;
mod svg_parser;
#[cfg(feature = "eval")]
mod traversal;
pub use command::{Command, Verb};
pub use geometry::{Angle, Bounds, Origin, Placement, Point, Transform, Vector};
#[cfg(feature = "eval")]
pub use hit_test::HitTest;
#[cfg(feature = "eval")]
pub use mask::{Format, Mask};
pub use path_builder::{ArcSize, ArcSweep, PathBuilder};
#[cfg(feature = "eval")]
pub use path_data::{apply, bounds};
pub use path_data::{length, PathData};
#[cfg(feature = "eval")]
pub use scratch::Scratch;
pub use style::*;
pub use svg_parser::validate_svg;
#[cfg(feature = "eval")]
pub use traversal::{Vertex, Vertices, Walk};
macro_rules! define_f32_ext {
($($fpname:ident($($argname:ident: $argty:ty),*) -> $ret:ty => $libmname:ident;)*) => {
/// An extension trait defining floating point operations.
#[allow(dead_code)]
trait F32Ext {
$(
fn $fpname(self, $($argname:$argty),*) -> $ret;
)*
}
#[cfg(feature = "std")]
impl F32Ext for f32 {
$(
fn $fpname(self, $($argname:$argty),*) -> $ret {
// This intrinsic is natively defined in libstd.
f32::$fpname(self, $($argname),*)
}
)*
}
#[cfg(all(not(feature = "std"), feature = "libm"))]
impl F32Ext for f32 {
$(
fn $fpname(self, $($argname:$argty),*) -> $ret {
// Use the libm version of this intrinsic.
<$ret>::libm_cvt(libm::$libmname(
self.into(),
$(($argname).into()),*
) as _)
}
)*
}
}
}
define_f32_ext! {
abs() -> f32 => fabs;
acos() -> f32 => acos;
atan2(x:f32) -> f32 => atan2;
ceil() -> f32 => ceil;
cos() -> f32 => cos;
floor() -> f32 => floor;
sin_cos() -> (f32, f32) => sincos;
sqrt() -> f32 => sqrt;
powf(x:f32) -> f32 => powf;
powi(x:i32) -> f32 => pow;
tan() -> f32 => tan;
}
#[cfg(all(not(feature = "std"), feature = "libm"))]
trait LibmCvt {
type Input;
fn libm_cvt(input: Self::Input) -> Self;
}
#[cfg(all(not(feature = "std"), feature = "libm"))]
impl LibmCvt for f32 {
type Input = f64;
fn libm_cvt(input: f64) -> f32 {
input as f32
}
}
#[cfg(all(not(feature = "std"), feature = "libm"))]
impl LibmCvt for (f32, f32) {
type Input = (f64, f64);
fn libm_cvt((a, b): (f64, f64)) -> (f32, f32) {
(a as f32, b as f32)
}
}
// Prep for no_std support when core supports FP intrinsics.
mod lib {
pub use alloc::vec::Vec;
}

455
vendor/zeno/src/mask.rs vendored Normal file
View File

@@ -0,0 +1,455 @@
//! Mask generator.
use super::geometry::{Origin, Placement, Transform, Vector};
use super::path_data::{apply, PathData};
use super::scratch::Scratch;
use super::style::{Fill, Style};
#[allow(unused)]
use super::F32Ext;
use crate::lib::Vec;
use core::cell::RefCell;
/// The desired output image format for rendering.
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum Format {
/// 8-bit alpha mask.
Alpha,
/// 32-bit RGBA subpixel mask with 1/3 pixel offsets for the red and
/// blue channels.
Subpixel,
/// 32-bit RGBA subpixel mask with custom offsets.
CustomSubpixel([f32; 3]),
}
impl Format {
/// Creates a format for BGRA subpixel rendering.
pub fn subpixel_bgra() -> Self {
Self::CustomSubpixel([0.3, 0., -0.3])
}
/// Returns the necessary buffer size to hold an image of the specified
/// width and height with this format.
pub fn buffer_size(self, width: u32, height: u32) -> usize {
(width
* height
* match self {
Self::Alpha => 1,
_ => 4,
}) as usize
}
}
impl Default for Format {
fn default() -> Self {
Self::Alpha
}
}
/// Builder for configuring and rendering a mask.
pub struct Mask<'a, 's, D> {
data: D,
style: Style<'a>,
transform: Option<Transform>,
format: Format,
origin: Origin,
offset: Vector,
render_offset: Vector,
width: u32,
height: u32,
explicit_size: bool,
has_size: bool,
bounds_offset: Vector,
scratch: RefCell<Option<&'s mut Scratch>>,
}
impl<'a, 's, D> Mask<'a, 's, D>
where
D: PathData,
{
/// Creates a new mask builder for the specified path data.
pub fn new(data: D) -> Self {
Self {
data,
style: Style::Fill(Fill::NonZero),
transform: None,
format: Format::Alpha,
origin: Origin::TopLeft,
offset: Vector::ZERO,
render_offset: Vector::ZERO,
width: 0,
height: 0,
explicit_size: false,
has_size: false,
bounds_offset: Vector::ZERO,
scratch: RefCell::new(None),
}
}
/// Creates a new mask builder for the specified path data and scratch memory.
pub fn with_scratch(data: D, scratch: &'s mut Scratch) -> Self {
Self {
data,
style: Style::Fill(Fill::NonZero),
transform: None,
format: Format::Alpha,
origin: Origin::TopLeft,
offset: Vector::ZERO,
render_offset: Vector::ZERO,
width: 0,
height: 0,
explicit_size: false,
has_size: false,
bounds_offset: Vector::ZERO,
scratch: RefCell::new(Some(scratch)),
}
}
/// Sets the style of the path. The default is a non-zero fill.
pub fn style(&mut self, style: impl Into<Style<'a>>) -> &mut Self {
self.style = style.into();
self
}
/// Sets the transformation matrix of the path.
pub fn transform(&mut self, transform: Option<Transform>) -> &mut Self {
self.transform = transform;
self
}
/// Sets the desired format of the mask. The default value is an 8-bit
/// alpha format.
pub fn format(&mut self, format: Format) -> &mut Self {
self.format = format;
self
}
/// Sets the origin that defines the coordinate system for the mask.
pub fn origin(&mut self, origin: Origin) -> &mut Self {
self.origin = origin;
self
}
/// Sets the offset for the path's rendered bounds. To translate both the
/// path and its rendered bounding box, set both [`Self::offset`] and
/// [`Self::render_offset`].
pub fn offset(&mut self, offset: impl Into<Vector>) -> &mut Self {
self.offset = offset.into();
self
}
/// Sets an explicit size for the mask. If left unspecified, the size will
/// be computed from the bounding box of the path after applying any
/// relevant style, offset and transform.
pub fn size(&mut self, width: u32, height: u32) -> &mut Self {
self.width = width;
self.height = height;
self.explicit_size = true;
self.has_size = true;
self
}
/// Sets an additional rendering offset for the mask. This offset does not
/// affect bounds or size computations and is only applied during
/// rendering.
pub fn render_offset(&mut self, offset: impl Into<Vector>) -> &mut Self {
self.render_offset = offset.into();
self
}
/// Invokes a closure with the format, width and height of the mask provided
/// as arguments. This is primarily useful for preparing a target buffer without
/// interrupting the call chain.
pub fn inspect(&mut self, mut f: impl FnMut(Format, u32, u32)) -> &mut Self {
self.ensure_size();
f(self.format, self.width, self.height);
self
}
/// Renders the mask into a byte buffer. If specified, the pitch describes
/// the number of bytes between subsequent rows of the target buffer. This
/// is primarily useful for rendering into tiled images such as texture
/// atlases. If left unspecified, the buffer is assumed to be linear and
/// tightly packed.
pub fn render_into(&self, buffer: &mut [u8], pitch: Option<usize>) -> Placement {
let (offset, placement) = self.placement();
let pitch = match pitch {
Some(pitch) => pitch,
_ => {
placement.width as usize
* match self.format {
Format::Alpha => 1,
_ => 4,
}
}
};
render(self, offset, &placement, buffer, pitch);
placement
}
/// Renders the mask to a newly allocated buffer.
pub fn render(&self) -> (Vec<u8>, Placement) {
let mut buf = Vec::new();
let (offset, placement) = self.placement();
buf.resize(
self.format.buffer_size(placement.width, placement.height),
0,
);
let pitch = placement.width as usize
* match self.format {
Format::Alpha => 1,
_ => 4,
};
render(self, offset, &placement, &mut buf, pitch);
(buf, placement)
}
fn ensure_size(&mut self) {
if self.has_size {
return;
}
let (offset, placement) = self.placement();
self.bounds_offset = offset;
self.width = placement.width;
self.height = placement.height;
self.explicit_size = false;
self.has_size = true;
}
fn placement(&self) -> (Vector, Placement) {
let mut placement = Placement {
left: 0,
top: 0,
width: self.width,
height: self.height,
};
let mut offset = self.offset;
if self.explicit_size {
return (offset, placement);
} else if !self.has_size {
let mut scratch = self.scratch.borrow_mut();
let mut bounds = if let Some(scratch) = scratch.as_mut() {
scratch.bounds(&self.data, self.style, self.transform)
} else {
super::bounds(&self.data, self.style, self.transform)
};
bounds.min = (bounds.min + self.offset).floor();
bounds.max = (bounds.max + self.offset).ceil();
offset = Vector::new(-bounds.min.x, -bounds.min.y);
placement.width = bounds.width() as u32;
placement.height = bounds.height() as u32;
} else {
offset = self.bounds_offset;
}
placement.left = -offset.x as i32;
placement.top = if self.origin == Origin::BottomLeft {
(-offset.y).floor() + self.height as f32
} else {
-offset.y
} as i32;
(offset, placement)
}
}
#[allow(clippy::needless_lifetimes)]
pub fn render<'a, 'c, D>(
mask: &'a Mask<'a, 'c, D>,
offset: Vector,
placement: &Placement,
buf: &mut [u8],
pitch: usize,
) where
D: PathData,
{
let y_up = mask.origin == Origin::BottomLeft;
let (is_subpx, subpx) = match mask.format {
Format::Alpha => (false, [Vector::ZERO; 3]),
Format::Subpixel => (
true,
[Vector::new(-0.3, 0.), Vector::ZERO, Vector::new(0.3, 0.)],
),
Format::CustomSubpixel(subpx) => (
true,
[
Vector::new(subpx[0], 0.),
Vector::new(subpx[1], 0.),
Vector::new(subpx[2], 0.),
],
),
};
let fill = match mask.style {
Style::Fill(fill) => fill,
_ => Fill::NonZero,
};
let w = placement.width;
let h = placement.height;
let shift = offset + mask.render_offset;
let data = &mask.data;
let style = mask.style;
let transform = mask.transform;
let mut scratch = mask.scratch.borrow_mut();
use super::raster::{AdaptiveStorage, Rasterizer};
if let Some(scratch) = scratch.as_mut() {
let mut ras = Rasterizer::new(&mut scratch.render);
let inner = &mut scratch.inner;
if is_subpx {
ras.rasterize_write(
shift + subpx[0],
w,
h,
&mut |r| {
inner.apply(data, &style, transform, r);
},
fill,
pitch,
y_up,
&mut |row_offset, x, count, coverage| {
let buf = &mut buf[row_offset..];
let mut i = 0;
let mut j = x * 4;
while i < count {
buf[j] = coverage;
i += 1;
j += 4;
}
},
);
ras.rasterize_write(
shift + subpx[1],
w,
h,
&mut |r| {
inner.apply(data, &style, transform, r);
},
fill,
pitch,
y_up,
&mut |row_offset, x, count, coverage| {
let buf = &mut buf[row_offset..];
let mut i = 0;
let mut j = x * 4 + 1;
while i < count {
buf[j] = coverage;
i += 1;
j += 4;
}
},
);
ras.rasterize_write(
shift + subpx[2],
w,
h,
&mut |r| {
inner.apply(data, &style, transform, r);
},
fill,
pitch,
y_up,
&mut |row_offset, x, count, coverage| {
let buf = &mut buf[row_offset..];
let mut i = 0;
let mut j = x * 4 + 2;
while i < count {
buf[j] = coverage;
i += 1;
j += 4;
}
},
);
} else {
ras.rasterize(
shift,
w,
h,
&mut |r| {
inner.apply(data, &style, transform, r);
},
fill,
buf,
pitch,
y_up,
);
}
} else {
let mut storage = AdaptiveStorage::new();
let mut ras = Rasterizer::new(&mut storage);
if is_subpx {
ras.rasterize_write(
shift + subpx[0],
w,
h,
&mut |r| {
apply(data, style, transform, r);
},
fill,
pitch,
y_up,
&mut |row_offset, x, count, coverage| {
let buf = &mut buf[row_offset..];
let mut i = 0;
let mut j = x * 4;
while i < count {
buf[j] = coverage;
i += 1;
j += 4;
}
},
);
ras.rasterize_write(
shift + subpx[1],
w,
h,
&mut |r| {
apply(data, style, transform, r);
},
fill,
pitch,
y_up,
&mut |row_offset, x, count, coverage| {
let buf = &mut buf[row_offset..];
let mut i = 0;
let mut j = x * 4 + 1;
while i < count {
buf[j] = coverage;
i += 1;
j += 4;
}
},
);
ras.rasterize_write(
shift + subpx[2],
w,
h,
&mut |r| {
apply(data, style, transform, r);
},
fill,
pitch,
y_up,
&mut |row_offset, x, count, coverage| {
let buf = &mut buf[row_offset..];
let mut i = 0;
let mut j = x * 4 + 2;
while i < count {
buf[j] = coverage;
i += 1;
j += 4;
}
},
);
} else {
ras.rasterize(
shift,
w,
h,
&mut |r| {
apply(data, style, transform, r);
},
fill,
buf,
pitch,
y_up,
);
}
}
}

593
vendor/zeno/src/path_builder.rs vendored Normal file
View File

@@ -0,0 +1,593 @@
//! Path builder.
#![allow(clippy::excessive_precision)]
use super::command::Command;
use super::geometry::{Angle, BoundsBuilder, Point, Transform};
#[allow(unused)]
use super::F32Ext;
use crate::lib::Vec;
use core::f32;
/// Describes the size of an arc.
#[derive(Copy, Clone, PartialEq)]
pub enum ArcSize {
/// An arc of <= 180 degrees will be drawn.
Small,
/// An arc of >= 180 degrees will be drawn.
Large,
}
/// Describes the sweep direction for an arc.
#[derive(Copy, Clone, PartialEq)]
pub enum ArcSweep {
/// The arc is drawn in a positive angle direction.
Positive,
/// The arc is drawn in a negative angle direction.
Negative,
}
/// Trait for types that accept path commands.
pub trait PathBuilder: Sized {
/// Returns the current point of the path.
fn current_point(&self) -> Point;
/// Moves to the specified point, beginning a new subpath.
fn move_to(&mut self, to: impl Into<Point>) -> &mut Self;
/// Moves to the specified point, relative to the current point,
/// beginning a new subpath.
fn rel_move_to(&mut self, to: impl Into<Point>) -> &mut Self {
self.move_to(to.into() + self.current_point())
}
/// Adds a line to the specified point. This will begin a new subpath
/// if the path is empty or the previous subpath was closed.
fn line_to(&mut self, to: impl Into<Point>) -> &mut Self;
/// Adds a line to the specified point, relative to the current point. This
/// will begin a new subpath if the path is empty or the previous subpath
/// was closed.
fn rel_line_to(&mut self, to: impl Into<Point>) -> &mut Self {
self.line_to(to.into() + self.current_point())
}
/// Adds a cubic bezier curve from the current point through the specified
/// control points to the final point. This will begin a new subpath if the
/// path is empty or the previous subpath was closed.
fn curve_to(
&mut self,
control1: impl Into<Point>,
control2: impl Into<Point>,
to: impl Into<Point>,
) -> &mut Self;
/// Adds a cubic bezier curve from the current point through the specified
/// control points to the final point. All points are considered relative to the
/// current point. This will begin a new subpath if the path is empty or the
/// previous subpath was closed.
fn rel_curve_to(
&mut self,
control1: impl Into<Point>,
control2: impl Into<Point>,
to: impl Into<Point>,
) -> &mut Self {
let r = self.current_point();
self.curve_to(control1.into() + r, control2.into() + r, to.into() + r)
}
/// Adds a quadratic bezier curve from the current point through the specified
/// control point to the final point. This will begin a new subpath if the
/// path is empty or the previous subpath was closed.
fn quad_to(&mut self, control1: impl Into<Point>, to: impl Into<Point>) -> &mut Self;
/// Adds a quadratic bezier curve from the current point through the specified
/// control point to the final point. All points are considered relative to the
/// current point. This will begin a new subpath if the path is empty or the
/// previous subpath was closed.
fn rel_quad_to(&mut self, control: impl Into<Point>, to: impl Into<Point>) -> &mut Self {
let r = self.current_point();
self.quad_to(control.into() + r, to.into() + r)
}
/// Adds an arc with the specified x- and y-radius, rotation angle, arc size,
/// and arc sweep from the current point to the specified end point. The center
/// point of the arc will be computed from the parameters. This will begin a
/// new subpath if the path is empty or the previous subpath was closed.
fn arc_to(
&mut self,
rx: f32,
ry: f32,
angle: Angle,
size: ArcSize,
sweep: ArcSweep,
to: impl Into<Point>,
) -> &mut Self {
let from = self.current_point();
arc(
self,
from,
rx,
ry,
angle.to_radians(),
size,
sweep,
to.into(),
);
self
}
/// Adds an arc with the specified x- and y-radius, rotation angle, arc size,
/// and arc sweep from the current point to the specified end point. The end
/// point is considered relative to the current point. The center point of the
/// arc will be computed from the parameters. This will begin a new subpath if
/// the path is empty or the previous subpath was closed.
fn rel_arc_to(
&mut self,
rx: f32,
ry: f32,
angle: Angle,
size: ArcSize,
sweep: ArcSweep,
to: impl Into<Point>,
) -> &mut Self {
self.arc_to(rx, ry, angle, size, sweep, to.into() + self.current_point())
}
/// Closes the current subpath.
fn close(&mut self) -> &mut Self;
/// Adds a rectangle with the specified position and size to the path. This
/// will create a new closed subpath.
fn add_rect(&mut self, xy: impl Into<Point>, w: f32, h: f32) -> &mut Self {
let p = xy.into();
let (l, t, r, b) = (p.x, p.y, p.x + w, p.y + h);
self.move_to(p);
self.line_to((r, t));
self.line_to((r, b));
self.line_to((l, b));
self.close()
}
/// Adds a rounded rectangle with the specified position, size and radii to
/// the path. This will create a new closed subpath.
fn add_round_rect(
&mut self,
xy: impl Into<Point>,
w: f32,
h: f32,
rx: f32,
ry: f32,
) -> &mut Self {
let p = xy.into();
let size = ArcSize::Small;
let sweep = ArcSweep::Positive;
let a = Angle::from_radians(0.);
let hw = w * 0.5;
let rx = rx.max(0.).min(hw);
let hh = h * 0.5;
let ry = ry.max(0.).min(hh);
self.move_to((p.x + rx, p.y));
self.line_to((p.x + w - rx, p.y));
self.arc_to(rx, ry, a, size, sweep, (p.x + w, p.y + ry));
self.line_to((p.x + w, p.y + h - ry));
self.arc_to(rx, ry, a, size, sweep, (p.x + w - rx, p.y + h));
self.line_to((p.x + rx, p.y + h));
self.arc_to(rx, ry, a, size, sweep, (p.x, p.y + h - ry));
self.line_to((p.x, p.y + ry));
self.arc_to(rx, ry, a, size, sweep, (p.x + rx, p.y));
self.close()
}
/// Adds an ellipse with the specified center and radii to the path. This
/// will create a new closed subpath.
fn add_ellipse(&mut self, center: impl Into<Point>, rx: f32, ry: f32) -> &mut Self {
let center = center.into();
let cx = center.x;
let cy = center.y;
let a = 0.551915024494;
let arx = a * rx;
let ary = a * ry;
self.move_to((cx + rx, cy));
self.curve_to((cx + rx, cy + ary), (cx + arx, cy + ry), (cx, cy + ry));
self.curve_to((cx - arx, cy + ry), (cx - rx, cy + ary), (cx - rx, cy));
self.curve_to((cx - rx, cy - ary), (cx - arx, cy - ry), (cx, cy - ry));
self.curve_to((cx + arx, cy - ry), (cx + rx, cy - ary), (cx + rx, cy));
self.close()
}
/// Adds a circle with the specified center and radius to the path. This
/// will create a new closed subpath.
fn add_circle(&mut self, center: impl Into<Point>, r: f32) -> &mut Self {
self.add_ellipse(center, r, r)
}
}
impl PathBuilder for Vec<Command> {
fn current_point(&self) -> Point {
match self.last() {
None => Point::ZERO,
Some(cmd) => match cmd {
Command::MoveTo(p)
| Command::LineTo(p)
| Command::QuadTo(_, p)
| Command::CurveTo(_, _, p) => *p,
Command::Close => {
for cmd in self.iter().rev().skip(1) {
if let Command::MoveTo(p) = cmd {
return *p;
}
}
Point::ZERO
}
},
}
}
fn move_to(&mut self, to: impl Into<Point>) -> &mut Self {
self.push(Command::MoveTo(to.into()));
self
}
fn line_to(&mut self, to: impl Into<Point>) -> &mut Self {
self.push(Command::LineTo(to.into()));
self
}
fn quad_to(&mut self, control: impl Into<Point>, to: impl Into<Point>) -> &mut Self {
self.push(Command::QuadTo(control.into(), to.into()));
self
}
fn curve_to(
&mut self,
control1: impl Into<Point>,
control2: impl Into<Point>,
to: impl Into<Point>,
) -> &mut Self {
self.push(Command::CurveTo(
control1.into(),
control2.into(),
to.into(),
));
self
}
fn close(&mut self) -> &mut Self {
self.push(Command::Close);
self
}
}
pub struct TransformSink<'a, S> {
pub sink: &'a mut S,
pub transform: Transform,
}
impl<S: PathBuilder> PathBuilder for TransformSink<'_, S> {
fn current_point(&self) -> Point {
self.sink.current_point()
}
fn move_to(&mut self, to: impl Into<Point>) -> &mut Self {
self.sink.move_to(self.transform.transform_point(to.into()));
self
}
fn line_to(&mut self, to: impl Into<Point>) -> &mut Self {
self.sink.line_to(self.transform.transform_point(to.into()));
self
}
fn quad_to(&mut self, control: impl Into<Point>, to: impl Into<Point>) -> &mut Self {
self.sink.quad_to(
self.transform.transform_point(control.into()),
self.transform.transform_point(to.into()),
);
self
}
fn curve_to(
&mut self,
control1: impl Into<Point>,
control2: impl Into<Point>,
to: impl Into<Point>,
) -> &mut Self {
self.sink.curve_to(
self.transform.transform_point(control1.into()),
self.transform.transform_point(control2.into()),
self.transform.transform_point(to.into()),
);
self
}
fn close(&mut self) -> &mut Self {
self.sink.close();
self
}
}
impl PathBuilder for BoundsBuilder {
fn current_point(&self) -> Point {
self.current
}
fn move_to(&mut self, to: impl Into<Point>) -> &mut Self {
let p = to.into();
self.add(p);
self.current = p;
self
}
fn line_to(&mut self, to: impl Into<Point>) -> &mut Self {
let p = to.into();
self.add(p);
self.current = p;
self
}
fn quad_to(&mut self, control: impl Into<Point>, to: impl Into<Point>) -> &mut Self {
self.add(control.into());
let p = to.into();
self.add(p);
self.current = p;
self
}
fn curve_to(
&mut self,
control1: impl Into<Point>,
control2: impl Into<Point>,
to: impl Into<Point>,
) -> &mut Self {
self.add(control1.into());
self.add(control2.into());
let p = to.into();
self.add(p);
self.current = p;
self
}
fn close(&mut self) -> &mut Self {
self
}
}
/// An iterator that generates cubic bezier curves for an arc.
#[derive(Copy, Clone, Default)]
pub struct Arc {
count: usize,
center: (f32, f32),
radii: (f32, f32),
cosphi: f32,
sinphi: f32,
ang1: f32,
ang2: f32,
a: f32,
}
impl Arc {
pub fn new(
from: impl Into<[f32; 2]>,
rx: f32,
ry: f32,
angle: f32,
size: ArcSize,
sweep: ArcSweep,
to: impl Into<[f32; 2]>,
) -> Self {
let from = from.into();
let to = to.into();
let (px, py) = (from[0], from[1]);
const TAU: f32 = 3.141579 * 2.;
let (sinphi, cosphi) = angle.sin_cos();
let pxp = cosphi * (px - to[0]) / 2. + sinphi * (py - to[1]) / 2.;
let pyp = -sinphi * (px - to[0]) / 2. + cosphi * (py - to[1]) / 2.;
if pxp == 0. && pyp == 0. {
return Self::default();
}
let mut rx = rx.abs();
let mut ry = ry.abs();
let lambda = pxp.powi(2) / rx.powi(2) + pyp.powi(2) / ry.powi(2);
if lambda > 1. {
let s = lambda.sqrt();
rx *= s;
ry *= s;
}
let large_arc = size == ArcSize::Large;
let sweep = sweep == ArcSweep::Positive;
let (cx, cy, ang1, mut ang2) = {
fn vec_angle(ux: f32, uy: f32, vx: f32, vy: f32) -> f32 {
let sign = if (ux * vy - uy * vx) < 0. { -1. } else { 1. };
let dot = (ux * vx + uy * vy).clamp(-1., 1.);
sign * dot.acos()
}
let rxsq = rx * rx;
let rysq = ry * ry;
let pxpsq = pxp * pxp;
let pypsq = pyp * pyp;
let mut radicant = (rxsq * rysq) - (rxsq * pypsq) - (rysq * pxpsq);
if radicant < 0. {
radicant = 0.;
}
radicant /= (rxsq * pypsq) + (rysq * pxpsq);
radicant = radicant.sqrt() * if large_arc == sweep { -1. } else { 1. };
let cxp = radicant * rx / ry * pyp;
let cyp = radicant * -ry / rx * pxp;
let cx = cosphi * cxp - sinphi * cyp + (px + to[0]) / 2.;
let cy = sinphi * cxp + cosphi * cyp + (py + to[1]) / 2.;
let vx1 = (pxp - cxp) / rx;
let vy1 = (pyp - cyp) / ry;
let vx2 = (-pxp - cxp) / rx;
let vy2 = (-pyp - cyp) / ry;
let ang1 = vec_angle(1., 0., vx1, vy1);
let mut ang2 = vec_angle(vx1, vy1, vx2, vy2);
if !sweep && ang2 > 0. {
ang2 -= TAU;
}
if sweep && ang2 < 0. {
ang2 += TAU;
}
(cx, cy, ang1, ang2)
};
let mut ratio = ang2.abs() / (TAU / 4.);
if (1. - ratio).abs() < 0.0000001 {
ratio = 1.
}
let segments = ratio.ceil().max(1.);
ang2 /= segments;
let a = if ang2 == f32::consts::FRAC_PI_2 {
0.551915024494
} else if ang2 == -f32::consts::FRAC_PI_2 {
-0.551915024494
} else {
4. / 3. * (ang2 / 4.).tan()
};
Self {
count: segments as usize,
center: (cx, cy),
radii: (rx, ry),
sinphi,
cosphi,
ang1,
ang2,
a,
}
}
}
impl Iterator for Arc {
type Item = Command;
fn next(&mut self) -> Option<Self::Item> {
if self.count == 0 {
return None;
}
self.count -= 1;
let (y1, x1) = self.ang1.sin_cos();
let (y2, x2) = (self.ang1 + self.ang2).sin_cos();
let a = self.a;
let (cx, cy) = self.center;
let (rx, ry) = self.radii;
let sinphi = self.sinphi;
let cosphi = self.cosphi;
let c1 = Point::new((x1 - y1 * a) * rx, (y1 + x1 * a) * ry);
let c1 = Point::new(
cx + (cosphi * c1.x - sinphi * c1.y),
cy + (sinphi * c1.x + cosphi * c1.y),
);
let c2 = Point::new((x2 + y2 * a) * rx, (y2 - x2 * a) * ry);
let c2 = Point::new(
cx + (cosphi * c2.x - sinphi * c2.y),
cy + (sinphi * c2.x + cosphi * c2.y),
);
let p = Point::new(x2 * rx, y2 * ry);
let p = Point::new(
cx + (cosphi * p.x - sinphi * p.y),
cy + (sinphi * p.x + cosphi * p.y),
);
self.ang1 += self.ang2;
Some(Command::CurveTo(c1, c2, p))
}
}
#[allow(clippy::too_many_arguments)]
pub fn arc(
sink: &mut impl PathBuilder,
from: Point,
rx: f32,
ry: f32,
angle: f32,
size: ArcSize,
sweep: ArcSweep,
to: Point,
) {
let p = from;
let (px, py) = (p.x, p.y);
const TAU: f32 = core::f32::consts::PI * 2.;
let (sinphi, cosphi) = angle.sin_cos();
let pxp = cosphi * (px - to.x) / 2. + sinphi * (py - to.y) / 2.;
let pyp = -sinphi * (px - to.x) / 2. + cosphi * (py - to.y) / 2.;
if pxp == 0. && pyp == 0. {
return;
}
let mut rx = rx.abs();
let mut ry = ry.abs();
let lambda = pxp.powi(2) / rx.powi(2) + pyp.powi(2) / ry.powi(2);
if lambda > 1. {
let s = lambda.sqrt();
rx *= s;
ry *= s;
}
let large_arc = size == ArcSize::Large;
let sweep = sweep == ArcSweep::Positive;
let (cx, cy, mut ang1, mut ang2) = {
fn vec_angle(ux: f32, uy: f32, vx: f32, vy: f32) -> f32 {
let sign = if (ux * vy - uy * vx) < 0. { -1. } else { 1. };
let dot = (ux * vx + uy * vy).clamp(-1., 1.);
sign * dot.acos()
}
let rxsq = rx * rx;
let rysq = ry * ry;
let pxpsq = pxp * pxp;
let pypsq = pyp * pyp;
let mut radicant = (rxsq * rysq) - (rxsq * pypsq) - (rysq * pxpsq);
if radicant < 0. {
radicant = 0.;
}
radicant /= (rxsq * pypsq) + (rysq * pxpsq);
radicant = radicant.sqrt() * if large_arc == sweep { -1. } else { 1. };
let cxp = radicant * rx / ry * pyp;
let cyp = radicant * -ry / rx * pxp;
let cx = cosphi * cxp - sinphi * cyp + (px + to.x) / 2.;
let cy = sinphi * cxp + cosphi * cyp + (py + to.y) / 2.;
let vx1 = (pxp - cxp) / rx;
let vy1 = (pyp - cyp) / ry;
let vx2 = (-pxp - cxp) / rx;
let vy2 = (-pyp - cyp) / ry;
let ang1 = vec_angle(1., 0., vx1, vy1);
let mut ang2 = vec_angle(vx1, vy1, vx2, vy2);
if !sweep && ang2 > 0. {
ang2 -= TAU;
}
if sweep && ang2 < 0. {
ang2 += TAU;
}
(cx, cy, ang1, ang2)
};
let mut ratio = ang2.abs() / (TAU / 4.);
if (1. - ratio).abs() < 0.0000001 {
ratio = 1.
}
let segments = ratio.ceil().max(1.);
ang2 /= segments;
let a = if ang2 == f32::consts::FRAC_PI_2 {
0.551915024494
} else if ang2 == -f32::consts::FRAC_PI_2 {
-0.551915024494
} else {
4. / 3. * (ang2 / 4.).tan()
};
for _ in 0..segments as usize {
let (y1, x1) = ang1.sin_cos();
let (y2, x2) = (ang1 + ang2).sin_cos();
let c1 = Point::new((x1 - y1 * a) * rx, (y1 + x1 * a) * ry);
let c1 = Point::new(
cx + (cosphi * c1.x - sinphi * c1.y),
cy + (sinphi * c1.x + cosphi * c1.y),
);
let c2 = Point::new((x2 + y2 * a) * rx, (y2 - x2 * a) * ry);
let c2 = Point::new(
cx + (cosphi * c2.x - sinphi * c2.y),
cy + (sinphi * c2.x + cosphi * c2.y),
);
let p = Point::new(x2 * rx, y2 * ry);
let p = Point::new(
cx + (cosphi * p.x - sinphi * p.y),
cy + (sinphi * p.x + cosphi * p.y),
);
sink.curve_to(c1, c2, p);
ang1 += ang2;
}
}

228
vendor/zeno/src/path_data.rs vendored Normal file
View File

@@ -0,0 +1,228 @@
//! Path data.
use super::command::{Command, PointsCommands, Verb};
use super::geometry::{Point, Transform};
use super::path_builder::PathBuilder;
use super::segment::segments;
use super::svg_parser::SvgCommands;
#[cfg(feature = "eval")]
use super::stroke::stroke_into;
#[cfg(feature = "eval")]
use super::style::*;
#[cfg(feature = "eval")]
use super::geometry::{Bounds, BoundsBuilder};
#[cfg(feature = "eval")]
use super::path_builder::TransformSink;
use crate::lib::Vec;
/// Trait for types that represent path data.
///
/// A primary design goal for this crate is to be agnostic with regard to
/// storage of path data. This trait provides the abstraction to make that
/// possible.
///
/// All path data is consumed internally as an iterator over path
/// [commands](Command) and as such, this trait is similar to
/// the `IntoIterator` trait, but restricted to iterators of commands and
/// without consuming itself.
///
/// Implementations of this trait are provided for SVG path data (in the form
/// of strings), slices/vectors of commands, and the common point and
/// verb list structure (as the tuple `(&[Point], &[Verb])`).
///
/// As such, these paths are all equivalent:
///
/// ```rust
/// use zeno::{Command, PathData, Point, Verb};
///
/// // SVG path data
/// let path1 = "M1,2 L3,4";
///
/// // Slice of commands
/// let path2 = &[
/// Command::MoveTo(Point::new(1.0, 2.0)),
/// Command::LineTo(Point::new(3.0, 4.0)),
/// ][..];
///
/// // Tuple of slices to points and verbs
/// let path3 = (
/// &[Point::new(1.0, 2.0), Point::new(3.0, 4.0)][..],
/// &[Verb::MoveTo, Verb::LineTo][..],
/// );
///
/// assert!(path1.commands().eq(path2.commands()));
/// assert!(path2.commands().eq(path3.commands()));
/// ```
///
/// Implementing `PathData` is similar to implementing `IntoIterator`:
///
/// ```rust
/// use zeno::{Command, PathData};
///
/// pub struct MyPath {
/// data: Vec<Command>
/// }
///
/// impl<'a> PathData for &'a MyPath {
/// // Copied here because PathData expects Commands by value
/// type Commands = std::iter::Copied<std::slice::Iter<'a, Command>>;
///
/// fn commands(&self) -> Self::Commands {
/// self.data.iter().copied()
/// }
/// }
/// ```
///
/// The provided `copy_into()` method evaluates the command iterator and
/// submits the commands to a sink. You should also implement this if you
/// have a more direct method of dispatching to a sink as rasterizer
/// performance can be sensitive to latencies here.
pub trait PathData {
/// Command iterator.
type Commands: Iterator<Item = Command> + Clone;
/// Returns an iterator over the commands described by the path data.
fn commands(&self) -> Self::Commands;
/// Copies the path data into the specified sink.
fn copy_to(&self, sink: &mut impl PathBuilder) {
for cmd in self.commands() {
use Command::*;
match cmd {
MoveTo(p) => sink.move_to(p),
LineTo(p) => sink.line_to(p),
QuadTo(c, p) => sink.quad_to(c, p),
CurveTo(c1, c2, p) => sink.curve_to(c1, c2, p),
Close => sink.close(),
};
}
}
}
/// Computes the total length of the path.
pub fn length(data: impl PathData, transform: Option<Transform>) -> f32 {
let data = data.commands();
let mut length = 0.;
if let Some(transform) = transform {
for s in segments(data.map(|cmd| cmd.transform(&transform)), false) {
length += s.length();
}
} else {
for s in segments(data, false) {
length += s.length();
}
}
length
}
/// Computes the bounding box of the path.
#[cfg(feature = "eval")]
pub fn bounds<'a>(
data: impl PathData,
style: impl Into<Style<'a>>,
transform: Option<Transform>,
) -> Bounds {
let style = style.into();
let mut bounds = BoundsBuilder::new();
apply(data, style, transform, &mut bounds);
bounds.build()
}
/// Applies the style and transform to the path and emits the result to the
/// specified sink.
#[cfg(feature = "eval")]
pub fn apply<'a>(
data: impl PathData,
style: impl Into<Style<'a>>,
transform: Option<Transform>,
sink: &mut impl PathBuilder,
) -> Fill {
let style = style.into();
match style {
Style::Fill(fill) => {
if let Some(transform) = transform {
let mut transform_sink = TransformSink { sink, transform };
data.copy_to(&mut transform_sink);
fill
} else {
data.copy_to(sink);
fill
}
}
Style::Stroke(stroke) => {
if let Some(transform) = transform {
if stroke.scale {
let mut transform_sink = TransformSink { sink, transform };
stroke_into(data.commands(), &stroke, &mut transform_sink);
} else {
stroke_into(
data.commands().map(|cmd| cmd.transform(&transform)),
&stroke,
sink,
);
}
} else {
stroke_into(data.commands(), &stroke, sink);
}
Fill::NonZero
}
}
}
impl<T> PathData for &'_ T
where
T: PathData,
{
type Commands = T::Commands;
fn commands(&self) -> Self::Commands {
T::commands(*self)
}
#[inline(always)]
fn copy_to(&self, sink: &mut impl PathBuilder) {
T::copy_to(*self, sink)
}
}
impl<'a> PathData for &'a str {
type Commands = SvgCommands<'a>;
fn commands(&self) -> Self::Commands {
SvgCommands::new(self)
}
}
impl<'a> PathData for (&'a [Point], &'a [Verb]) {
type Commands = PointsCommands<'a>;
fn commands(&self) -> Self::Commands {
PointsCommands::new(self.0, self.1)
}
#[inline(always)]
fn copy_to(&self, sink: &mut impl PathBuilder) {
self.commands().copy_to(sink);
}
}
impl<'a> PathData for &'a [Command] {
type Commands = core::iter::Copied<core::slice::Iter<'a, Command>>;
fn commands(&self) -> Self::Commands {
self.iter().copied()
}
}
impl<'a> PathData for &'a Vec<Command> {
type Commands = core::iter::Copied<core::slice::Iter<'a, Command>>;
fn commands(&self) -> Self::Commands {
self.iter().copied()
}
}

831
vendor/zeno/src/raster.rs vendored Normal file
View File

@@ -0,0 +1,831 @@
//! Path rasterizer.
#![allow(clippy::too_many_arguments)]
use super::geometry::{Point, Vector};
use super::path_builder::PathBuilder;
use super::style::Fill;
use crate::lib::Vec;
use core::fmt;
#[inline(always)]
fn coverage(fill: Fill, mut coverage: i32) -> u8 {
coverage >>= PIXEL_BITS * 2 + 1 - 8;
if fill == Fill::EvenOdd {
coverage &= 511;
if coverage >= 256 {
coverage = 511i32.wrapping_sub(coverage);
}
} else {
if coverage < 0 {
coverage = !coverage;
}
if coverage >= 256 {
coverage = 255;
}
}
coverage as u8
}
pub struct Rasterizer<'a, S: RasterStorage> {
storage: &'a mut S,
xmin: i32,
xmax: i32,
ymin: i32,
ymax: i32,
height: i32,
shift: Vector,
start: FixedPoint,
closed: bool,
current: Point,
x: i32,
y: i32,
px: i32,
py: i32,
cover: i32,
area: i32,
invalid: bool,
}
impl<'a, S: RasterStorage> Rasterizer<'a, S> {
pub fn new(storage: &'a mut S) -> Self {
Self {
storage,
xmin: 0,
xmax: 0,
ymin: 0,
ymax: 0,
height: 0,
shift: Vector::ZERO,
start: FixedPoint::default(),
closed: false,
current: Point::ZERO,
x: 0,
y: 0,
px: 0,
py: 0,
cover: 0,
area: 0,
invalid: false,
}
}
pub fn rasterize(
&mut self,
shift: Vector,
width: u32,
height: u32,
apply: &mut impl FnMut(&mut Self),
fill: Fill,
buffer: &mut [u8],
pitch: usize,
y_up: bool,
) {
let w = width as i32;
let h = height as i32;
self.storage
.reset(FixedPoint { x: 0, y: 0 }, FixedPoint { x: w, y: h });
self.shift = shift;
self.start = FixedPoint::default();
self.closed = true;
self.current = Point::ZERO;
self.xmin = 0;
self.ymin = 0;
self.xmax = w;
self.ymax = h;
self.height = h;
self.x = 0;
self.y = 0;
self.px = 0;
self.py = 0;
self.invalid = true;
apply(self);
if !self.closed {
self.line_to(self.start);
}
if !self.invalid {
self.storage.set(self.x, self.y, self.area, self.cover);
}
let indices = self.storage.indices();
let cells = self.storage.cells();
let min = FixedPoint::new(self.xmin, self.ymin);
let max = FixedPoint::new(self.xmax, self.ymax);
let height = height as usize;
for (i, &index) in indices.iter().enumerate() {
if index != -1 {
let y = ((i as i32) - min.y) as usize;
let row_offset = if y_up {
pitch * (height - 1 - y)
} else {
pitch * y
};
let row = &mut buffer[row_offset..];
let mut x = min.x;
let mut cover = 0;
let mut area;
let mut index = index;
loop {
let cell = &cells[index as usize];
if cover != 0 && cell.x > x {
let count = (cell.x - x) as usize;
let c = coverage(fill, cover);
let xi = x as usize;
for b in &mut row[xi..xi + count] {
*b = c;
}
}
cover = cover.wrapping_add(cell.cover.wrapping_mul(ONE_PIXEL * 2));
area = cover.wrapping_sub(cell.area);
if area != 0 && cell.x >= min.x {
let count = 1;
let c = coverage(fill, area);
let xi = cell.x as usize;
for b in &mut row[xi..xi + count] {
*b = c;
}
}
x = cell.x + 1;
index = cell.next;
if index == -1 {
break;
}
}
if cover != 0 {
let count = (max.x - x) as usize;
let c = coverage(fill, cover);
let xi = x as usize;
for b in &mut row[xi..xi + count] {
*b = c;
}
}
}
}
}
pub fn rasterize_write(
&mut self,
shift: Vector,
width: u32,
height: u32,
apply: &mut impl FnMut(&mut Self),
fill: Fill,
pitch: usize,
y_up: bool,
write: &mut impl FnMut(usize, usize, usize, u8),
) {
let w = width as i32;
let h = height as i32;
self.storage
.reset(FixedPoint { x: 0, y: 0 }, FixedPoint { x: w, y: h });
self.shift = shift;
self.start = FixedPoint::default();
self.closed = true;
self.current = Point::ZERO;
self.xmin = 0;
self.ymin = 0;
self.xmax = w;
self.ymax = h;
self.height = h;
self.x = 0;
self.y = 0;
self.px = 0;
self.py = 0;
self.invalid = true;
apply(self);
if !self.closed {
self.line_to(self.start);
}
if !self.invalid {
self.storage.set(self.x, self.y, self.area, self.cover);
}
let indices = self.storage.indices();
let cells = self.storage.cells();
let min = FixedPoint::new(self.xmin, self.ymin);
let max = FixedPoint::new(self.xmax, self.ymax);
let height = height as usize;
for (i, &index) in indices.iter().enumerate() {
if index != -1 {
let y = ((i as i32) - min.y) as usize;
let row_offset = if y_up {
pitch * (height - 1 - y)
} else {
pitch * y
};
let mut x = min.x;
let mut cover = 0;
let mut area;
let mut index = index;
loop {
let cell = &cells[index as usize];
if cover != 0 && cell.x > x {
let count = (cell.x - x) as usize;
let c = coverage(fill, cover);
let xi = x as usize;
write(row_offset, xi, count, c);
}
cover = cover.wrapping_add(cell.cover.wrapping_mul(ONE_PIXEL * 2));
area = cover.wrapping_sub(cell.area);
if area != 0 && cell.x >= min.x {
let count = 1;
let c = coverage(fill, area);
let xi = cell.x as usize;
write(row_offset, xi, count, c);
}
x = cell.x + 1;
index = cell.next;
if index == -1 {
break;
}
}
if cover != 0 {
let count = (max.x - x) as usize;
let c = coverage(fill, cover);
let xi = x as usize;
write(row_offset, xi, count, c);
}
}
}
}
#[inline(always)]
fn set_cell(&mut self, x: i32, y: i32) {
if !self.invalid && (self.area != 0 || self.cover != 0) {
self.storage.set(self.x, self.y, self.area, self.cover);
}
self.area = 0;
self.cover = 0;
self.x = if x > (self.xmin - 1) {
x
} else {
self.xmin - 1
};
self.y = y;
self.invalid = y >= self.ymax || y < self.ymin || x >= self.xmax;
}
fn move_to(&mut self, to: FixedPoint) {
self.set_cell(trunc(to.x), trunc(to.y));
self.px = to.x;
self.py = to.y;
}
fn line_to(&mut self, to: FixedPoint) {
let to_x = to.x;
let to_y = to.y;
let mut ey1 = trunc(self.py);
let ey2 = trunc(to_y);
if (ey1 >= self.ymax && ey2 >= self.ymax) || (ey1 < self.ymin && ey2 < self.ymin) {
self.px = to_x;
self.py = to_y;
return;
}
let mut ex1 = trunc(self.px);
let ex2 = trunc(to_x);
let mut fx1 = fract(self.px);
let mut fy1 = fract(self.py);
let dx = to_x - self.px;
let dy = to_y - self.py;
if ex1 == ex2 && ey1 == ey2 {
// empty
} else if dy == 0 {
self.set_cell(ex2, ey2);
self.px = to_x;
self.py = to_y;
return;
} else if dx == 0 {
if dy > 0 {
loop {
let fy2 = ONE_PIXEL;
self.cover += fy2 - fy1;
self.area += (fy2 - fy1) * fx1 * 2;
fy1 = 0;
ey1 += 1;
self.set_cell(ex1, ey1);
if ey1 == ey2 {
break;
}
}
} else {
loop {
let fy2 = 0;
self.cover += fy2 - fy1;
self.area += (fy2 - fy1) * fx1 * 2;
fy1 = ONE_PIXEL;
ey1 -= 1;
self.set_cell(ex1, ey1);
if ey1 == ey2 {
break;
}
}
}
} else {
let mut prod = dx * fy1 - dy * fx1;
let dx_r = if ex1 != ex2 { (0x00FFFFFF) / dx } else { 0 };
let dy_r = if ey1 != ey2 { (0x00FFFFFF) / dy } else { 0 };
fn udiv(a: i32, b: i32) -> i32 {
((a as u64 * b as u64) >> (4 * 8 - PIXEL_BITS)) as i32
}
loop {
if prod <= 0 && prod - dx * ONE_PIXEL > 0 {
let fx2 = 0;
let fy2 = udiv(-prod, -dx_r);
prod -= dy * ONE_PIXEL;
self.cover += fy2 - fy1;
self.area += (fy2 - fy1) * (fx1 + fx2);
fx1 = ONE_PIXEL;
fy1 = fy2;
ex1 -= 1;
} else if prod - dx * ONE_PIXEL <= 0 && prod - dx * ONE_PIXEL + dy * ONE_PIXEL > 0 {
prod -= dx * ONE_PIXEL;
let fx2 = udiv(-prod, dy_r);
let fy2 = ONE_PIXEL;
self.cover += fy2 - fy1;
self.area += (fy2 - fy1) * (fx1 + fx2);
fx1 = fx2;
fy1 = 0;
ey1 += 1;
} else if prod - dx * ONE_PIXEL + dy * ONE_PIXEL <= 0 && prod + dy * ONE_PIXEL >= 0
{
prod += dy * ONE_PIXEL;
let fx2 = ONE_PIXEL;
let fy2 = udiv(prod, dx_r);
self.cover += fy2 - fy1;
self.area += (fy2 - fy1) * (fx1 + fx2);
fx1 = 0;
fy1 = fy2;
ex1 += 1;
} else {
let fx2 = udiv(prod, -dy_r);
let fy2 = 0;
prod += dx * ONE_PIXEL;
self.cover += fy2 - fy1;
self.area += (fy2 - fy1) * (fx1 + fx2);
fx1 = fx2;
fy1 = ONE_PIXEL;
ey1 -= 1;
}
self.set_cell(ex1, ey1);
if ex1 == ex2 && ey1 == ey2 {
break;
}
}
}
let fx2 = fract(to_x);
let fy2 = fract(to_y);
self.cover += fy2 - fy1;
self.area += (fy2 - fy1) * (fx1 + fx2);
self.px = to_x;
self.py = to_y;
}
#[allow(clippy::uninit_assumed_init, invalid_value)]
fn quad_to(&mut self, control: FixedPoint, to: FixedPoint) {
let mut arc = [FixedPoint::default(); 16 * 2 + 1];
arc[0].x = to.x;
arc[0].y = to.y;
arc[1].x = control.x;
arc[1].y = control.y;
arc[2].x = self.px;
arc[2].y = self.py;
if (trunc(arc[0].y) >= self.ymax
&& trunc(arc[1].y) >= self.ymax
&& trunc(arc[2].y) >= self.ymax)
|| (trunc(arc[0].y) < self.ymin
&& trunc(arc[1].y) < self.ymin
&& trunc(arc[2].y) < self.ymin)
{
self.px = arc[0].x;
self.py = arc[0].y;
return;
}
let mut dx = (arc[2].x + arc[0].x - 2 * arc[1].x).abs();
let dy = (arc[2].y + arc[0].y - 2 * arc[1].y).abs();
if dx < dy {
dx = dy;
}
let mut draw = 1;
while dx > ONE_PIXEL / 4 {
dx >>= 2;
draw <<= 1;
}
let mut a = 0;
loop {
let mut split = draw & (-draw);
loop {
split >>= 1;
if split == 0 {
break;
}
split_quad(&mut arc[a..]);
a += 2;
}
let p = arc[a];
self.line_to(p);
draw -= 1;
if draw == 0 {
break;
}
a -= 2;
}
}
#[allow(clippy::uninit_assumed_init, invalid_value)]
fn curve_to(&mut self, control1: FixedPoint, control2: FixedPoint, to: FixedPoint) {
let mut arc = [FixedPoint::default(); 16 * 8 + 1];
arc[0].x = to.x;
arc[0].y = to.y;
arc[1].x = control2.x;
arc[1].y = control2.y;
arc[2].x = control1.x;
arc[2].y = control1.y;
arc[3].x = self.px;
arc[3].y = self.py;
if (trunc(arc[0].y) >= self.ymax
&& trunc(arc[1].y) >= self.ymax
&& trunc(arc[2].y) >= self.ymax
&& trunc(arc[3].y) >= self.ymax)
|| (trunc(arc[0].y) < self.ymin
&& trunc(arc[1].y) < self.ymin
&& trunc(arc[2].y) < self.ymin
&& trunc(arc[3].y) < self.ymin)
{
self.px = arc[0].x;
self.py = arc[0].y;
return;
}
let mut a = 0;
loop {
if (2 * arc[a].x - 3 * arc[a + 1].x + arc[a + 3].x).abs() > ONE_PIXEL / 2
|| (2 * arc[a].y - 3 * arc[a + 1].y + arc[a + 3].y).abs() > ONE_PIXEL / 2
|| (arc[a].x - 3 * arc[a + 2].x + 2 * arc[a + 3].x).abs() > ONE_PIXEL / 2
|| (arc[a].y - 3 * arc[a + 2].y + 2 * arc[a + 3].y).abs() > ONE_PIXEL / 2
{
let buf = &mut arc[a..];
// if buf.len() < 7 {
// return;
// }
if buf.len() >= 7 {
split_cubic(buf);
a += 3;
continue;
} else {
self.line_to(to);
return;
}
}
let p = arc[a];
self.line_to(p);
if a == 0 {
return;
}
a -= 3;
}
}
}
impl<S: RasterStorage> PathBuilder for Rasterizer<'_, S> {
fn current_point(&self) -> Point {
self.current + self.shift
}
#[inline(always)]
fn move_to(&mut self, to: impl Into<Point>) -> &mut Self {
if !self.closed {
self.line_to(self.start);
}
let to = to.into();
let p = FixedPoint::from_point(to + self.shift);
self.move_to(p);
self.closed = false;
self.start = p;
self.current = to;
self
}
#[inline(always)]
fn line_to(&mut self, to: impl Into<Point>) -> &mut Self {
let to = to.into();
self.current = to;
self.closed = false;
self.line_to(FixedPoint::from_point(to + self.shift));
self
}
#[inline(always)]
fn quad_to(&mut self, control: impl Into<Point>, to: impl Into<Point>) -> &mut Self {
let to = to.into();
self.current = to;
self.closed = false;
self.quad_to(
FixedPoint::from_point(control.into() + self.shift),
FixedPoint::from_point(to + self.shift),
);
self
}
#[inline(always)]
fn curve_to(
&mut self,
control1: impl Into<Point>,
control2: impl Into<Point>,
to: impl Into<Point>,
) -> &mut Self {
let to = to.into();
self.current = to;
self.closed = false;
self.curve_to(
FixedPoint::from_point(control1.into() + self.shift),
FixedPoint::from_point(control2.into() + self.shift),
FixedPoint::from_point(to + self.shift),
);
self
}
#[inline(always)]
fn close(&mut self) -> &mut Self {
self.line_to(self.start);
self.closed = true;
self
}
}
#[derive(Copy, Clone, Default)]
pub struct Cell {
x: i32,
cover: i32,
area: i32,
next: i32,
}
pub trait RasterStorage {
fn reset(&mut self, min: FixedPoint, max: FixedPoint);
fn cells(&self) -> &[Cell];
fn indices(&self) -> &[i32];
fn set(&mut self, x: i32, y: i32, area: i32, cover: i32);
}
#[derive(Default)]
pub struct HeapStorage {
min: FixedPoint,
max: FixedPoint,
cells: Vec<Cell>,
indices: Vec<i32>,
}
impl RasterStorage for HeapStorage {
fn reset(&mut self, min: FixedPoint, max: FixedPoint) {
self.min = min;
self.max = max;
self.cells.clear();
self.indices.clear();
self.indices.resize((max.y - min.y) as usize, -1);
}
fn cells(&self) -> &[Cell] {
&self.cells
}
fn indices(&self) -> &[i32] {
&self.indices
}
#[inline(always)]
#[allow(clippy::comparison_chain)]
fn set(&mut self, x: i32, y: i32, area: i32, cover: i32) {
let yindex = (y - self.min.y) as usize;
let mut cell_index = self.indices[yindex];
let mut last_index = -1;
while cell_index != -1 {
let cell = &mut self.cells[cell_index as usize];
if cell.x > x {
break;
} else if cell.x == x {
cell.area = cell.area.wrapping_add(area);
cell.cover = cell.cover.wrapping_add(cover);
return;
}
last_index = cell_index;
cell_index = cell.next;
}
let new_index = self.cells.len();
let cell = Cell {
x,
area,
cover,
next: cell_index,
};
if last_index != -1 {
self.cells[last_index as usize].next = new_index as i32;
} else {
self.indices[yindex] = new_index as i32;
}
self.cells.push(cell);
}
}
const MAX_CELLS: usize = 1024;
const MAX_BAND: usize = 512;
pub struct AdaptiveStorage {
min: FixedPoint,
max: FixedPoint,
height: usize,
cell_count: usize,
cells: [Cell; MAX_CELLS],
heap_cells: Vec<Cell>,
indices: [i32; MAX_BAND],
heap_indices: Vec<i32>,
}
impl AdaptiveStorage {
#[allow(clippy::uninit_assumed_init, invalid_value)]
pub fn new() -> Self {
Self {
min: FixedPoint::default(),
max: FixedPoint::default(),
height: 0,
cell_count: 0,
cells: [Default::default(); MAX_CELLS],
heap_cells: Vec::new(),
indices: [Default::default(); MAX_BAND],
heap_indices: Vec::new(),
}
}
}
impl RasterStorage for AdaptiveStorage {
fn reset(&mut self, min: FixedPoint, max: FixedPoint) {
self.min = min;
self.max = max;
self.height = (max.y - min.y) as usize;
self.cell_count = 0;
self.heap_cells.clear();
self.heap_indices.clear();
if self.height > MAX_BAND {
self.heap_indices.resize((max.y - min.y) as usize, -1);
} else {
for i in 0..self.height {
self.indices[i] = -1;
}
}
}
fn cells(&self) -> &[Cell] {
if self.cell_count > MAX_CELLS {
&self.heap_cells
} else {
&self.cells
}
}
fn indices(&self) -> &[i32] {
if self.height > MAX_BAND {
&self.heap_indices
} else {
&self.indices[..self.height]
}
}
#[inline(always)]
#[allow(clippy::comparison_chain)]
fn set(&mut self, x: i32, y: i32, area: i32, cover: i32) {
let yindex = (y - self.min.y) as usize;
let indices = if self.height > MAX_BAND {
&mut self.heap_indices[..]
} else {
&mut self.indices[..]
};
let cells = if !self.heap_cells.is_empty() {
&mut self.heap_cells[..]
} else {
&mut self.cells[..]
};
let mut cell_index = indices[yindex];
let mut last_index = -1;
while cell_index != -1 {
let cell = &mut cells[cell_index as usize];
if cell.x > x {
break;
} else if cell.x == x {
cell.area = cell.area.wrapping_add(area);
cell.cover = cell.cover.wrapping_add(cover);
return;
}
last_index = cell_index;
cell_index = cell.next;
}
let new_index = self.cell_count;
self.cell_count += 1;
let cell = Cell {
x,
area,
cover,
next: cell_index,
};
if last_index != -1 {
cells[last_index as usize].next = new_index as i32;
} else {
indices[yindex] = new_index as i32;
}
if new_index < MAX_CELLS {
cells[new_index] = cell;
} else {
if self.heap_cells.is_empty() {
self.heap_cells.extend_from_slice(&self.cells);
}
self.heap_cells.push(cell);
}
}
}
const _MAX_DIM: u32 = i16::MAX as u32;
fn split_quad(base: &mut [FixedPoint]) {
let mut a;
let mut b;
base[4].x = base[2].x;
a = base[0].x + base[1].x;
b = base[1].x + base[2].x;
base[3].x = b >> 1;
base[2].x = (a + b) >> 2;
base[1].x = a >> 1;
base[4].y = base[2].y;
a = base[0].y + base[1].y;
b = base[1].y + base[2].y;
base[3].y = b >> 1;
base[2].y = (a + b) >> 2;
base[1].y = a >> 1;
}
fn split_cubic(base: &mut [FixedPoint]) {
let mut a;
let mut b;
let mut c;
base[6].x = base[3].x;
a = base[0].x + base[1].x;
b = base[1].x + base[2].x;
c = base[2].x + base[3].x;
base[5].x = c >> 1;
c += b;
base[4].x = c >> 2;
base[1].x = a >> 1;
a += b;
base[2].x = a >> 2;
base[3].x = (a + c) >> 3;
base[6].y = base[3].y;
a = base[0].y + base[1].y;
b = base[1].y + base[2].y;
c = base[2].y + base[3].y;
base[5].y = c >> 1;
c += b;
base[4].y = c >> 2;
base[1].y = a >> 1;
a += b;
base[2].y = a >> 2;
base[3].y = (a + c) >> 3;
}
#[derive(Copy, Clone, Default)]
pub struct FixedPoint {
pub x: i32,
pub y: i32,
}
impl fmt::Debug for FixedPoint {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
impl FixedPoint {
pub fn new(x: i32, y: i32) -> Self {
Self { x, y }
}
#[inline(always)]
pub fn from_point(p: Point) -> Self {
Self {
x: to_fixed(p.x),
y: to_fixed(p.y),
}
}
}
#[inline(always)]
fn to_fixed(v: f32) -> i32 {
unsafe { (v * 256.).to_int_unchecked() }
}
const PIXEL_BITS: i32 = 8;
const ONE_PIXEL: i32 = 1 << PIXEL_BITS;
#[inline(always)]
fn trunc(x: i32) -> i32 {
x >> PIXEL_BITS
}
#[inline(always)]
fn fract(x: i32) -> i32 {
x & (ONE_PIXEL - 1)
}

102
vendor/zeno/src/scratch.rs vendored Normal file
View File

@@ -0,0 +1,102 @@
//! Context for reusing dynamic memory allocations.
use super::geometry::{Bounds, BoundsBuilder, Transform};
use super::path_builder::{PathBuilder, TransformSink};
use super::path_data::PathData;
use super::raster::HeapStorage;
use super::segment::Segment;
use super::stroke::stroke_with_storage;
use super::style::{Fill, Style};
use crate::lib::Vec;
use core::borrow::Borrow;
/// Scratch memory for reusable heap allocations.
#[derive(Default)]
pub struct Scratch {
pub(super) inner: Inner,
pub(super) render: HeapStorage,
}
impl Scratch {
/// Creates a new scratch memory context.
pub fn new() -> Self {
Self::default()
}
/// Applies the style and transform to the path and emits the result to the specified sink.
pub fn apply<'a>(
&mut self,
data: impl PathData,
style: impl Into<Style<'a>>,
transform: Option<Transform>,
sink: &mut impl PathBuilder,
) -> Fill {
self.inner.apply(data, &style.into(), transform, sink)
}
/// Computes the bounding box of the path.
pub fn bounds<'a>(
&mut self,
data: impl PathData,
style: impl Into<Style<'a>>,
transform: Option<Transform>,
) -> Bounds {
let style = style.into();
let mut bounds = BoundsBuilder::new();
self.apply(data, style, transform, &mut bounds);
bounds.build()
}
}
#[derive(Default)]
pub(super) struct Inner {
pub segments: Vec<Segment>,
}
impl Inner {
pub fn apply(
&mut self,
data: impl PathData,
style: &Style,
transform: Option<Transform>,
sink: &mut impl PathBuilder,
) -> Fill {
match style {
Style::Fill(fill) => {
if let Some(transform) = transform {
let mut transform_sink = TransformSink { sink, transform };
data.copy_to(&mut transform_sink);
*fill
} else {
data.copy_to(sink);
*fill
}
}
Style::Stroke(stroke) => {
if let Some(transform) = transform {
if stroke.scale {
let mut transform_sink = TransformSink { sink, transform };
stroke_with_storage(
data.commands(),
stroke,
&mut transform_sink,
&mut self.segments,
);
} else {
stroke_with_storage(
data.commands()
.map(|cmd| cmd.borrow().transform(&transform)),
stroke,
sink,
&mut self.segments,
);
}
} else {
stroke_with_storage(data.commands(), stroke, sink, &mut self.segments);
}
Fill::NonZero
}
}
}
}

650
vendor/zeno/src/segment.rs vendored Normal file
View File

@@ -0,0 +1,650 @@
//! Path segmentation.
#![allow(clippy::excessive_precision)]
use super::command::Command;
use super::geometry::*;
#[allow(unused)]
use super::F32Ext;
use core::borrow::Borrow;
use core::f32;
/// Represents the time parameter for a specific distance along
/// a segment.
#[derive(Copy, Clone, Debug)]
pub struct SegmentTime {
pub distance: f32,
pub time: f32,
}
/// Line segment.
#[derive(Copy, Clone, PartialEq, Debug)]
pub struct Line {
pub a: Point,
pub b: Point,
}
impl Line {
/// Creates a new line segment.
pub fn new(a: impl Into<Vector>, b: impl Into<Vector>) -> Self {
Self {
a: a.into(),
b: b.into(),
}
}
/// Returns the length of the line segment.
pub fn length(&self) -> f32 {
(self.b - self.a).length()
}
/// Returns a slice of the line segment described by the specified start and end times.
#[allow(unused)]
pub fn slice(&self, start: f32, end: f32) -> Self {
let dir = self.b - self.a;
Self::new(self.a + dir * start, self.a + dir * end)
}
#[allow(unused)]
pub fn time(&self, distance: f32) -> SegmentTime {
let len = (self.b - self.a).length();
if distance > len {
return SegmentTime {
distance: len,
time: 1.,
};
}
SegmentTime {
distance,
time: distance / len,
}
}
#[allow(unused)]
pub fn reverse(&self) -> Self {
Self::new(self.b, self.a)
}
}
/// Cubic bezier curve.
#[derive(Copy, Clone, PartialEq, Default, Debug)]
pub struct Curve {
pub a: Point,
pub b: Point,
pub c: Point,
pub d: Point,
}
impl Curve {
/// Creates a new curve.
pub fn new(
a: impl Into<Point>,
b: impl Into<Point>,
c: impl Into<Point>,
d: impl Into<Point>,
) -> Self {
Curve {
a: a.into(),
b: b.into(),
c: c.into(),
d: d.into(),
}
}
/// Creates a new curve from a quadratic bezier curve.
pub fn from_quadratic(a: impl Into<Point>, b: impl Into<Point>, c: impl Into<Point>) -> Self {
let a = a.into();
let b = b.into();
let c = c.into();
Self {
a,
b: Point::new(a.x + 2. / 3. * (b.x - a.x), a.y + 2. / 3. * (b.y - a.y)),
c: Point::new(c.x + 2. / 3. * (b.x - c.x), c.y + 2. / 3. * (b.y - c.y)),
d: c,
}
}
/// Returns the length of the curve.
pub fn length(&self) -> f32 {
let mut len = 0.;
let mut prev = self.a;
let steps = 64;
let step = 1. / steps as f32;
let mut t = 0.;
for _ in 0..=steps {
t += step;
let next = self.evaluate(t);
len += (next - prev).length();
prev = next;
}
len
}
/// Returns a slice of the curve described by the specified start and end times.
pub fn slice(&self, start: f32, end: f32) -> Self {
let t0 = start;
let t1 = end;
let u0 = 1. - t0;
let u1 = 1. - t1;
let v0 = self.a;
let v1 = self.b;
let v2 = self.c;
let v3 = self.d;
Self::new(
(v0 * (u0 * u0 * u0))
+ (v1 * (t0 * u0 * u0 + u0 * t0 * u0 + u0 * u0 * t0))
+ (v2 * (t0 * t0 * u0 + u0 * t0 * t0 + t0 * u0 * t0))
+ (v3 * (t0 * t0 * t0)),
(v0 * (u0 * u0 * u1))
+ (v1 * (t0 * u0 * u1 + u0 * t0 * u1 + u0 * u0 * t1))
+ (v2 * (t0 * t0 * u1 + u0 * t0 * t1 + t0 * u0 * t1))
+ (v3 * (t0 * t0 * t1)),
(v0 * (u0 * u1 * u1))
+ (v1 * (t0 * u1 * u1 + u0 * t1 * u1 + u0 * u1 * t1))
+ (v2 * (t0 * t1 * u1 + u0 * t1 * t1 + t0 * u1 * t1))
+ (v3 * (t0 * t1 * t1)),
(v0 * (u1 * u1 * u1))
+ (v1 * (t1 * u1 * u1 + u1 * t1 * u1 + u1 * u1 * t1))
+ (v2 * (t1 * t1 * u1 + u1 * t1 * t1 + t1 * u1 * t1))
+ (v3 * (t1 * t1 * t1)),
)
}
/// Returns a curve with the direction reversed.
#[allow(unused)]
pub fn reverse(&self) -> Self {
Self::new(self.d, self.c, self.b, self.a)
}
/// Returns the time parameter for the specified linear distance along
/// the curve.
#[allow(unused)]
pub fn time(&self, distance: f32, tolerance: f32) -> SegmentTime {
let (distance, time) = self.time_impl(distance, tolerance, 1., 0);
SegmentTime { distance, time }
}
/// Returns true if the curve can be represented as a line within some
/// tolerance.
pub fn is_line(&self, tolerance: f32) -> bool {
let degen_ab = self.a.nearly_eq_by(self.b, tolerance);
let degen_bc = self.b.nearly_eq_by(self.c, tolerance);
let degen_cd = self.c.nearly_eq_by(self.d, tolerance);
degen_ab as u8 + degen_bc as u8 + degen_cd as u8 >= 2
}
/// Evaluates the curve at the specified time.
pub fn evaluate(&self, time: f32) -> Point {
let t = time;
let t0 = 1. - t;
(self.a * (t0 * t0 * t0))
+ (self.b * (3. * t0 * t0 * t))
+ (self.c * (3. * t0 * t * t))
+ (self.d * (t * t * t))
}
#[allow(clippy::wrong_self_convention)]
fn to_segment(&self, id: SegmentId) -> Option<Segment> {
if self.is_line(MERGE_EPSILON) {
if self.a.nearly_eq_by(self.d, MERGE_EPSILON) {
None
} else {
Some(Segment::Line(id, Line::new(self.a, self.d)))
}
} else {
Some(Segment::Curve(id, *self))
}
}
fn split_at_max_curvature(&self, splits: &mut [Curve; 4]) -> usize {
let mut tmp = [0f32; 3];
let count1 = self.max_curvature(&mut tmp);
let mut count = 0;
let mut ts = [0f32; 4];
for &t in &tmp[..count1] {
if t > 0. && t < 1. {
ts[count] = t;
count += 1;
}
}
if count == 0 {
splits[0] = *self;
} else {
let mut i = 0;
let mut last_t = 0.;
for &t in &ts[..count] {
splits[i] = self.slice(last_t, t);
i += 1;
last_t = t;
}
splits[i] = self.slice(last_t, 1.);
}
count + 1
}
fn split(&self, t: f32) -> (Self, Self) {
(self.slice(0., t), self.slice(t, 1.))
}
fn time_impl(&self, distance: f32, tolerance: f32, t: f32, level: u8) -> (f32, f32) {
if level < 5 && self.too_curvy(tolerance) {
let c0 = self.slice(0., 0.5);
let (dist0, t0) = c0.time_impl(distance, tolerance, t * 0.5, level + 1);
if dist0 < distance {
let c1 = self.slice(0.5, 1.);
let (dist1, t1) = c1.time_impl(distance - dist0, tolerance, t * 0.5, level + 1);
(dist0 + dist1, t0 + t1)
} else {
(dist0, t0)
}
} else {
let dist = (self.d - self.a).length();
if dist >= distance {
let s = distance / dist;
(distance, t * s)
} else {
(dist, t)
}
}
}
fn max_curvature(&self, ts: &mut [f32; 3]) -> usize {
let comps_x = [self.a.x, self.b.x, self.c.x, self.d.x];
let comps_y = [self.a.y, self.b.y, self.c.y, self.d.y];
fn get_coeffs(src: [f32; 4]) -> [f32; 4] {
let a = src[1] - src[0];
let b = src[2] - 2. * src[1] + src[0];
let c = src[3] + 3. * (src[1] - src[2]) - src[0];
[c * c, 3. * b * c, 2. * b * b + c * a, a * b]
}
let mut coeffs = get_coeffs(comps_x);
let coeffs_y = get_coeffs(comps_y);
for i in 0..4 {
coeffs[i] += coeffs_y[i];
}
Self::solve(coeffs, ts)
}
fn solve(coeff: [f32; 4], ts: &mut [f32; 3]) -> usize {
const PI: f32 = core::f32::consts::PI;
let i = 1. / coeff[0];
let a = coeff[1] * i;
let b = coeff[2] * i;
let c = coeff[3] * i;
let q = (a * a - b * 3.) / 9.;
let r = (2. * a * a * a - 9. * a * b + 27. * c) / 54.;
let q3 = q * q * q;
let r2_sub_q3 = r * r - q3;
let adiv3 = a / 3.;
if r2_sub_q3 < 0. {
let theta = satf32(r / q3.sqrt()).acos();
let neg2_root_q = -2. * q.sqrt();
ts[0] = satf32(neg2_root_q * (theta / 3.).cos() - adiv3);
ts[1] = satf32(neg2_root_q * ((theta + 2. * PI) / 3.).cos() - adiv3);
ts[2] = satf32(neg2_root_q * ((theta - 2. * PI) / 3.).cos() - adiv3);
ts.sort_unstable_by(|x, y| x.partial_cmp(y).unwrap_or(core::cmp::Ordering::Less));
let mut count = 3;
if ts[0] == ts[1] {
ts[1] = ts[2];
count -= 1;
}
if ts[1] == ts[2] {
count -= 1;
}
count
} else {
let mut a = r.abs() + r2_sub_q3.sqrt();
a = a.powf(0.3333333);
if r > 0. {
a = -a;
}
if a != 0. {
a += q / a;
}
ts[0] = satf32(a - adiv3);
1
}
}
fn too_curvy(&self, tolerance: f32) -> bool {
(2. * self.d.x - 3. * self.c.x + self.a.x).abs() > tolerance
|| (2. * self.d.y - 3. * self.c.y + self.a.y).abs() > tolerance
|| (self.d.x - 3. * self.b.x + 2. * self.a.x).abs() > tolerance
|| (self.d.y - 3. * self.b.y + 2. * self.a.y).abs() > tolerance
}
fn needs_split(&self) -> bool {
if self.b.nearly_eq_by(self.c, MERGE_EPSILON) {
return true;
}
let normal_ab = normal(self.a, self.b);
let normal_bc = normal(self.b, self.c);
fn too_curvy(n0: Vector, n1: Vector) -> bool {
const FLAT_ENOUGH: f32 = f32::consts::SQRT_2 / 2. + 1. / 10.;
n0.dot(n1) <= FLAT_ENOUGH
}
too_curvy(normal_ab, normal_bc) || too_curvy(normal_bc, normal(self.c, self.d))
}
}
fn satf32(x: f32) -> f32 {
x.clamp(0., 1.)
}
/// Marker that allows regrouping of previously split segments due to simplification.
pub type SegmentId = u8;
/// Segment of a path.
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum Segment {
/// Line segment..
Line(SegmentId, Line),
/// Cubic bezier segment.
Curve(SegmentId, Curve),
/// Marks the end of a subpath. Contains the value `true` if the subpath
/// is closed.
End(bool),
}
impl Segment {
pub fn length(&self) -> f32 {
match self {
Self::Line(_, line) => line.length(),
Self::Curve(_, curve) => curve.length(),
_ => 0.,
}
}
#[allow(unused)]
pub fn slice(&self, start: f32, end: f32) -> Self {
match self {
Self::Line(id, line) => Self::Line(*id, line.slice(start, end)),
Self::Curve(id, curve) => Self::Curve(*id, curve.slice(start, end)),
Self::End(..) => *self,
}
}
#[allow(unused)]
pub fn reverse(&self) -> Self {
match self {
Self::Line(id, line) => Self::Line(*id, line.reverse()),
Self::Curve(id, curve) => Self::Curve(*id, curve.reverse()),
Self::End(..) => *self,
}
}
#[allow(unused)]
pub fn time(&self, distance: f32, tolerance: f32) -> SegmentTime {
match self {
Self::Line(_, line) => line.time(distance),
Self::Curve(_, curve) => curve.time(distance, tolerance),
_ => SegmentTime {
distance: 0.,
time: 0.,
},
}
}
#[allow(unused)]
pub fn point_normal(&self, time: f32) -> (Point, Vector) {
match self {
Self::Line(_, line) => {
let dir = line.b - line.a;
let p = line.a + dir * time;
let n = normal(line.a, line.b);
(p, n)
}
Self::Curve(_, curve) => {
let p = curve.evaluate(time);
let a = curve.evaluate(time - 0.05);
let b = curve.evaluate(time + 0.05);
let n = normal(a, b);
(p, n)
}
Self::End(..) => (Point::ZERO, Vector::ZERO),
}
}
}
impl Default for Segment {
fn default() -> Self {
Self::End(false)
}
}
// This large epsilon trades fidelity for performance, visual continuity
// and numeric stability.
const MERGE_EPSILON: f32 = 0.01;
/// Creates a segment iterator from a command iterator, optionally producing
/// simplified curves.
pub fn segments<I>(commands: I, simplify_curves: bool) -> Segments<I>
where
I: Iterator + Clone,
I::Item: Borrow<Command>,
{
Segments::new(simplify_curves, commands)
}
/// Iterator over path segments.
#[derive(Clone)]
pub struct Segments<I> {
commands: I,
start: Vector,
prev: Vector,
close: bool,
split: bool,
splits: [Curve; 16],
split_count: usize,
split_index: usize,
last_was_end: bool,
id: u8,
count: u32,
}
impl<I> Segments<I>
where
I: Iterator + Clone,
I::Item: Borrow<Command>,
{
fn new(split: bool, commands: I) -> Self {
Self {
commands,
start: Vector::ZERO,
prev: Vector::ZERO,
close: false,
split,
splits: [Curve::default(); 16],
split_count: 0,
split_index: 0,
last_was_end: true,
id: 0,
count: 0,
}
}
#[allow(clippy::needless_range_loop)]
fn split_curve(&mut self, id: SegmentId, c: &Curve) -> Option<Segment> {
if c.is_line(MERGE_EPSILON) {
if c.a.nearly_eq_by(c.d, MERGE_EPSILON) {
return None;
}
return Some(Segment::Line(id, Line::new(c.a, c.d)));
}
let mut splits = [Curve::default(); 4];
let count = c.split_at_max_curvature(&mut splits);
let mut i = 0;
for j in 0..count {
let curve = splits[j];
if curve.needs_split() {
let (a, b) = curve.split(0.5);
if a.needs_split() {
let (c, d) = a.split(0.5);
self.splits[i] = c;
self.splits[i + 1] = d;
i += 2;
} else {
self.splits[i] = a;
i += 1;
}
if b.needs_split() {
let (c, d) = b.split(0.5);
self.splits[i] = c;
self.splits[i + 1] = d;
i += 2;
} else {
self.splits[i] = b;
i += 1;
}
} else {
self.splits[i] = curve;
i += 1;
}
}
self.split_count = i;
self.split_index = 1;
self.splits[0].to_segment(id)
}
fn inc_id(&mut self) {
if self.id == 254 {
self.id = 0;
} else {
self.id += 1;
}
}
}
impl<I> Iterator for Segments<I>
where
I: Iterator + Clone,
I::Item: Borrow<Command>,
{
type Item = Segment;
fn next(&mut self) -> Option<Self::Item> {
use Command::*;
if self.close {
self.close = false;
self.last_was_end = true;
return Some(Segment::End(true));
}
if self.split {
loop {
if self.split_index < self.split_count {
let curve = self.splits[self.split_index];
self.split_index += 1;
if let Some(segment) = curve.to_segment(self.id) {
self.count += 1;
self.last_was_end = false;
self.prev = curve.d;
return Some(segment);
}
continue;
}
self.inc_id();
let id = self.id;
let from = self.prev;
match *self.commands.next()?.borrow() {
MoveTo(to) => {
self.start = to;
self.prev = to;
self.count = 0;
if !self.last_was_end {
self.last_was_end = true;
return Some(Segment::End(false));
}
}
LineTo(to) => {
if !from.nearly_eq_by(to, MERGE_EPSILON) {
self.count += 1;
self.prev = to;
self.last_was_end = false;
return Some(Segment::Line(id, Line::new(from, to)));
}
}
CurveTo(c1, c2, to) => {
if let Some(segment) = self.split_curve(id, &Curve::new(from, c1, c2, to)) {
self.count += 1;
self.prev = to;
self.last_was_end = false;
return Some(segment);
}
}
QuadTo(c, to) => {
if let Some(segment) =
self.split_curve(id, &Curve::from_quadratic(from, c, to))
{
self.count += 1;
self.prev = to;
self.last_was_end = false;
return Some(segment);
}
}
Close => {
self.prev = self.start;
if self.count == 0 || !from.nearly_eq_by(self.start, MERGE_EPSILON) {
self.close = true;
return Some(Segment::Line(id, Line::new(from, self.start)));
} else {
self.count = 0;
self.last_was_end = true;
return Some(Segment::End(true));
}
}
}
}
} else {
let id = self.id;
self.inc_id();
loop {
let from = self.prev;
match *self.commands.next()?.borrow() {
MoveTo(to) => {
self.start = to;
self.prev = to;
self.count = 0;
if !self.last_was_end {
self.last_was_end = true;
return Some(Segment::End(false));
}
}
LineTo(to) => {
if !from.nearly_eq_by(to, MERGE_EPSILON) {
self.count += 1;
self.prev = to;
self.last_was_end = false;
return Some(Segment::Line(id, Line::new(from, to)));
}
}
CurveTo(c1, c2, to) => {
let segment = Segment::Curve(id, Curve::new(from, c1, c2, to));
self.count += 1;
self.prev = to;
self.last_was_end = false;
return Some(segment);
}
QuadTo(c, to) => {
let segment = Segment::Curve(id, Curve::from_quadratic(from, c, to));
self.count += 1;
self.prev = to;
self.last_was_end = false;
return Some(segment);
}
Close => {
self.prev = self.start;
if self.count == 0 || !from.nearly_eq_by(self.start, 0.01) {
self.close = true;
return Some(Segment::Line(id, Line::new(from, self.start)));
} else {
self.count = 0;
self.last_was_end = true;
return Some(Segment::End(true));
}
}
}
}
}
}
}

853
vendor/zeno/src/stroke.rs vendored Normal file
View File

@@ -0,0 +1,853 @@
//! Stroking and dashing of paths.
#![allow(clippy::needless_lifetimes)]
use super::command::Command;
use super::geometry::*;
use super::path_builder::*;
use super::segment::*;
use super::style::*;
#[allow(unused)]
use super::F32Ext;
use crate::lib::Vec;
use core::borrow::Borrow;
pub fn stroke_into<'a, I>(commands: I, style: &Stroke<'a>, sink: &mut impl PathBuilder)
where
I: Iterator + Clone,
I::Item: Borrow<Command>,
{
let mut stroker = Stroker::new(segments(commands, true), sink, style);
let (dashes, dash_offset, empty_gaps) = validate_dashes(style.dashes, style.offset);
let mut segment_buf = SmallBuf::new();
if !dashes.is_empty() {
stroker.dash(&mut segment_buf, dashes, dash_offset, empty_gaps);
} else {
stroker.stroke(&mut segment_buf);
}
}
pub fn stroke_with_storage<'a, I>(
commands: I,
style: &Stroke<'a>,
sink: &mut impl PathBuilder,
storage: &mut impl StrokerStorage,
) where
I: Iterator + Clone,
I::Item: Borrow<Command>,
{
let mut stroker = Stroker::new(segments(commands, true), sink, style);
let (dashes, dash_offset, empty_gaps) = validate_dashes(style.dashes, style.offset);
if !dashes.is_empty() {
stroker.dash(storage, dashes, dash_offset, empty_gaps);
} else {
stroker.stroke(storage);
}
}
pub struct Stroker<'a, I, S> {
source: Segments<I>,
sink: &'a mut S,
radius: f32,
radius_abs: f32,
join: Join,
inv_miter_limit: f32,
start_cap: Cap,
end_cap: Cap,
}
impl<'a, I, S> Stroker<'a, I, S>
where
I: Iterator + Clone,
I::Item: Borrow<Command>,
S: PathBuilder,
{
pub(super) fn new(source: Segments<I>, sink: &'a mut S, style: &Stroke) -> Self {
let radius = style.width.max(0.01) * 0.5;
Self {
source,
sink,
radius,
radius_abs: radius.abs(),
join: style.join,
inv_miter_limit: if style.miter_limit >= 1. {
1. / style.miter_limit
} else {
1.
},
start_cap: style.start_cap,
end_cap: style.end_cap,
}
}
fn stroke(&mut self, segment_buf: &mut impl StrokerStorage) {
loop {
let (closed, done) = segment_buf.collect(&mut self.source);
self.stroke_segments(segment_buf.get(), closed);
if done {
break;
}
}
}
fn stroke_segments(&mut self, segments: &[Segment], is_closed: bool) {
let len = segments.len();
if len == 0 {
return;
}
if len == 1
&& segments[0].length() == 0.
&& (self.start_cap != Cap::Butt || self.end_cap != Cap::Butt)
{
let segment = segments[0];
let from = match &segment {
Segment::Line(_, line) => line.a,
Segment::Curve(_, curve) => curve.a,
Segment::End(..) => Point::ZERO,
};
let n = Vector::new(0., 1.);
let nr = n * self.radius;
let start = from + nr;
let rstart = from - nr;
self.sink.move_to(start);
self.add_end_cap(start, rstart, n);
self.add_start_cap(rstart, start, n * -1.);
return;
}
let radius = self.radius;
let mut last_dir = Vector::ZERO;
let mut first_point = Point::ZERO;
let mut last_point = Point::ZERO;
let mut pivot = Point::ZERO;
let mut last_id = 0xFF;
if is_closed {
let segment = segments[len - 1].offset(radius);
let end_point = segment.end;
let out_dir = segment.end_normal;
pivot = segment.end_pivot;
last_dir = out_dir;
last_point = end_point;
first_point = end_point;
self.sink.move_to(last_point);
}
// Forward for the outer stroke.
let mut is_first = !is_closed;
for segment in segments {
let segment = segment.offset(radius);
let id = segment.id;
let start = segment.start;
if is_first {
self.sink.move_to(start);
first_point = start;
is_first = false;
} else {
self.add_join(last_point, start, pivot, last_dir, segment.start_normal);
}
last_id = id;
last_dir = segment.end_normal;
pivot = segment.end_pivot;
last_point = self.emit(&segment.segment);
}
// Now backward for the inner stroke.
is_first = true;
for segment in segments.iter().rev() {
let segment = segment.reverse().offset(radius);
let id = segment.id;
let start = segment.start;
if is_first {
if is_closed {
let init = segments[0].reverse().offset(self.radius);
last_point = init.end;
last_dir = init.end_normal;
pivot = init.end_pivot;
self.sink.line_to(init.end);
self.add_join(last_point, start, pivot, last_dir, segment.start_normal);
} else {
self.add_end_cap(last_point, start, last_dir);
}
is_first = false;
} else if id != last_id {
self.add_join(last_point, start, pivot, last_dir, segment.start_normal);
} else {
self.add_split_join(last_point, start, pivot, last_dir, segment.start_normal);
};
last_id = id;
last_dir = segment.end_normal;
pivot = segment.end_pivot;
last_point = self.emit(&segment.segment);
}
if !is_closed {
self.add_start_cap(last_point, first_point, last_dir);
}
self.sink.close();
}
#[allow(clippy::field_reassign_with_default)]
fn dash(
&mut self,
segment_buf: &mut impl StrokerStorage,
dashes: &[f32],
offset: f32,
empty_gaps: bool,
) {
let mut dasher = Dasher::default();
dasher.empty_gaps = empty_gaps;
let mut done = false;
while !done {
let (is_closed, is_done) = segment_buf.collect(&mut self.source);
done = is_done;
let segments = segment_buf.get();
if segments.is_empty() {
continue;
}
dasher.init(is_closed, dashes, offset);
loop {
match dasher.next(segments, dashes) {
DashOp::Done => break,
DashOp::Continue => {}
DashOp::Emit => {
let (start, end) = dasher.range;
let (t0, t1) = dasher.trange;
self.dash_segments(segments, start, end, t0, t1);
}
DashOp::Stroke => {
self.stroke_segments(segments, true);
break;
}
}
}
}
}
fn dash_segments(&mut self, segments: &[Segment], start: isize, end: isize, t0: f32, t1: f32) {
let radius = self.radius;
if t0 == t1 && start == end {
if self.start_cap == Cap::Butt && self.end_cap == Cap::Butt {
return;
}
let (t0, t1) = if t0 >= 1. {
(t0 - 0.001, t0)
} else {
(t0, t0 + 0.001)
};
let segment = get_signed(segments, start).slice(t0, t1).offset(radius);
let start = segment.start;
let rstart = segment.start - (segment.start_normal * (2. * radius));
self.sink.move_to(start);
self.add_end_cap(start, rstart, segment.start_normal);
self.add_start_cap(rstart, start, segment.start_normal * -1.);
self.sink.close();
return;
}
let mut last_dir = Vector::ZERO;
let mut first_point = Point::ZERO;
let mut last_point = Point::ZERO;
let mut pivot = Point::ZERO;
let mut is_first = true;
let mut last_id = 0xFF;
for i in start..=end {
let t0 = if i == start { t0 } else { 0. };
let t1 = if i == end { t1 } else { 1. };
if t0 >= 1. {
continue;
}
let segment = get_signed(segments, i).slice(t0, t1).offset(radius);
let id = segment.id;
let start = segment.start;
if is_first {
self.sink.move_to(start);
first_point = start;
is_first = false;
} else if id != last_id {
self.add_join(last_point, start, pivot, last_dir, segment.start_normal);
} else {
self.add_split_join(last_point, start, pivot, last_dir, segment.start_normal);
};
last_id = id;
pivot = segment.end_pivot;
last_dir = segment.end_normal;
last_point = self.emit(&segment.segment);
}
is_first = true;
last_id = 0xFF;
for i in (start..=end).rev() {
let t0 = if i == start { t0 } else { 0. };
let t1 = if i == end { t1 } else { 1. };
if t0 >= 1. {
continue;
}
let segment = get_signed(segments, i)
.slice(t0, t1)
.reverse()
.offset(radius);
let id = segment.id;
let start = segment.start;
if is_first {
self.add_end_cap(last_point, start, last_dir);
is_first = false;
} else if id != last_id {
self.add_join(last_point, start, pivot, last_dir, segment.start_normal);
} else {
self.add_split_join(last_point, start, pivot, last_dir, segment.start_normal);
};
last_id = id;
pivot = segment.end_pivot;
last_dir = segment.end_normal;
last_point = self.emit(&segment.segment);
}
self.add_start_cap(last_point, first_point, last_dir);
self.sink.close();
}
#[inline(always)]
fn emit(&mut self, segment: &Segment) -> Point {
match segment {
Segment::Line(_, line) => {
self.sink.line_to(line.b);
line.b
}
Segment::Curve(_, curve) => {
self.sink.curve_to(curve.b, curve.c, curve.d);
curve.d
}
_ => Point::ZERO,
}
}
fn add_join(
&mut self,
from: Point,
to: Point,
pivot: Point,
from_normal: Vector,
to_normal: Vector,
) -> Point {
if from.nearly_eq(to) {
return from;
}
if !is_clockwise(from_normal, to_normal) {
self.sink.line_to(pivot);
self.sink.line_to(to);
return to;
}
match self.join {
Join::Bevel => {
self.sink.line_to(to);
to
}
Join::Round => {
let r = self.radius_abs;
let (size, sweep) = (ArcSize::Small, ArcSweep::Positive);
arc(self.sink, from, r, r, 0., size, sweep, to);
to
}
Join::Miter => {
let inv_limit = self.inv_miter_limit;
let dot = from_normal.dot(to_normal);
let sin_half = ((1. + dot) * 0.5).sqrt();
if dot < 0.0 || sin_half < inv_limit {
self.sink.line_to(to);
to
} else {
let mid = (from_normal + to_normal).normalize() * (self.radius / sin_half);
let p = pivot + mid;
self.sink.line_to(p);
self.sink.line_to(to);
to
}
}
}
}
fn add_split_join(
&mut self,
from: Point,
to: Point,
pivot: Point,
from_normal: Vector,
to_normal: Vector,
) -> Point {
if from.nearly_eq(to) {
return from;
}
if !is_clockwise(from_normal, to_normal) {
self.sink.line_to(pivot);
self.sink.line_to(to);
return to;
}
let r = self.radius_abs;
let (size, sweep) = (ArcSize::Small, ArcSweep::Positive);
arc(self.sink, from, r, r, 0., size, sweep, to);
to
}
fn add_cap(&mut self, from: Point, to: Point, dir: Vector, cap: Cap) {
match cap {
Cap::Butt => {
self.sink.line_to(to);
}
Cap::Square => {
let dir = Vector::new(-dir.y, dir.x);
self.sink.line_to(from + dir * self.radius_abs);
self.sink.line_to(to + dir * self.radius_abs);
self.sink.line_to(to);
}
Cap::Round => {
let r = self.radius_abs;
let (size, sweep) = (ArcSize::Small, ArcSweep::Positive);
arc(self.sink, from, r, r, 0., size, sweep, to);
}
}
}
fn add_start_cap(&mut self, from: Point, to: Point, dir: Vector) {
self.add_cap(from, to, dir, self.start_cap);
}
fn add_end_cap(&mut self, from: Point, to: Point, dir: Vector) {
self.add_cap(from, to, dir, self.end_cap);
}
}
enum DashOp {
Done,
Continue,
Emit,
Stroke,
}
#[derive(Copy, Clone, Default)]
struct Dasher {
done: bool,
is_closed: bool,
empty_gaps: bool,
on: bool,
cur: isize,
t0: f32,
t0_offset: f32,
index: usize,
is_first: bool,
first_on: bool,
first_dash: f32,
is_dot: bool,
range: (isize, isize),
trange: (f32, f32),
}
impl Dasher {
fn init(&mut self, is_closed: bool, dashes: &[f32], offset: f32) {
self.done = false;
self.is_closed = is_closed;
self.on = true;
self.cur = 0;
self.t0 = 0.;
self.t0_offset = 0.;
self.index = 0;
self.is_first = true;
self.first_on = true;
let mut first_dash = self.next_dash(dashes);
if offset > 0. {
let mut accum = first_dash;
while accum < offset {
self.on = !self.on;
accum += self.next_dash(dashes);
}
self.first_on = self.on;
first_dash = accum - offset;
}
self.first_dash = first_dash;
}
#[inline(always)]
fn next_dash(&mut self, dashes: &[f32]) -> f32 {
let len = dashes.len();
let mut dash = dashes[self.index % len];
if self.on && self.empty_gaps {
loop {
let next_dash = dashes[(self.index + 1) % len];
if next_dash != 0. {
break;
}
self.index += 2;
dash += dashes[self.index % len];
}
}
self.index += 1;
dash
}
#[inline(always)]
fn next_segments(
dash: f32,
segments: &[Segment],
limit: isize,
start: isize,
start_offset: f32,
) -> (bool, isize, f32, f32) {
let mut cur = start;
let mut goal = dash + start_offset;
let mut segment = get_signed(segments, cur);
loop {
let td = segment.time(goal, 1.);
let dist = td.distance;
let t2 = td.time;
goal -= dist;
if goal <= 0. {
return (true, cur, dist, t2);
}
if cur + 1 >= limit {
return (false, cur, dist, t2);
}
cur += 1;
segment = get_signed(segments, cur);
}
}
#[inline(always)]
fn next(&mut self, segments: &[Segment], dashes: &[f32]) -> DashOp {
if self.done {
return DashOp::Done;
}
let first = self.is_first;
let first_and_closed = first && self.is_closed;
let mut dash = if first {
self.first_dash
} else {
self.next_dash(dashes)
};
let mut on = self.on;
let mut start = self.cur;
let limit = segments.len() as isize;
if self.t0 == 1. && start < limit - 1 {
start += 1;
self.t0 = 0.;
self.t0_offset = 0.;
self.cur = start;
}
let (cont, mut end, mut t1_offset, mut t1) = if dash == 0. {
(true, start, self.t0_offset, self.t0)
} else {
Self::next_segments(dash, segments, limit, start, self.t0_offset)
};
if !cont {
self.done = true;
}
// This is tricky. If the subpath is closed and the last dash is
// "on" we need to join the last dash to the first. Otherwise, we
// need to go back and produce the initial dash that was skipped
// in anticipation of joining to the final dash.
if self.done && self.is_closed {
if on {
// Recompute the final dash including the first.
if first_and_closed {
// The first dash consumed the whole path: emit a single stroke.
return DashOp::Stroke;
}
if self.first_on {
self.cur = start - limit;
start = self.cur;
let (_, end2, end_offset, end_t) =
Self::next_segments(self.first_dash, segments, limit, 0, 0.);
end = end2;
t1_offset = end_offset;
t1 = end_t;
}
} else {
// Emit the first dash.
if !self.first_on {
return DashOp::Done;
}
dash = self.first_dash;
self.cur = 0;
self.t0 = 0.;
self.t0_offset = 0.;
self.on = true;
on = true;
start = self.cur;
let (_, end2, end_offset, end_t) =
Self::next_segments(self.first_dash, segments, limit, 0, 0.);
end = end2;
t1_offset = end_offset;
t1 = end_t;
}
} else if self.done && !on {
return DashOp::Done;
}
self.is_dot = dash == 0.;
let t0 = self.t0;
self.is_first = false;
self.cur = end;
self.t0 = t1;
self.t0_offset = t1_offset;
self.on = !self.on;
if on && !first_and_closed {
self.range = (start, end);
self.trange = (t0, t1);
return DashOp::Emit;
}
DashOp::Continue
}
}
fn validate_dashes(dashes: &[f32], offset: f32) -> (&[f32], f32, bool) {
let len = dashes.len();
if len > 0 {
// Generate a full stroke under any of the following conditions:
// 1. The array contains any negative values.
// 2. All dashes are less than 1 unit.
// 3. All gap dashes are less than 1 unit.
let mut small_count = 0;
let mut gap_sum = 0.;
let mut empty_gaps = false;
let is_odd = len & 1 != 0;
for (i, dash) in dashes.iter().enumerate() {
let is_gap = i & 1 == 1;
if *dash < 1. {
small_count += 1;
if *dash < 0. {
return (&[], 0., false);
} else if *dash == 0. && (is_gap || is_odd) {
empty_gaps = true;
}
} else if is_gap {
gap_sum += *dash;
}
}
if dashes.len() == 1 {
gap_sum = 1.;
}
if small_count < dashes.len() && gap_sum > 0. {
let offset = if offset != 0. {
let mut s: f32 = dashes.iter().sum();
if is_odd {
s *= 2.;
}
if offset < 0. {
s - (offset.abs() % s)
} else {
offset % s
}
} else {
0.
};
return (dashes, offset, empty_gaps);
}
}
(&[], 0., false)
}
#[inline(always)]
fn get_signed(segments: &[Segment], index: isize) -> Segment {
let index = if index < 0 {
segments.len() - (-index) as usize
} else {
index as usize
};
segments[index]
}
fn is_clockwise(a: Vector, b: Vector) -> bool {
a.x * b.y > a.y * b.x
}
impl Segment {
fn offset(&self, radius: f32) -> OffsetSegment {
OffsetSegment::new(self, radius)
}
}
#[derive(Copy, Clone)]
pub struct OffsetSegment {
pub segment: Segment,
pub id: u8,
pub start: Point,
pub end: Point,
pub start_normal: Vector,
pub end_normal: Vector,
pub end_pivot: Point,
}
impl OffsetSegment {
fn new(segment: &Segment, radius: f32) -> Self {
match segment {
Segment::Line(id, Line { a, b }) => {
let n = normal(*a, *b);
let nr = n * radius;
let start = *a + nr;
let end = *b + nr;
Self {
segment: Segment::Line(*id, Line { a: start, b: end }),
id: *id,
start,
end,
start_normal: n,
end_normal: n,
end_pivot: *b,
}
}
Segment::Curve(id, c) => {
const EPS: f32 = 0.5;
//const EPS: f32 = CURVE_EPSILON;
let normal_ab = if c.a.nearly_eq_by(c.b, EPS) {
if c.a.nearly_eq_by(c.c, EPS) {
normal(c.a, c.d)
} else {
normal(c.a, c.c)
}
} else {
normal(c.a, c.b)
};
let normal_bc = if c.b.nearly_eq_by(c.c, EPS) {
if c.b.nearly_eq_by(c.d, EPS) {
normal(c.a, c.d)
} else {
normal(c.b, c.d)
}
} else {
normal(c.b, c.c)
};
let normal_cd = if c.c.nearly_eq_by(c.d, EPS) {
if c.b.nearly_eq_by(c.d, EPS) {
normal(c.a, c.d)
} else {
normal(c.b, c.d)
}
} else {
normal(c.c, c.d)
};
let mut normal_b = normal_ab + normal_bc;
let mut normal_c = normal_cd + normal_bc;
let dot = normal_ab.dot(normal_bc);
normal_b = normal_b.normalize() * (radius / ((1. + dot) * 0.5).sqrt());
let dot = normal_cd.dot(normal_bc);
normal_c = normal_c.normalize() * (radius / ((1. + dot) * 0.5).sqrt());
let start = c.a + normal_ab * radius;
let end = c.d + normal_cd * radius;
Self {
segment: Segment::Curve(
*id,
Curve::new(start, c.b + normal_b, c.c + normal_c, end),
),
id: *id,
start,
end,
start_normal: normal_ab,
end_normal: normal_cd,
end_pivot: c.d,
}
}
Segment::End(..) => Self {
segment: *segment,
id: 0,
start: Point::ZERO,
end: Point::ZERO,
start_normal: Vector::ZERO,
end_normal: Vector::ZERO,
end_pivot: Point::ZERO,
},
}
}
}
pub trait StrokerStorage {
fn clear(&mut self);
fn push(&mut self, segment: &Segment);
fn get(&self) -> &[Segment];
fn collect(&mut self, segments: &mut impl Iterator<Item = Segment>) -> (bool, bool) {
self.clear();
let mut is_closed = false;
let mut done = false;
loop {
if let Some(segment) = segments.next() {
match segment {
Segment::End(closed) => {
is_closed = closed;
break;
}
_ => self.push(&segment),
}
} else {
done = true;
break;
}
}
(is_closed, done)
}
}
impl StrokerStorage for SmallBuf<Segment> {
fn clear(&mut self) {
self.clear();
}
#[inline(always)]
fn push(&mut self, segment: &Segment) {
self.push(*segment);
}
fn get(&self) -> &[Segment] {
self.data()
}
}
impl StrokerStorage for Vec<Segment> {
fn clear(&mut self) {
self.clear();
}
#[inline(always)]
fn push(&mut self, segment: &Segment) {
self.push(*segment);
}
fn get(&self) -> &[Segment] {
self
}
}
const MAX_SMALL_BUF: usize = 128;
#[derive(Clone)]
enum SmallBuf<T> {
Array([T; MAX_SMALL_BUF], usize),
Vec(Vec<T>),
}
impl<T: Copy + Default> SmallBuf<T> {
pub fn new() -> Self {
Self::Array([T::default(); MAX_SMALL_BUF], 0)
}
pub fn data(&self) -> &[T] {
match self {
Self::Array(ref buf, len) => &buf[..*len],
Self::Vec(ref buf) => buf,
}
}
pub fn push(&mut self, value: T) {
match self {
Self::Vec(ref mut buf) => buf.push(value),
Self::Array(ref mut buf, ref mut len) => {
if *len == MAX_SMALL_BUF {
let mut vec = Vec::from(&buf[..]);
vec.push(value);
*self = Self::Vec(vec);
} else {
buf[*len] = value;
*len += 1;
}
}
}
}
pub fn clear(&mut self) {
match self {
Self::Array(_, ref mut len) => *len = 0,
Self::Vec(ref mut buf) => buf.clear(),
}
}
}

172
vendor/zeno/src/style.rs vendored Normal file
View File

@@ -0,0 +1,172 @@
//! Path styles.
/// Describes the visual style of a fill.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum Fill {
/// The non-zero fill rule.
NonZero,
/// The even-odd fill rule.
EvenOdd,
}
/// Defines the connection between two segments of a stroke.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum Join {
/// A straight line connecting the segments.
Bevel,
/// The segments are extended to their natural intersection point.
Miter,
/// An arc between the segments.
Round,
}
/// Defines the shape to be drawn at the beginning or end of a stroke.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum Cap {
/// Flat cap.
Butt,
/// Square cap with dimensions equal to half the stroke width.
Square,
/// Rounded cap with radius equal to half the stroke width.
Round,
}
/// Describes the visual style of a stroke.
#[derive(Copy, Clone, Debug)]
pub struct Stroke<'a> {
/// Width of the stroke.
pub width: f32,
/// Style for connecting segments of the stroke.
pub join: Join,
/// Limit for miter joins.
pub miter_limit: f32,
/// Style for capping the beginning of an open subpath.
pub start_cap: Cap,
/// Style for capping the end of an open subpath.
pub end_cap: Cap,
/// Lengths of dashes in alternating on/off order.
pub dashes: &'a [f32],
/// Offset of the first dash.
pub offset: f32,
/// True if the stroke width should be affected by the scale of a transform.
pub scale: bool,
}
impl Default for Stroke<'_> {
fn default() -> Self {
Self {
width: 1.,
join: Join::Miter,
miter_limit: 4.,
start_cap: Cap::Butt,
end_cap: Cap::Butt,
dashes: &[],
offset: 0.,
scale: true,
}
}
}
impl<'a> Stroke<'a> {
/// Creates a new stroke style with the specified width.
#[allow(clippy::field_reassign_with_default)]
pub fn new(width: f32) -> Self {
let mut s = Self::default();
s.width = width;
s
}
/// Sets the width of the stroke. The default is 1.
pub fn width(&mut self, width: f32) -> &mut Self {
self.width = width;
self
}
/// Sets the join style that determines how individual segments of the path
/// will be connected. The default is miter.
pub fn join(&mut self, join: Join) -> &mut Self {
self.join = join;
self
}
/// Sets the limit for miter joins beyond which a bevel will be generated.
/// The default is 4.
pub fn miter_limit(&mut self, limit: f32) -> &mut Self {
self.miter_limit = limit;
self
}
/// Sets the cap style that will be generated at the start and end of the
/// stroke. Note that this will override the individual start and end cap
/// options. The default is butt.
pub fn cap(&mut self, cap: Cap) -> &mut Self {
self.start_cap = cap;
self.end_cap = cap;
self
}
/// Sets both the start and end cap styles for the stroke.
pub fn caps(&mut self, start: Cap, end: Cap) -> &mut Self {
self.start_cap = start;
self.end_cap = end;
self
}
/// Sets the dash array and offset of the stroke. The default is an empty
/// array, meaning that the stroke will be drawn as a continuous line.
pub fn dash(&mut self, dashes: &'a [f32], offset: f32) -> &mut Self {
self.dashes = dashes;
self.offset = offset;
self
}
/// Sets whether or not scaling is applied to the stroke. The default is true.
pub fn scale(&mut self, scale: bool) -> &mut Self {
self.scale = scale;
self
}
}
/// Represents the style of a path for rendering or hit testing.
#[derive(Copy, Clone, Debug)]
pub enum Style<'a> {
Fill(Fill),
Stroke(Stroke<'a>),
}
impl Default for Style<'_> {
fn default() -> Self {
Self::Fill(Fill::NonZero)
}
}
impl Style<'_> {
/// Returns true if the style is a stroke.
pub fn is_stroke(&self) -> bool {
matches!(self, Self::Stroke(_))
}
}
impl From<Fill> for Style<'_> {
fn from(style: Fill) -> Self {
Self::Fill(style)
}
}
impl<'a> From<Stroke<'a>> for Style<'a> {
fn from(style: Stroke<'a>) -> Self {
Self::Stroke(style)
}
}
impl<'a> From<&'a Stroke<'a>> for Style<'a> {
fn from(style: &'a Stroke<'a>) -> Self {
Self::Stroke(*style)
}
}
impl<'a> From<&'a mut Stroke<'a>> for Style<'a> {
fn from(style: &'a mut Stroke<'a>) -> Self {
Self::Stroke(*style)
}
}

659
vendor/zeno/src/svg_parser.rs vendored Normal file
View File

@@ -0,0 +1,659 @@
//! SVG path data parser.
use super::command::Command;
use super::geometry::Vector;
use super::path_builder::{Arc, ArcSize, ArcSweep};
#[derive(Copy, Clone)]
enum State {
Initial,
Next,
Continue(u8),
}
#[derive(Clone)]
pub struct SvgCommands<'a> {
buf: &'a [u8],
cur: u8,
pub pos: usize,
cmd_pos: usize,
pub error: bool,
pub done: bool,
start_point: Vector,
cur_point: Vector,
last_control: Vector,
last_cmd: u8,
state: State,
arc: Arc,
}
impl Iterator for SvgCommands<'_> {
type Item = Command;
fn next(&mut self) -> Option<Self::Item> {
self.parse()
}
}
impl<'a> SvgCommands<'a> {
pub(crate) fn new(source: &'a str) -> Self {
Self {
buf: source.as_bytes(),
cur: 0,
pos: 0,
cmd_pos: 0,
error: false,
done: false,
start_point: Vector::ZERO,
cur_point: Vector::ZERO,
last_control: Vector::ZERO,
last_cmd: 0,
state: State::Initial,
arc: Arc::default(),
}
}
fn parse(&mut self) -> Option<Command> {
use Command::*;
let mut cmd = self.cur;
loop {
if let Some(cmd) = self.arc.next() {
return Some(cmd);
}
self.last_cmd = cmd;
match self.state {
State::Initial => {
self.advance();
self.skip_whitespace();
self.state = State::Next;
continue;
}
State::Next => {
self.skip_whitespace();
self.cmd_pos = self.pos;
cmd = self.cur;
self.advance();
self.skip_whitespace();
self.state = State::Continue(cmd);
match cmd {
b'z' | b'Z' => {
self.state = State::Next;
self.cur_point = self.start_point;
return Some(Close);
}
b'M' => {
let to = self.point_to()?;
self.start_point = to;
self.skip_comma_whitespace();
return Some(MoveTo(to));
}
b'm' => {
let to = self.rel_point_to()?;
self.start_point = to;
self.skip_comma_whitespace();
return Some(MoveTo(to));
}
b'L' => {
let to = self.point_to()?;
self.skip_comma_whitespace();
return Some(LineTo(to));
}
b'l' => {
let to = self.rel_point_to()?;
self.skip_comma_whitespace();
return Some(LineTo(to));
}
b'H' => {
let x = self.coord()?;
let to = Vector::new(x, self.cur_point.y);
self.cur_point = to;
self.skip_comma_whitespace();
return Some(LineTo(to));
}
b'h' => {
let x = self.coord()?;
let to = Vector::new(self.cur_point.x + x, self.cur_point.y);
self.cur_point = to;
self.skip_comma_whitespace();
return Some(LineTo(to));
}
b'V' => {
let y = self.coord()?;
let to = Vector::new(self.cur_point.x, y);
self.cur_point = to;
self.skip_comma_whitespace();
return Some(LineTo(to));
}
b'v' => {
let y = self.coord()?;
let to = Vector::new(self.cur_point.x, self.cur_point.y + y);
self.cur_point = to;
self.skip_comma_whitespace();
return Some(LineTo(to));
}
b'C' => {
let (c1, c2, to) = self.three_points_to()?;
self.last_control = c2;
self.skip_comma_whitespace();
return Some(CurveTo(c1, c2, to));
}
b'c' => {
let (c1, c2, to) = self.rel_three_points_to()?;
self.last_control = c2;
self.skip_comma_whitespace();
return Some(CurveTo(c1, c2, to));
}
b'S' => {
let (c2, to) = self.two_points()?;
let c1 = self.reflected_control(cmd);
self.cur_point = to;
self.last_control = c2;
self.skip_comma_whitespace();
return Some(CurveTo(c1, c2, to));
}
b's' => {
let (c2, to) = self.rel_two_points()?;
let c1 = self.reflected_control(cmd);
self.cur_point = to;
self.last_control = c2;
self.skip_comma_whitespace();
return Some(CurveTo(c1, c2, to));
}
b'Q' => {
let (c, to) = self.two_points_to()?;
self.last_control = c;
self.skip_comma_whitespace();
return Some(QuadTo(c, to));
}
b'q' => {
let (c, to) = self.rel_two_points_to()?;
self.last_control = c;
self.skip_comma_whitespace();
return Some(QuadTo(c, to));
}
b'T' => {
let to = self.point()?;
let c = self.reflected_control(cmd);
self.cur_point = to;
self.last_control = c;
self.skip_comma_whitespace();
return Some(QuadTo(c, to));
}
b't' => {
let to = self.rel_point()?;
let c = self.reflected_control(cmd);
self.cur_point = to;
self.last_control = c;
self.skip_comma_whitespace();
return Some(QuadTo(c, to));
}
b'A' => {
let from = self.cur_point;
let (rx, ry, a, size, sweep, to) = self.arc_arguments(false)?;
self.arc = Arc::new(from, rx, ry, a.to_radians(), size, sweep, to);
self.skip_comma_whitespace();
continue;
}
b'a' => {
let from = self.cur_point;
let (rx, ry, a, size, sweep, to) = self.arc_arguments(true)?;
self.arc = Arc::new(from, rx, ry, a.to_radians(), size, sweep, to);
self.skip_comma_whitespace();
continue;
}
_ => {
if !self.done || cmd != 0 {
self.error = true;
self.pos = self.cmd_pos;
}
return None;
}
}
}
State::Continue(cmd) => match cmd {
b'M' => {
if let Some(to) = self.point_to() {
self.skip_comma_whitespace();
return Some(LineTo(to));
} else {
self.state = State::Next;
}
}
b'm' => {
if let Some(to) = self.rel_point_to() {
self.skip_comma_whitespace();
return Some(LineTo(to));
} else {
self.state = State::Next;
}
}
b'L' => {
if let Some(to) = self.point_to() {
self.skip_comma_whitespace();
return Some(LineTo(to));
} else {
self.state = State::Next;
}
}
b'l' => {
if let Some(to) = self.rel_point_to() {
self.skip_comma_whitespace();
return Some(LineTo(to));
} else {
self.state = State::Next;
}
}
b'H' => {
if let Some(x) = self.coord() {
let to = Vector::new(x, self.cur_point.y);
self.cur_point = to;
self.skip_comma_whitespace();
return Some(LineTo(to));
} else {
self.state = State::Next;
}
}
b'h' => {
if let Some(x) = self.coord() {
let to = Vector::new(self.cur_point.x + x, self.cur_point.y);
self.cur_point = to;
self.skip_comma_whitespace();
return Some(LineTo(to));
} else {
self.state = State::Next;
}
}
b'V' => {
if let Some(y) = self.coord() {
let to = Vector::new(self.cur_point.x, y);
self.cur_point = to;
self.skip_comma_whitespace();
return Some(LineTo(to));
} else {
self.state = State::Next;
}
}
b'v' => {
if let Some(y) = self.coord() {
let to = Vector::new(self.cur_point.x, self.cur_point.y + y);
self.cur_point = to;
self.skip_comma_whitespace();
return Some(LineTo(to));
} else {
self.state = State::Next;
}
}
b'C' => {
if let Some(c1) = self.point() {
self.skip_comma_whitespace();
let (c2, to) = self.two_points_to()?;
self.last_control = c2;
self.skip_comma_whitespace();
return Some(CurveTo(c1, c2, to));
} else {
self.state = State::Next;
}
}
b'c' => {
if let Some(c1) = self.rel_point() {
self.skip_comma_whitespace();
let (c2, to) = self.rel_two_points_to()?;
self.last_control = c2;
self.skip_comma_whitespace();
return Some(CurveTo(c1, c2, to));
} else {
self.state = State::Next;
}
}
b'S' => {
if let Some(c2) = self.point() {
self.skip_comma_whitespace();
let to = self.point()?;
let c1 = self.reflected_control(cmd);
self.cur_point = to;
self.last_control = c2;
self.skip_comma_whitespace();
return Some(CurveTo(c1, c2, to));
} else {
self.state = State::Next;
}
}
b's' => {
if let Some(c2) = self.rel_point() {
self.skip_comma_whitespace();
let to = self.rel_point()?;
let c1 = self.reflected_control(cmd);
self.cur_point = to;
self.last_control = c2;
self.skip_comma_whitespace();
return Some(CurveTo(c1, c2, to));
} else {
self.state = State::Next;
}
}
b'Q' => {
if let Some(c) = self.point() {
self.last_control = c;
self.skip_comma_whitespace();
let to = self.point_to()?;
self.skip_comma_whitespace();
return Some(QuadTo(c, to));
} else {
self.state = State::Next;
}
}
b'q' => {
if let Some(c) = self.rel_point() {
self.last_control = c;
self.skip_comma_whitespace();
let to = self.rel_point_to()?;
self.skip_comma_whitespace();
return Some(QuadTo(c, to));
} else {
self.state = State::Next;
}
}
b'T' => {
if let Some(to) = self.point() {
let c = self.reflected_control(cmd);
self.cur_point = to;
self.last_control = c;
self.skip_comma_whitespace();
return Some(QuadTo(c, to));
} else {
self.state = State::Next;
}
}
b't' => {
if let Some(to) = self.rel_point() {
let c = self.reflected_control(cmd);
self.cur_point = to;
self.last_control = c;
self.skip_comma_whitespace();
return Some(QuadTo(c, to));
} else {
self.state = State::Next;
}
}
b'A' => {
if let Some(rx) = self.coord() {
let from = self.cur_point;
let (ry, a, size, sweep, to) = self.arc_rest_arguments(false)?;
self.arc = Arc::new(from, rx, ry, a.to_radians(), size, sweep, to);
self.skip_comma_whitespace();
} else {
self.state = State::Next;
}
}
b'a' => {
if let Some(rx) = self.coord() {
let from = self.cur_point;
let (ry, a, size, sweep, to) = self.arc_rest_arguments(true)?;
self.arc = Arc::new(from, rx, ry, a.to_radians(), size, sweep, to);
self.skip_comma_whitespace();
} else {
self.state = State::Next;
}
}
_ => {
if !self.done || cmd != 0 {
self.error = true;
self.pos = self.cmd_pos;
}
return None;
}
},
}
}
}
fn reflected_control(&self, cmd: u8) -> Vector {
let cur = self.cur_point;
let old = self.last_control;
if cmd == b'S' || cmd == b's' {
match self.last_cmd {
b'C' | b'c' | b'S' | b's' => (2. * cur.x - old.x, 2. * cur.y - old.y).into(),
_ => self.cur_point,
}
} else {
match self.last_cmd {
b'Q' | b'q' | b'T' | b't' => (2. * cur.x - old.x, 2. * cur.y - old.y).into(),
_ => self.cur_point,
}
}
}
fn arc_arguments(&mut self, rel: bool) -> Option<(f32, f32, f32, ArcSize, ArcSweep, Vector)> {
let rx = self.coord()?;
self.skip_comma_whitespace();
let ry = self.coord()?;
self.skip_comma_whitespace();
let a = self.coord()?;
self.skip_comma_whitespace();
let large_arc = self.boolean()?;
self.skip_comma_whitespace();
let sweep = self.boolean()?;
self.skip_comma_whitespace();
let to = if rel {
self.rel_point_to()?
} else {
self.point_to()?
};
let size = if large_arc {
ArcSize::Large
} else {
ArcSize::Small
};
let sweep = if sweep {
ArcSweep::Positive
} else {
ArcSweep::Negative
};
Some((rx, ry, a, size, sweep, to))
}
fn arc_rest_arguments(&mut self, rel: bool) -> Option<(f32, f32, ArcSize, ArcSweep, Vector)> {
let ry = self.coord()?;
self.skip_comma_whitespace();
let a = self.coord()?;
self.skip_comma_whitespace();
let large_arc = self.boolean()?;
self.skip_comma_whitespace();
let sweep = self.boolean()?;
self.skip_comma_whitespace();
let to = if rel {
self.rel_point_to()?
} else {
self.point_to()?
};
let size = if large_arc {
ArcSize::Large
} else {
ArcSize::Small
};
let sweep = if sweep {
ArcSweep::Positive
} else {
ArcSweep::Negative
};
Some((ry, a, size, sweep, to))
}
fn point(&mut self) -> Option<Vector> {
let a = self.coord()?;
self.skip_comma_whitespace();
let b = self.coord()?;
Some((a, b).into())
}
fn point_to(&mut self) -> Option<Vector> {
let p = self.point()?;
self.cur_point = p;
Some(p)
}
fn rel_point(&mut self) -> Option<Vector> {
let p = self.point()?;
Some(p + self.cur_point)
}
fn rel_point_to(&mut self) -> Option<Vector> {
let p = self.rel_point()?;
self.cur_point = p;
Some(p)
}
fn two_points_to(&mut self) -> Option<(Vector, Vector)> {
let a = self.point()?;
self.skip_comma_whitespace();
let b = self.point_to()?;
Some((a, b))
}
fn two_points(&mut self) -> Option<(Vector, Vector)> {
let a = self.point()?;
self.skip_comma_whitespace();
let b = self.point()?;
Some((a, b))
}
fn rel_two_points_to(&mut self) -> Option<(Vector, Vector)> {
let a = self.rel_point()?;
self.skip_comma_whitespace();
let b = self.rel_point_to()?;
Some((a, b))
}
fn rel_two_points(&mut self) -> Option<(Vector, Vector)> {
let a = self.rel_point()?;
self.skip_comma_whitespace();
let b = self.rel_point()?;
Some((a, b))
}
fn three_points_to(&mut self) -> Option<(Vector, Vector, Vector)> {
let a = self.point()?;
self.skip_comma_whitespace();
let b = self.point()?;
self.skip_comma_whitespace();
let c = self.point_to()?;
Some((a, b, c))
}
fn rel_three_points_to(&mut self) -> Option<(Vector, Vector, Vector)> {
let a = self.rel_point()?;
self.skip_comma_whitespace();
let b = self.rel_point()?;
self.skip_comma_whitespace();
let c = self.rel_point_to()?;
Some((a, b, c))
}
fn coord(&mut self) -> Option<f32> {
match self.cur {
b'+' => {
self.advance();
self.number()
}
b'-' => {
self.advance();
Some(-self.number()?)
}
_ => self.number(),
}
}
fn number(&mut self) -> Option<f32> {
let mut buf = [0u8; 32];
let mut pos = 0;
let mut has_decimal = false;
loop {
match self.cur {
b'.' => {
if has_decimal {
break;
} else {
*buf.get_mut(pos)? = self.cur;
pos += 1;
has_decimal = true;
}
}
b'0'..=b'9' => {
*buf.get_mut(pos)? = self.cur;
pos += 1;
}
_ => break,
}
self.advance();
}
let s = core::str::from_utf8(&buf[..pos]).ok()?;
s.parse::<f32>().ok()
}
fn boolean(&mut self) -> Option<bool> {
match self.cur {
b'0' => {
self.advance();
Some(false)
}
b'1' => {
self.advance();
Some(true)
}
_ => None,
}
}
fn skip_comma_whitespace(&mut self) {
self.skip_whitespace();
if self.accept(b',') {
self.skip_whitespace();
}
}
#[allow(clippy::match_like_matches_macro)]
fn skip_whitespace(&mut self) {
while self.accept_by(|b| match b {
0x9 | 0x20 | 0xA | 0xC | 0xD => true,
_ => false,
}) {}
}
fn accept(&mut self, b: u8) -> bool {
if self.cur == b {
self.advance();
return true;
}
false
}
fn accept_by(&mut self, f: impl Fn(u8) -> bool) -> bool {
if f(self.cur) {
self.advance();
return true;
}
false
}
fn advance(&mut self) {
if self.pos == self.buf.len() {
self.done = true;
self.cur = 0;
return;
}
self.cur = self.buf[self.pos];
self.pos += 1;
}
}
/// Returns an error indicating the first position of invalid SVG path data.
pub fn validate_svg(svg: &str) -> Result<(), usize> {
let cmds = &mut SvgCommands::new(svg);
cmds.count();
let pos = cmds.pos;
if cmds.error || pos != svg.len() {
Err(pos.saturating_sub(1))
} else {
Ok(())
}
}

235
vendor/zeno/src/traversal.rs vendored Normal file
View File

@@ -0,0 +1,235 @@
//! Path traversal algorithms.
use super::command::{Command, TransformCommands};
use super::geometry::*;
use super::path_data::PathData;
use super::segment::{segments, Segment, Segments};
use core::borrow::Borrow;
use core::cell::RefCell;
/// A vertex of a path.
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum Vertex {
/// The start point and direction of a subpath.
Start(Point, Vector),
/// The incoming direction, location, and outgoing direction of an
/// intermediate vertex in a subpath.
Middle(Vector, Point, Vector),
/// The incoming direction and location of the final vertex in a subpath.
/// The boolean value is true if the subpath is closed.
End(Vector, Point, bool),
}
/// An iterator over the vertices of a path.
#[derive(Clone)]
pub struct Vertices<D> {
segments: Segments<D>,
prev_point: Point,
prev_dir: Vector,
is_first: bool,
}
impl<D> Vertices<D>
where
D: Iterator<Item = Command> + Clone,
{
/// Creates a new iterator over the vertices of a path.
pub fn new(data: impl PathData<Commands = D>) -> Self {
Self {
segments: segments(data.commands(), false),
prev_point: Point::ZERO,
prev_dir: Vector::new(1., 0.),
is_first: true,
}
}
}
impl<D> Vertices<TransformCommands<D>>
where
D: Iterator<Item = Command> + Clone,
{
/// Creates a new iterator over the vertices of a transformed path.
pub fn with_transform(data: impl PathData<Commands = D>, transform: Transform) -> Self {
let data = TransformCommands {
data: data.commands(),
transform,
};
Self {
segments: segments(data, false),
prev_point: Point::ZERO,
prev_dir: Vector::new(1., 0.),
is_first: true,
}
}
}
impl<D> Iterator for Vertices<D>
where
D: Iterator + Clone,
D::Item: Borrow<Command>,
{
type Item = Vertex;
fn next(&mut self) -> Option<Self::Item> {
use Segment::*;
if self.is_first {
self.is_first = false;
match self.segments.next()?.borrow() {
End(closed) => {
self.is_first = true;
Some(Vertex::End(self.prev_dir, self.prev_point, *closed))
}
segment => {
let (start, in_dir, out_dir, end) = get_components(segment);
self.prev_dir = out_dir;
self.prev_point = end;
Some(Vertex::Start(start, in_dir))
}
}
} else {
match self.segments.next()?.borrow() {
End(closed) => {
self.is_first = true;
Some(Vertex::End(self.prev_dir, self.prev_point, *closed))
}
segment => {
let (start, in_dir, out_dir, end) = get_components(segment);
let prev_dir = self.prev_dir;
self.prev_dir = out_dir;
self.prev_point = end;
Some(Vertex::Middle(prev_dir, start, in_dir))
}
}
}
}
}
fn get_components(segment: &Segment) -> (Point, Vector, Vector, Point) {
match segment {
Segment::Curve(_, curve) => {
let a = curve.evaluate(0.05);
let b = curve.evaluate(0.95);
let a_dir = (a - curve.a).normalize();
let b_dir = (curve.d - b).normalize();
(curve.a, a_dir, b_dir, curve.d)
}
Segment::Line(_, line) => {
let dir = (line.b - line.a).normalize();
(line.a, dir, dir, line.b)
}
Segment::End(..) => (Point::ZERO, Vector::ZERO, Vector::ZERO, Point::ZERO),
}
}
/// An iterator like type that walks along a path by arbitrary steps.
pub struct Walk<D> {
init: Segments<D>,
iter: Segments<D>,
segment: Segment,
segment_offset: f32,
first: bool,
length: RefCell<Option<f32>>,
walked: f32,
}
impl<D> Walk<D>
where
D: Iterator<Item = Command> + Clone,
{
/// Creates a new iterator like type that steps along a path by arbitrary distances.
pub fn new(data: impl PathData<Commands = D>) -> Self {
let data = data.commands();
Self {
init: segments(data.clone(), false),
iter: segments(data, false),
segment: Segment::default(),
segment_offset: 0.,
first: true,
length: RefCell::new(None),
walked: 0.,
}
}
}
impl<D> Walk<TransformCommands<D>>
where
D: Iterator<Item = Command> + Clone,
{
/// Creates a new iterator like type that steps along a transformed path by arbitrary distances.
pub fn with_transform(data: impl PathData<Commands = D>, transform: Transform) -> Self {
let data = data.commands();
let data = TransformCommands { data, transform };
Self {
init: segments(data.clone(), false),
iter: segments(data, false),
segment: Segment::default(),
segment_offset: 0.,
first: true,
length: RefCell::new(None),
walked: 0.,
}
}
}
impl<D> Walk<D>
where
D: Iterator + Clone,
D::Item: Borrow<Command>,
{
/// Steps by the specified distance and returns the point at the new
/// location and the normal vector describing the left-ward direction at
/// that point. Returns `None` if the distance steps beyond the end
/// of the path.
pub fn step(&mut self, distance: f32) -> Option<(Point, Vector)> {
if self.first {
self.segment = self.next_segment()?;
self.segment_offset = 0.;
self.first = false;
}
let mut t;
let mut offset = self.segment_offset;
let mut segment = self.segment;
let mut remaining = distance;
loop {
let dt = segment.time(offset + remaining, 1.);
remaining -= dt.distance - offset;
t = dt.time;
offset = dt.distance;
if remaining <= 0. {
break;
}
segment = self.next_segment()?;
offset = 0.;
}
self.segment = segment;
self.segment_offset = offset;
self.walked += distance;
let (p, n) = segment.point_normal(t);
Some((p, n))
}
/// Returns the remaining distance available to walk on the path.
pub fn remaining(&self) -> f32 {
let mut l = self.length.borrow_mut();
if l.is_none() {
let iter = self.init.clone();
let mut sum = 0.;
for s in iter {
sum += s.length();
}
*l = Some(sum);
}
l.unwrap() - self.walked
}
fn next_segment(&mut self) -> Option<Segment> {
for s in self.iter.by_ref() {
match s {
Segment::End(..) => continue,
_ => return Some(s),
}
}
None
}
}