Class: Prawn::Accessibility::StructureTree Private

Inherits:
Object
  • Object
show all
Includes:
MarkedContent
Defined in:
lib/prawn/accessibility/structure_tree.rb

Overview

This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.

Manages the PDF structure tree for tagged/accessible PDFs.

The structure tree provides the logical structure of a document, mapping marked content sequences in page content streams to structure elements (headings, paragraphs, tables, etc.).

It talks to the document exclusively through the public renderer API (ref!, deref, add_content, state), so it requires no patches to any pdf-core class.

PDF spec references: Section 14.7 (Logical Structure)

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from MarkedContent

#begin_marked_content, #begin_marked_content_with_properties, #end_marked_content, #marked_content_sequence, #marked_content_sequence_with_properties

Constructor Details

#initialize(renderer) ⇒ StructureTree

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns a new instance of StructureTree.

Parameters:

  • renderer (PDF::Core::Renderer)


46
47
48
49
50
51
52
53
54
55
# File 'lib/prawn/accessibility/structure_tree.rb', line 46

def initialize(renderer)
  @renderer = renderer
  @elements = []
  @parent_tree_map = {}
  @next_mcid = 0
  @element_stack = []
  @page_mcid_map = {} # page_ref_id => next mcid for that page
  @root_ref = nil
  @document_elem_ref = nil
end

Instance Attribute Details

#document_elem_refPDF::Core::Reference (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns Document-level structure element.

Returns:

  • (PDF::Core::Reference)

    Document-level structure element



30
31
32
# File 'lib/prawn/accessibility/structure_tree.rb', line 30

def document_elem_ref
  @document_elem_ref
end

#element_stackArray<PDF::Core::Reference> (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns stack of open structure elements.

Returns:

  • (Array<PDF::Core::Reference>)

    stack of open structure elements



43
44
45
# File 'lib/prawn/accessibility/structure_tree.rb', line 43

def element_stack
  @element_stack
end

#elementsArray<Hash> (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns all structure elements created.

Returns:

  • (Array<Hash>)

    all structure elements created



33
34
35
# File 'lib/prawn/accessibility/structure_tree.rb', line 33

def elements
  @elements
end

#next_mcidInteger (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns next available MCID for the current page.

Returns:

  • (Integer)

    next available MCID for the current page



40
41
42
# File 'lib/prawn/accessibility/structure_tree.rb', line 40

def next_mcid
  @next_mcid
end

#parent_tree_mapHash{Integer => Array} (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns page StructParents index => array of structure element refs for marked content on that page.

Returns:

  • (Hash{Integer => Array})

    page StructParents index => array of structure element refs for marked content on that page



37
38
39
# File 'lib/prawn/accessibility/structure_tree.rb', line 37

def parent_tree_map
  @parent_tree_map
end

#rendererPDF::Core::Renderer (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns owning renderer.

Returns:

  • (PDF::Core::Renderer)

    owning renderer



24
25
26
# File 'lib/prawn/accessibility/structure_tree.rb', line 24

def renderer
  @renderer
end

#root_refPDF::Core::Reference (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns StructTreeRoot indirect object.

Returns:

  • (PDF::Core::Reference)

    StructTreeRoot indirect object



27
28
29
# File 'lib/prawn/accessibility/structure_tree.rb', line 27

def root_ref
  @root_ref
end

Instance Method Details

#add_element(tag, attributes = {}) ⇒ PDF::Core::Reference

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Add a structure element as a child of the current open element (or the document element if none is open).

Parameters:

  • tag (Symbol)

    structure type (e.g., :P, :H1, :Table, :TD)

  • attributes (Hash) (defaults to: {})

    additional attributes for the structure element

Options Hash (attributes):

  • :Alt (String)

    alternative text (for Figure, Formula)

  • :ActualText (String)

    replacement text for screen readers (e.g., "required" instead of reading "*")

  • :Lang (String)

    language tag

  • :Scope (Symbol)

    TH scope (:Column, :Row, :Both)

Returns:

  • (PDF::Core::Reference)

    the structure element reference



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/prawn/accessibility/structure_tree.rb', line 82

