hexapdf 0.6.0 → 0.7.0

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 (257) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +33 -0
  3. data/CONTRIBUTERS +1 -1
  4. data/LICENSE +1 -1
  5. data/Rakefile +1 -1
  6. data/VERSION +1 -1
  7. data/bin/hexapdf +1 -1
  8. data/examples/text_layouter_styling.rb +1 -2
  9. data/lib/hexapdf.rb +2 -2
  10. data/lib/hexapdf/cli.rb +3 -3
  11. data/lib/hexapdf/cli/batch.rb +5 -5
  12. data/lib/hexapdf/cli/command.rb +15 -17
  13. data/lib/hexapdf/cli/files.rb +3 -3
  14. data/lib/hexapdf/cli/images.rb +3 -4
  15. data/lib/hexapdf/cli/info.rb +5 -5
  16. data/lib/hexapdf/cli/inspect.rb +6 -6
  17. data/lib/hexapdf/cli/merge.rb +6 -6
  18. data/lib/hexapdf/cli/modify.rb +4 -4
  19. data/lib/hexapdf/cli/optimize.rb +3 -3
  20. data/lib/hexapdf/configuration.rb +4 -5
  21. data/lib/hexapdf/content.rb +2 -2
  22. data/lib/hexapdf/content/canvas.rb +35 -36
  23. data/lib/hexapdf/content/color_space.rb +9 -14
  24. data/lib/hexapdf/content/graphic_object.rb +2 -2
  25. data/lib/hexapdf/content/graphic_object/arc.rb +3 -3
  26. data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +2 -2
  27. data/lib/hexapdf/content/graphic_object/solid_arc.rb +4 -8
  28. data/lib/hexapdf/content/graphics_state.rb +4 -13
  29. data/lib/hexapdf/content/operator.rb +33 -35
  30. data/lib/hexapdf/content/parser.rb +28 -18
  31. data/lib/hexapdf/content/processor.rb +4 -5
  32. data/lib/hexapdf/content/transformation_matrix.rb +2 -2
  33. data/lib/hexapdf/data_dir.rb +2 -2
  34. data/lib/hexapdf/dictionary.rb +8 -9
  35. data/lib/hexapdf/dictionary_fields.rb +7 -10
  36. data/lib/hexapdf/document.rb +18 -18
  37. data/lib/hexapdf/document/files.rb +12 -10
  38. data/lib/hexapdf/document/fonts.rb +2 -2
  39. data/lib/hexapdf/document/images.rb +3 -3
  40. data/lib/hexapdf/document/pages.rb +4 -4
  41. data/lib/hexapdf/encryption.rb +2 -2
  42. data/lib/hexapdf/encryption/aes.rb +2 -2
  43. data/lib/hexapdf/encryption/arc4.rb +4 -4
  44. data/lib/hexapdf/encryption/fast_aes.rb +2 -2
  45. data/lib/hexapdf/encryption/fast_arc4.rb +4 -4
  46. data/lib/hexapdf/encryption/identity.rb +5 -4
  47. data/lib/hexapdf/encryption/ruby_aes.rb +147 -139
  48. data/lib/hexapdf/encryption/ruby_arc4.rb +4 -4
  49. data/lib/hexapdf/encryption/security_handler.rb +11 -12
  50. data/lib/hexapdf/encryption/standard_security_handler.rb +6 -9
  51. data/lib/hexapdf/error.rb +7 -9
  52. data/lib/hexapdf/filter.rb +2 -3
  53. data/lib/hexapdf/filter/ascii85_decode.rb +3 -3
  54. data/lib/hexapdf/filter/ascii_hex_decode.rb +2 -2
  55. data/lib/hexapdf/filter/dct_decode.rb +2 -2
  56. data/lib/hexapdf/filter/encryption.rb +2 -2
  57. data/lib/hexapdf/filter/flate_decode.rb +16 -33
  58. data/lib/hexapdf/filter/jpx_decode.rb +2 -2
  59. data/lib/hexapdf/filter/lzw_decode.rb +4 -4
  60. data/lib/hexapdf/filter/predictor.rb +2 -6
  61. data/lib/hexapdf/filter/run_length_decode.rb +2 -2
  62. data/lib/hexapdf/font/cmap.rb +2 -3
  63. data/lib/hexapdf/font/cmap/parser.rb +11 -11
  64. data/lib/hexapdf/font/cmap/writer.rb +25 -25
  65. data/lib/hexapdf/font/encoding.rb +2 -2
  66. data/lib/hexapdf/font/encoding/base.rb +2 -2
  67. data/lib/hexapdf/font/encoding/difference_encoding.rb +2 -2
  68. data/lib/hexapdf/font/encoding/glyph_list.rb +6 -6
  69. data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +2 -2
  70. data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +2 -2
  71. data/lib/hexapdf/font/encoding/standard_encoding.rb +2 -2
  72. data/lib/hexapdf/font/encoding/symbol_encoding.rb +2 -2
  73. data/lib/hexapdf/font/encoding/win_ansi_encoding.rb +2 -2
  74. data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +2 -2
  75. data/lib/hexapdf/font/invalid_glyph.rb +7 -7
  76. data/lib/hexapdf/font/true_type.rb +2 -2
  77. data/lib/hexapdf/font/true_type/builder.rb +13 -7
  78. data/lib/hexapdf/font/true_type/font.rb +2 -3
  79. data/lib/hexapdf/font/true_type/optimizer.rb +2 -2
  80. data/lib/hexapdf/font/true_type/subsetter.rb +4 -4
  81. data/lib/hexapdf/font/true_type/table.rb +3 -5
  82. data/lib/hexapdf/font/true_type/table/cmap.rb +2 -2
  83. data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +9 -16
  84. data/lib/hexapdf/font/true_type/table/directory.rb +4 -4
  85. data/lib/hexapdf/font/true_type/table/glyf.rb +2 -2
  86. data/lib/hexapdf/font/true_type/table/head.rb +3 -2
  87. data/lib/hexapdf/font/true_type/table/hhea.rb +2 -2
  88. data/lib/hexapdf/font/true_type/table/hmtx.rb +2 -2
  89. data/lib/hexapdf/font/true_type/table/kern.rb +3 -3
  90. data/lib/hexapdf/font/true_type/table/loca.rb +3 -3
  91. data/lib/hexapdf/font/true_type/table/maxp.rb +2 -2
  92. data/lib/hexapdf/font/true_type/table/name.rb +3 -5
  93. data/lib/hexapdf/font/true_type/table/os2.rb +4 -3
  94. data/lib/hexapdf/font/true_type/table/post.rb +3 -7
  95. data/lib/hexapdf/font/true_type_wrapper.rb +20 -22
  96. data/lib/hexapdf/font/type1.rb +2 -2
  97. data/lib/hexapdf/font/type1/afm_parser.rb +7 -7
  98. data/lib/hexapdf/font/type1/character_metrics.rb +2 -2
  99. data/lib/hexapdf/font/type1/font.rb +3 -3
  100. data/lib/hexapdf/font/type1/font_metrics.rb +2 -4
  101. data/lib/hexapdf/font/type1/pfb_parser.rb +2 -2
  102. data/lib/hexapdf/font/type1_wrapper.rb +8 -9
  103. data/lib/hexapdf/font_loader.rb +2 -2
  104. data/lib/hexapdf/font_loader/from_configuration.rb +2 -2
  105. data/lib/hexapdf/font_loader/from_file.rb +2 -2
  106. data/lib/hexapdf/font_loader/standard14.rb +2 -2
  107. data/lib/hexapdf/image_loader.rb +2 -2
  108. data/lib/hexapdf/image_loader/jpeg.rb +6 -4
  109. data/lib/hexapdf/image_loader/pdf.rb +2 -2
  110. data/lib/hexapdf/image_loader/png.rb +6 -6
  111. data/lib/hexapdf/importer.rb +6 -4
  112. data/lib/hexapdf/layout.rb +2 -2
  113. data/lib/hexapdf/layout/box.rb +4 -4
  114. data/lib/hexapdf/layout/inline_box.rb +2 -2
  115. data/lib/hexapdf/layout/line.rb +6 -6
  116. data/lib/hexapdf/layout/numeric_refinements.rb +2 -2
  117. data/lib/hexapdf/layout/style.rb +17 -8
  118. data/lib/hexapdf/layout/text_fragment.rb +86 -48
  119. data/lib/hexapdf/layout/text_layouter.rb +40 -21
  120. data/lib/hexapdf/layout/text_shaper.rb +2 -2
  121. data/lib/hexapdf/name_tree_node.rb +2 -2
  122. data/lib/hexapdf/number_tree_node.rb +2 -2
  123. data/lib/hexapdf/object.rb +6 -8
  124. data/lib/hexapdf/parser.rb +10 -10
  125. data/lib/hexapdf/rectangle.rb +4 -4
  126. data/lib/hexapdf/reference.rb +2 -2
  127. data/lib/hexapdf/revision.rb +4 -4
  128. data/lib/hexapdf/revisions.rb +5 -5
  129. data/lib/hexapdf/serializer.rb +27 -24
  130. data/lib/hexapdf/stream.rb +4 -4
  131. data/lib/hexapdf/task.rb +2 -2
  132. data/lib/hexapdf/task/dereference.rb +4 -4
  133. data/lib/hexapdf/task/optimize.rb +5 -4
  134. data/lib/hexapdf/tokenizer.rb +12 -14
  135. data/lib/hexapdf/type.rb +2 -2
  136. data/lib/hexapdf/type/action.rb +3 -3
  137. data/lib/hexapdf/type/actions.rb +2 -2
  138. data/lib/hexapdf/type/actions/go_to.rb +2 -2
  139. data/lib/hexapdf/type/actions/go_to_r.rb +2 -2
  140. data/lib/hexapdf/type/actions/launch.rb +2 -2
  141. data/lib/hexapdf/type/actions/uri.rb +3 -3
  142. data/lib/hexapdf/type/annotation.rb +3 -3
  143. data/lib/hexapdf/type/annotations.rb +2 -2
  144. data/lib/hexapdf/type/annotations/link.rb +2 -2
  145. data/lib/hexapdf/type/annotations/markup_annotation.rb +2 -2
  146. data/lib/hexapdf/type/annotations/text.rb +2 -2
  147. data/lib/hexapdf/type/catalog.rb +4 -4
  148. data/lib/hexapdf/type/cid_font.rb +4 -5
  149. data/lib/hexapdf/type/embedded_file.rb +3 -3
  150. data/lib/hexapdf/type/file_specification.rb +6 -7
  151. data/lib/hexapdf/type/font.rb +4 -4
  152. data/lib/hexapdf/type/font_descriptor.rb +3 -4
  153. data/lib/hexapdf/type/font_simple.rb +12 -16
  154. data/lib/hexapdf/type/font_true_type.rb +2 -2
  155. data/lib/hexapdf/type/font_type0.rb +5 -5
  156. data/lib/hexapdf/type/font_type1.rb +4 -4
  157. data/lib/hexapdf/type/form.rb +3 -3
  158. data/lib/hexapdf/type/graphics_state_parameter.rb +3 -3
  159. data/lib/hexapdf/type/image.rb +13 -14
  160. data/lib/hexapdf/type/info.rb +2 -2
  161. data/lib/hexapdf/type/names.rb +2 -2
  162. data/lib/hexapdf/type/object_stream.rb +5 -5
  163. data/lib/hexapdf/type/page.rb +9 -10
  164. data/lib/hexapdf/type/page_tree_node.rb +5 -6
  165. data/lib/hexapdf/type/resources.rb +11 -11
  166. data/lib/hexapdf/type/trailer.rb +3 -3
  167. data/lib/hexapdf/type/viewer_preferences.rb +2 -2
  168. data/lib/hexapdf/type/xref_stream.rb +8 -4
  169. data/lib/hexapdf/utils/bit_field.rb +4 -4
  170. data/lib/hexapdf/utils/bit_stream.rb +4 -4
  171. data/lib/hexapdf/utils/graphics_helpers.rb +2 -2
  172. data/lib/hexapdf/utils/lru_cache.rb +2 -2
  173. data/lib/hexapdf/utils/math_helpers.rb +2 -2
  174. data/lib/hexapdf/utils/object_hash.rb +3 -3
  175. data/lib/hexapdf/utils/pdf_doc_encoding.rb +3 -3
  176. data/lib/hexapdf/utils/sorted_tree_node.rb +12 -11
  177. data/lib/hexapdf/version.rb +3 -3
  178. data/lib/hexapdf/writer.rb +8 -8
  179. data/lib/hexapdf/xref_section.rb +3 -3
  180. data/test/hexapdf/common_tokenizer_tests.rb +6 -7
  181. data/test/hexapdf/content/graphic_object/test_endpoint_arc.rb +0 -1
  182. data/test/hexapdf/content/test_canvas.rb +28 -36
  183. data/test/hexapdf/content/test_color_space.rb +4 -0
  184. data/test/hexapdf/content/test_graphics_state.rb +2 -0
  185. data/test/hexapdf/content/test_operator.rb +6 -7
  186. data/test/hexapdf/content/test_parser.rb +2 -2
  187. data/test/hexapdf/content/test_processor.rb +1 -1
  188. data/test/hexapdf/document/test_files.rb +1 -0
  189. data/test/hexapdf/document/test_images.rb +1 -1
  190. data/test/hexapdf/encryption/common.rb +3 -3
  191. data/test/hexapdf/encryption/test_aes.rb +10 -4
  192. data/test/hexapdf/encryption/test_identity.rb +1 -1
  193. data/test/hexapdf/encryption/test_security_handler.rb +13 -17
  194. data/test/hexapdf/encryption/test_standard_security_handler.rb +12 -12
  195. data/test/hexapdf/filter/test_ascii85_decode.rb +2 -3
  196. data/test/hexapdf/filter/test_ascii_hex_decode.rb +6 -1
  197. data/test/hexapdf/filter/test_flate_decode.rb +2 -2
  198. data/test/hexapdf/filter/test_lzw_decode.rb +10 -10
  199. data/test/hexapdf/filter/test_predictor.rb +10 -2
  200. data/test/hexapdf/filter/test_run_length_decode.rb +1 -1
  201. data/test/hexapdf/font/cmap/test_parser.rb +40 -40
  202. data/test/hexapdf/font/cmap/test_writer.rb +29 -29
  203. data/test/hexapdf/font/test_true_type_wrapper.rb +3 -2
  204. data/test/hexapdf/font/true_type/common.rb +2 -0
  205. data/test/hexapdf/font/true_type/table/test_cmap.rb +1 -1
  206. data/test/hexapdf/font/true_type/table/test_cmap_subtable.rb +5 -4
  207. data/test/hexapdf/font/true_type/table/test_glyf.rb +2 -2
  208. data/test/hexapdf/font/true_type/table/test_head.rb +2 -2
  209. data/test/hexapdf/font/true_type/table/test_name.rb +9 -6
  210. data/test/hexapdf/font/true_type/test_builder.rb +8 -3
  211. data/test/hexapdf/font/true_type/test_optimizer.rb +1 -2
  212. data/test/hexapdf/font/type1/test_afm_parser.rb +2 -2
  213. data/test/hexapdf/image_loader/test_jpeg.rb +1 -1
  214. data/test/hexapdf/image_loader/test_pdf.rb +1 -1
  215. data/test/hexapdf/image_loader/test_png.rb +3 -3
  216. data/test/hexapdf/layout/test_inline_box.rb +10 -1
  217. data/test/hexapdf/layout/test_line.rb +4 -4
  218. data/test/hexapdf/layout/test_style.rb +19 -7
  219. data/test/hexapdf/layout/test_text_fragment.rb +73 -27
  220. data/test/hexapdf/layout/test_text_layouter.rb +84 -68
  221. data/test/hexapdf/layout/test_text_shaper.rb +4 -6
  222. data/test/hexapdf/task/test_dereference.rb +2 -2
  223. data/test/hexapdf/task/test_optimize.rb +16 -7
  224. data/test/hexapdf/test_configuration.rb +1 -1
  225. data/test/hexapdf/test_data_dir.rb +2 -2
  226. data/test/hexapdf/test_dictionary.rb +6 -3
  227. data/test/hexapdf/test_dictionary_fields.rb +15 -14
  228. data/test/hexapdf/test_document.rb +47 -48
  229. data/test/hexapdf/test_filter.rb +30 -26
  230. data/test/hexapdf/test_importer.rb +14 -0
  231. data/test/hexapdf/test_object.rb +16 -4
  232. data/test/hexapdf/test_parser.rb +36 -36
  233. data/test/hexapdf/test_reference.rb +7 -5
  234. data/test/hexapdf/test_revision.rb +1 -1
  235. data/test/hexapdf/test_revisions.rb +90 -90
  236. data/test/hexapdf/test_serializer.rb +3 -2
  237. data/test/hexapdf/test_stream.rb +2 -4
  238. data/test/hexapdf/test_tokenizer.rb +2 -2
  239. data/test/hexapdf/test_writer.rb +80 -80
  240. data/test/hexapdf/test_xref_section.rb +1 -1
  241. data/test/hexapdf/type/annotations/test_link.rb +1 -1
  242. data/test/hexapdf/type/annotations/test_text.rb +3 -3
  243. data/test/hexapdf/type/test_font_descriptor.rb +1 -1
  244. data/test/hexapdf/type/test_font_simple.rb +2 -2
  245. data/test/hexapdf/type/test_font_type0.rb +0 -1
  246. data/test/hexapdf/type/test_image.rb +0 -3
  247. data/test/hexapdf/type/test_object_stream.rb +2 -1
  248. data/test/hexapdf/type/test_page.rb +5 -1
  249. data/test/hexapdf/type/test_page_tree_node.rb +4 -4
  250. data/test/hexapdf/type/test_trailer.rb +1 -1
  251. data/test/hexapdf/type/test_xref_stream.rb +4 -0
  252. data/test/hexapdf/utils/test_bit_field.rb +2 -0
  253. data/test/hexapdf/utils/test_bit_stream.rb +1 -1
  254. data/test/hexapdf/utils/test_lru_cache.rb +1 -1
  255. data/test/hexapdf/utils/test_sorted_tree_node.rb +10 -4
  256. data/test/test_helper.rb +3 -6
  257. metadata +3 -3
