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
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ # blend_mode.rb : Implements blend modes
4
+ #
5
+ # Contributed by John Ford. October, 2015
6
+ #
7
+ # This is free software. Please see the LICENSE and COPYING files for details.
8
+ #
9
+
10
+ module Prawn
11
+ module Graphics
12
+ # The Prawn::BlendMode module is used to change the way
13
+ # two layers are blended together.
14
+ #
15
+ # Passing an array of blend modes is allowed. PDF viewers should
16
+ # blend layers based on the first recognized blend mode.
17
+ #
18
+ # Valid blend modes in v1.4 of the PDF spec include :Normal, :Multiply,
19
+ # :Screen, :Overlay, :Darken, :Lighten, :ColorDodge, :ColorBurn, :HardLight,
20
+ # :SoftLight, :Difference, :Exclusion, :Hue, :Saturation, :Color, and
21
+ # :Luminosity.
22
+ #
23
+ # Example:
24
+ # pdf.fill_color('0000ff')
25
+ # pdf.fill_rectangle([x, y+25], 50, 50)
26
+ # pdf.blend_mode(:Multiply) do
27
+ # pdf.fill_color('ff0000')
28
+ # pdf.fill_circle([x, y], 25)
29
+ # end
30
+ #
31
+ module BlendMode
32
+ # @group Stable API
33
+
34
+ def blend_mode(blend_mode = :Normal)
35
+ renderer.min_version(1.4)
36
+
37
+ save_graphics_state if block_given?
38
+ renderer.add_content "/#{blend_mode_dictionary_name(blend_mode)} gs"
39
+ if block_given?
40
+ yield
41
+ restore_graphics_state
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def blend_mode_dictionary_registry
48
+ @blend_mode_dictionary_registry ||= {}
49
+ end
50
+
51
+ def blend_mode_dictionary_name(blend_mode)
52
+ key = Array(blend_mode).join('')
53
+ dictionary_name = "BM#{key}"
54
+
55
+ dictionary = blend_mode_dictionary_registry[dictionary_name] ||= ref!(
56
+ Type: :ExtGState,
57
+ BM: blend_mode
58
+ )
59
+
60
+ page.ext_gstates[dictionary_name] = dictionary
61
+ dictionary_name
62
+ end
63
+ end
64
+ end
65
+ end
@@ -1,4 +1,4 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  # cap_style.rb : Implements stroke cap styling
4
4
  #
@@ -11,7 +11,7 @@ module Prawn
11
11
  module CapStyle
12
12
  # @group Stable API
13
13
 
14
- CAP_STYLES = { :butt => 0, :round => 1, :projecting_square => 2 }
14
+ CAP_STYLES = { butt: 0, round: 1, projecting_square: 2 }.freeze
15
15
 
16
16
  # Sets the cap style for stroked lines and curves
17
17
  #
@@ -27,7 +27,7 @@ module Prawn
27
27
  write_stroke_cap_style
28
28
  end
29
29
 
30
- alias_method :cap_style=, :cap_style
30
+ alias cap_style= cap_style
31
31
 
32
32
  private
33
33
 
@@ -1,4 +1,4 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  # color.rb : Implements color handling
4
4
  #
@@ -27,11 +27,12 @@ module Prawn
27
27
  #
28
28
  def fill_color(*color)
29
29
  return current_fill_color if color.empty?
30
+
30
31
  self.current_fill_color = process_color(*color)
31
32
  set_fill_color
32
33
  end
33
34
 
34
- alias_method :fill_color=, :fill_color
35
+ alias fill_color= fill_color
35
36
 
36
37
  # Sets or returns the line stroking color.
37
38
  #
@@ -49,12 +50,13 @@ module Prawn
49
50
  #
50
51
  def stroke_color(*color)
51
52
  return current_stroke_color if color.empty?
53
+
52
54
  color = process_color(*color)
53
55
  self.current_stroke_color = color
54
56
  set_stroke_color(color)
55
57
  end
56
58
 
57
- alias_method :stroke_color=, :stroke_color
59
+ alias stroke_color= stroke_color
58
60
 
59
61
  module_function
60
62
 
@@ -65,7 +67,7 @@ module Prawn
65
67
  # => "ff7808"
66
68
  #
