hexapdf 0.7.0 → 0.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 (106) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -1
  3. data/CONTRIBUTERS +1 -1
  4. data/LICENSE +3 -0
  5. data/README.md +2 -1
  6. data/Rakefile +3 -1
  7. data/VERSION +1 -1
  8. data/examples/{hello_world.rb → 001-hello_world.rb} +0 -0
  9. data/examples/{graphics.rb → 002-graphics.rb} +1 -1
  10. data/examples/{arc.rb → 003-arcs.rb} +2 -2
  11. data/examples/{optimizing.rb → 004-optimizing.rb} +0 -0
  12. data/examples/{merging.rb → 005-merging.rb} +0 -0
  13. data/examples/{standard_pdf_fonts.rb → 006-standard_pdf_fonts.rb} +0 -0
  14. data/examples/{truetype.rb → 007-truetype.rb} +0 -0
  15. data/examples/{show_char_bboxes.rb → 008-show_char_bboxes.rb} +0 -0
  16. data/examples/{text_layouter_alignment.rb → 009-text_layouter_alignment.rb} +3 -3
  17. data/examples/{text_layouter_inline_boxes.rb → 010-text_layouter_inline_boxes.rb} +7 -9
  18. data/examples/{text_layouter_line_wrapping.rb → 011-text_layouter_line_wrapping.rb} +6 -5
  19. data/examples/{text_layouter_styling.rb → 012-text_layouter_styling.rb} +6 -8
  20. data/examples/013-text_layouter_shapes.rb +176 -0
  21. data/examples/014-text_in_polygon.rb +60 -0
  22. data/examples/{boxes.rb → 015-boxes.rb} +29 -21
  23. data/examples/016-frame_automatic_box_placement.rb +90 -0
  24. data/examples/017-frame_text_flow.rb +60 -0
  25. data/lib/hexapdf/cli/command.rb +4 -3
  26. data/lib/hexapdf/cli/files.rb +1 -1
  27. data/lib/hexapdf/cli/inspect.rb +0 -1
  28. data/lib/hexapdf/cli/merge.rb +1 -1
  29. data/lib/hexapdf/cli/modify.rb +1 -1
  30. data/lib/hexapdf/configuration.rb +2 -0
  31. data/lib/hexapdf/content/canvas.rb +3 -3
  32. data/lib/hexapdf/content/graphic_object.rb +1 -0
  33. data/lib/hexapdf/content/graphic_object/geom2d.rb +132 -0
  34. data/lib/hexapdf/dictionary.rb +7 -1
  35. data/lib/hexapdf/dictionary_fields.rb +35 -83
  36. data/lib/hexapdf/document.rb +9 -5
  37. data/lib/hexapdf/document/fonts.rb +1 -1
  38. data/lib/hexapdf/encryption/standard_security_handler.rb +1 -1
  39. data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
  40. data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
  41. data/lib/hexapdf/font/cmap/writer.rb +2 -2
  42. data/lib/hexapdf/font/true_type/builder.rb +1 -1
  43. data/lib/hexapdf/font/true_type/table.rb +1 -1
  44. data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
  45. data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +3 -3
  46. data/lib/hexapdf/font/true_type/table/kern.rb +1 -1
  47. data/lib/hexapdf/font/true_type/table/post.rb +1 -1
  48. data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
  49. data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
  50. data/lib/hexapdf/image_loader/jpeg.rb +1 -1
  51. data/lib/hexapdf/image_loader/png.rb +2 -2
  52. data/lib/hexapdf/layout.rb +3 -0
  53. data/lib/hexapdf/layout/box.rb +64 -46
  54. data/lib/hexapdf/layout/frame.rb +348 -0
  55. data/lib/hexapdf/layout/inline_box.rb +2 -2
  56. data/lib/hexapdf/layout/line.rb +3 -3
  57. data/lib/hexapdf/layout/style.rb +81 -14
  58. data/lib/hexapdf/layout/text_box.rb +84 -0
  59. data/lib/hexapdf/layout/text_fragment.rb +8 -8
  60. data/lib/hexapdf/layout/text_layouter.rb +278 -169
  61. data/lib/hexapdf/layout/width_from_polygon.rb +246 -0
  62. data/lib/hexapdf/rectangle.rb +9 -9
  63. data/lib/hexapdf/stream.rb +2 -2
  64. data/lib/hexapdf/type.rb +1 -0
  65. data/lib/hexapdf/type/action.rb +1 -1
  66. data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
  67. data/lib/hexapdf/type/catalog.rb +1 -1
  68. data/lib/hexapdf/type/cid_font.rb +2 -1
  69. data/lib/hexapdf/type/font.rb +0 -1
  70. data/lib/hexapdf/type/font_descriptor.rb +1 -1
  71. data/lib/hexapdf/type/font_simple.rb +3 -3
  72. data/lib/hexapdf/type/font_true_type.rb +8 -0
  73. data/lib/hexapdf/type/font_type0.rb +2 -1
  74. data/lib/hexapdf/type/font_type1.rb +7 -1
  75. data/lib/hexapdf/type/font_type3.rb +61 -0
  76. data/lib/hexapdf/type/graphics_state_parameter.rb +8 -8
  77. data/lib/hexapdf/type/image.rb +10 -0
  78. data/lib/hexapdf/type/page.rb +83 -10
  79. data/lib/hexapdf/version.rb +1 -1
  80. data/test/hexapdf/common_tokenizer_tests.rb +2 -2
  81. data/test/hexapdf/content/graphic_object/test_geom2d.rb +79 -0
  82. data/test/hexapdf/encryption/test_standard_security_handler.rb +1 -1
  83. data/test/hexapdf/font/test_true_type_wrapper.rb +1 -1
  84. data/test/hexapdf/font/test_type1_wrapper.rb +1 -1
  85. data/test/hexapdf/font/true_type/table/test_cmap.rb +1 -1
  86. data/test/hexapdf/font/true_type/table/test_directory.rb +1 -1
  87. data/test/hexapdf/font/true_type/table/test_head.rb +7 -3
  88. data/test/hexapdf/layout/test_box.rb +57 -15
  89. data/test/hexapdf/layout/test_frame.rb +313 -0
  90. data/test/hexapdf/layout/test_inline_box.rb +1 -1
  91. data/test/hexapdf/layout/test_style.rb +74 -0
  92. data/test/hexapdf/layout/test_text_box.rb +77 -0
  93. data/test/hexapdf/layout/test_text_layouter.rb +220 -239
  94. data/test/hexapdf/layout/test_width_from_polygon.rb +108 -0
  95. data/test/hexapdf/test_dictionary_fields.rb +22 -26
  96. data/test/hexapdf/test_document.rb +3 -3
  97. data/test/hexapdf/test_reference.rb +1 -0
  98. data/test/hexapdf/test_writer.rb +2 -2
  99. data/test/hexapdf/type/test_font_true_type.rb +25 -0
  100. data/test/hexapdf/type/test_font_type1.rb +6 -0
  101. data/test/hexapdf/type/test_font_type3.rb +26 -0
  102. data/test/hexapdf/type/test_image.rb +10 -0
  103. data/test/hexapdf/type/test_page.rb +114 -0
  104. data/test/test_helper.rb +1 -1
  105. metadata +65 -17
  106. data/examples/text_layouter_shapes.rb +0 -170
