hexapdf 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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
-