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

neuromancer noreply at scummvm.org
Wed Jun 3 12:33:49 UTC 2026


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

Summary:
a3cd1a9917 SCUMM: RA2: avoid incorrect palettes in transition frames
bfe931de30 SCUMM: RA2: correctly center input when a new gameplay section starts
ddfbc182ef SCUMM: RA2: refactor nutcracker/font code to isolate it from the rest of the scumm games


Commit: a3cd1a99175cef34d0830c125f79cf40405cd047
    https://github.com/scummvm/scummvm/commit/a3cd1a99175cef34d0830c125f79cf40405cd047
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-03T14:31:53+02:00

Commit Message:
SCUMM: RA2: avoid incorrect palettes in transition frames

Changed paths:
    engines/scumm/insane/rebel2/iact.cpp
    engines/scumm/insane/rebel2/rebel.h
    engines/scumm/insane/rebel2/render.cpp
    engines/scumm/smush/rebel/smush_player_ra2.cpp
    engines/scumm/smush/rebel/smush_player_ra2.h


diff --git a/engines/scumm/insane/rebel2/iact.cpp b/engines/scumm/insane/rebel2/iact.cpp
index af30f749a81..cb34ed8fffb 100644
--- a/engines/scumm/insane/rebel2/iact.cpp
+++ b/engines/scumm/insane/rebel2/iact.cpp
@@ -1236,43 +1236,9 @@ void InsaneRebel2::handleOpcode6Handler25(byte *renderBitmap, Common::SeekableRe
 		_player->_fobjOffsetY = _rebelViewOffsetY;
 	}
 
-	// Draw corridor overlay OPAQUELY (FUN_00428A10 in original, line 216).
+	// Draw corridor overlay opaquely (FUN_00428a10 in original, line 216).
 	// This wipes previous frame content so codec 23 delta skip regions show clean corridor.
-	if (renderBitmap) {
-		EmbeddedSanFrame &corridorOverlay = _rebelEmbeddedHud[4];
-		if (corridorOverlay.valid && corridorOverlay.pixels) {
-			int pitch = (_player && _player->_width > 0) ? _player->_width : 320;
-			int bufHeight = (_player && _player->_height > 0) ? _player->_height : 200;
-
-			int srcOffsetX = 0;
-			int srcOffsetY = 0;
-			int destX = _rebelViewOffsetX;
-			int destY = _rebelViewOffsetY;
-			int drawWidth = corridorOverlay.width;
-			int drawHeight = corridorOverlay.height;
-
-			if (destX < 0) { srcOffsetX = -destX; drawWidth -= srcOffsetX; destX = 0; }
-			if (destY < 0) { srcOffsetY = -destY; drawHeight -= srcOffsetY; destY = 0; }
-			if (destX + drawWidth > pitch)
-				drawWidth = pitch - destX;
-			if (destY + drawHeight > bufHeight)
-				drawHeight = bufHeight - destY;
-			if (drawWidth > corridorOverlay.width - srcOffsetX)
-				drawWidth = corridorOverlay.width - srcOffsetX;
-			if (drawHeight > corridorOverlay.height - srcOffsetY)
-				drawHeight = corridorOverlay.height - srcOffsetY;
-
-			if (drawWidth > 0 && drawHeight > 0) {
-				for (int y = 0; y < drawHeight; y++) {
-					memcpy(renderBitmap + (destY + y) * pitch + destX,
-						   corridorOverlay.pixels + (srcOffsetY + y) * corridorOverlay.width + srcOffsetX,
-						   drawWidth);
-				}
-			}
-			debug("Rebel2 Opcode 6: Corridor overlay drawn at (%d,%d) size(%d,%d)",
-				_rebelViewOffsetX, _rebelViewOffsetY, corridorOverlay.width, corridorOverlay.height);
-		}
-	}
+	drawHandler25CorridorOverlay(renderBitmap);
 }
 
 // ScummVM refactor helper for opcode 6 Handler 0x26, not a separate retail function.
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index c4ed62eb146..c51f4dfa214 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -765,6 +765,7 @@ public:
 	// Render a decoded embedded frame to the video buffer
 	// Handles transparency (color 0 and 231) and boundary checks
 	void renderEmbeddedFrame(byte *renderBitmap, const EmbeddedSanFrame &frame, int userId);
+	void drawHandler25CorridorOverlay(byte *renderBitmap);
 
 	int16 _rebelLinks[512][3]; // Dependency links: Slot 0 (Disable on death), Slot 1/2 (Enable on death)
 	void clearBit(int n);
diff --git a/engines/scumm/insane/rebel2/render.cpp b/engines/scumm/insane/rebel2/render.cpp
index e5a373e253f..890a19c9166 100644
--- a/engines/scumm/insane/rebel2/render.cpp
+++ b/engines/scumm/insane/rebel2/render.cpp
@@ -132,13 +132,18 @@ void InsaneRebel2::renderEmbeddedFrame(byte *renderBitmap, const EmbeddedSanFram
 	                          _rebelHandler == 0x26 || _rebelHandler == 0x19);
 
 	// Handler 25 overlays:
-	// - userId 4 (corridor overlay): Draw during procPostRendering at view offset, NOT immediately
+	// - userId 4 (corridor overlay): draw immediately at the current view offset.
+	//   FUN_0041cadb case 6/par4=4 decodes DAT_00482268, then calls
+	//   FUN_00428a10(param_1, 0, DAT_0045790c, DAT_0045790e, DAT_00482268).
 	// - userId 6, 7 (static overlays): Draw immediately (they don't move)
+	if (_rebelHandler == 0x19 && userId == 4) {
+		drawHandler25CorridorOverlay(renderBitmap);
+		return;
+	}
 	if (_rebelHandler == 0x19 && (userId == 6 || userId == 7)) {
 		skipImmediateDraw = false;
 		debug("Rebel2: Handler 25 static overlay userId=%d - forcing immediate draw", userId);
 	}
