[Scummvm-git-logs] scummvm master -> a728a5aee1893ea7f55308a74cc1305b4ef36300

sev- noreply at scummvm.org
Mon Feb 27 23:40:41 UTC 2023


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

Summary:
e53cc43272 SHERLOCK: Enable Chinese serrated scalpel
c4da7fffdf SHERLOCK: Skip images with negative sizes
47cbf56d94 GRAPHICS: Extract big5 renderer from sky into common graphics code
7daa2b5d85 SHERLOCK: Add rendering of Serrated scalpel chinese texts
518b96f24f SHERLOCK: Add function for width of multibyte character
5068c3253e SHERLOCK: Add skipping comments in big5
96d4f93d40 SHERLOCK: Extract word-wrapping algorithm into common file
d2b37c9dde SHERLOCK: Use common word-wrapping algorithm in journal
d5480c37c3 SHERLOCK: Handle scalpel big5 wrapping
0b9e85f93a SHERLOCK: Support Chinese object description wrapping for serrated scalpel
3396f84269 SHERLOCK: Increase line height in Chinese serrated scalpel
9329f7410a SHERLOCK: Fix button positions in Chinese serrated scalpel
b6efa850aa SHERLOCK: Increase line height for Chinese object description for serrated scalpel
4c782a176c SHERLOCK: Minor infoline fix for Chinese serrated scalpel
24baae1ae0 SHERLOCK: Add Chinese strings for Serrated Scalpel
8486739c00 SHERLOCK: Rearrange buttons in Chinese serrated scalpel
38a6cb8e8b SHERLOCK: Always use font 2 for Chinese serrated scalpel
c58c5cb86a SHERLOCK: Fix handling of big5 in journal
187b28f0e5 SHERLOCK: Fix offsets in Chinese journal for serrated scalpel
cf651ca336 SHERLOCK: Fix isPrintable for big5
f407e19890 SHERLOCK: Adjust journal buttons for Chinese serrated scalpel
12ed6c83fe SHERLOCK: Change coordinates for search bar in Chinese serrated scalpel
a728a5aee1 SHERLOCK: Don't print bogus hotkey


Commit: e53cc43272fb851be4dec2c3f00b7b73f087e255
    https://github.com/scummvm/scummvm/commit/e53cc43272fb851be4dec2c3f00b7b73f087e255
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2023-02-28T00:40:25+01:00

Commit Message:
SHERLOCK: Enable Chinese serrated scalpel

Changed paths:
    engines/sherlock/detection_tables.h


