hexapdf 0.20.4 → 0.21.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 +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
|