Compare commits

..
Author SHA1 Message Date
Dylan M. Taylor d55b798408 Format pedit_vertex imports so the CI fmt gate passes 2026-10-25 19:13:38 -04:00
Dylan M. Taylor fc18f945f0 Restore imports that the cadcraft batch dropped so the engine compiles
The batch replaced glob imports with explicit lists that missed names
the files still use, and pointed some sub-module imports at the wrong
level. Restore the required imports and the test module globs.
2026-10-25 19:13:38 -04:00
Dylan M. Taylor 6b7ccaf213 refactor: replace wildcard imports with explicit ones (S2208, batch_06) 2026-10-25 19:13:38 -04:00
46 changed files with 644 additions and 786 deletions
Generated
+120 -120
View File
@@ -4,9 +4,9 @@ version = 4
[[package]]
name = "acadrust"
version = "0.6.6"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8923c99ea1f151cb7fc17ce2fc14f8b1e330fa22611e535602d985c0cc3c89a2"
checksum = "5eaa72520f70eb6f7f09b06f44e1ed0d8fad5fa743364f2c3715c30df962ec7c"
dependencies = [
"anyhow",
"bitflags 2.13.2",
@@ -28,9 +28,9 @@ dependencies = [
[[package]]
name = "accesskit"
version = "0.25.2"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e883d1d28c5fd5568c79ed2d2bd48d9f2fd3945c8974ed0f53690ceddb051be"
checksum = "ad442f58ee04714aaa0ba0a2768c1ea1935b29507bb22ddece8cbbc76db02932"
dependencies = [
"enumn",
"serde",
@@ -39,9 +39,9 @@ dependencies = [
[[package]]
name = "accesskit_atspi_common"
version = "0.21.1"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24f5baf62e34a4a6c30e3d697e4782f821a7337049c44c3b40576a27dfbb580d"
checksum = "52c182f9c282ac9c5638d876d551d15e5f7d397ec263349a0c6a2b61595dd5e4"
dependencies = [
"accesskit",
"accesskit_consumer",
@@ -53,9 +53,9 @@ dependencies = [
[[package]]
name = "accesskit_consumer"
version = "0.39.2"
version = "0.39.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3da8f27636015191b2264ee1df681503170a632cafd832fdc1edfdd4e3a2fd3"
checksum = "8a45e2bb300bea6d5f33a82c3e051c3ddc6f41c209750f68a0acbe991a284344"
dependencies = [
"accesskit",
"hashbrown 0.17.1",
@@ -63,9 +63,9 @@ dependencies = [
[[package]]
name = "accesskit_ios"
version = "0.2.2"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5280724673bbfa8b30a838c013500b719392989be3b97c5a9964062c252c232"
checksum = "28e539d64b27fd4c14de8f78ada8f7c944c092f52f7e49fb63316507a57f6937"
dependencies = [
"accesskit",
"accesskit_consumer",
@@ -77,9 +77,9 @@ dependencies = [
[[package]]
name = "accesskit_macos"
version = "0.27.2"
version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "123828ceeec755b05255e3976cb0852993202de98a0626c97dd8d34246991f84"
checksum = "5701624bf6a11efb6d4c8e922bf44126ed96250e5faa9a7a0468c979635839a4"
dependencies = [
"accesskit",
"accesskit_consumer",
@@ -91,9 +91,9 @@ dependencies = [
[[package]]
name = "accesskit_unix"
version = "0.24.1"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30d5192871228c54f1b0fb4a85cef000e11a3804f25d2165f14afa0daa67f3f6"
checksum = "202f24df034a7476d07b7f74284de84f6d62aabd858dbe7ee9cad3b7ad6f8f9d"
dependencies = [
"accesskit",
"accesskit_atspi_common",
@@ -109,9 +109,9 @@ dependencies = [
[[package]]
name = "accesskit_windows"
version = "0.35.2"
version = "0.35.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "498668b90da9995302a0055aaecad4756932333da2ddef6baee1c00844c430fc"
checksum = "ce63f35d6bdcf59f26b76b3379063f738e6412cef46999cc772d46aa3de35adb"
dependencies = [
"accesskit",
"accesskit_consumer",
@@ -123,9 +123,9 @@ dependencies = [
[[package]]
name = "accesskit_winit"
version = "0.34.2"
version = "0.34.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd4ab4437b520ff03088fbe48676a11e7d44a929535e64b31432c1073e48dbcc"
checksum = "ee96dd150375c05912d8a473f98c2fb155fb3234fac88292d396548a2460fa77"
dependencies = [
"accesskit",
"accesskit_ios",
@@ -343,7 +343,7 @@ checksum = "4f8abc12baad266b1c8cec146854c195b5864b4221d4b2ca7296a7ae82d9e451"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.9",
"syn 3.0.6",
]
[[package]]
@@ -378,7 +378,7 @@ checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.9",
"syn 3.0.6",
]
[[package]]
@@ -444,9 +444,9 @@ checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5"
[[package]]
name = "basic-udev"
version = "0.1.3"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "868a8681ed3a19fae3e302c705b1cabf5be53278cc8720a9b82e9805c9ab171d"
checksum = "5a45f9771ced8a774de5e5ebffbe520f52e3943bf5a9a6baa3a5d14a5de1afe6"
[[package]]
name = "bit-set"
@@ -541,7 +541,7 @@ checksum = "6a1f896587b6f2c069c73d2f0913e2d590c3990285cd2f0b6aa02b786b4c679c"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.9",
"syn 3.0.6",
]
[[package]]
@@ -820,9 +820,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.10.0"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86e055fa6e3e40ad6187ba25b6855eeb635d5271f8e0f0e1e9a5826b8d75bea0"
checksum = "f74872d07caf508b30a21f6836e7d7016a2eaf7d9ff4f48deaa58cd8a0407630"
dependencies = [
"find-msvc-tools",
"jobserver",
@@ -862,9 +862,9 @@ dependencies = [
[[package]]
name = "clipboard-win"
version = "5.4.3"
version = "5.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "052af7878466feccda12ba7a6a67d019d712f296809559b47c12c8cc196d3a9a"
checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4"
dependencies = [
"error-code",
]
@@ -907,9 +907,9 @@ dependencies = [
[[package]]
name = "combine"
version = "4.6.9"
version = "4.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4356eba6976435498d05a311ee9fc486da3bc81c200d3f0d7dd9703b41913f92"
checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e"
dependencies = [
"bytes",
"memchr",
@@ -1044,9 +1044,9 @@ dependencies = [
[[package]]
name = "crc32fast"
version = "1.5.3"
version = "1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ba106e844045ddc02c20768ac4b0b7cb45f2cc2ecbd833ac9f5dacb183c2055"
checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78"
dependencies = [
"cfg-if",
]
@@ -1162,7 +1162,7 @@ checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.9",
"syn 3.0.6",
]
[[package]]
@@ -1449,9 +1449,9 @@ source = "git+https://github.com/storytold/egui.git?rev=6d518fc799681571963842f9
[[package]]
name = "equivalent"
version = "1.0.3"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
@@ -1537,9 +1537,9 @@ dependencies = [
[[package]]
name = "find-msvc-tools"
version = "0.1.15"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c66915cc5391097de7a2b0b972e8505aa33c0df401fd3a6811dbdb32881ba48"
checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484"
[[package]]
name = "fixedbitset"
@@ -1622,7 +1622,7 @@ checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.9",
"syn 3.0.6",
]
[[package]]
@@ -1673,7 +1673,7 @@ checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.9",
"syn 3.0.6",
]
[[package]]
@@ -2060,9 +2060,9 @@ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
[[package]]
name = "icu_collections"
version = "2.4.0"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe090a42a0d3140fed8c63263ed1fecc02422cb2f4bfe55cd8de2de1b8e105dd"
checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513"
dependencies = [
"displaydoc",
"potential_utf",
@@ -2074,9 +2074,9 @@ dependencies = [
[[package]]
name = "icu_locale_core"
version = "2.4.0"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25d29b57c17c6528d0dc4c2141cfa2d08200465acb537a3909c8b9b8a4a984c0"
checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb"
dependencies = [
"displaydoc",
"litemap",
@@ -2087,9 +2087,9 @@ dependencies = [
[[package]]
name = "icu_normalizer"
version = "2.4.0"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "404a3b8c486f205c87fe7f23ab6894d84a4ad76d6e4671ae1981ba7def87c599"
checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f"
dependencies = [
"icu_collections",
"icu_normalizer_data",
@@ -2101,15 +2101,15 @@ dependencies = [
[[package]]
name = "icu_normalizer_data"
version = "2.4.0"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20f0088bfb7441f7a37fa0c9e20cb1e9cba1cac6b9ce8cd2a6dc44294f8e8cbc"
checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0"
[[package]]
name = "icu_properties"
version = "2.4.0"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ea83d991c56158f7cf898bf86436c1dfe7c07ee82fe88951f2695c9f37a379f"
checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148"
dependencies = [
"displaydoc",
"icu_collections",
@@ -2122,15 +2122,15 @@ dependencies = [
[[package]]
name = "icu_properties_data"
version = "2.4.0"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b98f29af82d02b163fe50161388350e743bb3107cc8e8c2f7314cb505faddfcf"
checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa"
[[package]]
name = "icu_provider"
version = "2.4.0"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413099315636c588ee8f011190b329b971a09a8fae9d157e01dcae8379468514"
checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73"
dependencies = [
"displaydoc",
"icu_locale_core",
@@ -2429,7 +2429,7 @@ dependencies = [
"bitflags 2.13.2",
"libc",
"plain",
"redox_syscall 0.9.5",
"redox_syscall 0.9.4",
]
[[package]]
@@ -2458,9 +2458,9 @@ checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae"
[[package]]
name = "litrs"
version = "1.0.1"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4744e383959f0db86ede514b809b1c53251889093803c05267acc7d4e7030d70"
checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
[[package]]
name = "lock_api"
@@ -2479,9 +2479,9 @@ checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
[[package]]
name = "lyon_geom"
version = "1.0.20"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d82dd26aa4dc0b5b70b9115de59824842bcb43cf4a28e1b1e6d0ad148c93f93e"
checksum = "4336502e29e32af93cf2dad2214ed6003c17ceb5bd499df77b1de663b9042b92"
dependencies = [
"arrayvec",
"euclid",
@@ -2517,9 +2517,9 @@ checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
name = "memmap2"
version = "0.9.12"
version = "0.9.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b0ca1155a9ecc25aa12e8313a119f32edf0255fea2f44839c4ac0a12b2ac157"
checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0"
dependencies = [
"libc",
]
@@ -3076,9 +3076,9 @@ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "open"
version = "5.4.5"
version = "5.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d8c8b8e725ad26404d6896eb2f064294a0a7b4f1d9120210d85544684c4ca95"
checksum = "aa576c76302b7b808eecc68061e67336c47833ef9d22caa74dda10fa9675eebc"
dependencies = [
"is-wsl",
"libc",
@@ -3102,9 +3102,9 @@ dependencies = [
[[package]]
name = "ordered-float"
version = "5.5.1"
version = "5.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f59006ce1e204c30956638535b74d317bca99477b9356dfaec54c37f6f600ab"
checksum = "8c7c9e0d9b23589f26070720bac724174bfec1083e82f7854cdd0267518343c0"
dependencies = [
"num-traits",
]
@@ -3580,9 +3580,9 @@ dependencies = [
[[package]]
name = "redox_syscall"
version = "0.9.5"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "580c875845b41ecb1564ad1ae4703044cef774145e3d7a94dc2b3fa7c97868fd"
checksum = "737970939a87c6fa31e7acad13307bccbb017a073b695b6089a2c484f929e20e"
dependencies = [
"bitflags 2.13.2",
]
@@ -3894,7 +3894,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.9",
"syn 3.0.6",
]
[[package]]
@@ -3918,14 +3918,14 @@ checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.9",
"syn 3.0.6",
]
[[package]]
name = "serde_spanned"
version = "1.1.2"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7523beb55eece201a2356bee0bbca0d1ab466c14c07703b2e0ee6d42cb0c2c"
checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
dependencies = [
"serde_core",
]
@@ -4032,9 +4032,9 @@ dependencies = [
[[package]]
name = "smallvec"
version = "1.16.4"
version = "1.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0d5d4b1981b8bbbb7aed204cae5726d683d5d6ac7e74ec54122ea28f315e7c8"
checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e"
dependencies = [
"serde",
]
@@ -4180,9 +4180,9 @@ dependencies = [
[[package]]
name = "syn"
version = "3.0.9"
version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b"
checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
dependencies = [
"proc-macro2",
"quote",
@@ -4197,7 +4197,7 @@ checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.9",
"syn 3.0.6",
]
[[package]]
@@ -4278,7 +4278,7 @@ checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.9",
"syn 3.0.6",
]
[[package]]
@@ -4353,9 +4353,9 @@ dependencies = [
[[package]]
name = "tinystr"
version = "0.8.5"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abc840c238154d58f9e86b2792261edfde565a17696e1cda1a449de27979801a"
checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643"
dependencies = [
"displaydoc",
"zerovec",
@@ -4369,18 +4369,18 @@ checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee"
[[package]]
name = "tokio"
version = "1.54.1"
version = "1.53.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b52fd3255258d26fac7264ab5a76b53c13367136dc011d3d6ad4f8ed070396a3"
checksum = "e95f91fcc7a621e8b030f6aa23c71fe9838ae2fb4d8118b75602a328f5144044"
dependencies = [
"pin-project-lite",
]
[[package]]
name = "toml"
version = "1.1.8+spec-1.1.0"
version = "1.1.6+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20489e00e4d8741d6be680764cc12e270655e375a20d1011e844a9c3379e678d"
checksum = "920602543f0911ab71da12c50d59701da54c196d1a2bf5cb4b75667f137a406a"
dependencies = [
"indexmap",
"serde_core",
@@ -4393,18 +4393,18 @@ dependencies = [
[[package]]
name = "toml_datetime"
version = "1.1.2+spec-1.1.0"
version = "1.1.1+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b86d767906c6c42421dcba507eb9d203e779497710a47782a224bb871653053"
checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
dependencies = [
"serde_core",
]
[[package]]
name = "toml_edit"
version = "0.25.17+spec-1.1.0"
version = "0.25.15+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3641d5bbb5349a79e1020a242d251efbc546ad8048d133958323ce9c40a9c9c"
checksum = "1340ea94a5856333492c9064b02c778b191dd2c853778d9609debdcdfea3a614"
dependencies = [
"indexmap",
"toml_datetime",
@@ -4414,18 +4414,18 @@ dependencies = [
[[package]]
name = "toml_parser"
version = "1.1.5+spec-1.1.0"
version = "1.1.3+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baa693a8032d7e1cada7d0041e96126df243179ff061456783ac7f12bda4744c"
checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56"
dependencies = [
"winnow",
]
[[package]]
name = "toml_writer"
version = "1.1.3+spec-1.1.0"
version = "1.1.2+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06bdbd8cfc056b8d2e2e85f29b56a3bdbecb527cef81eb39e3e7b98af4652770"
checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2"
[[package]]
name = "tracing"
@@ -4522,9 +4522,9 @@ checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f"
[[package]]
name = "unicode-ident"
version = "1.0.27"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
[[package]]
name = "unicode-normalization"
@@ -4614,9 +4614,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "uuid"
version = "1.29.0"
version = "1.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a0e2d11620c50695fe12bbbfde96b3477fe606e7284c22d505c12ce51214514"
checksum = "97277d36b9c3ace13e58fa6e753f8b0bbbf302a18dd193240d70f9e29681059a"
dependencies = [
"js-sys",
"serde_core",
@@ -4734,7 +4734,7 @@ dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn 3.0.9",
"syn 3.0.6",
"wasm-bindgen-shared",
]
@@ -4904,9 +4904,9 @@ dependencies = [
[[package]]
name = "webbrowser"
version = "1.2.5"
version = "1.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2c8ddfbbf66e501434657400268cefe51b95140a0266236415cda7627732df7"
checksum = "62c35be770821a214dbc362fc26908c853e776c0004294d0b10b8a6bad582f94"
dependencies = [
"jni",
"log",
@@ -5533,9 +5533,9 @@ dependencies = [
[[package]]
name = "yoke"
version = "0.8.4"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "417ef3fcf6c300b7dbd25c85531a746931c02ad536f3cf4786ed1e88223340b3"
checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5"
dependencies = [
"stable_deref_trait",
"yoke-derive",
@@ -5550,7 +5550,7 @@ checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.9",
"syn 3.0.6",
"synstructure",
]
@@ -5622,7 +5622,7 @@ dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn 3.0.9",
"syn 3.0.6",
"zbus_names",
"zvariant",
"zvariant_utils",
@@ -5630,9 +5630,9 @@ dependencies = [
[[package]]
name = "zbus_names"
version = "4.3.5"
version = "4.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812a12be4ead7a59e18ea8f047d08add0066b73bc62fc1467066a2472ae088dd"
checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e"
dependencies = [
"serde",
"winnow",
@@ -5662,18 +5662,18 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.8.63"
version = "0.8.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5fe1f8f1b06191a00962174c61aa5005e0bb391a6d80d07e24d115c01a92ed8"
checksum = "879fb705ce98c32e41ebdb970fbe1204f8492423b314c6ab0354c3e7b5542866"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.63"
version = "0.8.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "863ad3ac83293fb4d740aedbfdc9240dd8d1a50c1099acd76ce80ce7c7230c7f"
checksum = "708882a28301d604fa039cc7727607a98d04c4b86dc76ec9cc683f805d709759"
dependencies = [
"proc-macro2",
"quote",
@@ -5697,7 +5697,7 @@ checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.9",
"syn 3.0.6",
"synstructure",
]
@@ -5720,9 +5720,9 @@ dependencies = [
[[package]]
name = "zerovec"
version = "0.11.9"
version = "0.11.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f58a2799cfe9a1670c7ea8edbd52ef8edb93cb37a7f83bf31ba9e35d9dae6a40"
checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8"
dependencies = [
"yoke",
"zerofrom",
@@ -5731,20 +5731,20 @@ dependencies = [
[[package]]
name = "zerovec-derive"
version = "0.11.7"
version = "0.11.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3743ddbf47738d929cd1ae2e199d40d0162979dfe31aef8ae646d2bf00bf97a"
checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.9",
"syn 3.0.6",
]
[[package]]
name = "zip"
version = "9.1.1"
version = "9.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "518b847fd864440c379748ad684c449f293af68978a485a4ff16f7c901c1f7c4"
checksum = "2886b00003a72c4ccee3f42cfc34908d33b2508a74d827dc2a4244706475c704"
dependencies = [
"crc32fast",
"flate2",
@@ -5798,9 +5798,9 @@ dependencies = [
[[package]]
name = "zvariant"
version = "5.15.1"
version = "5.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80bf6d6cd6a4b601acb64cfb8284cdc643fcfa75ef26ae3c46ec8aa4e8a1fbb7"
checksum = "c1d34c27cc6cdd1f458427519dd6b8612f7b7e3f7b9a0b2355d041dda9869147"
dependencies = [
"endi",
"enumflags2",
@@ -5813,14 +5813,14 @@ dependencies = [
[[package]]
name = "zvariant_derive"
version = "5.15.1"
version = "5.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef2672456ba1cb54b5eaec136e63135b3c7495fb938e53b28a62e633496d486d"
checksum = "864155e69b4352db0c7f374917bf45d1e0c8d17659c8b3dbf9795f3673f8c497"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn 3.0.9",
"syn 3.0.6",
"zvariant_utils",
]
@@ -5833,7 +5833,7 @@ dependencies = [
"proc-macro2",
"quote",
"serde",
"syn 3.0.9",
"syn 3.0.6",
"winnow",
]
+24 -23
View File
@@ -120,7 +120,7 @@ fn style(s: &Session) -> String {
s.doc().map(|d| d.header.str("DIMSTYLE", "Standard")).unwrap_or_else(|_| "Standard".into())
}
fn dim(s: &Session, kind: DimKind, defpt: Vec2, p13: Vec2, p14: Vec2, p15: Vec2, text: &str) -> EntityKind {
fn dim(s: &Session, kind: DimKind, defpt: Vec2, p13: Vec2, p14: Vec2, p15: Vec2, p16: Vec2, text: &str) -> EntityKind {
EntityKind::Dimension(Dimension {
kind,
defpt: v3(defpt),
@@ -128,7 +128,7 @@ fn dim(s: &Session, kind: DimKind, defpt: Vec2, p13: Vec2, p14: Vec2, p15: Vec2,
p13: v3(p13),
p14: v3(p14),
p15: v3(p15),
p16: v3(Vec2::ZERO),
p16: v3(p16),
text: text.into(),
style: style(s),
measurement: 0.0,
@@ -205,10 +205,7 @@ fn angular_from_lines(s: &Session, h1: Handle, h2: Handle, at: Vec2) -> Option<E
// The picked leg end goes last in each pair, so the renderer measures between p14 and p15.
let (o1, t1) = if s1 == AssocSnap::Start { (a2, AssocSnap::End) } else { (a1, AssocSnap::Start) };
let (o2, t2) = if s2 == AssocSnap::Start { (b2, AssocSnap::End) } else { (b1, AssocSnap::Start) };
let mut k = dim(s, DimKind::Angular, o2, o1, p1, p2, "");
if let EntityKind::Dimension(d) = &mut k {
d.p16 = v3(at);
}
let k = dim(s, DimKind::Angular, o2, o1, p1, p2, at, "");
let mut assoc = Vec::new();
if assoc_enabled(s) {
assoc = vec![
@@ -228,7 +225,7 @@ fn run_linear(s: &mut Session, p: &Value) -> Result<Value> {
};
let at = point_req("dimlinear", p, "at")?;
let rot = p.get("rotation").and_then(Value::as_f64).map(f64::to_radians);
let k = dim(s, linear_kind(a, b, at, rot, false), at, a, b, Vec2::ZERO, str_param(p, "text").unwrap_or(""));
let k = dim(s, linear_kind(a, b, at, rot, false), at, a, b, Vec2::ZERO, Vec2::ZERO, str_param(p, "text").unwrap_or(""));
Ok(json!({ "handle": add_dim(s, k)?.hex() }))
}
@@ -236,7 +233,7 @@ fn run_aligned(s: &mut Session, p: &Value) -> Result<Value> {
let a = point_req("dimaligned", p, "p1")?;
let b = point_req("dimaligned", p, "p2")?;
let at = point_req("dimaligned", p, "at")?;
let k = dim(s, DimKind::Aligned, at, a, b, Vec2::ZERO, str_param(p, "text").unwrap_or(""));
let k = dim(s, DimKind::Aligned, at, a, b, Vec2::ZERO, Vec2::ZERO, str_param(p, "text").unwrap_or(""));
Ok(json!({ "handle": add_dim(s, k)?.hex() }))
}
@@ -264,9 +261,9 @@ fn radial(s: &mut Session, p: &Value, diameter: bool) -> Result<Value> {
return Err(bad(id, "`point` must differ from `center`"));
}
let k = if diameter {
dim(s, DimKind::Diameter, c + (c - pt), Vec2::ZERO, Vec2::ZERO, pt, "")
dim(s, DimKind::Diameter, c + (c - pt), Vec2::ZERO, Vec2::ZERO, pt, Vec2::ZERO, "")
} else {
dim(s, DimKind::Radius, c, Vec2::ZERO, Vec2::ZERO, pt, "")
dim(s, DimKind::Radius, c, Vec2::ZERO, Vec2::ZERO, pt, Vec2::ZERO, "")
};
let k = match at {
Some(at) => text_at(k, at),
@@ -311,7 +308,7 @@ fn run_angular(s: &mut Session, p: &Value) -> Result<Value> {
_ => return Err(bad("dimangular", "object is not an arc")),
};
let g = cadcraft_geom::Arc::new(a.center.xy(), a.radius, a.start, a.end);
let k = dim(s, DimKind::Angular3P, at, g.start_point(), g.end_point(), g.center, "");
let k = dim(s, DimKind::Angular3P, at, g.start_point(), g.end_point(), g.center, Vec2::ZERO, "");
return Ok(json!({ "handle": add_dim(s, k)?.hex() }));
}
let v = point_req("dimangular", p, "vertex")?;
@@ -321,7 +318,7 @@ fn run_angular(s: &mut Session, p: &Value) -> Result<Value> {
if v.near(a, 1e-12) || v.near(b, 1e-12) {
return Err(bad("dimangular", "`p1` and `p2` must differ from `vertex`"));
}
let k = dim(s, DimKind::Angular3P, at, a, b, v, "");
let k = dim(s, DimKind::Angular3P, at, a, b, v, Vec2::ZERO, "");
Ok(json!({ "handle": add_dim(s, k)?.hex() }))
}
@@ -333,7 +330,7 @@ fn run_arclen(s: &mut Session, p: &Value) -> Result<Value> {
_ => return Err(bad("dimarc", "object is not an arc")),
};
let ga = cadcraft_geom::Arc::new(a.center.xy(), a.radius, a.start, a.end);
let k = dim(s, DimKind::ArcLength, at, ga.start_point(), ga.end_point(), ga.center, "");
let k = dim(s, DimKind::ArcLength, at, ga.start_point(), ga.end_point(), ga.center, Vec2::ZERO, "");
Ok(json!({ "handle": add_dim(s, k)?.hex() }))
}
@@ -341,7 +338,7 @@ fn run_ordinate(s: &mut Session, p: &Value) -> Result<Value> {
let f = point_req("dimordinate", p, "feature")?;
let l = point_req("dimordinate", p, "leader")?;
let xtype = p.get("xtype").and_then(Value::as_bool).unwrap_or((l - f).y.abs() > (l - f).x.abs());
let k = dim(s, DimKind::Ordinate { x_type: xtype }, Vec2::ZERO, f, l, Vec2::ZERO, "");
let k = dim(s, DimKind::Ordinate { x_type: xtype }, Vec2::ZERO, f, l, Vec2::ZERO, Vec2::ZERO, "");
Ok(json!({ "handle": add_dim(s, k)?.hex() }))
}
@@ -1063,7 +1060,7 @@ impl Interactive for LinearM {
}
(_, Input::Point(at)) => {
let (Some(&a), Some(&b)) = (self.pts.first(), self.pts.get(1)) else { return Ok(Step::Continue) };
let k = self.text.apply(dim(s, linear_kind(a, b, at, self.rotation, self.aligned), at, a, b, Vec2::ZERO, ""));
let k = self.text.apply(dim(s, linear_kind(a, b, at, self.rotation, self.aligned), at, a, b, Vec2::ZERO, Vec2::ZERO, ""));
add_dim(s, k)?;
if let Some(EntityKind::Dimension(d)) = s.last_dim.and_then(|h| s.doc().ok()?.entity(h).map(|e| e.kind.clone())) {
let st = s.doc()?.dim_style(&d.style).cloned().unwrap_or_default();
@@ -1090,7 +1087,7 @@ impl Interactive for LinearM {
fn preview(&self, s: &Session, c: Vec2) -> Vec<EntityKind> {
match self.pts.as_slice() {
[a] => vec![super::helpers::line(*a, c)],
[a, b] => vec![self.text.apply(dim(s, linear_kind(*a, *b, c, self.rotation, self.aligned), c, *a, *b, Vec2::ZERO, ""))],
[a, b] => vec![self.text.apply(dim(s, linear_kind(*a, *b, c, self.rotation, self.aligned), c, *a, *b, Vec2::ZERO, Vec2::ZERO, ""))],
_ => Vec::new(),
}
}
@@ -1109,9 +1106,9 @@ impl RadialM {
fn kind(&self, s: &Session, c: Vec2, r: f64, at: Vec2) -> EntityKind {
let pt = c + (at - c).normalized() * r;
let k = self.text.apply(if self.diameter {
dim(s, DimKind::Diameter, c + (c - pt), Vec2::ZERO, Vec2::ZERO, pt, "")
dim(s, DimKind::Diameter, c + (c - pt), Vec2::ZERO, Vec2::ZERO, pt, Vec2::ZERO, "")
} else {
dim(s, DimKind::Radius, c, Vec2::ZERO, Vec2::ZERO, pt, "")
dim(s, DimKind::Radius, c, Vec2::ZERO, Vec2::ZERO, pt, Vec2::ZERO, "")
});
text_at(k, at)
}
@@ -1244,7 +1241,7 @@ impl Interactive for AngularM {
([h1, h2], None) => angular_from_lines(s, *h1, *h2, at),
_ => None,
};
let k = k.unwrap_or_else(|| dim(s, DimKind::Angular3P, at, a, b, v, ""));
let k = k.unwrap_or_else(|| dim(s, DimKind::Angular3P, at, a, b, v, Vec2::ZERO, ""));
add_dim(s, self.text.apply(k))?;
Ok(Step::Done)
}
@@ -1310,7 +1307,9 @@ impl Interactive for AngularM {
}
}
fn preview(&self, s: &Session, c: Vec2) -> Vec<EntityKind> {
self.geometry().map(|(v, a, b)| vec![self.text.apply(dim(s, DimKind::Angular3P, self.located(v, c), a, b, v, ""))]).unwrap_or_default()
self.geometry()
.map(|(v, a, b)| vec![self.text.apply(dim(s, DimKind::Angular3P, self.located(v, c), a, b, v, Vec2::ZERO, ""))])
.unwrap_or_default()
}
}
@@ -1386,7 +1385,7 @@ impl Interactive for ArcLenM {
Ok(Step::Continue)
}
(Some(g), Input::Point(at)) => {
add_dim(s, self.text.apply(dim(s, DimKind::ArcLength, at, g.start_point(), g.end_point(), g.center, "")))?;
add_dim(s, self.text.apply(dim(s, DimKind::ArcLength, at, g.start_point(), g.end_point(), g.center, Vec2::ZERO, "")))?;
Ok(Step::Done)
}
(Some(_), Input::Keyword(k)) => {
@@ -1405,7 +1404,9 @@ impl Interactive for ArcLenM {
if self.partial.is_some() {
return Vec::new();
}
self.arc.map(|g| vec![self.text.apply(dim(s, DimKind::ArcLength, c, g.start_point(), g.end_point(), g.center, ""))]).unwrap_or_default()
self.arc
.map(|g| vec![self.text.apply(dim(s, DimKind::ArcLength, c, g.start_point(), g.end_point(), g.center, Vec2::ZERO, ""))])
.unwrap_or_default()
}
}
@@ -1420,7 +1421,7 @@ struct OrdinateM {
impl OrdinateM {
fn kind(&self, s: &Session, f: Vec2, l: Vec2) -> EntityKind {
let x = self.xtype.unwrap_or((l - f).y.abs() > (l - f).x.abs());
self.text.apply(dim(s, DimKind::Ordinate { x_type: x }, Vec2::ZERO, f, l, Vec2::ZERO, ""))
self.text.apply(dim(s, DimKind::Ordinate { x_type: x }, Vec2::ZERO, f, l, Vec2::ZERO, Vec2::ZERO, ""))
}
}
+1 -1
View File
@@ -283,7 +283,7 @@ fn jogged_kind(s: &Session, c: Vec2, r: f64, center: Vec2, at: Vec2, jog: Option
}
let tip = c + dir * r;
let jog = jog.unwrap_or_else(|| center.mid(tip));
let mut k = dim(s, DimKind::Radius, c, Vec2::ZERO, Vec2::ZERO, tip, text);
let mut k = dim(s, DimKind::Radius, c, Vec2::ZERO, Vec2::ZERO, tip, Vec2::ZERO, text);
if let EntityKind::Dimension(dm) = &mut k {
dm.marks.jogged = Some(JoggedRadius { center: v3(center), jog: v3(jog) });
if at.dist(c) > r + 1e-9 {
+5 -4
View File
@@ -96,9 +96,9 @@ fn plan(
let dir = if dir == Vec2::ZERO || !dir.is_finite() { Vec2::from_angle(std::f64::consts::FRAC_PI_4) } else { dir };
let p = *c + dir * *r;
out.push(if mode == Mode::Diameter {
dim(s, DimKind::Diameter, *c - dir * *r, Vec2::ZERO, Vec2::ZERO, p, "")
dim(s, DimKind::Diameter, *c - dir * *r, Vec2::ZERO, Vec2::ZERO, p, Vec2::ZERO, "")
} else {
dim(s, DimKind::Radius, *c, Vec2::ZERO, Vec2::ZERO, p, "")
dim(s, DimKind::Radius, *c, Vec2::ZERO, Vec2::ZERO, p, Vec2::ZERO, "")
});
}
if out.is_empty() {
@@ -131,7 +131,7 @@ fn plan(
.iter()
.map(|p| {
let end = if vertical { Vec2::new(at.x, p.y) } else { Vec2::new(p.x, at.y) };
dim(s, DimKind::Ordinate { x_type: !vertical }, origin, *p, end, Vec2::ZERO, "")
dim(s, DimKind::Ordinate { x_type: !vertical }, origin, *p, end, Vec2::ZERO, Vec2::ZERO, "")
})
.collect());
}
@@ -147,7 +147,8 @@ fn plan(
let mid = bounds.center();
let out_dir =
if vertical { Vec2::new(if at.x >= mid.x { 1.0 } else { -1.0 }, 0.0) } else { Vec2::new(0.0, if at.y >= mid.y { 1.0 } else { -1.0 }) };
let lin = |a: Vec2, b: Vec2, k: usize| dim(s, DimKind::Linear { rotation: rot }, at + out_dir * (step * k as f64), a, b, Vec2::ZERO, "");
let lin =
|a: Vec2, b: Vec2, k: usize| dim(s, DimKind::Linear { rotation: rot }, at + out_dir * (step * k as f64), a, b, Vec2::ZERO, Vec2::ZERO, "");
Ok(match mode {
Mode::Baseline => {
// From the datum (the point nearest it, by default the first) to every other point.
+1 -2
View File
@@ -16,8 +16,7 @@ use serde_json::{Value, json};
use super::super::helpers::v3;
use super::super::machines::{SelOutcome, SelectPhase, number};
use super::super::{CommandSpec, bad, bool_or, point_param, targets};
use super::super::*;
use super::{KEYWORDS, angle, check_size, count, fmt, nesting, other, yes, yes_no};
use crate::{Accept, Input, Interactive, Prompt, Result, Session, Step};
+1 -3
View File
@@ -5,9 +5,7 @@ use std::sync::Arc;
use serde_json::{Value, json};
use super::{CommandSpec, bad, bool_or, file, str_param};
use super::always;
use super::*;
use crate::{Accept, Input, Interactive, Prompt, Result, Session, Step};
pub fn specs() -> Vec<CommandSpec> {
+1 -3
View File
@@ -12,9 +12,7 @@ use serde_json::{Value, json};
use super::helpers::v3;
use super::machines::{SelOutcome, SelectPhase, number};
use super::{CommandSpec, bad, bool_or, f64_or, find_command, ok, point_param, point_req, point_value, str_param, targets};
use super::has_doc;
use super::*;
use crate::{Accept, EngineError, Input, Interactive, Prompt, Result, Session, Step};
mod attedit;
+1 -2
View File
@@ -14,8 +14,7 @@ use cadcraft_geom::{Line, Vec2};
use serde_json::{Value, json};
use super::machines::{SelOutcome, SelectPhase};
use super::{CommandSpec, bad, bool_or, f64_or, point_value, str_param, targets};
use super::*;
use crate::{Accept, EngineError, Input, Interactive, Prompt, Result, Session, Step};
macro_rules! gc {
+2 -7
View File
@@ -5,15 +5,10 @@ use cadcraft_doc::{EntityKind, HAlign, MText, Point, RayLine, Text, VAlign};
use cadcraft_geom::{Arc, Circle, Ellipse, PolyVertex, Polyline, Spline, Vec2, arc_to_bulge, bulge_to_arc};
use serde_json::{Value, json};
use super::helpers::{arc, circle, line, lwpoly, polygon_from_edge, polygon_typed_radius_point, polygon_vertices, v3};
use super::helpers::*;
use super::machines::number;
use super::pline_opts::{Outcome, PlineAsk};
use super::{
CommandSpec, bad, bool_or, distinct_points, f64_or, f64_req, find_command, point_param, point_req, point_value, points_param, size_param,
str_param,
};
use super::*;
use crate::{Accept, Input, Interactive, Prompt, Result, Session, Step, snap};
/// Every draw command's spec: JSON parameters, menu path, aliases and the interactive prompt
+6 -9
View File
@@ -7,11 +7,9 @@ use cadcraft_geom::{Arc, Circle, Ellipse, Line, PolyVertex, Polyline, Spline, TA
use serde_json::{Value, json};
use super::curves::{self, Chain, MAX_GEN, TanObj};
use super::helpers::{arc, circle, line, lwpoly, rect_vertices, rect_with_corners, v3};
use super::helpers::*;
use super::machines::number;
use super::{CommandSpec, bad, bool_or, f64_or, f64_req, point_param, point_req, point_value, points_param, size_param, str_param, targets};
use super::*;
use crate::{Accept, EngineError, Input, Interactive, Prompt, Result, Session, Step};
pub(crate) mod breakline;
@@ -1574,7 +1572,7 @@ impl Interactive for XlineM2 {
// RECTANG with Area / Dimensions / Rotation / Width / Elevation / Thickness
// =====================================================================================
fn rect_kind(first: Vec2, (w, h): (f64, f64), rot: f64, fillet: f64, chamfer: (f64, f64), width: f64, elevation: f64) -> EntityKind {
fn rect_kind(first: Vec2, w: f64, h: f64, rot: f64, fillet: f64, chamfer: (f64, f64), width: f64, elevation: f64) -> EntityKind {
let local = rect_with_corners(Vec2::ZERO, Vec2::new(w, h), fillet, chamfer);
let vs = local.into_iter().map(|v| PolyVertex { p: first + v.p.rotate(rot), ..v }).collect();
let mut k = lwpoly(vs, true);
@@ -1633,7 +1631,7 @@ pub(crate) fn run_rectang2(s: &mut Session, p: &Value) -> Result<Value> {
return Err(bad("rectang", "rectangle has no area"));
}
let first = if centered { a - Vec2::new(w / 2.0, h / 2.0).rotate(rot) } else { a };
let k = rect_kind(first, (w, h), rot, fillet, ch, width, f64_or(p, "elevation", 0.0));
let k = rect_kind(first, w, h, rot, fillet, ch, width, f64_or(p, "elevation", 0.0));
let hd = s.add_entity(k)?;
// A rectangle has its own thickness option and does not take THICKNESS.
let th = f64_or(p, "thickness", 0.0);
@@ -1685,8 +1683,7 @@ impl RectM2 {
return Err(other("The rectangle has no area."));
}
let first = self.origin(w, h).unwrap_or(a);
let hd = s.add_entity(rect_kind(first, (w, h), self.rotation, self.fillet, self.chamfer, self.width, self.elevation))?;
// A rectangle has its own thickness option and does not take THICKNESS.
let hd = s.add_entity(rect_kind(first, w, h, self.rotation, self.fillet, self.chamfer, self.width, self.elevation))?;
let t = self.thickness;
s.doc_mut()?.modify_entity(hd, |e| e.common.thickness = t)?;
Ok(Step::Done)
@@ -1864,7 +1861,7 @@ impl Interactive for RectM2 {
}
match self.other_corner(c).and_then(|(w, h)| self.origin(w, h).map(|a| (a, w, h))) {
Some((a, w, h)) if w.abs() > 1e-12 && h.abs() > 1e-12 => {
vec![rect_kind(a, (w, h), self.rotation, self.fillet, self.chamfer, self.width, self.elevation)]
vec![rect_kind(a, w, h, self.rotation, self.fillet, self.chamfer, self.width, self.elevation)]
}
_ => Vec::new(),
}
+2 -3
View File
@@ -9,11 +9,10 @@ use cadcraft_doc::{Drawing, EntityKind};
use cadcraft_geom::{PolyVertex, Vec2};
use serde_json::Value;
use super::super::machines::number;
use super::added;
use super::curves::MAX_GEN;
use super::{CommandSpec, line, lwpoly, point_param, point_req, size_param, str_param};
use super::machines::number;
use super::*;
use crate::cmd::modify2::as_lwpoly;
use crate::{Accept, EngineError, Input, Interactive, Prompt, Result, Session, Step};
+2 -4
View File
@@ -9,11 +9,9 @@ use cadcraft_doc::{
use cadcraft_geom::{Line, Vec2, line_line_infinite};
use serde_json::{Value, json};
use super::super::machines::number;
use super::super::{distinct_points, ok};
use super::curves::{self, MAX_GEN};
use super::{CommandSpec, bad, bool_or, f64_or, point_req, points_param, str_param};
use super::machines::number;
use super::*;
use crate::{Accept, EngineError, Input, Interactive, Prompt, Result, Session, Step};
pub fn specs() -> Vec<CommandSpec> {
+1 -2
View File
@@ -8,8 +8,7 @@ use cadcraft_doc::{EntityKind, Handle};
use serde_json::{Value, json};
use super::machines::{SelOutcome, SelectPhase, SelectRun};
use super::{CommandSpec, EngineError, bad, ok, targets};
use super::*;
use crate::{Accept, Input, Interactive, Prompt, Result, Session, Step};
pub fn specs() -> Vec<CommandSpec> {
+1 -2
View File
@@ -5,8 +5,7 @@ use cadcraft_geom::{Bounds2, Mat3, Vec2};
use serde_json::{Value, json};
use super::machines::{SelOutcome, SelectPhase, SelectRun};
use super::{CommandSpec, EngineError, bad, bool_or, clipboard, f64_or, has_selection, point_param, point_req, points_param, str_param, targets};
use super::*;
use crate::undoctl::Control;
use crate::{Accept, Input, Interactive, Prompt, Result, Session, Step};
+1 -5
View File
@@ -18,8 +18,7 @@ use cadcraft_render::raster::{RasterOptions, View};
use serde_json::{Value, json};
use super::file::{base64_decode, base64_encode, io};
use super::{CommandSpec, bad, point_value, str_param};
use super::*;
use crate::{EngineError, Result, Session};
/// Formats EXPORT writes (the io layer picks the writer from the extension).
@@ -558,10 +557,7 @@ mod tests {
use cadcraft_geom::Vec3;
use super::super::file::{IoHooks, set_io};
use super::super::resolve_alias;
use super::*;
use serde_json::json;
static DELIVERED: std::sync::Mutex<Vec<(String, String, usize)>> = std::sync::Mutex::new(Vec::new());
+1 -3
View File
@@ -11,8 +11,7 @@ use cadcraft_doc::Drawing;
pub use cadcraft_dxf::Version;
use serde_json::{Value, json};
use super::{CommandSpec, EngineError, Interactive, always, bad, bool_or, find_command, ok, str_param};
use super::*;
use crate::{Accept, Input, Prompt, Result, Session, Step};
/// The rules the Command Aliases dialog checks before it sends an alias to ALIASEDIT.
@@ -732,7 +731,6 @@ mod tests {
#[cfg(test)]
mod pr193_tests {
use super::*;
use crate::Session;
#[test]
fn failed_store_keeps_unsaved_changes_and_delivered_save_clears_them() {
+1 -3
View File
@@ -7,9 +7,7 @@ use std::collections::{HashMap, HashSet};
use cadcraft_doc::Handle;
use serde_json::{Value, json};
use super::super::{CommandSpec, bad};
use super::super::always;
use super::super::*;
use crate::{Accept, Input, Interactive, Prompt, Result, Session, Step};
pub fn specs() -> Vec<CommandSpec> {
-2
View File
@@ -11,13 +11,11 @@ use serde_json::{Value, json};
use super::helpers::lwpoly;
use super::*;
use crate::{Accept, EngineError, Input, Interactive, Picker, Prompt, Result, Session, Step};
mod assoc;
mod edit;
mod pick;
use super::has_doc;
pub(crate) use assoc::update as refill_associative;
pub use pick::{HatchPicks, PickMode};
+1 -1
View File
@@ -13,7 +13,7 @@ use cadcraft_doc::library::{PatternLine, pattern, standard_patterns};
use serde_json::{Value, json};
use super::super::helpers::support_text;
use super::{Result, Session, bad, bool_or};
use super::*;
/// Most user patterns held at once.
pub const MAX_USER_PATTERNS: usize = 4096;
+1 -4
View File
@@ -6,8 +6,7 @@ use cadcraft_doc::{Entity, EntityKind, Handle};
use cadcraft_geom::{Polyline, Vec2, Vec3};
use serde_json::{Value, json};
use super::{CommandSpec, bad, bool_or, f64_req, point_req, point_value, points_param, targets};
use super::*;
use crate::units::{format_angle, format_distance};
use crate::{Accept, Input, Interactive, Prompt, Result, Session, Step};
@@ -1131,8 +1130,6 @@ impl Interactive for AreaM {
#[cfg(test)]
mod tests {
use cadcraft_doc::Handle;
use super::*;
/// #1440 review: a value one ulp past its bound (a border rectangle's shoelace area against
+1 -2
View File
@@ -9,8 +9,7 @@ use cadcraft_doc::{Drawing, Entity, EntityKind, Handle, Insert, MAX_BLOCK_DEPTH,
use cadcraft_geom::{Mat3, Vec2};
use serde_json::{Value, json};
use super::super::{bad, bool_or, point_req, str_param, targets};
use super::super::*;
use super::SelfCheck;
use crate::{Accept, Input, Interactive, Prompt, Result, Session, Step};
+1 -2
View File
@@ -13,8 +13,7 @@ use cadcraft_doc::{EntityKind, Handle};
use cadcraft_geom::{Arc as GeoArc, EPS, Vec2};
use serde_json::{Value, json};
use super::super::bad;
use super::super::*;
use crate::{Result, Session};
const CMD: &str = "loops";
+1 -2
View File
@@ -11,8 +11,7 @@ use cadcraft_geom::{Line, Vec2, line_line, point_in_polygon};
use serde_json::{Value, json};
use super::super::machines::{SelOutcome, SelectPhase, SelectRun};
use super::super::{CommandSpec, bad, targets};
use super::super::*;
use super::SelfCheck;
use crate::{EngineError, Input, Interactive, Prompt, Result, Session, Step};
+1 -3
View File
@@ -6,9 +6,7 @@ use cadcraft_doc::{Entity, EntityKind};
use cadcraft_geom::{Bounds2, Polyline, Vec2, intersect_segments};
use serde_json::{Value, json};
use super::super::bad;
use super::super::point_value;
use super::super::*;
use super::{SelfCheck, area_of, object_area};
use crate::{Result, Session};
+9 -8
View File
@@ -8,9 +8,8 @@ use std::collections::{HashMap, HashSet};
use cadcraft_doc::{Drawing, Entity, EntityKind};
use serde_json::{Value, json};
use super::super::bad;
use super::super::blocks::purge;
use super::super::*;
use crate::{Result, Session};
const CMD: &str = "usage";
@@ -281,9 +280,11 @@ impl Unused {
fn item_json(
c: Category,
name: &str,
(entities, block_entities): (usize, usize),
entities: usize,
block_entities: usize,
references: usize,
(used, empty): (bool, bool),
used: bool,
empty: bool,
current: Option<bool>,
unused: &Unused,
) -> Value {
@@ -313,13 +314,13 @@ fn items(scan: &Scan, unused: &Unused, d: &Drawing) -> Vec<Value> {
let (e, b) = scan.layers.get(&l.name.to_ascii_lowercase()).copied().unwrap_or_default();
let uses = e.saturating_add(b);
let cur = l.name.eq_ignore_ascii_case(&current_layer);
out.push(item_json(Category::Layer, &l.name, (e, b), 0, (uses > 0, uses == 0), Some(cur), unused));
out.push(item_json(Category::Layer, &l.name, e, b, 0, uses > 0, uses == 0, Some(cur), unused));
}
for b in d.blocks.values() {
let (e, n) = scan.blocks.get(&b.name.to_ascii_lowercase()).copied().unwrap_or_default();
// A definition is used when a reference to it is reachable, as PURGE counts it.
let used = scan.used_blocks.contains(&b.name.to_ascii_uppercase());
out.push(item_json(Category::Block, &b.name, (e, n), 0, (used, e.saturating_add(n) == 0), None, unused));
out.push(item_json(Category::Block, &b.name, e, n, 0, used, e.saturating_add(n) == 0, None, unused));
}
let current_style = d.header.str("TEXTSTYLE", "Standard");
for t in &d.text_styles {
@@ -327,7 +328,7 @@ fn items(scan: &Scan, unused: &Unused, d: &Drawing) -> Vec<Value> {
let r = scan.style_refs.get(&t.name.to_ascii_lowercase()).copied().unwrap_or(0);
let uses = e.saturating_add(b).saturating_add(r);
let cur = t.name.eq_ignore_ascii_case(&current_style);
out.push(item_json(Category::TextStyle, &t.name, (e, b), r, (uses > 0, uses == 0), Some(cur), unused));
out.push(item_json(Category::TextStyle, &t.name, e, b, r, uses > 0, uses == 0, Some(cur), unused));
}
let current_linetype = d.header.str("CELTYPE", "ByLayer");
for t in &d.linetypes {
@@ -335,7 +336,7 @@ fn items(scan: &Scan, unused: &Unused, d: &Drawing) -> Vec<Value> {
let r = scan.linetype_refs.get(&t.name.to_ascii_lowercase()).copied().unwrap_or(0);
let uses = e.saturating_add(b).saturating_add(r);
let cur = t.name.eq_ignore_ascii_case(&current_linetype);
out.push(item_json(Category::Linetype, &t.name, (e, b), r, (uses > 0, uses == 0), Some(cur), unused));
out.push(item_json(Category::Linetype, &t.name, e, b, r, uses > 0, uses == 0, Some(cur), unused));
}
out
}
+1 -2
View File
@@ -7,8 +7,7 @@ use cadcraft_doc::{Layer, Lineweight};
use serde_json::{Value, json};
use super::machines::SelectRun;
use super::{CommandSpec, bad, bool_or, ok, str_param, targets};
use super::*;
use crate::{Result, Session};
pub fn specs() -> Vec<CommandSpec> {
-1
View File
@@ -10,7 +10,6 @@ use serde_json::{Value, json};
use super::file::{base64_encode, io};
use super::*;
use crate::{Accept, Input, Interactive, Prompt, Result, Session, Step};
use viewports::MviewM;
+1 -2
View File
@@ -8,8 +8,7 @@
use cadcraft_doc::{Drawing, PlotStyle, PlotStyleKind, PlotStyleTable};
use serde_json::{Value, json};
use super::super::targets;
use super::{CommandSpec, Result, Session, bad, str_param};
use super::*;
pub fn specs() -> Vec<CommandSpec> {
vec![
@@ -80,7 +80,8 @@ fn same_polyline(
s: &mut Session,
h: Handle,
pl: &LwPolyline,
(p1, p2): (Vec2, Vec2),
p1: Vec2,
p2: Vec2,
r: f64,
chamfer: Option<ChamferDistances>,
trim: bool,
@@ -222,7 +223,7 @@ pub(crate) fn fillet_pline(
_ => None,
};
let made = match (&e1.kind, &e2.kind) {
(EntityKind::LwPolyline(pl), _) if h1 == h2 => same_polyline(s, h1, pl, (p1, p2), r, chamfer, trim)?,
(EntityKind::LwPolyline(pl), _) if h1 == h2 => same_polyline(s, h1, pl, p1, p2, r, chamfer, trim)?,
(EntityKind::LwPolyline(pl), k) if as_line(k).is_some() => {
let ln = as_line(k).ok_or_else(|| other("Select a line."))?;
polyline_and_line(s, (h1, pl, p1), (h2, ln, p2), true, r, chamfer, trim)?
+1 -2
View File
@@ -13,8 +13,7 @@ use cadcraft_geom::{TAU, Vec2, Vec3, ccw_sweep, norm_angle};
use serde_json::{Value, json};
use super::machines::{SelOutcome, SelectPhase, number};
use super::{CommandSpec, bad, targets};
use super::*;
use crate::{Accept, Input, Interactive, Prompt, Result, Session, Step};
pub fn specs() -> Vec<CommandSpec> {
+1 -2
View File
@@ -14,8 +14,7 @@ use cadcraft_doc::{DevicePaper, Orientation, PageSetup, PlotOutput, PlotterConfi
use cadcraft_geom::{Bounds2, Vec2};
use serde_json::{Value, json};
use super::{CommandSpec, Result, always, bad, has_doc, point_req, str_param};
use super::*;
use crate::{Accept, EngineError, Input, Interactive, Prompt, Session, Step};
/// Where plotter configuration files live. Names are file names ending in
+3 -3
View File
@@ -10,8 +10,7 @@ use cadcraft_doc::{EntityKind, Lineweight, Transparency};
use cadcraft_geom::Vec3;
use serde_json::{Value, json};
use super::{CommandSpec, always, bad, bool_or, f64_req, find_command, ok, point_req, point_value, str_param, targets};
use super::*;
use crate::{Accept, Input, Interactive, Prompt, Result, Session, Step};
pub fn specs() -> Vec<CommandSpec> {
@@ -1178,9 +1177,10 @@ fn run_rename(s: &mut Session, p: &Value) -> Result<Value> {
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
use crate::Session;
#[test]
fn properties_set_rejects_unloaded_linetype() {
let mut s = Session::new();
+1 -2
View File
@@ -7,8 +7,7 @@ use serde_json::{Value, json};
use super::super::helpers::support_text;
use super::super::qselect::wildcard;
use super::{Result, Session, bad, bool_or, str_param};
use super::*;
use crate::{Accept, Input, Interactive, Prompt, Step};
/// Names no file may define: they are not linetype definitions.
+1 -2
View File
@@ -6,8 +6,7 @@
use cadcraft_doc::{Entity, EntityKind, Handle};
use serde_json::{Value, json};
use super::super::{bad, targets};
use super::super::*;
use crate::{Accept, Input, Interactive, Prompt, Result, Session, Step};
/// Which properties MATCHPROP copies (its Settings).
+3 -3
View File
@@ -5,8 +5,7 @@ use cadcraft_color::Color;
use cadcraft_doc::{Drawing, Entity, Handle};
use serde_json::{Value, json};
use super::{CommandSpec, bad, str_param};
use super::*;
use crate::{Result, Session};
pub fn specs() -> Vec<CommandSpec> {
@@ -321,9 +320,10 @@ fn run_info(s: &mut Session, p: &Value) -> Result<Value> {
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
use super::*;
fn session() -> Session {
let mut s = Session::new();
s.execute("layer.new", &json!({ "name": "Walls" })).unwrap();
+1 -2
View File
@@ -9,8 +9,7 @@ use cadcraft_doc::{Drawing, Entity, EntityKind, Handle};
use serde_json::{Value, json};
use super::machines::{SelOutcome, SelectPhase, SelectRun};
use super::{CommandSpec, bad, targets};
use super::*;
use crate::{Accept, Input, Interactive, Prompt, Result, Session, Step};
/// Most objects one call looks at (a hostile drawing can be huge).
+1 -2
View File
@@ -2,8 +2,7 @@
use serde_json::{Value, json};
use super::{CommandSpec, always, bad, point_param, str_param};
use super::*;
use crate::{Accept, Input, Interactive, Prompt, Result, Session, Step};
pub(super) mod iso;
+1 -2
View File
@@ -12,8 +12,7 @@ use cadcraft_geom::Vec2;
use serde_json::{Value, json};
use super::helpers::v3;
use super::{CommandSpec, bad, point_req, size_param, str_param};
use super::*;
use crate::{Accept, Input, Interactive, Prompt, Result, Session, Step};
/// Caps for hostile parameters.
+1 -2
View File
@@ -5,8 +5,7 @@ use cadcraft_geom::{PolyVertex, Vec2};
use serde_json::{Value, json};
use super::helpers::{line, lwpoly, v3};
use super::{CommandSpec, bad, bool_or, f64_or, point_req, str_param};
use super::*;
use crate::{Accept, Input, Interactive, Prompt, Result, Session, Step};
pub fn specs() -> Vec<CommandSpec> {
+1 -2
View File
@@ -4,8 +4,7 @@ use cadcraft_doc::{EntityKind, Handle};
use cadcraft_geom::{Segment, Vec2};
use serde_json::{Value, json};
use super::{CommandSpec, always, bad, bool_or, point_param, points_param, str_param, targets};
use super::*;
use crate::{Accept, Input, Interactive, Prompt, Result, Session, Step};
pub fn specs() -> Vec<CommandSpec> {
+1 -2
View File
@@ -6,8 +6,7 @@ use cadcraft_geom::{Bounds2, Vec2};
use serde_json::{Value, json};
use super::machines::{SelectRun, number};
use super::{CommandSpec, bad, bool_or, f64_or, f64_req, ok, point_param, point_req, str_param, targets};
use super::*;
use crate::{Accept, Input, Interactive, Prompt, Result, Session, Step, View};
mod named_views;
+326 -396
View File
@@ -249,130 +249,6 @@ fn entity(kind: &str, tags: &[Tag]) -> Option<(Common, EntityKind)> {
}
/// Parse one entity while charging materialized table cells to the caller's shared import budget.
/// An ARC, with its angles run the other way in a mirrored OCS.
fn arc(c: &Common, t: &T) -> Arc {
let mut a = Arc { center: ocs(c, t.p(10)), radius: t.fd(40, 1.0).abs(), start: t.fd(50, 0.0).to_radians(), end: t.fd(51, 360.0).to_radians() };
if c.extrusion.z < 0.0 {
// Mirrored OCS: angles run the other way.
let (s, e) = (std::f64::consts::PI - a.end, std::f64::consts::PI - a.start);
a.start = cadcraft_geom::norm_angle(s);
a.end = cadcraft_geom::norm_angle(e);
}
a
}
fn ellipse(c: &Common, t: &T) -> Ellipse {
let mut e = Ellipse {
center: t.p(10),
major: t.p(11),
ratio: t.fd(40, 1.0).clamp(1e-9, 1.0),
start: t.fd(41, 0.0),
end: t.fd(42, std::f64::consts::TAU),
};
if c.extrusion.z < 0.0 && (cadcraft_geom::ccw_sweep(e.start, e.end) - std::f64::consts::TAU).abs() > 1e-9 {
// Centre and major axis are WCS, but the minor axis is extrusion × major: with (0,0,-1)
// it points the other way, so parameter t lands where -t does in the XY plane. A full
// ellipse is symmetric and stays as it is.
let (s, e2) = (-e.end, -e.start);
e.start = cadcraft_geom::norm_angle(s);
e.end = cadcraft_geom::norm_angle(e2);
}
e
}
/// A LWPOLYLINE, or the region CADCraft wrote as one.
fn lwpolyline(tags: &[Tag], c: &Common, t: &T) -> Option<EntityKind> {
let mut verts = Vec::new();
let mut cur: Option<PolyVertex> = None;
for tg in tags {
match tg.code {
10 => {
if let Some(v) = cur.take() {
verts.push(v);
}
cur = Some(PolyVertex { p: Vec2::new(tg.f64(), 0.0), ..Default::default() });
}
20 => {
if let Some(v) = cur.as_mut() {
v.p.y = tg.f64();
}
}
40 => {
if let Some(v) = cur.as_mut() {
v.start_width = tg.f64();
}
}
41 => {
if let Some(v) = cur.as_mut() {
v.end_width = tg.f64();
}
}
42 => {
if let Some(v) = cur.as_mut() {
v.bulge = tg.f64();
}
}
_ => {}
}
}
verts.extend(cur);
let flags = t.i(70).unwrap_or(0);
if c.extrusion.z < 0.0 {
for v in &mut verts {
v.p.x = -v.p.x;
v.bulge = -v.bulge;
}
}
let pl = LwPolyline { vertices: verts, closed: flags & 1 != 0, const_width: t.fd(43, 0.0), elevation: t.fd(38, 0.0), plinegen: flags & 128 != 0 };
// A region CADCraft wrote as polylines (`dxf_region`).
crate::dxf_region::from_polyline(tags, pl)
}
fn spline(t: &T) -> Spline {
let degree = t.i(71).unwrap_or(3).clamp(1, 10) as usize;
let knots = t.all_f(40);
let control = t.pts(10);
let weights = t.all_f(41);
let fit = t.pts(11);
let tangent = |c: i32| t.f(c).map(|_| t.p(c).xy());
let fit_opts =
cadcraft_geom::FitOptions { knots: Default::default(), start_tangent: tangent(12), end_tangent: tangent(13), tolerance: t.fd(44, 0.0) }
.sanitized();
let closed = t.i(70).unwrap_or(0) & 1 != 0;
let mut sp = Spline { degree, knots, control, weights, fit: fit.clone(), closed, fit_opts };
if !sp.is_valid() && fit.len() >= 2 {
sp = Spline::fit_with(&fit, sp.closed, fit_opts);
} else if !sp.is_valid() && sp.control.len() >= 2 {
sp = Spline::from_control(sp.control.clone(), degree);
} else if fit.len() >= 3 {
// DXF keeps the knots, not how they were spaced: recover it for later refits.
sp.fit_opts.knots = sp.infer_knot_param();
}
sp
}
/// An ATTDEF, whose multiline text is embedded MTEXT after its own groups.
fn attdef(tags: &[Tag], c: &Common) -> Attrib {
// A multiline definition has its text as embedded MTEXT after its own groups.
let (own, embedded) = split_embedded(tags);
let multiline = multiline_attrib(embedded, c);
let own = T(own);
let mut text = text_from(&own);
if let Some(m) = &multiline {
text.value.clone_from(&m.read_as);
}
Attrib {
tag: own.s(2).unwrap_or_default(),
text,
invisible: own.i(70).unwrap_or(0) & 1 != 0,
constant: own.i(70).unwrap_or(0) & 2 != 0,
prompt: own.s(3).unwrap_or_default(),
props: Default::default(),
multiline,
modes: attrib_modes(own.0),
}
}
fn entity_with_table_budget(kind: &str, tags: &[Tag], table_cells_left: &mut usize) -> Option<(Common, EntityKind)> {
// The paper-space viewport our writer adds (dxf_write::paper_view) stays implicit.
if kind == "VIEWPORT" && crate::dxf_ext::is_paper_view(tags) {
@@ -384,16 +260,138 @@ fn entity_with_table_budget(kind: &str, tags: &[Tag], table_cells_left: &mut usi
"LINE" => EntityKind::Line(Line { a: t.p(10), b: t.p(11) }),
"POINT" => EntityKind::Point(Point { p: t.p(10), angle: t.fd(50, 0.0).to_radians() }),
"CIRCLE" => EntityKind::Circle(Circle { center: ocs(&c, t.p(10)), radius: t.fd(40, 1.0).abs() }),
"ARC" => EntityKind::Arc(arc(&c, &t)),
"ELLIPSE" => EntityKind::Ellipse(ellipse(&c, &t)),
"LWPOLYLINE" => lwpolyline(tags, &c, &t)?,
"SPLINE" => EntityKind::Spline(spline(&t)),
"ARC" => {
let mut a =
Arc { center: ocs(&c, t.p(10)), radius: t.fd(40, 1.0).abs(), start: t.fd(50, 0.0).to_radians(), end: t.fd(51, 360.0).to_radians() };
if c.extrusion.z < 0.0 {
// Mirrored OCS: angles run the other way.
let (s, e) = (std::f64::consts::PI - a.end, std::f64::consts::PI - a.start);
a.start = cadcraft_geom::norm_angle(s);
a.end = cadcraft_geom::norm_angle(e);
}
EntityKind::Arc(a)
}
"ELLIPSE" => {
let mut e = Ellipse {
center: t.p(10),
major: t.p(11),
ratio: t.fd(40, 1.0).clamp(1e-9, 1.0),
start: t.fd(41, 0.0),
end: t.fd(42, std::f64::consts::TAU),
};
if c.extrusion.z < 0.0 && (cadcraft_geom::ccw_sweep(e.start, e.end) - std::f64::consts::TAU).abs() > 1e-9 {
// Centre and major axis are WCS, but the minor axis is extrusion × major: with (0,0,-1)
// it points the other way, so parameter t lands where -t does in the XY plane. A full
// ellipse is symmetric and stays as it is.
let (s, e2) = (-e.end, -e.start);
e.start = cadcraft_geom::norm_angle(s);
e.end = cadcraft_geom::norm_angle(e2);
}
EntityKind::Ellipse(e)
}
"LWPOLYLINE" => {
let mut verts = Vec::new();
let mut cur: Option<PolyVertex> = None;
for tg in tags {
match tg.code {
10 => {
if let Some(v) = cur.take() {
verts.push(v);
}
cur = Some(PolyVertex { p: Vec2::new(tg.f64(), 0.0), ..Default::default() });
}
20 => {
if let Some(v) = cur.as_mut() {
v.p.y = tg.f64();
}
}
40 => {
if let Some(v) = cur.as_mut() {
v.start_width = tg.f64();
}
}
41 => {
if let Some(v) = cur.as_mut() {
v.end_width = tg.f64();
}
}
42 => {
if let Some(v) = cur.as_mut() {
v.bulge = tg.f64();
}
}
_ => {}
}
}
verts.extend(cur);
let flags = t.i(70).unwrap_or(0);
if c.extrusion.z < 0.0 {
for v in &mut verts {
v.p.x = -v.p.x;
v.bulge = -v.bulge;
}
}
let pl = LwPolyline {
vertices: verts,
closed: flags & 1 != 0,
const_width: t.fd(43, 0.0),
elevation: t.fd(38, 0.0),
plinegen: flags & 128 != 0,
};
// A region CADCraft wrote as polylines (`dxf_region`).
crate::dxf_region::from_polyline(tags, pl)?
}
"SPLINE" => {
let degree = t.i(71).unwrap_or(3).clamp(1, 10) as usize;
let knots = t.all_f(40);
let control = t.pts(10);
let weights = t.all_f(41);
let fit = t.pts(11);
let tangent = |c: i32| t.f(c).map(|_| t.p(c).xy());
let fit_opts = cadcraft_geom::FitOptions {
knots: Default::default(),
start_tangent: tangent(12),
end_tangent: tangent(13),
tolerance: t.fd(44, 0.0),
}
.sanitized();
let closed = t.i(70).unwrap_or(0) & 1 != 0;
let mut sp = Spline { degree, knots, control, weights, fit: fit.clone(), closed, fit_opts };
if !sp.is_valid() && fit.len() >= 2 {
sp = Spline::fit_with(&fit, sp.closed, fit_opts);
} else if !sp.is_valid() && sp.control.len() >= 2 {
sp = Spline::from_control(sp.control.clone(), degree);
} else if fit.len() >= 3 {
// DXF keeps the knots, not how they were spaced: recover it for later refits.
sp.fit_opts.knots = sp.infer_knot_param();
}
EntityKind::Spline(sp)
}
"HELIX" => EntityKind::Helix(helix(tags)),
"RAY" => EntityKind::Ray(RayLine { base: t.p(10), dir: t.p(11) }),
"XLINE" => EntityKind::XLine(RayLine { base: t.p(10), dir: t.p(11) }),
"TEXT" => EntityKind::Text(text_from(&t)),
"MTEXT" => EntityKind::MText(mtext_from(tags, &c)),
"ATTDEF" => EntityKind::AttDef(attdef(tags, &c)),
"ATTDEF" => {
// A multiline definition has its text as embedded MTEXT after its own groups.
let (own, embedded) = split_embedded(tags);
let multiline = multiline_attrib(embedded, &c);
let own = T(own);
let mut text = text_from(&own);
if let Some(m) = &multiline {
text.value.clone_from(&m.read_as);
}
EntityKind::AttDef(Attrib {
tag: own.s(2).unwrap_or_default(),
text,
invisible: own.i(70).unwrap_or(0) & 1 != 0,
constant: own.i(70).unwrap_or(0) & 2 != 0,
prompt: own.s(3).unwrap_or_default(),
props: Default::default(),
multiline,
modes: attrib_modes(own.0),
})
}
"INSERT" => {
// A mirrored block reference comes with extrusion (0,0,-1): its insertion point, scale,
// rotation and column spacing are in that OCS, where x runs the other way. In the WCS that
@@ -1111,91 +1109,6 @@ fn entity_extras(kind: &str, tags: &[Tag], h: Handle, k: &mut EntityKind, rx: &m
/// Parse a run of entity records (ENTITIES section or a block's body) into entities. Records that
/// can't be placed are counted in `skipped`.
/// The entity of a POLYLINE, with its mesh faces (advancing `i` past its VERTEX records). `None`
/// for a mesh without faces.
fn polyline_entity(recs: &[(String, Vec<Tag>)], i: &mut usize, t: &T) -> Option<(Common, EntityKind, Vec<EntityKind>)> {
let flags = t.i(70).unwrap_or(0);
let mut pts: Vec<(Vec3, f64, f64, f64)> = Vec::new();
// Polyface face records (VERTEX 70 bit 128 without 64): vertex indices 71–74.
let mut faces: Vec<[i64; 4]> = Vec::new();
while let Some((k2, t2)) = recs.get(*i) {
if k2 == "VERTEX" {
let tt = T(t2);
let vflags = tt.i(70).unwrap_or(0);
if vflags & 128 != 0 && vflags & 64 == 0 {
faces.push([71, 72, 73, 74].map(|c| tt.i(c).unwrap_or(0)));
} else if vflags & 16 == 0 {
pts.push((tt.p(10), tt.fd(42, 0.0), tt.fd(40, 0.0), tt.fd(41, 0.0)));
}
*i += 1;
} else {
if k2 == "SEQEND" {
*i += 1;
}
break;
}
}
let c = common(t);
let mut extra: Vec<EntityKind> = Vec::new();
let k = if flags & (16 | 64) != 0 {
// Polyface (64) and polygon (16) meshes become 3D faces; the first takes the
// polyline's place.
let verts: Vec<Vec3> = pts.iter().map(|p| p.0).collect();
let mut fs = if flags & 64 != 0 { polyface_faces(&verts, &faces) } else { mesh_faces(&verts, t, flags) }.into_iter().map(EntityKind::Face3d);
let first = fs.next()?;
extra.extend(fs);
first
} else if flags & 8 != 0 {
EntityKind::Polyline3d(Polyline3d { points: pts.iter().map(|p| p.0).collect(), closed: flags & 1 != 0 })
} else {
// A 2D polyline's vertices are in its OCS: mirrored like LWPOLYLINE.
let m = if c.extrusion.z < 0.0 { -1.0 } else { 1.0 };
EntityKind::LwPolyline(LwPolyline {
vertices: pts.iter().map(|p| PolyVertex { p: Vec2::new(m * p.0.x, p.0.y), bulge: m * p.1, start_width: p.2, end_width: p.3 }).collect(),
closed: flags & 1 != 0,
const_width: t.fd(40, 0.0).min(t.fd(41, 0.0)).max(0.0),
elevation: t.p(10).z,
plinegen: flags & 128 != 0,
})
};
Some((c, k, extra))
}
/// The ATTRIB records after an INSERT, advancing `i` past them and their SEQEND.
fn insert_attribs(recs: &[(String, Vec<Tag>)], i: &mut usize, ins_layer: &str) -> Vec<Attrib> {
let mut attribs = Vec::new();
while let Some((k2, t2)) = recs.get(*i) {
if k2 == "ATTRIB" {
let tt = T(t2);
// A multiline attribute has its text as embedded MTEXT after its own groups.
let (own, embedded) = split_embedded(t2);
let multiline = multiline_attrib(embedded, &common(&tt));
let own = T(own);
let mut text = text_from(&own);
if let Some(m) = &multiline {
text.value.clone_from(&m.read_as);
}
attribs.push(Attrib {
tag: own.s(2).unwrap_or_default(),
text,
invisible: own.i(70).unwrap_or(0) & 1 != 0,
constant: false,
prompt: String::new(),
props: attrib_props(&tt, ins_layer),
multiline,
modes: attrib_modes(own.0),
});
*i += 1;
} else {
if k2 == "SEQEND" {
*i += 1;
}
break;
}
}
attribs
}
fn parse_entities(
recs: &[(String, Vec<Tag>)],
d: &mut Drawing,
@@ -1221,17 +1134,92 @@ fn parse_entities(
i += 1;
match kind.as_str() {
"POLYLINE" => {
let Some((c, k, more)) = polyline_entity(recs, &mut i, &t) else {
*skipped = skipped.saturating_add(1);
continue;
let flags = t.i(70).unwrap_or(0);
let mut pts: Vec<(Vec3, f64, f64, f64)> = Vec::new();
// Polyface face records (VERTEX 70 bit 128 without 64): vertex indices 71–74.
let mut faces: Vec<[i64; 4]> = Vec::new();
while let Some((k2, t2)) = recs.get(i) {
if k2 == "VERTEX" {
let tt = T(t2);
let vflags = tt.i(70).unwrap_or(0);
if vflags & 128 != 0 && vflags & 64 == 0 {
faces.push([71, 72, 73, 74].map(|c| tt.i(c).unwrap_or(0)));
} else if vflags & 16 == 0 {
pts.push((tt.p(10), tt.fd(42, 0.0), tt.fd(40, 0.0), tt.fd(41, 0.0)));
}
i += 1;
} else {
if k2 == "SEQEND" {
i += 1;
}
break;
}
}
let c = common(&t);
let k = if flags & (16 | 64) != 0 {
// Polyface (64) and polygon (16) meshes become 3D faces; the first takes the
// polyline's place.
let verts: Vec<Vec3> = pts.iter().map(|p| p.0).collect();
let mut fs = if flags & 64 != 0 { polyface_faces(&verts, &faces) } else { mesh_faces(&verts, &t, flags) }
.into_iter()
.map(EntityKind::Face3d);
let Some(first) = fs.next() else {
*skipped = skipped.saturating_add(1);
continue;
};
extra.extend(fs);
first
} else if flags & 8 != 0 {
EntityKind::Polyline3d(Polyline3d { points: pts.iter().map(|p| p.0).collect(), closed: flags & 1 != 0 })
} else {
// A 2D polyline's vertices are in its OCS: mirrored like LWPOLYLINE.
let m = if c.extrusion.z < 0.0 { -1.0 } else { 1.0 };
EntityKind::LwPolyline(LwPolyline {
vertices: pts
.iter()
.map(|p| PolyVertex { p: Vec2::new(m * p.0.x, p.0.y), bulge: m * p.1, start_width: p.2, end_width: p.3 })
.collect(),
closed: flags & 1 != 0,
const_width: t.fd(40, 0.0).min(t.fd(41, 0.0)).max(0.0),
elevation: t.p(10).z,
plinegen: flags & 128 != 0,
})
};
extra = more;
kindent = Some((c, k));
}
"INSERT" => {
if t.i(66) == Some(1) {
let mut attribs = Vec::new();
let ins_layer = kindent.as_ref().map(|(c, _)| c.layer.clone()).unwrap_or_default();
let attribs = insert_attribs(recs, &mut i, &ins_layer);
while let Some((k2, t2)) = recs.get(i) {
if k2 == "ATTRIB" {
let tt = T(t2);
// A multiline attribute has its text as embedded MTEXT after its own groups.
let (own, embedded) = split_embedded(t2);
let multiline = multiline_attrib(embedded, &common(&tt));
let own = T(own);
let mut text = text_from(&own);
if let Some(m) = &multiline {
text.value.clone_from(&m.read_as);
}
attribs.push(Attrib {
tag: own.s(2).unwrap_or_default(),
text,
invisible: own.i(70).unwrap_or(0) & 1 != 0,
constant: false,
prompt: String::new(),
props: attrib_props(&tt, &ins_layer),
multiline,
modes: attrib_modes(own.0),
});
i += 1;
} else {
if k2 == "SEQEND" {
i += 1;
}
break;
}
}
if let Some((_, EntityKind::Insert(ins))) = kindent.as_mut() {
ins.attribs = attribs;
}
@@ -1700,13 +1688,6 @@ pub fn read(bytes: &[u8]) -> Result<Drawing> {
read_budgeted(bytes, 4_000_000)
}
/// A block definition read from BLOCKS: name, base point, description, entities (owner, paper
/// flag, entity) and block flags.
type BlockDef = (String, Vec3, String, Vec<(Option<String>, bool, Entity)>, i64);
/// A layout read from OBJECTS: name, tab order, paper block handle and page setup.
type LayoutDef = (String, u32, String, PageSetup);
/// Import DXF sections while sharing one materialized-table-cell budget across blocks and entity sections.
fn read_budgeted(bytes: &[u8], mut table_cells_left: usize) -> Result<Drawing> {
let tags = cadcraft_dxf::parse(bytes).map_err(|e| IoError::Format(e.to_string()))?;
@@ -1719,8 +1700,8 @@ fn read_budgeted(bytes: &[u8], mut table_cells_left: usize) -> Result<Drawing> {
// A file without $INSUNITS declares no units (#1209); only new drawings take the template's.
d.header.set_i64("INSUNITS", 0);
let mut rx = Rx { pstyles: plot_style_names(&secs), ..Rx::default() };
let mut blocks: Vec<BlockDef> = Vec::new();
let mut layout_objs: Vec<LayoutDef> = Vec::new();
let mut blocks: Vec<(String, Vec3, String, Vec<(Option<String>, bool, Entity)>, i64)> = Vec::new();
let mut layout_objs: Vec<(String, u32, String, PageSetup)> = Vec::new();
let mut entities = Vec::new();
let mut objs = Objects::default();
// Every handle another program's file already uses is taken before anything gets a new one:
@@ -1740,109 +1721,73 @@ fn read_budgeted(bytes: &[u8], mut table_cells_left: usize) -> Result<Drawing> {
match s.name.as_str() {
"HEADER" => header(&s.tags, &mut d),
"TABLES" => tables(&s.tags, &mut d, &mut rx),
"BLOCKS" => blocks.extend(read_blocks(&s.tags, &mut d, &mut rx, &mut table_cells_left, &mut skipped)),
"BLOCKS" => {
let recs = records(&s.tags);
for (k, tg) in &recs {
let t = T(tg);
if k == "BLOCK" && t.i(70).unwrap_or(0) & 4 != 0 {
rx.xref_paths.insert(t.s(2).unwrap_or_default().to_ascii_uppercase(), t.s(1).unwrap_or_default());
}
}
let mut n = 0;
let mut i = 0;
while let Some((k, tg)) = recs.get(i) {
if k == "BLOCK" {
let t = T(tg);
let name = t.s(2).unwrap_or_default();
let base = t.p(10);
let description = t.s(4).unwrap_or_default();
// Block flags: 4 xref, 8 overlay.
let xref = t.i(70).unwrap_or(0) & 12;
let mut j = i + 1;
while recs.get(j).is_some_and(|(k, _)| k != "ENDBLK") {
j += 1;
}
let body = recs.get(i + 1..j).unwrap_or(&[]);
let ents = parse_entities(body, &mut d, &mut rx, &mut n, &mut table_cells_left);
blocks.push((name, base, description, ents, xref));
i = j + 1;
} else {
// A record outside any BLOCK ... ENDBLK pair.
if k != "ENDBLK" {
n = n.saturating_add(1);
}
i += 1;
}
}
count_skipped(&mut skipped, "BLOCKS", n);
}
// A damaged file may split its entities over several ENTITIES sections (#865).
"ENTITIES" => {
let mut n = 0;
entities.extend(parse_entities(&records(&s.tags), &mut d, &mut rx, &mut n, &mut table_cells_left));
count_skipped(&mut skipped, "ENTITIES", n);
}
"OBJECTS" => read_objects(&s.tags, &mut objs, &mut layout_objs),
"OBJECTS" => {
for (k, tg) in records(&s.tags) {
objs.add(&k, &tg);
if k == "LAYOUT" {
// Fields after the AcDbLayout marker (AcDbPlotSettings also has a code 1).
let start = tg.iter().position(|x| x.code == 100 && x.str() == "AcDbLayout").unwrap_or(0);
let tail = tg.get(start..).unwrap_or(&[]);
let t = T(tail);
// LAYOUT has two 330s: owner dict and the block record; take the last.
let br = tg.iter().rfind(|x| x.code == 330).map(Tag::str).unwrap_or_default();
let plot = T(tg.get(..start).unwrap_or(&[]));
layout_objs.push((t.s(1).unwrap_or_default(), t.i(71).unwrap_or(0) as u32, br.to_ascii_uppercase(), page_setup(&plot)));
}
}
}
_ => {}
}
}
// A file CADCraft wrote names its own next handle (#495): its table records and objects
// are numbered above it and rebuilt on save, so following $HANDSEED would drift every save.
let own_seed = objs.xrecord_text(crate::dxf_ext::HANDSEED_KEY).and_then(|t| Handle::parse_hex(t.trim()));
let (paper_block_for, first_layout) = setup_layouts(&mut d, &mut layout_objs, &rx);
place_blocks(&mut d, blocks, &mut rx, &paper_block_for, &first_layout);
place_entities(&mut d, entities, &rx, &paper_block_for, &first_layout);
apply_sortents(&mut d, &objs.sortents, &rx.brs, &paper_block_for, &first_layout);
objs.apply(&mut d, &rx);
crate::dxf_image::resolve(&secs, &mut d);
crate::dxf_mline::resolve(&secs, &mut d);
// Keep handles unique against the header's seed.
let seed = d.header.str("HANDSEED", "");
if let Some(h) = own_seed.or_else(|| Handle::parse_hex(&seed)) {
d.bump_handseed(Handle(h.0.saturating_sub(1)));
}
if d.layer("0").is_none() {
d.layers.insert(0, Layer::default());
}
crate::dxf_raw::read(&secs, &mut d);
d.skipped_records = skipped;
Ok(d)
}
/// Read the BLOCKS section: the xref paths of external blocks, then each block's entities.
fn read_blocks(
tags: &[Tag],
d: &mut Drawing,
rx: &mut Rx,
table_cells_left: &mut usize,
skipped: &mut std::collections::BTreeMap<String, usize>,
) -> Vec<BlockDef> {
let recs = records(tags);
for (k, tg) in &recs {
let t = T(tg);
if k == "BLOCK" && t.i(70).unwrap_or(0) & 4 != 0 {
rx.xref_paths.insert(t.s(2).unwrap_or_default().to_ascii_uppercase(), t.s(1).unwrap_or_default());
}
}
let mut blocks = Vec::new();
let mut n = 0;
let mut i = 0;
while let Some((k, tg)) = recs.get(i) {
if k == "BLOCK" {
let t = T(tg);
let name = t.s(2).unwrap_or_default();
let base = t.p(10);
let description = t.s(4).unwrap_or_default();
// Block flags: 4 xref, 8 overlay.
let xref = t.i(70).unwrap_or(0) & 12;
let mut j = i + 1;
while recs.get(j).is_some_and(|(k, _)| k != "ENDBLK") {
j += 1;
}
let body = recs.get(i + 1..j).unwrap_or(&[]);
let ents = parse_entities(body, d, rx, &mut n, table_cells_left);
blocks.push((name, base, description, ents, xref));
i = j + 1;
} else {
// A record outside any BLOCK ... ENDBLK pair.
if k != "ENDBLK" {
n = n.saturating_add(1);
}
i += 1;
}
}
count_skipped(skipped, "BLOCKS", n);
blocks
}
/// Read the OBJECTS section into `objs`, and each LAYOUT into `layout_objs`.
fn read_objects(tags: &[Tag], objs: &mut Objects, layout_objs: &mut Vec<LayoutDef>) {
for (k, tg) in records(tags) {
objs.add(&k, &tg);
if k == "LAYOUT" {
// Fields after the AcDbLayout marker (AcDbPlotSettings also has a code 1).
let start = tg.iter().position(|x| x.code == 100 && x.str() == "AcDbLayout").unwrap_or(0);
let tail = tg.get(start..).unwrap_or(&[]);
let t = T(tail);
// LAYOUT has two 330s: owner dict and the block record; take the last.
let br = tg.iter().rfind(|x| x.code == 330).map(Tag::str).unwrap_or_default();
let plot = T(tg.get(..start).unwrap_or(&[]));
layout_objs.push((t.s(1).unwrap_or_default(), t.i(71).unwrap_or(0) as u32, br.to_ascii_uppercase(), page_setup(&plot)));
}
}
}
/// Add the drawing's layouts, and return the layout each paper-space block belongs to, and the
/// layout that takes paper-space entities without an owner.
fn setup_layouts(d: &mut Drawing, layout_objs: &mut [LayoutDef], rx: &Rx) -> (HashMap<String, String>, String) {
// Layouts.
let mut paper_block_for: HashMap<String, String> = HashMap::new();
layout_objs.sort_by_key(|l| l.1);
for (name, order, br, page) in layout_objs.iter() {
for (name, order, br, page) in &layout_objs {
if name.eq_ignore_ascii_case("Model") {
continue;
}
@@ -1861,12 +1806,7 @@ fn setup_layouts(d: &mut Drawing, layout_objs: &mut [LayoutDef], rx: &Rx) -> (Ha
let first_tab = d.layouts.iter().min_by_key(|l| l.tab_order).map(|l| l.name.clone());
d.active_layout = active.clone().filter(|a| Some(a) != first_tab.as_ref());
let first_layout = active.or_else(|| d.layouts.first().map(|l| l.name.clone())).unwrap_or_else(|| "Layout1".into());
(paper_block_for, first_layout)
}
/// Place the BLOCKS entities: model and paper space blocks go to their space, the rest become
/// block definitions.
fn place_blocks(d: &mut Drawing, blocks: Vec<BlockDef>, rx: &mut Rx, paper_block_for: &HashMap<String, String>, first_layout: &str) {
// Blocks: model/paper space blocks hold entities in newer files.
for (name, base, description, ents, xref) in blocks {
let up = name.to_ascii_uppercase();
if up == "*MODEL_SPACE" {
@@ -1877,7 +1817,7 @@ fn place_blocks(d: &mut Drawing, blocks: Vec<BlockDef>, rx: &mut Rx, paper_block
continue;
}
if up.starts_with("*PAPER_SPACE") {
let lname = paper_block_for.get(&up).cloned().unwrap_or_else(|| first_layout.to_string());
let lname = paper_block_for.get(&up).cloned().unwrap_or_else(|| first_layout.clone());
if let Some(l) = d.layouts.iter_mut().find(|l| l.name == lname) {
for (_, _, e) in ents {
l.entities.push(e);
@@ -1917,22 +1857,13 @@ fn place_blocks(d: &mut Drawing, blocks: Vec<BlockDef>, rx: &mut Rx, paper_block
}
d.blocks.insert(name, std::sync::Arc::new(b));
}
}
/// Place the ENTITIES section: model space, or paper space when flagged (67=1).
fn place_entities(
d: &mut Drawing,
entities: Vec<(Option<String>, bool, Entity)>,
rx: &Rx,
paper_block_for: &HashMap<String, String>,
first_layout: &str,
) {
// ENTITIES section: model space, or paper space when flagged (67=1).
for (owner, paper, e) in entities {
d.ensure_layer(&e.common.layer);
let owner_name = owner.and_then(|o| rx.brs.get(&o.trim().to_ascii_uppercase()).cloned()).map(|n| n.to_ascii_uppercase());
let to_paper = paper || owner_name.as_deref().is_some_and(|n| n.starts_with("*PAPER_SPACE"));
if to_paper {
let lname = owner_name.and_then(|n| paper_block_for.get(&n).cloned()).unwrap_or_else(|| first_layout.to_string());
let lname = owner_name.and_then(|n| paper_block_for.get(&n).cloned()).unwrap_or_else(|| first_layout.clone());
if let Some(l) = d.layouts.iter_mut().find(|l| l.name == lname) {
l.entities.push(e);
}
@@ -1940,6 +1871,21 @@ fn place_entities(
d.model.push(e);
}
}
apply_sortents(&mut d, &objs.sortents, &rx.brs, &paper_block_for, &first_layout);
objs.apply(&mut d, &rx);
crate::dxf_image::resolve(&secs, &mut d);
crate::dxf_mline::resolve(&secs, &mut d);
// Keep handles unique against the header's seed.
let seed = d.header.str("HANDSEED", "");
if let Some(h) = own_seed.or_else(|| Handle::parse_hex(&seed)) {
d.bump_handseed(Handle(h.0.saturating_sub(1)));
}
if d.layer("0").is_none() {
d.layers.insert(0, Layer::default());
}
crate::dxf_raw::read(&secs, &mut d);
d.skipped_records = skipped;
Ok(d)
}
fn count_skipped(skipped: &mut std::collections::BTreeMap<String, usize>, section: &str, n: usize) {
@@ -2108,7 +2054,13 @@ impl Objects {
Some(text)
}
fn apply_table_styles(&self, d: &mut Drawing) {
fn apply(self, d: &mut Drawing, rx: &Rx) {
if let Some(v) = self.wipeout_frame {
d.header.set_i64("WIPEOUTFRAME", v);
}
if let Some(style) = self.xrecord_text(crate::dxf_ext::MLEADER_STYLE_KEY).filter(|s| !s.trim().is_empty()) {
d.header.set_str("CMLEADERSTYLE", &style);
}
// Table styles (named by their ACAD_TABLESTYLE dictionary entries).
for (h, tags) in &self.table_styles {
let Some(name) = self.names.get(h).filter(|n| !n.is_empty()) else { continue };
@@ -2118,9 +2070,6 @@ impl Objects {
None => d.table_styles.push(st),
}
}
}
fn apply_table_style_fixes(&self, d: &mut Drawing, rx: &Rx) {
for (h, sh) in &rx.table_style_fix {
let name = self.names.get(sh).cloned();
// A table from another writer has no scale of its own: its text height is its
@@ -2141,61 +2090,6 @@ impl Objects {
}
});
}
}
fn apply_mleader_styles(&self, d: &mut Drawing, rx: &Rx) {
// Multileader styles (named by their ACAD_MLEADERSTYLE dictionary entries).
for (h, tags) in &self.mleader_styles {
let Some(name) = self.names.get(h).filter(|n| !n.is_empty()) else { continue };
let t = T(tags);
let def = MLeaderStyle::default();
let num = |code: i32, d: f64, min: f64| t.f(code).filter(|v| v.is_finite() && *v >= min).unwrap_or(d);
let text_style = t.s(342).and_then(|sh| rx.styles.get(&sh.trim().to_ascii_uppercase()).cloned()).filter(|n| !n.is_empty());
let st = MLeaderStyle {
name: name.clone(),
arrow_size: num(44, def.arrow_size, 0.0),
text_height: t.f(45).filter(|v| v.is_finite() && *v > 0.0).unwrap_or(def.text_height),
landing_gap: num(42, def.landing_gap, 0.0),
dogleg: num(43, def.dogleg, 0.0),
text_style: text_style.unwrap_or(def.text_style),
annotative: t.i(296).unwrap_or(0) != 0,
};
match d.mleader_styles.iter_mut().find(|s| s.name.eq_ignore_ascii_case(name)) {
Some(x) => *x = st,
None => d.mleader_styles.push(st),
}
}
}
fn apply_groups(&self, d: &mut Drawing) {
// Groups (named by their ACAD_GROUP dictionary entries); members that exist.
for (h, tags) in &self.groups {
let Some(name) = self.names.get(h).filter(|n| !n.is_empty()) else { continue };
let t = T(tags);
let members: Vec<Handle> = tags
.iter()
.filter(|x| x.code == 340)
.filter_map(|x| Handle::parse_hex(&x.str()))
.filter(|m| d.entity(*m).is_some())
.take(MAX_OBJECTS)
.collect();
let g = Group { name: name.clone(), description: t.s(300).unwrap_or_default(), selectable: t.i(71).unwrap_or(1) != 0, members };
match d.groups.iter_mut().find(|x| x.name.eq_ignore_ascii_case(name)) {
Some(x) => *x = g,
None => d.groups.push(g),
}
}
}
fn apply(self, d: &mut Drawing, rx: &Rx) {
if let Some(v) = self.wipeout_frame {
d.header.set_i64("WIPEOUTFRAME", v);
}
if let Some(style) = self.xrecord_text(crate::dxf_ext::MLEADER_STYLE_KEY).filter(|s| !s.trim().is_empty()) {
d.header.set_str("CMLEADERSTYLE", &style);
}
self.apply_table_styles(d);
self.apply_table_style_fixes(d, rx);
// Parametric constraints and parameters.
if let Some((c, p)) = self.xrecord_text(crate::dxf_ext::CONSTRAINTS_KEY).and_then(|t| crate::dxf_ext::parse_constraints(&t)) {
d.constraints = c;
@@ -2219,14 +2113,50 @@ impl Objects {
if let Some(tables) = self.xrecord_text(crate::dxf_ext::PLOT_STYLES_KEY).and_then(|t| crate::dxf_ext::parse_plot_styles(&t)) {
d.plot_style_tables = tables;
}
self.apply_mleader_styles(d, rx);
// Multileader styles (named by their ACAD_MLEADERSTYLE dictionary entries).
for (h, tags) in &self.mleader_styles {
let Some(name) = self.names.get(h).filter(|n| !n.is_empty()) else { continue };
let t = T(tags);
let def = MLeaderStyle::default();
let num = |code: i32, d: f64, min: f64| t.f(code).filter(|v| v.is_finite() && *v >= min).unwrap_or(d);
let text_style = t.s(342).and_then(|sh| rx.styles.get(&sh.trim().to_ascii_uppercase()).cloned()).filter(|n| !n.is_empty());
let st = MLeaderStyle {
name: name.clone(),
arrow_size: num(44, def.arrow_size, 0.0),
text_height: t.f(45).filter(|v| v.is_finite() && *v > 0.0).unwrap_or(def.text_height),
landing_gap: num(42, def.landing_gap, 0.0),
dogleg: num(43, def.dogleg, 0.0),
text_style: text_style.unwrap_or(def.text_style),
annotative: t.i(296).unwrap_or(0) != 0,
};
match d.mleader_styles.iter_mut().find(|s| s.name.eq_ignore_ascii_case(name)) {
Some(x) => *x = st,
None => d.mleader_styles.push(st),
}
}
crate::dxf_mleader::apply(d, &rx.mleader_fix, &self.names);
for (name, view) in &self.layout_views {
if let Some(l) = d.layouts.iter_mut().find(|l| l.name == *name) {
l.view = Some(*view);
}
}
self.apply_groups(d);
// Groups (named by their ACAD_GROUP dictionary entries); members that exist.
for (h, tags) in &self.groups {
let Some(name) = self.names.get(h).filter(|n| !n.is_empty()) else { continue };
let t = T(tags);
let members: Vec<Handle> = tags
.iter()
.filter(|x| x.code == 340)
.filter_map(|x| Handle::parse_hex(&x.str()))
.filter(|m| d.entity(*m).is_some())
.take(MAX_OBJECTS)
.collect();
let g = Group { name: name.clone(), description: t.s(300).unwrap_or_default(), selectable: t.i(71).unwrap_or(1) != 0, members };
match d.groups.iter_mut().find(|x| x.name.eq_ignore_ascii_case(name)) {
Some(x) => *x = g,
None => d.groups.push(g),
}
}
// Standard associativity, for dimensions without CADCraft's exact links.
for tags in &self.dimassocs {
dimassoc(d, tags);
+11 -11
View File
@@ -2373,7 +2373,7 @@ fn tags_for(d: &Drawing, version: Version, target: Version) -> Vec<Tag> {
// ---------------- BLOCKS ----------------
w.s(0, "SECTION");
w.s(2, "BLOCKS");
let block = |w: &mut W, (name, desc): (&str, &str), brh: &str, base: Vec3, flags: i64, ents: &mut dyn Iterator<Item = &Entity>, paper: bool| {
let block = |w: &mut W, name: &str, desc: &str, brh: &str, base: Vec3, flags: i64, ents: &mut dyn Iterator<Item = &Entity>, paper: bool| {
let bh = w.h();
w.s(0, "BLOCK");
w.s(5, bh);
@@ -2406,16 +2406,16 @@ fn tags_for(d: &Drawing, version: Version, target: Version) -> Vec<Tag> {
w.s(8, "0");
w.s(100, "AcDbBlockEnd");
};
block(&mut w, ("*Model_Space", ""), &ms_br, Vec3::ZERO, 0, &mut std::iter::empty(), false);
block(&mut w, "*Model_Space", "", &ms_br, Vec3::ZERO, 0, &mut std::iter::empty(), false);
for (i, (n, h, l)) in ps_brs.iter().enumerate() {
if i == 0 {
block(&mut w, (n, ""), h, Vec3::ZERO, 0, &mut std::iter::empty(), true);
block(&mut w, n, "", h, Vec3::ZERO, 0, &mut std::iter::empty(), true);
} else {
block(&mut w, (n, ""), h, Vec3::ZERO, 0, &mut sheet_first(l).into_iter(), true);
block(&mut w, n, "", h, Vec3::ZERO, 0, &mut sheet_first(l).into_iter(), true);
}
}
for (n, h, b) in &user_blocks {
block(&mut w, (n, &b.description), h, b.base, if b.anonymous { 1 } else { 0 }, &mut b.entities.iter().map(|e| e.as_ref()), false);
block(&mut w, n, &b.description, h, b.base, if b.anonymous { 1 } else { 0 }, &mut b.entities.iter().map(|e| e.as_ref()), false);
}
// Generated geometry entities (dimension, arrowhead and table blocks) need handles.
for (_, _, ents) in dim_defs.iter_mut().chain(arrow_defs.iter_mut()).chain(table_defs.iter_mut()) {
@@ -2425,13 +2425,13 @@ fn tags_for(d: &Drawing, version: Version, target: Version) -> Vec<Tag> {
}
}
for (n, h, ents) in &dim_defs {
block(&mut w, (n, ""), h, Vec3::ZERO, 1, &mut ents.iter(), false);
block(&mut w, n, "", h, Vec3::ZERO, 1, &mut ents.iter(), false);
}
for (n, h, ents) in &arrow_defs {
block(&mut w, (n, ""), h, Vec3::ZERO, 0, &mut ents.iter(), false);
block(&mut w, n, "", h, Vec3::ZERO, 0, &mut ents.iter(), false);
}
for (n, h, ents) in &table_defs {
block(&mut w, (n, ""), h, Vec3::ZERO, 1, &mut ents.iter(), false);
block(&mut w, n, "", h, Vec3::ZERO, 1, &mut ents.iter(), false);
}
w.s(0, "ENDSEC");
@@ -2612,7 +2612,7 @@ fn tags_for(d: &Drawing, version: Version, target: Version) -> Vec<Tag> {
w.s(3, &l.name);
w.s(350, layout_handles.get(i).cloned().unwrap_or_default());
}
let layout_obj = |w: &mut W, h: &str, (name, tab): (&str, i64), brh: &str, flags: i64, page: &PageSetup, view: Option<(Vec2, f64)>| {
let layout_obj = |w: &mut W, h: &str, name: &str, tab: i64, brh: &str, flags: i64, page: &PageSetup, view: Option<(Vec2, f64)>| {
// Plot scale: a standard scale when fitting or 1:1, otherwise custom (142 paper units
// per 143 drawing units).
let scale = if page.scale.is_finite() && page.scale > 0.0 { page.scale } else { 1.0 };
@@ -2697,10 +2697,10 @@ fn tags_for(d: &Drawing, version: Version, target: Version) -> Vec<Tag> {
// plot type 5, "Layout information" (DXF Reference, PLOTSETTINGS group 74), which means
// nothing for model space: another program's Plot dialog then shows no plot area or scale.
let model_page = PageSetup { plot_area: "extents".into(), scale_to_fit: true, ..PageSetup::default() };
layout_obj(&mut w, &model_layout, ("Model", 0), &ms_br, 1, &model_page, None);
layout_obj(&mut w, &model_layout, "Model", 0, &ms_br, 1, &model_page, None);
for (i, (_, brh, l)) in ps_brs.iter().enumerate() {
let h = layout_handles.get(i).cloned().unwrap_or_default();
layout_obj(&mut w, &h, (&l.name, i64::from(l.tab_order.max(1))), brh, 1, &l.page, l.view);
layout_obj(&mut w, &h, &l.name, i64::from(l.tab_order.max(1)), brh, 1, &l.page, l.view);
}
// Raster image definitions.
w.t.extend(crate::dxf_image::objects(&cx.images, &root_dict));
+84 -110
View File
@@ -17,7 +17,7 @@ use std::fmt::Write as _;
use cadcraft_color::Rgb;
use cadcraft_doc::{Drawing, Handle, PageSetup, Space};
use cadcraft_geom::{Bounds2, Vec2};
use cadcraft_render::{DPrim, Kind, Sheet, clip, paper};
use cadcraft_render::{Kind, Sheet, clip, paper};
use serde_json::Value;
use craft_pdf::write::backend as kr;
@@ -808,7 +808,7 @@ fn krilla_failure(e: KrillaError) -> Failure {
/// as text instead: the glyph at font size 1 under its glyph matrix (the matrix axes are one
/// em).
fn marks(s: &mut Surface<'_>, p: &SheetPage, groups: &Groups, fonts: &mut fonts::Fonts) -> std::result::Result<(), Failure> {
let list = &p.list;
let (list, map) = (&p.list, &p.map);
let texts = fonts::texts(list);
let white = Rgb(255, 255, 255);
let mut batch: Option<(StrokeStyle, PathBuilder)> = None;
@@ -829,126 +829,100 @@ fn marks(s: &mut Surface<'_>, p: &SheetPage, groups: &Groups, fonts: &mut fonts:
let t = if prim.kind == Kind::Mask { 0 } else { prim.transparency.min(cadcraft_render::MAX_TRANSPARENCY) };
let w = f64::from(prim.lw) * PT_PER_MM;
let w = if w.is_finite() && w > 0.0 { w.min(100.0) } else { 0.0 };
let m = Mark { rgb, t, w };
match prim.kind {
Kind::Polyline | Kind::Infinite { .. } => polyline(s, &mut batch, p, prim, raw, m),
Kind::Polyline | Kind::Infinite { .. } => {
let pts: Vec<Vec2> = match prim.kind {
Kind::Infinite { ray } => match (raw.first(), raw.get(1)) {
(Some(base), Some(dir)) => {
let a = map.pt(*base);
let dir_pt = map.pt(*base + *dir) - a;
match clip::clip_infinite(a, dir_pt, ray, &p.clip_pt) {
Some((x, y)) => vec![x, y],
None => continue,
}
}
_ => continue,
},
_ => raw.iter().map(|q| map.pt(*q)).collect(),
};
if pts.len() < 2 {
continue;
}
// With a vertex out of range, keep the pieces inside the plot area, widened so
// a wide stroke just outside it still shows its edge.
let runs = if pts.iter().all(|q| coord(*q).is_some()) { vec![pts] } else { clip::clip_polyline(&pts, &p.clip_pt.expand(w + 1.0)) };
let style = StrokeStyle { rgb, width: w as f32, transparency: t };
if t > 0 || batch.as_ref().is_some_and(|(b, _)| *b != style) {
flush(s, &mut batch);
}
for run in runs {
let Some(pts) = run.iter().map(|q| coord(*q)).collect::<Option<Vec<_>>>() else { continue };
// The writer leaves out a path without extent, so a zero-length line is
// painted as the round-cap dot its stroke would make.
if let Some(&(x, y)) = pts.first()
&& pts.iter().all(|q| *q == (x, y))
{
flush(s, &mut batch);
let d = if w > 0.0 { w as f32 } else { HAIRLINE_DOT_PT };
if let Some(path) = circle_path(x, y, d / 2.0) {
fill_path(s, &path, rgb, t);
}
continue;
}
let (_, pb) = batch.get_or_insert_with(|| (style, PathBuilder::new()));
for (j, (x, y)) in pts.iter().enumerate() {
if j == 0 {
pb.move_to(*x, *y);
} else {
pb.line_to(*x, *y);
}
}
}
}
Kind::Tris | Kind::Mask => {
flush(s, &mut batch);
// A wipeout's mask is paper white.
let m = if prim.kind == Kind::Mask { Mark { rgb: white, ..m } } else { m };
let text_font =
u32::try_from(i).ok().and_then(|i| texts.get(&i).copied()).and_then(|text| fonts.font(&text.glyph.face).map(|font| (text, font)));
match text_font {
Some((text, font)) => glyph(s, font, text, &p.map, m.rgb, m.t),
None => fill_tris(s, p, raw, m.rgb, m.t),
let rgb = if prim.kind == Kind::Mask { white } else { rgb };
if let Some(text) = u32::try_from(i).ok().and_then(|i| texts.get(&i))
&& let Some(font) = fonts.font(&text.glyph.face)
{
glyph(s, font, text, map, rgb, t);
continue;
}
let mapped: Vec<Vec2> = raw.iter().map(|q| map.pt(*q)).collect();
// With a vertex out of range, keep the parts inside the plot area.
let tris = if mapped.iter().all(|q| coord(*q).is_some()) { mapped } else { clip::clip_triangles(&mapped, &p.clip_pt) };
let mut pb = PathBuilder::new();
for tri in tris.as_chunks::<3>().0 {
let Some(v) = tri.iter().map(|q| coord(*q)).collect::<Option<Vec<_>>>() else { continue };
for (j, (x, y)) in v.iter().enumerate() {
if j == 0 {
pb.move_to(*x, *y);
} else {
pb.line_to(*x, *y);
}
}
pb.close();
}
if let Some(path) = pb.finish() {
fill_path(s, &path, rgb, t);
}
}
Kind::Point => {
let Some(q) = raw.first() else { continue };
flush(s, &mut batch);
let half = (w.max(0.5)) / 2.0;
let c = map.pt(*q);
if let Some(path) = rect_path(c - Vec2::new(half, half), c + Vec2::new(half, half)) {
fill_path(s, &path, rgb, t);
}
}
Kind::Point => point(s, &mut batch, &p.map, raw, m),
}
}
flush(s, &mut batch);
layer_changed(s, layer, None)
}
/// How a primitive is painted: colour, transparency (percent) and lineweight (points).
#[derive(Clone, Copy)]
struct Mark {
rgb: Rgb,
t: u8,
w: f64,
}
/// Stroke a polyline or infinite line, clipped to the plot area, into the open batch.
fn polyline(s: &mut Surface<'_>, batch: &mut Option<(StrokeStyle, PathBuilder)>, p: &SheetPage, prim: &DPrim, raw: &[Vec2], m: Mark) {
let Some(pts) = polyline_points(prim, raw, &p.map, &p.clip_pt) else { return };
if pts.len() < 2 {
return;
}
// With a vertex out of range, keep the pieces inside the plot area, widened so
// a wide stroke just outside it still shows its edge.
let runs = if pts.iter().all(|q| coord(*q).is_some()) { vec![pts] } else { clip::clip_polyline(&pts, &p.clip_pt.expand(m.w + 1.0)) };
let style = StrokeStyle { rgb: m.rgb, width: m.w as f32, transparency: m.t };
if m.t > 0 || batch.as_ref().is_some_and(|(b, _)| *b != style) {
flush(s, batch);
}
for run in runs {
stroke_run(s, batch, &run, style, m);
}
}
/// The page points of a polyline, or of an infinite line clipped to the plot area; `None` when
/// there is nothing to draw.
fn polyline_points(prim: &DPrim, raw: &[Vec2], map: &Map, clip_pt: &Bounds2) -> Option<Vec<Vec2>> {
match prim.kind {
Kind::Infinite { ray } => {
let (Some(base), Some(dir)) = (raw.first(), raw.get(1)) else { return None };
let a = map.pt(*base);
let dir_pt = map.pt(*base + *dir) - a;
let (x, y) = clip::clip_infinite(a, dir_pt, ray, clip_pt)?;
Some(vec![x, y])
}
_ => Some(raw.iter().map(|q| map.pt(*q)).collect()),
}
}
/// Add one run of a polyline to the batch, or paint it as a dot when it has no extent.
fn stroke_run(s: &mut Surface<'_>, batch: &mut Option<(StrokeStyle, PathBuilder)>, run: &[Vec2], style: StrokeStyle, m: Mark) {
let Some(pts) = run.iter().map(|q| coord(*q)).collect::<Option<Vec<_>>>() else { return };
// The writer leaves out a path without extent, so a zero-length line is
// painted as the round-cap dot its stroke would make.
if let Some(&(x, y)) = pts.first()
&& pts.iter().all(|q| *q == (x, y))
{
flush(s, batch);
let d = if m.w > 0.0 { m.w as f32 } else { HAIRLINE_DOT_PT };
if let Some(path) = circle_path(x, y, d / 2.0) {
fill_path(s, &path, m.rgb, m.t);
}
return;
}
let (_, pb) = batch.get_or_insert_with(|| (style, PathBuilder::new()));
for (j, (x, y)) in pts.iter().enumerate() {
if j == 0 {
pb.move_to(*x, *y);
} else {
pb.line_to(*x, *y);
}
}
}
/// Fill a set of triangles, clipped to the plot area.
fn fill_tris(s: &mut Surface<'_>, p: &SheetPage, raw: &[Vec2], rgb: Rgb, t: u8) {
let mapped: Vec<Vec2> = raw.iter().map(|q| p.map.pt(*q)).collect();
// With a vertex out of range, keep the parts inside the plot area.
let tris = if mapped.iter().all(|q| coord(*q).is_some()) { mapped } else { clip::clip_triangles(&mapped, &p.clip_pt) };
let mut pb = PathBuilder::new();
for tri in tris.as_chunks::<3>().0 {
let Some(v) = tri.iter().map(|q| coord(*q)).collect::<Option<Vec<_>>>() else { continue };
for (j, (x, y)) in v.iter().enumerate() {
if j == 0 {
pb.move_to(*x, *y);
} else {
pb.line_to(*x, *y);
}
}
pb.close();
}
if let Some(path) = pb.finish() {
fill_path(s, &path, rgb, t);
}
}
/// Paint a point marker: a small square at the point.
fn point(s: &mut Surface<'_>, batch: &mut Option<(StrokeStyle, PathBuilder)>, map: &Map, raw: &[Vec2], m: Mark) {
let Some(q) = raw.first() else { return };
flush(s, batch);
let half = (m.w.max(0.5)) / 2.0;
let c = map.pt(*q);
if let Some(path) = rect_path(c - Vec2::new(half, half), c + Vec2::new(half, half)) {
fill_path(s, &path, m.rgb, m.t);
}
}
/// Write one glyph as text in its embedded font: font size 1, placed by the glyph's matrix (one
/// em along each axis). A glyph whose matrix is out of range or collapses is left out.
fn glyph(s: &mut Surface<'_>, font: kr::text::Font, t: &cadcraft_render::PlotText, map: &Map, rgb: Rgb, transparency: u8) {
+14 -14
View File
@@ -165,7 +165,7 @@ fn style(d: &mut Drawing, name: &str, font: &str) {
d.text_styles.push(TextStyle { name: name.into(), font: font.into(), ..TextStyle::default() });
}
fn text(d: &mut Drawing, space: &Space, style: &str, at: (f64, f64), value: &str, (rotation, width_factor, oblique): (f64, f64, f64)) {
fn text(d: &mut Drawing, space: &Space, style: &str, at: (f64, f64), value: &str, rotation: f64, width_factor: f64, oblique: f64) {
let t = Text {
insert: Vec3::new(at.0, at.1, 0.0),
align_pt: None,
@@ -225,7 +225,7 @@ fn font_files(doc: &Doc) -> usize {
#[test]
fn truetype_text_is_embedded_as_text() {
let mut d = drawing();
text(&mut d, &Space::Model, "Real", (0.0, 0.0), "ABBA", (0.0, 1.0, 0.0));
text(&mut d, &Space::Model, "Real", (0.0, 0.0), "ABBA", 0.0, 1.0, 0.0);
let doc = pdf(&d, &Space::Model, json!({}));
let page = &doc.pages[0];
// A Type0 font over a CIDFontType2 subset with a ToUnicode map.
@@ -260,7 +260,7 @@ fn truetype_text_is_embedded_as_text() {
#[test]
fn the_subset_is_a_valid_truetype_font() {
let mut d = drawing();
text(&mut d, &Space::Model, "Real", (0.0, 0.0), "A", (0.0, 1.0, 0.0));
text(&mut d, &Space::Model, "Real", (0.0, 0.0), "A", 0.0, 1.0, 0.0);
let doc = pdf(&d, &Space::Model, json!({}));
let file = doc.pages[0].fonts[0].font_file.clone().expect("an embedded font file");
let face = cadcraft_fonts::ttf::Face::new(std::sync::Arc::new(file.clone()), 0).expect("the subset parses");
@@ -273,9 +273,9 @@ fn the_subset_is_a_valid_truetype_font() {
fn rotation_width_factor_and_obliquing_go_into_the_text_matrix() {
let mut d = drawing();
let quarter = std::f64::consts::FRAC_PI_2;
text(&mut d, &Space::Model, "Real", (0.0, 0.0), "A", (quarter, 1.0, 0.0));
text(&mut d, &Space::Model, "Real", (0.0, 50.0), "A", (0.0, 2.0, 0.0));
text(&mut d, &Space::Model, "Real", (0.0, 100.0), "A", (0.0, 1.0, 15f64.to_radians()));
text(&mut d, &Space::Model, "Real", (0.0, 0.0), "A", quarter, 1.0, 0.0);
text(&mut d, &Space::Model, "Real", (0.0, 50.0), "A", 0.0, 2.0, 0.0);
text(&mut d, &Space::Model, "Real", (0.0, 100.0), "A", 0.0, 1.0, 15f64.to_radians());
let m = matrices(&pdf(&d, &Space::Model, json!({})));
assert_eq!(m.len(), 3, "{m:?}");
// Rotated a quarter turn: the baseline points up, "up" points left.
@@ -294,7 +294,7 @@ fn rotation_width_factor_and_obliquing_go_into_the_text_matrix() {
#[test]
fn a_font_the_writer_cannot_subset_plots_as_outlines() {
let mut d = drawing();
text(&mut d, &Space::Model, "Short", (0.0, 0.0), "AB", (0.0, 1.0, 0.0));
text(&mut d, &Space::Model, "Short", (0.0, 0.0), "AB", 0.0, 1.0, 0.0);
let p = pdf(&d, &Space::Model, json!({}));
assert!(font_files(&p) == 0 && pdf_read::count(p.content(), "BT") == 0 && fills(&p) > 0, "outlines: {}", p.content());
}
@@ -302,16 +302,16 @@ fn a_font_the_writer_cannot_subset_plots_as_outlines() {
#[test]
fn fonts_that_forbid_embedding_and_the_geometry_option_draw_outlines() {
let mut d = drawing();
text(&mut d, &Space::Model, "Locked", (0.0, 0.0), "AB", (0.0, 1.0, 0.0));
text(&mut d, &Space::Model, "Locked", (0.0, 0.0), "AB", 0.0, 1.0, 0.0);
let p = pdf(&d, &Space::Model, json!({}));
assert!(font_files(&p) == 0 && pdf_read::count(p.content(), "BT") == 0 && fills(&p) > 0, "restricted licence: outlines");
let mut d = drawing();
text(&mut d, &Space::Model, "Real", (0.0, 0.0), "AB", (0.0, 1.0, 0.0));
text(&mut d, &Space::Model, "Real", (0.0, 0.0), "AB", 0.0, 1.0, 0.0);
let p = pdf(&d, &Space::Model, json!({"text": "geometry"}));
assert!(font_files(&p) == 0 && pdf_read::count(p.content(), "BT") == 0 && fills(&p) > 0, "text as geometry");
// The stroke font is always geometry.
let mut d = drawing();
text(&mut d, &Space::Model, "Standard", (0.0, 0.0), "AB", (0.0, 1.0, 0.0));
text(&mut d, &Space::Model, "Standard", (0.0, 0.0), "AB", 0.0, 1.0, 0.0);
let p = pdf(&d, &Space::Model, json!({}));
assert!(pdf_read::count(p.content(), "BT") == 0 && pdf_read::count(p.content(), "S") > 0);
}
@@ -321,8 +321,8 @@ fn text_seen_through_a_viewport_and_on_layers() {
let mut d = drawing();
let paper = Space::Paper("Layout1".into());
// Model: one word well inside the viewport, one cut by its right edge.
text(&mut d, &Space::Model, "Real", (0.0, 0.0), "AB", (0.0, 1.0, 0.0));
text(&mut d, &Space::Model, "Real", (88.0, 0.0), "BA", (0.0, 1.0, 0.0));
text(&mut d, &Space::Model, "Real", (0.0, 0.0), "AB", 0.0, 1.0, 0.0);
text(&mut d, &Space::Model, "Real", (88.0, 0.0), "BA", 0.0, 1.0, 0.0);
let vp = Viewport {
center: Vec3::new(150.0, 100.0, 0.0),
width: 100.0,
@@ -337,7 +337,7 @@ fn text_seen_through_a_viewport_and_on_layers() {
on: true,
};
d.add(&paper, Common::default(), EntityKind::Viewport(vp)).unwrap();
text(&mut d, &paper, "Real", (20.0, 20.0), "B", (0.0, 1.0, 0.0));
text(&mut d, &paper, "Real", (20.0, 20.0), "B", 0.0, 1.0, 0.0);
let p = pdf(&d, &paper, json!({}));
// "AB" inside, the paper-space "B", and the glyph of "BA" left of the edge: real text. The
// glyph the edge cuts stays a (clipped) outline.
@@ -353,7 +353,7 @@ fn text_seen_through_a_viewport_and_on_layers() {
#[test]
fn text_sits_in_its_layers_marked_content() {
let mut d = drawing();
text(&mut d, &Space::Model, "Real", (0.0, 0.0), "AB", (0.0, 1.0, 0.0));
text(&mut d, &Space::Model, "Real", (0.0, 0.0), "AB", 0.0, 1.0, 0.0);
let p = pdf(&d, &Space::Model, json!({}));
let ops = pdf_read::ops(p.content());
let first_bt = ops.iter().position(|o| o.op == "BT").unwrap();
+2 -2
View File
@@ -528,14 +528,14 @@ impl Family<'_> {
if solid_line {
out.push(vec![self.to_world(Vec2::new(x0, y)), self.to_world(Vec2::new(x1, y))]);
} else {
self.dash((x0, x1), y, offset, pat_len, out, work);
self.dash(x0, x1, y, offset, pat_len, out, work);
}
}
}
}
/// The dashes and dots of the span `x0..x1` of the line at `y`, phase anchored at x = `offset`.
fn dash(&self, (x0, x1): (f64, f64), y: f64, offset: f64, pat_len: f64, out: &mut Vec<Vec<Vec2>>, work: &mut u64) {
fn dash(&self, x0: f64, x1: f64, y: f64, offset: f64, pat_len: f64, out: &mut Vec<Vec<Vec2>>, work: &mut u64) {
let start_rep = ((x0 - offset) / pat_len).floor();
let mut x = offset + start_rep * pat_len;
let mut guard = 0;