defmodule Pdf.Dictionary do
@moduledoc false
import Pdf.Utils
import Pdf.Size
@dict_start "<<\n"
@dict_start_length byte_size(@dict_start)
@dict_end ">>"
@dict_end_length byte_size(@dict_end)
@initial_length @dict_start_length + @dict_end_length
@value_separator " "
@value_separator_length byte_size(@value_separator)
@entry_separator "\n"
@entry_separator_length byte_size(@entry_separator)
defstruct size: @initial_length, entries: %{}
def new, do: %__MODULE__{size: @initial_length}
def new(map) do
map
|> Enum.filter(fn {_, value} -> value != nil end)
|> Enum.reduce(new(), fn {key, value}, dictionary ->
put(dictionary, key, value)
end)
end
def put(dictionary, key, value) when is_binary(value), do: put(dictionary, key, s(value))
def put(dictionary, key, value) do
key = n(key)
case Map.fetch(dictionary.entries, key) do
:error ->
entries = Map.put(dictionary.entries, key, value)
size = increment_internal_size(dictionary, key, value)
%{dictionary | entries: entries, size: size}
{:ok, ^value} ->
dictionary
{:ok, old_value} ->
entries = Map.put(dictionary.entries, key, value)
size = decrement_internal_size(dictionary, key, old_value)
size = increment_internal_size(%{dictionary | size: size}, key, value)
%{dictionary | entries: entries, size: size}
end
end
defp increment_internal_size(%__MODULE__{size: size}, key, %{size: _size}),
do: size + size_of(key) + @value_separator_length + @entry_separator_length
defp increment_internal_size(dictionary, key, value),
do: increment_internal_size(dictionary, key, %{size: 0}) + size_of(value)
defp decrement_internal_size(%__MODULE__{size: size}, key, %{size: _size}),
do: size - size_of(key) - @value_separator_length - @entry_separator_length
defp decrement_internal_size(dictionary, key, value),
do: decrement_internal_size(dictionary, key, %{size: 0}) - size_of(value)
def size(dict) do
dict
|> to_iolist()
|> :binary.list_to_bin()
|> byte_size()
end
def to_iolist(dictionary) do
[@dict_start, entries_to_iolist(dictionary.entries), @dict_end]
end
defp entries_to_iolist(%{} = entries), do: entries_to_iolist(Enum.to_list(entries))
defp entries_to_iolist([]), do: []
defp entries_to_iolist([entry | tail]), do: [entry_to_iolist(entry) | entries_to_iolist(tail)]
defp entry_to_iolist({key, value}),
do: Pdf.Export.to_iolist([key, @value_separator, value, @entry_separator])
defimpl Pdf.Size do
def size_of(%Pdf.Dictionary{} = dictionary), do: Pdf.Dictionary.size(dictionary)
end
defimpl Pdf.Export do
def to_iolist(dictionary), do: Pdf.Dictionary.to_iolist(dictionary)
end
def to_map(%__MODULE__{entries: entries}) do
entries
|> Enum.map(fn {{:name, name}, value} ->
{name, to_value(value)}
end)
|> Map.new()
end
defp to_value({:name, name}), do: name
defp to_value({:string, string}), do: string
defp to_value(other), do: other
end