hexapdf 0.11.9 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +82 -0
- data/LICENSE +1 -1
- data/examples/001-hello_world.rb +1 -1
- data/examples/002-graphics.rb +1 -1
- data/examples/003-arcs.rb +1 -1
- data/examples/004-optimizing.rb +1 -1
- data/examples/005-merging.rb +1 -1
- data/examples/006-standard_pdf_fonts.rb +1 -1
- data/examples/007-truetype.rb +1 -1
- data/examples/008-show_char_bboxes.rb +1 -1
- data/examples/009-text_layouter_alignment.rb +1 -1
- data/examples/010-text_layouter_inline_boxes.rb +1 -1
- data/examples/011-text_layouter_line_wrapping.rb +1 -1
- data/examples/012-text_layouter_styling.rb +1 -1
- data/examples/013-text_layouter_shapes.rb +1 -1
- data/examples/014-text_in_polygon.rb +1 -1
- data/examples/015-boxes.rb +1 -1
- data/examples/016-frame_automatic_box_placement.rb +1 -1
- data/examples/017-frame_text_flow.rb +1 -1
- data/examples/018-composer.rb +1 -1
- data/examples/019-acro_form.rb +51 -0
- data/lib/hexapdf.rb +1 -1
- data/lib/hexapdf/cli.rb +3 -1
- data/lib/hexapdf/cli/batch.rb +1 -1
- data/lib/hexapdf/cli/command.rb +18 -9
- data/lib/hexapdf/cli/files.rb +1 -1
- data/lib/hexapdf/cli/form.rb +240 -0
- data/lib/hexapdf/cli/image2pdf.rb +1 -1
- data/lib/hexapdf/cli/images.rb +1 -1
- data/lib/hexapdf/cli/info.rb +1 -1
- data/lib/hexapdf/cli/inspect.rb +1 -1
- data/lib/hexapdf/cli/merge.rb +1 -1
- data/lib/hexapdf/cli/modify.rb +1 -1
- data/lib/hexapdf/cli/optimize.rb +1 -1
- data/lib/hexapdf/cli/split.rb +1 -1
- data/lib/hexapdf/cli/watermark.rb +1 -1
- data/lib/hexapdf/composer.rb +2 -2
- data/lib/hexapdf/configuration.rb +66 -11
- data/lib/hexapdf/content.rb +3 -1
- data/lib/hexapdf/content/canvas.rb +5 -18
- data/lib/hexapdf/content/color_space.rb +111 -32
- data/lib/hexapdf/content/graphic_object.rb +1 -1
- data/lib/hexapdf/content/graphic_object/arc.rb +1 -1
- data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +1 -1
- data/lib/hexapdf/content/graphic_object/geom2d.rb +1 -1
- data/lib/hexapdf/content/graphic_object/solid_arc.rb +1 -1
- data/lib/hexapdf/content/graphics_state.rb +1 -1
- data/lib/hexapdf/content/operator.rb +9 -9
- data/lib/hexapdf/content/parser.rb +18 -5
- data/lib/hexapdf/content/processor.rb +1 -1
- data/lib/hexapdf/content/transformation_matrix.rb +1 -1
- data/lib/hexapdf/data_dir.rb +1 -1
- data/lib/hexapdf/dictionary.rb +1 -1
- data/lib/hexapdf/dictionary_fields.rb +1 -1
- data/lib/hexapdf/document.rb +14 -5
- data/lib/hexapdf/document/files.rb +1 -1
- data/lib/hexapdf/document/fonts.rb +1 -1
- data/lib/hexapdf/document/images.rb +1 -1
- data/lib/hexapdf/document/pages.rb +3 -14
- data/lib/hexapdf/encryption.rb +1 -1
- data/lib/hexapdf/encryption/aes.rb +1 -1
- data/lib/hexapdf/encryption/arc4.rb +1 -1
- data/lib/hexapdf/encryption/fast_aes.rb +1 -1
- data/lib/hexapdf/encryption/fast_arc4.rb +1 -1
- data/lib/hexapdf/encryption/identity.rb +1 -1
- data/lib/hexapdf/encryption/ruby_aes.rb +1 -1
- data/lib/hexapdf/encryption/ruby_arc4.rb +1 -1
- data/lib/hexapdf/encryption/security_handler.rb +1 -1
- data/lib/hexapdf/encryption/standard_security_handler.rb +1 -1
- data/lib/hexapdf/error.rb +1 -1
- data/lib/hexapdf/filter.rb +3 -3
- data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
- data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
- data/lib/hexapdf/filter/encryption.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/{jpx_decode.rb → pass_through.rb} +5 -5
- data/lib/hexapdf/filter/predictor.rb +1 -1
- data/lib/hexapdf/filter/run_length_decode.rb +1 -1
- data/lib/hexapdf/font/cmap.rb +1 -1
- data/lib/hexapdf/font/cmap/parser.rb +1 -1
- data/lib/hexapdf/font/cmap/writer.rb +1 -1
- data/lib/hexapdf/font/encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/base.rb +1 -1
- data/lib/hexapdf/font/encoding/difference_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/glyph_list.rb +1 -1
- data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +1 -1
- 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 +1 -1
- data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +1 -1
- data/lib/hexapdf/font/invalid_glyph.rb +1 -1
- data/lib/hexapdf/font/true_type.rb +1 -1
- data/lib/hexapdf/font/true_type/builder.rb +1 -1
- data/lib/hexapdf/font/true_type/font.rb +1 -1
- data/lib/hexapdf/font/true_type/optimizer.rb +1 -1
- data/lib/hexapdf/font/true_type/subsetter.rb +1 -1
- data/lib/hexapdf/font/true_type/table.rb +1 -1
- data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
- data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +1 -1
- data/lib/hexapdf/font/true_type/table/directory.rb +1 -1
- data/lib/hexapdf/font/true_type/table/glyf.rb +1 -1
- data/lib/hexapdf/font/true_type/table/head.rb +1 -1
- data/lib/hexapdf/font/true_type/table/hhea.rb +1 -1
- data/lib/hexapdf/font/true_type/table/hmtx.rb +1 -1
- data/lib/hexapdf/font/true_type/table/kern.rb +1 -1
- data/lib/hexapdf/font/true_type/table/loca.rb +1 -1
- data/lib/hexapdf/font/true_type/table/maxp.rb +1 -1
- data/lib/hexapdf/font/true_type/table/name.rb +1 -1
- data/lib/hexapdf/font/true_type/table/os2.rb +1 -1
- data/lib/hexapdf/font/true_type/table/post.rb +1 -1
- data/lib/hexapdf/font/true_type_wrapper.rb +54 -51
- data/lib/hexapdf/font/type1.rb +1 -1
- data/lib/hexapdf/font/type1/afm_parser.rb +1 -1
- data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
- data/lib/hexapdf/font/type1/font.rb +1 -1
- data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
- data/lib/hexapdf/font/type1/pfb_parser.rb +1 -1
- data/lib/hexapdf/font/type1_wrapper.rb +67 -51
- data/lib/hexapdf/font_loader.rb +1 -1
- data/lib/hexapdf/font_loader/from_configuration.rb +1 -1
- data/lib/hexapdf/font_loader/from_file.rb +1 -1
- data/lib/hexapdf/font_loader/standard14.rb +1 -1
- data/lib/hexapdf/image_loader.rb +1 -1
- data/lib/hexapdf/image_loader/jpeg.rb +1 -1
- data/lib/hexapdf/image_loader/pdf.rb +1 -1
- data/lib/hexapdf/image_loader/png.rb +1 -1
- data/lib/hexapdf/importer.rb +2 -4
- data/lib/hexapdf/layout.rb +1 -1
- data/lib/hexapdf/layout/box.rb +1 -1
- data/lib/hexapdf/layout/frame.rb +1 -1
- data/lib/hexapdf/layout/image_box.rb +1 -1
- data/lib/hexapdf/layout/inline_box.rb +1 -1
- data/lib/hexapdf/layout/line.rb +1 -1
- data/lib/hexapdf/layout/numeric_refinements.rb +1 -1
- data/lib/hexapdf/layout/style.rb +1 -1
- data/lib/hexapdf/layout/text_box.rb +1 -1
- data/lib/hexapdf/layout/text_fragment.rb +1 -1
- data/lib/hexapdf/layout/text_layouter.rb +1 -1
- data/lib/hexapdf/layout/text_shaper.rb +1 -1
- data/lib/hexapdf/layout/width_from_polygon.rb +1 -1
- data/lib/hexapdf/name_tree_node.rb +1 -1
- data/lib/hexapdf/number_tree_node.rb +1 -1
- data/lib/hexapdf/object.rb +2 -2
- data/lib/hexapdf/parser.rb +4 -3
- data/lib/hexapdf/pdf_array.rb +1 -1
- data/lib/hexapdf/rectangle.rb +31 -1
- data/lib/hexapdf/reference.rb +1 -1
- data/lib/hexapdf/revision.rb +2 -1
- data/lib/hexapdf/revisions.rb +1 -1
- data/lib/hexapdf/serializer.rb +1 -1
- data/lib/hexapdf/stream.rb +1 -1
- data/lib/hexapdf/task.rb +1 -1
- data/lib/hexapdf/task/dereference.rb +1 -1
- data/lib/hexapdf/task/optimize.rb +1 -1
- data/lib/hexapdf/tokenizer.rb +1 -1
- data/lib/hexapdf/type.rb +1 -1
- data/lib/hexapdf/type/acro_form.rb +7 -1
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +401 -0
- data/lib/hexapdf/type/acro_form/button_field.rb +300 -0
- data/lib/hexapdf/type/acro_form/choice_field.rb +220 -0
- data/lib/hexapdf/type/acro_form/field.rb +220 -17
- data/lib/hexapdf/type/acro_form/form.rb +157 -7
- data/lib/hexapdf/type/acro_form/text_field.rb +186 -0
- data/lib/hexapdf/type/acro_form/variable_text_field.rb +122 -0
- data/lib/hexapdf/type/action.rb +1 -1
- data/lib/hexapdf/type/actions.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/uri.rb +1 -1
- data/lib/hexapdf/type/annotation.rb +73 -3
- data/lib/hexapdf/type/annotations.rb +1 -1
- data/lib/hexapdf/type/annotations/link.rb +2 -2
- data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
- data/lib/hexapdf/type/annotations/text.rb +1 -1
- data/lib/hexapdf/type/annotations/widget.rb +239 -2
- data/lib/hexapdf/type/catalog.rb +21 -1
- data/lib/hexapdf/type/cid_font.rb +1 -1
- data/lib/hexapdf/type/embedded_file.rb +1 -1
- data/lib/hexapdf/type/file_specification.rb +1 -1
- data/lib/hexapdf/type/font.rb +18 -1
- data/lib/hexapdf/type/font_descriptor.rb +2 -2
- data/lib/hexapdf/type/font_simple.rb +1 -1
- data/lib/hexapdf/type/font_true_type.rb +1 -1
- data/lib/hexapdf/type/font_type0.rb +1 -1
- data/lib/hexapdf/type/font_type1.rb +16 -1
- data/lib/hexapdf/type/font_type3.rb +1 -1
- data/lib/hexapdf/type/form.rb +1 -1
- 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 +3 -1
- data/lib/hexapdf/type/info.rb +1 -1
- data/lib/hexapdf/type/names.rb +1 -1
- data/lib/hexapdf/type/object_stream.rb +1 -1
- data/lib/hexapdf/type/page.rb +1 -1
- data/lib/hexapdf/type/page_tree_node.rb +8 -11
- data/lib/hexapdf/type/resources.rb +16 -3
- data/lib/hexapdf/type/trailer.rb +2 -3
- data/lib/hexapdf/type/viewer_preferences.rb +1 -1
- data/lib/hexapdf/type/xref_stream.rb +1 -1
- data/lib/hexapdf/utils/bit_field.rb +38 -24
- data/lib/hexapdf/utils/bit_stream.rb +1 -1
- data/lib/hexapdf/utils/graphics_helpers.rb +1 -1
- data/lib/hexapdf/utils/lru_cache.rb +1 -1
- data/lib/hexapdf/utils/math_helpers.rb +1 -1
- data/lib/hexapdf/utils/object_hash.rb +1 -1
- data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -1
- data/lib/hexapdf/utils/sorted_tree_node.rb +1 -1
- data/lib/hexapdf/version.rb +2 -2
- data/lib/hexapdf/writer.rb +1 -1
- data/lib/hexapdf/xref_section.rb +1 -1
- data/test/hexapdf/content/common.rb +2 -2
- data/test/hexapdf/content/test_color_space.rb +71 -8
- data/test/hexapdf/content/test_operator.rb +22 -22
- data/test/hexapdf/content/test_parser.rb +14 -0
- data/test/hexapdf/document/test_fonts.rb +1 -1
- data/test/hexapdf/document/test_pages.rb +6 -6
- data/test/hexapdf/font/test_true_type_wrapper.rb +10 -7
- data/test/hexapdf/font/test_type1_wrapper.rb +32 -8
- data/test/hexapdf/test_document.rb +12 -0
- data/test/hexapdf/test_parser.rb +10 -0
- data/test/hexapdf/test_rectangle.rb +14 -0
- data/test/hexapdf/test_revision.rb +3 -0
- data/test/hexapdf/test_writer.rb +2 -2
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +515 -0
- data/test/hexapdf/type/acro_form/test_button_field.rb +276 -0
- data/test/hexapdf/type/acro_form/test_choice_field.rb +137 -0
- data/test/hexapdf/type/acro_form/test_field.rb +124 -6
- data/test/hexapdf/type/acro_form/test_form.rb +189 -22
- data/test/hexapdf/type/acro_form/test_text_field.rb +119 -0
- data/test/hexapdf/type/acro_form/test_variable_text_field.rb +77 -0
- data/test/hexapdf/type/annotations/test_text.rb +1 -1
- data/test/hexapdf/type/annotations/test_widget.rb +199 -0
- data/test/hexapdf/type/test_annotation.rb +45 -0
- data/test/hexapdf/type/test_catalog.rb +18 -0
- data/test/hexapdf/type/test_font.rb +5 -0
- data/test/hexapdf/type/test_font_type1.rb +8 -0
- data/test/hexapdf/type/test_image.rb +7 -0
- data/test/hexapdf/type/test_page_tree_node.rb +20 -12
- data/test/hexapdf/type/test_resources.rb +20 -0
- data/test/hexapdf/type/test_trailer.rb +4 -0
- data/test/hexapdf/utils/test_bit_field.rb +13 -1
- data/test/test_helper.rb +1 -1
- metadata +37 -18
- data/lib/hexapdf/filter/dct_decode.rb +0 -60
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 34c65a5eca10ade6778049ce85a8913ce180c32fed6826254e61a9cd801482fb
|
|
4
|
+
data.tar.gz: 7fd2cd5870e03acd71e5d723e41199664d9cabd2ddb1fd9430f997918cc15dd3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 843a8e201d625d0d1d4a6755aeecf0d2ed810372058d1fec0136bf781e51ed9c46346d3d52fb4d0eda50100aac0b29112f6e4d42c3555bc923deee4583dbba00
|
|
7
|
+
data.tar.gz: d2042cb0b6cbd82bae3d6252a87ef68ee7791e02bf87e96a233d10bf3bb37eb991dca63c7293396ad5c75d03ecc668938722dbc03ade777131c4cc190cbfbda4
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,85 @@
|
|
|
1
|
+
## 0.12.0 - 2020-08-12
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
* Convenience methods for accessing field flags for
|
|
6
|
+
[HexaPDF::Type::AcroForm::Field]
|
|
7
|
+
* [HexaPDF::Type::AcroForm::TextField] and
|
|
8
|
+
[HexaPDF::Type::AcroForm::VariableTextField] for basic text field support
|
|
9
|
+
* [HexaPDF::Type::AcroForm::ButtonField] for push button, radio button and
|
|
10
|
+
check box support
|
|
11
|
+
* [HexaPDF::Type::AcroForm::ChoiceField] for combo box and list box support
|
|
12
|
+
* [HexaPDF::Type::AcroForm::AppearanceGenerator] as central class for
|
|
13
|
+
generating appearance streams for form fields
|
|
14
|
+
* Various convenience methods for [HexaPDF::Type::AcroForm::Form]
|
|
15
|
+
* Various convenience methods for [HexaPDF::Type::AcroForm::Field]
|
|
16
|
+
* Various convenience methods for [HexaPDF::Type::Annotations::Widget]
|
|
17
|
+
* [HexaPDF::Type::Annotation::AppearanceDictionary]
|
|
18
|
+
* [HexaPDF::Document#acro_form] and [HexaPDF::Type::Catalog#acro_form]
|
|
19
|
+
convenience methods
|
|
20
|
+
* CLI command `hexapdf form` for listing fields of interactive forms and filling
|
|
21
|
+
them out
|
|
22
|
+
* [HexaPDF::Rectangle] methods for setting the left, top, right, bottom, width
|
|
23
|
+
and height
|
|
24
|
+
* Method #prenormalized_color to all color space implementations
|
|
25
|
+
* [HexaPDF::Type::Font#font_wrapper] for accessing an associated font wrapper
|
|
26
|
+
instance
|
|
27
|
+
* [HexaPDF::Type::FontType1#font_wrapper] for providing a font wrapper for the
|
|
28
|
+
standard PDF fonts
|
|
29
|
+
* [HexaPDF::Type::Annotation::Border] class
|
|
30
|
+
* [HexaPDF::Content::ColorSpace::device_color_from_specification] for easily
|
|
31
|
+
getting a device color object
|
|
32
|
+
* [HexaPDF::Content::ColorSpace::prenormalized_device_color] for getting a device
|
|
33
|
+
color object without normalizing values
|
|
34
|
+
* [HexaPDF::Type::Annotation#appearance] for returning the associated appearance
|
|
35
|
+
dictionary
|
|
36
|
+
* [HexaPDF::Type::Annotation#appearance?] for checking whether an appearance for
|
|
37
|
+
the annotation exists
|
|
38
|
+
* Configuration option 'acro_form.create_appearance_streams' for automatically
|
|
39
|
+
creating appearance streams
|
|
40
|
+
* [HexaPDF::Type::Resources] methods `#pattern` and `add_pattern`
|
|
41
|
+
|
|
42
|
+
### Changed
|
|
43
|
+
|
|
44
|
+
* Deletion of pages to delete them from the document as well
|
|
45
|
+
* Refactored [HexaPDF::Font::Type1Wrapper] and [HexaPDF::Font::TrueTypeWrapper]
|
|
46
|
+
and renamed `#dict` to `#pdf_object`
|
|
47
|
+
* Fall back to the Type1 font's internal encoding when decoding a string
|
|
48
|
+
* All [HexaPDF::Content::ColorSpace] implementations to only normalize values
|
|
49
|
+
when using the ::color method
|
|
50
|
+
* [HexaPDF::Content::Parser#parse] to also accept a block in place of a
|
|
51
|
+
processor object
|
|
52
|
+
* HexaPDF::Type::AcroForm::Field#full_name to
|
|
53
|
+
[HexaPDF::Type::AcroForm::Field#full_field_name]
|
|
54
|
+
* Moved `HexaPDF::Content::Canvas#color_space_for_components` to class method on
|
|
55
|
+
[HexaPDF::Content::ColorSpace]
|
|
56
|
+
* Added bit unsetter method to[HexaPDF::Utils::BitField]
|
|
57
|
+
* [HexaPDF::Type::AcroForm::Form#find_root_fields] and `#each_field` to take the
|
|
58
|
+
field type into account when wrapping a field dictionary
|
|
59
|
+
* Pages specification of CLI commands to allow counting from the end using the
|
|
60
|
+
new `r<N>` notation
|
|
61
|
+
* [HexaPDF::Font::Type1Wrapper] to use the internal encoding of a font with a
|
|
62
|
+
'Special' character set instead of a custom encoding
|
|
63
|
+
* Configuration 'filter.map' to use the pass-through filter on all unsupported
|
|
64
|
+
filters
|
|
65
|
+
|
|
66
|
+
### Fixed
|
|
67
|
+
|
|
68
|
+
* Wrong normalization of color values when invoking a color operator
|
|
69
|
+
* Invalid type of `/DR` field of [HexaPDF::Type::AcroForm::Form]
|
|
70
|
+
* Invalid ordering of types for the `/V` and `/DV` fields of
|
|
71
|
+
[HexaPDF::Type::AcroForm::Field]
|
|
72
|
+
* [HexaPDF::Type::AcroForm::Field#terminal_field?] to work according to the spec
|
|
73
|
+
* Handling of empty files by throwing better error messages
|
|
74
|
+
* [HexaPDF::Type::Image#info] to correctly identify images with a soft mask as
|
|
75
|
+
currently not supported for writing
|
|
76
|
+
* [HexaPDF::Revision#delete] to remove the connection between the object and the
|
|
77
|
+
document
|
|
78
|
+
* Missing `#definition` method of `DeviceRGB`, `DeviceCMYK` and `DeviceGray`
|
|
79
|
+
color spaces
|
|
80
|
+
* Handling of 'Pattern' color spaces when parsing content streams
|
|
81
|
+
|
|
82
|
+
|
|
1
83
|
## 0.11.9 - 2020-06-15
|
|
2
84
|
|
|
3
85
|
### Changed
|
data/LICENSE
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
|
2
|
-
Copyright (C) 2014-
|
|
2
|
+
Copyright (C) 2014-2020 Thomas Leitner
|
|
3
3
|
|
|
4
4
|
HexaPDF is free software: you can redistribute it and/or modify it
|
|
5
5
|
under the terms of the GNU Affero General Public License version 3 as
|
data/examples/001-hello_world.rb
CHANGED
data/examples/002-graphics.rb
CHANGED
data/examples/003-arcs.rb
CHANGED
data/examples/004-optimizing.rb
CHANGED
data/examples/005-merging.rb
CHANGED
data/examples/007-truetype.rb
CHANGED
data/examples/015-boxes.rb
CHANGED
data/examples/018-composer.rb
CHANGED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# # PDF Forms
|
|
2
|
+
#
|
|
3
|
+
# PDF files can be used for interactive forms, containing various types of form
|
|
4
|
+
# fields. HexaPDF supports the creation and processing of these forms.
|
|
5
|
+
#
|
|
6
|
+
# This example show-cases how to create the various form field types and their
|
|
7
|
+
# possible standard appearances.
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# : `ruby acro_form.rb`
|
|
11
|
+
#
|
|
12
|
+
|
|
13
|
+
require 'hexapdf'
|
|
14
|
+
|
|
15
|
+
doc = HexaPDF::Document.new
|
|
16
|
+
page = doc.pages.add
|
|
17
|
+
canvas = page.canvas
|
|
18
|
+
|
|
19
|
+
canvas.font("Helvetica", size: 36)
|
|
20
|
+
canvas.text("Form Example", at: [50, 750])
|
|
21
|
+
form = doc.acro_form(create: true)
|
|
22
|
+
|
|
23
|
+
canvas.font_size(16)
|
|
24
|
+
canvas.text("Check boxes", at: [50, 650])
|
|
25
|
+
[:check, :circle, :cross, :diamond, :square, :star].each_with_index do |symbol, index|
|
|
26
|
+
cb = form.create_check_box("Checkbox #{index}")
|
|
27
|
+
widget = cb.create_widget(page, Rect: [200 + 50 * index, 640, 240 + 50 * index, 680])
|
|
28
|
+
widget.background_color(1 - 0.05 * index)
|
|
29
|
+
widget.marker_style(style: symbol, color: [0.166 * index, 0, 1 - 0.166 * index],
|
|
30
|
+
size: 7 * index)
|
|
31
|
+
cb.field_value = true
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
canvas.text("Radio buttons", at: [50, 550])
|
|
35
|
+
rb = form.create_radio_button("Radio")
|
|
36
|
+
[:check, :circle, :cross, :diamond, :square, :star].each_with_index do |symbol, index|
|
|
37
|
+
widget = rb.create_widget(page, value: :"button#{index}",
|
|
38
|
+
Rect: [200 + 50 * index, 540, 240 + 50 * index, 580])
|
|
39
|
+
widget.background_color(1 - 0.05 * index)
|
|
40
|
+
widget.marker_style(style: symbol, color: [0.166 * index, 0, 1 - 0.166 * index],
|
|
41
|
+
size: 7 * index)
|
|
42
|
+
end
|
|
43
|
+
rb.field_value = :button0
|
|
44
|
+
|
|
45
|
+
canvas.text("Text field", at: [50, 450])
|
|
46
|
+
tx = form.create_text_field("Single Line")
|
|
47
|
+
widget = tx.create_widget(page, Rect: [200, 445, 500, 465])
|
|
48
|
+
tx.set_default_appearance_string(font_size: 16)
|
|
49
|
+
tx.field_value = "A sample test string!"
|
|
50
|
+
|
|
51
|
+
doc.write('acro_form.pdf', optimize: true)
|
data/lib/hexapdf.rb
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# This file is part of HexaPDF.
|
|
5
5
|
#
|
|
6
6
|
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
|
7
|
-
# Copyright (C) 2014-
|
|
7
|
+
# Copyright (C) 2014-2020 Thomas Leitner
|
|
8
8
|
#
|
|
9
9
|
# HexaPDF is free software: you can redistribute it and/or modify it
|
|
10
10
|
# under the terms of the GNU Affero General Public License version 3 as
|
data/lib/hexapdf/cli.rb
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# This file is part of HexaPDF.
|
|
5
5
|
#
|
|
6
6
|
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
|
7
|
-
# Copyright (C) 2014-
|
|
7
|
+
# Copyright (C) 2014-2020 Thomas Leitner
|
|
8
8
|
#
|
|
9
9
|
# HexaPDF is free software: you can redistribute it and/or modify it
|
|
10
10
|
# under the terms of the GNU Affero General Public License version 3 as
|
|
@@ -46,6 +46,7 @@ require 'hexapdf/cli/batch'
|
|
|
46
46
|
require 'hexapdf/cli/split'
|
|
47
47
|
require 'hexapdf/cli/watermark'
|
|
48
48
|
require 'hexapdf/cli/image2pdf'
|
|
49
|
+
require 'hexapdf/cli/form'
|
|
49
50
|
require 'hexapdf/version'
|
|
50
51
|
require 'hexapdf/document'
|
|
51
52
|
|
|
@@ -99,6 +100,7 @@ module HexaPDF
|
|
|
99
100
|
add_command(HexaPDF::CLI::Split.new)
|
|
100
101
|
add_command(HexaPDF::CLI::Watermark.new)
|
|
101
102
|
add_command(HexaPDF::CLI::Image2PDF.new)
|
|
103
|
+
add_command(HexaPDF::CLI::Form.new)
|
|
102
104
|
add_command(CmdParse::HelpCommand.new)
|
|
103
105
|
version_command = CmdParse::VersionCommand.new(add_switches: false)
|
|
104
106
|
add_command(version_command)
|
data/lib/hexapdf/cli/batch.rb
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# This file is part of HexaPDF.
|
|
5
5
|
#
|
|
6
6
|
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
|
7
|
-
# Copyright (C) 2014-
|
|
7
|
+
# Copyright (C) 2014-2020 Thomas Leitner
|
|
8
8
|
#
|
|
9
9
|
# HexaPDF is free software: you can redistribute it and/or modify it
|
|
10
10
|
# under the terms of the GNU Affero General Public License version 3 as
|
data/lib/hexapdf/cli/command.rb
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# This file is part of HexaPDF.
|
|
5
5
|
#
|
|
6
6
|
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
|
7
|
-
# Copyright (C) 2014-
|
|
7
|
+
# Copyright (C) 2014-2020 Thomas Leitner
|
|
8
8
|
#
|
|
9
9
|
# HexaPDF is free software: you can redistribute it and/or modify it
|
|
10
10
|
# under the terms of the GNU Affero General Public License version 3 as
|
|
@@ -80,7 +80,7 @@ module HexaPDF
|
|
|
80
80
|
# Creates a HexaPDF::Document instance for the PDF file and yields it.
|
|
81
81
|
#
|
|
82
82
|
# If +out_file+ is given, the document is written to it after yielding.
|
|
83
|
-
def with_document(file, password: nil, out_file: nil) #:yield: document
|
|
83
|
+
def with_document(file, password: nil, out_file: nil, incremental: false) #:yield: document
|
|
84
84
|
if file == out_file
|
|
85
85
|
doc = HexaPDF::Document.open(file, **pdf_options(password))
|
|
86
86
|
else
|
|
@@ -90,7 +90,7 @@ module HexaPDF
|
|
|
90
90
|
|
|
91
91
|
yield(doc)
|
|
92
92
|
|
|
93
|
-
write_document(doc, out_file)
|
|
93
|
+
write_document(doc, out_file, incremental: incremental)
|
|
94
94
|
ensure
|
|
95
95
|
file_io&.close
|
|
96
96
|
end
|
|
@@ -116,7 +116,7 @@ module HexaPDF
|
|
|
116
116
|
end
|
|
117
117
|
|
|
118
118
|
# Writes the document to the given file or does nothing if +out_file+ is +nil+.
|
|
119
|
-
def write_document(doc, out_file)
|
|
119
|
+
def write_document(doc, out_file, incremental: false)
|
|
120
120
|
if out_file
|
|
121
121
|
doc.validate(auto_correct: true) do |object, msg, correctable|
|
|
122
122
|
if command_parser.strict && !correctable
|
|
@@ -126,7 +126,7 @@ module HexaPDF
|
|
|
126
126
|
"for object (#{object.oid},#{object.gen}): #{msg}"
|
|
127
127
|
end
|
|
128
128
|
end
|
|
129
|
-
doc.write(out_file, validate: false)
|
|
129
|
+
doc.write(out_file, validate: false, incremental: incremental)
|
|
130
130
|
end
|
|
131
131
|
end
|
|
132
132
|
|
|
@@ -289,7 +289,16 @@ module HexaPDF
|
|
|
289
289
|
end
|
|
290
290
|
end
|
|
291
291
|
|
|
292
|
-
PAGE_NUMBER_SPEC = "([1-9]\\d*|e)" #:nodoc:
|
|
292
|
+
PAGE_NUMBER_SPEC = "(r?[1-9]\\d*|e)" #:nodoc:
|
|
293
|
+
PAGE_MAP = lambda do |result, count|
|
|
294
|
+
if result == 'e'
|
|
295
|
+
count
|
|
296
|
+
elsif result.start_with?('r')
|
|
297
|
+
count - result[1..-1].to_i + 1
|
|
298
|
+
else
|
|
299
|
+
result.to_i
|
|
300
|
+
end
|
|
301
|
+
end
|
|
293
302
|
ROTATE_MAP = {'l' => 90, 'r' => -90, 'd' => 180, 'n' => :none}.freeze #:nodoc:
|
|
294
303
|
|
|
295
304
|
# Parses the pages specification string and returns an array of tuples containing a page
|
|
@@ -304,12 +313,12 @@ module HexaPDF
|
|
|
304
313
|
range.split(',').each_with_object([]) do |str, arr|
|
|
305
314
|
case str
|
|
306
315
|
when /\A#{PAGE_NUMBER_SPEC}(l|r|d|n)?\z/o
|
|
307
|
-
page_num =
|
|
316
|
+
page_num = PAGE_MAP[$1, count]
|
|
308
317
|
next if page_num > count
|
|
309
318
|
arr << [page_num - 1, ROTATE_MAP[$2]]
|
|
310
319
|
when /\A#{PAGE_NUMBER_SPEC}-#{PAGE_NUMBER_SPEC}(?:\/([1-9]\d*))?(l|r|d|n)?\z/o
|
|
311
|
-
start_nr =
|
|
312
|
-
end_nr =
|
|
320
|
+
start_nr = [PAGE_MAP[$1, count], count].min - 1
|
|
321
|
+
end_nr = [PAGE_MAP[$2, count], count].min - 1
|
|
313
322
|
step = ($3 ? $3.to_i : 1) * (start_nr > end_nr ? -1 : 1)
|
|
314
323
|
rotation = ROTATE_MAP[$4]
|
|
315
324
|
start_nr.step(to: end_nr, by: step) {|n| arr << [n, rotation] }
|
data/lib/hexapdf/cli/files.rb
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# This file is part of HexaPDF.
|
|
5
5
|
#
|
|
6
6
|
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
|
7
|
-
# Copyright (C) 2014-
|
|
7
|
+
# Copyright (C) 2014-2020 Thomas Leitner
|
|
8
8
|
#
|
|
9
9
|
# HexaPDF is free software: you can redistribute it and/or modify it
|
|
10
10
|
# under the terms of the GNU Affero General Public License version 3 as
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# -*- encoding: utf-8; frozen_string_literal: true -*-
|
|
2
|
+
#
|
|
3
|
+
#--
|
|
4
|
+
# This file is part of HexaPDF.
|
|
5
|
+
#
|
|
6
|
+
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
|
7
|
+
# Copyright (C) 2014-2020 Thomas Leitner
|
|
8
|
+
#
|
|
9
|
+
# HexaPDF is free software: you can redistribute it and/or modify it
|
|
10
|
+
# under the terms of the GNU Affero General Public License version 3 as
|
|
11
|
+
# published by the Free Software Foundation with the addition of the
|
|
12
|
+
# following permission added to Section 15 as permitted in Section 7(a):
|
|
13
|
+
# FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
|
|
14
|
+
# THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
|
|
15
|
+
# INFRINGEMENT OF THIRD PARTY RIGHTS.
|
|
16
|
+
#
|
|
17
|
+
# HexaPDF is distributed in the hope that it will be useful, but WITHOUT
|
|
18
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
|
20
|
+
# License for more details.
|
|
21
|
+
#
|
|
22
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
23
|
+
# along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
|
|
24
|
+
#
|
|
25
|
+
# The interactive user interfaces in modified source and object code
|
|
26
|
+
# versions of HexaPDF must display Appropriate Legal Notices, as required
|
|
27
|
+
# under Section 5 of the GNU Affero General Public License version 3.
|
|
28
|
+
#
|
|
29
|
+
# In accordance with Section 7(b) of the GNU Affero General Public
|
|
30
|
+
# License, a covered work must retain the producer line in every PDF that
|
|
31
|
+
# is created or manipulated using HexaPDF.
|
|
32
|
+
#
|
|
33
|
+
# If the GNU Affero General Public License doesn't fit your need,
|
|
34
|
+
# commercial licenses are available at <https://gettalong.at/hexapdf/>.
|
|
35
|
+
#++
|
|
36
|
+
|
|
37
|
+
require 'hexapdf/cli/command'
|
|
38
|
+
require 'strscan'
|
|
39
|
+
|
|
40
|
+
module HexaPDF
|
|
41
|
+
module CLI
|
|
42
|
+
|
|
43
|
+
# Processes a PDF that contains an interactive form (AcroForm).
|
|
44
|
+
class Form < Command
|
|
45
|
+
|
|
46
|
+
def initialize #:nodoc:
|
|
47
|
+
super('form', takes_commands: false)
|
|
48
|
+
short_desc("Show form fields and fill out a form")
|
|
49
|
+
long_desc(<<~EOF)
|
|
50
|
+
Use this command to process interactive PDF forms.
|
|
51
|
+
|
|
52
|
+
If the the output file name is not given, all form fields are listed in page order. Use
|
|
53
|
+
the global --verbose option to show additional information like field type and location.
|
|
54
|
+
|
|
55
|
+
If the output file name is given, the fields can be interactively filled out. By
|
|
56
|
+
additionally using the --template option, the data for the fields is read from the given
|
|
57
|
+
template file instead of the standard input.
|
|
58
|
+
EOF
|
|
59
|
+
|
|
60
|
+
options.on("--password PASSWORD", "-p", String,
|
|
61
|
+
"The password for decryption. Use - for reading from standard input.") do |pwd|
|
|
62
|
+
@password = (pwd == '-' ? read_password : pwd)
|
|
63
|
+
end
|
|
64
|
+
options.on("--template TEMPLATE_FILE", "-t TEMPLATE_FILE",
|
|
65
|
+
"Use the template file for the field values") do |template|
|
|
66
|
+
@template = template
|
|
67
|
+
end
|
|
68
|
+
options.on("--[no-]viewer-override", "Let the PDF viewer override the visual " \
|
|
69
|
+
"appearance. Default: use setting from input PDF") do |need_appearances|
|
|
70
|
+
@need_appearances = need_appearances
|
|
71
|
+
end
|
|
72
|
+
options.on("--[no-]incremental-save", "Append the changes instead of rewriting the " \
|
|
73
|
+
"whole file. Default: true") do |incremental|
|
|
74
|
+
@incremental = incremental
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
@password = nil
|
|
78
|
+
@template = nil
|
|
79
|
+
@need_appearances = nil
|
|
80
|
+
@incremental = true
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def execute(in_file, out_file = nil) #:nodoc:
|
|
84
|
+
maybe_raise_on_existing_file(out_file) if out_file
|
|
85
|
+
with_document(in_file, password: @password, out_file: out_file,
|
|
86
|
+
incremental: @incremental) do |doc|
|
|
87
|
+
if !doc.acro_form
|
|
88
|
+
raise "This PDF doesn't contain an interactive form"
|
|
89
|
+
elsif out_file
|
|
90
|
+
doc.acro_form[:NeedAppearances] = @need_appearances unless @need_appearances.nil?
|
|
91
|
+
if @template
|
|
92
|
+
fill_form_with_template(doc)
|
|
93
|
+
else
|
|
94
|
+
fill_form(doc)
|
|
95
|
+
end
|
|
96
|
+
else
|
|
97
|
+
list_form_fields(doc)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
# Lists all terminal form fields.
|
|
105
|
+
def list_form_fields(doc)
|
|
106
|
+
current_page_index = -1
|
|
107
|
+
each_field(doc) do |_page, page_index, field, widget|
|
|
108
|
+
if current_page_index != page_index
|
|
109
|
+
puts "Page #{page_index + 1}"
|
|
110
|
+
current_page_index = page_index
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
field_name = field.full_field_name +
|
|
114
|
+
(field.alternate_field_name ? " (#{field.alternate_field_name})" : '')
|
|
115
|
+
concrete_field_type = field.concrete_field_type
|
|
116
|
+
nice_field_type = concrete_field_type.to_s.split('_').map(&:capitalize).join(' ')
|
|
117
|
+
position = "(#{widget[:Rect].left}, #{widget[:Rect].bottom})"
|
|
118
|
+
|
|
119
|
+
puts " #{field_name}"
|
|
120
|
+
if command_parser.verbosity_info?
|
|
121
|
+
printf(" └─ %-22s | %-20s\n", nice_field_type, position)
|
|
122
|
+
end
|
|
123
|
+
puts " └─ #{field.field_value.inspect}"
|
|
124
|
+
if command_parser.verbosity_info?
|
|
125
|
+
if field.field_type == :Ch
|
|
126
|
+
puts " └─ Options: #{field.option_items.map(&:inspect).join(', ')}"
|
|
127
|
+
elsif concrete_field_type == :radio_button
|
|
128
|
+
puts " └─ Options: #{field.radio_button_values.map(&:inspect).join(', ')}"
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Fills out the form by interactively asking the user for field values.
|
|
135
|
+
def fill_form(doc)
|
|
136
|
+
current_page_index = -1
|
|
137
|
+
each_field(doc) do |_page, page_index, field, _widget|
|
|
138
|
+
if current_page_index != page_index
|
|
139
|
+
puts "Page #{page_index + 1}"
|
|
140
|
+
current_page_index = page_index
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
field_name = field.full_field_name +
|
|
144
|
+
(field.alternate_field_name ? " (#{field.alternate_field_name})" : '')
|
|
145
|
+
concrete_field_type = field.concrete_field_type
|
|
146
|
+
|
|
147
|
+
puts " #{field_name}"
|
|
148
|
+
puts " └─ Current value: #{field.field_value.inspect}"
|
|
149
|
+
|
|
150
|
+
if field.field_type == :Ch
|
|
151
|
+
puts " └─ Possible values: #{field.option_items.map(&:inspect).join(', ')}"
|
|
152
|
+
elsif concrete_field_type == :radio_button
|
|
153
|
+
puts " └─ Possible values: #{field.radio_button_values.map(&:inspect).join(', ')}"
|
|
154
|
+
elsif concrete_field_type == :check_box
|
|
155
|
+
puts " └─ Possible values: y(es), t(rue); n(o), f(alse)"
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
begin
|
|
159
|
+
print " └─ New value: "
|
|
160
|
+
value = $stdin.readline.chomp
|
|
161
|
+
next if value.empty?
|
|
162
|
+
apply_field_value(field, value)
|
|
163
|
+
rescue HexaPDF::Error => e
|
|
164
|
+
puts " ⚠ #{e.message}"
|
|
165
|
+
retry
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Fills out the form using the data from the provided template file.
|
|
171
|
+
def fill_form_with_template(doc)
|
|
172
|
+
data = parse_template
|
|
173
|
+
form = doc.acro_form
|
|
174
|
+
data.each do |name, value|
|
|
175
|
+
field = form.field_by_name(name)
|
|
176
|
+
raise "Field '#{name}' not found in input PDF" unless field
|
|
177
|
+
apply_field_value(field, value)
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Parses the data from the given template file.
|
|
182
|
+
def parse_template
|
|
183
|
+
data = {}
|
|
184
|
+
scanner = StringScanner.new(File.read(@template))
|
|
185
|
+
until scanner.eos?
|
|
186
|
+
field_name = scanner.scan(/(\\:|[^:])*?:/)
|
|
187
|
+
break unless field_name
|
|
188
|
+
field_name.gsub!(/\\:/, ':')
|
|
189
|
+
field_value = scanner.scan(/.*?(?=^\S|\z)/m)
|
|
190
|
+
data[field_name.chop] = field_value.strip.gsub(/^\s*/, '') if field_value
|
|
191
|
+
end
|
|
192
|
+
if !scanner.eos? && command_parser.verbosity_warning?
|
|
193
|
+
$stderr.puts "Warning: Some template could not be parsed"
|
|
194
|
+
end
|
|
195
|
+
data
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Applies the given value to the field.
|
|
199
|
+
def apply_field_value(field, value)
|
|
200
|
+
case field.concrete_field_type
|
|
201
|
+
when :single_line_text_field
|
|
202
|
+
field.field_value = value
|
|
203
|
+
when :combo_box, :list_box
|
|
204
|
+
field.field_value = value
|
|
205
|
+
when :editable_combo_box
|
|
206
|
+
field.field_value = value
|
|
207
|
+
when :check_box
|
|
208
|
+
unless value.match?(/y(es)?|t(rue)?|f(alse)?|n(o)/)
|
|
209
|
+
raise HexaPDF::Error, "Invalid input, use one of the possible values"
|
|
210
|
+
end
|
|
211
|
+
field.field_value = value.match?(/y(es)?|t(rue)?/)
|
|
212
|
+
when :radio_button
|
|
213
|
+
field.field_value = value.intern
|
|
214
|
+
else
|
|
215
|
+
raise "Field type not yet supported"
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Iterates over all non-push button fields in page order. If a field appears on multiple
|
|
220
|
+
# pages, it is only yielded on the first page.
|
|
221
|
+
def each_field(doc) # :yields: page, page_index, field
|
|
222
|
+
seen = {}
|
|
223
|
+
|
|
224
|
+
doc.pages.each_with_index do |page, page_index|
|
|
225
|
+
page[:Annots]&.each do |annotation|
|
|
226
|
+
next unless annotation[:Subtype] == :Widget
|
|
227
|
+
field = annotation.form_field
|
|
228
|
+
next if field.concrete_field_type == :push_button
|
|
229
|
+
unless seen[field]
|
|
230
|
+
yield(page, page_index, field, annotation)
|
|
231
|
+
seen[field] = true
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
end
|
|
240
|
+
end
|