@@ -16,15 +16,23 @@ describe HexaPDF::Layout::TextFragment do
16
16
  style = HexaPDF::Layout::Style.new(font: @font, font_size: 20,
17
17
  horizontal_scaling: 200, character_spacing: 1,
18
18
  word_spacing: 2, text_rise: text_rise)
19
- @fragment = HexaPDF::Layout::TextFragment.new(items: items, style: style)
19
+ @fragment = HexaPDF::Layout::TextFragment.new(items, style)
20
20
  end
21
21
 
22
- it "creates a TextFragment from text and options" do
23
- frag = HexaPDF::Layout::TextFragment.create("Tom", font: @font, font_size: 20,
24
- font_features: {kern: true})
25
- assert_equal(4, frag.items.length)
26
- assert_equal(36.18, frag.width)
27
- assert_equal(13.66 + 4.34, frag.height)
22
+ describe "create" do
23
+ it "creates a TextFragment from text and options" do
24
+ frag = HexaPDF::Layout::TextFragment.create("Tom", font: @font, font_size: 20,
25
+ font_features: {kern: true})
26
+ assert_equal(4, frag.items.length)
27
+ assert_equal(36.18, frag.width)
28
+ assert_equal(13.66 + 4.34, frag.height)
29
+ end
30
+
31
+ it "creates a TextFragment from text and a Style object" do
32
+ style = HexaPDF::Layout::Style.new(font: @font)
33
+ frag = HexaPDF::Layout::TextFragment.create("Tom", style)
34
+ assert_equal(style, frag.style)
35
+ end
28
36
  end
