Skip to content

Fixes #5809: Use view text color and force block rendering to fix math fraction visibility in dark mode#6283

Open
nikhilkumarpanigrahi wants to merge 7 commits into
oppia:developfrom
nikhilkumarpanigrahi:fix-math-fraction-visibility
Open

Fixes #5809: Use view text color and force block rendering to fix math fraction visibility in dark mode#6283
nikhilkumarpanigrahi wants to merge 7 commits into
oppia:developfrom
nikhilkumarpanigrahi:fix-math-fraction-visibility

Conversation

@nikhilkumarpanigrahi

@nikhilkumarpanigrahi nikhilkumarpanigrahi commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator

Explanation

Fixes #5809

In dark mode, LaTeX math expressions containing \frac were invisible because MathTagHandler was using a hardcoded equation color instead of the surrounding text view's current color. Additionally, \frac expressions rendered in inline mode omit the horizontal fraction bar.

This PR fixes both issues:

  1. Color fix: HtmlParser now passes htmlContentTextView.currentTextColor to MathTagHandler as equationColor, so LaTeX math inherits the correct color in all themes including dark mode.
  2. Block rendering fix: MathTagHandler now forces any LaTeX expression containing \frac to use block/display rendering mode, ensuring the horizontal fraction bar is always rendered visibly.

Before: fractions such as \frac{1}{7} were invisible in dark mode (white image on dark background).
After: fractions are rendered using the view's text color and are fully visible.

Essential Checklist

  • The PR title starts with "Fix #bugnum: " (If this PR fixes part of an issue, prefix the title with "Fix part of #bugnum: ...".)
  • The explanation section above starts with "Fixes #bugnum: " (If this PR fixes part of an issue, use instead: "Fixes part of #bugnum: ...".)
  • Any changes to scripts/assets files have their rationale included in the PR explanation.
  • The PR follows the style guide.
  • The PR does not contain any unnecessary code changes from Android Studio (reference).
  • The PR is made from a branch that's not called "develop" and is up-to-date with "develop".
  • The PR is assigned to the appropriate reviewers (reference).

For UI-specific PRs only

  • Add screenshots for portrait/landscape for both a tablet & phone of the before & after UI changes
  • For the screenshots above, include both English and pseudo-localized (RTL) screenshots (see RTL guide)
  • For PRs introducing new UI elements or color changes, both light and dark mode screenshots must be included
  • Add a screenshot demonstrating that you ran affected Espresso tests locally & that they're passing
Screenshot 2026-06-19 at 2 00 54 AM

- Add equationColor parameter to MathTagHandler so callers can pass the
  host view's currentTextColor instead of relying on the app-level theme
  resource. This fixes contrast when a view's background differs from the
  global theme (e.g. a dark card inside a light screen).
- HtmlParser now passes htmlContentTextView.currentTextColor as the
  equation color so LaTeX adopts the same color as the surrounding text.
- Force block/display rendering mode whenever a \frac expression is
  detected. KotliTeX renders \frac with a horizontal bar only in block
  mode; inline mode produces a barely visible diagonal slash.
- Update existing tests to expect BLOCK_IMAGE for all \frac content and
  add two new tests covering the provided-equationColor code path.
- Update 2 inline-alignment tests to use x^2 (non-\frac) since \frac is
  now forced to block mode by the visibility fix
- Add MATH_WITHOUT_FILENAME_INLINE_MARKUP constant for non-\frac test cases
- Add try-import for .bazelrc.local to allow machine-specific build overrides
- Gitignore .bazelrc.local, MODULE.bazel, and MODULE.bazel.lock
@nikhilkumarpanigrahi
nikhilkumarpanigrahi requested a review from a team as a code owner June 18, 2026 20:51
Copilot AI review requested due to automatic review settings June 18, 2026 20:51
@nikhilkumarpanigrahi
nikhilkumarpanigrahi requested review from a team as code owners June 18, 2026 20:51
@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 79301062-685e-43d7-88ff-f906f756a368

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

Copy link
Copy Markdown
Contributor

@nikhilkumarpanigrahi this PR is being marked as draft because the PR description must contain 'Fixes #' or 'Fixes part of #' for each issue the PR is changing, and each one on its own line with no other text.

