prawn 2.1.0 → 2.3.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 (280) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/GPLv2 +20 -21
  5. data/Gemfile +3 -9
  6. data/Rakefile +20 -23
  7. data/lib/prawn.rb +36 -49
  8. data/lib/prawn/document.rb +180 -133
  9. data/lib/prawn/document/bounding_box.rb +41 -29
  10. data/lib/prawn/document/column_box.rb +7 -7
  11. data/lib/prawn/document/internals.rb +8 -6
  12. data/lib/prawn/document/span.rb +21 -16
  13. data/lib/prawn/encoding.rb +69 -68
  14. data/lib/prawn/errors.rb +12 -7
  15. data/lib/prawn/font.rb +102 -69
  16. data/lib/prawn/font_metric_cache.rb +14 -8
  17. data/lib/prawn/{font → fonts}/afm.rb +102 -68
  18. data/lib/prawn/{font → fonts}/dfont.rb +5 -11
  19. data/lib/prawn/fonts/otf.rb +11 -0
  20. data/lib/prawn/fonts/ttc.rb +36 -0
  21. data/lib/prawn/{font → fonts}/ttf.rb +87 -68
  22. data/lib/prawn/graphics.rb +119 -81
  23. data/lib/prawn/graphics/blend_mode.rb +9 -8
  24. data/lib/prawn/graphics/cap_style.rb +3 -3
  25. data/lib/prawn/graphics/color.rb +27 -25
  26. data/lib/prawn/graphics/dash.rb +23 -11
  27. data/lib/prawn/graphics/join_style.rb +9 -3
  28. data/lib/prawn/graphics/patterns.rb +190 -96
  29. data/lib/prawn/graphics/transformation.rb +15 -9
  30. data/lib/prawn/graphics/transparency.rb +17 -13
  31. data/lib/prawn/grid.rb +48 -47
  32. data/lib/prawn/image_handler.rb +5 -5
  33. data/lib/prawn/images.rb +39 -30
  34. data/lib/prawn/images/image.rb +2 -1
  35. data/lib/prawn/images/jpg.rb +28 -22
  36. data/lib/prawn/images/png.rb +65 -62
  37. data/lib/prawn/measurement_extensions.rb +10 -9
  38. data/lib/prawn/measurements.rb +19 -15
  39. data/lib/prawn/outline.rb +97 -77
  40. data/lib/prawn/repeater.rb +14 -10
  41. data/lib/prawn/security.rb +81 -61
  42. data/lib/prawn/security/arcfour.rb +2 -2
  43. data/lib/prawn/soft_mask.rb +26 -26
  44. data/lib/prawn/stamp.rb +20 -13
  45. data/lib/prawn/text.rb +68 -52
  46. data/lib/prawn/text/box.rb +11 -8
  47. data/lib/prawn/text/formatted.rb +5 -5
  48. data/lib/prawn/text/formatted/arranger.rb +53 -32
  49. data/lib/prawn/text/formatted/box.rb +134 -100
  50. data/lib/prawn/text/formatted/fragment.rb +11 -14
  51. data/lib/prawn/text/formatted/line_wrap.rb +122 -63
  52. data/lib/prawn/text/formatted/parser.rb +139 -117
  53. data/lib/prawn/text/formatted/wrap.rb +43 -31
  54. data/lib/prawn/transformation_stack.rb +7 -5
  55. data/lib/prawn/utilities.rb +7 -22
  56. data/lib/prawn/version.rb +2 -2
  57. data/lib/prawn/view.rb +17 -7
  58. data/manual/basic_concepts/adding_pages.rb +6 -7
  59. data/manual/basic_concepts/basic_concepts.rb +31 -22
  60. data/manual/basic_concepts/creation.rb +10 -11
  61. data/manual/basic_concepts/cursor.rb +4 -5
  62. data/manual/basic_concepts/measurement.rb +6 -7
  63. data/manual/basic_concepts/origin.rb +5 -6
  64. data/manual/basic_concepts/other_cursor_helpers.rb +11 -12
  65. data/manual/basic_concepts/view.rb +22 -16
  66. data/manual/bounding_box/bounding_box.rb +29 -24
  67. data/manual/bounding_box/bounds.rb +11 -12
  68. data/manual/bounding_box/canvas.rb +4 -5
  69. data/manual/bounding_box/creation.rb +6 -7
  70. data/manual/bounding_box/indentation.rb +14 -15
  71. data/manual/bounding_box/nesting.rb +24 -17
  72. data/manual/bounding_box/russian_boxes.rb +14 -13
  73. data/manual/bounding_box/stretchy.rb +12 -13
  74. data/manual/contents.rb +28 -22
  75. data/manual/cover.rb +33 -28
  76. data/manual/document_and_page_options/background.rb +11 -13
  77. data/manual/document_and_page_options/document_and_page_options.rb +25 -20
  78. data/manual/document_and_page_options/metadata.rb +18 -16
  79. data/manual/document_and_page_options/page_margins.rb +18 -20
  80. data/manual/document_and_page_options/page_size.rb +13 -12
  81. data/manual/document_and_page_options/print_scaling.rb +17 -15
  82. data/manual/example_helper.rb +5 -4
  83. data/manual/graphics/blend_mode.rb +12 -9
  84. data/manual/graphics/circle_and_ellipse.rb +4 -5
  85. data/manual/graphics/color.rb +7 -9
  86. data/manual/graphics/common_lines.rb +7 -8
  87. data/manual/graphics/fill_and_stroke.rb +4 -5
  88. data/manual/graphics/fill_rules.rb +9 -10
  89. data/manual/graphics/gradients.rb +27 -21
  90. data/manual/graphics/graphics.rb +48 -40
  91. data/manual/graphics/helper.rb +12 -9
  92. data/manual/graphics/line_width.rb +8 -7
  93. data/manual/graphics/lines_and_curves.rb +7 -8
  94. data/manual/graphics/polygon.rb +6 -8
  95. data/manual/graphics/rectangle.rb +4 -5
  96. data/manual/graphics/rotate.rb +6 -7
  97. data/manual/graphics/scale.rb +14 -15
  98. data/manual/graphics/soft_masks.rb +3 -4
  99. data/manual/graphics/stroke_cap.rb +6 -7
  100. data/manual/graphics/stroke_dash.rb +11 -12
  101. data/manual/graphics/stroke_join.rb +5 -6
  102. data/manual/graphics/translate.rb +9 -10
  103. data/manual/graphics/transparency.rb +7 -8
  104. data/manual/how_to_read_this_manual.rb +6 -6
  105. data/manual/images/absolute_position.rb +6 -7
  106. data/manual/images/fit.rb +7 -8
  107. data/manual/images/horizontal.rb +9 -10
  108. data/manual/images/images.rb +28 -24
  109. data/manual/images/plain_image.rb +5 -6
  110. data/manual/images/scale.rb +9 -10
  111. data/manual/images/vertical.rb +13 -14
  112. data/manual/images/width_and_height.rb +10 -11
  113. data/manual/layout/boxes.rb +5 -6
  114. data/manual/layout/content.rb +7 -8
  115. data/manual/layout/layout.rb +18 -16
  116. data/manual/layout/simple_grid.rb +6 -7
  117. data/manual/outline/add_subsection_to.rb +20 -21
  118. data/manual/outline/insert_section_after.rb +15 -16
  119. data/manual/outline/outline.rb +21 -17
  120. data/manual/outline/sections_and_pages.rb +17 -18
  121. data/manual/repeatable_content/alternate_page_numbering.rb +21 -17
  122. data/manual/repeatable_content/page_numbering.rb +17 -16
  123. data/manual/repeatable_content/repeatable_content.rb +25 -19
  124. data/manual/repeatable_content/repeater.rb +14 -15
  125. data/manual/repeatable_content/stamp.rb +14 -15
  126. data/manual/security/encryption.rb +9 -10
  127. data/manual/security/permissions.rb +19 -14
  128. data/manual/security/security.rb +19 -16
  129. data/manual/table.rb +3 -3
  130. data/manual/text/alignment.rb +16 -17
  131. data/manual/text/color.rb +12 -11
  132. data/manual/text/column_box.rb +9 -10
  133. data/manual/text/fallback_fonts.rb +25 -21
  134. data/manual/text/font.rb +11 -12
  135. data/manual/text/font_size.rb +13 -14
  136. data/manual/text/font_style.rb +7 -8
  137. data/manual/text/formatted_callbacks.rb +25 -21
  138. data/manual/text/formatted_text.rb +33 -25
  139. data/manual/text/free_flowing_text.rb +20 -21
  140. data/manual/text/inline.rb +18 -19
  141. data/manual/text/kerning_and_character_spacing.rb +14 -15
  142. data/manual/text/leading.rb +7 -8
  143. data/manual/text/line_wrapping.rb +37 -18
  144. data/manual/text/paragraph_indentation.rb +13 -14
  145. data/manual/text/positioned_text.rb +15 -16
  146. data/manual/text/registering_families.rb +20 -21
  147. data/manual/text/rendering_and_color.rb +9 -10
  148. data/manual/text/right_to_left_text.rb +26 -19
  149. data/manual/text/rotation.rb +28 -23
  150. data/manual/text/single_usage.rb +8 -9
  151. data/manual/text/text.rb +57 -52
  152. data/manual/text/text_box_excess.rb +20 -17
  153. data/manual/text/text_box_extensions.rb +18 -15
  154. data/manual/text/text_box_overflow.rb +18 -19
  155. data/manual/text/utf8.rb +11 -12
  156. data/manual/text/win_ansi_charset.rb +21 -19
  157. data/prawn.gemspec +44 -31
  158. data/spec/extensions/encoding_helpers.rb +3 -3
  159. data/spec/prawn/document/bounding_box_spec.rb +546 -0
  160. data/spec/prawn/document/column_box_spec.rb +75 -0
  161. data/spec/prawn/document/security_spec.rb +176 -0
  162. data/spec/prawn/document_annotations_spec.rb +76 -0
  163. data/spec/prawn/document_destinations_spec.rb +15 -0
  164. data/spec/prawn/document_grid_spec.rb +99 -0
  165. data/spec/prawn/document_reference_spec.rb +27 -0
  166. data/spec/prawn/document_span_spec.rb +36 -0
  167. data/spec/prawn/document_spec.rb +802 -0
  168. data/spec/prawn/font_metric_cache_spec.rb +54 -0
  169. data/spec/prawn/font_spec.rb +542 -0
  170. data/spec/prawn/graphics/blend_mode_spec.rb +63 -0
  171. data/spec/prawn/graphics/transparency_spec.rb +81 -0
  172. data/spec/prawn/graphics_spec.rb +837 -0
  173. data/spec/prawn/graphics_stroke_styles_spec.rb +229 -0
  174. data/spec/{image_handler_spec.rb → prawn/image_handler_spec.rb} +14 -14
  175. data/spec/prawn/images/jpg_spec.rb +20 -0
  176. data/spec/prawn/images/png_spec.rb +283 -0
  177. data/spec/prawn/images_spec.rb +224 -0
  178. data/spec/prawn/measurements_extensions_spec.rb +24 -0
  179. data/spec/prawn/outline_spec.rb +412 -0
  180. data/spec/prawn/repeater_spec.rb +165 -0
  181. data/spec/prawn/soft_mask_spec.rb +74 -0
  182. data/spec/prawn/stamp_spec.rb +172 -0
  183. data/spec/prawn/text/box_spec.rb +1112 -0
  184. data/spec/prawn/text/formatted/arranger_spec.rb +466 -0
  185. data/spec/prawn/text/formatted/box_spec.rb +846 -0
  186. data/spec/prawn/text/formatted/fragment_spec.rb +343 -0
  187. data/spec/prawn/text/formatted/line_wrap_spec.rb +494 -0
  188. data/spec/prawn/text/formatted/parser_spec.rb +697 -0
  189. data/spec/prawn/text_draw_text_spec.rb +149 -0
  190. data/spec/prawn/text_rendering_mode_spec.rb +48 -0
  191. data/spec/prawn/text_spacing_spec.rb +95 -0
  192. data/spec/prawn/text_spec.rb +603 -0
  193. data/spec/prawn/text_with_inline_formatting_spec.rb +35 -0
  194. data/spec/{transformation_stack_spec.rb → prawn/transformation_stack_spec.rb} +22 -19
  195. data/spec/prawn/view_spec.rb +63 -0
  196. data/spec/prawn_manual_spec.rb +35 -0
  197. data/spec/spec_helper.rb +18 -19
  198. metadata +144 -180
  199. metadata.gz.sig +4 -0
  200. data/data/images/16bit.alpha +0 -0
  201. data/data/images/16bit.color +0 -0
  202. data/data/images/16bit.png +0 -0
  203. data/data/images/arrow.png +0 -0
  204. data/data/images/arrow2.png +0 -0
  205. data/data/images/blend_modes_bottom_layer.jpg +0 -0
  206. data/data/images/blend_modes_top_layer.jpg +0 -0
  207. data/data/images/dice.alpha +0 -0
  208. data/data/images/dice.color +0 -0
  209. data/data/images/dice.png +0 -0
  210. data/data/images/dice_interlaced.png +0 -0
  211. data/data/images/fractal.jpg +0 -0
  212. data/data/images/indexed_color.dat +0 -0
  213. data/data/images/indexed_color.png +0 -0
  214. data/data/images/indexed_transparency.png +0 -0
  215. data/data/images/indexed_transparency_alpha.dat +0 -0
  216. data/data/images/indexed_transparency_color.dat +0 -0
  217. data/data/images/letterhead.jpg +0 -0
  218. data/data/images/license.md +0 -8
  219. data/data/images/page_white_text.alpha +0 -0
  220. data/data/images/page_white_text.color +0 -0
  221. data/data/images/page_white_text.png +0 -0
  222. data/data/images/pigs.jpg +0 -0
  223. data/data/images/prawn.png +0 -0
  224. data/data/images/ruport.png +0 -0
  225. data/data/images/ruport_data.dat +0 -0
  226. data/data/images/ruport_transparent.png +0 -0
  227. data/data/images/ruport_type0.png +0 -0
  228. data/data/images/stef.jpg +0 -0
  229. data/data/images/tru256.bmp +0 -0
  230. data/data/images/web-links.dat +0 -1
  231. data/data/images/web-links.png +0 -0
  232. data/data/pdfs/complex_template.pdf +0 -0
  233. data/data/pdfs/contains_ttf_font.pdf +0 -0
  234. data/data/pdfs/encrypted.pdf +0 -0
  235. data/data/pdfs/form.pdf +1 -819
  236. data/data/pdfs/hexagon.pdf +0 -61
  237. data/data/pdfs/indirect_reference.pdf +0 -86
  238. data/data/pdfs/multipage_template.pdf +0 -127
  239. data/data/pdfs/nested_pages.pdf +0 -118
  240. data/data/pdfs/page_without_mediabox.pdf +0 -193
  241. data/data/pdfs/resources_as_indirect_object.pdf +0 -83
  242. data/data/pdfs/two_hexagons.pdf +0 -90
  243. data/data/pdfs/version_1_6.pdf +0 -61
  244. data/data/shift_jis_text.txt +0 -1
  245. data/spec/acceptance/png_spec.rb +0 -35
  246. data/spec/annotations_spec.rb +0 -67
  247. data/spec/blend_mode_spec.rb +0 -71
  248. data/spec/bounding_box_spec.rb +0 -501
  249. data/spec/column_box_spec.rb +0 -59
  250. data/spec/destinations_spec.rb +0 -13
  251. data/spec/document_spec.rb +0 -738
  252. data/spec/font_metric_cache_spec.rb +0 -52
  253. data/spec/font_spec.rb +0 -475
  254. data/spec/formatted_text_arranger_spec.rb +0 -452
  255. data/spec/formatted_text_box_spec.rb +0 -716
  256. data/spec/formatted_text_fragment_spec.rb +0 -299
  257. data/spec/graphics_spec.rb +0 -705
  258. data/spec/grid_spec.rb +0 -95
  259. data/spec/images_spec.rb +0 -167
  260. data/spec/inline_formatted_text_parser_spec.rb +0 -568
  261. data/spec/jpg_spec.rb +0 -23
  262. data/spec/line_wrap_spec.rb +0 -366
  263. data/spec/measurement_units_spec.rb +0 -22
  264. data/spec/outline_spec.rb +0 -409
  265. data/spec/png_spec.rb +0 -257
  266. data/spec/reference_spec.rb +0 -25
  267. data/spec/repeater_spec.rb +0 -154
  268. data/spec/security_spec.rb +0 -151
  269. data/spec/soft_mask_spec.rb +0 -78
  270. data/spec/span_spec.rb +0 -43
  271. data/spec/stamp_spec.rb +0 -179
  272. data/spec/stroke_styles_spec.rb +0 -208
  273. data/spec/text_at_spec.rb +0 -142
  274. data/spec/text_box_spec.rb +0 -1042
  275. data/spec/text_rendering_mode_spec.rb +0 -45
  276. data/spec/text_spacing_spec.rb +0 -93
  277. data/spec/text_spec.rb +0 -543
  278. data/spec/text_with_inline_formatting_spec.rb +0 -35
  279. data/spec/transparency_spec.rb +0 -91
  280. data/spec/view_spec.rb +0 -42
