hexapdf 0.8.0 → 0.9.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 +52 -1
- data/CONTRIBUTERS +1 -1
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/examples/018-composer.rb +44 -0
- data/lib/hexapdf/cli.rb +2 -0
- data/lib/hexapdf/cli/command.rb +2 -2
- data/lib/hexapdf/cli/optimize.rb +1 -1
- data/lib/hexapdf/cli/split.rb +82 -0
- data/lib/hexapdf/composer.rb +303 -0
- data/lib/hexapdf/configuration.rb +2 -2
- data/lib/hexapdf/content/canvas.rb +3 -6
- data/lib/hexapdf/dictionary.rb +0 -3
- data/lib/hexapdf/document.rb +30 -22
- data/lib/hexapdf/document/files.rb +1 -1
- data/lib/hexapdf/document/images.rb +1 -1
- data/lib/hexapdf/filter/predictor.rb +8 -8
- data/lib/hexapdf/layout.rb +1 -0
- data/lib/hexapdf/layout/box.rb +55 -12
- data/lib/hexapdf/layout/frame.rb +143 -46
- data/lib/hexapdf/layout/image_box.rb +96 -0
- data/lib/hexapdf/layout/inline_box.rb +10 -0
- data/lib/hexapdf/layout/line.rb +1 -1
- data/lib/hexapdf/layout/style.rb +55 -3
- data/lib/hexapdf/layout/text_box.rb +38 -8
- data/lib/hexapdf/layout/text_layouter.rb +66 -52
- data/lib/hexapdf/object.rb +3 -2
- data/lib/hexapdf/parser.rb +6 -1
- data/lib/hexapdf/rectangle.rb +6 -0
- data/lib/hexapdf/reference.rb +4 -6
- data/lib/hexapdf/revision.rb +34 -8
- data/lib/hexapdf/revisions.rb +12 -2
- data/lib/hexapdf/stream.rb +18 -0
- data/lib/hexapdf/task.rb +1 -1
- data/lib/hexapdf/task/dereference.rb +1 -1
- data/lib/hexapdf/task/optimize.rb +2 -2
- data/lib/hexapdf/type/form.rb +10 -0
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/writer.rb +25 -5
- data/man/man1/hexapdf.1 +17 -0
- data/test/hexapdf/layout/test_box.rb +7 -1
- data/test/hexapdf/layout/test_frame.rb +38 -1
- data/test/hexapdf/layout/test_image_box.rb +73 -0
- data/test/hexapdf/layout/test_inline_box.rb +8 -0
- data/test/hexapdf/layout/test_line.rb +1 -1
- data/test/hexapdf/layout/test_style.rb +58 -1
- data/test/hexapdf/layout/test_text_box.rb +57 -12
- data/test/hexapdf/layout/test_text_layouter.rb +14 -4
- data/test/hexapdf/task/test_optimize.rb +3 -3
- data/test/hexapdf/test_composer.rb +258 -0
- data/test/hexapdf/test_document.rb +29 -3
- data/test/hexapdf/test_importer.rb +8 -2
- data/test/hexapdf/test_object.rb +3 -1
- data/test/hexapdf/test_parser.rb +6 -0
- data/test/hexapdf/test_rectangle.rb +7 -0
- data/test/hexapdf/test_reference.rb +3 -1
- data/test/hexapdf/test_revision.rb +13 -0
- data/test/hexapdf/test_revisions.rb +1 -0
- data/test/hexapdf/test_stream.rb +13 -0
- data/test/hexapdf/test_writer.rb +13 -2
- data/test/hexapdf/type/test_annotation.rb +1 -1
- data/test/hexapdf/type/test_form.rb +13 -2
- metadata +10 -4
@@ -0,0 +1,96 @@
|
|
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-2018 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
|
+
require 'hexapdf/layout/box'
|
34
|
+
|
35
|
+
module HexaPDF
|
36
|
+
module Layout
|
37
|
+
|
38
|
+
# An Image box object is used for displaying an image.
|
39
|
+
#
|
40
|
+
# How an image is displayed inside an image box, depends on whether the +width+ and/or +height+
|
41
|
+
# of the box has been set:
|
42
|
+
#
|
43
|
+
# * If one of them has been set, the other is adjusted to retain the image ratio.
|
44
|
+
# * If both have been set, both are used as is.
|
45
|
+
# * If neither has been set, the image is scaled to fit the available space.
|
46
|
+
#
|
47
|
+
# Also see: HexaPDF::Content::Canvas#image
|
48
|
+
class ImageBox < Box
|
49
|
+
|
50
|
+
# The image that is shown in the box.
|
51
|
+
attr_reader :image
|
52
|
+
|
53
|
+
# Creates a new Image box object for the given +image+ argument which needs to be an image
|
54
|
+
# object (e.g. returned by HexaPDF::Document::Images#add).
|
55
|
+
def initialize(image, **kwargs)
|
56
|
+
super(kwargs, &:unused_draw_block)
|
57
|
+
@image = image
|
58
|
+
end
|
59
|
+
|
60
|
+
# Fits the image into the available space.
|
61
|
+
def fit(available_width, available_height, _)
|
62
|
+
image_width = @image.width.to_f
|
63
|
+
image_height = @image.height.to_f
|
64
|
+
image_ratio = image_width / image_height
|
65
|
+
|
66
|
+
if @initial_width > 0 && @initial_height > 0
|
67
|
+
@width = @initial_width
|
68
|
+
@height = @initial_height
|
69
|
+
elsif @initial_width > 0
|
70
|
+
@width = @initial_width
|
71
|
+
@height = (@width - reserved_width) / image_ratio + reserved_height
|
72
|
+
elsif @initial_height > 0
|
73
|
+
@height = @initial_height
|
74
|
+
@width = (@height - reserved_height) * image_ratio + reserved_width
|
75
|
+
else
|
76
|
+
rw = reserved_width
|
77
|
+
rh = reserved_height
|
78
|
+
ratio = [(available_width - rw) / image_width, (available_height - rh) / image_height].min
|
79
|
+
@width = image_width * ratio + rw
|
80
|
+
@height = image_height * ratio + rh
|
81
|
+
end
|
82
|
+
|
83
|
+
@width <= available_width && @height <= available_height
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
# Draws the image onto the canvas at position [x, y].
|
89
|
+
def draw_content(canvas, x, y)
|
90
|
+
canvas.image(@image, at: [x, y], width: content_width, height: content_height)
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
@@ -98,6 +98,16 @@ module HexaPDF
|
|
98
98
|
width
|
99
99
|
end
|
100
100
|
|
101
|
+
# The minimum y-coordinate which is always 0.
|
102
|
+
def y_min
|
103
|
+
0
|
104
|
+
end
|
105
|
+
|
106
|
+
# The maximum y-coordinate which is equivalent to the height of the inline box.
|
107
|
+
def y_max
|
108
|
+
height
|
109
|
+
end
|
110
|
+
|
101
111
|
end
|
102
112
|
|
103
113
|
end
|
data/lib/hexapdf/layout/line.rb
CHANGED
@@ -153,7 +153,7 @@ module HexaPDF
|
|
153
153
|
max_bottom_height = @max_bottom_height
|
154
154
|
max_text_bottom_height = @max_text_bottom_height
|
155
155
|
y_min, y_max, = add(item).result
|
156
|
-
y_max - y_min
|
156
|
+
[y_min, y_max, y_max - y_min]
|
157
157
|
ensure
|
158
158
|
@text_y_min = text_y_min
|
159
159
|
@text_y_max = text_y_max
|
data/lib/hexapdf/layout/style.rb
CHANGED
@@ -78,7 +78,7 @@ module HexaPDF
|
|
78
78
|
|
79
79
|
# Creates a new LineSpacing object for the given type which can be any valid line spacing
|
80
80
|
# type or a LineSpacing object.
|
81
|
-
def initialize(type
|
81
|
+
def initialize(type:, value: 1)
|
82
82
|
case type
|
83
83
|
when :single
|
84
84
|
@type = :proportional
|
@@ -207,6 +207,14 @@ module HexaPDF
|
|
207
207
|
@style = Quad.new(style)
|
208
208
|
end
|
209
209
|
|
210
|
+
# Duplicates a Border object's properties.
|
211
|
+
def initialize_copy(other)
|
212
|
+
super
|
213
|
+
@width = @width.dup
|
214
|
+
@color = @color.dup
|
215
|
+
@style = @style.dup
|
216
|
+
end
|
217
|
+
|
210
218
|
# Returns +true+ if there is no border.
|
211
219
|
def none?
|
212
220
|
width.simple? && width.top == 0
|
@@ -372,6 +380,12 @@ module HexaPDF
|
|
372
380
|
@layers = layers
|
373
381
|
end
|
374
382
|
|
383
|
+
# Duplicates the array holding the layers.
|
384
|
+
def initialize_copy(other)
|
385
|
+
super
|
386
|
+
@layers = @layers.dup
|
387
|
+
end
|
388
|
+
|
375
389
|
# :call-seq:
|
376
390
|
# layers.add {|canvas, box| block}
|
377
391
|
# layers.add(name, **options)
|
@@ -510,6 +524,20 @@ module HexaPDF
|
|
510
524
|
@scaled_item_widths = {}
|
511
525
|
end
|
512
526
|
|
527
|
+
# Duplicates the complex properties that can be modified, as well as the cache.
|
528
|
+
def initialize_copy(other)
|
529
|
+
super
|
530
|
+
@scaled_item_widths = {}
|
531
|
+
clear_cache
|
532
|
+
|
533
|
+
@font_features = @font_features.dup if defined?(@font_features)
|
534
|
+
@padding = @padding.dup if defined?(@padding)
|
535
|
+
@margin = @margin.dup if defined?(@margin)
|
536
|
+
@border = @border.dup if defined?(@border)
|
537
|
+
@overlays = @overlays.dup if defined?(@overlays)
|
538
|
+
@underlays = @underlays.dup if defined?(@underlays)
|
539
|
+
end
|
540
|
+
|
513
541
|
# :call-seq:
|
514
542
|
# style.update(**properties) -> style
|
515
543
|
#
|
@@ -724,6 +752,28 @@ module HexaPDF
|
|
724
752
|
#
|
725
753
|
# The indentation to be used for the first line of a sequence of text lines, defaults to 0.
|
726
754
|
|
755
|
+
##
|
756
|
+
# :method: line_spacing
|
757
|
+
# :call-seq:
|
758
|
+
# line_spacing(type = nil, value = nil)
|
759
|
+
# line_spacing(type:, value: 1)
|
760
|
+
#
|
761
|
+
# The type of line spacing to be used for text lines, defaults to type :single.
|
762
|
+
#
|
763
|
+
# This method can set the line spacing in two ways:
|
764
|
+
#
|
765
|
+
# * Using two positional arguments +type+ and +value+.
|
766
|
+
# * Or a hash with the keys +type+ and +value+.
|
767
|
+
#
|
768
|
+
# See LineSpacing for supported types of line spacing.
|
769
|
+
|
770
|
+
##
|
771
|
+
# :method: last_line_gap
|
772
|
+
# :call-seq:
|
773
|
+
# last_line_gap(enable = false)
|
774
|
+
#
|
775
|
+
# Add an appropriately sized gap after the last line of text if enabled, defaults to false.
|
776
|
+
|
727
777
|
##
|
728
778
|
# :method: background_color
|
729
779
|
# :call-seq:
|
@@ -842,8 +892,10 @@ module HexaPDF
|
|
842
892
|
[:align, :left],
|
843
893
|
[:valign, :top],
|
844
894
|
[:text_indent, 0],
|
845
|
-
[:line_spacing, "LineSpacing.new(:single)",
|
846
|
-
"LineSpacing.new(value
|
895
|
+
[:line_spacing, "LineSpacing.new(type: :single)",
|
896
|
+
"LineSpacing.new(value.kind_of?(Symbol) ? {type: value, value: extra_arg} : value)",
|
897
|
+
", extra_arg = nil"],
|
898
|
+
[:last_line_gap, false],
|
847
899
|
[:background_color, nil],
|
848
900
|
[:padding, "Quad.new(0)", "Quad.new(value)"],
|
849
901
|
[:margin, "Quad.new(0)", "Quad.new(value)"],
|
@@ -56,26 +56,56 @@ module HexaPDF
|
|
56
56
|
# Depending on the 'position' style property, the text is either fit into the rectangular area
|
57
57
|
# given by +available_width+ and +available_height+, or fit to the outline of the frame
|
58
58
|
# starting from the top.
|
59
|
+
#
|
60
|
+
# The spacing after the last line can be controlled via the style property +last_line_gap+.
|
61
|
+
#
|
62
|
+
# Also see TextLayouter#style for other style properties taken into account.
|
59
63
|
def fit(available_width, available_height, frame)
|
64
|
+
@width = @height = 0
|
60
65
|
@result = if style.position == :flow
|
61
66
|
@tl.fit(@items, frame.width_specification, frame.contour_line.bbox.height)
|
62
67
|
else
|
63
|
-
@
|
68
|
+
@width = reserved_width
|
69
|
+
@height = reserved_height
|
70
|
+
width = (@initial_width > 0 ? @initial_width : available_width) - @width
|
71
|
+
height = (@initial_height > 0 ? @initial_height : available_height) - @height
|
72
|
+
@tl.fit(@items, width, height)
|
64
73
|
end
|
65
|
-
@
|
66
|
-
@
|
74
|
+
@width += @result.lines.max_by(&:width)&.width || 0
|
75
|
+
@height += @result.height
|
76
|
+
if style.last_line_gap && @result.lines.last
|
77
|
+
@height += style.line_spacing.gap(@result.lines.last, @result.lines.last)
|
78
|
+
end
|
79
|
+
|
80
|
+
@result.status == :success
|
81
|
+
end
|
82
|
+
|
83
|
+
# Splits the text box into two boxes if necessary and possible.
|
84
|
+
def split(available_width, available_height, frame)
|
85
|
+
fit(available_width, available_height, frame) unless @result
|
86
|
+
if @result.remaining_items.empty?
|
87
|
+
[self]
|
88
|
+
elsif @result.lines.empty?
|
89
|
+
[nil, self]
|
90
|
+
else
|
91
|
+
box = clone
|
92
|
+
box.instance_variable_set(:@result, nil)
|
93
|
+
box.instance_variable_set(:@items, @result.remaining_items)
|
94
|
+
[self, box]
|
95
|
+
end
|
96
|
+
end
|
67
97
|
|
68
|
-
|
69
|
-
|
70
|
-
|
98
|
+
# :nodoc:
|
99
|
+
def empty?
|
100
|
+
super && (!@result || @result.lines.empty?)
|
71
101
|
end
|
72
102
|
|
73
103
|
private
|
74
104
|
|
75
105
|
# Draws the text into the box.
|
76
|
-
def
|
106
|
+
def draw_content(canvas, x, y)
|
77
107
|
return unless @result && !@result.lines.empty?
|
78
|
-
@result.draw(canvas,
|
108
|
+
@result.draw(canvas, x, y + content_height)
|
79
109
|
end
|
80
110
|
|
81
111
|
end
|
@@ -276,6 +276,19 @@ module HexaPDF
|
|
276
276
|
end
|
277
277
|
end
|
278
278
|
|
279
|
+
# A dummy line class for use with variable width wrapping, and Style#line_spacing methods in
|
280
|
+
# case a line actually consists of multiple line fragments.
|
281
|
+
DummyLine = Struct.new(:y_min, :y_max) do
|
282
|
+
def update(y_min, y_max)
|
283
|
+
self.y_min = y_min
|
284
|
+
self.y_max = y_max
|
285
|
+
end
|
286
|
+
|
287
|
+
def height
|
288
|
+
y_max - y_min
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
279
292
|
# Implementation of a simple line wrapping algorithm.
|
280
293
|
#
|
281
294
|
# The algorithm arranges the given items so that the maximum number is put onto each line,
|
@@ -297,10 +310,11 @@ module HexaPDF
|
|
297
310
|
# is the indentation of the first line). This is the general case.
|
298
311
|
#
|
299
312
|
# * However, if lines should have varying widths (e.g. for flowing text around shapes), the
|
300
|
-
# +width_block+ argument should be an object responding to #call(
|
301
|
-
# +
|
302
|
-
# for
|
303
|
-
# and is therefore
|
313
|
+
# +width_block+ argument should be an object responding to #call(line_like) where
|
314
|
+
# +line_like+ is a Line-like object responding to #y_min, #y_max and #height holding the
|
315
|
+
# values for the currently layed out line. The caller is responsible for tracking the
|
316
|
+
# height of the already layed out lines. This method involves more work and is therefore
|
317
|
+
# slower.
|
304
318
|
#
|
305
319
|
# Regardless of whether varying line widths are used or not, each time a line is finished,
|
306
320
|
# it is yielded to the caller. The second argument +item+ is the item that caused the line
|
@@ -332,7 +346,6 @@ module HexaPDF
|
|
332
346
|
def initialize(items, width_block)
|
333
347
|
@items = items
|
334
348
|
@width_block = width_block
|
335
|
-
@available_width = @width_block.call(0)
|
336
349
|
@line_items = []
|
337
350
|
@width = 0
|
338
351
|
@glue_items = []
|
@@ -342,7 +355,9 @@ module HexaPDF
|
|
342
355
|
@break_prohibited_state = false
|
343
356
|
|
344
357
|
@height_calc = Line::HeightCalculator.new
|
345
|
-
@
|
358
|
+
@line = DummyLine.new(0, 0)
|
359
|
+
|
360
|
+
@available_width = @width_block.call(@line)
|
346
361
|
end
|
347
362
|
|
348
363
|
# Peforms line wrapping with a fixed width per line, with line height playing no role.
|
@@ -408,14 +423,14 @@ module HexaPDF
|
|
408
423
|
while (item = @items[index])
|
409
424
|
case item.type
|
410
425
|
when :box
|
411
|
-
new_height = @height_calc.simulate_height(item.item)
|
412
|
-
if new_height > @
|
413
|
-
@
|
414
|
-
@available_width = @width_block.call(@
|
426
|
+
y_min, y_max, new_height = @height_calc.simulate_height(item.item)
|
427
|
+
if new_height > @line.height
|
428
|
+
@line.update(y_min, y_max)
|
429
|
+
@available_width = @width_block.call(@line)
|
415
430
|
if !@available_width || @width > @available_width
|
416
431
|
index = (@available_width ? @beginning_of_line_index : @stored_index)
|
417
432
|
item = @items[index]
|
418
|
-
reset_after_line_break_variable_width(index
|
433
|
+
reset_after_line_break_variable_width(index)
|
419
434
|
redo
|
420
435
|
end
|
421
436
|
end
|
@@ -427,18 +442,18 @@ module HexaPDF
|
|
427
442
|
item = @items[index]
|
428
443
|
end
|
429
444
|
break unless (action = yield(create_line, item))
|
430
|
-
reset_after_line_break_variable_width(index,
|
445
|
+
reset_after_line_break_variable_width(index, true, action)
|
431
446
|
redo
|
432
447
|
end
|
433
448
|
when :glue
|
434
449
|
unless add_glue_item(item.item, index)
|
435
450
|
break unless (action = yield(create_line, item))
|
436
|
-
reset_after_line_break_variable_width(index + 1,
|
451
|
+
reset_after_line_break_variable_width(index + 1, true, action)
|
437
452
|
end
|
438
453
|
when :penalty
|
439
454
|
if item.penalty <= -Penalty::INFINITY
|
440
455
|
break unless (action = yield(create_unjustified_line, item))
|
441
|
-
reset_after_line_break_variable_width(index + 1,
|
456
|
+
reset_after_line_break_variable_width(index + 1, true, action)
|
442
457
|
elsif item.penalty >= Penalty::INFINITY
|
443
458
|
@break_prohibited_state = true
|
444
459
|
add_box_item(item.item) if item.width > 0
|
@@ -447,8 +462,8 @@ module HexaPDF
|
|
447
462
|
next_index = index + 1
|
448
463
|
next_item = @items[next_index]
|
449
464
|
next_item = @items[next_index += 1] while next_item&.type == :penalty
|
450
|
-
new_height = @height_calc.simulate_height(next_item.item)
|
451
|
-
if next_item && @width + next_item.width > @width_block.call(
|
465
|
+
y_min, y_max, new_height = @height_calc.simulate_height(next_item.item)
|
466
|
+
if next_item && @width + next_item.width > @width_block.call(DummyLine.new(y_min, y_max))
|
452
467
|
@line_items.concat(@glue_items).push(item.item)
|
453
468
|
@width += item.width
|
454
469
|
# No need to clean up, since in the next iteration a line break occurs
|
@@ -531,7 +546,7 @@ module HexaPDF
|
|
531
546
|
# Resets the line state variables to their initial values. The +index+ specifies the items
|
532
547
|
# index of the first item on the new line. The +line_height+ specifies the line height to
|
533
548
|
# use for getting the available width.
|
534
|
-
def reset_after_line_break(index
|
549
|
+
def reset_after_line_break(index)
|
535
550
|
@beginning_of_line_index = index
|
536
551
|
@line_items.clear
|
537
552
|
@width = 0
|
@@ -539,7 +554,7 @@ module HexaPDF
|
|
539
554
|
@last_breakpoint_index = index
|
540
555
|
@last_breakpoint_line_items_index = 0
|
541
556
|
@break_prohibited_state = false
|
542
|
-
@available_width = @width_block.call(
|
557
|
+
@available_width = @width_block.call(@line)
|
543
558
|
end
|
544
559
|
|
545
560
|
# Specialized reset method for variable width wrapping.
|
@@ -548,11 +563,11 @@ module HexaPDF
|
|
548
563
|
#
|
549
564
|
# * If the +action+ argument is +:store_start_of_line+, the stored item index is reset to
|
550
565
|
# the index of the first item of the line.
|
551
|
-
def reset_after_line_break_variable_width(index,
|
566
|
+
def reset_after_line_break_variable_width(index, reset_line = false, action = :none)
|
552
567
|
@stored_index = @beginning_of_line_index if action == :store_start_of_line
|
553
|
-
@
|
568
|
+
@line.update(0, 0) if reset_line
|
554
569
|
@height_calc.reset
|
555
|
-
reset_after_line_break(index
|
570
|
+
reset_after_line_break(index)
|
556
571
|
end
|
557
572
|
|
558
573
|
end
|
@@ -592,23 +607,23 @@ module HexaPDF
|
|
592
607
|
|
593
608
|
# Draws the layed out lines onto the canvas with the top-left corner being at [x, y].
|
594
609
|
def draw(canvas, x, y)
|
595
|
-
|
610
|
+
last_text_fragment = nil
|
596
611
|
canvas.save_graphics_state do
|
597
612
|
# Best effort for leading in case we have an evenly spaced paragraph
|
598
613
|
canvas.leading(@lines[1].y_offset) if @lines.size > 1
|
599
|
-
@lines.
|
600
|
-
y -=
|
614
|
+
@lines.each do |line|
|
615
|
+
y -= line.y_offset
|
601
616
|
line_x = x + line.x_offset
|
602
617
|
line.each do |item, item_x, item_y|
|
603
618
|
if item.kind_of?(TextFragment)
|
604
619
|
item.draw(canvas, line_x + item_x, y + item_y,
|
605
|
-
ignore_text_properties:
|
606
|
-
|
620
|
+
ignore_text_properties: last_text_fragment&.style == item.style)
|
621
|
+
last_text_fragment = item
|
607
622
|
elsif !item.empty?
|
608
623
|
canvas.restore_graphics_state
|
609
624
|
item.draw(canvas, line_x + item_x, y + item_y)
|
610
625
|
canvas.save_graphics_state
|
611
|
-
|
626
|
+
last_text_fragment = nil
|
612
627
|
end
|
613
628
|
end
|
614
629
|
end
|
@@ -681,21 +696,26 @@ module HexaPDF
|
|
681
696
|
indent = style.text_indent
|
682
697
|
line_fragments = []
|
683
698
|
line_height = 0
|
684
|
-
|
699
|
+
previous_line = nil
|
685
700
|
y_offset = 0
|
686
701
|
width_spec = nil
|
687
702
|
width_spec_index = 0
|
688
703
|
width_block =
|
689
704
|
if width.respond_to?(:call)
|
690
705
|
last_actual_height = nil
|
691
|
-
|
692
|
-
proc do |
|
693
|
-
line_height = [line_height,
|
694
|
-
if last_actual_height != actual_height ||
|
695
|
-
|
706
|
+
previous_line_height = nil
|
707
|
+
proc do |cur_line|
|
708
|
+
line_height = [line_height, cur_line.height || 0].max
|
709
|
+
if last_actual_height != actual_height || previous_line_height != line_height
|
710
|
+
gap = if previous_line
|
711
|
+
style.line_spacing.gap(previous_line, cur_line)
|
712
|
+
else
|
713
|
+
0
|
714
|
+
end
|
715
|
+
spec = width.call(actual_height + gap, cur_line.height)
|
696
716
|
spec = [0, spec] unless spec.kind_of?(Array)
|
697
717
|
last_actual_height = actual_height
|
698
|
-
|
718
|
+
previous_line_height = line_height
|
699
719
|
else
|
700
720
|
spec = width_spec
|
701
721
|
end
|
@@ -732,7 +752,7 @@ module HexaPDF
|
|
732
752
|
# item didn't fit into first part, find next available part
|
733
753
|
if line.items.empty? && line_fragments.empty?
|
734
754
|
old_height = actual_height
|
735
|
-
while item.width > width_block.call(item.
|
755
|
+
while item.width > width_block.call(item.item) && actual_height <= height
|
736
756
|
width_spec_index += 1
|
737
757
|
if width_spec_index >= width_spec.size / 2
|
738
758
|
actual_height += item.height / 3
|
@@ -761,11 +781,11 @@ module HexaPDF
|
|
761
781
|
|
762
782
|
combined_line = create_combined_line(line_fragments)
|
763
783
|
new_height = actual_height + combined_line.height +
|
764
|
-
(
|
784
|
+
(previous_line ? style.line_spacing.gap(previous_line, combined_line) : 0)
|
765
785
|
|
766
786
|
if new_height <= height
|
767
787
|
# valid line found, use it
|
768
|
-
apply_offsets(line_fragments, width_spec, indent,
|
788
|
+
apply_offsets(line_fragments, width_spec, indent, previous_line, combined_line, y_offset)
|
769
789
|
lines.concat(line_fragments)
|
770
790
|
line_fragments.clear
|
771
791
|
width_spec_index = 0
|
@@ -774,7 +794,7 @@ module HexaPDF
|
|
774
794
|
else
|
775
795
|
0
|
776
796
|
end
|
777
|
-
|
797
|
+
previous_line = combined_line
|
778
798
|
actual_height = new_height
|
779
799
|
line_height = 0
|
780
800
|
y_offset = nil
|
@@ -805,16 +825,6 @@ module HexaPDF
|
|
805
825
|
|
806
826
|
private
|
807
827
|
|
808
|
-
# :nodoc:
|
809
|
-
#
|
810
|
-
# A dummy line class for use with Style#line_spacing methods in case a line actually consists
|
811
|
-
# of multiple line fragments.
|
812
|
-
DummyLine = Struct.new(:y_min, :y_max) do
|
813
|
-
def height
|
814
|
-
y_max - y_min
|
815
|
-
end
|
816
|
-
end
|
817
|
-
|
818
828
|
# Creates a line combining all items from the given line fragments for height calculations.
|
819
829
|
def create_combined_line(line_frags)
|
820
830
|
if line_frags.size == 1
|
@@ -828,7 +838,11 @@ module HexaPDF
|
|
828
838
|
end
|
829
839
|
|
830
840
|
# Applies the necessary x- and y-offsets to the line fragments.
|
831
|
-
|
841
|
+
#
|
842
|
+
# Note that the offset for the first fragment of the first line is the top of the line since
|
843
|
+
# the #initial_baseline_offset method applies the correct offset to it once layouting is
|
844
|
+
# completely done.
|
845
|
+
def apply_offsets(line_frags, width_spec, indent, previous_line, combined_line, y_offset)
|
832
846
|
cumulated_width = 0
|
833
847
|
line_frags.each_with_index do |line, index|
|
834
848
|
line.x_offset = cumulated_width + indent
|
@@ -838,12 +852,12 @@ module HexaPDF
|
|
838
852
|
if index == 0
|
839
853
|
line.y_offset = if y_offset
|
840
854
|
y_offset + combined_line.y_max -
|
841
|
-
(
|
855
|
+
(previous_line ? previous_line.y_min : line.y_max)
|
842
856
|
else
|
843
|
-
style.line_spacing.baseline_distance(
|
857
|
+
style.line_spacing.baseline_distance(previous_line, combined_line)
|
844
858
|
end
|
859
|
+
indent = 0
|
845
860
|
end
|
846
|
-
indent = 0
|
847
861
|
end
|
848
862
|
end
|
849
863
|
|