diff options
Diffstat (limited to 'engines/sci/graphics/text32.cpp')
-rw-r--r-- | engines/sci/graphics/text32.cpp | 976 |
1 files changed, 552 insertions, 424 deletions
diff --git a/engines/sci/graphics/text32.cpp b/engines/sci/graphics/text32.cpp index eabc329ee0..181dabc9a8 100644 --- a/engines/sci/graphics/text32.cpp +++ b/engines/sci/graphics/text32.cpp @@ -29,6 +29,7 @@ #include "sci/engine/selector.h" #include "sci/engine/state.h" #include "sci/graphics/cache.h" +#include "sci/graphics/celobj32.h" #include "sci/graphics/compare.h" #include "sci/graphics/font.h" #include "sci/graphics/frameout.h" @@ -37,51 +38,29 @@ namespace Sci { -#define BITMAP_HEADER_SIZE 46 +int16 GfxText32::_defaultFontId = 0; +int16 GfxText32::_xResolution = 0; +int16 GfxText32::_yResolution = 0; -GfxText32::GfxText32(SegManager *segMan, GfxCache *fonts, GfxScreen *screen) : +GfxText32::GfxText32(SegManager *segMan, GfxCache *fonts) : _segMan(segMan), _cache(fonts), - _screen(screen), - _scaledWidth(g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth), - _scaledHeight(g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight), - _bitmap(NULL_REG) {} - -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); - WRITE_SCI11ENDIAN_UINT16(bitmap + 2, height); - WRITE_SCI11ENDIAN_UINT16(bitmap + 4, (uint16)displaceX); - WRITE_SCI11ENDIAN_UINT16(bitmap + 6, (uint16)displaceY); - bitmap[8] = skipColor; - bitmap[9] = 0; - WRITE_SCI11ENDIAN_UINT16(bitmap + 10, 0); - - if (useRemap) { - bitmap[10] |= 2; + // Not a typo, the original engine did not initialise height, only width + _width(0), + _text(""), + _bitmap(NULL_REG) { + _fontId = _defaultFontId; + _font = _cache->getFont(_defaultFontId); + + if (_xResolution == 0) { + // initialize the statics + _xResolution = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + _yResolution = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; + } } - WRITE_SCI11ENDIAN_UINT32(bitmap + 12, width * height); - WRITE_SCI11ENDIAN_UINT32(bitmap + 16, 0); - - if (hunkPaletteOffset) { - WRITE_SCI11ENDIAN_UINT32(bitmap + 20, hunkPaletteOffset + BITMAP_HEADER_SIZE); - } else { - WRITE_SCI11ENDIAN_UINT32(bitmap + 20, 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, const bool gc) { - WRITE_SCI11ENDIAN_UINT32(bitmap + 24, BITMAP_HEADER_SIZE); - WRITE_SCI11ENDIAN_UINT32(bitmap + 28, BITMAP_HEADER_SIZE); - WRITE_SCI11ENDIAN_UINT32(bitmap + 32, 0); - WRITE_SCI11ENDIAN_UINT16(bitmap + 36, scaledWidth); - 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; _borderColor = borderColor; _text = text; _textRect = rect; @@ -93,21 +72,18 @@ 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; int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; - Ratio scaleX(_scaledWidth, scriptWidth); - Ratio scaleY(_scaledHeight, scriptHeight); + Ratio scaleX(_xResolution, scriptWidth); + Ratio scaleY(_yResolution, scriptHeight); _width = (_width * scaleX).toInt(); _height = (_height * scaleY).toInt(); - mul(_textRect, scaleX, scaleY); + mulinc(_textRect, scaleX, scaleY); } // _textRect represents where text is drawn inside the @@ -120,10 +96,7 @@ reg_t GfxText32::createFontBitmap(int16 width, int16 height, const Common::Rect _textRect = Common::Rect(); } - _bitmap = _segMan->allocateHunkEntry("FontBitmap()", _width * _height + BITMAP_HEADER_SIZE); - - byte *bitmap = _segMan->getHunkPointer(_bitmap); - buildBitmapHeader(bitmap, _width, _height, _skipColor, 0, 0, _scaledWidth, _scaledHeight, 0, false); + _segMan->allocateBitmap(&_bitmap, _width, _height, _skipColor, 0, 0, _xResolution, _yResolution, 0, false, gc); erase(bitmapRect, false); @@ -132,461 +105,616 @@ reg_t GfxText32::createFontBitmap(int16 width, int16 height, const Common::Rect } drawTextBox(); + return _bitmap; +} + +reg_t GfxText32::createFontBitmap(const CelInfo32 &celInfo, const Common::Rect &rect, const Common::String &text, const int16 foreColor, const int16 backColor, const GuiResourceId fontId, const int16 skipColor, const int16 borderColor, const bool dimmed, const bool gc) { + _borderColor = borderColor; + _text = text; + _textRect = rect; + _foreColor = foreColor; + _dimmed = dimmed; + + setFont(fontId); + + int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; - debug("Drawing a bitmap %dx%d, scaled %dx%d, border %d, font %d", width, height, _width, _height, _borderColor, _fontId); + mulinc(_textRect, Ratio(_xResolution, scriptWidth), Ratio(_yResolution, scriptHeight)); + + CelObjView view(celInfo.resourceId, celInfo.loopNo, celInfo.celNo); + _skipColor = view._skipColor; + _width = view._width * _xResolution / view._xResolution; + _height = view._height * _yResolution / view._yResolution; + + Common::Rect bitmapRect(_width, _height); + if (_textRect.intersects(bitmapRect)) { + _textRect.clip(bitmapRect); + } else { + _textRect = Common::Rect(); + } + + SciBitmap &bitmap = *_segMan->allocateBitmap(&_bitmap, _width, _height, _skipColor, 0, 0, _xResolution, _yResolution, 0, false, gc); + + // NOTE: The engine filled the bitmap pixels with 11 here, which is silly + // because then it just erased the bitmap using the skip color. So we don't + // fill the bitmap redundantly here. + + _backColor = _skipColor; + erase(bitmapRect, false); + _backColor = backColor; + + view.draw(bitmap.getBuffer(), bitmapRect, Common::Point(0, 0), false, Ratio(_xResolution, view._xResolution), Ratio(_yResolution, view._yResolution)); + + if (_backColor != skipColor && _foreColor != skipColor) { + erase(_textRect, false); + } + + if (text.size() > 0) { + if (_foreColor == skipColor) { + error("TODO: Implement transparent text"); + } else { + if (borderColor != -1) { + drawFrame(bitmapRect, 1, _borderColor, false); + } + + drawTextBox(); + } + } - *outBitmapObject = _bitmap; return _bitmap; } -reg_t GfxText32::createTitledFontBitmap(CelInfo32 &celInfo, Common::Rect &rect, Common::String &text, int16 foreColor, int16 backColor, int font, int16 skipColor, int16 borderColor, bool dimmed, void *unknown1) { - warning("TODO: createTitledFontBitmap"); - return NULL_REG; +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 int size, const uint8 color, const bool doScaling) { +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); - byte *pixels = bitmap + READ_SCI11ENDIAN_UINT32(bitmap + 28); + SciBitmap &bitmap = *_segMan->lookupBitmap(_bitmap); + byte *pixels = bitmap.getPixels() + rect.top * _width + rect.left; // NOTE: Not fully disassembled, but this should be right - // TODO: Implement variable frame size - assert(size == 1); - Buffer buffer(_width, _height, pixels); - buffer.frameRect(targetRect, color); + int16 rectWidth = targetRect.width(); + int16 heightRemaining = targetRect.height(); + int16 sidesHeight = heightRemaining - size * 2; + int16 centerWidth = rectWidth - size * 2; + int16 stride = _width - rectWidth; + + for (int16 y = 0; y < size && y < heightRemaining; ++y) { + memset(pixels, color, rectWidth); + pixels += _width; + --heightRemaining; + } + for (int16 y = 0; y < sidesHeight; ++y) { + for (int16 x = 0; x < size; ++x) { + *pixels++ = color; + } + pixels += centerWidth; + for (int16 x = 0; x < size; ++x) { + *pixels++ = color; + } + pixels += stride; + } + for (int16 y = 0; y < size && y < heightRemaining; ++y) { + memset(pixels, color, rectWidth); + pixels += _width; + --heightRemaining; + } +} + +void GfxText32::drawChar(const char charIndex) { + SciBitmap &bitmap = *_segMan->lookupBitmap(_bitmap); + byte *pixels = bitmap.getPixels(); + + _font->drawToBuffer((unsigned char)charIndex, _drawPosition.y, _drawPosition.x, _foreColor, _dimmed, pixels, _width, _height); + _drawPosition.x += _font->getCharWidth((unsigned char)charIndex); +} + +uint16 GfxText32::getCharWidth(const char charIndex, const bool doScaling) const { + uint16 width = _font->getCharWidth((unsigned char)charIndex); + if (doScaling) { + width = scaleUpWidth(width); + } + return width; } -// 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 - } + if (_text.size() == 0) { + return; } - 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); - } + const char *text = _text.c_str(); + const char *sourceText = text; + int16 textRectWidth = _textRect.width(); + _drawPosition.y = _textRect.top; + uint charIndex = 0; - 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; - } + if (g_sci->getGameId() == GID_SQ6 || g_sci->getGameId() == GID_MOTHERGOOSEHIRES) { + if (getLongest(&charIndex, textRectWidth) == 0) { + error("DrawTextBox GetLongest=0"); } + } + + charIndex = 0; + uint nextCharIndex = 0; + while (*text != '\0') { + _drawPosition.x = _textRect.left; + + uint length = getLongest(&nextCharIndex, textRectWidth); + int16 textWidth = getTextWidth(charIndex, length); - curX = 0; - curY += _font->getHeight(); - txt += charCount; - while (*txt == ' ') { - txt++; // skip over breaking spaces + if (_alignment == kTextAlignCenter) { + _drawPosition.x += (textRectWidth - textWidth) / 2; + } else if (_alignment == kTextAlignRight) { + _drawPosition.x += textRectWidth - textWidth; } + + drawText(charIndex, length); + charIndex = nextCharIndex; + text = sourceText + charIndex; + _drawPosition.y += _font->getHeight(); } } -void GfxText32::erase(const Common::Rect &rect, const bool doScaling) { - Common::Rect targetRect = doScaling ? rect : scaleRect(rect); +void GfxText32::drawTextBox(const Common::String &text) { + _text = text; + drawTextBox(); +} - byte *bitmap = _segMan->getHunkPointer(_bitmap); - byte *pixels = bitmap + READ_SCI11ENDIAN_UINT32(bitmap + 28); +void GfxText32::drawText(const uint index, uint length) { + assert(index + length <= _text.size()); - // 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); -} + // 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++; -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)); + if (currentChar == '|') { + const char controlChar = *text++; + --length; - Common::String text = _segMan->getString(stringObject); + if (length == 0) { + return; + } + + 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); + } + } - return createTextBitmapInternal(text, textObject, maxWidth, maxHeight, prevHunk); + while (length > 0 && *text != '|') { + ++text; + --length; + } + if (length > 0) { + ++text; + --length; + } + } else { + drawChar(currentChar); + } + } } -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::invertRect(const reg_t bitmapId, int16 bitmapStride, const Common::Rect &rect, const uint8 foreColor, const uint8 backColor, const bool doScaling) { + Common::Rect targetRect = rect; + if (doScaling) { + bitmapStride = bitmapStride * _xResolution / g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + targetRect = scaleRect(rect); } - int entrySize = width * height + BITMAP_HEADER_SIZE; - reg_t memoryId = NULL_REG; - if (prevHunk.isNull()) { - memoryId = _segMan->allocateHunkEntry("TextBitmap()", entrySize); + SciBitmap &bitmap = *_segMan->lookupBitmap(bitmapId); - // 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 + // NOTE: SCI code is super weird here; it seems to be trying to look at the + // entire size of the bitmap including the header, instead of just the pixel + // data size. We just look at the pixel size. This function generally is an + // odd duck since the stride dimension for a bitmap is built in to the bitmap + // header, so perhaps it was once an unheadered bitmap format and this + // function was never updated to match? Or maybe they exploit the + // configurable stride length somewhere else to do stair stepping inverts... + uint32 invertSize = targetRect.height() * bitmapStride + targetRect.width(); + uint32 bitmapSize = bitmap.getDataSize(); + + if (invertSize >= bitmapSize) { + error("InvertRect too big: %u >= %u", invertSize, bitmapSize); } - 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); - } + // NOTE: Actual engine just added the bitmap header size hardcoded here + byte *pixel = bitmap.getPixels() + bitmapStride * targetRect.top + targetRect.left; + + int16 stride = bitmapStride - targetRect.width(); + int16 targetHeight = targetRect.height(); + int16 targetWidth = targetRect.width(); - 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; + for (int16 y = 0; y < targetHeight; ++y) { + for (int16 x = 0; x < targetWidth; ++x) { + if (*pixel == foreColor) { + *pixel = backColor; + } else if (*pixel == backColor) { + *pixel = foreColor; } + + ++pixel; } - curX = 0; - curY += font->getHeight(); - txt += charCount; - while (*txt == ' ') - txt++; // skip over breaking spaces + pixel += stride; } - - 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; } } + + // 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; + } + + // The last word in the line made it wider than the text area, so return + // up to the penultimate word + *charIndex = lastWordBreakIndex; + return length; } -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; +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; + while (length > 0 && *text >= '0' && *text <= '9') { + currentChar = *text++; + --length; + + fontId = fontId * 10 + currentChar - '0'; + } + + if (length > 0) { + font = _cache->getFont(fontId); + } + } + + // Forward through any more unknown control character data + while (length > 0 && *text != '|') { + ++text; + --length; + } + if (length > 0) { + ++text; + --length; + } + } else { + width += font->getCharWidth((unsigned char)currentChar); + } + + if (length > 0) { + currentChar = *text++; + --length; } - if (width + font->getCharWidth(curChar) > maxWidth) - break; - width += font->getCharWidth(curChar); - curCharCount++; } - return maxChars; + return width; } -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(); +int16 GfxText32::getTextWidth(const Common::String &text, const uint index, const uint length) { + _text = text; + return scaleUpWidth(getTextWidth(index, length)); } -void GfxText32::StringWidth(const char *str, GuiResourceId fontId, int16 &textWidth, int16 &textHeight) { - Width(str, 0, (int16)strlen(str), fontId, textWidth, textHeight, true); -} +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. Like other functions in SCI32, this has been converted + // to use exclusive rects with inclusive rounding. + + Common::Rect result; + + int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; + + maxWidth = maxWidth * _xResolution / scriptWidth; + + _text = text; + + if (maxWidth >= 0) { + if (maxWidth == 0) { + maxWidth = _xResolution * 3 / 5; + } -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; + 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); + + if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) { + result.bottom = 0; + } else { + // NOTE: In the original engine code, the bottom was not decremented + // by 1, which means that the rect was actually a pixel taller than + // the height of the font. This was not the case in the other branch, + // which decremented the bottom by 1 at the end of the loop. + result.bottom = _font->getHeight() + 1; + } } + + if (doScaling) { + // NOTE: The original engine code also scaled top/left but these are + // always zero so there is no reason to do that. + result.right = ((result.right - 1) * scriptWidth + _xResolution - 1) / _xResolution + 1; + result.bottom = ((result.bottom - 1) * scriptHeight + _yResolution - 1) / _yResolution + 1; + } + + return result; } -int16 GfxText32::Size(Common::Rect &rect, const char *text, GuiResourceId fontId, int16 maxWidth) { - int16 charCount; - int16 maxTextWidth = 0, textWidth; - int16 totalHeight = 0, textHeight; +void GfxText32::erase(const Common::Rect &rect, const bool doScaling) { + Common::Rect targetRect = doScaling ? scaleRect(rect) : rect; - int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; - int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; + SciBitmap &bitmap = *_segMan->lookupBitmap(_bitmap); + bitmap.getBuffer().fillRect(targetRect, _backColor); +} + +int16 GfxText32::getStringWidth(const Common::String &text) { + return getTextWidth(text, 0, 10000); +} + +int16 GfxText32::getTextCount(const Common::String &text, const uint index, const Common::Rect &textRect, const bool doScaling) { + const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; - maxWidth = maxWidth * _scaledWidth / scriptWidth; + Common::Rect scaledRect(textRect); + if (doScaling) { + mulinc(scaledRect, Ratio(_xResolution, scriptWidth), Ratio(_yResolution, scriptHeight)); + } - rect.top = rect.left = 0; - GfxFont *font = _cache->getFont(fontId); + Common::String oldText = _text; + _text = text; + + uint charIndex = index; + int16 maxWidth = scaledRect.width(); + int16 lineCount = (scaledRect.height() - 2) / _font->getHeight(); + while (lineCount--) { + getLongest(&charIndex, maxWidth); + } - if (maxWidth < 0) { // force output as single line - StringWidth(text, fontId, textWidth, textHeight); - rect.bottom = textHeight; - rect.right = textWidth; + _text = oldText; + return charIndex - index; +} + +int16 GfxText32::getTextCount(const Common::String &text, const uint index, const GuiResourceId fontId, const Common::Rect &textRect, const bool doScaling) { + setFont(fontId); + return getTextCount(text, index, textRect, doScaling); +} + +void GfxText32::scrollLine(const Common::String &lineText, int numLines, uint8 color, TextAlign align, GuiResourceId fontId, ScrollDirection dir) { + SciBitmap &bmr = *_segMan->lookupBitmap(_bitmap); + byte *pixels = bmr.getPixels(); + + int h = _font->getHeight(); + + if (dir == kScrollUp) { + // Scroll existing text down + for (int i = 0; i < (numLines - 1) * h; ++i) { + int y = _textRect.top + numLines * h - i - 1; + memcpy(pixels + y * _width + _textRect.left, + pixels + (y - h) * _width + _textRect.left, + _textRect.width()); + } } 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 + // Scroll existing text up + for (int i = 0; i < (numLines - 1) * h; ++i) { + int y = _textRect.top + i; + memcpy(pixels + y * _width + _textRect.left, + pixels + (y + h) * _width + _textRect.left, + _textRect.width()); } - rect.bottom = totalHeight; - rect.right = maxWidth ? maxWidth : MIN(rect.right, maxTextWidth); } - rect.right = rect.right * scriptWidth / _scaledWidth; - rect.bottom = rect.bottom * scriptHeight / _scaledHeight; + Common::Rect lineRect = _textRect; + + if (dir == kScrollUp) { + lineRect.bottom = lineRect.top + h; + } else { + // It is unclear to me what the purpose of this bottom++ is. + // It does not seem to be the usual inc/exc issue. + lineRect.top += (numLines - 1) * h; + lineRect.bottom++; + } + + erase(lineRect, false); + + _drawPosition.x = _textRect.left; + _drawPosition.y = _textRect.top; + if (dir == kScrollDown) { + _drawPosition.y += (numLines - 1) * h; + } + + _foreColor = color; + _alignment = align; + //int fc = _foreColor; + + setFont(fontId); + + _text = lineText; + int16 textWidth = getTextWidth(0, lineText.size()); + + if (_alignment == kTextAlignCenter) { + _drawPosition.x += (_textRect.width() - textWidth) / 2; + } else if (_alignment == kTextAlignRight) { + _drawPosition.x += _textRect.width() - textWidth; + } + + //_foreColor = fc; + //setFont(fontId); - return rect.right; + drawText(0, lineText.size()); } + } // End of namespace Sci |