@github-actions
github-actions Bot marked this pull request as draft June 18, 2026 20:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Fixes dark-mode LaTeX fraction visibility by ensuring math rendering inherits the surrounding TextView color and by forcing \frac expressions to render in block/display mode so the horizontal bar is shown.

Changes:

  • Pass htmlContentTextView.currentTextColor into MathTagHandler so LaTeX uses the correct theme-aware color.
  • Force block rendering for LaTeX containing \frac to ensure fraction bars render.
  • Update and add tests for provided equation color and block rendering behavior; add Bazel local override support.

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
utility/src/test/java/org/oppia/android/util/parser/html/MathTagHandlerTest.kt Updates existing expectations for \frac to block rendering and adds tests for custom equation colors.
utility/src/main/java/org/oppia/android/util/parser/html/MathTagHandler.kt Adds optional equationColor override and forces block rendering for LaTeX containing \frac.
utility/src/main/java/org/oppia/android/util/parser/html/HtmlParser.kt Passes the current TextView text color into MathTagHandler for theme-correct math rendering.
.gitignore Ignores local Bazel override and Bazel module files.
.bazelrc Adds try-import for an optional, gitignored .bazelrc.local.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +556 to +572
fun testParseHtml_withMathMarkup_cachingOn_withProvidedEquationColor_usesProvidedColor() {
val customColor = Color.CYAN
CustomHtmlContentHandler.fromHtml(
html = MATH_WITHOUT_FILENAME_MARKUP,
imageRetriever = mockImageRetriever,
customTagHandlers = mapOf(
CUSTOM_MATH_TAG to createMathTagHandler(cacheLatexRendering = true, equationColor = customColor)
)
)

verify(mockImageRetriever)!!.loadMathDrawable(
capture(stringCaptor), capture(floatCaptor),
capture(colorCaptor), capture(retrieverTypeCaptor)
)
assertThat(colorCaptor.value).isEqualTo(customColor)
assertThat(retrieverTypeCaptor.value).isEqualTo(ImageRetriever.Type.BLOCK_IMAGE)
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done — switched the fixture to MATH_WITHOUT_FILENAME_INLINE_MARKUP (uses x^2, no \frac) so the color assertion is independent of the block-forcing behavior, and removed the BLOCK_IMAGE assertion.

Comment on lines +551 to +552
val equationColor = parsedHtml.getSpansFromWholeString(MathExpressionSpan::class)
assertThat(equationColor[0].equationColor).isEqualTo(customColor)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done, renamed equationColor to mathExpressionSpans to accurately reflect that it holds a collection of spans, not a color value.

@nikhilkumarpanigrahi nikhilkumarpanigrahi changed the title Fix #5773: Use view text color and force block rendering to fix math fraction visibility in dark mode Fix #5809: Use view text color and force block rendering to fix math fraction visibility in dark mode Jun 18, 2026
@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Caution

Review failed

An error occurred during the review process. Please try again later.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

1 similar comment
@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Caution

Review failed

An error occurred during the review process. Please try again later.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

nikhilkumarpanigrahi and others added 3 commits June 19, 2026 03:36
Three lines exceeded the 100-char limit: one overly long test name and
two single-line createMathTagHandler() calls inside mapOf(). Shorten
the test name and wrap the argument lists.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The frac block-forcing fix causes any frac expression to render in
block mode, so the inline-mode test must use a non-frac expression
(x^2) to correctly exercise the inline rendering path.
The inline-mode test used \\x^2 in the markup string which decodes
to \x^2 as the LaTeX value, but the assertion expected x^2. Use x^2
directly in the markup so the decoded value matches the assertion.
@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report

Results

Number of files assessed: 2
Overall Coverage: 88.14%
Coverage Analysis: PASS

Passing coverage

Files with passing code coverage
File Coverage Lines Hit Status Min Required
MathTagHandler.ktutility/src/main/java/org/oppia/android/util/parser/html/MathTagHandler.kt
88.14% 104 / 118 70%

Exempted coverage

Files exempted from coverage
File Exemption Reason
HtmlParser.ktutility/src/main/java/org/oppia/android/util/parser/html/HtmlParser.kt
This file is incompatible with code coverage tooling; skipping coverage check.

Refer test_file_exemptions.textproto for the comprehensive list of file exemptions and their required coverage percentages.

To learn more, visit the Oppia Android Code Coverage wiki page

@nikhilkumarpanigrahi
nikhilkumarpanigrahi marked this pull request as ready for review June 19, 2026 05:22
@github-actions

Copy link
Copy Markdown
Contributor

@nikhilkumarpanigrahi this PR is being marked as draft because the PR description must contain 'Fixes #' or 'Fixes part of #' for each issue the PR is changing, and each one on its own line with no other text.

@github-actions
github-actions Bot marked this pull request as draft June 19, 2026 05:22
@nikhilkumarpanigrahi nikhilkumarpanigrahi changed the title Fix #5809: Use view text color and force block rendering to fix math fraction visibility in dark mode Fixes #5809: Use view text color and force block rendering to fix math fraction visibility in dark mode Jun 19, 2026
@nikhilkumarpanigrahi
nikhilkumarpanigrahi marked this pull request as ready for review June 19, 2026 06:14
@nikhilkumarpanigrahi

Copy link
Copy Markdown
Collaborator Author

@adhiamboperes PTAL when you get a chance!

@BenHenning BenHenning self-assigned this Jun 25, 2026
@oppiabot

oppiabot Bot commented Jun 26, 2026

Copy link
Copy Markdown

Hi @nikhilkumarpanigrahi, I'm going to mark this PR as stale because it hasn't had any updates for 7 days. If no further activity occurs within 7 days, it will be automatically closed so that others can take up the issue.
If you are still working on this PR, please make a follow-up commit within 3 days (and submit it for review, if applicable). Please also let us know if you are stuck so we can help you! If you're unsure how to reassign this PR to a reviewer, please make sure to review the wiki page that details the Guidance on submitting PRs.

@oppiabot oppiabot Bot added the stale Corresponds to items that haven't seen a recent update and may be automatically closed. label Jun 26, 2026
@Neer-rn Neer-rn removed the stale Corresponds to items that haven't seen a recent update and may be automatically closed. label Jun 26, 2026
@Neer-rn

Neer-rn commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Hi @BenHenning if you have still not started reviewing this PR yet, would it be okay if I take a first pass on it?

@Neer-rn Neer-rn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Hi @nikhilkumarpanigrahi, thanks for working on this.

I checked this, but I am still not fully sure that this PR is fixing #5809. Could you please explain how you reproduced the issue on current develop before this fix?

From what I checked, the “Writing Ratios in Simplest Form” asset in current develop has the relevant option as plain HTML:

domain/src/main/assets/dev/tIoSb3HZFN6e.textproto -> <p>1:1/7</p>
domain/src/main/assets/dev/tIoSb3HZFN6e.json -> <p>1:1/7</p>

So I am not able to understand how this goes through the <oppia-noninteractive-math> / MathTagHandler LaTeX path changed in this PR.

Also I remember sangeeta working on #6122 and it already fixed the LaTeX color/cache path for dark mode if I am correct, and current develop must have that change. So could you please clarify what exact remaining gap this PR is fixing after #6122?

The screenshot attached in this PR only shows the after state. Could you please add a before screenshot from develop and an after screenshot from this PR for the same screen/language? Without these, I am not sure about PR. PTAL

null
)
// \frac renders with a horizontal bar only in block/display mode; force it so the bar is visible.
if (content is MathContent.MathAsLatex && content.rawLatex.contains("\\frac")) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I am not fully sure about forcing every \frac expression into block mode here. This seems to override render-type="inline" for all fractions across the app. From checking the KotliTeX fraction code, \frac already creates a fraction line, so I am not sure the horizontal bar only exists in block/display mode.

