[Scummvm-git-logs] scummvm master -> 2bea7912782700f14b3058a2ee677d3353511a40

sev- noreply at scummvm.org
Sat Sep 16 22:57:15 UTC 2023


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

Summary:
7ca349c392 GUI: Initial code for backend help dialog
7f3d615500 GUI: Proper dialog dimensions for HelDialog
6d6a7a9b3a GUI: First step to draw tabs on bottom
5653eccba4 GUI: Propagate tab vertical flip to the VectorRenderer
f048d170e7 GUI: Added stubs for RichTextWidget
57568e8fdf GUI: Further work on RichText widget
76dbb66489 GUI: Added RichText to HelpDialog
db62679c74 GUI: Use proper font in RichTextWidget
3c6284949c COMMON: Added initial code for Markdown parsing
ce04c4b309 GRAPHICS: MACGUI: Initial stub for MacText::setMarkdownText()
f6ffcf3c82 GRAPHICS: MACGUI: Implemented stubs for Markdown parser
5719847354 GRAPHICS: MACGUI: Pass setMarkdownText through MacTextWindow
85b2b3ce1d COMMON: Fix function prototype
bbc6dbcd10 GRAPHICS: MACGUI: Properly grow buffer for Markdown
d10f80a8c9 GRAPHICS: MACGUI: Implement equivalent of tag opening and closing for MacText
d69a9689cc GRAPHICS: MACGUI: fix printing out of debug info in Markdown
aeb925a790 GUI: Added more text to HelpDialog test
027e3794d3 GRAPHICS: MACGUI: implemented italic and bold Markdown output
ee7573b222 GRAPHICS: MACGUI: Marked Markdown parts with STUB and started real rendering
32f7eec2c6 GRAPHICS: MACGUI: Remove using of \015 as a binary MacText formatting
a314def2cc GRAPHICS: MACGUI: Added Header formatting, synced with stripFormatting(), fixed closing formatting
345706521c GUI: Fixed font setting in RichText
85a492e039 GRAPHICS: MACGUI: Properly reset font slant on end of formatting
f086c5cd13 GRAPHICS: MACGUI: Added more debug output to MacText
5e895c6af5 GUI: Added extrapath to SearchMan before initializing WM in RichText
fd844bc00f GRAPHICS: MACGUI: Added kWMModeNoCursorOverride
2b498a0ff1 GUI: Do not override GUI cursor for RichText
e555528a42 GUI: Expose theme text colors int ThemeEngine
c8cc2c46f7 GUI: Use theme colors for RichText
f12386adea COMMON: FORMATS: Catch list start in Markdown
4033c0f869 GRAPHICS: MACGUI: Implemented nested list processing in MarkDown
3cfc80e110 GRAPHICS: MACGUI: Properly set default text color in MacText
1e6d7db76e GRAPHICS: MACGUI: Added one more ident level for lists in Marcdown
49d9261fd5 GUI: Simulatoe better colors in RichText
873926f88c GUI: Rearranged tabs in help dialog
69aee0d228 GRAPHICS: Fix regression in font scaling code
75ebd7b28c GUI: Added more content to HelpDialog
28b14d83fc GUI: Initial code for vertical scroll bar in RichTextWidget
daf829aeb4 GUI: Replace MacTextWindow with plain MacText in RichTextWidget
bb4792e413 GUI: MOre work on adding scrollbar to RichTextWidget
75fa02a879 GUI: Implement scrolling in RichTextWidget
255a205896 GUI: Improved visuals in HelpDialog
20a0888c8d GUI: Fix scrollbar behavior in RichTextWidget
df0fa2c709 GUI: Simplified text width calcultions in RichTextWidget
72f57a670e GRAPHICS: Removed temporary debug output
a00ae54785 GUI: Fixed default text color in RechText
85f54663f5 GRAPHICS: JANITORIAL: More logical text formatting processing sequence
5b00cc3f56 GRAPHICS: MACGUI: Implemented color on/off for MacText
6044980b2d GRAPHICS: MACGUI: Paint links blue in MarkDown
125d3dfa69 GRAPPHICS: MACGUI: Added a way to get link under cursor for MacText
2b35c7997f GUI: Implemented link tooltips for RichTextWidget
8eb91297c1 GUI: Fixed coordinate calculation for dynamic tooltips
9de6c06d9d GRAPHICS: MACGUI: Simplified buffer manipulation in Markdown
eac4a62389 COMMON: Remove unused functions in Markdown
3cbb035875 COMMON: Added String::chop() method
3bce81ac70 TESTS: Added test for String::chop() method
fe0fbe5cff COMMON: Make Markdown return Common::String
ce8a0456db COMMON: Reduce Common:: namespace pollution by Markdown
e237634777 COMMON: Turn sd_markdown into a class
c20290afeb COMMON: Rename class variables in SDMarkdown
2df991721d GRAPHICS: MACGUI: Added initial code for indentaiton in MacText
168f4b7b3a GRAPHICS: MACGUI: Apply indentation to MacText
c147f4ee5f GUI: Indent list items in Markdown
0294d607de GRAPHICS: MACGUI: Made inentation in Markdown dynamic and fix it for long lines
03a758c3b1 GRAPHICS: MACGUI: Proper indentation of bullet lists in Markdown
c668169636 GRAPHICS: MACGUI: Encode images into MacText from Markdown
1a6ad65f17 GRAPHICS: MACGUI: Added crash on raw html tags processing in Markdown
461d3218af GUI: Fix assert when dynamic tooltip is empty
ae41e3aa24 GUI: Added SAF instuctions to help dialog. This contains pictures
f53d39741d GRAPHICS: MACGUI: Added tag for switching font to MacText
c9a123b45a GRAPHICS: MACGUI: Implemented rendering for blockcode and raw_html_tag in Markdown
9e488b4ee7 GUI: Added paragraph space for SAF text in HelpDialog
efdc9c3193 GRAPHICS: MACGUI: More rebust debug output in Markdown
49ef52079c GRAPHICS: MACGUI: Improved blockcode rendering in Markdown
b614756ae6 GUI: Fix SAF page formatting in HelpDialog
01263461a5 GRAPHICS: MACGUI: Fix bullet lists indentation in the Markdown
7ddffd0a44 GRAPHICS: MACGUI: Fix computing of cursor position for indented lines in MacText
34acf247be GRAPHICS: MACGUI: Used switch() statement in MacText formatting parsing
91a829db69 GRAPHICS: MACGUI: Implemented image rendering in Markdown
1c39564262 GRAPHICS: MACGUI: Properly process missing images in Markdown
c4b3ece401 GRAPHICS: MACGUI: Load pictures from a specified zip file in Markdown
3a84773936 GUI: Pass image archive from RichText to MacText
02cffed5e2 GUI: Specify image archive in HelpDialog
30f825135c DISTS: ANDROID: Added archive with help dialog pictures
152feb0f46 GRAPHICS: MACGUI: Properly pass link URLs from Markdown to MacText
fd98104c9e GUI: Open URL when clicking on the links in RichTextWidget
eb169e8ed1 GRAPHICS: MACGUI: Added (disabled) code for dumping MacText rendering into PNG
004eba9d25 GRAPHICS: MACGUI: Fix MacText transparency rendering
3857f716e6 GUI: THEMES: Moved help button to the left side
4b86df2f62 GUI: Regenerated themes
c0db1957c8 GUI: Pick up theme font size in RIchTextWidget
367b059040 GUI: Fix type in ScrollCOntainerWidget height calculation
7ab1c93712 OSYSTEM: Added buildHelpDialogData() call
ba851757f0 GRAPHICS: MACGUI: Used more logical sizes for headers
c57b199eb4 GUI: Adjusted header sizes in HelpDialog
6109f08cfb GRAPHICS: MACGUI: Implemented codespan rendering in Markdown
878870b0ef GUI: Added keyboard shortcuts tab to HelpDialog
70d47e31ed GRAPHICS: MACGUI: Remove leftover debug paint
afd6866a3c GUI: Speed up RichTextWidget scrolling
b307976808 GUI: Add supoport for backend-provided tabs in HelpDialog
6ae8089c61 GUI: Move Keyboards shortcut help to SDL backend
d4d82be3e7 BACKENDS: SDL: Specified platform-dependent quit shortcut in help dialog
7527757d6f BACKENDS: ANDROID: Move Android-specific HelpDialog tabs to backend
bf7c041b36 GUI: Switch to the first tab upon opening HelpDialog
1d636a1687 OSYSTEM: Described format for buildHelpDialogData() call
9cd36c7032 GUI: Fixed warning when constructing HelpDialog
a14b2bf9ba ANDROID: Added andriod-help.zip to the bundle
005609b560 GUI: Add link to compatibility page in the gnereal help tab
b45e812a13 GUI: Reworded help tabe "where to get games"
2bea791278 OSYSTEM: Improed const'ness of the buildHelpDialogData() call


Commit: 7ca349c392788635ccdcf8553adaaad7ddbd62a9
    https://github.com/scummvm/scummvm/commit/7ca349c392788635ccdcf8553adaaad7ddbd62a9
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Initial code for backend help dialog

Changed paths:
  A gui/helpdialog.cpp
  A gui/helpdialog.h
    gui/launcher.cpp
    gui/module.mk
    gui/themes/common/highres_layout.stx


diff --git a/gui/helpdialog.cpp b/gui/helpdialog.cpp
new file mode 100644
index 00000000000..a64eaf70488
--- /dev/null
+++ b/gui/helpdialog.cpp
@@ -0,0 +1,41 @@
+/* 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 "common/translation.h"
+#include "gui/helpdialog.h"
+#include "gui/widget.h"
+
+namespace GUI {
+
+HelpDialog::HelpDialog()
+	: Dialog(30, 20, 260, 124) {
+
+	new ButtonWidget(this, 10, 10, 16, 16, Common::U32String("Close"), Common::U32String(), kCloseCmd);
+}
+
+void HelpDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
+	switch (cmd) {
+		case kCloseCmd:
+			close();
+	}
+}
+
+} // End of namespace GUI
diff --git a/gui/helpdialog.h b/gui/helpdialog.h
new file mode 100644
index 00000000000..cdf0786cfd5
--- /dev/null
+++ b/gui/helpdialog.h
@@ -0,0 +1,47 @@
+/* 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 GUI_HELP_DIALOG_H
+#define GUI_HELP_DIALOG_H
+
+#include "gui/dialog.h"
+#include "common/str.h"
+#include "common/str-array.h"
+
+namespace GUI {
+
+class CommandSender;
+class StaticTextWidget;
+
+/**
+ * Multitab help dialog
+ */
+class HelpDialog : public Dialog {
+public:
+	HelpDialog();
+
+	void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
+};
+
+
+} // End of namespace GUI
+
+#endif
diff --git a/gui/launcher.cpp b/gui/launcher.cpp
index 4e373c9bab5..43712840fa5 100644
--- a/gui/launcher.cpp
+++ b/gui/launcher.cpp
@@ -34,6 +34,7 @@
 #include "gui/chooser.h"
 #include "gui/dlcsdialog.h"
 #include "gui/editgamedialog.h"
+#include "gui/helpdialog.h"
 #include "gui/launcher.h"
 #include "gui/massadd.h"
 #include "gui/message.h"
@@ -80,6 +81,7 @@ enum {
 	kListSearchCmd = 'LSSR',
 	kSearchClearCmd = 'SRCL',
 	kSetGroupMethodCmd = 'GPBY',
+	kHelpCmd = 'HELP',
 
 	kListSwitchCmd = 'LIST',
 	kGridSwitchCmd = 'GRID',
@@ -256,6 +258,9 @@ void LauncherDialog::build() {
 #endif
 		new StaticTextWidget(this, _title + ".Version", Common::U32String(gScummVMFullVersion));
 
+	//if (g_system->hasFeature(OSystem::kFeatureHelpDialog))
+		new ButtonWidget(this, _title + ".HelpButton", Common::U32String("?"), _("Help"), kHelpCmd);
+
 	if (!g_system->hasFeature(OSystem::kFeatureNoQuit)) {
 		// I18N: Button Quit ScummVM program. Q is the shortcut, Ctrl+Q, put it in parens for non-latin (~Q~)
 		new ButtonWidget(this, _title + ".QuitButton", _("~Q~uit"), _("Quit ScummVM"), kQuitCmd);
@@ -790,6 +795,11 @@ void LauncherDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 dat
 		setResult(-1);
 		close();
 		break;
+	case kHelpCmd: {
+		HelpDialog dlg;
+		dlg.runModal();
+		}
+		break;
 #ifndef DISABLE_LAUNCHERDISPLAY_GRID
 	case kGridSwitchCmd:
 		setResult(kSwitchLauncherDialog);
@@ -1153,7 +1163,7 @@ void LauncherSimple::updateListing() {
 	// Update the filter settings, those are lost when "setList"
 	// is called.
 	_list->setFilter(_searchWidget->getEditString());
-	
+
 	// Close groups that the user closed earlier
 	_list->loadClosedGroups(Common::U32String(groupingModes[_groupBy].name));
 }
@@ -1482,7 +1492,7 @@ void LauncherGrid::handleCommand(CommandSender *sender, uint32 cmd, uint32 data)
 		break;
 	case kSetGroupMethodCmd: {
 		_grid->saveClosedGroups(Common::U32String(groupingModes[_groupBy].name));
-	
+
 		// Change the grouping criteria
 		GroupingMethod newGroupBy = (GroupingMethod)data;
 		if (_groupBy != newGroupBy) {
diff --git a/gui/module.mk b/gui/module.mk
index 5f93f397526..78758e0e3a2 100644
--- a/gui/module.mk
+++ b/gui/module.mk
@@ -13,6 +13,7 @@ MODULE_OBJS := \
 	EventRecorder.o \
 	filebrowser-dialog.o \
 	gui-manager.o \
+	helpdialog.o \
 	imagealbum-dialog.o \
 	launcher.o \
 	massadd.o \
diff --git a/gui/themes/common/highres_layout.stx b/gui/themes/common/highres_layout.stx
index fa866fc0c46..79e1638a668 100644
--- a/gui/themes/common/highres_layout.stx
+++ b/gui/themes/common/highres_layout.stx
@@ -153,11 +153,21 @@
 
 	<dialog name = 'Launcher' overlays = 'screen' resolution = 'y<=x'>
 		<layout type = 'vertical' align = 'center' padding = '23, 23, 8, 40'>
-			<widget name = 'Logo'
-					width = '287'
-					height = '80'
-					rtl = 'no'
-			/>
+			<layout type = 'horizontal'  spacing = '5' padding = '10, 0, 0, 0'>
+				<space size  = 'Globals.Button.Height' />
+				<space />
+				<widget name = 'Logo'
+						width = '287'
+						height = '80'
+						rtl = 'no'
+				/>
+				<space />
+				<widget name = 'HelpButton'
+						height = 'Globals.Button.Height'
+						width = 'Globals.Button.Height'
+						rtl = 'yes'
+				/>
+			</layout>
 			<layout type = 'horizontal'  spacing = '5' padding = '10, 0, 0, 0'>
 				<widget name = 'SearchPic'
 						width = '16'


Commit: 7f3d615500f157f908d39a00ffdae03f8d59c1e9
    https://github.com/scummvm/scummvm/commit/7f3d615500f157f908d39a00ffdae03f8d59c1e9
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Proper dialog dimensions for HelDialog

Changed paths:
    gui/helpdialog.cpp


diff --git a/gui/helpdialog.cpp b/gui/helpdialog.cpp
index a64eaf70488..1cd8761c530 100644
--- a/gui/helpdialog.cpp
+++ b/gui/helpdialog.cpp
@@ -21,6 +21,8 @@
 
 #include "common/translation.h"
 #include "gui/helpdialog.h"
+#include "gui/gui-manager.h"
+#include "gui/ThemeEval.h"
 #include "gui/widget.h"
 
 namespace GUI {
@@ -28,7 +30,21 @@ namespace GUI {
 HelpDialog::HelpDialog()
 	: Dialog(30, 20, 260, 124) {
 
-	new ButtonWidget(this, 10, 10, 16, 16, Common::U32String("Close"), Common::U32String(), kCloseCmd);
+	const int screenW = g_system->getOverlayWidth();
+	const int screenH = g_system->getOverlayHeight();
+
+	int buttonWidth = g_gui.xmlEval()->getVar("Globals.Button.Width", 0);
+	int buttonHeight = g_gui.xmlEval()->getVar("Globals.Button.Height", 0);
+
+	_w = screenW * 8 / 10;
+	_h = screenH * 8 / 10;
+
+	// Center the dialog
+	_x = (screenW - _w) / 2;
+	_y = (screenH - _h) / 2;
+
+
+	new ButtonWidget(this, _w - buttonWidth - 10, _h - buttonHeight - 10, buttonWidth, buttonHeight, Common::U32String("Close"), Common::U32String(), kCloseCmd);
 }
 
 void HelpDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {


Commit: 6d6a7a9b3a64beccdc36743eeab90c704d661097
    https://github.com/scummvm/scummvm/commit/6d6a7a9b3a64beccdc36743eeab90c704d661097
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: First step to draw tabs on bottom

Changed paths:
    gui/ThemeEngine.cpp
    gui/ThemeEngine.h
    gui/widgets/tab.cpp
    gui/widgets/tab.h


diff --git a/gui/ThemeEngine.cpp b/gui/ThemeEngine.cpp
index f973aed4579..596e25a8786 100644
--- a/gui/ThemeEngine.cpp
+++ b/gui/ThemeEngine.cpp
@@ -1295,13 +1295,22 @@ void ThemeEngine::drawWidgetBackground(const Common::Rect &r, WidgetBackground b
 }
 
 void ThemeEngine::drawTab(const Common::Rect &r, int tabHeight, const Common::Array<int> &tabWidths,
-						  const Common::Array<Common::U32String> &tabs, int active, bool rtl) {
+						  const Common::Array<Common::U32String> &tabs, int active, bool rtl,
+						  ThemeEngine::TextAlignVertical alignV) {
 	if (!ready())
 		return;
 
 	assert(tabs.size() == tabWidths.size());
 
-	drawDD(kDDTabBackground, Common::Rect(r.left, r.top, r.right, r.top + tabHeight));
+	int y1 = r.top;
+	int y2 = r.top + tabHeight;
+
+	if (alignV == ThemeEngine::kTextAlignVBottom) {
+		y1 = r.bottom;
+		y2 = r.bottom + tabHeight;
+	}
+
+	drawDD(kDDTabBackground, Common::Rect(r.left, y1, r.right, y2));
 
 	const int numTabs = (int)tabs.size();
 	int width = 0;
@@ -1329,7 +1338,7 @@ void ThemeEngine::drawTab(const Common::Rect &r, int tabHeight, const Common::Ar
 		}
 
 
-		Common::Rect tabRect(r.left + width, r.top, r.left + width + tabWidths[current], r.top + tabHeight);
+		Common::Rect tabRect(r.left + width, y1, r.left + width + tabWidths[current], y2);
 		drawDD(kDDTabInactive, tabRect);
 		drawDDText(getTextData(kDDTabInactive), getTextColor(kDDTabInactive), tabRect, tabs[current], false, false,
 		           convertTextAlignH(_widgets[kDDTabInactive]->_textAlignH, rtl), _widgets[kDDTabInactive]->_textAlignV);
@@ -1337,7 +1346,7 @@ void ThemeEngine::drawTab(const Common::Rect &r, int tabHeight, const Common::Ar
 	}
 
 	if (activePos >= 0) {
-		Common::Rect tabRect(r.left + activePos, r.top, r.left + activePos + tabWidths[active], r.top + tabHeight);
+		Common::Rect tabRect(r.left + activePos, y1, r.left + activePos + tabWidths[active], y2);
 		const uint16 tabLeft = activePos;
 		const uint16 tabRight = MAX(r.right - tabRect.right, 0);
 		drawDD(kDDTabActive, tabRect, (tabLeft << 16) | (tabRight & 0xFFFF));
diff --git a/gui/ThemeEngine.h b/gui/ThemeEngine.h
index c912fd25ab7..72d31039e56 100644
--- a/gui/ThemeEngine.h
+++ b/gui/ThemeEngine.h
@@ -477,7 +477,8 @@ public:
 	                     WidgetStateInfo state = kStateEnabled, bool rtl = false);
 
 	void drawTab(const Common::Rect &r, int tabHeight, const Common::Array<int> &tabWidths,
-	             const Common::Array<Common::U32String> &tabs, int active, bool rtl = false);
+	             const Common::Array<Common::U32String> &tabs, int active, bool rtl,
+				 ThemeEngine::TextAlignVertical alignV);
 
 	void drawScrollbar(const Common::Rect &r, int sliderY, int sliderHeight, ScrollbarState scrollState);
 
diff --git a/gui/widgets/tab.cpp b/gui/widgets/tab.cpp
index 1e130ea34b6..548b89b8114 100644
--- a/gui/widgets/tab.cpp
+++ b/gui/widgets/tab.cpp
@@ -51,13 +51,13 @@ void TabWidget::recalc() {
 	_titleSpacing = g_gui.xmlEval()->getVar("Globals.TabWidget.TitleSpacing");
 }
 
-TabWidget::TabWidget(GuiObject *boss, int x, int y, int w, int h)
-	: Widget(boss, x, y, w, h), _bodyBackgroundType(GUI::ThemeEngine::kDialogBackgroundDefault) {
+TabWidget::TabWidget(GuiObject *boss, int x, int y, int w, int h, ThemeEngine::TextAlignVertical alignV)
+	: Widget(boss, x, y, w, h), _bodyBackgroundType(ThemeEngine::kDialogBackgroundDefault), _alignV(alignV) {
 	init();
 }
 
-TabWidget::TabWidget(GuiObject *boss, const Common::String &name)
-	: Widget(boss, name), _bodyBackgroundType(GUI::ThemeEngine::kDialogBackgroundDefault) {
+TabWidget::TabWidget(GuiObject *boss, const Common::String &name, ThemeEngine::TextAlignVertical alignV)
+	: Widget(boss, name), _bodyBackgroundType(ThemeEngine::kDialogBackgroundDefault), _alignV(alignV) {
 	init();
 }
 
@@ -74,6 +74,9 @@ void TabWidget::init() {
 	int x = _w - _butRP - _butW * 2 - 2;
 	int y = _butTP - _tabHeight;
 
+	if (_alignV == ThemeEngine::kTextAlignVBottom)
+		y = _h - _tabHeight + _butTP;
+
 	Common::String leftArrow = g_gui.useRTL() ? ">" : "<";
 	Common::String rightArrow = g_gui.useRTL() ? "<" : ">";
 
@@ -266,8 +269,6 @@ void TabWidget::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
 }
 
 void TabWidget::handleMouseDown(int x, int y, int button, int clickCount) {
-	assert(y < _tabHeight);
-
 	if (x < 0)
 		return;
 
@@ -285,8 +286,13 @@ void TabWidget::handleMouseDown(int x, int y, int button, int clickCount) {
 }
 
 void TabWidget::handleMouseMoved(int x, int y, int button) {
-	if (y < 0 || y >= _tabHeight)
-		return;
+	if (_alignV == ThemeEngine::kTextAlignVBottom) {
+		if (y < _w - _tabHeight || y > _w)
+			return;
+	} else {
+		if (y < 0 || y >= _tabHeight)
+			return;
+	}
 
 	if (x < 0)
 		return;
@@ -424,6 +430,10 @@ void TabWidget::reflowLayout() {
 
 	int x = _w - _butRP - _butW * 2 - 2;
 	int y = _butTP - _tabHeight;
+
+	if (_alignV == ThemeEngine::kTextAlignVBottom)
+		y = _h - _tabHeight + _butTP;
+
 	_navLeft->resize(x, y, _butW, _butH, false);
 	_navRight->resize(x + _butW + 2, y, _butW, _butH, false);
 }
@@ -441,7 +451,7 @@ void TabWidget::drawWidget() {
 			_bodyBackgroundType);
 
 	g_gui.theme()->drawTab(Common::Rect(_x, _y, _x + _w, _y + _h), _tabHeight, widths, tabs,
-				_activeTab - _firstVisibleTab, (g_gui.useRTL() && _useRTL));
+				_activeTab - _firstVisibleTab, (g_gui.useRTL() && _useRTL), _alignV);
 }
 
 void TabWidget::draw() {
@@ -470,22 +480,25 @@ bool TabWidget::containsWidget(Widget *w) const {
 
 
 Widget *TabWidget::findWidget(int x, int y) {
-	if (y < _tabHeight) {
-		if (_navButtonsVisible) {
-			if (y >= _butTP && y < _butTP + _butH) {
-				if (x >= _w - _butRP - _butW * 2 - 2 && x < _w - _butRP - _butW - 2)
-					return _navLeft;
-				if (x >= _w - _butRP - _butW &&  x < _w - _butRP)
+	if ((_alignV == ThemeEngine::kTextAlignVBottom && y < _h - _tabHeight) ||
+		(_alignV == ThemeEngine::kTextAlignVTop && y >= _tabHeight)) {
+		// Iterate over all child widgets and find the one which was clicked
+		return Widget::findWidgetInChain(_firstWidget, x, y - _tabHeight);
+	}
+
+	if (_navButtonsVisible) {
+		if ((_alignV == ThemeEngine::kTextAlignVTop && y >= _butTP && y < _butTP + _butH) ||
+			(_alignV == ThemeEngine::kTextAlignVBottom && y >= _h - _butH - _butTP + _tabHeight && y < _h + _tabHeight)) {
+			if (x >= _w - _butRP - _butW * 2 - 2 && x < _w - _butRP - _butW - 2)
+				return _navLeft;
+
+			if (x >= _w - _butRP - _butW &&  x < _w - _butRP)
 					return _navRight;
 			}
 		}
 
-		// Click was in the tab area
-		return this;
-	} else {
-		// Iterate over all child widgets and find the one which was clicked
-		return Widget::findWidgetInChain(_firstWidget, x, y - _tabHeight);
-	}
+	// Click was in the tab area
+	return this;
 }
 
 void TabWidget::computeLastVisibleTab(bool adjustFirstIfRoom) {
diff --git a/gui/widgets/tab.h b/gui/widgets/tab.h
index fd92671948f..23d5200f156 100644
--- a/gui/widgets/tab.h
+++ b/gui/widgets/tab.h
@@ -52,6 +52,8 @@ protected:
 	int _minTabWidth;
 	int _titleSpacing;
 
+	ThemeEngine::TextAlignVertical _alignV;
+
 	int _bodyRP, _bodyTP, _bodyLP, _bodyBP;
 	ThemeEngine::DialogBackground _bodyBackgroundType;
 
@@ -66,8 +68,8 @@ protected:
 	void recalc();
 
 public:
-	TabWidget(GuiObject *boss, int x, int y, int w, int h);
-	TabWidget(GuiObject *boss, const Common::String &name);
+	TabWidget(GuiObject *boss, int x, int y, int w, int h, ThemeEngine::TextAlignVertical alignV = ThemeEngine::kTextAlignVTop);
+	TabWidget(GuiObject *boss, const Common::String &name, ThemeEngine::TextAlignVertical alignV = ThemeEngine::kTextAlignVTop);
 	~TabWidget() override;
 
 	void init();


Commit: 5653eccba46552154d1c4ea159ed6fe54252d0d7
    https://github.com/scummvm/scummvm/commit/5653eccba46552154d1c4ea159ed6fe54252d0d7
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Propagate tab vertical flip to the VectorRenderer

Changed paths:
    graphics/VectorRendererSpec.cpp
    graphics/VectorRendererSpec.h
    gui/ThemeEngine.cpp
    gui/helpdialog.cpp


diff --git a/graphics/VectorRendererSpec.cpp b/graphics/VectorRendererSpec.cpp
index a0fc9f32198..17cb6593931 100644
--- a/graphics/VectorRendererSpec.cpp
+++ b/graphics/VectorRendererSpec.cpp
@@ -1301,12 +1301,15 @@ drawTab(int x, int y, int r, int w, int h, int s) {
 		return;
 
 	bool useClippingVersions = !_clippingArea.contains(Common::Rect(x, y, x + w, y + h));
+	uint32 baseLeft = (Base::_dynamicData >> 16) & 0x7FFF;
+	uint32 baseRight = Base::_dynamicData & 0xFFFF;
+	bool vFlip = (Base::_dynamicData >> 31) != 0;
 
 	if (r == 0 && Base::_bevel > 0) {
 		if (useClippingVersions)
-			drawBevelTabAlgClip(x, y, w, h, Base::_bevel, _bevelColor, _fgColor, (Base::_dynamicData >> 16), (Base::_dynamicData & 0xFFFF));
+			drawBevelTabAlgClip(x, y, w, h, Base::_bevel, _bevelColor, _fgColor, baseLeft, baseRight, vFlip);
 		else
-			drawBevelTabAlg(x, y, w, h, Base::_bevel, _bevelColor, _fgColor, (Base::_dynamicData >> 16), (Base::_dynamicData & 0xFFFF));
+			drawBevelTabAlg(x, y, w, h, Base::_bevel, _bevelColor, _fgColor, baseLeft, baseRight, vFlip);
 		return;
 	}
 
@@ -1325,23 +1328,23 @@ drawTab(int x, int y, int r, int w, int h, int s) {
 		// See the rounded rect alg for how to fix it. (The border should
 		// be drawn before the interior, both inside drawTabAlg.)
 		if (useClippingVersions) {
-			drawTabShadowClip(x, y, w - 2, h, r, s, Base::_shadowIntensity);
-			drawTabAlgClip(x, y, w - 2, h, r, _bgColor, Base::_fillMode);
+			drawTabShadowClip(x, y, w - 2, h, r, s, Base::_shadowIntensity, vFlip);
+			drawTabAlgClip(x, y, w - 2, h, r, _bgColor, Base::_fillMode, 0, 0, vFlip);
 			if (Base::_strokeWidth)
-				drawTabAlgClip(x, y, w, h, r, _fgColor, kFillDisabled, (Base::_dynamicData >> 16), (Base::_dynamicData & 0xFFFF));
+				drawTabAlgClip(x, y, w, h, r, _fgColor, kFillDisabled, baseLeft, baseRight, vFlip);
 		} else {
-			drawTabShadow(x, y, w - 2, h, r, s, Base::_shadowIntensity);
-			drawTabAlg(x, y, w - 2, h, r, _bgColor, Base::_fillMode);
+			drawTabShadow(x, y, w - 2, h, r, s, Base::_shadowIntensity, vFlip);
+			drawTabAlg(x, y, w - 2, h, r, _bgColor, Base::_fillMode, 0, 0, vFlip);
 			if (Base::_strokeWidth)
-				drawTabAlg(x, y, w, h, r, _fgColor, kFillDisabled, (Base::_dynamicData >> 16), (Base::_dynamicData & 0xFFFF));
+				drawTabAlg(x, y, w, h, r, _fgColor, kFillDisabled, baseLeft, baseRight, vFlip);
 		}
 		break;
 
 	case kFillForeground:
 		if (useClippingVersions)
-			drawTabAlgClip(x, y, w, h, r, _fgColor, Base::_fillMode);
+			drawTabAlgClip(x, y, w, h, r, _fgColor, Base::_fillMode, 0, 0, vFlip);
 		else
-			drawTabAlg(x, y, w, h, r, _fgColor, Base::_fillMode);
+			drawTabAlg(x, y, w, h, r, _fgColor, Base::_fillMode, 0, 0, vFlip);
 		break;
 
 	default:
@@ -1418,7 +1421,7 @@ drawTriangle(int x, int y, int w, int h, TriangleOrientation orient) {
 /** TAB ALGORITHM - NON AA */
 template<typename PixelType>
 void VectorRendererSpec<PixelType>::
-drawTabAlg(int x1, int y1, int w, int h, int r, PixelType color, VectorRenderer::FillMode fill_m, int baseLeft, int baseRight) {
+drawTabAlg(int x1, int y1, int w, int h, int r, PixelType color, VectorRenderer::FillMode fill_m, int baseLeft, int baseRight, bool vFlip) {
 	// Don't draw anything for empty rects.
 	if (w <= 0 || h <= 0) {
 		return;
@@ -1520,7 +1523,7 @@ drawTabAlg(int x1, int y1, int w, int h, int r, PixelType color, VectorRenderer:
 
 template<typename PixelType>
 void VectorRendererSpec<PixelType>::
-drawTabAlgClip(int x1, int y1, int w, int h, int r, PixelType color, VectorRenderer::FillMode fill_m, int baseLeft, int baseRight) {
+drawTabAlgClip(int x1, int y1, int w, int h, int r, PixelType color, VectorRenderer::FillMode fill_m, int baseLeft, int baseRight, bool vFlip) {
 	// Don't draw anything for empty rects.
 	if (w <= 0 || h <= 0) {
 		return;
@@ -1635,7 +1638,7 @@ drawTabAlgClip(int x1, int y1, int w, int h, int r, PixelType color, VectorRende
 
 template<typename PixelType>
 void VectorRendererSpec<PixelType>::
-drawTabShadow(int x1, int y1, int w, int h, int r, int offset, uint32 shadowIntensity) {
+drawTabShadow(int x1, int y1, int w, int h, int r, int offset, uint32 shadowIntensity, bool vFlip) {
 	int pitch = _activeSurface->pitch / _activeSurface->format.bytesPerPixel;
 
 	// "Harder" shadows when having lower BPP, since we will have artifacts (greenish tint on the modern theme)
@@ -1646,7 +1649,7 @@ drawTabShadow(int x1, int y1, int w, int h, int r, int offset, uint32 shadowInte
 	int ystart = y1;
 	int width = w;
 	int height = h + offset + 1;
-	
+
 	// HACK: shadowIntensity is tailed with 16-bits mantissa. We also represent the
 	// offset as a 16.16 fixed point number here as termination condition to simplify
 	// looping logic. An additional `shadowIntensity` is added to to keep consistent
@@ -1702,7 +1705,7 @@ drawTabShadow(int x1, int y1, int w, int h, int r, int offset, uint32 shadowInte
 
 template<typename PixelType>
 void VectorRendererSpec<PixelType>::
-drawTabShadowClip(int x1, int y1, int w, int h, int r, int offset, uint32 shadowIntensity) {
+drawTabShadowClip(int x1, int y1, int w, int h, int r, int offset, uint32 shadowIntensity, bool vFlip) {
 	int pitch = _activeSurface->pitch / _activeSurface->format.bytesPerPixel;
 
 	// "Harder" shadows when having lower BPP, since we will have artifacts (greenish tint on the modern theme)
@@ -1775,7 +1778,7 @@ drawTabShadowClip(int x1, int y1, int w, int h, int r, int offset, uint32 shadow
 /** BEVELED TABS FOR CLASSIC THEME **/
 template<typename PixelType>
 void VectorRendererSpec<PixelType>::
-drawBevelTabAlg(int x, int y, int w, int h, int bevel, PixelType top_color, PixelType bottom_color, int baseLeft, int baseRight) {
+drawBevelTabAlg(int x, int y, int w, int h, int bevel, PixelType top_color, PixelType bottom_color, int baseLeft, int baseRight, bool vFlip) {
 	int pitch = _activeSurface->pitch / _activeSurface->format.bytesPerPixel;
 	int i, j;
 
@@ -1818,7 +1821,7 @@ drawBevelTabAlg(int x, int y, int w, int h, int bevel, PixelType top_color, Pixe
 
 template<typename PixelType>
 void VectorRendererSpec<PixelType>::
-drawBevelTabAlgClip(int x, int y, int w, int h, int bevel, PixelType top_color, PixelType bottom_color, int baseLeft, int baseRight) {
+drawBevelTabAlgClip(int x, int y, int w, int h, int bevel, PixelType top_color, PixelType bottom_color, int baseLeft, int baseRight, bool vFlip) {
 	int pitch = _activeSurface->pitch / _activeSurface->format.bytesPerPixel;
 	int i, j;
 
@@ -4067,7 +4070,7 @@ drawLineAlg(int x1, int y1, int x2, int y2, uint dx, uint dy, PixelType color) {
 /** TAB ALGORITHM */
 template<typename PixelType>
 void VectorRendererAA<PixelType>::
-drawTabAlg(int x1, int y1, int w, int h, int r, PixelType color, VectorRenderer::FillMode fill_m, int baseLeft, int baseRight) {
+drawTabAlg(int x1, int y1, int w, int h, int r, PixelType color, VectorRenderer::FillMode fill_m, int baseLeft, int baseRight, bool vFlip) {
 	// Don't draw anything for empty rects.
 	if (w <= 0 || h <= 0) {
 		return;
diff --git a/graphics/VectorRendererSpec.h b/graphics/VectorRendererSpec.h
index 675725bf376..50372706770 100644
--- a/graphics/VectorRendererSpec.h
+++ b/graphics/VectorRendererSpec.h
@@ -228,23 +228,23 @@ protected:
 
 	virtual void drawTabAlg(int x, int y, int w, int h, int r,
 	    PixelType color, VectorRenderer::FillMode fill_m,
-	    int baseLeft = 0, int baseRight = 0);
+	    int baseLeft, int baseRight, bool vFlip);
 
 	virtual void drawTabAlgClip(int x, int y, int w, int h, int r,
 		PixelType color, VectorRenderer::FillMode fill_m,
-		int baseLeft = 0, int baseRight = 0);
+		int baseLeft, int baseRight, bool vFlip);
 
-	virtual void drawTabShadow(int x, int y, int w, int h, int r, int offset, uint32 shadowIntensity);
+	virtual void drawTabShadow(int x, int y, int w, int h, int r, int offset, uint32 shadowIntensity, bool vFlip);
 
-	virtual void drawTabShadowClip(int x, int y, int w, int h, int r, int offset, uint32 shadowIntensity);
+	virtual void drawTabShadowClip(int x, int y, int w, int h, int r, int offset, uint32 shadowIntensity, bool vFlip);
 
 	virtual void drawBevelTabAlg(int x, int y, int w, int h,
 	    int bevel, PixelType topColor, PixelType bottomColor,
-	    int baseLeft = 0, int baseRight = 0);
+	    int baseLeft, int baseRight, bool vFlip);
 
 	virtual void drawBevelTabAlgClip(int x, int y, int w, int h,
 		int bevel, PixelType topColor, PixelType bottomColor,
-		int baseLeft = 0, int baseRight = 0);
+		int baseLeft, int baseRight, bool vFlip);
 
 	/**
 	 * SHADOW DRAWING ALGORITHMS
@@ -386,7 +386,7 @@ protected:
 
 	virtual void drawTabAlg(int x, int y, int w, int h, int r,
 	    PixelType color, VectorRenderer::FillMode fill_m,
-	    int baseLeft = 0, int baseRight = 0);
+	    int baseLeft, int baseRight, bool vFlip);
 };
 #endif
 /** @} */
diff --git a/gui/ThemeEngine.cpp b/gui/ThemeEngine.cpp
index 596e25a8786..eba8345a806 100644
--- a/gui/ThemeEngine.cpp
+++ b/gui/ThemeEngine.cpp
@@ -1304,10 +1304,12 @@ void ThemeEngine::drawTab(const Common::Rect &r, int tabHeight, const Common::Ar
 
 	int y1 = r.top;
 	int y2 = r.top + tabHeight;
+	uint32 vFlag = 0;
 
 	if (alignV == ThemeEngine::kTextAlignVBottom) {
 		y1 = r.bottom;
 		y2 = r.bottom + tabHeight;
+		vFlag = 1;
 	}
 
 	drawDD(kDDTabBackground, Common::Rect(r.left, y1, r.right, y2));
@@ -1339,7 +1341,7 @@ void ThemeEngine::drawTab(const Common::Rect &r, int tabHeight, const Common::Ar
 
 
 		Common::Rect tabRect(r.left + width, y1, r.left + width + tabWidths[current], y2);
-		drawDD(kDDTabInactive, tabRect);
+		drawDD(kDDTabInactive, tabRect, (vFlag << 31));
 		drawDDText(getTextData(kDDTabInactive), getTextColor(kDDTabInactive), tabRect, tabs[current], false, false,
 		           convertTextAlignH(_widgets[kDDTabInactive]->_textAlignH, rtl), _widgets[kDDTabInactive]->_textAlignV);
 		width += tabWidths[current];
@@ -1347,9 +1349,9 @@ void ThemeEngine::drawTab(const Common::Rect &r, int tabHeight, const Common::Ar
 
 	if (activePos >= 0) {
 		Common::Rect tabRect(r.left + activePos, y1, r.left + activePos + tabWidths[active], y2);
-		const uint16 tabLeft = activePos;
+		const uint16 tabLeft = activePos & 0x7FFF; // Keep only 15 bits
 		const uint16 tabRight = MAX(r.right - tabRect.right, 0);
-		drawDD(kDDTabActive, tabRect, (tabLeft << 16) | (tabRight & 0xFFFF));
+		drawDD(kDDTabActive, tabRect, (vFlag << 31) | (tabLeft << 16) | (tabRight & 0xFFFF));
 		drawDDText(getTextData(kDDTabActive), getTextColor(kDDTabActive), tabRect, tabs[active], false, false,
 		           convertTextAlignH(_widgets[kDDTabActive]->_textAlignH, rtl), _widgets[kDDTabActive]->_textAlignV);
 	}
diff --git a/gui/helpdialog.cpp b/gui/helpdialog.cpp
index 1cd8761c530..803e7456fc6 100644
--- a/gui/helpdialog.cpp
+++ b/gui/helpdialog.cpp
@@ -24,6 +24,7 @@
 #include "gui/gui-manager.h"
 #include "gui/ThemeEval.h"
 #include "gui/widget.h"
+#include "gui/widgets/tab.h"
 
 namespace GUI {
 
@@ -44,6 +45,20 @@ HelpDialog::HelpDialog()
 	_y = (screenH - _h) / 2;
 
 
+	TabWidget *tab = new TabWidget(this, 10, 10, _w - 10, _h - (buttonHeight + 10) * 5 / 2, ThemeEngine::kTextAlignVBottom);
+
+	tab->addTab(_("General"), "GlobalOptions_Graphics", false);
+	tab->addTab(_("Controls"), "GlobalOptions_Graphics", false);
+	tab->addTab(_("More Stuff"), "GlobalOptions_Graphics", false);
+	tab->addTab(_("Even More"), "GlobalOptions_Graphics", false);
+	tab->addTab(_("Bubba"), "GlobalOptions_Graphics", false);
+	tab->addTab(_("Rulez"), "GlobalOptions_Graphics", false);
+	tab->addTab(_("Abra"), "GlobalOptions_Graphics", false);
+	tab->addTab(_("Shwabra"), "GlobalOptions_Graphics", false);
+	tab->addTab(_("Kadabra"), "GlobalOptions_Graphics", false);
+	tab->addTab(_("Kaboom"), "GlobalOptions_Graphics", false);
+	tab->addTab(_("Boomka"), "GlobalOptions_Graphics", false);
+
 	new ButtonWidget(this, _w - buttonWidth - 10, _h - buttonHeight - 10, buttonWidth, buttonHeight, Common::U32String("Close"), Common::U32String(), kCloseCmd);
 }
 


Commit: f048d170e7503263beddcafb1b95a114a743ffd8
    https://github.com/scummvm/scummvm/commit/f048d170e7503263beddcafb1b95a114a743ffd8
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Added stubs for RichTextWidget

Changed paths:
  A gui/widgets/richtext.cpp
  A gui/widgets/richtext.h
    gui/module.mk
    gui/widget.h


diff --git a/gui/module.mk b/gui/module.mk
index 78758e0e3a2..09026f5f56e 100644
--- a/gui/module.mk
+++ b/gui/module.mk
@@ -43,6 +43,7 @@ MODULE_OBJS := \
 	widgets/groupedlist.o \
 	widgets/list.o \
 	widgets/popup.o \
+	widgets/richtext.o \
 	widgets/scrollbar.o \
 	widgets/scrollcontainer.o \
 	widgets/tab.o
diff --git a/gui/widget.h b/gui/widget.h
index e85e4f32e56..baa9891be79 100644
--- a/gui/widget.h
+++ b/gui/widget.h
@@ -73,7 +73,8 @@ enum {
 	kTabWidget			= 'TABW',
 	kGraphicsWidget		= 'GFXW',
 	kContainerWidget	= 'CTNR',
-	kScrollContainerWidget = 'SCTR'
+	kScrollContainerWidget = 'SCTR',
+	kRichTextWidget		= 'RTXT',
 };
 
 enum {
diff --git a/gui/widgets/richtext.cpp b/gui/widgets/richtext.cpp
new file mode 100644
index 00000000000..3b877278a14
--- /dev/null
+++ b/gui/widgets/richtext.cpp
@@ -0,0 +1,173 @@
+/* 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 "common/system.h"
+#include "common/unicode-bidi.h"
+#include "gui/widgets/richtext.h"
+#include "gui/gui-manager.h"
+
+#include "gui/ThemeEval.h"
+
+namespace GUI {
+
+RichTextWidget::RichTextWidget(GuiObject *boss, int x, int y, int w, int h, bool scale, const Common::U32String &text, const Common::U32String &tooltip, uint32 cmd, uint32 finishCmd, ThemeEngine::FontStyle font)
+	: EditableWidget(boss, x, y - 1, w, h + 2, scale, tooltip, cmd) {
+	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_RETAIN_FOCUS | WIDGET_WANT_TICKLE);
+	_type = kRichTextWidget;
+	_finishCmd = finishCmd;
+
+	_leftPadding = _rightPadding = 0;
+
+	setEditString(text);
+	setFontStyle(font);
+}
+
+RichTextWidget::RichTextWidget(GuiObject *boss, int x, int y, int w, int h, const Common::U32String &text, const Common::U32String &tooltip, uint32 cmd, uint32 finishCmd, ThemeEngine::FontStyle font)
+	: RichTextWidget(boss, x, y, w, h, false, text, tooltip, cmd, finishCmd, font) {
+}
+
+RichTextWidget::RichTextWidget(GuiObject *boss, const Common::String &name, const Common::U32String &text, const Common::U32String &tooltip, uint32 cmd, uint32 finishCmd, ThemeEngine::FontStyle font)
+	: EditableWidget(boss, name, tooltip, cmd) {
+	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_RETAIN_FOCUS | WIDGET_WANT_TICKLE);
+	_type = kRichTextWidget;
+	_finishCmd = finishCmd;
+
+	_leftPadding = _rightPadding = 0;
+	_shiftPressed = _isDragging = false;
+
+	setEditString(text);
+	setFontStyle(font);
+}
+
+void RichTextWidget::setEditString(const Common::U32String &str) {
+	EditableWidget::setEditString(str);
+	_backupString = str;
+}
+
+void RichTextWidget::reflowLayout() {
+	_leftPadding = g_gui.xmlEval()->getVar("Globals.EditTextWidget.Padding.Left", 0);
+	_rightPadding = g_gui.xmlEval()->getVar("Globals.EditTextWidget.Padding.Right", 0);
+
+	EditableWidget::reflowLayout();
+}
+
+void RichTextWidget::drawWidget() {
+	g_gui.theme()->drawWidgetBackground(Common::Rect(_x, _y, _x + _w, _y + _h),
+	                                    ThemeEngine::kWidgetBackgroundEditText);
+
+	// Draw the text
+	adjustOffset();
+	Common::Rect drawRect = getEditRect();
+	drawRect.translate(_x, _y);
+	setTextDrawableArea(drawRect);
+
+	int x = -_editScrollOffset;
+	int y = drawRect.top;
+
+	int selBegin = _selCaretPos;
+	int selEnd = _selOffset + _selCaretPos;
+	if (selBegin > selEnd)
+		SWAP(selBegin, selEnd);
+	selBegin = MAX(selBegin, 0);
+	selEnd = MAX(selEnd, 0);
+	
+	if (!g_gui.useRTL()) {
+		Common::UnicodeBiDiText utxt(_editString);
+		Common::U32String left = Common::U32String(utxt.visual.c_str(), utxt.visual.c_str() + selBegin);
+		Common::U32String selected = Common::U32String(utxt.visual.c_str() + selBegin, selEnd - selBegin);
+		Common::U32String right = Common::U32String(utxt.visual.c_str() + selEnd);
+		Common::U32StringArray parts {left, selected, right};
+		int scrollOffset = _editScrollOffset;
+		for (uint i = 0; i < parts.size(); i++) {
+			if (!parts[i].size())
+				continue;
+			Common::U32String part = parts[i];
+			int partW = g_gui.getStringWidth(part, _font);
+			int clipL = drawRect.left + (scrollOffset < 0 ? -scrollOffset : 0);
+			int clipR = MIN(clipL + partW, (int)drawRect.right);
+			if (x + partW > 0 && x < _w && clipL < drawRect.right) {
+				int sO = scrollOffset < 0 ? 0 : -scrollOffset;
+				_inversion = i == 1 ? ThemeEngine::kTextInversionFocus : ThemeEngine::kTextInversionNone;
+				g_gui.theme()->drawText(Common::Rect(clipL, y, clipR, y + drawRect.height()), part, _state,
+				                        _drawAlign, _inversion, sO, false, _font, ThemeEngine::kFontColorNormal, 
+				                        true, _textDrawableArea);
+			}
+			x += partW;
+			scrollOffset -= partW;
+		}
+	} else {
+		// The above method does not render RTL languages correctly, so fallback to default method
+		// There are only two possible cases, either the whole string has been selected
+		// or nothing has been selected.
+		_inversion = _selOffset ? ThemeEngine::kTextInversionFocus : ThemeEngine::kTextInversionNone;
+		g_gui.theme()->drawText(drawRect, _editString, _state, _drawAlign, _inversion, 
+		                        -_editScrollOffset, false, _font, ThemeEngine::kFontColorNormal, true, 
+		                        _textDrawableArea);
+	}
+}
+
+Common::Rect RichTextWidget::getEditRect() const {
+	// Calculate (right - left) difference for editRect's X-axis coordinates:
+	// (_w - 1 - _rightPadding) - (2 + _leftPadding)
+	int editWidth = _w - _rightPadding - _leftPadding - 3;
+	int editHeight = _h - 2;
+	// Ensure r will always be a valid rect
+	if (editWidth < 0) {
+		editWidth = 0;
+	}
+	if (editHeight < 0) {
+		editHeight = 0;
+	}
+	Common::Rect r(2 + _leftPadding, 1, 2 + _leftPadding + editWidth, 1 + editHeight);
+
+	return r;
+}
+
+void RichTextWidget::receivedFocusWidget() {
+	g_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, true);
+}
+
+void RichTextWidget::lostFocusWidget() {
+	// If we lose focus, 'commit' the user changes and clear selection
+	_backupString = _editString;
+	drawCaret(true);
+	clearSelection();
+
+	g_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, false);
+}
+
+void RichTextWidget::startEditMode() {
+}
+
+void RichTextWidget::endEditMode() {
+	releaseFocus();
+
+	sendCommand(_finishCmd, 0);
+}
+
+void RichTextWidget::abortEditMode() {
+	setEditString(_backupString);
+	sendCommand(_cmd, 0);
+
+	releaseFocus();
+}
+
+} // End of namespace GUI
diff --git a/gui/widgets/richtext.h b/gui/widgets/richtext.h
new file mode 100644
index 00000000000..d6dc151ffd0
--- /dev/null
+++ b/gui/widgets/richtext.h
@@ -0,0 +1,66 @@
+/* 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 GUI_WIDGETS_RICHTEXT_H
+#define GUI_WIDGETS_RICHTEXT_H
+
+#include "gui/widgets/editable.h"
+#include "common/str.h"
+#include "gui/dialog.h"
+
+namespace GUI {
+
+/* RichTextWidget */
+class RichTextWidget : public EditableWidget {
+protected:
+	Common::U32String _backupString;
+
+	int				_leftPadding;
+	int				_rightPadding;
+
+public:
+	RichTextWidget(GuiObject *boss, int x, int y, int w, int h, bool scale, const Common::U32String &text, const Common::U32String &tooltip = Common::U32String(), uint32 cmd = 0, uint32 finishCmd = 0, ThemeEngine::FontStyle font = ThemeEngine::kFontStyleNormal);
+	RichTextWidget(GuiObject *boss, int x, int y, int w, int h, const Common::U32String &text, const Common::U32String &tooltip = Common::U32String(), uint32 cmd = 0, uint32 finishCmd = 0, ThemeEngine::FontStyle font = ThemeEngine::kFontStyleNormal);
+	RichTextWidget(GuiObject *boss, const Common::String &name, const Common::U32String &text, const Common::U32String &tooltip = Common::U32String(), uint32 cmd = 0, uint32 finishCmd = 0, ThemeEngine::FontStyle font = ThemeEngine::kFontStyleNormal);
+
+	void setEditString(const Common::U32String &str) override;
+
+	bool wantsFocus() override { return true; }
+
+	void reflowLayout() override;
+
+protected:
+	void drawWidget() override;
+	void receivedFocusWidget() override;
+	void lostFocusWidget() override;
+
+	void startEditMode() override;
+	void endEditMode() override;
+	void abortEditMode() override;
+
+	Common::Rect getEditRect() const override;
+
+	uint32 _finishCmd;
+};
+
+} // End of namespace GUI
+
+#endif


Commit: 57568e8fdff3dd15fd6755afd782ee5822a4b0d4
    https://github.com/scummvm/scummvm/commit/57568e8fdff3dd15fd6755afd782ee5822a4b0d4
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Further work on RichText widget

Changed paths:
    gui/widgets/richtext.cpp
    gui/widgets/richtext.h


diff --git a/gui/widgets/richtext.cpp b/gui/widgets/richtext.cpp
index 3b877278a14..3aaefde8fda 100644
--- a/gui/widgets/richtext.cpp
+++ b/gui/widgets/richtext.cpp
@@ -21,6 +21,9 @@
 
 #include "common/system.h"
 #include "common/unicode-bidi.h"
+
+#include "graphics/macgui/mactextwindow.h"
+
 #include "gui/widgets/richtext.h"
 #include "gui/gui-manager.h"
 
@@ -28,146 +31,68 @@
 
 namespace GUI {
 
-RichTextWidget::RichTextWidget(GuiObject *boss, int x, int y, int w, int h, bool scale, const Common::U32String &text, const Common::U32String &tooltip, uint32 cmd, uint32 finishCmd, ThemeEngine::FontStyle font)
-	: EditableWidget(boss, x, y - 1, w, h + 2, scale, tooltip, cmd) {
-	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_RETAIN_FOCUS | WIDGET_WANT_TICKLE);
-	_type = kRichTextWidget;
-	_finishCmd = finishCmd;
+Graphics::MacWindowManager *_wm = nullptr;
 
-	_leftPadding = _rightPadding = 0;
+void ensureWM() {
+	if (_wm)
+		return;
 
-	setEditString(text);
-	setFontStyle(font);
-}
+	uint32 wmMode = Graphics::kWMModeNoDesktop | Graphics::kWMModeForceBuiltinFonts
+		| Graphics::kWMModeUnicode | Graphics::kWMModeWin95;
 
-RichTextWidget::RichTextWidget(GuiObject *boss, int x, int y, int w, int h, const Common::U32String &text, const Common::U32String &tooltip, uint32 cmd, uint32 finishCmd, ThemeEngine::FontStyle font)
-	: RichTextWidget(boss, x, y, w, h, false, text, tooltip, cmd, finishCmd, font) {
+	_wm = new Graphics::MacWindowManager(wmMode);
 }
 
-RichTextWidget::RichTextWidget(GuiObject *boss, const Common::String &name, const Common::U32String &text, const Common::U32String &tooltip, uint32 cmd, uint32 finishCmd, ThemeEngine::FontStyle font)
-	: EditableWidget(boss, name, tooltip, cmd) {
+RichTextWidget::RichTextWidget(GuiObject *boss, int x, int y, int w, int h, bool scale, const Common::U32String &text, const Common::U32String &tooltip)
+	: Widget(boss, x, y, w, h, scale, tooltip)  {
 	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_RETAIN_FOCUS | WIDGET_WANT_TICKLE);
 	_type = kRichTextWidget;
-	_finishCmd = finishCmd;
 
-	_leftPadding = _rightPadding = 0;
-	_shiftPressed = _isDragging = false;
+	_text = text;
 
-	setEditString(text);
-	setFontStyle(font);
+	ensureWM();
 }
 
-void RichTextWidget::setEditString(const Common::U32String &str) {
-	EditableWidget::setEditString(str);
-	_backupString = str;
+RichTextWidget::RichTextWidget(GuiObject *boss, int x, int y, int w, int h, const Common::U32String &text, const Common::U32String &tooltip)
+	: RichTextWidget(boss, x, y, w, h, false, text, tooltip) {
 }
 
-void RichTextWidget::reflowLayout() {
-	_leftPadding = g_gui.xmlEval()->getVar("Globals.EditTextWidget.Padding.Left", 0);
-	_rightPadding = g_gui.xmlEval()->getVar("Globals.EditTextWidget.Padding.Right", 0);
-
-	EditableWidget::reflowLayout();
-}
-
-void RichTextWidget::drawWidget() {
-	g_gui.theme()->drawWidgetBackground(Common::Rect(_x, _y, _x + _w, _y + _h),
-	                                    ThemeEngine::kWidgetBackgroundEditText);
-
-	// Draw the text
-	adjustOffset();
-	Common::Rect drawRect = getEditRect();
-	drawRect.translate(_x, _y);
-	setTextDrawableArea(drawRect);
-
-	int x = -_editScrollOffset;
-	int y = drawRect.top;
-
-	int selBegin = _selCaretPos;
-	int selEnd = _selOffset + _selCaretPos;
-	if (selBegin > selEnd)
-		SWAP(selBegin, selEnd);
-	selBegin = MAX(selBegin, 0);
-	selEnd = MAX(selEnd, 0);
-	
-	if (!g_gui.useRTL()) {
-		Common::UnicodeBiDiText utxt(_editString);
-		Common::U32String left = Common::U32String(utxt.visual.c_str(), utxt.visual.c_str() + selBegin);
-		Common::U32String selected = Common::U32String(utxt.visual.c_str() + selBegin, selEnd - selBegin);
-		Common::U32String right = Common::U32String(utxt.visual.c_str() + selEnd);
-		Common::U32StringArray parts {left, selected, right};
-		int scrollOffset = _editScrollOffset;
-		for (uint i = 0; i < parts.size(); i++) {
-			if (!parts[i].size())
-				continue;
-			Common::U32String part = parts[i];
-			int partW = g_gui.getStringWidth(part, _font);
-			int clipL = drawRect.left + (scrollOffset < 0 ? -scrollOffset : 0);
-			int clipR = MIN(clipL + partW, (int)drawRect.right);
-			if (x + partW > 0 && x < _w && clipL < drawRect.right) {
-				int sO = scrollOffset < 0 ? 0 : -scrollOffset;
-				_inversion = i == 1 ? ThemeEngine::kTextInversionFocus : ThemeEngine::kTextInversionNone;
-				g_gui.theme()->drawText(Common::Rect(clipL, y, clipR, y + drawRect.height()), part, _state,
-				                        _drawAlign, _inversion, sO, false, _font, ThemeEngine::kFontColorNormal, 
-				                        true, _textDrawableArea);
-			}
-			x += partW;
-			scrollOffset -= partW;
-		}
-	} else {
-		// The above method does not render RTL languages correctly, so fallback to default method
-		// There are only two possible cases, either the whole string has been selected
-		// or nothing has been selected.
-		_inversion = _selOffset ? ThemeEngine::kTextInversionFocus : ThemeEngine::kTextInversionNone;
-		g_gui.theme()->drawText(drawRect, _editString, _state, _drawAlign, _inversion, 
-		                        -_editScrollOffset, false, _font, ThemeEngine::kFontColorNormal, true, 
-		                        _textDrawableArea);
-	}
-}
-
-Common::Rect RichTextWidget::getEditRect() const {
-	// Calculate (right - left) difference for editRect's X-axis coordinates:
-	// (_w - 1 - _rightPadding) - (2 + _leftPadding)
-	int editWidth = _w - _rightPadding - _leftPadding - 3;
-	int editHeight = _h - 2;
-	// Ensure r will always be a valid rect
-	if (editWidth < 0) {
-		editWidth = 0;
-	}
-	if (editHeight < 0) {
-		editHeight = 0;
-	}
-	Common::Rect r(2 + _leftPadding, 1, 2 + _leftPadding + editWidth, 1 + editHeight);
-
-	return r;
-}
+RichTextWidget::RichTextWidget(GuiObject *boss, const Common::String &name, const Common::U32String &text, const Common::U32String &tooltip)
+	: Widget(boss, name, tooltip) {
+	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_RETAIN_FOCUS | WIDGET_WANT_TICKLE);
+	_type = kRichTextWidget;
+	_text = text;
 
-void RichTextWidget::receivedFocusWidget() {
-	g_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, true);
+	ensureWM();
 }
 
-void RichTextWidget::lostFocusWidget() {
-	// If we lose focus, 'commit' the user changes and clear selection
-	_backupString = _editString;
-	drawCaret(true);
-	clearSelection();
-
-	g_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, false);
+void RichTextWidget::createWidget() {
+	_txtWnd = _wm->addTextWindow(FontMan.getFontByUsage(Graphics::FontManager::kGUIFont),
+				0, 0xffffffff, _w, Graphics::kTextAlignLeft, nullptr, false);
+	_txtWnd->setTextColorRGB(0xffffffff);
+	_txtWnd->setBorderType(Graphics::kWindowBorderMacOSNoBorderScrollbar);
+	_txtWnd->enableScrollbar(true);
+	// it will hide the scrollbar when the text height is smaller than the window height
+	_txtWnd->setMode(Graphics::kWindowModeDynamicScrollbar);
+	_txtWnd->resize(_w, _h);
+	_txtWnd->setEditable(false);
+	_txtWnd->setSelectable(false);
+
+	_txtWnd->appendText(_text);
 }
 
-void RichTextWidget::startEditMode() {
+void RichTextWidget::reflowLayout() {
+	Widget::reflowLayout();
 }
 
-void RichTextWidget::endEditMode() {
-	releaseFocus();
-
-	sendCommand(_finishCmd, 0);
-}
+void RichTextWidget::drawWidget() {
+	if (!_txtWnd)
+		createWidget();
 
-void RichTextWidget::abortEditMode() {
-	setEditString(_backupString);
-	sendCommand(_cmd, 0);
+	g_gui.theme()->drawWidgetBackground(Common::Rect(_x, _y, _x + _w, _y + _h),
+	                                    ThemeEngine::kWidgetBackgroundEditText);
 
-	releaseFocus();
+	_wm->draw();
 }
 
 } // End of namespace GUI
diff --git a/gui/widgets/richtext.h b/gui/widgets/richtext.h
index d6dc151ffd0..4da2f770388 100644
--- a/gui/widgets/richtext.h
+++ b/gui/widgets/richtext.h
@@ -26,39 +26,30 @@
 #include "common/str.h"
 #include "gui/dialog.h"
 
+namespace Graphics {
+class MacTextWindow;
+}
+
 namespace GUI {
 
 /* RichTextWidget */
-class RichTextWidget : public EditableWidget {
+class RichTextWidget : public Widget {
 protected:
 	Common::U32String _backupString;
 
-	int				_leftPadding;
-	int				_rightPadding;
+	Graphics::MacTextWindow *_txtWnd = nullptr;
+	Common::U32String _text;
 
 public:
-	RichTextWidget(GuiObject *boss, int x, int y, int w, int h, bool scale, const Common::U32String &text, const Common::U32String &tooltip = Common::U32String(), uint32 cmd = 0, uint32 finishCmd = 0, ThemeEngine::FontStyle font = ThemeEngine::kFontStyleNormal);
-	RichTextWidget(GuiObject *boss, int x, int y, int w, int h, const Common::U32String &text, const Common::U32String &tooltip = Common::U32String(), uint32 cmd = 0, uint32 finishCmd = 0, ThemeEngine::FontStyle font = ThemeEngine::kFontStyleNormal);
-	RichTextWidget(GuiObject *boss, const Common::String &name, const Common::U32String &text, const Common::U32String &tooltip = Common::U32String(), uint32 cmd = 0, uint32 finishCmd = 0, ThemeEngine::FontStyle font = ThemeEngine::kFontStyleNormal);
-
-	void setEditString(const Common::U32String &str) override;
-
-	bool wantsFocus() override { return true; }
+	RichTextWidget(GuiObject *boss, int x, int y, int w, int h, bool scale, const Common::U32String &text, const Common::U32String &tooltip = Common::U32String());
+	RichTextWidget(GuiObject *boss, int x, int y, int w, int h, const Common::U32String &text, const Common::U32String &tooltip = Common::U32String());
+	RichTextWidget(GuiObject *boss, const Common::String &name, const Common::U32String &text, const Common::U32String &tooltip = Common::U32String());
 
 	void reflowLayout() override;
 
 protected:
 	void drawWidget() override;
-	void receivedFocusWidget() override;
-	void lostFocusWidget() override;
-
-	void startEditMode() override;
-	void endEditMode() override;
-	void abortEditMode() override;
-
-	Common::Rect getEditRect() const override;
-
-	uint32 _finishCmd;
+	void createWidget();
 };
 
 } // End of namespace GUI


Commit: 76dbb66489f0cef259649c3381532e52788f1901
    https://github.com/scummvm/scummvm/commit/76dbb66489f0cef259649c3381532e52788f1901
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Added RichText to HelpDialog

Changed paths:
    gui/helpdialog.cpp
    gui/widgets/richtext.cpp
    gui/widgets/richtext.h


diff --git a/gui/helpdialog.cpp b/gui/helpdialog.cpp
index 803e7456fc6..dfef97d109f 100644
--- a/gui/helpdialog.cpp
+++ b/gui/helpdialog.cpp
@@ -24,6 +24,7 @@
 #include "gui/gui-manager.h"
 #include "gui/ThemeEval.h"
 #include "gui/widget.h"
+#include "gui/widgets/richtext.h"
 #include "gui/widgets/tab.h"
 
 namespace GUI {
@@ -45,7 +46,9 @@ HelpDialog::HelpDialog()
 	_y = (screenH - _h) / 2;
 
 
-	TabWidget *tab = new TabWidget(this, 10, 10, _w - 10, _h - (buttonHeight + 10) * 5 / 2, ThemeEngine::kTextAlignVBottom);
+	int tabHeight = _h - (buttonHeight + 10) * 5 / 2;
+
+	TabWidget *tab = new TabWidget(this, 10, 10, _w - 10, tabHeight, ThemeEngine::kTextAlignVBottom);
 
 	tab->addTab(_("General"), "GlobalOptions_Graphics", false);
 	tab->addTab(_("Controls"), "GlobalOptions_Graphics", false);
@@ -59,6 +62,9 @@ HelpDialog::HelpDialog()
 	tab->addTab(_("Kaboom"), "GlobalOptions_Graphics", false);
 	tab->addTab(_("Boomka"), "GlobalOptions_Graphics", false);
 
+	new RichTextWidget(tab, 10, 10, _w - 40, tabHeight - buttonHeight, Common::U32String("Hello world"));
+
+
 	new ButtonWidget(this, _w - buttonWidth - 10, _h - buttonHeight - 10, buttonWidth, buttonHeight, Common::U32String("Close"), Common::U32String(), kCloseCmd);
 }
 
diff --git a/gui/widgets/richtext.cpp b/gui/widgets/richtext.cpp
index 3aaefde8fda..2b7cb711a3b 100644
--- a/gui/widgets/richtext.cpp
+++ b/gui/widgets/richtext.cpp
@@ -38,7 +38,7 @@ void ensureWM() {
 		return;
 
 	uint32 wmMode = Graphics::kWMModeNoDesktop | Graphics::kWMModeForceBuiltinFonts
-		| Graphics::kWMModeUnicode | Graphics::kWMModeWin95;
+		| Graphics::kWMModeUnicode | Graphics::kWMMode32bpp;
 
 	_wm = new Graphics::MacWindowManager(wmMode);
 }
@@ -79,6 +79,10 @@ void RichTextWidget::createWidget() {
 	_txtWnd->setSelectable(false);
 
 	_txtWnd->appendText(_text);
+
+	_surface = new Graphics::ManagedSurface(_w, _h, _wm->_pixelformat);
+
+	warning("Pixel format: %s", _wm->_pixelformat.toString().c_str());
 }
 
 void RichTextWidget::reflowLayout() {
@@ -92,7 +96,11 @@ void RichTextWidget::drawWidget() {
 	g_gui.theme()->drawWidgetBackground(Common::Rect(_x, _y, _x + _w, _y + _h),
 	                                    ThemeEngine::kWidgetBackgroundEditText);
 
-	_wm->draw();
+	_txtWnd->draw(_surface);
+
+	g_gui.theme()->drawManagedSurface(Common::Point(_x, _y), *_surface);
+
+	warning("DRAW");
 }
 
 } // End of namespace GUI
diff --git a/gui/widgets/richtext.h b/gui/widgets/richtext.h
index 4da2f770388..1728cb7841a 100644
--- a/gui/widgets/richtext.h
+++ b/gui/widgets/richtext.h
@@ -22,12 +22,12 @@
 #ifndef GUI_WIDGETS_RICHTEXT_H
 #define GUI_WIDGETS_RICHTEXT_H
 
-#include "gui/widgets/editable.h"
 #include "common/str.h"
-#include "gui/dialog.h"
+#include "gui/widget.h"
 
 namespace Graphics {
 class MacTextWindow;
+class ManagedSurface;
 }
 
 namespace GUI {
@@ -35,9 +35,8 @@ namespace GUI {
 /* RichTextWidget */
 class RichTextWidget : public Widget {
 protected:
-	Common::U32String _backupString;
-
 	Graphics::MacTextWindow *_txtWnd = nullptr;
+	Graphics::ManagedSurface *_surface = nullptr;
 	Common::U32String _text;
 
 public:


Commit: db62679c74478dbd4a9e0c551d8fcc81ced523f4
    https://github.com/scummvm/scummvm/commit/db62679c74478dbd4a9e0c551d8fcc81ced523f4
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Use proper font in RichTextWidget

Changed paths:
    gui/widgets/richtext.cpp


diff --git a/gui/widgets/richtext.cpp b/gui/widgets/richtext.cpp
index 2b7cb711a3b..544df42f101 100644
--- a/gui/widgets/richtext.cpp
+++ b/gui/widgets/richtext.cpp
@@ -27,6 +27,7 @@
 #include "gui/widgets/richtext.h"
 #include "gui/gui-manager.h"
 
+#include "gui/ThemeEngine.h"
 #include "gui/ThemeEval.h"
 
 namespace GUI {
@@ -67,9 +68,12 @@ RichTextWidget::RichTextWidget(GuiObject *boss, const Common::String &name, cons
 }
 
 void RichTextWidget::createWidget() {
-	_txtWnd = _wm->addTextWindow(FontMan.getFontByUsage(Graphics::FontManager::kGUIFont),
-				0, 0xffffffff, _w, Graphics::kTextAlignLeft, nullptr, false);
-	_txtWnd->setTextColorRGB(0xffffffff);
+	uint32 white = _wm->_pixelformat.RGBToColor(0xff, 0xff, 0xff);
+	uint32 black = _wm->_pixelformat.RGBToColor(0x00, 0x00, 0x00);
+
+	_txtWnd = _wm->addTextWindow(g_gui.theme()->getFont(),
+			black, white, _w, Graphics::kTextAlignLeft, nullptr, false);
+	_txtWnd->setTextColorRGB(black);
 	_txtWnd->setBorderType(Graphics::kWindowBorderMacOSNoBorderScrollbar);
 	_txtWnd->enableScrollbar(true);
 	// it will hide the scrollbar when the text height is smaller than the window height
@@ -81,8 +85,6 @@ void RichTextWidget::createWidget() {
 	_txtWnd->appendText(_text);
 
 	_surface = new Graphics::ManagedSurface(_w, _h, _wm->_pixelformat);
-
-	warning("Pixel format: %s", _wm->_pixelformat.toString().c_str());
 }
 
 void RichTextWidget::reflowLayout() {


Commit: 3c6284949c94247856f42fff8d5793845748a985
    https://github.com/scummvm/scummvm/commit/3c6284949c94247856f42fff8d5793845748a985
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
COMMON: Added initial code for Markdown parsing

Taken from https://github.com/vmg/sundown

Changed paths:
  A common/formats/markdown.cpp
  A common/formats/markdown.h
    common/formats/module.mk


diff --git a/common/formats/markdown.cpp b/common/formats/markdown.cpp
new file mode 100644
index 00000000000..c7f6e0f0fa4
--- /dev/null
+++ b/common/formats/markdown.cpp
@@ -0,0 +1,3095 @@
+/* markdown.c - generic markdown parser */
+
+/*
+ * Copyright (c) 2009, Natacha Porté
+ * Copyright (c) 2011, Vicent Marti
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "common/formats/markdown.h"
+#include "common/str.h"
+#include "common/util.h"
+
+namespace Common {
+
+#define REF_TABLE_SIZE 8
+
+#define BUFFER_BLOCK 0
+#define BUFFER_SPAN 1
+
+#define MKD_LI_END 8    /* internal list flag */
+
+#define gperf_case_strncmp(s1, s2, n) scumm_strnicmp(s1, s2, n)
+#define GPERF_DOWNCASE 1
+#define GPERF_CASE_STRNCMP 1
+
+/***************
+ * LOCAL TYPES *
+ ***************/
+
+/* link_ref: reference to a link */
+struct LinkRef {
+	uint id;
+
+	DataBuffer *link;
+	DataBuffer *title;
+
+	LinkRef *next;
+};
+
+/* render • structure containing one particular render */
+struct SDMarkdown {
+	SDCallbacks cb;
+	void *opaque;
+
+	LinkRef *refs[REF_TABLE_SIZE];
+	byte active_char[256];
+	SDStack work_bufs[2];
+	uint ext_flags;
+	size_t max_nesting;
+	int in_link_body;
+};
+
+/* char_trigger: function pointer to render active chars */
+/*   returns the number of chars taken care of */
+/*   data is the pointer of the beginning of the span */
+/*   offset is the number of valid chars before data */
+typedef size_t (*char_trigger)(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size);
+
+static size_t char_emphasis(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size);
+static size_t char_linebreak(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size);
+static size_t char_codespan(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size);
+static size_t char_escape(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size);
+static size_t char_entity(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size);
+static size_t char_langle_tag(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size);
+static size_t char_autolink_url(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size);
+static size_t char_autolink_email(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size);
+static size_t char_autolink_www(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size);
+static size_t char_link(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size);
+static size_t char_superscript(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size);
+
+enum markdown_char_t {
+	MD_CHAR_NONE = 0,
+	MD_CHAR_EMPHASIS,
+	MD_CHAR_CODESPAN,
+	MD_CHAR_LINEBREAK,
+	MD_CHAR_LINK,
+	MD_CHAR_LANGLE,
+	MD_CHAR_ESCAPE,
+	MD_CHAR_ENTITITY,
+	MD_CHAR_AUTOLINK_URL,
+	MD_CHAR_AUTOLINK_EMAIL,
+	MD_CHAR_AUTOLINK_WWW,
+	MD_CHAR_SUPERSCRIPT,
+};
+
+static char_trigger markdown_char_ptrs[] = {
+	NULL,
+	&char_emphasis,
+	&char_codespan,
+	&char_linebreak,
+	&char_link,
+	&char_langle_tag,
+	&char_escape,
+	&char_entity,
+	&char_autolink_url,
+	&char_autolink_email,
+	&char_autolink_www,
+	&char_superscript,
+};
+
+/***************************
+ * HELPER FUNCTIONS *
+ ***************************/
+
+static inline DataBuffer *rndr_newbuf(SDMarkdown *rndr, int type) {
+	static const size_t buf_size[2] = {256, 64};
+	DataBuffer *work = NULL;
+	SDStack *pool = &rndr->work_bufs[type];
+
+	if (pool->size < pool->asize &&
+	        pool->item[pool->size] != NULL) {
+		work = (DataBuffer *)pool->item[pool->size++];
+		work->size = 0;
+	} else {
+		work = bufnew(buf_size[type]);
+		stack_push(pool, work);
+	}
+
+	return work;
+}
+
+static inline void rndr_popbuf(SDMarkdown *rndr, int type) {
+	rndr->work_bufs[type].size--;
+}
+
+static void unscape_text(DataBuffer *ob, DataBuffer *src) {
+	size_t i = 0, org;
+	while (i < src->size) {
+		org = i;
+		while (i < src->size && src->data[i] != '\\')
+			i++;
+
+		if (i > org)
+			bufput(ob, src->data + org, i - org);
+
+		if (i + 1 >= src->size)
+			break;
+
+		bufputc(ob, src->data[i + 1]);
+		i += 2;
+	}
+}
+
+static uint hash_link_ref(const byte *link_ref, size_t length) {
+	size_t i;
+	uint hash = 0;
+
+	for (i = 0; i < length; ++i)
+		hash = tolower(link_ref[i]) + (hash << 6) + (hash << 16) - hash;
+
+	return hash;
+}
+
+static LinkRef *add_link_ref(
+    LinkRef **references,
+    const byte *name, size_t name_size) {
+	LinkRef *ref = (LinkRef *)calloc(1, sizeof(LinkRef));
+
+	if (!ref)
+		return NULL;
+
+	ref->id = hash_link_ref(name, name_size);
+	ref->next = references[ref->id % REF_TABLE_SIZE];
+
+	references[ref->id % REF_TABLE_SIZE] = ref;
+	return ref;
+}
+
+static LinkRef *find_link_ref(LinkRef **references, byte *name, size_t length) {
+	uint hash = hash_link_ref(name, length);
+	LinkRef *ref = NULL;
+
+	ref = references[hash % REF_TABLE_SIZE];
+
+	while (ref != NULL) {
+		if (ref->id == hash)
+			return ref;
+
+		ref = ref->next;
+	}
+
+	return NULL;
+}
+
+static void free_link_refs(LinkRef **references) {
+	size_t i;
+
+	for (i = 0; i < REF_TABLE_SIZE; ++i) {
+		LinkRef *r = references[i];
+		LinkRef *next;
+
+		while (r) {
+			next = r->next;
+			bufrelease(r->link);
+			bufrelease(r->title);
+			free(r);
+			r = next;
+		}
+	}
+}
+
+/*
+ * Check whether a char is a Markdown space.
+
+ * Right now we only consider spaces the actual
+ * space and a newline: tabs and carriage returns
+ * are filtered out during the preprocessing phase.
+ *
+ * If we wanted to actually be UTF-8 compliant, we
+ * should instead extract an Unicode codepoint from
+ * this character and check for space properties.
+ */
+static inline int _isspace(int c) {
+	return c == ' ' || c == '\n';
+}
+
+/****************************
+ * INLINE PARSING FUNCTIONS *
+ ****************************/
+
+/* is_mail_autolink • looks for the address part of a mail autolink and '>' */
+/* this is less strict than the original markdown e-mail address matching */
+static size_t is_mail_autolink(byte *data, size_t size) {
+	size_t i = 0, nb = 0;
+
+	/* address is assumed to be: [- at ._a-zA-Z0-9]+ with exactly one '@' */
+	for (i = 0; i < size; ++i) {
+		if (Common::isAlnum(data[i]))
+			continue;
+
+		switch (data[i]) {
+		case '@':
+			nb++;
+
+		case '-':
+		case '.':
+		case '_':
+			break;
+
+		case '>':
+			return (nb == 1) ? i + 1 : 0;
+
+		default:
+			return 0;
+		}
+	}
+
+	return 0;
+}
+
+/* tag_length • returns the length of the given tag, or 0 is it's not valid */
+static size_t tag_length(byte *data, size_t size, MKDAutolink *autolink) {
+	size_t i, j;
+
+	/* a valid tag can't be shorter than 3 chars */
+	if (size < 3) return 0;
+
+	/* begins with a '<' optionally followed by '/', followed by letter or number */
+	if (data[0] != '<') return 0;
+	i = (data[1] == '/') ? 2 : 1;
+
+	if (!Common::isAlnum(data[i]))
+		return 0;
+
+	/* scheme test */
+	*autolink = MKDA_NOT_AUTOLINK;
+
+	/* try to find the beginning of an URI */
+	while (i < size && (Common::isAlnum(data[i]) || data[i] == '.' || data[i] == '+' || data[i] == '-'))
+		i++;
+
+	if (i > 1 && data[i] == '@') {
+		if ((j = is_mail_autolink(data + i, size - i)) != 0) {
+			*autolink = MKDA_EMAIL;
+			return i + j;
+		}
+	}
+
+	if (i > 2 && data[i] == ':') {
+		*autolink = MKDA_NORMAL;
+		i++;
+	}
+
+	/* completing autolink test: no whitespace or ' or " */
+	if (i >= size)
+		*autolink = MKDA_NOT_AUTOLINK;
+
+	else if (*autolink) {
+		j = i;
+
+		while (i < size) {
+			if (data[i] == '\\') i += 2;
+			else if (data[i] == '>' || data[i] == '\'' ||
+			         data[i] == '"' || data[i] == ' ' || data[i] == '\n')
+				break;
+			else i++;
+		}
+
+		if (i >= size) return 0;
+		if (i > j && data[i] == '>') return i + 1;
+		/* one of the forbidden chars has been found */
+		*autolink = MKDA_NOT_AUTOLINK;
+	}
+
+	/* looking for sometinhg looking like a tag end */
+	while (i < size && data[i] != '>') i++;
+	if (i >= size) return 0;
+	return i + 1;
+}
+
+/* parse_inline • parses inline markdown elements */
+static void parse_inline(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size) {
+	size_t i = 0, end = 0;
+	byte action = 0;
+	DataBuffer work = { 0, 0, 0, 0 };
+
+	if (rndr->work_bufs[BUFFER_SPAN].size +
+	        rndr->work_bufs[BUFFER_BLOCK].size > rndr->max_nesting)
+		return;
+
+	while (i < size) {
+		/* copying inactive chars into the output */
+		while (end < size && (action = rndr->active_char[data[end]]) == 0) {
+			end++;
+		}
+
+		if (rndr->cb.normal_text) {
+			work.data = data + i;
+			work.size = end - i;
+			rndr->cb.normal_text(ob, &work, rndr->opaque);
+		} else
+			bufput(ob, data + i, end - i);
+
+		if (end >= size) break;
+		i = end;
+
+		end = markdown_char_ptrs[(int)action](ob, rndr, data + i, i, size - i);
+		if (!end) /* no action from the callback */
+			end = i + 1;
+		else {
+			i += end;
+			end = i;
+		}
+	}
+}
+
+/* find_emph_char • looks for the next emph byte, skipping other constructs */
+static size_t find_emph_char(byte *data, size_t size, byte c) {
+	size_t i = 1;
+
+	while (i < size) {
+		while (i < size && data[i] != c && data[i] != '`' && data[i] != '[')
+			i++;
+
+		if (i == size)
+			return 0;
+
+		if (data[i] == c)
+			return i;
+
+		/* not counting escaped chars */
+		if (i && data[i - 1] == '\\') {
+			i++;
+			continue;
+		}
+
+		if (data[i] == '`') {
+			size_t span_nb = 0, bt;
+			size_t tmp_i = 0;
+
+			/* counting the number of opening backticks */
+			while (i < size && data[i] == '`') {
+				i++;
+				span_nb++;
+			}
+
+			if (i >= size) return 0;
+
+			/* finding the matching closing sequence */
+			bt = 0;
+			while (i < size && bt < span_nb) {
+				if (!tmp_i && data[i] == c) tmp_i = i;
+				if (data[i] == '`') bt++;
+				else bt = 0;
+				i++;
+			}
+
+			if (i >= size) return tmp_i;
+		}
+		/* skipping a link */
+		else if (data[i] == '[') {
+			size_t tmp_i = 0;
+			byte cc;
+
+			i++;
+			while (i < size && data[i] != ']') {
+				if (!tmp_i && data[i] == c) tmp_i = i;
+				i++;
+			}
+
+			i++;
+			while (i < size && (data[i] == ' ' || data[i] == '\n'))
+				i++;
+
+			if (i >= size)
+				return tmp_i;
+
+			switch (data[i]) {
+			case '[':
+				cc = ']';
+				break;
+
+			case '(':
+				cc = ')';
+				break;
+
+			default:
+				if (tmp_i)
+					return tmp_i;
+				else
+					continue;
+			}
+
+			i++;
+			while (i < size && data[i] != cc) {
+				if (!tmp_i && data[i] == c) tmp_i = i;
+				i++;
+			}
+
+			if (i >= size)
+				return tmp_i;
+
+			i++;
+		}
+	}
+
+	return 0;
+}
+
+/* parse_emph1 • parsing single emphase */
+/* closed by a symbol not preceded by whitespace and not followed by symbol */
+static size_t parse_emph1(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size, byte c) {
+	size_t i = 0, len;
+	DataBuffer *work = 0;
+	int r;
+
+	if (!rndr->cb.emphasis) return 0;
+
+	/* skipping one symbol if coming from emph3 */
+	if (size > 1 && data[0] == c && data[1] == c) i = 1;
+
+	while (i < size) {
+		len = find_emph_char(data + i, size - i, c);
+		if (!len) return 0;
+		i += len;
+		if (i >= size) return 0;
+
+		if (data[i] == c && !_isspace(data[i - 1])) {
+
+			if (rndr->ext_flags & MKDEXT_NO_INTRA_EMPHASIS) {
+				if (i + 1 < size && Common::isAlnum(data[i + 1]))
+					continue;
+			}
+
+			work = rndr_newbuf(rndr, BUFFER_SPAN);
+			parse_inline(work, rndr, data, i);
+			r = rndr->cb.emphasis(ob, work, rndr->opaque);
+			rndr_popbuf(rndr, BUFFER_SPAN);
+			return r ? i + 1 : 0;
+		}
+	}
+
+	return 0;
+}
+
+/* parse_emph2 • parsing single emphase */
+static size_t parse_emph2(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size, byte c) {
+	int (*render_method)(DataBuffer * ob, const DataBuffer * text, void *opaque);
+	size_t i = 0, len;
+	DataBuffer *work = 0;
+	int r;
+
+	render_method = (c == '~') ? rndr->cb.strikethrough : rndr->cb.double_emphasis;
+
+	if (!render_method)
+		return 0;
+
+	while (i < size) {
+		len = find_emph_char(data + i, size - i, c);
+		if (!len) return 0;
+		i += len;
+
+		if (i + 1 < size && data[i] == c && data[i + 1] == c && i && !_isspace(data[i - 1])) {
+			work = rndr_newbuf(rndr, BUFFER_SPAN);
+			parse_inline(work, rndr, data, i);
+			r = render_method(ob, work, rndr->opaque);
+			rndr_popbuf(rndr, BUFFER_SPAN);
+			return r ? i + 2 : 0;
+		}
+		i++;
+	}
+	return 0;
+}
+
+/* parse_emph3 • parsing single emphase */
+/* finds the first closing tag, and delegates to the other emph */
+static size_t parse_emph3(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size, byte c) {
+	size_t i = 0, len;
+	int r;
+
+	while (i < size) {
+		len = find_emph_char(data + i, size - i, c);
+		if (!len) return 0;
+		i += len;
+
+		/* skip whitespace preceded symbols */
+		if (data[i] != c || _isspace(data[i - 1]))
+			continue;
+
+		if (i + 2 < size && data[i + 1] == c && data[i + 2] == c && rndr->cb.triple_emphasis) {
+			/* triple symbol found */
+			DataBuffer *work = rndr_newbuf(rndr, BUFFER_SPAN);
+
+			parse_inline(work, rndr, data, i);
+			r = rndr->cb.triple_emphasis(ob, work, rndr->opaque);
+			rndr_popbuf(rndr, BUFFER_SPAN);
+			return r ? i + 3 : 0;
+
+		} else if (i + 1 < size && data[i + 1] == c) {
+			/* double symbol found, handing over to emph1 */
+			len = parse_emph1(ob, rndr, data - 2, size + 2, c);
+			if (!len) return 0;
+			else return len - 2;
+
+		} else {
+			/* single symbol found, handing over to emph2 */
+			len = parse_emph2(ob, rndr, data - 1, size + 1, c);
+			if (!len) return 0;
+			else return len - 1;
+		}
+	}
+	return 0;
+}
+
+/* char_emphasis • single and double emphasis parsing */
+static size_t char_emphasis(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size) {
+	byte c = data[0];
+	size_t ret;
+
+	if (rndr->ext_flags & MKDEXT_NO_INTRA_EMPHASIS) {
+		if (offset > 0 && !_isspace(data[-1]) && data[-1] != '>')
+			return 0;
+	}
+
+	if (size > 2 && data[1] != c) {
+		/* whitespace cannot follow an opening emphasis;
+		 * strikethrough only takes two characters '~~' */
+		if (c == '~' || _isspace(data[1]) || (ret = parse_emph1(ob, rndr, data + 1, size - 1, c)) == 0)
+			return 0;
+
+		return ret + 1;
+	}
+
+	if (size > 3 && data[1] == c && data[2] != c) {
+		if (_isspace(data[2]) || (ret = parse_emph2(ob, rndr, data + 2, size - 2, c)) == 0)
+			return 0;
+
+		return ret + 2;
+	}
+
+	if (size > 4 && data[1] == c && data[2] == c && data[3] != c) {
+		if (c == '~' || _isspace(data[3]) || (ret = parse_emph3(ob, rndr, data + 3, size - 3, c)) == 0)
+			return 0;
+
+		return ret + 3;
+	}
+
+	return 0;
+}
+
+/* char_linebreak • '\n' preceded by two spaces (assuming linebreak != 0) */
+static size_t char_linebreak(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size) {
+	if (offset < 2 || data[-1] != ' ' || data[-2] != ' ')
+		return 0;
+
+	/* removing the last space from ob and rendering */
+	while (ob->size && ob->data[ob->size - 1] == ' ')
+		ob->size--;
+
+	return rndr->cb.linebreak(ob, rndr->opaque) ? 1 : 0;
+}
+
+/* char_codespan • '`' parsing a code span (assuming codespan != 0) */
+static size_t char_codespan(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size) {
+	size_t end, nb = 0, i, f_begin, f_end;
+
+	/* counting the number of backticks in the delimiter */
+	while (nb < size && data[nb] == '`')
+		nb++;
+
+	/* finding the next delimiter */
+	i = 0;
+	for (end = nb; end < size && i < nb; end++) {
+		if (data[end] == '`') i++;
+		else i = 0;
+	}
+
+	if (i < nb && end >= size)
+		return 0; /* no matching delimiter */
+
+	/* trimming outside whitespaces */
+	f_begin = nb;
+	while (f_begin < end && data[f_begin] == ' ')
+		f_begin++;
+
+	f_end = end - nb;
+	while (f_end > nb && data[f_end - 1] == ' ')
+		f_end--;
+
+	/* real code span */
+	if (f_begin < f_end) {
+		DataBuffer work = { data + f_begin, f_end - f_begin, 0, 0 };
+		if (!rndr->cb.codespan(ob, &work, rndr->opaque))
+			end = 0;
+	} else {
+		if (!rndr->cb.codespan(ob, 0, rndr->opaque))
+			end = 0;
+	}
+
+	return end;
+}
+
+/* char_escape • '\\' backslash escape */
+static size_t char_escape(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size) {
+	static const char *escape_chars = "\\`*_{}[]()#+-.!:|&<>^~";
+	DataBuffer work = { 0, 0, 0, 0 };
+
+	if (size > 1) {
+		if (strchr(escape_chars, data[1]) == NULL)
+			return 0;
+
+		if (rndr->cb.normal_text) {
+			work.data = data + 1;
+			work.size = 1;
+			rndr->cb.normal_text(ob, &work, rndr->opaque);
+		} else bufputc(ob, data[1]);
+	} else if (size == 1) {
+		bufputc(ob, data[0]);
+	}
+
+	return 2;
+}
+
+/* char_entity • '&' escaped when it doesn't belong to an entity */
+/* valid entities are assumed to be anything matching &#?[A-Za-z0-9]+; */
+static size_t char_entity(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size) {
+	size_t end = 1;
+	DataBuffer work = { 0, 0, 0, 0 };
+
+	if (end < size && data[end] == '#')
+		end++;
+
+	while (end < size && Common::isAlnum(data[end]))
+		end++;
+
+	if (end < size && data[end] == ';')
+		end++; /* real entity */
+	else
+		return 0; /* lone '&' */
+
+	if (rndr->cb.entity) {
+		work.data = data;
+		work.size = end;
+		rndr->cb.entity(ob, &work, rndr->opaque);
+	} else bufput(ob, data, end);
+
+	return end;
+}
+
+/* char_langle_tag • '<' when tags or autolinks are allowed */
+static size_t char_langle_tag(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size) {
+	MKDAutolink altype = MKDA_NOT_AUTOLINK;
+	size_t end = tag_length(data, size, &altype);
+	DataBuffer work = { data, end, 0, 0 };
+	int ret = 0;
+
+	if (end > 2) {
+		if (rndr->cb.autolink && altype != MKDA_NOT_AUTOLINK) {
+			DataBuffer *u_link = rndr_newbuf(rndr, BUFFER_SPAN);
+			work.data = data + 1;
+			work.size = end - 2;
+			unscape_text(u_link, &work);
+			ret = rndr->cb.autolink(ob, u_link, altype, rndr->opaque);
+			rndr_popbuf(rndr, BUFFER_SPAN);
+		} else if (rndr->cb.raw_html_tag)
+			ret = rndr->cb.raw_html_tag(ob, &work, rndr->opaque);
+	}
+
+	if (!ret) return 0;
+	else return end;
+}
+
+static size_t char_autolink_www(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size) {
+	DataBuffer *link, *link_url, *link_text;
+	size_t link_len, rewind;
+
+	if (!rndr->cb.link || rndr->in_link_body)
+		return 0;
+
+	link = rndr_newbuf(rndr, BUFFER_SPAN);
+
+	if ((link_len = sd_autolink__www(&rewind, link, data, offset, size, 0)) > 0) {
+		link_url = rndr_newbuf(rndr, BUFFER_SPAN);
+		BUFPUTSL(link_url, "http://");
+		bufput(link_url, link->data, link->size);
+
+		ob->size -= rewind;
+		if (rndr->cb.normal_text) {
+			link_text = rndr_newbuf(rndr, BUFFER_SPAN);
+			rndr->cb.normal_text(link_text, link, rndr->opaque);
+			rndr->cb.link(ob, link_url, NULL, link_text, rndr->opaque);
+			rndr_popbuf(rndr, BUFFER_SPAN);
+		} else {
+			rndr->cb.link(ob, link_url, NULL, link, rndr->opaque);
+		}
+		rndr_popbuf(rndr, BUFFER_SPAN);
+	}
+
+	rndr_popbuf(rndr, BUFFER_SPAN);
+	return link_len;
+}
+
+static size_t char_autolink_email(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size) {
+	DataBuffer *link;
+	size_t link_len, rewind;
+
+	if (!rndr->cb.autolink || rndr->in_link_body)
+		return 0;
+
+	link = rndr_newbuf(rndr, BUFFER_SPAN);
+
+	if ((link_len = sd_autolink__email(&rewind, link, data, offset, size, 0)) > 0) {
+		ob->size -= rewind;
+		rndr->cb.autolink(ob, link, MKDA_EMAIL, rndr->opaque);
+	}
+
+	rndr_popbuf(rndr, BUFFER_SPAN);
+	return link_len;
+}
+
+static size_t char_autolink_url(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size) {
+	DataBuffer *link;
+	size_t link_len, rewind;
+
+	if (!rndr->cb.autolink || rndr->in_link_body)
+		return 0;
+
+	link = rndr_newbuf(rndr, BUFFER_SPAN);
+
+	if ((link_len = sd_autolink__url(&rewind, link, data, offset, size, 0)) > 0) {
+		ob->size -= rewind;
+		rndr->cb.autolink(ob, link, MKDA_NORMAL, rndr->opaque);
+	}
+
+	rndr_popbuf(rndr, BUFFER_SPAN);
+	return link_len;
+}
+
+/* char_link • '[': parsing a link or an image */
+static size_t char_link(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size) {
+	int is_img = (offset && data[-1] == '!'), level;
+	size_t i = 1, txt_e, link_b = 0, link_e = 0, title_b = 0, title_e = 0;
+	DataBuffer *content = 0;
+	DataBuffer *link = 0;
+	DataBuffer *title = 0;
+	DataBuffer *u_link = 0;
+	size_t org_work_size = rndr->work_bufs[BUFFER_SPAN].size;
+	int text_has_nl = 0, ret = 0;
+	int in_title = 0, qtype = 0;
+
+	/* checking whether the correct renderer exists */
+	if ((is_img && !rndr->cb.image) || (!is_img && !rndr->cb.link))
+		goto cleanup;
+
+	/* looking for the matching closing bracket */
+	for (level = 1; i < size; i++) {
+		if (data[i] == '\n')
+			text_has_nl = 1;
+
+		else if (data[i - 1] == '\\')
+			continue;
+
+		else if (data[i] == '[')
+			level++;
+
+		else if (data[i] == ']') {
+			level--;
+			if (level <= 0)
+				break;
+		}
+	}
+
+	if (i >= size)
+		goto cleanup;
+
+	txt_e = i;
+	i++;
+
+	/* skip any amount of whitespace or newline */
+	/* (this is much more laxist than original markdown syntax) */
+	while (i < size && _isspace(data[i]))
+		i++;
+
+	/* inline style link */
+	if (i < size && data[i] == '(') {
+		/* skipping initial whitespace */
+		i++;
+
+		while (i < size && _isspace(data[i]))
+			i++;
+
+		link_b = i;
+
+		/* looking for link end: ' " ) */
+		while (i < size) {
+			if (data[i] == '\\') i += 2;
+			else if (data[i] == ')') break;
+			else if (i >= 1 && _isspace(data[i - 1]) && (data[i] == '\'' || data[i] == '"')) break;
+			else i++;
+		}
+
+		if (i >= size) goto cleanup;
+		link_e = i;
+
+		/* looking for title end if present */
+		if (data[i] == '\'' || data[i] == '"') {
+			qtype = data[i];
+			in_title = 1;
+			i++;
+			title_b = i;
+
+			while (i < size) {
+				if (data[i] == '\\') i += 2;
+				else if (data[i] == qtype) {
+					in_title = 0;
+					i++;
+				} else if ((data[i] == ')') && !in_title) break;
+				else i++;
+			}
+
+			if (i >= size) goto cleanup;
+
+			/* skipping whitespaces after title */
+			title_e = i - 1;
+			while (title_e > title_b && _isspace(data[title_e]))
+				title_e--;
+
+			/* checking for closing quote presence */
+			if (data[title_e] != '\'' &&  data[title_e] != '"') {
+				title_b = title_e = 0;
+				link_e = i;
+			}
+		}
+
+		/* remove whitespace at the end of the link */
+		while (link_e > link_b && _isspace(data[link_e - 1]))
+			link_e--;
+
+		/* remove optional angle brackets around the link */
+		if (data[link_b] == '<') link_b++;
+		if (data[link_e - 1] == '>') link_e--;
+
+		/* building escaped link and title */
+		if (link_e > link_b) {
+			link = rndr_newbuf(rndr, BUFFER_SPAN);
+			bufput(link, data + link_b, link_e - link_b);
+		}
+
+		if (title_e > title_b) {
+			title = rndr_newbuf(rndr, BUFFER_SPAN);
+			bufput(title, data + title_b, title_e - title_b);
+		}
+
+		i++;
+	}
+
+	/* reference style link */
+	else if (i < size && data[i] == '[') {
+		DataBuffer id = { 0, 0, 0, 0 };
+		LinkRef *lr;
+
+		/* looking for the id */
+		i++;
+		link_b = i;
+		while (i < size && data[i] != ']') i++;
+		if (i >= size) goto cleanup;
+		link_e = i;
+
+		/* finding the link_ref */
+		if (link_b == link_e) {
+			if (text_has_nl) {
+				DataBuffer *b = rndr_newbuf(rndr, BUFFER_SPAN);
+				size_t j;
+
+				for (j = 1; j < txt_e; j++) {
+					if (data[j] != '\n')
+						bufputc(b, data[j]);
+					else if (data[j - 1] != ' ')
+						bufputc(b, ' ');
+				}
+
+				id.data = b->data;
+				id.size = b->size;
+			} else {
+				id.data = data + 1;
+				id.size = txt_e - 1;
+			}
+		} else {
+			id.data = data + link_b;
+			id.size = link_e - link_b;
+		}
+
+		lr = find_link_ref(rndr->refs, id.data, id.size);
+		if (!lr)
+			goto cleanup;
+
+		/* keeping link and title from link_ref */
+		link = lr->link;
+		title = lr->title;
+		i++;
+	}
+
+	/* shortcut reference style link */
+	else {
+		DataBuffer id = { 0, 0, 0, 0 };
+		LinkRef *lr;
+
+		/* crafting the id */
+		if (text_has_nl) {
+			DataBuffer *b = rndr_newbuf(rndr, BUFFER_SPAN);
+			size_t j;
+
+			for (j = 1; j < txt_e; j++) {
+				if (data[j] != '\n')
+					bufputc(b, data[j]);
+				else if (data[j - 1] != ' ')
+					bufputc(b, ' ');
+			}
+
+			id.data = b->data;
+			id.size = b->size;
+		} else {
+			id.data = data + 1;
+			id.size = txt_e - 1;
+		}
+
+		/* finding the link_ref */
+		lr = find_link_ref(rndr->refs, id.data, id.size);
+		if (!lr)
+			goto cleanup;
+
+		/* keeping link and title from link_ref */
+		link = lr->link;
+		title = lr->title;
+
+		/* rewinding the whitespace */
+		i = txt_e + 1;
+	}
+
+	/* building content: img alt is escaped, link content is parsed */
+	if (txt_e > 1) {
+		content = rndr_newbuf(rndr, BUFFER_SPAN);
+		if (is_img) {
+			bufput(content, data + 1, txt_e - 1);
+		} else {
+			/* disable autolinking when parsing inline the
+			 * content of a link */
+			rndr->in_link_body = 1;
+			parse_inline(content, rndr, data + 1, txt_e - 1);
+			rndr->in_link_body = 0;
+		}
+	}
+
+	if (link) {
+		u_link = rndr_newbuf(rndr, BUFFER_SPAN);
+		unscape_text(u_link, link);
+	}
+
+	/* calling the relevant rendering function */
+	if (is_img) {
+		if (ob->size && ob->data[ob->size - 1] == '!')
+			ob->size -= 1;
+
+		ret = rndr->cb.image(ob, u_link, title, content, rndr->opaque);
+	} else {
+		ret = rndr->cb.link(ob, u_link, title, content, rndr->opaque);
+	}
+
+	/* cleanup */
+cleanup:
+	rndr->work_bufs[BUFFER_SPAN].size = (int)org_work_size;
+	return ret ? i : 0;
+}
+
+static size_t char_superscript(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size) {
+	size_t sup_start, sup_len;
+	DataBuffer *sup;
+
+	if (!rndr->cb.superscript)
+		return 0;
+
+	if (size < 2)
+		return 0;
+
+	if (data[1] == '(') {
+		sup_start = sup_len = 2;
+
+		while (sup_len < size && data[sup_len] != ')' && data[sup_len - 1] != '\\')
+			sup_len++;
+
+		if (sup_len == size)
+			return 0;
+	} else {
+		sup_start = sup_len = 1;
+
+		while (sup_len < size && !_isspace(data[sup_len]))
+			sup_len++;
+	}
+
+	if (sup_len - sup_start == 0)
+		return (sup_start == 2) ? 3 : 0;
+
+	sup = rndr_newbuf(rndr, BUFFER_SPAN);
+	parse_inline(sup, rndr, data + sup_start, sup_len - sup_start);
+	rndr->cb.superscript(ob, sup, rndr->opaque);
+	rndr_popbuf(rndr, BUFFER_SPAN);
+
+	return (sup_start == 2) ? sup_len + 1 : sup_len;
+}
+
+/*********************************
+ * BLOCK-LEVEL PARSING FUNCTIONS *
+ *********************************/
+
+/* is_empty • returns the line length when it is empty, 0 otherwise */
+static size_t is_empty(byte *data, size_t size) {
+	size_t i;
+
+	for (i = 0; i < size && data[i] != '\n'; i++)
+		if (data[i] != ' ')
+			return 0;
+
+	return i + 1;
+}
+
+/* is_hrule • returns whether a line is a horizontal rule */
+static int is_hrule(byte *data, size_t size) {
+	size_t i = 0, n = 0;
+	byte c;
+
+	/* skipping initial spaces */
+	if (size < 3) return 0;
+	if (data[0] == ' ') {
+		i++;
+		if (data[1] == ' ') {
+			i++;
+			if (data[2] == ' ') {
+				i++;
+			}
+		}
+	}
+
+	/* looking at the hrule byte */
+	if (i + 2 >= size
+	        || (data[i] != '*' && data[i] != '-' && data[i] != '_'))
+		return 0;
+	c = data[i];
+
+	/* the whole line must be the char or whitespace */
+	while (i < size && data[i] != '\n') {
+		if (data[i] == c) n++;
+		else if (data[i] != ' ')
+			return 0;
+
+		i++;
+	}
+
+	return n >= 3;
+}
+
+/* check if a line begins with a code fence; return the
+ * width of the code fence */
+static size_t prefix_codefence(byte *data, size_t size) {
+	size_t i = 0, n = 0;
+	byte c;
+
+	/* skipping initial spaces */
+	if (size < 3) return 0;
+	if (data[0] == ' ') {
+		i++;
+		if (data[1] == ' ') {
+			i++;
+			if (data[2] == ' ') {
+				i++;
+			}
+		}
+	}
+
+	/* looking at the hrule byte */
+	if (i + 2 >= size || !(data[i] == '~' || data[i] == '`'))
+		return 0;
+
+	c = data[i];
+
+	/* the whole line must be the byte or whitespace */
+	while (i < size && data[i] == c) {
+		n++;
+		i++;
+	}
+
+	if (n < 3)
+		return 0;
+
+	return i;
+}
+
+/* check if a line is a code fence; return its size if it is */
+static size_t is_codefence(byte *data, size_t size, DataBuffer *syntax) {
+	size_t i = 0, syn_len = 0;
+	byte *syn_start;
+
+	i = prefix_codefence(data, size);
+	if (i == 0)
+		return 0;
+
+	while (i < size && data[i] == ' ')
+		i++;
+
+	syn_start = data + i;
+
+	if (i < size && data[i] == '{') {
+		i++;
+		syn_start++;
+
+		while (i < size && data[i] != '}' && data[i] != '\n') {
+			syn_len++;
+			i++;
+		}
+
+		if (i == size || data[i] != '}')
+			return 0;
+
+		/* strip all whitespace at the beginning and the end
+		 * of the {} block */
+		while (syn_len > 0 && _isspace(syn_start[0])) {
+			syn_start++;
+			syn_len--;
+		}
+
+		while (syn_len > 0 && _isspace(syn_start[syn_len - 1]))
+			syn_len--;
+
+		i++;
+	} else {
+		while (i < size && !_isspace(data[i])) {
+			syn_len++;
+			i++;
+		}
+	}
+
+	if (syntax) {
+		syntax->data = syn_start;
+		syntax->size = syn_len;
+	}
+
+	while (i < size && data[i] != '\n') {
+		if (!_isspace(data[i]))
+			return 0;
+
+		i++;
+	}
+
+	return i + 1;
+}
+
+/* is_atxheader • returns whether the line is a hash-prefixed header */
+static int is_atxheader(SDMarkdown *rndr, byte *data, size_t size) {
+	if (data[0] != '#')
+		return 0;
+
+	if (rndr->ext_flags & MKDEXT_SPACE_HEADERS) {
+		size_t level = 0;
+
+		while (level < size && level < 6 && data[level] == '#')
+			level++;
+
+		if (level < size && data[level] != ' ')
+			return 0;
+	}
+
+	return 1;
+}
+
+/* is_headerline • returns whether the line is a setext-style hdr underline */
+static int is_headerline(byte *data, size_t size) {
+	size_t i = 0;
+
+	/* test of level 1 header */
+	if (data[i] == '=') {
+		for (i = 1; i < size && data[i] == '='; i++);
+		while (i < size && data[i] == ' ') i++;
+		return (i >= size || data[i] == '\n') ? 1 : 0;
+	}
+
+	/* test of level 2 header */
+	if (data[i] == '-') {
+		for (i = 1; i < size && data[i] == '-'; i++);
+		while (i < size && data[i] == ' ') i++;
+		return (i >= size || data[i] == '\n') ? 2 : 0;
+	}
+
+	return 0;
+}
+
+static int is_next_headerline(byte *data, size_t size) {
+	size_t i = 0;
+
+	while (i < size && data[i] != '\n')
+		i++;
+
+	if (++i >= size)
+		return 0;
+
+	return is_headerline(data + i, size - i);
+}
+
+/* prefix_quote • returns blockquote prefix length */
+static size_t prefix_quote(byte *data, size_t size) {
+	size_t i = 0;
+	if (i < size && data[i] == ' ') i++;
+	if (i < size && data[i] == ' ') i++;
+	if (i < size && data[i] == ' ') i++;
+
+	if (i < size && data[i] == '>') {
+		if (i + 1 < size && data[i + 1] == ' ')
+			return i + 2;
+
+		return i + 1;
+	}
+
+	return 0;
+}
+
+/* prefix_code • returns prefix length for block code*/
+static size_t prefix_code(byte *data, size_t size) {
+	if (size > 3 && data[0] == ' ' && data[1] == ' '
+	        && data[2] == ' ' && data[3] == ' ') return 4;
+
+	return 0;
+}
+
+/* prefix_oli • returns ordered list item prefix */
+static size_t prefix_oli(byte *data, size_t size) {
+	size_t i = 0;
+
+	if (i < size && data[i] == ' ') i++;
+	if (i < size && data[i] == ' ') i++;
+	if (i < size && data[i] == ' ') i++;
+
+	if (i >= size || data[i] < '0' || data[i] > '9')
+		return 0;
+
+	while (i < size && data[i] >= '0' && data[i] <= '9')
+		i++;
+
+	if (i + 1 >= size || data[i] != '.' || data[i + 1] != ' ')
+		return 0;
+
+	if (is_next_headerline(data + i, size - i))
+		return 0;
+
+	return i + 2;
+}
+
+/* prefix_uli • returns ordered list item prefix */
+static size_t prefix_uli(byte *data, size_t size) {
+	size_t i = 0;
+
+	if (i < size && data[i] == ' ') i++;
+	if (i < size && data[i] == ' ') i++;
+	if (i < size && data[i] == ' ') i++;
+
+	if (i + 1 >= size ||
+	        (data[i] != '*' && data[i] != '+' && data[i] != '-') ||
+	        data[i + 1] != ' ')
+		return 0;
+
+	if (is_next_headerline(data + i, size - i))
+		return 0;
+
+	return i + 2;
+}
+
+/* parse_block • parsing of one block, returning next byte to parse */
+static void parse_block(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size);
+
+/* parse_blockquote • handles parsing of a blockquote fragment */
+static size_t parse_blockquote(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size) {
+	size_t beg, end = 0, pre, work_size = 0;
+	byte *work_data = 0;
+	DataBuffer *out = 0;
+
+	out = rndr_newbuf(rndr, BUFFER_BLOCK);
+	beg = 0;
+	while (beg < size) {
+		for (end = beg + 1; end < size && data[end - 1] != '\n'; end++);
+
+		pre = prefix_quote(data + beg, end - beg);
+
+		if (pre)
+			beg += pre; /* skipping prefix */
+
+		/* empty line followed by non-quote line */
+		else if (is_empty(data + beg, end - beg) &&
+		         (end >= size || (prefix_quote(data + end, size - end) == 0 &&
+		                          !is_empty(data + end, size - end))))
+			break;
+
+		if (beg < end) { /* copy into the in-place working buffer */
+			/* bufput(work, data + beg, end - beg); */
+			if (!work_data)
+				work_data = data + beg;
+			else if (data + beg != work_data + work_size)
+				memmove(work_data + work_size, data + beg, end - beg);
+			work_size += end - beg;
+		}
+		beg = end;
+	}
+
+	parse_block(out, rndr, work_data, work_size);
+	if (rndr->cb.blockquote)
+		rndr->cb.blockquote(ob, out, rndr->opaque);
+	rndr_popbuf(rndr, BUFFER_BLOCK);
+	return end;
+}
+
+static size_t parse_htmlblock(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size, int do_render);
+
+/* parse_blockquote • handles parsing of a regular paragraph */
+static size_t parse_paragraph(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size) {
+	size_t i = 0, end = 0;
+	int level = 0;
+	DataBuffer work = { data, 0, 0, 0 };
+
+	while (i < size) {
+		for (end = i + 1; end < size && data[end - 1] != '\n'; end++) /* empty */;
+
+		if (is_empty(data + i, size - i))
+			break;
+
+		if ((level = is_headerline(data + i, size - i)) != 0)
+			break;
+
+		if (is_atxheader(rndr, data + i, size - i) ||
+		        is_hrule(data + i, size - i) ||
+		        prefix_quote(data + i, size - i)) {
+			end = i;
+			break;
+		}
+
+		/*
+		 * Early termination of a paragraph with the same logic
+		 * as Markdown 1.0.0. If this logic is applied, the
+		 * Markdown 1.0.3 test suite won't pass cleanly
+		 *
+		 * :: If the first character in a new line is not a letter,
+		 * let's check to see if there's some kind of block starting
+		 * here
+		 */
+		if ((rndr->ext_flags & MKDEXT_LAX_SPACING) && !Common::isAlnum(data[i])) {
+			if (prefix_oli(data + i, size - i) ||
+			        prefix_uli(data + i, size - i)) {
+				end = i;
+				break;
+			}
+
+			/* see if an html block starts here */
+			if (data[i] == '<' && rndr->cb.blockhtml &&
+			        parse_htmlblock(ob, rndr, data + i, size - i, 0)) {
+				end = i;
+				break;
+			}
+
+			/* see if a code fence starts here */
+			if ((rndr->ext_flags & MKDEXT_FENCED_CODE) != 0 &&
+			        is_codefence(data + i, size - i, NULL) != 0) {
+				end = i;
+				break;
+			}
+		}
+
+		i = end;
+	}
+
+	work.size = i;
+	while (work.size && data[work.size - 1] == '\n')
+		work.size--;
+
+	if (!level) {
+		DataBuffer *tmp = rndr_newbuf(rndr, BUFFER_BLOCK);
+		parse_inline(tmp, rndr, work.data, work.size);
+		if (rndr->cb.paragraph)
+			rndr->cb.paragraph(ob, tmp, rndr->opaque);
+		rndr_popbuf(rndr, BUFFER_BLOCK);
+	} else {
+		DataBuffer *header_work;
+
+		if (work.size) {
+			size_t beg;
+			i = work.size;
+			work.size -= 1;
+
+			while (work.size && data[work.size] != '\n')
+				work.size -= 1;
+
+			beg = work.size + 1;
+			while (work.size && data[work.size - 1] == '\n')
+				work.size -= 1;
+
+			if (work.size > 0) {
+				DataBuffer *tmp = rndr_newbuf(rndr, BUFFER_BLOCK);
+				parse_inline(tmp, rndr, work.data, work.size);
+
+				if (rndr->cb.paragraph)
+					rndr->cb.paragraph(ob, tmp, rndr->opaque);
+
+				rndr_popbuf(rndr, BUFFER_BLOCK);
+				work.data += beg;
+				work.size = i - beg;
+			} else work.size = i;
+		}
+
+		header_work = rndr_newbuf(rndr, BUFFER_SPAN);
+		parse_inline(header_work, rndr, work.data, work.size);
+
+		if (rndr->cb.header)
+			rndr->cb.header(ob, header_work, (int)level, rndr->opaque);
+
+		rndr_popbuf(rndr, BUFFER_SPAN);
+	}
+
+	return end;
+}
+
+/* parse_fencedcode • handles parsing of a block-level code fragment */
+static size_t parse_fencedcode(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size) {
+	size_t beg, end;
+	DataBuffer *work = 0;
+	DataBuffer lang = { 0, 0, 0, 0 };
+
+	beg = is_codefence(data, size, &lang);
+	if (beg == 0) return 0;
+
+	work = rndr_newbuf(rndr, BUFFER_BLOCK);
+
+	while (beg < size) {
+		size_t fence_end;
+		DataBuffer fence_trail = { 0, 0, 0, 0 };
+
+		fence_end = is_codefence(data + beg, size - beg, &fence_trail);
+		if (fence_end != 0 && fence_trail.size == 0) {
+			beg += fence_end;
+			break;
+		}
+
+		for (end = beg + 1; end < size && data[end - 1] != '\n'; end++);
+
+		if (beg < end) {
+			/* verbatim copy to the working buffer,
+			    escaping entities */
+			if (is_empty(data + beg, end - beg))
+				bufputc(work, '\n');
+			else bufput(work, data + beg, end - beg);
+		}
+		beg = end;
+	}
+
+	if (work->size && work->data[work->size - 1] != '\n')
+		bufputc(work, '\n');
+
+	if (rndr->cb.blockcode)
+		rndr->cb.blockcode(ob, work, lang.size ? &lang : NULL, rndr->opaque);
+
+	rndr_popbuf(rndr, BUFFER_BLOCK);
+	return beg;
+}
+
+static size_t parse_blockcode(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size) {
+	size_t beg, end, pre;
+	DataBuffer *work = 0;
+
+	work = rndr_newbuf(rndr, BUFFER_BLOCK);
+
+	beg = 0;
+	while (beg < size) {
+		for (end = beg + 1; end < size && data[end - 1] != '\n'; end++) {};
+		pre = prefix_code(data + beg, end - beg);
+
+		if (pre)
+			beg += pre; /* skipping prefix */
+		else if (!is_empty(data + beg, end - beg))
+			/* non-empty non-prefixed line breaks the pre */
+			break;
+
+		if (beg < end) {
+			/* verbatim copy to the working buffer,
+			    escaping entities */
+			if (is_empty(data + beg, end - beg))
+				bufputc(work, '\n');
+			else bufput(work, data + beg, end - beg);
+		}
+		beg = end;
+	}
+
+	while (work->size && work->data[work->size - 1] == '\n')
+		work->size -= 1;
+
+	bufputc(work, '\n');
+
+	if (rndr->cb.blockcode)
+		rndr->cb.blockcode(ob, work, NULL, rndr->opaque);
+
+	rndr_popbuf(rndr, BUFFER_BLOCK);
+	return beg;
+}
+
+/* parse_listitem • parsing of a single list item */
+/*  assuming initial prefix is already removed */
+static size_t parse_listitem(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size, int *flags) {
+	DataBuffer *work = 0, *inter = 0;
+	size_t beg = 0, end, pre, sublist = 0, orgpre = 0, i;
+	int in_empty = 0, has_inside_empty = 0, in_fence = 0;
+
+	/* keeping track of the first indentation prefix */
+	while (orgpre < 3 && orgpre < size && data[orgpre] == ' ')
+		orgpre++;
+
+	beg = prefix_uli(data, size);
+	if (!beg)
+		beg = prefix_oli(data, size);
+
+	if (!beg)
+		return 0;
+
+	/* skipping to the beginning of the following line */
+	end = beg;
+	while (end < size && data[end - 1] != '\n')
+		end++;
+
+	/* getting working buffers */
+	work = rndr_newbuf(rndr, BUFFER_SPAN);
+	inter = rndr_newbuf(rndr, BUFFER_SPAN);
+
+	/* putting the first line into the working buffer */
+	bufput(work, data + beg, end - beg);
+	beg = end;
+
+	/* process the following lines */
+	while (beg < size) {
+		size_t has_next_uli = 0, has_next_oli = 0;
+
+		end++;
+
+		while (end < size && data[end - 1] != '\n')
+			end++;
+
+		/* process an empty line */
+		if (is_empty(data + beg, end - beg)) {
+			in_empty = 1;
+			beg = end;
+			continue;
+		}
+
+		/* calculating the indentation */
+		i = 0;
+		while (i < 4 && beg + i < end && data[beg + i] == ' ')
+			i++;
+
+		pre = i;
+
+		if (rndr->ext_flags & MKDEXT_FENCED_CODE) {
+			if (is_codefence(data + beg + i, end - beg - i, NULL) != 0)
+				in_fence = !in_fence;
+		}
+
+		/* Only check for new list items if we are **not** inside
+		 * a fenced code block */
+		if (!in_fence) {
+			has_next_uli = prefix_uli(data + beg + i, end - beg - i);
+			has_next_oli = prefix_oli(data + beg + i, end - beg - i);
+		}
+
+		/* checking for ul/ol switch */
+		if (in_empty && (
+		            ((*flags & MKD_LIST_ORDERED) && has_next_uli) ||
+		            (!(*flags & MKD_LIST_ORDERED) && has_next_oli))) {
+			*flags |= MKD_LI_END;
+			break; /* the following item must have same list type */
+		}
+
+		/* checking for a new item */
+		if ((has_next_uli && !is_hrule(data + beg + i, end - beg - i)) || has_next_oli) {
+			if (in_empty)
+				has_inside_empty = 1;
+
+			if (pre == orgpre) /* the following item must have */
+				break;             /* the same indentation */
+
+			if (!sublist)
+				sublist = work->size;
+		}
+		/* joining only indented stuff after empty lines;
+		 * note that now we only require 1 space of indentation
+		 * to continue a list */
+		else if (in_empty && pre == 0) {
+			*flags |= MKD_LI_END;
+			break;
+		} else if (in_empty) {
+			bufputc(work, '\n');
+			has_inside_empty = 1;
+		}
+
+		in_empty = 0;
+
+		/* adding the line without prefix into the working buffer */
+		bufput(work, data + beg + i, end - beg - i);
+		beg = end;
+	}
+
+	/* render of li contents */
+	if (has_inside_empty)
+		*flags |= MKD_LI_BLOCK;
+
+	if (*flags & MKD_LI_BLOCK) {
+		/* intermediate render of block li */
+		if (sublist && sublist < work->size) {
+			parse_block(inter, rndr, work->data, sublist);
+			parse_block(inter, rndr, work->data + sublist, work->size - sublist);
+		} else
+			parse_block(inter, rndr, work->data, work->size);
+	} else {
+		/* intermediate render of inline li */
+		if (sublist && sublist < work->size) {
+			parse_inline(inter, rndr, work->data, sublist);
+			parse_block(inter, rndr, work->data + sublist, work->size - sublist);
+		} else
+			parse_inline(inter, rndr, work->data, work->size);
+	}
+
+	/* render of li itself */
+	if (rndr->cb.listitem)
+		rndr->cb.listitem(ob, inter, *flags, rndr->opaque);
+
+	rndr_popbuf(rndr, BUFFER_SPAN);
+	rndr_popbuf(rndr, BUFFER_SPAN);
+	return beg;
+}
+
+/* parse_list • parsing ordered or unordered list block */
+static size_t parse_list(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size, int flags) {
+	DataBuffer *work = 0;
+	size_t i = 0, j;
+
+	work = rndr_newbuf(rndr, BUFFER_BLOCK);
+
+	while (i < size) {
+		j = parse_listitem(work, rndr, data + i, size - i, &flags);
+		i += j;
+
+		if (!j || (flags & MKD_LI_END))
+			break;
+	}
+
+	if (rndr->cb.list)
+		rndr->cb.list(ob, work, flags, rndr->opaque);
+	rndr_popbuf(rndr, BUFFER_BLOCK);
+	return i;
+}
+
+/* parse_atxheader • parsing of atx-style headers */
+static size_t parse_atxheader(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size) {
+	size_t level = 0;
+	size_t i, end, skip;
+
+	while (level < size && level < 6 && data[level] == '#')
+		level++;
+
+	for (i = level; i < size && data[i] == ' '; i++);
+
+	for (end = i; end < size && data[end] != '\n'; end++);
+	skip = end;
+
+	while (end && data[end - 1] == '#')
+		end--;
+
+	while (end && data[end - 1] == ' ')
+		end--;
+
+	if (end > i) {
+		DataBuffer *work = rndr_newbuf(rndr, BUFFER_SPAN);
+
+		parse_inline(work, rndr, data + i, end - i);
+
+		if (rndr->cb.header)
+			rndr->cb.header(ob, work, (int)level, rndr->opaque);
+
+		rndr_popbuf(rndr, BUFFER_SPAN);
+	}
+
+	return skip;
+}
+
+/* htmlblock_end • checking end of HTML block : </tag>[ \t]*\n[ \t*]\n */
+/*  returns the length on match, 0 otherwise */
+static size_t htmlblock_end_tag(
+    const char *tag,
+    size_t tag_len,
+    SDMarkdown *rndr,
+    byte *data,
+    size_t size) {
+	size_t i, w;
+
+	/* checking if tag is a match */
+	if (tag_len + 3 >= size ||
+	        scumm_strnicmp((char *)data + 2, tag, tag_len) != 0 ||
+	        data[tag_len + 2] != '>')
+		return 0;
+
+	/* checking white lines */
+	i = tag_len + 3;
+	w = 0;
+	if (i < size && (w = is_empty(data + i, size - i)) == 0)
+		return 0; /* non-blank after tag */
+	i += w;
+	w = 0;
+
+	if (i < size)
+		w = is_empty(data + i, size - i);
+
+	return i + w;
+}
+
+static size_t htmlblock_end(const char *curtag, SDMarkdown *rndr, byte *data, size_t size, int start_of_line) {
+	size_t tag_size = strlen(curtag);
+	size_t i = 1, end_tag;
+	int block_lines = 0;
+
+	while (i < size) {
+		i++;
+		while (i < size && !(data[i - 1] == '<' && data[i] == '/')) {
+			if (data[i] == '\n')
+				block_lines++;
+
+			i++;
+		}
+
+		/* If we are only looking for unindented tags, skip the tag
+		 * if it doesn't follow a newline.
+		 *
+		 * The only exception to this is if the tag is still on the
+		 * initial line; in that case it still counts as a closing
+		 * tag
+		 */
+		if (start_of_line && block_lines > 0 && data[i - 2] != '\n')
+			continue;
+
+		if (i + 2 + tag_size >= size)
+			break;
+
+		end_tag = htmlblock_end_tag(curtag, tag_size, rndr, data + i - 1, size - i + 1);
+		if (end_tag)
+			return i + end_tag - 1;
+	}
+
+	return 0;
+}
+
+inline const char *find_block_tag(const char *str, uint len);
+
+/* parse_htmlblock • parsing of inline HTML block */
+static size_t parse_htmlblock(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size, int do_render) {
+	size_t i, j = 0, tag_end;
+	const char *curtag = NULL;
+	DataBuffer work = { data, 0, 0, 0 };
+
+	/* identification of the opening tag */
+	if (size < 2 || data[0] != '<')
+		return 0;
+
+	i = 1;
+	while (i < size && data[i] != '>' && data[i] != ' ')
+		i++;
+
+	if (i < size)
+		curtag = find_block_tag((char *)data + 1, (int)i - 1);
+
+	/* handling of special cases */
+	if (!curtag) {
+
+		/* HTML comment, laxist form */
+		if (size > 5 && data[1] == '!' && data[2] == '-' && data[3] == '-') {
+			i = 5;
+
+			while (i < size && !(data[i - 2] == '-' && data[i - 1] == '-' && data[i] == '>'))
+				i++;
+
+			i++;
+
+			if (i < size)
+				j = is_empty(data + i, size - i);
+
+			if (j) {
+				work.size = i + j;
+				if (do_render && rndr->cb.blockhtml)
+					rndr->cb.blockhtml(ob, &work, rndr->opaque);
+				return work.size;
+			}
+		}
+
+		/* HR, which is the only self-closing block tag considered */
+		if (size > 4 && (data[1] == 'h' || data[1] == 'H') && (data[2] == 'r' || data[2] == 'R')) {
+			i = 3;
+			while (i < size && data[i] != '>')
+				i++;
+
+			if (i + 1 < size) {
+				i++;
+				j = is_empty(data + i, size - i);
+				if (j) {
+					work.size = i + j;
+					if (do_render && rndr->cb.blockhtml)
+						rndr->cb.blockhtml(ob, &work, rndr->opaque);
+					return work.size;
+				}
+			}
+		}
+
+		/* no special case recognised */
+		return 0;
+	}
+
+	/* looking for an unindented matching closing tag */
+	/*  followed by a blank line */
+	tag_end = htmlblock_end(curtag, rndr, data, size, 1);
+
+	/* if not found, trying a second pass looking for indented match */
+	/* but not if tag is "ins" or "del" (following original Markdown.pl) */
+	if (!tag_end && strcmp(curtag, "ins") != 0 && strcmp(curtag, "del") != 0) {
+		tag_end = htmlblock_end(curtag, rndr, data, size, 0);
+	}
+
+	if (!tag_end)
+		return 0;
+
+	/* the end of the block has been found */
+	work.size = tag_end;
+	if (do_render && rndr->cb.blockhtml)
+		rndr->cb.blockhtml(ob, &work, rndr->opaque);
+
+	return tag_end;
+}
+
+static void parse_table_row(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size, size_t columns, int *col_data, int header_flag) {
+	size_t i = 0, col;
+	DataBuffer *row_work = 0;
+
+	if (!rndr->cb.table_cell || !rndr->cb.table_row)
+		return;
+
+	row_work = rndr_newbuf(rndr, BUFFER_SPAN);
+
+	if (i < size && data[i] == '|')
+		i++;
+
+	for (col = 0; col < columns && i < size; ++col) {
+		size_t cell_start, cell_end;
+		DataBuffer *cell_work;
+
+		cell_work = rndr_newbuf(rndr, BUFFER_SPAN);
+
+		while (i < size && _isspace(data[i]))
+			i++;
+
+		cell_start = i;
+
+		while (i < size && data[i] != '|')
+			i++;
+
+		cell_end = i - 1;
+
+		while (cell_end > cell_start && _isspace(data[cell_end]))
+			cell_end--;
+
+		parse_inline(cell_work, rndr, data + cell_start, 1 + cell_end - cell_start);
+		rndr->cb.table_cell(row_work, cell_work, col_data[col] | header_flag, rndr->opaque);
+
+		rndr_popbuf(rndr, BUFFER_SPAN);
+		i++;
+	}
+
+	for (; col < columns; ++col) {
+		DataBuffer empty_cell = { 0, 0, 0, 0 };
+		rndr->cb.table_cell(row_work, &empty_cell, col_data[col] | header_flag, rndr->opaque);
+	}
+
+	rndr->cb.table_row(ob, row_work, rndr->opaque);
+
+	rndr_popbuf(rndr, BUFFER_SPAN);
+}
+
+static size_t parse_table_header(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size, size_t *columns, int **column_data) {
+	int pipes;
+	size_t i = 0, col, header_end, under_end;
+
+	pipes = 0;
+	while (i < size && data[i] != '\n')
+		if (data[i++] == '|')
+			pipes++;
+
+	if (i == size || pipes == 0)
+		return 0;
+
+	header_end = i;
+
+	while (header_end > 0 && _isspace(data[header_end - 1]))
+		header_end--;
+
+	if (data[0] == '|')
+		pipes--;
+
+	if (header_end && data[header_end - 1] == '|')
+		pipes--;
+
+	*columns = pipes + 1;
+	*column_data = (int *)calloc(*columns, sizeof(int));
+
+	/* Parse the header underline */
+	i++;
+	if (i < size && data[i] == '|')
+		i++;
+
+	under_end = i;
+	while (under_end < size && data[under_end] != '\n')
+		under_end++;
+
+	for (col = 0; col < *columns && i < under_end; ++col) {
+		size_t dashes = 0;
+
+		while (i < under_end && data[i] == ' ')
+			i++;
+
+		if (data[i] == ':') {
+			i++;
+			(*column_data)[col] |= MKD_TABLE_ALIGN_L;
+			dashes++;
+		}
+
+		while (i < under_end && data[i] == '-') {
+			i++;
+			dashes++;
+		}
+
+		if (i < under_end && data[i] == ':') {
+			i++;
+			(*column_data)[col] |= MKD_TABLE_ALIGN_R;
+			dashes++;
+		}
+
+		while (i < under_end && data[i] == ' ')
+			i++;
+
+		if (i < under_end && data[i] != '|')
+			break;
+
+		if (dashes < 3)
+			break;
+
+		i++;
+	}
+
+	if (col < *columns)
+		return 0;
+
+	parse_table_row(
+	    ob, rndr, data,
+	    header_end,
+	    *columns,
+	    *column_data,
+	    MKD_TABLE_HEADER
+	);
+
+	return under_end + 1;
+}
+
+static size_t parse_table(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size) {
+	size_t i;
+
+	DataBuffer *header_work = 0;
+	DataBuffer *body_work = 0;
+
+	size_t columns;
+	int *col_data = NULL;
+
+	header_work = rndr_newbuf(rndr, BUFFER_SPAN);
+	body_work = rndr_newbuf(rndr, BUFFER_BLOCK);
+
+	i = parse_table_header(header_work, rndr, data, size, &columns, &col_data);
+	if (i > 0) {
+
+		while (i < size) {
+			size_t row_start;
+			int pipes = 0;
+
+			row_start = i;
+
+			while (i < size && data[i] != '\n')
+				if (data[i++] == '|')
+					pipes++;
+
+			if (pipes == 0 || i == size) {
+				i = row_start;
+				break;
+			}
+
+			parse_table_row(
+			    body_work,
+			    rndr,
+			    data + row_start,
+			    i - row_start,
+			    columns,
+			    col_data, 0
+			);
+
+			i++;
+		}
+
+		if (rndr->cb.table)
+			rndr->cb.table(ob, header_work, body_work, rndr->opaque);
+	}
+
+	free(col_data);
+	rndr_popbuf(rndr, BUFFER_SPAN);
+	rndr_popbuf(rndr, BUFFER_BLOCK);
+	return i;
+}
+
+/* parse_block • parsing of one block, returning next byte to parse */
+static void parse_block(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size) {
+	size_t beg, end, i;
+	byte *txt_data;
+	beg = 0;
+
+	if (rndr->work_bufs[BUFFER_SPAN].size +
+	        rndr->work_bufs[BUFFER_BLOCK].size > rndr->max_nesting)
+		return;
+
+	while (beg < size) {
+		txt_data = data + beg;
+		end = size - beg;
+
+		if (is_atxheader(rndr, txt_data, end))
+			beg += parse_atxheader(ob, rndr, txt_data, end);
+
+		else if (data[beg] == '<' && rndr->cb.blockhtml &&
+		         (i = parse_htmlblock(ob, rndr, txt_data, end, 1)) != 0)
+			beg += i;
+
+		else if ((i = is_empty(txt_data, end)) != 0)
+			beg += i;
+
+		else if (is_hrule(txt_data, end)) {
+			if (rndr->cb.hrule)
+				rndr->cb.hrule(ob, rndr->opaque);
+
+			while (beg < size && data[beg] != '\n')
+				beg++;
+
+			beg++;
+		}
+
+		else if ((rndr->ext_flags & MKDEXT_FENCED_CODE) != 0 &&
+		         (i = parse_fencedcode(ob, rndr, txt_data, end)) != 0)
+			beg += i;
+
+		else if ((rndr->ext_flags & MKDEXT_TABLES) != 0 &&
+		         (i = parse_table(ob, rndr, txt_data, end)) != 0)
+			beg += i;
+
+		else if (prefix_quote(txt_data, end))
+			beg += parse_blockquote(ob, rndr, txt_data, end);
+
+		else if (prefix_code(txt_data, end))
+			beg += parse_blockcode(ob, rndr, txt_data, end);
+
+		else if (prefix_uli(txt_data, end))
+			beg += parse_list(ob, rndr, txt_data, end, 0);
+
+		else if (prefix_oli(txt_data, end))
+			beg += parse_list(ob, rndr, txt_data, end, MKD_LIST_ORDERED);
+
+		else
+			beg += parse_paragraph(ob, rndr, txt_data, end);
+	}
+}
+
+/*********************
+ * REFERENCE PARSING *
+ *********************/
+
+/* is_ref • returns whether a line is a reference or not */
+static int is_ref(const byte *data, size_t beg, size_t end, size_t *last, LinkRef **refs) {
+	/*  int n; */
+	size_t i = 0;
+	size_t id_offset, id_end;
+	size_t link_offset, link_end;
+	size_t title_offset, title_end;
+	size_t line_end;
+
+	/* up to 3 optional leading spaces */
+	if (beg + 3 >= end) return 0;
+	if (data[beg] == ' ') {
+		i = 1;
+		if (data[beg + 1] == ' ') {
+			i = 2;
+			if (data[beg + 2] == ' ') {
+				i = 3;
+				if (data[beg + 3] == ' ') return 0;
+			}
+		}
+	}
+	i += beg;
+
+	/* id part: anything but a newline between brackets */
+	if (data[i] != '[') return 0;
+	i++;
+	id_offset = i;
+	while (i < end && data[i] != '\n' && data[i] != '\r' && data[i] != ']')
+		i++;
+	if (i >= end || data[i] != ']') return 0;
+	id_end = i;
+
+	/* spacer: colon (space | tab)* newline? (space | tab)* */
+	i++;
+	if (i >= end || data[i] != ':') return 0;
+	i++;
+	while (i < end && data[i] == ' ') i++;
+	if (i < end && (data[i] == '\n' || data[i] == '\r')) {
+		i++;
+		if (i < end && data[i] == '\r' && data[i - 1] == '\n') i++;
+	}
+	while (i < end && data[i] == ' ') i++;
+	if (i >= end) return 0;
+
+	/* link: whitespace-free sequence, optionally between angle brackets */
+	if (data[i] == '<')
+		i++;
+
+	link_offset = i;
+
+	while (i < end && data[i] != ' ' && data[i] != '\n' && data[i] != '\r')
+		i++;
+
+	if (data[i - 1] == '>') link_end = i - 1;
+	else link_end = i;
+
+	/* optional spacer: (space | tab)* (newline | '\'' | '"' | '(' ) */
+	while (i < end && data[i] == ' ') i++;
+	if (i < end && data[i] != '\n' && data[i] != '\r'
+	        && data[i] != '\'' && data[i] != '"' && data[i] != '(')
+		return 0;
+	line_end = 0;
+	/* computing end-of-line */
+	if (i >= end || data[i] == '\r' || data[i] == '\n') line_end = i;
+	if (i + 1 < end && data[i] == '\n' && data[i + 1] == '\r')
+		line_end = i + 1;
+
+	/* optional (space|tab)* spacer after a newline */
+	if (line_end) {
+		i = line_end + 1;
+		while (i < end && data[i] == ' ') i++;
+	}
+
+	/* optional title: any non-newline sequence enclosed in '"()
+	                alone on its line */
+	title_offset = title_end = 0;
+	if (i + 1 < end
+	        && (data[i] == '\'' || data[i] == '"' || data[i] == '(')) {
+		i++;
+		title_offset = i;
+		/* looking for EOL */
+		while (i < end && data[i] != '\n' && data[i] != '\r') i++;
+		if (i + 1 < end && data[i] == '\n' && data[i + 1] == '\r')
+			title_end = i + 1;
+		else    title_end = i;
+		/* stepping back */
+		i -= 1;
+		while (i > title_offset && data[i] == ' ')
+			i -= 1;
+		if (i > title_offset
+		        && (data[i] == '\'' || data[i] == '"' || data[i] == ')')) {
+			line_end = title_end;
+			title_end = i;
+		}
+	}
+
+	if (!line_end || link_end == link_offset)
+		return 0; /* garbage after the link empty link */
+
+	/* a valid ref has been found, filling-in return structures */
+	if (last)
+		*last = line_end;
+
+	if (refs) {
+		LinkRef *ref;
+
+		ref = add_link_ref(refs, data + id_offset, id_end - id_offset);
+		if (!ref)
+			return 0;
+
+		ref->link = bufnew(link_end - link_offset);
+		bufput(ref->link, data + link_offset, link_end - link_offset);
+
+		if (title_end > title_offset) {
+			ref->title = bufnew(title_end - title_offset);
+			bufput(ref->title, data + title_offset, title_end - title_offset);
+		}
+	}
+
+	return 1;
+}
+
+static void expand_tabs(DataBuffer *ob, const byte *line, size_t size) {
+	size_t  i = 0, tab = 0;
+
+	while (i < size) {
+		size_t org = i;
+
+		while (i < size && line[i] != '\t') {
+			i++;
+			tab++;
+		}
+
+		if (i > org)
+			bufput(ob, line + org, i - org);
+
+		if (i >= size)
+			break;
+
+		do {
+			bufputc(ob, ' ');
+			tab++;
+		} while (tab % 4);
+
+		i++;
+	}
+}
+
+/**********************
+ * EXPORTED FUNCTIONS *
+ **********************/
+
+SDMarkdown *sd_markdown_new(uint extensions, size_t max_nesting, const SDCallbacks *callbacks, void *opaque) {
+	SDMarkdown *md = NULL;
+
+	assert(max_nesting > 0 && callbacks);
+
+	md = (SDMarkdown *)malloc(sizeof(SDMarkdown));
+	if (!md)
+		return NULL;
+
+	memcpy(&md->cb, callbacks, sizeof(SDCallbacks));
+
+	stack_init(&md->work_bufs[BUFFER_BLOCK], 4);
+	stack_init(&md->work_bufs[BUFFER_SPAN], 8);
+
+	memset(md->active_char, 0x0, 256);
+
+	if (md->cb.emphasis || md->cb.double_emphasis || md->cb.triple_emphasis) {
+		md->active_char[(int)'*'] = MD_CHAR_EMPHASIS;
+		md->active_char[(int)'_'] = MD_CHAR_EMPHASIS;
+		if (extensions & MKDEXT_STRIKETHROUGH)
+			md->active_char[(int)'~'] = MD_CHAR_EMPHASIS;
+	}
+
+	if (md->cb.codespan)
+		md->active_char[(int)'`'] = MD_CHAR_CODESPAN;
+
+	if (md->cb.linebreak)
+		md->active_char[(int)'\n'] = MD_CHAR_LINEBREAK;
+
+	if (md->cb.image || md->cb.link)
+		md->active_char[(int)'['] = MD_CHAR_LINK;
+
+	md->active_char[(int)'<'] = MD_CHAR_LANGLE;
+	md->active_char[(int)'\\'] = MD_CHAR_ESCAPE;
+	md->active_char[(int)'&'] = MD_CHAR_ENTITITY;
+
+	if (extensions & MKDEXT_AUTOLINK) {
+		md->active_char[(int)':'] = MD_CHAR_AUTOLINK_URL;
+		md->active_char[(int)'@'] = MD_CHAR_AUTOLINK_EMAIL;
+		md->active_char[(int)'w'] = MD_CHAR_AUTOLINK_WWW;
+	}
+
+	if (extensions & MKDEXT_SUPERSCRIPT)
+		md->active_char[(int)'^'] = MD_CHAR_SUPERSCRIPT;
+
+	/* Extension data */
+	md->ext_flags = extensions;
+	md->opaque = opaque;
+	md->max_nesting = max_nesting;
+	md->in_link_body = 0;
+
+	return md;
+}
+
+void sd_markdown_render(DataBuffer *ob, const byte *document, size_t doc_size, SDMarkdown *md) {
+#define MARKDOWN_GROW(x) ((x) + ((x) >> 1))
+	static const byte UTF8_BOM[] = {0xEF, 0xBB, 0xBF};
+
+	DataBuffer *text;
+	size_t beg, end;
+
+	text = bufnew(64);
+	if (!text)
+		return;
+
+	/* Preallocate enough space for our buffer to avoid expanding while copying */
+	bufgrow(text, doc_size);
+
+	/* reset the references table */
+	memset(&md->refs, 0x0, REF_TABLE_SIZE * sizeof(void *));
+
+	/* first pass: looking for references, copying everything else */
+	beg = 0;
+
+	/* Skip a possible UTF-8 BOM, even though the Unicode standard
+	 * discourages having these in UTF-8 documents */
+	if (doc_size >= 3 && memcmp(document, UTF8_BOM, 3) == 0)
+		beg += 3;
+
+	while (beg < doc_size) /* iterating over lines */
+		if (is_ref(document, beg, doc_size, &end, md->refs))
+			beg = end;
+		else { /* skipping to the next line */
+			end = beg;
+			while (end < doc_size && document[end] != '\n' && document[end] != '\r')
+				end++;
+
+			/* adding the line body if present */
+			if (end > beg)
+				expand_tabs(text, document + beg, end - beg);
+
+			while (end < doc_size && (document[end] == '\n' || document[end] == '\r')) {
+				/* add one \n per newline */
+				if (document[end] == '\n' || (end + 1 < doc_size && document[end + 1] != '\n'))
+					bufputc(text, '\n');
+				end++;
+			}
+
+			beg = end;
+		}
+
+	/* pre-grow the output buffer to minimize allocations */
+	bufgrow(ob, MARKDOWN_GROW(text->size));
+
+	/* second pass: actual rendering */
+	if (md->cb.doc_header)
+		md->cb.doc_header(ob, md->opaque);
+
+	if (text->size) {
+		/* adding a final newline if not already present */
+		if (text->data[text->size - 1] != '\n' &&  text->data[text->size - 1] != '\r')
+			bufputc(text, '\n');
+
+		parse_block(ob, md, text->data, text->size);
+	}
+
+	if (md->cb.doc_footer)
+		md->cb.doc_footer(ob, md->opaque);
+
+	/* clean-up */
+	bufrelease(text);
+	free_link_refs(md->refs);
+
+	assert(md->work_bufs[BUFFER_SPAN].size == 0);
+	assert(md->work_bufs[BUFFER_BLOCK].size == 0);
+}
+
+void
+sd_markdown_free(SDMarkdown *md) {
+	size_t i;
+
+	for (i = 0; i < (size_t)md->work_bufs[BUFFER_SPAN].asize; ++i)
+		bufrelease((DataBuffer *)md->work_bufs[BUFFER_SPAN].item[i]);
+
+	for (i = 0; i < (size_t)md->work_bufs[BUFFER_BLOCK].asize; ++i)
+		bufrelease((DataBuffer *)md->work_bufs[BUFFER_BLOCK].item[i]);
+
+	stack_free(&md->work_bufs[BUFFER_SPAN]);
+	stack_free(&md->work_bufs[BUFFER_BLOCK]);
+
+	free(md);
+}
+
+void
+sd_version(int *ver_major, int *ver_minor, int *ver_revision) {
+	*ver_major = SUNDOWN_VER_MAJOR;
+	*ver_minor = SUNDOWN_VER_MINOR;
+	*ver_revision = SUNDOWN_VER_REVISION;
+}
+
+
+// autolink.h
+
+int sd_autolink_issafe(const byte *link, size_t link_len) {
+	static const size_t valid_uris_count = 5;
+	static const char *valid_uris[] = {
+		"/", "http://", "https://", "ftp://", "mailto:"
+	};
+
+	size_t i;
+
+	for (i = 0; i < valid_uris_count; ++i) {
+		size_t len = strlen(valid_uris[i]);
+
+		if (link_len > len &&
+		        scumm_strnicmp((const char *)link, valid_uris[i], len) == 0 &&
+		        Common::isAlnum(link[len]))
+			return 1;
+	}
+
+	return 0;
+}
+
+static size_t autolink_delim(byte *data, size_t link_end, size_t max_rewind, size_t size) {
+	byte cclose, copen = 0;
+	size_t i;
+
+	for (i = 0; i < link_end; ++i)
+		if (data[i] == '<') {
+			link_end = i;
+			break;
+		}
+
+	while (link_end > 0) {
+		if (strchr("?!.,", data[link_end - 1]) != NULL)
+			link_end--;
+
+		else if (data[link_end - 1] == ';') {
+			size_t new_end = link_end - 2;
+
+			while (new_end > 0 && Common::isAlpha(data[new_end]))
+				new_end--;
+
+			if (new_end < link_end - 2 && data[new_end] == '&')
+				link_end = new_end;
+			else
+				link_end--;
+		} else break;
+	}
+
+	if (link_end == 0)
+		return 0;
+
+	cclose = data[link_end - 1];
+
+	switch (cclose) {
+	case '"':
+		copen = '"';
+		break;
+	case '\'':
+		copen = '\'';
+		break;
+	case ')':
+		copen = '(';
+		break;
+	case ']':
+		copen = '[';
+		break;
+	case '}':
+		copen = '{';
+		break;
+	}
+
+	if (copen != 0) {
+		size_t closing = 0;
+		size_t opening = 0;
+		size_t ii = 0;
+
+		/* Try to close the final punctuation sign in this same line;
+		 * if we managed to close it outside of the URL, that means that it's
+		 * not part of the URL. If it closes inside the URL, that means it
+		 * is part of the URL.
+		 *
+		 * Examples:
+		 *
+		 *  foo http://www.pokemon.com/Pikachu_(Electric) bar
+		 *      => http://www.pokemon.com/Pikachu_(Electric)
+		 *
+		 *  foo (http://www.pokemon.com/Pikachu_(Electric)) bar
+		 *      => http://www.pokemon.com/Pikachu_(Electric)
+		 *
+		 *  foo http://www.pokemon.com/Pikachu_(Electric)) bar
+		 *      => http://www.pokemon.com/Pikachu_(Electric))
+		 *
+		 *  (foo http://www.pokemon.com/Pikachu_(Electric)) bar
+		 *      => foo http://www.pokemon.com/Pikachu_(Electric)
+		 */
+
+		while (ii < link_end) {
+			if (data[ii] == copen)
+				opening++;
+			else if (data[ii] == cclose)
+				closing++;
+
+			ii++;
+		}
+
+		if (closing != opening)
+			link_end--;
+	}
+
+	return link_end;
+}
+
+static size_t check_domain(byte *data, size_t size, int allow_short) {
+	size_t i, np = 0;
+
+	if (!Common::isAlnum(data[0]))
+		return 0;
+
+	for (i = 1; i < size - 1; ++i) {
+		if (data[i] == '.') np++;
+		else if (!Common::isAlnum(data[i]) && data[i] != '-') break;
+	}
+
+	if (allow_short) {
+		/* We don't need a valid domain in the strict sense (with
+		 * least one dot; so just make sure it's composed of valid
+		 * domain characters and return the length of the the valid
+		 * sequence. */
+		return i;
+	} else {
+		/* a valid domain needs to have at least a dot.
+		 * that's as far as we get */
+		return np ? i : 0;
+	}
+}
+
+size_t sd_autolink__www(size_t *rewind_p, DataBuffer *link, byte *data, size_t max_rewind, size_t size, uint flags) {
+	size_t link_end;
+
+	if (max_rewind > 0 && !Common::isPunct(data[-1]) && !Common::isSpace(data[-1]))
+		return 0;
+
+	if (size < 4 || memcmp(data, "www.", strlen("www.")) != 0)
+		return 0;
+
+	link_end = check_domain(data, size, 0);
+
+	if (link_end == 0)
+		return 0;
+
+	while (link_end < size && !Common::isSpace(data[link_end]))
+		link_end++;
+
+	link_end = autolink_delim(data, link_end, max_rewind, size);
+
+	if (link_end == 0)
+		return 0;
+
+	bufput(link, data, link_end);
+	*rewind_p = 0;
+
+	return (int)link_end;
+}
+
+size_t sd_autolink__email(size_t *rewind_p, DataBuffer *link, byte *data, size_t max_rewind, size_t size, uint flags) {
+	size_t link_end, rewind;
+	int nb = 0, np = 0;
+
+	for (rewind = 0; rewind < max_rewind; ++rewind) {
+		byte c = data[-rewind - 1];
+
+		if (Common::isAlnum(c))
+			continue;
+
+		if (strchr(".+-_", c) != NULL)
+			continue;
+
+		break;
+	}
+
+	if (rewind == 0)
+		return 0;
+
+	for (link_end = 0; link_end < size; ++link_end) {
+		byte c = data[link_end];
+
+		if (Common::isAlnum(c))
+			continue;
+
+		if (c == '@')
+			nb++;
+		else if (c == '.' && link_end < size - 1)
+			np++;
+		else if (c != '-' && c != '_')
+			break;
+	}
+
+	if (link_end < 2 || nb != 1 || np == 0 ||
+	        !Common::isAlpha(data[link_end - 1]))
+		return 0;
+
+	link_end = autolink_delim(data, link_end, max_rewind, size);
+
+	if (link_end == 0)
+		return 0;
+
+	bufput(link, data - rewind, link_end + rewind);
+	*rewind_p = rewind;
+
+	return link_end;
+}
+
+size_t sd_autolink__url(size_t *rewind_p, DataBuffer *link, byte *data, size_t max_rewind, size_t size, uint flags) {
+	size_t link_end, rewind = 0, domain_len;
+
+	if (size < 4 || data[1] != '/' || data[2] != '/')
+		return 0;
+
+	while (rewind < max_rewind && Common::isAlpha(data[-rewind - 1]))
+		rewind++;
+
+	if (!sd_autolink_issafe(data - rewind, size + rewind))
+		return 0;
+
+	link_end = strlen("://");
+
+	domain_len = check_domain(
+	                 data + link_end,
+	                 size - link_end,
+	                 flags & SD_AUTOLINK_SHORT_DOMAINS);
+
+	if (domain_len == 0)
+		return 0;
+
+	link_end += domain_len;
+	while (link_end < size && !Common::isSpace(data[link_end]))
+		link_end++;
+
+	link_end = autolink_delim(data, link_end, max_rewind, size);
+
+	if (link_end == 0)
+		return 0;
+
+	bufput(link, data - rewind, link_end + rewind);
+	*rewind_p = rewind;
+
+	return link_end;
+}
+
+// buffer.c
+
+#define BUFFER_MAX_ALLOC_SIZE (1024 * 1024 * 16) //16mb
+
+int bufprefix(const DataBuffer *buf, const char *prefix) {
+	size_t i;
+	assert(buf && buf->unit);
+
+	for (i = 0; i < buf->size; ++i) {
+		if (prefix[i] == 0)
+			return 0;
+
+		if (buf->data[i] != prefix[i])
+			return buf->data[i] - prefix[i];
+	}
+
+	return 0;
+}
+
+/* bufgrow: increasing the allocated size to the given value */
+int bufgrow(DataBuffer *buf, size_t neosz) {
+	size_t neoasz;
+	byte *neodata;
+
+	assert(buf && buf->unit);
+
+	if (neosz > BUFFER_MAX_ALLOC_SIZE)
+		return BUF_ENOMEM;
+
+	if (buf->asize >= neosz)
+		return BUF_OK;
+
+	neoasz = buf->asize + buf->unit;
+	while (neoasz < neosz)
+		neoasz += buf->unit;
+
+	neodata = (byte *)realloc(buf->data, neoasz);
+	if (!neodata)
+		return BUF_ENOMEM;
+
+	buf->data = neodata;
+	buf->asize = neoasz;
+	return BUF_OK;
+}
+
+/* bufnew: allocation of a new buffer */
+DataBuffer *bufnew(size_t unit) {
+	DataBuffer *ret = (DataBuffer *)malloc(sizeof(DataBuffer));
+
+	if (ret) {
+		ret->data = 0;
+		ret->size = ret->asize = 0;
+		ret->unit = unit;
+	}
+	return ret;
+}
+
+/* bufnullterm: NULL-termination of the string array */
+const char *bufcstr(DataBuffer *buf) {
+	assert(buf && buf->unit);
+
+	if (buf->size < buf->asize && buf->data[buf->size] == 0)
+		return (char *)buf->data;
+
+	if (buf->size + 1 <= buf->asize || bufgrow(buf, buf->size + 1) == 0) {
+		buf->data[buf->size] = 0;
+		return (char *)buf->data;
+	}
+
+	return NULL;
+}
+
+/* bufprintf: formatted printing to a buffer */
+void bufprintf(DataBuffer *buf, const char *fmt, ...) {
+	va_list ap;
+	int n;
+
+	assert(buf && buf->unit);
+
+	if (buf->size >= buf->asize && bufgrow(buf, buf->size + 1) < 0)
+		return;
+
+	va_start(ap, fmt);
+	n = vsnprintf((char *)buf->data + buf->size, buf->asize - buf->size, fmt, ap);
+	va_end(ap);
+
+	if (n < 0) {
+#ifdef _MSC_VER
+		va_start(ap, fmt);
+		n = _vscprintf(fmt, ap);
+		va_end(ap);
+#else
+		return;
+#endif
+	}
+
+	if ((size_t)n >= buf->asize - buf->size) {
+		if (bufgrow(buf, buf->size + n + 1) < 0)
+			return;
+
+		va_start(ap, fmt);
+		n = vsnprintf((char *)buf->data + buf->size, buf->asize - buf->size, fmt, ap);
+		va_end(ap);
+	}
+
+	if (n < 0)
+		return;
+
+	buf->size += n;
+}
+
+/* bufput: appends raw data to a buffer */
+void bufput(DataBuffer *buf, const void *data, size_t len) {
+	assert(buf && buf->unit);
+
+	if (buf->size + len > buf->asize && bufgrow(buf, buf->size + len) < 0)
+		return;
+
+	memcpy(buf->data + buf->size, data, len);
+	buf->size += len;
+}
+
+/* bufputs: appends a NUL-terminated string to a buffer */
+void bufputs(DataBuffer *buf, const char *str) {
+	bufput(buf, str, strlen(str));
+}
+
+/* bufputc: appends a single byte to a buffer */
+void bufputc(DataBuffer *buf, int c) {
+	assert(buf && buf->unit);
+
+	if (buf->size + 1 > buf->asize && bufgrow(buf, buf->size + 1) < 0)
+		return;
+
+	buf->data[buf->size] = c;
+	buf->size += 1;
+}
+
+/* bufrelease: decrease the reference count and free the buffer if needed */
+void bufrelease(DataBuffer *buf) {
+	if (!buf)
+		return;
+
+	free(buf->data);
+	free(buf);
+}
+
+/* bufreset: frees internal data of the buffer */
+void bufreset(DataBuffer *buf) {
+	if (!buf)
+		return;
+
+	free(buf->data);
+	buf->data = NULL;
+	buf->size = buf->asize = 0;
+}
+
+/* bufslurp: removes a given number of bytes from the head of the array */
+void bufslurp(DataBuffer *buf, size_t len) {
+	assert(buf && buf->unit);
+
+	if (len >= buf->size) {
+		buf->size = 0;
+		return;
+	}
+
+	buf->size -= len;
+	memmove(buf->data, buf->data + len, buf->size);
+}
+
+// stack.c
+
+int stack_grow(SDStack *st, size_t new_size) {
+	void **new_st;
+
+	if (st->asize >= new_size)
+		return 0;
+
+	new_st = (void **)realloc(st->item, new_size * sizeof(void *));
+	if (new_st == NULL)
+		return -1;
+
+	memset(new_st + st->asize, 0x0,
+	       (new_size - st->asize) * sizeof(void *));
+
+	st->item = new_st;
+	st->asize = new_size;
+
+	if (st->size > new_size)
+		st->size = new_size;
+
+	return 0;
+}
+
+void stack_free(SDStack *st) {
+	if (!st)
+		return;
+
+	free(st->item);
+
+	st->item = NULL;
+	st->size = 0;
+	st->asize = 0;
+}
+
+int stack_init(SDStack *st, size_t initial_size) {
+	st->item = NULL;
+	st->size = 0;
+	st->asize = 0;
+
+	if (!initial_size)
+		initial_size = 8;
+
+	return stack_grow(st, initial_size);
+}
+
+void *stack_pop(SDStack *st) {
+	if (!st->size)
+		return NULL;
+
+	return st->item[--st->size];
+}
+
+int stack_push(SDStack *st, void *item) {
+	if (stack_grow(st, st->size * 2) < 0)
+		return -1;
+
+	st->item[st->size++] = item;
+	return 0;
+}
+
+void *stack_top(SDStack *st) {
+	if (!st->size)
+		return NULL;
+
+	return st->item[st->size - 1];
+}
+
+// html_blocks.h
+/* C code produced by gperf version 3.0.3 */
+/* Command-line: gperf -N find_block_tag -H hash_block_tag -C -c -E --ignore-case html_block_names.txt  */
+/* Computed positions: -k'1-2' */
+
+#if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \
+      && ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \
+      && (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \
+      && ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \
+      && ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \
+      && ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \
+      && ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \
+      && ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \
+      && ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \
+      && ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \
+      && ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \
+      && ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \
+      && ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \
+      && ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \
+      && ('Z' == 90) && ('[' == 91) && ('\\' == 92) && (']' == 93) \
+      && ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \
+      && ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \
+      && ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \
+      && ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \
+      && ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \
+      && ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \
+      && ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \
+      && ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126))
+/* The character set is not based on ISO-646.  */
+error "gperf generated tables don't work with this execution character set. Please report a bug to <bug-gnu-gperf at gnu.org>."
+#endif
+
+/* maximum key range = 37, duplicates = 0 */
+
+#ifndef GPERF_DOWNCASE
+#define GPERF_DOWNCASE 1
+static byte gperf_downcase[256] = {
+	0,   1,   2,   3,   4,   5,   6,   7,   8,   9,  10,  11,  12,  13,  14,
+	15,  16,  17,  18,  19,  20,  21,  22,  23,  24,  25,  26,  27,  28,  29,
+	30,  31,  32,  33,  34,  35,  36,  37,  38,  39,  40,  41,  42,  43,  44,
+	45,  46,  47,  48,  49,  50,  51,  52,  53,  54,  55,  56,  57,  58,  59,
+	60,  61,  62,  63,  64,  97,  98,  99, 100, 101, 102, 103, 104, 105, 106,
+	107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121,
+	122,  91,  92,  93,  94,  95,  96,  97,  98,  99, 100, 101, 102, 103, 104,
+	105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
+	120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134,
+	135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149,
+	150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164,
+	165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179,
+	180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194,
+	195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209,
+	210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224,
+	225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
+	240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254,
+	255
+};
+#endif
+
+#ifndef GPERF_CASE_STRNCMP
+#define GPERF_CASE_STRNCMP 1
+static int gperf_case_strncmp(const char *s1, const char *s2, uint n) {
+	for (; n > 0;) {
+		byte c1 = gperf_downcase[(byte) * s1++];
+		byte c2 = gperf_downcase[(byte) * s2++];
+		if (c1 != 0 && c1 == c2) {
+			n--;
+			continue;
+		}
+		return (int)c1 - (int)c2;
+	}
+	return 0;
+}
+#endif
+
+inline static uint hash_block_tag(const char *str, uint len) {
+	static const byte asso_values[] = {
+		38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+		38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+		38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+		38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+		38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+		8, 30, 25, 20, 15, 10, 38, 38, 38, 38,
+		38, 38, 38, 38, 38, 38,  0, 38,  0, 38,
+		5,  5,  5, 15,  0, 38, 38,  0, 15, 10,
+		0, 38, 38, 15,  0,  5, 38, 38, 38, 38,
+		38, 38, 38, 38, 38, 38, 38, 38,  0, 38,
+		0, 38,  5,  5,  5, 15,  0, 38, 38,  0,
+		15, 10,  0, 38, 38, 15,  0,  5, 38, 38,
+		38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+		38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+		38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+		38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+		38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+		38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+		38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+		38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+		38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+		38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+		38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+		38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+		38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+		38, 38, 38, 38, 38, 38, 38
+	};
+	int hval = len;
+
+	switch (hval) {
+	default:
+		hval += asso_values[(byte)str[1] + 1];
+	/*FALLTHROUGH*/
+	case 1:
+		hval += asso_values[(byte)str[0]];
+		break;
+	}
+	return hval;
+}
+
+inline const char *find_block_tag(const char *str, uint len) {
+	enum {
+		TOTAL_KEYWORDS = 24,
+		MIN_WORD_LENGTH = 1,
+		MAX_WORD_LENGTH = 10,
+		MIN_HASH_VALUE = 1,
+		MAX_HASH_VALUE = 37
+	};
+
+	static const char *const wordlist[] = {
+		"",
+		"p",
+		"dl",
+		"div",
+		"math",
+		"table",
+		"",
+		"ul",
+		"del",
+		"form",
+		"blockquote",
+		"figure",
+		"ol",
+		"fieldset",
+		"",
+		"h1",
+		"",
+		"h6",
+		"pre",
+		"", "",
+		"script",
+		"h5",
+		"noscript",
+		"",
+		"style",
+		"iframe",
+		"h4",
+		"ins",
+		"", "", "",
+		"h3",
+		"", "", "", "",
+		"h2"
+	};
+
+	if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH) {
+		int key = hash_block_tag(str, len);
+
+		if (key <= MAX_HASH_VALUE && key >= 0) {
+			const char *s = wordlist[key];
+
+			if ((((byte)*str ^ (byte)*s) & ~32) == 0 && !gperf_case_strncmp(str, s, len) && s[len] == '\0')
+				return s;
+		}
+	}
+	return 0;
+}
+
+} // End of namespace Common
diff --git a/common/formats/markdown.h b/common/formats/markdown.h
new file mode 100644
index 00000000000..59ddb01b2b2
--- /dev/null
+++ b/common/formats/markdown.h
@@ -0,0 +1,215 @@
+/*
+ * Copyright (c) 2009, Natacha Porté
+ * Copyright (c) 2011, Vicent Marti
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef COMMON_FORMATS_MARKDOWN_H
+#define COMMON_FORMATS_MARKDOWN_H
+
+#include "common/scummsys.h"
+
+namespace Common {
+
+#define SUNDOWN_VERSION "1.16.0"
+#define SUNDOWN_VER_MAJOR 1
+#define SUNDOWN_VER_MINOR 16
+#define SUNDOWN_VER_REVISION 0
+
+/********************
+ * TYPE DEFINITIONS *
+ ********************/
+
+struct DataBuffer;
+struct SDMarkdown;
+
+/* mkd_autolink - type of autolink */
+enum MKDAutolink {
+	MKDA_NOT_AUTOLINK,	/* used internally when it is not an autolink*/
+	MKDA_NORMAL,		/* normal http/http/ftp/mailto/etc link */
+	MKDA_EMAIL,			/* e-mail link without explit mailto: */
+};
+
+enum mkd_tableflags {
+	MKD_TABLE_ALIGN_L = 1,
+	MKD_TABLE_ALIGN_R = 2,
+	MKD_TABLE_ALIGN_CENTER = 3,
+	MKD_TABLE_ALIGNMASK = 3,
+	MKD_TABLE_HEADER = 4
+};
+
+enum mkd_extensions {
+	MKDEXT_NO_INTRA_EMPHASIS = (1 << 0),
+	MKDEXT_TABLES = (1 << 1),
+	MKDEXT_FENCED_CODE = (1 << 2),
+	MKDEXT_AUTOLINK = (1 << 3),
+	MKDEXT_STRIKETHROUGH = (1 << 4),
+	MKDEXT_SPACE_HEADERS = (1 << 6),
+	MKDEXT_SUPERSCRIPT = (1 << 7),
+	MKDEXT_LAX_SPACING = (1 << 8),
+};
+
+/* sd_callbacks - functions for rendering parsed data */
+struct SDCallbacks {
+	/* block level callbacks - NULL skips the block */
+	void (*blockcode)(DataBuffer *ob, const DataBuffer *text, const DataBuffer *lang, void *opaque);
+	void (*blockquote)(DataBuffer *ob, const DataBuffer *text, void *opaque);
+	void (*blockhtml)(DataBuffer *ob,const  DataBuffer *text, void *opaque);
+	void (*header)(DataBuffer *ob, const DataBuffer *text, int level, void *opaque);
+	void (*hrule)(DataBuffer *ob, void *opaque);
+	void (*list)(DataBuffer *ob, const DataBuffer *text, int flags, void *opaque);
+	void (*listitem)(DataBuffer *ob, const DataBuffer *text, int flags, void *opaque);
+	void (*paragraph)(DataBuffer *ob, const DataBuffer *text, void *opaque);
+	void (*table)(DataBuffer *ob, const DataBuffer *header, const DataBuffer *body, void *opaque);
+	void (*table_row)(DataBuffer *ob, const DataBuffer *text, void *opaque);
+	void (*table_cell)(DataBuffer *ob, const DataBuffer *text, int flags, void *opaque);
+
+
+	/* span level callbacks - NULL or return 0 prints the span verbatim */
+	int (*autolink)(DataBuffer *ob, const DataBuffer *link, MKDAutolink type, void *opaque);
+	int (*codespan)(DataBuffer *ob, const DataBuffer *text, void *opaque);
+	int (*double_emphasis)(DataBuffer *ob, const DataBuffer *text, void *opaque);
+	int (*emphasis)(DataBuffer *ob, const DataBuffer *text, void *opaque);
+	int (*image)(DataBuffer *ob, const DataBuffer *link, const DataBuffer *title, const DataBuffer *alt, void *opaque);
+	int (*linebreak)(DataBuffer *ob, void *opaque);
+	int (*link)(DataBuffer *ob, const DataBuffer *link, const DataBuffer *title, const DataBuffer *content, void *opaque);
+	int (*raw_html_tag)(DataBuffer *ob, const DataBuffer *tag, void *opaque);
+	int (*triple_emphasis)(DataBuffer *ob, const DataBuffer *text, void *opaque);
+	int (*strikethrough)(DataBuffer *ob, const DataBuffer *text, void *opaque);
+	int (*superscript)(DataBuffer *ob, const DataBuffer *text, void *opaque);
+
+	/* low level callbacks - NULL copies input directly into the output */
+	void (*entity)(DataBuffer *ob, const DataBuffer *entity, void *opaque);
+	void (*normal_text)(DataBuffer *ob, const DataBuffer *text, void *opaque);
+
+	/* header and footer */
+	void (*doc_header)(DataBuffer *ob, void *opaque);
+	void (*doc_footer)(DataBuffer *ob, void *opaque);
+};
+
+/*********
+ * FLAGS *
+ *********/
+
+/* list/listitem flags */
+#define MKD_LIST_ORDERED	1
+#define MKD_LI_BLOCK		2  /* <li> containing block data */
+
+SDMarkdown *sd_markdown_new(int extensions, size_t max_nesting, const SDCallbacks *callbacks, void *opaque);
+
+void sd_markdown_render(DataBuffer *ob, const byte *document, size_t doc_size, SDMarkdown *md);
+
+void sd_markdown_free(SDMarkdown *md);
+
+void sd_version(int *major, int *minor, int *revision);
+
+// buffer.h
+
+enum buferror_t {
+	BUF_OK = 0,
+	BUF_ENOMEM = -1,
+};
+
+/* DataBuffer: character array buffer */
+struct DataBuffer {
+	byte *data;		/* actual character data */
+	size_t size;	/* size of the string */
+	size_t asize;	/* allocated size (0 = volatile buffer) */
+	size_t unit;	/* reallocation unit size (0 = read-only buffer) */
+};
+
+/* CONST_BUF: global buffer from a string litteral */
+#define BUF_STATIC(string) \
+	{ (byte *)string, sizeof string -1, sizeof string, 0, 0 }
+
+/* VOLATILE_BUF: macro for creating a volatile buffer on the stack */
+#define BUF_VOLATILE(strname) \
+	{ (byte *)strname, strlen(strname), 0, 0, 0 }
+
+/* BUFPUTSL: optimized bufputs of a string litteral */
+#define BUFPUTSL(output, literal) \
+	bufput(output, literal, sizeof literal - 1)
+
+/* bufgrow: increasing the allocated size to the given value */
+int bufgrow(DataBuffer *, size_t);
+
+/* bufnew: allocation of a new buffer */
+DataBuffer *bufnew(size_t);
+
+/* bufnullterm: NUL-termination of the string array (making a C-string) */
+const char *bufcstr(DataBuffer *);
+
+/* bufprefix: compare the beginning of a buffer with a string */
+int bufprefix(const DataBuffer *buf, const char *prefix);
+
+/* bufput: appends raw data to a buffer */
+void bufput(DataBuffer *, const void *, size_t);
+
+/* bufputs: appends a NUL-terminated string to a buffer */
+void bufputs(DataBuffer *, const char *);
+
+/* bufputc: appends a single char to a buffer */
+void bufputc(DataBuffer *, int);
+
+/* bufrelease: decrease the reference count and free the buffer if needed */
+void bufrelease(DataBuffer *);
+
+/* bufreset: frees internal data of the buffer */
+void bufreset(DataBuffer *);
+
+/* bufslurp: removes a given number of bytes from the head of the array */
+void bufslurp(DataBuffer *, size_t);
+
+/* bufprintf: formatted printing to a buffer */
+void bufprintf(DataBuffer *, const char *, ...);
+
+
+// autolink.h
+enum {
+	SD_AUTOLINK_SHORT_DOMAINS = (1 << 0),
+};
+
+int sd_autolink_issafe(const byte *link, size_t link_len);
+
+size_t sd_autolink__www(size_t *rewind_p, DataBuffer *link,
+	byte *data, size_t offset, size_t size, uint flags);
+
+size_t sd_autolink__email(size_t *rewind_p, DataBuffer *link,
+	byte *data, size_t offset, size_t size, uint flags);
+
+size_t sd_autolink__url(size_t *rewind_p, DataBuffer *link,
+	byte *data, size_t offset, size_t size, uint flags);
+
+
+// stack.h
+
+struct SDStack {
+	void **item;
+	size_t size;
+	size_t asize;
+};
+
+void stack_free(SDStack *);
+int stack_grow(SDStack *, size_t);
+int stack_init(SDStack *, size_t);
+
+int stack_push(SDStack *, void *);
+
+void *stack_pop(SDStack *);
+void *stack_top(SDStack *);
+
+
+} // end of namespace Common
+
+#endif // COMMON_FORMATS_MARKDOWN_H
diff --git a/common/formats/module.mk b/common/formats/module.mk
index 9537f8def77..d653c43c9ef 100644
--- a/common/formats/module.mk
+++ b/common/formats/module.mk
@@ -5,6 +5,7 @@ MODULE_OBJS := \
 	iff_container.o \
 	ini-file.o \
 	json.o \
+	markdown.o \
 	prodos.o \
 	quicktime.o \
 	winexe.o \


Commit: ce04c4b309fcd486642f14bef802697402b82028
    https://github.com/scummvm/scummvm/commit/ce04c4b309fcd486642f14bef802697402b82028
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: Initial stub for MacText::setMarkdownText()

Changed paths:
  A graphics/macgui/mactext-md.cpp
    graphics/macgui/mactext.h
    graphics/module.mk


diff --git a/graphics/macgui/mactext-md.cpp b/graphics/macgui/mactext-md.cpp
new file mode 100644
index 00000000000..7402ad6dcb4
--- /dev/null
+++ b/graphics/macgui/mactext-md.cpp
@@ -0,0 +1,163 @@
+/* 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 "common/formats/markdown.h"
+#include "graphics/macgui/mactext.h"
+
+namespace Graphics {
+
+void render_blockcode(Common::DataBuffer *ob, const Common::DataBuffer *text, const Common::DataBuffer *lang, void *opaque) {
+}
+
+void render_blockquote(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
+}
+
+void render_blockhtml(Common::DataBuffer *ob,const  Common::DataBuffer *text, void *opaque) {
+}
+
+void render_header(Common::DataBuffer *ob, const Common::DataBuffer *text, int level, void *opaque) {
+}
+
+void render_hrule(Common::DataBuffer *ob, void *opaque) {
+}
+
+void render_list(Common::DataBuffer *ob, const Common::DataBuffer *text, int flags, void *opaque) {
+}
+
+void render_listitem(Common::DataBuffer *ob, const Common::DataBuffer *text, int flags, void *opaque) {
+}
+
+void render_paragraph(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
+}
+
+void render_table(Common::DataBuffer *ob, const Common::DataBuffer *header, const Common::DataBuffer *body, void *opaque) {
+}
+
+void render_table_row(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
+}
+
+void render_table_cell(Common::DataBuffer *ob, const Common::DataBuffer *text, int flags, void *opaque) {
+}
+
+int render_autolink(Common::DataBuffer *ob, const Common::DataBuffer *link, Common::MKDAutolink type, void *opaque) {
+	return 0;
+}
+
+int render_codespan(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
+	return 0;
+}
+
+int render_double_emphasis(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
+	return 0;
+}
+
+int render_emphasis(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
+	return 0;
+}
+
+int render_image(Common::DataBuffer *ob, const Common::DataBuffer *link, const Common::DataBuffer *title, const Common::DataBuffer *alt, void *opaque) {
+	return 0;
+}
+
+int render_linebreak(Common::DataBuffer *ob, void *opaque) {
+	return 0;
+}
+
+int render_link(Common::DataBuffer *ob, const Common::DataBuffer *link, const Common::DataBuffer *title, const Common::DataBuffer *content, void *opaque) {
+	return 0;
+}
+
+int render_raw_html_tag(Common::DataBuffer *ob, const Common::DataBuffer *tag, void *opaque) {
+	return 0;
+}
+
+int render_triple_emphasis(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
+	return 0;
+}
+
+int render_strikethrough(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
+	return 0;
+}
+
+int render_superscript(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
+	return 0;
+}
+
+void render_entity(Common::DataBuffer *ob, const Common::DataBuffer *entity, void *opaque) {
+}
+
+void render_normal_text(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
+}
+
+void render_doc_header(Common::DataBuffer *ob, void *opaque) {
+}
+
+void render_doc_footer(Common::DataBuffer *ob, void *opaque) {
+}
+
+
+void MacText::setMarkdownText(const Common::U32String &str) {
+
+	const Common::SDCallbacks cb = {
+		/* block level callbacks - NULL skips the block */
+		render_blockcode,
+		render_blockquote,
+		render_blockhtml,
+		render_header,
+		render_hrule,
+		render_list,
+		render_listitem,
+		render_paragraph,
+		render_table,
+		render_table_row,
+		render_table_cell,
+
+
+		/* span level callbacks - NULL or return 0 prints the span verbatim */
+		render_autolink,
+		render_codespan,
+		render_double_emphasis,
+		render_emphasis,
+		render_image,
+		render_linebreak,
+		render_link,
+		render_raw_html_tag,
+		render_triple_emphasis,
+		render_strikethrough,
+		render_superscript,
+
+		/* low level callbacks - NULL copies input directly into the output */
+		render_entity,
+		render_normal_text,
+
+		/* header and footer */
+		render_doc_header,
+		render_doc_footer,
+	};
+
+	Common::String input = str.encode(); // Encode to UTF8
+
+	Common::DataBuffer *ib = Common::bufnew(input.size());
+
+
+}
+
+} // End of namespace Graphics
diff --git a/graphics/macgui/mactext.h b/graphics/macgui/mactext.h
index e36297de58c..aadaef8a7a5 100644
--- a/graphics/macgui/mactext.h
+++ b/graphics/macgui/mactext.h
@@ -289,6 +289,10 @@ public:
 
 	void scroll(int delta);
 
+	// Markdown
+public:
+	void setMarkdownText(const Common::U32String &str);
+
 private:
 	void init();
 	bool isCutAllowed();
diff --git a/graphics/module.mk b/graphics/module.mk
index 32c5fc20eb9..c2a2113896c 100644
--- a/graphics/module.mk
+++ b/graphics/module.mk
@@ -29,6 +29,7 @@ MODULE_OBJS := \
 	macgui/macmenu.o \
 	macgui/macpopupmenu.o \
 	macgui/mactext.o \
+	macgui/mactext-md.o \
 	macgui/mactextwindow.o \
 	macgui/macwidget.o \
 	macgui/macwindow.o \


Commit: f6ffcf3c82b65500917d2a407266244ded7683d1
    https://github.com/scummvm/scummvm/commit/f6ffcf3c82b65500917d2a407266244ded7683d1
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: Implemented stubs for Markdown parser

Changed paths:
    graphics/macgui/mactext-md.cpp


diff --git a/graphics/macgui/mactext-md.cpp b/graphics/macgui/mactext-md.cpp
index 7402ad6dcb4..733daf47c94 100644
--- a/graphics/macgui/mactext-md.cpp
+++ b/graphics/macgui/mactext-md.cpp
@@ -25,92 +25,187 @@
 namespace Graphics {
 
 void render_blockcode(Common::DataBuffer *ob, const Common::DataBuffer *text, const Common::DataBuffer *lang, void *opaque) {
+	if (!text)
+		return;
+
+	warning("render_blockcode(%s)", text->data);
 }
 
 void render_blockquote(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
+	if (!text)
+		return;
+
+	warning("render_blockquote(%s)", text->data);
 }
 
-void render_blockhtml(Common::DataBuffer *ob,const  Common::DataBuffer *text, void *opaque) {
+void render_blockhtml(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
+	if (!text)
+		return;
+
+	warning("render_blockhtml(%s)", text->data);
 }
 
 void render_header(Common::DataBuffer *ob, const Common::DataBuffer *text, int level, void *opaque) {
+	if (!text)
+		return;
+
+	warning("render_header(%s)", text->data);
 }
 
 void render_hrule(Common::DataBuffer *ob, void *opaque) {
+	warning("render_hrule()");
 }
 
 void render_list(Common::DataBuffer *ob, const Common::DataBuffer *text, int flags, void *opaque) {
+	if (!text)
+		return;
+
+	warning("render_list(%s)", text->data);
 }
 
 void render_listitem(Common::DataBuffer *ob, const Common::DataBuffer *text, int flags, void *opaque) {
+	if (!text)
+		return;
+
+	warning("render_listitem(%s)", text->data);
 }
 
 void render_paragraph(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
+	if (!text)
+		return;
+
+	warning("render_paragraph(%s)", text->data);
 }
 
 void render_table(Common::DataBuffer *ob, const Common::DataBuffer *header, const Common::DataBuffer *body, void *opaque) {
+	if (!body)
+		return;
+
+	warning("render_table(%s, %s)", header ? header->data : 0, body->data);
 }
 
 void render_table_row(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
+	if (!text)
+		return;
+
+	warning("render_table_row(%s)", text->data);
 }
 
 void render_table_cell(Common::DataBuffer *ob, const Common::DataBuffer *text, int flags, void *opaque) {
+	if (!text)
+		return;
+
+	warning("render_table_cell(%s)", text->data);
 }
 
 int render_autolink(Common::DataBuffer *ob, const Common::DataBuffer *link, Common::MKDAutolink type, void *opaque) {
+	if (!link)
+		return 0;
+
+	warning("render_autolink(%s)", link->data);
 	return 0;
 }
 
 int render_codespan(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
+	if (!text)
+		return 0;
+
+	warning("render_codespan(%s)", text->data);
 	return 0;
 }
 
 int render_double_emphasis(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
+	if (!text)
+		return 0;
+
+	warning("render_double_emphasis(%s)", text->data);
 	return 0;
 }
 
 int render_emphasis(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
+	if (!text)
+		return 0;
+
+	warning("render_emphasis(%s)", text->data);
 	return 0;
 }
 
 int render_image(Common::DataBuffer *ob, const Common::DataBuffer *link, const Common::DataBuffer *title, const Common::DataBuffer *alt, void *opaque) {
+	if (!link)
+		return 0;
+
+	warning("render_image(%s, %s, %s)", link->data, title ? title->data : 0, alt ? alt->data : 0);
 	return 0;
 }
 
 int render_linebreak(Common::DataBuffer *ob, void *opaque) {
+	warning("render_linebreak()");
 	return 0;
 }
 
 int render_link(Common::DataBuffer *ob, const Common::DataBuffer *link, const Common::DataBuffer *title, const Common::DataBuffer *content, void *opaque) {
+	if (!link)
+		return 0;
+
+	warning("render_link(%s, %s, %s)", link->data, title ? title->data : 0, content ? content->data : 0);
 	return 0;
 }
 
 int render_raw_html_tag(Common::DataBuffer *ob, const Common::DataBuffer *tag, void *opaque) {
+	if (!tag)
+		return 0;
+
+	warning("render_raw_html_tag(%s)", tag->data);
 	return 0;
 }
 
 int render_triple_emphasis(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
+	if (!text)
+		return 0;
+
+	warning("render_triple_emphasis(%s)", text->data);
 	return 0;
 }
 
 int render_strikethrough(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
+	if (!text)
+		return 0;
+
+	warning("render_strikethrough(%s)", text->data);
 	return 0;
 }
 
 int render_superscript(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
+	if (!text)
+		return 0;
+
+	warning("render_superscript(%s)", text->data);
 	return 0;
 }
 
 void render_entity(Common::DataBuffer *ob, const Common::DataBuffer *entity, void *opaque) {
+	if (!entity)
+		return;
+
+	warning("render_entity(%s)", entity->data);
 }
 
 void render_normal_text(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
+	if (!text)
+		return;
+
+	warning("render_normal_text(%s)", text->data);
+
+	Common::bufgrow(ob, text->size);
+	Common::bufputs(ob, (char *)text->data);
 }
 
 void render_doc_header(Common::DataBuffer *ob, void *opaque) {
+	warning("render_doc_header()");
 }
 
 void render_doc_footer(Common::DataBuffer *ob, void *opaque) {
+	warning("render_doc_footer()");
 }
 
 
@@ -156,8 +251,15 @@ void MacText::setMarkdownText(const Common::U32String &str) {
 	Common::String input = str.encode(); // Encode to UTF8
 
 	Common::DataBuffer *ib = Common::bufnew(input.size());
+	memcpy(ib->data, input.c_str(), input.size());
+
+	Common::DataBuffer *ob = Common::bufnew(1024);
 
+	Common::SDMarkdown *md = sd_markdown_new(0, 16, &cb, this);
+	sd_markdown_render(ob, ib->data, ib->size, md);
+	sd_markdown_free(md);
 
+	warning("%zu bytes: %s", ob->size, ob->data);
 }
 
 } // End of namespace Graphics


Commit: 57198473541dacf5b2b35e533c506a5b8ca5b7c3
    https://github.com/scummvm/scummvm/commit/57198473541dacf5b2b35e533c506a5b8ca5b7c3
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: Pass setMarkdownText through MacTextWindow

Changed paths:
    graphics/macgui/mactextwindow.cpp
    graphics/macgui/mactextwindow.h
    gui/widgets/richtext.cpp


diff --git a/graphics/macgui/mactextwindow.cpp b/graphics/macgui/mactextwindow.cpp
index 1012efc21b3..5915a7ac295 100644
--- a/graphics/macgui/mactextwindow.cpp
+++ b/graphics/macgui/mactextwindow.cpp
@@ -173,6 +173,12 @@ void MacTextWindow::appendText(const Common::String &str, const MacFont *macFont
 	appendText(Common::U32String(str), macFont, skipAdd);
 }
 
+void MacTextWindow::setMarkdownText(const Common::U32String &str) {
+	_mactext->setMarkdownText(str);
+
+	_contentIsDirty = true;
+}
+
 void MacTextWindow::clearText() {
 	_mactext->clearText();
 
diff --git a/graphics/macgui/mactextwindow.h b/graphics/macgui/mactextwindow.h
index cc106489308..6c0607bc6ce 100644
--- a/graphics/macgui/mactextwindow.h
+++ b/graphics/macgui/mactextwindow.h
@@ -48,6 +48,7 @@ public:
 	void appendText(const Common::U32String &str, const MacFont *macFont = nullptr, bool skipAdd = false);
 	void appendText(const Common::String &str, const MacFont *macFont = nullptr, bool skipAdd = false);
 	void clearText();
+	void setMarkdownText(const Common::U32String &str);
 
 	void setEditable(bool editable) { _editable = editable; _mactext->setEditable(editable); }
 	void setSelectable(bool selectable) { _selectable = selectable; }
diff --git a/gui/widgets/richtext.cpp b/gui/widgets/richtext.cpp
index 544df42f101..1fa781cd26c 100644
--- a/gui/widgets/richtext.cpp
+++ b/gui/widgets/richtext.cpp
@@ -82,7 +82,7 @@ void RichTextWidget::createWidget() {
 	_txtWnd->setEditable(false);
 	_txtWnd->setSelectable(false);
 
-	_txtWnd->appendText(_text);
+	_txtWnd->setMarkdownText(_text);
 
 	_surface = new Graphics::ManagedSurface(_w, _h, _wm->_pixelformat);
 }


Commit: 85b2b3ce1dceebd6b74487286a4fbef63e4f18eb
    https://github.com/scummvm/scummvm/commit/85b2b3ce1dceebd6b74487286a4fbef63e4f18eb
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
COMMON: Fix function prototype

Changed paths:
    common/formats/markdown.h


diff --git a/common/formats/markdown.h b/common/formats/markdown.h
index 59ddb01b2b2..fccc152cd8a 100644
--- a/common/formats/markdown.h
+++ b/common/formats/markdown.h
@@ -106,7 +106,7 @@ struct SDCallbacks {
 #define MKD_LIST_ORDERED	1
 #define MKD_LI_BLOCK		2  /* <li> containing block data */
 
-SDMarkdown *sd_markdown_new(int extensions, size_t max_nesting, const SDCallbacks *callbacks, void *opaque);
+SDMarkdown *sd_markdown_new(uint extensions, size_t max_nesting, const SDCallbacks *callbacks, void *opaque);
 
 void sd_markdown_render(DataBuffer *ob, const byte *document, size_t doc_size, SDMarkdown *md);
 


Commit: bbc6dbcd1020e9a3ad6029f69228a47593e68957
    https://github.com/scummvm/scummvm/commit/bbc6dbcd1020e9a3ad6029f69228a47593e68957
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: Properly grow buffer for Markdown

Changed paths:
    graphics/macgui/mactext-md.cpp


diff --git a/graphics/macgui/mactext-md.cpp b/graphics/macgui/mactext-md.cpp
index 733daf47c94..521ec383fba 100644
--- a/graphics/macgui/mactext-md.cpp
+++ b/graphics/macgui/mactext-md.cpp
@@ -251,6 +251,7 @@ void MacText::setMarkdownText(const Common::U32String &str) {
 	Common::String input = str.encode(); // Encode to UTF8
 
 	Common::DataBuffer *ib = Common::bufnew(input.size());
+	Common::bufgrow(ib, input.size());
 	memcpy(ib->data, input.c_str(), input.size());
 
 	Common::DataBuffer *ob = Common::bufnew(1024);


Commit: d10f80a8c9149a866abdbb3ecfdf7fc6499e902a
    https://github.com/scummvm/scummvm/commit/d10f80a8c9149a866abdbb3ecfdf7fc6499e902a
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: Implement equivalent of tag opening and closing for MacText

Changed paths:
    graphics/macgui/mactext.cpp


diff --git a/graphics/macgui/mactext.cpp b/graphics/macgui/mactext.cpp
index bf4372f53ff..5189596b28d 100644
--- a/graphics/macgui/mactext.cpp
+++ b/graphics/macgui/mactext.cpp
@@ -715,23 +715,38 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 			} else if (*s == '\016') {	// human-readable format
 				s++;
 
-				uint16 fontId, textSlant, fontSize, palinfo1, palinfo2, palinfo3;
+				if (*s == '-') { // \016-XX  -- closing textSlant
+					uint16 textSlant;
 
-				s = readHex(&fontId, s, 4);
-				s = readHex(&textSlant, s, 2);
-				s = readHex(&fontSize, s, 4);
-				s = readHex(&palinfo1, s, 4);
-				s = readHex(&palinfo2, s, 4);
-				s = readHex(&palinfo3, s, 4);
+					s = readHex(&textSlant, s, 2);
 
-				D(9, "** splitString: fontId: %d, textSlant: %d, fontSize: %d, p0: %x p1: %x p2: %x",
-						fontId, textSlant, fontSize, palinfo1, palinfo2, palinfo3);
+					current_format.textSlant &= ~textSlant; // Clearing the specified byte
+					current_format.textSlant &= ~textSlant; // Clearing the specified bit
+				} else if (*s == '+') { // \016+XX  -- opening textSlant
+					uint16 textSlant;
 
-				current_format.setValues(_wm, fontId, textSlant, fontSize, palinfo1, palinfo2, palinfo3);
+					s = readHex(&textSlant, s, 2);
 
-				// So far, we enforce single font here, though in the future, font size could be altered
-				if (!_macFontMode)
-					current_format.font = _defaultFormatting.font;
+					current_format.textSlant |= textSlant; // Setting the specified bit
+				} else {
+					uint16 fontId, textSlant, fontSize, palinfo1, palinfo2, palinfo3;
+
+					s = readHex(&fontId, s, 4);
+					s = readHex(&textSlant, s, 2);
+					s = readHex(&fontSize, s, 4);
+					s = readHex(&palinfo1, s, 4);
+					s = readHex(&palinfo2, s, 4);
+					s = readHex(&palinfo3, s, 4);
+
+					D(9, "** splitString: fontId: %d, textSlant: %d, fontSize: %d, p0: %x p1: %x p2: %x",
+							fontId, textSlant, fontSize, palinfo1, palinfo2, palinfo3);
+
+					current_format.setValues(_wm, fontId, textSlant, fontSize, palinfo1, palinfo2, palinfo3);
+
+					// So far, we enforce single font here, though in the future, font size could be altered
+					if (!_macFontMode)
+						current_format.font = _defaultFormatting.font;
+				}
 			}
 
 			while (*s && *s != ' ' && *s != '\001') {


Commit: d69a9689cc75685c83f7ee497a2f98d2fa60bd17
    https://github.com/scummvm/scummvm/commit/d69a9689cc75685c83f7ee497a2f98d2fa60bd17
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: fix printing out of debug info in Markdown

Changed paths:
    graphics/macgui/mactext-md.cpp


diff --git a/graphics/macgui/mactext-md.cpp b/graphics/macgui/mactext-md.cpp
index 521ec383fba..22cd05ce95c 100644
--- a/graphics/macgui/mactext-md.cpp
+++ b/graphics/macgui/mactext-md.cpp
@@ -24,32 +24,34 @@
 
 namespace Graphics {
 
+#define PR(x) (x->data ? Common::String((const char *)(x)->data , (x)->size).c_str() : "(null)")
+
 void render_blockcode(Common::DataBuffer *ob, const Common::DataBuffer *text, const Common::DataBuffer *lang, void *opaque) {
 	if (!text)
 		return;
 
-	warning("render_blockcode(%s)", text->data);
+	warning("render_blockcode(%s)", PR(text));
 }
 
 void render_blockquote(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
 	if (!text)
 		return;
 
-	warning("render_blockquote(%s)", text->data);
+	warning("render_blockquote(%s)", PR(text));
 }
 
 void render_blockhtml(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
 	if (!text)
 		return;
 
-	warning("render_blockhtml(%s)", text->data);
+	warning("render_blockhtml(%s)", PR(text));
 }
 
 void render_header(Common::DataBuffer *ob, const Common::DataBuffer *text, int level, void *opaque) {
 	if (!text)
 		return;
 
-	warning("render_header(%s)", text->data);
+	warning("render_header(%s)", PR(text));
 }
 
 void render_hrule(Common::DataBuffer *ob, void *opaque) {
@@ -60,95 +62,99 @@ void render_list(Common::DataBuffer *ob, const Common::DataBuffer *text, int fla
 	if (!text)
 		return;
 
-	warning("render_list(%s)", text->data);
+	warning("render_list(%s)", PR(text));
 }
 
 void render_listitem(Common::DataBuffer *ob, const Common::DataBuffer *text, int flags, void *opaque) {
 	if (!text)
 		return;
 
-	warning("render_listitem(%s)", text->data);
+	warning("render_listitem(%s)", PR(text));
 }
 
 void render_paragraph(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
 	if (!text)
 		return;
 
-	warning("render_paragraph(%s)", text->data);
+	warning("render_paragraph(%s)", PR(text));
 }
 
 void render_table(Common::DataBuffer *ob, const Common::DataBuffer *header, const Common::DataBuffer *body, void *opaque) {
 	if (!body)
 		return;
 
-	warning("render_table(%s, %s)", header ? header->data : 0, body->data);
+	warning("render_table(%s, %s)", header ? PR(header) : 0, PR(body));
 }
 
 void render_table_row(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
 	if (!text)
 		return;
 
-	warning("render_table_row(%s)", text->data);
+	warning("render_table_row(%s)", PR(text));
 }
 
 void render_table_cell(Common::DataBuffer *ob, const Common::DataBuffer *text, int flags, void *opaque) {
 	if (!text)
 		return;
 
-	warning("render_table_cell(%s)", text->data);
+	warning("render_table_cell(%s)", PR(text));
 }
 
 int render_autolink(Common::DataBuffer *ob, const Common::DataBuffer *link, Common::MKDAutolink type, void *opaque) {
 	if (!link)
 		return 0;
 
-	warning("render_autolink(%s)", link->data);
-	return 0;
+	warning("render_autolink(%s)", PR(link));
+	return 1;
 }
 
 int render_codespan(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
 	if (!text)
 		return 0;
 
-	warning("render_codespan(%s)", text->data);
-	return 0;
+	warning("render_codespan(%s)", PR(text));
+	return 1;
 }
 
 int render_double_emphasis(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
 	if (!text)
 		return 0;
 
-	warning("render_double_emphasis(%s)", text->data);
-	return 0;
+	warning("render_double_emphasis(%s)", PR(text));
+
+	bufput(ob, text->data, text->size);
+	return 1;
 }
 
 int render_emphasis(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
 	if (!text)
 		return 0;
 
-	warning("render_emphasis(%s)", text->data);
-	return 0;
+	warning("render_emphasis(%s)", PR(text));
+
+	bufput(ob, text->data, text->size);
+	return 1;
 }
 
 int render_image(Common::DataBuffer *ob, const Common::DataBuffer *link, const Common::DataBuffer *title, const Common::DataBuffer *alt, void *opaque) {
 	if (!link)
 		return 0;
 
-	warning("render_image(%s, %s, %s)", link->data, title ? title->data : 0, alt ? alt->data : 0);
-	return 0;
+	warning("render_image(%s, %s, %s)", PR(link), title ? PR(title) : 0, alt ? PR(alt) : 0);
+	return 1;
 }
 
 int render_linebreak(Common::DataBuffer *ob, void *opaque) {
 	warning("render_linebreak()");
-	return 0;
+	return 1;
 }
 
 int render_link(Common::DataBuffer *ob, const Common::DataBuffer *link, const Common::DataBuffer *title, const Common::DataBuffer *content, void *opaque) {
 	if (!link)
 		return 0;
 
-	warning("render_link(%s, %s, %s)", link->data, title ? title->data : 0, content ? content->data : 0);
-	return 0;
+	warning("render_link(%s, %s, %s)", PR(link), title ? PR(title) : 0, content ? PR(content) : 0);
+	return 1;
 }
 
 int render_raw_html_tag(Common::DataBuffer *ob, const Common::DataBuffer *tag, void *opaque) {
@@ -156,59 +162,33 @@ int render_raw_html_tag(Common::DataBuffer *ob, const Common::DataBuffer *tag, v
 		return 0;
 
 	warning("render_raw_html_tag(%s)", tag->data);
-	return 0;
+	return 1;
 }
 
 int render_triple_emphasis(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
 	if (!text)
 		return 0;
 
-	warning("render_triple_emphasis(%s)", text->data);
-	return 0;
+	warning("render_triple_emphasis(%s)", PR(text));
+	return 1;
 }
 
 int render_strikethrough(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
 	if (!text)
 		return 0;
 
-	warning("render_strikethrough(%s)", text->data);
-	return 0;
+	warning("render_strikethrough(%s)", PR(text));
+	return 1;
 }
 
 int render_superscript(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
 	if (!text)
 		return 0;
 
-	warning("render_superscript(%s)", text->data);
-	return 0;
-}
-
-void render_entity(Common::DataBuffer *ob, const Common::DataBuffer *entity, void *opaque) {
-	if (!entity)
-		return;
-
-	warning("render_entity(%s)", entity->data);
-}
-
-void render_normal_text(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
-	if (!text)
-		return;
-
-	warning("render_normal_text(%s)", text->data);
-
-	Common::bufgrow(ob, text->size);
-	Common::bufputs(ob, (char *)text->data);
+	warning("render_superscript(%s)", PR(text));
+	return 1;
 }
 
-void render_doc_header(Common::DataBuffer *ob, void *opaque) {
-	warning("render_doc_header()");
-}
-
-void render_doc_footer(Common::DataBuffer *ob, void *opaque) {
-	warning("render_doc_footer()");
-}
-
-
 void MacText::setMarkdownText(const Common::U32String &str) {
 
 	const Common::SDCallbacks cb = {
@@ -240,18 +220,19 @@ void MacText::setMarkdownText(const Common::U32String &str) {
 		render_superscript,
 
 		/* low level callbacks - NULL copies input directly into the output */
-		render_entity,
-		render_normal_text,
+		NULL,
+		NULL,
 
 		/* header and footer */
-		render_doc_header,
-		render_doc_footer,
+		NULL,
+		NULL,
 	};
 
 	Common::String input = str.encode(); // Encode to UTF8
 
 	Common::DataBuffer *ib = Common::bufnew(input.size());
 	Common::bufgrow(ib, input.size());
+	ib->size = input.size();
 	memcpy(ib->data, input.c_str(), input.size());
 
 	Common::DataBuffer *ob = Common::bufnew(1024);
@@ -260,7 +241,7 @@ void MacText::setMarkdownText(const Common::U32String &str) {
 	sd_markdown_render(ob, ib->data, ib->size, md);
 	sd_markdown_free(md);
 
-	warning("%zu bytes: %s", ob->size, ob->data);
+	warning("%zu bytes: %s", ob->size, Common::String((const char *)ob->data, ob->size).c_str());
 }
 
 } // End of namespace Graphics


Commit: aeb925a790400dd725b954664764403d0e92a4ff
    https://github.com/scummvm/scummvm/commit/aeb925a790400dd725b954664764403d0e92a4ff
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Added more text to HelpDialog test

Changed paths:
    gui/helpdialog.cpp


diff --git a/gui/helpdialog.cpp b/gui/helpdialog.cpp
index dfef97d109f..7de37362986 100644
--- a/gui/helpdialog.cpp
+++ b/gui/helpdialog.cpp
@@ -62,8 +62,46 @@ HelpDialog::HelpDialog()
 	tab->addTab(_("Kaboom"), "GlobalOptions_Graphics", false);
 	tab->addTab(_("Boomka"), "GlobalOptions_Graphics", false);
 
-	new RichTextWidget(tab, 10, 10, _w - 40, tabHeight - buttonHeight, Common::U32String("Hello world"));
-
+	Common::U32String helpText = _(
+"# Touch controls\n"
+"\n"
+"The touch control scheme can be configured in the global settings. From the Launcher, go to **Options > Backend > Choose the preferred touch mode**.\n"
+"It's possible to configure the touch mode for three situations (ScummVM menus, 2D games and 3D games) and choose one of the three possible modes:\n"
+"\n"
+" * Direct mouse, the touch controls are direct. The pointer jumps to where the finger touches the screen (default for menus).\n"
+" * Touchpad emulation, the touch controls are indirect. The finger can be far away from the pointer and still move it, like on a laptop touchpad.\n"
+" * Gamepad emulation, the touch controls don't move any mouse. The fingers must be placed on lower left and right of the screen and respectively emulate a directional pad and action buttons.\n"
+" * The pointer speed setting in the **Controls tab** affects how far the pointer moves in response to a finger movement.\n"
+"\n"
+"The touch mode can be switched at anytime by tapping on the controller icon, next to the menu icon at the top right of the screen.\n"
+"\n"
+"To display or hide the small controller icon, from the Launcher select **Options** and then the **Backend** tab. Tick the **Show on-screen control** box to enable the controller icon.\n"
+"\n"
+"## Two finger tap\n"
+"\n"
+"To do a two finger tap, hold one finger down and then tap with a second finger.\n"
+"\n"
+"## Three finger tap\n"
+"\n"
+"To do a three finger tap, start with holding down one finger and progressively touch down the other two fingers, one at a time, while still holding down the previous fingers. Imagine you are impatiently tapping your fingers on a surface, but then slow down that movement so it is rhythmic, but not too slow.\n"
+"\n"
+"## Immersive Sticky fullscreen mode\n"
+"\n"
+"ScummVM for Android uses the Immersive Sticky fullscreen mode, which means that the Android system bar is hidden until the user swipes from an edge with a system bar. Swipe from the edge to reveal the system bars.  They remain semi-transparent and disappear after a few seconds unless you interact with them. Your swipe also registers in the game, so if you need to swipe from an edge with system bars, your game play is not interrupted.\n"
+"\n"
+"## Global Main Menu\n"
+"\n"
+"To open the Global Main Menu, tap on the small menu icon at the top right of the screen.\n"
+"\n"
+"To display or hide the small menu icon, from the Launcher select **Options** and then the **Backend** tab. Tick the **Show on-screen control** box to enable the menu icon.\n"
+"\n"
+"## Virtual keyboard\n"
+"\n"
+"To open the virtual keyboard, long press on the small controller icon at the top right of the screen, or tap on any editable text field. To hide the virtual keyboard, tap the small controller icon (which became a keyboard one) again, or tap outside the text field.\n"
+"\n"
+	);
+
+	new RichTextWidget(tab, 10, 10, _w - 40, tabHeight - buttonHeight, helpText);
 
 	new ButtonWidget(this, _w - buttonWidth - 10, _h - buttonHeight - 10, buttonWidth, buttonHeight, Common::U32String("Close"), Common::U32String(), kCloseCmd);
 }


Commit: 027e3794d3f5e3b3bf7a7886bdcf8f1d13893aaf
    https://github.com/scummvm/scummvm/commit/027e3794d3f5e3b3bf7a7886bdcf8f1d13893aaf
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: implemented italic and bold Markdown output

Changed paths:
    graphics/macgui/mactext-md.cpp
    graphics/macgui/mactext.cpp


diff --git a/graphics/macgui/mactext-md.cpp b/graphics/macgui/mactext-md.cpp
index 22cd05ce95c..5368489aac0 100644
--- a/graphics/macgui/mactext-md.cpp
+++ b/graphics/macgui/mactext-md.cpp
@@ -117,22 +117,26 @@ int render_codespan(Common::DataBuffer *ob, const Common::DataBuffer *text, void
 }
 
 int render_double_emphasis(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
-	if (!text)
+	if (!text || !text->size)
 		return 0;
 
 	warning("render_double_emphasis(%s)", PR(text));
 
-	bufput(ob, text->data, text->size);
+	Common::String res = Common::String::format("\016+%02x%s\016-%02x", kMacFontBold, Common::String((const char *)text->data , text->size).c_str(), kMacFontBold);
+
+	bufput(ob, res.c_str(), res.size());
 	return 1;
 }
 
 int render_emphasis(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
-	if (!text)
+	if (!text || !text->size)
 		return 0;
 
 	warning("render_emphasis(%s)", PR(text));
 
-	bufput(ob, text->data, text->size);
+	Common::String res = Common::String::format("\016+%02x%s\016-%02x", kMacFontItalic, Common::String((const char *)text->data , text->size).c_str(), kMacFontItalic);
+
+	bufput(ob, res.c_str(), res.size());
 	return 1;
 }
 
diff --git a/graphics/macgui/mactext.cpp b/graphics/macgui/mactext.cpp
index 5189596b28d..24c83997eac 100644
--- a/graphics/macgui/mactext.cpp
+++ b/graphics/macgui/mactext.cpp
@@ -720,7 +720,6 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 
 					s = readHex(&textSlant, s, 2);
 
-					current_format.textSlant &= ~textSlant; // Clearing the specified byte
 					current_format.textSlant &= ~textSlant; // Clearing the specified bit
 				} else if (*s == '+') { // \016+XX  -- opening textSlant
 					uint16 textSlant;


Commit: ee7573b222ebc0acfd59cb8975a769673e408ada
    https://github.com/scummvm/scummvm/commit/ee7573b222ebc0acfd59cb8975a769673e408ada
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: Marked Markdown parts with STUB and started real rendering

Changed paths:
    graphics/macgui/mactext-md.cpp


diff --git a/graphics/macgui/mactext-md.cpp b/graphics/macgui/mactext-md.cpp
index 5368489aac0..aa103045a1a 100644
--- a/graphics/macgui/mactext-md.cpp
+++ b/graphics/macgui/mactext-md.cpp
@@ -30,81 +30,84 @@ void render_blockcode(Common::DataBuffer *ob, const Common::DataBuffer *text, co
 	if (!text)
 		return;
 
-	warning("render_blockcode(%s)", PR(text));
+	warning("STUB: render_blockcode(%s)", PR(text));
 }
 
 void render_blockquote(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
 	if (!text)
 		return;
 
-	warning("render_blockquote(%s)", PR(text));
+	warning("STUB: render_blockquote(%s)", PR(text));
 }
 
 void render_blockhtml(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
 	if (!text)
 		return;
 
-	warning("render_blockhtml(%s)", PR(text));
+	warning("STUB: render_blockhtml(%s)", PR(text));
 }
 
 void render_header(Common::DataBuffer *ob, const Common::DataBuffer *text, int level, void *opaque) {
 	if (!text)
 		return;
 
-	warning("render_header(%s)", PR(text));
+	warning("STUB: render_header(%s)", PR(text));
 }
 
 void render_hrule(Common::DataBuffer *ob, void *opaque) {
-	warning("render_hrule()");
+	warning("STUB: render_hrule()");
 }
 
 void render_list(Common::DataBuffer *ob, const Common::DataBuffer *text, int flags, void *opaque) {
 	if (!text)
 		return;
 
-	warning("render_list(%s)", PR(text));
+	warning("STUB: render_list(%s)", PR(text));
 }
 
 void render_listitem(Common::DataBuffer *ob, const Common::DataBuffer *text, int flags, void *opaque) {
 	if (!text)
 		return;
 
-	warning("render_listitem(%s)", PR(text));
+	warning("STUB: render_listitem(%s)", PR(text));
 }
 
 void render_paragraph(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
 	if (!text)
 		return;
 
-	warning("render_paragraph(%s)", PR(text));
+	debug(1, "render_paragraph(%s)", PR(text));
+
+	bufput(ob, text->data, text->size);
+	bufput(ob, "\n\n", 2);
 }
 
 void render_table(Common::DataBuffer *ob, const Common::DataBuffer *header, const Common::DataBuffer *body, void *opaque) {
 	if (!body)
 		return;
 
-	warning("render_table(%s, %s)", header ? PR(header) : 0, PR(body));
+	warning("STUB: render_table(%s, %s)", header ? PR(header) : 0, PR(body));
 }
 
 void render_table_row(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
 	if (!text)
 		return;
 
-	warning("render_table_row(%s)", PR(text));
+	warning("STUB: render_table_row(%s)", PR(text));
 }
 
 void render_table_cell(Common::DataBuffer *ob, const Common::DataBuffer *text, int flags, void *opaque) {
 	if (!text)
 		return;
 
-	warning("render_table_cell(%s)", PR(text));
+	warning("STUB: render_table_cell(%s)", PR(text));
 }
 
 int render_autolink(Common::DataBuffer *ob, const Common::DataBuffer *link, Common::MKDAutolink type, void *opaque) {
 	if (!link)
 		return 0;
 
-	warning("render_autolink(%s)", PR(link));
+	warning("STUB: render_autolink(%s)", PR(link));
 	return 1;
 }
 
@@ -112,7 +115,7 @@ int render_codespan(Common::DataBuffer *ob, const Common::DataBuffer *text, void
 	if (!text)
 		return 0;
 
-	warning("render_codespan(%s)", PR(text));
+	warning("STUB: render_codespan(%s)", PR(text));
 	return 1;
 }
 
@@ -120,7 +123,7 @@ int render_double_emphasis(Common::DataBuffer *ob, const Common::DataBuffer *tex
 	if (!text || !text->size)
 		return 0;
 
-	warning("render_double_emphasis(%s)", PR(text));
+	debug(1, "render_double_emphasis(%s)", PR(text));
 
 	Common::String res = Common::String::format("\016+%02x%s\016-%02x", kMacFontBold, Common::String((const char *)text->data , text->size).c_str(), kMacFontBold);
 
@@ -132,7 +135,7 @@ int render_emphasis(Common::DataBuffer *ob, const Common::DataBuffer *text, void
 	if (!text || !text->size)
 		return 0;
 
-	warning("render_emphasis(%s)", PR(text));
+	debug(1, "render_emphasis(%s)", PR(text));
 
 	Common::String res = Common::String::format("\016+%02x%s\016-%02x", kMacFontItalic, Common::String((const char *)text->data , text->size).c_str(), kMacFontItalic);
 
@@ -144,12 +147,14 @@ int render_image(Common::DataBuffer *ob, const Common::DataBuffer *link, const C
 	if (!link)
 		return 0;
 
-	warning("render_image(%s, %s, %s)", PR(link), title ? PR(title) : 0, alt ? PR(alt) : 0);
+	warning("STUB: render_image(%s, %s, %s)", PR(link), title ? PR(title) : 0, alt ? PR(alt) : 0);
 	return 1;
 }
 
 int render_linebreak(Common::DataBuffer *ob, void *opaque) {
-	warning("render_linebreak()");
+	debug(1, "render_linebreak()");
+
+	bufput(ob, "\n", 1);
 	return 1;
 }
 
@@ -157,7 +162,7 @@ int render_link(Common::DataBuffer *ob, const Common::DataBuffer *link, const Co
 	if (!link)
 		return 0;
 
-	warning("render_link(%s, %s, %s)", PR(link), title ? PR(title) : 0, content ? PR(content) : 0);
+	warning("STUB: render_link(%s, %s, %s)", PR(link), title ? PR(title) : 0, content ? PR(content) : 0);
 	return 1;
 }
 
@@ -165,7 +170,7 @@ int render_raw_html_tag(Common::DataBuffer *ob, const Common::DataBuffer *tag, v
 	if (!tag)
 		return 0;
 
-	warning("render_raw_html_tag(%s)", tag->data);
+	warning("STUB: render_raw_html_tag(%s)", tag->data);
 	return 1;
 }
 
@@ -173,7 +178,7 @@ int render_triple_emphasis(Common::DataBuffer *ob, const Common::DataBuffer *tex
 	if (!text)
 		return 0;
 
-	warning("render_triple_emphasis(%s)", PR(text));
+	warning("STUB: render_triple_emphasis(%s)", PR(text));
 	return 1;
 }
 
@@ -181,7 +186,7 @@ int render_strikethrough(Common::DataBuffer *ob, const Common::DataBuffer *text,
 	if (!text)
 		return 0;
 
-	warning("render_strikethrough(%s)", PR(text));
+	warning("STUB: render_strikethrough(%s)", PR(text));
 	return 1;
 }
 
@@ -189,7 +194,7 @@ int render_superscript(Common::DataBuffer *ob, const Common::DataBuffer *text, v
 	if (!text)
 		return 0;
 
-	warning("render_superscript(%s)", PR(text));
+	warning("STUB: render_superscript(%s)", PR(text));
 	return 1;
 }
 
@@ -245,6 +250,7 @@ void MacText::setMarkdownText(const Common::U32String &str) {
 	sd_markdown_render(ob, ib->data, ib->size, md);
 	sd_markdown_free(md);
 
+	setText(Common::String((const char *)ob->data, ob->size));
 	warning("%zu bytes: %s", ob->size, Common::String((const char *)ob->data, ob->size).c_str());
 }
 


Commit: 32f7eec2c67685fbd8494176a5dfebb1baf70f22
    https://github.com/scummvm/scummvm/commit/32f7eec2c67685fbd8494176a5dfebb1baf70f22
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: Remove using of \015 as a binary MacText formatting

It was not used, and moreover \015 is \x0d which is \r. oops

Changed paths:
    graphics/macgui/mactext.cpp
    graphics/macgui/macwindowmanager.cpp


diff --git a/graphics/macgui/mactext.cpp b/graphics/macgui/mactext.cpp
index 24c83997eac..cd5cc701650 100644
--- a/graphics/macgui/mactext.cpp
+++ b/graphics/macgui/mactext.cpp
@@ -695,34 +695,19 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 			}
 
 			// get format
-			if (*s == '\015') {	// binary format
-				s++;
-
-				uint16 fontId = *s++; fontId = (fontId << 8) | *s++;
-				byte textSlant = *s++;
-				uint16 fontSize = *s++; fontSize = (fontSize << 8) | *s++;
-				uint16 palinfo1 = *s++; palinfo1 = (palinfo1 << 8) | *s++;
-				uint16 palinfo2 = *s++; palinfo2 = (palinfo2 << 8) | *s++;
-				uint16 palinfo3 = *s++; palinfo3 = (palinfo3 << 8) | *s++;
-
-				D(9, "** splitString: fontId: %d, textSlant: %d, fontSize: %d, p0: %x p1: %x p2: %x",
-						fontId, textSlant, fontSize, palinfo1, palinfo2, palinfo3);
-
-				current_format.setValues(_wm, fontId, textSlant, fontSize, palinfo1, palinfo2, palinfo3);
-
-				if (!_macFontMode)
-					current_format.font = _defaultFormatting.font;
-			} else if (*s == '\016') {	// human-readable format
+			if (*s == '\016') {	// human-readable format
 				s++;
 
 				if (*s == '-') { // \016-XX  -- closing textSlant
 					uint16 textSlant;
+					s++;
 
 					s = readHex(&textSlant, s, 2);
 
 					current_format.textSlant &= ~textSlant; // Clearing the specified bit
 				} else if (*s == '+') { // \016+XX  -- opening textSlant
 					uint16 textSlant;
+					s++;
 
 					s = readHex(&textSlant, s, 2);
 
diff --git a/graphics/macgui/macwindowmanager.cpp b/graphics/macgui/macwindowmanager.cpp
index 5bb41a4e1a5..e259e1b9753 100644
--- a/graphics/macgui/macwindowmanager.cpp
+++ b/graphics/macgui/macwindowmanager.cpp
@@ -580,10 +580,6 @@ Common::U32String stripFormat(const Common::U32String &str) {
 				if (*s == '\001') {
 					tmp += *s++;
 				}
-			} else if (*s == '\015') {	// binary format
-				// we are skipping the formatting stuffs
-				// this number 12, and the number 23, is the size of our format
-				s += 12;
 			} else if (*s == '\016') {	// human-readable format
 				s += 23;
 			} else {


Commit: a314def2cc6c057f340db6ed4668b3ca3801ff2f
    https://github.com/scummvm/scummvm/commit/a314def2cc6c057f340db6ed4668b3ca3801ff2f
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: Added Header formatting, synced with stripFormatting(), fixed closing formatting

Changed paths:
    graphics/macgui/mactext-md.cpp
    graphics/macgui/mactext.cpp
    graphics/macgui/macwindowmanager.cpp


diff --git a/graphics/macgui/mactext-md.cpp b/graphics/macgui/mactext-md.cpp
index aa103045a1a..b34ae437cdc 100644
--- a/graphics/macgui/mactext-md.cpp
+++ b/graphics/macgui/mactext-md.cpp
@@ -51,7 +51,11 @@ void render_header(Common::DataBuffer *ob, const Common::DataBuffer *text, int l
 	if (!text)
 		return;
 
-	warning("STUB: render_header(%s)", PR(text));
+	debug(1, "render_header(%s)", PR(text));
+
+	Common::String res = Common::String::format("\016+00%01x%s\001\016-00f", level, Common::String((const char *)text->data , text->size).c_str());
+
+	bufput(ob, res.c_str(), res.size());
 }
 
 void render_hrule(Common::DataBuffer *ob, void *opaque) {
@@ -125,7 +129,7 @@ int render_double_emphasis(Common::DataBuffer *ob, const Common::DataBuffer *tex
 
 	debug(1, "render_double_emphasis(%s)", PR(text));
 
-	Common::String res = Common::String::format("\016+%02x%s\016-%02x", kMacFontBold, Common::String((const char *)text->data , text->size).c_str(), kMacFontBold);
+	Common::String res = Common::String::format("\016+%02x0%s\001\016-%02x0", kMacFontBold, Common::String((const char *)text->data , text->size).c_str(), kMacFontBold);
 
 	bufput(ob, res.c_str(), res.size());
 	return 1;
@@ -137,7 +141,7 @@ int render_emphasis(Common::DataBuffer *ob, const Common::DataBuffer *text, void
 
 	debug(1, "render_emphasis(%s)", PR(text));
 
-	Common::String res = Common::String::format("\016+%02x%s\016-%02x", kMacFontItalic, Common::String((const char *)text->data , text->size).c_str(), kMacFontItalic);
+	Common::String res = Common::String::format("\016+%02x0%s\001\016-%02x0", kMacFontItalic, Common::String((const char *)text->data , text->size).c_str(), kMacFontItalic);
 
 	bufput(ob, res.c_str(), res.size());
 	return 1;
@@ -250,8 +254,10 @@ void MacText::setMarkdownText(const Common::U32String &str) {
 	sd_markdown_render(ob, ib->data, ib->size, md);
 	sd_markdown_free(md);
 
+	warning("%zu bytes: %s", ob->size, toPrintable(Common::String((const char *)ob->data, ob->size)).c_str());
+
+	setDefaultFormatting(kMacFontChicago, kMacFontChicago, 40, 0, 0, 0);
 	setText(Common::String((const char *)ob->data, ob->size));
-	warning("%zu bytes: %s", ob->size, Common::String((const char *)ob->data, ob->size).c_str());
 }
 
 } // End of namespace Graphics
diff --git a/graphics/macgui/mactext.cpp b/graphics/macgui/mactext.cpp
index cd5cc701650..11944075ef6 100644
--- a/graphics/macgui/mactext.cpp
+++ b/graphics/macgui/mactext.cpp
@@ -694,24 +694,35 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 				}
 			}
 
-			// get format
+			// get format (sync with stripFormat() )
 			if (*s == '\016') {	// human-readable format
 				s++;
 
-				if (*s == '-') { // \016-XX  -- closing textSlant
-					uint16 textSlant;
+				// First two digits is slant, third digit is Header number
+				if (*s == '-') { // \016-XXY  -- closing textSlant, H<Y>
+					uint16 textSlant, headSize;
 					s++;
 
 					s = readHex(&textSlant, s, 2);
 
 					current_format.textSlant &= ~textSlant; // Clearing the specified bit
-				} else if (*s == '+') { // \016+XX  -- opening textSlant
-					uint16 textSlant;
+
+					s = readHex(&headSize, s, 1);
+					if (headSize == 0xf) // reset
+						current_format.fontSize = _defaultFormatting.fontSize;
+				} else if (*s == '+') { // \016+XXY  -- opening textSlant. H<Y>
+					uint16 textSlant, headSize;
 					s++;
 
 					s = readHex(&textSlant, s, 2);
 
 					current_format.textSlant |= textSlant; // Setting the specified bit
+
+					s = readHex(&headSize, s, 1);
+					if (headSize >= 1 && headSize <= 6) { // set
+						const float sizes[] = { 1, 3.0f, 2.5f, 2.0f, 1.75f, 1.5f, 1.25f };
+						current_format.fontSize = _defaultFormatting.fontSize * sizes[headSize];
+					}
 				} else {
 					uint16 fontId, textSlant, fontSize, palinfo1, palinfo2, palinfo3;
 
diff --git a/graphics/macgui/macwindowmanager.cpp b/graphics/macgui/macwindowmanager.cpp
index e259e1b9753..6b8835dd11a 100644
--- a/graphics/macgui/macwindowmanager.cpp
+++ b/graphics/macgui/macwindowmanager.cpp
@@ -581,7 +581,11 @@ Common::U32String stripFormat(const Common::U32String &str) {
 					tmp += *s++;
 				}
 			} else if (*s == '\016') {	// human-readable format
-				s += 23;
+				s++;
+				if (*s == '+' || *s == '-')
+					s += 3;
+				else
+					s += 22;
 			} else {
 				tmp += *s++;
 			}


Commit: 345706521c6ba0f90258c067aa377c3f8a6051ca
    https://github.com/scummvm/scummvm/commit/345706521c6ba0f90258c067aa377c3f8a6051ca
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Fixed font setting in RichText

Changed paths:
    gui/widgets/richtext.cpp


diff --git a/gui/widgets/richtext.cpp b/gui/widgets/richtext.cpp
index 1fa781cd26c..e8a27db8450 100644
--- a/gui/widgets/richtext.cpp
+++ b/gui/widgets/richtext.cpp
@@ -71,7 +71,9 @@ void RichTextWidget::createWidget() {
 	uint32 white = _wm->_pixelformat.RGBToColor(0xff, 0xff, 0xff);
 	uint32 black = _wm->_pixelformat.RGBToColor(0x00, 0x00, 0x00);
 
-	_txtWnd = _wm->addTextWindow(g_gui.theme()->getFont(),
+	Graphics::MacFont macFont(Graphics::kMacFontChicago, 30, Graphics::kMacFontRegular);
+
+	_txtWnd = _wm->addTextWindow(&macFont,
 			black, white, _w, Graphics::kTextAlignLeft, nullptr, false);
 	_txtWnd->setTextColorRGB(black);
 	_txtWnd->setBorderType(Graphics::kWindowBorderMacOSNoBorderScrollbar);


Commit: 85a492e03995aeecac3e4c2309cc46f12557161c
    https://github.com/scummvm/scummvm/commit/85a492e03995aeecac3e4c2309cc46f12557161c
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: Properly reset font slant on end of formatting

Changed paths:
    graphics/macgui/mactext-md.cpp


diff --git a/graphics/macgui/mactext-md.cpp b/graphics/macgui/mactext-md.cpp
index b34ae437cdc..b44fbd96700 100644
--- a/graphics/macgui/mactext-md.cpp
+++ b/graphics/macgui/mactext-md.cpp
@@ -53,7 +53,7 @@ void render_header(Common::DataBuffer *ob, const Common::DataBuffer *text, int l
 
 	debug(1, "render_header(%s)", PR(text));
 
-	Common::String res = Common::String::format("\016+00%01x%s\001\016-00f", level, Common::String((const char *)text->data , text->size).c_str());
+	Common::String res = Common::String::format("\016+00%01x%s\001\016-00f\n", level, Common::String((const char *)text->data , text->size).c_str());
 
 	bufput(ob, res.c_str(), res.size());
 }
@@ -129,7 +129,7 @@ int render_double_emphasis(Common::DataBuffer *ob, const Common::DataBuffer *tex
 
 	debug(1, "render_double_emphasis(%s)", PR(text));
 
-	Common::String res = Common::String::format("\016+%02x0%s\001\016-%02x0", kMacFontBold, Common::String((const char *)text->data , text->size).c_str(), kMacFontBold);
+	Common::String res = Common::String::format("\001\016+%02x0%s\001\016-%02x0", kMacFontBold, Common::String((const char *)text->data , text->size).c_str(), kMacFontBold);
 
 	bufput(ob, res.c_str(), res.size());
 	return 1;
@@ -141,7 +141,7 @@ int render_emphasis(Common::DataBuffer *ob, const Common::DataBuffer *text, void
 
 	debug(1, "render_emphasis(%s)", PR(text));
 
-	Common::String res = Common::String::format("\016+%02x0%s\001\016-%02x0", kMacFontItalic, Common::String((const char *)text->data , text->size).c_str(), kMacFontItalic);
+	Common::String res = Common::String::format("\001\016+%02x0%s\001\016-%02x0", kMacFontItalic, Common::String((const char *)text->data , text->size).c_str(), kMacFontItalic);
 
 	bufput(ob, res.c_str(), res.size());
 	return 1;
@@ -256,7 +256,7 @@ void MacText::setMarkdownText(const Common::U32String &str) {
 
 	warning("%zu bytes: %s", ob->size, toPrintable(Common::String((const char *)ob->data, ob->size)).c_str());
 
-	setDefaultFormatting(kMacFontChicago, kMacFontChicago, 40, 0, 0, 0);
+	//setDefaultFormatting(kMacFontChicago, kMacFontRegular, 40, 0, 0, 0);
 	setText(Common::String((const char *)ob->data, ob->size));
 }
 


Commit: f086c5cd13cf8cb10d146e5beaf509ce34832e1e
    https://github.com/scummvm/scummvm/commit/f086c5cd13cf8cb10d146e5beaf509ce34832e1e
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: Added more debug output to MacText

Changed paths:
    graphics/macgui/mactext.cpp


diff --git a/graphics/macgui/mactext.cpp b/graphics/macgui/mactext.cpp
index 11944075ef6..ce2e487ec3d 100644
--- a/graphics/macgui/mactext.cpp
+++ b/graphics/macgui/mactext.cpp
@@ -710,6 +710,9 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 					s = readHex(&headSize, s, 1);
 					if (headSize == 0xf) // reset
 						current_format.fontSize = _defaultFormatting.fontSize;
+
+					D(9, "** splitString-: fontId: %d, textSlant: %d, fontSize: %d,",
+							current_format.fontId, current_format.textSlant, current_format.fontSize);
 				} else if (*s == '+') { // \016+XXY  -- opening textSlant. H<Y>
 					uint16 textSlant, headSize;
 					s++;
@@ -723,6 +726,9 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 						const float sizes[] = { 1, 3.0f, 2.5f, 2.0f, 1.75f, 1.5f, 1.25f };
 						current_format.fontSize = _defaultFormatting.fontSize * sizes[headSize];
 					}
+
+					D(9, "** splitString+: fontId: %d, textSlant: %d, fontSize: %d,",
+							current_format.fontId, current_format.textSlant, current_format.fontSize);
 				} else {
 					uint16 fontId, textSlant, fontSize, palinfo1, palinfo2, palinfo3;
 
@@ -957,9 +963,9 @@ void MacText::render(int from, int to, int shadow) {
 
 		// TODO: _textMaxWidth, when -1, was not rendering ANY text.
 		for (int j = start; j != end; j += delta) {
-			debug(9, "MacText::render: line %d[%d] h:%d at %d,%d (%s) fontid: %d on %dx%d, fgcolor: %d bgcolor: %d, font: %p",
+			debug(9, "MacText::render: line %d[%d] h:%d at %d,%d (%s) fontid: %d fontsize: %d on %dx%d, fgcolor: %d bgcolor: %d, font: %p",
 				  i, j, _textLines[i].height, xOffset, _textLines[i].y, _textLines[i].chunks[j].text.encode().c_str(),
-				  _textLines[i].chunks[j].fontId, _surface->w, _surface->h, _textLines[i].chunks[j].fgcolor, _bgcolor,
+				  _textLines[i].chunks[j].fontId, _textLines[i].chunks[j].fontSize, _surface->w, _surface->h, _textLines[i].chunks[j].fgcolor, _bgcolor,
 				  (const void *)_textLines[i].chunks[j].getFont());
 
 			if (_textLines[i].chunks[j].text.empty())


Commit: 5e895c6af5fec7499985ba8c50e715fa0633b27b
    https://github.com/scummvm/scummvm/commit/5e895c6af5fec7499985ba8c50e715fa0633b27b
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Added extrapath to SearchMan before initializing WM in RichText

This lets us find macgui.dat and classicmacfonts.dat

Changed paths:
    gui/widgets/richtext.cpp


diff --git a/gui/widgets/richtext.cpp b/gui/widgets/richtext.cpp
index e8a27db8450..ad8b43c0223 100644
--- a/gui/widgets/richtext.cpp
+++ b/gui/widgets/richtext.cpp
@@ -38,8 +38,12 @@ void ensureWM() {
 	if (_wm)
 		return;
 
-	uint32 wmMode = Graphics::kWMModeNoDesktop | Graphics::kWMModeForceBuiltinFonts
-		| Graphics::kWMModeUnicode | Graphics::kWMMode32bpp;
+	if (ConfMan.hasKey("extrapath")) {
+		Common::FSNode dir(ConfMan.get("extrapath"));
+		SearchMan.addDirectory(dir.getPath(), dir);
+	}
+
+	uint32 wmMode = Graphics::kWMModeNoDesktop | Graphics::kWMMode32bpp;
 
 	_wm = new Graphics::MacWindowManager(wmMode);
 }


Commit: fd844bc00f196fed2e5ae52bdf8317d2aee630d5
    https://github.com/scummvm/scummvm/commit/fd844bc00f196fed2e5ae52bdf8317d2aee630d5
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: Added kWMModeNoCursorOverride

Changed paths:
    graphics/macgui/macwindowmanager.cpp
    graphics/macgui/macwindowmanager.h


diff --git a/graphics/macgui/macwindowmanager.cpp b/graphics/macgui/macwindowmanager.cpp
index 6b8835dd11a..393020c3a87 100644
--- a/graphics/macgui/macwindowmanager.cpp
+++ b/graphics/macgui/macwindowmanager.cpp
@@ -222,10 +222,12 @@ MacWindowManager::MacWindowManager(uint32 mode, MacPatterns *patterns, Common::L
 
 	_fontMan = new MacFontManager(mode, language);
 
-	_cursor = nullptr;
-	_tempType = kMacCursorArrow;
-	replaceCursor(kMacCursorArrow);
-	CursorMan.showMouse(true);
+	if (!(mode & kWMModeNoCursorOverride)) {
+		_cursor = nullptr;
+		_tempType = kMacCursorArrow;
+		replaceCursor(kMacCursorArrow);
+		CursorMan.showMouse(true);
+	}
 
 	loadDataBundle();
 	setDesktopMode(mode);
diff --git a/graphics/macgui/macwindowmanager.h b/graphics/macgui/macwindowmanager.h
index cf7cecc563c..8629547c953 100644
--- a/graphics/macgui/macwindowmanager.h
+++ b/graphics/macgui/macwindowmanager.h
@@ -88,7 +88,8 @@ enum {
 	kWMMode32bpp				= (1 << 8),
 	kWMNoScummVMWallpaper		= (1 << 9),
 	kWMModeWin95				= (1 << 10),
-	kWMModeForceMacFontsInWin95 = (1 << 11) // Enforce Mac font for languages which don't have glyphs in ms_sans_serif.ttf
+	kWMModeForceMacFontsInWin95 = (1 << 11), // Enforce Mac font for languages which don't have glyphs in ms_sans_serif.ttf
+	kWMModeNoCursorOverride     = (1 << 12),
 };
 
 }
@@ -306,7 +307,7 @@ public:
 	 * @param window Pointer to the widget to background, nullptr for no widget
 	 */
 	void setBackgroundWindow(MacWindow *window);
-	
+
 	MacPatterns  &getBuiltinPatterns() { return _builtinPatterns; }
 
 	MacWidget *getActiveWidget() { return _activeWidget; }


Commit: 2b498a0ff1faf13834d8975a11933076a8f5f077
    https://github.com/scummvm/scummvm/commit/2b498a0ff1faf13834d8975a11933076a8f5f077
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Do not override GUI cursor for RichText

Changed paths:
    gui/widgets/richtext.cpp


diff --git a/gui/widgets/richtext.cpp b/gui/widgets/richtext.cpp
index ad8b43c0223..87581a00f26 100644
--- a/gui/widgets/richtext.cpp
+++ b/gui/widgets/richtext.cpp
@@ -43,7 +43,7 @@ void ensureWM() {
 		SearchMan.addDirectory(dir.getPath(), dir);
 	}
 
-	uint32 wmMode = Graphics::kWMModeNoDesktop | Graphics::kWMMode32bpp;
+	uint32 wmMode = Graphics::kWMModeNoDesktop | Graphics::kWMMode32bpp | Graphics::kWMModeNoCursorOverride;
 
 	_wm = new Graphics::MacWindowManager(wmMode);
 }


Commit: e555528a42d69679993b999992552268690c2d73
    https://github.com/scummvm/scummvm/commit/e555528a42d69679993b999992552268690c2d73
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Expose theme text colors int ThemeEngine

Changed paths:
    gui/ThemeEngine.cpp
    gui/ThemeEngine.h


diff --git a/gui/ThemeEngine.cpp b/gui/ThemeEngine.cpp
index eba8345a806..8b1b019fb37 100644
--- a/gui/ThemeEngine.cpp
+++ b/gui/ThemeEngine.cpp
@@ -68,10 +68,6 @@ struct TextDrawData {
 	const Graphics::Font *_fontPtr;
 };
 
-struct TextColorData {
-	int r, g, b;
-};
-
 struct WidgetDrawData {
 	/** List of all the steps needed to draw this widget */
 	Common::List<Graphics::DrawStep> _steps;
@@ -1696,6 +1692,13 @@ TextColor ThemeEngine::getTextColor(DrawData ddId) const {
 	return _widgets[ddId] ? _widgets[ddId]->_textColorId : kTextColorMAX;
 }
 
+TextColorData *ThemeEngine::getTextColorData(TextColor color) const {
+	if (color >= kTextColorMAX)
+		color = kTextColorMAX;
+
+	return _textColors[color];
+}
+
 DrawData ThemeEngine::parseDrawDataId(const Common::String &name) const {
 	for (int i = 0; i < kDrawDataMAX; ++i)
 		if (name.compareToIgnoreCase(kDrawDataDefaults[i].name) == 0)
diff --git a/gui/ThemeEngine.h b/gui/ThemeEngine.h
index 72d31039e56..8266b693bfd 100644
--- a/gui/ThemeEngine.h
+++ b/gui/ThemeEngine.h
@@ -49,7 +49,6 @@ namespace GUI {
 
 struct WidgetDrawData;
 struct TextDrawData;
-struct TextColorData;
 class Dialog;
 class GuiObject;
 class ThemeEval;
@@ -175,6 +174,10 @@ enum TextColor {
 	kTextColorMAX
 };
 
+struct TextColorData {
+	int r, g, b;
+};
+
 class LangExtraFont {
 public:
 	LangExtraFont(TextData textId, Common::Array<Common::Language> &lngs, const Common::String &filename, const Common::String &scalableFile, int ps) : _langs(lngs) {
@@ -529,6 +532,7 @@ public:
 	TextData getTextData(DrawData ddId) const;
 	TextColor getTextColor(DrawData ddId) const;
 
+	TextColorData *getTextColorData(TextColor color) const;
 
 	/**
 	 * Interface for ThemeParser class: Parsed DrawSteps are added via this function.


Commit: c8cc2c46f7f499dddfd8666d6948a4ac0e274e25
    https://github.com/scummvm/scummvm/commit/c8cc2c46f7f499dddfd8666d6948a4ac0e274e25
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Use theme colors for RichText

Changed paths:
    gui/widgets/richtext.cpp


diff --git a/gui/widgets/richtext.cpp b/gui/widgets/richtext.cpp
index 87581a00f26..616b270acea 100644
--- a/gui/widgets/richtext.cpp
+++ b/gui/widgets/richtext.cpp
@@ -73,9 +73,10 @@ RichTextWidget::RichTextWidget(GuiObject *boss, const Common::String &name, cons
 
 void RichTextWidget::createWidget() {
 	uint32 white = _wm->_pixelformat.RGBToColor(0xff, 0xff, 0xff);
-	uint32 black = _wm->_pixelformat.RGBToColor(0x00, 0x00, 0x00);
+	TextColorData *normal = g_gui.theme()->getTextColorData(kTextColorNormal);
+	uint32 black = _wm->_pixelformat.RGBToColor(normal->r, normal->g, normal->b);
 
-	Graphics::MacFont macFont(Graphics::kMacFontChicago, 30, Graphics::kMacFontRegular);
+	Graphics::MacFont macFont(Graphics::kMacFontNewYork, 30, Graphics::kMacFontRegular);
 
 	_txtWnd = _wm->addTextWindow(&macFont,
 			black, white, _w, Graphics::kTextAlignLeft, nullptr, false);


Commit: f12386adeaa58e883bf271afd7c3dd763d6ad2c6
    https://github.com/scummvm/scummvm/commit/f12386adeaa58e883bf271afd7c3dd763d6ad2c6
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
COMMON: FORMATS: Catch list start in Markdown

Changed paths:
    common/formats/markdown.cpp
    common/formats/markdown.h
    graphics/macgui/mactext-md.cpp


diff --git a/common/formats/markdown.cpp b/common/formats/markdown.cpp
index c7f6e0f0fa4..a96a1cdb1af 100644
--- a/common/formats/markdown.cpp
+++ b/common/formats/markdown.cpp
@@ -1684,6 +1684,9 @@ static size_t parse_list(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t si
 
 	work = rndr_newbuf(rndr, BUFFER_BLOCK);
 
+	if (rndr->cb.list_start)
+		rndr->cb.list_start(ob, work, flags, rndr->opaque);
+
 	while (i < size) {
 		j = parse_listitem(work, rndr, data + i, size - i, &flags);
 		i += j;
diff --git a/common/formats/markdown.h b/common/formats/markdown.h
index fccc152cd8a..365a8a4d681 100644
--- a/common/formats/markdown.h
+++ b/common/formats/markdown.h
@@ -68,6 +68,7 @@ struct SDCallbacks {
 	void (*blockhtml)(DataBuffer *ob,const  DataBuffer *text, void *opaque);
 	void (*header)(DataBuffer *ob, const DataBuffer *text, int level, void *opaque);
 	void (*hrule)(DataBuffer *ob, void *opaque);
+	void (*list_start)(DataBuffer *ob, const DataBuffer *text, int flags, void *opaque);
 	void (*list)(DataBuffer *ob, const DataBuffer *text, int flags, void *opaque);
 	void (*listitem)(DataBuffer *ob, const DataBuffer *text, int flags, void *opaque);
 	void (*paragraph)(DataBuffer *ob, const DataBuffer *text, void *opaque);
diff --git a/graphics/macgui/mactext-md.cpp b/graphics/macgui/mactext-md.cpp
index b44fbd96700..baac0274fc8 100644
--- a/graphics/macgui/mactext-md.cpp
+++ b/graphics/macgui/mactext-md.cpp
@@ -20,12 +20,18 @@
  */
 
 #include "common/formats/markdown.h"
+
 #include "graphics/macgui/mactext.h"
 
 namespace Graphics {
 
 #define PR(x) (x->data ? Common::String((const char *)(x)->data , (x)->size).c_str() : "(null)")
 
+struct MDState {
+	Common::List<int> listNum;
+};
+
+
 void render_blockcode(Common::DataBuffer *ob, const Common::DataBuffer *text, const Common::DataBuffer *lang, void *opaque) {
 	if (!text)
 		return;
@@ -62,18 +68,25 @@ void render_hrule(Common::DataBuffer *ob, void *opaque) {
 	warning("STUB: render_hrule()");
 }
 
+void render_list_start(Common::DataBuffer *ob, const Common::DataBuffer *text, int flags, void *opaque) {
+	if (!text)
+		return;
+
+	warning("STUB: render_list_start(%s, %d)", PR(text), flags);
+}
+
 void render_list(Common::DataBuffer *ob, const Common::DataBuffer *text, int flags, void *opaque) {
 	if (!text)
 		return;
 
-	warning("STUB: render_list(%s)", PR(text));
+	warning("STUB: render_list(%s, %d)", PR(text), flags);
 }
 
 void render_listitem(Common::DataBuffer *ob, const Common::DataBuffer *text, int flags, void *opaque) {
 	if (!text)
 		return;
 
-	warning("STUB: render_listitem(%s)", PR(text));
+	warning("STUB: render_listitem(%s, %d)", PR(text), flags);
 }
 
 void render_paragraph(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
@@ -211,6 +224,7 @@ void MacText::setMarkdownText(const Common::U32String &str) {
 		render_blockhtml,
 		render_header,
 		render_hrule,
+		render_list_start,
 		render_list,
 		render_listitem,
 		render_paragraph,
@@ -250,7 +264,9 @@ void MacText::setMarkdownText(const Common::U32String &str) {
 
 	Common::DataBuffer *ob = Common::bufnew(1024);
 
-	Common::SDMarkdown *md = sd_markdown_new(0, 16, &cb, this);
+	MDState mdState;
+
+	Common::SDMarkdown *md = sd_markdown_new(0, 16, &cb, &mdState);
 	sd_markdown_render(ob, ib->data, ib->size, md);
 	sd_markdown_free(md);
 


Commit: 4033c0f8699684a6c9471aa17e94da0134ecb71a
    https://github.com/scummvm/scummvm/commit/4033c0f8699684a6c9471aa17e94da0134ecb71a
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: Implemented nested list processing in MarkDown

Changed paths:
    graphics/macgui/mactext-md.cpp


diff --git a/graphics/macgui/mactext-md.cpp b/graphics/macgui/mactext-md.cpp
index baac0274fc8..f975570205b 100644
--- a/graphics/macgui/mactext-md.cpp
+++ b/graphics/macgui/mactext-md.cpp
@@ -69,24 +69,46 @@ void render_hrule(Common::DataBuffer *ob, void *opaque) {
 }
 
 void render_list_start(Common::DataBuffer *ob, const Common::DataBuffer *text, int flags, void *opaque) {
-	if (!text)
-		return;
+	MDState *mdstate = (MDState *)opaque;
+
+	mdstate->listNum.push_back(flags & MKD_LIST_ORDERED ? 1 : -1);
 
-	warning("STUB: render_list_start(%s, %d)", PR(text), flags);
+	debug(1, "render_list_start(%s, %d)", PR(text), flags);
 }
 
 void render_list(Common::DataBuffer *ob, const Common::DataBuffer *text, int flags, void *opaque) {
-	if (!text)
-		return;
+	MDState *mdstate = (MDState *)opaque;
+
+	mdstate->listNum.pop_back();
+
+	bufput(ob, text->data, text->size);
+	bufput(ob, "\n", 1);
 
-	warning("STUB: render_list(%s, %d)", PR(text), flags);
+	debug(1, "render_list(%s, %d)", PR(text), flags);
 }
 
 void render_listitem(Common::DataBuffer *ob, const Common::DataBuffer *text, int flags, void *opaque) {
-	if (!text)
-		return;
+	MDState *mdstate = (MDState *)opaque;
+
+	int listNum = mdstate->listNum.back();
+	int depth = mdstate->listNum.size() - 1;
+
+	for (int i = 0; i < depth; i++)
+		bufput(ob, "  ", 2);
+
+	if (flags & MKD_LIST_ORDERED) {
+		Common::String prefix = Common::String::format("%d. ", listNum);
+
+		bufput(ob, prefix.c_str(), prefix.size());
+
+		mdstate->listNum.back()++;
+	} else {
+		bufput(ob, "* ", 2);
+	}
+
+	bufput(ob, text->data, text->size);
 
-	warning("STUB: render_listitem(%s, %d)", PR(text), flags);
+	debug(1, "render_listitem(%s, %d)", PR(text), flags);
 }
 
 void render_paragraph(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {


Commit: 3cfc80e110075565bef7bd37c407524955c4bca9
    https://github.com/scummvm/scummvm/commit/3cfc80e110075565bef7bd37c407524955c4bca9
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: Properly set default text color in MacText

Changed paths:
    graphics/macgui/mactext.cpp


diff --git a/graphics/macgui/mactext.cpp b/graphics/macgui/mactext.cpp
index ce2e487ec3d..b930b746f11 100644
--- a/graphics/macgui/mactext.cpp
+++ b/graphics/macgui/mactext.cpp
@@ -130,7 +130,9 @@ MacText::MacText(MacWidget *parent, int x, int y, int w, int h, MacWindowManager
 	if (macFont) {
 		_defaultFormatting = MacFontRun(_wm);
 		_defaultFormatting.font = wm->_fontMan->getFont(*macFont);
-		_defaultFormatting.setValues(_wm, macFont->getId(), macFont->getSlant(), macFont->getSize(), 0, 0, 0);
+		byte r, g, b;
+		_wm->_pixelformat.colorToRGB(fgcolor, r, g, b);
+		_defaultFormatting.setValues(_wm, macFont->getId(), macFont->getSlant(), macFont->getSize(), r, g, b);
 	} else {
 		_defaultFormatting.font = NULL;
 	}


Commit: 1e6d7db76e5c14500ff8cbf96826d91e96f273ea
    https://github.com/scummvm/scummvm/commit/1e6d7db76e5c14500ff8cbf96826d91e96f273ea
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: Added one more ident level for lists in Marcdown

Changed paths:
    graphics/macgui/mactext-md.cpp


diff --git a/graphics/macgui/mactext-md.cpp b/graphics/macgui/mactext-md.cpp
index f975570205b..6e006e6b1ac 100644
--- a/graphics/macgui/mactext-md.cpp
+++ b/graphics/macgui/mactext-md.cpp
@@ -91,7 +91,7 @@ void render_listitem(Common::DataBuffer *ob, const Common::DataBuffer *text, int
 	MDState *mdstate = (MDState *)opaque;
 
 	int listNum = mdstate->listNum.back();
-	int depth = mdstate->listNum.size() - 1;
+	int depth = mdstate->listNum.size();
 
 	for (int i = 0; i < depth; i++)
 		bufput(ob, "  ", 2);


Commit: 49d9261fd5bd555c88cdaf92531787cd665fad90
    https://github.com/scummvm/scummvm/commit/49d9261fd5bd555c88cdaf92531787cd665fad90
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Simulatoe better colors in RichText

Changed paths:
    gui/widgets/richtext.cpp


diff --git a/gui/widgets/richtext.cpp b/gui/widgets/richtext.cpp
index 616b270acea..5eea52a4d56 100644
--- a/gui/widgets/richtext.cpp
+++ b/gui/widgets/richtext.cpp
@@ -72,14 +72,14 @@ RichTextWidget::RichTextWidget(GuiObject *boss, const Common::String &name, cons
 }
 
 void RichTextWidget::createWidget() {
-	uint32 white = _wm->_pixelformat.RGBToColor(0xff, 0xff, 0xff);
+	uint32 white = _wm->_pixelformat.RGBToColor(0, 0, 0);
 	TextColorData *normal = g_gui.theme()->getTextColorData(kTextColorNormal);
 	uint32 black = _wm->_pixelformat.RGBToColor(normal->r, normal->g, normal->b);
+	black = _wm->_pixelformat.RGBToColor(1, 1, 1);
 
 	Graphics::MacFont macFont(Graphics::kMacFontNewYork, 30, Graphics::kMacFontRegular);
 
-	_txtWnd = _wm->addTextWindow(&macFont,
-			black, white, _w, Graphics::kTextAlignLeft, nullptr, false);
+	_txtWnd = _wm->addTextWindow(&macFont, black, white, _w, Graphics::kTextAlignLeft, nullptr, false);
 	_txtWnd->setTextColorRGB(black);
 	_txtWnd->setBorderType(Graphics::kWindowBorderMacOSNoBorderScrollbar);
 	_txtWnd->enableScrollbar(true);


Commit: 873926f88c9e0f1c381585e30bc07d7bfb807a25
    https://github.com/scummvm/scummvm/commit/873926f88c9e0f1c381585e30bc07d7bfb807a25
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Rearranged tabs in help dialog

Changed paths:
    gui/helpdialog.cpp


diff --git a/gui/helpdialog.cpp b/gui/helpdialog.cpp
index 7de37362986..34d17db25d2 100644
--- a/gui/helpdialog.cpp
+++ b/gui/helpdialog.cpp
@@ -52,15 +52,6 @@ HelpDialog::HelpDialog()
 
 	tab->addTab(_("General"), "GlobalOptions_Graphics", false);
 	tab->addTab(_("Controls"), "GlobalOptions_Graphics", false);
-	tab->addTab(_("More Stuff"), "GlobalOptions_Graphics", false);
-	tab->addTab(_("Even More"), "GlobalOptions_Graphics", false);
-	tab->addTab(_("Bubba"), "GlobalOptions_Graphics", false);
-	tab->addTab(_("Rulez"), "GlobalOptions_Graphics", false);
-	tab->addTab(_("Abra"), "GlobalOptions_Graphics", false);
-	tab->addTab(_("Shwabra"), "GlobalOptions_Graphics", false);
-	tab->addTab(_("Kadabra"), "GlobalOptions_Graphics", false);
-	tab->addTab(_("Kaboom"), "GlobalOptions_Graphics", false);
-	tab->addTab(_("Boomka"), "GlobalOptions_Graphics", false);
 
 	Common::U32String helpText = _(
 "# Touch controls\n"
@@ -101,7 +92,10 @@ HelpDialog::HelpDialog()
 "\n"
 	);
 
-	new RichTextWidget(tab, 10, 10, _w - 40, tabHeight - buttonHeight, helpText);
+	new RichTextWidget(tab, 10, 10, _w - 40, tabHeight - buttonHeight - 10, helpText);
+
+	tab->addTab(_("Adding Games"), "GlobalOptions_Graphics", false);
+	tab->addTab(_("Paths"), "GlobalOptions_Graphics", false);
 
 	new ButtonWidget(this, _w - buttonWidth - 10, _h - buttonHeight - 10, buttonWidth, buttonHeight, Common::U32String("Close"), Common::U32String(), kCloseCmd);
 }


Commit: 69aee0d228b11a9098072228a0176d5d02e3734d
    https://github.com/scummvm/scummvm/commit/69aee0d228b11a9098072228a0176d5d02e3734d
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: Fix regression in font scaling code

Changed paths:
    graphics/font.cpp


diff --git a/graphics/font.cpp b/graphics/font.cpp
index 2b42a28fe63..96f018c8351 100644
--- a/graphics/font.cpp
+++ b/graphics/font.cpp
@@ -511,8 +511,8 @@ TextAlign convertTextAlignH(TextAlign alignH, bool rtl) {
 #define wholedivide(x, y)	(((x)+((y)-1))/(y))
 
 static void countupScore(int *dstGray, int x, int y, int bbw, int bbh, float scale) {
-	int newbbw = (int)(roundf((float)bbw * scale));
-	int newbbh = (int)(roundf((float)bbh * scale));
+	int newbbw = bbw * scale;
+	int newbbh = bbh * scale;
 	int x_ = x * newbbw;
 	int y_ = y * newbbh;
 	int x1 = x_ + newbbw;


Commit: 75ebd7b28c23c3df20bb5a261e9dbdfb12d6a5d6
    https://github.com/scummvm/scummvm/commit/75ebd7b28c23c3df20bb5a261e9dbdfb12d6a5d6
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Added more content to HelpDialog

Changed paths:
    gui/helpdialog.cpp


diff --git a/gui/helpdialog.cpp b/gui/helpdialog.cpp
index 34d17db25d2..e5ea77fe025 100644
--- a/gui/helpdialog.cpp
+++ b/gui/helpdialog.cpp
@@ -51,10 +51,24 @@ HelpDialog::HelpDialog()
 	TabWidget *tab = new TabWidget(this, 10, 10, _w - 10, tabHeight, ThemeEngine::kTextAlignVBottom);
 
 	tab->addTab(_("General"), "GlobalOptions_Graphics", false);
+	Common::U32String helpText1 = _(
+"### Where to get the games\n"
+"\n"
+"Many games supported by ScummVM can still be bought from companies at the links below. Not all games on this list are supported by ScummVM, please check the compatibility page beforehand.\n"
+"\n"
+"Several games have been released for free legal download by their respective copyright holders. You can download them from [our website](https://scummvm.org/games).\n"
+"\n"
+"For other (out of print) games try Amazon, eBay, Game Trading Zone or other auction sites but beware of faulty games (e.g., scratched discs) and illegal game copies (e.g., from Butterfly Media).\n"
+"\n"
+"The ScummVM team does not recommend any individual supplier of games and this list is for reference purposes only. The ScummVM project does get a cut from every purchase on [GOG.com](https://www.gog.com/?pp=22d200f8670dbdb3e253a90eee5098477c95c23d) and [ZOOM-Platform](https://www.zoom-platform.com/?affiliate=c049516c-9c4c-42d6-8649-92ed870e8b53) through one of the links with the added affiliate referrer though.\n"
+	);
+
+	new RichTextWidget(tab, 10, 10, _w - 10, tabHeight - buttonHeight - 10, helpText1);
+
 	tab->addTab(_("Controls"), "GlobalOptions_Graphics", false);
 
-	Common::U32String helpText = _(
-"# Touch controls\n"
+	Common::U32String helpText2 = _(
+"### Touch controls\n"
 "\n"
 "The touch control scheme can be configured in the global settings. From the Launcher, go to **Options > Backend > Choose the preferred touch mode**.\n"
 "It's possible to configure the touch mode for three situations (ScummVM menus, 2D games and 3D games) and choose one of the three possible modes:\n"
@@ -92,7 +106,7 @@ HelpDialog::HelpDialog()
 "\n"
 	);
 
-	new RichTextWidget(tab, 10, 10, _w - 40, tabHeight - buttonHeight - 10, helpText);
+	new RichTextWidget(tab, 10, 10, _w - 10, tabHeight - buttonHeight - 10, helpText2);
 
 	tab->addTab(_("Adding Games"), "GlobalOptions_Graphics", false);
 	tab->addTab(_("Paths"), "GlobalOptions_Graphics", false);


Commit: 28b14d83fcd3dbf666293c0863b70181b9ac5cc7
    https://github.com/scummvm/scummvm/commit/28b14d83fcd3dbf666293c0863b70181b9ac5cc7
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Initial code for vertical scroll bar in RichTextWidget

Changed paths:
    gui/widgets/richtext.cpp
    gui/widgets/richtext.h


diff --git a/gui/widgets/richtext.cpp b/gui/widgets/richtext.cpp
index 5eea52a4d56..cc11a4bd7fc 100644
--- a/gui/widgets/richtext.cpp
+++ b/gui/widgets/richtext.cpp
@@ -24,12 +24,14 @@
 
 #include "graphics/macgui/mactextwindow.h"
 
-#include "gui/widgets/richtext.h"
 #include "gui/gui-manager.h"
 
 #include "gui/ThemeEngine.h"
 #include "gui/ThemeEval.h"
 
+#include "gui/widgets/richtext.h"
+#include "gui/widgets/scrollbar.h"
+
 namespace GUI {
 
 Graphics::MacWindowManager *_wm = nullptr;
@@ -50,12 +52,10 @@ void ensureWM() {
 
 RichTextWidget::RichTextWidget(GuiObject *boss, int x, int y, int w, int h, bool scale, const Common::U32String &text, const Common::U32String &tooltip)
 	: Widget(boss, x, y, w, h, scale, tooltip)  {
-	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_RETAIN_FOCUS | WIDGET_WANT_TICKLE);
-	_type = kRichTextWidget;
 
 	_text = text;
 
-	ensureWM();
+	init();
 }
 
 RichTextWidget::RichTextWidget(GuiObject *boss, int x, int y, int w, int h, const Common::U32String &text, const Common::U32String &tooltip)
@@ -64,11 +64,46 @@ RichTextWidget::RichTextWidget(GuiObject *boss, int x, int y, int w, int h, cons
 
 RichTextWidget::RichTextWidget(GuiObject *boss, const Common::String &name, const Common::U32String &text, const Common::U32String &tooltip)
 	: Widget(boss, name, tooltip) {
+
+	_text = text;
+
+	init();
+}
+
+void RichTextWidget::init() {
 	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_RETAIN_FOCUS | WIDGET_WANT_TICKLE);
+
 	_type = kRichTextWidget;
-	_text = text;
+
+	_verticalScroll = new ScrollBarWidget(this, _w, 0, 16, _h);
+	_verticalScroll->setTarget(this);
+	_scrolledX = 0;
+	_scrolledY = 0;
 
 	ensureWM();
+
+	_limitH = 140;
+}
+
+void RichTextWidget::recalc() {
+	_scrollbarWidth = g_gui.xmlEval()->getVar("Globals.Scrollbar.Width", 0);
+	_limitH = _h;
+
+	//calculate virtual height
+	const int spacing = g_gui.xmlEval()->getVar("Global.Font.Height", 16); //on the bottom
+	int min = spacing, max = 0;
+
+	int h = max - min;
+
+	if (h <= _limitH) _scrolledY = 0;
+	if (_scrolledY > h - _limitH) _scrolledY = 0;
+
+	_verticalScroll->_numEntries = h;
+	_verticalScroll->_currentPos = _scrolledY;
+	_verticalScroll->_entriesPerPage = _limitH;
+	_verticalScroll->_singleStep = kLineHeight;
+	_verticalScroll->setPos(_w, _scrolledY);
+	_verticalScroll->setSize(_scrollbarWidth, _limitH-1);
 }
 
 void RichTextWidget::createWidget() {
@@ -92,6 +127,8 @@ void RichTextWidget::createWidget() {
 	_txtWnd->setMarkdownText(_text);
 
 	_surface = new Graphics::ManagedSurface(_w, _h, _wm->_pixelformat);
+
+	recalc();
 }
 
 void RichTextWidget::reflowLayout() {
@@ -108,8 +145,14 @@ void RichTextWidget::drawWidget() {
 	_txtWnd->draw(_surface);
 
 	g_gui.theme()->drawManagedSurface(Common::Point(_x, _y), *_surface);
+}
+
+void RichTextWidget::draw() {
+	Widget::draw();
 
-	warning("DRAW");
+	if (_verticalScroll->isVisible()) {
+		_verticalScroll->draw();
+	}
 }
 
 } // End of namespace GUI
diff --git a/gui/widgets/richtext.h b/gui/widgets/richtext.h
index 1728cb7841a..2ddfbe4074a 100644
--- a/gui/widgets/richtext.h
+++ b/gui/widgets/richtext.h
@@ -32,6 +32,8 @@ class ManagedSurface;
 
 namespace GUI {
 
+class ScrollBarWidget;
+
 /* RichTextWidget */
 class RichTextWidget : public Widget {
 protected:
@@ -39,14 +41,23 @@ protected:
 	Graphics::ManagedSurface *_surface = nullptr;
 	Common::U32String _text;
 
+	ScrollBarWidget *_verticalScroll;
+	int16 _scrolledX, _scrolledY;
+	int _scrollbarWidth;
+	uint16 _limitH;
+	uint32 _reflowCmd;
+
 public:
 	RichTextWidget(GuiObject *boss, int x, int y, int w, int h, bool scale, const Common::U32String &text, const Common::U32String &tooltip = Common::U32String());
 	RichTextWidget(GuiObject *boss, int x, int y, int w, int h, const Common::U32String &text, const Common::U32String &tooltip = Common::U32String());
 	RichTextWidget(GuiObject *boss, const Common::String &name, const Common::U32String &text, const Common::U32String &tooltip = Common::U32String());
 
 	void reflowLayout() override;
+	void draw() override;
 
 protected:
+	void init();
+	void recalc();
 	void drawWidget() override;
 	void createWidget();
 };


Commit: daf829aeb43dabf7bc1fd038f774e1bbbe2e921e
    https://github.com/scummvm/scummvm/commit/daf829aeb43dabf7bc1fd038f774e1bbbe2e921e
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Replace MacTextWindow with plain MacText in RichTextWidget

Changed paths:
    gui/widgets/richtext.cpp
    gui/widgets/richtext.h


diff --git a/gui/widgets/richtext.cpp b/gui/widgets/richtext.cpp
index cc11a4bd7fc..2a51eb93ff3 100644
--- a/gui/widgets/richtext.cpp
+++ b/gui/widgets/richtext.cpp
@@ -22,7 +22,7 @@
 #include "common/system.h"
 #include "common/unicode-bidi.h"
 
-#include "graphics/macgui/mactextwindow.h"
+#include "graphics/macgui/mactext.h"
 
 #include "gui/gui-manager.h"
 
@@ -107,23 +107,13 @@ void RichTextWidget::recalc() {
 }
 
 void RichTextWidget::createWidget() {
-	uint32 white = _wm->_pixelformat.RGBToColor(0, 0, 0);
+	uint32 white = _wm->_pixelformat.RGBToColor(0xff, 0xff, 0xff);
 	TextColorData *normal = g_gui.theme()->getTextColorData(kTextColorNormal);
 	uint32 black = _wm->_pixelformat.RGBToColor(normal->r, normal->g, normal->b);
-	black = _wm->_pixelformat.RGBToColor(1, 1, 1);
 
 	Graphics::MacFont macFont(Graphics::kMacFontNewYork, 30, Graphics::kMacFontRegular);
 
-	_txtWnd = _wm->addTextWindow(&macFont, black, white, _w, Graphics::kTextAlignLeft, nullptr, false);
-	_txtWnd->setTextColorRGB(black);
-	_txtWnd->setBorderType(Graphics::kWindowBorderMacOSNoBorderScrollbar);
-	_txtWnd->enableScrollbar(true);
-	// it will hide the scrollbar when the text height is smaller than the window height
-	_txtWnd->setMode(Graphics::kWindowModeDynamicScrollbar);
-	_txtWnd->resize(_w, _h);
-	_txtWnd->setEditable(false);
-	_txtWnd->setSelectable(false);
-
+	_txtWnd = new Graphics::MacText(Common::U32String(), _wm, &macFont, black, white, _w, Graphics::kTextAlignLeft);
 	_txtWnd->setMarkdownText(_text);
 
 	_surface = new Graphics::ManagedSurface(_w, _h, _wm->_pixelformat);
@@ -142,7 +132,7 @@ void RichTextWidget::drawWidget() {
 	g_gui.theme()->drawWidgetBackground(Common::Rect(_x, _y, _x + _w, _y + _h),
 	                                    ThemeEngine::kWidgetBackgroundEditText);
 
-	_txtWnd->draw(_surface);
+	_txtWnd->draw(_surface, 0, 0, _w, _h, 0, 0);
 
 	g_gui.theme()->drawManagedSurface(Common::Point(_x, _y), *_surface);
 }
diff --git a/gui/widgets/richtext.h b/gui/widgets/richtext.h
index 2ddfbe4074a..04cb2090c18 100644
--- a/gui/widgets/richtext.h
+++ b/gui/widgets/richtext.h
@@ -26,7 +26,7 @@
 #include "gui/widget.h"
 
 namespace Graphics {
-class MacTextWindow;
+class MacText;
 class ManagedSurface;
 }
 
@@ -37,7 +37,7 @@ class ScrollBarWidget;
 /* RichTextWidget */
 class RichTextWidget : public Widget {
 protected:
-	Graphics::MacTextWindow *_txtWnd = nullptr;
+	Graphics::MacText *_txtWnd = nullptr;
 	Graphics::ManagedSurface *_surface = nullptr;
 	Common::U32String _text;
 


Commit: bb4792e413ae018fc3a335d3ca765e4be279d724
    https://github.com/scummvm/scummvm/commit/bb4792e413ae018fc3a335d3ca765e4be279d724
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: MOre work on adding scrollbar to RichTextWidget

Changed paths:
    gui/widgets/richtext.cpp
    gui/widgets/richtext.h


diff --git a/gui/widgets/richtext.cpp b/gui/widgets/richtext.cpp
index 2a51eb93ff3..32eac9511ef 100644
--- a/gui/widgets/richtext.cpp
+++ b/gui/widgets/richtext.cpp
@@ -71,7 +71,7 @@ RichTextWidget::RichTextWidget(GuiObject *boss, const Common::String &name, cons
 }
 
 void RichTextWidget::init() {
-	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_RETAIN_FOCUS | WIDGET_WANT_TICKLE);
+	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG);
 
 	_type = kRichTextWidget;
 
@@ -80,6 +80,8 @@ void RichTextWidget::init() {
 	_scrolledX = 0;
 	_scrolledY = 0;
 
+	_scrollbarWidth = g_gui.xmlEval()->getVar("Globals.Scrollbar.Width", 0);
+
 	ensureWM();
 
 	_limitH = 140;
@@ -89,11 +91,7 @@ void RichTextWidget::recalc() {
 	_scrollbarWidth = g_gui.xmlEval()->getVar("Globals.Scrollbar.Width", 0);
 	_limitH = _h;
 
-	//calculate virtual height
-	const int spacing = g_gui.xmlEval()->getVar("Global.Font.Height", 16); //on the bottom
-	int min = spacing, max = 0;
-
-	int h = max - min;
+	int h = _txtWnd->getTextHeight();
 
 	if (h <= _limitH) _scrolledY = 0;
 	if (_scrolledY > h - _limitH) _scrolledY = 0;
@@ -104,6 +102,8 @@ void RichTextWidget::recalc() {
 	_verticalScroll->_singleStep = kLineHeight;
 	_verticalScroll->setPos(_w, _scrolledY);
 	_verticalScroll->setSize(_scrollbarWidth, _limitH-1);
+
+	warning("STUB: Text width recalc");
 }
 
 void RichTextWidget::createWidget() {
@@ -113,7 +113,7 @@ void RichTextWidget::createWidget() {
 
 	Graphics::MacFont macFont(Graphics::kMacFontNewYork, 30, Graphics::kMacFontRegular);
 
-	_txtWnd = new Graphics::MacText(Common::U32String(), _wm, &macFont, black, white, _w, Graphics::kTextAlignLeft);
+	_txtWnd = new Graphics::MacText(Common::U32String(), _wm, &macFont, black, white, _w - _scrollbarWidth, Graphics::kTextAlignLeft);
 	_txtWnd->setMarkdownText(_text);
 
 	_surface = new Graphics::ManagedSurface(_w, _h, _wm->_pixelformat);
@@ -123,6 +123,14 @@ void RichTextWidget::createWidget() {
 
 void RichTextWidget::reflowLayout() {
 	Widget::reflowLayout();
+
+	if (!_txtWnd)
+		createWidget();
+
+	recalc();
+
+	_verticalScroll->setVisible(_verticalScroll->_numEntries > _limitH); //show when there is something to scroll
+	_verticalScroll->recalc();
 }
 
 void RichTextWidget::drawWidget() {
@@ -132,9 +140,11 @@ void RichTextWidget::drawWidget() {
 	g_gui.theme()->drawWidgetBackground(Common::Rect(_x, _y, _x + _w, _y + _h),
 	                                    ThemeEngine::kWidgetBackgroundEditText);
 
-	_txtWnd->draw(_surface, 0, 0, _w, _h, 0, 0);
+	_txtWnd->draw(_surface, 0, 0, _w - _scrollbarWidth, _h, 0, 0);
 
 	g_gui.theme()->drawManagedSurface(Common::Point(_x, _y), *_surface);
+
+	warning("drawWidget()");
 }
 
 void RichTextWidget::draw() {
@@ -143,6 +153,28 @@ void RichTextWidget::draw() {
 	if (_verticalScroll->isVisible()) {
 		_verticalScroll->draw();
 	}
+
+}
+
+void RichTextWidget::markAsDirty() {
+	Widget::markAsDirty();
+
+	if (_verticalScroll->isVisible()) {
+		_verticalScroll->markAsDirty();
+	}
+}
+
+bool RichTextWidget::containsWidget(Widget *w) const {
+	if (w == _verticalScroll || _verticalScroll->containsWidget(w))
+		return true;
+	return false;
+}
+
+Widget *RichTextWidget::findWidget(int x, int y) {
+	if (_verticalScroll->isVisible() && x >= _w)
+		return _verticalScroll;
+
+	return this;
 }
 
 } // End of namespace GUI
diff --git a/gui/widgets/richtext.h b/gui/widgets/richtext.h
index 04cb2090c18..042cc081ca0 100644
--- a/gui/widgets/richtext.h
+++ b/gui/widgets/richtext.h
@@ -55,11 +55,16 @@ public:
 	void reflowLayout() override;
 	void draw() override;
 
+	void markAsDirty() override;
+
+	bool containsWidget(Widget *) const override;
+
 protected:
 	void init();
 	void recalc();
 	void drawWidget() override;
 	void createWidget();
+	Widget *findWidget(int x, int y) override;
 };
 
 } // End of namespace GUI


Commit: 75fa02a879002b50d24a767af1f3e929fd848870
    https://github.com/scummvm/scummvm/commit/75fa02a879002b50d24a767af1f3e929fd848870
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Implement scrolling in RichTextWidget

Changed paths:
    gui/widgets/richtext.cpp
    gui/widgets/richtext.h


diff --git a/gui/widgets/richtext.cpp b/gui/widgets/richtext.cpp
index 32eac9511ef..c81cfac28b4 100644
--- a/gui/widgets/richtext.cpp
+++ b/gui/widgets/richtext.cpp
@@ -51,7 +51,7 @@ void ensureWM() {
 }
 
 RichTextWidget::RichTextWidget(GuiObject *boss, int x, int y, int w, int h, bool scale, const Common::U32String &text, const Common::U32String &tooltip)
-	: Widget(boss, x, y, w, h, scale, tooltip)  {
+	: Widget(boss, x, y, w, h, scale, tooltip), CommandSender(nullptr)  {
 
 	_text = text;
 
@@ -63,7 +63,7 @@ RichTextWidget::RichTextWidget(GuiObject *boss, int x, int y, int w, int h, cons
 }
 
 RichTextWidget::RichTextWidget(GuiObject *boss, const Common::String &name, const Common::U32String &text, const Common::U32String &tooltip)
-	: Widget(boss, name, tooltip) {
+	: Widget(boss, name, tooltip), CommandSender(nullptr)  {
 
 	_text = text;
 
@@ -87,6 +87,23 @@ void RichTextWidget::init() {
 	_limitH = 140;
 }
 
+void RichTextWidget::handleMouseWheel(int x, int y, int direction) {
+	_verticalScroll->handleMouseWheel(x, y, direction);
+}
+
+void RichTextWidget::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
+	Widget::handleCommand(sender, cmd, data);
+	switch (cmd) {
+	case kSetPositionCmd:
+		_scrolledY = _verticalScroll->_currentPos;
+		reflowLayout();
+		g_gui.scheduleTopDialogRedraw();
+		break;
+	default:
+		break;
+	}
+}
+
 void RichTextWidget::recalc() {
 	_scrollbarWidth = g_gui.xmlEval()->getVar("Globals.Scrollbar.Width", 0);
 	_limitH = _h;
@@ -99,7 +116,7 @@ void RichTextWidget::recalc() {
 	_verticalScroll->_numEntries = h;
 	_verticalScroll->_currentPos = _scrolledY;
 	_verticalScroll->_entriesPerPage = _limitH;
-	_verticalScroll->_singleStep = kLineHeight;
+	_verticalScroll->_singleStep = 30 * 3;
 	_verticalScroll->setPos(_w, _scrolledY);
 	_verticalScroll->setSize(_scrollbarWidth, _limitH-1);
 
@@ -140,7 +157,9 @@ void RichTextWidget::drawWidget() {
 	g_gui.theme()->drawWidgetBackground(Common::Rect(_x, _y, _x + _w, _y + _h),
 	                                    ThemeEngine::kWidgetBackgroundEditText);
 
-	_txtWnd->draw(_surface, 0, 0, _w - _scrollbarWidth, _h, 0, 0);
+	_surface->clear(_wm->_pixelformat.RGBToColor(0xff, 0xff, 0xff));
+
+	_txtWnd->draw(_surface, 0, _scrolledY, _w - _scrollbarWidth, _h, 0, 0);
 
 	g_gui.theme()->drawManagedSurface(Common::Point(_x, _y), *_surface);
 
diff --git a/gui/widgets/richtext.h b/gui/widgets/richtext.h
index 042cc081ca0..fbf86be1544 100644
--- a/gui/widgets/richtext.h
+++ b/gui/widgets/richtext.h
@@ -35,7 +35,7 @@ namespace GUI {
 class ScrollBarWidget;
 
 /* RichTextWidget */
-class RichTextWidget : public Widget {
+class RichTextWidget : public Widget, public CommandSender {
 protected:
 	Graphics::MacText *_txtWnd = nullptr;
 	Graphics::ManagedSurface *_surface = nullptr;
@@ -55,6 +55,9 @@ public:
 	void reflowLayout() override;
 	void draw() override;
 
+	void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
+	void handleMouseWheel(int x, int y, int direction) override;
+
 	void markAsDirty() override;
 
 	bool containsWidget(Widget *) const override;


Commit: 255a20589681f09573bc04b14e9d86115fc676c3
    https://github.com/scummvm/scummvm/commit/255a20589681f09573bc04b14e9d86115fc676c3
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Improved visuals in HelpDialog

Changed paths:
    gui/helpdialog.cpp


diff --git a/gui/helpdialog.cpp b/gui/helpdialog.cpp
index e5ea77fe025..797a8882bd1 100644
--- a/gui/helpdialog.cpp
+++ b/gui/helpdialog.cpp
@@ -48,11 +48,11 @@ HelpDialog::HelpDialog()
 
 	int tabHeight = _h - (buttonHeight + 10) * 5 / 2;
 
-	TabWidget *tab = new TabWidget(this, 10, 10, _w - 10, tabHeight, ThemeEngine::kTextAlignVBottom);
+	TabWidget *tab = new TabWidget(this, 10, 10, _w - 10, tabHeight);
 
 	tab->addTab(_("General"), "GlobalOptions_Graphics", false);
 	Common::U32String helpText1 = _(
-"### Where to get the games\n"
+"#### Where to get the games\n"
 "\n"
 "Many games supported by ScummVM can still be bought from companies at the links below. Not all games on this list are supported by ScummVM, please check the compatibility page beforehand.\n"
 "\n"
@@ -68,7 +68,7 @@ HelpDialog::HelpDialog()
 	tab->addTab(_("Controls"), "GlobalOptions_Graphics", false);
 
 	Common::U32String helpText2 = _(
-"### Touch controls\n"
+"#### Touch controls\n"
 "\n"
 "The touch control scheme can be configured in the global settings. From the Launcher, go to **Options > Backend > Choose the preferred touch mode**.\n"
 "It's possible to configure the touch mode for three situations (ScummVM menus, 2D games and 3D games) and choose one of the three possible modes:\n"
@@ -82,25 +82,25 @@ HelpDialog::HelpDialog()
 "\n"
 "To display or hide the small controller icon, from the Launcher select **Options** and then the **Backend** tab. Tick the **Show on-screen control** box to enable the controller icon.\n"
 "\n"
-"## Two finger tap\n"
+"#### Two finger tap\n"
 "\n"
 "To do a two finger tap, hold one finger down and then tap with a second finger.\n"
 "\n"
-"## Three finger tap\n"
+"#### Three finger tap\n"
 "\n"
 "To do a three finger tap, start with holding down one finger and progressively touch down the other two fingers, one at a time, while still holding down the previous fingers. Imagine you are impatiently tapping your fingers on a surface, but then slow down that movement so it is rhythmic, but not too slow.\n"
 "\n"
-"## Immersive Sticky fullscreen mode\n"
+"#### Immersive Sticky fullscreen mode\n"
 "\n"
 "ScummVM for Android uses the Immersive Sticky fullscreen mode, which means that the Android system bar is hidden until the user swipes from an edge with a system bar. Swipe from the edge to reveal the system bars.  They remain semi-transparent and disappear after a few seconds unless you interact with them. Your swipe also registers in the game, so if you need to swipe from an edge with system bars, your game play is not interrupted.\n"
 "\n"
-"## Global Main Menu\n"
+"#### Global Main Menu\n"
 "\n"
 "To open the Global Main Menu, tap on the small menu icon at the top right of the screen.\n"
 "\n"
 "To display or hide the small menu icon, from the Launcher select **Options** and then the **Backend** tab. Tick the **Show on-screen control** box to enable the menu icon.\n"
 "\n"
-"## Virtual keyboard\n"
+"#### Virtual keyboard\n"
 "\n"
 "To open the virtual keyboard, long press on the small controller icon at the top right of the screen, or tap on any editable text field. To hide the virtual keyboard, tap the small controller icon (which became a keyboard one) again, or tap outside the text field.\n"
 "\n"


Commit: 20a0888c8d4b9c3e1cc27c50de75fdcc01018a1d
    https://github.com/scummvm/scummvm/commit/20a0888c8d4b9c3e1cc27c50de75fdcc01018a1d
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Fix scrollbar behavior in RichTextWidget

Changed paths:
    gui/widgets/richtext.cpp


diff --git a/gui/widgets/richtext.cpp b/gui/widgets/richtext.cpp
index c81cfac28b4..7892d45a900 100644
--- a/gui/widgets/richtext.cpp
+++ b/gui/widgets/richtext.cpp
@@ -117,10 +117,10 @@ void RichTextWidget::recalc() {
 	_verticalScroll->_currentPos = _scrolledY;
 	_verticalScroll->_entriesPerPage = _limitH;
 	_verticalScroll->_singleStep = 30 * 3;
-	_verticalScroll->setPos(_w, _scrolledY);
+	_verticalScroll->setPos(_w - _scrollbarWidth - _x, 0);
 	_verticalScroll->setSize(_scrollbarWidth, _limitH-1);
 
-	warning("STUB: Text width recalc");
+	_txtWnd->setMaxWidth(_w - _scrollbarWidth - _x);
 }
 
 void RichTextWidget::createWidget() {
@@ -162,8 +162,6 @@ void RichTextWidget::drawWidget() {
 	_txtWnd->draw(_surface, 0, _scrolledY, _w - _scrollbarWidth, _h, 0, 0);
 
 	g_gui.theme()->drawManagedSurface(Common::Point(_x, _y), *_surface);
-
-	warning("drawWidget()");
 }
 
 void RichTextWidget::draw() {
@@ -172,7 +170,6 @@ void RichTextWidget::draw() {
 	if (_verticalScroll->isVisible()) {
 		_verticalScroll->draw();
 	}
-
 }
 
 void RichTextWidget::markAsDirty() {
@@ -190,7 +187,7 @@ bool RichTextWidget::containsWidget(Widget *w) const {
 }
 
 Widget *RichTextWidget::findWidget(int x, int y) {
-	if (_verticalScroll->isVisible() && x >= _w)
+	if (_verticalScroll->isVisible() && x >= _w - _scrollbarWidth - _x)
 		return _verticalScroll;
 
 	return this;


Commit: df0fa2c709d4408457fb60a0374ff92cf78a82b1
    https://github.com/scummvm/scummvm/commit/df0fa2c709d4408457fb60a0374ff92cf78a82b1
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Simplified text width calcultions in RichTextWidget

Changed paths:
    gui/widgets/richtext.cpp
    gui/widgets/richtext.h


diff --git a/gui/widgets/richtext.cpp b/gui/widgets/richtext.cpp
index 7892d45a900..bbdd4109941 100644
--- a/gui/widgets/richtext.cpp
+++ b/gui/widgets/richtext.cpp
@@ -82,6 +82,8 @@ void RichTextWidget::init() {
 
 	_scrollbarWidth = g_gui.xmlEval()->getVar("Globals.Scrollbar.Width", 0);
 
+	_textWidth = _w - _scrollbarWidth - _x;
+
 	ensureWM();
 
 	_limitH = 140;
@@ -107,6 +109,7 @@ void RichTextWidget::handleCommand(CommandSender *sender, uint32 cmd, uint32 dat
 void RichTextWidget::recalc() {
 	_scrollbarWidth = g_gui.xmlEval()->getVar("Globals.Scrollbar.Width", 0);
 	_limitH = _h;
+	_textWidth = _w - _scrollbarWidth - _x;
 
 	int h = _txtWnd->getTextHeight();
 
@@ -117,10 +120,10 @@ void RichTextWidget::recalc() {
 	_verticalScroll->_currentPos = _scrolledY;
 	_verticalScroll->_entriesPerPage = _limitH;
 	_verticalScroll->_singleStep = 30 * 3;
-	_verticalScroll->setPos(_w - _scrollbarWidth - _x, 0);
+	_verticalScroll->setPos(_textWidth, 0);
 	_verticalScroll->setSize(_scrollbarWidth, _limitH-1);
 
-	_txtWnd->setMaxWidth(_w - _scrollbarWidth - _x);
+	_txtWnd->setMaxWidth(_textWidth);
 }
 
 void RichTextWidget::createWidget() {
@@ -130,7 +133,7 @@ void RichTextWidget::createWidget() {
 
 	Graphics::MacFont macFont(Graphics::kMacFontNewYork, 30, Graphics::kMacFontRegular);
 
-	_txtWnd = new Graphics::MacText(Common::U32String(), _wm, &macFont, black, white, _w - _scrollbarWidth, Graphics::kTextAlignLeft);
+	_txtWnd = new Graphics::MacText(Common::U32String(), _wm, &macFont, black, white, _textWidth, Graphics::kTextAlignLeft);
 	_txtWnd->setMarkdownText(_text);
 
 	_surface = new Graphics::ManagedSurface(_w, _h, _wm->_pixelformat);
@@ -155,7 +158,7 @@ void RichTextWidget::drawWidget() {
 		createWidget();
 
 	g_gui.theme()->drawWidgetBackground(Common::Rect(_x, _y, _x + _w, _y + _h),
-	                                    ThemeEngine::kWidgetBackgroundEditText);
+	                                    ThemeEngine::kWidgetBackgroundPlain);
 
 	_surface->clear(_wm->_pixelformat.RGBToColor(0xff, 0xff, 0xff));
 
diff --git a/gui/widgets/richtext.h b/gui/widgets/richtext.h
index fbf86be1544..1cc3220bb0c 100644
--- a/gui/widgets/richtext.h
+++ b/gui/widgets/richtext.h
@@ -46,6 +46,7 @@ protected:
 	int _scrollbarWidth;
 	uint16 _limitH;
 	uint32 _reflowCmd;
+	int _textWidth;
 
 public:
 	RichTextWidget(GuiObject *boss, int x, int y, int w, int h, bool scale, const Common::U32String &text, const Common::U32String &tooltip = Common::U32String());


Commit: 72f57a670e27f626de70e06ec33f3098637dbb29
    https://github.com/scummvm/scummvm/commit/72f57a670e27f626de70e06ec33f3098637dbb29
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: Removed temporary debug output

Changed paths:
    graphics/VectorRendererSpec.cpp


diff --git a/graphics/VectorRendererSpec.cpp b/graphics/VectorRendererSpec.cpp
index 17cb6593931..99b7afcd474 100644
--- a/graphics/VectorRendererSpec.cpp
+++ b/graphics/VectorRendererSpec.cpp
@@ -4146,8 +4146,8 @@ drawTabAlg(int x1, int y1, int w, int h, int r, PixelType color, VectorRenderer:
 			}
 		}
 	} else {
-		PixelType color1, color2;
-		color1 = color2 = color;
+		PixelType color1, color2, color3, color4;
+		color1 = color2 = color3 = color4 = color;
 
 		int long_h = h;
 		int short_h = h - real_radius;


Commit: a00ae5478591d9547f92e3fe4dfdb8d35c2dfdce
    https://github.com/scummvm/scummvm/commit/a00ae5478591d9547f92e3fe4dfdb8d35c2dfdce
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Fixed default text color in RechText

Changed paths:
    gui/widgets/richtext.cpp


diff --git a/gui/widgets/richtext.cpp b/gui/widgets/richtext.cpp
index bbdd4109941..1b7b4eb0f21 100644
--- a/gui/widgets/richtext.cpp
+++ b/gui/widgets/richtext.cpp
@@ -127,13 +127,13 @@ void RichTextWidget::recalc() {
 }
 
 void RichTextWidget::createWidget() {
-	uint32 white = _wm->_pixelformat.RGBToColor(0xff, 0xff, 0xff);
+	uint32 bg = _wm->_pixelformat.ARGBToColor(0, 0xff, 0xff, 0xff); // transparent
 	TextColorData *normal = g_gui.theme()->getTextColorData(kTextColorNormal);
-	uint32 black = _wm->_pixelformat.RGBToColor(normal->r, normal->g, normal->b);
+	uint32 fg = _wm->_pixelformat.RGBToColor(normal->r, normal->g, normal->b);
 
 	Graphics::MacFont macFont(Graphics::kMacFontNewYork, 30, Graphics::kMacFontRegular);
 
-	_txtWnd = new Graphics::MacText(Common::U32String(), _wm, &macFont, black, white, _textWidth, Graphics::kTextAlignLeft);
+	_txtWnd = new Graphics::MacText(Common::U32String(), _wm, &macFont, fg, bg, _textWidth, Graphics::kTextAlignLeft);
 	_txtWnd->setMarkdownText(_text);
 
 	_surface = new Graphics::ManagedSurface(_w, _h, _wm->_pixelformat);
@@ -157,10 +157,9 @@ void RichTextWidget::drawWidget() {
 	if (!_txtWnd)
 		createWidget();
 
-	g_gui.theme()->drawWidgetBackground(Common::Rect(_x, _y, _x + _w, _y + _h),
-	                                    ThemeEngine::kWidgetBackgroundPlain);
+	g_gui.theme()->drawWidgetBackground(Common::Rect(_x, _y, _x + _w, _y + _h), ThemeEngine::kWidgetBackgroundPlain);
 
-	_surface->clear(_wm->_pixelformat.RGBToColor(0xff, 0xff, 0xff));
+	_surface->clear(_wm->_pixelformat.ARGBToColor(0, 0xff, 0xff, 0xff)); // transparent
 
 	_txtWnd->draw(_surface, 0, _scrolledY, _w - _scrollbarWidth, _h, 0, 0);
 


Commit: 85f54663f53661b1019c209c72bfd0647deacc88
    https://github.com/scummvm/scummvm/commit/85f54663f53661b1019c209c72bfd0647deacc88
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: JANITORIAL: More logical text formatting processing sequence

Changed paths:
    graphics/macgui/mactext.cpp


diff --git a/graphics/macgui/mactext.cpp b/graphics/macgui/mactext.cpp
index b930b746f11..7f2e483a270 100644
--- a/graphics/macgui/mactext.cpp
+++ b/graphics/macgui/mactext.cpp
@@ -701,35 +701,35 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 				s++;
 
 				// First two digits is slant, third digit is Header number
-				if (*s == '-') { // \016-XXY  -- closing textSlant, H<Y>
+				if (*s == '+') { // \016+XXY  -- opening textSlant. H<Y>
 					uint16 textSlant, headSize;
 					s++;
 
 					s = readHex(&textSlant, s, 2);
 
-					current_format.textSlant &= ~textSlant; // Clearing the specified bit
+					current_format.textSlant |= textSlant; // Setting the specified bit
 
 					s = readHex(&headSize, s, 1);
-					if (headSize == 0xf) // reset
-						current_format.fontSize = _defaultFormatting.fontSize;
+					if (headSize >= 1 && headSize <= 6) { // set
+						const float sizes[] = { 1, 3.0f, 2.5f, 2.0f, 1.75f, 1.5f, 1.25f };
+						current_format.fontSize = _defaultFormatting.fontSize * sizes[headSize];
+					}
 
-					D(9, "** splitString-: fontId: %d, textSlant: %d, fontSize: %d,",
+					D(9, "** splitString+: fontId: %d, textSlant: %d, fontSize: %d,",
 							current_format.fontId, current_format.textSlant, current_format.fontSize);
-				} else if (*s == '+') { // \016+XXY  -- opening textSlant. H<Y>
+				} else if (*s == '-') { // \016-XXY  -- closing textSlant, H<Y>
 					uint16 textSlant, headSize;
 					s++;
 
 					s = readHex(&textSlant, s, 2);
 
-					current_format.textSlant |= textSlant; // Setting the specified bit
+					current_format.textSlant &= ~textSlant; // Clearing the specified bit
 
 					s = readHex(&headSize, s, 1);
-					if (headSize >= 1 && headSize <= 6) { // set
-						const float sizes[] = { 1, 3.0f, 2.5f, 2.0f, 1.75f, 1.5f, 1.25f };
-						current_format.fontSize = _defaultFormatting.fontSize * sizes[headSize];
-					}
+					if (headSize == 0xf) // reset
+						current_format.fontSize = _defaultFormatting.fontSize;
 
-					D(9, "** splitString+: fontId: %d, textSlant: %d, fontSize: %d,",
+					D(9, "** splitString-: fontId: %d, textSlant: %d, fontSize: %d,",
 							current_format.fontId, current_format.textSlant, current_format.fontSize);
 				} else {
 					uint16 fontId, textSlant, fontSize, palinfo1, palinfo2, palinfo3;


Commit: 5b00cc3f56890666d554db7ed7e64344eafc3c3a
    https://github.com/scummvm/scummvm/commit/5b00cc3f56890666d554db7ed7e64344eafc3c3a
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: Implemented color on/off for MacText

Changed paths:
    graphics/macgui/mactext.cpp
    graphics/macgui/macwindowmanager.cpp


diff --git a/graphics/macgui/mactext.cpp b/graphics/macgui/mactext.cpp
index 7f2e483a270..88862506f88 100644
--- a/graphics/macgui/mactext.cpp
+++ b/graphics/macgui/mactext.cpp
@@ -731,6 +731,27 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 
 					D(9, "** splitString-: fontId: %d, textSlant: %d, fontSize: %d,",
 							current_format.fontId, current_format.textSlant, current_format.fontSize);
+				} else if (*s == '[') { // \016[RRGGBB  -- setting color
+					uint16 palinfo1, palinfo2, palinfo3;
+					s++;
+
+					s = readHex(&palinfo1, s, 4);
+					s = readHex(&palinfo2, s, 4);
+					s = readHex(&palinfo3, s, 4);
+
+					current_format.palinfo1 = palinfo1;
+					current_format.palinfo2 = palinfo2;
+					current_format.palinfo3 = palinfo3;
+
+					D(9, "** splitString[: %02x%02x%02x", palinfo1, plinfo2, plainfo3);
+				} else if (*s == ']') { // \016]  -- setting default color
+					s++;
+
+					current_format.palinfo1 = _defaultFormatting.palinfo1;
+					current_format.palinfo2 = _defaultFormatting.palinfo2;
+					current_format.palinfo3 = _defaultFormatting.palinfo3;
+
+					D(9, "** splitString]: %02x%02x%02x", current_format.palinfo1, current_format.plinfo2, current_format.plainfo3);
 				} else {
 					uint16 fontId, textSlant, fontSize, palinfo1, palinfo2, palinfo3;
 
@@ -741,7 +762,7 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 					s = readHex(&palinfo2, s, 4);
 					s = readHex(&palinfo3, s, 4);
 
-					D(9, "** splitString: fontId: %d, textSlant: %d, fontSize: %d, p0: %x p1: %x p2: %x",
+					D(9, "** splitString: fontId: %d, textSlant: %d, fontSize: %d, p: %02x%02x%02x",
 							fontId, textSlant, fontSize, palinfo1, palinfo2, palinfo3);
 
 					current_format.setValues(_wm, fontId, textSlant, fontSize, palinfo1, palinfo2, palinfo3);
diff --git a/graphics/macgui/macwindowmanager.cpp b/graphics/macgui/macwindowmanager.cpp
index 393020c3a87..512e65b7fdd 100644
--- a/graphics/macgui/macwindowmanager.cpp
+++ b/graphics/macgui/macwindowmanager.cpp
@@ -584,8 +584,12 @@ Common::U32String stripFormat(const Common::U32String &str) {
 				}
 			} else if (*s == '\016') {	// human-readable format
 				s++;
-				if (*s == '+' || *s == '-')
-					s += 3;
+				if (*s == '+' || *s == '-') // style + header size
+					s += 4;
+				else if (*s == '[') // color information
+					s += 13;
+				else if (*s == ']') // default color
+					s += 1;
 				else
 					s += 22;
 			} else {


Commit: 6044980b2d4fe96e710dc752d396e2be48f3ed6b
    https://github.com/scummvm/scummvm/commit/6044980b2d4fe96e710dc752d396e2be48f3ed6b
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: Paint links blue in MarkDown

Changed paths:
    graphics/macgui/mactext-md.cpp
    graphics/macgui/mactext.cpp


diff --git a/graphics/macgui/mactext-md.cpp b/graphics/macgui/mactext-md.cpp
index 6e006e6b1ac..b53a17ab3e9 100644
--- a/graphics/macgui/mactext-md.cpp
+++ b/graphics/macgui/mactext-md.cpp
@@ -29,9 +29,9 @@ namespace Graphics {
 
 struct MDState {
 	Common::List<int> listNum;
+	uint32 linkr = 0, linkg = 0, linkb = 0;
 };
 
-
 void render_blockcode(Common::DataBuffer *ob, const Common::DataBuffer *text, const Common::DataBuffer *lang, void *opaque) {
 	if (!text)
 		return;
@@ -201,7 +201,16 @@ int render_link(Common::DataBuffer *ob, const Common::DataBuffer *link, const Co
 	if (!link)
 		return 0;
 
-	warning("STUB: render_link(%s, %s, %s)", PR(link), title ? PR(title) : 0, content ? PR(content) : 0);
+	MDState *mdstate = (MDState *)opaque;
+	const Common::DataBuffer *text = content ? content : link;
+
+	Common::String res = Common::String::format("\001" "\016+%02x0" "\001\016[%04x%04x%04x"
+		"%s" "\001\016]" "\001\016-%02x0", kMacFontUnderline, mdstate->linkr, mdstate->linkg, mdstate->linkb,
+		Common::String((const char *)text->data , text->size).c_str(), kMacFontUnderline);
+
+	bufput(ob, res.c_str(), res.size());
+
+	debug(1, "render_link(%s, %s, %s)", PR(link), title ? PR(title) : 0, content ? PR(content) : 0);
 	return 1;
 }
 
@@ -288,6 +297,11 @@ void MacText::setMarkdownText(const Common::U32String &str) {
 
 	MDState mdState;
 
+	// Set link color to blue
+	mdState.linkr = 0;
+	mdState.linkg = 0;
+	mdState.linkb = 0xff;
+
 	Common::SDMarkdown *md = sd_markdown_new(0, 16, &cb, &mdState);
 	sd_markdown_render(ob, ib->data, ib->size, md);
 	sd_markdown_free(md);
diff --git a/graphics/macgui/mactext.cpp b/graphics/macgui/mactext.cpp
index 88862506f88..f1c6ccfaf40 100644
--- a/graphics/macgui/mactext.cpp
+++ b/graphics/macgui/mactext.cpp
@@ -742,16 +742,18 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 					current_format.palinfo1 = palinfo1;
 					current_format.palinfo2 = palinfo2;
 					current_format.palinfo3 = palinfo3;
+					current_format.fgcolor  = _wm->findBestColor(palinfo1 & 0xff, palinfo2 & 0xff, palinfo3 & 0xff);
 
-					D(9, "** splitString[: %02x%02x%02x", palinfo1, plinfo2, plainfo3);
+					D(9, "** splitString[: %08x", fgcolor);
 				} else if (*s == ']') { // \016]  -- setting default color
 					s++;
 
 					current_format.palinfo1 = _defaultFormatting.palinfo1;
 					current_format.palinfo2 = _defaultFormatting.palinfo2;
 					current_format.palinfo3 = _defaultFormatting.palinfo3;
+					current_format.fgcolor = _defaultFormatting.fgcolor;
 
-					D(9, "** splitString]: %02x%02x%02x", current_format.palinfo1, current_format.plinfo2, current_format.plainfo3);
+					D(9, "** splitString]: %08x", current_format.fgcolor);
 				} else {
 					uint16 fontId, textSlant, fontSize, palinfo1, palinfo2, palinfo3;
 
@@ -762,11 +764,11 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 					s = readHex(&palinfo2, s, 4);
 					s = readHex(&palinfo3, s, 4);
 
-					D(9, "** splitString: fontId: %d, textSlant: %d, fontSize: %d, p: %02x%02x%02x",
-							fontId, textSlant, fontSize, palinfo1, palinfo2, palinfo3);
-
 					current_format.setValues(_wm, fontId, textSlant, fontSize, palinfo1, palinfo2, palinfo3);
 
+					D(9, "** splitString: fontId: %d, textSlant: %d, fontSize: %d, fg: %04x",
+							fontId, textSlant, fontSize, current_format.fgcolor);
+
 					// So far, we enforce single font here, though in the future, font size could be altered
 					if (!_macFontMode)
 						current_format.font = _defaultFormatting.font;


Commit: 125d3dfa69f29d9a3cd1d9a621403ad441a488c6
    https://github.com/scummvm/scummvm/commit/125d3dfa69f29d9a3cd1d9a621403ad441a488c6
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPPHICS: MACGUI: Added a way to get link under cursor for MacText

Changed paths:
    graphics/macgui/mactext.cpp
    graphics/macgui/mactext.h


diff --git a/graphics/macgui/mactext.cpp b/graphics/macgui/mactext.cpp
index f1c6ccfaf40..84683fea9c7 100644
--- a/graphics/macgui/mactext.cpp
+++ b/graphics/macgui/mactext.cpp
@@ -2096,6 +2096,18 @@ int MacText::getMouseLine(int x, int y) {
 	return row + 1;
 }
 
+Common::U32String MacText::getMouseLink(int x, int y) {
+	Common::Point offset = calculateOffset();
+	x -= getDimensions().left - offset.x;
+	y -= getDimensions().top - offset.y;
+	y += _scrollPos;
+
+	int row, chunk;
+	getRowCol(x, y, nullptr, nullptr, &row, nullptr, &chunk);
+
+	return _textLines[row].chunks[chunk].text;
+}
+
 int MacText::getAlignOffset(int row) {
 	int alignOffset = 0;
 	if (_textAlignment == kTextAlignRight)
@@ -2105,7 +2117,7 @@ int MacText::getAlignOffset(int row) {
 	return alignOffset;
 }
 
-void MacText::getRowCol(int x, int y, int *sx, int *sy, int *row, int *col) {
+void MacText::getRowCol(int x, int y, int *sx, int *sy, int *row, int *col, int *chunk_) {
 	int nsx = 0, nsy = 0, nrow = 0, ncol = 0;
 
 	if (y > _textMaxHeight) {
@@ -2151,6 +2163,9 @@ void MacText::getRowCol(int x, int y, int *sx, int *sy, int *row, int *col) {
 		if (chunk == _textLines[nrow].chunks.size())
 			chunk--;
 
+		if (chunk_)
+			*chunk_ = (int)chunk;
+
 		Common::U32String str = _textLines[nrow].chunks[chunk].text;
 
 		ncol = mcol;
diff --git a/graphics/macgui/mactext.h b/graphics/macgui/mactext.h
index aadaef8a7a5..e087dbf83bc 100644
--- a/graphics/macgui/mactext.h
+++ b/graphics/macgui/mactext.h
@@ -227,6 +227,7 @@ public:
 	int getMouseWord(int x, int y);
 	int getMouseItem(int x, int y);
 	int getMouseLine(int x, int y);
+	Common::U32String getMouseLink(int x, int y);
 
 private:
 	MacFontRun getTextChunks(int start, int end);
@@ -263,7 +264,7 @@ public:
 	void insertChar(byte c, int *row, int *col);
 
 	void getChunkPosFromIndex(int index, uint &lineNum, uint &chunkNum, uint &offset);
-	void getRowCol(int x, int y, int *sx, int *sy, int *row, int *col);
+	void getRowCol(int x, int y, int *sx, int *sy, int *row, int *col, int *chunk_ = nullptr);
 	Common::U32String getTextChunk(int startRow, int startCol, int endRow, int endCol, bool formatted = false, bool newlines = true);
 
 	Common::U32String getSelection(bool formatted = false, bool newlines = true);


Commit: 2b35c7997f3c01f5f169c9cdd5b486569b618163
    https://github.com/scummvm/scummvm/commit/2b35c7997f3c01f5f169c9cdd5b486569b618163
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Implemented link tooltips for RichTextWidget

Changed paths:
    gui/gui-manager.cpp
    gui/object.h
    gui/widget.h
    gui/widgets/richtext.cpp
    gui/widgets/richtext.h


diff --git a/gui/gui-manager.cpp b/gui/gui-manager.cpp
index 6a8495ee8ab..797037a433c 100644
--- a/gui/gui-manager.cpp
+++ b/gui/gui-manager.cpp
@@ -546,13 +546,15 @@ void GuiManager::runLoop() {
 		    && systemMillisNowForTooltipCheck - _lastMousePosition.time > (uint32)kTooltipDelay
 		    && !activeDialog->isDragging()) {
 			Widget *wdg = activeDialog->findWidget(_lastMousePosition.x, _lastMousePosition.y);
-			if (wdg && wdg->hasTooltip() && !(wdg->getFlags() & WIDGET_PRESSED)
+			if (wdg && (wdg->hasTooltip() || (wdg->getFlags() & WIDGET_DYN_TOOLTIP)) && !(wdg->getFlags() & WIDGET_PRESSED)
 			    && (_lastTooltipShown.wdg != wdg || systemMillisNowForTooltipCheck - _lastTooltipShown.time > (uint32)kTooltipSameWidgetDelay)) {
 				_lastTooltipShown.time = systemMillisNowForTooltipCheck;
 				_lastTooltipShown.wdg  = wdg;
 				_lastTooltipShown.x = _lastMousePosition.x;
 				_lastTooltipShown.y = _lastMousePosition.y;
 				if (wdg->getType() != kEditTextWidget || activeDialog->getFocusWidget() != wdg) {
+					if (wdg->getFlags() & WIDGET_DYN_TOOLTIP)
+						wdg->handleTooltipUpdate(_lastMousePosition.x- activeDialog->_x - wdg->getRelX(), _lastMousePosition.y - activeDialog->_y - wdg->getRelY());
 					Tooltip *tooltip = new Tooltip();
 					tooltip->setup(activeDialog, wdg, _lastMousePosition.x, _lastMousePosition.y);
 					tooltip->runModal();
diff --git a/gui/object.h b/gui/object.h
index a1c4e8d3eb8..1eaa2ebb434 100644
--- a/gui/object.h
+++ b/gui/object.h
@@ -104,6 +104,7 @@ public:
 	virtual Common::Rect getClipRect() const;
 
 	virtual void handleMouseWheel(int x, int y, int direction) {};
+	virtual void handleTooltipUpdate(int x, int y) {};
 protected:
 	virtual void	releaseFocus() = 0;
 };
diff --git a/gui/widget.h b/gui/widget.h
index baa9891be79..a7c8ce22c67 100644
--- a/gui/widget.h
+++ b/gui/widget.h
@@ -57,7 +57,8 @@ enum {
 	// The PopUpWidget for example does not want this behavior, since the
 	// mouse down will open up a new dialog which silently eats the mouse
 	// up event for its own purposes.
-	WIDGET_IGNORE_DRAG	= 1 << 10
+	WIDGET_IGNORE_DRAG	= 1 << 10,
+	WIDGET_DYN_TOOLTIP  = 1 << 11, // Widgets updates tooltip by coordinates
 };
 
 enum {
@@ -203,7 +204,7 @@ protected:
 	Common::U32String		_label;
 	Graphics::TextAlign		_align;
 	ThemeEngine::FontStyle	_font;
-	ThemeEngine::FontColor  _fontColor; 
+	ThemeEngine::FontColor  _fontColor;
 	bool _useEllipsis;
 
 public:
@@ -217,7 +218,7 @@ public:
 	void setAlign(Graphics::TextAlign align);
 	Graphics::TextAlign getAlign() const		{ return _align; }
 	void readLabel() { read(_label); }
-	void setFontColor(ThemeEngine::FontColor color);  
+	void setFontColor(ThemeEngine::FontColor color);
 
 protected:
 	void drawWidget() override;
@@ -323,7 +324,7 @@ protected:
 class CheckboxWidget : public ButtonWidget {
 protected:
 	bool	_state;
-	bool _overrideText; 
+	bool _overrideText;
 	int _spacing;
 public:
 	CheckboxWidget(GuiObject *boss, int x, int y, int w, int h, bool scale, const Common::U32String &label, const Common::U32String &tooltip = Common::U32String(), uint32 cmd = 0, uint8 hotkey = 0);
@@ -338,8 +339,8 @@ public:
 	void toggleState()			{ setState(!_state); }
 	bool getState() const		{ return _state; }
 
-	void setOverride(bool enable); 
-	
+	void setOverride(bool enable);
+
 protected:
 	void drawWidget() override;
 };
diff --git a/gui/widgets/richtext.cpp b/gui/widgets/richtext.cpp
index 1b7b4eb0f21..d21738ef154 100644
--- a/gui/widgets/richtext.cpp
+++ b/gui/widgets/richtext.cpp
@@ -71,7 +71,7 @@ RichTextWidget::RichTextWidget(GuiObject *boss, const Common::String &name, cons
 }
 
 void RichTextWidget::init() {
-	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG);
+	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_TRACK_MOUSE | WIDGET_DYN_TOOLTIP);
 
 	_type = kRichTextWidget;
 
@@ -93,6 +93,15 @@ void RichTextWidget::handleMouseWheel(int x, int y, int direction) {
 	_verticalScroll->handleMouseWheel(x, y, direction);
 }
 
+void RichTextWidget::handleMouseDown(int x, int y, int button, int clickCount) {
+	warning("M: %s", _txtWnd->getMouseLink(x + _x + _scrolledX, y + _y + _scrolledY).encode().c_str());
+}
+
+void RichTextWidget::handleTooltipUpdate(int x, int y) {
+	_tooltip = _txtWnd->getMouseLink(x + _x + _scrolledX, y + _y + _scrolledY);
+	//warning("t: %s", _tooltip.encode().c_str());
+}
+
 void RichTextWidget::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
 	Widget::handleCommand(sender, cmd, data);
 	switch (cmd) {
diff --git a/gui/widgets/richtext.h b/gui/widgets/richtext.h
index 1cc3220bb0c..8048fa1ad13 100644
--- a/gui/widgets/richtext.h
+++ b/gui/widgets/richtext.h
@@ -58,6 +58,8 @@ public:
 
 	void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
 	void handleMouseWheel(int x, int y, int direction) override;
+	void handleMouseDown(int x, int y, int button, int clickCount) override;
+	void handleTooltipUpdate(int x, int y) override;
 
 	void markAsDirty() override;
 


Commit: 8eb91297c13402331c8d4dce3fc5a72317c7a11d
    https://github.com/scummvm/scummvm/commit/8eb91297c13402331c8d4dce3fc5a72317c7a11d
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Fixed coordinate calculation for dynamic tooltips

Changed paths:
    gui/gui-manager.cpp


diff --git a/gui/gui-manager.cpp b/gui/gui-manager.cpp
index 797037a433c..fef512520e1 100644
--- a/gui/gui-manager.cpp
+++ b/gui/gui-manager.cpp
@@ -554,7 +554,7 @@ void GuiManager::runLoop() {
 				_lastTooltipShown.y = _lastMousePosition.y;
 				if (wdg->getType() != kEditTextWidget || activeDialog->getFocusWidget() != wdg) {
 					if (wdg->getFlags() & WIDGET_DYN_TOOLTIP)
-						wdg->handleTooltipUpdate(_lastMousePosition.x- activeDialog->_x - wdg->getRelX(), _lastMousePosition.y - activeDialog->_y - wdg->getRelY());
+						wdg->handleTooltipUpdate(_lastMousePosition.x + activeDialog->_x - wdg->getAbsX(), _lastMousePosition.y + activeDialog->_y - wdg->getAbsY());
 					Tooltip *tooltip = new Tooltip();
 					tooltip->setup(activeDialog, wdg, _lastMousePosition.x, _lastMousePosition.y);
 					tooltip->runModal();


Commit: 9de6c06d9d460c017fb575e6a46f6eebc4796720
    https://github.com/scummvm/scummvm/commit/9de6c06d9d460c017fb575e6a46f6eebc4796720
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: Simplified buffer manipulation in Markdown

Changed paths:
    graphics/macgui/mactext-md.cpp


diff --git a/graphics/macgui/mactext-md.cpp b/graphics/macgui/mactext-md.cpp
index b53a17ab3e9..a70b5d8c052 100644
--- a/graphics/macgui/mactext-md.cpp
+++ b/graphics/macgui/mactext-md.cpp
@@ -288,11 +288,6 @@ void MacText::setMarkdownText(const Common::U32String &str) {
 
 	Common::String input = str.encode(); // Encode to UTF8
 
-	Common::DataBuffer *ib = Common::bufnew(input.size());
-	Common::bufgrow(ib, input.size());
-	ib->size = input.size();
-	memcpy(ib->data, input.c_str(), input.size());
-
 	Common::DataBuffer *ob = Common::bufnew(1024);
 
 	MDState mdState;
@@ -303,7 +298,7 @@ void MacText::setMarkdownText(const Common::U32String &str) {
 	mdState.linkb = 0xff;
 
 	Common::SDMarkdown *md = sd_markdown_new(0, 16, &cb, &mdState);
-	sd_markdown_render(ob, ib->data, ib->size, md);
+	sd_markdown_render(ob, (const byte *)input.c_str(), input.size(), md);
 	sd_markdown_free(md);
 
 	warning("%zu bytes: %s", ob->size, toPrintable(Common::String((const char *)ob->data, ob->size)).c_str());


Commit: eac4a62389e14162a19015c43d1b15999d4f58ef
    https://github.com/scummvm/scummvm/commit/eac4a62389e14162a19015c43d1b15999d4f58ef
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
COMMON: Remove unused functions in Markdown

Changed paths:
    common/formats/markdown.cpp
    common/formats/markdown.h


diff --git a/common/formats/markdown.cpp b/common/formats/markdown.cpp
index a96a1cdb1af..eacb208059e 100644
--- a/common/formats/markdown.cpp
+++ b/common/formats/markdown.cpp
@@ -34,6 +34,40 @@ namespace Common {
 #define GPERF_DOWNCASE 1
 #define GPERF_CASE_STRNCMP 1
 
+
+// autolink.h
+enum {
+	SD_AUTOLINK_SHORT_DOMAINS = (1 << 0),
+};
+
+int sd_autolink_issafe(const byte *link, size_t link_len);
+
+size_t sd_autolink__www(size_t *rewind_p, DataBuffer *link,
+	byte *data, size_t offset, size_t size, uint flags);
+
+size_t sd_autolink__email(size_t *rewind_p, DataBuffer *link,
+	byte *data, size_t offset, size_t size, uint flags);
+
+size_t sd_autolink__url(size_t *rewind_p, DataBuffer *link,
+	byte *data, size_t offset, size_t size, uint flags);
+
+
+// stack.h
+
+struct SDStack {
+	void **item;
+	size_t size;
+	size_t asize;
+};
+
+void stack_free(SDStack *);
+int stack_grow(SDStack *, size_t);
+int stack_init(SDStack *, size_t);
+
+int stack_push(SDStack *, void *);
+
+void *stack_pop(SDStack *);
+
 /***************
  * LOCAL TYPES *
  ***************/
@@ -2688,21 +2722,6 @@ size_t sd_autolink__url(size_t *rewind_p, DataBuffer *link, byte *data, size_t m
 
 #define BUFFER_MAX_ALLOC_SIZE (1024 * 1024 * 16) //16mb
 
-int bufprefix(const DataBuffer *buf, const char *prefix) {
-	size_t i;
-	assert(buf && buf->unit);
-
-	for (i = 0; i < buf->size; ++i) {
-		if (prefix[i] == 0)
-			return 0;
-
-		if (buf->data[i] != prefix[i])
-			return buf->data[i] - prefix[i];
-	}
-
-	return 0;
-}
-
 /* bufgrow: increasing the allocated size to the given value */
 int bufgrow(DataBuffer *buf, size_t neosz) {
 	size_t neoasz;
@@ -2741,60 +2760,6 @@ DataBuffer *bufnew(size_t unit) {
 	return ret;
 }
 
-/* bufnullterm: NULL-termination of the string array */
-const char *bufcstr(DataBuffer *buf) {
-	assert(buf && buf->unit);
-
-	if (buf->size < buf->asize && buf->data[buf->size] == 0)
-		return (char *)buf->data;
-
-	if (buf->size + 1 <= buf->asize || bufgrow(buf, buf->size + 1) == 0) {
-		buf->data[buf->size] = 0;
-		return (char *)buf->data;
-	}
-
-	return NULL;
-}
-
-/* bufprintf: formatted printing to a buffer */
-void bufprintf(DataBuffer *buf, const char *fmt, ...) {
-	va_list ap;
-	int n;
-
-	assert(buf && buf->unit);
-
-	if (buf->size >= buf->asize && bufgrow(buf, buf->size + 1) < 0)
-		return;
-
-	va_start(ap, fmt);
-	n = vsnprintf((char *)buf->data + buf->size, buf->asize - buf->size, fmt, ap);
-	va_end(ap);
-
-	if (n < 0) {
-#ifdef _MSC_VER
-		va_start(ap, fmt);
-		n = _vscprintf(fmt, ap);
-		va_end(ap);
-#else
-		return;
-#endif
-	}
-
-	if ((size_t)n >= buf->asize - buf->size) {
-		if (bufgrow(buf, buf->size + n + 1) < 0)
-			return;
-
-		va_start(ap, fmt);
-		n = vsnprintf((char *)buf->data + buf->size, buf->asize - buf->size, fmt, ap);
-		va_end(ap);
-	}
-
-	if (n < 0)
-		return;
-
-	buf->size += n;
-}
-
 /* bufput: appends raw data to a buffer */
 void bufput(DataBuffer *buf, const void *data, size_t len) {
 	assert(buf && buf->unit);
@@ -2806,11 +2771,6 @@ void bufput(DataBuffer *buf, const void *data, size_t len) {
 	buf->size += len;
 }
 
-/* bufputs: appends a NUL-terminated string to a buffer */
-void bufputs(DataBuffer *buf, const char *str) {
-	bufput(buf, str, strlen(str));
-}
-
 /* bufputc: appends a single byte to a buffer */
 void bufputc(DataBuffer *buf, int c) {
 	assert(buf && buf->unit);
@@ -2831,29 +2791,6 @@ void bufrelease(DataBuffer *buf) {
 	free(buf);
 }
 
-/* bufreset: frees internal data of the buffer */
-void bufreset(DataBuffer *buf) {
-	if (!buf)
-		return;
-
-	free(buf->data);
-	buf->data = NULL;
-	buf->size = buf->asize = 0;
-}
-
-/* bufslurp: removes a given number of bytes from the head of the array */
-void bufslurp(DataBuffer *buf, size_t len) {
-	assert(buf && buf->unit);
-
-	if (len >= buf->size) {
-		buf->size = 0;
-		return;
-	}
-
-	buf->size -= len;
-	memmove(buf->data, buf->data + len, buf->size);
-}
-
 // stack.c
 
 int stack_grow(SDStack *st, size_t new_size) {
@@ -2915,13 +2852,6 @@ int stack_push(SDStack *st, void *item) {
 	return 0;
 }
 
-void *stack_top(SDStack *st) {
-	if (!st->size)
-		return NULL;
-
-	return st->item[st->size - 1];
-}
-
 // html_blocks.h
 /* C code produced by gperf version 3.0.3 */
 /* Command-line: gperf -N find_block_tag -H hash_block_tag -C -c -E --ignore-case html_block_names.txt  */
diff --git a/common/formats/markdown.h b/common/formats/markdown.h
index 365a8a4d681..aaea2774ad0 100644
--- a/common/formats/markdown.h
+++ b/common/formats/markdown.h
@@ -148,69 +148,15 @@ int bufgrow(DataBuffer *, size_t);
 /* bufnew: allocation of a new buffer */
 DataBuffer *bufnew(size_t);
 
-/* bufnullterm: NUL-termination of the string array (making a C-string) */
-const char *bufcstr(DataBuffer *);
-
-/* bufprefix: compare the beginning of a buffer with a string */
-int bufprefix(const DataBuffer *buf, const char *prefix);
-
 /* bufput: appends raw data to a buffer */
 void bufput(DataBuffer *, const void *, size_t);
 
-/* bufputs: appends a NUL-terminated string to a buffer */
-void bufputs(DataBuffer *, const char *);
-
 /* bufputc: appends a single char to a buffer */
 void bufputc(DataBuffer *, int);
 
 /* bufrelease: decrease the reference count and free the buffer if needed */
 void bufrelease(DataBuffer *);
 
-/* bufreset: frees internal data of the buffer */
-void bufreset(DataBuffer *);
-
-/* bufslurp: removes a given number of bytes from the head of the array */
-void bufslurp(DataBuffer *, size_t);
-
-/* bufprintf: formatted printing to a buffer */
-void bufprintf(DataBuffer *, const char *, ...);
-
-
-// autolink.h
-enum {
-	SD_AUTOLINK_SHORT_DOMAINS = (1 << 0),
-};
-
-int sd_autolink_issafe(const byte *link, size_t link_len);
-
-size_t sd_autolink__www(size_t *rewind_p, DataBuffer *link,
-	byte *data, size_t offset, size_t size, uint flags);
-
-size_t sd_autolink__email(size_t *rewind_p, DataBuffer *link,
-	byte *data, size_t offset, size_t size, uint flags);
-
-size_t sd_autolink__url(size_t *rewind_p, DataBuffer *link,
-	byte *data, size_t offset, size_t size, uint flags);
-
-
-// stack.h
-
-struct SDStack {
-	void **item;
-	size_t size;
-	size_t asize;
-};
-
-void stack_free(SDStack *);
-int stack_grow(SDStack *, size_t);
-int stack_init(SDStack *, size_t);
-
-int stack_push(SDStack *, void *);
-
-void *stack_pop(SDStack *);
-void *stack_top(SDStack *);
-
-
 } // end of namespace Common
 
 #endif // COMMON_FORMATS_MARKDOWN_H


Commit: 3cbb035875d31608ae92d3abcc336b355b2665af
    https://github.com/scummvm/scummvm/commit/3cbb035875d31608ae92d3abcc336b355b2665af
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
COMMON: Added String::chop() method

Changed paths:
    common/str-base.cpp
    common/str-base.h


diff --git a/common/str-base.cpp b/common/str-base.cpp
index b74e11db0a0..71ae708a499 100644
--- a/common/str-base.cpp
+++ b/common/str-base.cpp
@@ -466,6 +466,13 @@ TEMPLATE void BASESTRING::deleteLastChar() {
 		deleteChar(_size - 1);
 }
 
+TEMPLATE void BASESTRING::chop(uint32 len) {
+	uint32 newSize = _size - MIN(_size, len);
+
+	_str[newSize] = 0;
+	_size = newSize;
+}
+
 TEMPLATE void BASESTRING::erase(uint32 p, uint32 len) {
 	if (len == 0)
 		return;
diff --git a/common/str-base.h b/common/str-base.h
index 19e9b94f3c3..aeb11885c6c 100644
--- a/common/str-base.h
+++ b/common/str-base.h
@@ -142,6 +142,9 @@ public:
 	/** Erases the character at the given iterator location */
 	iterator erase(iterator it);
 
+	/** Removes characters from the end of the string */
+	void chop(uint32 len = 1);
+
 	/** Clears the string, making it empty. */
 	void clear();
 


Commit: 3bce81ac706bc13d17c483e1cd4ebaaf7d1fe493
    https://github.com/scummvm/scummvm/commit/3bce81ac706bc13d17c483e1cd4ebaaf7d1fe493
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
TESTS: Added test for String::chop() method

Changed paths:
    test/common/str.h


diff --git a/test/common/str.h b/test/common/str.h
index 06a066b1387..a6655a74540 100644
--- a/test/common/str.h
+++ b/test/common/str.h
@@ -27,6 +27,28 @@ class StringTestSuite : public CxxTest::TestSuite
 		TS_ASSERT_EQUALS(str2, "  This is a s tring with spaces  ");
 	}
 
+	void test_chop() {
+		Common::String str("test-string");
+		str.chop();
+		TS_ASSERT_EQUALS(str, "test-strin");
+
+		str = "test-string";
+		str.chop(2);
+		TS_ASSERT_EQUALS(str, "test-stri");
+
+		str = "test-string";
+		str.chop(10);
+		TS_ASSERT_EQUALS(str, "t");
+
+		str = "test-string";
+		str.chop(11);
+		TS_ASSERT(str.empty());
+
+		str = "test-string";
+		str.chop(200);
+		TS_ASSERT(str.empty());
+	}
+
 	void test_empty_clear() {
 		Common::String str("test");
 		TS_ASSERT(!str.empty());


Commit: fe0fbe5cff19d5c573d11a4f32d51c7114908e5f
    https://github.com/scummvm/scummvm/commit/fe0fbe5cff19d5c573d11a4f32d51c7114908e5f
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
COMMON: Make Markdown return Common::String

Changed paths:
    common/formats/markdown.cpp
    common/formats/markdown.h
    graphics/macgui/mactext-md.cpp


diff --git a/common/formats/markdown.cpp b/common/formats/markdown.cpp
index eacb208059e..e1400f567e8 100644
--- a/common/formats/markdown.cpp
+++ b/common/formats/markdown.cpp
@@ -2371,7 +2371,7 @@ SDMarkdown *sd_markdown_new(uint extensions, size_t max_nesting, const SDCallbac
 	return md;
 }
 
-void sd_markdown_render(DataBuffer *ob, const byte *document, size_t doc_size, SDMarkdown *md) {
+Common::String sd_markdown_render(const byte *document, size_t doc_size, SDMarkdown *md) {
 #define MARKDOWN_GROW(x) ((x) + ((x) >> 1))
 	static const byte UTF8_BOM[] = {0xEF, 0xBB, 0xBF};
 
@@ -2380,7 +2380,7 @@ void sd_markdown_render(DataBuffer *ob, const byte *document, size_t doc_size, S
 
 	text = bufnew(64);
 	if (!text)
-		return;
+		return Common::String();
 
 	/* Preallocate enough space for our buffer to avoid expanding while copying */
 	bufgrow(text, doc_size);
@@ -2418,6 +2418,8 @@ void sd_markdown_render(DataBuffer *ob, const byte *document, size_t doc_size, S
 			beg = end;
 		}
 
+	DataBuffer *ob = Common::bufnew(1024);
+
 	/* pre-grow the output buffer to minimize allocations */
 	bufgrow(ob, MARKDOWN_GROW(text->size));
 
@@ -2442,6 +2444,12 @@ void sd_markdown_render(DataBuffer *ob, const byte *document, size_t doc_size, S
 
 	assert(md->work_bufs[BUFFER_SPAN].size == 0);
 	assert(md->work_bufs[BUFFER_BLOCK].size == 0);
+
+	Common::String res = Common::String((const char *)ob->data, ob->size);
+
+	bufrelease(ob);
+
+	return res;
 }
 
 void
diff --git a/common/formats/markdown.h b/common/formats/markdown.h
index aaea2774ad0..e7b93bdab42 100644
--- a/common/formats/markdown.h
+++ b/common/formats/markdown.h
@@ -22,6 +22,8 @@
 
 namespace Common {
 
+class String;
+
 #define SUNDOWN_VERSION "1.16.0"
 #define SUNDOWN_VER_MAJOR 1
 #define SUNDOWN_VER_MINOR 16
@@ -109,7 +111,7 @@ struct SDCallbacks {
 
 SDMarkdown *sd_markdown_new(uint extensions, size_t max_nesting, const SDCallbacks *callbacks, void *opaque);
 
-void sd_markdown_render(DataBuffer *ob, const byte *document, size_t doc_size, SDMarkdown *md);
+Common::String sd_markdown_render(const byte *document, size_t doc_size, SDMarkdown *md);
 
 void sd_markdown_free(SDMarkdown *md);
 
diff --git a/graphics/macgui/mactext-md.cpp b/graphics/macgui/mactext-md.cpp
index a70b5d8c052..c7d38ba7709 100644
--- a/graphics/macgui/mactext-md.cpp
+++ b/graphics/macgui/mactext-md.cpp
@@ -288,8 +288,6 @@ void MacText::setMarkdownText(const Common::U32String &str) {
 
 	Common::String input = str.encode(); // Encode to UTF8
 
-	Common::DataBuffer *ob = Common::bufnew(1024);
-
 	MDState mdState;
 
 	// Set link color to blue
@@ -298,13 +296,10 @@ void MacText::setMarkdownText(const Common::U32String &str) {
 	mdState.linkb = 0xff;
 
 	Common::SDMarkdown *md = sd_markdown_new(0, 16, &cb, &mdState);
-	sd_markdown_render(ob, (const byte *)input.c_str(), input.size(), md);
+	Common::String rendered = sd_markdown_render((const byte *)input.c_str(), input.size(), md);
 	sd_markdown_free(md);
 
-	warning("%zu bytes: %s", ob->size, toPrintable(Common::String((const char *)ob->data, ob->size)).c_str());
-
-	//setDefaultFormatting(kMacFontChicago, kMacFontRegular, 40, 0, 0, 0);
-	setText(Common::String((const char *)ob->data, ob->size));
+	setText(rendered);
 }
 
 } // End of namespace Graphics


Commit: ce8a0456dbde1bb927ae9574a6d77baadf78c578
    https://github.com/scummvm/scummvm/commit/ce8a0456dbde1bb927ae9574a6d77baadf78c578
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
COMMON: Reduce Common:: namespace pollution by Markdown

Changed paths:
    common/formats/markdown.cpp
    common/formats/markdown.h
    graphics/macgui/mactext-md.cpp


diff --git a/common/formats/markdown.cpp b/common/formats/markdown.cpp
index e1400f567e8..c309b84a761 100644
--- a/common/formats/markdown.cpp
+++ b/common/formats/markdown.cpp
@@ -42,13 +42,13 @@ enum {
 
 int sd_autolink_issafe(const byte *link, size_t link_len);
 
-size_t sd_autolink__www(size_t *rewind_p, DataBuffer *link,
+size_t sd_autolink__www(size_t *rewind_p, SDDataBuffer *link,
 	byte *data, size_t offset, size_t size, uint flags);
 
-size_t sd_autolink__email(size_t *rewind_p, DataBuffer *link,
+size_t sd_autolink__email(size_t *rewind_p, SDDataBuffer *link,
 	byte *data, size_t offset, size_t size, uint flags);
 
-size_t sd_autolink__url(size_t *rewind_p, DataBuffer *link,
+size_t sd_autolink__url(size_t *rewind_p, SDDataBuffer *link,
 	byte *data, size_t offset, size_t size, uint flags);
 
 
@@ -68,6 +68,37 @@ int stack_push(SDStack *, void *);
 
 void *stack_pop(SDStack *);
 
+// buffer.h
+
+enum buferror_t {
+	BUF_OK = 0,
+	BUF_ENOMEM = -1,
+};
+
+/* CONST_BUF: global buffer from a string litteral */
+#define BUF_STATIC(string) \
+	{ (byte *)string, sizeof string -1, sizeof string, 0, 0 }
+
+/* VOLATILE_BUF: macro for creating a volatile buffer on the stack */
+#define BUF_VOLATILE(strname) \
+	{ (byte *)strname, strlen(strname), 0, 0, 0 }
+
+/* sd_bufputSL: optimized sd_bufputs of a string litteral */
+#define sd_bufputSL(output, literal) \
+	sd_bufput(output, literal, sizeof literal - 1)
+
+/* sd_bufgrow: increasing the allocated size to the given value */
+int sd_bufgrow(SDDataBuffer *, size_t);
+
+/* sd_bufnew: allocation of a new buffer */
+SDDataBuffer *sd_bufnew(size_t);
+
+/* sd_bufputc: appends a single char to a buffer */
+void sd_bufputc(SDDataBuffer *, int);
+
+/* sd_bufrelease: decrease the reference count and free the buffer if needed */
+void sd_bufrelease(SDDataBuffer *);
+
 /***************
  * LOCAL TYPES *
  ***************/
@@ -76,8 +107,8 @@ void *stack_pop(SDStack *);
 struct LinkRef {
 	uint id;
 
-	DataBuffer *link;
-	DataBuffer *title;
+	SDDataBuffer *link;
+	SDDataBuffer *title;
 
 	LinkRef *next;
 };
@@ -99,19 +130,19 @@ struct SDMarkdown {
 /*   returns the number of chars taken care of */
 /*   data is the pointer of the beginning of the span */
 /*   offset is the number of valid chars before data */
-typedef size_t (*char_trigger)(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size);
-
-static size_t char_emphasis(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size);
-static size_t char_linebreak(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size);
-static size_t char_codespan(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size);
-static size_t char_escape(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size);
-static size_t char_entity(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size);
-static size_t char_langle_tag(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size);
-static size_t char_autolink_url(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size);
-static size_t char_autolink_email(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size);
-static size_t char_autolink_www(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size);
-static size_t char_link(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size);
-static size_t char_superscript(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size);
+typedef size_t (*char_trigger)(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size);
+
+static size_t char_emphasis(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size);
+static size_t char_linebreak(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size);
+static size_t char_codespan(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size);
+static size_t char_escape(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size);
+static size_t char_entity(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size);
+static size_t char_langle_tag(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size);
+static size_t char_autolink_url(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size);
+static size_t char_autolink_email(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size);
+static size_t char_autolink_www(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size);
+static size_t char_link(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size);
+static size_t char_superscript(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size);
 
 enum markdown_char_t {
 	MD_CHAR_NONE = 0,
@@ -147,17 +178,17 @@ static char_trigger markdown_char_ptrs[] = {
  * HELPER FUNCTIONS *
  ***************************/
 
-static inline DataBuffer *rndr_newbuf(SDMarkdown *rndr, int type) {
+static inline SDDataBuffer *rndr_newbuf(SDMarkdown *rndr, int type) {
 	static const size_t buf_size[2] = {256, 64};
-	DataBuffer *work = NULL;
+	SDDataBuffer *work = NULL;
 	SDStack *pool = &rndr->work_bufs[type];
 
 	if (pool->size < pool->asize &&
 	        pool->item[pool->size] != NULL) {
-		work = (DataBuffer *)pool->item[pool->size++];
+		work = (SDDataBuffer *)pool->item[pool->size++];
 		work->size = 0;
 	} else {
-		work = bufnew(buf_size[type]);
+		work = sd_bufnew(buf_size[type]);
 		stack_push(pool, work);
 	}
 
@@ -168,7 +199,7 @@ static inline void rndr_popbuf(SDMarkdown *rndr, int type) {
 	rndr->work_bufs[type].size--;
 }
 
-static void unscape_text(DataBuffer *ob, DataBuffer *src) {
+static void unscape_text(SDDataBuffer *ob, SDDataBuffer *src) {
 	size_t i = 0, org;
 	while (i < src->size) {
 		org = i;
@@ -176,12 +207,12 @@ static void unscape_text(DataBuffer *ob, DataBuffer *src) {
 			i++;
 
 		if (i > org)
-			bufput(ob, src->data + org, i - org);
+			sd_bufput(ob, src->data + org, i - org);
 
 		if (i + 1 >= src->size)
 			break;
 
-		bufputc(ob, src->data[i + 1]);
+		sd_bufputc(ob, src->data[i + 1]);
 		i += 2;
 	}
 }
@@ -236,8 +267,8 @@ static void free_link_refs(LinkRef **references) {
 
 		while (r) {
 			next = r->next;
-			bufrelease(r->link);
-			bufrelease(r->title);
+			sd_bufrelease(r->link);
+			sd_bufrelease(r->title);
 			free(r);
 			r = next;
 		}
@@ -354,10 +385,10 @@ static size_t tag_length(byte *data, size_t size, MKDAutolink *autolink) {
 }
 
 /* parse_inline • parses inline markdown elements */
-static void parse_inline(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size) {
+static void parse_inline(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size) {
 	size_t i = 0, end = 0;
 	byte action = 0;
-	DataBuffer work = { 0, 0, 0, 0 };
+	SDDataBuffer work = { 0, 0, 0, 0 };
 
 	if (rndr->work_bufs[BUFFER_SPAN].size +
 	        rndr->work_bufs[BUFFER_BLOCK].size > rndr->max_nesting)
@@ -374,7 +405,7 @@ static void parse_inline(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t si
 			work.size = end - i;
 			rndr->cb.normal_text(ob, &work, rndr->opaque);
 		} else
-			bufput(ob, data + i, end - i);
+			sd_bufput(ob, data + i, end - i);
 
 		if (end >= size) break;
 		i = end;
@@ -484,9 +515,9 @@ static size_t find_emph_char(byte *data, size_t size, byte c) {
 
 /* parse_emph1 • parsing single emphase */
 /* closed by a symbol not preceded by whitespace and not followed by symbol */
-static size_t parse_emph1(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size, byte c) {
+static size_t parse_emph1(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size, byte c) {
 	size_t i = 0, len;
-	DataBuffer *work = 0;
+	SDDataBuffer *work = 0;
 	int r;
 
 	if (!rndr->cb.emphasis) return 0;
@@ -519,10 +550,10 @@ static size_t parse_emph1(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t s
 }
 
 /* parse_emph2 • parsing single emphase */
-static size_t parse_emph2(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size, byte c) {
-	int (*render_method)(DataBuffer * ob, const DataBuffer * text, void *opaque);
+static size_t parse_emph2(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size, byte c) {
+	int (*render_method)(SDDataBuffer * ob, const SDDataBuffer * text, void *opaque);
 	size_t i = 0, len;
-	DataBuffer *work = 0;
+	SDDataBuffer *work = 0;
 	int r;
 
 	render_method = (c == '~') ? rndr->cb.strikethrough : rndr->cb.double_emphasis;
@@ -549,7 +580,7 @@ static size_t parse_emph2(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t s
 
 /* parse_emph3 • parsing single emphase */
 /* finds the first closing tag, and delegates to the other emph */
-static size_t parse_emph3(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size, byte c) {
+static size_t parse_emph3(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size, byte c) {
 	size_t i = 0, len;
 	int r;
 
@@ -564,7 +595,7 @@ static size_t parse_emph3(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t s
 
 		if (i + 2 < size && data[i + 1] == c && data[i + 2] == c && rndr->cb.triple_emphasis) {
 			/* triple symbol found */
-			DataBuffer *work = rndr_newbuf(rndr, BUFFER_SPAN);
+			SDDataBuffer *work = rndr_newbuf(rndr, BUFFER_SPAN);
 
 			parse_inline(work, rndr, data, i);
 			r = rndr->cb.triple_emphasis(ob, work, rndr->opaque);
@@ -588,7 +619,7 @@ static size_t parse_emph3(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t s
 }
 
 /* char_emphasis • single and double emphasis parsing */
-static size_t char_emphasis(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size) {
+static size_t char_emphasis(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size) {
 	byte c = data[0];
 	size_t ret;
 
@@ -624,7 +655,7 @@ static size_t char_emphasis(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t
 }
 
 /* char_linebreak • '\n' preceded by two spaces (assuming linebreak != 0) */
-static size_t char_linebreak(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size) {
+static size_t char_linebreak(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size) {
 	if (offset < 2 || data[-1] != ' ' || data[-2] != ' ')
 		return 0;
 
@@ -636,7 +667,7 @@ static size_t char_linebreak(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_
 }
 
 /* char_codespan • '`' parsing a code span (assuming codespan != 0) */
-static size_t char_codespan(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size) {
+static size_t char_codespan(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size) {
 	size_t end, nb = 0, i, f_begin, f_end;
 
 	/* counting the number of backticks in the delimiter */
@@ -664,7 +695,7 @@ static size_t char_codespan(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t
 
 	/* real code span */
 	if (f_begin < f_end) {
-		DataBuffer work = { data + f_begin, f_end - f_begin, 0, 0 };
+		SDDataBuffer work = { data + f_begin, f_end - f_begin, 0, 0 };
 		if (!rndr->cb.codespan(ob, &work, rndr->opaque))
 			end = 0;
 	} else {
@@ -676,9 +707,9 @@ static size_t char_codespan(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t
 }
 
 /* char_escape • '\\' backslash escape */
-static size_t char_escape(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size) {
+static size_t char_escape(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size) {
 	static const char *escape_chars = "\\`*_{}[]()#+-.!:|&<>^~";
-	DataBuffer work = { 0, 0, 0, 0 };
+	SDDataBuffer work = { 0, 0, 0, 0 };
 
 	if (size > 1) {
 		if (strchr(escape_chars, data[1]) == NULL)
@@ -688,9 +719,9 @@ static size_t char_escape(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t o
 			work.data = data + 1;
 			work.size = 1;
 			rndr->cb.normal_text(ob, &work, rndr->opaque);
-		} else bufputc(ob, data[1]);
+		} else sd_bufputc(ob, data[1]);
 	} else if (size == 1) {
-		bufputc(ob, data[0]);
+		sd_bufputc(ob, data[0]);
 	}
 
 	return 2;
@@ -698,9 +729,9 @@ static size_t char_escape(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t o
 
 /* char_entity • '&' escaped when it doesn't belong to an entity */
 /* valid entities are assumed to be anything matching &#?[A-Za-z0-9]+; */
-static size_t char_entity(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size) {
+static size_t char_entity(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size) {
 	size_t end = 1;
-	DataBuffer work = { 0, 0, 0, 0 };
+	SDDataBuffer work = { 0, 0, 0, 0 };
 
 	if (end < size && data[end] == '#')
 		end++;
@@ -717,21 +748,21 @@ static size_t char_entity(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t o
 		work.data = data;
 		work.size = end;
 		rndr->cb.entity(ob, &work, rndr->opaque);
-	} else bufput(ob, data, end);
+	} else sd_bufput(ob, data, end);
 
 	return end;
 }
 
 /* char_langle_tag • '<' when tags or autolinks are allowed */
-static size_t char_langle_tag(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size) {
+static size_t char_langle_tag(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size) {
 	MKDAutolink altype = MKDA_NOT_AUTOLINK;
 	size_t end = tag_length(data, size, &altype);
-	DataBuffer work = { data, end, 0, 0 };
+	SDDataBuffer work = { data, end, 0, 0 };
 	int ret = 0;
 
 	if (end > 2) {
 		if (rndr->cb.autolink && altype != MKDA_NOT_AUTOLINK) {
-			DataBuffer *u_link = rndr_newbuf(rndr, BUFFER_SPAN);
+			SDDataBuffer *u_link = rndr_newbuf(rndr, BUFFER_SPAN);
 			work.data = data + 1;
 			work.size = end - 2;
 			unscape_text(u_link, &work);
@@ -745,8 +776,8 @@ static size_t char_langle_tag(DataBuffer *ob, SDMarkdown *rndr, byte *data, size
 	else return end;
 }
 
-static size_t char_autolink_www(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size) {
-	DataBuffer *link, *link_url, *link_text;
+static size_t char_autolink_www(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size) {
+	SDDataBuffer *link, *link_url, *link_text;
 	size_t link_len, rewind;
 
 	if (!rndr->cb.link || rndr->in_link_body)
@@ -756,8 +787,8 @@ static size_t char_autolink_www(DataBuffer *ob, SDMarkdown *rndr, byte *data, si
 
 	if ((link_len = sd_autolink__www(&rewind, link, data, offset, size, 0)) > 0) {
 		link_url = rndr_newbuf(rndr, BUFFER_SPAN);
-		BUFPUTSL(link_url, "http://");
-		bufput(link_url, link->data, link->size);
+		sd_bufputSL(link_url, "http://");
+		sd_bufput(link_url, link->data, link->size);
 
 		ob->size -= rewind;
 		if (rndr->cb.normal_text) {
@@ -775,8 +806,8 @@ static size_t char_autolink_www(DataBuffer *ob, SDMarkdown *rndr, byte *data, si
 	return link_len;
 }
 
-static size_t char_autolink_email(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size) {
-	DataBuffer *link;
+static size_t char_autolink_email(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size) {
+	SDDataBuffer *link;
 	size_t link_len, rewind;
 
 	if (!rndr->cb.autolink || rndr->in_link_body)
@@ -793,8 +824,8 @@ static size_t char_autolink_email(DataBuffer *ob, SDMarkdown *rndr, byte *data,
 	return link_len;
 }
 
-static size_t char_autolink_url(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size) {
-	DataBuffer *link;
+static size_t char_autolink_url(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size) {
+	SDDataBuffer *link;
 	size_t link_len, rewind;
 
 	if (!rndr->cb.autolink || rndr->in_link_body)
@@ -812,13 +843,13 @@ static size_t char_autolink_url(DataBuffer *ob, SDMarkdown *rndr, byte *data, si
 }
 
 /* char_link • '[': parsing a link or an image */
-static size_t char_link(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size) {
+static size_t char_link(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size) {
 	int is_img = (offset && data[-1] == '!'), level;
 	size_t i = 1, txt_e, link_b = 0, link_e = 0, title_b = 0, title_e = 0;
-	DataBuffer *content = 0;
-	DataBuffer *link = 0;
-	DataBuffer *title = 0;
-	DataBuffer *u_link = 0;
+	SDDataBuffer *content = 0;
+	SDDataBuffer *link = 0;
+	SDDataBuffer *title = 0;
+	SDDataBuffer *u_link = 0;
 	size_t org_work_size = rndr->work_bufs[BUFFER_SPAN].size;
 	int text_has_nl = 0, ret = 0;
 	int in_title = 0, qtype = 0;
@@ -918,12 +949,12 @@ static size_t char_link(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t off
 		/* building escaped link and title */
 		if (link_e > link_b) {
 			link = rndr_newbuf(rndr, BUFFER_SPAN);
-			bufput(link, data + link_b, link_e - link_b);
+			sd_bufput(link, data + link_b, link_e - link_b);
 		}
 
 		if (title_e > title_b) {
 			title = rndr_newbuf(rndr, BUFFER_SPAN);
-			bufput(title, data + title_b, title_e - title_b);
+			sd_bufput(title, data + title_b, title_e - title_b);
 		}
 
 		i++;
@@ -931,7 +962,7 @@ static size_t char_link(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t off
 
 	/* reference style link */
 	else if (i < size && data[i] == '[') {
-		DataBuffer id = { 0, 0, 0, 0 };
+		SDDataBuffer id = { 0, 0, 0, 0 };
 		LinkRef *lr;
 
 		/* looking for the id */
@@ -944,14 +975,14 @@ static size_t char_link(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t off
 		/* finding the link_ref */
 		if (link_b == link_e) {
 			if (text_has_nl) {
-				DataBuffer *b = rndr_newbuf(rndr, BUFFER_SPAN);
+				SDDataBuffer *b = rndr_newbuf(rndr, BUFFER_SPAN);
 				size_t j;
 
 				for (j = 1; j < txt_e; j++) {
 					if (data[j] != '\n')
-						bufputc(b, data[j]);
+						sd_bufputc(b, data[j]);
 					else if (data[j - 1] != ' ')
-						bufputc(b, ' ');
+						sd_bufputc(b, ' ');
 				}
 
 				id.data = b->data;
@@ -977,19 +1008,19 @@ static size_t char_link(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t off
 
 	/* shortcut reference style link */
 	else {
-		DataBuffer id = { 0, 0, 0, 0 };
+		SDDataBuffer id = { 0, 0, 0, 0 };
 		LinkRef *lr;
 
 		/* crafting the id */
 		if (text_has_nl) {
-			DataBuffer *b = rndr_newbuf(rndr, BUFFER_SPAN);
+			SDDataBuffer *b = rndr_newbuf(rndr, BUFFER_SPAN);
 			size_t j;
 
 			for (j = 1; j < txt_e; j++) {
 				if (data[j] != '\n')
-					bufputc(b, data[j]);
+					sd_bufputc(b, data[j]);
 				else if (data[j - 1] != ' ')
-					bufputc(b, ' ');
+					sd_bufputc(b, ' ');
 			}
 
 			id.data = b->data;
@@ -1016,7 +1047,7 @@ static size_t char_link(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t off
 	if (txt_e > 1) {
 		content = rndr_newbuf(rndr, BUFFER_SPAN);
 		if (is_img) {
-			bufput(content, data + 1, txt_e - 1);
+			sd_bufput(content, data + 1, txt_e - 1);
 		} else {
 			/* disable autolinking when parsing inline the
 			 * content of a link */
@@ -1047,9 +1078,9 @@ cleanup:
 	return ret ? i : 0;
 }
 
-static size_t char_superscript(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size) {
+static size_t char_superscript(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t offset, size_t size) {
 	size_t sup_start, sup_len;
-	DataBuffer *sup;
+	SDDataBuffer *sup;
 
 	if (!rndr->cb.superscript)
 		return 0;
@@ -1170,7 +1201,7 @@ static size_t prefix_codefence(byte *data, size_t size) {
 }
 
 /* check if a line is a code fence; return its size if it is */
-static size_t is_codefence(byte *data, size_t size, DataBuffer *syntax) {
+static size_t is_codefence(byte *data, size_t size, SDDataBuffer *syntax) {
 	size_t i = 0, syn_len = 0;
 	byte *syn_start;
 
@@ -1347,13 +1378,13 @@ static size_t prefix_uli(byte *data, size_t size) {
 }
 
 /* parse_block • parsing of one block, returning next byte to parse */
-static void parse_block(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size);
+static void parse_block(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size);
 
 /* parse_blockquote • handles parsing of a blockquote fragment */
-static size_t parse_blockquote(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size) {
+static size_t parse_blockquote(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size) {
 	size_t beg, end = 0, pre, work_size = 0;
 	byte *work_data = 0;
-	DataBuffer *out = 0;
+	SDDataBuffer *out = 0;
 
 	out = rndr_newbuf(rndr, BUFFER_BLOCK);
 	beg = 0;
@@ -1372,7 +1403,7 @@ static size_t parse_blockquote(DataBuffer *ob, SDMarkdown *rndr, byte *data, siz
 			break;
 
 		if (beg < end) { /* copy into the in-place working buffer */
-			/* bufput(work, data + beg, end - beg); */
+			/* sd_bufput(work, data + beg, end - beg); */
 			if (!work_data)
 				work_data = data + beg;
 			else if (data + beg != work_data + work_size)
@@ -1389,13 +1420,13 @@ static size_t parse_blockquote(DataBuffer *ob, SDMarkdown *rndr, byte *data, siz
 	return end;
 }
 
-static size_t parse_htmlblock(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size, int do_render);
+static size_t parse_htmlblock(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size, int do_render);
 
 /* parse_blockquote • handles parsing of a regular paragraph */
-static size_t parse_paragraph(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size) {
+static size_t parse_paragraph(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size) {
 	size_t i = 0, end = 0;
 	int level = 0;
-	DataBuffer work = { data, 0, 0, 0 };
+	SDDataBuffer work = { data, 0, 0, 0 };
 
 	while (i < size) {
 		for (end = i + 1; end < size && data[end - 1] != '\n'; end++) /* empty */;
@@ -1452,13 +1483,13 @@ static size_t parse_paragraph(DataBuffer *ob, SDMarkdown *rndr, byte *data, size
 		work.size--;
 
 	if (!level) {
-		DataBuffer *tmp = rndr_newbuf(rndr, BUFFER_BLOCK);
+		SDDataBuffer *tmp = rndr_newbuf(rndr, BUFFER_BLOCK);
 		parse_inline(tmp, rndr, work.data, work.size);
 		if (rndr->cb.paragraph)
 			rndr->cb.paragraph(ob, tmp, rndr->opaque);
 		rndr_popbuf(rndr, BUFFER_BLOCK);
 	} else {
-		DataBuffer *header_work;
+		SDDataBuffer *header_work;
 
 		if (work.size) {
 			size_t beg;
@@ -1473,7 +1504,7 @@ static size_t parse_paragraph(DataBuffer *ob, SDMarkdown *rndr, byte *data, size
 				work.size -= 1;
 
 			if (work.size > 0) {
-				DataBuffer *tmp = rndr_newbuf(rndr, BUFFER_BLOCK);
+				SDDataBuffer *tmp = rndr_newbuf(rndr, BUFFER_BLOCK);
 				parse_inline(tmp, rndr, work.data, work.size);
 
 				if (rndr->cb.paragraph)
@@ -1498,10 +1529,10 @@ static size_t parse_paragraph(DataBuffer *ob, SDMarkdown *rndr, byte *data, size
 }
 
 /* parse_fencedcode • handles parsing of a block-level code fragment */
-static size_t parse_fencedcode(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size) {
+static size_t parse_fencedcode(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size) {
 	size_t beg, end;
-	DataBuffer *work = 0;
-	DataBuffer lang = { 0, 0, 0, 0 };
+	SDDataBuffer *work = 0;
+	SDDataBuffer lang = { 0, 0, 0, 0 };
 
 	beg = is_codefence(data, size, &lang);
 	if (beg == 0) return 0;
@@ -1510,7 +1541,7 @@ static size_t parse_fencedcode(DataBuffer *ob, SDMarkdown *rndr, byte *data, siz
 
 	while (beg < size) {
 		size_t fence_end;
-		DataBuffer fence_trail = { 0, 0, 0, 0 };
+		SDDataBuffer fence_trail = { 0, 0, 0, 0 };
 
 		fence_end = is_codefence(data + beg, size - beg, &fence_trail);
 		if (fence_end != 0 && fence_trail.size == 0) {
@@ -1524,14 +1555,14 @@ static size_t parse_fencedcode(DataBuffer *ob, SDMarkdown *rndr, byte *data, siz
 			/* verbatim copy to the working buffer,
 			    escaping entities */
 			if (is_empty(data + beg, end - beg))
-				bufputc(work, '\n');
-			else bufput(work, data + beg, end - beg);
+				sd_bufputc(work, '\n');
+			else sd_bufput(work, data + beg, end - beg);
 		}
 		beg = end;
 	}
 
 	if (work->size && work->data[work->size - 1] != '\n')
-		bufputc(work, '\n');
+		sd_bufputc(work, '\n');
 
 	if (rndr->cb.blockcode)
 		rndr->cb.blockcode(ob, work, lang.size ? &lang : NULL, rndr->opaque);
@@ -1540,9 +1571,9 @@ static size_t parse_fencedcode(DataBuffer *ob, SDMarkdown *rndr, byte *data, siz
 	return beg;
 }
 
-static size_t parse_blockcode(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size) {
+static size_t parse_blockcode(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size) {
 	size_t beg, end, pre;
-	DataBuffer *work = 0;
+	SDDataBuffer *work = 0;
 
 	work = rndr_newbuf(rndr, BUFFER_BLOCK);
 
@@ -1561,8 +1592,8 @@ static size_t parse_blockcode(DataBuffer *ob, SDMarkdown *rndr, byte *data, size
 			/* verbatim copy to the working buffer,
 			    escaping entities */
 			if (is_empty(data + beg, end - beg))
-				bufputc(work, '\n');
-			else bufput(work, data + beg, end - beg);
+				sd_bufputc(work, '\n');
+			else sd_bufput(work, data + beg, end - beg);
 		}
 		beg = end;
 	}
@@ -1570,7 +1601,7 @@ static size_t parse_blockcode(DataBuffer *ob, SDMarkdown *rndr, byte *data, size
 	while (work->size && work->data[work->size - 1] == '\n')
 		work->size -= 1;
 
-	bufputc(work, '\n');
+	sd_bufputc(work, '\n');
 
 	if (rndr->cb.blockcode)
 		rndr->cb.blockcode(ob, work, NULL, rndr->opaque);
@@ -1581,8 +1612,8 @@ static size_t parse_blockcode(DataBuffer *ob, SDMarkdown *rndr, byte *data, size
 
 /* parse_listitem • parsing of a single list item */
 /*  assuming initial prefix is already removed */
-static size_t parse_listitem(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size, int *flags) {
-	DataBuffer *work = 0, *inter = 0;
+static size_t parse_listitem(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size, int *flags) {
+	SDDataBuffer *work = 0, *inter = 0;
 	size_t beg = 0, end, pre, sublist = 0, orgpre = 0, i;
 	int in_empty = 0, has_inside_empty = 0, in_fence = 0;
 
@@ -1607,7 +1638,7 @@ static size_t parse_listitem(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_
 	inter = rndr_newbuf(rndr, BUFFER_SPAN);
 
 	/* putting the first line into the working buffer */
-	bufput(work, data + beg, end - beg);
+	sd_bufput(work, data + beg, end - beg);
 	beg = end;
 
 	/* process the following lines */
@@ -1671,14 +1702,14 @@ static size_t parse_listitem(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_
 			*flags |= MKD_LI_END;
 			break;
 		} else if (in_empty) {
-			bufputc(work, '\n');
+			sd_bufputc(work, '\n');
 			has_inside_empty = 1;
 		}
 
 		in_empty = 0;
 
 		/* adding the line without prefix into the working buffer */
-		bufput(work, data + beg + i, end - beg - i);
+		sd_bufput(work, data + beg + i, end - beg - i);
 		beg = end;
 	}
 
@@ -1712,8 +1743,8 @@ static size_t parse_listitem(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_
 }
 
 /* parse_list • parsing ordered or unordered list block */
-static size_t parse_list(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size, int flags) {
-	DataBuffer *work = 0;
+static size_t parse_list(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size, int flags) {
+	SDDataBuffer *work = 0;
 	size_t i = 0, j;
 
 	work = rndr_newbuf(rndr, BUFFER_BLOCK);
@@ -1736,7 +1767,7 @@ static size_t parse_list(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t si
 }
 
 /* parse_atxheader • parsing of atx-style headers */
-static size_t parse_atxheader(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size) {
+static size_t parse_atxheader(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size) {
 	size_t level = 0;
 	size_t i, end, skip;
 
@@ -1755,7 +1786,7 @@ static size_t parse_atxheader(DataBuffer *ob, SDMarkdown *rndr, byte *data, size
 		end--;
 
 	if (end > i) {
-		DataBuffer *work = rndr_newbuf(rndr, BUFFER_SPAN);
+		SDDataBuffer *work = rndr_newbuf(rndr, BUFFER_SPAN);
 
 		parse_inline(work, rndr, data + i, end - i);
 
@@ -1836,10 +1867,10 @@ static size_t htmlblock_end(const char *curtag, SDMarkdown *rndr, byte *data, si
 inline const char *find_block_tag(const char *str, uint len);
 
 /* parse_htmlblock • parsing of inline HTML block */
-static size_t parse_htmlblock(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size, int do_render) {
+static size_t parse_htmlblock(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size, int do_render) {
 	size_t i, j = 0, tag_end;
 	const char *curtag = NULL;
-	DataBuffer work = { data, 0, 0, 0 };
+	SDDataBuffer work = { data, 0, 0, 0 };
 
 	/* identification of the opening tag */
 	if (size < 2 || data[0] != '<')
@@ -1918,9 +1949,9 @@ static size_t parse_htmlblock(DataBuffer *ob, SDMarkdown *rndr, byte *data, size
 	return tag_end;
 }
 
-static void parse_table_row(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size, size_t columns, int *col_data, int header_flag) {
+static void parse_table_row(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size, size_t columns, int *col_data, int header_flag) {
 	size_t i = 0, col;
-	DataBuffer *row_work = 0;
+	SDDataBuffer *row_work = 0;
 
 	if (!rndr->cb.table_cell || !rndr->cb.table_row)
 		return;
@@ -1932,7 +1963,7 @@ static void parse_table_row(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t
 
 	for (col = 0; col < columns && i < size; ++col) {
 		size_t cell_start, cell_end;
-		DataBuffer *cell_work;
+		SDDataBuffer *cell_work;
 
 		cell_work = rndr_newbuf(rndr, BUFFER_SPAN);
 
@@ -1957,7 +1988,7 @@ static void parse_table_row(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t
 	}
 
 	for (; col < columns; ++col) {
-		DataBuffer empty_cell = { 0, 0, 0, 0 };
+		SDDataBuffer empty_cell = { 0, 0, 0, 0 };
 		rndr->cb.table_cell(row_work, &empty_cell, col_data[col] | header_flag, rndr->opaque);
 	}
 
@@ -1966,7 +1997,7 @@ static void parse_table_row(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t
 	rndr_popbuf(rndr, BUFFER_SPAN);
 }
 
-static size_t parse_table_header(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size, size_t *columns, int **column_data) {
+static size_t parse_table_header(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size, size_t *columns, int **column_data) {
 	int pipes;
 	size_t i = 0, col, header_end, under_end;
 
@@ -2050,11 +2081,11 @@ static size_t parse_table_header(DataBuffer *ob, SDMarkdown *rndr, byte *data, s
 	return under_end + 1;
 }
 
-static size_t parse_table(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size) {
+static size_t parse_table(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size) {
 	size_t i;
 
-	DataBuffer *header_work = 0;
-	DataBuffer *body_work = 0;
+	SDDataBuffer *header_work = 0;
+	SDDataBuffer *body_work = 0;
 
 	size_t columns;
 	int *col_data = NULL;
@@ -2103,7 +2134,7 @@ static size_t parse_table(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t s
 }
 
 /* parse_block • parsing of one block, returning next byte to parse */
-static void parse_block(DataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size) {
+static void parse_block(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t size) {
 	size_t beg, end, i;
 	byte *txt_data;
 	beg = 0;
@@ -2275,19 +2306,19 @@ static int is_ref(const byte *data, size_t beg, size_t end, size_t *last, LinkRe
 		if (!ref)
 			return 0;
 
-		ref->link = bufnew(link_end - link_offset);
-		bufput(ref->link, data + link_offset, link_end - link_offset);
+		ref->link = sd_bufnew(link_end - link_offset);
+		sd_bufput(ref->link, data + link_offset, link_end - link_offset);
 
 		if (title_end > title_offset) {
-			ref->title = bufnew(title_end - title_offset);
-			bufput(ref->title, data + title_offset, title_end - title_offset);
+			ref->title = sd_bufnew(title_end - title_offset);
+			sd_bufput(ref->title, data + title_offset, title_end - title_offset);
 		}
 	}
 
 	return 1;
 }
 
-static void expand_tabs(DataBuffer *ob, const byte *line, size_t size) {
+static void expand_tabs(SDDataBuffer *ob, const byte *line, size_t size) {
 	size_t  i = 0, tab = 0;
 
 	while (i < size) {
@@ -2299,13 +2330,13 @@ static void expand_tabs(DataBuffer *ob, const byte *line, size_t size) {
 		}
 
 		if (i > org)
-			bufput(ob, line + org, i - org);
+			sd_bufput(ob, line + org, i - org);
 
 		if (i >= size)
 			break;
 
 		do {
-			bufputc(ob, ' ');
+			sd_bufputc(ob, ' ');
 			tab++;
 		} while (tab % 4);
 
@@ -2375,15 +2406,15 @@ Common::String sd_markdown_render(const byte *document, size_t doc_size, SDMarkd
 #define MARKDOWN_GROW(x) ((x) + ((x) >> 1))
 	static const byte UTF8_BOM[] = {0xEF, 0xBB, 0xBF};
 
-	DataBuffer *text;
+	SDDataBuffer *text;
 	size_t beg, end;
 
-	text = bufnew(64);
+	text = sd_bufnew(64);
 	if (!text)
 		return Common::String();
 
 	/* Preallocate enough space for our buffer to avoid expanding while copying */
-	bufgrow(text, doc_size);
+	sd_bufgrow(text, doc_size);
 
 	/* reset the references table */
 	memset(&md->refs, 0x0, REF_TABLE_SIZE * sizeof(void *));
@@ -2411,17 +2442,17 @@ Common::String sd_markdown_render(const byte *document, size_t doc_size, SDMarkd
 			while (end < doc_size && (document[end] == '\n' || document[end] == '\r')) {
 				/* add one \n per newline */
 				if (document[end] == '\n' || (end + 1 < doc_size && document[end + 1] != '\n'))
-					bufputc(text, '\n');
+					sd_bufputc(text, '\n');
 				end++;
 			}
 
 			beg = end;
 		}
 
-	DataBuffer *ob = Common::bufnew(1024);
+	SDDataBuffer *ob = Common::sd_bufnew(1024);
 
 	/* pre-grow the output buffer to minimize allocations */
-	bufgrow(ob, MARKDOWN_GROW(text->size));
+	sd_bufgrow(ob, MARKDOWN_GROW(text->size));
 
 	/* second pass: actual rendering */
 	if (md->cb.doc_header)
@@ -2430,7 +2461,7 @@ Common::String sd_markdown_render(const byte *document, size_t doc_size, SDMarkd
 	if (text->size) {
 		/* adding a final newline if not already present */
 		if (text->data[text->size - 1] != '\n' &&  text->data[text->size - 1] != '\r')
-			bufputc(text, '\n');
+			sd_bufputc(text, '\n');
 
 		parse_block(ob, md, text->data, text->size);
 	}
@@ -2439,7 +2470,7 @@ Common::String sd_markdown_render(const byte *document, size_t doc_size, SDMarkd
 		md->cb.doc_footer(ob, md->opaque);
 
 	/* clean-up */
-	bufrelease(text);
+	sd_bufrelease(text);
 	free_link_refs(md->refs);
 
 	assert(md->work_bufs[BUFFER_SPAN].size == 0);
@@ -2447,7 +2478,7 @@ Common::String sd_markdown_render(const byte *document, size_t doc_size, SDMarkd
 
 	Common::String res = Common::String((const char *)ob->data, ob->size);
 
-	bufrelease(ob);
+	sd_bufrelease(ob);
 
 	return res;
 }
@@ -2457,10 +2488,10 @@ sd_markdown_free(SDMarkdown *md) {
 	size_t i;
 
 	for (i = 0; i < (size_t)md->work_bufs[BUFFER_SPAN].asize; ++i)
-		bufrelease((DataBuffer *)md->work_bufs[BUFFER_SPAN].item[i]);
+		sd_bufrelease((SDDataBuffer *)md->work_bufs[BUFFER_SPAN].item[i]);
 
 	for (i = 0; i < (size_t)md->work_bufs[BUFFER_BLOCK].asize; ++i)
-		bufrelease((DataBuffer *)md->work_bufs[BUFFER_BLOCK].item[i]);
+		sd_bufrelease((SDDataBuffer *)md->work_bufs[BUFFER_BLOCK].item[i]);
 
 	stack_free(&md->work_bufs[BUFFER_SPAN]);
 	stack_free(&md->work_bufs[BUFFER_BLOCK]);
@@ -2613,7 +2644,7 @@ static size_t check_domain(byte *data, size_t size, int allow_short) {
 	}
 }
 
-size_t sd_autolink__www(size_t *rewind_p, DataBuffer *link, byte *data, size_t max_rewind, size_t size, uint flags) {
+size_t sd_autolink__www(size_t *rewind_p, SDDataBuffer *link, byte *data, size_t max_rewind, size_t size, uint flags) {
 	size_t link_end;
 
 	if (max_rewind > 0 && !Common::isPunct(data[-1]) && !Common::isSpace(data[-1]))
@@ -2635,13 +2666,13 @@ size_t sd_autolink__www(size_t *rewind_p, DataBuffer *link, byte *data, size_t m
 	if (link_end == 0)
 		return 0;
 
-	bufput(link, data, link_end);
+	sd_bufput(link, data, link_end);
 	*rewind_p = 0;
 
 	return (int)link_end;
 }
 
-size_t sd_autolink__email(size_t *rewind_p, DataBuffer *link, byte *data, size_t max_rewind, size_t size, uint flags) {
+size_t sd_autolink__email(size_t *rewind_p, SDDataBuffer *link, byte *data, size_t max_rewind, size_t size, uint flags) {
 	size_t link_end, rewind;
 	int nb = 0, np = 0;
 
@@ -2683,13 +2714,13 @@ size_t sd_autolink__email(size_t *rewind_p, DataBuffer *link, byte *data, size_t
 	if (link_end == 0)
 		return 0;
 
-	bufput(link, data - rewind, link_end + rewind);
+	sd_bufput(link, data - rewind, link_end + rewind);
 	*rewind_p = rewind;
 
 	return link_end;
 }
 
-size_t sd_autolink__url(size_t *rewind_p, DataBuffer *link, byte *data, size_t max_rewind, size_t size, uint flags) {
+size_t sd_autolink__url(size_t *rewind_p, SDDataBuffer *link, byte *data, size_t max_rewind, size_t size, uint flags) {
 	size_t link_end, rewind = 0, domain_len;
 
 	if (size < 4 || data[1] != '/' || data[2] != '/')
@@ -2720,7 +2751,7 @@ size_t sd_autolink__url(size_t *rewind_p, DataBuffer *link, byte *data, size_t m
 	if (link_end == 0)
 		return 0;
 
-	bufput(link, data - rewind, link_end + rewind);
+	sd_bufput(link, data - rewind, link_end + rewind);
 	*rewind_p = rewind;
 
 	return link_end;
@@ -2730,8 +2761,8 @@ size_t sd_autolink__url(size_t *rewind_p, DataBuffer *link, byte *data, size_t m
 
 #define BUFFER_MAX_ALLOC_SIZE (1024 * 1024 * 16) //16mb
 
-/* bufgrow: increasing the allocated size to the given value */
-int bufgrow(DataBuffer *buf, size_t neosz) {
+/* sd_bufgrow: increasing the allocated size to the given value */
+int sd_bufgrow(SDDataBuffer *buf, size_t neosz) {
 	size_t neoasz;
 	byte *neodata;
 
@@ -2756,9 +2787,9 @@ int bufgrow(DataBuffer *buf, size_t neosz) {
 	return BUF_OK;
 }
 
-/* bufnew: allocation of a new buffer */
-DataBuffer *bufnew(size_t unit) {
-	DataBuffer *ret = (DataBuffer *)malloc(sizeof(DataBuffer));
+/* sd_bufnew: allocation of a new buffer */
+SDDataBuffer *sd_bufnew(size_t unit) {
+	SDDataBuffer *ret = (SDDataBuffer *)malloc(sizeof(SDDataBuffer));
 
 	if (ret) {
 		ret->data = 0;
@@ -2768,30 +2799,30 @@ DataBuffer *bufnew(size_t unit) {
 	return ret;
 }
 
-/* bufput: appends raw data to a buffer */
-void bufput(DataBuffer *buf, const void *data, size_t len) {
+/* sd_bufput: appends raw data to a buffer */
+void sd_bufput(SDDataBuffer *buf, const void *data, size_t len) {
 	assert(buf && buf->unit);
 
-	if (buf->size + len > buf->asize && bufgrow(buf, buf->size + len) < 0)
+	if (buf->size + len > buf->asize && sd_bufgrow(buf, buf->size + len) < 0)
 		return;
 
 	memcpy(buf->data + buf->size, data, len);
 	buf->size += len;
 }
 
-/* bufputc: appends a single byte to a buffer */
-void bufputc(DataBuffer *buf, int c) {
+/* sd_bufputc: appends a single byte to a buffer */
+void sd_bufputc(SDDataBuffer *buf, int c) {
 	assert(buf && buf->unit);
 
-	if (buf->size + 1 > buf->asize && bufgrow(buf, buf->size + 1) < 0)
+	if (buf->size + 1 > buf->asize && sd_bufgrow(buf, buf->size + 1) < 0)
 		return;
 
 	buf->data[buf->size] = c;
 	buf->size += 1;
 }
 
-/* bufrelease: decrease the reference count and free the buffer if needed */
-void bufrelease(DataBuffer *buf) {
+/* sd_bufrelease: decrease the reference count and free the buffer if needed */
+void sd_bufrelease(SDDataBuffer *buf) {
 	if (!buf)
 		return;
 
diff --git a/common/formats/markdown.h b/common/formats/markdown.h
index e7b93bdab42..e2f17581162 100644
--- a/common/formats/markdown.h
+++ b/common/formats/markdown.h
@@ -33,7 +33,7 @@ class String;
  * TYPE DEFINITIONS *
  ********************/
 
-struct DataBuffer;
+struct SDDataBuffer;
 struct SDMarkdown;
 
 /* mkd_autolink - type of autolink */
@@ -65,40 +65,40 @@ enum mkd_extensions {
 /* sd_callbacks - functions for rendering parsed data */
 struct SDCallbacks {
 	/* block level callbacks - NULL skips the block */
-	void (*blockcode)(DataBuffer *ob, const DataBuffer *text, const DataBuffer *lang, void *opaque);
-	void (*blockquote)(DataBuffer *ob, const DataBuffer *text, void *opaque);
-	void (*blockhtml)(DataBuffer *ob,const  DataBuffer *text, void *opaque);
-	void (*header)(DataBuffer *ob, const DataBuffer *text, int level, void *opaque);
-	void (*hrule)(DataBuffer *ob, void *opaque);
-	void (*list_start)(DataBuffer *ob, const DataBuffer *text, int flags, void *opaque);
-	void (*list)(DataBuffer *ob, const DataBuffer *text, int flags, void *opaque);
-	void (*listitem)(DataBuffer *ob, const DataBuffer *text, int flags, void *opaque);
-	void (*paragraph)(DataBuffer *ob, const DataBuffer *text, void *opaque);
-	void (*table)(DataBuffer *ob, const DataBuffer *header, const DataBuffer *body, void *opaque);
-	void (*table_row)(DataBuffer *ob, const DataBuffer *text, void *opaque);
-	void (*table_cell)(DataBuffer *ob, const DataBuffer *text, int flags, void *opaque);
+	void (*blockcode)(SDDataBuffer *ob, const SDDataBuffer *text, const SDDataBuffer *lang, void *opaque);
+	void (*blockquote)(SDDataBuffer *ob, const SDDataBuffer *text, void *opaque);
+	void (*blockhtml)(SDDataBuffer *ob,const  SDDataBuffer *text, void *opaque);
+	void (*header)(SDDataBuffer *ob, const SDDataBuffer *text, int level, void *opaque);
+	void (*hrule)(SDDataBuffer *ob, void *opaque);
+	void (*list_start)(SDDataBuffer *ob, const SDDataBuffer *text, int flags, void *opaque);
+	void (*list)(SDDataBuffer *ob, const SDDataBuffer *text, int flags, void *opaque);
+	void (*listitem)(SDDataBuffer *ob, const SDDataBuffer *text, int flags, void *opaque);
+	void (*paragraph)(SDDataBuffer *ob, const SDDataBuffer *text, void *opaque);
+	void (*table)(SDDataBuffer *ob, const SDDataBuffer *header, const SDDataBuffer *body, void *opaque);
+	void (*table_row)(SDDataBuffer *ob, const SDDataBuffer *text, void *opaque);
+	void (*table_cell)(SDDataBuffer *ob, const SDDataBuffer *text, int flags, void *opaque);
 
 
 	/* span level callbacks - NULL or return 0 prints the span verbatim */
-	int (*autolink)(DataBuffer *ob, const DataBuffer *link, MKDAutolink type, void *opaque);
-	int (*codespan)(DataBuffer *ob, const DataBuffer *text, void *opaque);
-	int (*double_emphasis)(DataBuffer *ob, const DataBuffer *text, void *opaque);
-	int (*emphasis)(DataBuffer *ob, const DataBuffer *text, void *opaque);
-	int (*image)(DataBuffer *ob, const DataBuffer *link, const DataBuffer *title, const DataBuffer *alt, void *opaque);
-	int (*linebreak)(DataBuffer *ob, void *opaque);
-	int (*link)(DataBuffer *ob, const DataBuffer *link, const DataBuffer *title, const DataBuffer *content, void *opaque);
-	int (*raw_html_tag)(DataBuffer *ob, const DataBuffer *tag, void *opaque);
-	int (*triple_emphasis)(DataBuffer *ob, const DataBuffer *text, void *opaque);
-	int (*strikethrough)(DataBuffer *ob, const DataBuffer *text, void *opaque);
-	int (*superscript)(DataBuffer *ob, const DataBuffer *text, void *opaque);
+	int (*autolink)(SDDataBuffer *ob, const SDDataBuffer *link, MKDAutolink type, void *opaque);
+	int (*codespan)(SDDataBuffer *ob, const SDDataBuffer *text, void *opaque);
+	int (*double_emphasis)(SDDataBuffer *ob, const SDDataBuffer *text, void *opaque);
+	int (*emphasis)(SDDataBuffer *ob, const SDDataBuffer *text, void *opaque);
+	int (*image)(SDDataBuffer *ob, const SDDataBuffer *link, const SDDataBuffer *title, const SDDataBuffer *alt, void *opaque);
+	int (*linebreak)(SDDataBuffer *ob, void *opaque);
+	int (*link)(SDDataBuffer *ob, const SDDataBuffer *link, const SDDataBuffer *title, const SDDataBuffer *content, void *opaque);
+	int (*raw_html_tag)(SDDataBuffer *ob, const SDDataBuffer *tag, void *opaque);
+	int (*triple_emphasis)(SDDataBuffer *ob, const SDDataBuffer *text, void *opaque);
+	int (*strikethrough)(SDDataBuffer *ob, const SDDataBuffer *text, void *opaque);
+	int (*superscript)(SDDataBuffer *ob, const SDDataBuffer *text, void *opaque);
 
 	/* low level callbacks - NULL copies input directly into the output */
-	void (*entity)(DataBuffer *ob, const DataBuffer *entity, void *opaque);
-	void (*normal_text)(DataBuffer *ob, const DataBuffer *text, void *opaque);
+	void (*entity)(SDDataBuffer *ob, const SDDataBuffer *entity, void *opaque);
+	void (*normal_text)(SDDataBuffer *ob, const SDDataBuffer *text, void *opaque);
 
 	/* header and footer */
-	void (*doc_header)(DataBuffer *ob, void *opaque);
-	void (*doc_footer)(DataBuffer *ob, void *opaque);
+	void (*doc_header)(SDDataBuffer *ob, void *opaque);
+	void (*doc_footer)(SDDataBuffer *ob, void *opaque);
 };
 
 /*********
@@ -117,47 +117,16 @@ void sd_markdown_free(SDMarkdown *md);
 
 void sd_version(int *major, int *minor, int *revision);
 
-// buffer.h
-
-enum buferror_t {
-	BUF_OK = 0,
-	BUF_ENOMEM = -1,
-};
-
-/* DataBuffer: character array buffer */
-struct DataBuffer {
+/* SDDataBuffer: character array buffer */
+struct SDDataBuffer {
 	byte *data;		/* actual character data */
 	size_t size;	/* size of the string */
 	size_t asize;	/* allocated size (0 = volatile buffer) */
 	size_t unit;	/* reallocation unit size (0 = read-only buffer) */
 };
 
-/* CONST_BUF: global buffer from a string litteral */
-#define BUF_STATIC(string) \
-	{ (byte *)string, sizeof string -1, sizeof string, 0, 0 }
-
-/* VOLATILE_BUF: macro for creating a volatile buffer on the stack */
-#define BUF_VOLATILE(strname) \
-	{ (byte *)strname, strlen(strname), 0, 0, 0 }
-
-/* BUFPUTSL: optimized bufputs of a string litteral */
-#define BUFPUTSL(output, literal) \
-	bufput(output, literal, sizeof literal - 1)
-
-/* bufgrow: increasing the allocated size to the given value */
-int bufgrow(DataBuffer *, size_t);
-
-/* bufnew: allocation of a new buffer */
-DataBuffer *bufnew(size_t);
-
-/* bufput: appends raw data to a buffer */
-void bufput(DataBuffer *, const void *, size_t);
-
-/* bufputc: appends a single char to a buffer */
-void bufputc(DataBuffer *, int);
-
-/* bufrelease: decrease the reference count and free the buffer if needed */
-void bufrelease(DataBuffer *);
+/* sd_bufput: appends raw data to a buffer */
+void sd_bufput(SDDataBuffer *, const void *, size_t);
 
 } // end of namespace Common
 
diff --git a/graphics/macgui/mactext-md.cpp b/graphics/macgui/mactext-md.cpp
index c7d38ba7709..9b1e604c338 100644
--- a/graphics/macgui/mactext-md.cpp
+++ b/graphics/macgui/mactext-md.cpp
@@ -32,28 +32,28 @@ struct MDState {
 	uint32 linkr = 0, linkg = 0, linkb = 0;
 };
 
-void render_blockcode(Common::DataBuffer *ob, const Common::DataBuffer *text, const Common::DataBuffer *lang, void *opaque) {
+void render_blockcode(Common::SDDataBuffer *ob, const Common::SDDataBuffer *text, const Common::SDDataBuffer *lang, void *opaque) {
 	if (!text)
 		return;
 
 	warning("STUB: render_blockcode(%s)", PR(text));
 }
 
-void render_blockquote(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
+void render_blockquote(Common::SDDataBuffer *ob, const Common::SDDataBuffer *text, void *opaque) {
 	if (!text)
 		return;
 
 	warning("STUB: render_blockquote(%s)", PR(text));
 }
 
-void render_blockhtml(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
+void render_blockhtml(Common::SDDataBuffer *ob, const Common::SDDataBuffer *text, void *opaque) {
 	if (!text)
 		return;
 
 	warning("STUB: render_blockhtml(%s)", PR(text));
 }
 
-void render_header(Common::DataBuffer *ob, const Common::DataBuffer *text, int level, void *opaque) {
+void render_header(Common::SDDataBuffer *ob, const Common::SDDataBuffer *text, int level, void *opaque) {
 	if (!text)
 		return;
 
@@ -61,14 +61,14 @@ void render_header(Common::DataBuffer *ob, const Common::DataBuffer *text, int l
 
 	Common::String res = Common::String::format("\016+00%01x%s\001\016-00f\n", level, Common::String((const char *)text->data , text->size).c_str());
 
-	bufput(ob, res.c_str(), res.size());
+	sd_bufput(ob, res.c_str(), res.size());
 }
 
-void render_hrule(Common::DataBuffer *ob, void *opaque) {
+void render_hrule(Common::SDDataBuffer *ob, void *opaque) {
 	warning("STUB: render_hrule()");
 }
 
-void render_list_start(Common::DataBuffer *ob, const Common::DataBuffer *text, int flags, void *opaque) {
+void render_list_start(Common::SDDataBuffer *ob, const Common::SDDataBuffer *text, int flags, void *opaque) {
 	MDState *mdstate = (MDState *)opaque;
 
 	mdstate->listNum.push_back(flags & MKD_LIST_ORDERED ? 1 : -1);
@@ -76,73 +76,73 @@ void render_list_start(Common::DataBuffer *ob, const Common::DataBuffer *text, i
 	debug(1, "render_list_start(%s, %d)", PR(text), flags);
 }
 
-void render_list(Common::DataBuffer *ob, const Common::DataBuffer *text, int flags, void *opaque) {
+void render_list(Common::SDDataBuffer *ob, const Common::SDDataBuffer *text, int flags, void *opaque) {
 	MDState *mdstate = (MDState *)opaque;
 
 	mdstate->listNum.pop_back();
 
-	bufput(ob, text->data, text->size);
-	bufput(ob, "\n", 1);
+	sd_bufput(ob, text->data, text->size);
+	sd_bufput(ob, "\n", 1);
 
 	debug(1, "render_list(%s, %d)", PR(text), flags);
 }
 
-void render_listitem(Common::DataBuffer *ob, const Common::DataBuffer *text, int flags, void *opaque) {
+void render_listitem(Common::SDDataBuffer *ob, const Common::SDDataBuffer *text, int flags, void *opaque) {
 	MDState *mdstate = (MDState *)opaque;
 
 	int listNum = mdstate->listNum.back();
 	int depth = mdstate->listNum.size();
 
 	for (int i = 0; i < depth; i++)
-		bufput(ob, "  ", 2);
+		sd_bufput(ob, "  ", 2);
 
 	if (flags & MKD_LIST_ORDERED) {
 		Common::String prefix = Common::String::format("%d. ", listNum);
 
-		bufput(ob, prefix.c_str(), prefix.size());
+		sd_bufput(ob, prefix.c_str(), prefix.size());
 
 		mdstate->listNum.back()++;
 	} else {
-		bufput(ob, "* ", 2);
+		sd_bufput(ob, "* ", 2);
 	}
 
-	bufput(ob, text->data, text->size);
+	sd_bufput(ob, text->data, text->size);
 
 	debug(1, "render_listitem(%s, %d)", PR(text), flags);
 }
 
-void render_paragraph(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
+void render_paragraph(Common::SDDataBuffer *ob, const Common::SDDataBuffer *text, void *opaque) {
 	if (!text)
 		return;
 
 	debug(1, "render_paragraph(%s)", PR(text));
 
-	bufput(ob, text->data, text->size);
-	bufput(ob, "\n\n", 2);
+	sd_bufput(ob, text->data, text->size);
+	sd_bufput(ob, "\n\n", 2);
 }
 
-void render_table(Common::DataBuffer *ob, const Common::DataBuffer *header, const Common::DataBuffer *body, void *opaque) {
+void render_table(Common::SDDataBuffer *ob, const Common::SDDataBuffer *header, const Common::SDDataBuffer *body, void *opaque) {
 	if (!body)
 		return;
 
 	warning("STUB: render_table(%s, %s)", header ? PR(header) : 0, PR(body));
 }
 
-void render_table_row(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
+void render_table_row(Common::SDDataBuffer *ob, const Common::SDDataBuffer *text, void *opaque) {
 	if (!text)
 		return;
 
 	warning("STUB: render_table_row(%s)", PR(text));
 }
 
-void render_table_cell(Common::DataBuffer *ob, const Common::DataBuffer *text, int flags, void *opaque) {
+void render_table_cell(Common::SDDataBuffer *ob, const Common::SDDataBuffer *text, int flags, void *opaque) {
 	if (!text)
 		return;
 
 	warning("STUB: render_table_cell(%s)", PR(text));
 }
 
-int render_autolink(Common::DataBuffer *ob, const Common::DataBuffer *link, Common::MKDAutolink type, void *opaque) {
+int render_autolink(Common::SDDataBuffer *ob, const Common::SDDataBuffer *link, Common::MKDAutolink type, void *opaque) {
 	if (!link)
 		return 0;
 
@@ -150,7 +150,7 @@ int render_autolink(Common::DataBuffer *ob, const Common::DataBuffer *link, Comm
 	return 1;
 }
 
-int render_codespan(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
+int render_codespan(Common::SDDataBuffer *ob, const Common::SDDataBuffer *text, void *opaque) {
 	if (!text)
 		return 0;
 
@@ -158,7 +158,7 @@ int render_codespan(Common::DataBuffer *ob, const Common::DataBuffer *text, void
 	return 1;
 }
 
-int render_double_emphasis(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
+int render_double_emphasis(Common::SDDataBuffer *ob, const Common::SDDataBuffer *text, void *opaque) {
 	if (!text || !text->size)
 		return 0;
 
@@ -166,11 +166,11 @@ int render_double_emphasis(Common::DataBuffer *ob, const Common::DataBuffer *tex
 
 	Common::String res = Common::String::format("\001\016+%02x0%s\001\016-%02x0", kMacFontBold, Common::String((const char *)text->data , text->size).c_str(), kMacFontBold);
 
-	bufput(ob, res.c_str(), res.size());
+	sd_bufput(ob, res.c_str(), res.size());
 	return 1;
 }
 
-int render_emphasis(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
+int render_emphasis(Common::SDDataBuffer *ob, const Common::SDDataBuffer *text, void *opaque) {
 	if (!text || !text->size)
 		return 0;
 
@@ -178,11 +178,11 @@ int render_emphasis(Common::DataBuffer *ob, const Common::DataBuffer *text, void
 
 	Common::String res = Common::String::format("\001\016+%02x0%s\001\016-%02x0", kMacFontItalic, Common::String((const char *)text->data , text->size).c_str(), kMacFontItalic);
 
-	bufput(ob, res.c_str(), res.size());
+	sd_bufput(ob, res.c_str(), res.size());
 	return 1;
 }
 
-int render_image(Common::DataBuffer *ob, const Common::DataBuffer *link, const Common::DataBuffer *title, const Common::DataBuffer *alt, void *opaque) {
+int render_image(Common::SDDataBuffer *ob, const Common::SDDataBuffer *link, const Common::SDDataBuffer *title, const Common::SDDataBuffer *alt, void *opaque) {
 	if (!link)
 		return 0;
 
@@ -190,31 +190,31 @@ int render_image(Common::DataBuffer *ob, const Common::DataBuffer *link, const C
 	return 1;
 }
 
-int render_linebreak(Common::DataBuffer *ob, void *opaque) {
+int render_linebreak(Common::SDDataBuffer *ob, void *opaque) {
 	debug(1, "render_linebreak()");
 
-	bufput(ob, "\n", 1);
+	sd_bufput(ob, "\n", 1);
 	return 1;
 }
 
-int render_link(Common::DataBuffer *ob, const Common::DataBuffer *link, const Common::DataBuffer *title, const Common::DataBuffer *content, void *opaque) {
+int render_link(Common::SDDataBuffer *ob, const Common::SDDataBuffer *link, const Common::SDDataBuffer *title, const Common::SDDataBuffer *content, void *opaque) {
 	if (!link)
 		return 0;
 
 	MDState *mdstate = (MDState *)opaque;
-	const Common::DataBuffer *text = content ? content : link;
+	const Common::SDDataBuffer *text = content ? content : link;
 
 	Common::String res = Common::String::format("\001" "\016+%02x0" "\001\016[%04x%04x%04x"
 		"%s" "\001\016]" "\001\016-%02x0", kMacFontUnderline, mdstate->linkr, mdstate->linkg, mdstate->linkb,
 		Common::String((const char *)text->data , text->size).c_str(), kMacFontUnderline);
 
-	bufput(ob, res.c_str(), res.size());
+	sd_bufput(ob, res.c_str(), res.size());
 
 	debug(1, "render_link(%s, %s, %s)", PR(link), title ? PR(title) : 0, content ? PR(content) : 0);
 	return 1;
 }
 
-int render_raw_html_tag(Common::DataBuffer *ob, const Common::DataBuffer *tag, void *opaque) {
+int render_raw_html_tag(Common::SDDataBuffer *ob, const Common::SDDataBuffer *tag, void *opaque) {
 	if (!tag)
 		return 0;
 
@@ -222,7 +222,7 @@ int render_raw_html_tag(Common::DataBuffer *ob, const Common::DataBuffer *tag, v
 	return 1;
 }
 
-int render_triple_emphasis(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
+int render_triple_emphasis(Common::SDDataBuffer *ob, const Common::SDDataBuffer *text, void *opaque) {
 	if (!text)
 		return 0;
 
@@ -230,7 +230,7 @@ int render_triple_emphasis(Common::DataBuffer *ob, const Common::DataBuffer *tex
 	return 1;
 }
 
-int render_strikethrough(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
+int render_strikethrough(Common::SDDataBuffer *ob, const Common::SDDataBuffer *text, void *opaque) {
 	if (!text)
 		return 0;
 
@@ -238,7 +238,7 @@ int render_strikethrough(Common::DataBuffer *ob, const Common::DataBuffer *text,
 	return 1;
 }
 
-int render_superscript(Common::DataBuffer *ob, const Common::DataBuffer *text, void *opaque) {
+int render_superscript(Common::SDDataBuffer *ob, const Common::SDDataBuffer *text, void *opaque) {
 	if (!text)
 		return 0;
 


Commit: e237634777ace6e2b7a6bec104b018a5798275a3
    https://github.com/scummvm/scummvm/commit/e237634777ace6e2b7a6bec104b018a5798275a3
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
COMMON: Turn sd_markdown into a class

Changed paths:
    common/formats/markdown.cpp
    common/formats/markdown.h
    graphics/macgui/mactext-md.cpp


diff --git a/common/formats/markdown.cpp b/common/formats/markdown.cpp
index c309b84a761..0735c182166 100644
--- a/common/formats/markdown.cpp
+++ b/common/formats/markdown.cpp
@@ -23,8 +23,6 @@
 
 namespace Common {
 
-#define REF_TABLE_SIZE 8
-
 #define BUFFER_BLOCK 0
 #define BUFFER_SPAN 1
 
@@ -54,12 +52,6 @@ size_t sd_autolink__url(size_t *rewind_p, SDDataBuffer *link,
 
 // stack.h
 
-struct SDStack {
-	void **item;
-	size_t size;
-	size_t asize;
-};
-
 void stack_free(SDStack *);
 int stack_grow(SDStack *, size_t);
 int stack_init(SDStack *, size_t);
@@ -113,19 +105,6 @@ struct LinkRef {
 	LinkRef *next;
 };
 
-/* render • structure containing one particular render */
-struct SDMarkdown {
-	SDCallbacks cb;
-	void *opaque;
-
-	LinkRef *refs[REF_TABLE_SIZE];
-	byte active_char[256];
-	SDStack work_bufs[2];
-	uint ext_flags;
-	size_t max_nesting;
-	int in_link_body;
-};
-
 /* char_trigger: function pointer to render active chars */
 /*   returns the number of chars taken care of */
 /*   data is the pointer of the beginning of the span */
@@ -2348,14 +2327,10 @@ static void expand_tabs(SDDataBuffer *ob, const byte *line, size_t size) {
  * EXPORTED FUNCTIONS *
  **********************/
 
-SDMarkdown *sd_markdown_new(uint extensions, size_t max_nesting, const SDCallbacks *callbacks, void *opaque) {
-	SDMarkdown *md = NULL;
-
+SDMarkdown::SDMarkdown(uint extensions, size_t max_nesting, const SDCallbacks *callbacks, void *opaque) {
 	assert(max_nesting > 0 && callbacks);
 
-	md = (SDMarkdown *)malloc(sizeof(SDMarkdown));
-	if (!md)
-		return NULL;
+	SDMarkdown *md = this;
 
 	memcpy(&md->cb, callbacks, sizeof(SDCallbacks));
 
@@ -2398,11 +2373,10 @@ SDMarkdown *sd_markdown_new(uint extensions, size_t max_nesting, const SDCallbac
 	md->opaque = opaque;
 	md->max_nesting = max_nesting;
 	md->in_link_body = 0;
-
-	return md;
 }
 
-Common::String sd_markdown_render(const byte *document, size_t doc_size, SDMarkdown *md) {
+Common::String SDMarkdown::render(const byte *document, size_t doc_size) {
+	SDMarkdown *md = this;
 #define MARKDOWN_GROW(x) ((x) + ((x) >> 1))
 	static const byte UTF8_BOM[] = {0xEF, 0xBB, 0xBF};
 
@@ -2483,24 +2457,20 @@ Common::String sd_markdown_render(const byte *document, size_t doc_size, SDMarkd
 	return res;
 }
 
-void
-sd_markdown_free(SDMarkdown *md) {
+SDMarkdown::~SDMarkdown() {
 	size_t i;
 
-	for (i = 0; i < (size_t)md->work_bufs[BUFFER_SPAN].asize; ++i)
-		sd_bufrelease((SDDataBuffer *)md->work_bufs[BUFFER_SPAN].item[i]);
-
-	for (i = 0; i < (size_t)md->work_bufs[BUFFER_BLOCK].asize; ++i)
-		sd_bufrelease((SDDataBuffer *)md->work_bufs[BUFFER_BLOCK].item[i]);
+	for (i = 0; i < (size_t)work_bufs[BUFFER_SPAN].asize; ++i)
+		sd_bufrelease((SDDataBuffer *)work_bufs[BUFFER_SPAN].item[i]);
 
-	stack_free(&md->work_bufs[BUFFER_SPAN]);
-	stack_free(&md->work_bufs[BUFFER_BLOCK]);
+	for (i = 0; i < (size_t)work_bufs[BUFFER_BLOCK].asize; ++i)
+		sd_bufrelease((SDDataBuffer *)work_bufs[BUFFER_BLOCK].item[i]);
 
-	free(md);
+	stack_free(&work_bufs[BUFFER_SPAN]);
+	stack_free(&work_bufs[BUFFER_BLOCK]);
 }
 
-void
-sd_version(int *ver_major, int *ver_minor, int *ver_revision) {
+void SDMarkdown::version(int *ver_major, int *ver_minor, int *ver_revision) {
 	*ver_major = SUNDOWN_VER_MAJOR;
 	*ver_minor = SUNDOWN_VER_MINOR;
 	*ver_revision = SUNDOWN_VER_REVISION;
diff --git a/common/formats/markdown.h b/common/formats/markdown.h
index e2f17581162..5047d4fa522 100644
--- a/common/formats/markdown.h
+++ b/common/formats/markdown.h
@@ -34,7 +34,6 @@ class String;
  ********************/
 
 struct SDDataBuffer;
-struct SDMarkdown;
 
 /* mkd_autolink - type of autolink */
 enum MKDAutolink {
@@ -62,6 +61,12 @@ enum mkd_extensions {
 	MKDEXT_LAX_SPACING = (1 << 8),
 };
 
+struct SDStack {
+	void **item;
+	size_t size;
+	size_t asize;
+};
+
 /* sd_callbacks - functions for rendering parsed data */
 struct SDCallbacks {
 	/* block level callbacks - NULL skips the block */
@@ -109,13 +114,29 @@ struct SDCallbacks {
 #define MKD_LIST_ORDERED	1
 #define MKD_LI_BLOCK		2  /* <li> containing block data */
 
-SDMarkdown *sd_markdown_new(uint extensions, size_t max_nesting, const SDCallbacks *callbacks, void *opaque);
+#define REF_TABLE_SIZE 8
+
+struct LinkRef;
 
-Common::String sd_markdown_render(const byte *document, size_t doc_size, SDMarkdown *md);
+class SDMarkdown {
+public:
+	SDCallbacks cb;
+	void *opaque;
 
-void sd_markdown_free(SDMarkdown *md);
+	LinkRef *refs[REF_TABLE_SIZE];
+	byte active_char[256];
+	SDStack work_bufs[2];
+	uint ext_flags;
+	size_t max_nesting;
+	int in_link_body;
 
-void sd_version(int *major, int *minor, int *revision);
+	SDMarkdown(uint extensions, size_t max_nesting, const SDCallbacks *callbacks, void *opaque);
+	~SDMarkdown();
+
+	Common::String render(const byte *document, size_t doc_size);
+
+	void version(int *major, int *minor, int *revision);
+};
 
 /* SDDataBuffer: character array buffer */
 struct SDDataBuffer {
diff --git a/graphics/macgui/mactext-md.cpp b/graphics/macgui/mactext-md.cpp
index 9b1e604c338..f15df583b3e 100644
--- a/graphics/macgui/mactext-md.cpp
+++ b/graphics/macgui/mactext-md.cpp
@@ -295,9 +295,8 @@ void MacText::setMarkdownText(const Common::U32String &str) {
 	mdState.linkg = 0;
 	mdState.linkb = 0xff;
 
-	Common::SDMarkdown *md = sd_markdown_new(0, 16, &cb, &mdState);
-	Common::String rendered = sd_markdown_render((const byte *)input.c_str(), input.size(), md);
-	sd_markdown_free(md);
+	Common::SDMarkdown md(0, 16, &cb, &mdState);
+	Common::String rendered = md.render((const byte *)input.c_str(), input.size());
 
 	setText(rendered);
 }


Commit: c20290afeb70ef99f90d507c97d30a4c0a2d14ae
    https://github.com/scummvm/scummvm/commit/c20290afeb70ef99f90d507c97d30a4c0a2d14ae
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
COMMON: Rename class variables in SDMarkdown

Changed paths:
    common/formats/markdown.cpp
    common/formats/markdown.h


diff --git a/common/formats/markdown.cpp b/common/formats/markdown.cpp
index 0735c182166..569f2873af8 100644
--- a/common/formats/markdown.cpp
+++ b/common/formats/markdown.cpp
@@ -160,7 +160,7 @@ static char_trigger markdown_char_ptrs[] = {
 static inline SDDataBuffer *rndr_newbuf(SDMarkdown *rndr, int type) {
 	static const size_t buf_size[2] = {256, 64};
 	SDDataBuffer *work = NULL;
-	SDStack *pool = &rndr->work_bufs[type];
+	SDStack *pool = &rndr->_work_bufs[type];
 
 	if (pool->size < pool->asize &&
 	        pool->item[pool->size] != NULL) {
@@ -175,7 +175,7 @@ static inline SDDataBuffer *rndr_newbuf(SDMarkdown *rndr, int type) {
 }
 
 static inline void rndr_popbuf(SDMarkdown *rndr, int type) {
-	rndr->work_bufs[type].size--;
+	rndr->_work_bufs[type].size--;
 }
 
 static void unscape_text(SDDataBuffer *ob, SDDataBuffer *src) {
@@ -369,20 +369,20 @@ static void parse_inline(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t
 	byte action = 0;
 	SDDataBuffer work = { 0, 0, 0, 0 };
 
-	if (rndr->work_bufs[BUFFER_SPAN].size +
-	        rndr->work_bufs[BUFFER_BLOCK].size > rndr->max_nesting)
+	if (rndr->_work_bufs[BUFFER_SPAN].size +
+	        rndr->_work_bufs[BUFFER_BLOCK].size > rndr->_max_nesting)
 		return;
 
 	while (i < size) {
 		/* copying inactive chars into the output */
-		while (end < size && (action = rndr->active_char[data[end]]) == 0) {
+		while (end < size && (action = rndr->_active_char[data[end]]) == 0) {
 			end++;
 		}
 
-		if (rndr->cb.normal_text) {
+		if (rndr->_cb.normal_text) {
 			work.data = data + i;
 			work.size = end - i;
-			rndr->cb.normal_text(ob, &work, rndr->opaque);
+			rndr->_cb.normal_text(ob, &work, rndr->_opaque);
 		} else
 			sd_bufput(ob, data + i, end - i);
 
@@ -499,7 +499,7 @@ static size_t parse_emph1(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t
 	SDDataBuffer *work = 0;
 	int r;
 
-	if (!rndr->cb.emphasis) return 0;
+	if (!rndr->_cb.emphasis) return 0;
 
 	/* skipping one symbol if coming from emph3 */
 	if (size > 1 && data[0] == c && data[1] == c) i = 1;
@@ -512,14 +512,14 @@ static size_t parse_emph1(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t
 
 		if (data[i] == c && !_isspace(data[i - 1])) {
 
-			if (rndr->ext_flags & MKDEXT_NO_INTRA_EMPHASIS) {
+			if (rndr->_ext_flags & MKDEXT_NO_INTRA_EMPHASIS) {
 				if (i + 1 < size && Common::isAlnum(data[i + 1]))
 					continue;
 			}
 
 			work = rndr_newbuf(rndr, BUFFER_SPAN);
 			parse_inline(work, rndr, data, i);
-			r = rndr->cb.emphasis(ob, work, rndr->opaque);
+			r = rndr->_cb.emphasis(ob, work, rndr->_opaque);
 			rndr_popbuf(rndr, BUFFER_SPAN);
 			return r ? i + 1 : 0;
 		}
@@ -535,7 +535,7 @@ static size_t parse_emph2(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t
 	SDDataBuffer *work = 0;
 	int r;
 
-	render_method = (c == '~') ? rndr->cb.strikethrough : rndr->cb.double_emphasis;
+	render_method = (c == '~') ? rndr->_cb.strikethrough : rndr->_cb.double_emphasis;
 
 	if (!render_method)
 		return 0;
@@ -548,7 +548,7 @@ static size_t parse_emph2(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t
 		if (i + 1 < size && data[i] == c && data[i + 1] == c && i && !_isspace(data[i - 1])) {
 			work = rndr_newbuf(rndr, BUFFER_SPAN);
 			parse_inline(work, rndr, data, i);
-			r = render_method(ob, work, rndr->opaque);
+			r = render_method(ob, work, rndr->_opaque);
 			rndr_popbuf(rndr, BUFFER_SPAN);
 			return r ? i + 2 : 0;
 		}
@@ -572,12 +572,12 @@ static size_t parse_emph3(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t
 		if (data[i] != c || _isspace(data[i - 1]))
 			continue;
 
-		if (i + 2 < size && data[i + 1] == c && data[i + 2] == c && rndr->cb.triple_emphasis) {
+		if (i + 2 < size && data[i + 1] == c && data[i + 2] == c && rndr->_cb.triple_emphasis) {
 			/* triple symbol found */
 			SDDataBuffer *work = rndr_newbuf(rndr, BUFFER_SPAN);
 
 			parse_inline(work, rndr, data, i);
-			r = rndr->cb.triple_emphasis(ob, work, rndr->opaque);
+			r = rndr->_cb.triple_emphasis(ob, work, rndr->_opaque);
 			rndr_popbuf(rndr, BUFFER_SPAN);
 			return r ? i + 3 : 0;
 
@@ -602,7 +602,7 @@ static size_t char_emphasis(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size
 	byte c = data[0];
 	size_t ret;
 
-	if (rndr->ext_flags & MKDEXT_NO_INTRA_EMPHASIS) {
+	if (rndr->_ext_flags & MKDEXT_NO_INTRA_EMPHASIS) {
 		if (offset > 0 && !_isspace(data[-1]) && data[-1] != '>')
 			return 0;
 	}
@@ -642,7 +642,7 @@ static size_t char_linebreak(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, siz
 	while (ob->size && ob->data[ob->size - 1] == ' ')
 		ob->size--;
 
-	return rndr->cb.linebreak(ob, rndr->opaque) ? 1 : 0;
+	return rndr->_cb.linebreak(ob, rndr->_opaque) ? 1 : 0;
 }
 
 /* char_codespan • '`' parsing a code span (assuming codespan != 0) */
@@ -675,10 +675,10 @@ static size_t char_codespan(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size
 	/* real code span */
 	if (f_begin < f_end) {
 		SDDataBuffer work = { data + f_begin, f_end - f_begin, 0, 0 };
-		if (!rndr->cb.codespan(ob, &work, rndr->opaque))
+		if (!rndr->_cb.codespan(ob, &work, rndr->_opaque))
 			end = 0;
 	} else {
-		if (!rndr->cb.codespan(ob, 0, rndr->opaque))
+		if (!rndr->_cb.codespan(ob, 0, rndr->_opaque))
 			end = 0;
 	}
 
@@ -694,10 +694,10 @@ static size_t char_escape(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t
 		if (strchr(escape_chars, data[1]) == NULL)
 			return 0;
 
-		if (rndr->cb.normal_text) {
+		if (rndr->_cb.normal_text) {
 			work.data = data + 1;
 			work.size = 1;
-			rndr->cb.normal_text(ob, &work, rndr->opaque);
+			rndr->_cb.normal_text(ob, &work, rndr->_opaque);
 		} else sd_bufputc(ob, data[1]);
 	} else if (size == 1) {
 		sd_bufputc(ob, data[0]);
@@ -723,10 +723,10 @@ static size_t char_entity(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t
 	else
 		return 0; /* lone '&' */
 
-	if (rndr->cb.entity) {
+	if (rndr->_cb.entity) {
 		work.data = data;
 		work.size = end;
-		rndr->cb.entity(ob, &work, rndr->opaque);
+		rndr->_cb.entity(ob, &work, rndr->_opaque);
 	} else sd_bufput(ob, data, end);
 
 	return end;
@@ -740,15 +740,15 @@ static size_t char_langle_tag(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, si
 	int ret = 0;
 
 	if (end > 2) {
-		if (rndr->cb.autolink && altype != MKDA_NOT_AUTOLINK) {
+		if (rndr->_cb.autolink && altype != MKDA_NOT_AUTOLINK) {
 			SDDataBuffer *u_link = rndr_newbuf(rndr, BUFFER_SPAN);
 			work.data = data + 1;
 			work.size = end - 2;
 			unscape_text(u_link, &work);
-			ret = rndr->cb.autolink(ob, u_link, altype, rndr->opaque);
+			ret = rndr->_cb.autolink(ob, u_link, altype, rndr->_opaque);
 			rndr_popbuf(rndr, BUFFER_SPAN);
-		} else if (rndr->cb.raw_html_tag)
-			ret = rndr->cb.raw_html_tag(ob, &work, rndr->opaque);
+		} else if (rndr->_cb.raw_html_tag)
+			ret = rndr->_cb.raw_html_tag(ob, &work, rndr->_opaque);
 	}
 
 	if (!ret) return 0;
@@ -759,7 +759,7 @@ static size_t char_autolink_www(SDDataBuffer *ob, SDMarkdown *rndr, byte *data,
 	SDDataBuffer *link, *link_url, *link_text;
 	size_t link_len, rewind;
 
-	if (!rndr->cb.link || rndr->in_link_body)
+	if (!rndr->_cb.link || rndr->_in_link_body)
 		return 0;
 
 	link = rndr_newbuf(rndr, BUFFER_SPAN);
@@ -770,13 +770,13 @@ static size_t char_autolink_www(SDDataBuffer *ob, SDMarkdown *rndr, byte *data,
 		sd_bufput(link_url, link->data, link->size);
 
 		ob->size -= rewind;
-		if (rndr->cb.normal_text) {
+		if (rndr->_cb.normal_text) {
 			link_text = rndr_newbuf(rndr, BUFFER_SPAN);
-			rndr->cb.normal_text(link_text, link, rndr->opaque);
-			rndr->cb.link(ob, link_url, NULL, link_text, rndr->opaque);
+			rndr->_cb.normal_text(link_text, link, rndr->_opaque);
+			rndr->_cb.link(ob, link_url, NULL, link_text, rndr->_opaque);
 			rndr_popbuf(rndr, BUFFER_SPAN);
 		} else {
-			rndr->cb.link(ob, link_url, NULL, link, rndr->opaque);
+			rndr->_cb.link(ob, link_url, NULL, link, rndr->_opaque);
 		}
 		rndr_popbuf(rndr, BUFFER_SPAN);
 	}
@@ -789,14 +789,14 @@ static size_t char_autolink_email(SDDataBuffer *ob, SDMarkdown *rndr, byte *data
 	SDDataBuffer *link;
 	size_t link_len, rewind;
 
-	if (!rndr->cb.autolink || rndr->in_link_body)
+	if (!rndr->_cb.autolink || rndr->_in_link_body)
 		return 0;
 
 	link = rndr_newbuf(rndr, BUFFER_SPAN);
 
 	if ((link_len = sd_autolink__email(&rewind, link, data, offset, size, 0)) > 0) {
 		ob->size -= rewind;
-		rndr->cb.autolink(ob, link, MKDA_EMAIL, rndr->opaque);
+		rndr->_cb.autolink(ob, link, MKDA_EMAIL, rndr->_opaque);
 	}
 
 	rndr_popbuf(rndr, BUFFER_SPAN);
@@ -807,14 +807,14 @@ static size_t char_autolink_url(SDDataBuffer *ob, SDMarkdown *rndr, byte *data,
 	SDDataBuffer *link;
 	size_t link_len, rewind;
 
-	if (!rndr->cb.autolink || rndr->in_link_body)
+	if (!rndr->_cb.autolink || rndr->_in_link_body)
 		return 0;
 
 	link = rndr_newbuf(rndr, BUFFER_SPAN);
 
 	if ((link_len = sd_autolink__url(&rewind, link, data, offset, size, 0)) > 0) {
 		ob->size -= rewind;
-		rndr->cb.autolink(ob, link, MKDA_NORMAL, rndr->opaque);
+		rndr->_cb.autolink(ob, link, MKDA_NORMAL, rndr->_opaque);
 	}
 
 	rndr_popbuf(rndr, BUFFER_SPAN);
@@ -829,12 +829,12 @@ static size_t char_link(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t o
 	SDDataBuffer *link = 0;
 	SDDataBuffer *title = 0;
 	SDDataBuffer *u_link = 0;
-	size_t org_work_size = rndr->work_bufs[BUFFER_SPAN].size;
+	size_t org_work_size = rndr->_work_bufs[BUFFER_SPAN].size;
 	int text_has_nl = 0, ret = 0;
 	int in_title = 0, qtype = 0;
 
 	/* checking whether the correct renderer exists */
-	if ((is_img && !rndr->cb.image) || (!is_img && !rndr->cb.link))
+	if ((is_img && !rndr->_cb.image) || (!is_img && !rndr->_cb.link))
 		goto cleanup;
 
 	/* looking for the matching closing bracket */
@@ -975,7 +975,7 @@ static size_t char_link(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t o
 			id.size = link_e - link_b;
 		}
 
-		lr = find_link_ref(rndr->refs, id.data, id.size);
+		lr = find_link_ref(rndr->_refs, id.data, id.size);
 		if (!lr)
 			goto cleanup;
 
@@ -1010,7 +1010,7 @@ static size_t char_link(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t o
 		}
 
 		/* finding the link_ref */
-		lr = find_link_ref(rndr->refs, id.data, id.size);
+		lr = find_link_ref(rndr->_refs, id.data, id.size);
 		if (!lr)
 			goto cleanup;
 
@@ -1030,9 +1030,9 @@ static size_t char_link(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t o
 		} else {
 			/* disable autolinking when parsing inline the
 			 * content of a link */
-			rndr->in_link_body = 1;
+			rndr->_in_link_body = 1;
 			parse_inline(content, rndr, data + 1, txt_e - 1);
-			rndr->in_link_body = 0;
+			rndr->_in_link_body = 0;
 		}
 	}
 
@@ -1046,14 +1046,14 @@ static size_t char_link(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t o
 		if (ob->size && ob->data[ob->size - 1] == '!')
 			ob->size -= 1;
 
-		ret = rndr->cb.image(ob, u_link, title, content, rndr->opaque);
+		ret = rndr->_cb.image(ob, u_link, title, content, rndr->_opaque);
 	} else {
-		ret = rndr->cb.link(ob, u_link, title, content, rndr->opaque);
+		ret = rndr->_cb.link(ob, u_link, title, content, rndr->_opaque);
 	}
 
 	/* cleanup */
 cleanup:
-	rndr->work_bufs[BUFFER_SPAN].size = (int)org_work_size;
+	rndr->_work_bufs[BUFFER_SPAN].size = (int)org_work_size;
 	return ret ? i : 0;
 }
 
@@ -1061,7 +1061,7 @@ static size_t char_superscript(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, s
 	size_t sup_start, sup_len;
 	SDDataBuffer *sup;
 
-	if (!rndr->cb.superscript)
+	if (!rndr->_cb.superscript)
 		return 0;
 
 	if (size < 2)
@@ -1087,7 +1087,7 @@ static size_t char_superscript(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, s
 
 	sup = rndr_newbuf(rndr, BUFFER_SPAN);
 	parse_inline(sup, rndr, data + sup_start, sup_len - sup_start);
-	rndr->cb.superscript(ob, sup, rndr->opaque);
+	rndr->_cb.superscript(ob, sup, rndr->_opaque);
 	rndr_popbuf(rndr, BUFFER_SPAN);
 
 	return (sup_start == 2) ? sup_len + 1 : sup_len;
@@ -1243,7 +1243,7 @@ static int is_atxheader(SDMarkdown *rndr, byte *data, size_t size) {
 	if (data[0] != '#')
 		return 0;
 
-	if (rndr->ext_flags & MKDEXT_SPACE_HEADERS) {
+	if (rndr->_ext_flags & MKDEXT_SPACE_HEADERS) {
 		size_t level = 0;
 
 		while (level < size && level < 6 && data[level] == '#')
@@ -1393,8 +1393,8 @@ static size_t parse_blockquote(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, s
 	}
 
 	parse_block(out, rndr, work_data, work_size);
-	if (rndr->cb.blockquote)
-		rndr->cb.blockquote(ob, out, rndr->opaque);
+	if (rndr->_cb.blockquote)
+		rndr->_cb.blockquote(ob, out, rndr->_opaque);
 	rndr_popbuf(rndr, BUFFER_BLOCK);
 	return end;
 }
@@ -1432,7 +1432,7 @@ static size_t parse_paragraph(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, si
 		 * let's check to see if there's some kind of block starting
 		 * here
 		 */
-		if ((rndr->ext_flags & MKDEXT_LAX_SPACING) && !Common::isAlnum(data[i])) {
+		if ((rndr->_ext_flags & MKDEXT_LAX_SPACING) && !Common::isAlnum(data[i])) {
 			if (prefix_oli(data + i, size - i) ||
 			        prefix_uli(data + i, size - i)) {
 				end = i;
@@ -1440,14 +1440,14 @@ static size_t parse_paragraph(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, si
 			}
 
 			/* see if an html block starts here */
-			if (data[i] == '<' && rndr->cb.blockhtml &&
+			if (data[i] == '<' && rndr->_cb.blockhtml &&
 			        parse_htmlblock(ob, rndr, data + i, size - i, 0)) {
 				end = i;
 				break;
 			}
 
 			/* see if a code fence starts here */
-			if ((rndr->ext_flags & MKDEXT_FENCED_CODE) != 0 &&
+			if ((rndr->_ext_flags & MKDEXT_FENCED_CODE) != 0 &&
 			        is_codefence(data + i, size - i, NULL) != 0) {
 				end = i;
 				break;
@@ -1464,8 +1464,8 @@ static size_t parse_paragraph(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, si
 	if (!level) {
 		SDDataBuffer *tmp = rndr_newbuf(rndr, BUFFER_BLOCK);
 		parse_inline(tmp, rndr, work.data, work.size);
-		if (rndr->cb.paragraph)
-			rndr->cb.paragraph(ob, tmp, rndr->opaque);
+		if (rndr->_cb.paragraph)
+			rndr->_cb.paragraph(ob, tmp, rndr->_opaque);
 		rndr_popbuf(rndr, BUFFER_BLOCK);
 	} else {
 		SDDataBuffer *header_work;
@@ -1486,8 +1486,8 @@ static size_t parse_paragraph(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, si
 				SDDataBuffer *tmp = rndr_newbuf(rndr, BUFFER_BLOCK);
 				parse_inline(tmp, rndr, work.data, work.size);
 
-				if (rndr->cb.paragraph)
-					rndr->cb.paragraph(ob, tmp, rndr->opaque);
+				if (rndr->_cb.paragraph)
+					rndr->_cb.paragraph(ob, tmp, rndr->_opaque);
 
 				rndr_popbuf(rndr, BUFFER_BLOCK);
 				work.data += beg;
@@ -1498,8 +1498,8 @@ static size_t parse_paragraph(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, si
 		header_work = rndr_newbuf(rndr, BUFFER_SPAN);
 		parse_inline(header_work, rndr, work.data, work.size);
 
-		if (rndr->cb.header)
-			rndr->cb.header(ob, header_work, (int)level, rndr->opaque);
+		if (rndr->_cb.header)
+			rndr->_cb.header(ob, header_work, (int)level, rndr->_opaque);
 
 		rndr_popbuf(rndr, BUFFER_SPAN);
 	}
@@ -1543,8 +1543,8 @@ static size_t parse_fencedcode(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, s
 	if (work->size && work->data[work->size - 1] != '\n')
 		sd_bufputc(work, '\n');
 
-	if (rndr->cb.blockcode)
-		rndr->cb.blockcode(ob, work, lang.size ? &lang : NULL, rndr->opaque);
+	if (rndr->_cb.blockcode)
+		rndr->_cb.blockcode(ob, work, lang.size ? &lang : NULL, rndr->_opaque);
 
 	rndr_popbuf(rndr, BUFFER_BLOCK);
 	return beg;
@@ -1582,8 +1582,8 @@ static size_t parse_blockcode(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, si
 
 	sd_bufputc(work, '\n');
 
-	if (rndr->cb.blockcode)
-		rndr->cb.blockcode(ob, work, NULL, rndr->opaque);
+	if (rndr->_cb.blockcode)
+		rndr->_cb.blockcode(ob, work, NULL, rndr->_opaque);
 
 	rndr_popbuf(rndr, BUFFER_BLOCK);
 	return beg;
@@ -1643,7 +1643,7 @@ static size_t parse_listitem(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, siz
 
 		pre = i;
 
-		if (rndr->ext_flags & MKDEXT_FENCED_CODE) {
+		if (rndr->_ext_flags & MKDEXT_FENCED_CODE) {
 			if (is_codefence(data + beg + i, end - beg - i, NULL) != 0)
 				in_fence = !in_fence;
 		}
@@ -1713,8 +1713,8 @@ static size_t parse_listitem(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, siz
 	}
 
 	/* render of li itself */
-	if (rndr->cb.listitem)
-		rndr->cb.listitem(ob, inter, *flags, rndr->opaque);
+	if (rndr->_cb.listitem)
+		rndr->_cb.listitem(ob, inter, *flags, rndr->_opaque);
 
 	rndr_popbuf(rndr, BUFFER_SPAN);
 	rndr_popbuf(rndr, BUFFER_SPAN);
@@ -1728,8 +1728,8 @@ static size_t parse_list(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t
 
 	work = rndr_newbuf(rndr, BUFFER_BLOCK);
 
-	if (rndr->cb.list_start)
-		rndr->cb.list_start(ob, work, flags, rndr->opaque);
+	if (rndr->_cb.list_start)
+		rndr->_cb.list_start(ob, work, flags, rndr->_opaque);
 
 	while (i < size) {
 		j = parse_listitem(work, rndr, data + i, size - i, &flags);
@@ -1739,8 +1739,8 @@ static size_t parse_list(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t
 			break;
 	}
 
-	if (rndr->cb.list)
-		rndr->cb.list(ob, work, flags, rndr->opaque);
+	if (rndr->_cb.list)
+		rndr->_cb.list(ob, work, flags, rndr->_opaque);
 	rndr_popbuf(rndr, BUFFER_BLOCK);
 	return i;
 }
@@ -1769,8 +1769,8 @@ static size_t parse_atxheader(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, si
 
 		parse_inline(work, rndr, data + i, end - i);
 
-		if (rndr->cb.header)
-			rndr->cb.header(ob, work, (int)level, rndr->opaque);
+		if (rndr->_cb.header)
+			rndr->_cb.header(ob, work, (int)level, rndr->_opaque);
 
 		rndr_popbuf(rndr, BUFFER_SPAN);
 	}
@@ -1879,8 +1879,8 @@ static size_t parse_htmlblock(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, si
 
 			if (j) {
 				work.size = i + j;
-				if (do_render && rndr->cb.blockhtml)
-					rndr->cb.blockhtml(ob, &work, rndr->opaque);
+				if (do_render && rndr->_cb.blockhtml)
+					rndr->_cb.blockhtml(ob, &work, rndr->_opaque);
 				return work.size;
 			}
 		}
@@ -1896,8 +1896,8 @@ static size_t parse_htmlblock(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, si
 				j = is_empty(data + i, size - i);
 				if (j) {
 					work.size = i + j;
-					if (do_render && rndr->cb.blockhtml)
-						rndr->cb.blockhtml(ob, &work, rndr->opaque);
+					if (do_render && rndr->_cb.blockhtml)
+						rndr->_cb.blockhtml(ob, &work, rndr->_opaque);
 					return work.size;
 				}
 			}
@@ -1922,8 +1922,8 @@ static size_t parse_htmlblock(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, si
 
 	/* the end of the block has been found */
 	work.size = tag_end;
-	if (do_render && rndr->cb.blockhtml)
-		rndr->cb.blockhtml(ob, &work, rndr->opaque);
+	if (do_render && rndr->_cb.blockhtml)
+		rndr->_cb.blockhtml(ob, &work, rndr->_opaque);
 
 	return tag_end;
 }
@@ -1932,7 +1932,7 @@ static void parse_table_row(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size
 	size_t i = 0, col;
 	SDDataBuffer *row_work = 0;
 
-	if (!rndr->cb.table_cell || !rndr->cb.table_row)
+	if (!rndr->_cb.table_cell || !rndr->_cb.table_row)
 		return;
 
 	row_work = rndr_newbuf(rndr, BUFFER_SPAN);
@@ -1960,7 +1960,7 @@ static void parse_table_row(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size
 			cell_end--;
 
 		parse_inline(cell_work, rndr, data + cell_start, 1 + cell_end - cell_start);
-		rndr->cb.table_cell(row_work, cell_work, col_data[col] | header_flag, rndr->opaque);
+		rndr->_cb.table_cell(row_work, cell_work, col_data[col] | header_flag, rndr->_opaque);
 
 		rndr_popbuf(rndr, BUFFER_SPAN);
 		i++;
@@ -1968,10 +1968,10 @@ static void parse_table_row(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size
 
 	for (; col < columns; ++col) {
 		SDDataBuffer empty_cell = { 0, 0, 0, 0 };
-		rndr->cb.table_cell(row_work, &empty_cell, col_data[col] | header_flag, rndr->opaque);
+		rndr->_cb.table_cell(row_work, &empty_cell, col_data[col] | header_flag, rndr->_opaque);
 	}
 
-	rndr->cb.table_row(ob, row_work, rndr->opaque);
+	rndr->_cb.table_row(ob, row_work, rndr->_opaque);
 
 	rndr_popbuf(rndr, BUFFER_SPAN);
 }
@@ -2102,8 +2102,8 @@ static size_t parse_table(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t
 			i++;
 		}
 
-		if (rndr->cb.table)
-			rndr->cb.table(ob, header_work, body_work, rndr->opaque);
+		if (rndr->_cb.table)
+			rndr->_cb.table(ob, header_work, body_work, rndr->_opaque);
 	}
 
 	free(col_data);
@@ -2118,8 +2118,8 @@ static void parse_block(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t s
 	byte *txt_data;
 	beg = 0;
 
-	if (rndr->work_bufs[BUFFER_SPAN].size +
-	        rndr->work_bufs[BUFFER_BLOCK].size > rndr->max_nesting)
+	if (rndr->_work_bufs[BUFFER_SPAN].size +
+	        rndr->_work_bufs[BUFFER_BLOCK].size > rndr->_max_nesting)
 		return;
 
 	while (beg < size) {
@@ -2129,7 +2129,7 @@ static void parse_block(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t s
 		if (is_atxheader(rndr, txt_data, end))
 			beg += parse_atxheader(ob, rndr, txt_data, end);
 
-		else if (data[beg] == '<' && rndr->cb.blockhtml &&
+		else if (data[beg] == '<' && rndr->_cb.blockhtml &&
 		         (i = parse_htmlblock(ob, rndr, txt_data, end, 1)) != 0)
 			beg += i;
 
@@ -2137,8 +2137,8 @@ static void parse_block(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t s
 			beg += i;
 
 		else if (is_hrule(txt_data, end)) {
-			if (rndr->cb.hrule)
-				rndr->cb.hrule(ob, rndr->opaque);
+			if (rndr->_cb.hrule)
+				rndr->_cb.hrule(ob, rndr->_opaque);
 
 			while (beg < size && data[beg] != '\n')
 				beg++;
@@ -2146,11 +2146,11 @@ static void parse_block(SDDataBuffer *ob, SDMarkdown *rndr, byte *data, size_t s
 			beg++;
 		}
 
-		else if ((rndr->ext_flags & MKDEXT_FENCED_CODE) != 0 &&
+		else if ((rndr->_ext_flags & MKDEXT_FENCED_CODE) != 0 &&
 		         (i = parse_fencedcode(ob, rndr, txt_data, end)) != 0)
 			beg += i;
 
-		else if ((rndr->ext_flags & MKDEXT_TABLES) != 0 &&
+		else if ((rndr->_ext_flags & MKDEXT_TABLES) != 0 &&
 		         (i = parse_table(ob, rndr, txt_data, end)) != 0)
 			beg += i;
 
@@ -2332,47 +2332,47 @@ SDMarkdown::SDMarkdown(uint extensions, size_t max_nesting, const SDCallbacks *c
 
 	SDMarkdown *md = this;
 
-	memcpy(&md->cb, callbacks, sizeof(SDCallbacks));
+	memcpy(&md->_cb, callbacks, sizeof(SDCallbacks));
 
-	stack_init(&md->work_bufs[BUFFER_BLOCK], 4);
-	stack_init(&md->work_bufs[BUFFER_SPAN], 8);
+	stack_init(&md->_work_bufs[BUFFER_BLOCK], 4);
+	stack_init(&md->_work_bufs[BUFFER_SPAN], 8);
 
-	memset(md->active_char, 0x0, 256);
+	memset(md->_active_char, 0x0, 256);
 
-	if (md->cb.emphasis || md->cb.double_emphasis || md->cb.triple_emphasis) {
-		md->active_char[(int)'*'] = MD_CHAR_EMPHASIS;
-		md->active_char[(int)'_'] = MD_CHAR_EMPHASIS;
+	if (md->_cb.emphasis || md->_cb.double_emphasis || md->_cb.triple_emphasis) {
+		md->_active_char[(int)'*'] = MD_CHAR_EMPHASIS;
+		md->_active_char[(int)'_'] = MD_CHAR_EMPHASIS;
 		if (extensions & MKDEXT_STRIKETHROUGH)
-			md->active_char[(int)'~'] = MD_CHAR_EMPHASIS;
+			md->_active_char[(int)'~'] = MD_CHAR_EMPHASIS;
 	}
 
-	if (md->cb.codespan)
-		md->active_char[(int)'`'] = MD_CHAR_CODESPAN;
+	if (md->_cb.codespan)
+		md->_active_char[(int)'`'] = MD_CHAR_CODESPAN;
 
-	if (md->cb.linebreak)
-		md->active_char[(int)'\n'] = MD_CHAR_LINEBREAK;
+	if (md->_cb.linebreak)
+		md->_active_char[(int)'\n'] = MD_CHAR_LINEBREAK;
 
-	if (md->cb.image || md->cb.link)
-		md->active_char[(int)'['] = MD_CHAR_LINK;
+	if (md->_cb.image || md->_cb.link)
+		md->_active_char[(int)'['] = MD_CHAR_LINK;
 
-	md->active_char[(int)'<'] = MD_CHAR_LANGLE;
-	md->active_char[(int)'\\'] = MD_CHAR_ESCAPE;
-	md->active_char[(int)'&'] = MD_CHAR_ENTITITY;
+	md->_active_char[(int)'<'] = MD_CHAR_LANGLE;
+	md->_active_char[(int)'\\'] = MD_CHAR_ESCAPE;
+	md->_active_char[(int)'&'] = MD_CHAR_ENTITITY;
 
 	if (extensions & MKDEXT_AUTOLINK) {
-		md->active_char[(int)':'] = MD_CHAR_AUTOLINK_URL;
-		md->active_char[(int)'@'] = MD_CHAR_AUTOLINK_EMAIL;
-		md->active_char[(int)'w'] = MD_CHAR_AUTOLINK_WWW;
+		md->_active_char[(int)':'] = MD_CHAR_AUTOLINK_URL;
+		md->_active_char[(int)'@'] = MD_CHAR_AUTOLINK_EMAIL;
+		md->_active_char[(int)'w'] = MD_CHAR_AUTOLINK_WWW;
 	}
 
 	if (extensions & MKDEXT_SUPERSCRIPT)
-		md->active_char[(int)'^'] = MD_CHAR_SUPERSCRIPT;
+		md->_active_char[(int)'^'] = MD_CHAR_SUPERSCRIPT;
 
 	/* Extension data */
-	md->ext_flags = extensions;
-	md->opaque = opaque;
-	md->max_nesting = max_nesting;
-	md->in_link_body = 0;
+	md->_ext_flags = extensions;
+	md->_opaque = opaque;
+	md->_max_nesting = max_nesting;
+	md->_in_link_body = 0;
 }
 
 Common::String SDMarkdown::render(const byte *document, size_t doc_size) {
@@ -2391,7 +2391,7 @@ Common::String SDMarkdown::render(const byte *document, size_t doc_size) {
 	sd_bufgrow(text, doc_size);
 
 	/* reset the references table */
-	memset(&md->refs, 0x0, REF_TABLE_SIZE * sizeof(void *));
+	memset(&md->_refs, 0x0, REF_TABLE_SIZE * sizeof(void *));
 
 	/* first pass: looking for references, copying everything else */
 	beg = 0;
@@ -2402,7 +2402,7 @@ Common::String SDMarkdown::render(const byte *document, size_t doc_size) {
 		beg += 3;
 
 	while (beg < doc_size) /* iterating over lines */
-		if (is_ref(document, beg, doc_size, &end, md->refs))
+		if (is_ref(document, beg, doc_size, &end, md->_refs))
 			beg = end;
 		else { /* skipping to the next line */
 			end = beg;
@@ -2429,8 +2429,8 @@ Common::String SDMarkdown::render(const byte *document, size_t doc_size) {
 	sd_bufgrow(ob, MARKDOWN_GROW(text->size));
 
 	/* second pass: actual rendering */
-	if (md->cb.doc_header)
-		md->cb.doc_header(ob, md->opaque);
+	if (md->_cb.doc_header)
+		md->_cb.doc_header(ob, md->_opaque);
 
 	if (text->size) {
 		/* adding a final newline if not already present */
@@ -2440,15 +2440,15 @@ Common::String SDMarkdown::render(const byte *document, size_t doc_size) {
 		parse_block(ob, md, text->data, text->size);
 	}
 
-	if (md->cb.doc_footer)
-		md->cb.doc_footer(ob, md->opaque);
+	if (md->_cb.doc_footer)
+		md->_cb.doc_footer(ob, md->_opaque);
 
 	/* clean-up */
 	sd_bufrelease(text);
-	free_link_refs(md->refs);
+	free_link_refs(md->_refs);
 
-	assert(md->work_bufs[BUFFER_SPAN].size == 0);
-	assert(md->work_bufs[BUFFER_BLOCK].size == 0);
+	assert(md->_work_bufs[BUFFER_SPAN].size == 0);
+	assert(md->_work_bufs[BUFFER_BLOCK].size == 0);
 
 	Common::String res = Common::String((const char *)ob->data, ob->size);
 
@@ -2460,14 +2460,14 @@ Common::String SDMarkdown::render(const byte *document, size_t doc_size) {
 SDMarkdown::~SDMarkdown() {
 	size_t i;
 
-	for (i = 0; i < (size_t)work_bufs[BUFFER_SPAN].asize; ++i)
-		sd_bufrelease((SDDataBuffer *)work_bufs[BUFFER_SPAN].item[i]);
+	for (i = 0; i < (size_t)_work_bufs[BUFFER_SPAN].asize; ++i)
+		sd_bufrelease((SDDataBuffer *)_work_bufs[BUFFER_SPAN].item[i]);
 
-	for (i = 0; i < (size_t)work_bufs[BUFFER_BLOCK].asize; ++i)
-		sd_bufrelease((SDDataBuffer *)work_bufs[BUFFER_BLOCK].item[i]);
+	for (i = 0; i < (size_t)_work_bufs[BUFFER_BLOCK].asize; ++i)
+		sd_bufrelease((SDDataBuffer *)_work_bufs[BUFFER_BLOCK].item[i]);
 
-	stack_free(&work_bufs[BUFFER_SPAN]);
-	stack_free(&work_bufs[BUFFER_BLOCK]);
+	stack_free(&_work_bufs[BUFFER_SPAN]);
+	stack_free(&_work_bufs[BUFFER_BLOCK]);
 }
 
 void SDMarkdown::version(int *ver_major, int *ver_minor, int *ver_revision) {
diff --git a/common/formats/markdown.h b/common/formats/markdown.h
index 5047d4fa522..357fa882067 100644
--- a/common/formats/markdown.h
+++ b/common/formats/markdown.h
@@ -120,15 +120,15 @@ struct LinkRef;
 
 class SDMarkdown {
 public:
-	SDCallbacks cb;
-	void *opaque;
-
-	LinkRef *refs[REF_TABLE_SIZE];
-	byte active_char[256];
-	SDStack work_bufs[2];
-	uint ext_flags;
-	size_t max_nesting;
-	int in_link_body;
+	SDCallbacks _cb;
+	void *_opaque;
+
+	LinkRef *_refs[REF_TABLE_SIZE];
+	byte _active_char[256];
+	SDStack _work_bufs[2];
+	uint _ext_flags;
+	size_t _max_nesting;
+	int _in_link_body;
 
 	SDMarkdown(uint extensions, size_t max_nesting, const SDCallbacks *callbacks, void *opaque);
 	~SDMarkdown();


Commit: 2df991721d303e3410e9c9328992bf35b7212a20
    https://github.com/scummvm/scummvm/commit/2df991721d303e3410e9c9328992bf35b7212a20
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: Added initial code for indentaiton in MacText

Changed paths:
    graphics/macgui/mactext-md.cpp
    graphics/macgui/mactext.cpp
    graphics/macgui/mactext.h
    graphics/macgui/macwindowmanager.cpp


diff --git a/graphics/macgui/mactext-md.cpp b/graphics/macgui/mactext-md.cpp
index f15df583b3e..c1eada16e0d 100644
--- a/graphics/macgui/mactext-md.cpp
+++ b/graphics/macgui/mactext-md.cpp
@@ -59,7 +59,7 @@ void render_header(Common::SDDataBuffer *ob, const Common::SDDataBuffer *text, i
 
 	debug(1, "render_header(%s)", PR(text));
 
-	Common::String res = Common::String::format("\016+00%01x%s\001\016-00f\n", level, Common::String((const char *)text->data , text->size).c_str());
+	Common::String res = Common::String::format("\016+00%01x0" "%s" "\001\016-00f0\n", level, Common::String((const char *)text->data , text->size).c_str());
 
 	sd_bufput(ob, res.c_str(), res.size());
 }
@@ -164,7 +164,7 @@ int render_double_emphasis(Common::SDDataBuffer *ob, const Common::SDDataBuffer
 
 	debug(1, "render_double_emphasis(%s)", PR(text));
 
-	Common::String res = Common::String::format("\001\016+%02x0%s\001\016-%02x0", kMacFontBold, Common::String((const char *)text->data , text->size).c_str(), kMacFontBold);
+	Common::String res = Common::String::format("\001\016+%02x00" "%s" "\001\016-%02x00", kMacFontBold, Common::String((const char *)text->data , text->size).c_str(), kMacFontBold);
 
 	sd_bufput(ob, res.c_str(), res.size());
 	return 1;
@@ -176,7 +176,7 @@ int render_emphasis(Common::SDDataBuffer *ob, const Common::SDDataBuffer *text,
 
 	debug(1, "render_emphasis(%s)", PR(text));
 
-	Common::String res = Common::String::format("\001\016+%02x0%s\001\016-%02x0", kMacFontItalic, Common::String((const char *)text->data , text->size).c_str(), kMacFontItalic);
+	Common::String res = Common::String::format("\001\016+%02x00" "%s" "\001\016-%02x00", kMacFontItalic, Common::String((const char *)text->data , text->size).c_str(), kMacFontItalic);
 
 	sd_bufput(ob, res.c_str(), res.size());
 	return 1;
@@ -204,8 +204,8 @@ int render_link(Common::SDDataBuffer *ob, const Common::SDDataBuffer *link, cons
 	MDState *mdstate = (MDState *)opaque;
 	const Common::SDDataBuffer *text = content ? content : link;
 
-	Common::String res = Common::String::format("\001" "\016+%02x0" "\001\016[%04x%04x%04x"
-		"%s" "\001\016]" "\001\016-%02x0", kMacFontUnderline, mdstate->linkr, mdstate->linkg, mdstate->linkb,
+	Common::String res = Common::String::format("\001" "\016+%02x00" "\001\016[%04x%04x%04x"
+		"%s" "\001\016]" "\001\016-%02x00", kMacFontUnderline, mdstate->linkr, mdstate->linkg, mdstate->linkb,
 		Common::String((const char *)text->data , text->size).c_str(), kMacFontUnderline);
 
 	sd_bufput(ob, res.c_str(), res.size());
diff --git a/graphics/macgui/mactext.cpp b/graphics/macgui/mactext.cpp
index 84683fea9c7..354cabb40c1 100644
--- a/graphics/macgui/mactext.cpp
+++ b/graphics/macgui/mactext.cpp
@@ -658,6 +658,7 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 	Common::U32String paragraph, tmp;
 
 	MacFontRun current_format = _defaultFormatting;
+	int indentSize = 0;
 
 	while (*l) {
 		paragraph.clear();
@@ -701,8 +702,8 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 				s++;
 
 				// First two digits is slant, third digit is Header number
-				if (*s == '+') { // \016+XXY  -- opening textSlant. H<Y>
-					uint16 textSlant, headSize;
+				if (*s == '+') { // \016+XXYZ  -- opening textSlant, H<Y>, indent<+Z>
+					uint16 textSlant, headSize, indent;
 					s++;
 
 					s = readHex(&textSlant, s, 2);
@@ -715,10 +716,16 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 						current_format.fontSize = _defaultFormatting.fontSize * sizes[headSize];
 					}
 
-					D(9, "** splitString+: fontId: %d, textSlant: %d, fontSize: %d,",
-							current_format.fontId, current_format.textSlant, current_format.fontSize);
-				} else if (*s == '-') { // \016-XXY  -- closing textSlant, H<Y>
-					uint16 textSlant, headSize;
+					s = readHex(&indent, s, 1);
+
+					if (s)
+						indentSize += 20 * indent;
+
+					D(9, "** splitString+: fontId: %d, textSlant: %d, fontSize: %d, indent: %d",
+							current_format.fontId, current_format.textSlant, current_format.fontSize,
+							indent);
+				} else if (*s == '-') { // \016-XXYZ  -- closing textSlant, H<Y>, indent<+Z>
+					uint16 textSlant, headSize, indent;
 					s++;
 
 					s = readHex(&textSlant, s, 2);
@@ -729,8 +736,14 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 					if (headSize == 0xf) // reset
 						current_format.fontSize = _defaultFormatting.fontSize;
 
-					D(9, "** splitString-: fontId: %d, textSlant: %d, fontSize: %d,",
-							current_format.fontId, current_format.textSlant, current_format.fontSize);
+					s = readHex(&indent, s, 1);
+
+					if (s)
+						indentSize -= 20 * indent;
+
+					D(9, "** splitString-: fontId: %d, textSlant: %d, fontSize: %d, indent: %d",
+							current_format.fontId, current_format.textSlant, current_format.fontSize,
+							indent);
 				} else if (*s == '[') { // \016[RRGGBB  -- setting color
 					uint16 palinfo1, palinfo2, palinfo3;
 					s++;
@@ -786,8 +799,11 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 				_textLines[curLine].lastChunk().text = tmp;
 				continue;
 			}
+
+			_textLines[curLine].indent = indentSize;
+
 			// calc word_width, the trick we define here is we don`t count the space
-			int word_width = getStringWidth(current_format, tmp);
+			int word_width = _textLines[curLine].indent + getStringWidth(current_format, tmp);
 			// add all spaces left
 			while (*s == ' ') {
 				tmp += *s;
@@ -830,7 +846,7 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 					// we just need to deal it specially
 
 					// meaning you have to split this word;
-					int tmp_width = 0;
+					int tmp_width = _textLines[curLine].indent;
 					_textLines[curLine].chunks.push_back(word[i]);
 					// empty the string
 					_textLines[curLine].lastChunk().text = Common::U32String();
@@ -858,8 +874,8 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 							_textLines.insert_at(curLine, MacTextLine());
 							_textLines[curLine].chunks.push_back(word[i]);
 							_textLines[curLine].lastChunk().text = Common::U32String();
-							tmp_width = 0;
-							cur_width = 0;
+							tmp_width = _textLines[curLine].indent;
+							cur_width = _textLines[curLine].indent;
 						}
 						tmp_width += char_width;
 						_textLines[curLine].lastChunk().text += c;
@@ -1051,7 +1067,7 @@ int MacText::getLineWidth(int line, bool enforce, int col) {
 	if (_textLines[line].width != -1 && !enforce && col == -1)
 		return _textLines[line].width;
 
-	int width = 0;
+	int width = _textLines[line].indent;
 	int height = 0;
 	int charwidth = 0;
 
diff --git a/graphics/macgui/mactext.h b/graphics/macgui/mactext.h
index e087dbf83bc..b0b8e0fc1c2 100644
--- a/graphics/macgui/mactext.h
+++ b/graphics/macgui/mactext.h
@@ -109,20 +109,15 @@ struct MacFontRun {
 };
 
 struct MacTextLine {
-	int width;
-	int height;
-	int y;
-	int charwidth;
-	bool paragraphEnd;
+	int width = -1;
+	int height = -1;
+	int y = 0;
+	int charwidth = -1;
+	bool paragraphEnd = false;
+	int indent = 0;
 
 	Common::Array<MacFontRun> chunks;
 
-	MacTextLine() {
-		width = height = charwidth = -1;
-		y = 0;
-		paragraphEnd = false;
-	}
-
 	MacFontRun &firstChunk() { return chunks[0]; }
 	MacFontRun &lastChunk() { return chunks[chunks.size() - 1]; }
 
diff --git a/graphics/macgui/macwindowmanager.cpp b/graphics/macgui/macwindowmanager.cpp
index 512e65b7fdd..9589bc036b2 100644
--- a/graphics/macgui/macwindowmanager.cpp
+++ b/graphics/macgui/macwindowmanager.cpp
@@ -585,7 +585,7 @@ Common::U32String stripFormat(const Common::U32String &str) {
 			} else if (*s == '\016') {	// human-readable format
 				s++;
 				if (*s == '+' || *s == '-') // style + header size
-					s += 4;
+					s += 5;
 				else if (*s == '[') // color information
 					s += 13;
 				else if (*s == ']') // default color


Commit: 168f4b7b3a01844d821d6040db1284604577fee9
    https://github.com/scummvm/scummvm/commit/168f4b7b3a01844d821d6040db1284604577fee9
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: Apply indentation to MacText

Changed paths:
    graphics/macgui/mactext.cpp


diff --git a/graphics/macgui/mactext.cpp b/graphics/macgui/mactext.cpp
index 354cabb40c1..599750e4c74 100644
--- a/graphics/macgui/mactext.cpp
+++ b/graphics/macgui/mactext.cpp
@@ -987,7 +987,7 @@ void MacText::render(int from, int to, int shadow) {
 	}
 
 	for (int i = myFrom; i != myTo; i += delta) {
-		int xOffset = getAlignOffset(i);
+		int xOffset = getAlignOffset(i) + _textLines[i].indent;
 		xOffset++;
 
 		int start = 0, end = _textLines[i].chunks.size();


Commit: c147f4ee5f6f1b441481e172a09edb6758fda998
    https://github.com/scummvm/scummvm/commit/c147f4ee5f6f1b441481e172a09edb6758fda998
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Indent list items in Markdown

Changed paths:
    graphics/macgui/mactext-md.cpp


diff --git a/graphics/macgui/mactext-md.cpp b/graphics/macgui/mactext-md.cpp
index c1eada16e0d..a0b6d802f13 100644
--- a/graphics/macgui/mactext-md.cpp
+++ b/graphics/macgui/mactext-md.cpp
@@ -73,6 +73,8 @@ void render_list_start(Common::SDDataBuffer *ob, const Common::SDDataBuffer *tex
 
 	mdstate->listNum.push_back(flags & MKD_LIST_ORDERED ? 1 : -1);
 
+	sd_bufput(ob, "\016+0001", 6);
+
 	debug(1, "render_list_start(%s, %d)", PR(text), flags);
 }
 
@@ -82,7 +84,7 @@ void render_list(Common::SDDataBuffer *ob, const Common::SDDataBuffer *text, int
 	mdstate->listNum.pop_back();
 
 	sd_bufput(ob, text->data, text->size);
-	sd_bufput(ob, "\n", 1);
+	sd_bufput(ob, "\n\016-0001", 7);
 
 	debug(1, "render_list(%s, %d)", PR(text), flags);
 }


Commit: 0294d607de3c10f1fb79726bb80cc686546b8c13
    https://github.com/scummvm/scummvm/commit/0294d607de3c10f1fb79726bb80cc686546b8c13
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: Made inentation in Markdown dynamic and fix it for long lines

Changed paths:
    graphics/macgui/mactext.cpp


diff --git a/graphics/macgui/mactext.cpp b/graphics/macgui/mactext.cpp
index 599750e4c74..34ef8ab9cd1 100644
--- a/graphics/macgui/mactext.cpp
+++ b/graphics/macgui/mactext.cpp
@@ -719,7 +719,7 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 					s = readHex(&indent, s, 1);
 
 					if (s)
-						indentSize += 20 * indent;
+						indentSize += indent * current_format.fontSize * 2;
 
 					D(9, "** splitString+: fontId: %d, textSlant: %d, fontSize: %d, indent: %d",
 							current_format.fontId, current_format.textSlant, current_format.fontSize,
@@ -739,7 +739,7 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 					s = readHex(&indent, s, 1);
 
 					if (s)
-						indentSize -= 20 * indent;
+						indentSize -= indent * current_format.fontSize * 2;
 
 					D(9, "** splitString-: fontId: %d, textSlant: %d, fontSize: %d, indent: %d",
 							current_format.fontId, current_format.textSlant, current_format.fontSize,
@@ -834,6 +834,7 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 			if (cur_width + word_width >= _maxWidth && cur_width != 0) {
 				++curLine;
 				_textLines.insert_at(curLine, MacTextLine());
+				_textLines[curLine].indent = indentSize;
 			}
 
 			// deal with the super long word situation


Commit: 03a758c3b185ae1347c2ff0f9613d9bb780703cf
    https://github.com/scummvm/scummvm/commit/03a758c3b185ae1347c2ff0f9613d9bb780703cf
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: Proper indentation of bullet lists in Markdown

Changed paths:
    graphics/macgui/mactext-md.cpp
    graphics/macgui/mactext.cpp
    graphics/macgui/mactext.h
    graphics/macgui/macwindowmanager.cpp


diff --git a/graphics/macgui/mactext-md.cpp b/graphics/macgui/mactext-md.cpp
index a0b6d802f13..ff84493b33e 100644
--- a/graphics/macgui/mactext-md.cpp
+++ b/graphics/macgui/mactext-md.cpp
@@ -98,16 +98,21 @@ void render_listitem(Common::SDDataBuffer *ob, const Common::SDDataBuffer *text,
 	for (int i = 0; i < depth; i++)
 		sd_bufput(ob, "  ", 2);
 
+	Common::String prefix;
 	if (flags & MKD_LIST_ORDERED) {
-		Common::String prefix = Common::String::format("%d. ", listNum);
-
-		sd_bufput(ob, prefix.c_str(), prefix.size());
+		prefix = Common::String::format("%d. ", listNum);
 
 		mdstate->listNum.back()++;
 	} else {
-		sd_bufput(ob, "* ", 2);
+		prefix = "* ";
 	}
 
+	Common::String res = Common::String::format("\016*%02x%s", prefix.size(), prefix.c_str());
+
+	sd_bufput(ob, res.c_str(), res.size());
+
+	sd_bufput(ob, prefix.c_str(), prefix.size());
+
 	sd_bufput(ob, text->data, text->size);
 
 	debug(1, "render_listitem(%s, %d)", PR(text), flags);
diff --git a/graphics/macgui/mactext.cpp b/graphics/macgui/mactext.cpp
index 34ef8ab9cd1..77b26d3015b 100644
--- a/graphics/macgui/mactext.cpp
+++ b/graphics/macgui/mactext.cpp
@@ -659,6 +659,7 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 
 	MacFontRun current_format = _defaultFormatting;
 	int indentSize = 0;
+	int firstLineIndent = 0;
 
 	while (*l) {
 		paragraph.clear();
@@ -685,6 +686,8 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 		// Now process whole paragraph
 		const Common::U32String::value_type *s = paragraph.c_str();
 
+		firstLineIndent = 0;
+
 		while (*s) {
 			tmp.clear();
 
@@ -767,6 +770,20 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 					current_format.fgcolor = _defaultFormatting.fgcolor;
 
 					D(9, "** splitString]: %08x", current_format.fgcolor);
+				} else if (*s == '*') { // \016*XXsssssss  -- negative indent, XX size, sssss is the string
+					s++;
+
+					uint16 len;
+
+					s = readHex(&len, s, 2);
+
+					Common::U32String bullet = Common::U32String(s, len);
+
+					s += len;
+
+					firstLineIndent = -current_format.getFont()->getStringWidth(bullet) * 2;
+
+					D(9, "** splitString*: %02x '%s' (%d)", len, bullet.encode().c_str(), firstLineIndent);
 				} else {
 					uint16 fontId, textSlant, fontSize, palinfo1, palinfo2, palinfo3;
 
@@ -801,9 +818,10 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 			}
 
 			_textLines[curLine].indent = indentSize;
+			_textLines[curLine].firstLineIndent = firstLineIndent;
 
 			// calc word_width, the trick we define here is we don`t count the space
-			int word_width = _textLines[curLine].indent + getStringWidth(current_format, tmp);
+			int word_width = _textLines[curLine].indent + getStringWidth(current_format, tmp) + firstLineIndent;
 			// add all spaces left
 			while (*s == ' ') {
 				tmp += *s;
@@ -835,6 +853,8 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 				++curLine;
 				_textLines.insert_at(curLine, MacTextLine());
 				_textLines[curLine].indent = indentSize;
+				_textLines[curLine].firstLineIndent = 0;
+				firstLineIndent = 0;
 			}
 
 			// deal with the super long word situation
@@ -875,6 +895,8 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 							_textLines.insert_at(curLine, MacTextLine());
 							_textLines[curLine].chunks.push_back(word[i]);
 							_textLines[curLine].lastChunk().text = Common::U32String();
+							_textLines[curLine].firstLineIndent = 0;
+							firstLineIndent = 0;
 							tmp_width = _textLines[curLine].indent;
 							cur_width = _textLines[curLine].indent;
 						}
@@ -988,7 +1010,7 @@ void MacText::render(int from, int to, int shadow) {
 	}
 
 	for (int i = myFrom; i != myTo; i += delta) {
-		int xOffset = getAlignOffset(i) + _textLines[i].indent;
+		int xOffset = getAlignOffset(i) + _textLines[i].indent + _textLines[i].firstLineIndent;
 		xOffset++;
 
 		int start = 0, end = _textLines[i].chunks.size();
@@ -1068,7 +1090,7 @@ int MacText::getLineWidth(int line, bool enforce, int col) {
 	if (_textLines[line].width != -1 && !enforce && col == -1)
 		return _textLines[line].width;
 
-	int width = _textLines[line].indent;
+	int width = _textLines[line].indent + _textLines[line].firstLineIndent;
 	int height = 0;
 	int charwidth = 0;
 
diff --git a/graphics/macgui/mactext.h b/graphics/macgui/mactext.h
index b0b8e0fc1c2..424ecc65725 100644
--- a/graphics/macgui/mactext.h
+++ b/graphics/macgui/mactext.h
@@ -114,7 +114,8 @@ struct MacTextLine {
 	int y = 0;
 	int charwidth = -1;
 	bool paragraphEnd = false;
-	int indent = 0;
+	int indent = 0; // in units
+	int firstLineIndent = 0; // in pixels
 
 	Common::Array<MacFontRun> chunks;
 
diff --git a/graphics/macgui/macwindowmanager.cpp b/graphics/macgui/macwindowmanager.cpp
index 9589bc036b2..c912c32bdd5 100644
--- a/graphics/macgui/macwindowmanager.cpp
+++ b/graphics/macgui/macwindowmanager.cpp
@@ -590,6 +590,8 @@ Common::U32String stripFormat(const Common::U32String &str) {
 					s += 13;
 				else if (*s == ']') // default color
 					s += 1;
+				else if (*s == '*') // default color
+					s += 3;
 				else
 					s += 22;
 			} else {


Commit: c668169636de71de1548cdf703c2928f0f59f3b6
    https://github.com/scummvm/scummvm/commit/c668169636de71de1548cdf703c2928f0f59f3b6
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: Encode images into MacText from Markdown

Changed paths:
    graphics/macgui/mactext-md.cpp
    graphics/macgui/mactext.cpp
    graphics/macgui/mactext.h
    graphics/macgui/macwindowmanager.cpp
    graphics/macgui/macwindowmanager.h


diff --git a/graphics/macgui/mactext-md.cpp b/graphics/macgui/mactext-md.cpp
index ff84493b33e..51f7f84bffa 100644
--- a/graphics/macgui/mactext-md.cpp
+++ b/graphics/macgui/mactext-md.cpp
@@ -193,7 +193,22 @@ int render_image(Common::SDDataBuffer *ob, const Common::SDDataBuffer *link, con
 	if (!link)
 		return 0;
 
-	warning("STUB: render_image(%s, %s, %s)", PR(link), title ? PR(title) : 0, alt ? PR(alt) : 0);
+	Common::String res = Common::String::format("\001" "\016i%02x" "%02x%s",
+			50, (uint)link->size, Common::String((const char *)link->data, link->size).c_str());
+
+	if (alt)
+		res += Common::String::format("%02x%s", (uint)alt->size, Common::String((const char *)alt->data, alt->size).c_str());
+	else
+		res += "00";
+
+	if (title)
+		res += Common::String::format("%02x%s\n", (uint)title->size, Common::String((const char *)title->data, title->size).c_str());
+	else
+		res += "00\n";
+
+	sd_bufput(ob, res.c_str(), res.size());
+
+	debug(1, "render_image(%s, %s, %s)", PR(link), title ? PR(title) : 0, alt ? PR(alt) : 0);
 	return 1;
 }
 
diff --git a/graphics/macgui/mactext.cpp b/graphics/macgui/mactext.cpp
index 77b26d3015b..1b71cf75c65 100644
--- a/graphics/macgui/mactext.cpp
+++ b/graphics/macgui/mactext.cpp
@@ -548,22 +548,6 @@ void MacText::setDefaultFormatting(uint16 fontId, byte textSlant, uint16 fontSiz
 	_defaultFormatting.font = _wm->_fontMan->getFont(macFont);
 }
 
-static const Common::U32String::value_type *readHex(uint16 *res, const Common::U32String::value_type *s, int len) {
-	*res = 0;
-
-	for (int i = 0; i < len; i++) {
-		char b = (char)*s++;
-
-		*res <<= 4;
-		if (tolower(b) >= 'a')
-			*res |= tolower(b) - 'a' + 10;
-		else
-			*res |= tolower(b) - '0';
-	}
-
-	return s;
-}
-
 // Adds the given string to the end of the last line/chunk
 // while observing the _maxWidth and keeping this chunk's
 // formatting
@@ -784,6 +768,27 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 					firstLineIndent = -current_format.getFont()->getStringWidth(bullet) * 2;
 
 					D(9, "** splitString*: %02x '%s' (%d)", len, bullet.encode().c_str(), firstLineIndent);
+				} else if (*s == 'i') { // \016iXXNNnnnnAAaaaaTTttt -- image, XX% width,
+										//          NN, nnnn -- filename len and text
+										//          AA, aaaa -- alt len and text
+										//          TT, tttt -- text (tooltip) len and text
+					s++;
+
+					uint16 percent, len;
+
+					s = readHex(&percent, s, 2);
+					s = readHex(&len, s, 2);
+					_textLines[curLine].picfname = Common::U32String(s, len);
+					s += len;
+
+					s = readHex(&len, s, 2);
+					_textLines[curLine].picalt = Common::U32String(s, len);
+					s += len;
+
+					s = readHex(&len, s, 2);
+					_textLines[curLine].pictitle = Common::U32String(s, len);
+					s += len;
+
 				} else {
 					uint16 fontId, textSlant, fontSize, palinfo1, palinfo2, palinfo3;
 
diff --git a/graphics/macgui/mactext.h b/graphics/macgui/mactext.h
index 424ecc65725..a7cd69405df 100644
--- a/graphics/macgui/mactext.h
+++ b/graphics/macgui/mactext.h
@@ -116,6 +116,7 @@ struct MacTextLine {
 	bool paragraphEnd = false;
 	int indent = 0; // in units
 	int firstLineIndent = 0; // in pixels
+	Common::U32String picfname, picalt, pictitle;
 
 	Common::Array<MacFontRun> chunks;
 
diff --git a/graphics/macgui/macwindowmanager.cpp b/graphics/macgui/macwindowmanager.cpp
index c912c32bdd5..0ef24eadec0 100644
--- a/graphics/macgui/macwindowmanager.cpp
+++ b/graphics/macgui/macwindowmanager.cpp
@@ -590,9 +590,28 @@ Common::U32String stripFormat(const Common::U32String &str) {
 					s += 13;
 				else if (*s == ']') // default color
 					s += 1;
-				else if (*s == '*') // default color
-					s += 3;
-				else
+				else if (*s == '*') { // bullet
+					s++;
+					uint16 len;
+					s = readHex(&len, s, 2);
+					s += len;
+				} else if (*s == 'i') { // image
+					s += 3; // skip percent
+					uint16 len;
+					s = readHex(&len, s, 2); // fname
+					s += len;
+
+					s = readHex(&len, s, 2);
+					Common::String alt = Common::U32String(s, len);
+					s += len;
+
+					res += '[';
+					res += alt;
+					res += ']';
+
+					s = readHex(&len, s, 2); // title
+					s += len;
+				} else
 					s += 22;
 			} else {
 				tmp += *s++;
@@ -1448,4 +1467,21 @@ void MacWindowManager::printWMMode(int debuglevel) {
 	debug(debuglevel, "WM mode: %s", out.c_str());
 }
 
+const Common::U32String::value_type *readHex(uint16 *res, const Common::U32String::value_type *s, int len) {
+	*res = 0;
+
+	for (int i = 0; i < len; i++) {
+		char b = (char)*s++;
+
+		*res <<= 4;
+		if (tolower(b) >= 'a')
+			*res |= tolower(b) - 'a' + 10;
+		else
+			*res |= tolower(b) - '0';
+	}
+
+	return s;
+}
+
+
 } // End of namespace Graphics
diff --git a/graphics/macgui/macwindowmanager.h b/graphics/macgui/macwindowmanager.h
index 8629547c953..5df15274d7a 100644
--- a/graphics/macgui/macwindowmanager.h
+++ b/graphics/macgui/macwindowmanager.h
@@ -477,6 +477,8 @@ private:
 	Common::U32String _clipboard;
 };
 
+const Common::U32String::value_type *readHex(uint16 *res, const Common::U32String::value_type *s, int len);
+
 } // End of namespace Graphics
 
 #endif


Commit: 1a6ad65f171d951b1bb0f7cfd260db19c26a3434
    https://github.com/scummvm/scummvm/commit/1a6ad65f171d951b1bb0f7cfd260db19c26a3434
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: Added crash on raw html tags processing in Markdown

Changed paths:
    graphics/macgui/mactext-md.cpp


diff --git a/graphics/macgui/mactext-md.cpp b/graphics/macgui/mactext-md.cpp
index 51f7f84bffa..e64ae669c5b 100644
--- a/graphics/macgui/mactext-md.cpp
+++ b/graphics/macgui/mactext-md.cpp
@@ -240,7 +240,7 @@ int render_raw_html_tag(Common::SDDataBuffer *ob, const Common::SDDataBuffer *ta
 	if (!tag)
 		return 0;
 
-	warning("STUB: render_raw_html_tag(%s)", tag->data);
+	warning("STUB: render_raw_html_tag(%s)", PR(tag));
 	return 1;
 }
 


Commit: 461d3218af6c0a96c66abfd1718f6b16ba3c377f
    https://github.com/scummvm/scummvm/commit/461d3218af6c0a96c66abfd1718f6b16ba3c377f
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Fix assert when dynamic tooltip is empty

Changed paths:
    gui/gui-manager.cpp


diff --git a/gui/gui-manager.cpp b/gui/gui-manager.cpp
index fef512520e1..d901e480b5b 100644
--- a/gui/gui-manager.cpp
+++ b/gui/gui-manager.cpp
@@ -555,10 +555,13 @@ void GuiManager::runLoop() {
 				if (wdg->getType() != kEditTextWidget || activeDialog->getFocusWidget() != wdg) {
 					if (wdg->getFlags() & WIDGET_DYN_TOOLTIP)
 						wdg->handleTooltipUpdate(_lastMousePosition.x + activeDialog->_x - wdg->getAbsX(), _lastMousePosition.y + activeDialog->_y - wdg->getAbsY());
-					Tooltip *tooltip = new Tooltip();
-					tooltip->setup(activeDialog, wdg, _lastMousePosition.x, _lastMousePosition.y);
-					tooltip->runModal();
-					delete tooltip;
+
+					if (wdg->hasTooltip()) {
+						Tooltip *tooltip = new Tooltip();
+						tooltip->setup(activeDialog, wdg, _lastMousePosition.x, _lastMousePosition.y);
+						tooltip->runModal();
+						delete tooltip;
+					}
 				}
 			}
 		}


Commit: ae41e3aa2498e3161c10351aa27fb0b9b4f307b1
    https://github.com/scummvm/scummvm/commit/ae41e3aa2498e3161c10351aa27fb0b9b4f307b1
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Added SAF instuctions to help dialog. This contains pictures

Changed paths:
    gui/helpdialog.cpp


diff --git a/gui/helpdialog.cpp b/gui/helpdialog.cpp
index 797a8882bd1..3ebf9cdf2c5 100644
--- a/gui/helpdialog.cpp
+++ b/gui/helpdialog.cpp
@@ -109,6 +109,64 @@ HelpDialog::HelpDialog()
 	new RichTextWidget(tab, 10, 10, _w - 10, tabHeight - buttonHeight - 10, helpText2);
 
 	tab->addTab(_("Adding Games"), "GlobalOptions_Graphics", false);
+
+	Common::U32String helpText3 = _(
+"#### Adding SAF paths to ScummVM directory list\n"
+"\n"
+"Starting with version 2.7.0 of ScummVM for Android, significant changes were made to the file access system to allow support for modern versions of the Android Operating System.\n"
+"If you find that your existing added games or custom paths no longer work, please edit those paths and this time use the SAF system to browse to the desired locations.\n"
+"To do that:\n"
+"\n"
+"  1. For each game whose data is not found, go to the \"Paths\" tab in the \"Game Options\" and change the \"Game path\"\n"
+"\n"
+"  2. Inside the ScummVM file browser, use \"Go Up\" until you reach the \"root\" folder where you will see the \"<Add a new folder>\" option.\n"
+"\n"
+"![File browser](browser-root.png \"Android browser\")\n"
+"\n"
+"    File Browser root with <Add a new folder> item\n"
+"\n"
+"   3. Choose that, then browse and select the \"parent\" folder for your games subfolders, e.g. \"SD Card > ScummVMgames\". Click on \"Use this folder\".\n"
+"\n"
+"![OS file browser root](fs-root.png \"OS file browser root\")\n"
+"\n"
+"    OS file browser root\n"
+"\n"
+"![OS selectable folder](fs-folder.png \"OS selectable folder\")\n"
+"\n"
+"    OS file browser selectable folder with \"Use this folder\" button\n"
+"\n"
+"![OS access permission dialog](fs-permission.png \"OS access permission\")\n"
+"\n"
+"    OS file browser ask to grant ScummVM directory access permission\n"
+"\n"
+"4. Then, a new folder \"ScummVMgames\" will appear on the \"root\" folder of the ScummVM browser.\n"
+"\n"
+"![SAF folder added](browser-folder-in-list.png \"SAF folder added\")\n"
+"\n"
+"    File browser with added SAF folder in root\n"
+"\n"
+"5. Browse through this folder to your game data.\n"
+"\n"
+"Steps 2 and 3 need to be done only once for all of your games.\n"
+"\n"
+"\n"
+"#### Removing SAF path authorizations\n"
+"\n"
+"In case you would like to revoke any of the granted SAF authorizations, there is an option for this in the \"Global Options > Backend\" tab as shown on the screenshot below:\n"
+"\n"
+"![\"Remove folder authorizations...\" button](gui-remove-permissions.png \"'Remove folder authorizations...' button\")\n"
+"\n"
+"    GUI tab with \"Remove folder authorizations...\" button\n"
+"\n"
+"![List of authorizations to revoked](gui-remove-list.png \"List of authorizations to revoke\")\n"
+"\n"
+"    GUI dialog with list of authorizations to revoke\n"
+"\n"
+"In case you revoke authorization to a path, still used for specific games/titles, please follow the procedure of fixing them outlined in the previous subheading.\n"
+	);
+
+	new RichTextWidget(tab, 10, 10, _w - 10, tabHeight - buttonHeight - 10, helpText3);
+
 	tab->addTab(_("Paths"), "GlobalOptions_Graphics", false);
 
 	new ButtonWidget(this, _w - buttonWidth - 10, _h - buttonHeight - 10, buttonWidth, buttonHeight, Common::U32String("Close"), Common::U32String(), kCloseCmd);


Commit: f53d39741df8e22ec9c6ee75e76170cb50f41d62
    https://github.com/scummvm/scummvm/commit/f53d39741df8e22ec9c6ee75e76170cb50f41d62
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: Added tag for switching font to MacText

Changed paths:
    graphics/macgui/mactext.cpp
    graphics/macgui/macwindowmanager.cpp


diff --git a/graphics/macgui/mactext.cpp b/graphics/macgui/mactext.cpp
index 1b71cf75c65..d1a35012fc0 100644
--- a/graphics/macgui/mactext.cpp
+++ b/graphics/macgui/mactext.cpp
@@ -789,6 +789,19 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 					_textLines[curLine].pictitle = Common::U32String(s, len);
 					s += len;
 
+					D(9, "** splitString[i]: %d%% fname: '%s'  alt: '%s'  title: '%s'", percent,
+						_textLines[curLine].picfname.c_str(), _textLines[curLine].picalt.c_str(),
+						_textLines[curLine].pictitle.c_str());
+				} else if (*s == 't') { // \016tXXXX -- switch to the requested font id
+					s++;
+
+					uint16 fontId;
+
+					s = readHex(&fontId, s, 4);
+
+					current_format.fontId = fontId;
+
+					D(9, "** splitString[t]: fontId: %d", fontId);
 				} else {
 					uint16 fontId, textSlant, fontSize, palinfo1, palinfo2, palinfo3;
 
diff --git a/graphics/macgui/macwindowmanager.cpp b/graphics/macgui/macwindowmanager.cpp
index 0ef24eadec0..a438521af87 100644
--- a/graphics/macgui/macwindowmanager.cpp
+++ b/graphics/macgui/macwindowmanager.cpp
@@ -611,6 +611,8 @@ Common::U32String stripFormat(const Common::U32String &str) {
 
 					s = readHex(&len, s, 2); // title
 					s += len;
+				} else if (*s == 't') { // font
+					s += 5;
 				} else
 					s += 22;
 			} else {


Commit: c9a123b45aa426bae7744cd0835f2bd0d7fbe2ac
    https://github.com/scummvm/scummvm/commit/c9a123b45aa426bae7744cd0835f2bd0d7fbe2ac
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: Implemented rendering for blockcode and raw_html_tag in Markdown

Changed paths:
    graphics/macgui/mactext-md.cpp


diff --git a/graphics/macgui/mactext-md.cpp b/graphics/macgui/mactext-md.cpp
index e64ae669c5b..f0fd4cf410a 100644
--- a/graphics/macgui/mactext-md.cpp
+++ b/graphics/macgui/mactext-md.cpp
@@ -36,7 +36,11 @@ void render_blockcode(Common::SDDataBuffer *ob, const Common::SDDataBuffer *text
 	if (!text)
 		return;
 
-	warning("STUB: render_blockcode(%s)", PR(text));
+	Common::String res = Common::String::format("\016t%04x" "%s" "\001\016]", kMacFontMonaco, Common::String((const char *)text->data , text->size).c_str());
+
+	sd_bufput(ob, res.c_str(), res.size());
+
+	//warning("STUB: render_blockcode(%s, %s)", PR(text), PR(lang));
 }
 
 void render_blockquote(Common::SDDataBuffer *ob, const Common::SDDataBuffer *text, void *opaque) {
@@ -240,7 +244,9 @@ int render_raw_html_tag(Common::SDDataBuffer *ob, const Common::SDDataBuffer *ta
 	if (!tag)
 		return 0;
 
-	warning("STUB: render_raw_html_tag(%s)", PR(tag));
+	sd_bufput(ob, tag->data, tag->size);
+
+	debug(1, "render_raw_html_tag(%s)", PR(tag));
 	return 1;
 }
 


Commit: 9e488b4ee799bcee30db8413ae06e3445ca63ba2
    https://github.com/scummvm/scummvm/commit/9e488b4ee799bcee30db8413ae06e3445ca63ba2
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Added paragraph space for SAF text in HelpDialog

Changed paths:
    gui/helpdialog.cpp


diff --git a/gui/helpdialog.cpp b/gui/helpdialog.cpp
index 3ebf9cdf2c5..11dea9b781c 100644
--- a/gui/helpdialog.cpp
+++ b/gui/helpdialog.cpp
@@ -114,7 +114,9 @@ HelpDialog::HelpDialog()
 "#### Adding SAF paths to ScummVM directory list\n"
 "\n"
 "Starting with version 2.7.0 of ScummVM for Android, significant changes were made to the file access system to allow support for modern versions of the Android Operating System.\n"
+"\n"
 "If you find that your existing added games or custom paths no longer work, please edit those paths and this time use the SAF system to browse to the desired locations.\n"
+"\n"
 "To do that:\n"
 "\n"
 "  1. For each game whose data is not found, go to the \"Paths\" tab in the \"Game Options\" and change the \"Game path\"\n"


Commit: efdc9c319341faba2b8a9b9e60a3c09d1294a266
    https://github.com/scummvm/scummvm/commit/efdc9c319341faba2b8a9b9e60a3c09d1294a266
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: More rebust debug output in Markdown

Changed paths:
    graphics/macgui/mactext-md.cpp


diff --git a/graphics/macgui/mactext-md.cpp b/graphics/macgui/mactext-md.cpp
index f0fd4cf410a..56bd72d512a 100644
--- a/graphics/macgui/mactext-md.cpp
+++ b/graphics/macgui/mactext-md.cpp
@@ -25,7 +25,7 @@
 
 namespace Graphics {
 
-#define PR(x) (x->data ? Common::String((const char *)(x)->data , (x)->size).c_str() : "(null)")
+#define PR(x) ((x && x->data) ? Common::String((const char *)(x)->data , (x)->size).c_str() : "(null)")
 
 struct MDState {
 	Common::List<int> listNum;
@@ -40,7 +40,7 @@ void render_blockcode(Common::SDDataBuffer *ob, const Common::SDDataBuffer *text
 
 	sd_bufput(ob, res.c_str(), res.size());
 
-	//warning("STUB: render_blockcode(%s, %s)", PR(text), PR(lang));
+	debug(1, "render_blockcode(%s, %s)", PR(text), PR(lang));
 }
 
 void render_blockquote(Common::SDDataBuffer *ob, const Common::SDDataBuffer *text, void *opaque) {
@@ -136,7 +136,7 @@ void render_table(Common::SDDataBuffer *ob, const Common::SDDataBuffer *header,
 	if (!body)
 		return;
 
-	warning("STUB: render_table(%s, %s)", header ? PR(header) : 0, PR(body));
+	warning("STUB: render_table(%s, %s)", PR(header), PR(body));
 }
 
 void render_table_row(Common::SDDataBuffer *ob, const Common::SDDataBuffer *text, void *opaque) {
@@ -212,7 +212,7 @@ int render_image(Common::SDDataBuffer *ob, const Common::SDDataBuffer *link, con
 
 	sd_bufput(ob, res.c_str(), res.size());
 
-	debug(1, "render_image(%s, %s, %s)", PR(link), title ? PR(title) : 0, alt ? PR(alt) : 0);
+	debug(1, "render_image(%s, %s, %s)", PR(link), PR(title), PR(alt));
 	return 1;
 }
 
@@ -236,7 +236,7 @@ int render_link(Common::SDDataBuffer *ob, const Common::SDDataBuffer *link, cons
 
 	sd_bufput(ob, res.c_str(), res.size());
 
-	debug(1, "render_link(%s, %s, %s)", PR(link), title ? PR(title) : 0, content ? PR(content) : 0);
+	debug(1, "render_link(%s, %s, %s)", PR(link), PR(title), PR(content));
 	return 1;
 }
 


Commit: 49ef52079cb6071d6bc84bee6e7ab152e837b6ac
    https://github.com/scummvm/scummvm/commit/49ef52079cb6071d6bc84bee6e7ab152e837b6ac
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: Improved blockcode rendering in Markdown

Changed paths:
    graphics/macgui/mactext-md.cpp
    graphics/macgui/mactext.cpp


diff --git a/graphics/macgui/mactext-md.cpp b/graphics/macgui/mactext-md.cpp
index 56bd72d512a..92f9194406b 100644
--- a/graphics/macgui/mactext-md.cpp
+++ b/graphics/macgui/mactext-md.cpp
@@ -36,7 +36,8 @@ void render_blockcode(Common::SDDataBuffer *ob, const Common::SDDataBuffer *text
 	if (!text)
 		return;
 
-	Common::String res = Common::String::format("\016t%04x" "%s" "\001\016]", kMacFontMonaco, Common::String((const char *)text->data , text->size).c_str());
+	Common::String res = Common::String::format("\n\016+0001" "\001\016t%04x" "%s" "\001\016tffff" "\n\016-0001",
+			kMacFontMonaco, Common::String((const char *)text->data , text->size).c_str());
 
 	sd_bufput(ob, res.c_str(), res.size());
 
diff --git a/graphics/macgui/mactext.cpp b/graphics/macgui/mactext.cpp
index d1a35012fc0..402f15f2f93 100644
--- a/graphics/macgui/mactext.cpp
+++ b/graphics/macgui/mactext.cpp
@@ -799,7 +799,7 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 
 					s = readHex(&fontId, s, 4);
 
-					current_format.fontId = fontId;
+					current_format.fontId = fontId == 0xffff ? _defaultFormatting.fontId : fontId;
 
 					D(9, "** splitString[t]: fontId: %d", fontId);
 				} else {


Commit: b614756ae63b097d8f4f8397b2b8cde1615c215a
    https://github.com/scummvm/scummvm/commit/b614756ae63b097d8f4f8397b2b8cde1615c215a
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Fix SAF page formatting in HelpDialog

Changed paths:
    gui/helpdialog.cpp


diff --git a/gui/helpdialog.cpp b/gui/helpdialog.cpp
index 11dea9b781c..d1c368b9b93 100644
--- a/gui/helpdialog.cpp
+++ b/gui/helpdialog.cpp
@@ -123,31 +123,31 @@ HelpDialog::HelpDialog()
 "\n"
 "  2. Inside the ScummVM file browser, use \"Go Up\" until you reach the \"root\" folder where you will see the \"<Add a new folder>\" option.\n"
 "\n"
-"![File browser](browser-root.png \"Android browser\")\n"
+"  ![File browser](browser-root.png \"Android browser\")\n"
 "\n"
 "    File Browser root with <Add a new folder> item\n"
 "\n"
-"   3. Choose that, then browse and select the \"parent\" folder for your games subfolders, e.g. \"SD Card > ScummVMgames\". Click on \"Use this folder\".\n"
+"  3. Choose that, then browse and select the \"parent\" folder for your games subfolders, e.g. \"SD Card > ScummVMgames\". Click on \"Use this folder\".\n"
 "\n"
-"![OS file browser root](fs-root.png \"OS file browser root\")\n"
+"  ![OS file browser root](fs-root.png \"OS file browser root\")\n"
 "\n"
 "    OS file browser root\n"
 "\n"
-"![OS selectable folder](fs-folder.png \"OS selectable folder\")\n"
+"  ![OS selectable folder](fs-folder.png \"OS selectable folder\")\n"
 "\n"
 "    OS file browser selectable folder with \"Use this folder\" button\n"
 "\n"
-"![OS access permission dialog](fs-permission.png \"OS access permission\")\n"
+"  ![OS access permission dialog](fs-permission.png \"OS access permission\")\n"
 "\n"
 "    OS file browser ask to grant ScummVM directory access permission\n"
 "\n"
-"4. Then, a new folder \"ScummVMgames\" will appear on the \"root\" folder of the ScummVM browser.\n"
+"  4. Then, a new folder \"ScummVMgames\" will appear on the \"root\" folder of the ScummVM browser.\n"
 "\n"
-"![SAF folder added](browser-folder-in-list.png \"SAF folder added\")\n"
+"  ![SAF folder added](browser-folder-in-list.png \"SAF folder added\")\n"
 "\n"
 "    File browser with added SAF folder in root\n"
 "\n"
-"5. Browse through this folder to your game data.\n"
+"  5. Browse through this folder to your game data.\n"
 "\n"
 "Steps 2 and 3 need to be done only once for all of your games.\n"
 "\n"


Commit: 01263461a53726d33d9f48a5d7c6133f5bc0bcef
    https://github.com/scummvm/scummvm/commit/01263461a53726d33d9f48a5d7c6133f5bc0bcef
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: Fix bullet lists indentation in the Markdown

Changed paths:
    graphics/macgui/mactext-md.cpp
    graphics/macgui/mactext.cpp


diff --git a/graphics/macgui/mactext-md.cpp b/graphics/macgui/mactext-md.cpp
index 92f9194406b..a2e7107617e 100644
--- a/graphics/macgui/mactext-md.cpp
+++ b/graphics/macgui/mactext-md.cpp
@@ -98,10 +98,6 @@ void render_listitem(Common::SDDataBuffer *ob, const Common::SDDataBuffer *text,
 	MDState *mdstate = (MDState *)opaque;
 
 	int listNum = mdstate->listNum.back();
-	int depth = mdstate->listNum.size();
-
-	for (int i = 0; i < depth; i++)
-		sd_bufput(ob, "  ", 2);
 
 	Common::String prefix;
 	if (flags & MKD_LIST_ORDERED) {
@@ -112,7 +108,7 @@ void render_listitem(Common::SDDataBuffer *ob, const Common::SDDataBuffer *text,
 		prefix = "* ";
 	}
 
-	Common::String res = Common::String::format("\016*%02x%s", prefix.size(), prefix.c_str());
+	Common::String res = Common::String::format("\001\016*%02x%s", prefix.size(), prefix.c_str());
 
 	sd_bufput(ob, res.c_str(), res.size());
 
diff --git a/graphics/macgui/mactext.cpp b/graphics/macgui/mactext.cpp
index 402f15f2f93..a9cf635a134 100644
--- a/graphics/macgui/mactext.cpp
+++ b/graphics/macgui/mactext.cpp
@@ -765,7 +765,7 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 
 					s += len;
 
-					firstLineIndent = -current_format.getFont()->getStringWidth(bullet) * 2;
+					firstLineIndent = -current_format.getFont()->getStringWidth(bullet);
 
 					D(9, "** splitString*: %02x '%s' (%d)", len, bullet.encode().c_str(), firstLineIndent);
 				} else if (*s == 'i') { // \016iXXNNnnnnAAaaaaTTttt -- image, XX% width,


Commit: 7ddffd0a441b597c38b2726f67bcad6b4d686d4b
    https://github.com/scummvm/scummvm/commit/7ddffd0a441b597c38b2726f67bcad6b4d686d4b
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: Fix computing of cursor position for indented lines in MacText

Changed paths:
    graphics/macgui/mactext.cpp


diff --git a/graphics/macgui/mactext.cpp b/graphics/macgui/mactext.cpp
index a9cf635a134..a55951ee830 100644
--- a/graphics/macgui/mactext.cpp
+++ b/graphics/macgui/mactext.cpp
@@ -2199,7 +2199,7 @@ void MacText::getRowCol(int x, int y, int *sx, int *sy, int *row, int *col, int
 	nsy = _textLines[nrow].y;
 
 	if (_textLines[nrow].chunks.size() > 0) {
-		int alignOffset = getAlignOffset(nrow);
+		int alignOffset = getAlignOffset(nrow) + _textLines[nrow].indent + _textLines[nrow].firstLineIndent;;
 
 		int width = 0, pwidth = 0;
 		int mcol = 0, pmcol = 0;


Commit: 34acf247bec3f449b133d9017b07ac9ed1253a2b
    https://github.com/scummvm/scummvm/commit/34acf247bec3f449b133d9017b07ac9ed1253a2b
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: Used switch() statement in MacText formatting parsing

Changed paths:
    graphics/macgui/mactext.cpp


diff --git a/graphics/macgui/mactext.cpp b/graphics/macgui/mactext.cpp
index a55951ee830..5a5aa54df80 100644
--- a/graphics/macgui/mactext.cpp
+++ b/graphics/macgui/mactext.cpp
@@ -689,7 +689,8 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 				s++;
 
 				// First two digits is slant, third digit is Header number
-				if (*s == '+') { // \016+XXYZ  -- opening textSlant, H<Y>, indent<+Z>
+				switch (*s) {
+				case '+': { // \016+XXYZ  -- opening textSlant, H<Y>, indent<+Z>
 					uint16 textSlant, headSize, indent;
 					s++;
 
@@ -711,7 +712,10 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 					D(9, "** splitString+: fontId: %d, textSlant: %d, fontSize: %d, indent: %d",
 							current_format.fontId, current_format.textSlant, current_format.fontSize,
 							indent);
-				} else if (*s == '-') { // \016-XXYZ  -- closing textSlant, H<Y>, indent<+Z>
+
+					break;
+					}
+				case '-': { // \016-XXYZ  -- closing textSlant, H<Y>, indent<+Z>
 					uint16 textSlant, headSize, indent;
 					s++;
 
@@ -731,7 +735,10 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 					D(9, "** splitString-: fontId: %d, textSlant: %d, fontSize: %d, indent: %d",
 							current_format.fontId, current_format.textSlant, current_format.fontSize,
 							indent);
-				} else if (*s == '[') { // \016[RRGGBB  -- setting color
+					break;
+					}
+
+				case '[': { // \016[RRGGBB  -- setting color
 					uint16 palinfo1, palinfo2, palinfo3;
 					s++;
 
@@ -745,7 +752,10 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 					current_format.fgcolor  = _wm->findBestColor(palinfo1 & 0xff, palinfo2 & 0xff, palinfo3 & 0xff);
 
 					D(9, "** splitString[: %08x", fgcolor);
-				} else if (*s == ']') { // \016]  -- setting default color
+					break;
+					}
+
+				case ']': { // \016]  -- setting default color
 					s++;
 
 					current_format.palinfo1 = _defaultFormatting.palinfo1;
@@ -754,7 +764,10 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 					current_format.fgcolor = _defaultFormatting.fgcolor;
 
 					D(9, "** splitString]: %08x", current_format.fgcolor);
-				} else if (*s == '*') { // \016*XXsssssss  -- negative indent, XX size, sssss is the string
+					break;
+					}
+
+				case '*': { // \016*XXsssssss  -- negative indent, XX size, sssss is the string
 					s++;
 
 					uint16 len;
@@ -768,7 +781,10 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 					firstLineIndent = -current_format.getFont()->getStringWidth(bullet);
 
 					D(9, "** splitString*: %02x '%s' (%d)", len, bullet.encode().c_str(), firstLineIndent);
-				} else if (*s == 'i') { // \016iXXNNnnnnAAaaaaTTttt -- image, XX% width,
+					break;
+					}
+
+				case 'i': { // \016iXXNNnnnnAAaaaaTTttt -- image, XX% width,
 										//          NN, nnnn -- filename len and text
 										//          AA, aaaa -- alt len and text
 										//          TT, tttt -- text (tooltip) len and text
@@ -792,7 +808,10 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 					D(9, "** splitString[i]: %d%% fname: '%s'  alt: '%s'  title: '%s'", percent,
 						_textLines[curLine].picfname.c_str(), _textLines[curLine].picalt.c_str(),
 						_textLines[curLine].pictitle.c_str());
-				} else if (*s == 't') { // \016tXXXX -- switch to the requested font id
+					break;
+					}
+
+				case 't': { // \016tXXXX -- switch to the requested font id
 					s++;
 
 					uint16 fontId;
@@ -802,7 +821,10 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 					current_format.fontId = fontId == 0xffff ? _defaultFormatting.fontId : fontId;
 
 					D(9, "** splitString[t]: fontId: %d", fontId);
-				} else {
+					break;
+					}
+
+				default: {
 					uint16 fontId, textSlant, fontSize, palinfo1, palinfo2, palinfo3;
 
 					s = readHex(&fontId, s, 4);
@@ -820,6 +842,7 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 					// So far, we enforce single font here, though in the future, font size could be altered
 					if (!_macFontMode)
 						current_format.font = _defaultFormatting.font;
+					}
 				}
 			}
 


Commit: 91a829db69365b765b3ba8587f26aa0d2f0e1fb0
    https://github.com/scummvm/scummvm/commit/91a829db69365b765b3ba8587f26aa0d2f0e1fb0
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: Implemented image rendering in Markdown

Changed paths:
    graphics/macgui/mactext.cpp
    graphics/macgui/mactext.h


diff --git a/graphics/macgui/mactext.cpp b/graphics/macgui/mactext.cpp
index 5a5aa54df80..d0a32a628f6 100644
--- a/graphics/macgui/mactext.cpp
+++ b/graphics/macgui/mactext.cpp
@@ -19,9 +19,9 @@
  *
  */
 
-#include "common/unicode-bidi.h"
+#include "common/file.h"
 #include "common/timer.h"
-#include "common/system.h"
+#include "common/unicode-bidi.h"
 
 #include "graphics/font.h"
 #include "graphics/macgui/mactext.h"
@@ -31,6 +31,8 @@
 #include "graphics/macgui/macwidget.h"
 #include "graphics/macgui/macwindow.h"
 
+#include "image/png.h"
+
 namespace Graphics {
 
 enum {
@@ -267,6 +269,9 @@ MacText::~MacText() {
 	delete _shadowSurface;
 	delete _cursorSurface;
 	delete _cursorSurface2;
+
+	for (auto &i : _imageCache)
+		delete i._value;
 }
 
 // this func returns the fg color of the first character we met in text
@@ -790,11 +795,11 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 										//          TT, tttt -- text (tooltip) len and text
 					s++;
 
-					uint16 percent, len;
+					uint16 len;
 
-					s = readHex(&percent, s, 2);
+					s = readHex(&_textLines[curLine].picpercent, s, 2);
 					s = readHex(&len, s, 2);
-					_textLines[curLine].picfname = Common::U32String(s, len);
+					_textLines[curLine].picfname = Common::U32String(s, len).encode();
 					s += len;
 
 					s = readHex(&len, s, 2);
@@ -805,7 +810,8 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 					_textLines[curLine].pictitle = Common::U32String(s, len);
 					s += len;
 
-					D(9, "** splitString[i]: %d%% fname: '%s'  alt: '%s'  title: '%s'", percent,
+					D(9, "** splitString[i]: %d%% fname: '%s'  alt: '%s'  title: '%s'",
+						_textLines[curLine].picpercent,
 						_textLines[curLine].picfname.c_str(), _textLines[curLine].picalt.c_str(),
 						_textLines[curLine].pictitle.c_str());
 					break;
@@ -1051,6 +1057,20 @@ void MacText::render(int from, int to, int shadow) {
 	}
 
 	for (int i = myFrom; i != myTo; i += delta) {
+		if (!_textLines[i].picfname.empty()) {
+			const Surface *image = getImageSurface(_textLines[i].picfname);
+
+			int xOffset = (_textLines[i].width - _textLines[i].charwidth) / 2;
+			Common::Rect bbox(xOffset, _textLines[i].y, xOffset + _textLines[i].charwidth, _textLines[i].y + _textLines[i].height);
+
+			// Pre-fill images with white to accommodate transparency
+			surface->fillRect(bbox, surface->format.RGBToColor(0xff, 0x0, 0x0));
+
+			surface->blitFrom(image, Common::Rect(0, 0, image->w, image->h), bbox);
+
+			continue;
+		}
+
 		int xOffset = getAlignOffset(i) + _textLines[i].indent + _textLines[i].firstLineIndent;
 		xOffset++;
 
@@ -1083,13 +1103,13 @@ void MacText::render(int from, int to, int shadow) {
 
 			if (_textLines[i].chunks[j].plainByteMode()) {
 				Common::String str = _textLines[i].chunks[j].getEncodedText();
-				_textLines[i].chunks[j].getFont()->drawString(surface, str, xOffset, _textLines[i].y + yOffset, w, shadow ? _wm->_colorBlack : _textLines[i].chunks[j].fgcolor, Graphics::kTextAlignLeft, 0, true);
+				_textLines[i].chunks[j].getFont()->drawString(surface, str, xOffset, _textLines[i].y + yOffset, w, shadow ? _wm->_colorBlack : _textLines[i].chunks[j].fgcolor, kTextAlignLeft, 0, true);
 				xOffset += _textLines[i].chunks[j].getFont()->getStringWidth(str);
 			} else {
 				if (_wm->_language == Common::HE_ISR)
-					_textLines[i].chunks[j].getFont()->drawString(surface, convertBiDiU32String(_textLines[i].chunks[j].text, Common::BIDI_PAR_RTL), xOffset, _textLines[i].y + yOffset, w, shadow ? _wm->_colorBlack : _textLines[i].chunks[j].fgcolor, Graphics::kTextAlignLeft, 0, true);
+					_textLines[i].chunks[j].getFont()->drawString(surface, convertBiDiU32String(_textLines[i].chunks[j].text, Common::BIDI_PAR_RTL), xOffset, _textLines[i].y + yOffset, w, shadow ? _wm->_colorBlack : _textLines[i].chunks[j].fgcolor, kTextAlignLeft, 0, true);
 				else
-					_textLines[i].chunks[j].getFont()->drawString(surface, convertBiDiU32String(_textLines[i].chunks[j].text), xOffset, _textLines[i].y + yOffset, w, shadow ? _wm->_colorBlack : _textLines[i].chunks[j].fgcolor, Graphics::kTextAlignLeft, 0, true);
+					_textLines[i].chunks[j].getFont()->drawString(surface, convertBiDiU32String(_textLines[i].chunks[j].text), xOffset, _textLines[i].y + yOffset, w, shadow ? _wm->_colorBlack : _textLines[i].chunks[j].fgcolor, kTextAlignLeft, 0, true);
 				xOffset += _textLines[i].chunks[j].getFont()->getStringWidth(_textLines[i].chunks[j].text);
 			}
 		}
@@ -1131,6 +1151,20 @@ int MacText::getLineWidth(int line, bool enforce, int col) {
 	if (_textLines[line].width != -1 && !enforce && col == -1)
 		return _textLines[line].width;
 
+	if (!_textLines[line].picfname.empty()) {
+		const Surface *image = getImageSurface(_textLines[line].picfname);
+
+		if (!image)
+			return 1; // No image
+
+		float ratio = _maxWidth * _textLines[line].picpercent / 100.0 / (float)image->w;
+		_textLines[line].width = _maxWidth;
+		_textLines[line].height = image->h * ratio;
+		_textLines[line].charwidth = image->w * ratio;
+
+		return _textLines[line].width;
+	}
+
 	int width = _textLines[line].indent + _textLines[line].firstLineIndent;
 	int height = 0;
 	int charwidth = 0;
@@ -1320,7 +1354,7 @@ void MacText::resize(int w, int h) {
 	setMaxWidth(w);
 	if (_composeSurface->w != w || _composeSurface->h != h) {
 		delete _composeSurface;
-		_composeSurface = new Graphics::ManagedSurface(w, h, _wm->_pixelformat);
+		_composeSurface = new ManagedSurface(w, h, _wm->_pixelformat);
 		_dims.right = _dims.left + w;
 		_dims.bottom = _dims.top + h;
 
@@ -2185,6 +2219,9 @@ Common::U32String MacText::getMouseLink(int x, int y) {
 	int row, chunk;
 	getRowCol(x, y, nullptr, nullptr, &row, nullptr, &chunk);
 
+	if (!_textLines[row].picfname.empty())
+		return _textLines[row].pictitle;
+
 	return _textLines[row].chunks[chunk].text;
 }
 
@@ -2719,4 +2756,28 @@ void MacText::undrawCursor() {
 	_composeSurface->blitFrom(*_cursorSurface2, *_cursorRect, Common::Point(_cursorX + offset.x, _cursorY + offset.y));
 }
 
+const Surface *MacText::getImageSurface(Common::String &fname) {
+	if (_imageCache.contains(fname))
+		return _imageCache[fname]->getSurface();
+
+	Common::File file;
+
+	if (!file.open(fname)) {
+		warning("MacText::getImageSurface(): Cannot open file %s", fname.c_str());
+		return nullptr;
+	}
+
+	_imageCache[fname] = new Image::PNGDecoder();
+
+	if (!_imageCache[fname]->loadStream(file)) {
+		delete _imageCache[fname];
+
+		warning("MacText::getImageSurface(): Cannot load file %s", fname.c_str());
+
+		return nullptr;
+	}
+
+	return _imageCache[fname]->getSurface();
+}
+
 } // End of namespace Graphics
diff --git a/graphics/macgui/mactext.h b/graphics/macgui/mactext.h
index a7cd69405df..153b02f30a9 100644
--- a/graphics/macgui/mactext.h
+++ b/graphics/macgui/mactext.h
@@ -31,6 +31,10 @@
 #include "graphics/macgui/macwidget.h"
 #include "graphics/macgui/macwindow.h"
 
+namespace Image {
+class PNGDecoder;
+}
+
 namespace Graphics {
 
 class MacMenu;
@@ -116,7 +120,9 @@ struct MacTextLine {
 	bool paragraphEnd = false;
 	int indent = 0; // in units
 	int firstLineIndent = 0; // in pixels
-	Common::U32String picfname, picalt, pictitle;
+	Common::String picfname;
+	Common::U32String picalt, pictitle;
+	uint16 picpercent = 50;
 
 	Common::Array<MacFontRun> chunks;
 
@@ -176,7 +182,7 @@ public:
 	void drawToPoint(ManagedSurface *g, Common::Rect srcRect, Common::Point dstPoint);
 	void drawToPoint(ManagedSurface *g, Common::Point dstPoint);
 
-	Graphics::ManagedSurface *getSurface() { return _surface; }
+	ManagedSurface *getSurface() { return _surface; }
 	int getInterLinear() { return _interLinear; }
 	void setInterLinear(int interLinear);
 	void setMaxWidth(int maxWidth);
@@ -291,6 +297,9 @@ public:
 public:
 	void setMarkdownText(const Common::U32String &str);
 
+private:
+	const Surface *getImageSurface(Common::String &fname);
+
 private:
 	void init();
 	bool isCutAllowed();
@@ -378,6 +387,8 @@ private:
 	SelectedText _selectedText;
 
 	MacMenu *_menu;
+
+	Common::HashMap<Common::String, Image::PNGDecoder *> _imageCache;
 };
 
 } // End of namespace Graphics


Commit: 1c3956426294662660a886131ec84b3315ce344d
    https://github.com/scummvm/scummvm/commit/1c3956426294662660a886131ec84b3315ce344d
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: Properly process missing images in Markdown

Changed paths:
    graphics/macgui/mactext.cpp


diff --git a/graphics/macgui/mactext.cpp b/graphics/macgui/mactext.cpp
index d0a32a628f6..7692253799d 100644
--- a/graphics/macgui/mactext.cpp
+++ b/graphics/macgui/mactext.cpp
@@ -1066,7 +1066,8 @@ void MacText::render(int from, int to, int shadow) {
 			// Pre-fill images with white to accommodate transparency
 			surface->fillRect(bbox, surface->format.RGBToColor(0xff, 0x0, 0x0));
 
-			surface->blitFrom(image, Common::Rect(0, 0, image->w, image->h), bbox);
+			if (image)
+				surface->blitFrom(image, Common::Rect(0, 0, image->w, image->h), bbox);
 
 			continue;
 		}
@@ -1154,13 +1155,16 @@ int MacText::getLineWidth(int line, bool enforce, int col) {
 	if (!_textLines[line].picfname.empty()) {
 		const Surface *image = getImageSurface(_textLines[line].picfname);
 
-		if (!image)
-			return 1; // No image
-
-		float ratio = _maxWidth * _textLines[line].picpercent / 100.0 / (float)image->w;
-		_textLines[line].width = _maxWidth;
-		_textLines[line].height = image->h * ratio;
-		_textLines[line].charwidth = image->w * ratio;
+		if (image) {
+			float ratio = _maxWidth * _textLines[line].picpercent / 100.0 / (float)image->w;
+			_textLines[line].width = _maxWidth;
+			_textLines[line].height = image->h * ratio;
+			_textLines[line].charwidth = image->w * ratio;
+		} else {
+			_textLines[line].width = _maxWidth;
+			_textLines[line].height = 1;
+			_textLines[line].charwidth = 1;
+		}
 
 		return _textLines[line].width;
 	}


Commit: c4b3ece401d38e55a1abfc96ff1489a05f26cbe5
    https://github.com/scummvm/scummvm/commit/c4b3ece401d38e55a1abfc96ff1489a05f26cbe5
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: Load pictures from a specified zip file in Markdown

Changed paths:
    graphics/macgui/mactext.cpp
    graphics/macgui/mactext.h


diff --git a/graphics/macgui/mactext.cpp b/graphics/macgui/mactext.cpp
index 7692253799d..56f35dde052 100644
--- a/graphics/macgui/mactext.cpp
+++ b/graphics/macgui/mactext.cpp
@@ -22,6 +22,7 @@
 #include "common/file.h"
 #include "common/timer.h"
 #include "common/unicode-bidi.h"
+#include "common/compression/unzip.h"
 
 #include "graphics/font.h"
 #include "graphics/macgui/mactext.h"
@@ -31,7 +32,9 @@
 #include "graphics/macgui/macwidget.h"
 #include "graphics/macgui/macwindow.h"
 
+#ifdef USE_PNG
 #include "image/png.h"
+#endif
 
 namespace Graphics {
 
@@ -272,6 +275,8 @@ MacText::~MacText() {
 
 	for (auto &i : _imageCache)
 		delete i._value;
+
+	delete _imageArchive;
 }
 
 // this func returns the fg color of the first character we met in text
@@ -2760,20 +2765,38 @@ void MacText::undrawCursor() {
 	_composeSurface->blitFrom(*_cursorSurface2, *_cursorRect, Common::Point(_cursorX + offset.x, _cursorY + offset.y));
 }
 
+void MacText::setImageArchive(Common::String fname) {
+	_imageArchive = Common::makeZipArchive(fname);
+
+	if (!_imageArchive)
+		warning("MacText::setImageArchive(): Could not find %s. Images will not be rendered", fname.c_str());
+}
+
 const Surface *MacText::getImageSurface(Common::String &fname) {
 	if (_imageCache.contains(fname))
 		return _imageCache[fname]->getSurface();
 
-	Common::File file;
+	if (!_imageArchive) {
+		warning("MacText::getImageSurface(): Image Archive was not loaded. Use setImageArchive()");
+		return nullptr;
+	}
+
+	Common::SeekableReadStream *stream = _imageArchive->createReadStreamForMember(fname);
 
-	if (!file.open(fname)) {
+	if (!stream) {
 		warning("MacText::getImageSurface(): Cannot open file %s", fname.c_str());
 		return nullptr;
 	}
 
+#ifndef USE_PNG
+	warning("MacText::getImageSurface(): PNG support not compiled. Cannot load file %s", fname.c_str());
+
+	return nullptr;
+#else
+
 	_imageCache[fname] = new Image::PNGDecoder();
 
-	if (!_imageCache[fname]->loadStream(file)) {
+	if (!_imageCache[fname]->loadStream(*stream)) {
 		delete _imageCache[fname];
 
 		warning("MacText::getImageSurface(): Cannot load file %s", fname.c_str());
@@ -2782,6 +2805,7 @@ const Surface *MacText::getImageSurface(Common::String &fname) {
 	}
 
 	return _imageCache[fname]->getSurface();
+#endif // USE_PNG
 }
 
 } // End of namespace Graphics
diff --git a/graphics/macgui/mactext.h b/graphics/macgui/mactext.h
index 153b02f30a9..d31b07ff3f2 100644
--- a/graphics/macgui/mactext.h
+++ b/graphics/macgui/mactext.h
@@ -31,6 +31,10 @@
 #include "graphics/macgui/macwidget.h"
 #include "graphics/macgui/macwindow.h"
 
+namespace Common {
+class Archive;
+}
+
 namespace Image {
 class PNGDecoder;
 }
@@ -232,6 +236,8 @@ public:
 	int getMouseLine(int x, int y);
 	Common::U32String getMouseLink(int x, int y);
 
+	void setImageArchive(Common::String name);
+
 private:
 	MacFontRun getTextChunks(int start, int end);
 	void setTextChunks(int start, int end, int param, void (*callback)(MacFontRun &, int));
@@ -389,6 +395,7 @@ private:
 	MacMenu *_menu;
 
 	Common::HashMap<Common::String, Image::PNGDecoder *> _imageCache;
+	Common::Archive *_imageArchive = nullptr;
 };
 
 } // End of namespace Graphics


Commit: 3a84773936837f27f31da465c02bcf4a61cdcc1f
    https://github.com/scummvm/scummvm/commit/3a84773936837f27f31da465c02bcf4a61cdcc1f
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Pass image archive from RichText to MacText

Changed paths:
    gui/widgets/richtext.cpp
    gui/widgets/richtext.h


diff --git a/gui/widgets/richtext.cpp b/gui/widgets/richtext.cpp
index d21738ef154..0c39501e446 100644
--- a/gui/widgets/richtext.cpp
+++ b/gui/widgets/richtext.cpp
@@ -143,6 +143,10 @@ void RichTextWidget::createWidget() {
 	Graphics::MacFont macFont(Graphics::kMacFontNewYork, 30, Graphics::kMacFontRegular);
 
 	_txtWnd = new Graphics::MacText(Common::U32String(), _wm, &macFont, fg, bg, _textWidth, Graphics::kTextAlignLeft);
+
+	if (!_imageArchive.empty())
+		_txtWnd->setImageArchive(_imageArchive);
+
 	_txtWnd->setMarkdownText(_text);
 
 	_surface = new Graphics::ManagedSurface(_w, _h, _wm->_pixelformat);
diff --git a/gui/widgets/richtext.h b/gui/widgets/richtext.h
index 8048fa1ad13..52a31cbaa72 100644
--- a/gui/widgets/richtext.h
+++ b/gui/widgets/richtext.h
@@ -48,6 +48,8 @@ protected:
 	uint32 _reflowCmd;
 	int _textWidth;
 
+	Common::String _imageArchive;
+
 public:
 	RichTextWidget(GuiObject *boss, int x, int y, int w, int h, bool scale, const Common::U32String &text, const Common::U32String &tooltip = Common::U32String());
 	RichTextWidget(GuiObject *boss, int x, int y, int w, int h, const Common::U32String &text, const Common::U32String &tooltip = Common::U32String());
@@ -65,6 +67,8 @@ public:
 
 	bool containsWidget(Widget *) const override;
 
+	void setImageArchive(Common::String fname) { _imageArchive = fname; }
+
 protected:
 	void init();
 	void recalc();


Commit: 02cffed5e21feb8ebe5c85ad14148e6b97595aed
    https://github.com/scummvm/scummvm/commit/02cffed5e21feb8ebe5c85ad14148e6b97595aed
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Specify image archive in HelpDialog

Changed paths:
    gui/helpdialog.cpp


diff --git a/gui/helpdialog.cpp b/gui/helpdialog.cpp
index d1c368b9b93..05df63c61f4 100644
--- a/gui/helpdialog.cpp
+++ b/gui/helpdialog.cpp
@@ -167,7 +167,8 @@ HelpDialog::HelpDialog()
 "In case you revoke authorization to a path, still used for specific games/titles, please follow the procedure of fixing them outlined in the previous subheading.\n"
 	);
 
-	new RichTextWidget(tab, 10, 10, _w - 10, tabHeight - buttonHeight - 10, helpText3);
+	RichTextWidget *saftext = new RichTextWidget(tab, 10, 10, _w - 10, tabHeight - buttonHeight - 10, helpText3);
+	saftext->setImageArchive("android-help.zip");
 
 	tab->addTab(_("Paths"), "GlobalOptions_Graphics", false);
 


Commit: 30f825135cc096b9f300c3a5ec9cbf8503cef463
    https://github.com/scummvm/scummvm/commit/30f825135cc096b9f300c3a5ec9cbf8503cef463
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
DISTS: ANDROID: Added archive with help dialog pictures

Changed paths:
  A dists/android/android-help.zip


diff --git a/dists/android/android-help.zip b/dists/android/android-help.zip
new file mode 100644
index 00000000000..458df11e852
Binary files /dev/null and b/dists/android/android-help.zip differ


Commit: 152feb0f46c211018cdf7d66319001b72dcb14e4
    https://github.com/scummvm/scummvm/commit/152feb0f46c211018cdf7d66319001b72dcb14e4
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: Properly pass link URLs from Markdown to MacText

Changed paths:
    graphics/macgui/mactext-md.cpp
    graphics/macgui/mactext.cpp
    graphics/macgui/mactext.h
    graphics/macgui/macwindowmanager.cpp


diff --git a/graphics/macgui/mactext-md.cpp b/graphics/macgui/mactext-md.cpp
index a2e7107617e..1a1c82f1ede 100644
--- a/graphics/macgui/mactext-md.cpp
+++ b/graphics/macgui/mactext-md.cpp
@@ -195,7 +195,7 @@ int render_image(Common::SDDataBuffer *ob, const Common::SDDataBuffer *link, con
 		return 0;
 
 	Common::String res = Common::String::format("\001" "\016i%02x" "%02x%s",
-			50, (uint)link->size, Common::String((const char *)link->data, link->size).c_str());
+			80, (uint)link->size, Common::String((const char *)link->data, link->size).c_str());
 
 	if (alt)
 		res += Common::String::format("%02x%s", (uint)alt->size, Common::String((const char *)alt->data, alt->size).c_str());
@@ -228,7 +228,9 @@ int render_link(Common::SDDataBuffer *ob, const Common::SDDataBuffer *link, cons
 	const Common::SDDataBuffer *text = content ? content : link;
 
 	Common::String res = Common::String::format("\001" "\016+%02x00" "\001\016[%04x%04x%04x"
-		"%s" "\001\016]" "\001\016-%02x00", kMacFontUnderline, mdstate->linkr, mdstate->linkg, mdstate->linkb,
+		"\001\016l%02x%s" "%s" "\001\016l00" "\001\016]" "\001\016-%02x00", kMacFontUnderline,
+		mdstate->linkr, mdstate->linkg, mdstate->linkb,
+		(uint)link->size, Common::String((const char *)link->data , link->size).c_str(),
 		Common::String((const char *)text->data , text->size).c_str(), kMacFontUnderline);
 
 	sd_bufput(ob, res.c_str(), res.size());
diff --git a/graphics/macgui/mactext.cpp b/graphics/macgui/mactext.cpp
index 56f35dde052..acefb403cb3 100644
--- a/graphics/macgui/mactext.cpp
+++ b/graphics/macgui/mactext.cpp
@@ -835,6 +835,17 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 					break;
 					}
 
+				case 'l': { // \016lLLllll -- link len and text
+					s++;
+
+					uint16 len;
+
+					s = readHex(&len, s, 2);
+					current_format.link = Common::U32String(s, len);
+					s += len;
+					break;
+					}
+
 				default: {
 					uint16 fontId, textSlant, fontSize, palinfo1, palinfo2, palinfo3;
 
@@ -2231,7 +2242,10 @@ Common::U32String MacText::getMouseLink(int x, int y) {
 	if (!_textLines[row].picfname.empty())
 		return _textLines[row].pictitle;
 
-	return _textLines[row].chunks[chunk].text;
+	if (!_textLines[row].chunks[chunk].link.empty())
+		return _textLines[row].chunks[chunk].link;
+
+	return Common::U32String();
 }
 
 int MacText::getAlignOffset(int row) {
diff --git a/graphics/macgui/mactext.h b/graphics/macgui/mactext.h
index d31b07ff3f2..542aa9e880f 100644
--- a/graphics/macgui/mactext.h
+++ b/graphics/macgui/mactext.h
@@ -61,6 +61,7 @@ struct MacFontRun {
 	bool wordContinuation;
 	const Font *font;
 	MacWindowManager *wm;
+	Common::String link;  // Substitute to return when hover or click
 
 	MacFontRun() {
 		wm = nullptr;
diff --git a/graphics/macgui/macwindowmanager.cpp b/graphics/macgui/macwindowmanager.cpp
index a438521af87..3145dea6fe9 100644
--- a/graphics/macgui/macwindowmanager.cpp
+++ b/graphics/macgui/macwindowmanager.cpp
@@ -613,6 +613,11 @@ Common::U32String stripFormat(const Common::U32String &str) {
 					s += len;
 				} else if (*s == 't') { // font
 					s += 5;
+				} else if (*s == 'l') { // link
+					s++;
+					uint16 len;
+					s = readHex(&len, s, 2);
+					s += len;
 				} else
 					s += 22;
 			} else {


Commit: fd98104c9ee952b95d2d208674993db520d0b1cc
    https://github.com/scummvm/scummvm/commit/fd98104c9ee952b95d2d208674993db520d0b1cc
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Open URL when clicking on the links in RichTextWidget

Changed paths:
    gui/widgets/richtext.cpp


diff --git a/gui/widgets/richtext.cpp b/gui/widgets/richtext.cpp
index 0c39501e446..a0cf88a93b1 100644
--- a/gui/widgets/richtext.cpp
+++ b/gui/widgets/richtext.cpp
@@ -94,12 +94,14 @@ void RichTextWidget::handleMouseWheel(int x, int y, int direction) {
 }
 
 void RichTextWidget::handleMouseDown(int x, int y, int button, int clickCount) {
-	warning("M: %s", _txtWnd->getMouseLink(x + _x + _scrolledX, y + _y + _scrolledY).encode().c_str());
+	Common::String link = _txtWnd->getMouseLink(x + _x + _scrolledX, y + _y + _scrolledY).encode();
+
+	if (link.hasPrefixIgnoreCase("http"))
+		g_system->openUrl(link);
 }
 
 void RichTextWidget::handleTooltipUpdate(int x, int y) {
 	_tooltip = _txtWnd->getMouseLink(x + _x + _scrolledX, y + _y + _scrolledY);
-	//warning("t: %s", _tooltip.encode().c_str());
 }
 
 void RichTextWidget::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {


Commit: eb169e8ed19b8e0d463c687bf14b6b43a18ed601
    https://github.com/scummvm/scummvm/commit/eb169e8ed19b8e0d463c687bf14b6b43a18ed601
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: Added (disabled) code for dumping MacText rendering into PNG

Changed paths:
    graphics/macgui/mactext.cpp


diff --git a/graphics/macgui/mactext.cpp b/graphics/macgui/mactext.cpp
index acefb403cb3..6ed94ffdf8a 100644
--- a/graphics/macgui/mactext.cpp
+++ b/graphics/macgui/mactext.cpp
@@ -1057,6 +1057,15 @@ void MacText::render() {
 		render(0, _textLines.size());
 
 		_fullRefresh = false;
+
+#if 0
+		Common::DumpFile out;
+		Common::String filename = Common::String::format("z-%p.png", (void *)this);
+		if (out.open(filename)) {
+			warning("Wrote: %s", filename.c_str());
+			Image::writePNG(out, _surface->rawSurface());
+		}
+#endif
 	}
 }
 


Commit: 004eba9d250071a717d8640a5eba392b97001041
    https://github.com/scummvm/scummvm/commit/004eba9d250071a717d8640a5eba392b97001041
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: Fix MacText transparency rendering

Could lead to regressions, need more testing

Changed paths:
    graphics/macgui/mactext.cpp


diff --git a/graphics/macgui/mactext.cpp b/graphics/macgui/mactext.cpp
index 6ed94ffdf8a..25419e36fda 100644
--- a/graphics/macgui/mactext.cpp
+++ b/graphics/macgui/mactext.cpp
@@ -1516,7 +1516,7 @@ void MacText::draw(ManagedSurface *g, int x, int y, int w, int h, int xoff, int
 	if (_textShadow)
 		g->blitFrom(*_shadowSurface, Common::Rect(MIN<int>(_surface->w, x), MIN<int>(_surface->h, y), MIN<int>(_surface->w, x + w), MIN<int>(_surface->h, y + h)), Common::Point(xoff + _textShadow, yoff + _textShadow));
 
-	g->transBlitFrom(*_surface, Common::Rect(MIN<int>(_surface->w, x), MIN<int>(_surface->h, y), MIN<int>(_surface->w, x + w), MIN<int>(_surface->h, y + h)), Common::Point(xoff, yoff), _bgcolor);
+	g->transBlitFrom(*_surface, Common::Rect(MIN<int>(_surface->w, x), MIN<int>(_surface->h, y), MIN<int>(_surface->w, x + w), MIN<int>(_surface->h, y + h)), Common::Point(xoff, yoff));
 
 	_contentIsDirty = false;
 	_cursorDirty = false;


Commit: 3857f716e644d35eb79c4eee219f44a1451de89d
    https://github.com/scummvm/scummvm/commit/3857f716e644d35eb79c4eee219f44a1451de89d
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: THEMES: Moved help button to the left side

Changed paths:
    gui/themes/common/highres_layout.stx
    gui/themes/common/lowres_layout.stx
    gui/themes/default.inc
    gui/themes/scummclassic/classic_layout.stx
    gui/themes/scummclassic/classic_layout_lowres.stx


diff --git a/gui/themes/common/highres_layout.stx b/gui/themes/common/highres_layout.stx
index 79e1638a668..df37bf32c5e 100644
--- a/gui/themes/common/highres_layout.stx
+++ b/gui/themes/common/highres_layout.stx
@@ -153,8 +153,12 @@
 
 	<dialog name = 'Launcher' overlays = 'screen' resolution = 'y<=x'>
 		<layout type = 'vertical' align = 'center' padding = '23, 23, 8, 40'>
-			<layout type = 'horizontal'  spacing = '5' padding = '10, 0, 0, 0'>
-				<space size  = 'Globals.Button.Height' />
+			<layout type = 'horizontal'  spacing = '5' padding = '0, 0, 0, 0'>
+				<widget name = 'HelpButton'
+						height = 'Globals.Button.Height'
+						width = 'Globals.Button.Height'
+						rtl = 'yes'
+				/>
 				<space />
 				<widget name = 'Logo'
 						width = '287'
@@ -162,11 +166,7 @@
 						rtl = 'no'
 				/>
 				<space />
-				<widget name = 'HelpButton'
-						height = 'Globals.Button.Height'
-						width = 'Globals.Button.Height'
-						rtl = 'yes'
-				/>
+				<space size  = 'Globals.Button.Height' />
 			</layout>
 			<layout type = 'horizontal'  spacing = '5' padding = '10, 0, 0, 0'>
 				<widget name = 'SearchPic'
@@ -2545,7 +2545,7 @@
 	</dialog>
 
 	<dialog name = 'ImageAlbum' overlays = 'screen' inset = '8' shading = 'dim'>
-		<layout type = 'vertical' padding = '8, 8, 8, 8' align = 'center'> 
+		<layout type = 'vertical' padding = '8, 8, 8, 8' align = 'center'>
 			<widget name = 'Title' height = 'Globals.Line.Height'/>
 			<widget name = 'ImageContainer' />
 			<layout type = 'horizontal' padding = '0, 0, 16, 0'>
diff --git a/gui/themes/common/lowres_layout.stx b/gui/themes/common/lowres_layout.stx
index 6fe4cb2c712..1fc3a1d6574 100644
--- a/gui/themes/common/lowres_layout.stx
+++ b/gui/themes/common/lowres_layout.stx
@@ -141,10 +141,20 @@
 
 	<dialog name = 'Launcher' overlays = 'screen'>
 		<layout type = 'vertical' align = 'center' padding = '4, 4, 2, 2' spacing = '2'>
-			<widget name = 'Version'
-					height = 'Globals.Line.Height'
-					textalign = 'center'
-			/>
+			<layout type = 'horizontal'  spacing = '5' padding = '0, 0, 0, 0'>
+				<widget name = 'HelpButton'
+						height = 'Globals.Button.Height'
+						width = 'Globals.Button.Height'
+						rtl = 'yes'
+				/>
+				<space />
+				<widget name = 'Version'
+						height = 'Globals.Line.Height'
+						textalign = 'center'
+				/>
+				<space />
+				<space size  = 'Globals.Button.Height' />
+			</layout>
 			<layout type = 'horizontal' spacing = '2' padding = '0, 0, 0, 0'>
 				<widget name = 'SearchDesc'
 						width = '45'
@@ -2360,7 +2370,7 @@
 	</dialog>
 
 	<dialog name = 'ImageAlbum' overlays = 'screen' inset = '8' shading = 'dim'>
-		<layout type = 'vertical' padding = '8, 8, 8, 8' align = 'center'> 
+		<layout type = 'vertical' padding = '8, 8, 8, 8' align = 'center'>
 			<widget name = 'Title' height = 'Globals.Line.Height'/>
 			<widget name = 'ImageContainer' />
 			<layout type = 'horizontal' padding = '0, 0, 16, 0'>
diff --git a/gui/themes/default.inc b/gui/themes/default.inc
index 46ad660db4d..f8299c0fbca 100644
--- a/gui/themes/default.inc
+++ b/gui/themes/default.inc
@@ -1538,10 +1538,20 @@ const char *defaultXML1 = "<?xml version = '1.0'?>"
 "</globals>"
 "<dialog name='Launcher' overlays='screen'>"
 "<layout type='vertical' align='center' padding='16,16,8,8'>"
+"<layout type='horizontal' spacing='5' padding='0,0,0,0'>"
+"<widget name='HelpButton' "
+"height='Globals.Button.Height' "
+"width='Globals.Button.Height' "
+"rtl='yes' "
+"/>"
+"<space />"
 "<widget name='Version' "
 "height='Globals.Line.Height' "
 "textalign='center' "
 "/>"
+"<space />"
+"<space size='Globals.Button.Height' />"
+"</layout>"
 "<layout type='horizontal' spacing='5' padding='10,0,0,0'>"
 "<widget name='SearchDesc' "
 "width='60' "
@@ -3640,10 +3650,20 @@ const char *defaultXML1 = "<?xml version = '1.0'?>"
 "</globals>"
 "<dialog name='Launcher' overlays='screen'>"
 "<layout type='vertical' align='center' padding='6,6,2,2'>"
+"<layout type='horizontal' spacing='5' padding='0,0,0,0'>"
+"<widget name='HelpButton' "
+"height='Globals.Button.Height' "
+"width='Globals.Button.Height' "
+"rtl='yes' "
+"/>"
+"<space />"
 "<widget name='Version' "
 "height='Globals.Line.Height' "
 "textalign='center' "
 "/>"
+"<space />"
+"<space size='Globals.Button.Height' />"
+"</layout>"
 "<layout type='horizontal' spacing='2' padding='0,0,0,0'>"
 "<widget name='SearchDesc' "
 "width='45' "
diff --git a/gui/themes/scummclassic/classic_layout.stx b/gui/themes/scummclassic/classic_layout.stx
index 38b20c5c79f..477ba641b2d 100644
--- a/gui/themes/scummclassic/classic_layout.stx
+++ b/gui/themes/scummclassic/classic_layout.stx
@@ -133,10 +133,20 @@
 
 	<dialog name = 'Launcher' overlays = 'screen'>
 		<layout type = 'vertical' align = 'center' padding = '16, 16, 8, 8'>
-			<widget name = 'Version'
-					height = 'Globals.Line.Height'
-					textalign = 'center'
-			/>
+			<layout type = 'horizontal'  spacing = '5' padding = '0, 0, 0, 0'>
+				<widget name = 'HelpButton'
+						height = 'Globals.Button.Height'
+						width = 'Globals.Button.Height'
+						rtl = 'yes'
+				/>
+				<space />
+				<widget name = 'Version'
+						height = 'Globals.Line.Height'
+						textalign = 'center'
+				/>
+				<space />
+				<space size  = 'Globals.Button.Height' />
+			</layout>
 			<layout type = 'horizontal' spacing = '5' padding = '10, 0, 0, 0'>
 				<widget name = 'SearchDesc'
 						width = '60'
@@ -2197,7 +2207,7 @@
 	</dialog>
 
 	<dialog name = 'ImageAlbum' overlays = 'screen' inset = '8' shading = 'dim'>
-		<layout type = 'vertical' padding = '8, 8, 8, 8' align = 'center'> 
+		<layout type = 'vertical' padding = '8, 8, 8, 8' align = 'center'>
 			<widget name = 'Title' height = 'Globals.Line.Height'/>
 			<widget name = 'ImageContainer' />
 			<layout type = 'horizontal' padding = '0, 0, 16, 0'>
diff --git a/gui/themes/scummclassic/classic_layout_lowres.stx b/gui/themes/scummclassic/classic_layout_lowres.stx
index 018c076ff3e..35c5373c6f4 100644
--- a/gui/themes/scummclassic/classic_layout_lowres.stx
+++ b/gui/themes/scummclassic/classic_layout_lowres.stx
@@ -133,10 +133,20 @@
 
 	<dialog name = 'Launcher' overlays = 'screen'>
 		<layout type = 'vertical' align = 'center' padding = '6, 6, 2, 2'>
-			<widget name = 'Version'
-					height = 'Globals.Line.Height'
-					textalign = 'center'
-			/>
+			<layout type = 'horizontal'  spacing = '5' padding = '0, 0, 0, 0'>
+				<widget name = 'HelpButton'
+						height = 'Globals.Button.Height'
+						width = 'Globals.Button.Height'
+						rtl = 'yes'
+				/>
+				<space />
+				<widget name = 'Version'
+						height = 'Globals.Line.Height'
+						textalign = 'center'
+				/>
+				<space />
+				<space size  = 'Globals.Button.Height' />
+			</layout>
 			<layout type = 'horizontal' spacing = '2' padding = '0, 0, 0, 0'>
 				<widget name = 'SearchDesc'
 						width = '45'
@@ -2174,7 +2184,7 @@
 	</dialog>
 
 	<dialog name = 'ImageAlbum' overlays = 'screen' inset = '8' shading = 'dim'>
-		<layout type = 'vertical' padding = '8, 8, 8, 8' align = 'center'> 
+		<layout type = 'vertical' padding = '8, 8, 8, 8' align = 'center'>
 			<widget name = 'Title' height = 'Globals.Line.Height'/>
 			<widget name = 'ImageContainer' />
 			<layout type = 'horizontal' padding = '0, 0, 16, 0'>


Commit: 4b86df2f62fb0780a4a064ee413656e858d0477a
    https://github.com/scummvm/scummvm/commit/4b86df2f62fb0780a4a064ee413656e858d0477a
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Regenerated themes

Changed paths:
    gui/themes/residualvm.zip
    gui/themes/scummclassic.zip
    gui/themes/scummmodern.zip
    gui/themes/scummremastered.zip


diff --git a/gui/themes/residualvm.zip b/gui/themes/residualvm.zip
index 044ef231380..cfd23feacdf 100644
Binary files a/gui/themes/residualvm.zip and b/gui/themes/residualvm.zip differ
diff --git a/gui/themes/scummclassic.zip b/gui/themes/scummclassic.zip
index 761009e102a..fb6eaae9a1c 100644
Binary files a/gui/themes/scummclassic.zip and b/gui/themes/scummclassic.zip differ
diff --git a/gui/themes/scummmodern.zip b/gui/themes/scummmodern.zip
index 5b5500c2b27..c43ccbcbd63 100644
Binary files a/gui/themes/scummmodern.zip and b/gui/themes/scummmodern.zip differ
diff --git a/gui/themes/scummremastered.zip b/gui/themes/scummremastered.zip
index 625a65bcf93..2a1d2f60af9 100644
Binary files a/gui/themes/scummremastered.zip and b/gui/themes/scummremastered.zip differ


Commit: c0db1957c82ac9e87bde609bd4d94d1dcf5587a3
    https://github.com/scummvm/scummvm/commit/c0db1957c82ac9e87bde609bd4d94d1dcf5587a3
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Pick up theme font size in RIchTextWidget

Changed paths:
    gui/widgets/richtext.cpp


diff --git a/gui/widgets/richtext.cpp b/gui/widgets/richtext.cpp
index a0cf88a93b1..7f4c4bbdfc0 100644
--- a/gui/widgets/richtext.cpp
+++ b/gui/widgets/richtext.cpp
@@ -142,7 +142,9 @@ void RichTextWidget::createWidget() {
 	TextColorData *normal = g_gui.theme()->getTextColorData(kTextColorNormal);
 	uint32 fg = _wm->_pixelformat.RGBToColor(normal->r, normal->g, normal->b);
 
-	Graphics::MacFont macFont(Graphics::kMacFontNewYork, 30, Graphics::kMacFontRegular);
+	const int fontHeight = g_gui.xmlEval()->getVar("Globals.Font.Height", 25);
+
+	Graphics::MacFont macFont(Graphics::kMacFontNewYork, fontHeight, Graphics::kMacFontRegular);
 
 	_txtWnd = new Graphics::MacText(Common::U32String(), _wm, &macFont, fg, bg, _textWidth, Graphics::kTextAlignLeft);
 


Commit: 367b059040121e44471b72d8787317c8bacfb6a4
    https://github.com/scummvm/scummvm/commit/367b059040121e44471b72d8787317c8bacfb6a4
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Fix type in ScrollCOntainerWidget height calculation

Changed paths:
    gui/widgets/scrollcontainer.cpp


diff --git a/gui/widgets/scrollcontainer.cpp b/gui/widgets/scrollcontainer.cpp
index 0baa42811fe..0bc8ba2c21c 100644
--- a/gui/widgets/scrollcontainer.cpp
+++ b/gui/widgets/scrollcontainer.cpp
@@ -58,7 +58,7 @@ void ScrollContainerWidget::recalc() {
 	_limitH = _h;
 
 	//calculate virtual height
-	const int spacing = g_gui.xmlEval()->getVar("Global.Font.Height", 16); //on the bottom
+	const int spacing = g_gui.xmlEval()->getVar("Globals.Font.Height", 16); //on the bottom
 	int min = spacing, max = 0;
 	Widget *ptr = _firstWidget;
 	while (ptr) {


Commit: 7ab1c9371274148c4617a9f22946bde0ca4f972c
    https://github.com/scummvm/scummvm/commit/7ab1c9371274148c4617a9f22946bde0ca4f972c
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
OSYSTEM: Added buildHelpDialogData() call

Changed paths:
    common/system.h


diff --git a/common/system.h b/common/system.h
index 72c71621cc2..c05c8a427d5 100644
--- a/common/system.h
+++ b/common/system.h
@@ -1472,7 +1472,7 @@ public:
 	 */
 	virtual void setCursorPalette(const byte *colors, uint start, uint num) {}
 
-	
+
 
 	/**
 	 * Get the system-configured double-click time interval.
@@ -1829,6 +1829,11 @@ public:
 	 */
 	virtual GUI::OptionsContainerWidget *buildBackendOptionsWidget(GUI::GuiObject *boss, const Common::String &name, const Common::String &target) const { return nullptr; }
 
+	/**
+	 * Return list of strings used for building help dialog
+	 */
+	 virtual const char **buildHelpDialogData() { return nullptr; }
+
 	/**
 	 * Notify the backend that the settings editable from the game tab in the
 	 * options dialog may have changed and that they need to be applied if


Commit: ba851757f01a02cdf81fd475db690fa9b08d3140
    https://github.com/scummvm/scummvm/commit/ba851757f01a02cdf81fd475db690fa9b08d3140
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: Used more logical sizes for headers

Changed paths:
    graphics/macgui/mactext.cpp


diff --git a/graphics/macgui/mactext.cpp b/graphics/macgui/mactext.cpp
index 25419e36fda..bf5025bd34a 100644
--- a/graphics/macgui/mactext.cpp
+++ b/graphics/macgui/mactext.cpp
@@ -710,7 +710,7 @@ void MacText::splitString(const Common::U32String &str, int curLine) {
 
 					s = readHex(&headSize, s, 1);
 					if (headSize >= 1 && headSize <= 6) { // set
-						const float sizes[] = { 1, 3.0f, 2.5f, 2.0f, 1.75f, 1.5f, 1.25f };
+						const float sizes[] = { 1, 2.0f, 1.41f, 1.155f, 1.0f, .894f, .816f };
 						current_format.fontSize = _defaultFormatting.fontSize * sizes[headSize];
 					}
 


Commit: c57b199eb40439fa1a99ddd1caca9280ff9fbd83
    https://github.com/scummvm/scummvm/commit/c57b199eb40439fa1a99ddd1caca9280ff9fbd83
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Adjusted header sizes in HelpDialog

Changed paths:
    gui/helpdialog.cpp


diff --git a/gui/helpdialog.cpp b/gui/helpdialog.cpp
index 05df63c61f4..39e428447b5 100644
--- a/gui/helpdialog.cpp
+++ b/gui/helpdialog.cpp
@@ -52,7 +52,7 @@ HelpDialog::HelpDialog()
 
 	tab->addTab(_("General"), "GlobalOptions_Graphics", false);
 	Common::U32String helpText1 = _(
-"#### Where to get the games\n"
+"## Where to get the games\n"
 "\n"
 "Many games supported by ScummVM can still be bought from companies at the links below. Not all games on this list are supported by ScummVM, please check the compatibility page beforehand.\n"
 "\n"
@@ -68,7 +68,7 @@ HelpDialog::HelpDialog()
 	tab->addTab(_("Controls"), "GlobalOptions_Graphics", false);
 
 	Common::U32String helpText2 = _(
-"#### Touch controls\n"
+"## Touch controls\n"
 "\n"
 "The touch control scheme can be configured in the global settings. From the Launcher, go to **Options > Backend > Choose the preferred touch mode**.\n"
 "It's possible to configure the touch mode for three situations (ScummVM menus, 2D games and 3D games) and choose one of the three possible modes:\n"
@@ -82,25 +82,25 @@ HelpDialog::HelpDialog()
 "\n"
 "To display or hide the small controller icon, from the Launcher select **Options** and then the **Backend** tab. Tick the **Show on-screen control** box to enable the controller icon.\n"
 "\n"
-"#### Two finger tap\n"
+"## Two finger tap\n"
 "\n"
 "To do a two finger tap, hold one finger down and then tap with a second finger.\n"
 "\n"
-"#### Three finger tap\n"
+"## Three finger tap\n"
 "\n"
 "To do a three finger tap, start with holding down one finger and progressively touch down the other two fingers, one at a time, while still holding down the previous fingers. Imagine you are impatiently tapping your fingers on a surface, but then slow down that movement so it is rhythmic, but not too slow.\n"
 "\n"
-"#### Immersive Sticky fullscreen mode\n"
+"## Immersive Sticky fullscreen mode\n"
 "\n"
 "ScummVM for Android uses the Immersive Sticky fullscreen mode, which means that the Android system bar is hidden until the user swipes from an edge with a system bar. Swipe from the edge to reveal the system bars.  They remain semi-transparent and disappear after a few seconds unless you interact with them. Your swipe also registers in the game, so if you need to swipe from an edge with system bars, your game play is not interrupted.\n"
 "\n"
-"#### Global Main Menu\n"
+"## Global Main Menu\n"
 "\n"
 "To open the Global Main Menu, tap on the small menu icon at the top right of the screen.\n"
 "\n"
 "To display or hide the small menu icon, from the Launcher select **Options** and then the **Backend** tab. Tick the **Show on-screen control** box to enable the menu icon.\n"
 "\n"
-"#### Virtual keyboard\n"
+"## Virtual keyboard\n"
 "\n"
 "To open the virtual keyboard, long press on the small controller icon at the top right of the screen, or tap on any editable text field. To hide the virtual keyboard, tap the small controller icon (which became a keyboard one) again, or tap outside the text field.\n"
 "\n"
@@ -111,7 +111,7 @@ HelpDialog::HelpDialog()
 	tab->addTab(_("Adding Games"), "GlobalOptions_Graphics", false);
 
 	Common::U32String helpText3 = _(
-"#### Adding SAF paths to ScummVM directory list\n"
+"## Adding SAF paths to ScummVM directory list\n"
 "\n"
 "Starting with version 2.7.0 of ScummVM for Android, significant changes were made to the file access system to allow support for modern versions of the Android Operating System.\n"
 "\n"
@@ -152,7 +152,7 @@ HelpDialog::HelpDialog()
 "Steps 2 and 3 need to be done only once for all of your games.\n"
 "\n"
 "\n"
-"#### Removing SAF path authorizations\n"
+"## Removing SAF path authorizations\n"
 "\n"
 "In case you would like to revoke any of the granted SAF authorizations, there is an option for this in the \"Global Options > Backend\" tab as shown on the screenshot below:\n"
 "\n"


Commit: 6109f08cfb120eeac876b780a503d872c91ad632
    https://github.com/scummvm/scummvm/commit/6109f08cfb120eeac876b780a503d872c91ad632
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: Implemented codespan rendering in Markdown

Changed paths:
    graphics/macgui/mactext-md.cpp


diff --git a/graphics/macgui/mactext-md.cpp b/graphics/macgui/mactext-md.cpp
index 1a1c82f1ede..b29bc494014 100644
--- a/graphics/macgui/mactext-md.cpp
+++ b/graphics/macgui/mactext-md.cpp
@@ -162,7 +162,12 @@ int render_codespan(Common::SDDataBuffer *ob, const Common::SDDataBuffer *text,
 	if (!text)
 		return 0;
 
-	warning("STUB: render_codespan(%s)", PR(text));
+	Common::String res = Common::String::format("\001\016t%04x" "%s" "\001\016tffff",
+			kMacFontMonaco, Common::String((const char *)text->data , text->size).c_str());
+
+	sd_bufput(ob, res.c_str(), res.size());
+
+	debug(1, "render_codespan(%s)", PR(text));
 	return 1;
 }
 


Commit: 878870b0ef48af5ad1eed21a2c08870de147c23e
    https://github.com/scummvm/scummvm/commit/878870b0ef48af5ad1eed21a2c08870de147c23e
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Added keyboard shortcuts tab to HelpDialog

Changed paths:
    gui/helpdialog.cpp


diff --git a/gui/helpdialog.cpp b/gui/helpdialog.cpp
index 39e428447b5..d77871a285f 100644
--- a/gui/helpdialog.cpp
+++ b/gui/helpdialog.cpp
@@ -170,7 +170,38 @@ HelpDialog::HelpDialog()
 	RichTextWidget *saftext = new RichTextWidget(tab, 10, 10, _w - 10, tabHeight - buttonHeight - 10, helpText3);
 	saftext->setImageArchive("android-help.zip");
 
-	tab->addTab(_("Paths"), "GlobalOptions_Graphics", false);
+	tab->addTab(_("Keyboard"), "GlobalOptions_Graphics", false);
+
+	Common::U32String helpText4 = _(
+"## Keyboard shortcuts\n"
+"\n"
+"ScummVM supports various in-game keyboard and mouse shortcuts, and since version 2.2.0 these can be manually configured in the **Keymaps tab**, or in the **configuration file**.\n"
+"\n"
+"For game-specific controls, see the [wiki entry](https://wiki.scummvm.org/index.php?title=Category:Supported_Games) for the game you are playing.\n"
+"\n"
+"Default shortcuts are shown in the table.\n"
+"\n"
+"| Shortcut      | Description\n"
+"| --------------------------------\n"
+"| `Ctrl+F5` -- Displays the Global Main Menu\n"
+"| `Cmd+q`    -- Quit (macOS)\n"
+"| `Ctrl+q`  -- Quit (Linux/Unix)\n"
+"| `Alt+F4`  -- Quit (Windows)\n"
+"| `Ctrl+z`  -- Quit (other platforms)\n"
+"| `Ctrl+u`  -- Mutes all sounds\n"
+"| `Ctrl+m`  -- Toggles mouse capture\n"
+"| `Ctrl+Alt` and `9` or `0` -- Cycles forwards/backwards between graphics filters\n"
+"| `Ctrl+Alt` and `+` or `-` -- Increases/decreases the scale factor\n"
+"| `Ctrl+Alt+a` -- Toggles aspect ratio correction on/off\n"
+"| `Ctrl+Alt+f` -- Toggles between nearest neighbor and bilinear interpolation (graphics filtering on/off)\n"
+"| `Ctrl+Alt+s` -- Cycles through stretch modes\n"
+"| `Alt+Enter`   -- Toggles full screen/windowed mode\n"
+"| `Alt+s`          -- Takes a screenshot\n"
+"| `Ctrl+F7`       -- Opens virtual keyboard (if enabled). This can also be opened with a long press of the middle mouse button or wheel.\n"
+"| `Ctrl+Alt+d` -- Opens the ScummVM debugger\n"
+	);
+
+	new RichTextWidget(tab, 10, 10, _w - 10, tabHeight - buttonHeight - 10, helpText4);
 
 	new ButtonWidget(this, _w - buttonWidth - 10, _h - buttonHeight - 10, buttonWidth, buttonHeight, Common::U32String("Close"), Common::U32String(), kCloseCmd);
 }


Commit: 70d47e31ed8ba7a4abe1ce02131623be14525904
    https://github.com/scummvm/scummvm/commit/70d47e31ed8ba7a4abe1ce02131623be14525904
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GRAPHICS: MACGUI: Remove leftover debug paint

Changed paths:
    graphics/macgui/mactext.cpp


diff --git a/graphics/macgui/mactext.cpp b/graphics/macgui/mactext.cpp
index bf5025bd34a..4dcaffe67fd 100644
--- a/graphics/macgui/mactext.cpp
+++ b/graphics/macgui/mactext.cpp
@@ -1088,9 +1088,6 @@ void MacText::render(int from, int to, int shadow) {
 			int xOffset = (_textLines[i].width - _textLines[i].charwidth) / 2;
 			Common::Rect bbox(xOffset, _textLines[i].y, xOffset + _textLines[i].charwidth, _textLines[i].y + _textLines[i].height);
 
-			// Pre-fill images with white to accommodate transparency
-			surface->fillRect(bbox, surface->format.RGBToColor(0xff, 0x0, 0x0));
-
 			if (image)
 				surface->blitFrom(image, Common::Rect(0, 0, image->w, image->h), bbox);
 


Commit: afd6866a3c5eb3a122e4b01095ed7ccf9782c2fa
    https://github.com/scummvm/scummvm/commit/afd6866a3c5eb3a122e4b01095ed7ccf9782c2fa
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Speed up RichTextWidget scrolling

Changed paths:
    gui/widgets/richtext.cpp


diff --git a/gui/widgets/richtext.cpp b/gui/widgets/richtext.cpp
index 7f4c4bbdfc0..0448ea7158f 100644
--- a/gui/widgets/richtext.cpp
+++ b/gui/widgets/richtext.cpp
@@ -130,7 +130,7 @@ void RichTextWidget::recalc() {
 	_verticalScroll->_numEntries = h;
 	_verticalScroll->_currentPos = _scrolledY;
 	_verticalScroll->_entriesPerPage = _limitH;
-	_verticalScroll->_singleStep = 30 * 3;
+	_verticalScroll->_singleStep = _h / 4;
 	_verticalScroll->setPos(_textWidth, 0);
 	_verticalScroll->setSize(_scrollbarWidth, _limitH-1);
 


Commit: b30797680881041c9e9e82712d676d045ea5bb0e
    https://github.com/scummvm/scummvm/commit/b30797680881041c9e9e82712d676d045ea5bb0e
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Add supoport for backend-provided tabs in HelpDialog

Changed paths:
    gui/helpdialog.cpp


diff --git a/gui/helpdialog.cpp b/gui/helpdialog.cpp
index d77871a285f..fd2ca6869fb 100644
--- a/gui/helpdialog.cpp
+++ b/gui/helpdialog.cpp
@@ -203,6 +203,29 @@ HelpDialog::HelpDialog()
 
 	new RichTextWidget(tab, 10, 10, _w - 10, tabHeight - buttonHeight - 10, helpText4);
 
+	const char **backendTabs = g_system->buildHelpDialogData();
+
+	if (backendTabs) {
+		while (*backendTabs) {
+			Common::U32String tabName(_(*backendTabs++));
+			const char *imagePack = nullptr;
+
+			if (*backendTabs && **backendTabs)
+				imagePack = *backendTabs;
+
+			backendTabs++;
+
+			Common::U32String tabText(_(*backendTabs++));
+
+			tab->addTab(tabName, "GlobalOptions_Graphics", false);
+
+			RichTextWidget *rt = new RichTextWidget(tab, 10, 10, _w - 10, tabHeight - buttonHeight - 10, tabText);
+
+			if (imagePack)
+				rt->setImageArchive(imagePack);
+		}
+	}
+
 	new ButtonWidget(this, _w - buttonWidth - 10, _h - buttonHeight - 10, buttonWidth, buttonHeight, Common::U32String("Close"), Common::U32String(), kCloseCmd);
 }
 


Commit: 6ae8089c617f4b468d700c9c06e13d32cebe7592
    https://github.com/scummvm/scummvm/commit/6ae8089c617f4b468d700c9c06e13d32cebe7592
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Move Keyboards shortcut help to SDL backend

Changed paths:
    backends/platform/sdl/sdl.cpp
    backends/platform/sdl/sdl.h
    gui/helpdialog.cpp
    po/POTFILES


diff --git a/backends/platform/sdl/sdl.cpp b/backends/platform/sdl/sdl.cpp
index 4aecdeda34b..8567e4f1239 100644
--- a/backends/platform/sdl/sdl.cpp
+++ b/backends/platform/sdl/sdl.cpp
@@ -26,6 +26,7 @@
 #include "gui/EventRecorder.h"
 #include "common/taskbar.h"
 #include "common/textconsole.h"
+#include "common/translation.h"
 
 #ifdef USE_DISCORD
 #include "backends/presence/discord/discord.h"
@@ -1004,3 +1005,42 @@ void OSystem_SDL::clearGraphicsModes() {
 	}
 }
 #endif
+
+static const char *helpTabs[] = {
+_s("Keyboard"),
+"",
+_s(
+"## Keyboard shortcuts\n"
+"\n"
+"ScummVM supports various in-game keyboard and mouse shortcuts, and since version 2.2.0 these can be manually configured in the **Keymaps tab**, or in the **configuration file**.\n"
+"\n"
+"For game-specific controls, see the [wiki entry](https://wiki.scummvm.org/index.php?title=Category:Supported_Games) for the game you are playing.\n"
+"\n"
+"Default shortcuts are shown in the table.\n"
+"\n"
+"| Shortcut      | Description\n"
+"| --------------------------------\n"
+"| `Ctrl+F5` -- Displays the Global Main Menu\n"
+"| `Cmd+q`    -- Quit (macOS)\n"
+"| `Ctrl+q`  -- Quit (Linux/Unix)\n"
+"| `Alt+F4`  -- Quit (Windows)\n"
+"| `Ctrl+z`  -- Quit (other platforms)\n"
+"| `Ctrl+u`  -- Mutes all sounds\n"
+"| `Ctrl+m`  -- Toggles mouse capture\n"
+"| `Ctrl+Alt` and `9` or `0` -- Cycles forwards/backwards between graphics filters\n"
+"| `Ctrl+Alt` and `+` or `-` -- Increases/decreases the scale factor\n"
+"| `Ctrl+Alt+a` -- Toggles aspect ratio correction on/off\n"
+"| `Ctrl+Alt+f` -- Toggles between nearest neighbor and bilinear interpolation (graphics filtering on/off)\n"
+"| `Ctrl+Alt+s` -- Cycles through stretch modes\n"
+"| `Alt+Enter`   -- Toggles full screen/windowed mode\n"
+"| `Alt+s`          -- Takes a screenshot\n"
+"| `Ctrl+F7`       -- Opens virtual keyboard (if enabled). This can also be opened with a long press of the middle mouse button or wheel.\n"
+"| `Ctrl+Alt+d` -- Opens the ScummVM debugger\n"
+),
+
+0,
+	};
+
+const char **OSystem_SDL::buildHelpDialogData() {
+	return helpTabs;
+}
diff --git a/backends/platform/sdl/sdl.h b/backends/platform/sdl/sdl.h
index ca74a05dd81..2ba69f78de0 100644
--- a/backends/platform/sdl/sdl.h
+++ b/backends/platform/sdl/sdl.h
@@ -191,6 +191,7 @@ protected:
 #endif
 
 	virtual uint32 getOSDoubleClickTime() const { return 0; }
+	virtual const char **buildHelpDialogData() override;
 };
 
 #endif
diff --git a/gui/helpdialog.cpp b/gui/helpdialog.cpp
index fd2ca6869fb..70e01f4dc14 100644
--- a/gui/helpdialog.cpp
+++ b/gui/helpdialog.cpp
@@ -170,39 +170,6 @@ HelpDialog::HelpDialog()
 	RichTextWidget *saftext = new RichTextWidget(tab, 10, 10, _w - 10, tabHeight - buttonHeight - 10, helpText3);
 	saftext->setImageArchive("android-help.zip");
 
-	tab->addTab(_("Keyboard"), "GlobalOptions_Graphics", false);
-
-	Common::U32String helpText4 = _(
-"## Keyboard shortcuts\n"
-"\n"
-"ScummVM supports various in-game keyboard and mouse shortcuts, and since version 2.2.0 these can be manually configured in the **Keymaps tab**, or in the **configuration file**.\n"
-"\n"
-"For game-specific controls, see the [wiki entry](https://wiki.scummvm.org/index.php?title=Category:Supported_Games) for the game you are playing.\n"
-"\n"
-"Default shortcuts are shown in the table.\n"
-"\n"
-"| Shortcut      | Description\n"
-"| --------------------------------\n"
-"| `Ctrl+F5` -- Displays the Global Main Menu\n"
-"| `Cmd+q`    -- Quit (macOS)\n"
-"| `Ctrl+q`  -- Quit (Linux/Unix)\n"
-"| `Alt+F4`  -- Quit (Windows)\n"
-"| `Ctrl+z`  -- Quit (other platforms)\n"
-"| `Ctrl+u`  -- Mutes all sounds\n"
-"| `Ctrl+m`  -- Toggles mouse capture\n"
-"| `Ctrl+Alt` and `9` or `0` -- Cycles forwards/backwards between graphics filters\n"
-"| `Ctrl+Alt` and `+` or `-` -- Increases/decreases the scale factor\n"
-"| `Ctrl+Alt+a` -- Toggles aspect ratio correction on/off\n"
-"| `Ctrl+Alt+f` -- Toggles between nearest neighbor and bilinear interpolation (graphics filtering on/off)\n"
-"| `Ctrl+Alt+s` -- Cycles through stretch modes\n"
-"| `Alt+Enter`   -- Toggles full screen/windowed mode\n"
-"| `Alt+s`          -- Takes a screenshot\n"
-"| `Ctrl+F7`       -- Opens virtual keyboard (if enabled). This can also be opened with a long press of the middle mouse button or wheel.\n"
-"| `Ctrl+Alt+d` -- Opens the ScummVM debugger\n"
-	);
-
-	new RichTextWidget(tab, 10, 10, _w - 10, tabHeight - buttonHeight - 10, helpText4);
-
 	const char **backendTabs = g_system->buildHelpDialogData();
 
 	if (backendTabs) {
diff --git a/po/POTFILES b/po/POTFILES
index cb7f58096d4..ae04aa61b55 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -107,6 +107,7 @@ backends/platform/ios7/ios7_options.mm
 backends/platform/ios7/ios7_osys_events.cpp
 backends/platform/ios7/ios7_osys_main.cpp
 backends/platform/maemo/maemo.cpp
+backends/platform/sdl/sdl.cpp
 backends/platform/sdl/macosx/appmenu_osx.mm
 backends/platform/sdl/macosx/macosx.cpp
 backends/platform/sdl/miyoo/miyoo.cpp


Commit: d4d82be3e7152b242850e5bdb57a518c572fad0a
    https://github.com/scummvm/scummvm/commit/d4d82be3e7152b242850e5bdb57a518c572fad0a
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
BACKENDS: SDL: Specified platform-dependent quit shortcut in help dialog

Changed paths:
    backends/platform/sdl/sdl.cpp


diff --git a/backends/platform/sdl/sdl.cpp b/backends/platform/sdl/sdl.cpp
index 8567e4f1239..172963d8345 100644
--- a/backends/platform/sdl/sdl.cpp
+++ b/backends/platform/sdl/sdl.cpp
@@ -1020,11 +1020,16 @@ _s(
 "\n"
 "| Shortcut      | Description\n"
 "| --------------------------------\n"
-"| `Ctrl+F5` -- Displays the Global Main Menu\n"
-"| `Cmd+q`    -- Quit (macOS)\n"
-"| `Ctrl+q`  -- Quit (Linux/Unix)\n"
-"| `Alt+F4`  -- Quit (Windows)\n"
-"| `Ctrl+z`  -- Quit (other platforms)\n"
+"| `Ctrl+F5` -- Displays the Global Main Menu\n")
+#ifdef MACOSX
+_s("| `Cmd+q`    -- Quit (macOS)\n")
+#elif WIN32
+_s("| `Alt+F4`  -- Quit (Windows)\n")
+#else
+_s("| `Ctrl+q`  -- Quit (Linux/Unix)\n")
+_s("| `Ctrl+z`  -- Quit (other platforms)\n")
+#endif
+_s(
 "| `Ctrl+u`  -- Mutes all sounds\n"
 "| `Ctrl+m`  -- Toggles mouse capture\n"
 "| `Ctrl+Alt` and `9` or `0` -- Cycles forwards/backwards between graphics filters\n"


Commit: 7527757d6fdf0c5aa091697a579e3e77b6bd645d
    https://github.com/scummvm/scummvm/commit/7527757d6fdf0c5aa091697a579e3e77b6bd645d
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
BACKENDS: ANDROID: Move Android-specific HelpDialog tabs to backend

Changed paths:
    backends/platform/android/android.cpp
    backends/platform/android/android.h
    gui/helpdialog.cpp
    po/POTFILES


diff --git a/backends/platform/android/android.cpp b/backends/platform/android/android.cpp
index e89dd8f84d4..d5b0ce83bb9 100644
--- a/backends/platform/android/android.cpp
+++ b/backends/platform/android/android.cpp
@@ -1024,4 +1024,112 @@ void *OSystem_Android::getOpenGLProcAddress(const char *name) const {
 }
 #endif
 
+static const char *helpTabs[] = {
+_s("Controls"),
+"",
+_s(
+"## Touch controls\n"
+"\n"
+"The touch control scheme can be configured in the global settings. From the Launcher, go to **Options > Backend > Choose the preferred touch mode**.\n"
+"It's possible to configure the touch mode for three situations (ScummVM menus, 2D games and 3D games) and choose one of the three possible modes:\n"
+"\n"
+" * Direct mouse, the touch controls are direct. The pointer jumps to where the finger touches the screen (default for menus).\n"
+" * Touchpad emulation, the touch controls are indirect. The finger can be far away from the pointer and still move it, like on a laptop touchpad.\n"
+" * Gamepad emulation, the touch controls don't move any mouse. The fingers must be placed on lower left and right of the screen and respectively emulate a directional pad and action buttons.\n"
+" * The pointer speed setting in the **Controls tab** affects how far the pointer moves in response to a finger movement.\n"
+"\n"
+"The touch mode can be switched at anytime by tapping on the controller icon, next to the menu icon at the top right of the screen.\n"
+"\n"
+"To display or hide the small controller icon, from the Launcher select **Options** and then the **Backend** tab. Tick the **Show on-screen control** box to enable the controller icon.\n"
+"\n"
+"## Two finger tap\n"
+"\n"
+"To do a two finger tap, hold one finger down and then tap with a second finger.\n"
+"\n"
+"## Three finger tap\n"
+"\n"
+"To do a three finger tap, start with holding down one finger and progressively touch down the other two fingers, one at a time, while still holding down the previous fingers. Imagine you are impatiently tapping your fingers on a surface, but then slow down that movement so it is rhythmic, but not too slow.\n"
+"\n"
+"## Immersive Sticky fullscreen mode\n"
+"\n"
+"ScummVM for Android uses the Immersive Sticky fullscreen mode, which means that the Android system bar is hidden until the user swipes from an edge with a system bar. Swipe from the edge to reveal the system bars.  They remain semi-transparent and disappear after a few seconds unless you interact with them. Your swipe also registers in the game, so if you need to swipe from an edge with system bars, your game play is not interrupted.\n"
+"\n"
+"## Global Main Menu\n"
+"\n"
+"To open the Global Main Menu, tap on the small menu icon at the top right of the screen.\n"
+"\n"
+"To display or hide the small menu icon, from the Launcher select **Options** and then the **Backend** tab. Tick the **Show on-screen control** box to enable the menu icon.\n"
+"\n"
+"## Virtual keyboard\n"
+"\n"
+"To open the virtual keyboard, long press on the small controller icon at the top right of the screen, or tap on any editable text field. To hide the virtual keyboard, tap the small controller icon (which became a keyboard one) again, or tap outside the text field.\n"
+"\n"
+	),
+
+_s("Adding Games"),
+"android-help.zip",
+_s(
+"## Adding SAF paths to ScummVM directory list\n"
+"\n"
+"Starting with version 2.7.0 of ScummVM for Android, significant changes were made to the file access system to allow support for modern versions of the Android Operating System.\n"
+"\n"
+"If you find that your existing added games or custom paths no longer work, please edit those paths and this time use the SAF system to browse to the desired locations.\n"
+"\n"
+"To do that:\n"
+"\n"
+"  1. For each game whose data is not found, go to the \"Paths\" tab in the \"Game Options\" and change the \"Game path\"\n"
+"\n"
+"  2. Inside the ScummVM file browser, use \"Go Up\" until you reach the \"root\" folder where you will see the \"<Add a new folder>\" option.\n"
+"\n"
+"  ![File browser](browser-root.png \"Android browser\")\n"
+"\n"
+"    File Browser root with <Add a new folder> item\n"
+"\n"
+"  3. Choose that, then browse and select the \"parent\" folder for your games subfolders, e.g. \"SD Card > ScummVMgames\". Click on \"Use this folder\".\n"
+"\n"
+"  ![OS file browser root](fs-root.png \"OS file browser root\")\n"
+"\n"
+"    OS file browser root\n"
+"\n"
+"  ![OS selectable folder](fs-folder.png \"OS selectable folder\")\n"
+"\n"
+"    OS file browser selectable folder with \"Use this folder\" button\n"
+"\n"
+"  ![OS access permission dialog](fs-permission.png \"OS access permission\")\n"
+"\n"
+"    OS file browser ask to grant ScummVM directory access permission\n"
+"\n"
+"  4. Then, a new folder \"ScummVMgames\" will appear on the \"root\" folder of the ScummVM browser.\n"
+"\n"
+"  ![SAF folder added](browser-folder-in-list.png \"SAF folder added\")\n"
+"\n"
+"    File browser with added SAF folder in root\n"
+"\n"
+"  5. Browse through this folder to your game data.\n"
+"\n"
+"Steps 2 and 3 need to be done only once for all of your games.\n"
+"\n"
+"\n"
+"## Removing SAF path authorizations\n"
+"\n"
+"In case you would like to revoke any of the granted SAF authorizations, there is an option for this in the \"Global Options > Backend\" tab as shown on the screenshot below:\n"
+"\n"
+"![\"Remove folder authorizations...\" button](gui-remove-permissions.png \"'Remove folder authorizations...' button\")\n"
+"\n"
+"    GUI tab with \"Remove folder authorizations...\" button\n"
+"\n"
+"![List of authorizations to revoked](gui-remove-list.png \"List of authorizations to revoke\")\n"
+"\n"
+"    GUI dialog with list of authorizations to revoke\n"
+"\n"
+"In case you revoke authorization to a path, still used for specific games/titles, please follow the procedure of fixing them outlined in the previous subheading.\n"
+	),
+
+0 // End of list
+};
+
+const char **OSystem_Android::buildHelpDialogData() {
+	return helpTabs;
+}
+
 #endif
diff --git a/backends/platform/android/android.h b/backends/platform/android/android.h
index 34a976078e4..6c57b6dfd06 100644
--- a/backends/platform/android/android.h
+++ b/backends/platform/android/android.h
@@ -262,6 +262,7 @@ public:
 	bool isRunningInMainThread() { return pthread_self() == _main_thread; }
 #endif
 
+	virtual const char **buildHelpDialogData() override;
 };
 
 #endif
diff --git a/gui/helpdialog.cpp b/gui/helpdialog.cpp
index 70e01f4dc14..2e3f6184caf 100644
--- a/gui/helpdialog.cpp
+++ b/gui/helpdialog.cpp
@@ -65,111 +65,8 @@ HelpDialog::HelpDialog()
 
 	new RichTextWidget(tab, 10, 10, _w - 10, tabHeight - buttonHeight - 10, helpText1);
 
-	tab->addTab(_("Controls"), "GlobalOptions_Graphics", false);
-
-	Common::U32String helpText2 = _(
-"## Touch controls\n"
-"\n"
-"The touch control scheme can be configured in the global settings. From the Launcher, go to **Options > Backend > Choose the preferred touch mode**.\n"
-"It's possible to configure the touch mode for three situations (ScummVM menus, 2D games and 3D games) and choose one of the three possible modes:\n"
-"\n"
-" * Direct mouse, the touch controls are direct. The pointer jumps to where the finger touches the screen (default for menus).\n"
-" * Touchpad emulation, the touch controls are indirect. The finger can be far away from the pointer and still move it, like on a laptop touchpad.\n"
-" * Gamepad emulation, the touch controls don't move any mouse. The fingers must be placed on lower left and right of the screen and respectively emulate a directional pad and action buttons.\n"
-" * The pointer speed setting in the **Controls tab** affects how far the pointer moves in response to a finger movement.\n"
-"\n"
-"The touch mode can be switched at anytime by tapping on the controller icon, next to the menu icon at the top right of the screen.\n"
-"\n"
-"To display or hide the small controller icon, from the Launcher select **Options** and then the **Backend** tab. Tick the **Show on-screen control** box to enable the controller icon.\n"
-"\n"
-"## Two finger tap\n"
-"\n"
-"To do a two finger tap, hold one finger down and then tap with a second finger.\n"
-"\n"
-"## Three finger tap\n"
-"\n"
-"To do a three finger tap, start with holding down one finger and progressively touch down the other two fingers, one at a time, while still holding down the previous fingers. Imagine you are impatiently tapping your fingers on a surface, but then slow down that movement so it is rhythmic, but not too slow.\n"
-"\n"
-"## Immersive Sticky fullscreen mode\n"
-"\n"
-"ScummVM for Android uses the Immersive Sticky fullscreen mode, which means that the Android system bar is hidden until the user swipes from an edge with a system bar. Swipe from the edge to reveal the system bars.  They remain semi-transparent and disappear after a few seconds unless you interact with them. Your swipe also registers in the game, so if you need to swipe from an edge with system bars, your game play is not interrupted.\n"
-"\n"
-"## Global Main Menu\n"
-"\n"
-"To open the Global Main Menu, tap on the small menu icon at the top right of the screen.\n"
-"\n"
-"To display or hide the small menu icon, from the Launcher select **Options** and then the **Backend** tab. Tick the **Show on-screen control** box to enable the menu icon.\n"
-"\n"
-"## Virtual keyboard\n"
-"\n"
-"To open the virtual keyboard, long press on the small controller icon at the top right of the screen, or tap on any editable text field. To hide the virtual keyboard, tap the small controller icon (which became a keyboard one) again, or tap outside the text field.\n"
-"\n"
-	);
-
-	new RichTextWidget(tab, 10, 10, _w - 10, tabHeight - buttonHeight - 10, helpText2);
-
-	tab->addTab(_("Adding Games"), "GlobalOptions_Graphics", false);
-
-	Common::U32String helpText3 = _(
-"## Adding SAF paths to ScummVM directory list\n"
-"\n"
-"Starting with version 2.7.0 of ScummVM for Android, significant changes were made to the file access system to allow support for modern versions of the Android Operating System.\n"
-"\n"
-"If you find that your existing added games or custom paths no longer work, please edit those paths and this time use the SAF system to browse to the desired locations.\n"
-"\n"
-"To do that:\n"
-"\n"
-"  1. For each game whose data is not found, go to the \"Paths\" tab in the \"Game Options\" and change the \"Game path\"\n"
-"\n"
-"  2. Inside the ScummVM file browser, use \"Go Up\" until you reach the \"root\" folder where you will see the \"<Add a new folder>\" option.\n"
-"\n"
-"  ![File browser](browser-root.png \"Android browser\")\n"
-"\n"
-"    File Browser root with <Add a new folder> item\n"
-"\n"
-"  3. Choose that, then browse and select the \"parent\" folder for your games subfolders, e.g. \"SD Card > ScummVMgames\". Click on \"Use this folder\".\n"
-"\n"
-"  ![OS file browser root](fs-root.png \"OS file browser root\")\n"
-"\n"
-"    OS file browser root\n"
-"\n"
-"  ![OS selectable folder](fs-folder.png \"OS selectable folder\")\n"
-"\n"
-"    OS file browser selectable folder with \"Use this folder\" button\n"
-"\n"
-"  ![OS access permission dialog](fs-permission.png \"OS access permission\")\n"
-"\n"
-"    OS file browser ask to grant ScummVM directory access permission\n"
-"\n"
-"  4. Then, a new folder \"ScummVMgames\" will appear on the \"root\" folder of the ScummVM browser.\n"
-"\n"
-"  ![SAF folder added](browser-folder-in-list.png \"SAF folder added\")\n"
-"\n"
-"    File browser with added SAF folder in root\n"
-"\n"
-"  5. Browse through this folder to your game data.\n"
-"\n"
-"Steps 2 and 3 need to be done only once for all of your games.\n"
-"\n"
-"\n"
-"## Removing SAF path authorizations\n"
-"\n"
-"In case you would like to revoke any of the granted SAF authorizations, there is an option for this in the \"Global Options > Backend\" tab as shown on the screenshot below:\n"
-"\n"
-"![\"Remove folder authorizations...\" button](gui-remove-permissions.png \"'Remove folder authorizations...' button\")\n"
-"\n"
-"    GUI tab with \"Remove folder authorizations...\" button\n"
-"\n"
-"![List of authorizations to revoked](gui-remove-list.png \"List of authorizations to revoke\")\n"
-"\n"
-"    GUI dialog with list of authorizations to revoke\n"
-"\n"
-"In case you revoke authorization to a path, still used for specific games/titles, please follow the procedure of fixing them outlined in the previous subheading.\n"
-	);
-
-	RichTextWidget *saftext = new RichTextWidget(tab, 10, 10, _w - 10, tabHeight - buttonHeight - 10, helpText3);
-	saftext->setImageArchive("android-help.zip");
 
+	// Now add backend-specific tabs if any
 	const char **backendTabs = g_system->buildHelpDialogData();
 
 	if (backendTabs) {
diff --git a/po/POTFILES b/po/POTFILES
index ae04aa61b55..70b89741882 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -100,6 +100,7 @@ backends/networking/sdl_net/localwebserver.cpp
 backends/networking/sdl_net/uploadfileclienthandler.cpp
 backends/platform/3ds/options-dialog.cpp
 backends/platform/3ds/osystem-events.cpp
+backends/platform/android/android.cpp
 backends/platform/android/options.cpp
 backends/platform/ds/ds-graphics.cpp
 backends/platform/ds/osystem_ds.cpp


Commit: bf7c041b36f3519c4a6e04ae7c60f726bec19dd2
    https://github.com/scummvm/scummvm/commit/bf7c041b36f3519c4a6e04ae7c60f726bec19dd2
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Switch to the first tab upon opening HelpDialog

Changed paths:
    gui/helpdialog.cpp


diff --git a/gui/helpdialog.cpp b/gui/helpdialog.cpp
index 2e3f6184caf..75df9fbee0d 100644
--- a/gui/helpdialog.cpp
+++ b/gui/helpdialog.cpp
@@ -90,6 +90,8 @@ HelpDialog::HelpDialog()
 		}
 	}
 
+	 tab->setActiveTab(0);
+
 	new ButtonWidget(this, _w - buttonWidth - 10, _h - buttonHeight - 10, buttonWidth, buttonHeight, Common::U32String("Close"), Common::U32String(), kCloseCmd);
 }
 


Commit: 1d636a1687cafee3cdc3512b0411363fc38563c7
    https://github.com/scummvm/scummvm/commit/1d636a1687cafee3cdc3512b0411363fc38563c7
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
OSYSTEM: Described format for buildHelpDialogData() call

Changed paths:
    common/system.h


diff --git a/common/system.h b/common/system.h
index c05c8a427d5..5dfd141ba67 100644
--- a/common/system.h
+++ b/common/system.h
@@ -1831,6 +1831,13 @@ public:
 
 	/**
 	 * Return list of strings used for building help dialog
+	 *
+	 * The strings represented in triplets:
+	 *   - Name of a tab (will be translated)
+	 *   - ZIP pack name with images (optional)
+	 *   - Text of the tab with Markdown formatting (also be translated)
+	 *
+	 * The string list is null-terminated.
 	 */
 	 virtual const char **buildHelpDialogData() { return nullptr; }
 


Commit: 9cd36c7032c1da98179af4fc52a3bc6607833373
    https://github.com/scummvm/scummvm/commit/9cd36c7032c1da98179af4fc52a3bc6607833373
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Fixed warning when constructing HelpDialog

Changed paths:
    gui/helpdialog.cpp


diff --git a/gui/helpdialog.cpp b/gui/helpdialog.cpp
index 75df9fbee0d..51b19629a21 100644
--- a/gui/helpdialog.cpp
+++ b/gui/helpdialog.cpp
@@ -50,7 +50,7 @@ HelpDialog::HelpDialog()
 
 	TabWidget *tab = new TabWidget(this, 10, 10, _w - 10, tabHeight);
 
-	tab->addTab(_("General"), "GlobalOptions_Graphics", false);
+	tab->addTab(_("General"), "HelpDialog", false);
 	Common::U32String helpText1 = _(
 "## Where to get the games\n"
 "\n"
@@ -81,7 +81,7 @@ HelpDialog::HelpDialog()
 
 			Common::U32String tabText(_(*backendTabs++));
 
-			tab->addTab(tabName, "GlobalOptions_Graphics", false);
+			tab->addTab(tabName, "HelpDialog", false);
 
 			RichTextWidget *rt = new RichTextWidget(tab, 10, 10, _w - 10, tabHeight - buttonHeight - 10, tabText);
 


Commit: a14b2bf9ba3e8a28d0569a4a0928561a8caeb4af
    https://github.com/scummvm/scummvm/commit/a14b2bf9ba3e8a28d0569a4a0928561a8caeb4af
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
ANDROID: Added andriod-help.zip to the bundle

Changed paths:
    backends/platform/android/android.mk


diff --git a/backends/platform/android/android.mk b/backends/platform/android/android.mk
index 8ba13deaa87..00060e9e83c 100644
--- a/backends/platform/android/android.mk
+++ b/backends/platform/android/android.mk
@@ -12,6 +12,8 @@ PATH_BUILD_LIBSCUMMVM = $(PATH_BUILD)/lib/$(ABI)/libscummvm.so
 APK_MAIN = ScummVM-debug.apk
 APK_MAIN_RELEASE = ScummVM-release-unsigned.apk
 
+DIST_FILES_HELP = $(PATH_DIST)/android-help.zip
+
 $(PATH_BUILD):
 	$(MKDIR) $(PATH_BUILD)
 
@@ -25,9 +27,9 @@ $(PATH_BUILD_GRADLE): $(GRADLE_FILES) | $(PATH_BUILD)
 	$(ECHO) "android.enableJetifier=true\n" >> $(PATH_BUILD)/gradle.properties
 	$(ECHO) "sdk.dir=$(realpath $(ANDROID_SDK_ROOT))\n" > $(PATH_BUILD)/local.properties
 
-$(PATH_BUILD_ASSETS): $(DIST_FILES_THEMES) $(DIST_FILES_ENGINEDATA) $(DIST_FILES_NETWORKING) $(DIST_FILES_VKEYBD) $(DIST_FILES_DOCS) | $(PATH_BUILD)
+$(PATH_BUILD_ASSETS): $(DIST_FILES_THEMES) $(DIST_FILES_ENGINEDATA) $(DIST_FILES_NETWORKING) $(DIST_FILES_VKEYBD) $(DIST_FILES_DOCS) $(DIST_FILES_HELP) | $(PATH_BUILD)
 	$(INSTALL) -d $(PATH_BUILD_ASSETS)
-	$(INSTALL) -c -m 644 $(DIST_FILES_THEMES) $(DIST_FILES_ENGINEDATA) $(DIST_FILES_NETWORKING) $(DIST_FILES_VKEYBD) $(DIST_FILES_DOCS) $(PATH_BUILD_ASSETS)/
+	$(INSTALL) -c -m 644 $(DIST_FILES_THEMES) $(DIST_FILES_ENGINEDATA) $(DIST_FILES_NETWORKING) $(DIST_FILES_VKEYBD) $(DIST_FILES_DOCS) $(DIST_FILES_HELP) $(PATH_BUILD_ASSETS)/
 ifneq ($(DIST_FILES_SHADERS),)
 	$(INSTALL) -d $(PATH_BUILD_ASSETS)/shaders
 	$(INSTALL) -c -m 644 $(DIST_FILES_SHADERS) $(PATH_BUILD_ASSETS)/shaders


Commit: 005609b5602f9eb2da1055505738c669c3058aa0
    https://github.com/scummvm/scummvm/commit/005609b5602f9eb2da1055505738c669c3058aa0
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Add link to compatibility page in the gnereal help tab

Changed paths:
    gui/helpdialog.cpp


diff --git a/gui/helpdialog.cpp b/gui/helpdialog.cpp
index 51b19629a21..e3994bbb207 100644
--- a/gui/helpdialog.cpp
+++ b/gui/helpdialog.cpp
@@ -54,7 +54,7 @@ HelpDialog::HelpDialog()
 	Common::U32String helpText1 = _(
 "## Where to get the games\n"
 "\n"
-"Many games supported by ScummVM can still be bought from companies at the links below. Not all games on this list are supported by ScummVM, please check the compatibility page beforehand.\n"
+"Many games supported by ScummVM can still be bought from companies at the links below. Not all games on this list are supported by ScummVM, please check [the compatibility page](https://scummvm.org/compatibility) beforehand.\n"
 "\n"
 "Several games have been released for free legal download by their respective copyright holders. You can download them from [our website](https://scummvm.org/games).\n"
 "\n"


Commit: b45e812a130a0eb0a50ab2dbb84cf28fca8414ec
    https://github.com/scummvm/scummvm/commit/b45e812a130a0eb0a50ab2dbb84cf28fca8414ec
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
GUI: Reworded help tabe "where to get games"

Changed paths:
    gui/helpdialog.cpp


diff --git a/gui/helpdialog.cpp b/gui/helpdialog.cpp
index e3994bbb207..d7ceea96976 100644
--- a/gui/helpdialog.cpp
+++ b/gui/helpdialog.cpp
@@ -54,13 +54,23 @@ HelpDialog::HelpDialog()
 	Common::U32String helpText1 = _(
 "## Where to get the games\n"
 "\n"
-"Many games supported by ScummVM can still be bought from companies at the links below. Not all games on this list are supported by ScummVM, please check [the compatibility page](https://scummvm.org/compatibility) beforehand.\n"
+"Many games supported by ScummVM can still be bought from companies at the links on "
+"[our Wiki](https://wiki.scummvm.org/index.php?title=Where_to_get_the_games).\n"
 "\n"
-"Several games have been released for free legal download by their respective copyright holders. You can download them from [our website](https://scummvm.org/games).\n"
+"Several games have been released for free legal download by their respective copyright "
+"holders. You can download them from [our website](https://scummvm.org/games).\n"
 "\n"
-"For other (out of print) games try Amazon, eBay, Game Trading Zone or other auction sites but beware of faulty games (e.g., scratched discs) and illegal game copies (e.g., from Butterfly Media).\n"
+"Also, we maintain a comprehensive [list of downloadable demos](https://www.scummvm.org/demos/).\n"
 "\n"
-"The ScummVM team does not recommend any individual supplier of games and this list is for reference purposes only. The ScummVM project does get a cut from every purchase on [GOG.com](https://www.gog.com/?pp=22d200f8670dbdb3e253a90eee5098477c95c23d) and [ZOOM-Platform](https://www.zoom-platform.com/?affiliate=c049516c-9c4c-42d6-8649-92ed870e8b53) through one of the links with the added affiliate referrer though.\n"
+"For other (out of print) games try Amazon, eBay, Game Trading Zone or other auction "
+"sites but beware of faulty games (e.g., scratched discs) and illegal game copies "
+"(e.g., from Butterfly Media).\n"
+"\n"
+"The ScummVM team does not recommend any individual supplier of games and thesr lists are "
+"for reference purposes only. However, the ScummVM project does get a cut from every purchase on "
+"[GOG.com](https://www.gog.com/?pp=22d200f8670dbdb3e253a90eee5098477c95c23d) and "
+"[ZOOM-Platform](https://www.zoom-platform.com/?affiliate=c049516c-9c4c-42d6-8649-92ed870e8b53) "
+"through one of the links with the added affiliate referrer though.\n"
 	);
 
 	new RichTextWidget(tab, 10, 10, _w - 10, tabHeight - buttonHeight - 10, helpText1);


Commit: 2bea7912782700f14b3058a2ee677d3353511a40
    https://github.com/scummvm/scummvm/commit/2bea7912782700f14b3058a2ee677d3353511a40
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-09-17T00:56:28+02:00

Commit Message:
OSYSTEM: Improed const'ness of the buildHelpDialogData() call

Changed paths:
    backends/platform/android/android.cpp
    backends/platform/android/android.h
    backends/platform/sdl/sdl.cpp
    backends/platform/sdl/sdl.h
    common/system.h
    gui/helpdialog.cpp


diff --git a/backends/platform/android/android.cpp b/backends/platform/android/android.cpp
index d5b0ce83bb9..3ef4438434f 100644
--- a/backends/platform/android/android.cpp
+++ b/backends/platform/android/android.cpp
@@ -1024,7 +1024,7 @@ void *OSystem_Android::getOpenGLProcAddress(const char *name) const {
 }
 #endif
 
-static const char *helpTabs[] = {
+static const char * const helpTabs[] = {
 _s("Controls"),
 "",
 _s(
@@ -1128,7 +1128,7 @@ _s(
 0 // End of list
 };
 
-const char **OSystem_Android::buildHelpDialogData() {
+const char * const *OSystem_Android::buildHelpDialogData() {
 	return helpTabs;
 }
 
diff --git a/backends/platform/android/android.h b/backends/platform/android/android.h
index 6c57b6dfd06..7be989a4a77 100644
--- a/backends/platform/android/android.h
+++ b/backends/platform/android/android.h
@@ -262,7 +262,7 @@ public:
 	bool isRunningInMainThread() { return pthread_self() == _main_thread; }
 #endif
 
-	virtual const char **buildHelpDialogData() override;
+	virtual const char * const *buildHelpDialogData() override;
 };
 
 #endif
diff --git a/backends/platform/sdl/sdl.cpp b/backends/platform/sdl/sdl.cpp
index 172963d8345..63efd30edcb 100644
--- a/backends/platform/sdl/sdl.cpp
+++ b/backends/platform/sdl/sdl.cpp
@@ -1006,7 +1006,7 @@ void OSystem_SDL::clearGraphicsModes() {
 }
 #endif
 
-static const char *helpTabs[] = {
+static const char * const helpTabs[] = {
 _s("Keyboard"),
 "",
 _s(
@@ -1046,6 +1046,6 @@ _s(
 0,
 	};
 
-const char **OSystem_SDL::buildHelpDialogData() {
+const char * const *OSystem_SDL::buildHelpDialogData() {
 	return helpTabs;
 }
diff --git a/backends/platform/sdl/sdl.h b/backends/platform/sdl/sdl.h
index 2ba69f78de0..1315ae9b7fe 100644
--- a/backends/platform/sdl/sdl.h
+++ b/backends/platform/sdl/sdl.h
@@ -191,7 +191,7 @@ protected:
 #endif
 
 	virtual uint32 getOSDoubleClickTime() const { return 0; }
-	virtual const char **buildHelpDialogData() override;
+	virtual const char * const *buildHelpDialogData() override;
 };
 
 #endif
diff --git a/common/system.h b/common/system.h
index 5dfd141ba67..578c0eb6162 100644
--- a/common/system.h
+++ b/common/system.h
@@ -1839,7 +1839,7 @@ public:
 	 *
 	 * The string list is null-terminated.
 	 */
-	 virtual const char **buildHelpDialogData() { return nullptr; }
+	 virtual const char * const *buildHelpDialogData() { return nullptr; }
 
 	/**
 	 * Notify the backend that the settings editable from the game tab in the
diff --git a/gui/helpdialog.cpp b/gui/helpdialog.cpp
index d7ceea96976..6dff023fbea 100644
--- a/gui/helpdialog.cpp
+++ b/gui/helpdialog.cpp
@@ -77,7 +77,7 @@ HelpDialog::HelpDialog()
 
 
 	// Now add backend-specific tabs if any
-	const char **backendTabs = g_system->buildHelpDialogData();
+	const char * const *backendTabs = g_system->buildHelpDialogData();
 
 	if (backendTabs) {
 		while (*backendTabs) {




More information about the Scummvm-git-logs mailing list