@@ -1,4 +1,4 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  # text/formatted/fragment.rb : Implements information about a formatted fragment
4
4
  #
@@ -24,14 +24,14 @@ module Prawn
24
24
  @document = document
25
25
  @word_spacing = 0
26
26
 
27
- # keep the original value of "text", so we can reinitialize @text if formatting parameters
28
- # like text direction are changed
27
+ # keep the original value of "text", so we can reinitialize @text if
28
+ # formatting parameters like text direction are changed
29
29
  @original_text = text
30
30
  @text = process_text(@original_text)
31
31
  end
32
32
 
33
33
  def width
34
- if @word_spacing == 0 then @width
34
+ if @word_spacing.zero? then @width
35
35
  else @width + @word_spacing * space_count
36
36
  end
37
37
  end
@@ -128,7 +128,7 @@ module Prawn
128
128
  end
129
129
 
130
130
  def space_count
131
- @text.count(" ")
131
+ @text.count(' ')
132
132
  end
133
133
 
134
134
  def callback_objects
@@ -213,14 +213,11 @@ module Prawn
213
213
  if soft_hyphens_need_processing?(string)
214
214
  string = process_soft_hyphens(string[0..-2]) + string[-1..-1]
215
215
  end
216
- else
217
- if soft_hyphens_need_processing?(string)
218
- string = process_soft_hyphens(string)
219
- end
216
+ elsif soft_hyphens_need_processing?(string)
217
+ string = process_soft_hyphens(string)
220
218
  end