diff --git a/engines/sherlock/detection_tables.h b/engines/sherlock/detection_tables.h
index 25aa978151d..9135985f8da 100644
--- a/engines/sherlock/detection_tables.h
+++ b/engines/sherlock/detection_tables.h
@@ -220,11 +220,11 @@ static const SherlockGameDescription gameDescriptions[] = {
 		// Provided by AquariumTroop
 		{
 			"scalpel",
-			_s("Missing game code"), // Reason for being unsupported
+			nullptr,
 			AD_ENTRY1s("talk.lib", "334c7d468860f20eafbcd002891f0c6b", 173935),
 			Common::ZH_TWN,
 			Common::kPlatformDOS,
-			ADGF_UNSUPPORTED,
+			ADGF_NO_FLAGS,
 			GUIO1(GUIO_NOSPEECH)
 		},
 		GType_SerratedScalpel,


Commit: c4da7fffdf42261c50b5b0a804e0db923f2296e3
    https://github.com/scummvm/scummvm/commit/c4da7fffdf42261c50b5b0a804e0db923f2296e3
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2023-02-28T00:40:25+01:00

Commit Message:
SHERLOCK: Skip images with negative sizes

Changed paths:
    engines/sherlock/image_file.cpp


diff --git a/engines/sherlock/image_file.cpp b/engines/sherlock/image_file.cpp
index d839f01b7b5..53d74eca710 100644
--- a/engines/sherlock/image_file.cpp
+++ b/engines/sherlock/image_file.cpp
@@ -97,10 +97,25 @@ void ImageFile::load(Common::SeekableReadStream &stream, bool skipPalette, bool
 	int streamSize = stream.size();
 	while (stream.pos() < streamSize) {
 		ImageFrame frame;
+		bool invalid = false;
+
 		frame._width = stream.readUint16LE() + 1;
 		frame._height = stream.readUint16LE() + 1;
 		frame._paletteBase = stream.readByte();
 
+		// Exact purpose of the image with size (-320)x(-200) in
+		// the titles is unclear but skipping it seems to have no ill effect.
+		// Just skip it.
+		if (frame._width > 32768) {
+			frame._width = -(int16_t)frame._width;
+			invalid = true;
+		}
+
+		if (frame._height > 32768) {
+			frame._height = -(int16_t)frame._height;
+			invalid = true;
+		}
+
 		if (animImages) {
 			// Animation cutscene image files use a 16-bit x offset
 			frame._offset.x = stream.readUint16LE();
@@ -129,7 +144,12 @@ void ImageFile::load(Common::SeekableReadStream &stream, bool skipPalette, bool
 
 		frame._pos = stream.pos();
 
-		if (_name.empty()) {
+		if (invalid) {
+			frame._decoded = true;
+			frame._frame.create(frame._width, frame._height, Graphics::PixelFormat::createFormatCLUT8());
+			frame._frame.fillRect(Common::Rect(0, 0, frame._width, frame._height), 0xff);
+			stream.seek(MIN(stream.pos() + frame._size, stream.size()));
+		} else if (_name.empty()) {
 			// Load data for frame and decompress it
 			frame._decoded = true;
 			byte *data1 = new byte[frame._size + 4];


Commit: 47cbf56d944bdf2394ba47faaa22fa415929a061
    https://github.com/scummvm/scummvm/commit/47cbf56d944bdf2394ba47faaa22fa415929a061
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2023-02-28T00:40:25+01:00

Commit Message:
GRAPHICS: Extract big5 renderer from sky into common graphics code

Changed paths:
  A graphics/big5.cpp
  A graphics/big5.h
    engines/sky/sky.cpp
    engines/sky/sky.h
    engines/sky/text.cpp
    graphics/module.mk


diff --git a/engines/sky/sky.cpp b/engines/sky/sky.cpp
index 88819b7801b..d78953d6be5 100644
--- a/engines/sky/sky.cpp
+++ b/engines/sky/sky.cpp
@@ -122,8 +122,8 @@ SkyEngine::~SkyEngine() {
 	delete [] _chineseTraditionalBlock;
 	_chineseTraditionalBlock = nullptr;
 
-	_chineseTraditionalFont.clear();
-	_chineseTraditionalIndex.clear();
+	delete _big5Font;
+	_big5Font = nullptr;
 }
 
 void SkyEngine::syncSoundSettings() {
@@ -283,39 +283,6 @@ Common::Error SkyEngine::go() {
 	return Common::kNoError;
 }
 
-void SkyEngine::ChineseTraditionalGlyph::makeOutline() {
-	outline[0][0] = 0;
-	outline[0][1] = 0;
-	// OR into outline the original bitmap moved by 1 pixel
-	// 1 pixel down
-	for (int y = 0; y < SkyEngine::kChineseTraditionalHeight - 1; y++) {
-		outline[y+1][0] = bitmap[y][0];
-		outline[y+1][1] = bitmap[y][1];
-	}
-	// 1 pixel up
-	for (int y = 0; y < SkyEngine::kChineseTraditionalHeight - 1; y++) {
-		outline[y][0] |= bitmap[y+1][0];
-		outline[y][1] |= bitmap[y+1][1];
-	}
-	for (int y = 0; y < SkyEngine::kChineseTraditionalHeight; y++) {
-		// 1 pixel right
-		outline[y][0] |= bitmap[y][0] >> 1;
-		outline[y][1] |= bitmap[y][0] << 7;
-		outline[y][1] |= bitmap[y][1] >> 1;
-
-		// 1 pixel left
-		outline[y][0] |= bitmap[y][0] << 1;
-		outline[y][0] |= bitmap[y][1] >> 7;
-		outline[y][1] |= bitmap[y][1] << 1;
-	}
-
-	// Then AND-out the original bitmap
-	for (int y = 0; y < SkyEngine::kChineseTraditionalHeight; y++) {
-		outline[y][0] &= ~bitmap[y][0];
-		outline[y][1] &= ~bitmap[y][1];
-	}
-}
-
 static const struct {
 	// Identification
 	const char *md5; // File MD5
@@ -367,21 +334,8 @@ bool SkyEngine::loadChineseTraditional() {
 			skyExe.read(_chineseTraditionalBlock, stringBlockLen);
 
 			skyExe.seek(chineseExes[i].fontOffset);
-			_chineseTraditionalIndex = Common::move(Common::Array<int>(0x8000, -1));
-			// So far the only version had 1981 glyphs. Optimize a little bit for this number
-			// but don't rely on it in any way
-			_chineseTraditionalFont.reserve(1981);
-			while(1) {
-				// Big-endian because it's not really a u16 but a big5 sequence.
-				uint16 ch = skyExe.readUint16BE();
-				ChineseTraditionalGlyph glyph;
-				if (ch == 0xffff)
-					break;
-				skyExe.read(&glyph.bitmap, sizeof(glyph.bitmap));
-				glyph.makeOutline();
-				_chineseTraditionalIndex[ch & 0x7fff] = _chineseTraditionalFont.size();
-				_chineseTraditionalFont.push_back(glyph);
-			}
+			_big5Font = new Graphics::Big5Font();
+			_big5Font->loadPrefixedRaw(skyExe, 15);
 			return true;
 		}
 	}
diff --git a/engines/sky/sky.h b/engines/sky/sky.h
index ec6054deb22..a0332d1d34c 100644
--- a/engines/sky/sky.h
+++ b/engines/sky/sky.h
@@ -27,6 +27,7 @@
 #include "common/error.h"
 #include "common/keyboard.h"
 #include "engines/engine.h"
+#include "graphics/big5.h"
 
 /**
  * This is the namespace of the Sky engine.
@@ -110,19 +111,9 @@ public:
 	static void *_itemList[300];
 	static SystemVars *_systemVars;
 	static const char *shortcutsKeymapId;
-	static const int kChineseTraditionalWidth = 16;
-	static const int kChineseTraditionalHeight = 15;
-	struct ChineseTraditionalGlyph {
-		byte bitmap[kChineseTraditionalHeight][kChineseTraditionalWidth / 8];
-		byte outline[kChineseTraditionalHeight][kChineseTraditionalWidth / 8];
-
-		void makeOutline();
-	};
-
-  	uint32 _chineseTraditionalOffsets[8];
+	uint32 _chineseTraditionalOffsets[8];
 	char *_chineseTraditionalBlock;
-	Common::Array<ChineseTraditionalGlyph> _chineseTraditionalFont;
-	Common::Array<int> _chineseTraditionalIndex;
+	Graphics::Big5Font *_big5Font;
 
 protected:
 	// Engine APIs
diff --git a/engines/sky/text.cpp b/engines/sky/text.cpp
index de3fa48926c..6f4455dd93f 100644
--- a/engines/sky/text.cpp
+++ b/engines/sky/text.cpp
@@ -284,7 +284,7 @@ DisplayedText Text::displayText(char *textPtr, uint32 bufLen, uint8 *dest, bool
 		if (isBig5 && (textChar & 0x80)) {
 			isDoubleChar = true;
 			curPos++;
-			lineWidth += SkyEngine::kChineseTraditionalWidth;
+			lineWidth += Graphics::Big5Font::kChineseTraditionalWidth;
 		} else {
 			if ((_curCharSet == 1) && (textChar >= 0x80))
 				textChar = 0x20;
@@ -332,7 +332,7 @@ DisplayedText Text::displayText(char *textPtr, uint32 bufLen, uint8 *dest, bool
 	if (numLines > MAX_NO_LINES)
 		error("Maximum no. of lines exceeded");
 
-	int charHeight = isBig5 ? MAX<int>(_charHeight, SkyEngine::kChineseTraditionalHeight) : _charHeight;
+	int charHeight = isBig5 && _vm->_big5Font ? MAX<int>(_charHeight, _vm->_big5Font->getFontHeight()) : _charHeight;
 	uint32 dtLineSize = pixelWidth * charHeight;
 	uint32 numBytes = (dtLineSize * numLines) + sizeof(DataFileHeader) + 4;
 
@@ -357,6 +357,7 @@ DisplayedText Text::displayText(char *textPtr, uint32 bufLen, uint8 *dest, bool
 	uint32 *centerTblPtr = centerTable;
 
 	do {
+		byte *lineEnd = curDest + pixelWidth;
 		if (center) {
 			uint32 width = (pixelWidth - *centerTblPtr) >> 1;
 			centerTblPtr++;
@@ -368,9 +369,17 @@ DisplayedText Text::displayText(char *textPtr, uint32 bufLen, uint8 *dest, bool
 			if (isBig5 && (textChar & 0x80)) {
 				uint8 trail = *curPos++;
 				uint16 fullCh = (textChar << 8) | trail;
-				makeChineseGameCharacter(fullCh, _characterSet, curDest, color, pixelWidth);
-			} else
-				makeGameCharacter(textChar - 0x20, _characterSet, curDest, color, pixelWidth);
+				if (_vm->_big5Font->drawBig5Char(curDest, fullCh, lineEnd - curDest, charHeight, pixelWidth, color, 240)) {
+					//update position
+					curDest += Graphics::Big5Font::kChineseTraditionalWidth;
+					textChar = *curPos++;
+					continue;
+				}
+
+				textChar = '?';
+			}
+
+			makeGameCharacter(textChar - 0x20, _characterSet, curDest, color, pixelWidth);
 			textChar = *curPos++;
 		}
 
@@ -385,29 +394,6 @@ DisplayedText Text::displayText(char *textPtr, uint32 bufLen, uint8 *dest, bool
 	return ret;
 }
 
-void Text::makeChineseGameCharacter(uint16 textChar, uint8 *charSetPtr, uint8 *&dest, uint8 color, uint16 bufPitch) {
-	int glyphIdx = _vm->_chineseTraditionalIndex[textChar & 0x7fff];
-	if (glyphIdx < 0) {
-		makeGameCharacter('?' - 0x20, charSetPtr, dest, color, bufPitch);
-		return;
-	}
-
-	const SkyEngine::ChineseTraditionalGlyph& glyph = _vm->_chineseTraditionalFont[glyphIdx];
-
-	for (int y = 0; y < SkyEngine::kChineseTraditionalHeight; y++) {
-		uint8 *cur = dest + y * bufPitch;
-
-		for (int byte = 0; byte < 2; byte++)
-			for (int bit = 0; bit < 8; bit++, cur++)
-				if ((glyph.bitmap[y][byte] << bit) & 0x80)
-					*cur = color;
-				else if ((glyph.outline[y][byte] << bit) & 0x80)
-					*cur = 240;
-	}
-	//update position
-	dest += SkyEngine::kChineseTraditionalWidth;
-}
-
 void Text::makeGameCharacter(uint8 textChar, uint8 *charSetPtr, uint8 *&dest, uint8 color, uint16 bufPitch) {
 	bool maskBit, dataBit;
 	uint8 charWidth = (uint8)((*(charSetPtr + textChar)) + 1 - _dtCharSpacing);
diff --git a/graphics/big5.cpp b/graphics/big5.cpp
new file mode 100644
index 00000000000..87a283e8b1b
--- /dev/null
+++ b/graphics/big5.cpp
@@ -0,0 +1,130 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "graphics/big5.h"
+
+namespace Graphics {
+
+Big5Font::Big5Font() : _chineseTraditionalHeight(0) {
+}
+
+Big5Font::~Big5Font() {
+	_chineseTraditionalFont.clear();
+	_chineseTraditionalIndex.clear();
+}
+
+void Big5Font::ChineseTraditionalGlyph::makeOutline(int height) {
+	outline[0][0] = 0;
+	outline[0][1] = 0;
+	// OR into outline the original bitmap moved by 1 pixel
+	// 1 pixel down
+	for (int y = 0; y < height - 1; y++) {
+		outline[y+1][0] = bitmap[y][0];
+		outline[y+1][1] = bitmap[y][1];
+	}
+	// 1 pixel up
+	for (int y = 0; y < height - 1; y++) {
+		outline[y][0] |= bitmap[y+1][0];
+		outline[y][1] |= bitmap[y+1][1];
+	}
+	for (int y = 0; y < height; y++) {
+		// 1 pixel right
+		outline[y][0] |= bitmap[y][0] >> 1;
+		outline[y][1] |= bitmap[y][0] << 7;
+		outline[y][1] |= bitmap[y][1] >> 1;
+
+		// 1 pixel left
+		outline[y][0] |= bitmap[y][0] << 1;
+		outline[y][0] |= bitmap[y][1] >> 7;
+		outline[y][1] |= bitmap[y][1] << 1;
+	}
+
+	// Then AND-out the original bitmap
+	for (int y = 0; y < height; y++) {
+		outline[y][0] &= ~bitmap[y][0];
+		outline[y][1] &= ~bitmap[y][1];
+	}
+}
+
+void Big5Font::loadPrefixedRaw(Common::ReadStream &input, int height) {
+	_chineseTraditionalHeight = height;
+	_chineseTraditionalFont.clear();
+
+	_chineseTraditionalIndex = Common::move(Common::Array<int>(0x8000, -1));
+	// So far the smallest version had 1981 glyphs. Optimize a little bit for this number
+	// but don't rely on it in any way
+	_chineseTraditionalFont.reserve(1981);
+	while(!input.eos()) {
+		// Big-endian because it's not really a u16 but a big5 sequence.
+		uint16 ch = input.readUint16BE();
+		ChineseTraditionalGlyph glyph;
+		if (ch == 0xffff)
+			break;
+		memset(&glyph.bitmap, 0, sizeof(glyph.bitmap));
+		memset(&glyph.outline, 0, sizeof(glyph.outline));
+		input.read(&glyph.bitmap, (kChineseTraditionalWidth / 8) * _chineseTraditionalHeight);
+		glyph.makeOutline(height);
+		_chineseTraditionalIndex[ch & 0x7fff] = _chineseTraditionalFont.size();
+		_chineseTraditionalFont.push_back(glyph);
+	}
+}
+
+template <class T> bool Big5Font::drawReal(byte *dest, uint16 textChar, int maxX, int maxY, uint32 destPitch, byte color, byte outlineColor, bool outline) const {
+	int glyphIdx = _chineseTraditionalIndex[textChar & 0x7fff];
+	if (glyphIdx < 0) {
+		return false;
+	}
+
+	const ChineseTraditionalGlyph& glyph = _chineseTraditionalFont[glyphIdx];
+
+	for (int y = 0; y < _chineseTraditionalHeight && y < maxY; y++) {
+		T *cur = (T*) (dest + y * destPitch);
+		T *curMax = cur + maxX;
+
+		for (int byte = 0; byte < 2; byte++)
+			for (int bit = 0; bit < 8 && cur < curMax; bit++, cur++)
+				if ((glyph.bitmap[y][byte] << bit) & 0x80)
+					*cur = color;
+				else if (outline && (((glyph.outline[y][byte] << bit) & 0x80)))
+					*cur = outlineColor;
+	}
+	return true;
+}
+
+bool Big5Font::drawBig5Char(byte *dest, uint16 ch, int maxX, int maxY, uint32 destPitch, byte color, byte outlineColor) const {
+	return drawReal<uint8>(dest, maxX, maxY, destPitch, ch, color, outlineColor, true);
+}
+
+bool Big5Font::drawBig5Char(Graphics::Surface *surf, uint16 ch, const Common::Point &pt, uint32 color) const {
+	switch(surf->format.bytesPerPixel) {
+	case 4:
+		return drawReal<uint32>((byte*)surf->getBasePtr(pt.x, pt.y), surf->w - pt.x, surf->h - pt.y, surf->pitch, ch, color, 0, false);
+	case 2:
+		return drawReal<uint16>((byte*)surf->getBasePtr(pt.x, pt.y), surf->w - pt.x, surf->h - pt.y, surf->pitch, ch, color, 0, false);
+	case 1:
+		return drawReal<uint8>((byte*)surf->getBasePtr(pt.x, pt.y), surf->w - pt.x, surf->h - pt.y, surf->pitch, ch, color, 0, false);
+	default:
+		error("Big5 font for bpp=%d is not supported", surf->format.bytesPerPixel);
+	}
+
+}
+
+}
diff --git a/graphics/big5.h b/graphics/big5.h
new file mode 100644
index 00000000000..1aa73876dc6
--- /dev/null
+++ b/graphics/big5.h
@@ -0,0 +1,60 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef GRAPHICS_BIG5_H
+#define GRAPHICS_BIG5_H
+
+#include "common/array.h"
+#include "common/rect.h"
+#include "common/stream.h"
+#include "graphics/surface.h"
+
+namespace Graphics {
+
+class Big5Font {
+public:
+	Big5Font();
+	~Big5Font();
+	void loadPrefixedRaw(Common::ReadStream &input, int height);
+	bool drawBig5Char(byte *dest, uint16 ch, int maxX, int maxY, uint32 destPitch, byte color, byte outlineColor) const;
+	bool drawBig5Char(Graphics::Surface *surf, uint16 ch, const Common::Point &pt, uint32 color) const;
+
+	int getFontHeight() const { return _chineseTraditionalHeight; }
+
+	static const int kChineseTraditionalWidth = 16;
+private:
+	static const int kChineseTraditionalMaxHeight = 16;
+	struct ChineseTraditionalGlyph {
+		byte bitmap[kChineseTraditionalMaxHeight][kChineseTraditionalWidth / 8];
+		byte outline[kChineseTraditionalMaxHeight][kChineseTraditionalWidth / 8];
+
+		void makeOutline(int height);
+	};
+
+	template <class T> bool drawReal(byte *dest, uint16 textChar, int maxX, int maxY, uint32 destPitch, byte color, byte outlineColor, bool outline) const;
+
+	Common::Array<ChineseTraditionalGlyph> _chineseTraditionalFont;
+	Common::Array<int> _chineseTraditionalIndex;
+	int _chineseTraditionalHeight;
+};
+
+}
+#endif
diff --git a/graphics/module.mk b/graphics/module.mk
index 6c8b021c81b..1e63b227526 100644
--- a/graphics/module.mk
+++ b/graphics/module.mk
@@ -1,6 +1,7 @@
 MODULE := graphics
 
 MODULE_OBJS := \
+	big5.o \
 	blit.o \
 	blit-scale.o \
 	cursorman.o \


Commit: 7daa2b5d85e644cefac039eb412e78d44a1637e4
    https://github.com/scummvm/scummvm/commit/7daa2b5d85e644cefac039eb412e78d44a1637e4
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2023-02-28T00:40:25+01:00

Commit Message:
SHERLOCK: Add rendering of Serrated scalpel chinese texts

Changed paths:
    engines/sherlock/fonts.cpp
    engines/sherlock/fonts.h


diff --git a/engines/sherlock/fonts.cpp b/engines/sherlock/fonts.cpp
index dfb75f7e281..a85927d8028 100644
--- a/engines/sherlock/fonts.cpp
+++ b/engines/sherlock/fonts.cpp
@@ -36,13 +36,16 @@ int Fonts::_widestChar;
 uint16 Fonts::_charCount;
 byte Fonts::_yOffsets[255];
 bool Fonts::_isModifiedEucCn;
+bool Fonts::_isBig5;
 byte *Fonts::_chineseFont;
+Graphics::Big5Font *Fonts::_big5Font;
 
 void Fonts::setVm(SherlockEngine *vm) {
 	_vm = vm;
 	_font = nullptr;
 	_charCount = 0;
 	_isModifiedEucCn = (_vm->getLanguage() == Common::Language::ZH_ANY && _vm->getGameID() == GameType::GType_RoseTattoo);
+	_isBig5 = (_vm->getLanguage() == Common::Language::ZH_TWN && _vm->getGameID() == GameType::GType_SerratedScalpel);
 }
 
 void Fonts::freeFont() {
@@ -78,6 +81,16 @@ void Fonts::setFont(int fontNum) {
 		}
 	}
 
+	if (_isBig5 && _chineseFont == nullptr) {
+		Common::File pat;
+		if (!pat.open("TEXTPAT.FNT")) {
+			_isBig5 = false;
+		} else {
+			_big5Font = new Graphics::Big5Font();
+			_big5Font->loadPrefixedRaw(pat, 14);
+		}
+	}
+
 	if (_vm->getPlatform() != Common::kPlatform3DO) {
 		// PC
 		// use FONT[number].VGS, which is a regular sherlock graphic file
@@ -290,6 +303,17 @@ void Fonts::writeString(BaseSurface *surface, const Common::String &str,
 			charPos.x += 5; // hardcoded space
 			continue;
 		}
+
+		if (_isBig5 && (curChar & 0x80) && nextChar) {
+			curCharPtr++;
+			uint16 point = (curChar << 8) | nextChar;
+			if (_big5Font->drawBig5Char(surface->surfacePtr(), point, charPos, overrideColor)) {
+				charPos.x += Graphics::Big5Font::kChineseTraditionalWidth;
+				continue;
+			}
+			curChar = '?';
+		}
+		
 		curChar = translateChar(curChar);
 
 		if (curChar < _charCount) {
@@ -334,6 +358,12 @@ int Fonts::stringWidth(const Common::String &str) {
 			continue;
 		}
 
+		if (_isBig5 && (curChar & 0x80) && nextChar) {
+			width += Graphics::Big5Font::kChineseTraditionalWidth;
+			c++;
+			continue;
+		}
+
 		width += charWidth(*c);
 	}
 
@@ -372,6 +402,12 @@ int Fonts::stringHeight(const Common::String &str) {
 			continue;
 		}
 
+		if (_isBig5 && _big5Font && (curChar & 0x80) && nextChar) {
+			height = MAX(height, _big5Font->getFontHeight());
+			c++;
+			continue;
+		}
+
 		height = MAX(height, charHeight(*c));
 	}
 
diff --git a/engines/sherlock/fonts.h b/engines/sherlock/fonts.h
index d5e52196788..d59ee3e8e9c 100644
--- a/engines/sherlock/fonts.h
+++ b/engines/sherlock/fonts.h
@@ -24,6 +24,7 @@
 
 #include "common/rect.h"
 #include "common/platform.h"
+#include "graphics/big5.h"
 #include "graphics/surface.h"
 
 namespace Sherlock {
@@ -34,14 +35,21 @@ class BaseSurface;
 
 class Fonts {
 private:
+	struct ChinaFontCodElement {
+		uint16 a;
+		uint16 b;
+		uint8 c;
+	};
 	static ImageFile *_font;
 	static byte *_chineseFont;
+	static Graphics::Big5Font *_big5Font;
 	static byte _yOffsets[255];
 	static int _fontNumber;
 	static int _fontHeight;
 	static int _widestChar;
 	static uint16 _charCount;
 	static bool _isModifiedEucCn;
+	static bool _isBig5;
 
 	static inline byte translateChar(byte c);
 protected:
@@ -65,6 +73,7 @@ public:
 	static void freeFont();
 
 	static bool isModifiedEucCn() { return _isModifiedEucCn; }
+	static bool isBig5() { return _isBig5; }
 
 	/**
 	 * Set the font to use for writing text on the screen
@@ -96,7 +105,7 @@ public:
 	/**
 	 * Return the font height
 	 */
-	int fontHeight() const { return _chineseFont ? MAX(_fontHeight, 16) : _fontHeight; }
+	int fontHeight() const { return _chineseFont || _isBig5 ? MAX(_fontHeight, 16) : _fontHeight; }
 
 	/**
 	 * Return the width of the widest character in the font


Commit: 518b96f24fec29bd24fe5bd8ff52c8ff79c79ab0
    https://github.com/scummvm/scummvm/commit/518b96f24fec29bd24fe5bd8ff52c8ff79c79ab0
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2023-02-28T00:40:25+01:00

Commit Message:
SHERLOCK: Add function for width of multibyte character

Changed paths:
    engines/sherlock/fonts.cpp
    engines/sherlock/fonts.h


diff --git a/engines/sherlock/fonts.cpp b/engines/sherlock/fonts.cpp
index a85927d8028..49fe0c24a68 100644
--- a/engines/sherlock/fonts.cpp
+++ b/engines/sherlock/fonts.cpp
@@ -334,37 +334,31 @@ int Fonts::stringWidth(const Common::String &str) {
 
 	bool isInEucEscape = false;
 
-	for (const char *c = str.c_str(); *c; ++c) {
-		byte curChar = *c;
-		byte nextChar = c[1];
+	for (int idx = 0; idx < (int) str.size(); ) {
+		byte curChar = str.c_str()[idx];
+		byte nextChar = str.c_str()[idx+1];
 
 		if (_isModifiedEucCn && !isInEucEscape && curChar == '@' && nextChar == '$') {
 			width += charWidth(' ');
-			c++;
+			idx += 2;
 			isInEucEscape = true;
 			continue;
 		}
 
 		if (_isModifiedEucCn && isInEucEscape && curChar == '$' && nextChar == '@') {
 			width += charWidth(' ');
-			c++;
+			idx += 2;
 			isInEucEscape = false;
 			continue;
 		}
 
 		if (_isModifiedEucCn && curChar >= 0x41 && nextChar >= 0x41 && (isInEucEscape || ((curChar >= 0xa1) && (nextChar >= 0xa1)))) {
 			width += kChineseWidth;
-			c++;
+			idx += 2;
 			continue;
 		}
 
-		if (_isBig5 && (curChar & 0x80) && nextChar) {
-			width += Graphics::Big5Font::kChineseTraditionalWidth;
-			c++;
-			continue;
-		}
-
-		width += charWidth(*c);
+		width += charWidth(str.c_str(), idx);
 	}
 
 	return width;
@@ -414,22 +408,37 @@ int Fonts::stringHeight(const Common::String &str) {
 	return height;
 }
 
-int Fonts::charWidth(unsigned char c) {
-	byte curChar;
+
+int Fonts::charWidth(const char *p, int &idx) {
+	byte curChar = p[idx];
+	byte nextChar = p[idx + 1];
+	if (_isBig5 && (curChar & 0x80) && nextChar) {
+		idx += 2;
+		return Graphics::Big5Font::kChineseTraditionalWidth;
+	}
+
+	idx++;
 
 	if (!_font)
 		return 0;
 
-	if (c == ' ') {
+	if (curChar == ' ') {
 		return 5; // hardcoded space
 	}
-	curChar = translateChar(c);
 
-	if (curChar < _charCount)
-		return (*_font)[curChar]._frame.w + 1;
+	byte translatedChar = translateChar(curChar);
+
+	if (translatedChar < _charCount)
+		return (*_font)[translatedChar]._frame.w + 1;
 	return 0;
 }
 
+int Fonts::charWidth(char c) {
+	char s[2] = { c, '\0' };
+	int idx = 0;
+	return charWidth(s, idx);
+}
+
 int Fonts::charHeight(unsigned char c) {
 	byte curChar;
 
diff --git a/engines/sherlock/fonts.h b/engines/sherlock/fonts.h
index d59ee3e8e9c..50ede979998 100644
--- a/engines/sherlock/fonts.h
+++ b/engines/sherlock/fonts.h
@@ -95,7 +95,12 @@ public:
 	/**
 	 * Returns the width of a character in pixels
 	 */
-	int charWidth(unsigned char c);
+	int charWidth(const char *str, int &idx);
+
+	/**
+	 * Returns the width of a character in pixels
+	 */
+	int charWidth(char ch);
 
 	/**
 	 * Returns the width of a character in pixels


Commit: 5068c3253e3a6cd388ad4a628a4ef71cad61d09e
    https://github.com/scummvm/scummvm/commit/5068c3253e3a6cd388ad4a628a4ef71cad61d09e
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2023-02-28T00:40:25+01:00

Commit Message:
SHERLOCK: Add skipping comments in big5

Changed paths:
    engines/sherlock/talk.cpp


diff --git a/engines/sherlock/talk.cpp b/engines/sherlock/talk.cpp
index 1e945ac9583..0bfdf669ede 100644
--- a/engines/sherlock/talk.cpp
+++ b/engines/sherlock/talk.cpp
@@ -773,8 +773,19 @@ void Talk::doScript(const Common::String &script) {
 			_endStr = true;
 		} else if (c == '{') {
 			// Start of comment, so skip over it
-			while (*str++ != '}')
-				;
+			if (Fonts::isBig5()) {
+				while (*str && *str != '}') {
+					if ((*str & 0x80) && str[1])
+						str += 2;
+					else
+						str++;
+				}
+				if (*str)
+					str++;
+			} else {
+				while (*str++ != '}')
+					;
+			}
 		} else if (isOpcode(c)) {
 			// the original interpreter checked for c being >= 0x80
 			// and if that is the case, it tried to process it as opcode, BUT ALSO ALWAYS skipped over it


Commit: 96d4f93d40cbf030d28d77c70b2446fa1458fc15
    https://github.com/scummvm/scummvm/commit/96d4f93d40cbf030d28d77c70b2446fa1458fc15
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2023-02-28T00:40:25+01:00

Commit Message:
SHERLOCK: Extract word-wrapping algorithm into common file

Changed paths:
    engines/sherlock/fonts.cpp
    engines/sherlock/fonts.h
    engines/sherlock/tattoo/widget_base.cpp


diff --git a/engines/sherlock/fonts.cpp b/engines/sherlock/fonts.cpp
index 49fe0c24a68..5a5b3a7fbc0 100644
--- a/engines/sherlock/fonts.cpp
+++ b/engines/sherlock/fonts.cpp
@@ -433,6 +433,122 @@ int Fonts::charWidth(const char *p, int &idx) {
 	return 0;
 }
 
+Common::Array<Common::String> Fonts::wordWrap(const Common::String &str, uint maxWidth, Common::String &rem, uint maxChars, uint maxLines, bool skipHeadAt) {
+	Common::Array<Common::String> lines;
+	int strIdx = 0;
+
+	bool isInEucEscape = false;
+	do {
+		uint width = 0;
+		uint numChars = 0;
+		int spaceIdx = 0;
+		bool spacePNeedsEndEscape = false;
+		bool spacePNeedsBeginEscape = false;
+		int lineStartIdx = strIdx;
+		// Invariant: lastCharIdx is either -1
+		// or is exactly one character behind strP
+		// and in the same escape state.
+		int lastCharIdx = -1;
+		bool isLineStartPInEucEscape = isInEucEscape;
+
+		if (skipHeadAt) {
+			// If the first character is a '@' flagging a title line, then move
+			// past it, so the @ won't be included in the line width calculation
+			if (strIdx + 1 < (int)str.size() && str[strIdx] == '@' && str[strIdx + 1] != '$')
+				++strIdx;
+		}
+
+		// Find how many characters will fit on the next line
+		while (width < maxWidth && numChars < maxChars && strIdx < (int)str.size() && str[strIdx] != '\n') {
+			if (_isModifiedEucCn) {
+				byte curChar = str[strIdx];
+				byte nextChar = strIdx + 1 < (int)str.size() ? str[strIdx + 1] : 0;
+				if (!isInEucEscape && curChar == '@' && nextChar == '$') {
+					width += charWidth(' ');
+					numChars++;
+					if (lineStartIdx != strIdx) {
+						spaceIdx = strIdx;
+						spacePNeedsEndEscape = isInEucEscape;
+						spacePNeedsBeginEscape = true;
+					}
+					lastCharIdx = -1;
+					strIdx += 2;
+					isInEucEscape = true;
+					continue;
+				}
+
+				if (isInEucEscape && curChar == '$' && nextChar == '@') {
+					width += charWidth(' ');
+					numChars++;
+					spaceIdx = strIdx;
+					lastCharIdx = -1;
+					strIdx += 2;
+					spacePNeedsEndEscape = isInEucEscape;
+					spacePNeedsBeginEscape = false;
+					isInEucEscape = false;
+					continue;
+				}
+
+				if (curChar >= 0x41 && nextChar >= 0x41 && (isInEucEscape || ((curChar >= 0xa1) && (nextChar >= 0xa1)))) {
+					width += kChineseWidth;
+					lastCharIdx = strIdx;
+					strIdx += 2;
+					numChars++;
+					continue;
+				}
+			}
+
+			// Keep track of the last space
+			if (str[strIdx] == ' ') {
+				spacePNeedsEndEscape = isInEucEscape;
+				spacePNeedsBeginEscape = isInEucEscape;
+				spaceIdx = strIdx;
+			}
+			lastCharIdx = strIdx;
+			width += charWidth(str.c_str(), strIdx);
+			numChars++;
+		}
+
+		bool previousEucEscape = isInEucEscape;
+
+		// If the line was too wide to fit on a single line, go back to the last space
+		// if there was one, or otherwise simply break the line at this point
+		if (width >= maxWidth || numChars >= maxChars) {
+			if (spaceIdx > 0) {
+				previousEucEscape = spacePNeedsEndEscape;
+				isInEucEscape = spacePNeedsBeginEscape;
+				strIdx = spaceIdx;
+			} else if (lastCharIdx > 0 && lastCharIdx != lineStartIdx) {
+				strIdx = lastCharIdx;
+			}
+		}
+
+		Common::String line = str.substr(lineStartIdx, strIdx - lineStartIdx);
+		assert(!line.contains('\n'));
+		if (!line.hasPrefix("@$") && isLineStartPInEucEscape)
+			line = "@$" + line;
+
+		if (!line.hasSuffix("$@") && previousEucEscape)
+			line = line + "$@";
+
+		// Add the line to the output array
+		lines.push_back(line);
+
+		// Move the string ahead to the next line
+		while (strIdx < (int)str.size() && (str[strIdx] == '\n' || str[strIdx] == ' ' || str[strIdx] == '\r'))
+			++strIdx;
+	} while (strIdx < (int)str.size() && lines.size() < maxLines);
+
+	rem = str.substr(strIdx);
+
+	return lines;
+}
+
+Common::Array<Common::String> Fonts::wordWrap(const Common::String &str, uint maxWidth, uint maxChars, uint maxLines, bool skipHeadAt) {
+	Common::String rem;
+	return wordWrap(str, maxWidth, rem, maxChars, maxLines, skipHeadAt);
+}
+
 int Fonts::charWidth(char c) {
 	char s[2] = { c, '\0' };
 	int idx = 0;
diff --git a/engines/sherlock/fonts.h b/engines/sherlock/fonts.h
index 50ede979998..cf9c3aa4321 100644
--- a/engines/sherlock/fonts.h
+++ b/engines/sherlock/fonts.h
@@ -121,6 +121,11 @@ public:
 	 * Return the currently active font number
 	 */
 	int fontNumber() const { return _fontNumber; }
+
+	Common::Array<Common::String> wordWrap(const Common::String &str, uint maxWidth, Common::String &rem,
+					       uint maxChars = Common::String::npos, uint maxLines = Common::String::npos, bool skipHeadAt = false);
+	Common::Array<Common::String> wordWrap(const Common::String &str, uint maxWidth,
+					       uint maxChars = Common::String::npos, uint maxLines = Common::String::npos, bool skipHeadAt = false);
 };
 
 } // End of namespace Sherlock
diff --git a/engines/sherlock/tattoo/widget_base.cpp b/engines/sherlock/tattoo/widget_base.cpp
index d002c638e33..7587b722e22 100644
--- a/engines/sherlock/tattoo/widget_base.cpp
+++ b/engines/sherlock/tattoo/widget_base.cpp
@@ -135,104 +135,18 @@ void WidgetBase::drawBackground() {
 
 Common::String WidgetBase::splitLines(const Common::String &str, Common::StringArray &lines, int maxWidth, uint maxLines) {
 	Talk &talk = *_vm->_talk;
-	const char *strP = str.c_str();
-	bool isModifiedEucCn = Fonts::isModifiedEucCn();
 
-	bool isInEucEscape = false;
-
-	// Loop counting up lines
 	lines.clear();
-	do {
-		int width = 0;
-		const char *spaceP = nullptr;
-		bool spacePNeedsEndEscape = false;
-		bool spacePNeedsBeginEscape = false;
-		const char *lineStartP = strP;
-		// Invariant: lastCharP is either nullptr
-		// or is exactly one character behind strP
-		// and in the same escape state.
-		const char *lastCharP = nullptr;
-		bool isLineStartPInEucEscape = isInEucEscape;
-
-		// Find how many characters will fit on the next line
-		while (width < maxWidth && *strP && ((byte)*strP < talk._opcodes[OP_SWITCH_SPEAKER] ||
-				(byte)*strP == talk._opcodes[OP_NULL])) {
-			if (isModifiedEucCn) {
-				byte curChar = *strP;
-				byte nextChar = strP[1];
-				if (!isInEucEscape && curChar == '@' && nextChar == '$') {
-					width += _surface.charWidth(' ');
-					if (lineStartP != strP) {
-						spaceP = strP;
-						spacePNeedsEndEscape = isInEucEscape;
-						spacePNeedsBeginEscape = true;
-					}
-					lastCharP = nullptr;
-					strP += 2;
-					isInEucEscape = true;
-					continue;
-				}
-				
-				if (isInEucEscape && curChar == '$' && nextChar == '@') {
-					width += _surface.charWidth(' ');
-					spaceP = strP;
-					lastCharP = nullptr;
-					strP += 2;
-					spacePNeedsEndEscape = isInEucEscape;
-					spacePNeedsBeginEscape = false;
-					isInEucEscape = false;
-					continue;
-				}
-
-				if (curChar >= 0x41 && nextChar >= 0x41 && (isInEucEscape || ((curChar >= 0xa1) && (nextChar >= 0xa1)))) {
-					width += Fonts::kChineseWidth;
-					lastCharP = strP;
-					strP += 2;
-					continue;
-				}
-			}
-			width += _surface.charWidth(*strP);
-
-			// Keep track of the last space
-			if (*strP == ' ') {
-				spacePNeedsEndEscape = isInEucEscape;
-				spacePNeedsBeginEscape = isInEucEscape;
-				spaceP = strP;
-			}
-			lastCharP = strP;
-			++strP;
-		}
-
-		bool previousEucEscape = isInEucEscape;
-
-		// If the line was too wide to fit on a single line, go back to the last space
-		// if there was one, or otherwise simply break the line at this point
-		if (width >= maxWidth && spaceP != nullptr) {
-			previousEucEscape = spacePNeedsEndEscape;
-			isInEucEscape = spacePNeedsBeginEscape;
-			strP = spaceP;
-		} else if (width >= maxWidth && lastCharP != nullptr && lastCharP != lineStartP) {
-			strP = lastCharP;
-		}
-
-		Common::String line(lineStartP, strP);
-		if (!line.hasPrefix("@$") && isLineStartPInEucEscape)
-			line = "@$" + line;
-
-		if (!line.hasSuffix("$@") && previousEucEscape)
-			line = line + "$@";		
-
-		// Add the line to the output array
-		lines.push_back(line);
-
-		// Move the string ahead to the next line
-		if (*strP == ' ' || *strP == 13)
-			++strP;
-	} while (*strP && (lines.size() < maxLines) && ((byte)*strP < talk._opcodes[OP_SWITCH_SPEAKER]
-			|| (byte)*strP == talk._opcodes[OP_NULL]));
+	uint idx;
+	for (idx = 0; idx < str.size(); idx++)
+		if (str[idx] >= talk._opcodes[OP_SWITCH_SPEAKER] && str[idx] != talk._opcodes[OP_NULL])
+			break;
+	Common::String rest;
+	Common::Array<Common::String> arr = _surface.wordWrap(str.substr(0, idx), maxWidth, rest, Common::String::npos, maxLines);
+	lines.swap(arr);
 
 	// Return any remaining text left over
-	return *strP ? Common::String(strP) : Common::String();
+	return rest + str.substr(idx);
 }
 
 void WidgetBase::restrictToScreen() {


Commit: d2b37c9dde58e8207b6dc430d8a0a005bf5ebdc7
    https://github.com/scummvm/scummvm/commit/d2b37c9dde58e8207b6dc430d8a0a005bf5ebdc7
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2023-02-28T00:40:25+01:00

Commit Message:
SHERLOCK: Use common word-wrapping algorithm in journal

Changed paths:
    engines/sherlock/journal.cpp


diff --git a/engines/sherlock/journal.cpp b/engines/sherlock/journal.cpp
index 15c8290e504..6ecdee9ca83 100644
--- a/engines/sherlock/journal.cpp
+++ b/engines/sherlock/journal.cpp
@@ -757,38 +757,11 @@ void Journal::loadJournalFile(bool alreadyLoaded) {
 	// _lines array
 	_lines.clear();
 
-	while (!journalString.empty()) {
-		const char *startP = journalString.c_str();
-
-		// If the first character is a '@' flagging a title line, then move
-		// past it, so the @ won't be included in the line width calculation
-		if (*startP == '@')
-			++startP;
-
-		// Build up chacters until a full line is found
-		int width = 0;
-		const char *endP = startP;
-		while (width < JOURNAL_MAX_WIDTH && *endP && *endP != '\n' && (endP - startP) < (JOURNAL_MAX_CHARS - 1))
-			width += screen.charWidth(*endP++);
-
-		// If word wrapping, move back to end of prior word
-		if (width >= JOURNAL_MAX_WIDTH || (endP - startP) >= (JOURNAL_MAX_CHARS - 1)) {
-			while (*--endP != ' ')
-				;
-		}
-
-		// Add in the line
-		_lines.push_back(Common::String(journalString.c_str(), endP));
-
-		// Strip line off from string being processed
-		journalString = *endP ? Common::String(endP + 1) : "";
-	}
+	_lines = screen.wordWrap(journalString, JOURNAL_MAX_WIDTH, JOURNAL_MAX_CHARS, Common::String::npos, true);
 
 	// Add a blank line at the end of the text as long as text was present
 	if (!startOfReply) {
 		_lines.push_back("");
-	} else {
-		_lines.clear();
 	}
 }
 


Commit: d5480c37c3c379184d0f1a7f4bd88586d3d269d8
    https://github.com/scummvm/scummvm/commit/d5480c37c3c379184d0f1a7f4bd88586d3d269d8
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2023-02-28T00:40:25+01:00

Commit Message:
SHERLOCK: Handle scalpel big5 wrapping

Changed paths:
    engines/sherlock/scalpel/scalpel_talk.cpp


diff --git a/engines/sherlock/scalpel/scalpel_talk.cpp b/engines/sherlock/scalpel/scalpel_talk.cpp
index 3faaabc0f75..a63b0eb6bce 100644
--- a/engines/sherlock/scalpel/scalpel_talk.cpp
+++ b/engines/sherlock/scalpel/scalpel_talk.cpp
@@ -232,11 +232,14 @@ void ScalpelTalk::talkInterface(const byte *&str) {
 	}
 
 	// Find amount of text that will fit on the line
-	int width = 0, idx = 0;
+	int width = 0, idx = 0, last_space = 0, last_valid = 0;
 	do {
-		width += screen.charWidth(str[idx]);
-		++idx;
-		++_charCount;
+		int old_idx = idx;
+		if (str[idx] == ' ')
+			last_space = idx;
+		last_valid = idx;
+		width += screen.charWidth((const char *) str, idx);
+		_charCount += idx - old_idx;
 	} while (width < 298 && str[idx] && str[idx] != '{' && (!isOpcode(str[idx])));
 
 	if (str[idx] || width >= 298) {
@@ -250,9 +253,12 @@ void ScalpelTalk::talkInterface(const byte *&str) {
 
 	// If word wrap is needed, find the start of the current word
 	if (width >= 298) {
-		while (str[idx] != ' ') {
-			--idx;
-			--_charCount;
+		if (last_space > 0) {
+			_charCount -= idx - last_space;
+			idx = last_space;
+		} else {
+			_charCount -= idx - last_valid;
+			idx = last_valid;
 		}
 	}
 
@@ -279,8 +285,8 @@ void ScalpelTalk::talkInterface(const byte *&str) {
 	// Move to end of displayed line
 	str += idx;
 
-	// If line wrap occurred, then move to after the separating space between the words
-	if (str[0] && (!isOpcode(str[0])) && str[0] != '{')
+	// If line wrap with space occurred, then move to after the separating space between the words
+	if (str[0] == ' ')
 		++str;
 
 	_yp += 9;
@@ -823,19 +829,27 @@ int ScalpelTalk::talkLine(int lineNum, int stateNum, byte color, int lineY, bool
 	for (;;) {
 		// Get as much of the statement as possible will fit on the
 		Common::String sLine;
-		const char *lineEndP = lineStartP;
 		int width = 0;
+		int lastSpace = 0;
+		int lastValid = 0;
+		int linePtr = 0;
+		int nextLine = -1;
 		do {
-			width += screen.charWidth(*lineEndP);
-		} while (*++lineEndP && width < maxWidth);
+			lastValid = linePtr;
+			if (lineStartP[linePtr] == ' ')
+				lastSpace = linePtr;
+			width += screen.charWidth(lineStartP, linePtr);
+		} while (lineStartP[linePtr] && width < maxWidth);
 
 		// Check if we need to wrap the line
 		if (width >= maxWidth) {
-			// Work backwards to the prior word's end
-			while (*--lineEndP != ' ')
-				;
-
-			sLine = Common::String(lineStartP, lineEndP++);
+			if (lastSpace > 0) {
+				sLine = Common::String(lineStartP, lastSpace);
+				nextLine = lastSpace + 1;
+			} else {
+				sLine = Common::String(lineStartP, lastValid);
+				nextLine = lastValid;
+			}
 		} else {
 			// Can display remainder of the statement on the current line
 			sLine = Common::String(lineStartP);
@@ -872,10 +886,9 @@ int ScalpelTalk::talkLine(int lineNum, int stateNum, byte color, int lineY, bool
 
 			// Move to next line, if any
 			lineY += 9;
-			lineStartP = lineEndP;
-
-			if (!*lineEndP)
+			if (nextLine < 0)
 				break;
+			lineStartP += nextLine;
 		} else {
 			// We're close to the bottom of the screen, so stop display
 			lineY = -1;


Commit: 0b9e85f93ac617161ea8bf68d98e298620dd951e
    https://github.com/scummvm/scummvm/commit/0b9e85f93ac617161ea8bf68d98e298620dd951e
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2023-02-28T00:40:25+01:00

Commit Message:
SHERLOCK: Support Chinese object description wrapping for serrated scalpel

Changed paths:
    engines/sherlock/scalpel/scalpel_user_interface.cpp


diff --git a/engines/sherlock/scalpel/scalpel_user_interface.cpp b/engines/sherlock/scalpel/scalpel_user_interface.cpp
index 9e590b7808e..77be5dc1f60 100644
--- a/engines/sherlock/scalpel/scalpel_user_interface.cpp
+++ b/engines/sherlock/scalpel/scalpel_user_interface.cpp
@@ -1996,47 +1996,20 @@ void ScalpelUserInterface::printObjectDesc(const Common::String &str, bool first
 	events.clearEvents();
 
 	// Loop through displaying up to five lines
-	bool endOfStr = false;
-	const char *msgP = str.c_str();
-	for (int lineNum = 0; lineNum < ONSCREEN_FILES_COUNT && !endOfStr; ++lineNum) {
-		int width = 0;
-		const char *lineStartP = msgP;
-
-		// Determine how much can be displayed on the line
-		do {
-			width += screen.charWidth(*msgP++);
-		} while (width < 300 && *msgP);
-
-		if (*msgP)
-			--msgP;
-		else
-			endOfStr = true;
-
-		// If the line needs to be wrapped, scan backwards to find
-		// the end of the previous word as a splitting point
-		if (width >= 300) {
-			while (*msgP != ' ')
-				--msgP;
-			endOfStr = false;
-		}
-
-		// Print out the line
-		Common::String line(lineStartP, msgP);
+	Common::String remainder;
+	Common::Array<Common::String> lines = screen.wordWrap(str, 300, remainder, Common::String::npos, ONSCREEN_FILES_COUNT);
+	for (uint lineNum = 0; lineNum < lines.size(); ++lineNum) {
 		screen.gPrint(Common::Point(16, CONTROLS_Y + 12 + lineNum * 9),
-			INV_FOREGROUND, "%s", line.c_str());
-
-		if (!endOfStr)
-			// Start next line at start of the nxet word after space
-			++msgP;
+			INV_FOREGROUND, "%s", lines[lineNum].c_str());
 	}
 
 	// Handle display depending on whether all the message was shown
-	if (!endOfStr) {
+	if (!remainder.empty()) {
 		Common::String fixedText_PressKeyForMore = FIXED(PressKey_ForMore);
 
 		screen.makeButton(Common::Rect(46, CONTROLS_Y, 272, CONTROLS_Y + 10),
 			SHERLOCK_SCREEN_WIDTH / 2, fixedText_PressKeyForMore);
-		_descStr = msgP;
+		_descStr = remainder;
 	} else {
 		Common::String fixedText_PressKeyToContinue = FIXED(PressKey_ToContinue);
 


Commit: 3396f8426934ab4e0c566cbf8af7a30cddb63436
    https://github.com/scummvm/scummvm/commit/3396f8426934ab4e0c566cbf8af7a30cddb63436
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2023-02-28T00:40:25+01:00

Commit Message:
SHERLOCK: Increase line height in Chinese serrated scalpel

Changed paths:
    engines/sherlock/scalpel/scalpel_talk.cpp


diff --git a/engines/sherlock/scalpel/scalpel_talk.cpp b/engines/sherlock/scalpel/scalpel_talk.cpp
index a63b0eb6bce..58e407de9d0 100644
--- a/engines/sherlock/scalpel/scalpel_talk.cpp
+++ b/engines/sherlock/scalpel/scalpel_talk.cpp
@@ -289,7 +289,7 @@ void ScalpelTalk::talkInterface(const byte *&str) {
 	if (str[0] == ' ')
 		++str;
 
-	_yp += 9;
+	_yp += _vm->getLanguage() == Common::Language::ZH_TWN ? 16 : 9;
 	++_line;
 
 	// Certain different conditions require a wait
@@ -885,7 +885,7 @@ int ScalpelTalk::talkLine(int lineNum, int stateNum, byte color, int lineY, bool
 			}
 
 			// Move to next line, if any
-			lineY += 9;
+			lineY += _vm->getLanguage() == Common::Language::ZH_TWN ? 16 : 9;
 			if (nextLine < 0)
 				break;
 			lineStartP += nextLine;


Commit: 9329f7410a008d803e47ffedb8a3f340e20c0c72
    https://github.com/scummvm/scummvm/commit/9329f7410a008d803e47ffedb8a3f340e20c0c72
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2023-02-28T00:40:25+01:00

Commit Message:
SHERLOCK: Fix button positions in Chinese serrated scalpel

Changed paths:
    engines/sherlock/scalpel/scalpel_user_interface.cpp
    engines/sherlock/scalpel/scalpel_user_interface.h


diff --git a/engines/sherlock/scalpel/scalpel_user_interface.cpp b/engines/sherlock/scalpel/scalpel_user_interface.cpp
index 77be5dc1f60..454bd59dd0e 100644
--- a/engines/sherlock/scalpel/scalpel_user_interface.cpp
+++ b/engines/sherlock/scalpel/scalpel_user_interface.cpp
@@ -38,7 +38,7 @@ namespace Sherlock {
 namespace Scalpel {
 
 // Main user interface menu control locations
-const int MENU_POINTS[12][4] = {
+const int MENU_POINTS_INTL[12][4] = {
 	{ 13, 153, 72, 165 },
 	{ 13, 169, 72, 181 },
 	{ 13, 185, 72, 197 },
@@ -53,6 +53,21 @@ const int MENU_POINTS[12][4] = {
 	{ 249, 185, 305, 197 }
 };
 
+const int MENU_POINTS_ZH[12][4] = {
+	{   5, 158,  47, 172 },
+	{   5, 181,  47, 196 },
+	{  57, 158,  99, 172 },
+	{  57, 181,  99, 196 },
+	{ 109, 158, 151, 172 },
+	{ 109, 181, 151, 196 },
+	{ 160, 158, 202, 172 },
+	{ 160, 181, 202, 196 },
+	{ 212, 158, 254, 172 },
+	{ 212, 181, 254, 196 },
+	{ 265, 158, 307, 172 },
+	{ 265, 181, 307, 196 }
+};
+
 // Inventory control locations */
 const int INVENTORY_POINTS[8][3] = {
 	{ 4, 50, 29 },
@@ -69,6 +84,42 @@ const int UI_OFFSET_3DO = 16;	// (320 - 288) / 2
 
 /*----------------------------------------------------------------*/
 
+Common::Point ScalpelUserInterface::getTopLeftButtonPoint(int num) const {
+	Common::Point pt;
+	if (_vm->getLanguage() == Common::Language::ZH_TWN) {
+		pt = Common::Point(MENU_POINTS_ZH[num][0], MENU_POINTS_ZH[num][1]);
+	} else {
+		pt = Common::Point(MENU_POINTS_INTL[num][0], MENU_POINTS_INTL[num][1]);
+	}
+
+	if (IS_3DO) {
+		if (num >= 0 && num <= 2)
+			pt.x += 15;
+		else if (num >= 6 && num <= 8)
+			pt.x -= 4;
+		else if (num >= 9 && num <= 11)
+			pt.x -= 8;
+	}
+
+	return pt;
+}
+
+Common::Rect ScalpelUserInterface::getButtonRect(int buttonNr) const {
+	Common::Rect r;
+
+	if (_vm->getLanguage() == Common::Language::ZH_TWN) {
+		r = Common::Rect(MENU_POINTS_ZH[buttonNr][0], MENU_POINTS_ZH[buttonNr][1],
+				 MENU_POINTS_ZH[buttonNr][2], MENU_POINTS_ZH[buttonNr][3]);
+	} else {
+		r = Common::Rect(MENU_POINTS_INTL[buttonNr][0], MENU_POINTS_INTL[buttonNr][1],
+				 MENU_POINTS_INTL[buttonNr][2], MENU_POINTS_INTL[buttonNr][3]);
+	}
+	if (IS_3DO && buttonNr <= 2) {
+		r.left += UI_OFFSET_3DO - 1;
+		r.right += UI_OFFSET_3DO - 1;
+	}
+	return r;
+}
 
 ScalpelUserInterface::ScalpelUserInterface(SherlockEngine *vm): UserInterface(vm) {
 	if (_vm->_interactiveFl) {
@@ -424,8 +475,7 @@ void ScalpelUserInterface::handleInput() {
 
 void ScalpelUserInterface::depressButton(int num) {
 	Screen &screen = *_vm->_screen;
-	Common::Point pt(MENU_POINTS[num][0], MENU_POINTS[num][1]);
-	offsetButton3DO(pt, num);
+	Common::Point pt = getTopLeftButtonPoint(num);
 
 	ImageFrame &frame = (*_controls)[num];
 	screen._backBuffer1.SHtransBlitFrom(frame, pt);
@@ -435,8 +485,7 @@ void ScalpelUserInterface::depressButton(int num) {
 void ScalpelUserInterface::restoreButton(int num) {
 	Events &events = *_vm->_events;
 	Screen &screen = *_vm->_screen;
-	Common::Point pt(MENU_POINTS[num][0], MENU_POINTS[num][1]);
-	offsetButton3DO(pt, num);
+	Common::Point pt = getTopLeftButtonPoint(num);
 
 	Graphics::Surface &frame = (*_controls)[num]._frame;
 
@@ -489,8 +538,7 @@ void ScalpelUserInterface::toggleButton(uint16 num) {
 			_keyboardInput = false;
 
 			ImageFrame &frame = (*_controls)[num];
-			Common::Point pt(MENU_POINTS[num][0], MENU_POINTS[num][1]);
-			offsetButton3DO(pt, num);
+			Common::Point pt = getTopLeftButtonPoint(num);
 			screen._backBuffer1.SHtransBlitFrom(frame, pt);
 			screen.slamArea(pt.x, pt.y, pt.x + frame._width, pt.y + frame._height);
 		}
@@ -1273,8 +1321,7 @@ void ScalpelUserInterface::doLookControl() {
 		else if (!_invLookFlag) {
 			if (!_lookHelp) {
 				// Need to close the window and depress the Look button
-				Common::Point pt(MENU_POINTS[0][0], MENU_POINTS[0][1]);
-				offsetButton3DO(pt, 0);
+				Common::Point pt = getTopLeftButtonPoint(0);
 				screen._backBuffer2.SHblitFrom((*_controls)[0], pt);
 				banishWindow();
 
@@ -1334,12 +1381,7 @@ void ScalpelUserInterface::doMainControl() {
 
 		// Check whether the mouse is in any of the command areas
 		for (uint16 buttonNr = 0; buttonNr < 12; buttonNr++) {
-			Common::Rect r(MENU_POINTS[buttonNr][0], MENU_POINTS[buttonNr][1],
-				MENU_POINTS[buttonNr][2], MENU_POINTS[buttonNr][3]);
-			if (IS_3DO && buttonNr <= 2) {
-				r.left += UI_OFFSET_3DO - 1;
-				r.right += UI_OFFSET_3DO - 1;
-			}
+			Common::Rect r = getButtonRect(buttonNr);
 			if (r.contains(pt)) {
 				_temp = buttonNr;
 				pressedButtonId = buttonNr;
@@ -1923,8 +1965,7 @@ void ScalpelUserInterface::printObjectDesc(const Common::String &str, bool first
 				// the look button before we close the window. So save a copy of the
 				// menu area, and draw the controls onto it
 				Surface tempSurface((*_controls)[0]._frame.w, (*_controls)[0]._frame.h);
-				Common::Point pt(MENU_POINTS[0][0], MENU_POINTS[0][1]);
-				offsetButton3DO(pt, 0);
+				Common::Point pt = getTopLeftButtonPoint(0);
 
 				tempSurface.SHblitFrom(screen._backBuffer2, Common::Point(0, 0),
 					Common::Rect(pt.x, pt.y, pt.x + tempSurface.width(), pt.y + tempSurface.height()));
@@ -2267,17 +2308,6 @@ void ScalpelUserInterface::checkUseAction(const UseType *use, const Common::Stri
 	events.setCursor(ARROW);
 }
 
-void ScalpelUserInterface::offsetButton3DO(Common::Point &pt, int num) {
-	if (IS_3DO) {
-		if (num >= 0 && num <= 2)
-			pt.x += 15;
-		else if (num >= 6 && num <= 8)
-			pt.x -= 4;
-		else if (num >= 9 && num <= 11)
-			pt.x -= 8;
-	}
-}
-
 } // End of namespace Scalpel
 
 } // End of namespace Sherlock
diff --git a/engines/sherlock/scalpel/scalpel_user_interface.h b/engines/sherlock/scalpel/scalpel_user_interface.h
index d8d34d3fb25..f898fa8a9ce 100644
--- a/engines/sherlock/scalpel/scalpel_user_interface.h
+++ b/engines/sherlock/scalpel/scalpel_user_interface.h
@@ -197,7 +197,9 @@ public:
 	 */
 	void examine();
 
-	void offsetButton3DO(Common::Point &pt, int num);
+	Common::Point getTopLeftButtonPoint(int num) const;
+	Common::Rect getButtonRect(int buttonNr) const;
+
 public:
 	/**
 	 * Resets the user interface


Commit: b6efa850aa458bcc333cf352ee049e7c447daad0
    https://github.com/scummvm/scummvm/commit/b6efa850aa458bcc333cf352ee049e7c447daad0
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2023-02-28T00:40:25+01:00

Commit Message:
SHERLOCK: Increase line height for Chinese object description for serrated scalpel

Changed paths:
    engines/sherlock/scalpel/scalpel_user_interface.cpp


diff --git a/engines/sherlock/scalpel/scalpel_user_interface.cpp b/engines/sherlock/scalpel/scalpel_user_interface.cpp
index 454bd59dd0e..d70eff5fd84 100644
--- a/engines/sherlock/scalpel/scalpel_user_interface.cpp
+++ b/engines/sherlock/scalpel/scalpel_user_interface.cpp
@@ -2040,7 +2040,7 @@ void ScalpelUserInterface::printObjectDesc(const Common::String &str, bool first
 	Common::String remainder;
 	Common::Array<Common::String> lines = screen.wordWrap(str, 300, remainder, Common::String::npos, ONSCREEN_FILES_COUNT);
 	for (uint lineNum = 0; lineNum < lines.size(); ++lineNum) {
-		screen.gPrint(Common::Point(16, CONTROLS_Y + 12 + lineNum * 9),
+		screen.gPrint(Common::Point(16, CONTROLS_Y + 12 + lineNum * (_vm->getLanguage() == Common::Language::ZH_TWN ? 16 : 9)),
 			INV_FOREGROUND, "%s", lines[lineNum].c_str());
 	}
 


Commit: 4c782a176cc53c057c1b859a67f7c4aa4f85ca8f
    https://github.com/scummvm/scummvm/commit/4c782a176cc53c057c1b859a67f7c4aa4f85ca8f
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2023-02-28T00:40:25+01:00

Commit Message:
SHERLOCK: Minor infoline fix for Chinese serrated scalpel

Changed paths:
    engines/sherlock/scalpel/scalpel_user_interface.cpp
    engines/sherlock/scalpel/scalpel_user_interface.h


diff --git a/engines/sherlock/scalpel/scalpel_user_interface.cpp b/engines/sherlock/scalpel/scalpel_user_interface.cpp
index d70eff5fd84..60a0b4c231b 100644
--- a/engines/sherlock/scalpel/scalpel_user_interface.cpp
+++ b/engines/sherlock/scalpel/scalpel_user_interface.cpp
@@ -197,6 +197,14 @@ void ScalpelUserInterface::reset() {
 	_help = _oldHelp = -1;
 }
 
+int ScalpelUserInterface::infoLineHeight() const {
+	return _vm->getLanguage() == Common::Language::ZH_TWN ? 14 : 10;
+}
+
+int ScalpelUserInterface::infoLineYOffset() const {
+	return _vm->getLanguage() == Common::Language::ZH_TWN ? 0 : 1;
+}
+
 void ScalpelUserInterface::drawInterface(int bufferNum) {
 	Screen &screen = *_vm->_screen;
 
@@ -217,7 +225,7 @@ void ScalpelUserInterface::drawInterface(int bufferNum) {
 	}
 	if (bufferNum == 3)
 		screen._backBuffer2.SHfillRect(Common::Rect(0, INFO_LINE,
-			SHERLOCK_SCREEN_WIDTH, INFO_LINE + 10), INFO_BLACK);
+			SHERLOCK_SCREEN_WIDTH, INFO_LINE + infoLineHeight()), INFO_BLACK);
 }
 
 void ScalpelUserInterface::handleInput() {
@@ -310,7 +318,7 @@ void ScalpelUserInterface::handleInput() {
 
 					if (_help != -1 && !scene._bgShapes[_bgFound]._description.empty()
 							&& scene._bgShapes[_bgFound]._description[0] != ' ')
-						screen.print(Common::Point(0, INFO_LINE + 1),
+						screen.print(Common::Point(0, INFO_LINE + infoLineYOffset()),
 						INFO_FOREGROUND, "%s", scene._bgShapes[_bgFound]._description.c_str());
 
 					_oldBgFound = _bgFound;
@@ -551,8 +559,8 @@ void ScalpelUserInterface::toggleButton(uint16 num) {
 
 void ScalpelUserInterface::clearInfo() {
 	if (_infoFlag) {
-		_vm->_screen->vgaBar(Common::Rect(IS_3DO ? 33 : 16, INFO_LINE,
-			SHERLOCK_SCREEN_WIDTH - (IS_3DO ? 33 : 19), INFO_LINE + 10), INFO_BLACK);
+		_vm->_screen->vgaBar(Common::Rect(IS_3DO ? 33 : 16, INFO_LINE + infoLineYOffset() - 1,
+			SHERLOCK_SCREEN_WIDTH - (IS_3DO ? 33 : 19), INFO_LINE + infoLineHeight()), INFO_BLACK);
 		_infoFlag = false;
 		_oldLook = -1;
 	}
@@ -703,16 +711,16 @@ void ScalpelUserInterface::lookScreen(const Common::Point &pt) {
 						}
 
 						int xStart = (SHERLOCK_SCREEN_WIDTH - x) / 2;
-						screen.print(Common::Point(xStart, INFO_LINE + 1),
+						screen.print(Common::Point(xStart, INFO_LINE + infoLineYOffset()),
 							INFO_FOREGROUND, "%s", useText1.c_str());
 
 						if (_selector != -1) {
-							screen.print(Common::Point(xStart + width1, INFO_LINE + 1),
+							screen.print(Common::Point(xStart + width1, INFO_LINE + infoLineYOffset()),
 								TALK_FOREGROUND, "%s", useText2.c_str());
-							screen.print(Common::Point(xStart + width1 + width2, INFO_LINE + 1),
+							screen.print(Common::Point(xStart + width1 + width2, INFO_LINE + infoLineYOffset()),
 								INFO_FOREGROUND, "%s", useText3.c_str());
 						} else {
-							screen.print(Common::Point(xStart + width1, INFO_LINE + 1),
+							screen.print(Common::Point(xStart + width1, INFO_LINE + infoLineYOffset()),
 								INFO_FOREGROUND, "%s", useText3.c_str());
 						}
 					} else if (temp >= 0 && temp < 1000 && _selector != -1 &&
@@ -735,15 +743,15 @@ void ScalpelUserInterface::lookScreen(const Common::Point &pt) {
 						}
 
 						int xStart = (SHERLOCK_SCREEN_WIDTH - x) / 2;
-						screen.print(Common::Point(xStart, INFO_LINE + 1),
+						screen.print(Common::Point(xStart, INFO_LINE + infoLineYOffset()),
 							INFO_FOREGROUND, "%s", giveText1.c_str());
-						screen.print(Common::Point(xStart + width1, INFO_LINE + 1),
+						screen.print(Common::Point(xStart + width1, INFO_LINE + infoLineYOffset()),
 							TALK_FOREGROUND, "%s", giveText2.c_str());
-						screen.print(Common::Point(xStart + width1 + width2, INFO_LINE + 1),
+						screen.print(Common::Point(xStart + width1 + width2, INFO_LINE + infoLineYOffset()),
 							INFO_FOREGROUND, "%s", giveText3.c_str());
 					}
 				} else {
-					screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", tempStr.c_str());
+					screen.print(Common::Point(0, INFO_LINE + infoLineYOffset()), INFO_FOREGROUND, "%s", tempStr.c_str());
 				}
 
 				_infoFlag = true;
@@ -767,7 +775,7 @@ void ScalpelUserInterface::lookInv() {
 		if (temp < inv._holdings) {
 			if (temp < inv._holdings) {
 				clearInfo();
-				screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND,
+				screen.print(Common::Point(0, INFO_LINE + infoLineYOffset()), INFO_FOREGROUND,
 					"%s", inv[temp]._description.c_str());
 				_infoFlag = true;
 				_oldLook = temp;
@@ -2231,7 +2239,7 @@ void ScalpelUserInterface::checkUseAction(const UseType *use, const Common::Stri
 
 		// Display error message
 		_menuCounter = 30;
-		screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "You can't do that to yourself.");
+		screen.print(Common::Point(0, INFO_LINE + infoLineYOffset()), INFO_FOREGROUND, "You can't do that to yourself.");
 		return;
 	}
 
@@ -2283,7 +2291,7 @@ void ScalpelUserInterface::checkUseAction(const UseType *use, const Common::Stri
 			if (scene._goToScene != 1 && !printed && !talk._talkToAbort) {
 				_infoFlag = true;
 				clearInfo();
-				screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", FIXED(UserInterface_Done));
+				screen.print(Common::Point(0, INFO_LINE + infoLineYOffset()), INFO_FOREGROUND, "%s", FIXED(UserInterface_Done));
 				_menuCounter = 25;
 			}
 		}
@@ -2293,12 +2301,12 @@ void ScalpelUserInterface::checkUseAction(const UseType *use, const Common::Stri
 		clearInfo();
 
 		if (giveMode) {
-			screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", FIXED(UserInterface_NoThankYou));
+			screen.print(Common::Point(0, INFO_LINE + infoLineYOffset()), INFO_FOREGROUND, "%s", FIXED(UserInterface_NoThankYou));
 		} else if (fixedTextActionId == kFixedTextAction_Invalid) {
-			screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", FIXED(UserInterface_YouCantDoThat));
+			screen.print(Common::Point(0, INFO_LINE + infoLineYOffset()), INFO_FOREGROUND, "%s", FIXED(UserInterface_YouCantDoThat));
 		} else {
 			Common::String errorMessage = fixedText.getActionMessage(fixedTextActionId, 0);
-			screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", errorMessage.c_str());
+			screen.print(Common::Point(0, INFO_LINE + infoLineYOffset()), INFO_FOREGROUND, "%s", errorMessage.c_str());
 		}
 
 		_infoFlag = true;
diff --git a/engines/sherlock/scalpel/scalpel_user_interface.h b/engines/sherlock/scalpel/scalpel_user_interface.h
index f898fa8a9ce..5b00f4bf9b3 100644
--- a/engines/sherlock/scalpel/scalpel_user_interface.h
+++ b/engines/sherlock/scalpel/scalpel_user_interface.h
@@ -199,6 +199,8 @@ public:
 
 	Common::Point getTopLeftButtonPoint(int num) const;
 	Common::Rect getButtonRect(int buttonNr) const;
+	int infoLineHeight() const;
+	int infoLineYOffset() const;
 
 public:
 	/**


Commit: 24baae1ae0dfcc1cba3f82ee156819553049a5fc
    https://github.com/scummvm/scummvm/commit/24baae1ae0dfcc1cba3f82ee156819553049a5fc
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2023-02-28T00:40:25+01:00

Commit Message:
SHERLOCK: Add Chinese strings for Serrated Scalpel

Changed paths:
    engines/sherlock/fixed_text.cpp
    engines/sherlock/scalpel/scalpel_fixed_text.cpp


diff --git a/engines/sherlock/fixed_text.cpp b/engines/sherlock/fixed_text.cpp
index 53dbfdb58c8..c184ebfa588 100644
--- a/engines/sherlock/fixed_text.cpp
+++ b/engines/sherlock/fixed_text.cpp
@@ -168,6 +168,37 @@ static const char *const fixedJournalTextES[] = {
 	"Despu\202s %s dijo, "
 };
 
+static const char *const fixedJournalTextZHBig5[] = {
+	// Holmes asked/said...
+	"\xba\xd6\xba\xb8\xbc\xaf\xb4\xb5\xb0\xdd\xa7\xda\x3a\x22", /* "福爾摩斯問我:"; "Holmes asked me, " */
+	"\xba\xd6\xba\xb8\xbc\xaf\xb4\xb5\xb0\xdd\xb1\xb4\xaa\xf8\x3a\x22", /* "福爾摩斯問探長:"; "Holmes asked the Inspector, " */
+	"\xba\xd6\xba\xb8\xbc\xaf\xb4\xb5\xb0\xdd%s\x3a\x22", /* "福爾摩斯問%s:"; "Holmes asked %s, " */
+	"\xba\xd6\xba\xb8\xbc\xaf\xb4\xb5\xbb\xa1\xa7\xda\x3a\x22", /* "福爾摩斯說我:"; "Holmes said to me, " */
+	"\xba\xd6\xba\xb8\xbc\xaf\xb4\xb5\xbb\xa1\xb1\xb4\xaa\xf8\x3a\x22", /* "福爾摩斯說探長:"; "Holmes said to the Inspector, " */
+	"\xba\xd6\xba\xb8\xbc\xaf\xb4\xb5\xbb\xa1%s\x3a\x22", /* "福爾摩斯說%s:"; "Holmes said to %s, " */
+	// I asked/said...
+	"\xa7\xda\xa6\x5e\xb5\xaa\x3a\x22", /* "我回答:""; "I replied, " */
+	"\xa6\x5e\xb5\xaa\xbb\xa1\x3a\x22", /* "回答說:""; "The reply was, " */
+	// Holmes/I/The Inspector/Person asked/said (without "Then" prefix)
+	"Holmes asked, ", // TODO
+	"\xba\xd6\xba\xb8\xbc\xaf\xb4\xb5\xbb\xa1\x3a", /* "福爾摩斯說:"; "Holmes said, " */
+	"I asked, ", // TODO
+	"I said, ", // TODO
+	"The Inspector asked, ", // TODO
+	"The Inspector said, ", // TODO
+	"%s asked, ", // TODO
+	"\x25\x73\xbb\xa1\x3a", /* "%s說:"; "%s said, " */
+	// Then Holmes/I/The Inspector/Person asked/said
+	"Then Holmes asked, ", // TODO
+	"Then Holmes said, ", // TODO
+	"Then I asked, ", // TODO
+	"Then I said, ", // TODO
+	"Then the Inspector asked, ", // TODO
+	"Then the Inspector said, ", // TODO
+	"Then %s asked, ", // TODO
+	"Then %s said, " // TODO
+};
+
 FixedText::FixedText(SherlockEngine *vm)  {
 	_vm = vm;
 
@@ -195,6 +226,11 @@ FixedText::FixedText(SherlockEngine *vm)  {
 		_fixedJournalTextArray = fixedJournalTextES;
 		_fixedObjectPickedUpText = "Cogido/a %s";
 		break;
+	case Common::ZH_TWN:
+		// Used by Sherlock Holmes 1+2
+		_fixedJournalTextArray = fixedJournalTextZHBig5;
+		_fixedObjectPickedUpText = "Picked up %s"; // TODO
+		break;
 	default:
 		// Default to English
 		_fixedJournalTextArray = fixedJournalTextEN;
diff --git a/engines/sherlock/scalpel/scalpel_fixed_text.cpp b/engines/sherlock/scalpel/scalpel_fixed_text.cpp
index 398962bf3cc..9eb7be78eaf 100644
--- a/engines/sherlock/scalpel/scalpel_fixed_text.cpp
+++ b/engines/sherlock/scalpel/scalpel_fixed_text.cpp
@@ -481,6 +481,160 @@ static const char *const fixedTextES[] = {
 	"El agente Dugan"
 };
 
+static const char *const fixedTextZH[] = {
+	// Game hotkeys
+	"LMTPOCIUGJFS",
+	// SH1: Window buttons
+	"E\xc2\xf7\xb6\x7d(E)", /* "E離開"; "EExit" */
+	"U\xa4\x57(U)", /* "U上"; "UUp" */
+	"D\xa4\x55(D)", /* "D下"; "DDown" */
+	// SH1: Inventory buttons
+	"E\xc2\xf7\xb6\x7d(E)", /* "E離開"; "EExit" */
+	"L\xac\x64\xac\xdd(L)", /* "L查看"; "LLook" */
+	"U\xa8\xcf\xa5\xce(U)", /* "U使用"; "UUse" */
+	"G\xb5\xb9\xbb\x50(G)", /* "G給與"; "GGive" */
+	// TODO: Inventorty next/prev buttons:
+	//"\xa5\xaa\xad\xb6", /* "左頁"; */
+	//"\xa5\xaa\xa4\x40", /* "左一"; */
+	//"\xa5\x6b\xa4\x40", /* "右一"; */
+	//"\xa5\x6b\xad\xb6", /* "右頁"; */
+	// SH1: Journal text
+	"\xb5\xd8\xa5\xcd\xaa\xba\xb5\xa7\xb0\x4f", /* "華生的筆記"; "Watson's Journal" */
+	"\xb2\xc4\x25\x64\xad\xb6", /* "第%d頁"; "Page %d" */
+	// SH1: Journal buttons
+	"E\xc2\xf7\xb6\x7d(E)", /* "E離開"; "EExit" */
+	"B\xab\x65\xa4\x51\xad\xb6(B)", /* "B前十頁"; "BBack 10" */
+	"U\xa4\x57(U)", /* "U上"; "UUp" */
+	"D\xa4\x55(D)", /* "D下"; "DDown" */
+	"A\xab\xe1\xa4\x51\xad\xb6(A)", /* "A後十頁"; "AAhead 10" */
+	"S\xb4\x4d\xa7\xe4(S)", /* "S尋找"; "SSearch" */
+	"F\xad\xba\xad\xb6(F)", /* "F首頁"; "FFirst Page" */
+	"L\xa9\xb3\xad\xb6(L)", /* "L底頁"; "LLast Page" */
+	"P\xa6\x43\xa6\x4c(P)", /* "列印"; "PPrint Text" */
+	// SH1: Journal search
+	"\xc2\xf7\xb6\x7d", /* "E離開"; "Exit" */
+	"\xab\x65\xb4\x4d", /* "前尋"; "Backward" */
+	"\xab\xe1\xb4\x4d", /* "後尋"; "Forward" */
+	"\xa8\x53\xa6\xb3\xa7\xe4\xa8\xec\x21", /* "沒有找到!"; "Text Not Found !" */
+	// SH1: Settings
+	"E\xc2\xf7\xb6\x7d(E)", /* "離開"; "EExit" */
+	"M\xad\xb5\xbc\xd6\xb6\x7d(M)", /* "M音樂開"; "MMusic on" */
+	"M\xad\xb5\xbc\xd6\xc3\xf6(M)", /* "M音樂關"; "MMusic off" */	
+	"P\xa8\x76\xb9\xb3\xb6\x7d(P)", /* "P肖像開"; "PPortrait on" */
+	"P\xa8\x76\xb9\xb3\xc3\xf6(P)", /* "P肖像關"; "PPortrait off" */
+	"JJoystick off", // Not used in Chinese as this button is skipped
+	"NNew Font Style", // Not used in Chinese as only one font is available
+	"S\xad\xb5\xae\xc4\xb6\x7d(S)", /* "S音效開"; "SSound Effects on" */
+	"S\xad\xb5\xae\xc4\xc3\xf6(S)", /* "S音效關"; "SSound Effects off" */	
+	"W\xb5\xf8\xb5\xa1\xb7\xc6\xb1\xb2(W)", /* "W視窗滑捲"; "WWindow Slide Scroll" */
+	"W\xb5\xf8\xb5\xa1\xa8\x71\xa5\x58(W)", /* "W視窗秀出"; "WWindow Show" */
+	"C\xbd\xd5\xbe\xe3\xb7\x6e\xb1\xec(C)", /* "調整搖桿"; "CCalibrate Joystick" */
+	"A\xbb\xb2\xa7\x55\xa5\xaa(A)", /* "A輔助左"; "AAuto Help left" */
+	"A\xbb\xb2\xa7\x55\xa5\x6b(A)", /* "A輔助右"; "AAuto Help right" */
+	"VVoices on", // Not used in Chinese as no voices are available
+	"VVoices off", // Not used in Chinese as no voices are available
+	"F\xb2\x48\xa5\x58\xc2\x49\xaa\xac(F)", /* "F淡出點狀"; "FFade by Pixel" */
+	"F\xb2\x48\xa5\x58\xaa\xbd\xb1\xb5(F)", /* "F淡出直接"; "FFade Directly" */	
+	"K\xc1\xe4\xaa\xa9\xba\x43(K)", /* "K鍵版慢"; "KKey Pad Slow" */
+	"K\xc1\xe4\xaa\xa9\xa7\xd6(K)", /* "K鍵版快"; "KKey Pad Fast" */
+	// Load/Save
+	"EExit", // TODO
+	"L\xb8\xfc\xa4\x4a(L)", /* "L載入"; "LLoad" */
+	"S\xc0\x78\xa6\x73(S)", /* "S儲存"; "SSave" */
+	"U\xa4\x57(U)", /* "U上"; "UUp" */
+	"D\xa4\x55(D)", /* "D下"; "DDown" */
+	"Q\xb5\xb2\xa7\xf4(Q)", /* "Q結束"; "QQuit" */
+	// Quit Game
+	"\xb1\x7a\xbd\x54\xa9\x77\xad\x6e\xb5\xb2\xa7\xf4\xb9\x43\xc0\xb8\xb6\xdc\x3f", /* "您確定要結束遊戲嗎?"; "Are you sure you wish to Quit ?" */
+	"Y\xac\x4f(Y)", /* "Y是"; "YYes" */
+	"N\xa4\xa3(N)", /* "N不"; "NNo" */
+	// SH1: Press key text
+	"P\xbd\xd0\xab\xf6\xa5\xf4\xb7\x4e\xc1\xe4\xc4\x7e\xc4\xf2\xa4\x55\xad\xb6\xa4\xba\xae\x65.(P)", /* "P請按任意鍵繼續下頁內容."; "PPress any Key for More." */
+	"P\xbd\xd0\xab\xf6\xa5\xf4\xb7\x4e\xc1\xe4\xc4\x7e\xc4\xf2.(P)", /* "P請按任意鍵繼續."; "PPress any Key to Continue." */
+	// SH1: Initial Inventory
+	"\xab\x4b\xb1\xf8\xaf\xc8", /* "便條紙"; "A message requesting help" */
+	"\xba\xd6\xba\xb8\xbc\xaf\xb4\xb5\xaa\xba\xa6\x57\xa4\xf9", /* "福爾摩斯的名片"; "A number of business cards" */
+	"\xba\x71\xbc\x40\xb0\x7c\xc1\x70\xb2\xbc", /* "歌劇院聯票"; "Opera Tickets" */
+	"\xb3\x53\xb3\xa7", /* "袖釦"; "Cuff Link" */
+	"\xc5\x4b\xb5\xb7\xa4\xc4", /* "鐵絲勾"; "Wire Hook" */
+	"\xa9\xf1\xa6\xe6\xb1\xf8", /* "放行條"; "Note" */
+	"\xa5\xb4\xb6\x7d\xaa\xba\xc3\x68\xbf\xf6", /* "打開的懷錶"; "An open pocket watch" */
+	"\xaf\xc8", /* "ç´™"; "A piece of paper with numbers on it" */
+	"\xab\x48", /* "ä¿¡"; "A letter folded many times" */
+	"\xaf\xc8\xb5\x50", /* "紙牌"; "Tarot Cards" */
+	"\xb5\xd8\xc4\x52\xaa\xba\xc6\x5f\xb0\xcd", /* "華麗的鑰匙"; "An ornate key" */
+	"\xb7\xed\xb2\xbc", /* "當票"; "A pawn ticket" */
+	// SH1: User Interface
+	"\xa4\xa3\x2c\xc1\xc2\xc1\xc2\xb1\x7a\x2e", /* "不,謝謝您."; "No, thank you." */
+	"You can't do that.", // TODO
+	"\xa7\xb9\xb2\xa6\x2e\x2e\x2e", /* "完畢..."; "Done..." */
+	"Use ", // TODO
+	" on %s", // TODO
+	"Give ", // TODO
+	" to %s", // TODO
+	// SH1: People names
+	"\xba\xd6\xba\xb8\xbc\xaf\xb4\xb5", /* "福爾摩斯"; "Sherlock Holmes" */
+	"\xb5\xd8\xa5\xcd\xc2\xe5\xa5\xcd", /* "華生醫生"; "Dr. Watson" */
+	"\xb5\xdc\xb4\xb5\xb1\x5a\xbc\x77\xb1\xb4\xaa\xf8", /* "萊斯崔德探長"; "Inspector Lestrade" */
+	"\xb6\xf8\xa5\xac\xb5\xdc\xa6\x77\xa8\xb5\xa6\xf5", /* "奧布萊安巡佐"; "Constable O'Brien" */
+	"\xb9\x70\xba\xfb\xb4\xb5\xa8\xb5\xa6\xf5", /* "雷維斯巡佐"; "Constable Lewis" */
+	"\xdf\xc4\xdb\x69\x2e\xa9\xac\xa7\x4a", /* "葸菈.帕克"; "Sheila Parker" */
+	"\xa6\xeb\xa7\x51\x2e\xa5\x64\xcd\xba\xb7\xe6", /* "亨利.卡芮瑟"; "Henry Carruthers" */
+	"\xb5\xdc\xb5\xb7\xb2\xfa", /* "萊絲莉"; "Lesley" */
+	"\xa4\xde\xae\x79\xad\xfb", /* "引座員"; "An Usher" */
+	"\xa5\xb1\xb7\xe7\xbc\x77\x2e\xa6\xe3\xa7\x42\xb4\xb5\xa5\xc5", /* "弗瑞德.艾伯斯汀"; "Fredrick Epstein" */
+	"\xb4\xec\xa8\xaf\xb9\x79\xa4\xd3\xa4\xd3", /* "渥辛頓太太"; "Mrs. Worthington" */
+	"\xb1\xd0\xbd\x6d", /* "教練"; "The Coach" */
+	"\xa4\x40\xa6\x57\xb6\xa4\xad\xfb", /* "一名隊員"; "A Player" */
+	"\xb4\xa3\xa9\x69", /* "提姆"; "Tim", */
+	"\xa9\x69\xa4\x68\x2e\xae\xe1\xbc\x77\xb4\xb5", /* "姆士.桑德斯"; "James Sanders" */
+	"\xa8\xa9\xb2\xfa", /* "貝莉"; "Belle" */
+	"\xb2\x4d\xbc\xe4\xa4\x6b\xa4\x75", /* "清潔女工"; "Cleaning Girl" */
+	"\xc3\x51\xaa\xf7\xb4\xb5", /* "魏金斯"; "Wiggins" */
+	"\xab\x4f\xc3\xb9", /* "保羅"; "Paul" */
+	"\xb0\x73\xab\x4f", /* "酒保"; "The Bartender" */
+	"\xa4\x40\xad\xd3\xbb\xea\xc5\xbc\xaa\xba\xb0\x73\xb0\xad", /* "一個骯髒的酒鬼"; "A Dirty Drunk" */
+	"\xa4\x40\xad\xd3\xa4\x6a\xc1\x6e\xbb\xa1\xb8\xdc\xaa\xba\xb0\x73\xb0\xad", /* "一個大聲說話的酒鬼"; "A Shouting Drunk" */
+	"\xa4\x40\xad\xd3\xa8\xab\xb8\xf4\xb7\x6e\xb7\x45\xaa\xba\xb0\x73\xb0\xad", /* "一個走路搖幌的酒鬼"; "A Staggering Drunk" */
+	"\xab\x4f\xc3\xf0", /* "保鏢"; "The Bouncer" */
+	"\xc5\xe7\xab\xcd\xa9\x78", /* "驗屍官"; "The Coroner" */
+	"\xc3\x4d\xa4\x68\xaa\x41\xa9\xb1\xaa\xba\xb9\xd9\xad\x70", /* "騎士服店的夥計"; "Reginald Snipes" lit. "The clerk of the knight clothing store" */
+	"\xb3\xec\xaa\x76\x2e\xa5\xac\xb5\xdc\xa7\x4a\xa5\xee", /* "喬治.布萊克伍"; "George Blackwood" */
+	"\xbf\xe0\xa6\xd5\xb4\xb5", /* "賴耳斯"; "Lars" */
+	"\xc3\xc4\xa9\xd0\xa6\xd1\xaa\x4f", /* "藥房老板"; "The Chemist" lit "Pharmacy owner" */
+	"\xb8\xaf\xb7\xe7\xb4\xcb\xb1\xb4\xaa\xf8", /* "葛瑞森探長"; "Inspector Gregson" */
+	"\xb8\xeb\xa5\x69\xa7\x42\x2e\xaa\x6b\xa8\xaf\xb9\x79", /* "賈可伯.法辛頓"; "Jacob Farthington" */
+	"\xb3\xc1\xa6\xd2\xa4\xd2", /* "麥考夫"; "Mycroft" */
+	"\xa6\xd1\xb3\xb7\xb0\xd2", /* "老雪曼"; "Old Sherman" */
+	"\xb2\x7a\xac\x64", /* "理查"; "Richard" */
+	"\xbd\xd5\xb0\x73\xae\x76", /* "調酒師"; "The Barman" */
+	"\xa4\x40\xad\xd3\xa4\x40\xac\x79\xaa\xba\xaa\xb1\xaa\xcc", /* "一個一流的玩者"; "A Dandy Player" */
+	"\xa4\x40\xad\xd3\xa4\x54\xac\x79\xaa\xba\xaa\xb1\xaa\xcc", /* "一個三流的玩者"; "A Rough-looking Player" lit "A third-rate player" */
+	"\xae\xc7\xc6\x5b\xaa\xcc", /* "旁觀者"; "A Spectator" */
+	"\xc3\xb9\xa7\x42\x2e\xba\x7e\xaf\x53", /* "羅伯.漢特"; "Robert Hunt" */
+	"Violet", // TODO, Maybe "\xcb\xa2\xb5\xdc\xaf\x53", /* "芃萊特" */
+	"\xa8\xa9\xab\xd2\xae\xe6\xbe\x7c", /* "貝帝格魯"; "Pettigrew" */
+	"\xb6\xf8\xa6\x4e", /* "奧吉"; "Augie" */
+	"\xa6\x77\xae\x52\x2e\xa5\x64\xac\xa5\xc1\xa8", /* "安娜.卡洛薇"; "Anna Carroway" */
+	"\xc4\xb5\xbd\xc3", /* "警衛"; "A Guard" */
+	"\xa6\x77\xaa\x46\xa5\xa7\xb6\xf8\x2e\xa5\x64", /* "安東尼奧.卡"; "Antonio Caruso" */
+	"\xa6\xab\xa4\xf1", /* "托比"; "Toby the Dog" lit "Toby" */
+	"\xa6\xe8\xbb\x58\x2e\xaa\xf7\xb4\xb5\xb5\xdc", /* "西蒙.金斯萊"; "Simon Kingsley" */
+	"\xa8\xc8\xa6\xf2\xa6\x43\xbc\x77", /* "亞佛列德"; "Alfred" */
+	"\xa5\xac\xaa\xf9\xab\xc2\xba\xb8\xa4\xd2\xa4\x48", /* "布門威爾夫人"; "Lady Brumwell" */
+	"\xc3\xb9\xb2\xef\xa4\xd2\xa4\x48", /* "羅莎夫人"; "Madame Rosa" */
+	"\xac\xf9\xb7\xe6\x2e\xbc\xaf\xba\xb8\xae\xfc\xbc\x77", /* "約瑟.摩爾海德"; "Joseph Moorehead" */
+	"\xb2\xa6\xba\xb8\xa4\xd3\xa4\xd3", /* "畢爾太太"; "Mrs. Beale" */
+	"\xb5\xe1\xa7\x51\xa7\x4a\xb4\xb5", /* "菲利克斯"; "Felix" */
+	"\xb2\xfc\xc6\x46\xb9\x79", /* "荷靈頓"; "Hollingston" */
+	"\xa5\x64\xb5\xdc\xba\x7e\xa8\xb5\xa6\xf5", /* "卡萊漢巡佐"; "Constable Callaghan" */
+	"\xbe\x48\xaa\xd6\xa8\xb5\xa6\xf5", /* "鄧肯巡佐"; "Sergeant Duncan" */
+	"\xa5\xac\xaa\xf9\xab\xc2\xba\xb8\xc0\xef\xa4\x68", /* "布門威爾爵士"; "Lord Brumwell" */
+	"\xa5\xa7\xae\xe6\x2e\xb3\xc7\xa9\x69\xb4\xcb", /* "尼格.傑姆森"; "Nigel Jaimeson" */
+	"\xc1\xe9\xaf\xc7\xb4\xb5\x2e\xb7\xe7\xa7\x4a", /* "鍾納斯.瑞克"; "Jonas" */
+	"\xbe\x48\xae\xda\xa8\xb5\xa6\xf5" /* "鄧根巡佐"; "Constable Dugan" */
+};
+
 // =========================================
 
 // === Sherlock Holmes 1: Serrated Scalpel ===
@@ -511,6 +665,15 @@ static const char *const fixedTextES_ActionOpen[] = {
 	"."
 };
 
+static const char *const fixedTextZH_ActionOpen[] = {
+	"\xb3\x6f\xb5\x4c\xaa\x6b\xa5\xb4\xb6\x7d\xaa\xba", /* "這無法打開的"; "This cannot be opened" */
+	"\xa5\xa6\xa4\x77\xb8\x67\xa5\xb4\xb6\x7d\xa4\x46", /* "它已經打開了"; "It is already open" */
+	"\xa5\xa6\xb3\x51\xc2\xea\xa6\xed\xa4\x46", /* "它被鎖住了"; "It is locked" */
+	"\xb5\xa5\xab\xdd\xb5\xd8\xa5\xcd", /* "等待華生"; "Wait for Watson" */
+	" ",
+	"."
+};
+
 static const char *const fixedTextEN_ActionClose[] = {
 	"This cannot be closed",
 	"It is already closed",
@@ -529,6 +692,12 @@ static const char *const fixedTextES_ActionClose[] = {
 	"La puerta de seguridad esta entre medias"
 };
 
+static const char *const fixedTextZH_ActionClose[] = {
+	"\xb3\x6f\xb5\x4c\xaa\x6b\xc3\xf6\xa6\xed\xaa\xba", /* "這無法關住的"; "This cannot be closed" */
+	"\xa5\xa6\xa4\x77\xb8\x67\xc3\xf6\xb0\x5f\xa8\xd3\xa4\x46", /* "它已經關起來了"; "It is already closed" */
+	"The safe door is in the way", // TODO
+};
+
 static const char *const fixedTextEN_ActionMove[] = {
 	"This cannot be moved",
 	"It is bolted to the floor",
@@ -551,6 +720,13 @@ static const char *const fixedTextES_ActionMove[] = {
 	"El otro cajon esta en mitad"
 };
 
+static const char *const fixedTextZH_ActionMove[] = {
+	"\xb3\x6f\xb5\x4c\xaa\x6b\xb2\xbe\xb0\xca\xaa\xba", /* "這無法移動的"; "This cannot be moved" */
+	"It is bolted to the floor",  // TODO
+	"\xb3\x6f\xaa\x46\xa6\xe8\xa4\xd3\xad\xab\xa4\x46", /* "這東西太重了"; "It is too heavy" */
+	"\xb3\x51\xa8\xe4\xa5\xa6\xaa\xba\xa4\xec\xbd\x63\xbe\xd7\xa6\xed\xb8\xf4\xa4\x46", /* "被其它的木箱擋住路了"; "The other crate is in the way" */
+};
+
 static const char *const fixedTextEN_ActionPick[] = {
 	"Nothing of interest here",
 	"It is bolted down",
@@ -587,6 +763,18 @@ static const char *const fixedTextES_ActionPick[] = {
 	"Propiedad del gobierno para uso oficial"
 };
 
+static const char *const fixedTextZH_ActionPick[] = {
+	"\xa8\x53\xa6\xb3\xa4\xb0\xbb\xf2\xa5\x69\xad\xc8\xb1\x6f\xae\xb3", /* "沒有什麼可值得拿"; "Nothing of interest here"  */
+	"It is bolted down",  // TODO
+	"It is too big to carry",  // TODO
+	"\xa8\xba\xa4\xd3\xad\xab\xa4\x46", /* "那太重了"; "It is too heavy" */
+	"I think a girl would be more your type",  // TODO
+	"Those flowers belong to Penny",  // TODO
+	"She's far too young for you!",  // TODO
+	"I think a girl would be more your type!",  // TODO
+	"\xac\x46\xa9\xb2\xa9\xd2\xa6\xb3\x2c\xb6\xc8\xaf\xe0\xa8\xd1\xa9\x78\xa4\xe8\xa8\xcf\xa5\xce" /* "政府所有,僅能供官方使用"; "Government property for official use only" */
+};
+
 static const char *const fixedTextEN_ActionUse[] = {
 	"You can't do that",
 	"It had no effect",
@@ -611,6 +799,15 @@ static const char *const fixedTextES_ActionUse[] = {
 	"Las puertas no fuman"
 };
 
+
+static const char *const fixedTextZH_ActionUse[] = {
+	"\xb1\x7a\xb5\x4c\xaa\x6b\xa8\xba\xbc\xcb\xa8\xcf\xa5\xce", /* "您無法那樣使用"; "You can't do that" */
+	"\xa5\xa6\xac\x4f\xa8\x53\xa6\xb3\xae\xc4\xaa\x47\xaa\xba", /* "它是沒有效果的"; "It had no effect" */
+	"\xb1\x7a\xb5\x4c\xaa\x6b\xa8\xec\xb9\x46\xa8\xba\xc3\xe4", /* "您無法到達那邊"; "You can't reach it" */
+	"\xa6\x6e\xa4\x46\x21\xaa\xf9\xa4\x77\xb8\x67\xb6\x7d\xa4\x46\x2c\xb0\xaa\xbf\xb3\xb6\xdc\x3f", /* "好了!門已經開了,高興嗎?"; "OK, the door looks bigger! Happy?" */
+	"\xaa\xf9\xb5\x4c\xaa\x6b\xa9\xe2\xb7\xcf" /* "門無法抽煙"; "Doors don't smoke" */
+};
+
 #define FIXEDTEXT_GETCOUNT(_name_) sizeof(_name_) / sizeof(byte *)
 #define FIXEDTEXT_ENTRY(_name_)    _name_, FIXEDTEXT_GETCOUNT(_name_)
 
@@ -638,12 +835,21 @@ static const FixedTextActionEntry fixedTextES_Actions[] = {
 	{ FIXEDTEXT_ENTRY(fixedTextES_ActionUse) }
 };
 
+static const FixedTextActionEntry fixedTextZH_Actions[] = {
+	{ FIXEDTEXT_ENTRY(fixedTextZH_ActionOpen) },
+	{ FIXEDTEXT_ENTRY(fixedTextZH_ActionClose) },
+	{ FIXEDTEXT_ENTRY(fixedTextZH_ActionMove) },
+	{ FIXEDTEXT_ENTRY(fixedTextZH_ActionPick) },
+	{ FIXEDTEXT_ENTRY(fixedTextZH_ActionUse) }
+};
+
 // =========================================
 
 static const FixedTextLanguageEntry fixedTextLanguages[] = {
 	{ Common::DE_DEU,   fixedTextDE, fixedTextDE_Actions },
 	{ Common::ES_ESP,   fixedTextES, fixedTextES_Actions },
 	{ Common::EN_ANY,   fixedTextEN, fixedTextEN_Actions },
+	{ Common::ZH_TWN,   fixedTextZH, fixedTextZH_Actions },
 	{ Common::UNK_LANG, fixedTextEN, fixedTextEN_Actions }
 };
 


Commit: 8486739c00750d8bb3346a5135692ed560b1d9bf
    https://github.com/scummvm/scummvm/commit/8486739c00750d8bb3346a5135692ed560b1d9bf
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2023-02-28T00:40:25+01:00

Commit Message:
SHERLOCK: Rearrange buttons in Chinese serrated scalpel

Changed paths:
    engines/sherlock/scalpel/scalpel_screen.cpp
    engines/sherlock/scalpel/scalpel_screen.h
    engines/sherlock/scalpel/settings.cpp
    engines/sherlock/scalpel/settings.h


diff --git a/engines/sherlock/scalpel/scalpel_screen.cpp b/engines/sherlock/scalpel/scalpel_screen.cpp
index ecdfd8338c1..c05deade992 100644
--- a/engines/sherlock/scalpel/scalpel_screen.cpp
+++ b/engines/sherlock/scalpel/scalpel_screen.cpp
@@ -32,7 +32,7 @@ ScalpelScreen::ScalpelScreen(SherlockEngine *vm) : Screen(vm) {
 	activateBackBuffer1();
 }
 
-void ScalpelScreen::makeButton(const Common::Rect &bounds, int textX,
+void ScalpelScreen::makeButton(const Common::Rect &bounds, const Common::Point &textPoint,
 		const Common::String &buttonText, bool textContainsHotkey) {
 
 	Surface &bb = _backBuffer;
@@ -42,7 +42,12 @@ void ScalpelScreen::makeButton(const Common::Rect &bounds, int textX,
 	bb.fillRect(Common::Rect(bounds.left + 1, bounds.bottom - 1, bounds.right, bounds.bottom), BUTTON_BOTTOM);
 	bb.fillRect(Common::Rect(bounds.left + 1, bounds.top + 1, bounds.right - 1, bounds.bottom - 1), BUTTON_MIDDLE);
 
-	buttonPrint(Common::Point(textX, bounds.top), COMMAND_FOREGROUND, false, buttonText, textContainsHotkey);
+	buttonPrint(textPoint, COMMAND_FOREGROUND, false, buttonText, textContainsHotkey);
+}
+
+void ScalpelScreen::makeButton(const Common::Rect &bounds, int textX,
+		const Common::String &buttonText, bool textContainsHotkey) {
+	makeButton(bounds, Common::Point(textX, bounds.top), buttonText, textContainsHotkey);
 }
 
 // ButtonText is supposed to have its hotkey as a prefix. The hotkey will get highlighted.
diff --git a/engines/sherlock/scalpel/scalpel_screen.h b/engines/sherlock/scalpel/scalpel_screen.h
index 32db55361a8..3a05656f9dc 100644
--- a/engines/sherlock/scalpel/scalpel_screen.h
+++ b/engines/sherlock/scalpel/scalpel_screen.h
@@ -39,6 +39,7 @@ public:
 	 * Draws a button for use in the inventory, talk, and examine dialogs.
 	 * ButtonText is supposed to have its hotkey as a prefix. The hotkey will get highlighted.
 	 */
+	void makeButton(const Common::Rect &bounds, const Common::Point &textPoint, const Common::String &buttonText, bool textContainsHotkey = true);
 	void makeButton(const Common::Rect &bounds, int textX, const Common::String &buttonText, bool textContainsHotkey = true);
 
 	/**
diff --git a/engines/sherlock/scalpel/settings.cpp b/engines/sherlock/scalpel/settings.cpp
index a721cf9ec61..21ef5c14142 100644
--- a/engines/sherlock/scalpel/settings.cpp
+++ b/engines/sherlock/scalpel/settings.cpp
@@ -30,7 +30,7 @@ namespace Sherlock {
 
 namespace Scalpel {
 
-static const int SETUP_POINTS[12][4]  = {
+static const int SETUP_POINTS_INTL[12][4]  = {
 	{ 4, 154, 101, 53 },		// Exit
 	{ 4, 165, 101, 53 },		// Music Toggle
 	{ 219, 165, 316, 268 },		// Voice Toggle
@@ -45,8 +45,65 @@ static const int SETUP_POINTS[12][4]  = {
 	{ 219, 187, 316, 268 }		// _key Pad Accel. Toggle
 };
 
+// Different from original to accomodate hotkeys
+static const int SETUP_POINTS_ZH[12][4]  = {
+	{ 3, 159, 73, 38 },		// Exit // OK
+	{ 3, 178, 73, 38 },		// Music Toggle // OK
+	{ 0, 0, 0, 0 },		// Voice Toggle
+	{ 74, 178, 145, 109 },		// Sound Effects Toggle // OK
+	{ 146, 178, 233, 191 },		// Help Button Left/Right // OK
+	{ 0, 0, 0, 0 },		// New Font Style // OK
+	{ 0, 0, 0, 0 },		// Joystick Toggle // OK
+	{ 0, 0, 0, 0 },		// Calibrate Joystick // OK
+	{ 234, 159, 317, 276 },		// Fade Style // OK
+	{ 146, 159, 233, 191 },		// Window Open Style // OK
+	{ 74, 159, 145, 109 }, 		// Portraits Toggle // OK
+	{ 234, 178, 317, 276 }		// _key Pad Accel. Toggle
+};
+
 /*----------------------------------------------------------------*/
 
+bool Settings::doesButtonExist(int num) const {
+	if (_vm->getLanguage() == Common::Language::ZH_TWN)
+		return num != 2 && num != 5 && num != 6 && num != 7;
+	return true;
+}
+
+void Settings::makeButtonNumDisabled(int num, const Common::String &s) {
+	if (!doesButtonExist(num))
+		return;
+
+	ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+	makeButtonNum(num, s);
+	screen.buttonPrint(getButtonTextPoint(num), COMMAND_NULL, false, s);
+}
+
+Common::Rect Settings::getButtonRect(int num) const {
+	if (_vm->getLanguage() == Common::Language::ZH_TWN) {
+		return Common::Rect(SETUP_POINTS_ZH[num][0], SETUP_POINTS_ZH[num][1],
+				    SETUP_POINTS_ZH[num][2], SETUP_POINTS_ZH[num][1] + 19);
+	} else {
+		return Common::Rect(SETUP_POINTS_INTL[num][0], SETUP_POINTS_INTL[num][1],
+				    SETUP_POINTS_INTL[num][2], SETUP_POINTS_INTL[num][1] + 10);
+	}
+}
+
+void Settings::makeButtonNum(int num, const Common::String &s) {
+	if (!doesButtonExist(num))
+		return;
+
+	ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+	screen.makeButton(getButtonRect(num), getButtonTextPoint(num), s);
+}
+
+Common::Point Settings::getButtonTextPoint(int num) const {
+	if (_vm->getLanguage() == Common::Language::ZH_TWN) {
+		return Common::Point(SETUP_POINTS_ZH[num][3], SETUP_POINTS_ZH[num][1] + 2);
+	} else {
+		return Common::Point(SETUP_POINTS_INTL[num][3], SETUP_POINTS_INTL[num][1]);
+	}
+}
+
 void Settings::drawInterface(bool flag) {
 	People &people = *_vm->_people;
 	ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
@@ -67,8 +124,7 @@ void Settings::drawInterface(bool flag) {
 
 	tempStr = FIXED(Settings_Exit);
 	_hotkeyExit = toupper(tempStr.firstChar());
-	screen.makeButton(Common::Rect(SETUP_POINTS[0][0], SETUP_POINTS[0][1], SETUP_POINTS[0][2], SETUP_POINTS[0][1] + 10),
-		SETUP_POINTS[0][3], tempStr);
+	makeButtonNum(0, tempStr);
 
 	if (music._musicOn) {
 		tempStr = FIXED(Settings_MusicOn);
@@ -76,8 +132,7 @@ void Settings::drawInterface(bool flag) {
 		tempStr = FIXED(Settings_MusicOff);
 	}
 	_hotkeyMusic = toupper(tempStr.firstChar());
-	screen.makeButton(Common::Rect(SETUP_POINTS[1][0], SETUP_POINTS[1][1], SETUP_POINTS[1][2], SETUP_POINTS[1][1] + 10),
-		SETUP_POINTS[1][3], tempStr);
+	makeButtonNum(1, tempStr);
 
 	if (people._portraitsOn) {
 		tempStr = FIXED(Settings_PortraitsOn);
@@ -85,19 +140,15 @@ void Settings::drawInterface(bool flag) {
 		tempStr = FIXED(Settings_PortraitsOff);
 	}
 	_hotkeyPortraits = toupper(tempStr.firstChar());
-	screen.makeButton(Common::Rect(SETUP_POINTS[10][0], SETUP_POINTS[10][1], SETUP_POINTS[10][2], SETUP_POINTS[10][1] + 10),
-		SETUP_POINTS[10][3], tempStr);
+	makeButtonNum(10, tempStr);
 
 	// WORKAROUND: We don't support the joystick in ScummVM, so draw the next two buttons as disabled
 	tempStr = FIXED(Settings_JoystickOff);
-	screen.makeButton(Common::Rect(SETUP_POINTS[6][0], SETUP_POINTS[6][1], SETUP_POINTS[6][2], SETUP_POINTS[6][1] + 10),
-		SETUP_POINTS[6][3], tempStr);
-	screen.buttonPrint(Common::Point(SETUP_POINTS[6][3], SETUP_POINTS[6][1]), COMMAND_NULL, false, tempStr);
+	makeButtonNumDisabled(6, tempStr);
 
 	tempStr = FIXED(Settings_NewFontStyle);
 	_hotkeyNewFontStyle = toupper(tempStr.firstChar());
-	screen.makeButton(Common::Rect(SETUP_POINTS[5][0], SETUP_POINTS[5][1], SETUP_POINTS[5][2], SETUP_POINTS[5][1] + 10),
-		SETUP_POINTS[5][3], tempStr);
+	makeButtonNum(5, tempStr);
 
 	if (sound._digitized) {
 		tempStr = FIXED(Settings_SoundEffectsOn);
@@ -105,8 +156,7 @@ void Settings::drawInterface(bool flag) {
 		tempStr = FIXED(Settings_SoundEffectsOff);
 	}
 	_hotkeySoundEffects = toupper(tempStr.firstChar());
-	screen.makeButton(Common::Rect(SETUP_POINTS[3][0], SETUP_POINTS[3][1], SETUP_POINTS[3][2], SETUP_POINTS[3][1] + 10),
-		SETUP_POINTS[3][3], tempStr);
+	makeButtonNum(3, tempStr);
 
 	if (ui._slideWindows) {
 		tempStr = FIXED(Settings_WindowsSlide);
@@ -114,13 +164,10 @@ void Settings::drawInterface(bool flag) {
 		tempStr = FIXED(Settings_WindowsAppear);
 	}
 	_hotkeyWindows = toupper(tempStr.firstChar());
-	screen.makeButton(Common::Rect(SETUP_POINTS[9][0], SETUP_POINTS[9][1], SETUP_POINTS[9][2], SETUP_POINTS[9][1] + 10),
-		SETUP_POINTS[9][3], tempStr);
+	makeButtonNum(9, tempStr);
 
 	tempStr = FIXED(Settings_CalibrateJoystick);
-	screen.makeButton(Common::Rect(SETUP_POINTS[7][0], SETUP_POINTS[7][1], SETUP_POINTS[7][2], SETUP_POINTS[7][1] + 10),
-		SETUP_POINTS[7][3], tempStr);
-	screen.buttonPrint(Common::Point(SETUP_POINTS[7][3], SETUP_POINTS[7][1]), COMMAND_NULL, false, tempStr);
+	makeButtonNumDisabled(7, tempStr);
 
 	if (ui._helpStyle) {
 		tempStr = FIXED(Settings_AutoHelpRight);
@@ -128,8 +175,7 @@ void Settings::drawInterface(bool flag) {
 		tempStr = FIXED(Settings_AutoHelpLeft);
 	}
 	_hotkeyAutoHelp = toupper(tempStr.firstChar());
-	screen.makeButton(Common::Rect(SETUP_POINTS[4][0], SETUP_POINTS[4][1], SETUP_POINTS[4][2], SETUP_POINTS[4][1] + 10),
-		SETUP_POINTS[4][3], tempStr);
+	makeButtonNum(4, tempStr);
 
 	if (sound._voices) {
 		tempStr = FIXED(Settings_VoicesOn);
@@ -137,8 +183,7 @@ void Settings::drawInterface(bool flag) {
 		tempStr = FIXED(Settings_VoicesOff);
 	}
 	_hotkeyVoices = toupper(tempStr.firstChar());
-	screen.makeButton(Common::Rect(SETUP_POINTS[2][0], SETUP_POINTS[2][1], SETUP_POINTS[2][2], SETUP_POINTS[2][1] + 10),
-		SETUP_POINTS[2][3], tempStr);
+	makeButtonNum(2, tempStr);
 
 	if (screen._fadeStyle) {
 		tempStr = FIXED(Settings_FadeByPixel);
@@ -146,13 +191,10 @@ void Settings::drawInterface(bool flag) {
 		tempStr = FIXED(Settings_FadeDirectly);
 	}
 	_hotkeyFade = toupper(tempStr.firstChar());
-	screen.makeButton(Common::Rect(SETUP_POINTS[8][0], SETUP_POINTS[8][1], SETUP_POINTS[8][2], SETUP_POINTS[8][1] + 10),
-		SETUP_POINTS[8][3], tempStr);
+	makeButtonNum(8, tempStr);
 
 	tempStr = FIXED(Settings_KeyPadSlow);
-	screen.makeButton(Common::Rect(SETUP_POINTS[11][0], SETUP_POINTS[11][1], SETUP_POINTS[11][2], SETUP_POINTS[11][1] + 10),
-		SETUP_POINTS[11][3], tempStr);
-	screen.buttonPrint(Common::Point(SETUP_POINTS[11][3], SETUP_POINTS[11][1]), COMMAND_NULL, false, tempStr);
+	makeButtonNumDisabled(11, tempStr);
 
 	_hotkeysIndexed[0] = _hotkeyExit;
 	_hotkeysIndexed[1] = _hotkeyMusic;
@@ -190,8 +232,9 @@ int Settings::drawButtons(const Common::Point &pt, int _key) {
 	Common::String tempStr;
 
 	for (int idx = 0; idx < 12; ++idx) {
-		if ((pt.x > SETUP_POINTS[idx][0] && pt.x < SETUP_POINTS[idx][2] && pt.y > SETUP_POINTS[idx][1]
-				&& pt.y < (SETUP_POINTS[idx][1] + 10) && (events._pressed || events._released))
+		if (!doesButtonExist(idx))
+			continue;
+		if ((getButtonRect(idx).contains(pt) && (events._pressed || events._released))
 				|| (_key == toupper(_hotkeysIndexed[idx]))) {
 			found = idx;
 			color = COMMAND_HIGHLIGHTED;
@@ -268,7 +311,7 @@ int Settings::drawButtons(const Common::Point &pt, int _key) {
 		default:
 			continue;
 		}
-		screen.buttonPrint(Common::Point(SETUP_POINTS[idx][3], SETUP_POINTS[idx][1]), color, true, tempStr);
+		screen.buttonPrint(getButtonTextPoint(idx), color, true, tempStr);
 	}
 
 	return found;
diff --git a/engines/sherlock/scalpel/settings.h b/engines/sherlock/scalpel/settings.h
index b0feaebba28..925f0c258f2 100644
--- a/engines/sherlock/scalpel/settings.h
+++ b/engines/sherlock/scalpel/settings.h
@@ -69,6 +69,13 @@ private:
 	 * Draws the buttons for the settings dialog
 	 */
 	int drawButtons(const Common::Point &pt, int key);
+
+	Common::Rect getButtonRect(int num) const;
+	Common::Point getButtonTextPoint(int num) const;
+	void makeButtonNum(int num, const Common::String &s);
+	void makeButtonNumDisabled(int num, const Common::String &s);
+	bool doesButtonExist(int num) const;
+
 public:
 	/**
 	 * Handles input when the settings window is being shown


Commit: 38a6cb8e8b666234f2d9f6fc4dc7425b09965d0f
    https://github.com/scummvm/scummvm/commit/38a6cb8e8b666234f2d9f6fc4dc7425b09965d0f
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2023-02-28T00:40:25+01:00

Commit Message:
SHERLOCK: Always use font 2 for Chinese serrated scalpel

Changed paths:
    engines/sherlock/fonts.cpp


diff --git a/engines/sherlock/fonts.cpp b/engines/sherlock/fonts.cpp
index 5a5b3a7fbc0..60f575b48af 100644
--- a/engines/sherlock/fonts.cpp
+++ b/engines/sherlock/fonts.cpp
@@ -67,6 +67,8 @@ void Fonts::setFont(int fontNum) {
 			// The non-interactive demo does not contain any font at all
 			return;
 		}
+		if (_vm->getLanguage() == Common::Language::ZH_TWN)
+			fontNum = 2;
 	}
 
 	Common::String fontFilename;


Commit: c58c5cb86a22b5f290b79e7282434110fd37115f
    https://github.com/scummvm/scummvm/commit/c58c5cb86a22b5f290b79e7282434110fd37115f
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2023-02-28T00:40:25+01:00

Commit Message:
SHERLOCK: Fix handling of big5 in journal

Changed paths:
    engines/sherlock/journal.cpp


diff --git a/engines/sherlock/journal.cpp b/engines/sherlock/journal.cpp
index 6ecdee9ca83..f7c00dfb162 100644
--- a/engines/sherlock/journal.cpp
+++ b/engines/sherlock/journal.cpp
@@ -304,6 +304,7 @@ void Journal::loadJournalFile(bool alreadyLoaded) {
 	Talk &talk = *_vm->_talk;
 	JournalEntry &journalEntry = _journal[_index];
 	const byte *opcodes = talk._opcodes;
+	bool isBig5 = Fonts::isBig5();
 
 	Common::String dirFilename = _directory[journalEntry._converseNum];
 	bool replyOnly = journalEntry._replyOnly;
@@ -520,7 +521,13 @@ void Journal::loadJournalFile(bool alreadyLoaded) {
 				// {} block is started, or a control character is encountered
 				journalString += c;
 				while (*replyP && isPrintable(*replyP) && *replyP != '{' && *replyP != '}') {
-					journalString += *replyP++;
+					journalString += *replyP;
+
+					if (isBig5 && (*replyP & 0x80)) {
+						journalString += *++replyP;
+					}
+
+					replyP++;
 				}
 
 				commentJustPrinted = false;


Commit: 187b28f0e52b5606e90d2e4f60a9fe223383732f
    https://github.com/scummvm/scummvm/commit/187b28f0e52b5606e90d2e4f60a9fe223383732f
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2023-02-28T00:40:25+01:00

Commit Message:
SHERLOCK: Fix offsets in Chinese journal for serrated scalpel

Changed paths:
    engines/sherlock/journal.cpp
    engines/sherlock/journal.h
    engines/sherlock/scalpel/scalpel_journal.cpp


diff --git a/engines/sherlock/journal.cpp b/engines/sherlock/journal.cpp
index f7c00dfb162..f27c25fcd25 100644
--- a/engines/sherlock/journal.cpp
+++ b/engines/sherlock/journal.cpp
@@ -56,8 +56,8 @@ bool Journal::drawJournal(int direction, int howFar) {
 	FixedText &fixedText = *_vm->_fixedText;
 	Screen &screen = *_vm->_screen;
 	Talk &talk = *_vm->_talk;
-	int topLineY = IS_SERRATED_SCALPEL ? 37 : 103 - screen.charHeight('A');
-	int yp = topLineY;
+	int topLineY;
+	int yp;
 	int startPage = _page;
 	bool endJournal = false;
 	bool firstOccurance = true;
@@ -68,6 +68,22 @@ bool Journal::drawJournal(int direction, int howFar) {
 	int temp;
 	const char *matchP;
 	int width;
+	int leftX;
+
+	if (IS_SERRATED_SCALPEL) {
+		if (_vm->getLanguage() == Common::ZH_TWN) {
+			topLineY = 31;
+			leftX = 36;
+		} else {
+			topLineY = 37;
+			leftX = 53;
+		}
+	} else {
+		topLineY = 103 - screen.charHeight('A');
+		leftX = 156;
+	}
+
+	yp = topLineY;
 
 	talk._converseNum = -1;
 	_down = true;
@@ -242,30 +258,30 @@ bool Journal::drawJournal(int direction, int howFar) {
 				Common::String lineStart(_lines[temp].c_str(), matchP);
 				if (lineStart.hasPrefix("@")) {
 					width = screen.stringWidth(lineStart.c_str() + 1);
-					screen.gPrint(Common::Point(JOURNAL_LEFT_X, yp), COL_PEN_HIGHLIGHT, "%s", lineStart.c_str() + 1);
+					screen.gPrint(Common::Point(leftX, yp), COL_PEN_HIGHLIGHT, "%s", lineStart.c_str() + 1);
 				} else {
 					width = screen.stringWidth(lineStart.c_str());
-					screen.gPrint(Common::Point(JOURNAL_LEFT_X, yp), COL_PEN_COLOR, "%s", lineStart.c_str());
+					screen.gPrint(Common::Point(leftX, yp), COL_PEN_COLOR, "%s", lineStart.c_str());
 				 }
 
 				// Print out the found keyword
 				Common::String lineMatch(matchP, matchP + _find.size());
 				byte fgColor = IS_SERRATED_SCALPEL ? (byte)Scalpel::INV_FOREGROUND : (byte)Tattoo::PEN_HIGHLIGHT_COLOR;
-				screen.gPrint(Common::Point(JOURNAL_LEFT_X + width, yp), fgColor, "%s", lineMatch.c_str());
+				screen.gPrint(Common::Point(leftX + width, yp), fgColor, "%s", lineMatch.c_str());
 				width += screen.stringWidth(lineMatch.c_str());
 
 				// Print remainder of line
-				screen.gPrint(Common::Point(JOURNAL_LEFT_X + width, yp), COL_PEN_COLOR, "%s", matchP + _find.size());
+				screen.gPrint(Common::Point(leftX + width, yp), COL_PEN_COLOR, "%s", matchP + _find.size());
 			} else if (_lines[temp].hasPrefix("@")) {
-				screen.gPrint(Common::Point(JOURNAL_LEFT_X, yp), COL_PEN_HIGHLIGHT, "%s", _lines[temp].c_str() + 1);
+				screen.gPrint(Common::Point(leftX, yp), COL_PEN_HIGHLIGHT, "%s", _lines[temp].c_str() + 1);
 			} else {
-				screen.gPrint(Common::Point(JOURNAL_LEFT_X, yp), COL_PEN_COLOR, "%s", _lines[temp].c_str());
+				screen.gPrint(Common::Point(leftX, yp), COL_PEN_COLOR, "%s", _lines[temp].c_str());
 			}
 		} else {
 			if (_lines[temp].hasPrefix("@")) {
-				screen.gPrint(Common::Point(JOURNAL_LEFT_X, yp), COL_PEN_HIGHLIGHT, "%s", _lines[temp].c_str() + 1);
+				screen.gPrint(Common::Point(leftX, yp), COL_PEN_HIGHLIGHT, "%s", _lines[temp].c_str() + 1);
 			} else {
-				screen.gPrint(Common::Point(JOURNAL_LEFT_X, yp), COL_PEN_COLOR, "%s", _lines[temp].c_str());
+				screen.gPrint(Common::Point(leftX, yp), COL_PEN_COLOR, "%s", _lines[temp].c_str());
 			}
 		}
 
@@ -286,7 +302,10 @@ bool Journal::drawJournal(int direction, int howFar) {
 
 		if (inc) {
 			// Move to next line
-			yp += IS_SERRATED_SCALPEL ? 13 : TATTOO_LINE_SPACING[lineNum];
+			if (IS_SERRATED_SCALPEL)
+				yp += _vm->getLanguage() == Common::ZH_TWN ? 16 : 13;
+			else
+				yp += TATTOO_LINE_SPACING[lineNum];
 			++lineNum;
 		}
 	} while (lineNum < LINES_PER_PAGE && !endFlag);
diff --git a/engines/sherlock/journal.h b/engines/sherlock/journal.h
index 9aa326fa052..0262dd3eaf1 100644
--- a/engines/sherlock/journal.h
+++ b/engines/sherlock/journal.h
@@ -34,7 +34,6 @@ namespace Sherlock {
 #define LINES_PER_PAGE (IS_SERRATED_SCALPEL ? 11 : 17)
 #define JOURNAL_MAX_WIDTH (IS_SERRATED_SCALPEL ? 230 : 422)
 #define JOURNAL_MAX_CHARS 80
-#define JOURNAL_LEFT_X (IS_SERRATED_SCALPEL ? 53 : 156)
 
 class SherlockEngine;
 
diff --git a/engines/sherlock/scalpel/scalpel_journal.cpp b/engines/sherlock/scalpel/scalpel_journal.cpp
index f1859437520..a5bcf5fe9cb 100644
--- a/engines/sherlock/scalpel/scalpel_journal.cpp
+++ b/engines/sherlock/scalpel/scalpel_journal.cpp
@@ -175,8 +175,13 @@ void ScalpelJournal::drawFrame() {
 
 	// Set the palette and print the title
 	screen.setPalette(palette);
-	screen.gPrint(Common::Point(111, 18), BUTTON_BOTTOM, "%s", _fixedTextWatsonsJournal.c_str());
-	screen.gPrint(Common::Point(110, 17), INV_FOREGROUND, "%s", _fixedTextWatsonsJournal.c_str());
+	if (_vm->getLanguage() == Common::ZH_TWN) {
+		screen.gPrint(Common::Point(111, 13), BUTTON_BOTTOM, "%s", _fixedTextWatsonsJournal.c_str());
+		screen.gPrint(Common::Point(110, 12), INV_FOREGROUND, "%s", _fixedTextWatsonsJournal.c_str());
+	} else {
+		screen.gPrint(Common::Point(111, 18), BUTTON_BOTTOM, "%s", _fixedTextWatsonsJournal.c_str());
+		screen.gPrint(Common::Point(110, 17), INV_FOREGROUND, "%s", _fixedTextWatsonsJournal.c_str());
+	}
 
 	// Draw the buttons
 	screen.makeButton(Common::Rect(JOURNAL_POINTS[0][0], JOURNAL_BUTTONS_Y,


Commit: cf651ca336c0edbbf6479d5e49dfc60ad3e41242
    https://github.com/scummvm/scummvm/commit/cf651ca336c0edbbf6479d5e49dfc60ad3e41242
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2023-02-28T00:40:25+01:00

Commit Message:
SHERLOCK: Fix isPrintable for big5

Changed paths:
    engines/sherlock/journal.cpp


diff --git a/engines/sherlock/journal.cpp b/engines/sherlock/journal.cpp
index f27c25fcd25..0553587f695 100644
--- a/engines/sherlock/journal.cpp
+++ b/engines/sherlock/journal.cpp
@@ -806,6 +806,9 @@ bool Journal::isPrintable(byte ch) const {
 	if (_vm->getLanguage() == Common::DE_DEU && ch == 0xe1) // accept German Sharp-S character
 		return true;
 
+	if (_vm->getLanguage() == Common::ZH_TWN && IS_SERRATED_SCALPEL && ch > 161)
+		return true;
+
 	return false;
 }
 


Commit: f407e19890667e31664cb9ca78bc08cde42cc835
    https://github.com/scummvm/scummvm/commit/f407e19890667e31664cb9ca78bc08cde42cc835
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2023-02-28T00:40:25+01:00

Commit Message:
SHERLOCK: Adjust journal buttons for Chinese serrated scalpel

Changed paths:
    engines/sherlock/scalpel/scalpel_journal.cpp
    engines/sherlock/scalpel/scalpel_journal.h


diff --git a/engines/sherlock/scalpel/scalpel_journal.cpp b/engines/sherlock/scalpel/scalpel_journal.cpp
index a5bcf5fe9cb..605611719bf 100644
--- a/engines/sherlock/scalpel/scalpel_journal.cpp
+++ b/engines/sherlock/scalpel/scalpel_journal.cpp
@@ -31,14 +31,15 @@ namespace Sherlock {
 
 namespace Scalpel {
 
-#define JOURNAL_BUTTONS_Y 178
+#define JOURNAL_BUTTONS_Y_INTL 178
+#define JOURNAL_BUTTONS_Y_ZH 181
 #define JOURNAL_SEARCH_LEFT 15
 #define JOURNAL_SEARCH_TOP 186
 #define JOURNAL_SEARCH_RIGHT 296
 #define JOURNAL_SEACRH_MAX_CHARS 50
 
 // Positioning of buttons in the journal view
-static const int JOURNAL_POINTS[9][3] = {
+static const int JOURNAL_POINTS_INTL[9][3] = {
 	{ 6, 68, 37 },
 	{ 69, 131, 100 },
 	{ 132, 192, 162 },
@@ -50,6 +51,18 @@ static const int JOURNAL_POINTS[9][3] = {
 	{ 237, 313, 275 }
 };
 
+static const int JOURNAL_POINTS_ZH[9][3] = {
+	{ 0, 52, 26 },
+	{ 53, 121, 87 },
+	{ 122, 157, 140 },
+	{ 158, 194, 176 },
+	{ 195, 265, 230 },
+	{ 266, 320, 293 },
+	{ 270, 320, 295 },
+	{ 270, 320, 295 },
+	{ 0, 0, 0 }
+};
+
 static const int SEARCH_POINTS[3][3] = {
 	{ 51, 123, 86 },
 	{ 124, 196, 159 },
@@ -95,6 +108,40 @@ ScalpelJournal::ScalpelJournal(SherlockEngine *vm) : Journal(vm) {
 	_hotkeySearchForward = toupper(_fixedTextSearchForward[0]);
 }
 
+Common::Rect ScalpelJournal::getButtonRect(JournalButton btn) {
+	int idx = btn - 1;
+	if (_vm->getLanguage() == Common::ZH_TWN) {
+		if (btn >= BTN_FIRST_PAGE) {
+			return Common::Rect(JOURNAL_POINTS_ZH[idx][0], JOURNAL_BUTTONS_Y_ZH - (btn - BTN_FIRST_PAGE + 1) * 19,
+				    JOURNAL_POINTS_ZH[idx][1], JOURNAL_BUTTONS_Y_ZH + 19 - (btn - BTN_FIRST_PAGE + 1) * 19);
+		} else
+			return Common::Rect(JOURNAL_POINTS_ZH[idx][0], JOURNAL_BUTTONS_Y_ZH,
+					    JOURNAL_POINTS_ZH[idx][1], JOURNAL_BUTTONS_Y_ZH + 19);
+	} else {
+		if (btn >= BTN_SEARCH)
+			return Common::Rect(JOURNAL_POINTS_INTL[idx][0], JOURNAL_BUTTONS_Y_INTL + 11,
+					    JOURNAL_POINTS_INTL[idx][1], JOURNAL_BUTTONS_Y_INTL + 21);
+		else
+			return Common::Rect(JOURNAL_POINTS_INTL[idx][0], JOURNAL_BUTTONS_Y_INTL,
+					    JOURNAL_POINTS_INTL[idx][1], JOURNAL_BUTTONS_Y_INTL + 10);
+	}
+}
+
+Common::Point ScalpelJournal::getButtonTextPoint(JournalButton btn) {
+	int idx = btn - 1;
+	if (_vm->getLanguage() == Common::ZH_TWN) {
+		if (btn >= BTN_FIRST_PAGE)
+			return Common::Point(JOURNAL_POINTS_ZH[idx][2], JOURNAL_BUTTONS_Y_ZH + 2 - (btn - BTN_FIRST_PAGE + 1) * 19);
+		else
+			return Common::Point(JOURNAL_POINTS_ZH[idx][2], JOURNAL_BUTTONS_Y_ZH + 2);
+	} else {
+		if (btn >= BTN_SEARCH)
+			return Common::Point(JOURNAL_POINTS_INTL[idx][2], JOURNAL_BUTTONS_Y_INTL + 11);
+		else
+			return Common::Point(JOURNAL_POINTS_INTL[idx][2], JOURNAL_BUTTONS_Y_INTL);
+	}
+}
+
 void ScalpelJournal::loadLocations() {
 	Resources &res = *_vm->_res;
 
@@ -184,37 +231,21 @@ void ScalpelJournal::drawFrame() {
 	}
 
 	// Draw the buttons
-	screen.makeButton(Common::Rect(JOURNAL_POINTS[0][0], JOURNAL_BUTTONS_Y,
-		JOURNAL_POINTS[0][1], JOURNAL_BUTTONS_Y + 10),
-		JOURNAL_POINTS[0][2], _fixedTextExit);
-	screen.makeButton(Common::Rect(JOURNAL_POINTS[1][0], JOURNAL_BUTTONS_Y,
-		JOURNAL_POINTS[1][1], JOURNAL_BUTTONS_Y + 10),
-		JOURNAL_POINTS[1][2], _fixedTextBack10);
-	screen.makeButton(Common::Rect(JOURNAL_POINTS[2][0], JOURNAL_BUTTONS_Y,
-		JOURNAL_POINTS[2][1], JOURNAL_BUTTONS_Y + 10),
-		JOURNAL_POINTS[2][2], _fixedTextUp);
-	screen.makeButton(Common::Rect(JOURNAL_POINTS[3][0], JOURNAL_BUTTONS_Y,
-		JOURNAL_POINTS[3][1], JOURNAL_BUTTONS_Y + 10),
-		JOURNAL_POINTS[3][2], _fixedTextDown);
-	screen.makeButton(Common::Rect(JOURNAL_POINTS[4][0], JOURNAL_BUTTONS_Y,
-		JOURNAL_POINTS[4][1], JOURNAL_BUTTONS_Y + 10),
-		JOURNAL_POINTS[4][2], _fixedTextAhead10);
-	screen.makeButton(Common::Rect(JOURNAL_POINTS[5][0], JOURNAL_BUTTONS_Y + 11,
-		JOURNAL_POINTS[5][1], JOURNAL_BUTTONS_Y + 21),
-		JOURNAL_POINTS[5][2], _fixedTextSearch);
-	screen.makeButton(Common::Rect(JOURNAL_POINTS[6][0], JOURNAL_BUTTONS_Y + 11,
-		JOURNAL_POINTS[6][1], JOURNAL_BUTTONS_Y + 21),
-		JOURNAL_POINTS[6][2], _fixedTextFirstPage);
-	screen.makeButton(Common::Rect(JOURNAL_POINTS[7][0], JOURNAL_BUTTONS_Y + 11,
-		JOURNAL_POINTS[7][1], JOURNAL_BUTTONS_Y + 21),
-		JOURNAL_POINTS[7][2], _fixedTextLastPage);
+	screen.makeButton(getButtonRect(BTN_EXIT), getButtonTextPoint(BTN_EXIT), _fixedTextExit);
+	screen.makeButton(getButtonRect(BTN_BACK10), getButtonTextPoint(BTN_BACK10), _fixedTextBack10);
+	screen.makeButton(getButtonRect(BTN_UP), getButtonTextPoint(BTN_UP), _fixedTextUp);
+	screen.makeButton(getButtonRect(BTN_DOWN), getButtonTextPoint(BTN_DOWN), _fixedTextDown);
+	screen.makeButton(getButtonRect(BTN_AHEAD110), getButtonTextPoint(BTN_AHEAD110), _fixedTextAhead10);
+	screen.makeButton(getButtonRect(BTN_SEARCH), getButtonTextPoint(BTN_SEARCH), _fixedTextSearch);
+	screen.makeButton(getButtonRect(BTN_FIRST_PAGE), getButtonTextPoint(BTN_FIRST_PAGE), _fixedTextFirstPage);
+	screen.makeButton(getButtonRect(BTN_LAST_PAGE), getButtonTextPoint(BTN_LAST_PAGE), _fixedTextLastPage);
 
 	// WORKAROUND: Draw Print Text button as disabled, since we don't support it in ScummVM
-	screen.makeButton(Common::Rect(JOURNAL_POINTS[8][0], JOURNAL_BUTTONS_Y + 11,
-		JOURNAL_POINTS[8][1], JOURNAL_BUTTONS_Y + 21),
-		JOURNAL_POINTS[8][2], _fixedTextPrintText);
-	screen.buttonPrint(Common::Point(JOURNAL_POINTS[8][2], JOURNAL_BUTTONS_Y + 11),
-		COMMAND_NULL, false, _fixedTextPrintText);
+	// In Chinese version skip it altogether to make space for hotkeys
+	if (_vm->getLanguage() != Common::ZH_TWN) {
+		screen.makeButton(getButtonRect(BTN_PRINT_TEXT), getButtonTextPoint(BTN_PRINT_TEXT), _fixedTextPrintText);
+		screen.buttonPrint(getButtonTextPoint(BTN_PRINT_TEXT), COMMAND_NULL, false, _fixedTextPrintText);
+	}
 }
 
 void ScalpelJournal::drawInterface() {
@@ -239,57 +270,50 @@ void ScalpelJournal::doArrows() {
 	byte color;
 
 	color = (_page > 1) ? COMMAND_FOREGROUND : COMMAND_NULL;
-	screen.buttonPrint(Common::Point(JOURNAL_POINTS[1][2], JOURNAL_BUTTONS_Y), color, false, _fixedTextBack10);
-	screen.buttonPrint(Common::Point(JOURNAL_POINTS[2][2], JOURNAL_BUTTONS_Y), color, false, _fixedTextUp);
+	screen.buttonPrint(getButtonTextPoint(BTN_BACK10), color, false, _fixedTextBack10);
+	screen.buttonPrint(getButtonTextPoint(BTN_UP), color, false, _fixedTextUp);
 
 	color = _down ? COMMAND_FOREGROUND : COMMAND_NULL;
-	screen.buttonPrint(Common::Point(JOURNAL_POINTS[3][2], JOURNAL_BUTTONS_Y), color, false, _fixedTextDown);
-	screen.buttonPrint(Common::Point(JOURNAL_POINTS[4][2], JOURNAL_BUTTONS_Y), color, false, _fixedTextAhead10);
-	screen.buttonPrint(Common::Point(JOURNAL_POINTS[7][2], JOURNAL_BUTTONS_Y + 11), color, false, _fixedTextLastPage);
+	screen.buttonPrint(getButtonTextPoint(BTN_DOWN), color, false, _fixedTextDown);
+	screen.buttonPrint(getButtonTextPoint(BTN_AHEAD110), color, false, _fixedTextAhead10);
+	screen.buttonPrint(getButtonTextPoint(BTN_LAST_PAGE), color, false, _fixedTextLastPage);
 
 	color = _journal.size() > 0 ? COMMAND_FOREGROUND : COMMAND_NULL;
-	screen.buttonPrint(Common::Point(JOURNAL_POINTS[5][2], JOURNAL_BUTTONS_Y + 11), color, false, _fixedTextSearch);
-	screen.buttonPrint(Common::Point(JOURNAL_POINTS[8][2], JOURNAL_BUTTONS_Y + 11), COMMAND_NULL, false, _fixedTextPrintText);
+	screen.buttonPrint(getButtonTextPoint(BTN_SEARCH), color, false, _fixedTextSearch);
+	if (_vm->getLanguage() != Common::ZH_TWN) {
+		screen.buttonPrint(getButtonTextPoint(BTN_PRINT_TEXT), COMMAND_NULL, false, _fixedTextPrintText);
+	}
 
 	color = _page > 1 ? COMMAND_FOREGROUND : COMMAND_NULL;
-	screen.buttonPrint(Common::Point(JOURNAL_POINTS[6][2], JOURNAL_BUTTONS_Y + 11), color, false, _fixedTextFirstPage);
+	screen.buttonPrint(getButtonTextPoint(BTN_FIRST_PAGE), color, false, _fixedTextFirstPage);
 }
 
 JournalButton ScalpelJournal::getHighlightedButton(const Common::Point &pt) {
-	if (pt.x > JOURNAL_POINTS[0][0] && pt.x < JOURNAL_POINTS[0][1] && pt.y >= JOURNAL_BUTTONS_Y &&
-			pt.y < (JOURNAL_BUTTONS_Y + 10))
+	if (getButtonRect(BTN_EXIT).contains(pt))
 		return BTN_EXIT;
 
-	if (pt.x > JOURNAL_POINTS[1][0] && pt.x < JOURNAL_POINTS[1][1] && pt.y >= JOURNAL_BUTTONS_Y &&
-			pt.y < (JOURNAL_BUTTONS_Y + 10) && _page > 1)
+	if (getButtonRect(BTN_BACK10).contains(pt) && _page > 1)
 		return BTN_BACK10;
 
-	if (pt.x > JOURNAL_POINTS[2][0] && pt.x < JOURNAL_POINTS[2][1] && pt.y >= JOURNAL_BUTTONS_Y &&
-			pt.y < (JOURNAL_BUTTONS_Y + 10) && _up)
+	if (getButtonRect(BTN_UP).contains(pt) && _up)
 		return BTN_UP;
 
-	if (pt.x > JOURNAL_POINTS[3][0] && pt.x < JOURNAL_POINTS[3][1] && pt.y >= JOURNAL_BUTTONS_Y &&
-			pt.y < (JOURNAL_BUTTONS_Y + 10) && _down)
+	if (getButtonRect(BTN_DOWN).contains(pt) && _down)
 		return BTN_DOWN;
 
-	if (pt.x > JOURNAL_POINTS[4][0] && pt.x < JOURNAL_POINTS[4][1] && pt.y >= JOURNAL_BUTTONS_Y &&
-			pt.y < (JOURNAL_BUTTONS_Y + 10) && _down)
+	if (getButtonRect(BTN_AHEAD110).contains(pt) && _down)
 		return BTN_AHEAD110;
 
-	if (pt.x > JOURNAL_POINTS[5][0] && pt.x < JOURNAL_POINTS[5][1] && pt.y >= (JOURNAL_BUTTONS_Y + 11) &&
-			pt.y < (JOURNAL_BUTTONS_Y + 20) && !_journal.empty())
+	if (getButtonRect(BTN_SEARCH).contains(pt) && !_journal.empty())
 		return BTN_SEARCH;
 
-	if (pt.x > JOURNAL_POINTS[6][0] && pt.x < JOURNAL_POINTS[6][1] && pt.y >= (JOURNAL_BUTTONS_Y + 11) &&
-			pt.y < (JOURNAL_BUTTONS_Y + 20) && _up)
+	if (getButtonRect(BTN_FIRST_PAGE).contains(pt) && _up)
 		return BTN_FIRST_PAGE;
 
-	if (pt.x > JOURNAL_POINTS[7][0] && pt.x < JOURNAL_POINTS[7][1] && pt.y >= (JOURNAL_BUTTONS_Y + 11) &&
-			pt.y < (JOURNAL_BUTTONS_Y + 20) && _down)
+	if (getButtonRect(BTN_LAST_PAGE).contains(pt) && _down)
 		return BTN_LAST_PAGE;
 
-	if (pt.x > JOURNAL_POINTS[8][0] && pt.x < JOURNAL_POINTS[8][1] && pt.y >= (JOURNAL_BUTTONS_Y + 11) &&
-			pt.y < (JOURNAL_BUTTONS_Y + 20) && !_journal.empty())
+	if (_vm->getLanguage() != Common::ZH_TWN && getButtonRect(BTN_PRINT_TEXT).contains(pt) && !_journal.empty())
 		return BTN_PRINT_TEXT;
 
 	return BTN_NONE;
@@ -307,34 +331,34 @@ bool ScalpelJournal::handleEvents(int key) {
 	if (events._pressed || events._released) {
 		// Exit button
 		color = (btn == BTN_EXIT) ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND;
-		screen.buttonPrint(Common::Point(JOURNAL_POINTS[0][2], JOURNAL_BUTTONS_Y), color, true, _fixedTextExit);
+		screen.buttonPrint(getButtonTextPoint(BTN_EXIT), color, true, _fixedTextExit);
 
 		// Back 10 button
 		if (btn == BTN_BACK10) {
-			screen.buttonPrint(Common::Point(JOURNAL_POINTS[1][2], JOURNAL_BUTTONS_Y), COMMAND_HIGHLIGHTED, true, _fixedTextBack10);
+			screen.buttonPrint(getButtonTextPoint(BTN_BACK10), COMMAND_HIGHLIGHTED, true, _fixedTextBack10);
 		} else if (_page > 1) {
-			screen.buttonPrint(Common::Point(JOURNAL_POINTS[1][2], JOURNAL_BUTTONS_Y), COMMAND_FOREGROUND, true, _fixedTextBack10);
+			screen.buttonPrint(getButtonTextPoint(BTN_BACK10), COMMAND_FOREGROUND, true, _fixedTextBack10);
 		}
 
 		// Up button
 		if (btn == BTN_UP) {
-			screen.buttonPrint(Common::Point(JOURNAL_POINTS[2][2], JOURNAL_BUTTONS_Y), COMMAND_HIGHLIGHTED, true, _fixedTextUp);
+			screen.buttonPrint(getButtonTextPoint(BTN_UP), COMMAND_HIGHLIGHTED, true, _fixedTextUp);
 		} else if (_up) {
-			screen.buttonPrint(Common::Point(JOURNAL_POINTS[2][2], JOURNAL_BUTTONS_Y), COMMAND_FOREGROUND, true, _fixedTextUp);
+			screen.buttonPrint(getButtonTextPoint(BTN_UP), COMMAND_FOREGROUND, true, _fixedTextUp);
 		}
 
 		// Down button
 		if (btn == BTN_DOWN) {
-			screen.buttonPrint(Common::Point(JOURNAL_POINTS[3][2], JOURNAL_BUTTONS_Y), COMMAND_HIGHLIGHTED, true, _fixedTextDown);
+			screen.buttonPrint(getButtonTextPoint(BTN_DOWN), COMMAND_HIGHLIGHTED, true, _fixedTextDown);
 		} else if (_down) {
-			screen.buttonPrint(Common::Point(JOURNAL_POINTS[3][2], JOURNAL_BUTTONS_Y), COMMAND_FOREGROUND, true, _fixedTextDown);
+			screen.buttonPrint(getButtonTextPoint(BTN_DOWN), COMMAND_FOREGROUND, true, _fixedTextDown);
 		}
 
 		// Ahead 10 button
 		if (btn == BTN_AHEAD110) {
-			screen.buttonPrint(Common::Point(JOURNAL_POINTS[4][2], JOURNAL_BUTTONS_Y), COMMAND_HIGHLIGHTED, true, _fixedTextAhead10);
+			screen.buttonPrint(getButtonTextPoint(BTN_AHEAD110), COMMAND_HIGHLIGHTED, true, _fixedTextAhead10);
 		} else if (_down) {
-			screen.buttonPrint(Common::Point(JOURNAL_POINTS[4][2], JOURNAL_BUTTONS_Y), COMMAND_FOREGROUND, true, _fixedTextAhead10);
+			screen.buttonPrint(getButtonTextPoint(BTN_AHEAD110), COMMAND_FOREGROUND, true, _fixedTextAhead10);
 		}
 
 		// Search button
@@ -345,7 +369,7 @@ bool ScalpelJournal::handleEvents(int key) {
 		} else {
 			color = COMMAND_FOREGROUND;
 		}
-		screen.buttonPrint(Common::Point(JOURNAL_POINTS[5][2], JOURNAL_BUTTONS_Y + 11), color, true, _fixedTextSearch);
+		screen.buttonPrint(getButtonTextPoint(BTN_SEARCH), color, true, _fixedTextSearch);
 
 		// First Page button
 		if (btn == BTN_FIRST_PAGE) {
@@ -355,7 +379,7 @@ bool ScalpelJournal::handleEvents(int key) {
 		} else {
 			color = COMMAND_NULL;
 		}
-		screen.buttonPrint(Common::Point(JOURNAL_POINTS[6][2], JOURNAL_BUTTONS_Y + 11), color, true, _fixedTextFirstPage);
+		screen.buttonPrint(getButtonTextPoint(BTN_FIRST_PAGE), color, true, _fixedTextFirstPage);
 
 		// Last Page button
 		if (btn == BTN_LAST_PAGE) {
@@ -365,10 +389,12 @@ bool ScalpelJournal::handleEvents(int key) {
 		} else {
 			color = COMMAND_NULL;
 		}
-		screen.buttonPrint(Common::Point(JOURNAL_POINTS[7][2], JOURNAL_BUTTONS_Y + 11), color, true, _fixedTextLastPage);
+		screen.buttonPrint(getButtonTextPoint(BTN_LAST_PAGE), color, true, _fixedTextLastPage);
 
-		// Print Text button
-		screen.buttonPrint(Common::Point(JOURNAL_POINTS[8][2], JOURNAL_BUTTONS_Y + 11), COMMAND_NULL, true, _fixedTextPrintText);
+		if (_vm->getLanguage() != Common::ZH_TWN) {
+			// Print Text button
+			screen.buttonPrint(getButtonTextPoint(BTN_PRINT_TEXT), COMMAND_NULL, true, _fixedTextPrintText);
+		}
 	}
 
 	if (btn == BTN_EXIT && events._released) {
@@ -408,7 +434,7 @@ bool ScalpelJournal::handleEvents(int key) {
 		screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
 
 	} else if (((btn == BTN_SEARCH && events._released) || key == _hotkeySearch) && !_journal.empty()) {
-		screen.buttonPrint(Common::Point(JOURNAL_POINTS[5][2], JOURNAL_BUTTONS_Y + 11), COMMAND_FOREGROUND, true, _fixedTextSearch);
+		screen.buttonPrint(getButtonTextPoint(BTN_SEARCH), COMMAND_FOREGROUND, true, _fixedTextSearch);
 		bool notFound = false;
 
 		do {
diff --git a/engines/sherlock/scalpel/scalpel_journal.h b/engines/sherlock/scalpel/scalpel_journal.h
index 39f373da3a3..c32377d3034 100644
--- a/engines/sherlock/scalpel/scalpel_journal.h
+++ b/engines/sherlock/scalpel/scalpel_journal.h
@@ -120,6 +120,9 @@ public:
 	 * can then read the journal to review them
 	 */
 	void record(int converseNum, int statementNum, bool replyOnly = false) override;
+
+	Common::Rect getButtonRect(JournalButton btn);
+	Common::Point getButtonTextPoint(JournalButton btn);
 };
 
 } // End of namespace Scalpel


Commit: 12ed6c83fe51ea37a6e34891a8ca79cb222386ac
    https://github.com/scummvm/scummvm/commit/12ed6c83fe51ea37a6e34891a8ca79cb222386ac
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2023-02-28T00:40:25+01:00

Commit Message:
SHERLOCK: Change coordinates for search bar in Chinese serrated scalpel

Changed paths:
    engines/sherlock/scalpel/scalpel_journal.cpp
    engines/sherlock/scalpel/scalpel_journal.h


diff --git a/engines/sherlock/scalpel/scalpel_journal.cpp b/engines/sherlock/scalpel/scalpel_journal.cpp
index 605611719bf..1719380ed49 100644
--- a/engines/sherlock/scalpel/scalpel_journal.cpp
+++ b/engines/sherlock/scalpel/scalpel_journal.cpp
@@ -34,7 +34,8 @@ namespace Scalpel {
 #define JOURNAL_BUTTONS_Y_INTL 178
 #define JOURNAL_BUTTONS_Y_ZH 181
 #define JOURNAL_SEARCH_LEFT 15
-#define JOURNAL_SEARCH_TOP 186
+#define JOURNAL_SEARCH_TOP_INTL 186
+#define JOURNAL_SEARCH_TOP_ZH 184
 #define JOURNAL_SEARCH_RIGHT 296
 #define JOURNAL_SEACRH_MAX_CHARS 50
 
@@ -53,22 +54,28 @@ static const int JOURNAL_POINTS_INTL[9][3] = {
 
 static const int JOURNAL_POINTS_ZH[9][3] = {
 	{ 0, 52, 26 },
-	{ 53, 121, 87 },
+	{ 52, 121, 87 },
 	{ 122, 157, 140 },
-	{ 158, 194, 176 },
-	{ 195, 265, 230 },
-	{ 266, 320, 293 },
+	{ 157, 194, 176 },
+	{ 194, 265, 230 },
+	{ 265, 320, 293 },
 	{ 270, 320, 295 },
 	{ 270, 320, 295 },
 	{ 0, 0, 0 }
 };
 
-static const int SEARCH_POINTS[3][3] = {
+static const int SEARCH_POINTS_INTL[3][3] = {
 	{ 51, 123, 86 },
 	{ 124, 196, 159 },
 	{ 197, 269, 232 }
 };
 
+static const int SEARCH_POINTS_ZH[3][3] = {
+	{ 206, 243, 225 },
+	{ 243, 279, 261 },
+	{ 279, 315, 297 }
+};
+
 /*----------------------------------------------------------------*/
 
 ScalpelJournal::ScalpelJournal(SherlockEngine *vm) : Journal(vm) {
@@ -142,6 +149,22 @@ Common::Point ScalpelJournal::getButtonTextPoint(JournalButton btn) {
 	}
 }
 
+Common::Rect ScalpelJournal::getSearchButtonRect(int idx) {
+	if (_vm->getLanguage() == Common::ZH_TWN) {
+		return Common::Rect(SEARCH_POINTS_ZH[idx][0], 175, SEARCH_POINTS_ZH[idx][1], 194);
+	} else {
+		return Common::Rect(SEARCH_POINTS_INTL[idx][0], 174, SEARCH_POINTS_INTL[idx][1], 184);
+	}
+}
+
+Common::Point ScalpelJournal::getSearchButtonTextPoint(int idx) {
+	if (_vm->getLanguage() == Common::ZH_TWN) {
+		return Common::Point(SEARCH_POINTS_ZH[idx][2], 177);
+	} else {
+		return Common::Point(SEARCH_POINTS_INTL[idx][2], 174);
+	}
+}
+
 void ScalpelJournal::loadLocations() {
 	Resources &res = *_vm->_res;
 
@@ -496,22 +519,27 @@ int ScalpelJournal::getSearchString(bool printError) {
 	ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
 	Talk &talk = *_vm->_talk;
 	int xp;
-	int yp = 174;
+	int yp;
 	bool flag = false;
 	Common::String name;
 	int done = 0;
 	byte color;
+	bool isChinese = _vm->getLanguage() == Common::ZH_TWN;
 
 	// Draw search panel
-	screen.makePanel(Common::Rect(6, 171, 313, 199));
-	screen.makeButton(Common::Rect(SEARCH_POINTS[0][0], yp, SEARCH_POINTS[0][1], yp + 10),
-		SEARCH_POINTS[0][2], _fixedTextSearchExit);
-	screen.makeButton(Common::Rect(SEARCH_POINTS[1][0], yp, SEARCH_POINTS[1][1], yp + 10),
-		SEARCH_POINTS[1][2], _fixedTextSearchBackward);
-	screen.makeButton(Common::Rect(SEARCH_POINTS[2][0], yp, SEARCH_POINTS[2][1], yp + 10),
-		SEARCH_POINTS[2][2], _fixedTextSearchForward);
+	if (isChinese)
+		screen.makePanel(Common::Rect(6, 171, 318, 199));
+	else
+		screen.makePanel(Common::Rect(6, 171, 313, 199));
+
+	screen.makeButton(getSearchButtonRect(0), getSearchButtonTextPoint(0), _fixedTextSearchExit, !isChinese);
+	screen.makeButton(getSearchButtonRect(1), getSearchButtonTextPoint(1), _fixedTextSearchBackward, !isChinese);
+	screen.makeButton(getSearchButtonRect(2), getSearchButtonTextPoint(2), _fixedTextSearchForward, !isChinese);
 
-	screen.makeField(Common::Rect(12, 185, 307, 196));
+	if (isChinese)
+		screen.makeField(Common::Rect(12, 175, 205, 194));
+	else
+		screen.makeField(Common::Rect(12, 185, 307, 196));
 
 	if (printError) {
 		screen.gPrint(Common::Point((SHERLOCK_SCREEN_WIDTH - screen.stringWidth(_fixedTextSearchNotFound)) / 2, 185),
@@ -522,7 +550,10 @@ int ScalpelJournal::getSearchString(bool printError) {
 		name = _find;
 	}
 
-	screen.slamArea(6, 171, 307, 28);
+	if (_vm->getLanguage() == Common::ZH_TWN)
+		screen.slamArea(6, 171, 312, 28);
+	else
+		screen.slamArea(6, 171, 307, 28);
 
 	if (printError) {
 		// Give time for user to see the message
@@ -545,7 +576,7 @@ int ScalpelJournal::getSearchString(bool printError) {
 	}
 
 	xp = JOURNAL_SEARCH_LEFT + screen.stringWidth(name);
-	yp = JOURNAL_SEARCH_TOP;
+	yp = isChinese ? JOURNAL_SEARCH_TOP_ZH : JOURNAL_SEARCH_TOP_INTL;
 
 	do {
 		events._released = false;
@@ -564,29 +595,29 @@ int ScalpelJournal::getSearchString(bool printError) {
 			screen.vgaBar(Common::Rect(xp, yp, xp + 8, yp + 9), flag ? INV_FOREGROUND : BUTTON_MIDDLE);
 
 			if (events._pressed || events._released) {
-				if (pt.x > SEARCH_POINTS[0][0] && pt.x < SEARCH_POINTS[0][1] && pt.y > 174 && pt.y < 183) {
+				if (getSearchButtonRect(0).contains(pt)) {
 					found = BTN_EXIT;
 					color = COMMAND_HIGHLIGHTED;
 				} else {
 					color = COMMAND_FOREGROUND;
 				}
-				screen.buttonPrint(Common::Point(SEARCH_POINTS[0][0], SEARCH_POINTS[0][2]), color, false, _fixedTextSearchExit);
+				screen.buttonPrint(getSearchButtonTextPoint(0), color, false, _fixedTextSearchExit, !isChinese);
 
-				if (pt.x > SEARCH_POINTS[1][0] && pt.x < SEARCH_POINTS[1][1] && pt.y > 174 && pt.y < 183) {
+				if (getSearchButtonRect(1).contains(pt)) {
 					found = BTN_BACKWARD;
 					color = COMMAND_HIGHLIGHTED;
 				} else {
 					color = COMMAND_FOREGROUND;
 				}
-				screen.buttonPrint(Common::Point(SEARCH_POINTS[1][0], SEARCH_POINTS[1][2]), color, false, _fixedTextSearchBackward);
+				screen.buttonPrint(getSearchButtonTextPoint(1), color, false, _fixedTextSearchBackward, !isChinese);
 
-				if (pt.x > SEARCH_POINTS[2][0] && pt.x < SEARCH_POINTS[2][1] && pt.y > 174 && pt.y < 183) {
+				if (getSearchButtonRect(2).contains(pt)) {
 					found = BTN_FORWARD;
 					color = COMMAND_HIGHLIGHTED;
 				} else {
 					color = COMMAND_FOREGROUND;
 				}
-				screen.buttonPrint(Common::Point(SEARCH_POINTS[2][0], SEARCH_POINTS[2][2]), color, false, _fixedTextSearchForward);
+				screen.buttonPrint(getSearchButtonTextPoint(2), color, false, _fixedTextSearchForward, !isChinese);
 			}
 
 			events.wait(2);
diff --git a/engines/sherlock/scalpel/scalpel_journal.h b/engines/sherlock/scalpel/scalpel_journal.h
index c32377d3034..294fda8e0e9 100644
--- a/engines/sherlock/scalpel/scalpel_journal.h
+++ b/engines/sherlock/scalpel/scalpel_journal.h
@@ -91,6 +91,9 @@ private:
 	 * Returns the button, if any, that is under the specified position
 	 */
 	JournalButton getHighlightedButton(const Common::Point &pt);
+
+	Common::Rect getSearchButtonRect(int idx);
+	Common::Point getSearchButtonTextPoint(int idx);
 public:
 	ScalpelJournal(SherlockEngine *vm);
 	~ScalpelJournal() override {}


Commit: a728a5aee1893ea7f55308a74cc1305b4ef36300
    https://github.com/scummvm/scummvm/commit/a728a5aee1893ea7f55308a74cc1305b4ef36300
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2023-02-28T00:40:25+01:00

Commit Message:
SHERLOCK: Don't print bogus hotkey

Changed paths:
    engines/sherlock/scalpel/scalpel_screen.cpp


diff --git a/engines/sherlock/scalpel/scalpel_screen.cpp b/engines/sherlock/scalpel/scalpel_screen.cpp
index c05deade992..a5f51941a4f 100644
--- a/engines/sherlock/scalpel/scalpel_screen.cpp
+++ b/engines/sherlock/scalpel/scalpel_screen.cpp
@@ -96,11 +96,13 @@ void ScalpelScreen::buttonPrint(const Common::Point &pt, uint color, bool slamIt
 		if (slamIt) {
 			print(Common::Point(xStart, pt.y + 1),
 				COMMAND_FOREGROUND, "%s", buttonText.c_str() + skipTextOffset);
-			print(Common::Point(xStart + prefixOffsetX, pt.y + 1), COMMAND_HIGHLIGHTED, "%c", hotkey);
+			if (textContainsHotkey)
+				print(Common::Point(xStart + prefixOffsetX, pt.y + 1), COMMAND_HIGHLIGHTED, "%c", hotkey);
 		} else {
 			gPrint(Common::Point(xStart, pt.y),
 				COMMAND_FOREGROUND, "%s", buttonText.c_str() + skipTextOffset);
-			gPrint(Common::Point(xStart + prefixOffsetX, pt.y), COMMAND_HIGHLIGHTED, "%c", hotkey);
+			if (textContainsHotkey)
+				gPrint(Common::Point(xStart + prefixOffsetX, pt.y), COMMAND_HIGHLIGHTED, "%c", hotkey);
 		}
 	} else if (slamIt) {
 		print(Common::Point(xStart, pt.y + 1), color, "%s", buttonText.c_str() + skipTextOffset);




More information about the Scummvm-git-logs mailing list