gosu 0.13.3 → 0.14.0.pre2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gosu/Audio.hpp +15 -11
- data/Gosu/Font.hpp +24 -20
- data/Gosu/Fwd.hpp +1 -1
- data/Gosu/Graphics.hpp +8 -9
- data/Gosu/ImageData.hpp +1 -1
- data/Gosu/Input.hpp +1 -1
- data/Gosu/Math.hpp +0 -18
- data/Gosu/Text.hpp +22 -30
- data/Gosu/TextInput.hpp +13 -0
- data/Gosu/Utility.hpp +2 -0
- data/Gosu/Window.hpp +3 -3
- data/README.md +3 -4
- data/ext/gosu/extconf.rb +7 -9
- data/lib/gosu/swig_patches.rb +1 -4
- data/rdoc/gosu.rb +34 -9
- data/src/Audio.cpp +6 -6
- data/src/AudioImpl.cpp +2 -2
- data/src/Bitmap.cpp +1 -2
- data/src/BitmapIO.cpp +21 -2
- data/src/BlockAllocator.cpp +1 -1
- data/src/Channel.cpp +7 -1
- data/src/ClipRectStack.hpp +4 -1
- data/src/Color.cpp +2 -1
- data/src/DirectoriesWin.cpp +1 -1
- data/src/DrawOp.hpp +8 -4
- data/src/DrawOpQueue.hpp +13 -24
- data/src/FileUnix.cpp +3 -1
- data/src/Font.cpp +92 -96
- data/src/GosuGLView.cpp +59 -31
- data/src/GosuGLView.hpp +14 -0
- data/src/GosuViewController.cpp +21 -21
- data/src/{GosuViewController.h → GosuViewController.hpp} +2 -4
- data/src/Graphics.cpp +71 -38
- data/src/GraphicsImpl.hpp +12 -29
- data/src/Image.cpp +5 -7
- data/src/Input.cpp +7 -5
- data/src/InputUIKit.cpp +19 -37
- data/src/Macro.cpp +10 -2
- data/src/MarkupParser.cpp +241 -0
- data/src/MarkupParser.hpp +61 -0
- data/src/Math.cpp +1 -1
- data/src/OffScreenTarget.cpp +99 -0
- data/src/OffScreenTarget.hpp +23 -0
- data/src/OggFile.hpp +10 -0
- data/src/RenderState.hpp +0 -2
- data/src/Resolution.cpp +2 -2
- data/src/RubyGosu.cxx +457 -244
- data/src/TexChunk.cpp +8 -6
- data/src/Text.cpp +58 -345
- data/src/TextBuilder.cpp +138 -0
- data/src/TextBuilder.hpp +55 -0
- data/src/TextInput.cpp +27 -10
- data/src/Texture.cpp +22 -17
- data/src/Texture.hpp +19 -20
- data/src/TimingApple.cpp +5 -7
- data/src/TimingUnix.cpp +1 -4
- data/src/TimingWin.cpp +4 -1
- data/src/TrueTypeFont.cpp +282 -0
- data/src/TrueTypeFont.hpp +66 -0
- data/src/TrueTypeFontApple.cpp +65 -0
- data/src/TrueTypeFontUnix.cpp +91 -0
- data/src/TrueTypeFontWin.cpp +82 -0
- data/src/Utility.cpp +40 -0
- data/src/Window.cpp +7 -6
- data/src/WindowUIKit.cpp +9 -4
- data/src/stb_truetype.h +4589 -0
- data/src/utf8proc.c +755 -0
- data/src/utf8proc.h +699 -0
- data/src/utf8proc_data.h +14386 -0
- metadata +23 -16
- data/src/FormattedString.cpp +0 -237
- data/src/FormattedString.hpp +0 -47
- data/src/GosuAppDelegate.cpp +0 -30
- data/src/GosuAppDelegate.h +0 -8
- data/src/GosuGLView.h +0 -8
- data/src/TextApple.cpp +0 -212
- data/src/TextTTFWin.cpp +0 -197
- data/src/TextUnix.cpp +0 -280
- data/src/TextWin.cpp +0 -191
data/src/TexChunk.cpp
CHANGED
@@ -3,21 +3,23 @@
|
|
3
3
|
#include "Texture.hpp"
|
4
4
|
#include <Gosu/Bitmap.hpp>
|
5
5
|
#include <Gosu/Graphics.hpp>
|
6
|
+
#include <stdexcept>
|
7
|
+
|
6
8
|
using namespace std;
|
7
9
|
|
8
10
|
void Gosu::TexChunk::set_tex_info()
|
9
11
|
{
|
10
|
-
double
|
12
|
+
double width = texture->width(), height = texture->height();
|
11
13
|
|
12
14
|
info.tex_name = texture->tex_name();
|
13
|
-
info.left = x /
|
14
|
-
info.top = y /
|
15
|
-
info.right = (x + w) /
|
16
|
-
info.bottom = (y + h) /
|
15
|
+
info.left = x / width;
|
16
|
+
info.top = y / height;
|
17
|
+
info.right = (x + w) / width;
|
18
|
+
info.bottom = (y + h) / height;
|
17
19
|
}
|
18
20
|
|
19
21
|
Gosu::TexChunk::TexChunk(shared_ptr<Texture> texture, int x, int y, int w, int h, int padding)
|
20
|
-
: texture(texture), x(x), y(y), w(w), h(h), padding(padding)
|
22
|
+
: texture(move(texture)), x(x), y(y), w(w), h(h), padding(padding)
|
21
23
|
{
|
22
24
|
set_tex_info();
|
23
25
|
}
|
data/src/Text.cpp
CHANGED
@@ -1,371 +1,84 @@
|
|
1
|
-
#include "
|
1
|
+
#include "MarkupParser.hpp"
|
2
|
+
#include "TextBuilder.hpp"
|
2
3
|
#include "GraphicsImpl.hpp"
|
3
|
-
#include
|
4
|
-
#include <Gosu/Graphics.hpp>
|
5
|
-
#include <Gosu/Image.hpp>
|
6
|
-
#include <Gosu/Math.hpp>
|
4
|
+
#include "TrueTypeFont.hpp"
|
7
5
|
#include <Gosu/Text.hpp>
|
8
|
-
#include <Gosu/Utility.hpp>
|
9
6
|
#include <cassert>
|
10
7
|
#include <cmath>
|
11
8
|
#include <algorithm>
|
12
|
-
#include <map>
|
13
9
|
#include <vector>
|
14
10
|
using namespace std;
|
15
11
|
|
16
|
-
|
12
|
+
double Gosu::text_width(const u32string& text,
|
13
|
+
const string& font_name, double font_height, unsigned font_flags)
|
17
14
|
{
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
// of the cases.
|
22
|
-
bool is_breaking_asian_glyph(wchar_t ch)
|
23
|
-
{
|
24
|
-
return (ch >= 0x3040 && ch <= 0x3096) || // Hiragana
|
25
|
-
(ch >= 0x30a0 && ch <= 0x30fa) || // Katakana
|
26
|
-
(ch >= 0x4e00 && ch <= 0x9fff) || // CJK Unfied Ideographs
|
27
|
-
(ch >= 0x3400 && ch <= 0x4db5); // CJK Unified Ideographs Extension A
|
28
|
-
}
|
29
|
-
|
30
|
-
struct WordInfo
|
31
|
-
{
|
32
|
-
FormattedString text;
|
33
|
-
unsigned width;
|
34
|
-
unsigned space_width;
|
35
|
-
};
|
36
|
-
typedef vector<WordInfo> Words;
|
37
|
-
|
38
|
-
// Local helper class which manages building the bitmap from the
|
39
|
-
// collected words.
|
40
|
-
class TextBlockBuilder
|
41
|
-
{
|
42
|
-
Bitmap bmp;
|
43
|
-
unsigned used_lines, allocated_lines;
|
44
|
-
|
45
|
-
string font_name;
|
46
|
-
unsigned font_height;
|
47
|
-
int line_spacing;
|
48
|
-
Alignment align;
|
49
|
-
|
50
|
-
unsigned space_width_;
|
51
|
-
|
52
|
-
void alloc_next_line()
|
53
|
-
{
|
54
|
-
++used_lines;
|
55
|
-
if (used_lines == allocated_lines) {
|
56
|
-
allocated_lines += 10;
|
57
|
-
bmp.resize(bmp.width(),
|
58
|
-
font_height * allocated_lines + line_spacing * (allocated_lines - 1),
|
59
|
-
0x00ffffff);
|
60
|
-
}
|
61
|
-
}
|
62
|
-
|
63
|
-
public:
|
64
|
-
TextBlockBuilder(const string& font_name, unsigned font_height, int line_spacing,
|
65
|
-
unsigned width, Alignment align)
|
66
|
-
{
|
67
|
-
used_lines = 0;
|
68
|
-
allocated_lines = 10;
|
69
|
-
|
70
|
-
bmp.resize(width, (line_spacing + font_height) * allocated_lines, 0x00ffffff);
|
71
|
-
|
72
|
-
this->font_name = font_name;
|
73
|
-
this->font_height = font_height;
|
74
|
-
this->line_spacing = line_spacing;
|
75
|
-
this->align = align;
|
76
|
-
|
77
|
-
space_width_ = text_width(FormattedString(L" ", 0));
|
78
|
-
}
|
79
|
-
|
80
|
-
unsigned width() const
|
81
|
-
{
|
82
|
-
return bmp.width();
|
83
|
-
}
|
84
|
-
|
85
|
-
unsigned text_width(const FormattedString& text) const
|
86
|
-
{
|
87
|
-
if (text.length() == 0) {
|
88
|
-
return 0;
|
89
|
-
}
|
90
|
-
|
91
|
-
if (text.entity_at(0)) {
|
92
|
-
return entity_bitmap(text.entity_at(0)).width();
|
93
|
-
}
|
94
|
-
|
95
|
-
vector<FormattedString> parts = text.split_parts();
|
96
|
-
unsigned result = 0;
|
97
|
-
for (auto& part : parts) {
|
98
|
-
string text = wstring_to_utf8(part.unformat());
|
99
|
-
result += Gosu::text_width(text, font_name, font_height, part.flags_at(0));
|
100
|
-
}
|
101
|
-
return result;
|
102
|
-
}
|
103
|
-
|
104
|
-
void add_line(Words::const_iterator begin, Words::const_iterator end,
|
105
|
-
unsigned words_width, bool override_align)
|
106
|
-
{
|
107
|
-
alloc_next_line();
|
108
|
-
|
109
|
-
auto words = end - begin;
|
110
|
-
|
111
|
-
unsigned total_spacing = 0;
|
112
|
-
if (begin < end) {
|
113
|
-
for (auto i = begin; i != end - 1; ++i) {
|
114
|
-
total_spacing += i->space_width;
|
115
|
-
}
|
116
|
-
}
|
117
|
-
|
118
|
-
// Where does the line start? (y)
|
119
|
-
int top = (used_lines - 1) * (font_height + line_spacing);
|
120
|
-
|
121
|
-
// Where does the line start? (x)
|
122
|
-
int pos;
|
123
|
-
switch (align) {
|
124
|
-
// Start so that the text touches the right border.
|
125
|
-
case AL_RIGHT:
|
126
|
-
pos = bmp.width() - words_width - total_spacing;
|
127
|
-
break;
|
128
|
-
|
129
|
-
// Start so that the text is centered.
|
130
|
-
case AL_CENTER:
|
131
|
-
pos = bmp.width() - words_width - total_spacing;
|
132
|
-
pos /= 2;
|
133
|
-
break;
|
134
|
-
|
135
|
-
// Just start at the left border.
|
136
|
-
default:
|
137
|
-
pos = 0;
|
138
|
-
}
|
139
|
-
|
140
|
-
for (auto cur = begin; cur != end; ++cur) {
|
141
|
-
vector<FormattedString> parts = cur->text.split_parts();
|
142
|
-
int x = 0;
|
143
|
-
for (auto& part : parts) {
|
144
|
-
if (part.entity_at(0)) {
|
145
|
-
Gosu::Bitmap entity = entity_bitmap(part.entity_at(0));
|
146
|
-
multiply_bitmap_alpha(entity, part.color_at(0).alpha());
|
147
|
-
bmp.insert(entity, pos + x, top);
|
148
|
-
x += entity.width();
|
149
|
-
continue;
|
150
|
-
}
|
151
|
-
|
152
|
-
string unformatted_part = wstring_to_utf8(part.unformat());
|
153
|
-
draw_text(bmp, unformatted_part, pos + x, top,
|
154
|
-
part.color_at(0), font_name, font_height, part.flags_at(0));
|
155
|
-
|
156
|
-
x += Gosu::text_width(unformatted_part, font_name, font_height,
|
157
|
-
part.flags_at(0));
|
158
|
-
}
|
159
|
-
|
160
|
-
if (align == AL_JUSTIFY && !override_align) {
|
161
|
-
pos += cur->width + 1.0 * (width() - words_width) / (words - 1);
|
162
|
-
}
|
163
|
-
else {
|
164
|
-
pos += cur->width + cur->space_width;
|
165
|
-
}
|
166
|
-
}
|
167
|
-
}
|
168
|
-
|
169
|
-
void add_empty_line()
|
170
|
-
{
|
171
|
-
alloc_next_line();
|
172
|
-
}
|
173
|
-
|
174
|
-
Bitmap result() const
|
175
|
-
{
|
176
|
-
Bitmap result = bmp;
|
177
|
-
result.resize(result.width(),
|
178
|
-
font_height * used_lines + line_spacing * (used_lines - 1),
|
179
|
-
0x00ffffff);
|
180
|
-
return result;
|
181
|
-
}
|
182
|
-
|
183
|
-
unsigned space_width() const
|
184
|
-
{
|
185
|
-
return space_width_;
|
186
|
-
}
|
187
|
-
};
|
188
|
-
|
189
|
-
void process_words(TextBlockBuilder& builder, const Words& words)
|
190
|
-
{
|
191
|
-
if (words.empty()) return builder.add_empty_line();
|
192
|
-
|
193
|
-
// Index into words to the first word in the current line.
|
194
|
-
auto line_begin = words.begin();
|
195
|
-
|
196
|
-
// Used width, in pixels, of the words [line_begin..w[.
|
197
|
-
unsigned words_width = 0;
|
198
|
-
|
199
|
-
// Used width of the spaces between (w-line_begin) words.
|
200
|
-
unsigned spaces_width = 0;
|
201
|
-
|
202
|
-
for (auto w = words.begin(); w != words.end(); ++w) {
|
203
|
-
unsigned new_words_width = words_width + w->width;
|
204
|
-
|
205
|
-
if (new_words_width + spaces_width <= builder.width()) {
|
206
|
-
// There's enough space for the words [line_begin..w] plus
|
207
|
-
// the spaces between them: Proceed with the next word.
|
208
|
-
words_width = new_words_width;
|
209
|
-
spaces_width += w->space_width;
|
210
|
-
}
|
211
|
-
else {
|
212
|
-
// No, this word wouldn't fit into the current line: Draw
|
213
|
-
// the current line, then start a new line with the current
|
214
|
-
// word.
|
215
|
-
builder.add_line(line_begin, w, words_width, false);
|
216
|
-
|
217
|
-
line_begin = w;
|
218
|
-
words_width = w->width;
|
219
|
-
spaces_width = w->space_width;
|
220
|
-
}
|
221
|
-
}
|
222
|
-
|
223
|
-
// Draw the last line as well.
|
224
|
-
if (words.empty() || line_begin != words.end()) {
|
225
|
-
builder.add_line(line_begin, words.end(), words_width, true);
|
226
|
-
}
|
227
|
-
}
|
228
|
-
|
229
|
-
void process_paragraph(TextBlockBuilder& builder, const FormattedString& paragraph)
|
230
|
-
{
|
231
|
-
Words collected_words;
|
232
|
-
|
233
|
-
unsigned begin_of_word = 0;
|
234
|
-
|
235
|
-
for (unsigned cur = 0; cur < paragraph.length(); ++cur) {
|
236
|
-
WordInfo new_word;
|
237
|
-
|
238
|
-
if (paragraph.char_at(cur) == L' ') {
|
239
|
-
// Whitespace:
|
240
|
-
// Add last word to list if existent
|
241
|
-
if (begin_of_word != cur) {
|
242
|
-
new_word.text = paragraph.range(begin_of_word, cur);
|
243
|
-
new_word.width = builder.text_width(new_word.text);
|
244
|
-
new_word.space_width = builder.space_width();
|
245
|
-
collected_words.push_back(new_word);
|
246
|
-
}
|
247
|
-
begin_of_word = cur + 1;
|
248
|
-
}
|
249
|
-
else if (is_breaking_asian_glyph(paragraph.char_at(cur))) {
|
250
|
-
// Asian glyph (treat as single word):
|
251
|
-
// Add last word to list if existent
|
252
|
-
if (begin_of_word != cur) {
|
253
|
-
new_word.text = paragraph.range(begin_of_word, cur);
|
254
|
-
new_word.width = builder.text_width(new_word.text);
|
255
|
-
new_word.space_width = 0;
|
256
|
-
collected_words.push_back(new_word);
|
257
|
-
}
|
258
|
-
// Add glyph as a single "word"
|
259
|
-
new_word.text = paragraph.range(cur, cur + 1);
|
260
|
-
new_word.width = builder.text_width(new_word.text);
|
261
|
-
new_word.space_width = 0;
|
262
|
-
collected_words.push_back(new_word);
|
263
|
-
begin_of_word = cur + 1;
|
264
|
-
}
|
265
|
-
}
|
266
|
-
if (begin_of_word < paragraph.length()) {
|
267
|
-
WordInfo last_word;
|
268
|
-
last_word.text = paragraph.range(begin_of_word, paragraph.length());
|
269
|
-
last_word.width = builder.text_width(last_word.text);
|
270
|
-
last_word.space_width = 0;
|
271
|
-
collected_words.push_back(last_word);
|
272
|
-
}
|
273
|
-
|
274
|
-
process_words(builder, collected_words);
|
275
|
-
}
|
15
|
+
auto& font = font_by_name(font_name, font_flags);
|
16
|
+
return font.draw_text(text, font_height, nullptr, 0, 0, Gosu::Color::NONE);
|
17
|
+
}
|
276
18
|
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
}
|
283
|
-
}
|
284
|
-
}
|
19
|
+
double Gosu::draw_text(Bitmap& bitmap, double x, double y, Color c, const u32string& text,
|
20
|
+
const string& font_name, double font_height, unsigned font_flags)
|
21
|
+
{
|
22
|
+
auto& font = font_by_name(font_name, font_flags);
|
23
|
+
return font.draw_text(text, font_height, &bitmap, x, y, c);
|
285
24
|
}
|
286
25
|
|
287
|
-
Gosu::Bitmap Gosu::create_text(const string& text, const string& font_name,
|
288
|
-
|
26
|
+
Gosu::Bitmap Gosu::create_text(const string& text, const string& font_name, double font_height,
|
27
|
+
double line_spacing, int width, Alignment align, unsigned font_flags)
|
289
28
|
{
|
290
|
-
if (
|
291
|
-
|
292
|
-
|
29
|
+
if (font_height <= 0) throw invalid_argument("font_height must be > 0");
|
30
|
+
if (width <= 0) throw invalid_argument("width must be > 0");
|
31
|
+
if (line_spacing < -font_height) throw invalid_argument("line_spacing must be ≥ -font_height");
|
293
32
|
|
294
|
-
|
295
|
-
FormattedString fs(wtext.c_str(), font_flags);
|
296
|
-
if (fs.length() == 0) {
|
297
|
-
return Bitmap(width, font_height);
|
298
|
-
}
|
33
|
+
TextBuilder text_builder(font_name, font_height, line_spacing, width, align);
|
299
34
|
|
300
|
-
//
|
301
|
-
//
|
302
|
-
|
35
|
+
// Feed all formatted substrings to the TextBuilder, which will construct the resulting bitmap.
|
36
|
+
// Split the input string into words here, because this method implements word-wrapping.
|
37
|
+
MarkupParser(text.c_str(), font_flags, true, [&text_builder](vector<FormattedString> word) {
|
38
|
+
text_builder.feed_word(move(word));
|
39
|
+
}).parse();
|
303
40
|
|
304
|
-
|
305
|
-
process_text(builder, fs);
|
306
|
-
|
307
|
-
// Done!
|
308
|
-
return builder.result();
|
41
|
+
return text_builder.move_into_bitmap();
|
309
42
|
}
|
310
43
|
|
311
|
-
|
312
|
-
|
313
|
-
unsigned font_flags)
|
44
|
+
Gosu::Bitmap Gosu::create_text(const string& text, const string& font_name, double font_height,
|
45
|
+
unsigned font_flags)
|
314
46
|
{
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
47
|
+
if (font_height <= 0) throw invalid_argument("font_height must be > 0");
|
48
|
+
|
49
|
+
vector<vector<FormattedString>> lines;
|
50
|
+
|
51
|
+
// Split the text into lines (split_words = false) since this method does not break lines.
|
52
|
+
MarkupParser(text.c_str(), font_flags, false, [&lines](vector<FormattedString>&& line) {
|
53
|
+
// Remove trailing \n characters from each line to avoid errors from Gosu::text_width().
|
54
|
+
if (line.back().text.back() == '\n') {
|
55
|
+
line.back().text.pop_back();
|
56
|
+
}
|
57
|
+
|
58
|
+
lines.emplace_back(line);
|
59
|
+
}).parse();
|
320
60
|
|
321
|
-
|
61
|
+
// Measure every part of every line.
|
62
|
+
int width = 0;
|
63
|
+
for (auto& line : lines) {
|
64
|
+
int line_width = 0;
|
65
|
+
for (auto& part : line) {
|
66
|
+
line_width += text_width(part.text, font_name, font_height, part.flags);
|
67
|
+
}
|
68
|
+
width = max(width, line_width);
|
69
|
+
}
|
322
70
|
|
323
|
-
Bitmap
|
71
|
+
Bitmap result(width, static_cast<int>(ceil(lines.size() * font_height)));
|
324
72
|
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
if (part.length() == 1 && part.entity_at(0)) {
|
332
|
-
Gosu::Bitmap entity = entity_bitmap(part.entity_at(0));
|
333
|
-
multiply_bitmap_alpha(entity, part.color_at(0).alpha());
|
334
|
-
bmp.resize(max(bmp.width(), x + entity.width()), bmp.height(), 0x00ffffff);
|
335
|
-
bmp.insert(entity, x, i * font_height);
|
336
|
-
x += entity.width();
|
337
|
-
continue;
|
338
|
-
}
|
339
|
-
|
340
|
-
assert (part.length() > 0);
|
341
|
-
string unformatted_text = wstring_to_utf8(part.unformat());
|
342
|
-
unsigned part_width = text_width(unformatted_text, font_name, font_height,
|
343
|
-
part.flags_at(0));
|
344
|
-
bmp.resize(max(bmp.width(), x + part_width), bmp.height(), 0x00ffffff);
|
345
|
-
draw_text(bmp, unformatted_text, x, i * font_height, part.color_at(0), font_name,
|
346
|
-
font_height, part.flags_at(0));
|
347
|
-
x += part_width;
|
73
|
+
// Render every part of every line.
|
74
|
+
int y = 0;
|
75
|
+
for (auto& line : lines) {
|
76
|
+
int x = 0;
|
77
|
+
for (auto& part : line) {
|
78
|
+
x += draw_text(result, x, y, part.color, part.text, font_name, font_height);
|
348
79
|
}
|
80
|
+
y += font_height;
|
349
81
|
}
|
350
82
|
|
351
|
-
return
|
352
|
-
}
|
353
|
-
|
354
|
-
static map<string, shared_ptr<Gosu::Bitmap>> entities;
|
355
|
-
|
356
|
-
void Gosu::register_entity(const string& name, const Gosu::Bitmap& replacement)
|
357
|
-
{
|
358
|
-
entities[name].reset(new Bitmap(replacement));
|
359
|
-
}
|
360
|
-
|
361
|
-
bool Gosu::is_entity(const string& name)
|
362
|
-
{
|
363
|
-
return entities[name].get();
|
364
|
-
}
|
365
|
-
|
366
|
-
const Gosu::Bitmap& Gosu::entity_bitmap(const string& name)
|
367
|
-
{
|
368
|
-
shared_ptr<Gosu::Bitmap>& ptr = entities[name];
|
369
|
-
if (!ptr) throw runtime_error("Unknown entity: " + name);
|
370
|
-
return *ptr;
|
83
|
+
return result;
|
371
84
|
}
|