From 7ddfef75174e0bb85ee6fb0b783ad065ffd1fcd8 Mon Sep 17 00:00:00 2001 From: arch1t3cht Date: Wed, 2 Nov 2022 19:24:42 +0100 Subject: [PATCH] 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. --- libaegisub/ass/dialogue_parser.cpp | 115 +++++++++++++++++- .../include/libaegisub/ass/dialogue_parser.h | 13 +- src/libresrc/default_config.json | 15 ++- src/libresrc/osx/default_config.json | 15 ++- src/preferences.cpp | 6 +- src/subs_edit_ctrl.cpp | 13 +- tests/tests/syntax_highlight.cpp | 37 +++++- tests/tests/word_split.cpp | 6 +- 8 files changed, 201 insertions(+), 19 deletions(-) diff --git a/libaegisub/ass/dialogue_parser.cpp b/libaegisub/ass/dialogue_parser.cpp index ea486dcf7..8cd1743e7 100644 --- a/libaegisub/ass/dialogue_parser.cpp +++ b/libaegisub/ass/dialogue_parser.cpp @@ -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 &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 &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 &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); diff --git a/libaegisub/include/libaegisub/ass/dialogue_parser.h b/libaegisub/include/libaegisub/ass/dialogue_parser.h index 0aa3f9962..727c0569f 100644 --- a/libaegisub/include/libaegisub/ass/dialogue_parser.h +++ b/libaegisub/include/libaegisub/ass/dialogue_parser.h @@ -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, diff --git a/src/libresrc/default_config.json b/src/libresrc/default_config.json index 318f8d3ee..9154556bb 100644 --- a/src/libresrc/default_config.json +++ b/src/libresrc/default_config.json @@ -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)", diff --git a/src/libresrc/osx/default_config.json b/src/libresrc/osx/default_config.json index 59f2ed05f..4405cd5cc 100644 --- a/src/libresrc/osx/default_config.json +++ b/src/libresrc/osx/default_config.json @@ -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)", diff --git a/src/preferences.cpp b/src/preferences.cpp index f6320dd75..e176ac58e 100644 --- a/src/preferences.cpp +++ b/src/preferences.cpp @@ -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"); diff --git a/src/subs_edit_ctrl.cpp b/src/subs_edit_ctrl.cpp index 9d1fc6a01..dbacce391 100644 --- a/src/subs_edit_ctrl.cpp +++ b/src/subs_edit_ctrl.cpp @@ -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); diff --git a/tests/tests/syntax_highlight.cpp b/tests/tests/syntax_highlight.cpp index ea3a2cc63..6b10c10b8 100644 --- a/tests/tests/syntax_highlight.cpp +++ b/tests/tests/syntax_highlight.cpp @@ -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); diff --git a/tests/tests/word_split.cpp b/tests/tests/word_split.cpp index 00ba82fcd..8ed0ff83f 100644 --- a/tests/tests/word_split.cpp +++ b/tests/tests/word_split.cpp @@ -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) {