221
219
 
222
- case direction
223
- when :rtl
220
+ if direction == :rtl
224
221
  string.reverse
225
222
  else
226
223
  string
@@ -232,7 +229,7 @@ module Prawn
232
229
  end
233
230
 
234
231
  def soft_hyphens_need_processing?(string)
235
- string.length > 0 && normalized_soft_hyphen
232
+ !string.empty? && normalized_soft_hyphen
236
233
  end
237
234
 
238
235
  def normalized_soft_hyphen
@@ -244,12 +241,12 @@ module Prawn
244
241
  string.force_encoding(normalized_soft_hyphen.encoding)
245
242
  end
246
243
 
247
- string.gsub(normalized_soft_hyphen, "")
244
+ string.gsub(normalized_soft_hyphen, '')
248
245
  end
249
246
 
250
247
  def strip_zero_width_spaces(string)
251
248
  if string.encoding == ::Encoding::UTF_8
252
- string.gsub(Prawn::Text::ZWSP, "")
249
+ string.gsub(Prawn::Text::ZWSP, '')
253
250
  else
254
251
  string
255
252
  end
@@ -1,4 +1,4 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  # core/text/formatted/line_wrap.rb : Implements individual line wrapping of
4
4
  # formatted text
@@ -21,16 +21,14 @@ module Prawn
21
21
 