29
37
 
30
38
  describe "initialize" do
@@ -34,12 +42,12 @@ describe HexaPDF::Layout::TextFragment do
34
42
 
35
43
  it "can use a Style object" do
36
44
  style = HexaPDF::Layout::Style.new(font: @font, font_size: 20)
37
- frag = HexaPDF::Layout::TextFragment.new(items: @items, style: style)
45
+ frag = HexaPDF::Layout::TextFragment.new(@items, style)
38
46
  assert_equal(20, frag.style.font_size)
39
47
  end
40
48
 
41
- it "can use a style options" do
42
- frag = HexaPDF::Layout::TextFragment.new(items: @items, style: {font: @font, font_size: 20})
49
+ it "can use style options" do
50
+ frag = HexaPDF::Layout::TextFragment.new(@items, font: @font, font_size: 20)
43
51
  assert_equal(20, frag.style.font_size)
44
52
  end
45
53
  end
@@ -51,7 +59,7 @@ describe HexaPDF::Layout::TextFragment do
51
59
  describe "draw" do
52
60
  def setup_with_style(**styles)
53
61
  setup_fragment(@font.decode_utf8('H'), 2)
54
- styles.each {|name, value| @fragment.style.send(name, value)}
62
+ styles.each {|name, value| @fragment.style.send(name, value) }
55
63
  @canvas = @doc.pages.add.canvas
