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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d651940209b634c4f8c95924479f46dca2b49a458b731cd6bfba5ad95151c70d
4
- data.tar.gz: aebe4ea9b21414b68483557f0f4cda0ea02ba17ae39bd58166c0864890c56691
3
+ metadata.gz: 0f8bf550cff0d50567439c9942c97d6fc32e9fa9edab3bbff05ce854e22b0cc3
4
+ data.tar.gz: bab6243ec81278f278589fc90768dd7e2c6c73c0c314bd8ce0f17350f2d6cd73
5
5
  SHA512:
6
- metadata.gz: 6d5fc26a440b93e140ae1cf761b969c9cb8c7b708db52674b3b8f785babfd4552f6682b536cae682570309d3f4b40fc8d26e8a9c933de271b7cd347a49334c07
7
- data.tar.gz: 61b7303d84de7bd0851e6f60de62e8e25a5c772ad97837c2ab03962f220a06caae03cb05c668b233eb8fc0273e3232a9be6965d31ad9add1bf6c9ee979cb828e
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. In
4
- short, it allows
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
- HexaPDF was designed with ease of use and performance in mind. It uses lazy loading and lazy
14
- computing when possible and tries to produce small PDF files by default.
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]: http://hexapdf.gettalong.org
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
- Such functionality will be incorporated into HexaPDF in the near future. The main functionality for
78
- providing such a feature is already available in HexaPDF (the [page canvas API]). Additionally,
79
- laying out text inside a box with line wrapping and such is also supported. What's missing (and this
80
- is still quite a big chunk) is support for advanced features like tables, page breaking and so on.
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
- So why use HexaPDF?
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 will provide a high level layer for **composing a document of individual elements** that
88
- are automatically layouted. Such elements can be headers, paragraphs, code blocks, ... or links,
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 <http://hexapdf.gettalong.org/contributing.html> for more information.
175
+ See <https://hexapdf.gettalong.org/contributing.html> for more information.
127
176
 
128
177
 
129
178
  ## Author
130
179
 
131
- Thomas Leitner, <http://gettalong.org>
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
@@ -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)
@@ -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
- calculated_font_size / 1000.0 * font.wrapped_font.underline_position *
1349
- font.scaling_factor - calculated_underline_thickness / 2.0
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
- calculated_font_size / 1000.0 * font.wrapped_font.underline_thickness * font.scaling_factor
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
- calculated_font_size / 1000.0 * font.wrapped_font.strikeout_position *
1361
- font.scaling_factor - calculated_strikeout_thickness / 2.0
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
- calculated_font_size / 1000.0 * font.wrapped_font.strikeout_thickness * font.scaling_factor
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 * font_size / 1000
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 * font_size / 1000
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 + calculated_text_rise
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 descender of the font.
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 + calculated_text_rise
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
@@ -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
@@ -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(:raw_signature_flags, {signatures_exist: 0, append_only: 1},
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(:raw_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")
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
@@ -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(:raw_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')
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 = []