hexapdf 0.33.0 → 0.34.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +42 -1
  3. data/examples/026-optional_content.rb +55 -0
  4. data/examples/027-composer_optional_content.rb +83 -0
  5. data/lib/hexapdf/cli/command.rb +7 -1
  6. data/lib/hexapdf/cli/fonts.rb +1 -1
  7. data/lib/hexapdf/cli/inspect.rb +2 -4
  8. data/lib/hexapdf/composer.rb +2 -1
  9. data/lib/hexapdf/configuration.rb +21 -1
  10. data/lib/hexapdf/content/canvas.rb +52 -0
  11. data/lib/hexapdf/content/operator.rb +2 -0
  12. data/lib/hexapdf/dictionary.rb +1 -0
  13. data/lib/hexapdf/dictionary_fields.rb +1 -2
  14. data/lib/hexapdf/digital_signature/verification_result.rb +1 -2
  15. data/lib/hexapdf/document/layout.rb +3 -0
  16. data/lib/hexapdf/document/pages.rb +1 -1
  17. data/lib/hexapdf/document.rb +7 -0
  18. data/lib/hexapdf/encryption/ruby_aes.rb +10 -20
  19. data/lib/hexapdf/layout/box.rb +23 -3
  20. data/lib/hexapdf/layout/column_box.rb +2 -1
  21. data/lib/hexapdf/layout/frame.rb +23 -6
  22. data/lib/hexapdf/layout/inline_box.rb +20 -9
  23. data/lib/hexapdf/layout/list_box.rb +34 -20
  24. data/lib/hexapdf/layout/page_style.rb +2 -1
  25. data/lib/hexapdf/layout/style.rb +46 -6
  26. data/lib/hexapdf/layout/table_box.rb +9 -7
  27. data/lib/hexapdf/layout/text_box.rb +9 -2
  28. data/lib/hexapdf/layout/text_fragment.rb +28 -2
  29. data/lib/hexapdf/layout/text_layouter.rb +21 -5
  30. data/lib/hexapdf/stream.rb +1 -2
  31. data/lib/hexapdf/type/actions/set_ocg_state.rb +86 -0
  32. data/lib/hexapdf/type/actions.rb +1 -0
  33. data/lib/hexapdf/type/annotations/text.rb +1 -2
  34. data/lib/hexapdf/type/catalog.rb +10 -1
  35. data/lib/hexapdf/type/cid_font.rb +15 -1
  36. data/lib/hexapdf/type/form.rb +75 -5
  37. data/lib/hexapdf/type/optional_content_configuration.rb +170 -0
  38. data/lib/hexapdf/type/optional_content_group.rb +370 -0
  39. data/lib/hexapdf/type/optional_content_membership.rb +63 -0
  40. data/lib/hexapdf/type/optional_content_properties.rb +158 -0
  41. data/lib/hexapdf/type/page.rb +27 -11
  42. data/lib/hexapdf/type/page_label.rb +4 -8
  43. data/lib/hexapdf/type.rb +4 -0
  44. data/lib/hexapdf/utils/pdf_doc_encoding.rb +0 -1
  45. data/lib/hexapdf/version.rb +1 -1
  46. data/test/hexapdf/content/test_canvas.rb +49 -0
  47. data/test/hexapdf/document/test_layout.rb +7 -2
  48. data/test/hexapdf/document/test_pages.rb +6 -6
  49. data/test/hexapdf/layout/test_box.rb +13 -4
  50. data/test/hexapdf/layout/test_frame.rb +13 -1
  51. data/test/hexapdf/layout/test_inline_box.rb +17 -8
  52. data/test/hexapdf/layout/test_list_box.rb +48 -31
  53. data/test/hexapdf/layout/test_style.rb +10 -0
  54. data/test/hexapdf/layout/test_table_box.rb +32 -26
  55. data/test/hexapdf/layout/test_text_box.rb +8 -0
  56. data/test/hexapdf/layout/test_text_fragment.rb +33 -0
  57. data/test/hexapdf/layout/test_text_layouter.rb +32 -5
  58. data/test/hexapdf/test_composer.rb +10 -0
  59. data/test/hexapdf/test_dictionary.rb +10 -0
  60. data/test/hexapdf/test_document.rb +4 -0
  61. data/test/hexapdf/test_writer.rb +3 -3
  62. data/test/hexapdf/type/actions/test_set_ocg_state.rb +40 -0
  63. data/test/hexapdf/type/test_catalog.rb +11 -0
  64. data/test/hexapdf/type/test_form.rb +119 -0
  65. data/test/hexapdf/type/test_optional_content_configuration.rb +112 -0
  66. data/test/hexapdf/type/test_optional_content_group.rb +158 -0
  67. data/test/hexapdf/type/test_optional_content_properties.rb +109 -0
  68. data/test/hexapdf/type/test_page.rb +2 -2
  69. metadata +14 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 689e6d8b70951307055c635c744a85bcade50f7065af32b35bd5fd15230cdf4e
