pdf-reader 2.6.0 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +21 -1
  3. data/Rakefile +1 -1
  4. data/examples/rspec.rb +1 -0
  5. data/lib/pdf/reader/bounding_rectangle_runs_filter.rb +16 -0
  6. data/lib/pdf/reader/buffer.rb +1 -0
  7. data/lib/pdf/reader/cid_widths.rb +1 -0
  8. data/lib/pdf/reader/cmap.rb +5 -3
  9. data/lib/pdf/reader/encoding.rb +2 -1
  10. data/lib/pdf/reader/error.rb +8 -0
  11. data/lib/pdf/reader/filter/ascii85.rb +2 -0
  12. data/lib/pdf/reader/filter/ascii_hex.rb +6 -1
  13. data/lib/pdf/reader/filter/depredict.rb +7 -5
  14. data/lib/pdf/reader/filter/flate.rb +2 -0
  15. data/lib/pdf/reader/filter/lzw.rb +2 -0
  16. data/lib/pdf/reader/filter/null.rb +1 -0
  17. data/lib/pdf/reader/filter/run_length.rb +19 -13
  18. data/lib/pdf/reader/filter.rb +1 -0
  19. data/lib/pdf/reader/font.rb +44 -0
  20. data/lib/pdf/reader/font_descriptor.rb +1 -0
  21. data/lib/pdf/reader/form_xobject.rb +1 -0
  22. data/lib/pdf/reader/glyph_hash.rb +1 -0
  23. data/lib/pdf/reader/lzw.rb +4 -2
  24. data/lib/pdf/reader/null_security_handler.rb +1 -0
  25. data/lib/pdf/reader/object_cache.rb +1 -0
  26. data/lib/pdf/reader/object_hash.rb +5 -2
  27. data/lib/pdf/reader/object_stream.rb +1 -0
  28. data/lib/pdf/reader/overlapping_runs_filter.rb +11 -4
  29. data/lib/pdf/reader/page.rb +73 -11
  30. data/lib/pdf/reader/page_layout.rb +28 -32
  31. data/lib/pdf/reader/page_state.rb +11 -10
  32. data/lib/pdf/reader/page_text_receiver.rb +53 -9
  33. data/lib/pdf/reader/pages_strategy.rb +1 -0
  34. data/lib/pdf/reader/parser.rb +7 -1
  35. data/lib/pdf/reader/point.rb +25 -0
  36. data/lib/pdf/reader/print_receiver.rb +1 -0
  37. data/lib/pdf/reader/rectangle.rb +113 -0
  38. data/lib/pdf/reader/reference.rb +1 -0
  39. data/lib/pdf/reader/register_receiver.rb +1 -0
  40. data/lib/pdf/reader/resource_methods.rb +5 -0
  41. data/lib/pdf/reader/standard_security_handler.rb +1 -0
  42. data/lib/pdf/reader/standard_security_handler_v5.rb +1 -0
  43. data/lib/pdf/reader/stream.rb +1 -0
  44. data/lib/pdf/reader/synchronized_cache.rb +1 -0
  45. data/lib/pdf/reader/text_run.rb +14 -6
  46. data/lib/pdf/reader/token.rb +1 -0
  47. data/lib/pdf/reader/transformation_matrix.rb +1 -0
  48. data/lib/pdf/reader/unimplemented_security_handler.rb +1 -0
  49. data/lib/pdf/reader/width_calculator/built_in.rb +1 -0
  50. data/lib/pdf/reader/width_calculator/composite.rb +1 -0
  51. data/lib/pdf/reader/width_calculator/true_type.rb +1 -0
  52. data/lib/pdf/reader/width_calculator/type_one_or_three.rb +1 -0
  53. data/lib/pdf/reader/width_calculator/type_zero.rb +1 -0
  54. data/lib/pdf/reader/width_calculator.rb +1 -0
  55. data/lib/pdf/reader/xref.rb +1 -0
  56. data/lib/pdf/reader/zero_width_runs_filter.rb +2 -0
  57. data/lib/pdf/reader.rb +29 -6
  58. data/lib/pdf-reader.rb +1 -0
  59. data/rbi/pdf-reader.rbi +1763 -0
  60. metadata +13 -10
  61. data/lib/pdf/reader/orientation_detector.rb +0 -34