56
64
  @fragment.draw(@canvas, 10, 15)
57
65
  end
@@ -59,16 +67,15 @@ describe HexaPDF::Layout::TextFragment do
59
67
  def assert_draw_operators(*args, front: [], middle: args, back: [])
60
68
  ops = [
61
69
  *front,
62
- [:begin_text],
63
- [:set_text_matrix, [1, 0, 0, 1, 10, 15]],
64
70
  [:set_font_and_size, [:F1, 20]],
65
- [:set_leading, [24.0]],
66
71
  [:set_horizontal_scaling, [200]],
67
72
  [:set_character_spacing, [1]],
68
73
  [:set_word_spacing, [2]],
69
74
  [:set_text_rise, [2]],
70
75
  *middle,
71
- [:show_text_with_positioning, [['!']]],
76
+ [:begin_text],
77
+ [:set_text_matrix, [1, 0, 0, 1, 10, 15]],
78
+ [:show_text, ['!']],
72
79
  *back,
73
80
  ].compact
74
81
  assert_operators(@canvas.contents, ops)
@@ -79,6 +86,47 @@ describe HexaPDF::Layout::TextFragment do
79
86
  assert_draw_operators
80
87
  end
81
88
 
89
+ it "doesn't set the text properties if instructed to do so" do
90
+ setup_fragment([])
91
+ @canvas = @doc.pages.add.canvas
92
+ @fragment.draw(@canvas, 10, 15, ignore_text_properties: true)
93
+ assert_operators(@canvas.contents, [[:begin_text],
94
+ [:set_text_matrix, [1, 0, 0, 1, 10, 15]]])
95
+ end
96
+
97
+ describe "uses an appropriate text position setter" do
98
+ before do
99
+ setup_fragment([])
100
+ @canvas = @doc.pages.add.canvas
101
+ end
102
+
103
+ it "with text leading graphics state" do
104
+ @canvas.begin_text.leading(10)
105
+ @fragment.draw(@canvas, 0, -10, ignore_text_properties: true)
106
+ assert_operators(@canvas.contents, [[:begin_text],
107
+ [:set_leading, [10]],
108
+ [:move_text_next_line]])
109
+ end
110
+
111
+ it "only horizontal movement" do
112
+ @fragment.draw(@canvas, 20, 0, ignore_text_properties: true)
113
+ assert_operators(@canvas.contents, [[:begin_text],
114
+ [:move_text, [20, 0]]])
115
+ end
116
+
117
+ it "only vertical movement" do
118
+ @fragment.draw(@canvas, 0, 10, ignore_text_properties: true)
119
+ assert_operators(@canvas.contents, [[:begin_text],
120
+ [:move_text, [0, 10]]])
121
+ end
122
+
123
+ it "horizontal and vertical movement" do
124
+ @fragment.draw(@canvas, 10, 10, ignore_text_properties: true)
125
+ assert_operators(@canvas.contents, [[:begin_text],
126
+ [:set_text_matrix, [1, 0, 0, 1, 10, 10]]])
127
+ end
128
+ end
129
+
82
130
  it "draws styled filled text" do
