prawn 2.1.0 → 2.4.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 +9 -41
  7. data/lib/prawn.rb +37 -49
  8. data/lib/prawn/document.rb +193 -141
  9. data/lib/prawn/document/bounding_box.rb +50 -30
  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 +22 -16
  13. data/lib/prawn/encoding.rb +69 -68
  14. data/lib/prawn/errors.rb +12 -7
  15. data/lib/prawn/font.rb +104 -69
  16. data/lib/prawn/font_metric_cache.rb +20 -13
  17. data/lib/prawn/{font → fonts}/afm.rb +108 -72
  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 +126 -81
  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 +43 -39
  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 +204 -102
  29. data/lib/prawn/graphics/transformation.rb +15 -9
  30. data/lib/prawn/graphics/transparency.rb +17 -13
  31. data/lib/prawn/grid.rb +84 -48
  32. data/lib/prawn/image_handler.rb +5 -5
  33. data/lib/prawn/images.rb +60 -49
  34. data/lib/prawn/images/image.rb +2 -1
  35. data/lib/prawn/images/jpg.rb +31 -22
  36. data/lib/prawn/images/png.rb +67 -63
  37. data/lib/prawn/measurement_extensions.rb +10 -9
  38. data/lib/prawn/measurements.rb +19 -15
  39. data/lib/prawn/outline.rb +98 -77
  40. data/lib/prawn/repeater.rb +15 -11
  41. data/lib/prawn/security.rb +93 -70
  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 +76 -60
  46. data/lib/prawn/text/box.rb +18 -14
  47. data/lib/prawn/text/formatted.rb +5 -5
  48. data/lib/prawn/text/formatted/arranger.rb +80 -40
  49. data/lib/prawn/text/formatted/box.rb +140 -101
  50. data/lib/prawn/text/formatted/fragment.rb +11 -14
  51. data/lib/prawn/text/formatted/line_wrap.rb +128 -67
  52. data/lib/prawn/text/formatted/parser.rb +147 -123
  53. data/lib/prawn/text/formatted/wrap.rb +48 -32
  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 +7 -8
  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 +7 -8
  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 +25 -18
  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 +15 -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 +18 -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 +5 -6
  88. data/manual/graphics/fill_rules.rb +10 -10
  89. data/manual/graphics/gradients.rb +27 -21
  90. data/manual/graphics/graphics.rb +48 -40
  91. data/manual/graphics/helper.rb +19 -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 +15 -16
  101. data/manual/graphics/stroke_join.rb +5 -6
  102. data/manual/graphics/translate.rb +10 -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 +10 -11
  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 +16 -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 +21 -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 +10 -8
  137. data/manual/text/formatted_callbacks.rb +33 -24
  138. data/manual/text/formatted_text.rb +36 -25
  139. data/manual/text/free_flowing_text.rb +22 -23
  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 +12 -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 +33 -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 +20 -19
  155. data/manual/text/utf8.rb +11 -12
  156. data/manual/text/win_ansi_charset.rb +27 -25
  157. data/prawn.gemspec +41 -34
  158. data/spec/extensions/encoding_helpers.rb +3 -3
  159. data/spec/prawn/document/bounding_box_spec.rb +550 -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 +44 -0
  167. data/spec/prawn/document_spec.rb +805 -0
  168. data/spec/prawn/font_metric_cache_spec.rb +54 -0
  169. data/spec/prawn/font_spec.rb +544 -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 +872 -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 +229 -0
  178. data/spec/prawn/measurements_extensions_spec.rb +24 -0
  179. data/spec/prawn/outline_spec.rb +512 -0
  180. data/spec/prawn/repeater_spec.rb +166 -0
  181. data/spec/prawn/soft_mask_spec.rb +74 -0
  182. data/spec/prawn/stamp_spec.rb +173 -0
  183. data/spec/prawn/text/box_spec.rb +1110 -0
  184. data/spec/prawn/text/formatted/arranger_spec.rb +466 -0
  185. data/spec/prawn/text/formatted/box_spec.rb +849 -0
  186. data/spec/prawn/text/formatted/fragment_spec.rb +343 -0
  187. data/spec/prawn/text/formatted/line_wrap_spec.rb +495 -0
  188. data/spec/prawn/text/formatted/parser_spec.rb +697 -0
  189. data/spec/prawn/text_draw_text_spec.rb +150 -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 +102 -222
  199. metadata.gz.sig +0 -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,33 @@ module Prawn
