gosu 0.7.20 → 0.7.21

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,12 +4,16 @@
4
4
  #include <Gosu/Image.hpp>
5
5
  #include <Gosu/Math.hpp>
6
6
  #include <Gosu/Utility.hpp>
7
- #include <boost/bind.hpp>
7
+ #include <GosuImpl/Graphics/Common.hpp>
8
+ #include <GosuImpl/Graphics/FormattedString.hpp>
8
9
  #include <boost/algorithm/string.hpp>
9
- #include <boost/tokenizer.hpp>
10
+ #include <boost/bind.hpp>
11
+ #include <boost/foreach.hpp>
12
+ #include <boost/shared_ptr.hpp>
10
13
  #include <cassert>
11
14
  #include <cmath>
12
15
  #include <algorithm>
16
+ #include <map>
13
17
  #include <vector>
14
18
  using namespace std;
15
19
 
@@ -38,7 +42,7 @@ namespace Gosu
38
42
 
39
43
  struct WordInfo
40
44
  {
41
- wstring text;
45
+ FormattedString text;
42
46
  unsigned width;
43
47
  unsigned spaceWidth;
44
48
  };
@@ -52,10 +56,10 @@ namespace Gosu
52
56
  unsigned usedLines, allocatedLines;
53
57
 
54
58
  wstring fontName;
55
- unsigned fontHeight, fontFlags, lineSpacing;
59
+ unsigned fontHeight, lineSpacing;
56
60
  TextAlign align;
57
61
 
58
- unsigned spaceWidth;
62
+ unsigned spaceWidth_;
59
63
 
60
64
  void allocNextLine()
61
65
  {
@@ -71,8 +75,7 @@ namespace Gosu
71
75
 
72
76
  public:
73
77
  TextBlockBuilder(const wstring& fontName, unsigned fontHeight,
74
- unsigned fontFlags, unsigned lineSpacing, unsigned width,
75
- TextAlign align)
78
+ unsigned lineSpacing, unsigned width, TextAlign align)
76
79
  {
77
80
  usedLines = 0;
78
81
  allocatedLines = 10;
@@ -81,11 +84,10 @@ namespace Gosu
81
84
 
82
85
  this->fontName = fontName;
83
86
  this->fontHeight = fontHeight;
84
- this->fontFlags = fontFlags;
85
87
  this->lineSpacing = lineSpacing;
86
88
  this->align = align;
87
89
 
88
- spaceWidth = textWidth(L" ");
90
+ spaceWidth_ = textWidth(FormattedString(L" ", 0));
89
91
  }
90
92
 
91
93
  unsigned width() const
@@ -93,9 +95,19 @@ namespace Gosu
93
95
  return bmp.width();
94
96
  }
95
97
 
96
- unsigned textWidth(const std::wstring& text) const
98
+ unsigned textWidth(const FormattedString& text) const
97
99
  {
98
- return Gosu::textWidth(text, fontName, fontHeight, fontFlags);
100
+ if (text.length() == 0)
101
+ return 0;
102
+
103
+ if (text.entityAt(0))
104
+ return entityBitmap(text.entityAt(0)).width();
105
+
106
+ std::vector<FormattedString> parts = text.splitParts();
107
+ unsigned result = 0;
108
+ BOOST_FOREACH (const FormattedString& part, parts)
109
+ result += Gosu::textWidth(part.unformat(), fontName, fontHeight, part.flagsAt(0));
110
+ return result;
99
111
  }
100
112
 
