diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000000000000000000000000000000000..403c8ef104b8bf3476b7a5155be3158eae28734e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +* text=auto +*.zig text eol=lf +zigmod.* text eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..755aa68f03a30361a954cb1bfbf823ba9fa604ae --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.zig-cache +zig-out +.zigmod +deps.zig +files.zig +zigmod.lock diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..c3c054f8a15f0007a70eab1ddf01bf95c0858bed --- /dev/null +++ b/LICENSE @@ -0,0 +1,307 @@ +Mozilla Public License Version 2.0 + +1. Definitions + + 1.1. "Contributor" means each individual or legal entity that creates, +contributes to the creation of, or owns Covered Software. + + 1.2. "Contributor Version" means the combination of the Contributions of +others (if any) used by a Contributor and that particular Contributor's +Contribution. + + 1.3. "Contribution" means Covered Software of a particular Contributor. + + 1.4. "Covered Software" means Source Code Form to which the initial +Contributor has attached the notice in Exhibit A, the Executable Form of such +Source Code Form, and Modifications of such Source Code Form, in each case +including portions thereof. + + 1.5. "Incompatible With Secondary Licenses" means + + (a) that the initial Contributor has attached the notice described +in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of +version 1.1 or earlier of the License, but not also under the terms of a +Secondary License. + + 1.6. "Executable Form" means any form of the work other than Source Code +Form. + + 1.7. "Larger Work" means a work that combines Covered Software with other +material, in a separate file or files, that is not Covered Software. + + 1.8. "License" means this document. + + 1.9. "Licensable" means having the right to grant, to the maximum extent +possible, whether at the time of the initial grant or subsequently, any and +all of the rights conveyed by this License. + + 1.10. "Modifications" means any of the following: + + (a) any file in Source Code Form that results from an addition to, +deletion from, or modification of the contents of Covered Software; or + + (b) any new file in Source Code Form that contains any Covered +Software. + + 1.11. "Patent Claims" of a Contributor means any patent claim(s), +including without limitation, method, process, and apparatus claims, in any +patent Licensable by such Contributor that would be infringed, but for the +grant of the License, by the making, using, selling, offering for sale, having +made, import, or transfer of either its Contributions or its Contributor +Version. + + 1.12. "Secondary License" means either the GNU General Public License, +Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU +Affero General Public License, Version 3.0, or any later versions of those +licenses. + + 1.13. "Source Code Form" means the form of the work preferred for making +modifications. + + 1.14. "You" (or "Your") means an individual or a legal entity exercising +rights under this License. For legal entities, "You" includes any entity that +controls, is controlled by, or is under common control with You. For purposes +of this definition, "control" means (a) the power, direct or indirect, to +cause the direction or management of such entity, whether by contract or +otherwise, or (b) ownership of more than fifty percent (50%) of the +outstanding shares or beneficial ownership of such entity. + +2. License Grants and Conditions + + 2.1. Grants + Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + + (a) under intellectual property rights (other than patent or +trademark) Licensable by such Contributor to use, reproduce, make available, +modify, display, perform, distribute, and otherwise exploit its Contributions, +either on an unmodified basis, with Modifications, or as part of a Larger +Work; and + + (b) under Patent Claims of such Contributor to make, use, sell, +offer for sale, have made, import, and otherwise transfer either its +Contributions or its Contributor Version. + + 2.2. Effective Date + The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + + 2.3. Limitations on Grant Scope + The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + + (a) for any code that a Contributor has removed from Covered +Software; or + + (b) for infringements caused by: (i) Your and any other third +party's modifications of Covered Software, or (ii) the combination of its +Contributions with other software (except as part of its Contributor Version); +or + + (c) under Patent Claims infringed by Covered Software in the absence +of its Contributions. + + This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with the +notice requirements in Section 3.4). + + 2.4. Subsequent Licenses + No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this License +(see Section 10.2) or under the terms of a Secondary License (if permitted +under the terms of Section 3.3). + + 2.5. Representation + Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights to +grant the rights to its Contributions conveyed by this License. + + 2.6. Fair Use + This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + + 2.7. Conditions + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in +Section 2.1. + +3. Responsibilities + + 3.1. Distribution of Source Form + All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under the +terms of this License. You must inform recipients that the Source Code Form of +the Covered Software is governed by the terms of this License, and how they +can obtain a copy of this License. You may not attempt to alter or restrict +the recipients' rights in the Source Code Form. + + 3.2. Distribution of Executable Form + If You distribute Covered Software in Executable Form then: + + (a) such Covered Software must also be made available in Source Code +Form, as described in Section 3.1, and You must inform recipients of the +Executable Form how they can obtain a copy of such Source Code Form by +reasonable means in a timely manner, at a charge no more than the cost of +distribution to the recipient; and + + (b) You may distribute such Executable Form under the terms of this +License, or sublicense it under different terms, provided that the license for +the Executable Form does not attempt to limit or alter the recipients' rights +in the Source Code Form under this License. + + 3.3. Distribution of a Larger Work + You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for the +Covered Software. If the Larger Work is a combination of Covered Software with +a work governed by one or more Secondary Licenses, and the Covered Software is +not Incompatible With Secondary Licenses, this License permits You to +additionally distribute such Covered Software under the terms of such +Secondary License(s), so that the recipient of the Larger Work may, at their +option, further distribute the Covered Software under the terms of either this +License or such Secondary License(s). + + 3.4. Notices + You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, or +limitations of liability) contained within the Source Code Form of the Covered +Software, except that You may alter any license notices to the extent required +to remedy known factual inaccuracies. + + 3.5. Application of Additional Terms + You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on behalf of +any Contributor. You must make it absolutely clear that any such warranty, +support, indemnity, or liability obligation is offered by You alone, and You +hereby agree to indemnify every Contributor for any liability incurred by such +Contributor as a result of warranty, support, indemnity or liability terms You +offer. You may include additional disclaimers of warranty and limitations of +liability specific to any jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +If it is impossible for You to comply with any of the terms of this License +with respect to some or all of the Covered Software due to statute, judicial +order, or regulation then You must: (a) comply with the terms of this License +to the maximum extent possible; and (b) describe the limitations and the code +they affect. Such description must be placed in a text file included with all +distributions of the Covered Software under this License. Except to the extent +prohibited by statute or regulation, such description must be sufficiently +detailed for a recipient of ordinary skill to be able to understand it. + +5. Termination + + 5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become compliant, +then the rights granted under this License from a particular Contributor are +reinstated (a) provisionally, unless and until such Contributor explicitly and +finally terminates Your grants, and (b) on an ongoing basis, if such +Contributor fails to notify You of the non-compliance by some reasonable means +prior to 60 days after You have come back into compliance. Moreover, Your +grants from a particular Contributor are reinstated on an ongoing basis if +such Contributor notifies You of the non-compliance by some reasonable means, +this is the first time You have received notice of non-compliance with this +License from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + + 5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, counter-claims, +and cross-claims) alleging that a Contributor Version directly or indirectly +infringes any patent, then the rights granted to You by any and all +Contributors for the Covered Software under Section 2.1 of this License shall +terminate. + + 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end +user license agreements (excluding distributors and resellers) which have been +validly granted by You or Your distributors under this License prior to +termination shall survive termination. + +6. Disclaimer of Warranty +Covered Software is provided under this License on an "as is" basis, without +warranty of any kind, either expressed, implied, or statutory, including, +without limitation, warranties that the Covered Software is free of defects, +merchantable, fit for a particular purpose or non-infringing. The entire risk +as to the quality and performance of the Covered Software is with You. Should +any Covered Software prove defective in any respect, You (not any Contributor) +assume the cost of any necessary servicing, repair, or correction. This +disclaimer of warranty constitutes an essential part of this License. No use +of any Covered Software is authorized under this License except under this +disclaimer. + +7. Limitation of Liability +Under no circumstances and under no legal theory, whether tort (including +negligence), contract, or otherwise, shall any Contributor, or anyone who +distributes Covered Software as permitted above, be liable to You for any +direct, indirect, special, incidental, or consequential damages of any +character including, without limitation, damages for lost profits, loss of +goodwill, work stoppage, computer failure or malfunction, or any and all other +commercial damages or losses, even if such party shall have been informed of +the possibility of such damages. This limitation of liability shall not apply +to liability for death or personal injury resulting from such party's +negligence to the extent applicable law prohibits such limitation. Some +jurisdictions do not allow the exclusion or limitation of incidental or +consequential damages, so this exclusion and limitation may not apply to You. + +8. Litigation +Any litigation relating to this License may be brought only in the courts of a +jurisdiction where the defendant maintains its principal place of business and +such litigation shall be governed by laws of that jurisdiction, without +reference to its conflict-of-law provisions. Nothing in this Section shall +prevent a party's ability to bring cross-claims or counter-claims. + +9. Miscellaneous +This License represents the complete agreement concerning the subject matter +hereof. If any provision of this License is held to be unenforceable, such +provision shall be reformed only to the extent necessary to make it +enforceable. Any law or regulation which provides that the language of a +contract shall be construed against the drafter shall not be used to construe +this License against a Contributor. + +10. Versions of the License + + 10.1. New Versions + Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or publish +new versions of this License. Each version will be given a distinguishing +version number. + + 10.2. Effect of New Versions + You may distribute the Covered Software under the terms of the version of +the License under which You originally received the Covered Software, or under +the terms of any subsequent version published by the license steward. + + 10.3. Modified Versions + If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a modified +version of this License if you rename the license and remove any references to +the name of the license steward (except to note that such modified license +differs from this License). + + 10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the notice +described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this file, You +can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, +then You may include the notice in a location (such as a LICENSE file in a +relevant directory) where a recipient would be likely to look for such a +notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice + + This Source Code Form is "Incompatible With Secondary Licenses", as +defined by the Mozilla Public License, v. 2.0. diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..3f0922d634bceaf620b5dffc7ec6963571f5cc11 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# zig-qrcode + +![loc](https://sloc.xyz/github/nektro/zig-qrcode) +[![license](https://img.shields.io/github/license/nektro/zig-qrcode.svg)](https://github.com/nektro/zig-qrcode/blob/master/LICENSE) +[![nektro @ github sponsors](https://img.shields.io/badge/sponsors-nektro-purple?logo=github)](https://github.com/sponsors/nektro) +[![Zig](https://img.shields.io/badge/Zig-0.14-f7a41d)](https://ziglang.org/) +[![Zigmod](https://img.shields.io/badge/Zigmod-latest-f7a41d)](https://github.com/nektro/zigmod) + +Zig generator for quick-response codes + +https://en.wikipedia.org/wiki/QR_code + +https://observablehq.com/@zavierhenry/encoding-qr-codes diff --git a/build.zig b/build.zig new file mode 100644 index 0000000000000000000000000000000000000000..f9ffeeac66f66074b0a7bbaee04d2452b06067c5 --- /dev/null +++ b/build.zig @@ -0,0 +1,20 @@ +const std = @import("std"); +const deps = @import("./deps.zig"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const mode = b.option(std.builtin.Mode, "mode", "") orelse .Debug; + + const tests = b.addTest(.{ + .root_source_file = b.path("test.zig"), + .target = target, + .optimize = mode, + }); + deps.addAllTo(tests); + + const test_step = b.step("test", "Run all library tests"); + const tests_run = b.addRunArtifact(tests); + tests_run.setCwd(b.path(".")); + tests_run.has_side_effects = true; + test_step.dependOn(&tests_run.step); +} diff --git a/licenses.txt b/licenses.txt new file mode 100644 index 0000000000000000000000000000000000000000..3203bcc7c5ffdfb417f2e424609843e7e3166641 --- /dev/null +++ b/licenses.txt @@ -0,0 +1,7 @@ +MPL-2.0: += https://spdx.org/licenses/MPL-2.0 +- This + +MIT: += https://spdx.org/licenses/MIT +- git https://github.com/nektro/zig-extras diff --git a/qrcode.zig b/qrcode.zig new file mode 100644 index 0000000000000000000000000000000000000000..53c72274ada0b6d30b44478146ffcc6a8ce6988d --- /dev/null +++ b/qrcode.zig @@ -0,0 +1,744 @@ +const std = @import("std"); +const extras = @import("extras"); + +/// GF(256)[0...255] +const GF = blk: { + var gf: [256]u8 = @splat(2); + for (0..256) |n| { + if (n == 0) { + gf[n] = 1; + continue; + } + if (n < 8) { + gf[n] <<= @intCast(n - 1); + continue; + } + gf[n] = gf[n - 1] *% 2; + if (gf[n - 1] > 127) gf[n] ^= 0b00011101; + } + const out = gf; + break :blk out; +}; + +pub fn encode(comptime mode: Mode, comptime level: Level, comptime version: u16, input: []const u8) BitGrid(4 * version + 17) { + _ = &version; + _ = &mode; + _ = &level; + _ = &input; + + std.log.warn("mode: {s}", .{@tagName(mode)}); + std.log.warn("level: {s}", .{@tagName(level)}); + + if (mode == .numeric) std.debug.assert(extras.matchesAll(u8, input, isNumeric)); + if (mode == .alphanumeric) std.debug.assert(extras.matchesAll(u8, input, isAlphanumeric)); + // latin1 is a full 1-byte format, some readers can detect utf-8 + // shift jis is a full 2-byte format + + const max_lengths = comptime maxLenPerModeLevelVersion(mode, level); + const max_length = max_lengths[version]; + std.log.warn("actual version: {d},{d},{d}", .{ version, max_length, input.len }); + std.debug.assert(input.len <= max_length); + for (max_lengths, 0..) |l, i| if (l > input.len) break std.log.warn("reccom version: {d},{d}", .{ i, l }); + + const mode_indicator: u4 = switch (mode) { + .numeric => 0b0001, + .alphanumeric => 0b0010, + .latin1 => 0b0100, + .shiftjis => 0b1000, + }; + std.log.warn("mode_indicator: {d}", .{mode_indicator}); + + const LenIndicator = switch (mode) { + .numeric => switch (version) { + 1...9 => u10, + 10...26 => u12, + 27...40 => u14, + else => unreachable, + }, + .alphanumeric => switch (version) { + 1...9 => u9, + 10...26 => u11, + 27...40 => u13, + else => unreachable, + }, + .latin1 => switch (version) { + 1...9 => u8, + 10...26 => u16, + 27...40 => u16, + else => unreachable, + }, + .shiftjis => switch (version) { + 1...9 => u8, + 10...26 => u10, + 27...40 => u12, + else => unreachable, + }, + }; + const len_indicator: LenIndicator = @intCast(input.len); + std.log.warn("len_indicator: {d}", .{len_indicator}); + + const total_bits = (max_length + 2) * 8; + std.log.warn("total_bits: {d}", .{total_bits}); + + var scratch: std.bit_set.ArrayBitSet(u8, total_bits) = .initEmpty(); + _ = &scratch; + var scratchw: BitsetWriter(u8, total_bits) = .{ .set = &scratch }; + _ = &scratchw; + + scratchw.writeInt(mode_indicator); + scratchw.writeInt(len_indicator); + for (input) |x| scratchw.writeInt(x); + + const data_bits = input.len * 8 + @bitSizeOf(@TypeOf(mode_indicator)) + @bitSizeOf(@TypeOf(len_indicator)); + std.log.warn("data_bits: {d}", .{data_bits}); + std.debug.assert(scratchw.idx == data_bits); + std.debug.assert(scratchw.idx == 44); //temp + + // write @max(4, padding_bits) 0s + for (0..@min(4, total_bits - scratchw.idx)) |_| scratchw.writeInt(@as(u1, 0)); + std.debug.assert(scratchw.idx == 48); //temp + + // add zero bits to make data_bits%8 == 0 + for (0..-%scratchw.idx & 7) |_| scratchw.writeInt(@as(u1, 0)); + std.debug.assert(scratchw.idx == 48); //temp + + // add 236(11101100) 17(00010001) until we reach total_bits + while (scratchw.idx < total_bits) { + scratchw.writeInt(@as(u8, 0xec)); + scratchw.writeInt(@as(u8, 0x11)); + } + + // std.log.warn("{b:0>8}", .{&bitReverse(scratch.masks)}); + // std.log.warn("{d}", .{&bitReverse(scratch.masks)}); + // std.log.warn("{x}", .{&bitReverse(scratch.masks)}); + + std.debug.assert(@bitReverse(scratch.masks[0]) == 0b01000000); //temp + std.debug.assert(!scratch.isSet(0)); //temp + std.debug.assert(scratch.isSet(1)); //temp + std.debug.assert(!scratch.isSet(2)); //temp + std.debug.assert(!scratch.isSet(3)); //temp + // 01000000 01000101 01100110 01010111 00100011 00010000 11101100 00010001 11101100 00010001 11101100 00010001 11101100 00010001 11101100 00010001 11101100 00010001 11101100 + // 01000000 01000101 01100110 01010111 00100011 00010000 11101100 00010001 11101100 00010001 11101100 00010001 11101100 00010001 11101100 00010001 11101100 00010001 11101100 + + const groups = 1; // ? + _ = &groups; + const blocks = 1; // ? + _ = &blocks; + const dc_per_block = 19; //? + _ = &dc_per_block; + const ecc_per_block = 7; //? + _ = &ecc_per_block; + + var message_polynomial = scratch.masks; + _ = &message_polynomial; + std.log.warn("message_polynomial: {d}", .{&bitReverse(message_polynomial)}); + + const generator_polynomial = reverseArray(generatorPolynomial(ecc_per_block)); + _ = &generator_polynomial; + std.log.warn("generator_polynomial: {d}", .{generator_polynomial}); + + const generator_polynomial_alpha = to_alpha(generator_polynomial); + _ = &generator_polynomial_alpha; + std.log.warn("generator_polynomial_alpha: {d}", .{generator_polynomial_alpha}); + + // Now that the message and generator polynomials have been created, divide them to get the error codewords. + { + // 1. Multiply the message polynomial by x^n where n is the number of error correction codewords to be generated + // and multiply the generator polynomial by x^m where m is the leading exponent of the message polynomial. + // 2. Multiply the generator polynomial by the lead term of the message polynomial. + // 3. XOR the result of Step 2 with the message polynomial and discard the lead 0 term. + // 4. Repeat Steps 2-3 n-1 more times where n is the number of data codewords. + } + var mp: [scratch.masks.len + ecc_per_block]?u8 = @splat(null); + for (&message_polynomial, 0..) |x, i| mp[i] = to_alpha(.{@bitReverse(x)})[0]; + + for (0..scratch.masks.len) |i| { + const lead = mp[i].?; + for (&generator_polynomial_alpha, 0..) |x, j| { + var y = GF[(@as(usize, x) + lead) % 255]; + if (mp[i + j]) |z| y ^= GF[z]; + mp[i + j] = if (y == 0) null else to_alpha(.{y})[0]; + } + } + + var error_codewords: [ecc_per_block]u8 = @splat(0); + for (0..ecc_per_block) |i| error_codewords[i] = if (mp[message_polynomial.len..][i]) |x| GF[x] else 0; + std.log.warn("error_codewords: {d}", .{error_codewords}); + + const interleaved_message = scratch.masks ++ bitReverse(error_codewords); + _ = &interleaved_message; + std.log.warn("interleaved_message: {b:0>8}", .{&bitReverse(interleaved_message)}); + + // remainder bits + + // + + const size = 4 * version + 17; + var grid: BitGrid(size) = .initEmpty(); + _ = &grid; + + // top left finder pattern + for (0..7) |i| for (0..7) |j| grid.setT(0 + i, 0 + j); + for (0..5) |i| for (0..5) |j| grid.setF(1 + i, 1 + j); + for (0..3) |i| for (0..3) |j| grid.setT(2 + i, 2 + j); + + // top right finder pattern + for (0..7) |i| for (0..7) |j| grid.setT(size - 7 + 0 + i, 0 + j); + for (0..5) |i| for (0..5) |j| grid.setF(size - 7 + 1 + i, 1 + j); + for (0..3) |i| for (0..3) |j| grid.setT(size - 7 + 2 + i, 2 + j); + + // bottom left finder pattern + for (0..7) |i| for (0..7) |j| grid.setT(0 + i, size - 7 + 0 + j); + for (0..5) |i| for (0..5) |j| grid.setF(1 + i, size - 7 + 1 + j); + for (0..3) |i| for (0..3) |j| grid.setT(2 + i, size - 7 + 2 + j); + + // timing pattern + for (8..size) |i| if (i % 2 == 0) grid.set(i, 6); + for (8..size) |i| if (i % 2 == 0) grid.set(6, i); + + // alignment patterns + + // dark module + grid.set(8, 4 * (version - 1) + 13); + + // version block + + // data mask (data cannot be placed where this is set) + var data_mask: BitGrid(size) = .initEmpty(); + for (0..9) |y| for (0..9) |x| data_mask.set(x, y); + for (0..9) |y| for (0..8) |x| data_mask.set(size - 8 + x, y); + for (0..8) |y| for (0..9) |x| data_mask.set(x, size - 8 + y); + for (0..size) |i| data_mask.set(i, 6); + for (0..size) |i| data_mask.set(6, i); + + // write data + var data_iter: BufBiterator = .init(&bitReverse(interleaved_message)); + _ = &data_iter; + + var griditer: GridZigZag = .init(size, 6); + while (griditer.next()) |idx| { + const x = idx % size; + const y = (idx - x) / size; + // std.log.warn("idx: {d} {d},{d}", .{ idx, x, y }); + if (data_mask.isSet(x, y)) continue; + grid.debug.set((y * size) + x); + if (@bitCast(data_iter.next() orelse break)) grid.set(x, y); + } + + // + + for (0..8) |y| for (0..8) |x| grid.debug.set((y * size) + x); + for (0..8) |y| for (0..8) |x| grid.debug.set((y * size) + size - 8 + x); + for (0..8) |y| for (0..8) |x| grid.debug.set(((size - 8 + y) * size) + x); + for (0..size) |i| grid.debug.set((6 * size) + i); + for (0..size) |i| grid.debug.set((i * size) + 6); + + // format info + var masks = [_]std.bit_set.IntegerBitSet(15){ + .{ .mask = @bitReverse(formatPattern(level, 0)) }, + .{ .mask = @bitReverse(formatPattern(level, 1)) }, + .{ .mask = @bitReverse(formatPattern(level, 2)) }, + .{ .mask = @bitReverse(formatPattern(level, 3)) }, + .{ .mask = @bitReverse(formatPattern(level, 4)) }, + .{ .mask = @bitReverse(formatPattern(level, 5)) }, + .{ .mask = @bitReverse(formatPattern(level, 6)) }, + .{ .mask = @bitReverse(formatPattern(level, 7)) }, + }; + var grids = [_]BitGrid(size){ + grid, + grid, + grid, + grid, + grid, + grid, + grid, + grid, + }; + for (&grids, &masks) |*g, m| { + g.setV(0, 8, m.isSet(0)); + g.setV(1, 8, m.isSet(1)); + g.setV(2, 8, m.isSet(2)); + g.setV(3, 8, m.isSet(3)); + g.setV(4, 8, m.isSet(4)); + g.setV(5, 8, m.isSet(5)); + g.setV(7, 8, m.isSet(6)); + g.setV(8, 8, m.isSet(7)); + g.setV(8, 7, m.isSet(8)); + g.setV(8, 5, m.isSet(9)); + g.setV(8, 4, m.isSet(10)); + g.setV(8, 3, m.isSet(11)); + g.setV(8, 2, m.isSet(12)); + g.setV(8, 1, m.isSet(13)); + g.setV(8, 0, m.isSet(14)); + + g.setV(8, size - 1 - 0, m.isSet(0)); + g.setV(8, size - 1 - 1, m.isSet(1)); + g.setV(8, size - 1 - 2, m.isSet(2)); + g.setV(8, size - 1 - 3, m.isSet(3)); + g.setV(8, size - 1 - 4, m.isSet(4)); + g.setV(8, size - 1 - 5, m.isSet(5)); + g.setV(8, size - 1 - 6, m.isSet(6)); + g.setV(size - 8 + 0, 8, m.isSet(7)); + g.setV(size - 8 + 1, 8, m.isSet(8)); + g.setV(size - 8 + 2, 8, m.isSet(9)); + g.setV(size - 8 + 3, 8, m.isSet(10)); + g.setV(size - 8 + 4, 8, m.isSet(11)); + g.setV(size - 8 + 5, 8, m.isSet(12)); + g.setV(size - 8 + 6, 8, m.isSet(13)); + g.setV(size - 8 + 7, 8, m.isSet(14)); + } + + // masking patterns + // Pattern 0: (row + column) mod 2 == 0 + for (0..size) |row| for (0..size) |col| if (!data_mask.isSet(col, row) and (row + col) % 2 == 0) grids[0].flip(col, row); + // Pattern 1: row mod 2 == 0 + for (0..size) |row| for (0..size) |col| if (!data_mask.isSet(col, row) and row % 2 == 0) grids[1].flip(col, row); + // Pattern 2: column mod 3 == 0 + for (0..size) |row| for (0..size) |col| if (!data_mask.isSet(col, row) and col % 3 == 0) grids[2].flip(col, row); + // Pattern 3: (row + column) mod 3 == 0 + for (0..size) |row| for (0..size) |col| if (!data_mask.isSet(col, row) and (row + col) % 3 == 0) grids[3].flip(col, row); + // Pattern 4: (floor(row/2) + floor(column / 3)) mod 2 == 0 + for (0..size) |row| for (0..size) |col| if (!data_mask.isSet(col, row) and (row / 2 + col / 3) % 2 == 0) grids[4].flip(col, row); + // Pattern 5: ((row * column) mod 2) + ((row * column) mod 3) == 0 + for (0..size) |row| for (0..size) |col| if (!data_mask.isSet(col, row) and (((row * col) % 2) + ((row * col) % 3)) == 0) grids[5].flip(col, row); + // Pattern 6: ( ((row * column) mod 2) + ((row * column) mod 3) ) mod 2 == 0 + for (0..size) |row| for (0..size) |col| if (!data_mask.isSet(col, row) and (((row * col) % 2) + ((row * col) % 3)) % 2 == 0) grids[6].flip(col, row); + // Pattern 7: ( ((row + column) mod 2) + ((row * column) mod 3) ) mod 2 == 0 + for (0..size) |row| for (0..size) |col| if (!data_mask.isSet(col, row) and (((row + col) % 2) + ((row * col) % 3)) % 2 == 0) grids[7].flip(col, row); + + // penalty + var penaltys: [8][4]u32 = @splat(@splat(0)); + for (&grids, 0..) |*g, i| { + var prev = false; + var run: u16 = 0; + for (0..size) |y| { + for (0..size) |x| { + if (x == 0) { + run = 0; + prev = g.isSet(0, y); + } + const this = g.isSet(x, y); + if (this == prev) { + run += 1; + penaltys[i][0] += if (run == 5) 3 else if (run > 5) 1 else 0; + } else { + run = 1; + } + prev = this; + } + } + for (0..size) |x| { + for (0..size) |y| { + if (y == 0) { + run = 0; + prev = g.isSet(x, 0); + } + const this = g.isSet(x, y); + if (this == prev) { + run += 1; + penaltys[i][0] += if (run == 5) 3 else if (run > 5) 1 else 0; + } else { + run = 1; + } + prev = this; + } + } + } + for (&grids, 0..) |*g, i| { + for (0..size - 1) |y| { + for (0..size - 1) |x| { + const p0 = g.isSet(x, y); + const p1 = g.isSet(x + 1, y); + const p2 = g.isSet(x, y + 1); + const p3 = g.isSet(x + 1, y + 1); + penaltys[i][1] += if (p0 == p1 and p1 == p2 and p2 == p3) 3 else 0; + } + } + } + for (&grids, 0..) |*g, i| { + for (0..size) |y| { + for (0..size - 10) |x| { + const sel = g.sliceX(y, x, 11); + penaltys[i][2] += if (sel.mask == 0b00001011101) 40 else 0; + penaltys[i][2] += if (sel.mask == 0b10111010000) 40 else 0; + } + } + for (0..size) |x| { + for (0..size - 10) |y| { + const sel = g.sliceY(x, y, 11); + penaltys[i][2] += if (sel.mask == 0b00001011101) 40 else 0; + penaltys[i][2] += if (sel.mask == 0b10111010000) 40 else 0; + } + } + } + for (&grids, 0..) |*g, i| { + const total_modules: comptime_float = size * size; + var dark_modules: f32 = 0; + for (0..size) |y| { + for (0..size) |x| { + if (g.isSet(x, y)) { + dark_modules += 1; + } + } + } + const percent: u32 = @intFromFloat(@ceil(dark_modules / total_modules * 100)); + const round1 = percent + (-%percent & 4); + const round2 = round1 -| 5; + const sub = [_]u32{ @abs(@as(i32, @intCast(round1)) - 50), @abs(@as(i32, @intCast(round2)) - 50) }; + const div = [_]u32{ @divFloor(sub[0], 5), @divFloor(sub[1], 5) }; + penaltys[i][3] += std.mem.min(u32, &div) * 10; + } + for (&penaltys) |x| std.log.warn("penaltys: {d}", .{&x}); + + var penalty: [8]u32 = @splat(0); + for (0..8) |i| penalty[i] = extras.sum(u32, &penaltys[i]); + + const min_penalty = std.mem.min(u32, &penalty); + std.log.warn("min_penalty: {d}", .{min_penalty}); + const min_mask = std.mem.indexOfScalar(u32, &penalty, min_penalty).?; + std.log.warn("min_mask: {d}", .{min_mask}); + + std.log.warn("", .{}); + return grids[min_mask]; +} + +fn BitGrid(comptime size: usize) type { + return struct { + bset: std.bit_set.ArrayBitSet(usize, size * size), + debug: std.bit_set.ArrayBitSet(usize, size * size), + comptime size: usize = size, + + pub fn initEmpty() @This() { + return .{ + .bset = .initEmpty(), + .debug = .initEmpty(), + }; + } + + pub fn isSet(self: *const @This(), x: usize, y: usize) bool { + return self.bset.isSet((y * size) + x); + } + + pub fn set(self: *@This(), x: usize, y: usize) void { + self.bset.set((y * size) + x); + self.debug.set((y * size) + x); + } + + pub fn setF(self: *@This(), x: usize, y: usize) void { + self.bset.setValue((y * size) + x, false); + self.debug.setValue((y * size) + x, true); + } + + pub fn setT(self: *@This(), x: usize, y: usize) void { + self.bset.setValue((y * size) + x, true); + self.debug.setValue((y * size) + x, true); + } + + pub fn setV(self: *@This(), x: usize, y: usize, value: bool) void { + self.bset.setValue((y * size) + x, value); + self.debug.setValue((y * size) + x, true); + } + + pub fn hasSet(self: *const @This(), x: usize, y: usize) bool { + return self.debug.isSet((y * size) + x); + } + + pub fn flip(self: *@This(), x: usize, y: usize) void { + std.debug.assert(self.debug.isSet((y * size) + x)); + self.bset.toggle((y * size) + x); + } + + pub fn sliceX(self: *@This(), y: usize, sx: usize, comptime len: usize) std.bit_set.IntegerBitSet(len) { + var result: std.bit_set.IntegerBitSet(len) = .initEmpty(); + for (0..len) |i| result.setValue(i, self.isSet(sx + i, y)); + return result; + } + + pub fn sliceY(self: *@This(), x: usize, sy: usize, comptime len: usize) std.bit_set.IntegerBitSet(len) { + var result: std.bit_set.IntegerBitSet(len) = .initEmpty(); + for (0..len) |i| result.setValue(i, self.isSet(x, sy + i)); + return result; + } + }; +} + +fn BitsetWriter(MaskIntType: type, size: usize) type { + return struct { + set: *std.bit_set.ArrayBitSet(MaskIntType, size), + idx: usize = 0, + + pub fn writeInt(self: *@This(), int: anytype) void { + return writeInt2(self, @bitReverse(int)); + } + fn writeInt2(self: *@This(), int: anytype) void { + const T = @TypeOf(int); + const info = @typeInfo(T).int; + comptime std.debug.assert(info.signedness == .unsigned); + if (self.idx == size) { + return; + } + if (T == u1) { + self.set.setValue(self.idx, @bitCast(int)); + self.idx += 1; + return; + } + self.set.setValue(self.idx, (int & 1) == 1); + self.idx += 1; + return writeInt2(self, @as(extras.OneSmallerInt(T), @intCast(int >> 1))); + } + }; +} + +fn bitReverse(array: anytype) [array.len]std.meta.Child(@TypeOf(array)) { + var result: [array.len]std.meta.Child(@TypeOf(array)) = @splat(0); + for (&array, &result) |x, *y| y.* = @bitReverse(x); + return result; +} + +fn reverseArray(array: anytype) @TypeOf(array) { + var result: @TypeOf(array) = array; + for (0..result.len / 2) |i| { + std.mem.swap(std.meta.Child(@TypeOf(result)), &result[i], &result[result.len - 1 - i]); + } + return result; +} + +// + +pub const Mode = enum { + numeric, + alphanumeric, + latin1, + shiftjis, +}; + +pub const Level = enum { + L, + M, + Q, + H, +}; + +// Sourced from https://gist.github.com/ZavierHenry/a4b3761212109d5598bb2db79c334782 +pub fn maxLenPerModeLevelVersion(mode: Mode, level: Level) *const [41]u16 { + return switch (mode) { + .numeric => switch (level) { + .L => &.{ 0, 41, 77, 127, 187, 255, 322, 370, 461, 552, 652, 772, 883, 1022, 1101, 1250, 1408, 1548, 1725, 1903, 2061, 2232, 2409, 2620, 2812, 3057, 2383, 3517, 3669, 3909, 4158, 4417, 4686, 4965, 5253, 5529, 5836, 6153, 6479, 6743, 7089 }, + .M => &.{ 0, 34, 63, 101, 149, 202, 255, 293, 365, 432, 513, 694, 691, 796, 871, 991, 1082, 1212, 1346, 1500, 1600, 1708, 1872, 2059, 2188, 2395, 2544, 2701, 2857, 3035, 3289, 3486, 3693, 3909, 4134, 4343, 4588, 4775, 5039, 5313, 5596 }, + .Q => &.{ 0, 27, 48, 77, 111, 144, 178, 207, 259, 312, 364, 427, 489, 580, 621, 703, 775, 876, 948, 1063, 1159, 1224, 1358, 1468, 1588, 1718, 1804, 1933, 2085, 2181, 2358, 2473, 2670, 2805, 2949, 3081, 3244, 3417, 3599, 3791, 3993 }, + .H => &.{ 0, 17, 34, 58, 82, 106, 139, 154, 202, 235, 288, 331, 374, 427, 468, 530, 602, 674, 746, 813, 919, 969, 1056, 1108, 1228, 1286, 1425, 1501, 1581, 1677, 1782, 1897, 2022, 2157, 2301, 2361, 2524, 2625, 2735, 2927, 3057 }, + }, + .alphanumeric => switch (level) { + .L => &.{ 0, 25, 47, 77, 114, 154, 195, 224, 279, 335, 395, 468, 535, 619, 667, 758, 854, 938, 1046, 1153, 1249, 1352, 1460, 1588, 1704, 1853, 1990, 2132, 2223, 2369, 2520, 2677, 2840, 3009, 3183, 3351, 3537, 3729, 3927, 4087, 4296 }, + .M => &.{ 0, 20, 38, 61, 90, 122, 154, 178, 221, 262, 311, 366, 419, 483, 528, 600, 656, 734, 816, 909, 970, 1035, 1134, 1248, 1326, 1451, 1542, 1637, 1732, 1839, 1994, 2113, 2238, 2369, 2506, 2632, 2780, 2894, 3054, 3220, 3391 }, + .Q => &.{ 0, 16, 29, 47, 67, 87, 108, 125, 157, 189, 221, 259, 296, 352, 376, 426, 470, 531, 574, 644, 702, 742, 823, 890, 963, 1041, 1094, 1172, 1263, 1322, 1429, 1499, 1618, 1700, 1787, 1867, 1966, 2071, 2181, 2298, 2420 }, + .H => &.{ 0, 10, 20, 35, 50, 64, 84, 93, 122, 143, 174, 200, 227, 259, 283, 321, 365, 408, 452, 493, 557, 587, 640, 672, 744, 779, 864, 910, 958, 1016, 1080, 1150, 1226, 1307, 1394, 1431, 1530, 1591, 1658, 1774, 1852 }, + }, + .latin1 => switch (level) { + .L => &.{ 0, 17, 32, 53, 78, 106, 134, 154, 192, 230, 271, 321, 367, 425, 458, 520, 586, 644, 718, 792, 858, 929, 1003, 1091, 1171, 1273, 1367, 1465, 1528, 1628, 1732, 1840, 1952, 2068, 2188, 2303, 2431, 2563, 2699, 2809, 2953 }, + .M => &.{ 0, 14, 26, 42, 62, 84, 106, 122, 152, 180, 213, 251, 287, 331, 362, 412, 450, 504, 560, 624, 666, 711, 779, 857, 911, 997, 1059, 1125, 1190, 1264, 1370, 1452, 1538, 1628, 1722, 1809, 1911, 1989, 2099, 2213, 2331 }, + .Q => &.{ 0, 11, 20, 32, 46, 60, 74, 86, 108, 130, 151, 177, 203, 241, 258, 292, 322, 364, 394, 442, 482, 509, 565, 611, 661, 715, 751, 805, 868, 908, 982, 1030, 1112, 1168, 1228, 1283, 1351, 1423, 1499, 1579, 1663 }, + .H => &.{ 0, 7, 14, 24, 34, 44, 58, 64, 84, 98, 119, 137, 155, 177, 194, 220, 250, 280, 310, 338, 382, 403, 439, 461, 511, 535, 593, 625, 658, 698, 742, 790, 842, 898, 958, 983, 1051, 1093, 1139, 1219, 1273 }, + }, + .shiftjis => switch (level) { + .L => &.{ 0, 10, 20, 32, 48, 65, 82, 95, 118, 141, 167, 198, 226, 262, 282, 320, 361, 397, 442, 488, 528, 572, 618, 672, 721, 784, 842, 902, 940, 1002, 1066, 1132, 1201, 1273, 1347, 1417, 1496, 1577, 1661, 1729, 1817 }, + .M => &.{ 0, 8, 16, 26, 38, 52, 65, 75, 93, 111, 131, 155, 177, 204, 223, 254, 277, 310, 345, 384, 410, 438, 480, 528, 561, 614, 652, 692, 732, 778, 843, 894, 947, 1002, 1060, 1113, 1176, 1224, 1292, 1362, 1435 }, + .Q => &.{ 0, 7, 12, 20, 28, 37, 45, 53, 66, 80, 93, 109, 125, 149, 159, 180, 198, 224, 243, 272, 297, 314, 348, 376, 407, 440, 462, 496, 534, 559, 604, 634, 684, 719, 756, 790, 832, 876, 923, 972, 1024 }, + .H => &.{ 0, 4, 8, 15, 21, 27, 36, 39, 52, 60, 74, 85, 96, 109, 120, 136, 154, 173, 191, 208, 235, 248, 270, 284, 315, 330, 365, 385, 405, 430, 457, 486, 518, 553, 590, 605, 647, 673, 701, 750, 784 }, + }, + }; +} + +fn isNumeric(c: u8) bool { + return switch (c) { + '0'...'9' => true, + else => false, + }; +} + +fn isAlphanumeric(c: u8) bool { + return switch (c) { + '0'...'9' => true, + 'A'...'Z' => true, + ' ', '$', '%', '*', '+', '-', '.', '/', ':', ',' => true, + else => false, + }; +} + +fn generatorPolynomial(comptime n: usize) [n + 1]u8 { + if (n == 1) return .{ 1, 1 }; + + const prev = generatorPolynomial(n - 1); + const prev_alpha = to_alpha(prev); + const term_alpha: [2]u8 = .{ n - 1, 0 }; + + var multiply_alpha: [n * 2][2]u8 = @splat(@splat(0)); + for (&prev_alpha, 0..) |a, i| { + for (&term_alpha, 0..) |b, j| { + multiply_alpha[(term_alpha.len * i) + j] = .{ a + b, @intCast(i + j) }; + } + } + var combine_alpha: [n + 1]u8 = @splat(0); + for (0..n + 1) |i| { + for (&multiply_alpha) |el| { + if (el[1] == i) combine_alpha[i] ^= GF[el[0]]; + } + } + return combine_alpha; +} + +fn to_alpha(array: anytype) [array.len]u8 { + var result: [array.len]u8 = array; + for (&result) |*x| x.* = @intCast(std.mem.indexOfScalar(u8, &GF, x.*).?); + return result; +} + +fn formatPattern(level: Level, mask: u3) u15 { + return switch (level) { + .L => switch (mask) { + 0 => 0b111011111000100, + 1 => 0b111001011110011, + 2 => 0b111110110101010, + 3 => 0b111100010011101, + 4 => 0b110011000101111, + 5 => 0b110001100011000, + 6 => 0b110110001000001, + 7 => 0b110100101110110, + }, + .M => switch (mask) { + 0 => 0b101010000010010, + 1 => 0b101000100100101, + 2 => 0b101111001111100, + 3 => 0b101101101001011, + 4 => 0b100010111111001, + 5 => 0b100000011001110, + 6 => 0b100111110010111, + 7 => 0b100101010100000, + }, + .Q => switch (mask) { + 0 => 0b011010101011111, + 1 => 0b011000001101000, + 2 => 0b011111100110001, + 3 => 0b011101000000110, + 4 => 0b010010010110100, + 5 => 0b010000110000011, + 6 => 0b010111011011010, + 7 => 0b010101111101101, + }, + .H => switch (mask) { + 0 => 0b001011010001001, + 1 => 0b001001110111110, + 2 => 0b001110011100111, + 3 => 0b001100111010000, + 4 => 0b000011101100010, + 5 => 0b000001001010101, + 6 => 0b000110100001100, + 7 => 0b000100000111011, + }, + }; +} + +// +// +// +// + +const BufBiterator = struct { + buf: []const u8, + bits: std.bit_set.IntegerBitSet(8), + idx: u8, // buf index + jdx: u8, // bits index + + pub fn init(buf: []const u8) BufBiterator { + return .{ + .buf = buf, + .bits = .{ .mask = buf[0] }, + .idx = 0, + .jdx = 0, + }; + } + + pub fn next(self: *BufBiterator) ?u1 { + if (self.jdx == 8) { + self.jdx = 0; + self.idx += 1; + if (self.idx == self.buf.len) return null; + self.bits.mask = self.buf[self.idx]; + return self.next(); + } + const result = self.bits.isSet(7 - self.jdx); + self.jdx += 1; + return @intFromBool(result); + } +}; + +// +// +// +// +// + +const GridZigZag = struct { + grid_size: usize, + col_skip: usize, + dir: u1, + phase: u1, + y: usize, + x: usize, + + pub fn init(grid_size: usize, col_skip: usize) GridZigZag { + return .{ + .grid_size = grid_size, + .col_skip = col_skip, + .dir = 0, + .phase = 0, + .y = grid_size, + .x = grid_size - 2, + }; + } + + pub fn next(self: *@This()) ?usize { + blk: switch (self.dir) { + 0 => switch (self.phase) { //up + 0 => { //prev left + if (self.y == 0) { + self.x -= 1; + self.dir = 1; + self.phase = 1; + if (self.x == self.col_skip and self.col_skip == 0) return null; + if (self.x == self.col_skip) self.x -= 1; + break :blk; + } + self.y -= 1; + self.x += 1; + self.phase +%= 1; + }, + 1 => { //prev right + self.x -= 1; + self.phase +%= 1; + }, + }, + 1 => switch (self.phase) { //down + 0 => { //prev left + if (self.y == self.grid_size - 1) { + if (self.x == 0) return null; + self.x -= 1; + if (self.x == self.col_skip and self.col_skip == 0) return null; + if (self.x == self.col_skip) self.x -= 1; + self.dir = 0; + self.phase = 1; + break :blk; + } + self.x += 1; + self.y += 1; + self.phase +%= 1; + }, + 1 => { //prev right + self.x -= 1; + self.phase +%= 1; + }, + }, + } + return (self.y * self.grid_size) + self.x; + } +}; diff --git a/test.zig b/test.zig new file mode 100644 index 0000000000000000000000000000000000000000..dbdc5e5c6d04b0a3c5e1f51e176f8ed17fd16099 --- /dev/null +++ b/test.zig @@ -0,0 +1,31 @@ +const std = @import("std"); +const qrcode = @import("qrcode"); + +test { + const grid = qrcode.encode(.latin1, .L, 1, "Ver1"); + // 40 45 66 57 23 10 ec 11 ec + _ = &grid; + + const bg_black = "\x1b[40m"; + const bg_white = "\x1b[47m"; + const bg_blue = "\x1b[44m"; + const bg_reset = "\x1b[49m"; + + const fg_black = "\x1b[30m"; + const fg_white = "\x1b[37m"; + const fg_reset = "\x1b[39m"; + + for (0..grid.size) |y| { + for (0..grid.size) |x| { + std.debug.print("{s}", .{if (grid.hasSet(x, y)) + if (grid.isSet(x, y)) + bg_black ++ fg_black ++ "██" + else + bg_white ++ fg_white ++ " " + else + bg_blue ++ fg_white ++ " -"}); + } + std.debug.print("{s}", .{fg_reset ++ bg_reset}); + std.debug.print("\n", .{}); + } +} diff --git a/zigmod.yml b/zigmod.yml new file mode 100644 index 0000000000000000000000000000000000000000..ab812d544ab3f1f30fe9f3ff49d57fa5355cc9fb --- /dev/null +++ b/zigmod.yml @@ -0,0 +1,7 @@ +id: nvs62rfbcub6qtnmc9v7753nokki6w1y5flyu6hfdkqvtn5x +name: qrcode +main: qrcode.zig +license: MPL-2.0 +description: QR code (quick-response code) generator for Zig +dependencies: + - src: git https://github.com/nektro/zig-extras