├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── assets ├── banner.png ├── banner.svg ├── example.com.round.svg └── example.com.svg ├── benches ├── README.md ├── bench.py └── qr.rs ├── examples ├── custom.rs ├── embed.rs ├── image.rs ├── node.mjs ├── simple.rs ├── svg.rs └── wasm.html ├── pkg ├── .gitignore ├── LICENSE ├── README.md ├── fast_qr.d.ts ├── fast_qr.js ├── fast_qr_bg.wasm.d.ts └── package.json ├── profile.sh ├── src ├── compact.rs ├── convert │ ├── image.rs │ ├── mod.rs │ └── svg.rs ├── datamasking.rs ├── default.rs ├── ecl.rs ├── encode.rs ├── hardcode.rs ├── helpers.rs ├── lib.rs ├── module.rs ├── placement.rs ├── polynomials.rs ├── qr.rs ├── score.rs ├── tests │ ├── bytes.rs │ ├── compact.rs │ ├── datamasking.rs │ ├── default.rs │ ├── encode.rs │ ├── error_correction.rs │ ├── mod.rs │ ├── polynomials.rs │ ├── score.rs │ ├── structure.rs │ ├── svg.rs │ └── version.rs ├── version.rs └── wasm.rs ├── wasm-pack.ps1 └── wasm-pack.sh /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: ["master"] 6 | pull_request: 7 | branches: ["master"] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | env: 16 | RUSTFLAGS: "--deny warnings" 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - name: Install wasm32-unknown-unknown target 22 | run: rustup target add wasm32-unknown-unknown 23 | 24 | # With no feature. Target: normal & wasm & wasm-bindgen 25 | - name: Build 26 | run: cargo build --verbose 27 | - name: Build in wasm 28 | run: cargo build --verbose --target wasm32-unknown-unknown 29 | - name: Build in wasm-bindgen 30 | run: cargo build --verbose -F wasm-bindgen --target wasm32-unknown-unknown 31 | 32 | # With feature `svg`. Target: normal & wasm & wasm-bindgen 33 | - name: Build with `svg` 34 | run: cargo build --verbose -F svg 35 | - name: Build with `svg` in wasm 36 | run: cargo build --verbose -F svg --target wasm32-unknown-unknown 37 | - name: Build with `svg` in wasm-bindgen 38 | run: cargo build --verbose -F svg,wasm-bindgen --target wasm32-unknown-unknown 39 | 40 | # With feature `image`. Target: normal & wasm only 41 | - name: Build with `image` 42 | run: cargo build --verbose -F image 43 | - name: Build with `image` in wasm 44 | run: cargo build --verbose -F image --target wasm32-unknown-unknown 45 | 46 | # With feature `wasm-bindgen`. Target: wasm only 47 | - name: Build with `wasm-bindgen` 48 | run: cargo build --verbose -F wasm-bindgen --target wasm32-unknown-unknown 49 | 50 | # Benchmarks 51 | - name: Build benchmarks 52 | run: cargo build --benches 53 | 54 | examples: 55 | runs-on: ubuntu-latest 56 | 57 | steps: 58 | - uses: actions/checkout@v4 59 | 60 | - name: Install wasm32-unknown-unknown target 61 | run: rustup target add wasm32-unknown-unknown 62 | 63 | # Examples 64 | - name: Build examples 65 | run: | 66 | for example in examples/*.rs; do 67 | cargo run --example "$(basename "${example%.rs}")" -Fsvg,image 68 | cargo build --example "$(basename "${example%.rs}")" -Fsvg,image --target wasm32-unknown-unknown 69 | done 70 | 71 | tests: 72 | runs-on: ubuntu-latest 73 | 74 | steps: 75 | - uses: actions/checkout@v4 76 | 77 | # Tests 78 | - name: Run tests 79 | run: cargo test --verbose -F svg,image 80 | 81 | meta: 82 | runs-on: ubuntu-latest 83 | 84 | steps: 85 | - uses: actions/checkout@v4 86 | 87 | # Typos 88 | - name: typos-action 89 | uses: crate-ci/typos@v1.0.4 90 | - name: format 91 | run: cargo fmt --all -- --check 92 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | *encoder*.php 3 | 4 | *venv* 5 | tmp.py 6 | benches 7 | 8 | .idea 9 | .vscode 10 | 11 | *.wasm 12 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fast_qr" 3 | version = "0.13.0" 4 | authors = ["erwan.vivien "] 5 | edition = "2021" 6 | description = "Generates optimized QRCode" 7 | documentation = "https://docs.rs/fast_qr/latest/fast_qr/" 8 | homepage = "https://fast-qr.com/" 9 | readme = "README.md" 10 | repository = "https://github.com/erwanvivien/fast_qr/" 11 | keywords = ["qr", "qrcode", "qr-generator", "qrcode-generator", "qr-gen"] 12 | categories = ["multimedia", "multimedia::encoding", "multimedia::images"] 13 | include = ["src", "Cargo.toml", "./README.md", "./LICENSE", "benches"] 14 | rust-version = "1.59" 15 | license = "MIT" 16 | 17 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 18 | [lib] 19 | crate-type = ["cdylib", "rlib"] 20 | 21 | [dependencies] 22 | resvg = { version = "0.28.0", optional = true } 23 | 24 | [features] 25 | svg = [] 26 | image = ["svg", "dep:resvg"] 27 | wasm-bindgen = ["dep:wasm-bindgen"] 28 | 29 | [target.'cfg(target_arch = "wasm32")'.dependencies] 30 | wasm-bindgen = { version = "0.2", optional = true } 31 | 32 | [profile.release] 33 | debug = false 34 | lto = true 35 | codegen-units = 1 36 | opt-level = 's' # Optimize for size 37 | panic = 'abort' # About unwinding code 38 | strip = "debuginfo" 39 | 40 | [package.metadata.wasm-pack.profile.release] 41 | wasm-opt = ["-Oz"] 42 | 43 | [dev-dependencies] 44 | base64 = "0.21.3" 45 | qrcode = "0.12.0" 46 | 47 | [target.'cfg(target_arch = "wasm32")'.dev-dependencies] 48 | criterion = { version = "0.4", default-features = false, features = [ 49 | "cargo_bench_support", 50 | "plotters", 51 | ] } 52 | 53 | [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] 54 | criterion = "0.4" 55 | 56 | [[bench]] 57 | name = "qr" 58 | harness = false 59 | 60 | [package.metadata.docs.rs] 61 | features = ["image", "svg"] 62 | rustdoc-args = ["--cfg", "docsrs"] 63 | 64 | [[example]] 65 | name = "custom" 66 | path = "examples/custom.rs" 67 | required-features = ["image"] 68 | 69 | [[example]] 70 | name = "embed" 71 | path = "examples/embed.rs" 72 | required-features = ["image"] 73 | 74 | [[example]] 75 | name = "image" 76 | path = "examples/image.rs" 77 | required-features = ["image"] 78 | 79 | [[example]] 80 | name = "svg" 81 | path = "examples/svg.rs" 82 | required-features = ["svg"] 83 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Erwan VIVIEN 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | Example qr for website example.com 3 |
4 | 5 | `fast_qr` is approximately 6-7 times faster than `qrcode`, see [benchmarks](#benchmarks) 6 | 7 | You can create a QR as 8 | 9 | - [x] Raw matrix, well suited for custom usage 10 | - [x] Vectorized image, well suited for web usage 11 | - [x] Image, well suited for mobile / print usage 12 | 13 | # Usage 14 | 15 | ## Rust 16 | 17 | ### Examples 18 | 19 | You can run the examples with: 20 | 21 | ```sh 22 | cargo run --example simple 23 | cargo run --example svg -F svg 24 | cargo run --example image -F image 25 | ``` 26 | 27 | They are all explained in detail below. 28 | 29 | ### Converts `QRCode` to Unicode 30 | 31 | ```rust 32 | use fast_qr::convert::ConvertError; 33 | use fast_qr::qr::QRBuilder; 34 | 35 | fn main() -> Result<(), ConvertError> { 36 | // QRBuilder::new can fail if content is too big for version, 37 | // please check before unwrapping. 38 | let qrcode = QRBuilder::new("https://example.com/") 39 | .build() 40 | .unwrap(); 41 | 42 | let str = qrcode.to_str(); // .print() exists 43 | println!("{}", str); 44 | 45 | Ok(()) 46 | } 47 | ``` 48 | 49 | ### Converts `QRCode` to SVG [docs.rs](https://docs.rs/fast_qr/latest/fast_qr/convert/svg/index.html) 50 | 51 | _Note: It requires the `svg` feature_ 52 | 53 | ```rust 54 | use fast_qr::convert::ConvertError; 55 | use fast_qr::convert::{svg::SvgBuilder, Builder, Shape}; 56 | use fast_qr::qr::QRBuilder; 57 | 58 | fn main() -> Result<(), ConvertError> { 59 | // QRBuilder::new can fail if content is too big for version, 60 | // please check before unwrapping. 61 | let qrcode = QRBuilder::new("https://example.com/") 62 | .build() 63 | .unwrap(); 64 | 65 | let _svg = SvgBuilder::default() 66 | .shape(Shape::RoundedSquare) 67 | .to_file(&qrcode, "out.svg"); 68 | 69 | Ok(()) 70 | } 71 | ``` 72 | 73 | ### Converts `QRCode` to an image [docs.rs](https://docs.rs/fast_qr/latest/fast_qr/convert/image/index.html) 74 | 75 | _Note: It requires the `image` feature_ 76 | 77 | ```rust 78 | use fast_qr::convert::ConvertError; 79 | use fast_qr::convert::{image::ImageBuilder, Builder, Shape}; 80 | use fast_qr::qr::QRBuilder; 81 | 82 | fn main() -> Result<(), ConvertError> { 83 | // QRBuilder::new can fail if content is too big for version, 84 | // please check before unwrapping. 85 | let qrcode = QRBuilder::new("https://example.com/") 86 | .build() 87 | .unwrap(); 88 | 89 | let _img = ImageBuilder::default() 90 | .shape(Shape::RoundedSquare) 91 | .background_color([255, 255, 255, 0]) // Handles transparency 92 | .fit_width(600) 93 | .to_file(&qrcode, "out.png"); 94 | 95 | Ok(()) 96 | } 97 | ``` 98 | 99 | ## JavaScript / Typescript 100 | 101 | ### Installation 102 | 103 | ```bash 104 | npm install --save fast_qr 105 | # Or 106 | yarn add fast_qr 107 | ``` 108 | 109 | ### Create an svg 110 | 111 | ```js 112 | /// Once `init` is called, `qr_svg` can be called any number of times 113 | import init, { qr_svg, SvgOptions, Shape } from '/pkg/fast_qr.js' 114 | 115 | const options = new SvgOptions() 116 | .margin(4) 117 | .shape(Shape.Square) 118 | .image("") // Can be a URL or a base64 encoded image 119 | .background_color("#b8a4e5") 120 | .module_color("#ffffff"); 121 | 122 | // Using then / catch: 123 | init() 124 | .then(() => { 125 | for (let i = 0; i < 10; i++) { 126 | const svg = qr_svg("https://fast-qr.com", options); 127 | console.log(svg); 128 | } 129 | }) 130 | .catch(console.error); 131 | 132 | // Or using modern async await: 133 | await init(); 134 | for (let i = 0; i < 10; i++) { 135 | const svg = qr_svg("https://fast-qr.com", options); 136 | console.log(svg); 137 | } 138 | ``` 139 | 140 | # Build WASM 141 | 142 | ### WASM module also exists in NPM registry 143 | 144 | Package is named `fast_qr` and can be installed like so : 145 | 146 | ``` 147 | npm install --save fast_qr 148 | ``` 149 | 150 | ### WASM module might be bundled 151 | 152 | Find a bundled version in the latest [release](https://github.com/erwanvivien/fast_qr/releases). 153 | 154 | ### WASM module can be built from source 155 | 156 | ```bash 157 | ./wasm-pack.sh # Runs build in release mode and wasm-opt twice again 158 | wasm-pack pack pkg # Creates an archive of said package 159 | # wasm-pack publish pkg # Creates an archive & publish it to npm 160 | ``` 161 | 162 | ## Benchmarks 163 | 164 | According to the following benchmarks, `fast_qr` is approximately 6-7x faster than `qrcode`. 165 | 166 | | Benchmark | Lower | Estimate | Upper | | 167 | | :----------- | :-------: | :-------: | :-------: | ----------------------- | 168 | | V03H/qrcode | 524.30 us | 535.02 us | 547.13 us | | 169 | | V03H/fast_qr | 82.079 us | 82.189 us | 82.318 us | fast_qr is 6.51x faster | 170 | | V10H/qrcode | 2.1105 ms | 2.1145 ms | 2.1186 ms | | 171 | | V10H/fast_qr | 268.70 us | 269.28 us | 269.85 us | fast_qr is 7.85x faster | 172 | | V40H/qrcode | 18.000 ms | 18.037 ms | 18.074 ms | | 173 | | V40H/fast_qr | 2.4313 ms | 2.4362 ms | 2.4411 ms | fast_qr is 7.40x faster | 174 | 175 | More benchmarks can be found in [/benches folder](https://github.com/erwanvivien/fast_qr/tree/master/benches). 176 | -------------------------------------------------------------------------------- /assets/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erwanvivien/fast_qr/ef426f3a70b996b394d5c84a55a91145604d4ec1/assets/banner.png -------------------------------------------------------------------------------- /assets/banner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | Fast 7 | 8 | 9 | 10 | 12 | 13 | 16 | QR 17 | 18 | 21 | fast-qr.com 22 | 23 | 24 | -------------------------------------------------------------------------------- /assets/example.com.round.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/example.com.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /benches/README.md: -------------------------------------------------------------------------------- 1 | # Benchmarking `fast_qr` & `qrcode` 2 | 3 | ## Benchmark Windows Powershell 4 | 5 | | Benchmark | Lower | Estimate | Upper | Ratio | 6 | | :-- | :--: | :--: | :--: | -- | 7 | | V03H/qrcode | 1.1646 ms | 1.1717 ms | 1.1804 ms | | 8 | | V03H/fast_qr | 113.58 µs | 113.95 µs | 114.37 µs | fast_qr is 10.28x faster | 9 | | V10H/qrcode | 4.8396 ms | 4.8459 ms | 4.8524 ms | | 10 | | V10H/fast_qr | 368.77 µs | 369.98 µs | 371.86 µs | fast_qr is 13.10x faster | 11 | | V40H/qrcode | 46.726 ms | 46.769 ms | 46.815 ms | | 12 | | V40H/fast_qr | 3.2223 ms | 3.2327 ms | 3.2458 ms | fast_qr is 14.47x faster | 13 | - System: Windows 14 | - Machine: AMD64 15 | - Processor: Intel64 Family 6 Model 158 Stepping 13, GenuineIntel 16 | 17 | ## Benchmark Windows Subsystem Linux 18 | 19 | | Benchmark | Lower | Estimate | Upper | Ratio | 20 | | :-- | :--: | :--: | :--: | -- | 21 | | V03H/qrcode | 623.48 µs | 624.84 µs | 626.68 µs | | 22 | | V03H/fast_qr | 103.42 µs | 104.06 µs | 104.75 µs | fast_qr is 6.00x faster | 23 | | V10H/qrcode | 2.5158 ms | 2.5174 ms | 2.5193 ms | | 24 | | V10H/fast_qr | 334.00 µs | 334.33 µs | 334.72 µs | fast_qr is 7.53x faster | 25 | | V40H/qrcode | 22.188 ms | 22.221 ms | 22.273 ms | | 26 | | V40H/fast_qr | 2.9143 ms | 2.9166 ms | 2.9190 ms | fast_qr is 7.62x faster | 27 | - System: Linux 28 | - Machine: x86_64 29 | - Processor: x86_64 30 | 31 | ## Benchmark Linux 32 | 33 | | Benchmark | Lower | Estimate | Upper | | 34 | | :----------- | :-------: | :-------: | :-------: | ----------------------- | 35 | | V03H/qrcode | 524.30 us | 535.02 us | 547.13 us | | 36 | | V03H/fast_qr | 82.079 us | 82.189 us | 82.318 us | fast_qr is 6.51x faster | 37 | | V10H/qrcode | 2.1105 ms | 2.1145 ms | 2.1186 ms | | 38 | | V10H/fast_qr | 268.70 us | 269.28 us | 269.85 us | fast_qr is 7.85x faster | 39 | | V40H/qrcode | 18.000 ms | 18.037 ms | 18.074 ms | | 40 | | V40H/fast_qr | 2.4313 ms | 2.4362 ms | 2.4411 ms | fast_qr is 7.40x faster | 41 | 42 | - System: Linux 43 | - Machine: x86_64 44 | - Processor: 45 | - RAM: 8GB 46 | 47 | --- 48 | 49 | | Benchmark | Lower | Estimate | Upper | | 50 | | :----------- | :-------: | :-------: | :-------: | ----------------------- | 51 | | V03H/qrcode | 1.0524 ms | 1.0714 ms | 1.0915 ms | | 52 | | V03H/fast_qr | 184.70 us | 187.05 us | 189.85 us | fast_qr is 5.73x faster | 53 | | V10H/qrcode | 3.9165 ms | 3.9448 ms | 3.9761 ms | | 54 | | V10H/fast_qr | 579.63 us | 584.54 us | 589.93 us | fast_qr is 6.75x faster | 55 | | V40H/qrcode | 35.741 ms | 36.093 ms | 36.476 ms | | 56 | | V40H/fast_qr | 5.0615 ms | 5.1513 ms | 5.2476 ms | fast_qr is 7.01x faster | 57 | 58 | - System: Linux 59 | - Machine: x86_64 60 | - Processor: 61 | - RAM: 5GB 62 | 63 | ## Benchmark Mac 64 | 65 | | Benchmark | Lower | Estimate | Upper | Ratio | 66 | | :-- | :--: | :--: | :--: | -- | 67 | | V03H/qrcode | 558.89 µs | 561.68 µs | 564.83 µs | | 68 | | V03H/fast_qr | 70.701 µs | 71.788 µs | 73.095 µs | fast_qr is 7.82x faster | 69 | | V10H/qrcode | 2.2440 ms | 2.2502 ms | 2.2565 ms | | 70 | | V10H/fast_qr | 210.11 µs | 210.50 µs | 210.92 µs | fast_qr is 10.69x faster | 71 | | V40H/qrcode | 19.469 ms | 19.519 ms | 19.587 ms | | 72 | | V40H/fast_qr | 1.8431 ms | 1.8459 ms | 1.8488 ms | fast_qr is 10.57x faster | 73 | 74 | - System: Darwin 75 | - Machine: arm64 76 | - Processor: arm 77 | 78 | Benchmarking powered by [Criterion.rs](https://github.com/bheisler/criterion.rs). \ 79 | Feel free to run some benchmarking yourself! 80 | -------------------------------------------------------------------------------- /benches/bench.py: -------------------------------------------------------------------------------- 1 | import enum 2 | import numbers 3 | import os 4 | import subprocess 5 | 6 | import re 7 | 8 | 9 | cur_dir = os.getcwd() 10 | toml_path = os.path.join(cur_dir, "Cargo.toml") 11 | 12 | if not os.getcwd().endswith("fast_qr") or not os.path.exists(toml_path): 13 | toml_path = "../Cargo.toml" 14 | 15 | # Dry-run to see the compilation output 16 | try: 17 | cargo_bench = subprocess.run( 18 | ["cargo", "bench", "--manifest-path", toml_path, "--no-run"] 19 | ) 20 | except Exception as e: 21 | print("An error occurred while building cargo bench") 22 | print(e) 23 | exit(1) 24 | 25 | try: 26 | cargo_bench = subprocess.check_output( 27 | ["cargo", "bench", "--manifest-path", toml_path], stderr=subprocess.STDOUT 28 | ) 29 | except Exception as e: 30 | print("An error occurred while running cargo bench") 31 | print(e) 32 | exit(1) 33 | 34 | 35 | lines = list(map(lambda x: x.decode("utf-8"), cargo_bench.splitlines())) 36 | 37 | analyze_regex = re.compile(r"^Benchmarking (.*): Analyzing$", re.M | re.I) 38 | benchmark_names = analyze_regex.findall("\n".join(lines)) 39 | 40 | time_lines = [] 41 | 42 | for line in lines: 43 | for benchmark in benchmark_names: 44 | if line.startswith(benchmark): 45 | time_lines.append(line) 46 | break 47 | 48 | time_lines.sort() 49 | 50 | # print("\n".join(time_lines)) 51 | 52 | number = re.compile(r"(\d+(?:\.\d*)?\s*(?:ms|ns|µs|s))") 53 | 54 | def print_bench(benchmark_name: str, lower: str, estimate: str, upper: str, ratio=" " * len('fast_qr is 10.16x faster')): 55 | print( 56 | f"| {benchmark_name:<24} | {lower:<9} | {estimate:<9} | {upper:<9} | {ratio:<24} |" 57 | ) 58 | 59 | print_bench("Benchmark", "Lower", "Estimate", "Upper", "Ratio") 60 | print_bench(":--", ":--:", ":--:", ":--:", "--") 61 | 62 | def number_with_unit(s): 63 | if s.endswith("ns"): 64 | return float(s) * 1000000000 65 | if s.endswith("ms"): 66 | return float(s[:-2]) * 1000000 67 | elif s.endswith("µs"): 68 | return float(s[:-2]) * 1000 69 | elif s.endswith("s"): 70 | return float(s[:-1]) 71 | else: 72 | raise "Not a valid number" 73 | 74 | 75 | for i in range(0, len(time_lines), 2): 76 | test1, time1 = time_lines[i + 0].split("time:") 77 | test2, time2 = time_lines[i + 1].split("time:") 78 | 79 | test1, test2 = test1.strip(), test2.strip() 80 | 81 | data1 = number.findall(time1) 82 | data2 = number.findall(time2) 83 | 84 | data1_number = list(map(number_with_unit, data1)) 85 | data2_number = list(map(number_with_unit, data2)) 86 | 87 | ratio = data2_number[1] / data1_number[1] 88 | print_bench(test2, data2[0], data2[1], data2[2], "") 89 | print_bench(test1, data1[0], data1[1], data1[2], f"fast_qr is {ratio:.2f}x faster") 90 | 91 | import platform 92 | 93 | 94 | print(f"- System: {platform.system()}") 95 | print(f"- Machine: {platform.machine()}") 96 | print(f"- Processor: {platform.processor()}") 97 | 98 | 99 | print() 100 | print( 101 | "Benchmarking powered by [Criterion.rs](https://github.com/bheisler/criterion.rs). \\\n" 102 | "Feel free to run some benchmarkings yourself!\n\n" 103 | ) 104 | -------------------------------------------------------------------------------- /benches/qr.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use criterion::*; 4 | 5 | use fast_qr::QRBuilder; 6 | 7 | fn bench_fastqr_qrcode(c: &mut Criterion) { 8 | let bytes: &[u8] = b"https://example.com/"; 9 | 10 | for (id, fast_qr_version, fast_qr_level, qrocde_version, qrcode_level) in &[ 11 | ( 12 | "V03H", 13 | fast_qr::Version::V03, 14 | fast_qr::ECL::H, 15 | qrcode::Version::Normal(3), 16 | qrcode::EcLevel::H, 17 | ), 18 | ( 19 | "V10H", 20 | fast_qr::Version::V10, 21 | fast_qr::ECL::H, 22 | qrcode::Version::Normal(10), 23 | qrcode::EcLevel::H, 24 | ), 25 | ( 26 | "V40H", 27 | fast_qr::Version::V40, 28 | fast_qr::ECL::H, 29 | qrcode::Version::Normal(40), 30 | qrcode::EcLevel::H, 31 | ), 32 | ] { 33 | let mut group = c.benchmark_group(*id); 34 | group.measurement_time(Duration::from_secs(10)); 35 | group.throughput(Throughput::Bytes(bytes.len() as u64)); 36 | group.sample_size(200); 37 | 38 | group.bench_function("qrcode", |b| { 39 | b.iter(|| { 40 | qrcode::QrCode::with_version( 41 | black_box(b"https://example.com/"), 42 | *qrocde_version, 43 | *qrcode_level, 44 | ) 45 | .unwrap() 46 | }) 47 | }); 48 | 49 | group.bench_function("fast_qr", |b| { 50 | b.iter(|| { 51 | QRBuilder::new(black_box("https://example.com/")) 52 | .ecl(*fast_qr_level) 53 | .version(*fast_qr_version) 54 | .build() 55 | .unwrap() 56 | }) 57 | }); 58 | 59 | group.finish(); 60 | } 61 | } 62 | 63 | criterion_group!(benches, bench_fastqr_qrcode); 64 | criterion_main!(benches); 65 | -------------------------------------------------------------------------------- /examples/custom.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | use fast_qr::{ 3 | convert::{image::ImageBuilder, Builder, Shape}, 4 | ModuleType, QRBuilder, Version, ECL, 5 | }; 6 | 7 | let qrcode = QRBuilder::new("https://example.com/") 8 | .ecl(ECL::H) 9 | .version(Version::V03) 10 | .build() 11 | .unwrap(); 12 | 13 | let mut _img = ImageBuilder::default() 14 | // Can have many shapes and custom shapes 15 | .shape(Shape::Command(|y, x, cell| { 16 | match cell.module_type() { 17 | ModuleType::FinderPattern | ModuleType::Alignment => String::new(), 18 | _ => { 19 | // Works thanks to Deref 20 | Shape::Square(y, x, cell) 21 | } 22 | } 23 | })) 24 | .shape_color( 25 | Shape::Command(|y, x, cell| { 26 | match cell.module_type() { 27 | ModuleType::FinderPattern | ModuleType::Alignment => { 28 | // Works thanks to Deref 29 | Shape::Circle(y, x, cell) 30 | } 31 | _ => String::new(), 32 | } 33 | }), 34 | [255, 0, 0, 255], 35 | ) 36 | .fit_width(600) 37 | .background_color([255, 255, 255, 255]) 38 | .to_file(&qrcode, "custom.png"); 39 | } 40 | -------------------------------------------------------------------------------- /examples/embed.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | use fast_qr::{ 3 | convert::{image::ImageBuilder, Builder, ImageBackgroundShape, Shape}, 4 | QRBuilder, Version, ECL, 5 | }; 6 | 7 | let qrcode = QRBuilder::new("https://example.com/") 8 | .ecl(ECL::H) 9 | .version(Version::V03) 10 | .build() 11 | .unwrap(); 12 | 13 | let mut _img = ImageBuilder::default() 14 | .shape(Shape::Square) 15 | .fit_width(600) 16 | .background_color([255, 255, 255, 255]) 17 | // New: embed an image 18 | .image(String::from("./assets/banner.png")) 19 | // .image_size(15f64) 20 | // .image_gap(2f64) 21 | // .image_position(37f64 / 2f64, 0f64) 22 | .image_background_color([165, 34, 247, 255]) 23 | .image_background_shape(ImageBackgroundShape::Square) 24 | .to_file(&qrcode, "embed.png"); 25 | } 26 | -------------------------------------------------------------------------------- /examples/image.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | use fast_qr::{ 3 | convert::{image::ImageBuilder, Builder, Shape}, 4 | QRBuilder, Version, ECL, 5 | }; 6 | 7 | let qrcode = QRBuilder::new("https://example.com/") 8 | .ecl(ECL::H) 9 | .version(Version::V03) 10 | .build() 11 | .unwrap(); 12 | 13 | let _image = ImageBuilder::default() 14 | .shape(Shape::RoundedSquare) 15 | .fit_width(600) 16 | .background_color([255, 255, 255, 0]) // transparency 17 | .to_file(&qrcode, "image.png"); 18 | 19 | // Or maybe as bytes. 20 | let _image_as_bytes = ImageBuilder::default() 21 | .shape(Shape::RoundedSquare) 22 | .fit_width(512) 23 | .background_color([255, 255, 255, 255]) // opaque 24 | .to_bytes(&qrcode); 25 | } 26 | -------------------------------------------------------------------------------- /examples/node.mjs: -------------------------------------------------------------------------------- 1 | import init, { Version, Shape, SvgOptions, qr_svg } from "../pkg/fast_qr.mjs"; 2 | import fs from "node:fs"; 3 | 4 | const url = new URL("../pkg/fast_qr_bg.wasm", import.meta.url); 5 | await init({ module_or_path: fs.readFileSync(url) }); 6 | 7 | const options = new SvgOptions() 8 | .version(Version.V03) 9 | .shape(Shape.Square) 10 | .image_background_color("#00000000") 11 | .image_size(15) 12 | .image_gap(3) 13 | .background_color("#00000000") 14 | .image( 15 | `data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MDAiIGhlaWdodD0iNDAwIiB2aWV3Qm94PSIwIDAgMTI0IDEyNCIgZmlsbD0ibm9uZSI+CjxyZWN0IHdpZHRoPSIxMjQiIGhlaWdodD0iMTI0IiByeD0iMjQiIGZpbGw9IiNGOTczMTYiLz4KPHBhdGggZD0iTTE5LjM3NSAzNi43ODE4VjEwMC42MjVDMTkuMzc1IDEwMi44MzQgMjEuMTY1OSAxMDQuNjI1IDIzLjM3NSAxMDQuNjI1SDg3LjIxODFDOTAuNzgxOCAxMDQuNjI1IDkyLjU2NjQgMTAwLjMxNiA5MC4wNDY2IDk3Ljc5NjZMMjYuMjAzNCAzMy45NTM0QzIzLjY4MzYgMzEuNDMzNiAxOS4zNzUgMzMuMjE4MiAxOS4zNzUgMzYuNzgxOFoiIGZpbGw9IndoaXRlIi8+CjxjaXJjbGUgY3g9IjYzLjIxMDkiIGN5PSIzNy41MzkxIiByPSIxOC4xNjQxIiBmaWxsPSJibGFjayIvPgo8cmVjdCBvcGFjaXR5PSIwLjQiIHg9IjgxLjEzMjgiIHk9IjgwLjcxOTgiIHdpZHRoPSIxNy41Njg3IiBoZWlnaHQ9IjE3LjM4NzYiIHJ4PSI0IiB0cmFuc2Zvcm09InJvdGF0ZSgtNDUgODEuMTMyOCA4MC43MTk4KSIgZmlsbD0iI0ZEQkE3NCIvPgo8L3N2Zz4=` 16 | ); 17 | const svg = qr_svg(`https://example.com/`, options); 18 | console.log(svg); 19 | -------------------------------------------------------------------------------- /examples/simple.rs: -------------------------------------------------------------------------------- 1 | use fast_qr::{QRBuilder, Version, ECL}; 2 | 3 | fn main() { 4 | let qrcode = QRBuilder::new("https://example.com/") 5 | .ecl(ECL::H) 6 | .version(Version::V03) 7 | .build() 8 | .unwrap(); 9 | 10 | qrcode.print(); 11 | } 12 | -------------------------------------------------------------------------------- /examples/svg.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | use fast_qr::{ 3 | convert::{svg::SvgBuilder, Builder, Shape}, 4 | QRBuilder, Version, ECL, 5 | }; 6 | 7 | let qrcode = QRBuilder::new("https://example.com/") 8 | .ecl(ECL::H) 9 | .version(Version::V03) 10 | .build() 11 | .unwrap(); 12 | 13 | let _svg = SvgBuilder::default() 14 | .shape(Shape::RoundedSquare) 15 | .to_file(&qrcode, "svg.svg"); 16 | } 17 | -------------------------------------------------------------------------------- /examples/wasm.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | WebAssembly example 7 | 8 | 9 | 10 |
11 | Serve using `npx serve` at root of project 12 |
13 | 14 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /pkg/.gitignore: -------------------------------------------------------------------------------- 1 | * -------------------------------------------------------------------------------- /pkg/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Erwan VIVIEN 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pkg/README.md: -------------------------------------------------------------------------------- 1 |
2 | Example qr for website example.com 3 |
4 | 5 | `fast_qr` is approximately 6-7 times faster than `qrcode`, see [benchmarks](#benchmarks) 6 | 7 | You can create a QR as 8 | 9 | - [x] Raw matrix, well suited for custom usage 10 | - [x] Vectorized image, well suited for web usage 11 | - [x] Image, well suited for mobile / print usage 12 | 13 | # Usage 14 | 15 | ## JavaScript / Typescript 16 | 17 | ### Installation 18 | 19 | ```bash 20 | npm install --save fast_qr 21 | # Or 22 | yarn add fast_qr 23 | ``` 24 | 25 | ### Create an svg 26 | 27 | ```js 28 | import init, { qr_svg } from "fast_qr"; 29 | import type { QrSvgOptions } from "fast_qr"; 30 | 31 | const options: QrSvgOptions = { 32 | module_color: "#FFF", 33 | background_color: "#000", 34 | }; 35 | 36 | /// Once `init` is called, `qr_svg` can be called any number of times 37 | // Using then / catch: 38 | init() 39 | .then(() => { 40 | for (let i = 0; i < 10; i++) { 41 | const svg = qr_svg("https://fast-qr.com", options); 42 | console.log(svg); 43 | } 44 | }) 45 | .catch((e) => { 46 | console.error("Could not fetch wasm: ", e); 47 | }); 48 | 49 | // Or using modern async await: 50 | await init(); 51 | for (let i = 0; i < 10; i++) { 52 | const svg = qr_svg("https://fast-qr.com", options); 53 | console.log(svg); 54 | } 55 | ``` 56 | 57 | ## Rust 58 | 59 | ### Examples 60 | 61 | You can run the examples with: 62 | 63 | ```sh 64 | cargo run --example simple 65 | cargo run --example svg -F svg 66 | cargo run --example image -F image 67 | ``` 68 | 69 | They are all explained in detail below. 70 | 71 | ### Converts `QRCode` to Unicode 72 | 73 | ```rust 74 | use fast_qr::convert::ConvertError; 75 | use fast_qr::qr::QRBuilder; 76 | 77 | fn main() -> Result<(), ConvertError> { 78 | // QRBuilder::new can fail if content is too big for version, 79 | // please check before unwrapping. 80 | let qrcode = QRBuilder::new("https://example.com/") 81 | .build() 82 | .unwrap(); 83 | 84 | let str = qrcode.to_str(); // .print() exists 85 | println!("{}", str); 86 | 87 | Ok(()) 88 | } 89 | ``` 90 | 91 | ### Converts `QRCode` to SVG [docs.rs](https://docs.rs/fast_qr/latest/fast_qr/convert/svg/index.html) 92 | 93 | _Note: It requires the `svg` feature_ 94 | 95 | ```rust 96 | use fast_qr::convert::ConvertError; 97 | use fast_qr::convert::{svg::SvgBuilder, Builder, Shape}; 98 | use fast_qr::qr::QRBuilder; 99 | 100 | fn main() -> Result<(), ConvertError> { 101 | // QRBuilder::new can fail if content is too big for version, 102 | // please check before unwrapping. 103 | let qrcode = QRBuilder::new("https://example.com/") 104 | .build() 105 | .unwrap(); 106 | 107 | let _svg = SvgBuilder::default() 108 | .shape(Shape::RoundedSquare) 109 | .to_file(&qrcode, "out.svg"); 110 | 111 | Ok(()) 112 | } 113 | ``` 114 | 115 | ### Converts `QRCode` to an image [docs.rs](https://docs.rs/fast_qr/latest/fast_qr/convert/image/index.html) 116 | 117 | _Note: It requires the `image` feature_ 118 | 119 | ```rust 120 | use fast_qr::convert::ConvertError; 121 | use fast_qr::convert::{image::ImageBuilder, Builder, Shape}; 122 | use fast_qr::qr::QRBuilder; 123 | 124 | fn main() -> Result<(), ConvertError> { 125 | // QRBuilder::new can fail if content is too big for version, 126 | // please check before unwrapping. 127 | let qrcode = QRBuilder::new("https://example.com/") 128 | .build() 129 | .unwrap(); 130 | 131 | let _img = ImageBuilder::default() 132 | .shape(Shape::RoundedSquare) 133 | .background_color([255, 255, 255, 0]) // Handles transparency 134 | .fit_width(600) 135 | .to_file(&qrcode, "out.png"); 136 | 137 | Ok(()) 138 | } 139 | ``` 140 | 141 | # Build WASM 142 | 143 | ### WASM module also exists in NPM registry 144 | 145 | Package is named `fast_qr` and can be installed like so : 146 | 147 | ``` 148 | npm install --save fast_qr 149 | ``` 150 | 151 | ### WASM module might be bundled 152 | 153 | Find a bundled version in the latest [release](https://github.com/erwanvivien/fast_qr/releases). 154 | 155 | ### WASM module can be built from source 156 | 157 | ```bash 158 | ./wasm-pack.sh # Runs build in release mode and wasm-opt twice again 159 | wasm-pack pack pkg # Creates an archive of said package 160 | # wasm-pack publish pkg # Creates an archive & publish it to npm 161 | ``` 162 | 163 | ## Benchmarks 164 | 165 | According to the following benchmarks, `fast_qr` is approximately 6-7x faster than `qrcode`. 166 | 167 | | Benchmark | Lower | Estimate | Upper | | 168 | | :----------- | :-------: | :-------: | :-------: | ----------------------- | 169 | | V03H/qrcode | 524.30 us | 535.02 us | 547.13 us | | 170 | | V03H/fast_qr | 82.079 us | 82.189 us | 82.318 us | fast_qr is 6.51x faster | 171 | | V10H/qrcode | 2.1105 ms | 2.1145 ms | 2.1186 ms | | 172 | | V10H/fast_qr | 268.70 us | 269.28 us | 269.85 us | fast_qr is 7.85x faster | 173 | | V40H/qrcode | 18.000 ms | 18.037 ms | 18.074 ms | | 174 | | V40H/fast_qr | 2.4313 ms | 2.4362 ms | 2.4411 ms | fast_qr is 7.40x faster | 175 | 176 | More benchmarks can be found in [/benches folder](https://github.com/erwanvivien/fast_qr/tree/master/benches). 177 | -------------------------------------------------------------------------------- /pkg/fast_qr.d.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | /** 4 | * Generate a QR code from a string. All parameters are automatically set. 5 | */ 6 | export function qr(content: string): Uint8Array; 7 | /** 8 | * Generate a QR code from a string. All parameters are automatically set. 9 | */ 10 | export function qr_svg(content: string, options: SvgOptions): string; 11 | /** 12 | * Error Correction Coding has 4 levels 13 | */ 14 | export enum ECL { 15 | /** 16 | * Low, 7% 17 | */ 18 | L = 0, 19 | /** 20 | * Medium, 15% 21 | */ 22 | M = 1, 23 | /** 24 | * Quartile, 25% 25 | */ 26 | Q = 2, 27 | /** 28 | * High, 30% 29 | */ 30 | H = 3, 31 | } 32 | /** 33 | * Different possible image background shapes 34 | */ 35 | export enum ImageBackgroundShape { 36 | /** 37 | * Square shape 38 | */ 39 | Square = 0, 40 | /** 41 | * Circle shape 42 | */ 43 | Circle = 1, 44 | /** 45 | * Rounded square shape 46 | */ 47 | RoundedSquare = 2, 48 | } 49 | /** 50 | * Different possible Shapes to represent modules in a [`crate::QRCode`] 51 | */ 52 | export enum Shape { 53 | /** 54 | * Square Shape 55 | */ 56 | Square = 0, 57 | /** 58 | * Circle Shape 59 | */ 60 | Circle = 1, 61 | /** 62 | * RoundedSquare Shape 63 | */ 64 | RoundedSquare = 2, 65 | /** 66 | * Vertical Shape 67 | */ 68 | Vertical = 3, 69 | /** 70 | * Horizontal Shape 71 | */ 72 | Horizontal = 4, 73 | /** 74 | * Diamond Shape 75 | */ 76 | Diamond = 5, 77 | } 78 | /** 79 | * Enum containing all possible `QRCode` versions 80 | */ 81 | export enum Version { 82 | /** 83 | * Version n°01 84 | */ 85 | V01 = 0, 86 | /** 87 | * Version n°02 88 | */ 89 | V02 = 1, 90 | /** 91 | * Version n°03 92 | */ 93 | V03 = 2, 94 | /** 95 | * Version n°04 96 | */ 97 | V04 = 3, 98 | /** 99 | * Version n°05 100 | */ 101 | V05 = 4, 102 | /** 103 | * Version n°06 104 | */ 105 | V06 = 5, 106 | /** 107 | * Version n°07 108 | */ 109 | V07 = 6, 110 | /** 111 | * Version n°08 112 | */ 113 | V08 = 7, 114 | /** 115 | * Version n°09 116 | */ 117 | V09 = 8, 118 | /** 119 | * Version n°10 120 | */ 121 | V10 = 9, 122 | /** 123 | * Version n°11 124 | */ 125 | V11 = 10, 126 | /** 127 | * Version n°12 128 | */ 129 | V12 = 11, 130 | /** 131 | * Version n°13 132 | */ 133 | V13 = 12, 134 | /** 135 | * Version n°14 136 | */ 137 | V14 = 13, 138 | /** 139 | * Version n°15 140 | */ 141 | V15 = 14, 142 | /** 143 | * Version n°16 144 | */ 145 | V16 = 15, 146 | /** 147 | * Version n°17 148 | */ 149 | V17 = 16, 150 | /** 151 | * Version n°18 152 | */ 153 | V18 = 17, 154 | /** 155 | * Version n°19 156 | */ 157 | V19 = 18, 158 | /** 159 | * Version n°20 160 | */ 161 | V20 = 19, 162 | /** 163 | * Version n°21 164 | */ 165 | V21 = 20, 166 | /** 167 | * Version n°22 168 | */ 169 | V22 = 21, 170 | /** 171 | * Version n°23 172 | */ 173 | V23 = 22, 174 | /** 175 | * Version n°24 176 | */ 177 | V24 = 23, 178 | /** 179 | * Version n°25 180 | */ 181 | V25 = 24, 182 | /** 183 | * Version n°26 184 | */ 185 | V26 = 25, 186 | /** 187 | * Version n°27 188 | */ 189 | V27 = 26, 190 | /** 191 | * Version n°28 192 | */ 193 | V28 = 27, 194 | /** 195 | * Version n°29 196 | */ 197 | V29 = 28, 198 | /** 199 | * Version n°30 200 | */ 201 | V30 = 29, 202 | /** 203 | * Version n°31 204 | */ 205 | V31 = 30, 206 | /** 207 | * Version n°32 208 | */ 209 | V32 = 31, 210 | /** 211 | * Version n°33 212 | */ 213 | V33 = 32, 214 | /** 215 | * Version n°34 216 | */ 217 | V34 = 33, 218 | /** 219 | * Version n°35 220 | */ 221 | V35 = 34, 222 | /** 223 | * Version n°36 224 | */ 225 | V36 = 35, 226 | /** 227 | * Version n°37 228 | */ 229 | V37 = 36, 230 | /** 231 | * Version n°38 232 | */ 233 | V38 = 37, 234 | /** 235 | * Version n°39 236 | */ 237 | V39 = 38, 238 | /** 239 | * Version n°40 240 | */ 241 | V40 = 39, 242 | } 243 | /** 244 | * Configuration for the SVG output. 245 | */ 246 | export class SvgOptions { 247 | free(): void; 248 | /** 249 | * Updates the shape of the QRCode modules. 250 | */ 251 | shape(shape: Shape): SvgOptions; 252 | /** 253 | * Updates the module color of the QRCode. Tales a string in the format `#RRGGBB[AA]`. 254 | */ 255 | module_color(module_color: string): SvgOptions; 256 | /** 257 | * Updates the margin of the QRCode. 258 | */ 259 | margin(margin: number): SvgOptions; 260 | /** 261 | * Updates the background color of the QRCode. Tales a string in the format `#RRGGBB[AA]`. 262 | */ 263 | background_color(background_color: string): SvgOptions; 264 | /** 265 | * Updates the image of the QRCode. Takes base64 or a url. 266 | */ 267 | image(image: string): SvgOptions; 268 | /** 269 | * Updates the background color of the image. Takes a string in the format `#RRGGBB[AA]`. 270 | */ 271 | image_background_color(image_background_color: string): SvgOptions; 272 | /** 273 | * Updates the shape of the image background. Takes an convert::ImageBackgroundShape. 274 | */ 275 | image_background_shape(image_background_shape: ImageBackgroundShape): SvgOptions; 276 | /** 277 | * Updates the size of the image. (unit being module size). 278 | */ 279 | image_size(size: number): SvgOptions; 280 | /** 281 | * Updates the gap between background color and the image. (unit being module size). 282 | */ 283 | image_gap(gap: number): SvgOptions; 284 | /** 285 | * Updates the position of the image. Takes an array [x, y] (unit being module size). 286 | */ 287 | image_position(image_position: Float64Array): SvgOptions; 288 | /** 289 | * Updates the error correction level of the QRCode (can increase the size of the QRCode) 290 | */ 291 | ecl(ecl: ECL): SvgOptions; 292 | /** 293 | * Forces the version of the QRCode 294 | */ 295 | version(version: Version): SvgOptions; 296 | /** 297 | * Creates a new SvgOptions object. 298 | */ 299 | constructor(); 300 | } 301 | 302 | export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; 303 | 304 | export interface InitOutput { 305 | readonly memory: WebAssembly.Memory; 306 | readonly qr: (a: number, b: number) => [number, number]; 307 | readonly __wbg_svgoptions_free: (a: number, b: number) => void; 308 | readonly svgoptions_shape: (a: number, b: number) => number; 309 | readonly svgoptions_module_color: (a: number, b: number, c: number) => number; 310 | readonly svgoptions_margin: (a: number, b: number) => number; 311 | readonly svgoptions_background_color: (a: number, b: number, c: number) => number; 312 | readonly svgoptions_image: (a: number, b: number, c: number) => number; 313 | readonly svgoptions_image_background_color: (a: number, b: number, c: number) => number; 314 | readonly svgoptions_image_background_shape: (a: number, b: number) => number; 315 | readonly svgoptions_image_size: (a: number, b: number) => number; 316 | readonly svgoptions_image_gap: (a: number, b: number) => number; 317 | readonly svgoptions_image_position: (a: number, b: number, c: number) => number; 318 | readonly svgoptions_ecl: (a: number, b: number) => number; 319 | readonly svgoptions_version: (a: number, b: number) => number; 320 | readonly svgoptions_new: () => number; 321 | readonly qr_svg: (a: number, b: number, c: number) => [number, number]; 322 | readonly __wbindgen_export_0: WebAssembly.Table; 323 | readonly __wbindgen_malloc: (a: number, b: number) => number; 324 | readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; 325 | readonly __wbindgen_free: (a: number, b: number, c: number) => void; 326 | readonly __wbindgen_start: () => void; 327 | } 328 | 329 | export type SyncInitInput = BufferSource | WebAssembly.Module; 330 | /** 331 | * Instantiates the given `module`, which can either be bytes or 332 | * a precompiled `WebAssembly.Module`. 333 | * 334 | * @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated. 335 | * 336 | * @returns {InitOutput} 337 | */ 338 | export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput; 339 | 340 | /** 341 | * If `module_or_path` is {RequestInfo} or {URL}, makes a request and 342 | * for everything else, calls `WebAssembly.instantiate` directly. 343 | * 344 | * @param {{ module_or_path: InitInput | Promise }} module_or_path - Passing `InitInput` directly is deprecated. 345 | * 346 | * @returns {Promise} 347 | */ 348 | export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise } | InitInput | Promise): Promise; 349 | -------------------------------------------------------------------------------- /pkg/fast_qr_bg.wasm.d.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | export const memory: WebAssembly.Memory; 4 | export const qr: (a: number, b: number) => [number, number]; 5 | export const __wbg_svgoptions_free: (a: number, b: number) => void; 6 | export const svgoptions_shape: (a: number, b: number) => number; 7 | export const svgoptions_module_color: (a: number, b: number, c: number) => number; 8 | export const svgoptions_margin: (a: number, b: number) => number; 9 | export const svgoptions_background_color: (a: number, b: number, c: number) => number; 10 | export const svgoptions_image: (a: number, b: number, c: number) => number; 11 | export const svgoptions_image_background_color: (a: number, b: number, c: number) => number; 12 | export const svgoptions_image_background_shape: (a: number, b: number) => number; 13 | export const svgoptions_image_size: (a: number, b: number) => number; 14 | export const svgoptions_image_gap: (a: number, b: number) => number; 15 | export const svgoptions_image_position: (a: number, b: number, c: number) => number; 16 | export const svgoptions_ecl: (a: number, b: number) => number; 17 | export const svgoptions_version: (a: number, b: number) => number; 18 | export const svgoptions_new: () => number; 19 | export const qr_svg: (a: number, b: number, c: number) => [number, number]; 20 | export const __wbindgen_export_0: WebAssembly.Table; 21 | export const __wbindgen_malloc: (a: number, b: number) => number; 22 | export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; 23 | export const __wbindgen_free: (a: number, b: number, c: number) => void; 24 | export const __wbindgen_start: () => void; 25 | -------------------------------------------------------------------------------- /pkg/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fast_qr", 3 | "collaborators": [ 4 | "erwan.vivien " 5 | ], 6 | "description": "Generates optimized QRCode", 7 | "version": "0.13.0", 8 | "license": "SEE LICENSE IN LICENSE", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/erwanvivien/fast_qr/" 12 | }, 13 | "files": [ 14 | "fast_qr_bg.wasm", 15 | "fast_qr.js", 16 | "fast_qr.d.ts" 17 | ], 18 | "module": "fast_qr.js", 19 | "homepage": "https://fast-qr.com/", 20 | "types": "fast_qr.d.ts", 21 | "sideEffects": false, 22 | "keywords": [ 23 | "qr", 24 | "qrcode", 25 | "qr-generator", 26 | "qrcode-generator", 27 | "qr-gen" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /profile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | args="${*:-https://fast-qr.com/}" 4 | echo "$args" 5 | 6 | cargo build --release 7 | 8 | echo "$args" | perf record --call-graph=dwarf target/release/fast_qr && \ 9 | perf script -F +pid > /tmp/test.perf && \ 10 | 11 | echo "View results on: https://profiler.firefox.com/" 12 | -------------------------------------------------------------------------------- /src/compact.rs: -------------------------------------------------------------------------------- 1 | //! Struct containing an u8-array of C size to store bitwise boolean values 2 | 3 | #![deny(unsafe_code)] 4 | #![warn(missing_docs)] 5 | 6 | use core::fmt::{Display, Formatter}; 7 | 8 | use crate::Version; 9 | 10 | /// Values to keep last X bits of a u8 11 | /// `KEEP_LAST[i]` equates `(1 << i) - 1` 12 | /// 13 | /// # Example 14 | /// ```rust 15 | /// # pub const KEEP_LAST: [usize; 65] = [ 16 | /// # 0, 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 17 | /// # 32767, 65535, 131071, 262143, 524287, 1048575, 2097151, 4194303, 8388607, 18 | /// # 16777215, 33554431, 67108863, 134217727, 268435455, 536870911, 1073741823, 19 | /// # 2147483647, 4294967295, 8589934591, 17179869183, 34359738367, 68719476735, 20 | /// # 137438953471, 274877906943, 549755813887, 1099511627775, 2199023255551, 21 | /// # 4398046511103, 8796093022207, 17592186044415, 35184372088831, 22 | /// # 70368744177663, 140737488355327, 281474976710655, 562949953421311, 23 | /// # 1125899906842623, 2251799813685247, 4503599627370495, 9007199254740991, 24 | /// # 18014398509481983, 36028797018963967, 72057594037927935, 25 | /// # 144115188075855871, 288230376151711743, 576460752303423487, 26 | /// # 1152921504606846975, 2305843009213693951, 4611686018427387903, 27 | /// # 9223372036854775807, 18446744073709551615, 28 | /// # ]; 29 | /// let mut b = 0b1010_1010; 30 | /// assert_eq!(b & KEEP_LAST[3], 0b010) 31 | /// ``` 32 | #[rustfmt::skip] 33 | #[cfg(not(target_arch = "wasm32"))] 34 | pub const KEEP_LAST: [usize; 65] = [ 35 | 0, 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 36 | 32767, 65535, 131_071, 262_143, 524_287, 1_048_575, 2_097_151, 4_194_303, 8_388_607, 37 | 16_777_215, 33_554_431, 67_108_863, 134_217_727, 268_435_455, 536_870_911, 1_073_741_823, 38 | 2_147_483_647, 4_294_967_295, 8_589_934_591, 17_179_869_183, 34_359_738_367, 68_719_476_735, 39 | 137_438_953_471, 274_877_906_943, 549_755_813_887, 1_099_511_627_775, 2_199_023_255_551, 40 | 4_398_046_511_103, 8_796_093_022_207, 17_592_186_044_415, 35_184_372_088_831, 41 | 70_368_744_177_663, 140_737_488_355_327, 281_474_976_710_655, 562_949_953_421_311, 42 | 1_125_899_906_842_623, 2_251_799_813_685_247, 4_503_599_627_370_495, 9_007_199_254_740_991, 43 | 18_014_398_509_481_983, 36_028_797_018_963_967, 72_057_594_037_927_935, 44 | 144_115_188_075_855_871, 288_230_376_151_711_743, 576_460_752_303_423_487, 45 | 1_152_921_504_606_846_975, 2_305_843_009_213_693_951, 4_611_686_018_427_387_903, 46 | 9_223_372_036_854_775_807, 18_446_744_073_709_551_615, 47 | ]; 48 | 49 | /// Values to keep last X bits of a u8 50 | /// `KEEP_LAST[i]` equates `(1 << i) - 1` 51 | #[rustfmt::skip] 52 | #[cfg(target_arch = "wasm32")] 53 | pub const KEEP_LAST: [usize; 33] = [ 54 | 0, 1, 3, 7, 15, 31, 63, 127, 255, 511, 1_023, 2_047, 4_095, 8_191, 16_383, 55 | 32_767, 65_535, 131_071, 262_143, 524_287, 1_048_575, 2_097_151, 4_194_303, 8_388_607, 56 | 16_777_215, 33_554_431, 67_108_863, 134_217_727, 268_435_455, 536_870_911, 1_073_741_823, 57 | 2_147_483_647, 4_294_967_295, 58 | ]; 59 | 60 | /// `CompactQR` is a struct that contains a `Vec` to store boolean values as bits. 61 | pub struct CompactQR { 62 | pub len: usize, 63 | pub data: Vec, 64 | } 65 | 66 | /// Returns a string visualization of the `CompactQR`. \ 67 | /// `CompactQR { len: 4, data: [0b1111_1010] }.to_string()` => `"1010"` 68 | impl Display for CompactQR { 69 | fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { 70 | let mut res = String::with_capacity(self.len); 71 | 72 | for i in 0..(self.data.capacity() / 8) { 73 | let nb = self.data[i]; 74 | for j in 0..8 { 75 | if i * 8 + j >= self.len { 76 | return f.write_str(&res); 77 | } 78 | 79 | let j = 7 - j; 80 | let c = if nb & (1 << j) == 0 { '0' } else { '1' }; 81 | res.push(c); 82 | } 83 | } 84 | 85 | f.write_str(&res) 86 | } 87 | } 88 | 89 | #[allow(clippy::cast_possible_truncation)] 90 | impl CompactQR { 91 | /// Instantiates a new `CompactQR`, should not be used, reduces performance. 92 | #[allow(dead_code)] 93 | pub const fn new() -> Self { 94 | CompactQR { 95 | len: 0, 96 | data: Vec::new(), 97 | } 98 | } 99 | 100 | pub fn from_version(version: Version) -> Self { 101 | let len = version.max_bytes(); 102 | let data = vec![0; len * 8]; 103 | 104 | CompactQR { len: 0, data } 105 | } 106 | 107 | /// Instantiates a new `CompactQR`, with a given length, expects the length to be a multiple of 8. 108 | #[allow(dead_code)] 109 | #[cfg(test)] 110 | pub fn with_len(data_length: usize) -> Self { 111 | let length = data_length / 8 + usize::from(data_length % 8 != 0); 112 | CompactQR { 113 | len: 0, 114 | data: vec![0; length], 115 | } 116 | } 117 | 118 | /// Increase the length of data to specified length. 119 | pub fn increase_len(&mut self, data_length: usize) { 120 | if data_length / 8 >= self.data.len() { 121 | self.data.resize(data_length / 8 + 1, 0); 122 | } 123 | } 124 | 125 | /// Instantiates a new `CompactQR` from an already created array 126 | pub fn from_array(data: &[u8], len: usize) -> Self { 127 | CompactQR { 128 | len, 129 | data: data.to_vec(), 130 | } 131 | } 132 | 133 | /// Returns `len`, length is the current number of bits / boolean values stored in the array. 134 | pub const fn len(&self) -> usize { 135 | self.len 136 | } 137 | 138 | /// Returns `data`, the array of bits. 139 | pub const fn get_data(&self) -> &Vec { 140 | &self.data 141 | } 142 | 143 | /// Pushes eight values in the `CompactQR`, if the array is not big enough, it will be resized. 144 | #[inline(always)] 145 | #[allow(dead_code)] 146 | pub fn push_u8(&mut self, bits: u8) { 147 | self.increase_len(self.len + 8); 148 | 149 | let right = self.len % 8; 150 | let first_idx = self.len / 8; 151 | 152 | if right == 0 { 153 | self.data[first_idx] = bits; 154 | } else { 155 | let left = 8 - right; 156 | self.data[first_idx] |= (bits >> right) & (KEEP_LAST[left] as u8); 157 | self.data[first_idx + 1] |= (bits & KEEP_LAST[right] as u8) << left; 158 | } 159 | 160 | self.len += 8; 161 | } 162 | 163 | /// Pushes the u8 array in the `CompactQR`, using the `push_u8` function. \ 164 | /// If the array is not big enough, it will be resized. 165 | #[inline(always)] 166 | pub fn push_u8_slice(&mut self, slice: &[u8]) { 167 | self.increase_len(self.len + 8 * slice.len()); 168 | 169 | for &u in slice { 170 | self.push_u8(u); 171 | } 172 | } 173 | 174 | /// Pushes `len` values to the `CompactQR`. \ 175 | /// If the array is not big enough, it will be resized. 176 | #[inline(always)] 177 | pub fn push_bits(&mut self, bits: usize, len: usize) { 178 | self.increase_len(self.len + len); 179 | 180 | // Caps to max usize bits 181 | let bits = bits & KEEP_LAST[len]; 182 | 183 | let rem_space = (8 - self.len % 8) % 8; 184 | let first = self.len / 8; 185 | 186 | if rem_space > len { 187 | self.data[first] |= (bits << (rem_space - len)) as u8; 188 | self.len += len; 189 | return; 190 | } 191 | 192 | if rem_space != 0 { 193 | self.data[first] |= ((bits >> (len - rem_space)) & KEEP_LAST[rem_space]) as u8; 194 | self.len += rem_space; 195 | } 196 | 197 | for i in (8..=len - rem_space).rev().step_by(8) { 198 | self.push_u8((bits >> (i - 8)) as u8); 199 | } 200 | 201 | let remaining = (len - rem_space) % 8; 202 | if remaining == 0 { 203 | return; 204 | } 205 | 206 | self.data[self.len / 8] += ((bits & KEEP_LAST[remaining]) as u8) << (8 - remaining); 207 | self.len += remaining; 208 | } 209 | 210 | /// Fills the `CompactQR`'s remaining space with `[236, 17]`. 211 | /// Expects the `CompactQR` `len` to be a multiple of 8. 212 | #[inline(always)] 213 | pub fn fill(&mut self) { 214 | const PAD_BYTES: [u8; 2] = [0b1110_1100, 0b0001_0001]; //[236, 17] 215 | 216 | #[cfg(debug_assertions)] 217 | assert_eq!(self.len % 8, 0); 218 | 219 | for (i, _) in (self.len..self.data.len()).step_by(8).enumerate() { 220 | let bits = PAD_BYTES[i % 2]; 221 | self.push_u8(bits); 222 | } 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /src/convert/image.rs: -------------------------------------------------------------------------------- 1 | //! Converts [`QRCode`] to an image 2 | //! 3 | //! ```rust 4 | //! use fast_qr::convert::ConvertError; 5 | //! use fast_qr::convert::{image::ImageBuilder, Builder, Shape}; 6 | //! use fast_qr::qr::QRBuilder; 7 | //! 8 | //! # fn main() -> Result<(), ConvertError> { 9 | //! // QRBuilde::new can fail if content is too big for version, 10 | //! // please check before unwrapping. 11 | //! let qrcode = QRBuilder::new("https://example.com/") 12 | //! .build() 13 | //! .unwrap(); 14 | //! 15 | //! let _img = ImageBuilder::default() 16 | //! .shape(Shape::RoundedSquare) 17 | //! .fit_width(600) 18 | //! .to_file(&qrcode, "out.png"); 19 | //! 20 | //! # std::fs::remove_file("out.png"); 21 | //! # Ok(()) 22 | //! # } 23 | //! ``` 24 | 25 | use std::fmt::Formatter; 26 | use std::io; 27 | 28 | use crate::QRCode; 29 | 30 | use super::Color; 31 | use super::{svg::SvgBuilder, Builder, Shape}; 32 | 33 | use resvg::tiny_skia::{self, Pixmap}; 34 | use resvg::usvg; 35 | 36 | /// [`ImageBuilder`] contains an [`SvgBuilder`] and adds some options \ 37 | /// - fit_height adds a max-height boundary 38 | /// - fit_width adds a max-width boundary 39 | pub struct ImageBuilder { 40 | fit_height: Option, 41 | fit_width: Option, 42 | svg_builder: SvgBuilder, 43 | } 44 | 45 | /// Error when converting to image 46 | #[derive(Debug)] 47 | pub enum ImageError { 48 | /// Error while writing to file 49 | IoError(io::Error), 50 | /// Error while creating image 51 | ImageError(String), 52 | /// Error while convert to bytes 53 | EncodingError(String), 54 | } 55 | 56 | impl std::error::Error for ImageError {} 57 | 58 | impl std::fmt::Display for ImageError { 59 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 60 | match self { 61 | ImageError::IoError(io_err) => f.write_str(io_err.to_string().as_str()), 62 | ImageError::ImageError(error) => f.write_str(error.as_str()), 63 | ImageError::EncodingError(error) => f.write_str(error.as_str()), 64 | } 65 | } 66 | } 67 | 68 | /// Creates an ImageBuilder instance, which contains an [`SvgBuilder`] 69 | impl Default for ImageBuilder { 70 | fn default() -> Self { 71 | ImageBuilder { 72 | fit_height: None, 73 | fit_width: None, 74 | svg_builder: Default::default(), 75 | } 76 | } 77 | } 78 | 79 | impl Builder for ImageBuilder { 80 | fn margin(&mut self, margin: usize) -> &mut Self { 81 | self.svg_builder.margin(margin); 82 | self 83 | } 84 | 85 | fn module_color>(&mut self, module_color: C) -> &mut Self { 86 | self.svg_builder.module_color(module_color); 87 | self 88 | } 89 | 90 | fn background_color>(&mut self, background_color: C) -> &mut Self { 91 | self.svg_builder.background_color(background_color); 92 | self 93 | } 94 | 95 | fn shape(&mut self, shape: Shape) -> &mut Self { 96 | self.svg_builder.shape(shape); 97 | self 98 | } 99 | 100 | fn image(&mut self, image: String) -> &mut Self { 101 | self.svg_builder.image(image); 102 | self 103 | } 104 | 105 | fn image_background_color>(&mut self, image_background_color: C) -> &mut Self { 106 | self.svg_builder 107 | .image_background_color(image_background_color); 108 | self 109 | } 110 | 111 | fn image_background_shape( 112 | &mut self, 113 | image_background_shape: super::ImageBackgroundShape, 114 | ) -> &mut Self { 115 | self.svg_builder 116 | .image_background_shape(image_background_shape); 117 | self 118 | } 119 | 120 | fn image_size(&mut self, image_size: f64) -> &mut Self { 121 | self.svg_builder.image_size(image_size); 122 | self 123 | } 124 | 125 | fn image_gap(&mut self, gap: f64) -> &mut Self { 126 | self.svg_builder.image_gap(gap); 127 | self 128 | } 129 | 130 | fn image_position(&mut self, x: f64, y: f64) -> &mut Self { 131 | self.svg_builder.image_position(x, y); 132 | self 133 | } 134 | 135 | fn shape_color>(&mut self, shape: Shape, color: C) -> &mut Self { 136 | self.svg_builder.shape_color(shape, color); 137 | self 138 | } 139 | } 140 | 141 | impl ImageBuilder { 142 | /// Add a max-height boundary 143 | pub fn fit_height(&mut self, height: u32) -> &mut Self { 144 | self.fit_height = Some(height); 145 | self 146 | } 147 | 148 | /// Add a max-width boundary 149 | pub fn fit_width(&mut self, width: u32) -> &mut Self { 150 | self.fit_width = Some(width); 151 | self 152 | } 153 | 154 | // From https://github.com/RazrFalcon/resvg/blob/374a25f/crates/resvg/tests/integration/main.rs 155 | /// Return a pixmap containing the svg for a QRCode 156 | pub fn to_pixmap(&self, qr: &QRCode) -> Pixmap { 157 | let opt = usvg::Options::default(); 158 | 159 | // Do not unwrap on the from_data line, because panic will poison GLOBAL_OPT. 160 | let tree = { 161 | let svg_data = self.svg_builder.to_str(qr); 162 | let tree = usvg::Tree::from_data(svg_data.as_bytes(), &opt); 163 | tree.expect("Failed to parse SVG") 164 | }; 165 | 166 | let fit_to = match (self.fit_width, self.fit_height) { 167 | (Some(w), Some(h)) => usvg::FitTo::Size(w, h), 168 | (Some(w), None) => usvg::FitTo::Width(w), 169 | (None, Some(h)) => usvg::FitTo::Height(h), 170 | _ => usvg::FitTo::Original, 171 | }; 172 | 173 | let size = fit_to 174 | .fit_to(tree.size.to_screen_size()) 175 | .unwrap_or(tree.size.to_screen_size()); 176 | let mut pixmap = 177 | tiny_skia::Pixmap::new(size.width(), size.height()).expect("Failed to create pixmap"); 178 | resvg::render( 179 | &tree, 180 | fit_to, 181 | tiny_skia::Transform::default(), 182 | pixmap.as_mut(), 183 | ) 184 | .unwrap(); 185 | 186 | pixmap 187 | } 188 | 189 | /// Saves the image for a QRCode to a file 190 | pub fn to_file(&self, qr: &QRCode, file: &str) -> Result<(), ImageError> { 191 | use io::{Error, ErrorKind}; 192 | 193 | self.to_pixmap(qr) 194 | .save_png(file) 195 | .map_err(|err| ImageError::IoError(Error::new(ErrorKind::Other, err.to_string()))) 196 | } 197 | 198 | /// Saves the image for a QRCode in a byte buffer 199 | pub fn to_bytes(&self, qr: &QRCode) -> Result, ImageError> { 200 | let out = self.to_pixmap(qr); 201 | out.encode_png() 202 | .map_err(|err| ImageError::EncodingError(err.to_string())) 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/convert/mod.rs: -------------------------------------------------------------------------------- 1 | //! Converts a [`crate::QRCode`] to image or SVG you will need to activate associated feature flag 2 | 3 | #[cfg(feature = "svg")] 4 | #[cfg_attr(docsrs, doc(cfg(feature = "svg")))] 5 | pub mod svg; 6 | use core::ops::Deref; 7 | 8 | #[cfg(feature = "svg")] 9 | use svg::SvgError; 10 | 11 | #[cfg(feature = "image")] 12 | #[cfg_attr(docsrs, doc(cfg(feature = "image")))] 13 | pub mod image; 14 | #[cfg(feature = "image")] 15 | use image::ImageError; 16 | 17 | use crate::Module; 18 | 19 | /// Converts a position to a module svg 20 | /// # Example 21 | /// 22 | /// For the square shape, the svg is `M{x},{y}h1v1h-1` 23 | /// 24 | /// ```rust 25 | /// # use fast_qr::Module; 26 | /// fn square(y: usize, x: usize, _module: Module) -> String { 27 | /// format!("M{x},{y}h1v1h-1") 28 | /// } 29 | /// ``` 30 | pub type ModuleFunction = fn(usize, usize, Module) -> String; 31 | 32 | #[cfg(all(target_arch = "wasm32", feature = "wasm-bindgen"))] 33 | use wasm_bindgen::prelude::*; 34 | 35 | /// Different possible Shapes to represent modules in a [`crate::QRCode`] 36 | #[repr(C)] 37 | #[wasm_bindgen] 38 | #[cfg(feature = "wasm-bindgen")] 39 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)] 40 | pub enum Shape { 41 | /// Square Shape 42 | Square, 43 | /// Circle Shape 44 | Circle, 45 | /// RoundedSquare Shape 46 | RoundedSquare, 47 | /// Vertical Shape 48 | Vertical, 49 | /// Horizontal Shape 50 | Horizontal, 51 | /// Diamond Shape 52 | Diamond, 53 | } 54 | 55 | /// Different possible Shapes to represent modules in a [`crate::QRCode`] 56 | #[cfg(not(feature = "wasm-bindgen"))] 57 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)] 58 | pub enum Shape { 59 | /// Square Shape 60 | Square, 61 | /// Circle Shape 62 | Circle, 63 | /// RoundedSquare Shape 64 | RoundedSquare, 65 | /// Vertical Shape 66 | Vertical, 67 | /// Horizontal Shape 68 | Horizontal, 69 | /// Diamond Shape 70 | Diamond, 71 | /// Custom Shape with a function / closure 72 | /// # Example 73 | /// ```rust 74 | /// use fast_qr::convert::Shape; 75 | /// let command_function = |y, x, cell| { 76 | /// if x % 2 == 0 { 77 | /// // Works thanks to Deref 78 | /// Shape::Square(y, x, cell) 79 | /// } else { 80 | /// // Rectangle 81 | /// format!("M{x},{y}h1v.5h-1") 82 | /// } 83 | /// }; 84 | /// let command = Shape::Command(command_function); 85 | /// ``` 86 | /// 87 | /// 88 | /// 89 | /// 92 | /// 93 | Command(ModuleFunction), 94 | } 95 | 96 | impl From for usize { 97 | fn from(shape: Shape) -> Self { 98 | match shape { 99 | Shape::Square => 0, 100 | Shape::Circle => 1, 101 | Shape::RoundedSquare => 2, 102 | Shape::Vertical => 3, 103 | Shape::Horizontal => 4, 104 | Shape::Diamond => 5, 105 | #[cfg(not(feature = "wasm-bindgen"))] 106 | Shape::Command(_) => 6, 107 | } 108 | } 109 | } 110 | 111 | impl From for Shape { 112 | #[allow(clippy::match_same_arms)] 113 | fn from(shape: String) -> Self { 114 | match shape.to_lowercase().as_str() { 115 | "square" => Shape::Square, 116 | "circle" => Shape::Circle, 117 | "rounded_square" => Shape::RoundedSquare, 118 | "vertical" => Shape::Vertical, 119 | "horizontal" => Shape::Horizontal, 120 | "diamond" => Shape::Diamond, 121 | 122 | _ => Shape::Square, 123 | } 124 | } 125 | } 126 | 127 | impl From for &str { 128 | fn from(shape: Shape) -> Self { 129 | match shape { 130 | Shape::Square => "square", 131 | Shape::Circle => "circle", 132 | Shape::RoundedSquare => "rounded_square", 133 | Shape::Vertical => "vertical", 134 | Shape::Horizontal => "horizontal", 135 | Shape::Diamond => "diamond", 136 | #[cfg(not(feature = "wasm-bindgen"))] 137 | Shape::Command(_) => "command", 138 | } 139 | } 140 | } 141 | 142 | impl Shape { 143 | pub(crate) fn square(y: usize, x: usize, _: Module) -> String { 144 | format!("M{x},{y}h1v1h-1") 145 | } 146 | 147 | pub(crate) fn circle(y: usize, x: usize, _: Module) -> String { 148 | format!("M{},{y}.5a.5,.5 0 1,1 0,-.1", x + 1) 149 | } 150 | 151 | pub(crate) fn rounded_square(y: usize, x: usize, _: Module) -> String { 152 | format!("M{x}.2,{y}.2 {x}.8,{y}.2 {x}.8,{y}.8 {x}.2,{y}.8z") 153 | } 154 | 155 | pub(crate) fn horizontal(y: usize, x: usize, _: Module) -> String { 156 | format!("M{x},{y}.1h1v.8h-1") 157 | } 158 | 159 | pub(crate) fn vertical(y: usize, x: usize, _: Module) -> String { 160 | format!("M{x}.1,{y}h.8v1h-.8") 161 | } 162 | 163 | pub(crate) fn diamond(y: usize, x: usize, _: Module) -> String { 164 | format!("M{x}.5,{y}l.5,.5l-.5,.5l-.5,-.5z") 165 | } 166 | 167 | const FUNCTIONS: [ModuleFunction; 6] = [ 168 | Shape::square, 169 | Shape::circle, 170 | Shape::rounded_square, 171 | Shape::vertical, 172 | Shape::horizontal, 173 | Shape::diamond, 174 | ]; 175 | } 176 | 177 | impl Deref for Shape { 178 | type Target = ModuleFunction; 179 | 180 | fn deref(&self) -> &Self::Target { 181 | let index: usize = (*self).into(); 182 | match self { 183 | #[cfg(not(feature = "wasm-bindgen"))] 184 | Self::Command(func) => func, 185 | _ => &Self::FUNCTIONS[index], 186 | } 187 | } 188 | } 189 | 190 | /// Different possible image background shapes 191 | #[cfg_attr(feature = "wasm-bindgen", repr(C), wasm_bindgen)] 192 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)] 193 | pub enum ImageBackgroundShape { 194 | /// Square shape 195 | Square, 196 | /// Circle shape 197 | Circle, 198 | /// Rounded square shape 199 | RoundedSquare, 200 | } 201 | 202 | /// Contains possible errors for a conversion 203 | #[derive(Debug)] 204 | pub enum ConvertError { 205 | /// Contains error message for a SVG conversion 206 | #[cfg(feature = "svg")] 207 | #[cfg_attr(docsrs, doc(cfg(feature = "svg")))] 208 | Svg(String), 209 | /// Contains error message for an Image conversion 210 | #[cfg(feature = "image")] 211 | #[cfg_attr(docsrs, doc(cfg(feature = "image")))] 212 | Image(String), 213 | /// Contains error message if a file write failed 214 | Io(std::io::Error), 215 | } 216 | 217 | #[cfg(feature = "svg")] 218 | #[cfg_attr(docsrs, doc(cfg(feature = "svg")))] 219 | impl From for ConvertError { 220 | fn from(err: SvgError) -> Self { 221 | match err { 222 | SvgError::SvgError(svg_err) => Self::Svg(svg_err), 223 | #[cfg(not(feature = "wasm-bindgen"))] 224 | SvgError::IoError(io_err) => Self::Io(io_err), 225 | } 226 | } 227 | } 228 | 229 | #[cfg(feature = "image")] 230 | #[cfg_attr(docsrs, doc(cfg(feature = "image")))] 231 | impl From for ConvertError { 232 | fn from(err: ImageError) -> Self { 233 | match err { 234 | ImageError::EncodingError(image_err) => Self::Image(image_err), 235 | ImageError::ImageError(image_err) => Self::Image(image_err), 236 | ImageError::IoError(io_err) => Self::Io(io_err), 237 | } 238 | } 239 | } 240 | 241 | /// Converts an array of pixel color to it's hexadecimal representation 242 | /// # Example 243 | /// ```rust 244 | /// # use fast_qr::convert::rgba2hex; 245 | /// let color = [0, 0, 0, 255]; 246 | /// assert_eq!(&rgba2hex(color), "#000000"); 247 | /// ``` 248 | #[must_use] 249 | pub fn rgba2hex(color: [u8; 4]) -> String { 250 | let mut hex = String::with_capacity(9); 251 | 252 | hex.push('#'); 253 | hex.push_str(&format!("{:02x}", color[0])); 254 | hex.push_str(&format!("{:02x}", color[1])); 255 | hex.push_str(&format!("{:02x}", color[2])); 256 | if color[3] != 255 { 257 | hex.push_str(&format!("{:02x}", color[3])); 258 | } 259 | 260 | hex 261 | } 262 | 263 | /// Allows to take String, string slices, arrays or slices of u8 (3 or 4) to create a [Color] 264 | pub struct Color(pub String); 265 | 266 | impl Color { 267 | /// Returns the contained color 268 | #[must_use] 269 | pub fn to_str(&self) -> &str { 270 | &self.0 271 | } 272 | } 273 | 274 | impl From for Color { 275 | fn from(color: String) -> Self { 276 | Self(color) 277 | } 278 | } 279 | 280 | impl From<&str> for Color { 281 | fn from(color: &str) -> Self { 282 | Self(color.to_string()) 283 | } 284 | } 285 | 286 | impl From<[u8; 4]> for Color { 287 | fn from(color: [u8; 4]) -> Self { 288 | Self(rgba2hex(color)) 289 | } 290 | } 291 | 292 | impl From<[u8; 3]> for Color { 293 | fn from(color: [u8; 3]) -> Self { 294 | Self::from([color[0], color[1], color[2], 255]) 295 | } 296 | } 297 | 298 | impl From<&[u8]> for Color { 299 | fn from(color: &[u8]) -> Self { 300 | if color.len() == 3 { 301 | Self::from([color[0], color[1], color[2]]) 302 | } else if color.len() == 4 { 303 | Self::from([color[0], color[1], color[2], color[3]]) 304 | } else { 305 | panic!("Invalid color length"); 306 | } 307 | } 308 | } 309 | 310 | impl From> for Color { 311 | fn from(color: Vec) -> Self { 312 | Self::from(&color[..]) 313 | } 314 | } 315 | 316 | /// Trait for `SvgBuilder` and `ImageBuilder` 317 | pub trait Builder { 318 | /// Updates margin (default: 4) 319 | fn margin(&mut self, margin: usize) -> &mut Self; 320 | /// Updates module color (default: #000000) 321 | fn module_color>(&mut self, module_color: C) -> &mut Self; 322 | /// Updates background color (default: #FFFFFF) 323 | fn background_color>(&mut self, background_color: C) -> &mut Self; 324 | /// Adds a shape to the shapes list 325 | fn shape(&mut self, shape: Shape) -> &mut Self; 326 | /// Add a shape to the shapes list with a specific color 327 | fn shape_color>(&mut self, shape: Shape, color: C) -> &mut Self; 328 | 329 | // Manages the image part 330 | 331 | /// Provides the image path or an base64 encoded image 332 | fn image(&mut self, image: String) -> &mut Self; 333 | /// Updates the image background color (default: #FFFFFF) 334 | fn image_background_color>(&mut self, image_background_color: C) -> &mut Self; 335 | /// Updates the image background shape (default: Square) 336 | fn image_background_shape(&mut self, image_background_shape: ImageBackgroundShape) 337 | -> &mut Self; 338 | /// Updates the image size and the gap between the image and the [`crate::QRCode`] 339 | /// Default is around 30% of the [`crate::QRCode`] size 340 | fn image_size(&mut self, image_size: f64) -> &mut Self; 341 | /// Updates the gap between the image and the [`crate::QRCode`] 342 | fn image_gap(&mut self, gap: f64) -> &mut Self; 343 | /// Updates the image position, anchor is the center of the image. Default is the center of the [`crate::QRCode`] 344 | fn image_position(&mut self, x: f64, y: f64) -> &mut Self; 345 | } 346 | -------------------------------------------------------------------------------- /src/convert/svg.rs: -------------------------------------------------------------------------------- 1 | //! Converts [`QRCode`] to SVG 2 | //! 3 | //! ```rust 4 | //! use fast_qr::convert::ConvertError; 5 | //! use fast_qr::convert::{svg::SvgBuilder, Builder, Shape}; 6 | //! use fast_qr::qr::QRBuilder; 7 | //! 8 | //! # fn main() -> Result<(), ConvertError> { 9 | //! // QRBuilde::new can fail if content is too big for version, 10 | //! // please check before unwrapping. 11 | //! let qrcode = QRBuilder::new("https://example.com/") 12 | //! .build() 13 | //! .unwrap(); 14 | //! 15 | //! let _svg = SvgBuilder::default() 16 | //! .shape(Shape::RoundedSquare) 17 | //! .to_file(&qrcode, "out.svg"); 18 | //! 19 | //! # std::fs::remove_file("out.svg"); 20 | //! # Ok(()) 21 | //! # } 22 | //! ``` 23 | 24 | use crate::{QRCode, Version}; 25 | 26 | use super::{Builder, Color, ImageBackgroundShape, ModuleFunction, Shape}; 27 | 28 | /// Builder for svg, can set shape, margin, background_color, dot_color 29 | pub struct SvgBuilder { 30 | /// Command vector allows predefined or custom shapes 31 | /// The default is square, commands can be added using `.shape()` 32 | commands: Vec, 33 | /// Commands can also have a custom color 34 | /// The default is `dot_color`, commands with specific colors can be 35 | /// added using `.shape_color()` 36 | command_colors: Vec>, 37 | /// The margin for the svg, default is 4 38 | margin: usize, 39 | /// The background color for the svg, default is #FFFFFF 40 | background_color: Color, 41 | /// The color for each module, default is #000000 42 | dot_color: Color, 43 | 44 | // Image Embedding 45 | /// Image to embed in the svg, can be a path or a base64 string 46 | image: Option, 47 | /// Background color for the image, default is #FFFFFF 48 | image_background_color: Color, 49 | /// Background shape for the image, default is square 50 | image_background_shape: ImageBackgroundShape, 51 | /// Size of the image (in module size), default is ~1/3 of the svg 52 | image_size: Option, 53 | /// Gap between the image and the border (in module size), default is calculated 54 | image_gap: Option, 55 | /// Position of the image, default is center 56 | image_position: Option<(f64, f64)>, 57 | } 58 | 59 | #[derive(Debug)] 60 | /// Possible errors when converting to SVG 61 | pub enum SvgError { 62 | /// Error while writing file 63 | #[cfg(not(feature = "wasm-bindgen"))] 64 | IoError(std::io::Error), 65 | /// Error while creating svg 66 | SvgError(String), 67 | } 68 | 69 | /// Creates a Builder instance 70 | impl Default for SvgBuilder { 71 | fn default() -> Self { 72 | SvgBuilder { 73 | background_color: [255; 4].into(), 74 | dot_color: [0, 0, 0, 255].into(), 75 | margin: 4, 76 | commands: Vec::new(), 77 | command_colors: Vec::new(), 78 | 79 | // Image Embedding 80 | image: None, 81 | image_background_color: [255; 4].into(), 82 | image_background_shape: ImageBackgroundShape::Square, 83 | image_size: None, 84 | image_gap: None, 85 | image_position: None, 86 | } 87 | } 88 | } 89 | 90 | impl Builder for SvgBuilder { 91 | fn margin(&mut self, margin: usize) -> &mut Self { 92 | self.margin = margin; 93 | self 94 | } 95 | 96 | fn module_color>(&mut self, dot_color: C) -> &mut Self { 97 | self.dot_color = dot_color.into(); 98 | self 99 | } 100 | 101 | fn background_color>(&mut self, background_color: C) -> &mut Self { 102 | self.background_color = background_color.into(); 103 | self 104 | } 105 | 106 | fn shape(&mut self, shape: Shape) -> &mut Self { 107 | self.commands.push(*shape); 108 | self.command_colors.push(None); 109 | self 110 | } 111 | 112 | fn shape_color>(&mut self, shape: Shape, color: C) -> &mut Self { 113 | self.commands.push(*shape); 114 | self.command_colors.push(Some(color.into())); 115 | self 116 | } 117 | 118 | fn image(&mut self, image: String) -> &mut Self { 119 | self.image = Some(image); 120 | self 121 | } 122 | 123 | fn image_background_color>(&mut self, image_background_color: C) -> &mut Self { 124 | self.image_background_color = image_background_color.into(); 125 | self 126 | } 127 | 128 | fn image_background_shape( 129 | &mut self, 130 | image_background_shape: ImageBackgroundShape, 131 | ) -> &mut Self { 132 | self.image_background_shape = image_background_shape; 133 | self 134 | } 135 | 136 | fn image_size(&mut self, image_size: f64) -> &mut Self { 137 | self.image_size = Some(image_size); 138 | self 139 | } 140 | 141 | fn image_gap(&mut self, gap: f64) -> &mut Self { 142 | self.image_gap = Some(gap); 143 | self 144 | } 145 | 146 | fn image_position(&mut self, x: f64, y: f64) -> &mut Self { 147 | self.image_position = Some((x, y)); 148 | self 149 | } 150 | } 151 | 152 | impl SvgBuilder { 153 | fn image_placement(image_background_shape: ImageBackgroundShape, n: usize) -> (f64, f64) { 154 | use ImageBackgroundShape::{Circle, RoundedSquare, Square}; 155 | 156 | #[rustfmt::skip] 157 | const SQUARE: [f64; 40] = [ 158 | 5f64, 9f64, 9f64, 11f64, 13f64, 159 | 13f64, 15f64, 17f64, 17f64, 19f64, 160 | 21f64, 21f64, 23f64, 25f64, 25f64, 161 | 27f64, 29f64, 29f64, 31f64, 33f64, 162 | 33f64, 35f64, 37f64, 37f64, 39f64, 163 | 41f64, 41f64, 43f64, 45f64, 45f64, 164 | 47f64, 49f64, 49f64, 51f64, 53f64, 165 | 53f64, 55f64, 57f64, 57f64, 59f64, 166 | ]; 167 | const ROUNDED_SQUARE: [f64; 40] = SQUARE; 168 | const CIRCLE: [f64; 40] = SQUARE; 169 | 170 | // Using hardcoded values 171 | let version = Version::from_n(n) as usize; 172 | let border_size = match image_background_shape { 173 | Square => SQUARE[version], 174 | RoundedSquare => ROUNDED_SQUARE[version], 175 | Circle => CIRCLE[version], 176 | }; 177 | 178 | // Allows for a module gap between the image and the border 179 | let gap = match image_background_shape { 180 | Square | RoundedSquare => 2f64, 181 | Circle => 3f64, 182 | }; 183 | // Make the image border bigger for bigger versions 184 | let gap = gap * (version + 10) as f64 / 10f64; 185 | (border_size, (border_size - gap).round()) 186 | } 187 | 188 | fn image(&self, n: usize) -> String { 189 | if self.image.is_none() { 190 | return String::new(); 191 | } 192 | 193 | let image = self.image.as_ref().unwrap(); 194 | let mut out = String::with_capacity(image.len() + 100); 195 | 196 | let (mut border_size, mut image_size) = 197 | Self::image_placement(self.image_background_shape, n); 198 | 199 | if let Some(override_size) = self.image_size { 200 | let gap = -(image_size - border_size); 201 | border_size = override_size + gap; 202 | image_size = override_size; 203 | } 204 | 205 | if let Some(override_gap) = self.image_gap { 206 | border_size = image_size + override_gap * 2f64; 207 | } 208 | 209 | let mut placed_coord_x = (self.margin * 2 + n) as f64 - border_size; 210 | 211 | // Adjust for non-integer initial x coordinates so as not to partially cover bits by rounding down. 212 | if placed_coord_x % 2f64 != 0f64 { 213 | placed_coord_x += 1f64; 214 | border_size -= 1f64; 215 | } 216 | 217 | placed_coord_x = placed_coord_x / 2f64; 218 | 219 | let mut placed_coord = (placed_coord_x, placed_coord_x); 220 | 221 | if let Some((x, y)) = self.image_position { 222 | placed_coord = (x - border_size / 2f64, y - border_size / 2f64); 223 | } 224 | 225 | let format = match self.image_background_shape { 226 | ImageBackgroundShape::Square => { 227 | r#""# 228 | } 229 | ImageBackgroundShape::Circle => { 230 | r#""# 231 | } 232 | ImageBackgroundShape::RoundedSquare => { 233 | r#""# 234 | } 235 | }; 236 | 237 | let format = format 238 | .replace("{0}", &placed_coord.0.to_string()) 239 | .replace("{1}", &placed_coord.1.to_string()) 240 | .replace("{2}", &border_size.to_string()) 241 | .replace("{3}", &self.image_background_color.to_str()); 242 | 243 | out.push_str(&format); 244 | 245 | out.push_str(&format!( 246 | r#""#, 247 | placed_coord.0 + (border_size - image_size) / 2f64, 248 | placed_coord.1 + (border_size - image_size) / 2f64, 249 | image_size, 250 | image 251 | )); 252 | 253 | out 254 | } 255 | 256 | fn path(&self, qr: &QRCode) -> String { 257 | const DEFAULT_COMMAND: [ModuleFunction; 1] = [Shape::square]; 258 | const DEFAULT_COMMAND_COLOR: [Option; 1] = [None]; 259 | 260 | // TODO: cleanup this basic logic 261 | let command_colors: &[Option] = if !self.commands.is_empty() { 262 | &self.command_colors 263 | } else { 264 | &DEFAULT_COMMAND_COLOR 265 | }; 266 | let commands: &[ModuleFunction] = if !self.commands.is_empty() { 267 | &self.commands 268 | } else { 269 | &DEFAULT_COMMAND 270 | }; 271 | 272 | let mut paths = vec![String::with_capacity(10 * qr.size * qr.size); commands.len()]; 273 | for path in paths.iter_mut() { 274 | path.push_str(r#""#, command_color.to_str())); 302 | } 303 | 304 | paths.join("") 305 | } 306 | 307 | /// Return a string containing the svg for a qr code 308 | pub fn to_str(&self, qr: &QRCode) -> String { 309 | let n = qr.size; 310 | 311 | let mut out = String::with_capacity(11 * n * n / 2); 312 | out.push_str(&format!( 313 | r#""#, 314 | self.margin * 2 + n 315 | )); 316 | 317 | out.push_str(&format!( 318 | r#""#, 319 | self.margin * 2 + n, 320 | self.background_color.to_str() 321 | )); 322 | 323 | out.push_str(&self.path(qr)); 324 | out.push_str(&self.image(n)); 325 | 326 | out.push_str(""); 327 | out 328 | } 329 | 330 | /// Saves the svg for a qr code to a file 331 | #[cfg(not(feature = "wasm-bindgen"))] 332 | pub fn to_file(&self, qr: &QRCode, file: &str) -> Result<(), SvgError> { 333 | use std::fs::File; 334 | use std::io::Write; 335 | 336 | let out = self.to_str(qr); 337 | 338 | let mut f = File::create(file).map_err(SvgError::IoError)?; 339 | f.write_all(out.as_bytes()).map_err(SvgError::IoError)?; 340 | 341 | Ok(()) 342 | } 343 | } 344 | -------------------------------------------------------------------------------- /src/datamasking.rs: -------------------------------------------------------------------------------- 1 | //! Contains the HEIGHT functions that can alter `QRCode` 2 | #![deny(unsafe_code)] 3 | #![warn(missing_docs)] 4 | 5 | use crate::module::ModuleType; 6 | use crate::QRCode; 7 | 8 | /// The different mask patterns. The mask pattern should only be applied to 9 | /// the data and error correction portion of the QR code. 10 | #[derive(Debug, Copy, Clone)] 11 | pub enum Mask { 12 | /// QR code pattern n°0: `(x + y) % 2 == 0`. 13 | Checkerboard = 0, 14 | /// QR code pattern n°1: `y % 2 == 0`. 15 | HorizontalLines = 1, 16 | /// QR code pattern n°2: `x % 3 == 0`. 17 | VerticalLines = 2, 18 | /// QR code pattern n°3: `(x + y) % 3 == 0`. 19 | DiagonalLines = 3, 20 | /// QR code pattern n°4: `((x/3) + (y/2)) % 2 == 0`. 21 | LargeCheckerboard = 4, 22 | /// QR code pattern n°5: `(x*y)%2 + (x*y)%3 == 0`. 23 | Fields = 5, 24 | /// QR code pattern n°6: `((x*y)%2 + (x*y)%3) % 2 == 0`. 25 | Diamonds = 6, 26 | /// QR code pattern n°7: `((x+y)%2 + (x*y)%3) % 2 == 0`. 27 | Meadow = 7, 28 | } 29 | 30 | /// Mask function nb°**0**, `Mask::Checkerboard`. 31 | fn mask_checkerboard(qr: &mut QRCode) { 32 | for row in 0..qr.size { 33 | for column in (row & 1..qr.size).step_by(2) { 34 | let module = &mut qr[row][column]; 35 | if module.module_type() == ModuleType::Data { 36 | module.toggle(); 37 | } 38 | } 39 | } 40 | } 41 | 42 | /// Mask function nb°**1**, `Mask::HorizontalLines`. 43 | fn mask_horizontal(qr: &mut QRCode) { 44 | for row in (0..qr.size).step_by(2) { 45 | for column in 0..qr.size { 46 | let module = &mut qr[row][column]; 47 | if module.module_type() == ModuleType::Data { 48 | module.toggle(); 49 | } 50 | } 51 | } 52 | } 53 | 54 | /// Mask function nb°**2**, `Mask::VerticalLines`. 55 | fn mask_vertical(qr: &mut QRCode) { 56 | for row in 0..qr.size { 57 | for column in (0..qr.size).step_by(3) { 58 | let module = &mut qr[row][column]; 59 | if module.module_type() == ModuleType::Data { 60 | module.toggle(); 61 | } 62 | } 63 | } 64 | } 65 | 66 | /// Mask function nb°**3**, `Mask::DiagonalLines`. 67 | fn mask_diagonal(qr: &mut QRCode) { 68 | for row in 0..qr.size { 69 | let start = (3 - row % 3) % 3; 70 | for column in (start..qr.size).step_by(3) { 71 | let module = &mut qr[row][column]; 72 | if module.module_type() == ModuleType::Data { 73 | module.toggle(); 74 | } 75 | } 76 | } 77 | } 78 | 79 | /// Mask function nb°**4**, `Mask::LargeCheckerboard`. 80 | fn mask_large_checkerboard(qr: &mut QRCode) { 81 | for row in 0..qr.size { 82 | let start = ((row >> 1) & 1) * 3; // ((row / 2) % 2) * 3; 83 | for column in (start..qr.size).step_by(6) { 84 | for i in column..core::cmp::min(qr.size, column + 3) { 85 | let module = &mut qr[row][i]; 86 | if module.module_type() == ModuleType::Data { 87 | module.toggle(); 88 | } 89 | } 90 | } 91 | } 92 | } 93 | 94 | fn mask_5_6(qr: &mut QRCode, offset: &[(usize, usize)]) { 95 | for row in (0..qr.size).step_by(6) { 96 | for column in 0..qr.size { 97 | let module = &mut qr[row][column]; 98 | if module.module_type() == ModuleType::Data { 99 | module.toggle(); 100 | } 101 | let module = &mut qr[column][row]; 102 | if module.module_type() == ModuleType::Data && (row % 6 != 0 || column % 6 != 0) { 103 | module.toggle(); 104 | } 105 | } 106 | } 107 | 108 | for row in (0..qr.size).step_by(6) { 109 | for column in (0..qr.size).step_by(6) { 110 | for (y, x) in offset { 111 | if row + y >= qr.size || column + x >= qr.size { 112 | continue; 113 | } 114 | 115 | let module = &mut qr[row + y][column + x]; 116 | if module.module_type() == ModuleType::Data { 117 | module.toggle(); 118 | } 119 | } 120 | } 121 | } 122 | } 123 | 124 | /// Mask function nb°**5**, `Mask::Fields`. 125 | fn mask_field(qr: &mut QRCode) { 126 | const OFFSETS: [(usize, usize); 4] = [(2, 3), (3, 2), (3, 4), (4, 3)]; 127 | mask_5_6(qr, &OFFSETS); 128 | } 129 | 130 | /// Mask function nb°**6**, `Mask::Diamonds`. 131 | fn mask_diamond(qr: &mut QRCode) { 132 | #[rustfmt::skip] 133 | const OFFSETS: [(usize, usize); 12] = [ 134 | (1, 1), (1, 2), (2, 1), (2, 3), 135 | (2, 4), (3, 2), (3, 4), (4, 2), 136 | (4, 3), (4, 5), (5, 4), (5, 5) 137 | ]; 138 | mask_5_6(qr, &OFFSETS); 139 | } 140 | 141 | /// Mask function nb°**7**, `Mask::Meadow`. 142 | fn mask_meadow(qr: &mut QRCode) { 143 | for row in 0..qr.size { 144 | for column in row..qr.size { 145 | if (((row + column) % 2) + ((row * column) % 3)) % 2 != 0 { 146 | continue; 147 | } 148 | 149 | let module = &mut qr[row][column]; 150 | if module.module_type() == ModuleType::Data { 151 | module.toggle(); 152 | } 153 | 154 | let module = &mut qr[column][row]; 155 | if column != row && module.module_type() == ModuleType::Data { 156 | module.toggle(); 157 | } 158 | } 159 | } 160 | } 161 | 162 | /// Applies the function at `mask_nb` on `mat` 163 | pub fn mask(qr: &mut QRCode, mask: Mask) { 164 | match mask { 165 | Mask::Checkerboard => mask_checkerboard(qr), 166 | Mask::HorizontalLines => mask_horizontal(qr), 167 | Mask::VerticalLines => mask_vertical(qr), 168 | Mask::DiagonalLines => mask_diagonal(qr), 169 | Mask::LargeCheckerboard => mask_large_checkerboard(qr), 170 | Mask::Fields => mask_field(qr), 171 | Mask::Diamonds => mask_diamond(qr), 172 | Mask::Meadow => mask_meadow(qr), 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/default.rs: -------------------------------------------------------------------------------- 1 | //! Creates the default empty `QRCode` (no data) 2 | #![deny(unsafe_code)] 3 | #![warn(missing_docs)] 4 | 5 | use crate::datamasking::Mask; 6 | use crate::module::Module; 7 | use crate::version::Version; 8 | use crate::{hardcode, QRCode, ECL}; 9 | 10 | /// Size of FIP (Finder Patterns) 11 | const POSITION_SIZE: usize = 7; 12 | 13 | pub fn transpose(qr: &QRCode) -> QRCode { 14 | let mut transpose = qr.clone(); 15 | 16 | for i in 0..qr.size { 17 | for j in i + 1..qr.size { 18 | transpose[i][j] = qr[j][i]; 19 | transpose[j][i] = qr[i][j]; 20 | } 21 | } 22 | 23 | transpose 24 | } 25 | 26 | pub fn create_matrix(version: Version) -> QRCode { 27 | let size = version.size(); 28 | let mut qr = QRCode::default(size); 29 | 30 | create_matrix_pattern(&mut qr); 31 | create_matrix_timing(&mut qr); 32 | create_matrix_dark_module(&mut qr); 33 | create_matrix_alignments(&mut qr, version); 34 | create_matrix_version_info(&mut qr, version); 35 | create_matrix_empty(&mut qr); 36 | 37 | let n: usize = qr.size; 38 | 39 | // Format information is not placed on the matrix yet 40 | // But we fill it anyway with garbage data to make it easier for placement 41 | { 42 | if (version as usize) < (Version::V01 as usize) { 43 | return qr; 44 | } 45 | 46 | for i in 0..=5 { 47 | // Top left 48 | qr[8][i] = Module::format(Module::LIGHT); 49 | qr[i][8] = Module::format(Module::LIGHT); 50 | 51 | // Top right 52 | qr[8][n - 1 - i] = Module::format(Module::LIGHT); 53 | 54 | // Bottom left 55 | qr[n - 1 - i][8] = Module::format(Module::LIGHT); 56 | } 57 | 58 | // Top left 59 | qr[8][7] = Module::format(Module::LIGHT); 60 | qr[8][8] = Module::format(Module::LIGHT); 61 | qr[7][8] = Module::format(Module::LIGHT); 62 | 63 | // Top right 64 | qr[8][n - 1 - 6] = Module::format(Module::LIGHT); 65 | qr[8][n - 1 - 7] = Module::format(Module::LIGHT); 66 | 67 | // Bottom left 68 | qr[n - 1 - 6][8] = Module::format(Module::LIGHT); 69 | } 70 | 71 | qr 72 | } 73 | 74 | /// Adds the 3 needed squares 75 | pub fn create_matrix_pattern(qr: &mut QRCode) { 76 | let length = qr.size; 77 | let offsets = [ 78 | (0, 0), 79 | (length - POSITION_SIZE, 0), 80 | (0, length - POSITION_SIZE), 81 | ]; 82 | 83 | // Required pattern (4.1 Positions) 84 | for (y, x) in offsets { 85 | // Border 86 | for j in 0..=6 { 87 | qr[y][j + x] = Module::finder_pattern(Module::DARK); 88 | qr[6 + y][j + x] = Module::finder_pattern(Module::DARK); 89 | 90 | qr[j + y][x] = Module::finder_pattern(Module::DARK); 91 | qr[j + y][6 + x] = Module::finder_pattern(Module::DARK); 92 | } 93 | 94 | for j in 1..=5 { 95 | qr[y + 1][j + x] = Module::finder_pattern(Module::LIGHT); 96 | qr[5 + y][j + x] = Module::finder_pattern(Module::LIGHT); 97 | 98 | qr[j + y][x + 1] = Module::finder_pattern(Module::LIGHT); 99 | qr[j + y][5 + x] = Module::finder_pattern(Module::LIGHT); 100 | } 101 | 102 | for j in 2..=4 { 103 | qr[j + y][2 + x] = Module::finder_pattern(Module::DARK); 104 | qr[j + y][3 + x] = Module::finder_pattern(Module::DARK); 105 | qr[j + y][4 + x] = Module::finder_pattern(Module::DARK); 106 | } 107 | } 108 | } 109 | 110 | /// Adds the two lines of Timing patterns 111 | pub fn create_matrix_timing(qr: &mut QRCode) { 112 | let length = qr.size; 113 | // Required pattern (4.3 Timing) 114 | for i in POSITION_SIZE + 1..length - POSITION_SIZE { 115 | let value = if (POSITION_SIZE + 1) % 2 == i % 2 { 116 | Module::DARK 117 | } else { 118 | Module::LIGHT 119 | }; 120 | 121 | qr[POSITION_SIZE - 1][i] = Module::timing(value); 122 | qr[i][POSITION_SIZE - 1] = Module::timing(value); 123 | } 124 | } 125 | 126 | /// Adds the forever present pixel 127 | pub fn create_matrix_dark_module(qr: &mut QRCode) { 128 | // Dark module 129 | let n: usize = qr.size; 130 | qr[n - 8][8] = Module::dark(Module::DARK); 131 | } 132 | 133 | /// Adds the smaller squares if needed 134 | pub fn create_matrix_alignments(qr: &mut QRCode, version: Version) { 135 | if let Version::V01 = version { 136 | return; 137 | } 138 | 139 | // Alignments (smaller cubes) 140 | let alignment_patterns = version.alignment_patterns_grid(); 141 | let max = alignment_patterns.len() - 1; 142 | 143 | for (i, &alignment_y) in alignment_patterns.iter().enumerate() { 144 | for (j, &alignment_x) in alignment_patterns.iter().enumerate() { 145 | if i == 0 && (j == max || j == 0) || (i == max && j == 0) { 146 | continue; 147 | } 148 | 149 | let y = alignment_y - 2; 150 | let x = alignment_x - 2; 151 | 152 | for offset in 0..=4 { 153 | qr[y][x + offset] = Module::alignment(Module::DARK); 154 | qr[y + 4][x + offset] = Module::alignment(Module::DARK); 155 | 156 | qr[y + offset][x] = Module::alignment(Module::DARK); 157 | qr[y + offset][x + 4] = Module::alignment(Module::DARK); 158 | } 159 | 160 | let y = alignment_y - 1; 161 | let x = alignment_x - 1; 162 | 163 | for offset in 0..=2 { 164 | qr[y][x + offset] = Module::alignment(Module::LIGHT); 165 | qr[y + 2][x + offset] = Module::alignment(Module::LIGHT); 166 | 167 | qr[y + offset][x] = Module::alignment(Module::LIGHT); 168 | qr[y + offset][x + 2] = Module::alignment(Module::LIGHT); 169 | } 170 | 171 | qr[alignment_y][alignment_x] = Module::alignment(Module::DARK); 172 | } 173 | } 174 | } 175 | 176 | /// Adds the version information if needed 177 | pub fn create_matrix_version_info(qr: &mut QRCode, version: Version) { 178 | if (version as usize) < (Version::V07 as usize) { 179 | return; 180 | } 181 | 182 | let version_info = version.information(); 183 | 184 | let n: usize = qr.size; 185 | 186 | for i in 0..=2 { 187 | for j in 0..=5 { 188 | let shift_i = 2 - i; 189 | let shift_j = 5 - j; 190 | let shift: u32 = 1 << ((5 - shift_j) * 3 + (2 - shift_i)); 191 | 192 | let value = (version_info & shift) != 0; 193 | qr[j][n - 11 + i] = Module::version(value); 194 | qr[n - 11 + i][j] = Module::version(value); 195 | } 196 | } 197 | } 198 | 199 | /// Adds the format information if needed 200 | pub fn create_matrix_format_info(qr: &mut QRCode, quality: ECL, mask: Mask) { 201 | let format_info = hardcode::ecm_to_format_information(quality, mask); 202 | 203 | let n: usize = qr.size; 204 | 205 | for i in (0..=5).rev() { 206 | let shift = 1 << (i + 9); 207 | let value = (format_info & shift) != 0; 208 | qr[8][5 - i] = Module::format(value); 209 | qr[n - 6 + i][8] = Module::format(value); 210 | } 211 | 212 | for i in 0..=5 { 213 | let shift = 1 << i; 214 | let value = (format_info & shift) != 0; 215 | qr[i][8] = Module::format(value); 216 | qr[8][n - i - 1] = Module::format(value); 217 | } 218 | 219 | { 220 | let shift = 1 << 8; 221 | let value = (format_info & shift) != 0; 222 | // Six on left 223 | qr[8][7] = Module::format(value); 224 | // Six on bottom 225 | qr[n - 7][8] = Module::format(value); 226 | } 227 | { 228 | let shift = 1 << 7; 229 | let value = (format_info & shift) != 0; 230 | // Seven on left 231 | qr[8][8] = Module::format(value); 232 | // Seven on right 233 | qr[8][n - 8] = Module::format(value); 234 | } 235 | { 236 | let shift = 1 << 6; 237 | let value = (format_info & shift) != 0; 238 | // Height on left 239 | qr[7][8] = Module::format(value); 240 | // Height on right 241 | qr[8][n - 7] = Module::format(value); 242 | } 243 | } 244 | 245 | /// Adds the space between finder patterns and data 246 | fn create_matrix_empty(qr: &mut QRCode) { 247 | let n: usize = qr.size; 248 | 249 | for i in 0..=7 { 250 | // Top left 251 | qr[i][7] = Module::empty(Module::LIGHT); 252 | qr[7][i] = Module::empty(Module::LIGHT); 253 | 254 | // Bottom left 255 | qr[n - 8 + i][7] = Module::empty(Module::LIGHT); 256 | qr[n - 8][i] = Module::empty(Module::LIGHT); 257 | 258 | // Top right 259 | qr[i][n - 8] = Module::empty(Module::LIGHT); 260 | qr[7][n - 8 + i] = Module::empty(Module::LIGHT); 261 | } 262 | } 263 | 264 | #[cfg(test)] 265 | pub fn create_mat_from_bool(bool_mat: &[[bool; N]; N]) -> QRCode { 266 | let mut mat = create_matrix(Version::from_n(N)); 267 | 268 | for (i, row) in bool_mat.iter().enumerate() { 269 | for (j, &value) in row.iter().enumerate() { 270 | mat[i][j].set(value); 271 | } 272 | } 273 | 274 | mat 275 | } 276 | -------------------------------------------------------------------------------- /src/ecl.rs: -------------------------------------------------------------------------------- 1 | //! Contains all different levels of quality. 2 | //! And allows to find easily max bits per version/quality pair 3 | 4 | #![deny(unsafe_code)] 5 | #![warn(missing_docs)] 6 | 7 | use std::fmt::Write; 8 | 9 | /// Error Correction Coding has 4 levels 10 | #[derive(Copy, Clone, Debug)] 11 | #[allow(dead_code)] 12 | #[cfg_attr(feature = "wasm-bindgen", wasm_bindgen::prelude::wasm_bindgen)] 13 | pub enum ECL { 14 | /// Low, 7% 15 | L, 16 | /// Medium, 15% 17 | M, 18 | /// Quartile, 25% 19 | Q, 20 | /// High, 30% 21 | H, 22 | } 23 | 24 | impl core::fmt::Display for ECL { 25 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 26 | match self { 27 | ECL::L => f.write_char('L'), 28 | ECL::M => f.write_char('M'), 29 | ECL::Q => f.write_char('Q'), 30 | ECL::H => f.write_char('H'), 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/encode.rs: -------------------------------------------------------------------------------- 1 | //! Contains all functions required to encode any string as a `QRCode` 2 | 3 | #![deny(unsafe_code)] 4 | #![warn(missing_docs)] 5 | 6 | use crate::compact::CompactQR; 7 | use crate::ecl::ECL; 8 | use crate::hardcode; 9 | use crate::version::Version; 10 | 11 | /// Enum for the 3 encoding mode 12 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 13 | pub enum Mode { 14 | /// Numeric mode (0-9 only) 15 | Numeric, 16 | /// Alphanumeric mode (0-9, A-Z, $%*./:+-?.= [space]) 17 | Alphanumeric, 18 | /// Byte mode (any) 19 | Byte, 20 | } 21 | 22 | /// Encodes the string according the mode and version 23 | pub fn encode(input: &[u8], ecl: ECL, mode: Mode, version: Version) -> CompactQR { 24 | let cci_bits = hardcode::cci_bits(version, mode); 25 | 26 | let mut compact = CompactQR::from_version(version); 27 | 28 | match mode { 29 | Mode::Numeric => encode_numeric(&mut compact, input, cci_bits), 30 | Mode::Alphanumeric => encode_alphanumeric(&mut compact, input, cci_bits), 31 | Mode::Byte => encode_byte(&mut compact, input, cci_bits), 32 | }; 33 | 34 | let data_bits = hardcode::data_bits(version, ecl); 35 | 36 | add_terminator(&mut compact, data_bits); 37 | pad_to_8(&mut compact); 38 | compact.fill(); 39 | 40 | compact 41 | } 42 | 43 | /// Find the best encoding (Numeric -> Alnum -> Byte) 44 | pub fn best_encoding(input: &[u8]) -> Mode { 45 | fn try_encode_numeric(input: &[u8], i: usize) -> Mode { 46 | for &c in input.iter().skip(i) { 47 | if !c.is_ascii_digit() { 48 | return try_encode_alphanumeric(input, i); 49 | } 50 | } 51 | Mode::Numeric 52 | } 53 | 54 | fn try_encode_alphanumeric(input: &[u8], i: usize) -> Mode { 55 | for &c in input.iter().skip(i) { 56 | if !is_qr_alphanumeric(c) { 57 | return Mode::Byte; 58 | } 59 | } 60 | Mode::Alphanumeric 61 | } 62 | 63 | try_encode_numeric(input, 0) 64 | } 65 | 66 | /// Encodes numeric strings (i.e. "123456789"), referring to 8.4.2 of the spec. 67 | pub(crate) fn encode_numeric(compact: &mut CompactQR, input: &[u8], cci_bits: usize) { 68 | #[derive(Clone, Copy)] 69 | enum NumericEncoding { 70 | Single, 71 | Double, 72 | Triple, 73 | } 74 | 75 | fn encode_number(compact: &mut CompactQR, number: usize, encoding: NumericEncoding) { 76 | match encoding { 77 | NumericEncoding::Single => compact.push_bits(number, 4), 78 | NumericEncoding::Double => compact.push_bits(number, 7), 79 | NumericEncoding::Triple => compact.push_bits(number, 10), 80 | } 81 | } 82 | 83 | compact.push_bits(0b0001, 4); 84 | compact.push_bits(input.len(), cci_bits); 85 | 86 | let mut i = 0; 87 | let len = input.len() - input.len() % 3; 88 | 89 | while i < len { 90 | let number = ascii_to_digit(input[i]) * 100 91 | + ascii_to_digit(input[i + 1]) * 10 92 | + ascii_to_digit(input[i + 2]); 93 | 94 | encode_number(compact, number, NumericEncoding::Triple); 95 | i += 3; 96 | } 97 | 98 | // If the length is a multiple of 3, we are done 99 | if len == input.len() { 100 | return; 101 | } 102 | 103 | let mut number = 0; 104 | while i < input.len() { 105 | number *= 10; 106 | number += ascii_to_digit(input[i]); 107 | i += 1; 108 | } 109 | 110 | let encoding = match i % 3 { 111 | 1 => NumericEncoding::Single, 112 | 2 => NumericEncoding::Double, 113 | _ => unreachable!("i % 3 can only be 1 or 2"), 114 | }; 115 | 116 | encode_number(compact, number, encoding); 117 | } 118 | 119 | /// Encodes alphanumeric strings (i.e. "FAST-QR123"), referring to 8.4.3 of the spec. 120 | pub(crate) fn encode_alphanumeric(compact: &mut CompactQR, input: &[u8], cci_bits: usize) { 121 | compact.push_bits(0b0010, 4); 122 | compact.push_bits(input.len(), cci_bits); 123 | 124 | let even_size = input.len() - input.len() % 2; 125 | for chunk in input.chunks_exact(2) { 126 | let a = ascii_to_alphanumeric(chunk[0]); 127 | let b = ascii_to_alphanumeric(chunk[1]); 128 | compact.push_bits(a * 45 + b, 11); 129 | } 130 | if even_size != input.len() { 131 | compact.push_bits(ascii_to_alphanumeric(*input.last().unwrap()), 6); 132 | } 133 | } 134 | 135 | /// Encodes any string (i.e. ""), referring to 8.4.4 of the spec. 136 | pub(crate) fn encode_byte(compact: &mut CompactQR, input: &[u8], cci_bits: usize) { 137 | compact.push_bits(0b0100, 4); 138 | compact.push_bits(input.len(), cci_bits); 139 | compact.push_u8_slice(input); 140 | } 141 | 142 | /// Adds needed terminator padding, terminating the data `BitString`, referring to 8.4.8 of the spec. 143 | fn add_terminator(compact: &mut CompactQR, data_bits: usize) { 144 | let len = data_bits - compact.len(); 145 | let len = core::cmp::min(len, 4); 146 | 147 | compact.push_bits(0, len); 148 | } 149 | 150 | /// Adds the padding to make the length of the `BitString` a multiple of 8, referring to 8.4.9 of the spec. 151 | fn pad_to_8(compact: &mut CompactQR) { 152 | let len = (8 - compact.len() % 8) % 8; 153 | compact.push_bits(0, len); 154 | } 155 | 156 | /// Converts ascii number to it's value in usize \ 157 | /// "5" -> 5 158 | fn ascii_to_digit(c: u8) -> usize { 159 | assert!( 160 | c.is_ascii_digit(), 161 | "Unexpected character '{}' in Numeric mode", 162 | c as char 163 | ); 164 | (c - b'0') as usize 165 | } 166 | 167 | /// Converts ascii alnum to it's numeric value, characters included in `AlphaNumeric` are: \ 168 | /// 0-9, A-Z, $%*./:+-?.= [space] \ 169 | /// referring to 7.1 of the spec. 170 | pub(crate) fn ascii_to_alphanumeric(c: u8) -> usize { 171 | match c { 172 | b'0'..=b'9' => (c - b'0') as usize, 173 | b'A'..=b'Z' => (c - b'A') as usize + 10, 174 | b' ' => 36, 175 | b'$' => 37, 176 | b'%' => 38, 177 | b'*' => 39, 178 | b'+' => 40, 179 | b'-' => 41, 180 | b'.' => 42, 181 | b'/' => 43, 182 | b':' => 44, 183 | _ => panic!("Unexpected character '{}' in Alphanumeric mode", c as char), 184 | } 185 | } 186 | 187 | /// Checks if character c is alphanumeric: 0-9, A-Z, $%*./:+-?.= [space] \ 188 | /// referring to 7.1 of the spec. 189 | const fn is_qr_alphanumeric(c: u8) -> bool { 190 | matches!(c, 191 | b'A'..=b'Z' 192 | | b'0'..=b'9' 193 | | b' ' 194 | | b'$' 195 | | b'%' 196 | | b'*' 197 | | b'+' 198 | | b'-' 199 | | b'.' 200 | | b'/' 201 | | b':') 202 | } 203 | -------------------------------------------------------------------------------- /src/helpers.rs: -------------------------------------------------------------------------------- 1 | //! Matrix helpers functions 2 | #![deny(unsafe_code)] 3 | #![warn(missing_docs)] 4 | 5 | use crate::module::Module; 6 | use crate::QRCode; 7 | 8 | /// Used to print a ` ` (space) 9 | const EMPTY: char = ' '; 10 | /// Used to print a `█` 11 | const BLOCK: char = '█'; 12 | /// Used to print a `▀` 13 | const TOP: char = '▀'; 14 | /// Used to print a `▄` 15 | const BOTTOM: char = '▄'; 16 | 17 | /// Helper to print two lines at the same time 18 | fn print_line(line1: &[Module], line2: &[Module], size: usize) -> String { 19 | let mut line = String::with_capacity(size); 20 | for i in 0..size { 21 | match (line1[i].value(), line2[i].value()) { 22 | (true, true) => line.push(EMPTY), 23 | (true, false) => line.push(BOTTOM), 24 | (false, true) => line.push(TOP), 25 | (false, false) => line.push(BLOCK), 26 | } 27 | } 28 | line 29 | } 30 | 31 | /// Prints a matrix with margins 32 | pub fn print_matrix_with_margin(qr: &QRCode) -> String { 33 | let mut out = String::new(); 34 | 35 | let line = print_line( 36 | &[Module::empty(true); 177], 37 | &[Module::empty(false); 177], 38 | qr.size, 39 | ); 40 | 41 | out.push(BOTTOM); 42 | out.push_str(&line); 43 | out.push_str(&format!("{BOTTOM}\n")); 44 | 45 | // Black background 46 | for i in (0..qr.size - 1).step_by(2) { 47 | let line = print_line(&qr[i], &qr[i + 1], qr.size); 48 | out.push(BLOCK); 49 | out.push_str(&line); 50 | out.push_str(&format!("{BLOCK}\n")); 51 | } 52 | 53 | let line = print_line(&qr[qr.size - 1], &[Module::empty(false); 177], qr.size); 54 | out.push(BLOCK); 55 | out.push_str(&line); 56 | out.push(BLOCK); 57 | 58 | out 59 | } 60 | 61 | #[cfg(test)] 62 | use crate::{compact::CompactQR, Version}; 63 | 64 | /// Convert a vector of u8 to it's representation in bits 65 | /// 66 | /// If bits are required by the QR code (referring to 8.6 of the spec), they are added to the end of the vector. 67 | /// 68 | /// ## Example 69 | /// { 101 } => "01100101" 70 | #[cfg(test)] 71 | pub fn binary_to_binarystring_version(binary: [u8; 5430], version: Version) -> CompactQR { 72 | let max = version.max_bytes() * 8; 73 | CompactQR::from_array(&binary, max + version.missing_bits()) 74 | } 75 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(docsrs, feature(doc_cfg))] 2 | #![warn(missing_docs)] 3 | //! # Easy to use fast QRCode generator 4 | //! 5 | //! More examples can be found on [GitHub](https://github.com/erwanvivien/fast_qr/tree/master/examples). 6 | //! 7 | //! ## Converts [`QRCode`] to Unicode 8 | //! 9 | //! ```rust 10 | //! # use fast_qr::convert::ConvertError; 11 | //! use fast_qr::qr::QRBuilder; 12 | //! 13 | //! # fn main() -> Result<(), ConvertError> { 14 | //! // QRBuilder::new can fail if content is too big for version, 15 | //! // please check before unwrapping. 16 | //! let qrcode = QRBuilder::new("https://example.com/") 17 | //! .build() 18 | //! .unwrap(); 19 | //! 20 | //! let str = qrcode.to_str(); // .print() exists 21 | //! println!("{}", str); 22 | //! 23 | //! # Ok(()) 24 | //! # } 25 | //! ``` 26 | //! 27 | //! ## Converts [`QRCode`] to SVG 28 | //! 29 | //! ```rust 30 | //! # use fast_qr::convert::ConvertError; 31 | //! use fast_qr::convert::{svg::SvgBuilder, Builder, Shape}; 32 | //! use fast_qr::qr::QRBuilder; 33 | //! 34 | //! # fn main() -> Result<(), ConvertError> { 35 | //! // QRBuilder::new can fail if content is too big for version, 36 | //! // please check before unwrapping. 37 | //! let qrcode = QRBuilder::new("https://example.com/") 38 | //! .build() 39 | //! .unwrap(); 40 | //! 41 | //! let _svg = SvgBuilder::default() 42 | //! .shape(Shape::RoundedSquare) 43 | //! .to_file(&qrcode, "out.svg"); 44 | //! # std::fs::remove_file("out.svg"); 45 | //! 46 | //! # Ok(()) 47 | //! # } 48 | //! ``` 49 | //! 50 | //! ## Converts [`QRCode`] to an image 51 | //! 52 | //! ```rust 53 | //! # use fast_qr::convert::ConvertError; 54 | //! use fast_qr::convert::{image::ImageBuilder, Builder, Shape}; 55 | //! use fast_qr::qr::QRBuilder; 56 | //! 57 | //! # fn main() -> Result<(), ConvertError> { 58 | //! // QRBuilder::new can fail if content is too big for version, 59 | //! // please check before unwrapping. 60 | //! let qrcode = QRBuilder::new("https://example.com/") 61 | //! .build() 62 | //! .unwrap(); 63 | //! 64 | //! let _img = ImageBuilder::default() 65 | //! .shape(Shape::RoundedSquare) 66 | //! .background_color([255, 255, 255, 0]) // transparency 67 | //! .fit_width(600) 68 | //! .to_file(&qrcode, "out.png"); 69 | //! # std::fs::remove_file("out.png"); 70 | //! 71 | //! # Ok(()) 72 | //! # } 73 | //! ``` 74 | 75 | pub use crate::datamasking::Mask; 76 | pub use crate::ecl::ECL; 77 | pub use crate::encode::Mode; 78 | pub use crate::module::{Module, ModuleType}; 79 | pub use crate::qr::{QRBuilder, QRCode}; 80 | pub use crate::version::Version; 81 | 82 | mod compact; 83 | #[doc(hidden)] 84 | pub mod datamasking; 85 | 86 | pub mod convert; 87 | mod default; 88 | mod ecl; 89 | mod encode; 90 | mod hardcode; 91 | #[cfg(not(feature = "wasm-bindgen"))] 92 | mod helpers; 93 | mod module; 94 | mod placement; 95 | mod polynomials; 96 | #[macro_use] 97 | pub mod qr; 98 | mod score; 99 | mod version; 100 | 101 | #[cfg(test)] 102 | mod tests; 103 | 104 | #[cfg(target_arch = "wasm32")] 105 | mod wasm; 106 | 107 | #[cfg(target_arch = "wasm32")] 108 | pub use wasm::*; 109 | -------------------------------------------------------------------------------- /src/module.rs: -------------------------------------------------------------------------------- 1 | /// Module is a single pixel in the QR code. 2 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 3 | #[repr(u8)] 4 | pub enum ModuleType { 5 | /// The module is part of the data (Encoded data) 6 | Data = 0 << 1, 7 | /// The module is part of the finder pattern (bigger cubes) 8 | FinderPattern = 1 << 1, 9 | /// The module is part of the alignment pattern (smaller cubes) 10 | Alignment = 2 << 1, 11 | /// The module is part of the timing pattern (Line between finder patterns) 12 | Timing = 3 << 1, 13 | /// The module is part of the format information 14 | Format = 4 << 1, 15 | /// The module is part of the version information 16 | Version = 5 << 1, 17 | /// Dark module 18 | DarkModule = 6 << 1, 19 | /// Space between finder patterns 20 | Empty = 7 << 1, 21 | } 22 | 23 | impl From for ModuleType { 24 | fn from(value: u8) -> Self { 25 | match value { 26 | 0 => ModuleType::Data, 27 | 1 => ModuleType::FinderPattern, 28 | 2 => ModuleType::Alignment, 29 | 3 => ModuleType::Timing, 30 | 4 => ModuleType::Format, 31 | 5 => ModuleType::Version, 32 | 6 => ModuleType::DarkModule, 33 | 7 => ModuleType::Empty, 34 | _ => unreachable!(), 35 | } 36 | } 37 | } 38 | 39 | /// Module is a single pixel in the QR code. 40 | /// Module uses u8 to store value and type. 41 | #[derive(Copy, Clone, Debug)] 42 | pub struct Module(pub u8); 43 | 44 | impl Module { 45 | /// Represents a dark module, which is a black pixel. 46 | pub const DARK: bool = true; 47 | /// Represents a light module, which is a white pixel. 48 | pub const LIGHT: bool = false; 49 | 50 | /// Creates a new module with the given type and value. 51 | #[must_use] 52 | pub const fn new(value: bool, module_type: ModuleType) -> Self { 53 | let value = value as u8; 54 | Module(value | (module_type as u8)) 55 | } 56 | 57 | /// Creates a new module with the given value with type data. 58 | #[must_use] 59 | pub const fn data(value: bool) -> Self { 60 | Module::new(value, ModuleType::Data) 61 | } 62 | 63 | /// Creates a new module with the given value with type finder pattern. 64 | #[must_use] 65 | pub const fn finder_pattern(value: bool) -> Self { 66 | Module::new(value, ModuleType::FinderPattern) 67 | } 68 | 69 | /// Creates a new module with the given value with type alignment. 70 | #[must_use] 71 | pub const fn alignment(value: bool) -> Self { 72 | Module::new(value, ModuleType::Alignment) 73 | } 74 | 75 | /// Creates a new module with the given value with type timing. 76 | #[must_use] 77 | pub const fn timing(value: bool) -> Self { 78 | Module::new(value, ModuleType::Timing) 79 | } 80 | 81 | /// Creates a new module with the given value with type format. 82 | #[must_use] 83 | pub const fn format(value: bool) -> Self { 84 | Module::new(value, ModuleType::Format) 85 | } 86 | 87 | /// Creates a new module with the given value with type version. 88 | #[must_use] 89 | pub const fn version(value: bool) -> Self { 90 | Module::new(value, ModuleType::Version) 91 | } 92 | 93 | /// Creates a new module with the given value with type dark module. 94 | #[must_use] 95 | pub const fn dark(value: bool) -> Self { 96 | Module::new(value, ModuleType::DarkModule) 97 | } 98 | 99 | /// Creates a new module with the given value with type empty. 100 | #[must_use] 101 | pub const fn empty(value: bool) -> Self { 102 | Module::new(value, ModuleType::Empty) 103 | } 104 | 105 | /// Returns the boolean value of the module. 106 | #[must_use] 107 | pub const fn value(self) -> bool { 108 | self.0 & 1 == 1 109 | } 110 | 111 | /// Returns the type of the module. 112 | #[must_use] 113 | pub fn module_type(self) -> ModuleType { 114 | ModuleType::from(self.0 >> 1) 115 | } 116 | 117 | /// Sets the boolean value of the module. 118 | pub fn set(&mut self, value: bool) { 119 | self.0 = if value { self.0 | 1 } else { self.0 & !1 }; 120 | } 121 | 122 | /// Toggles the boolean value of the module. 123 | pub fn toggle(&mut self) { 124 | self.0 ^= 1; 125 | } 126 | } 127 | 128 | impl From for Module { 129 | fn from(value: bool) -> Self { 130 | Module::empty(value) 131 | } 132 | } 133 | 134 | impl PartialEq for Module { 135 | fn eq(&self, other: &bool) -> bool { 136 | self.value() == *other 137 | } 138 | } 139 | 140 | impl PartialEq for Module { 141 | fn eq(&self, other: &Self) -> bool { 142 | self.0 == other.0 143 | } 144 | } 145 | 146 | impl Eq for Module {} 147 | 148 | #[cfg(test)] 149 | mod test { 150 | use super::*; 151 | 152 | #[test] 153 | fn byte_size() { 154 | assert_eq!(std::mem::size_of::(), 1); 155 | } 156 | 157 | #[test] 158 | fn data() { 159 | let module = Module::data(Module::LIGHT); 160 | assert_eq!(module.module_type(), ModuleType::Data); 161 | } 162 | 163 | #[test] 164 | fn finder_pattern() { 165 | let module = Module::finder_pattern(Module::LIGHT); 166 | assert_eq!(module.module_type(), ModuleType::FinderPattern); 167 | } 168 | 169 | #[test] 170 | fn alignment() { 171 | let module = Module::alignment(Module::LIGHT); 172 | assert_eq!(module.module_type(), ModuleType::Alignment); 173 | } 174 | 175 | #[test] 176 | fn timing() { 177 | let module = Module::timing(Module::LIGHT); 178 | assert_eq!(module.module_type(), ModuleType::Timing); 179 | } 180 | 181 | #[test] 182 | fn format() { 183 | let module = Module::format(Module::LIGHT); 184 | assert_eq!(module.module_type(), ModuleType::Format); 185 | } 186 | 187 | #[test] 188 | fn version() { 189 | let module = Module::version(Module::LIGHT); 190 | assert_eq!(module.module_type(), ModuleType::Version); 191 | } 192 | 193 | #[test] 194 | fn dark() { 195 | let module = Module::dark(Module::LIGHT); 196 | assert_eq!(module.module_type(), ModuleType::DarkModule); 197 | } 198 | 199 | #[test] 200 | fn value_light() { 201 | let module = Module::data(Module::LIGHT); 202 | assert_eq!(module.value(), Module::LIGHT); 203 | } 204 | 205 | #[test] 206 | fn value_dark() { 207 | let module = Module::data(Module::DARK); 208 | assert_eq!(module.value(), Module::DARK); 209 | } 210 | 211 | #[test] 212 | fn set() { 213 | let mut module = Module::data(Module::LIGHT); 214 | module.set(Module::DARK); 215 | assert_eq!(module.value(), Module::DARK); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/placement.rs: -------------------------------------------------------------------------------- 1 | //! Places data on a matrix 2 | #![deny(unsafe_code)] 3 | #![warn(missing_docs)] 4 | 5 | use crate::compact::CompactQR; 6 | use crate::datamasking::Mask; 7 | use crate::encode::Mode; 8 | 9 | use crate::module::ModuleType; 10 | use crate::{datamasking, default, encode, polynomials, score, QRCode}; 11 | use crate::{Version, ECL}; 12 | use core::iter::Rev; 13 | use core::ops::Range; 14 | 15 | pub enum BiRange { 16 | Forward(Range), 17 | Backwards(Rev>), 18 | } 19 | 20 | impl Iterator for BiRange { 21 | type Item = usize; 22 | fn next(&mut self) -> Option { 23 | match self { 24 | BiRange::Forward(range) => range.next(), 25 | BiRange::Backwards(range) => range.next(), 26 | } 27 | } 28 | } 29 | 30 | #[cfg(test)] 31 | pub fn test_place_on_matrix_data(qr: &mut QRCode, structure_as_binarystring: &CompactQR) { 32 | place_on_matrix_data(qr, structure_as_binarystring); 33 | } 34 | 35 | /// Places the data on the matrix 36 | pub fn place_on_matrix_data(qr: &mut QRCode, structure_as_binarystring: &CompactQR) { 37 | let structure_bytes_tmp = structure_as_binarystring.get_data(); 38 | 39 | let mut rev = true; 40 | let mut idx = 0; 41 | 42 | // 0, 2, 4, 7, 9, .., N (skipping 6) 43 | for x in (0..6).chain(7..qr.size).rev().step_by(2) { 44 | let y_range = if rev { 45 | BiRange::Backwards((0..qr.size).rev()) 46 | } else { 47 | BiRange::Forward(0..qr.size) 48 | }; 49 | 50 | for y in y_range { 51 | if qr[y][x].module_type() == ModuleType::Data { 52 | let c = structure_bytes_tmp[idx / 8] & (1 << (7 - idx % 8)); 53 | idx += 1; 54 | qr[y][x].set(c != 0); 55 | } 56 | if qr[y][x - 1].module_type() == ModuleType::Data { 57 | let c = structure_bytes_tmp[idx / 8] & (1 << (7 - idx % 8)); 58 | idx += 1; 59 | qr[y][x - 1].set(c != 0); 60 | } 61 | } 62 | 63 | rev = !rev; 64 | } 65 | 66 | #[cfg(debug_assertions)] 67 | { 68 | let version = Version::from_n(qr.size); 69 | assert_eq!(idx - version.missing_bits(), version.max_bytes() * 8); 70 | } 71 | } 72 | 73 | const MASKS: [Mask; 8] = [ 74 | Mask::Checkerboard, 75 | Mask::HorizontalLines, 76 | Mask::VerticalLines, 77 | Mask::DiagonalLines, 78 | Mask::LargeCheckerboard, 79 | Mask::Fields, 80 | Mask::Diamonds, 81 | Mask::Meadow, 82 | ]; 83 | 84 | /// Main function to place everything in the `QRCode`, returns a valid matrix 85 | pub fn place_on_matrix( 86 | structure_as_binarystring: &CompactQR, 87 | quality: ECL, 88 | version: Version, 89 | mask: &mut Option, 90 | ) -> QRCode { 91 | let mut best_score = u32::MAX; 92 | let mut best_mask = MASKS[0]; 93 | 94 | let mut qr = default::create_matrix(version); 95 | place_on_matrix_data(&mut qr, structure_as_binarystring); 96 | 97 | let transpose = default::transpose(&qr); 98 | 99 | for mask in MASKS { 100 | let mut copy = qr.clone(); 101 | let copy_transpose = transpose.clone(); 102 | 103 | datamasking::mask(&mut copy, mask); 104 | let matrix_score = score::score(©, ©_transpose); 105 | if matrix_score < best_score { 106 | best_score = matrix_score; 107 | best_mask = mask; 108 | } 109 | } 110 | 111 | best_mask = mask.unwrap_or(best_mask); 112 | *mask = Some(best_mask); 113 | 114 | default::create_matrix_format_info(&mut qr, quality, best_mask); 115 | datamasking::mask(&mut qr, best_mask); 116 | 117 | qr.mask = *mask; 118 | qr 119 | } 120 | 121 | /// Generate the whole matrix 122 | pub fn create_matrix( 123 | input: &[u8], 124 | ecl: ECL, 125 | mode: Mode, 126 | version: Version, 127 | mask: &mut Option, 128 | ) -> QRCode { 129 | let data_codewords = encode::encode(input, ecl, mode, version); 130 | let structure = polynomials::structure(data_codewords.get_data(), ecl, version); 131 | 132 | let max = version.max_bytes() * 8; 133 | let structure_binstring = CompactQR::from_array(&structure, max + version.missing_bits()); 134 | 135 | QRCode { 136 | mode: Some(mode), 137 | ecl: Some(ecl), 138 | version: Some(version), 139 | ..place_on_matrix(&structure_binstring, ecl, version, mask) 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/polynomials.rs: -------------------------------------------------------------------------------- 1 | //! Is used to compute ECC (Error Correction Coding) 2 | 3 | #![deny(unsafe_code)] 4 | #![warn(missing_docs)] 5 | 6 | use crate::hardcode; 7 | use crate::polynomials; 8 | use crate::{Version, ECL}; 9 | 10 | /// Used in the ring, convert a^x using `LOG[x % 255]` to it's decimal Galois-Field value 11 | const LOG: [u8; 256] = [ 12 | 1, 2, 4, 8, 16, 32, 64, 128, 29, 58, 116, 232, 205, 135, 19, 38, 76, 152, 45, 90, 180, 117, 13 | 234, 201, 143, 3, 6, 12, 24, 48, 96, 192, 157, 39, 78, 156, 37, 74, 148, 53, 106, 212, 181, 14 | 119, 238, 193, 159, 35, 70, 140, 5, 10, 20, 40, 80, 160, 93, 186, 105, 210, 185, 111, 222, 161, 15 | 95, 190, 97, 194, 153, 47, 94, 188, 101, 202, 137, 15, 30, 60, 120, 240, 253, 231, 211, 187, 16 | 107, 214, 177, 127, 254, 225, 223, 163, 91, 182, 113, 226, 217, 175, 67, 134, 17, 34, 68, 136, 17 | 13, 26, 52, 104, 208, 189, 103, 206, 129, 31, 62, 124, 248, 237, 199, 147, 59, 118, 236, 197, 18 | 151, 51, 102, 204, 133, 23, 46, 92, 184, 109, 218, 169, 79, 158, 33, 66, 132, 21, 42, 84, 168, 19 | 77, 154, 41, 82, 164, 85, 170, 73, 146, 57, 114, 228, 213, 183, 115, 230, 209, 191, 99, 198, 20 | 145, 63, 126, 252, 229, 215, 179, 123, 246, 241, 255, 227, 219, 171, 75, 150, 49, 98, 196, 149, 21 | 55, 110, 220, 165, 87, 174, 65, 130, 25, 50, 100, 200, 141, 7, 14, 28, 56, 112, 224, 221, 167, 22 | 83, 166, 81, 162, 89, 178, 121, 242, 249, 239, 195, 155, 43, 86, 172, 69, 138, 9, 18, 36, 72, 23 | 144, 61, 122, 244, 245, 247, 243, 251, 235, 203, 139, 11, 22, 44, 88, 176, 125, 250, 233, 207, 24 | 131, 27, 54, 108, 216, 173, 71, 142, 1, 25 | ]; 26 | 27 | /// Reverses a ring value, converts decimal value x using `ANTILOG[x % 255]` to it's alpha power value 28 | const ANTILOG: [u8; 256] = [ 29 | 175, 0, 1, 25, 2, 50, 26, 198, 3, 223, 51, 238, 27, 104, 199, 75, 4, 100, 224, 14, 52, 141, 30 | 239, 129, 28, 193, 105, 248, 200, 8, 76, 113, 5, 138, 101, 47, 225, 36, 15, 33, 53, 147, 142, 31 | 218, 240, 18, 130, 69, 29, 181, 194, 125, 106, 39, 249, 185, 201, 154, 9, 120, 77, 228, 114, 32 | 166, 6, 191, 139, 98, 102, 221, 48, 253, 226, 152, 37, 179, 16, 145, 34, 136, 54, 208, 148, 33 | 206, 143, 150, 219, 189, 241, 210, 19, 92, 131, 56, 70, 64, 30, 66, 182, 163, 195, 72, 126, 34 | 110, 107, 58, 40, 84, 250, 133, 186, 61, 202, 94, 155, 159, 10, 21, 121, 43, 78, 212, 229, 172, 35 | 115, 243, 167, 87, 7, 112, 192, 247, 140, 128, 99, 13, 103, 74, 222, 237, 49, 197, 254, 24, 36 | 227, 165, 153, 119, 38, 184, 180, 124, 17, 68, 146, 217, 35, 32, 137, 46, 55, 63, 209, 91, 149, 37 | 188, 207, 205, 144, 135, 151, 178, 220, 252, 190, 97, 242, 86, 211, 171, 20, 42, 93, 158, 132, 38 | 60, 57, 83, 71, 109, 65, 162, 31, 45, 67, 216, 183, 123, 164, 118, 196, 23, 73, 236, 127, 12, 39 | 111, 246, 108, 161, 59, 82, 41, 157, 85, 170, 251, 96, 134, 177, 187, 204, 62, 90, 203, 89, 95, 40 | 176, 156, 169, 160, 81, 11, 245, 22, 235, 122, 117, 44, 215, 79, 174, 213, 233, 230, 231, 173, 41 | 232, 116, 214, 244, 234, 168, 80, 88, 175, 42 | ]; 43 | 44 | /// Return a string of human readable polynomial 45 | /// 46 | /// `[0, 75, 249, 78, 6]` => "α0x4 + α75x3 + α249x2 + α78x + α6" 47 | #[cfg(test)] 48 | pub fn generated_to_string(poly: &[u8]) -> String { 49 | let mut s = String::new(); 50 | let length = poly.len(); 51 | 52 | for (i, item) in poly.iter().enumerate() { 53 | s.push_str(&format!( 54 | "α{}{}", 55 | item, 56 | &match length - i - 1 { 57 | 0 => String::new(), 58 | 1 => String::from("x + "), 59 | n => format!("x{} + ", n), 60 | }, 61 | )); 62 | } 63 | 64 | s 65 | } 66 | 67 | /// Takes an array and divides it by the other in a Galois Field (256) 68 | /// ```txt 69 | /// from: [ 32, 91, 11, 120, 209, 114, 220, 77, 67, 64, 236, 70 | /// 17, 236, 17, 236, 17] (integer) 71 | /// by : [ 0, 251, 67, 46, 61, 118, 72 | /// 70, 64, 94, 32, 45] (alpha) 73 | /// ``` 74 | /// 75 | /// `from` should be of length `from.len() + by.len()`, so we pad zeroes, like so: 76 | /// ```txt 77 | /// from: [ 32, 91, 11, 120, 209, 114, 220, 77, 67, 64, 236, 78 | /// 17, 236, 17, 236, 17, 0, ..eight.., 0] (integer) 79 | /// ``` 80 | /// 81 | /// Then the actual division takes place 82 | /// We convert `from` from INTEGER to ALPHA 83 | pub fn division(from: &[u8], by: &[u8]) -> [u8; 255] { 84 | let mut from_mut = [0; 255]; 85 | let start = 256 - from.len() - by.len(); 86 | 87 | from_mut[start..(256 - by.len())].copy_from_slice(&from[..((256 - by.len()) - start)]); 88 | 89 | for i in start..start + from.len() { 90 | if from_mut[i] == 0 { 91 | continue; 92 | } 93 | 94 | let alpha = ANTILOG[from_mut[i] as usize]; 95 | for j in 0..by.len() { 96 | let tmp = by[j] as usize + alpha as usize; 97 | from_mut[i + j] ^= LOG[tmp % 255]; 98 | } 99 | } 100 | 101 | from_mut 102 | } 103 | 104 | /// Uses the data and error(generator polynomial) to compute the divisions 105 | /// for each block. 106 | pub fn structure(data: &[u8], quality: ECL, version: Version) -> [u8; 5430] { 107 | const MAX_ERROR: usize = 30; 108 | const MAX_GROUP_COUNT: usize = 81; 109 | const MAX_DATABITS: usize = 3000; 110 | 111 | // Need to find a more accurate way to do this. 112 | // let mut interleaved_data = vec![0; 0]; 113 | 114 | let error = hardcode::get_polynomial(version, quality); 115 | 116 | let [(g1_count, g1_size), (g2_count, g2_size)] = hardcode::ecc_to_groups(quality, version); 117 | let groups_count_total = g1_count + g2_count; 118 | 119 | let mut interleaved_data = [0; MAX_DATABITS + MAX_ERROR * MAX_GROUP_COUNT]; 120 | 121 | let start_error_idx = hardcode::data_codewords(version, quality); 122 | 123 | for i in 0..g1_count { 124 | let start_idx = i * g1_size; 125 | let division = polynomials::division(&data[start_idx..start_idx + g1_size], error); 126 | 127 | for j in 0..error.len() - 1 { 128 | interleaved_data[start_error_idx + j * groups_count_total + i] = 129 | division[256 - error.len() + j]; 130 | } 131 | } 132 | 133 | for i in 0..g2_count { 134 | let start_idx = g1_size * g1_count + i * g2_size; 135 | let division = polynomials::division(&data[start_idx..start_idx + g2_size], error); 136 | 137 | for j in 0..error.len() - 1 { 138 | interleaved_data[start_error_idx + j * groups_count_total + i + g1_count] = 139 | division[256 - error.len() + j]; 140 | } 141 | } 142 | 143 | let mut push_idx = 0; 144 | let max = core::cmp::max(g1_size, g2_size); 145 | 146 | for i in 0..max { 147 | if i < g1_size { 148 | for j in 0..g1_count { 149 | let idx = j * g1_size + i; 150 | interleaved_data[push_idx] = data[idx]; 151 | push_idx += 1; 152 | } 153 | } 154 | if i < g2_size { 155 | for j in 0..g2_count { 156 | let idx = j * g2_size + i + g1_size * g1_count; 157 | interleaved_data[push_idx] = data[idx]; 158 | push_idx += 1; 159 | } 160 | } 161 | } 162 | 163 | interleaved_data 164 | } 165 | -------------------------------------------------------------------------------- /src/qr.rs: -------------------------------------------------------------------------------- 1 | //! Module `qr` is the entrypoint to start making `QRCodes` 2 | 3 | use crate::module::Module; 4 | use core::fmt::{Debug, Formatter}; 5 | use core::ops::{Index, IndexMut}; 6 | 7 | use crate::datamasking::Mask; 8 | use crate::encode::Mode; 9 | #[cfg(not(feature = "wasm-bindgen"))] 10 | use crate::helpers; 11 | use crate::{encode, Version, ECL}; 12 | 13 | const QR_MAX_WIDTH: usize = 177; 14 | const QR_MAX_MODULES: usize = QR_MAX_WIDTH * QR_MAX_WIDTH; 15 | 16 | /// A `QRCode` can be created using [`QRBuilder`]. Simple API for simple usage. 17 | /// If you need to use `QRCode` directly, please file an [issue on 18 | /// github](https://github.com/erwanvivien/fast_qr) explaining your use case. 19 | /// 20 | /// Contains all needed information about the `QRCode`. 21 | /// This is the main struct of the crate. 22 | /// 23 | /// It contains the matrix of the `QRCode`, stored as a one-dimensional array. 24 | #[derive(Clone)] 25 | pub struct QRCode { 26 | /// This array length is of size `177 x 177`. It is using a fixed size 27 | /// array simply because of performance. 28 | /// 29 | /// # Other data type possible: 30 | /// - Templated Matrix was faster but crate size was huge. 31 | /// - Vector using `with_capacity`, really bad. 32 | pub data: [Module; QR_MAX_MODULES], 33 | /// Width & Height of QRCode. If manually set, should be `version * 4 + 17`, `version` going 34 | /// from 1 to 40 both included. 35 | pub size: usize, 36 | 37 | /// Version of the `QRCode`, impacts the size. 38 | /// 39 | /// `None` will optimize Version according to ECL and Mode 40 | pub version: Option, 41 | /// Defines how powerful `QRCode` redundancy should be or how much percent of a QRCode can be 42 | /// recovered. 43 | /// 44 | /// - `ECL::L`: 7% 45 | /// - `ECL::M`: 15% 46 | /// - `ECL::Q`: 25% 47 | /// - `ELC::H`: 30% 48 | /// 49 | /// `None` will set ECL to Quartile (`ELC::Q`) 50 | pub ecl: Option, 51 | 52 | /// Changes the final pattern used. 53 | /// 54 | /// None will find the best suited mask. 55 | pub mask: Option, 56 | /// Mode defines which data is being parsed, between Numeric, AlphaNumeric & Byte. 57 | /// 58 | /// `None` will optimize Mode according to user input. 59 | /// 60 | /// ## Note 61 | /// Kanji mode is not supported (yet). 62 | pub mode: Option, 63 | } 64 | 65 | impl Debug for QRCode { 66 | fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { 67 | f.debug_struct("QRCode") 68 | .field("size", &self.size) 69 | .field("version", &self.version) 70 | .field("ecl", &self.ecl) 71 | .field("mask", &self.mask) 72 | .field("mode", &self.mode) 73 | .finish_non_exhaustive() 74 | } 75 | } 76 | 77 | impl QRCode { 78 | /// A default `QRCode` will have all it's fields as `None` and a default Matrix filled with `Module::LIGHT`. 79 | #[must_use] 80 | pub const fn default(size: usize) -> Self { 81 | QRCode { 82 | data: [Module::data(Module::LIGHT); QR_MAX_MODULES], 83 | size, 84 | version: None, 85 | ecl: None, 86 | mask: None, 87 | mode: None, 88 | } 89 | } 90 | } 91 | 92 | impl Index for QRCode { 93 | type Output = [Module]; 94 | 95 | fn index(&self, index: usize) -> &Self::Output { 96 | &self.data[index * self.size..(index + 1) * self.size] 97 | } 98 | } 99 | 100 | impl IndexMut for QRCode { 101 | fn index_mut(&mut self, index: usize) -> &mut Self::Output { 102 | &mut self.data[index * self.size..(index + 1) * self.size] 103 | } 104 | } 105 | 106 | /// Contains different error when [`QRCode`] could not be created 107 | pub enum QRCodeError { 108 | /// If data if too large to be encoded (refer to Table 7-11 of the spec or [an online table](https://fast-qr.com/blog/tables/ecl)) 109 | EncodedData, 110 | /// Specified version too small to contain data 111 | SpecifiedVersion, 112 | } 113 | 114 | // We don't want to use `std::error::Error` on wasm32 115 | impl std::error::Error for QRCodeError {} 116 | 117 | impl std::fmt::Display for QRCodeError { 118 | fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { 119 | match self { 120 | QRCodeError::EncodedData => f.write_str("Data too big to be encoded"), 121 | QRCodeError::SpecifiedVersion => { 122 | f.write_str("Specified version too low to contain data") 123 | } 124 | } 125 | } 126 | } 127 | 128 | impl Debug for QRCodeError { 129 | fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { 130 | match self { 131 | QRCodeError::EncodedData => f.write_str("Data too big to be encoded"), 132 | QRCodeError::SpecifiedVersion => { 133 | f.write_str("Specified version too low to contain data") 134 | } 135 | } 136 | } 137 | } 138 | 139 | impl QRCode { 140 | /// Creates a new `QRCode` from a ECL / version 141 | /// 142 | /// # Errors 143 | /// - `QRCodeError::EncodedData` if `input` is too large to be encoded 144 | /// - `QRCodeError::SpecifiedVersion` if specified `version` is too small to contain data 145 | pub(crate) fn new( 146 | input: &[u8], 147 | ecl: Option, 148 | v: Option, 149 | mode: Option, 150 | mut mask: Option, 151 | ) -> Result { 152 | use crate::placement::create_matrix; 153 | 154 | let mode = mode.unwrap_or_else(|| encode::best_encoding(input)); 155 | let level = ecl.unwrap_or(ECL::Q); 156 | 157 | let version = match Version::get(mode, level, input.len()) { 158 | Some(version) => version, 159 | None => return Err(QRCodeError::EncodedData), 160 | }; 161 | let version = match v { 162 | Some(user_version) if user_version as usize >= version as usize => user_version, 163 | None => version, 164 | Some(_) => return Err(QRCodeError::SpecifiedVersion), 165 | }; 166 | 167 | let out = create_matrix(input, level, mode, version, &mut mask); 168 | Ok(out) 169 | } 170 | 171 | /// Prints the `QRCode` to the terminal 172 | #[must_use] 173 | #[cfg(not(feature = "wasm-bindgen"))] 174 | pub fn to_str(&self) -> String { 175 | helpers::print_matrix_with_margin(self) 176 | } 177 | 178 | /// Prints the `QRCode` to the terminal 179 | #[cfg(not(feature = "wasm-bindgen"))] 180 | pub fn print(&self) { 181 | println!("{}", helpers::print_matrix_with_margin(self)); 182 | } 183 | } 184 | 185 | /// Builder struct, makes it easier to create a [`QRCode`]. 186 | /// 187 | /// # Example 188 | /// ```rust 189 | /// use fast_qr::QRBuilder; 190 | /// use fast_qr::{Mask, ECL, Version}; 191 | /// 192 | /// // Creates a `QRCode` with a forced `version`, `ecl` and/or `mask` 193 | /// let input = String::from("Hello World!"); 194 | /// let qr = QRBuilder::new(input) 195 | /// // .version(Version::V05) 196 | /// // .ecl(ECL::H) 197 | /// // .mask(Mask::Checkerboard) 198 | /// .build(); 199 | /// ``` 200 | pub struct QRBuilder { 201 | input: Vec, 202 | ecl: Option, 203 | mode: Option, 204 | version: Option, 205 | mask: Option, 206 | } 207 | 208 | impl QRBuilder { 209 | /// Creates an instance of `QRBuilder` with default parameters 210 | #[must_use] 211 | pub fn new>>(input: I) -> QRBuilder { 212 | QRBuilder { 213 | input: input.into(), 214 | mask: None, 215 | mode: None, 216 | version: None, 217 | ecl: None, 218 | } 219 | } 220 | 221 | /// Forces the Mode 222 | pub fn mode(&mut self, mode: Mode) -> &mut Self { 223 | self.mode = Some(mode); 224 | self 225 | } 226 | 227 | /// Forces the Encoding Level 228 | pub fn ecl(&mut self, ecl: ECL) -> &mut Self { 229 | self.ecl = Some(ecl); 230 | self 231 | } 232 | 233 | /// Forces the version 234 | pub fn version(&mut self, version: Version) -> &mut Self { 235 | self.version = Some(version); 236 | self 237 | } 238 | 239 | /// Forces the mask, should very rarely be used 240 | pub fn mask(&mut self, mask: Mask) -> &mut Self { 241 | self.mask = Some(mask); 242 | self 243 | } 244 | 245 | /// Computes a [`QRCode`] with given parameters 246 | /// 247 | /// # Errors 248 | /// - `QRCodeError::EncodedData` if `input` is too large to be encoded. See [an online table](https://fast-qr.com/blog/tables/ecl) for more info. 249 | /// - `QRCodeError::SpecifiedVersion` if specified `version` is too small to contain data 250 | pub fn build(&self) -> Result { 251 | QRCode::new(&self.input, self.ecl, self.version, self.mode, self.mask) 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /src/score.rs: -------------------------------------------------------------------------------- 1 | //! `QRCode` need a way to define if they are readable, using a 2 | //! scoring system. The lesser, the better. 3 | 4 | #![warn(missing_docs)] 5 | 6 | #[cfg(test)] 7 | use crate::default::transpose; 8 | use crate::module::{Module, ModuleType}; 9 | use crate::QRCode; 10 | 11 | use super::hardcode; 12 | 13 | #[cfg(test)] 14 | pub fn test_score_line(l: &[Module]) -> u32 { 15 | line(l).1 16 | } 17 | 18 | #[cfg(test)] 19 | pub fn test_score_pattern(l: &[Module]) -> u32 { 20 | line(l).0 21 | } 22 | 23 | #[cfg(test)] 24 | pub fn test_matrix_dark_modules(qr: &QRCode) -> u32 { 25 | dark_module_score(qr) 26 | } 27 | 28 | #[cfg(test)] 29 | pub fn test_matrix_pattern_and_line(qr: &QRCode) -> (u32, u32, u32) { 30 | let transpose = transpose(qr); 31 | matrix_pattern_and_line(qr, &transpose) 32 | } 33 | 34 | #[cfg(test)] 35 | pub fn test_matrix_score_squares(qr: &QRCode) -> u32 { 36 | matrix_score_squares(qr) 37 | } 38 | 39 | /// Computes scores for squares, any 2x2 square (black or white) 40 | /// add 3 to the score 41 | /// 42 | /// ### Opti: 43 | /// We don't want to access the 4 squares each time, so we score the left most 44 | /// ones and only fetch the next right ones 45 | fn matrix_score_squares(qr: &QRCode) -> u32 { 46 | let mut square_score = 0; 47 | 48 | for i in 0..qr.size - 1 { 49 | let mut count_data = 2; 50 | 51 | let line1 = &qr[i]; 52 | let line2 = &qr[i + 1]; 53 | 54 | let mut buffer = 0u8; 55 | buffer |= u8::from(line1[0].value()) << 2; 56 | buffer |= u8::from(line2[0].value()) << 3; 57 | 58 | for j in 0..qr.size - 1 { 59 | buffer >>= 2; 60 | buffer |= u8::from(line1[j + 1].value()) << 2; 61 | buffer |= u8::from(line2[j + 1].value()) << 3; 62 | 63 | if line1[j + 1].module_type() != ModuleType::Data 64 | || line2[j + 1].module_type() != ModuleType::Data 65 | { 66 | count_data = 0; 67 | } 68 | 69 | if count_data >= 2 && (buffer == 0b1111 || buffer == 0b0000) { 70 | square_score += 3; 71 | } 72 | 73 | count_data += 1; 74 | } 75 | } 76 | 77 | square_score 78 | } 79 | 80 | /// Computes scores for both patterns (`0b10111010000` or `0b00001011101`) 81 | /// 82 | /// ### Opti: 83 | /// We convert the line to a u11 (supposedly) so comparing it to a pattern is 84 | /// a simple comparison. 85 | fn line(line: &[Module]) -> (u32, u32) { 86 | const PATTERN_LEN: usize = 7; 87 | 88 | let mut line_score = 0; 89 | let mut patt_score = 0; 90 | 91 | let mut count = 1; 92 | let mut current = !line[0].value(); 93 | 94 | let mut buffer = 0; 95 | let mut count_data = 0; 96 | 97 | for &item in line { 98 | buffer = ((buffer << 1) | u16::from(item.value())) & 0b111_1111; 99 | count_data += 1; 100 | 101 | if item.value() != current { 102 | if count >= 5 { 103 | line_score += count - 2; 104 | } 105 | count = 0; 106 | current = item.value(); 107 | } 108 | 109 | if item.module_type() != ModuleType::Data { 110 | if count >= 5 { 111 | line_score += count - 2; 112 | } 113 | 114 | count_data = 0; 115 | count = 0; 116 | continue; 117 | } 118 | 119 | if count_data >= PATTERN_LEN && buffer == 0b101_1101 { 120 | patt_score += 40; 121 | } 122 | 123 | count += 1; 124 | } 125 | 126 | if count >= 5 { 127 | line_score += count - 2; 128 | } 129 | 130 | (patt_score, line_score) 131 | } 132 | 133 | /// Converts the matrix to lines & columns and feed it to `score_line` 134 | fn matrix_pattern_and_line(qr: &QRCode, qr_transpose: &QRCode) -> (u32, u32, u32) { 135 | let mut line_score = 0; 136 | let mut col_score = 0; 137 | let mut patt_score = 0; 138 | 139 | let n = qr.size; 140 | 141 | for i in 0..n { 142 | let l = line(&qr[i]); 143 | line_score += l.1; 144 | 145 | let c = line(&qr_transpose[i]); 146 | col_score += c.1; 147 | 148 | patt_score += l.0 + c.0; 149 | } 150 | 151 | (line_score, col_score, patt_score) 152 | } 153 | 154 | /// Computes the number of `ModuleType::Dark` modules 155 | fn dark_module_score(qr: &QRCode) -> u32 { 156 | let n = qr.size; 157 | let dark_modules = qr.data[..n * n] 158 | .iter() 159 | .filter(|m| m.value() == Module::DARK) 160 | .count(); 161 | 162 | let percent = (dark_modules * 100) / (n * n); 163 | u32::from(hardcode::PERCENT_SCORE[percent]) 164 | } 165 | 166 | /// Computes the score for the matrix 167 | /// - `matrix_pattern_and_line`: 168 | /// - 40 points for each [TFTTTFT] pattern (T: true / F: false) 169 | /// - N - 2 points for each line with N consecutive modules of the same color (N >= 5) 170 | /// - `matrix_score_squares`: 3 points for each 2x2 square (black or white) 171 | /// - `dark_module_score`: 10 points for each 5% of dark modules away from 50% 172 | pub fn score(qr: &QRCode, qr_transpose: &QRCode) -> u32 { 173 | let dark_score = dark_module_score(qr); 174 | let square_score = matrix_score_squares(qr); 175 | let (line_score, col_score, patt_score) = matrix_pattern_and_line(qr, qr_transpose); 176 | 177 | line_score + patt_score + col_score + dark_score + square_score 178 | } 179 | -------------------------------------------------------------------------------- /src/tests/bytes.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "image")] 2 | #[test] 3 | fn it_can_output_to_bytes_from_image() { 4 | use base64::engine::general_purpose; 5 | use base64::Engine; 6 | 7 | use crate::convert::image::ImageBuilder; 8 | use crate::{QRBuilder, ECL}; 9 | 10 | // Expected 11 | let image_base64 = "iVBORw0KGgoAAAANSUhEUgAAACUAAAAlCAYAAADFniADAAACxElEQVR4Ae2W224UQQxEc/7/o2HOOrV2z/Qs0fJAkIKo2K4qX6YBCX4dvz6+2a+fo776B/J/vBRw+0H+9YPSzWMEPqyhojxUDh3ld7B38peXghoyTeZQPNRRchkGrU1u55GbgOpduGPIr4X4NMG6HKoZ7qNzjnmGB6BmyEH3PcTjx+SP8vn79qWgBuo8N0MvUNMDxc0c9pweYS+0R068dZSNAfTx4bLsXL/i4zXeHqU4AfVFsD9CL5TnnO9qOQHdYy22RynskC99N+5myjnPGFyOinAXoV7KQVD59MrP+p389ijohS6CemaoeLcMSofun15nWUPr4eTF7VGKAfx5EbTnsuTQMmtGfVB9k78cBWWC+pI02jRz6zvElzh9crPe5S+PcgBcj3PQTptcPEaoGebxzCg/cTlKEfq10iwfhDPKQfnNz4A6SC+0D4rXr2YMbo/SoBmq2VwOevCsoXioGM0YOAN6HnQej/FyFLQRagGsnI0iS4zWsPrk4TpD7ytsj8qwNFonh14sB7XUPID2wKrDvfbsPxYu/0tQgLVRThxewwes+o5/GD9/wP4w5fSaB2+9lIOgFsF6oIPVjQJKN5/QA6WZT+32KE3QTVBHhHcQFAcd1cXUrWE/S+2M26OgFtkAlUNH+cADzKEWm8tB1cln1AM1z3zi5VEaM2jmk5M/46u6vnOv9eUoyVeA9eugauiYfpdC8eGgX08OVv3BHY3Lvz6oJsUzDu+TghoGq18P7LloxuegTXJ5KahlZy80D+tSvS6C4mcezSigPdY73B4F1WxTliSGM0Ifm9oopt/6jOjGqf3VUdCHO3QOh6sG9QFQ2vTbH7x9lAMcCr0otdorTJ/52Xt71MX4uVwe6hDzAOrrU5+XQfVA+dShuPQkbo+KeI4OCgc1PHViPLAuhKphjemb8XLUFP9V/nPUV1/+W77Ub25RML9l49+tAAAAAElFTkSuQmCC"; 12 | let expected_data_uri = format!("data:image/png;base64,{image_base64}"); 13 | 14 | // Source 15 | let qrcode = QRBuilder::new("https://example.com/") 16 | .ecl(ECL::H) 17 | .build() 18 | .unwrap(); 19 | 20 | // As bytes 21 | let png_bytes = ImageBuilder::default().to_bytes(&qrcode).unwrap(); 22 | 23 | // As base64 24 | let png_base64 = general_purpose::STANDARD.encode(png_bytes); 25 | let data_uri = format!("data:image/png;base64,{png_base64}"); 26 | 27 | // Verify 28 | assert_eq!(data_uri, expected_data_uri); 29 | } 30 | -------------------------------------------------------------------------------- /src/tests/compact.rs: -------------------------------------------------------------------------------- 1 | use crate::compact::CompactQR; 2 | 3 | #[test] 4 | fn push8_lined() { 5 | let mut expected = [0u8; 64]; 6 | let mut res = CompactQR::with_len(64); 7 | 8 | res.push_u8(0); 9 | res.push_u8(1); 10 | res.push_u8(2); 11 | 12 | expected[1] = 1; 13 | expected[2] = 2; 14 | 15 | assert_eq!(res.len, 8 * 3, "expected 24, got {}", res.len); 16 | assert_eq!(res.get_data()[..4], expected[..4]); 17 | } 18 | 19 | #[test] 20 | fn push_bits_half() { 21 | let mut expected = [0u8; 64]; 22 | let mut res = CompactQR::with_len(64); 23 | 24 | res.push_bits(0b1111, 4); 25 | assert_eq!(res.len, 4, "expected 4, got {}", res.len); 26 | res.push_bits(0, 8); 27 | assert_eq!(res.len, 12, "expected 12, got {}", res.len); 28 | res.push_bits(0b1111, 4); 29 | assert_eq!(res.len, 16, "expected 16, got {}", res.len); 30 | 31 | expected[0] = 0b1111_0000; 32 | expected[1] = 0b0000_1111; 33 | 34 | assert_eq!(res.get_data()[..4], expected[..4]); 35 | } 36 | 37 | #[test] 38 | fn push_bits_random() { 39 | let mut expected = [0u8; 64]; 40 | let mut res = CompactQR::with_len(64); 41 | 42 | res.push_bits(0b1111, 2); 43 | expected[0] = 0b1100_0000; 44 | assert_eq!(res.len, 2, "expected 2, got {}", res.len); 45 | assert_eq!(res.get_data()[..4], expected[..4]); 46 | 47 | res.push_bits(0, 1); 48 | expected[0] = 0b1100_0000; 49 | assert_eq!(res.len, 3, "expected 3, got {}", res.len); 50 | assert_eq!(res.get_data()[..4], expected[..4]); 51 | 52 | res.push_bits(5, 3); 53 | expected[0] = 0b1101_0100; 54 | assert_eq!(res.len, 6, "expected 6, got {}", res.len); 55 | assert_eq!(res.get_data()[..4], expected[..4]); 56 | 57 | res.push_bits(0b1101, 2); 58 | expected[0] = 0b1101_0101; 59 | assert_eq!(res.len, 8, "expected 8, got {}", res.len); 60 | assert_eq!(res.get_data()[..4], expected[..4]); 61 | } 62 | 63 | #[test] 64 | fn push_bits_push8() { 65 | let mut expected = [0u8; 64]; 66 | let mut res = CompactQR::with_len(64); 67 | 68 | res.push_bits(0b1111, 3); 69 | expected[0] = 0b1110_0000; 70 | assert_eq!(res.get_data()[..4], expected[..4]); 71 | 72 | res.push_u8(0b1001_1110); 73 | expected[0] = 0b1111_0011; 74 | expected[1] = 0b1100_0000; 75 | assert_eq!(res.get_data()[..4], expected[..4]); 76 | } 77 | 78 | #[test] 79 | fn push_bits_push8_2() { 80 | let mut expected = [0u8; 64]; 81 | let mut res = CompactQR::with_len(64); 82 | 83 | res.push_bits(0b1111, 3); 84 | expected[0] = 0b1110_0000; 85 | assert_eq!(res.get_data()[..4], expected[..4]); 86 | 87 | res.push_u8(0b1001_1110); 88 | expected[0] = 0b1111_0011; 89 | expected[1] = 0b1100_0000; 90 | assert_eq!(res.get_data()[..4], expected[..4]); 91 | 92 | res.push_u8(0b1001_1110); 93 | expected[0] = 0b1111_0011; 94 | expected[1] = 0b1101_0011; 95 | expected[2] = 0b1100_0000; 96 | assert_eq!(res.get_data()[..4], expected[..4]); 97 | } 98 | 99 | #[test] 100 | fn push8_push_bits() { 101 | let mut expected = [0u8; 64]; 102 | let mut res = CompactQR::with_len(64); 103 | 104 | res.push_u8(0b1001_1110); 105 | expected[0] = 0b1001_1110; 106 | assert_eq!(res.get_data()[..4], expected[..4]); 107 | 108 | res.push_bits(0b1_1011_1001_1110, 13); 109 | expected[0] = 0b1001_1110; 110 | expected[1] = 0b1101_1100; 111 | expected[2] = 0b1111_0000; 112 | assert_eq!(res.get_data()[..4], expected[..4]); 113 | } 114 | 115 | #[test] 116 | fn push_slice() { 117 | let mut expected = [0u8; 64]; 118 | let mut res = CompactQR::with_len(64); 119 | 120 | res.push_u8_slice(&[0b1001_1110, 0b1001_1110, 0b1001_1110]); 121 | expected[0] = 0b1001_1110; 122 | expected[1] = 0b1001_1110; 123 | expected[2] = 0b1001_1110; 124 | assert_eq!(res.get_data()[..4], expected[..4]); 125 | } 126 | 127 | #[test] 128 | fn push_slice_off() { 129 | let mut expected = [0u8; 64]; 130 | let mut res = CompactQR::with_len(64); 131 | 132 | res.push_bits(0b1111, 3); 133 | expected[0] = 0b1110_0000; 134 | assert_eq!(res.get_data()[..4], expected[..4]); 135 | 136 | res.push_u8_slice(&[0b0000_0000, 0b1111_1111, 0b0000_0000]); 137 | expected[0] = 0b1110_0000; 138 | expected[1] = 0b0001_1111; 139 | expected[2] = 0b1110_0000; 140 | expected[3] = 0b0000_0000; 141 | assert_eq!(res.get_data()[..4], expected[..4]); 142 | } 143 | 144 | #[test] 145 | fn push_bitfs_off() { 146 | let mut expected = [0u8; 64]; 147 | let mut res = CompactQR::with_len(64); 148 | 149 | res.push_bits(0b0_0000_0000_0000_0000, 17); 150 | expected[0] |= 0b0000_0000; 151 | expected[1] |= 0b0000_0000; 152 | expected[2] |= 0b0000_0000; 153 | assert_eq!(res.len, 17); 154 | assert_eq!(res.get_data()[..8], expected[..8]); 155 | 156 | res.push_bits(0b1_1111_1111_1111_1111, 17); 157 | expected[2] |= 0b0111_1111; 158 | expected[3] |= 0b1111_1111; 159 | expected[4] |= 0b1100_0000; 160 | assert_eq!(res.get_data()[..8], expected[..8]); 161 | 162 | res.push_bits(0b0_0000_0000_0000_0000, 17); 163 | expected[4] |= 0b1100_0000; 164 | expected[5] |= 0b0000_0000; 165 | expected[6] |= 0b0000_0000; 166 | assert_eq!(res.get_data()[..8], expected[..8]); 167 | 168 | res.push_bits(0b1, 2); 169 | expected[5] |= 0b0000_0000; 170 | expected[6] |= 0b0000_1000; 171 | assert_eq!(res.get_data()[..8], expected[..8]); 172 | 173 | res.push_u8(0b1111_1111); 174 | expected[6] |= 0b0000_1111; 175 | expected[7] |= 0b1111_1000; 176 | assert_eq!(res.get_data()[..8], expected[..8]); 177 | } 178 | 179 | #[test] 180 | fn push_random() { 181 | let mut expected = [0u8; 64]; 182 | let mut res = CompactQR::with_len(64); 183 | 184 | res.push_bits(1, 1); 185 | expected[0] = 0b1000_0000; 186 | assert_eq!(res.get_data()[..8], expected[..8]); 187 | 188 | res.push_u8(0b1010_1010); 189 | expected[0] = 0b1101_0101; 190 | expected[1] = 0b0000_0000; 191 | assert_eq!(res.get_data()[..8], expected[..8]); 192 | 193 | res.push_bits(1, 1); 194 | expected[1] = 0b0100_0000; 195 | assert_eq!(res.get_data()[..8], expected[..8]); 196 | 197 | res.push_bits(1, 3); 198 | expected[1] = 0b0100_1000; 199 | assert_eq!(res.get_data()[..8], expected[..8]); 200 | } 201 | -------------------------------------------------------------------------------- /src/tests/datamasking.rs: -------------------------------------------------------------------------------- 1 | use crate::datamasking::Mask; 2 | use crate::QRCode; 3 | 4 | const F: bool = false; 5 | const T: bool = true; 6 | 7 | #[test] 8 | fn mask_checkerboard_test() { 9 | let mut qr = QRCode::default(10); 10 | crate::datamasking::mask(&mut qr, Mask::Checkerboard); 11 | 12 | #[rustfmt::skip] 13 | let qr_bool = [ 14 | &qr[0][..10], &qr[1][..10], &qr[2][..10], &qr[3][..10], &qr[4][..10], 15 | &qr[5][..10], &qr[6][..10], &qr[7][..10], &qr[8][..10], &qr[9][..10], 16 | ]; 17 | 18 | #[rustfmt::skip] 19 | assert_eq!( 20 | qr_bool, 21 | [ 22 | [T, F, T, F, T, F, T, F, T, F], 23 | [F, T, F, T, F, T, F, T, F, T], 24 | [T, F, T, F, T, F, T, F, T, F], 25 | [F, T, F, T, F, T, F, T, F, T], 26 | [T, F, T, F, T, F, T, F, T, F], 27 | [F, T, F, T, F, T, F, T, F, T], 28 | [T, F, T, F, T, F, T, F, T, F], 29 | [F, T, F, T, F, T, F, T, F, T], 30 | [T, F, T, F, T, F, T, F, T, F], 31 | [F, T, F, T, F, T, F, T, F, T], 32 | ] 33 | ); 34 | } 35 | 36 | #[test] 37 | fn mask_horizontal_test() { 38 | let mut qr = QRCode::default(10); 39 | crate::datamasking::mask(&mut qr, Mask::HorizontalLines); 40 | 41 | #[rustfmt::skip] 42 | let qr_bool = [ 43 | &qr[0][..10], &qr[1][..10], &qr[2][..10], &qr[3][..10], &qr[4][..10], 44 | &qr[5][..10], &qr[6][..10], &qr[7][..10], &qr[8][..10], &qr[9][..10], 45 | ]; 46 | 47 | #[rustfmt::skip] 48 | assert_eq!( 49 | qr_bool, 50 | [ 51 | [T, T, T, T, T, T, T, T, T, T], 52 | [F, F, F, F, F, F, F, F, F, F], 53 | [T, T, T, T, T, T, T, T, T, T], 54 | [F, F, F, F, F, F, F, F, F, F], 55 | [T, T, T, T, T, T, T, T, T, T], 56 | [F, F, F, F, F, F, F, F, F, F], 57 | [T, T, T, T, T, T, T, T, T, T], 58 | [F, F, F, F, F, F, F, F, F, F], 59 | [T, T, T, T, T, T, T, T, T, T], 60 | [F, F, F, F, F, F, F, F, F, F], 61 | ] 62 | ); 63 | } 64 | 65 | #[test] 66 | fn mask_vertical_test() { 67 | let mut qr = QRCode::default(10); 68 | crate::datamasking::mask(&mut qr, Mask::VerticalLines); 69 | 70 | #[rustfmt::skip] 71 | let qr_bool = [ 72 | &qr[0][..10], &qr[1][..10], &qr[2][..10], &qr[3][..10], &qr[4][..10], 73 | &qr[5][..10], &qr[6][..10], &qr[7][..10], &qr[8][..10], &qr[9][..10], 74 | ]; 75 | 76 | #[rustfmt::skip] 77 | assert_eq!( 78 | qr_bool, 79 | [ 80 | [T, F, F, T, F, F, T, F, F, T], 81 | [T, F, F, T, F, F, T, F, F, T], 82 | [T, F, F, T, F, F, T, F, F, T], 83 | [T, F, F, T, F, F, T, F, F, T], 84 | [T, F, F, T, F, F, T, F, F, T], 85 | [T, F, F, T, F, F, T, F, F, T], 86 | [T, F, F, T, F, F, T, F, F, T], 87 | [T, F, F, T, F, F, T, F, F, T], 88 | [T, F, F, T, F, F, T, F, F, T], 89 | [T, F, F, T, F, F, T, F, F, T], 90 | ] 91 | ); 92 | } 93 | 94 | #[test] 95 | fn mask_diagonal_test() { 96 | let mut qr = QRCode::default(10); 97 | crate::datamasking::mask(&mut qr, Mask::DiagonalLines); 98 | 99 | #[rustfmt::skip] 100 | let qr_bool = [ 101 | &qr[0][..10], &qr[1][..10], &qr[2][..10], &qr[3][..10], &qr[4][..10], 102 | &qr[5][..10], &qr[6][..10], &qr[7][..10], &qr[8][..10], &qr[9][..10], 103 | ]; 104 | 105 | #[rustfmt::skip] 106 | assert_eq!( 107 | qr_bool, 108 | [ 109 | [T, F, F, T, F, F, T, F, F, T], 110 | [F, F, T, F, F, T, F, F, T, F], 111 | [F, T, F, F, T, F, F, T, F, F], 112 | [T, F, F, T, F, F, T, F, F, T], 113 | [F, F, T, F, F, T, F, F, T, F], 114 | [F, T, F, F, T, F, F, T, F, F], 115 | [T, F, F, T, F, F, T, F, F, T], 116 | [F, F, T, F, F, T, F, F, T, F], 117 | [F, T, F, F, T, F, F, T, F, F], 118 | [T, F, F, T, F, F, T, F, F, T], 119 | ] 120 | ); 121 | } 122 | 123 | #[test] 124 | fn mask_large_checkerboard_test() { 125 | let mut qr = QRCode::default(10); 126 | crate::datamasking::mask(&mut qr, Mask::LargeCheckerboard); 127 | 128 | #[rustfmt::skip] 129 | let qr_bool = [ 130 | &qr[0][..10], &qr[1][..10], &qr[2][..10], &qr[3][..10], &qr[4][..10], 131 | &qr[5][..10], &qr[6][..10], &qr[7][..10], &qr[8][..10], &qr[9][..10], 132 | ]; 133 | 134 | #[rustfmt::skip] 135 | assert_eq!( 136 | qr_bool, 137 | [ 138 | [T, T, T, F, F, F, T, T, T, F], 139 | [T, T, T, F, F, F, T, T, T, F], 140 | [F, F, F, T, T, T, F, F, F, T], 141 | [F, F, F, T, T, T, F, F, F, T], 142 | [T, T, T, F, F, F, T, T, T, F], 143 | [T, T, T, F, F, F, T, T, T, F], 144 | [F, F, F, T, T, T, F, F, F, T], 145 | [F, F, F, T, T, T, F, F, F, T], 146 | [T, T, T, F, F, F, T, T, T, F], 147 | [T, T, T, F, F, F, T, T, T, F], 148 | ] 149 | ); 150 | } 151 | 152 | #[test] 153 | fn mask_field_test() { 154 | let mut qr = QRCode::default(10); 155 | crate::datamasking::mask(&mut qr, Mask::Fields); 156 | 157 | #[rustfmt::skip] 158 | let qr_bool = [ 159 | &qr[0][..10], &qr[1][..10], &qr[2][..10], &qr[3][..10], &qr[4][..10], 160 | &qr[5][..10], &qr[6][..10], &qr[7][..10], &qr[8][..10], &qr[9][..10], 161 | ]; 162 | 163 | #[rustfmt::skip] 164 | assert_eq!( 165 | qr_bool, 166 | [ 167 | [T, T, T, T, T, T, T, T, T, T], 168 | [T, F, F, F, F, F, T, F, F, F], 169 | [T, F, F, T, F, F, T, F, F, T], 170 | [T, F, T, F, T, F, T, F, T, F], 171 | [T, F, F, T, F, F, T, F, F, T], 172 | [T, F, F, F, F, F, T, F, F, F], 173 | [T, T, T, T, T, T, T, T, T, T], 174 | [T, F, F, F, F, F, T, F, F, F], 175 | [T, F, F, T, F, F, T, F, F, T], 176 | [T, F, T, F, T, F, T, F, T, F], 177 | ] 178 | ); 179 | } 180 | 181 | #[test] 182 | fn mask_diamond_test() { 183 | let mut qr = QRCode::default(10); 184 | crate::datamasking::mask(&mut qr, Mask::Diamonds); 185 | 186 | #[rustfmt::skip] 187 | let qr_bool = [ 188 | &qr[0][..10], &qr[1][..10], &qr[2][..10], &qr[3][..10], &qr[4][..10], 189 | &qr[5][..10], &qr[6][..10], &qr[7][..10], &qr[8][..10], &qr[9][..10], 190 | ]; 191 | 192 | #[rustfmt::skip] 193 | assert_eq!( 194 | qr_bool, 195 | [ 196 | [T, T, T, T, T, T, T, T, T, T], 197 | [T, T, T, F, F, F, T, T, T, F], 198 | [T, T, F, T, T, F, T, T, F, T], 199 | [T, F, T, F, T, F, T, F, T, F], 200 | [T, F, T, T, F, T, T, F, T, T], 201 | [T, F, F, F, T, T, T, F, F, F], 202 | [T, T, T, T, T, T, T, T, T, T], 203 | [T, T, T, F, F, F, T, T, T, F], 204 | [T, T, F, T, T, F, T, T, F, T], 205 | [T, F, T, F, T, F, T, F, T, F], 206 | ] 207 | ); 208 | } 209 | 210 | #[test] 211 | fn mask_meadow_test() { 212 | let mut qr = QRCode::default(10); 213 | 214 | crate::datamasking::mask(&mut qr, Mask::Meadow); 215 | 216 | #[rustfmt::skip] 217 | let qr_bool = [ 218 | &qr[0][..10], &qr[1][..10], &qr[2][..10], &qr[3][..10], &qr[4][..10], 219 | &qr[5][..10], &qr[6][..10], &qr[7][..10], &qr[8][..10], &qr[9][..10], 220 | ]; 221 | 222 | #[rustfmt::skip] 223 | assert_eq!( 224 | qr_bool, 225 | [ 226 | [T, F, T, F, T, F, T, F, T, F], 227 | [F, F, F, T, T, T, F, F, F, T], 228 | [T, F, F, F, T, T, T, F, F, F], 229 | [F, T, F, T, F, T, F, T, F, T], 230 | [T, T, T, F, F, F, T, T, T, F], 231 | [F, T, T, T, F, F, F, T, T, T], 232 | [T, F, T, F, T, F, T, F, T, F], 233 | [F, F, F, T, T, T, F, F, F, T], 234 | [T, F, F, F, T, T, T, F, F, F], 235 | [F, T, F, T, F, T, F, T, F, T], 236 | ] 237 | ); 238 | } 239 | -------------------------------------------------------------------------------- /src/tests/encode.rs: -------------------------------------------------------------------------------- 1 | use crate::compact::{CompactQR, KEEP_LAST}; 2 | use crate::encode; 3 | use crate::encode::Mode; 4 | use crate::hardcode::cci_bits; 5 | 6 | #[test] 7 | fn best_encoding_numeric_0() { 8 | let res = encode::best_encoding(b"589492"); 9 | assert_eq!(Mode::Numeric, res); 10 | } 11 | 12 | #[test] 13 | fn best_encoding_numeric_1() { 14 | let res = encode::best_encoding(b"95904409521090298052194059450950249521940"); 15 | assert_eq!(Mode::Numeric, res); 16 | } 17 | 18 | #[test] 19 | fn best_encoding_alnum_0() { 20 | let res = encode::best_encoding(b"HELLO WORLD"); 21 | assert_eq!(Mode::Alphanumeric, res); 22 | } 23 | 24 | #[test] 25 | fn best_encoding_alnum_1() { 26 | let res = encode::best_encoding(b"HELLO WORLD MY NAME IS ERWAN VIVIEN: THIS IS A TEST//////"); 27 | assert_eq!(Mode::Alphanumeric, res); 28 | } 29 | 30 | #[test] 31 | fn best_encoding_byte_0() { 32 | let res = encode::best_encoding(b"589492h"); 33 | assert_eq!(Mode::Byte, res); 34 | } 35 | 36 | #[test] 37 | fn best_encoding_byte_1() { 38 | let res = encode::best_encoding(b"HELLO WORLD!"); 39 | assert_eq!(Mode::Byte, res); 40 | } 41 | 42 | #[test] 43 | fn best_encoding_byte_2() { 44 | let res = encode::best_encoding(b"HELLO WORLD MY NAME, IS ERWAN VIVIEN: THIS IS A TEST//////"); 45 | assert_eq!(Mode::Byte, res); 46 | } 47 | 48 | fn test_encode_header(compact: &CompactQR, input: &[u8], expected_mode: Mode) { 49 | let res = compact.get_data(); 50 | let mode = match res[0] >> 4 { 51 | 0b0001 => Mode::Numeric, 52 | 0b0010 => Mode::Alphanumeric, 53 | 0b0100 => Mode::Byte, 54 | _ => panic!("Invalid encoding mode"), 55 | }; 56 | 57 | assert_eq!( 58 | expected_mode, mode, 59 | "Encoding mode should be {:?}", 60 | expected_mode 61 | ); 62 | 63 | let cci = cci_bits(crate::Version::V01, mode) as u16; 64 | let character_count: u16 = { 65 | let first_nb_bits = 4; 66 | let second_nb_bits = cci - first_nb_bits; 67 | 68 | let first = ((res[0] & 0b0000_1111) as u16) << second_nb_bits; 69 | let second_mask = u8::MAX << (8 - second_nb_bits); 70 | let second = ((res[1] & second_mask) as u16) >> (8 - second_nb_bits); 71 | 72 | first | second 73 | }; 74 | 75 | assert_eq!( 76 | character_count, 77 | input.len() as u16, 78 | "Input length, should be {}", 79 | input.len() 80 | ); 81 | } 82 | 83 | #[test] 84 | fn encode_byte_1() { 85 | let mut compact = CompactQR::new(); 86 | const INPUT: &[u8] = b"Hello WORLD!"; 87 | encode::encode_byte(&mut compact, INPUT, 8); 88 | 89 | test_encode_header(&compact, INPUT, Mode::Byte); 90 | 91 | let res = compact.get_data(); 92 | for (i, b) in INPUT.iter().enumerate() { 93 | assert_eq!(res[1 + i] & 0b111, *b >> 4, "Left part at index {}", i); 94 | assert_eq!(res[2 + i] >> 4, *b & 0b1111, "Right part at index {}", i); 95 | } 96 | } 97 | 98 | #[test] 99 | fn encode_alphanumeric_1() { 100 | let mut compact = CompactQR::new(); 101 | const INPUT: &[u8] = b"HELLO WORLD"; 102 | encode::encode_alphanumeric(&mut compact, INPUT, 9); 103 | 104 | test_encode_header(&compact, INPUT, Mode::Alphanumeric); 105 | 106 | let keep_last = KEEP_LAST.map(|x| x as u16); 107 | 108 | let res = compact 109 | .get_data() 110 | .iter() 111 | .map(|&x| u16::from(x)) 112 | .collect::>(); 113 | 114 | // 13, 'HE' 115 | assert_eq!(res[1] & 0b0000_0111, (17 * 45 + 14) >> 8); 116 | assert_eq!(res[2] & 0b1111_1111, (17 * 45 + 14) & keep_last[8]); 117 | // 24, 'LL' 118 | assert_eq!(res[3] & 0b1111_1111, (21 * 45 + 21) >> 3); 119 | assert_eq!(res[4] & 0b1110_0000, (21 * 45 + 21) << 5 & keep_last[8]); 120 | // 35, 'O ' 121 | assert_eq!(res[4] & 0b0001_1111, (24 * 45 + 36) >> 6); 122 | assert_eq!(res[5] & 0b1111_1100, (24 * 45 + 36) << 2 & keep_last[8]); 123 | // 46, 'WO' 124 | assert_eq!(res[5] & 0b0000_0011, (32 * 45 + 24) >> 9); 125 | assert_eq!(res[6] & 0b1111_1111, (32 * 45 + 24) >> 1 & keep_last[8]); 126 | assert_eq!(res[7] & 0b1000_0000, (32 * 45 + 24) << 8 & keep_last[8]); 127 | // 57, 'RL' 128 | assert_eq!(res[7] & 0b0111_1111, (27 * 45 + 21) >> 4); 129 | assert_eq!(res[8] & 0b1111_0000, (27 * 45 + 21) << 4 & keep_last[8]); 130 | // 68, 'D' 131 | assert_eq!(res[8] & 0b0000_1111, (13) >> 2); 132 | assert_eq!(res[9] & 0b1100_0000, (13) << 6 & keep_last[8]); 133 | } 134 | 135 | #[test] 136 | fn encode_numeric_1() { 137 | let mut compact = CompactQR::new(); 138 | const INPUT: &[u8] = b"5894"; 139 | encode::encode_numeric(&mut compact, INPUT, 10); 140 | 141 | test_encode_header(&compact, INPUT, Mode::Numeric); 142 | 143 | let keep_last = KEEP_LAST.map(|x| x as u16); 144 | 145 | let res = compact 146 | .get_data() 147 | .iter() 148 | .map(|&x| u16::from(x)) 149 | .collect::>(); 150 | 151 | // 13, '589' 152 | assert_eq!(res[1] & 0b0000_0011, 589 >> 8); 153 | assert_eq!(res[2] & 0b1111_1111, (589 << 0) & keep_last[8]); 154 | // 24, '4' 155 | assert_eq!(res[3] & 0b1111_0000, (4 << 4) & keep_last[8]); 156 | } 157 | 158 | #[test] 159 | fn encode_numeric_2() { 160 | let mut compact = CompactQR::new(); 161 | const INPUT: &[u8] = b"58949"; 162 | encode::encode_numeric(&mut compact, INPUT, 10); 163 | 164 | test_encode_header(&compact, INPUT, Mode::Numeric); 165 | 166 | let keep_last = KEEP_LAST.map(|x| x as u16); 167 | 168 | let res = compact 169 | .get_data() 170 | .iter() 171 | .map(|&x| u16::from(x)) 172 | .collect::>(); 173 | 174 | // 13, '589' 175 | assert_eq!(res[1] & 0b0000_0011, 589 >> 8); 176 | assert_eq!(res[2] & 0b1111_1111, (589 << 0) & keep_last[8]); 177 | // 24, '49' 178 | assert_eq!(res[3] & 0b1111_1110, 49 << 1 & keep_last[8]); 179 | } 180 | 181 | #[test] 182 | fn encode_numeric_3() { 183 | let mut compact = CompactQR::new(); 184 | const INPUT: &[u8] = b"589491"; 185 | encode::encode_numeric(&mut compact, INPUT, 10); 186 | 187 | test_encode_header(&compact, INPUT, Mode::Numeric); 188 | 189 | let keep_last = KEEP_LAST.map(|x| x as u16); 190 | 191 | let res = compact 192 | .get_data() 193 | .iter() 194 | .map(|&x| u16::from(x)) 195 | .collect::>(); 196 | 197 | // 13, '589' 198 | assert_eq!(res[1] & 0b0000_0011, 589 >> 8); 199 | assert_eq!(res[2] & 0b1111_1111, (589 << 0) & keep_last[8]); 200 | // 24, '491' 201 | assert_eq!(res[3] & 0b1111_1111, (491 >> 2) & keep_last[8]); 202 | assert_eq!(res[4] & 0b1100_0000, (491 << 6) & keep_last[8]); 203 | } 204 | 205 | #[test] 206 | fn encode_numeric_4() { 207 | let mut compact = CompactQR::new(); 208 | const INPUT: &[u8] = b"200505150001"; 209 | encode::encode_numeric(&mut compact, INPUT, 10); 210 | 211 | test_encode_header(&compact, INPUT, Mode::Numeric); 212 | 213 | let keep_last = KEEP_LAST.map(|x| x as u16); 214 | 215 | let res = compact 216 | .get_data() 217 | .iter() 218 | .map(|&x| u16::from(x)) 219 | .collect::>(); 220 | 221 | // 13, '200' 222 | assert_eq!(res[1] & 0b0000_0011, 200 >> 8); 223 | assert_eq!(res[2] & 0b1111_1111, (200 << 0) & keep_last[8]); 224 | // 24, '505' 225 | assert_eq!(res[3] & 0b1111_1111, (505 >> 2) & keep_last[8]); 226 | assert_eq!(res[4] & 0b1100_0000, (505 << 6) & keep_last[8]); 227 | // 35, '150' 228 | assert_eq!(res[4] & 0b0011_1111, (150 >> 4) & keep_last[8]); 229 | assert_eq!(res[5] & 0b1111_0000, (150 << 4) & keep_last[8]); 230 | // 46, '001' 231 | assert_eq!(res[5] & 0b0000_1111, (1) >> 6); 232 | assert_eq!(res[6] & 0b1111_1100, (1) << 2 & keep_last[8]); 233 | } 234 | -------------------------------------------------------------------------------- /src/tests/error_correction.rs: -------------------------------------------------------------------------------- 1 | use crate::{hardcode, polynomials, Version, ECL}; 2 | 3 | #[test] 4 | fn error_code_computation_01() { 5 | let version = Version::V05; 6 | let quality = ECL::Q; 7 | 8 | let vec = [67, 85, 70, 134, 87, 38, 85, 194, 119, 50, 6, 18, 6, 103, 38]; 9 | let generator_polynomials = hardcode::get_polynomial(version, quality); 10 | 11 | let div = polynomials::division(&vec, generator_polynomials); 12 | assert_eq!( 13 | div[255 - generator_polynomials.len() + 1..], 14 | [213, 199, 11, 45, 115, 247, 241, 223, 229, 248, 154, 117, 154, 111, 86, 161, 111, 39] 15 | ) 16 | } 17 | 18 | #[test] 19 | fn error_code_computation_02() { 20 | let version = Version::V05; 21 | let quality = ECL::Q; 22 | 23 | let vec = [ 24 | 246, 246, 66, 7, 118, 134, 242, 7, 38, 86, 22, 198, 199, 146, 6, 25 | ]; 26 | 27 | let generator_polynomials = hardcode::get_polynomial(version, quality); 28 | 29 | let div = polynomials::division(&vec, generator_polynomials); 30 | 31 | assert_eq!( 32 | div[255 - generator_polynomials.len() + 1..], 33 | [87, 204, 96, 60, 202, 182, 124, 157, 200, 134, 27, 129, 209, 17, 163, 163, 120, 133] 34 | ) 35 | } 36 | 37 | #[test] 38 | fn error_code_computation_03() { 39 | let version = Version::V05; 40 | let quality = ECL::Q; 41 | 42 | let vec = [ 43 | 182, 230, 247, 119, 50, 7, 118, 134, 87, 38, 82, 6, 134, 151, 50, 7, 44 | ]; 45 | let generator_polynomials = hardcode::get_polynomial(version, quality); 46 | 47 | let div = polynomials::division(&vec, generator_polynomials); 48 | 49 | assert_eq!( 50 | div[255 - generator_polynomials.len() + 1..], 51 | [148, 116, 177, 212, 76, 133, 75, 242, 238, 76, 195, 230, 189, 10, 108, 240, 192, 141] 52 | ) 53 | } 54 | 55 | #[test] 56 | fn error_code_computation_04() { 57 | let version = Version::V05; 58 | let quality = ECL::Q; 59 | 60 | let vec = [ 61 | 70, 247, 118, 86, 194, 6, 151, 50, 16, 236, 17, 236, 17, 236, 17, 236, 62 | ]; 63 | let generator_polynomials = hardcode::get_polynomial(version, quality); 64 | 65 | let div = polynomials::division(&vec, generator_polynomials); 66 | 67 | assert_eq!( 68 | div[255 - generator_polynomials.len() + 1..], 69 | [235, 159, 5, 173, 24, 147, 59, 33, 106, 40, 255, 172, 82, 2, 131, 32, 178, 236] 70 | ) 71 | } 72 | 73 | #[test] 74 | fn error_code_computation_821043386() { 75 | let tmp1 = [ 76 | 29, 10, 145, 40, 0, 90, 126, 137, 221, 186, 137, 39, 208, 250, 199, 176, 202, 124, 200, 85, 77 | 63, 254, 78 | ]; 79 | let tmp2 = [ 80 | 0, 156, 45, 183, 29, 151, 219, 54, 96, 249, 24, 136, 5, 241, 175, 189, 28, 75, 234, 150, 81 | 148, 23, 9, 202, 162, 68, 250, 140, 24, 151, 82 | ]; 83 | let div = polynomials::division(&tmp1, &tmp2); 84 | assert_eq!( 85 | div[255 - 29..], 86 | ([ 87 | 0, 85, 37, 253, 234, 217, 13, 16, 62, 107, 80, 72, 22, 66, 240, 139, 57, 109, 195, 68, 88 | 121, 32, 206, 196, 117, 252, 175, 189, 167 89 | ]) 90 | ) 91 | } 92 | 93 | #[test] 94 | fn error_code_computation_struct_31_0() { 95 | let tmp1 = [28, 195, 100, 36, 175, 11, 35, 243, 28, 137, 59, 182, 193]; 96 | let tmp2 = [ 97 | 0, 173, 125, 158, 2, 103, 182, 118, 17, 145, 201, 111, 28, 165, 53, 161, 21, 245, 142, 13, 98 | 102, 48, 227, 153, 145, 218, 70, 99 | ]; 100 | let div = polynomials::division(&tmp1, &tmp2); 101 | assert_eq!( 102 | div[255 - 26..], 103 | ([ 104 | 68, 150, 68, 205, 197, 78, 104, 100, 177, 0, 185, 7, 178, 106, 110, 170, 101, 222, 45, 105 | 74, 31, 75, 3, 126, 216, 208 106 | ]) 107 | ) 108 | } 109 | 110 | #[test] 111 | fn error_code_computation_struct_31_1() { 112 | let tmp1 = [35, 37, 251, 189, 8, 169, 15, 34, 59, 137, 187, 114, 134]; 113 | let tmp2 = [ 114 | 0, 173, 125, 158, 2, 103, 182, 118, 17, 145, 201, 111, 28, 165, 53, 161, 21, 245, 142, 13, 115 | 102, 48, 227, 153, 145, 218, 70, 116 | ]; 117 | let div = polynomials::division(&tmp1, &tmp2); 118 | assert_eq!( 119 | div[255 - 26..], 120 | ([ 121 | 246, 74, 169, 24, 210, 247, 165, 59, 102, 186, 144, 234, 202, 247, 84, 191, 166, 28, 122 | 140, 190, 219, 81, 72, 34, 159, 0 123 | ]) 124 | ) 125 | } 126 | 127 | #[test] 128 | fn division_small_1() { 129 | let a = polynomials::division(&[32, 9], &[0, 0]); 130 | assert_eq!(&a[254..], &[41]) 131 | } 132 | -------------------------------------------------------------------------------- /src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | mod bytes; 2 | mod compact; 3 | mod datamasking; 4 | mod default; 5 | mod encode; 6 | mod error_correction; 7 | mod polynomials; 8 | mod score; 9 | mod structure; 10 | mod svg; 11 | mod version; 12 | -------------------------------------------------------------------------------- /src/tests/svg.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "svg")] 2 | #[test] 3 | fn it_embeds_an_image_via_data_uri() { 4 | use crate::convert::svg::SvgBuilder; 5 | use crate::convert::Builder; 6 | use crate::{QRBuilder, ECL}; 7 | 8 | let image_base64 = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAFUlEQVR4AWP4oyVDEhrGGkY1jGoAABACQhA+7XDPAAAAAElFTkSuQmCC"; 9 | let data_uri = format!("data:image/png;base64,{image_base64}"); 10 | 11 | let qrcode = QRBuilder::new("https://example.com/") 12 | .ecl(ECL::H) 13 | .build() 14 | .unwrap(); 15 | 16 | let svg = SvgBuilder::default() 17 | .image(data_uri.clone()) 18 | .to_str(&qrcode); 19 | 20 | let expected_href = format!(r#"href="{data_uri}""#); 21 | assert!(svg.contains(&expected_href)); 22 | } 23 | 24 | #[cfg(feature = "svg")] 25 | #[test] 26 | fn check_svg_is_not_inverted() { 27 | use crate::convert::svg::SvgBuilder; 28 | use crate::convert::Builder; 29 | use crate::{QRBuilder, Version, ECL}; 30 | 31 | let qrcode = QRBuilder::new("Test") 32 | .ecl(ECL::M) 33 | .version(Version::V01) 34 | .build() 35 | .unwrap(); 36 | 37 | const MARGIN: usize = 4; 38 | let svg = SvgBuilder::default().margin(MARGIN).to_str(&qrcode); 39 | 40 | let size = qrcode.size; 41 | for y in 0..size { 42 | for x in 0..size { 43 | let index = y * size + x; 44 | let expected = qrcode.data[index]; 45 | if expected.value() { 46 | let expected = format!(r#"M{x},{y}"#, x = x + MARGIN, y = y + MARGIN); 47 | assert!(svg.contains(&expected)); 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/wasm.rs: -------------------------------------------------------------------------------- 1 | use crate::QRCode; 2 | #[cfg(feature = "svg")] 3 | use crate::{convert, Version, ECL}; 4 | #[cfg(feature = "wasm-bindgen")] 5 | use wasm_bindgen::prelude::*; 6 | 7 | fn bool_to_u8(qr: QRCode) -> Vec { 8 | let dim = qr.size; 9 | qr.data[..dim * dim] 10 | .iter() 11 | .map(|x| u8::from(x.value())) 12 | .collect() 13 | } 14 | 15 | /// Generate a QR code from a string. All parameters are automatically set. 16 | #[cfg_attr(feature = "wasm-bindgen", wasm_bindgen)] 17 | #[must_use] 18 | pub fn qr(content: &str) -> Vec { 19 | let qrcode = QRCode::new(content.as_bytes(), None, None, None, None); 20 | qrcode.map(bool_to_u8).unwrap_or(Vec::new()) 21 | } 22 | 23 | /// Configuration for the SVG output. 24 | #[cfg(feature = "svg")] 25 | #[cfg_attr(feature = "wasm-bindgen", wasm_bindgen)] 26 | #[derive(Debug, Clone)] 27 | pub struct SvgOptions { 28 | shape: convert::Shape, 29 | module_color: Vec, 30 | margin: usize, 31 | 32 | ecl: Option, 33 | version: Option, 34 | 35 | background_color: Vec, 36 | 37 | image: String, 38 | image_background_color: Vec, 39 | image_background_shape: convert::ImageBackgroundShape, 40 | image_size: Option, 41 | image_gap: Option, 42 | image_position: Vec, 43 | } 44 | 45 | #[cfg_attr(feature = "wasm-bindgen", wasm_bindgen)] 46 | #[cfg(feature = "svg")] 47 | impl SvgOptions { 48 | fn color_to_code(color: String) -> Vec { 49 | let mut color = color; 50 | if color.starts_with('#') { 51 | color.remove(0); 52 | } 53 | let color = color.as_bytes(); 54 | let color = color.chunks_exact(2); 55 | let color = color.map(|x| u8::from_str_radix(std::str::from_utf8(x).unwrap(), 16).unwrap()); 56 | 57 | let mut color = color.collect::>(); 58 | if color.len() == 3 { 59 | color.push(255); 60 | } 61 | 62 | color 63 | } 64 | 65 | /// Updates the shape of the QRCode modules. 66 | pub fn shape(self, shape: convert::Shape) -> Self { 67 | Self { shape, ..self } 68 | } 69 | 70 | /// Updates the module color of the QRCode. Tales a string in the format `#RRGGBB[AA]`. 71 | pub fn module_color(self, module_color: String) -> Self { 72 | let code = Self::color_to_code(module_color); 73 | if code.len() != 4 { 74 | return self; 75 | } 76 | Self { 77 | module_color: code, 78 | ..self 79 | } 80 | } 81 | 82 | /// Updates the margin of the QRCode. 83 | pub fn margin(self, margin: usize) -> Self { 84 | Self { margin, ..self } 85 | } 86 | 87 | /// Updates the background color of the QRCode. Tales a string in the format `#RRGGBB[AA]`. 88 | pub fn background_color(self, background_color: String) -> Self { 89 | let code = Self::color_to_code(background_color); 90 | if code.len() != 4 { 91 | return self; 92 | } 93 | Self { 94 | background_color: code, 95 | ..self 96 | } 97 | } 98 | 99 | /// Updates the image of the QRCode. Takes base64 or a url. 100 | pub fn image(self, image: String) -> Self { 101 | Self { image, ..self } 102 | } 103 | 104 | /// Updates the background color of the image. Takes a string in the format `#RRGGBB[AA]`. 105 | pub fn image_background_color(self, image_background_color: String) -> Self { 106 | let code = Self::color_to_code(image_background_color); 107 | if code.len() != 4 { 108 | return self; 109 | } 110 | 111 | Self { 112 | image_background_color: code, 113 | ..self 114 | } 115 | } 116 | 117 | /// Updates the shape of the image background. Takes an convert::ImageBackgroundShape. 118 | pub fn image_background_shape( 119 | self, 120 | image_background_shape: convert::ImageBackgroundShape, 121 | ) -> Self { 122 | Self { 123 | image_background_shape, 124 | ..self 125 | } 126 | } 127 | 128 | /// Updates the size of the image. (unit being module size). 129 | pub fn image_size(self, size: f64) -> Self { 130 | Self { 131 | image_size: Some(size), 132 | ..self 133 | } 134 | } 135 | 136 | /// Updates the gap between background color and the image. (unit being module size). 137 | pub fn image_gap(self, gap: f64) -> Self { 138 | Self { 139 | image_gap: Some(gap), 140 | ..self 141 | } 142 | } 143 | 144 | /// Updates the position of the image. Takes an array [x, y] (unit being module size). 145 | pub fn image_position(self, image_position: Vec) -> Self { 146 | if image_position.len() != 2 { 147 | return self; 148 | } 149 | 150 | Self { 151 | image_position, 152 | ..self 153 | } 154 | } 155 | 156 | /// Updates the error correction level of the QRCode (can increase the size of the QRCode) 157 | pub fn ecl(self, ecl: ECL) -> Self { 158 | Self { 159 | ecl: Some(ecl), 160 | ..self 161 | } 162 | } 163 | 164 | /// Forces the version of the QRCode 165 | pub fn version(self, version: Version) -> Self { 166 | Self { 167 | version: Some(version), 168 | ..self 169 | } 170 | } 171 | } 172 | 173 | #[cfg_attr(feature = "wasm-bindgen", wasm_bindgen)] 174 | #[cfg(feature = "svg")] 175 | impl SvgOptions { 176 | /// Creates a new SvgOptions object. 177 | #[cfg_attr(feature = "wasm-bindgen", wasm_bindgen(constructor))] 178 | pub fn new() -> Self { 179 | Self { 180 | shape: convert::Shape::Square, 181 | module_color: vec![0, 0, 0, 255], 182 | margin: 4, 183 | 184 | ecl: None, 185 | version: None, 186 | 187 | background_color: vec![255, 255, 255, 255], 188 | 189 | image: String::new(), 190 | image_background_color: vec![255, 255, 255, 255], 191 | image_background_shape: convert::ImageBackgroundShape::Square, 192 | image_size: None, 193 | image_gap: None, 194 | image_position: vec![], 195 | } 196 | } 197 | } 198 | 199 | /// Generate a QR code from a string. All parameters are automatically set. 200 | #[cfg_attr(feature = "wasm-bindgen", wasm_bindgen)] 201 | #[cfg(feature = "svg")] 202 | pub fn qr_svg(content: &str, options: SvgOptions) -> String { 203 | use crate::convert::svg::SvgBuilder; 204 | use crate::convert::Builder; 205 | let qrcode = QRCode::new(content.as_bytes(), options.ecl, options.version, None, None); 206 | 207 | let mut builder = SvgBuilder::default(); 208 | builder.shape(options.shape); 209 | builder.margin(options.margin); 210 | builder.background_color(options.background_color); 211 | builder.module_color(options.module_color); 212 | if !options.image.is_empty() { 213 | builder.image(options.image); 214 | } 215 | 216 | builder.image_background_color(options.image_background_color); 217 | builder.image_background_shape(options.image_background_shape); 218 | 219 | if let Some(image_size) = options.image_size { 220 | builder.image_size(image_size); 221 | } 222 | 223 | if let Some(image_gap) = options.image_gap { 224 | builder.image_gap(image_gap); 225 | } 226 | 227 | if options.image_position.len() == 2 { 228 | let x = options.image_position[0]; 229 | let y = options.image_position[1]; 230 | builder.image_position(x, y); 231 | } 232 | 233 | qrcode 234 | .map(|qrcode| builder.to_str(&qrcode)) 235 | .unwrap_or(String::new()) 236 | } 237 | -------------------------------------------------------------------------------- /wasm-pack.ps1: -------------------------------------------------------------------------------- 1 | $CARGO_MODE="--release" 2 | $TARGET_PATH="release" 3 | $BUILD_STD_FEATURES="panic_immediate_abort" 4 | 5 | Write-Host "Building with cargo mode: ${CARGO_MODE}" 6 | 7 | $OUTPUT_DIR="pkg" 8 | 9 | cargo +nightly build ${CARGO_MODE} ` 10 | --target wasm32-unknown-unknown ` 11 | -Z "build-std=std,panic_abort" ` 12 | -Z "build-std-features=${BUILD_STD_FEATURES}" ` 13 | --features svg,wasm-bindgen 14 | 15 | & wasm-bindgen --out-dir "${OUTPUT_DIR}" ` 16 | --web target/wasm32-unknown-unknown/${TARGET_PATH}/fast_qr.wasm 17 | 18 | & wasm-opt ` 19 | -O2 ` 20 | "${OUTPUT_DIR}/fast_qr_bg.wasm" ` 21 | -o "${OUTPUT_DIR}/fast_qr_bg.wasm" 22 | 23 | & Write-Host "Done!" 24 | -------------------------------------------------------------------------------- /wasm-pack.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | CARGO_MODE="--release" 4 | TARGET_PATH="release" 5 | BUILD_STD_FEATURES="panic_immediate_abort" 6 | 7 | echo "Building with cargo mode: ${CARGO_MODE}" 8 | 9 | OUTPUT_DIR="pkg" 10 | 11 | cargo +nightly build ${CARGO_MODE} \ 12 | --target wasm32-unknown-unknown \ 13 | -Z "build-std=std,panic_abort" \ 14 | -Z "build-std-features=${BUILD_STD_FEATURES}" \ 15 | --features svg,wasm-bindgen && \ 16 | 17 | wasm-bindgen \ 18 | --out-dir ${OUTPUT_DIR} \ 19 | --web \ 20 | "target/wasm32-unknown-unknown/${TARGET_PATH}/fast_qr.wasm" && \ 21 | 22 | wasm-opt \ 23 | -Oz \ 24 | -o "${OUTPUT_DIR}/fast_qr_bg.wasm" \ 25 | "${OUTPUT_DIR}/fast_qr_bg.wasm" && \ 26 | 27 | echo "Done!" --------------------------------------------------------------------------------