4
- data.tar.gz: a4186126b30e18ad629db5e176be38f81b7367c72c369d8e715a1e952ad49298
3
+ metadata.gz: 00aaef323df26b1a17c6e4ee39787c92037e8ae887713b28c3674962fcb6ac26
4
+ data.tar.gz: a46ecda2514bc5c044d115370f66402fc22ad3f8c8dfa1741e057b6849a6b952
5
5
  SHA512:
6
- metadata.gz: 05ad5c0f6b7819499ef0f013d28c7ee821f94cdb02f3487d27bd91089c5238c3b5b91d25ed8be0dc9b548a1e3daae17dea96171f38522587afe6b0aed47d8585
7
- data.tar.gz: 51119d2f079f827271378fc9a36310ff3abcda221113096cae0fe4abee42bf2421b6ac73232fe586d9cfcf0045b5f238bd8ff69491d6f6329decc77bba0285ff
6
+ metadata.gz: a15e468bdc852fd4c16286ba0e4829fcd3fb3be64aa48c429246216fbfe1c856cb8e155bfefa83c47b99246e7161f20a1de54769aa222dbcdfdb25f8be777643
7
+ data.tar.gz: d75048ff9d77601d93893cceb0c73673410695e3ea381e17d27de8c92d7de3d29d2ed467e44df6362dd28d3ee374a8e5710e5f1323393ee2e4104cdcecef1112
data/CHANGELOG.md CHANGED
@@ -1,3 +1,44 @@
1
+ ## 0.34.0 - 2023-10-22
2
+
3
+ ### Added
4
+
5
+ * Support for optional content groups (layers)
6
+ * Support for reference XObjects
7
+ * Basic support for group XObjects
8
+ * [HexaPDF::Layout::Style#fill_horizontal] for allowing a text fragment to fill
9
+ the remaining space of line
10
+ * [HexaPDF::Layout::TextFragment#text] and [HexaPDF::Layout::TextBox#text] for
11
+ retrieving the text represented by the stored items
12
+ * [HexaPDF::Content::Canvas#pos] for retrieving untransformed positions
13
+ * [HexaPDF::Type::CIDFont::CIDSystemInfo] type class
14
+
15
+ ### Changed
16
+
17
+ * [HexaPDF::Composer#draw_box] to return the last drawn box
18
+ * [HexaPDF::Layout::Style::LinkLayer] to support arbitrary actions
19
+ * [HexaPDF::Layout::Frame::new] (and adapted other layout classes) to accept a
20
+ context argument (a page or Form XObject instance)
21
+ * [HexaPDF::Layout::ListBox] to use its 'fill_color' style property for the item
22
+ marker color
23
+ * [HexaPDF::Layout::Frame::FitResult#draw] to use optional content groups for
24
+ debug output
25
+
26
+ ### Fixed
27
+
28
+ * [HexaPDF::Document::Pages#add_labelling_range] to add a correct entry for
29
+ the default range starting at page 1
30
+ * [HexaPDF::Type::Page#flatten_annotations] to correctly handle scaled
31
+ appearances
32
+ * Using an unknown style name in [HexaPDF:Document::Layout] method by providing
33
+ a useful error message
34
+ * [HexaPDF::Layout::Box::new] to ensure that the properties attribute is always
35
+ a hash
36
+ * [HexaPDF::Layout::ListBox] to work correctly if the marker height is larger
37
+ than the item content height
38
+ * [HexaPDF::Dictionary] setting default values on wrong classes in certain
39
+ situations
40
+
41
+
1
42
  ## 0.33.0 - 2023-08-02
2
43
 
3
44
  ### Added
@@ -39,7 +80,7 @@
39
80
 
40
81
  ### Fixed
41
82
 
42
- **Breaking change**: [HexaPDF::Object::make_direct] now needs the document
83
+ * **Breaking change**: [HexaPDF::Object::make_direct] now needs the document
43
84
  instance as second argument to correctly resolve references
44
85
  * [HexaPDF::Layout::ColumnBox], [HexaPDF::Layout::ListBox] and
45
86
  [HexaPDF::Layout::ImageBox] to correctly respond to `#empty?`
@@ -0,0 +1,55 @@
1
+ # # Optional Content (a.k.a. Layers)
2
+ #
3
+ # This example shows how to create and assign optional content groups (OCGs) to
4
+ # parts of the content of a page.
5
+ #
6
+ # Four OCGs are created: Squares, Black, Blue, and Orange. The Squares one is
7
+ # applied to everything, the others to the respectively colored squares.
8
+ #
9
+ # When viewed in a compatible viewer, the "Optional Content" or "Layers" panel
10
+ # can be used to switch the layers on and off, resulting in the respective
11
+ # squares appearing or disappearing. Initially, the blue one is not shown, only
12
+ # the black and orange ones.
13
+ #
14
+ # Additionally, if supported by a viewer and if the visibility hasn't been
15
+ # manually changed, the OCGs for the squares are also configured to only be
16
+ # visible at certain zoom levels. For example, the black one is only visible up
17
+ # to a zoom level of 100%.
18
+ #
19
+ # Usage:
20
+ # : `ruby optional_content.rb`
21
+ #
22
+ require 'hexapdf'
23
+
24
+ doc = HexaPDF::Document.new
25
+
26
+ ocg = doc.optional_content.ocg('Squares')
27
+ ocg1 = doc.optional_content.ocg('Black')
28
+ ocg1.zoom(max: 1)
29
+ ocg1.add_to_ui(path: ocg)
30
+ ocg2 = doc.optional_content.ocg('Blue')
31
+ ocg2.zoom(min: 1, max: 2)
32
+ ocg2.add_to_ui(path: ocg)
33
+ ocg2.off!
34
+ ocg3 = doc.optional_content.ocg('Orange')
35
+ ocg3.zoom(min: 2, max: 20)
36
+ ocg3.add_to_ui(path: ocg)
37
+
38
+ canvas = doc.pages.add([0, 0, 200, 200]).canvas
39
+ canvas.optional_content(ocg) do
40
+ canvas.optional_content(ocg1) do
41
+ canvas.fill_color('black').rectangle(20, 80, 100, 100).fill
42
+ end
43
+ canvas.optional_content(ocg2) do
44
+ canvas.fill_color('hp-blue').rectangle(50, 50, 100, 100).fill
45
+ end
46
+ canvas.optional_content(ocg3) do
47
+ canvas.fill_color('hp-orange').rectangle(80, 20, 100, 100).fill
48
+ end
49
+ end
50
+
51
+ doc.optional_content.default_configuration[:AS] = [
52
+ {Event: :View, Category: [:Zoom], OCGs: [ocg1, ocg2, ocg3]}
53
+ ]
54
+
55
+ doc.write('optional_content.pdf')
@@ -0,0 +1,83 @@
1
+ # # Composer - Optional Content
2
+ #
3
+ # This example shows how to use the optional content feature to create a quiz
4
+ # where the answers can be individually shown and hidden. There is also a link
5
+ # after the questions to toggle all answers.
6
+ #
7
+ # Note: To provide the "All answers" layer switch functionality we need to make
8
+ # use of optional content membership dictionaries. However, this PDF feature is
9
+ # not supported by all PDF viewers. To enable the "All answers" switch in this
10
+ # example, use `a1m`, `a2m`, and `a3m` instead of `a1`, `a2`, and `a3` when
11
+ # defining the optional content for a box.
12
+ #
13
+ # Usage:
14
+ # : `ruby composer_optional_content.rb`
15
+ #
16
+ require 'hexapdf'
17
+
18
+ HexaPDF::Composer.create('composer_optional_content.pdf') do |composer|
19
+ composer.style(:question, font_size: 16, margin: [0, 0, 16], fill_color: 'hp-blue')
20
+ composer.style(:answer, font: 'ZapfDingbats', fill_color: "green")
21
+
22
+ all = composer.document.optional_content.ocg('All answers')
23
+ a1 = composer.document.optional_content.ocg('Answer 1')
24
+ a1m = composer.document.optional_content.create_ocmd([a1, all], policy: :any_on)
25
+ a2 = composer.document.optional_content.ocg('Answer 2')
26
+ a2m = composer.document.optional_content.create_ocmd([a2, all], policy: :any_on)
27
+ a3 = composer.document.optional_content.ocg('Answer 3')
28
+ a3m = composer.document.optional_content.create_ocmd([a3, all], policy: :any_on)
29
+
30
+ composer.text('The Great Ruby Quiz', align: :center, margin: [0, 0, 24],
31
+ font: ['Helvetica', variant: :bold], font_size: 24)
32
+
33
+ composer.list(item_type: :decimal, item_spacing: 32, style: :question) do |listing|
34
+ listing.multiple do |item|
35
+ item.text('Who created Ruby?', style: :question)
36
+ item.column(columns: 3, gaps: 5) do |cols|
37
+ cols.list(item_type: :decimal) do |answers|
38
+ answers.text('Guido van Rossum')
39
+ answers.multiple do |answer|
40
+ answer.text('Yukihiro “Matz” Matsumoto', position: :float)
41
+ answer.text("\u{a0}\u{a0}4", style: :answer,
42
+ properties: {'optional_content' => a1})
43
+ end
44
+ answers.text('Rob Pike')
45
+ end
46
+ end
47
+ end
48
+
49
+ listing.multiple do |item|
50
+ item.text('When was Ruby created?', style: :question)
51
+ item.column(columns: 3, gaps: 5) do |cols|
52
+ cols.list(item_type: :decimal) do |answers|
53
+ answers.text('1991')
54
+ answers.text('1992')
55
+ answers.multiple do |answer|
56
+ answer.text('1993', position: :float)
57
+ answer.text("\u{a0}\u{a0}4", style: :answer,
58
+ properties: {'optional_content' => a2})
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ listing.multiple do |item|
65
+ item.text('What is the best PDF library for Ruby?', style: :question)
66
+ answer = composer.document.layout.text('There are several PDF libraries for ' \
67
+ 'Ruby but the best is HexaPDF! :)',
68
+ width: 400,
69
+ properties: {'optional_content' => a3})
70
+ item.formatted_text([{box: answer}], border: {width: [0, 0, 1]})
71
+ end
72
+ end
73
+
74
+ action = composer.document.wrap({Type: :Action, S: :SetOCGState})
75
+ action.add_state_change(:toggle, [a1, a2, a3])
76
+ composer.text("Click to toggle answers", border: {width: 1, color: "red"},
77
+ position_hint: :right, padding: 2, overlays: [[:link, action: action]])
78
+
79
+ composer.document.optional_content.default_configuration(
80
+ BaseState: :OFF,
81
+ Order: [all, a1, a2, a3],
82
+ )
83
+ end
@@ -53,9 +53,15 @@ module HexaPDF
53
53
  module Extensions #:nodoc:
54
54
  def help_banner #:nodoc:
55
55
  "hexapdf #{HexaPDF::VERSION} - Versatile PDF Manipulation Tool\n" \
56
- "Copyright (c) 2014-2021 Thomas Leitner; licensed under the AGPLv3\n\n" \
56
+ "Copyright (c) 2014-2023 Thomas Leitner; licensed under the AGPLv3\n\n" \
57
57
  "#{format(usage, indent: 7)}\n\n"
58
58
  end
59
+
60
+ def help #:nodoc:
61
+ super << format("See https://hexapdf.gettalong.org/documentation/hexapdf.1.html " \
62
+ "for the full manual page with examples.", indent: 0)
63
+ end
64
+
59
65
  end
60
66
 
61
67
  include Extensions
@@ -127,7 +127,7 @@ module HexaPDF
127
127
  page.resources[:Font]&.each(&font_proc)
128
128
  page.resources[:XObject]&.each do |_, xobj|
129
129
  next unless xobj[:Subtype] == :Form
130
- xobj.ressources[:Font]&.each(&font_proc)
130
+ xobj.resources[:Font]&.each(&font_proc)
131
131
  end
132
132
  page.each_annotation do |annotation|
133
133
  appearance = annotation.appearance
@@ -93,11 +93,9 @@ module HexaPDF
93
93
 
94
94
  private
95
95
 
96
- # :nodoc:
97
- COMMAND_LIST = %w[object recursive stream raw-stream xref catalog trailer pages
96
+ COMMAND_LIST = %w[object recursive stream raw-stream xref catalog trailer pages # :nodoc:
98
97
  page-count search quit help]
99
- # :nodoc:
100
- RELINE_COMPLETION_PROC = proc do |s|
98
+ RELINE_COMPLETION_PROC = proc do |s| # :nodoc:
101
99
  if s.empty?
102
100
  COMMAND_DESCRIPTIONS.map {|cmd, desc| cmd.ljust(35) << desc }
103
101
  else
@@ -401,7 +401,7 @@ module HexaPDF
401
401
  @document.layout.box_creation_method?(name) || super
402
402
  end
403
403
 
404
- # Draws the given HexaPDF::Layout::Box.
404
+ # Draws the given HexaPDF::Layout::Box and returns the last drawn box.
405
405
  #
406
406
  # The box is drawn into the current frame if possible. If it doesn't fit, the box is split. If
407
407
  # it still doesn't fit, a new region of the frame is determined and then the process starts
@@ -433,6 +433,7 @@ module HexaPDF
433
433
  end
434
434
  end
435
435
  end
436
+ box
436
437
  end
437
438
 
438
439
  # Creates a stamp (Form XObject) which can be used like an image multiple times on a single page
@@ -268,7 +268,8 @@ module HexaPDF
268
268
  #
269
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
- # be used instead.
271
+ # be used instead. This substitute glyph needs to be from the same font, i.e. it needs to be
272
+ # created through the provided +font_wrapper+ instance.
272
273
  #
273
274
  # The +font_wrapper+ argument is the used font wrapper object, e.g.
274
275
  # HexaPDF::Font::TrueTypeWrapper. To access the HexaPDF::Document instance from which this hook
@@ -609,6 +610,23 @@ module HexaPDF
609
610
  XXOutlineItem: 'HexaPDF::Type::OutlineItem',
610
611
  PageLabel: 'HexaPDF::Type::PageLabel',
611
612
  XXMarkInformation: 'HexaPDF::Type::MarkInformation',
613
+ OCG: 'HexaPDF::Type::OptionalContentGroup',
614
+ OCMD: 'HexaPDF::Type::OptionalContentMembership',
615
+ XXOCUsage: 'HexaPDF::Type::OptionalContentGroup::OptionalContentUsage',
616
+ XXOCUsageCreatorInfo: 'HexaPDF::Type::OptionalContentGroup::OptionalContentUsage::CreatorInfo',
617
+ XXOCUsageLanguage: 'HexaPDF::Type::OptionalContentGroup::OptionalContentUsage::Language',
618
+ XXOCUsageExport: 'HexaPDF::Type::OptionalContentGroup::OptionalContentUsage::Export',
619
+ XXOCUsageZoom: 'HexaPDF::Type::OptionalContentGroup::OptionalContentUsage::Zoom',
620
+ XXOCUsagePrint: 'HexaPDF::Type::OptionalContentGroup::OptionalContentUsage::Print',
621
+ XXOCUsageView: 'HexaPDF::Type::OptionalContentGroup::OptionalContentUsage::View',
622
+ XXOCUsageUser: 'HexaPDF::Type::OptionalContentGroup::OptionalContentUsage::User',
623
+ XXOCUsagePageElement: 'HexaPDF::Type::OptionalContentGroup::OptionalContentUsage::PageElement',
624
+ XXOCProperties: 'HexaPDF::Type::OptionalContentProperties',
625
+ XXOCConfiguration: 'HexaPDF::Type::OptionalContentConfiguration',
626
+ XXOCUsageApplication: 'HexaPDF::Type::OptionalContentConfiguration::UsageApplication',
627
+ XXReference: 'HexaPDF::Type::Form::Reference',
628
+ XXCIDSystemInfo: 'HexaPDF::Type::CIDFont::CIDSystemInfo',
629
+ Group: 'HexaPDF::Type::Form::Group',
612
630
  },
613
631
  'object.subtype_map' => {
614
632
  nil => {
@@ -623,6 +641,7 @@ module HexaPDF
623
641
  GoToR: 'HexaPDF::Type::Actions::GoToR',
624
642
  Launch: 'HexaPDF::Type::Actions::Launch',
625
643
  URI: 'HexaPDF::Type::Actions::URI',
644
+ SetOCGState: 'HexaPDF::Type::Actions::SetOCGState',
626
645
  Text: 'HexaPDF::Type::Annotations::Text',
627
646
  Link: 'HexaPDF::Type::Annotations::Link',
628
647
  Widget: 'HexaPDF::Type::Annotations::Widget',
@@ -644,6 +663,7 @@ module HexaPDF
644
663
  GoToR: 'HexaPDF::Type::Actions::GoToR',
645
664
  Launch: 'HexaPDF::Type::Actions::Launch',
646
665
  URI: 'HexaPDF::Type::Actions::URI',
666
+ SetOCGState: 'HexaPDF::Type::Actions::SetOCGState',
647
667
  },
648
668
  Annot: {
649
669
  Text: 'HexaPDF::Type::Annotations::Text',
@@ -317,6 +317,14 @@ module HexaPDF
317
317
  @context.resources
318
318
  end
319
319
 
320
+ # Returns the position (x,y) transformed by the current transformation matrix.
321
+ #
322
+ # The resulting position should be interpreted in terms of the coordinate system of the
323
+ # context object (e.g. the page or Form XObject).
324
+ def pos(x, y)
325
+ graphics_state.ctm.evaluate(x, y)
326
+ end
327
+
320
328
  # :call-seq:
321
329
  # canvas.save_graphics_state => canvas
322
330
  # canvas.save_graphics_state { block } => canvas
@@ -2483,6 +2491,50 @@ module HexaPDF
2483
2491
  self
2484
2492
  end
2485
2493
 
2494
+ # :call-seq:
2495
+ # canvas.optional_content(ocg, &block) -> canvas
2496
+ # canvas.optional_content(name, use_existing_ocg: true, &block) -> canvas
2497
+ #
2498
+ # Inserts an optional content sequence. Returns +self+.
2499
+ #
2500
+ # An optional content sequence marks part of the content stream as belonging to the given
2501
+ # optional content group. See HexaPDF::Type::OptionalContentProperties for details.
2502
+ #
2503
+ # If the first argument is already an optional content group dictionary, it is used.
2504
+ # Otherwise, the first argument needs to be the name of the optional content group. In that
2505
+ # case, the +use_existing_ocg+ specifies whether the first found optional content group with
2506
+ # that name should be used or whether a new OCG should always be created.
2507
+ #
2508
+ # If invoked without a block, a corresponding call to #end_optional_content must be done.
2509
+ # Otherwise the optional content sequence automatically ends when the block is finished.
2510
+ #
2511
+ # Examples:
2512
+ #
2513
+ # canvas.optional_content('Hints')
2514
+ # # Other instructions
2515
+ # canvas.end_optional_content
2516
+ #
2517
+ # canvas.optional_content('Hints', use_existing_ocg: false) do
2518
+ # # Other instructions
2519
+ # end
2520
+ #
2521
+ # See: PDF2.0 s8.11, #end_optional_content, HexaPDF::Type::OptionalContentProperties
2522
+ def optional_content(ocg, use_existing_ocg: true, &block)
2523
+ ocg = if ocg.kind_of?(HexaPDF::Dictionary) || !use_existing_ocg
2524
+ context.document.optional_content.add_ocg(ocg)
2525
+ else
2526
+ context.document.optional_content.ocg(ocg, create: true)
2527
+ end
2528
+ marked_content_sequence(:OC, property_list: ocg, &block)
2529
+ end
2530
+
2531
+ # Ends an optional content sequence and returns +self+.
2532
+ #
2533
+ # See #optional_content for details.
2534
+ #
2535
+ # See: PDF2.0 s8.11
2536
+ alias :end_optional_content :end_marked_content_sequence
2537
+
2486
2538
  # Creates and returns a color object from the given color specification. See #stroke_color for
2487
2539
  # details on the possible color specifications.
2488
2540
  #
@@ -1001,6 +1001,8 @@ module HexaPDF
1001
1001
 
1002
1002
  # Mapping of operator names to their default operator implementations.
1003
1003
  DEFAULT_OPERATORS = {
1004
+ BX: NoArgumentOperator.new('BX'),
1005
+ EX: NoArgumentOperator.new('EX'),
1004
1006
  q: SaveGraphicsState.new,
1005
1007
  Q: RestoreGraphicsState.new,
1006
1008
  cm: ConcatenateMatrix.new,
@@ -249,6 +249,7 @@ module HexaPDF
249
249
  # Sets all required fields that have no current value but a default value to their respective
250
250
  # default value.
251
251
  def set_required_fields_with_defaults
252
+ return if (type = value[:Type]) && self.class.type != type
252
253
  self.class.each_field do |name, field|
253
254
  if !key?(name) && field.required? && field.default?
254
255
  value[name] = field.default
@@ -313,8 +313,7 @@ module HexaPDF
313
313
  [String, Time, Date, DateTime]
314
314
  end
315
315
 
316
- # :nodoc:
317
- DATE_RE = /\AD:(\d{4})(\d\d)?(\d\d)?(\d\d)?(\d\d)?(\d\d)?([Z+-])?(?:(\d+)(?:'|'(\d+)'?|\z)?)?\z/n
316
+ DATE_RE = /\AD:(\d{4})(\d\d)?(\d\d)?(\d\d)?(\d\d)?(\d\d)?([Z+-])?(?:(\d+)(?:'|'(\d+)'?|\z)?)?\z/n # :nodoc:
318
317
 
319
318
  # Checks if the given object is a string and converts into a Time object if possible.
320
319
  # Otherwise returns +nil+.
@@ -40,8 +40,7 @@ module HexaPDF
40
40
  # Holds the result of verifying a signature.
41
41
  class VerificationResult
42
42
 
43
- # :nodoc:
44
- MESSAGE_SORT_MAP = {
43
+ MESSAGE_SORT_MAP = { # :nodoc:
45
44
  info: {warning: 1, error: 1, info: 0},
46
45
  warning: {info: -1, error: 1, warning: 0},
47
46
  error: {info: -1, warning: -1, error: 0},
@@ -607,6 +607,9 @@ module HexaPDF
607
607
  # Finally, a default font is set if necessary to ensure that the style object works in all
608
608
  # cases.
609
609
  def retrieve_style(style, properties = nil)
610
+ if style.kind_of?(Symbol) && !@styles.key?(style)
611
+ raise HexaPDF::Error, "Style #{style} not defined"
612
+ end
610
613
  style = HexaPDF::Layout::Style.create(@styles[style] || style || @styles[:base])
611
614
  style = style.dup.update(**properties) unless properties.nil? || properties.empty?
612
615
  style.font('Times') unless style.font?
@@ -246,7 +246,7 @@ module HexaPDF
246
246
 
247
247
  labels = @document.catalog.page_labels(create: true)
248
248
  labels.add_entry(start_index, page_label)
249
- labels.add_entry(0, {S: :d}) unless labels.find_entry(0)
249
+ labels.add_entry(0, {S: :D}) unless labels.find_entry(0)
250
250
 
251
251
  page_label
252
252
  end
@@ -538,6 +538,13 @@ module HexaPDF
538
538
  catalog.outline
539
539
  end
540
540
 
541
+ # Returns the main object for working with optional content (a.k.a. layers).
542
+ #
543
+ # See: Type::Catalog#optional_content
544
+ def optional_content
545
+ catalog.optional_content
546
+ end
547
+
541
548
  # Executes the given task and returns its result.
542
549
  #
543
550
  # Tasks provide an extensible way for performing operations on a PDF document without
@@ -92,9 +92,8 @@ module HexaPDF
92
92
 
93
93
  private
94
94
 
95
- # :nodoc:
96
95
  # Rijndael S-box
97
- SBOX = [
96
+ SBOX = [ # :nodoc:
98
97
  0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
99
98
  0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
100
99
  0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
@@ -113,9 +112,8 @@ module HexaPDF
113
112
  0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16
114
113
  ].freeze
115
114
 
116
- # :nodoc:
117
115
  # Inverse of the Rijndael S-box
118
- INV_SBOX = [
116
+ INV_SBOX = [ # :nodoc:
119
117
  0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
120
118
  0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
121
119
  0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
@@ -134,12 +132,10 @@ module HexaPDF
134
132
  0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d
135
133
  ].freeze
136
134
 
137
- # :nodoc:
138
- RCON = [0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c].freeze
135
+ RCON = [0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c].freeze # :nodoc:
139
136
 
140
- # :nodoc:
141
137
  # Precomputed Galois multiplication table for multiplication with 2 in GF(2^8)
142
- G2MULT = [
138
+ G2MULT = [ # :nodoc:
143
139
  0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e,
144
140
  0x20, 0x22, 0x24, 0x26, 0x28, 0x2a, 0x2c, 0x2e, 0x30, 0x32, 0x34, 0x36, 0x38, 0x3a, 0x3c, 0x3e,
145
141
  0x40, 0x42, 0x44, 0x46, 0x48, 0x4a, 0x4c, 0x4e, 0x50, 0x52, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e,
@@ -158,9 +154,8 @@ module HexaPDF
158
154
  0xfb, 0xf9, 0xff, 0xfd, 0xf3, 0xf1, 0xf7, 0xf5, 0xeb, 0xe9, 0xef, 0xed, 0xe3, 0xe1, 0xe7, 0xe5
159
155
  ].freeze
160
156
 
161
- # :nodoc:
162
157
  # Precomputed Galois multiplication table for multiplication with 3 in GF(2^8)
163
- G3MULT = [
158
+ G3MULT = [ # :nodoc:
164
159
  0x00, 0x03, 0x06, 0x05, 0x0c, 0x0f, 0x0a, 0x09, 0x18, 0x1b, 0x1e, 0x1d, 0x14, 0x17, 0x12, 0x11,
165
160
  0x30, 0x33, 0x36, 0x35, 0x3c, 0x3f, 0x3a, 0x39, 0x28, 0x2b, 0x2e, 0x2d, 0x24, 0x27, 0x22, 0x21,
166
161
  0x60, 0x63, 0x66, 0x65, 0x6c, 0x6f, 0x6a, 0x69, 0x78, 0x7b, 0x7e, 0x7d, 0x74, 0x77, 0x72, 0x71,
@@ -179,9 +174,8 @@ module HexaPDF
179
174
  0x0b, 0x08, 0x0d, 0x0e, 0x07, 0x04, 0x01, 0x02, 0x13, 0x10, 0x15, 0x16, 0x1f, 0x1c, 0x19, 0x1a
180
175
  ].freeze
181
176
 
182
- # :nodoc:
183
177
  # Precomputed Galois multiplication table for multiplication with 9 in GF(2^8)
184
- G9MULT = [
178
+ G9MULT = [ # :nodoc:
185
179
  0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77,
186
180
  0x90, 0x99, 0x82, 0x8b, 0xb4, 0xbd, 0xa6, 0xaf, 0xd8, 0xd1, 0xca, 0xc3, 0xfc, 0xf5, 0xee, 0xe7,
187
181
  0x3b, 0x32, 0x29, 0x20, 0x1f, 0x16, 0x0d, 0x04, 0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c,
@@ -200,9 +194,8 @@ module HexaPDF
200
194
  0x31, 0x38, 0x23, 0x2a, 0x15, 0x1c, 0x07, 0x0e, 0x79, 0x70, 0x6b, 0x62, 0x5d, 0x54, 0x4f, 0x46
201
195
  ].freeze
202
196
 
203
- # :nodoc:
204
197
  # Precomputed Galois multiplication table for multiplication with 11 in GF(2^8)
205
- G11MULT = [
198
+ G11MULT = [ # :nodoc:
206
199
  0x00, 0x0b, 0x16, 0x1d, 0x2c, 0x27, 0x3a, 0x31, 0x58, 0x53, 0x4e, 0x45, 0x74, 0x7f, 0x62, 0x69,
207
200
  0xb0, 0xbb, 0xa6, 0xad, 0x9c, 0x97, 0x8a, 0x81, 0xe8, 0xe3, 0xfe, 0xf5, 0xc4, 0xcf, 0xd2, 0xd9,
208
201
  0x7b, 0x70, 0x6d, 0x66, 0x57, 0x5c, 0x41, 0x4a, 0x23, 0x28, 0x35, 0x3e, 0x0f, 0x04, 0x19, 0x12,
@@ -221,9 +214,8 @@ module HexaPDF
221
214
  0xca, 0xc1, 0xdc, 0xd7, 0xe6, 0xed, 0xf0, 0xfb, 0x92, 0x99, 0x84, 0x8f, 0xbe, 0xb5, 0xa8, 0xa3
222
215
  ].freeze
223
216
 
224
- # :nodoc:
225
217
  # Precomputed Galois multiplication table for multiplication with 13 in GF(2^8)
226
- G13MULT = [
218
+ G13MULT = [ # :nodoc:
227
219
  0x00, 0x0d, 0x1a, 0x17, 0x34, 0x39, 0x2e, 0x23, 0x68, 0x65, 0x72, 0x7f, 0x5c, 0x51, 0x46, 0x4b,
228
220
  0xd0, 0xdd, 0xca, 0xc7, 0xe4, 0xe9, 0xfe, 0xf3, 0xb8, 0xb5, 0xa2, 0xaf, 0x8c, 0x81, 0x96, 0x9b,
229
221
  0xbb, 0xb6, 0xa1, 0xac, 0x8f, 0x82, 0x95, 0x98, 0xd3, 0xde, 0xc9, 0xc4, 0xe7, 0xea, 0xfd, 0xf0,
@@ -242,9 +234,8 @@ module HexaPDF
242
234
  0xdc, 0xd1, 0xc6, 0xcb, 0xe8, 0xe5, 0xf2, 0xff, 0xb4, 0xb9, 0xae, 0xa3, 0x80, 0x8d, 0x9a, 0x97
243
235
  ].freeze
244
236
 
245
- # :nodoc:
246
237
  # Precomputed Galois multiplication table for multiplication with 14 in GF(2^8)
247
- G14MULT = [
238
+ G14MULT = [ # :nodoc:
248
239
  0x00, 0x0e, 0x1c, 0x12, 0x38, 0x36, 0x24, 0x2a, 0x70, 0x7e, 0x6c, 0x62, 0x48, 0x46, 0x54, 0x5a,
249
240
  0xe0, 0xee, 0xfc, 0xf2, 0xd8, 0xd6, 0xc4, 0xca, 0x90, 0x9e, 0x8c, 0x82, 0xa8, 0xa6, 0xb4, 0xba,
250
241
  0xdb, 0xd5, 0xc7, 0xc9, 0xe3, 0xed, 0xff, 0xf1, 0xab, 0xa5, 0xb7, 0xb9, 0x93, 0x9d, 0x8f, 0x81,
@@ -263,9 +254,8 @@ module HexaPDF
263
254
  0xd7, 0xd9, 0xcb, 0xc5, 0xef, 0xe1, 0xf3, 0xfd, 0xa7, 0xa9, 0xbb, 0xb5, 0x9f, 0x91, 0x83, 0x8d
264
255
  ].freeze
265
256
 
266
- # :nodoc:
267
257
  # Number of rounds needed in various parts of the algorithm, depends on the key size
268
- NUMBER_OF_ROUNDS = {16 => 10, 24 => 12, 32 => 14}.freeze
258
+ NUMBER_OF_ROUNDS = {16 => 10, 24 => 12, 32 => 14}.freeze # :nodoc:
269
259
 
270
260
  # KeyExpansion step
271
261
  #
@@ -127,10 +127,24 @@ module HexaPDF
127
127
  #
128
128
  # This can be used to store arbitrary information on boxes for later use. For example, a
129
129
  # generic style layer could use one or more custom properties for its work.
130
+ #
131
+ # The Box class itself uses the following properties:
132
+ #
133
+ # optional_content::
134
+ #
135
+ # If this property is set, it needs to be an optional content group dictionary, a String
136
+ # defining an (optionally existing) optional content group dictionary, or an optional
137
+ # content membership dictionary.
138
+ #
139
+ # The whole content of the box, i.e. including padding, border, background..., is
140
+ # wrapped with the appropriate commands so that the optional content group or membership
141
+ # dictionary specifies whether the content is shown or not.
142
+ #
143
+ # See: HexaPDF::Type::OptionalContentProperties
130
144
  attr_reader :properties
131
145
 
132
146
  # :call-seq:
133
- # Box.new(width: 0, height: 0, style: nil, properties: {}) {|canv, box| block} -> box
147
+ # Box.new(width: 0, height: 0, style: nil, properties: nil) {|canv, box| block} -> box
134
148
  #
135
149
  # Creates a new Box object with the given width and height that uses the provided block when
136
150
  # it is asked to draw itself on a canvas (see #draw).
@@ -138,11 +152,11 @@ module HexaPDF
138
152
  # Since the final location of the box is not known beforehand, the drawing operations inside
139
153
  # the block should draw inside the rectangle (0, 0, content_width, content_height) - note that
140
154
  # the width and height of the box may not be known beforehand.
141
- def initialize(width: 0, height: 0, style: nil, properties: {}, &block)
155
+ def initialize(width: 0, height: 0, style: nil, properties: nil, &block)
142
156
  @width = @initial_width = width
143
157
  @height = @initial_height = height
144
158
  @style = Style.create(style)
145
- @properties = properties
159
+ @properties = properties || {}
146
160
  @draw_block = block
147
161
  @fit_successful = false
148
162
  @split_box = false
@@ -220,6 +234,10 @@ module HexaPDF
220
234
  # instance variable to +nil+ or a valid block. This is useful to avoid unnecessary set-up
221
235
  # operations when the block does nothing.
222
236
  def draw(canvas, x, y)
237
+ if (oc = properties['optional_content'])
238
+ canvas.optional_content(oc)
239
+ end
240
+
223
241
  if style.background_color? && style.background_color
224
242
  canvas.save_graphics_state do
225
243
  canvas.opacity(fill_alpha: style.background_alpha).
@@ -233,6 +251,8 @@ module HexaPDF
233
251
  draw_content(canvas, x + reserved_width_left, y + reserved_height_bottom)
234
252
 
235
253
  style.overlays.draw(canvas, x, y, self) if style.overlays?
254
+
255
+ canvas.end_optional_content if oc
236
256
  end
237
257
 
238
258
  # Returns +true+ if no drawing operations are performed.
@@ -177,7 +177,8 @@ module HexaPDF
177
177
  [column_left, column_bottom + height])
178
178
  shape = Geom2D::Algorithms::PolygonOperation.run(frame.shape, rect, :intersection)
179
179
  end
180
- column_frame = Frame.new(column_left, column_bottom, column_width, height, shape: shape)
180
+ column_frame = Frame.new(column_left, column_bottom, column_width, height,
181
+ shape: shape, context: frame.context)
181
182
  @box_fitter << column_frame
182
183
  end
183
184