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.
Files changed (255) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1024 -0
  3. data/LICENSE +29 -0
  4. data/README.md +129 -0
  5. data/Rakefile +109 -0
  6. data/agpl-3.0.txt +661 -0
  7. data/examples/001-hello_world.rb +16 -0
  8. data/examples/002-graphics.rb +275 -0
  9. data/examples/003-arcs.rb +50 -0
  10. data/examples/004-optimizing.rb +23 -0
  11. data/examples/005-merging.rb +27 -0
  12. data/examples/006-standard_pdf_fonts.rb +73 -0
  13. data/examples/007-truetype.rb +42 -0
  14. data/examples/008-show_char_bboxes.rb +55 -0
  15. data/examples/009-text_layouter_alignment.rb +47 -0
  16. data/examples/010-text_layouter_inline_boxes.rb +64 -0
  17. data/examples/011-text_layouter_line_wrapping.rb +57 -0
  18. data/examples/012-text_layouter_styling.rb +122 -0
  19. data/examples/013-text_layouter_shapes.rb +176 -0
  20. data/examples/014-text_in_polygon.rb +60 -0
  21. data/examples/015-boxes.rb +76 -0
  22. data/examples/016-frame_automatic_box_placement.rb +90 -0
  23. data/examples/017-frame_text_flow.rb +60 -0
  24. data/examples/018-composer.rb +44 -0
  25. data/examples/019-acro_form.rb +88 -0
  26. data/examples/emoji-smile.png +0 -0
  27. data/examples/emoji-wink.png +0 -0
  28. data/examples/machupicchu.jpg +0 -0
  29. data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +66 -0
  30. data/lib/hexapdf/content/graphic_object/geom2d.rb +13 -0
  31. data/lib/hexapdf/version.rb +1 -1
  32. data/test/data/aes-test-vectors/CBCGFSbox-128-decrypt.data.gz +0 -0
  33. data/test/data/aes-test-vectors/CBCGFSbox-128-encrypt.data.gz +0 -0
  34. data/test/data/aes-test-vectors/CBCGFSbox-192-decrypt.data.gz +0 -0
  35. data/test/data/aes-test-vectors/CBCGFSbox-192-encrypt.data.gz +0 -0
  36. data/test/data/aes-test-vectors/CBCGFSbox-256-decrypt.data.gz +0 -0
  37. data/test/data/aes-test-vectors/CBCGFSbox-256-encrypt.data.gz +0 -0
  38. data/test/data/aes-test-vectors/CBCKeySbox-128-decrypt.data.gz +0 -0
  39. data/test/data/aes-test-vectors/CBCKeySbox-128-encrypt.data.gz +0 -0
  40. data/test/data/aes-test-vectors/CBCKeySbox-192-decrypt.data.gz +0 -0
  41. data/test/data/aes-test-vectors/CBCKeySbox-192-encrypt.data.gz +0 -0
  42. data/test/data/aes-test-vectors/CBCKeySbox-256-decrypt.data.gz +0 -0
  43. data/test/data/aes-test-vectors/CBCKeySbox-256-encrypt.data.gz +0 -0
  44. data/test/data/aes-test-vectors/CBCVarKey-128-decrypt.data.gz +0 -0
  45. data/test/data/aes-test-vectors/CBCVarKey-128-encrypt.data.gz +0 -0
  46. data/test/data/aes-test-vectors/CBCVarKey-192-decrypt.data.gz +0 -0
  47. data/test/data/aes-test-vectors/CBCVarKey-192-encrypt.data.gz +0 -0
  48. data/test/data/aes-test-vectors/CBCVarKey-256-decrypt.data.gz +0 -0
  49. data/test/data/aes-test-vectors/CBCVarKey-256-encrypt.data.gz +0 -0
  50. data/test/data/aes-test-vectors/CBCVarTxt-128-decrypt.data.gz +0 -0
  51. data/test/data/aes-test-vectors/CBCVarTxt-128-encrypt.data.gz +0 -0
  52. data/test/data/aes-test-vectors/CBCVarTxt-192-decrypt.data.gz +0 -0
  53. data/test/data/aes-test-vectors/CBCVarTxt-192-encrypt.data.gz +0 -0
  54. data/test/data/aes-test-vectors/CBCVarTxt-256-decrypt.data.gz +0 -0
  55. data/test/data/aes-test-vectors/CBCVarTxt-256-encrypt.data.gz +0 -0
  56. data/test/data/fonts/Ubuntu-Title.ttf +0 -0
  57. data/test/data/images/cmyk.jpg +0 -0
  58. data/test/data/images/fillbytes.jpg +0 -0
  59. data/test/data/images/gray.jpg +0 -0
  60. data/test/data/images/greyscale-1bit.png +0 -0
  61. data/test/data/images/greyscale-2bit.png +0 -0
  62. data/test/data/images/greyscale-4bit.png +0 -0
  63. data/test/data/images/greyscale-8bit.png +0 -0
  64. data/test/data/images/greyscale-alpha-8bit.png +0 -0
  65. data/test/data/images/greyscale-trns-8bit.png +0 -0
  66. data/test/data/images/greyscale-with-gamma1.0.png +0 -0
  67. data/test/data/images/greyscale-with-gamma1.5.png +0 -0
  68. data/test/data/images/indexed-1bit.png +0 -0
  69. data/test/data/images/indexed-2bit.png +0 -0
  70. data/test/data/images/indexed-4bit.png +0 -0
  71. data/test/data/images/indexed-8bit.png +0 -0
  72. data/test/data/images/indexed-alpha-4bit.png +0 -0
  73. data/test/data/images/indexed-alpha-8bit.png +0 -0
  74. data/test/data/images/rgb.jpg +0 -0
  75. data/test/data/images/truecolour-8bit.png +0 -0
  76. data/test/data/images/truecolour-alpha-8bit.png +0 -0
  77. data/test/data/images/truecolour-gama-chrm-8bit.png +0 -0
  78. data/test/data/images/truecolour-srgb-8bit.png +0 -0
  79. data/test/data/images/ycck.jpg +0 -0
  80. data/test/data/minimal.pdf +44 -0
  81. data/test/data/standard-security-handler/README +9 -0
  82. data/test/data/standard-security-handler/bothpwd-aes-128bit-V4.pdf +44 -0
  83. data/test/data/standard-security-handler/bothpwd-aes-256bit-V5.pdf +0 -0
  84. data/test/data/standard-security-handler/bothpwd-arc4-128bit-V2.pdf +43 -0
  85. data/test/data/standard-security-handler/bothpwd-arc4-128bit-V4.pdf +43 -0
  86. data/test/data/standard-security-handler/bothpwd-arc4-40bit-V1.pdf +0 -0
  87. data/test/data/standard-security-handler/nopwd-aes-128bit-V4.pdf +43 -0
  88. data/test/data/standard-security-handler/nopwd-aes-256bit-V5.pdf +0 -0
  89. data/test/data/standard-security-handler/nopwd-arc4-128bit-V2.pdf +43 -0
  90. data/test/data/standard-security-handler/nopwd-arc4-128bit-V4.pdf +43 -0
  91. data/test/data/standard-security-handler/nopwd-arc4-40bit-V1.pdf +43 -0
  92. data/test/data/standard-security-handler/ownerpwd-aes-128bit-V4.pdf +0 -0
  93. data/test/data/standard-security-handler/ownerpwd-aes-256bit-V5.pdf +43 -0
  94. data/test/data/standard-security-handler/ownerpwd-arc4-128bit-V2.pdf +43 -0
  95. data/test/data/standard-security-handler/ownerpwd-arc4-128bit-V4.pdf +43 -0
  96. data/test/data/standard-security-handler/ownerpwd-arc4-40bit-V1.pdf +43 -0
  97. data/test/data/standard-security-handler/userpwd-aes-128bit-V4.pdf +43 -0
  98. data/test/data/standard-security-handler/userpwd-aes-256bit-V5.pdf +43 -0
  99. data/test/data/standard-security-handler/userpwd-arc4-128bit-V2.pdf +0 -0
  100. data/test/data/standard-security-handler/userpwd-arc4-128bit-V4.pdf +0 -0
  101. data/test/data/standard-security-handler/userpwd-arc4-40bit-V1.pdf +43 -0
  102. data/test/hexapdf/common_tokenizer_tests.rb +236 -0
  103. data/test/hexapdf/content/common.rb +39 -0
  104. data/test/hexapdf/content/graphic_object/test_arc.rb +102 -0
  105. data/test/hexapdf/content/graphic_object/test_endpoint_arc.rb +90 -0
  106. data/test/hexapdf/content/graphic_object/test_geom2d.rb +79 -0
  107. data/test/hexapdf/content/graphic_object/test_solid_arc.rb +86 -0
  108. data/test/hexapdf/content/test_canvas.rb +1279 -0
  109. data/test/hexapdf/content/test_color_space.rb +176 -0
  110. data/test/hexapdf/content/test_graphics_state.rb +151 -0
  111. data/test/hexapdf/content/test_operator.rb +619 -0
  112. data/test/hexapdf/content/test_parser.rb +99 -0
  113. data/test/hexapdf/content/test_processor.rb +163 -0
  114. data/test/hexapdf/content/test_transformation_matrix.rb +64 -0
  115. data/test/hexapdf/document/test_files.rb +72 -0
  116. data/test/hexapdf/document/test_fonts.rb +60 -0
  117. data/test/hexapdf/document/test_images.rb +72 -0
  118. data/test/hexapdf/document/test_pages.rb +130 -0
  119. data/test/hexapdf/encryption/common.rb +87 -0
  120. data/test/hexapdf/encryption/test_aes.rb +129 -0
  121. data/test/hexapdf/encryption/test_arc4.rb +39 -0
  122. data/test/hexapdf/encryption/test_fast_aes.rb +17 -0
  123. data/test/hexapdf/encryption/test_fast_arc4.rb +12 -0
  124. data/test/hexapdf/encryption/test_identity.rb +21 -0
  125. data/test/hexapdf/encryption/test_ruby_aes.rb +23 -0
  126. data/test/hexapdf/encryption/test_ruby_arc4.rb +20 -0
  127. data/test/hexapdf/encryption/test_security_handler.rb +380 -0
  128. data/test/hexapdf/encryption/test_standard_security_handler.rb +322 -0
  129. data/test/hexapdf/filter/common.rb +53 -0
  130. data/test/hexapdf/filter/test_ascii85_decode.rb +59 -0
  131. data/test/hexapdf/filter/test_ascii_hex_decode.rb +38 -0
  132. data/test/hexapdf/filter/test_crypt.rb +21 -0
  133. data/test/hexapdf/filter/test_encryption.rb +24 -0
  134. data/test/hexapdf/filter/test_flate_decode.rb +44 -0
  135. data/test/hexapdf/filter/test_lzw_decode.rb +52 -0
  136. data/test/hexapdf/filter/test_predictor.rb +219 -0
  137. data/test/hexapdf/filter/test_run_length_decode.rb +32 -0
  138. data/test/hexapdf/font/cmap/test_parser.rb +102 -0
  139. data/test/hexapdf/font/cmap/test_writer.rb +66 -0
  140. data/test/hexapdf/font/encoding/test_base.rb +45 -0
  141. data/test/hexapdf/font/encoding/test_difference_encoding.rb +29 -0
  142. data/test/hexapdf/font/encoding/test_glyph_list.rb +59 -0
  143. data/test/hexapdf/font/encoding/test_zapf_dingbats_encoding.rb +16 -0
  144. data/test/hexapdf/font/test_cmap.rb +104 -0
  145. data/test/hexapdf/font/test_encoding.rb +27 -0
  146. data/test/hexapdf/font/test_invalid_glyph.rb +34 -0
  147. data/test/hexapdf/font/test_true_type_wrapper.rb +186 -0
  148. data/test/hexapdf/font/test_type1_wrapper.rb +107 -0
  149. data/test/hexapdf/font/true_type/common.rb +17 -0
  150. data/test/hexapdf/font/true_type/table/common.rb +27 -0
  151. data/test/hexapdf/font/true_type/table/test_cmap.rb +47 -0
  152. data/test/hexapdf/font/true_type/table/test_cmap_subtable.rb +141 -0
  153. data/test/hexapdf/font/true_type/table/test_directory.rb +30 -0
  154. data/test/hexapdf/font/true_type/table/test_glyf.rb +58 -0
  155. data/test/hexapdf/font/true_type/table/test_head.rb +56 -0
  156. data/test/hexapdf/font/true_type/table/test_hhea.rb +26 -0
  157. data/test/hexapdf/font/true_type/table/test_hmtx.rb +30 -0
  158. data/test/hexapdf/font/true_type/table/test_kern.rb +61 -0
  159. data/test/hexapdf/font/true_type/table/test_loca.rb +33 -0
  160. data/test/hexapdf/font/true_type/table/test_maxp.rb +50 -0
  161. data/test/hexapdf/font/true_type/table/test_name.rb +76 -0
  162. data/test/hexapdf/font/true_type/table/test_os2.rb +55 -0
  163. data/test/hexapdf/font/true_type/table/test_post.rb +78 -0
  164. data/test/hexapdf/font/true_type/test_builder.rb +42 -0
  165. data/test/hexapdf/font/true_type/test_font.rb +116 -0
  166. data/test/hexapdf/font/true_type/test_optimizer.rb +26 -0
  167. data/test/hexapdf/font/true_type/test_subsetter.rb +73 -0
  168. data/test/hexapdf/font/true_type/test_table.rb +48 -0
  169. data/test/hexapdf/font/type1/common.rb +6 -0
  170. data/test/hexapdf/font/type1/test_afm_parser.rb +65 -0
  171. data/test/hexapdf/font/type1/test_font.rb +104 -0
  172. data/test/hexapdf/font/type1/test_font_metrics.rb +22 -0
  173. data/test/hexapdf/font/type1/test_pfb_parser.rb +37 -0
  174. data/test/hexapdf/font_loader/test_from_configuration.rb +43 -0
  175. data/test/hexapdf/font_loader/test_from_file.rb +36 -0
  176. data/test/hexapdf/font_loader/test_standard14.rb +33 -0
  177. data/test/hexapdf/image_loader/test_jpeg.rb +93 -0
  178. data/test/hexapdf/image_loader/test_pdf.rb +47 -0
  179. data/test/hexapdf/image_loader/test_png.rb +259 -0
  180. data/test/hexapdf/layout/test_box.rb +154 -0
  181. data/test/hexapdf/layout/test_frame.rb +350 -0
  182. data/test/hexapdf/layout/test_image_box.rb +73 -0
  183. data/test/hexapdf/layout/test_inline_box.rb +71 -0
  184. data/test/hexapdf/layout/test_line.rb +206 -0
  185. data/test/hexapdf/layout/test_style.rb +790 -0
  186. data/test/hexapdf/layout/test_text_box.rb +140 -0
  187. data/test/hexapdf/layout/test_text_fragment.rb +375 -0
  188. data/test/hexapdf/layout/test_text_layouter.rb +758 -0
  189. data/test/hexapdf/layout/test_text_shaper.rb +62 -0
  190. data/test/hexapdf/layout/test_width_from_polygon.rb +109 -0
  191. data/test/hexapdf/task/test_dereference.rb +51 -0
  192. data/test/hexapdf/task/test_optimize.rb +162 -0
  193. data/test/hexapdf/test_composer.rb +258 -0
  194. data/test/hexapdf/test_configuration.rb +93 -0
  195. data/test/hexapdf/test_data_dir.rb +32 -0
  196. data/test/hexapdf/test_dictionary.rb +340 -0
  197. data/test/hexapdf/test_dictionary_fields.rb +269 -0
  198. data/test/hexapdf/test_document.rb +641 -0
  199. data/test/hexapdf/test_filter.rb +100 -0
  200. data/test/hexapdf/test_importer.rb +106 -0
  201. data/test/hexapdf/test_object.rb +258 -0
  202. data/test/hexapdf/test_parser.rb +645 -0
  203. data/test/hexapdf/test_pdf_array.rb +169 -0
  204. data/test/hexapdf/test_rectangle.rb +73 -0
  205. data/test/hexapdf/test_reference.rb +50 -0
  206. data/test/hexapdf/test_revision.rb +188 -0
  207. data/test/hexapdf/test_revisions.rb +196 -0
  208. data/test/hexapdf/test_serializer.rb +195 -0
  209. data/test/hexapdf/test_stream.rb +274 -0
  210. data/test/hexapdf/test_tokenizer.rb +80 -0
  211. data/test/hexapdf/test_type.rb +18 -0
  212. data/test/hexapdf/test_writer.rb +140 -0
  213. data/test/hexapdf/test_xref_section.rb +61 -0
  214. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +795 -0
  215. data/test/hexapdf/type/acro_form/test_button_field.rb +308 -0
  216. data/test/hexapdf/type/acro_form/test_choice_field.rb +220 -0
  217. data/test/hexapdf/type/acro_form/test_field.rb +259 -0
  218. data/test/hexapdf/type/acro_form/test_form.rb +357 -0
  219. data/test/hexapdf/type/acro_form/test_signature_field.rb +38 -0
  220. data/test/hexapdf/type/acro_form/test_text_field.rb +201 -0
  221. data/test/hexapdf/type/acro_form/test_variable_text_field.rb +88 -0
  222. data/test/hexapdf/type/actions/test_launch.rb +24 -0
  223. data/test/hexapdf/type/actions/test_uri.rb +23 -0
  224. data/test/hexapdf/type/annotations/test_markup_annotation.rb +22 -0
  225. data/test/hexapdf/type/annotations/test_text.rb +34 -0
  226. data/test/hexapdf/type/annotations/test_widget.rb +225 -0
  227. data/test/hexapdf/type/test_annotation.rb +97 -0
  228. data/test/hexapdf/type/test_catalog.rb +48 -0
  229. data/test/hexapdf/type/test_cid_font.rb +61 -0
  230. data/test/hexapdf/type/test_file_specification.rb +141 -0
  231. data/test/hexapdf/type/test_font.rb +67 -0
  232. data/test/hexapdf/type/test_font_descriptor.rb +61 -0
  233. data/test/hexapdf/type/test_font_simple.rb +176 -0
  234. data/test/hexapdf/type/test_font_true_type.rb +31 -0
  235. data/test/hexapdf/type/test_font_type0.rb +120 -0
  236. data/test/hexapdf/type/test_font_type1.rb +142 -0
  237. data/test/hexapdf/type/test_font_type3.rb +26 -0
  238. data/test/hexapdf/type/test_form.rb +120 -0
  239. data/test/hexapdf/type/test_image.rb +261 -0
  240. data/test/hexapdf/type/test_info.rb +9 -0
  241. data/test/hexapdf/type/test_object_stream.rb +117 -0
  242. data/test/hexapdf/type/test_page.rb +598 -0
  243. data/test/hexapdf/type/test_page_tree_node.rb +315 -0
  244. data/test/hexapdf/type/test_resources.rb +209 -0
  245. data/test/hexapdf/type/test_trailer.rb +116 -0
  246. data/test/hexapdf/type/test_xref_stream.rb +143 -0
  247. data/test/hexapdf/utils/test_bit_field.rb +63 -0
  248. data/test/hexapdf/utils/test_bit_stream.rb +69 -0
  249. data/test/hexapdf/utils/test_graphics_helpers.rb +37 -0
  250. data/test/hexapdf/utils/test_lru_cache.rb +22 -0
  251. data/test/hexapdf/utils/test_object_hash.rb +120 -0
  252. data/test/hexapdf/utils/test_pdf_doc_encoding.rb +18 -0
  253. data/test/hexapdf/utils/test_sorted_tree_node.rb +239 -0
  254. data/test/test_helper.rb +58 -0
  255. 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
@@ -0,0 +1,9 @@
1
+ require 'test_helper'
2
+ require 'hexapdf/type/info'
3
+
4
+ describe HexaPDF::Type::Info do
5
+ it "must always be indirect" do
6
+ obj = HexaPDF::Type::Info.new({})
7
+ assert(obj.must_be_indirect?)
8
+ end
9
+ end