From f81480587b66bbbc7cb6f9a083684670d45fb07b Mon Sep 17 00:00:00 2001 From: TQ Hirsch Date: Thu, 20 Mar 2025 08:04:47 +0100 Subject: [PATCH] Initial commit --- .appveyor.yml | 11 +++ .github/dependabot.yml | 8 ++ .gitignore | 6 ++ .travis.yml | 69 ++++++++++++++ Cargo.toml | 34 +++++++ LICENSE_APACHE | 201 +++++++++++++++++++++++++++++++++++++++++ LICENSE_MIT | 25 +++++ README.md | 4 + rust-toolchain.toml | 4 + src/datamatrix.rs | 176 ++++++++++++++++++++++++++++++++++++ src/lib.rs | 14 +++ src/size_spec.rs | 183 +++++++++++++++++++++++++++++++++++++ src/utils.rs | 11 +++ tests/web.rs | 13 +++ 14 files changed, 759 insertions(+) create mode 100644 .appveyor.yml create mode 100644 .github/dependabot.yml create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 Cargo.toml create mode 100644 LICENSE_APACHE create mode 100644 LICENSE_MIT create mode 100644 README.md create mode 100644 rust-toolchain.toml create mode 100644 src/datamatrix.rs create mode 100644 src/lib.rs create mode 100644 src/size_spec.rs create mode 100644 src/utils.rs create mode 100644 tests/web.rs diff --git a/.appveyor.yml b/.appveyor.yml new file mode 100644 index 0000000..50910bd --- /dev/null +++ b/.appveyor.yml @@ -0,0 +1,11 @@ +install: + - appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe + - if not defined RUSTFLAGS rustup-init.exe -y --default-host x86_64-pc-windows-msvc --default-toolchain nightly + - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin + - rustc -V + - cargo -V + +build: false + +test_script: + - cargo test --locked diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..7377d37 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: +- package-ecosystem: cargo + directory: "/" + schedule: + interval: daily + time: "08:00" + open-pull-requests-limit: 10 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4e30131 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/target +**/*.rs.bk +Cargo.lock +bin/ +pkg/ +wasm-pack.log diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..7a91325 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,69 @@ +language: rust +sudo: false + +cache: cargo + +matrix: + include: + + # Builds with wasm-pack. + - rust: beta + env: RUST_BACKTRACE=1 + addons: + firefox: latest + chrome: stable + before_script: + - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update) + - (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate) + - cargo install-update -a + - curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -f + script: + - cargo generate --git . --name testing + # Having a broken Cargo.toml (in that it has curlies in fields) anywhere + # in any of our parent dirs is problematic. + - mv Cargo.toml Cargo.toml.tmpl + - cd testing + - wasm-pack build + - wasm-pack test --chrome --firefox --headless + + # Builds on nightly. + - rust: nightly + env: RUST_BACKTRACE=1 + before_script: + - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update) + - (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate) + - cargo install-update -a + - rustup target add wasm32-unknown-unknown + script: + - cargo generate --git . --name testing + - mv Cargo.toml Cargo.toml.tmpl + - cd testing + - cargo check + - cargo check --target wasm32-unknown-unknown + - cargo check --no-default-features + - cargo check --target wasm32-unknown-unknown --no-default-features + - cargo check --no-default-features --features console_error_panic_hook + - cargo check --target wasm32-unknown-unknown --no-default-features --features console_error_panic_hook + - cargo check --no-default-features --features "console_error_panic_hook wee_alloc" + - cargo check --target wasm32-unknown-unknown --no-default-features --features "console_error_panic_hook wee_alloc" + + # Builds on beta. + - rust: beta + env: RUST_BACKTRACE=1 + before_script: + - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update) + - (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate) + - cargo install-update -a + - rustup target add wasm32-unknown-unknown + script: + - cargo generate --git . --name testing + - mv Cargo.toml Cargo.toml.tmpl + - cd testing + - cargo check + - cargo check --target wasm32-unknown-unknown + - cargo check --no-default-features + - cargo check --target wasm32-unknown-unknown --no-default-features + - cargo check --no-default-features --features console_error_panic_hook + - cargo check --target wasm32-unknown-unknown --no-default-features --features console_error_panic_hook + # Note: no enabling the `wee_alloc` feature here because it requires + # nightly for now. diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..b3dbb60 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "dmtx" +version = "0.1.0" +authors = ["TQ Hirsch "] +edition = "2018" +description = "WASM-based Datamatrix encoder" +license = "MIT" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +default = ["console_error_panic_hook"] + +[dependencies] +wasm-bindgen = "0.2.84" +datamatrix = "0.3.1" +js-sys = "0.3.69" + +# The `console_error_panic_hook` crate provides better debugging of panics by +# logging them with `console.error`. This is great for development, but requires +# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for +# code size when deploying. +console_error_panic_hook = { version = "0.1.7", optional = true } +serde-wasm-bindgen = "0.6.5" +serde = { version = "1.0.204", features = ["derive"] } +flagset = "0.4.5" + +[dev-dependencies] +wasm-bindgen-test = "0.3.34" + +[profile.release] +# Tell `rustc` to optimize for small code size. +opt-level = "s" diff --git a/LICENSE_APACHE b/LICENSE_APACHE new file mode 100644 index 0000000..11069ed --- /dev/null +++ b/LICENSE_APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/LICENSE_MIT b/LICENSE_MIT new file mode 100644 index 0000000..fa3d38e --- /dev/null +++ b/LICENSE_MIT @@ -0,0 +1,25 @@ +Copyright (c) 2018 TQ Hirsch + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..1534d63 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +JavaScript/WASM bindings for the Rust [datamatrix] crate + +[datamatrix]: https://crates.io/crates/datamatrix + diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..8af56e0 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +# We need at least 1.80 for stable lazy_cell +channel = "beta" +targets = ["wasm32-unknown-unknown"] \ No newline at end of file diff --git a/src/datamatrix.rs b/src/datamatrix.rs new file mode 100644 index 0000000..198077b --- /dev/null +++ b/src/datamatrix.rs @@ -0,0 +1,176 @@ +use datamatrix::{DataMatrixBuilder, EncodationType}; +use datamatrix::data::DataEncodingError; +use datamatrix::placement::PathSegment; +use flagset::FlagSet; +use js_sys::{JsString, Uint8Array}; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen(typescript_custom_section)] +const TS_ENCODINGS: &'static str = r#" +/// Ugh, I hate this so much. But this is what it says in the spec... +type EncodationName = "ascii" | "c40" | "text" | "x12" | "edifact" | "base256"; +type Encodation = EncodationName | Array; + +interface CodingOptions { + size?: SymbolSizeSpec; + macros?: bool; + encodings?: Encodation; +} +"#; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "CodingOptions")] + pub type CodingOptions; + type Encodation; + + #[wasm_bindgen(method, structural, getter = size)] + fn co_size(this: &CodingOptions) -> Option; + + #[wasm_bindgen(method, structural, getter = macros)] + fn co_macros(this: &CodingOptions) -> Option; + + #[wasm_bindgen(method, structural, getter = encodings)] + fn co_encodings(this: &CodingOptions) -> Option; + + #[wasm_bindgen(typescript_type = "Uint8Array | string")] + pub type Buffer; +} + +enum DataSource { + String(String), + Bytes(Vec), +} +fn buffer_from_js(buf: Buffer) -> Result { + buf.dyn_into::() + .map(|array| DataSource::Bytes(array.to_vec())) + .or_else(|js| { + js.dyn_into::() + .and_then(|s| s.as_string().ok_or_else(|| JsValue::from(s).into())) + .map(DataSource::String) + }) + .or_else(|_| Err(JsError::new("Buffer must be a string or Uint8Array"))) +} + +fn encodation_type_from_js_string(s: JsValue) -> EncodationType { + let s = s + .as_string() + .unwrap_or_else(|| wasm_bindgen::throw_str("Invalid encodation type")); + let string = s.as_str(); + match string { + "ascii" => EncodationType::Ascii, + "c40" => EncodationType::C40, + "text" => EncodationType::Text, + "x12" => EncodationType::X12, + "edifact" => EncodationType::Edifact, + "base256" => EncodationType::Base256, + _ => wasm_bindgen::throw_str("Invalid encodation type"), + } +} + +fn encodation_type_from_js(e: Encodation) -> FlagSet { + e.dyn_into::() + .map(|s| encodation_type_from_js_string(s.into()).into()) + .or_else(|e| { + e.dyn_into::().map(|a| { + a.iter() + .map(|js| encodation_type_from_js_string(js)) + .fold(FlagSet::default(), |a, n| a | n) + }) + }) + .unwrap_or_else(|_| wasm_bindgen::throw_str("Invalid encodation type")) +} + +#[wasm_bindgen] +pub struct DataMatrix{ + dmtx: datamatrix::DataMatrix, + bitmap: datamatrix::placement::Bitmap, +} + +#[wasm_bindgen] +impl DataMatrix { + #[wasm_bindgen(constructor)] + pub fn encode(data: Buffer, options: Option) -> Result { + let mut builder = DataMatrixBuilder::new(); + if let Some(options) = options { + if let Some(size) = options.co_size() { + let size = crate::size_spec::symbolset_from_json(size); + builder = builder.with_symbol_list(size); + } + if let Some(encoding) = options.co_encodings() { + builder = builder.with_encodation_types(encodation_type_from_js(encoding)); + } + if let Some(macros) = options.co_macros() { + builder = builder.with_macros(macros); + } + } + + match buffer_from_js(data)? { + DataSource::Bytes(bytes) => builder.encode(bytes.as_slice()), + DataSource::String(str) => builder.encode_str(&str), + } + .map(|dmtx| { + let bitmap = dmtx.bitmap(); + DataMatrix{dmtx, bitmap} + }) + .map_err(|err| match err { + DataEncodingError::TooMuchOrIllegalData => "Too much data", + DataEncodingError::SymbolListEmpty => "No valid symbol size", + }) + .map_err(|err| JsError::new(err)) + .map_err(Into::into) + } + + /// Get the full set of codewords encoded into this barcode + pub fn codewords(&self) -> Box<[u8]> { + self.dmtx.codewords().to_vec().into_boxed_slice() + } + + /// Get the data part of the low-level encoding of the barcode + pub fn data_codewords(&self) -> Box<[u8]> { + self.dmtx.data_codewords().to_vec().into_boxed_slice() + } + + /// Draw the barcode via an SVG path. Note: this path expects even/odd fill + /// i.e., embed this in ` + pub fn svg_path(&self, x0: Option, y0: Option) -> String { + use std::fmt::Write; + let mut path: String = String::new(); + + let x0 = x0.unwrap_or(0); + let y0 = y0.unwrap_or(0); + + write!(path, "M{x0},{y0}").unwrap(); + for part in self.bitmap.path() { + match part { + PathSegment::Move(dx, dy) => write!(path, "m{dx},{dy}"), + PathSegment::Horizontal(h) => write!(path, "h{h}"), + PathSegment::Vertical(v) => write!(path, "v{v}"), + PathSegment::Close => write!(path, "z"), + }.unwrap(); + } + path + } + + /// Return an array of filled pixel coordinates. + /// The returned buffer is `[x0,y0,x1,y1,...]` + pub fn pixels(&self) -> Box<[u8]> { + let mut ret = Vec::new(); + for (x,y) in self.bitmap.pixels() { + if x > 255 || y > 255 { + panic!("Returned too high of a pixel number"); + } + ret.push(x as u8); + ret.push(y as u8); + } + ret.into_boxed_slice() + } + + pub fn width(&self) -> usize { + self.bitmap.width() + } + + pub fn height(&self) -> usize { + self.bitmap.height() + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..7cee8e0 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,14 @@ +mod utils; + +macro_rules! hash_map { + ($($($key:expr),+ => $value:expr),*$(,)?) => {{ + let mut res = HashMap::new(); + $($( + res.insert($key, $value); + )+)* + res + }}; +} + +mod size_spec; +mod datamatrix; \ No newline at end of file diff --git a/src/size_spec.rs b/src/size_spec.rs new file mode 100644 index 0000000..f38595a --- /dev/null +++ b/src/size_spec.rs @@ -0,0 +1,183 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::sync::LazyLock; +use datamatrix::{SymbolList, SymbolSize}; +use wasm_bindgen::throw_val; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen(typescript_custom_section)] +const SIZE_SPEC_TS: &'static str = r#" +interface SymbolSizeFilter { + square?: bool; + rectangle?: bool; + extended?: bool; + min_width?: number; + max_width?: number; + min_height?: number; + max_height?: number; +} + +type SymbolSize = "10" | "10x10" | "12" | "12x12" | "14" | "14x14" | "16" | + "16x16" | "18" | "18x18" | "20" | "20x20" | "22" | "22x22" | "24" | + "24x24" | "26" | "26x26" | "32" | "32x32" | "36" | "36x36" | "40" | + "40x40" | "44" | "44x44" | "48" | "48x48" | "52" | "52x52" | "64" | + "64x64" | "72" | "72x72" | "80" | "80x80" | "88" | "88x88" | "96" | + "96x96" | "104" | "104x104" | "120" | "120x120" | "132" | "132x132" | + "144" | "144x144" | "8x18" | "8x32" | "12x26" | "12x36" | "16x36" | + "16x48" | "8x48" | "8x64" | "8x80" | "8x96" | "8x120" | "8x144" | + "12x64" | "12x88" | "16x64" | "20x36" | "20x44" | "20x64" | "22x48" | + "24x48" | "24x64" | "26x40" | "26x48" | "26x64"; + +type SymbolSizeSpec = SymbolSize | SymbolSizeFilter | Array; +"#; +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "SymbolSizeSpec")] + pub type SizeSpec; +} +#[derive(Serialize, Deserialize)] +pub struct SizeFilter { + square: Option, + rectangle: Option, + extended: Option, + min_width: Option, + max_width: Option, + min_height: Option, + max_height: Option, +} + +static SYMBOL_SIZE_STRINGS: LazyLock> = LazyLock::new(|| { + hash_map! { + "10" , "10x10" => SymbolSize::Square10, + "12" , "12x12" => SymbolSize::Square12, + "14" , "14x14" => SymbolSize::Square14, + "16" , "16x16" => SymbolSize::Square16, + "18" , "18x18" => SymbolSize::Square18, + "20" , "20x20" => SymbolSize::Square20, + "22" , "22x22" => SymbolSize::Square22, + "24" , "24x24" => SymbolSize::Square24, + "26" , "26x26" => SymbolSize::Square26, + "32", "32x32" => SymbolSize::Square32, + "36", "36x36" => SymbolSize::Square36, + "40", "40x40" => SymbolSize::Square40, + "44", "44x44" => SymbolSize::Square44, + "48", "48x48" => SymbolSize::Square48, + "52", "52x52" => SymbolSize::Square52, + "64", "64x64" => SymbolSize::Square64, + "72", "72x72" => SymbolSize::Square72, + "80", "80x80" => SymbolSize::Square80, + "88", "88x88" => SymbolSize::Square88, + "96", "96x96" => SymbolSize::Square96, + "104", "104x104" => SymbolSize::Square104, + "120", "120x120" => SymbolSize::Square120, + "132", "132x132" => SymbolSize::Square132, + "144", "144x144" => SymbolSize::Square144, + "8x18" => SymbolSize::Rect8x18, + "8x32" => SymbolSize::Rect8x32, + "12x26" => SymbolSize::Rect12x26, + "12x36" => SymbolSize::Rect12x36, + "16x36" => SymbolSize::Rect16x36, + "16x48" => SymbolSize::Rect16x48, + "8x48" => SymbolSize::Rect8x48, + "8x64" => SymbolSize::Rect8x64, + "8x80" => SymbolSize::Rect8x80, + "8x96" => SymbolSize::Rect8x96, + "8x120" => SymbolSize::Rect8x120, + "8x144" => SymbolSize::Rect8x144, + "12x64" => SymbolSize::Rect12x64, + "12x88" => SymbolSize::Rect12x88, + "16x64" => SymbolSize::Rect16x64, + "20x36" => SymbolSize::Rect20x36, + "20x44" => SymbolSize::Rect20x44, + "20x64" => SymbolSize::Rect20x64, + "22x48" => SymbolSize::Rect22x48, + "24x48" => SymbolSize::Rect24x48, + "24x64" => SymbolSize::Rect24x64, + "26x40" => SymbolSize::Rect26x40, + "26x48" => SymbolSize::Rect26x48, + "26x64" => SymbolSize::Rect26x64, +} +}); + +fn symbolsize_from_string(name: &str) -> Option { + SYMBOL_SIZE_STRINGS.get(name).copied() +} + +fn symbolsize_from_int(name: u32) -> Option { + Some(match name { + 10 => SymbolSize::Square10, + 12 => SymbolSize::Square12, + 14 => SymbolSize::Square14, + 16 => SymbolSize::Square16, + 18 => SymbolSize::Square18, + 20 => SymbolSize::Square20, + 22 => SymbolSize::Square22, + 24 => SymbolSize::Square24, + 26 => SymbolSize::Square26, + 32 => SymbolSize::Square32, + 36 => SymbolSize::Square36, + 40 => SymbolSize::Square40, + 44 => SymbolSize::Square44, + 48 => SymbolSize::Square48, + 52 => SymbolSize::Square52, + 64 => SymbolSize::Square64, + 72 => SymbolSize::Square72, + 80 => SymbolSize::Square80, + 88 => SymbolSize::Square88, + 96 => SymbolSize::Square96, + 104 => SymbolSize::Square104, + 120 => SymbolSize::Square120, + 132 => SymbolSize::Square132, + 144 => SymbolSize::Square144, + _ => return None, + }) +} + +pub fn symbolset_from_json(json: SizeSpec) -> SymbolList { + if let Some(size) = json.as_string() { + if let Some(size) = symbolsize_from_string(&size) { + return size.into() + } + } else if let Some(size) = json.as_f64() { + if size as u32 as f64 == size { + if let Some(size) = symbolsize_from_int(size as u32) { + return size.into() + } + } + } else if let Some(arr) = json.dyn_ref::() { + let mut symset = SymbolList::with_whitelist(std::iter::empty()); + for item in arr.values() { + match item { + Err(err) => throw_val(err), + Ok(val) => symset.extend(symbolset_from_json(val.into())), + } + } + return symset; + } else if let Ok(value) = serde_wasm_bindgen::from_value::(json.into()) { + let mut symset = if value.extended.unwrap_or(false) { + SymbolList::with_extended_rectangles() + } else { + SymbolList::default() + }; + if !value.square.unwrap_or(true) { + symset = symset.enforce_rectangular() + } + if !value.rectangle.unwrap_or(true) { + symset = symset.enforce_square() + } + if let Some(v) = value.min_height { + symset = symset.enforce_height_in(v..); + } + if let Some(v) = value.max_height { + symset = symset.enforce_height_in(..v + 1); + } + if let Some(v) = value.min_width { + symset = symset.enforce_width_in(v..); + } + if let Some(v) = value.max_width { + symset = symset.enforce_width_in(..v + 1); + } + return symset; + } + wasm_bindgen::throw_str("Invalid size"); +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..7b369ce --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,11 @@ +#[allow(unused)] +pub fn set_panic_hook() { + // When the `console_error_panic_hook` feature is enabled, we can call the + // `set_panic_hook` function at least once during initialization, and then + // we will get better error messages if our code ever panics. + // + // For more details see + // https://github.com/rustwasm/console_error_panic_hook#readme + #[cfg(feature = "console_error_panic_hook")] + console_error_panic_hook::set_once(); +} diff --git a/tests/web.rs b/tests/web.rs new file mode 100644 index 0000000..de5c1da --- /dev/null +++ b/tests/web.rs @@ -0,0 +1,13 @@ +//! Test suite for the Web and headless browsers. + +#![cfg(target_arch = "wasm32")] + +extern crate wasm_bindgen_test; +use wasm_bindgen_test::*; + +wasm_bindgen_test_configure!(run_in_browser); + +#[wasm_bindgen_test] +fn pass() { + assert_eq!(1 + 1, 2); +}