22
22
  # The number of spaces in the last wrapped line
23
23
  attr_reader :space_count
24
- attr_reader :soft_hyphen
25
- attr_reader :zero_width_space
26
24
 
27
25
  # Whether this line is the last line in the paragraph
28
26
  def paragraph_finished?
29
- @newline_encountered || is_next_string_newline? || @arranger.finished?
27
+ @newline_encountered || next_string_newline? || @arranger.finished?
30
28
  end
31
29
 
32
30
  def tokenize(fragment)
33
- fragment.scan(scan_pattern)
31
+ fragment.scan(scan_pattern(fragment.encoding))
34
32
  end
35
33
 
36
34
  # Work in conjunction with the PDF::Formatted::Arranger
@@ -40,8 +38,10 @@ module Prawn
40
38
  def wrap_line(options)
41
39
  initialize_line(options)
42
40
 
41
+ # rubocop: disable Lint/AssignmentInCondition
43
42
  while fragment = @arranger.next_string
44
- @fragment_output = ""
43
+ # rubocop: enable Lint/AssignmentInCondition
44
+ @fragment_output = +''
45
45
 
46
46
  fragment.lstrip! if first_fragment_on_this_line?(fragment)
47
47
  next if empty_line?(fragment)
@@ -63,21 +63,20 @@ module Prawn
63
63
  end
64
64
 
65
65
  def empty_line?(fragment)
66
- empty = line_empty? && fragment.empty? && is_next_string_newline?
67
- @arranger.update_last_string("", "", soft_hyphen) if empty
66
+ empty = line_empty? && fragment.empty? && next_string_newline?
67
+ if empty
68
+ @arranger.update_last_string('', '', soft_hyphen(fragment.encoding))
69
+ end
68
70
  empty
69
71
  end
70
72
 
71
- def is_next_string_newline?
73
+ def next_string_newline?
72
74
  @arranger.preview_next_string == "\n"
73
75
  end
74
76
 
75
77
  def apply_font_settings_and_add_fragment_to_line(fragment)
76
78
  result = nil
77
79
  @arranger.apply_font_settings do
78
- # if font has changed from Unicode to non-Unicode, or vice versa, the characters used for soft hyphens
79
- # and zero-width spaces will be different
80
- set_soft_hyphen_and_zero_width_space
81
80
  result = add_fragment_to_line(fragment)
82
81
  end
83
82
  result
@@ -87,27 +86,31 @@ module Prawn
87
86
  # the line
88
87
  #
89
88
  def add_fragment_to_line(fragment)
90
- if fragment == ""
89
+ if fragment == ''
91
90
  true
92
91
  elsif fragment == "\n"
93
92
  @newline_encountered = true
94
93
  false
95
94
  else
96
95
  tokenize(fragment).each do |segment|
97
- if segment == zero_width_space
98
- segment_width = 0
99
- else
100
- segment_width = @document.width_of(segment, :kerning => @kerning)
101
- end
96
+ segment_width = if segment == zero_width_space(segment.encoding)
97
+ 0
98
+ else
99
+ @document.width_of(segment, kerning: @kerning)
100
+ end
102
101
 
103
102
  if @accumulated_width + segment_width <= @width
104
103
  @accumulated_width += segment_width
105
- if segment[-1] == soft_hyphen
106
- sh_width = @document.width_of("#{soft_hyphen}", :kerning => @kerning)
104
+ shy = soft_hyphen(segment.encoding)
105
+ if segment[-1] == shy
106
+ sh_width = @document.width_of(shy, kerning: @kerning)
107
107
  @accumulated_width -= sh_width
108
108
  end
109
109
  @fragment_output += segment
110
110
  else
111
+ if @accumulated_width.zero? && @line_contains_more_than_one_word
112
+ @line_contains_more_than_one_word = false
113
+ end
111
114
  end_of_the_line_reached(segment)
