hexapdf 0.32.2 → 0.34.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (221) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +104 -1
  3. data/README.md +9 -0
  4. data/examples/002-graphics.rb +15 -17
  5. data/examples/003-arcs.rb +9 -9
  6. data/examples/009-text_layouter_alignment.rb +1 -1
  7. data/examples/010-text_layouter_inline_boxes.rb +2 -2
  8. data/examples/011-text_layouter_line_wrapping.rb +1 -1
  9. data/examples/012-text_layouter_styling.rb +7 -7
  10. data/examples/013-text_layouter_shapes.rb +1 -1
  11. data/examples/014-text_in_polygon.rb +1 -1
  12. data/examples/015-boxes.rb +8 -7
  13. data/examples/016-frame_automatic_box_placement.rb +2 -2
  14. data/examples/017-frame_text_flow.rb +2 -1
  15. data/examples/018-composer.rb +1 -1
  16. data/examples/020-column_box.rb +2 -1
  17. data/examples/025-table_box.rb +46 -0
  18. data/examples/026-optional_content.rb +55 -0
  19. data/examples/027-composer_optional_content.rb +83 -0
  20. data/lib/hexapdf/cli/command.rb +12 -3
  21. data/lib/hexapdf/cli/fonts.rb +1 -1
  22. data/lib/hexapdf/cli/form.rb +5 -5
  23. data/lib/hexapdf/cli/inspect.rb +5 -7
  24. data/lib/hexapdf/composer.rb +106 -53
  25. data/lib/hexapdf/configuration.rb +65 -40
  26. data/lib/hexapdf/content/canvas.rb +445 -267
  27. data/lib/hexapdf/content/color_space.rb +72 -25
  28. data/lib/hexapdf/content/graphic_object/arc.rb +57 -24
  29. data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +66 -23
  30. data/lib/hexapdf/content/graphic_object/geom2d.rb +47 -6
  31. data/lib/hexapdf/content/graphic_object/solid_arc.rb +58 -36
  32. data/lib/hexapdf/content/graphic_object.rb +6 -7
  33. data/lib/hexapdf/content/graphics_state.rb +54 -45
  34. data/lib/hexapdf/content/operator.rb +54 -54
  35. data/lib/hexapdf/content/parser.rb +2 -2
  36. data/lib/hexapdf/content/processor.rb +15 -15
  37. data/lib/hexapdf/content/transformation_matrix.rb +1 -1
  38. data/lib/hexapdf/content.rb +5 -0
  39. data/lib/hexapdf/dictionary.rb +7 -5
  40. data/lib/hexapdf/dictionary_fields.rb +43 -16
  41. data/lib/hexapdf/digital_signature/cms_handler.rb +2 -2
  42. data/lib/hexapdf/digital_signature/handler.rb +1 -1
  43. data/lib/hexapdf/digital_signature/pkcs1_handler.rb +2 -3
  44. data/lib/hexapdf/digital_signature/signature.rb +6 -6
  45. data/lib/hexapdf/digital_signature/signatures.rb +13 -12
  46. data/lib/hexapdf/digital_signature/signing/default_handler.rb +14 -5
  47. data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +2 -4
  48. data/lib/hexapdf/digital_signature/signing/timestamp_handler.rb +4 -4
  49. data/lib/hexapdf/digital_signature/signing.rb +4 -0
  50. data/lib/hexapdf/digital_signature/verification_result.rb +3 -4
  51. data/lib/hexapdf/digital_signature.rb +7 -2
  52. data/lib/hexapdf/document/destinations.rb +12 -11
  53. data/lib/hexapdf/document/files.rb +1 -1
  54. data/lib/hexapdf/document/fonts.rb +1 -1
  55. data/lib/hexapdf/document/layout.rb +170 -39
  56. data/lib/hexapdf/document/pages.rb +4 -3
  57. data/lib/hexapdf/document.rb +96 -55
  58. data/lib/hexapdf/encryption/aes.rb +5 -5
  59. data/lib/hexapdf/encryption/arc4.rb +1 -1
  60. data/lib/hexapdf/encryption/fast_aes.rb +2 -2
  61. data/lib/hexapdf/encryption/fast_arc4.rb +1 -1
  62. data/lib/hexapdf/encryption/identity.rb +1 -1
  63. data/lib/hexapdf/encryption/ruby_aes.rb +11 -21
  64. data/lib/hexapdf/encryption/ruby_arc4.rb +1 -1
  65. data/lib/hexapdf/encryption/security_handler.rb +31 -24
  66. data/lib/hexapdf/encryption/standard_security_handler.rb +45 -36
  67. data/lib/hexapdf/encryption.rb +7 -2
  68. data/lib/hexapdf/error.rb +18 -0
  69. data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
  70. data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
  71. data/lib/hexapdf/filter/flate_decode.rb +1 -1
  72. data/lib/hexapdf/filter/lzw_decode.rb +1 -1
  73. data/lib/hexapdf/filter/pass_through.rb +1 -1
  74. data/lib/hexapdf/filter/predictor.rb +1 -1
  75. data/lib/hexapdf/filter/run_length_decode.rb +1 -1
  76. data/lib/hexapdf/filter.rb +55 -6
  77. data/lib/hexapdf/font/cmap/parser.rb +2 -2
  78. data/lib/hexapdf/font/cmap.rb +1 -1
  79. data/lib/hexapdf/font/encoding/difference_encoding.rb +1 -1
  80. data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +1 -1
  81. data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +2 -2
  82. data/lib/hexapdf/font/encoding/standard_encoding.rb +1 -1
  83. data/lib/hexapdf/font/encoding/symbol_encoding.rb +1 -1
  84. data/lib/hexapdf/font/encoding/win_ansi_encoding.rb +3 -3
  85. data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +1 -1
  86. data/lib/hexapdf/font/invalid_glyph.rb +3 -0
  87. data/lib/hexapdf/font/true_type_wrapper.rb +17 -4
  88. data/lib/hexapdf/font/type1_wrapper.rb +19 -4
  89. data/lib/hexapdf/font_loader/from_configuration.rb +5 -2
  90. data/lib/hexapdf/font_loader/from_file.rb +5 -5
  91. data/lib/hexapdf/font_loader/standard14.rb +3 -3
  92. data/lib/hexapdf/font_loader.rb +3 -0
  93. data/lib/hexapdf/image_loader/jpeg.rb +2 -2
  94. data/lib/hexapdf/image_loader/pdf.rb +1 -1
  95. data/lib/hexapdf/image_loader/png.rb +2 -2
  96. data/lib/hexapdf/image_loader.rb +1 -1
  97. data/lib/hexapdf/importer.rb +13 -0
  98. data/lib/hexapdf/layout/box.rb +32 -5
  99. data/lib/hexapdf/layout/box_fitter.rb +2 -2
  100. data/lib/hexapdf/layout/column_box.rb +20 -5
  101. data/lib/hexapdf/layout/frame.rb +53 -18
  102. data/lib/hexapdf/layout/image_box.rb +5 -0
  103. data/lib/hexapdf/layout/inline_box.rb +21 -9
  104. data/lib/hexapdf/layout/list_box.rb +50 -20
  105. data/lib/hexapdf/layout/page_style.rb +6 -5
  106. data/lib/hexapdf/layout/style.rb +64 -9
  107. data/lib/hexapdf/layout/table_box.rb +684 -0
  108. data/lib/hexapdf/layout/text_box.rb +12 -3
  109. data/lib/hexapdf/layout/text_fragment.rb +29 -3
  110. data/lib/hexapdf/layout/text_layouter.rb +32 -8
  111. data/lib/hexapdf/layout.rb +1 -0
  112. data/lib/hexapdf/name_tree_node.rb +1 -1
  113. data/lib/hexapdf/number_tree_node.rb +1 -1
  114. data/lib/hexapdf/object.rb +18 -7
  115. data/lib/hexapdf/parser.rb +7 -7
  116. data/lib/hexapdf/pdf_array.rb +1 -1
  117. data/lib/hexapdf/rectangle.rb +1 -1
  118. data/lib/hexapdf/reference.rb +1 -1
  119. data/lib/hexapdf/revision.rb +1 -1
  120. data/lib/hexapdf/revisions.rb +3 -3
  121. data/lib/hexapdf/serializer.rb +15 -15
  122. data/lib/hexapdf/stream.rb +5 -4
  123. data/lib/hexapdf/tokenizer.rb +14 -14
  124. data/lib/hexapdf/type/acro_form/appearance_generator.rb +22 -22
  125. data/lib/hexapdf/type/acro_form/button_field.rb +1 -1
  126. data/lib/hexapdf/type/acro_form/choice_field.rb +1 -1
  127. data/lib/hexapdf/type/acro_form/field.rb +2 -2
  128. data/lib/hexapdf/type/acro_form/form.rb +1 -1
  129. data/lib/hexapdf/type/acro_form/signature_field.rb +4 -4
  130. data/lib/hexapdf/type/acro_form/text_field.rb +1 -1
  131. data/lib/hexapdf/type/acro_form/variable_text_field.rb +1 -1
  132. data/lib/hexapdf/type/acro_form.rb +1 -1
  133. data/lib/hexapdf/type/action.rb +1 -1
  134. data/lib/hexapdf/type/actions/go_to.rb +1 -1
  135. data/lib/hexapdf/type/actions/go_to_r.rb +1 -1
  136. data/lib/hexapdf/type/actions/launch.rb +1 -1
  137. data/lib/hexapdf/type/actions/set_ocg_state.rb +86 -0
  138. data/lib/hexapdf/type/actions/uri.rb +1 -1
  139. data/lib/hexapdf/type/actions.rb +2 -1
  140. data/lib/hexapdf/type/annotation.rb +3 -3
  141. data/lib/hexapdf/type/annotations/link.rb +1 -1
  142. data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
  143. data/lib/hexapdf/type/annotations/text.rb +2 -3
  144. data/lib/hexapdf/type/annotations/widget.rb +2 -2
  145. data/lib/hexapdf/type/annotations.rb +1 -1
  146. data/lib/hexapdf/type/catalog.rb +11 -2
  147. data/lib/hexapdf/type/cid_font.rb +18 -4
  148. data/lib/hexapdf/type/embedded_file.rb +1 -1
  149. data/lib/hexapdf/type/file_specification.rb +2 -2
  150. data/lib/hexapdf/type/font_descriptor.rb +1 -1
  151. data/lib/hexapdf/type/font_simple.rb +2 -2
  152. data/lib/hexapdf/type/font_type0.rb +3 -3
  153. data/lib/hexapdf/type/font_type3.rb +1 -1
  154. data/lib/hexapdf/type/form.rb +76 -6
  155. data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
  156. data/lib/hexapdf/type/icon_fit.rb +1 -1
  157. data/lib/hexapdf/type/image.rb +1 -1
  158. data/lib/hexapdf/type/info.rb +1 -1
  159. data/lib/hexapdf/type/mark_information.rb +1 -1
  160. data/lib/hexapdf/type/names.rb +2 -2
  161. data/lib/hexapdf/type/object_stream.rb +2 -1
  162. data/lib/hexapdf/type/optional_content_configuration.rb +170 -0
  163. data/lib/hexapdf/type/optional_content_group.rb +370 -0
  164. data/lib/hexapdf/type/optional_content_membership.rb +63 -0
  165. data/lib/hexapdf/type/optional_content_properties.rb +158 -0
  166. data/lib/hexapdf/type/outline.rb +1 -1
  167. data/lib/hexapdf/type/outline_item.rb +1 -1
  168. data/lib/hexapdf/type/page.rb +46 -21
  169. data/lib/hexapdf/type/page_label.rb +5 -9
  170. data/lib/hexapdf/type/page_tree_node.rb +1 -1
  171. data/lib/hexapdf/type/resources.rb +1 -1
  172. data/lib/hexapdf/type/trailer.rb +2 -2
  173. data/lib/hexapdf/type/viewer_preferences.rb +1 -1
  174. data/lib/hexapdf/type/xref_stream.rb +2 -2
  175. data/lib/hexapdf/type.rb +4 -0
  176. data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -2
  177. data/lib/hexapdf/version.rb +1 -1
  178. data/lib/hexapdf/writer.rb +4 -4
  179. data/lib/hexapdf/xref_section.rb +2 -2
  180. data/test/hexapdf/content/graphic_object/test_endpoint_arc.rb +11 -1
  181. data/test/hexapdf/content/graphic_object/test_geom2d.rb +7 -0
  182. data/test/hexapdf/content/test_canvas.rb +49 -1
  183. data/test/hexapdf/digital_signature/test_signatures.rb +22 -0
  184. data/test/hexapdf/document/test_files.rb +2 -2
  185. data/test/hexapdf/document/test_layout.rb +105 -2
  186. data/test/hexapdf/document/test_pages.rb +6 -6
  187. data/test/hexapdf/encryption/test_security_handler.rb +12 -11
  188. data/test/hexapdf/encryption/test_standard_security_handler.rb +35 -23
  189. data/test/hexapdf/font/test_true_type_wrapper.rb +18 -1
  190. data/test/hexapdf/font/test_type1_wrapper.rb +15 -1
  191. data/test/hexapdf/layout/test_box.rb +14 -5
  192. data/test/hexapdf/layout/test_column_box.rb +65 -21
  193. data/test/hexapdf/layout/test_frame.rb +27 -15
  194. data/test/hexapdf/layout/test_image_box.rb +4 -0
  195. data/test/hexapdf/layout/test_inline_box.rb +17 -3
  196. data/test/hexapdf/layout/test_list_box.rb +84 -33
  197. data/test/hexapdf/layout/test_page_style.rb +3 -2
  198. data/test/hexapdf/layout/test_style.rb +60 -0
  199. data/test/hexapdf/layout/test_table_box.rb +728 -0
  200. data/test/hexapdf/layout/test_text_box.rb +26 -0
  201. data/test/hexapdf/layout/test_text_fragment.rb +33 -0
  202. data/test/hexapdf/layout/test_text_layouter.rb +36 -5
  203. data/test/hexapdf/test_composer.rb +10 -0
  204. data/test/hexapdf/test_dictionary.rb +10 -0
  205. data/test/hexapdf/test_dictionary_fields.rb +4 -1
  206. data/test/hexapdf/test_document.rb +5 -0
  207. data/test/hexapdf/test_filter.rb +8 -0
  208. data/test/hexapdf/test_importer.rb +9 -0
  209. data/test/hexapdf/test_object.rb +16 -5
  210. data/test/hexapdf/test_stream.rb +7 -0
  211. data/test/hexapdf/test_writer.rb +3 -3
  212. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +13 -5
  213. data/test/hexapdf/type/acro_form/test_form.rb +4 -3
  214. data/test/hexapdf/type/actions/test_set_ocg_state.rb +40 -0
  215. data/test/hexapdf/type/test_catalog.rb +11 -0
  216. data/test/hexapdf/type/test_form.rb +119 -0
  217. data/test/hexapdf/type/test_optional_content_configuration.rb +112 -0
  218. data/test/hexapdf/type/test_optional_content_group.rb +158 -0
  219. data/test/hexapdf/type/test_optional_content_properties.rb +109 -0
  220. data/test/hexapdf/type/test_page.rb +20 -6
  221. metadata +28 -8
