inker 0.1.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.
@@ -0,0 +1,30 @@
1
+ require "yaml"
2
+ require "digest"
3
+ require "inker/version"
4
+ require "inker/color"
5
+ require "inker/wrappers/string"
6
+
7
+
8
+ # The main module of the gem, which loads all necessary classes and
9
+ # provides a helper for {Inker::Color} object generation from a string and
10
+ # for named colors map.
11
+ module Inker
12
+ class << self
13
+ # Creates a new instance of {Inker::Color}, which could be used for color
14
+ # manipulation or for collecting color info.
15
+ #
16
+ # @param str [String] the string to transform into {Inker::Color}
17
+ # @return [Inker::Color]
18
+ def color(str)
19
+ Color.new(str)
20
+ end
21
+
22
+
23
+ # Returns the map of named colors and their respective HEX representation.
24
+ #
25
+ # @return [Hash] a map of named colors and their HEX color
26
+ def named_colors
27
+ @named_colors ||= YAML.load_file(File.expand_path('../data/colors.yml', __FILE__))
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,152 @@
1
+ require_relative 'color/tools'
2
+ require_relative 'color/serializers'
3
+
4
+ module Inker
5
+ # This class is used to represent a color in Ruby as an object. It allows
6
+ # to create a new instance of {Inker::Color} from a string which represents a color.
7
+ # It also allows to obtain more info about the color and convert color to a different
8
+ # format.
9
+ class Color
10
+ extend Tools
11
+ include Serializers
12
+
13
+ attr_reader :red, :green, :blue, :alpha
14
+
15
+ # Create a new {Inker::Color} object from a color string.
16
+ #
17
+ # @param color_str [String] a color string
18
+ def initialize(color_str)
19
+ @input = color_str.to_s.downcase.gsub(/\s+/, "")
20
+
21
+ Color.parse_color(@input).tap do |color|
22
+ @red = color[:red]
23
+ @green = color[:green]
24
+ @blue = color[:blue]
25
+ @alpha = color[:alpha]
26
+ end
27
+
28
+ validate_color!
29
+ end
30
+
31
+ def ==(color)
32
+ self.red == color.red and
33
+ self.green == color.green and
34
+ self.blue == color.blue and
35
+ self.alpha == color.alpha
36
+ end
37
+
38
+
39
+ # Set the value of red component.
40
+ #
41
+ # @param value [Integer] the value of red component [0-255]
42
+ def red=(value)
43
+ raise ArgumentError.new("Invalid value: #{value.inspect}") if value.to_i < 0 or value.to_i > 255
44
+ @red = value
45
+ end
46
+
47
+
48
+ # Set the value of green component.
49
+ #
50
+ # @param value [Integer] the value of green component [0-255]
51
+ def green=(value)
52
+ raise ArgumentError.new("Invalid value: #{value.inspect}") if value.to_i < 0 or value.to_i > 255
53
+ @green = value
54
+ end
55
+
56
+
57
+ # Set the value of blue component.
58
+ #
59
+ # @param value [Integer] the value of blue component [0-255]
60
+ def blue=(value)
61
+ raise ArgumentError.new("Invalid value: #{value.inspect}") if value.to_i < 0 or value.to_i > 255
62
+ @blue = value
63
+ end
64
+
65
+
66
+ # Set the value of alpha component.
67
+ #
68
+ # @param value [Float] the value of alpha component [0.0-1.0]
69
+ def alpha=(value)
70
+ raise ArgumentError.new("Invalid value: #{value.inspect}") if value.to_f < 0 or value.to_f > 1
71
+ @alpha = value
72
+ end
73
+
74
+
75
+ # Calculate the brightness of a color.
76
+ #
77
+ # @return [Integer] a value between 0-255 which indicates the brightness of the color
78
+ def brightness
79
+ Color.brightness(@red, @green, @blue)
80
+ end
81
+
82
+
83
+ # Calculate the lightness of a color.
84
+ #
85
+ # @return [Float] a value between 0.0-1.0 which indicates the lightness of the color
86
+ def lightness
87
+ Color.lightness(@red, @green, @blue)
88
+ end
89
+
90
+
91
+ # Calculate the saturation of a color.
92
+ #
93
+ # @return [Float] a value between 0.0-1.0 which indicates the saturation of the color
94
+ def saturation
95
+ Color.saturation(@red, @green, @blue)
96
+ end
97
+
98
+
99
+ # Calculate the HUE of a color.
100
+ #
101
+ # @return [Integer] a value between 0-360 which indicates the HUE of the color
102
+ def hue
103
+ Color.hue(@red, @green, @blue)
104
+ end
105
+
106
+ # Returns a boolean which indicates if the color is dark.
107
+ #
108
+ # @return [Boolean] `true` when color is dark
109
+ def dark?
110
+ brightness < 128
111
+ end
112
+
113
+ # Returns a boolean which indicates if the color is light.
114
+ # @return [Boolean] `true` when color is light
115
+ def light?
116
+ !dark?
117
+ end
118
+
119
+
120
+ # Convert color to string in the specified format.
121
+ #
122
+ # @param format [String] indicates the format to which to output the color (default: `hex`)
123
+ #
124
+ # @return [String] a string representation of the color
125
+ def to_s(format = 'hex')
126
+ case format.to_s.strip.downcase
127
+ when 'hex6' then self.hex6
128
+ when 'rgb' then self.rgb
129
+ when 'rgba' then self.rgba
130
+ when 'hsl' then self.hsl
131
+ when 'hsla' then self.hsla
132
+ else
133
+ self.hex
134
+ end
135
+ end
136
+
137
+ private
138
+
139
+ # Validates the values of the color.
140
+ def validate_color!
141
+ invalid = (@red < 0 or @red > 255)
142
+ invalid ||= (@green < 0 or @green > 255)
143
+ invalid ||= (@blue < 0 or @blue > 255)
144
+ invalid ||= (@alpha < 0 or @alpha > 1)
145
+
146
+ if invalid
147
+ raise ArgumentError.new "Invalid color: #{@input.inspect} " \
148
+ "(R: #{@red.inspect}, G: #{@green.inspect}, B: #{@blue.inspect}, A: #{@alpha.inspect})"
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,69 @@
1
+ module Inker
2
+ class Color
3
+ # This module implements the methods which can be used to serialize
4
+ # a color as string in different formats.
5
+ module Serializers
6
+
7
+ # Convert color to a HEX color string.
8
+ #
9
+ # @param force_alpha [Boolean] indicates if alpha channel should be included in HEX color string
10
+ # when alpha component wasn't specified
11
+ # @return [String] a HEX color string
12
+ def hex(force_alpha: false)
13
+ result = hex6
14
+ result += (alpha * 255).to_i.to_s(16).rjust(2, "0") if alpha < 1 or force_alpha
15
+
16
+ return result
17
+ end
18
+
19
+
20
+ # Convert color to a HEX color string without alpha channel.
21
+ #
22
+ # @return [String] a HEX color string without alpha channel
23
+ def hex6
24
+ result = "#"
25
+ result += red.to_s(16).rjust(2, "0")
26
+ result += green.to_s(16).rjust(2, "0")
27
+ result += blue.to_s(16).rjust(2, "0")
28
+
29
+ return result
30
+ end
31
+
32
+
33
+ # Convert color to RGB color string.
34
+ #
35
+ # @return [String] a RGB color string
36
+ def rgb
37
+ return "rgb(#{red}, #{green}, #{blue})"
38
+ end
39
+
40
+
41
+ # Convert color to RGBA color string.
42
+ #
43
+ # @param alpha_precision [Integer] indicates the precision of alpha value
44
+ #
45
+ # @return [String] a RGBA color string
46
+ def rgba(alpha_precision: 2)
47
+ return "rgba(#{red}, #{green}, #{blue}, #{alpha.round(alpha_precision)})"
48
+ end
49
+
50
+
51
+ # Convert color to HSL color string.
52
+ #
53
+ # @return [String] a HSL color string
54
+ def hsl
55
+ return "hsl(#{hue}, #{(saturation * 100).round}%, #{(lightness * 100).round}%)"
56
+ end
57
+
58
+
59
+ # Convert color to HSL color string.
60
+ #
61
+ # @param alpha_precision [Integer] indicates the precision of alpha value
62
+ #
63
+ # @return [String] a HSL color string
64
+ def hsla(alpha_precision: 2)
65
+ return "hsl(#{hue}, #{(saturation * 100).round}%, #{(lightness * 100).round}%, #{alpha.round(alpha_precision)})"
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,389 @@
1
+ module Inker
2
+ class Color
3
+ # Tools module implements a set of methods useful for color parsing and
4
+ # for getting useful info about color.
5
+ module Tools
6
+ # Regular expression for HEX colors matching
7
+ HEX_REGEX = /^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/
8
+
9
+ # Regular expression for RGB colors matching
10
+ RGB_REGEX = /^rgb\((\d+,){2}\d+\)$/
11
+
12
+ # Regular expression for RGBA colors matching
13
+ RGBA_REGEX = /^rgba\((\d+,){3}\d+(.\d+)?\)$/
14
+
15
+ # Regular expression for HSL colors matching
16
+ HSL_REGEX = /^hsl\((\d+)(,\d+%|,\d+(.\d+)){2}\)$/
17
+
18
+ # Regular expression for HSLA colors matching
19
+ HSLA_REGEX = /^hsla\((\d+)(,\d+%|,\d+(.\d+)){2},\d+(.\d+)?\)$/
20
+
21
+ # Calculate the brightness of a color.
22
+ #
23
+ # @param red [Integer] the value of red component [0-255]
24
+ # @param green [Integer] the value of green component [0-255]
25
+ # @param blue [Integer] the value of blue component [0-255]
26
+ #
27
+ # @return [Integer] a value between 0-255 which indicates the brightness of the color
28
+ def brightness(red, green, blue)
29
+ Math.sqrt(0.299 * red**2 + 0.587 * green**2 + 0.114 * blue**2).round
30
+ end
31
+
32
+
33
+ # Calculate the lightness of a color from RGB components.
34
+ #
35
+ # @param red [Integer] the value of red component [0-255]
36
+ # @param green [Integer] the value of green component [0-255]
37
+ # @param blue [Integer] the value of blue component [0-255]
38
+ #
39
+ # @return [Float] a value in range 0.0-1.0 which indicates the ligthness of the color
40
+ def lightness(red, green, blue)
41
+ min, max = [red, green, blue].minmax
42
+
43
+ return (min + max) / (2.0 * 255)
44
+ end
45
+
46
+
47
+ # Calculate the saturation of a color from RGB components.
48
+ #
49
+ # @param red [Integer] the value of red component [0-255]
50
+ # @param green [Integer] the value of green component [0-255]
51
+ # @param blue [Integer] the value of blue component [0-255]
52
+ #
53
+ # @return [Float] a value in range 0.0-1.0 which indicates the saturation of the color
54
+ def saturation(red, green, blue)
55
+ # return 0 for black and white colors
56
+ return 0 if red == green and red == blue and (red == 0 or red == 255)
57
+
58
+ lightness = lightness(red, green, blue)
59
+ min, max = [red / 255.0, green / 255.0, blue / 255.0].minmax
60
+
61
+ return lightness < 0.5 ? (max - min) / (max + min) : (max - min) / (2.0 - max - min)
62
+ end
63
+
64
+
65
+ # Calculate the HUE value of a color from RGB components.
66
+ #
67
+ # @param red [Integer] the value of red component [0-255]
68
+ # @param green [Integer] the value of green component [0-255]
69
+ # @param blue [Integer] the value of blue component [0-255]
70
+ #
71
+ # @return [Integer] a value in range 0-360 which indicates the HUE value of the color
72
+ def hue(red, green, blue)
73
+ min, max = [red, green, blue].minmax
74
+
75
+ numerator = (max - min).to_f
76
+ return 0 if numerator == 0
77
+
78
+ hue = (red == max) ? (green - blue) / numerator :
79
+ (green == max) ? 2 + (blue - red) / numerator :
80
+ 4 + (red - green) / numerator
81
+ hue = hue * 60
82
+ return (hue < 0 ? hue + 360 : hue).round
83
+ end
84
+
85
+
86
+ # Get RGB values from a color in HSL format.
87
+ #
88
+ # @param hue [Integer] the value of HUE component [0-360]
89
+ # @param saturation [Float] the saturation of the color [0.0-1.0]
90
+ # @param lightness [Float] the lightness of the color [0.0-1.0]
91
+ #
92
+ # @return [Hash] a `Hash` which contains the values of RGB components
93
+ def hsl_to_rgb(hue, saturation, lightness)
94
+ result = nil
95
+ if saturation == 0
96
+ # There's no saturation, so it's a gray scale color, which
97
+ # depends only on brightness
98
+ brightness = (lightness * 255).round
99
+
100
+ # All RGB components are equal to brightness
101
+ result = {
102
+ red: brightness,
103
+ green: brightness,
104
+ blue: brightness
105
+ }
106
+ else
107
+ q = lightness < 0.5 ? lightness * (1 + saturation) : lightness + saturation - lightness * saturation
108
+ p = 2 * lightness - q
109
+ norm_hue = hue / 360.0
110
+
111
+ result = {
112
+ red: (hue_to_rgb(p, q, norm_hue + 1.0/3.0) * 255).round,
113
+ green: (hue_to_rgb(p, q, norm_hue) * 255).round,
114
+ blue: (hue_to_rgb(p, q, norm_hue - 1.0/3.0) * 255).round
115
+ }
116
+ end
117
+
118
+ return result
119
+ end
120
+
121
+
122
+ # Returns a `Boolean` which indicates if color is in HEX format
123
+ #
124
+ # @param color_str [String] a color string
125
+ #
126
+ # @return [Boolean] `true` when color is in HEX format
127
+ def is_hex?(color_str)
128
+ !!(color_str.to_s.downcase.strip =~ HEX_REGEX)
129
+ end
130
+
131
+
132
+ # Returns a `Boolean` which indicates if color is in RGB format
133
+ #
134
+ # @param color_str [String] a color string
135
+ #
136
+ # @return [Boolean] `true` when color is in RGB format
137
+ def is_rgb?(color_str)
138
+ !!(color_str.to_s.downcase.gsub(/\s+/, '') =~ RGB_REGEX)
139
+ end
140
+
141
+
142
+ # Returns a `Boolean` which indicates if color is in RGBA format
143
+ #
144
+ # @param color_str [String] a color string
145
+ #
146
+ # @return [Boolean] `true` when color is in RGBA format
147
+ def is_rgba?(color_str)
148
+ !!(color_str.to_s.downcase.gsub(/\s+/, '') =~ RGBA_REGEX)
149
+ end
150
+
151
+
152
+ # Returns a `Boolean` which indicates if color is in HSL format
153
+ #
154
+ # @param color_str [String] a color string
155
+ #
156
+ # @return [Boolean] `true` when color is in HSL format
157
+ def is_hsl?(color_str)
158
+ !!(color_str.to_s.downcase.gsub(/\s+/, '') =~ HSL_REGEX)
159
+ end
160
+
161
+
162
+ # Returns a `Boolean` which indicates if color is in HSLA format
163
+ #
164
+ # @param color_str [String] a color string
165
+ #
166
+ # @return [Boolean] `true` when color is in HSLA format
167
+ def is_hsla?(color_str)
168
+ !!(color_str.to_s.downcase.gsub(/\s+/, '') =~ HSLA_REGEX)
169
+ end
170
+
171
+
172
+ # Generate a random `Inker::Color`.
173
+ #
174
+ # @param with_alpha [Boolean] when `true` include alpha channel
175
+ #
176
+ # @return [Inker::Color] a random color
177
+ def random(with_alpha: false)
178
+ prefix = with_alpha ? "rgba" : "rgb"
179
+ values = (1..3).map{ (rand * 255).round }
180
+ values << rand.round(2) if with_alpha
181
+
182
+ Inker.color("#{prefix}(#{values.join(",")})")
183
+ end
184
+
185
+
186
+ # A helper for `Inker::Color` generation from RGB components.
187
+ #
188
+ # @param red [Integer] the value of red component [0-255]
189
+ # @param green [Integer] the value of green component [0-255]
190
+ # @param blue [Integer] the value of blue component [0-255]
191
+ #
192
+ # @return [Inker::Color] a `Inker::Color` generated from passed RGB values
193
+ def from_rgb(red, green, blue)
194
+ Inker.color("rgb(#{red}, #{green}, #{blue})")
195
+ end
196
+
197
+
198
+ # A helper for `Inker::Color` generation from RGBA components.
199
+ #
200
+ # @param red [Integer] the value of red component [0-255]
201
+ # @param green [Integer] the value of green component [0-255]
202
+ # @param blue [Integer] the value of blue component [0-255]
203
+ # @param alpha [Float] the value of alpha component [0.0-1.1]
204
+ #
205
+ # @return [Inker::Color] a `Inker::Color` generated from passed RGBA values
206
+ def from_rgba(red, green, blue, alpha)
207
+ Inker.color("rgba(#{red}, #{green}, #{blue}, #{alpha})")
208
+ end
209
+
210
+
211
+ # A helper for `Inker::Color` generation from HSL components.
212
+ #
213
+ # @param hue [Integer] the value of HUE component [0-360]
214
+ # @param saturation [Float] the value of saturation component [0.0-1.0]
215
+ # @param lightness [Float] the value of lightness component [0.0-1.0]
216
+ #
217
+ # @return [Inker::Color] a `Inker::Color` generated from passed HSL values
218
+ def from_hsl(hue, saturation, lightness)
219
+ Inker.color("hsl(#{hue}, #{saturation}, #{lightness})")
220
+ end
221
+
222
+
223
+ # A helper for `Inker::Color` generation from HSLA components.
224
+ #
225
+ # @param hue [Integer] the value of HUE component [0-360]
226
+ # @param saturation [Float] the value of saturation component [0.0-1.0]
227
+ # @param lightness [Float] the value of lightness component [0.0-1.0]
228
+ # @param alpha [Float] the value of alpha component [0.0-1.1]
229
+ #
230
+ # @return [Inker::Color] a `Inker::Color` generated from passed HSLA values
231
+ def from_hsla(hue, saturation, lightness, alpha)
232
+ Inker.color("hsla(#{hue}, #{saturation}, #{lightness}, #{alpha})")
233
+ end
234
+
235
+
236
+ # Use MD5 digest of the string to get hex values from specified positions (by default `[0, 29, 14, 30, 28, 31]`)
237
+ # in order to obtain a color in HEX format which represents the specified string.
238
+ #
239
+ # @params custom_string [String] a string from which to generate a color
240
+ # @params positions [Array] an array of 6 numbers in range 0-31 which indicates the position
241
+ # of hex value to get in order to obtain a 6 chars hex string, which will be the result color
242
+ #
243
+ # @return [Inker::Color] a `Inker::Color` object which represents the color associated to input string
244
+ def from_custom_string(custom_string, positions: [0, 29, 14, 30, 28, 31])
245
+ digest = Digest::MD5.hexdigest(custom_string.to_s)
246
+ Inker.color("##{positions.map{|p| digest[p]}.join}")
247
+ end
248
+
249
+
250
+ # Parse a color string an return it's RGBA components as a hash.
251
+ #
252
+ # @example
253
+ # Inker::Color.parse_color("#FF005544") # returns {:red=>255, :green=>0, :blue=>85, :alpha=>0.4}
254
+ #
255
+ # @param color_str [String] color string to parse
256
+ # @return [Hash] a hash which contains RGBA components of parsed color
257
+ def parse_color(color_str)
258
+ # Normalize input string by stripping white spaces and converting
259
+ # string to downcase
260
+ input = color_str.to_s.strip.downcase
261
+
262
+ # By default result is nil
263
+ result = nil
264
+
265
+ # Try to guess the format of color string and parse it by
266
+ # using the apropriate algorithm
267
+ if is_hex?(input)
268
+ # Parse the string as HEX color
269
+ result = parse_hex(input)
270
+ elsif is_rgb?(input)
271
+ # Parse the string as RGB color
272
+ result = parse_rgb(input)
273
+ elsif is_rgba?(input)
274
+ # Parse the string as RGBA color
275
+ result = parse_rgb(input, is_rgba: true)
276
+ elsif is_hsl?(input)
277
+ # Parse the string as HSL color
278
+ result = parse_hsl(input)
279
+ elsif is_hsla?(input)
280
+ # Parse the string as HSLA color
281
+ result = parse_hsl(input, is_hsla: true)
282
+ else
283
+ # Check if color is in "named color" format
284
+ named_color = Inker.named_colors[input]
285
+ if named_color
286
+ # If a named color has been matched, use it's HEX value and
287
+ # parse it as HEX color
288
+ result = parse_hex(named_color)
289
+ end
290
+ end
291
+
292
+ # If we didn't have any match, throw an ArgumentError error
293
+ raise ArgumentError.new("Unknown color format: #{color_str.to_s.strip.inspect}") if result.nil?
294
+
295
+ return result
296
+ end
297
+
298
+ private
299
+
300
+ # Parse a color string as HEX color.
301
+ #
302
+ # @param color_str [String] input color string
303
+ #
304
+ # @return [Hash] a `Hash` which contains RGBA components of parsed color
305
+ def parse_hex(color_str)
306
+ # Remove the leading '#' character from input color string
307
+ input = color_str.gsub(/^#/, '')
308
+
309
+ # Convert to HEX6 when color is in HEX3 format
310
+ input = input.chars.map{|x| x * 2 }.join if input.length == 3
311
+
312
+ # Get RGB components
313
+ result = {
314
+ red: Integer(input[0..1], 16),
315
+ green: Integer(input[2..3], 16),
316
+ blue: Integer(input[4..5], 16),
317
+ alpha: 1.0
318
+ }
319
+
320
+ # When color is in HEX8 format, get also alpha channel value
321
+ if input.length == 8
322
+ result[:alpha] = Integer(input[6..7], 16) / 255.0
323
+ end
324
+
325
+ return result
326
+ end
327
+
328
+
329
+ # Parse color string as RGB(A) color.
330
+ #
331
+ # @param color_str [String] input RGB(A) color string
332
+ # @param is_rgba [Boolean] indicates if color string is in RGBA format
333
+ #
334
+ # @return [Hash] a `Hash` which contains RGBA components of parsed color
335
+ def parse_rgb(color_str, is_rgba: false)
336
+ components = color_str.gsub(/(^rgb(a)?\(|\)$)/, "").split(",")
337
+
338
+ return {
339
+ red: components.shift.to_i,
340
+ green: components.shift.to_i,
341
+ blue: components.shift.to_i,
342
+ alpha: (is_rgba ? components.shift.to_f : 1.0)
343
+ }
344
+ end
345
+
346
+
347
+ # Parse color string as HSL(A) color.
348
+ #
349
+ # @param color_str [String] input HSL(A) color string
350
+ # @param is_hsla [Boolean] indicates if color string is in HSL(A) format
351
+ #
352
+ # @return [Hash] a `Hash` which contains RGBA components of parsed color
353
+ def parse_hsl(color_str, is_hsla: false)
354
+ components = color_str.gsub(/(^hsl(a)?\(|\)$)/, "").split(",")
355
+
356
+ hue = components.shift.to_i
357
+
358
+ saturation = components.shift
359
+ saturation = saturation.include?("%") ? saturation.to_f / 100 : saturation.to_f
360
+
361
+ lightness = components.shift
362
+ lightness = lightness.include?("%") ? lightness.to_f / 100 : lightness.to_f
363
+
364
+ result = hsl_to_rgb(hue, saturation, lightness)
365
+ result[:alpha] = is_hsla ? components.shift.to_f : 1.0
366
+
367
+ return result
368
+ end
369
+
370
+
371
+ # A helper function which allows to calculate the RGB component value from HSL color.
372
+ #
373
+ # @param p [Float] `2 * lightness -q`
374
+ # @param q [Float] `lightness < 0.5 ? lightness * (1 + saturation) : lightness + saturation - lightness * saturation`
375
+ # @param t [Float] `hue + 1/3`, `hue` or `hue - 1/3` according to which component is going to be calculated [r,g,b]
376
+ #
377
+ # @return [Float] a value which represents a RGB component in range 0.0-1.0
378
+ def hue_to_rgb(p, q, t)
379
+ t += 1 if t < 0
380
+ t -= 1 if t > 1
381
+
382
+ return p + (q - p) * 6 * t if t < 1.0 / 6.0
383
+ return q if t < 1.0 / 2.0
384
+ return p + (q - p) * (2/3 - t) * 6 if t < 2.0 / 3.0
385
+ return p;
386
+ end
387
+ end
388
+ end
389
+ end