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,62 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require_relative '../font/true_type/table/common'
5
+ require 'hexapdf/document'
6
+ require 'hexapdf/font/true_type_wrapper'
7
+ require 'hexapdf/layout/text_shaper'
8
+
9
+ using HexaPDF::Layout::NumericRefinements
10
+
11
+ describe HexaPDF::Layout::TextShaper do
12
+ before do
13
+ @doc = HexaPDF::Document.new
14
+ @shaper = HexaPDF::Layout::TextShaper.new
15
+ end
16
+
17
+ def setup_fragment(items, **options)
18
+ style = HexaPDF::Layout::Style.new(font: @font, font_size: 20, font_features: options)
19
+ HexaPDF::Layout::TextFragment.new(items, style)
20
+ end
21
+
22
+ describe "Type1 font features" do
23
+ before do
24
+ @font = @doc.fonts.add("Times", custom_encoding: true)
25
+ end
26
+
27
+ it "handles ligatures" do
28
+ fragment = setup_fragment(@font.decode_utf8('fish fish fi').insert(1, 100).
29
+ insert(0, 100), liga: true)
30
+ @shaper.shape_text(fragment)
31
+ assert_equal([100, :fi, :s, :h, :space, :fi, :s, :h, :space, :fi],
32
+ fragment.items.map {|item| item.kind_of?(Numeric) ? item : item.id })
33
+ end
34
+
35
+ it "handles kerning" do
36
+ fragment = setup_fragment(@font.decode_utf8('fish fish wow').insert(1, 100), kern: true)
37
+ @shaper.shape_text(fragment)
38
+ assert_equal([:f, 100, :i, :s, :h, :space, :f, 20, :i, :s, :h, :space, :w, 10, :o, 25, :w],
39
+ fragment.items.map {|item| item.kind_of?(Numeric) ? item : item.id })
40
+ end
41
+ end
42
+
43
+ describe "TrueType font features" do
44
+ before do
45
+ font_file = File.join(TEST_DATA_DIR, "fonts", "Ubuntu-Title.ttf")
46
+ @wrapped_font = HexaPDF::Font::TrueType::Font.new(File.open(font_file))
47
+ @font = HexaPDF::Font::TrueTypeWrapper.new(@doc, @wrapped_font)
48
+ end
49
+
50
+ it "handles kerning" do
51
+ data = [0, 1].pack('n2') << \
52
+ [0, 6 + 8 + 12, 0x1].pack('n3') << \
53
+ [2, 0, 0, 0, 53, 80, -20, 80, 81, -10].pack('n4n2s>n2s>')
54
+ table = create_table(:Kern, data, standalone: true)
55
+ @wrapped_font.instance_eval { @tables[:kern] = table }
56
+ fragment = setup_fragment(@font.decode_utf8('Top Top').insert(1, 100), kern: true)
57
+ @shaper.shape_text(fragment)
58
+ assert_equal([53, [100], 80, [10], 81, 3, 53, [20], 80, [10], 81],
59
+ fragment.items.map {|item| item.kind_of?(Numeric) ? [item] : item.id })
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,109 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'geom2d'
5
+ require 'hexapdf/layout/width_from_polygon'
6
+
7
+ describe HexaPDF::Layout::WidthFromPolygon do
8
+ def create_width_spec(polygon, offset = 0)
9
+ HexaPDF::Layout::WidthFromPolygon.new(polygon, offset)
10
+ end
11
+
12
+ it "respects the offset" do
13
+ ws = create_width_spec(Geom2D::Polygon([0, 0], [0, 10], [10, 5]), 5)
14
+ assert_equal([0, 8], ws.call(0, 1))
15
+ end
16
+
17
+ it "works in the case bottom and top line are the same" do
18
+ ws = create_width_spec(Geom2D::Polygon([0, 0], [0, 10], [10, 5]))
19
+ assert_equal([0, 0], ws.call(0, 0))
20
+ assert_equal([0, 0], ws.call(5, 0))
21
+ end
22
+
23
+ it "works when the first segment has not the minimal x-value" do
24
+ ws = create_width_spec(Geom2D::Polygon([10, 10], [10, 0], [0, 0], [5, 10]))
25
+ assert_equal([5, 5], ws.call(0, 1))
26
+ assert_equal([2.5, 7.5], ws.call(5, 1))
27
+ end
28
+
29
+ it "works when the polygon is specified in counterclockwise order" do
30
+ ws = create_width_spec(Geom2D::Polygon([10, 10], [5, 10], [0, 0], [10, 0]))
31
+ assert_equal([5, 5], ws.call(0, 1))
32
+ assert_equal([2.5, 7.5], ws.call(5, 1))
33
+ end
34
+
35
+ it "works if some segments only cross the top line" do
36
+ ws = create_width_spec(Geom2D::Polygon([0, 0], [0, 10], [2, 11], [4, 9], [6, 11], [10, 10],
37
+ [10, 0]))
38
+ assert_equal([0, 3, 2, 5], ws.call(1, 2))
39
+ end
40
+
41
+ it "works if some segments only cross the bottom line" do
42
+ ws = create_width_spec(Geom2D::Polygon([0, 0], [0, 10], [2, 4], [4, 6], [6, 4], [10, 10],
43
+ [10, 0]))
44
+ assert_equal([0, 1, 7, 2], ws.call(3, 2))
45
+ end
46
+
47
+ it "works if some non-horizontal segments don't cross the top/bottom line at all" do
48
+ ws = create_width_spec(Geom2D::Polygon([0, 0], [0, 10], [2, 4], [4, 6.5], [6, 6], [10, 10],
49
+ [10, 0]))
50
+ assert_equal([0, 1, 6, 3], ws.call(3, 2))
51
+ end
52
+
53
+ it "works if there is no available space" do
54
+ ws = create_width_spec(Geom2D::Polygon([0, 0], [0, 10], [5, 9], [10, 10], [10, 0]))
55
+ assert_equal([0, 0], ws.call(0, 2))
56
+ end
57
+
58
+ it "works if the first processed segment doesn't cross both lines" do
59
+ ws = create_width_spec(Geom2D::Polygon([0, 5], [0, 0], [10, 0], [10, 10], [5, 10], [5, 5]))
60
+ assert_equal([5, 5], ws.call(4, 2))
61
+ end
62
+
63
+ describe "multiple polygons" do
64
+ it "rectangle in rectangle" do
65
+ ws = create_width_spec(Geom2D::PolygonSet(Geom2D::Polygon([0, 0], [0, 10], [10, 10], [10, 0]),
66
+ Geom2D::Polygon([2, 2], [2, 8], [8, 8], [8, 2])))
67
+ assert_equal([0, 2, 6, 2], ws.call(1, 8))
68
+ assert_equal([0, 10], ws.call(0, 2))
69
+ assert_equal([0, 2, 6, 2], ws.call(2, 1))
70
+ assert_equal([0, 2, 6, 2], ws.call(7, 2))
71
+ end
72
+
73
+ it "rectangle in rectangle with reverse direction" do
74
+ ws = create_width_spec(Geom2D::PolygonSet(Geom2D::Polygon([0, 0], [0, 10], [10, 10], [10, 0]),
75
+ Geom2D::Polygon([2, 8], [2, 2], [8, 2], [8, 8])))
76
+ assert_equal([0, 2, 6, 2], ws.call(7, 2))
77
+ assert_equal([0, 2, 6, 2], ws.call(1, 8))
78
+ assert_equal([0, 10], ws.call(0, 2))
79
+ assert_equal([0, 2, 6, 2], ws.call(2, 1))
80
+ end
81
+
82
+ it "first segment of inner polygon is between the lines, polygon crosses both lines" do
83
+ ws = create_width_spec(Geom2D::PolygonSet(Geom2D::Polygon([0, 0], [0, 10], [10, 10], [10, 0]),
84
+ Geom2D::Polygon([2, 4], [2, 6], [8, 8], [8, 2])))
85
+ assert_equal([0, 10], ws.call(0, 2))
86
+ assert_equal([0, 5, 3, 2], ws.call(2, 1).map {|f| f.round(5) })
87
+ assert_equal([0, 2, 6, 2], ws.call(3, 4))
88
+ end
89
+
90
+ it "first segment of inner polygon is between the lines, polygon crosses one line" do
91
+ ws = create_width_spec(Geom2D::PolygonSet(Geom2D::Polygon([0, 0], [0, 10], [10, 10], [10, 0]),
92
+ Geom2D::Polygon([2, 4], [4, 6], [8, 2])))
93
+ assert_equal([0, 2, 5, 3], ws.call(3, 4))
94
+ end
95
+
96
+ it "polygon is partly between the lines, maximum between the lines" do
97
+ ws = create_width_spec(Geom2D::PolygonSet(Geom2D::Polygon([0, 0], [0, 10], [10, 10], [10, 0]),
98
+ Geom2D::Polygon([2, 4], [2, 6], [8, 8], [9, 5],
99
+ [8, 2])))
100
+ assert_equal([0, 2, 7, 1], ws.call(3, 4))
101
+ end
102
+
103
+ it "polygon is partly between the lines, maximum is at an line crossing" do
104
+ ws = create_width_spec(Geom2D::PolygonSet(Geom2D::Polygon([0, 0], [0, 10], [10, 10], [10, 0]),
105
+ Geom2D::Polygon([2, 4], [8, 8], [5, 5], [8, 2])))
106
+ assert_equal([0, 2, 5, 3], ws.call(3, 4))
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,51 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/document'
5
+ require 'hexapdf/task/dereference'
6
+
7
+ describe HexaPDF::Task::Dereference do
8
+ before do
9
+ @doc = HexaPDF::Document.new(io: StringIO.new(MINIMAL_PDF))
10
+ end
11
+
12
+ it "dereferences all references to objects" do
13
+ obj = @doc.add(:test)
14
+ len = @doc.add(5)
15
+ str = @doc.add(@doc.wrap({Length: len}, stream: ''))
16
+ @doc.trailer[:Test] = str
17
+ pages = @doc.wrap({Type: :Pages})
18
+ pages.add_page(@doc.wrap({Type: :Page}))
19
+ @doc.trailer[:Test2] = pages
20
+ @doc.trailer[:InvalidRef] = HexaPDF::Reference.new(5000, 2)
21
+
22
+ checker = lambda do |val, done = {}|
23
+ case val
24
+ when Array then val.all? {|v| checker.call(v, done) }
25
+ when Hash then val.all? {|_, v| checker.call(v, done) }
26
+ when HexaPDF::Reference
27
+ false
28
+ when HexaPDF::Object
29
+ if done.key?(val)
30
+ true
31
+ else
32
+ done[val] = true
33
+ checker.call(val.value, done)
34
+ end
35
+ else
36
+ true
37
+ end
38
+ end
39
+ refute(checker.call(@doc.trailer))
40
+ assert_equal([obj, len], @doc.task(:dereference))
41
+ assert(checker.call(@doc.trailer))
42
+ assert_equal([obj, len], @doc.task(:dereference))
43
+ assert(checker.call(@doc.trailer))
44
+ end
45
+
46
+ it "dereferences only a single object" do
47
+ assert(@doc.object(5).value[:Font][:F1].kind_of?(HexaPDF::Reference))
48
+ assert_nil(@doc.task(:dereference, object: @doc.object(5)))
49
+ refute(@doc.object(5).value[:Font][:F1].kind_of?(HexaPDF::Reference))
50
+ end
51
+ end
@@ -0,0 +1,162 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/document'
5
+ require 'hexapdf/task/optimize'
6
+
7
+ describe HexaPDF::Task::Optimize do
8
+ class TestType < HexaPDF::Dictionary
9
+
10
+ define_type :Test
11
+ define_field :Optional, type: Symbol, default: :Optional
12
+
13
+ end
14
+
15
+ before do
16
+ HexaPDF::GlobalConfiguration['object.type_map'][:Test] = TestType
17
+ @doc = HexaPDF::Document.new
18
+ @obj1 = @doc.add({Type: :Test, Optional: :Optional})
19
+ @doc.trailer[:Test] = @doc.wrap(@obj1)
20
+ @doc.revisions.add
21
+ @obj2 = @doc.add({Type: :UsedEntry})
22
+ @obj3 = @doc.add({Unused: @obj2})
23
+ @obj4 = @doc.add({Test: :Test})
24
+ @obj1[:Test] = @doc.wrap(@obj4, type: TestType)
25
+ end
26
+
27
+ after do
28
+ HexaPDF::GlobalConfiguration['object.type_map'].delete(:Test)
29
+ end
30
+
31
+ def assert_objstms_generated
32
+ assert(@doc.revisions.all? {|rev| rev.any? {|obj| obj.type == :ObjStm } })
33
+ assert(@doc.revisions.all? {|rev| rev.any? {|obj| obj.type == :XRef } })
34
+ end
35
+
36
+ def assert_xrefstms_generated
37
+ assert(@doc.revisions.all? {|rev| rev.find_all {|obj| obj.type == :XRef }.size == 1 })
38
+ end
39
+
40
+ def assert_no_objstms
41
+ assert(@doc.each(only_current: false).all? {|obj| obj.type != :ObjStm })
42
+ end
43
+
44
+ def assert_no_xrefstms
45
+ assert(@doc.each(only_current: false).all? {|obj| obj.type != :XRef })
46
+ end
47
+
48
+ def assert_default_deleted
49
+ refute(@doc.object(1).key?(:Optional))
50
+ end
51
+
52
+ describe "compact" do
53
+ it "compacts the document" do
54
+ @doc.task(:optimize, compact: true)
55
+ assert_equal(1, @doc.revisions.size)
56
+ assert_equal(2, @doc.each(only_current: false).to_a.size)
57
+ refute_equal(@obj2, @doc.object(@obj2))
58
+ refute_equal(@obj3, @doc.object(@obj3))
59
+ assert_default_deleted
60
+ assert_equal(2, @obj4.oid)
61
+ assert_equal(@obj1[:Test], @obj4)
62
+ end
63
+
64
+ it "compacts and generates object streams" do
65
+ @doc.task(:optimize, compact: true, object_streams: :generate)
66
+ assert_objstms_generated
67
+ assert_default_deleted
68
+ end
69
+
70
+ it "compacts and deletes object streams" do
71
+ @doc.add({Type: :ObjStm})
72
+ @doc.task(:optimize, compact: true, object_streams: :delete)
73
+ assert_no_objstms
74
+ assert_default_deleted
75
+ end
76
+
77
+ it "compacts and generates xref streams" do
78
+ @doc.task(:optimize, compact: true, xref_streams: :generate)
79
+ assert_xrefstms_generated
80
+ assert_default_deleted
81
+ end
82
+
83
+ it "compacts and deletes xref streams" do
84
+ @doc.add({Type: :XRef}, revision: 0)
85
+ @doc.add({Type: :XRef}, revision: 1)
86
+ @doc.task(:optimize, compact: true, xref_streams: :delete)
87
+ assert_no_xrefstms
88
+ assert_default_deleted
89
+ end
90
+ end
91
+
92
+ describe "object_streams" do
93
+ def reload_document_with_objstm_from_io
94
+ io = StringIO.new
95
+ objstm = @doc.add({Type: :ObjStm})
96
+ @doc.add({Type: :XRef})
97
+ objstm.add_object(@doc.add({Type: :Test}))
98
+ @doc.write(io)
99
+ io.rewind
100
+ @doc = HexaPDF::Document.new(io: io)
101
+ end
102
+
103
+ it "generates object streams" do
104
+ 210.times { @doc.add(5) }
105
+ objstm = @doc.add({Type: :ObjStm})
106
+ reload_document_with_objstm_from_io
107
+ @doc.task(:optimize, object_streams: :generate)
108
+ assert_objstms_generated
109
+ assert_default_deleted
110
+ assert_nil(@doc.object(objstm).value)
111
+ assert_equal(2, @doc.revisions.current.find_all {|obj| obj.type == :ObjStm }.size)
112
+ end
113
+
114
+ it "deletes object and xref streams" do
115
+ reload_document_with_objstm_from_io
116
+ @doc.task(:optimize, object_streams: :delete, xref_streams: :delete)
117
+ assert_no_objstms
118
+ assert_no_xrefstms
119
+ assert_default_deleted
120
+ end
121
+
122
+ it "deletes object and generates xref streams" do
123
+ @doc.add({Type: :ObjStm})
124
+ xref = @doc.add({Type: :XRef})
125
+ @doc.task(:optimize, object_streams: :delete, xref_streams: :generate)
126
+ assert_no_objstms
127
+ assert_xrefstms_generated
128
+ assert_equal([xref], @doc.revisions.current.find_all {|obj| obj.type == :XRef })
129
+ assert_default_deleted
130
+ end
131
+ end
132
+
133
+ describe "xref_streams" do
134
+ it "generates xref streams" do
135
+ @doc.task(:optimize, xref_streams: :generate)
136
+ assert_xrefstms_generated
137
+ assert_default_deleted
138
+ end
139
+
140
+ it "reuses an xref stream in generatation mode" do
141
+ @doc.add({Type: :XRef})
142
+ @doc.task(:optimize, xref_streams: :generate)
143
+ assert_xrefstms_generated
144
+ end
145
+
146
+ it "deletes xref streams" do
147
+ @doc.add({Type: :XRef})
148
+ @doc.task(:optimize, xref_streams: :delete)
149
+ assert_no_xrefstms
150
+ assert_default_deleted
151
+ end
152
+ end
153
+
154
+ describe "compress_pages" do
155
+ it "compresses pages streams" do
156
+ page = @doc.pages.add
157
+ page.contents = " 10 10 m q Q BI /Name 5 ID dataEI "
158
+ @doc.task(:optimize, compress_pages: true)
159
+ assert_equal("10 10 m\nq\nQ\nBI\n/Name 5 ID\ndataEI\n", page.contents)
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,258 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require_relative 'content/common'
5
+ require 'hexapdf/document'
6
+ require 'hexapdf/composer'
7
+ require 'stringio'
8
+
9
+ describe HexaPDF::Composer do
10
+ before do
11
+ @composer = HexaPDF::Composer.new
12
+ end
13
+
14
+ describe "initialize" do
15
+ it "creates a composer object with default values" do
16
+ assert_kind_of(HexaPDF::Document, @composer.document)
17
+ assert_kind_of(HexaPDF::Type::Page, @composer.page)
18
+ assert_equal(36, @composer.frame.left)
19
+ assert_equal(36, @composer.frame.bottom)
20
+ assert_equal(523, @composer.frame.width)
21
+ assert_equal(770, @composer.frame.height)
22
+ assert_equal("Times", @composer.base_style.font)
23
+ end
24
+
25
+ it "allows the customization of the page size" do
26
+ composer = HexaPDF::Composer.new(page_size: [0, 0, 100, 100])
27
+ assert_equal([0, 0, 100, 100], composer.page.box.value)
28
+ end
29
+
30
+ it "allows the customization of the page orientation" do
31
+ composer = HexaPDF::Composer.new(page_orientation: :landscape)
32
+ assert_equal([0, 0, 842, 595], composer.page.box.value)
33
+ end
34
+
35
+ it "allows the customization of the margin" do
36
+ composer = HexaPDF::Composer.new(margin: [100, 80, 60, 40])
37
+ assert_equal(40, composer.frame.left)
38
+ assert_equal(60, composer.frame.bottom)
39
+ assert_equal(475, composer.frame.width)
40
+ assert_equal(682, composer.frame.height)
41
+ end
42
+
43
+ it "yields itself" do
44
+ yielded = nil
45
+ composer = HexaPDF::Composer.new {|c| yielded = c }
46
+ assert_same(composer, yielded)
47
+ end
48
+ end
49
+
50
+ describe "::create" do
51
+ it "creates, yields, and writes a document" do
52
+ io = StringIO.new
53
+ HexaPDF::Composer.create(io, &:new_page)
54
+ io.rewind
55
+ assert_equal(2, HexaPDF::Document.new(io: io).pages.count)
56
+ end
57
+ end
58
+
59
+ describe "new_page" do
60
+ it "creates a new page with the stored information" do
61
+ c = HexaPDF::Composer.new(page_size: [0, 0, 50, 100], margin: 10)
62
+ c.new_page
63
+ assert_equal([0, 0, 50, 100], c.page.box.value)
64
+ assert_equal(10, c.frame.left)
65
+ assert_equal(10, c.frame.bottom)
66
+ end
67
+
68
+ it "uses the provided information for the new and all following pages" do
69
+ @composer.new_page(page_size: [0, 0, 50, 100], margin: 10)
70
+ assert_equal([0, 0, 50, 100], @composer.page.box.value)
71
+ assert_equal(10, @composer.frame.left)
72
+ assert_equal(10, @composer.frame.bottom)
73
+ @composer.new_page
74
+ assert_same(@composer.document.pages[2], @composer.page)
75
+ assert_equal([0, 0, 50, 100], @composer.page.box.value)
76
+ assert_equal(10, @composer.frame.left)
77
+ assert_equal(10, @composer.frame.bottom)
78
+ end
79
+ end
80
+
81
+ it "returns the current x-position" do
82
+ assert_equal(36, @composer.x)
83
+ end
84
+
85
+ it "returns the current y-position" do
86
+ assert_equal(806, @composer.y)
87
+ end
88
+
89
+ describe "text" do
90
+ it "creates a text box and draws it on the canvas" do
91
+ box = nil
92
+ @composer.define_singleton_method(:draw_box) {|arg| box = arg }
93
+
94
+ @composer.text("Test", width: 10, height: 15)
95
+ assert_equal(10, box.width)
96
+ assert_equal(15, box.height)
97
+ assert_same(@composer.document.fonts.add("Times"), box.style.font)
98
+ items = box.instance_variable_get(:@items)
99
+ assert_equal(1, items.length)
100
+ assert_same(box.style, items.first.style)
101
+ end
102
+
103
+ it "allows setting of a custom style" do
104
+ box = nil
105
+ @composer.define_singleton_method(:draw_box) {|arg| box = arg }
106
+
107
+ @composer.text("Test", style: HexaPDF::Layout::Style.new(font_size: 20))
108
+ assert_same(@composer.document.fonts.add("Times"), box.style.font)
109
+ assert_equal(20, box.style.font_size)
110
+ end
111
+
112
+ it "updates the used style with the provided options" do
113
+ box = nil
114
+ @composer.define_singleton_method(:draw_box) {|arg| box = arg }
115
+
116
+ @composer.text("Test", style: HexaPDF::Layout::Style.new, font_size: 20)
117
+ assert_equal(20, box.style.font_size)
118
+ end
119
+ end
120
+
121
+ describe "formatted_text" do
122
+ it "creates a text box with the formatted text and draws it on the canvas" do
123
+ box = nil
124
+ @composer.define_singleton_method(:draw_box) {|arg| box = arg }
125
+
126
+ @composer.formatted_text(["Test"], width: 10, height: 15)
127
+ assert_equal(10, box.width)
128
+ assert_equal(15, box.height)
129
+ assert_equal(1, box.instance_variable_get(:@items).length)
130
+ end
131
+
132
+ it "a hash can be used for custom style properties" do
133
+ box = nil
134
+ @composer.define_singleton_method(:draw_box) {|arg| box = arg }
135
+
136
+ @composer.formatted_text([{text: "Test", font_size: 20}], align: :center)
137
+ items = box.instance_variable_get(:@items)
138
+ assert_equal(1, items.length)
139
+ assert_equal(20, items.first.style.font_size)
140
+ assert_equal(:center, items.first.style.align)
141
+ assert_equal(10, box.style.font_size)
142
+ end
143
+
144
+ it "a hash can be used to provide a custom style" do
145
+ box = nil
146
+ @composer.define_singleton_method(:draw_box) {|arg| box = arg }
147
+
148
+ @composer.formatted_text([{text: "Test", style: HexaPDF::Layout::Style.new(fill_color: 128),
149
+ font_size: 20}], align: :center)
150
+ items = box.instance_variable_get(:@items)
151
+ assert_equal(20, items.first.style.font_size)
152
+ assert_equal(128, items.first.style.fill_color)
153
+ assert_equal(:center, items.first.style.align)
154
+ end
155
+
156
+ it "a hash can be used to link to an URL" do
157
+ box = nil
158
+ @composer.define_singleton_method(:draw_box) {|arg| box = arg }
159
+
160
+ @composer.formatted_text([{text: "Test", link: "URI"}, {link: "URI"}])
161
+ items = box.instance_variable_get(:@items)
162
+ assert_equal(2, items.length)
163
+ assert_equal(4, items[0].items.length)
164
+ assert_equal(3, items[1].items.length)
165
+ assert_equal([:link, {uri: 'URI'}], items[0].style.overlays.instance_variable_get(:@layers)[0])
166
+ end
167
+ end
168
+
169
+ describe "image" do
170
+ it "creates an image box and draws it on the canvas" do
171
+ box = nil
172
+ @composer.define_singleton_method(:draw_box) {|arg| box = arg }
173
+ image_path = File.join(TEST_DATA_DIR, 'images', 'gray.jpg')
174
+
175
+ @composer.image(image_path, width: 10, height: 15)
176
+ assert_equal(10, box.width)
177
+ assert_equal(15, box.height)
178
+ assert_same(@composer.document.images.add(image_path), box.image)
179
+ end
180
+ end
181
+
182
+ describe "draw_box" do
183
+ def create_box(**kwargs)
184
+ HexaPDF::Layout::Box.new(**kwargs) {}
185
+ end
186
+
187
+ it "draws the box if it completely fits" do
188
+ @composer.draw_box(create_box(height: 100))
189
+ @composer.draw_box(create_box)
190
+ assert_operators(@composer.canvas.contents,
191
+ [[:save_graphics_state],
192
+ [:concatenate_matrix, [1, 0, 0, 1, 36, 706]],
193
+ [:restore_graphics_state],
194
+ [:save_graphics_state],
195
+ [:concatenate_matrix, [1, 0, 0, 1, 36, 36]],
196
+ [:restore_graphics_state]])
197
+ end
198
+
199
+ it "draws the box on a new page if the frame is already full" do
200
+ first_page_canvas = @composer.canvas
201
+ @composer.draw_box(create_box)
202
+ @composer.draw_box(create_box)
203
+ refute_same(first_page_canvas, @composer.canvas)
204
+ assert_operators(@composer.canvas.contents,
205
+ [[:save_graphics_state],
206
+ [:concatenate_matrix, [1, 0, 0, 1, 36, 36]],
207
+ [:restore_graphics_state]])
208
+ end
209
+
210
+ it "splits the box across two pages" do
211
+ first_page_contents = @composer.canvas.contents
212
+ @composer.draw_box(create_box(height: 400))
213
+
214
+ box = create_box(height: 400)
215
+ box.define_singleton_method(:split) do |*|
216
+ [box, HexaPDF::Layout::Box.new(height: 100) {}]
217
+ end
218
+ @composer.draw_box(box)
219
+ assert_operators(first_page_contents,
220
+ [[:save_graphics_state],
221
+ [:concatenate_matrix, [1, 0, 0, 1, 36, 406]],
222
+ [:restore_graphics_state],
223
+ [:save_graphics_state],
224
+ [:concatenate_matrix, [1, 0, 0, 1, 36, 6]],
225
+ [:restore_graphics_state]])
226
+ assert_operators(@composer.canvas.contents,
227
+ [[:save_graphics_state],
228
+ [:concatenate_matrix, [1, 0, 0, 1, 36, 706]],
229
+ [:restore_graphics_state]])
230
+ end
231
+
232
+ it "finds a new region if splitting didn't work" do
233
+ first_page_contents = @composer.canvas.contents
234
+ @composer.draw_box(create_box(height: 400))
235
+ @composer.draw_box(create_box(height: 100, width: 300, style: {position: :float}))
236
+
237
+ box = create_box(width: 400, height: 400)
238
+ @composer.draw_box(box)
239
+ assert_operators(first_page_contents,
240
+ [[:save_graphics_state],
241
+ [:concatenate_matrix, [1, 0, 0, 1, 36, 406]],
242
+ [:restore_graphics_state],
243
+ [:save_graphics_state],
244
+ [:concatenate_matrix, [1, 0, 0, 1, 36, 306]],
245
+ [:restore_graphics_state]])
246
+ assert_operators(@composer.canvas.contents,
247
+ [[:save_graphics_state],
248
+ [:concatenate_matrix, [1, 0, 0, 1, 36, 406]],
249
+ [:restore_graphics_state]])
250
+ end
251
+
252
+ it "raises an error if a box doesn't fit onto an empty page" do
253
+ assert_raises(HexaPDF::Error) do
254
+ @composer.draw_box(create_box(height: 800))
255
+ end
256
+ end
257
+ end
258
+ end