@@ -54,7 +54,7 @@ module HexaPDF
54
54
  # Field inheritance means that if a field is not set on the page object itself, the value is
55
55
  # taken from the nearest page tree ancestor that has this value set.
56
56
  #
57
- # See: PDF1.7 s7.7.3.3, s7.7.3.4, Pages
57
+ # See: PDF2.0 s7.7.3.3, s7.7.3.4, Pages
58
58
  class Page < Dictionary
59
59
 
60
60
  # The predefined paper sizes in points (1/72 inch):
@@ -223,7 +223,7 @@ module HexaPDF
223
223
  # The art box defines the region of the page's meaningful content as intended by the
224
224
  # author. The default is the crop box.
225
225
  #
226
- # See: PDF1.7 s14.11.2
226
+ # See: PDF2.0 s14.11.2
227
227
  def box(type = :crop, rectangle = nil)
228
228
  if rectangle
229
229
  case type
@@ -261,12 +261,23 @@ module HexaPDF
261
261
  # Rotates the page +angle+ degrees counterclockwise where +angle+ has to be a multiple of 90.
262
262
  #
263
263
  # Positive values rotate the page to the left, negative values to the right. If +flatten+ is
264
- # +true+, the rotation is not done via the page's meta data but by "rotating" the canvas
265
- # itself.
264
+ # +true+, the rotation is not done via the page's meta (i.e. the /Rotate key) data but by
265
+ # rotating the canvas itself and all other necessary objects like the various page boxes and
266
+ # annotations.
266
267
  #
