prawn 2.0.2 → 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 (277) 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 +37 -49
  8. data/lib/prawn/document.rb +181 -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 +18 -8
  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 +115 -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 +120 -80
  23. data/lib/prawn/graphics/blend_mode.rb +65 -0
  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 +197 -67
  29. data/lib/prawn/graphics/transformation.rb +17 -8
  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 +107 -66
  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 +78 -49
  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 +121 -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 +44 -0
  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 +52 -0
  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 -39
  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 +4 -5
  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 +45 -33
  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/prawn/image_handler_spec.rb +53 -0
  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/prawn/transformation_stack_spec.rb +66 -0
  195. data/spec/prawn/view_spec.rb +63 -0
  196. data/spec/prawn_manual_spec.rb +35 -0
  197. data/spec/spec_helper.rb +19 -23
  198. metadata +145 -185
  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/dice.alpha +0 -0
  206. data/data/images/dice.color +0 -0
  207. data/data/images/dice.png +0 -0
  208. data/data/images/dice_interlaced.png +0 -0
  209. data/data/images/fractal.jpg +0 -0
  210. data/data/images/indexed_color.dat +0 -0
  211. data/data/images/indexed_color.png +0 -0
  212. data/data/images/letterhead.jpg +0 -0
  213. data/data/images/license.md +0 -8
  214. data/data/images/page_white_text.alpha +0 -0
  215. data/data/images/page_white_text.color +0 -0
  216. data/data/images/page_white_text.png +0 -0
  217. data/data/images/pal_bk.png +0 -0
  218. data/data/images/pigs.jpg +0 -0
  219. data/data/images/prawn.png +0 -0
  220. data/data/images/ruport.png +0 -0
  221. data/data/images/ruport_data.dat +0 -0
  222. data/data/images/ruport_transparent.png +0 -0
  223. data/data/images/ruport_type0.png +0 -0
  224. data/data/images/stef.jpg +0 -0
  225. data/data/images/tru256.bmp +0 -0
  226. data/data/images/web-links.dat +0 -1
  227. data/data/images/web-links.png +0 -0
  228. data/data/pdfs/complex_template.pdf +0 -0
  229. data/data/pdfs/contains_ttf_font.pdf +0 -0
  230. data/data/pdfs/encrypted.pdf +0 -0
  231. data/data/pdfs/form.pdf +1 -819
  232. data/data/pdfs/hexagon.pdf +0 -61
  233. data/data/pdfs/indirect_reference.pdf +0 -86
  234. data/data/pdfs/multipage_template.pdf +0 -127
  235. data/data/pdfs/nested_pages.pdf +0 -118
  236. data/data/pdfs/page_without_mediabox.pdf +0 -193
  237. data/data/pdfs/resources_as_indirect_object.pdf +0 -83
  238. data/data/pdfs/two_hexagons.pdf +0 -90
  239. data/data/pdfs/version_1_6.pdf +0 -61
  240. data/data/shift_jis_text.txt +0 -1
  241. data/spec/acceptance/png.rb +0 -24
  242. data/spec/annotations_spec.rb +0 -67
  243. data/spec/bounding_box_spec.rb +0 -501
  244. data/spec/column_box_spec.rb +0 -59
  245. data/spec/destinations_spec.rb +0 -13
  246. data/spec/document_spec.rb +0 -742
  247. data/spec/extensions/mocha.rb +0 -45
  248. data/spec/font_metric_cache_spec.rb +0 -52
  249. data/spec/font_spec.rb +0 -475
  250. data/spec/formatted_text_arranger_spec.rb +0 -423
  251. data/spec/formatted_text_box_spec.rb +0 -716
  252. data/spec/formatted_text_fragment_spec.rb +0 -299
  253. data/spec/graphics_spec.rb +0 -666
  254. data/spec/grid_spec.rb +0 -95
  255. data/spec/image_handler_spec.rb +0 -53
  256. data/spec/images_spec.rb +0 -167
  257. data/spec/inline_formatted_text_parser_spec.rb +0 -568
  258. data/spec/jpg_spec.rb +0 -23
  259. data/spec/line_wrap_spec.rb +0 -366
  260. data/spec/measurement_units_spec.rb +0 -22
  261. data/spec/outline_spec.rb +0 -409
  262. data/spec/png_spec.rb +0 -235
  263. data/spec/reference_spec.rb +0 -25
  264. data/spec/repeater_spec.rb +0 -154
  265. data/spec/security_spec.rb +0 -151
  266. data/spec/soft_mask_spec.rb +0 -78
  267. data/spec/span_spec.rb +0 -43
  268. data/spec/stamp_spec.rb +0 -179
  269. data/spec/stroke_styles_spec.rb +0 -208
  270. data/spec/text_at_spec.rb +0 -142
  271. data/spec/text_box_spec.rb +0 -1038
  272. data/spec/text_rendering_mode_spec.rb +0 -45
  273. data/spec/text_spacing_spec.rb +0 -93
  274. data/spec/text_spec.rb +0 -549
  275. data/spec/text_with_inline_formatting_spec.rb +0 -35
  276. data/spec/transparency_spec.rb +0 -91
  277. 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
