Class: Tabula::TextChunk

Inherits:
Rectangle show all
Defined in:
lib/tabula/text/text_chunk.rb

Overview

Represents a group of text elements (typically a word or phrase). Extends Rectangle to provide bounding box functionality.

Constant Summary

Constants inherited from Rectangle

Rectangle::VERTICAL_COMPARISON_THRESHOLD

Instance Attribute Summary collapse

Attributes inherited from Rectangle

#height, #left, #top, #width

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Rectangle

#<=>, #==, #area, #bottom, #bottom=, bounding_box_of, #bounds, #center, #contains?, #contains_point?, #dup, from_bounds, from_points, #hash, #horizontal_overlap, #horizontally_overlaps?, #intersection, #intersects?, #merge, #merge!, #overlap_ratio, #points, #right, #right=, #vertical_overlap, #vertically_overlaps?, #x, #x=, #y, #y=

Constructor Details

#initialize(element_or_rect = nil) ⇒ TextChunk

Returns a new instance of TextChunk.

Parameters:



10
11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/tabula/text/text_chunk.rb', line 10

def initialize(element_or_rect = nil)
  if element_or_rect.is_a?(TextElement)
    super(element_or_rect.top, element_or_rect.left, element_or_rect.width, element_or_rect.height)
    @elements = [element_or_rect]
  elsif element_or_rect.is_a?(Rectangle)
    super(element_or_rect.top, element_or_rect.left, element_or_rect.width, element_or_rect.height)
    @elements = []
  elsif element_or_rect.nil?
    super(0, 0, 0, 0)
    @elements = []
  else
    raise ArgumentError, 'Expected TextElement, Rectangle, or nil'
  end
end

Instance Attribute Details

#elements ⇒ Object (readonly)

Returns the value of attribute elements.



7
8
9
# File 'lib/tabula/text/text_chunk.rb', line 7

def elements
  @elements
end

Class Method Details

.all_same_char?(chunks, chars) ⇒ Boolean

Check if all chunks contain the same repeated character

Parameters:

  • chunks (Array<TextChunk>) —

    chunks to check

  • chars (Array<String>) —

    characters to check for

Returns:

  • (Boolean)


158
159
160
# File 'lib/tabula/text/text_chunk.rb', line 158

def all_same_char?(chunks, chars)
  chunks.all? { |c| c.same_char?(chars) }
end

.group_by_lines(chunks) ⇒ Array<Line>

Group text chunks into lines

Parameters:

  • chunks (Array<TextChunk>) —

    chunks to group

Returns:

  • (Array<Line>) —

    lines of text



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/tabula/text/text_chunk.rb', line 165

def (chunks)
  return [] if chunks.empty?

  sorted = chunks.sort_by { |c| [c.top, c.left] }
  lines = []
  current_line = Line.new

  sorted.each do |chunk|
    unless current_line.empty? || current_line.vertically_overlaps?(chunk)
      lines << current_line
      current_line = Line.new
    end
    current_line.add_chunk(chunk)
  end

  lines << current_line unless current_line.empty?
  lines
end

Instance Method Details

#add(element) ⇒ Object

Add a text element to this chunk

Parameters:



27
28
29
30
31
# File 'lib/tabula/text/text_chunk.rb', line 27

def add(element)
  @elements << element
  merge!(element)
  self
end

#add_all(elements) ⇒ Object

Add multiple elements

Parameters:



35
36
37
38
# File 'lib/tabula/text/text_chunk.rb', line 35

def add_all(elements)
  elements.each { |e| add(e) }
  self
end

#empty? ⇒ Boolean

Returns:

  • (Boolean)


145
146
147
# File 'lib/tabula/text/text_chunk.rb', line 145

def empty?
  @elements.empty?
end

#font_name ⇒ Object

Get font name



65
66
67
# File 'lib/tabula/text/text_chunk.rb', line 65

def font_name
  @elements.first&.font_name
end

#font_size ⇒ Object

Get font size



70
71
72
# File 'lib/tabula/text/text_chunk.rb', line 70

def font_size
  @elements.first&.font_size