267
- # Note that the :Rotate key of a page object describes the angle in a clockwise orientation
268
- # but this method uses counterclockwise rotation to be consistent with other rotation methods
269
- # (e.g. HexaPDF::Content::Canvas#rotate).
268
+ # Notes:
269
+ #
270
+ # * The given +angle+ is applied in addition to a possibly already existing rotation
271
+ # (specified via the /Rotate key) and does not replace it.
272
+ #
273
+ # * Specifying 0 for +angle+ is valid and means that no additional rotation should be applied.
274
+ # The only meaningful usage of 0 for +angle+ is when +flatten+ is set to +true+ (so that the
275
+ # /Rotate key is removed and the existing rotation information incorporated into the canvas,
276
+ # page boxes and annotations).
277
+ #
278
+ # * The /Rotate key of a page object describes the angle in a clockwise orientation but this
279
+ # method uses counterclockwise rotation to be consistent with other rotation methods (e.g.
280
+ # HexaPDF::Content::Canvas#rotate).
270
281
  def rotate(angle, flatten: false)
271
282
  if angle % 90 != 0
272
283
  raise ArgumentError, "Page rotation has to be multiple of 90 degrees"
@@ -423,7 +434,7 @@ module HexaPDF
423
434
  #
424
435
  # To check whether the origin has been translated or not, use