101
113
  void addLine(Words::const_iterator begin, Words::const_iterator end,
@@ -134,9 +146,26 @@ namespace Gosu
134
146
 
135
147
  for (Words::const_iterator cur = begin; cur != end; ++cur)
136
148
  {
137
- drawText(bmp, cur->text, trunc(pos), trunc(top),
138
- Color::WHITE, fontName, fontHeight, fontFlags);
139
-
149
+ std::vector<FormattedString> parts = cur->text.splitParts();
150
+ int x = 0;
151
+ BOOST_FOREACH (const FormattedString& part, parts)
152
+ {
153
+ if (part.entityAt(0))
154
+ {
155
+ Gosu::Bitmap entity = entityBitmap(part.entityAt(0));
156
+ multiplyBitmapAlpha(entity, part.colorAt(0).alpha());
157
+ bmp.insert(entity, trunc(pos) + x, trunc(top));
158
+ x += entity.width();
159
+ continue;
160
+ }
161
+
162
+ std::wstring unformattedPart = part.unformat();
163
+ drawText(bmp, unformattedPart, trunc(pos) + x, trunc(top),
164
+ part.colorAt(0), fontName, fontHeight, part.flagsAt(0));
165
+ x += Gosu::textWidth(unformattedPart, fontName, fontHeight,
166
+ part.flagsAt(0));
167
+ }
168
+
140
169
  if (align == taJustify && !overrideAlign)
141
170
  pos += cur->width + 1.0 * (width() - wordsWidth) / (words - 1);
142
171
  else
@@ -156,6 +185,11 @@ namespace Gosu
156
185
  usedLines * (lineSpacing + fontHeight));
157
186
  return result;
158
187
  }
188
+
189
+ unsigned spaceWidth() const
190
+ {
191
+ return spaceWidth_;
192
+ }
159
193
  };
160
194
 
161
195
  void processWords(TextBlockBuilder& builder, const Words& words)
@@ -200,47 +234,41 @@ namespace Gosu
200
234
  if (words.empty() || lineBegin != words.end())
201
235
  builder.addLine(lineBegin, words.end(), wordsWidth, true);
202
236
  }
