Class: Rpdfium::Structure::Tree

Inherits:
Object
  • Object
show all
Defined in:
lib/rpdfium/structure/tree.rb

Overview

StructTree di una pagina PDF tagged.

Per PDF tagged (PDF/UA, esport accessibility-friendly da Word/LibreOffice/InDesign), espone la struttura logica del documento: Document → P, H1, Table, TR, TH, TD, Figure, ecc.

Per PDF NON tagged, ‘Page#struct_tree` ritorna nil. Per PDF “tagged ma vuoti” (es. CR Banca d’Italia, StructTreeRoot presente ma con element placeholder senza type/MCID), ‘Tree#empty?` ritorna true.

Lifecycle: il Tree mantiene un handle PDFium che è “owning” — chiamare ‘FPDF_StructTree_Close` lo dealloca. PDFium dealloca automaticamente lo struct tree alla chiusura del documento, quindi in pratica:

- se non chiudi mai il tree esplicitamente, PDFium lo libera con
  `FPDF_CloseDocument` (zero perdita persistente, ma il tree resta
  in memoria fino alla chiusura del doc — può essere ~MB)
- per controllo deterministico (rilascia subito), usa il blocco:

    page.struct_tree do |tree|
      tree.walk { |el| ... }
    end
  all'uscita dal blocco il tree viene chiuso, anche su eccezione.

Per scelta progettuale NON usiamo ‘ObjectSpace.define_finalizer`: se il GC chiamasse `FPDF_StructTree_Close` dopo che il documento è già stato chiuso, si avrebbe un use-after-free → segfault. La chiusura via Document è sempre sicura; la chiusura via Tree.close (esplicita o tramite blocco) richiede che il documento sia ancora vivo.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(page, handle) ⇒ Tree

Returns a new instance of Tree.



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/rpdfium/structure/tree.rb', line 45

def initialize(page, handle)
  @page = page
  @handle = handle
  @closed = false
  @mcid_text_cache = nil

  # NOTA: niente finalizer. FPDF_StructTree_Close è "owning": chiama
  # ~CPDF_StructTree() che libera l'oggetto. Se il documento PDF
  # viene chiuso prima del tree, il finalizer GC chiamerebbe Close
  # su memoria già liberata → segfault. Lifetime sicuro:
  #   - close esplicito via `tree.close` o via blocco
  #     `page.struct_tree { |tree| ... }`
  #   - se nessuno chiude esplicitamente, PDFium libera il tree
  #     insieme al documento al `FPDF_CloseDocument` (no leak
  #     persistent, solo riserva memoria fino a chiusura doc)
end

Instance Attribute Details

#handleObject (readonly)

Returns the value of attribute handle.



35
36
37
# File 'lib/rpdfium/structure/tree.rb', line 35

def handle
  @handle
end

#pageObject (readonly)

Returns the value of attribute page.



35
36
37
# File 'lib/rpdfium/structure/tree.rb', line 35

def page
  @page
end

Class Method Details

.for_page(page) ⇒ Object

Ritorna nil se la pagina non è tagged. Altrimenti un Tree.



38
39
40
41
42
43
# File 'lib/rpdfium/structure/tree.rb', line 38

def self.for_page(page)
  h = Raw.FPDF_StructTree_GetForPage(page.handle)
  return nil if h.null?

  new(page, h)
end

Instance Method Details

#closeObject

Chiusura esplicita (idempotente). Dopo close, non chiamare metodi su questo Tree né sugli Element che ha generato.



68
69
70
71
72
73
74
# File 'lib/rpdfium/structure/tree.rb', line 68

def close
  return if @closed

  Raw.FPDF_StructTree_Close(@handle)
  @closed = true
  @mcid_text_cache = nil
end

#closed?Boolean

Returns:

  • (Boolean)


62
63
64
# File 'lib/rpdfium/structure/tree.rb', line 62

def closed?
  @closed
end

#empty?Boolean

True se il tree è strutturalmente vuoto (nessun element con type leggibile dai root). Caso comune per PDF “fintamente tagged” come CR Banca d’Italia: il StructTreeRoot esiste ma gli element sono placeholder vuoti.

Returns:

  • (Boolean)


97
98
99
100
101
# File 'lib/rpdfium/structure/tree.rb', line 97

def empty?
  return true if root_count.zero?

  roots.none? { |r| r.type || r.children.any? }
end

#find_all(type:) ⇒ Object

Trova tutti gli element del tipo specificato (es. “Table”, “P”, “Figure”). Confronto case-sensitive (i tipi PDF sono “Table”, “P”, “H1”, ecc.).



114
115
116
# File 'lib/rpdfium/structure/tree.rb', line 114

def find_all(type:)
  walk.select { |el| el.type == type }
end

#mcid_text_mapObject

Page objects raggruppati per Marked Content ID, per consentire a Element#text di risolvere il testo dei suoi MCID. La mappa è costruita una sola volta per Tree e cached.

Pubblico ma destinato a uso interno; non parte dell’API stabile.



129
130
131
# File 'lib/rpdfium/structure/tree.rb', line 129

def mcid_text_map
  @mcid_text_cache ||= build_mcid_text_map
end

#root_countObject

Numero di element root (figli diretti del StructTreeRoot per questa pagina). Tipicamente 1 (‘<Document>`), ma può essere arbitrariamente alto su PDF strani (es. cu.pdf: 717 placeholder).



79
80
81
82
# File 'lib/rpdfium/structure/tree.rb', line 79

def root_count
  n = Raw.FPDF_StructTree_CountChildren(@handle)
  [n, 0].max
end

#rootsObject

Element root (figli diretti del StructTreeRoot). Tipicamente 1 (‘<Document>`).



86
87
88
89
90
91
# File 'lib/rpdfium/structure/tree.rb', line 86

def roots
  (0...root_count).filter_map do |i|
    h = Raw.FPDF_StructTree_GetChildAtIndex(@handle, i)
    h.null? ? nil : Element.new(self, h)
  end
end

#tablesObject

Restituisce tutti gli element di tipo “Table”. Conveniente per estrazione tabelle semantica.



120
121
122
# File 'lib/rpdfium/structure/tree.rb', line 120

def tables
  find_all(type: "Table")
end

#to_sObject Also known as: inspect



133
134
135
# File 'lib/rpdfium/structure/tree.rb', line 133

def to_s
  "#<Rpdfium::Structure::Tree roots=#{root_count}#{empty? ? ' empty' : ''}>"
end

#walk(&block) ⇒ Object

Walk depth-first di TUTTI gli element del tree. Equivalente a ‘roots.flat_map(&:walk)`. Senza block ritorna Enumerator.



105
106
107
108
109
# File 'lib/rpdfium/structure/tree.rb', line 105

def walk(&block)
  return enum_for(:walk) unless block

  roots.each { |r| r.walk(&block) }
end