lib/pdf/reader/encoding/differences.ex

defmodule Pdf.Reader.Encoding.Differences do
  @moduledoc """
  Applies a PDF `/Differences` array on top of a base encoding override map.

  Spec reference: PDF 1.7 § 9.6.5.1.

  ## Format

  `/Differences` is an array mixing integers and names:

      [32 /space 65 /A /B /C 200 /uni0024 ...]

  - An integer `N` sets the current code position to `N`.
  - Each subsequent name installs that glyph name at position, then increments by 1.

  ## API

      apply(base_overrides, differences_array) :: %{integer() => glyph_name :: binary()}

  The output is a byte→glyph_name map. Codepoint resolution (via AGL or ToUnicode)
  happens later in the encoding facade (`Pdf.Reader.Encoding`).

  `/Differences` entries override the base map. Base entries not touched by
  `/Differences` are preserved.
  """

  @doc """
  Applies a `/Differences` array on top of `base`, returning the merged
  byte→glyph_name override map.

  `differences` is a list of integers and `{:name, binary()}` tuples, matching
  the tagged-tuple convention used by the reader's parser.
  """
  @spec apply(%{non_neg_integer() => binary()}, list()) :: %{non_neg_integer() => binary()}
  def apply(base, differences) when is_map(base) and is_list(differences) do
    do_apply(differences, base, 0)
  end

  defp do_apply([], acc, _pos), do: acc

  defp do_apply([n | rest], acc, _pos) when is_integer(n) do
    do_apply(rest, acc, n)
  end

  defp do_apply([{:name, name} | rest], acc, pos) when is_binary(name) do
    do_apply(rest, Map.put(acc, pos, name), pos + 1)
  end

  # Handle bare atom/string names if parser uses those shapes
  defp do_apply([name | rest], acc, pos) when is_binary(name) do
    do_apply(rest, Map.put(acc, pos, name), pos + 1)
  end
end