87
86
  # the line
88
87
  #
89
88
  def add_fragment_to_line(fragment)
90
- if fragment == ""
89
+ case fragment
90
+ when ''
91
91
  true
92
- elsif fragment == "\n"
92
+ when "\n"
93
93
  @newline_encountered = true
94
94
  false
95
95
  else
96
96
  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
97
+ segment_width =
98
+ if segment == zero_width_space(segment.encoding)
99
+ 0
100
+ else
101
+ @document.width_of(segment, kerning: @kerning)
102
+ end
102
103
 
103
104
  if @accumulated_width + segment_width <= @width
104
105
  @accumulated_width += segment_width
105
- if segment[-1] == soft_hyphen
106
- sh_width = @document.width_of("#{soft_hyphen}", :kerning => @kerning)
106
+ shy = soft_hyphen(segment.encoding)
107
+ if segment[-1] == shy
108
+ sh_width = @document.width_of(shy, kerning: @kerning)
107
109
  @accumulated_width -= sh_width
108
110
  end
109
111
  @fragment_output += segment
110
112
  else
113
+ if @accumulated_width.zero? && @line_contains_more_than_one_word
114
+ @line_contains_more_than_one_word = false
115
+ end
111
116
  end_of_the_line_reached(segment)
112
117
  fragment_finished(fragment)
113
118
  return false
@@ -121,13 +126,24 @@ module Prawn
121
126
 
122
127
  # The pattern used to determine chunks of text to place on a given line
123
128
  #
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}"
129
+ def scan_pattern(encoding = ::Encoding::UTF_8)
130
+ ebc = break_chars(encoding)
131
+ eshy = soft_hyphen(encoding)
132
+ ehy = hyphen(encoding)
133
+ ews = whitespace(encoding)
134
+
135
+ patterns = [
136
+ "[^#{ebc}]+#{eshy}",
137
+ "[^#{ebc}]+#{ehy}+",
138
+ "[^#{ebc}]+",
139
+ "[#{ews}]+",
140
+ "#{ehy}+[^#{ebc}]*",
141
+ eshy.to_s
142
+ ]
143
+
144
+ pattern = patterns
145
+ .map { |p| p.encode(encoding) }
146
+ .join('|')
131
147
 
132
148
  Regexp.new(pattern)
133
149
  end
@@ -136,24 +152,57 @@ module Prawn
136
152
  # current line, which in turn determines whether character level
137
153
  # word breaking is needed
138
154
  #
139
- def word_division_scan_pattern
140
- Regexp.new("\\s|[#{zero_width_space}#{soft_hyphen}#{hyphen}]")
155
+ def word_division_scan_pattern(encoding = ::Encoding::UTF_8)
156
+ common_whitespaces =
157
+ ["\t", "\n", "\v", "\r", ' '].map do |c|
158
+ c.encode(encoding)
159
+ end
160
+
161
+ Regexp.union(
162
+ common_whitespaces +
163
+ [
164
+ zero_width_space(encoding),
165
+ soft_hyphen(encoding),
166
+ hyphen(encoding)
167
+ ].compact
168
+ )
169
+ end
170
+
171
+ def soft_hyphen(encoding = ::Encoding::UTF_8)
172
+ Prawn::Text::SHY.encode(encoding)
173
+ rescue ::Encoding::InvalidByteSequenceError,
174
+ ::Encoding::UndefinedConversionError
175
+ nil
141
176
  end
142
177
 
