Current section

Files

Jump to
barlix2 lib barlix itf.ex
Raw

lib/barlix/itf.ex

defmodule Barlix.ITF do
@moduledoc """
This module implements the [Interleaved 2 of 5 (ITF)](https://en.wikipedia.org/wiki/Interleaved_2_of_5) symbology.
"""
require Integer
alias Barlix.Utils
@doc """
Encodes the given value using ITF symbology. Only numeric characters
are supported.
## Options
* `:pad` (boolean) - adds a prefix character `0` if the length of
characters is not even. Defaults to `false`
* `:checksum` (boolean) - enables checksum. Defaults to `false`
"""
@spec encode(String.t() | charlist, Keyword.t()) :: {:error, binary} | {:ok, Barlix.code()}
def encode(value, options) do
with {:ok, digits} <- digits(value) do
checksum(digits, Keyword.get(options, :checksum, false))
|> pad(Keyword.get(options, :pad, false))
|> interleave
end
end
@doc """
Accepts the same arguments as `encode/2`. Returns `t:Barlix.code/0` or
raises `Barlix.Error` in case of invalid value.
"""
@spec encode!(String.t() | charlist, Keyword.t()) :: Barlix.code() | no_return
def encode!(value, options \\ []) do
case encode(value, options) do
{:ok, code} -> code
{:error, error} -> raise Barlix.Error, error
end
end
defp digits(value) do
Utils.normalize_string(value)
|> Enum.reverse()
|> Enum.reduce({:ok, []}, fn
x, {:ok, digits} when x >= ?0 and x <= ?9 ->
{:ok, [x - ?0 | digits]}
_, {:error, _} = error ->
error
x, _ ->
{:error,
"Invalid character found #{IO.chardata_to_string([x])}. Only numeric characters are allowed"}
end)
end
@black 1
@white 0
defp interleave(digits) when Integer.is_even(length(digits)) do
encoded =
digits
|> Enum.chunk_every(2)
|> Enum.map(fn chunk ->
Enum.map(chunk, &encode_digit/1)
|> Enum.map(&String.to_charlist/1)
|> List.zip()
|> chunks_to_bars
end)
{:ok, {:D1, List.flatten([start_code(), encoded, stop_code()])}}
end
defp interleave(digits) do
{:error, "Even number of digits is required digits: #{inspect(digits)}"}
end
defp checksum(digits, false), do: digits
defp checksum(digits, true) do
total =
Enum.with_index(digits)
|> Enum.reduce(0, fn {digit, i}, total ->
if Integer.is_even(i) do
total + 3 * digit
else
total + digit
end
end)
c = rem(10 - rem(total, 10), 10)
digits ++ [c]
end
defp pad(digits, true) when Integer.is_odd(length(digits)), do: [0 | digits]
defp pad(digits, _), do: digits
defp chunks_to_bars(chunks) do
Enum.map(chunks, fn {l, r} ->
[bars(l, @black), bars(r, @white)]
end)
end
defp start_code, do: chunks_to_bars([{?n, ?n}, {?n, ?n}])
defp stop_code, do: chunks_to_bars([{?W, ?n}]) ++ bars(?n, @black)
defp encode_digit(0), do: "nnWWn"
defp encode_digit(1), do: "WnnnW"
defp encode_digit(2), do: "nWnnW"
defp encode_digit(3), do: "WWnnn"
defp encode_digit(4), do: "nnWnW"
defp encode_digit(5), do: "WnWnn"
defp encode_digit(6), do: "nWWnn"
defp encode_digit(7), do: "nnnWW"
defp encode_digit(8), do: "WnnWn"
defp encode_digit(9), do: "nWnWn"
defp bars(?n, @black), do: [@black, @black]
defp bars(?n, @white), do: [@white, @white]
defp bars(?W, @black), do: [@black, @black, @black, @black, @black]
defp bars(?W, @white), do: [@white, @white, @white, @white, @white]
end