gosu 0.7.20 → 0.7.21

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.
@@ -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);