@@ -24,11 +24,11 @@ module Prawn
24
24
 
25
25
  # Whether this line is the last line in the paragraph
26
26
  def paragraph_finished?
27
- @newline_encountered || is_next_string_newline? || @arranger.finished?
27
+ @newline_encountered || next_string_newline? || @arranger.finished?
28
28
  end
29
29
 
30
30
  def tokenize(fragment)
31
- fragment.scan(scan_pattern)
31
+ fragment.scan(scan_pattern(fragment.encoding))
32
32
  end
33
33
 
34
34
  # Work in conjunction with the PDF::Formatted::Arranger
@@ -38,8 +38,10 @@ module Prawn
38
38
  def wrap_line(options)
39
39
  initialize_line(options)
40
40
 
41
+ # rubocop: disable Lint/AssignmentInCondition
41
42
  while fragment = @arranger.next_string
42
- @fragment_output = ""
43
+ # rubocop: enable Lint/AssignmentInCondition
44
+ @fragment_output = +''
43
45
 
44
46
  fragment.lstrip! if first_fragment_on_this_line?(fragment)
45
47
  next if empty_line?(fragment)
@@ -61,21 +63,20 @@ module Prawn
61
63
  end
62
64
 
63
65
  def empty_line?(fragment)
64
- empty = line_empty? && fragment.empty? && is_next_string_newline?
65
- @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
66
70
  empty
67
71
  end
68
72
 
69
- def is_next_string_newline?
73
+ def next_string_newline?
70
74
  @arranger.preview_next_string == "\n"
71
75
  end
72
76
 
73
77
  def apply_font_settings_and_add_fragment_to_line(fragment)
74
78
  result = nil
75
79
  @arranger.apply_font_settings do
76
- # if font has changed from Unicode to non-Unicode, or vice versa, the characters used for soft hyphens
77
- # and zero-width spaces will be different
78
- set_soft_hyphen_and_zero_width_space
79
80
  result = add_fragment_to_line(fragment)
80
81
  end
81
82
  result
@@ -85,27 +86,31 @@ module Prawn
85
86
  # the line
86
87
  #
87
88
  def add_fragment_to_line(fragment)
88
- if fragment == ""
89
+ if fragment == ''
89
90
  true
90
91
  elsif fragment == "\n"
91
92
  @newline_encountered = true
92
93
  false
93
94
  else
94
95
  tokenize(fragment).each do |segment|
95
- if segment == zero_width_space
96
- segment_width = 0
97
- else
98
- segment_width = @document.width_of(segment, :kerning => @kerning)
99
- end
96
+ segment_width = if segment == zero_width_space(segment.encoding)
97
+ 0
98
+ else
99
+ @document.width_of(segment, kerning: @kerning)
100
+ end
100
101
 
101
102
  if @accumulated_width + segment_width <= @width
102
103
  @accumulated_width += segment_width
103
- if segment[-1] == soft_hyphen
104
- 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)
105
107
  @accumulated_width -= sh_width
106
108
  end
107
109
  @fragment_output += segment
108
110
  else
111
+ if @accumulated_width.zero? && @line_contains_more_than_one_word
112
+ @line_contains_more_than_one_word = false
113
+ end
109
114
  end_of_the_line_reached(segment)
110
115
  fragment_finished(fragment)
111
116
  return false
@@ -119,13 +124,24 @@ module Prawn
119
124
 
120
125
  # The pattern used to determine chunks of text to place on a given line
121
126
  #
122
- def scan_pattern
123
- pattern = "[^#{break_chars}]+#{soft_hyphen}|" \
124
- "[^#{break_chars}]+#{hyphen}+|" \
125
- "[^#{break_chars}]+|" \
126
- "[#{whitespace}]+|" \
127
- "#{hyphen}+[^#{break_chars}]*|" \
128
- "#{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('|')
129
145
 
130
146
  Regexp.new(pattern)
131
147
  end
@@ -134,27 +150,56 @@ module Prawn
134
150
  # current line, which in turn determines whether character level
135
151
  # word breaking is needed
136
152
  #
137
- def word_division_scan_pattern
138
- 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
+ )
139
166
  end
140
167
 
141
- def break_chars
142
- "#{whitespace}#{soft_hyphen}#{hyphen}"
168
+ def soft_hyphen(encoding = ::Encoding::UTF_8)
169
+ Prawn::Text::SHY.encode(encoding)
170
+ rescue ::Encoding::InvalidByteSequenceError,
171
+ ::Encoding::UndefinedConversionError
172
+ nil
143
173
  end