67
69
  def rgb2hex(rgb)
68
- rgb.map { |e| "%02x" % e }.join
70
+ rgb.map { |e| format '%<value>02x', value: e }.join
69
71
  end
70
72
 
71
73
  # Converts hex string into RGB value array:
@@ -74,27 +76,33 @@ module Prawn
74
76
  # => [255, 120, 8]
75
77
  #
76
78
  def hex2rgb(hex)
77
- r, g, b = hex[0..1], hex[2..3], hex[4..5]
79
+ r = hex[0..1]
80
+ g = hex[2..3]
81
+ b = hex[4..5]
78
82
  [r, g, b].map { |e| e.to_i(16) }
79
83
  end
80
84
 
81
85
  private
82
86
 
83
87
  def process_color(*color)
84
- case(color.size)
88
+ case color.size
85
89
  when 1
86
90
  color[0]
87
91
  when 4
88
92
  color
89
93
  else
90
- fail ArgumentError, 'wrong number of arguments supplied'
94
+ raise ArgumentError, 'wrong number of arguments supplied'
91
95
  end
92
96
  end
93
97
 
94
98
  def color_type(color)
95
99
  case color
96
100
  when String
97
- :RGB
101
+ if /\A\h{6}\z/.match?(color)
102
+ :RGB
103
+ else
104
+ raise ArgumentError, "Unknown type of color: #{color.inspect}"
105
+ end
98
106
  when Array
99
107
  case color.length
100
108
  when 3
@@ -102,7 +110,7 @@ module Prawn
102
110
  when 4
103
111
  :CMYK
104
112
  else
105
- fail ArgumentError, "Unknown type of color: #{color.inspect}"
113
+ raise ArgumentError, "Unknown type of color: #{color.inspect}"
106
114
  end
107
115
  end
108
116
  end
@@ -119,7 +127,7 @@ module Prawn
119
127
  end
120
128
 
121
129
  def color_to_s(color)
122
- normalize_color(color).map { |c| '%.3f' % c }.join(' ')
130
+ PDF::Core.real_params normalize_color(color)
123
131
  end
124
132
 
125
133
  def color_space(color)
@@ -131,15 +139,19 @@ module Prawn
131
139
  end
132
140
  end
133
141
 
134
- COLOR_SPACES = [:DeviceRGB, :DeviceCMYK, :Pattern]
142
+ COLOR_SPACES = %i[DeviceRGB DeviceCMYK Pattern].freeze
135
143
 
136
144
  def set_color_space(type, color_space)
137
145
  # don't set the same color space again
138
- return if current_color_space(type) == color_space && !state.page.in_stamp_stream?
146
+ if current_color_space(type) == color_space &&
147
+ !state.page.in_stamp_stream?
148
+ return
149
+ end
150
+
139
151
  set_current_color_space(color_space, type)
140
152
 
141
153
  unless COLOR_SPACES.include?(color_space)
142
- fail ArgumentError, "unknown color space: '#{color_space}'"
154
+ raise ArgumentError, "unknown color space: '#{color_space}'"
143
155
  end
144
156
 
145
157
  operator = case type
@@ -148,7 +160,7 @@ module Prawn
148
160
  when :stroke
149
161
  'CS'
150
162
  else
151
- fail ArgumentError, "unknown type '#{type}'"
163
+ raise ArgumentError, "unknown type '#{type}'"
152
164
  end
153
165
 
154
166
  renderer.add_content "/#{color_space} #{operator}"
@@ -161,7 +173,7 @@ module Prawn
161
173
  when :stroke
162
174
  'SCN'
163
175
  else
164
- fail ArgumentError, "unknown type '#{type}'"
176
+ raise ArgumentError, "unknown type '#{type}'"
165
177
  end
166
178
 
167
179
  if options[:pattern]
@@ -187,8 +199,6 @@ module Prawn
187
199
  set_stroke_color
188
200
  end
189
201
 
190
- private
191
-
192
202
  def current_color_space(type)
193
203
  graphic_state.color_space[type]
194
204
  end
@@ -214,14 +224,6 @@ module Prawn
214
224
  graphic_state.stroke_color = color
215
225
  end
216
226
 
