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
|
@@ -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
|