[Scummvm-git-logs] scummvm master -> 758e9a52bc676d2791bd501ed6561ff8224b30f5

neuromancer noreply at scummvm.org
Tue Aug 19 07:06:09 UTC 2025


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

Summary:
758e9a52bc PRIVATE: better support for parsing tags in subtitles


Commit: 758e9a52bc676d2791bd501ed6561ff8224b30f5
    https://github.com/scummvm/scummvm/commit/758e9a52bc676d2791bd501ed6561ff8224b30f5
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2025-08-19T09:08:11+02:00

Commit Message:
PRIVATE: better support for parsing tags in subtitles

Changed paths:
    video/subtitles.cpp
    video/subtitles.h


diff --git a/video/subtitles.cpp b/video/subtitles.cpp
index 1fb801d57f5..37b6077eccf 100644
--- a/video/subtitles.cpp
+++ b/video/subtitles.cpp
@@ -49,33 +49,51 @@ void SRTParser::cleanup() {
 	_entries.clear();
 }
 
-Common::String SRTParser::SRTParser::parseTag(Common::String &text) const {
-    // Parse tags in _subtitle
-    // <i> and </i> for italics
-    // <sfx> and </sfx> for sound effects
-
-    Common::String tag;
-    Common::String::size_type pos = text.find("<i>");
-    debug(1, "Subtitles: %s", text.c_str());
-    if (pos != Common::String::npos) {
-        text.erase(pos, 3);
-        pos = text.find("</i>");
-        if (pos != Common::String::npos) {
-            text.erase(pos, 4);
-        }
-        tag = "i";
-    } else {
-        pos = text.find("<sfx>");
-        if (pos != Common::String::npos) {
-            text.erase(pos, 5);
-            pos = text.find("</sfx>");
-            if (pos != Common::String::npos) {
-                text.erase(pos, 6);
-            }
-            tag = "sfx";
-        }
-    }
-    return tag;
+void SRTParser::parseTextAndTags(const Common::String &text, Common::Array<SubtitlePart> &parts) const {
+	Common::String currentText = text;
+	currentText.replace('\n', ' ');
+
+	while (true) {
+		Common::String::size_type pos_i_start = currentText.find("<i>");
+		Common::String::size_type pos_sfx_start = currentText.find("<sfx>");
+
+		Common::String::size_type first_tag_start = Common::String::npos;
+		Common::String start_tag, end_tag;
+		Common::String tag;
+
+		if (pos_i_start != Common::String::npos && (pos_sfx_start == Common::String::npos || pos_i_start < pos_sfx_start)) {
+			first_tag_start = pos_i_start;
+			start_tag = "<i>";
+			end_tag = "</i>";
+			tag = "i";
+		} else if (pos_sfx_start != Common::String::npos) {
+			first_tag_start = pos_sfx_start;
+			start_tag = "<sfx>";
+			end_tag = "</sfx>";
+			tag = "sfx";
+		}
+
+		if (first_tag_start == Common::String::npos) {
+			if (!currentText.empty()) {
+				parts.push_back(SubtitlePart(currentText, ""));
+			}
+			break;
+		}
+
+		if (first_tag_start > 0) {
+			parts.push_back(SubtitlePart(currentText.substr(0, first_tag_start), ""));
+		}
+
+		Common::String::size_type end_tag_pos = currentText.find(end_tag, first_tag_start);
+		if (end_tag_pos == Common::String::npos) {
+			parts.push_back(SubtitlePart(currentText.substr(first_tag_start + start_tag.size()), tag));
+			break;
+		}
+
+		parts.push_back(SubtitlePart(currentText.substr(first_tag_start + start_tag.size(), end_tag_pos - (first_tag_start + start_tag.size())), tag));
+
+		currentText = currentText.substr(end_tag_pos + end_tag.size());
+	}
 }
 
 bool parseTime(const char **pptr, uint32 *res) {
@@ -235,8 +253,9 @@ bool SRTParser::parseFile(const Common::Path &fname) {
 			break;
 		}
 
-		Common::String tag = parseTag(text);
-		_entries.push_back(new SRTEntry(seq, start, end, text, tag));
+		Common::Array<SubtitlePart> parts;
+		parseTextAndTags(text, parts);
+		_entries.push_back(new SRTEntry(seq, start, end, parts));
 	}
 
 	qsort(_entries.data(), _entries.size(), sizeof(SRTEntry *), &SRTEntryComparator);
@@ -246,28 +265,28 @@ bool SRTParser::parseFile(const Common::Path &fname) {
 	return true;
 }
 
-Common::String SRTParser::getSubtitle(uint32 timestamp) const {
+const Common::Array<SubtitlePart> *SRTParser::getSubtitleParts(uint32 timestamp) const {
 	SRTEntry test(0, timestamp, 0, "");
 	SRTEntry *testptr = &test;
 
 	const SRTEntry **entry = (const SRTEntry **)bsearch(&testptr, _entries.data(), _entries.size(), sizeof(SRTEntry *), &SRTEntryComparatorBSearch);
 
 	if (entry == NULL)
-		return "";
+		return nullptr;
 
-	return (*entry)->text;
+	return &(*entry)->parts;
 }
 
-Common::String SRTParser::getTag(uint32 timestamp) const {
-	SRTEntry test(0, timestamp, 0, "");
-	SRTEntry *testptr = &test;
-
-	const SRTEntry **entry = (const SRTEntry **)bsearch(&testptr, _entries.data(), _entries.size(), sizeof(SRTEntry *), &SRTEntryComparatorBSearch);
-
-	if (entry == NULL)
+Common::String SRTParser::getSubtitle(uint32 timestamp) const {
+	const Common::Array<SubtitlePart> *parts = getSubtitleParts(timestamp);
+	if (!parts)
 		return "";
 
-	return (*entry)->tag;
+	Common::String subtitle;
+	for (const auto &part : *parts) {
+		subtitle += part.text;
+	}
+	return subtitle;
 }
 
 #define SHADOW 1
@@ -348,11 +367,16 @@ void Subtitles::setPadding(uint16 horizontal, uint16 vertical) {
 }
 
 bool Subtitles::drawSubtitle(uint32 timestamp, bool force, bool showSFX) {
-	Common::String subtitle;
-	Common::String tag;
+	const Common::Array<SubtitlePart> *parts = nullptr;
 	if (_loaded) {
-		subtitle = _srtParser.getSubtitle(timestamp);
-		tag = _srtParser.getTag(timestamp);
+		parts = _srtParser.getSubtitleParts(timestamp);
+	}
+
+	Common::String subtitle;
+	if (parts) {
+		for (const auto &part : *parts) {
+			subtitle += part.text;
+		}
 	} else if (_subtitleDev) {
 		subtitle = _fname.toString('/');
 		uint32 hours, mins, secs, msecs;
@@ -404,12 +428,12 @@ bool Subtitles::drawSubtitle(uint32 timestamp, bool force, bool showSFX) {
 		return false;
 
 	if (force || subtitle != _subtitle) {
-		debug(1, "%d: %s with tag %s", timestamp, subtitle.c_str(), tag.c_str());
+		debug(1, "%d: %s", timestamp, subtitle.c_str());
 
 		_subtitle = subtitle;
-		_tag = tag;
+		_parts = parts;
 
-		if (_tag != "sfx" || showSFX)
+		if ((!parts || parts->empty() || (*parts)[0].tag != "sfx") || showSFX)
 			renderSubtitle();
 	}
 
@@ -431,51 +455,82 @@ bool Subtitles::drawSubtitle(uint32 timestamp, bool force, bool showSFX) {
 void Subtitles::renderSubtitle() const {
 	_surface->fillRect(Common::Rect(0, 0, _surface->w, _surface->h), _transparentColor);
 
-	Common::Array<Common::U32String> lines;
-	Common::String fontType = _tag == "i" ? "italic" : "regular";
-	const Graphics::Font *font = _fonts[fontType] ? _fonts[fontType] : _fonts["regular"];
-
-	font->wordWrapText(convertUtf8ToUtf32(_subtitle), _realBBox.width(), lines);
-
-	if (lines.empty()) {
+	if (!_parts || _parts->empty()) {
 		_drawRect.left = 0;
 		_drawRect.top = 0;
 		_drawRect.right = 0;
 		_drawRect.bottom = 0;
-
 		return;
 	}
 
-	int height = _vPad;
+	Common::Array<Common::Array<SubtitlePart>> lines;
+	lines.push_back(Common::Array<SubtitlePart>());
+
+	int currentLineWidth = 0;
+	for (const auto &part : *_parts) {
+		const Graphics::Font *font = _fonts[part.tag == "i" ? "italic" : "regular"];
+		if (!font) font = _fonts["regular"];
 
-	int width = 0;
-	for (uint i = 0; i < lines.size(); i++)
-		width = MAX(font->getStringWidth(lines[i]), width);
-	width = MIN(width + 2 * _hPad, (int)_realBBox.width());
+		Common::U32String u32_text(part.text);
+		int partWidth = font->getStringWidth(u32_text);
 
-	int originX = (_realBBox.width() - width) / 2;
+		if (currentLineWidth + partWidth > _realBBox.width()) {
+			lines.push_back(Common::Array<SubtitlePart>());
+			currentLineWidth = 0;
+		}
+		lines.back().push_back(part);
+		currentLineWidth += partWidth;
+	}
 
-	for (uint i = 0; i < lines.size(); i++) {
-		Common::U32String line = convertBiDiU32String(lines[i]).visual;
+	int height = _vPad;
+	int totalWidth = 0;
+
+	for (const auto &line : lines) {
+		int lineWidth = 0;
+		for (const auto &part : line) {
+			const Graphics::Font *font = _fonts[part.tag == "i" ? "italic" : "regular"];
+			if (!font) font = _fonts["regular"];
+			lineWidth += font->getStringWidth(convertUtf8ToUtf32(part.text));
+		}
+		totalWidth = MAX(totalWidth, lineWidth);
+	}
+	totalWidth = MIN(totalWidth + 2 * _hPad, (int)_realBBox.width());
+
+	for (const auto &line : lines) {
+		int lineWidth = 0;
+		for (const auto &part : line) {
+			const Graphics::Font *font = _fonts[part.tag == "i" ? "italic" : "regular"];
+			if (!font) font = _fonts["regular"];
+			lineWidth += font->getStringWidth(convertUtf8ToUtf32(part.text));
+		}
 
-		font->drawString(_surface, line, originX, height, width, _blackColor, Graphics::kTextAlignCenter);
-		font->drawString(_surface, line, originX + SHADOW * 2, height, width, _blackColor, Graphics::kTextAlignCenter);
-		font->drawString(_surface, line, originX, height + SHADOW * 2, width, _blackColor, Graphics::kTextAlignCenter);
-		font->drawString(_surface, line, originX + SHADOW * 2, height + SHADOW * 2, width, _blackColor, Graphics::kTextAlignCenter);
+		int originX = (_realBBox.width() - lineWidth) / 2;
+		int currentX = originX;
 
-		font->drawString(_surface, line, originX + SHADOW, height + SHADOW, width, _color, Graphics::kTextAlignCenter);
+		for (const auto &part : line) {
+			Common::String fontType = part.tag == "i" ? "italic" : "regular";
+			const Graphics::Font *font = _fonts[fontType] ? _fonts[fontType] : _fonts["regular"];
+			Common::U32String u32_text = convertBiDiU32String(Common::U32String(part.text)).visual;
+			int partWidth = font->getStringWidth(u32_text);
 
-		height += font->getFontHeight();
+			font->drawString(_surface, u32_text, currentX, height, partWidth, _blackColor, Graphics::kTextAlignLeft);
+			font->drawString(_surface, u32_text, currentX + SHADOW * 2, height, partWidth, _blackColor, Graphics::kTextAlignLeft);
+			font->drawString(_surface, u32_text, currentX, height + SHADOW * 2, partWidth, _blackColor, Graphics::kTextAlignLeft);
+			font->drawString(_surface, u32_text, currentX + SHADOW * 2, height + SHADOW * 2, partWidth, _blackColor, Graphics::kTextAlignLeft);
+			font->drawString(_surface, u32_text, currentX + SHADOW, height + SHADOW, partWidth, _color, Graphics::kTextAlignLeft);
 
+			currentX += partWidth;
+		}
+		height += _fonts["regular"]->getFontHeight();
 		if (height + _vPad > _realBBox.bottom)
 			break;
 	}
 
 	height += _vPad;
 
-	_drawRect.left = originX;
+	_drawRect.left = (_realBBox.width() - totalWidth) / 2;
 	_drawRect.top = 0;
-	_drawRect.setWidth(width + SHADOW * 2);
+	_drawRect.setWidth(totalWidth + SHADOW * 2);
 	_drawRect.setHeight(height + SHADOW * 2);
 }
 
diff --git a/video/subtitles.h b/video/subtitles.h
index 1f5aba94a31..ad771ab25cf 100644
--- a/video/subtitles.h
+++ b/video/subtitles.h
@@ -34,16 +34,28 @@ struct Surface;
 
 namespace Video {
 
+struct SubtitlePart {
+	Common::String text;
+	Common::String tag;
+
+	SubtitlePart(const Common::String &text_, const Common::String &tag_) : text(text_), tag(tag_) {}
+};
+
 struct SRTEntry {
 	uint seq;
 	uint32 start;
 	uint32 end;
 
-	Common::String text;
-	Common::String tag;
+	Common::Array<SubtitlePart> parts;
+
+	SRTEntry(uint seq_, uint32 start_, uint32 end_, const Common::Array<SubtitlePart> &parts_) {
+		seq = seq_; start = start_; end = end_; parts = parts_;
+	}
 
-	SRTEntry(uint seq_, uint32 start_, uint32 end_, Common::String text_, Common::String tag_ = "") {
-		seq = seq_; start = start_; end = end_; text = text_; tag = tag_;
+	// Dummy constructor for bsearch
+	SRTEntry(uint seq_, uint32 start_, uint32 end_, const Common::String &text, const Common::String &tag = "") {
+		seq = seq_; start = start_; end = end_;
+		parts.push_back(SubtitlePart(text, tag));
 	}
 };
 
@@ -54,9 +66,9 @@ public:
 
 	void cleanup();
 	bool parseFile(const Common::Path &fname);
-	Common::String parseTag(Common::String &text) const;
+	void parseTextAndTags(const Common::String &text, Common::Array<SubtitlePart> &parts) const;
+	const Common::Array<SubtitlePart> *getSubtitleParts(uint32 timestamp) const;
 	Common::String getSubtitle(uint32 timestamp) const;
-	Common::String getTag(uint32 timestamp) const;
 
 private:
 	Common::Array<SRTEntry *> _entries;
@@ -96,7 +108,7 @@ private:
 
 	Common::Path _fname;
 	mutable Common::String _subtitle;
-	mutable Common::String _tag;
+	mutable const Common::Array<SubtitlePart> *_parts;
 	uint32 _color;
 	uint32 _blackColor;
 	uint32 _transparentColor;




More information about the Scummvm-git-logs mailing list