[Scummvm-git-logs] scummvm master -> 33894ae5045be959280dec14c124d66fdbc84ce6

bluegr noreply at scummvm.org
Mon Apr 13 14:10:39 UTC 2026


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

Summary:
635d855848 AGOS: PN Amiga improvements and quick save/load
aeb3751590 AGOS: Simplify PN Amiga width handling
0629e6e220 AGOS: Back out of quicksave changes, swallow input instead
33894ae504 AGOS: Move topaz 9 double height draw from AGOS to amigafont


Commit: 635d8558484924f39e06e8cf15a6fccf25a2d520
    https://github.com/scummvm/scummvm/commit/635d8558484924f39e06e8cf15a6fccf25a2d520
Author: Robert Megone (robert.megone at gmail.com)
Date: 2026-04-13T17:10:32+03:00

Commit Message:
AGOS: PN Amiga improvements and quick save/load

Changed paths:
    engines/agos/agos.cpp
    engines/agos/agos.h
    engines/agos/charset-fontdata.cpp
    engines/agos/charset.cpp
    engines/agos/cursor.cpp
    engines/agos/gfx.cpp
    engines/agos/input_pn.cpp
    engines/agos/pn.cpp
    engines/agos/saveload.cpp
    engines/agos/script_pn.cpp
    engines/agos/string_pn.cpp
    engines/agos/window.cpp
    graphics/fonts/amigafont.cpp
    graphics/fonts/amigafont.h


diff --git a/engines/agos/agos.cpp b/engines/agos/agos.cpp
index b04b4c230d8..0ef6a06a1c2 100644
--- a/engines/agos/agos.cpp
+++ b/engines/agos/agos.cpp
@@ -38,6 +38,7 @@
 
 #include "graphics/surface.h"
 #include "graphics/sjis.h"
+#include "graphics/fonts/amigafont.h"
 
 #include "audio/mididrv.h"
 
@@ -312,6 +313,7 @@ AGOSEngine::AGOSEngine(OSystem *system, const AGOSGameDescription *gd)
 
 	_curWindow = 0;
 	_textWindow = nullptr;
+	_pnAmigaFont = nullptr;
 
 	_subjectItem = nullptr;
 	_objectItem = nullptr;
@@ -412,6 +414,7 @@ AGOSEngine::AGOSEngine(OSystem *system, const AGOSGameDescription *gd)
 	_vgaFrozenBase = nullptr;
 	_vgaRealBase = nullptr;
 	_zoneBuffers = nullptr;
+	_pnAmigaUiVisible = false;
 
 	_curVgaFile1 = nullptr;
 	_curVgaFile2 = nullptr;
@@ -612,11 +615,15 @@ Common::Error AGOSEngine::init() {
 		_screenHeight = 480;
 	} else {
 		_screenWidth = 320;
-		_screenHeight = 200;
+		_screenHeight = isPnAmiga() ? 240 : 200;
 	}
 
 	_internalWidth = _screenWidth;
 	_internalHeight = _screenHeight;
