lib/pdf/reader/line.ex

defmodule Pdf.Reader.Line do
  @moduledoc """
  Logical text line reconstructed from individual `TextRun`s.

  Many PDFs (particularly machine-generated ones such as government
  forms and tax documents) place glyphs individually with the `TJ`
  operator and per-glyph kerning, producing one `TextRun` per character.
  Working with that flat run list is awkward — `Line` coalesces those
  runs into the structure a human reader sees: lines and, within each
  line, tokens separated by visible whitespace.

  ## Shape

  - `:page` — 1-indexed page number
  - `:y` — baseline Y of the line (PDF user-space, origin bottom-left)
  - `:x` — leftmost X of the first token on the line
  - `:text` — joined text, tokens separated by single spaces
  - `:tokens` — ordered list of `t:token/0` maps, sorted by X ascending

  Each token carries its own `:x` so callers can detect column layouts
  (e.g. table rows where every line has tokens at the same X positions).

  ## Spec references

  - PDF 1.7 § 9.4 — Text objects:
    https://opensource.adobe.com/dc-acrobat-sdk-docs/standards/pdfstandards/pdf/PDF32000_2008.pdf
  - PDF 1.7 § 9.4.4 — Text-showing operators (Tj, TJ, ', ")
  """

  @type token_kind :: :text | :link | :email | :button | :form_field | :table_cell | atom()

  @type token :: %{
          required(:x) => float(),
          required(:text) => String.t(),
          required(:width) => float(),
          optional(:kind) => token_kind(),
          optional(:shape) => Pdf.Reader.Shape.t() | nil
        }

  @type t :: %__MODULE__{
          page: pos_integer(),
          y: float(),
          x: float(),
          text: String.t(),
          tokens: [token()]
        }

  defstruct page: 1,
            y: 0.0,
            x: 0.0,
            text: "",
            tokens: []
end