Skip to content

KaTeX(8/n): Support colored text in KaTeX content #1670

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 31, 2025

Conversation

rajveermalviya
Copy link
Member

@rajveermalviya rajveermalviya commented Jul 4, 2025

Flutter Web
image image

Survey results

@@ -1,5 +1,5 @@
-  …, 31380 of them were KaTeX containing messages and 3823 of those failed.
-  There were 1075 math block nodes out of which 615 failed.
-  There were 164014 math inline nodes out of which 6497 failed.
+  …, 31380 of them were KaTeX containing messages and 3817 of those failed.
+  There were 1075 math block nodes out of which 614 failed.
+  There were 164014 math inline nodes out of which 6470 failed.
   Because of hard fail: unsupported inline CSS property: top:
     1546 messages failed.
@@ -84,6 +84,4 @@
   Because of unsupported css class: stretchy:
     16 messages failed.
-  Because of unsupported inline css property: color:
-    16 messages failed.
   Because of unsupported css class: sout:
     12 messages failed.

Fixes: #1679

Copy link
Member

@gnprice gnprice left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for building this! I especially appreciate the clear comments and the spec links.

Generally this all looks good. See comments below; and please also rebase it atop main, and adjust the style to match the changes that were in #1722 and #1729.

Comment on lines 553 to 559
if (valueStr.startsWith('#')) {
color = parseCssHexColor(valueStr);
if (color != null) continue;
}

color = _cssNamedColorsMap[valueStr];
if (color != null) continue;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On its face, this looks like even if the value starts with # it could overwrite that with what it finds in the map.

That won't happen in practice, because no names start with #. But it'd be cleaner to not have to think about that — once we decide it looks like hex notation, we stick with that and don't try something else. Maybe just wrap this second case in an else?

Comment on lines 530 to 534
case 'color':
// `package:csslib` parser emits a HexColorTerm for the `color`
// attribute. It automatically resolves the named CSS colors to
// their hex values. The `HexColorTerm.value` is the hex
// encoded in an integer in the same sequence as the input hex
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a helpful comment, thanks.

Since the logic in this case gets somewhat long, let's push all that logic, along with this accompanying comment, into a dedicated private helper function. That will help keep it easy to read in isolation, and also keep this surrounding code easy to read when not thinking specifically about colors.

Comment on lines 596 to 597
String? _getRawValue(css_visitor.Expression expression) {
return expression.span?.text;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This name doesn't seem quite right — this isn't really the value of the expression, so much as the source code of the expression.

When we call this, it does represent the value of a property… but only because this expression is the value of a property. So that's not a fact that this method itself expresses.

I think "get raw text" would be a good name. Or just inline it — that's not really longer, and makes transparent what it does.

if (match == null) return null;

String hexValue = match.group(1)!;
hexValue = hexValue.toLowerCase();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this line needed? I'd have thought that hex.decode would accept upper as well as lower case.

Comment on lines 767 to 930
/// Parses the CSS hex color notation.
///
/// See: https://drafts.csswg.org/css-color/#hex-notation
KatexSpanColor? parseCssHexColor(String hexStr) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for including that spec link — very helpful!

Comment on lines 541 to 543
// Also the generated integer value will be in 0xRRGGBBAA
// sequence (CSS notation), whereas `dart:ui` Color
// requires 0xAARRGGBB.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem material — even in this version, we break it up into bytes and then say
Color.fromARGB(color.a, color.r, color.g, color.b), which isn't a single integer anyway.

@gnprice
Copy link
Member

gnprice commented Jul 22, 2025

For this and the other TeX content PRs, would you also include two things in the PR description:

  • Screenshots of example messages; for this one, I think just one message like the one in the test would be good.
  • Some numbers on the effect in an empirical corpus. Specifically, let's use the diff/grep commands shown in Refactor KaTeX parsing of inline styles and vlists; normalize survey output a bit more #1722 — that should be easy to do and produce a nice clear picture of the overall effect. (Then where it seems appropriate go ahead and include more detail, too; but I imagine that won't be needed for this PR.)

