hexapdf 0.34.1 → 0.35.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 +59 -0
- data/examples/009-text_layouter_alignment.rb +7 -7
- data/examples/010-text_layouter_inline_boxes.rb +1 -1
- data/examples/011-text_layouter_line_wrapping.rb +2 -4
- data/examples/013-text_layouter_shapes.rb +9 -11
- data/examples/014-text_in_polygon.rb +2 -2
- data/examples/016-frame_automatic_box_placement.rb +6 -7
- data/examples/017-frame_text_flow.rb +2 -2
- data/examples/018-composer.rb +5 -6
- data/examples/020-column_box.rb +2 -2
- data/examples/021-list_box.rb +1 -1
- data/examples/027-composer_optional_content.rb +5 -5
- data/examples/028-frame_mask_mode.rb +23 -0
- data/examples/029-composer_fallback_fonts.rb +22 -0
- data/lib/hexapdf/cli/info.rb +1 -0
- data/lib/hexapdf/cli/inspect.rb +55 -2
- data/lib/hexapdf/composer.rb +2 -2
- data/lib/hexapdf/configuration.rb +61 -1
- data/lib/hexapdf/content/canvas.rb +63 -0
- data/lib/hexapdf/content/canvas_composer.rb +142 -0
- data/lib/hexapdf/content.rb +1 -0
- data/lib/hexapdf/dictionary.rb +14 -3
- data/lib/hexapdf/document/layout.rb +35 -13
- data/lib/hexapdf/encryption/standard_security_handler.rb +15 -0
- data/lib/hexapdf/error.rb +2 -1
- data/lib/hexapdf/font/invalid_glyph.rb +22 -6
- data/lib/hexapdf/font/true_type_wrapper.rb +48 -20
- data/lib/hexapdf/font/type1_wrapper.rb +48 -24
- data/lib/hexapdf/layout/box.rb +11 -8
- data/lib/hexapdf/layout/column_box.rb +5 -3
- data/lib/hexapdf/layout/frame.rb +77 -39
- data/lib/hexapdf/layout/image_box.rb +3 -3
- data/lib/hexapdf/layout/list_box.rb +20 -19
- data/lib/hexapdf/layout/style.rb +173 -68
- data/lib/hexapdf/layout/table_box.rb +3 -3
- data/lib/hexapdf/layout/text_box.rb +5 -5
- data/lib/hexapdf/layout/text_fragment.rb +50 -0
- data/lib/hexapdf/layout/text_layouter.rb +7 -6
- data/lib/hexapdf/object.rb +5 -2
- data/lib/hexapdf/pdf_array.rb +5 -0
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +16 -11
- data/lib/hexapdf/utils/sorted_tree_node.rb +0 -10
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/content/test_canvas.rb +37 -0
- data/test/hexapdf/content/test_canvas_composer.rb +112 -0
- data/test/hexapdf/document/test_layout.rb +40 -12
- data/test/hexapdf/encryption/test_standard_security_handler.rb +43 -0
- data/test/hexapdf/font/test_invalid_glyph.rb +13 -1
- data/test/hexapdf/font/test_true_type_wrapper.rb +15 -2
- data/test/hexapdf/font/test_type1_wrapper.rb +21 -2
- data/test/hexapdf/layout/test_column_box.rb +14 -0
- data/test/hexapdf/layout/test_frame.rb +181 -95
- data/test/hexapdf/layout/test_list_box.rb +7 -7
- data/test/hexapdf/layout/test_style.rb +14 -10
- data/test/hexapdf/layout/test_table_box.rb +3 -3
- data/test/hexapdf/layout/test_text_box.rb +2 -2
- data/test/hexapdf/layout/test_text_fragment.rb +37 -0
- data/test/hexapdf/layout/test_text_layouter.rb +10 -10
- data/test/hexapdf/test_configuration.rb +49 -0
- data/test/hexapdf/test_dictionary.rb +1 -1
- data/test/hexapdf/test_object.rb +13 -12
- data/test/hexapdf/test_pdf_array.rb +9 -0
- data/test/hexapdf/test_writer.rb +3 -3
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +41 -13
- data/test/hexapdf/utils/test_sorted_tree_node.rb +1 -1
- metadata +7 -3
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
|
|
37
37
|
require 'hexapdf/content/graphics_state'
|
|
38
38
|
require 'hexapdf/content/operator'
|
|
39
|
+
require 'hexapdf/content/canvas_composer'
|
|
39
40
|
require 'hexapdf/serializer'
|
|
40
41
|
require 'hexapdf/utils/math_helpers'
|
|
41
42
|
require 'hexapdf/utils/graphics_helpers'
|
|
@@ -1450,6 +1451,43 @@ module HexaPDF
|
|
|
1450
1451
|
self
|
|
1451
1452
|
end
|
|
1452
1453
|
|
|
1454
|
+
# :call-seq:
|
|
1455
|
+
# canvas.form {|form_canvas| block } => form
|
|
1456
|
+
# canvas.form(width, height) {|form_canvas| block } => form
|
|
1457
|
+
#
|
|
1458
|
+
# Creates a reusable Form XObject, yields its canvas and then returns it.
|
|
1459
|
+
#
|
|
1460
|
+
# If no arguments are provided, the bounding box of the form is the same as that of the
|
|
1461
|
+
# context object of this canvas. Otherwise you need to provide the +width+ and +height+ for
|
|
1462
|
+
# the form.
|
|
1463
|
+
#
|
|
1464
|
+
# Once the form has been created, it can be used like an image and drawn mulitple times with
|
|
1465
|
+
# the #xobject method. Note that the created form object is independent of this canvas and its
|
|
1466
|
+
# context object. This means it can also be used with other canvases.
|
|
1467
|
+
#
|
|
1468
|
+
# Examples:
|
|
1469
|
+
#
|
|
1470
|
+
# #>pdf
|
|
1471
|
+
# form = canvas.form do |form_canvas|
|
|
1472
|
+
# form_canvas.fill_color("hp-blue").line_width(5).
|
|
1473
|
+
# rectangle(10, 10, 80, 80).fill_stroke
|
|
1474
|
+
# end
|
|
1475
|
+
# canvas.xobject(form, at: [0, 0])
|
|
1476
|
+
# canvas.xobject(form, width: 50, at: [100, 100])
|
|
1477
|
+
#
|
|
1478
|
+
# See: HexaPDF::Type::Form
|
|
1479
|
+
def form(width = nil, height = nil) # :yield: canvas
|
|
1480
|
+
obj = if width && height
|
|
1481
|
+
context.document.add({Type: :XObject, Subtype: :Form, BBox: [0, 0, width, height]})
|
|
1482
|
+
elsif width || height
|
|
1483
|
+
raise ArgumentError, "Both arguments width and height need to be provided"
|
|
1484
|
+
else
|
|
1485
|
+
context.document.add({Type: :XObject, Subtype: :Form, BBox: context.box.value.dup})
|
|
1486
|
+
end
|
|
1487
|
+
yield(obj.canvas) if block_given?
|
|
1488
|
+
obj
|
|
1489
|
+
end
|
|
1490
|
+
|
|
1453
1491
|
# :call-seq:
|
|
1454
1492
|
# canvas.graphic_object(obj, **options) => obj
|
|
1455
1493
|
# canvas.graphic_object(name, **options) => graphic_object
|
|
@@ -2535,6 +2573,31 @@ module HexaPDF
|
|
|
2535
2573
|
# See: PDF2.0 s8.11
|
|
2536
2574
|
alias :end_optional_content :end_marked_content_sequence
|
|
2537
2575
|
|
|
2576
|
+
# :call-seq:
|
|
2577
|
+
# canvas.composer(margin: 0) {|composer| block } -> composer
|
|
2578
|
+
#
|
|
2579
|
+
# Creates a CanvasComposer object for composing content using high-level document layout
|
|
2580
|
+
# features, yields it, if a block is given, and returns it.
|
|
2581
|
+
#
|
|
2582
|
+
# The +margin+ can be any value allowed by HexaPDF::Layout::Style::Quad#set and defines the
|
|
2583
|
+
# margin that should not be used during composition. For the remaining area of the canvas a
|
|
2584
|
+
# frame object will be created.
|
|
2585
|
+
#
|
|
2586
|
+
# Examples:
|
|
2587
|
+
#
|
|
2588
|
+
# #>pdf
|
|
2589
|
+
# canvas.composer(margin: [10, 30]) do |composer|
|
|
2590
|
+
# composer.image(machu_picchu, height: 30, position: :float)
|
|
2591
|
+
# composer.lorem_ipsum(position: :flow)
|
|
2592
|
+
# end
|
|
2593
|
+
#
|
|
2594
|
+
# See: CanvasComposer, HexaPDF::Document::Layout
|
|
2595
|
+
def composer(margin: 0)
|
|
2596
|
+
composer = CanvasComposer.new(self, margin: margin)
|
|
2597
|
+
yield(composer) if block_given?
|
|
2598
|
+
composer
|
|
2599
|
+
end
|
|
2600
|
+
|
|
2538
2601
|
# Creates and returns a color object from the given color specification. See #stroke_color for
|
|
2539
2602
|
# details on the possible color specifications.
|
|
2540
2603
|
#
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# -*- encoding: utf-8; frozen_string_literal: true -*-
|
|
2
|
+
#
|
|
3
|
+
#--
|
|
4
|
+
# This file is part of HexaPDF.
|
|
5
|
+
#
|
|
6
|
+
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
|
7
|
+
# Copyright (C) 2014-2023 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
|
+
# If the GNU Affero General Public License doesn't fit your need,
|
|
34
|
+
# commercial licenses are available at <https://gettalong.at/hexapdf/>.
|
|
35
|
+
#++
|
|
36
|
+
|
|
37
|
+
require 'hexapdf/layout'
|
|
38
|
+
|
|
39
|
+
module HexaPDF
|
|
40
|
+
module Content
|
|
41
|
+
|
|
42
|
+
# The CanvasComposer class allows using the document layout functionality for a single canvas.
|
|
43
|
+
# It works in a similar manner as the HexaPDF::Composer class.
|
|
44
|
+
#
|
|
45
|
+
# See: HexaPDF::Composer, HexaPDF::Document::Layout
|
|
46
|
+
class CanvasComposer
|
|
47
|
+
|
|
48
|
+
# The associated canvas.
|
|
49
|
+
attr_reader :canvas
|
|
50
|
+
|
|
51
|
+
# The associated HexaPDF::Document instance.
|
|
52
|
+
attr_reader :document
|
|
53
|
+
|
|
54
|
+
# The HexaPDF::Layout::Frame instance into which the boxes are laid out.
|
|
55
|
+
attr_reader :frame
|
|
56
|
+
|
|
57
|
+
# Creates a new CanvasComposer instance for the given +canvas+.
|
|
58
|
+
#
|
|
59
|
+
# The +margin+ can be any value allowed by HexaPDF::Layout::Style::Quad#set and defines the
|
|
60
|
+
# margin that should not be used during composition. For the remaining area of the canvas a
|
|
61
|
+
# frame object will be created.
|
|
62
|
+
def initialize(canvas, margin: 0)
|
|
63
|
+
@canvas = canvas
|
|
64
|
+
@document = @canvas.context.document
|
|
65
|
+
|
|
66
|
+
box = @canvas.context.box
|
|
67
|
+
margin = Layout::Style::Quad.new(margin)
|
|
68
|
+
@frame = Layout::Frame.new(box.left + margin.left,
|
|
69
|
+
box.bottom + margin.bottom,
|
|
70
|
+
box.width - margin.left - margin.right,
|
|
71
|
+
box.height - margin.bottom - margin.top,
|
|
72
|
+
context: @canvas.context)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Invokes HexaPDF::Document::Layout#style with the given arguments to create/update and return
|
|
76
|
+
# a style object.
|
|
77
|
+
def style(name, base: :base, **properties)
|
|
78
|
+
@document.layout.style(name, base: base, **properties)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Draws the given HexaPDF::Layout::Box and returns the last drawn box.
|
|
82
|
+
#
|
|
83
|
+
# The box is drawn into the frame. If it doesn't fit, the box is split. If it still doesn't
|
|
84
|
+
# fit, a new region of the frame is determined and then the process starts again.
|
|
85
|
+
#
|
|
86
|
+
# If none or only some parts of the box fit into the frame, an exception is thrown.
|
|
87
|
+
def draw_box(box)
|
|
88
|
+
while true
|
|
89
|
+
result = @frame.fit(box)
|
|
90
|
+
if result.success?
|
|
91
|
+
@frame.draw(@canvas, result)
|
|
92
|
+
break
|
|
93
|
+
elsif @frame.full?
|
|
94
|
+
raise HexaPDF::Error, "Frame for canvas composer is full and box doesn't fit anymore"
|
|
95
|
+
else
|
|
96
|
+
draw_box, box = @frame.split(result)
|
|
97
|
+
if draw_box
|
|
98
|
+
@frame.draw(@canvas, result)
|
|
99
|
+
elsif !@frame.find_next_region
|
|
100
|
+
raise HexaPDF::Error, "Frame for canvas composer is full and box doesn't fit anymore"
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
box
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Draws any box that can be created using HexaPDF::Document::Layout.
|
|
108
|
+
#
|
|
109
|
+
# This includes all named boxes defined in the 'layout.boxes.map' configuration option.
|
|
110
|
+
#
|
|
111
|
+
# Examples:
|
|
112
|
+
#
|
|
113
|
+
# #>pdf
|
|
114
|
+
# canvas.composer(margin: 10) do |composer|
|
|
115
|
+
# composer.text("Some text", position: :float)
|
|
116
|
+
# composer.image(machu_picchu, height: 30, align: :right)
|
|
117
|
+
# composer.lorem_ipsum(sentences: 1, margin: [0, 0, 5])
|
|
118
|
+
# composer.list(item_spacing: 2) do |list|
|
|
119
|
+
# composer.document.config['layout.boxes.map'].each do |name, klass|
|
|
120
|
+
# list.formatted_text([{text: name.to_s, fill_color: "hp-blue-dark"},
|
|
121
|
+
# {text: "\n#{klass}", font_size: 7}])
|
|
122
|
+
# end
|
|
123
|
+
# end
|
|
124
|
+
# end
|
|
125
|
+
#
|
|
126
|
+
# See: HexaPDF::Document::Layout#box
|
|
127
|
+
def method_missing(name, *args, **kwargs, &block)
|
|
128
|
+
if @document.layout.box_creation_method?(name)
|
|
129
|
+
draw_box(@document.layout.send(name, *args, **kwargs, &block))
|
|
130
|
+
else
|
|
131
|
+
super
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def respond_to_missing?(name, _private) # :nodoc:
|
|
136
|
+
@document.layout.box_creation_method?(name) || super
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
end
|
|
142
|
+
end
|
data/lib/hexapdf/content.rb
CHANGED
data/lib/hexapdf/dictionary.rb
CHANGED
|
@@ -257,14 +257,25 @@ module HexaPDF
|
|
|
257
257
|
end
|
|
258
258
|
end
|
|
259
259
|
|
|
260
|
+
# Iterates over all currently set entries and all fields that are required.
|
|
261
|
+
def each_set_key_or_required_field #:yields: name, field
|
|
262
|
+
value.keys.each {|name| yield(name, self.class.field(name)) }
|
|
263
|
+
self.class.each_field do |name, field|
|
|
264
|
+
yield(name, field) if field.required? && !value.key?(name)
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
260
268
|
# Performs validation tasks based on the currently set keys and defined fields.
|
|
261
269
|
def perform_validation(&block)
|
|
262
270
|
super
|
|
263
|
-
|
|
264
|
-
next unless field.required? || value.key?(name)
|
|
265
|
-
|
|
271
|
+
each_set_key_or_required_field do |name, field|
|
|
266
272
|
obj = key?(name) ? self[name] : nil
|
|
267
273
|
|
|
274
|
+
validate_nested(obj, &block)
|
|
275
|
+
|
|
276
|
+
# The checks below need associated field information
|
|
277
|
+
next unless field
|
|
278
|
+
|
|
268
279
|
# Check that required fields are set
|
|
269
280
|
if field.required? && obj.nil?
|
|
270
281
|
yield("Required field #{name} is not set", field.default?)
|
|
@@ -260,6 +260,28 @@ module HexaPDF
|
|
|
260
260
|
style: retrieve_style(style), **box_options)
|
|
261
261
|
end
|
|
262
262
|
|
|
263
|
+
# Creates an array of HexaPDF::Layout::TextFragment objects for the given +text+.
|
|
264
|
+
#
|
|
265
|
+
# This method uses the configuration option 'font.on_invalid_glyph' to map Unicode characters
|
|
266
|
+
# without a valid glyph in the given font to zero, one or more glyphs in a fallback font.
|
|
267
|
+
#
|
|
268
|
+
# +style+, +style_properties+::
|
|
269
|
+
# The text is styled using the given +style+. This can either be a style name set via
|
|
270
|
+
# #style or anything Layout::Style::create accepts. If any additional +style_properties+
|
|
271
|
+
# are specified, the style is duplicated and the additional styles are applied.
|
|
272
|
+
#
|
|
273
|
+
# +properties+::
|
|
274
|
+
# This can be used to set custom properties on the created text fragments. See
|
|
275
|
+
# Layout::Box#properties for details and usage.
|
|
276
|
+
def text_fragments(text, style: nil, properties: nil, **style_properties)
|
|
277
|
+
style = retrieve_style(style, style_properties)
|
|
278
|
+
fragments = HexaPDF::Layout::TextFragment.create_with_fallback_glyphs(
|
|
279
|
+
text, style, &@document.config['font.on_invalid_glyph']
|
|
280
|
+
)
|
|
281
|
+
fragments.each {|f| f.properties.update(properties) } if properties
|
|
282
|
+
fragments
|
|
283
|
+
end
|
|
284
|
+
|
|
263
285
|
# Creates a HexaPDF::Layout::TextBox for the given text.
|
|
264
286
|
#
|
|
265
287
|
# This method is of the two main methods for creating text boxes, the other being
|
|
@@ -300,7 +322,7 @@ module HexaPDF
|
|
|
300
322
|
**style_properties)
|
|
301
323
|
style = retrieve_style(style, style_properties)
|
|
302
324
|
box_style = (box_style ? retrieve_style(box_style) : style)
|
|
303
|
-
box_class_for_name(:text).new(items:
|
|
325
|
+
box_class_for_name(:text).new(items: text_fragments(text, style: style),
|
|
304
326
|
width: width, height: height, properties: properties,
|
|
305
327
|
style: box_style)
|
|
306
328
|
end
|
|
@@ -319,11 +341,11 @@ module HexaPDF
|
|
|
319
341
|
# * Hashes can contain any style properties and the following special keys:
|
|
320
342
|
#
|
|
321
343
|
# text:: The text to be formatted. If this is set and :box is not, the hash will be
|
|
322
|
-
# transformed into
|
|
344
|
+
# transformed into text fragments.
|
|
323
345
|
#
|
|
324
346
|
# link:: A URL that should be linked to. If no text is provided but a link, the link is used
|
|
325
|
-
# for the text. If this is set and :box is not, the hash will be transformed into
|
|
326
|
-
# text
|
|
347
|
+
# for the text. If this is set and :box is not, the hash will be transformed into
|
|
348
|
+
# text fragments with an appropriate link overlay.
|
|
327
349
|
#
|
|
328
350
|
# style:: The style to use as base style instead of the style created from the +style+ and
|
|
329
351
|
# +style_properties+ arguments. This can either be a style name set via #style or
|
|
@@ -334,6 +356,8 @@ module HexaPDF
|
|
|
334
356
|
#
|
|
335
357
|
# The final style is used for a created text fragment.
|
|
336
358
|
#
|
|
359
|
+
# properties:: The custom properties that should be set on the created text fragments.
|
|
360
|
+
#
|
|
337
361
|
# box:: An inline box to be used. If this is set, the hash will be transformed into an
|
|
338
362
|
# inline box.
|
|
339
363
|
#
|
|
@@ -379,26 +403,24 @@ module HexaPDF
|
|
|
379
403
|
**style_properties)
|
|
380
404
|
style = retrieve_style(style, style_properties)
|
|
381
405
|
box_style = (box_style ? retrieve_style(box_style) : style)
|
|
382
|
-
data.
|
|
406
|
+
data = data.inject([]) do |result, item|
|
|
383
407
|
case item
|
|
384
408
|
when String
|
|
385
|
-
|
|
409
|
+
result.concat(text_fragments(item, style: style))
|
|
386
410
|
when Hash
|
|
387
411
|
if (args = item.delete(:box))
|
|
388
412
|
block = item.delete(:block)
|
|
389
|
-
inline_box(*args, **item, &block)
|
|
413
|
+
result << inline_box(*args, **item, &block)
|
|
390
414
|
else
|
|
391
415
|
link = item.delete(:link)
|
|
392
416
|
(item[:overlays] ||= []) << [:link, {uri: link}] if link
|
|
393
417
|
text = item.delete(:text) || link || ""
|
|
394
|
-
|
|
418
|
+
item_properties = item.delete(:properties)
|
|
395
419
|
frag_style = retrieve_style(item.delete(:style) || style, item)
|
|
396
|
-
|
|
397
|
-
fragment.properties.update(properties) if properties
|
|
398
|
-
fragment
|
|
420
|
+
result.concat(text_fragments(text, style: frag_style, properties: item_properties))
|
|
399
421
|
end
|
|
400
422
|
when HexaPDF::Layout::InlineBox
|
|
401
|
-
item
|
|
423
|
+
result << item
|
|
402
424
|
else
|
|
403
425
|
raise ArgumentError, "Invalid item of class #{item.class} in data array"
|
|
404
426
|
end
|
|
@@ -506,7 +528,7 @@ module HexaPDF
|
|
|
506
528
|
# # row 0 has a grey background and bold text
|
|
507
529
|
# args[0] = {font: ['Helvetica', variant: :bold], cell: {background_color: 'eee'}}
|
|
508
530
|
# # text in last column is right aligned
|
|
509
|
-
# args[0..-1, -1] = {
|
|
531
|
+
# args[0..-1, -1] = {text_align: :right}
|
|
510
532
|
# end
|
|
511
533
|
#
|
|
512
534
|
# See: HexaPDF::Layout::TableBox
|
|
@@ -250,6 +250,18 @@ module HexaPDF
|
|
|
250
250
|
end
|
|
251
251
|
end
|
|
252
252
|
|
|
253
|
+
# Returns the type of password used for decrypting the PDF document.
|
|
254
|
+
#
|
|
255
|
+
# The return value is one of the following:
|
|
256
|
+
#
|
|
257
|
+
# :none:: No password was needed for decryption.
|
|
258
|
+
# :user:: The provided user password was used for decryption.
|
|
259
|
+
# :owner:: The provided owner password was used for decryption.
|
|
260
|
+
# :unknown:: The document was not decrypted, only encrypted.
|
|
261
|
+
def decryption_password_type
|
|
262
|
+
@decryption_password_type || :unknown
|
|
263
|
+
end
|
|
264
|
+
|
|
253
265
|
def decrypt(obj) #:nodoc:
|
|
254
266
|
if dict[:V] >= 4 && obj.type == :Metadata && obj[:Subtype] == :XML && !dict[:EncryptMetadata]
|
|
255
267
|
obj
|
|
@@ -345,10 +357,13 @@ module HexaPDF
|
|
|
345
357
|
password = prepare_password(password)
|
|
346
358
|
|
|
347
359
|
if user_password_valid?(prepare_password(''))
|
|
360
|
+
@decryption_password_type = :none
|
|
348
361
|
encryption_key = compute_user_encryption_key(prepare_password(''))
|
|
349
362
|
elsif user_password_valid?(password)
|
|
363
|
+
@decryption_password_type = :user
|
|
350
364
|
encryption_key = compute_user_encryption_key(password)
|
|
351
365
|
elsif owner_password_valid?(password)
|
|
366
|
+
@decryption_password_type = :owner
|
|
352
367
|
encryption_key = compute_owner_encryption_key(password)
|
|
353
368
|
else
|
|
354
369
|
raise HexaPDF::EncryptionError, "Invalid password specified"
|
data/lib/hexapdf/error.rb
CHANGED
|
@@ -94,7 +94,8 @@ module HexaPDF
|
|
|
94
94
|
end
|
|
95
95
|
|
|
96
96
|
def message # :nodoc:
|
|
97
|
-
"No glyph for #{glyph.str.inspect} in font '#{glyph.
|
|
97
|
+
"No glyph for #{glyph.str.inspect} in font '#{glyph.font_wrapper.wrapped_font.full_name}' " \
|
|
98
|
+
"found. \n\n" \
|
|
98
99
|
"Use the configuration option 'font.on_missing_glyph' to customize missing glyph handling."
|
|
99
100
|
end
|
|
100
101
|
|
|
@@ -34,6 +34,8 @@
|
|
|
34
34
|
# commercial licenses are available at <https://gettalong.at/hexapdf/>.
|
|
35
35
|
#++
|
|
36
36
|
|
|
37
|
+
require 'set'
|
|
38
|
+
|
|
37
39
|
module HexaPDF
|
|
38
40
|
module Font
|
|
39
41
|
|
|
@@ -41,21 +43,21 @@ module HexaPDF
|
|
|
41
43
|
# font.
|
|
42
44
|
class InvalidGlyph
|
|
43
45
|
|
|
44
|
-
# The associated font object.
|
|
45
|
-
attr_reader :
|
|
46
|
+
# The associated font wrapper object, either a Type1Wrapper or a TrueTypeWrapper.
|
|
47
|
+
attr_reader :font_wrapper
|
|
46
48
|
|
|
47
49
|
# The string that could not be represented as a glyph.
|
|
48
50
|
attr_reader :str
|
|
49
51
|
|
|
50
52
|
# Creates a new Glyph object.
|
|
51
|
-
def initialize(
|
|
52
|
-
@
|
|
53
|
+
def initialize(font_wrapper, str)
|
|
54
|
+
@font_wrapper = font_wrapper
|
|
53
55
|
@str = str
|
|
54
56
|
end
|
|
55
57
|
|
|
56
58
|
# Returns the appropriate missing glyph id based on the used font.
|
|
57
59
|
def id
|
|
58
|
-
@
|
|
60
|
+
@font_wrapper.wrapped_font.missing_glyph_id
|
|
59
61
|
end
|
|
60
62
|
alias name id
|
|
61
63
|
|
|
@@ -73,9 +75,23 @@ module HexaPDF
|
|
|
73
75
|
false
|
|
74
76
|
end
|
|
75
77
|
|
|
78
|
+
# Returns +false+ since this is an invalid glyph.
|
|
79
|
+
def valid?
|
|
80
|
+
false
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Set of codepoints for text control characters, like tabulator, line separators, non-breaking
|
|
84
|
+
# space etc.
|
|
85
|
+
CONTROL_CHARS = Set.new([9, 10, 11, 12, 13, 133, 8232, 8233, 8203, 173, 160]) #:nodoc:
|
|
86
|
+
|
|
87
|
+
# Returns +true+ if this glyph represents a control character like tabulator or newline.
|
|
88
|
+
def control_char?
|
|
89
|
+
CONTROL_CHARS.include?(str.ord)
|
|
90
|
+
end
|
|
91
|
+
|
|
76
92
|
#:nodoc:
|
|
77
93
|
def inspect
|
|
78
|
-
"#<#{self.class.name} font=#{@
|
|
94
|
+
"#<#{self.class.name} font=#{@font_wrapper.wrapped_font.full_name.inspect} id=#{id} #{@str.inspect}>"
|
|
79
95
|
end
|
|
80
96
|
|
|
81
97
|
end
|
|
@@ -59,8 +59,8 @@ module HexaPDF
|
|
|
59
59
|
# Represents a single glyph of the wrapped font.
|
|
60
60
|
class Glyph
|
|
61
61
|
|
|
62
|
-
# The associated
|
|
63
|
-
attr_reader :
|
|
62
|
+
# The associated TrueTypeWrapper object.
|
|
63
|
+
attr_reader :font_wrapper
|
|
64
64
|
|
|
65
65
|
# The glyph ID.
|
|
66
66
|
attr_reader :id
|
|
@@ -69,35 +69,40 @@ module HexaPDF
|
|
|
69
69
|
attr_reader :str
|
|
70
70
|
|
|
71
71
|
# Creates a new Glyph object.
|
|
72
|
-
def initialize(
|
|
73
|
-
@
|
|
72
|
+
def initialize(font_wrapper, id, str)
|
|
73
|
+
@font_wrapper = font_wrapper
|
|
74
74
|
@id = id
|
|
75
75
|
@str = str
|
|
76
76
|
end
|
|
77
77
|
|
|
78
78
|
# Returns the glyph's minimum x coordinate.
|
|
79
79
|
def x_min
|
|
80
|
-
@x_min ||= @
|
|
80
|
+
@x_min ||= @font_wrapper.wrapped_font[:glyf][id].x_min * 1000.0 /
|
|
81
|
+
@font_wrapper.wrapped_font[:head].units_per_em
|
|
81
82
|
end
|
|
82
83
|
|
|
83
84
|
# Returns the glyph's maximum x coordinate.
|
|
84
85
|
def x_max
|
|
85
|
-
@x_max ||= @
|
|
86
|
+
@x_max ||= @font_wrapper.wrapped_font[:glyf][id].x_max * 1000.0 /
|
|
87
|
+
@font_wrapper.wrapped_font[:head].units_per_em
|
|
86
88
|
end
|
|
87
89
|
|
|
88
90
|
# Returns the glyph's minimum y coordinate.
|
|
89
91
|
def y_min
|
|
90
|
-
@y_min ||= @
|
|
92
|
+
@y_min ||= @font_wrapper.wrapped_font[:glyf][id].y_min * 1000.0 /
|
|
93
|
+
@font_wrapper.wrapped_font[:head].units_per_em
|
|
91
94
|
end
|
|
92
95
|
|
|
93
96
|
# Returns the glyph's maximum y coordinate.
|
|
94
97
|
def y_max
|
|
95
|
-
@y_max ||= @
|
|
98
|
+
@y_max ||= @font_wrapper.wrapped_font[:glyf][id].y_max * 1000.0 /
|
|
99
|
+
@font_wrapper.wrapped_font[:head].units_per_em
|
|
96
100
|
end
|
|
97
101
|
|
|
98
102
|
# Returns the width of the glyph.
|
|
99
103
|
def width
|
|
100
|
-
@width ||= @
|
|
104
|
+
@width ||= @font_wrapper.wrapped_font[:hmtx][id].advance_width * 1000.0 /
|
|
105
|
+
@font_wrapper.wrapped_font[:head].units_per_em
|
|
101
106
|
end
|
|
102
107
|
|
|
103
108
|
# Returns +false+ since the word spacing parameter is never applied for multibyte font
|
|
@@ -106,9 +111,14 @@ module HexaPDF
|
|
|
106
111
|
false
|
|
107
112
|
end
|
|
108
113
|
|
|
114
|
+
# Returns +true+ since this is a valid glyph.
|
|
115
|
+
def valid?
|
|
116
|
+
true
|
|
117
|
+
end
|
|
118
|
+
|
|
109
119
|
#:nodoc:
|
|
110
120
|
def inspect
|
|
111
|
-
"#<#{self.class.name} font=#{@
|
|
121
|
+
"#<#{self.class.name} font=#{@font_wrapper.wrapped_font.full_name.inspect} id=#{id} #{str.inspect}>"
|
|
112
122
|
end
|
|
113
123
|
|
|
114
124
|
end
|
|
@@ -154,6 +164,16 @@ module HexaPDF
|
|
|
154
164
|
@scaling_factor ||= 1000.0 / @wrapped_font[:head].units_per_em
|
|
155
165
|
end
|
|
156
166
|
|
|
167
|
+
# Returns +true+ if the font contains bold glyphs.
|
|
168
|
+
def bold?
|
|
169
|
+
@wrapped_font.weight > 500
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Returns +true+ if the font contains glyphs with an incline (italic or slant).
|
|
173
|
+
def italic?
|
|
174
|
+
@wrapped_font.italic_angle.to_i != 0
|
|
175
|
+
end
|
|
176
|
+
|
|
157
177
|
# Returns +true+ if the wrapped TrueType font will be subset.
|
|
158
178
|
def subset?
|
|
159
179
|
!@subsetter.nil?
|
|
@@ -168,7 +188,7 @@ module HexaPDF
|
|
|
168
188
|
def glyph(id, str = nil)
|
|
169
189
|
@id_to_glyph[id] ||=
|
|
170
190
|
if id >= 0 && id < @wrapped_font[:maxp].num_glyphs
|
|
171
|
-
Glyph.new(
|
|
191
|
+
Glyph.new(self, id, str || (+'' << (@cmap.gid_to_code(id) || 0xFFFD)))
|
|
172
192
|
else
|
|
173
193
|
@pdf_object.document.config['font.on_missing_glyph'].call("\u{FFFD}", self)
|
|
174
194
|
end
|
|
@@ -183,19 +203,27 @@ module HexaPDF
|
|
|
183
203
|
if id < 0 || id >= @wrapped_font[:maxp].num_glyphs
|
|
184
204
|
raise HexaPDF::Error, "Glyph ID #{id} is invalid for font '#{@wrapped_font.full_name}'"
|
|
185
205
|
end
|
|
186
|
-
Glyph.new(
|
|
206
|
+
Glyph.new(self, id, string)
|
|
187
207
|
end
|
|
188
208
|
|
|
189
209
|
# Returns an array of glyph objects representing the characters in the UTF-8 encoded string.
|
|
210
|
+
#
|
|
211
|
+
# See #decode_codepoint for details.
|
|
190
212
|
def decode_utf8(str)
|
|
191
|
-
str.codepoints.map!
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
213
|
+
str.codepoints.map! {|c| @codepoint_to_glyph[c] || decode_codepoint(c) }
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Returns a glyph object for the given Unicode codepoint.
|
|
217
|
+
#
|
|
218
|
+
# The configuration option 'font.on_missing_glyph' is invoked if no glyph for a given
|
|
219
|
+
# codepoint is available.
|
|
220
|
+
def decode_codepoint(codepoint)
|
|
221
|
+
@codepoint_to_glyph[codepoint] ||=
|
|
222
|
+
if (gid = @cmap[codepoint])
|
|
223
|
+
glyph(gid, +'' << codepoint)
|
|
224
|
+
else
|
|
225
|
+
@pdf_object.document.config['font.on_missing_glyph'].call(+'' << codepoint, self)
|
|
226
|
+
end
|
|
199
227
|
end
|
|
200
228
|
|
|
201
229
|
# Encodes the glyph and returns the code string.
|