Skip to content

Commit a623fdf

Browse files
committed
- 修复TypesettingTools#491: Lua删除崩溃,ObjectIndexWrite delete分支修正Lua栈操作 - 修复TypesettingTools#446: 样式重命名损坏,StyleRenamer添加modified_current标志 - 修复TypesettingTools#443: margin解析崩溃,std::from_chars替代boost::lexical_cast - 修复TypesettingTools#538: CPS字符计数错误,IGNORE_BLOCKS模式使用TokenizeDialogueBody - 新增6个回归测试用例验证#538修复
1 parent 16b6791 commit a623fdf

5 files changed

Lines changed: 49 additions & 23 deletions

File tree

libaegisub/common/character_count.cpp

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -84,24 +84,26 @@ size_t CharacterCount(std::string_view str, int ignore) {
8484
if ((ignore & agi::IGNORE_BLOCKS) == 0)
8585
return count_in_range(str, mask);
8686

87+
// 使用分词器正确处理 \N、\n、\h 等 ASS 转义序列
88+
auto tokens = agi::ass::TokenizeDialogueBody(str);
89+
agi::ass::MarkDrawings(str, tokens);
90+
8791
size_t characters = 0;
88-
while (!str.empty()) {
89-
auto pos = str.find('{');
90-
if (pos == str.npos) break;
91-
92-
// if there's no trailing }, the rest of the string counts as characters,
93-
// including the leading {
94-
auto end = str.find('}');
95-
if (end == str.npos) break;
96-
97-
if (pos > 0)
98-
characters += count_in_range(str.substr(0, pos), mask);
99-
str.remove_prefix(end + 1);
92+
size_t pos = 0;
93+
for (auto token : tokens) {
94+
if (token.type == agi::ass::DialogueTokenType::TEXT)
95+
characters += count_in_range(str.substr(pos, token.length), mask);
96+
else if (token.type == agi::ass::DialogueTokenType::LINE_BREAK) {
97+
// \h 是硬空格,非忽略空白时计为 1 个字符
98+
if (str[pos + 1] == 'h') {
99+
if (!(mask & U_GC_Z_MASK))
100+
characters += 1;
101+
}
102+
// \N 和 \n 是换行符,不计入字符数
103+
}
104+
pos += token.length;
100105
}
101106

102-
if (!str.empty())
103-
characters += count_in_range(str, mask);
104-
105107
return characters;
106108
}
107109

src/ass_dialogue.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include <libaegisub/string.h>
3737

3838
#include <algorithm>
39+
#include <charconv>
3940

4041
#include <boost/algorithm/string/predicate.hpp>
4142
#include <boost/lexical_cast.hpp>
@@ -111,8 +112,12 @@ void AssDialogue::Parse(std::string const& raw) {
111112
End = agi::Trim(tkn.next_tok());
112113
Style = tkn.next_str_trim();
113114
Actor = tkn.next_str_trim();
114-
for (int& margin : Margin)
115-
margin = std::clamp(boost::lexical_cast<int>(tkn.next_tok()), -9999, 99999);
115+
for (int& margin : Margin) {
116+
auto tok = agi::Trim(tkn.next_tok());
117+
int val = 0;
118+
std::from_chars(tok.data(), tok.data() + tok.size(), val);
119+
margin = std::clamp(val, -9999, 99999);
120+
}
116121
Effect = tkn.next_str_trim();
117122

118123
std::string text{tkn.next_tok().begin(), str.end()};

src/auto4_lua_assfile.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -478,9 +478,9 @@ namespace Automation4 {
478478
AssignLine(n - 1, std::move(e));
479479
}
480480
else {
481-
// delete
482-
lua_remove(L, 1);
483-
lua_remove(L, 1);
481+
// delete: 清空栈后压入索引,供 ObjectDelete 使用
482+
lua_settop(L, 0);
483+
lua_pushinteger(L, n);
484484
ObjectDelete(L);
485485
}
486486
}

src/dialog_style_editor.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,15 +114,18 @@ class StyleRenamer {
114114
agi::Context *c;
115115
bool found_any = false;
116116
bool do_replace = false;
117+
bool modified_current = false; ///< 当前行的 \r 标签是否被修改
117118
std::string source_name;
118119
std::string new_name;
119120

120-
/// Process a single override parameter to check if it's \r with this style name
121+
/// @brief 处理单个覆写参数,检查是否为引用目标样式的 \r 标签
121122
static void ProcessTag(std::string const& tag, AssOverrideParameter* param, void *userData) {
122123
StyleRenamer *self = static_cast<StyleRenamer*>(userData);
123124
if (tag == "\\r" && param->GetType() == VariableDataType::TEXT && param->Get<std::string>() == self->source_name) {
124-
if (self->do_replace)
125+
if (self->do_replace) {
125126
param->Set(self->new_name);
127+
self->modified_current = true;
128+
}
126129
else
127130
self->found_any = true;
128131
}
@@ -140,10 +143,11 @@ class StyleRenamer {
140143
found_any = true;
141144
}
142145

146+
modified_current = false;
143147
auto blocks = diag.ParseTags();
144148
for (auto block : blocks | agi::of_type<AssDialogueBlockOverride>())
145149
block->ProcessParameters(&StyleRenamer::ProcessTag, this);
146-
if (replace)
150+
if (replace && modified_current)
147151
diag.UpdateText(blocks);
148152

149153
if (found_any) return;

tests/tests/character_count.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,21 @@ TEST(lagi_character_count, ignore_blocks_unclosed) {
6767
EXPECT_EQ(6, agi::CharacterCount("{hello", agi::IGNORE_BLOCKS));
6868
}
6969

70+
/// \brief 验证 IGNORE_BLOCKS 模式下 ASS 转义序列的正确处理 (#538)
71+
TEST(lagi_character_count, ignore_blocks_line_breaks) {
72+
// \N 和 \n 是换行符,不计入字符数
73+
EXPECT_EQ(10, agi::CharacterCount("{\\b1}hello\\Nworld", agi::IGNORE_BLOCKS));
74+
EXPECT_EQ(10, agi::CharacterCount("{\\b1}hello\\nworld", agi::IGNORE_BLOCKS));
75+
// \h 是硬空格,计为 1 个字符
76+
EXPECT_EQ(11, agi::CharacterCount("{\\b1}hello\\hworld", agi::IGNORE_BLOCKS));
77+
// 忽略空白时 \h 不计入
78+
EXPECT_EQ(10, agi::CharacterCount("{\\b1}hello\\hworld", agi::IGNORE_BLOCKS | agi::IGNORE_WHITESPACE));
79+
// 多个连续换行
80+
EXPECT_EQ(10, agi::CharacterCount("{\\b1}hello\\N\\Nworld", agi::IGNORE_BLOCKS));
81+
// 文本中混合块和换行
82+
EXPECT_EQ(10, agi::CharacterCount("{\\b1}hello{\\i1}\\Nworld", agi::IGNORE_BLOCKS));
83+
}
84+
7085
TEST(lagi_character_count, line_length) {
7186
EXPECT_EQ(5, agi::MaxLineLength("hello", agi::IGNORE_NONE));
7287
EXPECT_EQ(5, agi::MaxLineLength("hello\\Nasdf", agi::IGNORE_NONE));

0 commit comments

Comments
 (0)