hexapdf 0.43.0 → 0.44.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -0
- data/examples/030-pdfa.rb +1 -0
- data/lib/hexapdf/composer.rb +1 -0
- data/lib/hexapdf/document/files.rb +7 -2
- data/lib/hexapdf/document/metadata.rb +12 -1
- data/lib/hexapdf/layout/box.rb +160 -61
- data/lib/hexapdf/layout/box_fitter.rb +1 -0
- data/lib/hexapdf/layout/column_box.rb +22 -24
- data/lib/hexapdf/layout/container_box.rb +2 -2
- data/lib/hexapdf/layout/frame.rb +13 -95
- data/lib/hexapdf/layout/image_box.rb +4 -4
- data/lib/hexapdf/layout/list_box.rb +11 -19
- data/lib/hexapdf/layout/style.rb +5 -1
- data/lib/hexapdf/layout/table_box.rb +48 -55
- data/lib/hexapdf/layout/text_box.rb +27 -42
- data/lib/hexapdf/parser.rb +5 -2
- data/lib/hexapdf/type/file_specification.rb +9 -5
- data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/document/test_files.rb +5 -0
- data/test/hexapdf/document/test_metadata.rb +21 -0
- data/test/hexapdf/layout/test_box.rb +82 -37
- data/test/hexapdf/layout/test_box_fitter.rb +7 -0
- data/test/hexapdf/layout/test_column_box.rb +7 -13
- data/test/hexapdf/layout/test_container_box.rb +1 -1
- data/test/hexapdf/layout/test_frame.rb +0 -48
- data/test/hexapdf/layout/test_image_box.rb +14 -6
- data/test/hexapdf/layout/test_list_box.rb +25 -26
- data/test/hexapdf/layout/test_table_box.rb +39 -53
- data/test/hexapdf/layout/test_text_box.rb +38 -66
- data/test/hexapdf/test_composer.rb +6 -0
- data/test/hexapdf/test_parser.rb +8 -0
- data/test/hexapdf/type/test_file_specification.rb +2 -1
- metadata +2 -2
data/lib/hexapdf/layout/frame.rb
CHANGED
@@ -45,7 +45,7 @@ module HexaPDF
|
|
45
45
|
#
|
46
46
|
# == Usage
|
47
47
|
#
|
48
|
-
# After a Frame object is initialized, it is ready for
|
48
|
+
# After a Frame object is initialized, it is ready for fitting boxes in it and drawing them.
|
49
49
|
#
|
50
50
|
# The explicit way of drawing a box follows these steps:
|
51
51
|
#
|
@@ -54,7 +54,7 @@ module HexaPDF
|
|
54
54
|
#
|
55
55
|
# The method #fit is also called for absolutely positioned boxes but since these boxes are not
|
56
56
|
# subject to the normal constraints, the provided available width and height are the width and
|
57
|
-
# height inside the frame to the right and top of the bottom
|
57
|
+
# height inside the frame to the right and top of the bottom left corner of the box.
|
58
58
|
#
|
59
59
|
# * If the box didn't fit, call #find_next_region to determine the next region for placing the
|
60
60
|
# box. If a new region was found, start over with #fit. Otherwise the frame has no more space
|
@@ -65,10 +65,6 @@ module HexaPDF
|
|
65
65
|
# splitting is successful, the first box can be drawn (Make sure that the second box is
|
66
66
|
# handled correctly). Otherwise, start over with #find_next_region.
|
67
67
|
#
|
68
|
-
# For applications where splitting is not necessary, an easier way is to just use #draw and
|
69
|
-
# #find_next_region together, as #draw calls #fit if the box was not fit into the current
|
70
|
-
# region.
|
71
|
-
#
|
72
68
|
# == Used Box Properties
|
73
69
|
#
|
74
70
|
# The style properties 'position', 'align', 'valign', 'margin' and 'mask_mode' are taken into
|
@@ -88,84 +84,10 @@ module HexaPDF
|
|
88
84
|
|
89
85
|
include HexaPDF::Utils
|
90
86
|
|
91
|
-
#
|
92
|
-
class FitResult
|
93
|
-
|
94
|
-
# The frame into which the box was fitted.
|
95
|
-
attr_accessor :frame
|
96
|
-
|
97
|
-
# The box that was fitted into the frame.
|
98
|
-
attr_accessor :box
|
99
|
-
|
100
|
-
# The horizontal position where the box will be drawn.
|
101
|
-
attr_accessor :x
|
102
|
-
|
103
|
-
# The vertical position where the box will be drawn.
|
104
|
-
attr_accessor :y
|
105
|
-
|
106
|
-
# The available width in the frame for this particular box.
|
107
|
-
attr_accessor :available_width
|
108
|
-
|
109
|
-
# The available height in the frame for this particular box.
|
110
|
-
attr_accessor :available_height
|
111
|
-
|
112
|
-
# The rectangle (a Geom2D::Rectangle object) that will be removed from the frame when
|
113
|
-
# drawing the box.
|
114
|
-
attr_accessor :mask
|
115
|
-
|
116
|
-
# Initialize the result object for the given frame and box.
|
117
|
-
def initialize(frame, box)
|
118
|
-
@frame = frame
|
119
|
-
@box = box
|
120
|
-
@available_width = 0
|
121
|
-
@available_height = 0
|
122
|
-
@success = false
|
123
|
-
end
|
124
|
-
|
125
|
-
# Marks the fitting status as success.
|
126
|
-
def success!
|
127
|
-
@success = true
|
128
|
-
end
|
129
|
-
|
130
|
-
# Returns +true+ if fitting was successful.
|
131
|
-
def success?
|
132
|
-
@success
|
133
|
-
end
|
134
|
-
|
135
|
-
# Draws the #box onto the canvas at (#x + *dx*, #y + *dy*).
|
136
|
-
#
|
137
|
-
# The relative offset (dx, dy) is useful when rendering results that were accumulated and
|
138
|
-
# then need to be moved because the container holding them changes its position.
|
139
|
-
#
|
140
|
-
# The configuration option "debug" can be used to add visual debug output with respect to
|
141
|
-
# box placement.
|
142
|
-
def draw(canvas, dx: 0, dy: 0)
|
143
|
-
doc = canvas.context.document
|
144
|
-
if doc.config['debug']
|
145
|
-
name = (frame.parent_boxes + [box]).map do |box|
|
146
|
-
box.class.to_s.sub(/.*::/, '')
|
147
|
-
end.join('-') << "##{box.object_id}"
|
148
|
-
name = "#{name} (#{(x + dx).to_i},#{(y + dy).to_i}-#{mask.width.to_i}x#{mask.height.to_i})"
|
149
|
-
ocg = doc.optional_content.ocg(name)
|
150
|
-
canvas.optional_content(ocg) do
|
151
|
-
canvas.translate(dx, dy) do
|
152
|
-
canvas.fill_color("green").stroke_color("darkgreen").
|
153
|
-
opacity(fill_alpha: 0.1, stroke_alpha: 0.2).
|
154
|
-
draw(:geom2d, object: mask, path_only: true).fill_stroke
|
155
|
-
end
|
156
|
-
end
|
157
|
-
page = "Page #{canvas.context.index + 1}" rescue "XObject"
|
158
|
-
doc.optional_content.default_configuration.add_ocg_to_ui(ocg, path: ['Debug', page])
|
159
|
-
end
|
160
|
-
box.draw(canvas, x + dx, y + dy)
|
161
|
-
end
|
162
|
-
|
163
|
-
end
|
164
|
-
|
165
|
-
# The x-coordinate of the bottom-left corner.
|
87
|
+
# The x-coordinate of the bottom left corner.
|
166
88
|
attr_reader :left
|
167
89
|
|
168
|
-
# The y-coordinate of the bottom
|
90
|
+
# The y-coordinate of the bottom left corner.
|
169
91
|
attr_reader :bottom
|
170
92
|
|
171
93
|
# The width of the frame.
|
@@ -253,16 +175,15 @@ module HexaPDF
|
|
253
175
|
@context&.document
|
254
176
|
end
|
255
177
|
|
256
|
-
# Fits the given box into the current region of available space and returns
|
257
|
-
# object.
|
178
|
+
# Fits the given box into the current region of available space and returns the associated
|
179
|
+
# Box::FitResult object.
|
258
180
|
#
|
259
181
|
# Fitting a box takes the style properties 'position', 'align', 'valign', 'margin', and
|
260
182
|
# 'mask_mode' into account.
|
261
183
|
#
|
262
|
-
# Use the FitResult#success? method to determine whether fitting was successful.
|
184
|
+
# Use the Box::FitResult#success? method to determine whether fitting was successful.
|
263
185
|
def fit(box)
|
264
|
-
|
265
|
-
return fit_result if full?
|
186
|
+
return Box::FitResult.new(box, frame: self) if full?
|
266
187
|
|
267
188
|
margin = box.style.margin if box.style.margin?
|
268
189
|
|
@@ -277,7 +198,7 @@ module HexaPDF
|
|
277
198
|
|
278
199
|
aw = width - x
|
279
200
|
ah = height - y
|
280
|
-
box.fit(aw, ah, self)
|
201
|
+
fit_result = box.fit(aw, ah, self)
|
281
202
|
fit_result.success!
|
282
203
|
|
283
204
|
x += left
|
@@ -294,7 +215,7 @@ module HexaPDF
|
|
294
215
|
ah -= margin_top = margin.top unless float_equal(@y, @bottom + @height)
|
295
216
|
end
|
296
217
|
|
297
|
-
fit_result
|
218
|
+
fit_result = box.fit(aw, ah, self)
|
298
219
|
|
299
220
|
width = box.width
|
300
221
|
height = box.height
|
@@ -372,27 +293,24 @@ module HexaPDF
|
|
372
293
|
create_rectangle(@x, @y - available_height, @x + available_width, @y)
|
373
294
|
end
|
374
295
|
|
375
|
-
fit_result.available_width = aw
|
376
|
-
fit_result.available_height = ah
|
377
296
|
fit_result.x = x
|
378
297
|
fit_result.y = y
|
379
298
|
fit_result.mask = rectangle
|
380
299
|
fit_result
|
381
300
|
end
|
382
301
|
|
383
|
-
# Tries to split the box of the given FitResult into two parts and returns both parts.
|
302
|
+
# Tries to split the box of the given Box::FitResult into two parts and returns both parts.
|
384
303
|
#
|
385
304
|
# See Box#split for further details.
|
386
305
|
def split(fit_result)
|
387
|
-
fit_result.box.split
|
306
|
+
fit_result.box.split
|
388
307
|
end
|
389
308
|
|
390
|
-
# Draws the box of the given FitResult onto the canvas at the fitted position.
|
309
|
+
# Draws the box of the given Box::FitResult onto the canvas at the fitted position.
|
391
310
|
#
|
392
311
|
# After a box is successfully drawn, the frame's shape is adjusted to remove the occupied
|
393
312
|
# area.
|
394
313
|
def draw(canvas, fit_result)
|
395
|
-
return if fit_result.box.height == 0 || fit_result.box.width == 0
|
396
314
|
fit_result.draw(canvas)
|
397
315
|
remove_area(fit_result.mask)
|
398
316
|
end
|
@@ -79,9 +79,11 @@ module HexaPDF
|
|
79
79
|
false
|
80
80
|
end
|
81
81
|
|
82
|
+
private
|
83
|
+
|
82
84
|
# Fits the image into the current region of the frame, taking the initially set width and
|
83
85
|
# height into account (see the class description for details).
|
84
|
-
def
|
86
|
+
def fit_content(available_width, available_height, _frame)
|
85
87
|
image_width = @image.width.to_f
|
86
88
|
image_height = @image.height.to_f
|
87
89
|
image_ratio = image_width / image_height
|
@@ -103,12 +105,10 @@ module HexaPDF
|
|
103
105
|
@height = image_height * ratio + rh
|
104
106
|
end
|
105
107
|
|
106
|
-
|
108
|
+
fit_result.success! if float_compare(@width, available_width) <= 0 &&
|
107
109
|
float_compare(@height, available_height) <= 0
|
108
110
|
end
|
109
111
|
|
110
|
-
private
|
111
|
-
|
112
112
|
# Draws the image onto the canvas at position [x, y].
|
113
113
|
def draw_content(canvas, x, y)
|
114
114
|
canvas.image(@image, at: [x, y], width: content_width, height: content_height)
|
@@ -186,8 +186,10 @@ module HexaPDF
|
|
186
186
|
super && (!@results || @results.all? {|result| result.box_fitter.fit_results.empty? })
|
187
187
|
end
|
188
188
|
|
189
|
+
private
|
190
|
+
|
189
191
|
# Fits the list box into the current region of the frame.
|
190
|
-
def
|
192
|
+
def fit_content(available_width, available_height, frame)
|
191
193
|
@width = if @initial_width > 0
|
192
194
|
@initial_width
|
193
195
|
else
|
@@ -253,15 +255,13 @@ module HexaPDF
|
|
253
255
|
|
254
256
|
@height = @results.sum(&:height) + (@results.count - 1) * item_spacing + reserved_height
|
255
257
|
|
256
|
-
@
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
258
|
+
if @results.size == @children.size && @results.all? {|r| r.box_fitter.success? }
|
259
|
+
fit_result.success!
|
260
|
+
elsif !@results.empty? && !@results[0].box_fitter.fit_results.empty?
|
261
|
+
fit_result.overflow!
|
262
|
+
end
|
261
263
|
end
|
262
264
|
|
263
|
-
private
|
264
|
-
|
265
265
|
# Removes the +content_indentation+ from the left side of the given shape (a Geom2D::PolygonSet).
|
266
266
|
def remove_indent_from_frame_shape(shape)
|
267
267
|
polygon_index = 0
|
@@ -307,7 +307,7 @@ module HexaPDF
|
|
307
307
|
end
|
308
308
|
|
309
309
|
# Splits the content of the list box. This method is called from Box#split.
|
310
|
-
def split_content
|
310
|
+
def split_content
|
311
311
|
remaining_boxes = @results[-1].box_fitter.remaining_boxes
|
312
312
|
first_is_split_box = !remaining_boxes.empty?
|
313
313
|
children = (remaining_boxes.empty? ? [] : [remaining_boxes]) + @children[@results.size..-1]
|
@@ -361,17 +361,9 @@ module HexaPDF
|
|
361
361
|
|
362
362
|
# Draws the list items onto the canvas at position [x, y].
|
363
363
|
def draw_content(canvas, x, y)
|
364
|
-
|
365
|
-
raise HexaPDF::Error, "Some items don't fit into box with limited height and " \
|
366
|
-
"style property overflow is set to :error"
|
367
|
-
end
|
364
|
+
translate = style.position != :flow && (x != @fit_x || y != @fit_y)
|
368
365
|
|
369
|
-
translate
|
370
|
-
|
371
|
-
if translate
|
372
|
-
canvas.save_graphics_state
|
373
|
-
canvas.translate(x - @draw_pos_x, y - @draw_pos_y)
|
374
|
-
end
|
366
|
+
canvas.save_graphics_state.translate(x - @fit_x, y - @fit_y) if translate
|
375
367
|
|
376
368
|
@results.each do |item_result|
|
377
369
|
box_fitter = item_result.box_fitter
|
data/lib/hexapdf/layout/style.rb
CHANGED
@@ -1254,7 +1254,11 @@ module HexaPDF
|
|
1254
1254
|
# doesn't. If a box doesn't support this value, it is positioned as if the value :default
|
1255
1255
|
# was set.
|
1256
1256
|
#
|
1257
|
-
#
|
1257
|
+
# Notes:
|
1258
|
+
#
|
1259
|
+
# * The properties #align and #valign are not used with this value.
|
1260
|
+
# * The rectangular area of the box is the rectangle containing all the flowed content.
|
1261
|
+
# That rectangle is used for drawing the border, background and so on.
|
1258
1262
|
#
|
1259
1263
|
# Examples:
|
1260
1264
|
#
|
@@ -211,21 +211,27 @@ module HexaPDF
|
|
211
211
|
@height = height
|
212
212
|
end
|
213
213
|
|
214
|
+
# :nodoc:
|
215
|
+
def inspect
|
216
|
+
"<Cell (#{row},#{column}) #{row_span}x#{col_span} #{Array(children).map(&:class)}>"
|
217
|
+
end
|
218
|
+
|
219
|
+
private
|
220
|
+
|
214
221
|
# Fits the children of the table cell into the given rectangular area.
|
215
|
-
def
|
216
|
-
@width = available_width
|
222
|
+
def fit_content(available_width, available_height, frame)
|
217
223
|
width = available_width - reserved_width
|
218
|
-
height = available_height - reserved_height
|
219
|
-
return
|
224
|
+
height = @used_height = available_height - reserved_height
|
225
|
+
return if width <= 0 || height <= 0
|
220
226
|
|
221
227
|
frame = frame.child_frame(0, 0, width, height, box: self)
|
222
228
|
case children
|
223
229
|
when Box
|
224
|
-
|
225
|
-
@preferred_width =
|
226
|
-
@height = @preferred_height =
|
227
|
-
@fit_results = [
|
228
|
-
|
230
|
+
child_result = frame.fit(children)
|
231
|
+
@preferred_width = child_result.x + child_result.box.width + reserved_width
|
232
|
+
@height = @preferred_height = child_result.box.height + reserved_height
|
233
|
+
@fit_results = [child_result]
|
234
|
+
fit_result.success! if child_result.success?
|
229
235
|
when Array
|
230
236
|
box_fitter = BoxFitter.new([frame])
|
231
237
|
children.each {|box| box_fitter.fit(box) }
|
@@ -233,34 +239,23 @@ module HexaPDF
|
|
233
239
|
@preferred_width = max_x_result.x + max_x_result.box.width + reserved_width
|
234
240
|
@height = @preferred_height = box_fitter.content_heights[0] + reserved_height
|
235
241
|
@fit_results = box_fitter.fit_results
|
236
|
-
|
242
|
+
fit_result.success! if box_fitter.success?
|
237
243
|
else
|
238
244
|
@preferred_width = reserved_width
|
239
245
|
@height = @preferred_height = reserved_height
|
240
246
|
@fit_results = []
|
241
|
-
|
247
|
+
fit_result.success!
|
242
248
|
end
|
243
249
|
end
|
244
250
|
|
245
|
-
# :nodoc:
|
246
|
-
def inspect
|
247
|
-
"<Cell (#{row},#{column}) #{row_span}x#{col_span} #{Array(children).map(&:class)}>"
|
248
|
-
end
|
249
|
-
|
250
|
-
private
|
251
|
-
|
252
251
|
# Draws the content of the cell.
|
253
252
|
def draw_content(canvas, x, y)
|
254
253
|
return if @fit_results.empty?
|
255
254
|
|
256
255
|
# available_width is always equal to content_width but we need to adjust for the
|
257
256
|
# difference in the y direction between fitting and drawing
|
258
|
-
y -= (@
|
259
|
-
@fit_results.each
|
260
|
-
#fit_result.x += x
|
261
|
-
#fit_result.y += y
|
262
|
-
fit_result.draw(canvas, dx: x, dy: y)
|
263
|
-
end
|
257
|
+
y -= (@used_height - content_height)
|
258
|
+
@fit_results.each {|fit_result| fit_result.draw(canvas, dx: x, dy: y) }
|
264
259
|
end
|
265
260
|
|
266
261
|
end
|
@@ -393,7 +388,7 @@ module HexaPDF
|
|
393
388
|
else
|
394
389
|
column_info[cell.column].last
|
395
390
|
end
|
396
|
-
unless cell.fit(available_cell_width, available_height, frame)
|
391
|
+
unless cell.fit(available_cell_width, available_height, frame).success?
|
397
392
|
row_fit = false
|
398
393
|
break
|
399
394
|
end
|
@@ -589,24 +584,23 @@ module HexaPDF
|
|
589
584
|
super && (!@last_fitted_row_index || @last_fitted_row_index < 0)
|
590
585
|
end
|
591
586
|
|
592
|
-
|
593
|
-
def fit(available_width, available_height, frame)
|
594
|
-
return false if (@initial_width > 0 && @initial_width > available_width) ||
|
595
|
-
(@initial_height > 0 && @initial_height > available_height)
|
587
|
+
private
|
596
588
|
|
589
|
+
# Fits the table into the current region of the frame.
|
590
|
+
def fit_content(_available_width, _available_height, frame)
|
597
591
|
# Adjust reserved width/height to include space used by the edge cells for their border
|
598
592
|
# since cell borders are drawn on the bounds and not inside.
|
599
|
-
# This uses the top
|
593
|
+
# This uses the top left and bottom right cells and so might not be correct in all cases.
|
600
594
|
@cell_tl_border_width = @cells[0, 0].style.border.width
|
601
595
|
cell_br_border_width = @cells[-1, -1].style.border.width
|
602
|
-
rw =
|
603
|
-
rh =
|
596
|
+
rw = (@cell_tl_border_width.left + cell_br_border_width.right) / 2.0
|
597
|
+
rh = (@cell_tl_border_width.top + cell_br_border_width.bottom) / 2.0
|
604
598
|
|
605
|
-
width =
|
606
|
-
height =
|
599
|
+
width = @width - reserved_width - rw
|
600
|
+
height = @height - reserved_height - rh
|
607
601
|
used_height = 0
|
608
602
|
columns = calculate_column_widths(width)
|
609
|
-
return
|
603
|
+
return if columns.empty?
|
610
604
|
|
611
605
|
frame = frame.child_frame(box: self)
|
612
606
|
@special_cells_fit_not_successful = false
|
@@ -616,18 +610,21 @@ module HexaPDF
|
|
616
610
|
height -= special_used_height
|
617
611
|
used_height += special_used_height
|
618
612
|
@special_cells_fit_not_successful = (last_fitted_row_index != special_cells.number_of_rows - 1)
|
619
|
-
return
|
613
|
+
return nil if @special_cells_fit_not_successful
|
620
614
|
end
|
621
615
|
|
622
616
|
main_used_height, @last_fitted_row_index = @cells.fit_rows(@start_row_index, height, columns, frame)
|
623
617
|
used_height += main_used_height
|
624
618
|
|
625
|
-
|
626
|
-
|
627
|
-
@fit_successful = (@last_fitted_row_index == @cells.number_of_rows - 1)
|
628
|
-
end
|
619
|
+
update_content_width { columns[-1].sum + rw }
|
620
|
+
update_content_height { used_height + rh }
|
629
621
|
|
630
|
-
|
622
|
+
if @last_fitted_row_index == @cells.number_of_rows - 1
|
623
|
+
fit_result.success!
|
624
|
+
elsif @last_fitted_row_index >= 0
|
625
|
+
fit_result.overflow!
|
626
|
+
end
|
627
|
+
end
|
631
628
|
|
632
629
|
# Calculates and returns the x-coordinates and widths of all columns based on the given total
|
633
630
|
# available width.
|
@@ -649,20 +646,16 @@ module HexaPDF
|
|
649
646
|
end
|
650
647
|
|
651
648
|
# Splits the content of the table box. This method is called from Box#split.
|
652
|
-
def split_content
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
footer_cells = @footer ? Cells.new(@footer.call(self), cell_style: @cell_style) : nil
|
663
|
-
box.instance_variable_set(:@footer_cells, footer_cells)
|
664
|
-
[self, box]
|
665
|
-
end
|
649
|
+
def split_content
|
650
|
+
box = create_split_box
|
651
|
+
box.instance_variable_set(:@start_row_index, @last_fitted_row_index + 1)
|
652
|
+
box.instance_variable_set(:@last_fitted_row_index, -1)
|
653
|
+
box.instance_variable_set(:@special_cells_fit_not_successful, nil)
|
654
|
+
header_cells = @header ? Cells.new(@header.call(self), cell_style: @cell_style) : nil
|
655
|
+
box.instance_variable_set(:@header_cells, header_cells)
|
656
|
+
footer_cells = @footer ? Cells.new(@footer.call(self), cell_style: @cell_style) : nil
|
657
|
+
box.instance_variable_set(:@footer_cells, footer_cells)
|
658
|
+
[self, box]
|
666
659
|
end
|
667
660
|
|
668
661
|
# Draws the child boxes onto the canvas at position [x, y].
|
@@ -43,6 +43,11 @@ module HexaPDF
|
|
43
43
|
# objects of a Frame.
|
44
44
|
#
|
45
45
|
# This class uses TextLayouter behind the scenes to do the hard work.
|
46
|
+
#
|
47
|
+
# == Used Box Properties
|
48
|
+
#
|
49
|
+
# The spacing after the last line can be controlled via the style property +last_line_gap+. Also
|
50
|
+
# see TextLayouter#style for other style properties taken into account.
|
46
51
|
class TextBox < Box
|
47
52
|
|
48
53
|
# Creates a new TextBox object with the given inline items (e.g. TextFragment and InlineBox
|
@@ -67,21 +72,27 @@ module HexaPDF
|
|
67
72
|
true
|
68
73
|
end
|
69
74
|
|
75
|
+
# :nodoc:
|
76
|
+
def draw(canvas, x, y)
|
77
|
+
super(canvas, x + @x_offset, y)
|
78
|
+
end
|
79
|
+
|
80
|
+
# :nodoc:
|
81
|
+
def empty?
|
82
|
+
super && (!@result || @result.lines.empty?)
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
70
87
|
# Fits the text box into the Frame.
|
71
88
|
#
|
72
89
|
# Depending on the 'position' style property, the text is either fit into the current region
|
73
90
|
# of the frame using +available_width+ and +available_height+, or fit to the shape of the
|
74
91
|
# frame starting from the top (when 'position' is set to :flow).
|
75
|
-
|
76
|
-
# The spacing after the last line can be controlled via the style property +last_line_gap+.
|
77
|
-
#
|
78
|
-
# Also see TextLayouter#style for other style properties taken into account.
|
79
|
-
def fit(available_width, available_height, frame)
|
80
|
-
return false if (@initial_width > 0 && @initial_width > available_width) ||
|
81
|
-
(@initial_height > 0 && @initial_height > available_height)
|
82
|
-
|
92
|
+
def fit_content(available_width, available_height, frame)
|
83
93
|
frame = frame.child_frame(box: self)
|
84
94
|
@width = @x_offset = @height = 0
|
95
|
+
|
85
96
|
@result = if style.position == :flow
|
86
97
|
@tl.fit(@items, frame.width_specification, frame.shape.bbox.height,
|
87
98
|
apply_first_text_indent: !split_box?, frame: frame)
|
@@ -92,6 +103,7 @@ module HexaPDF
|
|
92
103
|
height = (@initial_height > 0 ? @initial_height : available_height) - @height
|
93
104
|
@tl.fit(@items, width, height, apply_first_text_indent: !split_box?, frame: frame)
|
94
105
|
end
|
106
|
+
|
95
107
|
@width += if @initial_width > 0 || style.text_align == :center || style.text_align == :right
|
96
108
|
width
|
97
109
|
elsif style.position == :flow
|
@@ -114,47 +126,20 @@ module HexaPDF
|
|
114
126
|
@height += style.line_spacing.gap(@result.lines.last, @result.lines.last)
|
115
127
|
end
|
116
128
|
|
117
|
-
@result.status == :success
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
# Splits the text box into two boxes if necessary and possible.
|
122
|
-
def split(available_width, available_height, frame)
|
123
|
-
fit(available_width, available_height, frame) unless @result
|
124
|
-
|
125
|
-
if style.position != :flow && (float_compare(@width, available_width) > 0 ||
|
126
|
-
float_compare(@height, available_height) > 0)
|
127
|
-
[nil, self]
|
128
|
-
elsif @result.remaining_items.empty?
|
129
|
-
[self]
|
130
|
-
elsif @result.lines.empty?
|
131
|
-
[nil, self]
|
132
|
-
else
|
133
|
-
[self, create_box_for_remaining_items]
|
129
|
+
if @result.status == :success
|
130
|
+
fit_result.success!
|
131
|
+
elsif @result.status == :height && !@result.lines.empty?
|
132
|
+
fit_result.overflow!
|
134
133
|
end
|
135
134
|
end
|
136
135
|
|
137
|
-
#
|
138
|
-
def
|
139
|
-
|
136
|
+
# Splits the text box into two.
|
137
|
+
def split_content
|
138
|
+
[self, create_box_for_remaining_items]
|
140
139
|
end
|
141
140
|
|
142
|
-
# :nodoc:
|
143
|
-
def empty?
|
144
|
-
super && (!@result || @result.lines.empty?)
|
145
|
-
end
|
146
|
-
|
147
|
-
private
|
148
|
-
|
149
141
|
# Draws the text into the box.
|
150
142
|
def draw_content(canvas, x, y)
|
151
|
-
return unless @result
|
152
|
-
|
153
|
-
if @result.status == :height && @initial_height > 0 && style.overflow == :error
|
154
|
-
raise HexaPDF::Error, "Text doesn't fit into box with limited height and " \
|
155
|
-
"style property overflow is set to :error"
|
156
|
-
end
|
157
|
-
|
158
143
|
return if @result.lines.empty?
|
159
144
|
@result.draw(canvas, x - @x_offset, y + content_height)
|
160
145
|
end
|
data/lib/hexapdf/parser.rb
CHANGED
@@ -365,11 +365,14 @@ module HexaPDF
|
|
365
365
|
# Need to iterate through the whole lines array in case there are multiple %%EOF to try
|
366
366
|
eof_index = 0
|
367
367
|
while (eof_index = lines[0..(eof_index - 1)].rindex {|l| l.strip == '%%EOF' })
|
368
|
-
if lines[eof_index - 1].strip =~ /\Astartxref\s(\d+)\z/
|
368
|
+
if eof_index > 0 && lines[eof_index - 1].strip =~ /\Astartxref\s(\d+)\z/
|
369
369
|
startxref_offset = $1.to_i
|
370
370
|
startxref_mangled = true
|
371
371
|
break # we found it even if it the syntax is not entirely correct
|
372
|
-
elsif eof_index < 2
|
372
|
+
elsif eof_index < 2
|
373
|
+
startxref_missing = true
|
374
|
+
break
|
375
|
+
elsif lines[eof_index - 2].strip != "startxref"
|
373
376
|
startxref_missing = true
|
374
377
|
else
|
375
378
|
startxref_offset = lines[eof_index - 1].to_i
|
@@ -158,11 +158,11 @@ module HexaPDF
|
|
158
158
|
end
|
159
159
|
|
160
160
|
# :call-seq:
|
161
|
-
# file_spec.embed(filename, name: File.basename(filename), register: true) -> ef_stream
|
162
|
-
# file_spec.embed(io, name:, register: true) -> ef_stream
|
161
|
+
# file_spec.embed(filename, name: File.basename(filename), mime_type: nil, register: true) -> ef_stream
|
162
|
+
# file_spec.embed(io, name:, mime_type: nil, register: true) -> ef_stream
|
163
163
|
#
|
164
|
-
# Embeds the given file or IO stream into the PDF file, sets the path
|
165
|
-
# the created stream object.
|
164
|
+
# Embeds the given file or IO stream into the PDF file, sets the path and MIME type
|
165
|
+
# accordingly and returns the created stream object.
|
166
166
|
#
|
167
167
|
# If a file is given, the +name+ option defaults to the basename of the file. However, if an
|
168
168
|
# IO object is given, the +name+ argument is mandatory.
|
@@ -177,13 +177,16 @@ module HexaPDF
|
|
177
177
|
# name::
|
178
178
|
# The name that should be used as path value and when registering.
|
179
179
|
#
|
180
|
+
# mime_type::
|
181
|
+
# Optionally specifies the MIME type of the file.
|
182
|
+
#
|
180
183
|
# register::
|
181
184
|
# Specifies whether the embedded file will be added to the EmbeddedFiles name tree under
|
182
185
|
# the +name+. If the name is already taken, it's value is overwritten.
|
183
186
|
#
|
184
187
|
# The file has to be available until the PDF document gets written because reading and
|
185
188
|
# writing is done lazily.
|
186
|
-
def embed(file_or_io, name: nil, register: true)
|
189
|
+
def embed(file_or_io, name: nil, mime_type: nil, register: true)
|
187
190
|
name ||= File.basename(file_or_io) if file_or_io.kind_of?(String)
|
188
191
|
if name.nil?
|
189
192
|
raise ArgumentError, "The name argument is mandatory when given an IO object"
|
@@ -194,6 +197,7 @@ module HexaPDF
|
|
194
197
|
|
195
198
|
self[:EF] ||= {}
|
196
199
|
ef_stream = self[:EF][:UF] = self[:EF][:F] = document.add({Type: :EmbeddedFile})
|
200
|
+
ef_stream[:Subtype] = mime_type.to_sym if mime_type
|
197
201
|
stat = if file_or_io.kind_of?(String)
|
198
202
|
File.stat(file_or_io)
|
199
203
|
elsif file_or_io.respond_to?(:stat)
|
@@ -51,7 +51,7 @@ module HexaPDF
|
|
51
51
|
|
52
52
|
define_type :ExtGState
|
53
53
|
|
54
|
-
define_field :Type, type: Symbol,
|
54
|
+
define_field :Type, type: Symbol, default: type
|
55
55
|
define_field :LW, type: Numeric, version: "1.3"
|
56
56
|
define_field :LC, type: Integer, version: "1.3"
|
57
57
|
define_field :LJ, type: Integer, version: "1.3"
|
data/lib/hexapdf/version.rb
CHANGED
@@ -43,6 +43,11 @@ describe HexaPDF::Document::Files do
|
|
43
43
|
assert_equal('Some file', spec[:Desc])
|
44
44
|
end
|
45
45
|
|
46
|
+
it "optionally sets the MIME type of an embedded file" do
|
47
|
+
spec = @doc.files.add(@file.path, mime_type: 'application/pdf')
|
48
|
+
assert_equal(:'application/pdf', spec.embedded_file_stream[:Subtype])
|
49
|
+
end
|
50
|
+
|
46
51
|
it "requires the name argument when given an IO object" do
|
47
52
|
assert_raises(ArgumentError) { @doc.files.add(StringIO.new) }
|
48
53
|
end
|