├── 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 | 
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 | 
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()
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 |
--------------------------------------------------------------------------------