hexapdf 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|