hexapdf 0.24.2 → 0.25.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 +63 -14
- data/examples/022-outline.rb +30 -0
- data/lib/hexapdf/configuration.rb +3 -0
- data/lib/hexapdf/document/destinations.rb +90 -1
- data/lib/hexapdf/document.rb +7 -0
- data/lib/hexapdf/encryption/security_handler.rb +4 -1
- data/lib/hexapdf/layout/style.rb +41 -12
- data/lib/hexapdf/layout/text_layouter.rb +5 -0
- data/lib/hexapdf/revision.rb +3 -0
- data/lib/hexapdf/revisions.rb +11 -1
- data/lib/hexapdf/type/acro_form/form.rb +3 -12
- data/lib/hexapdf/type/annotation.rb +5 -16
- data/lib/hexapdf/type/catalog.rb +7 -0
- data/lib/hexapdf/type/font_descriptor.rb +4 -13
- data/lib/hexapdf/type/object_stream.rb +9 -1
- data/lib/hexapdf/type/outline.rb +122 -0
- data/lib/hexapdf/type/outline_item.rb +373 -0
- data/lib/hexapdf/type.rb +2 -0
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/document/test_destinations.rb +87 -0
- data/test/hexapdf/encryption/test_security_handler.rb +4 -2
- data/test/hexapdf/layout/test_style.rb +1 -0
- data/test/hexapdf/layout/test_text_layouter.rb +6 -0
- data/test/hexapdf/test_document.rb +4 -0
- data/test/hexapdf/test_revisions.rb +47 -0
- data/test/hexapdf/test_writer.rb +3 -3
- data/test/hexapdf/type/test_catalog.rb +7 -0
- data/test/hexapdf/type/test_object_stream.rb +11 -10
- data/test/hexapdf/type/test_outline.rb +69 -0
- data/test/hexapdf/type/test_outline_item.rb +292 -0
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0f8bf550cff0d50567439c9942c97d6fc32e9fa9edab3bbff05ce854e22b0cc3
|
4
|
+
data.tar.gz: bab6243ec81278f278589fc90768dd7e2c6c73c0c314bd8ce0f17350f2d6cd73
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bfdfa30c16cae575fb028b9f804516601c56e02dfcf77b63452d63a9ce3ce28950661ef0de3b5a178fef7442fee51ef911e798b627f627e8f1991db6ae185691
|
7
|
+
data.tar.gz: aea9f97cb7465cc9592cab25e11c890efce3d10fa0c6c69dcaade78fc94a68fef1b1dd907f87c96f894840d913342d7183ad1efc6fd6d6e8be290759168af105
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,30 @@
|
|
1
|
+
## 0.25.0 - 2022-10-02
|
2
|
+
|
3
|
+
### Added
|
4
|
+
|
5
|
+
* Support for the document outline
|
6
|
+
* [HexaPDF::Layout::Style#line_height] for setting a custom line height
|
7
|
+
independent of the font size
|
8
|
+
* [HexaPDF::Document::Destinations#use_or_create] as unified interface for
|
9
|
+
using or creating destinations
|
10
|
+
* [HexaPDF::Document::Destinations::Destination#valid?] and class method for
|
11
|
+
checking whether a destination array is valid
|
12
|
+
|
13
|
+
### Fixed
|
14
|
+
|
15
|
+
* Calculation of text related [HexaPDF::Layout::Style] values for Type3 fonts
|
16
|
+
* [HexaPDF::Encryption::SecurityHandler#encrypt_string] to either return a
|
17
|
+
dupped or encrypted string
|
18
|
+
* [HexaPDF::Layout::TextLayouter#fit] to avoid infinite loop when encountering
|
19
|
+
a non-zero width breakpoint penalty
|
20
|
+
* [HexaPDF::Type::ObjectStream] to parse the initial stream data right after
|
21
|
+
initialization to avoid access errors
|
22
|
+
* [HexaPDF::Revisions::from_io] to merge a completely empty revision with just a
|
23
|
+
/XRefStm pointing into the previous one with the latter
|
24
|
+
* [HexaPDF::Revisions::from_io] to handle the case of the configuration option
|
25
|
+
'parser.try_xref_reconstruction' being false
|
26
|
+
|
27
|
+
|
1
28
|
## 0.24.2 - 2022-08-31
|
2
29
|
|
3
30
|
### Fixed
|
data/README.md
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
2
2
|
|
3
|
-
HexaPDF is a pure Ruby library with an accompanying application for working with PDF files.
|
4
|
-
|
3
|
+
HexaPDF is a pure Ruby library with an accompanying application for working with PDF files. It was
|
4
|
+
designed with ease of use and performance in mind. It uses lazy loading and lazy computing when
|
5
|
+
possible and tries to produce small PDF files by default.
|
6
|
+
|
7
|
+
In short, it allows
|
5
8
|
|
6
9
|
* **creating** new PDF files,
|
7
10
|
* **manipulating** existing PDF files,
|
@@ -10,8 +13,51 @@ short, it allows
|
|
10
13
|
* **securing** PDF files by encrypting or signing them and
|
11
14
|
* **optimizing** PDF files for smaller file size or other criteria.
|
12
15
|
|
13
|
-
|
14
|
-
|
16
|
+
|
17
|
+
## Features
|
18
|
+
|
19
|
+
* Pure Ruby
|
20
|
+
* Minimal dependencies ('cmdparse' for `hexapdf` binary, 'geom2d' for document layout)
|
21
|
+
* Easy to use, Ruby-esque API
|
22
|
+
* Fully tested with 100% code coverage
|
23
|
+
* Low-level API with high-level convenience interface on top
|
24
|
+
* Complete [canvas API] which directly maps to PDF internal operators
|
25
|
+
* Path drawing operations like lines, polylines, rectangles, bézier curves, arcs, ...
|
26
|
+
* Embedding images in JPEG (lossy), PNG (lossless) and PDF (vector) format with support for
|
27
|
+
transparency
|
28
|
+
* UTF-8 text via TrueType fonts and support for font subsetting
|
29
|
+
* High-level [document composition engine] with automatic content layout
|
30
|
+
* [Flowing text] around other content
|
31
|
+
* Pre-define [styles] and assign to multiple content boxes
|
32
|
+
* Automatic page breaks
|
33
|
+
* [(Un)ordered lists]
|
34
|
+
* [Multi-column layout]
|
35
|
+
* [PDF forms] (AcroForm) with Adobe Reader like appearance generation
|
36
|
+
* Annotations
|
37
|
+
* [Document outline]
|
38
|
+
* [Attaching files] to the whole PDF or individual pages, extracting files
|
39
|
+
* Image extraction
|
40
|
+
* [Encryption] including PDF 2.0 features (e.g. AES256)
|
41
|
+
* [Digital signatures]
|
42
|
+
* [File size optimization]
|
43
|
+
* PDF object validation
|
44
|
+
* [`hexapdf` binary][hp] for most common PDF manipulation tasks
|
45
|
+
|
46
|
+
|
47
|
+
[canvas API]: https://hexapdf.gettalong.org/documentation/reference/api/HexaPDF/Content/Canvas.html
|
48
|
+
[document composition engine]: https://hexapdf.gettalong.org/documentation/key-topics/document-layout.html
|
49
|
+
[flowing text]: https://hexapdf.gettalong.org/examples/frame_text_flow.html
|
50
|
+
[styles]: https://hexapdf.gettalong.org/documentation/reference/api/HexaPDF/Layout/Style/index.html
|
51
|
+
[(un)ordered lists]: https://hexapdf.gettalong.org/documentation/reference/api/HexaPDF/Layout/ListBox.html
|
52
|
+
[multi-column layout]: https://hexapdf.gettalong.org/documentation/reference/api/HexaPDF/Layout/ColumnBox.html
|
53
|
+
[PDF forms]: https://hexapdf.gettalong.org/documentation/key-topics/forms.html
|
54
|
+
[Document outline]: https://hexapdf.gettalong.org/documentation/reference/api/HexaPDF/Type/Outline.html
|
55
|
+
[attaching files]: https://hexapdf.gettalong.org/documentation/reference/api/HexaPDF/Document/Files.html
|
56
|
+
[Encryption]: https://hexapdf.gettalong.org/documentation/key-topics/encryption.html
|
57
|
+
[Digital Signatures]: https://hexapdf.gettalong.org/documentation/key-topics/digital-signatures.html
|
58
|
+
[File size optimization]: https://hexapdf.gettalong.org/documentation/benchmarks/optimization.html
|
59
|
+
[hp]: https://hexapdf.gettalong.org/documentation/reference/hexapdf.1.html
|
60
|
+
|
15
61
|
|
16
62
|
## Usage
|
17
63
|
|
@@ -39,7 +85,7 @@ documentation, example code and more.
|
|
39
85
|
It is recommend to use the HTML API documentation provided by the HexaPDF website as it is enhanced
|
40
86
|
with example graphics and PDF files and tightly integrated into the rest of the website.
|
41
87
|
|
42
|
-
[website]:
|
88
|
+
[website]: https://hexapdf.gettalong.org
|
43
89
|
|
44
90
|
|
45
91
|
## Requirements and Installation
|
@@ -74,18 +120,21 @@ Prawn has no such functionality. There is basic support for using a PDF as a tem
|
|
74
120
|
`pdf-reader` and `prawn-template` gems but support is very limited. However, Prawn has a very
|
75
121
|
featureful API when it comes to creating content, for individual pages as well as across pages.
|
76
122
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
123
|
+
If you want to migrate from Prawn to HexaPDF, there is the [migration guide] with detailed
|
124
|
+
information and examples, comparing the Prawn API to HexaPDF's equivalents.
|
125
|
+
|
126
|
+
[migration guide]: https://hexapdf.gettalong.org/documentation/howtos/migrating-from-prawn.html
|
127
|
+
|
128
|
+
Why use HexaPDF?
|
81
129
|
|
82
|
-
|
130
|
+
* It has many more [features](#features) beside content creation that might come in handy (e.g. PDF
|
131
|
+
form creation, encryption, digital signatures, ...).
|
83
132
|
|
84
133
|
* The architecture of HexaPDF is based on the object model of the PDF standard. This makes extending
|
85
134
|
HexaPDF very easy and allows for **reading PDF files for templating purposes**.
|
86
135
|
|
87
|
-
* HexaPDF
|
88
|
-
|
136
|
+
* HexaPDF provides a high level API for **composing a document of individual elements** that are
|
137
|
+
automatically layouted. Such elements can be headers, paragraphs, code blocks, ... or links,
|
89
138
|
emphasized text and so on. These elements can be customized and additional element types easily
|
90
139
|
added.
|
91
140
|
|
@@ -123,9 +172,9 @@ Some included files have a different license:
|
|
123
172
|
|
124
173
|
## Contributing
|
125
174
|
|
126
|
-
See <
|
175
|
+
See <https://hexapdf.gettalong.org/contributing.html> for more information.
|
127
176
|
|
128
177
|
|
129
178
|
## Author
|
130
179
|
|
131
|
-
Thomas Leitner, <
|
180
|
+
Thomas Leitner, <https://gettalong.org>
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# # Document Outline (Bookmarks)
|
2
|
+
#
|
3
|
+
# This example shows how to add a document outline, also known as
|
4
|
+
# bookmarks, to a PDF document.
|
5
|
+
#
|
6
|
+
# Usage:
|
7
|
+
# : `ruby outline.rb`
|
8
|
+
#
|
9
|
+
|
10
|
+
require 'hexapdf'
|
11
|
+
|
12
|
+
doc = HexaPDF::Document.new
|
13
|
+
6.times { doc.pages.add }
|
14
|
+
|
15
|
+
doc.outline.add_item("Main") do |main|
|
16
|
+
main.add_item("Page 1", destination: 0)
|
17
|
+
main.add_item("Page 2", destination: 1)
|
18
|
+
main.add_item("Sub", flags: [:bold], text_color: "red", open: false) do |sub|
|
19
|
+
sub.add_item("Page 3", destination: {type: :fit_page_horizontal, page: doc.pages[2], top: 480})
|
20
|
+
sub.add_item("Page 4", destination: 3)
|
21
|
+
end
|
22
|
+
main.add_item("Page 5", destination: 4)
|
23
|
+
end
|
24
|
+
doc.outline.add_item("Appendix") do |appendix|
|
25
|
+
dest = doc.destinations.use_or_create(5)
|
26
|
+
appendix.add_item("Page 6", action: {S: :GoTo, D: dest})
|
27
|
+
end
|
28
|
+
|
29
|
+
doc.catalog[:PageMode] = :UseOutlines
|
30
|
+
doc.write('outline.pdf', optimize: true)
|
@@ -472,6 +472,7 @@ module HexaPDF
|
|
472
472
|
'image_loader.pdf.use_stringio' => true,
|
473
473
|
'io.chunk_size' => 2**16,
|
474
474
|
'layout.boxes.map' => {
|
475
|
+
base: 'HexaPDF::Layout::Box',
|
475
476
|
text: 'HexaPDF::Layout::TextBox',
|
476
477
|
image: 'HexaPDF::Layout::ImageBox',
|
477
478
|
column: 'HexaPDF::Layout::ColumnBox',
|
@@ -585,6 +586,8 @@ module HexaPDF
|
|
585
586
|
DocTimeStamp: 'HexaPDF::Type::Signature',
|
586
587
|
SigRef: 'HexaPDF::Type::Signature::SignatureReference',
|
587
588
|
TransformParams: 'HexaPDF::Type::Signature::TransformParams',
|
589
|
+
Outlines: 'HexaPDF::Type::Outline',
|
590
|
+
XXOutlineItem: 'HexaPDF::Type::OutlineItem',
|
588
591
|
},
|
589
592
|
'object.subtype_map' => {
|
590
593
|
nil => {
|
@@ -67,7 +67,8 @@ module HexaPDF
|
|
67
67
|
# There are eight different types of destinations, each taking different arguments. The
|
68
68
|
# arguments are marked up in the list below and are in the correct order for use in the
|
69
69
|
# destination array. The first name in the list is the PDF internal name, the second one the
|
70
|
-
# explicit, more descriptive one used by HexaPDF
|
70
|
+
# explicit, more descriptive one used by HexaPDF (though the PDF internal name can also be
|
71
|
+
# used):
|
71
72
|
#
|
72
73
|
# :XYZ, :xyz::
|
73
74
|
# Display the page with the given (+left+, +top+) coordinate at the upper-left corner of
|
@@ -121,6 +122,13 @@ module HexaPDF
|
|
121
122
|
# :nodoc:
|
122
123
|
REVERSE_TYPE_MAPPING = Hash[*TYPE_MAPPING.flatten.reverse]
|
123
124
|
|
125
|
+
# Returns +true+ if the destination is valid.
|
126
|
+
def self.valid?(destination)
|
127
|
+
TYPE_MAPPING.key?(destination[1]) &&
|
128
|
+
(destination[0].kind_of?(Integer) || destination[0]&.type == :Page) &&
|
129
|
+
destination[2..-1].all? {|item| item.nil? || item.kind_of?(Numeric) }
|
130
|
+
end
|
131
|
+
|
124
132
|
# Creates a new Destination for the given +destination+ which may be an explicit destination
|
125
133
|
# array or a dictionary with a /D entry (as allowed for a named destination).
|
126
134
|
def initialize(destination)
|
@@ -199,6 +207,11 @@ module HexaPDF
|
|
199
207
|
end
|
200
208
|
end
|
201
209
|
|
210
|
+
# Returns +true+ if the destination is valid.
|
211
|
+
def valid?
|
212
|
+
self.class.valid?(@destination)
|
213
|
+
end
|
214
|
+
|
202
215
|
end
|
203
216
|
|
204
217
|
include Enumerable
|
@@ -208,6 +221,82 @@ module HexaPDF
|
|
208
221
|
@document = document
|
209
222
|
end
|
210
223
|
|
224
|
+
# :call-seq:
|
225
|
+
# destinations.use_or_create(name) -> name
|
226
|
+
# destinations.use_or_create(destination) -> destination
|
227
|
+
# destinations.use_or_create(page) -> destination
|
228
|
+
# destinations.use_or_create(type:, page, **options) -> destination
|
229
|
+
#
|
230
|
+
# Uses the given destination name/array or creates a destination array based on the given
|
231
|
+
# +value+.
|
232
|
+
#
|
233
|
+
# This is the main utility method for other parts of HexaPDF for getting a valid destination
|
234
|
+
# array based on various different types of the given +value+:
|
235
|
+
#
|
236
|
+
# String::
|
237
|
+
#
|
238
|
+
# If a string is provided, it is assumed to be a named destination. If the named
|
239
|
+
# destination exists, the value itself is returned. Otherwise an error is raised.
|
240
|
+
#
|
241
|
+
# Array::
|
242
|
+
#
|
243
|
+
# If a valid destination array is provided, it is returned. Otherwise an error is raised.
|
244
|
+
#
|
245
|
+
# Page dictionary::
|
246
|
+
#
|
247
|
+
# If the value is a valid page dictionary object, a fit to page (#create_fit_page)
|
248
|
+
# destination array is created and returned.
|
249
|
+
#
|
250
|
+
# Integer::
|
251
|
+
#
|
252
|
+
# If the value is an integer, it is interpreted as a zero-based page index and a fit to
|
253
|
+
# page (#create_fit_page) destination array is created and returned.
|
254
|
+
#
|
255
|
+
# Hash containing at least :type and :page::
|
256
|
+
#
|
257
|
+
# If the value is a hash, the :type key specifies the type of the destination that should
|
258
|
+
# be created and the :page key the target page. Which other keys are allowed depends on
|
259
|
+
# the destination type, so see the various create_XXX methods. Uses #create to do the job.
|
260
|
+
def use_or_create(value)
|
261
|
+
case value
|
262
|
+
when String
|
263
|
+
if self[value]
|
264
|
+
value
|
265
|
+
else
|
266
|
+
raise HexaPDF::Error, "Named destination '#{value}' doesn't exist"
|
267
|
+
end
|
268
|
+
when Array
|
269
|
+
raise HexaPDF::Error, "Invalid destination array" unless Destination.new(value).valid?
|
270
|
+
value
|
271
|
+
when HexaPDF::Dictionary
|
272
|
+
if value.type != :Page
|
273
|
+
raise HexaPDF::Error, "Invalid dictionary type '#{value.type}' given, needs to be a page"
|
274
|
+
end
|
275
|
+
create_fit_page(value)
|
276
|
+
when Integer
|
277
|
+
if value < 0 || value >= @document.pages.count
|
278
|
+
raise ArgumentError, "Page index #{value} out of bounds"
|
279
|
+
end
|
280
|
+
create_fit_page(@document.pages[value])
|
281
|
+
when Hash
|
282
|
+
type = value.delete(:type) { raise ArgumentError, "Missing keyword argument :type" }
|
283
|
+
page = value.delete(:page) { raise ArgumentError, "Missing keyword argument :page" }
|
284
|
+
create(type, page, **value)
|
285
|
+
else
|
286
|
+
raise ArgumentError, "Invalid argument type '#{value.class}'"
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
# :call-seq:
|
291
|
+
# destinations.create(type, page, **options) -> dest or name
|
292
|
+
#
|
293
|
+
# Creates a new destination array with the given +type+ (see Destination for all available
|
294
|
+
# type names; PDF internal type names are also allowed) and +page+ by calling the respective
|
295
|
+
# +create_type+ method.
|
296
|
+
def create(type, page, **options)
|
297
|
+
send("create_#{Destination::TYPE_MAPPING.fetch(type, type)}", page, **options)
|
298
|
+
end
|
299
|
+
|
211
300
|
# :call-seq:
|
212
301
|
# destinations.create_xyz(page, left: nil, top: nil, zoom: nil) -> dest
|
213
302
|
# destinations.create_xyz(page, name: nil, left: nil, top: nil, zoom: nil) -> name
|
data/lib/hexapdf/document.rb
CHANGED
@@ -494,6 +494,13 @@ module HexaPDF
|
|
494
494
|
catalog.acro_form(create: create)
|
495
495
|
end
|
496
496
|
|
497
|
+
# Returns the main document outline object.
|
498
|
+
#
|
499
|
+
# See HexaPDF::Type::Outline for details.
|
500
|
+
def outline
|
501
|
+
catalog.outline
|
502
|
+
end
|
503
|
+
|
497
504
|
# Executes the given task and returns its result.
|
498
505
|
#
|
499
506
|
# Tasks provide an extensible way for performing operations on a PDF document without
|
@@ -286,9 +286,12 @@ module HexaPDF
|
|
286
286
|
|
287
287
|
# Returns the encrypted version of the string that resides in the given indirect object.
|
288
288
|
#
|
289
|
+
# Note that some strings won't be encrypted as per the specification. The returned string,
|
290
|
+
# however, is always a different object.
|
291
|
+
#
|
289
292
|
# See: PDF1.7 s7.6.2
|
290
293
|
def encrypt_string(str, obj)
|
291
|
-
return str if str.empty? || obj == document.trailer[:Encrypt] || obj.type == :XRef ||
|
294
|
+
return str.dup if str.empty? || obj == document.trailer[:Encrypt] || obj.type == :XRef ||
|
292
295
|
(obj.type == :Sig && obj[:Contents].equal?(str))
|
293
296
|
|
294
297
|
key = object_key(obj.oid, obj.gen, string_algorithm)
|
data/lib/hexapdf/layout/style.rb
CHANGED
@@ -613,6 +613,24 @@ module HexaPDF
|
|
613
613
|
# composer.text("Default size")
|
614
614
|
# composer.text("Larger size", font_size: 20)
|
615
615
|
|
616
|
+
##
|
617
|
+
# :method: line_height
|
618
|
+
# :call-seq:
|
619
|
+
# line_height(size = nil)
|
620
|
+
#
|
621
|
+
# The font size used for line height calculations, default is +nil+ meaing it defaults to
|
622
|
+
# #font_size.
|
623
|
+
#
|
624
|
+
# This value should never be smaller than the font size since this would lead to overlapping
|
625
|
+
# text.
|
626
|
+
#
|
627
|
+
# Examples:
|
628
|
+
#
|
629
|
+
# #>pdf-composer100
|
630
|
+
# composer.text("Line 1")
|
631
|
+
# composer.text("Larger line height", line_height: 30)
|
632
|
+
# composer.text("Line 3")
|
633
|
+
|
616
634
|
##
|
617
635
|
# :method: character_spacing
|
618
636
|
# :call-seq:
|
@@ -1212,6 +1230,7 @@ module HexaPDF
|
|
1212
1230
|
[
|
1213
1231
|
[:font, "raise HexaPDF::Error, 'No font set'"],
|
1214
1232
|
[:font_size, 10],
|
1233
|
+
[:line_height, nil],
|
1215
1234
|
[:character_spacing, 0],
|
1216
1235
|
[:word_spacing, 0],
|
1217
1236
|
[:horizontal_scaling, 100],
|
@@ -1345,25 +1364,29 @@ module HexaPDF
|
|
1345
1364
|
# Returns the correct offset from the baseline for the underline.
|
1346
1365
|
def calculated_underline_position
|
1347
1366
|
calculated_text_rise +
|
1348
|
-
|
1349
|
-
font.
|
1367
|
+
font.wrapped_font.underline_position * font.scaling_factor *
|
1368
|
+
font.pdf_object.glyph_scaling_factor * calculated_font_size -
|
1369
|
+
calculated_underline_thickness / 2.0
|
1350
1370
|
end
|
1351
1371
|
|
1352
1372
|
# Returns the correct thickness for the underline.
|
1353
1373
|
def calculated_underline_thickness
|
1354
|
-
|
1374
|
+
font.wrapped_font.underline_thickness * font.scaling_factor *
|
1375
|
+
font.pdf_object.glyph_scaling_factor * calculated_font_size
|
1355
1376
|
end
|
1356
1377
|
|
1357
1378
|
# Returns the correct offset from the baseline for the strikeout line.
|
1358
1379
|
def calculated_strikeout_position
|
1359
1380
|
calculated_text_rise +
|
1360
|
-
|
1361
|
-
font.
|
1381
|
+
font.wrapped_font.strikeout_position * font.scaling_factor *
|
1382
|
+
font.pdf_object.glyph_scaling_factor * calculated_font_size -
|
1383
|
+
calculated_strikeout_thickness / 2.0
|
1362
1384
|
end
|
1363
1385
|
|
1364
1386
|
# Returns the correct thickness for the strikeout line.
|
1365
1387
|
def calculated_strikeout_thickness
|
1366
|
-
|
1388
|
+
font.wrapped_font.strikeout_thickness * font.scaling_factor *
|
1389
|
+
font.pdf_object.glyph_scaling_factor * calculated_font_size
|
1367
1390
|
end
|
1368
1391
|
|
1369
1392
|
# The font size scaled appropriately.
|
@@ -1389,22 +1412,28 @@ module HexaPDF
|
|
1389
1412
|
|
1390
1413
|
# The ascender of the font scaled appropriately.
|
1391
1414
|
def scaled_font_ascender
|
1392
|
-
@scaled_font_ascender ||= font.wrapped_font.ascender * font.scaling_factor *
|
1415
|
+
@scaled_font_ascender ||= font.wrapped_font.ascender * font.scaling_factor *
|
1416
|
+
font.pdf_object.glyph_scaling_factor * font_size
|
1393
1417
|
end
|
1394
1418
|
|
1395
1419
|
# The descender of the font scaled appropriately.
|
1396
1420
|
def scaled_font_descender
|
1397
|
-
@scaled_font_descender ||= font.wrapped_font.descender * font.scaling_factor *
|
1421
|
+
@scaled_font_descender ||= font.wrapped_font.descender * font.scaling_factor *
|
1422
|
+
font.pdf_object.glyph_scaling_factor * font_size
|
1398
1423
|
end
|
1399
1424
|
|
1400
|
-
# The minimum y-coordinate, calculated using the scaled descender of the font
|
1425
|
+
# The minimum y-coordinate, calculated using the scaled descender of the font and the line
|
1426
|
+
# height or font size.
|
1401
1427
|
def scaled_y_min
|
1402
|
-
@scaled_y_min ||= scaled_font_descender +
|
1428
|
+
@scaled_y_min ||= scaled_font_descender * (line_height || font_size) / font_size.to_f +
|
1429
|
+
calculated_text_rise
|
1403
1430
|
end
|
1404
1431
|
|
1405
|
-
# The maximum y-coordinate, calculated using the scaled
|
1432
|
+
# The maximum y-coordinate, calculated using the scaled ascender of the font and the line
|
1433
|
+
# height or font size.
|
1406
1434
|
def scaled_y_max
|
1407
|
-
@scaled_y_max ||= scaled_font_ascender +
|
1435
|
+
@scaled_y_max ||= scaled_font_ascender * (line_height || font_size) / font_size.to_f +
|
1436
|
+
calculated_text_rise
|
1408
1437
|
end
|
1409
1438
|
|
1410
1439
|
# Returns the width of the item scaled appropriately (by taking font size, characters spacing,
|
@@ -773,6 +773,11 @@ module HexaPDF
|
|
773
773
|
if line.items.empty? && line_fragments.empty?
|
774
774
|
# item didn't fit because no more height is available
|
775
775
|
next nil if actual_height + item.height > height
|
776
|
+
# item fits but is followed by penalty item that didn't fit
|
777
|
+
if item.width < width_block.call(item.item)
|
778
|
+
too_wide_box = item
|
779
|
+
next nil
|
780
|
+
end
|
776
781
|
|
777
782
|
old_height = actual_height
|
778
783
|
while item.width > width_block.call(item.item) && actual_height <= height
|
data/lib/hexapdf/revision.rb
CHANGED
@@ -59,6 +59,9 @@ module HexaPDF
|
|
59
59
|
# The callable object responsible for loading objects.
|
60
60
|
attr_accessor :loader
|
61
61
|
|
62
|
+
# The associated XRefSection object.
|
63
|
+
attr_reader :xref_section
|
64
|
+
|
62
65
|
# :call-seq:
|
63
66
|
# Revision.new(trailer) -> revision
|
64
67
|
# Revision.new(trailer, xref_section: section, loader: loader) -> revision
|
data/lib/hexapdf/revisions.rb
CHANGED
@@ -83,18 +83,28 @@ module HexaPDF
|
|
83
83
|
|
84
84
|
stm = trailer[:XRefStm]
|
85
85
|
if stm && !seen_xref_offsets.key?(stm)
|
86
|
+
if xref_section.max_oid == 0 && trailer[:Prev] > stm
|
87
|
+
# Revision is completely empty, with xref stream in previous revision
|
88
|
+
merge_revision = trailer[:Prev]
|
89
|
+
end
|
86
90
|
stm_xref_section, = parser.load_revision(stm)
|
87
91
|
stm_xref_section.merge!(xref_section)
|
88
92
|
xref_section = stm_xref_section
|
89
93
|
seen_xref_offsets[stm] = true
|
90
94
|
end
|
91
95
|
|
96
|
+
if merge_revision == offset
|
97
|
+
xref_section.merge!(revisions.first.xref_section)
|
98
|
+
trailer = revisions.first.trailer
|
99
|
+
revisions.shift
|
100
|
+
end
|
101
|
+
|
92
102
|
revisions.unshift(Revision.new(document.wrap(trailer, type: :XXTrailer),
|
93
103
|
xref_section: xref_section, loader: object_loader))
|
94
104
|
offset = trailer[:Prev]
|
95
105
|
end
|
96
106
|
rescue HexaPDF::MalformedPDFError
|
97
|
-
reconstructed_revision = parser.reconstructed_revision
|
107
|
+
raise unless (reconstructed_revision = parser.reconstructed_revision)
|
98
108
|
unless revisions.empty?
|
99
109
|
reconstructed_revision.trailer.data.value = revisions.last.trailer.data.value
|
100
110
|
end
|
@@ -83,9 +83,10 @@ module HexaPDF
|
|
83
83
|
define_field :DA, type: String
|
84
84
|
define_field :XFA, type: [Stream, PDFArray], version: '1.5'
|
85
85
|
|
86
|
-
bit_field(:
|
86
|
+
bit_field(:signature_flags, {signatures_exist: 0, append_only: 1},
|
87
87
|
lister: "signature_flags", getter: "signature_flag?", setter: "signature_flag",
|
88
|
-
unsetter: "signature_unflag"
|
88
|
+
unsetter: "signature_unflag", value_getter: "self[:SigFlags]",
|
89
|
+
value_setter: "self[:SigFlags]")
|
89
90
|
|
90
91
|
# Returns the PDFArray containing the root fields.
|
91
92
|
def root_fields
|
@@ -411,16 +412,6 @@ module HexaPDF
|
|
411
412
|
|
412
413
|
private
|
413
414
|
|
414
|
-
# Helper method for bit field getter access.
|
415
|
-
def raw_signature_flags
|
416
|
-
self[:SigFlags]
|
417
|
-
end
|
418
|
-
|
419
|
-
# Helper method for bit field setter access.
|
420
|
-
def raw_signature_flags=(value)
|
421
|
-
self[:SigFlags] = value
|
422
|
-
end
|
423
|
-
|
424
415
|
# Creates a new field with the full name +name+ and the field type +type+.
|
425
416
|
def create_field(name, type)
|
426
417
|
parent_name, _, name = name.rpartition('.')
|
@@ -132,10 +132,11 @@ module HexaPDF
|
|
132
132
|
define_field :StructParent, type: Integer, version: '1.3'
|
133
133
|
define_field :OC, type: Dictionary, version: '1.5'
|
134
134
|
|
135
|
-
bit_field(:
|
136
|
-
|
137
|
-
|
138
|
-
lister: "flags", getter: "flagged?", setter: "flag", unsetter: "unflag"
|
135
|
+
bit_field(:flags, {invisible: 0, hidden: 1, print: 2, no_zoom: 3, no_rotate: 4,
|
136
|
+
no_view: 5, read_only: 6, locked: 7, toggle_no_view: 8,
|
137
|
+
locked_contents: 9},
|
138
|
+
lister: "flags", getter: "flagged?", setter: "flag", unsetter: "unflag",
|
139
|
+
value_getter: "self[:F]", value_setter: "self[:F]")
|
139
140
|
|
140
141
|
# Returns +true+ because annotation objects must always be indirect objects.
|
141
142
|
def must_be_indirect?
|
@@ -182,18 +183,6 @@ module HexaPDF
|
|
182
183
|
xobject
|
183
184
|
end
|
184
185
|
|
185
|
-
private
|
186
|
-
|
187
|
-
# Helper method for bit field getter access.
|
188
|
-
def raw_flags
|
189
|
-
self[:F]
|
190
|
-
end
|
191
|
-
|
192
|
-
# Helper method for bit field setter access.
|
193
|
-
def raw_flags=(value)
|
194
|
-
self[:F] = value
|
195
|
-
end
|
196
|
-
|
197
186
|
end
|
198
187
|
|
199
188
|
end
|
data/lib/hexapdf/type/catalog.rb
CHANGED
@@ -105,6 +105,13 @@ module HexaPDF
|
|
105
105
|
self[:Names] ||= document.add({}, type: :XXNames)
|
106
106
|
end
|
107
107
|
|
108
|
+
# Returns the document outline, creating it if needed.
|
109
|
+
#
|
110
|
+
# See: Outline
|
111
|
+
def outline
|
112
|
+
self[:Outlines] ||= document.add({}, type: :Outlines)
|
113
|
+
end
|
114
|
+
|
108
115
|
# Returns the main AcroForm object.
|
109
116
|
#
|
110
117
|
# * If an AcroForm object exists, the +create+ argument is not used.
|
@@ -81,22 +81,13 @@ module HexaPDF
|
|
81
81
|
define_field :FD, type: Dictionary
|
82
82
|
define_field :CIDSet, type: Stream
|
83
83
|
|
84
|
-
bit_field(:
|
85
|
-
|
86
|
-
lister: "flags", getter: "flagged?", setter: "flag", unsetter: 'unflag'
|
84
|
+
bit_field(:flags, {fixed_pitch: 0, serif: 1, symbolic: 2, script: 3, nonsymbolic: 5,
|
85
|
+
italic: 6, all_cap: 16, small_cap: 17, force_bold: 18},
|
86
|
+
lister: "flags", getter: "flagged?", setter: "flag", unsetter: 'unflag',
|
87
|
+
value_getter: "self[:Flags]", value_setter: "self[:Flags]")
|
87
88
|
|
88
89
|
private
|
89
90
|
|
90
|
-
# Helper method for bit field getter access.
|
91
|
-
def raw_flags
|
92
|
-
self[:Flags]
|
93
|
-
end
|
94
|
-
|
95
|
-
# Helper method for bit field setter access.
|
96
|
-
def raw_flags=(value)
|
97
|
-
self[:Flags] = value
|
98
|
-
end
|
99
|
-
|
100
91
|
ALLOWED_FONT_WEIGHTS = [100, 200, 300, 400, 500, 600, 700, 800, 900] #:nodoc:
|
101
92
|
|
102
93
|
def perform_validation #:nodoc:
|
@@ -111,10 +111,11 @@ module HexaPDF
|
|
111
111
|
# The object references are also added to this object stream so that they are included when
|
112
112
|
# the object gets written.
|
113
113
|
def parse_stream
|
114
|
+
return @stream_data if defined?(@stream_data)
|
114
115
|
data = stream
|
115
116
|
oids, offsets = parse_oids_and_offsets(data)
|
116
117
|
oids.each {|oid| add_object(Reference.new(oid, 0)) }
|
117
|
-
Data.new(data, oids, offsets)
|
118
|
+
@stream_data = Data.new(data, oids, offsets)
|
118
119
|
end
|
119
120
|
|
120
121
|
# Adds the given object to the list of objects that should be stored in this object stream.
|
@@ -202,6 +203,13 @@ module HexaPDF
|
|
202
203
|
|
203
204
|
private
|
204
205
|
|
206
|
+
# Parses the stream data after the object is first initialized. Since the parsed stream data
|
207
|
+
# is cached, it is only parsed on initialization and not again if e.g. the stream is changed.
|
208
|
+
def after_data_change
|
209
|
+
super
|
210
|
+
parse_stream
|
211
|
+
end
|
212
|
+
|
205
213
|
# Parses the object numbers and their offsets from the start of the stream data.
|
206
214
|
def parse_oids_and_offsets(data)
|
207
215
|
oids = []
|