112
115
  fragment_finished(fragment)
113
116
  return false
@@ -121,13 +124,24 @@ module Prawn
121
124
 
122
125
  # The pattern used to determine chunks of text to place on a given line
123
126
  #
124
- def scan_pattern
125
- pattern = "[^#{break_chars}]+#{soft_hyphen}|" \
126
- "[^#{break_chars}]+#{hyphen}+|" \
127
- "[^#{break_chars}]+|" \
128
- "[#{whitespace}]+|" \
129
- "#{hyphen}+[^#{break_chars}]*|" \
130
- "#{soft_hyphen}"
127
+ def scan_pattern(encoding = ::Encoding::UTF_8)
128
+ ebc = break_chars(encoding)
129
+ eshy = soft_hyphen(encoding)
130
+ ehy = hyphen(encoding)
131
+ ews = whitespace(encoding)
132
+
133
+ patterns = [
134
+ "[^#{ebc}]+#{eshy}",
135
+ "[^#{ebc}]+#{ehy}+",
136
+ "[^#{ebc}]+",
137
+ "[#{ews}]+",
138
+ "#{ehy}+[^#{ebc}]*",
139
+ eshy.to_s
140
+ ]
141
+
142
+ pattern = patterns
143
+ .map { |p| p.encode(encoding) }
144
+ .join('|')
131
145
 
132
146
  Regexp.new(pattern)
133
147
  end
@@ -136,24 +150,56 @@ module Prawn
136
150
  # current line, which in turn determines whether character level
137
151
  # word breaking is needed
138
152
  #
139
- def word_division_scan_pattern
140
- Regexp.new("\\s|[#{zero_width_space}#{soft_hyphen}#{hyphen}]")
153
+ def word_division_scan_pattern(encoding = ::Encoding::UTF_8)
154
+ common_whitespaces = ["\t", "\n", "\v", "\r", ' '].map do |c|
155
+ c.encode(encoding)
156
+ end
157
+
158
+ Regexp.union(
159
+ common_whitespaces +
160
+ [
161
+ zero_width_space(encoding),
162
+ soft_hyphen(encoding),
163
+ hyphen(encoding)
164
+ ].compact
165
+ )
166
+ end
167
+
168
+ def soft_hyphen(encoding = ::Encoding::UTF_8)
169
+ Prawn::Text::SHY.encode(encoding)
170
+ rescue ::Encoding::InvalidByteSequenceError,
171
+ ::Encoding::UndefinedConversionError
172
+ nil
141
173
  end
142
174
 
143
- def break_chars
144
- "#{whitespace}#{soft_hyphen}#{hyphen}"
175
+ def break_chars(encoding = ::Encoding::UTF_8)
176
+ [
177
+ whitespace(encoding),
178
+ soft_hyphen(encoding),
179
+ hyphen(encoding)
180
+ ].join('')
145
181
  end
146
182
 
147
- def whitespace
148
- " \\t#{zero_width_space}"
183
+ def zero_width_space(encoding = ::Encoding::UTF_8)
184
+ Prawn::Text::ZWSP.encode(encoding)
185
+ rescue ::Encoding::InvalidByteSequenceError,
186
+ ::Encoding::UndefinedConversionError
187
+ nil
149
188
  end
150
189
 
151
- def hyphen
152
- "-"
190
+ def whitespace(encoding = ::Encoding::UTF_8)
191
+ "\s\t#{zero_width_space(encoding)}".encode(encoding)
192
+ end
193
+
194
+ def hyphen(_encoding = ::Encoding::UTF_8)
195
+ '-'
196
+ rescue ::Encoding::InvalidByteSequenceError,
197
+ ::Encoding::UndefinedConversionError
198
+ nil
153
199
  end
154
200
 
155
201
  def line_empty?
156
- @line_empty && @accumulated_width == 0
202
+ @line_empty && @accumulated_width.zero?
157
203
  end
158
204
 
159
205
  def initialize_line(options)
@@ -174,35 +220,43 @@ module Prawn
174
220
  @line_full = false
175
221
  end
176
222
 
177
- def set_soft_hyphen_and_zero_width_space
178
- # this is done once per fragment, after the font settings for the fragment are applied --
179
- # it could actually be skipped if the font hasn't changed
180
- font = @document.font
181
- @soft_hyphen = font.normalize_encoding(Prawn::Text::SHY)
182
- @zero_width_space = font.unicode? ? Prawn::Text::ZWSP : ""
183
- end
184
-
185
223
  def fragment_finished(fragment)
186
224
  if fragment == "\n"
187
225
  @newline_encountered = true
188
226
  @line_empty = false
189
227
  else
190
- update_output_based_on_last_fragment(fragment, soft_hyphen)
228
+ update_output_based_on_last_fragment(
229
+ fragment,
230
+ soft_hyphen(fragment.encoding)
231
+ )
191
232
  update_line_status_based_on_last_output
192
- determine_whether_to_pull_preceding_fragment_to_join_this_one(fragment)
233
+ pull_preceding_fragment_to_join_this_one?(fragment)
193
234
  end
194
235
  remember_this_fragment_for_backward_looking_ops
195
236
  end
196
237
 
197
- def update_output_based_on_last_fragment(fragment, normalized_soft_hyphen = nil)
198
- remaining_text = fragment.slice(@fragment_output.length..fragment.length)
199
- fail Prawn::Errors::CannotFit if line_finished? && line_empty? && @fragment_output.empty? && !fragment.strip.empty?
200
- @arranger.update_last_string(@fragment_output, remaining_text, normalized_soft_hyphen)
238
+ def update_output_based_on_last_fragment(
239
+ fragment, normalized_soft_hyphen = nil
240
+ )
241
+ remaining_text =
242
+ fragment.slice(@fragment_output.length..fragment.length)
243
+ if line_finished? && line_empty? && @fragment_output.empty? &&
244
+ !fragment.strip.empty?
245
+ raise Prawn::Errors::CannotFit
246
+ end
247
+
248
+ @arranger.update_last_string(
249
+ @fragment_output,
250
+ remaining_text,
251
+ normalized_soft_hyphen
252
+ )
201
253
  end