@@ -1,4 +1,5 @@
1
1
  # coding: utf-8
2
+ # typed: true
2
3
  # frozen_string_literal: true
3
4
 
4
5
  require 'pdf/reader/overlapping_runs_filter'
@@ -16,16 +17,15 @@ class PDF::Reader
16
17
  DEFAULT_FONT_SIZE = 12
17
18
 
18
19
  def initialize(runs, mediabox)
19
- raise ArgumentError, "a mediabox must be provided" if mediabox.nil?
20
+ # mediabox is a 4-element array for now, but it'd be nice to switch to a
21
+ # PDF::Reader::Rectangle at some point
22
+ PDF::Reader::Error.validate_not_nil(mediabox, "mediabox")
20
23
 
21
- runs = ZeroWidthRunsFilter.exclude_zero_width_runs(runs)
22
- runs = OverlappingRunsFilter.exclude_redundant_runs(runs)
23
- @runs = merge_runs(runs)
24
+ @mediabox = process_mediabox(mediabox)
25
+ @runs = runs
24
26
  @mean_font_size = mean(@runs.map(&:font_size)) || DEFAULT_FONT_SIZE
25
27
  @mean_font_size = DEFAULT_FONT_SIZE if @mean_font_size == 0
26
28
  @median_glyph_width = median(@runs.map(&:mean_character_width)) || 0
27
- @page_width = (mediabox[2] - mediabox[0]).abs
28
- @page_height = (mediabox[3] - mediabox[1]).abs
29
29
  @x_offset = @runs.map(&:x).sort.first || 0
30
30
  lowest_y = @runs.map(&:y).sort.first || 0
31
31
  @y_offset = lowest_y > 0 ? 0 : lowest_y
@@ -48,6 +48,14 @@ class PDF::Reader
48
48
 
49
49
  private
50
50
 
51
+ def page_width
52
+ @mediabox.width
53
+ end
54
+
55
+ def page_height
56
+ @mediabox.height
57
+ end
58
+
51
59
  # given an array of strings, return a new array with empty rows from the
52
60
  # beginning and end removed.
53
61
  #
@@ -66,19 +74,19 @@ class PDF::Reader
66
74
  end
67
75
 
68
76
  def row_count
69
- @row_count ||= (@page_height / @mean_font_size).floor
77
+ @row_count ||= (page_height / @mean_font_size).floor
70
78
  end
71
79
 
72
80
  def col_count
73
- @col_count ||= ((@page_width / @median_glyph_width) * 1.05).floor
81
+ @col_count ||= ((page_width / @median_glyph_width) * 1.05).floor
74
82
  end
75
83
 
76
84
  def row_multiplier
77
- @row_multiplier ||= @page_height.to_f / row_count.to_f
85
+ @row_multiplier ||= page_height.to_f / row_count.to_f
78
86
  end
79
87
 
80
88
  def col_multiplier
81
- @col_multiplier ||= @page_width.to_f / col_count.to_f
89
+ @col_multiplier ||= page_width.to_f / col_count.to_f
82
90
  end
83
91
 
84
92
  def mean(collection)
@@ -97,32 +105,20 @@ class PDF::Reader
97
105
  end
98
106
  end
99
107
 
100
- # take a collection of TextRun objects and merge any that are in close
101
- # proximity
102
- def merge_runs(runs)
103
- runs.group_by { |char|
104
- char.y.to_i
105
- }.map { |y, chars|
106
- group_chars_into_runs(chars.sort)
107
- }.flatten.sort
108
+ def local_string_insert(haystack, needle, index)
109
+ haystack[Range.new(index, index + needle.length - 1)] = String.new(needle)
108
110
  end
109
111
 
110
- def group_chars_into_runs(chars)
111
- runs = []
112
- while head = chars.shift
113
- if runs.empty?
114
- runs << head
115
- elsif runs.last.mergable?(head)
116
- runs[-1] = runs.last + head
117
- else
118
- runs << head
119
- end
112
+ def process_mediabox(mediabox)
113
+ if mediabox.is_a?(Array)
114
+ msg = "Passing the mediabox to PageLayout as an Array is deprecated," +
115
+ " please use a Rectangle instead"
116
+ $stderr.puts msg
117
+ PDF::Reader::Rectangle.from_array(mediabox)
118
+ else
119
+ mediabox
120
120
  end
