hexapdf 0.8.0 → 0.9.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 +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
|
|