217
- def write_fill_color
218
- write_color(current_fill_color, 'scn')
219
- end
220
-
221
- def write_stroke_color
222
- write_color(current_fill_color, 'SCN')
223
- end
224
-
225
227
  def write_color(color, operator)
226
228
  renderer.add_content "#{color} #{operator}"
227
229
  end
@@ -1,4 +1,4 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  # dash.rb : Implements stroke dashing
4
4
  #
@@ -28,8 +28,9 @@ module Prawn
28
28
  # 3 on, 2 off, 3 on, 2 off, ...
29
29
  #
30
30
  # * If the parameter +length+ is an array, it specifies the
31
- # lengths of alternating dashes and gaps. The :space option is
32
- # ignored in this case.
31
+ # lengths of alternating dashes and gaps. The numbers must be
32
+ # non-negative and not all zero. The :space option is ignored
33
+ # in this case.
33
34
  #
34
35
  # Examples:
35
36
  #
@@ -37,6 +38,8 @@ module Prawn
37
38
  # 2 on, 1 off, 2 on, 1 off, ...
38
39
  # length = [3, 1, 2, 3]
39
40
  # 3 on, 1 off, 2 on, 3 off, 3 on, 1 off, ...
41
+ # length = [3, 0, 1]
42
+ # 3 on, 0 off, 1 on, 3 off, 0 on, 1 off, ...
40
43
  #
41
44
  # Options may contain the keys :space and :phase
42
45
  #
@@ -55,19 +58,28 @@ module Prawn
55
58
  def dash(length = nil, options = {})
56
59
  return current_dash_state if length.nil?
57
60
 
58
- if length == 0 || length.kind_of?(Array) && length.any? { |e| e == 0 }
59
- fail ArgumentError,
60
- "Zero length dashes are invalid. Call #undash to disable dashes."
61
+ length = Array(length)
62
+
63
+ if length.all?(&:zero?)
64
+ raise ArgumentError,
65
+ 'Zero length dashes are invalid. Call #undash to disable dashes.'
66
+ elsif length.any?(&:negative?)
67
+ raise ArgumentError,
68
+ 'Negative numbers are not allowed for dash lengths.'
61
69
  end
62
70
 
63
- self.current_dash_state = { :dash => length,
64
- :space => length.kind_of?(Array) ? nil : options[:space] || length,
65
- :phase => options[:phase] || 0 }
71
+ length = length.first if length.length == 1
72
+
73
+ self.current_dash_state = {
74
+ dash: length,
75
+ space: length.is_a?(Array) ? nil : options[:space] || length,
76
+ phase: options[:phase] || 0
77
+ }
66
78
 
67
79
  write_stroke_dash
68
80
  end
69
81
 
70
- alias_method :dash=, :dash
82
+ alias dash= dash
71
83
 
72
84
  # Stops dashing, restoring solid stroked lines and curves
73
85
  #
@@ -89,7 +101,7 @@ module Prawn
89
101
  end
90
102
 
91
103
  def undashed_setting
92
- { :dash => nil, :space => nil, :phase => 0 }
104
+ { dash: nil, space: nil, phase: 0 }
93
105
  end
94
106
 
95
107
  def current_dash_state=(dash_options)
@@ -1,4 +1,4 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  # join_style.rb : Implements stroke join styling
4
4
  #
@@ -9,7 +9,7 @@
9
9
  module Prawn
10
10
  module Graphics
11
11
  module JoinStyle
12
- JOIN_STYLES = { :miter => 0, :round => 1, :bevel => 2 }
12
+ JOIN_STYLES = { miter: 0, round: 1, bevel: 2 }.freeze
13
13
 
14
14
  # @group Stable API
15
15
 
@@ -25,10 +25,16 @@ module Prawn
25
25
 
26
26
  self.current_join_style = style
27
27
 
28
+ unless JOIN_STYLES.key?(current_join_style)
29
+ raise Prawn::Errors::InvalidJoinStyle,
30
+ "#{style} is not a recognized join style. Valid styles are " +
31
+ JOIN_STYLES.keys.join(', ')
32
+ end
33
+
28
34
  write_stroke_join_style
29
35
  end
30
36
 
31
- alias_method :join_style=, :join_style
37
+ alias join_style= join_style
32
38
 
33
39
  private
34
40
 
