hexapdf 0.7.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +39 -1
- data/CONTRIBUTERS +1 -1
- data/LICENSE +3 -0
- data/README.md +2 -1
- data/Rakefile +3 -1
- data/VERSION +1 -1
- data/examples/{hello_world.rb → 001-hello_world.rb} +0 -0
- data/examples/{graphics.rb → 002-graphics.rb} +1 -1
- data/examples/{arc.rb → 003-arcs.rb} +2 -2
- data/examples/{optimizing.rb → 004-optimizing.rb} +0 -0
- data/examples/{merging.rb → 005-merging.rb} +0 -0
- data/examples/{standard_pdf_fonts.rb → 006-standard_pdf_fonts.rb} +0 -0
- data/examples/{truetype.rb → 007-truetype.rb} +0 -0
- data/examples/{show_char_bboxes.rb → 008-show_char_bboxes.rb} +0 -0
- data/examples/{text_layouter_alignment.rb → 009-text_layouter_alignment.rb} +3 -3
- data/examples/{text_layouter_inline_boxes.rb → 010-text_layouter_inline_boxes.rb} +7 -9
- data/examples/{text_layouter_line_wrapping.rb → 011-text_layouter_line_wrapping.rb} +6 -5
- data/examples/{text_layouter_styling.rb → 012-text_layouter_styling.rb} +6 -8
- data/examples/013-text_layouter_shapes.rb +176 -0
- data/examples/014-text_in_polygon.rb +60 -0
- data/examples/{boxes.rb → 015-boxes.rb} +29 -21
- data/examples/016-frame_automatic_box_placement.rb +90 -0
- data/examples/017-frame_text_flow.rb +60 -0
- data/lib/hexapdf/cli/command.rb +4 -3
- data/lib/hexapdf/cli/files.rb +1 -1
- data/lib/hexapdf/cli/inspect.rb +0 -1
- data/lib/hexapdf/cli/merge.rb +1 -1
- data/lib/hexapdf/cli/modify.rb +1 -1
- data/lib/hexapdf/configuration.rb +2 -0
- data/lib/hexapdf/content/canvas.rb +3 -3
- data/lib/hexapdf/content/graphic_object.rb +1 -0
- data/lib/hexapdf/content/graphic_object/geom2d.rb +132 -0
- data/lib/hexapdf/dictionary.rb +7 -1
- data/lib/hexapdf/dictionary_fields.rb +35 -83
- data/lib/hexapdf/document.rb +9 -5
- data/lib/hexapdf/document/fonts.rb +1 -1
- data/lib/hexapdf/encryption/standard_security_handler.rb +1 -1
- data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
- data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
- data/lib/hexapdf/font/cmap/writer.rb +2 -2
- data/lib/hexapdf/font/true_type/builder.rb +1 -1
- data/lib/hexapdf/font/true_type/table.rb +1 -1
- data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
- data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +3 -3
- data/lib/hexapdf/font/true_type/table/kern.rb +1 -1
- data/lib/hexapdf/font/true_type/table/post.rb +1 -1
- data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
- data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
- data/lib/hexapdf/image_loader/jpeg.rb +1 -1
- data/lib/hexapdf/image_loader/png.rb +2 -2
- data/lib/hexapdf/layout.rb +3 -0
- data/lib/hexapdf/layout/box.rb +64 -46
- data/lib/hexapdf/layout/frame.rb +348 -0
- data/lib/hexapdf/layout/inline_box.rb +2 -2
- data/lib/hexapdf/layout/line.rb +3 -3
- data/lib/hexapdf/layout/style.rb +81 -14
- data/lib/hexapdf/layout/text_box.rb +84 -0
- data/lib/hexapdf/layout/text_fragment.rb +8 -8
- data/lib/hexapdf/layout/text_layouter.rb +278 -169
- data/lib/hexapdf/layout/width_from_polygon.rb +246 -0
- data/lib/hexapdf/rectangle.rb +9 -9
- data/lib/hexapdf/stream.rb +2 -2
- data/lib/hexapdf/type.rb +1 -0
- data/lib/hexapdf/type/action.rb +1 -1
- data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
- data/lib/hexapdf/type/catalog.rb +1 -1
- data/lib/hexapdf/type/cid_font.rb +2 -1
- data/lib/hexapdf/type/font.rb +0 -1
- data/lib/hexapdf/type/font_descriptor.rb +1 -1
- data/lib/hexapdf/type/font_simple.rb +3 -3
- data/lib/hexapdf/type/font_true_type.rb +8 -0
- data/lib/hexapdf/type/font_type0.rb +2 -1
- data/lib/hexapdf/type/font_type1.rb +7 -1
- data/lib/hexapdf/type/font_type3.rb +61 -0
- data/lib/hexapdf/type/graphics_state_parameter.rb +8 -8
- data/lib/hexapdf/type/image.rb +10 -0
- data/lib/hexapdf/type/page.rb +83 -10
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/common_tokenizer_tests.rb +2 -2
- data/test/hexapdf/content/graphic_object/test_geom2d.rb +79 -0
- data/test/hexapdf/encryption/test_standard_security_handler.rb +1 -1
- data/test/hexapdf/font/test_true_type_wrapper.rb +1 -1
- data/test/hexapdf/font/test_type1_wrapper.rb +1 -1
- data/test/hexapdf/font/true_type/table/test_cmap.rb +1 -1
- data/test/hexapdf/font/true_type/table/test_directory.rb +1 -1
- data/test/hexapdf/font/true_type/table/test_head.rb +7 -3
- data/test/hexapdf/layout/test_box.rb +57 -15
- data/test/hexapdf/layout/test_frame.rb +313 -0
- data/test/hexapdf/layout/test_inline_box.rb +1 -1
- data/test/hexapdf/layout/test_style.rb +74 -0
- data/test/hexapdf/layout/test_text_box.rb +77 -0
- data/test/hexapdf/layout/test_text_layouter.rb +220 -239
- data/test/hexapdf/layout/test_width_from_polygon.rb +108 -0
- data/test/hexapdf/test_dictionary_fields.rb +22 -26
- data/test/hexapdf/test_document.rb +3 -3
- data/test/hexapdf/test_reference.rb +1 -0
- data/test/hexapdf/test_writer.rb +2 -2
- data/test/hexapdf/type/test_font_true_type.rb +25 -0
- data/test/hexapdf/type/test_font_type1.rb +6 -0
- data/test/hexapdf/type/test_font_type3.rb +26 -0
- data/test/hexapdf/type/test_image.rb +10 -0
- data/test/hexapdf/type/test_page.rb +114 -0
- data/test/test_helper.rb +1 -1
- metadata +65 -17
- 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
|
data/lib/hexapdf/rectangle.rb
CHANGED
@@ -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
|
-
# [
|
46
|
+
# [left, bottom, right, top]
|
47
47
|
#
|
48
|
-
# where +
|
49
|
-
#
|
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
|
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
|
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
|
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
|
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
|
87
|
-
#
|
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) }
|
data/lib/hexapdf/stream.rb
CHANGED
@@ -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,
|
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,
|
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.
|
data/lib/hexapdf/type.rb
CHANGED
data/lib/hexapdf/type/action.rb
CHANGED
@@ -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,
|
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: [
|
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'
|
data/lib/hexapdf/type/catalog.rb
CHANGED
@@ -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: [
|
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 :
|
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
|
data/lib/hexapdf/type/font.rb
CHANGED
@@ -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: [
|
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: [
|
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
|
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
|
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 :
|
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
|
-
|
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
|