Current section
Files
Jump to
Current section
Files
lib/pdf_ex/cos/serializer.ex
defmodule PdfEx.COS.Serializer do
@moduledoc false
alias PdfEx.COS.{Reference, Stream, Name, LiteralString, HexString, PdfString}
@spec serialize_val(any()) :: binary()
def serialize_val(true), do: "true"
def serialize_val(false), do: "false"
def serialize_val(nil), do: "null"
def serialize_val(%Reference{id: id, gen: g}), do: "#{id} #{g} R"
def serialize_val(%Stream{dictionary: d, raw_bytes: r}),
do: serialize_val(d) <> "\nstream\n" <> r <> "\nendstream"
def serialize_val(%Name{value: v}), do: "/" <> escape_name(v)
def serialize_val(%LiteralString{value: v}), do: "(" <> PdfString.escape(v) <> ")"
def serialize_val(%HexString{value: v}), do: "<" <> v <> ">"
def serialize_val(a) when is_atom(a), do: "/" <> escape_name(a)
def serialize_val(i) when is_integer(i), do: Integer.to_string(i)
def serialize_val(f) when is_float(f) and abs(f) < 1.0e240,
do: :erlang.float_to_binary(f, [:compact, decimals: 6])
def serialize_val(f) when is_float(f), do: Integer.to_string(trunc(f)) <> ".0"
def serialize_val(m) when is_map(m) do
inner = Enum.map_join(m, " ", fn {k, v} -> "/#{escape_name(k)} #{serialize_val(v)}" end)
"<< " <> inner <> " >>"
end
def serialize_val(l) when is_list(l),
do: "[" <> Enum.map_join(l, " ", &serialize_val/1) <> "]"
def serialize_val(b) when is_binary(b), do: "(" <> PdfString.escape(b) <> ")"
# Characters that cannot appear literally in a name are written as #xx
# (ISO 32000 §7.3.5): whitespace, the delimiters, and # itself, plus any byte
# outside the printable ASCII range. Mirrors the lexer's #xx decode so a
# parsed name round-trips byte-for-byte.
@name_delimiters [?(, ?), ?<, ?>, ?[, ?], ?{, ?}, ?/, ?%, ?#]
defp escape_name(value) do
value
|> to_string()
|> :binary.bin_to_list()
|> Enum.map_join("", &escape_name_char/1)
end
defp escape_name_char(c) when c in 0x21..0x7E and c not in @name_delimiters, do: <<c>>
defp escape_name_char(c), do: "#" <> Base.encode16(<<c>>)
end