143
- def break_chars
144
- "#{whitespace}#{soft_hyphen}#{hyphen}"
178
+ def break_chars(encoding = ::Encoding::UTF_8)
179
+ [
180
+ whitespace(encoding),
181
+ soft_hyphen(encoding),
182
+ hyphen(encoding)
183
+ ].join('')
145
184
  end
146
185
 
147
- def whitespace
148
- " \\t#{zero_width_space}"
186
+ def zero_width_space(encoding = ::Encoding::UTF_8)
187
+ Prawn::Text::ZWSP.encode(encoding)
188
+ rescue ::Encoding::InvalidByteSequenceError,
189
+ ::Encoding::UndefinedConversionError
190
+ nil
149
191
  end
150
192
 
151
- def hyphen
152
- "-"
193
+ def whitespace(encoding = ::Encoding::UTF_8)
194
+ "\s\t#{zero_width_space(encoding)}".encode(encoding)
195
+ end
196
+
197
+ def hyphen(_encoding = ::Encoding::UTF_8)
198
+ '-'
199
+ rescue ::Encoding::InvalidByteSequenceError,
200
+ ::Encoding::UndefinedConversionError
201
+ nil
153
202
  end
154
203
 
155
204
  def line_empty?
156
- @line_empty && @accumulated_width == 0
205
+ @line_empty && @accumulated_width.zero?
157
206
  end
158
207
 
159
208
  def initialize_line(options)
@@ -174,48 +223,57 @@ module Prawn
174
223
  @line_full = false
175
224
  end
176
225
 
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
226
  def fragment_finished(fragment)
186
227
  if fragment == "\n"
187
228
  @newline_encountered = true
188
229
  @line_empty = false
189
230
  else
190
- update_output_based_on_last_fragment(fragment, soft_hyphen)
231
+ update_output_based_on_last_fragment(
232
+ fragment,
233
+ soft_hyphen(fragment.encoding)
234
+ )
191
235
  update_line_status_based_on_last_output
192
- determine_whether_to_pull_preceding_fragment_to_join_this_one(fragment)
236
+ pull_preceding_fragment_to_join_this_one?(fragment)
193
237
  end
194
238
  remember_this_fragment_for_backward_looking_ops
195
239
  end
196
240
 
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)
241
+ def update_output_based_on_last_fragment(
242
+ fragment, normalized_soft_hyphen = nil
243
+ )
244
+ remaining_text =
245
+ fragment.slice(@fragment_output.length..fragment.length)
246
+ if line_finished? && line_empty? && @fragment_output.empty? &&
247
+ !fragment.strip.empty?
248
+ raise Prawn::Errors::CannotFit
249
+ end
250
+
251
+ @arranger.update_last_string(
252
+ @fragment_output,
253
+ remaining_text,
254
+ normalized_soft_hyphen
255
+ )
201
256
  end
202
257
 
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)
206
- @fragment_output = @previous_fragment_output_without_last_word
207
- update_output_based_on_last_fragment(@previous_fragment)
208
- end
258
+ def pull_preceding_fragment_to_join_this_one?(current_fragment)
259
+ if @fragment_output.empty? && !current_fragment.empty? &&
260
+ @line_contains_more_than_one_word &&
261
+ !(previous_fragment_ended_with_breakable? ||
262
+ fragment_begins_with_breakable?(current_fragment))
263
+ @fragment_output = @previous_fragment_output_without_last_word
264
+ update_output_based_on_last_fragment(@previous_fragment)
209
265
  end
210
266
  end
211
267
 
212
268
  def remember_this_fragment_for_backward_looking_ops
213
269
  @previous_fragment = @fragment_output.dup
214
270
  pf = @previous_fragment
