Add syntax highlighting for drawings and vector clips

The highlighting distinguishes drawing commands from coordinates, and
colors x and y coordinates in different colors to make coordinates
easier to visually parse. Furthermore, in cubic Bezier curves, it
underlines the coordinates which corresponds to endpoints of the curves.
This commit is contained in:
arch1t3cht 2022-11-02 19:24:42 +01:00
parent c7c874acc4
commit 7ddfef7517
8 changed files with 201 additions and 19 deletions

View File

@ -60,7 +60,11 @@ public:
case dt::ERROR: SetStyling(tok.length, ss::ERROR); break;
case dt::ARG: SetStyling(tok.length, ss::PARAMETER); break;
case dt::COMMENT: SetStyling(tok.length, ss::COMMENT); break;
case dt::DRAWING: SetStyling(tok.length, ss::DRAWING); break;
case dt::DRAWING_CMD:SetStyling(tok.length, ss::DRAWING_CMD);break;
case dt::DRAWING_X: SetStyling(tok.length, ss::DRAWING_X); break;
case dt::DRAWING_Y: SetStyling(tok.length, ss::DRAWING_Y); break;
case dt::DRAWING_ENDPOINT_X: SetStyling(tok.length, ss::DRAWING_ENDPOINT_X); break;
case dt::DRAWING_ENDPOINT_Y: SetStyling(tok.length, ss::DRAWING_ENDPOINT_Y); break;
case dt::TEXT: SetStyling(tok.length, ss::NORMAL); break;
case dt::TAG_NAME: SetStyling(tok.length, ss::TAG); break;
case dt::OPEN_PAREN: case dt::CLOSE_PAREN: case dt::ARG_SEP: case dt::TAG_START:
@ -72,6 +76,8 @@ public:
case dt::WHITESPACE:
if (ranges.size() && ranges.back().type == ss::PARAMETER)
SetStyling(tok.length, ss::PARAMETER);
else if (ranges.size() && ranges.back().type == ss::DRAWING_ENDPOINT_X)
SetStyling(tok.length, ss::DRAWING_ENDPOINT_X); // connect the underline between x and y of endpoints
else
SetStyling(tok.length, ss::NORMAL);
break;
@ -118,6 +124,64 @@ class WordSplitter {
}
}
void SplitDrawing(size_t &i) {
size_t starti = i;
// First, split into words
size_t dpos = pos;
size_t tlen = 0;
bool tokentype = text[pos] == ' ' || text[pos] == '\t';
while (tlen < tokens[i].length) {
bool newtype = text[dpos] == ' ' || text[dpos] == '\t';
if (newtype != tokentype) {
tokentype = newtype;
SwitchTo(i, tokentype ? dt::DRAWING_FULL : dt::WHITESPACE, tlen);
tokens[i].type = tokentype ? dt::WHITESPACE : dt::DRAWING_FULL;
tlen = 0;
}
++tlen;
++dpos;
}
// Then, label all the tokens
dpos = pos;
int num_coord = 0;
char lastcmd = ' ';
for (size_t j = starti; j <= i; j++) {
char c = text[dpos];
if (tokens[j].type == dt::WHITESPACE) {
} else if (c == 'm' || c == 'n' || c == 'l' || c == 's' || c == 'b' || c == 'p' || c == 'c') {
tokens[j].type = dt::DRAWING_CMD;
if (tokens[j].length != 1)
tokens[j].type = dt::ERROR;
if (num_coord % 2 != 0)
tokens[j].type = dt::ERROR;
lastcmd = c;
num_coord = 0;
} else {
bool valid = true;
for (size_t k = 0; k < tokens[j].length; k++) {
char c = text[dpos + k];
if (!((c >= '0' && c <= '9') || c == '.' || c == '-' || c == 'e')) {
valid = false;
}
}
if (!valid)
tokens[j].type = dt::ERROR;
else if (lastcmd == 'b' && num_coord % 6 >= 4)
tokens[j].type = num_coord % 2 == 0 ? dt::DRAWING_ENDPOINT_X : dt::DRAWING_ENDPOINT_Y;
else
tokens[j].type = num_coord % 2 == 0 ? dt::DRAWING_X : dt::DRAWING_Y;
++num_coord;
}
dpos += tokens[j].length;
}
}
public:
WordSplitter(std::string const& text, std::vector<DialogueToken> &tokens)
: text(text)
@ -131,6 +195,9 @@ public:
size_t len = tokens[i].length;
if (tokens[i].type == dt::TEXT)
SplitText(i);
else if (tokens[i].type == dt::DRAWING_FULL) {
SplitDrawing(i);
}
pos += len;
}
}
@ -163,9 +230,51 @@ void MarkDrawings(std::string const& str, std::vector<DialogueToken> &tokens) {
switch (tokens[i].type) {
case dt::TEXT:
if (in_drawing)
tokens[i].type = dt::DRAWING;
tokens[i].type = dt::DRAWING_FULL;
break;
case dt::TAG_NAME:
if (i + 3 < tokens.size() && (len == 4 || len == 5) && !strncmp(str.c_str() + pos + len - 4, "clip", 4)) {
if (tokens[i + 1].type != dt::OPEN_PAREN)
goto tag_p;
size_t drawing_start = 0;
size_t drawing_end = 0;
// Try to find a vector clip
for (size_t j = i + 2; j < tokens.size(); j++) {
if (tokens[j].type == dt::ARG_SEP) {
if (drawing_start) {
break; // More than two arguents - this is a rectangular clip
}
drawing_start = j + 1;
} else if (tokens[j].type == dt::CLOSE_PAREN) {
drawing_end = j;
break;
} else if (tokens[j].type != dt::WHITESPACE && tokens[j].type != dt::ARG) {
break;
}
}
if (!drawing_end)
goto tag_p;
if (!drawing_start)
drawing_start = i + 2;
if (drawing_end == drawing_start + 1)
goto tag_p;
// We found a clip between drawing_start and drawing_end. Now, join
// all the tokens into one and label it as a drawing.
size_t tokenlen = 0;
for (size_t j = drawing_start; j < drawing_end; j++) {
tokenlen += tokens[j].length;
}
tokens[drawing_start].length = tokenlen;
tokens[drawing_start].type = dt::DRAWING_FULL;
tokens.erase(tokens.begin() + drawing_start + 1, tokens.begin() + drawing_end);
last_ovr_end -= drawing_end - drawing_start - 1;
}
tag_p:
if (len != 1 || i + 1 >= tokens.size() || str[pos] != 'p')
break;
@ -199,7 +308,7 @@ void MarkDrawings(std::string const& str, std::vector<DialogueToken> &tokens) {
case dt::KARAOKE_VARIABLE: break;
case dt::LINE_BREAK: break;
default:
tokens[i].type = in_drawing ? dt::DRAWING : dt::TEXT;
tokens[i].type = in_drawing ? dt::DRAWING_FULL : dt::TEXT;
if (i > 0 && tokens[i - 1].type == tokens[i].type) {
tokens[i - 1].length += tokens[i].length;
tokens.erase(tokens.begin() + i);

View File

@ -39,7 +39,12 @@ namespace agi {
ERROR,
COMMENT,
WHITESPACE,
DRAWING,
DRAWING_FULL,
DRAWING_CMD,
DRAWING_X,
DRAWING_Y,
DRAWING_ENDPOINT_X,
DRAWING_ENDPOINT_Y,
KARAOKE_TEMPLATE,
KARAOKE_VARIABLE
};
@ -49,7 +54,11 @@ namespace agi {
enum {
NORMAL = 0,
COMMENT,
DRAWING,
DRAWING_CMD,
DRAWING_X,
DRAWING_Y,
DRAWING_ENDPOINT_X,
DRAWING_ENDPOINT_Y,
OVERRIDE,
PUNCTUATION,
TAG,

View File

@ -228,7 +228,9 @@
"Background" : {
"Brackets" : "",
"Comment" : "",
"Drawing" : "",
"Drawing Command" : "",
"Drawing X" : "",
"Drawing Y" : "",
"Error" : "rgb(255, 200, 200)",
"Karaoke Template" : "",
"Karaoke Variable" : "",
@ -241,7 +243,9 @@
"Bold" : {
"Brackets" : false,
"Comment" : true,
"Drawing" : true,
"Drawing Command" : true,
"Drawing X" : false,
"Drawing Y" : false,
"Error" : false,
"Karaoke Template" : true,
"Karaoke Variable" : true,
@ -251,9 +255,14 @@
"Slashes" : false,
"Tags" : true
},
"Underline": {
"Drawing Endpoint": true
},
"Brackets" : "rgb(20, 50, 255)",
"Comment" : "rgb(0,0,0)",
"Drawing" : "rgb(0,0,0)",
"Drawing Command" : "rgb(0,0,0)",
"Drawing X" : "rgb(90,40,40)",
"Drawing Y" : "rgb(40,90,40)",
"Error" : "rgb(200, 0, 0)",
"Karaoke Template" : "rgb(128, 0, 192)",
"Karaoke Variable" : "rgb(128, 0, 192)",

View File

@ -228,7 +228,9 @@
"Background" : {
"Brackets" : "",
"Comment" : "",
"Drawing" : "",
"Drawing Command" : "",
"Drawing X" : "",
"Drawing Y" : "",
"Error" : "rgb(255, 200, 200)",
"Karaoke Template" : "",
"Karaoke Variable" : "",
@ -241,7 +243,9 @@
"Bold" : {
"Brackets" : false,
"Comment" : true,
"Drawing" : true,
"Drawing Command" : true,
"Drawing X" : false,
"Drawing Y" : false,
"Error" : false,
"Karaoke Template" : true,
"Karaoke Variable" : true,
@ -251,9 +255,14 @@
"Slashes" : false,
"Tags" : true
},
"Underline": {
"Drawing Endpoint": true
},
"Brackets" : "rgb(20, 50, 255)",
"Comment" : "rgb(0,0,0)",
"Drawing" : "rgb(0,0,0)",
"Drawing Command" : "rgb(0,0,0)",
"Drawing X" : "rgb(90,40,40)",
"Drawing Y" : "rgb(40,90,40)",
"Error" : "rgb(200, 0, 0)",
"Karaoke Template" : "rgb(128, 0, 192)",
"Karaoke Variable" : "rgb(128, 0, 192)",

View File

@ -253,7 +253,11 @@ void Interface_Colours(wxTreebook *book, Preferences *parent) {
p->OptionAdd(syntax, _("Background"), "Colour/Subtitle/Background");
p->OptionAdd(syntax, _("Normal"), "Colour/Subtitle/Syntax/Normal");
p->OptionAdd(syntax, _("Comments"), "Colour/Subtitle/Syntax/Comment");
p->OptionAdd(syntax, _("Drawings"), "Colour/Subtitle/Syntax/Drawing");
p->OptionAdd(syntax, _("Drawing Commands"), "Colour/Subtitle/Syntax/Drawing Command");
p->OptionAdd(syntax, _("Drawing X Coords"), "Colour/Subtitle/Syntax/Drawing X");
p->OptionAdd(syntax, _("Drawing Y Coords"), "Colour/Subtitle/Syntax/Drawing Y");
p->OptionAdd(syntax, _("Underline Spline Endpoints"), "Colour/Subtitle/Syntax/Underline/Drawing Endpoint");
p->CellSkip(syntax);
p->OptionAdd(syntax, _("Brackets"), "Colour/Subtitle/Syntax/Brackets");
p->OptionAdd(syntax, _("Slashes and Parentheses"), "Colour/Subtitle/Syntax/Slashes");
p->OptionAdd(syntax, _("Tags"), "Colour/Subtitle/Syntax/Tags");

View File

@ -138,7 +138,10 @@ SubsTextEditCtrl::SubsTextEditCtrl(wxWindow* parent, wxSize wsize, long style, a
OPT_SUB("Subtitle/Edit Box/Font Size", &SubsTextEditCtrl::SetStyles, this);
Subscribe("Normal");
Subscribe("Comment");
Subscribe("Drawing");
Subscribe("Drawing Command");
Subscribe("Drawing X");
Subscribe("Drawing Y");
OPT_SUB("Colour/Subtitle/Syntax/Underline/Drawing Endpoint", &SubsTextEditCtrl::SetStyles, this);
Subscribe("Brackets");
Subscribe("Slashes");
Subscribe("Tags");
@ -230,7 +233,13 @@ void SubsTextEditCtrl::SetStyles() {
namespace ss = agi::ass::SyntaxStyle;
SetSyntaxStyle(ss::NORMAL, font, "Normal", default_background);
SetSyntaxStyle(ss::COMMENT, font, "Comment", default_background);
SetSyntaxStyle(ss::DRAWING, font, "Drawing", default_background);
SetSyntaxStyle(ss::DRAWING_CMD, font, "Drawing Command", default_background);
SetSyntaxStyle(ss::DRAWING_X, font, "Drawing X", default_background);
SetSyntaxStyle(ss::DRAWING_Y, font, "Drawing Y", default_background);
SetSyntaxStyle(ss::DRAWING_ENDPOINT_X, font, "Drawing X", default_background);
SetSyntaxStyle(ss::DRAWING_ENDPOINT_Y, font, "Drawing Y", default_background);
StyleSetUnderline(ss::DRAWING_ENDPOINT_X, OPT_GET("Colour/Subtitle/Syntax/Underline/Drawing Endpoint")->GetBool());
StyleSetUnderline(ss::DRAWING_ENDPOINT_Y, OPT_GET("Colour/Subtitle/Syntax/Underline/Drawing Endpoint")->GetBool());
SetSyntaxStyle(ss::OVERRIDE, font, "Brackets", default_background);
SetSyntaxStyle(ss::PUNCTUATION, font, "Slashes", default_background);
SetSyntaxStyle(ss::TAG, font, "Tags", default_background);

View File

@ -74,14 +74,47 @@ TEST(lagi_syntax, spellcheck) {
}
TEST(lagi_syntax, drawing) {
tok_str("incorrect{\\p1}m 10 10{\\p}correct", false,
tok_str("incorrect{\\clip(m 10 10 l 20 20 c)\\p1}m 10 10 b 0 0 0 100 100 0{\\p}correct", false,
expect_style(ss::SPELLING, 9u);
expect_style(ss::OVERRIDE, 1u);
expect_style(ss::PUNCTUATION, 1u);
expect_style(ss::TAG, 4u);
expect_style(ss::PUNCTUATION, 1u);
expect_style(ss::DRAWING_CMD, 1u);
expect_style(ss::NORMAL, 1u);
expect_style(ss::DRAWING_X, 2u);
expect_style(ss::NORMAL, 1u);
expect_style(ss::DRAWING_Y, 2u);
expect_style(ss::NORMAL, 1u);
expect_style(ss::DRAWING_CMD, 1u);
expect_style(ss::NORMAL, 1u);
expect_style(ss::DRAWING_X, 2u);
expect_style(ss::NORMAL, 1u);
expect_style(ss::DRAWING_Y, 2u);
expect_style(ss::NORMAL, 1u);
expect_style(ss::DRAWING_CMD, 1u);
expect_style(ss::PUNCTUATION, 2u);
expect_style(ss::TAG, 1u);
expect_style(ss::PARAMETER, 1u);
expect_style(ss::OVERRIDE, 1u);
expect_style(ss::DRAWING, 7u);
expect_style(ss::DRAWING_CMD, 1u);
expect_style(ss::NORMAL, 1u);
expect_style(ss::DRAWING_X, 2u);
expect_style(ss::NORMAL, 1u);
expect_style(ss::DRAWING_Y, 2u);
expect_style(ss::NORMAL, 1u);
expect_style(ss::DRAWING_CMD, 1u);
expect_style(ss::NORMAL, 1u);
expect_style(ss::DRAWING_X, 1u);
expect_style(ss::NORMAL, 1u);
expect_style(ss::DRAWING_Y, 1u);
expect_style(ss::NORMAL, 1u);
expect_style(ss::DRAWING_X, 1u);
expect_style(ss::NORMAL, 1u);
expect_style(ss::DRAWING_Y, 3u);
expect_style(ss::NORMAL, 1u);
expect_style(ss::DRAWING_ENDPOINT_X, 4u);
expect_style(ss::DRAWING_ENDPOINT_Y, 1u);
expect_style(ss::OVERRIDE, 1u);
expect_style(ss::PUNCTUATION, 1u);
expect_style(ss::TAG, 1u);

View File

@ -108,12 +108,12 @@ TEST(lagi_word_split, drawing) {
SplitWords(text, tokens);
ASSERT_EQ(15u, tokens.size());
ASSERT_EQ(17u, tokens.size());
EXPECT_EQ(dt::WORD, tokens[0].type);
EXPECT_EQ(dt::WORD, tokens[2].type);
EXPECT_EQ(dt::WORD, tokens[14].type);
EXPECT_EQ(dt::WORD, tokens[16].type);
EXPECT_EQ(dt::DRAWING, tokens[8].type);
EXPECT_EQ(dt::DRAWING_CMD, tokens[8].type);
}
TEST(lagi_word_split, unclosed_ovr) {