aboutsummaryrefslogtreecommitdiff
path: root/engines/draci/font.cpp
blob: 669c7d85e034b2bee33e2c3bc48cc801d34ba5d4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#include "common/file.h"

#include "draci/draci.h"
#include "draci/font.h"
#include "draci/surface.h"

namespace Draci {

const char * const kFontSmall = "Small.fon";
const char * const kFontBig = "Big.fon";

Font::Font(const Common::String &filename) {
	_fontHeight = 0;
	_maxCharWidth = 0;
	_charWidths = NULL;
	_charData = NULL;

	loadFont(filename);
}

Font::~Font() {
	 freeFont();
}

/**
 * @brief Loads fonts from a file
 * @param path Path to font file
 * @return true if the font was loaded successfully, false otherwise
 *
 * Loads fonts from a file into a Font instance. The original game uses two
 * fonts (located inside files "Small.fon" and "Big.fon"). The characters in the
 * font are indexed from the space character so an appropriate offset must be
 * added to convert them to equivalent char values, i.e. kDraciIndexOffset.
 * Characters in the higher range are non-ASCII and vary between different
 * language versions of the game.
 *
 * font format: [1 byte] maximum character width
 *              [1 byte] font height
 *              [138 bytes] character widths of all 138 characters in the font
 *              [138 * fontHeight * maxWidth bytes] character data, stored row-wise
 */

bool Font::loadFont(const Common::String &filename) {
	// Free previously loaded font (if any)
	freeFont();

	Common::File f;

	f.open(filename);
	if (f.isOpen()) {
		debugC(6, kDraciGeneralDebugLevel, "Opened font file %s",
			filename.c_str());
	} else {
		debugC(6, kDraciGeneralDebugLevel, "Error opening font file %s",
			filename.c_str());
		return false;
	}

	_maxCharWidth = f.readByte();
	_fontHeight = f.readByte();

	// Read in the widths of the glyphs
	_charWidths = new uint8[kCharNum];
	for (uint i = 0; i < kCharNum; ++i) {
		_charWidths[i] = f.readByte();
	}

	// Calculate size of font data
	uint fontDataSize = kCharNum * _maxCharWidth * _fontHeight;

	// Read in all glyphs
	_charData = new byte[fontDataSize];
	f.read(_charData, fontDataSize);

	debugC(5, kDraciGeneralDebugLevel, "Font %s loaded", filename.c_str());

	return true;
}

void Font::freeFont() {
	delete[] _charWidths;
	delete[] _charData;
}

uint8 Font::getCharWidth(uint8 chr) const {
	// Safe-guard against incorrect strings containing localized characters
	// with inaccessible codes.  These strings do not exist in the original
	// Czech version, but they do in the (never properly reviewed) English
	// version.
	return chr >= kCharIndexOffset && chr < kCharIndexOffset + kCharNum
		? _charWidths[chr - kCharIndexOffset] : 0;
}

/**
 * @brief Draw a char to a Draci::Surface
 *
 * @param dst   Pointer to the destination surface
 * @param chr   Character to draw
 * @param tx    Horizontal offset on the surface
 * @param ty    Vertical offset on the surface
 */

void Font::drawChar(Surface *dst, uint8 chr, int tx, int ty, int with_color) const {
	assert(dst != NULL);
	assert(tx >= 0);
	assert(ty >= 0);

	byte *ptr = (byte *)dst->getBasePtr(tx, ty);
	const uint8 currentWidth = getCharWidth(chr);
	if (currentWidth == 0) {
		return;
	}

	const uint8 charIndex = chr - kCharIndexOffset;
	const int charOffset = charIndex * _fontHeight * _maxCharWidth;

	// Determine how many pixels to draw horizontally (to prevent overflow)
	int xSpaceLeft = dst->w - tx - 1;
	int xPixelsToDraw = (currentWidth < xSpaceLeft) ? currentWidth : xSpaceLeft;

	// Determine how many pixels to draw vertically
	int ySpaceLeft = dst->h - ty - 1;
	int yPixelsToDraw = (_fontHeight < ySpaceLeft) ? _fontHeight : ySpaceLeft;

	int _transparent = dst->getTransparentColor();

	for (int y = 0; y < yPixelsToDraw; ++y) {
		for (int x = 0; x <= xPixelsToDraw; ++x) {

			int curr = y * _maxCharWidth + x;
			int color = _charData[charOffset + curr];

			// If pixel is transparent, skip it
			if (color == _transparent)
				continue;

			// Replace color with font colors
			switch (color) {
			case 254:
				color = with_color;
				break;

			case 253:
				color = kFontColor2;
				break;

			case 252:
				color = kFontColor3;
				break;

			case 251:
				color = kFontColor4;
				break;

			default:
				break;
			}

			// Paint the pixel
			ptr[x] = color;
		}

		// Advance to next row
		ptr += dst->pitch;
	}
}

/**
 * @brief Draw a string to a Draci::Surface
 *
 * @param dst       Pointer to the destination surface
 * @param str       Buffer containing string data
 * @param len       Length of the data
 * @param x         Horizontal offset on the surface
 * @param y         Vertical offset on the surface
 * @param spacing   Space to leave between individual characters. Defaults to 0.
 */
void Font::drawString(Surface *dst, const byte *str, uint len,
	                  int x, int y, int with_color, int spacing, bool markDirty) const {
	drawString(dst, Common::String((const char *)str, len), x, y, with_color, spacing, markDirty);
}

/**
 * @brief Draw a string to a Draci::Surface
 *
 * @param dst       Pointer to the destination surface
 * @param str       String to draw
 * @param x         Horizontal offset on the surface
 * @param y         Vertical offset on the surface
 * @param spacing   Space to leave between individual characters. Defaults to 0.
 */

void Font::drawString(Surface *dst, const Common::String &str,
	                  int x, int y, int with_color, int spacing, bool markDirty) const {
	assert(dst != NULL);
	assert(x >= 0);
	assert(y >= 0);

	uint widest = getStringWidth(str, spacing);

	int curx = x + (widest - getLineWidth(str, 0, spacing)) / 2;
	int cury = y;

	for (uint i = 0; i < str.size(); ++i) {

		// If we encounter the '|' char (newline and end of string marker),
		// skip it and go to the start of the next line
		if (str[i] == '|') {
			cury += getFontHeight();
			curx = x + (widest - getLineWidth(str, i+1, spacing) - 1) / 2;
			continue;
		}

		// Break early if there's no more space on the screen
		if (curx >= dst->w - 1 || cury >= dst->h - 1) {
			break;
		}

		drawChar(dst, str[i], curx, cury, with_color);
		curx += getCharWidth(str[i]) + spacing;
	}

	if (markDirty) {
		Common::Rect r(x, y, x + widest, y + getStringHeight(str));
		dst->markDirtyRect(r);
	}
}

/**
 * @brief Calculate the width of a string when drawn in the current font
 *
 * @param str       String to draw
 * @param spacing   Space to leave between individual characters. Defaults to 0.
 *
 * @return The calculated width of the string
 */
uint Font::getStringWidth(const Common::String &str, int spacing) const {
	uint width = 0;

	// Real length, including '|' separators
	uint len = str.size();

	for (uint i = 0, tmp = 0; i < len; ++i) {

		if (str[i] != '|') {
			tmp += getCharWidth(str[i]) + spacing;
		}

		// Newline char encountered, skip it and store the new length if it is greater.
		// Also, all strings in the data files should end with '|' but not all do.
		// This is why we check whether we are at the last char too.
		if (str[i] == '|' || i == len - 1) {
			if (tmp > width) {
				width = tmp;
			}

			tmp = 0;
		}
	}

	return width + 1;
}

uint Font::getLineWidth(const Common::String &str, uint startIndex, int spacing) const {
	uint width = 0;

	// If the index is greater or equal to the string size,
	// the width of the line is 0
	if (startIndex >= str.size())
		return 0;

	for (uint i = startIndex; i < str.size(); ++i) {

		// EOL encountered
		if (str[i] == '|')
			break;

		// Add width of the current char
		width += getCharWidth(str[i]) + spacing;
	}

	return width;
}

/**
 * @brief Calculate the height of a string by counting the number of '|' chars (which
 *        are used as newline characters and end-of-string markers)
 *
 * @param str       String to draw
 * @param spacing   Space to leave between individual characters. Defaults to 0.
 *
 * @return The calculated height of the string
 */
uint Font::getStringHeight(const Common::String &str) const {
	uint len = str.size();
	int separators = 0;

	for (uint i = 0; i < len; ++i) {
		// All strings in the data files should end with '|' but not all do.
		// This is why we check whether we are at the last char too.
		if (str[i] == '|' || i == len - 1) {
			++separators;
		}
	}

	return separators * getFontHeight();
}

} // End of namespace Draci