215
- @previous_fragment_ended_with_breakable = pf =~ /[#{break_chars}]$/
216
- last_word = pf.slice(/[^#{break_chars}]*$/)
271
+ @previous_fragment_ended_with_breakable =
272
+ pf =~ /[#{break_chars(pf.encoding)}]$/
273
+ last_word = pf.slice(/[^#{break_chars(pf.encoding)}]*$/)
217
274
  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)
275
+ @previous_fragment_output_without_last_word =
276
+ pf.slice(0, pf.length - last_word_length)
219
277
  end
220
278
 
221
279
  def previous_fragment_ended_with_breakable?
@@ -223,7 +281,7 @@ module Prawn
223
281
  end
224
282
 
225
283
  def fragment_begins_with_breakable?(fragment)
226
- fragment =~ /^[#{break_chars}]/
284
+ fragment =~ /^[#{break_chars(fragment.encoding)}]/
227
285
  end
228
286
 
229
287
  def line_finished?
@@ -231,25 +289,28 @@ module Prawn
231
289
  end
232
290
 
233
291
  def update_line_status_based_on_last_output
234
- @line_contains_more_than_one_word = true if @fragment_output =~ word_division_scan_pattern
292
+ if @fragment_output&.match?(word_division_scan_pattern(@fragment_output.encoding))
293
+ @line_contains_more_than_one_word = true
294
+ end
235
295
  end
236
296
 
237
297
  def end_of_the_line_reached(segment)
238
298
  update_line_status_based_on_last_output
239
- wrap_by_char(segment) unless @disable_wrap_by_char || @line_contains_more_than_one_word
299
+ unless @disable_wrap_by_char || @line_contains_more_than_one_word
300
+ wrap_by_char(segment)
301
+ end
240
302
  @line_full = true
241
303
  end
242
304
 
243
305
  def wrap_by_char(segment)
244
- font = @document.font
245
306
  segment.each_char do |char|
246
- break unless append_char(char, font)
307
+ break unless append_char(char)
247
308
  end
248
309
  end
249
310
 
250
- def append_char(char, font)
311
+ def append_char(char)
251
312
  # kerning doesn't make sense in the context of a single character
252
- char_width = font.compute_width_of(char)
313
+ char_width = @document.width_of(char)
253
314
 
254
315
  if @accumulated_width + char_width <= @width
255
316
  @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
@@ -14,84 +14,88 @@ module Prawn
14
14
  class Parser
15
15
  # @group Extension API
16
16
 
17
- PARSER_REGEX = begin
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>|" \
31
- "[^<\n]+"
32
- Regexp.new(regex_string, Regexp::MULTILINE)
33
- end
17
+ PARSER_REGEX =
18
+ begin
19
+ regex_string = "\n|" \
20
+ '<b>|</b>|' \
21
+ '<i>|</i>|' \
22
+ '<u>|</u>|' \
23
+ '<strikethrough>|</strikethrough>|' \
24
+ '<sub>|</sub>|' \
25
+ '<sup>|</sup>|' \
26
+ '<link[^>]*>|</link>|' \
27
+ '<color[^>]*>|</color>|' \
28
+ '<font[^>]*>|</font>|' \
29
+ '<strong>|</strong>|' \
30
+ '<em>|</em>|' \
31
+ '<a[^>]*>|</a>|' \
32
+ "[^<\n]+"
33
+ Regexp.new(regex_string, Regexp::MULTILINE)
34
+ end
34
35
 
35
- def self.format(string, *args)
36
- tokens = string.gsub(/<br\s*\/?>/, "\n").scan(PARSER_REGEX)
37
- self.array_from_tokens(tokens)
36
+ def self.format(string, *_args)
37
+ tokens = string.gsub(%r{<br\s*/?>}, "\n").scan(PARSER_REGEX)
38
+ array_from_tokens(tokens)
38
39
  end
39
40
 
40
41
  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>" }
53
- 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
42
+ prefixes = {
43
+ bold: '<b>',
44
+ italic: '<i>',
45
+ underline: '<u>',
46
+ strikethrough: '<strikethrough>',
47
+ subscript: '<sub>',
48
+ superscript: '<sup>'
49
+ }
50
+ suffixes = {
51
+ bold: '</b>',
52
+ italic: '</i>',
53
+ underline: '</u>',
54
+ strikethrough: '</strikethrough>',
55
+ subscript: '</sub>',
56
+ superscript: '</sup>'
57
+ }
58
+ array.map do |hash|
59
+ prefix = ''
60
+ suffix = ''
61
+ hash[:styles]&.each do |style|
62
+ prefix += prefixes[style]
63
+ suffix = suffixes[style] + suffix
61
64
  end
62
65
 
63
66
  font = hash[:font] ? " name='#{hash[:font]}'" : nil
64
67
  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
68
+ character_spacing =
69
+ if hash[:character_spacing]
70
+ " character_spacing='#{hash[:character_spacing]}'"
71
+ end
70
72
  if font || size || character_spacing
71
- prefix = prefix + "<font#{font}#{size}#{character_spacing}>"
72
- suffix = "</font>"
73
+ prefix += "<font#{font}#{size}#{character_spacing}>"
74
+ suffix = '</font>'
73
75
  end
74
76
 
75
77
  link = hash[:link] ? " href='#{hash[:link]}'" : nil
76
78
  anchor = hash[:anchor] ? " anchor='#{hash[:anchor]}'" : nil
77
79
  if link || anchor
78
- prefix = prefix + "<link#{link}#{anchor}>"
79
- suffix = "</link>"
80
+ prefix += "<link#{link}#{anchor}>"
81
+ suffix = '</link>'
80
82
  end
81
83
 
82
84
  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>"
85
+ prefix +=
86
+ if hash[:color].is_a?(Array)
87
+ "<color c='#{hash[:color][0]}'" \
88
+ " m='#{hash[:color][1]}'" \
89
+ " y='#{hash[:color][2]}'" \
90
+ " k='#{hash[:color][3]}'>"
91
+ else
92
+ "<color rgb='#{hash[:color]}'>"
93
+ end
94
+ suffix = '</color>'
92
95
  end
93
96
 
94
- string = hash[:text].gsub("&", "&amp;").gsub(">", "&gt;").gsub("<", "&lt;")
97
+ string = hash[:text].gsub('&', '&amp;').gsub('>', '&gt;')
98
+ .gsub('<', '&lt;')
95
99
  prefix + string + suffix
96
100
  end.join
97
101
  end
@@ -104,11 +108,13 @@ module Prawn
104
108
  array.each do |hash|
105
109
  hash[:text].scan(scan_pattern).each do |string|
106
110
  if string == "\n"
107
- paragraph << hash.dup.merge(:text => "\n") if previous_string == "\n"
111
+ if previous_string == "\n"
112
+ paragraph << hash.dup.merge(text: "\n")
113
+ end
108
114
  paragraphs << paragraph unless paragraph.empty?
109
115
  paragraph = []
110
116
  else
111
- paragraph << hash.dup.merge(:text => string)
117
+ paragraph << hash.dup.merge(text: string)
112
118
  end
113
119
  previous_string = string
114
120
  end
@@ -128,88 +134,106 @@ module Prawn
128
134
  sizes = []
129
135
  character_spacings = []
130
136
 
131
- while token = tokens.shift
137
+ tokens.each do |token|
132
138
  case token
133
- when "<b>", "<strong>"
139
+ when '<b>', '<strong>'
134
140
  styles << :bold
135
- when "<i>", "<em>"
141
+ when '<i>', '<em>'
136
142
  styles << :italic
137
- when "<u>"
143
+ when '<u>'
138
144
  styles << :underline
139
- when "<strikethrough>"
145
+ when '<strikethrough>'
140
146
  styles << :strikethrough
141
- when "<sub>"
147
+ when '<sub>'
142
148
  styles << :subscript
143
- when "<sup>"
149
+ when '<sup>'
144
150
  styles << :superscript
145
- when "</b>", "</strong>"
151
+ when '</b>', '</strong>'
146
152
  styles.delete(:bold)
147
- when "</i>", "</em>"
153
+ when '</i>', '</em>'
148
154
  styles.delete(:italic)
149
- when "</u>"
155
+ when '</u>'
150
156
  styles.delete(:underline)
151
- when "</strikethrough>"
157
+ when '</strikethrough>'
152
158
  styles.delete(:strikethrough)
153
- when "</sub>"
159
+ when '</sub>'
154
160
  styles.delete(:subscript)
155
- when "</sup>"
161
+ when '</sup>'
156
162
  styles.delete(:superscript)
157
- when "</link>", "</a>"
163
+ when '</link>', '</a>'
158
164
  link = nil
159
165
  anchor = nil
160
166
  local = nil
161
- when "</color>"
167
+ when '</color>'
162
168
  colors.pop
163
- when "</font>"
169
+ when '</font>'
164
170
  fonts.pop
165
171
  sizes.pop
166
172
  character_spacings.pop
173
+ when /^<link[^>]*>$/, /^<a[^>]*>$/
174
+ matches = /href="([^"]*)"/.match(token) ||
175
+ /href='([^']*)'/.match(token)
176
+ link = matches[1] unless matches.nil?
177
+
178
+ matches = /anchor="([^"]*)"/.match(token) ||
179
+ /anchor='([^']*)'/.match(token)
180
+ anchor = matches[1] unless matches.nil?
181
+
182
+ matches = /local="([^"]*)"/.match(token) ||
183
+ /local='([^']*)'/.match(token)
184
+ local = matches[1] unless matches.nil?
185
+ when /^<color[^>]*>$/
186
+ matches = /rgb="#?([^"]*)"/.match(token) ||
187
+ /rgb='#?([^']*)'/.match(token)
188
+ colors << matches[1] if matches
189
+
190
+ match = /c="#?([^"]*)"/.match(token) ||
191
+ /c='#?([^']*)'/.match(token)
192
+ c = match[1].to_i unless match.nil?
193
+ match = /m="#?([^"]*)"/.match(token) ||
194
+ /m='#?([^']*)'/.match(token)
195
+ m = match[1].to_i unless match.nil?
196
+ match = /y="#?([^"]*)"/.match(token) ||
197
+ /y='#?([^']*)'/.match(token)
198
+ y = match[1].to_i unless match.nil?
199
+ match = /k="#?([^"]*)"/.match(token) ||
200
+ /k='#?([^']*)'/.match(token)
201
+ k = match[1].to_i unless match.nil?
202
+ colors << [c, m, y, k] if [c, m, y, k].all?
203
+
204
+ # intend to support rgb="#ffffff" or rgb='#ffffff',
205
+ # r="255" g="255" b="255" or r='255' g='255' b='255',
206
+ # and c="100" m="100" y="100" k="100" or
207
+ # c='100' m='100' y='100' k='100'
208
+ # color = { :rgb => "#ffffff" }
209
+ # color = { :r => 255, :g => 255, :b => 255 }
210
+ # color = { :c => 100, :m => 100, :y => 100, :k => 100 }
211
+ when /^<font[^>]*>$/
212
+ matches = /name="([^"]*)"/.match(token) ||
213
+ /name='([^']*)'/.match(token)
214
+ fonts << matches[1] unless matches.nil?
215
+
216
+ matches = /size="([^"]*)"/.match(token) ||
217
+ /size='([^']*)'/.match(token)
218
+ sizes << matches[1].to_f unless matches.nil?
219
+
220
+ matches = /character_spacing="([^"]*)"/.match(token) ||
221
+ /character_spacing='([^']*)'/.match(token)
222
+ character_spacings << matches[1].to_f unless matches.nil?
167
223
  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
224
+ string = token.gsub('&lt;', '<').gsub('&gt;', '>')
225
+ .gsub('&amp;', '&')
226
+ array << {
227
+ text: string,
228
+ styles: styles.dup,
229
+ color: colors.last,
230
+ local: local,
231
+ link: link,
232
+ anchor: anchor,
233
+ font: fonts.last,
234
+ size: sizes.last,
235
+ character_spacing: character_spacings.last
236
+ }
213
237
  end
214
238
  end
215
239
  array