121
- runs
122
121
  end
123
122
 
124
- def local_string_insert(haystack, needle, index)
125
- haystack[Range.new(index, index + needle.length - 1)] = String.new(needle)
126
- end
127
123
  end
128
124
  end
@@ -1,4 +1,5 @@
1
1
  # coding: utf-8
2
+ # typed: true
2
3
  # frozen_string_literal: true
3
4
 
4
5
  require 'pdf/reader/transformation_matrix'
@@ -312,7 +313,7 @@ class PDF::Reader
312
313
  # may need to be added
313
314
  #
314
315
  def process_glyph_displacement(w0, tj, word_boundary)
315
- fs = font_size # font size
316
+ fs = state[:text_font_size]
316
317
  tc = state[:char_spacing]
317
318
  if word_boundary
318
319
  tw = state[:word_spacing]
@@ -330,16 +331,16 @@ class PDF::Reader
330
331
  # apply horizontal scaling to spacing values but not font size
331
332
  tx = ((w0 * fs) + tc + tw) * th
332
333
  end
333
-
334
- # TODO: I'm pretty sure that tx shouldn't need to be divided by
335
- # ctm[0] here, but this gets my tests green and I'm out of
336
- # ideas for now
337
334
  # TODO: support ty > 0
338
- if ctm.a == 1 || ctm.a == 0
339
- @text_matrix.horizontal_displacement_multiply!(tx)
340
- else
341
- @text_matrix.horizontal_displacement_multiply!(tx/ctm.a)
342
- end
335
+ ty = 0
336
+ temp = TransformationMatrix.new(1, 0,
337
+ 0, 1,
338
+ tx, ty)
339
+ @text_matrix = temp.multiply!(
340
+ @text_matrix.a, @text_matrix.b,
341
+ @text_matrix.c, @text_matrix.d,
342
+ @text_matrix.e, @text_matrix.f
343
+ )
343
344
  @font_size = @text_rendering_matrix = nil # invalidate cached value
344
345
  end
345
346
 
@@ -1,4 +1,5 @@
1
1
  # coding: utf-8
2
+ # typed: true
2
3
  # frozen_string_literal: true
3
4
 
4
5
  require 'forwardable'
@@ -44,14 +45,34 @@ module PDF
44
45
  @page = page
45
46
  @content = []
46
47
  @characters = []
47
- @mediabox = page.objects.deref(page.attributes[:MediaBox])
48
- device_bl = apply_rotation(*@state.ctm_transform(@mediabox[0], @mediabox[1]))
49
- device_tr = apply_rotation(*@state.ctm_transform(@mediabox[2], @mediabox[3]))
50
- @device_mediabox = [ device_bl.first, device_bl.last, device_tr.first, device_tr.last]
51
48
  end
52
49
 
50
+ def runs(opts = {})
51
+ runs = @characters
52
+
53
+ if rect = opts.fetch(:rect, @page.rectangles[:CropBox])
54
+ runs = BoundingRectangleRunsFilter.runs_within_rect(runs, rect)
55
+ end
56
+
57
+ if opts.fetch(:skip_zero_width, true)
58
+ runs = ZeroWidthRunsFilter.exclude_zero_width_runs(runs)
59
+ end
60
+
61
+ if opts.fetch(:skip_overlapping, true)
62
+ runs = OverlappingRunsFilter.exclude_redundant_runs(runs)
63
+ end
64
+
65
+ if opts.fetch(:merge, true)
66
+ runs = merge_runs(runs)
67
+ end
68
+
69
+ runs
70
+ end
71
+
72
+ # deprecated
53
73
  def content
54
- PageLayout.new(@characters, @device_mediabox).to_s
74
+ mediabox = @page.rectangles[:MediaBox]
75
+ PageLayout.new(runs, mediabox).to_s
55
76
  end
56
77
 
57
78
  #####################################################
@@ -111,7 +132,7 @@ module PDF
111
132
 
112
133
  # apply to glyph displacment for the current glyph so the next
113
134
  # glyph will appear in the correct position
114
- glyph_width = @state.current_font.glyph_width(glyph_code) / 1000.0
135
+ glyph_width = @state.current_font.glyph_width_in_text_space(glyph_code)
115
136
  th = 1