83
131
  setup_with_style(fill_color: 0.5, fill_alpha: 0.5)
84
132
  assert_draw_operators([:set_graphics_state_parameters, [:GS1]],
@@ -132,14 +180,16 @@ describe HexaPDF::Layout::TextFragment do
132
180
  [:set_line_width, [5]],
133
181
  [:set_line_cap_style, [1]],
134
182
  [:set_line_dash_pattern, [[5], 0]]],
135
- back: [[:set_device_gray_stroking_color, [0]],
183
+ back: [[:end_text],
184
+ [:save_graphics_state],
185
+ [:set_device_gray_stroking_color, [0]],
136
186
  [:set_line_width, [@fragment.style.calculated_underline_thickness]],
137
187
  [:set_line_cap_style, [0]],
138
188
  [:set_line_dash_pattern, [[], 0]],
139
- [:end_text],
140
189
  [:move_to, [10, 15]],
141
190
  [:line_to, [40.88, 15]],
142
- [:stroke_path]])
191
+ [:stroke_path],
192
+ [:restore_graphics_state]])
143
193
  end
144
194
 
145
195
  it "draws the strikeout line" do
@@ -151,14 +201,16 @@ describe HexaPDF::Layout::TextFragment do
151
201
  [:set_line_width, [5]],
152
202
  [:set_line_cap_style, [1]],
153
203
  [:set_line_dash_pattern, [[5], 0]]],
154
- back: [[:set_device_gray_stroking_color, [0]],
204
+ back: [[:end_text],
205
+ [:save_graphics_state],
206
+ [:set_device_gray_stroking_color, [0]],
155
207
  [:set_line_width, [@fragment.style.calculated_strikeout_thickness]],
156
208
  [:set_line_cap_style, [0]],
157
209
  [:set_line_dash_pattern, [[], 0]],
158
- [:end_text],
159
210
  [:move_to, [10, 21.01]],
160
211
  [:line_to, [40.88, 21.01]],
161
- [:stroke_path]])
212
+ [:stroke_path],
213
+ [:restore_graphics_state]])
162
214
  end
163
215
  end
164
216
 
@@ -190,12 +242,6 @@ describe HexaPDF::Layout::TextFragment do
190
242
  it "calculates the height" do
191
243
  assert_equal(13.66 + 4.34, @fragment.height)
192
244
  end
193
-
194
- it "draws nothing" do
195
- canvas = @doc.pages.add.canvas
196
- @fragment.draw(canvas, 10, 15)
197
- assert_operators(canvas.contents, [])
198
- end
199
245
  end