202
254
 
203
- def determine_whether_to_pull_preceding_fragment_to_join_this_one(current_fragment)
204
- if @fragment_output.empty? && !current_fragment.empty? && @line_contains_more_than_one_word
205
- unless previous_fragment_ended_with_breakable? || fragment_begins_with_breakable?(current_fragment)
255
+ def pull_preceding_fragment_to_join_this_one?(current_fragment)
256
+ if @fragment_output.empty? && !current_fragment.empty? &&
257
+ @line_contains_more_than_one_word
258
+ unless previous_fragment_ended_with_breakable? ||
259
+ fragment_begins_with_breakable?(current_fragment)
206
260
  @fragment_output = @previous_fragment_output_without_last_word
207
261
  update_output_based_on_last_fragment(@previous_fragment)
208
262
  end
@@ -212,10 +266,12 @@ module Prawn
212
266
  def remember_this_fragment_for_backward_looking_ops
213
267
  @previous_fragment = @fragment_output.dup
214
268
  pf = @previous_fragment
215
- @previous_fragment_ended_with_breakable = pf =~ /[#{break_chars}]$/
216
- last_word = pf.slice(/[^#{break_chars}]*$/)
269
+ @previous_fragment_ended_with_breakable =
270
+ pf =~ /[#{break_chars(pf.encoding)}]$/
271
+ last_word = pf.slice(/[^#{break_chars(pf.encoding)}]*$/)
217
272
  last_word_length = last_word.nil? ? 0 : last_word.length
218
- @previous_fragment_output_without_last_word = pf.slice(0, pf.length - last_word_length)
273
+ @previous_fragment_output_without_last_word =
274
+ pf.slice(0, pf.length - last_word_length)
219
275
  end
220
276
 
221
277
  def previous_fragment_ended_with_breakable?
@@ -223,7 +279,7 @@ module Prawn
223
279
  end
224
280
 
225
281
  def fragment_begins_with_breakable?(fragment)
226
- fragment =~ /^[#{break_chars}]/
282
+ fragment =~ /^[#{break_chars(fragment.encoding)}]/
227
283
  end
228
284
 
229
285
  def line_finished?
@@ -231,25 +287,28 @@ module Prawn
231
287
  end
232
288
 
233
289
  def update_line_status_based_on_last_output
234
- @line_contains_more_than_one_word = true if @fragment_output =~ word_division_scan_pattern
290
+ if @fragment_output&.match?(word_division_scan_pattern(@fragment_output.encoding))
291
+ @line_contains_more_than_one_word = true
292
+ end
235
293
  end
236
294
 
237
295
  def end_of_the_line_reached(segment)
238
296
  update_line_status_based_on_last_output
239
- wrap_by_char(segment) unless @disable_wrap_by_char || @line_contains_more_than_one_word
297
+ unless @disable_wrap_by_char || @line_contains_more_than_one_word
298
+ wrap_by_char(segment)
299
+ end
240
300
  @line_full = true
241
301
  end
242
302
 
243
303
  def wrap_by_char(segment)
244
- font = @document.font
245
304
  segment.each_char do |char|
246
- break unless append_char(char, font)
305
+ break unless append_char(char)
247
306
  end
248
307
  end
249
308
 
250
- def append_char(char, font)
309
+ def append_char(char)
251
310
  # kerning doesn't make sense in the context of a single character
252
- char_width = font.compute_width_of(char)
311
+ char_width = @document.width_of(char)
253
312
 
254
313
  if @accumulated_width + char_width <= @width
255
314
  @accumulated_width += char_width
@@ -1,4 +1,4 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  # text/formatted/parser.rb : Implements a bi-directional parser between a subset
4
4
  # of html and formatted text arrays
@@ -16,82 +16,84 @@ module Prawn
16
16
 
17
17
  PARSER_REGEX = begin
18
18
  regex_string = "\n|" \
19
- "<b>|</b>|" \
20
- "<i>|</i>|" \
21
- "<u>|</u>|" \
22
- "<strikethrough>|</strikethrough>|" \
23
- "<sub>|</sub>|" \
24
- "<sup>|</sup>|" \
25
- "<link[^>]*>|</link>|" \
26
- "<color[^>]*>|</color>|" \
27
- "<font[^>]*>|</font>|" \
28
- "<strong>|</strong>|" \
29
- "<em>|</em>|" \
30
- "<a[^>]*>|</a>|" \
19
+ '<b>|</b>|' \
20
+ '<i>|</i>|' \
21
+ '<u>|</u>|' \
22
+ '<strikethrough>|</strikethrough>|' \
23
+ '<sub>|</sub>|' \
24
+ '<sup>|</sup>|' \
25
+ '<link[^>]*>|</link>|' \
26
+ '<color[^>]*>|</color>|' \
27
+ '<font[^>]*>|</font>|' \
28
+ '<strong>|</strong>|' \
29
+ '<em>|</em>|' \
30
+ '<a[^>]*>|</a>|' \
31
31
  "[^<\n]+"
32
32
  Regexp.new(regex_string, Regexp::MULTILINE)
33
33
  end
34
34
 
35
- def self.format(string, *args)
36
- tokens = string.gsub(/<br\s*\/?>/, "\n").scan(PARSER_REGEX)
37
- self.array_from_tokens(tokens)
35
+ def self.format(string, *_args)
36
+ tokens = string.gsub(%r{<br\s*/?>}, "\n").scan(PARSER_REGEX)
37
+ array_from_tokens(tokens)
38
38
  end
39
39
 
40
40
  def self.to_string(array)
41
- prefixes = { :bold => "<b>",
42
- :italic => "<i>",
43
- :underline => "<u>",
44
- :strikethrough => "<strikethrough>",
45
- :subscript => "<sub>",
46
- :superscript => "<sup>" }
47
- suffixes = { :bold => "</b>",
48
- :italic => "</i>",
49
- :underline => "</u>",
50
- :strikethrough => "</strikethrough>",
51
- :subscript => "</sub>",
52
- :superscript => "</sup>" }
41
+ prefixes = {
42
+ bold: '<b>',
43
+ italic: '<i>',
44
+ underline: '<u>',
45
+ strikethrough: '<strikethrough>',
46
+ subscript: '<sub>',
47
+ superscript: '<sup>'
48
+ }
49
+ suffixes = {
50
+ bold: '</b>',
51
+ italic: '</i>',
52
+ underline: '</u>',
53
+ strikethrough: '</strikethrough>',
54
+ subscript: '</sub>',
55
+ superscript: '</sup>'
56
+ }
53
57
  array.collect do |hash|
54
- prefix = ""
55
- suffix = ""
56
- if hash[:styles]
57
- hash[:styles].each do |style|
58
- prefix = prefix + prefixes[style]
59
- suffix = suffixes[style] + suffix
60
- end
58
+ prefix = ''
59
+ suffix = ''
60
+ hash[:styles]&.each do |style|
61
+ prefix += prefixes[style]
62
+ suffix = suffixes[style] + suffix
61
63
  end
62
64
 
63
65
  font = hash[:font] ? " name='#{hash[:font]}'" : nil
64
66
  size = hash[:size] ? " size='#{hash[:size]}'" : nil
65
- if hash[:character_spacing]
66
- character_spacing = " character_spacing='#{hash[:character_spacing]}'"
67
- else
68
- character_spacing = nil
69
- end
67
+ character_spacing =
68
+ if hash[:character_spacing]
69
+ " character_spacing='#{hash[:character_spacing]}'"
70
+ end
70
71
  if font || size || character_spacing
71
- prefix = prefix + "<font#{font}#{size}#{character_spacing}>"
72
- suffix = "</font>"
72
+ prefix += "<font#{font}#{size}#{character_spacing}>"
73
+ suffix = '</font>'
73
74
  end
74
75
 
75
76
  link = hash[:link] ? " href='#{hash[:link]}'" : nil
76
77
  anchor = hash[:anchor] ? " anchor='#{hash[:anchor]}'" : nil
77
78
  if link || anchor
78
- prefix = prefix + "<link#{link}#{anchor}>"
79
- suffix = "</link>"
79
+ prefix += "<link#{link}#{anchor}>"
80
+ suffix = '</link>'
80
81
  end
81
82
 
82
83
  if hash[:color]
83
- if hash[:color].kind_of?(Array)
84
- prefix = prefix + "<color c='#{hash[:color][0]}'" \
85
- " m='#{hash[:color][1]}'" \
86
- " y='#{hash[:color][2]}'" \
87
- " k='#{hash[:color][3]}'>"
88
- else
89
- prefix = prefix + "<color rgb='#{hash[:color]}'>"
90
- end
91
- suffix = "</color>"
84
+ prefix += if hash[:color].is_a?(Array)
85
+ "<color c='#{hash[:color][0]}'" \
86
+ " m='#{hash[:color][1]}'" \
87
+ " y='#{hash[:color][2]}'" \
88
+ " k='#{hash[:color][3]}'>"
89
+ else
90
+ "<color rgb='#{hash[:color]}'>"
91
+ end
92
+ suffix = '</color>'
92
93
  end
93
94
 
94
- string = hash[:text].gsub("&", "&amp;").gsub(">", "&gt;").gsub("<", "&lt;")
95
+ string = hash[:text].gsub('&', '&amp;').gsub('>', '&gt;')
96
+ .gsub('<', '&lt;')
95
97
  prefix + string + suffix
96
98
  end.join
97
99
  end
@@ -104,11 +106,13 @@ module Prawn
104
106
  array.each do |hash|
105
107
  hash[:text].scan(scan_pattern).each do |string|
106
108
  if string == "\n"
107
- paragraph << hash.dup.merge(:text => "\n") if previous_string == "\n"
109
+ if previous_string == "\n"
110
+ paragraph << hash.dup.merge(text: "\n")
111
+ end
108
112
  paragraphs << paragraph unless paragraph.empty?
109
113
  paragraph = []
110
114
  else
111
- paragraph << hash.dup.merge(:text => string)
115
+ paragraph << hash.dup.merge(text: string)
112
116
  end
113
117
  previous_string = string
114
118
  end
@@ -128,88 +132,106 @@ module Prawn
128
132
  sizes = []
129
133
  character_spacings = []
130
134
 
131
- while token = tokens.shift
135
+ tokens.each do |token|
132
136
  case token
133
- when "<b>", "<strong>"
137
+ when '<b>', '<strong>'
134
138
  styles << :bold
135
- when "<i>", "<em>"
139
+ when '<i>', '<em>'
136
140
  styles << :italic
137
- when "<u>"
141
+ when '<u>'
138
142
  styles << :underline
139
- when "<strikethrough>"
143
+ when '<strikethrough>'
140
144
  styles << :strikethrough
141
- when "<sub>"
145
+ when '<sub>'
142
146
  styles << :subscript
143
- when "<sup>"
147
+ when '<sup>'
144
148
  styles << :superscript
145
- when "</b>", "</strong>"
149
+ when '</b>', '</strong>'
146
150
  styles.delete(:bold)
147
- when "</i>", "</em>"
151
+ when '</i>', '</em>'
148
152
  styles.delete(:italic)
149
- when "</u>"
153
+ when '</u>'
150
154
  styles.delete(:underline)
151
- when "</strikethrough>"
155
+ when '</strikethrough>'
152
156
  styles.delete(:strikethrough)
153
- when "</sub>"
157
+ when '</sub>'
154
158
  styles.delete(:subscript)
155
- when "</sup>"
159
+ when '</sup>'
156
160
  styles.delete(:superscript)
157
- when "</link>", "</a>"
161
+ when '</link>', '</a>'
158
162
  link = nil
159
163
  anchor = nil
160
164
  local = nil
161
- when "</color>"
165
+ when '</color>'
162
166
  colors.pop
163
- when "</font>"
167
+ when '</font>'
164
168
  fonts.pop
165
169
  sizes.pop
166
170
  character_spacings.pop
171
+ when /^<link[^>]*>$/, /^<a[^>]*>$/
172
+ matches = /href="([^"]*)"/.match(token) ||
173
+ /href='([^']*)'/.match(token)
174
+ link = matches[1] unless matches.nil?
175
+
176
+ matches = /anchor="([^"]*)"/.match(token) ||
177
+ /anchor='([^']*)'/.match(token)
178
+ anchor = matches[1] unless matches.nil?
179
+
180
+ matches = /local="([^"]*)"/.match(token) ||
181
+ /local='([^']*)'/.match(token)
182
+ local = matches[1] unless matches.nil?
183
+ when /^<color[^>]*>$/
184
+ matches = /rgb="#?([^"]*)"/.match(token) ||
185
+ /rgb='#?([^']*)'/.match(token)
186
+ colors << matches[1] if matches
187
+
188
+ match = /c="#?([^"]*)"/.match(token) ||
189
+ /c='#?([^']*)'/.match(token)
190
+ c = match[1].to_i unless match.nil?
191
+ match = /m="#?([^"]*)"/.match(token) ||
192
+ /m='#?([^']*)'/.match(token)
193
+ m = match[1].to_i unless match.nil?
194
+ match = /y="#?([^"]*)"/.match(token) ||
195
+ /y='#?([^']*)'/.match(token)
196
+ y = match[1].to_i unless match.nil?
197
+ match = /k="#?([^"]*)"/.match(token) ||
198
+ /k='#?([^']*)'/.match(token)
199
+ k = match[1].to_i unless match.nil?
200
+ colors << [c, m, y, k] if [c, m, y, k].all?
201
+
202
+ # intend to support rgb="#ffffff" or rgb='#ffffff',
203
+ # r="255" g="255" b="255" or r='255' g='255' b='255',
204
+ # and c="100" m="100" y="100" k="100" or
205
+ # c='100' m='100' y='100' k='100'
206
+ # color = { :rgb => "#ffffff" }
207
+ # color = { :r => 255, :g => 255, :b => 255 }
208
+ # color = { :c => 100, :m => 100, :y => 100, :k => 100 }
209
+ when /^<font[^>]*>$/
210
+ matches = /name="([^"]*)"/.match(token) ||
211
+ /name='([^']*)'/.match(token)
212
+ fonts << matches[1] unless matches.nil?
213
+
214
+ matches = /size="([^"]*)"/.match(token) ||
215
+ /size='([^']*)'/.match(token)
216
+ sizes << matches[1].to_f unless matches.nil?
217
+
218
+ matches = /character_spacing="([^"]*)"/.match(token) ||
219
+ /character_spacing='([^']*)'/.match(token)
220
+ character_spacings << matches[1].to_f unless matches.nil?
167
221
  else