+	if (isPnAmiga()) {
+		_internalWidth = 640;
+		_internalHeight = 480;
+	}
 
 	if (getPlatform() == Common::kPlatformPC98) {
 		_internalWidth <<= 1;
@@ -665,11 +672,16 @@ Common::Error AGOSEngine::init() {
 	_backGroundBuf = new Graphics::Surface();
 	_backGroundBuf->create(_screenWidth, _screenHeight, Graphics::PixelFormat::createFormatCLUT8());
 
-	if (getGameType() == GType_FF || getGameType() == GType_PP || (getGameType() == GType_ELVIRA1 && getPlatform() == Common::kPlatformPC98)) {
+	if (getGameType() == GType_FF || getGameType() == GType_PP ||
+			(getGameType() == GType_ELVIRA1 && getPlatform() == Common::kPlatformPC98) || isPnAmiga()) {
 		_backBuf = new Graphics::Surface();
 		_backBuf->create(_screenWidth, _screenHeight, Graphics::PixelFormat::createFormatCLUT8());
+		memset(_backBuf->getPixels(), 0, _backBuf->pitch * _backBuf->h);
 		_scaleBuf = new Graphics::Surface();
-		_scaleBuf->create(_internalWidth, _internalHeight, Graphics::PixelFormat::createFormatCLUT8());
+		const uint16 scaleWidth = isPnAmiga() ? 640 : _internalWidth;
+		const uint16 scaleHeight = isPnAmiga() ? _screenHeight : _internalHeight;
+		_scaleBuf->create(scaleWidth, scaleHeight, Graphics::PixelFormat::createFormatCLUT8());
+		memset(_scaleBuf->getPixels(), 0, _scaleBuf->pitch * _scaleBuf->h);
 	}
 
 	if (getGameType() == GType_SIMON2) {
@@ -963,10 +975,111 @@ void AGOSEngine::setupGame() {
 	if (getGameType() == GType_ELVIRA2 && getPlatform() == Common::kPlatformAtariST) {
 		_videoWindows[9] = 75;
 	}
+	if (isPnAmiga()) {
+		_videoWindows[3] = 134;
+		_videoWindows[7] = 134;
+		_videoWindows[11] = 134;
+		_videoWindows[15] = 240;
+		if (_pnAmigaFont == nullptr)
+			_pnAmigaFont = new Graphics::AmigaFont(Graphics::AmigaFont::kTopaz9Builtin);
+	}
+}
+
+
+bool AGOSEngine::isPnAmiga() const {
+	return getGameType() == GType_PN && getPlatform() == Common::kPlatformAmiga;
+}
+
+bool AGOSEngine::usePnAmigaDoubleHeightTopaz() const {
+	return ConfMan.hasKey("pn_amiga_doubleheight_font") && ConfMan.getBool("pn_amiga_doubleheight_font");
 }
 
+const Graphics::AmigaFont *AGOSEngine::getPnAmigaFont() const {
+	return _pnAmigaFont;
+}
+
+uint16 AGOSEngine::getPnAmigaGlyphAdvance(byte chr) const {
+	const Graphics::AmigaFont *font = getPnAmigaFont();
+	if (font == nullptr)
+		return 0;
+	if (chr == 128 || chr == 129)
+		return font->getCharAdvanceWidth(' ');
+	if (chr < font->getLoChar() || chr > font->getHiChar())
+		return 0;
+
+	return font->getCharAdvanceWidth(chr);
+}
+
+uint16 AGOSEngine::getPnAmigaGlyphRenderWidth(byte chr) const {
+	const Graphics::AmigaFont *font = getPnAmigaFont();
+	if (font == nullptr)
+		return 0;
+	if (chr == 128 && isPnAmiga())
+		chr = '_';
+	if (chr == 128 || chr == 129)
+		return getPnAmigaGlyphAdvance(' ');
+	if (chr < font->getLoChar() || chr > font->getHiChar())
+		return 0;
+
+	return font->getCharRenderWidth(chr);
+}
+
+uint16 AGOSEngine::getPnAmigaGlyphHeight() const {
+	const Graphics::AmigaFont *font = getPnAmigaFont();
+	const uint16 rawHeight = font ? font->getFontHeight() : 9;
+	return usePnAmigaDoubleHeightTopaz() ? rawHeight * 2 : rawHeight;
+}
+
+void AGOSEngine::ensurePnAmigaTextPlanes() {
+	if (!isPnAmiga())
+		return;
+	const WindowBlock *mainWindow = _windowArray[0];
+	const WindowBlock *inputWindow = _windowArray[2];
+	if (mainWindow == nullptr || inputWindow == nullptr)
+		return;
+
+	const uint16 mainWidth = getPnAmigaTextPlaneWidth(mainWindow);
+	const uint16 mainHeight = getPnAmigaWindowInteriorHeight(mainWindow);
+	const uint16 inputWidth = getPnAmigaTextPlaneWidth(inputWindow);
+	const uint16 inputHeight = getPnAmigaWindowInteriorHeight(inputWindow);
+
+	if (_pnAmigaMainTextPlane.pixels == nullptr || _pnAmigaMainTextPlane.width != mainWidth || _pnAmigaMainTextPlane.height != mainHeight) {
+		delete[] _pnAmigaMainTextPlane.pixels;
+		_pnAmigaMainTextPlane.width = mainWidth;
+		_pnAmigaMainTextPlane.height = mainHeight;
+		_pnAmigaMainTextPlane.pixels = new byte[mainWidth * mainHeight];
+		memset(_pnAmigaMainTextPlane.pixels, 0, mainWidth * mainHeight);
+	}
+
+	if (_pnAmigaInputTextPlane.pixels == nullptr || _pnAmigaInputTextPlane.width != inputWidth || _pnAmigaInputTextPlane.height != inputHeight) {
+		delete[] _pnAmigaInputTextPlane.pixels;
+		_pnAmigaInputTextPlane.width = inputWidth;
+		_pnAmigaInputTextPlane.height = inputHeight;
+		_pnAmigaInputTextPlane.pixels = new byte[inputWidth * inputHeight];
+		memset(_pnAmigaInputTextPlane.pixels, 0, inputWidth * inputHeight);
+	}
+}
+
+AGOSEngine::PnAmigaTextPlane *AGOSEngine::getPnAmigaTextPlane(const WindowBlock *window) {
+	if (isPnAmigaMainTextWindow(window))
+		return &_pnAmigaMainTextPlane;
+	if (isPnAmigaInputWindow(window))
+		return &_pnAmigaInputTextPlane;
+	return nullptr;
+}
+
+const AGOSEngine::PnAmigaTextPlane *AGOSEngine::getPnAmigaTextPlane(const WindowBlock *window) const {
+	if (isPnAmigaMainTextWindow(window))
+		return &_pnAmigaMainTextPlane;
+	if (isPnAmigaInputWindow(window))
+		return &_pnAmigaInputTextPlane;
+	return nullptr;
+}
+
+
 AGOSEngine::~AGOSEngine() {
 	_system->getAudioCDManager()->stop();
+	delete _pnAmigaFont;
 
 	for (uint i = 0; i < _itemHeap.size(); i++) {
 		delete[] _itemHeap[i];
@@ -984,6 +1097,8 @@ AGOSEngine::~AGOSEngine() {
 	free(_roomsList);
 	free(_roomStates);
 	free(_stringTabPtr);
+	delete[] _pnAmigaMainTextPlane.pixels;
+	delete[] _pnAmigaInputTextPlane.pixels;
 	free(_strippedTxtMem);
 	free(_tblList);
 	free(_textMem);
diff --git a/engines/agos/agos.h b/engines/agos/agos.h
index 2a25cdf2163..fc054b3cbb8 100644
--- a/engines/agos/agos.h
+++ b/engines/agos/agos.h
@@ -62,6 +62,7 @@ class SeekableReadStream;
 namespace Graphics {
 struct Surface;
 class FontSJIS;
+class AmigaFont;
 }
 
 namespace Audio {
@@ -540,6 +541,20 @@ protected:
 
 	VgaTimerEntry *_nextVgaTimerToProcess;
 
+	struct PnAmigaTextPlane {
+		byte *pixels;
+		uint16 width, height;
+		PnAmigaTextPlane() : pixels(nullptr), width(0), height(0) {}
+	};
+
+	enum {
+		kPnAmigaTextStartX = 2,
+		kPnAmigaLowresWidth = 320,
+		kPnAmigaMainTextTop = 136,
+		kPnAmigaInputTop = 224,
+		kPnAmigaTextPlaneWidth = 552
+	};
+
 	uint8 _opcode177Var1, _opcode177Var2;
 	uint8 _opcode178Var1, _opcode178Var2;
 
@@ -561,6 +576,10 @@ protected:
 
 	WindowBlock *_dummyWindow;
 	WindowBlock *_windowArray[80];
+	Graphics::AmigaFont *_pnAmigaFont;
+	bool _pnAmigaUiVisible;
+	PnAmigaTextPlane _pnAmigaMainTextPlane;
+	PnAmigaTextPlane _pnAmigaInputTextPlane;
 
 	byte _fcsData1[8];
 	bool _fcsData2[8];
@@ -702,6 +721,27 @@ protected:
 	void decompressData(const char *srcName, byte *dst, uint32 offset, uint32 srcSize, uint32 dstSize);
 	void decompressPN(Common::Stack<uint32> &dataList, uint8 *&dataOut, int &dataOutSize);
 	void drawPnSqueezedChar(WindowBlock *window, uint x, uint y, byte chr);
+	bool isPnAmiga() const;
+	bool isPnAmigaMainTextWindow(const WindowBlock *window) const;
+	bool isPnAmigaInputWindow(const WindowBlock *window) const;
+	bool isPnAmigaTextWindow(const WindowBlock *window) const;
+	const Graphics::AmigaFont *getPnAmigaFont() const;
+	uint16 getPnAmigaWindowInteriorHeight(const WindowBlock *window) const;
+	uint16 getPnAmigaTextPlaneWidth(const WindowBlock *window) const;
+	uint16 getPnAmigaTextLineStep() const;
+	uint16 getPnAmigaGlyphAdvance(byte chr) const;
+	uint16 getPnAmigaGlyphRenderWidth(byte chr) const;
+	uint16 getPnAmigaGlyphHeight() const;
+	bool usePnAmigaDoubleHeightTopaz() const;
+	void ensurePnAmigaTextPlanes();
+	PnAmigaTextPlane *getPnAmigaTextPlane(const WindowBlock *window);
+	const PnAmigaTextPlane *getPnAmigaTextPlane(const WindowBlock *window) const;
+	void clearPnAmigaTextPlane(WindowBlock *window);
+	void compositePnAmigaTextPlane(WindowBlock *window);
+	void scrollPnAmigaTextPlane(WindowBlock *window);
+	void drawPnAmigaTopazChar(WindowBlock *window, byte chr);
+	void drawPnAmigaTextWindowBorders();
+
 	void loadOffsets(const char *filename, int number, uint32 &file, uint32 &offset, uint32 &compressedSize, uint32 &size);
 	void loadSound(uint16 sound, int16 pan, int16 vol, uint16 type);
 	void playSfx(uint16 sound, uint16 freq, uint16 flags, bool digitalOnly = false, bool midiOnly = false);
@@ -1412,6 +1452,7 @@ public:
 	void setupGame() override;
 	void setupOpcodes() override;
 	void setupVideoOpcodes(VgaOpcodeProc *op) override;
+	void quickLoadOrSave() override;
 	void windowDrawChar(WindowBlock *window, uint x, uint y, byte chr) override;
 	void saveInventoryPalette();
 	void applyInventoryPalette();
diff --git a/engines/agos/charset-fontdata.cpp b/engines/agos/charset-fontdata.cpp
index 868d297efd5..14fa4cee8ee 100644
--- a/engines/agos/charset-fontdata.cpp
+++ b/engines/agos/charset-fontdata.cpp
@@ -28,6 +28,7 @@
 
 #include "graphics/surface.h"
 #include "graphics/sjis.h"
+#include "graphics/fonts/amigafont.h"
 
 namespace AGOS {
 
@@ -3060,6 +3061,80 @@ static inline void pnSqueezeGlyph8Rows(const byte *src8, byte *dst8) {
 		dst8[i] = pnSqueezeRow(src8[i], anyBit1SetAcrossGlyph);
 }
 
+static void drawPnAmigaDoubleHeightTopazChar(const Graphics::AmigaFont *font, Graphics::Surface *dst, byte chr, int x, int y, byte color) {
+	enum {
+		kPnAmigaTopazScratchWidth = 16,
+		kPnAmigaTopazScratchHeight = 16
+	};
+
+	const int glyphWidth = font->getCharRenderWidth(chr);
+	const int rawHeight = font->getFontHeight();
+	const int drawOffset = font->getCharDrawOffset(chr);
+	const int glyphLeft = MIN<int>(0, drawOffset);
+	const int glyphOriginX = drawOffset - glyphLeft;
+	if (glyphWidth <= 0 || rawHeight <= 0)
+		return;
+	assert(glyphWidth <= kPnAmigaTopazScratchWidth);
+	assert(rawHeight <= kPnAmigaTopazScratchHeight);
+
+	byte glyphPixels[kPnAmigaTopazScratchWidth * kPnAmigaTopazScratchHeight];
+	memset(glyphPixels, 0, sizeof(glyphPixels));
+
+	Graphics::Surface glyphSurface;
+	glyphSurface.init(glyphWidth, rawHeight, glyphWidth, glyphPixels, Graphics::PixelFormat::createFormatCLUT8());
+	font->drawChar(&glyphSurface, chr, glyphOriginX, 0, 1);
+
+	for (int srcY = 0; srcY < rawHeight; ++srcY) {
+		for (int srcX = 0; srcX < glyphWidth; ++srcX) {
+			if (glyphPixels[srcY * glyphWidth + srcX] == 0)
+				continue;
+
+			const int dstX = x + glyphLeft + srcX;
+			if (dstX < 0 || dstX >= dst->w)
+				continue;
+
+			for (int repeat = 0; repeat < 2; ++repeat) {
+				const int dstY = y + srcY * 2 + repeat;
+				if (dstY >= 0 && dstY < dst->h)
+					*(byte *)dst->getBasePtr(dstX, dstY) = color;
+			}
+		}
+	}
+}
+
+void AGOSEngine::drawPnAmigaTopazChar(WindowBlock *window, byte chr) {
+	PnAmigaTextPlane *plane = getPnAmigaTextPlane(window);
+	if (plane == nullptr || plane->pixels == nullptr)
+		return;
+
+	Graphics::Surface surface;
+	surface.init(plane->width, plane->height, plane->width, plane->pixels, Graphics::PixelFormat::createFormatCLUT8());
+
+	const int x = window->textColumn;
+	const int y = window->textRow;
+	const int glyphWidth = getPnAmigaGlyphAdvance(' ');
+	const int fontHeight = getPnAmigaGlyphHeight();
+	if (chr == 128 && isPnAmigaInputWindow(window)) {
+		chr = '_';
+	}
+
+	if (chr == 128 || chr == 129) {
+		surface.fillRect(Common::Rect(x, y, x + glyphWidth, y + fontHeight), window->textColor);
+		return;
+	}
+
+	const Graphics::AmigaFont *font = getPnAmigaFont();
+	if (font == nullptr)
+		return;
+	if (chr < font->getLoChar() || chr > font->getHiChar())
+		return;
+
+	if (usePnAmigaDoubleHeightTopaz())
+		drawPnAmigaDoubleHeightTopazChar(font, &surface, chr, x, y, window->textColor);
+	else
+		font->drawChar(&surface, chr, x + font->getCharDrawOffset(chr), y, window->textColor);
+}
+
 void AGOSEngine::drawPnSqueezedChar(WindowBlock *window, uint x, uint y, byte chr) {
 	const byte *src;
 	byte color, *dst;
@@ -3103,6 +3178,15 @@ void AGOSEngine::drawPnSqueezedChar(WindowBlock *window, uint x, uint y, byte ch
 }
 
 void AGOSEngine_PN::windowDrawChar(WindowBlock *window, uint x, uint y, byte chr) {
+	if (isPnAmigaTextWindow(window)) {
+		(void)x;
+		(void)y;
+		_videoLockOut |= 0x8000;
+		drawPnAmigaTopazChar(window, chr);
+		compositePnAmigaTextPlane(window);
+		_videoLockOut &= ~0x8000;
+		return;
+	}
 	_videoLockOut |= 0x8000;
 	drawPnSqueezedChar(window, x, y, chr);
 	_videoLockOut &= ~0x8000;
diff --git a/engines/agos/charset.cpp b/engines/agos/charset.cpp
index 05451427c00..de1ccee14ec 100644
--- a/engines/agos/charset.cpp
+++ b/engines/agos/charset.cpp
@@ -134,6 +134,10 @@ void AGOSEngine::justifyStart() {
 	} else {
 		_printCharCurPos = _textWindow->textLength;
 		_printCharMaxPos = _textWindow->textMaxLength;
+		if (isPnAmigaTextWindow(_textWindow)) {
+			_printCharCurPos = _textWindow->textColumn;
+			_printCharMaxPos = getPnAmigaTextPlaneWidth(_textWindow);
+		}
 	}
 	_printCharPixelCount = 0;
 	_numLettersToPrint = 0;
@@ -141,6 +145,8 @@ void AGOSEngine::justifyStart() {
 }
 
 void AGOSEngine::justifyOutPut(byte chr) {
+	const bool pnAmigaText = isPnAmigaTextWindow(_textWindow);
+
 	if (chr == 12) {
 		_numLettersToPrint = 0;
 		_printCharCurPos = 0;
@@ -154,6 +160,10 @@ void AGOSEngine::justifyOutPut(byte chr) {
 		doOutput(_lettersToPrintBuf, 1);
 	} else if (chr == 0 || chr == ' ' || chr == 10) {
 		bool fit;
+		int16 separatorWidth = (getGameType() == GType_FF || getGameType() == GType_PP) ? getFeebleFontSize(chr) : 1;
+
+		if (pnAmigaText && chr == ' ')
+			separatorWidth = getPnAmigaGlyphAdvance(chr);
 
 		if (getGameType() == GType_FF || getGameType() == GType_PP) {
 			fit = _printCharMaxPos - _printCharCurPos > _printCharPixelCount;
@@ -166,14 +176,14 @@ void AGOSEngine::justifyOutPut(byte chr) {
 			doOutput(_lettersToPrintBuf, _numLettersToPrint);
 
 			if (_printCharCurPos == _printCharMaxPos) {
-				_printCharCurPos = 0;
+				_printCharCurPos = pnAmigaText ? kPnAmigaTextStartX : 0;
 			} else {
 				if (chr)
 					doOutput(&chr, 1);
 				if (chr == 10)
-					_printCharCurPos = 0;
+					_printCharCurPos = pnAmigaText ? kPnAmigaTextStartX : 0;
 				else if (chr != 0)
-					_printCharCurPos += (getGameType() == GType_FF || getGameType() == GType_PP) ? getFeebleFontSize(chr) : 1;
+					_printCharCurPos += separatorWidth;
 			}
 		} else {
 			const byte newline_character = 10;
@@ -182,17 +192,23 @@ void AGOSEngine::justifyOutPut(byte chr) {
 			doOutput(_lettersToPrintBuf, _numLettersToPrint);
 			if (chr == ' ') {
 				doOutput(&chr, 1);
-				_printCharCurPos += (getGameType() == GType_FF || getGameType() == GType_PP) ? getFeebleFontSize(chr) : 1;
+				_printCharCurPos += separatorWidth;
 			} else {
 				doOutput(&chr, 1);
-				_printCharCurPos = 0;
+				_printCharCurPos = pnAmigaText ? kPnAmigaTextStartX : 0;
 			}
 		}
 		_numLettersToPrint = 0;
 		_printCharPixelCount = 0;
 	} else {
+		int16 charWidth = (getGameType() == GType_FF || getGameType() == GType_PP) ? getFeebleFontSize(chr) : 1;
+
 		_lettersToPrintBuf[_numLettersToPrint++] = chr;
-		_printCharPixelCount += (getGameType() == GType_FF || getGameType() == GType_PP) ? getFeebleFontSize(chr) : 1;
+
+		if (pnAmigaText)
+			charWidth = getPnAmigaGlyphAdvance(chr);
+
+		_printCharPixelCount += charWidth;
 	}
 }
 
@@ -221,6 +237,37 @@ void AGOSEngine::windowPutChar(WindowBlock *window, byte c, byte b) {
 	byte width = 6;
 	byte textColumnWidth = 8;
 
+	if (isPnAmigaTextWindow(window)) {
+		ensurePnAmigaTextPlanes();
+		if (c == 12) {
+			clearWindow(window);
+		} else if (c == 13 || c == 10) {
+			windowNewLine(window);
+		} else if ((c == 1 || c == 8) && window->textColumn > kPnAmigaTextStartX) {
+			window->textColumn = MAX<int16>(kPnAmigaTextStartX, window->textColumn - (int16)getPnAmigaGlyphAdvance(' '));
+		} else if (c >= 32) {
+			const PnAmigaTextPlane *plane = getPnAmigaTextPlane(window);
+			const uint16 advance = getPnAmigaGlyphAdvance(c);
+			const uint16 renderWidth = getPnAmigaGlyphRenderWidth(c);
+			const uint16 cursorWidth = isPnAmigaInputWindow(window) ? getPnAmigaGlyphRenderWidth('_') : 0;
+			const uint16 drawWidth = isPnAmigaInputWindow(window) ? MAX<uint16>(renderWidth, advance + cursorWidth) : renderWidth;
+			if (plane == nullptr)
+				return;
+			if (advance == 0 && renderWidth == 0)
+				return;
+			if (window->textColumn + drawWidth > plane->width) {
+				if (isPnAmigaInputWindow(window))
+					return;
+				windowNewLine(window);
+			}
+			drawPnAmigaTopazChar(window, c);
+			compositePnAmigaTextPlane(window);
+			window->textColumn += advance;
+			window->textLength++;
+		}
+		return;
+	}
+
 	if (c == 12) {
 		clearWindow(window);
 	} else if (c == 13 || c == 10) {
@@ -333,6 +380,23 @@ void AGOSEngine_Feeble::windowNewLine(WindowBlock *window) {
 #endif
 
 void AGOSEngine::windowNewLine(WindowBlock *window) {
+	if (isPnAmigaTextWindow(window)) {
+		window->textColumn = kPnAmigaTextStartX;
+		window->textLength = 0;
+		if (isPnAmigaInputWindow(window)) {
+			clearPnAmigaTextPlane(window);
+			const int16 top = ((int16)getPnAmigaWindowInteriorHeight(window) - (int16)getPnAmigaGlyphHeight()) / 2;
+			window->textRow = MAX((int16)0, top);
+			compositePnAmigaTextPlane(window);
+			return;
+		}
+		window->textRow += getPnAmigaTextLineStep();
+		if (window->textRow + getPnAmigaGlyphHeight() > (int16)getPnAmigaWindowInteriorHeight(window) - 2) {
+			scrollPnAmigaTextPlane(window);
+			window->textRow -= getPnAmigaTextLineStep();
+		}
+		return;
+	}
 	window->textColumn = 0;
 	window->textColumnOffset = (getGameType() == GType_ELVIRA2) ? 4 : 0;
 	window->textLength = 0;
@@ -356,6 +420,10 @@ void AGOSEngine::windowNewLine(WindowBlock *window) {
 }
 
 void AGOSEngine::windowScroll(WindowBlock *window) {
+	if (isPnAmigaTextWindow(window)) {
+		scrollPnAmigaTextPlane(window);
+		return;
+	}
 	_videoLockOut |= 0x8000;
 
 	if (window->height != 1) {
diff --git a/engines/agos/cursor.cpp b/engines/agos/cursor.cpp
index 9b4374de908..31019c5fc75 100644
--- a/engines/agos/cursor.cpp
+++ b/engines/agos/cursor.cpp
@@ -177,6 +177,23 @@ static const byte _amiga_handInfo[16 * 16] = {
 	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF
 };
 
+static void scaleCursor2x(const byte *src, int srcWidth, int srcHeight, byte *dst, int dstWidth) {
+	for (int y = 0; y < srcHeight; ++y) {
+		byte *dstRow1 = dst + (y * 2) * dstWidth;
+		byte *dstRow2 = dstRow1 + dstWidth;
+		const byte *srcRow = src + y * srcWidth;
+
+		for (int x = 0; x < srcWidth; ++x) {
+			const byte pixel = srcRow[x];
+			const int dstX = x * 2;
+			dstRow1[dstX] = pixel;
+			dstRow1[dstX + 1] = pixel;
+			dstRow2[dstX] = pixel;
+			dstRow2[dstX + 1] = pixel;
+		}
+	}
+}
+
 static const byte _simon2_cursors[10][256] = {
 	// cross hair
 	{ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xec,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
@@ -529,6 +546,10 @@ void AGOSEngine_PN::handleMouseMoved() {
 
 	CursorMan.showMouse(true);
 	_mouse = _eventMan->getMousePos();
+	if (isPnAmiga()) {
+		_mouse.x >>= 1;
+		_mouse.y >>= 1;
+	}
 
 	if (_leftClick == true) {
 		_leftClick = false;
@@ -847,7 +868,7 @@ void AGOSEngine::initMouse() {
 	_maxCursorWidth = 16;
 	_maxCursorHeight = 16;
 
-	if (getGameId() == GID_ELVIRA1 && getPlatform() == Common::kPlatformPC98) {
+	if ((getGameId() == GID_ELVIRA1 && getPlatform() == Common::kPlatformPC98) || isPnAmiga()) {
 		_maxCursorWidth <<= 1;
 		_maxCursorHeight <<= 1;
 	}
@@ -873,7 +894,13 @@ void AGOSEngine::drawMousePointer() {
 			}
 
 			CursorMan.replaceCursorPalette(_amiga_mousePalettePN, 0, ARRAYSIZE(_amiga_mousePalettePN) / 3);
-			CursorMan.replaceCursor(cursor, 16, 16, 0, 0, 0xFF);
+			if (_maxCursorWidth == 32 && _maxCursorHeight == 32) {
+				memset(_mouseData, 0xFF, _maxCursorWidth * _maxCursorHeight);
+				scaleCursor2x(cursor, 16, 16, _mouseData, _maxCursorWidth);
+				CursorMan.replaceCursor(_mouseData, _maxCursorWidth, _maxCursorHeight, 0, 0, 0xFF);
+			} else {
+				CursorMan.replaceCursor(cursor, 16, 16, 0, 0, 0xFF);
+			}
 			return;
 		}
 
diff --git a/engines/agos/gfx.cpp b/engines/agos/gfx.cpp
index 2870b9be681..22e21d7de03 100644
--- a/engines/agos/gfx.cpp
+++ b/engines/agos/gfx.cpp
@@ -1088,7 +1088,9 @@ void AGOSEngine::verticalScroll(VC10_state *state) {
 }
 
 Graphics::Surface *AGOSEngine::getBackendSurface() const {
-	return (getGameId() == GID_ELVIRA1 && getPlatform() == Common::kPlatformPC98) ? _backBuf : _system->lockScreen();
+	if ((getGameId() == GID_ELVIRA1 && getPlatform() == Common::kPlatformPC98) || isPnAmiga())
+		return _backBuf;
+	return _system->lockScreen();
 }
 
 void AGOSEngine::updateBackendSurface(Common::Rect *area) const {
@@ -1137,6 +1139,42 @@ void AGOSEngine::updateBackendSurface(Common::Rect *area) const {
 			dst10 += dst1Pitch;
 			dst11 += dst1Pitch;
 		}
+	} else if (isPnAmiga()) {
+		int x = 0;
+		int y = 0;
+		int w = _screenWidth;
+		int h = _screenHeight;
+
+		if (area) {
+			x = area->left;
+			y = area->top;
+			w = area->width();
+			h = area->height();
+		}
+
+		Graphics::Surface *screen = _system->lockScreen();
+		for (int row = y; row < y + h; ++row) {
+			const int dstRow = row << 1;
+			const bool usePnAmigaTextPane = _pnAmigaUiVisible && row >= 136;
+			if (!usePnAmigaTextPane) {
+				const byte *src = (const byte *)_backBuf->getBasePtr(x, row);
+				byte *dst0 = (byte *)screen->getBasePtr(x << 1, dstRow);
+				byte *dst1 = (byte *)screen->getBasePtr(x << 1, dstRow + 1);
+				for (int i = 0; i < w; ++i) {
+					const uint8 v = *src++;
+					*dst0++ = v;
+					*dst0++ = v;
+					*dst1++ = v;
+					*dst1++ = v;
+				}
+			} else {
+				const byte *src = (const byte *)_scaleBuf->getBasePtr(x << 1, row);
+				byte *dst0 = (byte *)screen->getBasePtr(x << 1, dstRow);
+				byte *dst1 = (byte *)screen->getBasePtr(x << 1, dstRow + 1);
+				memcpy(dst0, src, w << 1);
+				memcpy(dst1, src, w << 1);
+			}
+		}
 	}
 
 	_system->unlockScreen();
@@ -1606,7 +1644,41 @@ void AGOSEngine::setWindowImage(uint16 mode, uint16 vgaSpriteId, bool specialCas
 }
 
 // Personal Nightmare specific
+void AGOSEngine::drawPnAmigaTextWindowBorders() {
+	if (_scaleBuf == nullptr)
+		return;
+
+	Graphics::Surface *screen = _scaleBuf;
+	const byte color = 14;
+	byte *dst = (byte *)screen->getBasePtr(0, 136);
+	memset(dst, color, screen->w);
+	dst = (byte *)screen->getBasePtr(0, 223);
+	memset(dst, color, screen->w);
+	dst = (byte *)screen->getBasePtr(0, 136);
+	for (int y = 136; y <= 223; ++y) {
+		dst[0] = color;
+		dst[screen->w - 1] = color;
+		dst += screen->pitch;
+	}
+	dst = (byte *)screen->getBasePtr(0, 224);
+	memset(dst, color, screen->w);
+	dst = (byte *)screen->getBasePtr(0, 239);
+	memset(dst, color, screen->w);
+	dst = (byte *)screen->getBasePtr(0, 224);
+	for (int y = 224; y <= 239; ++y) {
+		dst[0] = color;
+		dst[screen->w - 1] = color;
+		dst += screen->pitch;
+	}
+	Common::Rect dirtyRect(0, 136, 320, _screenHeight);
+	updateBackendSurface(&dirtyRect);
+}
+
 void AGOSEngine::drawEdging() {
+	if (isPnAmiga()) {
+		drawPnAmigaTextWindowBorders();
+		return;
+	}
 	byte *dst;
 	uint8 color = (getPlatform() == Common::kPlatformDOS) ? 7 : 15;
 
diff --git a/engines/agos/input_pn.cpp b/engines/agos/input_pn.cpp
index a078f0b569e..ac0840888b7 100644
--- a/engines/agos/input_pn.cpp
+++ b/engines/agos/input_pn.cpp
@@ -22,6 +22,9 @@
 #include "agos/agos.h"
 #include "agos/intern.h"
 
+#include "graphics/fonts/amigafont.h"
+#include "graphics/surface.h"
+
 namespace AGOS {
 
 void AGOSEngine_PN::clearInputLine() {
@@ -70,7 +73,9 @@ void AGOSEngine_PN::handleKeyboard() {
 		if (_keyPressed.keycode == Common::KEYCODE_BACKSPACE || _keyPressed.keycode == Common::KEYCODE_RETURN) {
 			chr = _keyPressed.keycode;
 			addChar(chr);
-		} else if (!(_videoLockOut & 0x10)) {
+		} else if (!(_videoLockOut & 0x10) &&
+				   !_keyPressed.hasFlags(Common::KBD_CTRL) &&
+				   !_keyPressed.hasFlags(Common::KBD_ALT)) {
 			chr = _keyPressed.ascii;
 			if (chr >= 32)
 				addChar(chr);
@@ -97,8 +102,13 @@ void AGOSEngine_PN::interact(char *buffer, uint8 size) {
 		_intputCounter = 0;
 		_inputMax = size;
 		_inputWindow = _windowArray[_curWindow];
-		windowPutChar(_inputWindow, 128);
-		windowPutChar(_inputWindow, 8);
+		if (isPnAmigaInputWindow(_inputWindow)) {
+			drawPnAmigaTopazChar(_inputWindow, 128);
+			compositePnAmigaTextPlane(_inputWindow);
+		} else {
+			windowPutChar(_inputWindow, 128);
+			windowPutChar(_inputWindow, 8);
+		}
 		_inputting = true;
 		_inputReady = true;
 	}
@@ -125,9 +135,41 @@ void AGOSEngine_PN::addChar(uint8 chr) {
 		windowPutChar(_inputWindow, 13);
 	} else if (chr == 8 && _intputCounter) {
 		clearCursor(_inputWindow);
-		windowPutChar(_inputWindow, 8);
-		windowPutChar(_inputWindow, 128);
-		windowPutChar(_inputWindow, 8);
+		if (isPnAmigaInputWindow(_inputWindow)) {
+			const byte deletedChar = _keyboardBuffer[_intputCounter - 1];
+			const Graphics::AmigaFont *font = getPnAmigaFont();
+			const uint16 advance = getPnAmigaGlyphAdvance(deletedChar);
+			byte metricChar = deletedChar;
+			int16 drawOffset = 0;
+			uint16 inkWidth = 0;
+
+			if (metricChar == 128)
+				metricChar = '_';
+			if (metricChar == 128 || metricChar == 129) {
+				inkWidth = getPnAmigaGlyphAdvance(' ');
+			} else if (font != nullptr && metricChar >= font->getLoChar() && metricChar <= font->getHiChar()) {
+				drawOffset = font->getCharDrawOffset(metricChar);
+				inkWidth = font->getCharInkWidth(metricChar);
+			}
+
+			_inputWindow->textColumn = MAX<int16>(kPnAmigaTextStartX, _inputWindow->textColumn - (int16)advance);
+
+			PnAmigaTextPlane *plane = getPnAmigaTextPlane(_inputWindow);
+			if (plane != nullptr && plane->pixels != nullptr) {
+				Graphics::Surface surface;
+				surface.init(plane->width, plane->height, plane->width, plane->pixels, Graphics::PixelFormat::createFormatCLUT8());
+				const int16 clearLeft = CLIP<int16>(_inputWindow->textColumn + drawOffset, 0, plane->width);
+				const int16 clearRight = CLIP<int16>(clearLeft + (int16)inkWidth, 0, plane->width);
+				surface.fillRect(Common::Rect(clearLeft, _inputWindow->textRow,
+					clearRight, _inputWindow->textRow + getPnAmigaGlyphHeight()), _inputWindow->fillColor);
+				drawPnAmigaTopazChar(_inputWindow, 128);
+				compositePnAmigaTextPlane(_inputWindow);
+			}
+		} else {
+			windowPutChar(_inputWindow, 8);
+			windowPutChar(_inputWindow, 128);
+			windowPutChar(_inputWindow, 8);
+		}
 
 		_keyboardBuffer[--_intputCounter] = 0;
 	} else if (chr >= 32 && _intputCounter < _inputMax) {
@@ -135,12 +177,38 @@ void AGOSEngine_PN::addChar(uint8 chr) {
 
 		clearCursor(_inputWindow);
 		windowPutChar(_inputWindow, chr);
-		windowPutChar(_inputWindow, 128);
-		windowPutChar(_inputWindow, 8);
+		if (isPnAmigaInputWindow(_inputWindow)) {
+			drawPnAmigaTopazChar(_inputWindow, 128);
+			compositePnAmigaTextPlane(_inputWindow);
+		} else {
+			windowPutChar(_inputWindow, 128);
+			windowPutChar(_inputWindow, 8);
+		}
 	}
 }
 
 void AGOSEngine_PN::clearCursor(WindowBlock *window) {
+	if (isPnAmigaInputWindow(window)) {
+		const Graphics::AmigaFont *font = getPnAmigaFont();
+		PnAmigaTextPlane *plane = getPnAmigaTextPlane(window);
+		if (plane != nullptr && plane->pixels != nullptr) {
+			Graphics::Surface surface;
+			surface.init(plane->width, plane->height, plane->width, plane->pixels, Graphics::PixelFormat::createFormatCLUT8());
+			int16 drawOffset = 0;
+			uint16 inkWidth = getPnAmigaGlyphAdvance(' ');
+			if (font != nullptr && '_' >= font->getLoChar() && '_' <= font->getHiChar()) {
+				drawOffset = font->getCharDrawOffset('_');
+				inkWidth = font->getCharInkWidth('_');
+			}
+			const int16 cursorLeft = CLIP<int16>(window->textColumn + drawOffset, 0, plane->width);
+			const int16 cursorRight = CLIP<int16>(cursorLeft + (int16)inkWidth, 0, plane->width);
+			surface.fillRect(Common::Rect(cursorLeft, window->textRow,
+				cursorRight, window->textRow + getPnAmigaGlyphHeight()), window->fillColor);
+			compositePnAmigaTextPlane(window);
+		}
+		return;
+	}
+
 	byte oldTextColor = window->textColor;
 
 	window->textColor = window->fillColor;
diff --git a/engines/agos/pn.cpp b/engines/agos/pn.cpp
index bb4b973c782..62988db18d9 100644
--- a/engines/agos/pn.cpp
+++ b/engines/agos/pn.cpp
@@ -130,13 +130,21 @@ Common::Error AGOSEngine_PN::go() {
 		_paletteFlag = 1;
 	}
 
-	_inputWindow = _windowArray[2] = openWindow(0, 192, 40, 1, 1, 0, 15);
-	_textWindow = _windowArray[0] = openWindow(1, 136, 38, 6, 1, 0, 15);
+	if (isPnAmiga())
+		_pnAmigaUiVisible = false;
+
+	_inputWindow = _windowArray[2] = openWindow(0, isPnAmiga() ? 224 : 192, 40, 1, 1, 0, isPnAmiga() ? 14 : 15);
+	_textWindow = _windowArray[0] = openWindow(isPnAmiga() ? 0 : 1, 136, isPnAmiga() ? 40 : 38, isPnAmiga() ? 5 : 6, 1, 0, isPnAmiga() ? 14 : 15);
 
 	if (getFeatures() & GF_DEMO) {
 		demoSeq();
 	} else {
 		introSeq();
+		if (isPnAmiga()) {
+			_pnAmigaUiVisible = true;
+			clearWindow(_windowArray[0]);
+			clearWindow(_windowArray[2]);
+		}
 		processor();
 	}
 
diff --git a/engines/agos/saveload.cpp b/engines/agos/saveload.cpp
index 9161fc54fab..b45ca0e8b3a 100644
--- a/engines/agos/saveload.cpp
+++ b/engines/agos/saveload.cpp
@@ -117,6 +117,55 @@ void AGOSEngine_Feeble::quickLoadOrSave() {
 }
 #endif
 
+void AGOSEngine_PN::quickLoadOrSave() {
+	Common::U32String buf;
+	const Common::String filename = genSaveName(_saveLoadSlot);
+	const byte requestedType = _saveLoadType;
+	bool success = false;
+
+	if (requestedType == 2) {
+		Common::InSaveFile *in = _saveFileMan->openForLoading(filename);
+		if (in == nullptr) {
+			buf = Common::U32String::format(_("Failed to load saved game from file:\n\n%s"), filename.c_str());
+		} else {
+			delete in;
+			const int numParams = _dataBase[getlong(_quickptr[6] + 3 * 267)];
+			if (numParams == 0) {
+				addstack(-1);
+				for (int i = 24; i < 32; ++i)
+					_variableArray[i] = 0;
+
+				setposition(267, 0);
+				success = (doline(1) != 0);
+			}
+			if (!success)
+				buf = Common::U32String::format(_("Failed to load saved game from file:\n\n%s"), filename.c_str());
+		}
+	} else if (requestedType == 1) {
+		memset(_saveFile, 0, sizeof(_saveFile));
+		for (uint i = 0; i < 8 && _saveLoadName[i] != 0; ++i)
+			_saveFile[i] = _saveLoadName[i];
+
+		success = (saveFile(filename) != 0);
+		if (!success)
+			buf = Common::U32String::format(_("Failed to save game to file:\n\n%s"), filename.c_str());
+	} else {
+		_saveLoadType = 0;
+		return;
+	}
+
+	if (!success) {
+		GUI::MessageDialog dialog(buf);
+		dialog.runModal();
+	} else if (requestedType == 1) {
+		buf = Common::U32String::format(_("Successfully saved game in file:\n\n%s"), filename.c_str());
+		GUI::TimedMessageDialog dialog(buf, 1500);
+		dialog.runModal();
+	}
+
+	_saveLoadType = 0;
+}
+
 // The function uses segments of code from the original game scripts
 // to allow quick loading and saving, but isn't perfect.
 //
diff --git a/engines/agos/script_pn.cpp b/engines/agos/script_pn.cpp
index 3e1e01f675b..0a4f05f8bd6 100644
--- a/engines/agos/script_pn.cpp
+++ b/engines/agos/script_pn.cpp
@@ -266,7 +266,11 @@ void AGOSEngine_PN::opn_opcode15() {
 
 	pcf((unsigned char)254);
 	_curWindow = x;
-	_xofs = (8 * _windowArray[_curWindow]->textLength) / 6 + 1;
+	WindowBlock *window = _windowArray[_curWindow];
+	if (isPnAmigaTextWindow(window))
+		_xofs = window->textColumn;
+	else
+		_xofs = (8 * window->textLength) / 6 + 1;
 	setScriptReturn(true);
 }
 
@@ -380,9 +384,18 @@ void AGOSEngine_PN::opn_opcode31() {
 
 	switch (a) {
 		case 0:
-			getFilename();
-			slot = matchSaveGame(_saveFile, countSaveGames());
-			bf = genSaveName(slot);
+			if (_saveLoadType == 2) {
+				memset(_saveFile, 0, sizeof(_saveFile));
+				for (uint i = 0; i < 8 && _saveLoadName[i] != 0; ++i)
+					_saveFile[i] = _saveLoadName[i];
+				slot = _saveLoadSlot;
+				bf = genSaveName(slot);
+				_saveLoadType = 0;
+			} else {
+				getFilename();
+				slot = matchSaveGame(_saveFile, countSaveGames());
+				bf = genSaveName(slot);
+			}
 			break;
 		case 1:
 			bf = "pn.sav";
@@ -419,12 +432,20 @@ void AGOSEngine_PN::opn_opcode32() {
 	uint16 curSlot = countSaveGames();
 	switch (a) {
 		case 0:
-			getFilename();
-			slot = matchSaveGame(_saveFile, curSlot);
-			if (slot != -1)
-				bf = genSaveName(slot);
-			else
-				bf = genSaveName(curSlot);
+			if (_saveLoadType == 1) {
+				memset(_saveFile, 0, sizeof(_saveFile));
+				for (uint i = 0; i < 8 && _saveLoadName[i] != 0; ++i)
+					_saveFile[i] = _saveLoadName[i];
+				bf = genSaveName(_saveLoadSlot);
+				_saveLoadType = 0;
+			} else {
+				getFilename();
+				slot = matchSaveGame(_saveFile, curSlot);
+				if (slot != -1)
+					bf = genSaveName(slot);
+				else
+					bf = genSaveName(curSlot);
+			}
 			break;
 		case 1:
 			bf = "pn.sav";
@@ -755,7 +776,7 @@ int AGOSEngine_PN::inventoryOn(int val) {
 
 int AGOSEngine_PN::inventoryOff() {
 	if (_videoLockOut & 0x10) {
-		_windowArray[2]->textColor = 15;
+		_windowArray[2]->textColor = isPnAmiga() ? 14 : 15;
 
 		restoreBlock(48, 2, 272, 130);
 
diff --git a/engines/agos/string_pn.cpp b/engines/agos/string_pn.cpp
index e974ae551f6..23caf11e239 100644
--- a/engines/agos/string_pn.cpp
+++ b/engines/agos/string_pn.cpp
@@ -124,6 +124,8 @@ void AGOSEngine_PN::pcl(const char *s) {
 }
 
 void AGOSEngine_PN::pcf(uint8 ch) {
+	WindowBlock *window = _windowArray[_curWindow];
+	const bool usePnAmigaWrap = isPnAmigaTextWindow(window);
 	int ct = 0;
 	if (ch == '[')
 		ch = '\n';
@@ -131,9 +133,62 @@ void AGOSEngine_PN::pcf(uint8 ch) {
 		return;	/* Trap any C EOS chrs */
 	if (ch == 255) {
 		_bp = 0;
-		_xofs = 0;
+		_xofs = usePnAmigaWrap ? window->textColumn : 0;
 		return;		/* pcf(255) initializes the routine */
 	}			/* pcf(254) flushes its working _buffer */
+
+	if (usePnAmigaWrap) {
+		const int wrapLimit = (int)getPnAmigaTextPlaneWidth(window);
+		int pendingWidth = 0;
+
+		_xofs = window->textColumn;
+		if (ch != 254) {
+			pendingWidth = 0;
+			for (int i = 0; i < _bp; ++i) {
+				const byte bufferedChar = (byte)_buffer[i];
+				if (bufferedChar >= 32)
+					pendingWidth += getPnAmigaGlyphAdvance(bufferedChar);
+			}
+			if ((ch != 32) || (pendingWidth + _xofs != wrapLimit))
+				_buffer[_bp++] = ch;
+		}
+		if ((ch != 254) && (!Common::isSpace(ch)) && (_bp < 60))
+			return;
+
+		pendingWidth = 0;
+		for (int i = 0; i < _bp; ++i) {
+			const byte bufferedChar = (byte)_buffer[i];
+			if (bufferedChar >= 32)
+				pendingWidth += getPnAmigaGlyphAdvance(bufferedChar);
+		}
+		if (pendingWidth + _xofs > wrapLimit && _bp > 0 && _buffer[_bp - 1] == ' ') {
+			const int trimmedWidth = pendingWidth - getPnAmigaGlyphAdvance(' ');
+			if (trimmedWidth + _xofs <= wrapLimit) {
+				_buffer[_bp - 1] = 0;
+				pcl(_buffer);
+				windowPutChar(window, '\n');
+				_sb[0] = '\0';
+				_bp = 0;
+				_xofs = kPnAmigaTextStartX;
+				return;
+			}
+		}
+		if (pendingWidth + _xofs > wrapLimit) {
+			pcl("\n");
+			if (_buffer[0] == ' ')
+				ct = 1;
+			_xofs = kPnAmigaTextStartX;
+		}
+		_buffer[_bp] = 0;
+		pcl(_buffer + ct);
+		if (ch == '\n')
+			_xofs = kPnAmigaTextStartX;
+		else
+			_xofs = window->textColumn;
+		_bp = 0;
+		return;
+	}
+
 	if (ch != 254) {
 		if ((ch != 32) || (_bp + _xofs != 50))
 			_buffer[_bp++] = ch;
diff --git a/engines/agos/window.cpp b/engines/agos/window.cpp
index 15475838d56..2871bb9d594 100644
--- a/engines/agos/window.cpp
+++ b/engines/agos/window.cpp
@@ -41,6 +41,45 @@ uint AGOSEngine::getWindowNum(WindowBlock *window) {
 	return 0;	// for compilers that don't support NORETURN
 }
 
+bool AGOSEngine::isPnAmigaMainTextWindow(const WindowBlock *window) const {
+	return window != nullptr && isPnAmiga() && window->x == 0 && window->width == 40 && window->y == kPnAmigaMainTextTop;
+}
+
+bool AGOSEngine::isPnAmigaInputWindow(const WindowBlock *window) const {
+	return window != nullptr && isPnAmiga() && window->x == 0 && window->width == 40 && window->y == kPnAmigaInputTop;
+}
+
+bool AGOSEngine::isPnAmigaTextWindow(const WindowBlock *window) const {
+	return isPnAmigaMainTextWindow(window) || isPnAmigaInputWindow(window);
+}
+
+uint16 AGOSEngine::getPnAmigaWindowInteriorHeight(const WindowBlock *window) const {
+	if (window == nullptr)
+		return 0;
+
+	uint16 pixelHeight = window->height * 8;
+	if (window->x == 0 && window->width == 40 && window->y == 136)
+		pixelHeight = 88;
+	else if (window->x == 0 && window->width == 40 && window->y == 224)
+		pixelHeight = 16;
+	return (pixelHeight > 2) ? pixelHeight - 2 : 0;
+}
+
+uint16 AGOSEngine::getPnAmigaTextPlaneWidth(const WindowBlock *window) const {
+	if (window == nullptr)
+		return 0;
+
+	if (isPnAmigaTextWindow(window))
+		return kPnAmigaTextPlaneWidth;
+	return window->width * 16 - 2;
+}
+
+uint16 AGOSEngine::getPnAmigaTextLineStep() const {
+	if (usePnAmigaDoubleHeightTopaz())
+		return getPnAmigaGlyphHeight();
+	return 9;
+}
+
 WindowBlock *AGOSEngine::openWindow(uint x, uint y, uint w, uint h, uint flags, uint fillColor, uint textColor) {
 	WindowBlock *window;
 
@@ -67,6 +106,8 @@ WindowBlock *AGOSEngine::openWindow(uint x, uint y, uint w, uint h, uint flags,
 	// Characters are 6 pixels (except Japanese: when downscaled, 1-byte characters are 4 pixels, 2-byte characters are 8 pixels)
 	if (getGameType() == GType_ELVIRA2)
 		window->textMaxLength = (window->width * 8 - 4) / 6;
+	else if (isPnAmigaTextWindow(window))
+		window->textMaxLength = getPnAmigaTextPlaneWidth(window) / getPnAmigaGlyphAdvance(' ');
 	else if (getGameType() == GType_PN)
 		window->textMaxLength = window->width * 8 / 6 + 1;
 	else if (getGameType() == GType_ELVIRA1 && getPlatform() == Common::kPlatformPC98)
@@ -119,6 +160,61 @@ void AGOSEngine::clearWindow(WindowBlock *window) {
 	window->textColumnOffset = (getGameType() == GType_ELVIRA2) ? 4 : 0;
 	window->textLength = 0;
 	window->scrollY = 0;
+
+	if (isPnAmigaTextWindow(window)) {
+		window->textColumn = kPnAmigaTextStartX;
+		clearPnAmigaTextPlane(window);
+		if (isPnAmigaInputWindow(window)) {
+			const int16 top = ((int16)getPnAmigaWindowInteriorHeight(window) - (int16)getPnAmigaGlyphHeight()) / 2;
+			window->textRow = MAX((int16)0, top);
+		}
+		compositePnAmigaTextPlane(window);
+	}
+}
+
+void AGOSEngine::clearPnAmigaTextPlane(WindowBlock *window) {
+	ensurePnAmigaTextPlanes();
+	PnAmigaTextPlane *plane = getPnAmigaTextPlane(window);
+	if (plane == nullptr || plane->pixels == nullptr)
+		return;
+	memset(plane->pixels, window->fillColor, plane->width * plane->height);
+}
+
+void AGOSEngine::compositePnAmigaTextPlane(WindowBlock *window) {
+	const PnAmigaTextPlane *plane = getPnAmigaTextPlane(window);
+	if (plane == nullptr || plane->pixels == nullptr || _scaleBuf == nullptr)
+		return;
+
+	Graphics::Surface *screen = _scaleBuf;
+	const uint16 dstLeft = window->x * 16 + 1;
+	const uint16 dstTop = window->y + 1;
+	const uint16 dstWidth = MIN<uint16>(getPnAmigaTextPlaneWidth(window), plane->width);
+	const uint16 dstHeight = MIN<uint16>(getPnAmigaWindowInteriorHeight(window), plane->height);
+
+	for (uint16 y = 0; y < dstHeight; ++y) {
+		byte *dst = (byte *)screen->getBasePtr(dstLeft, dstTop + y);
+		const byte *src = plane->pixels + y * plane->width;
+		memcpy(dst, src, dstWidth);
+	}
+
+	Common::Rect dirtyRect(0, 136, 320, _screenHeight);
+	updateBackendSurface(&dirtyRect);
+}
+
+void AGOSEngine::scrollPnAmigaTextPlane(WindowBlock *window) {
+	PnAmigaTextPlane *plane = getPnAmigaTextPlane(window);
+	if (plane == nullptr || plane->pixels == nullptr)
+		return;
+
+	const uint16 step = getPnAmigaTextLineStep();
+	if (plane->height > step) {
+		memmove(plane->pixels, plane->pixels + plane->width * step, plane->width * (plane->height - step));
+		memset(plane->pixels + plane->width * (plane->height - step), window->fillColor, plane->width * step);
+	} else {
+		memset(plane->pixels, window->fillColor, plane->width * plane->height);
+	}
+
+	compositePnAmigaTextPlane(window);
 }
 
 #ifdef ENABLE_AGOS2
@@ -144,6 +240,30 @@ void AGOSEngine_Feeble::colorWindow(WindowBlock *window) {
 
 void AGOSEngine::colorWindow(WindowBlock *window) {
 	uint16 y, h;
+	uint16 x, w;
+
+	if (isPnAmigaTextWindow(window)) {
+		x = window->x * 16 + 1;
+		y = window->y + 1;
+		w = window->width * 16 - 2;
+		h = getPnAmigaWindowInteriorHeight(window);
+		_videoLockOut |= 0x8000;
+
+		Graphics::Surface *screen = _scaleBuf;
+		byte *dst = (byte *)screen->getBasePtr(x, y);
+
+		do {
+			memset(dst, window->fillColor, w);
+			dst += screen->pitch;
+		} while (--h);
+
+		Common::Rect dirtyRect(0, 136, kPnAmigaLowresWidth, _screenHeight);
+		updateBackendSurface(&dirtyRect);
+
+		_videoLockOut &= ~0x8000;
+		drawPnAmigaTextWindowBorders();
+		return;
+	}
 
 	y = window->y;
 	h = window->height * 8;
@@ -202,6 +322,13 @@ void AGOSEngine::resetWindow(WindowBlock *window) {
 void AGOSEngine::restoreWindow(WindowBlock *window) {
 	_videoLockOut |= 0x8000;
 
+	if (isPnAmigaTextWindow(window)) {
+		_videoLockOut &= ~0x8000;
+		colorWindow(window);
+		compositePnAmigaTextPlane(window);
+		return;
+	}
+
 	if (getGameType() == GType_FF || getGameType() == GType_PP) {
 		restoreBlock(window->x, window->y, window->x + window->width, window->y + window->height);
 	} else if (getGameType() == GType_SIMON2) {
@@ -290,8 +417,14 @@ void AGOSEngine::waitWindow(WindowBlock *window) {
 	HitArea *ha;
 	const char *message;
 
-	window->textColumn = (window->width / 2) - 3;
-	window->textRow = window->height - 1;
+	if (isPnAmigaTextWindow(window)) {
+		const uint16 buttonWidth = getPnAmigaGlyphAdvance(' ') * 6;
+		window->textColumn = MAX<int16>((int16)kPnAmigaTextStartX, ((int16)getPnAmigaTextPlaneWidth(window) - (int16)buttonWidth) / 2);
+		window->textRow = MAX<int16>(0, (int16)getPnAmigaWindowInteriorHeight(window) - (int16)getPnAmigaGlyphHeight());
+	} else {
+		window->textColumn = (window->width / 2) - 3;
+		window->textRow = window->height - 1;
+	}
 	window->textLength = 0;
 
 	_forceAscii = true;
@@ -301,10 +434,16 @@ void AGOSEngine::waitWindow(WindowBlock *window) {
 	_forceAscii = false;
 
 	ha = findEmptyHitArea();
-	ha->x = (window->width / 2 + window->x - 3) * 8;
-	ha->y = window->height * 8 + window->y - 8;
-	ha->width = 48;
-	ha->height = 8;
+	if (isPnAmigaTextWindow(window)) {
+		ha->x = window->x * 8 + window->textColumn / 2;
+		ha->y = window->y + getPnAmigaWindowInteriorHeight(window) + 2 - getPnAmigaGlyphHeight();
+		ha->width = getPnAmigaGlyphAdvance(' ') * 3;
+	} else {
+		ha->x = (window->width / 2 + window->x - 3) * 8;
+		ha->y = window->height * 8 + window->y - 8;
+		ha->width = 48;
+	}
+	ha->height = isPnAmiga() ? getPnAmigaGlyphHeight() : 8;
 	ha->flags = kBFBoxInUse;
 	ha->id = 0x7FFF;
 	ha->priority = 999;
diff --git a/graphics/fonts/amigafont.cpp b/graphics/fonts/amigafont.cpp
index 9911fb80961..d464f48282e 100644
--- a/graphics/fonts/amigafont.cpp
+++ b/graphics/fonts/amigafont.cpp
@@ -194,6 +194,107 @@ static const byte amigaTopazFont[2600] = {
 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf2
 };
 
+// Topaz 9
+static const byte amigaTopaz9Font[] = {
+	0x00, 0x09, 0x08, 0x41, 0x00, 0x0a, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x20, 0x7f, 0x00, 0x00,
+	0x00, 0x20, 0x00, 0x52, 0x00, 0x00, 0x03, 0x02, 0x00, 0x00, 0x04, 0x82, 0x00, 0x00, 0x05, 0x42,
+	0x6c, 0xd9, 0x86, 0x00, 0x3c, 0x67, 0xe0, 0x00, 0x00, 0x00, 0x00, 0xdf, 0x18, 0xf9, 0xf0, 0xf7,
+	0xf3, 0xdf, 0xdf, 0x3e, 0x00, 0xc0, 0x30, 0xf9, 0xf8, 0x63, 0xf8, 0xfb, 0xf3, 0xff, 0xfc, 0xfb,
+	0x1f, 0xe1, 0xf8, 0xfe, 0x20, 0x70, 0xcf, 0x3f, 0x8f, 0x3f, 0x8f, 0xbf, 0xf1, 0xe1, 0xe1, 0xe1,
+	0xe1, 0xff, 0xfe, 0x07, 0x8c, 0x00, 0x60, 0x0e, 0x00, 0x01, 0xc0, 0x0f, 0x00, 0x70, 0x30, 0x3e,
+	0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xf0,
+	0xe6, 0x1e, 0xfc, 0xd9, 0x9f, 0xe3, 0x66, 0x6c, 0x33, 0x30, 0xc0, 0x00, 0x01, 0xb1, 0xb9, 0x8f,
+	0x19, 0xb6, 0x06, 0x18, 0xf1, 0xe3, 0xd9, 0x80, 0x19, 0x8f, 0x0c, 0xf1, 0x8d, 0x8d, 0x99, 0x8d,
+	0x8d, 0x8f, 0x19, 0x80, 0xd8, 0xd8, 0x30, 0xf8, 0xd9, 0x98, 0xd9, 0x98, 0xd8, 0xe6, 0x71, 0xe1,
+	0xe1, 0xb3, 0x61, 0xe3, 0x63, 0x01, 0x9e, 0x00, 0x60, 0x06, 0x00, 0x00, 0xc0, 0x19, 0x80, 0x30,
+	0x00, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x19, 0x99, 0x9c, 0x78, 0xf0, 0x3f, 0xf0, 0x66, 0x64, 0xd8, 0x19, 0xe0, 0xc0, 0x00, 0x03, 0x33,
+	0x98, 0x0c, 0x1b, 0x37, 0xec, 0x00, 0xf1, 0xe3, 0xdb, 0x3f, 0xcc, 0x0f, 0x3c, 0xf1, 0x8f, 0x01,
+	0x8d, 0x81, 0x83, 0x03, 0x19, 0x80, 0xd9, 0x98, 0x39, 0xfc, 0xf0, 0xd8, 0xf0, 0xd8, 0xdc, 0x06,
+	0x31, 0xb3, 0x61, 0x9e, 0x33, 0x46, 0x61, 0x81, 0xb3, 0x00, 0x37, 0xc6, 0xe7, 0xce, 0xcf, 0x98,
+	0x3d, 0xb7, 0x70, 0x76, 0x36, 0xc6, 0xfc, 0xfb, 0x79, 0xf7, 0xb9, 0xff, 0xd8, 0xd8, 0x78, 0x78,
+	0xf0, 0xff, 0x99, 0x98, 0x01, 0xe0, 0x60, 0x19, 0x9f, 0x0c, 0x7b, 0x18, 0x1f, 0xff, 0xf8, 0xff,
+	0x06, 0x3f, 0x98, 0x38, 0xf6, 0x30, 0x3f, 0xc1, 0x9f, 0x3f, 0x06, 0x00, 0x06, 0x3b, 0x6d, 0x99,
+	0xfb, 0x01, 0x8d, 0xf1, 0xf3, 0x1f, 0xf9, 0x80, 0xdf, 0x18, 0x3f, 0xf6, 0xf0, 0xdf, 0xb0, 0xdf,
+	0x87, 0x06, 0x31, 0xb3, 0x6d, 0x8c, 0x1e, 0x0c, 0x60, 0xc1, 0xe1, 0x80, 0x00, 0x67, 0x3c, 0x79,
+	0xd8, 0xfc, 0x63, 0x39, 0xb0, 0x36, 0x66, 0xef, 0xc7, 0x8d, 0x8f, 0x19, 0xcf, 0x03, 0x18, 0xd8,
+	0x7b, 0x6d, 0xb0, 0xe3, 0x71, 0x8e, 0x01, 0x86, 0x60, 0x3f, 0xc1, 0x98, 0xce, 0x18, 0x19, 0xe0,
+	0xc0, 0x00, 0x0c, 0x39, 0x98, 0xc0, 0x1f, 0xf8, 0x3c, 0x63, 0x31, 0x83, 0x03, 0x00, 0x0c, 0x63,
+	0x3d, 0xf9, 0x8f, 0x01, 0x8d, 0x81, 0x83, 0x0f, 0x19, 0x98, 0xd9, 0x98, 0x76, 0xf3, 0xf0, 0xd8,
+	0x30, 0xd9, 0x81, 0xc6, 0x31, 0x9e, 0x7f, 0x9e, 0x0c, 0x18, 0xe0, 0x61, 0x80, 0x00, 0x03, 0xe6,
+	0x3c, 0x18, 0xdf, 0xd8, 0x63, 0x31, 0xb0, 0x37, 0xc6, 0xdb, 0xc7, 0x8d, 0x8f, 0x19, 0x8d, 0xf3,
+	0x18, 0xcc, 0xdb, 0x67, 0x19, 0x8e, 0x19, 0x98, 0x00, 0x1f, 0x00, 0x19, 0xbf, 0x33, 0xc6, 0x0c,
+	0x33, 0x30, 0xc3, 0x00, 0xd8, 0x31, 0x99, 0x8f, 0x18, 0x36, 0x3c, 0x66, 0x31, 0x86, 0xd9, 0xbf,
+	0xd8, 0x03, 0x03, 0x0d, 0x8d, 0x8d, 0x99, 0x8d, 0x81, 0x8f, 0x19, 0x98, 0xd8, 0xd8, 0xf0, 0xf1,
+	0xd9, 0x98, 0x19, 0x98, 0xd8, 0xc6, 0x31, 0x9e, 0x73, 0xb3, 0x0c, 0x31, 0xe0, 0x31, 0x80, 0x00,
+	0x0c, 0x66, 0x3c, 0x78, 0xd8, 0x18, 0x3e, 0x31, 0xb0, 0x36, 0x66, 0xc3, 0xc7, 0x8d, 0xf9, 0xf9,
+	0x80, 0x1b, 0x38, 0xc7, 0x8c, 0xcd, 0x8f, 0x18, 0x99, 0x98, 0x00, 0x79, 0x60, 0x19, 0x8c, 0x63,
+	0x7b, 0x07, 0xe0, 0x00, 0x03, 0x00, 0xf0, 0x1f, 0x7f, 0xfd, 0xf0, 0xfb, 0xe7, 0xc6, 0x1f, 0x3c,
+	0xd8, 0xc0, 0x30, 0x61, 0xf3, 0x0f, 0xf8, 0xfb, 0xf3, 0xff, 0xe0, 0xff, 0x1f, 0xef, 0xb8, 0xff,
+	0xf0, 0xf0, 0xcf, 0x3e, 0x0f, 0xb8, 0x6f, 0x8f, 0x1f, 0x8c, 0x61, 0xe1, 0x9e, 0x7f, 0xf8, 0x1f,
+	0x80, 0x00, 0x07, 0xb3, 0xe7, 0xcf, 0x6f, 0xbc, 0x03, 0x71, 0xfe, 0x3e, 0x3f, 0xc3, 0xc6, 0xf9,
+	0x80, 0x1b, 0xc3, 0xf1, 0xcf, 0x63, 0x0c, 0xd8, 0xc6, 0x3f, 0x8f, 0xf0, 0x01, 0xe0, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x7f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x03, 0xe0, 0x00, 0x00,
+	0x00, 0x03, 0xc0, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x06, 0x00, 0x0a,
+	0x00, 0x08, 0x00, 0x12, 0x00, 0x07, 0x00, 0x19, 0x00, 0x07, 0x00, 0x20, 0x00, 0x08, 0x00, 0x28,
+	0x00, 0x03, 0x00, 0x2b, 0x00, 0x05, 0x00, 0x30, 0x00, 0x05, 0x00, 0x35, 0x00, 0x08, 0x00, 0x3d,
+	0x00, 0x08, 0x00, 0x45, 0x00, 0x03, 0x00, 0x48, 0x00, 0x08, 0x00, 0x50, 0x00, 0x02, 0x00, 0x52,
+	0x00, 0x08, 0x00, 0x5a, 0x00, 0x07, 0x00, 0x61, 0x00, 0x06, 0x00, 0x67, 0x00, 0x07, 0x00, 0x6e,
+	0x00, 0x07, 0x00, 0x75, 0x00, 0x08, 0x00, 0x7d, 0x00, 0x07, 0x00, 0x84, 0x00, 0x07, 0x00, 0x8b,
+	0x00, 0x07, 0x00, 0x92, 0x00, 0x07, 0x00, 0x99, 0x00, 0x07, 0x00, 0xa0, 0x00, 0x02, 0x00, 0xa2,
+	0x00, 0x03, 0x00, 0xa5, 0x00, 0x05, 0x00, 0xaa, 0x00, 0x08, 0x00, 0xb2, 0x00, 0x05, 0x00, 0xb7,
+	0x00, 0x07, 0x00, 0xbe, 0x00, 0x08, 0x00, 0xc6, 0x00, 0x08, 0x00, 0xce, 0x00, 0x08, 0x00, 0xd6,
+	0x00, 0x08, 0x00, 0xde, 0x00, 0x08, 0x00, 0xe6, 0x00, 0x08, 0x00, 0xee, 0x00, 0x08, 0x00, 0xf6,
+	0x00, 0x08, 0x00, 0xfe, 0x00, 0x07, 0x01, 0x05, 0x00, 0x06, 0x01, 0x0b, 0x00, 0x07, 0x01, 0x12,
+	0x00, 0x08, 0x01, 0x1a, 0x00, 0x08, 0x01, 0x22, 0x00, 0x08, 0x01, 0x2a, 0x00, 0x08, 0x01, 0x32,
+	0x00, 0x08, 0x01, 0x3a, 0x00, 0x08, 0x01, 0x42, 0x00, 0x08, 0x01, 0x4a, 0x00, 0x09, 0x01, 0x53,
+	0x00, 0x07, 0x01, 0x5a, 0x00, 0x08, 0x01, 0x62, 0x00, 0x07, 0x01, 0x69, 0x00, 0x08, 0x01, 0x71,
+	0x00, 0x08, 0x01, 0x79, 0x00, 0x08, 0x01, 0x81, 0x00, 0x08, 0x01, 0x89, 0x00, 0x08, 0x01, 0x91,
+	0x00, 0x04, 0x01, 0x95, 0x00, 0x08, 0x01, 0x9d, 0x00, 0x04, 0x01, 0xa1, 0x00, 0x08, 0x01, 0xa9,
+	0x00, 0x08, 0x01, 0xb1, 0x00, 0x03, 0x01, 0xb4, 0x00, 0x08, 0x01, 0xbc, 0x00, 0x08, 0x01, 0xc4,
+	0x00, 0x07, 0x01, 0xcb, 0x00, 0x08, 0x01, 0xd3, 0x00, 0x07, 0x01, 0xda, 0x00, 0x07, 0x01, 0xe1,
+	0x00, 0x08, 0x01, 0xe9, 0x00, 0x08, 0x01, 0xf1, 0x00, 0x04, 0x01, 0xf5, 0x00, 0x07, 0x01, 0xfc,
+	0x00, 0x08, 0x02, 0x04, 0x00, 0x04, 0x02, 0x08, 0x00, 0x08, 0x02, 0x10, 0x00, 0x07, 0x02, 0x17,
+	0x00, 0x07, 0x02, 0x1e, 0x00, 0x08, 0x02, 0x26, 0x00, 0x08, 0x02, 0x2e, 0x00, 0x08, 0x02, 0x36,
+	0x00, 0x07, 0x02, 0x3d, 0x00, 0x06, 0x02, 0x43, 0x00, 0x08, 0x02, 0x4b, 0x00, 0x08, 0x02, 0x53,
+	0x00, 0x08, 0x02, 0x5b, 0x00, 0x07, 0x02, 0x62, 0x00, 0x08, 0x02, 0x6a, 0x00, 0x07, 0x02, 0x71,
+	0x00, 0x06, 0x02, 0x77, 0x00, 0x02, 0x02, 0x79, 0x00, 0x06, 0x02, 0x7f, 0x00, 0x08, 0x02, 0x87,
+	0x00, 0x08, 0x00, 0x0a, 0x00, 0x07, 0x00, 0x08, 0x00, 0x09, 0x00, 0x08, 0x00, 0x08, 0x00, 0x09,
+	0x00, 0x07, 0x00, 0x07, 0x00, 0x08, 0x00, 0x09, 0x00, 0x09, 0x00, 0x07, 0x00, 0x09, 0x00, 0x06,
+	0x00, 0x09, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x09, 0x00, 0x08, 0x00, 0x08,
+	0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x06, 0x00, 0x07, 0x00, 0x07, 0x00, 0x09, 0x00, 0x07,
+	0x00, 0x08, 0x00, 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, 0x09,
+	0x00, 0x09, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, 0x09,
+	0x00, 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, 0x08, 0x00, 0x09, 0x00, 0x08, 0x00, 0x09,
+	0x00, 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, 0x07, 0x00, 0x09, 0x00, 0x07, 0x00, 0x09,
+	0x00, 0x09, 0x00, 0x06, 0x00, 0x09, 0x00, 0x09, 0x00, 0x08, 0x00, 0x09, 0x00, 0x08, 0x00, 0x08,
+	0x00, 0x09, 0x00, 0x09, 0x00, 0x07, 0x00, 0x08, 0x00, 0x09, 0x00, 0x07, 0x00, 0x09, 0x00, 0x08,
+	0x00, 0x08, 0x00, 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, 0x08, 0x00, 0x07, 0x00, 0x09, 0x00, 0x09,
+	0x00, 0x09, 0x00, 0x08, 0x00, 0x09, 0x00, 0x08, 0x00, 0x08, 0x00, 0x06, 0x00, 0x08, 0x00, 0x09,
+	0x00, 0x09, 0x00, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x01,
+	0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x01, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x04,
+	0x00, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02,
+	0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x04, 0x00, 0x03, 0x00, 0x03, 0x00, 0x01, 0x00, 0x03,
+	0x00, 0x02, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
+	0x00, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
+	0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01,
+	0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01,
+	0x00, 0x01, 0x00, 0x04, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02,
+	0x00, 0x01, 0x00, 0x01, 0x00, 0x03, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02,
+	0x00, 0x02, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x01, 0x00, 0x01,
+	0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x04, 0x00, 0x02, 0x00, 0x01,
+	0x00, 0x01,
+};
+
 AmigaFont::AmigaFont(Common::SeekableReadStream *stream) {
 	Common::SeekableReadStream *tmp;
 	if (!stream) {
@@ -243,10 +344,63 @@ AmigaFont::AmigaFont(Common::SeekableReadStream *stream) {
 	}
 }
 
+AmigaFont::AmigaFont(Topaz9Builtin)
+	: _font(nullptr), _data(nullptr), _charData(nullptr), _charLoc(nullptr), _charSpace(nullptr), _charKern(nullptr),
+	  _cp(nullptr), _pitch(0), _maxCharWidth(0) {
+	initFromData(amigaTopaz9Font, sizeof(amigaTopaz9Font), 0);
+}
+
 AmigaFont::~AmigaFont() {
 	free(_data);
 }
 
+void AmigaFont::initFromData(const byte *data, uint32 dataSize, uint32 fontOffset) {
+	_data = (byte *)malloc(dataSize);
+	memcpy(_data, data, dataSize);
+
+	_font = (AmigaDiskFont *)(_data + fontOffset);
+	_font->_ySize = FROM_BE_16(_font->_ySize);
+	_font->_xSize = FROM_BE_16(_font->_xSize);
+	_font->_baseline = FROM_BE_16(_font->_baseline);
+	_font->_modulo = FROM_BE_16(_font->_modulo);
+
+	_charLoc = (CharLoc *)(_data + FROM_BE_32(_font->_charLoc));
+	_charData = _data + FROM_BE_32(_font->_charData);
+
+	_charSpace = nullptr;
+	_charKern = nullptr;
+	_cp = nullptr;
+	_pitch = 0;
+
+	if (_font->_charSpace != 0)
+		_charSpace = (uint16 *)(_data + FROM_BE_32(_font->_charSpace));
+	if (_font->_charKern != 0)
+		_charKern = (uint16 *)(_data + FROM_BE_32(_font->_charKern));
+
+	_maxCharWidth = _font->_xSize;
+	if (_charSpace) {
+		_maxCharWidth = FROM_BE_16(_charSpace[0]);
+		for (int i = _font->_hiChar - _font->_loChar; i > 0; --i) {
+			const int width = FROM_BE_16(_charSpace[i]);
+			if (_maxCharWidth < width)
+				_maxCharWidth = width;
+		}
+	}
+}
+
+bool AmigaFont::isProportional() const {
+	static constexpr byte kAmigaFpfProportional = 0x20;
+	return (_font->_flags & kAmigaFpfProportional) != 0;
+}
+
+int16 AmigaFont::getCharSpace(byte c) const {
+	return (_charSpace == nullptr) ? _font->_xSize : FROM_BE_16(_charSpace[c]);
+}
+
+int16 AmigaFont::getCharKern(byte c) const {
+	return (_charKern == nullptr) ? 0 : FROM_BE_16(_charKern[c]);
+}
+
 int AmigaFont::getFontHeight() const {
 	return _font->_ySize;
 }
@@ -271,6 +425,36 @@ uint16 AmigaFont::getOffset(byte c) const {
 	return FROM_BE_16(_charLoc[c]._offset);
 }
 
+int AmigaFont::getCharAdvanceWidth(uint32 chr) const {
+	const byte mapped = mapChar(chr);
+	return isProportional() ? getCharSpace(mapped) : _font->_xSize;
+}
+
+int AmigaFont::getCharDrawOffset(uint32 chr) const {
+	return getCharKern(mapChar(chr));
+}
+
+int AmigaFont::getCharInkWidth(uint32 chr) const {
+	return getPixels(mapChar(chr));
+}
+
+int AmigaFont::getCharRenderWidth(uint32 chr) const {
+	const int drawOffset = getCharDrawOffset(chr);
+	const int inkWidth = getCharInkWidth(chr);
+	const int advance = getCharAdvanceWidth(chr);
+	int renderWidth = inkWidth;
+
+	if (drawOffset > 0)
+		renderWidth += drawOffset;
+	else if (drawOffset < 0)
+		renderWidth -= drawOffset;
+
+	if (renderWidth < advance)
+		renderWidth = advance;
+
+	return renderWidth;
+}
+
 uint32 AmigaFont::mapChar(uint32 c) const {
 	if (c < _font->_loChar || c > _font->_hiChar)
 		error("character '%c (%x)' not supported by font", c, c);
diff --git a/graphics/fonts/amigafont.h b/graphics/fonts/amigafont.h
index da917c8ce45..8f4b1672c30 100644
--- a/graphics/fonts/amigafont.h
+++ b/graphics/fonts/amigafont.h
@@ -56,6 +56,12 @@ class AmigaFont : public Font {
 	};
 #include "common/pack-end.h"
 
+public:
+	enum Topaz9Builtin {
+		kTopaz9Builtin
+	};
+
+private:
 	AmigaDiskFont	*_font;
 	byte			*_data;
 	byte			*_charData;
@@ -68,6 +74,10 @@ class AmigaFont : public Font {
 	int             _maxCharWidth;
 
 private:
+	void initFromData(const byte *data, uint32 dataSize, uint32 fontOffset);
+	bool isProportional() const;
+	int16 getCharSpace(byte c) const;
+	int16 getCharKern(byte c) const;
 	uint16 getPixels(byte c) const;
 	uint16 getOffset(byte c) const;
 
@@ -81,16 +91,21 @@ public:
 	 *				  Topaz font is used.
 	 */
 	AmigaFont(Common::SeekableReadStream *stream = NULL);
+	AmigaFont(Topaz9Builtin);
 	virtual ~AmigaFont();
 
 	virtual int getFontHeight() const;
 	virtual int getCharWidth(uint32 chr) const;
 	virtual int getMaxCharWidth() const;
 	virtual int getKerningOffset(uint32 left, uint32 right) const;
+	int getCharAdvanceWidth(uint32 chr) const;
+	int getCharDrawOffset(uint32 chr) const;
+	int getCharInkWidth(uint32 chr) const;
+	int getCharRenderWidth(uint32 chr) const;
 	virtual void drawChar(Surface *dst, uint32 chr, int x, int y, uint32 color) const;
 
-	int getLoChar() { return _font->_loChar; }
-	int getHiChar() { return _font->_hiChar; }
+	int getLoChar() const { return _font->_loChar; }
+	int getHiChar() const { return _font->_hiChar; }
 };
 
 } // End of namespace Graphics


Commit: aeb375159061ce07b7820303f8e70d18b41a9c9b
    https://github.com/scummvm/scummvm/commit/aeb375159061ce07b7820303f8e70d18b41a9c9b
Author: Robert Megone (robert.megone at gmail.com)
Date: 2026-04-13T17:10:32+03:00

Commit Message:
AGOS: Simplify PN Amiga width handling

Changed paths:
    engines/agos/agos.cpp


diff --git a/engines/agos/agos.cpp b/engines/agos/agos.cpp
index 0ef6a06a1c2..56d8aa01f18 100644
--- a/engines/agos/agos.cpp
+++ b/engines/agos/agos.cpp
@@ -678,9 +678,7 @@ Common::Error AGOSEngine::init() {
 		_backBuf->create(_screenWidth, _screenHeight, Graphics::PixelFormat::createFormatCLUT8());
 		memset(_backBuf->getPixels(), 0, _backBuf->pitch * _backBuf->h);
 		_scaleBuf = new Graphics::Surface();
-		const uint16 scaleWidth = isPnAmiga() ? 640 : _internalWidth;
-		const uint16 scaleHeight = isPnAmiga() ? _screenHeight : _internalHeight;
-		_scaleBuf->create(scaleWidth, scaleHeight, Graphics::PixelFormat::createFormatCLUT8());
+		_scaleBuf->create(_internalWidth, isPnAmiga() ? _screenHeight : _internalHeight, Graphics::PixelFormat::createFormatCLUT8());
 		memset(_scaleBuf->getPixels(), 0, _scaleBuf->pitch * _scaleBuf->h);
 	}
 


Commit: 0629e6e22026a3ff7bf98df44f8336841df02190
    https://github.com/scummvm/scummvm/commit/0629e6e22026a3ff7bf98df44f8336841df02190
Author: Robert Megone (robert.megone at gmail.com)
Date: 2026-04-13T17:10:32+03:00

Commit Message:
AGOS: Back out of quicksave changes, swallow input instead

Changed paths:
    engines/agos/agos.h
    engines/agos/event.cpp
    engines/agos/saveload.cpp
    engines/agos/script_pn.cpp


diff --git a/engines/agos/agos.h b/engines/agos/agos.h
index fc054b3cbb8..f4ba808cea7 100644
--- a/engines/agos/agos.h
+++ b/engines/agos/agos.h
@@ -1452,7 +1452,6 @@ public:
 	void setupGame() override;
 	void setupOpcodes() override;
 	void setupVideoOpcodes(VgaOpcodeProc *op) override;
-	void quickLoadOrSave() override;
 	void windowDrawChar(WindowBlock *window, uint x, uint y, byte chr) override;
 	void saveInventoryPalette();
 	void applyInventoryPalette();
diff --git a/engines/agos/event.cpp b/engines/agos/event.cpp
index d145cb0febc..b1b84baf9c0 100644
--- a/engines/agos/event.cpp
+++ b/engines/agos/event.cpp
@@ -490,6 +490,9 @@ void AGOSEngine::delay(uint amount) {
 				if (event.kbd.keycode >= Common::KEYCODE_0 && event.kbd.keycode <= Common::KEYCODE_9
 					&& (event.kbd.hasFlags(Common::KBD_ALT) ||
 						event.kbd.hasFlags(Common::KBD_CTRL))) {
+					if (getGameType() == GType_PN)
+						break;
+
 					_saveLoadSlot = event.kbd.keycode - Common::KEYCODE_0;
 
 					// There is no save slot 0
diff --git a/engines/agos/saveload.cpp b/engines/agos/saveload.cpp
index b45ca0e8b3a..9161fc54fab 100644
--- a/engines/agos/saveload.cpp
+++ b/engines/agos/saveload.cpp
@@ -117,55 +117,6 @@ void AGOSEngine_Feeble::quickLoadOrSave() {
 }
 #endif
 
-void AGOSEngine_PN::quickLoadOrSave() {
-	Common::U32String buf;
-	const Common::String filename = genSaveName(_saveLoadSlot);
-	const byte requestedType = _saveLoadType;
-	bool success = false;
-
-	if (requestedType == 2) {
-		Common::InSaveFile *in = _saveFileMan->openForLoading(filename);
-		if (in == nullptr) {
-			buf = Common::U32String::format(_("Failed to load saved game from file:\n\n%s"), filename.c_str());
-		} else {
-			delete in;
-			const int numParams = _dataBase[getlong(_quickptr[6] + 3 * 267)];
-			if (numParams == 0) {
-				addstack(-1);
-				for (int i = 24; i < 32; ++i)
-					_variableArray[i] = 0;
-
-				setposition(267, 0);
-				success = (doline(1) != 0);
-			}
-			if (!success)
-				buf = Common::U32String::format(_("Failed to load saved game from file:\n\n%s"), filename.c_str());
-		}
-	} else if (requestedType == 1) {
-		memset(_saveFile, 0, sizeof(_saveFile));
-		for (uint i = 0; i < 8 && _saveLoadName[i] != 0; ++i)
-			_saveFile[i] = _saveLoadName[i];
-
-		success = (saveFile(filename) != 0);
-		if (!success)
-			buf = Common::U32String::format(_("Failed to save game to file:\n\n%s"), filename.c_str());
-	} else {
-		_saveLoadType = 0;
-		return;
-	}
-
-	if (!success) {
-		GUI::MessageDialog dialog(buf);
-		dialog.runModal();
-	} else if (requestedType == 1) {
-		buf = Common::U32String::format(_("Successfully saved game in file:\n\n%s"), filename.c_str());
-		GUI::TimedMessageDialog dialog(buf, 1500);
-		dialog.runModal();
-	}
-
-	_saveLoadType = 0;
-}
-
 // The function uses segments of code from the original game scripts
 // to allow quick loading and saving, but isn't perfect.
 //
diff --git a/engines/agos/script_pn.cpp b/engines/agos/script_pn.cpp
index 0a4f05f8bd6..90ee4f66f33 100644
--- a/engines/agos/script_pn.cpp
+++ b/engines/agos/script_pn.cpp
@@ -384,18 +384,9 @@ void AGOSEngine_PN::opn_opcode31() {
 
 	switch (a) {
 		case 0:
-			if (_saveLoadType == 2) {
-				memset(_saveFile, 0, sizeof(_saveFile));
-				for (uint i = 0; i < 8 && _saveLoadName[i] != 0; ++i)
-					_saveFile[i] = _saveLoadName[i];
-				slot = _saveLoadSlot;
-				bf = genSaveName(slot);
-				_saveLoadType = 0;
-			} else {
-				getFilename();
-				slot = matchSaveGame(_saveFile, countSaveGames());
-				bf = genSaveName(slot);
-			}
+			getFilename();
+			slot = matchSaveGame(_saveFile, countSaveGames());
+			bf = genSaveName(slot);
 			break;
 		case 1:
 			bf = "pn.sav";
@@ -432,20 +423,12 @@ void AGOSEngine_PN::opn_opcode32() {
 	uint16 curSlot = countSaveGames();
 	switch (a) {
 		case 0:
-			if (_saveLoadType == 1) {
-				memset(_saveFile, 0, sizeof(_saveFile));
-				for (uint i = 0; i < 8 && _saveLoadName[i] != 0; ++i)
-					_saveFile[i] = _saveLoadName[i];
-				bf = genSaveName(_saveLoadSlot);
-				_saveLoadType = 0;
-			} else {
-				getFilename();
-				slot = matchSaveGame(_saveFile, curSlot);
-				if (slot != -1)
-					bf = genSaveName(slot);
-				else
-					bf = genSaveName(curSlot);
-			}
+			getFilename();
+			slot = matchSaveGame(_saveFile, curSlot);
+			if (slot != -1)
+				bf = genSaveName(slot);
+			else
+				bf = genSaveName(curSlot);
 			break;
 		case 1:
 			bf = "pn.sav";


Commit: 33894ae5045be959280dec14c124d66fdbc84ce6
    https://github.com/scummvm/scummvm/commit/33894ae5045be959280dec14c124d66fdbc84ce6
Author: Robert Megone (robert.megone at gmail.com)
Date: 2026-04-13T17:10:32+03:00

Commit Message:
AGOS: Move topaz 9 double height draw from AGOS to amigafont

Changed paths:
    engines/agos/charset-fontdata.cpp
    graphics/fonts/amigafont.cpp
    graphics/fonts/amigafont.h


diff --git a/engines/agos/charset-fontdata.cpp b/engines/agos/charset-fontdata.cpp
index 14fa4cee8ee..4a350757b77 100644
--- a/engines/agos/charset-fontdata.cpp
+++ b/engines/agos/charset-fontdata.cpp
@@ -3061,47 +3061,6 @@ static inline void pnSqueezeGlyph8Rows(const byte *src8, byte *dst8) {
 		dst8[i] = pnSqueezeRow(src8[i], anyBit1SetAcrossGlyph);
 }
 
-static void drawPnAmigaDoubleHeightTopazChar(const Graphics::AmigaFont *font, Graphics::Surface *dst, byte chr, int x, int y, byte color) {
-	enum {
-		kPnAmigaTopazScratchWidth = 16,
-		kPnAmigaTopazScratchHeight = 16
-	};
-
-	const int glyphWidth = font->getCharRenderWidth(chr);
-	const int rawHeight = font->getFontHeight();
-	const int drawOffset = font->getCharDrawOffset(chr);
-	const int glyphLeft = MIN<int>(0, drawOffset);
-	const int glyphOriginX = drawOffset - glyphLeft;
-	if (glyphWidth <= 0 || rawHeight <= 0)
-		return;
-	assert(glyphWidth <= kPnAmigaTopazScratchWidth);
-	assert(rawHeight <= kPnAmigaTopazScratchHeight);
-
-	byte glyphPixels[kPnAmigaTopazScratchWidth * kPnAmigaTopazScratchHeight];
-	memset(glyphPixels, 0, sizeof(glyphPixels));
-
-	Graphics::Surface glyphSurface;
-	glyphSurface.init(glyphWidth, rawHeight, glyphWidth, glyphPixels, Graphics::PixelFormat::createFormatCLUT8());
-	font->drawChar(&glyphSurface, chr, glyphOriginX, 0, 1);
-
-	for (int srcY = 0; srcY < rawHeight; ++srcY) {
-		for (int srcX = 0; srcX < glyphWidth; ++srcX) {
-			if (glyphPixels[srcY * glyphWidth + srcX] == 0)
-				continue;
-
-			const int dstX = x + glyphLeft + srcX;
-			if (dstX < 0 || dstX >= dst->w)
-				continue;
-
-			for (int repeat = 0; repeat < 2; ++repeat) {
-				const int dstY = y + srcY * 2 + repeat;
-				if (dstY >= 0 && dstY < dst->h)
-					*(byte *)dst->getBasePtr(dstX, dstY) = color;
-			}
-		}
-	}
-}
-
 void AGOSEngine::drawPnAmigaTopazChar(WindowBlock *window, byte chr) {
 	PnAmigaTextPlane *plane = getPnAmigaTextPlane(window);
 	if (plane == nullptr || plane->pixels == nullptr)
@@ -3130,7 +3089,7 @@ void AGOSEngine::drawPnAmigaTopazChar(WindowBlock *window, byte chr) {
 		return;
 
 	if (usePnAmigaDoubleHeightTopaz())
-		drawPnAmigaDoubleHeightTopazChar(font, &surface, chr, x, y, window->textColor);
+		font->drawCharDoubleHeight(&surface, chr, x, y, window->textColor);
 	else
 		font->drawChar(&surface, chr, x + font->getCharDrawOffset(chr), y, window->textColor);
 }
diff --git a/graphics/fonts/amigafont.cpp b/graphics/fonts/amigafont.cpp
index d464f48282e..7a07ca226d1 100644
--- a/graphics/fonts/amigafont.cpp
+++ b/graphics/fonts/amigafont.cpp
@@ -27,8 +27,9 @@
 
 namespace Graphics {
 
+// Topaz 8
 // For the data source and license look into gui/themes/fonts/topaz in ScummVM distribution
-static const byte amigaTopazFont[2600] = {
+static const byte amigaTopaz8Font[2600] = {
 	0x00, 0x00, 0x03, 0xf3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x79, 0x00, 0x00, 0x03, 0xe9, 0x00, 0x00, 0x02, 0x79,
 	0x70, 0xff, 0x4e, 0x75, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
@@ -298,7 +299,7 @@ static const byte amigaTopaz9Font[] = {
 AmigaFont::AmigaFont(Common::SeekableReadStream *stream) {
 	Common::SeekableReadStream *tmp;
 	if (!stream) {
-		tmp = new Common::MemoryReadStream(amigaTopazFont, sizeof(amigaTopazFont), DisposeAfterUse::NO);
+		tmp = new Common::MemoryReadStream(amigaTopaz8Font, sizeof(amigaTopaz8Font), DisposeAfterUse::NO);
 	} else {
 		tmp = stream;
 	}
@@ -497,6 +498,47 @@ void drawCharIntern(byte *ptr, uint32 pitch, int num, int bitOffset, byte *charD
 	}
 }
 
+void AmigaFont::drawCharDoubleHeight(Surface *dst, uint32 chr, int x, int y, uint32 color) const {
+	enum {
+		kAmigaFontScratchWidth = 16,
+		kAmigaFontScratchHeight = 16
+	};
+
+	const int glyphWidth = getCharRenderWidth(chr);
+	const int rawHeight = getFontHeight();
+	const int drawOffset = getCharDrawOffset(chr);
+	const int glyphLeft = MIN<int>(0, drawOffset);
+	const int glyphOriginX = drawOffset - glyphLeft;
+	if (glyphWidth <= 0 || rawHeight <= 0)
+		return;
+	assert(glyphWidth <= kAmigaFontScratchWidth);
+	assert(rawHeight <= kAmigaFontScratchHeight);
+
+	byte glyphPixels[kAmigaFontScratchWidth * kAmigaFontScratchHeight];
+	memset(glyphPixels, 0, sizeof(glyphPixels));
+
+	Surface glyphSurface;
+	glyphSurface.init(glyphWidth, rawHeight, glyphWidth, glyphPixels, PixelFormat::createFormatCLUT8());
+	drawChar(&glyphSurface, chr, glyphOriginX, 0, 1);
+
+	for (int srcY = 0; srcY < rawHeight; ++srcY) {
+		for (int srcX = 0; srcX < glyphWidth; ++srcX) {
+			if (glyphPixels[srcY * glyphWidth + srcX] == 0)
+				continue;
+
+			const int dstX = x + glyphLeft + srcX;
+			if (dstX < 0 || dstX >= dst->w)
+				continue;
+
+			for (int repeat = 0; repeat < 2; ++repeat) {
+				const int dstY = y + srcY * 2 + repeat;
+				if (dstY >= 0 && dstY < dst->h)
+					*(byte *)dst->getBasePtr(dstX, dstY) = color;
+			}
+		}
+	}
+}
+
 void AmigaFont::drawChar(Surface *dst, uint32 chr, int x, int y, uint32 color) const {
 	chr = mapChar(chr);
 
diff --git a/graphics/fonts/amigafont.h b/graphics/fonts/amigafont.h
index 8f4b1672c30..c5dc7dac7f6 100644
--- a/graphics/fonts/amigafont.h
+++ b/graphics/fonts/amigafont.h
@@ -88,7 +88,7 @@ public:
 	 * Create font in Amiga format.
 	 *
 	 * @param stream  Stream with the font data. If NULL, then the built-in
-	 *				  Topaz font is used.
+	 *				  Topaz 8 font is used.
 	 */
 	AmigaFont(Common::SeekableReadStream *stream = NULL);
 	AmigaFont(Topaz9Builtin);
@@ -102,6 +102,7 @@ public:
 	int getCharDrawOffset(uint32 chr) const;
 	int getCharInkWidth(uint32 chr) const;
 	int getCharRenderWidth(uint32 chr) const;
+	void drawCharDoubleHeight(Surface *dst, uint32 chr, int x, int y, uint32 color) const;
 	virtual void drawChar(Surface *dst, uint32 chr, int x, int y, uint32 color) const;
 
 	int getLoChar() const { return _font->_loChar; }




More information about the Scummvm-git-logs mailing list