-	// userId 4 should NOT draw immediately - it will be drawn at view offset each frame
 
 	if (!frame.valid || !renderBitmap || skipImmediateDraw) {
 		if (skipImmediateDraw && frame.valid) {
@@ -156,6 +161,56 @@ void InsaneRebel2::renderEmbeddedFrame(byte *renderBitmap, const EmbeddedSanFram
 	debug("Rebel2: Rendered embedded HUD %d at (%d,%d)", userId, frame.renderX, frame.renderY);
 }
 
+void InsaneRebel2::drawHandler25CorridorOverlay(byte *renderBitmap) {
+	if (!renderBitmap)
+		return;
+
+	EmbeddedSanFrame &corridorOverlay = _rebelEmbeddedHud[4];
+	if (!isValidEmbeddedFrame(corridorOverlay))
+		return;
+
+	int pitch = (_player && _player->_width > 0) ? _player->_width : 320;
+	int bufHeight = (_player && _player->_height > 0) ? _player->_height : 200;
+
+	int srcOffsetX = 0;
+	int srcOffsetY = 0;
+	int destX = _rebelViewOffsetX;
+	int destY = _rebelViewOffsetY;
+	int drawWidth = corridorOverlay.width;
+	int drawHeight = corridorOverlay.height;
+
+	if (destX < 0) {
+		srcOffsetX = -destX;
+		drawWidth -= srcOffsetX;
+		destX = 0;
+	}
+	if (destY < 0) {
+		srcOffsetY = -destY;
+		drawHeight -= srcOffsetY;
+		destY = 0;
+	}
+	if (destX + drawWidth > pitch)
+		drawWidth = pitch - destX;
+	if (destY + drawHeight > bufHeight)
+		drawHeight = bufHeight - destY;
+	if (drawWidth > corridorOverlay.width - srcOffsetX)
+		drawWidth = corridorOverlay.width - srcOffsetX;
+	if (drawHeight > corridorOverlay.height - srcOffsetY)
+		drawHeight = corridorOverlay.height - srcOffsetY;
+
+	if (drawWidth <= 0 || drawHeight <= 0)
+		return;
+
+	for (int y = 0; y < drawHeight; y++) {
+		memcpy(renderBitmap + (destY + y) * pitch + destX,
+			   corridorOverlay.pixels + (srcOffsetY + y) * corridorOverlay.width + srcOffsetX,
+			   drawWidth);
+	}
+
+	debug("Rebel2 Handler25: Corridor overlay drawn at (%d,%d) size(%d,%d)",
+		_rebelViewOffsetX, _rebelViewOffsetY, corridorOverlay.width, corridorOverlay.height);
+}
+
 //
 // loadEmbeddedSan -- Decode an embedded SAN (ANIM/FOBJ) from IACT opcode 8 data.
 //
@@ -2866,10 +2921,9 @@ void InsaneRebel2::renderEmbeddedHudOverlays(byte *renderBitmap, int pitch, int
 		if (!isValidEmbeddedFrame(frame))
 			continue;
 
-		// Handler 25: Skip slot 4 (corridor overlay) in post-rendering.
-		// The corridor is a full background image (no color 0 transparent center).
-		// Drawing it here would cover enemies. It's already drawn in procPreRendering
-		// with transparency to preserve frame persistence for codec 23 delta.
+		// Handler 25: skip slot 4 (corridor overlay) in post-rendering.
+		// FUN_0041cadb draws it opaquely from opcode 6 and immediately after
+		// loading par4=4; drawing it here would cover enemies.
 		if (_rebelHandler == 25 && hudSlot == 4) {
 			continue;
 		}
diff --git a/engines/scumm/smush/rebel/smush_player_ra2.cpp b/engines/scumm/smush/rebel/smush_player_ra2.cpp
index e24b8f56d3c..58d1c3fdf18 100644
--- a/engines/scumm/smush/rebel/smush_player_ra2.cpp
+++ b/engines/scumm/smush/rebel/smush_player_ra2.cpp
@@ -89,6 +89,7 @@ void SmushPlayerRebel2::initGamePlayerFields() {
 	_ra2FrameSourceSkipY = 0;
 	_ra2FrameObjectSurfaceWidth = 0;
 	_ra2FrameObjectSurfaceHeight = 0;
+	_ra2PendingAnimHeaderPalette = false;
 	_scrollX = 0;
 	_scrollY = 0;
 }
@@ -106,10 +107,12 @@ void SmushPlayerRebel2::destroyGamePlayerFields() {
 
 /**
  * RA2-specific initialization in SmushPlayer::init().
- * Re-pushes the SMUSH palette (videos without NPAL inherit from previous),
- * and handles background preservation between cinematic and gameplay videos.
+ * Re-pushes the SMUSH palette for videos that inherit it from the previous video,
+ * and initializes the screen target state before AHDR/FOBJ selects dimensions.
  */
 void SmushPlayerRebel2::initGameVideoState() {
+	_ra2PendingAnimHeaderPalette = false;
+
 	// Re-push the SMUSH palette to the system. Videos like O_LEVEL.SAN
 	// have no NPAL chunk and inherit the palette from the previous video.
 	// Since play() resets _palDirtyMin/Max, the palette would never be pushed otherwise.
@@ -120,16 +123,15 @@ void SmushPlayerRebel2::initGameVideoState() {
 	_width = _vm->_screenWidth;
 	_height = _vm->_screenHeight;
 
-	// Handle background preservation between videos:
-	// - Cinematic videos (flags 0x20) clear the buffer for a fresh start
-	// - Gameplay videos (flags 0x08) preserve the existing screen content
+	// Keep ScummVM's virtual screen consistent with FUN_00424d70: bit 0x20
+	// controls per-frame clearing. Videos that clear every frame can also start
+	// from a cleared target; videos with bit 0x20 set preserve until their first
+	// decoded frame overwrites or composes over it.
 	if (_dst != nullptr) {
 		VirtScreen *vs = &_vm->_virtscr[kMainVirtScreen];
-		if ((_curVideoFlags & 0x08) == 0) {
-			// Cinematic mode (flags 0x20) - clear buffer for fresh video
+		if ((_curVideoFlags & 0x20) == 0) {
 			memset(_dst, 0, vs->w * vs->h);
 		}
-		// Gameplay mode: no-op, the existing screen content is preserved.
 	}
 }
 
@@ -397,8 +399,9 @@ void SmushPlayerRebel2::adjustGamePalette() {
 }
 
 bool SmushPlayerRebel2::shouldLoadAnimHeaderPalette() const {
-	// Original RA2 AHDR handler skips the embedded palette when video flag 0x400 is set.
-	return (_curVideoFlags & 0x400) == 0;
+	// RA2 copies the AHDR palette in handleGameAnimHeader() so it can match
+	// FUN_00424640/FUN_00424540 timing without changing the generic player.
+	return false;
 }
 
 void SmushPlayerRebel2::ra2HandleDeltaPalette(int32 subSize, Common::SeekableReadStream &b) {
@@ -450,6 +453,20 @@ void SmushPlayerRebel2::ra2HandleDeltaPalette(int32 subSize, Common::SeekableRea
 // backing bitmap, but active low-res gameplay dimensions are selected later by
 // IACT/FOBJ state; keep the virtual-screen pitch safe until then.
 bool SmushPlayerRebel2::handleGameAnimHeader(byte *headerContent) {
+	if (!_skipPalette && (_curVideoFlags & 0x400) == 0) {
+		memcpy(_pal, headerContent + 6, sizeof(_pal));
+		adjustGamePalette();
+
+		// initGameVideoState() marks the previous palette dirty for videos that
+		// inherit it. AHDR replaces _pal before the first FRME, so clear that
+		// inherited dirty range and delay the new AHDR palette until frame start.
+		// Original RA2 marks it dirty from FUN_00424640, then applies it from
+		// FUN_00424540 after FUN_00424d70 has processed a frame.
+		_palDirtyMin = 256;
+		_palDirtyMax = -1;
+		_ra2PendingAnimHeaderPalette = true;
+	}
+
 	int width = READ_LE_UINT16(&headerContent[4]);
 	int height = READ_LE_UINT16(&headerContent[6]);
 
@@ -838,6 +855,11 @@ void SmushPlayerRebel2::handleGameFrameObjectPost(int codec, const byte *data, i
 void SmushPlayerRebel2::handleGameFrameStart() {
 	_hasFrameFobjForGost = false;
 
+	if (_ra2PendingAnimHeaderPalette) {
+		setDirtyColors(0, 255);
+		_ra2PendingAnimHeaderPalette = false;
+	}
+
 	// FUN_00424d70 clears the target buffer before decoding a frame
 	// unless playback flags contain 0x20. Codec 21 frames in levels like
 	// LEV05/05PLAY.SAN only contain non-zero literals, so stale skipped pixels
diff --git a/engines/scumm/smush/rebel/smush_player_ra2.h b/engines/scumm/smush/rebel/smush_player_ra2.h
index c10a544cb76..415401af885 100644
--- a/engines/scumm/smush/rebel/smush_player_ra2.h
+++ b/engines/scumm/smush/rebel/smush_player_ra2.h
@@ -85,6 +85,7 @@ private:
 	int _ra2FrameSourceSkipY;
 	int _ra2FrameObjectSurfaceWidth;
 	int _ra2FrameObjectSurfaceHeight;
+	bool _ra2PendingAnimHeaderPalette;
 };
 
 } // End of namespace Scumm


Commit: bfe931de30a8d0c3d36a9c9c490a1828a400812d
    https://github.com/scummvm/scummvm/commit/bfe931de30a8d0c3d36a9c9c490a1828a400812d
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-03T14:31:53+02:00

Commit Message:
SCUMM: RA2: correctly center input when a new gameplay section starts

Changed paths:
    engines/scumm/insane/rebel2/levels.cpp
    engines/scumm/insane/rebel2/rebel.cpp
    engines/scumm/insane/rebel2/rebel.h
    engines/scumm/insane/rebel2/runlevels.cpp


diff --git a/engines/scumm/insane/rebel2/levels.cpp b/engines/scumm/insane/rebel2/levels.cpp
index 63d49134bba..8b010dca334 100644
--- a/engines/scumm/insane/rebel2/levels.cpp
+++ b/engines/scumm/insane/rebel2/levels.cpp
@@ -19,6 +19,7 @@
  *
  */
 
+#include "common/events.h"
 #include "common/system.h"
 
 #include "graphics/cursorman.h"
@@ -34,6 +35,15 @@ namespace Scumm {
 static const int kRebel2GameplayAimCenterX = 160;
 static const int kRebel2GameplayAimCenterY = 100;
 
+static void purgeRebel2GameplayInputEvents(Common::EventManager *eventMan) {
+	if (!eventMan)
+		return;
+
+	eventMan->getEventDispatcher()->clearEvents();
+	eventMan->purgeMouseEvents();
+	eventMan->purgeKeyboardEvents();
+}
+
 // ---------------------------------------------------------------------------
 // Level Loading System
 // ---------------------------------------------------------------------------
@@ -174,6 +184,7 @@ void InsaneRebel2::playMissionBriefing() {
 // Resets handler to 0 (no HUD) and sets flags to 0x28 (cinematic + buffer preserve).
 // All wrapper functions (FUN_00417168/4171c5/417ab2/417327) add | 8 before calling FUN_0041f4d0.
 void InsaneRebel2::playCinematic(const char *filename) {
+	_gameplaySectionActive = false;
 	_rebelHandler = 0;
 	_rebelStatusBarSprite = 0;  // No status bar during cinematics
 
@@ -188,6 +199,7 @@ void InsaneRebel2::playCinematic(const char *filename) {
 void InsaneRebel2::playVideoWithText(const char *filename, int textID, int textX, int textY,
                                      int fadeInFrame, int fadeOutFrame) {
 
+	_gameplaySectionActive = false;
 	_rebelHandler = 0;
 	_rebelStatusBarSprite = 0;
 
@@ -258,6 +270,7 @@ void InsaneRebel2::playLevelBegin(int levelId) {
 // playLevelEnd -- Level completion video (FUN_00417327).
 void InsaneRebel2::playLevelEnd(int levelId) {
 
+	_gameplaySectionActive = false;
 	_rebelHandler = 0;
 	_rebelStatusBarSprite = 0;  // No status bar during end cinematic
 
@@ -276,6 +289,7 @@ void InsaneRebel2::playLevelEnd(int levelId) {
 // playLevelRetry -- Retry prompt video (LEVXX/XXRETRY.SAN, FUN_00417168).
 void InsaneRebel2::playLevelRetry(int levelId) {
 
+	_gameplaySectionActive = false;
 	_rebelHandler = 0;
 	_rebelStatusBarSprite = 0;  // Reset for retry - will be set by IACT opcode 6 if needed
 
@@ -294,6 +308,7 @@ void InsaneRebel2::playLevelRetry(int levelId) {
 // playLevelGameOver -- Game over video (FUN_00417ab2).
 void InsaneRebel2::playLevelGameOver(int levelId) {
 
+	_gameplaySectionActive = false;
 	_rebelHandler = 0;
 	_rebelStatusBarSprite = 0;  // No status bar during game over cinematic
 
@@ -367,6 +382,9 @@ void InsaneRebel2::playCreditsSequence() {
 }
 
 void InsaneRebel2::centerGameplayAim() {
+	Common::EventManager *eventMan = _vm->_system->getEventManager();
+	purgeRebel2GameplayInputEvents(eventMan);
+
 	_vm->_mouse.x = kRebel2GameplayAimCenterX;
 	_vm->_mouse.y = kRebel2GameplayAimCenterY;
 
@@ -375,6 +393,7 @@ void InsaneRebel2::centerGameplayAim() {
 	_gamepadAimActive = false;
 
 	smush_warpMouse(kRebel2GameplayAimCenterX, kRebel2GameplayAimCenterY, -1);
+	purgeRebel2GameplayInputEvents(eventMan);
 }
 
 // runLevel -- Main level dispatcher, calls per-level handlers.
@@ -414,7 +433,7 @@ int InsaneRebel2::runLevel(int levelId) {
 	// The original hides the cursor (ShowCursor(0)) and relies on Windows confining
 	// the mouse to the game window. Without locking, the cursor can escape the
 	// ScummVM window making the ship uncontrollable.
-	centerGameplayAim();
+	_gameplaySectionActive = false;
 	CursorMan.showMouse(false);
 	g_system->lockMouse(true);
 
@@ -677,6 +696,7 @@ Common::String InsaneRebel2::selectDeathVideoVariant(int levelId, int phase, int
 // playLevelDeathVariant -- Death video with variant selection.
 void InsaneRebel2::playLevelDeathVariant(int levelId, int phase, int frame) {
 
+	_gameplaySectionActive = false;
 	_rebelHandler = 0;
 	_rebelStatusBarSprite = 0;  // No status bar during death cinematic
 
@@ -703,6 +723,7 @@ void InsaneRebel2::playLevelDeathVariant(int levelId, int phase, int frame) {
 // playLevelRetryVariant -- Phase-specific retry video.
 void InsaneRebel2::playLevelRetryVariant(int levelId, int phase) {
 
+	_gameplaySectionActive = false;
 	_rebelHandler = 0;
 	_rebelStatusBarSprite = 0;  // Reset for retry - will be set by IACT opcode 6 if needed
 
diff --git a/engines/scumm/insane/rebel2/rebel.cpp b/engines/scumm/insane/rebel2/rebel.cpp
index d8dfa9bc0b0..1e5f241014c 100644
--- a/engines/scumm/insane/rebel2/rebel.cpp
+++ b/engines/scumm/insane/rebel2/rebel.cpp
@@ -519,6 +519,7 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
 	_joystickAxisX = 0;
 	_joystickAxisY = 0;
 	_gamepadAimActive = false;
+	_gameplaySectionActive = false;
 	_lastGameplayMenuCloseTime = 0;
 	_lastMenuGamepadNavigationTime = 0;
 
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index c51f4dfa214..d833df9de23 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -468,6 +468,8 @@ public:
 
 	// Reset gameplay aim to the original centered mouse/joystick baseline.
 	void centerGameplayAim();
+	// Tracks consecutive recorded gameplay SANs so wave-loop videos do not recenter aim.
+	bool _gameplaySectionActive;
 
 	// Level state tracking for multi-phase levels
 	int _currentPhase;        // Current gameplay phase (1, 2, 3 for Level 2; 1, 2 for Level 3/6)
diff --git a/engines/scumm/insane/rebel2/runlevels.cpp b/engines/scumm/insane/rebel2/runlevels.cpp
index 77f5c4cbdb0..63dc995ce41 100644
--- a/engines/scumm/insane/rebel2/runlevels.cpp
+++ b/engines/scumm/insane/rebel2/runlevels.cpp
@@ -191,11 +191,15 @@ InsaneRebel2::WaveEndResult InsaneRebel2::processWaveEnd(int16 mask, int16 *budg
 bool InsaneRebel2::playLevelSegment(const char *filename, uint16 flags, bool recordFrame) {
 	SmushPlayer *splayer = ((ScummEngine_v7 *)_vm)->_splayer;
 
-	// Retail reset helpers clear the centered input axes before gameplay starts.
-	// Do the same for playable segments, but keep seamless continuation videos
-	// (0x40, e.g. 13PLAY_B) from snapping the aim point mid-sequence.
-	if (recordFrame && (flags & 0x08) != 0 && (flags & 0x40) == 0)
-		centerGameplayAim();
+	const bool isRecordedGameplay = recordFrame && (flags & 0x08) != 0;
+	if (isRecordedGameplay) {
+		// Center only at the section boundary; looped wave videos are continuations.
+		if (!_gameplaySectionActive && (flags & 0x40) == 0)
+			centerGameplayAim();
+		_gameplaySectionActive = true;
+	} else {
+		_gameplaySectionActive = false;
+	}
 
 	splayer->setCurVideoFlags(flags);
 	splayer->play(filename, 15);


Commit: ddfbc182ef404711cc508543e10a687038825dab
    https://github.com/scummvm/scummvm/commit/ddfbc182ef404711cc508543e10a687038825dab
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-03T14:31:53+02:00

Commit Message:
SCUMM: RA2: refactor nutcracker/font code to isolate it from the rest of the scumm games

Changed paths:
  A engines/scumm/smush/rebel/font_rebel2.cpp
  A engines/scumm/smush/rebel/font_rebel2.h
    engines/scumm/insane/rebel2/iact.cpp
    engines/scumm/insane/rebel2/menu.cpp
    engines/scumm/insane/rebel2/rebel.cpp
    engines/scumm/insane/rebel2/rebel.h
    engines/scumm/insane/rebel2/render.cpp
    engines/scumm/module.mk
    engines/scumm/nut_renderer.cpp
    engines/scumm/smush/rebel/smush_multi_font.cpp
    engines/scumm/smush/rebel/smush_multi_font.h


diff --git a/engines/scumm/insane/rebel2/iact.cpp b/engines/scumm/insane/rebel2/iact.cpp
index cb34ed8fffb..c7d1bf5ca9f 100644
--- a/engines/scumm/insane/rebel2/iact.cpp
+++ b/engines/scumm/insane/rebel2/iact.cpp
@@ -27,8 +27,8 @@
 #include "scumm/scumm_v7.h"
 
 #include "scumm/smush/smush_player.h"
-#include "scumm/smush/smush_font.h"
 #include "scumm/smush/rebel/codec_ra2.h"
+#include "scumm/smush/rebel/font_rebel2.h"
 
 #include "scumm/insane/rebel2/rebel.h"
 
@@ -2588,12 +2588,16 @@ void InsaneRebel2::iactRebel2Opcode9(byte *renderBitmap, Common::SeekableReadStr
 	// "subtitles" setting and the in-game TEXT toggle (same ConfMan key). The chunk is
 	// still fully parsed above so stream consumption is unaffected.
 	if (ConfMan.getBool("subtitles")) {
+		Rebel2FontSet fontSet;
+		fontSet.numFonts = 1;
+		fontSet.fonts[0] = _rebelMsgFont;
+
 		if (textFlags & 0x04) {
 			// Word-wrapped text
-			_rebelMsgFont->drawStringWrap(convertedText, renderBitmap, clipRect, posX, posY, textColor, styleFlags);
+			drawRebel2StringWrap(fontSet, convertedText, dstIdx, renderBitmap, clipRect, posX, posY, width, textColor, styleFlags);
 		} else {
 			// Single-line text
-			_rebelMsgFont->drawString(convertedText, renderBitmap, clipRect, posX, posY, textColor, styleFlags);
+			drawRebel2String(fontSet, convertedText, dstIdx, renderBitmap, clipRect, posX, posY, width, textColor, styleFlags);
 		}
 	}
 
diff --git a/engines/scumm/insane/rebel2/menu.cpp b/engines/scumm/insane/rebel2/menu.cpp
index 83a5f178ebb..b238e163ce7 100644
--- a/engines/scumm/insane/rebel2/menu.cpp
+++ b/engines/scumm/insane/rebel2/menu.cpp
@@ -33,7 +33,7 @@
 #include "scumm/scumm_v7.h"
 
 #include "scumm/smush/smush_player.h"
-#include "scumm/smush/smush_font.h"
+#include "scumm/smush/rebel/font_rebel2.h"
 #include "scumm/smush/rebel/smush_multi_font.h"
 
 #include "scumm/insane/rebel2/rebel.h"
@@ -416,8 +416,7 @@ void InsaneRebel2::drawMenuString(byte *renderBitmap, const char *str, int x, in
 		if (!curFont || c >= curFont->getNumChars()) continue;
 		int charW = curFont->getCharWidth(c);
 		if (x >= 0 && y >= 0 && charW > 0)
-			curFont->drawCharV7(renderBitmap, clipRect, x, y, pitch, curColor,
-			                    kStyleAlignLeft, c, false, false);
+			drawRebel2Char(curFont, renderBitmap, clipRect, x, y, pitch, curColor, c);
 		x += charW;
 	}
 }
diff --git a/engines/scumm/insane/rebel2/rebel.cpp b/engines/scumm/insane/rebel2/rebel.cpp
index 1e5f241014c..eef134ae7aa 100644
--- a/engines/scumm/insane/rebel2/rebel.cpp
+++ b/engines/scumm/insane/rebel2/rebel.cpp
@@ -41,7 +41,7 @@
 #include "scumm/imuse_digi/dimuse_engine.h"
 
 #include "scumm/smush/smush_player.h"
-#include "scumm/smush/smush_font.h"
+#include "scumm/smush/rebel/font_rebel2.h"
 
 #include "scumm/insane/rebel2/rebel.h"
 
@@ -140,7 +140,7 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
 	_smush_cockpitNut = new NutRenderer(_vm, "SYSTM/DISPFONT.NUT");
 
 	// Load DIHIFONT.NUT for in-video messages/subtitles (Opcode 9)
-	_rebelMsgFont = new SmushFont(_vm, "SYSTM/DIHIFONT.NUT", true);
+	_rebelMsgFont = makeRebel2Font(_vm, "SYSTM/DIHIFONT.NUT");
 
 	// Load menu system fonts (from info.md - FUN_403BD0 lines 302-348)
 	// In low resolution mode, fonts are loaded as a linked list:
@@ -148,9 +148,9 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
 	//   Font 1 (^f01): SMALFONT.NUT - Small font for format code switching
 	//   Font 2 (^f02): TITLFONT.NUT - Title font
 	//   Font 3 (^f03): POVFONT.NUT - POV font (not loaded here)
-	_smush_talkfontNut = new NutRenderer(_vm, "SYSTM/TALKFONT.NUT");
-	_smush_smalfontNut = new NutRenderer(_vm, "SYSTM/SMALFONT.NUT");
-	_smush_titlefontNut = new NutRenderer(_vm, "SYSTM/TITLFONT.NUT");
+	_smush_talkfontNut = makeRebel2Font(_vm, "SYSTM/TALKFONT.NUT");
+	_smush_smalfontNut = makeRebel2Font(_vm, "SYSTM/SMALFONT.NUT");
+	_smush_titlefontNut = makeRebel2Font(_vm, "SYSTM/TITLFONT.NUT");
 
 	_pauseOverlayActive = false;
 	memset(_savedPausePalette, 0, sizeof(_savedPausePalette));
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index d833df9de23..45ed0628a66 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -483,7 +483,7 @@ public:
 	NutRenderer *_smush_cockpitNut;
 
 	// Font used for opcode 9 text/subtitle rendering (DIHIFONT / TALKFONT)
-	SmushFont *_rebelMsgFont;
+	NutRenderer *_rebelMsgFont;
 
 	// Menu system fonts (from info.md - FUN_403BD0 font loading)
 	// Low resolution mode font list (stored in DAT_00485058 linked list):
diff --git a/engines/scumm/insane/rebel2/render.cpp b/engines/scumm/insane/rebel2/render.cpp
index 890a19c9166..5724492124b 100644
--- a/engines/scumm/insane/rebel2/render.cpp
+++ b/engines/scumm/insane/rebel2/render.cpp
@@ -28,8 +28,8 @@
 #include "scumm/scumm_v7.h"
 
 #include "scumm/smush/smush_player.h"
-#include "scumm/smush/smush_font.h"
 #include "scumm/smush/rebel/codec_ra2.h"
+#include "scumm/smush/rebel/font_rebel2.h"
 
 #include "scumm/insane/rebel2/rebel.h"
 
@@ -2806,8 +2806,7 @@ void InsaneRebel2::renderTextOverlay(byte *renderBitmap, int pitch, int width, i
 				}
 				int charW = curFont->getCharWidth(c);
 				if (drawX >= 0 && drawY >= 0 && charW > 0) {
-					curFont->drawCharV7(renderBitmap, clipRect, drawX, drawY,
-					                    pitch, curColor, kStyleAlignLeft, c, false, false);
+					drawRebel2Char(curFont, renderBitmap, clipRect, drawX, drawY, pitch, curColor, c);
 				}
 				drawX += charW;
 				lineCharsDrawn++;
diff --git a/engines/scumm/module.mk b/engines/scumm/module.mk
index e7fe13f0263..4f42d417fdc 100644
--- a/engines/scumm/module.mk
+++ b/engines/scumm/module.mk
@@ -156,6 +156,7 @@ MODULE_OBJS += \
 	smush/smush_player.o \
 	smush/rebel/codec_ra1.o \
 	smush/rebel/codec_ra2.o \
+	smush/rebel/font_rebel2.o \
 	smush/rebel/smush_multi_font.o \
 	smush/rebel/smush_player_rebel.o \
 	smush/rebel/smush_player_ra1.o \
diff --git a/engines/scumm/nut_renderer.cpp b/engines/scumm/nut_renderer.cpp
index 5fa5819a613..85cfef2cffd 100644
--- a/engines/scumm/nut_renderer.cpp
+++ b/engines/scumm/nut_renderer.cpp
@@ -196,12 +196,7 @@ void NutRenderer::loadFont(const char *filename) {
 		// If characters have transparency, then bytes just get skipped and
 		// so there may appear some garbage. That's why we have to fill it
 		// with a default color first.
-		//
-		// For codec 44: standard SCUMM v7/v8 fonts use value 2 as the
-		// transparent color. But RA2 codec 44 fonts use value 2 as an actual
-		// glyph color (medium body shade), so we must use 0 instead to avoid
-		// making those pixels invisible during rendering.
-		if (codec == 44 && _vm->_game.id != GID_REBEL2) {
+		if (codec == 44) {
 			memset(_chars[l].src, kSmush44TransparentColor, _chars[l].width * _chars[l].height);
 			_chars[l].transparency = kSmush44TransparentColor;
 		} else {
@@ -315,9 +310,7 @@ void NutRenderer::loadFontFromData(const byte *data, int32 dataSize) {
 
 		decodedPtr += (_chars[l].width * _chars[l].height);
 
-		// Same transparency logic as loadFont: RA2 codec 44 fonts use
-		// value 2 as a glyph color, not as transparency.
-		if (codec == 44 && _vm->_game.id != GID_REBEL2) {
+		if (codec == 44) {
 			memset(_chars[l].src, kSmush44TransparentColor, _chars[l].width * _chars[l].height);
 			_chars[l].transparency = kSmush44TransparentColor;
 		} else {
@@ -438,26 +431,7 @@ int NutRenderer::drawCharV7(byte *buffer, Common::Rect &clipRect, int x, int y,
 	int clipWdth = (_chars[chr].width - width);
 	char color = (col != -1) ? col : 1;
 
-	if (_vm->_game.id == GID_REBEL2) {
-		// RA2 codec 44 font rendering, matching the original behavior:
-		//   - Pixel value 1 → remapped to the caller's text color
-		//   - Other non-transparent values → used as-is (palette indices)
-		// The font's pixel layout: value 4 = black outline (38% of pixels),
-		// value 1 = body (11%, remapped to color), value 3 = gray AA (2%).
-		// The SMUSH video palette reserves indices 0-4 for text rendering:
-		//   0=(0,0,0), 1=(255,255,255), 2=(188,188,188), 3=(128,128,128), 4=(0,0,0).
-		for (int j = minY; j < height; j++) {
-			for (int i = minX; i < width; i++) {
-				int8 value = *src++;
-				if (value == 1)
-					dst[i] = color;
-				else if (value != _chars[chr].transparency)
-					dst[i] = value;
-			}
-			src += clipWdth;
-			dst += pitch;
-		}
-	} else if (_vm->_game.version == 7) {
+	if (_vm->_game.version == 7) {
 		if (hardcodedColors) {
 			for (int j = minY; j < height; j++) {
 				for (int i = minX; i < width; i++) {
diff --git a/engines/scumm/smush/rebel/font_rebel2.cpp b/engines/scumm/smush/rebel/font_rebel2.cpp
new file mode 100644
index 00000000000..d83fbef6585
--- /dev/null
+++ b/engines/scumm/smush/rebel/font_rebel2.cpp
@@ -0,0 +1,504 @@
+/* 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 "scumm/smush/rebel/font_rebel2.h"
+
+#include "common/endian.h"
+#include "common/util.h"
+
+#include "scumm/file.h"
+#include "scumm/nut_renderer.h"
+#include "scumm/scumm.h"
+
+namespace Scumm {
+
+enum {
+	kRebel2MaxStrings = 80
+};
+
+class Rebel2NutRenderer : public NutRenderer {
+public:
+	Rebel2NutRenderer(ScummEngine *vm, const char *filename) : NutRenderer(vm, nullptr) {
+		loadRebel2Font(filename);
+	}
+
+private:
+	void codec44(byte *dst, const byte *src, int width, int height, int pitch);
+	void loadRebel2Font(const char *filename);
+};
+
+void Rebel2NutRenderer::codec44(byte *dst, const byte *src, int width, int height, int pitch) {
+	while (height--) {
+		byte *dstPtrNext = dst + pitch;
+		const byte *srcPtrNext = src + 2 + READ_LE_UINT16(src);
+		src += 2;
+		int len = width;
+		do {
+			int offs = READ_LE_UINT16(src); src += 2;
+			dst += offs;
+			len -= offs;
+			if (len <= 0)
+				break;
+
+			int w = READ_LE_UINT16(src) + 1; src += 2;
+			len -= w;
+			if (len < 0)
+				w += len;
+
+			while (w--) {
+				byte value = *src++;
+				*dst++ = (value == 0xff) ? 0 : value;
+			}
+		} while (len > 0);
+		dst = dstPtrNext;
+		src = srcPtrNext;
+	}
+}
+
+void Rebel2NutRenderer::loadRebel2Font(const char *filename) {
+	ScummFile *file = _vm->instantiateScummFile();
+
+	_vm->openFile(*file, filename);
+	if (!file->isOpen())
+		error("Rebel2NutRenderer::loadRebel2Font() Can't open font file: %s", filename);
+
+	uint32 tag = file->readUint32BE();
+	if (tag != MKTAG('A','N','I','M'))
+		error("Rebel2NutRenderer::loadRebel2Font() there is no ANIM chunk in font header");
+
+	uint32 length = file->readUint32BE();
+	byte *dataSrc = new byte[length];
+	file->read(dataSrc, length);
+	file->close();
+	delete file;
+
+	if (READ_BE_UINT32(dataSrc) != MKTAG('A','H','D','R'))
+		error("Rebel2NutRenderer::loadRebel2Font() there is no AHDR chunk in font header");
+
+	_numChars = READ_LE_UINT16(dataSrc + 10);
+	if (_numChars > ARRAYSIZE(_chars)) {
+		warning("Rebel2NutRenderer::loadRebel2Font(%s) numChars (%d) exceeds max, clamping", filename, _numChars);
+		_numChars = ARRAYSIZE(_chars);
+	}
+
+	uint32 offset = 0;
+	uint32 decodedLength = 0;
+	int l;
+
+	for (l = 0; l < _numChars; l++) {
+		if (offset + 8 > length) {
+			warning("Rebel2NutRenderer::loadRebel2Font(%s) truncated before char %d (offset %x), clamping", filename, l, offset);
+			break;
+		}
+		uint32 chunkSize = READ_BE_UINT32(dataSrc + offset + 4);
+		uint64 nextOffset = (uint64)offset + chunkSize + 16 + (chunkSize & 1);
+		if (nextOffset + 18 > length) {
+			warning("Rebel2NutRenderer::loadRebel2Font(%s) font chunk exceeds file at char %d (offset %x), clamping", filename, l, offset);
+			break;
+		}
+		offset = (uint32)nextOffset;
+		int width = READ_LE_UINT16(dataSrc + offset + 14);
+		_fontHeight = READ_LE_UINT16(dataSrc + offset + 16);
+		decodedLength += width * _fontHeight;
+	}
+
+	if (l < _numChars)
+		_numChars = l;
+
+	if (_numChars <= 0 || decodedLength == 0)
+		error("Rebel2NutRenderer::loadRebel2Font(%s) no decodable characters", filename);
+
+	debug(1, "Rebel2NutRenderer::loadRebel2Font('%s') - decodedLength = %d", filename, decodedLength);
+
+	_decodedData = new byte[decodedLength];
+	byte *decodedPtr = _decodedData;
+
+	offset = 0;
+	for (l = 0; l < _numChars; l++) {
+		if (offset + 8 > length) {
+			warning("Rebel2NutRenderer::loadRebel2Font(%s) invalid font chunk header %d (offset %x), stopping decode", filename, l, offset);
+			break;
+		}
+		uint32 chunkSize = READ_BE_UINT32(dataSrc + offset + 4);
+		uint64 nextOffset = (uint64)offset + chunkSize + 8 + (chunkSize & 1);
+		if (nextOffset + 8 > length) {
+			warning("Rebel2NutRenderer::loadRebel2Font(%s) FRME chunk exceeds file %d (offset %x), stopping decode", filename, l, offset);
+			break;
+		}
+		offset = (uint32)nextOffset;
+		if (READ_BE_UINT32(dataSrc + offset) != MKTAG('F','R','M','E')) {
+			warning("Rebel2NutRenderer::loadRebel2Font(%s) no FRME chunk %d (offset %x), stopping decode", filename, l, offset);
+			break;
+		}
+		offset += 8;
+		if (offset + 22 > length) {
+			warning("Rebel2NutRenderer::loadRebel2Font(%s) FOBJ chunk exceeds file %d (offset %x), stopping decode", filename, l, offset);
+			break;
+		}
+		if (READ_BE_UINT32(dataSrc + offset) != MKTAG('F','O','B','J')) {
+			warning("Rebel2NutRenderer::loadRebel2Font(%s) no FOBJ chunk in FRME chunk %d (offset %x), stopping decode", filename, l, offset);
+			break;
+		}
+
+		int codec = READ_LE_UINT16(dataSrc + offset + 8);
+		_chars[l].xoffs = READ_LE_INT16(dataSrc + offset + 10);
+		_chars[l].yoffs = READ_LE_INT16(dataSrc + offset + 12);
+		_chars[l].width = READ_LE_UINT16(dataSrc + offset + 14);
+		_chars[l].height = READ_LE_UINT16(dataSrc + offset + 16);
+		_chars[l].src = decodedPtr;
+
+		decodedPtr += (_chars[l].width * _chars[l].height);
+
+		memset(_chars[l].src, kDefaultTransparentColor, _chars[l].width * _chars[l].height);
+		_chars[l].transparency = kDefaultTransparentColor;
+
+		const uint8 *fobjptr = dataSrc + offset + 22;
+		switch (codec) {
+		case 1:
+			codec1(_chars[l].src, fobjptr, _chars[l].width, _chars[l].height, _chars[l].width);
+			break;
+		case 21:
+			codec21(_chars[l].src, fobjptr, _chars[l].width, _chars[l].height, _chars[l].width);
+			break;
+		case 44:
+			codec44(_chars[l].src, fobjptr, _chars[l].width, _chars[l].height, _chars[l].width);
+			break;
+		default:
+			error("Rebel2NutRenderer::loadRebel2Font: unknown codec: %d", codec);
+		}
+	}
+
+	delete[] dataSrc;
+}
+
+NutRenderer *makeRebel2Font(ScummEngine *vm, const char *filename) {
+	return new Rebel2NutRenderer(vm, filename);
+}
+
+Rebel2FontSet::Rebel2FontSet() : numFonts(0), defaultFont(0) {
+	memset(fonts, 0, sizeof(fonts));
+}
+
+NutRenderer *Rebel2FontSet::getFont(int id) const {
+	if (numFonts <= 0)
+		return nullptr;
+
+	if (id < 0 || id >= numFonts || fonts[id] == nullptr)
+		id = defaultFont;
+
+	if (id < 0 || id >= numFonts)
+		return nullptr;
+
+	return fonts[id] ? fonts[id] : fonts[0];
+}
+
+static bool parseRebel2FormatCode(const char *str, uint len, uint &pos, int &fontId, int16 &color) {
+	if (pos + 1 >= len || str[pos] != '^')
+		return false;
+
+	const char code = str[pos + 1];
+	if (code == '^') {
+		pos++;
+		return false;
+	}
+
+	if (code == 'f') {
+		pos += 2;
+		int value = 0;
+		while (pos < len && str[pos] >= '0' && str[pos] <= '9') {
+			value = value * 10 + str[pos] - '0';
+			pos++;
+		}
+		fontId = value;
+		return true;
+	}
+
+	if (code == 'c') {
+		pos += 2;
+		int value = 0;
+		while (pos < len && str[pos] >= '0' && str[pos] <= '9') {
+			value = value * 10 + str[pos] - '0';
+			pos++;
+		}
+		color = value;
+		return true;
+	}
+
+	if (code == 'l') {
+		pos += 2;
+		return true;
+	}
+
+	return false;
+}
+
+int drawRebel2Char(NutRenderer *font, byte *buffer, Common::Rect &clipRect, int x, int y,
+		int pitch, int16 col, byte chr) {
+	if (!font || chr >= font->getNumChars())
+		return 0;
+
+	const int charWidth = font->getCharWidth(chr);
+	const int charHeight = font->getCharHeight(chr);
+	int width = MIN(charWidth, clipRect.right - x);
+	int height = MIN(charHeight, clipRect.bottom - y);
+	const int minX = x < clipRect.left ? clipRect.left - x : 0;
+	const int minY = y < clipRect.top ? clipRect.top - y : 0;
+
+	if (width <= 0 || height <= 0)
+		return 0;
+
+	width -= minX;
+	height -= minY;
+	if (width <= 0 || height <= 0)
+		return 0;
+
+	const byte *src = font->getCharData(chr) + minY * charWidth + minX;
+	byte *dst = buffer + pitch * (y + minY) + x + minX;
+	const int color = (col != -1) ? col : 1;
+
+	for (int row = 0; row < height; row++) {
+		for (int column = 0; column < width; column++) {
+			const byte value = src[column];
+			if (value != 0 && value != 0xff)
+				dst[column] = (byte)(value + color - 1);
+		}
+		src += charWidth;
+		dst += pitch;
+	}
+
+	return MIN(charWidth, clipRect.right - x);
+}
+
+int getRebel2StringWidth(const Rebel2FontSet &fontSet, const char *str, uint len) {
+	int width = 0;
+	int fontId = fontSet.defaultFont;
+	int16 color = 0;
+
+	for (uint pos = 0; pos < len;) {
+		if (parseRebel2FormatCode(str, len, pos, fontId, color))
+			continue;
+
+		const byte chr = (byte)str[pos++];
+		if (chr == '\n' || chr == '\r')
+			continue;
+
+		NutRenderer *font = fontSet.getFont(fontId);
+		if (font && chr < font->getNumChars())
+			width += font->getCharWidth(chr);
+	}
+
+	return width;
+}
+
+int getRebel2StringHeight(const Rebel2FontSet &fontSet, const char *str, uint len) {
+	int height = 0;
+	int lineHeight = 0;
+	int fontId = fontSet.defaultFont;
+	int16 color = 0;
+
+	for (uint pos = 0; pos < len;) {
+		if (parseRebel2FormatCode(str, len, pos, fontId, color))
+			continue;
+
+		const byte chr = (byte)str[pos++];
+		if (chr == '\n') {
+			NutRenderer *font = fontSet.getFont(fontId);
+			height += lineHeight ? lineHeight : (font ? font->getFontHeight() : 0);
+			lineHeight = 0;
+		} else if (chr != '\r') {
+			NutRenderer *font = fontSet.getFont(fontId);
+			if (font && chr < font->getNumChars())
+				lineHeight = MAX<int>(lineHeight, font->getCharHeight(chr));
+		}
+	}
+
+	NutRenderer *font = fontSet.getFont(fontId);
+	return height + (lineHeight ? lineHeight : (font ? font->getFontHeight() : 0));
+}
+
+static void drawRebel2Substring(const Rebel2FontSet &fontSet, const char *str, uint len,
+		byte *buffer, Common::Rect &clipRect, int x, int y, int pitch,
+		int &fontId, int16 &color) {
+	for (uint pos = 0; pos < len;) {
+		if (parseRebel2FormatCode(str, len, pos, fontId, color))
+			continue;
+
+		const byte chr = (byte)str[pos++];
+		if (chr == '\n' || chr == '\r')
+			continue;
+
+		NutRenderer *font = fontSet.getFont(fontId);
+		x += drawRebel2Char(font, buffer, clipRect, x, y, pitch, color, chr);
+	}
+}
+
+void drawRebel2String(const Rebel2FontSet &fontSet, const char *str, uint len, byte *buffer,
+		Common::Rect &clipRect, int x, int y, int pitch, int16 col, TextStyleFlags flags) {
+	if (!str || !buffer)
+		return;
+
+	int lineStart = 0;
+	int maxWidth = 0;
+	int yStart = y;
+	int fontId = fontSet.defaultFont;
+	int16 color = col;
+
+	for (uint pos = 0; pos <= len; pos++) {
+		if (pos < len && str[pos] != '\n')
+			continue;
+
+		const int lineLen = pos - lineStart;
+		const int lineWidth = getRebel2StringWidth(fontSet, str + lineStart, lineLen);
+		const int lineHeight = getRebel2StringHeight(fontSet, str + lineStart, lineLen);
+		maxWidth = MAX(maxWidth, lineWidth);
+
+		int drawX = x;
+		if (flags & kStyleAlignCenter)
+			drawX = x - lineWidth / 2;
+		else if (flags & kStyleAlignRight)
+			drawX = x - lineWidth;
+
+		drawRebel2Substring(fontSet, str + lineStart, lineLen, buffer, clipRect, drawX, y, pitch, fontId, color);
+		y += lineHeight;
+		lineStart = pos + 1;
+	}
+
+	clipRect.left = MAX<int>(0, (flags & kStyleAlignCenter) ? x - maxWidth / 2 : ((flags & kStyleAlignRight) ? x - maxWidth : x));
+	clipRect.right = MIN<int>(clipRect.right, clipRect.left + maxWidth);
+	clipRect.top = yStart;
+	clipRect.bottom = y;
+}
+
+void drawRebel2StringWrap(const Rebel2FontSet &fontSet, const char *str, uint len, byte *buffer,
+		Common::Rect &clipRect, int x, int y, int pitch, int16 col, TextStyleFlags flags) {
+	if (!str || !buffer)
+		return;
+
+	int16 substrByteLength[kRebel2MaxStrings];
+	int16 substrWidths[kRebel2MaxStrings];
+	int16 substrStart[kRebel2MaxStrings];
+	memset(substrByteLength, 0, sizeof(substrByteLength));
+	memset(substrWidths, 0, sizeof(substrWidths));
+	memset(substrStart, 0, sizeof(substrStart));
+
+	int16 numSubstrings = 0;
+	int curWidth = 0;
+	int curPos = -1;
+	int maxWidth = 0;
+	int height = 0;
+	int lastSubstrHeight = 0;
+
+	while (curPos < (int)len) {
+		int textStart = curPos + 1;
+		while (textStart < (int)len && str[textStart] == ' ')
+			textStart++;
+
+		const int separatorWidth = curPos > 0 ? getRebel2StringWidth(fontSet, str + curPos, textStart - curPos) : 0;
+
+		int nextSeparatorPos = textStart;
+		while (nextSeparatorPos < (int)len && str[nextSeparatorPos] != ' ' && str[nextSeparatorPos] != '\n')
+			nextSeparatorPos++;
+
+		const int wordWidth = getRebel2StringWidth(fontSet, str + textStart, nextSeparatorPos - textStart);
+		int newWidth = curWidth + separatorWidth + wordWidth;
+
+		if (curWidth && newWidth > clipRect.width()) {
+			if (numSubstrings < kRebel2MaxStrings) {
+				substrWidths[numSubstrings] = curWidth;
+				substrByteLength[numSubstrings] = curPos - substrStart[numSubstrings];
+				numSubstrings++;
+			}
+			newWidth = wordWidth;
+			substrStart[numSubstrings] = textStart;
+		}
+		curWidth = newWidth;
+
+		curPos = nextSeparatorPos;
+		if (curPos >= (int)len || str[curPos] == '\n') {
+			if (numSubstrings < kRebel2MaxStrings) {
+				substrWidths[numSubstrings] = curWidth;
+				substrByteLength[numSubstrings] = curPos - substrStart[numSubstrings];
+				numSubstrings++;
+				if (numSubstrings < kRebel2MaxStrings)
+					substrStart[numSubstrings] = curPos + 1;
+			}
+			curWidth = 0;
+		}
+	}
+
+	if (curWidth && numSubstrings < kRebel2MaxStrings) {
+		substrWidths[numSubstrings] = curWidth;
+		substrByteLength[numSubstrings] = curPos - substrStart[numSubstrings];
+		numSubstrings++;
+	}
+
+	for (int i = 0; i < numSubstrings; i++) {
+		maxWidth = MAX<int>(maxWidth, substrWidths[i]);
+		lastSubstrHeight = substrByteLength[i] > 0 ? getRebel2StringHeight(fontSet, str + substrStart[i], substrByteLength[i]) : 0;
+		height += lastSubstrHeight;
+	}
+
+	const int clipHeight = height + lastSubstrHeight / 2;
+	if (y > clipRect.bottom - clipHeight)
+		y = clipRect.bottom - clipHeight;
+	if (y < clipRect.top)
+		y = clipRect.top;
+
+	if (flags & kStyleAlignCenter) {
+		if (x + (maxWidth >> 1) > clipRect.right)
+			x = clipRect.right - (maxWidth >> 1);
+		if (x - (maxWidth >> 1) < clipRect.left)
+			x = clipRect.left + (maxWidth >> 1);
+	} else if (flags & kStyleAlignRight) {
+		if (x > clipRect.right)
+			x = clipRect.right;
+		if (x < clipRect.left + maxWidth)
+			x = clipRect.left + maxWidth;
+	} else {
+		if (x > clipRect.right - maxWidth)
+			x = clipRect.right - maxWidth;
+		if (x < clipRect.left)
+			x = clipRect.left;
+	}
+
+	const int yStart = y;
+	int fontId = fontSet.defaultFont;
+	int16 color = col;
+
+	for (int i = 0; i < numSubstrings; i++) {
+		int drawX = x;
+		if (flags & kStyleAlignCenter)
+			drawX = x - substrWidths[i] / 2;
+		else if (flags & kStyleAlignRight)
+			drawX = x - substrWidths[i];
+
+		const int lineLen = substrByteLength[i] > 0 ? substrByteLength[i] : 0;
+		drawRebel2Substring(fontSet, str + substrStart[i], lineLen, buffer, clipRect, drawX, y, pitch, fontId, color);
+		y += getRebel2StringHeight(fontSet, str + substrStart[i], lineLen);
+	}
+
+	clipRect.left = MAX<int>(0, (flags & kStyleAlignCenter) ? x - maxWidth / 2 : ((flags & kStyleAlignRight) ? x - maxWidth : x));
+	clipRect.right = MIN<int>(clipRect.right, clipRect.left + maxWidth);
+	clipRect.top = yStart;
+	clipRect.bottom = y;
+}
+
+} // End of namespace Scumm
diff --git a/engines/scumm/smush/rebel/font_rebel2.h b/engines/scumm/smush/rebel/font_rebel2.h
new file mode 100644
index 00000000000..2a5ae2f7ae7
--- /dev/null
+++ b/engines/scumm/smush/rebel/font_rebel2.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 SCUMM_SMUSH_REBEL_FONT_REBEL2_H
+#define SCUMM_SMUSH_REBEL_FONT_REBEL2_H
+
+#include "common/scummsys.h"
+#include "common/rect.h"
+
+#include "scumm/charset_v7.h"
+
+namespace Scumm {
+
+class ScummEngine;
+class NutRenderer;
+
+struct Rebel2FontSet {
+	enum {
+		kMaxFonts = 5
+	};
+
+	NutRenderer *fonts[kMaxFonts];
+	int numFonts;
+	int defaultFont;
+
+	Rebel2FontSet();
+	NutRenderer *getFont(int id) const;
+};
+
+NutRenderer *makeRebel2Font(ScummEngine *vm, const char *filename);
+int drawRebel2Char(NutRenderer *font, byte *buffer, Common::Rect &clipRect, int x, int y,
+		int pitch, int16 col, byte chr);
+int getRebel2StringWidth(const Rebel2FontSet &fontSet, const char *str, uint len);
+int getRebel2StringHeight(const Rebel2FontSet &fontSet, const char *str, uint len);
+void drawRebel2String(const Rebel2FontSet &fontSet, const char *str, uint len, byte *buffer,
+		Common::Rect &clipRect, int x, int y, int pitch, int16 col, TextStyleFlags flags);
+void drawRebel2StringWrap(const Rebel2FontSet &fontSet, const char *str, uint len, byte *buffer,
+		Common::Rect &clipRect, int x, int y, int pitch, int16 col, TextStyleFlags flags);
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/smush/rebel/smush_multi_font.cpp b/engines/scumm/smush/rebel/smush_multi_font.cpp
index 9c4205b7a65..47193e58a6e 100644
--- a/engines/scumm/smush/rebel/smush_multi_font.cpp
+++ b/engines/scumm/smush/rebel/smush_multi_font.cpp
@@ -28,10 +28,13 @@ namespace Scumm {
 
 SmushMultiFont::SmushMultiFont(ScummEngine *vm, SmushPlayer *player, bool useOriginalColors)
 	: _vm(vm), _player(player), _currentFont(0), _defaultFont(0), _hardcodedFontColors(useOriginalColors) {
+	memset(_rebel2Fonts, 0, sizeof(_rebel2Fonts));
 	_textRenderer = new TextRenderer_v7(vm, this);
 }
 
 SmushMultiFont::~SmushMultiFont() {
+	for (int i = 0; i < ARRAYSIZE(_rebel2Fonts); i++)
+		delete _rebel2Fonts[i];
 	delete _textRenderer;
 }
 
@@ -47,20 +50,54 @@ NutRenderer *SmushMultiFont::getCurrentFont() const {
 	return const_cast<SmushMultiFont*>(this)->getFont(_currentFont);
 }
 
+Rebel2FontSet SmushMultiFont::getRebel2FontSet() {
+	static const char *ra2Fonts[] = {
+		"SYSTM/TALKFONT.NUT",
+		"SYSTM/SMALFONT.NUT",
+		"SYSTM/TITLFONT.NUT",
+		"SYSTM/POVFONT.NUT"
+	};
+
+	Rebel2FontSet fontSet;
+	fontSet.numFonts = ARRAYSIZE(ra2Fonts);
+	fontSet.defaultFont = CLIP<int>(_defaultFont, 0, fontSet.numFonts - 1);
+	for (int i = 0; i < fontSet.numFonts; i++) {
+		if (!_rebel2Fonts[i])
+			_rebel2Fonts[i] = makeRebel2Font(_vm, ra2Fonts[i]);
+		fontSet.fonts[i] = _rebel2Fonts[i];
+	}
+	return fontSet;
+}
+
 void SmushMultiFont::drawString(const char *str, byte *buffer, Common::Rect &clipRect, int x, int y, int16 col, TextStyleFlags flags) {
 	// Reset to default font before drawing
 	_currentFont = _defaultFont;
+	if (_vm->_game.id == GID_REBEL2) {
+		Rebel2FontSet fontSet = getRebel2FontSet();
+		drawRebel2String(fontSet, str, strlen(str), buffer, clipRect, x, y, _vm->_screenWidth, col, flags);
+		return;
+	}
 	_textRenderer->drawString(str, buffer, clipRect, x, y, _vm->_screenWidth, col, flags);
 }
 
 void SmushMultiFont::drawString(const char *str, byte *buffer, Common::Rect &clipRect, int x, int y, int pitch, int16 col, TextStyleFlags flags) {
 	_currentFont = _defaultFont;
+	if (_vm->_game.id == GID_REBEL2) {
+		Rebel2FontSet fontSet = getRebel2FontSet();
+		drawRebel2String(fontSet, str, strlen(str), buffer, clipRect, x, y, pitch, col, flags);
+		return;
+	}
 	_textRenderer->drawString(str, buffer, clipRect, x, y, pitch, col, flags);
 }
 
 void SmushMultiFont::drawStringWrap(const char *str, byte *buffer, Common::Rect &clipRect, int x, int y, int16 col, TextStyleFlags flags) {
 	// Reset to default font before drawing
 	_currentFont = _defaultFont;
+	if (_vm->_game.id == GID_REBEL2) {
+		Rebel2FontSet fontSet = getRebel2FontSet();
+		drawRebel2StringWrap(fontSet, str, strlen(str), buffer, clipRect, x, y, _vm->_screenWidth, col, flags);
+		return;
+	}
 	_textRenderer->drawStringWrap(str, buffer, clipRect, x, y, _vm->_screenWidth, col, flags);
 }
 
@@ -80,6 +117,11 @@ int SmushMultiFont::draw2byte(byte *buffer, Common::Rect &clipRect, int x, int y
 }
 
 int SmushMultiFont::drawCharV7(byte *buffer, Common::Rect &clipRect, int x, int y, int pitch, int16 col, TextStyleFlags flags, byte chr) {
+	if (_vm->_game.id == GID_REBEL2) {
+		Rebel2FontSet fontSet = getRebel2FontSet();
+		return drawRebel2Char(fontSet.getFont(_currentFont), buffer, clipRect, x, y, pitch, col, chr);
+	}
+
 	NutRenderer *font = getCurrentFont();
 	if (!font)
 		return 0;
diff --git a/engines/scumm/smush/rebel/smush_multi_font.h b/engines/scumm/smush/rebel/smush_multi_font.h
index 6c57acbe57f..1f7fba58487 100644
--- a/engines/scumm/smush/rebel/smush_multi_font.h
+++ b/engines/scumm/smush/rebel/smush_multi_font.h
@@ -25,6 +25,7 @@
 #include "common/scummsys.h"
 #include "scumm/nut_renderer.h"
 #include "scumm/scumm.h"
+#include "scumm/smush/rebel/font_rebel2.h"
 #include "scumm/string_v7.h"
 
 namespace Scumm {
@@ -70,10 +71,12 @@ public:
 private:
 	NutRenderer *getFont(int id);
 	NutRenderer *getCurrentFont() const;
+	Rebel2FontSet getRebel2FontSet();
 
 	ScummEngine *_vm;
 	SmushPlayer *_player;
 	TextRenderer_v7 *_textRenderer;
+	NutRenderer *_rebel2Fonts[Rebel2FontSet::kMaxFonts];
 
 	int _currentFont;
 	int _defaultFont;




More information about the Scummvm-git-logs mailing list