@@ -0,0 +1,246 @@
1
+ # -*- encoding: utf-8; frozen_string_literal: true -*-
2
+ #
3
+ #--
4
+ # This file is part of HexaPDF.
5
+ #
6
+ # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
+ # Copyright (C) 2014-2017 Thomas Leitner
8
+ #
9
+ # HexaPDF is free software: you can redistribute it and/or modify it
10
+ # under the terms of the GNU Affero General Public License version 3 as
11
+ # published by the Free Software Foundation with the addition of the
12
+ # following permission added to Section 15 as permitted in Section 7(a):
13
+ # FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
14
+ # THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
15
+ # INFRINGEMENT OF THIRD PARTY RIGHTS.
16
+ #
17
+ # HexaPDF is distributed in the hope that it will be useful, but WITHOUT
18
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
20
+ # License for more details.
21
+ #
22
+ # You should have received a copy of the GNU Affero General Public License
23
+ # along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
24
+ #
25
+ # The interactive user interfaces in modified source and object code
26
+ # versions of HexaPDF must display Appropriate Legal Notices, as required
27
+ # under Section 5 of the GNU Affero General Public License version 3.
28
+ #
29
+ # In accordance with Section 7(b) of the GNU Affero General Public
30
+ # License, a covered work must retain the producer line in every PDF that
31
+ # is created or manipulated using HexaPDF.
32
+ #++
33
+
34
+ module HexaPDF
35
+ module Layout
36
+
37
+ # Utility class for generating width specifications for TextLayouter#fit from polygons.
38
+ class WidthFromPolygon
39
+
40
+ # Creates a new object for the given polygon (or polygon set) and immediately prepares it so
41
+ # that #call can be used.
42
+ #
43
+ # The offset argument specifies the vertical offset from the top at which calculations
44
+ # should start.
45
+ def initialize(polygon, offset = 0)
46
+ @polygon = polygon
47
+ prepare(offset)
48
+ end
49
+
50
+ # Returns the width specification for the given values with respect to the wrapped polygon.
51
+ def call(height, line_height)
52
+ width(@max_y - height - line_height, @max_y - height)
53
+ end
54
+
55
+ private
56
+
57
+ # Calculates the width specification for the area between the horizontal lines at y1 < y2.
58
+ #
59
+ # The following algorithm is used: Given y1 < y2 as the horizontal lines between which text
60
+ # should be layed out, and a polygon set p that is not self-intersecting but may have
61
+ # arbitrarily nested holes:
62
+ #
63
+ # * Get all segments of the polygon set in sequence, removing the horizontal segments in the
64
+ # process (done in #prepare).
65
+ #
66
+ # * Make sure that the first segment represents a left-most outside-inside transition,
67
+ # rotate array of segments (separate for each polygon) if necessary. (done in #prepare)
68
+ #
69
+ # * For the segments of each polygon do separately:
70
+ #
71
+ # * Ignore all segments except those with min_y < y2 and max_y > y1.
72
+ #
73
+ # * Determine the min_x and max_x of the segment within y1 <= y2.
74
+ #
75
+ # * If the segment crosses both, y1 and y2, store min_x/max_x and this segment is
76
+ # finished. Otherwise traverse the segments in-order to find the next crossing, updating
77
+ # min_x/max_x in the process. If it crosses the other line, the result is the same as if
78
+ # a single segment had crossed both lines. Otherwise the result depends on whether the
79
+ # segment sequence represents an outside-inside transition (it is ignored) or
80
+ # inside-outside transition (store two pairs min_x/min_x and max_x/max_x).
81
+ #
82
+ # * Order stored x-values.
83
+ #
84
+ # * For each pair [a_min, a_max], [b_min, b_max]
85
+ # - if inside (index is even): calculate width = b_min - a_max
86
+ # - if outside: calculate offset = b_max - a_min
87
+ #
88
+ # * Prepend a0_max for first offset and remove all offset-width pairs where width is zero.
89
+ def width(y1, y2)
90
+ result = []
91
+
92
+ @polygon_segments.each do |segments|
93
+ temp_result = []
94
+ status = if segments.first[0].start_point.y > y2 || segments.first[0].start_point.y < y1
95
+ :outside
96
+ else
97
+ :inside
98
+ end
99
+
100
+ segments.each do |_segment, miny, maxy, minyx, maxyx, vertical, slope, intercept|
101
+ next unless miny < y2 && maxy > y1
102
+
103
+ if vertical
104
+ min_x = max_x = minyx
105
+ else
106
+ min_x = (miny <= y1 ? (y1 - intercept) / slope : (miny <= y2 ? minyx : maxyx))
107
+ max_x = (maxy >= y2 ? (y2 - intercept) / slope : (miny >= y1 ? minyx : maxyx))
108
+ min_x, max_x = max_x, min_x if min_x > max_x
109
+ end
110
+
111
+ if miny <= y1 && maxy >= y2 # segment crosses both lines
112
+ temp_result << [min_x, max_x, :crossed_both]
113
+ elsif miny <= y1 # segment crosses bottom line
114
+ if status == :outside
115
+ temp_result << [min_x, max_x, :crossed_bottom]
116
+ status = :inside
117
+ elsif temp_result.last
118
+ temp_result.last[0] = min_x if temp_result.last[0] > min_x
119
+ temp_result.last[1] = max_x if temp_result.last[1] < max_x
120
+ temp_result.last[2] = :crossed_both if temp_result.last[2] == :crossed_top
121
+ temp_result.last[2] = :crossed_bottom if temp_result.last[2] == :crossed_none
122
+ status = :outside
123
+ else
124
+ temp_result << [min_x, max_x, :crossed_bottom]
125
+ status = :outside
126
+ end
127
+ elsif maxy >= y2 # segment crosses top line
128
+ if status == :outside
129
+ temp_result << [min_x, max_x, :crossed_top]
130
+ status = :inside
131
+ elsif temp_result.last
132
+ temp_result.last[0] = min_x if temp_result.last[0] > min_x
133
+ temp_result.last[1] = max_x if temp_result.last[1] < max_x
134
+ temp_result.last[2] = :crossed_both if temp_result.last[2] == :crossed_bottom
135
+ temp_result.last[2] = :crossed_top if temp_result.last[2] == :crossed_none
136
+ status = :outside
137
+ else
138
+ temp_result << [min_x, max_x, :crossed_top]
139
+ status = :outside
140
+ end
141
+ elsif status == :inside && temp_result.last # segment crosses no line
142
+ temp_result.last[0] = min_x if temp_result.last[0] > min_x
143
+ temp_result.last[1] = max_x if temp_result.last[1] < max_x
144
+ else # first segment completely inside
145
+ temp_result << [min_x, max_x, :crossed_none]
146
+ end
147
+ end
148
+
149
+ if temp_result.empty? # Ignore degenerate results
150
+ next
151
+ elsif temp_result.size == 1
152
+ # either polygon completely inside or just the top/bottom part, handle the same
153
+ temp_result[0][2] = :crossed_top
154
+ elsif temp_result[0][2] != :crossed_both && temp_result[-1][2] != :crossed_both
155
+ # Handle case where first and last segments only crosses one line
156
+ temp_result[0][0] = temp_result[-1][0] if temp_result[0][0] > temp_result[-1][0]
157
+ temp_result[0][1] = temp_result[-1][1] if temp_result[0][1] < temp_result[-1][1]
158
+ temp_result[0][2] = :crossed_both if temp_result[0][2] != temp_result[-1][2]
159
+ temp_result.pop
160
+ end
161
+
162
+ result.concat(temp_result)
163
+ end
164
+
165
+ temp_result = result
166
+ outside = true
167
+ temp_result.sort_by! {|a| a[0] }.map! do |min, max, stat|
168
+ if stat == :crossed_both
169
+ outside = !outside
170
+ [min, max]
171
+ elsif outside
172
+ []
173
+ else
174
+ [min, min, max, max]
175
+ end
176
+ end.flatten!
177
+ temp_result.unshift(0, 0)
178
+
179
+ i = 0
180
+ result = []
181
+ while i < temp_result.size - 2
182
+ if i % 4 == 2 # inside the polygon, i.e. width (min2 - max1)
183
+ if (width = temp_result[i + 2] - temp_result[i + 1]) > 0
184
+ result << width
185
+ else
186
+ result.pop # remove last offset and don't add width
187
+ end
188
+ else # outside the polygon, i.e. offset (max2 - min1)
189
+ result << temp_result[i + 3] - temp_result[i + 0]
190
+ end
191
+ i += 2
192
+ end
193
+ result.empty? ? [0, 0] : result
194
+ end
195
+
196
+ # Prepare the segments and other data for later use.
197
+ def prepare(offset)
198
+ @max_y = @polygon.bbox.max_y - offset
199
+ @polygon_segments = if @polygon.nr_of_contours > 1
200
+ @polygon.polygons.map {|polygon| process_polygon(polygon) }
201
+ else
202
+ [process_polygon(@polygon)]
203
+ end
204
+ end
205
+
206
+ # Processes the given polygon segment by segment and returns an array with the following
207
+ # processing information for each segment of the polygon:
208
+ #
209
+ # * the segment itself
210
+ # * minimum y-value
211
+ # * maximum y-value
212
+ # * x-value corresponding to the minimum y-value
213
+ # * x-value corresponding to the maximum y-value
214
+ # * whether the segment is vertical
215
+ # * for non-vertical segments: slope and y-intercept of the segment
216
+ #
217
+ # Additionally, the returned array is rotated sothat the data for the segment with the
218
+ # minimum x-value is the first item (without changing the order).
219
+ def process_polygon(polygon)
220
+ rotate_nr = 0
221
+ min_x = Float::INFINITY
222
+ segments = polygon.each_segment.reject(&:horizontal?)
223
+ segments.map!.with_index do |segment, index|
224
+ (rotate_nr = index; min_x = segment.min.x) if segment.min.x < min_x
225
+ data = [segment]
226
+ if segment.start_point.y < segment.end_point.y
227
+ data.push(segment.start_point.y, segment.end_point.y,
228
+ segment.start_point.x, segment.end_point.x)
229
+ else
230
+ data.push(segment.end_point.y, segment.start_point.y,
231
+ segment.end_point.x, segment.start_point.x)
232
+ end
233
+ data.push(segment.vertical?)
234
+ unless segment.vertical?
235
+ data.push(segment.slope)
236
+ data.push((segment.start_point.y - segment.slope * segment.start_point.x).to_f)
237
+ end
238
+ data
239
+ end
240
+ segments.rotate!(rotate_nr)
241
+ end
242
+
243
+ end
244
+
245
+ end
246
+ end
@@ -43,30 +43,30 @@ module HexaPDF
43
43
  # This class simplifies the usage of rectangles by automatically normalizing the coordinates so