425
436
  #
426
- # canvas.graphics_state.ctm.evaluate(0, 0)
437
+ # canvas.pos(0, 0)
427
438
  #
428
439
  # and check whether the result is [0, 0]. If it is, then the origin has not been
429
440
  # translated.
@@ -522,8 +533,8 @@ module HexaPDF
522
533
  # Yields each annotation of this page.
523
534
  def each_annotation
524
535
  return to_enum(__method__) unless block_given?
525
- self[:Annots]&.each do |annotation|
526
- next unless annotation
536
+ Array(self[:Annots]).each do |annotation|
537
+ next unless annotation&.key?(:Subtype) && annotation&.key?(:Rect)
527
538
  yield(document.wrap(annotation, type: :Annot))
528
539
  end
529
540
  self
@@ -539,14 +550,18 @@ module HexaPDF
539
550
  # If an annotation is a form field widget, only the widget will be deleted but not the form
540
551
  # field itself.
541
552
  def flatten_annotations(annotations = self[:Annots])
542
- not_flattened = (annotations || []).to_ary
553
+ not_flattened = Array(annotations) || []
543
554
  return not_flattened unless key?(:Annots)
544
555
 
545
- annotations = not_flattened & self[:Annots] if annotations != self[:Annots]
556
+ annotations = if annotations == self[:Annots]
557
+ not_flattened
558
+ else
559
+ not_flattened & Array(self[:Annots])
560
+ end
546
561
  return not_flattened if annotations.empty?
547
562
 
548
563
  canvas = self.canvas(type: :overlay)
549
- if (pos = canvas.graphics_state.ctm.evaluate(0, 0)) != [0, 0]
564
+ if (pos = canvas.pos(0, 0)) != [0, 0]
550
565
  canvas.save_graphics_state
551
566
  canvas.translate(-pos[0], -pos[1])
552
567
  end
@@ -554,6 +569,11 @@ module HexaPDF
554
569
  to_delete = []
555
570
  not_flattened -= annotations
556
571
  annotations.each do |annotation|
572
+ unless annotation&.key?(:Subtype) && annotation&.key?(:Rect)
573
+ to_delete << annotation if annotation
574
+ next
575
+ end
576
+
557
577
  annotation = document.wrap(annotation, type: :Annot)
558
578
  appearance = annotation.appearance
559
579
  if annotation.flagged?(:hidden) || annotation.flagged?(:invisible)
@@ -567,8 +587,8 @@ module HexaPDF
567
587
  rect = annotation[:Rect]
568
588
  box = appearance.box
569
589
 
570
- # PDF1.7 12.5.5 algorithm
571
- # Step a) Calculate smallest rectangle containing transformed bounding box
590
+ # PDF2.0 12.5.5 algorithm
591
+ # Step 1) Calculate smallest rectangle containing transformed bounding box
572
592
  matrix = HexaPDF::Content::TransformationMatrix.new(*appearance[:Matrix].value)
573
593
  llx, lly = matrix.evaluate(box.left, box.bottom)
574
594
  ulx, uly = matrix.evaluate(box.left, box.top)
@@ -582,14 +602,19 @@ module HexaPDF
582
602
  next
583
603
  end
584
604
 
585
- # Step b) Fit calculated rectangle to annotation rectangle by translating/scaling
586
- a = HexaPDF::Content::TransformationMatrix.new
587
- a.translate(rect.left - left, rect.bottom - bottom)
588
- a.scale(rect.width.fdiv(right - left), rect.height.fdiv(top - bottom))
605
+ # Step 2) Fit calculated rectangle to annotation rectangle by translating/scaling
606
+
607
+ # The final matrix is composed by translating the bottom-left corner of the transformed
608
+ # bounding box to the bottom-left corner of the annotation rectangle and scaling from the
609
+ # bottom-left corner of the transformed bounding box.
610
+ sx = rect.width.fdiv(right - left)
611
+ sy = rect.height.fdiv(top - bottom)
612
+ tx = rect.left - left + left - left * sx
613
+ ty = rect.bottom - bottom + bottom - bottom * sy
589
614
 
590
- # Step c) Premultiply form matrix - done implicitly when drawing the XObject
615
+ # Step 3) Premultiply form matrix - done implicitly when drawing the XObject
591
616
 
592
- canvas.transform(*a) do
617
+ canvas.transform(sx, 0, 0, sy, tx, ty) do
593
618
  # Use [box.left, box.bottom] to counter default translation in #xobject since that
594
619
  # is already taken care of in matrix a
595
620
  canvas.xobject(appearance, at: [box.left, box.bottom])
@@ -70,7 +70,7 @@ module HexaPDF
70
70
  #
71
71
  # "", "", "", ... (i.e. always the empty string)
72
72
  #
73
- # See: PDF1.7 s12.4.2, HexaPDF::Document::Pages, HexaPDF::Type::Catalog
73
+ # See: PDF2.0 s12.4.2, HexaPDF::Document::Pages, HexaPDF::Type::Catalog
74
74
  class PageLabel < Dictionary
75
75
 
76
76
  define_type :PageLabel
@@ -103,8 +103,7 @@ module HexaPDF
103
103
  end
104
104
  end
105
105
 