116
137
  scaled_glyph_width = glyph_width * @state.font_size * th
117
138
  unless utf8_chars == SPACE
@@ -128,14 +149,37 @@ module PDF
128
149
  y = tmp * -1
129
150
  elsif @page.rotate == 180
130
151
  y *= -1
152
+ x *= -1
131
153
  elsif @page.rotate == 270
132
- tmp = x
133
- x = y * -1
134
- y = tmp * -1
154
+ tmp = y
155
+ y = x
156
+ x = tmp * -1
135
157
  end
136
158
  return x, y
137
159
  end
138
160
 
161
+ # take a collection of TextRun objects and merge any that are in close
162
+ # proximity
163
+ def merge_runs(runs)
164
+ runs.group_by { |char|
165
+ char.y.to_i
166
+ }.map { |y, chars|
167
+ group_chars_into_runs(chars.sort)
168
+ }.flatten.sort
169
+ end
170
+
171
+ def group_chars_into_runs(chars)
172
+ chars.each_with_object([]) do |char, runs|
173
+ if runs.empty?
174
+ runs << char
175
+ elsif runs.last.mergable?(char)
176
+ runs[-1] = runs.last + char
177
+ else
178
+ runs << char
179
+ end
180
+ end
181
+ end
182
+
139
183
  end
140
184
  end
141
185
  end
@@ -1,4 +1,5 @@
1
1
  # coding: utf-8
2
+ # typed: true
2
3
  # frozen_string_literal: true
3
4
 
4
5
  ################################################################################
@@ -1,4 +1,5 @@
1
1
  # coding: utf-8
2
+ # typed: true
2
3
  # frozen_string_literal: true
3
4
 
4
5
  ################################################################################
@@ -166,7 +167,9 @@ class PDF::Reader
166
167
 
167
168
  # add a missing digit if required, as required by the spec
168
169
  str << "0" unless str.size % 2 == 0
169
- str.scan(/../).map {|i| i.hex.chr}.join.force_encoding("binary")
170
+ str.chars.each_slice(2).map { |nibbles|
171
+ nibbles.join("").hex.chr
172
+ }.join.force_encoding("binary")
170
173
  end
171
174
  ################################################################################
172
175
  # Reads a PDF String from the buffer and converts it to a Ruby String
@@ -207,6 +210,9 @@ class PDF::Reader
207
210
  raise MalformedPDFError, "PDF malformed, missing stream length" unless dict.has_key?(:Length)
208
211
  if @objects
209
212
  length = @objects.deref(dict[:Length])
213
+ if dict[:Filter]
214
+ dict[:Filter] = @objects.deref(dict[:Filter])
215
+ end
210
216
  else
211
217
  length = dict[:Length] || 0
212
218
  end
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ # typed: true
3
+ # frozen_string_literal: true
4
+
5
+ module PDF
6
+ class Reader
7
+
8
+ # PDFs are all about positioning content on a page, so there's lots of need to
9
+ # work with a set of X,Y coordinates.
10
+ #
11
+ class Point
12
+
13
+ attr_reader :x, :y
14
+
15
+ def initialize(x, y)
16
+ @x, @y = x, y
17
+ end
18
+
19
+ def ==(other)
20
+ other.respond_to?(:x) && other.respond_to?(:y) && x == other.x && y == other.y
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -1,4 +1,5 @@
1
1
  # coding: utf-8
2
+ # typed: strict
2
3
  # frozen_string_literal: true
3
4
 
4
5
  class PDF::Reader