44
44
  # that they are in the order:
45
45
  #
46
- # [llx, lly, urx, ury]
46
+ # [left, bottom, right, top]
47
47
  #
48
- # where +llx+ is the lower-left x-coordinate, +lly+ is the lower-left y-coordinate, +urx+ is the
49
- # upper-right x-coordinate and +ury+ is the upper-right y-coordinate.
48
+ # where +left+ is the bottom left x-coordinate, +bottom+ is the bottom left y-coordinate, +right+
49
+ # is the top right x-coordinate and +top+ is the top right y-coordinate.
50
50
  #
51
51
  # See: PDF1.7 s7.9.5
52
52
  class Rectangle < HexaPDF::Object
53
53
 
54
- # Returns the x-coordinate of the lower-left corner.
54
+ # Returns the x-coordinate of the bottom-left corner.
55
55
  def left
56
56
  value[0]
57
57
  end
58
58
 
59
- # Returns the x-coordinate of the upper-right corner.
59
+ # Returns the x-coordinate of the top-right corner.
60
60
  def right
61
61
  value[2]
62
62
  end
63
63
 
64
- # Returns the y-coordinate of the lower-left corner.
64
+ # Returns the y-coordinate of the bottom-left corner.
65
65
  def bottom
66
66
  value[1]
67
67
  end
