hexapdf 0.33.0 → 0.34.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 +42 -1
- data/examples/026-optional_content.rb +55 -0
- data/examples/027-composer_optional_content.rb +83 -0
- data/lib/hexapdf/cli/command.rb +7 -1
- data/lib/hexapdf/cli/fonts.rb +1 -1
- data/lib/hexapdf/cli/inspect.rb +2 -4
- data/lib/hexapdf/composer.rb +2 -1
- data/lib/hexapdf/configuration.rb +21 -1
- data/lib/hexapdf/content/canvas.rb +52 -0
- data/lib/hexapdf/content/operator.rb +2 -0
- data/lib/hexapdf/dictionary.rb +1 -0
- data/lib/hexapdf/dictionary_fields.rb +1 -2
- data/lib/hexapdf/digital_signature/verification_result.rb +1 -2
- data/lib/hexapdf/document/layout.rb +3 -0
- data/lib/hexapdf/document/pages.rb +1 -1
- data/lib/hexapdf/document.rb +7 -0
- data/lib/hexapdf/encryption/ruby_aes.rb +10 -20
- data/lib/hexapdf/layout/box.rb +23 -3
- data/lib/hexapdf/layout/column_box.rb +2 -1
- data/lib/hexapdf/layout/frame.rb +23 -6
- data/lib/hexapdf/layout/inline_box.rb +20 -9
- data/lib/hexapdf/layout/list_box.rb +34 -20
- data/lib/hexapdf/layout/page_style.rb +2 -1
- data/lib/hexapdf/layout/style.rb +46 -6
- data/lib/hexapdf/layout/table_box.rb +9 -7
- data/lib/hexapdf/layout/text_box.rb +9 -2
- data/lib/hexapdf/layout/text_fragment.rb +28 -2
- data/lib/hexapdf/layout/text_layouter.rb +21 -5
- data/lib/hexapdf/stream.rb +1 -2
- data/lib/hexapdf/type/actions/set_ocg_state.rb +86 -0
- data/lib/hexapdf/type/actions.rb +1 -0
- data/lib/hexapdf/type/annotations/text.rb +1 -2
- data/lib/hexapdf/type/catalog.rb +10 -1
- data/lib/hexapdf/type/cid_font.rb +15 -1
- data/lib/hexapdf/type/form.rb +75 -5
- data/lib/hexapdf/type/optional_content_configuration.rb +170 -0
- data/lib/hexapdf/type/optional_content_group.rb +370 -0
- data/lib/hexapdf/type/optional_content_membership.rb +63 -0
- data/lib/hexapdf/type/optional_content_properties.rb +158 -0
- data/lib/hexapdf/type/page.rb +27 -11
- data/lib/hexapdf/type/page_label.rb +4 -8
- data/lib/hexapdf/type.rb +4 -0
- data/lib/hexapdf/utils/pdf_doc_encoding.rb +0 -1
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/content/test_canvas.rb +49 -0
- data/test/hexapdf/document/test_layout.rb +7 -2
- data/test/hexapdf/document/test_pages.rb +6 -6
- data/test/hexapdf/layout/test_box.rb +13 -4
- data/test/hexapdf/layout/test_frame.rb +13 -1
- data/test/hexapdf/layout/test_inline_box.rb +17 -8
- data/test/hexapdf/layout/test_list_box.rb +48 -31
- data/test/hexapdf/layout/test_style.rb +10 -0
- data/test/hexapdf/layout/test_table_box.rb +32 -26
- data/test/hexapdf/layout/test_text_box.rb +8 -0
- data/test/hexapdf/layout/test_text_fragment.rb +33 -0
- data/test/hexapdf/layout/test_text_layouter.rb +32 -5
- data/test/hexapdf/test_composer.rb +10 -0
- data/test/hexapdf/test_dictionary.rb +10 -0
- data/test/hexapdf/test_document.rb +4 -0
- data/test/hexapdf/test_writer.rb +3 -3
- data/test/hexapdf/type/actions/test_set_ocg_state.rb +40 -0
- data/test/hexapdf/type/test_catalog.rb +11 -0
- data/test/hexapdf/type/test_form.rb +119 -0
- data/test/hexapdf/type/test_optional_content_configuration.rb +112 -0
- data/test/hexapdf/type/test_optional_content_group.rb +158 -0
- data/test/hexapdf/type/test_optional_content_properties.rb +109 -0
- data/test/hexapdf/type/test_page.rb +2 -2
- metadata +14 -3
data/lib/hexapdf/layout/frame.rb
CHANGED
|
@@ -133,12 +133,18 @@ module HexaPDF
|
|
|
133
133
|
# The configuration option "debug" can be used to add visual debug output with respect to
|
|
134
134
|
# box placement.
|
|
135
135
|
def draw(canvas)
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
136
|
+
doc = canvas.context.document
|
|
137
|
+
if doc.config['debug']
|
|
138
|
+
name = "#{box.class} (#{x.to_i},#{y.to_i}-#{box.width.to_i}x#{box.height.to_i})"
|
|
139
|
+
ocg = doc.optional_content.ocg(name)
|
|
140
|
+
canvas.optional_content(ocg) do
|
|
141
|
+
canvas.save_graphics_state do
|
|
142
|
+
canvas.fill_color("green").stroke_color("darkgreen").
|
|
143
|
+
opacity(fill_alpha: 0.1, stroke_alpha: 0.2).
|
|
144
|
+
draw(:geom2d, object: mask, path_only: true).fill_stroke
|
|
145
|
+
end
|
|
141
146
|
end
|
|
147
|
+
doc.optional_content.default_configuration.add_ocg_to_ui(ocg, path: 'Debug')
|
|
142
148
|
end
|
|
143
149
|
box.draw(canvas, x, y)
|
|
144
150
|
end
|
|
@@ -182,13 +188,18 @@ module HexaPDF
|
|
|
182
188
|
# Also see the note in the #x documentation for further information.
|
|
183
189
|
attr_reader :available_height
|
|
184
190
|
|
|
191
|
+
# The context object (a HexaPDF::Type::Page or HexaPDF::Type::Form) for which this frame
|
|
192
|
+
# should be used.
|
|
193
|
+
attr_reader :context
|
|
194
|
+
|
|
185
195
|
# Creates a new Frame object for the given rectangular area.
|
|
186
|
-
def initialize(left, bottom, width, height, shape: nil)
|
|
196
|
+
def initialize(left, bottom, width, height, shape: nil, context: nil)
|
|
187
197
|
@left = left
|
|
188
198
|
@bottom = bottom
|
|
189
199
|
@width = width
|
|
190
200
|
@height = height
|
|
191
201
|
@shape = shape || create_rectangle(left, bottom, left + width, bottom + height)
|
|
202
|
+
@context = context
|
|
192
203
|
|
|
193
204
|
@x = left
|
|
194
205
|
@y = bottom + height
|
|
@@ -199,6 +210,12 @@ module HexaPDF
|
|
|
199
210
|
@region_selection = :max_height
|
|
200
211
|
end
|
|
201
212
|
|
|
213
|
+
# Returns the HexaPDF::Document instance (through #context) that is associated with this Frame
|
|
214
|
+
# object or +nil+ if no context object has been set.
|
|
215
|
+
def document
|
|
216
|
+
@context&.document
|
|
217
|
+
end
|
|
218
|
+
|
|
202
219
|
# Fits the given box into the current region of available space and returns a FitResult
|
|
203
220
|
# object.
|
|
204
221
|
#
|
|
@@ -47,9 +47,10 @@ module HexaPDF
|
|
|
47
47
|
# beforehand! This means the box *must* have at least its width set. The height may either also
|
|
48
48
|
# be set or determined during fitting.
|
|
49
49
|
#
|
|
50
|
-
# Fitting of the wrapped box
|
|
51
|
-
# a frame is used that has the width of the
|
|
52
|
-
# practically infinite height. In the latter case
|
|
50
|
+
# Fitting of the wrapped box via #fit_wrapped_box needs to be done before accessing any other
|
|
51
|
+
# method that uses the wrapped box. For fitting, a frame is used that has the width of the
|
|
52
|
+
# wrapped box and its height, or if not set, a practically infinite height. In the latter case
|
|
53
|
+
# the height *must* be set during fitting.
|
|
53
54
|
class InlineBox
|
|
54
55
|
|
|
55
56
|
# Creates an InlineBox that wraps a basic Box. All arguments (except +valign+) and the block
|
|
@@ -76,12 +77,11 @@ module HexaPDF
|
|
|
76
77
|
raise HexaPDF::Error, "Width of box not set" if box.width == 0
|
|
77
78
|
@box = box
|
|
78
79
|
@valign = valign
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Returns the style of the wrapped box.
|
|
83
|
+
def style
|
|
84
|
+
box.style
|
|
85
85
|
end
|
|
86
86
|
|
|
87
87
|
# Returns +true+ if this inline box is just a placeholder without drawing operations.
|
|
@@ -126,6 +126,17 @@ module HexaPDF
|
|
|
126
126
|
height
|
|
127
127
|
end
|
|
128
128
|
|
|
129
|
+
# Fits the wrapped box, using the given context (see Frame#context).
|
|
130
|
+
def fit_wrapped_box(context)
|
|
131
|
+
@fit_result = Frame.new(0, 0, box.width, box.height == 0 ? 100_000 : box.height,
|
|
132
|
+
context: context).fit(box)
|
|
133
|
+
if !@fit_result.success?
|
|
134
|
+
raise HexaPDF::Error, "Box for inline use could not be fit"
|
|
135
|
+
elsif box.height > 99_000
|
|
136
|
+
raise HexaPDF::Error, "Box for inline use has no valid height set after fitting"
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
129
140
|
end
|
|
130
141
|
|
|
131
142
|
end
|
|
@@ -60,6 +60,9 @@ module HexaPDF
|
|
|
60
60
|
# arguments are ignored.
|
|
61
61
|
class ListBox < Box
|
|
62
62
|
|
|
63
|
+
# Stores the information when fitting an item of the list box.
|
|
64
|
+
ItemResult = Struct.new(:box_fitter, :height, :marker, :marker_pos_x)
|
|
65
|
+
|
|
63
66
|
# The child boxes of this ListBox. They need to be finalized before #fit is called.
|
|
64
67
|
attr_reader :children
|
|
65
68
|
|
|
@@ -179,7 +182,7 @@ module HexaPDF
|
|
|
179
182
|
|
|
180
183
|
# Returns +true+ if no box was fitted into the list box.
|
|
181
184
|
def empty?
|
|
182
|
-
super && (!@results || @results.all? {|
|
|
185
|
+
super && (!@results || @results.all? {|result| result.box_fitter.fit_results.empty? })
|
|
183
186
|
end
|
|
184
187
|
|
|
185
188
|
# Fits the list box into the available space.
|
|
@@ -210,9 +213,10 @@ module HexaPDF
|
|
|
210
213
|
end
|
|
211
214
|
|
|
212
215
|
@results = []
|
|
213
|
-
@results_item_marker_x = []
|
|
214
216
|
|
|
215
|
-
@children.
|
|
217
|
+
@children.each_with_index do |child, index|
|
|
218
|
+
item_result = ItemResult.new
|
|
219
|
+
|
|
216
220
|
shape = Geom2D::Polygon([left, top - height],
|
|
217
221
|
[left + width, top - height],
|
|
218
222
|
[left + width, top],
|
|
@@ -222,26 +226,36 @@ module HexaPDF
|
|
|
222
226
|
remove_indent_from_frame_shape(shape) unless shape.polygons.empty?
|
|
223
227
|
end
|
|
224
228
|
|
|
225
|
-
item_frame = Frame.new(item_frame_left, top - height, item_frame_width, height,
|
|
226
|
-
|
|
229
|
+
item_frame = Frame.new(item_frame_left, top - height, item_frame_width, height,
|
|
230
|
+
shape: shape, context: frame.context)
|
|
231
|
+
|
|
232
|
+
if index != 0 || !split_box? || @split_box == :show_first_marker
|
|
233
|
+
box = item_marker_box(frame.document, index)
|
|
234
|
+
break unless box.fit(content_indentation, height, nil)
|
|
235
|
+
item_result.marker = box
|
|
236
|
+
item_result.marker_pos_x = item_frame.x - content_indentation
|
|
237
|
+
item_result.height = box.height
|
|
238
|
+
end
|
|
227
239
|
|
|
228
240
|
box_fitter = BoxFitter.new([item_frame])
|
|
229
241
|
Array(child).each {|box| box_fitter.fit(box) }
|
|
230
|
-
|
|
242
|
+
item_result.box_fitter = box_fitter
|
|
243
|
+
item_result.height = [item_result.height.to_i, box_fitter.content_heights[0]].max
|
|
244
|
+
@results << item_result
|
|
231
245
|
|
|
232
|
-
top -=
|
|
233
|
-
height -=
|
|
246
|
+
top -= item_result.height + item_spacing
|
|
247
|
+
height -= item_result.height + item_spacing
|
|
234
248
|
|
|
235
249
|
break if !box_fitter.fit_successful? || height <= 0
|
|
236
250
|
end
|
|
237
251
|
|
|
238
|
-
@height = @results.sum {|
|
|
252
|
+
@height = @results.sum {|item_result| item_result.height } +
|
|
239
253
|
(@results.count - 1) * item_spacing +
|
|
240
254
|
reserved_height
|
|
241
255
|
|
|
242
256
|
@draw_pos_x = frame.x + reserved_width_left
|
|
243
257
|
@draw_pos_y = frame.y - @height + reserved_height_bottom
|
|
244
|
-
@fit_successful = @results.all?
|
|
258
|
+
@fit_successful = @results.all? {|r| r.box_fitter.fit_successful? } && @results.size == @children.size
|
|
245
259
|
end
|
|
246
260
|
|
|
247
261
|
private
|
|
@@ -292,7 +306,7 @@ module HexaPDF
|
|
|
292
306
|
|
|
293
307
|
# Splits the content of the list box. This method is called from Box#split.
|
|
294
308
|
def split_content(_available_width, _available_height, _frame)
|
|
295
|
-
remaining_boxes = @results[-1].remaining_boxes
|
|
309
|
+
remaining_boxes = @results[-1].box_fitter.remaining_boxes
|
|
296
310
|
first_is_split_box = remaining_boxes.first&.split_box?
|
|
297
311
|
children = (remaining_boxes.empty? ? [] : [remaining_boxes]) + @children[@results.size..-1]
|
|
298
312
|
|
|
@@ -301,7 +315,6 @@ module HexaPDF
|
|
|
301
315
|
box.instance_variable_set(:@start_number,
|
|
302
316
|
@start_number + @results.size + (first_is_split_box ? -1 : 0))
|
|
303
317
|
box.instance_variable_set(:@results, [])
|
|
304
|
-
box.instance_variable_set(:@results_item_marker_x, [])
|
|
305
318
|
|
|
306
319
|
[self, box]
|
|
307
320
|
end
|
|
@@ -315,20 +328,22 @@ module HexaPDF
|
|
|
315
328
|
fragment = case @item_type
|
|
316
329
|
when :disc
|
|
317
330
|
TextFragment.create("•", font: document.fonts.add("Times"),
|
|
318
|
-
font_size: style.font_size)
|
|
331
|
+
font_size: style.font_size, fill_color: style.fill_color)
|
|
319
332
|
when :circle
|
|
320
333
|
TextFragment.create("❍", font: document.fonts.add("ZapfDingbats"),
|
|
321
334
|
font_size: style.font_size / 2.0,
|
|
335
|
+
fill_color: style.fill_color,
|
|
322
336
|
text_rise: -style.font_size / 1.8)
|
|
323
337
|
when :square
|
|
324
338
|
TextFragment.create("■", font: document.fonts.add("ZapfDingbats"),
|
|
325
339
|
font_size: style.font_size / 2.0,
|
|
340
|
+
fill_color: style.fill_color,
|
|
326
341
|
text_rise: -style.font_size / 1.8)
|
|
327
342
|
when :decimal
|
|
328
343
|
text = (@start_number + index).to_s << "."
|
|
329
344
|
decimal_style = {
|
|
330
345
|
font: (style.font? ? style.font : document.fonts.add("Times")),
|
|
331
|
-
font_size: style.font_size || 10,
|
|
346
|
+
font_size: style.font_size || 10, fill_color: style.fill_color
|
|
332
347
|
}
|
|
333
348
|
TextFragment.create(text, decimal_style)
|
|
334
349
|
else
|
|
@@ -348,12 +363,11 @@ module HexaPDF
|
|
|
348
363
|
canvas.translate(x - @draw_pos_x, y - @draw_pos_y)
|
|
349
364
|
end
|
|
350
365
|
|
|
351
|
-
@results.
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
box_fitter.frames[0].bottom + box_fitter.frames[0].height - box.height)
|
|
366
|
+
@results.each do |item_result|
|
|
367
|
+
box_fitter = item_result.box_fitter
|
|
368
|
+
if (marker = item_result.marker)
|
|
369
|
+
marker.draw(canvas, item_result.marker_pos_x,
|
|
370
|
+
box_fitter.frames[0].bottom + box_fitter.frames[0].height - marker.height)
|
|
357
371
|
end
|
|
358
372
|
box_fitter.fit_results.each {|result| result.draw(canvas) }
|
|
359
373
|
end
|
|
@@ -135,7 +135,8 @@ module HexaPDF
|
|
|
135
135
|
Layout::Frame.new(box.left + margin.left,
|
|
136
136
|
box.bottom + margin.bottom,
|
|
137
137
|
box.width - margin.left - margin.right,
|
|
138
|
-
box.height - margin.bottom - margin.top
|
|
138
|
+
box.height - margin.bottom - margin.top,
|
|
139
|
+
context: page)
|
|
139
140
|
end
|
|
140
141
|
|
|
141
142
|
end
|
data/lib/hexapdf/layout/style.rb
CHANGED
|
@@ -464,12 +464,12 @@ module HexaPDF
|
|
|
464
464
|
|
|
465
465
|
# Creates a new LinkLayer object.
|
|
466
466
|
#
|
|
467
|
-
# The following arguments are allowed (note that only *one* of +dest+, +uri
|
|
468
|
-
# be specified):
|
|
467
|
+
# The following arguments are allowed (note that only *one* of +dest+, +uri+, +file+ or
|
|
468
|
+
# +action+ may be specified):
|
|
469
469
|
#
|
|
470
470
|
# +dest+::
|
|
471
471
|
# The destination array or a name of a named destination for in-document links. If neither
|
|
472
|
-
# +dest
|
|
472
|
+
# +dest+, +uri+, +file+ nor +action+ is specified, it is assumed that the box has a custom
|
|
473
473
|
# property named 'link' which is used for the destination.
|
|
474
474
|
#
|
|
475
475
|
# +uri+::
|
|
@@ -480,6 +480,9 @@ module HexaPDF
|
|
|
480
480
|
# should be launched. Can either be a string or a Filespec object. Also see:
|
|
481
481
|
# HexaPDF::Type::FileSpecification.
|
|
482
482
|
#
|
|
483
|
+
# +action+::
|
|
484
|
+
# The PDF action that should be executed.
|
|
485
|
+
#
|
|
483
486
|
# +border+::
|
|
484
487
|
# If set to +true+, a standard border is used. Also accepts an array that adheres to the
|
|
485
488
|
# rules for annotation borders.
|
|
@@ -492,15 +495,17 @@ module HexaPDF
|
|
|
492
495
|
# LinkLayer.new(dest: [page, :XYZ, nil, nil, nil], border: true)
|
|
493
496
|
# LinkLayer.new(uri: "https://my.example.com/path", border: [5 5 2])
|
|
494
497
|
# LinkLayer.new # use 'link' custom box property for dest
|
|
495
|
-
def initialize(dest: nil, uri: nil, file: nil, border: false, border_color: nil)
|
|
496
|
-
if dest && (uri || file) || uri && file
|
|
497
|
-
raise ArgumentError, "Only one of dest, uri
|
|
498
|
+
def initialize(dest: nil, uri: nil, file: nil, action: nil, border: false, border_color: nil)
|
|
499
|
+
if dest && (uri || file || action) || uri && (file || action) || file && action
|
|
500
|
+
raise ArgumentError, "Only one of dest, uri, file or action is allowed"
|
|
498
501
|
end
|
|
499
502
|
@dest = dest
|
|
500
503
|
@action = if uri
|
|
501
504
|
{S: :URI, URI: uri}
|
|
502
505
|
elsif file
|
|
503
506
|
{S: :Launch, F: file, NewWindow: true}
|
|
507
|
+
elsif action
|
|
508
|
+
action
|
|
504
509
|
end
|
|
505
510
|
@border = case border
|
|
506
511
|
when false then [0, 0, 0]
|
|
@@ -1045,6 +1050,40 @@ module HexaPDF
|
|
|
1045
1050
|
# line_spacing: 1.5, last_line_gap: true)
|
|
1046
1051
|
# composer.text("There is spacing above this line due to last_line_gap.")
|
|
1047
1052
|
|
|
1053
|
+
##
|
|
1054
|
+
# :method: fill_horizontal
|
|
1055
|
+
# :call-seq:
|
|
1056
|
+
# fill_horizontal(factor = nil)
|
|
1057
|
+
#
|
|
1058
|
+
# If set to a positive number, it specifies that the content of the text item should be
|
|
1059
|
+
# repeated and appropriate spacing applied so that the remaining space of the line is
|
|
1060
|
+
# completely filled.
|
|
1061
|
+
#
|
|
1062
|
+
# If there are multiple text items with this property set for a single line, the remaining
|
|
1063
|
+
# space is split between those items using the set +factors+. For example, if item A has a
|
|
1064
|
+
# factor of 1 and item B a factor of 2, the remaining space will be split so that item
|
|
1065
|
+
# B will receive twice the space of A.
|
|
1066
|
+
#
|
|
1067
|
+
# Notes:
|
|
1068
|
+
#
|
|
1069
|
+
# * This property _must not_ be applied to inline boxes, it only works for text items.
|
|
1070
|
+
# * If the filling should be done with spaces, the non-breaking space character \u{00a0} has
|
|
1071
|
+
# to be used.
|
|
1072
|
+
#
|
|
1073
|
+
# Examples:
|
|
1074
|
+
#
|
|
1075
|
+
# #>pdf-composer100
|
|
1076
|
+
# composer.formatted_text(["Left", {text: "\u{00a0}", fill_horizontal: 1},
|
|
1077
|
+
# "Right"])
|
|
1078
|
+
# composer.formatted_text(["Typical table of contents entry",
|
|
1079
|
+
# {text: ".", fill_horizontal: 1}, "34"])
|
|
1080
|
+
# composer.formatted_text(["Factor 1", {text: "\u{00a0}", fill_horizontal: 1},
|
|
1081
|
+
# "Factor 3", {text: "\u{00a0}", fill_horizontal: 3}, "End"])
|
|
1082
|
+
# overlays = [proc {|c, b| c.line(0, b.height / 2.0, b.width, b.height / 2.0).stroke}]
|
|
1083
|
+
# composer.formatted_text([{text: "\u{00a0}", fill_horizontal: 1, overlays: overlays},
|
|
1084
|
+
# 'Centered',
|
|
1085
|
+
# {text: "\u{00a0}", fill_horizontal: 1, overlays: overlays}])
|
|
1086
|
+
|
|
1048
1087
|
##
|
|
1049
1088
|
# :method: background_color
|
|
1050
1089
|
# :call-seq:
|
|
@@ -1293,6 +1332,7 @@ module HexaPDF
|
|
|
1293
1332
|
"{type: value, value: extra_arg} : value))",
|
|
1294
1333
|
extra_args: ", extra_arg = nil"}],
|
|
1295
1334
|
[:last_line_gap, false, {valid_values: [true, false]}],
|
|
1335
|
+
[:fill_horizontal, nil],
|
|
1296
1336
|
[:background_color, nil],
|
|
1297
1337
|
[:background_alpha, 1],
|
|
1298
1338
|
[:padding, "Quad.new(0)", {setter: "Quad.new(value)"}],
|
|
@@ -212,13 +212,13 @@ module HexaPDF
|
|
|
212
212
|
end
|
|
213
213
|
|
|
214
214
|
# Fits the children of the table cell into the given rectangular area.
|
|
215
|
-
def fit(available_width, available_height,
|
|
215
|
+
def fit(available_width, available_height, frame)
|
|
216
216
|
@width = available_width
|
|
217
217
|
width = available_width - reserved_width
|
|
218
218
|
height = available_height - reserved_height
|
|
219
219
|
return false if width <= 0 || height <= 0
|
|
220
220
|
|
|
221
|
-
frame = Frame.new(0, 0, width, height)
|
|
221
|
+
frame = Frame.new(0, 0, width, height, context: frame.context)
|
|
222
222
|
case children
|
|
223
223
|
when Box
|
|
224
224
|
fit_result = frame.fit(children)
|
|
@@ -376,9 +376,11 @@ module HexaPDF
|
|
|
376
376
|
# The +column_info+ argument needs to be an array of arrays of the form [x_pos, width]
|
|
377
377
|
# containing the horizontal positions and widths of each column.
|
|
378
378
|
#
|
|
379
|
+
# The +frame+ argument is further handed down to the Cell instances for fitting.
|
|
380
|
+
#
|
|
379
381
|
# The fitting of a cell is done through the Cell#fit method which stores the result in the
|
|
380
382
|
# cell itself. Furthermore, Cell#left and Cell#top are also assigned correctly.
|
|
381
|
-
def fit_rows(start_row, available_height, column_info)
|
|
383
|
+
def fit_rows(start_row, available_height, column_info, frame)
|
|
382
384
|
height = available_height
|
|
383
385
|
last_fitted_row_index = -1
|
|
384
386
|
@cells[start_row..-1].each.with_index(start_row) do |columns, row_index|
|
|
@@ -391,7 +393,7 @@ module HexaPDF
|
|
|
391
393
|
else
|
|
392
394
|
column_info[cell.column].last
|
|
393
395
|
end
|
|
394
|
-
unless cell.fit(available_cell_width, available_height,
|
|
396
|
+
unless cell.fit(available_cell_width, available_height, frame)
|
|
395
397
|
row_fit = false
|
|
396
398
|
break
|
|
397
399
|
end
|
|
@@ -587,7 +589,7 @@ module HexaPDF
|
|
|
587
589
|
end
|
|
588
590
|
|
|
589
591
|
# Fits the table into the available space.
|
|
590
|
-
def fit(available_width, available_height,
|
|
592
|
+
def fit(available_width, available_height, frame)
|
|
591
593
|
return false if (@initial_width > 0 && @initial_width > available_width) ||
|
|
592
594
|
(@initial_height > 0 && @initial_height > available_height)
|
|
593
595
|
|
|
@@ -608,14 +610,14 @@ module HexaPDF
|
|
|
608
610
|
@special_cells_fit_not_successful = false
|
|
609
611
|
[@header_cells, @footer_cells].each do |special_cells|
|
|
610
612
|
next unless special_cells
|
|
611
|
-
special_used_height, last_fitted_row_index = special_cells.fit_rows(0, height, columns)
|
|
613
|
+
special_used_height, last_fitted_row_index = special_cells.fit_rows(0, height, columns, frame)
|
|
612
614
|
height -= special_used_height
|
|
613
615
|
used_height += special_used_height
|
|
614
616
|
@special_cells_fit_not_successful = (last_fitted_row_index != special_cells.number_of_rows - 1)
|
|
615
617
|
return false if @special_cells_fit_not_successful
|
|
616
618
|
end
|
|
617
619
|
|
|
618
|
-
main_used_height, @last_fitted_row_index = @cells.fit_rows(@start_row_index, height, columns)
|
|
620
|
+
main_used_height, @last_fitted_row_index = @cells.fit_rows(@start_row_index, height, columns, frame)
|
|
619
621
|
used_height += main_used_height
|
|
620
622
|
|
|
621
623
|
@width = (@initial_width > 0 ? @initial_width : columns[-1].sum + rw)
|
|
@@ -54,6 +54,13 @@ module HexaPDF
|
|
|
54
54
|
@result = nil
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
+
# Returns the text that will be drawn.
|
|
58
|
+
#
|
|
59
|
+
# This will ignore any inline boxes or kerning values.
|
|
60
|
+
def text
|
|
61
|
+
@items.map {|item| item.kind_of?(TextFragment) ? item.text : '' }.join
|
|
62
|
+
end
|
|
63
|
+
|
|
57
64
|
# Returns +true+ as the 'position' style property value :flow is supported.
|
|
58
65
|
def supports_position_flow?
|
|
59
66
|
true
|
|
@@ -75,13 +82,13 @@ module HexaPDF
|
|
|
75
82
|
@width = @height = 0
|
|
76
83
|
@result = if style.position == :flow
|
|
77
84
|
@tl.fit(@items, frame.width_specification, frame.shape.bbox.height,
|
|
78
|
-
apply_first_text_indent: !split_box
|
|
85
|
+
apply_first_text_indent: !split_box?, frame: frame)
|
|
79
86
|
else
|
|
80
87
|
@width = reserved_width
|
|
81
88
|
@height = reserved_height
|
|
82
89
|
width = (@initial_width > 0 ? @initial_width : available_width) - @width
|
|
83
90
|
height = (@initial_height > 0 ? @initial_height : available_height) - @height
|
|
84
|
-
@tl.fit(@items, width, height, apply_first_text_indent: !split_box
|
|
91
|
+
@tl.fit(@items, width, height, apply_first_text_indent: !split_box?, frame: frame)
|
|
85
92
|
end
|
|
86
93
|
@width += if @initial_width > 0 || style.align == :center || style.align == :right
|
|
87
94
|
width
|
|
@@ -111,6 +111,11 @@ module HexaPDF
|
|
|
111
111
|
@properties = properties
|
|
112
112
|
end
|
|
113
113
|
|
|
114
|
+
# Returns the text of the fragment.
|
|
115
|
+
def text
|
|
116
|
+
items.reject {|i| i.kind_of?(Numeric) }.map(&:str).join
|
|
117
|
+
end
|
|
118
|
+
|
|
114
119
|
# Creates a new TextFragment with the same style and custom properties as this one but with
|
|
115
120
|
# the given +items+.
|
|
116
121
|
def dup_attributes(items)
|
|
@@ -283,6 +288,28 @@ module HexaPDF
|
|
|
283
288
|
:text
|
|
284
289
|
end
|
|
285
290
|
|
|
291
|
+
# Creates a new text fragment that repeats this fragment's items and applies the necessary
|
|
292
|
+
# spacing so that the returned text fragment fills the given +width+ completely.
|
|
293
|
+
#
|
|
294
|
+
# If the given +width+ is less than the fragment's width, +self+ is returned.
|
|
295
|
+
def fill_horizontal!(width)
|
|
296
|
+
return self if width < self.width
|
|
297
|
+
|
|
298
|
+
factor, rest = width.divmod(self.width)
|
|
299
|
+
items = @items * factor
|
|
300
|
+
rest = @items.inject(rest) do |available_width, item|
|
|
301
|
+
new_available_width = available_width - style.scaled_item_width(item)
|
|
302
|
+
break available_width if new_available_width < 0
|
|
303
|
+
items << item
|
|
304
|
+
new_available_width
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
spacing = rest / (items.size - 1)
|
|
308
|
+
new_style = @style.dup.update(character_spacing: spacing)
|
|
309
|
+
items << spacing / new_style.scaled_font_size # correct spacing after last item
|
|
310
|
+
self.class.new(items, new_style, properties: @properties.dup)
|
|
311
|
+
end
|
|
312
|
+
|
|
286
313
|
# Clears all cached values.
|
|
287
314
|
#
|
|
288
315
|
# This method needs to be called if the fragment's items or attributes are changed!
|
|
@@ -293,8 +320,7 @@ module HexaPDF
|
|
|
293
320
|
|
|
294
321
|
# :nodoc:
|
|
295
322
|
def inspect
|
|
296
|
-
"#<#{self.class.name} #{
|
|
297
|
-
"#{items.inspect}>"
|
|
323
|
+
"#<#{self.class.name} #{text.inspect} #{items.inspect}>"
|
|
298
324
|
end
|
|
299
325
|
|
|
300
326
|
private
|
|
@@ -340,8 +340,8 @@ module HexaPDF
|
|
|
340
340
|
# current start of the line index should be stored for later use.
|
|
341
341
|
#
|
|
342
342
|
# After the algorithm is finished, it returns the unused items.
|
|
343
|
-
def self.call(items, width_block, &block)
|
|
344
|
-
obj = new(items, width_block)
|
|
343
|
+
def self.call(items, width_block, frame, &block)
|
|
344
|
+
obj = new(items, width_block, frame)
|
|
345
345
|
if width_block.arity == 1
|
|
346
346
|
obj.variable_width_wrapping(&block)
|
|
347
347
|
else
|
|
@@ -353,9 +353,10 @@ module HexaPDF
|
|
|
353
353
|
|
|
354
354
|
# Creates a new line wrapping object that arranges the +items+ on lines with the given
|
|
355
355
|
# width.
|
|
356
|
-
def initialize(items, width_block)
|
|
356
|
+
def initialize(items, width_block, frame)
|
|
357
357
|
@items = items
|
|
358
358
|
@width_block = width_block
|
|
359
|
+
@frame = frame
|
|
359
360
|
@line_items = []
|
|
360
361
|
@width = 0
|
|
361
362
|
@glue_items = []
|
|
@@ -363,6 +364,7 @@ module HexaPDF
|
|
|
363
364
|
@last_breakpoint_index = 0
|
|
364
365
|
@last_breakpoint_line_items_index = 0
|
|
365
366
|
@break_prohibited_state = false
|
|
367
|
+
@fill_horizontal = false
|
|
366
368
|
|
|
367
369
|
@height_calc = Line::HeightCalculator.new
|
|
368
370
|
@line = DummyLine.new(0, 0)
|
|
@@ -505,9 +507,11 @@ module HexaPDF
|
|
|
505
507
|
#
|
|
506
508
|
# Returns +true+ if the item could be added and +false+ otherwise.
|
|
507
509
|
def add_box_item(item)
|
|
510
|
+
item.fit_wrapped_box(@frame&.context) if item.kind_of?(InlineBox)
|
|
508
511
|
return false unless @width + item.width <= @available_width
|
|
509
512
|
@line_items.concat(@glue_items).push(item)
|
|
510
513
|
@width += item.width
|
|
514
|
+
@fill_horizontal ||= item.style.fill_horizontal
|
|
511
515
|
@glue_items.clear
|
|
512
516
|
true
|
|
513
517
|
end
|
|
@@ -547,6 +551,17 @@ module HexaPDF
|
|
|
547
551
|
|
|
548
552
|
# Creates a Line object from the current line items.
|
|
549
553
|
def create_line
|
|
554
|
+
if @fill_horizontal
|
|
555
|
+
rest_width = @available_width - @width
|
|
556
|
+
indices = []
|
|
557
|
+
@line_items.each_with_index do |item, index|
|
|
558
|
+
next unless item.style.fill_horizontal
|
|
559
|
+
indices << [index, item.style.fill_horizontal]
|
|
560
|
+
rest_width += item.width
|
|
561
|
+
end
|
|
562
|
+
unit_width = rest_width / indices.sum(&:last)
|
|
563
|
+
indices.each {|i, count| @line_items[i] = @line_items[i].fill_horizontal!(unit_width * count) }
|
|
564
|
+
end
|
|
550
565
|
Line.new(@line_items)
|
|
551
566
|
end
|
|
552
567
|
|
|
@@ -566,6 +581,7 @@ module HexaPDF
|
|
|
566
581
|
@last_breakpoint_index = index
|
|
567
582
|
@last_breakpoint_line_items_index = 0
|
|
568
583
|
@break_prohibited_state = false
|
|
584
|
+
@fill_horizontal = false
|
|
569
585
|
@available_width = @width_block.call(@line)
|
|
570
586
|
end
|
|
571
587
|
|
|
@@ -701,7 +717,7 @@ module HexaPDF
|
|
|
701
717
|
# Specifies whether style.text_indent should be applied to the first line. This should be
|
|
702
718
|
# set to +false+ if the items start with a continuation of a paragraph instead of starting
|
|
703
719
|
# a new paragraph (e.g. after a page break).
|
|
704
|
-
def fit(items, width, height, apply_first_text_indent: true)
|
|
720
|
+
def fit(items, width, height, apply_first_text_indent: true, frame: nil)
|
|
705
721
|
unless items.empty? || items[0].respond_to?(:type)
|
|
706
722
|
items = style.text_segmentation_algorithm.call(items)
|
|
707
723
|
end
|
|
@@ -765,7 +781,7 @@ module HexaPDF
|
|
|
765
781
|
too_wide_box = nil
|
|
766
782
|
line_height = 0
|
|
767
783
|
|
|
768
|
-
rest = style.text_line_wrapping_algorithm.call(rest, width_block) do |line, item|
|
|
784
|
+
rest = style.text_line_wrapping_algorithm.call(rest, width_block, frame) do |line, item|
|
|
769
785
|
# make sure empty lines broken by mandatory paragraph breaks are not empty
|
|
770
786
|
line << TextFragment.new([], style) if item&.type != :box && line.items.empty?
|
|
771
787
|
|
data/lib/hexapdf/stream.rb
CHANGED
|
@@ -278,9 +278,8 @@ module HexaPDF
|
|
|
278
278
|
end
|
|
279
279
|
end
|
|
280
280
|
|
|
281
|
-
# :nodoc:
|
|
282
281
|
# A mapping from short name to long name for filters.
|
|
283
|
-
FILTER_MAP = {AHx: :ASCIIHexDecode, A85: :ASCII85Decode, LZW: :LZWDecode,
|
|
282
|
+
FILTER_MAP = {AHx: :ASCIIHexDecode, A85: :ASCII85Decode, LZW: :LZWDecode, # :nodoc:
|
|
284
283
|
Fl: :FlateDecode, RL: :RunLengthDecode, CCF: :CCITTFaxDecode,
|
|
285
284
|
DCT: :DCTDecode}.freeze
|
|
286
285
|
|