168
- if token =~ /^<link[^>]*>$/ or token =~ /^<a[^>]*>$/
169
- matches = /href="([^"]*)"/.match(token) || /href='([^']*)'/.match(token)
170
- link = matches[1] unless matches.nil?
171
-
172
- matches = /anchor="([^"]*)"/.match(token) || /anchor='([^']*)'/.match(token)
173
- anchor = matches[1] unless matches.nil?
174
-
175
- matches = /local="([^"]*)"/.match(token) || /local='([^']*)'/.match(token)
176
- local = matches[1] unless matches.nil?
177
- elsif token =~ /^<color[^>]*>$/
178
- matches = /rgb="#?([^"]*)"/.match(token) || /rgb='#?([^']*)'/.match(token)
179
- colors << matches[1] if matches
180
-
181
- matches = /c="#?([^"]*)" +m="#?([^"]*)" +y="#?([^"]*)" +k="#?([^"]*)"/.match(token) ||
182
- /c='#?([^']*)' +m='#?([^']*)' +y='#?([^']*)' +k='#?([^']*)'/.match(token)
183
- colors << [matches[1].to_i, matches[2].to_i, matches[3].to_i, matches[4].to_i] if matches
184
-
185
- # intend to support rgb="#ffffff" or rgb='#ffffff',
186
- # r="255" g="255" b="255" or r='255' g='255' b='255',
187
- # and c="100" m="100" y="100" k="100" or
188
- # c='100' m='100' y='100' k='100'
189
- # color = { :rgb => "#ffffff" }
190
- # color = { :r => 255, :g => 255, :b => 255 }
191
- # color = { :c => 100, :m => 100, :y => 100, :k => 100 }
192
- elsif token =~ /^<font[^>]*>$/
193
- matches = /name="([^"]*)"/.match(token) || /name='([^']*)'/.match(token)
194
- fonts << matches[1] unless matches.nil?
195
-
196
- matches = /size="([^"]*)"/.match(token) || /size='([^']*)'/.match(token)
197
- sizes << matches[1].to_f unless matches.nil?
198
-
199
- matches = /character_spacing="([^"]*)"/.match(token) || /character_spacing='([^']*)'/.match(token)
200
- character_spacings << matches[1].to_f unless matches.nil?
201
- else
202
- string = token.gsub("&lt;", "<").gsub("&gt;", ">").gsub("&amp;", "&")
203
- array << { :text => string,
204
- :styles => styles.dup,
205
- :color => colors.last,
206
- :local => local,
207
- :link => link,
208
- :anchor => anchor,
209
- :font => fonts.last,
210
- :size => sizes.last,
211
- :character_spacing => character_spacings.last }
212
- end
222
+ string = token.gsub('&lt;', '<').gsub('&gt;', '>')
223
+ .gsub('&amp;', '&')
224
+ array << {
225
+ text: string,
226
+ styles: styles.dup,
227
+ color: colors.last,
228
+ local: local,
229
+ link: link,
230
+ anchor: anchor,
231
+ font: fonts.last,
232
+ size: sizes.last,
233
+ character_spacing: character_spacings.last
234
+ }
213
235
  end
214
236
  end
215
237
  array