68
68
 
69
- # Returns the y-coordinate of the upper-right corner.
69
+ # Returns the y-coordinate of the top-right corner.
70
70
  def top
71
71
  value[3]
72
72
  end
@@ -83,8 +83,8 @@ module HexaPDF
83
83
 
84
84
  private
85
85
 
86
- # Ensures that the value is an array containing four numbers that specify the lower-left and
87
- # upper-right corner.
86
+ # Ensures that the value is an array containing four numbers that specify the bottom left and
87
+ # top right corner.
88
88
  def after_data_change
89
89
  super
90
90
  unless value.kind_of?(Array) && value.size == 4 && value.all? {|i| i.kind_of?(Numeric) }
@@ -116,10 +116,10 @@ module HexaPDF
116
116
 
117
117
  define_field :Length, type: Integer # not required, will be auto-filled when writing
118
118
  define_field :Filter, type: [Symbol, Array]
119
- define_field :DecodeParms, type: [Dictionary, Hash, Array]
119
+ define_field :DecodeParms, type: [Dictionary, Array]
120
120
  define_field :F, type: :Filespec, version: '1.2'
121
121
  define_field :FFilter, type: [Symbol, Array], version: '1.2'
122
- define_field :FDecodeParms, type: [Dictionary, Hash, Array], version: '1.2'
122
+ define_field :FDecodeParms, type: [Dictionary, Array], version: '1.2'
123
123
  define_field :DL, type: Integer