106
- # :nodoc:
107
- NUMBERING_STYLE_MAPPING = {
106
+ NUMBERING_STYLE_MAPPING = { # :nodoc:
108
107
  decimal: :D, D: :D,
109
108
  uppercase_roman: :R, R: :R,
110
109
  lowercase_roman: :r, r: :r,
@@ -113,8 +112,7 @@ module HexaPDF
113
112
  none: nil
114
113
  }
115
114
 
116
- # :nodoc:
117
- REVERSE_NUMBERING_STYLE_MAPPING = Hash[*NUMBERING_STYLE_MAPPING.flatten.reverse]
115
+ REVERSE_NUMBERING_STYLE_MAPPING = Hash[*NUMBERING_STYLE_MAPPING.flatten.reverse] # :nodoc:
118
116
 
119
117
  # :call-seq:
120
118
  # page_label.numbering_style -> numbering_style
@@ -174,8 +172,7 @@ module HexaPDF
174
172
 
175
173
  private
176
174
 
177
- # :nodoc:
178
- ALPHABET = ('A'..'Z').to_a
175
+ ALPHABET = ('A'..'Z').to_a # :nodoc:
179
176
 
180
177
  # Maps the given number to uppercase (or, if +lowercase+ is +true+, lowercase) letters (e.g. 1
181
178
  # -> A, 27 -> AA, 28 -> AB, ...).
@@ -188,8 +185,7 @@ module HexaPDF
188
185
  lowercase ? result.downcase : result
189
186
  end
190
187
 
191
- # :nodoc:
192
- ROMAN_NUMERAL_MAPPING = {
188
+ ROMAN_NUMERAL_MAPPING = { # :nodoc:
193
189
  1000 => "M",
194
190
  900 => "CM",
195
191
  500 => "D",
@@ -60,7 +60,7 @@ module HexaPDF
60
60
  # value. If an inherited /Resources dictionary does *not* exist, an empty one is created for
61
61
  # the page.
62
62
  #
63
- # See: PDF1.7 s7.7.3.2, Page
63
+ # See: PDF2.0 s7.7.3.2, Page
64
64
  class PageTreeNode < Dictionary
65
65
 
66
66
  define_type :Pages
@@ -44,7 +44,7 @@ module HexaPDF
44
44
 
45
45
  # Represents the resources needed by a content stream.
46
46
  #
47
- # See: PDF1.7 s7.8.3
47
+ # See: PDF2.0 s7.8.3
48
48
  class Resources < Dictionary
49
49
 
50
50
  define_type :XXResources
@@ -54,7 +54,7 @@ module HexaPDF
54
54
  # HexaPDF::Revision object's trailer dictionary is always of this type. Only when a
55
55
  # cross-reference stream is written is the trailer integrated into the stream's dictionary.
56
56
  #
57
- # See: PDF1.7 s7.5.5, s14.4; XRefStream
57
+ # See: PDF2.0 s7.5.5, s14.4; XRefStream
58
58
  class Trailer < Dictionary
59
59
 
60
60
  define_type :XXTrailer
@@ -79,7 +79,7 @@ module HexaPDF
79
79
 
80
80
  # Sets the /ID field to an array of two copies of a random string and returns this array.
81
81
  #
82
- # See: PDF1.7 14.4
82
+ # See: PDF2.0 14.4
83
83
  def set_random_id
84
84
  value[:ID] = [Digest::MD5.digest(rand.to_s)] * 2
85
85
  end
@@ -44,7 +44,7 @@ module HexaPDF
44
44
  #
45
45
  # This dictionary is linked via the /ViewerPreferences entry from the Type::Catalog.
46
46
  #
47
- # See: PDF1.7 s12.2, Catalog
47
+ # See: PDF2.0 s12.2, Catalog
48
48
  class ViewerPreferences < Dictionary
49
49
 
50
50
  define_type :XXViewerPreferences
@@ -65,7 +65,7 @@ module HexaPDF
65
65
  # cross-reference section and trailer information and then written. Otherwise a normal
66
66
  # cross-reference section plus trailer are written.
67
67
  #
68
- # See: PDF1.7 s7.5.8
68
+ # See: PDF2.0 s7.5.8
69
69
  class XRefStream < HexaPDF::Stream
70
70
 
71
71
  define_type :XRef
@@ -161,7 +161,7 @@ module HexaPDF
161
161
  when TYPE_COMPRESSED
162
162
  xref.add_compressed_entry(oid, field2, field3)
163
163
  else
164
- nil # Ignore entry as per PDF1.7 s7.5.8.3
164
+ nil # Ignore entry as per PDF2.0 s7.5.8.3
165
165
  end
166
166
  start_pos = end_pos
167
167
  end
data/lib/hexapdf/type.rb CHANGED
@@ -76,6 +76,10 @@ module HexaPDF
76
76
  autoload(:OutlineItem, 'hexapdf/type/outline_item')
77
77
  autoload(:PageLabel, 'hexapdf/type/page_label')
78
78
  autoload(:MarkInformation, 'hexapdf/type/mark_information')
79
+ autoload(:OptionalContentGroup, 'hexapdf/type/optional_content_group')
80
+ autoload(:OptionalContentMembership, 'hexapdf/type/optional_content_membership')
81
+ autoload(:OptionalContentProperties, 'hexapdf/type/optional_content_properties')
82
+ autoload(:OptionalContentConfiguration, 'hexapdf/type/optional_content_configuration')
79
83
 
80
84
  end
81
85
 
@@ -49,10 +49,9 @@ module HexaPDF
49
49
  # When a PDF file is written, text strings are automatically encoded in either PDFDocEncoding
50
50
  # or UTF-16BE depending on the characters in the text string.
51
51
  #
52
- # See: PDF1.7 s7.9.2, D.1, D.3
52
+ # See: PDF2.0 s7.9.2, D.1, D.3
53
53
  module PDFDocEncoding
54
54
 
55
- # :nodoc:
56
55
  CHARACTER_MAP = %W[\uFFFD \uFFFD \uFFFD \uFFFD \uFFFD \uFFFD \uFFFD \uFFFD
57
56
  \uFFFD \u0009 \u000A \uFFFD \uFFFD \u000D \uFFFD \uFFFD
58
57
  \uFFFD \uFFFD \uFFFD \uFFFD \uFFFD \uFFFD \uFFFD \uFFFD
@@ -37,6 +37,6 @@
37
37
  module HexaPDF
38
38
 
39
39
  # The version of HexaPDF.
40
- VERSION = '0.32.2'
40
+ VERSION = '0.34.0'
41
41
 
42
42
  end
@@ -124,7 +124,7 @@ module HexaPDF
124
124
 
125
125
  # Writes the PDF file header.
126
126
  #
127
- # See: PDF1.7 s7.5.2
127
+ # See: PDF2.0 s7.5.2
128
128
  def write_file_header
129
129
  @io << "%PDF-#{@document.version}\n%\xCF\xEC\xFF\xE8\xD7\xCB\xCD\n"
130
130
  end
@@ -225,7 +225,7 @@ module HexaPDF
225
225
 
226
226
  # Writes the cross-reference section.
227
227
  #
228
- # See: PDF1.7 s7.5.4
228
+ # See: PDF2.0 s7.5.4
229
229
  def write_xref_section(xref_section)
230
230
  @io << "xref\n"
231
231
  xref_section.each_subsection do |entries|
@@ -245,14 +245,14 @@ module HexaPDF
245
245
 
246
246
  # Writes the trailer dictionary.
247
247
  #
248
- # See: PDF1.7 s7.5.5
248
+ # See: PDF2.0 s7.5.5
249
249
  def write_trailer(trailer)
250
250
  @io << "trailer\n#{@serializer.serialize(trailer)}\n"
251
251
  end
252
252
 
253
253
  # Writes the startxref line needed for cross-reference sections and cross-reference streams.
254
254
  #
255
- # See: PDF1.7 s7.5.5, s7.5.8
255
+ # See: PDF2.0 s7.5.5, s7.5.8
256
256
  def write_startxref(startxref)
257
257
  @io << "startxref\n#{startxref}\n%%EOF\n"
258
258
  end
@@ -46,7 +46,7 @@ module HexaPDF
46
46
  #
47
47
  # Note that a cross-reference section may contain a single object number only once.
48
48
  #
49
- # See: HexaPDF::Revision, PDF1.7 s7.5.4, s7.5.8
49
+ # See: HexaPDF::Revision, PDF2.0 s7.5.4, s7.5.8
50
50
  class XRefSection < Utils::ObjectHash
51
51
 
52
52
  # One entry of a cross-reference section or stream.
@@ -69,7 +69,7 @@ module HexaPDF
69
69
  #
70
70
  # Objects in an object stream always have a generation number of 0!
71
71
  #
72
- # See: PDF1.7 s7.5.4, s7.5.8
72
+ # See: PDF2.0 s7.5.4, s7.5.8
73
73
  Entry = Struct.new(:type, :oid, :gen, :pos, :objstm) do
74
74
  def free?
75
75
  type == :free
@@ -16,13 +16,15 @@ describe HexaPDF::Content::GraphicObject::EndpointArc do
16
16
  assert_equal(0, arc.inclination)
17
17
  assert(arc.large_arc)
18
18
  refute(arc.clockwise)
19
+ assert_nil(arc.max_curves)
19
20
  end
20
21
  end
21
22
 
22
23
  describe "configure" do
23
24
  it "changes the values" do
24
25
  arc = HexaPDF::Content::GraphicObject::EndpointArc.new
25
- arc.configure(x: 1, y: 2, a: 3, b: 4, inclination: 5, large_arc: false, clockwise: true)
26
+ arc.configure(x: 1, y: 2, a: 3, b: 4, inclination: 5, large_arc: false, clockwise: true,
27
+ max_curves: 8)
26
28
  assert_equal(1, arc.x)
27
29
  assert_equal(2, arc.y)
28
30
  assert_equal(3, arc.a)
@@ -30,6 +32,7 @@ describe HexaPDF::Content::GraphicObject::EndpointArc do
30
32
  assert_equal(5, arc.inclination)
31
33
  refute(arc.large_arc)
32
34
  assert(arc.clockwise)
35
+ assert_equal(8, arc.max_curves)
33
36
  end
34
37
  end
35
38
 
@@ -94,5 +97,12 @@ describe HexaPDF::Content::GraphicObject::EndpointArc do
94
97
  clockwise: false)
95
98
  assert_equal(arc_data, @page.contents)
96
99
  end
100
+
101
+ it "assigns the max curves to the generated arc" do
102
+ arc = HexaPDF::Content::GraphicObject::EndpointArc.new
103
+ arc.configure(a: 1, b: 1, x: -1, y: 0, max_curves: 10)
104
+ hash = arc.send(:compute_arc_values, 1, 0)
105
+ assert_equal(10, hash[:max_curves])
106
+ end
97
107
  end
98
108
  end
@@ -51,6 +51,13 @@ describe HexaPDF::Content::GraphicObject::Geom2D do
51
51
  [[:move_to, [5, 6]], [:line_to, [10, 11]], [:stroke_path]])
52
52
  end
53
53
 
54
+ it "draws a Geom2D::Rectangle onto the canvas" do
55
+ @obj.object = Geom2D::Rectangle(5, 6, 20, 50)
56
+ @obj.draw(@canvas)
57
+ assert_operators(@canvas.contents,
58
+ [[:append_rectangle, [5, 6, 20, 50]], [:stroke_path]])
59
+ end
60
+
54
61
  it "draws a Geom2D::Polygon onto the canvas" do
55
62
  @obj.object = Geom2D::Polygon([5, 6], [10, 11], [7, 9])
56
63
  @obj.draw(@canvas)
@@ -71,6 +71,13 @@ describe HexaPDF::Content::Canvas do
71
71
  end
72
72
  end
73
73
 
74
+ describe "pos" do
75
+ it "returns the transformed position" do
76
+ @canvas.translate(9, 4)
77
+ assert_equal([10, 5], @canvas.pos(1, 1))
78
+ end
79
+ end
80
+
74
81
  describe "save_graphics_state" do
75
82
  it "invokes the operator implementation" do
76
83
  assert_operator_invoked(:q) { @canvas.save_graphics_state }
@@ -1193,7 +1200,6 @@ describe HexaPDF::Content::Canvas do
1193
1200
  assert_operators(@canvas.contents, [[:set_font_and_size, [:F1, 10]],
1194
1201
  [:begin_text],
1195
1202
  [:set_text_matrix, [1, 0, 0, 1, 100, 100]],
1196
- [:set_leading, [10]],
1197
1203
  [:show_text_with_positioning, [["Hallo"]]]])
1198
1204
  end
1199
1205
 
@@ -1281,6 +1287,48 @@ describe HexaPDF::Content::Canvas do
1281
1287
  end
1282
1288
  end
1283
1289
 
1290
+ describe "optional_content" do
1291
+ it "invokes the marked-sequence operator implementation" do
1292
+ assert_operator_invoked(:BDC, :OC, :P1) { @canvas.optional_content('Test') }
1293
+ end
1294
+
1295
+ it "is serialized correctly when no block is used" do
1296
+ @canvas.optional_content('Test')
1297
+ assert_operators(@canvas.contents, [[:begin_marked_content_with_property_list, [:OC, :P1]]])
1298
+ end
1299
+
1300
+ it "is serialized correctly when a block is used" do
1301
+ @canvas.optional_content('Test') {}
1302
+ assert_operators(@canvas.contents, [[:begin_marked_content_with_property_list, [:OC, :P1]],
1303
+ [:end_marked_content]])
1304
+ end
1305
+
1306
+ it "uses the provided OCG dictionary" do
1307
+ ocg = @doc.optional_content.add_ocg('Test')
1308
+ @canvas.optional_content(ocg)
1309
+ assert_equal(ocg, @page.resources.property_list(:P1))
1310
+ end
1311
+
1312
+ it "uses an existing OCG specified by name" do
1313
+ ocg = @doc.optional_content.add_ocg('Test')
1314
+ @canvas.optional_content('Test')
1315
+ assert_equal(ocg, @page.resources.property_list(:P1))
1316
+ end
1317
+
1318
+ it "creates an OCG if the named one doesn't yet exist" do
1319
+ @canvas.optional_content('Test')
1320
+ assert_equal(@doc.optional_content.ocg('Test'), @page.resources.property_list(:P1))
1321
+ end
1322
+
1323
+ it "always creates a new OCG if use_existing_ocg is false" do
1324
+ ocg = @doc.optional_content.add_ocg('Test')
1325
+ @canvas.optional_content('Test', use_existing_ocg: false)
1326
+ pl_item = @page.resources.property_list(:P1)
1327
+ refute_equal(ocg, pl_item)
1328
+ assert_equal(@doc.optional_content.ocgs.last, pl_item)
1329
+ end
1330
+ end
1331
+
1284
1332
  describe "color_from_specification "do
1285
1333
  it "accepts a color string" do
1286
1334
  assert_equal([1, 0, 0], @canvas.color_from_specification("red").components)
@@ -107,6 +107,28 @@ describe HexaPDF::DigitalSignature::Signatures do
107
107
  assert_equal(1, field.each_widget.count)
108
108
  end
109
109
 
110
+ it "creates an empty widget on the first page for the signature field if necessary" do
111
+ @doc.pages.add
112
+ field = @doc.acro_form(create: true).create_signature_field('Signature2')
113
+ field.field_value = sig = @doc.add({Type: :Sig, key: :value})
114
+ @doc.signatures.add(@io, @handler, signature: sig)
115
+ widgets = field.each_widget.to_a
116
+ assert_equal(1, widgets.size)
117
+ assert_equal(@doc.pages[0], widgets[0][:P])
118
+ assert_equal([0, 0, 0, 0], widgets[0][:Rect])
119
+ end
120
+
121
+ it "handles a bug in Adobe Acrobat related to images not showing without a /Resources entry" do
122
+ field = @doc.acro_form(create: true).create_signature_field('Signature')
123
+ image = @doc.add({Type: :XObject, Subtype: :Image, Width: 1, Height: 1, ColorSpace: :DeviceGray,
124
+ BitsPerComponent: 8}, stream: 'A')
125
+ field.create_widget(@doc.pages[0], Rect: [0, 0, 100, 100]).create_appearance.
126
+ canvas.xobject(image, at: [0, 0])
127
+ @doc.signatures.add(@io, @handler, signature: field)
128
+ assert(image.key?(:Resources))
129
+ assert_equal({}, image[:Resources])
130
+ end
131
+
110
132
  it "handles different xref section types correctly when determing the offsets" do
111
133
  @doc.delete(7)
112
134
  sig = @doc.signatures.add(@io, @handler, write_options: {update_fields: false})
@@ -55,8 +55,8 @@ describe HexaPDF::Document::Files do
55
55
  spec2 = @doc.add({Type: :Filespec})
56
56
  @doc.pages.add # page without annot
57
57
  @doc.pages.add[:Annots] = [
58
- {Subtype: :FileAttachment, FS: HexaPDF::Reference.new(spec1.oid, spec1.gen)},
59
- {Subtype: :FileAttachment, FS: spec2},
58
+ {Subtype: :FileAttachment, Rect: [0, 0, 0, 0], FS: HexaPDF::Reference.new(spec1.oid, spec1.gen)},
59
+ {Subtype: :FileAttachment, Rect: [0, 0, 0, 0], FS: spec2},
60
60
  {},
61
61
  ]
62
62
  assert_equal([spec1, spec2], @doc.files.to_a)
@@ -59,6 +59,54 @@ describe HexaPDF::Document::Layout::ChildrenCollector do
59
59
  end
60
60
  end
61
61
 
62
+ describe HexaPDF::Document::Layout::CellArgumentCollector do
63
+ before do
64
+ @args = HexaPDF::Document::Layout::CellArgumentCollector.new(20, 10)
65
+ end
66
+
67
+ describe "[]" do
68
+ def check_argument_info(info, rows, cols, args)
69
+ assert_equal(rows, info.rows)
70
+ assert_equal(cols, info.cols)
71
+ assert_equal(args, info.args)
72
+ end
73
+
74
+ it "allows assigning to all cells" do
75
+ @args[] = {key: :value}
76
+ check_argument_info(@args.argument_infos.first, 0..19, 0..9, {key: :value})
77
+ end
78
+
79
+ it "allows assigning to all columns of a range of rows" do
80
+ @args[1..4] = {key: :value}
81
+ check_argument_info(@args.argument_infos.first, 1..4, 0..9, {key: :value})
82
+ end
83
+
84
+ it "allows assigning to the intersection of a range of rows with a range of columns" do
85
+ @args[1..4, 3..5] = {key: :value}
86
+ check_argument_info(@args.argument_infos.first, 1..4, 3..5, {key: :value})
87
+ end
88
+
89
+ it "allows selecting a single row or column" do
90
+ @args[1, 3] = {key: :value}
91
+ check_argument_info(@args.argument_infos.first, 1..1, 3..3, {key: :value})
92
+ end
93
+
94
+ it "allows using negative indices" do
95
+ @args[-3..-1, -5..-2] = {key: :value}
96
+ check_argument_info(@args.argument_infos.first, 17..19, 5..8, {key: :value})
97
+ end
98
+ end
99
+
100
+ describe "retrieve_arguments_for" do
101
+ it "merges all argument hashes, with later defined ones overridding prior ones" do
102
+ @args[] = {key: :value, a: :b}
103
+ @args[3..7] = {a: :c}
104
+ @args[5, 6] = {e: :f}
105
+ assert_equal({key: :value, a: :c, e: :f}, @args.retrieve_arguments_for(5, 6))
106
+ end
107
+ end
108
+ end
109
+
62
110
  describe HexaPDF::Document::Layout do
63
111
  before do
64
112
  @doc = HexaPDF::Document.new
@@ -171,6 +219,10 @@ describe HexaPDF::Document::Layout do
171
219
  box = @layout.text_box("Test", box_style: :named)
172
220
  assert_equal(20, box.style.font_size)
173
221
  end
222
+
223
+ it "raises an error if the to-be-used style doesn't exist" do
224
+ assert_raises(HexaPDF::Error) { @layout.text_box("Test", style: :unknown) }
225
+ end
174
226
  end
175
227
 
176
228
  describe "formatted_text" do
@@ -257,9 +309,10 @@ describe HexaPDF::Document::Layout do
257
309
 
258
310
  it "allows creating an inline box through a hash with a :box key" do
259
311
  block = lambda {|item| item.box(:base, width: 5, height: 15) }
260
- box = @layout.formatted_text_box([{box: :list, width: 10, block: block}])
312
+ box = @layout.formatted_text_box([{box: :column, columns: 1, width: 100, block: block}])
261
313
  ibox = box.instance_variable_get(:@items).first
262
- assert_equal(10, ibox.width)
314
+ ibox.fit_wrapped_box(nil)
315
+ assert_equal(100, ibox.width)
263
316
  assert_equal(15, ibox.height)
264
317
  end
265
318
 
@@ -290,6 +343,56 @@ describe HexaPDF::Document::Layout do
290
343
  end
291
344
  end
292
345
 
346
+ describe "table_box" do
347
+ it "creates a table box" do
348
+ box = @layout.table_box([['m']], column_widths: [100], header: proc { [['a']] },
349
+ footer: proc { [['b']] }, cell_style: {background_color: "red"},
350
+ width: 100, height: 300, style: {background_color: "blue"},
351
+ properties: {key: :value}, border: {width: 1})
352
+ assert_equal(100, box.width)
353
+ assert_equal(300, box.height)
354
+ assert_equal("blue", box.style.background_color)
355
+ assert_equal(1, box.style.border.width.left)
356
+ assert_equal({key: :value}, box.properties)
357
+ assert_equal(HexaPDF::Layout::TextBox, box.cells[0, 0].children.class)
358
+ assert_equal([100], box.column_widths)
359
+ assert_equal('a', box.header_cells[0, 0].children)
360
+ assert_equal('b', box.footer_cells[0, 0].children)
361
+ end
362
+
363
+ it "doesn't modify the children of cells if they are already in the correct form" do
364
+ image_path = File.join(TEST_DATA_DIR, 'images', 'gray.jpg')
365
+ cell0 = @layout.text('a')
366
+ cell1 = [@layout.text('b'), @layout.image(image_path)]
367
+ box = @layout.table_box([[cell0, cell1]])
368
+ assert_same(cell0, box.cells[0, 0].children)
369
+ assert_same(cell1, box.cells[0, 1].children)
370
+ end
371
+
372
+ it "converts cells containing other than Box and Array instances" do
373
+ box = @layout.table_box([['a', 5]])
374
+ assert_kind_of(HexaPDF::Layout::TextBox, box.cells[0, 0].children)
375
+ assert_kind_of(HexaPDF::Layout::TextBox, box.cells[0, 1].children)
376
+ end
377
+
378
+ it "allows customizing the creation arguments" do
379
+ box = @layout.table_box([['a']]) do |args|
380
+ args[] = {font_size: 20}
381
+ end
382
+ assert_equal(20, box.cells[0, 0].children.style.font_size)
383
+ refute_equal(20, box.cells[0, 0].style.font_size)
384
+ end
385
+
386
+ it "allows styling table cells themselves" do
387
+ box = @layout.table_box([['a', @layout.text('b')]]) do |args|
388
+ args[] = {cell: {background_color: "green"}}
389
+ end
390
+ assert_equal('green', box.cells[0, 0].style.background_color)
391
+ assert_nil(box.cells[0, 0].children.style.background_color)
392
+ assert_equal('green', box.cells[0, 1].style.background_color)
393
+ end
394
+ end
395
+
293
396
  describe "lorem_ipsum_box" do
294
397
  it "creates a standard lorem ipsum box" do
295
398
  box = @layout.lorem_ipsum_box(width: 10, height: 15, font_size: 15)