@rajveermalviya
Copy link
Member Author

Thanks for the review @gnprice! Pushed an update, PTAL.

@rajveermalviya rajveermalviya requested a review from gnprice July 29, 2025 14:03
@gnprice gnprice added the integration review Added by maintainers when PR may be ready for integration label Jul 29, 2025
Copy link
Member

@gnprice gnprice left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the revision! All looks good modulo a few nits below.

Comment on lines 570 to 573
static final mathBlockKatexColorFunction = KatexExample.block(
'math block, KaTeX color function, hex color',
// https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2232197
r'\color{#f00} 0',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: TeX has no concept of "function"; \color here is a LaTeX command, which is a type of TeX macro.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just sent #1767 to simplify a different aspect of these tests' names and descriptions. Combining that and my comment above, one good version of this would be:

Suggested change
static final mathBlockKatexColorFunction = KatexExample.block(
'math block, KaTeX color function, hex color',
// https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2232197
r'\color{#f00} 0',
static final color = KatexExample.block(
r'\color: 3-digit hex color',
// https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2232197
r'\color{#f00} 0',

static final mathBlockKatexPhantomFunction = KatexExample.block(
'math block, KaTeX phantom function',
// https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2229515
r'\phantom{*}',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For example (apropos of the "function" question): one fun TeX fact is that \phantom{*} means exactly the same thing as \phantom * would; in both cases, * is the argument consumed by \phantom.

Similarly, one can write \frac12$$\frac12$$ — as a lazy/quick-to-type way of saying the same thing as \frac{1}{2}. With or without curly braces, the \frac command will consume the next two things as its arguments; the braces just serve to group a whole string of characters and/or commands into a single item, so that they get consumed together as a single argument.

Suggested change
r'\phantom{*}',
r'\phantom{*}',

KatexStrutNode(heightEm: 0.6444, verticalAlignEm: null),
KatexSpanNode(
styles: KatexSpanStyles(color: KatexSpanColor(255, 0, 0, 255)),
text: '0', nodes: null),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
text: '0', nodes: null),
text: '0'),

'<span class="base">'
'<span class="strut" style="height:0.6444em;"></span>'
'<span class="mord" style="color:#f00;">0</span></span></span></span></span></p>', [
KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: simplify

Suggested change
KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
KatexSpanNode(nodes: [

@@ -604,4 +685,49 @@ void main() async {
}, skip: Platform.isWindows, // [intended] purely analyzes source, so
// any one platform is enough; avoid dealing with Windows file paths
);

group('parseCssHexColor', () {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: let's keep the "all examples are tested" test at the end — it's a rather special kind of test, in that it reads the test file's own source code

@rajveermalviya
Copy link
Member Author

Thanks for the review @gnprice! Pushed an update, PTAL.

@gnprice
Copy link
Member

gnprice commented Jul 31, 2025

Thanks! Looks good; merging.

Made a tweak to clarify one test name:

   // KaTeX custom color macros, see https://github.com/KaTeX/KaTeX/blob/9fb63136e/src/macros.js#L977-L1033
   static final customColorMacro = KatexExample.block(
-    r'\red: custom color macro',
+    r'\red, custom KaTeX color macro: CSS 6-digit hex color',

since the meaning of "custom" here is that it's something KaTeX made up (according to that handy link you included in the comment).

@gnprice
Copy link
Member

gnprice commented Jul 31, 2025

Oh and also tweaked the commit message:

-    content: Support colored text in KaTeX content
+    katex: Support colored text in KaTeX content

Now that we've got the KaTeX logic all into its own files, I think a corresponding specific prefix is helpful.

@gnprice gnprice force-pushed the pr-tex-content-8 branch from ed53544 to f9ea6c9 Compare July 31, 2025 18:08
@gnprice gnprice merged commit f9ea6c9 into zulip:main Jul 31, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
integration review Added by maintainers when PR may be ready for integration
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Handle color in KaTeX
2 participants