hexapdf 0.20.4 → 0.21.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 +27 -0
- data/README.md +5 -3
- data/Rakefile +10 -1
- data/examples/018-composer.rb +10 -10
- data/lib/hexapdf/cli/batch.rb +4 -6
- data/lib/hexapdf/cli/info.rb +5 -1
- data/lib/hexapdf/cli/inspect.rb +59 -0
- data/lib/hexapdf/cli/split.rb +1 -1
- data/lib/hexapdf/composer.rb +147 -53
- data/lib/hexapdf/configuration.rb +7 -3
- data/lib/hexapdf/content/canvas.rb +1 -1
- data/lib/hexapdf/content/color_space.rb +1 -1
- data/lib/hexapdf/content/operator.rb +7 -7
- data/lib/hexapdf/content/parser.rb +3 -3
- data/lib/hexapdf/content/processor.rb +9 -9
- data/lib/hexapdf/document/signatures.rb +5 -4
- data/lib/hexapdf/document.rb +7 -0
- data/lib/hexapdf/font/true_type/font.rb +7 -7
- data/lib/hexapdf/font/true_type/optimizer.rb +1 -1
- data/lib/hexapdf/font/true_type/subsetter.rb +1 -1
- data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +3 -3
- data/lib/hexapdf/font/true_type_wrapper.rb +9 -14
- data/lib/hexapdf/font/type1/font.rb +10 -12
- data/lib/hexapdf/font/type1_wrapper.rb +1 -2
- data/lib/hexapdf/layout/box.rb +12 -9
- data/lib/hexapdf/layout/image_box.rb +1 -1
- data/lib/hexapdf/layout/style.rb +28 -8
- data/lib/hexapdf/layout/text_fragment.rb +10 -9
- data/lib/hexapdf/parser.rb +5 -0
- data/lib/hexapdf/tokenizer.rb +3 -3
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +6 -4
- data/lib/hexapdf/type/acro_form/choice_field.rb +2 -2
- data/lib/hexapdf/type/acro_form/field.rb +2 -2
- data/lib/hexapdf/type/font_type0.rb +1 -1
- data/lib/hexapdf/type/font_type3.rb +1 -1
- data/lib/hexapdf/type/resources.rb +4 -4
- data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +1 -1
- data/lib/hexapdf/type/signature.rb +1 -1
- data/lib/hexapdf/type/trailer.rb +3 -3
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/xref_section.rb +1 -1
- data/test/hexapdf/common_tokenizer_tests.rb +5 -5
- data/test/hexapdf/content/test_graphics_state.rb +1 -0
- data/test/hexapdf/content/test_operator.rb +2 -2
- data/test/hexapdf/content/test_processor.rb +1 -1
- data/test/hexapdf/encryption/test_standard_security_handler.rb +23 -29
- data/test/hexapdf/filter/test_predictor.rb +16 -20
- data/test/hexapdf/font/test_type1_wrapper.rb +1 -1
- data/test/hexapdf/font/true_type/table/common.rb +1 -1
- data/test/hexapdf/font/true_type/table/test_cmap.rb +1 -1
- data/test/hexapdf/font/true_type/table/test_cmap_subtable.rb +1 -1
- data/test/hexapdf/image_loader/test_pdf.rb +6 -8
- data/test/hexapdf/image_loader/test_png.rb +2 -2
- data/test/hexapdf/layout/test_box.rb +11 -1
- data/test/hexapdf/layout/test_style.rb +23 -0
- data/test/hexapdf/layout/test_text_fragment.rb +21 -21
- data/test/hexapdf/test_composer.rb +115 -52
- data/test/hexapdf/test_dictionary.rb +2 -2
- data/test/hexapdf/test_document.rb +11 -9
- data/test/hexapdf/test_object.rb +1 -1
- data/test/hexapdf/test_parser.rb +13 -7
- data/test/hexapdf/test_serializer.rb +20 -22
- data/test/hexapdf/test_stream.rb +7 -9
- data/test/hexapdf/test_writer.rb +2 -2
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +1 -2
- data/test/hexapdf/type/acro_form/test_choice_field.rb +1 -1
- data/test/hexapdf/type/signature/common.rb +1 -1
- data/test/hexapdf/type/test_font_type0.rb +1 -1
- data/test/hexapdf/type/test_font_type1.rb +7 -7
- data/test/hexapdf/type/test_image.rb +13 -17
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b666a69330b87a3ad7a5c937a1113a66a3b5ed3d0cb4f4478ee24f97bed0e411
|
4
|
+
data.tar.gz: 460c3cd90b8f76d3e4d32fdd6f1bc8fd71ddfef5d18799c6a207f562773371f8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a9135019912d8c3d1e282797054dfbc99c028f26aa28f8412a138ab3d8d67124c4e9c52ad104eeb4289487e47dd850e3ff4211b5e928cdb0a8aab2d8ea773930
|
7
|
+
data.tar.gz: 070d0facdb6576fb6a0377a7139609ee648a580be879dabfd10e943bd2f62d66d9d5077058edcecf9ca3eb4e7969a2427c7a4af26bbb4d8f734e238f7022c7cd
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,30 @@
|
|
1
|
+
## 0.21.0 - 2022-03-04
|
2
|
+
|
3
|
+
### Added
|
4
|
+
|
5
|
+
* [HexaPDF::Parser#reconstructed?] which returns true if the cross-reference
|
6
|
+
table was reconstructed
|
7
|
+
- [HexaPDF::Layout::Style::create] for easier creation of style objects
|
8
|
+
* The ability to view revisions of a PDF document or extract a single revision
|
9
|
+
via `hexapdf inspect`
|
10
|
+
|
11
|
+
### Changed
|
12
|
+
|
13
|
+
* **Breaking change**: Refactored [HexaPDF::Composer] for better and more
|
14
|
+
consistent style support
|
15
|
+
* **Breaking change**: Arguments for configuration option
|
16
|
+
'font.on_missing_glyph' have changed to allow access to the document instance
|
17
|
+
|
18
|
+
### Fixed
|
19
|
+
|
20
|
+
* Setter for [HexaPDF::Layout::Style#line_spacing] to allow usage of numeric
|
21
|
+
arguments
|
22
|
+
* Digital Signature validation for 'adbe.pkcs7.detached' certifiates in case no
|
23
|
+
key usage was defined
|
24
|
+
* Removed caching of configuration 'font.on_missing_glyph' in font wrappers to
|
25
|
+
avoid problems
|
26
|
+
|
27
|
+
|
1
28
|
## 0.20.4 - 2022-01-26
|
2
29
|
|
3
30
|
### Fixed
|
data/README.md
CHANGED
@@ -7,13 +7,12 @@ short, it allows
|
|
7
7
|
* **manipulating** existing PDF files,
|
8
8
|
* **merging** multiple PDF files into one,
|
9
9
|
* **extracting** meta information, text, images and files from PDF files,
|
10
|
-
* **securing** PDF files by encrypting them and
|
10
|
+
* **securing** PDF files by encrypting or signing them and
|
11
11
|
* **optimizing** PDF files for smaller file size or other criteria.
|
12
12
|
|
13
13
|
HexaPDF was designed with ease of use and performance in mind. It uses lazy loading and lazy
|
14
14
|
computing when possible and tries to produce small PDF files by default.
|
15
15
|
|
16
|
-
|
17
16
|
## Usage
|
18
17
|
|
19
18
|
The HexaPDF distribution provides the library as well as the `hexapdf` application. The application
|
@@ -46,9 +45,12 @@ with example graphics and PDF files and tightly integrated into the rest of the
|
|
46
45
|
## Requirements and Installation
|
47
46
|
|
48
47
|
Since HexaPDF is written in Ruby, a working Ruby installation is needed - see the
|
49
|
-
[official installation documentation][rbinstall] for details. Note that you need Ruby version 2.
|
48
|
+
[official installation documentation][rbinstall] for details. Note that you need Ruby version 2.5 or
|
50
49
|
higher as prior versions are not supported!
|
51
50
|
|
51
|
+
HexaPDF works on all Ruby implementations that are CRuby compatible, e.g. TruffleRuby, and on any
|
52
|
+
platform supported by Ruby (Linux, macOS, Windows, ...).
|
53
|
+
|
52
54
|
Apart from Ruby itself the HexaPDF library has only one external dependency `geom2d` which is
|
53
55
|
written and provided by the HexaPDF authors. The `hexapdf` application has an additional dependency
|
54
56
|
on `cmdparse`, a command line parsing library.
|
data/Rakefile
CHANGED
@@ -46,8 +46,17 @@ namespace :dev do
|
|
46
46
|
puts 'done'
|
47
47
|
end
|
48
48
|
|
49
|
+
task :test_all do
|
50
|
+
versions = `rbenv versions --bare | grep -i 2.[567]\\\\\\|3.`.split("\n")
|
51
|
+
versions.each do |version|
|
52
|
+
sh "rbenv shell #{version} &>/dev/null && rake test"
|
53
|
+
end
|
54
|
+
puts "Looks okay? (enter to continue, Ctrl-c to abort)"
|
55
|
+
$stdin.gets
|
56
|
+
end
|
57
|
+
|
49
58
|
desc 'Release HexaPDF version ' + HexaPDF::VERSION
|
50
|
-
task release: [:clobber, :package, :publish_files]
|
59
|
+
task release: [:clobber, :test_all, :package, :publish_files]
|
51
60
|
|
52
61
|
desc "Set-up everything for development"
|
53
62
|
task :setup do
|
data/examples/018-composer.rb
CHANGED
@@ -3,9 +3,10 @@
|
|
3
3
|
# This example shows how [HexaPDF::Composer] simplifies the creation of PDF
|
4
4
|
# documents by providing a high-level interface to the box layouting engine.
|
5
5
|
#
|
6
|
-
# Basic style properties can be set
|
7
|
-
# These properties are reused by every box and can
|
8
|
-
# basis.
|
6
|
+
# Basic style properties can be set using the [HexaPDF::Composer#style] method
|
7
|
+
# and the style name `:basic`. These properties are reused by every box and can
|
8
|
+
# be adjusted on a box-by-box basis. Newly defined styles also inherit the
|
9
|
+
# properties from the `:basic` style.
|
9
10
|
#
|
10
11
|
# Various methods allow the easy creation of boxes, for example, text and image
|
11
12
|
# boxes. All these boxes are automatically drawn on the page. If the page has
|
@@ -24,21 +25,20 @@ dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exer\u{00AD}citation
|
|
24
25
|
ullamco laboris nisi ut aliquip ex ea commodo consequat. ".tr("\n", " ")
|
25
26
|
|
26
27
|
HexaPDF::Composer.create('composer.pdf') do |pdf|
|
27
|
-
pdf.
|
28
|
-
|
29
|
-
|
30
|
-
link_style = pdf.base_style.dup.update(fill_color: [6, 158, 224], underline: true)
|
28
|
+
pdf.style(:base, line_spacing: 1.5, last_line_gap: true, align: :justify)
|
29
|
+
pdf.style(:image, border: {width: 1}, padding: 5, margin: 10)
|
30
|
+
pdf.style(:link, fill_color: [6, 158, 224], underline: true)
|
31
31
|
image = File.join(__dir__, 'machupicchu.jpg')
|
32
32
|
|
33
33
|
pdf.text(lorem_ipsum * 2)
|
34
|
-
pdf.image(image, style:
|
35
|
-
pdf.image(image, style:
|
34
|
+
pdf.image(image, style: :image, width: 200, position: :float)
|
35
|
+
pdf.image(image, style: :image, width: 200, position: :absolute,
|
36
36
|
position_hint: [200, 300])
|
37
37
|
pdf.text(lorem_ipsum * 20, position: :flow)
|
38
38
|
|
39
39
|
pdf.formatted_text(["Produced by ",
|
40
40
|
{link: "https://hexapdf.gettalong.org", text: "HexaPDF",
|
41
|
-
style:
|
41
|
+
style: :link},
|
42
42
|
" via HexaPDF::Composer"],
|
43
43
|
font_size: 15, align: :center, padding: 15)
|
44
44
|
end
|
data/lib/hexapdf/cli/batch.rb
CHANGED
@@ -59,12 +59,10 @@ module HexaPDF
|
|
59
59
|
def execute(command, *files) #:nodoc:
|
60
60
|
args = Shellwords.split(command)
|
61
61
|
files.each do |file|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
$stderr.puts "Error processing '#{file}': #{$!.message}"
|
67
|
-
end
|
62
|
+
HexaPDF::CLI::Application.new.parse(args.map {|a| a.gsub(/{}/, file) })
|
63
|
+
rescue StandardError
|
64
|
+
if command_parser.verbosity_warning?
|
65
|
+
$stderr.puts "Error processing '#{file}': #{$!.message}"
|
68
66
|
end
|
69
67
|
end
|
70
68
|
end
|
data/lib/hexapdf/cli/info.rb
CHANGED
@@ -112,7 +112,8 @@ module HexaPDF
|
|
112
112
|
output_line("File name", file)
|
113
113
|
output_line("File size", File.stat(file).size.to_s << " bytes")
|
114
114
|
@auto_decrypt && INFO_KEYS.each do |name|
|
115
|
-
|
115
|
+
value = doc.trailer.info[name]
|
116
|
+
next if !value || (value.kind_of?(String) && value.empty?)
|
116
117
|
output_line(name.to_s, doc.trailer.info[name].to_s)
|
117
118
|
end
|
118
119
|
|
@@ -152,6 +153,9 @@ module HexaPDF
|
|
152
153
|
|
153
154
|
output_line("Pages", doc.pages.count.to_s)
|
154
155
|
output_line("Version", doc.version)
|
156
|
+
if doc.revisions.parser.reconstructed?
|
157
|
+
output_line("Reconstructed", "yes (use --check for details)")
|
158
|
+
end
|
155
159
|
end
|
156
160
|
rescue HexaPDF::EncryptionError
|
157
161
|
if @auto_decrypt
|
data/lib/hexapdf/cli/inspect.rb
CHANGED
@@ -216,6 +216,28 @@ module HexaPDF
|
|
216
216
|
end
|
217
217
|
end
|
218
218
|
|
219
|
+
when 'rev', 'revision'
|
220
|
+
if (rev_index = data.shift)
|
221
|
+
rev_index = rev_index.to_i - 1
|
222
|
+
if rev_index < 0 || rev_index >= @doc.revisions.count
|
223
|
+
$stderr.puts("Error: Invalid revision numer specified")
|
224
|
+
next
|
225
|
+
end
|
226
|
+
length = 0
|
227
|
+
revision_information do |_, index, _, _, end_offset|
|
228
|
+
length = end_offset if index == rev_index
|
229
|
+
end
|
230
|
+
IO.copy_stream(@doc.revisions.parser.io, $stdout, length, 0)
|
231
|
+
else
|
232
|
+
puts "Document has #{@doc.revisions.size} revision#{@doc.revisions.size == 1 ? '' : 's'}"
|
233
|
+
revision_information do |_, index, count, signature, end_offset|
|
234
|
+
puts "Revision #{index + 1}"
|
235
|
+
puts " Objects : #{count}"
|
236
|
+
puts " Signed : yes" if signature
|
237
|
+
puts " Byte range: 0-#{end_offset}"
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
219
241
|
when 'q', 'quit'
|
220
242
|
return true
|
221
243
|
|
@@ -294,11 +316,48 @@ module HexaPDF
|
|
294
316
|
puts if indent == 0
|
295
317
|
end
|
296
318
|
|
319
|
+
# Yields information about the document's revisions.
|
320
|
+
#
|
321
|
+
# Returns an array of arrays that include the following information:
|
322
|
+
#
|
323
|
+
# - The revision object itself
|
324
|
+
# - The index of the revision in terms of all revisions of the document
|
325
|
+
# - The number of objects in the revision
|
326
|
+
# - The signature dictionary if this revision was signed
|
327
|
+
# - The byte offset from the start of the file to the end of the revision
|
328
|
+
def revision_information
|
329
|
+
signatures = @doc.signatures.map do |sig|
|
330
|
+
[@doc.revisions.find {|rev| rev.object(sig) == sig }, sig]
|
331
|
+
end.to_h
|
332
|
+
io = @doc.revisions.parser.io
|
333
|
+
|
334
|
+
startxrefs = @doc.revisions.map {|rev| rev.trailer[:Prev] }
|
335
|
+
io.seek(0, IO::SEEK_END)
|
336
|
+
startxrefs.push(@doc.revisions.parser.startxref_offset, io.pos).shift
|
337
|
+
|
338
|
+
@doc.revisions.each_with_index.map do |rev, index|
|
339
|
+
end_index = 0
|
340
|
+
sig = signatures[rev]
|
341
|
+
if sig
|
342
|
+
end_index = sig[:ByteRange][-2] + sig[:ByteRange][-1]
|
343
|
+
else
|
344
|
+
io.seek(startxrefs[index], IO::SEEK_SET)
|
345
|
+
while io.pos < startxrefs[index + 1]
|
346
|
+
if io.gets =~ /^\s*%%EOF\s*$/
|
347
|
+
end_index = io.pos
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
351
|
+
yield(rev, index, rev.next_free_oid - 1, sig, end_index)
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
297
355
|
COMMAND_DESCRIPTIONS = [ #:nodoc:
|
298
356
|
["OID[,GEN] | o[bject] OID[,GEN]", "Print object"],
|
299
357
|
["r[ecursive] OID[,GEN]", "Print object recursively"],
|
300
358
|
["s[tream] OID[,GEN]", "Print filtered stream"],
|
301
359
|
["raw[-stream] OID[,GEN]", "Print raw stream"],
|
360
|
+
["rev[ision] [NUMBER]", "Print or extract revision"],
|
302
361
|
["x[ref] OID[,GEN]", "Print the cross-reference entry"],
|
303
362
|
["c[atalog]", "Print the catalog dictionary"],
|
304
363
|
["t[railer]", "Print the trailer dictionary"],
|
data/lib/hexapdf/cli/split.rb
CHANGED
data/lib/hexapdf/composer.rb
CHANGED
@@ -39,8 +39,9 @@ require 'hexapdf/layout'
|
|
39
39
|
|
40
40
|
module HexaPDF
|
41
41
|
|
42
|
-
# The composer class can be used to create PDF documents from scratch. It uses
|
43
|
-
# objects underneath
|
42
|
+
# The composer class can be used to create PDF documents from scratch. It uses
|
43
|
+
# HexaPDF::Layout::Frame and HexaPDF::Layout::Box objects underneath and binds them together to
|
44
|
+
# provide a convenient interface for working with them.
|
44
45
|
#
|
45
46
|
# == Usage
|
46
47
|
#
|
@@ -57,10 +58,23 @@ module HexaPDF
|
|
57
58
|
# page. Behind the scenes HexaPDF::Layout::Box (and subclass) objects are created and drawn on the
|
58
59
|
# page via the frame.
|
59
60
|
#
|
60
|
-
#
|
61
|
-
#
|
62
|
-
#
|
63
|
-
#
|
61
|
+
# All drawing methods accept HexaPDF::Layout::Style objects or names for style objects (defined
|
62
|
+
# via #style). The HexaPDF::Layout::Style#font is handled specially:
|
63
|
+
#
|
64
|
+
# * If no font is set on a style, the font "Times" is automatically set because otherwise there
|
65
|
+
# would be problems with text drawing operations (font is the only style property that has no
|
66
|
+
# valid default value).
|
67
|
+
#
|
68
|
+
# * Standard style objects only allow font wrapper objects to be set via the
|
69
|
+
# HexaPDF::Layout::Style#font method. Composer makes usage easier by allowing strings or an
|
70
|
+
# array [name, options_hash] to be used, like with e.g Content::Canvas. So using Helvetica as
|
71
|
+
# font, one could just do this by saying
|
72
|
+
#
|
73
|
+
# style.font = 'Helvetica'
|
74
|
+
#
|
75
|
+
# And if Helvetica bold should be used it would be
|
76
|
+
#
|
77
|
+
# style.font = ['Helvetica', variant: :bold]
|
64
78
|
#
|
65
79
|
# If the frame of a page is full and a box doesn't fit anymore, a new page is automatically
|
66
80
|
# created. The box is either split into two boxes where one fits on the first page and the other
|
@@ -105,12 +119,9 @@ module HexaPDF
|
|
105
119
|
# The Content::Canvas of the current page. Can be used to perform arbitrary drawing operations.
|
106
120
|
attr_reader :canvas
|
107
121
|
|
108
|
-
# The Layout::Frame for automatic box placement.
|
122
|
+
# The HexaPDF::Layout::Frame for automatic box placement.
|
109
123
|
attr_reader :frame
|
110
124
|
|
111
|
-
# The base style which is used when no explicit style is provided to methods (e.g. to #text).
|
112
|
-
attr_reader :base_style
|
113
|
-
|
114
125
|
# Creates a new Composer object and optionally yields it to the given block.
|
115
126
|
#
|
116
127
|
# page_size::
|
@@ -122,15 +133,22 @@ module HexaPDF
|
|
122
133
|
# +page_size+ is one of the predefined page sizes.
|
123
134
|
#
|
124
135
|
# margin::
|
125
|
-
# The margin to use. See Layout::Style::Quad#set for possible values.
|
136
|
+
# The margin to use. See HexaPDF::Layout::Style::Quad#set for possible values.
|
137
|
+
#
|
138
|
+
# Example:
|
139
|
+
#
|
140
|
+
# composer = HexaPDF::Composer.new # uses the default values
|
141
|
+
# HexaPDF::Composer.new(page_size: :Letter, margin: 72) do |composer|
|
142
|
+
# #...
|
143
|
+
# end
|
126
144
|
def initialize(page_size: :A4, page_orientation: :portrait, margin: 36) #:yields: composer
|
127
145
|
@document = HexaPDF::Document.new
|
128
146
|
@page_size = page_size
|
129
147
|
@page_orientation = page_orientation
|
130
148
|
@margin = Layout::Style::Quad.new(margin)
|
149
|
+
@styles = {base: Layout::Style.new}
|
131
150
|
|
132
151
|
new_page
|
133
|
-
@base_style = Layout::Style.new(font: 'Times')
|
134
152
|
yield(self) if block_given?
|
135
153
|
end
|
136
154
|
|
@@ -170,32 +188,85 @@ module HexaPDF
|
|
170
188
|
@document.write(output, optimize: optimize, **options)
|
171
189
|
end
|
172
190
|
|
191
|
+
# :call-seq:
|
192
|
+
# composer.style(:header) -> style
|
193
|
+
# composer.style(:header, base: :base, **properties) -> style
|
194
|
+
#
|
195
|
+
# Creates or updates the HexaPDF::Layout::Style object called +name+ with the given property
|
196
|
+
# values and returns it. Such a style can then be used by name in the various box drawing
|
197
|
+
# methods, e.g. #text or #image.
|
198
|
+
#
|
199
|
+
# If neither +base+ nor any style properties are specified, the style +name+ is just returned.
|
200
|
+
#
|
201
|
+
# If the style +name+ does not exist yet and the argument +base+ specifies the name of another
|
202
|
+
# style, that style is duplicated and used as basis for the style.
|
203
|
+
#
|
204
|
+
# The special name :base should be used for setting the base style which is used when no
|
205
|
+
# specific style is set. It is best to fully initialize the base style before creating any
|
206
|
+
# other styles.
|
207
|
+
#
|
208
|
+
# Note that the style property 'font' is handled specially by Composer, see the class
|
209
|
+
# documentation for details.
|
210
|
+
#
|
211
|
+
# Example:
|
212
|
+
#
|
213
|
+
# composer.style(:base, font_size: 12, leading: 1.2)
|
214
|
+
# composer.style(:header, font: 'Helvetica', fill_color: "008")
|
215
|
+
# composer.style(:header1, base: :header, font_size: 30)
|
216
|
+
#
|
217
|
+
# See: HexaPDF::Layout::Style
|
218
|
+
def style(name, base: :base, **properties)
|
219
|
+
style = @styles[name] ||= (@styles.key?(base) ? @styles[base].dup : Layout::Style.new)
|
220
|
+
style.update(**properties) unless properties.empty?
|
221
|
+
style
|
222
|
+
end
|
223
|
+
|
173
224
|
# Draws the given text at the current position into the current frame.
|
174
225
|
#
|
175
|
-
# This method is the main method for displaying text on a PDF page. It uses a
|
176
|
-
# behind the scenes to do the actual work.
|
226
|
+
# This method is the main method for displaying text on a PDF page. It uses a
|
227
|
+
# HexaPDF::Layout::TextBox behind the scenes to do the actual work.
|
177
228
|
#
|
178
229
|
# The text will be positioned at the current position if possible. Otherwise the next best
|
179
230
|
# position is used. If the text doesn't fit onto the current page or only partially, new pages
|
180
231
|
# are created automatically.
|
181
232
|
#
|
182
|
-
#
|
183
|
-
#
|
233
|
+
# +width+, +height+::
|
234
|
+
# The arguments +width+ and +height+ are used as constraints and are respected when fitting
|
235
|
+
# the box. The default value of 0 means that no constraints are set.
|
184
236
|
#
|
185
|
-
#
|
186
|
-
#
|
187
|
-
#
|
237
|
+
# +style+, +style_properties+::
|
238
|
+
# The box and the text are styled using the given +style+. This can either be a style name
|
239
|
+
# set via #style or anything HexaPDF::Layout::Style::create accepts. If any additional
|
240
|
+
# +style_properties+ are specified, the style is duplicated and the additional styles are
|
241
|
+
# applied.
|
188
242
|
#
|
189
|
-
#
|
190
|
-
|
191
|
-
|
243
|
+
# +box_style+::
|
244
|
+
# Sometimes it is necessary for the box to have a different style than the text, e.g. when
|
245
|
+
# using overlays. In such a case use +box_style+ for specifiying the style of the box (a
|
246
|
+
# style name set via #style or anything HexaPDF::Layout::Style::create accepts). The +style+
|
247
|
+
# together with the +style_properties+ will be used for the text style.
|
248
|
+
#
|
249
|
+
# Examples:
|
250
|
+
#
|
251
|
+
# #>pdf-composer
|
252
|
+
# composer.text("Test " * 15)
|
253
|
+
# composer.text("Now " * 7, width: 100)
|
254
|
+
# composer.text("Another test", font_size: 15, fill_color: "green")
|
255
|
+
# composer.text("Different box style", fill_color: 'white', box_style: {
|
256
|
+
# underlays: [->(c, b) { c.rectangle(0, 0, b.content_width, b.content_height).fill }]
|
257
|
+
# })
|
258
|
+
#
|
259
|
+
# See HexaPDF::HexaPDF::Layout::TextBox for details.
|
260
|
+
def text(str, width: 0, height: 0, style: nil, box_style: nil, **style_properties)
|
261
|
+
style = retrieve_style(style, style_properties)
|
262
|
+
box_style = (box_style ? retrieve_style(box_style) : style)
|
192
263
|
draw_box(Layout::TextBox.new([Layout::TextFragment.create(str, style)],
|
193
|
-
width: width, height: height, style:
|
264
|
+
width: width, height: height, style: box_style))
|
194
265
|
end
|
195
266
|
|
196
|
-
# Draws text like #text but
|
267
|
+
# Draws text like #text but allows parts of the text to be formatted differently.
|
197
268
|
#
|
198
|
-
# The argument +data+ needs to be an array of String or Hash objects:
|
269
|
+
# The argument +data+ needs to be an array of String and/or Hash objects:
|
199
270
|
#
|
200
271
|
# * A String object is treated like {text: data}.
|
201
272
|
#
|
@@ -206,48 +277,58 @@ module HexaPDF
|
|
206
277
|
# link:: A URL that should be linked to. If no text is provided but a link, the link is used
|
207
278
|
# as text.
|
208
279
|
#
|
209
|
-
# style::
|
210
|
-
#
|
280
|
+
# style:: The style to be use as basis instead of the style created from the +style+ and
|
281
|
+
# +style_properties+ arguments. See HexaPDF::Layout::Style::create for allowed values.
|
211
282
|
#
|
212
283
|
# If any style properties are set, the used style is copied and the additional properties
|
213
284
|
# applied.
|
214
285
|
#
|
286
|
+
# See #text for details on +width+, +height+, +style+, +style_properties+ and +box_style+.
|
287
|
+
#
|
215
288
|
# Examples:
|
216
289
|
#
|
217
|
-
# composer
|
218
|
-
# composer.formatted_text(["Some
|
219
|
-
# composer.formatted_text(["Some ", {
|
220
|
-
# composer.formatted_text(["Some ", {
|
221
|
-
|
222
|
-
|
290
|
+
# #>pdf-composer
|
291
|
+
# composer.formatted_text(["Some string"])
|
292
|
+
# composer.formatted_text(["Some ", {text: "string", fill_color: 128}])
|
293
|
+
# composer.formatted_text(["Some ", {link: "https://example.com",
|
294
|
+
# fill_color: 'blue', text: "Example"}])
|
295
|
+
# composer.formatted_text(["Some ", {text: "string", style: {font_size: 20}}])
|
296
|
+
#
|
297
|
+
# See: #text, HexaPDF::Layout::TextBox, HexaPDF::Layout::TextFragment
|
298
|
+
def formatted_text(data, width: 0, height: 0, style: nil, box_style: nil, **style_properties)
|
299
|
+
style = retrieve_style(style, style_properties)
|
300
|
+
box_style = (box_style ? retrieve_style(box_style) : style)
|
223
301
|
data.map! do |hash|
|
224
302
|
if hash.kind_of?(String)
|
225
303
|
Layout::TextFragment.create(hash, style)
|
226
304
|
else
|
227
305
|
link = hash.delete(:link)
|
306
|
+
(hash[:overlays] ||= []) << [:link, {uri: link}] if link
|
228
307
|
text = hash.delete(:text) || link || ""
|
229
|
-
|
230
|
-
if link || !hash.empty?
|
231
|
-
used_style = used_style.dup
|
232
|
-
hash.each {|key, value| used_style.send(key, value) }
|
233
|
-
used_style.overlays.add(:link, uri: link) if link
|
234
|
-
end
|
235
|
-
Layout::TextFragment.create(text, used_style)
|
308
|
+
Layout::TextFragment.create(text, retrieve_style(hash.delete(:style) || style, hash))
|
236
309
|
end
|
237
310
|
end
|
238
|
-
draw_box(Layout::TextBox.new(data, width: width, height: height, style:
|
311
|
+
draw_box(Layout::TextBox.new(data, width: width, height: height, style: box_style))
|
239
312
|
end
|
240
313
|
|
241
|
-
# Draws the given image
|
314
|
+
# Draws the given image at the current position.
|
315
|
+
#
|
316
|
+
# The +file+ argument can be anything that is accepted by HexaPDF::Document::Images#add.
|
317
|
+
#
|
318
|
+
# See #text for details on +width+, +height+, +style+ and +style_properties+.
|
319
|
+
#
|
320
|
+
# Examples:
|
242
321
|
#
|
243
|
-
#
|
244
|
-
|
245
|
-
|
322
|
+
# #>pdf-composer
|
323
|
+
# composer.image(machu_picchu, border: {width: 3})
|
324
|
+
# composer.image(machu_picchu, height: 30)
|
325
|
+
def image(file, width: 0, height: 0, style: nil, **style_properties)
|
326
|
+
style = retrieve_style(style, style_properties)
|
246
327
|
image = document.images.add(file)
|
247
328
|
draw_box(Layout::ImageBox.new(image, width: width, height: height, style: style))
|
248
329
|
end
|
249
330
|
|
250
|
-
# Draws the given Layout::Box.
|
331
|
+
# Draws the given HexaPDF::Layout::Box.
|
251
332
|
#
|
252
333
|
# The box is drawn into the current frame if possible. If it doesn't fit, the box is split. If
|
253
334
|
# it still doesn't fit, a new region of the frame is determined and then the process starts
|
@@ -291,13 +372,26 @@ module HexaPDF
|
|
291
372
|
media_box.height - @margin.bottom - @margin.top)
|
292
373
|
end
|
293
374
|
|
294
|
-
#
|
295
|
-
#
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
375
|
+
# Retrieves the appropriate HexaPDF::Layout::Style object based on the +style+ and +properties+
|
376
|
+
# arguments.
|
377
|
+
#
|
378
|
+
# The +style+ argument specifies the style to retrieve. It can either be a registered style name
|
379
|
+
# (see #style), a hash with style properties or +nil+. In the latter case the registered style
|
380
|
+
# :base is used
|
381
|
+
#
|
382
|
+
# If the +properties+ hash is not empty, the retrieved style is duplicated and the properties
|
383
|
+
# hash is applied to it.
|
384
|
+
#
|
385
|
+
# Finally, a default font is set if necessary to ensure that the style object works in all
|
386
|
+
# cases.
|
387
|
+
def retrieve_style(style, properties = nil)
|
388
|
+
style = Layout::Style.create(@styles[style] || style || @styles[:base])
|
389
|
+
style = style.dup.update(**properties) unless properties.nil? || properties.empty?
|
390
|
+
style.font('Times') unless style.font?
|
391
|
+
unless style.font.respond_to?(:pdf_object)
|
392
|
+
name, options = *style.font
|
393
|
+
style.font(@document.fonts.add(name, **(options || {})))
|
394
|
+
end
|
301
395
|
style
|
302
396
|
end
|
303
397
|
|
@@ -266,10 +266,14 @@ module HexaPDF
|
|
266
266
|
# font.on_missing_glyph::
|
267
267
|
# Callback hook when an UTF-8 character cannot be mapped to a glyph of a font.
|
268
268
|
#
|
269
|
-
# The value needs to be an object that responds to \#call(character,
|
269
|
+
# The value needs to be an object that responds to \#call(character, font_wrapper) where
|
270
270
|
# +character+ is the Unicode character for the missing glyph and returns a substitute glyph to
|
271
271
|
# be used instead.
|
272
272
|
#
|
273
|
+
# The +font_wrapper+ argument is the used font wrapper object, e.g.
|
274
|
+
# HexaPDF::Font::TrueTypeWrapper. To access the HexaPDF::Document instance from which this hook
|
275
|
+
# was called, you can use +font_wrapper.pdf_object.document+.
|
276
|
+
#
|
273
277
|
# The default implementation returns an object of class HexaPDF::Font::InvalidGlyph which, when
|
274
278
|
# not removed before encoding, will raise an error.
|
275
279
|
#
|
@@ -431,8 +435,8 @@ module HexaPDF
|
|
431
435
|
Encryption: 'HexaPDF::Filter::Encryption',
|
432
436
|
},
|
433
437
|
'font.map' => {},
|
434
|
-
'font.on_missing_glyph' => proc do |char,
|
435
|
-
HexaPDF::Font::InvalidGlyph.new(
|
438
|
+
'font.on_missing_glyph' => proc do |char, font_wrapper|
|
439
|
+
HexaPDF::Font::InvalidGlyph.new(font_wrapper.wrapped_font, char)
|
436
440
|
end,
|
437
441
|
'font.on_missing_unicode_mapping' => proc do |code_point, font|
|
438
442
|
raise HexaPDF::Error, "No Unicode mapping for code point #{code_point} " \
|
@@ -296,7 +296,7 @@ module HexaPDF
|
|
296
296
|
spec = if first_item.match?(/\A\h{6}\z/)
|
297
297
|
first_item.scan(/../).map!(&:hex)
|
298
298
|
elsif first_item.match?(/\A\h{3}\z/)
|
299
|
-
first_item.each_char.map {|x| (x*2).hex}
|
299
|
+
first_item.each_char.map {|x| (x * 2).hex }
|
300
300
|
elsif CSS_COLOR_NAMES.key?(first_item)
|
301
301
|
CSS_COLOR_NAMES[first_item]
|
302
302
|
else
|
@@ -1036,14 +1036,14 @@ module HexaPDF
|
|
1036
1036
|
s: EndPath.new('s'),
|
1037
1037
|
f: EndPath.new('f'),
|
1038
1038
|
F: EndPath.new('F'),
|
1039
|
-
'f*'
|
1039
|
+
'f*': EndPath.new('f*'),
|
1040
1040
|
B: EndPath.new('B'),
|
1041
|
-
'B*'
|
1041
|
+
'B*': EndPath.new('B*'),
|
1042
1042
|
b: EndPath.new('b'),
|
1043
|
-
'b*'
|
1043
|
+
'b*': EndPath.new('b*'),
|
1044
1044
|
n: EndPath.new('n'),
|
1045
1045
|
W: ClipPath.new('W'),
|
1046
|
-
'W*'
|
1046
|
+
'W*': ClipPath.new('W*'),
|
1047
1047
|
|
1048
1048
|
BI: InlineImage.new,
|
1049
1049
|
|
@@ -1059,10 +1059,10 @@ module HexaPDF
|
|
1059
1059
|
Td: MoveText.new,
|
1060
1060
|
TD: MoveTextAndSetLeading.new,
|
1061
1061
|
Tm: SetTextMatrix.new,
|
1062
|
-
'T*'
|
1062
|
+
'T*': MoveTextNextLine.new,
|
1063
1063
|
Tj: ShowText.new,
|
1064
|
-
'
|
1065
|
-
'"'
|
1064
|
+
"'": MoveTextNextLineAndShowText.new,
|
1065
|
+
'"': SetSpacingMoveTextNextLineAndShowText.new,
|
1066
1066
|
TJ: ShowTextWithPositioning.new,
|
1067
1067
|
}
|
1068
1068
|
DEFAULT_OPERATORS.default_proc = proc {|h, k| h[k] = BaseOperator.new(k.to_s) }
|
@@ -94,11 +94,11 @@ module HexaPDF
|
|
94
94
|
elsif byte == 40 # (
|
95
95
|
parse_literal_string
|
96
96
|
elsif byte == 60 # <
|
97
|
-
if @string.getbyte(@ss.pos + 1)
|
98
|
-
parse_hex_string
|
99
|
-
else
|
97
|
+
if @string.getbyte(@ss.pos + 1) == 60
|
100
98
|
@ss.pos += 2
|
101
99
|
TOKEN_DICT_START
|
100
|
+
else
|
101
|
+
parse_hex_string
|
102
102
|
end
|
103
103
|
elsif byte == 62 # >
|
104
104
|
unless @string.getbyte(@ss.pos + 1) == 62
|