def add_element(tag, attributes = {})
  parent_ref = current_element || document_element

  elem_data = {
    Type: :StructElem,
    S: tag,
    P: parent_ref,
    K: [],
  }

  elem_data[:Alt] = attributes[:Alt] if attributes[:Alt]
  elem_data[:ActualText] = attributes[:ActualText] if attributes[:ActualText]
  elem_data[:Lang] = attributes[:Lang] if attributes[:Lang]

  if attributes[:Scope]
    elem_data[:A] = {
      O: :Table,
      Scope: attributes[:Scope],
    }
  end

  elem_ref = renderer.ref!(elem_data)
  @elements << elem_ref

  # Add as child of parent
  parent_data = renderer.deref(parent_ref)
  parent_data[:K] << elem_ref

  elem_ref
end

#allocate_mcidInteger

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Allocate the next MCID for the current page and track it.

Returns:

  • (Integer)

    the allocated MCID



60
61
62
63
64
65
66
67
68
69
# File 'lib/prawn/accessibility/structure_tree.rb', line 60

def allocate_mcid
  page = renderer.state.page
  page_id = page.dictionary.identifier

  @page_mcid_map[page_id] ||= 0
  mcid = @page_mcid_map[page_id]
  @page_mcid_map[page_id] += 1

  mcid
end

#begin_element(tag, attributes = {}) ⇒ PDF::Core::Reference

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Begin a structure element scope. Content rendered inside will be children of this element.

Parameters:

  • tag (Symbol)

    structure type

  • attributes (Hash) (defaults to: {})

    additional attributes

Returns:

  • (PDF::Core::Reference)

    the opened structure element



119
120
121
122
123
# File 'lib/prawn/accessibility/structure_tree.rb', line 119

def begin_element(tag, attributes = {})
  elem_ref = add_element(tag, attributes)
  @element_stack.push(elem_ref)
  elem_ref
end

#end_elementPDF::Core::Reference

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

End the current structure element scope.

Returns:

  • (PDF::Core::Reference)

    the closed structure element



128
129
130
# File 'lib/prawn/accessibility/structure_tree.rb', line 128

def end_element
  @element_stack.pop
end

#finalize!void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Finalize the structure tree before rendering. Called via before_render callback.

Builds the StructTreeRoot, ParentTree, and wires everything into the Catalog.



188
189
190
191
192
193
194
195
# File 'lib/prawn/accessibility/structure_tree.rb', line 188

def finalize!
  return if @elements.empty? && @parent_tree_map.empty?

  build_root
  build_parent_tree
  assign_struct_parents_to_pages
  attach_to_catalog
end

#mark_artifact(artifact_type: nil) { ... } ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Mark content as an artifact (decorative, not read by screen readers).

Parameters:

  • artifact_type (Symbol, nil) (defaults to: nil)

    optional artifact type (:Pagination, :Layout, :Page, :Background)

Yields:

  • content to render as artifact



171
172
173
174
175
176
177
178
179
# File 'lib/prawn/accessibility/structure_tree.rb', line 171

def mark_artifact(artifact_type: nil)
  if artifact_type
    begin_marked_content_with_properties(:Artifact, { Type: artifact_type })
  else
    begin_marked_content(:Artifact)
  end
  yield if block_given?
  end_marked_content
end

#mark_content(tag, struct_elem_ref: nil) { ... } ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Add marked content to the current structure element. This allocates an MCID, records the mapping, and emits BDC/EMC operators around the yielded block.

Parameters:

  • tag (Symbol)

    marked content tag (e.g., :P, :Span)

  • struct_elem_ref (PDF::Core::Reference, nil) (defaults to: nil)

    the structure element this content belongs to. If nil, uses current_element.

Yields:

  • content to render inside the marked content sequence



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/prawn/accessibility/structure_tree.rb', line 141

def mark_content(tag, struct_elem_ref: nil)
  elem_ref = struct_elem_ref || current_element || document_element
  mcid = allocate_mcid
  page = renderer.state.page
  page_ref = page.dictionary

  # Record in parent tree map
  page_struct_parents = page_struct_parents_index(page_ref)
  @parent_tree_map[page_struct_parents] ||= []

  # Add marked content reference to the structure element's K array
  mcr = { Type: :MCR, MCID: mcid, Pg: page_ref }
  elem_data = renderer.deref(elem_ref)
  elem_data[:K] << mcr

  # Track which struct element owns this MCID on this page
  @parent_tree_map[page_struct_parents][mcid] = elem_ref

  # Emit BDC/EMC in content stream (via the MarkedContent mixin)
  begin_marked_content_with_properties(tag, { MCID: mcid })
  yield if block_given?
  end_marked_content
end