@@ -0,0 +1,113 @@
1
+ # coding: utf-8
2
+ # typed: true
3
+ # frozen_string_literal: true
4
+
5
+ module PDF
6
+ class Reader
7
+
8
+ # PDFs represent rectangles all over the place. They're 4 element arrays, like this:
9
+ #
10
+ # [A, B, C, D]
11
+ #
12
+ # Four element arrays are yucky to work with though, so here's a class that's better.
13
+ # Initialize it with the 4 elements, and get utility functions (width, height, etc)
14
+ # for free.
15
+ #
16
+ # By convention the first two elements are x1, y1, the co-ords for the bottom left corner
17
+ # of the rectangle. The third and fourth elements are x2, y2, the co-ords for the top left
18
+ # corner of the rectangle. It's valid for the alternative corners to be used though, so
19
+ # we don't assume which is which.
20
+ #
21
+ class Rectangle
22
+
23
+ attr_reader :bottom_left, :bottom_right, :top_left, :top_right
24
+
25
+ def initialize(x1, y1, x2, y2)
26
+ set_corners(x1, y1, x2, y2)
27
+ end
28
+
29
+ def self.from_array(arr)
30
+ if arr.size != 4
31
+ raise ArgumentError, "Only 4-element Arrays can be converted to a Rectangle"
32
+ end
33
+
34
+ PDF::Reader::Rectangle.new(
35
+ arr[0].to_f,
36
+ arr[1].to_f,
37
+ arr[2].to_f,
38
+ arr[3].to_f,
39
+ )
40
+ end
41
+
42
+ def ==(other)
43
+ to_a == other.to_a
44
+ end
45
+
46
+ def height
47
+ top_right.y - bottom_right.y
48
+ end
49
+
50
+ def width
51
+ bottom_right.x - bottom_left.x
52
+ end
53
+
54
+ def contains?(point)
55
+ point.x >= bottom_left.x && point.x <= top_right.x &&
56
+ point.y >= bottom_left.y && point.y <= top_right.y
57
+ end
58
+
59
+ # A pdf-style 4-number array
60
+ def to_a
61
+ [
62
+ bottom_left.x,
63
+ bottom_left.y,
64
+ top_right.x,
65
+ top_right.y,
66
+ ]
67
+ end
68
+
69
+ def apply_rotation(degrees)
70
+ return if degrees != 90 && degrees != 180 && degrees != 270
71
+
72
+ if degrees == 90
73
+ new_x1 = bottom_left.x
74
+ new_y1 = bottom_left.y - width
75
+ new_x2 = bottom_left.x + height
76
+ new_y2 = bottom_left.y
77
+ elsif degrees == 180
78
+ new_x1 = bottom_left.x - width
79
+ new_y1 = bottom_left.y - height
80
+ new_x2 = bottom_left.x
81
+ new_y2 = bottom_left.y
82
+ elsif degrees == 270
83
+ new_x1 = bottom_left.x - height
84
+ new_y1 = bottom_left.y
85
+ new_x2 = bottom_left.x
86
+ new_y2 = bottom_left.y + width
87
+ end
88
+ set_corners(new_x1, new_y1, new_x2, new_y2)
89
+ end
90
+
91
+ private
92
+
93
+ def set_corners(x1, y1, x2, y2)
94
+ @bottom_left = PDF::Reader::Point.new(
95
+ [x1, x2].min,
96
+ [y1, y2].min,
97
+ )
98
+ @bottom_right = PDF::Reader::Point.new(
99
+ [x1, x2].max,
100
+ [y1, y2].min,
101
+ )
102
+ @top_left = PDF::Reader::Point.new(
103
+ [x1, x2].min,
104
+ [y1, y2].max,
105
+ )
106
+ @top_right = PDF::Reader::Point.new(
107
+ [x1, x2].max,
108
+ [y1, y2].max,
109
+ )
110
+ end
111
+ end
112
+ end
113
+ end
@@ -1,4 +1,5 @@
1
1
  # coding: utf-8
2
+ # typed: true
2
3
  # frozen_string_literal: true
3
4
 
4
5
  ################################################################################
@@ -1,4 +1,5 @@
1
1
  # coding: utf-8
2
+ # typed: strict
2
3
  # frozen_string_literal: true
3
4
 
4
5
  # Copyright (C) 2010 James Healy (jimmy@deefa.com)
@@ -1,12 +1,17 @@
1
1
  # coding: utf-8
2
+ # typed: false
2
3
  # frozen_string_literal: true
3
4
 
5
+ # Setting this file to "typed: true" is difficult because it's a mixin that assumes some things
6
+ # are aavailable from the class, like @objects and resources. Sorbet doesn't know about them.
7
+
4
8
  module PDF
5
9
  class Reader
6
10
 
7
11
  # mixin for common methods in Page and FormXobjects
8
12
  #
9
13
  module ResourceMethods
14
+
10
15
  # Returns a Hash of color spaces that are available to this page
11
16
  #
12
17
  # NOTE: this method de-serialise objects from the underlying PDF
@@ -1,4 +1,5 @@
1
1
  # coding: utf-8
