2121#include < QApplication>
2222#include < QColor>
2323#include < QDebug>
24+ #include < QFont>
2425#include < QObject>
2526#include < QRegularExpression>
2627#include < QRegularExpressionMatch>
@@ -109,6 +110,10 @@ void QOwnNotesMarkdownHighlighter::highlightBlock(const QString &text) {
109110#endif
110111
111112 highlightScriptingRules (ScriptingService::instance ()->getHighlightingRules (), text);
113+
114+ // Call the per-block highlightingHook in scripts (only invoked if at
115+ // least one script provides the hook, checked via cached flag)
116+ highlightScriptingHook (text);
112117 }
113118
114119 // Only check for encryption end marker if we're highlighting encrypted text
@@ -134,18 +139,49 @@ void QOwnNotesMarkdownHighlighter::highlightScriptingRules(
134139 auto iterator = rule.pattern .globalMatch (text);
135140 const uint8_t capturingGroup = rule.capturingGroup ;
136141 const uint8_t maskedGroup = rule.maskedGroup ;
137- const QTextCharFormat &format = _formats[rule.state ];
138142
139- // find and format all occurrences
143+ // Build the format: use custom format if provided, otherwise use the
144+ // predefined format for the given state
145+ QTextCharFormat format;
146+ if (rule.hasCustomFormat ) {
147+ // Start from the state format if a valid state was specified
148+ if (rule.state != NoState) {
149+ format = _formats[rule.state ];
150+ }
151+
152+ // Override with custom properties
153+ if (!rule.foregroundColor .isEmpty ()) {
154+ format.setForeground (QColor (rule.foregroundColor ));
155+ }
156+ if (!rule.backgroundColor .isEmpty ()) {
157+ format.setBackground (QColor (rule.backgroundColor ));
158+ }
159+ if (rule.bold ) {
160+ format.setFontWeight (QFont::Bold);
161+ }
162+ if (rule.italic ) {
163+ format.setFontItalic (true );
164+ }
165+ if (rule.underline ) {
166+ format.setFontUnderline (true );
167+ }
168+ if (rule.fontSize > 0 ) {
169+ format.setFontPointSize (rule.fontSize );
170+ }
171+ } else {
172+ format = _formats[rule.state ];
173+ }
174+
175+ // Find and format all occurrences
140176 while (iterator.hasNext ()) {
141177 QRegularExpressionMatch match = iterator.next ();
142178
143- // if there is a capturingGroup set then first highlight
179+ // If there is a capturingGroup set then first highlight
144180 // everything as MaskedSyntax and highlight capturingGroup
145181 // with the real format
146182 if (capturingGroup > 0 ) {
147183 QTextCharFormat currentMaskedFormat = maskedFormat;
148- // set the font size from the current rule's font format
184+ // Set the font size from the current rule's font format
149185 if (format.fontPointSize () > 0 ) {
150186 currentMaskedFormat.setFontPointSize (format.fontPointSize ());
151187 }
@@ -160,6 +196,69 @@ void QOwnNotesMarkdownHighlighter::highlightScriptingRules(
160196 }
161197}
162198
199+ /* *
200+ * Calls the highlightingHook in all script components for the current text
201+ * block and applies the returned highlight ranges
202+ */
203+ void QOwnNotesMarkdownHighlighter::highlightScriptingHook (const QString &text) {
204+ ScriptingService *scriptingService = ScriptingService::instance ();
205+
206+ if (!scriptingService->highlightingHookExists ()) {
207+ return ;
208+ }
209+
210+ const QVariantList highlights =
211+ scriptingService->callHighlightingHook (text, previousBlockState ());
212+
213+ for (const QVariant &item : highlights) {
214+ const QVariantMap m = item.toMap ();
215+ const int start = m.value (QStringLiteral (" start" )).toInt ();
216+ const int length = m.value (QStringLiteral (" length" )).toInt ();
217+
218+ if (length <= 0 ) {
219+ continue ;
220+ }
221+
222+ // Determine the format from the state or custom properties
223+ const int state = m.value (QStringLiteral (" state" ), -1 ).toInt ();
224+ QTextCharFormat format;
225+
226+ if (state >= 0 ) {
227+ format = _formats[static_cast <HighlighterState>(state)];
228+ }
229+
230+ // Apply custom format overrides
231+ const QString fg = m.value (QStringLiteral (" foregroundColor" )).toString ();
232+ if (!fg.isEmpty ()) {
233+ format.setForeground (QColor (fg));
234+ }
235+
236+ const QString bg = m.value (QStringLiteral (" backgroundColor" )).toString ();
237+ if (!bg.isEmpty ()) {
238+ format.setBackground (QColor (bg));
239+ }
240+
241+ if (m.value (QStringLiteral (" bold" )).toBool ()) {
242+ format.setFontWeight (QFont::Bold);
243+ }
244+
245+ if (m.value (QStringLiteral (" italic" )).toBool ()) {
246+ format.setFontItalic (true );
247+ }
248+
249+ if (m.value (QStringLiteral (" underline" )).toBool ()) {
250+ format.setFontUnderline (true );
251+ }
252+
253+ const qreal fontSize = m.value (QStringLiteral (" fontSize" )).toReal ();
254+ if (fontSize > 0 ) {
255+ format.setFontPointSize (fontSize);
256+ }
257+
258+ setFormat (start, length, format);
259+ }
260+ }
261+
163262void QOwnNotesMarkdownHighlighter::updateCachedRegexes (const QString &newExt) {
164263 _regexTagStyleLink = QRegularExpression (R"( <([^\s`][^`]*?\.)" + newExt + R"( )>)" );
165264 _regexBracketLink = QRegularExpression (R"( \[[^\[\]]+\]\((\S+\.)" + newExt + R"( |.+?\.)" +
0 commit comments