hexapdf 0.17.1 → 0.17.2

Sign up to get free protection for your applications and to get access to all the features.
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,350 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/layout/frame'
5
+ require 'hexapdf/layout/box'
6
+
7
+ describe HexaPDF::Layout::Frame do
8
+ before do
9
+ @frame = HexaPDF::Layout::Frame.new(5, 10, 100, 150)
10
+ end
11
+
12
+ it "allows access to the bounding box attributes" do
13
+ assert_equal(5, @frame.left)
14
+ assert_equal(10, @frame.bottom)
15
+ assert_equal(100, @frame.width)
16
+ assert_equal(150, @frame.height)
17
+ end
18
+
19
+ it "allows access to the current region attributes" do
20
+ assert_equal(5, @frame.x)
21
+ assert_equal(160, @frame.y)
22
+ assert_equal(100, @frame.available_width)
23
+ assert_equal(150, @frame.available_height)
24
+ end
25
+
26
+ describe "contour_line" do
27
+ it "has a contour line equal to the bounding box by default" do
28
+ assert_equal([[5, 10], [105, 10], [105, 160], [5, 160]], @frame.contour_line.polygons[0].to_a)
29
+ end
30
+
31
+ it "can have a custom contour line polygon" do
32
+ contour_line = Geom2D::Polygon([0, 0], [10, 10], [10, 0])
33
+ frame = HexaPDF::Layout::Frame.new(0, 0, 10, 10, contour_line: contour_line)
34
+ assert_same(contour_line, frame.contour_line)
35
+ end
36
+ end
37
+
38
+ it "returns an appropriate width specification object" do
39
+ ws = @frame.width_specification(10)
40
+ assert_kind_of(HexaPDF::Layout::WidthFromPolygon, ws)
41
+ end
42
+
43
+ describe "fit and draw" do
44
+ before do
45
+ @frame = HexaPDF::Layout::Frame.new(10, 10, 100, 100)
46
+ @canvas = Minitest::Mock.new
47
+ end
48
+
49
+ # Creates a box with the given option, storing it in @box, and draws it inside @frame. It is
50
+ # checked whether the box coordinates are pos and whether the frame has the shape given by
51
+ # points.
52
+ def check_box(box_opts, pos, points)
53
+ @box = HexaPDF::Layout::Box.create(**box_opts) {}
54
+ @canvas.expect(:translate, nil, pos)
55
+ assert(@frame.draw(@canvas, @box))
56
+ assert_equal(points, @frame.shape.polygons.map(&:to_a))
57
+ @canvas.verify
58
+ end
59
+
60
+ # Removes a 10pt area from the :left, :right or :top.
61
+ def remove_area(*areas)
62
+ areas.each do |area|
63
+ @frame.remove_area(
64
+ case area
65
+ when :left then Geom2D::Polygon([10, 10], [10, 110], [20, 110], [20, 10])
66
+ when :right then Geom2D::Polygon([100, 10], [100, 110], [110, 110], [110, 10])
67
+ when :top then Geom2D::Polygon([10, 110], [110, 110], [110, 100], [10, 100])
68
+ end
69
+ )
70
+ end
71
+ end
72
+
73
+ describe "absolute position" do
74
+ it "draws the box at the given absolute position" do
75
+ check_box(
76
+ {width: 50, height: 50, position: :absolute, position_hint: [10, 10]},
77
+ [20, 20],
78
+ [[[10, 10], [110, 10], [110, 110], [10, 110]],
79
+ [[20, 20], [70, 20], [70, 70], [20, 70]]]
80
+ )
81
+ end
82
+
83
+ it "determines the available space for #fit by using the space to the right and above" do
84
+ check_box(
85
+ {position: :absolute, position_hint: [10, 10]},
86
+ [20, 20],
87
+ [[[10, 10], [110, 10], [110, 20], [20, 20], [20, 110], [10, 110]]]
88
+ )
89
+ end
90
+
91
+ it "always removes the whole margin box from the frame" do
92
+ check_box(
93
+ {width: 50, height: 50, position: :absolute, position_hint: [10, 10],
94
+ margin: [10, 20, 30, 40]},
95
+ [20, 20],
96
+ [[[10, 80], [90, 80], [90, 10], [110, 10], [110, 110], [10, 110]]]
97
+ )
98
+ end
99
+ end
100
+
101
+ describe "default position" do
102
+ it "draws the box on the left side" do
103
+ check_box({width: 50, height: 50},
104
+ [10, 60],
105
+ [[[10, 10], [110, 10], [110, 60], [10, 60]]])
106
+ end
107
+
108
+ it "draws the box on the right side" do
109
+ check_box({width: 50, height: 50, position_hint: :right},
110
+ [60, 60],
111
+ [[[10, 10], [110, 10], [110, 60], [10, 60]]])
112
+ end
113
+
114
+ it "draws the box in the center" do
115
+ check_box({width: 50, height: 50, position_hint: :center},
116
+ [35, 60],
117
+ [[[10, 10], [110, 10], [110, 60], [10, 60]]])
118
+ end
119
+
120
+ describe "with margin" do
121
+ [:left, :center, :right].each do |hint|
122
+ it "ignores all margins if the box fills the whole frame, with position hint #{hint}" do
123
+ check_box({margin: 10, position_hint: hint},
124
+ [10, 10], [])
125
+ assert_equal(100, @box.width)
126
+ assert_equal(100, @box.height)
127
+ end
128
+
129
+ it "ignores the left/top/right margin if the available bounds coincide with the " \
130
+ "frame's, with position hint #{hint}" do
131
+ check_box({height: 50, margin: 10, position_hint: hint},
132
+ [10, 60],
133
+ [[[10, 10], [110, 10], [110, 50], [10, 50]]])
134
+ end
135
+
136
+ it "doesn't ignore top margin if the available bounds' top doesn't coincide with the " \
137
+ "frame's top, with position hint #{hint}" do
138
+ remove_area(:top)
139
+ check_box({height: 50, margin: 10, position_hint: hint},
140
+ [10, 40],
141
+ [[[10, 10], [110, 10], [110, 30], [10, 30]]])
142
+ assert_equal(100, @box.width)
143
+ end
144
+
145
+ it "doesn't ignore left margin if the available bounds' left doesn't coincide with the " \
146
+ "frame's left, with position hint #{hint}" do
147
+ remove_area(:left)
148
+ check_box({height: 50, margin: 10, position_hint: hint},
149
+ [30, 60],
150
+ [[[20, 10], [110, 10], [110, 50], [20, 50]]])
151
+ assert_equal(80, @box.width)
152
+ end
153
+
154
+ it "doesn't ignore right margin if the available bounds' right doesn't coincide with " \
155
+ "the frame's right, with position hint #{hint}" do
156
+ remove_area(:right)
157
+ check_box({height: 50, margin: 10, position_hint: hint},
158
+ [10, 60],
159
+ [[[10, 10], [100, 10], [100, 50], [10, 50]]])
160
+ assert_equal(80, @box.width)
161
+ end
162
+ end
163
+
164
+ it "perfectly centers a box if possible, margins ignored" do
165
+ check_box({width: 50, height: 10, margin: [10, 10, 10, 20], position_hint: :center},
166
+ [35, 100],
167
+ [[[10, 10], [110, 10], [110, 90], [10, 90]]])
168
+ end
169
+
170
+ it "perfectly centers a box if possible, margins not ignored" do
171
+ remove_area(:left, :right)
172
+ check_box({width: 40, height: 10, margin: [10, 10, 10, 20], position_hint: :center},
173
+ [40, 100],
174
+ [[[20, 10], [100, 10], [100, 90], [20, 90]]])
175
+ end
176
+
177
+ it "centers a box as good as possible when margins aren't equal" do
178
+ remove_area(:left, :right)
179
+ check_box({width: 20, height: 10, margin: [10, 10, 10, 40], position_hint: :center},
180
+ [65, 100],
181
+ [[[20, 10], [100, 10], [100, 90], [20, 90]]])
182
+ end
183
+ end
184
+ end
185
+
186
+ describe "floating boxes" do
187
+ it "draws the box on the left side" do
188
+ check_box({width: 50, height: 50, position: :float},
189
+ [10, 60],
190
+ [[[10, 10], [110, 10], [110, 110], [60, 110], [60, 60], [10, 60]]])
191
+ end
192
+
193
+ it "draws the box on the right side" do
194
+ check_box({width: 50, height: 50, position: :float, position_hint: :right},
195
+ [60, 60],
196
+ [[[10, 10], [110, 10], [110, 60], [60, 60], [60, 110], [10, 110]]])
197
+ end
198
+
199
+ describe "with margin" do
200
+ [:left, :right].each do |hint|
201
+ it "ignores all margins if the box fills the whole frame, with position hint #{hint}" do
202
+ check_box({margin: 10, position: :float, position_hint: hint},
203
+ [10, 10], [])
204
+ assert_equal(100, @box.width)
205
+ assert_equal(100, @box.height)
206
+ end
207
+ end
208
+
209
+ it "ignores the left, but not the right margin if aligned left to the frame border" do
210
+ check_box({width: 50, height: 50, margin: 10, position: :float, position_hint: :left},
211
+ [10, 60],
212
+ [[[10, 10], [110, 10], [110, 110], [70, 110], [70, 50], [10, 50]]])
213
+ end
214
+
215
+ it "uses the left and the right margin if aligned left and not to the frame border" do
216
+ remove_area(:left)
217
+ check_box({width: 50, height: 50, margin: 10, position: :float, position_hint: :left},
218
+ [30, 60],
219
+ [[[20, 10], [110, 10], [110, 110], [90, 110], [90, 50], [20, 50]]])
220
+ end
221
+
222
+ it "ignores the right, but not the left margin if aligned right to the frame border" do
223
+ check_box({width: 50, height: 50, margin: 10, position: :float, position_hint: :right},
224
+ [60, 60],
225
+ [[[10, 10], [110, 10], [110, 50], [50, 50], [50, 110], [10, 110]]])
226
+ end
227
+
228
+ it "uses the left and the right margin if aligned right and not to the frame border" do
229
+ remove_area(:right)
230
+ check_box({width: 50, height: 50, margin: 10, position: :float, position_hint: :right},
231
+ [40, 60],
232
+ [[[10, 10], [100, 10], [100, 50], [30, 50], [30, 110], [10, 110]]])
233
+ end
234
+ end
235
+ end
236
+
237
+ describe "flowing boxes" do
238
+ it "flows inside the frame's outline" do
239
+ check_box({width: 10, height: 20, position: :flow},
240
+ [0, 90],
241
+ [[[10, 10], [110, 10], [110, 90], [10, 90]]])
242
+ end
243
+ end
244
+
245
+ it "doesn't draw the box if it doesn't fit into the available space" do
246
+ box = HexaPDF::Layout::Box.create(width: 150, height: 50)
247
+ refute(@frame.draw(@canvas, box))
248
+ end
249
+
250
+ it "can't fit the box if there is no available space" do
251
+ @frame.remove_area(Geom2D::Polygon([0, 0], [110, 0], [110, 110], [0, 110]))
252
+ box = HexaPDF::Layout::Box.create
253
+ refute(@frame.fit(box))
254
+ end
255
+
256
+ it "draws the box even if the box's height is zero" do
257
+ box = HexaPDF::Layout::Box.create
258
+ box.define_singleton_method(:height) { 0 }
259
+ assert(@frame.draw(@canvas, box))
260
+ end
261
+ end
262
+
263
+ describe "split" do
264
+ it "splits the box if necessary" do
265
+ box = HexaPDF::Layout::Box.create(width: 10, height: 10)
266
+ assert_equal([nil, box], @frame.split(box))
267
+ assert_nil(@frame.instance_variable_get(:@fit_data).box)
268
+ end
269
+ end
270
+
271
+ describe "find_next_region" do
272
+ # Checks all availability regions of the frame
273
+ def check_regions(frame, regions)
274
+ regions.each_with_index do |region, index|
275
+ assert_equal(region[0], frame.x, "region #{index} invalid x")
276
+ assert_equal(region[1], frame.y, "region #{index} invalid y")
277
+ assert_equal(region[2], frame.available_width, "region #{index} invalid available width")
278
+ assert_equal(region[3], frame.available_height, "region #{index} invalid available height")
279
+ frame.find_next_region
280
+ end
281
+ assert_equal(0, frame.x)
282
+ assert_equal(0, frame.y)
283
+ assert_equal(0, frame.available_width)
284
+ assert_equal(0, frame.available_height)
285
+ end
286
+
287
+ # o------+
288
+ # | |
289
+ # | |
290
+ # | |
291
+ # +------+
292
+ it "works for a rectangular region" do
293
+ frame = HexaPDF::Layout::Frame.new(0, 0, 100, 300)
294
+ check_regions(frame, [[0, 300, 100, 300]])
295
+ end
296
+
297
+ # o--------+
298
+ # | |
299
+ # | +--+ |
300
+ # | | | |
301
+ # | +--+ |
302
+ # | |
303
+ # +--------+
304
+ it "works for a region with a hole" do
305
+ frame = HexaPDF::Layout::Frame.new(0, 0, 100, 100)
306
+ frame.remove_area(Geom2D::Polygon([20, 20], [80, 20], [80, 80], [20, 80]))
307
+ check_regions(frame, [[0, 100, 100, 20], [0, 100, 20, 100],
308
+ [0, 80, 20, 80], [0, 20, 100, 20]])
309
+ end
310
+
311
+ # o--+ +--+
312
+ # | | | |
313
+ # | +--+ |
314
+ # | |
315
+ # +--------+
316
+ it "works for a u-shaped frame" do
317
+ frame = HexaPDF::Layout::Frame.new(0, 0, 100, 100)
318
+ frame.remove_area(Geom2D::Polygon([30, 100], [70, 100], [70, 60], [30, 60]))
319
+ check_regions(frame, [[0, 100, 30, 100], [0, 60, 100, 60]])
320
+ end
321
+
322
+ # o---+ +--+
323
+ # | | +--+ |
324
+ # | +--+ |
325
+ # | |
326
+ # +----+ |
327
+ # +----+ |
328
+ # | |
329
+ # +------------+
330
+ it "works for a complicated frame" do
331
+ frame = HexaPDF::Layout::Frame.new(0, 0, 100, 100)
332
+ top_cut = Geom2D::Polygon([20, 100], [20, 80], [40, 80], [40, 90], [60, 90], [60, 100])
333
+ left_cut = Geom2D::Polygon([0, 20], [30, 20], [30, 40], [0, 40])
334
+ frame.remove_area(Geom2D::PolygonSet(top_cut, left_cut))
335
+
336
+ check_regions(frame, [[0, 100, 20, 60], [0, 90, 20, 50], [0, 80, 100, 40],
337
+ [30, 40, 70, 40], [0, 20, 100, 20]])
338
+ end
339
+ end
340
+
341
+ describe "remove_area" do
342
+ it "recalculates the contour line only if necessary" do
343
+ contour = Geom2D::Polygon([10, 10], [10, 90], [90, 90], [90, 10])
344
+ frame = HexaPDF::Layout::Frame.new(0, 0, 100, 100, contour_line: contour)
345
+ frame.remove_area(Geom2D::Polygon([0, 0], [20, 0], [20, 100], [0, 100]))
346
+ assert_equal([[[20, 10], [90, 10], [90, 90], [20, 90]]],
347
+ frame.contour_line.polygons.map(&:to_a))
348
+ end
349
+ end
350
+ end
@@ -0,0 +1,73 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require_relative '../content/common'
5
+ require 'hexapdf/document'
6
+ require 'hexapdf/layout/image_box'
7
+
8
+ describe HexaPDF::Layout::ImageBox do
9
+ before do
10
+ @image = HexaPDF::Stream.new({Subtype: :Image}, stream: '')
11
+ @image.define_singleton_method(:width) { 40 }
12
+ @image.define_singleton_method(:height) { 20 }
13
+ end
14
+
15
+ def create_box(**kwargs)
16
+ HexaPDF::Layout::ImageBox.new(@image, **kwargs)
17
+ end
18
+
19
+ describe "initialize" do
20
+ it "takes the image to be displayed" do
21
+ box = create_box
22
+ assert_equal(@image, box.image)
23
+ end
24
+ end
25
+
26
+ describe "fit" do
27
+ it "fits with fixed dimensions" do
28
+ box = create_box(width: 50, height: 30, style: {padding: [10, 4, 6, 2]})
29
+ assert(box.fit(100, 100, nil))
30
+ assert_equal(50, box.width)
31
+ assert_equal(30, box.height)
32
+ end
33
+
34
+ it "fits with a fixed width" do
35
+ box = create_box(width: 60, style: {padding: [10, 4, 6, 2]})
36
+ assert(box.fit(100, 100, nil))
37
+ assert_equal(60, box.width)
38
+ assert_equal(43, box.height)
39
+ end
40
+
41
+ it "fits with a fixed height" do
42
+ box = create_box(height: 40, style: {padding: [10, 4, 6, 2]})
43
+ assert(box.fit(100, 100, nil))
44
+ assert_equal(54, box.width)
45
+ assert_equal(40, box.height)
46
+ end
47
+
48
+ it "fits with auto-scaling to available space" do
49
+ box = create_box(style: {padding: [10, 4, 6, 2]})
50
+ assert(box.fit(100, 100, nil))
51
+ assert_equal(100, box.width)
52
+ assert_equal(63, box.height)
53
+
54
+ assert(box.fit(100, 30, nil))
55
+ assert_equal(34, box.width)
56
+ assert_equal(30, box.height)
57
+ end
58
+ end
59
+
60
+ describe "draw" do
61
+ it "draws the image" do
62
+ box = create_box(height: 40, style: {padding: [10, 4, 6, 2]})
63
+ box.fit(100, 100, nil)
64
+
65
+ @canvas = HexaPDF::Document.new.pages.add.canvas
66
+ box.draw(@canvas, 0, 0)
67
+ assert_operators(@canvas.contents, [[:save_graphics_state],
68
+ [:concatenate_matrix, [48, 0, 0, 24, 2, 6]],
69
+ [:paint_xobject, [:XO1]],
70
+ [:restore_graphics_state]])
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,71 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/layout/inline_box'
5
+
6
+ describe HexaPDF::Layout::InlineBox do
7
+ before do
8
+ @box = HexaPDF::Layout::InlineBox.create(width: 10, height: 15, margin: [15, 10])
9
+ end
10
+
11
+ it "needs a box to wrap and an optional alignment on initialization" do
12
+ ibox = HexaPDF::Layout::InlineBox.new(@box)
13
+ assert_equal(@box, ibox.box)
14
+ assert_equal(:baseline, ibox.valign)
15
+
16
+ ibox = HexaPDF::Layout::InlineBox.new(@box, valign: :top)
17
+ assert_equal(:top, ibox.valign)
18
+ end
19
+
20
+ it "draws the wrapped box at the correct position" do
21
+ canvas = Object.new
22
+ block = lambda do |c, x, y|
23
+ assert_equal(canvas, c)
24
+ assert_equal(10, x)
25
+ assert_equal(15, y)
26
+ end
27
+ @box.box.stub(:draw, block) do
28
+ @box.draw(canvas, 0, 0)
29
+ end
30
+ end
31
+
32
+ it "returns true if the inline box is empty with no drawing operations" do
33
+ assert(@box.empty?)
34
+ refute(HexaPDF::Layout::InlineBox.create(width: 10, height: 15) {}.empty?)
35
+ end
36
+
37
+ describe "valign" do
38
+ it "has a default value of :baseline" do
39
+ assert_equal(:baseline, @box.valign)
40
+ end
41
+
42
+ it "can be changed on creation" do
43
+ box = HexaPDF::Layout::InlineBox.create(width: 10, height: 15, valign: :test)
44
+ assert_equal(:test, box.valign)
45
+ end
46
+ end
47
+
48
+ it "returns the width including margins" do
49
+ assert_equal(30, @box.width)
50
+ end
51
+
52
+ it "returns the height including margins" do
53
+ assert_equal(45, @box.height)
54
+ end
55
+
56
+ it "returns 0 for x_min" do
57
+ assert_equal(0, @box.x_min)
58
+ end
59
+
60
+ it "returns width for x_max" do
61
+ assert_equal(@box.width, @box.x_max)
62
+ end
63
+
64
+ it "returns 0 for y_min" do
65
+ assert_equal(0, @box.y_min)
66
+ end
67
+
68
+ it "returns height for y_max" do
69
+ assert_equal(@box.height, @box.y_max)
70
+ end
71
+ end
@@ -0,0 +1,206 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/document'
5
+
6
+ describe HexaPDF::Layout::Line::HeightCalculator do
7
+ before do
8
+ @calc = HexaPDF::Layout::Line::HeightCalculator.new
9
+ end
10
+
11
+ it "simulate the height as if an item was added" do
12
+ @calc << HexaPDF::Layout::InlineBox.create(width: 10, height: 20, valign: :baseline) {}
13
+ assert_equal([0, 20, 0, 0], @calc.result)
14
+ new_item = HexaPDF::Layout::InlineBox.create(width: 10, height: 30, valign: :top) {}
15
+ assert_equal([-10, 20, 30], @calc.simulate_height(new_item))
16
+ assert_equal([0, 20, 0, 0], @calc.result)
17
+ end
18
+ end
19
+
20
+ describe HexaPDF::Layout::Line do
21
+ before do
22
+ @doc = HexaPDF::Document.new
23
+ @font = @doc.fonts.add("Times", custom_encoding: true)
24
+ @line = HexaPDF::Layout::Line.new
25
+ end
26
+
27
+ def setup_fragment(text)
28
+ HexaPDF::Layout::TextFragment.create(text, font: @font, font_size: 10)
29
+ end
30
+
31
+ def setup_box(width, height, valign = :baseline)
32
+ HexaPDF::Layout::InlineBox.create(width: width, height: height, valign: valign) {}
33
+ end
34
+
35
+ describe "initialize" do
36
+ it "allows setting the items of the line fragment" do
37
+ frag1 = setup_fragment("Hello")
38
+ frag2 = HexaPDF::Layout::TextFragment.new(frag1.items.slice!(3, 2), frag1.style)
39
+ line = HexaPDF::Layout::Line.new([frag1, frag2])
40
+ assert_equal(1, line.items.count)
41
+ assert_equal(5, line.items[0].items.count)
42
+ end
43
+ end
44
+
45
+ describe "add" do
46
+ it "adds items to the line" do
47
+ @line << :test << :other
48
+ assert_equal([:test, :other], @line.items)
49
+ end
50
+
51
+ it "combines text fragments if possible" do
52
+ frag1 = setup_fragment("Home")
53
+ frag2 = HexaPDF::Layout::TextFragment.new(frag1.items.slice!(2, 2), frag1.style)
54
+ @line << setup_fragment("o") << :other << frag1 << frag2
55
+ assert_equal(3, @line.items.length)
56
+ assert_equal(4, @line.items.last.items.length)
57
+ end
58
+
59
+ it "duplicates the first of two combinable text fragments if its items are frozen" do
60
+ frag1 = setup_fragment("Home")
61
+ frag2 = HexaPDF::Layout::TextFragment.new(frag1.items.slice!(2, 2), frag1.style)
62
+ frag1.items.freeze
63
+ frag2.items.freeze
64
+
65
+ @line << setup_fragment("o") << frag1 << frag2 << :other
66
+ assert_equal(3, @line.items.length)
67
+ assert_equal(4, @line.items[-2].items.length)
68
+ end
69
+ end
70
+
71
+ describe "with text fragments" do
72
+ before do
73
+ @frag_h = setup_fragment("H")
74
+ @frag_y = setup_fragment("y")
75
+ @line << @frag_h << @frag_y << @frag_h
76
+ end
77
+
78
+ it "calculates the various x/y values correctly" do
79
+ assert_equal(@frag_h.x_min, @line.x_min)
80
+ assert_equal(@frag_h.width + @frag_y.width + @frag_h.x_max, @line.x_max)
81
+ assert_equal(@frag_y.y_min, @line.y_min)
82
+ assert_equal(@frag_h.y_max, @line.y_max)
83
+ assert_equal(@frag_y.y_min, @line.text_y_min)
84
+ assert_equal(@frag_h.y_max, @line.text_y_max)
85
+ assert_equal(2 * @frag_h.width + @frag_y.width, @line.width)
86
+ assert_equal(@frag_h.y_max - @frag_y.y_min, @line.height)
87
+ end
88
+
89
+ describe "and with inline boxes" do
90
+ it "x_min is correct if an inline box is the first item" do
91
+ @line.items.unshift(setup_box(10, 10))
92
+ assert_equal(0, @line.x_min)
93
+ end
94
+
95
+ it "x_max is correct if an inline box is the last item" do
96
+ @line << setup_box(10, 10)
97
+ assert_equal(@line.width, @line.x_max)
98
+ end
99
+
100
+ it "doesn't change text_y_min/text_y_max" do
101
+ text_y_min, text_y_max = @line.text_y_min, @line.text_y_max
102
+ @line << setup_box(10, 30, :text_top) << setup_box(10, 30, :text_bottom)
103
+ @line.clear_cache
104
+ assert_equal(text_y_min, @line.text_y_min)
105
+ assert_equal(text_y_max, @line.text_y_max)
106
+ end
107
+
108
+ it "y values are not changed if all boxes are smaller than the text's height" do
109
+ *y_values = @line.y_min, @line.y_max, @line.text_y_min, @line.text_y_max
110
+ @line << setup_box(10, 5, :baseline)
111
+ @line.clear_cache
112
+ assert_equal(y_values, [@line.y_min, @line.y_max, @line.text_y_min, @line.text_y_max])
113
+ end
114
+
115
+ it "changes y_max to fit if baseline boxes are higher than the text" do
116
+ y_min = @line.y_min
117
+ box = setup_box(10, 50, :baseline)
118
+ @line.add(box)
119
+
120
+ @line.clear_cache
121
+ assert_equal(50, @line.y_max)
122
+ assert_equal(y_min, @line.y_min)
123
+ end
124
+
125
+ it "changes y_max to fit if text_bottom boxes are higher than the text" do
126
+ y_min = @line.y_min
127
+ box = setup_box(10, 50, :text_bottom)
128
+ @line.add(box)
129
+
130
+ @line.clear_cache
131
+ assert_equal(50 + @line.text_y_min, @line.y_max)
132
+ assert_equal(y_min, @line.y_min)
133
+ end
134
+
135
+ it "changes y_max to fit if bottom boxes are higher than the text" do
136
+ y_min = @line.y_min
137
+ box = setup_box(10, 50, :bottom)
138
+ @line.add(box)
139
+
140
+ @line.clear_cache
141
+ assert_equal(50 + @line.text_y_min, @line.y_max)
142
+ assert_equal(y_min, @line.y_min)
143
+ end
144
+
145
+ it "changes y_min to fit if text_top/top boxes are higher than the text" do
146
+ y_max = @line.y_max
147
+ box = setup_box(10, 50, :text_top)
148
+ @line.add(box)
149
+
150
+ @line.clear_cache
151
+ assert_equal(@line.text_y_max - 50, @line.y_min)
152
+ assert_equal(y_max, @line.y_max)
153
+
154
+ box.instance_variable_set(:@valign, :top)
155
+ @line.clear_cache
156
+ assert_equal(@line.text_y_max - 50, @line.y_min)
157
+ assert_equal(y_max, @line.y_max)
158
+ end
159
+
160
+ it "changes y_min/y_max to fit if boxes are aligned in both directions" do
161
+ @line << setup_box(10, 20, :text_top) <<
162
+ setup_box(10, 20, :text_bottom) <<
163
+ setup_box(10, 20, :top) <<
164
+ setup_box(10, 70, :bottom)
165
+ assert_equal(@line.text_y_max - 20, @line.y_min)
166
+ assert_equal(@line.text_y_max - 20 + 70, @line.y_max)
167
+ end
168
+ end
169
+ end
170
+
171
+ it "fails when accessing a vertical measurement if an item uses an invalid valign value" do
172
+ @line << setup_box(10, 20, :invalid)
173
+ assert_raises(HexaPDF::Error) { @line.y_min }
174
+ end
175
+
176
+ describe "each" do
177
+ it "iterates over all items and yields them with their offset values" do
178
+ @line << setup_fragment("H") <<
179
+ setup_box(10, 10, :top) <<
180
+ setup_box(10, 10, :text_top) <<
181
+ setup_box(10, 10, :baseline) <<
182
+ setup_box(10, 10, :text_bottom) <<
183
+ setup_box(10, 10, :bottom)
184
+ result = [
185
+ [@line.items[0], 0, 0],
186
+ [@line.items[1], @line.items[0].width, @line.y_max - 10],
187
+ [@line.items[2], @line.items[0].width + 10, @line.text_y_max - 10],
188
+ [@line.items[3], @line.items[0].width + 20, 0],
189
+ [@line.items[4], @line.items[0].width + 30, @line.text_y_min],
190
+ [@line.items[5], @line.items[0].width + 40, @line.y_min],
191
+ ]
192
+ assert_equal(result, @line.to_enum(:each).map {|*a| a })
193
+ end
194
+
195
+ it "fails if an item uses an invalid valign value" do
196
+ @line << setup_box(10, 10, :invalid)
197
+ assert_raises(HexaPDF::Error) { @line.each {} }
198
+ end
199
+ end
200
+
201
+ it "allows ignoring line justification" do
202
+ refute(@line.ignore_justification?)
203
+ @line.ignore_justification!
204
+ assert(@line.ignore_justification?)
205
+ end
206
+ end