2
+ # typed: true
2
3
  # frozen_string_literal: true
3
4
 
4
5
  ################################################################################
@@ -1,4 +1,5 @@
1
1
  # coding: utf-8
2
+ # typed: true
2
3
  # frozen_string_literal: true
3
4
 
4
5
  require 'digest'
@@ -1,4 +1,5 @@
1
1
  # coding: utf-8
2
+ # typed: true
2
3
  # frozen_string_literal: true
3
4
 
4
5
  ################################################################################
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # typed: true
2
3
  # frozen_string_literal: true
3
4
 
4
5
  # utilities.rb : General-purpose utility classes which don't fit anywhere else
@@ -1,4 +1,5 @@
1
1
  # coding: utf-8
2
+ # typed: true
2
3
  # frozen_string_literal: true
3
4
 
4
5
  class PDF::Reader
@@ -6,15 +7,14 @@ class PDF::Reader
6
7
  class TextRun
7
8
  include Comparable
8
9
 
9
- attr_reader :x, :y, :width, :font_size, :text
10
+ attr_reader :origin, :width, :font_size, :text
10
11
 
11
12
  alias :to_s :text
12
13
 
13
14
  def initialize(x, y, width, font_size, text)
14
- @x = x
15
- @y = y
15
+ @origin = PDF::Reader::Point.new(x, y)
16
16
  @width = width
17
- @font_size = font_size.floor
17
+ @font_size = font_size
18
18
  @text = text
19
19
  end
20
20
 
@@ -34,12 +34,20 @@ class PDF::Reader
34
34
  end
35
35
  end
36
36
 
37
+ def x
38
+ @origin.x
39
+ end
40
+
41
+ def y
42
+ @origin.y
43
+ end
44
+
37
45
  def endx
38
- @endx ||= x + width
46
+ @endx ||= @origin.x + width
39
47
  end
40
48
 
41
49
  def endy
42
- @endy ||= y + font_size
50
+ @endy ||= @origin.y + font_size
43
51
  end
44
52
 
45
53
  def mean_character_width
@@ -1,4 +1,5 @@
1
1
  # coding: utf-8
2
+ # typed: strict
2
3
  # frozen_string_literal: true
3
4
 
4
5
  ################################################################################
@@ -1,4 +1,5 @@
1
1
  # coding: utf-8
2
+ # typed: true
2
3
  # frozen_string_literal: true
3
4
 
4
5
  class PDF::Reader
@@ -1,4 +1,5 @@
1
1
  # coding: utf-8
2
+ # typed: strict
2
3
  # frozen_string_literal: true
3
4
 
4
5
  class PDF::Reader
@@ -1,4 +1,5 @@
1
1
  # coding: utf-8
2
+ # typed: true
2
3
  # frozen_string_literal: true
3
4
 
4
5
  require 'afm'
@@ -1,4 +1,5 @@
1
1
  # coding: utf-8
2
+ # typed: true
2
3
  # frozen_string_literal: true
3
4
 
4
5
  class PDF::Reader
@@ -1,4 +1,5 @@
1
1
  # coding: utf-8
2
+ # typed: true
2
3
  # frozen_string_literal: true
3
4
 
4
5
  class PDF::Reader
@@ -1,4 +1,5 @@
1
1
  # coding: utf-8
2
+ # typed: true
2
3
  # frozen_string_literal: true
3
4
 
4
5
  class PDF::Reader
@@ -1,4 +1,5 @@
1
1
  # coding: utf-8
2
+ # typed: true
2
3
  # frozen_string_literal: true
3
4
 
4
5
  class PDF::Reader
@@ -1,4 +1,5 @@
1
1
  # coding: utf-8
2
+ # typed: strict
2
3
  # frozen_string_literal: true
3
4
 
4
5
  # PDF files may define fonts in a number of ways. Each approach means we must
@@ -1,4 +1,5 @@
1
1
  # coding: utf-8
2
+ # typed: true
2
3
  # frozen_string_literal: true
3
4
 
4
5
  ################################################################################
@@ -1,4 +1,6 @@
1
1
  # coding: utf-8
2
+ # typed: strict
3
+ # frozen_string_literal: true
2
4
 
3
5
  class PDF::Reader
4
6
  # There's no point rendering zero-width characters