freetype 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,149 @@
1
+ require 'freetype/api'
2
+
3
+ module FreeTypeApiTest
4
+ include FreeType::API
5
+
6
+ def libopen
7
+ Library.open do |lib|
8
+ ['data/Prida01.otf', 'data/Starjedi.ttf'].each do |font|
9
+ lib.face_open(font) do |f|
10
+ f.set_char_size(0, 0, 300, 300)
11
+ yield f
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ def test_Library(t)
18
+ lib = nil
19
+ ret = Library.open do |l|
20
+ lib = l
21
+
22
+ unless /\A\d+\.\d+\.\d+\z/.match l.version
23
+ t.error "return value break got #{l.version}"
24
+ end
25
+
26
+ :abc
27
+ end
28
+ if lib.nil?
29
+ t.error('cannot get FT_Library in `open` with block')
30
+ end
31
+ if ret != :abc
32
+ t.error 'want to return last value in block'
33
+ end
34
+ end
35
+
36
+ def test_Face(t)
37
+ face = nil
38
+ Library.open do |lib|
39
+ ['data/Prida01.otf', 'data/Starjedi.ttf'].each do |font|
40
+ lib.face_open(font) do |f|
41
+ face = f
42
+ if f.char_index('a') == 0
43
+ t.error('ascii char not defined this font')
44
+ end
45
+ if f.char_index('㍿') != 0
46
+ t.error("I don't know why set character was defined in font")
47
+ end
48
+
49
+ v = f.kerning('A', 'W')
50
+ unless v
51
+ t.error('#kerning return object was changed')
52
+ end
53
+ unless Fixnum === v.x && Fixnum === v.y
54
+ t.error('Not vector object. Check spec for FT_Get_Kerning()')
55
+ end
56
+
57
+ if /darwin/ =~ RUBY_PLATFORM
58
+ begin
59
+ err = StringIO.new
60
+ origerr = $stderr
61
+ $stderr = err
62
+ f.glyph('a')
63
+ rescue FreeType::Error::Invalid_Size_Handle
64
+ if err.string.empty?
65
+ t.error('recommend warn miss?')
66
+ end
67
+ else
68
+ t.error('check freetype spec')
69
+ ensure
70
+ $stderr = origerr
71
+ end
72
+ end
73
+
74
+ f.set_char_size(0, 0, 300, 300)
75
+
76
+ bbox = f.bbox
77
+ unless BBox === bbox
78
+ t.error('FreeType::API::Face#bbox return value was break')
79
+ end
80
+
81
+ unless Glyph === f.glyph('a')
82
+ t.error 'return value was break'
83
+ end
84
+
85
+ # unless Glyph === f.notdef
86
+ # t.error 'return value was break'
87
+ # end
88
+ end
89
+ end
90
+ end
91
+ if face.nil?
92
+ t.error('cannot get FT_Face in `open` with block')
93
+ end
94
+ end
95
+
96
+ def test_glyph(t)
97
+ libopen do |f|
98
+ table = { 'a' => nil, 'b' => nil, 'c' => nil, 'd' => nil }
99
+ table.each do |char, _|
100
+ glyph = f.glyph(char)
101
+
102
+ metrics = glyph.metrics
103
+ unless FreeType::C::FT_Glyph_Metrics === metrics
104
+ t.error 'return value was break'
105
+ end
106
+
107
+ space_width = glyph.space_width
108
+ unless Fixnum === space_width
109
+ t.error 'return value was break'
110
+ end
111
+
112
+ outline = glyph.outline
113
+ unless Outline === outline
114
+ t.error('FreeType::API::Face#outline return value was break')
115
+ end
116
+ end
117
+ end
118
+ end
119
+
120
+ def test_outline(t)
121
+ libopen do |f|
122
+ table = { 'a' => nil, 'b' => nil, 'c' => nil, 'd' => nil }
123
+ table.each do |char, _|
124
+ outline = f.glyph(char).outline
125
+
126
+ unless 0 < outline.points.length
127
+ t.error('FT_Outline.points get failed from ffi')
128
+ end
129
+
130
+ unless outline.points.all? { |i| Point === i }
131
+ t.error('Miss array of FreeType::API::Outline#points objects assignment')
132
+ end
133
+
134
+ unless outline.tags.all? { |i| Fixnum === i }
135
+ t.error('Got values miss assigned from ffi')
136
+ end
137
+
138
+ unless outline.contours.all? { |i| Fixnum === i }
139
+ t.error('Got values miss assigned from ffi')
140
+ end
141
+
142
+ table[char] = outline.points.map(&:x)
143
+ end
144
+ if table.values.uniq.length != table.length
145
+ t.error 'char reference miss'
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,243 @@
1
+ require 'ffi'
2
+
3
+ module FreeType
4
+ # low level APIs call by FFI
5
+ module C
6
+ extend ::FFI::Library
7
+ ffi_lib ['libfreetype.dylib', 'libfreetype.6.dylib', 'freetype.so', 'freetype.so.6', 'freetype']
8
+ typedef :long, :FT_Pos
9
+ typedef :long, :FT_Fixed
10
+ typedef :long, :FT_F26Dot6
11
+ typedef :int, :FT_Error
12
+
13
+ FT_ENC_TAG = lambda do |a, b, c, d|
14
+ [a, b, c, d].map(&:ord).inject(0) { |r, i| (r << 8) + i }
15
+ end
16
+
17
+ FT_IMAGE_TAG = FT_ENC_TAG
18
+
19
+ # http://www.freetype.org/freetype2/docs/reference/ft2-basic_types.html#FT_Glyph_Format
20
+ FT_Glyph_Format = enum(
21
+ :FT_GLYPH_FORMAT_NONE, 0,
22
+ :FT_GLYPH_FORMAT_COMPOSITE, FT_IMAGE_TAG['c', 'o', 'm', 'p'],
23
+ :FT_GLYPH_FORMAT_BITMAP, FT_IMAGE_TAG['b', 'i', 't', 's'],
24
+ :FT_GLYPH_FORMAT_OUTLINE, FT_IMAGE_TAG['o', 'u', 't', 'l'],
25
+ :FT_GLYPH_FORMAT_PLOTTER, FT_IMAGE_TAG['p', 'l', 'o', 't'],
26
+ )
27
+
28
+ # http://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_Kerning_Mode
29
+ FT_Kerning_Mode = enum(
30
+ :FT_KERNING_DEFAULT, 0,
31
+ :FT_KERNING_UNFITTED,
32
+ :FT_KERNING_UNSCALED,
33
+ )
34
+
35
+ FT_Encoding = enum(
36
+ :FT_ENCODING_NONE, 0,
37
+ :FT_ENCODING_MS_SYMBOL, FT_ENC_TAG['s', 'y', 'm', 'b'],
38
+ :FT_ENCODING_UNICODE, FT_ENC_TAG['u', 'n', 'i', 'c'],
39
+ :FT_ENCODING_SJIS, FT_ENC_TAG['s', 'j', 'i', 's'],
40
+ :FT_ENCODING_GB2312, FT_ENC_TAG['g', 'b', ' ', ' '],
41
+ :FT_ENCODING_BIG5, FT_ENC_TAG['b', 'i', 'g', '5'],
42
+ :FT_ENCODING_WANSUNG, FT_ENC_TAG['w', 'a', 'n', 's'],
43
+ :FT_ENCODING_JOHAB, FT_ENC_TAG['j', 'o', 'h', 'a'],
44
+ :FT_ENCODING_ADOBE_STANDARD, FT_ENC_TAG['A', 'D', 'O', 'B'],
45
+ :FT_ENCODING_ADOBE_EXPERT, FT_ENC_TAG['A', 'D', 'B', 'E'],
46
+ :FT_ENCODING_ADOBE_CUSTOM, FT_ENC_TAG['A', 'D', 'B', 'C'],
47
+ :FT_ENCODING_ADOBE_LATIN_1, FT_ENC_TAG['l', 'a', 't', '1'],
48
+ :FT_ENCODING_OLD_LATIN_2, FT_ENC_TAG['l', 'a', 't', '2'],
49
+ :FT_ENCODING_APPLE_ROMAN, FT_ENC_TAG['a', 'r', 'm', 'n'],
50
+ )
51
+
52
+ # http://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_LOAD_XXX
53
+ FT_LOAD_DEFAULT = 0x0
54
+ FT_LOAD_NO_SCALE = 1 << 0
55
+ FT_LOAD_NO_HINTING = 1 << 1
56
+ FT_LOAD_RENDER = 1 << 2
57
+ FT_LOAD_NO_BITMAP = 1 << 3
58
+ FT_LOAD_VERTICAL_LAYOUT = 1 << 4
59
+ FT_LOAD_FORCE_AUTOHINT = 1 << 5
60
+ FT_LOAD_CROP_BITMAP = 1 << 6
61
+ FT_LOAD_PEDANTIC = 1 << 7
62
+ FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH = 1 << 9
63
+ FT_LOAD_NO_RECURSE = 1 << 10
64
+ FT_LOAD_IGNORE_TRANSFORM = 1 << 11
65
+ FT_LOAD_MONOCHROME = 1 << 12
66
+ FT_LOAD_LINEAR_DESIGN = 1 << 13
67
+ FT_LOAD_NO_AUTOHINT = 1 << 15
68
+ FT_LOAD_COLOR = 1 << 20
69
+ FT_LOAD_COMPUTE_METRICS = 1 << 21
70
+
71
+ # http://www.freetype.org/freetype2/docs/reference/ft2-basic_types.html#FT_BBox
72
+ class FT_BBox < ::FFI::Struct
73
+ layout xMin: :FT_Pos,
74
+ yMin: :FT_Pos,
75
+ xMax: :FT_Pos,
76
+ yMax: :FT_Pos
77
+ end
78
+
79
+ # http://www.freetype.org/freetype2/docs/reference/ft2-basic_types.html#FT_Bitmap
80
+ class FT_Bitmap < ::FFI::Struct
81
+ layout rows: :uint,
82
+ width: :uint,
83
+ pitch: :int,
84
+ buffer: :pointer,
85
+ num_grays: :ushort,
86
+ pixel_mode: :char,
87
+ palette_mode: :char,
88
+ palette: :pointer
89
+ end
90
+
91
+ # http://www.freetype.org/freetype2/docs/reference/ft2-basic_types.html#FT_Generic
92
+ class FT_Generic < ::FFI::Struct
93
+ layout data: :pointer,
94
+ finalizer: :pointer
95
+ end
96
+
97
+ # http://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_Glyph_Metrics
98
+ class FT_Glyph_Metrics < ::FFI::Struct
99
+ layout width: :FT_Pos,
100
+ height: :FT_Pos,
101
+ horiBearingX: :FT_Pos,
102
+ horiBearingY: :FT_Pos,
103
+ horiAdvance: :FT_Pos,
104
+ vertBearingX: :FT_Pos,
105
+ vertBearingY: :FT_Pos,
106
+ vertAdvance: :FT_Pos
107
+ end
108
+
109
+ # http://www.freetype.org/freetype2/docs/reference/ft2-outline_processing.html#FT_Outline
110
+ class FT_Outline < ::FFI::Struct
111
+ layout n_contours: :short,
112
+ n_points: :short,
113
+ points: :pointer, # FT_Vector* (n_points)
114
+ tags: :pointer, # char * (n_points)
115
+ contours: :pointer, # short * (n_contours)
116
+ # http://www.freetype.org/freetype2/docs/reference/ft2-outline_processing.html#FT_OUTLINE_XXX
117
+ flags: :int
118
+ end
119
+
120
+ # http://www.freetype.org/freetype2/docs/reference/ft2-basic_types.html#FT_Vector
121
+ class FT_Vector < ::FFI::Struct
122
+ layout x: :FT_Pos,
123
+ y: :FT_Pos
124
+ end
125
+
126
+ # http://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_GlyphSlotRec
127
+ class FT_GlyphSlotRec < ::FFI::Struct
128
+ layout library: :pointer,
129
+ face: :pointer,
130
+ next: :pointer,
131
+ reserved: :uint,
132
+ generic: FT_Generic,
133
+ metrics: FT_Glyph_Metrics,
134
+ linearHoriAdvance: :FT_Fixed,
135
+ linearVertAdvance: :FT_Fixed,
136
+ advance: FT_Vector,
137
+ format: FT_Glyph_Format,
138
+ bitmap: FT_Bitmap,
139
+ bitmap_left: :int,
140
+ bitmap_top: :int,
141
+ outline: FT_Outline,
142
+ num_subglyphs: :uint,
143
+ subglyphs: :pointer, # FT_SubGlyph
144
+ control_data: :pointer, # void *
145
+ control_len: :long,
146
+ lsb_delta: :FT_Pos,
147
+ rsb_delta: :FT_Pos,
148
+ other: :pointer, # void *
149
+ internal: :pointer # FT_Slot_Internal
150
+ end
151
+
152
+ # http://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_Size_Metrics
153
+ class FT_Size_Metrics < ::FFI::Struct
154
+ layout x_ppem: :ushort,
155
+ y_ppem: :ushort,
156
+ x_scale: :FT_Fixed,
157
+ y_scale: :FT_Fixed,
158
+ ascender: :FT_Pos,
159
+ descender: :FT_Pos,
160
+ height: :FT_Pos,
161
+ max_advance: :FT_Pos
162
+ end
163
+
164
+ # http://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_SizeRec
165
+ class FT_SizeRec < ::FFI::Struct
166
+ layout face: :pointer, # FT_Face
167
+ generic: FT_Generic,
168
+ metrics: FT_Size_Metrics,
169
+ internal: :pointer # FT_Size_Internal
170
+ end
171
+
172
+ class FT_CharMapRec < ::FFI::Struct
173
+ layout face: :pointer,
174
+ encoding: FT_Encoding,
175
+ platform_id: :ushort,
176
+ encoding_id: :ushort
177
+ end
178
+
179
+ # http://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_FaceRec
180
+ class FT_FaceRec < ::FFI::Struct
181
+ layout num_faces: :long,
182
+ face_index: :long,
183
+ face_flags: :long,
184
+ style_flags: :long,
185
+ num_glyphs: :long,
186
+ family_name: :string,
187
+ style_name: :string,
188
+ num_fixed_sizes: :int,
189
+ available_sizes: :pointer, # FT_Bitmap_Size*
190
+ num_charmaps: :int,
191
+ charmaps: FT_CharMapRec.ptr,
192
+ generic: FT_Generic,
193
+ bbox: FT_BBox,
194
+ units_per_EM: :ushort,
195
+ ascender: :short,
196
+ descender: :short,
197
+ height: :short,
198
+ max_advance_width: :short,
199
+ max_advance_height: :short,
200
+ underline_position: :short,
201
+ underline_thickness: :short,
202
+ glyph: FT_GlyphSlotRec.ptr,
203
+ size: FT_SizeRec.ptr,
204
+ charmap: :pointer
205
+ end
206
+
207
+ # library = FFI::MemoryPointer.new(:pointer)
208
+ # err = FT_Init_FreeType(library)
209
+ # err = FT_Done_Library(library.get_pointer(0))
210
+ attach_function :FT_Init_FreeType, [:pointer], :FT_Error
211
+ attach_function :FT_Done_Library, [:pointer], :FT_Error
212
+ attach_function :FT_Library_Version, [:pointer, :pointer, :pointer, :pointer], :void
213
+
214
+ # face = FFI::MemoryPointer.new(:pointer)
215
+ # err = FT_New_Face(library.get_pointer(0), 'font.otf', 0, face)
216
+ # face = FT_FaceRec.new(face.get_pointer(0))
217
+ # err = FT_Done_Face(face)
218
+ attach_function :FT_New_Face, [:pointer, :string, :long, :pointer], :FT_Error
219
+ attach_function :FT_Done_Face, [:pointer], :FT_Error
220
+
221
+ # err = FT_Set_Char_Size(face, 0, 36 * 64, 300, 300)
222
+ attach_function :FT_Set_Char_Size, [:pointer, :FT_F26Dot6, :FT_F26Dot6, :uint, :uint], :FT_Error
223
+
224
+ # err = FT_Select_Charmap(face, :FT_ENCODING_UNICODE)
225
+ attach_function :FT_Select_Charmap, [:pointer, FT_Encoding], :FT_Error
226
+
227
+ # err = FT_Load_Char(face, 'Q'.ord, FreeType::FT_LOAD_DEFAULT)
228
+ attach_function :FT_Load_Char, [:pointer, :ulong, :int32], :FT_Error
229
+
230
+ # err = FT_Load_Glyph(face, 0, FT_LOAD_DEFAULT)
231
+ # attach_function :FT_Load_Glyph, [FT_FaceRec.ptr, :uint, :int32], :FT_Error
232
+ # attach_function :FT_Get_Glyph, [FT_GlyphSlotRec.ptr, :pointer], :FT_Error
233
+ # attach_function :FT_Done_Glyph, [:pointer], :void
234
+
235
+ # id = FT_Get_Char_Index(face, 'A'.ord) -> glyph id or 0 (undefined)
236
+ attach_function :FT_Get_Char_Index, [:pointer, :ulong], :uint
237
+
238
+ # v = FT_Vector.new
239
+ # err = FT_Get_Kerning(face, before_id, id, :FT_KERNING_DEFAULT, v)
240
+ # p v
241
+ attach_function :FT_Get_Kerning, [:pointer, :uint, :uint, :uint, :pointer], :FT_Error
242
+ end
243
+ end
@@ -0,0 +1,173 @@
1
+ require 'freetype/c'
2
+
3
+ module FFITest
4
+ include FreeType::C
5
+
6
+ FONTS = ['data/Prida01.otf', 'data/Starjedi.ttf']
7
+
8
+ def libopen
9
+ library = ::FFI::MemoryPointer.new(:pointer)
10
+ err = FT_Init_FreeType(library)
11
+ raise FreeType::Error.find(err) unless err == 0
12
+
13
+ FONTS.each do |font|
14
+ face = ::FFI::MemoryPointer.new(:pointer)
15
+ err = FT_New_Face(library.get_pointer(0), font, 0, face)
16
+ raise FreeType::Error.find(err) unless err == 0
17
+
18
+ yield FT_FaceRec.new(face.get_pointer(0)), font
19
+ end
20
+ end
21
+
22
+ def test_Library(t)
23
+ library = ::FFI::MemoryPointer.new(:pointer)
24
+ err = FT_Init_FreeType(library)
25
+ if err != 0
26
+ t.fatal FreeType::Error.find(err).message
27
+ end
28
+
29
+ amajor = FFI::MemoryPointer.new(:int)
30
+ aminor = FFI::MemoryPointer.new(:int)
31
+ apatch = FFI::MemoryPointer.new(:int)
32
+ FT_Library_Version(library.get_pointer(0), amajor, aminor, apatch)
33
+ a = [
34
+ amajor.get_int(0),
35
+ aminor.get_int(0),
36
+ apatch.get_int(0),
37
+ ]
38
+ unless a.all? { |i| Fixnum === i }
39
+ t.error 'miss get values from FT_Library_Version()'
40
+ end
41
+ end
42
+
43
+ def test_Face(t)
44
+ library = ::FFI::MemoryPointer.new(:pointer)
45
+ err = FT_Init_FreeType(library)
46
+ if err != 0
47
+ t.fatal FreeType::Error.find(err).message
48
+ end
49
+ FONTS.each do |font|
50
+ face = ::FFI::MemoryPointer.new(:pointer)
51
+ err = FT_New_Face(library.get_pointer(0), font, 0, face)
52
+ if err != 0
53
+ t.fatal FreeType::Error.find(err).message
54
+ end
55
+
56
+ face = FT_FaceRec.new(face.get_pointer(0))
57
+ err = FT_Select_Charmap(face, :FT_ENCODING_UNICODE)
58
+ if err != 0
59
+ t.error FreeType::Error.find(err).message
60
+ end
61
+ end
62
+ end
63
+
64
+ def test_FT_Set_Char_Size(t)
65
+ libopen do |face|
66
+ if /darwin/ =~ RUBY_PLATFORM
67
+ err = FT_Load_Char(face, 'a'.ord, FreeType::C::FT_LOAD_DEFAULT)
68
+ e = FreeType::Error.find(err)
69
+ unless FreeType::Error::Invalid_Size_Handle === e
70
+ t.fatal 'check freetype spec'
71
+ end
72
+ end
73
+
74
+ err = FT_Set_Char_Size(face, 0, 32, 300, 300)
75
+ if err != 0
76
+ t.error FreeType::Error.find(err).message
77
+ end
78
+
79
+ err = FT_Load_Char(face, 'a'.ord, FreeType::C::FT_LOAD_DEFAULT)
80
+ if err != 0
81
+ t.error FreeType::Error.find(err).message
82
+ end
83
+ end
84
+ end
85
+
86
+ def test_char(t)
87
+ libopen do |face, _font|
88
+ err = FT_Set_Char_Size(face, 0, 32, 300, 300)
89
+ if err != 0
90
+ t.fatal FreeType::Error.find(err).message
91
+ end
92
+
93
+ before_glyph_id = nil
94
+ %w(i e f g A W & * @ % - + < >).concat([' ', 'あ', ' ', "\n"]).each do |char|
95
+ glyph_id = FT_Get_Char_Index(face, char.ord)
96
+ if glyph_id == 0
97
+ unless /あ| |\n/.match(char)
98
+ t.error('ascii char is undefined')
99
+ end
100
+ next
101
+ end
102
+
103
+ if before_glyph_id
104
+ v = FT_Vector.new
105
+ err = FT_Get_Kerning(face, before_glyph_id, glyph_id, :FT_KERNING_UNFITTED, v)
106
+ if err != 0
107
+ t.error FreeType::Error.find(err).message
108
+ end
109
+ unless Fixnum === v[:x] && Fixnum === v[:y]
110
+ t.error 'cannot get kerning value from FT_Get_Kerning()'
111
+ end
112
+ end
113
+
114
+ err = FT_Load_Char(face, char.ord, FreeType::C::FT_LOAD_DEFAULT)
115
+ if err != 0
116
+ t.error FreeType::Error.find(err).message
117
+ end
118
+
119
+ size = face[:size]
120
+ unless FT_SizeRec === size
121
+ t.error 'Miss Struct bind'
122
+ end
123
+
124
+ size_metrics = face[:size][:metrics]
125
+ unless FT_Size_Metrics === size_metrics
126
+ t.error 'Miss Struct bind'
127
+ end
128
+
129
+ glyph = face[:glyph]
130
+ unless FT_GlyphSlotRec === glyph
131
+ t.error 'Miss Struct bind'
132
+ end
133
+
134
+ glyph_metrics = face[:glyph][:metrics]
135
+ unless FT_Glyph_Metrics === glyph_metrics
136
+ t.error 'Miss Struct bind'
137
+ end
138
+
139
+ outline = face[:glyph][:outline]
140
+ unless 0 <= outline[:n_points]
141
+ t.error "n_outline:#{outline[:n_points]} Cannot get FT_Outline.n_prints member from ffi"
142
+ end
143
+
144
+ unless 0 <= outline[:n_contours]
145
+ t.error "n_contours:#{outline[:n_contours]} Cannot get FT_Outline.n_contours member from ffi"
146
+ end
147
+
148
+ end_ptd_of_counts = outline[:contours].get_array_of_short(0, outline[:n_contours])
149
+
150
+ unless end_ptd_of_counts.all? { |i| Fixnum === i }
151
+ t.error 'FT_Outline.contours is array if short. broken or fail when get form ffi.'
152
+ end
153
+
154
+ tags = outline[:tags].get_array_of_char(0, outline[:n_points])
155
+ unless tags.all? { |i| Fixnum === i }
156
+ t.error 'FT_Outline.tags is array of char. broken or fail when get form ffi.'
157
+ end
158
+
159
+ points = outline[:n_points].times.map do |i|
160
+ FT_Vector.new(outline[:points] + i * FT_Vector.size)
161
+ end
162
+
163
+ points.each do |i|
164
+ unless i[:x].kind_of?(Fixnum) && i[:y].kind_of?(Fixnum)
165
+ t.error('Miss assignment from ffi')
166
+ end
167
+ end
168
+
169
+ before_glyph_id = glyph_id
170
+ end
171
+ end
172
+ end
173
+ end