@@ -1,4 +1,6 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest/sha1'
2
4
 
3
5
  # patterns.rb : Implements axial & radial gradients
4
6
  #
@@ -10,36 +12,83 @@
10
12
  module Prawn
11
13
  module Graphics
12
14
  module Patterns
15
+ GradientStop = Struct.new(:position, :color)
16
+ Gradient = Struct.new(
17
+ :type, :apply_transformations, :stops, :from, :to, :r1, :r2
18
+ )
19
+
13
20
  # @group Stable API
14
21
 
15
- # Sets the fill gradient from color1 to color2.
16
- # old arguments: point, width, height, color1, color2, options = {}
17
- # new arguments: from, to, color1, color1
18
- # or from, r1, to, r2, color1, color2
19
- def fill_gradient(*args)
20
- set_gradient(:fill, *args)
22
+ # Sets the fill gradient.
23
+ # old arguments:
24
+ # from, to, color1, color2
25
+ # or
26
+ # from, r1, to, r2, color1, color2
27
+ # new arguments:
28
+ # from: [x, y]
29
+ # to: [x, y]
30
+ # r1: radius
31
+ # r2: radius
32
+ # stops: [color, color, ...] or
33
+ # { position => color, position => color, ... }
34
+ # apply_transformations: true
35
+ #
36
+ # Examples:
37
+ #
38
+ # # draws a horizontal axial gradient that starts at red on the left
39
+ # # and ends at blue on the right
40
+ # fill_gradient from: [0, 0], to: [100, 0], stops: ['red', 'blue']
41
+ #
42
+ # # draws a horizontal radial gradient that starts at red, is green
43
+ # # 80% of the way through, and finishes blue
44
+ # fill_gradient from: [0, 0], r1: 0, to: [100, 0], r2: 180,
45
+ # stops: { 0 => 'red', 0.8 => 'green', 1 => 'blue' }
46
+ #
47
+ # <tt>from</tt> and <tt>to</tt> specify the axis of where the gradient
48
+ # should be painted.
49
+ #
50
+ # <tt>r1</tt> and <tt>r2</tt>, if specified, make a radial gradient with
51
+ # the starting circle of radius <tt>r1</tt> centered at <tt>from</tt>
52
+ # and ending at a circle of radius <tt>r2</tt> centered at <tt>to</tt>.
53
+ # If <tt>r1</tt> is not specified, a axial gradient will be drawn.
54
+ #
55
+ # <tt>stops</tt> is an array or hash of stops. Each stop is either just a
56
+ # string indicating the color, in which case the stops will be evenly
57
+ # distributed across the gradient, or a hash where the key is
58
+ # a position between 0 and 1 indicating what distance through the
59
+ # gradient the color should change, and the value is a color string.
60
+ #
61
+ # Option <tt>apply_transformations</tt>, if set true, will transform the
62
+ # gradient's co-ordinate space so it matches the current co-ordinate
63
+ # space of the document. This option will be the default from Prawn v3,
64
+ # and is default true if you use the new arguments format.
65
+ # The default for the old arguments format, false, will mean if you
66
+ # (for example) scale your document by 2 and put a gradient inside, you
67
+ # will have to manually multiply your co-ordinates by 2 so the gradient
68
+ # is correctly positioned.
69
+ def fill_gradient(*args, **kwargs)
70
+ set_gradient(:fill, *args, **kwargs)
21
71
  end
22
72
 
23
- # Sets the stroke gradient from color1 to color2.
24
- # old arguments: point, width, height, color1, color2, options = {}
25
- # new arguments: from, to, color1, color2
26
- # or from, r1, to, r2, color1, color2
27
- def stroke_gradient(*args)
28
- set_gradient(:stroke, *args)
73
+ # Sets the stroke gradient.
74
+ # See fill_gradient for a description of the arguments to this method.
75
+ def stroke_gradient(*args, **kwargs)
76
+ set_gradient(:stroke, *args, **kwargs)
29
77
  end
30
78
 
31
79
  private
32
80
 
33
- def set_gradient(type, *grad)
81
+ def set_gradient(type, *grad, **kwargs)
82
+ gradient = parse_gradient_arguments(*grad, **kwargs)
83
+
34
84
  patterns = page.resources[:Pattern] ||= {}
