mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-23 10:19:27 +00:00
a7d7e6eb7a
svn-id: r13959
564 lines
16 KiB
C++
564 lines
16 KiB
C++
/* Copyright (C) 1994-2004 Revolution Software Ltd
|
|
*
|
|
* 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*
|
|
* $Header$
|
|
*/
|
|
|
|
// MAKETEXT - Constructs a single-frame text sprite: returns a handle to a
|
|
// FLOATING memory block containing the sprite, given a
|
|
// null-terminated string, max width allowed, pen colour and
|
|
// pointer to required character set.
|
|
//
|
|
// NB 1) The routine does not create a standard file header or
|
|
// an anim header for the text sprite - the data simply begins
|
|
// with the frame header.
|
|
//
|
|
// NB 2) If pen colour is zero, it copies the characters into
|
|
// the sprite without remapping the colours.
|
|
// ie. It can handle both the standard 2-colour font for speech
|
|
// and any multicoloured fonts for control panels, etc.
|
|
//
|
|
// Based on textsprt.c as used for Broken Sword 1, but updated
|
|
// for new system by JEL on 9oct96 and updated again (for font
|
|
// as a resource) on 5dec96.
|
|
|
|
#include "common/stdafx.h"
|
|
#include "sword2/sword2.h"
|
|
#include "sword2/defs.h"
|
|
#include "sword2/logic.h"
|
|
#include "sword2/maketext.h"
|
|
#include "sword2/resman.h"
|
|
#include "sword2/driver/d_draw.h"
|
|
|
|
namespace Sword2 {
|
|
|
|
#define MAX_LINES 30 // max character lines in output sprite
|
|
|
|
#define BORDER_COL 200 // source colour for character border (only
|
|
// needed for remapping colours)
|
|
#define LETTER_COL 193 // source colour for bulk of character ( " )
|
|
#define SPACE ' '
|
|
#define FIRST_CHAR SPACE // first character in character set
|
|
#define LAST_CHAR 255 // last character in character set
|
|
#define DUD 64 // the first "chequered flag" (dud) symbol in
|
|
// our character set is in the '@' position
|
|
|
|
/**
|
|
* This function creates a new text sprite. The sprite data contains a
|
|
* FrameHeader, but not a standard file header.
|
|
*
|
|
* @param sentence pointer to a null-terminated string
|
|
* @param maxWidth the maximum allowed text sprite width in pixels
|
|
* @param pen the text colour, or zero to use the source colours
|
|
* @param fontRes the font resource id
|
|
* @param border the border colour; black by default
|
|
* @return a handle to a floating memory block containing the text sprite
|
|
* @note The sentence must contain no leading, trailing or extra spaces.
|
|
* Out-of-range characters in the string are replaced by a special
|
|
* error-signal character (chequered flag)
|
|
*/
|
|
|
|
byte *FontRenderer::makeTextSprite(byte *sentence, uint16 maxWidth, uint8 pen, uint32 fontRes, uint8 border) {
|
|
debug(3, "makeTextSprite(\"%s\", maxWidth=%u)", sentence, maxWidth);
|
|
|
|
_borderPen = border;
|
|
|
|
// Line- and character spacing are hard-wired, rather than being part
|
|
// of the resource.
|
|
|
|
if (fontRes == _vm->_speechFontId) {
|
|
_lineSpacing = -6;
|
|
_charSpacing = -3;
|
|
} else if (fontRes == CONSOLE_FONT_ID) {
|
|
_lineSpacing = 0;
|
|
_charSpacing = 1;
|
|
} else {
|
|
_lineSpacing = 0;
|
|
_charSpacing = 0;
|
|
}
|
|
|
|
// Allocate memory for array of lineInfo structures
|
|
|
|
byte *line = (byte *) malloc(MAX_LINES * sizeof(LineInfo));
|
|
|
|
// Get details of sentence breakdown into array of LineInfo structures
|
|
// and get the number of lines involved
|
|
|
|
uint16 noOfLines = analyseSentence(sentence, maxWidth, fontRes, (LineInfo *) line);
|
|
|
|
// Construct the sprite based on the info gathered - returns floating
|
|
// mem block
|
|
|
|
byte *textSprite = buildTextSprite(sentence, fontRes, pen, (LineInfo *) line, noOfLines);
|
|
|
|
free(line);
|
|
return textSprite;
|
|
}
|
|
|
|
uint16 FontRenderer::analyseSentence(byte *sentence, uint16 maxWidth, uint32 fontRes, LineInfo *line) {
|
|
// joinWidth = how much extra space is needed to append a word to a
|
|
// line. NB. SPACE requires TWICE the '_charSpacing' to join a word
|
|
// to line
|
|
|
|
uint16 joinWidth = charWidth(SPACE, fontRes) + 2 * _charSpacing;
|
|
|
|
uint16 lineNo = 0;
|
|
uint16 pos = 0;
|
|
bool firstWord = true;
|
|
|
|
byte ch;
|
|
|
|
do {
|
|
uint16 wordWidth = 0;
|
|
uint16 wordLength = 0;
|
|
|
|
// Calculate the width of the word.
|
|
|
|
ch = sentence[pos++];
|
|
|
|
while (ch && ch != SPACE) {
|
|
wordWidth += charWidth(ch, fontRes) + _charSpacing;
|
|
wordLength++;
|
|
ch = sentence[pos++];
|
|
}
|
|
|
|
// Don't include any character spacing at the end of the word.
|
|
wordWidth -= _charSpacing;
|
|
|
|
// 'ch' is now the SPACE or NULL following the word
|
|
// 'pos' indexes to the position following 'ch'
|
|
|
|
if (firstWord) {
|
|
// This is the first word on the line, so no separating
|
|
// space is needed.
|
|
|
|
line[0].width = wordWidth;
|
|
line[0].length = wordLength;
|
|
firstWord = false;
|
|
} else {
|
|
// See how much extra space this word will need to
|
|
// fit on current line (with a separating space
|
|
// character - also overlapped)
|
|
|
|
uint16 spaceNeeded = joinWidth + wordWidth;
|
|
|
|
if (line[lineNo].width + spaceNeeded <= maxWidth) {
|
|
// The word fits on this line.
|
|
line[lineNo].width += spaceNeeded;
|
|
line[lineNo].length += (1 + wordLength);
|
|
} else {
|
|
// The word spills over to the next line, i.e.
|
|
// no separating space.
|
|
|
|
lineNo++;
|
|
|
|
assert(lineNo < MAX_LINES);
|
|
|
|
line[lineNo].width = wordWidth;
|
|
line[lineNo].length = wordLength;
|
|
}
|
|
}
|
|
} while (ch);
|
|
|
|
return lineNo + 1;
|
|
}
|
|
|
|
/**
|
|
* This function creates a new text sprite in a movable memory block. It must
|
|
* be locked before use, i.e. lock, draw sprite, unlock/free. The sprite data
|
|
* contains a FrameHeader, but not a standard file header.
|
|
*
|
|
* @param sentence pointer to a null-terminated string
|
|
* @param fontRes the font resource id
|
|
* @param pen the text colour, or zero to use the source colours
|
|
* @param line array of LineInfo structures, created by analyseSentence()
|
|
* @param noOfLines the number of lines, i.e. the number of elements in 'line'
|
|
* @return a handle to a floating memory block containing the text sprite
|
|
* @note The sentence must contain no leading, trailing or extra spaces.
|
|
* Out-of-range characters in the string are replaced by a special
|
|
* error-signal character (chequered flag)
|
|
*/
|
|
|
|
byte *FontRenderer::buildTextSprite(byte *sentence, uint32 fontRes, uint8 pen, LineInfo *line, uint16 noOfLines) {
|
|
uint16 i;
|
|
|
|
// Find the width of the widest line in the output text
|
|
|
|
uint16 spriteWidth = 0;
|
|
|
|
for (i = 0; i < noOfLines; i++)
|
|
if (line[i].width > spriteWidth)
|
|
spriteWidth = line[i].width;
|
|
|
|
// Find the total height of the text sprite: the total height of the
|
|
// text lines, plus the total height of the spacing between them.
|
|
|
|
uint16 char_height = charHeight(fontRes);
|
|
uint16 spriteHeight = char_height * noOfLines + _lineSpacing * (noOfLines - 1);
|
|
|
|
// Allocate memory for the text sprite
|
|
|
|
uint32 sizeOfSprite = spriteWidth * spriteHeight;
|
|
byte *textSprite = (byte *) malloc(sizeof(FrameHeader) + sizeOfSprite);
|
|
|
|
// At this stage, textSprite points to an unmovable memory block. Set
|
|
// up the frame header.
|
|
|
|
FrameHeader *frameHeadPtr = (FrameHeader *) textSprite;
|
|
|
|
frameHeadPtr->compSize = 0;
|
|
frameHeadPtr->width = spriteWidth;
|
|
frameHeadPtr->height = spriteHeight;
|
|
|
|
debug(4, "Text sprite size: %ux%u", spriteWidth, spriteHeight);
|
|
|
|
// Clear the entire sprite to make it transparent.
|
|
|
|
byte *linePtr = textSprite + sizeof(FrameHeader);
|
|
memset(linePtr, 0, sizeOfSprite);
|
|
|
|
byte *charSet = _vm->_resman->openResource(fontRes);
|
|
|
|
// Build the sprite, one line at a time
|
|
|
|
uint16 pos = 0;
|
|
|
|
for (i = 0; i < noOfLines; i++) {
|
|
// Center each line
|
|
byte *spritePtr = linePtr + (spriteWidth - line[i].width) / 2;
|
|
|
|
// copy the sprite for each character in this line to the
|
|
// text sprite and inc the sprite ptr by the character's
|
|
// width minus the 'overlap'
|
|
|
|
for (uint j = 0; j < line[i].length; j++) {
|
|
FrameHeader *charPtr = findChar(sentence[pos++], charSet);
|
|
|
|
assert(charPtr->height == char_height);
|
|
copyChar(charPtr, spritePtr, spriteWidth, pen);
|
|
spritePtr += charPtr->width + _charSpacing;
|
|
}
|
|
|
|
// Skip space at end of last word in this line
|
|
pos++;
|
|
|
|
linePtr += (char_height + _lineSpacing) * spriteWidth;
|
|
}
|
|
|
|
_vm->_resman->closeResource(fontRes);
|
|
|
|
return textSprite;
|
|
}
|
|
|
|
/**
|
|
* @param ch the ASCII code of the character
|
|
* @param fontRes the font resource id
|
|
* @return the width of the character
|
|
*/
|
|
|
|
uint16 FontRenderer::charWidth(byte ch, uint32 fontRes) {
|
|
byte *charSet = _vm->_resman->openResource(fontRes);
|
|
|
|
FrameHeader *charFrame = findChar(ch, charSet);
|
|
uint16 width = charFrame->width;
|
|
|
|
_vm->_resman->closeResource(fontRes);
|
|
return width;
|
|
}
|
|
|
|
/**
|
|
* @param fontRes the font resource id
|
|
* @return the height of a character sprite
|
|
* @note All characters in a font are assumed to have the same height, so
|
|
* there is no need to specify which one to look at.
|
|
*/
|
|
|
|
// Returns the height of a character sprite, given the character's ASCII code
|
|
// and a pointer to the start of the character set.
|
|
|
|
uint16 FontRenderer::charHeight(uint32 fontRes) {
|
|
byte *charSet = _vm->_resman->openResource(fontRes);
|
|
|
|
FrameHeader *charFrame = findChar(FIRST_CHAR, charSet);
|
|
uint16 height = charFrame->height;
|
|
|
|
_vm->_resman->closeResource(fontRes);
|
|
return height;
|
|
}
|
|
|
|
/**
|
|
* @param ch the ASCII code of the character to find
|
|
* @param charSet pointer to the start of the character set
|
|
* @return pointer to the requested character or, if it's out of range, the
|
|
* 'dud' character (chequered flag)
|
|
*/
|
|
|
|
FrameHeader* FontRenderer::findChar(byte ch, byte *charSet) {
|
|
if (ch < FIRST_CHAR)
|
|
ch = DUD;
|
|
return _vm->fetchFrameHeader(charSet, ch - FIRST_CHAR);
|
|
}
|
|
|
|
/**
|
|
* Copies a character sprite to the sprite buffer.
|
|
* @param charPtr pointer to the character sprite
|
|
* @param spritePtr pointer to the sprite buffer
|
|
* @param spriteWidth the width of the character
|
|
* @param pen If zero, copy the data directly. Otherwise remap the
|
|
* sprite's colours from BORDER_COL to _borderPen and from
|
|
* LETTER_COL to pen.
|
|
*/
|
|
|
|
void FontRenderer::copyChar(FrameHeader *charPtr, byte *spritePtr, uint16 spriteWidth, uint8 pen) {
|
|
byte *source = (byte *) charPtr + sizeof(FrameHeader);
|
|
byte *rowPtr = spritePtr;
|
|
|
|
for (uint i = 0; i < charPtr->height; i++) {
|
|
byte *dest = rowPtr;
|
|
|
|
if (pen) {
|
|
// Use the specified colours
|
|
for (uint j = 0; j < charPtr->width; j++) {
|
|
switch (*source++) {
|
|
case LETTER_COL:
|
|
*dest = pen;
|
|
break;
|
|
case BORDER_COL:
|
|
// Don't do a border pixel if there's
|
|
// already a bit of another character
|
|
// underneath (for overlapping!)
|
|
if (!*dest)
|
|
*dest = _borderPen;
|
|
break;
|
|
default:
|
|
// Do nothing if source pixel is zero,
|
|
// ie. transparent
|
|
break;
|
|
}
|
|
dest++;
|
|
}
|
|
} else {
|
|
// Pen is zero, so just copy character sprites
|
|
// directly into text sprite without remapping colours.
|
|
// Apparently overlapping is never considered here?
|
|
memcpy(dest, source, charPtr->width);
|
|
source += charPtr->width;
|
|
}
|
|
rowPtr += spriteWidth;
|
|
}
|
|
}
|
|
|
|
// Distance to keep speech text from edges of screen
|
|
#define TEXT_MARGIN 12
|
|
|
|
/**
|
|
* Creates a text bloc in the list and returns the bloc number. The list of
|
|
* blocs is read and blitted at render time. Choose alignment type
|
|
* RDSPR_DISPLAYALIGN or 0
|
|
*/
|
|
|
|
uint32 FontRenderer::buildNewBloc(byte *ascii, int16 x, int16 y, uint16 width, uint8 pen, uint32 type, uint32 fontRes, uint8 justification) {
|
|
uint32 i = 0;
|
|
|
|
while (i < MAX_text_blocs && _blocList[i].text_mem)
|
|
i++;
|
|
|
|
assert(i < MAX_text_blocs);
|
|
|
|
// Create and position the sprite
|
|
|
|
_blocList[i].text_mem = makeTextSprite(ascii, width, pen, fontRes);
|
|
|
|
// 'NO_JUSTIFICATION' means print sprite with top-left at (x,y)
|
|
// without margin checking - used for debug text
|
|
|
|
if (justification != NO_JUSTIFICATION) {
|
|
FrameHeader *frame_head = (FrameHeader *) _blocList[i].text_mem;
|
|
|
|
switch (justification) {
|
|
case POSITION_AT_CENTRE_OF_BASE:
|
|
// This one is always used for SPEECH TEXT; possibly
|
|
// also for pointer text
|
|
x -= (frame_head->width / 2);
|
|
y -= frame_head->height;
|
|
break;
|
|
case POSITION_AT_CENTRE_OF_TOP:
|
|
x -= (frame_head->width / 2);
|
|
break;
|
|
case POSITION_AT_LEFT_OF_TOP:
|
|
// The given coords are already correct for this!
|
|
break;
|
|
case POSITION_AT_RIGHT_OF_TOP:
|
|
x -= frame_head->width;
|
|
break;
|
|
case POSITION_AT_LEFT_OF_BASE:
|
|
y -= frame_head->height;
|
|
break;
|
|
case POSITION_AT_RIGHT_OF_BASE:
|
|
x -= frame_head->width;
|
|
y -= frame_head->height;
|
|
break;
|
|
case POSITION_AT_LEFT_OF_CENTRE:
|
|
y -= (frame_head->height / 2);
|
|
break;
|
|
case POSITION_AT_RIGHT_OF_CENTRE:
|
|
x -= frame_head->width;
|
|
y -= (frame_head->height) / 2;
|
|
break;
|
|
}
|
|
|
|
// Ensure text sprite is a few pixels inside the visible screen
|
|
// remember - it's RDSPR_DISPLAYALIGN
|
|
|
|
uint16 text_left_margin = TEXT_MARGIN;
|
|
uint16 text_right_margin = 640 - TEXT_MARGIN - frame_head->width;
|
|
uint16 text_top_margin = TEXT_MARGIN;
|
|
uint16 text_bottom_margin = 400 - TEXT_MARGIN - frame_head->height;
|
|
|
|
// Move if too far left or too far right
|
|
|
|
if (x < text_left_margin)
|
|
x = text_left_margin;
|
|
else if (x > text_right_margin)
|
|
x = text_right_margin;
|
|
|
|
// Move if too high or too low
|
|
|
|
if (y < text_top_margin)
|
|
y = text_top_margin;
|
|
else if (y > text_bottom_margin)
|
|
y = text_bottom_margin;
|
|
}
|
|
|
|
// The sprite is always uncompressed
|
|
_blocList[i].type = type | RDSPR_NOCOMPRESSION;
|
|
|
|
_blocList[i].x = x;
|
|
_blocList[i].y = y;
|
|
|
|
return i + 1;
|
|
}
|
|
|
|
/**
|
|
* Called by buildDisplay()
|
|
*/
|
|
|
|
void FontRenderer::printTextBlocs(void) {
|
|
for (uint i = 0; i < MAX_text_blocs; i++) {
|
|
if (_blocList[i].text_mem) {
|
|
FrameHeader *frame = (FrameHeader *) _blocList[i].text_mem;
|
|
SpriteInfo spriteInfo;
|
|
|
|
spriteInfo.x = _blocList[i].x;
|
|
spriteInfo.y = _blocList[i].y;
|
|
spriteInfo.w = frame->width;
|
|
spriteInfo.h = frame->height;
|
|
spriteInfo.scale = 0;
|
|
spriteInfo.scaledWidth = 0;
|
|
spriteInfo.scaledHeight = 0;
|
|
spriteInfo.type = _blocList[i].type;
|
|
spriteInfo.blend = 0;
|
|
spriteInfo.data = _blocList[i].text_mem + sizeof(FrameHeader);
|
|
spriteInfo.colourTable = 0;
|
|
|
|
uint32 rv = _vm->_graphics->drawSprite(&spriteInfo);
|
|
if (rv)
|
|
error("Driver Error %.8x in printTextBlocs", rv);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FontRenderer::killTextBloc(uint32 bloc_number) {
|
|
bloc_number--;
|
|
free(_blocList[bloc_number].text_mem);
|
|
_blocList[bloc_number].text_mem = NULL;
|
|
}
|
|
|
|
// Resource 3258 contains text from location script for 152 (install, save &
|
|
// restore text, etc)
|
|
|
|
#define TEXT_RES 3258
|
|
|
|
// Local line number of "save" (actor no. 1826)
|
|
|
|
#define SAVE_LINE_NO 1
|
|
|
|
void Sword2Engine::initialiseFontResourceFlags(void) {
|
|
byte *textFile = _resman->openResource(TEXT_RES);
|
|
|
|
// If language is Polish or Finnish it requires alternate fonts.
|
|
// Otherwise, use regular fonts
|
|
|
|
// "talenna" Finnish for "save"
|
|
// "zapisz" Polish for "save"
|
|
|
|
// Get the text line (& skip the 2 chars containing the wavId)
|
|
char *textLine = (char *) fetchTextLine(textFile, SAVE_LINE_NO) + 2;
|
|
|
|
if (strcmp(textLine, "tallenna") == 0)
|
|
initialiseFontResourceFlags(FINNISH_TEXT);
|
|
else if (strcmp(textLine, "zapisz") == 0)
|
|
initialiseFontResourceFlags(POLISH_TEXT);
|
|
else
|
|
initialiseFontResourceFlags(DEFAULT_TEXT);
|
|
|
|
// Get the game name for the windows application
|
|
|
|
// According to the GetNameFunction(), which was never called and has
|
|
// therefore been removed, the name of the game is:
|
|
//
|
|
// ENGLISH: "Broken Sword II"
|
|
// AMERICAN: "Circle of Blood II"
|
|
// GERMAN: "Baphomet's Fluch II"
|
|
// default: "Some game or other, part 86"
|
|
//
|
|
// But we get it from the text resource instead.
|
|
|
|
if (Logic::_scriptVars[DEMO])
|
|
textLine = (char *) fetchTextLine(textFile, 451) + 2;
|
|
else
|
|
textLine = (char *) fetchTextLine(textFile, 54) + 2;
|
|
|
|
_system->setWindowCaption(textLine);
|
|
_resman->closeResource(TEXT_RES);
|
|
}
|
|
|
|
/**
|
|
* Called from initialiseFontResourceFlags(), and also from console.cpp
|
|
*/
|
|
|
|
void Sword2Engine::initialiseFontResourceFlags(uint8 language) {
|
|
switch (language) {
|
|
case FINNISH_TEXT:
|
|
_speechFontId = FINNISH_SPEECH_FONT_ID;
|
|
_controlsFontId = FINNISH_CONTROLS_FONT_ID;
|
|
_redFontId = FINNISH_RED_FONT_ID;
|
|
break;
|
|
case POLISH_TEXT:
|
|
_speechFontId = POLISH_SPEECH_FONT_ID;
|
|
_controlsFontId = POLISH_CONTROLS_FONT_ID;
|
|
_redFontId = POLISH_RED_FONT_ID;
|
|
break;
|
|
default:
|
|
_speechFontId = ENGLISH_SPEECH_FONT_ID;
|
|
_controlsFontId = ENGLISH_CONTROLS_FONT_ID;
|
|
_redFontId = ENGLISH_RED_FONT_ID;
|
|
break;
|
|
}
|
|
}
|
|
|
|
} // End of namespace Sword2
|