hexapdf 0.24.2 → 0.25.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 +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 = []
|