hexapdf 0.17.1 → 0.17.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1024 -0
- data/LICENSE +29 -0
- data/README.md +129 -0
- data/Rakefile +109 -0
- data/agpl-3.0.txt +661 -0
- data/examples/001-hello_world.rb +16 -0
- data/examples/002-graphics.rb +275 -0
- data/examples/003-arcs.rb +50 -0
- data/examples/004-optimizing.rb +23 -0
- data/examples/005-merging.rb +27 -0
- data/examples/006-standard_pdf_fonts.rb +73 -0
- data/examples/007-truetype.rb +42 -0
- data/examples/008-show_char_bboxes.rb +55 -0
- data/examples/009-text_layouter_alignment.rb +47 -0
- data/examples/010-text_layouter_inline_boxes.rb +64 -0
- data/examples/011-text_layouter_line_wrapping.rb +57 -0
- data/examples/012-text_layouter_styling.rb +122 -0
- data/examples/013-text_layouter_shapes.rb +176 -0
- data/examples/014-text_in_polygon.rb +60 -0
- data/examples/015-boxes.rb +76 -0
- data/examples/016-frame_automatic_box_placement.rb +90 -0
- data/examples/017-frame_text_flow.rb +60 -0
- data/examples/018-composer.rb +44 -0
- data/examples/019-acro_form.rb +88 -0
- data/examples/emoji-smile.png +0 -0
- data/examples/emoji-wink.png +0 -0
- data/examples/machupicchu.jpg +0 -0
- data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +66 -0
- data/lib/hexapdf/content/graphic_object/geom2d.rb +13 -0
- data/lib/hexapdf/version.rb +1 -1
- 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/images/ycck.jpg +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 +236 -0
- data/test/hexapdf/content/common.rb +39 -0
- data/test/hexapdf/content/graphic_object/test_arc.rb +102 -0
- data/test/hexapdf/content/graphic_object/test_endpoint_arc.rb +90 -0
- data/test/hexapdf/content/graphic_object/test_geom2d.rb +79 -0
- data/test/hexapdf/content/graphic_object/test_solid_arc.rb +86 -0
- data/test/hexapdf/content/test_canvas.rb +1279 -0
- data/test/hexapdf/content/test_color_space.rb +176 -0
- data/test/hexapdf/content/test_graphics_state.rb +151 -0
- data/test/hexapdf/content/test_operator.rb +619 -0
- data/test/hexapdf/content/test_parser.rb +99 -0
- data/test/hexapdf/content/test_processor.rb +163 -0
- data/test/hexapdf/content/test_transformation_matrix.rb +64 -0
- data/test/hexapdf/document/test_files.rb +72 -0
- data/test/hexapdf/document/test_fonts.rb +60 -0
- data/test/hexapdf/document/test_images.rb +72 -0
- data/test/hexapdf/document/test_pages.rb +130 -0
- data/test/hexapdf/encryption/common.rb +87 -0
- data/test/hexapdf/encryption/test_aes.rb +129 -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 +380 -0
- data/test/hexapdf/encryption/test_standard_security_handler.rb +322 -0
- data/test/hexapdf/filter/common.rb +53 -0
- data/test/hexapdf/filter/test_ascii85_decode.rb +59 -0
- data/test/hexapdf/filter/test_ascii_hex_decode.rb +38 -0
- data/test/hexapdf/filter/test_crypt.rb +21 -0
- data/test/hexapdf/filter/test_encryption.rb +24 -0
- data/test/hexapdf/filter/test_flate_decode.rb +44 -0
- data/test/hexapdf/filter/test_lzw_decode.rb +52 -0
- data/test/hexapdf/filter/test_predictor.rb +219 -0
- data/test/hexapdf/filter/test_run_length_decode.rb +32 -0
- data/test/hexapdf/font/cmap/test_parser.rb +102 -0
- data/test/hexapdf/font/cmap/test_writer.rb +66 -0
- data/test/hexapdf/font/encoding/test_base.rb +45 -0
- data/test/hexapdf/font/encoding/test_difference_encoding.rb +29 -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_cmap.rb +104 -0
- data/test/hexapdf/font/test_encoding.rb +27 -0
- data/test/hexapdf/font/test_invalid_glyph.rb +34 -0
- data/test/hexapdf/font/test_true_type_wrapper.rb +186 -0
- data/test/hexapdf/font/test_type1_wrapper.rb +107 -0
- data/test/hexapdf/font/true_type/common.rb +17 -0
- data/test/hexapdf/font/true_type/table/common.rb +27 -0
- data/test/hexapdf/font/true_type/table/test_cmap.rb +47 -0
- data/test/hexapdf/font/true_type/table/test_cmap_subtable.rb +141 -0
- data/test/hexapdf/font/true_type/table/test_directory.rb +30 -0
- data/test/hexapdf/font/true_type/table/test_glyf.rb +58 -0
- data/test/hexapdf/font/true_type/table/test_head.rb +56 -0
- data/test/hexapdf/font/true_type/table/test_hhea.rb +26 -0
- data/test/hexapdf/font/true_type/table/test_hmtx.rb +30 -0
- data/test/hexapdf/font/true_type/table/test_kern.rb +61 -0
- data/test/hexapdf/font/true_type/table/test_loca.rb +33 -0
- data/test/hexapdf/font/true_type/table/test_maxp.rb +50 -0
- data/test/hexapdf/font/true_type/table/test_name.rb +76 -0
- data/test/hexapdf/font/true_type/table/test_os2.rb +55 -0
- data/test/hexapdf/font/true_type/table/test_post.rb +78 -0
- data/test/hexapdf/font/true_type/test_builder.rb +42 -0
- data/test/hexapdf/font/true_type/test_font.rb +116 -0
- data/test/hexapdf/font/true_type/test_optimizer.rb +26 -0
- data/test/hexapdf/font/true_type/test_subsetter.rb +73 -0
- data/test/hexapdf/font/true_type/test_table.rb +48 -0
- data/test/hexapdf/font/type1/common.rb +6 -0
- data/test/hexapdf/font/type1/test_afm_parser.rb +65 -0
- data/test/hexapdf/font/type1/test_font.rb +104 -0
- data/test/hexapdf/font/type1/test_font_metrics.rb +22 -0
- data/test/hexapdf/font/type1/test_pfb_parser.rb +37 -0
- data/test/hexapdf/font_loader/test_from_configuration.rb +43 -0
- data/test/hexapdf/font_loader/test_from_file.rb +36 -0
- data/test/hexapdf/font_loader/test_standard14.rb +33 -0
- data/test/hexapdf/image_loader/test_jpeg.rb +93 -0
- data/test/hexapdf/image_loader/test_pdf.rb +47 -0
- data/test/hexapdf/image_loader/test_png.rb +259 -0
- data/test/hexapdf/layout/test_box.rb +154 -0
- data/test/hexapdf/layout/test_frame.rb +350 -0
- data/test/hexapdf/layout/test_image_box.rb +73 -0
- data/test/hexapdf/layout/test_inline_box.rb +71 -0
- data/test/hexapdf/layout/test_line.rb +206 -0
- data/test/hexapdf/layout/test_style.rb +790 -0
- data/test/hexapdf/layout/test_text_box.rb +140 -0
- data/test/hexapdf/layout/test_text_fragment.rb +375 -0
- data/test/hexapdf/layout/test_text_layouter.rb +758 -0
- data/test/hexapdf/layout/test_text_shaper.rb +62 -0
- data/test/hexapdf/layout/test_width_from_polygon.rb +109 -0
- data/test/hexapdf/task/test_dereference.rb +51 -0
- data/test/hexapdf/task/test_optimize.rb +162 -0
- data/test/hexapdf/test_composer.rb +258 -0
- data/test/hexapdf/test_configuration.rb +93 -0
- data/test/hexapdf/test_data_dir.rb +32 -0
- data/test/hexapdf/test_dictionary.rb +340 -0
- data/test/hexapdf/test_dictionary_fields.rb +269 -0
- data/test/hexapdf/test_document.rb +641 -0
- data/test/hexapdf/test_filter.rb +100 -0
- data/test/hexapdf/test_importer.rb +106 -0
- data/test/hexapdf/test_object.rb +258 -0
- data/test/hexapdf/test_parser.rb +645 -0
- data/test/hexapdf/test_pdf_array.rb +169 -0
- data/test/hexapdf/test_rectangle.rb +73 -0
- data/test/hexapdf/test_reference.rb +50 -0
- data/test/hexapdf/test_revision.rb +188 -0
- data/test/hexapdf/test_revisions.rb +196 -0
- data/test/hexapdf/test_serializer.rb +195 -0
- data/test/hexapdf/test_stream.rb +274 -0
- data/test/hexapdf/test_tokenizer.rb +80 -0
- data/test/hexapdf/test_type.rb +18 -0
- data/test/hexapdf/test_writer.rb +140 -0
- data/test/hexapdf/test_xref_section.rb +61 -0
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +795 -0
- data/test/hexapdf/type/acro_form/test_button_field.rb +308 -0
- data/test/hexapdf/type/acro_form/test_choice_field.rb +220 -0
- data/test/hexapdf/type/acro_form/test_field.rb +259 -0
- data/test/hexapdf/type/acro_form/test_form.rb +357 -0
- data/test/hexapdf/type/acro_form/test_signature_field.rb +38 -0
- data/test/hexapdf/type/acro_form/test_text_field.rb +201 -0
- data/test/hexapdf/type/acro_form/test_variable_text_field.rb +88 -0
- data/test/hexapdf/type/actions/test_launch.rb +24 -0
- data/test/hexapdf/type/actions/test_uri.rb +23 -0
- data/test/hexapdf/type/annotations/test_markup_annotation.rb +22 -0
- data/test/hexapdf/type/annotations/test_text.rb +34 -0
- data/test/hexapdf/type/annotations/test_widget.rb +225 -0
- data/test/hexapdf/type/test_annotation.rb +97 -0
- data/test/hexapdf/type/test_catalog.rb +48 -0
- data/test/hexapdf/type/test_cid_font.rb +61 -0
- data/test/hexapdf/type/test_file_specification.rb +141 -0
- data/test/hexapdf/type/test_font.rb +67 -0
- data/test/hexapdf/type/test_font_descriptor.rb +61 -0
- data/test/hexapdf/type/test_font_simple.rb +176 -0
- data/test/hexapdf/type/test_font_true_type.rb +31 -0
- data/test/hexapdf/type/test_font_type0.rb +120 -0
- data/test/hexapdf/type/test_font_type1.rb +142 -0
- data/test/hexapdf/type/test_font_type3.rb +26 -0
- data/test/hexapdf/type/test_form.rb +120 -0
- data/test/hexapdf/type/test_image.rb +261 -0
- data/test/hexapdf/type/test_info.rb +9 -0
- data/test/hexapdf/type/test_object_stream.rb +117 -0
- data/test/hexapdf/type/test_page.rb +598 -0
- data/test/hexapdf/type/test_page_tree_node.rb +315 -0
- data/test/hexapdf/type/test_resources.rb +209 -0
- data/test/hexapdf/type/test_trailer.rb +116 -0
- data/test/hexapdf/type/test_xref_stream.rb +143 -0
- data/test/hexapdf/utils/test_bit_field.rb +63 -0
- data/test/hexapdf/utils/test_bit_stream.rb +69 -0
- data/test/hexapdf/utils/test_graphics_helpers.rb +37 -0
- data/test/hexapdf/utils/test_lru_cache.rb +22 -0
- data/test/hexapdf/utils/test_object_hash.rb +120 -0
- data/test/hexapdf/utils/test_pdf_doc_encoding.rb +18 -0
- data/test/hexapdf/utils/test_sorted_tree_node.rb +239 -0
- data/test/test_helper.rb +58 -0
- metadata +263 -3
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
require 'test_helper'
|
|
4
|
+
require 'hexapdf/document'
|
|
5
|
+
require 'hexapdf/type/font_type0'
|
|
6
|
+
|
|
7
|
+
describe HexaPDF::Type::FontType0 do
|
|
8
|
+
before do
|
|
9
|
+
@doc = HexaPDF::Document.new
|
|
10
|
+
fd = @doc.add({Type: :FontDescriptor, FontBBox: [0, 1, 2, 3]})
|
|
11
|
+
@cid_font = @doc.wrap({Type: :Font, Subtype: :CIDFontType2, W: [633, [100]], FontDescriptor: fd,
|
|
12
|
+
CIDSystemInfo: {Registry: 'Adobe', Ordering: 'Japan1', Supplement: 1}})
|
|
13
|
+
@font = @doc.wrap({Type: :Font, Subtype: :Type0, Encoding: :H, DescendantFonts: [@cid_font]})
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "returns the correct writing mode" do
|
|
17
|
+
assert_equal(:horizontal, @font.writing_mode)
|
|
18
|
+
font = @doc.wrap({Type: :Font, Subtype: :Type0, Encoding: :V})
|
|
19
|
+
assert_equal(:vertical, font.writing_mode)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "resolves the descendant font object correctly" do
|
|
23
|
+
assert_equal(@cid_font, @font.descendant_font)
|
|
24
|
+
@doc.clear_cache
|
|
25
|
+
@font[:DescendantFonts] = [@cid_font.value]
|
|
26
|
+
assert_equal(@cid_font.value, @font.descendant_font.value)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "uses the descendant font for getting the width of a code point" do
|
|
30
|
+
assert_equal(100, @font.width(0x2121))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "uses the descendant font for getting the bounding box" do
|
|
34
|
+
assert_equal([0, 1, 2, 3], @font.bounding_box)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "uses the descendant font for determining whether the font is embedded" do
|
|
38
|
+
refute(@font.embedded?)
|
|
39
|
+
@cid_font[:FontDescriptor][:FontFile2] = 5
|
|
40
|
+
assert(@font.embedded?)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it "uses the descendant font for returning the embedded font file" do
|
|
44
|
+
@cid_font[:FontDescriptor][:FontFile2] = 5
|
|
45
|
+
assert_equal(5, @font.font_file)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
describe "word_spacing_applicable?" do
|
|
49
|
+
it "returns false if code point 32 is not a single-byte code point" do
|
|
50
|
+
refute(@font.word_spacing_applicable?)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it "returns true if code point 32 is a single-byte code point" do
|
|
54
|
+
@font[:Encoding] = @doc.wrap({}, stream: <<-EOF)
|
|
55
|
+
begincodespacerange
|
|
56
|
+
<00> <ff>
|
|
57
|
+
endcodespacerange
|
|
58
|
+
EOF
|
|
59
|
+
assert(@font.word_spacing_applicable?)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
describe "handling of /Encoding value" do
|
|
64
|
+
it "can use predefined CMaps" do
|
|
65
|
+
assert_equal([0x2121], @font.decode("\x21\x21"))
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it "can use custom CMaps" do
|
|
69
|
+
@font[:Encoding] = @doc.wrap({}, stream: <<-EOF)
|
|
70
|
+
begincodespacerange
|
|
71
|
+
<00> <ff>
|
|
72
|
+
endcodespacerange
|
|
73
|
+
EOF
|
|
74
|
+
assert_equal([0x41], @font.decode("\x41"))
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it "raises an error if the /Encoding value is invalid" do
|
|
78
|
+
@font.delete(:Encoding)
|
|
79
|
+
assert_raises(HexaPDF::Error) { @font.decode("a") }
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
describe "decode" do
|
|
84
|
+
it "allows reading CIDs from string using the encoding CMap" do
|
|
85
|
+
assert_equal([0x2121, 0x7e7e], @font.decode("\x21\x21\x7e\x7e"))
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it "fails if the string contains invalid codes" do
|
|
89
|
+
assert_raises(HexaPDF::Error) { @font.decode("a") }
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
describe "to_utf" do
|
|
94
|
+
it "uses the /ToUnicode CMap if it is available" do
|
|
95
|
+
@font[:ToUnicode] = @doc.add({}, stream: <<-EOF)
|
|
96
|
+
2 beginbfchar
|
|
97
|
+
<20> <0041>
|
|
98
|
+
endbfchar
|
|
99
|
+
EOF
|
|
100
|
+
assert_equal("A", @font.to_utf8(32))
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
describe "it uses a predefined UCS2 CMap" do
|
|
104
|
+
it "for predefined CMaps except Identity-H/-V" do
|
|
105
|
+
assert_equal("?", @font.to_utf8(32))
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
it "for CMaps with predefined character collections" do
|
|
109
|
+
@font[:Encoding] = @doc.add({}, stream: "")
|
|
110
|
+
assert_equal("?", @font.to_utf8(32))
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
it "calls the configured proc if no mapping is available" do
|
|
115
|
+
@font[:Encoding] = :"Identity-H"
|
|
116
|
+
@cid_font[:CIDSystemInfo][:Registry] = :Unknown
|
|
117
|
+
assert_raises(HexaPDF::Error) { @font.to_utf8(32) }
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
require 'test_helper'
|
|
4
|
+
require 'hexapdf/document'
|
|
5
|
+
require 'hexapdf/type/font_type1'
|
|
6
|
+
|
|
7
|
+
describe HexaPDF::Type::FontType1::StandardFonts do
|
|
8
|
+
before do
|
|
9
|
+
@obj = HexaPDF::Type::FontType1::StandardFonts
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it "checks whether a given name corresponds to a standard font via #standard_font?" do
|
|
13
|
+
assert(@obj.standard_font?(:"Times-Roman"))
|
|
14
|
+
assert(@obj.standard_font?(:TimesNewRoman))
|
|
15
|
+
refute(@obj.standard_font?(:LibreSans))
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "returns the standard PDF name for an alias via #standard_name" do
|
|
19
|
+
assert_equal(:"Times-Roman", @obj.standard_name(:TimesNewRoman))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe "font" do
|
|
23
|
+
it "returns the Type1 font object for a given standard name" do
|
|
24
|
+
font = @obj.font(:"Times-Roman")
|
|
25
|
+
assert_equal("Times Roman", font.full_name)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "caches the font for reuse" do
|
|
29
|
+
font = @obj.font(:"Times-Roman")
|
|
30
|
+
assert_same(font, @obj.font(:"Times-Roman"))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "returns nil if the given name doesn't belong to a standard font" do
|
|
34
|
+
refute_nil(@obj.font(:TimesNewRoman))
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
describe HexaPDF::Type::FontType1 do
|
|
40
|
+
before do
|
|
41
|
+
@doc = HexaPDF::Document.new
|
|
42
|
+
@font = @doc.add({Type: :Font, Subtype: :Type1, Encoding: :WinAnsiEncoding,
|
|
43
|
+
BaseFont: :"Times-Roman"})
|
|
44
|
+
|
|
45
|
+
font_file = @doc.add({}, stream: <<-EOF)
|
|
46
|
+
/Encoding 256 array
|
|
47
|
+
0 1 255 {1 index exch /.notdef put} for
|
|
48
|
+
dup 32 /A put
|
|
49
|
+
dup 34 /B put
|
|
50
|
+
readonly def
|
|
51
|
+
EOF
|
|
52
|
+
font_descriptor = @doc.add({Type: :FontDescriptor, FontName: :Embedded, Flags: 0b100,
|
|
53
|
+
FontBBox: [0, 1, 2, 3], ItalicAngle: 0, Ascent: 900,
|
|
54
|
+
Descent: -100, CapHeight: 800, StemV: 20, FontFile: font_file})
|
|
55
|
+
@embedded_font = @doc.add({Type: :Font, Subtype: :Type1, Encoding: :WinAnsiEncoding,
|
|
56
|
+
BaseFont: :Embedded, FontDescriptor: font_descriptor,
|
|
57
|
+
FirstChar: 32, LastChar: 34, Widths: [600, 0, 700]})
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it "can create a usable font wrapper for the standard fonts" do
|
|
61
|
+
wrapper = @font.font_wrapper
|
|
62
|
+
assert(wrapper)
|
|
63
|
+
assert_same(@font, wrapper.pdf_object)
|
|
64
|
+
assert_equal(@font[:BaseFont], wrapper.wrapped_font.font_name.intern)
|
|
65
|
+
assert_same(wrapper, @font.font_wrapper)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
describe "encoding" do
|
|
69
|
+
it "returns the the standard font's encoding" do
|
|
70
|
+
@font.delete(:Encoding)
|
|
71
|
+
assert_equal(HexaPDF::Font::Encoding.for_name(:StandardEncoding), @font.encoding)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it "uses the encoding of the embedded font when necessary" do
|
|
75
|
+
@embedded_font.delete(:Encoding)
|
|
76
|
+
assert_equal({32 => :A, 34 => :B}, @embedded_font.encoding.code_to_name)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "fails if the encoding needs to be read from the font but is is not embedded" do
|
|
80
|
+
@embedded_font.delete(:Encoding)
|
|
81
|
+
@embedded_font[:FontDescriptor].delete(:FontFile)
|
|
82
|
+
assert_raises(HexaPDF::Error) { @embedded_font.encoding }
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
describe "width" do
|
|
87
|
+
it "returns the glyph width when using a standard font" do
|
|
88
|
+
assert_equal(250, @font.width(32))
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it "defers to its superclass for all other cases" do
|
|
92
|
+
assert_equal(600, @embedded_font.width(32))
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
describe "bounding_box" do
|
|
97
|
+
it "returns the bounding box for a standard font" do
|
|
98
|
+
font = HexaPDF::Type::FontType1::StandardFonts.font(:"Times-Roman")
|
|
99
|
+
assert_equal(font.bounding_box, @font.bounding_box)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it "defers to its superclass for all other cases" do
|
|
103
|
+
assert_equal([0, 1, 2, 3], @embedded_font.bounding_box)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
it "returns nil for non-standard fonts without bounding box information" do
|
|
107
|
+
@embedded_font[:FontDescriptor].delete(:FontBBox)
|
|
108
|
+
assert_nil(@embedded_font.bounding_box)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
describe "symbolic?" do
|
|
113
|
+
it "return true for the standard fonts Symbol and ZapfDingbats" do
|
|
114
|
+
@font[:BaseFont] = :Symbol
|
|
115
|
+
assert(@font.symbolic?)
|
|
116
|
+
|
|
117
|
+
@font[:BaseFont] = :ZapfDingbats
|
|
118
|
+
assert(@font.symbolic?)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it "defers to its superclass for all other cases" do
|
|
122
|
+
assert(@embedded_font.symbolic?)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it "returns nil if it cannot be determined whether the font is symbolic" do
|
|
126
|
+
@embedded_font.delete(:FontDescriptor)
|
|
127
|
+
assert_nil(@embedded_font.symbolic?)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
describe "validation" do
|
|
132
|
+
it "allows empty fields for standard fonts" do
|
|
133
|
+
assert(@font.validate)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
it "requires that the FontDescriptor key is set for non-standard fonts" do
|
|
137
|
+
assert(@embedded_font.validate)
|
|
138
|
+
@embedded_font.delete(:FontDescriptor)
|
|
139
|
+
refute(@embedded_font.validate)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
require 'test_helper'
|
|
4
|
+
require 'hexapdf/document'
|
|
5
|
+
require 'hexapdf/type/font_type3'
|
|
6
|
+
|
|
7
|
+
describe HexaPDF::Type::FontType3 do
|
|
8
|
+
before do
|
|
9
|
+
@doc = HexaPDF::Document.new
|
|
10
|
+
@font = @doc.add({Type: :Font, Subtype: :Type3, Encoding: :WinAnsiEncoding,
|
|
11
|
+
FirstChar: 32, LastChar: 34, Widths: [600, 0, 700],
|
|
12
|
+
FontBBox: [0, 0, 100, 100], FontMatrix: [1, 0, 0, 1, 0, 0],
|
|
13
|
+
CharProcs: {}})
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
describe "validation" do
|
|
17
|
+
it "works for valid objects" do
|
|
18
|
+
assert(@font.validate)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "fails if the Encoding key is missing" do
|
|
22
|
+
@font.delete(:Encoding)
|
|
23
|
+
refute(@font.validate)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
require 'test_helper'
|
|
4
|
+
require_relative '../content/common'
|
|
5
|
+
require 'hexapdf/document'
|
|
6
|
+
require 'hexapdf/type/form'
|
|
7
|
+
|
|
8
|
+
describe HexaPDF::Type::Form do
|
|
9
|
+
before do
|
|
10
|
+
@doc = HexaPDF::Document.new
|
|
11
|
+
@form = @doc.wrap({}, type: :XObject, subtype: :Form)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
describe "box" do
|
|
15
|
+
before do
|
|
16
|
+
@form[:BBox] = [10, 10, 110, 60]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "returns the /BBox entry" do
|
|
20
|
+
assert_equal([10, 10, 110, 60], @form.box.value)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "returns the box's width" do
|
|
24
|
+
assert_equal(100, @form.width)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "returns the box's height" do
|
|
28
|
+
assert_equal(50, @form.height)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
describe "contents" do
|
|
33
|
+
it "returns a duplicate of the stream" do
|
|
34
|
+
@form.stream = 'test'
|
|
35
|
+
assert_equal(@form.stream, @form.contents)
|
|
36
|
+
@form.contents.gsub!(/test/, 'other')
|
|
37
|
+
assert_equal(@form.stream, @form.contents)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
describe "contents=" do
|
|
42
|
+
it "set the stream contents" do
|
|
43
|
+
@form.contents = 'test'
|
|
44
|
+
assert_equal('test', @form.stream)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "clears the cache to make sure that a new canvas can be created" do
|
|
48
|
+
@form[:BBox] = [0, 0, 100, 100]
|
|
49
|
+
canvas = @form.canvas
|
|
50
|
+
@form.contents = ''
|
|
51
|
+
refute_same(canvas, @form.canvas)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
describe "resources" do
|
|
56
|
+
it "creates the resource dictionary if it is not found" do
|
|
57
|
+
resources = @form.resources
|
|
58
|
+
assert_equal(:XXResources, resources.type)
|
|
59
|
+
assert_equal({ProcSet: [:PDF, :Text, :ImageB, :ImageC, :ImageI]}, resources.value)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it "returns the already used resource dictionary" do
|
|
63
|
+
@form[:Resources] = {Font: nil}
|
|
64
|
+
resources = @form.resources
|
|
65
|
+
assert_equal(:XXResources, resources.type)
|
|
66
|
+
assert_equal(@form[:Resources], resources)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
describe "process_contents" do
|
|
71
|
+
it "parses the contents and processes it" do
|
|
72
|
+
@form.stream = '10 w'
|
|
73
|
+
processor = TestHelper::OperatorRecorder.new
|
|
74
|
+
@form.process_contents(processor)
|
|
75
|
+
assert_equal([[:set_line_width, [10]]], processor.recorded_ops)
|
|
76
|
+
assert_nil(@form[:Resources])
|
|
77
|
+
|
|
78
|
+
resources = @form.resources
|
|
79
|
+
@form.process_contents(processor)
|
|
80
|
+
assert_same(resources, processor.resources)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "uses the provided resources if it has no resources itself" do
|
|
84
|
+
resources = @doc.wrap({}, type: :XXResources)
|
|
85
|
+
processor = TestHelper::OperatorRecorder.new
|
|
86
|
+
@form.process_contents(processor, original_resources: resources)
|
|
87
|
+
assert_same(resources, processor.resources)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
describe "canvas" do
|
|
92
|
+
# Asserts that the form's contents contains the operators.
|
|
93
|
+
def assert_operators(form, operators)
|
|
94
|
+
processor = TestHelper::OperatorRecorder.new
|
|
95
|
+
form.process_contents(processor)
|
|
96
|
+
assert_equal(operators, processor.recorded_ops)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it "always returns the same Canvas instance" do
|
|
100
|
+
@form[:BBox] = [0, 0, 100, 100]
|
|
101
|
+
canvas = @form.canvas
|
|
102
|
+
assert_same(canvas, @form.canvas)
|
|
103
|
+
assert_operators(@form, [])
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
it "always moves the origin to the bottom left corner of the bounding box" do
|
|
107
|
+
@form[:BBox] = [-10, -5, 100, 300]
|
|
108
|
+
@form.canvas.line_width = 5
|
|
109
|
+
assert_operators(@form, [[:save_graphics_state],
|
|
110
|
+
[:concatenate_matrix, [1, 0, 0, 1, -10, -5]],
|
|
111
|
+
[:set_line_width, [5]],
|
|
112
|
+
[:restore_graphics_state]])
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it "fails if the form XObject already has data" do
|
|
116
|
+
@form.stream = '10 w'
|
|
117
|
+
assert_raises(HexaPDF::Error) { @form.canvas }
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
require 'test_helper'
|
|
4
|
+
require 'stringio'
|
|
5
|
+
require 'tempfile'
|
|
6
|
+
require 'hexapdf/document'
|
|
7
|
+
|
|
8
|
+
describe HexaPDF::Type::Image do
|
|
9
|
+
before do
|
|
10
|
+
@jpg = File.join(TEST_DATA_DIR, 'images', 'rgb.jpg')
|
|
11
|
+
@doc = HexaPDF::Document.new
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it "returns the width of the image" do
|
|
15
|
+
@image = @doc.wrap({Type: :XObject, Subtype: :Image, Width: 10})
|
|
16
|
+
assert_equal(10, @image.width)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "returns the height of the image" do
|
|
20
|
+
@image = @doc.wrap({Type: :XObject, Subtype: :Image, Height: 10})
|
|
21
|
+
assert_equal(10, @image.height)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
describe "info" do
|
|
25
|
+
before do
|
|
26
|
+
@image = @doc.wrap({Type: :XObject, Subtype: :Image, Width: 10, Height: 5,
|
|
27
|
+
ColorSpace: :DeviceRGB, BitsPerComponent: 4})
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it "uses the Width, Height and BitsPerComponent values" do
|
|
31
|
+
assert_equal(10, @image.info.width)
|
|
32
|
+
assert_equal(5, @image.info.height)
|
|
33
|
+
assert_equal(4, @image.info.bits_per_component)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "determines the type and extension based on the stream filter" do
|
|
37
|
+
@image.set_filter(:DCTDecode)
|
|
38
|
+
info = @image.info
|
|
39
|
+
assert_equal(:jpeg, info.type)
|
|
40
|
+
assert_equal('jpg', info.extension)
|
|
41
|
+
assert(info.writable)
|
|
42
|
+
|
|
43
|
+
@image.set_filter(:JPXDecode)
|
|
44
|
+
info = @image.info
|
|
45
|
+
assert_equal(:jp2, info.type)
|
|
46
|
+
assert_equal('jpx', info.extension)
|
|
47
|
+
assert(info.writable)
|
|
48
|
+
|
|
49
|
+
@image.set_filter(:JBIG2Decode)
|
|
50
|
+
info = @image.info
|
|
51
|
+
assert_equal(:jbig2, info.type)
|
|
52
|
+
refute(info.writable)
|
|
53
|
+
|
|
54
|
+
@image.set_filter(:CCITTFaxDecode)
|
|
55
|
+
info = @image.info
|
|
56
|
+
assert_equal(:ccitt, info.type)
|
|
57
|
+
refute(info.writable)
|
|
58
|
+
|
|
59
|
+
@image.set_filter(nil)
|
|
60
|
+
info = @image.info
|
|
61
|
+
assert_equal(:png, @image.info.type)
|
|
62
|
+
assert_equal('png', info.extension)
|
|
63
|
+
assert(info.writable)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it "determines the color space, indexed and components values using the ColorSpace value" do
|
|
67
|
+
@image[:ColorSpace] = :DeviceGray
|
|
68
|
+
info = @image.info
|
|
69
|
+
assert_equal(:gray, info.color_space)
|
|
70
|
+
assert_equal(1, info.components)
|
|
71
|
+
refute(info.indexed)
|
|
72
|
+
assert(info.writable)
|
|
73
|
+
|
|
74
|
+
@image[:ColorSpace] = [:CalGray, {WhitePoint: [1, 1, 1]}]
|
|
75
|
+
info = @image.info
|
|
76
|
+
assert_equal(:gray, info.color_space)
|
|
77
|
+
assert_equal(1, info.components)
|
|
78
|
+
refute(info.indexed)
|
|
79
|
+
assert(info.writable)
|
|
80
|
+
|
|
81
|
+
@image[:ColorSpace] = :DeviceRGB
|
|
82
|
+
info = @image.info
|
|
83
|
+
assert_equal(:rgb, info.color_space)
|
|
84
|
+
assert_equal(3, info.components)
|
|
85
|
+
refute(info.indexed)
|
|
86
|
+
assert(info.writable)
|
|
87
|
+
|
|
88
|
+
@image[:ColorSpace] = [:CalRGB, {WhitePoint: [1, 1, 1]}]
|
|
89
|
+
info = @image.info
|
|
90
|
+
assert_equal(:rgb, info.color_space)
|
|
91
|
+
assert_equal(3, info.components)
|
|
92
|
+
refute(info.indexed)
|
|
93
|
+
assert(info.writable)
|
|
94
|
+
|
|
95
|
+
@image[:ColorSpace] = :DeviceCMYK
|
|
96
|
+
@image[:Filter] = :DCTDecode
|
|
97
|
+
info = @image.info
|
|
98
|
+
assert_equal(:cmyk, info.color_space)
|
|
99
|
+
assert_equal(4, info.components)
|
|
100
|
+
refute(info.indexed)
|
|
101
|
+
assert(info.writable)
|
|
102
|
+
|
|
103
|
+
@image[:ColorSpace] = :DeviceCMYK
|
|
104
|
+
@image[:Filter] = :FlateDecode
|
|
105
|
+
info = @image.info
|
|
106
|
+
assert_equal(:cmyk, info.color_space)
|
|
107
|
+
assert_equal(4, info.components)
|
|
108
|
+
refute(info.indexed)
|
|
109
|
+
refute(info.writable)
|
|
110
|
+
|
|
111
|
+
@image[:ColorSpace] = [:Indexed, :DeviceRGB, 1, "\x80".b * 6]
|
|
112
|
+
info = @image.info
|
|
113
|
+
assert_equal(:rgb, info.color_space)
|
|
114
|
+
assert_equal(3, info.components)
|
|
115
|
+
assert(info.indexed)
|
|
116
|
+
assert(info.writable)
|
|
117
|
+
|
|
118
|
+
@image[:ColorSpace] = :ICCBased
|
|
119
|
+
info = @image.info
|
|
120
|
+
assert_equal(:other, info.color_space)
|
|
121
|
+
assert_equal(-1, info.components)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
it "processes the SMask entry" do
|
|
125
|
+
@image[:SMask] = :something
|
|
126
|
+
info = @image.info
|
|
127
|
+
refute(info.writable)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
describe "write" do
|
|
132
|
+
before do
|
|
133
|
+
@file = Tempfile.new(['hexapdf-image-write-test', '.png'])
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
after do
|
|
137
|
+
@file.unlink
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
`which pngcheck 2>&1`
|
|
141
|
+
PNG_CHECK_AVAILABLE = $?.exitstatus == 0
|
|
142
|
+
|
|
143
|
+
`which pngtopnm 2>&1`
|
|
144
|
+
PNG_COMPARE_AVAILABLE = $?.exitstatus == 0
|
|
145
|
+
|
|
146
|
+
def assert_valid_png(filename, original = nil)
|
|
147
|
+
if PNG_CHECK_AVAILABLE
|
|
148
|
+
result = `pngcheck -q #{filename}`
|
|
149
|
+
assert(result.empty?, "pngcheck error: #{result}")
|
|
150
|
+
else
|
|
151
|
+
skip("Skipping PNG output validity check because pngcheck executable is missing")
|
|
152
|
+
end
|
|
153
|
+
if PNG_COMPARE_AVAILABLE
|
|
154
|
+
assert_equal(`pngtopnm #{original}`, `pngtopnm #{filename}`) if original
|
|
155
|
+
else
|
|
156
|
+
skip("Skipping PNG output comparison check because pngtopnm executable is missing")
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
it "can write to an IO" do
|
|
161
|
+
io = StringIO.new(''.b)
|
|
162
|
+
image = @doc.images.add(@jpg)
|
|
163
|
+
image.write(io)
|
|
164
|
+
assert_equal(File.binread(@jpg), io.string)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
it "writes JPEG images to a file with .jpg extension" do
|
|
168
|
+
begin
|
|
169
|
+
file = Tempfile.new(['hexapdf-image-write-test', '.jpg'])
|
|
170
|
+
image = @doc.images.add(@jpg)
|
|
171
|
+
image.write(file.path)
|
|
172
|
+
assert_equal(File.binread(@jpg), File.binread(file.path))
|
|
173
|
+
ensure
|
|
174
|
+
file.unlink
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
it "writes JPEG2000 images to a file with .jpx extension" do
|
|
179
|
+
begin
|
|
180
|
+
file = Tempfile.new(['hexapdf-image-write-test', '.jpx'])
|
|
181
|
+
image = @doc.images.add(@jpg)
|
|
182
|
+
image.set_filter(:JPXDecode) # fake it
|
|
183
|
+
image.write(file.path)
|
|
184
|
+
assert_equal(File.binread(@jpg), File.binread(file.path))
|
|
185
|
+
ensure
|
|
186
|
+
file.unlink
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
Dir.glob(File.join(TEST_DATA_DIR, 'images', '*.png')).each do |png_file|
|
|
191
|
+
next if png_file =~ /alpha/
|
|
192
|
+
it "writes #{File.basename(png_file)} correctly as PNG file" do
|
|
193
|
+
image = @doc.images.add(png_file)
|
|
194
|
+
if png_file =~ /greyscale-1bit.png/ # force use of arrays for one image
|
|
195
|
+
image[:DecodeParms] = [image[:DecodeParms]]
|
|
196
|
+
image[:Filter] = [image[:Filter]]
|
|
197
|
+
end
|
|
198
|
+
image.write(@file.path)
|
|
199
|
+
assert_valid_png(@file.path, png_file)
|
|
200
|
+
|
|
201
|
+
image.delete(:DecodeParms) # force re-encoding of stream
|
|
202
|
+
image.write(@file.path)
|
|
203
|
+
assert_valid_png(@file.path, png_file)
|
|
204
|
+
|
|
205
|
+
new_image = @doc.images.add(@file.path)
|
|
206
|
+
|
|
207
|
+
assert_equal(image[:Width], new_image[:Width], "file: #{png_file}")
|
|
208
|
+
assert_equal(image[:Height], new_image[:Height], "file: #{png_file}")
|
|
209
|
+
assert_equal(image[:BitsPerComponent], new_image[:BitsPerComponent], "file: #{png_file}")
|
|
210
|
+
if image[:Mask]
|
|
211
|
+
assert_equal(image[:Mask].value, new_image[:Mask].value, "file: #{png_file}")
|
|
212
|
+
else
|
|
213
|
+
assert_nil(new_image[:Mask], "file: #{png_file}")
|
|
214
|
+
end
|
|
215
|
+
assert_equal(image.stream, new_image.stream, "file: #{png_file}")
|
|
216
|
+
|
|
217
|
+
# ColorSpace is currently not always preserved, e.g. with CalRGB
|
|
218
|
+
if Array(image[:ColorSpace]).first == :Indexed
|
|
219
|
+
assert_equal(image[:ColorSpace][2], new_image[:ColorSpace][2], "file: #{png_file}")
|
|
220
|
+
|
|
221
|
+
img_palette = image[:ColorSpace][3]
|
|
222
|
+
img_palette = img_palette.stream if img_palette.respond_to?(:stream)
|
|
223
|
+
new_img_palette = new_image[:ColorSpace][3]
|
|
224
|
+
new_img_palette = new_img_palette.stream if new_img_palette.respond_to?(:stream)
|
|
225
|
+
assert_equal(img_palette, new_img_palette, "file: #{png_file}")
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
it "works for greyscale indexed images" do
|
|
231
|
+
image = @doc.add({Type: :XObject, Subtype: :Image, Width: 2, Height: 2, BitsPerComponent: 2,
|
|
232
|
+
ColorSpace: [:Indexed, :DeviceGray, 3, "\x00\x40\x80\xFF".b]})
|
|
233
|
+
image.stream = HexaPDF::StreamData.new(filter: :ASCIIHexDecode) { "10 B0".b }
|
|
234
|
+
image.write(@file.path)
|
|
235
|
+
assert_valid_png(@file.path)
|
|
236
|
+
|
|
237
|
+
new_image = @doc.images.add(@file.path)
|
|
238
|
+
assert_equal([:Indexed, :DeviceRGB, 3, "\x00\x00\x00\x40\x40\x40\x80\x80\x80\xFF\xFF\xFF".b],
|
|
239
|
+
new_image[:ColorSpace].value)
|
|
240
|
+
assert_equal(image.stream, new_image.stream)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
it "fails if an unsupported stream filter is used" do
|
|
244
|
+
image = @doc.images.add(@jpg)
|
|
245
|
+
image.set_filter([:DCTDecode, :ASCIIHexDecode])
|
|
246
|
+
assert_raises(HexaPDF::Error) { image.write(@file) }
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
it "fails if an unsupported colorspace is used" do
|
|
250
|
+
image = @doc.add({Type: :XObject, Subtype: :Image, Width: 1, Height: 1, BitsPerComponent: 8,
|
|
251
|
+
ColorSpace: :ICCBased})
|
|
252
|
+
assert_raises(HexaPDF::Error) { image.write(@file) }
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
it "fails if an indexed image with an unsupported colorspace is used" do
|
|
256
|
+
image = @doc.add({Type: :XObject, Subtype: :Image, Width: 1, Height: 1, BitsPerComponent: 8,
|
|
257
|
+
ColorSpace: [:Indexed, :ICCBased, 0, "0"]})
|
|
258
|
+
assert_raises(HexaPDF::Error) { image.write(@file) }
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|