144
174
 
145
- def whitespace
146
- " \\t#{zero_width_space}"
175
+ def break_chars(encoding = ::Encoding::UTF_8)
176
+ [
177
+ whitespace(encoding),
178
+ soft_hyphen(encoding),
179
+ hyphen(encoding)
180
+ ].join('')
147
181
  end
148
182
 
149
- def hyphen
150
- "-"
183
+ def zero_width_space(encoding = ::Encoding::UTF_8)
184
+ Prawn::Text::ZWSP.encode(encoding)
185
+ rescue ::Encoding::InvalidByteSequenceError,
186
+ ::Encoding::UndefinedConversionError
187
+ nil
151
188
  end
152
189
 
153
- attr_reader :soft_hyphen
154
- attr_reader :zero_width_space
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
199
+ end
155
200
 
156
201
  def line_empty?
157
- @line_empty && @accumulated_width == 0
202
+ @line_empty && @accumulated_width.zero?
158
203
  end
159
204
 
160
205
  def initialize_line(options)
@@ -175,35 +220,43 @@ module Prawn
175
220
  @line_full = false
176
221
  end
177
222
 
178
- def set_soft_hyphen_and_zero_width_space
179
- # this is done once per fragment, after the font settings for the fragment are applied --
180
- # it could actually be skipped if the font hasn't changed
181
- font = @document.font
182
- @soft_hyphen = font.normalize_encoding(Prawn::Text::SHY)
183
- @zero_width_space = font.unicode? ? Prawn::Text::ZWSP : ""
184
- end
185
-
186
223
  def fragment_finished(fragment)
187
224
  if fragment == "\n"
188
225
  @newline_encountered = true
189
226
  @line_empty = false
190
227
  else
191
- update_output_based_on_last_fragment(fragment, soft_hyphen)
228
+ update_output_based_on_last_fragment(
229
+ fragment,
230
+ soft_hyphen(fragment.encoding)
231
+ )
192
232
  update_line_status_based_on_last_output
193
- determine_whether_to_pull_preceding_fragment_to_join_this_one(fragment)
233
+ pull_preceding_fragment_to_join_this_one?(fragment)
194
234
  end
195
235
  remember_this_fragment_for_backward_looking_ops
196
236
  end
197
237
 
198
- def update_output_based_on_last_fragment(fragment, normalized_soft_hyphen = nil)
199
- remaining_text = fragment.slice(@fragment_output.length..fragment.length)
200
- fail Prawn::Errors::CannotFit if line_finished? && line_empty? && @fragment_output.empty? && !fragment.strip.empty?
201
- @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
+ )
202
253
  end
203
254
 
204
- def determine_whether_to_pull_preceding_fragment_to_join_this_one(current_fragment)
205
- if @fragment_output.empty? && !current_fragment.empty? && @line_contains_more_than_one_word
206
- 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)
207
260
  @fragment_output = @previous_fragment_output_without_last_word
208
261
  update_output_based_on_last_fragment(@previous_fragment)
209
262
  end
@@ -213,10 +266,12 @@ module Prawn
213
266
  def remember_this_fragment_for_backward_looking_ops
214
267
  @previous_fragment = @fragment_output.dup
215
268
  pf = @previous_fragment
216
- @previous_fragment_ended_with_breakable = pf =~ /[#{break_chars}]$/
217
- 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)}]*$/)
218
272
  last_word_length = last_word.nil? ? 0 : last_word.length
219
- @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)
220
275
  end
221
276
 
222
277
  def previous_fragment_ended_with_breakable?
@@ -224,7 +279,7 @@ module Prawn
224
279
  end
225
280
 
226
281
  def fragment_begins_with_breakable?(fragment)
227
- fragment =~ /^[#{break_chars}]/
282
+ fragment =~ /^[#{break_chars(fragment.encoding)}]/
228
283
  end
229
284
 
230
285
  def line_finished?
@@ -232,25 +287,28 @@ module Prawn
232
287
  end
233
288
 
234
289
  def update_line_status_based_on_last_output
235
- @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
236
293
  end
237
294
 
238
295
  def end_of_the_line_reached(segment)
239
296
  update_line_status_based_on_last_output
240
- 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
241
300
  @line_full = true
242
301
  end
243
302
 
244
303
  def wrap_by_char(segment)
245
- font = @document.font
246
304
  segment.each_char do |char|
247
- break unless append_char(char, font)
305
+ break unless append_char(char)
248
306
  end
249
307
  end
250
308
 
251
- def append_char(char, font)
309
+ def append_char(char)
252
310
  # kerning doesn't make sense in the context of a single character
253
- char_width = font.compute_width_of(char)
311
+ char_width = @document.width_of(char)
254
312
 
255
313
  if @accumulated_width + char_width <= @width
256
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