hexapdf 0.5.0 → 0.6.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 +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
@@ -132,6 +132,32 @@ module HexaPDF
|
|
132
132
|
# EncryptionDictionary class.
|
133
133
|
class SecurityHandler
|
134
134
|
|
135
|
+
# Provides additional encryption specific information for HexaPDF::StreamData objects.
|
136
|
+
class EncryptedStreamData < StreamData
|
137
|
+
|
138
|
+
# The encryption key.
|
139
|
+
attr_reader :key
|
140
|
+
|
141
|
+
# The encryption algorithm.
|
142
|
+
attr_reader :algorithm
|
143
|
+
|
144
|
+
# Creates a new encrypted stream data object by utilizing the given stream data object as
|
145
|
+
# template. The arguments +key+ and +algorithm+ are used for decrypting purposes.
|
146
|
+
def initialize(obj, key, algorithm)
|
147
|
+
obj.instance_variables.each {|v| instance_variable_set(v, obj.instance_variable_get(v))}
|
148
|
+
@key = key
|
149
|
+
@algorithm = algorithm
|
150
|
+
end
|
151
|
+
|
152
|
+
alias :undecrypted_fiber :fiber
|
153
|
+
|
154
|
+
# Returns a fiber like HexaPDF::StreamData#fiber, but one wrapped in a decrypting fiber.
|
155
|
+
def fiber(*args)
|
156
|
+
@algorithm.decryption_fiber(@key, super(*args))
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
|
135
161
|
# :call-seq:
|
136
162
|
# SecurityHandler.set_up_encryption(document, handler_name, **options) -> handler
|
137
163
|
#
|
@@ -143,8 +169,8 @@ module HexaPDF
|
|
143
169
|
#
|
144
170
|
# See: #set_up_encryption (for the common encryption options).
|
145
171
|
def self.set_up_encryption(document, handler_name, **options)
|
146
|
-
handler =
|
147
|
-
|
172
|
+
handler = document.config.constantize('encryption.filter_map', handler_name) do
|
173
|
+
document.config.constantize('encryption.sub_filter_map', handler_name) do
|
148
174
|
raise HexaPDF::EncryptionError, "Could not find the specified security handler"
|
149
175
|
end
|
150
176
|
end
|
@@ -170,8 +196,8 @@ module HexaPDF
|
|
170
196
|
if dict.nil?
|
171
197
|
raise HexaPDF::EncryptionError, "No /Encrypt dictionary found"
|
172
198
|
end
|
173
|
-
handler =
|
174
|
-
|
199
|
+
handler = document.config.constantize('encryption.filter_map', dict[:Filter]) do
|
200
|
+
document.config.constantize('encryption.sub_filter_map', dict[:SubFilter]) do
|
175
201
|
raise HexaPDF::EncryptionError, "Could not find a suitable security handler"
|
176
202
|
end
|
177
203
|
end
|
@@ -237,8 +263,7 @@ module HexaPDF
|
|
237
263
|
unless string_algorithm == stream_algorithm
|
238
264
|
key = object_key(obj.oid, obj.gen, stream_algorithm)
|
239
265
|
end
|
240
|
-
obj.
|
241
|
-
obj.raw_stream.decode_parms.unshift(key: key, algorithm: stream_algorithm)
|
266
|
+
obj.data.stream = EncryptedStreamData.new(obj.raw_stream, key, stream_algorithm)
|
242
267
|
end
|
243
268
|
|
244
269
|
obj
|
@@ -259,7 +284,14 @@ module HexaPDF
|
|
259
284
|
return obj.stream_encoder if obj.type == :XRef
|
260
285
|
|
261
286
|
key = object_key(obj.oid, obj.gen, stream_algorithm)
|
262
|
-
obj.
|
287
|
+
source = obj.stream_source
|
288
|
+
result = obj.stream_encoder(source)
|
289
|
+
if result == source && obj.raw_stream.kind_of?(EncryptedStreamData) &&
|
290
|
+
obj.raw_stream.key == key && obj.raw_stream.algorithm == stream_algorithm
|
291
|
+
obj.raw_stream.undecrypted_fiber
|
292
|
+
else
|
293
|
+
stream_algorithm.encryption_fiber(key, result)
|
294
|
+
end
|
263
295
|
end
|
264
296
|
|
265
297
|
# Computes the encryption key and sets up the algorithms for encrypting the document based on
|
@@ -420,12 +452,12 @@ module HexaPDF
|
|
420
452
|
|
421
453
|
# Returns the class that is used for ARC4 encryption.
|
422
454
|
def arc4_algorithm
|
423
|
-
@arc4_algorithm ||=
|
455
|
+
@arc4_algorithm ||= document.config.constantize('encryption.arc4')
|
424
456
|
end
|
425
457
|
|
426
458
|
# Returns the class that is used for AES encryption.
|
427
459
|
def aes_algorithm
|
428
|
-
@aes_algorithm ||=
|
460
|
+
@aes_algorithm ||= document.config.constantize('encryption.aes')
|
429
461
|
end
|
430
462
|
|
431
463
|
# Returns the class that is used for the identity algorithm which passes back the data as is
|
@@ -33,6 +33,7 @@
|
|
33
33
|
|
34
34
|
require 'fiber'
|
35
35
|
require 'hexapdf/error'
|
36
|
+
require 'hexapdf/configuration'
|
36
37
|
require 'hexapdf/utils/bit_stream'
|
37
38
|
|
38
39
|
module HexaPDF
|
@@ -254,8 +255,13 @@ module HexaPDF
|
|
254
255
|
Fiber.yield(result) unless result.empty?
|
255
256
|
end
|
256
257
|
|
257
|
-
|
258
|
+
if pos != data.length && GlobalConfiguration['filter.predictor.strict']
|
258
259
|
raise FilterError, "Data is missing for PNG predictor"
|
260
|
+
elsif pos != data.length && data.length != 1
|
261
|
+
result = ''.b
|
262
|
+
bytes_per_row = data.length - pos
|
263
|
+
row_action.call(result)
|
264
|
+
result
|
259
265
|
end
|
260
266
|
end
|
261
267
|
end
|
@@ -162,6 +162,26 @@ module HexaPDF
|
|
162
162
|
weight / 5
|
163
163
|
end
|
164
164
|
|
165
|
+
# Returns the distance from the baseline to the top of the underline.
|
166
|
+
def underline_position
|
167
|
+
self[:post].underline_position
|
168
|
+
end
|
169
|
+
|
170
|
+
# Returns the stroke width for the underline.
|
171
|
+
def underline_thickness
|
172
|
+
self[:post].underline_thickness
|
173
|
+
end
|
174
|
+
|
175
|
+
# Returns the distance from the baseline to the top of the strikeout line.
|
176
|
+
def strikeout_position
|
177
|
+
self[:"OS/2"].strikeout_position
|
178
|
+
end
|
179
|
+
|
180
|
+
# Returns the stroke width for the strikeout line.
|
181
|
+
def strikeout_thickness
|
182
|
+
self[:"OS/2"].strikeout_size
|
183
|
+
end
|
184
|
+
|
165
185
|
# Returns th glyph ID of the missing glyph, i.e. 0.
|
166
186
|
def missing_glyph_id
|
167
187
|
0
|
@@ -76,6 +76,7 @@ module HexaPDF
|
|
76
76
|
def_delegators :@metrics, :weight, :weight_class, :bounding_box, :italic_angle
|
77
77
|
def_delegators :@metrics, :ascender, :descender, :cap_height, :x_height
|
78
78
|
def_delegators :@metrics, :dominant_horizontal_stem_width, :dominant_vertical_stem_width
|
79
|
+
def_delegators :@metrics, :underline_thickness
|
79
80
|
|
80
81
|
# Creates a new Type1 font object with the given font metrics.
|
81
82
|
def initialize(metrics)
|
@@ -128,6 +129,28 @@ module HexaPDF
|
|
128
129
|
end
|
129
130
|
end
|
130
131
|
|
132
|
+
# Returns the distance from the baseline to the top of the underline.
|
133
|
+
def underline_position
|
134
|
+
@metrics.underline_position + @metrics.underline_thickness / 2.0
|
135
|
+
end
|
136
|
+
|
137
|
+
# Returns the distance from the baseline to the top of the strikeout line.
|
138
|
+
def strikeout_position
|
139
|
+
# We use the suggested value from the OpenType spec.
|
140
|
+
225
|
141
|
+
end
|
142
|
+
|
143
|
+
# Returns the thickness of the strikeout line.
|
144
|
+
def strikeout_thickness
|
145
|
+
# The OpenType spec suggests the width of an em-dash or 50, so we use that as
|
146
|
+
# approximation since the AFM files don't contain this information.
|
147
|
+
if (bbox = @metrics.character_metrics[:emdash]&.bbox)
|
148
|
+
bbox[3] - bbox[1]
|
149
|
+
else
|
150
|
+
50
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
131
154
|
end
|
132
155
|
|
133
156
|
end
|
data/lib/hexapdf/font_loader.rb
CHANGED
@@ -32,6 +32,7 @@
|
|
32
32
|
#++
|
33
33
|
|
34
34
|
require 'hexapdf/font/true_type_wrapper'
|
35
|
+
require 'hexapdf/font_loader/from_file'
|
35
36
|
|
36
37
|
module HexaPDF
|
37
38
|
module FontLoader
|
@@ -62,9 +63,7 @@ module HexaPDF
|
|
62
63
|
unless File.file?(file)
|
63
64
|
raise HexaPDF::Error, "The configured font file #{file} does not exist"
|
64
65
|
end
|
65
|
-
|
66
|
-
font = HexaPDF::Font::TrueType::Font.new(File.open(file))
|
67
|
-
HexaPDF::Font::TrueTypeWrapper.new(document, font, subset: subset)
|
66
|
+
FromFile.call(document, file, subset: subset)
|
68
67
|
end
|
69
68
|
|
70
69
|
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
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-2017 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
|
+
|
34
|
+
require 'hexapdf/font/true_type_wrapper'
|
35
|
+
|
36
|
+
module HexaPDF
|
37
|
+
module FontLoader
|
38
|
+
|
39
|
+
# This module interprets the font name as file name and tries to load it.
|
40
|
+
module FromFile
|
41
|
+
|
42
|
+
# Loads the given font by interpreting the font name as file name.
|
43
|
+
#
|
44
|
+
# The file object representing the font file is *not* closed and if needed must be closed by
|
45
|
+
# the caller once the font is not needed anymore.
|
46
|
+
#
|
47
|
+
# +document+::
|
48
|
+
# The PDF document to associate the font object with.
|
49
|
+
#
|
50
|
+
# +name+::
|
51
|
+
# The file name.
|
52
|
+
#
|
53
|
+
# +subset+::
|
54
|
+
# Specifies whether the font should be subset if possible.
|
55
|
+
def self.call(document, name, subset: true, **)
|
56
|
+
return nil unless File.file?(name)
|
57
|
+
|
58
|
+
font = HexaPDF::Font::TrueType::Font.new(File.open(name, 'rb'))
|
59
|
+
HexaPDF::Font::TrueTypeWrapper.new(document, font, subset: subset)
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
@@ -347,7 +347,7 @@ module HexaPDF
|
|
347
347
|
image_data = ''.b
|
348
348
|
mask_data = ''.b
|
349
349
|
|
350
|
-
flate_decode =
|
350
|
+
flate_decode = @document.config.constantize('filter.map', :FlateDecode)
|
351
351
|
source = flate_decode.decoder(Fiber.new(&image_data_proc(offset)))
|
352
352
|
|
353
353
|
data = ''.b
|
@@ -381,7 +381,7 @@ module HexaPDF
|
|
381
381
|
bpc = decode_parms[:BitsPerComponent]
|
382
382
|
bytes_per_row = (width * bpc + 7) / 8 + 1
|
383
383
|
|
384
|
-
flate_decode =
|
384
|
+
flate_decode = @document.config.constantize('filter.map', :FlateDecode)
|
385
385
|
source = flate_decode.decoder(Fiber.new(&image_data_proc(offset)))
|
386
386
|
|
387
387
|
mask_data = ''.b
|
data/lib/hexapdf/layout.rb
CHANGED
@@ -42,9 +42,10 @@ module HexaPDF
|
|
42
42
|
autoload(:Style, 'hexapdf/layout/style')
|
43
43
|
autoload(:TextFragment, 'hexapdf/layout/text_fragment')
|
44
44
|
autoload(:InlineBox, 'hexapdf/layout/inline_box')
|
45
|
-
autoload(:
|
45
|
+
autoload(:Line, 'hexapdf/layout/line')
|
46
46
|
autoload(:TextShaper, 'hexapdf/layout/text_shaper')
|
47
|
-
autoload(:
|
47
|
+
autoload(:TextLayouter, 'hexapdf/layout/text_layouter')
|
48
|
+
autoload(:Box, 'hexapdf/layout/box')
|
48
49
|
|
49
50
|
end
|
50
51
|
|
@@ -0,0 +1,146 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
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-2017 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/style'
|
34
|
+
|
35
|
+
module HexaPDF
|
36
|
+
module Layout
|
37
|
+
|
38
|
+
# The base class for all layout boxes.
|
39
|
+
#
|
40
|
+
# HexaPDF uses the following box model:
|
41
|
+
#
|
42
|
+
# * Each box can specify a content width and content height. Padding, border and margin are
|
43
|
+
# *outside* of this content rectangle.
|
44
|
+
#
|
45
|
+
# * The #width and #height accessors can be used to get the width and height of the box
|
46
|
+
# including padding and the border.
|
47
|
+
#
|
48
|
+
class Box
|
49
|
+
|
50
|
+
# The width of the content box, i.e. without padding or borders.
|
51
|
+
#
|
52
|
+
# The value 0 means that the width is dynamically determined.
|
53
|
+
attr_reader :content_width
|
54
|
+
|
55
|
+
# The height of the content box, i.e. without padding or borders.
|
56
|
+
#
|
57
|
+
# The value 0 means that the height is dynamically determined.
|
58
|
+
attr_reader :content_height
|
59
|
+
|
60
|
+
# The style to be applied.
|
61
|
+
#
|
62
|
+
# Only the following properties are used:
|
63
|
+
#
|
64
|
+
# * Style#background_color
|
65
|
+
# * Style#padding
|
66
|
+
# * Style#border
|
67
|
+
# * Style#overlay_callback
|
68
|
+
# * Style#underlay_callback
|
69
|
+
attr_reader :style
|
70
|
+
|
71
|
+
# :call-seq:
|
72
|
+
# Box.new(content_width: 0, content_height: 0, style: Style.new) {|canvas, box| block} -> box
|
73
|
+
# Box.new(width: 0, height: 0, style: Style.new) {|canvas, box| block} -> box
|
74
|
+
#
|
75
|
+
# Creates a new Box object with the given width and height for its content that uses the
|
76
|
+
# provided block when it is asked to draw itself on a canvas (see #draw).
|
77
|
+
#
|
78
|
+
# Alternative to specifying the content width/height, it is also possible to specify the box
|
79
|
+
# width/height. The content width is then immediately calculated using the border and padding
|
80
|
+
# information from the style and stored.
|
81
|
+
#
|
82
|
+
# Since the final location of the box is not known beforehand, the drawing operations inside
|
83
|
+
# the block should draw inside the rectangle (0, 0, content_width, content_height) - note that
|
84
|
+
# the width and height of the box may not be known beforehand.
|
85
|
+
def initialize(content_width: 0, content_height: 0, width: 0, height: 0,
|
86
|
+
style: Style.new, &block)
|
87
|
+
@style = (style.kind_of?(Style) ? style : Style.new(style))
|
88
|
+
@draw_block = block
|
89
|
+
@content_width = content_width
|
90
|
+
@content_width = [width - self.width, 0].max if width != 0 && @content_width == 0
|
91
|
+
@content_height = content_height
|
92
|
+
@content_height = [height - self.height, 0].max if height != 0 && @content_height == 0
|
93
|
+
end
|
94
|
+
|
95
|
+
# Returns the width of the box, including padding and border widths.
|
96
|
+
def width
|
97
|
+
@content_width + @style.padding.left + @style.padding.right +
|
98
|
+
@style.border.width.left + @style.border.width.right
|
99
|
+
end
|
100
|
+
|
101
|
+
# Returns the height of the box, including padding and border widths.
|
102
|
+
def height
|
103
|
+
@content_height + @style.padding.top + @style.padding.bottom +
|
104
|
+
@style.border.width.top + @style.border.width.bottom
|
105
|
+
end
|
106
|
+
|
107
|
+
# :call-seq:
|
108
|
+
# box.draw(canvas, x, y)
|
109
|
+
#
|
110
|
+
# Draws the contents of the box onto the canvas at the position (x, y).
|
111
|
+
#
|
112
|
+
# The coordinate system is translated so that the origin is at the lower left corner of the
|
113
|
+
# contents box during the drawing operations.
|
114
|
+
def draw(canvas, x, y)
|
115
|
+
if style.background_color
|
116
|
+
canvas.save_graphics_state do
|
117
|
+
canvas.fill_color(style.background_color).rectangle(x, y, width, height).fill
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
style.underlays.draw(canvas, x, y, self)
|
122
|
+
|
123
|
+
unless style.border.none?
|
124
|
+
style.border.draw(canvas, x, y, width, height)
|
125
|
+
end
|
126
|
+
|
127
|
+
if @draw_block
|
128
|
+
canvas.translate(x + style.padding.left + style.border.width.left,
|
129
|
+
y + style.padding.bottom + style.border.width.bottom) do
|
130
|
+
@draw_block.call(canvas, self)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
style.overlays.draw(canvas, x, y, self)
|
135
|
+
end
|
136
|
+
|
137
|
+
# Returns +true+ if no drawing operations are performed.
|
138
|
+
def empty?
|
139
|
+
!(@draw_block || style.background_color || !style.underlays.none? ||
|
140
|
+
!style.border.none? || !style.overlays.none?)
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
end
|