Could you please share a small before/after screenshot showing that inline \frac loses the horizontal bar? This feels separate from the dark-mode contrast issue in #5809, and it may change inline lesson layout in other places.

Also, the old inline \frac test was changed to x^2, so we no longer have coverage for the explicit inline fraction case. Can we avoid this override, or add proof/tests that this behavior is intended? PTAL.

@oppiabot oppiabot Bot unassigned Neer-rn Jun 28, 2026
@oppiabot

oppiabot Bot commented Jun 28, 2026

Copy link
Copy Markdown

Unassigning @Neer-rn since the review is done.

@oppiabot

oppiabot Bot commented Jun 28, 2026

Copy link
Copy Markdown

Hi @nikhilkumarpanigrahi, it looks like some changes were requested on this pull request by @Neer-rn. PTAL. Thanks!

@nikhilkumarpanigrahi

Copy link
Copy Markdown
Collaborator Author

Thanks @Neer-rn!
You are right on these points, and looking into them helped me understand the bug better. Here is where I have landed.

About reproducing it / the asset. You are correct. The bundled dev asset tIoSb3HZFN6e uses plain HTML (<p>1:1/7</p>), so it never goes through MathTagHandler. My "after" screenshot was taken on that plain-text content in dark mode, so it does not actually show the fix working. That was my mistake. The real bug shows up in the production content: in the issue screenshot the option is 1:¼, which is a real rendered LaTeX fraction, and it is in light mode, not dark mode.

