├── test ├── test_helper.exs ├── matrix_test.exs ├── galois_field_test.exs ├── reed_solomon_test.exs ├── png_test.exs ├── svg_test.exs └── eqrcode_test.exs ├── assets ├── default.png └── circle-color.png ├── .formatter.exs ├── .gitignore ├── lib ├── eqrcode │ ├── galois_field.ex │ ├── render.ex │ ├── png.ex │ ├── mask.ex │ ├── encode.ex │ ├── svg.ex │ ├── reed_solomon.ex │ ├── matrix.ex │ └── spec_table.ex └── eqrcode.ex ├── LICENSE.md ├── .github └── workflows │ └── test.yml ├── config └── config.exs ├── mix.exs ├── mix.lock └── README.md /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /assets/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiliconJungles/eqrcode/HEAD/assets/default.png -------------------------------------------------------------------------------- /assets/circle-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SiliconJungles/eqrcode/HEAD/assets/circle-color.png -------------------------------------------------------------------------------- /test/matrix_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EQRCode.MatrixTest do 2 | use ExUnit.Case 3 | doctest EQRCode.Matrix 4 | end 5 | -------------------------------------------------------------------------------- /.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /test/galois_field_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EQRCode.GaloisFieldTest do 2 | use ExUnit.Case 3 | doctest EQRCode.GaloisField 4 | end 5 | -------------------------------------------------------------------------------- /test/reed_solomon_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EQRCode.ReedSolomonTest do 2 | use ExUnit.Case 3 | doctest EQRCode.ReedSolomon 4 | end 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | eqrcode-*.tar 24 | 25 | # Temporary files, for example, from tests. 26 | /tmp/ 27 | 28 | # Misc. 29 | **/.DS_Store 30 | .elixir_ls 31 | /test/images/ 32 | /test/html/ 33 | .mix_tasks 34 | -------------------------------------------------------------------------------- /lib/eqrcode/galois_field.ex: -------------------------------------------------------------------------------- 1 | defmodule EQRCode.GaloisField do 2 | @moduledoc false 3 | 4 | import Bitwise 5 | 6 | @gf256 Stream.iterate({1, 0}, fn {e, i} -> 7 | n = e <<< 1 8 | {if(n >= 256, do: bxor(n, 0b100011101), else: n), i + 1} 9 | end) 10 | |> Enum.take(256) 11 | |> Enum.reduce({%{}, %{}}, fn {e, i}, {to_i, to_a} -> 12 | {Map.put(to_i, i, e), Map.put_new(to_a, e, i)} 13 | end) 14 | 15 | @gf256_to_i elem(@gf256, 0) 16 | @gf256_to_a elem(@gf256, 1) 17 | 18 | @doc """ 19 | Given alpha exponent returns integer. 20 | 21 | ## Examples 22 | 23 | iex> EQRCode.GaloisField.to_i(1) 24 | 2 25 | 26 | """ 27 | @spec to_i(integer) :: integer 28 | def to_i(alpha), do: @gf256_to_i[alpha] 29 | 30 | @doc """ 31 | Given integer returns alpha exponent. 32 | 33 | ## Examples 34 | 35 | iex> EQRCode.GaloisField.to_a(2) 36 | 1 37 | 38 | """ 39 | @spec to_a(integer) :: integer 40 | def to_a(integer), do: @gf256_to_a[integer] 41 | end 42 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2024 Silicon Jungles 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | 8 | jobs: 9 | build_linux: 10 | name: Build and test on Linux 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | otp: ['25.3', '26.2', '27.2.1'] 15 | elixir: ['1.15.7', '1.16.3', '1.17.2', '1.18.2'] 16 | exclude: 17 | - otp: '27.2.1' 18 | elixir: '1.15.7' 19 | - otp: '27.2.1' 20 | elixir: '1.16.3' 21 | - otp: '26.2' 22 | elixir: '1.18.2' 23 | env: 24 | MIX_ENV: test 25 | 26 | steps: 27 | - name: Checkout repository 28 | uses: actions/checkout@v4 29 | 30 | - name: Setup beam 31 | uses: erlef/setup-beam@v1 32 | with: 33 | otp-version: ${{matrix.otp}} 34 | elixir-version: ${{matrix.elixir}} 35 | install-hex: true 36 | install-rebar: true 37 | 38 | - name: Setup Elixir Project 39 | run: mix deps.get 40 | 41 | - name: Check format 42 | run: mix format --check-formatted 43 | 44 | - name: Run Tests 45 | run: mix test --warnings-as-errors 46 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | import Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for 9 | # 3rd-party users, it should be done in your "mix.exs" file. 10 | 11 | # You can configure your application as: 12 | # 13 | # config :eqrcode, key: :value 14 | # 15 | # and access this configuration in your application as: 16 | # 17 | # Application.get_env(:eqrcode, :key) 18 | # 19 | # You can also configure a 3rd-party app: 20 | # 21 | # config :logger, level: :info 22 | # 23 | 24 | # It is also possible to import configuration files, relative to this 25 | # directory. For example, you can emulate configuration per environment 26 | # by uncommenting the line below and defining dev.exs, test.exs and such. 27 | # Configuration from the imported file will override the ones defined 28 | # here (which is why it is important to import them last). 29 | # 30 | # import_config "#{Mix.env}.exs" 31 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule EQRCode.MixProject do 2 | use Mix.Project 3 | 4 | @source_url "https://github.com/SiliconJungles/eqrcode" 5 | @version "0.2.1" 6 | 7 | def project do 8 | [ 9 | app: :eqrcode, 10 | version: @version, 11 | elixir: "~> 1.6", 12 | start_permanent: Mix.env() == :prod, 13 | name: "EQRCode", 14 | package: package(), 15 | deps: deps(), 16 | docs: docs() 17 | ] 18 | end 19 | 20 | # Run "mix help compile.app" to learn about applications. 21 | def application do 22 | [ 23 | extra_applications: [:logger] 24 | ] 25 | end 26 | 27 | defp package() do 28 | [ 29 | description: "Simple QRCode Generator in Elixir", 30 | licenses: ["MIT"], 31 | maintainers: ["siliconavengers", "nthock"], 32 | links: %{ 33 | "GitHub" => @source_url 34 | } 35 | ] 36 | end 37 | 38 | defp docs do 39 | [ 40 | extras: [ 41 | "LICENSE.md": [title: "License"], 42 | "README.md": [title: "Overview"] 43 | ], 44 | main: "readme", 45 | source_url: @source_url, 46 | source_ref: "v#{@version}", 47 | assets: "assets", 48 | formatters: ["html"] 49 | ] 50 | end 51 | 52 | defp deps do 53 | [ 54 | {:ex_doc, ">= 0.25.2", only: :dev, runtime: false} 55 | ] 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/eqrcode/render.ex: -------------------------------------------------------------------------------- 1 | defmodule EQRCode.Render do 2 | @moduledoc """ 3 | Render the QR Code matrix. 4 | 5 | Taken essentially verbatim from https://github.com/sunboshan/qrcode 6 | """ 7 | 8 | @doc """ 9 | Render the QR Code to terminal. 10 | 11 | ## Examples 12 | 13 | qr_code_content 14 | |> EQRCode.encode() 15 | |> EQRCode.render() 16 | 17 | """ 18 | @spec render(EQRCode.Matrix.t()) :: :ok 19 | def render(%EQRCode.Matrix{matrix: matrix}) do 20 | Tuple.to_list(matrix) 21 | |> Stream.map(fn e -> 22 | Tuple.to_list(e) 23 | |> Enum.map(&do_render/1) 24 | end) 25 | |> Enum.intersperse("\n") 26 | |> IO.puts() 27 | end 28 | 29 | defp do_render(1), do: "\e[40m \e[0m" 30 | defp do_render(0), do: "\e[0;107m \e[0m" 31 | defp do_render(nil), do: "\e[0;106m \e[0m" 32 | defp do_render(:data), do: "\e[0;102m \e[0m" 33 | defp do_render(:reserved), do: "\e[0;104m \e[0m" 34 | 35 | @doc """ 36 | Rotate the QR Code 90 degree clockwise and render to terminal. 37 | """ 38 | @spec render2(EQRCode.Matrix.t()) :: :ok 39 | def render2(%EQRCode.Matrix{matrix: matrix}) do 40 | for(e <- Tuple.to_list(matrix), do: Tuple.to_list(e)) 41 | |> Enum.reverse() 42 | |> transform() 43 | |> Stream.map(fn e -> 44 | Enum.map(e, &do_render/1) 45 | end) 46 | |> Enum.intersperse("\n") 47 | |> IO.puts() 48 | end 49 | 50 | defp transform(matrix) do 51 | for e <- Enum.zip(matrix), do: Tuple.to_list(e) 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "earmark": {:hex, :earmark, "1.2.6", "b6da42b3831458d3ecc57314dff3051b080b9b2be88c2e5aa41cd642a5b044ed", [:mix], [], "hexpm"}, 3 | "earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"}, 4 | "ex_doc": {:hex, :ex_doc, "0.35.1", "de804c590d3df2d9d5b8aec77d758b00c814b356119b3d4455e4b8a8687aecaf", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "2121c6402c8d44b05622677b761371a759143b958c6c19f6558ff64d0aed40df"}, 5 | "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, 6 | "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, 7 | "makeup_erlang": {:hex, :makeup_erlang, "1.0.1", "c7f58c120b2b5aa5fd80d540a89fdf866ed42f1f3994e4fe189abebeab610839", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "8a89a1eeccc2d798d6ea15496a6e4870b75e014d1af514b1b71fa33134f57814"}, 8 | "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, 9 | } 10 | -------------------------------------------------------------------------------- /test/png_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EQRCode.PNGTest do 2 | use ExUnit.Case 3 | 4 | defp content, do: "www.google.com" 5 | defp html_path, do: Path.expand("./test/html") 6 | defp image_path, do: Path.expand("./test/images") 7 | 8 | setup do 9 | html_path() |> File.mkdir_p!() 10 | image_path() |> File.mkdir_p!() 11 | qr = content() |> EQRCode.encode() 12 | [qr: qr] 13 | end 14 | 15 | test "Generate an html page with different types of PNG images", %{qr: qr} do 16 | color = <<0, 0, 255>> 17 | background_color = <<255, 255, 0>> 18 | 19 | pngs = 20 | [] 21 | ## Test Default 22 | |> build_png(qr, "Default", []) 23 | ## Test Foreground Color png 24 | |> build_png(qr, "Foreground", color: color) 25 | ## Test Background Color png 26 | |> build_png(qr, "Background", background_color: background_color) 27 | ## Test Foreground and Background Color 28 | |> build_png(qr, "Both Colors", background_color: background_color, color: color) 29 | ## Test Background Transparency 30 | |> build_png(qr, "Transparent", background_color: :transparent, color: color) 31 | 32 | html = gen_html(pngs) 33 | 34 | html_path() 35 | |> Path.join("png_test.html") 36 | |> File.write(html) 37 | end 38 | 39 | defp write_png_to_file(png_binary, path), do: File.write!(path, png_binary, [:binary]) 40 | 41 | defp build_png(png_list, qr, label, opts) do 42 | png_path = image_path() |> Path.join(Macro.underscore(label) <> ".png") 43 | 44 | EQRCode.png(qr, opts) 45 | |> write_png_to_file(png_path) 46 | 47 | png_relative_path = "../images/" <> Path.basename(png_path) 48 | png_list ++ [{label, png_relative_path}] 49 | end 50 | 51 | defp gen_html(kw_png) when is_list(kw_png) do 52 | png = 53 | Enum.map(kw_png, fn {key, value} -> 54 | ~s{

#{key}:

} 55 | end) 56 | 57 | """ 58 | 59 | 60 | 61 | EQRCode PNG Image Tests 62 | 65 | 66 | 67 | #{png} 68 | 69 | 70 | """ 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /test/svg_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EQRCode.SVGTest do 2 | use ExUnit.Case 3 | 4 | defp content, do: "www.google.com" 5 | defp html_path, do: Path.expand("./test/html") 6 | 7 | setup do 8 | html_path() |> File.mkdir_p!() 9 | qr = content() |> EQRCode.encode() 10 | [qr: qr] 11 | end 12 | 13 | test "Generate an html page with different types of SVG images", %{qr: qr} do 14 | color = "blue" 15 | background_color = "yellow" 16 | 17 | svgs = 18 | [] 19 | ## Test default option 20 | |> build_svgs(qr, "Default", []) 21 | ## Test foreground color being set 22 | |> build_svgs(qr, "Foreground", color: color) 23 | ## Test background being set 24 | |> build_svgs(qr, "Background", background_color: background_color) 25 | ## Test both background and foreground color being set 26 | |> build_svgs(qr, "Both Colors", background_color: background_color, color: color) 27 | ## Test transparency in HTML 28 | |> build_svgs(qr, "Transparent", background_color: :transparent, color: color) 29 | ## Test ViewBox 30 | |> build_svgs(qr, "ViewBox", 31 | background_color: background_color, 32 | color: color, 33 | viewbox: true 34 | ) 35 | ## Test transparent viewbox in HTML 36 | |> build_svgs(qr, "Transparent ViewBox", 37 | background_color: :transparent, 38 | color: color, 39 | viewbox: true 40 | ) 41 | 42 | html = gen_html(svgs) 43 | 44 | html_path() 45 | |> Path.join("svg_test.html") 46 | |> File.write(html) 47 | end 48 | 49 | defp build_svgs(svgs, qr, label, opts) do 50 | svg = EQRCode.svg(qr, opts) 51 | svg_c = EQRCode.svg(qr, [shape: "circle"] ++ opts) 52 | svgs ++ [{label, svg <> svg_c}] 53 | end 54 | 55 | defp gen_html(kw_svg) when is_list(kw_svg) do 56 | svg = Enum.map(kw_svg, fn {key, value} -> "

#{key}:

