384 lines
9.6 KiB
C++
384 lines
9.6 KiB
C++
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "ultima/ultima.h"
|
|
#include "ultima/ultima8/misc/debugger.h"
|
|
#include "ultima/ultima8/misc/common_types.h"
|
|
#include "ultima/ultima8/gfx/fonts/font.h"
|
|
|
|
namespace Ultima {
|
|
namespace Ultima8 {
|
|
|
|
Font::Font() : _highRes(false) {
|
|
}
|
|
|
|
|
|
Font::~Font() {
|
|
}
|
|
|
|
|
|
void Font::getTextSize(const Std::string &text,
|
|
int32 &resultwidth, int32 &resultheight,
|
|
unsigned int &remaining,
|
|
int32 width, int32 height, TextAlign align,
|
|
bool u8specials, bool pagebreaks) {
|
|
Std::list<PositionedText> tmp;
|
|
tmp = typesetText<Traits>(this, text, remaining,
|
|
width, height, align, u8specials, pagebreaks,
|
|
resultwidth, resultheight);
|
|
}
|
|
|
|
|
|
//static
|
|
bool Font::Traits::canBreakAfter(Std::string::const_iterator &i) {
|
|
// It's not really relevant what we do here, because this probably will
|
|
// not be used at normal font sizes.
|
|
return true;
|
|
}
|
|
|
|
|
|
//static
|
|
bool Font::SJISTraits::canBreakAfter(Std::string::const_iterator &i) {
|
|
Std::string::const_iterator j = i;
|
|
uint32 u1 = unicode(j);
|
|
|
|
// See: https://wiki.wesnoth.org/index.php?title=JapaneseTranslation&oldid=23480#Word-Wrapping
|
|
// and: https://ja.wikipedia.org/wiki/%E7%A6%81%E5%89%87
|
|
|
|
switch (u1) {
|
|
case 0xff08:
|
|
case 0x3014:
|
|
case 0xff3b:
|
|
case 0xff5b:
|
|
case 0x3008:
|
|
case 0x300a:
|
|
case 0x300c:
|
|
case 0x300e:
|
|
case 0x3010:
|
|
case 0x2018:
|
|
case 0x201c:
|
|
return false;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
uint32 u2 = unicode(j);
|
|
switch (u2) {
|
|
case 0x3001:
|
|
case 0x3002:
|
|
case 0xff0c:
|
|
case 0xff0e:
|
|
case 0xff09:
|
|
case 0x3015:
|
|
case 0xff3d:
|
|
case 0xff5d:
|
|
case 0x3009:
|
|
case 0x300b:
|
|
case 0x300d:
|
|
case 0x300f:
|
|
case 0x3011:
|
|
case 0x2019:
|
|
case 0x201d:
|
|
case 0x309d:
|
|
case 0x309e:
|
|
case 0x30fd:
|
|
case 0x30fe:
|
|
case 0x3005:
|
|
case 0xff1f:
|
|
case 0xff01:
|
|
case 0xff1a:
|
|
case 0xff1b:
|
|
case 0x3041:
|
|
case 0x3043:
|
|
case 0x3045:
|
|
case 0x3047:
|
|
case 0x3049:
|
|
case 0x3083:
|
|
case 0x3085:
|
|
case 0x3087:
|
|
case 0x308e:
|
|
case 0x30a1:
|
|
case 0x30a3:
|
|
case 0x30a5:
|
|
case 0x30a7:
|
|
case 0x30a9:
|
|
case 0x30e3:
|
|
case 0x30e5:
|
|
case 0x30e7:
|
|
case 0x30ee:
|
|
case 0x3063:
|
|
case 0x30f5:
|
|
case 0x30c3:
|
|
case 0x30f6:
|
|
case 0x30fb:
|
|
case 0x2026:
|
|
case 0x30fc:
|
|
return false;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Also don't allow breaking between roman characters
|
|
if (((u1 >= 'A' && u1 <= 'Z') || (u1 >= 'a' && u1 <= 'z')) &&
|
|
((u2 >= 'A' && u2 <= 'Z') || (u2 >= 'a' && u2 <= 'z'))) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
template<class T>
|
|
static void findWordEnd(const Std::string &text,
|
|
Std::string::const_iterator &iter, bool u8specials) {
|
|
while (iter != text.end()) {
|
|
if (T::isSpace(iter, u8specials)) return;
|
|
T::advance(iter);
|
|
}
|
|
}
|
|
|
|
template<class T>
|
|
static void passSpace(const Std::string &text,
|
|
Std::string::const_iterator &iter, bool u8specials) {
|
|
while (iter != text.end()) {
|
|
if (!T::isSpace(iter, u8specials)) return;
|
|
T::advance(iter);
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
Special characters in U8:
|
|
|
|
@ = bullet for conversation options
|
|
~ = line break
|
|
% = tab
|
|
* = line break on graves and plaques, possibly page break in books
|
|
CHECKME: any others? (page breaks for books?)
|
|
|
|
*/
|
|
|
|
template<class T>
|
|
Std::list<PositionedText> typesetText(Font *font,
|
|
const Std::string &text, unsigned int &remaining, int32 width, int32 height,
|
|
Font::TextAlign align, bool u8specials, bool pagebreaks, int32 &resultwidth,
|
|
int32 &resultheight, Std::string::size_type cursor) {
|
|
|
|
debugC(kDebugGraphics, "typeset (%d, %d) %s", width, height, text.c_str());
|
|
|
|
// be optimistic and assume everything will fit
|
|
remaining = text.size();
|
|
|
|
Std::string curline;
|
|
|
|
int totalwidth = 0;
|
|
int totalheight = 0;
|
|
|
|
Std::list<PositionedText> lines;
|
|
PositionedText line;
|
|
|
|
Std::string::const_iterator iter = text.begin();
|
|
Std::string::const_iterator cursoriter = text.begin();
|
|
if (cursor != Std::string::npos) cursoriter += cursor;
|
|
Std::string::const_iterator curlinestart = text.begin();
|
|
|
|
bool breakhere = false;
|
|
while (true) {
|
|
if (iter == text.end() || breakhere || T::isBreak(iter, u8specials)) {
|
|
// break here
|
|
bool atpagebreak = pagebreaks && T::isPageBreak(iter, u8specials);
|
|
int32 stringwidth = 0, stringheight = 0;
|
|
font->getStringSize(curline, stringwidth, stringheight);
|
|
line._dims.left = 0;
|
|
line._dims.top = totalheight;
|
|
line._dims.setWidth(stringwidth);
|
|
line._dims.setHeight(stringheight);
|
|
line._text = curline;
|
|
line._cursor = Std::string::npos;
|
|
if (cursor != Std::string::npos && cursoriter >= curlinestart &&
|
|
(cursoriter < iter || (!breakhere && cursoriter == iter))) {
|
|
line._cursor = cursoriter - curlinestart;
|
|
if (line._dims.width() == 0) {
|
|
stringwidth = 2;
|
|
line._dims.setWidth(stringwidth);
|
|
}
|
|
}
|
|
lines.push_back(line);
|
|
|
|
if (stringwidth > totalwidth) totalwidth = stringwidth;
|
|
totalheight += font->getBaselineSkip();
|
|
|
|
curline = "";
|
|
|
|
if (iter == text.end())
|
|
break; // done
|
|
|
|
if (breakhere) {
|
|
breakhere = false;
|
|
curlinestart = iter;
|
|
} else {
|
|
T::advance(iter);
|
|
curlinestart = iter;
|
|
}
|
|
|
|
if (atpagebreak || (height != 0 && totalheight + font->getHeight() > height)) {
|
|
// next line won't fit
|
|
remaining = curlinestart - text.begin();
|
|
break;
|
|
}
|
|
|
|
} else {
|
|
|
|
// see if next word still fits on the current line
|
|
Std::string::const_iterator nextword = iter;
|
|
passSpace<T>(text, nextword, u8specials);
|
|
|
|
// process spaces
|
|
bool foundLF = false;
|
|
Std::string spaces;
|
|
for (; iter < nextword; T::advance(iter)) {
|
|
if (T::isBreak(iter, u8specials)) {
|
|
foundLF = true;
|
|
break;
|
|
} else if (T::isTab(iter, u8specials)) {
|
|
// ignore tabs at beginning of line when centered
|
|
if (!(curline.empty() && align == Font::TEXT_CENTER))
|
|
spaces.append(" ");
|
|
} else if (!curline.empty()) {
|
|
spaces.append(" ");
|
|
}
|
|
}
|
|
// no next word?
|
|
if (foundLF || nextword == text.end()) continue;
|
|
|
|
// process word
|
|
Std::string::const_iterator endofnextword = iter;
|
|
findWordEnd<T>(text, endofnextword, u8specials);
|
|
int32 stringwidth = 0, stringheight = 0;
|
|
Std::string newline = curline + spaces +
|
|
text.substr(nextword - text.begin(), endofnextword - nextword);
|
|
font->getStringSize(newline, stringwidth, stringheight);
|
|
|
|
// if not, break line before this word
|
|
if (width != 0 && stringwidth > width) {
|
|
if (!curline.empty()) {
|
|
iter = nextword;
|
|
} else {
|
|
// word is longer than the line; have to break in mid-word
|
|
// FIXME: this is rather inefficient; binary search?
|
|
// FIXME: clean up...
|
|
iter = nextword;
|
|
Std::string::const_iterator saveiter = nextword; // Dummy initialization
|
|
Std::string::const_iterator saveiter_fail;
|
|
Std::string curline_fail;
|
|
newline = spaces;
|
|
bool breakok = true;
|
|
int breakcount = -1;
|
|
do {
|
|
if (breakok) {
|
|
curline = newline;
|
|
saveiter = iter;
|
|
breakcount++;
|
|
}
|
|
curline_fail = newline;
|
|
saveiter_fail = iter;
|
|
|
|
if (iter == text.end()) break;
|
|
|
|
breakok = T::canBreakAfter(iter);
|
|
|
|
// try next character
|
|
T::advance(iter);
|
|
newline = spaces + text.substr(nextword - text.begin(),
|
|
iter - nextword);
|
|
font->getStringSize(newline, stringwidth, stringheight);
|
|
} while (stringwidth <= width);
|
|
if (breakcount > 0) {
|
|
iter = saveiter;
|
|
} else {
|
|
iter = saveiter_fail;
|
|
curline = curline_fail;
|
|
}
|
|
}
|
|
breakhere = true;
|
|
continue;
|
|
} else {
|
|
// copy next word into curline
|
|
curline = newline;
|
|
iter = endofnextword;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (lines.size() == 1 && align == Font::TEXT_LEFT) {
|
|
// only one line, so use the actual text width
|
|
width = totalwidth;
|
|
}
|
|
|
|
if (width != 0) totalwidth = width;
|
|
|
|
// adjust total height
|
|
totalheight -= font->getBaselineSkip();
|
|
totalheight += font->getHeight();
|
|
|
|
// fixup x coordinates of lines
|
|
for (auto &l : lines) {
|
|
switch (align) {
|
|
case Font::TEXT_LEFT:
|
|
break;
|
|
case Font::TEXT_RIGHT:
|
|
l._dims.moveTo(totalwidth - l._dims.width(), l._dims.top);
|
|
break;
|
|
case Font::TEXT_CENTER:
|
|
l._dims.moveTo((totalwidth - l._dims.width()) / 2, l._dims.top);
|
|
break;
|
|
}
|
|
|
|
debugC(kDebugGraphics, "%d, %d, %d, %d: %s", l._dims.left, l._dims.top,
|
|
l._dims.width(), l._dims.height(), l._text.c_str());
|
|
}
|
|
|
|
resultwidth = totalwidth;
|
|
resultheight = totalheight;
|
|
|
|
return lines;
|
|
}
|
|
|
|
|
|
// explicit instantiations
|
|
template
|
|
Std::list<PositionedText> typesetText<Font::Traits>
|
|
(Font *font, const Std::string &text,
|
|
unsigned int &remaining, int32 width, int32 height,
|
|
Font::TextAlign align, bool u8specials, bool pagebreaks,
|
|
int32 &resultwidth, int32 &resultheight, Std::string::size_type cursor);
|
|
|
|
template
|
|
Std::list<PositionedText> typesetText<Font::SJISTraits>
|
|
(Font *font, const Std::string &text,
|
|
unsigned int &remaining, int32 width, int32 height,
|
|
Font::TextAlign align, bool u8specials, bool pagebreaks,
|
|
int32 &resultwidth, int32 &resultheight, Std::string::size_type cursor);
|
|
|
|
} // End of namespace Ultima8
|
|
} // End of namespace Ultima
|