200
246
 
201
247
  describe "normal text" do
@@ -48,6 +48,13 @@ module TestTextLayouterHelpers
48
48
  assert_equal(item.items, obj.item.items)
49
49
  end
50
50
  end
51
+
52
+ def assert_line_wrapping(result, widths)
53
+ rest, lines = *result
54
+ assert(rest.empty?)
55
+ assert_equal(widths.length, lines.count)
56
+ widths.each_with_index {|width, index| assert_equal(width, lines[index].width) }
57
+ end
51
58
  end
52
59
 
53
60
  describe HexaPDF::Layout::TextLayouter::SimpleTextSegmentation do
@@ -61,14 +68,14 @@ describe HexaPDF::Layout::TextLayouter::SimpleTextSegmentation do
61
68
 
62
69
  def setup_fragment(text, style = nil)
63
70
  if style
64
- HexaPDF::Layout::TextFragment.new(items: style.font.decode_utf8(text), style: style)
71
+ HexaPDF::Layout::TextFragment.create(text, style)
65
72
  else
66
73
  HexaPDF::Layout::TextFragment.create(text, font: @font)
67
74
  end
68
75
  end
69
76
 
70
77
  it "handles InlineBox objects" do
71
- input = HexaPDF::Layout::InlineBox.create(width: 10, height: 10) { }
78
+ input = HexaPDF::Layout::InlineBox.create(width: 10, height: 10) {}
72
79
  result = @obj.call([input, input])
73
80
  assert_equal(2, result.size)
74
81
  assert_box(result[0], input)
@@ -103,11 +110,12 @@ describe HexaPDF::Layout::TextLayouter::SimpleTextSegmentation do
103
110
  end
104
111
 
105
112
  it "insert a mandatory break when an Unicode line boundary characters is encountered" do
106
- frag = setup_fragment("A\rB\r\nC\nD\vE\fF\u{85}G\u{2029}H\u{2028}I")
113
+ frag = setup_fragment("A\rB\r\nC\nD\vE\fF\u{85}G\u{2029}H\u{2028}I\r")
114
+ frag.items << 5 << frag.items[-2]
107
115
 
108
116
  result = @obj.call([frag])
109
- assert_equal(17, result.size)
110
- [1, 3, 5, 7, 9, 11, 13].each do |index|
117
+ assert_equal(20, result.size)
118
+ [1, 3, 5, 7, 9, 11, 13, 17, 19].each do |index|
111
119
  assert_penalty(result[index],
112
120
  HexaPDF::Layout::TextLayouter::Penalty::MandatoryParagraphBreak.penalty)
113
121
  end
@@ -163,98 +171,83 @@ module CommonLineWrappingTests
163
171
 
164
172
  it "breaks before a box if it doesn't fit onto the line anymore" do
165
173
  rest, lines = call(boxes(25, 50, 25, 10))
166
- assert(rest.empty?)
167
- assert_equal(2, lines.count)
168
- lines.each {|line| line.items.each {|item| assert_kind_of(HexaPDF::Layout::InlineBox, item)}}
169
- assert_equal(100, lines[0].width)
170
- assert_equal(10, lines[1].width)
174
+ assert_line_wrapping([rest, lines], [100, 10])
175
+ lines.each {|line| line.items.each {|item| assert_kind_of(HexaPDF::Layout::InlineBox, item) } }
171
176
  end
172
177
 
173
178
  it "breaks at a glue and ignores it if it doesn't fit onto the line anymore" do
174
- rest, lines = call(boxes(90, 20).insert(-2, glue(20)))
175
- assert(rest.empty?)
176
- assert_equal(2, lines.count)
177
- assert_equal(90, lines[0].width)
178
- assert_equal(20, lines[1].width)
179
+ result = call(boxes(90) + [glue(20)] + boxes(20))
180
+ assert_line_wrapping(result, [90, 20])
179
181
  end
180
182
 
181
183
  it "handles spaces at the start of a line" do
182
- rest, lines = call(boxes(25, 50).unshift(glue(15)))
183
- assert(rest.empty?)
184
- assert_equal(1, lines.count)
185
- assert_equal(75, lines[0].width)
184
+ rest, lines = call([glue(15)] + boxes(25, 50))
185
+ assert_line_wrapping([rest, lines], [75])
186
186
  assert_equal(25, lines[0].items[0].width)
187
187
  end
188
188
 
189
189
  it "handles spaces at the end of a line" do
190
- rest, lines = call(boxes(20, 50, 20).insert(-2, glue(10)).insert(-2, glue(10)))
191
- assert(rest.empty?)
192
- assert_equal(2, lines.count)
193
- assert_equal(70, lines[0].width)
194
- assert_equal(20, lines[1].width)
190
+ rest, lines = call(boxes(20, 50) + [glue(10), glue(10)] + boxes(20))
191
+ assert_line_wrapping([rest, lines], [70, 20])
195
192
  assert_equal(50, lines[0].items[-1].width)
196
193
  end
197
194
 
198
195
  it "handles spaces at the end of a line before a mandatory break" do
199
- rest, lines = call(boxes(20, 50, 20).insert(-2, glue(10)).insert(-2, penalty(-5000)))
200
- assert(rest.empty?)
201
- assert_equal(2, lines.count)
202
- assert_equal(70, lines[0].width)
203
- assert_equal(20, lines[1].width)
196
+ rest, lines = call(boxes(20, 50) + [glue(10), penalty(-5000)] + boxes(20))
197
+ assert_line_wrapping([rest, lines], [70, 20])
204
198
  assert_equal(50, lines[0].items[-1].width)
