1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #include "graphics/font.h"
24 #include "graphics/managed_surface.h"
25
26 #include "common/array.h"
27 #include "common/util.h"
28
29 namespace Graphics {
30
getKerningOffset(uint32 left,uint32 right) const31 int Font::getKerningOffset(uint32 left, uint32 right) const {
32 return 0;
33 }
34
getBoundingBox(uint32 chr) const35 Common::Rect Font::getBoundingBox(uint32 chr) const {
36 return Common::Rect(getCharWidth(chr), getFontHeight());
37 }
38
39 namespace {
40
41 template<class StringType>
getBoundingBoxImpl(const Font & font,const StringType & str,int x,int y,int w,TextAlign align,int deltax)42 Common::Rect getBoundingBoxImpl(const Font &font, const StringType &str, int x, int y, int w, TextAlign align, int deltax) {
43 // We follow the logic of drawStringImpl here. The only exception is
44 // that we do allow an empty width to be specified here. This allows us
45 // to obtain the complete bounding box of a string.
46 const int leftX = x, rightX = w ? (x + w) : 0x7FFFFFFF;
47 int width = font.getStringWidth(str);
48
49 if (align == kTextAlignCenter)
50 x = x + (w - width)/2;
51 else if (align == kTextAlignRight)
52 x = x + w - width;
53 x += deltax;
54
55 bool first = true;
56 Common::Rect bbox;
57
58 typename StringType::unsigned_type last = 0;
59 for (typename StringType::const_iterator i = str.begin(), end = str.end(); i != end; ++i) {
60 const typename StringType::unsigned_type cur = *i;
61 x += font.getKerningOffset(last, cur);
62 last = cur;
63 w = font.getCharWidth(cur);
64 if (x+w > rightX)
65 break;
66 if (x+w >= leftX) {
67 Common::Rect charBox = font.getBoundingBox(cur);
68 charBox.translate(x, y);
69 if (first) {
70 bbox = charBox;
71 first = false;
72 } else {
73 bbox.extend(charBox);
74 }
75 }
76 x += w;
77 }
78
79 return bbox;
80 }
81
82 template<class StringType>
getStringWidthImpl(const Font & font,const StringType & str)83 int getStringWidthImpl(const Font &font, const StringType &str) {
84 int space = 0;
85 typename StringType::unsigned_type last = 0;
86
87 for (uint i = 0; i < str.size(); ++i) {
88 const typename StringType::unsigned_type cur = str[i];
89 space += font.getCharWidth(cur) + font.getKerningOffset(last, cur);
90 last = cur;
91 }
92
93 return space;
94 }
95
96 template<class StringType>
drawStringImpl(const Font & font,Surface * dst,const StringType & str,int x,int y,int w,uint32 color,TextAlign align,int deltax)97 void drawStringImpl(const Font &font, Surface *dst, const StringType &str, int x, int y, int w, uint32 color, TextAlign align, int deltax) {
98 // The logic in getBoundingImpl is the same as we use here. In case we
99 // ever change something here we will need to change it there too.
100 assert(dst != 0);
101
102 const int leftX = x, rightX = x + w;
103 int width = font.getStringWidth(str);
104
105 if (align == kTextAlignCenter)
106 x = x + (w - width)/2;
107 else if (align == kTextAlignRight)
108 x = x + w - width;
109 x += deltax;
110
111 typename StringType::unsigned_type last = 0;
112 for (typename StringType::const_iterator i = str.begin(), end = str.end(); i != end; ++i) {
113 const typename StringType::unsigned_type cur = *i;
114 x += font.getKerningOffset(last, cur);
115 last = cur;
116 w = font.getCharWidth(cur);
117 if (x+w > rightX)
118 break;
119 if (x+w >= leftX)
120 font.drawChar(dst, cur, x, y, color);
121 x += w;
122 }
123 }
124
125 template<class StringType>
126 struct WordWrapper {
127 Common::Array<StringType> &lines;
128 int actualMaxLineWidth;
129
WordWrapperGraphics::__anond3eae2410111::WordWrapper130 WordWrapper(Common::Array<StringType> &l) : lines(l), actualMaxLineWidth(0) {
131 }
132
addGraphics::__anond3eae2410111::WordWrapper133 void add(StringType &line, int &w) {
134 if (actualMaxLineWidth < w)
135 actualMaxLineWidth = w;
136
137 lines.push_back(line);
138
139 line.clear();
140 w = 0;
141 }
142 };
143
144 template<class StringType>
wordWrapTextImpl(const Font & font,const StringType & str,int maxWidth,Common::Array<StringType> & lines,int initWidth)145 int wordWrapTextImpl(const Font &font, const StringType &str, int maxWidth, Common::Array<StringType> &lines, int initWidth) {
146 WordWrapper<StringType> wrapper(lines);
147 StringType line;
148 StringType tmpStr;
149 int lineWidth = initWidth;
150 int tmpWidth = 0;
151
152 // The rough idea behind this algorithm is as follows:
153 // We accumulate characters into the string tmpStr. Whenever a full word
154 // has been gathered together this way, we 'commit' it to the line buffer
155 // 'line', i.e. we add tmpStr to the end of line, then clear it. Before
156 // we do that, we check whether it would cause 'line' to exceed maxWidth;
157 // in that case, we first add line to lines, then reset it.
158 //
159 // If a newline character is read, then we also add line to lines and clear it.
160 //
161 // Special care has to be taken to account for 'words' that exceed the width
162 // of a line. If we encounter such a word, we have to wrap it over multiple
163 // lines.
164
165 typename StringType::unsigned_type last = 0;
166 for (typename StringType::const_iterator x = str.begin(); x != str.end(); ++x) {
167 typename StringType::unsigned_type c = *x;
168
169 // Convert Windows and Mac line breaks into plain \n
170 if (c == '\r') {
171 if (x != str.end() && *(x + 1) == '\n') {
172 ++x;
173 }
174 c = '\n';
175 }
176
177 const int w = font.getCharWidth(c) + font.getKerningOffset(last, c);
178 last = c;
179 const bool wouldExceedWidth = (lineWidth + tmpWidth + w > maxWidth);
180
181 // If this char is a whitespace, then it represents a potential
182 // 'wrap point' where wrapping could take place. Everything that
183 // came before it can now safely be added to the line, as we know
184 // that it will not have to be wrapped.
185 if (Common::isSpace(c)) {
186 line += tmpStr;
187 lineWidth += tmpWidth;
188
189 tmpStr.clear();
190 tmpWidth = 0;
191
192 // If we encounter a line break (\n), or if the new space would
193 // cause the line to overflow: start a new line
194 if (c == '\n' || wouldExceedWidth) {
195 wrapper.add(line, lineWidth);
196 continue;
197 }
198 }
199
200 // If the max line width would be exceeded by adding this char,
201 // insert a line break.
202 if (wouldExceedWidth) {
203 // Commit what we have so far, *if* we have anything.
204 // If line is empty, then we are looking at a word
205 // which exceeds the maximum line width.
206 if (lineWidth > 0) {
207 wrapper.add(line, lineWidth);
208 // Trim left side
209 while (tmpStr.size() && Common::isSpace(tmpStr[0])) {
210 tmpStr.deleteChar(0);
211 // This is not very fast, but it is the simplest way to
212 // assure we do not mess something up because of kerning.
213 tmpWidth = font.getStringWidth(tmpStr);
214 }
215 } else {
216 wrapper.add(tmpStr, tmpWidth);
217 }
218 }
219
220 tmpWidth += w;
221 tmpStr += c;
222 }
223
224 // If some text is left over, add it as the final line
225 line += tmpStr;
226 lineWidth += tmpWidth;
227 if (lineWidth > 0) {
228 wrapper.add(line, lineWidth);
229 }
230 return wrapper.actualMaxLineWidth;
231 }
232
233 } // End of anonymous namespace
234
getBoundingBox(const Common::String & input,int x,int y,const int w,TextAlign align,int deltax,bool useEllipsis) const235 Common::Rect Font::getBoundingBox(const Common::String &input, int x, int y, const int w, TextAlign align, int deltax, bool useEllipsis) const {
236 // In case no width was given we cannot use ellipsis or any alignment
237 // apart from left alignment.
238 if (w == 0) {
239 if (useEllipsis) {
240 warning("Font::getBoundingBox: Requested ellipsis when no width was specified");
241 }
242
243 if (align != kTextAlignLeft) {
244 warning("Font::getBoundingBox: Requested text alignment when no width was specified");
245 }
246
247 useEllipsis = false;
248 align = kTextAlignLeft;
249 }
250
251 const Common::String str = useEllipsis ? handleEllipsis(input, w) : input;
252 return getBoundingBoxImpl(*this, str, x, y, w, align, deltax);
253 }
254
getBoundingBox(const Common::U32String & str,int x,int y,const int w,TextAlign align) const255 Common::Rect Font::getBoundingBox(const Common::U32String &str, int x, int y, const int w, TextAlign align) const {
256 // In case no width was given we cannot any alignment apart from left
257 // alignment.
258 if (w == 0) {
259 if (align != kTextAlignLeft) {
260 warning("Font::getBoundingBox: Requested text alignment when no width was specified");
261 }
262
263 align = kTextAlignLeft;
264 }
265
266 return getBoundingBoxImpl(*this, str, x, y, w, align, 0);
267 }
268
getStringWidth(const Common::String & str) const269 int Font::getStringWidth(const Common::String &str) const {
270 return getStringWidthImpl(*this, str);
271 }
272
getStringWidth(const Common::U32String & str) const273 int Font::getStringWidth(const Common::U32String &str) const {
274 return getStringWidthImpl(*this, str);
275 }
276
drawChar(ManagedSurface * dst,uint32 chr,int x,int y,uint32 color) const277 void Font::drawChar(ManagedSurface *dst, uint32 chr, int x, int y, uint32 color) const {
278 drawChar(&dst->_innerSurface, chr, x, y, color);
279
280 Common::Rect charBox = getBoundingBox(chr);
281 charBox.translate(x, y);
282 dst->addDirtyRect(charBox);
283 }
284
drawString(Surface * dst,const Common::String & str,int x,int y,int w,uint32 color,TextAlign align,int deltax,bool useEllipsis) const285 void Font::drawString(Surface *dst, const Common::String &str, int x, int y, int w, uint32 color, TextAlign align, int deltax, bool useEllipsis) const {
286 Common::String renderStr = useEllipsis ? handleEllipsis(str, w) : str;
287 drawStringImpl(*this, dst, renderStr, x, y, w, color, align, deltax);
288 }
289
drawString(Surface * dst,const Common::U32String & str,int x,int y,int w,uint32 color,TextAlign align) const290 void Font::drawString(Surface *dst, const Common::U32String &str, int x, int y, int w, uint32 color, TextAlign align) const {
291 drawStringImpl(*this, dst, str, x, y, w, color, align, 0);
292 }
293
drawString(ManagedSurface * dst,const Common::String & str,int x,int y,int w,uint32 color,TextAlign align,int deltax,bool useEllipsis) const294 void Font::drawString(ManagedSurface *dst, const Common::String &str, int x, int y, int w, uint32 color, TextAlign align, int deltax, bool useEllipsis) const {
295 drawString(&dst->_innerSurface, str, x, y, w, color, align, deltax, useEllipsis);
296 if (w != 0) {
297 dst->addDirtyRect(getBoundingBox(str, x, y, w, align, deltax, useEllipsis));
298 }
299 }
300
drawString(ManagedSurface * dst,const Common::U32String & str,int x,int y,int w,uint32 color,TextAlign align) const301 void Font::drawString(ManagedSurface *dst, const Common::U32String &str, int x, int y, int w, uint32 color, TextAlign align) const {
302 drawString(&dst->_innerSurface, str, x, y, w, color, align);
303 if (w != 0) {
304 dst->addDirtyRect(getBoundingBox(str, x, y, w, align));
305 }
306 }
307
wordWrapText(const Common::String & str,int maxWidth,Common::Array<Common::String> & lines,int initWidth) const308 int Font::wordWrapText(const Common::String &str, int maxWidth, Common::Array<Common::String> &lines, int initWidth) const {
309 return wordWrapTextImpl(*this, str, maxWidth, lines, initWidth);
310 }
311
wordWrapText(const Common::U32String & str,int maxWidth,Common::Array<Common::U32String> & lines,int initWidth) const312 int Font::wordWrapText(const Common::U32String &str, int maxWidth, Common::Array<Common::U32String> &lines, int initWidth) const {
313 return wordWrapTextImpl(*this, str, maxWidth, lines, initWidth);
314 }
315
handleEllipsis(const Common::String & input,int w) const316 Common::String Font::handleEllipsis(const Common::String &input, int w) const {
317 Common::String s = input;
318 int width = getStringWidth(s);
319
320 if (width > w && s.hasSuffix("...")) {
321 // String is too wide. Check whether it ends in an ellipsis
322 // ("..."). If so, remove that and try again!
323 s.deleteLastChar();
324 s.deleteLastChar();
325 s.deleteLastChar();
326 width = getStringWidth(s);
327 }
328
329 if (width > w) {
330 Common::String str;
331
332 // String is too wide. So we shorten it "intelligently" by
333 // replacing parts of the string by an ellipsis. There are
334 // three possibilities for this: replace the start, the end, or
335 // the middle of the string. What is best really depends on the
336 // context; but unless we want to make this configurable,
337 // replacing the middle seems to be a good compromise.
338
339 const int ellipsisWidth = getStringWidth("...");
340
341 // SLOW algorithm to remove enough of the middle. But it is good enough
342 // for now.
343 const int halfWidth = (w - ellipsisWidth) / 2;
344 int w2 = 0;
345 Common::String::unsigned_type last = 0;
346 uint i = 0;
347
348 for (; i < s.size(); ++i) {
349 const Common::String::unsigned_type cur = s[i];
350 int charWidth = getCharWidth(cur) + getKerningOffset(last, cur);
351 if (w2 + charWidth > halfWidth)
352 break;
353 last = cur;
354 w2 += charWidth;
355 str += cur;
356 }
357
358 // At this point we know that the first 'i' chars are together 'w2'
359 // pixels wide. We took the first i-1, and add "..." to them.
360 str += "...";
361 last = '.';
362
363 // The original string is width wide. Of those we already skipped past
364 // w2 pixels, which means (width - w2) remain.
365 // The new str is (w2+ellipsisWidth) wide, so we can accommodate about
366 // (w - (w2+ellipsisWidth)) more pixels.
367 // Thus we skip ((width - w2) - (w - (w2+ellipsisWidth))) =
368 // (width + ellipsisWidth - w)
369 int skip = width + ellipsisWidth - w;
370 for (; i < s.size() && skip > 0; ++i) {
371 const Common::String::unsigned_type cur = s[i];
372 skip -= getCharWidth(cur) + getKerningOffset(last, cur);
373 last = cur;
374 }
375
376 // Append the remaining chars, if any
377 for (; i < s.size(); ++i) {
378 str += s[i];
379 }
380
381 return str;
382 } else {
383 return s;
384 }
385 }
386
387 } // End of namespace Graphics
388