end

#inspect ⇒ Object



141
142
143
# File 'lib/tabula/text/text_chunk.rb', line 141

def inspect
  to_s
end

#ltr_dominant? ⇒ Boolean

Check if LTR text is dominant in this chunk

Returns:

  • (Boolean)


107
108
109
110
111
# File 'lib/tabula/text/text_chunk.rb', line 107

def ltr_dominant?
  ltr_count = @elements.count(&:ltr?)
  rtl_count = @elements.count(&:rtl?)
  ltr_count >= rtl_count
end

#merge_chunk(other) ⇒ TextChunk

Merge with another chunk

Parameters:

Returns:



132
133
134
135
# File 'lib/tabula/text/text_chunk.rb', line 132

def merge_chunk(other)
  other.elements.each { |e| add(e) }
  self
end

#rtl_dominant? ⇒ Boolean

Check if this chunk is RTL dominant

Returns:

  • (Boolean)


55
56
57
# File 'lib/tabula/text/text_chunk.rb', line 55

def rtl_dominant?
  !ltr_dominant?
end

#same_char?(chars) ⇒ Boolean

Check if this chunk contains only a single repeated character

Parameters:

  • chars (Array<String>) —

    characters to check for

Returns:

  • (Boolean)


77
78
79
80
81
# File 'lib/tabula/text/text_chunk.rb', line 77

def same_char?(chars)
  return false if @elements.empty?

  @elements.all? { |e| chars.include?(e.text) }
end

#size ⇒ Object



149
150
151
# File 'lib/tabula/text/text_chunk.rb', line 149

def size
  @elements.size
end

#split_at(index) ⇒ Array<TextChunk>

Split this chunk at an index

Parameters:

  • index (Integer) —

    element index to split at

Returns:

  • (Array<TextChunk>) —

    two chunks, before and after the split



116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/tabula/text/text_chunk.rb', line 116

def split_at(index)
  return [dup, TextChunk.new] if index >= @elements.size
  return [TextChunk.new, dup] if index <= 0

  left_chunk = TextChunk.new
  right_chunk = TextChunk.new

  @elements[0...index].each { |e| left_chunk.add(e) }
  @elements[index..].each { |e| right_chunk.add(e) }

  [left_chunk, right_chunk]
end

#squeeze(char, min_run: 3) ⇒ TextChunk

Remove runs of identical characters

Parameters:

  • char (String) —

    character to squeeze

  • min_run (Integer) (defaults to: 3) —

    minimum run length to squeeze

Returns:

  • (TextChunk) —

    new chunk with squeezed text



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/tabula/text/text_chunk.rb', line 87

def squeeze(char, min_run: 3)
  return self if @elements.size < min_run

  new_chunk = TextChunk.new(Rectangle.new(top, left, width, height))
  run_count = 0

  @elements.each do |element|
    if element.text == char
      run_count += 1
      new_chunk.add(element) if run_count <= 1
    else
      run_count = 0
      new_chunk.add(element)
    end
  end

  new_chunk
end

#text(normalize: true) ⇒ String

Get the combined text content

Parameters:

  • normalize (Boolean) (defaults to: true) —

    whether to normalize whitespace

Returns:

  • (String) —

    the text content



43
44
45
46
47
48
49
50
51
52
# File 'lib/tabula/text/text_chunk.rb', line 43

def text(normalize: true)
  # Sort elements based on text direction
  sorted = if ltr_dominant?
             @elements.sort_by(&:left)
           else
             @elements.sort_by(&:left).reverse
           end
  raw = sorted.map(&:text).join
  normalize ? raw.gsub(/\s+/, ' ').strip : raw
end

#to_s ⇒ Object



137
138
139
# File 'lib/tabula/text/text_chunk.rb', line 137

def to_s
  "TextChunk[#{text.inspect}](#{left}, #{top}, #{width}, #{height})"
end

#width_of_space ⇒ Object

Get width of space character for this chunk



60
61
62
# File 'lib/tabula/text/text_chunk.rb', line 60

def width_of_space
  @elements.map(&:width_of_space).compact.first
end