Tincture.PDF (Tincture v0.1.0)

Copy Markdown View Source

Internal PDF state container.

Summary

Functions

Add a radio button group: one field, one widget per choice.

Open an artifact: content that is decoration rather than meaning.

Open a structure element, bracketing whatever is drawn until it is closed.

Close the innermost open artifact.

Close the innermost open structure element.

Set the document's natural language, as a BCP 47 tag such as "en-GB".

Declare a PDF/A conformance level, adding what archival validity requires.

Record the intent to sign a signature field.

Whether this document carries logical structure.

Types

annotation()

@type annotation() :: %{
  type: :link,
  rect: {number(), number(), number(), number()},
  target: link_target(),
  border: annotation_border()
}

annotation_border()

@type annotation_border() :: :none | {number(), number(), number()}

begin_artifact_op()

@type begin_artifact_op() :: {:begin_artifact}

begin_marked_content_op()

@type begin_marked_content_op() ::
  {:begin_marked_content, String.t(), non_neg_integer()}

bezier_op()

@type bezier_op() ::
  {:bezier, number(), number(), number(), number(), number(), number()}

bookmark()

@type bookmark() :: %{title: String.t(), page_number: pos_integer()}

button_action()

@type button_action() :: :reset | {:url, String.t()} | {:submit, String.t()}

What a push button does when clicked.

A push button holds no value, so an action is the only reason to have one.

circle_op()

@type circle_op() :: {:circle, number(), number(), number(), paint()}

clip_even_odd_op()

@type clip_even_odd_op() :: :clip_even_odd

clip_op()

@type clip_op() :: :clip

color_op()

@type color_op() :: {:set_stroke_color, rgb()} | {:set_fill_color, rgb()}

draw_op()

@type draw_op() :: {:line, number(), number(), number(), number(), paint()}

embedded_font()

@type embedded_font() :: %{
  :format => :ttf | :otf,
  :name => String.t(),
  :data => binary(),
  :subset => embedded_font_subset_mode(),
  optional(:ttf_metrics) => ttf_metrics()
}

embedded_font_option()

@type embedded_font_option() ::
  {:subset, embedded_font_subset_mode()}
  | {:enforce_embedding_permissions, boolean()}

embedded_font_subset_mode()

@type embedded_font_subset_mode() :: :none | :ascii_basic | :used_text

end_marked_content_op()

@type end_marked_content_op() :: {:end_marked_content}

fill_even_odd_op()

@type fill_even_odd_op() :: :fill_even_odd

fill_op()

@type fill_op() :: :fill

font()

@type font() :: {String.t(), number()}

form_field()

@type form_field() :: %{
  :type => form_field_type(),
  :name => String.t(),
  :page_number => pos_integer(),
  :rect => {number(), number(), number(), number()},
  :value => String.t() | boolean(),
  :flags => non_neg_integer(),
  :font => String.t(),
  :size => number(),
  :border => annotation_border(),
  optional(:max_length) => pos_integer(),
  optional(:tooltip) => String.t(),
  optional(:options) => [String.t()],
  optional(:widgets) => [radio_widget()],
  optional(:action) => button_action(),
  optional(:label) => String.t()
}

form_field_type()

@type form_field_type() ::
  :text | :checkbox | :choice | :radio | :push_button | :signature

graphics_state_op()

@type graphics_state_op() :: :save_state | :restore_state

image()

@type image() :: %{
  :format => :jpeg | :png,
  :data => binary(),
  :width => pos_integer(),
  :height => pos_integer(),
  :bits_per_component => pos_integer(),
  :color_space => image_color_space(),
  optional(:decode_parms) => map(),
  optional(:alpha_data) => binary(),
  optional(:alpha_decode_parms) => map()
}

image_color_space()

@type image_color_space() :: :device_gray | :device_rgb | :device_cmyk

image_op()

@type image_op() :: {:image, number(), number(), number(), number(), pos_integer()}

line_style_op()

@type line_style_op() ::
  {:set_line_width, number()}
  | {:set_line_cap, 0 | 1 | 2}
  | {:set_line_join, 0 | 1 | 2}
  | {:set_dash, [number()], number()}
  | {:set_miter_limit, number()}

line_to_op()

@type line_to_op() :: {:line_to, number(), number()}

move_to_op()

@type move_to_op() :: {:move_to, number(), number()}

op()

page_size()

@type page_size() :: :a4 | :letter | :legal | {number(), number()}

paint()

@type paint() :: :stroke | :fill | :fill_even_odd | :fill_and_stroke | :none

radio_widget()

@type radio_widget() :: %{
  export_value: String.t(),
  page_number: pos_integer(),
  rect: {number(), number(), number(), number()}
}

One button within a radio group.

A radio group is a single field with several on-page widgets, one per choice. export_value is the value the field takes when that button is the selected one, and is what appears in the filled document.

rect_op()

@type rect_op() :: {:rectangle, number(), number(), number(), number(), paint()}

rgb()

@type rgb() :: {number(), number(), number()}

stroke_op()

@type stroke_op() :: :stroke

t()