203
-
204
- typedef boost::tokenizer<boost::char_separator<wchar_t>,
205
- wstring::const_iterator, wstring> Tokenizer;
206
-
207
- void processParagraph(TextBlockBuilder& builder,
208
- const wstring& paragraph)
237
+
238
+ void processParagraph(TextBlockBuilder& builder, const FormattedString& paragraph)
209
239
  {
210
240
  Words collectedWords;
211
-
212
- const unsigned spaceWidth = builder.textWidth(L" ");
213
-
241
+
214
242
  unsigned beginOfWord = 0;
215
243
 
216
244
  for (unsigned cur = 0; cur < paragraph.length(); ++cur)
217
245
  {
218
246
  WordInfo newWord;
219
247
 
220
- if (paragraph[cur] == L' ')
248
+ if (paragraph.charAt(cur) == L' ')
221
249
  {
222
250
  // Whitespace: Add last word to list if existent
223
251
  if (beginOfWord != cur)
224
252
  {
225
- newWord.text.assign(paragraph.begin() + beginOfWord, paragraph.begin() + cur);
253
+ newWord.text = paragraph.range(beginOfWord, cur);
226
254
  newWord.width = builder.textWidth(newWord.text);
227
- newWord.spaceWidth = spaceWidth;
255
+ newWord.spaceWidth = builder.spaceWidth();
228
256
  collectedWords.push_back(newWord);
229
257
  }
230
258
  beginOfWord = cur + 1;
231
259
  }
232
- else if (isBreakingAsianGlyph(paragraph[cur]))
260
+ else if (isBreakingAsianGlyph(paragraph.charAt(cur)))
233
261
  {
234
262
  // Whitespace: Add last word to list if existent
235
263
  if (beginOfWord != cur)
236
264
  {
237
- newWord.text.assign(paragraph.begin() + beginOfWord, paragraph.begin() + cur);
265
+ newWord.text = paragraph.range(beginOfWord, cur);
238
266
  newWord.width = builder.textWidth(newWord.text);
239
267
  newWord.spaceWidth = 0;
240
268
  collectedWords.push_back(newWord);
241
269
  }
242
270
  // Add glyph as a single "word"
243
- newWord.text = wstring(1, paragraph[cur]);
271
+ newWord.text = paragraph.range(cur, cur + 2);
244
272
  newWord.width = builder.textWidth(newWord.text);
245
273
  newWord.spaceWidth = 0;
246
274
  collectedWords.push_back(newWord);
@@ -250,7 +278,7 @@ namespace Gosu
250
278
  if (beginOfWord < paragraph.length())
251
279
  {
252
280
  WordInfo lastWord;
253
- lastWord.text.assign(paragraph.begin() + beginOfWord, paragraph.end());
281
+ lastWord.text = paragraph.range(beginOfWord, paragraph.length());
254
282
  lastWord.width = builder.textWidth(lastWord.text);
255
283
  lastWord.spaceWidth = 0;
256
284
  collectedWords.push_back(lastWord);
@@ -259,14 +287,11 @@ namespace Gosu
259
287
  processWords(builder, collectedWords);
260
288
  }
261
289
 
262
- void processText(TextBlockBuilder& builder, const wstring& text)
290
+ void processText(TextBlockBuilder& builder, const FormattedString& text)
263
291
  {
264
- vector<wstring> paragraphs;
265
- wstring processedText = boost::replace_all_copy(text, L"\r\n", L"\n");
266
- boost::split(paragraphs, processedText,
267
- boost::is_any_of(L"\r\n"));
268
- for_each(paragraphs.begin(), paragraphs.end(),
269
- boost::bind(processParagraph, boost::ref(builder), _1));
292
+ vector<FormattedString> paragraphs = text.splitLines();
293
+ BOOST_FOREACH (FormattedString& fs, paragraphs)
294
+ processParagraph(builder, fs);
270
295
  }
271
296
  }
272
297
  }
@@ -275,7 +300,8 @@ Gosu::Bitmap Gosu::createText(const std::wstring& text,
275
300
  const std::wstring& fontName, unsigned fontHeight, unsigned lineSpacing,
276
301
  unsigned maxWidth, TextAlign align, unsigned fontFlags)
277
302
  {
278
- if (text.empty())
303
+ FormattedString fs(boost::replace_all_copy(text, L"\r\n", L"\n"), fontFlags);
304
+ if (fs.length() == 0)
279
305
  {
280
306
  Bitmap emptyBitmap;
281
307
  emptyBitmap.resize(1, fontHeight);
@@ -284,11 +310,11 @@ Gosu::Bitmap Gosu::createText(const std::wstring& text,
284
310
 
285
311
  // Set up the builder object which will manage all the drawing and
286
312
  // conversions for us.
287
- TextBlockBuilder builder(fontName, fontHeight, fontFlags,
313
+ TextBlockBuilder builder(fontName, fontHeight,
288
314
  lineSpacing, maxWidth, align);
289
315
 
290
316
  // Let the process* functions draw everything.
291
- processText(builder, text);
317
+ processText(builder, fs);
292
318
 
293
319
  // Done!
294
320
  return builder.result();
@@ -298,27 +324,62 @@ Gosu::Bitmap Gosu::createText(const std::wstring& text,
298
324
  Gosu::Bitmap Gosu::createText(const std::wstring& text,
299
325
  const std::wstring& fontName, unsigned fontHeight, unsigned fontFlags)
300
326
  {
301
- if (text.empty())
302
- {
303
- Bitmap emptyBitmap;
304
- emptyBitmap.resize(1, fontHeight);
305
- return emptyBitmap;
306
- }
307
-
308
- vector<wstring> lines;
309
- wstring processedText = boost::replace_all_copy(text, L"\r\n", L"\n");
310
- boost::split(lines, processedText, boost::is_any_of(L"\r\n"));
311
-
312
327
  Bitmap bmp;
313
- bmp.resize(textWidth(lines.front(), fontName, fontHeight, fontFlags), fontHeight);
314
- drawText(bmp, text, 0, 0, 0xffffffff, fontName, fontHeight, fontFlags);
328
+ bmp.resize(1, fontHeight);
329
+
330
+ FormattedString fs(boost::replace_all_copy(text, L"\r\n", L"\n"), fontFlags);
331
+ if (fs.length() == 0)
332
+ return bmp;
315
333
 
316
- for (int i = 1; i < lines.size(); ++i)
334
+ vector<FormattedString> lines = fs.splitLines();
335
+
336
+ for (int i = 0; i < lines.size(); ++i)
317
337
  {
318
- bmp.resize(max(bmp.width(), textWidth(lines[i], fontName, fontHeight, fontFlags)),
319
- bmp.height() + fontHeight);
320
- drawText(bmp, lines[i], 0, fontHeight * i, 0xffffffff, fontName, fontHeight, fontFlags);
338
+ bmp.resize(bmp.width(), (i + 1) * fontHeight);
339
+
340
+ if (lines[i].length() == 0)
341
+ continue;
342
+
343
+ unsigned x = 0;
344
+ std::vector<FormattedString> parts = lines[i].splitParts();
345
+ for (int p = 0; p < parts.size(); ++p)
346
+ {
347
+ const FormattedString& part = parts[p];
348
+ if (part.length() == 1 && part.entityAt(0))
349
+ {
350
+ Gosu::Bitmap entity = entityBitmap(part.entityAt(0));
351
+ multiplyBitmapAlpha(entity, part.colorAt(0).alpha());
352
+ bmp.resize(std::max(bmp.width(), x + entity.width()), bmp.height());
353
+ bmp.insert(entity, x, i * fontHeight);
354
+ x += entity.width();
355
+ continue;
356
+ }
357
+
358
+ assert(part.length() > 0);
359
+ std::wstring unformattedText = part.unformat();
360
+ unsigned partWidth =
361
+ textWidth(unformattedText, fontName, fontHeight, part.flagsAt(0));
362
+ bmp.resize(std::max(bmp.width(), x + partWidth), bmp.height());
363
+ drawText(bmp, unformattedText, x, i * fontHeight, part.colorAt(0),
364
+ fontName, fontHeight, part.flagsAt(0));
365
+ x += partWidth;
366
+ }
321
367
  }
322
368
 
323
369
  return bmp;
324
370
  }
371
+
372
+ namespace
373
+ {
374
+ std::map<std::wstring, boost::shared_ptr<Gosu::Bitmap> > entities;
375
+ }
376
+
377
+ void Gosu::registerEntity(const std::wstring& name, const Gosu::Bitmap& replacement)
378
+ {
379
+ entities[name].reset(new Bitmap(replacement));
380
+ }
381
+
382
+ const Gosu::Bitmap& Gosu::entityBitmap(const std::wstring& name)
383
+ {
384
+ return *entities[name];
385
+ }
@@ -79,25 +79,30 @@ namespace
79
79
  #endif
80
80
  }
81
81
 
82
- OSXFont* getFont(wstring fontName, double height)
82
+ OSXFont* getFont(wstring fontName, unsigned fontFlags, double height)
83
83
  {
84
84
  fontName = normalizeFont(fontName);
85
85
 
86
- static map<pair<wstring, double>, OSXFont*> usedFonts;
86
+ static map<pair<wstring, pair<unsigned, double> >, OSXFont*> usedFonts;
87
87
 
88
- OSXFont* result = usedFonts[make_pair(fontName, height)];
88
+ OSXFont* result = usedFonts[make_pair(fontName, make_pair(fontFlags, height))];
89
89
  if (!result)
90
90
  {
91
91
  Gosu::ObjRef<NSString> name([[NSString alloc] initWithUTF8String: Gosu::wstringToUTF8(fontName).c_str()]);
92
92
  #ifdef GOSU_IS_IPHONE
93
- result = [[OSXFont fontWithName: name.obj() size: height] retain];
93
+ result = [OSXFont fontWithName: name.obj() size: height];
94
94
  #else
95
- NSFontDescriptor* desc = [[[NSFontDescriptor alloc] initWithFontAttributes:nil] fontDescriptorWithFamily:name.obj()];
95
+ NSFontDescriptor* desc = [[NSFontDescriptor fontDescriptorWithFontAttributes:nil] fontDescriptorWithFamily:name.obj()];
96
96
  result = [[NSFont fontWithDescriptor:desc size:height] retain];
97
+ if (result && (fontFlags & Gosu::ffBold))
98
+ result = [[NSFontManager sharedFontManager] convertFont:result toHaveTrait:NSFontBoldTrait];
99
+ if (result && (fontFlags & Gosu::ffItalic))
100
+ result = [[NSFontManager sharedFontManager] convertFont:result toHaveTrait:NSFontItalicTrait];
97
101
  #endif
98
102
  if (!result && fontName != Gosu::defaultFontName())
99
- result = getFont(Gosu::defaultFontName(), height);
100
- usedFonts[make_pair(fontName, height)] = result;
103
+ result = getFont(Gosu::defaultFontName(), 0, height);
104
+ assert(result);
105
+ usedFonts[make_pair(fontName, make_pair(fontFlags, height))] = [result retain];
101
106
  }
102
107
  return result;
103
108
  }
@@ -110,17 +115,46 @@ wstring Gosu::defaultFontName()
110
115
  return L"Arial";
111
116
  }
112
117
 
118
+ #ifndef GOSU_IS_IPHONE
119
+ namespace
120
+ {
121
+ NSDictionary* attributeDictionary(NSFont* font, Gosu::Color color, unsigned fontFlags)
122
+ {
123
+ static std::map<Gosu::Color, NSColor*> colorCache;
124
+
125
+ // Because of the way we later copy the buffer directly to a Gosu::Bitmap, we
126
+ // need to swap the color components already.
127
+ color = color.abgr();
128
+
129
+ if (!colorCache[color])
130
+ colorCache[color] = [[NSColor colorWithDeviceRed:color.red()/255.0
131
+ green:color.green()/255.0 blue:color.blue()/255.0 alpha:color.alpha()/255.0] retain];
132
+
133
+ NSMutableDictionary* dict =
134
+ [[NSMutableDictionary alloc] initWithObjectsAndKeys:
135
+ font, NSFontAttributeName,
136
+ colorCache[color], NSForegroundColorAttributeName,
137
+ nil];
138
+ if (fontFlags & Gosu::ffUnderline)
139
+ {
140
+ Gosu::ObjRef<NSNumber> underline([[NSNumber alloc] initWithInt:NSUnderlineStyleSingle]);
141
+ [dict setValue: underline.obj() forKey:NSUnderlineStyleAttributeName];
142
+ }
143
+ return dict;
144
+ }
145
+ }
146
+ #endif
147
+
113
148
  unsigned Gosu::textWidth(const wstring& text,
114
149
  const wstring& fontName, unsigned fontHeight, unsigned fontFlags)
115
150
  {
116
- OSXFont* font = getFont(fontName, fontHeight);
151
+ OSXFont* font = getFont(fontName, fontFlags, fontHeight);
117
152
 
118
153
  // This will, of course, compute a too large size; fontHeight is in pixels,
119
154
  // the method expects point.
120
155
  ObjRef<NSString> string([[NSString alloc] initWithUTF8String: wstringToUTF8(text).c_str()]);
121
156
  #ifndef GOSU_IS_IPHONE
122
- ObjRef<NSDictionary> attributes([[NSDictionary alloc] initWithObjectsAndKeys:
123
- font, NSFontAttributeName, nil]);
157
+ ObjRef<NSDictionary> attributes(attributeDictionary(font, 0xffffffff, fontFlags));
124
158
  NSSize size = [string.obj() sizeWithAttributes: attributes.get()];
125
159
  #else
126
160
  CGSize size = [string.obj() sizeWithFont: font];
@@ -134,13 +168,12 @@ void Gosu::drawText(Bitmap& bitmap, const wstring& text, int x, int y,
134
168
  Color c, const wstring& fontName, unsigned fontHeight,
135
169
  unsigned fontFlags)
136
170
  {
137
- OSXFont* font = getFont(fontName, fontHeight);
171
+ OSXFont* font = getFont(fontName, fontFlags, fontHeight);
138
172
  ObjRef<NSString> string([[NSString alloc] initWithUTF8String: wstringToUTF8(text).c_str()]);
139
173
 
140
174
  // This will, of course, compute a too large size; fontHeight is in pixels, the method expects point.
141
175
  #ifndef GOSU_IS_IPHONE
142
- ObjRef<NSDictionary> attributes([[NSDictionary alloc] initWithObjectsAndKeys:
143
- font, NSFontAttributeName, nil]);
176
+ ObjRef<NSDictionary> attributes(attributeDictionary(font, c, fontFlags));
144
177
  NSSize size = [string.obj() sizeWithAttributes: attributes.get()];
145
178
  #else
146
179
  CGSize size = [string.obj() sizeWithFont: font];
@@ -160,14 +193,14 @@ void Gosu::drawText(Bitmap& bitmap, const wstring& text, int x, int y,
160
193
  colorSpace,
161
194
  kCGImageAlphaPremultipliedLast);
162
195
  CGColorSpaceRelease(colorSpace);
163
- #if defined(GOSU_IS_IPHONE)
196
+ #ifdef GOSU_IS_IPHONE
164
197
  CGFloat color[] = { c.green() / 255.0, c.blue() / 255.0, c.red() / 255.0, 0 };
165
198
  CGContextSetStrokeColor(context, color);
166
199
  CGContextSetFillColor(context, color);
167
200
  #endif
168
201
 
169
202
  // Use new font with proper size this time.
170
- font = getFont(fontName, fontHeight * fontHeight / size.height);
203
+ font = getFont(fontName, fontFlags, fontHeight * fontHeight / size.height);
171
204
 
172
205
  #ifdef GOSU_IS_IPHONE
173
206
  CGContextTranslateCTM(context, 0, fontHeight);
@@ -177,8 +210,7 @@ void Gosu::drawText(Bitmap& bitmap, const wstring& text, int x, int y,
177
210
  UIGraphicsPopContext();
178
211
  #else
179
212
  NSPoint NSPointZero = { 0, 0 };
180
- attributes.reset([[NSDictionary alloc] initWithObjectsAndKeys:
181
- font, NSFontAttributeName, [NSColor whiteColor], NSForegroundColorAttributeName, nil]);
213
+ attributes.reset(attributeDictionary(font, c, fontFlags));
182
214
 
183
215
  [NSGraphicsContext saveGraphicsState];
184
216
  [NSGraphicsContext setCurrentContext:
@@ -149,8 +149,7 @@ namespace Gosu
149
149
  if (x + x2 < 0 || x + x2 >= bitmap.width())
150
150
  break;
151
151
  unsigned val = ft_bitmap.buffer[y2*width+x2];
152
- Color color = c;
153
- color.setAlpha(val);
152
+ Color color = multiply(c, Gosu::Color(val, 255, 255, 255));
154
153
  bitmap.setPixel(x2 + x, y2 + y, color);
155
154
  }
156
155
  }
@@ -173,9 +173,8 @@ void Gosu::drawText(Bitmap& bitmap, const std::wstring& text, int x, int y,
173
173
  for (unsigned relX = 0; relX < width; ++relX)
174
174
  {
175
175
  Color pixel = c;
176
- pixel.setAlpha(GetPixel(helper.context(), relX, relY) & 0xff);
177
- // IMPR: Make better use of c.alpha()?
178
- // Maybe even add an AlphaMode parameter?
176
+ Color::Channel srcAlpha = GetPixel(helper.context(), relX, relY) & 0xff;
177
+ pixel = multiply(c, Color(srcAlpha, 255, 255, 255));
179
178
  if (pixel != 0 && x + relX >= 0 && x + relX < bitmap.width() &&
180
179
  y + relY >= 0 && y + relY < bitmap.height())
181
180
  bitmap.setPixel(x + relX, y + relY, pixel);