diff options
author | Colin Snover | 2016-02-28 21:19:06 -0600 |
---|---|---|
committer | Colin Snover | 2016-02-28 21:48:56 -0600 |
commit | 3e5adc33a84b0a4f0af6ab289782bc6ec7319c9c (patch) | |
tree | f287a8ebe00b4ce1d2c432b2be45d6f5dc3d0103 | |
parent | 2c8160245460cc9a7a864b58b4e962f7a2c77c4b (diff) | |
download | scummvm-rg350-3e5adc33a84b0a4f0af6ab289782bc6ec7319c9c.tar.gz scummvm-rg350-3e5adc33a84b0a4f0af6ab289782bc6ec7319c9c.tar.bz2 scummvm-rg350-3e5adc33a84b0a4f0af6ab289782bc6ec7319c9c.zip |
SCI32: Non-titled text bitmap implementation
This implementation is not 100% engine accurate, but it is
more accurate than what was there, and hopefully the differences
between this and the engine code are merely cosmetic.
The known (intentional) differences are:
1. Uses ScummVM rects inside the engine code, converting to/from
SCI rects on the kernel edges and when scaling
2. Fewer side effects when performing operations that *should*
have been pure from the start (like text dimension calculation).
Still not side-effect-free, but at least things like colours
and alignment do not need to be reset every time a measurement
is taken, unlike in the actual engine.
Editor controls and some other kBitmap code are temporarily
disabled as a result of changes to GfxText32 until they can be
updated to be engine-accurate.
-rw-r--r-- | engines/sci/engine/kernel.h | 3 | ||||
-rw-r--r-- | engines/sci/engine/kernel_tables.h | 55 | ||||
-rw-r--r-- | engines/sci/engine/kgraphics.cpp | 7 | ||||
-rw-r--r-- | engines/sci/engine/kgraphics32.cpp | 58 | ||||
-rw-r--r-- | engines/sci/engine/kstring.cpp | 13 | ||||
-rw-r--r-- | engines/sci/graphics/controls32.cpp | 3 | ||||
-rw-r--r-- | engines/sci/graphics/helpers.h | 12 | ||||
-rw-r--r-- | engines/sci/graphics/text32.cpp | 715 | ||||
-rw-r--r-- | engines/sci/graphics/text32.h | 98 |
9 files changed, 504 insertions, 460 deletions
diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h index 92916ecc68..3a6809aec1 100644 --- a/engines/sci/engine/kernel.h +++ b/engines/sci/engine/kernel.h @@ -499,6 +499,8 @@ reg_t kPalVaryMergeStart(EngineState *s, int argc, reg_t *argv); // SCI2.1 Kernel Functions reg_t kMorphOn(EngineState *s, int argc, reg_t *argv); reg_t kText(EngineState *s, int argc, reg_t *argv); +reg_t kTextSize32(EngineState *s, int argc, reg_t *argv); +reg_t kTextWidth(EngineState *s, int argc, reg_t *argv); reg_t kSave(EngineState *s, int argc, reg_t *argv); reg_t kAutoSave(EngineState *s, int argc, reg_t *argv); reg_t kList(EngineState *s, int argc, reg_t *argv); @@ -516,6 +518,7 @@ reg_t kGetSierraProfileInt(EngineState *s, int argc, reg_t *argv); reg_t kCelInfo(EngineState *s, int argc, reg_t *argv); reg_t kSetLanguage(EngineState *s, int argc, reg_t *argv); reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv); +reg_t kSetFontHeight(EngineState *s, int argc, reg_t *argv); reg_t kSetFontRes(EngineState *s, int argc, reg_t *argv); reg_t kFont(EngineState *s, int argc, reg_t *argv); reg_t kBitmap(EngineState *s, int argc, reg_t *argv); diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h index fbd0b13c88..d3b2bfee54 100644 --- a/engines/sci/engine/kernel_tables.h +++ b/engines/sci/engine/kernel_tables.h @@ -60,15 +60,17 @@ struct SciKernelMapSubEntry { #define SCI_SUBOPENTRY_TERMINATOR { SCI_VERSION_NONE, SCI_VERSION_NONE, 0, NULL, NULL, NULL, NULL } -#define SIG_SCIALL SCI_VERSION_NONE, SCI_VERSION_NONE -#define SIG_SCI0 SCI_VERSION_NONE, SCI_VERSION_01 -#define SIG_SCI1 SCI_VERSION_1_EGA_ONLY, SCI_VERSION_1_LATE -#define SIG_SCI11 SCI_VERSION_1_1, SCI_VERSION_1_1 -#define SIG_SINCE_SCI11 SCI_VERSION_1_1, SCI_VERSION_NONE -#define SIG_SCI21EARLY_ONLY SCI_VERSION_2_1_EARLY, SCI_VERSION_2_1_EARLY -#define SIG_SINCE_SCI21 SCI_VERSION_2_1_EARLY, SCI_VERSION_3 -#define SIG_UNTIL_SCI21MID SCI_VERSION_2, SCI_VERSION_2_1_MIDDLE -#define SIG_SINCE_SCI21LATE SCI_VERSION_2_1_LATE, SCI_VERSION_3 +#define SIG_SCIALL SCI_VERSION_NONE, SCI_VERSION_NONE +#define SIG_SCI0 SCI_VERSION_NONE, SCI_VERSION_01 +#define SIG_SCI1 SCI_VERSION_1_EGA_ONLY, SCI_VERSION_1_LATE +#define SIG_SCI11 SCI_VERSION_1_1, SCI_VERSION_1_1 +#define SIG_SINCE_SCI11 SCI_VERSION_1_1, SCI_VERSION_NONE +#define SIG_SCI21EARLY SCI_VERSION_2_1_EARLY, SCI_VERSION_2_1_EARLY +#define SIG_UNTIL_SCI21EARLY SCI_VERSION_2, SCI_VERSION_2_1_EARLY +#define SIG_UNTIL_SCI21MID SCI_VERSION_2, SCI_VERSION_2_1_MIDDLE +#define SIG_SINCE_SCI21 SCI_VERSION_2_1_EARLY, SCI_VERSION_3 +#define SIG_SINCE_SCI21MID SCI_VERSION_2_1_MIDDLE, SCI_VERSION_3 +#define SIG_SINCE_SCI21LATE SCI_VERSION_2_1_LATE, SCI_VERSION_3 #define SIG_SCI16 SCI_VERSION_NONE, SCI_VERSION_1_1 #define SIG_SCI32 SCI_VERSION_2, SCI_VERSION_NONE @@ -284,6 +286,20 @@ static const SciKernelMapSubEntry kSave_subops[] = { }; // version, subId, function-mapping, signature, workarounds +static const SciKernelMapSubEntry kFont_subops[] = { + { SIG_SINCE_SCI21MID, 0, MAP_CALL(SetFontHeight), "i", NULL }, + { SIG_SINCE_SCI21MID, 1, MAP_CALL(SetFontRes), "ii", NULL }, + SCI_SUBOPENTRY_TERMINATOR +}; + +// version, subId, function-mapping, signature, workarounds +static const SciKernelMapSubEntry kText_subops[] = { + { SIG_SINCE_SCI21MID, 0, MAP_CALL(TextSize32), "r[r0]i(i)(i)", NULL }, + { SIG_SINCE_SCI21MID, 1, MAP_CALL(TextWidth), "ri", NULL }, + SCI_SUBOPENTRY_TERMINATOR +}; + +// version, subId, function-mapping, signature, workarounds static const SciKernelMapSubEntry kList_subops[] = { { SIG_SINCE_SCI21, 0, MAP_CALL(NewList), "", NULL }, { SIG_SINCE_SCI21, 1, MAP_CALL(DisposeList), "l", NULL }, @@ -546,10 +562,10 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(StrEnd), SIG_EVERYWHERE, "r", NULL, NULL }, { MAP_CALL(StrLen), SIG_EVERYWHERE, "[r0]", NULL, kStrLen_workarounds }, { MAP_CALL(StrSplit), SIG_EVERYWHERE, "rr[r0]", NULL, NULL }, - { MAP_CALL(TextColors), SIG_EVERYWHERE, "(i*)", NULL, NULL }, - { MAP_CALL(TextFonts), SIG_EVERYWHERE, "(i*)", NULL, NULL }, - { MAP_CALL(TextSize), SIG_SCIALL, SIGFOR_MAC, "r[r0]i(i)(r0)(i)", NULL, NULL }, - { MAP_CALL(TextSize), SIG_EVERYWHERE, "r[r0]i(i)(r0)", NULL, NULL }, + { MAP_CALL(TextColors), SIG_SCI16, SIGFOR_ALL, "(i*)", NULL, NULL }, + { MAP_CALL(TextFonts), SIG_SCI16, SIGFOR_ALL, "(i*)", NULL, NULL }, + { MAP_CALL(TextSize), SIG_SCI16, SIGFOR_MAC, "r[r0]i(i)(r0)(i)", NULL, NULL }, + { MAP_CALL(TextSize), SIG_SCI16, SIGFOR_ALL, "r[r0]i(i)(r0)", NULL, NULL }, { MAP_CALL(TimesCos), SIG_EVERYWHERE, "ii", NULL, NULL }, { "CosMult", kTimesCos, SIG_EVERYWHERE, "ii", NULL, NULL }, { MAP_CALL(TimesCot), SIG_EVERYWHERE, "ii", NULL, NULL }, @@ -581,6 +597,10 @@ static SciKernelMapEntry s_kernelMap[] = { #ifdef ENABLE_SCI32 // SCI2 Kernel Functions // TODO: whoever knows his way through those calls, fix the signatures. + { "TextSize", kTextSize32, SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "r[r0]i(i)", NULL, NULL }, + { MAP_DUMMY(TextColors), SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "(.*)", NULL, NULL }, + { MAP_DUMMY(TextFonts), SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "(.*)", NULL, NULL }, + { MAP_CALL(AddPlane), SIG_EVERYWHERE, "o", NULL, NULL }, { MAP_CALL(AddScreenItem), SIG_EVERYWHERE, "o", NULL, NULL }, { MAP_CALL(Array), SIG_EVERYWHERE, "(.*)", NULL, NULL }, @@ -651,7 +671,7 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_DUMMY(ShowStylePercent), SIG_EVERYWHERE, "(.*)", NULL, NULL }, { MAP_DUMMY(InvertRect), SIG_EVERYWHERE, "(.*)", NULL, NULL }, { MAP_DUMMY(InputText), SIG_EVERYWHERE, "(.*)", NULL, NULL }, - { MAP_DUMMY(TextWidth), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(TextWidth), SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "ri", NULL, NULL }, { MAP_DUMMY(PointSize), SIG_EVERYWHERE, "(.*)", NULL, NULL }, // SCI2.1 Kernel Functions @@ -662,7 +682,7 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(PlayVMD), SIG_EVERYWHERE, "(.*)", NULL, NULL }, { MAP_CALL(Robot), SIG_EVERYWHERE, "(.*)", NULL, NULL }, { MAP_CALL(Save), SIG_EVERYWHERE, "i(.*)", kSave_subops, NULL }, - { MAP_CALL(Text), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(Text), SIG_SINCE_SCI21MID, SIGFOR_ALL, "i(.*)", kText_subops, NULL }, { MAP_CALL(AddPicAt), SIG_EVERYWHERE, "oiii", NULL, NULL }, { MAP_CALL(GetWindowsOption), SIG_EVERYWHERE, "i", NULL, NULL }, { MAP_CALL(WinHelp), SIG_EVERYWHERE, "(.*)", NULL, NULL }, @@ -671,8 +691,8 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(CelInfo), SIG_EVERYWHERE, "iiiiii", NULL, NULL }, { MAP_CALL(SetLanguage), SIG_EVERYWHERE, "r", NULL, NULL }, { MAP_CALL(ScrollWindow), SIG_EVERYWHERE, "i(.*)", kScrollWindow_subops, NULL }, - { MAP_CALL(SetFontRes), SIG_SCI21EARLY_ONLY, SIGFOR_ALL, "ii", NULL, NULL }, - { MAP_CALL(Font), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, + { MAP_CALL(SetFontRes), SIG_SCI21EARLY, SIGFOR_ALL, "ii", NULL, NULL }, + { MAP_CALL(Font), SIG_SINCE_SCI21MID, SIGFOR_ALL, "i(.*)", kFont_subops, NULL }, { MAP_CALL(Bitmap), SIG_EVERYWHERE, "(.*)", NULL, NULL }, { MAP_CALL(AddLine), SIG_EVERYWHERE, "oiiiiiiiii", NULL, NULL }, { MAP_CALL(UpdateLine), SIG_EVERYWHERE, "[r0]oiiiiiiiii", NULL, NULL }, @@ -1030,7 +1050,6 @@ static const char *const sci2_default_knames[] = { /*0x89*/ "TextWidth", // for debugging(?), only in SCI2, not used in any SCI2 game /*0x8a*/ "PointSize", // for debugging(?), only in SCI2, not used in any SCI2 game - // GK2 Demo (and similar) only kernel functions /*0x8b*/ "AddLine", /*0x8c*/ "DeleteLine", /*0x8d*/ "UpdateLine", diff --git a/engines/sci/engine/kgraphics.cpp b/engines/sci/engine/kgraphics.cpp index 91d241fb79..c0a3be4c11 100644 --- a/engines/sci/engine/kgraphics.cpp +++ b/engines/sci/engine/kgraphics.cpp @@ -359,12 +359,7 @@ reg_t kTextSize(EngineState *s, int argc, reg_t *argv) { uint16 languageSplitter = 0; Common::String splitText = g_sci->strSplitLanguage(text.c_str(), &languageSplitter, sep); -#ifdef ENABLE_SCI32 - if (g_sci->_gfxText32) - g_sci->_gfxText32->kernelTextSize(splitText.c_str(), font_nr, maxwidth, &textWidth, &textHeight); - else -#endif - g_sci->_gfxText16->kernelTextSize(splitText.c_str(), languageSplitter, font_nr, maxwidth, &textWidth, &textHeight); + g_sci->_gfxText16->kernelTextSize(splitText.c_str(), languageSplitter, font_nr, maxwidth, &textWidth, &textHeight); // One of the game texts in LB2 German contains loads of spaces in // its end. We trim the text here, otherwise the graphics code will diff --git a/engines/sci/engine/kgraphics32.cpp b/engines/sci/engine/kgraphics32.cpp index 4d48ae4e99..706edc87c6 100644 --- a/engines/sci/engine/kgraphics32.cpp +++ b/engines/sci/engine/kgraphics32.cpp @@ -197,7 +197,7 @@ reg_t kCreateTextBitmap(EngineState *s, int argc, reg_t *argv) { if (subop == 0) { TextAlign alignment = (TextAlign)readSelectorValue(segMan, object, SELECTOR(mode)); reg_t out; - return g_sci->_gfxText32->createFontBitmap(width, height, rect, text, foreColor, backColor, skipColor, fontId, alignment, borderColor, dimmed, 1, &out); + return g_sci->_gfxText32->createFontBitmap(width, height, rect, text, foreColor, backColor, skipColor, fontId, alignment, borderColor, dimmed, true, &out); } else { CelInfo32 celInfo; celInfo.type = kCelTypeView; @@ -214,6 +214,35 @@ reg_t kDisposeTextBitmap(EngineState *s, int argc, reg_t *argv) { return s->r_acc; } +reg_t kText(EngineState *s, int argc, reg_t *argv) { + if (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); +} + +reg_t kTextSize32(EngineState *s, int argc, reg_t *argv) { + g_sci->_gfxText32->setFont(argv[2].toUint16()); + + reg_t *rect = s->_segMan->derefRegPtr(argv[0], 4); + + Common::String text = s->_segMan->getString(argv[1]); + int16 maxWidth = argc > 3 ? argv[3].toSint16() : 0; + bool doScaling = argc > 4 ? argv[4].toSint16() : true; + + Common::Rect textRect = g_sci->_gfxText32->getTextSize(text, maxWidth, doScaling); + rect[0] = make_reg(0, textRect.left); + rect[1] = make_reg(0, textRect.top); + rect[2] = make_reg(0, textRect.right - 1); + rect[3] = make_reg(0, textRect.bottom - 1); + return NULL_REG; +} + +reg_t kTextWidth(EngineState *s, int argc, reg_t *argv) { + g_sci->_gfxText32->setFont(argv[1].toUint16()); + Common::String text = s->_segMan->getString(argv[0]); + return make_reg(0, g_sci->_gfxText32->getStringWidth(text)); +} + reg_t kWinHelp(EngineState *s, int argc, reg_t *argv) { switch (argv[0].toUint16()) { case 1: @@ -454,18 +483,18 @@ reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv) { #endif reg_t kFont(EngineState *s, int argc, reg_t *argv) { - // TODO: Handle font settings for SCI2.1 - - switch (argv[0].toUint16()) { - case 1: - g_sci->_gfxText32->_scaledWidth = argv[1].toUint16(); - g_sci->_gfxText32->_scaledHeight = argv[2].toUint16(); - return NULL_REG; - default: - error("kFont: unknown subop %d", argv[0].toUint16()); - } + if (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); +} - return s->r_acc; +reg_t kSetFontHeight(EngineState *s, int argc, reg_t *argv) { + // TODO: Setting font may have just been for side effect + // of setting the fontHeight on the font manager, in + // which case we could just get the font directly ourselves. + g_sci->_gfxText32->setFont(argv[0].toUint16()); + g_sci->_gfxText32->_scaledHeight = (g_sci->_gfxText32->_font->getHeight() * g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight + g_sci->_gfxText32->_scaledHeight - 1) / g_sci->_gfxText32->_scaledHeight; + return NULL_REG; } reg_t kSetFontRes(EngineState *s, int argc, reg_t *argv) { @@ -563,6 +592,10 @@ reg_t kBitmap(EngineState *s, int argc, reg_t *argv) { break; case 4: // add text to bitmap { + warning("kBitmap(4)"); + return NULL_REG; + } +#if 0 // 13 params, called e.g. from TextButton::createBitmap() in Torin's Passage, // script 64894 reg_t hunkId = argv[1]; // obtained from kBitmap(0) @@ -613,6 +646,7 @@ reg_t kBitmap(EngineState *s, int argc, reg_t *argv) { } break; +#endif case 5: // fill with color { // 6 params, called e.g. from TextView::init() and TextView::draw() diff --git a/engines/sci/engine/kstring.cpp b/engines/sci/engine/kstring.cpp index 310e38dbd1..f598cf7457 100644 --- a/engines/sci/engine/kstring.cpp +++ b/engines/sci/engine/kstring.cpp @@ -661,19 +661,6 @@ reg_t kStrSplit(EngineState *s, int argc, reg_t *argv) { #ifdef ENABLE_SCI32 -reg_t kText(EngineState *s, int argc, reg_t *argv) { - switch (argv[0].toUint16()) { - case 0: - return kTextSize(s, argc - 1, argv + 1); - default: - // TODO: Other subops here too, perhaps kTextColors and kTextFonts - warning("kText(%d)", argv[0].toUint16()); - break; - } - - return s->r_acc; -} - // TODO: there is an unused second argument, happens at least in LSL6 right during the intro reg_t kStringNew(EngineState *s, int argc, reg_t *argv) { reg_t stringHandle; diff --git a/engines/sci/graphics/controls32.cpp b/engines/sci/graphics/controls32.cpp index 04a70d35f3..df518888a9 100644 --- a/engines/sci/graphics/controls32.cpp +++ b/engines/sci/graphics/controls32.cpp @@ -178,11 +178,14 @@ void GfxControls32::kernelTexteditChange(reg_t controlObject) { // Write back string _segMan->strcpy(textReference, text.c_str()); // Modify the buffer and show it + warning("kernelTexteditChange"); +#if 0 _text->createTextBitmap(controlObject, 0, 0, hunkId); _text->drawTextBitmap(0, 0, nsRect, controlObject); //texteditCursorDraw(rect, text.c_str(), cursorPos); // TODO: Cursor g_system->updateScreen(); +#endif } else { // TODO: Cursor /* diff --git a/engines/sci/graphics/helpers.h b/engines/sci/graphics/helpers.h index fbad120374..1d16b49798 100644 --- a/engines/sci/graphics/helpers.h +++ b/engines/sci/graphics/helpers.h @@ -139,6 +139,18 @@ inline void mul(Common::Rect &rect, const Common::Rational &ratioX, const Common } /** + * Multiplies a rectangle by two ratios with default + * rounding. Modifies the rect directly. Uses inclusive + * rectangle rounding. + */ +inline void mulinc(Common::Rect &rect, const Common::Rational &ratioX, const Common::Rational &ratioY) { + rect.left = (rect.left * ratioX).toInt(); + rect.top = (rect.top * ratioY).toInt(); + rect.right = ((rect.right - 1) * ratioX).toInt() + 1; + rect.bottom = ((rect.bottom - 1) * ratioY).toInt() + 1; +} + +/** * Multiplies a number by a rational number, rounding up to * the nearest whole number. */ diff --git a/engines/sci/graphics/text32.cpp b/engines/sci/graphics/text32.cpp index eabc329ee0..e393a1c68f 100644 --- a/engines/sci/graphics/text32.cpp +++ b/engines/sci/graphics/text32.cpp @@ -37,7 +37,7 @@ namespace Sci { -#define BITMAP_HEADER_SIZE 46 +int16 GfxText32::_defaultFontId = 0; GfxText32::GfxText32(SegManager *segMan, GfxCache *fonts, GfxScreen *screen) : _segMan(segMan), @@ -45,8 +45,21 @@ GfxText32::GfxText32(SegManager *segMan, GfxCache *fonts, GfxScreen *screen) : _screen(screen), _scaledWidth(g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth), _scaledHeight(g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight), - _bitmap(NULL_REG) {} + // Not a typo, the original engine did not initialise height, only width + _width(0), + _text(""), + _field_20(0), + _field_2C(2), + _field_30(0), + _field_34(0), + _field_38(0), + _field_3C(0), + _bitmap(NULL_REG) { + _fontId = _defaultFontId; + _font = _cache->getFont(_defaultFontId); + } +#define BITMAP_HEADER_SIZE 46 void GfxText32::buildBitmapHeader(byte *bitmap, const int16 width, const int16 height, const uint8 skipColor, const int16 displaceX, const int16 displaceY, const int16 scaledWidth, const int16 scaledHeight, const uint32 hunkPaletteOffset, const bool useRemap) const { WRITE_SCI11ENDIAN_UINT16(bitmap + 0, width); @@ -77,8 +90,6 @@ void GfxText32::buildBitmapHeader(byte *bitmap, const int16 width, const int16 h WRITE_SCI11ENDIAN_UINT16(bitmap + 38, scaledHeight); } -int16 GfxText32::_defaultFontId = 0; - reg_t GfxText32::createFontBitmap(int16 width, int16 height, const Common::Rect &rect, const Common::String &text, const uint8 foreColor, const uint8 backColor, const uint8 skipColor, const GuiResourceId fontId, const TextAlign alignment, const int16 borderColor, const bool dimmed, const bool doScaling, reg_t *outBitmapObject) { _field_22 = 0; @@ -93,10 +104,7 @@ reg_t GfxText32::createFontBitmap(int16 width, int16 height, const Common::Rect _alignment = alignment; _dimmed = dimmed; - if (fontId != _fontId) { - _fontId = fontId == -1 ? _defaultFontId : fontId; - _font = _cache->getFont(_fontId); - } + setFont(fontId); if (doScaling) { int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; @@ -107,7 +115,7 @@ reg_t GfxText32::createFontBitmap(int16 width, int16 height, const Common::Rect _width = (_width * scaleX).toInt(); _height = (_height * scaleY).toInt(); - mul(_textRect, scaleX, scaleY); + mulinc(_textRect, scaleX, scaleY); } // _textRect represents where text is drawn inside the @@ -133,8 +141,6 @@ reg_t GfxText32::createFontBitmap(int16 width, int16 height, const Common::Rect drawTextBox(); - debug("Drawing a bitmap %dx%d, scaled %dx%d, border %d, font %d", width, height, _width, _height, _borderColor, _fontId); - *outBitmapObject = _bitmap; return _bitmap; } @@ -144,7 +150,17 @@ reg_t GfxText32::createTitledFontBitmap(CelInfo32 &celInfo, Common::Rect &rect, return NULL_REG; } -void GfxText32::drawFrame(const Common::Rect &rect, const int size, const uint8 color, const bool doScaling) { +void GfxText32::setFont(const GuiResourceId fontId) { + // NOTE: In SCI engine this calls FontMgr::BuildFontTable and then a font + // table is built on the FontMgr directly; instead, because we already have + // font resources, this code just grabs a font out of GfxCache. + if (fontId != _fontId) { + _fontId = fontId == -1 ? _defaultFontId : fontId; + _font = _cache->getFont(_fontId); + } +} + +void GfxText32::drawFrame(const Common::Rect &rect, const int16 size, const uint8 color, const bool doScaling) { Common::Rect targetRect = doScaling ? scaleRect(rect) : rect; byte *bitmap = _segMan->getHunkPointer(_bitmap); @@ -157,436 +173,367 @@ void GfxText32::drawFrame(const Common::Rect &rect, const int size, const uint8 buffer.frameRect(targetRect, color); } -// TODO: This is not disassembled -void GfxText32::drawTextBox() { - int16 charCount = 0; - uint16 curX = 0, curY = 0; - const char *txt = _text.c_str(); - int16 textWidth, textHeight, totalHeight = 0, offsetX = 0, offsetY = 0; - uint16 start = 0; - - // Calculate total text height - while (*txt) { - charCount = GetLongest(txt, _textRect.width(), _font); - if (charCount == 0) - break; - - Width(txt, 0, (int16)strlen(txt), _fontId, textWidth, textHeight, true); - - totalHeight += textHeight; - txt += charCount; - while (*txt == ' ') { - txt++; // skip over breaking spaces - } - } - - txt = _text.c_str(); - - byte *pixels = _segMan->getHunkPointer(_bitmap); - pixels = pixels + READ_SCI11ENDIAN_UINT32(pixels + 28) + _width * _textRect.top + _textRect.left; - - // Draw text in buffer - while (*txt) { - charCount = GetLongest(txt, _textRect.width(), _font); - if (charCount == 0) - break; - Width(txt, start, charCount, _fontId, textWidth, textHeight, true); - - switch (_alignment) { - case kTextAlignRight: - offsetX = _textRect.width() - textWidth; - break; - case kTextAlignCenter: - // Center text both horizontally and vertically - offsetX = (_textRect.width() - textWidth) / 2; - offsetY = (_textRect.height() - totalHeight) / 2; - break; - case kTextAlignLeft: - offsetX = 0; - break; - - default: - warning("Invalid alignment %d used in TextBox()", _alignment); - } +void GfxText32::drawChar(const uint8 charIndex) { + byte *bitmap = _segMan->getHunkPointer(_bitmap); + byte *pixels = bitmap + READ_SCI11ENDIAN_UINT32(bitmap + 28); - byte curChar; - - for (int i = 0; i < charCount; i++) { - curChar = txt[i]; - - switch (curChar) { - case 0x0A: - case 0x0D: - case 0: - break; - case 0x7C: - warning("Code processing isn't implemented in SCI32"); - break; - default: - _font->drawToBuffer(curChar, curY + offsetY, curX + offsetX, _foreColor, _dimmed, pixels, _width, _height); - curX += _font->getCharWidth(curChar); - break; - } - } + _font->drawToBuffer(charIndex, _drawPosition.y, _drawPosition.x, _foreColor, _dimmed, pixels, _width, _height); + _drawPosition.x += _font->getCharWidth(charIndex); +} - curX = 0; - curY += _font->getHeight(); - txt += charCount; - while (*txt == ' ') { - txt++; // skip over breaking spaces - } +uint16 GfxText32::getCharWidth(const uint8 charIndex, const bool doScaling) const { + uint16 width = _font->getCharWidth(charIndex); + if (doScaling) { + width = scaleUpWidth(width); } + return width; } -void GfxText32::erase(const Common::Rect &rect, const bool doScaling) { - Common::Rect targetRect = doScaling ? rect : scaleRect(rect); +void GfxText32::drawTextBox() { + if (_text.size() == 0) { + return; + } - byte *bitmap = _segMan->getHunkPointer(_bitmap); - byte *pixels = bitmap + READ_SCI11ENDIAN_UINT32(bitmap + 28); + const char *text = _text.c_str(); + const char *sourceText = text; + int16 textRectWidth = _textRect.width(); + _drawPosition.y = _textRect.top; + uint charIndex = 0; + if (getLongest(&charIndex, textRectWidth) == 0) { + error("DrawTextBox GetLongest=0"); + } - // NOTE: There is an extra optimisation within the SCI code to - // do a single memset if the scaledRect is the same size as - // the bitmap, not implemented here. - Buffer buffer(_width, _height, pixels); - buffer.fillRect(targetRect, _backColor); -} + charIndex = 0; + uint nextCharIndex = 0; + while (*text != '\0') { + _drawPosition.x = _textRect.left; -reg_t GfxText32::createScrollTextBitmap(Common::String text, reg_t textObject, uint16 maxWidth, uint16 maxHeight, reg_t prevHunk) { - return createTextBitmapInternal(text, textObject, maxWidth, maxHeight, prevHunk); -} -reg_t GfxText32::createTextBitmap(reg_t textObject, uint16 maxWidth, uint16 maxHeight, reg_t prevHunk) { - reg_t stringObject = readSelector(_segMan, textObject, SELECTOR(text)); - // The object in the text selector of the item can be either a raw string - // or a Str object. In the latter case, we need to access the object's data - // selector to get the raw string. - if (_segMan->isHeapObject(stringObject)) - stringObject = readSelector(_segMan, stringObject, SELECTOR(data)); + uint length = getLongest(&nextCharIndex, textRectWidth); + int16 textWidth = getTextWidth(charIndex, length); - Common::String text = _segMan->getString(stringObject); + if (_alignment == kTextAlignCenter) { + _drawPosition.x += (textRectWidth - textWidth) / 2; + } else if (_alignment == kTextAlignRight) { + _drawPosition.x += textRectWidth - textWidth; + } - return createTextBitmapInternal(text, textObject, maxWidth, maxHeight, prevHunk); + drawText(charIndex, length); + charIndex = nextCharIndex; + text = sourceText + charIndex; + _drawPosition.y += _font->getHeight(); + } } -reg_t GfxText32::createTextBitmapInternal(Common::String &text, reg_t textObject, uint16 maxWidth, uint16 maxHeight, reg_t prevHunk) { - GuiResourceId fontId = readSelectorValue(_segMan, textObject, SELECTOR(font)); - GfxFont *font = _cache->getFont(fontId); - bool dimmed = readSelectorValue(_segMan, textObject, SELECTOR(dimmed)); - int16 alignment = readSelectorValue(_segMan, textObject, SELECTOR(mode)); - uint16 foreColor = readSelectorValue(_segMan, textObject, SELECTOR(fore)); - uint16 backColor = readSelectorValue(_segMan, textObject, SELECTOR(back)); - - Common::Rect nsRect = g_sci->_gfxCompare->getNSRect(textObject); - uint16 width = nsRect.width() + 1; - uint16 height = nsRect.height() + 1; - - // Limit rectangle dimensions, if requested - if (maxWidth > 0) - width = maxWidth; - if (maxHeight > 0) - height = maxHeight; - - // Upscale the coordinates/width if the fonts are already upscaled - if (_screen->fontIsUpscaled()) { - width = width * _screen->getDisplayWidth() / _screen->getWidth(); - height = height * _screen->getDisplayHeight() / _screen->getHeight(); - } +void GfxText32::drawText(const uint index, uint length) { + assert(index + length <= _text.size()); - int entrySize = width * height + BITMAP_HEADER_SIZE; - reg_t memoryId = NULL_REG; - if (prevHunk.isNull()) { - memoryId = _segMan->allocateHunkEntry("TextBitmap()", entrySize); + // NOTE: This draw loop implementation is somewhat different than the + // implementation in the actual engine, but should be accurate. Primarily + // the changes revolve around eliminating some extra temporaries and + // fixing the logic to match. + const char *text = _text.c_str() + index; + while (length-- > 0) { + char currentChar = *text++; - // Scroll text objects have no bitmap selector! - ObjVarRef varp; - if (lookupSelector(_segMan, textObject, SELECTOR(bitmap), &varp, NULL) == kSelectorVariable) - writeSelector(_segMan, textObject, SELECTOR(bitmap), memoryId); - } else { - memoryId = prevHunk; - } - byte *memoryPtr = _segMan->getHunkPointer(memoryId); - - if (prevHunk.isNull()) - memset(memoryPtr, 0, BITMAP_HEADER_SIZE); - - byte *bitmap = memoryPtr + BITMAP_HEADER_SIZE; - memset(bitmap, backColor, width * height); - - // Save totalWidth, totalHeight - WRITE_SCI11ENDIAN_UINT16(memoryPtr, width); - WRITE_SCI11ENDIAN_UINT16(memoryPtr + 2, height); - WRITE_SCI11ENDIAN_UINT16(memoryPtr + 4, 0); - WRITE_SCI11ENDIAN_UINT16(memoryPtr + 6, 0); - memoryPtr[8] = 0; - WRITE_SCI11ENDIAN_UINT16(memoryPtr + 10, 0); - WRITE_SCI11ENDIAN_UINT16(memoryPtr + 20, BITMAP_HEADER_SIZE); - WRITE_SCI11ENDIAN_UINT32(memoryPtr + 28, 46); - WRITE_SCI11ENDIAN_UINT16(memoryPtr + 36, width); - WRITE_SCI11ENDIAN_UINT16(memoryPtr + 38, height); - - int16 charCount = 0; - uint16 curX = 0, curY = 0; - const char *txt = text.c_str(); - int16 textWidth, textHeight, totalHeight = 0, offsetX = 0, offsetY = 0; - uint16 start = 0; - - // Calculate total text height - while (*txt) { - charCount = GetLongest(txt, width, font); - if (charCount == 0) - break; - - Width(txt, 0, (int16)strlen(txt), fontId, textWidth, textHeight, true); - - totalHeight += textHeight; - txt += charCount; - while (*txt == ' ') - txt++; // skip over breaking spaces - } + if (currentChar == '|') { + const char controlChar = *text++; + --length; - txt = text.c_str(); - - // Draw text in buffer - while (*txt) { - charCount = GetLongest(txt, width, font); - if (charCount == 0) - break; - Width(txt, start, charCount, fontId, textWidth, textHeight, true); - - switch (alignment) { - case kTextAlignRight: - offsetX = width - textWidth; - break; - case kTextAlignCenter: - // Center text both horizontally and vertically - offsetX = (width - textWidth) / 2; - offsetY = (height - totalHeight) / 2; - break; - case kTextAlignLeft: - offsetX = 0; - break; - - default: - warning("Invalid alignment %d used in TextBox()", alignment); - } + if (length == 0) { + return; + } - byte curChar; - - for (int i = 0; i < charCount; i++) { - curChar = txt[i]; - - switch (curChar) { - case 0x0A: - case 0x0D: - case 0: - break; - case 0x7C: - warning("Code processing isn't implemented in SCI32"); - break; - default: - font->drawToBuffer(curChar, curY + offsetY, curX + offsetX, foreColor, dimmed, bitmap, width, height); - curX += font->getCharWidth(curChar); - break; + if (controlChar == 'a' || controlChar == 'c' || controlChar == 'f') { + uint16 value = 0; + + while (length > 0) { + const char valueChar = *text; + if (valueChar < '0' || valueChar > '9') { + break; + } + + ++text; + --length; + value = 10 * value + (valueChar - '0'); + } + + if (length == 0) { + return; + } + + if (controlChar == 'a') { + _alignment = (TextAlign)value; + } else if (controlChar == 'c') { + _foreColor = value; + } else if (controlChar == 'f') { + setFont(value); + } } - } - curX = 0; - curY += font->getHeight(); - txt += charCount; - while (*txt == ' ') - txt++; // skip over breaking spaces + while (length > 0 && *text != '|') { + ++text; + --length; + } + } else { + drawChar(currentChar); + } } - - return memoryId; } -void GfxText32::disposeTextBitmap(reg_t hunkId) { - _segMan->freeHunkEntry(hunkId); -} +uint GfxText32::getLongest(uint *charIndex, const int16 width) { + assert(width > 0); -void GfxText32::drawTextBitmap(int16 x, int16 y, Common::Rect planeRect, reg_t textObject) { - reg_t hunkId = readSelector(_segMan, textObject, SELECTOR(bitmap)); - drawTextBitmapInternal(x, y, planeRect, textObject, hunkId); -} + uint testLength = 0; + uint length = 0; -void GfxText32::drawScrollTextBitmap(reg_t textObject, reg_t hunkId, uint16 x, uint16 y) { - /*reg_t plane = readSelector(_segMan, textObject, SELECTOR(plane)); - Common::Rect planeRect; - planeRect.top = readSelectorValue(_segMan, plane, SELECTOR(top)); - planeRect.left = readSelectorValue(_segMan, plane, SELECTOR(left)); - planeRect.bottom = readSelectorValue(_segMan, plane, SELECTOR(bottom)); - planeRect.right = readSelectorValue(_segMan, plane, SELECTOR(right)); + const uint initialCharIndex = *charIndex; - drawTextBitmapInternal(x, y, planeRect, textObject, hunkId);*/ + // The index of the next word after the last word break + uint lastWordBreakIndex = *charIndex; - // HACK: we pretty much ignore the plane rect and x, y... - drawTextBitmapInternal(0, 0, Common::Rect(20, 390, 600, 460), textObject, hunkId); -} + const char *text = _text.c_str() + *charIndex; -void GfxText32::drawTextBitmapInternal(int16 x, int16 y, Common::Rect planeRect, reg_t textObject, reg_t hunkId) { - int16 backColor = (int16)readSelectorValue(_segMan, textObject, SELECTOR(back)); - // Sanity check: Check if the hunk is set. If not, either the game scripts - // didn't set it, or an old saved game has been loaded, where it wasn't set. - if (hunkId.isNull()) - return; + char currentChar; + while ((currentChar = *text++) != '\0') { + // NOTE: In the original engine, the font, color, and alignment were + // reset here to their initial values - // Negative coordinates indicate that text shouldn't be displayed - if (x < 0 || y < 0) - return; + // The text to render contains a line break; stop at the line break + if (currentChar == '\r' || currentChar == '\n') { + // Skip the rest of the line break if it is a Windows-style + // \r\n or non-standard \n\r + // NOTE: In the original engine, the `text` pointer had not been + // advanced yet so the indexes used to access characters were + // one higher + if ( + (currentChar == '\r' && text[0] == '\n') || + (currentChar == '\n' && text[0] == '\r' && text[1] != '\n') + ) { + ++*charIndex; + } - byte *memoryPtr = _segMan->getHunkPointer(hunkId); + // We are at the end of a line but the last word in the line made + // it too wide to fit in the text area; return up to the previous + // word + if (length && getTextWidth(initialCharIndex, testLength) > width) { + *charIndex = lastWordBreakIndex; + return length; + } - if (!memoryPtr) { - // Happens when restoring in some SCI32 games (e.g. SQ6). - // Commented out to reduce console spam - //warning("Attempt to draw an invalid text bitmap"); - return; - } + // Skip the line break and return all text seen up to now + // NOTE: In original engine, the font, color, and alignment were + // reset, then getTextWidth was called to use its side-effects to + // set font, color, and alignment according to the text from + // `initialCharIndex` to `testLength` + ++*charIndex; + return testLength; + } else if (currentChar == ' ') { + // The last word in the line made it too wide to fit in the text area; + // return up to the previous word, then collapse the whitespace + // between that word and its next sibling word into the line break + if (getTextWidth(initialCharIndex, testLength) > width) { + *charIndex = lastWordBreakIndex; + const char *nextChar = _text.c_str() + lastWordBreakIndex; + while (*nextChar++ == ' ') { + ++*charIndex; + } + + // NOTE: In original engine, the font, color, and alignment were + // set here to the values that were seen at the last space character + return length; + } - byte *surface = memoryPtr + BITMAP_HEADER_SIZE; + // NOTE: In the original engine, the values of _fontId, _foreColor, + // and _alignment were stored for use in the return path mentioned + // just above here - int curByte = 0; - int16 skipColor = (int16)readSelectorValue(_segMan, textObject, SELECTOR(skip)); - uint16 textX = planeRect.left + x; - uint16 textY = planeRect.top + y; - // Get totalWidth, totalHeight - uint16 width = READ_LE_UINT16(memoryPtr); - uint16 height = READ_LE_UINT16(memoryPtr + 2); + // We found a word break that was within the text area, memorise it + // and continue processing. +1 on the character index because it has + // not been incremented yet so currently points to the word break + // and not the word after the break + length = testLength; + lastWordBreakIndex = *charIndex + 1; + } - // Upscale the coordinates/width if the fonts are already upscaled - if (_screen->fontIsUpscaled()) { - textX = textX * _screen->getDisplayWidth() / _screen->getWidth(); - textY = textY * _screen->getDisplayHeight() / _screen->getHeight(); - } + // In the middle of a line, keep processing + ++*charIndex; + ++testLength; - bool translucent = (skipColor == -1 && backColor == -1); + // NOTE: In the original engine, the font, color, and alignment were + // reset here to their initial values - for (int curY = 0; curY < height; curY++) { - for (int curX = 0; curX < width; curX++) { - byte pixel = surface[curByte++]; - if ((!translucent && pixel != skipColor && pixel != backColor) || - (translucent && pixel != 0xFF)) - _screen->putFontPixel(textY, curX + textX, curY, pixel); + // The text to render contained no word breaks yet but is already too + // wide for the text area; just split the word in half at the point + // where it overflows + if (length == 0 && getTextWidth(initialCharIndex, testLength) > width) { + *charIndex = --testLength + lastWordBreakIndex; + return testLength; } } -} -int16 GfxText32::GetLongest(const char *text, int16 maxWidth, GfxFont *font) { - uint16 curChar = 0; - int16 maxChars = 0, curCharCount = 0; - uint16 width = 0; - - while (width <= maxWidth) { - curChar = (*(const byte *)text++); - - switch (curChar) { - // We need to add 0xD, 0xA and 0xD 0xA to curCharCount and then exit - // which means, we split text like - // 'Mature, experienced software analyst available.' 0xD 0xA - // 'Bug installation a proven speciality. "No version too clean."' (normal game text, this is from lsl2) - // and 0xA '-------' 0xA (which is the official sierra subtitle separator) - // Sierra did it the same way. - case 0xD: - // Check, if 0xA is following, if so include it as well - if ((*(const unsigned char *)text) == 0xA) - curCharCount++; - // it's meant to pass through here - case 0xA: - curCharCount++; - // and it's also meant to pass through here - case 0: - return curCharCount; - case ' ': - maxChars = curCharCount; // return count up to (but not including) breaking space - break; - } - if (width + font->getCharWidth(curChar) > maxWidth) - break; - width += font->getCharWidth(curChar); - curCharCount++; + // The complete text to render was a single word, or was narrower than + // the text area, so return the entire line + if (length == 0 || getTextWidth(initialCharIndex, testLength) <= width) { + // NOTE: In original engine, the font, color, and alignment were + // reset, then getTextWidth was called to use its side-effects to + // set font, color, and alignment according to the text from + // `initialCharIndex` to `testLength` + return testLength; } - return maxChars; -} - -void GfxText32::kernelTextSize(const char *text, int16 font, int16 maxWidth, int16 *textWidth, int16 *textHeight) { - Common::Rect rect(0, 0, 0, 0); - Size(rect, text, font, maxWidth); - *textWidth = rect.width(); - *textHeight = rect.height(); + // The last word in the line made it wider than the text area, so return + // up to the penultimate word + *charIndex = lastWordBreakIndex; + return length; } -void GfxText32::StringWidth(const char *str, GuiResourceId fontId, int16 &textWidth, int16 &textHeight) { - Width(str, 0, (int16)strlen(str), fontId, textWidth, textHeight, true); -} +int16 GfxText32::getTextWidth(const uint index, uint length) const { + int16 width = 0; + + const char *text = _text.c_str() + index; + + GfxFont *font = _font; + + char currentChar = *text++; + while (length > 0 && currentChar != '\0') { + // Control codes are in the format `|<code><value>|` + if (currentChar == '|') { + // NOTE: Original engine code changed the global state of the + // FontMgr here upon encountering any color, alignment, or + // font control code. + // To avoid requiring all callers to manually restore these + // values on every call, we ignore control codes other than + // font change (since alignment and color do not change the + // width of characters), and simply update the font pointer + // on stack instead of the member property font. + currentChar = *text++; + --length; + + if (length > 0 && currentChar == 'f') { + GuiResourceId fontId = 0; + do { + currentChar = *text++; + --length; + + fontId = fontId * 10 + currentChar - '0'; + } while (length > 0 && currentChar >= '0' && currentChar <= '9'); + + if (length > 0) { + font = _cache->getFont(fontId); + } + } -void GfxText32::Width(const char *text, int16 from, int16 len, GuiResourceId fontId, int16 &textWidth, int16 &textHeight, bool restoreFont) { - byte curChar; - textWidth = 0; textHeight = 0; - - GfxFont *font = _cache->getFont(fontId); - - if (font) { - text += from; - while (len--) { - curChar = (*(const byte *)text++); - switch (curChar) { - case 0x0A: - case 0x0D: - textHeight = MAX<int16> (textHeight, font->getHeight()); - break; - case 0x7C: - warning("Code processing isn't implemented in SCI32"); - break; - default: - textHeight = MAX<int16> (textHeight, font->getHeight()); - textWidth += font->getCharWidth(curChar); - break; + // Forward through any more unknown control character data + while (length > 0 && currentChar != '|') { + ++text; + --length; } + } else { + width += font->getCharWidth(currentChar); } + + currentChar = *text++; + --length; } + + return width; +} + +int16 GfxText32::getTextWidth(const Common::String &text, const uint index, const uint length) { + _text = text; + return scaleUpWidth(getTextWidth(index, length)); } -int16 GfxText32::Size(Common::Rect &rect, const char *text, GuiResourceId fontId, int16 maxWidth) { - int16 charCount; - int16 maxTextWidth = 0, textWidth; - int16 totalHeight = 0, textHeight; +Common::Rect GfxText32::getTextSize(const Common::String &text, int16 maxWidth, bool doScaling) { + // NOTE: Like most of the text rendering code, this function was pretty + // weird in the original engine. The initial result rectangle was actually + // a 1x1 rectangle (0, 0, 0, 0), which was then "fixed" after the main + // text size loop finished running by subtracting 1 from the right and + // bottom edges. + + Common::Rect result; int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; maxWidth = maxWidth * _scaledWidth / scriptWidth; - rect.top = rect.left = 0; - GfxFont *font = _cache->getFont(fontId); + _text = text; - if (maxWidth < 0) { // force output as single line - StringWidth(text, fontId, textWidth, textHeight); - rect.bottom = textHeight; - rect.right = textWidth; - } else { - // rect.right=found widest line with RTextWidth and GetLongest - // rect.bottom=num. lines * GetPointSize - rect.right = (maxWidth ? maxWidth : 192); - const char *curPos = text; - while (*curPos) { - charCount = GetLongest(curPos, rect.right, font); - if (charCount == 0) - break; - Width(curPos, 0, charCount, fontId, textWidth, textHeight, false); - maxTextWidth = MAX(textWidth, maxTextWidth); - totalHeight += textHeight; - curPos += charCount; - while (*curPos == ' ') - curPos++; // skip over breaking spaces + if (maxWidth >= 0) { + if (maxWidth == 0) { + // TODO: This was hardcoded to 192, but guessing + // that it was originally 60% of the scriptWidth + // before the compiler took over. + // Verify this by looking at a game that uses a + // scriptWidth other than 320, like LSL7 + maxWidth = _scaledWidth * (scriptWidth * 0.6) / scriptWidth; } - rect.bottom = totalHeight; - rect.right = maxWidth ? maxWidth : MIN(rect.right, maxTextWidth); + + result.right = maxWidth; + + int16 textWidth = 0; + if (_text.size() > 0) { + const char *rawText = _text.c_str(); + const char *sourceText = rawText; + uint charIndex = 0; + uint nextCharIndex = 0; + while (*rawText != '\0') { + uint length = getLongest(&nextCharIndex, result.width()); + textWidth = MAX(textWidth, getTextWidth(charIndex, length)); + charIndex = nextCharIndex; + rawText = sourceText + charIndex; + // TODO: Due to getLongest and getTextWidth not having side + // effects, it is possible that the currently loaded font's + // height is wrong for this line if it was changed inline + result.bottom += _font->getHeight(); + } + } + + if (textWidth < maxWidth) { + result.right = textWidth; + } + } else { + result.right = getTextWidth(0, 10000); + result.bottom = _font->getHeight() + 1; + } + + if (doScaling) { + // NOTE: The original code did some more complex stuff for edge + // rounding. The right edge was designed to always round down, + // and the bottom edge was designed to always round up. It seems + // we are able to get away with simpler rounding, but it is + // possible that there are still some edge cases here. + mul(result, Ratio(scriptWidth, _scaledWidth), Ratio(scriptHeight, _scaledHeight)); + result.right += 1; + result.bottom += 1; } - rect.right = rect.right * scriptWidth / _scaledWidth; - rect.bottom = rect.bottom * scriptHeight / _scaledHeight; + return result; +} + +void GfxText32::erase(const Common::Rect &rect, const bool doScaling) { + Common::Rect targetRect = doScaling ? rect : scaleRect(rect); + + byte *bitmap = _segMan->getHunkPointer(_bitmap); + byte *pixels = bitmap + READ_SCI11ENDIAN_UINT32(bitmap + 28); + + // NOTE: There is an extra optimisation within the SCI code to + // do a single memset if the scaledRect is the same size as + // the bitmap, not implemented here. + Buffer buffer(_width, _height, pixels); + buffer.fillRect(targetRect, _backColor); +} + +void GfxText32::disposeTextBitmap(reg_t hunkId) { + _segMan->freeHunkEntry(hunkId); +} - return rect.right; +int16 GfxText32::getStringWidth(const Common::String &text) { + // TODO: The fact that this double-scales the text makes it + // seem pretty unlikely that this is ever called in real life + error("Called weirdo getStringWidth (FontMgr::StringWidth)"); + return scaleUpWidth(getTextWidth(text, 0, 10000)); } } // End of namespace Sci diff --git a/engines/sci/graphics/text32.h b/engines/sci/graphics/text32.h index 08568e4958..f9a302a65c 100644 --- a/engines/sci/graphics/text32.h +++ b/engines/sci/graphics/text32.h @@ -31,7 +31,7 @@ namespace Sci { enum TextAlign { kTextAlignLeft = 0, kTextAlignCenter = 1, - kTextAlignRight = -1 + kTextAlignRight = 2 }; /** @@ -43,6 +43,10 @@ enum TextAlign { */ class GfxText32 { private: + SegManager *_segMan; + GfxCache *_cache; + GfxScreen *_screen; + /** * The resource ID of the default font used by the game. * @@ -54,6 +58,8 @@ private: /** * The width and height of the currently active text * bitmap, in text-system coordinates. + * + * @note These are unsigned in the actual engine. */ int16 _width, _height; @@ -110,29 +116,63 @@ private: */ reg_t _bitmap; + int16 _field_20; + /** * TODO: Document */ - int _field_22; + int16 _field_22; + + int _field_2C, _field_30, _field_34, _field_38; + + int16 _field_3C; /** - * The currently active font resource used to write text - * into the bitmap. - * - * @note SCI engine builds the font table directly - * inside of FontMgr; we use GfxFont instead. + * The position of the text draw cursor. */ - GfxFont *_font; + Common::Point _drawPosition; // TODO: This is general for all CelObjMem and should be // put in a single location, like maybe as a static // method of CelObjMem?! void buildBitmapHeader(byte *bitmap, const int16 width, const int16 height, const uint8 skipColor, const int16 displaceX, const int16 displaceY, const int16 scaledWidth, const int16 scaledHeight, const uint32 hunkPaletteOffset, const bool useRemap) const; - void drawFrame(const Common::Rect &rect, const int size, const uint8 color, const bool doScaling); + void drawFrame(const Common::Rect &rect, const int16 size, const uint8 color, const bool doScaling); void drawTextBox(); void erase(const Common::Rect &rect, const bool doScaling); + void drawChar(const uint8 charIndex); + uint16 getCharWidth(const uint8 charIndex, const bool doScaling) const; + void drawText(const uint index, uint length); + + inline int scaleUpWidth(int value) const { + const int scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + return (value * scriptWidth + _scaledWidth - 1) / _scaledWidth; + } + + /** + * Gets the length of the longest run of text available + * within the currently loaded text, starting from the + * given `charIndex` and running for up to `maxWidth` + * pixels. Returns the number of characters that can be + * written, and mutates the value pointed to by + * `charIndex` to point to the index of the next + * character to render. + */ + uint getLongest(uint *charIndex, const int16 maxWidth); + + /** + * Gets the pixel width of a substring of the currently + * loaded text, without scaling. + */ + int16 getTextWidth(const uint index, uint length) const; + + /** + * Gets the pixel width of a substring of the currently + * loaded text, with scaling. + */ + int16 getTextWidth(const Common::String &text, const uint index, const uint length); + inline Common::Rect scaleRect(const Common::Rect &rect) { Common::Rect scaledRect(rect); int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; @@ -158,32 +198,36 @@ public: */ int16 _scaledHeight; + /** + * The currently active font resource used to write text + * into the bitmap. + * + * @note SCI engine builds the font table directly + * inside of FontMgr; we use GfxFont instead. + */ + GfxFont *_font; + reg_t createFontBitmap(int16 width, int16 height, const Common::Rect &rect, const Common::String &text, const uint8 foreColor, const uint8 backColor, const uint8 skipColor, const GuiResourceId fontId, TextAlign alignment, const int16 borderColor, bool dimmed, const bool doScaling, reg_t *outBitmapObject); reg_t createTitledFontBitmap(CelInfo32 &celInfo, Common::Rect &rect, Common::String &text, int16 foreColor, int16 backColor, int font, int16 skipColor, int16 borderColor, bool dimmed, void *unknown1); -#pragma mark - -#pragma mark Old stuff - - reg_t createTextBitmap(reg_t textObject, uint16 maxWidth = 0, uint16 maxHeight = 0, reg_t prevHunk = NULL_REG); - reg_t createScrollTextBitmap(Common::String text, reg_t textObject, uint16 maxWidth = 0, uint16 maxHeight = 0, reg_t prevHunk = NULL_REG); - void drawTextBitmap(int16 x, int16 y, Common::Rect planeRect, reg_t textObject); - void drawScrollTextBitmap(reg_t textObject, reg_t hunkId, uint16 x, uint16 y); void disposeTextBitmap(reg_t hunkId); - int16 GetLongest(const char *text, int16 maxWidth, GfxFont *font); - void kernelTextSize(const char *text, int16 font, int16 maxWidth, int16 *textWidth, int16 *textHeight); + /** + * Sets the font to be used for rendering and + * calculation of text dimensions. + */ + void setFont(const GuiResourceId fontId); -private: - reg_t createTextBitmapInternal(Common::String &text, reg_t textObject, uint16 maxWidth, uint16 maxHeight, reg_t hunkId); - void drawTextBitmapInternal(int16 x, int16 y, Common::Rect planeRect, reg_t textObject, reg_t hunkId); - int16 Size(Common::Rect &rect, const char *text, GuiResourceId fontId, int16 maxWidth); - void Width(const char *text, int16 from, int16 len, GuiResourceId orgFontId, int16 &textWidth, int16 &textHeight, bool restoreFont); - void StringWidth(const char *str, GuiResourceId orgFontId, int16 &textWidth, int16 &textHeight); + /** + * Retrieves the width and height of a block of text. + */ + Common::Rect getTextSize(const Common::String &text, const int16 maxWidth, bool doScaling); - SegManager *_segMan; - GfxCache *_cache; - GfxScreen *_screen; + /** + * Retrieves the width of a line of text. + */ + int16 getStringWidth(const Common::String &text); }; } // End of namespace Sci |