35
85
 
36
- registry_key = gradient_registry_key grad
86
+ registry_key = gradient_registry_key gradient
37
87
 
38
- if patterns["SP#{registry_key}"]
39
- shading = patterns["SP#{registry_key}"]
40
- else
41
- unless shading = gradient_registry[registry_key]
42
- shading = gradient(*grad)
88
+ unless patterns.key? "SP#{registry_key}"
89
+ shading = gradient_registry[registry_key]
90
+ unless shading
91
+ shading = create_gradient_pattern(gradient)
43
92
  gradient_registry[registry_key] = shading
44
93
  end
45
94
 
@@ -52,79 +101,160 @@ module Prawn
52
101
  when :stroke
53
102
  'SCN'
54
103
  else
55
- fail ArgumentError, "unknown type '#{type}'"
104
+ raise ArgumentError, "unknown type '#{type}'"
56
105
  end
57
106
 
58
107
  set_color_space type, :Pattern
59
108
  renderer.add_content "/SP#{registry_key} #{operator}"
60
109
  end
61
110
 
111
+ # rubocop: disable Metrics/ParameterLists
112
+ def parse_gradient_arguments(
113
+ *arguments, from: nil, to: nil, r1: nil, r2: nil, stops: nil,
114
+ apply_transformations: nil
115
+ )
116
+ case arguments.length
117
+ when 0
118
+ apply_transformations = true if apply_transformations.nil?
119
+ when 4
120
+ from, to, *stops = arguments
121
+ when 6
122
+ from, r1, to, r2, *stops = arguments
123
+ else
124
+ raise ArgumentError, "Unknown type of gradient: #{arguments.inspect}"
125
+ end
126
+
127
+ if stops.length < 2
128
+ raise ArgumentError, 'At least two stops must be specified'
129
+ end
130
+
131
+ stops = stops.map.with_index do |stop, index|
132
+ case stop
133
+ when Array, Hash
134
+ position, color = stop
135
+ else
136
+ position = index / (stops.length.to_f - 1)
137
+ color = stop
138
+ end
139
+
140
+ unless (0..1).cover?(position)
141
+ raise ArgumentError, 'position must be between 0 and 1'
142
+ end
143
+
144
+ GradientStop.new(position, normalize_color(color))
145
+ end
146
+
147
+ if stops.first.position != 0
148
+ raise ArgumentError, 'The first stop must have a position of 0'
149
+ end
150
+ if stops.last.position != 1
151
+ raise ArgumentError, 'The last stop must have a position of 1'
152
+ end
153
+
154
+ if stops.map { |stop| color_type(stop.color) }.uniq.length > 1
155
+ raise ArgumentError, 'All colors must be of the same color space'
156
+ end
157
+
158
+ Gradient.new(
159
+ r1 ? :radial : :axial,
160
+ apply_transformations,
161
+ stops,
162
+ from, to,
163
+ r1, r2
164
+ )
165
+ end
166
+ # rubocop: enable Metrics/ParameterLists
167
+
62
168
  def gradient_registry_key(gradient)
63
- if gradient[1].is_a?(Array) # axial
64
- [
65
- map_to_absolute(gradient[0]),
66
- map_to_absolute(gradient[1]),
67
- gradient[2], gradient[3]
68
- ]
69
- else # radial
70
- [
71
- map_to_absolute(gradient[0]),
72
- gradient[1],
73
- map_to_absolute(gradient[2]),
74
- gradient[3],
75
- gradient[4], gradient[5]
76
- ]
77
- end.hash
169
+ _x1, _y1, x2, y2, transformation = gradient_coordinates(gradient)
170
+
171
+ key = [
172
+ gradient.type.to_s,
173
+ transformation,
174
+ x2, y2,
175
+ gradient.r1 || -1, gradient.r2 || -1,
176
+ gradient.stops.length,
177
+ gradient.stops.map { |s| [s.position, s.color] }
178
+ ].flatten
179
+ Digest::SHA1.hexdigest(key.join(','))
78
180
  end
79
181
 
80
182
  def gradient_registry
81
183
  @gradient_registry ||= {}
82
184
  end
83
185
 
