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