124
124
 
125
125
  # Stream objects must always be indirect.
@@ -66,6 +66,7 @@ module HexaPDF
66
66
  autoload(:FontTrueType, 'hexapdf/type/font_true_type')
67
67
  autoload(:FontType0, 'hexapdf/type/font_type0')
68
68
  autoload(:CIDFont, 'hexapdf/type/cid_font')
69
+ autoload(:FontType3, 'hexapdf/type/font_type3')
69
70
 
70
71
  end
71
72
 
@@ -48,7 +48,7 @@ module HexaPDF
48
48
 
49
49
  define_field :Type, type: Symbol, default: type
50
50
  define_field :S, type: Symbol, required: true
51
- define_field :Next, type: [Dictionary, Hash, Array], version: '1.2'
51
+ define_field :Next, type: [Dictionary, Array], version: '1.2'
52
52
 
53
53
  end
54
54
 
@@ -46,7 +46,7 @@ module HexaPDF
46
46
  define_field :T, type: String, version: '1.1'
47
47
  define_field :Popup, type: :Annotation, version: '1.3'
48
48
  define_field :CA, type: Numeric, default: 1.0, version: '1.4'
49
- define_field :RC, type: [String, Stream], version: '1.5'
49
+ define_field :RC, type: [Stream, String], version: '1.5'
50
50
  define_field :CreationDate, type: PDFDate, version: '1.5'
