[Scummvm-cvs-logs] scummvm master -> 3e5adc33a84b0a4f0af6ab289782bc6ec7319c9c

csnover csnover at users.noreply.github.com
Mon Feb 29 04:51:54 CET 2016


This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
3e5adc33a8 SCI32: Non-titled text bitmap implementation


Commit: 3e5adc33a84b0a4f0af6ab289782bc6ec7319c9c
    https://github.com/scummvm/scummvm/commit/3e5adc33a84b0a4f0af6ab289782bc6ec7319c9c
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-02-28T21:48:56-06:00

Commit Message:
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.

Changed paths:
    engines/sci/engine/kernel.h
    engines/sci/engine/kernel_tables.h
    engines/sci/engine/kgraphics.cpp
    engines/sci/engine/kgraphics32.cpp
    engines/sci/engine/kstring.cpp
    engines/sci/graphics/controls32.cpp
    engines/sci/graphics/helpers.h
    engines/sci/graphics/text32.cpp
    engines/sci/graphics/text32.h



diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h
index 92916ec..3a6809a 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 fbd0b13..d3b2bfe 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 91d241f..c0a3be4 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 4d48ae4..706edc8 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 310e38d..f598cf7 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 04a70d3..df51888 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 fbad120..1d16b49 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 eabc329..e393a1c 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 08568e4..f9a302a 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






More information about the Scummvm-git-logs mailing list