hexapdf 0.1.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 +7 -0
- data/CONTRIBUTERS +3 -0
- data/LICENSE +26 -0
- data/README.md +88 -0
- data/Rakefile +121 -0
- data/VERSION +1 -0
- data/agpl-3.0.txt +661 -0
- data/bin/hexapdf +6 -0
- data/data/hexapdf/afm/Courier-Bold.afm +342 -0
- data/data/hexapdf/afm/Courier-BoldOblique.afm +342 -0
- data/data/hexapdf/afm/Courier-Oblique.afm +342 -0
- data/data/hexapdf/afm/Courier.afm +342 -0
- data/data/hexapdf/afm/Helvetica-Bold.afm +2827 -0
- data/data/hexapdf/afm/Helvetica-BoldOblique.afm +2827 -0
- data/data/hexapdf/afm/Helvetica-Oblique.afm +3051 -0
- data/data/hexapdf/afm/Helvetica.afm +3051 -0
- data/data/hexapdf/afm/MustRead.html +1 -0
- data/data/hexapdf/afm/Symbol.afm +213 -0
- data/data/hexapdf/afm/Times-Bold.afm +2588 -0
- data/data/hexapdf/afm/Times-BoldItalic.afm +2384 -0
- data/data/hexapdf/afm/Times-Italic.afm +2667 -0
- data/data/hexapdf/afm/Times-Roman.afm +2419 -0
- data/data/hexapdf/afm/ZapfDingbats.afm +225 -0
- data/data/hexapdf/encoding/glyphlist.txt +4305 -0
- data/data/hexapdf/encoding/zapfdingbats.txt +225 -0
- data/examples/arc.rb +50 -0
- data/examples/graphics.rb +274 -0
- data/examples/hello_world.rb +16 -0
- data/examples/machupicchu.jpg +0 -0
- data/examples/merging.rb +24 -0
- data/examples/optimizing.rb +20 -0
- data/examples/show_char_bboxes.rb +55 -0
- data/examples/standard_pdf_fonts.rb +72 -0
- data/examples/truetype.rb +45 -0
- data/lib/hexapdf/cli/extract.rb +128 -0
- data/lib/hexapdf/cli/info.rb +121 -0
- data/lib/hexapdf/cli/inspect.rb +157 -0
- data/lib/hexapdf/cli/modify.rb +218 -0
- data/lib/hexapdf/cli.rb +121 -0
- data/lib/hexapdf/configuration.rb +392 -0
- data/lib/hexapdf/content/canvas.rb +1974 -0
- data/lib/hexapdf/content/color_space.rb +364 -0
- data/lib/hexapdf/content/graphic_object/arc.rb +267 -0
- data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +208 -0
- data/lib/hexapdf/content/graphic_object/solid_arc.rb +173 -0
- data/lib/hexapdf/content/graphic_object.rb +81 -0
- data/lib/hexapdf/content/graphics_state.rb +579 -0
- data/lib/hexapdf/content/operator.rb +1072 -0
- data/lib/hexapdf/content/parser.rb +204 -0
- data/lib/hexapdf/content/processor.rb +451 -0
- data/lib/hexapdf/content/transformation_matrix.rb +172 -0
- data/lib/hexapdf/content.rb +47 -0
- data/lib/hexapdf/data_dir.rb +51 -0
- data/lib/hexapdf/dictionary.rb +303 -0
- data/lib/hexapdf/dictionary_fields.rb +382 -0
- data/lib/hexapdf/document.rb +589 -0
- data/lib/hexapdf/document_utils.rb +209 -0
- data/lib/hexapdf/encryption/aes.rb +206 -0
- data/lib/hexapdf/encryption/arc4.rb +93 -0
- data/lib/hexapdf/encryption/fast_aes.rb +79 -0
- data/lib/hexapdf/encryption/fast_arc4.rb +67 -0
- data/lib/hexapdf/encryption/identity.rb +63 -0
- data/lib/hexapdf/encryption/ruby_aes.rb +447 -0
- data/lib/hexapdf/encryption/ruby_arc4.rb +96 -0
- data/lib/hexapdf/encryption/security_handler.rb +494 -0
- data/lib/hexapdf/encryption/standard_security_handler.rb +616 -0
- data/lib/hexapdf/encryption.rb +94 -0
- data/lib/hexapdf/error.rb +73 -0
- data/lib/hexapdf/filter/ascii85_decode.rb +160 -0
- data/lib/hexapdf/filter/ascii_hex_decode.rb +87 -0
- data/lib/hexapdf/filter/dct_decode.rb +57 -0
- data/lib/hexapdf/filter/encryption.rb +59 -0
- data/lib/hexapdf/filter/flate_decode.rb +93 -0
- data/lib/hexapdf/filter/jpx_decode.rb +56 -0
- data/lib/hexapdf/filter/lzw_decode.rb +191 -0
- data/lib/hexapdf/filter/predictor.rb +266 -0
- data/lib/hexapdf/filter/run_length_decode.rb +108 -0
- data/lib/hexapdf/filter.rb +176 -0
- data/lib/hexapdf/font/cmap/parser.rb +146 -0
- data/lib/hexapdf/font/cmap/writer.rb +176 -0
- data/lib/hexapdf/font/cmap.rb +90 -0
- data/lib/hexapdf/font/encoding/base.rb +77 -0
- data/lib/hexapdf/font/encoding/difference_encoding.rb +64 -0
- data/lib/hexapdf/font/encoding/glyph_list.rb +150 -0
- data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +221 -0
- data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +265 -0
- data/lib/hexapdf/font/encoding/standard_encoding.rb +205 -0
- data/lib/hexapdf/font/encoding/symbol_encoding.rb +244 -0
- data/lib/hexapdf/font/encoding/win_ansi_encoding.rb +280 -0
- data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +250 -0
- data/lib/hexapdf/font/encoding.rb +68 -0
- data/lib/hexapdf/font/true_type/font.rb +179 -0
- data/lib/hexapdf/font/true_type/table/cmap.rb +103 -0
- data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +384 -0
- data/lib/hexapdf/font/true_type/table/directory.rb +92 -0
- data/lib/hexapdf/font/true_type/table/glyf.rb +166 -0
- data/lib/hexapdf/font/true_type/table/head.rb +143 -0
- data/lib/hexapdf/font/true_type/table/hhea.rb +109 -0
- data/lib/hexapdf/font/true_type/table/hmtx.rb +79 -0
- data/lib/hexapdf/font/true_type/table/loca.rb +79 -0
- data/lib/hexapdf/font/true_type/table/maxp.rb +112 -0
- data/lib/hexapdf/font/true_type/table/name.rb +218 -0
- data/lib/hexapdf/font/true_type/table/os2.rb +200 -0
- data/lib/hexapdf/font/true_type/table/post.rb +230 -0
- data/lib/hexapdf/font/true_type/table.rb +155 -0
- data/lib/hexapdf/font/true_type.rb +48 -0
- data/lib/hexapdf/font/true_type_wrapper.rb +240 -0
- data/lib/hexapdf/font/type1/afm_parser.rb +230 -0
- data/lib/hexapdf/font/type1/character_metrics.rb +67 -0
- data/lib/hexapdf/font/type1/font.rb +123 -0
- data/lib/hexapdf/font/type1/font_metrics.rb +117 -0
- data/lib/hexapdf/font/type1/pfb_parser.rb +71 -0
- data/lib/hexapdf/font/type1.rb +52 -0
- data/lib/hexapdf/font/type1_wrapper.rb +193 -0
- data/lib/hexapdf/font_loader/from_configuration.rb +70 -0
- data/lib/hexapdf/font_loader/standard14.rb +98 -0
- data/lib/hexapdf/font_loader.rb +85 -0
- data/lib/hexapdf/font_utils.rb +89 -0
- data/lib/hexapdf/image_loader/jpeg.rb +166 -0
- data/lib/hexapdf/image_loader/pdf.rb +89 -0
- data/lib/hexapdf/image_loader/png.rb +410 -0
- data/lib/hexapdf/image_loader.rb +68 -0
- data/lib/hexapdf/importer.rb +139 -0
- data/lib/hexapdf/name_tree_node.rb +78 -0
- data/lib/hexapdf/number_tree_node.rb +67 -0
- data/lib/hexapdf/object.rb +363 -0
- data/lib/hexapdf/parser.rb +349 -0
- data/lib/hexapdf/rectangle.rb +99 -0
- data/lib/hexapdf/reference.rb +98 -0
- data/lib/hexapdf/revision.rb +206 -0
- data/lib/hexapdf/revisions.rb +194 -0
- data/lib/hexapdf/serializer.rb +326 -0
- data/lib/hexapdf/stream.rb +279 -0
- data/lib/hexapdf/task/dereference.rb +109 -0
- data/lib/hexapdf/task/optimize.rb +230 -0
- data/lib/hexapdf/task.rb +68 -0
- data/lib/hexapdf/tokenizer.rb +406 -0
- data/lib/hexapdf/type/catalog.rb +107 -0
- data/lib/hexapdf/type/embedded_file.rb +87 -0
- data/lib/hexapdf/type/file_specification.rb +232 -0
- data/lib/hexapdf/type/font.rb +81 -0
- data/lib/hexapdf/type/font_descriptor.rb +109 -0
- data/lib/hexapdf/type/font_simple.rb +190 -0
- data/lib/hexapdf/type/font_true_type.rb +47 -0
- data/lib/hexapdf/type/font_type1.rb +162 -0
- data/lib/hexapdf/type/form.rb +103 -0
- data/lib/hexapdf/type/graphics_state_parameter.rb +79 -0
- data/lib/hexapdf/type/image.rb +73 -0
- data/lib/hexapdf/type/info.rb +70 -0
- data/lib/hexapdf/type/names.rb +69 -0
- data/lib/hexapdf/type/object_stream.rb +224 -0
- data/lib/hexapdf/type/page.rb +355 -0
- data/lib/hexapdf/type/page_tree_node.rb +269 -0
- data/lib/hexapdf/type/resources.rb +212 -0
- data/lib/hexapdf/type/trailer.rb +128 -0
- data/lib/hexapdf/type/viewer_preferences.rb +73 -0
- data/lib/hexapdf/type/xref_stream.rb +204 -0
- data/lib/hexapdf/type.rb +67 -0
- data/lib/hexapdf/utils/bit_field.rb +87 -0
- data/lib/hexapdf/utils/bit_stream.rb +148 -0
- data/lib/hexapdf/utils/lru_cache.rb +65 -0
- data/lib/hexapdf/utils/math_helpers.rb +55 -0
- data/lib/hexapdf/utils/object_hash.rb +130 -0
- data/lib/hexapdf/utils/pdf_doc_encoding.rb +93 -0
- data/lib/hexapdf/utils/sorted_tree_node.rb +339 -0
- data/lib/hexapdf/version.rb +39 -0
- data/lib/hexapdf/writer.rb +199 -0
- data/lib/hexapdf/xref_section.rb +152 -0
- data/lib/hexapdf.rb +34 -0
- data/man/man1/hexapdf.1 +249 -0
- data/test/data/aes-test-vectors/CBCGFSbox-128-decrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCGFSbox-128-encrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCGFSbox-192-decrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCGFSbox-192-encrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCGFSbox-256-decrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCGFSbox-256-encrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCKeySbox-128-decrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCKeySbox-128-encrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCKeySbox-192-decrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCKeySbox-192-encrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCKeySbox-256-decrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCKeySbox-256-encrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCVarKey-128-decrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCVarKey-128-encrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCVarKey-192-decrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCVarKey-192-encrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCVarKey-256-decrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCVarKey-256-encrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCVarTxt-128-decrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCVarTxt-128-encrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCVarTxt-192-decrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCVarTxt-192-encrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCVarTxt-256-decrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCVarTxt-256-encrypt.data.gz +0 -0
- data/test/data/fonts/Ubuntu-Title.ttf +0 -0
- data/test/data/images/cmyk.jpg +0 -0
- data/test/data/images/fillbytes.jpg +0 -0
- data/test/data/images/gray.jpg +0 -0
- data/test/data/images/greyscale-1bit.png +0 -0
- data/test/data/images/greyscale-2bit.png +0 -0
- data/test/data/images/greyscale-4bit.png +0 -0
- data/test/data/images/greyscale-8bit.png +0 -0
- data/test/data/images/greyscale-alpha-8bit.png +0 -0
- data/test/data/images/greyscale-trns-8bit.png +0 -0
- data/test/data/images/greyscale-with-gamma1.0.png +0 -0
- data/test/data/images/greyscale-with-gamma1.5.png +0 -0
- data/test/data/images/indexed-1bit.png +0 -0
- data/test/data/images/indexed-2bit.png +0 -0
- data/test/data/images/indexed-4bit.png +0 -0
- data/test/data/images/indexed-8bit.png +0 -0
- data/test/data/images/indexed-alpha-4bit.png +0 -0
- data/test/data/images/indexed-alpha-8bit.png +0 -0
- data/test/data/images/rgb.jpg +0 -0
- data/test/data/images/truecolour-8bit.png +0 -0
- data/test/data/images/truecolour-alpha-8bit.png +0 -0
- data/test/data/images/truecolour-gama-chrm-8bit.png +0 -0
- data/test/data/images/truecolour-srgb-8bit.png +0 -0
- data/test/data/minimal.pdf +44 -0
- data/test/data/standard-security-handler/README +9 -0
- data/test/data/standard-security-handler/bothpwd-aes-128bit-V4.pdf +44 -0
- data/test/data/standard-security-handler/bothpwd-aes-256bit-V5.pdf +0 -0
- data/test/data/standard-security-handler/bothpwd-arc4-128bit-V2.pdf +43 -0
- data/test/data/standard-security-handler/bothpwd-arc4-128bit-V4.pdf +43 -0
- data/test/data/standard-security-handler/bothpwd-arc4-40bit-V1.pdf +0 -0
- data/test/data/standard-security-handler/nopwd-aes-128bit-V4.pdf +43 -0
- data/test/data/standard-security-handler/nopwd-aes-256bit-V5.pdf +0 -0
- data/test/data/standard-security-handler/nopwd-arc4-128bit-V2.pdf +43 -0
- data/test/data/standard-security-handler/nopwd-arc4-128bit-V4.pdf +43 -0
- data/test/data/standard-security-handler/nopwd-arc4-40bit-V1.pdf +43 -0
- data/test/data/standard-security-handler/ownerpwd-aes-128bit-V4.pdf +0 -0
- data/test/data/standard-security-handler/ownerpwd-aes-256bit-V5.pdf +43 -0
- data/test/data/standard-security-handler/ownerpwd-arc4-128bit-V2.pdf +43 -0
- data/test/data/standard-security-handler/ownerpwd-arc4-128bit-V4.pdf +43 -0
- data/test/data/standard-security-handler/ownerpwd-arc4-40bit-V1.pdf +43 -0
- data/test/data/standard-security-handler/userpwd-aes-128bit-V4.pdf +43 -0
- data/test/data/standard-security-handler/userpwd-aes-256bit-V5.pdf +43 -0
- data/test/data/standard-security-handler/userpwd-arc4-128bit-V2.pdf +0 -0
- data/test/data/standard-security-handler/userpwd-arc4-128bit-V4.pdf +0 -0
- data/test/data/standard-security-handler/userpwd-arc4-40bit-V1.pdf +43 -0
- data/test/hexapdf/common_tokenizer_tests.rb +204 -0
- data/test/hexapdf/content/common.rb +31 -0
- data/test/hexapdf/content/graphic_object/test_arc.rb +93 -0
- data/test/hexapdf/content/graphic_object/test_endpoint_arc.rb +91 -0
- data/test/hexapdf/content/graphic_object/test_solid_arc.rb +86 -0
- data/test/hexapdf/content/test_canvas.rb +1113 -0
- data/test/hexapdf/content/test_color_space.rb +97 -0
- data/test/hexapdf/content/test_graphics_state.rb +138 -0
- data/test/hexapdf/content/test_operator.rb +619 -0
- data/test/hexapdf/content/test_parser.rb +66 -0
- data/test/hexapdf/content/test_processor.rb +156 -0
- data/test/hexapdf/content/test_transformation_matrix.rb +64 -0
- data/test/hexapdf/encryption/common.rb +87 -0
- data/test/hexapdf/encryption/test_aes.rb +121 -0
- data/test/hexapdf/encryption/test_arc4.rb +39 -0
- data/test/hexapdf/encryption/test_fast_aes.rb +17 -0
- data/test/hexapdf/encryption/test_fast_arc4.rb +12 -0
- data/test/hexapdf/encryption/test_identity.rb +21 -0
- data/test/hexapdf/encryption/test_ruby_aes.rb +23 -0
- data/test/hexapdf/encryption/test_ruby_arc4.rb +20 -0
- data/test/hexapdf/encryption/test_security_handler.rb +356 -0
- data/test/hexapdf/encryption/test_standard_security_handler.rb +274 -0
- data/test/hexapdf/filter/common.rb +53 -0
- data/test/hexapdf/filter/test_ascii85_decode.rb +60 -0
- data/test/hexapdf/filter/test_ascii_hex_decode.rb +33 -0
- data/test/hexapdf/filter/test_encryption.rb +24 -0
- data/test/hexapdf/filter/test_flate_decode.rb +35 -0
- data/test/hexapdf/filter/test_lzw_decode.rb +52 -0
- data/test/hexapdf/filter/test_predictor.rb +183 -0
- data/test/hexapdf/filter/test_run_length_decode.rb +32 -0
- data/test/hexapdf/font/cmap/test_parser.rb +67 -0
- data/test/hexapdf/font/cmap/test_writer.rb +58 -0
- data/test/hexapdf/font/encoding/test_base.rb +35 -0
- data/test/hexapdf/font/encoding/test_difference_encoding.rb +21 -0
- data/test/hexapdf/font/encoding/test_glyph_list.rb +59 -0
- data/test/hexapdf/font/encoding/test_zapf_dingbats_encoding.rb +16 -0
- data/test/hexapdf/font/test_encoding.rb +27 -0
- data/test/hexapdf/font/test_true_type_wrapper.rb +110 -0
- data/test/hexapdf/font/test_type1_wrapper.rb +66 -0
- data/test/hexapdf/font/true_type/common.rb +19 -0
- data/test/hexapdf/font/true_type/table/test_cmap.rb +59 -0
- data/test/hexapdf/font/true_type/table/test_cmap_subtable.rb +133 -0
- data/test/hexapdf/font/true_type/table/test_directory.rb +35 -0
- data/test/hexapdf/font/true_type/table/test_glyf.rb +58 -0
- data/test/hexapdf/font/true_type/table/test_head.rb +76 -0
- data/test/hexapdf/font/true_type/table/test_hhea.rb +40 -0
- data/test/hexapdf/font/true_type/table/test_hmtx.rb +38 -0
- data/test/hexapdf/font/true_type/table/test_loca.rb +43 -0
- data/test/hexapdf/font/true_type/table/test_maxp.rb +62 -0
- data/test/hexapdf/font/true_type/table/test_name.rb +95 -0
- data/test/hexapdf/font/true_type/table/test_os2.rb +65 -0
- data/test/hexapdf/font/true_type/table/test_post.rb +89 -0
- data/test/hexapdf/font/true_type/test_font.rb +120 -0
- data/test/hexapdf/font/true_type/test_table.rb +41 -0
- data/test/hexapdf/font/type1/test_afm_parser.rb +51 -0
- data/test/hexapdf/font/type1/test_font.rb +68 -0
- data/test/hexapdf/font/type1/test_pfb_parser.rb +37 -0
- data/test/hexapdf/font_loader/test_from_configuration.rb +28 -0
- data/test/hexapdf/font_loader/test_standard14.rb +22 -0
- data/test/hexapdf/image_loader/test_jpeg.rb +83 -0
- data/test/hexapdf/image_loader/test_pdf.rb +47 -0
- data/test/hexapdf/image_loader/test_png.rb +258 -0
- data/test/hexapdf/task/test_dereference.rb +46 -0
- data/test/hexapdf/task/test_optimize.rb +137 -0
- data/test/hexapdf/test_configuration.rb +82 -0
- data/test/hexapdf/test_data_dir.rb +32 -0
- data/test/hexapdf/test_dictionary.rb +284 -0
- data/test/hexapdf/test_dictionary_fields.rb +185 -0
- data/test/hexapdf/test_document.rb +574 -0
- data/test/hexapdf/test_document_utils.rb +144 -0
- data/test/hexapdf/test_filter.rb +96 -0
- data/test/hexapdf/test_font_utils.rb +47 -0
- data/test/hexapdf/test_importer.rb +78 -0
- data/test/hexapdf/test_object.rb +177 -0
- data/test/hexapdf/test_parser.rb +394 -0
- data/test/hexapdf/test_rectangle.rb +36 -0
- data/test/hexapdf/test_reference.rb +41 -0
- data/test/hexapdf/test_revision.rb +139 -0
- data/test/hexapdf/test_revisions.rb +93 -0
- data/test/hexapdf/test_serializer.rb +169 -0
- data/test/hexapdf/test_stream.rb +262 -0
- data/test/hexapdf/test_tokenizer.rb +30 -0
- data/test/hexapdf/test_writer.rb +120 -0
- data/test/hexapdf/test_xref_section.rb +35 -0
- data/test/hexapdf/type/test_catalog.rb +30 -0
- data/test/hexapdf/type/test_embedded_file.rb +16 -0
- data/test/hexapdf/type/test_file_specification.rb +148 -0
- data/test/hexapdf/type/test_font.rb +35 -0
- data/test/hexapdf/type/test_font_descriptor.rb +51 -0
- data/test/hexapdf/type/test_font_simple.rb +190 -0
- data/test/hexapdf/type/test_font_type1.rb +128 -0
- data/test/hexapdf/type/test_form.rb +60 -0
- data/test/hexapdf/type/test_info.rb +14 -0
- data/test/hexapdf/type/test_names.rb +9 -0
- data/test/hexapdf/type/test_object_stream.rb +84 -0
- data/test/hexapdf/type/test_page.rb +260 -0
- data/test/hexapdf/type/test_page_tree_node.rb +255 -0
- data/test/hexapdf/type/test_resources.rb +167 -0
- data/test/hexapdf/type/test_trailer.rb +109 -0
- data/test/hexapdf/type/test_xref_stream.rb +131 -0
- data/test/hexapdf/utils/test_bit_field.rb +47 -0
- data/test/hexapdf/utils/test_lru_cache.rb +22 -0
- data/test/hexapdf/utils/test_object_hash.rb +115 -0
- data/test/hexapdf/utils/test_pdf_doc_encoding.rb +18 -0
- data/test/hexapdf/utils/test_sorted_tree_node.rb +232 -0
- data/test/test_helper.rb +56 -0
- metadata +427 -0
@@ -0,0 +1,1974 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
#
|
3
|
+
#--
|
4
|
+
# This file is part of HexaPDF.
|
5
|
+
#
|
6
|
+
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
7
|
+
# Copyright (C) 2016 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
|
+
|
34
|
+
require 'hexapdf/content/graphics_state'
|
35
|
+
require 'hexapdf/content/operator'
|
36
|
+
require 'hexapdf/serializer'
|
37
|
+
require 'hexapdf/utils/math_helpers'
|
38
|
+
require 'hexapdf/content/graphic_object'
|
39
|
+
require 'hexapdf/stream'
|
40
|
+
|
41
|
+
module HexaPDF
|
42
|
+
module Content
|
43
|
+
|
44
|
+
# This class provides the basic drawing operations supported by PDF.
|
45
|
+
#
|
46
|
+
# == General Information
|
47
|
+
#
|
48
|
+
# A canvas object is used for modifying content streams on a level higher than text. It would
|
49
|
+
# be possible to write a content stream by hand since PDF uses a simplified reversed polish
|
50
|
+
# notation for specifying operators: First come the operands, then comes the operator and no
|
51
|
+
# operator returns any result. However, it is easy to make mistakes this way and one has to
|
52
|
+
# know all operators and their operands.
|
53
|
+
#
|
54
|
+
# This is rather tedious and therefore this class exists. It allows one to modify a content
|
55
|
+
# stream by invoking methods that should be familiar to anyone that has ever used a graphic
|
56
|
+
# API. There are methods for moving the current point, drawing lines and curves, setting the
|
57
|
+
# color, line width and so on.
|
58
|
+
#
|
59
|
+
# The PDF operators themselves are implemented as classes, see Operator. The canvas class uses
|
60
|
+
# the Operator::BaseOperator#invoke and Operator::BaseOperator#serialize methods for applying
|
61
|
+
# changes and serialization, with one exception: color setters don't invoke the corresponding
|
62
|
+
# operator implementation but directly work on the graphics state.
|
63
|
+
#
|
64
|
+
#
|
65
|
+
# == PDF Graphics
|
66
|
+
#
|
67
|
+
# === Graphics Operators and Objects
|
68
|
+
#
|
69
|
+
# There are about 60 PDF content stream operators. Some are used for changing the graphics
|
70
|
+
# state, some for drawing paths and others for showing text. This is all abstracted through
|
71
|
+
# the Canvas class.
|
72
|
+
#
|
73
|
+
# PDF knows about five different graphics objects: path objects, text objects, external
|
74
|
+
# objects, inline image objects and shading objects. If none of the five graphics objects is
|
75
|
+
# current, the content stream is at the so called page description level (in between graphics
|
76
|
+
# objects).
|
77
|
+
#
|
78
|
+
# Additionally the PDF operators are divided into several groups, like path painting or text
|
79
|
+
# showing operators, and such groups of operators are allowed to be used only in certain
|
80
|
+
# graphics objects or the page description level.
|
81
|
+
#
|
82
|
+
# Have a look at the PDF specification (PDF1.7 s8.2) for more details.
|
83
|
+
#
|
84
|
+
# HexaPDF tries to ensure the proper use of the operators and graphics objects and if it
|
85
|
+
# cannot do it, an error is raised. So if you don't modify a content stream directly but via
|
86
|
+
# the Canvas methods, you generally don't have to worry about the low-level inner workings.
|
87
|
+
#
|
88
|
+
# === Graphics State
|
89
|
+
#
|
90
|
+
# Some operators modify the so called graphics state (see Content::GraphicsState). The graphics
|
91
|
+
# state is a collection of settings that is used during processing or creating a content stream.
|
92
|
+
# For example, the path painting operators don't have operands to specify the line width or the
|
93
|
+
# stroke color but take this information from the graphics state.
|
94
|
+
#
|
95
|
+
# One important thing about the graphics state is that it is only possible to restore a prior
|
96
|
+
# state using the save and restore methods. It is not possible to reset the graphics state
|
97
|
+
# while creating the content stream!
|
98
|
+
#
|
99
|
+
# === Paths
|
100
|
+
#
|
101
|
+
# A PDF path object consists of one or more subpaths. Each subpath can be a rectangle or can
|
102
|
+
# consist of lines and cubic bezier curves. No other types of subpaths are known to PDF.
|
103
|
+
# However, the Canvas class contains additional methods that use the basic path construction
|
104
|
+
# methods for drawing other paths like circles.
|
105
|
+
#
|
106
|
+
# When a subpath is started, the current graphics object is changed to :path. After all path
|
107
|
+
# constructions are finished, a path painting method needs to be invoked to change back to the
|
108
|
+
# page description level. Optionally, the path painting method may be preceeded by a clipping
|
109
|
+
# path method to change the current clipping path (see #clip_path).
|
110
|
+
#
|
111
|
+
# There are four kinds of path painting methods:
|
112
|
+
#
|
113
|
+
# * Those that stroke the path,
|
114
|
+
# * those that fill the path,
|
115
|
+
# * those that stroke and fill the path and
|
116
|
+
# * one to neither stroke or fill the path (used, for example, to just set the clipping path).
|
117
|
+
#
|
118
|
+
# In addition filling may be done using either the nonzero winding number rule or the even-odd
|
119
|
+
# rule.
|
120
|
+
#
|
121
|
+
#
|
122
|
+
# == Special Graphics State Methods
|
123
|
+
#
|
124
|
+
# These methods are only allowed when the current graphics object is :none, i.e. operations are
|
125
|
+
# done on the page description level.
|
126
|
+
#
|
127
|
+
# * #save_graphics_state
|
128
|
+
# * #restore_graphics_state
|
129
|
+
# * #transform, #rotate, #scale, #translate, #skew
|
130
|
+
#
|
131
|
+
# See: PDF1.7 s8, s9
|
132
|
+
class Canvas
|
133
|
+
|
134
|
+
include HexaPDF::Utils::MathHelpers
|
135
|
+
|
136
|
+
# The context for which the canvas was created (a HexaPDF::Type::Page or HexaPDF::Type::Form
|
137
|
+
# object).
|
138
|
+
attr_reader :context
|
139
|
+
|
140
|
+
# The serialized contents produced by the various canvas operations up to this point.
|
141
|
+
#
|
142
|
+
# Note that the returned string may not be a completely valid PDF content stream since a
|
143
|
+
# graphic object may be open or the graphics state not completely restored.
|
144
|
+
#
|
145
|
+
# See: #stream_data
|
146
|
+
attr_reader :contents
|
147
|
+
|
148
|
+
# A StreamData object representing the serialized contents produced by the various canvas
|
149
|
+
# operations.
|
150
|
+
#
|
151
|
+
# In contrast to #contents, it is ensured that an open graphics object is closed and all saved
|
152
|
+
# graphics states are restored when the contents of the stream data object is read. *Note*
|
153
|
+
# that this means that reading the stream data object may change the state of the canvas.
|
154
|
+
attr_reader :stream_data
|
155
|
+
|
156
|
+
# The Content::GraphicsState object containing the current graphics state.
|
157
|
+
#
|
158
|
+
# The graphics state must not be changed directly, only by using the provided methods. If it
|
159
|
+
# is changed directly, the output will not be correct.
|
160
|
+
attr_reader :graphics_state
|
161
|
+
|
162
|
+
# The current graphics object.
|
163
|
+
#
|
164
|
+
# The graphics object should not be changed directly. It is automatically updated according
|
165
|
+
# to the invoked methods.
|
166
|
+
#
|
167
|
+
# This attribute can have the following values:
|
168
|
+
#
|
169
|
+
# :none:: No current graphics object, i.e. the page description level.
|
170
|
+
# :path:: The current graphics object is a path.
|
171
|
+
# :clipping_path:: The current graphics object is a clipping path.
|
172
|
+
# :text:: The current graphics object is a text object.
|
173
|
+
#
|
174
|
+
# See: PDF1.7 s8.2
|
175
|
+
attr_accessor :graphics_object
|
176
|
+
|
177
|
+
# The current point [x, y] of the path.
|
178
|
+
#
|
179
|
+
# This attribute holds the current point which is only valid if the current graphics objects
|
180
|
+
# is :path.
|
181
|
+
#
|
182
|
+
# When the current point changes, the array is modified in place instead of creating a new
|
183
|
+
# array!
|
184
|
+
attr_reader :current_point
|
185
|
+
|
186
|
+
# The operator name/implementation map used when invoking or serializing an operator.
|
187
|
+
attr_reader :operators
|
188
|
+
|
189
|
+
# Creates a new Canvas object for the given context object (either a Page or a Form).
|
190
|
+
def initialize(context)
|
191
|
+
@context = context
|
192
|
+
@operators = Operator::DEFAULT_OPERATORS.dup
|
193
|
+
@graphics_state = GraphicsState.new
|
194
|
+
@graphics_object = :none
|
195
|
+
@font = nil
|
196
|
+
@serializer = HexaPDF::Serializer.new
|
197
|
+
@current_point = [0, 0]
|
198
|
+
@start_point = [0, 0]
|
199
|
+
@contents = ''.b
|
200
|
+
@stream_data = HexaPDF::StreamData.new do
|
201
|
+
case graphics_object
|
202
|
+
when :path, :clipping_path then end_path
|
203
|
+
when :text then end_text
|
204
|
+
end
|
205
|
+
restore_graphics_state while graphics_state.saved_states?
|
206
|
+
@contents
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# Returns the resource dictionary of the context object.
|
211
|
+
def resources
|
212
|
+
@context.resources
|
213
|
+
end
|
214
|
+
|
215
|
+
# :call-seq:
|
216
|
+
# canvas.save_graphics_state => canvas
|
217
|
+
# canvas.save_graphics_state { block } => canvas
|
218
|
+
#
|
219
|
+
# Saves the current graphics state and returns self.
|
220
|
+
#
|
221
|
+
# If invoked without a block a corresponding call to #restore_graphics_state must be done.
|
222
|
+
# Otherwise the graphics state is automatically restored when the block is finished.
|
223
|
+
#
|
224
|
+
# Examples:
|
225
|
+
#
|
226
|
+
# # With a block
|
227
|
+
# canvas.save_graphics_state do
|
228
|
+
# canvas.line_width(10)
|
229
|
+
# canvas.line(100, 100, 200, 200)
|
230
|
+
# end
|
231
|
+
#
|
232
|
+
# # Same without a block
|
233
|
+
# canvas.save_graphics_state
|
234
|
+
# canvas.line_width(10)
|
235
|
+
# canvas.line(100, 100, 200, 200)
|
236
|
+
# canvas.restore_graphics_state
|
237
|
+
#
|
238
|
+
# See: PDF1.7 s8.4.2, #restore_graphics_state
|
239
|
+
def save_graphics_state
|
240
|
+
raise_unless_at_page_description_level
|
241
|
+
invoke0(:q)
|
242
|
+
if block_given?
|
243
|
+
yield
|
244
|
+
restore_graphics_state
|
245
|
+
end
|
246
|
+
self
|
247
|
+
end
|
248
|
+
|
249
|
+
# :call-seq:
|
250
|
+
# canvas.restore_graphics_state => canvas
|
251
|
+
#
|
252
|
+
# Restores the current graphics state and returns self.
|
253
|
+
#
|
254
|
+
# Must not be invoked more times than #save_graphics_state.
|
255
|
+
#
|
256
|
+
# See: PDF1.7 s8.4.2, #save_graphics_state
|
257
|
+
def restore_graphics_state
|
258
|
+
raise_unless_at_page_description_level
|
259
|
+
invoke0(:Q)
|
260
|
+
self
|
261
|
+
end
|
262
|
+
|
263
|
+
# :call-seq:
|
264
|
+
# canvas.transform(a, b, c, d, e, f) => canvas
|
265
|
+
# canvas.transform(a, b, c, d, e, f) { block } => canvas
|
266
|
+
#
|
267
|
+
# Transforms the user space by applying the given matrix to the current transformation
|
268
|
+
# matrix and returns self.
|
269
|
+
#
|
270
|
+
# If invoked with a block, the transformation is only active during the block by saving and
|
271
|
+
# restoring the graphics state.
|
272
|
+
#
|
273
|
+
# The given values are interpreted as a matrix in the following way:
|
274
|
+
#
|
275
|
+
# a b 0
|
276
|
+
# c d 0
|
277
|
+
# e f 1
|
278
|
+
#
|
279
|
+
# Examples:
|
280
|
+
#
|
281
|
+
# canvas.transform(1, 0, 0, 1, 100, 100) do # Translate origin to (100, 100)
|
282
|
+
# canvas.line(0, 0, 100, 100) # Actually from (100, 100) to (200, 200)
|
283
|
+
# end
|
284
|
+
# canvas.line(0, 0, 100, 100) # Again from (0, 0) to (100, 100)
|
285
|
+
#
|
286
|
+
# See: PDF1.7 s8.3, s8.4.4
|
287
|
+
def transform(a, b, c, d, e, f)
|
288
|
+
raise_unless_at_page_description_level
|
289
|
+
save_graphics_state if block_given?
|
290
|
+
invoke(:cm, a, b, c, d, e, f)
|
291
|
+
if block_given?
|
292
|
+
yield
|
293
|
+
restore_graphics_state
|
294
|
+
end
|
295
|
+
self
|
296
|
+
end
|
297
|
+
|
298
|
+
# :call-seq:
|
299
|
+
# canvas.rotate(angle, origin: nil) => canvas
|
300
|
+
# canvas.rotate(angle, origin: nil) { block } => canvas
|
301
|
+
#
|
302
|
+
# Rotates the user space +angle+ degrees around the coordinate system origin or around the
|
303
|
+
# given point and returns self.
|
304
|
+
#
|
305
|
+
# If invoked with a block, the rotation of the user space is only active during the block by
|
306
|
+
# saving and restoring the graphics state.
|
307
|
+
#
|
308
|
+
# Note that the origin of the coordinate system itself doesn't change!
|
309
|
+
#
|
310
|
+
# origin::
|
311
|
+
# The point around which the user space should be rotated.
|
312
|
+
#
|
313
|
+
# Examples:
|
314
|
+
#
|
315
|
+
# canvas.rotate(90) do # Positive x-axis is now pointing upwards
|
316
|
+
# canvas.line(0, 0, 100, 0) # Actually from (0, 0) to (0, 100)
|
317
|
+
# end
|
318
|
+
# canvas.line(0, 0, 100, 0) # Again from (0, 0) to (100, 0)
|
319
|
+
#
|
320
|
+
# canvas.rotate(90, origin: [100, 100]) do
|
321
|
+
# canvas.line(100, 100, 200, 0) # Actually from (100, 100) to (100, 200)
|
322
|
+
# end
|
323
|
+
#
|
324
|
+
# See: #transform
|
325
|
+
def rotate(angle, origin: nil, &block)
|
326
|
+
cos = Math.cos(deg_to_rad(angle))
|
327
|
+
sin = Math.sin(deg_to_rad(angle))
|
328
|
+
|
329
|
+
# Rotation is performed around the coordinate system origin but points are translated so
|
330
|
+
# that the rotated rotation origin coincides with the unrotated one.
|
331
|
+
tx = (origin ? origin[0] - (origin[0] * cos - origin[1] * sin) : 0)
|
332
|
+
ty = (origin ? origin[1] - (origin[0] * sin + origin[1] * cos) : 0)
|
333
|
+
transform(cos, sin, -sin, cos, tx, ty, &block)
|
334
|
+
end
|
335
|
+
|
336
|
+
# :call-seq:
|
337
|
+
# canvas.scale(sx, sy = sx, origin: nil) => canvas
|
338
|
+
# canvas.scale(sx, sy = sx, origin: nil) { block } => canvas
|
339
|
+
#
|
340
|
+
# Scales the user space +sx+ units in the horizontal and +sy+ units in the vertical
|
341
|
+
# direction and returns self. If the optional +origin+ is specified, scaling is done from
|
342
|
+
# that point.
|
343
|
+
#
|
344
|
+
# If invoked with a block, the scaling is only active during the block by saving and
|
345
|
+
# restoring the graphics state.
|
346
|
+
#
|
347
|
+
# Note that the origin of the coordinate system itself doesn't change!
|
348
|
+
#
|
349
|
+
# origin::
|
350
|
+
# The point from which the user space should be scaled.
|
351
|
+
#
|
352
|
+
# Examples:
|
353
|
+
#
|
354
|
+
# canvas.scale(2, 3) do # Point (1, 1) is now actually (2, 3)
|
355
|
+
# canvas.line(50, 50, 100, 100) # Actually from (100, 150) to (200, 300)
|
356
|
+
# end
|
357
|
+
# canvas.line(0, 0, 100, 0) # Again from (0, 0) to (100, 0)
|
358
|
+
#
|
359
|
+
# canvas.scale(2, 3, origin: [50, 50]) do
|
360
|
+
# canvas.line(50, 50, 100, 100) # Actually from (50, 50) to (200, 300)
|
361
|
+
# end
|
362
|
+
#
|
363
|
+
# See: #transform
|
364
|
+
def scale(sx, sy = sx, origin: nil, &block)
|
365
|
+
# As with rotation, scaling is performed around the coordinate system origin but points
|
366
|
+
# are translated so that the scaled scaling origin coincides with the unscaled one.
|
367
|
+
tx = (origin ? origin[0] - origin[0] * sx : 0)
|
368
|
+
ty = (origin ? origin[1] - origin[1] * sy : 0)
|
369
|
+
transform(sx, 0, 0, sy, tx, ty, &block)
|
370
|
+
end
|
371
|
+
|
372
|
+
# :call-seq:
|
373
|
+
# canvas.translate(x, y) => canvas
|
374
|
+
# canvas.translate(x, y) { block } => canvas
|
375
|
+
#
|
376
|
+
# Translates the user space coordinate system origin to the given +x+ and +y+ coordinates
|
377
|
+
# and returns self.
|
378
|
+
#
|
379
|
+
# If invoked with a block, the translation of the user space is only active during the block
|
380
|
+
# by saving and restoring the graphics state.
|
381
|
+
#
|
382
|
+
# Examples:
|
383
|
+
#
|
384
|
+
# canvas.translate(100, 100) do # Origin is now at (100, 100)
|
385
|
+
# canvas.line(0, 0, 100, 0) # Actually from (100, 100) to (200, 100)
|
386
|
+
# end
|
387
|
+
# canvas.line(0, 0, 100, 0) # Again from (0, 0) to (100, 0)
|
388
|
+
#
|
389
|
+
# See: #transform
|
390
|
+
def translate(x, y, &block)
|
391
|
+
transform(1, 0, 0, 1, x, y, &block)
|
392
|
+
end
|
393
|
+
|
394
|
+
# :call-seq:
|
395
|
+
# canvas.skew(a, b, origin: nil) => canvas
|
396
|
+
# canvas.skew(a, b, origin: nil) { block } => canvas
|
397
|
+
#
|
398
|
+
# Skews the the x-axis by +a+ degrees and the y-axis by +b+ degress and returns self. If the
|
399
|
+
# optional +origin+ is specified, skewing is done from that point.
|
400
|
+
#
|
401
|
+
# If invoked with a block, the skewing is only active during the block by saving and
|
402
|
+
# restoring the graphics state.
|
403
|
+
#
|
404
|
+
# Note that the origin of the coordinate system itself doesn't change!
|
405
|
+
#
|
406
|
+
# origin::
|
407
|
+
# The point from which the axes are skewed.
|
408
|
+
#
|
409
|
+
# Examples:
|
410
|
+
#
|
411
|
+
# canvas.skew(0, 45) do # Point (1, 1) is now actually (2, 1)
|
412
|
+
# canvas.line(50, 50, 100, 100) # Actually from (100, 50) to (200, 100)
|
413
|
+
# end
|
414
|
+
# canvas.line(0, 0, 100, 0) # Again from (0, 0) to (100, 0)
|
415
|
+
#
|
416
|
+
# canvas.skew(0, origin: [50, 50]) do
|
417
|
+
# canvas.line(50, 50, 100, 100) # Actually from (50, 50) to (200, 300)
|
418
|
+
# end
|
419
|
+
#
|
420
|
+
# See: #transform
|
421
|
+
def skew(a, b, origin: nil, &block)
|
422
|
+
tan_a = Math.tan(deg_to_rad(a))
|
423
|
+
tan_b = Math.sin(deg_to_rad(b))
|
424
|
+
|
425
|
+
# As with rotation, skewing is performed around the coordinate system origin but points
|
426
|
+
# are translated so that the skewed skewing origin coincides with the unskewed one.
|
427
|
+
tx = (origin ? -origin[1] * tan_b : 0)
|
428
|
+
ty = (origin ? -origin[0] * tan_a : 0)
|
429
|
+
transform(1, tan_a, tan_b, 1, tx, ty, &block)
|
430
|
+
end
|
431
|
+
|
432
|
+
# :call-seq:
|
433
|
+
# canvas.line_width => current_line_width
|
434
|
+
# canvas.line_width(width) => canvas
|
435
|
+
# canvas.line_width(width) { block } => canvas
|
436
|
+
#
|
437
|
+
# The line width determines the thickness of a stroked path.
|
438
|
+
#
|
439
|
+
# Returns the current line width (see Content::GraphicsState#line_width) when no argument is
|
440
|
+
# given. Otherwise sets the line width to the given +width+ and returns self. The setter
|
441
|
+
# version can also be called in the line_width= form.
|
442
|
+
#
|
443
|
+
# If the +width+ and a block are provided, the changed line width is only active during the
|
444
|
+
# block by saving and restoring the graphics state.
|
445
|
+
#
|
446
|
+
# Examples:
|
447
|
+
#
|
448
|
+
# canvas.line_width(10)
|
449
|
+
# canvas.line_width # => 10
|
450
|
+
# canvas.line_width = 5 # => 5
|
451
|
+
#
|
452
|
+
# canvas.line_width(10) do
|
453
|
+
# canvas.line_width # => 10
|
454
|
+
# end
|
455
|
+
# canvas.line_width # => 5
|
456
|
+
#
|
457
|
+
# See: PDF1.7 s8.4.3.2
|
458
|
+
def line_width(width = nil, &block)
|
459
|
+
gs_getter_setter(:line_width, :w, width, &block)
|
460
|
+
end
|
461
|
+
alias :line_width= :line_width
|
462
|
+
|
463
|
+
# :call-seq:
|
464
|
+
# canvas.line_cap_style => current_line_cap_style
|
465
|
+
# canvas.line_cap_style(style) => canvas
|
466
|
+
# canvas.line_cap_style(style) { block } => canvas
|
467
|
+
#
|
468
|
+
# The line cap style specifies how the ends of stroked open paths should look like. The
|
469
|
+
# +style+ parameter can either be a valid integer or one of the symbols +:butt+, +:round+ or
|
470
|
+
# +:projecting_square+ (see Content::LineCapStyle.normalize for details). Note that the return
|
471
|
+
# value is always a normalized line cap style.
|
472
|
+
#
|
473
|
+
# Returns the current line cap style (see Content::GraphicsState#line_cap_style) when no
|
474
|
+
# argument is given. Otherwise sets the line cap style to the given +style+ and returns self.
|
475
|
+
# The setter version can also be called in the line_cap_style= form.
|
476
|
+
#
|
477
|
+
# If the +style+ and a block are provided, the changed line cap style is only active during
|
478
|
+
# the block by saving and restoring the graphics state.
|
479
|
+
#
|
480
|
+
# Examples:
|
481
|
+
#
|
482
|
+
# canvas.line_cap_style(:butt)
|
483
|
+
# canvas.line_cap_style # => #<NamedValue @name=:butt, @value=0>
|
484
|
+
# canvas.line_cap_style = :round # => #<NamedValue @name=:round, @value=1>
|
485
|
+
#
|
486
|
+
# canvas.line_cap_style(:butt) do
|
487
|
+
# canvas.line_cap_style # => #<NamedValue @name=:butt, @value=0>
|
488
|
+
# end
|
489
|
+
# canvas.line_cap_style # => #<NamedValue @name=:round, @value=1>
|
490
|
+
#
|
491
|
+
# See: PDF1.7 s8.4.3.3
|
492
|
+
def line_cap_style(style = nil, &block)
|
493
|
+
gs_getter_setter(:line_cap_style, :J, style && LineCapStyle.normalize(style), &block)
|
494
|
+
end
|
495
|
+
alias :line_cap_style= :line_cap_style
|
496
|
+
|
497
|
+
# :call-seq:
|
498
|
+
# canvas.line_join_style => current_line_join_style
|
499
|
+
# canvas.line_join_style(style) => canvas
|
500
|
+
# canvas.line_join_style(style) { block } => canvas
|
501
|
+
#
|
502
|
+
# The line join style specifies the shape that is used at the corners of stroked paths. The
|
503
|
+
# +style+ parameter can either be a valid integer or one of the symbols +:miter+, +:round+ or
|
504
|
+
# +:bevel+ (see Content::LineJoinStyle.normalize for details). Note that the return value is
|
505
|
+
# always a normalized line join style.
|
506
|
+
#
|
507
|
+
# Returns the current line join style (see Content::GraphicsState#line_join_style) when no
|
508
|
+
# argument is given. Otherwise sets the line join style to the given +style+ and returns self.
|
509
|
+
# The setter version can also be called in the line_join_style= form.
|
510
|
+
#
|
511
|
+
# If the +style+ and a block are provided, the changed line join style is only active during
|
512
|
+
# the block by saving and restoring the graphics state.
|
513
|
+
#
|
514
|
+
# Examples:
|
515
|
+
#
|
516
|
+
# canvas.line_join_style(:miter)
|
517
|
+
# canvas.line_join_style # => #<NamedValue @name=:miter, @value=0>
|
518
|
+
# canvas.line_join_style = :round # => #<NamedValue @name=:round, @value=1>
|
519
|
+
#
|
520
|
+
# canvas.line_join_style(:bevel) do
|
521
|
+
# canvas.line_join_style # => #<NamedValue @name=:bevel, @value=2>
|
522
|
+
# end
|
523
|
+
# canvas.line_join_style # => #<NamedValue @name=:round, @value=1>
|
524
|
+
#
|
525
|
+
# See: PDF1.7 s8.4.3.4
|
526
|
+
def line_join_style(style = nil, &block)
|
527
|
+
gs_getter_setter(:line_join_style, :j, style && LineJoinStyle.normalize(style), &block)
|
528
|
+
end
|
529
|
+
alias :line_join_style= :line_join_style
|
530
|
+
|
531
|
+
# :call-seq:
|
532
|
+
# canvas.miter_limit => current_miter_limit
|
533
|
+
# canvas.miter_limit(limit) => canvas
|
534
|
+
# canvas.miter_limit(limit) { block } => canvas
|
535
|
+
#
|
536
|
+
# The miter limit specifies the maximum ratio of the miter length to the line width for
|
537
|
+
# mitered line joins (see #line_join_style). When the limit is exceeded, a bevel join is
|
538
|
+
# used instead of a miter join.
|
539
|
+
#
|
540
|
+
# Returns the current miter limit (see Content::GraphicsState#miter_limit) when no argument is
|
541
|
+
# given. Otherwise sets the miter limit to the given +limit+ and returns self. The setter
|
542
|
+
# version can also be called in the miter_limit= form.
|
543
|
+
#
|
544
|
+
# If the +limit+ and a block are provided, the changed miter limit is only active during the
|
545
|
+
# block by saving and restoring the graphics state.
|
546
|
+
#
|
547
|
+
# Examples:
|
548
|
+
#
|
549
|
+
# canvas.miter_limit(10)
|
550
|
+
# canvas.miter_limit # => 10
|
551
|
+
# canvas.miter_limit = 5 # => 5
|
552
|
+
#
|
553
|
+
# canvas.miter_limit(10) do
|
554
|
+
# canvas.miter_limit # => 10
|
555
|
+
# end
|
556
|
+
# canvas.miter_limit # => 5
|
557
|
+
#
|
558
|
+
# See: PDF1.7 s8.4.3.5
|
559
|
+
def miter_limit(limit = nil, &block)
|
560
|
+
gs_getter_setter(:miter_limit, :M, limit, &block)
|
561
|
+
end
|
562
|
+
alias :miter_limit= :miter_limit
|
563
|
+
|
564
|
+
# :call-seq:
|
565
|
+
# canvas.line_dash_pattern => current_line_dash_pattern
|
566
|
+
# canvas.line_dash_pattern(line_dash_pattern) => canvas
|
567
|
+
# canvas.line_dash_pattern(length, phase = 0) => canvas
|
568
|
+
# canvas.line_dash_pattern(array, phase = 0) => canvas
|
569
|
+
# canvas.line_dash_pattern(value, phase = 0) { block } => canvas
|
570
|
+
#
|
571
|
+
# The line dash pattern defines the appearance of a stroked path (line _or_ curve), ie. if
|
572
|
+
# it is solid or if it contains dashes and gaps.
|
573
|
+
#
|
574
|
+
# There are multiple ways to set the line dash pattern:
|
575
|
+
#
|
576
|
+
# * By providing a Content::LineDashPattern object
|
577
|
+
# * By providing a single Integer/Float that is used for both dashes and gaps
|
578
|
+
# * By providing an array of Integers/Floats that specify the alternating dashes and gaps
|
579
|
+
#
|
580
|
+
# The phase (i.e. the distance into the dashes/gaps at which to start) can additionally be
|
581
|
+
# set in the last two cases.
|
582
|
+
#
|
583
|
+
# A solid line can be achieved by using 0 for the length or by using an empty array.
|
584
|
+
#
|
585
|
+
# Returns the current line dash pattern (see Content::GraphicsState#line_dash_pattern) when no
|
586
|
+
# argument is given. Otherwise sets the line dash pattern using the given arguments and
|
587
|
+
# returns self. The setter version can also be called in the line_dash_pattern= form (but only
|
588
|
+
# without the second argument!).
|
589
|
+
#
|
590
|
+
# If arguments and a block are provided, the changed line dash pattern is only active during
|
591
|
+
# the block by saving and restoring the graphics state.
|
592
|
+
#
|
593
|
+
# Examples:
|
594
|
+
#
|
595
|
+
# canvas.line_dash_pattern(10)
|
596
|
+
# canvas.line_dash_pattern # => LineDashPattern.new([10], 0)
|
597
|
+
# canvas.line_dash_pattern(10, 2)
|
598
|
+
# canvas.line_dash_pattern([5, 3, 1], 2)
|
599
|
+
# canvas.line_dash_pattern = LineDashPattern.new([5, 3, 1], 1)
|
600
|
+
#
|
601
|
+
# canvas.line_dash_pattern(10) do
|
602
|
+
# canvas.line_dash_pattern # => LineDashPattern.new([10], 0)
|
603
|
+
# end
|
604
|
+
# canvas.line_dash_pattern # => LineDashPattern.new([5, 3, 1], 1)
|
605
|
+
#
|
606
|
+
# See: PDF1.7 s8.4.3.5, LineDashPattern
|
607
|
+
def line_dash_pattern(value = nil, phase = 0, &block)
|
608
|
+
case value
|
609
|
+
when nil, LineDashPattern
|
610
|
+
when Array
|
611
|
+
value = LineDashPattern.new(value, phase)
|
612
|
+
when 0
|
613
|
+
value = LineDashPattern.new([], 0)
|
614
|
+
else
|
615
|
+
value = LineDashPattern.new([value], phase)
|
616
|
+
end
|
617
|
+
gs_getter_setter(:line_dash_pattern, :d, value, &block)
|
618
|
+
end
|
619
|
+
alias :line_dash_pattern= :line_dash_pattern
|
620
|
+
|
621
|
+
# :call-seq:
|
622
|
+
# canvas.rendering_intent => current_rendering_intent
|
623
|
+
# canvas.rendering_intent(intent) => canvas
|
624
|
+
# canvas.rendering_intent(intent) { block } => canvas
|
625
|
+
#
|
626
|
+
# The rendering intent is used to specify the intent on how colors should be rendered since
|
627
|
+
# sometimes compromises have to be made when the capabilities of an output device are not
|
628
|
+
# sufficient. The +intent+ parameter can be one of the following symbols:
|
629
|
+
#
|
630
|
+
# * +:AbsoluteColorimetric+
|
631
|
+
# * +:RelativeColorimetric+
|
632
|
+
# * +:Saturation+
|
633
|
+
# * +:Perceptual+
|
634
|
+
#
|
635
|
+
# Returns the current rendering intent (see Content::GraphicsState#rendering_intent) when no
|
636
|
+
# argument is given. Otherwise sets the rendering intent using the +intent+ argument and
|
637
|
+
# returns self. The setter version can also be called in the rendering_intent= form.
|
638
|
+
#
|
639
|
+
# If the +intent+ and a block are provided, the changed rendering intent is only active
|
640
|
+
# during the block by saving and restoring the graphics state.
|
641
|
+
#
|
642
|
+
# Examples:
|
643
|
+
#
|
644
|
+
# canvas.rendering_intent(:Perceptual)
|
645
|
+
# canvas.rendering_intent # => :Perceptual
|
646
|
+
# canvas.rendering_intent = :Saturation # => :Saturation
|
647
|
+
#
|
648
|
+
# canvas.rendering_intent(:Perceptual) do
|
649
|
+
# canvas.rendering_intent # => :Perceptual
|
650
|
+
# end
|
651
|
+
# canvas.rendering_intent # => :Saturation
|
652
|
+
#
|
653
|
+
# See: PDF1.7 s8.6.5.8, RenderingIntent
|
654
|
+
def rendering_intent(intent = nil, &bk)
|
655
|
+
gs_getter_setter(:rendering_intent, :ri, intent && RenderingIntent.normalize(intent), &bk)
|
656
|
+
end
|
657
|
+
alias :rendering_intent= :rendering_intent
|
658
|
+
|
659
|
+
# :call-seq:
|
660
|
+
# canvas.stroke_color => current_stroke_color
|
661
|
+
# canvas.stroke_color(gray) => canvas
|
662
|
+
# canvas.stroke_color(r, g, b) => canvas
|
663
|
+
# canvas.stroke_color(c, m, y, k) => canvas
|
664
|
+
# canvas.stroke_color(string) => canvas
|
665
|
+
# canvas.stroke_color(color_object) => canvas
|
666
|
+
# canvas.stroke_color(array) => canvas
|
667
|
+
# canvas.stroke_color(color_spec) { block } => canvas
|
668
|
+
#
|
669
|
+
# The stroke color defines the color used for stroking operations, i.e. for painting paths.
|
670
|
+
#
|
671
|
+
# There are several ways to define the color that should be used:
|
672
|
+
#
|
673
|
+
# * A single numeric argument specifies a gray color (see
|
674
|
+
# Content::ColorSpace::DeviceGray::Color).
|
675
|
+
# * Three numeric arguments specify an RGB color (see Content::ColorSpace::DeviceRGB::Color).
|
676
|
+
# * A string in the format "RRGGBB" where "RR" is the hexadecimal number for the red, "GG"
|
677
|
+
# for the green and "BB" for the blue color value also specifies an RGB color.
|
678
|
+
# * Four numeric arguments specify a CMYK color (see Content::ColorSpace::DeviceCMYK::Color).
|
679
|
+
# * A color object is used directly (normally used for color spaces other than DeviceRGB,
|
680
|
+
# DeviceCMYK and DeviceGray).
|
681
|
+
# * An array is treated as if its items were specified separately as arguments.
|
682
|
+
#
|
683
|
+
# Returns the current stroke color (see Content::GraphicsState#stroke_color) when no argument
|
684
|
+
# is given. Otherwise sets the stroke color using the given arguments and returns self. The
|
685
|
+
# setter version can also be called in the stroke_color= form.
|
686
|
+
#
|
687
|
+
# If the arguments and a block are provided, the changed stroke color is only active during
|
688
|
+
# the block by saving and restoring the graphics state.
|
689
|
+
#
|
690
|
+
# Examples:
|
691
|
+
#
|
692
|
+
# # With no arguments just returns the current color
|
693
|
+
# canvas.stroke_color # => DeviceGray.color(0.0)
|
694
|
+
#
|
695
|
+
# # Same gray color because integer values are normalized to the range of 0.0 to 1.0
|
696
|
+
# canvas.stroke_color(102)
|
697
|
+
# canvas.stroke_color(0.4)
|
698
|
+
#
|
699
|
+
# # Specifying RGB colors
|
700
|
+
# canvas.stroke_color(255, 255, 0)
|
701
|
+
# canvas.stroke_color("FFFF00")
|
702
|
+
#
|
703
|
+
# # Specifying CMYK colors
|
704
|
+
# canvas.stroke_color(255, 255, 0, 128)
|
705
|
+
#
|
706
|
+
# # Can use a color object directly
|
707
|
+
# color = HexaPDF::Content::ColorSpace::DeviceRGB.color(255, 255, 0)
|
708
|
+
# canvas.stroke_color(color)
|
709
|
+
#
|
710
|
+
# # An array argument is destructured - these calls are all equal
|
711
|
+
# cnavas.stroke_color(255, 255, 0)
|
712
|
+
# canvas.stroke_color([255, 255, 0])
|
713
|
+
# canvas.stroke_color = [255, 255, 0]
|
714
|
+
#
|
715
|
+
# # As usual, can be invoked with a block to limit the effects
|
716
|
+
# canvas.stroke_color(102) do
|
717
|
+
# canvas.stroke_color # => ColorSpace::DeviceGray.color(0.4)
|
718
|
+
# end
|
719
|
+
#
|
720
|
+
# See: PDF1.7 s8.6, ColorSpace
|
721
|
+
def stroke_color(*color, &block)
|
722
|
+
color_getter_setter(:stroke_color, color, :RG, :G, :K, :CS, :SCN, &block)
|
723
|
+
end
|
724
|
+
alias :stroke_color= :stroke_color
|
725
|
+
|
726
|
+
# The fill color defines the color used for non-stroking operations, i.e. for filling paths.
|
727
|
+
#
|
728
|
+
# Works exactly the same #stroke_color but for the fill color. See #stroke_color for
|
729
|
+
# details on invocation and use.
|
730
|
+
def fill_color(*color, &block)
|
731
|
+
color_getter_setter(:fill_color, color, :rg, :g, :k, :cs, :scn, &block)
|
732
|
+
end
|
733
|
+
alias :fill_color= :fill_color
|
734
|
+
|
735
|
+
# :call-seq:
|
736
|
+
# canvas.opacity => current_values
|
737
|
+
# canvas.opacity(fill_alpha:) => canvas
|
738
|
+
# canvas.opacity(stroke_alpha:) => canvas
|
739
|
+
# canvas.opacity(fill_alpha:, stroke_alpha:) => canvas
|
740
|
+
# canvas.opacity(fill_alpha:, stroke_alpha:) { block } => canvas
|
741
|
+
#
|
742
|
+
# The fill and stroke alpha values determine how opaque drawn elements will be. Note that
|
743
|
+
# the fill alpha value applies not just to fill values but to all non-stroking operations
|
744
|
+
# (e.g. images, ...).
|
745
|
+
#
|
746
|
+
# Returns the current fill alpha (see Content::GraphicsState#fill_alpha) and stroke alpha (see
|
747
|
+
# Content::GraphicsState#stroke_alpha) values using a hash with the keys +:fill_alpha+ and
|
748
|
+
# +:stroke_alpha+ when no argument is given. Otherwise sets the fill and stroke alpha values
|
749
|
+
# and returns self. The setter version can also be called in the #opacity= form.
|
750
|
+
#
|
751
|
+
# If the values are set and a block is provided, the changed alpha values are only active
|
752
|
+
# during the block by saving and restoring the graphics state.
|
753
|
+
#
|
754
|
+
# Examples:
|
755
|
+
#
|
756
|
+
# canvas.opacity(fill_alpha: 0.5)
|
757
|
+
# canvas.opacity # => {fill_alpha: 0.5, stroke_alpha: 1.0}
|
758
|
+
# canvas.opacity(fill_alpha: 0.4, stroke_alpha: 0.9)
|
759
|
+
# canvas.opacity # => {fill_alpha: 0.4, stroke_alpha: 0.9}
|
760
|
+
#
|
761
|
+
# canvas.opacity(stroke_alpha: 0.7) do
|
762
|
+
# canvas.opacity # => {fill_alpha: 0.4, stroke_alpha: 0.7}
|
763
|
+
# end
|
764
|
+
# canvas.opacity # => {fill_alpha: 0.4, stroke_alpha: 0.9}
|
765
|
+
#
|
766
|
+
# See: PDF1.7 s11.6.4.4
|
767
|
+
def opacity(fill_alpha: nil, stroke_alpha: nil)
|
768
|
+
if !fill_alpha.nil? || !stroke_alpha.nil?
|
769
|
+
raise_unless_at_page_description_level_or_in_text
|
770
|
+
save_graphics_state if block_given?
|
771
|
+
if (!fill_alpha.nil? && graphics_state.fill_alpha != fill_alpha) ||
|
772
|
+
(!stroke_alpha.nil? && graphics_state.stroke_alpha != stroke_alpha)
|
773
|
+
dict = {Type: :ExtGState}
|
774
|
+
dict[:CA] = stroke_alpha unless stroke_alpha.nil?
|
775
|
+
dict[:ca] = fill_alpha unless fill_alpha.nil?
|
776
|
+
dict[:AIS] = false if graphics_state.alpha_source
|
777
|
+
invoke1(:gs, resources.add_ext_gstate(dict))
|
778
|
+
end
|
779
|
+
if block_given?
|
780
|
+
yield
|
781
|
+
restore_graphics_state
|
782
|
+
end
|
783
|
+
self
|
784
|
+
elsif block_given?
|
785
|
+
raise ArgumentError, "Block only allowed with an argument"
|
786
|
+
else
|
787
|
+
{fill_alpha: graphics_state.fill_alpha, stroke_alpha: graphics_state.stroke_alpha}
|
788
|
+
end
|
789
|
+
end
|
790
|
+
|
791
|
+
# :call-seq:
|
792
|
+
# canvas.move_to(x, y) => canvas
|
793
|
+
#
|
794
|
+
# Begins a new subpath (and possibly a new path) by moving the current point to the given
|
795
|
+
# point.
|
796
|
+
#
|
797
|
+
# Examples:
|
798
|
+
#
|
799
|
+
# canvas.move_to(100, 50)
|
800
|
+
def move_to(x, y)
|
801
|
+
raise_unless_at_page_description_level_or_in_path
|
802
|
+
invoke2(:m, x, y)
|
803
|
+
@current_point[0] = @start_point[0] = x
|
804
|
+
@current_point[1] = @start_point[1] = y
|
805
|
+
self
|
806
|
+
end
|
807
|
+
|
808
|
+
# :call-seq:
|
809
|
+
# canvas.line_to(x, y) => canvas
|
810
|
+
#
|
811
|
+
# Appends a straight line segment from the current point to the given point (which becomes the
|
812
|
+
# new current point) to the current subpath.
|
813
|
+
#
|
814
|
+
# Examples:
|
815
|
+
#
|
816
|
+
# canvas.line_to(100, 100)
|
817
|
+
def line_to(x, y)
|
818
|
+
raise_unless_in_path
|
819
|
+
invoke2(:l, x, y)
|
820
|
+
@current_point[0] = x
|
821
|
+
@current_point[1] = y
|
822
|
+
self
|
823
|
+
end
|
824
|
+
|
825
|
+
# :call-seq:
|
826
|
+
# canvas.curve_to(x, y, p1:, p2:) => canvas
|
827
|
+
# canvas.curve_to(x, y, p1:) => canvas
|
828
|
+
# canvas.curve_to(x, y, p2:) => canvas
|
829
|
+
#
|
830
|
+
# Appends a cubic Bezier curve to the current subpath starting from the current point. The end
|
831
|
+
# point becomes the new current point.
|
832
|
+
#
|
833
|
+
# A Bezier curve consists of the start point, the end point and the two control points +p1+
|
834
|
+
# and +p2+. The start point is always the current point and the end point is specified as
|
835
|
+
# +x+ and +y+ arguments.
|
836
|
+
#
|
837
|
+
# Additionally, either the first control point +p1+ or the second control +p2+ or both
|
838
|
+
# control points have to be specified (as arrays containing two numbers). If the first
|
839
|
+
# control point is not specified, the current point is used as first control point. If the
|
840
|
+
# second control point is not specified, the end point is used as the second control point.
|
841
|
+
#
|
842
|
+
# Examples:
|
843
|
+
#
|
844
|
+
# canvas.curve_to(100, 100, p1: [100, 50], p2: [50, 100])
|
845
|
+
# canvas.curve_to(100, 100, p1: [100, 50])
|
846
|
+
# canvas.curve_to(100, 100, p2: [50, 100])
|
847
|
+
def curve_to(x, y, p1: nil, p2: nil)
|
848
|
+
raise_unless_in_path
|
849
|
+
if p1 && p2
|
850
|
+
invoke(:c, *p1, *p2, x, y)
|
851
|
+
elsif p1
|
852
|
+
invoke(:y, *p1, x, y)
|
853
|
+
elsif p2
|
854
|
+
invoke(:v, *p2, x, y)
|
855
|
+
else
|
856
|
+
raise ArgumentError, "At least one control point must be specified for Bézier curves"
|
857
|
+
end
|
858
|
+
@current_point[0] = x
|
859
|
+
@current_point[1] = y
|
860
|
+
self
|
861
|
+
end
|
862
|
+
|
863
|
+
# :call-seq:
|
864
|
+
# canvas.rectangle(x, y, width, height, radius: 0) => canvas
|
865
|
+
#
|
866
|
+
# Appends a rectangle to the current path as a complete subpath (drawn in counterclockwise
|
867
|
+
# direction), with the lower-left corner specified by +x+ and +y+ and the given +width+ and
|
868
|
+
# +height+.
|
869
|
+
#
|
870
|
+
# If +radius+ is greater than 0, the corners are rounded with the given radius.
|
871
|
+
#
|
872
|
+
# If there is no current path when the method is invoked, a new path is automatically begun.
|
873
|
+
#
|
874
|
+
# The current point is set to the lower-left corner if +radius+ is zero, otherwise it is set
|
875
|
+
# to (x, y + radius).
|
876
|
+
#
|
877
|
+
# Examples:
|
878
|
+
#
|
879
|
+
# canvas.rectangle(100, 100, 100, 50)
|
880
|
+
# canvas.rectangle(100, 100, 100, 50, radius: 10)
|
881
|
+
def rectangle(x, y, width, height, radius: 0)
|
882
|
+
raise_unless_at_page_description_level_or_in_path
|
883
|
+
if radius == 0
|
884
|
+
invoke(:re, x, y, width, height)
|
885
|
+
@current_point[0] = @start_point[0] = x
|
886
|
+
@current_point[1] = @start_point[1] = y
|
887
|
+
self
|
888
|
+
else
|
889
|
+
polygon(x, y, x + width, y, x + width, y + height, x, y + height, radius: radius)
|
890
|
+
end
|
891
|
+
end
|
892
|
+
|
893
|
+
# :call-seq:
|
894
|
+
# canvas.close_subpath => canvas
|
895
|
+
#
|
896
|
+
# Closes the current subpath by appending a straight line from the current point to the
|
897
|
+
# start point of the subpath which also becomes the new current point.
|
898
|
+
def close_subpath
|
899
|
+
raise_unless_in_path
|
900
|
+
invoke0(:h)
|
901
|
+
@current_point = @start_point
|
902
|
+
self
|
903
|
+
end
|
904
|
+
|
905
|
+
# :call-seq:
|
906
|
+
# canvas.line(x0, y0, x1, y1) => canvas
|
907
|
+
#
|
908
|
+
# Moves the current point to (x0, y0) and appends a line to (x1, y1) to the current path.
|
909
|
+
#
|
910
|
+
# This method is equal to "canvas.move_to(x0, y0).line_to(x1, y1)".
|
911
|
+
#
|
912
|
+
# Examples:
|
913
|
+
#
|
914
|
+
# canvas.line(10, 10, 100, 100)
|
915
|
+
def line(x0, y0, x1, y1)
|
916
|
+
move_to(x0, y0)
|
917
|
+
line_to(x1, y1)
|
918
|
+
end
|
919
|
+
|
920
|
+
# :call-seq:
|
921
|
+
# canvas.polyline(x0, y0, x1, y1, x2, y2, ...) => canvas
|
922
|
+
#
|
923
|
+
# Moves the current point to (x0, y0) and appends line segments between all given
|
924
|
+
# consecutive points, i.e. between (x0, y0) and (x1, y1), between (x1, y1) and (x2, y2) and
|
925
|
+
# so on. The last point becomes the new current point.
|
926
|
+
#
|
927
|
+
# Examples:
|
928
|
+
#
|
929
|
+
# canvas.polyline(0, 0, 100, 0, 100, 100, 0, 100, 0, 0)
|
930
|
+
def polyline(*points)
|
931
|
+
check_poly_points(points)
|
932
|
+
move_to(points[0], points[1])
|
933
|
+
i = 2
|
934
|
+
while i < points.length
|
935
|
+
line_to(points[i], points[i + 1])
|
936
|
+
i += 2
|
937
|
+
end
|
938
|
+
self
|
939
|
+
end
|
940
|
+
|
941
|
+
# :call-seq:
|
942
|
+
# canvas.polygon(x0, y0, x1, y1, x2, y2, ..., radius: 0) => canvas
|
943
|
+
#
|
944
|
+
# Appends a polygon consisting of the given points to the path as a complete subpath. The
|
945
|
+
# point (x0, y0 + radius) becomes the new current point.
|
946
|
+
#
|
947
|
+
# If +radius+ is greater than 0, the corners are rounded with the given radius.
|
948
|
+
#
|
949
|
+
# If there is no current path when the method is invoked, a new path is automatically begun.
|
950
|
+
#
|
951
|
+
# Examples:
|
952
|
+
#
|
953
|
+
# canvas.polygon(0, 0, 100, 0, 100, 100, 0, 100)
|
954
|
+
# canvas.polygon(0, 0, 100, 0, 100, 100, 0, 100, radius: 10)
|
955
|
+
def polygon(*points, radius: 0)
|
956
|
+
if radius == 0
|
957
|
+
polyline(*points)
|
958
|
+
else
|
959
|
+
check_poly_points(points)
|
960
|
+
move_to(*point_on_line(points[0], points[1], points[2], points[3], distance: radius))
|
961
|
+
points.concat(points[0, 4])
|
962
|
+
0.step(points.length - 6, 2) {|i| line_with_rounded_corner(*points[i, 6], radius)}
|
963
|
+
end
|
964
|
+
close_subpath
|
965
|
+
end
|
966
|
+
|
967
|
+
# :call-seq:
|
968
|
+
# canvas.circle(cx, cy, radius) => canvas
|
969
|
+
#
|
970
|
+
# Appends a circle with center (cx, cy) and the given radius (in degrees) to the path as a
|
971
|
+
# complete subpath (drawn in counterclockwise direction). The point (center_x + radius,
|
972
|
+
# center_y) becomes the new current point.
|
973
|
+
#
|
974
|
+
# If there is no current path when the method is invoked, a new path is automatically begun.
|
975
|
+
#
|
976
|
+
# Examples:
|
977
|
+
#
|
978
|
+
# canvas.circle(100, 100, 10)
|
979
|
+
#
|
980
|
+
# See: #arc (for approximation accuracy)
|
981
|
+
def circle(cx, cy, radius)
|
982
|
+
arc(cx, cy, a: radius)
|
983
|
+
close_subpath
|
984
|
+
end
|
985
|
+
|
986
|
+
# :call-seq:
|
987
|
+
# canvas.ellipse(cx, cy, a:, b:, inclination: 0) => canvas
|
988
|
+
#
|
989
|
+
# Appends an ellipse with center (cx, cy), semi-major axis +a+, semi-minor axis +b+ and an
|
990
|
+
# inclination from the x-axis of +inclination+ degrees to the path as a complete subpath. The
|
991
|
+
# outer-most point on the semi-major axis becomes the new current point.
|
992
|
+
#
|
993
|
+
# If there is no current path when the method is invoked, a new path is automatically begun.
|
994
|
+
#
|
995
|
+
# Examples:
|
996
|
+
#
|
997
|
+
# # Ellipse aligned to x-axis and y-axis
|
998
|
+
# canvas.ellipse(100, 100, a: 10, b: 5)
|
999
|
+
#
|
1000
|
+
# # Inclined ellipse
|
1001
|
+
# canvas.ellipse(100, 100, a: 10, b: 5, inclination: 45)
|
1002
|
+
#
|
1003
|
+
# See: #arc (for approximation accuracy)
|
1004
|
+
def ellipse(cx, cy, a:, b:, inclination: 0)
|
1005
|
+
arc(cx, cy, a: a, b: b, inclination: inclination)
|
1006
|
+
close_subpath
|
1007
|
+
end
|
1008
|
+
|
1009
|
+
# :call-seq:
|
1010
|
+
# canvas.arc(cx, cy, a:, b: a, start_angle: 0, end_angle: 360, clockwise: false, inclination: 0) => canvas
|
1011
|
+
#
|
1012
|
+
# Appends an elliptical arc to the path. The endpoint of the arc becomes the new current
|
1013
|
+
# point.
|
1014
|
+
#
|
1015
|
+
# +cx+::
|
1016
|
+
# x-coordinate of the center point of the arc
|
1017
|
+
#
|
1018
|
+
# +cy+::
|
1019
|
+
# y-coordinate of the center point of the arc
|
1020
|
+
#
|
1021
|
+
# +a+::
|
1022
|
+
# Length of semi-major axis
|
1023
|
+
#
|
1024
|
+
# +b+::
|
1025
|
+
# Length of semi-minor axis (default: +a+)
|
1026
|
+
#
|
1027
|
+
# +start_angle+::
|
1028
|
+
# Angle in degrees at which to start the arc (default: 0)
|
1029
|
+
#
|
1030
|
+
# +end_angle+::
|
1031
|
+
# Angle in degrees at which to end the arc (default: 360)
|
1032
|
+
#
|
1033
|
+
# +clockwise+::
|
1034
|
+
# If +true+ the arc is drawn in clockwise direction, otherwise in counterclockwise
|
1035
|
+
# direction.
|
1036
|
+
#
|
1037
|
+
# +inclination+::
|
1038
|
+
# Angle in degrees between the x-axis and the semi-major axis (default: 0)
|
1039
|
+
#
|
1040
|
+
# If +a+ and +b+ are equal, a circular arc is drawn. If the difference of the start angle
|
1041
|
+
# and end angle is equal to 360, a full ellipse (or circle) is drawn.
|
1042
|
+
#
|
1043
|
+
# If there is no current path when the method is invoked, a new path is automatically begun.
|
1044
|
+
#
|
1045
|
+
# Since PDF doesn't have operators for drawing elliptical or circular arcs, they have to be
|
1046
|
+
# approximated using Bezier curves (see #curve_to). The accuracy of the approximation can be
|
1047
|
+
# controlled using the configuration option 'graphic_object.arc.max_curves'.
|
1048
|
+
#
|
1049
|
+
# Examples:
|
1050
|
+
#
|
1051
|
+
# canvas.arc(0, 0, a: 10) # Circle at (0, 0) with radius 10
|
1052
|
+
# canvas.arc(0, 0, a: 10, b: 5) # Ellipse at (0, 0) with radii 10 and 5
|
1053
|
+
# canvas.arc(0, 0, a: 10, b: 5, inclination: 45) # The above ellipse inclined 45 degrees
|
1054
|
+
#
|
1055
|
+
# # Circular and elliptical arcs from 45 degrees to 135 degrees
|
1056
|
+
# canvas.arc(0, 0, a: 10, start_angle: 45, end_angle: 135)
|
1057
|
+
# canvas.arc(0, 0, a: 10, b: 5, start_angle: 45, end_angle: 135)
|
1058
|
+
#
|
1059
|
+
# # Arcs from 135 degrees to 15 degrees, the first in counterclockwise direction (i.e. the
|
1060
|
+
# # big arc), the other in clockwise direction (i.e. the small arc)
|
1061
|
+
# canvas.arc(0, 0, a: 10, start_angle: 135, end_angle: 15)
|
1062
|
+
# canvas.arc(0, 0, a: 10, start_angle: 135, end_angle: 15, clockwise: true)
|
1063
|
+
#
|
1064
|
+
# See: Content::GraphicObject::Arc
|
1065
|
+
def arc(cx, cy, a:, b: a, start_angle: 0, end_angle: 360, clockwise: false, inclination: 0)
|
1066
|
+
arc = GraphicObject::Arc.configure(cx: cx, cy: cy, a: a, b: b,
|
1067
|
+
start_angle: start_angle, end_angle: end_angle,
|
1068
|
+
clockwise: clockwise, inclination: inclination)
|
1069
|
+
arc.draw(self)
|
1070
|
+
self
|
1071
|
+
end
|
1072
|
+
|
1073
|
+
# :call-seq:
|
1074
|
+
# canvas.graphic_object(obj, **options) => obj
|
1075
|
+
# canvas.graphic_object(name, **options) => graphic_object
|
1076
|
+
#
|
1077
|
+
# Returns the named graphic object, configured with the given options.
|
1078
|
+
#
|
1079
|
+
# If an object responding to :configure is given, it is used. Otherwise the graphic object
|
1080
|
+
# is looked up via the given name in the configuration option 'graphic_object.map'. Then the
|
1081
|
+
# graphic object is configured with the given options if at least one is given.
|
1082
|
+
#
|
1083
|
+
# Examples:
|
1084
|
+
#
|
1085
|
+
# obj = canvas.graphic_object(:arc, cx: 10, cy: 10)
|
1086
|
+
# canvas.draw(obj)
|
1087
|
+
def graphic_object(obj, **options)
|
1088
|
+
unless obj.respond_to?(:configure)
|
1089
|
+
obj = context.document.config.constantize('graphic_object.map', obj)
|
1090
|
+
end
|
1091
|
+
obj = obj.configure(options) if options.size > 0 || !obj.respond_to?(:draw)
|
1092
|
+
obj
|
1093
|
+
end
|
1094
|
+
|
1095
|
+
# :call-seq:
|
1096
|
+
# canvas.draw(obj, **options) => canvas
|
1097
|
+
# canvas.draw(name, **options) => canvas
|
1098
|
+
#
|
1099
|
+
# Draws the given graphic object on the canvas.
|
1100
|
+
#
|
1101
|
+
# See #graphic_object for information on the arguments.
|
1102
|
+
#
|
1103
|
+
# Examples:
|
1104
|
+
#
|
1105
|
+
# canvas.draw(:arc, cx: 10, cy: 10)
|
1106
|
+
def draw(name, **options)
|
1107
|
+
graphic_object(name, **options).draw(self)
|
1108
|
+
self
|
1109
|
+
end
|
1110
|
+
|
1111
|
+
# :call-seq:
|
1112
|
+
# canvas.stroke => canvas
|
1113
|
+
#
|
1114
|
+
# Strokes the path.
|
1115
|
+
#
|
1116
|
+
# See: PDF1.7 s8.5.3.1, s8.5.3.2
|
1117
|
+
def stroke
|
1118
|
+
raise_unless_in_path_or_clipping_path
|
1119
|
+
invoke0(:S)
|
1120
|
+
self
|
1121
|
+
end
|
1122
|
+
|
1123
|
+
# :call-seq:
|
1124
|
+
# canvas.close_stroke => canvas
|
1125
|
+
#
|
1126
|
+
# Closes the last subpath and then strokes the path.
|
1127
|
+
#
|
1128
|
+
# See: PDF1.7 s8.5.3.1, s8.5.3.2
|
1129
|
+
def close_stroke
|
1130
|
+
raise_unless_in_path_or_clipping_path
|
1131
|
+
invoke0(:s)
|
1132
|
+
self
|
1133
|
+
end
|
1134
|
+
|
1135
|
+
# :call-seq:
|
1136
|
+
# canvas.fill(rule = :nonzero) => canvas
|
1137
|
+
#
|
1138
|
+
# Fills the path using the given rule.
|
1139
|
+
#
|
1140
|
+
# The argument +rule+ may either be +:nonzero+ to use the nonzero winding number rule or
|
1141
|
+
# +:even_odd+ to use the even-odd rule for determining which regions to fill in.
|
1142
|
+
#
|
1143
|
+
# Any open subpaths are implicitly closed before being filled.
|
1144
|
+
#
|
1145
|
+
# See: PDF1.7 s8.5.3.1, s8.5.3.3
|
1146
|
+
def fill(rule = :nonzero)
|
1147
|
+
raise_unless_in_path_or_clipping_path
|
1148
|
+
invoke0(rule == :nonzero ? :f : :'f*')
|
1149
|
+
self
|
1150
|
+
end
|
1151
|
+
|
1152
|
+
# :call-seq:
|
1153
|
+
# canvas.fill_stroke(rule = :nonzero) => canvas
|
1154
|
+
#
|
1155
|
+
# Fills and then strokes the path using the given rule.
|
1156
|
+
#
|
1157
|
+
# The argument +rule+ may either be +:nonzero+ to use the nonzero winding number rule or
|
1158
|
+
# +:even_odd+ to use the even-odd rule for determining which regions to fill in.
|
1159
|
+
#
|
1160
|
+
# See: PDF1.7 s8.5.3
|
1161
|
+
def fill_stroke(rule = :nonzero)
|
1162
|
+
raise_unless_in_path_or_clipping_path
|
1163
|
+
invoke0(rule == :nonzero ? :B : :'B*')
|
1164
|
+
self
|
1165
|
+
end
|
1166
|
+
|
1167
|
+
# :call-seq:
|
1168
|
+
# canvas.close_fill_stroke(rule = :nonzero) => canvas
|
1169
|
+
#
|
1170
|
+
# Closes the last subpath and then fills and strokes the path using the given rule.
|
1171
|
+
#
|
1172
|
+
# The argument +rule+ may either be +:nonzero+ to use the nonzero winding number rule or
|
1173
|
+
# +:even_odd+ to use the even-odd rule for determining which regions to fill in.
|
1174
|
+
#
|
1175
|
+
# See: PDF1.7 s8.5.3
|
1176
|
+
def close_fill_stroke(rule = :nonzero)
|
1177
|
+
raise_unless_in_path_or_clipping_path
|
1178
|
+
invoke0(rule == :nonzero ? :b : :'b*')
|
1179
|
+
self
|
1180
|
+
end
|
1181
|
+
|
1182
|
+
# :call-seq:
|
1183
|
+
# canvas.end_path => canvas
|
1184
|
+
#
|
1185
|
+
# Ends the path without stroking or filling it.
|
1186
|
+
#
|
1187
|
+
# This method is normally used in conjunction with the clipping path methods to define the
|
1188
|
+
# clipping.
|
1189
|
+
#
|
1190
|
+
# See: PDF1.7 s8.5.3.1 #clip
|
1191
|
+
def end_path
|
1192
|
+
raise_unless_in_path_or_clipping_path
|
1193
|
+
invoke0(:n)
|
1194
|
+
self
|
1195
|
+
end
|
1196
|
+
|
1197
|
+
# :call-seq:
|
1198
|
+
# canvas.clip_path(rule = :nonzero) => canvas
|
1199
|
+
#
|
1200
|
+
# Modifies the clipping path by intersecting it with the current path.
|
1201
|
+
#
|
1202
|
+
# The argument +rule+ may either be +:nonzero+ to use the nonzero winding number rule or
|
1203
|
+
# +:even_odd+ to use the even-odd rule for determining which regions lie inside the clipping
|
1204
|
+
# path.
|
1205
|
+
#
|
1206
|
+
# Note that the current path cannot be modified after invoking this method! This means that
|
1207
|
+
# one of the path painting methods or #end_path must be called immediately afterwards.
|
1208
|
+
#
|
1209
|
+
# See: PDF1.7 s8.5.4
|
1210
|
+
def clip_path(rule = :nonzero)
|
1211
|
+
raise_unless_in_path
|
1212
|
+
invoke0(rule == :nonzero ? :W : :'W*')
|
1213
|
+
self
|
1214
|
+
end
|
1215
|
+
|
1216
|
+
# :call-seq:
|
1217
|
+
# canvas.xobject(filename, at:, width: nil, height: nil) => xobject
|
1218
|
+
# canvas.xobject(io, at:, width: nil, height: nil) => xobject
|
1219
|
+
# canvas.xobject(image_object, at:, width: nil, height: nil) => image_object
|
1220
|
+
# canvas.xobject(form_object, at:, width: nil, height: nil) => form_object
|
1221
|
+
#
|
1222
|
+
# Draws the given XObject (either an image XObject or a form XObject) at the specified
|
1223
|
+
# position and returns the XObject.
|
1224
|
+
#
|
1225
|
+
# Any image format for which a HexaPDF::ImageLoader object is available and registered with
|
1226
|
+
# the configuration option 'image_loader' can be used. PNG and JPEG images are supported out
|
1227
|
+
# of the box.
|
1228
|
+
#
|
1229
|
+
# If the filename or the IO specifies a PDF file, the first page of this file is used to
|
1230
|
+
# create a form XObject which is then drawn.
|
1231
|
+
#
|
1232
|
+
# The +at+ argument has to be an array containing two numbers specifying the lower-left
|
1233
|
+
# corner at which to draw the XObject.
|
1234
|
+
#
|
1235
|
+
# If +width+ and +height+ are specified, the drawn XObject will have exactly these
|
1236
|
+
# dimensions. If only one of them is specified, the other dimension is automatically
|
1237
|
+
# calculated so that the aspect ratio is retained. If neither is specified, the width and
|
1238
|
+
# height of the XObject are used (for images, 1 pixel being represented by 1 PDF point, i.e.
|
1239
|
+
# 72 DPI).
|
1240
|
+
#
|
1241
|
+
# *Note*: If a form XObject is drawn, all currently set graphics state parameters influence
|
1242
|
+
# the rendering of the form XObject. This means, for example, that when the line width is
|
1243
|
+
# set to 20, all lines of the form XObject are drawn with that line width unless the line
|
1244
|
+
# width is changed in the form XObject itself.
|
1245
|
+
#
|
1246
|
+
# Examples:
|
1247
|
+
#
|
1248
|
+
# canvas.xobject('test.png', at: [100, 100])
|
1249
|
+
# canvas.xobject('test.pdf', at: [100, 100])
|
1250
|
+
#
|
1251
|
+
# File.new('test.jpg', 'rb') do |io|
|
1252
|
+
# canvas.xobject(io, at: [100, 200], width: 300)
|
1253
|
+
# end
|
1254
|
+
#
|
1255
|
+
# image = document.object(5) # Object with oid=5 is an image XObject in this example
|
1256
|
+
# canvas.xobject(image, at: [100, 200], width: 200, heigth: 300)
|
1257
|
+
#
|
1258
|
+
# See: PDF1.7 s8.8, s.8.10.1
|
1259
|
+
def xobject(obj, at:, width: nil, height: nil)
|
1260
|
+
unless obj.kind_of?(HexaPDF::Stream)
|
1261
|
+
obj = context.document.utils.add_image(obj)
|
1262
|
+
end
|
1263
|
+
|
1264
|
+
if obj[:Subtype] == :Image
|
1265
|
+
width, height = calculate_dimensions(obj[:Width], obj[:Height],
|
1266
|
+
rwidth: width, rheight: height)
|
1267
|
+
else
|
1268
|
+
width, height = calculate_dimensions(obj.box.width, obj.box.height,
|
1269
|
+
rwidth: width, rheight: height)
|
1270
|
+
width /= obj.box.width.to_f
|
1271
|
+
height /= obj.box.height.to_f
|
1272
|
+
at[0] -= obj.box.left
|
1273
|
+
at[1] -= obj.box.bottom
|
1274
|
+
end
|
1275
|
+
|
1276
|
+
transform(width, 0, 0, height, at[0], at[1]) do
|
1277
|
+
invoke1(:Do, resources.add_xobject(obj))
|
1278
|
+
end
|
1279
|
+
|
1280
|
+
obj
|
1281
|
+
end
|
1282
|
+
alias :image :xobject
|
1283
|
+
|
1284
|
+
# :call-seq:
|
1285
|
+
# canvas.character_spacing => current_character_spacing
|
1286
|
+
# canvas.character_spacing(amount) => canvas
|
1287
|
+
# canvas.character_spacing(amount) { block } => canvas
|
1288
|
+
#
|
1289
|
+
# The character spacing determines how much additional space is added between two
|
1290
|
+
# consecutive characters. For horizontal writing positive values increase the distance
|
1291
|
+
# between two characters, whereas for vertical writing negative values increase the
|
1292
|
+
# distance.
|
1293
|
+
#
|
1294
|
+
# Returns the current character spacing value (see Content::GraphicsState#character_spacing)
|
1295
|
+
# when no argument is given. Otherwise sets the character spacing using the +amount+ argument
|
1296
|
+
# and returns self. The setter version can also be called in the character_spacing= form.
|
1297
|
+
#
|
1298
|
+
# If the +amount+ and a block are provided, the changed character spacing is only active
|
1299
|
+
# during the block by saving and restoring the graphics state.
|
1300
|
+
#
|
1301
|
+
# Examples:
|
1302
|
+
#
|
1303
|
+
# canvas.character_spacing(0.25)
|
1304
|
+
# canvas.character_spacing # => 0.25
|
1305
|
+
# canvas.character_spacing = 0.5 # => 0.5
|
1306
|
+
#
|
1307
|
+
# canvas.character_spacing(0.10) do
|
1308
|
+
# canvas.character_spacing # => 0.10
|
1309
|
+
# end
|
1310
|
+
# canvas.character_spacing # => 0.5
|
1311
|
+
#
|
1312
|
+
# See: PDF1.7 s9.3.2
|
1313
|
+
def character_spacing(amount = nil, &bk)
|
1314
|
+
gs_getter_setter(:character_spacing, :Tc, amount, &bk)
|
1315
|
+
end
|
1316
|
+
alias :character_spacing= :character_spacing
|
1317
|
+
|
1318
|
+
# :call-seq:
|
1319
|
+
# canvas.word_spacing => current_word_spacing
|
1320
|
+
# canvas.word_spacing(amount) => canvas
|
1321
|
+
# canvas.word_spacing(amount) { block } => canvas
|
1322
|
+
#
|
1323
|
+
# The word spacing determines how much additional space is added when the ASCII space
|
1324
|
+
# character is encountered in a text. For horizontal writing positive values increase the
|
1325
|
+
# distance between two words, whereas for vertical writing negative values increase the
|
1326
|
+
# distance.
|
1327
|
+
#
|
1328
|
+
# Returns the current word spacing value (see Content::GraphicsState#word_spacing) when no
|
1329
|
+
# argument is given. Otherwise sets the word spacing using the +amount+ argument and returns
|
1330
|
+
# self. The setter version can also be called in the word_spacing= form.
|
1331
|
+
#
|
1332
|
+
# If the +amount+ and a block are provided, the changed word spacing is only active during
|
1333
|
+
# the block by saving and restoring the graphics state.
|
1334
|
+
#
|
1335
|
+
# Examples:
|
1336
|
+
#
|
1337
|
+
# canvas.word_spacing(0.25)
|
1338
|
+
# canvas.word_spacing # => 0.25
|
1339
|
+
# canvas.word_spacing = 0.5 # => 0.5
|
1340
|
+
#
|
1341
|
+
# canvas.word_spacing(0.10) do
|
1342
|
+
# canvas.word_spacing # => 0.10
|
1343
|
+
# end
|
1344
|
+
# canvas.word_spacing # => 0.5
|
1345
|
+
#
|
1346
|
+
# See: PDF1.7 s9.3.3
|
1347
|
+
def word_spacing(amount = nil, &bk)
|
1348
|
+
gs_getter_setter(:word_spacing, :Tw, amount, &bk)
|
1349
|
+
end
|
1350
|
+
alias :word_spacing= :word_spacing
|
1351
|
+
|
1352
|
+
# :call-seq:
|
1353
|
+
# canvas.horizontal_scaling => current_horizontal_scaling
|
1354
|
+
# canvas.horizontal_scaling(percent) => canvas
|
1355
|
+
# canvas.horizontal_scaling(percent) { block } => canvas
|
1356
|
+
#
|
1357
|
+
# The horizontal scaling adjusts the width of text character glyphs by stretching or
|
1358
|
+
# compressing them in the horizontal direction. The value is specified as percent of the
|
1359
|
+
# normal width.
|
1360
|
+
#
|
1361
|
+
# Returns the current horizontal scaling value (see Content::GraphicsState#horizontal_scaling)
|
1362
|
+
# when no argument is given. Otherwise sets the horizontal scaling using the +percent+
|
1363
|
+
# argument and returns self. The setter version can also be called in the horizontal_scaling=
|
1364
|
+
# form.
|
1365
|
+
#
|
1366
|
+
# If the +percent+ and a block are provided, the changed horizontal scaling is only active
|
1367
|
+
# during the block by saving and restoring the graphics state.
|
1368
|
+
#
|
1369
|
+
# Examples:
|
1370
|
+
#
|
1371
|
+
# canvas.horizontal_scaling(50) # each glyph has only 50% width
|
1372
|
+
# canvas.horizontal_scaling # => 50
|
1373
|
+
# canvas.horizontal_scaling = 125 # => 125
|
1374
|
+
#
|
1375
|
+
# canvas.horizontal_scaling(75) do
|
1376
|
+
# canvas.horizontal_scaling # => 75
|
1377
|
+
# end
|
1378
|
+
# canvas.horizontal_scaling # => 125
|
1379
|
+
#
|
1380
|
+
# See: PDF1.7 s9.3.4
|
1381
|
+
def horizontal_scaling(amount = nil, &bk)
|
1382
|
+
gs_getter_setter(:horizontal_scaling, :Tz, amount, &bk)
|
1383
|
+
end
|
1384
|
+
alias :horizontal_scaling= :horizontal_scaling
|
1385
|
+
|
1386
|
+
# :call-seq:
|
1387
|
+
# canvas.leading => current_leading
|
1388
|
+
# canvas.leading(amount) => canvas
|
1389
|
+
# canvas.leading(amount) { block } => canvas
|
1390
|
+
#
|
1391
|
+
# The leading specifies the vertical distance between the baselines of adjacent text lines.
|
1392
|
+
#
|
1393
|
+
# Returns the current leading value (see Content::GraphicsState#leading) when no argument is
|
1394
|
+
# given. Otherwise sets the leading using the +amount+ argument and returns self. The setter
|
1395
|
+
# version can also be called in the leading= form.
|
1396
|
+
#
|
1397
|
+
# If the +amount+ and a block are provided, the changed leading is only active during the
|
1398
|
+
# block by saving and restoring the graphics state.
|
1399
|
+
#
|
1400
|
+
# Examples:
|
1401
|
+
#
|
1402
|
+
# canvas.leading(14.5)
|
1403
|
+
# canvas.leading # => 14.5
|
1404
|
+
# canvas.leading = 10 # => 10
|
1405
|
+
#
|
1406
|
+
# canvas.leading(25) do
|
1407
|
+
# canvas.leading # => 25
|
1408
|
+
# end
|
1409
|
+
# canvas.leading # => 10
|
1410
|
+
#
|
1411
|
+
# See: PDF1.7 s9.3.5
|
1412
|
+
def leading(amount = nil, &bk)
|
1413
|
+
gs_getter_setter(:leading, :TL, amount, &bk)
|
1414
|
+
end
|
1415
|
+
alias :leading= :leading
|
1416
|
+
|
1417
|
+
# :call-seq:
|
1418
|
+
# canvas.text_rendering_mode => current_text_rendering_mode
|
1419
|
+
# canvas.text_rendering_mode(mode) => canvas
|
1420
|
+
# canvas.text_rendering_mode(mode) { block } => canvas
|
1421
|
+
#
|
1422
|
+
# The text rendering mode determines if and how glyphs are rendered. The +mode+ parameter
|
1423
|
+
# can either be a valid integer or one of the symbols +:fill+, +:stroke+, +:fill_stroke+,
|
1424
|
+
# +:invisible+, +:fill_clip+, +:stroke_clip+, +:fill_stroke_clip+ or +:clip+ (see
|
1425
|
+
# TextRenderingMode.normalize for details). Note that the return value is always a
|
1426
|
+
# normalized text rendering mode value.
|
1427
|
+
#
|
1428
|
+
# Returns the current text rendering mode value (see
|
1429
|
+
# Content::GraphicsState#text_rendering_mode) when no argument is given. Otherwise sets the
|
1430
|
+
# text rendering mode using the +mode+ argument and returns self. The setter version can also
|
1431
|
+
# be called in the text_rendering_mode= form.
|
1432
|
+
#
|
1433
|
+
# If the +mode+ and a block are provided, the changed text rendering mode is only active
|
1434
|
+
# during the block by saving and restoring the graphics state.
|
1435
|
+
#
|
1436
|
+
# Examples:
|
1437
|
+
#
|
1438
|
+
# canvas.text_rendering_mode(:fill)
|
1439
|
+
# canvas.text_rendering_mode # => #<NamedValue @name=:fill, @value = 0>
|
1440
|
+
# canvas.text_rendering_mode = :stroke # => #<NamedValue @name=:stroke, @value = 1>
|
1441
|
+
#
|
1442
|
+
# canvas.text_rendering_mode(3) do
|
1443
|
+
# canvas.text_rendering_mode # => #<NamedValue @name=:invisible, @value = 3>
|
1444
|
+
# end
|
1445
|
+
# canvas.text_rendering_mode # => #<NamedValue @name=:stroke, @value = 1>
|
1446
|
+
#
|
1447
|
+
# See: PDF1.7 s9.3.6
|
1448
|
+
def text_rendering_mode(m = nil, &bk)
|
1449
|
+
gs_getter_setter(:text_rendering_mode, :Tr, m && TextRenderingMode.normalize(m), &bk)
|
1450
|
+
end
|
1451
|
+
alias :text_rendering_mode= :text_rendering_mode
|
1452
|
+
|
1453
|
+
# :call-seq:
|
1454
|
+
# canvas.text_rise => current_text_rise
|
1455
|
+
# canvas.text_rise(amount) => canvas
|
1456
|
+
# canvas.text_rise(amount) { block } => canvas
|
1457
|
+
#
|
1458
|
+
# The text rise specifies the vertical distance to move the baseline up or down from its
|
1459
|
+
# default location. Positive values move the baseline up, negative values down.
|
1460
|
+
#
|
1461
|
+
# Returns the current text rise value (see Content::GraphicsState#text_rise) when no argument
|
1462
|
+
# is given. Otherwise sets the text rise using the +amount+ argument and returns self. The
|
1463
|
+
# setter version can also be called in the text_rise= form.
|
1464
|
+
#
|
1465
|
+
# If the +amount+ and a block are provided, the changed text rise is only active during the
|
1466
|
+
# block by saving and restoring the graphics state.
|
1467
|
+
#
|
1468
|
+
# Examples:
|
1469
|
+
#
|
1470
|
+
# canvas.text_rise(5)
|
1471
|
+
# canvas.text_rise # => 5
|
1472
|
+
# canvas.text_rise = 10 # => 10
|
1473
|
+
#
|
1474
|
+
# canvas.text_rise(15) do
|
1475
|
+
# canvas.text_rise # => 15
|
1476
|
+
# end
|
1477
|
+
# canvas.text_rise # => 10
|
1478
|
+
#
|
1479
|
+
# See: PDF1.7 s9.3.7
|
1480
|
+
def text_rise(amount = nil, &bk)
|
1481
|
+
gs_getter_setter(:text_rise, :Ts, amount, &bk)
|
1482
|
+
end
|
1483
|
+
alias :text_rise= :text_rise
|
1484
|
+
|
1485
|
+
# :call-seq:
|
1486
|
+
# canvas.begin_text(force_new: false) -> canvas
|
1487
|
+
#
|
1488
|
+
# Begins a new text object.
|
1489
|
+
#
|
1490
|
+
# If +force+ is +true+ and the current graphics object is already a text object, it is ended
|
1491
|
+
# and a new text object is begun.
|
1492
|
+
#
|
1493
|
+
# See: PDF1.7 s9.4.1
|
1494
|
+
def begin_text(force_new: false)
|
1495
|
+
raise_unless_at_page_description_level_or_in_text
|
1496
|
+
end_text if force_new
|
1497
|
+
invoke0(:BT) if graphics_object == :none
|
1498
|
+
self
|
1499
|
+
end
|
1500
|
+
|
1501
|
+
# :call-seq:
|
1502
|
+
# canvas.end_text -> canvas
|
1503
|
+
#
|
1504
|
+
# Ends the current text object.
|
1505
|
+
#
|
1506
|
+
# See: PDF1.7 s9.4.1
|
1507
|
+
def end_text
|
1508
|
+
raise_unless_at_page_description_level_or_in_text
|
1509
|
+
invoke0(:ET) if graphics_object == :text
|
1510
|
+
self
|
1511
|
+
end
|
1512
|
+
|
1513
|
+
# :call-seq:
|
1514
|
+
# canvas.text_matrix(a, b, c, d, e, f) => canvas
|
1515
|
+
#
|
1516
|
+
# Sets the text matrix (and the text line matrix) to the given matrix and returns self.
|
1517
|
+
#
|
1518
|
+
# The given values are interpreted as a matrix in the following way:
|
1519
|
+
#
|
1520
|
+
# a b 0
|
1521
|
+
# c d 0
|
1522
|
+
# e f 1
|
1523
|
+
#
|
1524
|
+
# Examples:
|
1525
|
+
#
|
1526
|
+
# canvas.begin_text
|
1527
|
+
# canvas.text_matrix(1, 0, 0, 1, 100, 100)
|
1528
|
+
#
|
1529
|
+
# See: PDF1.7 s9.4.2
|
1530
|
+
def text_matrix(a, b, c, d, e, f)
|
1531
|
+
begin_text
|
1532
|
+
invoke(:Tm, a, b, c, d, e, f)
|
1533
|
+
self
|
1534
|
+
end
|
1535
|
+
|
1536
|
+
# :call-seq:
|
1537
|
+
# canvas.move_text_cursor(offset: nil, absolute: true) -> canvas
|
1538
|
+
#
|
1539
|
+
# Moves the text cursor by modifying the text and text line matrices.
|
1540
|
+
#
|
1541
|
+
# If +offset+ is not specified, the text cursor is moved to the start of the next text line
|
1542
|
+
# using #leading as vertical offset.
|
1543
|
+
#
|
1544
|
+
# Otherwise, the arguments +offset+, which has to be an array of the form [x, y], and
|
1545
|
+
# +absolute+ work together:
|
1546
|
+
#
|
1547
|
+
# * If +absolute+ is +true+, then the text and text line matrices are set to [1, 0, 0, 1, x,
|
1548
|
+
# y], placing the origin of text space, and therefore the text cursor, at [x, y].
|
1549
|
+
#
|
1550
|
+
# Note that +absolute+ has to be understood in terms of the text matrix since for the actual
|
1551
|
+
# rendering the current transformation matrix is multiplied with the text matrix.
|
1552
|
+
#
|
1553
|
+
# * If +absolute+ is +false+, then the text cursor is moved to the start of the next line,
|
1554
|
+
# offset from the start of the current line (the origin of the text line matrix) by
|
1555
|
+
# +offset+.
|
1556
|
+
#
|
1557
|
+
# See: #show_glyphs
|
1558
|
+
def move_text_cursor(offset: nil, absolute: true)
|
1559
|
+
begin_text
|
1560
|
+
if offset
|
1561
|
+
if absolute
|
1562
|
+
text_matrix(1, 0, 0, 1, offset[0], offset[1])
|
1563
|
+
else
|
1564
|
+
invoke2(:Td, offset[0], offset[1])
|
1565
|
+
end
|
1566
|
+
else
|
1567
|
+
invoke0(:"T*")
|
1568
|
+
end
|
1569
|
+
self
|
1570
|
+
end
|
1571
|
+
|
1572
|
+
# :call-seq:
|
1573
|
+
# canvas.text_cursor -> [x, y]
|
1574
|
+
#
|
1575
|
+
# Returns the position of the text cursor, i.e. the origin of the current text matrix.
|
1576
|
+
#
|
1577
|
+
# Note that this method can only be called while the current graphic object is a text object
|
1578
|
+
# since the text matrix is otherwise undefined.
|
1579
|
+
def text_cursor
|
1580
|
+
raise_unless_in_text
|
1581
|
+
graphics_state.tm.evaluate(0, 0)
|
1582
|
+
end
|
1583
|
+
|
1584
|
+
# :call-seq:
|
1585
|
+
# canvas.font => current_font
|
1586
|
+
# canvas.font(name, size: nil, **options) => canvas
|
1587
|
+
#
|
1588
|
+
# Specifies the font that should be used when showing text.
|
1589
|
+
#
|
1590
|
+
# A valid font size need to be provided on the first invocation, otherwise an error is raised.
|
1591
|
+
#
|
1592
|
+
# *Note* that this method returns the font object itself, not the PDF dictionary representing
|
1593
|
+
# the font!
|
1594
|
+
#
|
1595
|
+
# If +size+ is specified, the #font_size method is invoked with it as argument. All other
|
1596
|
+
# options are passed on to the font loaders (see HexaPDF::FontLoader) that are used for
|
1597
|
+
# loading the specified font.
|
1598
|
+
#
|
1599
|
+
# Returns the current font object when no argument is given.
|
1600
|
+
#
|
1601
|
+
# Examples:
|
1602
|
+
#
|
1603
|
+
# canvas.font("Times", variant: :bold, size: 12)
|
1604
|
+
# canvas.font # => font object
|
1605
|
+
# canvas.font = "Times"
|
1606
|
+
#
|
1607
|
+
# See: PDF1.7 s9.2.2
|
1608
|
+
def font(name = nil, size: nil, **options)
|
1609
|
+
if name
|
1610
|
+
@font = context.document.fonts.load(name, options)
|
1611
|
+
if size
|
1612
|
+
font_size(size)
|
1613
|
+
else
|
1614
|
+
size = font_size
|
1615
|
+
raise HexaPDF::Error, "No valid font size set" if size <= 0
|
1616
|
+
invoke_font_operator(@font.dict, size)
|
1617
|
+
end
|
1618
|
+
self
|
1619
|
+
else
|
1620
|
+
@font
|
1621
|
+
end
|
1622
|
+
end
|
1623
|
+
alias :font= :font
|
1624
|
+
|
1625
|
+
# :call-seq:
|
1626
|
+
# canvas.font_size => font_size
|
1627
|
+
# canvas.font_size(size, leading: size * 1.2) => canvas
|
1628
|
+
#
|
1629
|
+
# Specifies the font size.
|
1630
|
+
#
|
1631
|
+
# Note that an error is raised if no font has been set before!
|
1632
|
+
#
|
1633
|
+
# The leading can be additionally set and defaults to the font size times 1.2. If the leading
|
1634
|
+
# should not be changed, +nil+ has to be passed for +leading+.
|
1635
|
+
#
|
1636
|
+
# Returns the current font size when no argument is given.
|
1637
|
+
#
|
1638
|
+
# Examples:
|
1639
|
+
#
|
1640
|
+
# canvas.font_size(12)
|
1641
|
+
# canvas.font_size # => 12
|
1642
|
+
# canvas.font_size(12, leading: 20)
|
1643
|
+
# canvas.font_size = 12
|
1644
|
+
#
|
1645
|
+
# See: PDF1.7 s9.2.2
|
1646
|
+
def font_size(size = nil, leading: size && size * 1.2)
|
1647
|
+
if size
|
1648
|
+
unless @font
|
1649
|
+
raise HexaPDF::Error, "A font needs to be set before the font size can be set"
|
1650
|
+
end
|
1651
|
+
invoke_font_operator(@font.dict, size)
|
1652
|
+
self.leading(leading) if leading
|
1653
|
+
self
|
1654
|
+
else
|
1655
|
+
graphics_state.font_size
|
1656
|
+
end
|
1657
|
+
end
|
1658
|
+
alias :font_size= :font_size
|
1659
|
+
|
1660
|
+
# :call-seq:
|
1661
|
+
# canvas.text(text) -> canvas
|
1662
|
+
# canvas.text(text, at: [x, y]) -> canvas
|
1663
|
+
#
|
1664
|
+
# Shows the given text string.
|
1665
|
+
#
|
1666
|
+
# If no position is provided, the text is positioned at the current position of the text
|
1667
|
+
# cursor (the origin in case of a new text object or otherwise after the last shown text).
|
1668
|
+
#
|
1669
|
+
# The text string may contain any valid Unicode newline separator and if so, multiple lines
|
1670
|
+
# are shown, using #leading for offsetting the lines.
|
1671
|
+
#
|
1672
|
+
# Note that there are no provisions to make sure that all text is visible! So if the text
|
1673
|
+
# string is too long, it will just flow off the page and be cut off.
|
1674
|
+
#
|
1675
|
+
# Examples:
|
1676
|
+
#
|
1677
|
+
# canvas.font('Times', size: 12)
|
1678
|
+
# canvas.text("This is a \n multiline text", at: [100, 100])
|
1679
|
+
#
|
1680
|
+
# See: http://www.unicode.org/reports/tr18/#Line_Boundaries
|
1681
|
+
def text(text, at: nil)
|
1682
|
+
move_text_cursor(offset: at) if at
|
1683
|
+
lines = text.split(/\u{D A}|(?!\u{D A})[\u{A}-\u{D}\u{85}\u{2028}\u{2029}]/, -1)
|
1684
|
+
lines.each_with_index do |str, index|
|
1685
|
+
show_glyphs(@font.decode_utf8(str))
|
1686
|
+
move_text_cursor unless index == lines.length - 1
|
1687
|
+
end
|
1688
|
+
self
|
1689
|
+
end
|
1690
|
+
|
1691
|
+
# :call-seq:
|
1692
|
+
# canvas.show_glyphs(glyphs) -> canvas
|
1693
|
+
#
|
1694
|
+
# Low-level method for actually showing text on the canvas.
|
1695
|
+
#
|
1696
|
+
# The argument +data+ needs to be a an array of glyph objects valid for the current font,
|
1697
|
+
# optionally interspersed with numbers for kerning.
|
1698
|
+
#
|
1699
|
+
# Text is always shown at the current position of the text cursor, i.e. the origin of the text
|
1700
|
+
# matrix. To move the text cursor to somewhere else use #move_text_cursor before calling this
|
1701
|
+
# method.
|
1702
|
+
#
|
1703
|
+
# The text matrix is updated to correctly represent the graphics state after the invocation.
|
1704
|
+
#
|
1705
|
+
# This method is usually not invoked directly but by higher level methods like #show_text.
|
1706
|
+
def show_glyphs(data)
|
1707
|
+
begin_text
|
1708
|
+
|
1709
|
+
result = [''.b]
|
1710
|
+
offset = 0
|
1711
|
+
data.each do |item|
|
1712
|
+
if item.kind_of?(Numeric)
|
1713
|
+
result << item << ''.b
|
1714
|
+
offset -= item * graphics_state.scaled_font_size
|
1715
|
+
else
|
1716
|
+
encoded = @font.encode(item)
|
1717
|
+
result[-1] << encoded
|
1718
|
+
|
1719
|
+
offset += item.width * graphics_state.scaled_font_size +
|
1720
|
+
graphics_state.scaled_character_spacing
|
1721
|
+
offset += graphics_state.scaled_word_spacing if encoded.length == 1 && item.space?
|
1722
|
+
end
|
1723
|
+
end
|
1724
|
+
|
1725
|
+
invoke1(:TJ, result)
|
1726
|
+
graphics_state.tm.translate(offset, 0)
|
1727
|
+
self
|
1728
|
+
end
|
1729
|
+
|
1730
|
+
private
|
1731
|
+
|
1732
|
+
# Invokes the given operator with the operands and serializes it.
|
1733
|
+
def invoke(operator, *operands)
|
1734
|
+
@operators[operator].invoke(self, *operands)
|
1735
|
+
serialize(operator, *operands)
|
1736
|
+
end
|
1737
|
+
|
1738
|
+
# Serializes the operator with the operands to the content stream.
|
1739
|
+
def serialize(operator, *operands)
|
1740
|
+
@contents << @operators[operator].serialize(@serializer, *operands)
|
1741
|
+
end
|
1742
|
+
|
1743
|
+
# Optimized method for zero operands.
|
1744
|
+
def invoke0(operator)
|
1745
|
+
@operators[operator].invoke(self)
|
1746
|
+
@contents << @operators[operator].serialize(@serializer)
|
1747
|
+
end
|
1748
|
+
|
1749
|
+
# Optimized method for one operand.
|
1750
|
+
def invoke1(operator, op1)
|
1751
|
+
@operators[operator].invoke(self, op1)
|
1752
|
+
@contents << @operators[operator].serialize(@serializer, op1)
|
1753
|
+
end
|
1754
|
+
|
1755
|
+
# Optimized method for two operands.
|
1756
|
+
def invoke2(operator, op1, op2)
|
1757
|
+
@operators[operator].invoke(self, op1, op2)
|
1758
|
+
@contents << @operators[operator].serialize(@serializer, op1, op2)
|
1759
|
+
end
|
1760
|
+
|
1761
|
+
# Invokes the font operator using the given PDF font dictionary.
|
1762
|
+
def invoke_font_operator(font, font_size)
|
1763
|
+
if graphics_state.font != font || graphics_state.font_size != font_size
|
1764
|
+
invoke(:Tf, resources.add_font(font), font_size)
|
1765
|
+
end
|
1766
|
+
end
|
1767
|
+
|
1768
|
+
# Raises an error unless the current graphics object is a path.
|
1769
|
+
def raise_unless_in_path
|
1770
|
+
if graphics_object != :path
|
1771
|
+
raise HexaPDF::Error, "Operation only allowed when current graphics object is a path"
|
1772
|
+
end
|
1773
|
+
end
|
1774
|
+
|
1775
|
+
# Raises an error unless the current graphics object is a path or a clipping path.
|
1776
|
+
def raise_unless_in_path_or_clipping_path
|
1777
|
+
if graphics_object != :path && graphics_object != :clipping_path
|
1778
|
+
raise HexaPDF::Error, "Operation only allowed when current graphics object is a " \
|
1779
|
+
"path or clipping path"
|
1780
|
+
end
|
1781
|
+
end
|
1782
|
+
|
1783
|
+
# Raises an error unless the current graphics object is none, i.e. the page description
|
1784
|
+
# level.
|
1785
|
+
def raise_unless_at_page_description_level
|
1786
|
+
end_text if graphics_object == :text
|
1787
|
+
if graphics_object != :none
|
1788
|
+
raise HexaPDF::Error, "Operation only allowed when there is no current graphics object"
|
1789
|
+
end
|
1790
|
+
end
|
1791
|
+
|
1792
|
+
# Raises an error unless the current graphics object is none or a text object.
|
1793
|
+
def raise_unless_at_page_description_level_or_in_text
|
1794
|
+
if graphics_object != :none && graphics_object != :text
|
1795
|
+
raise HexaPDF::Error, "Operation only allowed when current graphics object is a " \
|
1796
|
+
"text object or if there is no current object"
|
1797
|
+
end
|
1798
|
+
end
|
1799
|
+
|
1800
|
+
# Raises an error unless the current graphics object is none or a path object.
|
1801
|
+
def raise_unless_at_page_description_level_or_in_path
|
1802
|
+
end_text if graphics_object == :text
|
1803
|
+
if graphics_object != :none && graphics_object != :path
|
1804
|
+
raise HexaPDF::Error, "Operation only allowed when current graphics object is a " \
|
1805
|
+
"path object or if there is no current object"
|
1806
|
+
end
|
1807
|
+
end
|
1808
|
+
|
1809
|
+
# Raises an error unless the current graphics object is a text object.
|
1810
|
+
def raise_unless_in_text
|
1811
|
+
if graphics_object != :text
|
1812
|
+
raise HexaPDF::Error, "Operation only allowed when current graphics object is a " \
|
1813
|
+
"text object"
|
1814
|
+
end
|
1815
|
+
end
|
1816
|
+
|
1817
|
+
# Utility method that abstracts the implementation of the stroke and fill color methods.
|
1818
|
+
def color_getter_setter(name, color, rg, g, k, cs, scn)
|
1819
|
+
color.flatten!
|
1820
|
+
if color.length > 0
|
1821
|
+
raise_unless_at_page_description_level_or_in_text
|
1822
|
+
color = color_from_specification(color)
|
1823
|
+
|
1824
|
+
save_graphics_state if block_given?
|
1825
|
+
if color != graphics_state.send(name)
|
1826
|
+
case color.color_space.family
|
1827
|
+
when :DeviceRGB then serialize(rg, *color.components)
|
1828
|
+
when :DeviceGray then serialize(g, *color.components)
|
1829
|
+
when :DeviceCMYK then serialize(k, *color.components)
|
1830
|
+
else
|
1831
|
+
if color.color_space != graphics_state.send(name).color_space
|
1832
|
+
serialize(cs, resources.add_color_space(color.color_space))
|
1833
|
+
end
|
1834
|
+
serialize(scn, *color.components)
|
1835
|
+
end
|
1836
|
+
graphics_state.send(:"#{name}=", color)
|
1837
|
+
end
|
1838
|
+
|
1839
|
+
if block_given?
|
1840
|
+
yield
|
1841
|
+
restore_graphics_state
|
1842
|
+
end
|
1843
|
+
|
1844
|
+
self
|
1845
|
+
elsif block_given?
|
1846
|
+
raise ArgumentError, "Block only allowed with arguments"
|
1847
|
+
else
|
1848
|
+
graphics_state.send(name)
|
1849
|
+
end
|
1850
|
+
end
|
1851
|
+
|
1852
|
+
# Creates a color object from the given color specification. See #stroke_color for details
|
1853
|
+
# on the possible color specifications.
|
1854
|
+
def color_from_specification(spec)
|
1855
|
+
if spec.length == 1 && spec[0].kind_of?(String)
|
1856
|
+
resources.color_space(:DeviceRGB).color(*spec[0].scan(/../).map!(&:hex))
|
1857
|
+
elsif spec.length == 1 && spec[0].respond_to?(:color_space)
|
1858
|
+
spec[0]
|
1859
|
+
else
|
1860
|
+
resources.color_space(color_space_for_components(spec)).color(*spec)
|
1861
|
+
end
|
1862
|
+
end
|
1863
|
+
|
1864
|
+
# Returns the name of the device color space that should be used for creating a color object
|
1865
|
+
# from the components array.
|
1866
|
+
def color_space_for_components(components)
|
1867
|
+
case components.length
|
1868
|
+
when 1 then :DeviceGray
|
1869
|
+
when 3 then :DeviceRGB
|
1870
|
+
when 4 then :DeviceCMYK
|
1871
|
+
else
|
1872
|
+
raise ArgumentError, "Invalid number of color components, 1|3|4 expected, " \
|
1873
|
+
"#{components.length} given"
|
1874
|
+
end
|
1875
|
+
end
|
1876
|
+
|
1877
|
+
# Utility method that abstracts the implementation of a graphics state parameter
|
1878
|
+
# getter/setter method with a call sequence of:
|
1879
|
+
#
|
1880
|
+
# canvas.method # => cur_value
|
1881
|
+
# canvas.method(new_value) # => canvas
|
1882
|
+
# canvas.method(new_value) { block } # => canvas
|
1883
|
+
#
|
1884
|
+
# +name+::
|
1885
|
+
# The name (Symbol) of the graphics state parameter for fetching the value from the
|
1886
|
+
# GraphicState.
|
1887
|
+
#
|
1888
|
+
# +op+::
|
1889
|
+
# The operator (Symbol) which should be invoked if the value is different from the current
|
1890
|
+
# value of the graphics state parameter.
|
1891
|
+
#
|
1892
|
+
# +value+::
|
1893
|
+
# The new value of the graphics state parameter, or +nil+ if the getter functionality is
|
1894
|
+
# needed.
|
1895
|
+
def gs_getter_setter(name, op, value)
|
1896
|
+
if !value.nil?
|
1897
|
+
raise_unless_at_page_description_level_or_in_text
|
1898
|
+
save_graphics_state if block_given?
|
1899
|
+
if graphics_state.send(name) != value
|
1900
|
+
value.respond_to?(:to_operands) ? invoke(op, *value.to_operands) : invoke1(op, value)
|
1901
|
+
end
|
1902
|
+
if block_given?
|
1903
|
+
yield
|
1904
|
+
restore_graphics_state
|
1905
|
+
end
|
1906
|
+
self
|
1907
|
+
elsif block_given?
|
1908
|
+
raise ArgumentError, "Block only allowed with an argument"
|
1909
|
+
else
|
1910
|
+
graphics_state.send(name)
|
1911
|
+
end
|
1912
|
+
end
|
1913
|
+
|
1914
|
+
# Modifies and checks the array +points+ so that polylines and polygons work correctly.
|
1915
|
+
def check_poly_points(points)
|
1916
|
+
if points.length < 4
|
1917
|
+
raise ArgumentError, "At least two points needed to make one line segment"
|
1918
|
+
elsif points.length.odd?
|
1919
|
+
raise ArgumentError, "Missing y-coordinate for last point"
|
1920
|
+
end
|
1921
|
+
end
|
1922
|
+
|
1923
|
+
# Used for calculating the optimal distance of the control points.
|
1924
|
+
#
|
1925
|
+
# See: http://itc.ktu.lt/itc354/Riskus354.pdf, p373 right column
|
1926
|
+
KAPPA = 0.55191496 #:nodoc:
|
1927
|
+
|
1928
|
+
# Appends a line with a rounded corner from the current point. The corner is specified by
|
1929
|
+
# the three points (x0, y0), (x1, y1) and (x2, y2) where (x1, y1) is the corner point.
|
1930
|
+
def line_with_rounded_corner(x0, y0, x1, y1, x2, y2, radius)
|
1931
|
+
p0 = point_on_line(x1, y1, x0, y0, distance: radius)
|
1932
|
+
p3 = point_on_line(x1, y1, x2, y2, distance: radius)
|
1933
|
+
p1 = point_on_line(p0[0], p0[1], x1, y1, distance: KAPPA * radius)
|
1934
|
+
p2 = point_on_line(p3[0], p3[1], x1, y1, distance: KAPPA * radius)
|
1935
|
+
line_to(p0[0], p0[1])
|
1936
|
+
curve_to(p3[0], p3[1], p1: p1, p2: p2)
|
1937
|
+
end
|
1938
|
+
|
1939
|
+
# Given two points p0 = (x0, y0) and p1 = (x1, y1), returns the point on the line through
|
1940
|
+
# these points that is +distance+ units away from p0.
|
1941
|
+
#
|
1942
|
+
# v = p1 - p0
|
1943
|
+
# result = p0 + distance * v/norm(v)
|
1944
|
+
def point_on_line(x0, y0, x1, y1, distance:)
|
1945
|
+
norm = Math.sqrt((x1 - x0)**2 + (y1 - y0)**2)
|
1946
|
+
[x0 + distance / norm * (x1 - x0), y0 + distance / norm * (y1 - y0)]
|
1947
|
+
end
|
1948
|
+
|
1949
|
+
# Calculates and returns the requested dimensions for the rectangular object with the given
|
1950
|
+
# +width+ and +height+ based on the options.
|
1951
|
+
#
|
1952
|
+
# +rwidth+::
|
1953
|
+
# The requested width. If +rheight+ is not specified, it is chosen so that the aspect
|
1954
|
+
# ratio is maintained
|
1955
|
+
#
|
1956
|
+
# +rheight+::
|
1957
|
+
# The requested height. If +rwidth+ is not specified, it is chosen so that the aspect
|
1958
|
+
# ratio is maintained
|
1959
|
+
def calculate_dimensions(width, height, rwidth: nil, rheight: nil)
|
1960
|
+
if rwidth && rheight
|
1961
|
+
[rwidth, rheight]
|
1962
|
+
elsif rwidth
|
1963
|
+
[rwidth, height * rwidth / width.to_f]
|
1964
|
+
elsif rheight
|
1965
|
+
[width * rheight / height.to_f, rheight]
|
1966
|
+
else
|
1967
|
+
[width, height]
|
1968
|
+
end
|
1969
|
+
end
|
1970
|
+
|
1971
|
+
end
|
1972
|
+
|
1973
|
+
end
|
1974
|
+
end
|