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,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