ResidualVM logo ResidualVM website - Forums - Contact us BuildBot - Doxygen - Wiki curved edge

subtitles.cpp

Go to the documentation of this file.
00001 /* ResidualVM - A 3D game interpreter
00002  *
00003  * ResidualVM is the legal property of its developers, whose names
00004  * are too numerous to list here. Please refer to the AUTHORS
00005  * file distributed with this source distribution.
00006  *
00007  * This program is free software; you can redistribute it and/or
00008  * modify it under the terms of the GNU General Public License
00009  * as published by the Free Software Foundation; either version 2
00010  * of the License, or (at your option) any later version.
00011  *
00012  * This program is distributed in the hope that it will be useful,
00013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  * GNU General Public License for more details.
00016  *
00017  * You should have received a copy of the GNU General Public License
00018  * along with this program; if not, write to the Free Software
00019  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00020  *
00021  */
00022 
00023 #include "engines/myst3/subtitles.h"
00024 #include "engines/myst3/myst3.h"
00025 #include "engines/myst3/scene.h"
00026 #include "engines/myst3/state.h"
00027 
00028 #include "common/archive.h"
00029 #include "common/iconv.h"
00030 
00031 #include "graphics/fontman.h"
00032 #include "graphics/font.h"
00033 #include "graphics/fonts/ttf.h"
00034 
00035 #include "video/bink_decoder.h"
00036 
00037 namespace Myst3 {
00038 
00039 class FontSubtitles : public Subtitles {
00040 public:
00041     FontSubtitles(Myst3Engine *vm);
00042     virtual ~FontSubtitles();
00043 
00044 protected:
00045     void loadResources() override;
00046     bool loadSubtitles(int32 id) override;
00047     void drawToTexture(const Phrase *phrase) override;
00048 
00049 private:
00050     void loadCharset(int32 id);
00051     void createTexture();
00052     void readPhrases(const DirectorySubEntry *desc);
00053     static Common::String fakeBidiProcessing(const Common::String &phrase);
00054 
00055     const Graphics::Font *_font;
00056     Graphics::Surface *_surface;
00057     float _scale;
00058     uint8 *_charset;
00059 };
00060 
00061 FontSubtitles::FontSubtitles(Myst3Engine *vm) :
00062     Subtitles(vm),
00063     _font(0),
00064     _surface(0),
00065     _scale(1.0),
00066     _charset(nullptr) {
00067 }
00068 
00069 FontSubtitles::~FontSubtitles() {
00070     if (_surface) {
00071         _surface->free();
00072         delete _surface;
00073     }
00074 
00075     delete _font;
00076     delete[] _charset;
00077 }
00078 
00079 void FontSubtitles::loadResources() {
00080     // We draw the subtitles in the adequate resolution so that they are not
00081     // scaled up. This is the scale factor of the current resolution
00082     // compared to the original
00083     _scale = getPosition().width() / (float) getOriginalPosition().width();
00084 
00085 #ifdef USE_FREETYPE2
00086     Common::String ttfFile;
00087     if (_fontFace == "Arial Narrow") {
00088         // Use the TTF font provided by the game if TTF support is available
00089         ttfFile = "arir67w.ttf";
00090     } else if (_fontFace == "MS Gothic") {
00091         // The Japanese font has to be supplied by the user
00092         ttfFile = "msgothic.ttf";
00093     } else if (_fontFace == "Arial2") {
00094         // The Hebrew font has to be supplied by the user
00095         ttfFile = "hebrew.ttf";
00096     } else {
00097         error("Unknown subtitles font face '%s'", _fontFace.c_str());
00098     }
00099 
00100     Common::SeekableReadStream *s = SearchMan.createReadStreamForMember(ttfFile);
00101     if (s) {
00102         _font = Graphics::loadTTFFont(*s, _fontSize * _scale);
00103         delete s;
00104     } else {
00105         warning("Unable to load the subtitles font '%s'", ttfFile.c_str());
00106     }
00107 #endif
00108 }
00109 
00110 void FontSubtitles::loadCharset(int32 id) {
00111     const DirectorySubEntry *fontCharset = _vm->getFileDescription("CHAR", id, 0, DirectorySubEntry::kRawData);
00112 
00113     // Load the font charset if any
00114     if (fontCharset) {
00115         Common::MemoryReadStream *data = fontCharset->getData();
00116 
00117         _charset = new uint8[data->size()];
00118 
00119         data->read(_charset, data->size());
00120 
00121         delete data;
00122     }
00123 }
00124 
00125 bool FontSubtitles::loadSubtitles(int32 id) {
00126     // No game-provided charset for the Japanese version
00127     if (_fontCharsetCode == 0) {
00128         loadCharset(1100);
00129     }
00130 
00131     int32 overridenId = checkOverridenId(id);
00132 
00133     const DirectorySubEntry *desc = loadText(overridenId, overridenId != id);
00134 
00135     if (!desc)
00136         return false;
00137 
00138     readPhrases(desc);
00139 
00140     if (_vm->getGameLanguage() == Common::HE_ISR) {
00141         for (uint i = 0; i < _phrases.size(); i++) {
00142             _phrases[i].string = fakeBidiProcessing(_phrases[i].string);
00143         }
00144     }
00145 
00146     return true;
00147 }
00148 
00149 void FontSubtitles::readPhrases(const DirectorySubEntry *desc) {
00150     Common::MemoryReadStream *crypted = desc->getData();
00151 
00152     // Read the frames and associated text offsets
00153     while (true) {
00154         Phrase s;
00155         s.frame = crypted->readUint32LE();
00156         s.offset = crypted->readUint32LE();
00157 
00158         if (!s.frame)
00159             break;
00160 
00161         _phrases.push_back(s);
00162     }
00163 
00164     // Read and decrypt the frames subtitles
00165     for (uint i = 0; i < _phrases.size(); i++) {
00166         crypted->seek(_phrases[i].offset);
00167 
00168         uint8 key = 35;
00169         while (true) {
00170             uint8 c = crypted->readByte() ^ key++;
00171 
00172             if (c >= 32 && _charset)
00173                 c = _charset[c - 32];
00174 
00175             if (!c)
00176                 break;
00177 
00178             _phrases[i].string += c;
00179         }
00180     }
00181 
00182     delete crypted;
00183 }
00184 
00185 static bool isPunctuation(char c) {
00186     return c == '.' || c == ',' || c == '\"'  || c == '!' || c == '?';
00187 }
00188 
00189 Common::String FontSubtitles::fakeBidiProcessing(const Common::String &phrase) {
00190     // The Hebrew subtitles are stored in logical order:
00191     // .ABC DEF GHI
00192     // This line should be rendered in visual order as:
00193     // .IHG FED CBA
00194 
00195     // Notice how the dot is on the left both in logical and visual order. This is
00196     // because it is in left to right order while the Hebrew characters are in right to
00197     // left order. Text rendering code needs to apply what is called the BiDirectional
00198     // algorithm to know which parts of an input string are LTR or RTL and how to render
00199     // them. This is a quite complicated algorithm. Fortunately the subtitles in Myst III
00200     // only require very specific BiDi processing. The punctuation signs at the beginning of
00201     // each line need to be moved to the end so that they are visually to the left once
00202     // the string is rendered from right to left.
00203     // This method works around the need to implement proper BiDi processing
00204     // by exploiting that fact.
00205 
00206     uint punctuationCounter = 0;
00207     while (punctuationCounter < phrase.size() && isPunctuation(phrase[punctuationCounter])) {
00208         punctuationCounter++;
00209     }
00210 
00211     Common::String output = Common::String(phrase.c_str() + punctuationCounter);
00212     for (uint i = 0; i < punctuationCounter; i++) {
00213         output += phrase[i];
00214     }
00215 
00216     // Also reverse the string so that it is in visual order.
00217     // This is necessary because our text rendering code does not actually support RTL.
00218     for (int i = 0, j = output.size() - 1; i < j; i++, j--) {
00219         char c = output[i];
00220         output.setChar(output[j], i);
00221         output.setChar(c, j);
00222     }
00223 
00224     return output;
00225 }
00226 
00227 void FontSubtitles::createTexture() {
00228     // Create a surface to draw the subtitles on
00229     // Use RGB 565 to allow use of BDF fonts
00230     if (!_surface) {
00231         uint16 width = Renderer::kOriginalWidth * _scale;
00232         uint16 height = _surfaceHeight * _scale;
00233 
00234         // Make sure the width is even. Some graphics drivers have trouble reading from
00235         // surfaces with an odd width (Mesa 18 on Intel).
00236         width &= ~1;
00237 
00238         _surface = new Graphics::Surface();
00239         _surface->create(width, height, Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0));
00240     }
00241 
00242     if (!_texture) {
00243         _texture = _vm->_gfx->createTexture(_surface);
00244     }
00245 }
00246 
00247 #ifdef USE_ICONV
00248 
00249 static Common::Encoding getEncodingFromCharsetCode(uint32 gdiCharset) {
00250     static const struct {
00251         uint32 charset;
00252         Common::Encoding encoding;
00253     } codepages[] = {
00254             { 128, Common::kEncodingCP932            }, // SHIFTJIS_CHARSET
00255             { 177, Common::kEncodingCP1255           }, // HEBREW_CHARSET
00256             { 204, Common::kEncodingCP1251           }, // RUSSIAN_CHARSET
00257             { 238, Common::kEncodingMacCentralEurope }  // EASTEUROPE_CHARSET
00258     };
00259 
00260     for (uint i = 0; i < ARRAYSIZE(codepages); i++) {
00261         if (gdiCharset == codepages[i].charset) {
00262             return codepages[i].encoding;
00263         }
00264     }
00265 
00266     error("Unknown font charset code '%d'", gdiCharset);
00267 }
00268 #endif
00269 
00270 void FontSubtitles::drawToTexture(const Phrase *phrase) {
00271     const Graphics::Font *font;
00272     if (_font)
00273         font = _font;
00274     else
00275         font = FontMan.getFontByUsage(Graphics::FontManager::kLocalizedFont);
00276 
00277     if (!font)
00278         error("No available font");
00279 
00280     if (!_texture || !_surface) {
00281         createTexture();
00282     }
00283 
00284     // Draw the new text
00285     memset(_surface->getPixels(), 0, _surface->pitch * _surface->h);
00286 
00287 
00288     if (_fontCharsetCode == 0) {
00289         font->drawString(_surface, phrase->string, 0, _singleLineTop * _scale, _surface->w, 0xFFFFFFFF, Graphics::kTextAlignCenter);
00290     } else {
00291 #ifdef USE_ICONV
00292         Common::Encoding encoding = getEncodingFromCharsetCode(_fontCharsetCode);
00293         Common::U32String unicode = Common::convertToU32String(encoding, phrase->string);
00294         font->drawString(_surface, unicode, 0, _singleLineTop * _scale, _surface->w, 0xFFFFFFFF, Graphics::kTextAlignCenter);
00295 #else
00296         warning("Unable to display charset '%d' subtitles, iconv support is not compiled in.", _fontCharsetCode);
00297 #endif
00298     }
00299 
00300     // Update the texture
00301     _texture->update(_surface);
00302 }
00303 
00304 class MovieSubtitles : public Subtitles {
00305 public:
00306     MovieSubtitles(Myst3Engine *vm);
00307     virtual ~MovieSubtitles();
00308 
00309 protected:
00310     void loadResources() override;
00311     bool loadSubtitles(int32 id) override;
00312     void drawToTexture(const Phrase *phrase) override;
00313 
00314 private:
00315     const DirectorySubEntry *loadMovie(int32 id, bool overriden);
00316     void readPhrases(const DirectorySubEntry *desc);
00317 
00318     Video::BinkDecoder _bink;
00319 };
00320 
00321 MovieSubtitles::MovieSubtitles(Myst3Engine *vm) :
00322         Subtitles(vm) {
00323 }
00324 
00325 MovieSubtitles::~MovieSubtitles() {
00326 }
00327 
00328 void MovieSubtitles::readPhrases(const DirectorySubEntry *desc) {
00329     Common::MemoryReadStream *frames = desc->getData();
00330 
00331     // Read the frames
00332     uint index = 0;
00333     while (true) {
00334         Phrase s;
00335         s.frame = frames->readUint32LE();
00336         s.offset = index;
00337 
00338         if (!s.frame)
00339             break;
00340 
00341         _phrases.push_back(s);
00342         index++;
00343     }
00344 
00345     delete frames;
00346 }
00347 
00348 const DirectorySubEntry *MovieSubtitles::loadMovie(int32 id, bool overriden) {
00349     const DirectorySubEntry *desc;
00350     if (overriden) {
00351         desc = _vm->getFileDescription("IMGR", 200000 + id, 0, DirectorySubEntry::kMovie);
00352     } else {
00353         desc = _vm->getFileDescription("", 200000 + id, 0, DirectorySubEntry::kMovie);
00354     }
00355     return desc;
00356 }
00357 
00358 bool MovieSubtitles::loadSubtitles(int32 id) {
00359     int32 overridenId = checkOverridenId(id);
00360 
00361     const DirectorySubEntry *phrases = loadText(overridenId, overridenId != id);
00362     const DirectorySubEntry *movie = loadMovie(overridenId, overridenId != id);
00363 
00364     if (!phrases || !movie)
00365         return false;
00366 
00367     readPhrases(phrases);
00368 
00369     // Load the movie
00370     Common::MemoryReadStream *movieStream = movie->getData();
00371     _bink.setDefaultHighColorFormat(Texture::getRGBAPixelFormat());
00372     _bink.loadStream(movieStream);
00373     _bink.start();
00374 
00375     return true;
00376 }
00377 
00378 void MovieSubtitles::loadResources() {
00379 }
00380 
00381 void MovieSubtitles::drawToTexture(const Phrase *phrase) {
00382     _bink.seekToFrame(phrase->offset);
00383     const Graphics::Surface *surface = _bink.decodeNextFrame();
00384 
00385     if (!_texture) {
00386         _texture = _vm->_gfx->createTexture(surface);
00387     } else {
00388         _texture->update(surface);
00389     }
00390 }
00391 
00392 Subtitles::Subtitles(Myst3Engine *vm) :
00393         Window(),
00394         _vm(vm),
00395         _texture(0),
00396         _frame(-1) {
00397     _scaled = !_vm->isWideScreenModEnabled();
00398 }
00399 
00400 Subtitles::~Subtitles() {
00401     freeTexture();
00402 }
00403 
00404 void Subtitles::loadFontSettings(int32 id) {
00405     // Load font settings
00406     const DirectorySubEntry *fontNums = _vm->getFileDescription("NUMB", id, 0, DirectorySubEntry::kNumMetadata);
00407 
00408     if (!fontNums)
00409         error("Unable to load font settings values");
00410 
00411     _fontSize = fontNums->getMiscData(0);
00412     _fontBold = fontNums->getMiscData(1);
00413     _surfaceHeight = fontNums->getMiscData(2);
00414     _singleLineTop = fontNums->getMiscData(3);
00415     _line1Top = fontNums->getMiscData(4);
00416     _line2Top = fontNums->getMiscData(5);
00417     _surfaceTop = fontNums->getMiscData(6);
00418     _fontCharsetCode = fontNums->getMiscData(7);
00419 
00420     if (_fontCharsetCode > 0) {
00421         _fontCharsetCode = 128; // The Japanese subtitles are encoded in CP 932 / Shift JIS
00422     }
00423 
00424     if (_vm->getGameLanguage() == Common::HE_ISR) {
00425         // The Hebrew subtitles are encoded in CP 1255, but the game data does not specify the appropriate encoding
00426         _fontCharsetCode = 177;
00427     }
00428 
00429     if (_fontCharsetCode < 0) {
00430         _fontCharsetCode = -_fontCharsetCode; // Negative values are GDI charset codes
00431     }
00432 
00433     const DirectorySubEntry *fontText = _vm->getFileDescription("TEXT", id, 0, DirectorySubEntry::kTextMetadata);
00434 
00435     if (!fontText)
00436         error("Unable to load font face");
00437 
00438     _fontFace = fontText->getTextData(0);
00439 }
00440 
00441 int32 Subtitles::checkOverridenId(int32 id) {
00442     // Subtitles may be overridden using a variable
00443     if (_vm->_state->getMovieOverrideSubtitles()) {
00444         id = _vm->_state->getMovieOverrideSubtitles();
00445         _vm->_state->setMovieOverrideSubtitles(0);
00446     }
00447     return id;
00448 }
00449 
00450 const DirectorySubEntry *Subtitles::loadText(int32 id, bool overriden) {
00451     const DirectorySubEntry *desc;
00452     if (overriden) {
00453         desc = _vm->getFileDescription("IMGR", 100000 + id, 0, DirectorySubEntry::kText);
00454     } else {
00455         desc = _vm->getFileDescription("", 100000 + id, 0, DirectorySubEntry::kText);
00456     }
00457     return desc;
00458 }
00459 
00460 void Subtitles::setFrame(int32 frame) {
00461     const Phrase *phrase = nullptr;
00462 
00463     for (uint i = 0; i < _phrases.size(); i++) {
00464         if (_phrases[i].frame > frame)
00465             break;
00466 
00467         phrase = &_phrases[i];
00468     }
00469 
00470     if (!phrase) {
00471         freeTexture();
00472         return;
00473     }
00474 
00475     if (phrase->frame == _frame) {
00476         return;
00477     }
00478 
00479     _frame = phrase->frame;
00480 
00481     drawToTexture(phrase);
00482 }
00483 
00484 void Subtitles::drawOverlay() {
00485     if (!_texture) return;
00486 
00487     Common::Rect screen = _vm->_gfx->viewport();
00488     Common::Rect bottomBorder = Common::Rect(Renderer::kOriginalWidth, _surfaceHeight);
00489     bottomBorder.translate(0, _surfaceTop);
00490 
00491     if (_vm->isWideScreenModEnabled()) {
00492         // Draw a black background to cover the main game frame
00493         _vm->_gfx->drawRect2D(Common::Rect(screen.width(), Renderer::kBottomBorderHeight), 0xFF000000);
00494 
00495         // Center the subtitles in the screen
00496         bottomBorder.translate((screen.width() - Renderer::kOriginalWidth) / 2, 0);
00497     }
00498 
00499     Common::Rect textureRect = Common::Rect(_texture->width, _texture->height);
00500 
00501     _vm->_gfx->drawTexturedRect2D(bottomBorder, textureRect, _texture);
00502 }
00503 
00504 Subtitles *Subtitles::create(Myst3Engine *vm, uint32 id) {
00505     Subtitles *s;
00506 
00507     if (vm->getPlatform() == Common::kPlatformXbox) {
00508         s = new MovieSubtitles(vm);
00509     } else {
00510         s = new FontSubtitles(vm);
00511     }
00512 
00513     s->loadFontSettings(1100);
00514 
00515     if (!s->loadSubtitles(id)) {
00516         delete s;
00517         return 0;
00518     }
00519 
00520     s->loadResources();
00521 
00522     return s;
00523 }
00524 
00525 void Subtitles::freeTexture() {
00526     if (_texture) {
00527         _vm->_gfx->freeTexture(_texture);
00528         _texture = nullptr;
00529     }
00530 }
00531 
00532 Common::Rect Subtitles::getPosition() const {
00533     Common::Rect screen = _vm->_gfx->viewport();
00534 
00535     Common::Rect frame;
00536 
00537     if (_vm->isWideScreenModEnabled()) {
00538         frame = Common::Rect(screen.width(), Renderer::kBottomBorderHeight);
00539 
00540         Common::Rect scenePosition = _vm->_scene->getPosition();
00541         int16 top = CLIP<int16>(screen.height() - frame.height(), 0, scenePosition.bottom);
00542 
00543         frame.translate(0, top);
00544     } else {
00545         frame = Common::Rect(screen.width(), screen.height() * Renderer::kBottomBorderHeight / Renderer::kOriginalHeight);
00546         frame.translate(screen.left, screen.top + screen.height() * (Renderer::kTopBorderHeight + Renderer::kFrameHeight) / Renderer::kOriginalHeight);
00547     }
00548 
00549     return frame;
00550 }
00551 
00552 Common::Rect Subtitles::getOriginalPosition() const {
00553     Common::Rect originalPosition = Common::Rect(Renderer::kOriginalWidth, Renderer::kBottomBorderHeight);
00554     originalPosition.translate(0, Renderer::kTopBorderHeight + Renderer::kFrameHeight);
00555     return originalPosition;
00556 }
00557 
00558 } // End of namespace Myst3


Generated on Sat Feb 16 2019 05:01:08 for ResidualVM by doxygen 1.7.1
curved edge   curved edge