hexapdf 0.32.2 → 0.34.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +104 -1
- data/README.md +9 -0
- data/examples/002-graphics.rb +15 -17
- data/examples/003-arcs.rb +9 -9
- data/examples/009-text_layouter_alignment.rb +1 -1
- data/examples/010-text_layouter_inline_boxes.rb +2 -2
- data/examples/011-text_layouter_line_wrapping.rb +1 -1
- data/examples/012-text_layouter_styling.rb +7 -7
- data/examples/013-text_layouter_shapes.rb +1 -1
- data/examples/014-text_in_polygon.rb +1 -1
- data/examples/015-boxes.rb +8 -7
- data/examples/016-frame_automatic_box_placement.rb +2 -2
- data/examples/017-frame_text_flow.rb +2 -1
- data/examples/018-composer.rb +1 -1
- data/examples/020-column_box.rb +2 -1
- data/examples/025-table_box.rb +46 -0
- data/examples/026-optional_content.rb +55 -0
- data/examples/027-composer_optional_content.rb +83 -0
- data/lib/hexapdf/cli/command.rb +12 -3
- data/lib/hexapdf/cli/fonts.rb +1 -1
- data/lib/hexapdf/cli/form.rb +5 -5
- data/lib/hexapdf/cli/inspect.rb +5 -7
- data/lib/hexapdf/composer.rb +106 -53
- data/lib/hexapdf/configuration.rb +65 -40
- data/lib/hexapdf/content/canvas.rb +445 -267
- data/lib/hexapdf/content/color_space.rb +72 -25
- data/lib/hexapdf/content/graphic_object/arc.rb +57 -24
- data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +66 -23
- data/lib/hexapdf/content/graphic_object/geom2d.rb +47 -6
- data/lib/hexapdf/content/graphic_object/solid_arc.rb +58 -36
- data/lib/hexapdf/content/graphic_object.rb +6 -7
- data/lib/hexapdf/content/graphics_state.rb +54 -45
- data/lib/hexapdf/content/operator.rb +54 -54
- data/lib/hexapdf/content/parser.rb +2 -2
- data/lib/hexapdf/content/processor.rb +15 -15
- data/lib/hexapdf/content/transformation_matrix.rb +1 -1
- data/lib/hexapdf/content.rb +5 -0
- data/lib/hexapdf/dictionary.rb +7 -5
- data/lib/hexapdf/dictionary_fields.rb +43 -16
- data/lib/hexapdf/digital_signature/cms_handler.rb +2 -2
- data/lib/hexapdf/digital_signature/handler.rb +1 -1
- data/lib/hexapdf/digital_signature/pkcs1_handler.rb +2 -3
- data/lib/hexapdf/digital_signature/signature.rb +6 -6
- data/lib/hexapdf/digital_signature/signatures.rb +13 -12
- data/lib/hexapdf/digital_signature/signing/default_handler.rb +14 -5
- data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +2 -4
- data/lib/hexapdf/digital_signature/signing/timestamp_handler.rb +4 -4
- data/lib/hexapdf/digital_signature/signing.rb +4 -0
- data/lib/hexapdf/digital_signature/verification_result.rb +3 -4
- data/lib/hexapdf/digital_signature.rb +7 -2
- data/lib/hexapdf/document/destinations.rb +12 -11
- data/lib/hexapdf/document/files.rb +1 -1
- data/lib/hexapdf/document/fonts.rb +1 -1
- data/lib/hexapdf/document/layout.rb +170 -39
- data/lib/hexapdf/document/pages.rb +4 -3
- data/lib/hexapdf/document.rb +96 -55
- data/lib/hexapdf/encryption/aes.rb +5 -5
- data/lib/hexapdf/encryption/arc4.rb +1 -1
- data/lib/hexapdf/encryption/fast_aes.rb +2 -2
- data/lib/hexapdf/encryption/fast_arc4.rb +1 -1
- data/lib/hexapdf/encryption/identity.rb +1 -1
- data/lib/hexapdf/encryption/ruby_aes.rb +11 -21
- data/lib/hexapdf/encryption/ruby_arc4.rb +1 -1
- data/lib/hexapdf/encryption/security_handler.rb +31 -24
- data/lib/hexapdf/encryption/standard_security_handler.rb +45 -36
- data/lib/hexapdf/encryption.rb +7 -2
- data/lib/hexapdf/error.rb +18 -0
- data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
- data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
- data/lib/hexapdf/filter/flate_decode.rb +1 -1
- data/lib/hexapdf/filter/lzw_decode.rb +1 -1
- data/lib/hexapdf/filter/pass_through.rb +1 -1
- data/lib/hexapdf/filter/predictor.rb +1 -1
- data/lib/hexapdf/filter/run_length_decode.rb +1 -1
- data/lib/hexapdf/filter.rb +55 -6
- data/lib/hexapdf/font/cmap/parser.rb +2 -2
- data/lib/hexapdf/font/cmap.rb +1 -1
- data/lib/hexapdf/font/encoding/difference_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +2 -2
- data/lib/hexapdf/font/encoding/standard_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/symbol_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/win_ansi_encoding.rb +3 -3
- data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +1 -1
- data/lib/hexapdf/font/invalid_glyph.rb +3 -0
- data/lib/hexapdf/font/true_type_wrapper.rb +17 -4
- data/lib/hexapdf/font/type1_wrapper.rb +19 -4
- data/lib/hexapdf/font_loader/from_configuration.rb +5 -2
- data/lib/hexapdf/font_loader/from_file.rb +5 -5
- data/lib/hexapdf/font_loader/standard14.rb +3 -3
- data/lib/hexapdf/font_loader.rb +3 -0
- data/lib/hexapdf/image_loader/jpeg.rb +2 -2
- data/lib/hexapdf/image_loader/pdf.rb +1 -1
- data/lib/hexapdf/image_loader/png.rb +2 -2
- data/lib/hexapdf/image_loader.rb +1 -1
- data/lib/hexapdf/importer.rb +13 -0
- data/lib/hexapdf/layout/box.rb +32 -5
- data/lib/hexapdf/layout/box_fitter.rb +2 -2
- data/lib/hexapdf/layout/column_box.rb +20 -5
- data/lib/hexapdf/layout/frame.rb +53 -18
- data/lib/hexapdf/layout/image_box.rb +5 -0
- data/lib/hexapdf/layout/inline_box.rb +21 -9
- data/lib/hexapdf/layout/list_box.rb +50 -20
- data/lib/hexapdf/layout/page_style.rb +6 -5
- data/lib/hexapdf/layout/style.rb +64 -9
- data/lib/hexapdf/layout/table_box.rb +684 -0
- data/lib/hexapdf/layout/text_box.rb +12 -3
- data/lib/hexapdf/layout/text_fragment.rb +29 -3
- data/lib/hexapdf/layout/text_layouter.rb +32 -8
- data/lib/hexapdf/layout.rb +1 -0
- data/lib/hexapdf/name_tree_node.rb +1 -1
- data/lib/hexapdf/number_tree_node.rb +1 -1
- data/lib/hexapdf/object.rb +18 -7
- data/lib/hexapdf/parser.rb +7 -7
- data/lib/hexapdf/pdf_array.rb +1 -1
- data/lib/hexapdf/rectangle.rb +1 -1
- data/lib/hexapdf/reference.rb +1 -1
- data/lib/hexapdf/revision.rb +1 -1
- data/lib/hexapdf/revisions.rb +3 -3
- data/lib/hexapdf/serializer.rb +15 -15
- data/lib/hexapdf/stream.rb +5 -4
- data/lib/hexapdf/tokenizer.rb +14 -14
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +22 -22
- data/lib/hexapdf/type/acro_form/button_field.rb +1 -1
- data/lib/hexapdf/type/acro_form/choice_field.rb +1 -1
- data/lib/hexapdf/type/acro_form/field.rb +2 -2
- data/lib/hexapdf/type/acro_form/form.rb +1 -1
- data/lib/hexapdf/type/acro_form/signature_field.rb +4 -4
- data/lib/hexapdf/type/acro_form/text_field.rb +1 -1
- data/lib/hexapdf/type/acro_form/variable_text_field.rb +1 -1
- data/lib/hexapdf/type/acro_form.rb +1 -1
- data/lib/hexapdf/type/action.rb +1 -1
- data/lib/hexapdf/type/actions/go_to.rb +1 -1
- data/lib/hexapdf/type/actions/go_to_r.rb +1 -1
- data/lib/hexapdf/type/actions/launch.rb +1 -1
- data/lib/hexapdf/type/actions/set_ocg_state.rb +86 -0
- data/lib/hexapdf/type/actions/uri.rb +1 -1
- data/lib/hexapdf/type/actions.rb +2 -1
- data/lib/hexapdf/type/annotation.rb +3 -3
- data/lib/hexapdf/type/annotations/link.rb +1 -1
- data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
- data/lib/hexapdf/type/annotations/text.rb +2 -3
- data/lib/hexapdf/type/annotations/widget.rb +2 -2
- data/lib/hexapdf/type/annotations.rb +1 -1
- data/lib/hexapdf/type/catalog.rb +11 -2
- data/lib/hexapdf/type/cid_font.rb +18 -4
- data/lib/hexapdf/type/embedded_file.rb +1 -1
- data/lib/hexapdf/type/file_specification.rb +2 -2
- data/lib/hexapdf/type/font_descriptor.rb +1 -1
- data/lib/hexapdf/type/font_simple.rb +2 -2
- data/lib/hexapdf/type/font_type0.rb +3 -3
- data/lib/hexapdf/type/font_type3.rb +1 -1
- data/lib/hexapdf/type/form.rb +76 -6
- data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
- data/lib/hexapdf/type/icon_fit.rb +1 -1
- data/lib/hexapdf/type/image.rb +1 -1
- data/lib/hexapdf/type/info.rb +1 -1
- data/lib/hexapdf/type/mark_information.rb +1 -1
- data/lib/hexapdf/type/names.rb +2 -2
- data/lib/hexapdf/type/object_stream.rb +2 -1
- data/lib/hexapdf/type/optional_content_configuration.rb +170 -0
- data/lib/hexapdf/type/optional_content_group.rb +370 -0
- data/lib/hexapdf/type/optional_content_membership.rb +63 -0
- data/lib/hexapdf/type/optional_content_properties.rb +158 -0
- data/lib/hexapdf/type/outline.rb +1 -1
- data/lib/hexapdf/type/outline_item.rb +1 -1
- data/lib/hexapdf/type/page.rb +46 -21
- data/lib/hexapdf/type/page_label.rb +5 -9
- data/lib/hexapdf/type/page_tree_node.rb +1 -1
- data/lib/hexapdf/type/resources.rb +1 -1
- data/lib/hexapdf/type/trailer.rb +2 -2
- data/lib/hexapdf/type/viewer_preferences.rb +1 -1
- data/lib/hexapdf/type/xref_stream.rb +2 -2
- data/lib/hexapdf/type.rb +4 -0
- data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -2
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/writer.rb +4 -4
- data/lib/hexapdf/xref_section.rb +2 -2
- data/test/hexapdf/content/graphic_object/test_endpoint_arc.rb +11 -1
- data/test/hexapdf/content/graphic_object/test_geom2d.rb +7 -0
- data/test/hexapdf/content/test_canvas.rb +49 -1
- data/test/hexapdf/digital_signature/test_signatures.rb +22 -0
- data/test/hexapdf/document/test_files.rb +2 -2
- data/test/hexapdf/document/test_layout.rb +105 -2
- data/test/hexapdf/document/test_pages.rb +6 -6
- data/test/hexapdf/encryption/test_security_handler.rb +12 -11
- data/test/hexapdf/encryption/test_standard_security_handler.rb +35 -23
- data/test/hexapdf/font/test_true_type_wrapper.rb +18 -1
- data/test/hexapdf/font/test_type1_wrapper.rb +15 -1
- data/test/hexapdf/layout/test_box.rb +14 -5
- data/test/hexapdf/layout/test_column_box.rb +65 -21
- data/test/hexapdf/layout/test_frame.rb +27 -15
- data/test/hexapdf/layout/test_image_box.rb +4 -0
- data/test/hexapdf/layout/test_inline_box.rb +17 -3
- data/test/hexapdf/layout/test_list_box.rb +84 -33
- data/test/hexapdf/layout/test_page_style.rb +3 -2
- data/test/hexapdf/layout/test_style.rb +60 -0
- data/test/hexapdf/layout/test_table_box.rb +728 -0
- data/test/hexapdf/layout/test_text_box.rb +26 -0
- data/test/hexapdf/layout/test_text_fragment.rb +33 -0
- data/test/hexapdf/layout/test_text_layouter.rb +36 -5
- data/test/hexapdf/test_composer.rb +10 -0
- data/test/hexapdf/test_dictionary.rb +10 -0
- data/test/hexapdf/test_dictionary_fields.rb +4 -1
- data/test/hexapdf/test_document.rb +5 -0
- data/test/hexapdf/test_filter.rb +8 -0
- data/test/hexapdf/test_importer.rb +9 -0
- data/test/hexapdf/test_object.rb +16 -5
- data/test/hexapdf/test_stream.rb +7 -0
- data/test/hexapdf/test_writer.rb +3 -3
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +13 -5
- data/test/hexapdf/type/acro_form/test_form.rb +4 -3
- data/test/hexapdf/type/actions/test_set_ocg_state.rb +40 -0
- data/test/hexapdf/type/test_catalog.rb +11 -0
- data/test/hexapdf/type/test_form.rb +119 -0
- data/test/hexapdf/type/test_optional_content_configuration.rb +112 -0
- data/test/hexapdf/type/test_optional_content_group.rb +158 -0
- data/test/hexapdf/type/test_optional_content_properties.rb +109 -0
- data/test/hexapdf/type/test_page.rb +20 -6
- metadata +28 -8
data/lib/hexapdf/type/page.rb
CHANGED
@@ -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:
|
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:
|
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
|
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
|
-
#
|
268
|
-
#
|
269
|
-
#
|
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.
|
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]
|
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 || []
|
553
|
+
not_flattened = Array(annotations) || []
|
543
554
|
return not_flattened unless key?(:Annots)
|
544
555
|
|
545
|
-
annotations =
|
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.
|
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
|
-
#
|
571
|
-
# Step
|
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
|
586
|
-
|
587
|
-
|
588
|
-
|
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
|
615
|
+
# Step 3) Premultiply form matrix - done implicitly when drawing the XObject
|
591
616
|
|
592
|
-
canvas.transform(
|
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:
|
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",
|
data/lib/hexapdf/type/trailer.rb
CHANGED
@@ -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:
|
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:
|
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
|
@@ -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:
|
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
|
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:
|
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
|
data/lib/hexapdf/version.rb
CHANGED
data/lib/hexapdf/writer.rb
CHANGED
@@ -124,7 +124,7 @@ module HexaPDF
|
|
124
124
|
|
125
125
|
# Writes the PDF file header.
|
126
126
|
#
|
127
|
-
# See:
|
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:
|
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:
|
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:
|
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
|
data/lib/hexapdf/xref_section.rb
CHANGED
@@ -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,
|
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:
|
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: :
|
312
|
+
box = @layout.formatted_text_box([{box: :column, columns: 1, width: 100, block: block}])
|
261
313
|
ibox = box.instance_variable_get(:@items).first
|
262
|
-
|
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)
|