" <> value end) 57 | 58 | """ 59 | 60 | 61 | 62 | EQRCode SVG Image Tests 63 | 66 | 67 | 68 | #{svg} 69 | 70 | 71 | """ 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /lib/eqrcode.ex: -------------------------------------------------------------------------------- 1 | defmodule EQRCode do 2 | @moduledoc """ 3 | Simple QR Code Generator written in Elixir with no other dependencies. 4 | """ 5 | 6 | alias EQRCode.{Encode, ReedSolomon, Matrix} 7 | 8 | @type error_correction_level :: :l | :m | :q | :h 9 | 10 | @doc """ 11 | Encode the binary. 12 | """ 13 | @spec encode(binary, error_correction_level()) :: Matrix.t() 14 | def encode(bin, error_correction_level \\ :l) 15 | 16 | def encode(bin, error_correction_level) when byte_size(bin) <= 2952 do 17 | {version, error_correction_level, data} = 18 | Encode.encode(bin, error_correction_level) 19 | |> ReedSolomon.encode() 20 | 21 | Matrix.new(version, error_correction_level) 22 | |> Matrix.draw_finder_patterns() 23 | |> Matrix.draw_seperators() 24 | |> Matrix.draw_alignment_patterns() 25 | |> Matrix.draw_timing_patterns() 26 | |> Matrix.draw_dark_module() 27 | |> Matrix.draw_reserved_format_areas() 28 | |> Matrix.draw_reserved_version_areas() 29 | |> Matrix.draw_data_with_mask(data) 30 | |> Matrix.draw_format_areas() 31 | |> Matrix.draw_version_areas() 32 | |> Matrix.draw_quite_zone() 33 | end 34 | 35 | def encode(bin, _error_correction_level) when is_nil(bin) do 36 | raise(ArgumentError, message: "you must pass in some input") 37 | end 38 | 39 | def encode(bin, _error_correction_level) when is_list(bin) do 40 | raise(ArgumentError, 41 | message: "You have passed a list instead of string. Did you pass in a charlist by mistake?" 42 | ) 43 | end 44 | 45 | def encode(_, _), 46 | do: raise(ArgumentError, message: "your input is too long. keep it under 2952 characters") 47 | 48 | @doc """ 49 | Encode the binary with custom pattern bits. 50 | 51 | Only supports version 5. 52 | """ 53 | @spec encode(binary, error_correction_level(), bitstring) :: Matrix.t() 54 | def encode(bin, error_correction_level, bits) when byte_size(bin) <= 106 do 55 | {version, error_correction_level, data} = 56 | Encode.encode(bin, error_correction_level, bits) 57 | |> ReedSolomon.encode() 58 | 59 | Matrix.new(version, error_correction_level) 60 | |> Matrix.draw_finder_patterns() 61 | |> Matrix.draw_seperators() 62 | |> Matrix.draw_alignment_patterns() 63 | |> Matrix.draw_timing_patterns() 64 | |> Matrix.draw_dark_module() 65 | |> Matrix.draw_reserved_format_areas() 66 | |> Matrix.draw_data_with_mask0(data) 67 | |> Matrix.draw_format_areas() 68 | |> Matrix.draw_quite_zone() 69 | end 70 | 71 | def encode(_, _, _), do: IO.puts("Binary too long.") 72 | 73 | defdelegate svg(matrix, options \\ []), to: EQRCode.SVG 74 | 75 | defdelegate png(matrix, options \\ []), to: EQRCode.PNG 76 | 77 | defdelegate render(matrix), to: EQRCode.Render 78 | end 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EQRCode 2 | 3 | Simple QR Code Generator written in Elixir with no other dependencies. 4 | 5 | ## Installation 6 | 7 | ```elixir 8 | def deps do 9 | [ 10 | {:eqrcode, "~> 0.2.0"} 11 | ] 12 | end 13 | ``` 14 | 15 | ## Using EQRCode (Basic Usage) 16 | 17 | You can use EQRCode to generate QR Code in SVG or PNG format. 18 | 19 | ```elixir 20 | qr_code_content = "your_qr_code_content" 21 | 22 | # To SVG 23 | qr_code_content 24 | |> EQRCode.encode() 25 | |> EQRCode.svg() 26 | 27 | # To PNG 28 | qr_code_content 29 | |> EQRCode.encode() 30 | |> EQRCode.png() 31 | ``` 32 | 33 | ![screenshot-defaults](./assets/default.png) 34 | 35 | Note that the PNG format is only the binary. You still have to write the data to a file: 36 | 37 | ```elixir 38 | qr_code_png = 39 | qr_code_content 40 | |> EQRCode.encode() 41 | |> EQRCode.png() 42 | 43 | File.write("path/where/you/want/to/save.png", qr_code_png, [:binary]) 44 | ``` 45 | 46 | You should be able to see the file generated in the path you specified. 47 | 48 | ## Image Rendering Options 49 | 50 | ### SVG 51 | 52 | You can pass in options into `EQRCode.svg()`: 53 | 54 | ```elixir 55 | qr_code_content 56 | |> EQRCode.encode() 57 | |> EQRCode.svg(color: "#03B6AD", shape: "circle", width: 300, background_color: "#FFF") 58 | ``` 59 | 60 | ![screenshot-circle-color](./assets/circle-color.png) 61 | 62 | You can specify the following attributes of the QR code: 63 | 64 | * `color`: In hexadecimal format. The default is `#000` 65 | 66 | * `background_color`: In hexadecimal format or `:transparent`. The default is `#FFF`. 67 | 68 | * `shape`: Only `square` or `circle`. The default is `square` 69 | 70 | * `width`: The width of the QR code in pixel. Without the width attribute, the 71 | QR code size will be dynamically generated based on the input string. 72 | 73 | * `viewbox`: When set to `true`, the SVG element will specify its height and 74 | width using `viewBox`, instead of explicit `height` and `width` tags. 75 | 76 | Default options are `[color: "#000", shape: "square", background_color: "#FFF"]`. 77 | 78 | ### PNG 79 | 80 | You can specify the following attributes of the QR code: 81 | 82 | * `color`: In binary format in the RGB order. The default is `<<0, 0, 0>>` 83 | 84 | * `background_color`: In binary format or `:transparent`. The default is 85 | `<<255, 255, 255>>` 86 | 87 | * `width`: The width of the QR code in pixel. (the actual size may vary, due to 88 | the number of modules in the code) 89 | 90 | By default, QR code size will be dynamically generated based on the input string. 91 | 92 | ### ASCII 93 | 94 | You can also render the QRCode in your console: 95 | 96 | ```elixir 97 | qr_code_content 98 | |> EQRCode.encode() 99 | |> EQRCode.render() 100 | ``` 101 | 102 | ## Credits 103 | 104 | We reused most of the code from [sunboshan/qrcode](https://github.com/sunboshan/qrcode) to generate the matrix required to render the QR Code. We also reference [rqrcode](https://github.com/whomwah/rqrcode) on how to generate SVG from the QR Code matrix. 105 | 106 | ## Copyright and License 107 | 108 | Copyright (c) 2024 Silicon Jungles 109 | 110 | This library is released under the MIT License. See the 111 | [LICENSE.md](./LICENSE.md) file. 112 | -------------------------------------------------------------------------------- /lib/eqrcode/png.ex: -------------------------------------------------------------------------------- 1 | defmodule EQRCode.PNG do 2 | @moduledoc """ 3 | Render the QR Code matrix in PNG format. 4 | """ 5 | 6 | alias EQRCode.Matrix 7 | 8 | @defaults %{ 9 | background_color: <<255, 255, 255>>, 10 | color: <<0, 0, 0>>, 11 | module_size: 11 12 | } 13 | 14 | @transparent_alpha <<0>> 15 | @opaque_alpha <<255>> 16 | 17 | @png_signature <<137, 80, 78, 71, 13, 10, 26, 10>> 18 | 19 | @doc """ 20 | Returns the PNG binary representation of the QR Code 21 | 22 | ## Options 23 | 24 | You can specify the following attributes of the QR code: 25 | 26 | * `:color` - In binary format. The default is `<<0, 0, 0>>` 27 | 28 | * `:background_color` - In binary format or `:transparent`. The default is 29 | `<<255, 255, 255>>` 30 | 31 | * `:width` - The width of the QR code in pixel. (the actual size may vary, due 32 | to the number of modules in the code) 33 | 34 | By default, QR code size will be dynamically generated based on the input string. 35 | 36 | ## Examples 37 | 38 | qr_code_content 39 | |> EQRCode.encode() 40 | |> EQRCode.png(color: <<255, 0, 255>>, width: 200) 41 | 42 | """ 43 | @spec png(Matrix.t(), map() | Keyword.t()) :: String.t() 44 | def png(%Matrix{matrix: matrix} = m, options \\ []) do 45 | matrix_size = Matrix.size(m) 46 | options = normalize_options(options, matrix_size) 47 | pixel_size = matrix_size * options[:module_size] 48 | 49 | ihdr = png_chunk("IHDR", <>) 50 | idat = png_chunk("IDAT", pixels(matrix, options)) 51 | iend = png_chunk("IEND", "") 52 | 53 | [@png_signature, ihdr, idat, iend] 54 | |> List.flatten() 55 | |> Enum.join() 56 | end 57 | 58 | defp normalize_options(options, matrix_size) do 59 | options 60 | |> Enum.into(@defaults) 61 | |> calc_module_size(matrix_size) 62 | end 63 | 64 | defp calc_module_size(%{width: width} = options, matrix_size) when is_integer(width) do 65 | size = (width / matrix_size) |> Float.round() |> trunc() 66 | Map.put(options, :module_size, size) 67 | end 68 | 69 | defp calc_module_size(options, _matrix_size), do: options 70 | 71 | defp png_chunk(type, binary) do 72 | length = byte_size(binary) 73 | crc = :erlang.crc32(type <> binary) 74 | 75 | [<>, type, binary, <>] 76 | end 77 | 78 | defp pixels(matrix, options) do 79 | matrix 80 | |> Tuple.to_list() 81 | |> Stream.map(&row_pixels(&1, options)) 82 | |> Enum.join() 83 | |> :zlib.compress() 84 | end 85 | 86 | defp row_pixels(row, %{module_size: module_size} = options) do 87 | pixels = 88 | row 89 | |> Tuple.to_list() 90 | |> Enum.map(&module_pixels(&1, options)) 91 | |> Enum.join() 92 | 93 | :binary.copy(<<0>> <> pixels, module_size) 94 | end 95 | 96 | defp module_pixels(value, %{background_color: background_color, module_size: module_size}) 97 | when is_nil(value) or value == 0 do 98 | background_color 99 | |> apply_alpha_channel() 100 | |> :binary.copy(module_size) 101 | end 102 | 103 | defp module_pixels(1, %{color: color, module_size: module_size}) do 104 | color 105 | |> apply_alpha_channel() 106 | |> :binary.copy(module_size) 107 | end 108 | 109 | defp apply_alpha_channel(:transparent), do: <<0, 0, 0>> <> @transparent_alpha 110 | defp apply_alpha_channel(color), do: color <> @opaque_alpha 111 | end 112 | -------------------------------------------------------------------------------- /test/eqrcode_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EQRCodeTest do 2 | use ExUnit.Case 3 | doctest EQRCode.Encode 4 | 5 | describe "encoder" do 6 | @sample_matrix {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 7 | {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 8 | {0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0}, 9 | {0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0}, 10 | {0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0}, 11 | {0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0}, 12 | {0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0}, 13 | {0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0}, 14 | {0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0}, 15 | {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 16 | {0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0}, 17 | {0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0}, 18 | {0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0}, 19 | {0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0}, 20 | {0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0}, 21 | {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0}, 22 | {0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0}, 23 | {0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0}, 24 | {0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0}, 25 | {0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0}, 26 | {0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0}, 27 | {0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0}, 28 | {0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0}, 29 | {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 30 | {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}} 31 | 32 | test "should return the sample matrix when call the method" do 33 | qr_code_matrix = 34 | "silicon jungles" 35 | |> EQRCode.encode() 36 | 37 | assert qr_code_matrix.matrix == @sample_matrix 38 | end 39 | 40 | test "should return error when the input is too long" do 41 | long_qr_content = String.duplicate("a", 2954) 42 | 43 | assert_raise ArgumentError, "your input is too long. keep it under 2952 characters", fn -> 44 | long_qr_content |> EQRCode.encode() 45 | end 46 | end 47 | 48 | test "should return error when the input is a charlist" do 49 | charlist = ~c"this_is_a_charlist" 50 | 51 | assert_raise ArgumentError, 52 | "You have passed a list instead of string. Did you pass in a charlist by mistake?", 53 | fn -> 54 | charlist |> EQRCode.encode() 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/eqrcode/mask.ex: -------------------------------------------------------------------------------- 1 | defmodule EQRCode.Mask do 2 | @moduledoc false 3 | 4 | @doc """ 5 | Get the total score for the masked matrix. 6 | """ 7 | @spec score(EQRCode.Matrix.matrix()) :: integer 8 | def score(matrix) do 9 | rule1(matrix) + rule2(matrix) + rule3(matrix) + rule4(matrix) 10 | end 11 | 12 | @doc """ 13 | Check for consecutive blocks. 14 | """ 15 | @spec rule1(EQRCode.Matrix.matrix()) :: integer 16 | def rule1(matrix) do 17 | matrix = for e <- Tuple.to_list(matrix), do: Tuple.to_list(e) 18 | 19 | Stream.concat(matrix, transform(matrix)) 20 | |> Enum.reduce(0, &(do_rule1(&1, {nil, 0}, 0) + &2)) 21 | end 22 | 23 | defp do_rule1([], _, acc), do: acc 24 | defp do_rule1([h | t], {_, 0}, acc), do: do_rule1(t, {h, 1}, acc) 25 | defp do_rule1([h | t], {h, 4}, acc), do: do_rule1(t, {h, 5}, acc + 3) 26 | defp do_rule1([h | t], {h, 5}, acc), do: do_rule1(t, {h, 5}, acc + 1) 27 | defp do_rule1([h | t], {h, n}, acc), do: do_rule1(t, {h, n + 1}, acc) 28 | defp do_rule1([h | t], {_, _}, acc), do: do_rule1(t, {h, 1}, acc) 29 | 30 | defp transform(matrix) do 31 | for e <- Enum.zip(matrix), do: Tuple.to_list(e) 32 | end 33 | 34 | @doc """ 35 | Check for 2x2 blocks. 36 | """ 37 | @spec rule2(EQRCode.Matrix.matrix()) :: integer 38 | def rule2(matrix) do 39 | z = tuple_size(matrix) - 2 40 | 41 | for i <- 0..z, 42 | j <- 0..z do 43 | EQRCode.Matrix.shape({i, j}, {2, 2}) 44 | |> Enum.map(&get(matrix, &1)) 45 | end 46 | |> Enum.reduce(0, &do_rule2/2) 47 | end 48 | 49 | defp do_rule2([1, 1, 1, 1], acc), do: acc + 3 50 | defp do_rule2([0, 0, 0, 0], acc), do: acc + 3 51 | defp do_rule2([_, _, _, _], acc), do: acc 52 | 53 | @doc """ 54 | Check for special blocks. 55 | """ 56 | @spec rule3(EQRCode.Matrix.matrix()) :: integer 57 | def rule3(matrix) do 58 | z = tuple_size(matrix) 59 | 60 | for i <- 0..(z - 1), 61 | j <- 0..(z - 11) do 62 | [{{i, j}, {11, 1}}, {{j, i}, {1, 11}}] 63 | |> Stream.map(fn {a, b} -> 64 | EQRCode.Matrix.shape(a, b) 65 | |> Enum.map(&get(matrix, &1)) 66 | end) 67 | |> Enum.map(&do_rule3/1) 68 | end 69 | |> List.flatten() 70 | |> Enum.sum() 71 | end 72 | 73 | defp do_rule3([1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0]), do: 40 74 | defp do_rule3([0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1]), do: 40 75 | defp do_rule3([_, _, _, _, _, _, _, _, _, _, _]), do: 0 76 | 77 | @doc """ 78 | Check for module's proportion. 79 | """ 80 | @spec rule4(EQRCode.Matrix.matrix()) :: integer 81 | def rule4(matrix) do 82 | m = tuple_size(matrix) 83 | 84 | black = 85 | Tuple.to_list(matrix) 86 | |> Enum.reduce(0, fn e, acc -> 87 | Tuple.to_list(e) 88 | |> Enum.reduce(acc, &do_rule4/2) 89 | end) 90 | 91 | div(abs(div(black * 100, m * m) - 50), 5) * 10 92 | end 93 | 94 | defp do_rule4(1, acc), do: acc + 1 95 | defp do_rule4(_, acc), do: acc 96 | 97 | defp get(matrix, {x, y}) do 98 | get_in(matrix, [Access.elem(x), Access.elem(y)]) 99 | end 100 | 101 | @doc """ 102 | The mask algorithm. 103 | """ 104 | @spec mask(integer, EQRCode.Matrix.coordinate()) :: 0 | 1 105 | def mask(0b000, {x, y}) when rem(x + y, 2) == 0, do: 1 106 | def mask(0b000, {_, _}), do: 0 107 | def mask(0b001, {x, _}) when rem(x, 2) == 0, do: 1 108 | def mask(0b001, {_, _}), do: 0 109 | def mask(0b010, {_, y}) when rem(y, 3) == 0, do: 1 110 | def mask(0b010, {_, _}), do: 0 111 | def mask(0b011, {x, y}) when rem(x + y, 3) == 0, do: 1 112 | def mask(0b011, {_, _}), do: 0 113 | def mask(0b100, {x, y}) when rem(div(x, 2) + div(y, 3), 2) == 0, do: 1 114 | def mask(0b100, {_, _}), do: 0 115 | def mask(0b101, {x, y}) when rem(x * y, 2) + rem(x * y, 3) == 0, do: 1 116 | def mask(0b101, {_, _}), do: 0 117 | def mask(0b110, {x, y}) when rem(rem(x * y, 2) + rem(x * y, 3), 2) == 0, do: 1 118 | def mask(0b110, {_, _}), do: 0 119 | def mask(0b111, {x, y}) when rem(rem(x + y, 2) + rem(x * y, 3), 2) == 0, do: 1 120 | def mask(0b111, {_, _}), do: 0 121 | end 122 | -------------------------------------------------------------------------------- /lib/eqrcode/encode.ex: -------------------------------------------------------------------------------- 1 | defmodule EQRCode.Encode do 2 | @moduledoc """ 3 | Data encoding in Byte Mode. 4 | """ 5 | 6 | alias EQRCode.SpecTable 7 | import Bitwise 8 | 9 | @error_correction_level SpecTable.error_correction_level() 10 | 11 | @pad <<236, 17>> 12 | @mask0 <<0x99999999999999666666666666669966666666659999999996699533333333332CCD332CCCCCCCCCCCCCCD333333333333332CCD332CCCCCCCCCCCCCCD333333333333332CCD332CCCCCCCCCCCCCCD333333333333332CCD332CCCCCCCCCCCCCCD333333333333332CCD332CCCCCCCCCCCCCCD33333333333333333332CCCCCCCCCD33333333::1072>> 13 | 14 | @doc """ 15 | Encode the binary. 16 | 17 | ## Examples 18 | 19 | iex> EQRCode.Encode.encode("hello world!", :l) 20 | {1, :l, [0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 21 | 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 22 | 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 23 | 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 24 | 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 25 | 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0]} 26 | 27 | """ 28 | @spec encode(binary, SpecTable.error_correction_level()) :: 29 | {SpecTable.version(), SpecTable.error_correction_level(), [0 | 1]} 30 | def encode(bin, error_correction_level) 31 | when error_correction_level in @error_correction_level do 32 | {:ok, version} = version(bin, error_correction_level) 33 | cci_len = SpecTable.character_count_indicator_bits(version, error_correction_level) 34 | mode = SpecTable.mode_indicator() 35 | 36 | encoded = 37 | [<>, <>, bin, <<0::4>>] 38 | |> Enum.flat_map(&bits/1) 39 | |> pad_bytes(version, error_correction_level) 40 | 41 | {version, error_correction_level, encoded} 42 | end 43 | 44 | # Encode the binary with custom pattern bits. 45 | @spec encode(binary, SpecTable.error_correction_level(), bitstring) :: 46 | {SpecTable.version(), SpecTable.error_correction_level(), [0 | 1]} 47 | def encode(bin, error_correction_level, bits) 48 | when error_correction_level in @error_correction_level do 49 | version = 5 50 | n = byte_size(bin) 51 | n1 = n + 2 52 | n2 = SpecTable.code_words_len(version, error_correction_level) - n1 53 | cci_len = SpecTable.character_count_indicator_bits(version, error_correction_level) 54 | mode = SpecTable.mode_indicator() 55 | <<_::binary-size(n1), mask::binary-size(n2), _::binary>> = @mask0 56 | 57 | encoded = 58 | <> 59 | |> bits() 60 | |> pad_bytes(version, error_correction_level) 61 | 62 | {version, error_correction_level, encoded} 63 | end 64 | 65 | defp xor(<<>>, _), do: <<>> 66 | defp xor(_, <<>>), do: <<>> 67 | 68 | defp xor(<>, <>) do 69 | <> 70 | end 71 | 72 | @doc """ 73 | Returns the lowest version for the given binary. 74 | 75 | ## Examples 76 | 77 | iex> EQRCode.Encode.version("hello world!", :l) 78 | {:ok, 1} 79 | 80 | """ 81 | @spec version(binary, SpecTable.error_correction_level()) :: 82 | {:error, :no_version_found} | {:ok, SpecTable.version()} 83 | def version(bin, error_correction_level) do 84 | byte_size(bin) 85 | |> SpecTable.find_version(error_correction_level) 86 | end 87 | 88 | @doc """ 89 | Returns bits for any binary data. 90 | 91 | ## Examples 92 | 93 | iex> EQRCode.Encode.bits(<<123, 4>>) 94 | [0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0] 95 | 96 | """ 97 | @spec bits(bitstring) :: [0 | 1] 98 | def bits(bin) do 99 | for <>, do: b 100 | end 101 | 102 | defp pad_bytes(list, version, error_correction_level) do 103 | n = SpecTable.code_words_len(version, error_correction_level) * 8 - length(list) 104 | 105 | Enum.concat( 106 | list, 107 | Stream.cycle(bits(@pad)) 108 | |> Stream.take(n) 109 | ) 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /lib/eqrcode/svg.ex: -------------------------------------------------------------------------------- 1 | defmodule EQRCode.SVG do 2 | @moduledoc """ 3 | Render the QR Code matrix in SVG format 4 | """ 5 | 6 | alias EQRCode.Matrix 7 | 8 | @doc """ 9 | Returns the SVG format of the QR Code. 10 | 11 | ## Options 12 | 13 | You can specify the following attributes of the QR code: 14 | 15 | * `:background_color` - In hexadecimal format or `:transparent`. The default is `#FFF` 16 | 17 | * `:color` - In hexadecimal format. The default is `#000` 18 | 19 | * `:shape` - Only `square` or `circle`. The default is `square` 20 | 21 | * `:width` - The width of the QR code in pixel. Without the width attribute, 22 | the QR code size will be dynamically generated based on the input string. 23 | 24 | * `:viewbox` - When set to `true`, the SVG element will specify its height and 25 | width using `viewBox`, instead of explicit `height` and `width` tags. 26 | 27 | * `:id` - An id to add to the svg 28 | 29 | * `:class` - HTML classes to apply to the svg 30 | 31 | Default options are `[color: "#000", shape: "square", background_color: "#FFF"]`. 32 | 33 | ## Examples 34 | 35 | qr_code_content 36 | |> EQRCode.encode() 37 | |> EQRCode.svg(color: "#cc6600", shape: "circle", width: 300) 38 | 39 | """ 40 | @spec svg(Matrix.t(), map() | Keyword.t()) :: String.t() 41 | def svg(m, options \\ []) 42 | 43 | def svg(%Matrix{} = m, options) when is_map(options) do 44 | svg(m, Map.to_list(options)) 45 | end 46 | 47 | def svg(%Matrix{matrix: matrix} = m, options) when is_list(options) do 48 | matrix_size = Matrix.size(m) 49 | svg_options = options |> Map.new() |> set_svg_options(matrix_size) 50 | dimension = matrix_size * svg_options[:module_size] 51 | xml_tag = ~s() 52 | 53 | attrs = 54 | [ 55 | {:version, "1.1"}, 56 | {:xmlns, "http://www.w3.org/2000/svg"}, 57 | {:"xmlns:xlink", "http://www.w3.org/1999/xlink"}, 58 | {:"xmlns:ev", "http://www.w3.org/2001/xml-events"}, 59 | {:viewBox, "0 0 #{matrix_size} #{matrix_size}"}, 60 | {:"shape-rendering", "crispEdges"}, 61 | {:style, "background-color: #{svg_options[:background_color]}"} 62 | | Keyword.take(options, [:id, :class]) 63 | ] 64 | |> then(fn attrs -> 65 | if Keyword.get(options, :viewbox, false) do 66 | attrs 67 | else 68 | [{:width, dimension}, {:height, dimension} | attrs] 69 | end 70 | end) 71 | |> Enum.map(fn {key, val} -> ~s(#{key}="#{val}") end) 72 | |> Enum.join(" ") 73 | 74 | open_tag = 75 | ~s() 76 | 77 | close_tag = ~s() 78 | 79 | result = 80 | Tuple.to_list(matrix) 81 | |> Stream.with_index() 82 | |> Stream.map(fn {row, row_num} -> 83 | Tuple.to_list(row) 84 | |> format_row_as_svg(row_num, svg_options) 85 | end) 86 | |> Enum.to_list() 87 | 88 | Enum.join([xml_tag, open_tag, result, close_tag], "\n") 89 | end 90 | 91 | defp set_svg_options(options, matrix_size) do 92 | options 93 | |> Map.put_new(:background_color, "#FFF") 94 | |> Map.put_new(:color, "#000") 95 | |> set_module_size(matrix_size) 96 | |> Map.put_new(:shape, "rectangle") 97 | |> Map.put_new(:size, matrix_size) 98 | end 99 | 100 | defp set_module_size(%{width: width} = options, matrix_size) when is_integer(width) do 101 | options 102 | |> Map.put_new(:module_size, width / matrix_size) 103 | end 104 | 105 | defp set_module_size(%{width: width} = options, matrix_size) when is_binary(width) do 106 | options 107 | |> Map.put_new(:module_size, String.to_integer(width) / matrix_size) 108 | end 109 | 110 | defp set_module_size(options, _matrix_size) do 111 | options 112 | |> Map.put_new(:module_size, 11) 113 | end 114 | 115 | defp format_row_as_svg(row_matrix, row_num, svg_options) do 116 | row_matrix 117 | |> Stream.with_index() 118 | |> Stream.map(fn {col, col_num} -> 119 | substitute(col, row_num, col_num, svg_options) 120 | end) 121 | |> Enum.to_list() 122 | end 123 | 124 | defp substitute(data, row_num, col_num, %{background_color: background_color}) 125 | when is_nil(data) or data == 0 do 126 | %{} 127 | |> Map.put(:height, 1) 128 | |> Map.put(:style, "fill: #{background_color};") 129 | |> Map.put(:width, 1) 130 | |> Map.put(:x, col_num) 131 | |> Map.put(:y, row_num) 132 | |> draw_rect 133 | end 134 | 135 | # This pattern match ensures that the QR Codes positional markers are drawn 136 | # as rectangles, regardless of the shape 137 | defp substitute(1, row_num, col_num, %{color: color, size: size}) 138 | when (row_num <= 8 and col_num <= 8) or 139 | (row_num >= size - 9 and col_num <= 8) or 140 | (row_num <= 8 and col_num >= size - 9) do 141 | %{} 142 | |> Map.put(:height, 1) 143 | |> Map.put(:style, "fill: #{color};") 144 | |> Map.put(:width, 1) 145 | |> Map.put(:x, col_num) 146 | |> Map.put(:y, row_num) 147 | |> draw_rect 148 | end 149 | 150 | defp substitute(1, row_num, col_num, %{color: color, shape: "circle"}) do 151 | radius = 0.5 152 | 153 | %{} 154 | |> Map.put(:cx, col_num + radius) 155 | |> Map.put(:cy, row_num + radius) 156 | |> Map.put(:r, radius) 157 | |> Map.put(:style, "fill: #{color};") 158 | |> draw_circle 159 | end 160 | 161 | defp substitute(1, row_num, col_num, %{color: color}) do 162 | %{} 163 | |> Map.put(:height, 1) 164 | |> Map.put(:style, "fill: #{color};") 165 | |> Map.put(:width, 1) 166 | |> Map.put(:x, col_num) 167 | |> Map.put(:y, row_num) 168 | |> draw_rect 169 | end 170 | 171 | defp draw_rect(attribute_map) do 172 | attributes = get_attributes(attribute_map) 173 | ~s() 174 | end 175 | 176 | defp draw_circle(attribute_map) do 177 | attributes = get_attributes(attribute_map) 178 | ~s() 179 | end 180 | 181 | defp get_attributes(attribute_map) do 182 | attribute_map 183 | |> Enum.map(fn {key, value} -> ~s(#{key}="#{value}") end) 184 | |> Enum.join(" ") 185 | end 186 | end 187 | -------------------------------------------------------------------------------- /lib/eqrcode/reed_solomon.ex: -------------------------------------------------------------------------------- 1 | defmodule EQRCode.ReedSolomon do 2 | @moduledoc false 3 | 4 | import Bitwise 5 | alias EQRCode.SpecTable 6 | 7 | @format_generator_polynomial 0b10100110111 8 | @format_mask 0b101010000010010 9 | 10 | @doc """ 11 | Returns generator polynomials in alpha exponent for given error code length. 12 | 13 | ## Examples 14 | 15 | iex> EQRCode.ReedSolomon.generator_polynomial(10) 16 | [0, 251, 67, 46, 61, 118, 70, 64, 94, 32, 45] 17 | 18 | """ 19 | def generator_polynomial(error_code_len) 20 | 21 | Stream.iterate({[0, 0], 1}, fn {e, i} -> 22 | {rest, last} = 23 | Stream.map(e, &rem(&1 + i, 255)) 24 | |> Enum.split(i) 25 | 26 | rest = 27 | Stream.zip(rest, tl(e)) 28 | |> Enum.map(fn {x, y} -> 29 | bxor(EQRCode.GaloisField.to_i(x), EQRCode.GaloisField.to_i(y)) 30 | |> EQRCode.GaloisField.to_a() 31 | end) 32 | 33 | {[0] ++ rest ++ last, i + 1} 34 | end) 35 | |> Stream.take(32) 36 | |> Enum.each(fn {e, i} -> 37 | def generator_polynomial(unquote(i)), do: unquote(e) 38 | end) 39 | 40 | @doc """ 41 | Reed-Solomon encode. 42 | 43 | # Examples 44 | 45 | iex> EQRCode.ReedSolomon.encode(EQRCode.Encode.encode("hello world!", :l)) 46 | {1, :l, <<64, 198, 134, 86, 198, 198, 242, 7, 118, 247, 38, 198, 66, 16, 47 | 236, 17, 236, 17, 236, 45, 99, 25, 84, 35, 114, 46>>} 48 | 49 | """ 50 | @spec encode({SpecTable.version(), SpecTable.error_correction_level(), [0 | 1]}) :: 51 | {SpecTable.version(), SpecTable.error_correction_level(), bitstring()} 52 | def encode({version, error_correction_level, message}) do 53 | remainder_len = SpecTable.remainer(version) 54 | 55 | data = 56 | Enum.chunk_every(message, 8) 57 | |> Enum.map(&String.to_integer(Enum.join(&1), 2)) 58 | |> chunk_and_devide_groups(version, error_correction_level) 59 | |> Enum.unzip() 60 | |> interleave() 61 | |> :binary.list_to_bin() 62 | 63 | {version, error_correction_level, <>} 64 | end 65 | 66 | def count(d) when is_map(d), do: count(d |> Map.values()) 67 | def count(d) when is_tuple(d), do: count(d |> Tuple.to_list()) 68 | def count(d) when is_list(d), do: Enum.reduce(d, 0, fn e, acc -> acc + count(e) end) 69 | def count(_), do: 1 70 | 71 | def print_count(d) do 72 | count(d) |> IO.inspect(label: "#{__ENV__.file}:#{__ENV__.line}") 73 | d 74 | end 75 | 76 | def chunk_and_devide_groups(enum, version, error_correction_level) do 77 | group1_data_code_len = SpecTable.group1_codewords_per_block(version, error_correction_level) 78 | group1_block_len = SpecTable.group1_block_len(version, error_correction_level) 79 | 80 | group2_data_code_len = SpecTable.group2_codewords_per_block(version, error_correction_level) 81 | group2_block_len = SpecTable.group2_block_len(version, error_correction_level) 82 | 83 | error_code_len = SpecTable.ec_codewords_per_block(version, error_correction_level) 84 | gen_poly = generator_polynomial(error_code_len) 85 | 86 | {group1, enum} = group(enum, group1_block_len, group1_data_code_len) 87 | {group2, _enum} = group(enum, group2_block_len, group2_data_code_len) 88 | 89 | Enum.concat( 90 | group1 91 | |> Enum.map(&group_devisions(&1, gen_poly, group1_data_code_len)), 92 | group2 93 | |> Enum.map(&group_devisions(&1, gen_poly, group2_data_code_len)) 94 | ) 95 | end 96 | 97 | defp group(enum, 0, _data_code_len) do 98 | {[], enum} 99 | end 100 | 101 | defp group(enum, block_len, data_code_len) do 102 | { 103 | Enum.take(enum, block_len * data_code_len) 104 | |> Enum.chunk_every(data_code_len), 105 | Enum.drop(enum, block_len * data_code_len) 106 | } 107 | end 108 | 109 | defp group_devisions(group, gen_poly, data_code_len) do 110 | {group, polynomial_division(group, gen_poly, data_code_len)} 111 | end 112 | 113 | def interleave(data, acc \\ []) 114 | 115 | def interleave({[], error_code_sec}, acc), 116 | do: interleave_sec(error_code_sec, acc) |> Enum.reverse() 117 | 118 | def interleave({code_sec, ec}, acc), do: interleave({[], ec}, interleave_sec(code_sec, acc)) 119 | 120 | @doc """ 121 | 122 | ## Examples 123 | 124 | iex> EQRCode.ReedSolomon.interleave_sec([[1, 2], [6, 7], [3, 4, 5], [8, 9, 10]], []) |> Enum.reverse() 125 | [1, 6, 3, 8, 2, 7, 4, 9, 5, 10] 126 | 127 | iex> EQRCode.ReedSolomon.interleave_sec([[]], []) 128 | [] 129 | 130 | """ 131 | def interleave_sec([], acc), do: acc 132 | 133 | def interleave_sec(data, acc) do 134 | for [h | tail] <- data do 135 | {h, tail} 136 | end 137 | |> Enum.unzip() 138 | |> case do 139 | {l, rest} -> interleave_sec(rest, Enum.concat(Enum.reverse(l), acc)) 140 | end 141 | end 142 | 143 | @doc """ 144 | Perform the polynomial division. 145 | 146 | # Examples 147 | 148 | iex> EQRCode.ReedSolomon.polynomial_division([64, 198, 134, 86, 198, 198, 242, 7, 118, 247, 38, 198, 66, 16, 236, 17, 236, 17, 236], [0, 87, 229, 146, 149, 238, 102, 21], 19) 149 | [45, 99, 25, 84, 35, 114, 46] 150 | 151 | """ 152 | @spec polynomial_division(list, list, integer) :: list 153 | def polynomial_division(msg_poly, gen_poly, data_code_len) do 154 | Stream.iterate(msg_poly, &do_polynomial_division(&1, gen_poly)) 155 | |> Enum.at(data_code_len) 156 | end 157 | 158 | defp do_polynomial_division([0 | t], _), do: t 159 | defp do_polynomial_division([], _), do: [] 160 | 161 | defp do_polynomial_division([h | _] = msg, gen_poly) do 162 | Enum.map(gen_poly, &rem(&1 + EQRCode.GaloisField.to_a(h), 255)) 163 | |> Enum.map(&EQRCode.GaloisField.to_i/1) 164 | |> pad_zip(msg) 165 | |> Enum.map(fn {a, b} -> bxor(a, b) end) 166 | |> tl() 167 | end 168 | 169 | # def my_len(v) when is_tuple(v), do: v |> Tuple.to_list() |> Enum.map(&my_len/1) 170 | # def my_len(v = [p|_]) when is_list(p), do: v |> Enum.map(&my_len/1) 171 | # def my_len(v = [p|_]) when is_tuple(p), do: v |> Enum.map(&my_len/1) 172 | # def my_len(v), do: length(v) 173 | 174 | # def len_inspect(v) do 175 | # my_len(v) |> IO.inspect(label: "#{__ENV__.file}:#{__ENV__.line}") 176 | # v 177 | # |> IO.inspect(label: "#{__ENV__.file}:#{__ENV__.line}") 178 | # end 179 | 180 | defp pad_zip(left, right) do 181 | [short, long] = Enum.sort_by([left, right], &length/1) 182 | 183 | Stream.concat(short, Stream.cycle([0])) 184 | |> Stream.zip(long) 185 | end 186 | 187 | def bch_encode(data) do 188 | bch = do_bch_encode(EQRCode.Encode.bits(<>)) 189 | 190 | (EQRCode.Encode.bits(data) ++ bch) 191 | |> Stream.zip(EQRCode.Encode.bits(<<@format_mask::15>>)) 192 | |> Enum.map(fn {a, b} -> bxor(a, b) end) 193 | end 194 | 195 | defp do_bch_encode(list) when length(list) == 10, do: list 196 | defp do_bch_encode([0 | t]), do: do_bch_encode(t) 197 | 198 | defp do_bch_encode(list) do 199 | EQRCode.Encode.bits(<<@format_generator_polynomial::11>>) 200 | |> Stream.concat(Stream.cycle([0])) 201 | |> Stream.zip(list) 202 | |> Enum.map(fn {a, b} -> bxor(a, b) end) 203 | |> do_bch_encode() 204 | end 205 | end 206 | -------------------------------------------------------------------------------- /lib/eqrcode/matrix.ex: -------------------------------------------------------------------------------- 1 | defmodule EQRCode.Matrix do 2 | @moduledoc false 3 | 4 | alias EQRCode.SpecTable 5 | 6 | import Bitwise 7 | 8 | @derive {Inspect, only: [:version, :error_correction_level, :modules, :mask]} 9 | defstruct [:version, :error_correction_level, :modules, :mask, :matrix] 10 | 11 | @type coordinate :: {non_neg_integer(), non_neg_integer()} 12 | @type matrix :: term 13 | @type t :: %__MODULE__{ 14 | version: SpecTable.version(), 15 | error_correction_level: SpecTable.error_correction_level(), 16 | modules: integer, 17 | matrix: matrix 18 | } 19 | 20 | @alignments %{ 21 | 1 => [], 22 | 2 => [6, 18], 23 | 3 => [6, 22], 24 | 4 => [6, 26], 25 | 5 => [6, 30], 26 | 6 => [6, 34], 27 | 7 => [6, 22, 38], 28 | 8 => [6, 24, 42], 29 | 9 => [6, 26, 46], 30 | 10 => [6, 28, 50], 31 | 11 => [6, 30, 54], 32 | 12 => [6, 32, 58], 33 | 13 => [6, 34, 62], 34 | 14 => [6, 26, 46, 66], 35 | 15 => [6, 26, 48, 70], 36 | 16 => [6, 26, 50, 74], 37 | 17 => [6, 30, 54, 78], 38 | 18 => [6, 30, 56, 82], 39 | 19 => [6, 30, 58, 86], 40 | 20 => [6, 34, 62, 90], 41 | 21 => [6, 28, 50, 72, 94], 42 | 22 => [6, 26, 50, 74, 98], 43 | 23 => [6, 30, 54, 78, 102], 44 | 24 => [6, 28, 54, 80, 106], 45 | 25 => [6, 32, 58, 84, 110], 46 | 26 => [6, 30, 58, 86, 114], 47 | 27 => [6, 34, 62, 90, 118], 48 | 28 => [6, 26, 50, 74, 98, 122], 49 | 29 => [6, 30, 54, 78, 102, 126], 50 | 30 => [6, 26, 52, 78, 104, 130], 51 | 31 => [6, 30, 56, 82, 108, 134], 52 | 32 => [6, 34, 60, 86, 112, 138], 53 | 33 => [6, 30, 58, 86, 114, 142], 54 | 34 => [6, 34, 62, 90, 118, 146], 55 | 35 => [6, 30, 54, 78, 102, 126, 150], 56 | 36 => [6, 24, 50, 76, 102, 128, 154], 57 | 37 => [6, 28, 54, 80, 106, 132, 158], 58 | 38 => [6, 32, 58, 84, 110, 136, 162], 59 | 39 => [6, 26, 54, 82, 110, 138, 166], 60 | 40 => [6, 30, 58, 86, 114, 142, 170] 61 | } 62 | 63 | @finder_pattern Code.eval_string(""" 64 | [ 65 | 1, 1, 1, 1, 1, 1, 1, 66 | 1, 0, 0, 0, 0, 0, 1, 67 | 1, 0, 1, 1, 1, 0, 1, 68 | 1, 0, 1, 1, 1, 0, 1, 69 | 1, 0, 1, 1, 1, 0, 1, 70 | 1, 0, 0, 0, 0, 0, 1, 71 | 1, 1, 1, 1, 1, 1, 1 72 | ] 73 | """) 74 | |> elem(0) 75 | 76 | @alignment_pattern Code.eval_string(""" 77 | [ 78 | 1, 1, 1, 1, 1, 79 | 1, 0, 0, 0, 1, 80 | 1, 0, 1, 0, 1, 81 | 1, 0, 0, 0, 1, 82 | 1, 1, 1, 1, 1, 83 | ] 84 | """) 85 | |> elem(0) 86 | 87 | @doc """ 88 | Initialize the matrix. 89 | """ 90 | @spec new(SpecTable.version(), SpecTable.error_correction_level()) :: t 91 | def new(version, error_correction_level \\ :l) do 92 | modules = (version - 1) * 4 + 21 93 | 94 | matrix = 95 | Tuple.duplicate(nil, modules) 96 | |> Tuple.duplicate(modules) 97 | 98 | %__MODULE__{ 99 | version: version, 100 | error_correction_level: error_correction_level, 101 | modules: modules, 102 | matrix: matrix 103 | } 104 | end 105 | 106 | @doc """ 107 | Draw the finder patterns, three at a time. 108 | """ 109 | @spec draw_finder_patterns(t) :: t 110 | def draw_finder_patterns(%__MODULE__{matrix: matrix, modules: modules} = m) do 111 | z = modules - 7 112 | 113 | matrix = 114 | [{0, 0}, {z, 0}, {0, z}] 115 | |> Stream.flat_map(&shape(&1, {7, 7})) 116 | |> Stream.zip(Stream.cycle(@finder_pattern)) 117 | |> Enum.reduce(matrix, fn {coordinate, v}, acc -> 118 | update(acc, coordinate, v) 119 | end) 120 | 121 | %{m | matrix: matrix} 122 | end 123 | 124 | @doc """ 125 | Draw the seperators. 126 | """ 127 | @spec draw_seperators(t) :: t 128 | def draw_seperators(%__MODULE__{matrix: matrix, modules: modules} = m) do 129 | z = modules - 8 130 | 131 | matrix = 132 | [ 133 | {{0, 7}, {1, 8}}, 134 | {{0, z}, {1, 8}}, 135 | {{7, z}, {8, 1}}, 136 | {{7, 0}, {8, 1}}, 137 | {{z, 0}, {8, 1}}, 138 | {{z, 7}, {1, 8}} 139 | ] 140 | |> Stream.flat_map(fn {a, b} -> shape(a, b) end) 141 | |> Enum.reduce(matrix, &update(&2, &1, 0)) 142 | 143 | %{m | matrix: matrix} 144 | end 145 | 146 | @doc """ 147 | Draw the alignment patterns. 148 | """ 149 | @spec draw_alignment_patterns(t) :: t 150 | def draw_alignment_patterns(%__MODULE__{matrix: matrix, version: version} = m) do 151 | matrix = 152 | for( 153 | x <- @alignments[version], 154 | y <- @alignments[version], 155 | do: {x, y} 156 | ) 157 | |> Stream.filter(&available?(matrix, &1)) 158 | |> Stream.map(fn {x, y} -> {x - 2, y - 2} end) 159 | |> Stream.flat_map(&shape(&1, {5, 5})) 160 | |> Stream.zip(Stream.cycle(@alignment_pattern)) 161 | |> Enum.reduce(matrix, fn {coordinate, v}, acc -> 162 | update(acc, coordinate, v) 163 | end) 164 | 165 | %{m | matrix: matrix} 166 | end 167 | 168 | @doc """ 169 | Draw the timing patterns. 170 | """ 171 | @spec draw_timing_patterns(t) :: t 172 | def draw_timing_patterns(%__MODULE__{matrix: matrix, modules: modules} = m) do 173 | z = modules - 13 174 | 175 | matrix = 176 | [{z, 1}, {1, z}] 177 | |> Stream.flat_map(&shape({6, 6}, &1)) 178 | |> Stream.zip(Stream.cycle([1, 0])) 179 | |> Enum.reduce(matrix, fn {coordinate, v}, acc -> 180 | update(acc, coordinate, v) 181 | end) 182 | 183 | %{m | matrix: matrix} 184 | end 185 | 186 | @doc """ 187 | Draw the dark module. 188 | """ 189 | @spec draw_dark_module(t) :: t 190 | def draw_dark_module(%__MODULE__{matrix: matrix, modules: modules} = m) do 191 | matrix = update(matrix, {modules - 8, 8}, 1) 192 | %{m | matrix: matrix} 193 | end 194 | 195 | @doc """ 196 | Draw the reserved format information areas. 197 | """ 198 | @spec draw_reserved_format_areas(t) :: t 199 | def draw_reserved_format_areas(%__MODULE__{matrix: matrix, modules: modules} = m) do 200 | z = modules - 8 201 | 202 | matrix = 203 | [{{0, 8}, {1, 9}}, {{z, 8}, {1, 8}}, {{8, 0}, {9, 1}}, {{8, z}, {8, 1}}] 204 | |> Stream.flat_map(fn {a, b} -> shape(a, b) end) 205 | |> Enum.reduce(matrix, &update(&2, &1, :reserved)) 206 | 207 | %{m | matrix: matrix} 208 | end 209 | 210 | @doc """ 211 | Draw the reserved version information areas. 212 | """ 213 | @spec draw_reserved_version_areas(t) :: t 214 | def draw_reserved_version_areas(%__MODULE__{version: version} = m) when version < 7, do: m 215 | 216 | def draw_reserved_version_areas(%__MODULE__{matrix: matrix, modules: modules} = m) do 217 | z = modules - 11 218 | 219 | matrix = 220 | [{{0, z}, {3, 6}}, {{z, 0}, {6, 3}}] 221 | |> Stream.flat_map(fn {a, b} -> shape(a, b) end) 222 | |> Enum.reduce(matrix, &update(&2, &1, :reserved)) 223 | 224 | %{m | matrix: matrix} 225 | end 226 | 227 | @doc """ 228 | Draw the data bits with mask. 229 | """ 230 | @spec draw_data_with_mask(t, binary) :: t 231 | def draw_data_with_mask(%__MODULE__{matrix: matrix, modules: modules} = m, data) do 232 | candidate = 233 | Stream.unfold(modules - 1, fn 234 | -1 -> nil 235 | 8 -> {8, 5} 236 | n -> {n, n - 2} 237 | end) 238 | |> Stream.zip(Stream.cycle([:up, :down])) 239 | |> Stream.flat_map(fn {z, path} -> path(path, {modules - 1, z}) end) 240 | |> Stream.filter(&available?(matrix, &1)) 241 | |> Stream.zip(EQRCode.Encode.bits(data)) 242 | 243 | {mask, _, matrix} = 244 | Stream.map(0b000..0b111, fn mask -> 245 | matrix = 246 | Enum.reduce(candidate, matrix, fn {coordinate, v}, acc -> 247 | update(acc, coordinate, bxor(v, EQRCode.Mask.mask(mask, coordinate))) 248 | end) 249 | 250 | {mask, EQRCode.Mask.score(matrix), matrix} 251 | end) 252 | |> Enum.min_by(&elem(&1, 1)) 253 | 254 | %{m | matrix: matrix, mask: mask} 255 | end 256 | 257 | @doc """ 258 | Draw the data bits with mask 0. 259 | """ 260 | @spec draw_data_with_mask0(t, binary) :: t 261 | def draw_data_with_mask0(%__MODULE__{matrix: matrix, modules: modules} = m, data) do 262 | matrix = 263 | Stream.unfold(modules - 1, fn 264 | -1 -> nil 265 | 8 -> {8, 5} 266 | n -> {n, n - 2} 267 | end) 268 | |> Stream.zip(Stream.cycle([:up, :down])) 269 | |> Stream.flat_map(fn {z, path} -> path(path, {modules - 1, z}) end) 270 | |> Stream.filter(&available?(matrix, &1)) 271 | |> Stream.zip(EQRCode.Encode.bits(data)) 272 | |> Enum.reduce(matrix, fn {coordinate, v}, acc -> 273 | update(acc, coordinate, bxor(v, EQRCode.Mask.mask(0, coordinate))) 274 | end) 275 | 276 | %{m | matrix: matrix, mask: 0} 277 | end 278 | 279 | defp path(:up, {x, y}), 280 | do: 281 | for( 282 | i <- x..0//-1, 283 | j <- y..(y - 1)//-1, 284 | do: {i, j} 285 | ) 286 | 287 | defp path(:down, {x, y}), 288 | do: 289 | for( 290 | i <- 0..x, 291 | j <- y..(y - 1)//-1, 292 | do: {i, j} 293 | ) 294 | 295 | @doc """ 296 | Fill the reserved format information areas. 297 | """ 298 | @spec draw_format_areas(t) :: t 299 | def draw_format_areas( 300 | %__MODULE__{matrix: matrix, modules: modules, mask: mask, error_correction_level: ecl} = m 301 | ) do 302 | ecc_l = SpecTable.error_corretion_bits(ecl) 303 | data = EQRCode.ReedSolomon.bch_encode(<>) 304 | 305 | matrix = 306 | [ 307 | {{8, 0}, {9, 1}}, 308 | {{7, 8}, {1, -6}}, 309 | {{modules - 1, 8}, {1, -6}}, 310 | {{8, modules - 8}, {8, 1}} 311 | ] 312 | |> Stream.flat_map(fn {a, b} -> shape(a, b) end) 313 | |> Stream.filter(&reserved?(matrix, &1)) 314 | |> Stream.zip(Stream.cycle(data)) 315 | |> Enum.reduce(matrix, fn {coordinate, v}, acc -> 316 | put(acc, coordinate, v) 317 | end) 318 | 319 | %{m | matrix: matrix} 320 | end 321 | 322 | @doc """ 323 | Fill the reserved version information areas. 324 | """ 325 | @spec draw_version_areas(t) :: t 326 | def draw_version_areas(%__MODULE__{version: version} = m) when version < 7, do: m 327 | 328 | def draw_version_areas(%__MODULE__{matrix: matrix, modules: modules, version: version} = m) do 329 | version_information_bits = SpecTable.version_information_bits(version) 330 | data = EQRCode.Encode.bits(<>) 331 | z = modules - 9 332 | 333 | matrix = 334 | [ 335 | {{z, 5}, {1, -1}}, 336 | {{z, 4}, {1, -1}}, 337 | {{z, 3}, {1, -1}}, 338 | {{z, 2}, {1, -1}}, 339 | {{z, 1}, {1, -1}}, 340 | {{z, 0}, {1, -1}}, 341 | {{5, z}, {-1, 1}}, 342 | {{4, z}, {-1, 1}}, 343 | {{3, z}, {-1, 1}}, 344 | {{2, z}, {-1, 1}}, 345 | {{1, z}, {-1, 1}}, 346 | {{0, z}, {-1, 1}} 347 | ] 348 | |> Stream.flat_map(fn {a, b} -> shape(a, b) end) 349 | |> Stream.filter(&reserved?(matrix, &1)) 350 | |> Stream.zip(Stream.cycle(data)) 351 | |> Enum.reduce(matrix, fn {coordinate, v}, acc -> 352 | put(acc, coordinate, v) 353 | end) 354 | 355 | %{m | matrix: matrix} 356 | end 357 | 358 | defp reserved?(matrix, {x, y}) do 359 | get_in(matrix, [Access.elem(x), Access.elem(y)]) == :reserved 360 | end 361 | 362 | defp put(matrix, {x, y}, value) do 363 | put_in(matrix, [Access.elem(x), Access.elem(y)], value) 364 | end 365 | 366 | @doc """ 367 | Draw the quite zone. 368 | """ 369 | @spec draw_quite_zone(t) :: t 370 | def draw_quite_zone(%__MODULE__{matrix: matrix, modules: modules} = m) do 371 | zone = Tuple.duplicate(0, modules + 4) 372 | 373 | matrix = 374 | Enum.reduce(0..(modules - 1), matrix, fn i, acc -> 375 | update_in(acc, [Access.elem(i)], fn row -> 376 | row_size = tuple_size(row) 377 | 378 | row 379 | |> Tuple.insert_at(0, 0) 380 | |> Tuple.insert_at(0, 0) 381 | |> Tuple.insert_at(row_size + 2, 0) 382 | |> Tuple.insert_at(row_size + 3, 0) 383 | end) 384 | end) 385 | |> Tuple.insert_at(0, zone) 386 | |> Tuple.insert_at(0, zone) 387 | 388 | matrix = Tuple.insert_at(matrix, tuple_size(matrix), zone) 389 | matrix = Tuple.insert_at(matrix, tuple_size(matrix), zone) 390 | 391 | %{m | matrix: matrix} 392 | end 393 | 394 | @doc """ 395 | Given the starting point {x, y} and {width, height} 396 | returns the coordinates of the shape. 397 | 398 | ## Examples 399 | 400 | iex> EQRCode.Matrix.shape({0, 0}, {3, 3}) 401 | [{0, 0}, {0, 1}, {0, 2}, 402 | {1, 0}, {1, 1}, {1, 2}, 403 | {2, 0}, {2, 1}, {2, 2}] 404 | 405 | """ 406 | @spec shape(coordinate, {integer, integer}) :: [coordinate] 407 | def shape({x, y}, {w, h}) do 408 | x_limit = x + h - 1 409 | y_limit = y + w - 1 410 | x_step = if x <= x_limit, do: 1, else: -1 411 | y_step = if y <= y_limit, do: 1, else: -1 412 | 413 | for i <- x..x_limit//x_step, 414 | j <- y..y_limit//y_step, 415 | do: {i, j} 416 | end 417 | 418 | defp update(matrix, {x, y}, value) do 419 | update_in(matrix, [Access.elem(x), Access.elem(y)], fn 420 | nil -> value 421 | val -> val 422 | end) 423 | end 424 | 425 | defp available?(matrix, {x, y}) do 426 | get_in(matrix, [Access.elem(x), Access.elem(y)]) == nil 427 | end 428 | 429 | @doc """ 430 | Get matrix size. 431 | """ 432 | @spec size(t()) :: integer() 433 | def size(%__MODULE__{matrix: matrix}) do 434 | matrix |> Tuple.to_list() |> Enum.count() 435 | end 436 | end 437 | -------------------------------------------------------------------------------- /lib/eqrcode/spec_table.ex: -------------------------------------------------------------------------------- 1 | defmodule EQRCode.SpecTable do 2 | @type error_correction_level :: :l | :m | :q | :h 3 | @type version :: 1..40 4 | @type mode :: :numeric | :alphanumeric | :byte | :kenji | :eci 5 | 6 | def error_correction_level(), do: [:l, :m, :q, :h] 7 | 8 | @mode [ 9 | numeric: 0b0001, 10 | alphanumeric: 0b0010, 11 | byte: 0b0100, 12 | kanji: 0b1000, 13 | eci: 0b0111 14 | ] 15 | 16 | @error_corretion_bits [ 17 | l: 0b01, 18 | m: 0b00, 19 | q: 0b11, 20 | h: 0b10 21 | ] 22 | 23 | @version_information_bits [ 24 | {7, 0b000111110010010100}, 25 | {8, 0b001000010110111100}, 26 | {9, 0b001001101010011001}, 27 | {10, 0b001010010011010011}, 28 | {11, 0b001011101111110110}, 29 | {12, 0b001100011101100010}, 30 | {13, 0b001101100001000111}, 31 | {14, 0b001110011000001101}, 32 | {15, 0b001111100100101000}, 33 | {16, 0b010000101101111000}, 34 | {17, 0b010001010001011101}, 35 | {18, 0b010010101000010111}, 36 | {19, 0b010011010100110010}, 37 | {20, 0b010100100110100110}, 38 | {21, 0b010101011010000011}, 39 | {22, 0b010110100011001001}, 40 | {23, 0b010111011111101100}, 41 | {24, 0b011000111011000100}, 42 | {25, 0b011001000111100001}, 43 | {26, 0b011010111110101011}, 44 | {27, 0b011011000010001110}, 45 | {28, 0b011100110000011010}, 46 | {29, 0b011101001100111111}, 47 | {30, 0b011110110101110101}, 48 | {31, 0b011111001001010000}, 49 | {32, 0b100000100111010101}, 50 | {33, 0b100001011011110000}, 51 | {34, 0b100010100010111010}, 52 | {35, 0b100011011110011111}, 53 | {36, 0b100100101100001011}, 54 | {37, 0b100101010000101110}, 55 | {38, 0b100110101001100100}, 56 | {39, 0b100111010101000001}, 57 | {40, 0b101000110001101001} 58 | ] 59 | 60 | @spec version_information_bits(version()) :: 1..1_114_111 61 | def version_information_bits(version) 62 | 63 | for {version, bits} <- @version_information_bits do 64 | def version_information_bits(unquote(version)), do: unquote(bits) 65 | end 66 | 67 | @spec error_corretion_bits(error_correction_level()) :: 1..3 68 | def error_corretion_bits(error_correction_level) 69 | 70 | for {level, bits} <- @error_corretion_bits do 71 | def error_corretion_bits(unquote(level)), do: unquote(bits) 72 | end 73 | 74 | def mode_indicator(mode \\ :byte) 75 | 76 | for {mode, mode_indictor} <- @mode do 77 | def mode_indicator(unquote(mode)), do: unquote(mode_indictor) 78 | end 79 | 80 | # {:version, :error_correction_level, :mode, :capacity, :character_count_indicator_bits} 81 | @table [ 82 | {1, :l, :numeric, 41, 10}, 83 | {1, :l, :alphanumeric, 25, 9}, 84 | {1, :l, :byte, 17, 8}, 85 | {1, :l, :kenji, 10, 8}, 86 | {1, :m, :numeric, 34, 10}, 87 | {1, :m, :alphanumeric, 20, 9}, 88 | {1, :m, :byte, 14, 8}, 89 | {1, :m, :kenji, 8, 8}, 90 | {1, :q, :numeric, 27, 10}, 91 | {1, :q, :alphanumeric, 16, 9}, 92 | {1, :q, :byte, 11, 8}, 93 | {1, :q, :kenji, 7, 8}, 94 | {1, :h, :numeric, 17, 10}, 95 | {1, :h, :alphanumeric, 10, 9}, 96 | {1, :h, :byte, 7, 8}, 97 | {1, :h, :kenji, 4, 8}, 98 | {2, :l, :numeric, 77, 10}, 99 | {2, :l, :alphanumeric, 47, 9}, 100 | {2, :l, :byte, 32, 8}, 101 | {2, :l, :kenji, 20, 8}, 102 | {2, :m, :numeric, 63, 10}, 103 | {2, :m, :alphanumeric, 38, 9}, 104 | {2, :m, :byte, 26, 8}, 105 | {2, :m, :kenji, 16, 8}, 106 | {2, :q, :numeric, 48, 10}, 107 | {2, :q, :alphanumeric, 29, 9}, 108 | {2, :q, :byte, 20, 8}, 109 | {2, :q, :kenji, 12, 8}, 110 | {2, :h, :numeric, 34, 10}, 111 | {2, :h, :alphanumeric, 20, 9}, 112 | {2, :h, :byte, 14, 8}, 113 | {2, :h, :kenji, 8, 8}, 114 | {3, :l, :numeric, 127, 10}, 115 | {3, :l, :alphanumeric, 77, 9}, 116 | {3, :l, :byte, 53, 8}, 117 | {3, :l, :kenji, 32, 8}, 118 | {3, :m, :numeric, 101, 10}, 119 | {3, :m, :alphanumeric, 61, 9}, 120 | {3, :m, :byte, 42, 8}, 121 | {3, :m, :kenji, 26, 8}, 122 | {3, :q, :numeric, 77, 10}, 123 | {3, :q, :alphanumeric, 47, 9}, 124 | {3, :q, :byte, 32, 8}, 125 | {3, :q, :kenji, 20, 8}, 126 | {3, :h, :numeric, 58, 10}, 127 | {3, :h, :alphanumeric, 35, 9}, 128 | {3, :h, :byte, 24, 8}, 129 | {3, :h, :kenji, 15, 8}, 130 | {4, :l, :numeric, 187, 10}, 131 | {4, :l, :alphanumeric, 114, 9}, 132 | {4, :l, :byte, 78, 8}, 133 | {4, :l, :kenji, 48, 8}, 134 | {4, :m, :numeric, 149, 10}, 135 | {4, :m, :alphanumeric, 90, 9}, 136 | {4, :m, :byte, 62, 8}, 137 | {4, :m, :kenji, 38, 8}, 138 | {4, :q, :numeric, 111, 10}, 139 | {4, :q, :alphanumeric, 67, 9}, 140 | {4, :q, :byte, 46, 8}, 141 | {4, :q, :kenji, 28, 8}, 142 | {4, :h, :numeric, 82, 10}, 143 | {4, :h, :alphanumeric, 50, 9}, 144 | {4, :h, :byte, 34, 8}, 145 | {4, :h, :kenji, 21, 8}, 146 | {5, :l, :numeric, 255, 10}, 147 | {5, :l, :alphanumeric, 154, 9}, 148 | {5, :l, :byte, 106, 8}, 149 | {5, :l, :kenji, 65, 8}, 150 | {5, :m, :numeric, 202, 10}, 151 | {5, :m, :alphanumeric, 122, 9}, 152 | {5, :m, :byte, 84, 8}, 153 | {5, :m, :kenji, 52, 8}, 154 | {5, :q, :numeric, 144, 10}, 155 | {5, :q, :alphanumeric, 87, 9}, 156 | {5, :q, :byte, 60, 8}, 157 | {5, :q, :kenji, 37, 8}, 158 | {5, :h, :numeric, 106, 10}, 159 | {5, :h, :alphanumeric, 64, 9}, 160 | {5, :h, :byte, 44, 8}, 161 | {5, :h, :kenji, 27, 8}, 162 | {6, :l, :numeric, 322, 10}, 163 | {6, :l, :alphanumeric, 195, 9}, 164 | {6, :l, :byte, 134, 8}, 165 | {6, :l, :kenji, 82, 8}, 166 | {6, :m, :numeric, 255, 10}, 167 | {6, :m, :alphanumeric, 154, 9}, 168 | {6, :m, :byte, 106, 8}, 169 | {6, :m, :kenji, 65, 8}, 170 | {6, :q, :numeric, 178, 10}, 171 | {6, :q, :alphanumeric, 108, 9}, 172 | {6, :q, :byte, 74, 8}, 173 | {6, :q, :kenji, 45, 8}, 174 | {6, :h, :numeric, 139, 10}, 175 | {6, :h, :alphanumeric, 84, 9}, 176 | {6, :h, :byte, 58, 8}, 177 | {6, :h, :kenji, 36, 8}, 178 | {7, :l, :numeric, 370, 10}, 179 | {7, :l, :alphanumeric, 224, 9}, 180 | {7, :l, :byte, 154, 8}, 181 | {7, :l, :kenji, 95, 8}, 182 | {7, :m, :numeric, 293, 10}, 183 | {7, :m, :alphanumeric, 178, 9}, 184 | {7, :m, :byte, 122, 8}, 185 | {7, :m, :kenji, 75, 8}, 186 | {7, :q, :numeric, 207, 10}, 187 | {7, :q, :alphanumeric, 125, 9}, 188 | {7, :q, :byte, 86, 8}, 189 | {7, :q, :kenji, 53, 8}, 190 | {7, :h, :numeric, 154, 10}, 191 | {7, :h, :alphanumeric, 93, 9}, 192 | {7, :h, :byte, 64, 8}, 193 | {7, :h, :kenji, 39, 8}, 194 | {8, :l, :numeric, 461, 10}, 195 | {8, :l, :alphanumeric, 279, 9}, 196 | {8, :l, :byte, 192, 8}, 197 | {8, :l, :kenji, 118, 8}, 198 | {8, :m, :numeric, 365, 10}, 199 | {8, :m, :alphanumeric, 221, 9}, 200 | {8, :m, :byte, 152, 8}, 201 | {8, :m, :kenji, 93, 8}, 202 | {8, :q, :numeric, 259, 10}, 203 | {8, :q, :alphanumeric, 157, 9}, 204 | {8, :q, :byte, 108, 8}, 205 | {8, :q, :kenji, 66, 8}, 206 | {8, :h, :numeric, 202, 10}, 207 | {8, :h, :alphanumeric, 122, 9}, 208 | {8, :h, :byte, 84, 8}, 209 | {8, :h, :kenji, 52, 8}, 210 | {9, :l, :numeric, 552, 10}, 211 | {9, :l, :alphanumeric, 335, 9}, 212 | {9, :l, :byte, 230, 8}, 213 | {9, :l, :kenji, 141, 8}, 214 | {9, :m, :numeric, 432, 10}, 215 | {9, :m, :alphanumeric, 262, 9}, 216 | {9, :m, :byte, 180, 8}, 217 | {9, :m, :kenji, 111, 8}, 218 | {9, :q, :numeric, 312, 10}, 219 | {9, :q, :alphanumeric, 189, 9}, 220 | {9, :q, :byte, 130, 8}, 221 | {9, :q, :kenji, 80, 8}, 222 | {9, :h, :numeric, 235, 10}, 223 | {9, :h, :alphanumeric, 143, 9}, 224 | {9, :h, :byte, 98, 8}, 225 | {9, :h, :kenji, 60, 8}, 226 | {10, :l, :numeric, 652, 12}, 227 | {10, :l, :alphanumeric, 395, 11}, 228 | {10, :l, :byte, 271, 16}, 229 | {10, :l, :kenji, 167, 10}, 230 | {10, :m, :numeric, 513, 12}, 231 | {10, :m, :alphanumeric, 311, 11}, 232 | {10, :m, :byte, 213, 16}, 233 | {10, :m, :kenji, 131, 10}, 234 | {10, :q, :numeric, 364, 12}, 235 | {10, :q, :alphanumeric, 221, 11}, 236 | {10, :q, :byte, 151, 16}, 237 | {10, :q, :kenji, 93, 10}, 238 | {10, :h, :numeric, 288, 12}, 239 | {10, :h, :alphanumeric, 174, 11}, 240 | {10, :h, :byte, 119, 16}, 241 | {10, :h, :kenji, 74, 10}, 242 | {11, :l, :numeric, 772, 12}, 243 | {11, :l, :alphanumeric, 468, 11}, 244 | {11, :l, :byte, 321, 16}, 245 | {11, :l, :kenji, 198, 10}, 246 | {11, :m, :numeric, 604, 12}, 247 | {11, :m, :alphanumeric, 366, 11}, 248 | {11, :m, :byte, 251, 16}, 249 | {11, :m, :kenji, 155, 10}, 250 | {11, :q, :numeric, 427, 12}, 251 | {11, :q, :alphanumeric, 259, 11}, 252 | {11, :q, :byte, 177, 16}, 253 | {11, :q, :kenji, 109, 10}, 254 | {11, :h, :numeric, 331, 12}, 255 | {11, :h, :alphanumeric, 200, 11}, 256 | {11, :h, :byte, 137, 16}, 257 | {11, :h, :kenji, 85, 10}, 258 | {12, :l, :numeric, 883, 12}, 259 | {12, :l, :alphanumeric, 535, 11}, 260 | {12, :l, :byte, 367, 16}, 261 | {12, :l, :kenji, 226, 10}, 262 | {12, :m, :numeric, 691, 12}, 263 | {12, :m, :alphanumeric, 419, 11}, 264 | {12, :m, :byte, 287, 16}, 265 | {12, :m, :kenji, 177, 10}, 266 | {12, :q, :numeric, 489, 12}, 267 | {12, :q, :alphanumeric, 296, 11}, 268 | {12, :q, :byte, 203, 16}, 269 | {12, :q, :kenji, 125, 10}, 270 | {12, :h, :numeric, 374, 12}, 271 | {12, :h, :alphanumeric, 227, 11}, 272 | {12, :h, :byte, 155, 16}, 273 | {12, :h, :kenji, 96, 10}, 274 | {13, :l, :numeric, 1022, 12}, 275 | {13, :l, :alphanumeric, 619, 11}, 276 | {13, :l, :byte, 425, 16}, 277 | {13, :l, :kenji, 262, 10}, 278 | {13, :m, :numeric, 796, 12}, 279 | {13, :m, :alphanumeric, 483, 11}, 280 | {13, :m, :byte, 331, 16}, 281 | {13, :m, :kenji, 204, 10}, 282 | {13, :q, :numeric, 580, 12}, 283 | {13, :q, :alphanumeric, 352, 11}, 284 | {13, :q, :byte, 241, 16}, 285 | {13, :q, :kenji, 149, 10}, 286 | {13, :h, :numeric, 427, 12}, 287 | {13, :h, :alphanumeric, 259, 11}, 288 | {13, :h, :byte, 177, 16}, 289 | {13, :h, :kenji, 109, 10}, 290 | {14, :l, :numeric, 1101, 12}, 291 | {14, :l, :alphanumeric, 667, 11}, 292 | {14, :l, :byte, 458, 16}, 293 | {14, :l, :kenji, 282, 10}, 294 | {14, :m, :numeric, 871, 12}, 295 | {14, :m, :alphanumeric, 528, 11}, 296 | {14, :m, :byte, 362, 16}, 297 | {14, :m, :kenji, 223, 10}, 298 | {14, :q, :numeric, 621, 12}, 299 | {14, :q, :alphanumeric, 376, 11}, 300 | {14, :q, :byte, 258, 16}, 301 | {14, :q, :kenji, 159, 10}, 302 | {14, :h, :numeric, 468, 12}, 303 | {14, :h, :alphanumeric, 283, 11}, 304 | {14, :h, :byte, 194, 16}, 305 | {14, :h, :kenji, 120, 10}, 306 | {15, :l, :numeric, 1250, 12}, 307 | {15, :l, :alphanumeric, 758, 11}, 308 | {15, :l, :byte, 520, 16}, 309 | {15, :l, :kenji, 320, 10}, 310 | {15, :m, :numeric, 991, 12}, 311 | {15, :m, :alphanumeric, 600, 11}, 312 | {15, :m, :byte, 412, 16}, 313 | {15, :m, :kenji, 254, 10}, 314 | {15, :q, :numeric, 703, 12}, 315 | {15, :q, :alphanumeric, 426, 11}, 316 | {15, :q, :byte, 292, 16}, 317 | {15, :q, :kenji, 180, 10}, 318 | {15, :h, :numeric, 530, 12}, 319 | {15, :h, :alphanumeric, 321, 11}, 320 | {15, :h, :byte, 220, 16}, 321 | {15, :h, :kenji, 136, 10}, 322 | {16, :l, :numeric, 1408, 12}, 323 | {16, :l, :alphanumeric, 854, 11}, 324 | {16, :l, :byte, 586, 16}, 325 | {16, :l, :kenji, 361, 10}, 326 | {16, :m, :numeric, 1082, 12}, 327 | {16, :m, :alphanumeric, 656, 11}, 328 | {16, :m, :byte, 450, 16}, 329 | {16, :m, :kenji, 277, 10}, 330 | {16, :q, :numeric, 775, 12}, 331 | {16, :q, :alphanumeric, 470, 11}, 332 | {16, :q, :byte, 322, 16}, 333 | {16, :q, :kenji, 198, 10}, 334 | {16, :h, :numeric, 602, 12}, 335 | {16, :h, :alphanumeric, 365, 11}, 336 | {16, :h, :byte, 250, 16}, 337 | {16, :h, :kenji, 154, 10}, 338 | {17, :l, :numeric, 1548, 12}, 339 | {17, :l, :alphanumeric, 938, 11}, 340 | {17, :l, :byte, 644, 16}, 341 | {17, :l, :kenji, 397, 10}, 342 | {17, :m, :numeric, 1212, 12}, 343 | {17, :m, :alphanumeric, 734, 11}, 344 | {17, :m, :byte, 504, 16}, 345 | {17, :m, :kenji, 310, 10}, 346 | {17, :q, :numeric, 876, 12}, 347 | {17, :q, :alphanumeric, 531, 11}, 348 | {17, :q, :byte, 364, 16}, 349 | {17, :q, :kenji, 224, 10}, 350 | {17, :h, :numeric, 674, 12}, 351 | {17, :h, :alphanumeric, 408, 11}, 352 | {17, :h, :byte, 280, 16}, 353 | {17, :h, :kenji, 173, 10}, 354 | {18, :l, :numeric, 1725, 12}, 355 | {18, :l, :alphanumeric, 1046, 11}, 356 | {18, :l, :byte, 718, 16}, 357 | {18, :l, :kenji, 442, 10}, 358 | {18, :m, :numeric, 1346, 12}, 359 | {18, :m, :alphanumeric, 816, 11}, 360 | {18, :m, :byte, 560, 16}, 361 | {18, :m, :kenji, 345, 10}, 362 | {18, :q, :numeric, 948, 12}, 363 | {18, :q, :alphanumeric, 574, 11}, 364 | {18, :q, :byte, 394, 16}, 365 | {18, :q, :kenji, 243, 10}, 366 | {18, :h, :numeric, 746, 12}, 367 | {18, :h, :alphanumeric, 452, 11}, 368 | {18, :h, :byte, 310, 16}, 369 | {18, :h, :kenji, 191, 10}, 370 | {19, :l, :numeric, 1903, 12}, 371 | {19, :l, :alphanumeric, 1153, 11}, 372 | {19, :l, :byte, 792, 16}, 373 | {19, :l, :kenji, 488, 10}, 374 | {19, :m, :numeric, 1500, 12}, 375 | {19, :m, :alphanumeric, 909, 11}, 376 | {19, :m, :byte, 624, 16}, 377 | {19, :m, :kenji, 384, 10}, 378 | {19, :q, :numeric, 1063, 12}, 379 | {19, :q, :alphanumeric, 644, 11}, 380 | {19, :q, :byte, 442, 16}, 381 | {19, :q, :kenji, 272, 10}, 382 | {19, :h, :numeric, 813, 12}, 383 | {19, :h, :alphanumeric, 493, 11}, 384 | {19, :h, :byte, 338, 16}, 385 | {19, :h, :kenji, 208, 10}, 386 | {20, :l, :numeric, 2061, 12}, 387 | {20, :l, :alphanumeric, 1249, 11}, 388 | {20, :l, :byte, 858, 16}, 389 | {20, :l, :kenji, 528, 10}, 390 | {20, :m, :numeric, 1600, 12}, 391 | {20, :m, :alphanumeric, 970, 11}, 392 | {20, :m, :byte, 666, 16}, 393 | {20, :m, :kenji, 410, 10}, 394 | {20, :q, :numeric, 1159, 12}, 395 | {20, :q, :alphanumeric, 702, 11}, 396 | {20, :q, :byte, 482, 16}, 397 | {20, :q, :kenji, 297, 10}, 398 | {20, :h, :numeric, 919, 12}, 399 | {20, :h, :alphanumeric, 557, 11}, 400 | {20, :h, :byte, 382, 16}, 401 | {20, :h, :kenji, 235, 10}, 402 | {21, :l, :numeric, 2232, 12}, 403 | {21, :l, :alphanumeric, 1352, 11}, 404 | {21, :l, :byte, 929, 16}, 405 | {21, :l, :kenji, 572, 10}, 406 | {21, :m, :numeric, 1708, 12}, 407 | {21, :m, :alphanumeric, 1035, 11}, 408 | {21, :m, :byte, 711, 16}, 409 | {21, :m, :kenji, 438, 10}, 410 | {21, :q, :numeric, 1224, 12}, 411 | {21, :q, :alphanumeric, 742, 11}, 412 | {21, :q, :byte, 509, 16}, 413 | {21, :q, :kenji, 314, 10}, 414 | {21, :h, :numeric, 969, 12}, 415 | {21, :h, :alphanumeric, 587, 11}, 416 | {21, :h, :byte, 403, 16}, 417 | {21, :h, :kenji, 248, 10}, 418 | {22, :l, :numeric, 2409, 12}, 419 | {22, :l, :alphanumeric, 1460, 11}, 420 | {22, :l, :byte, 1003, 16}, 421 | {22, :l, :kenji, 618, 10}, 422 | {22, :m, :numeric, 1872, 12}, 423 | {22, :m, :alphanumeric, 1134, 11}, 424 | {22, :m, :byte, 779, 16}, 425 | {22, :m, :kenji, 480, 10}, 426 | {22, :q, :numeric, 1358, 12}, 427 | {22, :q, :alphanumeric, 823, 11}, 428 | {22, :q, :byte, 565, 16}, 429 | {22, :q, :kenji, 348, 10}, 430 | {22, :h, :numeric, 1056, 12}, 431 | {22, :h, :alphanumeric, 640, 11}, 432 | {22, :h, :byte, 439, 16}, 433 | {22, :h, :kenji, 270, 10}, 434 | {23, :l, :numeric, 2620, 12}, 435 | {23, :l, :alphanumeric, 1588, 11}, 436 | {23, :l, :byte, 1091, 16}, 437 | {23, :l, :kenji, 672, 10}, 438 | {23, :m, :numeric, 2059, 12}, 439 | {23, :m, :alphanumeric, 1248, 11}, 440 | {23, :m, :byte, 857, 16}, 441 | {23, :m, :kenji, 528, 10}, 442 | {23, :q, :numeric, 1468, 12}, 443 | {23, :q, :alphanumeric, 890, 11}, 444 | {23, :q, :byte, 611, 16}, 445 | {23, :q, :kenji, 376, 10}, 446 | {23, :h, :numeric, 1108, 12}, 447 | {23, :h, :alphanumeric, 672, 11}, 448 | {23, :h, :byte, 461, 16}, 449 | {23, :h, :kenji, 284, 10}, 450 | {24, :l, :numeric, 2812, 12}, 451 | {24, :l, :alphanumeric, 1704, 11}, 452 | {24, :l, :byte, 1171, 16}, 453 | {24, :l, :kenji, 721, 10}, 454 | {24, :m, :numeric, 2188, 12}, 455 | {24, :m, :alphanumeric, 1326, 11}, 456 | {24, :m, :byte, 911, 16}, 457 | {24, :m, :kenji, 561, 10}, 458 | {24, :q, :numeric, 1588, 12}, 459 | {24, :q, :alphanumeric, 963, 11}, 460 | {24, :q, :byte, 661, 16}, 461 | {24, :q, :kenji, 407, 10}, 462 | {24, :h, :numeric, 1228, 12}, 463 | {24, :h, :alphanumeric, 744, 11}, 464 | {24, :h, :byte, 511, 16}, 465 | {24, :h, :kenji, 315, 10}, 466 | {25, :l, :numeric, 3057, 12}, 467 | {25, :l, :alphanumeric, 1853, 11}, 468 | {25, :l, :byte, 1273, 16}, 469 | {25, :l, :kenji, 784, 10}, 470 | {25, :m, :numeric, 2395, 12}, 471 | {25, :m, :alphanumeric, 1451, 11}, 472 | {25, :m, :byte, 997, 16}, 473 | {25, :m, :kenji, 614, 10}, 474 | {25, :q, :numeric, 1718, 12}, 475 | {25, :q, :alphanumeric, 1041, 11}, 476 | {25, :q, :byte, 715, 16}, 477 | {25, :q, :kenji, 440, 10}, 478 | {25, :h, :numeric, 1286, 12}, 479 | {25, :h, :alphanumeric, 779, 11}, 480 | {25, :h, :byte, 535, 16}, 481 | {25, :h, :kenji, 330, 10}, 482 | {26, :l, :numeric, 3283, 12}, 483 | {26, :l, :alphanumeric, 1990, 11}, 484 | {26, :l, :byte, 1367, 16}, 485 | {26, :l, :kenji, 842, 10}, 486 | {26, :m, :numeric, 2544, 12}, 487 | {26, :m, :alphanumeric, 1542, 11}, 488 | {26, :m, :byte, 1059, 16}, 489 | {26, :m, :kenji, 652, 10}, 490 | {26, :q, :numeric, 1804, 12}, 491 | {26, :q, :alphanumeric, 1094, 11}, 492 | {26, :q, :byte, 751, 16}, 493 | {26, :q, :kenji, 462, 10}, 494 | {26, :h, :numeric, 1425, 12}, 495 | {26, :h, :alphanumeric, 864, 11}, 496 | {26, :h, :byte, 593, 16}, 497 | {26, :h, :kenji, 365, 10}, 498 | {27, :l, :numeric, 3517, 14}, 499 | {27, :l, :alphanumeric, 2132, 13}, 500 | {27, :l, :byte, 1465, 16}, 501 | {27, :l, :kenji, 902, 12}, 502 | {27, :m, :numeric, 2701, 14}, 503 | {27, :m, :alphanumeric, 1637, 13}, 504 | {27, :m, :byte, 1125, 16}, 505 | {27, :m, :kenji, 692, 12}, 506 | {27, :q, :numeric, 1933, 14}, 507 | {27, :q, :alphanumeric, 1172, 13}, 508 | {27, :q, :byte, 805, 16}, 509 | {27, :q, :kenji, 496, 12}, 510 | {27, :h, :numeric, 1501, 14}, 511 | {27, :h, :alphanumeric, 910, 13}, 512 | {27, :h, :byte, 625, 16}, 513 | {27, :h, :kenji, 385, 12}, 514 | {28, :l, :numeric, 3669, 14}, 515 | {28, :l, :alphanumeric, 2223, 13}, 516 | {28, :l, :byte, 1528, 16}, 517 | {28, :l, :kenji, 940, 12}, 518 | {28, :m, :numeric, 2857, 14}, 519 | {28, :m, :alphanumeric, 1732, 13}, 520 | {28, :m, :byte, 1190, 16}, 521 | {28, :m, :kenji, 732, 12}, 522 | {28, :q, :numeric, 2085, 14}, 523 | {28, :q, :alphanumeric, 1263, 13}, 524 | {28, :q, :byte, 868, 16}, 525 | {28, :q, :kenji, 534, 12}, 526 | {28, :h, :numeric, 1581, 14}, 527 | {28, :h, :alphanumeric, 958, 13}, 528 | {28, :h, :byte, 658, 16}, 529 | {28, :h, :kenji, 405, 12}, 530 | {29, :l, :numeric, 3909, 14}, 531 | {29, :l, :alphanumeric, 2369, 13}, 532 | {29, :l, :byte, 1628, 16}, 533 | {29, :l, :kenji, 1002, 12}, 534 | {29, :m, :numeric, 3035, 14}, 535 | {29, :m, :alphanumeric, 1839, 13}, 536 | {29, :m, :byte, 1264, 16}, 537 | {29, :m, :kenji, 778, 12}, 538 | {29, :q, :numeric, 2181, 14}, 539 | {29, :q, :alphanumeric, 1322, 13}, 540 | {29, :q, :byte, 908, 16}, 541 | {29, :q, :kenji, 559, 12}, 542 | {29, :h, :numeric, 1677, 14}, 543 | {29, :h, :alphanumeric, 1016, 13}, 544 | {29, :h, :byte, 698, 16}, 545 | {29, :h, :kenji, 430, 12}, 546 | {30, :l, :numeric, 4158, 14}, 547 | {30, :l, :alphanumeric, 2520, 13}, 548 | {30, :l, :byte, 1732, 16}, 549 | {30, :l, :kenji, 1066, 12}, 550 | {30, :m, :numeric, 3289, 14}, 551 | {30, :m, :alphanumeric, 1994, 13}, 552 | {30, :m, :byte, 1370, 16}, 553 | {30, :m, :kenji, 843, 12}, 554 | {30, :q, :numeric, 2358, 14}, 555 | {30, :q, :alphanumeric, 1429, 13}, 556 | {30, :q, :byte, 982, 16}, 557 | {30, :q, :kenji, 604, 12}, 558 | {30, :h, :numeric, 1782, 14}, 559 | {30, :h, :alphanumeric, 1080, 13}, 560 | {30, :h, :byte, 742, 16}, 561 | {30, :h, :kenji, 457, 12}, 562 | {31, :l, :numeric, 4417, 14}, 563 | {31, :l, :alphanumeric, 2677, 13}, 564 | {31, :l, :byte, 1840, 16}, 565 | {31, :l, :kenji, 1132, 12}, 566 | {31, :m, :numeric, 3486, 14}, 567 | {31, :m, :alphanumeric, 2113, 13}, 568 | {31, :m, :byte, 1452, 16}, 569 | {31, :m, :kenji, 894, 12}, 570 | {31, :q, :numeric, 2473, 14}, 571 | {31, :q, :alphanumeric, 1499, 13}, 572 | {31, :q, :byte, 1030, 16}, 573 | {31, :q, :kenji, 634, 12}, 574 | {31, :h, :numeric, 1897, 14}, 575 | {31, :h, :alphanumeric, 1150, 13}, 576 | {31, :h, :byte, 790, 16}, 577 | {31, :h, :kenji, 486, 12}, 578 | {32, :l, :numeric, 4686, 14}, 579 | {32, :l, :alphanumeric, 2840, 13}, 580 | {32, :l, :byte, 1952, 16}, 581 | {32, :l, :kenji, 1201, 12}, 582 | {32, :m, :numeric, 3693, 14}, 583 | {32, :m, :alphanumeric, 2238, 13}, 584 | {32, :m, :byte, 1538, 16}, 585 | {32, :m, :kenji, 947, 12}, 586 | {32, :q, :numeric, 2670, 14}, 587 | {32, :q, :alphanumeric, 1618, 13}, 588 | {32, :q, :byte, 1112, 16}, 589 | {32, :q, :kenji, 684, 12}, 590 | {32, :h, :numeric, 2022, 14}, 591 | {32, :h, :alphanumeric, 1226, 13}, 592 | {32, :h, :byte, 842, 16}, 593 | {32, :h, :kenji, 518, 12}, 594 | {33, :l, :numeric, 4965, 14}, 595 | {33, :l, :alphanumeric, 3009, 13}, 596 | {33, :l, :byte, 2068, 16}, 597 | {33, :l, :kenji, 1273, 12}, 598 | {33, :m, :numeric, 3909, 14}, 599 | {33, :m, :alphanumeric, 2369, 13}, 600 | {33, :m, :byte, 1628, 16}, 601 | {33, :m, :kenji, 1002, 12}, 602 | {33, :q, :numeric, 2805, 14}, 603 | {33, :q, :alphanumeric, 1700, 13}, 604 | {33, :q, :byte, 1168, 16}, 605 | {33, :q, :kenji, 719, 12}, 606 | {33, :h, :numeric, 2157, 14}, 607 | {33, :h, :alphanumeric, 1307, 13}, 608 | {33, :h, :byte, 898, 16}, 609 | {33, :h, :kenji, 553, 12}, 610 | {34, :l, :numeric, 5253, 14}, 611 | {34, :l, :alphanumeric, 3183, 13}, 612 | {34, :l, :byte, 2188, 16}, 613 | {34, :l, :kenji, 1347, 12}, 614 | {34, :m, :numeric, 4134, 14}, 615 | {34, :m, :alphanumeric, 2506, 13}, 616 | {34, :m, :byte, 1722, 16}, 617 | {34, :m, :kenji, 1060, 12}, 618 | {34, :q, :numeric, 2949, 14}, 619 | {34, :q, :alphanumeric, 1787, 13}, 620 | {34, :q, :byte, 1228, 16}, 621 | {34, :q, :kenji, 756, 12}, 622 | {34, :h, :numeric, 2301, 14}, 623 | {34, :h, :alphanumeric, 1394, 13}, 624 | {34, :h, :byte, 958, 16}, 625 | {34, :h, :kenji, 590, 12}, 626 | {35, :l, :numeric, 5529, 14}, 627 | {35, :l, :alphanumeric, 3351, 13}, 628 | {35, :l, :byte, 2303, 16}, 629 | {35, :l, :kenji, 1417, 12}, 630 | {35, :m, :numeric, 4343, 14}, 631 | {35, :m, :alphanumeric, 2632, 13}, 632 | {35, :m, :byte, 1809, 16}, 633 | {35, :m, :kenji, 1113, 12}, 634 | {35, :q, :numeric, 3081, 14}, 635 | {35, :q, :alphanumeric, 1867, 13}, 636 | {35, :q, :byte, 1283, 16}, 637 | {35, :q, :kenji, 790, 12}, 638 | {35, :h, :numeric, 2361, 14}, 639 | {35, :h, :alphanumeric, 1431, 13}, 640 | {35, :h, :byte, 983, 16}, 641 | {35, :h, :kenji, 605, 12}, 642 | {36, :l, :numeric, 5836, 14}, 643 | {36, :l, :alphanumeric, 3537, 13}, 644 | {36, :l, :byte, 2431, 16}, 645 | {36, :l, :kenji, 1496, 12}, 646 | {36, :m, :numeric, 4588, 14}, 647 | {36, :m, :alphanumeric, 2780, 13}, 648 | {36, :m, :byte, 1911, 16}, 649 | {36, :m, :kenji, 1176, 12}, 650 | {36, :q, :numeric, 3244, 14}, 651 | {36, :q, :alphanumeric, 1966, 13}, 652 | {36, :q, :byte, 1351, 16}, 653 | {36, :q, :kenji, 832, 12}, 654 | {36, :h, :numeric, 2524, 14}, 655 | {36, :h, :alphanumeric, 1530, 13}, 656 | {36, :h, :byte, 1051, 16}, 657 | {36, :h, :kenji, 647, 12}, 658 | {37, :l, :numeric, 6153, 14}, 659 | {37, :l, :alphanumeric, 3729, 13}, 660 | {37, :l, :byte, 2563, 16}, 661 | {37, :l, :kenji, 1577, 12}, 662 | {37, :m, :numeric, 4775, 14}, 663 | {37, :m, :alphanumeric, 2894, 13}, 664 | {37, :m, :byte, 1989, 16}, 665 | {37, :m, :kenji, 1224, 12}, 666 | {37, :q, :numeric, 3417, 14}, 667 | {37, :q, :alphanumeric, 2071, 13}, 668 | {37, :q, :byte, 1423, 16}, 669 | {37, :q, :kenji, 876, 12}, 670 | {37, :h, :numeric, 2625, 14}, 671 | {37, :h, :alphanumeric, 1591, 13}, 672 | {37, :h, :byte, 1093, 16}, 673 | {37, :h, :kenji, 673, 12}, 674 | {38, :l, :numeric, 6479, 14}, 675 | {38, :l, :alphanumeric, 3927, 13}, 676 | {38, :l, :byte, 2699, 16}, 677 | {38, :l, :kenji, 1661, 12}, 678 | {38, :m, :numeric, 5039, 14}, 679 | {38, :m, :alphanumeric, 3054, 13}, 680 | {38, :m, :byte, 2099, 16}, 681 | {38, :m, :kenji, 1292, 12}, 682 | {38, :q, :numeric, 3599, 14}, 683 | {38, :q, :alphanumeric, 2181, 13}, 684 | {38, :q, :byte, 1499, 16}, 685 | {38, :q, :kenji, 923, 12}, 686 | {38, :h, :numeric, 2735, 14}, 687 | {38, :h, :alphanumeric, 1658, 13}, 688 | {38, :h, :byte, 1139, 16}, 689 | {38, :h, :kenji, 701, 12}, 690 | {39, :l, :numeric, 6743, 14}, 691 | {39, :l, :alphanumeric, 4087, 13}, 692 | {39, :l, :byte, 2809, 16}, 693 | {39, :l, :kenji, 1729, 12}, 694 | {39, :m, :numeric, 5313, 14}, 695 | {39, :m, :alphanumeric, 3220, 13}, 696 | {39, :m, :byte, 2213, 16}, 697 | {39, :m, :kenji, 1362, 12}, 698 | {39, :q, :numeric, 3791, 14}, 699 | {39, :q, :alphanumeric, 2298, 13}, 700 | {39, :q, :byte, 1579, 16}, 701 | {39, :q, :kenji, 972, 12}, 702 | {39, :h, :numeric, 2927, 14}, 703 | {39, :h, :alphanumeric, 1774, 13}, 704 | {39, :h, :byte, 1219, 16}, 705 | {39, :h, :kenji, 750, 12}, 706 | {40, :l, :numeric, 7089, 14}, 707 | {40, :l, :alphanumeric, 4296, 13}, 708 | {40, :l, :byte, 2953, 16}, 709 | {40, :l, :kenji, 1817, 12}, 710 | {40, :m, :numeric, 5596, 14}, 711 | {40, :m, :alphanumeric, 3391, 13}, 712 | {40, :m, :byte, 2331, 16}, 713 | {40, :m, :kenji, 1435, 12}, 714 | {40, :q, :numeric, 3993, 14}, 715 | {40, :q, :alphanumeric, 2420, 13}, 716 | {40, :q, :byte, 1663, 16}, 717 | {40, :q, :kenji, 1024, 12}, 718 | {40, :h, :numeric, 3057, 14}, 719 | {40, :h, :alphanumeric, 1852, 13}, 720 | {40, :h, :byte, 1273, 16}, 721 | {40, :h, :kenji, 784, 12} 722 | ] 723 | 724 | @ec_levels @table |> Enum.map(&elem(&1, 1)) |> Enum.uniq() 725 | @modes @table |> Enum.map(&elem(&1, 2)) |> Enum.uniq() 726 | 727 | @spec find_version(non_neg_integer(), error_correction_level(), mode()) :: 728 | {:error, :no_version_found} | {:ok, version()} 729 | def find_version(bin_len, ec_level \\ :h, mode \\ :byte) 730 | 731 | for ec_level <- @ec_levels, 732 | mode <- @modes, 733 | {version, _, _, cap, _} <- 734 | Enum.filter(@table, &match?({_, ^ec_level, ^mode, _, _}, &1)) 735 | |> Enum.sort_by(&elem(&1, 3)) do 736 | def find_version(bin_len, unquote(ec_level), unquote(mode)) when bin_len <= unquote(cap), 737 | do: {:ok, unquote(version)} 738 | end 739 | 740 | def find_version(_bin_len, _ec_level, _mode), do: {:error, :no_version_found} 741 | 742 | @spec character_count_indicator_bits(version(), error_correction_level(), mode()) :: 743 | non_neg_integer() 744 | def character_count_indicator_bits(version, ec_level, mode \\ :byte) 745 | 746 | for {version, ec_level, mode, _, cci_len} <- @table do 747 | def character_count_indicator_bits(unquote(version), unquote(ec_level), unquote(mode)), 748 | do: unquote(cci_len) 749 | end 750 | 751 | def character_count_indicator_bits(_version, _ec_level, _mode), do: 0 752 | 753 | # {:version, :error_correction_level, :ec_codewords_per_block, :group1_block_len, :group1_codewords_per_block, :group2_block_len, :group2_codewords_per_block} 754 | @error_correction_table [ 755 | {1, :l, 7, 1, 19, 0, 0}, 756 | {1, :m, 10, 1, 16, 0, 0}, 757 | {1, :q, 13, 1, 13, 0, 0}, 758 | {1, :h, 17, 1, 9, 0, 0}, 759 | {2, :l, 10, 1, 34, 0, 0}, 760 | {2, :m, 16, 1, 28, 0, 0}, 761 | {2, :q, 22, 1, 22, 0, 0}, 762 | {2, :h, 28, 1, 16, 0, 0}, 763 | {3, :l, 15, 1, 55, 0, 0}, 764 | {3, :m, 26, 1, 44, 0, 0}, 765 | {3, :q, 18, 2, 17, 0, 0}, 766 | {3, :h, 22, 2, 13, 0, 0}, 767 | {4, :l, 20, 1, 80, 0, 0}, 768 | {4, :m, 18, 2, 32, 0, 0}, 769 | {4, :q, 26, 2, 24, 0, 0}, 770 | {4, :h, 16, 4, 9, 0, 0}, 771 | {5, :l, 26, 1, 108, 0, 0}, 772 | {5, :m, 24, 2, 43, 0, 0}, 773 | {5, :q, 18, 2, 15, 2, 16}, 774 | {5, :h, 22, 2, 11, 2, 12}, 775 | {6, :l, 18, 2, 68, 0, 0}, 776 | {6, :m, 16, 4, 27, 0, 0}, 777 | {6, :q, 24, 4, 19, 0, 0}, 778 | {6, :h, 28, 4, 15, 0, 0}, 779 | {7, :l, 20, 2, 78, 0, 0}, 780 | {7, :m, 18, 4, 31, 0, 0}, 781 | {7, :q, 18, 2, 14, 4, 15}, 782 | {7, :h, 26, 4, 13, 1, 14}, 783 | {8, :l, 24, 2, 97, 0, 0}, 784 | {8, :m, 22, 2, 38, 2, 39}, 785 | {8, :q, 22, 4, 18, 2, 19}, 786 | {8, :h, 26, 4, 14, 2, 15}, 787 | {9, :l, 30, 2, 116, 0, 0}, 788 | {9, :m, 22, 3, 36, 2, 37}, 789 | {9, :q, 20, 4, 16, 4, 17}, 790 | {9, :h, 24, 4, 12, 4, 13}, 791 | {10, :l, 18, 2, 68, 2, 69}, 792 | {10, :m, 26, 4, 43, 1, 44}, 793 | {10, :q, 24, 6, 19, 2, 20}, 794 | {10, :h, 28, 6, 15, 2, 16}, 795 | {11, :l, 20, 4, 81, 0, 0}, 796 | {11, :m, 30, 1, 50, 4, 51}, 797 | {11, :q, 28, 4, 22, 4, 23}, 798 | {11, :h, 24, 3, 12, 8, 13}, 799 | {12, :l, 24, 2, 92, 2, 93}, 800 | {12, :m, 22, 6, 36, 2, 37}, 801 | {12, :q, 26, 4, 20, 6, 21}, 802 | {12, :h, 28, 7, 14, 4, 15}, 803 | {13, :l, 26, 4, 107, 0, 0}, 804 | {13, :m, 22, 8, 37, 1, 38}, 805 | {13, :q, 24, 8, 20, 4, 21}, 806 | {13, :h, 22, 12, 11, 4, 12}, 807 | {14, :l, 30, 3, 115, 1, 116}, 808 | {14, :m, 24, 4, 40, 5, 41}, 809 | {14, :q, 20, 11, 16, 5, 17}, 810 | {14, :h, 24, 11, 12, 5, 13}, 811 | {15, :l, 22, 5, 87, 1, 88}, 812 | {15, :m, 24, 5, 41, 5, 42}, 813 | {15, :q, 30, 5, 24, 7, 25}, 814 | {15, :h, 24, 11, 12, 7, 13}, 815 | {16, :l, 24, 5, 98, 1, 99}, 816 | {16, :m, 28, 7, 45, 3, 46}, 817 | {16, :q, 24, 15, 19, 2, 20}, 818 | {16, :h, 30, 3, 15, 13, 16}, 819 | {17, :l, 28, 1, 107, 5, 108}, 820 | {17, :m, 28, 10, 46, 1, 47}, 821 | {17, :q, 28, 1, 22, 15, 23}, 822 | {17, :h, 28, 2, 14, 17, 15}, 823 | {18, :l, 30, 5, 120, 1, 121}, 824 | {18, :m, 26, 9, 43, 4, 44}, 825 | {18, :q, 28, 17, 22, 1, 23}, 826 | {18, :h, 28, 2, 14, 19, 15}, 827 | {19, :l, 28, 3, 113, 4, 114}, 828 | {19, :m, 26, 3, 44, 11, 45}, 829 | {19, :q, 26, 17, 21, 4, 22}, 830 | {19, :h, 26, 9, 13, 16, 14}, 831 | {20, :l, 28, 3, 107, 5, 108}, 832 | {20, :m, 26, 3, 41, 13, 42}, 833 | {20, :q, 30, 15, 24, 5, 25}, 834 | {20, :h, 28, 15, 15, 10, 16}, 835 | {21, :l, 28, 4, 116, 4, 117}, 836 | {21, :m, 26, 17, 42, 0, 0}, 837 | {21, :q, 28, 17, 22, 6, 23}, 838 | {21, :h, 30, 19, 16, 6, 17}, 839 | {22, :l, 28, 2, 111, 7, 112}, 840 | {22, :m, 28, 17, 46, 0, 0}, 841 | {22, :q, 30, 7, 24, 16, 25}, 842 | {22, :h, 24, 34, 13, 0, 0}, 843 | {23, :l, 30, 4, 121, 5, 122}, 844 | {23, :m, 28, 4, 47, 14, 48}, 845 | {23, :q, 30, 11, 24, 14, 25}, 846 | {23, :h, 30, 16, 15, 14, 16}, 847 | {24, :l, 30, 6, 117, 4, 118}, 848 | {24, :m, 28, 6, 45, 14, 46}, 849 | {24, :q, 30, 11, 24, 16, 25}, 850 | {24, :h, 30, 30, 16, 2, 17}, 851 | {25, :l, 26, 8, 106, 4, 107}, 852 | {25, :m, 28, 8, 47, 13, 48}, 853 | {25, :q, 30, 7, 24, 22, 25}, 854 | {25, :h, 30, 22, 15, 13, 16}, 855 | {26, :l, 28, 10, 114, 2, 115}, 856 | {26, :m, 28, 19, 46, 4, 47}, 857 | {26, :q, 28, 28, 22, 6, 23}, 858 | {26, :h, 30, 33, 16, 4, 17}, 859 | {27, :l, 30, 8, 122, 4, 123}, 860 | {27, :m, 28, 22, 45, 3, 46}, 861 | {27, :q, 30, 8, 23, 26, 24}, 862 | {27, :h, 30, 12, 15, 28, 16}, 863 | {28, :l, 30, 3, 117, 10, 118}, 864 | {28, :m, 28, 3, 45, 23, 46}, 865 | {28, :q, 30, 4, 24, 31, 25}, 866 | {28, :h, 30, 11, 15, 31, 16}, 867 | {29, :l, 30, 7, 116, 7, 117}, 868 | {29, :m, 28, 21, 45, 7, 46}, 869 | {29, :q, 30, 1, 23, 37, 24}, 870 | {29, :h, 30, 19, 15, 26, 16}, 871 | {30, :l, 30, 5, 115, 10, 116}, 872 | {30, :m, 28, 19, 47, 10, 48}, 873 | {30, :q, 30, 15, 24, 25, 25}, 874 | {30, :h, 30, 23, 15, 25, 16}, 875 | {31, :l, 30, 13, 115, 3, 116}, 876 | {31, :m, 28, 2, 46, 29, 47}, 877 | {31, :q, 30, 42, 24, 1, 25}, 878 | {31, :h, 30, 23, 15, 28, 16}, 879 | {32, :l, 30, 17, 115, 0, 0}, 880 | {32, :m, 28, 10, 46, 23, 47}, 881 | {32, :q, 30, 10, 24, 35, 25}, 882 | {32, :h, 30, 19, 15, 35, 16}, 883 | {33, :l, 30, 17, 115, 1, 116}, 884 | {33, :m, 28, 14, 46, 21, 47}, 885 | {33, :q, 30, 29, 24, 19, 25}, 886 | {33, :h, 30, 11, 15, 46, 16}, 887 | {34, :l, 30, 13, 115, 6, 116}, 888 | {34, :m, 28, 14, 46, 23, 47}, 889 | {34, :q, 30, 44, 24, 7, 25}, 890 | {34, :h, 30, 59, 16, 1, 17}, 891 | {35, :l, 30, 12, 121, 7, 122}, 892 | {35, :m, 28, 12, 47, 26, 48}, 893 | {35, :q, 30, 39, 24, 14, 25}, 894 | {35, :h, 30, 22, 15, 41, 16}, 895 | {36, :l, 30, 6, 121, 14, 122}, 896 | {36, :m, 28, 6, 47, 34, 48}, 897 | {36, :q, 30, 46, 24, 10, 25}, 898 | {36, :h, 30, 2, 15, 64, 16}, 899 | {37, :l, 30, 17, 122, 4, 123}, 900 | {37, :m, 28, 29, 46, 14, 47}, 901 | {37, :q, 30, 49, 24, 10, 25}, 902 | {37, :h, 30, 24, 15, 46, 16}, 903 | {38, :l, 30, 4, 122, 18, 123}, 904 | {38, :m, 28, 13, 46, 32, 47}, 905 | {38, :q, 30, 48, 24, 14, 25}, 906 | {38, :h, 30, 42, 15, 32, 16}, 907 | {39, :l, 30, 20, 117, 4, 118}, 908 | {39, :m, 28, 40, 47, 7, 48}, 909 | {39, :q, 30, 43, 24, 22, 25}, 910 | {39, :h, 30, 10, 15, 67, 16}, 911 | {40, :l, 30, 19, 118, 6, 119}, 912 | {40, :m, 28, 18, 47, 31, 48}, 913 | {40, :q, 30, 34, 24, 34, 25}, 914 | {40, :h, 30, 20, 15, 61, 16} 915 | ] 916 | 917 | @spec code_words_len(version(), error_correction_level()) :: non_neg_integer() 918 | def code_words_len(version, error_correction_level) 919 | 920 | for {version, error_correction_level, _, g1_blocks, g1_codewords, g2_blocks, g2_codewords} <- 921 | @error_correction_table do 922 | def code_words_len(unquote(version), unquote(error_correction_level)), 923 | do: unquote(g1_blocks * g1_codewords + g2_blocks * g2_codewords) 924 | end 925 | 926 | def code_words_len(_version, _error_correction_level), do: 0 927 | 928 | @spec ec_codewords_per_block(version(), error_correction_level()) :: non_neg_integer() 929 | def ec_codewords_per_block(version, error_correction_level) 930 | 931 | for {version, error_correction_level, ec_codewords_per_block, _, _, _, _} <- 932 | @error_correction_table do 933 | def ec_codewords_per_block(unquote(version), unquote(error_correction_level)), 934 | do: unquote(ec_codewords_per_block) 935 | end 936 | 937 | def ec_codewords_per_block(_version, _error_correction_level), do: 0 938 | 939 | @spec group1_block_len(version(), error_correction_level()) :: non_neg_integer() 940 | def group1_block_len(version, error_correction_level) 941 | 942 | for {version, error_correction_level, _, group1_block_len, _, _, _} <- @error_correction_table do 943 | def group1_block_len(unquote(version), unquote(error_correction_level)), 944 | do: unquote(group1_block_len) 945 | end 946 | 947 | def group1_block_len(_version, _error_correction_level), do: 0 948 | 949 | @spec group1_codewords_per_block(version(), error_correction_level()) :: non_neg_integer() 950 | def group1_codewords_per_block(version, error_correction_level) 951 | 952 | for {version, error_correction_level, _, _, group1_codewords_per_block, _, _} <- 953 | @error_correction_table do 954 | def group1_codewords_per_block(unquote(version), unquote(error_correction_level)), 955 | do: unquote(group1_codewords_per_block) 956 | end 957 | 958 | def group1_codewords_per_block(_version, _error_correction_level), do: 0 959 | 960 | @spec group2_block_len(version(), error_correction_level()) :: non_neg_integer() 961 | def group2_block_len(version, error_correction_level) 962 | 963 | for {version, error_correction_level, _, _, _, group2_block_len, _} <- @error_correction_table do 964 | def group2_block_len(unquote(version), unquote(error_correction_level)), 965 | do: unquote(group2_block_len) 966 | end 967 | 968 | def group2_block_len(_version, _error_correction_level), do: 0 969 | 970 | @spec group2_codewords_per_block(version(), error_correction_level()) :: non_neg_integer() 971 | def group2_codewords_per_block(version, error_correction_level) 972 | 973 | for {version, error_correction_level, _, _, _, _, group2_codewords_per_block} <- 974 | @error_correction_table do 975 | def group2_codewords_per_block(unquote(version), unquote(error_correction_level)), 976 | do: unquote(group2_codewords_per_block) 977 | end 978 | 979 | def group2_codewords_per_block(_version, _error_correction_level), do: 0 980 | 981 | # {:version, :remainer} 982 | @remainer [ 983 | {1, 0}, 984 | {2, 7}, 985 | {3, 7}, 986 | {4, 7}, 987 | {5, 7}, 988 | {6, 7}, 989 | {7, 0}, 990 | {8, 0}, 991 | {9, 0}, 992 | {10, 0}, 993 | {11, 0}, 994 | {12, 0}, 995 | {13, 0}, 996 | {14, 3}, 997 | {15, 3}, 998 | {16, 3}, 999 | {17, 3}, 1000 | {18, 3}, 1001 | {19, 3}, 1002 | {20, 3}, 1003 | {21, 4}, 1004 | {22, 4}, 1005 | {23, 4}, 1006 | {24, 4}, 1007 | {25, 4}, 1008 | {26, 4}, 1009 | {27, 4}, 1010 | {28, 3}, 1011 | {29, 3}, 1012 | {30, 3}, 1013 | {31, 3}, 1014 | {32, 3}, 1015 | {33, 3}, 1016 | {34, 3}, 1017 | {35, 0}, 1018 | {36, 0}, 1019 | {37, 0}, 1020 | {38, 0}, 1021 | {39, 0}, 1022 | {40, 0} 1023 | ] 1024 | 1025 | @spec remainer(any) :: 0..7 1026 | def remainer(_version) 1027 | 1028 | for {version, remainer} <- @remainer do 1029 | def remainer(unquote(version)), do: unquote(remainer) 1030 | end 1031 | 1032 | def remainer(_version), do: 0 1033 | end 1034 | --------------------------------------------------------------------------------