84
- def gradient(*args)
85
- if args.length != 4 && args.length != 6
86
- fail ArgumentError, "Unknown type of gradient: #{args.inspect}"
186
+ def create_gradient_pattern(gradient)
187
+ if gradient.apply_transformations.nil? &&
188
+ current_transformation_matrix_with_translation(0, 0) !=
189
+ [1, 0, 0, 1, 0, 0]
190
+ warn 'Gradients in Prawn 2.x and lower are not correctly positioned '\
191
+ 'when a transformation has been made to the document. ' \
192
+ "Pass 'apply_transformations: true' to correctly transform the " \
193
+ 'gradient, or see ' \
194
+ 'https://github.com/prawnpdf/prawn/wiki/Gradient-Transformations ' \
195
+ 'for more information.'
87
196
  end
88
197
 
89
- color1 = normalize_color(args[-2]).dup.freeze
90
- color2 = normalize_color(args[-1]).dup.freeze
91
-
92
- if color_type(color1) != color_type(color2)
93
- fail ArgumentError, "Both colors must be of the same color space: #{color1.inspect} and #{color2.inspect}"
198
+ shader_stops = gradient.stops.each_cons(2).map do |first, second|
199
+ ref!(
200
+ FunctionType: 2,
201
+ Domain: [0.0, 1.0],
202
+ C0: first.color,
203
+ C1: second.color,
204
+ N: 1.0
205
+ )
94
206
  end
95
207
 
96
- process_color color1
97
- process_color color2
208
+ # If there's only two stops, we can use the single shader.
209
+ # Otherwise we stitch the multiple shaders together.
210
+ shader = if shader_stops.length == 1
211
+ shader_stops.first
212
+ else
213
+ ref!(
214
+ FunctionType: 3, # stitching function
215
+ Domain: [0.0, 1.0],
216
+ Functions: shader_stops,
217
+ Bounds: gradient.stops[1..-2].map(&:position),
218
+ Encode: [0.0, 1.0] * shader_stops.length
219
+ )
220
+ end
98
221
 
99
- shader = ref!(
100
- :FunctionType => 2,
101
- :Domain => [0.0, 1.0],
102
- :C0 => color1,
103
- :C1 => color2,
104
- :N => 1.0
105
- )
222
+ x1, y1, x2, y2, transformation = gradient_coordinates(gradient)
106
223
 
107
- if args.length == 4
108
- coords = [0, 0, args[1].first - args[0].first, args[1].last - args[0].last]
109
- else
110
- coords = [0, 0, args[1], args[2].first - args[0].first, args[2].last - args[0].last, args[3]]
111
- end
224
+ coords = if gradient.type == :axial
225
+ [0, 0, x2 - x1, y2 - y1]
226
+ else
227
+ [0, 0, gradient.r1, x2 - x1, y2 - y1, gradient.r2]
228
+ end
112
229
 
113
230
  shading = ref!(
114
- :ShadingType => args.length == 4 ? 2 : 3, # axial : radial shading
115
- :ColorSpace => color_space(color1),
116
- :Coords => coords,
117
- :Function => shader,
118
- :Extend => [true, true]
231
+ ShadingType: gradient.type == :axial ? 2 : 3,
232
+ ColorSpace: color_space(gradient.stops.first.color),
233
+ Coords: coords,
234
+ Function: shader,
235
+ Extend: [true, true]
119
236
  )
120
237
 
121
238
  ref!(
122
- :PatternType => 2, # shading pattern
123
- :Shading => shading,
124
- :Matrix => [1, 0,
125
- 0, 1] + map_to_absolute(args[0])
239
+ PatternType: 2, # shading pattern
240
+ Shading: shading,
241
+ Matrix: transformation
126
242
  )
127
243
  end
244
+
245
+ def gradient_coordinates(gradient)
246
+ x1, y1 = map_to_absolute(gradient.from)
247
+ x2, y2 = map_to_absolute(gradient.to)
248
+
249
+ transformation =
250
+ if gradient.apply_transformations
251
+ current_transformation_matrix_with_translation(x1, y1)
252
+ else
253
+ [1, 0, 0, 1, x1, y1]
254
+ end
255
+
256
+ [x1, y1, x2, y2, transformation]
257
+ end
128
258
  end
129
259
  end
130
260
  end