51
51
  define_field :IRT, type: Dictionary, version: '1.5'
52
52
  define_field :Subj, type: String, version: '1.5'
@@ -61,7 +61,7 @@ module HexaPDF
61
61
  define_field :PageMode, type: Symbol, default: :UseNone
62
62
  define_field :Outlines, type: Dictionary, indirect: true
63
63
  define_field :Threads, type: Array, version: '1.1'
64
- define_field :OpenAction, type: [Array, Dictionary, Hash], version: '1.1'
64
+ define_field :OpenAction, type: [Dictionary, Array], version: '1.1'
65
65
  define_field :AA, type: Dictionary, version: '1.4'
66
66
  define_field :URI, type: Dictionary, version: '1.1'
67
67
  define_field :AcroForm, type: Dictionary, version: '1.2'
@@ -44,7 +44,8 @@ module HexaPDF
44
44
 
45
45
  DEFAULT_WIDTH = 1000 # :nodoc:
46
46
 
47
- define_field :CIDSystemInfo, type: [Hash, Dictionary], required: true
47
+ define_field :BaseFont, type: Symbol, required: true
48
+ define_field :CIDSystemInfo, type: Dictionary, required: true
48
49
  define_field :FontDescriptor, type: :FontDescriptor, indirect: true, required: true
49
50
  define_field :DW, type: Integer, default: DEFAULT_WIDTH
50
51
  define_field :W, type: Array
@@ -45,7 +45,6 @@ module HexaPDF
45
45
  define_type :Font
46
46
 
47
47
  define_field :Type, type: Symbol, required: true, default: type
48
- define_field :BaseFont, type: Symbol, required: true
49
48
  define_field :ToUnicode, type: Stream, version: '1.2'
50
49
 
51
50
  # Font objects must always be indirect.