205
199
  end
206
200
 
207
201
  it "handles multiple glue items after another" do
208
- rest, lines = call(boxes(20, 20, 50, 20).insert(1, glue(20)).insert(1, glue(20)))
209
- assert(rest.empty?)
210
- assert_equal(2, lines.count)
211
- assert_equal(80, lines[0].width)
212
- assert_equal(70, lines[1].width)
202
+ result = call(boxes(20) + [glue(20), glue(20)] + boxes(20, 50, 20))
203
+ assert_line_wrapping(result, [80, 70])
213
204
  end
214
205
 
215
206
  it "handles mandatory line breaks" do
216
- rest, lines = call(boxes(20, 20).insert(-2, penalty(-5000)))
217
- assert(rest.empty?)
218
- assert_equal(2, lines.count)
219
- assert_equal(20, lines[0].width)
220
- assert_equal(20, lines[1].width)
207
+ rest, lines = call(boxes(20) + [penalty(-5000)] + boxes(20))
208
+ assert_line_wrapping([rest, lines], [20, 20])
221
209
  assert(lines[0].ignore_justification?)
222
210
  end
223
211
 
224
212
  it "handles breaking at penalties with zero width" do
225
- rest, lines = call(boxes(80, 10, 20).insert(1, penalty(0)).insert(-2, penalty(0)))
226
- assert(rest.empty?)
227
- assert_equal(2, lines.count)
228
- assert_equal(90, lines[0].width)
229
- assert_equal(20, lines[1].width)
213
+ result = call(boxes(80) + [penalty(0)] + boxes(10) + [penalty(0)] + boxes(20))
214
+ assert_line_wrapping(result, [90, 20])
230
215
  end
231
216
 
232
217
  it "handles breaking at penalties with non-zero width if they fit on the line" do
233
- item = HexaPDF::Layout::InlineBox.create(width: 20) {}
234
- rest, lines = call(boxes(20, 60, 30).insert(1, penalty(0, item)).insert(-2, penalty(0, item)))
235
- assert(rest.empty?)
236
- assert_equal(2, lines.count)
237
- assert_equal(100, lines[0].width)
238
- assert_equal(30, lines[1].width)
239
- assert_same(item, lines[0].items[-1])
218
+ pitem = penalty(0, boxes(20).first)
219
+ rest, lines = call(boxes(20) + [pitem] + boxes(50) + [glue(10), pitem] + boxes(30))
220
+ assert_line_wrapping([rest, lines], [100, 30])
221
+ assert_same(pitem.item, lines[0].items[-1])
222
+ end
223
+
224
+ it "handles breaking at penalties with non-zero width that fit on the line and are followed by 1+ penalties" do
225
+ pitem = penalty(0, boxes(20).first)
226
+ result = call(boxes(80) + [pitem, penalty(0), penalty(0)] + boxes(30))
227
+ assert_line_wrapping(result, [100, 30])
240
228
  end
241
229
 
242
230
  it "handles penalties with non-zero width if they don't fit on the line" do
243
- item = HexaPDF::Layout::InlineBox.create(width: 20) {}
244
- rest, lines = call(boxes(70) + [glue(10)] + boxes(10) + [penalty(0, item)] + boxes(30))
245
- assert(rest.empty?)
246
- assert_equal(2, lines.count)
247
- assert_equal(70, lines[0].width)
248
- assert_equal(40, lines[1].width)
231
+ item = boxes(20).first
232
+ result = call(boxes(70) + [glue(10)] + boxes(10) + [penalty(0, item)] + boxes(30))
233
+ assert_line_wrapping(result, [70, 40])
249
234
  end
250
235
 
251
- it "handles breaking at prohibited breakpoints by back-tracking to the last valid breakpoint " do
252
- item = HexaPDF::Layout::InlineBox.create(width: 20) {}
253
- rest, lines = call(boxes(70) + [glue(10)] + boxes(10) + [penalty(5000, item)] + boxes(30))
254
- assert(rest.empty?)
255
- assert_equal(2, lines.count)
256
- assert_equal(70, lines[0].width)
257
- assert_equal(60, lines[1].width)
236
+ it "handles breaking at penalties with non-zero width surrounded by glue" do
237
+ item = boxes(20).first
238
+ result = call(boxes(70) + [glue(10)] + [penalty(0, item)] + [glue(30)] + boxes(30))
239
+ assert_line_wrapping(result, [100, 30])
240
+ end
241
+
242
+ it "handles prohibited breakpoint penalties with zero width" do
243
+ result = call(boxes(70) + [glue(10)] + boxes(10) + [penalty(5000)] + boxes(30))
244
+ assert_line_wrapping(result, [70, 40])
245
+ end
246
+
247
+ it "handles prohibited breakpoint penalties with non-zero width" do
248
+ item = boxes(20).first
249
+ result = call(boxes(70) + [glue(10)] + boxes(10) + [penalty(5000, item)] + boxes(30))
250
+ assert_line_wrapping(result, [70, 60])
258
251
  end
259
252
 
260
253
  it "stops when nil is returned by the block: last item is a box" do
@@ -527,7 +520,7 @@ describe HexaPDF::Layout::TextLayouter do
527
520
  rest, reason = layouter.fit
528
521
  assert(rest.empty?)
529
522
  assert_equal(:success, reason)
