[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