About #6122. I agree #6122 already made the equation color match the theme (white in dark mode, black in light mode), and it is already in this branch. But the issue screenshot is in light mode, where the color was already correct. So this is not a color or dark-mode problem. The real problem is that the fraction's horizontal bar is too thin to see at inline size. That means my color change does not help #5809, so I will remove it.

About forcing block mode. You are right about KotliTeX. I checked FunctionFrac.kt, and \frac sets hasBarLine = true no matter the style, so the fraction bar is drawn in inline mode too — the style only changes the size and spacing. So my comment "the bar only renders in block/display mode" was wrong: the bar is not missing inline, it is just drawn too thin to see. Forcing block mode makes the fraction bigger, so that thin bar becomes easy to see. That is why it looked like it fixed the problem. But as you said, doing this for every fraction changes all inline fractions across the app, which is too broad and not proven.

So here is my plan:

  1. Remove the color change, since it does not help this bug.
  2. Reproduce the real bug using actual inline \frac content in light mode, and post proper before and after screenshots for the same content and mode.
  3. Rethink the fix — either keep the block-mode change with the correct reason and real proof, or use a more targeted fix (for example, making the inline fraction render bigger or the bar thicker) so I am not overriding render-type="inline" everywhere.
  4. Add back the inline \frac test I removed.

One question before I rework this: if I can show it is readable and does not break other layouts, would forcing block size for \frac be acceptable to you in principle? Or would you prefer a more targeted rendering fix? I want to make sure we agree on the direction first. Thanks for the careful review.

@Neer-rn

Neer-rn commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator

Thanks @nikhilkumarpanigrahi for checking all this in detail, it explains the actual bug much better now. For the direction you asked: I would prefer the targeted rendering fix. Forcing block mode overrides the render-type that comes from the lesson content itself, and block rendering also increases the line height, so inline fractions in the middle of a sentence would change layout in every lesson across the app. Since the real problem is just the bar being too thin at inline size, fixing the size/thickness on the rendering side feels much safer and kotlitex is oppia's own fork, so a fix there is possible too.

One place worth checking: the fraction bar drawing in the fork's MathExpressionSpan seems to have a 1px minimum stroke width, which ends up as a hairline on high-density screens. Making that minimum density-aware something like ~1dp might fix it, but could you please verify whether this is actually the cause and the right fix?

This changes rendering for all lessons though, so I would suggest getting @BenHenning's thoughts on the direction as well. Your 4-step plan looks good to me otherwise, please go ahead.

@oppiabot

oppiabot Bot commented Jul 10, 2026

Copy link
Copy Markdown

Hi @nikhilkumarpanigrahi, I'm going to mark this PR as stale because it hasn't had any updates for 7 days. If no further activity occurs within 7 days, it will be automatically closed so that others can take up the issue.
If you are still working on this PR, please make a follow-up commit within 3 days (and submit it for review, if applicable). Please also let us know if you are stuck so we can help you! If you're unsure how to reassign this PR to a reviewer, please make sure to review the wiki page that details the Guidance on submitting PRs.

