hexapdf 0.33.0 → 0.34.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 +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
|
|