hexapdf 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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