/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "common/stream.h" #include "graphics/macgui/macwidget.h" #include "director/director.h" #include "director/cast.h" #include "director/channel.h" #include "director/images.h" #include "director/movie.h" #include "director/picture.h" #include "director/rte.h" #include "director/score.h" #include "director/window.h" #include "director/castmember/richtext.h" #include "director/lingo/lingo-the.h" namespace Director { RichTextCastMember::RichTextCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version) : CastMember(cast, castId, stream) { _pf32 = Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0); if (version >= kFileVer500 && version < kFileVer1100) { if (debugChannelSet(5, kDebugLoading)) { debugC(5, kDebugLoading, "RichTextCastMember():"); stream.hexdump(stream.size()); } _initialRect = Movie::readRect(stream); _boundingRect = Movie::readRect(stream); _antialiasFlag = stream.readByte(); _cropFlags = stream.readByte(); _scrollPos = stream.readUint16BE(); _antialiasFontSize = stream.readUint16BE(); _displayHeight = stream.readUint16BE(); uint8 r = 0, g = 0, b = 0; stream.readByte(); // skip one byte r = stream.readByte(); g = stream.readByte(); b = stream.readByte(); _foreColor = _pf32.RGBToColor(r, g, b); r = (stream.readUint16BE() >> 8); g = (stream.readUint16BE() >> 8); b = (stream.readUint16BE() >> 8); _bgColor = _pf32.RGBToColor(r, g, b); debugC(3, kDebugLoading, " RichTextCastMember(): initialRect: [%s], boundingRect: [%s], antialiasFlag: 0x%02x, cropFlags: 0x%02x, scrollPos: %d, antialiasFontSize: %d, displayHeight: %d", _initialRect.toString().c_str(), _boundingRect.toString().c_str(), _antialiasFlag, _cropFlags, _scrollPos, _antialiasFontSize, _displayHeight); debugC(3, kDebugLoading, " RichTextCastMember(): foreColor: 0x%08x, bgColor: 0x%08x", _foreColor, _bgColor); } else { warning("STUB: RichTextCastMember: RTE not yet supported for version v%d (%d)", humanVersion(_cast->_version), _cast->_version); } _type = kCastRichText; _picture = nullptr; _pictureWithBg = nullptr; } RichTextCastMember::RichTextCastMember(Cast *cast, uint16 castId, RichTextCastMember &source) : CastMember(cast, castId) { _type = kCastRichText; _initialRect = source._initialRect; _boundingRect = source._boundingRect; _bgColor = source._bgColor; if (cast == source._cast) _children = source._children; } RichTextCastMember::~RichTextCastMember() { if (_picture) delete _picture; if (_pictureWithBg) delete _pictureWithBg; } void RichTextCastMember::load() { if (_loaded) return; // RichText casts consist of 3 files: // RTE0: Editor data, used only by the Authoring Tool // RTE1: Plain text data // RTE2: Bitmap representation for rendering // // RTE0 is using Paige editor by Hermes, which was recently // open sourced. So, if anyone wants to look into internals, // https://github.com/nmatavka/Hermes-Paige/tree/main // the pgReadDoc() is the code entry: // https://github.com/nmatavka/Hermes-Paige/blob/main/PGSOURCE/PGREAD.C#L767 uint rte0id = 0; uint rte1id = 0; uint rte2id = 0; for (auto &it : _children) { if (it.tag == MKTAG('R', 'T', 'E', '0')) { rte0id = it.index; } else if (it.tag == MKTAG('R', 'T', 'E', '1')) { rte1id = it.index; } else if (it.tag == MKTAG('R', 'T', 'E', '2')) { rte2id = it.index; } } if (_cast->_loadedRTE0s.contains(rte0id)) { // TODO: Copy the formatted text data // There doesn't appear to be a way of using it outside of the Director editor. } else { warning("RichTextCastMember::load(): rte0tid %i isn't loaded", rte0id); } if (_cast->_loadedRTE1s.contains(rte1id)) { const RTE1 *rte1 = _cast->_loadedRTE1s.getVal(rte1id); if (!rte1->data.empty()) _plainText = Common::U32String((const char *)&rte1->data[0], rte1->data.size(), g_director->getPlatformEncoding()); } else { warning("RichTextCastMember::load(): rte1tid %i isn't loaded, no plain text!", rte1id); } if (_cast->_loadedRTE2s.contains(rte2id)) { _picture = new Picture(); const RTE2 *rte2 = _cast->_loadedRTE2s.getVal(rte2id); Graphics::ManagedSurface *surface = rte2->createSurface(_foreColor, _bgColor, _pf32, false); if (surface) { _picture->_surface.copyFrom(surface->rawSurface()); surface->free(); delete surface; } _pictureWithBg = new Picture(); surface = rte2->createSurface(_foreColor, _bgColor, _pf32, true); if (surface) { _pictureWithBg->_surface.copyFrom(surface->rawSurface()); surface->free(); delete surface; } } else { warning("RichTextCastMember::load(): rte2tid %i isn't loaded, no bitmap text!", rte2id); } _loaded = true; } Graphics::MacWidget *RichTextCastMember::createWidget(Common::Rect &bbox, Channel *channel, SpriteType spriteType) { if (!_picture) { warning("RichTextCastMember::createWidget: No picture"); return nullptr; } // skip creating widget when the bbox is not available, maybe we should create it using initialRect if (!bbox.width() || !bbox.height()) return nullptr; // Check if we need to dither the image int dstBpp = g_director->_wm->_pixelformat.bytesPerPixel; Graphics::MacWidget *widget = new Graphics::MacWidget(g_director->getCurrentWindow()->getMacWindow(), bbox.left, bbox.top, bbox.width(), bbox.height(), g_director->_wm, false); Graphics::Surface *dithered = nullptr; Picture *src = _pictureWithBg; if (channel->_sprite->_ink == kInkTypeBackgndTrans) src = _picture; if (dstBpp == 1) { dithered = src->_surface.convertTo(g_director->_wm->_pixelformat, nullptr, 0, g_director->_wm->getPalette(), g_director->_wm->getPaletteSize()); } // scale for drawing a different size sprite copyStretchImg( dithered ? dithered : &src->_surface, widget->getSurface()->surfacePtr(), _initialRect, bbox, g_director->_wm->getPalette() ); if (dithered) { dithered->free(); delete dithered; } return widget; } bool RichTextCastMember::hasField(int field) { switch (field) { case kTheText: case kThePageHeight: case kTheScrollTop: return true; default: break; } return CastMember::hasField(field); } Datum RichTextCastMember::getField(int field) { Datum d; switch (field) { case kTheText: d = Datum(Common::String(_plainText)); break; case kThePageHeight: case kTheScrollTop: default: d = CastMember::getField(field); break; } return d; } void RichTextCastMember::setField(int field, const Datum &d) { switch (field) { case kTheText: _plainText = Common::U32String(d.asString()); warning("STUB: RichTextCastMember::setField: text set to \"%s\", but won't rerender!", d.asString().c_str()); return; case kThePageHeight: case kTheScrollTop: default: break; } CastMember::setField(field, d); } Common::String RichTextCastMember::formatInfo() { // need to pull the data from the STXT resource before the // debug output will be visible load(); Common::String format = formatStringForDump(_plainText.encode()); return Common::String::format( "initialRect: %dx%d@%d,%d, boundingRect: %dx%d@%d,%d, text: \"%s\"", _initialRect.width(), _initialRect.height(), _initialRect.left, _initialRect.top, _boundingRect.width(), _boundingRect.height(), _boundingRect.left, _boundingRect.top, format.c_str() ); } uint32 RichTextCastMember::getCastDataSize() { if (_cast->_version >= kFileVer500 && _cast->_version < kFileVer600) { // 8 bytes (_initialRect) // 8 bytes (_boundingRect) // Ignored 9 bytes // 3 bytes r, g, b (foreground, each a byte) // 6 bytes r, g, b (background, each 2 bytes) return 26; } else { warning("RichTextCastMember()::getCastDataSize():>D5 isn't handled"); return 0; } } void RichTextCastMember::writeCastData(Common::SeekableWriteStream *writeStream) { if (_cast->_version >= kFileVer500 && _cast->_version < kFileVer600) { Movie::writeRect(writeStream, _initialRect); Movie::writeRect(writeStream, _boundingRect); writeStream->write(0, 8); writeStream->writeByte(0); uint8 r, g, b; _pf32.colorToRGB(_foreColor, r, g, b); writeStream->writeByte(r); writeStream->writeByte(g); writeStream->writeByte(b); _pf32.colorToRGB(_bgColor, r, g, b); writeStream->writeUint16BE(r << 8); writeStream->writeUint16BE(g << 8); writeStream->writeUint16BE(b << 8); } else { warning("RichTextCastMember()::writeCastData(): >D5 isn't handled"); } } } // End of namespace Director