hexapdf 0.5.0 → 0.6.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 +76 -2
- data/CONTRIBUTERS +1 -1
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/examples/boxes.rb +68 -0
- data/examples/graphics.rb +12 -12
- data/examples/{text_box_alignment.rb → text_layouter_alignment.rb} +14 -14
- data/examples/text_layouter_inline_boxes.rb +66 -0
- data/examples/{text_box_line_wrapping.rb → text_layouter_line_wrapping.rb} +9 -10
- data/examples/{text_box_shapes.rb → text_layouter_shapes.rb} +58 -54
- data/examples/text_layouter_styling.rb +125 -0
- data/examples/truetype.rb +5 -7
- data/lib/hexapdf/cli/command.rb +1 -0
- data/lib/hexapdf/configuration.rb +170 -106
- data/lib/hexapdf/content/canvas.rb +41 -36
- data/lib/hexapdf/content/graphics_state.rb +15 -0
- data/lib/hexapdf/content/operator.rb +1 -1
- data/lib/hexapdf/dictionary.rb +20 -8
- data/lib/hexapdf/dictionary_fields.rb +8 -6
- data/lib/hexapdf/document.rb +25 -26
- data/lib/hexapdf/document/fonts.rb +4 -4
- data/lib/hexapdf/document/images.rb +2 -2
- data/lib/hexapdf/document/pages.rb +16 -16
- data/lib/hexapdf/encryption/security_handler.rb +41 -9
- data/lib/hexapdf/filter/flate_decode.rb +1 -1
- data/lib/hexapdf/filter/lzw_decode.rb +1 -1
- data/lib/hexapdf/filter/predictor.rb +7 -1
- data/lib/hexapdf/font/true_type/font.rb +20 -0
- data/lib/hexapdf/font/type1/font.rb +23 -0
- data/lib/hexapdf/font_loader.rb +1 -0
- data/lib/hexapdf/font_loader/from_configuration.rb +2 -3
- data/lib/hexapdf/font_loader/from_file.rb +65 -0
- data/lib/hexapdf/image_loader/png.rb +2 -2
- data/lib/hexapdf/layout.rb +3 -2
- data/lib/hexapdf/layout/box.rb +146 -0
- data/lib/hexapdf/layout/inline_box.rb +40 -31
- data/lib/hexapdf/layout/{line_fragment.rb → line.rb} +12 -13
- data/lib/hexapdf/layout/style.rb +630 -41
- data/lib/hexapdf/layout/text_fragment.rb +80 -12
- data/lib/hexapdf/layout/{text_box.rb → text_layouter.rb} +164 -109
- data/lib/hexapdf/number_tree_node.rb +1 -1
- data/lib/hexapdf/parser.rb +4 -1
- data/lib/hexapdf/revisions.rb +11 -4
- data/lib/hexapdf/stream.rb +8 -9
- data/lib/hexapdf/tokenizer.rb +5 -3
- data/lib/hexapdf/type.rb +3 -0
- data/lib/hexapdf/type/action.rb +56 -0
- data/lib/hexapdf/type/actions.rb +52 -0
- data/lib/hexapdf/type/actions/go_to.rb +52 -0
- data/lib/hexapdf/type/actions/go_to_r.rb +54 -0
- data/lib/hexapdf/type/actions/launch.rb +73 -0
- data/lib/hexapdf/type/actions/uri.rb +65 -0
- data/lib/hexapdf/type/annotation.rb +85 -0
- data/lib/hexapdf/type/annotations.rb +51 -0
- data/lib/hexapdf/type/annotations/link.rb +70 -0
- data/lib/hexapdf/type/annotations/markup_annotation.rb +70 -0
- data/lib/hexapdf/type/annotations/text.rb +81 -0
- data/lib/hexapdf/type/catalog.rb +3 -1
- data/lib/hexapdf/type/embedded_file.rb +6 -11
- data/lib/hexapdf/type/file_specification.rb +4 -6
- data/lib/hexapdf/type/font.rb +3 -1
- data/lib/hexapdf/type/font_descriptor.rb +18 -16
- data/lib/hexapdf/type/form.rb +3 -1
- data/lib/hexapdf/type/graphics_state_parameter.rb +3 -1
- data/lib/hexapdf/type/image.rb +4 -2
- data/lib/hexapdf/type/info.rb +2 -5
- data/lib/hexapdf/type/names.rb +2 -5
- data/lib/hexapdf/type/object_stream.rb +2 -1
- data/lib/hexapdf/type/page.rb +14 -1
- data/lib/hexapdf/type/page_tree_node.rb +9 -6
- data/lib/hexapdf/type/resources.rb +2 -5
- data/lib/hexapdf/type/trailer.rb +2 -5
- data/lib/hexapdf/type/viewer_preferences.rb +2 -5
- data/lib/hexapdf/type/xref_stream.rb +3 -1
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/common_tokenizer_tests.rb +3 -1
- data/test/hexapdf/content/test_canvas.rb +29 -3
- data/test/hexapdf/content/test_graphics_state.rb +11 -0
- data/test/hexapdf/content/test_operator.rb +3 -2
- data/test/hexapdf/document/test_fonts.rb +8 -8
- data/test/hexapdf/document/test_images.rb +4 -12
- data/test/hexapdf/document/test_pages.rb +7 -7
- data/test/hexapdf/encryption/test_security_handler.rb +1 -5
- data/test/hexapdf/filter/test_predictor.rb +40 -12
- data/test/hexapdf/font/true_type/test_font.rb +16 -0
- data/test/hexapdf/font/type1/test_font.rb +30 -0
- data/test/hexapdf/font_loader/test_from_file.rb +29 -0
- data/test/hexapdf/font_loader/test_standard14.rb +4 -3
- data/test/hexapdf/layout/test_box.rb +104 -0
- data/test/hexapdf/layout/test_inline_box.rb +24 -10
- data/test/hexapdf/layout/{test_line_fragment.rb → test_line.rb} +9 -9
- data/test/hexapdf/layout/test_style.rb +519 -31
- data/test/hexapdf/layout/test_text_fragment.rb +136 -15
- data/test/hexapdf/layout/{test_text_box.rb → test_text_layouter.rb} +224 -144
- data/test/hexapdf/layout/test_text_shaper.rb +1 -1
- data/test/hexapdf/test_configuration.rb +12 -6
- data/test/hexapdf/test_dictionary.rb +27 -2
- data/test/hexapdf/test_dictionary_fields.rb +10 -1
- data/test/hexapdf/test_document.rb +14 -13
- data/test/hexapdf/test_parser.rb +12 -0
- data/test/hexapdf/test_revisions.rb +34 -0
- data/test/hexapdf/test_stream.rb +1 -1
- data/test/hexapdf/test_type.rb +18 -0
- data/test/hexapdf/test_writer.rb +2 -2
- data/test/hexapdf/type/actions/test_launch.rb +24 -0
- data/test/hexapdf/type/actions/test_uri.rb +23 -0
- data/test/hexapdf/type/annotations/test_link.rb +19 -0
- data/test/hexapdf/type/annotations/test_markup_annotation.rb +22 -0
- data/test/hexapdf/type/annotations/test_text.rb +38 -0
- data/test/hexapdf/type/test_annotation.rb +38 -0
- data/test/hexapdf/type/test_file_specification.rb +0 -7
- data/test/hexapdf/type/test_info.rb +0 -5
- data/test/hexapdf/type/test_page.rb +14 -0
- data/test/hexapdf/type/test_page_tree_node.rb +4 -1
- data/test/hexapdf/type/test_trailer.rb +0 -4
- data/test/test_helper.rb +6 -3
- metadata +36 -15
- data/examples/text_box_inline_boxes.rb +0 -56
- data/examples/text_box_styling.rb +0 -72
- data/test/hexapdf/type/test_embedded_file.rb +0 -16
- data/test/hexapdf/type/test_names.rb +0 -9
|
@@ -31,52 +31,61 @@
|
|
|
31
31
|
# is created or manipulated using HexaPDF.
|
|
32
32
|
#++
|
|
33
33
|
|
|
34
|
+
require 'hexapdf/layout/box'
|
|
35
|
+
|
|
34
36
|
module HexaPDF
|
|
35
37
|
module Layout
|
|
36
38
|
|
|
37
|
-
# An InlineBox can be used as an item for a
|
|
38
|
-
#
|
|
39
|
+
# An InlineBox wraps a regular Box so that it can be used as an item for a Line. This enables
|
|
40
|
+
# inline graphics.
|
|
41
|
+
#
|
|
42
|
+
# The wrapped box *must* have a fixed size!
|
|
39
43
|
class InlineBox
|
|
40
44
|
|
|
41
|
-
#
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
#
|
|
45
|
-
|
|
45
|
+
# Creates an InlineBox that wraps a basic Box. All arguments (except +valign+) and the block
|
|
46
|
+
# are passed to Box::new.
|
|
47
|
+
#
|
|
48
|
+
# See ::new for the +valign+ argument.
|
|
49
|
+
def self.create(valign: :baseline, **args, &block)
|
|
50
|
+
new(Box.new(**args, &block), valign: valign)
|
|
51
|
+
end
|
|
46
52
|
|
|
47
53
|
# The vertical alignment of the box.
|
|
48
54
|
#
|
|
49
|
-
# Can be any supported value except :text - see
|
|
55
|
+
# Can be any supported value except :text - see Line for all possible values.
|
|
50
56
|
attr_reader :valign
|
|
51
57
|
|
|
52
|
-
#
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
# Creates a new InlineBox object
|
|
56
|
-
# on a canvas (see #draw).
|
|
57
|
-
#
|
|
58
|
-
# Since the final location of the box is not known beforehand, the drawing operations inside
|
|
59
|
-
# the block should draw inside the rectangle (0, 0, width, height).
|
|
58
|
+
# The wrapped Box object.
|
|
59
|
+
attr_reader :box
|
|
60
|
+
|
|
61
|
+
# Creates a new InlineBox object wrapping +box+.
|
|
60
62
|
#
|
|
61
63
|
# The +valign+ argument can be used to specify the vertical alignment of the box relative to
|
|
62
|
-
# other items in the
|
|
63
|
-
def initialize(
|
|
64
|
-
@
|
|
65
|
-
@height = height
|
|
64
|
+
# other items in the Line.
|
|
65
|
+
def initialize(box, valign: :baseline)
|
|
66
|
+
@box = box
|
|
66
67
|
@valign = valign
|
|
67
|
-
@draw_block = block
|
|
68
68
|
end
|
|
69
69
|
|
|
70
|
-
#
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
#
|
|
76
|
-
|
|
77
|
-
|
|
70
|
+
# Returns +true+ if this inline box is just a placeholder without drawing operations.
|
|
71
|
+
def empty?
|
|
72
|
+
box.empty?
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Returns the width of the wrapped box plus its left and right margins.
|
|
76
|
+
def width
|
|
77
|
+
box.width + box.style.margin.left + box.style.margin.right
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Returns the height of the wrapped box plus its top and bottom margins.
|
|
81
|
+
def height
|
|
82
|
+
box.height + box.style.margin.top + box.style.margin.bottom
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Draws the wrapped box. If the box has margins specified, the x and y offsets are correctly
|
|
86
|
+
# adjusted.
|
|
78
87
|
def draw(canvas, x, y)
|
|
79
|
-
|
|
88
|
+
box.draw(canvas, x + box.style.margin.left, y + box.style.margin.bottom)
|
|
80
89
|
end
|
|
81
90
|
|
|
82
91
|
# The minimum x-coordinate which is always 0.
|
|
@@ -84,7 +93,7 @@ module HexaPDF
|
|
|
84
93
|
0
|
|
85
94
|
end
|
|
86
95
|
|
|
87
|
-
# The maximum x-coordinate which is equivalent to the width of the box.
|
|
96
|
+
# The maximum x-coordinate which is equivalent to the width of the inline box.
|
|
88
97
|
def x_max
|
|
89
98
|
width
|
|
90
99
|
end
|
|
@@ -37,26 +37,25 @@ require 'hexapdf/layout/text_fragment'
|
|
|
37
37
|
module HexaPDF
|
|
38
38
|
module Layout
|
|
39
39
|
|
|
40
|
-
# A
|
|
41
|
-
# objects.
|
|
40
|
+
# A Line describes a line of text and can contain TextFragment objects or InlineBox objects.
|
|
42
41
|
#
|
|
43
42
|
# The items of a line fragment are aligned along the x-axis which coincides with the text
|
|
44
43
|
# baseline. The vertical alignment is determined by the value of the #valign method:
|
|
45
44
|
#
|
|
46
45
|
# :text_top::
|
|
47
|
-
# Align the top of the box with the top of the text of the
|
|
46
|
+
# Align the top of the box with the top of the text of the Line.
|
|
48
47
|
#
|
|
49
48
|
# :text_bottom::
|
|
50
|
-
# Align the bottom of the box with the bottom of the text of the
|
|
49
|
+
# Align the bottom of the box with the bottom of the text of the Line.
|
|
51
50
|
#
|
|
52
51
|
# :baseline::
|
|
53
|
-
# Align the bottom of the box with the baseline of the
|
|
52
|
+
# Align the bottom of the box with the baseline of the Line.
|
|
54
53
|
#
|
|
55
54
|
# :top::
|
|
56
|
-
# Align the top of the box with the top of the
|
|
55
|
+
# Align the top of the box with the top of the Line.
|
|
57
56
|
#
|
|
58
57
|
# :bottom::
|
|
59
|
-
# Align the bottom of the box with the bottom of the
|
|
58
|
+
# Align the bottom of the box with the bottom of the Line.
|
|
60
59
|
#
|
|
61
60
|
# :text::
|
|
62
61
|
# This is a special alignment value for text fragment objects. The text fragment is aligned
|
|
@@ -86,7 +85,7 @@ module HexaPDF
|
|
|
86
85
|
# implemented:
|
|
87
86
|
#
|
|
88
87
|
# #height:: The height of the item.
|
|
89
|
-
class
|
|
88
|
+
class Line
|
|
90
89
|
|
|
91
90
|
# Helper class for calculating the needed vertical dimensions of a line.
|
|
92
91
|
class HeightCalculator
|
|
@@ -122,7 +121,7 @@ module HexaPDF
|
|
|
122
121
|
|
|
123
122
|
# Returns the result of the calculations, the array [y_min, y_max, text_y_min, text_y_max].
|
|
124
123
|
#
|
|
125
|
-
# See
|
|
124
|
+
# See Line for their meaning.
|
|
126
125
|
def result
|
|
127
126
|
y_min = [@text_y_max - @max_text_top_height, @text_y_min].min
|
|
128
127
|
y_max = [@text_y_min + @max_text_bottom_height, @max_base_height, @text_y_max].max
|
|
@@ -180,7 +179,7 @@ module HexaPDF
|
|
|
180
179
|
# the baseline of this line.
|
|
181
180
|
attr_accessor :y_offset
|
|
182
181
|
|
|
183
|
-
# Creates a new
|
|
182
|
+
# Creates a new Line object, adding all given items to it.
|
|
184
183
|
def initialize(items = [])
|
|
185
184
|
@items = []
|
|
186
185
|
items.each {|i| add(i)}
|
|
@@ -211,7 +210,7 @@ module HexaPDF
|
|
|
211
210
|
alias :<< :add
|
|
212
211
|
|
|
213
212
|
# :call-seq:
|
|
214
|
-
#
|
|
213
|
+
# line.each {|item, x, y| block }
|
|
215
214
|
#
|
|
216
215
|
# Yields each item together with its horizontal offset from 0 and vertical offset from the
|
|
217
216
|
# baseline.
|
|
@@ -287,7 +286,7 @@ module HexaPDF
|
|
|
287
286
|
end
|
|
288
287
|
|
|
289
288
|
# :call-seq:
|
|
290
|
-
#
|
|
289
|
+
# line.clear_cache -> line
|
|
291
290
|
#
|
|
292
291
|
# Clears all cached values.
|
|
293
292
|
#
|
|
@@ -300,7 +299,7 @@ module HexaPDF
|
|
|
300
299
|
private
|
|
301
300
|
|
|
302
301
|
# :call-seq:
|
|
303
|
-
#
|
|
302
|
+
# line.calculate_y_dimensions -> [y_min, y_max, text_y_min, text_y_max]
|
|
304
303
|
#
|
|
305
304
|
# Calculates all y-values and returns them as array.
|
|
306
305
|
#
|
data/lib/hexapdf/layout/style.rb
CHANGED
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
#++
|
|
33
33
|
|
|
34
34
|
require 'hexapdf/error'
|
|
35
|
+
require 'hexapdf/content/graphics_state'
|
|
35
36
|
|
|
36
37
|
module HexaPDF
|
|
37
38
|
module Layout
|
|
@@ -40,6 +41,13 @@ module HexaPDF
|
|
|
40
41
|
#
|
|
41
42
|
# Each property except #font has a default value, so only the desired properties need to be
|
|
42
43
|
# changed.
|
|
44
|
+
#
|
|
45
|
+
# Each property has three associated methods:
|
|
46
|
+
#
|
|
47
|
+
# property_name:: Getter method.
|
|
48
|
+
# property_name(*args) and property_name=:: Setter method.
|
|
49
|
+
# property_name?:: Tester method to see if a value has been set or if the default value has
|
|
50
|
+
# already been used.
|
|
43
51
|
class Style
|
|
44
52
|
|
|
45
53
|
# Defines how the distance between the baselines of two adjacent text lines is determined:
|
|
@@ -92,7 +100,7 @@ module HexaPDF
|
|
|
92
100
|
end
|
|
93
101
|
end
|
|
94
102
|
|
|
95
|
-
# Returns the distance between the baselines of the two given
|
|
103
|
+
# Returns the distance between the baselines of the two given Line objects.
|
|
96
104
|
def baseline_distance(line1, line2)
|
|
97
105
|
case type
|
|
98
106
|
when :proportional then (line1.y_min.abs + line2.y_max) * value
|
|
@@ -101,8 +109,8 @@ module HexaPDF
|
|
|
101
109
|
end
|
|
102
110
|
end
|
|
103
111
|
|
|
104
|
-
# Returns the gap between the two given
|
|
105
|
-
#
|
|
112
|
+
# Returns the gap between the two given Line objects, i.e. the distance between the y_min of
|
|
113
|
+
# the first line and the y_max of the second line.
|
|
106
114
|
def gap(line1, line2)
|
|
107
115
|
case type
|
|
108
116
|
when :proportional then (line1.y_min.abs + line2.y_max) * (value - 1)
|
|
@@ -113,6 +121,374 @@ module HexaPDF
|
|
|
113
121
|
|
|
114
122
|
end
|
|
115
123
|
|
|
124
|
+
# A Quad holds four values and allows them to be accessed by the names top, right, bottom and
|
|
125
|
+
# left. Quads are normally used for holding values pertaining to boxes, like margins, paddings
|
|
126
|
+
# or borders.
|
|
127
|
+
class Quad
|
|
128
|
+
|
|
129
|
+
# The value for top.
|
|
130
|
+
attr_accessor :top
|
|
131
|
+
|
|
132
|
+
# The value for bottom.
|
|
133
|
+
attr_accessor :bottom
|
|
134
|
+
|
|
135
|
+
# The value for left.
|
|
136
|
+
attr_accessor :left
|
|
137
|
+
|
|
138
|
+
# The value for right.
|
|
139
|
+
attr_accessor :right
|
|
140
|
+
|
|
141
|
+
# :call-seq:
|
|
142
|
+
# Quad.new(value)
|
|
143
|
+
# Quad.new(array)
|
|
144
|
+
# Quad.new(quad)
|
|
145
|
+
#
|
|
146
|
+
# Creates a new Quad object.
|
|
147
|
+
#
|
|
148
|
+
# * If a single value is provided that is neither a Quad nor an array, it is handled as if
|
|
149
|
+
# an array with one value was given.
|
|
150
|
+
#
|
|
151
|
+
# * If a Quad is provided, its values are used.
|
|
152
|
+
#
|
|
153
|
+
# * If an array is provided, it depends on the number of elemens in it:
|
|
154
|
+
#
|
|
155
|
+
# * One value: All attributes are set to the same value.
|
|
156
|
+
# * Two values: Top and bottom are set to the first value, left and right to the second
|
|
157
|
+
# value.
|
|
158
|
+
# * Three values: Top is set to the first, left and right to the second, and bottom to the
|
|
159
|
+
# third value.
|
|
160
|
+
# * Four or more values: Top is set to the first, right to the second, bottom to the third
|
|
161
|
+
# and left to the fourth value.
|
|
162
|
+
def initialize(obj)
|
|
163
|
+
case obj
|
|
164
|
+
when Quad
|
|
165
|
+
@top = obj.top
|
|
166
|
+
@bottom = obj.bottom
|
|
167
|
+
@left = obj.left
|
|
168
|
+
@right = obj.right
|
|
169
|
+
when Array
|
|
170
|
+
@top = obj[0]
|
|
171
|
+
@bottom = obj[2] || obj[0]
|
|
172
|
+
@left = obj[3] || obj[1] || obj[0]
|
|
173
|
+
@right = obj[1] || obj[0]
|
|
174
|
+
else
|
|
175
|
+
@top = @bottom = @left = @right = obj
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Returns +true+ if the quad effectively contains only one value.
|
|
180
|
+
def simple?
|
|
181
|
+
@top == @bottom && @top == @left && @top == @right
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Represents the border of a rectangular area.
|
|
187
|
+
class Border
|
|
188
|
+
|
|
189
|
+
# The widths of each edge. See Quad.
|
|
190
|
+
attr_reader :width
|
|
191
|
+
|
|
192
|
+
# The colors of each edge. See Quad.
|
|
193
|
+
attr_reader :color
|
|
194
|
+
|
|
195
|
+
# The styles of each edge. See Quad.
|
|
196
|
+
attr_reader :style
|
|
197
|
+
|
|
198
|
+
# Creates a new border style. All arguments can be set to any value that a Quad can process.
|
|
199
|
+
def initialize(width: 0, color: 0, style: :solid)
|
|
200
|
+
@width = Quad.new(width)
|
|
201
|
+
@color = Quad.new(color)
|
|
202
|
+
@style = Quad.new(style)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Returns +true+ if there is no border.
|
|
206
|
+
def none?
|
|
207
|
+
width.simple? && width.top == 0
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Draws the border onto the canvas, inside the rectangle (x, y, w, h).
|
|
211
|
+
def draw(canvas, x, y, w, h)
|
|
212
|
+
canvas.save_graphics_state do
|
|
213
|
+
if width.simple? && color.simple? && style.simple?
|
|
214
|
+
draw_simple_border(canvas, x, y, w, h)
|
|
215
|
+
else
|
|
216
|
+
draw_complex_border(canvas, x, y, w, h)
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
private
|
|
222
|
+
|
|
223
|
+
# Draws the border assuming that only one width, style and color are used.
|
|
224
|
+
def draw_simple_border(canvas, x, y, w, h)
|
|
225
|
+
offset = width.top / 2.0
|
|
226
|
+
canvas.stroke_color(color.top).
|
|
227
|
+
line_width(width.top).
|
|
228
|
+
line_join_style(:miter).
|
|
229
|
+
miter_limit(10).
|
|
230
|
+
line_cap_style(line_cap_style(:top))
|
|
231
|
+
|
|
232
|
+
if style.top == :solid
|
|
233
|
+
canvas.line_dash_pattern(0).
|
|
234
|
+
rectangle(x + offset, y + offset, w - 2 * offset, h - 2 * offset).stroke
|
|
235
|
+
else
|
|
236
|
+
canvas.rectangle(x, y, w, h).clip_path.end_path
|
|
237
|
+
|
|
238
|
+
canvas.line_dash_pattern(line_dash_pattern(:top, w)).
|
|
239
|
+
line(x, y + h - offset, x + w, y + h - offset).
|
|
240
|
+
line(x + w, y + offset, x, y + offset).stroke
|
|
241
|
+
canvas.line_dash_pattern(line_dash_pattern(:right, h)).
|
|
242
|
+
line(x + w - offset, y + h, x + w - offset, y).
|
|
243
|
+
line(x + offset, y, x + offset, y + h).stroke
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Draws a complex border, i.e. one where every edge is potentially differently styled.
|
|
248
|
+
def draw_complex_border(canvas, x, y, w, h)
|
|
249
|
+
left = x
|
|
250
|
+
bottom = y
|
|
251
|
+
right = left + w
|
|
252
|
+
top = bottom + h
|
|
253
|
+
inner_left = left + width.left
|
|
254
|
+
inner_bottom = bottom + width.bottom
|
|
255
|
+
inner_right = right - width.right
|
|
256
|
+
inner_top = top - width.top
|
|
257
|
+
|
|
258
|
+
if width.top > 0
|
|
259
|
+
canvas.save_graphics_state do
|
|
260
|
+
canvas.polyline(left, top, right, top, inner_right, inner_top,
|
|
261
|
+
inner_left, inner_top).
|
|
262
|
+
clip_path.end_path
|
|
263
|
+
canvas.stroke_color(color.top).
|
|
264
|
+
line_width(width.top).
|
|
265
|
+
line_cap_style(line_cap_style(:top)).
|
|
266
|
+
line_dash_pattern(line_dash_pattern(:top, w)).
|
|
267
|
+
line(left, top - width.top / 2.0, right, top - width.top / 2.0).stroke
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
if width.right > 0
|
|
272
|
+
canvas.save_graphics_state do
|
|
273
|
+
canvas.polyline(right, top, right, bottom, inner_right, inner_bottom,
|
|
274
|
+
inner_right, inner_top).
|
|
275
|
+
clip_path.end_path
|
|
276
|
+
canvas.stroke_color(color.right).
|
|
277
|
+
line_width(width.right).
|
|
278
|
+
line_cap_style(line_cap_style(:right)).
|
|
279
|
+
line_dash_pattern(line_dash_pattern(:right, h)).
|
|
280
|
+
line(right - width.right / 2.0, top, right - width.right / 2.0, bottom).stroke
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
if width.bottom > 0
|
|
285
|
+
canvas.save_graphics_state do
|
|
286
|
+
canvas.polyline(right, bottom, left, bottom, inner_left, inner_bottom,
|
|
287
|
+
inner_right, inner_bottom).
|
|
288
|
+
clip_path.end_path
|
|
289
|
+
canvas.stroke_color(color.bottom).
|
|
290
|
+
line_width(width.bottom).
|
|
291
|
+
line_cap_style(line_cap_style(:bottom)).
|
|
292
|
+
line_dash_pattern(line_dash_pattern(:bottom, w)).
|
|
293
|
+
line(right, bottom + width.bottom / 2.0, left, bottom + width.bottom / 2.0).stroke
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
if width.left > 0
|
|
298
|
+
canvas.save_graphics_state do
|
|
299
|
+
canvas.polyline(left, bottom, left, top, inner_left, inner_top,
|
|
300
|
+
inner_left, inner_bottom).
|
|
301
|
+
clip_path.end_path
|
|
302
|
+
canvas.stroke_color(color.left).
|
|
303
|
+
line_width(width.left).
|
|
304
|
+
line_cap_style(line_cap_style(:left)).
|
|
305
|
+
line_dash_pattern(line_dash_pattern(:left, h)).
|
|
306
|
+
line(left + width.left / 2.0, bottom, left + width.left / 2.0, top).stroke
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# Returns the line cap style for the given edge name.
|
|
312
|
+
def line_cap_style(edge)
|
|
313
|
+
case style.send(edge)
|
|
314
|
+
when :solid then :butt
|
|
315
|
+
when :dashed then :projecting_square
|
|
316
|
+
when :dashed_round, :dotted then :round
|
|
317
|
+
else
|
|
318
|
+
raise ArgumentError, "Invalid border style specified: #{style.send(edge)}"
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# Returns the line dash pattern for the given edge name. The argument +length+ needs to
|
|
323
|
+
# contain the length of the edge.
|
|
324
|
+
def line_dash_pattern(edge, length)
|
|
325
|
+
case style.send(edge)
|
|
326
|
+
when :solid
|
|
327
|
+
0
|
|
328
|
+
when :dashed, :dashed_round
|
|
329
|
+
# Due to the used line cap styles, a dash of length w appears with a length of 2w. The
|
|
330
|
+
# gap between dashes is nominally 3w but adjusted so that full dashes start and end in
|
|
331
|
+
# the corners.
|
|
332
|
+
w = width.send(edge)
|
|
333
|
+
count = [(length.to_f / (w * 3)).floor, 1].max
|
|
334
|
+
gap = [(length - w * (count + 2)).to_f, 0].max / count
|
|
335
|
+
HexaPDF::Content::LineDashPattern.new([w, gap], w * 0.5 + gap)
|
|
336
|
+
when :dotted
|
|
337
|
+
# Adjust the gap so that full dots appear in the corners.
|
|
338
|
+
w = width.send(edge)
|
|
339
|
+
gap = (length - w).to_f / (length.to_f / (w * 2)).ceil
|
|
340
|
+
HexaPDF::Content::LineDashPattern.new([0, gap], [gap - w * 0.5, 0].max)
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
# Represents layers that can be drawn under or over a box.
|
|
347
|
+
#
|
|
348
|
+
# There are two ways to specify layers via #add:
|
|
349
|
+
#
|
|
350
|
+
# * Directly by providing a callable object.
|
|
351
|
+
#
|
|
352
|
+
# * By reference to a callable object or class in the 'style.layers_map' configuration option.
|
|
353
|
+
# The reference name is looked up in the configuration option using
|
|
354
|
+
# HexaPDF::Configuration#constantize. If the resulting object is a callable object, it is
|
|
355
|
+
# used; otherwise it is assumed that it is a class and an object is instantiated, passing in
|
|
356
|
+
# any options given on #add.
|
|
357
|
+
#
|
|
358
|
+
# The object resolved in this way needs to respond to #call(canvas, box) where +canvas+ is the
|
|
359
|
+
# HexaPDF::Content::Canvas object on which it should be drawn and +box+ is a box-like object
|
|
360
|
+
# (e.g. Box or TextFragment). The coordinate system is translated so that the origin is at the
|
|
361
|
+
# lower right corner of the box during the drawing operations.
|
|
362
|
+
class Layers
|
|
363
|
+
|
|
364
|
+
# Creates a new Layers object popuplated with the given +layers+.
|
|
365
|
+
def initialize(layers = [])
|
|
366
|
+
@layers = layers
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
# :call-seq:
|
|
370
|
+
# layers.add {|canvas, box| block}
|
|
371
|
+
# layers.add(name, **options)
|
|
372
|
+
#
|
|
373
|
+
# Adds a new layer object.
|
|
374
|
+
#
|
|
375
|
+
# The layer object can either be specified as a block or by reference to a configured layer
|
|
376
|
+
# object in 'style.layers_map'. In this case +name+ is used as the reference and the options
|
|
377
|
+
# are passed to layer object if it needs initialization.
|
|
378
|
+
def add(name = nil, **options, &block)
|
|
379
|
+
if block_given?
|
|
380
|
+
@layers << block
|
|
381
|
+
elsif name
|
|
382
|
+
@layers << [name, options]
|
|
383
|
+
else
|
|
384
|
+
raise ArgumentError, "Layer object name or block missing"
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
# Draws all layer objects onto the canvas at the position [x, y] for the given box.
|
|
389
|
+
def draw(canvas, x, y, box)
|
|
390
|
+
return if none?
|
|
391
|
+
|
|
392
|
+
canvas.translate(x, y) do
|
|
393
|
+
each(canvas.context.document.config) do |layer|
|
|
394
|
+
canvas.save_graphics_state { layer.call(canvas, box) }
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
# Yields all layer objects. Objects that have been specified via a reference are first
|
|
400
|
+
# resolved using the provided configuration object.
|
|
401
|
+
def each(config) #:yield: layer
|
|
402
|
+
@layers.each do |obj, options|
|
|
403
|
+
obj = config.constantize('style.layers_map', obj) unless obj.respond_to?(:call)
|
|
404
|
+
obj = obj.new(**options) unless obj.respond_to?(:call)
|
|
405
|
+
yield(obj)
|
|
406
|
+
end
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
# Returns +true+ if there are no layers defined.
|
|
410
|
+
def none?
|
|
411
|
+
@layers.empty?
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
# The LinkLayer class provides support for linking to in-document or remote destinations for
|
|
417
|
+
# Style objects using link annotations. Typical use cases would be linking to a (named)
|
|
418
|
+
# destination on a different page or executing a URI action.
|
|
419
|
+
#
|
|
420
|
+
# See: PDF1.7 s12.5.6.6, Layers, HexaPDF::Type::Annotations::Link
|
|
421
|
+
class LinkLayer
|
|
422
|
+
|
|
423
|
+
# Creates a new LinkLayer object.
|
|
424
|
+
#
|
|
425
|
+
# The following arguments are allowed (note that only *one* of +dest+, +uri+ or +file+ may
|
|
426
|
+
# be specified):
|
|
427
|
+
#
|
|
428
|
+
# +dest+::
|
|
429
|
+
# The destination array or a name of a named destination for in-document links.
|
|
430
|
+
#
|
|
431
|
+
# +uri+::
|
|
432
|
+
# The URI to link to.
|
|
433
|
+
#
|
|
434
|
+
# +file+::
|
|
435
|
+
# The file that should be opened or, if it refers to an application, the application that
|
|
436
|
+
# should be launched. Can either be a string or a Filespec object. Also see:
|
|
437
|
+
# HexaPDF::Type::FileSpecification.
|
|
438
|
+
#
|
|
439
|
+
# +border+::
|
|
440
|
+
# If set to +true+, a standard border is used. Also accepts an array that adheres to the
|
|
441
|
+
# rules for annotation borders.
|
|
442
|
+
#
|
|
443
|
+
# +border_color+::
|
|
444
|
+
# Defines the border color. Can be an array with 0 (transparent), 1 (grayscale), 3 (RGB)
|
|
445
|
+
# or 4 (CMYK) values.
|
|
446
|
+
#
|
|
447
|
+
# Examples:
|
|
448
|
+
# LinkLayer.new(dest: [page, :XYZ, nil, nil, nil], border: true)
|
|
449
|
+
# LinkLayer.new(uri: "https://my.example.com/path", border: [5 5 2])
|
|
450
|
+
def initialize(dest: nil, uri: nil, file: nil, border: false, border_color: nil)
|
|
451
|
+
if dest && (uri || file) || uri && file
|
|
452
|
+
raise ArgumentError, "Only one of dest, uri and file is allowed"
|
|
453
|
+
end
|
|
454
|
+
@dest = dest
|
|
455
|
+
@action = if uri
|
|
456
|
+
{S: :URI, URI: uri}
|
|
457
|
+
elsif file
|
|
458
|
+
{S: :Launch, F: file, NewWindow: true}
|
|
459
|
+
end
|
|
460
|
+
@border = case border
|
|
461
|
+
when false then [0, 0, 0]
|
|
462
|
+
when true then nil
|
|
463
|
+
when Array then border
|
|
464
|
+
else raise ArgumentError, "Invalid value for border: #{border}"
|
|
465
|
+
end
|
|
466
|
+
@border_color = border_color
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
# Creates the needed link annotation if possible, i.e. if the context of the canvas is a page.
|
|
470
|
+
def call(canvas, box)
|
|
471
|
+
return unless canvas.context.type == :Page
|
|
472
|
+
page = canvas.context
|
|
473
|
+
matrix = canvas.graphics_state.ctm
|
|
474
|
+
quad_points = [*matrix.evaluate(0, 0), *matrix.evaluate(box.width, 0),
|
|
475
|
+
*matrix.evaluate(box.width, box.height), *matrix.evaluate(0, box.height)]
|
|
476
|
+
x_minmax = quad_points.values_at(0, 2, 4, 6).minmax
|
|
477
|
+
y_minmax = quad_points.values_at(1, 3, 5, 7).minmax
|
|
478
|
+
annot = {
|
|
479
|
+
Subtype: :Link,
|
|
480
|
+
Rect: [x_minmax[0], y_minmax[0], x_minmax[1], y_minmax[1]],
|
|
481
|
+
QuadPoints: quad_points,
|
|
482
|
+
Dest: @dest,
|
|
483
|
+
A: @action,
|
|
484
|
+
Border: @border,
|
|
485
|
+
C: @border_color && canvas.color_from_specification(@border_color).components,
|
|
486
|
+
}
|
|
487
|
+
(page[:Annots] ||= []) << page.document.add(annot)
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
end
|
|
491
|
+
|
|
116
492
|
UNSET = ::Object.new # :nodoc:
|
|
117
493
|
|
|
118
494
|
# Creates a new Style object.
|
|
@@ -195,6 +571,109 @@ module HexaPDF
|
|
|
195
571
|
#
|
|
196
572
|
# See: HexaPDF::Layout::TextShaper#shape_text for available features.
|
|
197
573
|
|
|
574
|
+
##
|
|
575
|
+
# :method: text_rendering_mode
|
|
576
|
+
# :call-seq:
|
|
577
|
+
# text_rendering_mode(mode = nil)
|
|
578
|
+
#
|
|
579
|
+
# The text rendering mode, i.e. whether text should be filled, stroked, clipped, invisible or
|
|
580
|
+
# a combination thereof, defaults to :fill.
|
|
581
|
+
#
|
|
582
|
+
# See: HexaPDF::Content::Canvas#text_rendering_mode
|
|
583
|
+
|
|
584
|
+
##
|
|
585
|
+
# :method: subscript
|
|
586
|
+
# :call-seq:
|
|
587
|
+
# subscript(enable = false)
|
|
588
|
+
#
|
|
589
|
+
# Render the text as subscript, i.e. lower and in a smaller font size; defaults to false.
|
|
590
|
+
#
|
|
591
|
+
# If superscript is set, it will be deactivated.
|
|
592
|
+
|
|
593
|
+
##
|
|
594
|
+
# :method: superscript
|
|
595
|
+
# :call-seq:
|
|
596
|
+
# superscript(enable = false)
|
|
597
|
+
#
|
|
598
|
+
# Render the text as superscript, i.e. higher and in a smaller font size; defaults to false.
|
|
599
|
+
#
|
|
600
|
+
# If subscript is set, it will be deactivated.
|
|
601
|
+
|
|
602
|
+
##
|
|
603
|
+
# :method: fill_color
|
|
604
|
+
# :call-seq:
|
|
605
|
+
# fill_color(color = nil)
|
|
606
|
+
#
|
|
607
|
+
# The color used for filling (e.g. text), defaults to black.
|
|
608
|
+
#
|
|
609
|
+
# See: HexaPDF::Content::Canvas#fill_color
|
|
610
|
+
|
|
611
|
+
##
|
|
612
|
+
# :method: fill_alpha
|
|
613
|
+
# :call-seq:
|
|
614
|
+
# fill_alpha(alpha = nil)
|
|
615
|
+
#
|
|
616
|
+
# The alpha value applied to filling operations (e.g. text), defaults to 1 (i.e. 100%
|
|
617
|
+
# opaque).
|
|
618
|
+
#
|
|
619
|
+
# See: HexaPDF::Content::Canvas#opacity
|
|
620
|
+
|
|
621
|
+
##
|
|
622
|
+
# :method: stroke_color
|
|
623
|
+
# :call-seq:
|
|
624
|
+
# stroke_color(color = nil)
|
|
625
|
+
#
|
|
626
|
+
# The color used for stroking (e.g. text outlines), defaults to black.
|
|
627
|
+
#
|
|
628
|
+
# See: HexaPDF::Content::Canvas#stroke_color
|
|
629
|
+
|
|
630
|
+
##
|
|
631
|
+
# :method: stroke_alpha
|
|
632
|
+
# :call-seq:
|
|
633
|
+
# stroke_alpha(alpha = nil)
|
|
634
|
+
#
|
|
635
|
+
# The alpha value applied to stroking operations (e.g. text outlines), defaults to 1 (i.e.
|
|
636
|
+
# 100% opaque).
|
|
637
|
+
#
|
|
638
|
+
# See: HexaPDF::Content::Canvas#opacity
|
|
639
|
+
|
|
640
|
+
##
|
|
641
|
+
# :method: stroke_width
|
|
642
|
+
# :call-seq:
|
|
643
|
+
# stroke_width(width = nil)
|
|
644
|
+
#
|
|
645
|
+
# The line width used for stroking operations (e.g. text outlines), defaults to 1.
|
|
646
|
+
#
|
|
647
|
+
# See: HexaPDF::Content::Canvas#line_width
|
|
648
|
+
|
|
649
|
+
##
|
|
650
|
+
# :method: stroke_cap_style
|
|
651
|
+
# :call-seq:
|
|
652
|
+
# stroke_cap_style(style = nil)
|
|
653
|
+
#
|
|
654
|
+
# The line cap style used for stroking operations (e.g. text outlines), defaults to :butt.
|
|
655
|
+
#
|
|
656
|
+
# See: HexaPDF::Content::Canvas#line_cap_style
|
|
657
|
+
|
|
658
|
+
##
|
|
659
|
+
# :method: stroke_join_style
|
|
660
|
+
# :call-seq:
|
|
661
|
+
# stroke_join_style(style = nil)
|
|
662
|
+
#
|
|
663
|
+
# The line join style used for stroking operations (e.g. text outlines), defaults to :miter.
|
|
664
|
+
#
|
|
665
|
+
# See: HexaPDF::Content::Canvas#line_join_style
|
|
666
|
+
|
|
667
|
+
##
|
|
668
|
+
# :method: stroke_miter_limit
|
|
669
|
+
# :call-seq:
|
|
670
|
+
# stroke_miter_limit(limit = nil)
|
|
671
|
+
#
|
|
672
|
+
# The miter limit used for stroking operations (e.g. text outlines) when #stroke_join_style is
|
|
673
|
+
# :miter, defaults to 10.0.
|
|
674
|
+
#
|
|
675
|
+
# See: HexaPDF::Content::Canvas#miter_limit
|
|
676
|
+
|
|
198
677
|
##
|
|
199
678
|
# :method: align
|
|
200
679
|
# :call-seq:
|
|
@@ -229,6 +708,50 @@ module HexaPDF
|
|
|
229
708
|
#
|
|
230
709
|
# The indentation to be used for the first line of a sequence of text lines, defaults to 0.
|
|
231
710
|
|
|
711
|
+
##
|
|
712
|
+
# :method: background_color
|
|
713
|
+
# :call-seq:
|
|
714
|
+
# background_color(color = nil)
|
|
715
|
+
#
|
|
716
|
+
# The color used for backgrounds, defaults to +nil+ (i.e. no background).
|
|
717
|
+
|
|
718
|
+
##
|
|
719
|
+
# :method: padding
|
|
720
|
+
# :call-seq:
|
|
721
|
+
# padding(value = nil)
|
|
722
|
+
#
|
|
723
|
+
# The padding between the border and the contents, defaults to 0 for all four sides.
|
|
724
|
+
|
|
725
|
+
##
|
|
726
|
+
# :method: margin
|
|
727
|
+
# :call-seq:
|
|
728
|
+
# margin(value = nil)
|
|
729
|
+
#
|
|
730
|
+
# The margin around a box, defaults to 0 for all four sides.
|
|
731
|
+
|
|
732
|
+
##
|
|
733
|
+
# :method: border
|
|
734
|
+
# :call-seq:
|
|
735
|
+
# border(value = nil)
|
|
736
|
+
#
|
|
737
|
+
# The border around the contents, defaults to no border.
|
|
738
|
+
|
|
739
|
+
##
|
|
740
|
+
# :method: overlays
|
|
741
|
+
# :call-seq:
|
|
742
|
+
# overlays(layers = nil)
|
|
743
|
+
#
|
|
744
|
+
# A Layers object containing all the layers that should be drawn over the box; defaults to no
|
|
745
|
+
# layers being drawn.
|
|
746
|
+
|
|
747
|
+
##
|
|
748
|
+
# :method: underlays
|
|
749
|
+
# :call-seq:
|
|
750
|
+
# underlays(layers = nil)
|
|
751
|
+
#
|
|
752
|
+
# A Layers object containing all the layers that should be drawn under the box; defaults to no
|
|
753
|
+
# layers being drawn.
|
|
754
|
+
|
|
232
755
|
[
|
|
233
756
|
[:font, "raise HexaPDF::Error, 'No font set'"],
|
|
234
757
|
[:font_size, 10],
|
|
@@ -237,73 +760,132 @@ module HexaPDF
|
|
|
237
760
|
[:horizontal_scaling, 100],
|
|
238
761
|
[:text_rise, 0],
|
|
239
762
|
[:font_features, {}],
|
|
763
|
+
[:text_rendering_mode, :fill],
|
|
764
|
+
[:subscript, false, "value; superscript(false) if superscript"],
|
|
765
|
+
[:superscript, false, "value; subscript(false) if subscript"],
|
|
766
|
+
[:underline, false],
|
|
767
|
+
[:strikeout, false],
|
|
768
|
+
[:fill_color, "default_color"],
|
|
769
|
+
[:fill_alpha, 1],
|
|
770
|
+
[:stroke_color, "default_color"],
|
|
771
|
+
[:stroke_alpha, 1],
|
|
772
|
+
[:stroke_width, 1],
|
|
773
|
+
[:stroke_cap_style, :butt],
|
|
774
|
+
[:stroke_join_style, :miter],
|
|
775
|
+
[:stroke_miter_limit, 10.0],
|
|
776
|
+
[:stroke_dash_pattern, "Content::LineDashPattern.new",
|
|
777
|
+
"Content::LineDashPattern.normalize(value, phase)", ", phase = 0"],
|
|
240
778
|
[:align, :left],
|
|
241
779
|
[:valign, :top],
|
|
242
780
|
[:text_indent, 0],
|
|
243
|
-
|
|
781
|
+
[:line_spacing, "LineSpacing.new(:single)",
|
|
782
|
+
"LineSpacing.new(value, value: extra_arg)", ", extra_arg = nil"],
|
|
783
|
+
[:background_color, nil],
|
|
784
|
+
[:padding, "Quad.new(0)", "Quad.new(value)"],
|
|
785
|
+
[:margin, "Quad.new(0)", "Quad.new(value)"],
|
|
786
|
+
[:border, "Border.new", "Border.new(value)"],
|
|
787
|
+
[:overlays, "Layers.new", "Layers.new(value)"],
|
|
788
|
+
[:underlays, "Layers.new", "Layers.new(value)"],
|
|
789
|
+
].each do |name, default, setter = "value", extra_args = ""|
|
|
244
790
|
default = default.inspect unless default.kind_of?(String)
|
|
245
791
|
module_eval(<<-EOF, __FILE__, __LINE__)
|
|
246
|
-
def #{name}(value = UNSET)
|
|
247
|
-
value == UNSET ? (@#{name} ||= #{default}) : (@#{name} =
|
|
792
|
+
def #{name}(value = UNSET#{extra_args})
|
|
793
|
+
value == UNSET ? (@#{name} ||= #{default}) : (@#{name} = #{setter}; self)
|
|
794
|
+
end
|
|
795
|
+
def #{name}?
|
|
796
|
+
defined?(@#{name})
|
|
248
797
|
end
|
|
249
798
|
EOF
|
|
250
799
|
alias_method("#{name}=", name)
|
|
251
800
|
end
|
|
252
801
|
|
|
253
|
-
# :call-seq:
|
|
254
|
-
# line_spacing(type = nil, value = nil)
|
|
255
|
-
#
|
|
256
|
-
# The spacing between consecutive lines, defaults to type = :single.
|
|
257
|
-
#
|
|
258
|
-
# See: LineSpacing
|
|
259
|
-
def line_spacing(type = UNSET, value = nil)
|
|
260
|
-
if type == UNSET
|
|
261
|
-
@line_spacing ||= LineSpacing.new(:single)
|
|
262
|
-
else
|
|
263
|
-
@line_spacing = LineSpacing.new(type, value: value)
|
|
264
|
-
self
|
|
265
|
-
end
|
|
266
|
-
end
|
|
267
|
-
alias_method(:line_spacing=, :line_spacing)
|
|
268
802
|
|
|
803
|
+
##
|
|
804
|
+
# :method: text_segmentation_algorithm
|
|
269
805
|
# :call-seq:
|
|
270
806
|
# text_segmentation_algorithm(algorithm = nil) {|items| block }
|
|
271
807
|
#
|
|
272
808
|
# The algorithm to use for text segmentation purposes, defaults to
|
|
273
|
-
#
|
|
809
|
+
# TextLayouter::SimpleTextSegmentation.
|
|
274
810
|
#
|
|
275
811
|
# When setting the algorithm, either an object that responds to #call(items) or a block can be
|
|
276
812
|
# used.
|
|
277
|
-
def text_segmentation_algorithm(algorithm = UNSET, &block)
|
|
278
|
-
if algorithm == UNSET && !block
|
|
279
|
-
@text_segmentation_algorithm ||= TextBox::SimpleTextSegmentation
|
|
280
|
-
else
|
|
281
|
-
@text_segmentation_algorithm = (algorithm != UNSET ? algorithm : block)
|
|
282
|
-
self
|
|
283
|
-
end
|
|
284
|
-
end
|
|
285
|
-
alias_method(:text_segmentation_algorithm=, :text_segmentation_algorithm)
|
|
286
813
|
|
|
814
|
+
##
|
|
815
|
+
# :method: text_line_wrapping_algorithm
|
|
287
816
|
# :call-seq:
|
|
288
|
-
# text_line_wrapping_algorithm(algorithm = nil) {|items| block }
|
|
817
|
+
# text_line_wrapping_algorithm(algorithm = nil) {|items, width_block| block }
|
|
289
818
|
#
|
|
290
|
-
# The line wrapping algorithm that should be used, defaults to
|
|
819
|
+
# The line wrapping algorithm that should be used, defaults to
|
|
820
|
+
# TextLayouter::SimpleLineWrapping.
|
|
291
821
|
#
|
|
292
822
|
# When setting the algorithm, either an object that responds to #call or a block can be used.
|
|
293
|
-
# See
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
823
|
+
# See TextLayouter::SimpleLineWrapping#call for the needed method signature.
|
|
824
|
+
|
|
825
|
+
[
|
|
826
|
+
[:text_segmentation_algorithm, 'TextLayouter::SimpleTextSegmentation'],
|
|
827
|
+
[:text_line_wrapping_algorithm, 'TextLayouter::SimpleLineWrapping'],
|
|
828
|
+
].each do |name, default|
|
|
829
|
+
default = default.inspect unless default.kind_of?(String)
|
|
830
|
+
module_eval(<<-EOF, __FILE__, __LINE__)
|
|
831
|
+
def #{name}(value = UNSET, &block)
|
|
832
|
+
if value == UNSET && !block
|
|
833
|
+
@#{name} ||= #{default}
|
|
834
|
+
else
|
|
835
|
+
@#{name} = (value != UNSET ? value : block)
|
|
836
|
+
self
|
|
837
|
+
end
|
|
838
|
+
end
|
|
839
|
+
def #{name}?
|
|
840
|
+
defined?(@#{name})
|
|
841
|
+
end
|
|
842
|
+
EOF
|
|
843
|
+
alias_method("#{name}=", name)
|
|
844
|
+
end
|
|
845
|
+
|
|
846
|
+
# The calculated text rise, taking superscript and subscript into account.
|
|
847
|
+
def calculated_text_rise
|
|
848
|
+
if superscript
|
|
849
|
+
text_rise + font_size * 0.33
|
|
850
|
+
elsif subscript
|
|
851
|
+
text_rise - font_size * 0.20
|
|
297
852
|
else
|
|
298
|
-
|
|
299
|
-
self
|
|
853
|
+
text_rise
|
|
300
854
|
end
|
|
301
855
|
end
|
|
302
|
-
|
|
856
|
+
|
|
857
|
+
# The calculated font size, taking superscript and subscript into account.
|
|
858
|
+
def calculated_font_size
|
|
859
|
+
(superscript || subscript ? 0.583 : 1) * font_size
|
|
860
|
+
end
|
|
861
|
+
|
|
862
|
+
# Returns the correct offset from the baseline for the underline.
|
|
863
|
+
def calculated_underline_position
|
|
864
|
+
calculated_text_rise +
|
|
865
|
+
calculated_font_size / 1000.0 * font.wrapped_font.underline_position *
|
|
866
|
+
font.scaling_factor - calculated_underline_thickness / 2.0
|
|
867
|
+
end
|
|
868
|
+
|
|
869
|
+
# Returns the correct thickness for the underline.
|
|
870
|
+
def calculated_underline_thickness
|
|
871
|
+
calculated_font_size / 1000.0 * font.wrapped_font.underline_thickness * font.scaling_factor
|
|
872
|
+
end
|
|
873
|
+
|
|
874
|
+
# Returns the correct offset from the baseline for the strikeout line.
|
|
875
|
+
def calculated_strikeout_position
|
|
876
|
+
calculated_text_rise +
|
|
877
|
+
calculated_font_size / 1000.0 * font.wrapped_font.strikeout_position *
|
|
878
|
+
font.scaling_factor - calculated_strikeout_thickness / 2.0
|
|
879
|
+
end
|
|
880
|
+
|
|
881
|
+
# Returns the correct thickness for the strikeout line.
|
|
882
|
+
def calculated_strikeout_thickness
|
|
883
|
+
calculated_font_size / 1000.0 * font.wrapped_font.strikeout_thickness * font.scaling_factor
|
|
884
|
+
end
|
|
303
885
|
|
|
304
886
|
# The font size scaled appropriately.
|
|
305
887
|
def scaled_font_size
|
|
306
|
-
@scaled_font_size ||=
|
|
888
|
+
@scaled_font_size ||= calculated_font_size / 1000.0 * scaled_horizontal_scaling
|
|
307
889
|
end
|
|
308
890
|
|
|
309
891
|
# The character spacing scaled appropriately.
|
|
@@ -359,6 +941,13 @@ module HexaPDF
|
|
|
359
941
|
@scaled_item_widths.clear
|
|
360
942
|
end
|
|
361
943
|
|
|
944
|
+
private
|
|
945
|
+
|
|
946
|
+
# Returns the default color for an empty PDF page, i.e. black.
|
|
947
|
+
def default_color
|
|
948
|
+
GlobalConfiguration.constantize('color_space.map'.freeze, :DeviceGray).new.default_color
|
|
949
|
+
end
|
|
950
|
+
|
|
362
951
|
end
|
|
363
952
|
|
|
364
953
|
end
|