@oppiabot oppiabot Bot added the stale Corresponds to items that haven't seen a recent update and may be automatically closed. label Jul 10, 2026
@nikhilkumarpanigrahi

Copy link
Copy Markdown
Collaborator Author

Thanks @Neer-rn, that pointer really helped. I went through the fork (we're pinned to ccdf417) and you're right that there's a 1px minimum in there — but I think the actual cause is slightly different from what we assumed.

The bar is drawn here:

// MathExpressionSpan.kt:122
paint.strokeWidth = max(1.0f, parent.bounds.height.toFloat())

So yes, there's a hard 1.0f floor, and since Paint.strokeWidth is in pixels (not dp), it's density-independent like you said.

But then I traced where bounds.height actually comes from:

// RenderTreeBuilder.kt:55
line.height = thickness ?: options.fontMetrics.defaultRuleThickness
// FontMetrics.kt:98 -> defaultRuleThickness = 0.04 (em)

That 0.04 em gets scaled by baseSize, which is the TextView's lineHeight in px — and that already scales with density. So on a high-density phone it works out to roughly 2-3px, which is already above the 1.0f floor. Meaning max() never actually kicks in there. The 1px clamp only really bites on mdpi screens.

So the bar isn't thin because we're clamping it to 1px — it's thin because 0.04 em is just physically tiny (under about 1dp) at inline size.

One other thing I noticed while I was in there — the bar's paint isn't anti-aliased, unlike the text:

// MathExpressionSpan.kt:45-46
val paint = Paint()                                // bar - no AA
val textPaint = TextPaint(Paint.ANTI_ALIAS_FLAG)   // text - AA

A sub-1dp line with no AA is exactly the kind of thing that shows up as a faint, patchy hairline, which lines up with the screenshot in #5809.

So I still think your suggestion is the right direction, just with two tweaks: a 1dp floor might only be slightly thicker than what's already being drawn (~0.9dp), so it may need to be a bit more (~1.5dp) to actually make a visible difference, and we should probably turn on anti-aliasing for that paint too.

One catch on implementation: MathExpressionDrawable doesn't have a Context or density anywhere — its constructor is just (expr, baseSize, fontLoader, isMathMode, drawBounds, equationColor). So we'd either use Resources.getSystem().displayMetrics.density inside the fork, or pass density down from oppia-android, which would mean changing MathExpressionSpan's constructor and touching MathTagHandler and MathBitmapModelLoader. Either way it has to go into the kotlitex fork first, then a WORKSPACE pin bump here.

On this PR — I'm going to strip out the block-forcing and the currentTextColor change and put the inline \frac test back. Once I push that this PR will basically be empty, which is expected, since none of the real fix can happen on the oppia-android side. I'll keep it open to carry the WORKSPACE bump once we settle the fork change, and I'll move it to draft for now.

@BenHenning — Neer suggested I loop you in since this would change rendering across every lesson. Would you be okay with a kotlitex fork change that makes the minimum rule thickness density-aware instead of a flat 1px, and enables anti-aliasing on the rule paint?

One caveat: the 2-3px number above is me doing the math from the source, not an actual measurement. Before I settle on a specific value I'll log the real bounds.height at inline size on a high-density device and report back.

@oppiabot oppiabot Bot removed the stale Corresponds to items that haven't seen a recent update and may be automatically closed. label Jul 13, 2026
@oppiabot

oppiabot Bot commented Jul 20, 2026

Copy link
Copy Markdown

Hi @nikhilkumarpanigrahi, I'm going to mark this PR as stale because it hasn't had any updates for 7 days. If no further activity occurs within 7 days, it will be automatically closed so that others can take up the issue.
If you are still working on this PR, please make a follow-up commit within 3 days (and submit it for review, if applicable). Please also let us know if you are stuck so we can help you! If you're unsure how to reassign this PR to a reviewer, please make sure to review the wiki page that details the Guidance on submitting PRs.

@oppiabot oppiabot Bot added the stale Corresponds to items that haven't seen a recent update and may be automatically closed. label Jul 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stale Corresponds to items that haven't seen a recent update and may be automatically closed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG]: Writing Ratios in Simplest Form - The fraction with horizontal bar are hard to see [Blocked: #5792]

5 participants