@@ -69,7 +69,7 @@ module HexaPDF
69
69
  define_field :FontFile, type: Stream
70
70
  define_field :FontFile2, type: Stream, version: '1.1'
71
71
  define_field :FontFile3, type: Stream, version: '1.2'
72
- define_field :CharSet, type: [String, PDFByteString], version: '1.1'
72
+ define_field :CharSet, type: [PDFByteString, String], version: '1.1'
73
73
 
74
74
  define_field :Style, type: Dictionary
75
75
  define_field :Lang, type: Symbol, version: '1.5'
@@ -48,7 +48,7 @@ module HexaPDF
48
48
  define_field :LastChar, type: Integer
49
49
  define_field :Widths, type: Array
50
50
  define_field :FontDescriptor, type: :FontDescriptor, indirect: true
51
- define_field :Encoding, type: [Symbol, Dictionary, Hash]
51
+ define_field :Encoding, type: [Dictionary, Symbol]
52
52
 
53
53
  # Returns the encoding object used for this font.
54
54
  #
@@ -60,7 +60,7 @@ module HexaPDF
60
60
  encoding = HexaPDF::Font::Encoding.for_name(val)
61
61
  encoding = encoding_from_font if encoding.nil?
62
62
  encoding
63
- when HexaPDF::Dictionary, Hash
63
+ when HexaPDF::Dictionary
64
64
  encoding = val[:BaseEncoding] && HexaPDF::Font::Encoding.for_name(val[:BaseEncoding])
65
65
  encoding ||= if embedded? || symbolic?
66
66
  encoding_from_font
@@ -164,7 +164,7 @@ module HexaPDF
164
164
  super()
165
165
  return if ignore_missing_font_fields
166
166
 
167
- [:FirstChar, :LastChar, :Widths, :FontDescriptor].each do |field|
167
+ [:FirstChar, :LastChar, :Widths].each do |field|
168
168
  yield("Required field #{field} is not set", false) if self[field].nil?
169
169
  end
170
170
  if self[:Widths].length != (self[:LastChar] - self[:FirstChar] + 1)
@@ -40,6 +40,14 @@ module HexaPDF
40
40
  class FontTrueType < FontSimple
41
41
 
42
42
  define_field :Subtype, type: Symbol, required: true, default: :TrueType
43
+ define_field :BaseFont, type: Symbol, required: true
44
+
45
+ private
46
+
47
+ def perform_validation
48
+ super
49
+ yield("Required field FontDescriptor is not set", false) if self[:FontDescriptor].nil?
50
+ end
43
51
 
44
52
  end
45
53
 
@@ -49,7 +49,8 @@ module HexaPDF
49
49
  class FontType0 < Font
50
50
 
51
51
  define_field :Subtype, type: Symbol, required: true, default: :Type0
52
- define_field :Encoding, type: [Symbol, Stream], required: true
52
+ define_field :BaseFont, type: Symbol, required: true
53
+ define_field :Encoding, type: [Stream, Symbol], required: true
53
54
  define_field :DescendantFonts, type: Array, required: true
54
55
 
55
56
  # Returns the CID font of this type 0 font.
@@ -100,6 +100,7 @@ module HexaPDF
100
100
  end
101
101
 
102
102
  define_field :Subtype, type: Symbol, required: true, default: :Type1
103
+ define_field :BaseFont, type: Symbol, required: true
103
104
 
104
105
  # Returns the unscaled width of the given code point in glyph units, or 0 if the width for the
105
106
  # code point is missing.
@@ -153,7 +154,12 @@ module HexaPDF
153
154
 
154
155
  # Validates the Type1 font dictionary.
155
156
  def perform_validation
156
- super(ignore_missing_font_fields: StandardFonts.standard_font?(self[:BaseFont]))
157
+ std_font = StandardFonts.standard_font?(self[:BaseFont])
158
+ super(ignore_missing_font_fields: std_font)
159
+
160
+ if !std_font && self[:FontDescriptor].nil?
161
+ yield("Required field FontDescriptor is not set", false)
162
+ end
157
163
  end
158
164
 
159
165
  end