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 "ultima/ultima8/misc/pent_include.h"
24 
25 #include "ultima/ultima8/graphics/fonts/font.h"
26 
27 namespace Ultima {
28 namespace Ultima8 {
29 
Font()30 Font::Font() : _highRes(false) {
31 }
32 
33 
~Font()34 Font::~Font() {
35 }
36 
37 
getTextSize(const Std::string & text,int32 & resultwidth,int32 & resultheight,unsigned int & remaining,int32 width,int32 height,TextAlign align,bool u8specials)38 void Font::getTextSize(const Std::string &text,
39 					   int32 &resultwidth, int32 &resultheight,
40 					   unsigned int &remaining,
41 					   int32 width, int32 height, TextAlign align,
42 					   bool u8specials) {
43 	Std::list<PositionedText> tmp;
44 	tmp = typesetText<Traits>(this, text, remaining,
45 	                          width, height, align, u8specials,
46 	                          resultwidth, resultheight);
47 }
48 
49 
50 //static
canBreakAfter(Std::string::const_iterator & i)51 bool Font::Traits::canBreakAfter(Std::string::const_iterator &i) {
52 	// It's not really relevant what we do here, because this probably will
53 	// not be used at normal font sizes.
54 	return true;
55 }
56 
57 
58 //static
canBreakAfter(Std::string::const_iterator & i)59 bool Font::SJISTraits::canBreakAfter(Std::string::const_iterator &i) {
60 	Std::string::const_iterator j = i;
61 	uint32 u1 = unicode(j);
62 
63 	// See: http://www.wesnoth.org/wiki/JapaneseTranslation#Word-Wrapping
64 	// and: http://ja.wikipedia.org/wiki/%E7%A6%81%E5%89%87
65 
66 	switch (u1) {
67 	case 0xff08:
68 	case 0x3014:
69 	case 0xff3b:
70 	case 0xff5b:
71 	case 0x3008:
72 	case 0x300a:
73 	case 0x300c:
74 	case 0x300e:
75 	case 0x3010:
76 	case 0x2018:
77 	case 0x201c:
78 		return false;
79 	default:
80 		break;
81 	}
82 
83 	uint32 u2 = unicode(j);
84 	switch (u2) {
85 	case 0x3001:
86 	case 0x3002:
87 	case 0xff0c:
88 	case 0xff0e:
89 	case 0xff09:
90 	case 0x3015:
91 	case 0xff3d:
92 	case 0xff5d:
93 	case 0x3009:
94 	case 0x300b:
95 	case 0x300d:
96 	case 0x300f:
97 	case 0x3011:
98 	case 0x2019:
99 	case 0x201d:
100 	case 0x309d:
101 	case 0x309e:
102 	case 0x30fd:
103 	case 0x30fe:
104 	case 0x3005:
105 	case 0xff1f:
106 	case 0xff01:
107 	case 0xff1a:
108 	case 0xff1b:
109 	case 0x3041:
110 	case 0x3043:
111 	case 0x3045:
112 	case 0x3047:
113 	case 0x3049:
114 	case 0x3083:
115 	case 0x3085:
116 	case 0x3087:
117 	case 0x308e:
118 	case 0x30a1:
119 	case 0x30a3:
120 	case 0x30a5:
121 	case 0x30a7:
122 	case 0x30a9:
123 	case 0x30e3:
124 	case 0x30e5:
125 	case 0x30e7:
126 	case 0x30ee:
127 	case 0x3063:
128 	case 0x30f5:
129 	case 0x30c3:
130 	case 0x30f6:
131 	case 0x30fb:
132 	case 0x2026:
133 	case 0x30fc:
134 		return false;
135 	default:
136 		break;
137 	}
138 
139 	// Also don't allow breaking between roman characters
140 	if (((u1 >= 'A' && u1 <= 'Z') || (u1 >= 'a' && u1 <= 'z')) &&
141 	        ((u2 >= 'A' && u2 <= 'Z') || (u2 >= 'a' && u2 <= 'z'))) {
142 		return false;
143 	}
144 	return true;
145 }
146 
147 template<class T>
findWordEnd(const Std::string & text,Std::string::const_iterator & iter,bool u8specials)148 static void findWordEnd(const Std::string &text,
149 						Std::string::const_iterator &iter, bool u8specials) {
150 	while (iter != text.end()) {
151 		if (T::isSpace(iter, u8specials)) return;
152 		T::advance(iter);
153 	}
154 }
155 
156 template<class T>
passSpace(const Std::string & text,Std::string::const_iterator & iter,bool u8specials)157 static void passSpace(const Std::string &text,
158 					  Std::string::const_iterator &iter, bool u8specials) {
159 	while (iter != text.end()) {
160 		if (!T::isSpace(iter, u8specials)) return;
161 		T::advance(iter);
162 	}
163 	return;
164 }
165 
166 
167 
168 
169 /*
170   Special characters in U8:
171 
172 @ = bullet for conversation options
173 ~ = line break
174 % = tab
175 * = line break on graves and plaques, possibly page break in books
176 CHECKME: any others? (page breaks for books?)
177 
178 */
179 
180 template<class T>
typesetText(Font * font,const Std::string & text,unsigned int & remaining,int32 width,int32 height,Font::TextAlign align,bool u8specials,int32 & resultwidth,int32 & resultheight,Std::string::size_type cursor)181 Std::list<PositionedText> typesetText(Font *font,
182 	const Std::string &text, unsigned int &remaining, int32 width, int32 height,
183 	Font::TextAlign align, bool u8specials, int32 &resultwidth,
184 	int32 &resultheight, Std::string::size_type cursor) {
185 #if 0
186 	pout << "typeset (" << width << "," << height << ") : "
187 	     << text << Std::endl;
188 #endif
189 
190 	// be optimistic and assume everything will fit
191 	remaining = text.size();
192 
193 	Std::string curline;
194 
195 	int totalwidth = 0;
196 	int totalheight = 0;
197 
198 	Std::list<PositionedText> lines;
199 	PositionedText line;
200 
201 	Std::string::const_iterator iter = text.begin();
202 	Std::string::const_iterator cursoriter = text.begin();
203 	if (cursor != Std::string::npos) cursoriter += cursor;
204 	Std::string::const_iterator curlinestart = text.begin();
205 
206 	bool breakhere = false;
207 	while (true) {
208 		if (iter == text.end() || breakhere || T::isBreak(iter, u8specials)) {
209 			// break here
210 			int32 stringwidth = 0, stringheight = 0;
211 			font->getStringSize(curline, stringwidth, stringheight);
212 			line._dims.left = 0;
213 			line._dims.top = totalheight;
214 			line._dims.setWidth(stringwidth);
215 			line._dims.setHeight(stringheight);
216 			line._text = curline;
217 			line._cursor = Std::string::npos;
218 			if (cursor != Std::string::npos && cursoriter >= curlinestart &&
219 			        (cursoriter < iter || (!breakhere && cursoriter == iter))) {
220 				line._cursor = cursoriter - curlinestart;
221 				if (line._dims.width() == 0) {
222 					stringwidth = 2;
223 					line._dims.setWidth(stringwidth);
224 				}
225 			}
226 			lines.push_back(line);
227 
228 			if (stringwidth > totalwidth) totalwidth = stringwidth;
229 			totalheight += font->getBaselineSkip();
230 
231 			curline = "";
232 
233 			if (iter == text.end())
234 				break; // done
235 
236 			if (breakhere) {
237 				breakhere = false;
238 				curlinestart = iter;
239 			} else {
240 				T::advance(iter);
241 				curlinestart = iter;
242 			}
243 
244 			if (height != 0 && totalheight + font->getHeight() > height) {
245 				// next line won't fit
246 				remaining = curlinestart - text.begin();
247 				break;
248 			}
249 
250 		} else {
251 
252 			// see if next word still fits on the current line
253 			Std::string::const_iterator nextword = iter;
254 			passSpace<T>(text, nextword, u8specials);
255 
256 			// process spaces
257 			bool foundLF = false;
258 			Std::string spaces;
259 			for (; iter < nextword; T::advance(iter)) {
260 				if (T::isBreak(iter, u8specials)) {
261 					foundLF = true;
262 					break;
263 				} else if (T::isTab(iter, u8specials)) {
264 					spaces.append("    ");
265 				} else if (!curline.empty()) {
266 					spaces.append(" ");
267 				}
268 			}
269 			if (foundLF) continue;
270 
271 			// process word
272 			Std::string::const_iterator endofnextword = iter;
273 			findWordEnd<T>(text, endofnextword, u8specials);
274 			int32 stringwidth = 0, stringheight = 0;
275 			Std::string newline = curline + spaces +
276 			                      text.substr(nextword - text.begin(), endofnextword - nextword);
277 			font->getStringSize(newline, stringwidth, stringheight);
278 
279 			// if not, break line before this word
280 			if (width != 0 && stringwidth > width) {
281 				if (!curline.empty()) {
282 					iter = nextword;
283 				} else {
284 					// word is longer than the line; have to break in mid-word
285 					// FIXME: this is rather inefficient; binary search?
286 					// FIXME: clean up...
287 					iter = nextword;
288 					Std::string::const_iterator saveiter = nextword;	// Dummy initialization
289 					Std::string::const_iterator saveiter_fail;
290 					Std::string curline_fail;
291 					newline = spaces;
292 					bool breakok = true;
293 					int breakcount = -1;
294 					do {
295 						if (breakok) {
296 							curline = newline;
297 							saveiter = iter;
298 							breakcount++;
299 						}
300 						curline_fail = newline;
301 						saveiter_fail = iter;
302 
303 						if (iter == text.end()) break;
304 
305 						breakok = T::canBreakAfter(iter);
306 
307 						// try next character
308 						T::advance(iter);
309 						newline = spaces + text.substr(nextword - text.begin(),
310 						                               iter - nextword);
311 						font->getStringSize(newline, stringwidth, stringheight);
312 					} while (stringwidth <= width);
313 					if (breakcount > 0) {
314 						iter = saveiter;
315 					} else {
316 						iter = saveiter_fail;
317 						curline = curline_fail;
318 					}
319 				}
320 				breakhere = true;
321 				continue;
322 			} else {
323 				// copy next word into curline
324 				curline = newline;
325 				iter = endofnextword;
326 			}
327 		}
328 	}
329 
330 	if (lines.size() == 1 && align == Font::TEXT_LEFT) {
331 		// only one line, so use the actual text width
332 		width = totalwidth;
333 	}
334 
335 	if (width != 0) totalwidth = width;
336 
337 	// adjust total height
338 	totalheight -= font->getBaselineSkip();
339 	totalheight += font->getHeight();
340 
341 	// fixup x coordinates of lines
342 	Std::list<PositionedText>::iterator lineiter;
343 	for (lineiter = lines.begin(); lineiter != lines.end(); ++lineiter) {
344 		switch (align) {
345 		case Font::TEXT_LEFT:
346 			break;
347 		case Font::TEXT_RIGHT:
348 			lineiter->_dims.moveTo(totalwidth - lineiter->_dims.width(), lineiter->_dims.top);
349 			break;
350 		case Font::TEXT_CENTER:
351 			lineiter->_dims.moveTo((totalwidth - lineiter->_dims.width()) / 2, lineiter->_dims.top);
352 			break;
353 		}
354 #if 0
355 		pout << lineiter->_dims.x << "," << lineiter->_dims.y << " "
356 		     << lineiter->_dims.width() << "," << lineiter->_dims.height() << ": "
357 		     << lineiter->text << Std::endl;
358 #endif
359 	}
360 
361 	resultwidth = totalwidth;
362 	resultheight = totalheight;
363 
364 	return lines;
365 }
366 
367 
368 // explicit instantiations
369 template
370 Std::list<PositionedText> typesetText<Font::Traits>
371 (Font *font, const Std::string &text,
372  unsigned int &remaining, int32 width, int32 height,
373  Font::TextAlign align, bool u8specials,
374  int32 &resultwidth, int32 &resultheight, Std::string::size_type cursor);
375 
376 template
377 Std::list<PositionedText> typesetText<Font::SJISTraits>
378 (Font *font, const Std::string &text,
379  unsigned int &remaining, int32 width, int32 height,
380  Font::TextAlign align, bool u8specials,
381  int32 &resultwidth, int32 &resultheight, Std::string::size_type cursor);
382 
383 } // End of namespace Ultima8
384 } // End of namespace Ultima
385