530
- assert_equal(str.strip.length, layouter.lines.sum {|l| l.items.sum {|i| i.items.count}})
523
+ assert_equal(str.strip.length, layouter.lines.sum {|l| l.items.sum {|i| i.items.count } })
531
524
  assert_equal(45, layouter.actual_height)
532
525
 
533
526
  layouter = HexaPDF::Layout::TextLayouter.new(items: [frag], width: 1, height: height,
@@ -564,7 +557,7 @@ describe HexaPDF::Layout::TextLayouter do
564
557
  end
565
558
 
566
559
  it "applies the optional horizontal offsets if set" do
567
- x_offsets = lambda {|height, line_height| height + line_height}
560
+ x_offsets = lambda {|height, line_height| height + line_height }
568
561
  layouter = HexaPDF::Layout::TextLayouter.new(items: boxes(*([[20, 10]] * 7)), width: 60,
569
562
  x_offsets: x_offsets, height: 100, style: @style)
570
563
  rest, reason = layouter.fit
@@ -582,7 +575,17 @@ describe HexaPDF::Layout::TextLayouter do
582
575
  processor = TestHelper::OperatorRecorder.new
583
576
  HexaPDF::Content::Parser.new.parse(content, processor)
584
577
  result = processor.recorded_ops
585
- result.select! {|name, _| name == :set_text_matrix}.map! {|_, ops| ops[-2, 2]}
578
+ leading = (result.select {|name, _| name == :set_leading } || [0]).map(&:last).flatten.first
579
+ pos = [0, 0]
580
+ result.select! {|name, _| name == :set_text_matrix || name == :move_text_next_line }.
581
+ map! do |name, ops|
582
+ if name == :set_text_matrix
583
+ pos = ops[-2, 2]
584
+ elsif name == :move_text_next_line
585
+ pos[1] -= leading
586
+ end
587
+ pos.dup
588
+ end
586
589
  positions.each_with_index do |(x, y), index|
587
590
  assert_in_delta(x, result[index][0], 0.00001)
588
591
  assert_in_delta(y, result[index][1], 0.00001)
@@ -600,6 +603,19 @@ describe HexaPDF::Layout::TextLayouter do
600
603
  @line2w = HexaPDF::Layout::TextFragment.create("more text.", font: @font).width
601
604
  end
602
605
 
606
+ it "returns the result of #fit if #fit needs to be run" do
607
+ rest, reason = @layouter.draw(@canvas, 0, 0)
608
+ assert(rest.empty?)
609
+ assert_equal(:success, reason)
610
+
611
+ @layouter.items = [HexaPDF::Layout::InlineBox.create(width: @width + 10) {}]
612
+ rest, reason = @layouter.draw(@canvas, 0, 0)
613
+ assert_equal(1, rest.size)
614
+ assert_equal(:box, reason)
615
+
616
+ assert_nil(@layouter.draw(@canvas, 0, 0))
617
+ end
618
+
603
619
  it "can horizontally align the contents to the left" do
604
620
  top = 100
605
621
  @layouter.style.align = :left
@@ -697,7 +713,7 @@ describe HexaPDF::Layout::TextLayouter do
697
713
 
698
714
  it "makes sure that text fragments don't pollute the graphics state for inline boxes" do
699
715
  frag = HexaPDF::Layout::TextFragment.create("Demo", font: @font)
700
- inline_box = HexaPDF::Layout::InlineBox.create(width: 10, height: 10) {|c, _| c.text("A")}
716
+ inline_box = HexaPDF::Layout::InlineBox.create(width: 10, height: 10) {|c, _| c.text("A") }
701
717
  layouter = HexaPDF::Layout::TextLayouter.new(items: [frag, inline_box], width: 200)
702
718
  assert_raises(HexaPDF::Error) { layouter.draw(@canvas, 0, 0) }
703
719
  end
@@ -16,7 +16,7 @@ describe HexaPDF::Layout::TextShaper do
16
16
 
17
17
  def setup_fragment(items, **options)
18
18
  style = HexaPDF::Layout::Style.new(font: @font, font_size: 20, font_features: options)
19
- HexaPDF::Layout::TextFragment.new(items: items, style: style)
19
+ HexaPDF::Layout::TextFragment.new(items, style)
20
20
  end
21
21
 
22
22
  describe "Type1 font features" do
@@ -29,14 +29,14 @@ describe HexaPDF::Layout::TextShaper do
29
29
  insert(0, 100), liga: true)
30
30
  @shaper.shape_text(fragment)
31
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})
32
+ fragment.items.map {|item| item.kind_of?(Numeric) ? item : item.id })
33
33
  end
34
34
 
35
35
  it "handles kerning" do
36
36
  fragment = setup_fragment(@font.decode_utf8('fish fish wow').insert(1, 100), kern: true)
37
37
  @shaper.shape_text(fragment)
38
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})
39
+ fragment.items.map {|item| item.kind_of?(Numeric) ? item : item.id })
40
40
  end
41
41
  end
42
42
 
@@ -56,9 +56,7 @@ describe HexaPDF::Layout::TextShaper do
56
56
  fragment = setup_fragment(@font.decode_utf8('Top Top').insert(1, 100), kern: true)
57
57
  @shaper.shape_text(fragment)
58
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})
59
+ fragment.items.map {|item| item.kind_of?(Numeric) ? [item] : item.id })
60
60
  end
61
61
  end
62
62
  end
63
-
64
-