@type t() :: %Tincture.PDF{
  annotations: %{required(pos_integer()) => [annotation()]},
  bookmarks: [bookmark()],
  current_font: font(),
  current_page: pos_integer(),
  embedded_fonts: %{optional(String.t()) => embedded_font()},
  encryption: map() | nil,
  form_fields: [form_field()],
  images: %{required(pos_integer()) => image()},
  language: String.t() | nil,
  mcid_counters: %{optional(pos_integer()) => non_neg_integer()},
  metadata: %{optional(atom()) => String.t()},
  next_image_id: pos_integer(),
  operations: [op()],
  page_size: page_size(),
  pages: %{required(pos_integer()) => [op()]},
  pdf_a: {2 | 3, :b | :u | :a} | nil,
  signature: map() | nil,
  structure_stack: [Tincture.PDF.Structure.t()],
  structure_tree: [Tincture.PDF.Structure.t()]
}

text_op()

@type text_op() :: {:text_at, number(), number(), String.t(), font()}

text_rotated_op()

@type text_rotated_op() ::
  {:text_at_rotated, number(), number(), number(), String.t(), font()}

ttf_metrics()

@type ttf_metrics() :: map()

Functions

add_bookmark(pdf, title, page_number)

@spec add_bookmark(t(), String.t(), pos_integer()) :: t()

add_form_field(pdf, type, name, arg, opts)

@spec add_form_field(
  t(),
  form_field_type(),
  String.t(),
  {number(), number(), number(), number()},
  keyword()
) :: t()

add_link(pdf, arg, target, opts \\ [])

@spec add_link(
  t(),
  {number(), number(), number(), number()},
  link_target(),
  keyword()
) :: t()

add_page(pdf)

@spec add_page(t()) :: t()

add_radio_group(pdf, name, buttons, opts)

@spec add_radio_group(t(), String.t(), [keyword()], keyword()) :: t()

Add a radio button group: one field, one widget per choice.

Unlike every other field type this is not one dictionary. The specification models a radio group as a parent field holding the value, with a kid widget for each button, and the button's export value is the name of its "on" appearance state. Choosing a button sets the parent's value to that name.

buttons is a list of keyword lists, each needing :value, :x, :y and :size, and optionally :page.

append_current_op(pdf, op)

@spec append_current_op(t(), op()) :: t()

begin_artifact(pdf)

@spec begin_artifact(t()) :: t()

Open an artifact: content that is decoration rather than meaning.

Rules, borders, background shading and repeating page furniture carry no information a reader needs. In a tagged document every operator must be either tagged or marked as an artifact — content that is neither is a conformance failure, and is read out as stray noise.

An artifact has no structure element and no marked-content id; it exists only to say "skip this".

begin_structure(pdf, tag, opts \\ [])

@spec begin_structure(t(), Tincture.PDF.Structure.tag(), keyword()) :: t()

Open a structure element, bracketing whatever is drawn until it is closed.

Content elements also open a marked-content sequence in the page's content stream, which is what ties the drawn operators to this element.

end_artifact(pdf)

@spec end_artifact(t()) :: t()

Close the innermost open artifact.

end_structure(pdf)

@spec end_structure(t()) :: t()

Close the innermost open structure element.

page_annotations(pdf, page_number)

@spec page_annotations(t(), pos_integer()) :: [annotation()]

page_numbers(pdf)

@spec page_numbers(t()) :: [pos_integer()]

page_operations(pdf, page_number)

@spec page_operations(t(), pos_integer()) :: [op()]

register_image(pdf, image)

@spec register_image(t(), image()) :: {t(), pos_integer()}

register_otf_font(pdf, font_name, path)

@spec register_otf_font(t(), String.t(), Path.t()) :: t()

register_otf_font(pdf, font_name, path, opts)

@spec register_otf_font(t(), String.t(), Path.t(), [embedded_font_option()]) :: t()

register_ttf_font(pdf, font_name, path)

@spec register_ttf_font(t(), String.t(), Path.t()) :: t()

register_ttf_font(pdf, font_name, path, opts)

@spec register_ttf_font(t(), String.t(), Path.t(), [embedded_font_option()]) :: t()

set_language(pdf, language)

@spec set_language(t(), String.t()) :: t()

Set the document's natural language, as a BCP 47 tag such as "en-GB".

Required for a document to be accessible: without it a screen reader has to guess which language to pronounce the text as.

set_metadata(pdf, metadata)

@spec set_metadata(t(), map() | keyword()) :: t()

set_page(pdf, page_number)

@spec set_page(t(), pos_integer()) :: t()

set_pdf_a(pdf, level)

@spec set_pdf_a(t(), atom()) :: t()

Declare a PDF/A conformance level, adding what archival validity requires.

Sets an sRGB output intent, a /Metadata stream carrying the PDF/A identification, and a file identifier.

set_signature(pdf, field_name, opts)

@spec set_signature(t(), String.t(), keyword()) :: t()

Record the intent to sign a signature field.

The signature itself cannot be computed here: it covers the finished file, so it is applied during Tincture.export/2.

tagged?(pdf)

@spec tagged?(t()) :: boolean()

Whether this document carries logical structure.