├── .gitignore ├── CLA-signed ├── CLA.wolfmcnally.943652EE38441760C3DC35364B6C2FCF894780AE.md └── README.md ├── CLA.md ├── CODEOWNERS ├── CONTRIBUTING.md ├── Demo └── QRCodeGeneratorDemo │ ├── .gitignore │ ├── CLA-signed │ └── CLA.wolfmcnally.943652EE38441760C3DC35364B6C2FCF894780AE.md │ ├── CLA.md │ ├── CODEOWNERS │ ├── CONTRIBUTING.md │ ├── Docs │ ├── screenshot-1.png │ └── screenshot-2.png │ ├── LICENSE │ ├── QRCodeGeneratorDemo │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── ContentView.swift │ ├── Info.plist │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ ├── QRCodeGeneratorDemo.entitlements │ └── QRCodeGeneratorDemoApp.swift │ └── README.md ├── LICENSE ├── Package.swift ├── README.md ├── Sources └── QRCodeGenerator │ ├── BitBuffer.swift │ ├── CorrectionLevel.swift │ ├── OSImage.swift │ ├── Private │ ├── Canvas.swift │ ├── IntPoint.swift │ └── IntSize.swift │ ├── QRCode.swift │ ├── QRError.swift │ ├── Segment.swift │ └── ShiftJISData.swift └── Tests └── QRCodeGeneratorTests └── QRCodeGeneratorTests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | Packages/ 41 | Package.pins 42 | Package.resolved 43 | *.xcodeproj 44 | 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | -------------------------------------------------------------------------------- /CLA-signed/CLA.wolfmcnally.943652EE38441760C3DC35364B6C2FCF894780AE.md: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP SIGNED MESSAGE----- 2 | Hash: SHA256 3 | 4 | # Contributor License Agreement 5 | 6 | Version 1.0 7 | 8 | Name: Wolf McNally 9 | 10 | E-Mail: wolf@wolfmcnally.com 11 | 12 | Legal Jurisdiction: Wyoming, United States of America 13 | 14 | Project: https://github.com/BlockchainCommons/QRCodeGenerator 15 | 16 | Date: July 23, 2021 17 | 18 | ## Purpose 19 | 20 | This agreement gives Blockchain Commons, LLC the permission it needs in order to accept my contributions into its open software project and to manage the intellectual property in that project over time. 21 | 22 | ## License 23 | 24 | I hereby license Blockchain Commons, LLC to: 25 | 26 | 1. do anything with my contributions that would otherwise infringe my copyright in them 27 | 28 | 2. do anything with my contributions that would otherwise infringe patents that I can or become able to license 29 | 30 | 3. sublicense these rights to others on any terms they like 31 | 32 | ## Reliability 33 | 34 | I understand that Blockchain Commons will rely on this license. I may not revoke this license. 35 | 36 | ## Awareness 37 | 38 | I promise that I am familiar with legal rules, like ["work made for hire" rules](http://worksmadeforhire.com), that can give employers and clients ownership of intellectual property in work that I do. I am also aware that legal agreements I might sign, like confidential information and invention assignment agreements, will usually give ownership of intellectual property in my work to employers, clients, and companies that I found. If someone else owns intellectual property in my work, I need their permission to license it. 39 | 40 | ## Copyright Guarantee 41 | 42 | I promise not to offer contributions to the project that contain copyrighted work that I do not have legally binding permission to contribute under these terms. When I offer a contribution with permission, I promise to document in the contribution who owns copyright in what work, and how they gave permission to contribute it. If I later become aware that one of my contributions may have copyrighted work of others that I did not have permission to contribute, I will notify Blockchain Commons, in confidence, immediately. 43 | 44 | ## Patent Guarantee 45 | 46 | I promise not to offer contributions to the project that I know infringe patents of others that I do not have permission to contribute under these terms. 47 | 48 | ## Open Source Guarantee 49 | 50 | I promise not to offer contributions that contain or depend on the work of others, unless that work is available under a license that [Blue Oak Council rates bronze or better](https://blueoakconcil.org/list), such as the MIT License, two- or three-clause BSD License, the Apache License Version 2.0, or the Blue Oak Model License 1.0.0. When I offer a contribution containing or depending on others' work, I promise to document in the contribution who licenses that work, along with copies of their license terms. 51 | 52 | ## Disclaimers 53 | 54 | ***As far as the law allows, my contributions come as is, without any warranty or condition. Other than under [Copyright Guarantee](#copyright-guarantee), [Patent Guarantee](#patent-guarantee), or [Open Source Guarantee](#open-source-guarantee), I will not be liable to anyone for any damages related to my contributions or this contributor license agreement, under any kind of legal claim.*** 55 | -----BEGIN PGP SIGNATURE----- 56 | 57 | iQIzBAEBCAAdFiEElDZS7jhEF2DD3DU2S2wvz4lHgK4FAmD6c8oACgkQS2wvz4lH 58 | gK44Dw//ce+I6E6TwPuF+dCYst7OjLTJfik8sRu3rYoLd9B1qMc4OXLf0teT3WoO 59 | wsKgZKlhsdxT5+kFUqN5BxJKFI+GSa2jlPMLk+1ejx93rBry/JakWBPPvBUgPQou 60 | HrlTwy/jR50fnGDCf6EWFSbfzCWUEe9j5r9dgCY//OQGjrCFJ/npGWgAuVfYfUwY 61 | YnCALm5AWIicdJSbPS58r9IhFKgkIZJEqXVr488CmjoDSrFWWv/KLwypmrFo7Ytz 62 | rzEX5E7LyHKkVqV88zgcFopHkyxnbUYISzGWCVOyeJQuI8fKdgtr542/GstebXZ/ 63 | Yczq9cVgOKdpp5lrDQunC77utpYsYIiooSQKV7TGw+byoMms5kyBB6khJlUL/TZi 64 | 5CGQqaEYcADzWYx2tL6ktPeV6Um8u8HJvrzOmXyR5f/aV1eHz4+uatjvpCW9hH++ 65 | LGqMTnE+28IoKfMxcocGQt/0nqwTTOvoAXlSgQuCEsJSeKk8Edc/l6wKB4OJBQmU 66 | 6Eewdr4ldZhFlvb2yaPC9607Vl9f/Z3/9aQQ6Z5AIMWO3eDJhYubvs2BFfo29i2k 67 | XU91vENdY6oul3KFoU4xBxp8uy1ykfNYOWCHVVngz3HdL6YREF/9P5C2FIj8Y06M 68 | y+rNiY4xVTMDIQrWlSHMPUUO5p6Ze+Z4cpLdZm9R4BzAhVcRUAU= 69 | =90se 70 | -----END PGP SIGNATURE----- 71 | -------------------------------------------------------------------------------- /CLA-signed/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlockchainCommons/QRCodeGenerator/57f800f5dcb90942bb49ce0f8458726d1424dc64/CLA-signed/README.md -------------------------------------------------------------------------------- /CLA.md: -------------------------------------------------------------------------------- 1 | # Contributor License Agreement 2 | 3 | Version 1.0 4 | 5 | Name: `$name` 6 | 7 | E-Mail: `$email` 8 | 9 | Legal Jurisdiction: Wyoming, United States of America 10 | 11 | Project: https://github.com/BlockchainCommons/QRCodeGenerator 12 | 13 | Date: `$date` 14 | 15 | ## Purpose 16 | 17 | This agreement gives Blockchain Commons, LLC the permission it needs in order to accept my contributions into its open software project and to manage the intellectual property in that project over time. 18 | 19 | ## License 20 | 21 | I hereby license Blockchain Commons, LLC to: 22 | 23 | 1. do anything with my contributions that would otherwise infringe my copyright in them 24 | 25 | 2. do anything with my contributions that would otherwise infringe patents that I can or become able to license 26 | 27 | 3. sublicense these rights to others on any terms they like 28 | 29 | ## Reliability 30 | 31 | I understand that Blockchain Commons will rely on this license. I may not revoke this license. 32 | 33 | ## Awareness 34 | 35 | I promise that I am familiar with legal rules, like ["work made for hire" rules](http://worksmadeforhire.com), that can give employers and clients ownership of intellectual property in work that I do. I am also aware that legal agreements I might sign, like confidential information and invention assignment agreements, will usually give ownership of intellectual property in my work to employers, clients, and companies that I found. If someone else owns intellectual property in my work, I need their permission to license it. 36 | 37 | ## Copyright Guarantee 38 | 39 | I promise not to offer contributions to the project that contain copyrighted work that I do not have legally binding permission to contribute under these terms. When I offer a contribution with permission, I promise to document in the contribution who owns copyright in what work, and how they gave permission to contribute it. If I later become aware that one of my contributions may have copyrighted work of others that I did not have permission to contribute, I will notify Blockchain Commons, in confidence, immediately. 40 | 41 | ## Patent Guarantee 42 | 43 | I promise not to offer contributions to the project that I know infringe patents of others that I do not have permission to contribute under these terms. 44 | 45 | ## Open Source Guarantee 46 | 47 | I promise not to offer contributions that contain or depend on the work of others, unless that work is available under a license that [Blue Oak Council rates bronze or better](https://blueoakconcil.org/list), such as the MIT License, two- or three-clause BSD License, the Apache License Version 2.0, or the Blue Oak Model License 1.0.0. When I offer a contribution containing or depending on others' work, I promise to document in the contribution who licenses that work, along with copies of their license terms. 48 | 49 | ## Disclaimers 50 | 51 | ***As far as the law allows, my contributions come as is, without any warranty or condition. Other than under [Copyright Guarantee](#copyright-guarantee), [Patent Guarantee](#patent-guarantee), or [Open Source Guarantee](#open-source-guarantee), I will not be liable to anyone for any damages related to my contributions or this contributor license agreement, under any kind of legal claim.*** 52 | 53 | --- 54 | 55 | To sign this Contributor License Agreement, fill in `$name`, `$email`, and `$date` above. Then sign using GPG using the following command `gpg --armor --clearsign --output ./CLA-signed/CLA.YOURGITHUBNAME.YOURGPGFINGERPRINT.md CLA.md`, then either submit your signed Contributor License Agreement to this repo as a GPG signed Pull Request or email it to [ChristopherA@BlockchainCommons.com](mailto:ChristopherA@BlockchainCommons.com). 56 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # These owners will be the default owners for everything in this repo. 2 | 3 | * @ChristopherA 4 | * @WolfMcNally 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's: 4 | 5 | - Reporting a bug 6 | - Discussing the current state of the code 7 | - Submitting a fix 8 | - Proposing new features 9 | - Becoming a maintainer 10 | 11 | ## We Develop with Github 12 | We use GitHub to host code, to track issues and feature requests, and to accept Pull Requests. 13 | 14 | ## Report Bugs using Github's [issues](https://github.com/briandk/transcriptase-atom/issues) 15 | 16 | If you find bugs, mistakes, or inconsistencies in this project's code or documents, please let us know by [opening a new issue](./issues), but consider searching through existing issues first to check and see if the problem has already been reported. If it has, it never hurts to add a quick "+1" or "I have this problem too". This helps prioritize the most common problems and requests. 17 | 18 | ### Write Bug Reports with Detail, Background, and Sample Code 19 | 20 | [This is an example](http://stackoverflow.com/q/12488905/180626) of a good bug report by @briandk. Here's [another example from craig.hockenberry](http://www.openradar.me/11905408). 21 | 22 | **Great Bug Reports** tend to have: 23 | 24 | - A quick summary and/or background 25 | - Steps to reproduce 26 | - Be specific! 27 | - Give sample code if you can. [The stackoverflow bug report](http://stackoverflow.com/q/12488905/180626) includes sample code that *anyone* with a base R setup can run to reproduce what I was seeing 28 | - What you expected would happen 29 | - What actually happens 30 | - Notes (possibly including why you think this might be happening, or stuff you tried that didn't work) 31 | 32 | People *love* thorough bug reports. I'm not even kidding. 33 | 34 | ## Submit Code Changes through Pull Requests 35 | 36 | Simple Pull Requests to fix typos, to document, or to fix small bugs are always welcome. 37 | 38 | We ask that more significant improvements to the project be first proposed before anybody starts to code as an [issue](./issues) or as a [draft Pull Request](./pulls), which is a [nice new feature](https://github.blog/2019-02-14-introducing-draft-pull-requests/) that gives other contributors a chance to point you in the right direction, give feedback on the design, and maybe discuss if related work is already under way. 39 | 40 | ### Use a Consistent Coding Style 41 | 42 | * We indent using two spaces (soft tabs) 43 | * We ALWAYS put spaces after list items and method parameters ([1, 2, 3], not [1,2,3]), around operators (x += 1, not x+=1), and around hash arrows. 44 | * This is open-source software. Consider the people who will read your code, and make it look nice for them. It's sort of like driving a car: Perhaps you love doing donuts when you're alone, but with passengers the goal is to make the ride as smooth as possible. 45 | 46 | ### Use [Github Flow](https://guides.github.com/introduction/flow/index.html) for Pull Requests 47 | 48 | We use [Github Flow](https://guides.github.com/introduction/flow/index.html). When you submit Pull Requests, please: 49 | 50 | 1. Fork the repo and create your branch from `master`. 51 | 2. If you've added code that should be tested, add tests. 52 | 3. If you've changed APIs, update the documentation. 53 | 4. Ensure the test suite passes. 54 | 5. Make sure your code lints. 55 | 6. Issue that Pull Request! 56 | 57 | ### Submit Under the BSD-2-Clause Plus Patent License 58 | 59 | In short, when you submit code changes, your submissions are understood to be available under the same [BSD-2-Clause Plus Patent License](./LICENSE.md) that covers the project. We also ask all code contributors to GPG sign the [Contributor License Agreement (CLA.md)](./CLA.md) to protect future users of this project. Feel free to contact the maintainers if that's a concern. 60 | 61 | ## References 62 | 63 | Portions of this CONTRIBUTING.md document were adopted from best practices of a number of open source projects, including: 64 | * [Facebook's Draft](https://github.com/facebook/draft-js/blob/a9316a723f9e918afde44dea68b5f9f39b7d9b00/CONTRIBUTING.md) 65 | * [IPFS Contributing](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md) 66 | -------------------------------------------------------------------------------- /Demo/QRCodeGeneratorDemo/.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | Packages/ 41 | Package.pins 42 | Package.resolved 43 | *.xcodeproj 44 | 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | -------------------------------------------------------------------------------- /Demo/QRCodeGeneratorDemo/CLA-signed/CLA.wolfmcnally.943652EE38441760C3DC35364B6C2FCF894780AE.md: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP SIGNED MESSAGE----- 2 | Hash: SHA256 3 | 4 | # Contributor License Agreement 5 | 6 | Version 1.0 7 | 8 | Name: Wolf McNally 9 | 10 | E-Mail: wolf@wolfmcnally.com 11 | 12 | Legal Jurisdiction: Wyoming, United States of America 13 | 14 | Project: https://github.com/BlockchainCommons/QRCodeGeneratorDemo 15 | 16 | Date: July 23, 2021 17 | 18 | ## Purpose 19 | 20 | This agreement gives Blockchain Commons, LLC the permission it needs in order to accept my contributions into its open software project and to manage the intellectual property in that project over time. 21 | 22 | ## License 23 | 24 | I hereby license Blockchain Commons, LLC to: 25 | 26 | 1. do anything with my contributions that would otherwise infringe my copyright in them 27 | 28 | 2. do anything with my contributions that would otherwise infringe patents that I can or become able to license 29 | 30 | 3. sublicense these rights to others on any terms they like 31 | 32 | ## Reliability 33 | 34 | I understand that Blockchain Commons will rely on this license. I may not revoke this license. 35 | 36 | ## Awareness 37 | 38 | I promise that I am familiar with legal rules, like ["work made for hire" rules](http://worksmadeforhire.com), that can give employers and clients ownership of intellectual property in work that I do. I am also aware that legal agreements I might sign, like confidential information and invention assignment agreements, will usually give ownership of intellectual property in my work to employers, clients, and companies that I found. If someone else owns intellectual property in my work, I need their permission to license it. 39 | 40 | ## Copyright Guarantee 41 | 42 | I promise not to offer contributions to the project that contain copyrighted work that I do not have legally binding permission to contribute under these terms. When I offer a contribution with permission, I promise to document in the contribution who owns copyright in what work, and how they gave permission to contribute it. If I later become aware that one of my contributions may have copyrighted work of others that I did not have permission to contribute, I will notify Blockchain Commons, in confidence, immediately. 43 | 44 | ## Patent Guarantee 45 | 46 | I promise not to offer contributions to the project that I know infringe patents of others that I do not have permission to contribute under these terms. 47 | 48 | ## Open Source Guarantee 49 | 50 | I promise not to offer contributions that contain or depend on the work of others, unless that work is available under a license that [Blue Oak Council rates bronze or better](https://blueoakconcil.org/list), such as the MIT License, two- or three-clause BSD License, the Apache License Version 2.0, or the Blue Oak Model License 1.0.0. When I offer a contribution containing or depending on others' work, I promise to document in the contribution who licenses that work, along with copies of their license terms. 51 | 52 | ## Disclaimers 53 | 54 | ***As far as the law allows, my contributions come as is, without any warranty or condition. Other than under [Copyright Guarantee](#copyright-guarantee), [Patent Guarantee](#patent-guarantee), or [Open Source Guarantee](#open-source-guarantee), I will not be liable to anyone for any damages related to my contributions or this contributor license agreement, under any kind of legal claim.*** 55 | 56 | -----BEGIN PGP SIGNATURE----- 57 | 58 | iQIzBAEBCAAdFiEElDZS7jhEF2DD3DU2S2wvz4lHgK4FAmD7rGYACgkQS2wvz4lH 59 | gK6NBxAAvb6Q3n0bl06IE+Qs9Sq4OyoOWhlr1MP65TUGAW769sYzZu1SoM0Hjk6R 60 | wgYQ6Hf6OL8aiMtmD/AMZkP0qdBDtBe0C0k/5Q79FSs07ev+7YLCmFXIN9brZAVM 61 | BnVNdonbwms37uTONQImLrM2bfFq5n7gs+ppeDLs7VsN0TcWym93KCX+/WWoJEgn 62 | uQ8wYt/N02anDTPHrGkA+FhLHqAwPFbl4kdBUKgzetwd1b7r617tE02EkWlObcwc 63 | 2JR32q1xuAqzQb/M3FnnAPta6jWWXh355wsdc2Dm1uSoaLP2Ce0OnDZr3d6iDYM5 64 | JdjwrjxTxDW6uNW7ZF7AzIuaPwDaLoXt0DarUuXBcDmElUKSux/8+27i0L8Roz5p 65 | exBIF1wtgOn1+p0iVgCFJo2ssta3cg66xIFE7O8LiVEXBFVgx+eVwnwEW9fGscKR 66 | 7JrgDY3iGO03faICbWFOyEbiNcKqZ7xLgDxvCp/mBcrx3+RED0cFdoL0gLlr7fwJ 67 | jh7WAds1qVjzwsAlffo17IddlH9840twSBxMkG78Ihh8B1uyLy3LgVfJSCrRtff4 68 | SdrvhrxU7L3/VAxPbx2eipByRXNP0gZfHKvwgiHbgAvqDduw1Sp9uNMfTaoYuPev 69 | djeKXpvgR7GaZ5i5Wai83ovYbuncb2Kv/oTRlb50mjRJFlGuCfg= 70 | =lx08 71 | -----END PGP SIGNATURE----- 72 | -------------------------------------------------------------------------------- /Demo/QRCodeGeneratorDemo/CLA.md: -------------------------------------------------------------------------------- 1 | # Contributor License Agreement 2 | 3 | Version 1.0 4 | 5 | Name: `$name` 6 | 7 | E-Mail: `$email` 8 | 9 | Legal Jurisdiction: Wyoming, United States of America 10 | 11 | Project: https://github.com/BlockchainCommons/QRCodeGeneratorDemo 12 | 13 | Date: `$date` 14 | 15 | ## Purpose 16 | 17 | This agreement gives Blockchain Commons, LLC the permission it needs in order to accept my contributions into its open software project and to manage the intellectual property in that project over time. 18 | 19 | ## License 20 | 21 | I hereby license Blockchain Commons, LLC to: 22 | 23 | 1. do anything with my contributions that would otherwise infringe my copyright in them 24 | 25 | 2. do anything with my contributions that would otherwise infringe patents that I can or become able to license 26 | 27 | 3. sublicense these rights to others on any terms they like 28 | 29 | ## Reliability 30 | 31 | I understand that Blockchain Commons will rely on this license. I may not revoke this license. 32 | 33 | ## Awareness 34 | 35 | I promise that I am familiar with legal rules, like ["work made for hire" rules](http://worksmadeforhire.com), that can give employers and clients ownership of intellectual property in work that I do. I am also aware that legal agreements I might sign, like confidential information and invention assignment agreements, will usually give ownership of intellectual property in my work to employers, clients, and companies that I found. If someone else owns intellectual property in my work, I need their permission to license it. 36 | 37 | ## Copyright Guarantee 38 | 39 | I promise not to offer contributions to the project that contain copyrighted work that I do not have legally binding permission to contribute under these terms. When I offer a contribution with permission, I promise to document in the contribution who owns copyright in what work, and how they gave permission to contribute it. If I later become aware that one of my contributions may have copyrighted work of others that I did not have permission to contribute, I will notify Blockchain Commons, in confidence, immediately. 40 | 41 | ## Patent Guarantee 42 | 43 | I promise not to offer contributions to the project that I know infringe patents of others that I do not have permission to contribute under these terms. 44 | 45 | ## Open Source Guarantee 46 | 47 | I promise not to offer contributions that contain or depend on the work of others, unless that work is available under a license that [Blue Oak Council rates bronze or better](https://blueoakconcil.org/list), such as the MIT License, two- or three-clause BSD License, the Apache License Version 2.0, or the Blue Oak Model License 1.0.0. When I offer a contribution containing or depending on others' work, I promise to document in the contribution who licenses that work, along with copies of their license terms. 48 | 49 | ## Disclaimers 50 | 51 | ***As far as the law allows, my contributions come as is, without any warranty or condition. Other than under [Copyright Guarantee](#copyright-guarantee), [Patent Guarantee](#patent-guarantee), or [Open Source Guarantee](#open-source-guarantee), I will not be liable to anyone for any damages related to my contributions or this contributor license agreement, under any kind of legal claim.*** 52 | 53 | --- 54 | 55 | To sign this Contributor License Agreement, fill in `$name`, `$email`, and `$date` above. Then sign using GPG using the following command `gpg --armor --clearsign --output ./CLA-signed/CLA.YOURGITHUBNAME.YOURGPGFINGERPRINT.md CLA.md`, then either submit your signed Contributor License Agreement to this repo as a GPG signed Pull Request or email it to [ChristopherA@BlockchainCommons.com](mailto:ChristopherA@BlockchainCommons.com). 56 | -------------------------------------------------------------------------------- /Demo/QRCodeGeneratorDemo/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # These owners will be the default owners for everything in this repo. 2 | 3 | * @ChristopherA 4 | * @WolfMcNally 5 | -------------------------------------------------------------------------------- /Demo/QRCodeGeneratorDemo/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's: 4 | 5 | - Reporting a bug 6 | - Discussing the current state of the code 7 | - Submitting a fix 8 | - Proposing new features 9 | - Becoming a maintainer 10 | 11 | ## We Develop with Github 12 | We use GitHub to host code, to track issues and feature requests, and to accept Pull Requests. 13 | 14 | ## Report Bugs using Github's [issues](https://github.com/briandk/transcriptase-atom/issues) 15 | 16 | If you find bugs, mistakes, or inconsistencies in this project's code or documents, please let us know by [opening a new issue](./issues), but consider searching through existing issues first to check and see if the problem has already been reported. If it has, it never hurts to add a quick "+1" or "I have this problem too". This helps prioritize the most common problems and requests. 17 | 18 | ### Write Bug Reports with Detail, Background, and Sample Code 19 | 20 | [This is an example](http://stackoverflow.com/q/12488905/180626) of a good bug report by @briandk. Here's [another example from craig.hockenberry](http://www.openradar.me/11905408). 21 | 22 | **Great Bug Reports** tend to have: 23 | 24 | - A quick summary and/or background 25 | - Steps to reproduce 26 | - Be specific! 27 | - Give sample code if you can. [The stackoverflow bug report](http://stackoverflow.com/q/12488905/180626) includes sample code that *anyone* with a base R setup can run to reproduce what I was seeing 28 | - What you expected would happen 29 | - What actually happens 30 | - Notes (possibly including why you think this might be happening, or stuff you tried that didn't work) 31 | 32 | People *love* thorough bug reports. I'm not even kidding. 33 | 34 | ## Submit Code Changes through Pull Requests 35 | 36 | Simple Pull Requests to fix typos, to document, or to fix small bugs are always welcome. 37 | 38 | We ask that more significant improvements to the project be first proposed before anybody starts to code as an [issue](./issues) or as a [draft Pull Request](./pulls), which is a [nice new feature](https://github.blog/2019-02-14-introducing-draft-pull-requests/) that gives other contributors a chance to point you in the right direction, give feedback on the design, and maybe discuss if related work is already under way. 39 | 40 | ### Use a Consistent Coding Style 41 | 42 | * We indent using two spaces (soft tabs) 43 | * We ALWAYS put spaces after list items and method parameters ([1, 2, 3], not [1,2,3]), around operators (x += 1, not x+=1), and around hash arrows. 44 | * This is open-source software. Consider the people who will read your code, and make it look nice for them. It's sort of like driving a car: Perhaps you love doing donuts when you're alone, but with passengers the goal is to make the ride as smooth as possible. 45 | 46 | ### Use [Github Flow](https://guides.github.com/introduction/flow/index.html) for Pull Requests 47 | 48 | We use [Github Flow](https://guides.github.com/introduction/flow/index.html). When you submit Pull Requests, please: 49 | 50 | 1. Fork the repo and create your branch from `master`. 51 | 2. If you've added code that should be tested, add tests. 52 | 3. If you've changed APIs, update the documentation. 53 | 4. Ensure the test suite passes. 54 | 5. Make sure your code lints. 55 | 6. Issue that Pull Request! 56 | 57 | ### Submit Under the BSD-2-Clause Plus Patent License 58 | 59 | In short, when you submit code changes, your submissions are understood to be available under the same [BSD-2-Clause Plus Patent License](./LICENSE.md) that covers the project. We also ask all code contributors to GPG sign the [Contributor License Agreement (CLA.md)](./CLA.md) to protect future users of this project. Feel free to contact the maintainers if that's a concern. 60 | 61 | ## References 62 | 63 | Portions of this CONTRIBUTING.md document were adopted from best practices of a number of open source projects, including: 64 | * [Facebook's Draft](https://github.com/facebook/draft-js/blob/a9316a723f9e918afde44dea68b5f9f39b7d9b00/CONTRIBUTING.md) 65 | * [IPFS Contributing](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md) 66 | -------------------------------------------------------------------------------- /Demo/QRCodeGeneratorDemo/Docs/screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlockchainCommons/QRCodeGenerator/57f800f5dcb90942bb49ce0f8458726d1424dc64/Demo/QRCodeGeneratorDemo/Docs/screenshot-1.png -------------------------------------------------------------------------------- /Demo/QRCodeGeneratorDemo/Docs/screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlockchainCommons/QRCodeGenerator/57f800f5dcb90942bb49ce0f8458726d1424dc64/Demo/QRCodeGeneratorDemo/Docs/screenshot-2.png -------------------------------------------------------------------------------- /Demo/QRCodeGeneratorDemo/LICENSE: -------------------------------------------------------------------------------- 1 | Unless otherwise noted (either in /README.md or in the file's header comments) the contents of this repository are released under the following license: 2 | 3 | BSD-2-Clause Plus Patent License 4 | 5 | SPDX-License-Identifier: [BSD-2-Clause-Patent](https://spdx.org/licenses/BSD-2-Clause-Patent.html) 6 | 7 | Copyright © 2021 Blockchain Commons, LLC 8 | 9 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 10 | 11 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 12 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 13 | Subject to the terms and conditions of this license, each copyright holder and contributor hereby grants to those receiving rights under this license a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except for failure to satisfy the conditions of this license) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer this software, where such license applies only to those patent claims, already acquired or hereafter acquired, licensable by such copyright holder or contributor that are necessarily infringed by: 14 | 15 | (a) their Contribution(s) (the licensed copyrights of copyright holders and non-copyrightable additions of contributors, in source or binary form) alone; or 16 | (b) combination of their Contribution(s) with the work of authorship to which such Contribution(s) was added by such copyright holder or contributor, if, at the time the Contribution is added, such addition causes such combination to be necessarily infringed. The patent license shall not apply to any other combinations which include the Contribution. 17 | Except as expressly stated above, no rights or licenses from any copyright holder or contributor is granted under this license, whether expressly, by implication, estoppel or otherwise. 18 | 19 | DISCLAIMER 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 22 | -------------------------------------------------------------------------------- /Demo/QRCodeGeneratorDemo/QRCodeGeneratorDemo/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Demo/QRCodeGeneratorDemo/QRCodeGeneratorDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Demo/QRCodeGeneratorDemo/QRCodeGeneratorDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Demo/QRCodeGeneratorDemo/QRCodeGeneratorDemo/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // QRCodeGeneratorDemo 4 | // 5 | // Created by Wolf McNally on 7/23/21. 6 | // 7 | 8 | import SwiftUI 9 | import QRCodeGenerator 10 | 11 | struct QRCodeView: View { 12 | let qrCode: QRCode 13 | let title: String 14 | let caption: String 15 | let border: Int 16 | let foregroundColor: UIColor 17 | let backgroundColor: UIColor 18 | 19 | init(title: String, text: String, correctionLevel: CorrectionLevel = .medium, mask: Int? = nil, border: Int = 1, foregroundColor: UIColor = .black, backgroundColor: UIColor = .white) { 20 | self.title = title 21 | self.qrCode = try! QRCode.encode(text: text, correctionLevel: correctionLevel, mask: mask) 22 | self.caption = text 23 | self.border = border 24 | self.foregroundColor = foregroundColor 25 | self.backgroundColor = backgroundColor 26 | } 27 | 28 | init(title: String, utf8Bytes: [UInt8], correctionLevel: CorrectionLevel = .medium, mask: Int? = nil, border: Int = 1, foregroundColor: UIColor = .black, backgroundColor: UIColor = .white) { 29 | self.title = title 30 | let text = String(data: Data(utf8Bytes), encoding: .utf8)! 31 | self.qrCode = try! QRCode.encode(text: text, correctionLevel: correctionLevel, mask: mask) 32 | self.caption = text 33 | self.border = border 34 | self.foregroundColor = foregroundColor 35 | self.backgroundColor = backgroundColor 36 | } 37 | 38 | init(title: String, segments: [Segment], caption: String, correctionLevel: CorrectionLevel = .medium, mask: Int? = nil, border: Int = 1, foregroundColor: UIColor = .black, backgroundColor: UIColor = .white) { 39 | self.title = title 40 | self.qrCode = try! QRCode.encode(segments: segments, correctionLevel: correctionLevel, mask: mask) 41 | self.caption = caption 42 | self.border = border 43 | self.foregroundColor = foregroundColor 44 | self.backgroundColor = backgroundColor 45 | } 46 | 47 | var body: some View { 48 | VStack { 49 | Text(title) 50 | .bold() 51 | .fixedSize(horizontal: false, vertical: true) 52 | qrCode 53 | .image(border: border, foregroundColor: foregroundColor, backgroundColor: backgroundColor) 54 | .resizable() 55 | .aspectRatio(contentMode: .fit) 56 | .frame(maxWidth: 300, maxHeight: 300) 57 | Text(caption) 58 | .font(.caption) 59 | .fixedSize(horizontal: false, vertical: true) 60 | } 61 | .padding() 62 | } 63 | } 64 | 65 | struct ContentView: View { 66 | // Unicode text as UTF-8 67 | // こんにちwa、世界! αβγδ 68 | let utf8Bytes: [UInt8] = [ 69 | 0xE3, 0x81, 0x93, 0xE3, 0x82, 0x93, 0xE3, 0x81, 0xAB, 0xE3, 70 | 0x81, 0xA1, 0x77, 0x61, 0xE3, 0x80, 0x81, 0xE4, 0xB8, 0x96, 71 | 0xE7, 0x95, 0x8C, 0xEF, 0xBC, 0x81, 0x20, 0xCE, 0xB1, 0xCE, 72 | 0xB2, 0xCE, 0xB3, 0xCE, 0xB4 73 | ] 74 | // Moderately large QR Code using longer text (from Lewis Carroll's Alice in Wonderland) 75 | let alice = "Alice was beginning to get very tired of sitting by her sister on the bank, and of having nothing to do: once or twice she had peeped into the book her sister was reading, but it had no pictures or conversations in it, 'and what is the use of a book,' thought Alice 'without pictures or conversations?' So she was considering in her own mind (as well as she could, for the hot day made her feel very sleepy and stupid), whether the pleasure of making a daisy-chain would be worth the trouble of getting up and picking the daisies, when suddenly a White Rabbit with pink eyes ran close by her." 76 | 77 | let silver0 = "THE SQUARE ROOT OF 2 IS 1." 78 | let silver1 = "41421356237309504880168872420969807856967187537694807317667973799" 79 | 80 | let golden0 = "Golden ratio φ = 1." 81 | let golden1 = "6180339887498948482045868343656381177203091798057628621354486227052604628189024497072072041893911374" 82 | let golden2 = "......" 83 | 84 | // Illustration "Madoka": kanji, kana, Cyrillic, full-width Latin, Greek characters 85 | // 「魔法少女まどか☆マギカ」って、 ИАИ desu κα? 86 | let madoka: [UInt8] = [ 87 | 0xE3, 0x80, 0x8C, 0xE9, 0xAD, 0x94, 0xE6, 0xB3, 0x95, 0xE5, 0xB0, 0x91, 0xE5, 0xA5, 0xB3, 0xE3, 0x81, 0xBE, 0xE3, 0x81, 0xA9, 0xE3, 0x81, 0x8B, 0xE2, 0x98, 0x86, 0xE3, 0x83, 0x9E, 0xE3, 0x82, 0xAE, 0xE3, 0x82, 0xAB, 0xE3, 0x80, 0x8D, 0xE3, 0x81, 0xA3, 0xE3, 0x81, 0xA6, 0xE3, 0x80, 0x81, 0xE3, 0x80, 0x80, 0xD0, 0x98, 0xD0, 0x90, 0xD0, 0x98, 0xE3, 0x80, 0x80, 0xEF, 0xBD, 0x84, 0xEF, 0xBD, 0x85, 0xEF, 0xBD, 0x93, 0xEF, 0xBD, 0x95, 0xE3, 0x80, 0x80, 0xCE, 0xBA, 0xCE, 0xB1, 0xEF, 0xBC, 0x9F 88 | ] 89 | 90 | // Kanji mode encoding (13 bits per character) 91 | // 「魔法少女まどか☆マギカ」って、 ИАИ desu κα? 92 | static let kanjiChars: [UInt16] = [ 93 | 0x0035, 0x1002, 0x0FC0, 0x0AED, 0x0AD7, 94 | 0x015C, 0x0147, 0x0129, 0x0059, 0x01BD, 95 | 0x018D, 0x018A, 0x0036, 0x0141, 0x0144, 96 | 0x0001, 0x0000, 0x0249, 0x0240, 0x0249, 97 | 0x0000, 0x0104, 0x0105, 0x0113, 0x0115, 98 | 0x0000, 0x0208, 0x01FF, 0x0008 99 | ] 100 | 101 | let kanjiBits: [Bool] = { 102 | var bb: [Bool] = [] 103 | for c in kanjiChars { 104 | bb.appendBits(c, 13) 105 | } 106 | return bb 107 | }() 108 | 109 | // Chinese text as UTF-8 110 | // 維基百科(Wikipedia,聆聽i/ˌwɪkᵻˈpiːdi.ə/)是一個自由內容、公開編輯且多語言的網路百科全書協作計畫 111 | static let chineseChars: [UInt8] = [ 112 | 0xE7, 0xB6, 0xAD, 0xE5, 0x9F, 0xBA, 0xE7, 0x99, 0xBE, 0xE7, 0xA7, 0x91, 0xEF, 0xBC, 0x88, 0x57, 0x69, 0x6B, 0x69, 0x70, 0x65, 0x64, 0x69, 0x61, 0xEF, 0xBC, 0x8C, 0xE8, 0x81, 0x86, 0xE8, 0x81, 0xBD, 0x69, 0x2F, 0xCB, 0x8C, 0x77, 0xC9, 0xAA, 0x6B, 0xE1, 0xB5, 0xBB, 0xCB, 0x88, 0x70, 0x69, 0xCB, 0x90, 0x64, 0x69, 0x2E, 0xC9, 0x99, 0x2F, 0xEF, 0xBC, 0x89, 0xE6, 0x98, 0xAF, 0xE4, 0xB8, 0x80, 0xE5, 0x80, 0x8B, 0xE8, 0x87, 0xAA, 0xE7, 0x94, 0xB1, 0xE5, 0x85, 0xA7, 0xE5, 0xAE, 0xB9, 0xE3, 0x80, 0x81, 0xE5, 0x85, 0xAC, 0xE9, 0x96, 0x8B, 0xE7, 0xB7, 0xA8, 0xE8, 0xBC, 0xAF, 0xE4, 0xB8, 0x94, 0xE5, 0xA4, 0x9A, 0xE8, 0xAA, 0x9E, 0xE8, 0xA8, 0x80, 0xE7, 0x9A, 0x84, 0xE7, 0xB6, 0xB2, 0xE8, 0xB7, 0xAF, 0xE7, 0x99, 0xBE, 0xE7, 0xA7, 0x91, 0xE5, 0x85, 0xA8, 0xE6, 0x9B, 0xB8, 0xE5, 0x8D, 0x94, 0xE4, 0xBD, 0x9C, 0xE8, 0xA8, 0x88, 0xE7, 0x95, 0xAB 113 | ] 114 | 115 | let chineseString = String(data: Data(chineseChars), encoding: .utf8)! 116 | 117 | var body: some View { 118 | ScrollView { 119 | VStack { 120 | Group { 121 | QRCodeView(title: "Basic QR code", text: "Hello, world!", correctionLevel: .low) 122 | QRCodeView(title: "Basic QR code, colored with 3 module border", text: "Hello, world!", correctionLevel: .low, border: 3, foregroundColor: .blue, backgroundColor: .yellow) 123 | 124 | QRCodeView(title: "Numeric mode encoding (3.33 bits per digit)", text: "314159265358979323846264338327950288419716939937510", correctionLevel: .medium) 125 | 126 | QRCodeView(title: "Alphanumeric mode encoding (5.5 bits per character)", text: "DOLLAR-AMOUNT:$39.87 PERCENTAGE:100.00% OPERATIONS:+-*/", correctionLevel: .high) 127 | 128 | QRCodeView(title: "Unicode text as UTF-8", utf8Bytes: utf8Bytes, correctionLevel: .quartile) 129 | 130 | QRCodeView(title: "Moderately large QR Code using longer text (from Lewis Carroll's Alice in Wonderland)", text: alice, correctionLevel: .high) 131 | } 132 | 133 | Group { 134 | // Illustration "silver" 135 | QRCodeView(title: "Arbitrary text encoded in binary mode.", text: silver0 + silver1, correctionLevel: .low) 136 | try! QRCodeView(title: "Same text, encoded as an alphanumeric segment and a numeric segment.", segments: [ 137 | Segment.makeAlphanumeric(text: silver0), 138 | Segment.makeNumeric(digits: silver1) 139 | ], caption: silver0 + silver1, correctionLevel: .low) 140 | 141 | // Illustration "golden" 142 | QRCodeView(title: "Arbitrary text encoded in binary mode.", text: golden0 + golden1 + golden2, correctionLevel: .low) 143 | try! QRCodeView(title: "Same text, encoded as three segments: binary, then numeric, then alphanumeric.", segments: [ 144 | Segment.makeBytes(data: golden0), 145 | Segment.makeNumeric(digits: golden1), 146 | Segment.makeAlphanumeric(text: golden2) 147 | ], caption: golden0 + golden1 + golden2, correctionLevel: .low) 148 | 149 | // Illustration "Madoka" 150 | QRCodeView(title: "Kanji, kana, Cyrillic, full-width Latin, Greek characters, UTF-8.", text: String(data: Data(madoka), encoding: .utf8)!, correctionLevel: .low) 151 | 152 | // Kanji 153 | try! QRCodeView(title: "Same text, Kanji mode encoding (13 bits per character).", segments: [ 154 | Segment(mode: .kanji, characterCount: Self.kanjiChars.count, data: kanjiBits) 155 | ], caption: "「魔法少女まどか☆マギカ」って、 ИАИ desu κα?", correctionLevel: .low) 156 | } 157 | 158 | Group { 159 | try! QRCodeView(title: "Automatic mask.", segments: Segment.makeSegments(text: "https://www.nayuki.io/"), caption: "https://www.nayuki.io/", correctionLevel: .high, mask: nil) 160 | try! QRCodeView(title: "Force mask 3.", segments: Segment.makeSegments(text: "https://www.nayuki.io/"), caption: "https://www.nayuki.io/", correctionLevel: .high, mask: 3) 161 | } 162 | 163 | Group { 164 | try! QRCodeView(title: "Chinese test, mask 0", segments: Segment.makeSegments(text: chineseString), caption: chineseString, correctionLevel: .medium, mask: 0) 165 | try! QRCodeView(title: "Chinese test, mask 1", segments: Segment.makeSegments(text: chineseString), caption: chineseString, correctionLevel: .medium, mask: 1) 166 | try! QRCodeView(title: "Chinese test, mask 5", segments: Segment.makeSegments(text: chineseString), caption: chineseString, correctionLevel: .medium, mask: 5) 167 | try! QRCodeView(title: "Chinese test, mask 7", segments: Segment.makeSegments(text: chineseString), caption: chineseString, correctionLevel: .medium, mask: 7) 168 | try! QRCodeView(title: "Chinese test, automatic mask, optimal encoding", segments: Segment.makeSegmentsOptimally(text: chineseString), caption: chineseString, correctionLevel: .medium) 169 | } 170 | 171 | Group { 172 | let part1 = "shc:/" 173 | let part2 = String((0..<1580).map({ _ in "0123456789".randomElement()!})) 174 | 175 | QRCodeView(title: "Simulated Smart Health Card, encoded in a single binary segment.", text: part1 + part2) 176 | 177 | try! QRCodeView(title: "Simulated Smart Health Card, manually encoded as a binary segment for header, and numeric segment for body.", segments: [ 178 | Segment.makeBytes(data: part1), 179 | Segment.makeNumeric(digits: part2) 180 | ], caption: part1 + part2) 181 | 182 | try! QRCodeView(title: "Simulated Smart Health Card, automatically encoded using optimal encoding.", segments: Segment.makeSegmentsOptimally(text: part1 + part2), caption: part1 + part2) 183 | } 184 | } 185 | } 186 | } 187 | } 188 | 189 | struct ContentView_Previews: PreviewProvider { 190 | static var previews: some View { 191 | ContentView() 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /Demo/QRCodeGeneratorDemo/QRCodeGeneratorDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | 28 | UIApplicationSupportsIndirectInputEvents 29 | 30 | UILaunchScreen 31 | 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /Demo/QRCodeGeneratorDemo/QRCodeGeneratorDemo/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Demo/QRCodeGeneratorDemo/QRCodeGeneratorDemo/QRCodeGeneratorDemo.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Demo/QRCodeGeneratorDemo/QRCodeGeneratorDemo/QRCodeGeneratorDemoApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QRCodeGeneratorDemoApp.swift 3 | // QRCodeGeneratorDemo 4 | // 5 | // Created by Wolf McNally on 7/23/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct QRCodeGeneratorDemoApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Demo/QRCodeGeneratorDemo/README.md: -------------------------------------------------------------------------------- 1 | # Blockchain Commons QRCodeGeneratorDemo 2 | 3 | ### by Wolf McNally 4 | 5 | QRCodeGeneratorDemo is a SwiftUI-based iOS app that demonstrates the [Blockchain Commons QRCodeGenerator Swift package](https://github.com/BlockchainCommons/QRCodeGenerator). It displays a simple scrolling list of generated QR codes, many of which show the multi-segment encoding features it enables. 6 | 7 | ![Screenshot 1](Docs/screenshot-1.png) 8 | 9 | ![Screenshot 2](Docs/screenshot-2.png) 10 | 11 | ## Installation Instructions 12 | 13 | Open `QRCodeGeneratorDemo.xcworkspace` in Xcode 12 or later and build the `QRCodeGeneratorDemo` target. It will run on iOS devices or on macOS using Catalyst. 14 | 15 | ## Origin, Authors, Copyright & Licenses 16 | 17 | Unless otherwise noted (either in this [/README.md](./README.md) or in the file's header comments) the contents of this repository are Copyright © 2021 by Blockchain Commons, LLC, and are [licensed](./LICENSE) under the [spdx:BSD-2-Clause Plus Patent License](https://spdx.org/licenses/BSD-2-Clause-Patent.html). 18 | 19 | ### Derived from ... 20 | 21 | The QRCodeGenerator project is either derived from or was inspired by: 22 | 23 | * The [Project Nayuki QR Code generator library](https://www.nayuki.io/page/qr-code-generator-library) 24 | 25 | ## Financial Support 26 | 27 | QRCodeGenerator is a project of [Blockchain Commons](https://www.blockchaincommons.com/). We are proudly a "not-for-profit" social benefit corporation committed to open source & open development. Our work is funded entirely by donations and collaborative partnerships with people like you. Every contribution will be spent on building open tools, technologies, and techniques that sustain and advance blockchain and internet security infrastructure and promote an open web. 28 | 29 | To financially support further development of QRCodeGenerator and other projects, please consider becoming a Patron of Blockchain Commons through ongoing monthly patronage as a [GitHub Sponsor](https://github.com/sponsors/BlockchainCommons). You can also support Blockchain Commons with bitcoins at our [BTCPay Server](https://btcpay.blockchaincommons.com/). 30 | 31 | ## Contributing 32 | 33 | We encourage public contributions through issues and pull requests! Please review [CONTRIBUTING.md](./CONTRIBUTING.md) for details on our development process. All contributions to this repository require a GPG signed [Contributor License Agreement](./CLA.md). 34 | 35 | ### Discussions 36 | 37 | The best place to talk about Blockchain Commons and its projects is in our GitHub Discussions areas. 38 | 39 | [**Gordian System Discussions**](https://github.com/BlockchainCommons/Gordian/discussions). For users and developers of the Gordian system, including the Gordian Server, Bitcoin Standup technology, QuickConnect, and the Gordian Wallet. If you want to talk about our linked full-node and wallet technology, suggest new additions to our Bitcoin Standup standards, or discuss the implementation our standalone wallet, the Discussions area of the [main Gordian repo](https://github.com/BlockchainCommons/Gordian) is the place. 40 | 41 | [**Wallet Standard Discussions**](https://github.com/BlockchainCommons/AirgappedSigning/discussions). For standards and open-source developers who want to talk about wallet standards, please use the Discussions area of the [Airgapped Signing repo](https://github.com/BlockchainCommons/AirgappedSigning). This is where you can talk about projects like our [LetheKit](https://github.com/BlockchainCommons/bc-lethekit) and command line tools such as [seedtool](https://github.com/BlockchainCommons/bc-seedtool-cli), both of which are intended to testbed wallet technologies, plus the libraries that we've built to support your own deployment of wallet technology such as [bc-bip39](https://github.com/BlockchainCommons/bc-bip39), [bc-slip39](https://github.com/BlockchainCommons/bc-slip39), [bc-shamir](https://github.com/BlockchainCommons/bc-shamir), [Sharded Secret Key Reconstruction](https://github.com/BlockchainCommons/bc-sskr), [bc-ur](https://github.com/BlockchainCommons/bc-ur), and the [bc-crypto-base](https://github.com/BlockchainCommons/bc-crypto-base). If it's a wallet-focused technology or a more general discussion of wallet standards,discuss it here. 42 | 43 | [**Blockchain Commons Discussions**](https://github.com/BlockchainCommons/Community/discussions). For developers, interns, and patrons of Blockchain Commons, please use the discussions area of the [Community repo](https://github.com/BlockchainCommons/Community) to talk about general Blockchain Commons issues, the intern program, or topics other than the [Gordian System](https://github.com/BlockchainCommons/Gordian/discussions) or the [wallet standards](https://github.com/BlockchainCommons/AirgappedSigning/discussions), each of which have their own discussion areas. 44 | 45 | ### Other Questions & Problems 46 | 47 | As an open-source, open-development community, Blockchain Commons does not have the resources to provide direct support of our projects. Please consider the discussions area as a locale where you might get answers to questions. Alternatively, please use this repository's [issues](./issues) feature. Unfortunately, we can not make any promises on response time. 48 | 49 | If your company requires support to use our projects, please feel free to contact us directly about options. We may be able to offer you a contract for support from one of our contributors, or we might be able to point you to another entity who can offer the contractual support that you need. 50 | 51 | ### Credits 52 | 53 | The following people directly contributed to this repository. You can add your name here by getting involved. The first step is learning how to contribute from our [CONTRIBUTING.md](./CONTRIBUTING.md) documentation. 54 | 55 | | Name | Role | Github | Email | GPG Fingerprint | 56 | | ----------------- | ------------------- | ------------------------------------------------- | ------------------------------------- | -------------------------------------------------- | 57 | | Wolf McNally | Project Lead | [@WolfMcNally](https://github.com/WolfMcNally) | \ | 9436 52EE 3844 1760 C3DC  3536 4B6C 2FCF 8947 80AE | 58 | | Christopher Allen | Principal Architect | [@ChristopherA](https://github.com/ChristopherA) | \ | FDFE 14A5 4ECB 30FC 5D22 74EF F8D3 6C91 3574 05ED | 59 | 60 | ## Responsible Disclosure 61 | 62 | We want to keep all of our software safe for everyone. If you have discovered a security vulnerability, we appreciate your help in disclosing it to us in a responsible manner. We are unfortunately not able to offer bug bounties at this time. 63 | 64 | We do ask that you offer us good faith and use best efforts not to leak information or harm any user, their data, or our developer community. Please give us a reasonable amount of time to fix the issue before you publish it. Do not defraud our users or us in the process of discovery. We promise not to bring legal action against researchers who point out a problem provided they do their best to follow the these guidelines. 65 | 66 | ### Reporting a Vulnerability 67 | 68 | Please report suspected security vulnerabilities in private via email to ChristopherA@BlockchainCommons.com (do not use this email for support). Please do NOT create publicly viewable issues for suspected security vulnerabilities. 69 | 70 | The following keys may be used to communicate sensitive information to developers: 71 | 72 | | Name | Fingerprint | 73 | | ----------------- | -------------------------------------------------- | 74 | | Christopher Allen | FDFE 14A5 4ECB 30FC 5D22 74EF F8D3 6C91 3574 05ED | 75 | 76 | You can import a key by running the following command with that individual’s fingerprint: `gpg --recv-keys ""` Ensure that you put quotes around fingerprints that contain spaces. 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Unless otherwise noted (either in /README.md or in the file's header comments) the contents of this repository are released under the following license: 2 | 3 | BSD-2-Clause Plus Patent License 4 | 5 | SPDX-License-Identifier: [BSD-2-Clause-Patent](https://spdx.org/licenses/BSD-2-Clause-Patent.html) 6 | 7 | Copyright © 2021 Blockchain Commons, LLC 8 | 9 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 10 | 11 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 12 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 13 | Subject to the terms and conditions of this license, each copyright holder and contributor hereby grants to those receiving rights under this license a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except for failure to satisfy the conditions of this license) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer this software, where such license applies only to those patent claims, already acquired or hereafter acquired, licensable by such copyright holder or contributor that are necessarily infringed by: 14 | 15 | (a) their Contribution(s) (the licensed copyrights of copyright holders and non-copyrightable additions of contributors, in source or binary form) alone; or 16 | (b) combination of their Contribution(s) with the work of authorship to which such Contribution(s) was added by such copyright holder or contributor, if, at the time the Contribution is added, such addition causes such combination to be necessarily infringed. The patent license shall not apply to any other combinations which include the Contribution. 17 | Except as expressly stated above, no rights or licenses from any copyright holder or contributor is granted under this license, whether expressly, by implication, estoppel or otherwise. 18 | 19 | DISCLAIMER 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:6.0 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "QRCodeGenerator", 7 | platforms: [ 8 | .macOS(.v11), 9 | .iOS(.v14) 10 | ], 11 | products: [ 12 | .library( 13 | name: "QRCodeGenerator", 14 | targets: ["QRCodeGenerator"]), 15 | ], 16 | targets: [ 17 | .target( 18 | name: "QRCodeGenerator", 19 | dependencies: []), 20 | .testTarget( 21 | name: "QRCodeGeneratorTests", 22 | dependencies: ["QRCodeGenerator"]), 23 | ] 24 | ) 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Blockchain Commons QRCodeGenerator 2 | 3 | ### by Wolf McNally 4 | 5 | QRCodeGenerator is a pure Swift translation of the [Project Nayuki QR code generator library](https://www.nayuki.io/page/qr-code-generator-library). Unlike Apple's built-in APIs for QR code generation, this package allows more sophisticated and tuneable encoding techniques that produce more compact QR codes in certain circumstances, including the ability to automatically optimize segmenting for greatest compactness. See the unit tests for examples. 6 | 7 | See the [QRCodeGeneratorDemo](https://github.com/BlockchainCommons/QRCodeGeneratorDemo) repo for an iOS app that demonstrates this library. 8 | 9 | ## Why Another QR Code Generator? 10 | 11 | The QR code standard provides for QR codes to be composed of a heterogenous sequence of "segments," each of which is optimized for encoding different sorts of data, and each requiring a different number of bits per character to encode. Therefore, choosing which segment type (or series of segment types) to use for efficient encoding is important. 12 | 13 | | Segment Type | Bits Per Character | Character Set | 14 | | :--- | :--- | :--- | 15 | | `bytes` | 8 | `0x00` - `0xff` | 16 | | `numeric` | 3.33 | `0123456789` | 17 | | `alphanumeric ` | 5.5 | `0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:` | 18 | | `kanji` | 13 | [Shift JIS](https://en.wikipedia.org/wiki/Shift_JIS) | 19 | 20 | Many QR code generators, including the one built into iOS and macOS, take an undifferentiated block of byte data as input and analyze it to determine which single segment type can most efficiently encode it. For instance, if the block contains only the ASCII Arabic numerals, the `numeric` segment type is selected. This results in the encoding only taking 3.33 bits per character. Similarly, if every character in the input block is in the limited character set provided by the `alphanumeric` encoding node, the encoding takes only 5.5 bits per character. 21 | 22 | The [Blockchain Commons UR standard](https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-005-ur.md) is designed to be compatible with the `alphanumeric` segment encoding node: URs are case-insensitive, and when a UR is transformed to upper case, it always is encodable in the `alphanumeric` mode. This results in an efficient encoding and therefore a less-dense QR code. 23 | 24 | However, not all formats are this straightforward. For example the data for the [SMART Health Card (SHC)](https://smarthealth.cards/) format is a URI that starts `shc:/` followed by often over a thousand numeric digits. The first five characters require the `bytes` encoding mode, while the following digits would be most efficiently encoded using the `numeric` mode. But if only one segment type can be selected, then it must be the `bytes` mode. For the SHC format, this results in a much denser QR code that requires more screen resolution to display and higher quality cameras to read. The solution is to encode the data as two segments: the first encoding the header using the `bytes` mode, and the second with the body using the `numeric` mode. 25 | 26 | It important to understand that no matter how many segments of any kind are used to create a QR code, all QR code readers are capable of reading them. So which segments to use is strictly a matter for the encoder to decide: the decoder always sees a scanned QR code as a string of UTF-8 bytes. 27 | 28 | So for certain data types, the QR code generator built into iOS and macOS will have less than efficient results. This QRCodeGenerator package allows you to automatically pick a segment type as the built-in generator does, or specify a sequence of segments, giving you control of encoding efficiency over all the segments. 29 | 30 | If you know the most efficient encoding for a data format in advance, you can manually specify a series of segments. But QRCodeGenerator also includes "optimal encoding" functions that search for and discover not a *single* segment type, but a *sequence* of most-efficient segment types. The use of this optimal encoding is optional because it does take a bit more processing power to search the space of possible segment encoding types to arrive at the optimal one. 31 | 32 | The ability to manually specify a sequence of segment types, or to have the package pick an optimal one automatically are the major advantages that QRCodeGenerator has over the built-in generator. 33 | 34 | As an example: the following Chinese string includes a variety of types of characters: 35 | 36 | ``` 37 | 維基百科(Wikipedia,聆聽i/ˌwɪkᵻˈpiːdi.ə/)是一個自由內容、公開編輯且多語言的網路百科全書協作計畫 38 | ``` 39 | 40 | If `Segment.makeBytes(text:)` is called with the above string, it returns the following single `Segment` that requires 1120 bits of space to encode: 41 | 42 | ``` 43 | Segment(mode: byte, characterCount: 140, data: <1120 bits>) 44 | ``` 45 | 46 | However, if `Segment.makeSegmentsOptimally(text:)` is called, it returns the following sequence of `Segment`s: 47 | 48 | ``` 49 | Segment(mode: kanji, characterCount: 5, data: <65 bits>) 50 | Segment(mode: byte, characterCount: 9, data: <72 bits>) 51 | Segment(mode: kanji, characterCount: 3, data: <39 bits>) 52 | Segment(mode: byte, characterCount: 23, data: <184 bits>) 53 | Segment(mode: kanji, characterCount: 6, data: <78 bits>) 54 | Segment(mode: byte, characterCount: 3, data: <24 bits>) 55 | Segment(mode: kanji, characterCount: 21, data: <273 bits>) 56 | ``` 57 | 58 | This totals to 735 bits, a 48% savings. 59 | 60 | ## Status - Release 61 | 62 | QRCodeGenerator is a translation of [Nayuki's library](https://www.nayuki.io/page/qr-code-generator-library). Its unit tests verify that it produces the exact same QR codes as those produced in the original source demo app. The maintainers believe this release to be stable and useful. 63 | 64 | ## Prerequisites 65 | 66 | This package has no dependencies. When compiled for iOS or macOS, the `QRCode` type has a `makeImage` function that produces a `UIImage` or `NSImage` that allows the specification of border size, module size, and colors. 67 | 68 | ## Installation Instructions 69 | 70 | Add this package to your project as a normal Swift package. 71 | 72 | ## Usage Instructions 73 | 74 | See the unit tests for extensive examples of use. A simple example: 75 | 76 | ```swift 77 | let qr = try QRCode.encode(text: "Hello, world!", correctionLevel: .low) 78 | print(qr.emojiString) 79 | 80 | ⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬜️⬜️⬜️⬜️⬜️⬛️⬜️⬛️⬛️⬛️⬛️⬛️⬛️⬛️ 81 | ⬛️⬜️⬜️⬜️⬜️⬜️⬛️⬜️⬜️⬛️⬜️⬛️⬜️⬜️⬛️⬜️⬜️⬜️⬜️⬜️⬛️ 82 | ⬛️⬜️⬛️⬛️⬛️⬜️⬛️⬜️⬛️⬜️⬛️⬛️⬛️⬜️⬛️⬜️⬛️⬛️⬛️⬜️⬛️ 83 | ⬛️⬜️⬛️⬛️⬛️⬜️⬛️⬜️⬛️⬜️⬜️⬜️⬜️⬜️⬛️⬜️⬛️⬛️⬛️⬜️⬛️ 84 | ⬛️⬜️⬛️⬛️⬛️⬜️⬛️⬜️⬛️⬛️⬜️⬜️⬛️⬜️⬛️⬜️⬛️⬛️⬛️⬜️⬛️ 85 | ⬛️⬜️⬜️⬜️⬜️⬜️⬛️⬜️⬛️⬛️⬛️⬛️⬜️⬜️⬛️⬜️⬜️⬜️⬜️⬜️⬛️ 86 | ⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬜️⬛️⬜️⬛️⬜️⬛️⬜️⬛️⬛️⬛️⬛️⬛️⬛️⬛️ 87 | ⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬛️⬜️⬛️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️ 88 | ⬛️⬜️⬛️⬛️⬛️⬛️⬛️⬜️⬜️⬛️⬛️⬛️⬜️⬜️⬛️⬛️⬛️⬛️⬛️⬜️⬜️ 89 | ⬜️⬜️⬜️⬛️⬛️⬜️⬜️⬛️⬛️⬜️⬜️⬜️⬛️⬛️⬜️⬜️⬛️⬛️⬛️⬜️⬛️ 90 | ⬜️⬜️⬜️⬛️⬜️⬜️⬛️⬜️⬛️⬛️⬛️⬜️⬛️⬛️⬛️⬜️⬜️⬛️⬛️⬛️⬜️ 91 | ⬜️⬛️⬛️⬜️⬜️⬛️⬜️⬛️⬜️⬜️⬛️⬛️⬛️⬛️⬜️⬛️⬜️⬛️⬛️⬜️⬜️ 92 | ⬛️⬛️⬜️⬛️⬛️⬛️⬛️⬜️⬛️⬜️⬜️⬜️⬛️⬜️⬛️⬛️⬜️⬜️⬜️⬜️⬛️ 93 | ⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬛️⬜️⬜️⬜️⬜️⬛️⬛️⬛️⬛️⬛️⬜️⬜️⬜️ 94 | ⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬜️⬜️⬛️⬛️⬜️⬛️⬛️⬛️⬛️⬜️⬜️⬛️⬛️⬜️ 95 | ⬛️⬜️⬜️⬜️⬜️⬜️⬛️⬜️⬛️⬜️⬛️⬜️⬛️⬛️⬜️⬛️⬜️⬛️⬛️⬛️⬜️ 96 | ⬛️⬜️⬛️⬛️⬛️⬜️⬛️⬜️⬛️⬛️⬜️⬛️⬛️⬛️⬛️⬜️⬛️⬜️⬜️⬛️⬛️ 97 | ⬛️⬜️⬛️⬛️⬛️⬜️⬛️⬜️⬛️⬜️⬛️⬜️⬜️⬜️⬜️⬛️⬛️⬛️⬜️⬜️⬜️ 98 | ⬛️⬜️⬛️⬛️⬛️⬜️⬛️⬜️⬛️⬛️⬛️⬛️⬛️⬜️⬛️⬛️⬜️⬜️⬛️⬜️⬜️ 99 | ⬛️⬜️⬜️⬜️⬜️⬜️⬛️⬜️⬜️⬜️⬛️⬜️⬛️⬛️⬜️⬜️⬛️⬛️⬛️⬜️⬜️ 100 | ⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬜️⬛️⬛️⬜️⬛️⬜️⬜️⬛️⬜️⬛️⬜️⬜️⬛️⬜️ 101 | ``` 102 | 103 | ## Origin, Authors, Copyright & Licenses 104 | 105 | Unless otherwise noted (either in this [/README.md](./README.md) or in the file's header comments) the contents of this repository are Copyright © 2021 by Blockchain Commons, LLC, and are [licensed](./LICENSE) under the [spdx:BSD-2-Clause Plus Patent License](https://spdx.org/licenses/BSD-2-Clause-Patent.html). 106 | 107 | ### Derived from ... 108 | 109 | This QRCodeGenerator project is either derived from or was inspired by: 110 | 111 | * The [Project Nayuki QR Code generator library](https://www.nayuki.io/page/qr-code-generator-library) 112 | 113 | While this project does not include any actual source code from the Nayuki's library, it is closely based on that library's C++ implementation, which is released under the MIT license. 114 | 115 | ## Financial Support 116 | 117 | QRCodeGenerator is a project of [Blockchain Commons](https://www.blockchaincommons.com/). We are proudly a "not-for-profit" social benefit corporation committed to open source & open development. Our work is funded entirely by donations and collaborative partnerships with people like you. Every contribution will be spent on building open tools, technologies, and techniques that sustain and advance blockchain and internet security infrastructure and promote an open web. 118 | 119 | To financially support further development of QRCodeGenerator and other projects, please consider becoming a Patron of Blockchain Commons through ongoing monthly patronage as a [GitHub Sponsor](https://github.com/sponsors/BlockchainCommons). You can also support Blockchain Commons with bitcoins at our [BTCPay Server](https://btcpay.blockchaincommons.com/). 120 | 121 | ## Contributing 122 | 123 | We encourage public contributions through issues and pull requests! Please review [CONTRIBUTING.md](./CONTRIBUTING.md) for details on our development process. All contributions to this repository require a GPG signed [Contributor License Agreement](./CLA.md). 124 | 125 | ### Discussions 126 | 127 | The best place to talk about Blockchain Commons and its projects is in our GitHub Discussions areas. 128 | 129 | [**Gordian Developer Community**](https://github.com/BlockchainCommons/Gordian-Developer-Community/discussions). For standards and open-source developers who want to talk about interoperable wallet specifications, please use the Discussions area of the [Gordian Developer Community repo](https://github.com/BlockchainCommons/Gordian-Developer-Community/discussions). This is where you talk about Gordian specifications such as [Gordian Envelope](https://github.com/BlockchainCommons/Gordian/tree/master/Envelope#articles), [bc-shamir](https://github.com/BlockchainCommons/bc-shamir), [Sharded Secret Key Reconstruction](https://github.com/BlockchainCommons/bc-sskr), and [bc-ur](https://github.com/BlockchainCommons/bc-ur) as well as the larger [Gordian Architecture](https://github.com/BlockchainCommons/Gordian/blob/master/Docs/Overview-Architecture.md), its [Principles](https://github.com/BlockchainCommons/Gordian#gordian-principles) of independence, privacy, resilience, and openness, and its macro-architectural ideas such as functional partition (including airgapping, the original name of this community). 130 | 131 | [**Blockchain Commons Discussions**](https://github.com/BlockchainCommons/Community/discussions). For developers, interns, and patrons of Blockchain Commons, please use the discussions area of the [Community repo](https://github.com/BlockchainCommons/Community) to talk about general Blockchain Commons issues, the intern program, or topics other than those covered by the [Gordian Developer Community](https://github.com/BlockchainCommons/Gordian-Developer-Community/discussions) or the 132 | [Gordian User Community](https://github.com/BlockchainCommons/Gordian/discussions). 133 | ### Other Questions & Problems 134 | 135 | As an open-source, open-development community, Blockchain Commons does not have the resources to provide direct support of our projects. Please consider the discussions area as a locale where you might get answers to questions. Alternatively, please use this repository's [issues](./issues) feature. Unfortunately, we can not make any promises on response time. 136 | 137 | If your company requires support to use our projects, please feel free to contact us directly about options. We may be able to offer you a contract for support from one of our contributors, or we might be able to point you to another entity who can offer the contractual support that you need. 138 | 139 | ### Credits 140 | 141 | The following people directly contributed to this repository. You can add your name here by getting involved. The first step is learning how to contribute from our [CONTRIBUTING.md](./CONTRIBUTING.md) documentation. 142 | 143 | | Name | Role | Github | Email | GPG Fingerprint | 144 | | ----------------- | ------------------- | ------------------------------------------------- | ------------------------------------- | -------------------------------------------------- | 145 | | Wolf McNally | Project Lead | [@WolfMcNally](https://github.com/WolfMcNally) | \ | 9436 52EE 3844 1760 C3DC  3536 4B6C 2FCF 8947 80AE | 146 | | Christopher Allen | Principal Architect | [@ChristopherA](https://github.com/ChristopherA) | \ | FDFE 14A5 4ECB 30FC 5D22 74EF F8D3 6C91 3574 05ED | 147 | 148 | ## Responsible Disclosure 149 | 150 | We want to keep all of our software safe for everyone. If you have discovered a security vulnerability, we appreciate your help in disclosing it to us in a responsible manner. We are unfortunately not able to offer bug bounties at this time. 151 | 152 | We do ask that you offer us good faith and use best efforts not to leak information or harm any user, their data, or our developer community. Please give us a reasonable amount of time to fix the issue before you publish it. Do not defraud our users or us in the process of discovery. We promise not to bring legal action against researchers who point out a problem provided they do their best to follow the these guidelines. 153 | 154 | ### Reporting a Vulnerability 155 | 156 | Please report suspected security vulnerabilities in private via email to ChristopherA@BlockchainCommons.com (do not use this email for support). Please do NOT create publicly viewable issues for suspected security vulnerabilities. 157 | 158 | The following keys may be used to communicate sensitive information to developers: 159 | 160 | | Name | Fingerprint | 161 | | ----------------- | -------------------------------------------------- | 162 | | Christopher Allen | FDFE 14A5 4ECB 30FC 5D22 74EF F8D3 6C91 3574 05ED | 163 | 164 | You can import a key by running the following command with that individual’s fingerprint: `gpg --recv-keys ""` Ensure that you put quotes around fingerprints that contain spaces. 165 | -------------------------------------------------------------------------------- /Sources/QRCodeGenerator/BitBuffer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BitBuffer.swift 3 | // QRCodeGenerator 4 | // 5 | // Copyright © 2021 by Blockchain Commons, LLC 6 | // Licensed under the "BSD-2-Clause Plus Patent License" 7 | // 8 | // Created by Wolf McNally on 7/20/21. 9 | // 10 | // Based on: QR Code generator library (C++) 11 | // Copyright (c) Project Nayuki. (MIT License) 12 | // https://www.nayuki.io/page/qr-code-generator-library 13 | 14 | import Foundation 15 | 16 | public extension Array where Element == Bool { 17 | mutating func appendBits(_ val: T, _ len: Int) where T: BinaryInteger { 18 | precondition((0...31).contains(len) && val >> len == 0) 19 | 20 | guard len > 0 else { 21 | return 22 | } 23 | 24 | for i in (0...(len - 1)).reversed() { 25 | append(((val >> i) & 1) != 0) // Append bit by bit 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/QRCodeGenerator/CorrectionLevel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OSImage.swift 3 | // QRCodeGenerator 4 | // 5 | // Copyright © 2021 by Blockchain Commons, LLC 6 | // Licensed under the "BSD-2-Clause Plus Patent License" 7 | // 8 | // Created by Wolf McNally on 7/23/21. 9 | // 10 | 11 | import Foundation 12 | 13 | // MARK: - Public helper enumeration 14 | 15 | public enum CorrectionLevel: Int { 16 | case low // The QR Code can tolerate about 7% erroneous codewords 17 | case medium // The QR Code can tolerate about 15% erroneous codewords 18 | case quartile // The QR Code can tolerate about 25% erroneous codewords 19 | case high // The QR Code can tolerate about 30% erroneous codewords 20 | 21 | var formatBits: Int { 22 | Self._formatBits[rawValue] 23 | } 24 | 25 | private static let _formatBits = [1, 0, 3, 2] 26 | } 27 | -------------------------------------------------------------------------------- /Sources/QRCodeGenerator/OSImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OSImage.swift 3 | // QRCodeGenerator 4 | // 5 | // Copyright © 2021 by Blockchain Commons, LLC 6 | // Licensed under the "BSD-2-Clause Plus Patent License" 7 | // 8 | // Created by Wolf McNally on 12/31/20. 9 | // 10 | 11 | #if canImport(UIKit) 12 | import UIKit 13 | public typealias OSImage = UIImage 14 | public typealias OSColor = UIColor 15 | public let blackOSColor = UIColor.black 16 | public let whiteOSColor = UIColor.white 17 | #elseif canImport(AppKit) 18 | import AppKit 19 | public typealias OSImage = NSImage 20 | public typealias OSColor = NSColor 21 | public let blackOSColor = NSColor(red: 0, green: 0, blue: 0, alpha: 1) 22 | public let whiteOSColor = NSColor(red: 1, green: 1, blue: 1, alpha: 1) 23 | #endif 24 | -------------------------------------------------------------------------------- /Sources/QRCodeGenerator/Private/Canvas.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Canvas.swift 3 | // QRCodeGenerator 4 | // 5 | // Copyright © 2021 by Blockchain Commons, LLC 6 | // Licensed under the "BSD-2-Clause Plus Patent License" 7 | // 8 | // Created by Wolf McNally on 7/6/20. 9 | // 10 | 11 | #if canImport(AppKit) || canImport(UIKit) 12 | import Foundation 13 | import Accelerate 14 | import CoreGraphics 15 | #endif 16 | 17 | #if canImport(UIKit) 18 | import UIKit 19 | #elseif canImport(AppKit) 20 | import AppKit 21 | #endif 22 | 23 | #if canImport(AppKit) || canImport(UIKit) 24 | class Canvas { 25 | let size: IntSize 26 | 27 | private let chunkyBytesCount: Int 28 | private let planarFloatsCount: Int 29 | private let planarFloatsPerRow: Int 30 | 31 | private let argb8Data: UnsafeMutablePointer 32 | private let argb8PremultipliedData: UnsafeMutablePointer 33 | private let alphaFData: UnsafeMutablePointer 34 | private let redFData: UnsafeMutablePointer 35 | private let greenFData: UnsafeMutablePointer 36 | private let blueFData: UnsafeMutablePointer 37 | 38 | private var argb8: vImage_Buffer 39 | private var argb8Premultiplied: vImage_Buffer 40 | private var alphaF: vImage_Buffer 41 | private var redF: vImage_Buffer 42 | private var greenF: vImage_Buffer 43 | private var blueF: vImage_Buffer 44 | 45 | private var maxPixelValues: [Float] = [1, 1, 1, 1] 46 | private var minPixelValues: [Float] = [0, 0, 0, 0] 47 | private let context: CGContext 48 | private var _image: CGImage? 49 | 50 | init(size: IntSize) { 51 | self.size = size 52 | 53 | assert(size.width >= 1, "width must be >= 1") 54 | assert(size.height >= 1, "height must be >= 1") 55 | 56 | let colorSpace = CGColorSpaceCreateDeviceRGB() 57 | let componentsPerPixel = Int(colorSpace.numberOfComponents) + 1 // alpha 58 | 59 | let chunkyBytesPerComponent = 1 60 | let chunkyBitsPerComponent = chunkyBytesPerComponent * 8 61 | let chunkyBytesPerPixel = componentsPerPixel * chunkyBytesPerComponent 62 | let chunkyBytesPerRow = Int(UInt(size.width * chunkyBytesPerPixel + 15) & ~UInt(0xf)) 63 | chunkyBytesCount = size.height * chunkyBytesPerRow 64 | 65 | let planarBytesPerComponent = MemoryLayout.size 66 | let planarBytesPerRow = Int(UInt(size.width * planarBytesPerComponent * componentsPerPixel + 15) & ~UInt(0xf)) 67 | planarFloatsPerRow = planarBytesPerRow >> 2 68 | planarFloatsCount = size.height * planarFloatsPerRow 69 | 70 | argb8Data = UnsafeMutablePointer.allocate(capacity: chunkyBytesCount) 71 | argb8PremultipliedData = UnsafeMutablePointer.allocate(capacity: chunkyBytesCount) 72 | alphaFData = UnsafeMutablePointer.allocate(capacity: planarFloatsCount) 73 | redFData = UnsafeMutablePointer.allocate(capacity: planarFloatsCount) 74 | greenFData = UnsafeMutablePointer.allocate(capacity: planarFloatsCount) 75 | blueFData = UnsafeMutablePointer.allocate(capacity: planarFloatsCount) 76 | 77 | let uWidth = vImagePixelCount(size.width) 78 | let uHeight = vImagePixelCount(size.height) 79 | 80 | // argb8 = vImage_Buffer(data: argb8Data, height: uHeight, width: uWidth, rowBytes: chunkyBytesPerRow) 81 | argb8 = vImage_Buffer(data: argb8Data, height: vImagePixelCount(size.height), width: uWidth, rowBytes: chunkyBytesPerRow) 82 | argb8Premultiplied = vImage_Buffer(data: argb8PremultipliedData, height: uHeight, width: uWidth, rowBytes: chunkyBytesPerRow) 83 | alphaF = vImage_Buffer(data: alphaFData, height: uHeight, width: uWidth, rowBytes: planarBytesPerRow) 84 | redF = vImage_Buffer(data: redFData, height: uHeight, width: uWidth, rowBytes: planarBytesPerRow) 85 | greenF = vImage_Buffer(data: greenFData, height: uHeight, width: uWidth, rowBytes: planarBytesPerRow) 86 | blueF = vImage_Buffer(data: blueFData, height: uHeight, width: uWidth, rowBytes: planarBytesPerRow) 87 | 88 | let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue) 89 | context = CGContext(data: argb8PremultipliedData, width: size.width, height: size.height, bitsPerComponent: chunkyBitsPerComponent, bytesPerRow: chunkyBytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo.rawValue)! 90 | } 91 | 92 | deinit { 93 | argb8Data.deallocate() 94 | argb8PremultipliedData.deallocate() 95 | alphaFData.deallocate() 96 | redFData.deallocate() 97 | greenFData.deallocate() 98 | blueFData.deallocate() 99 | } 100 | 101 | #if canImport(UIKit) 102 | var image: UIImage { 103 | UIImage(cgImage: cgImage) 104 | } 105 | #elseif canImport(AppKit) 106 | var image: NSImage { 107 | let size = CGSize(width: CGFloat(self.size.width), height: CGFloat(self.size.height)) 108 | return NSImage(cgImage: cgImage, size: size) 109 | } 110 | #endif 111 | 112 | var cgImage: CGImage { 113 | get { 114 | if self._image == nil { 115 | var error = vImageConvert_PlanarFToARGB8888(&alphaF, &redF, &greenF, &blueF, &argb8, &maxPixelValues, &minPixelValues, UInt32(kvImageNoFlags)) 116 | assert(error == kvImageNoError, "Error when converting canvas to chunky") 117 | error = vImagePremultiplyData_ARGB8888(&argb8, &argb8Premultiplied, UInt32(kvImageNoFlags)) 118 | assert(error == kvImageNoError, "Error when premultiplying canvas") 119 | _image = self.context.makeImage() 120 | assert(self._image != nil, "Error when converting") 121 | } 122 | return self._image! 123 | } 124 | } 125 | 126 | func invalidateImage() { 127 | self._image = nil 128 | } 129 | 130 | private func offsetForPoint(_ point: IntPoint) -> Int { 131 | return planarFloatsPerRow * point.y + point.x 132 | } 133 | 134 | func setPoint(_ point: IntPoint, to color: OSColor) { 135 | invalidateImage() 136 | 137 | var r: CGFloat = 0 138 | var g: CGFloat = 0 139 | var b: CGFloat = 0 140 | var a: CGFloat = 0 141 | #if canImport(UIKit) 142 | let success = color.getRed(&r, green: &g, blue: &b, alpha: &a) 143 | precondition(success) 144 | #elseif canImport(AppKit) 145 | if let rgbColor = color.usingColorSpace(.deviceRGB) { 146 | rgbColor.getRed(&r, green: &g, blue: &b, alpha: &a) 147 | } else { 148 | fatalError("Failed to convert to RGB color space") 149 | } 150 | #endif 151 | let offset = offsetForPoint(point) 152 | alphaFData[offset] = Float(a) 153 | redFData[offset] = Float(r) 154 | greenFData[offset] = Float(g) 155 | blueFData[offset] = Float(b) 156 | } 157 | } 158 | #endif 159 | -------------------------------------------------------------------------------- /Sources/QRCodeGenerator/Private/IntPoint.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IntPoint.swift 3 | // QRCodeGenerator 4 | // 5 | // Copyright © 2021 by Blockchain Commons, LLC 6 | // Licensed under the "BSD-2-Clause Plus Patent License" 7 | // 8 | // Created by Wolf McNally on 7/6/20. 9 | // 10 | 11 | import Foundation 12 | 13 | struct IntPoint { 14 | var x: Int 15 | var y: Int 16 | 17 | static let zero = IntPoint(x: 0, y: 0) 18 | } 19 | -------------------------------------------------------------------------------- /Sources/QRCodeGenerator/Private/IntSize.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IntSize.swift 3 | // QRCodeGenerator 4 | // 5 | // Copyright © 2021 by Blockchain Commons, LLC 6 | // Licensed under the "BSD-2-Clause Plus Patent License" 7 | // 8 | // Created by Wolf McNally on 7/6/20. 9 | // 10 | 11 | import Foundation 12 | 13 | struct IntSize { 14 | var width: Int 15 | var height: Int 16 | } 17 | -------------------------------------------------------------------------------- /Sources/QRCodeGenerator/QRCode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QRCode.swift 3 | // QRCodeGenerator 4 | // 5 | // Copyright © 2021 by Blockchain Commons, LLC 6 | // Licensed under the "BSD-2-Clause Plus Patent License" 7 | // 8 | // Created by Wolf McNally on 7/20/21. 9 | // 10 | // Based on: QR Code generator library (C++) 11 | // Copyright (c) Project Nayuki. (MIT License) 12 | // https://www.nayuki.io/page/qr-code-generator-library 13 | // 14 | 15 | import Foundation 16 | 17 | #if canImport(AppKit) 18 | import AppKit 19 | #elseif canImport(UIKit) 20 | import UIKit 21 | #endif 22 | 23 | #if canImport(SwiftUI) 24 | import SwiftUI 25 | #endif 26 | 27 | /** 28 | * A QR Code symbol, which is a type of two-dimension barcode. 29 | * Invented by Denso Wave and described in the ISO/IEC 18004 standard. 30 | * Instances of this class represent an immutable square grid of black and white cells. 31 | * The class provides static factory functions to create a QR Code from text or binary data. 32 | * The class covers the QR Code Model 2 specification, supporting all versions (sizes) 33 | * from 1 to 40, all 4 error correction levels, and 4 character encoding modes. 34 | * 35 | * Ways to create a QR Code object: 36 | * - High level: Take the payload data and call QRCode.encodeText() or QRCode.encodeBinary(). 37 | * - Mid level: Custom-make the list of segments and call QRCode.encodeSegments(). 38 | * - Low level: Custom-make the array of data codeword bytes (including 39 | * segment headers and final padding, excluding error correction codewords), 40 | * supply the appropriate version number, and call the QRCode() constructor. 41 | * (Note that all ways require supplying the desired error correction level.) 42 | */ 43 | public struct QRCode { 44 | // MARK: - Instance fields 45 | 46 | // Immutable scalar parameters: 47 | 48 | /// The version number of this QR Code, which is between 1 and 40 (inclusive). This determines the size of this barcode. 49 | public let version: Int 50 | 51 | /// The width and height of this QR Code, measured in modules, between 21 and 177 (inclusive). This is equal to version * 4 + 17. 52 | public let size: Int 53 | 54 | /// The error correction level used in this QR Code. 55 | public let correctionLevel: CorrectionLevel 56 | 57 | /// The index of the mask pattern used in this QR Code, which is between 0 and 7 (inclusive). Even if a QR Code is created with automatic masking requested (mask = nil), the resulting object still has a mask value between 0 and 7. 58 | public private(set) var mask: Int? 59 | 60 | /// The modules of this QR Code (false = white, true = black). 61 | public subscript(_ x: Int, _ y: Int) -> Bool { 62 | return 0 <= x && x < size && 0 <= y && y < size && module(x, y) 63 | } 64 | 65 | // MARK: - Private grids of modules/pixels, with dimensions of size*size 66 | 67 | /// The modules of this QR Code (false = white, true = black). 68 | /// Immutable after constructor finishes. Accessed through subscript(x, y). 69 | private var modules: [[Bool]] 70 | 71 | /// Indicates function modules that are not subjected to masking. Discarded when constructor finishes. 72 | private var isFunction: [[Bool]] 73 | 74 | // MARK: - Output functions 75 | 76 | public var emojiString: String { 77 | var lines: [String] = [] 78 | for y in 0 ..< size { 79 | var s = "" 80 | for x in 0 ..< size { 81 | s.append(self[x, y] ? "⬛️" : "⬜️") 82 | } 83 | lines.append(s) 84 | } 85 | return lines.joined(separator: "\n") 86 | } 87 | 88 | #if canImport(AppKit) || canImport(UIKit) 89 | public func makeImage(border: Int = 3, moduleSize: Int = 1, foregroundColor: OSColor = blackOSColor, backgroundColor: OSColor = whiteOSColor) -> OSImage { 90 | let fullSize = size + 2 * border 91 | let scaledSize = fullSize * moduleSize 92 | let canvas = Canvas(size: IntSize(width: scaledSize, height: scaledSize)) 93 | for y in 0 ..< fullSize { 94 | for x in 0 ..< fullSize { 95 | let color = self[x - border, y - border] ? foregroundColor : backgroundColor 96 | for py in 0 ..< moduleSize { 97 | for px in 0 ..< moduleSize { 98 | canvas.setPoint(IntPoint(x: x * moduleSize + px, y: y * moduleSize + py), to: color) 99 | } 100 | } 101 | } 102 | } 103 | return canvas.image 104 | } 105 | 106 | #if canImport(SwiftUI) 107 | #if canImport(UIKit) 108 | @available(iOS 13.0, *) 109 | public func image(border: Int = 3, moduleSize: Int = 1, foregroundColor: OSColor = blackOSColor, backgroundColor: OSColor = whiteOSColor) -> Image { 110 | Image(uiImage: makeImage(border: border, moduleSize: moduleSize, foregroundColor: foregroundColor, backgroundColor: backgroundColor)).interpolation(.none) 111 | } 112 | #elseif canImport(AppKit) 113 | @available(macOS 10.15, *) 114 | public func image(border: Int = 3, moduleSize: Int = 1, foregroundColor: OSColor = blackOSColor, backgroundColor: OSColor = whiteOSColor) -> Image { 115 | Image(nsImage: makeImage(border: border, moduleSize: moduleSize, foregroundColor: foregroundColor, backgroundColor: backgroundColor)).interpolation(.none) 116 | } 117 | #endif 118 | #endif 119 | 120 | #endif 121 | 122 | // MARK: - Static factory functions (high level) 123 | 124 | /** 125 | * Returns a QR Code representing the given Unicode text string at the given error correction level. 126 | * As a conservative upper bound, this function is guaranteed to succeed for strings that have 2953 or fewer 127 | * UTF-8 code units (not Unicode code points) if the low error correction level is used. The smallest possible 128 | * QR Code version is automatically chosen for the output. The ECC level of the result may be higher than 129 | * the ecl argument if it can be done without increasing the version. 130 | */ 131 | public static func encode(text: String, correctionLevel: CorrectionLevel = .medium, minVersion: Int = 1, maxVersion: Int = 40, mask: Int? = nil, boostEcl: Bool = true, optimize: Bool = false) throws -> QRCode { 132 | let segments = try Segment.makeText(text: text, correctionLevel: correctionLevel, minVersion: minVersion, maxVersion: maxVersion, optimize: optimize) 133 | return try encode(segments: segments, correctionLevel: correctionLevel, minVersion: minVersion, maxVersion: maxVersion, mask: mask, boostEcl: boostEcl) 134 | } 135 | 136 | public static func getInfo(text: String, correctionLevel: CorrectionLevel = .medium, minVersion: Int = 1, maxVersion: Int = 40, optimize: Bool = false) throws -> Info { 137 | let segments = try Segment.makeText(text: text, correctionLevel: correctionLevel, minVersion: minVersion, maxVersion: maxVersion, optimize: optimize) 138 | return try Self.getInfo(segments: segments) 139 | } 140 | 141 | /** 142 | * Returns a QR Code representing the given binary data at the given error correction level. 143 | * This function always encodes using the binary segment mode, not any text mode. The maximum number of 144 | * bytes allowed is 2953. The smallest possible QR Code version is automatically chosen for the output. 145 | * The ECC level of the result may be higher than the ecl argument if it can be done without increasing the version. 146 | */ 147 | public static func encode(data: [UInt8], correctionLevel: CorrectionLevel = .medium, minVersion: Int = 1, maxVersion: Int = 40, mask: Int? = nil, boostEcl: Bool = true) throws -> QRCode { 148 | let segment = try Segment.makeBytes(data: data) 149 | return try encode(segments: [segment], correctionLevel: correctionLevel, minVersion: minVersion, maxVersion: maxVersion, mask: mask, boostEcl: boostEcl) 150 | } 151 | 152 | public static func getInfo(data: [UInt8], correctionLevel: CorrectionLevel = .medium, minVersion: Int = 1, maxVersion: Int = 40) throws -> Info { 153 | let segment = try Segment.makeBytes(data: data) 154 | return try Self.getInfo(segments: [segment]) 155 | } 156 | 157 | // MARK: - Static factory functions (mid level) 158 | 159 | public static func dataCapacityBits(_ version: Int, _ correctionLevel: CorrectionLevel = .medium) -> Int { 160 | getNumDataCodewords(version, correctionLevel) * 8 161 | } 162 | 163 | public struct Info { 164 | public let version: Int 165 | public let size: Int 166 | public let correctionLevel: CorrectionLevel 167 | public let dataUsedBits: Int 168 | public let dataCapacityBits: Int 169 | } 170 | 171 | public static func getInfo(segments: [Segment], correctionLevel: CorrectionLevel = .medium, minVersion: Int = 1, maxVersion: Int = 40) throws -> Info { 172 | try checkVersion(minVersion: minVersion, maxVersion: maxVersion) 173 | 174 | // Find the minimal version number to use 175 | var version = minVersion 176 | var capacityBits = -1 177 | var usedBits = -1 178 | while true { 179 | capacityBits = Self.dataCapacityBits(version, correctionLevel) // Number of data bits available 180 | usedBits = Segment.getTotalBits(segments, version) 181 | if usedBits != -1 && usedBits <= capacityBits { 182 | break // This version number is found to be suitable 183 | } 184 | if version >= maxVersion { // All versions in the range could not fit the given data 185 | throw QRError.dataTooLong(usedBits, capacityBits) 186 | } 187 | 188 | version += 1 189 | } 190 | precondition(usedBits != -1) 191 | 192 | return Info(version: version, size: version * 4 + 17, correctionLevel: correctionLevel, dataUsedBits: usedBits, dataCapacityBits: capacityBits) 193 | } 194 | 195 | /** 196 | * Returns a QR Code representing the given segments with the given encoding parameters. 197 | * The smallest possible QR Code version within the given range is automatically 198 | * chosen for the output. Iff boostEcl is true, then the ECC level of the result 199 | * may be higher than the ecl argument if it can be done without increasing the 200 | * version. The mask number is either between 0 to 7 (inclusive) to force that 201 | * mask, or `nil` to automatically choose an appropriate mask (which may be slow). 202 | * This function allows the user to create a custom sequence of segments that switches 203 | * between modes (such as alphanumeric and byte) to encode text in less space. 204 | * This is a mid-level API; the high-level API is encodeText() and encodeBinary(). 205 | */ 206 | public static func encode(segments: [Segment], correctionLevel: CorrectionLevel = .medium, minVersion: Int = 1, maxVersion: Int = 40, mask: Int? = nil, boostEcl: Bool = true) throws -> QRCode { 207 | let info = try Self.getInfo(segments: segments, correctionLevel: correctionLevel, minVersion: minVersion, maxVersion: maxVersion) 208 | 209 | let dataUsedBits = info.dataUsedBits 210 | let version = info.version 211 | 212 | // Increase the error correction level while the data still fits in the current version number 213 | var ecl = correctionLevel 214 | for newEcl in [CorrectionLevel.medium, CorrectionLevel.quartile, CorrectionLevel.high] { // From low to high 215 | if boostEcl && dataUsedBits <= Self.dataCapacityBits(version, newEcl) { 216 | ecl = newEcl 217 | } 218 | } 219 | 220 | // Concatenate all segments to create the data bit string 221 | var bb: [Bool] = [] 222 | for seg in segments { 223 | bb.appendBits(seg.mode.modeBits, 4) 224 | bb.appendBits(seg.characterCount, seg.mode.numCharCountBits(version)) 225 | bb.append(contentsOf: seg.data) 226 | } 227 | precondition(bb.count == dataUsedBits) 228 | 229 | // Add terminator and pad up to a byte if applicable 230 | let dataCapacityBits = Self.dataCapacityBits(version, ecl) 231 | precondition(bb.count <= dataCapacityBits) 232 | bb.appendBits(0, min(4, dataCapacityBits - bb.count)) 233 | bb.appendBits(0, (8 - bb.count % 8) % 8) 234 | precondition(bb.count % 8 == 0) 235 | 236 | // Pad with alternating bytes until data capacity is reached 237 | var padByte = 0xEC 238 | while bb.count < dataCapacityBits { 239 | bb.appendBits(padByte, 8) 240 | padByte ^= 0xEC ^ 0x11 241 | } 242 | 243 | // Pack bits into bytes in big endian 244 | var dataCodewords = [UInt8](repeating: 0, count: bb.count / 8) 245 | for i in 0 ..< bb.count { 246 | dataCodewords[i >> 3] |= (bb[i] ? 1 : 0) << (7 - (i & 7)) 247 | } 248 | 249 | // Create the QR Code object 250 | return try QRCode(version: version, correctionLevel: ecl, dataCodewords: dataCodewords, mask: mask) 251 | } 252 | 253 | static func checkVersion(minVersion: Int, maxVersion: Int) throws { 254 | guard [minVersion, maxVersion].allSatisfy({ (Self.minVersion...Self.maxVersion).contains($0) }) else { 255 | throw QRError.invalidVersion 256 | } 257 | } 258 | 259 | // MARK: - Constructor (low level) 260 | 261 | /** 262 | * Creates a new QR Code with the given version number, 263 | * error correction level, data codeword bytes, and mask number. 264 | * This is a low-level API that most users should not use directly. 265 | * A mid-level API is the encodeSegments() function. 266 | */ 267 | public init(version: Int, correctionLevel: CorrectionLevel = .medium, dataCodewords: [UInt8], mask: Int?) throws { 268 | self.version = version 269 | self.correctionLevel = correctionLevel 270 | self.mask = nil 271 | 272 | guard (Self.minVersion...Self.maxVersion).contains(version) else { 273 | throw QRError.invalidVersion 274 | } 275 | guard mask == nil || (0...7).contains(mask!) else { 276 | throw QRError.invalidMask 277 | } 278 | self.size = version * 4 + 17 279 | self.modules = [[Bool]](repeating: [Bool](repeating: false, count: size), count: size) // Initially all white 280 | self.isFunction = [[Bool]](repeating: [Bool](repeating: false, count: size), count: size) 281 | 282 | // Compute ECC, draw modules 283 | drawFunctionPatterns() 284 | let allCodewords = addEccAndInterleave(dataCodewords) 285 | drawCodewords(allCodewords) 286 | 287 | // Do masking 288 | var mask: Int! = mask 289 | if mask == nil { // Automatically choose best mask 290 | var minPenalty = Int.max 291 | for i in 0 ..< 8 { 292 | applyMask(i) 293 | drawFormatBits(i) 294 | let penalty = getPenaltyScore() 295 | if penalty < minPenalty { 296 | mask = i 297 | minPenalty = penalty 298 | } 299 | applyMask(i) // Undoes the mask due to XOR 300 | } 301 | } 302 | precondition((0...7).contains(mask)) 303 | self.mask = mask 304 | applyMask(mask) // Apply the final choice of mask 305 | drawFormatBits(mask) // Overwrite old format bits 306 | 307 | isFunction.removeAll() 308 | } 309 | 310 | // MARK: - Private helper methods for constructor: Drawing function modules 311 | 312 | /// Reads this object's version field, and draws and marks all function modules. 313 | private mutating func drawFunctionPatterns() { 314 | // Draw horizontal and vertical timing patterns 315 | for i in 0 ..< size { 316 | setFunctionModule(6, i, i % 2 == 0) 317 | setFunctionModule(i, 6, i % 2 == 0) 318 | } 319 | 320 | // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules) 321 | drawFinderPattern(3, 3) 322 | drawFinderPattern(size - 4, 3) 323 | drawFinderPattern(3, size - 4) 324 | 325 | // Draw numerous alignment patterns 326 | let alignPatPos = getAlignmentPatternPositions() 327 | let numAlign = alignPatPos.count 328 | for i in 0 ..< numAlign { 329 | for j in 0 ..< numAlign { 330 | // Don't draw on the three finder corners 331 | if !((i == 0 && j == 0) || (i == 0 && j == numAlign - 1) || (i == numAlign - 1 && j == 0)) { 332 | drawAlignmentPattern(alignPatPos[i], alignPatPos[j]) 333 | } 334 | } 335 | } 336 | 337 | // Draw configuration data 338 | drawFormatBits(0) // Dummy mask value; overwritten later in the constructor 339 | drawVersion() 340 | } 341 | 342 | /// Draws two copies of the format bits (with its own error correction code) 343 | /// based on the given mask and this object's error correction level field. 344 | private mutating func drawFormatBits(_ msk: Int) { 345 | // Calculate error correction code and pack bits 346 | let data = correctionLevel.formatBits << 3 | msk // errCorrLvl is uint2, msk is uint3 347 | var rem = data 348 | for _ in 0 ..< 10 { 349 | rem = (rem << 1) ^ ((rem >> 9) * 0x537) 350 | } 351 | let bits = (data << 10 | rem) ^ 0x5412 // uint15 352 | precondition(bits >> 15 == 0) 353 | 354 | // Draw first copy 355 | for i in 0...5 { 356 | setFunctionModule(8, i, Self.getBit(bits, i)) 357 | } 358 | setFunctionModule(8, 7, Self.getBit(bits, 6)) 359 | setFunctionModule(8, 8, Self.getBit(bits, 7)) 360 | setFunctionModule(7, 8, Self.getBit(bits, 8)) 361 | for i in 9..<15 { 362 | setFunctionModule(14 - i, 8, Self.getBit(bits, i)) 363 | } 364 | 365 | // Draw second copy 366 | for i in 0 ..< 8 { 367 | setFunctionModule(size - 1 - i, 8, Self.getBit(bits, i)) 368 | } 369 | for i in 8 ..< 15 { 370 | setFunctionModule(8, size - 15 + i, Self.getBit(bits, i)) 371 | } 372 | setFunctionModule(8, size - 8, true) // Always black 373 | } 374 | 375 | 376 | /// Draws two copies of the version bits (with its own error correction code), 377 | /// based on this object's version field, iff 7 <= version <= 40. 378 | private mutating func drawVersion() { 379 | guard version >= 7 else { 380 | return 381 | } 382 | 383 | // Calculate error correction code and pack bits 384 | var rem = version // version is uint6, in the range [7, 40] 385 | for _ in 0 ..< 12 { 386 | rem = (rem << 1) ^ ((rem >> 11) * 0x1F25) 387 | } 388 | let bits = version << 12 | rem // uint18 389 | precondition(bits >> 18 == 0) 390 | 391 | // Draw two copies 392 | for i in 0 ..< 18 { 393 | let bit = Self.getBit(bits, i) 394 | let a = size - 11 + i % 3 395 | let b = i / 3 396 | setFunctionModule(a, b, bit) 397 | setFunctionModule(b, a, bit) 398 | } 399 | } 400 | 401 | /// Draws a 9*9 finder pattern including the border separator, 402 | /// with the center module at (x, y). Modules can be out of bounds. 403 | private mutating func drawFinderPattern(_ x: Int, _ y: Int) { 404 | for dy in -4...4 { 405 | for dx in -4...4 { 406 | let dist = max(abs(dx), abs(dy)) // Chebyshev/infinity norm 407 | let xx = x + dx 408 | let yy = y + dy 409 | if 0 <= xx && xx < size && 0 <= yy && yy < size { 410 | setFunctionModule(xx, yy, dist != 2 && dist != 4) 411 | } 412 | } 413 | } 414 | } 415 | 416 | /// Draws a 5*5 alignment pattern, with the center module 417 | /// at (x, y). All modules must be in bounds. 418 | private mutating func drawAlignmentPattern(_ x: Int, _ y: Int) { 419 | for dy in -2...2 { 420 | for dx in -2...2 { 421 | setFunctionModule(x + dx, y + dy, max(abs(dx), abs(dy)) != 1) 422 | } 423 | } 424 | } 425 | 426 | /// Sets the color of a module and marks it as a function module. 427 | /// Only used by the constructor. Coordinates must be in bounds. 428 | private mutating func setFunctionModule(_ x: Int, _ y: Int, _ isBlack: Bool) { 429 | modules[y][x] = isBlack 430 | isFunction[y][x] = true 431 | } 432 | 433 | /// Returns the color of the module at the given coordinates, which must be in range. 434 | private func module(_ x: Int, _ y: Int) -> Bool { 435 | modules[y][x] 436 | } 437 | 438 | // MARK: - Private helper methods for constructor: Codewords and masking 439 | 440 | /// Returns a new byte string representing the given data with the appropriate error correction 441 | /// codewords appended to it, based on this object's version and error correction level. 442 | private func addEccAndInterleave(_ data: [UInt8]) -> [UInt8] { 443 | let codeWords = Self.getNumDataCodewords(version, correctionLevel) 444 | precondition(data.count == codeWords) 445 | 446 | // Calculate parameter numbers 447 | let numBlocks = Self.numErrorCorrectionBlocks[correctionLevel.rawValue][version] 448 | let blockEccLen = Self.eccCodewordsPerBlock[correctionLevel.rawValue][version] 449 | let rawCodewords = Self.getNumRawDataModules(version) / 8 450 | let numShortBlocks = numBlocks - rawCodewords % numBlocks 451 | let shortBlockLen = rawCodewords / numBlocks 452 | 453 | // Split data into blocks and append ECC to each block 454 | var blocks = [[UInt8]]() 455 | let rsDiv = Self.reedSolomonComputeDivisor(blockEccLen) 456 | var k = 0 457 | for i in 0 ..< numBlocks { 458 | var dat = Array(data[k..<(k + shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1))]) 459 | k += dat.count 460 | let ecc = Self.reedSolomonComputeRemainder(dat, rsDiv) 461 | if i < numShortBlocks { 462 | dat.append(0) 463 | } 464 | dat.append(contentsOf: ecc) 465 | blocks.append(dat) 466 | } 467 | 468 | // Interleave (not concatenate) the bytes from every block into a single sequence 469 | var result: [UInt8] = [] 470 | for i in 0 ..< blocks[0].count { 471 | for j in 0 ..< blocks.count { 472 | // Skip the padding byte in short blocks 473 | if i != (shortBlockLen - blockEccLen) || j >= numShortBlocks { 474 | result.append(blocks[j][i]) 475 | } 476 | } 477 | } 478 | precondition(result.count == rawCodewords) 479 | return result 480 | } 481 | 482 | /// Draws the given sequence of 8-bit codewords (data and error correction) onto the entire 483 | /// data area of this QR Code. Function modules need to be marked off before this is called. 484 | private mutating func drawCodewords(_ data: [UInt8]) { 485 | precondition(data.count == Self.getNumRawDataModules(version) / 8) 486 | 487 | var i = 0 // Bit index into the data 488 | // Do the funny zigzag scan 489 | var right = size - 1 490 | while right >= 1 { // Index of right column in each column pair} 491 | if right == 6 { 492 | right = 5 493 | } 494 | for vert in 0 ..< size { // Vertical counter 495 | for j in 0 ..< 2 { 496 | let x = right - j // Actual x coordinate 497 | let upward = ((right + 1) & 2) == 0 498 | let y = upward ? size - 1 - vert : vert // Actual y coordinate 499 | if !isFunction[y][x] && i < data.count * 8 { 500 | modules[y][x] = Self.getBit(Int(data[i >> 3]), 7 - (i & 7)) 501 | i += 1 502 | } 503 | // If this QR Code has any remainder bits (0 to 7), they were assigned as 504 | // 0/false/white by the constructor and are left unchanged by this method 505 | } 506 | } 507 | right -= 2 508 | } 509 | precondition(i == data.count * 8) 510 | } 511 | 512 | /// XORs the codeword modules in this QR Code with the given mask pattern. 513 | /// The function modules must be marked and the codeword bits must be drawn 514 | /// before masking. Due to the arithmetic of XOR, calling applyMask() with 515 | /// the same mask value a second time will undo the mask. A final well-formed 516 | /// QR Code needs exactly one (not zero, two, etc.) mask applied. 517 | private mutating func applyMask(_ msk: Int) { 518 | precondition((0...7).contains(msk)) 519 | for y in 0 ..< size { 520 | for x in 0 ..< size { 521 | var invert: Bool 522 | switch msk { 523 | case 0: 524 | invert = (x + y) % 2 == 0 525 | case 1: 526 | invert = y % 2 == 0 527 | case 2: 528 | invert = x % 3 == 0 529 | case 3: 530 | invert = (x + y) % 3 == 0 531 | case 4: 532 | invert = (x / 3 + y / 2) % 2 == 0 533 | case 5: 534 | invert = x * y % 2 + x * y % 3 == 0 535 | case 6: 536 | invert = (x * y % 2 + x * y % 3) % 2 == 0 537 | case 7: 538 | invert = ((x + y) % 2 + x * y % 3) % 2 == 0 539 | default: 540 | fatalError() 541 | } 542 | modules[y][x] = modules[y][x] != (invert && !isFunction[y][x]) 543 | } 544 | } 545 | } 546 | 547 | /// Calculates and returns the penalty score based on state of this QR Code's current modules. 548 | /// This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score. 549 | private func getPenaltyScore() -> Int { 550 | var result = 0 551 | 552 | // Adjacent modules in row having same color, and finder-like patterns 553 | for y in 0 ..< size { 554 | var runColor = false 555 | var runX = 0 556 | var runHistory = [Int](repeating: 0, count: 7) 557 | for x in 0 ..< size { 558 | if module(x, y) == runColor { 559 | runX += 1 560 | if runX == 5 { 561 | result += Self.penaltyN1 562 | } else if runX > 5 { 563 | result += 1 564 | } 565 | } else { 566 | finderPenaltyAddHistory(runX, &runHistory) 567 | if !runColor { 568 | result += finderPenaltyCountPatterns(&runHistory) * Self.penaltyN3 569 | } 570 | runColor = module(x, y) 571 | runX = 1 572 | } 573 | } 574 | result += finderPenaltyTerminateAndCount(runColor, runX, &runHistory) * Self.penaltyN3 575 | } 576 | // Adjacent modules in column having same color, and finder-like patterns 577 | for x in 0 ..< size { 578 | var runColor = false 579 | var runY = 0 580 | var runHistory = [Int](repeating: 0, count: 7) 581 | for y in 0 ..< size { 582 | if module(x, y) == runColor { 583 | runY += 1 584 | if runY == 5 { 585 | result += Self.penaltyN1 586 | } else if runY > 5 { 587 | result += 1 588 | } 589 | } else { 590 | finderPenaltyAddHistory(runY, &runHistory) 591 | if !runColor { 592 | result += finderPenaltyCountPatterns(&runHistory) * Self.penaltyN3 593 | } 594 | runColor = module(x, y) 595 | runY = 1 596 | } 597 | } 598 | result += finderPenaltyTerminateAndCount(runColor, runY, &runHistory) * Self.penaltyN3 599 | } 600 | 601 | // 2*2 blocks of modules having same color 602 | for y in 0 ..< size - 1 { 603 | for x in 0 ..< size - 1 { 604 | let color = module(x, y) 605 | if color == module(x + 1, y) && 606 | color == module(x, y + 1) && 607 | color == module(x + 1, y + 1) { 608 | result += Self.penaltyN2 609 | } 610 | } 611 | } 612 | 613 | // Balance of black and white modules 614 | var black = 0 615 | for row in modules { 616 | for color in row { 617 | if color { 618 | black += 1 619 | } 620 | } 621 | } 622 | let total = size * size // Note that size is odd, so black/total != 1/2 623 | // Compute the smallest integer k >= 0 such that (45-5k)% <= black/total <= (55+5k)% 624 | let k = ((abs(black * 20 - total * 10) + total - 1) / total) - 1 625 | result += k * Self.penaltyN4 626 | return result 627 | } 628 | 629 | // MARK: - Private helper functions 630 | 631 | /// Returns an ascending list of positions of alignment patterns for this version number. 632 | /// Each position is in the range [0,177), and are used on both the x and y axes. 633 | /// This could be implemented as lookup table of 40 variable-length lists of unsigned bytes. 634 | private func getAlignmentPatternPositions() -> [Int] { 635 | guard version > 1 else { 636 | return [] 637 | } 638 | 639 | let numAlign = version / 7 + 2 640 | let step = (version == 32) ? 26 : 641 | (version * 4 + numAlign * 2 + 1) / (numAlign * 2 - 2) * 2 642 | var result: [Int] = [] 643 | var pos = size - 7 644 | for _ in 0 ..< (numAlign - 1) { 645 | result.insert(pos, at: 0) 646 | pos -= step 647 | } 648 | result.insert(6, at: 0) 649 | return result 650 | } 651 | 652 | /// Returns the number of data bits that can be stored in a QR Code of the given version number, after 653 | /// all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8. 654 | /// The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table. 655 | private static func getNumRawDataModules(_ ver: Int) -> Int { 656 | precondition((minVersion...maxVersion).contains(ver)) 657 | var result = (16 * ver + 128) * ver + 64 658 | if ver >= 2 { 659 | let numAlign = ver / 7 + 2 660 | result -= (25 * numAlign - 10) * numAlign - 55 661 | if ver >= 7 { 662 | result -= 36 663 | } 664 | } 665 | precondition((208...29648).contains(result)) 666 | return result 667 | } 668 | 669 | /// Returns the number of 8-bit data (i.e. not error correction) codewords contained in any 670 | /// QR Code of the given version number and error correction level, with remainder bits discarded. 671 | /// This stateless pure function could be implemented as a (40*4)-cell lookup table. 672 | static func getNumDataCodewords(_ ver: Int, _ ecl: CorrectionLevel) -> Int { 673 | getNumRawDataModules(ver) / 8 674 | - eccCodewordsPerBlock[ecl.rawValue][ver] 675 | * numErrorCorrectionBlocks[ecl.rawValue][ver] 676 | } 677 | 678 | /// Returns a Reed-Solomon ECC generator polynomial for the given degree. This could be 679 | /// implemented as a lookup table over all possible parameter values, instead of as an algorithm. 680 | private static func reedSolomonComputeDivisor(_ degree: Int) -> [UInt8] { 681 | precondition((1...255).contains(degree)) 682 | // Polynomial coefficients are stored from highest to lowest power, excluding the leading term which is always 1. 683 | // For example the polynomial x^3 + 255x^2 + 8x + 93 is stored as the uint8 array {255, 8, 93}. 684 | var result = [UInt8](repeating: 0, count: degree) 685 | result[result.count - 1] = 1 // Start off with the monomial x^0 686 | 687 | // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}), 688 | // and drop the highest monomial term which is always 1x^degree. 689 | // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). 690 | var root: UInt8 = 1 691 | for _ in 0 ..< degree { 692 | // Multiply the current product by (x - r^i) 693 | for j in 0.. [UInt8] { 706 | var result = [UInt8](repeating: 0, count: divisor.count) 707 | for b in data { // Polynomial division 708 | let factor = b ^ result[0] 709 | result.removeFirst() 710 | result.append(0) 711 | for i in 0.. UInt8 { 721 | // Russian peasant multiplication 722 | var z = 0 723 | for i in (0...7).reversed() { 724 | z = (z << 1) ^ ((z >> 7) * 0x11D) 725 | z ^= ((Int(y) >> i) & 1) * Int(x) 726 | } 727 | precondition(z >> 8 == 0) 728 | return UInt8(z) 729 | } 730 | 731 | /// Can only be called immediately after a white run is added, and 732 | /// returns either 0, 1, or 2. A helper function for getPenaltyScore(). 733 | private func finderPenaltyCountPatterns(_ runHistory: inout [Int]) -> Int { 734 | let n = runHistory[1] 735 | precondition(n <= size * 3) 736 | let core = n > 0 && runHistory[2] == n && runHistory[3] == n * 3 && runHistory[4] == n && runHistory[5] == n 737 | return (core && runHistory[0] >= n * 4 && runHistory[6] >= n ? 1 : 0) 738 | + (core && runHistory[6] >= n * 4 && runHistory[0] >= n ? 1 : 0) 739 | } 740 | 741 | /// Must be called at the end of a line (row or column) of modules. A helper function for getPenaltyScore(). 742 | private func finderPenaltyTerminateAndCount(_ currentRunColor: Bool, _ currentRunLength: Int, _ runHistory: inout [Int]) -> Int { 743 | var currentRunLength = currentRunLength 744 | if currentRunColor { // Terminate black run 745 | finderPenaltyAddHistory(currentRunLength, &runHistory) 746 | currentRunLength = 0 747 | } 748 | currentRunLength += size // Add white border to final run 749 | finderPenaltyAddHistory(currentRunLength, &runHistory) 750 | return finderPenaltyCountPatterns(&runHistory) 751 | } 752 | 753 | /// Pushes the given value to the front and drops the last value. A helper function for getPenaltyScore(). 754 | private func finderPenaltyAddHistory(_ currentRunLength: Int, _ runHistory: inout [Int]) { 755 | var currentRunLength = currentRunLength 756 | if runHistory[0] == 0 { 757 | currentRunLength += size // Add white border to initial run 758 | } 759 | 760 | // runHistory.insert(currentRunLength, at: 0) 761 | // runHistory.removeLast() 762 | 763 | for i in (0.. Bool { 771 | ((x >> i) & 1) != 0 772 | } 773 | 774 | // MARK: - Constants and tables 775 | 776 | // The minimum version number supported in the QR Code Model 2 standard. 777 | public static let minVersion = 1 778 | 779 | // The maximum version number supported in the QR Code Model 2 standard. 780 | public static let maxVersion = 40 781 | 782 | private static let penaltyN1 = 3 783 | private static let penaltyN2 = 3 784 | private static let penaltyN3 = 40 785 | private static let penaltyN4 = 10 786 | 787 | private static let eccCodewordsPerBlock = [ 788 | // Version: (note that index 0 is for padding, and is set to an illegal value) 789 | //0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level 790 | [-1, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30], // Low 791 | [-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28], // Medium 792 | [-1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30], // Quartile 793 | [-1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30], // High 794 | ] 795 | 796 | private static let numErrorCorrectionBlocks = [ 797 | // Version: (note that index 0 is for padding, and is set to an illegal value) 798 | //0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level 799 | [-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25], // Low 800 | [-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49], // Medium 801 | [-1, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68], // Quartile 802 | [-1, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81], // High 803 | ] 804 | } 805 | -------------------------------------------------------------------------------- /Sources/QRCodeGenerator/QRError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QRError.swift 3 | // QRCodeGenerator 4 | // 5 | // Copyright © 2021 by Blockchain Commons, LLC 6 | // Licensed under the "BSD-2-Clause Plus Patent License" 7 | // 8 | // Created by Wolf McNally on 7/20/21. 9 | // 10 | // Based on: QR Code generator library (C++) 11 | // Copyright (c) Project Nayuki. (MIT License) 12 | // https://www.nayuki.io/page/qr-code-generator-library 13 | // 14 | 15 | import Foundation 16 | 17 | public enum QRError: LocalizedError { 18 | case invalidVersion 19 | case invalidMask 20 | case invalidNumericString 21 | case invalidAlphanumericString 22 | case invalidKanjiString 23 | case invalidECIDesignator 24 | case invalidCharacterCount 25 | case dataTooLong(Int, Int) 26 | 27 | public var errorDescription: String? { 28 | switch self { 29 | case .invalidVersion: 30 | return "Invalid version." 31 | case .invalidMask: 32 | return "Invalid mask." 33 | case .invalidNumericString: 34 | return "String contains non-numeric characters." 35 | case .invalidAlphanumericString: 36 | return "String contains unencodable characters in alphanumeric mode." 37 | case .invalidKanjiString: 38 | return "String contains non-kanji-mode characters." 39 | case .invalidECIDesignator: 40 | return "Invalid ECI designator." 41 | case .invalidCharacterCount: 42 | return "Invalid number of characters." 43 | case .dataTooLong(let dataUsedBits, let dataCapacityBits): 44 | var sb = "Data too long." 45 | if dataUsedBits != -1 { 46 | sb += " Data length = \(dataUsedBits) bits, Max capacity = \(dataCapacityBits) bits." 47 | } 48 | return sb 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sources/QRCodeGenerator/Segment.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Segment.swift 3 | // QRCodeGenerator 4 | // 5 | // Copyright © 2021 by Blockchain Commons, LLC 6 | // Licensed under the "BSD-2-Clause Plus Patent License" 7 | // 8 | // Created by Wolf McNally on 7/20/21. 9 | // 10 | // Based on: QR Code generator library (C++) 11 | // Copyright (c) Project Nayuki. (MIT License) 12 | // https://www.nayuki.io/page/qr-code-generator-library 13 | // 14 | 15 | import Foundation 16 | 17 | /** 18 | A segment of character/binary/control data in a QR Code symbol. 19 | Instances of this class are immutable. 20 | The mid-level way to create a segment is to take the payload data 21 | and call a static factory function such as QrSegment::makeNumeric(). 22 | The low-level way to create a segment is to custom-make the bit buffer 23 | and call the QrSegment() constructor with appropriate values. 24 | This segment class imposes no length restrictions, but QR Codes have restrictions. 25 | Even in the most favorable conditions, a QR Code can only hold 7089 characters of data. 26 | Any segment longer than this is meaningless for the purpose of generating QR Codes. 27 | */ 28 | public struct Segment { 29 | // MARK: - Instance fields 30 | 31 | /// The mode indicator of this segment. 32 | public let mode: Mode 33 | 34 | /** The length of this segment's unencoded data. Measured in characters for 35 | * numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode. 36 | * Always zero or positive. Not the same as the data's bit length. 37 | */ 38 | public let characterCount: Int 39 | 40 | /// The data bits of this segment. 41 | public let data: [Bool] 42 | 43 | // MARK: - Public helper enumeration 44 | 45 | /// Describes how a segment's data bits are interpreted. Immutable. 46 | public enum Mode: Int { 47 | case numeric 48 | case alphanumeric 49 | case byte 50 | case kanji 51 | case eci 52 | 53 | /// The mode indicator bits, which is a uint4 value (range 0 to 15). 54 | public var modeBits: Int { 55 | Self._modeBits[rawValue] 56 | } 57 | 58 | /// Returns the bit width of the character count field for a segment in this mode in a QR Code at the given version number. The result is in the range [0, 16]. 59 | public func numCharCountBits(_ ver: Int) -> Int { 60 | Self._numCharCountBits[rawValue][(ver + 7) / 17] 61 | } 62 | 63 | private static let _modeBits = [ 64 | 1, // numeric 65 | 2, // alphanumeric 66 | 4, // byte 67 | 8, // kanji 68 | 7 // eci 69 | ] 70 | 71 | private static let _numCharCountBits = [ 72 | [10, 12, 14], // numeric 73 | [ 9, 11, 13], // alphanumeric 74 | [ 8, 16, 16], // byte 75 | [ 8, 10, 12], // kanji 76 | [ 0, 0, 0] // eci 77 | ] 78 | } 79 | 80 | // MARK: - Static factory functions (mid level) 81 | 82 | /** 83 | * Returns a segment representing the given binary data encoded in 84 | * byte mode. All input byte vectors are acceptable. Any text string 85 | * can be converted to UTF-8 bytes and encoded as a byte mode segment. 86 | */ 87 | public static func makeBytes(data: [UInt8]) throws -> Segment { 88 | var bb: [Bool] = [] 89 | for b in data { 90 | bb.appendBits(b, 8) 91 | } 92 | return try Segment(mode: .byte, characterCount: data.count, data: bb) 93 | } 94 | 95 | public static func makeBytes(data: Data) throws -> Segment { 96 | return try makeBytes(data: Array(data)) 97 | } 98 | 99 | public static func makeBytes(data: String) throws -> Segment { 100 | return try makeBytes(data: data.data(using: .utf8)!) 101 | } 102 | 103 | public static func makeText(text: String, correctionLevel: CorrectionLevel = .medium, minVersion: Int = 1, maxVersion: Int = 40, optimize: Bool = false) throws -> [Segment] { 104 | let segments: [Segment] 105 | if optimize { 106 | segments = try Segment.makeSegmentsOptimally(text: text, correctionLevel: correctionLevel, minVersion: minVersion, maxVersion: maxVersion) 107 | } else { 108 | segments = try Segment.makeSegments(text: text) 109 | } 110 | return segments 111 | } 112 | 113 | /** 114 | * Returns a segment representing the given string of decimal digits encoded in numeric mode. 115 | */ 116 | public static func makeNumeric(digits: String) throws -> Segment { 117 | var bb: [Bool] = [] 118 | var accumData: UInt = 0 119 | var accumCount = 0 120 | for c in digits.utf8 { 121 | guard isNumeric(c) else { 122 | throw QRError.invalidNumericString 123 | } 124 | accumData = accumData * 10 + UInt(c - ascii0) 125 | accumCount += 1 126 | if accumCount == 3 { 127 | bb.appendBits(accumData, 10) 128 | accumData = 0 129 | accumCount = 0 130 | } 131 | } 132 | if accumCount > 0 { // 1 or 2 digits remaining 133 | bb.appendBits(accumData, accumCount * 3 + 1) 134 | } 135 | return try Segment(mode: .numeric, characterCount: digits.count, data: bb) 136 | } 137 | 138 | private static let ascii0 = Character("0").asciiValue! 139 | private static let ascii9 = Character("9").asciiValue! 140 | 141 | static func isNumeric(_ c: I) -> Bool where I: BinaryInteger { 142 | guard c <= UInt8.max else { 143 | return false 144 | } 145 | return (ascii0...ascii9).contains(UInt8(c)) 146 | } 147 | 148 | static let alphanumericCharset: [UInt8] = Array("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:".utf8) 149 | 150 | static func alphanumericIndex(of c: I) -> Int? where I: BinaryInteger { 151 | guard c <= UInt8.max else { 152 | return nil 153 | } 154 | return Self.alphanumericCharset.firstIndex(of: UInt8(c)) 155 | } 156 | 157 | static func isAlphanumeric(_ c: I) -> Bool where I: BinaryInteger { 158 | alphanumericIndex(of: c) != nil 159 | } 160 | 161 | static func isAlphanumeric(_ c: UnicodeScalar) -> Bool { 162 | isAlphanumeric(c.value) 163 | } 164 | 165 | /** 166 | * Returns a segment representing the given text string encoded in alphanumeric mode. 167 | * The characters allowed are: 0 to 9, A to Z (uppercase only), space, 168 | * dollar, percent, asterisk, plus, hyphen, period, slash, colon. 169 | */ 170 | public static func makeAlphanumeric(text: String) throws -> Segment { 171 | var bb: [Bool] = [] 172 | var accumData: UInt = 0 173 | var accumCount = 0 174 | for c in text.utf8 { 175 | guard let index = alphanumericIndex(of: c) else { 176 | throw QRError.invalidAlphanumericString 177 | } 178 | accumData = accumData * 45 + UInt(index) 179 | accumCount += 1 180 | if accumCount == 2 { 181 | bb.appendBits(accumData, 11) 182 | accumData = 0 183 | accumCount = 0 184 | } 185 | } 186 | if accumCount > 0 { // 1 character remaining 187 | bb.appendBits(accumData, 6) 188 | } 189 | return try Segment(mode: .alphanumeric, characterCount: text.count, data: bb) 190 | } 191 | 192 | /** 193 | * Returns a segment representing the specified text string encoded in kanji mode. Throws if the string contains any non-kanji-mode characters. 194 | * 195 | * Broadly speaking, the set of encodable characters are {kanji used in Japan, 196 | * hiragana, katakana, East Asian punctuation, full-width ASCII, Greek, Cyrillic}. 197 | * Examples of non-encodable characters include {ordinary ASCII, half-width katakana, 198 | * more extensive Chinese hanzi}. 199 | */ 200 | public static func makeKanji(text: String) throws -> Segment { 201 | var bb: [Bool] = [] 202 | try text.forEach { 203 | guard let val = toKanji($0) else { 204 | throw QRError.invalidKanjiString 205 | } 206 | bb.appendBits(val, 13) 207 | } 208 | return try Segment(mode: .kanji, characterCount: text.count, data: bb) 209 | } 210 | 211 | public static func isEncodableAsKanji(_ s: String) -> Bool { 212 | s.allSatisfy(isKanji) 213 | } 214 | 215 | public static func toKanji(_ c: Character) -> Int? { 216 | let scalars = c.unicodeScalars 217 | guard scalars.count == 1 else { 218 | return nil 219 | } 220 | let value = Int(scalars.first!.value) 221 | return toKanji(value) 222 | } 223 | 224 | static func toKanji(_ value: Int) -> Int? { 225 | guard value < (1 << 16) else { 226 | return nil 227 | } 228 | guard value < unicodeToQRKanji.count && unicodeToQRKanji[value] != -1 else { 229 | return nil 230 | } 231 | let result = Int(unicodeToQRKanji[value]) 232 | guard result != -1 else { 233 | return nil 234 | } 235 | return result 236 | } 237 | 238 | public static func isKanji(_ c: Character) -> Bool { 239 | toKanji(c) != nil 240 | } 241 | 242 | public static func isKanji(_ c: I) -> Bool where I: BinaryInteger { 243 | toKanji(Int(c)) != nil 244 | } 245 | 246 | public static func isKanji(_ c: UnicodeScalar) -> Bool { 247 | isKanji(c.value) 248 | } 249 | 250 | static let unicodeToQRKanji: [Int16] = { 251 | var result = [Int16](repeating: -1, count: 1 << 16) 252 | let bytes = Data(base64Encoded: packedQRKanjiToUnicode)! 253 | for i in stride(from: 0, to: bytes.count, by: 2) { 254 | let c = ((UInt16(bytes[i]) & 0xff) << 8) | (UInt16(bytes[i + 1]) & 0xff) 255 | guard c != 0xffff else { 256 | continue 257 | } 258 | precondition(result[Int(c)] == -1) 259 | result[Int(c)] = Int16(i / 2) 260 | } 261 | return result 262 | }() 263 | 264 | /** 265 | * Returns a list of zero or more segments to represent the given text string. The result 266 | * may use various segment modes and switch modes to optimize the length of the bit stream. 267 | */ 268 | public static func makeSegments(text: String) throws -> [Segment] { 269 | var result: [Segment] = [] 270 | if text.isEmpty { 271 | // Leave result empty 272 | } else if isNumeric(text: text) { 273 | try result.append(makeNumeric(digits: text)) 274 | } else if isAlphanumeric(text: text) { 275 | try result.append(makeAlphanumeric(text: text)) 276 | } else { 277 | try result.append(makeBytes(data: Array(text.utf8))) 278 | } 279 | return result 280 | } 281 | 282 | /** 283 | * Returns a segment representing an Extended Channel Interpretation 284 | * (ECI) designator with the given assignment value. 285 | */ 286 | public static func makeECI(designator: Int) throws -> Segment { 287 | var bb: [Bool] = [] 288 | if designator < 0 { 289 | throw QRError.invalidECIDesignator 290 | } else if designator < (1 << 7) { 291 | bb.appendBits(UInt32(designator), 8) 292 | } else if designator < (1 << 14) { 293 | bb.appendBits(UInt32(designator), 14) 294 | } else if designator < 1_000_000 { 295 | bb.appendBits(UInt32(6), 3) 296 | bb.appendBits(UInt32(designator), 21) 297 | } else { 298 | throw QRError.invalidECIDesignator 299 | } 300 | return try Segment(mode: .eci, characterCount: 0, data: bb) 301 | } 302 | 303 | // MARK: - Public static helper functions 304 | 305 | /** 306 | * Tests whether the given string can be encoded as a segment in alphanumeric mode. 307 | * A string is encodable iff each character is in the following set: 0 to 9, A to Z 308 | * (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon. 309 | */ 310 | public static func isAlphanumeric(text: String) -> Bool { 311 | text.utf8.allSatisfy { Self.alphanumericCharset.contains($0) } 312 | } 313 | 314 | /** 315 | * Tests whether the given string can be encoded as a segment in numeric mode. 316 | * A string is encodable iff each character is in the range 0 to 9. 317 | */ 318 | public static func isNumeric(text: String) -> Bool { 319 | text.utf8.allSatisfy { isNumeric($0) } 320 | } 321 | 322 | public static func isNumeric(_ c: UnicodeScalar) -> Bool { 323 | isNumeric(c.value) 324 | } 325 | 326 | // MARK: - Constructors (low level) 327 | 328 | /** 329 | * Creates a new QR Code segment with the given attributes and data. 330 | * The character count (numCh) must agree with the mode and the bit buffer length, 331 | * but the constraint isn't checked. 332 | */ 333 | public init(mode: Segment.Mode, characterCount: Int, data: [Bool]) throws { 334 | guard characterCount >= 0 else { 335 | throw QRError.invalidCharacterCount 336 | } 337 | self.mode = mode 338 | self.characterCount = characterCount 339 | self.data = data 340 | } 341 | 342 | // Calculates the number of bits needed to encode the given segments at 343 | // the given version. Returns a non-negative number if successful. Otherwise returns -1 if a 344 | // segment has too many characters to fit its length field, or the total bits exceeds INT_MAX. 345 | static func getTotalBits(_ segs: [Segment], _ version: Int) -> Int { 346 | var result = 0 347 | for seg in segs { 348 | let ccbits = seg.mode.numCharCountBits(version) 349 | if seg.characterCount >= (1 << ccbits) { 350 | return -1 // The segment's length doesn't fit the field's bit width 351 | } else if 4 + ccbits > Int.max - result { 352 | return -1 // The sum will overflow an int type 353 | } 354 | result += 4 + ccbits 355 | if seg.data.count > Int.max - result { 356 | return -1 // The sum will overflow an int type 357 | } 358 | result += seg.data.count 359 | } 360 | return result 361 | } 362 | 363 | // MARK: - Optimal Segmentation 364 | 365 | /** 366 | * Returns a list of zero or more segments to represent the specified Unicode text string. 367 | * The resulting list optimally minimizes the total encoded bit length, subjected to the constraints 368 | * in the specified {error correction level, minimum version number, maximum version number}. 369 | * 370 | * This function can utilize all four text encoding modes: numeric, alphanumeric, byte (UTF-8), 371 | * and kanji. This can be considered as a sophisticated but slower replacement for 372 | * `makeSegments()`. This requires more input parameters because it searches a 373 | * range of versions, like `QRCode.encode(segments:correctionLevel:minVersion:maxVersion:mask:booscEcl:)`. 374 | */ 375 | public static func makeSegmentsOptimally(text: String, correctionLevel: CorrectionLevel = .medium, minVersion: Int = 1, maxVersion: Int = 40) throws -> [Segment] { 376 | try QRCode.checkVersion(minVersion: minVersion, maxVersion: maxVersion) 377 | 378 | // Iterate through version numbers, and make tentative segments 379 | var segs: [Segment]! 380 | let codePoints = Array(text.unicodeScalars) 381 | var version = minVersion 382 | repeat { 383 | defer { 384 | version += 1 385 | } 386 | if version == minVersion || version == 10 || version == 27 { 387 | segs = makeSegmentsOptimally(codePoints: codePoints, version: version) 388 | } 389 | precondition(segs != nil) 390 | 391 | // Check if the segments fit 392 | let dataCapacityBits = QRCode.dataCapacityBits(version, correctionLevel) 393 | let dataUsedBits = Segment.getTotalBits(segs, version) 394 | if dataUsedBits != -1 && dataUsedBits <= dataCapacityBits { 395 | return segs // This version number is found to be suitable 396 | } 397 | if version >= maxVersion { // All versions in the range could not fit the given text 398 | throw QRError.dataTooLong(dataUsedBits, dataCapacityBits) 399 | } 400 | } while true 401 | } 402 | 403 | /// Returns a new list of segments that is optimal for the given text at the given version number. 404 | static func makeSegmentsOptimally(codePoints: [UnicodeScalar], version: Int) -> [Segment] { 405 | guard !codePoints.isEmpty else { 406 | return [] 407 | } 408 | let charModes = computeCharacterModes(codePoints: codePoints, version: version) 409 | return splitIntoSegments(codePoints: codePoints, charModes: charModes) 410 | } 411 | 412 | /// Returns a new array representing the optimal mode per code point based on the given text and version. 413 | static func computeCharacterModes(codePoints: [UnicodeScalar], version: Int) -> [Mode] { 414 | precondition(!codePoints.isEmpty) 415 | let modeTypes: [Segment.Mode] = [.byte, .alphanumeric, .numeric, .kanji] // Do not change 416 | let modesCount = modeTypes.count 417 | 418 | // Segment header sizes, measured in 1/6 bits 419 | let headCosts = modeTypes.map { 420 | (4 + $0.numCharCountBits(version)) * 6 421 | } 422 | 423 | // charModes[i][j] represents the mode to encode the code point at 424 | // index i such that the final segment ends in modeTypes[j] and the 425 | // total number of bits is minimized over all possible choices 426 | var charModes: [[Segment.Mode?]] = 427 | Array(repeating: 428 | Array(repeating: nil, count: modesCount), 429 | count: codePoints.count) 430 | 431 | // At the beginning of each iteration of the loop below, 432 | // prevCosts[j] is the exact minimum number of 1/6 bits needed to 433 | // encode the entire string prefix of length i, and end in modeTypes[j] 434 | var prevCosts = headCosts 435 | 436 | // Calculate costs using dynamic programming 437 | for (i, c) in codePoints.enumerated() { 438 | var curCosts = [Int](repeating: 0, count: modesCount) 439 | do { // Always extend a byte mode segment 440 | curCosts[0] = prevCosts[0] + countUTF8Bytes(c) * 8 * 6 441 | charModes[i][0] = modeTypes[0] 442 | } 443 | // Extend a segment if possible 444 | if isAlphanumeric(c) { // Is alphanumeric 445 | curCosts[1] = prevCosts[1] + 33 // 5.5 bits per alphanumeric char 446 | charModes[i][1] = modeTypes[1] 447 | } 448 | if isNumeric(c) { // Is numeric 449 | curCosts[2] = prevCosts[2] + 20 // 3.33 bits per digit 450 | charModes[i][2] = modeTypes[2] 451 | } 452 | if isKanji(c) { 453 | curCosts[3] = prevCosts[3] + 78 // 13 bits per Shift JIS char 454 | charModes[i][3] = modeTypes[3] 455 | } 456 | 457 | // Start new segment at the end to switch modes 458 | for j in 0 ..< modesCount { // To mode 459 | for k in 0 ..< modesCount { // From mode 460 | let newCost = (curCosts[k] + 5) / 6 * 6 + headCosts[j] 461 | if charModes[i][k] != nil && (charModes[i][j] == nil || newCost < curCosts[j]) { 462 | curCosts[j] = newCost 463 | charModes[i][j] = modeTypes[k] 464 | } 465 | } 466 | } 467 | 468 | prevCosts = curCosts 469 | } 470 | 471 | // Find optimal ending mode 472 | var curMode: Mode? 473 | var minCost = 0 474 | for i in 0 ..< modesCount { 475 | if curMode == nil || prevCosts[i] < minCost { 476 | minCost = prevCosts[i] 477 | curMode = modeTypes[i] 478 | } 479 | } 480 | 481 | // Get optimal mode for each code point by tracing backwards 482 | var r = [Mode?](repeating: nil, count: charModes.count) 483 | for i in (0 ..< charModes.count).reversed() { 484 | for j in 0 ..< modesCount { 485 | if modeTypes[j] == curMode { 486 | curMode = charModes[i][j] 487 | r[i] = curMode 488 | break 489 | } 490 | } 491 | } 492 | 493 | let result = r.compactMap { $0 } 494 | precondition(result.count == r.count) 495 | 496 | return result 497 | } 498 | 499 | // Returns a new list of segments based on the given text and modes, such that 500 | // consecutive code points in the same mode are put into the same segment. 501 | static func splitIntoSegments(codePoints: [UnicodeScalar], charModes: [Mode]) -> [Segment] { 502 | precondition(!codePoints.isEmpty) 503 | var result: [Segment] = [] 504 | 505 | // Accumulate run of modes 506 | var curMode = charModes.first! 507 | var start = 0 508 | var i = 1 509 | repeat { 510 | defer { 511 | i += 1 512 | } 513 | if i < codePoints.count && charModes[i] == curMode { 514 | continue 515 | } 516 | let s = String(String.UnicodeScalarView(codePoints[start ..< i])) 517 | switch curMode { 518 | case .byte: 519 | try! result.append(makeBytes(data: Array(s.utf8))) 520 | case .numeric: 521 | try! result.append(makeNumeric(digits: s)) 522 | case .alphanumeric: 523 | try! result.append(makeAlphanumeric(text: s)) 524 | case .kanji: 525 | try! result.append(makeKanji(text: s)) 526 | default: 527 | preconditionFailure() 528 | } 529 | 530 | if i >= codePoints.count { 531 | return result 532 | } 533 | curMode = charModes[i] 534 | start = i 535 | } while true 536 | } 537 | 538 | /// Returns the number of UTF-8 bytes needed to encode the given Unicode code point. 539 | static func countUTF8Bytes(_ cp: UnicodeScalar) -> Int { 540 | let v = cp.value 541 | if v < 0x80 { return 1 } 542 | else if v < 0x800 { return 2 } 543 | else if v < 0x10000 { return 3 } 544 | else if v < 0x110000 { return 4 } 545 | preconditionFailure() 546 | } 547 | } 548 | 549 | extension Segment: CustomStringConvertible { 550 | public var description: String { 551 | "Segment(mode: \(mode), characterCount: \(characterCount), data: <\(data.count) bits>)" 552 | } 553 | } 554 | -------------------------------------------------------------------------------- /Sources/QRCodeGenerator/ShiftJISData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Wolf McNally on 8/28/21. 6 | // 7 | 8 | import Foundation 9 | 10 | let packedQRKanjiToUnicode = "MAAwATAC/wz/DjD7/xr/G/8f/wEwmzCcALT/QACo/z7/4/8/MP0w/jCdMJ4wA07dMAUwBjAHMPwgFSAQ/w8AXDAcIBb/XCAmICUgGCAZIBwgHf8I/wkwFDAV/zv/Pf9b/10wCDAJMAowCzAMMA0wDjAPMBAwEf8LIhIAsQDX//8A9/8dImD/HP8eImYiZyIeIjQmQiZAALAgMiAzIQP/5f8EAKIAo/8F/wP/Bv8K/yAApyYGJgUlyyXPJc4lxyXGJaEloCWzJbIlvSW8IDswEiGSIZAhkSGTMBP/////////////////////////////IggiCyKGIocigiKDIioiKf////////////////////8iJyIoAKwh0iHUIgAiA/////////////////////////////8iICKlIxIiAiIHImEiUiJqImsiGiI9Ih0iNSIrIiz//////////////////yErIDAmbyZtJmogICAhALb//////////yXv/////////////////////////////////////////////////xD/Ef8S/xP/FP8V/xb/F/8Y/xn///////////////////8h/yL/I/8k/yX/Jv8n/yj/Kf8q/yv/LP8t/y7/L/8w/zH/Mv8z/zT/Nf82/zf/OP85/zr///////////////////9B/0L/Q/9E/0X/Rv9H/0j/Sf9K/0v/TP9N/07/T/9Q/1H/Uv9T/1T/Vf9W/1f/WP9Z/1r//////////zBBMEIwQzBEMEUwRjBHMEgwSTBKMEswTDBNME4wTzBQMFEwUjBTMFQwVTBWMFcwWDBZMFowWzBcMF0wXjBfMGAwYTBiMGMwZDBlMGYwZzBoMGkwajBrMGwwbTBuMG8wcDBxMHIwczB0MHUwdjB3MHgweTB6MHswfDB9MH4wfzCAMIEwgjCDMIQwhTCGMIcwiDCJMIowizCMMI0wjjCPMJAwkTCSMJP/////////////////////////////////////MKEwojCjMKQwpTCmMKcwqDCpMKowqzCsMK0wrjCvMLAwsTCyMLMwtDC1MLYwtzC4MLkwujC7MLwwvTC+ML8wwDDBMMIwwzDEMMUwxjDHMMgwyTDKMMswzDDNMM4wzzDQMNEw0jDTMNQw1TDWMNcw2DDZMNow2zDcMN0w3jDf//8w4DDhMOIw4zDkMOUw5jDnMOgw6TDqMOsw7DDtMO4w7zDwMPEw8jDzMPQw9TD2/////////////////////wORA5IDkwOUA5UDlgOXA5gDmQOaA5sDnAOdA54DnwOgA6EDowOkA6UDpgOnA6gDqf////////////////////8DsQOyA7MDtAO1A7YDtwO4A7kDugO7A7wDvQO+A78DwAPBA8MDxAPFA8YDxwPIA8n/////////////////////////////////////////////////////////////////////////////////////////////////////////////BBAEEQQSBBMEFAQVBAEEFgQXBBgEGQQaBBsEHAQdBB4EHwQgBCEEIgQjBCQEJQQmBCcEKAQpBCoEKwQsBC0ELgQv////////////////////////////////////////BDAEMQQyBDMENAQ1BFEENgQ3BDgEOQQ6BDsEPAQ9//8EPgQ/BEAEQQRCBEMERARFBEYERwRIBEkESgRLBEwETQROBE///////////////////////////////////yUAJQIlDCUQJRglFCUcJSwlJCU0JTwlASUDJQ8lEyUbJRclIyUzJSslOyVLJSAlLyUoJTclPyUdJTAlJSU4JUL//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////06cVRZaA5Y/VMBhG2MoWfaQIoR1gxx6UGCqY+FuJWXthGaCppv1aJNXJ2WhYnFbm1nQhnuY9H1ifb6bjmIWfJ+It1uJXrVjCWaXaEiVx5eNZ09O5U8KT01PnVBJVvJZN1nUWgFcCWDfYQ9hcGYTaQVwunVPdXB5+32tfe+Aw4QOiGOLApBVkHpTO06VTqVX34CykMF4704AWPFuopA4ejKDKIKLnC9RQVNwVL1U4VbgWftfFZjybeuA5IUt////////lmKWcJagl/tUC1PzW4dwz3+9j8KW6FNvnVx6uk4ReJOB/G4mVhhVBGsdhRqcO1nlU6ltZnTclY9WQk6RkEuW8oNPmQxT4VW2WzBfcWYgZvNoBGw4bPNtKXRbdsh6Tpg0gvGIW4pgku1tsnWrdsqZxWCmiwGNipWyaY5TrVGG//9XElgwWURbtF72YChjqWP0bL9vFHCOcRRxWXHVcz9+AYJ2gtGFl5BgkludG1hpZbxsWnUlUflZLlllX4Bf3GK8ZfpqKmsna7Rzi3/BiVadLJ0OnsRcoWyWg3tRBFxLYbaBxmh2cmFOWU/6U3hgaW4pek+X804LUxZO7k9VTz1PoU9zUqBT71YJWQ9awVu2W+F50WaHZ5xntmtMbLNwa3PCeY15vno8e4eCsYLbgwSDd4Pvg9OHZoqyVimMqI/mkE6XHoaKT8Rc6GIRcll1O4Hlgr2G/ozAlsWZE5nVTstPGonjVt5YSljKXvtf62AqYJRgYmHQYhJi0GU5////////m0FmZmiwbXdwcHVMdoZ9dYKlh/mVi5aOjJ1R8VK+WRZUs1uzXRZhaGmCba94jYTLiFeKcpOnmrhtbJmohtlXo2f/hs6SDlKDVodUBF7TYuFkuWg8aDhru3NyeLp6a4maidKNa48DkO2Vo5aUl2lbZlyzaX2YTZhOY5t7IGor//9qf2i2nA1vX1JyVZ1gcGLsbTtuB27RhFuJEI9EThScOVP2aRtqOpeEaCpRXHrDhLKR3JOMVludKGgigwWEMXylUgiCxXTmTn5Pg1GgW9JSClLYUudd+1WaWCpZ5luMW5hb215yXnlgo2EfYWNhvmPbZWJn0WhTaPprPmtTbFdvIm+Xb0V0sHUYduN3C3r/e6F8IX3pfzZ/8ICdgmaDnomzisyMq5CElFGVk5WRlaKWZZfTmSiCGE44VCtcuF3Mc6l2THc8XKl/640LlsGYEZhUmFhPAU8OU3FVnFZoV/pZR1sJW8RckF4MXn5fzGPuZzpl12XiZx9oy2jE////////al9eMGvFbBdsfXV/eUhbY3oAfQBfvYmPihiMtI13jsyPHZjimg6bPE6AUH1RAFmTW5xiL2KAZOxrOnKgdZF5R3+ph/uKvItwY6yDypegVAlUA1WraFRqWIpweCdndZ7NU3RbooEahlCQBk4YTkVOx08RU8pUOFuuXxNgJWVR//9nPWxCbHJs43B4dAN6dnquewh9Gnz+fWZl53JbU7tcRV3oYtJi4GMZbiCGWooxjd2S+G8BeaabWk6oTqtOrE+bT6BQ0VFHevZRcVH2U1RTIVN/U+tVrFiDXOFfN19KYC9gUGBtYx9lWWpLbMFywnLtd++A+IEFggiFTpD3k+GX/5lXmlpO8FHdXC1mgWltXEBm8ml1c4loUHyBUMVS5FdHXf6TJmWkayNrPXQ0eYF5vXtLfcqCuYPMiH+JX4s5j9GR0VQfkoBOXVA2U+VTOnLXc5Z36YLmjq+ZxpnImdJRd2Eahl5VsHp6UHZb05BHloVOMmrbkedcUVxI////////Y5h6n2yTl3SPYXqqcYqWiHyCaBd+cGhRk2xS8lQbhauKE3+kjs2Q4VNmiIh5QU/CUL5SEVFEVVNXLXPqV4tZUV9iX4RgdWF2YWdhqWOyZDplbGZvaEJuE3Vmej18+31MfZl+S39rgw6DSobNigiKY4tmjv2YGp2PgriPzpvo//9Sh2IfZINvwJaZaEFQkWsgbHpvVHp0fVCIQIojZwhO9lA5UCZQZVF8UjhSY1WnVw9YBVrMXvphsmH4YvNjcmkcailyfXKscy54FHhvfXl3DICpiYuLGYzijtKQY5N1lnqYVZoTnnhRQ1OfU7Nee18mbhtukHOEc/59Q4I3igCK+pZQTk5QC1PkVHxW+lnRW2Rd8V6rXydiOGVFZ69uVnLQfMqItIChgOGD8IZOioeN6JI3lseYZ58TTpROkk8NU0hUSVQ+Wi9fjF+hYJ9op2qOdFp4gYqeiqSLd5GQTl6byU6kT3xPr1AZUBZRSVFsUp9SuVL+U5pT41QR////////VA5ViVdRV6JZfVtUW11bj13lXedd9154XoNeml63XxhgUmFMYpdi2GOnZTtmAmZDZvRnbWghaJdpy2xfbSptaW4vbp11MnaHeGx6P3zgfQV9GH1efbGAFYADgK+AsYFUgY+CKoNSiEyIYYsbjKKM/JDKkXWScXg/kvyVpJZN//+YBZmZmtidO1JbUqtT91QIWNVi92/gjGqPX565UUtSO1RKVv16QJF3nWCe0nNEbwmBcHURX/1g2pqoctuPvGtkmANOylbwV2RYvlpaYGhhx2YPZgZoOWixbfd11X06gm6bQk6bT1BTyVUGXW9d5l3uZ/tsmXRzeAKKUJOWiN9XUF6nYytQtVCsUY1nAFTJWF5Zu1uwX2liTWOhaD1rc24IcH2Rx3KAeBV4JnltZY59MIPciMGPCZabUmRXKGdQf2qMoVG0V0KWKlg6aYqAtFSyXQ5X/HiVnfpPXFJKVItkPmYoZxRn9XqEe1Z9IpMvaFybrXs5UxlRilI3////////W99i9mSuZOZnLWu6hamW0XaQm9ZjTJMGm6t2v2ZSTglQmFPCXHFg6GSSZWNoX3Hmc8p1I3uXfoKGlYuDjNuReJkQZaxmq2uLTtVO1E86T39SOlP4U/JV41bbWOtZy1nJWf9bUFxNXgJeK1/XYB1jB2UvW1xlr2W9ZehnnWti//9re2wPc0V5SXnBfPh9GX0rgKKBAoHziZaKXoppimaKjIrujMeM3JbMmPxrb06LTzxPjVFQW1db+mFIYwFmQmshbstsu3I+dL111HjBeTqADIAzgeqElI+ebFCef18Pi1idK3r6jvhbjZbrTgNT8Vf3WTFayVukYIluf28Gdb6M6lufhQB74FByZ/SCnVxhhUp+HoIOUZlcBGNojWZlnHFueT59F4AFix2OypBuhseQqlAfUvpcOmdTcHxyNZFMkciTK4LlW8JfMWD5TjtT1luIYktnMWuKculz4HougWuNo5FSmZZRElPXVGpb/2OIajl9rJcAVtpTzlRo////////W5dcMV3eT+5hAWL+bTJ5wHnLfUJ+TX/Sge2CH4SQiEaJcouQjnSPL5AxkUuRbJbGkZxOwE9PUUVTQV+TYg5n1GxBbgtzY34mkc2Sg1PUWRlbv23ReV1+LnybWH5xn1H6iFOP8E/KXPtmJXeseuOCHJn/UcZfqmXsaW9riW3z//9ulm9kdv59FF3hkHWRh5gGUeZSHWJAZpFm2W4aXrZ90n9yZviFr4X3ivhSqVPZWXNej1+QYFWS5JZkULdRH1LdUyBTR1PsVOhVRlUxVhdZaFm+WjxbtVwGXA9cEVwaXoReil7gX3Bif2KEYttjjGN3ZgdmDGYtZnZnfmiiah9qNWy8bYhuCW5YcTxxJnFndcd3AXhdeQF5ZXnweuB7EXynfTmAloPWhIuFSYhdiPOKH4o8ilSKc4xhjN6RpJJmk36UGJacl5hOCk4ITh5OV1GXUnBXzlg0WMxbIl44YMVk/mdhZ1ZtRHK2dXN6Y4S4i3KRuJMgVjFX9Jj+////////Yu1pDWuWce1+VIB3gnKJ5pjfh1WPsVw7TzhP4U+1VQdaIFvdW+lfw2FOYy9lsGZLaO5pm214bfF1M3W5dx95XnnmfTOB44KvhaqJqoo6jquPm5Aykd2XB066TsFSA1h1WOxcC3UaXD2BTooKj8WWY5dteyWKz5gIkWJW81Oo//+QF1Q5V4JeJWOobDRwindhfIt/4IhwkEKRVJMQkxiWj3RemsRdB11pZXBnoo2olttjbmdJaRmDxZgXlsCI/m+EZHpb+E4WcCx1XWYvUcRSNlLiWdNfgWAnYhBlP2V0Zh9mdGjyaBZrY24FcnJ1H3bbfL6AVljwiP2Jf4qgipOKy5AdkZKXUpdZZYl6DoEGlrteLWDcYhplpWYUZ5B383pNfE1+PoEKjKyNZI3hjl94qVIHYtljpWRCYpiKLXqDe8CKrJbqfXaCDIdJTtlRSFNDU2Bbo1wCXBZd3WImYkdksGgTaDRsyW1FbRdn029ccU5xfWXLen97rX3a////////fkp/qIF6ghuCOYWmim6Mzo31kHiQd5KtkpGVg5uuUk1VhG84cTZRaHmFflWBs3zOVkxYUVyoY6pm/mb9aVpy2XWPdY55DnlWed98l30gfUSGB4o0ljuQYZ8gUOdSdVPMU+JQCVWqWO5ZT3I9W4tcZFMdYONg82NcY4NjP2O7//9kzWXpZvld42nNaf1vFXHlTol16Xb4epN8333PfZyAYYNJg1iEbIS8hfuIxY1wkAGQbZOXlxyaElDPWJdhjoHThTWNCJAgT8NQdFJHU3Ngb2NJZ19uLI2zkB9P11xejMplz32aU1KIllF2Y8NbWFtrXApkDWdRkFxO1lkaWSpscIpRVT5YFVmlYPBiU2fBgjVpVZZAmcSaKE9TWAZb/oAQXLFeL1+FYCBhS2I0Zv9s8G7egM6Bf4LUiIuMuJAAkC6Wip7bm9tO41PwWSd7LJGNmEyd+W7dcCdTU1VEW4ViWGKeYtNsom/vdCKKF5Q4b8GK/oM4UeeG+FPq////////U+lPRpBUj7BZaoExXf166o+/aNqMN3L4nEhqPYqwTjlTWFYGV2ZixWOiZeZrTm3hbltwrXfteu97qn27gD2AxobLipWTW1bjWMdfPmWtZpZqgGu1dTeKx1Akd+VXMF8bYGVmemxgdfR6Gn9ugfSHGJBFmbN7yXVcevl7UYTE//+QEHnpepKDNlrhd0BOLU7yW5lf4GK9Zjxn8WzohmuId4o7kU6S85nQahdwJnMqgueEV4yvTgFRRlHLVYtb9V4WXjNegV8UXzVfa1+0YfJjEWaiZx1vbnJSdTp3OoB0gTmBeId2ir+K3I2FjfOSmpV3mAKc5VLFY1d29GcVbIhzzYzDk66Wc20lWJxpDmnMj/2TmnXbkBpYWmgCY7Rp+09Dbyxn2I+7hSZ9tJNUaT9vcFdqWPdbLH0scipUCpHjnbROrU9OUFxQdVJDjJ5USFgkW5peHV6VXq1e918fYIxitWM6Y9Bor2xAeId5jnoLfeCCR4oCiuaORJAT////////kLiRLZHYnw5s5WRYZOJldW70doR7G5Bpk9FuulTyX7lkpI9Nj+2SRFF4WGtZKVxVXpdt+36PdRyMvI7imFtwuU8da79vsXUwlvtRTlQQWDVYV1msXGBfkmWXZ1xuIXZ7g9+M7ZAUkP2TTXgleDpSql6mVx9ZdGASUBJRWlGs//9RzVIAVRBYVFhYWVdblVz2XYtgvGKVZC1ncWhDaLxo33bXbdhub22bcG9xyF9Tddh5d3tJe1R7UnzWfXFSMIRjhWmF5IoOiwSMRo4PkAOQD5QZlnaYLZowldhQzVLVVAxYAlwOYadknm0ed7N65YD0hASQU5KFXOCdB1M/X5dfs22ccnl3Y3m/e+Rr0nLsiq1oA2phUfh6gWk0XEqc9oLrW8WRSXAeVnhcb2DHZWZsjIxakEGYE1RRZseSDVlIkKNRhU5NUeqFmYsOcFhjepNLaWKZtH4EdXdTV2lgjt+W42xdToxcPF8Qj+lTAozRgImGeV7/ZeVOc1Fl////////WYJcP5fuTvtZil/Nio1v4XmweWJb54RxcytxsV50X/Vje2SaccN8mE5DXvxOS1fcVqJgqW/DfQ2A/YEzgb+PsomXhqRd9GKKZK2Jh2d3bOJtPnQ2eDRaRn91gq2ZrE/zXsNi3WOSZVdnb3bDckyAzIC6jymRTVANV/lakmiF//9pc3Fkcv2Mt1jyjOCWapAZh3955HfnhClPL1JlU1pizWfPbMp2fXuUfJWCNoWEj+tm3W8gcgZ+G4OrmcGeplH9e7F4cnu4gId7SGroXmGAjHVRdWBRa5Jibox2epGXmupPEH9wYpx7T5WlnOlWelhZhuSWvE80UiRTSlPNU9teBmQsZZFnf2w+bE5ySHKvc+11VH5BgiyF6Yype8SRxnFpmBKY72M9Zml1anbkeNCFQ4buUypTUVQmWYNeh198YLJiSWJ5YqtlkGvUbMx1snaueJF52H3Lf3eApYirirmMu5B/l16Y22oLfDhQmVw+X65nh2vYdDV3CX+O////////nztnynoXUzl1i5rtX2aBnYPxgJhfPF/FdWJ7RpA8aGdZ61qbfRB2fossT/VfamoZbDdvAnTieWiIaIpVjHle32PPdcV50oLXkyiS8oSchu2cLVTBX2xljG1ccBWMp4zTmDtlT3T2Tg1O2FfgWStaZlvMUaheA16cYBZidmV3//9lp2ZubW5yNnsmgVCBmoKZi1yMoIzmjXSWHJZET65kq2tmgh6EYYVqkOhcAWlTmKiEeoVXTw9Sb1+pXkVnDXmPgXmJB4mGbfVfF2JVbLhOz3Jpm5JSBlQ7VnRYs2GkYm5xGllufIl83n0blvBlh4BeThlPdVF1WEBeY15zXwpnxE4mhT2ViZZbfHOYAVD7WMF2VninUiV3pYURe4ZQT1kJckd7x33oj7qP1JBNT79SyVopXwGXrU/dgheS6lcDY1VraXUriNyPFHpCUt9Yk2FVYgpmrmvNfD+D6VAjT/hTBVRGWDFZSVudXPBc710pXpZisWNnZT5luWcL////////bNVs4XD5eDJ+K4DegrOEDITshwKJEooqjEqQppLSmP2c851sTk9OoVCNUlZXSlmoXj1f2F/ZYj9mtGcbZ9Bo0lGSfSGAqoGoiwCMjIy/kn6WMlQgmCxTF1DVU1xYqGSyZzRyZ3dmekaR5lLDbKFrhlgAXkxZVGcsf/tR4XbG//9kaXjom1Seu1fLWblmJ2eaa85U6WnZXlWBnGeVm6pn/pxSaF1Opk/jU8hiuWcrbKuPxE+tfm2ev04HYWJugG8rhRNUc2cqm0Vd83uVXKxbxoccbkqE0XoUgQhZmXyNbBF3IFLZWSJxIXJfd9uXJ51haQtaf1oYUaVUDVR9Zg5234/3kpic9Fnqcl1uxVFNaMl9v33sl2KeumR4aiGDAlmEW19r23MbdvJ9soAXhJlRMmcontl27mdiUv+ZBVwkYjt8foywVU9gtn0LlYBTAU5fUbZZHHI6gDaRzl8ld+JThF95fQSFrIozjo2XVmfzha6UU2EJYQhsuXZS////////iu2POFUvT1FRKlLHU8tbpV59YKBhgmPWZwln2m5nbYxzNnM3dTF5UIjVipiQSpCRkPWWxIeNWRVOiE9ZTg6KiY8/mBBQrV58WZZbuV64Y9pj+mTBZtxpSmnYbQtutnGUdSh6r3+KgACESYTJiYGLIY4KkGWWfZkKYX5ikWsy//9sg210f8x//G3Af4WHuoj4Z2WDsZg8lvdtG31hhD2Rak5xU3VdUGsEb+uFzYYtiadSKVQPXGVnTmiodAZ0g3XiiM+I4ZHMluKWeF+Lc4d6y4ROY6B1ZVKJbUFunHQJdVl4a3ySloZ63J+NT7ZhbmXFhlxOhk6uUNpOIVHMW+5lmWiBbbxzH3ZCd616HHzngm+K0pB8kc+WdZgYUpt90VArU5hnl23LcdB0M4HojyqWo5xXnp90YFhBbZl9L5heTuRPNk+LUbdSsV26YBxzsnk8gtOSNJa3lvaXCp6Xn2Jmpmt0UhdSo3DIiMJeyWBLYZBvI3FJfD599IBv////////hO6QI5MsVEKbb2rTcImMwo3vlzJStFpBXspfBGcXaXxplG1qbw9yYnL8e+2AAYB+h0uQzlFtnpN5hICLkzKK1lAtVIyKcWtqjMSBB2DRZ6Cd8k6ZTpicEIprhcGFaGkAbn54l4FV/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////18MThBOFU4qTjFONk48Tj9OQk5WTlhOgk6FjGtOioISXw1Ojk6eTp9OoE6iTrBOs062Ts5OzU7ETsZOwk7XTt5O7U7fTvdPCU9aTzBPW09dT1dPR092T4hPj0+YT3tPaU9wT5FPb0+GT5ZRGE/UT99Pzk/YT9tP0U/aT9BP5E/lUBpQKFAUUCpQJVAFTxxP9lAhUClQLE/+T+9QEVAGUENQR2cDUFVQUFBIUFpQVlBsUHhQgFCaUIVQtFCy////////UMlQylCzUMJQ1lDeUOVQ7VDjUO5Q+VD1UQlRAVECURZRFVEUURpRIVE6UTdRPFE7UT9RQFFSUUxRVFFievhRaVFqUW5RgFGCVthRjFGJUY9RkVGTUZVRllGkUaZRolGpUapRq1GzUbFRslGwUbVRvVHFUclR21HghlVR6VHt//9R8FH1Uf5SBFILUhRSDlInUipSLlIzUjlST1JEUktSTFJeUlRSalJ0UmlSc1J/Un1SjVKUUpJScVKIUpGPqI+nUqxSrVK8UrVSwVLNUtdS3lLjUuaY7VLgUvNS9VL4UvlTBlMIdThTDVMQUw9TFVMaUyNTL1MxUzNTOFNAU0ZTRU4XU0lTTVHWU15TaVNuWRhTe1N3U4JTllOgU6ZTpVOuU7BTtlPDfBKW2VPfZvxx7lPuU+hT7VP6VAFUPVRAVCxULVQ8VC5UNlQpVB1UTlSPVHVUjlRfVHFUd1RwVJJUe1SAVHZUhFSQVIZUx1SiVLhUpVSsVMRUyFSo////////VKtUwlSkVL5UvFTYVOVU5lUPVRRU/VTuVO1U+lTiVTlVQFVjVUxVLlVcVUVVVlVXVThVM1VdVZlVgFSvVYpVn1V7VX5VmFWeVa5VfFWDValVh1WoVdpVxVXfVcRV3FXkVdRWFFX3VhZV/lX9VhtV+VZOVlBx31Y0VjZWMlY4//9Wa1ZkVi9WbFZqVoZWgFaKVqBWlFaPVqVWrla2VrRWwla8VsFWw1bAVshWzlbRVtNW11buVvlXAFb/VwRXCVcIVwtXDVcTVxhXFlXHVxxXJlc3VzhXTlc7V0BXT1dpV8BXiFdhV39XiVeTV6BXs1ekV6pXsFfDV8ZX1FfSV9NYClfWV+NYC1gZWB1YclghWGJYS1hwa8BYUlg9WHlYhVi5WJ9Yq1i6WN5Yu1i4WK5YxVjTWNFY11jZWNhY5VjcWORY31jvWPpY+Vj7WPxY/VkCWQpZEFkbaKZZJVksWS1ZMlk4WT560llVWVBZTllaWVhZYllgWWdZbFlp////////WXhZgVmdT15Pq1mjWbJZxlnoWdxZjVnZWdpaJVofWhFaHFoJWhpaQFpsWklaNVo2WmJaalqaWrxavlrLWsJavVrjWtda5lrpWtZa+lr7WwxbC1sWWzJa0FsqWzZbPltDW0VbQFtRW1VbWltbW2VbaVtwW3NbdVt4ZYhbeluA//9bg1umW7hbw1vHW8lb1FvQW+Rb5lviW95b5VvrW/Bb9lvzXAVcB1wIXA1cE1wgXCJcKFw4XDlcQVxGXE5cU1xQXE9bcVxsXG5OYlx2XHlcjFyRXJRZm1yrXLtctly8XLdcxVy+XMdc2VzpXP1c+lztXYxc6l0LXRVdF11cXR9dG10RXRRdIl0aXRldGF1MXVJdTl1LXWxdc112XYddhF2CXaJdnV2sXa5dvV2QXbddvF3JXc1d013SXdZd213rXfJd9V4LXhpeGV4RXhteNl43XkReQ15AXk5eV15UXl9eYl5kXkdedV52XnqevF5/XqBewV7CXshe0F7P////////XtZe417dXtpe217iXuFe6F7pXuxe8V7zXvBe9F74Xv5fA18JX11fXF8LXxFfFl8pXy1fOF9BX0hfTF9OXy9fUV9WX1dfWV9hX21fc193X4Nfgl9/X4pfiF+RX4dfnl+ZX5hfoF+oX61fvF/WX/tf5F/4X/Ff3WCzX/9gIWBg//9gGWAQYClgDmAxYBtgFWArYCZgD2A6YFpgQWBqYHdgX2BKYEZgTWBjYENgZGBCYGxga2BZYIFgjWDnYINgmmCEYJtglmCXYJJgp2CLYOFguGDgYNNgtF/wYL1gxmC1YNhhTWEVYQZg9mD3YQBg9GD6YQNhIWD7YPFhDWEOYUdhPmEoYSdhSmE/YTxhLGE0YT1hQmFEYXNhd2FYYVlhWmFrYXRhb2FlYXFhX2FdYVNhdWGZYZZhh2GsYZRhmmGKYZFhq2GuYcxhymHJYfdhyGHDYcZhumHLf3lhzWHmYeNh9mH6YfRh/2H9Yfxh/mIAYghiCWINYgxiFGIb////////Yh5iIWIqYi5iMGIyYjNiQWJOYl5iY2JbYmBiaGJ8YoJiiWJ+YpJik2KWYtRig2KUYtdi0WK7Ys9i/2LGZNRiyGLcYsxiymLCYsdim2LJYwxi7mLxYydjAmMIYu9i9WNQYz5jTWQcY09jlmOOY4Bjq2N2Y6Njj2OJY59jtWNr//9jaWO+Y+ljwGPGY+NjyWPSY/ZjxGQWZDRkBmQTZCZkNmUdZBdkKGQPZGdkb2R2ZE5lKmSVZJNkpWSpZIhkvGTaZNJkxWTHZLtk2GTCZPFk54IJZOBk4WKsZONk72UsZPZk9GTyZPplAGT9ZRhlHGUFZSRlI2UrZTRlNWU3ZTZlOHVLZUhlVmVVZU1lWGVeZV1lcmV4ZYJlg4uKZZtln2WrZbdlw2XGZcFlxGXMZdJl22XZZeBl4WXxZ3JmCmYDZftnc2Y1ZjZmNGYcZk9mRGZJZkFmXmZdZmRmZ2ZoZl9mYmZwZoNmiGaOZolmhGaYZp1mwWa5Zslmvma8////////ZsRmuGbWZtpm4GY/ZuZm6WbwZvVm92cPZxZnHmcmZyeXOGcuZz9nNmdBZzhnN2dGZ15nYGdZZ2NnZGeJZ3BnqWd8Z2pnjGeLZ6ZnoWeFZ7dn72e0Z+xns2fpZ7hn5GfeZ91n4mfuZ7lnzmfGZ+dqnGgeaEZoKWhAaE1oMmhO//9os2graFloY2h3aH9on2iPaK1olGidaJtog2quaLlodGi1aKBoumkPaI1ofmkBaMppCGjYaSJpJmjhaQxozWjUaOdo1Wk2aRJpBGjXaONpJWj5aOBo72koaSppGmkjaSFoxml5aXdpXGl4aWtpVGl+aW5pOWl0aT1pWWkwaWFpXmldaYFpammyaa5p0Gm/acFp02m+ac5b6GnKad1pu2nDaadqLmmRaaBpnGmVabRp3mnoagJqG2n/awpp+WnyaedqBWmxah5p7WoUaetqCmoSasFqI2oTakRqDGpyajZqeGpHamJqWWpmakhqOGoiapBqjWqgaoRqomqj////////apeGF2q7asNqwmq4arNqrGreatFq32qqatpq6mr7awWGFmr6axJrFpsxax9rOGs3dtxrOZjua0drQ2tJa1BrWWtUa1trX2tha3hreWt/a4BrhGuDa41rmGuVa55rpGuqa6trr2uya7Frs2u3a7xrxmvLa9Nr32vsa+tr82vv//+evmwIbBNsFGwbbCRsI2xebFVsYmxqbIJsjWyabIFsm2x+bGhsc2ySbJBsxGzxbNNsvWzXbMVs3WyubLFsvmy6bNts72zZbOptH4hNbTZtK209bThtGW01bTNtEm0MbWNtk21kbVpteW1ZbY5tlW/kbYVt+W4VbgpttW3HbeZtuG3Gbext3m3Mbeht0m3Fbfpt2W3kbdVt6m3ubi1ubm4ubhlucm5fbj5uI25rbitudm5Nbh9uQ246bk5uJG7/bh1uOG6CbqpumG7Jbrdu0269bq9uxG6ybtRu1W6PbqVuwm6fb0FvEXBMbuxu+G7+bz9u8m8xbu9vMm7M////////bz5vE273b4Zvem94b4FvgG9vb1tv829tb4JvfG9Yb45vkW/Cb2Zvs2+jb6FvpG+5b8Zvqm/fb9Vv7G/Ub9hv8W/ub9twCXALb/pwEXABcA9v/nAbcBpvdHAdcBhwH3AwcD5wMnBRcGNwmXCScK9w8XCscLhws3CucN9wy3Dd//9w2XEJcP1xHHEZcWVxVXGIcWZxYnFMcVZxbHGPcftxhHGVcahxrHHXcblxvnHScclx1HHOceBx7HHncfVx/HH5cf9yDXIQchtyKHItcixyMHIycjtyPHI/ckByRnJLclhydHJ+coJygXKHcpJylnKicqdyuXKycsNyxnLEcs5y0nLicuBy4XL5cvdQD3MXcwpzHHMWcx1zNHMvcylzJXM+c05zT57Yc1dzanNoc3BzeHN1c3tzenPIc7NzznO7c8Bz5XPuc950onQFdG90JXP4dDJ0OnRVdD90X3RZdEF0XHRpdHB0Y3RqdHZ0fnSLdJ50p3TKdM901HPx////////dOB043TndOl07nTydPB08XT4dPd1BHUDdQV1DHUOdQ11FXUTdR51JnUsdTx1RHVNdUp1SXVbdUZ1WnVpdWR1Z3VrdW11eHV2dYZ1h3V0dYp1iXWCdZR1mnWddaV1o3XCdbN1w3W1db11uHW8dbF1zXXKddJ12XXjdd51/nX///91/HYBdfB1+nXydfN2C3YNdgl2H3YndiB2IXYidiR2NHYwdjt2R3ZIdkZ2XHZYdmF2YnZodml2anZndmx2cHZydnZ2eHZ8doB2g3aIdot2jnaWdpN2mXaadrB2tHa4drl2unbCds121nbSdt524Xbldud26oYvdvt3CHcHdwR3KXckdx53JXcmdxt3N3c4d0d3Wndod2t3W3dld393fnd5d453i3eRd6B3nnewd7Z3uXe/d7x3vXe7d8d3zXfXd9p33Hfjd+53/HgMeBJ5JnggeSp4RXiOeHR4hnh8eJp4jHijeLV4qniveNF4xnjLeNR4vni8eMV4ynjs////////eOd42nj9ePR5B3kSeRF5GXkseSt5QHlgeVd5X3laeVV5U3l6eX95inmdeaefS3mqea55s3m5ebp5yXnVeed57HnheeN6CHoNehh6GXogeh95gHoxejt6Pno3ekN6V3pJemF6Ynppn516cHp5en16iHqXepV6mHqWeql6yHqw//96tnrFesR6v5CDesd6ynrNes961XrTetl62nrdeuF64nrmeu168HsCew97CnsGezN7GHsZex57NXsoezZ7UHt6ewR7TXsLe0x7RXt1e2V7dHtne3B7cXtse257nXuYe597jXuce5p7i3uSe497XXuZe8t7wXvMe897tHvGe9176XwRfBR75nvlfGB8AHwHfBN783v3fBd8DXv2fCN8J3wqfB98N3wrfD18THxDfFR8T3xAfFB8WHxffGR8VnxlfGx8dXyDfJB8pHytfKJ8q3yhfKh8s3yyfLF8rny5fL18wHzFfMJ82HzSfNx84ps7fO988nz0fPZ8+n0G////////fQJ9HH0VfQp9RX1LfS59Mn0/fTV9Rn1zfVZ9Tn1yfWh9bn1PfWN9k32JfVt9j319fZt9un2ufaN9tX3Hfb19q349faJ9r33cfbh9n32wfdh93X3kfd59+33yfeF+BX4KfiN+IX4SfjF+H34Jfgt+In5GfmZ+O341fjl+Q343//9+Mn46fmd+XX5Wfl5+WX5afnl+an5pfnx+e36DfdV+fY+ufn9+iH6Jfox+kn6QfpN+lH6Wfo5+m36cfzh/On9Ff0x/TX9Of1B/UX9Vf1R/WH9ff2B/aH9pf2d/eH+Cf4Z/g3+If4d/jH+Uf55/nX+af6N/r3+yf7l/rn+2f7iLcX/Ff8Z/yn/Vf9R/4X/mf+l/83/5mNyABoAEgAuAEoAYgBmAHIAhgCiAP4A7gEqARoBSgFiAWoBfgGKAaIBzgHKAcIB2gHmAfYB/gISAhoCFgJuAk4CagK1RkICsgNuA5YDZgN2AxIDagNaBCYDvgPGBG4EpgSOBL4FL////////louBRoE+gVOBUYD8gXGBboFlgWaBdIGDgYiBioGAgYKBoIGVgaSBo4FfgZOBqYGwgbWBvoG4gb2BwIHCgbqByYHNgdGB2YHYgciB2oHfgeCB54H6gfuB/oIBggKCBYIHggqCDYIQghaCKYIrgjiCM4JAglmCWIJdglqCX4Jk//+CYoJogmqCa4IugnGCd4J4gn6CjYKSgquCn4K7gqyC4YLjgt+C0oL0gvOC+oOTgwOC+4L5gt6DBoLcgwmC2YM1gzSDFoMygzGDQIM5g1CDRYMvgyuDF4MYg4WDmoOqg5+DooOWgyODjoOHg4qDfIO1g3ODdYOgg4mDqIP0hBOD64POg/2EA4PYhAuDwYP3hAeD4IPyhA2EIoQgg72EOIUGg/uEbYQqhDyFWoSEhHeEa4SthG6EgoRphEaELIRvhHmENYTKhGKEuYS/hJ+E2YTNhLuE2oTQhMGExoTWhKGFIYT/hPSFF4UYhSyFH4UVhRSE/IVAhWOFWIVI////////hUGGAoVLhVWFgIWkhYiFkYWKhaiFbYWUhZuF6oWHhZyFd4V+hZCFyYW6hc+FuYXQhdWF3YXlhdyF+YYKhhOGC4X+hfqGBoYihhqGMIY/hk1OVYZUhl+GZ4ZxhpOGo4aphqqGi4aMhraGr4bEhsaGsIbJiCOGq4bUht6G6Ybs//+G34bbhu+HEocGhwiHAIcDhvuHEYcJhw2G+YcKhzSHP4c3hzuHJYcphxqHYIdfh3iHTIdOh3SHV4doh26HWYdTh2OHaogFh6KHn4eCh6+Hy4e9h8CH0JbWh6uHxIezh8eHxoe7h++H8ofgiA+IDYf+h/aH94gOh9KIEYgWiBWIIoghiDGINog5iCeIO4hEiEKIUohZiF6IYohriIGIfoieiHWIfYi1iHKIgoiXiJKIroiZiKKIjYikiLCIv4ixiMOIxIjUiNiI2YjdiPmJAoj8iPSI6IjyiQSJDIkKiROJQ4keiSWJKokriUGJRIk7iTaJOIlMiR2JYIle////////iWaJZIltiWqJb4l0iXeJfomDiYiJiomTiZiJoYmpiaaJrImvibKJuom9ib+JwInaidyJ3YnnifSJ+IoDihaKEIoMihuKHYolijaKQYpbilKKRopIinyKbYpsimKKhYqCioSKqIqhipGKpYqmipqKo4rEis2KworaiuuK84rn//+K5IrxixSK4IriiveK3orbiwyLB4saiuGLFosQixeLIIszl6uLJosriz6LKItBi0yLT4tOi0mLVotbi1qLa4tfi2yLb4t0i32LgIuMi46LkouTi5aLmYuajDqMQYw/jEiMTIxOjFCMVYxijGyMeIx6jIKMiYyFjIqMjYyOjJSMfIyYYh2MrYyqjL2MsoyzjK6MtozIjMGM5IzjjNqM/Yz6jPuNBI0FjQqNB40PjQ2NEJ9OjROMzY0UjRaNZ41tjXGNc42BjZmNwo2+jbqNz43ajdaNzI3bjcuN6o3rjd+N4438jgiOCY3/jh2OHo4Qjh+OQo41jjCONI5K////////jkeOSY5MjlCOSI5ZjmSOYI4qjmOOVY52jnKOfI6BjoeOhY6EjouOio6TjpGOlI6ZjqqOoY6sjrCOxo6xjr6OxY7IjsuO247jjvyO+47rjv6PCo8FjxWPEo8ZjxOPHI8fjxuPDI8mjzOPO485j0WPQo8+j0yPSY9Gj06PV49c//+PYo9jj2SPnI+fj6OPrY+vj7eP2o/lj+KP6o/vkIeP9JAFj/mP+pARkBWQIZANkB6QFpALkCeQNpA1kDmP+JBPkFCQUZBSkA6QSZA+kFaQWJBekGiQb5B2lqiQcpCCkH2QgZCAkIqQiZCPkKiQr5CxkLWQ4pDkYkiQ25ECkRKRGZEykTCRSpFWkViRY5FlkWmRc5FykYuRiZGCkaKRq5GvkaqRtZG0kbqRwJHBkcmRy5HQkdaR35HhkduR/JH1kfaSHpH/khSSLJIVkhGSXpJXkkWSSZJkkkiSlZI/kkuSUJKckpaSk5KbklqSz5K5kreS6ZMPkvqTRJMu////////kxmTIpMakyOTOpM1kzuTXJNgk3yTbpNWk7CTrJOtk5STuZPWk9eT6JPlk9iTw5Pdk9CTyJPklBqUFJQTlAOUB5QQlDaUK5Q1lCGUOpRBlFKURJRblGCUYpRelGqSKZRwlHWUd5R9lFqUfJR+lIGUf5WClYeVipWUlZaVmJWZ//+VoJWolaeVrZW8lbuVuZW+lcpv9pXDlc2VzJXVldSV1pXcleGV5ZXiliGWKJYuli+WQpZMlk+WS5Z3llyWXpZdll+WZpZylmyWjZaYlpWWl5aqlqeWsZaylrCWtJa2lriWuZbOlsuWyZbNiU2W3JcNltWW+ZcElwaXCJcTlw6XEZcPlxaXGZcklyqXMJc5lz2XPpdEl0aXSJdCl0mXXJdgl2SXZpdoUtKXa5dxl3mXhZd8l4GXepeGl4uXj5eQl5yXqJeml6OXs5e0l8OXxpfIl8uX3Jftn0+X8nrfl/aX9ZgPmAyYOJgkmCGYN5g9mEaYT5hLmGuYb5hw////////mHGYdJhzmKqYr5ixmLaYxJjDmMaY6ZjrmQOZCZkSmRSZGJkhmR2ZHpkkmSCZLJkumT2ZPplCmUmZRZlQmUuZUZlSmUyZVZmXmZiZpZmtma6ZvJnfmduZ3ZnYmdGZ7ZnumfGZ8pn7mfiaAZoPmgWZ4poZmiuaN5pFmkKaQJpD//+aPppVmk2aW5pXml+aYpplmmSaaZprmmqarZqwmryawJrPmtGa05rUmt6a35rimuOa5prvmuua7pr0mvGa95r7mwabGJsamx+bIpsjmyWbJ5somymbKpsumy+bMptEm0ObT5tNm06bUZtYm3Sbk5uDm5GblpuXm5+boJuom7SbwJvKm7mbxpvPm9Gb0pvjm+Kb5JvUm+GcOpvym/Gb8JwVnBScCZwTnAycBpwInBKcCpwEnC6cG5wlnCScIZwwnEecMpxGnD6cWpxgnGecdpx4nOec7JzwnQmdCJzrnQOdBp0qnSadr50jnR+dRJ0VnRKdQZ0/nT6dRp1I////////nV2dXp1knVGdUJ1ZnXKdiZ2Hnaudb516nZqdpJ2pnbKdxJ3BnbuduJ26ncadz53Cndmd0534nead7Z3vnf2eGp4bnh6edZ55nn2egZ6InouejJ6SnpWekZ6dnqWeqZ64nqqerZdhnsyezp7PntCe1J7cnt6e3Z7gnuWe6J7v//+e9J72nvee+Z77nvye/Z8Hnwh2t58VnyGfLJ8+n0qfUp9Un2OfX59gn2GfZp9nn2yfap93n3Kfdp+Vn5yfoFgvaceQWXRkUdxxmf///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////w==" 11 | --------------------------------------------------------------------------------