Class: Rpdfium::Table::Table

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

Overview

Rappresenta una tabella trovata su una pagina. Espone celle, righe, colonne, bbox, e il metodo ‘extract` che ritorna i dati testuali.

Ogni cella è una bbox ‘[x0, top, x1, bottom]` (top-down). Una “row” è il gruppo di celle che condividono la stessa `top`. Una “column” è il gruppo che condivide la stessa `x0`.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(page, cells) ⇒ Table

Returns a new instance of Table.



14
15
16
17
# File 'lib/rpdfium/table/table.rb', line 14

def initialize(page, cells)
  @page = page
  @cells = cells
end

Instance Attribute Details

#cellsObject (readonly)

Returns the value of attribute cells.



12
13
14
# File 'lib/rpdfium/table/table.rb', line 12

def cells
  @cells
end

#pageObject (readonly)

Returns the value of attribute page.



12
13
14
# File 'lib/rpdfium/table/table.rb', line 12

def page
  @page
end

Instance Method Details

#bboxObject



19
20
21
22
23
24
25
26
27
28
# File 'lib/rpdfium/table/table.rb', line 19

def bbox
  @cells.each_with_object(
    [Float::INFINITY, Float::INFINITY, -Float::INFINITY, -Float::INFINITY]
  ) do |c, acc|
    acc[0] = c[0] if c[0] < acc[0]
    acc[1] = c[1] if c[1] < acc[1]
    acc[2] = c[2] if c[2] > acc[2]
    acc[3] = c[3] if c[3] > acc[3]
  end
end

#columnsObject



37
38
39
# File 'lib/rpdfium/table/table.rb', line 37

def columns
  rows_or_columns(:col)
end

#extract(x_tolerance: Util::WordExtractor::DEFAULT_X_TOLERANCE, y_tolerance: Util::WordExtractor::DEFAULT_Y_TOLERANCE, keep_blank_chars: false, cell_padding: 0.0) ⇒ Object

Estrai dati: Array<Array<String>>. Per ogni riga, per ogni cella, filtra i char della pagina il cui MIDPOINT è nella bbox della cella, poi ricostruisce il testo via Util::TextExtraction (che a sua volta passa da WordExtractor).

Questo è il path di pdfplumber.Table.extract — per ogni riga prima filtra i char della riga (ottimizzazione: quasi tutti i char delle altre righe vengono scartati subito), poi per ogni cella filtra ancora dentro la sub-bbox.

Ottimizzazione rispetto al path naïve: i char vengono ordinati per midpoint verticale una sola volta; per ogni riga si usa bsearch per trovare in O(log n) i char candidati invece di scansionare tutto l’array O(n) per ogni riga.

NOTA su strategia :text: ‘words_to_edges_h` emette per design DUE edges per riga (top e bottom della bbox del cluster). Significa che una tabella detectata da text-strategy avrà righe “vere” intervallate da righe “vuote” tra il bottom-edge della riga N e il top-edge della riga N+1. Questo è identico al comportamento di pdfplumber. Il caller può filtrare via `result.reject { |row| row.all?(&:empty?) }` se vuole eliminarle. `cell_padding`: estende il bbox di ogni cella verso sinistra e verso l’alto di N punti. Default 0 (= comportamento pdfplumber identico). Utile per PDF dove i char sporgono leggermente dal bordo della cella (es. la “I” maiuscola della cella “Intermediario” in CR Banca d’Italia ha x0=24.0 ma il bordo della cella è a x=25.6 — viene scartata dal filtro midpoint, output “ntermediario:”). Con ‘cell_padding: 2.0` la cella diventa [23.6, …, 100, …] e la “I” viene catturata.

Padding solo sui bordi “interno-sinistro” e “interno-alto” per evitare di duplicare char condivisi tra celle adiacenti (un char tra cella A e cella B finirebbe in entrambe se entrambe paddassero su tutti i lati).



75
76
77
78
79
80
81
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
112
113
114
115
116
117
# File 'lib/rpdfium/table/table.rb', line 75

def extract(x_tolerance: Util::WordExtractor::DEFAULT_X_TOLERANCE,
            y_tolerance: Util::WordExtractor::DEFAULT_Y_TOLERANCE,
            keep_blank_chars: false,
            cell_padding: 0.0)
  # `lean: true`: salta 5 chiamate FFI per char (font name, weight,
  # angle, hyphen flag, unicode error) che non servono al pipeline
  # di estrazione tabelle. Su tabelle con migliaia di char riduce
  # il tempo di compute_chars del ~30%.
  chars = @page.chars(lean: true)

  # Ordina per midpoint verticale una volta sola; costruisce un array
  # parallelo di vmid per bsearch. Costo: O(n log n) una tantum.
  sorted_chars = chars.sort_by { |c| (c[:top] + c[:bottom]) / 2.0 }
  vmids = sorted_chars.map { |c| (c[:top] + c[:bottom]) / 2.0 }

  # Istanzia WordExtractor UNA volta sola e riusalo per tutte le celle
  # (può esserci una tabella con decine di celle, evitiamo allocazioni).
  word_extractor = Util::WordExtractor.new(
    x_tolerance: x_tolerance,
    y_tolerance: y_tolerance,
    keep_blank_chars: keep_blank_chars
  )

  all_rows = rows
  all_rows.map do |row|
    row_bbox = row_bounding_box(row)
    lo = vmids.bsearch_index { |v| v >= row_bbox[1] - cell_padding } || sorted_chars.size
    hi = vmids.bsearch_index { |v| v >= row_bbox[3] } || sorted_chars.size
    row_chars = sorted_chars[lo...hi]

    row.map do |cell|
      next nil if cell.nil?

      padded = cell_padding.zero? ? cell : pad_cell_bbox(cell, cell_padding)
      cell_chars = row_chars.select { |c| char_in_bbox?(c, padded) }
      if cell_chars.empty?
        ""
      else
        extract_text_with(cell_chars, word_extractor, y_tolerance)
      end
    end
  end
end

#rowsObject

Restituisce le righe come Array<Array<bbox|nil>>. Le celle “mancanti” in una riga (es. perché la tabella ha una topologia irregolare) sono rappresentate come nil — coerente con pdfplumber.



33
34
35
# File 'lib/rpdfium/table/table.rb', line 33

def rows
  rows_or_columns(:row)
end