From 74b3ba52d320915f16138318bcde758587c66c8c Mon Sep 17 00:00:00 2001 From: nikhilkumarpanigrahi Date: Thu, 18 Jun 2026 17:56:24 +0530 Subject: [PATCH 1/7] Use view text color and force block rendering to fix fraction visibility - 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. --- .../android/util/parser/html/HtmlParser.kt | 3 +- .../util/parser/html/MathTagHandler.kt | 15 +++-- .../util/parser/html/MathTagHandlerTest.kt | 59 +++++++++++++++---- 3 files changed, 61 insertions(+), 16 deletions(-) diff --git a/utility/src/main/java/org/oppia/android/util/parser/html/HtmlParser.kt b/utility/src/main/java/org/oppia/android/util/parser/html/HtmlParser.kt index ac86e0fded7..9ed0e0ff9f9 100755 --- a/utility/src/main/java/org/oppia/android/util/parser/html/HtmlParser.kt +++ b/utility/src/main/java/org/oppia/android/util/parser/html/HtmlParser.kt @@ -168,7 +168,8 @@ class HtmlParser private constructor( context.assets, htmlContentTextView.lineHeight.toFloat(), cacheLatexRendering, - context as? Application ?: context.applicationContext as Application + context as? Application ?: context.applicationContext as Application, + equationColor = htmlContentTextView.currentTextColor ) if (supportsConceptCards) { handlersMap[CUSTOM_CONCEPT_CARD_TAG] = conceptCardTagHandler diff --git a/utility/src/main/java/org/oppia/android/util/parser/html/MathTagHandler.kt b/utility/src/main/java/org/oppia/android/util/parser/html/MathTagHandler.kt index 6288410b471..16e149a2a77 100644 --- a/utility/src/main/java/org/oppia/android/util/parser/html/MathTagHandler.kt +++ b/utility/src/main/java/org/oppia/android/util/parser/html/MathTagHandler.kt @@ -31,7 +31,8 @@ class MathTagHandler( private val assetManager: AssetManager, private val lineHeight: Float, private val cacheLatexRendering: Boolean, - private val application: Application + private val application: Application, + private val equationColor: Int? = null ) : CustomHtmlContentHandler.CustomTagHandler, CustomHtmlContentHandler.ContentDescriptionProvider { override fun handleTag( attributes: Attributes, @@ -44,17 +45,21 @@ class MathTagHandler( val content = MathContent.parseMathContent( attributes.getJsonObjectValue(CUSTOM_MATH_MATH_CONTENT_ATTRIBUTE) ) - val useInlineRendering = when (attributes.getValue(CUSTOM_MATH_RENDER_TYPE_ATTRIBUTE)) { + var useInlineRendering = when (attributes.getValue(CUSTOM_MATH_RENDER_TYPE_ATTRIBUTE)) { "inline" -> true "block" -> false else -> true } checkNotNull(imageRetriever) { "Expected imageRetriever to be not null." } - val equationColor = ResourcesCompat.getColor( + val resolvedEquationColor = equationColor ?: ResourcesCompat.getColor( application.resources, R.color.component_color_shared_equation_color, 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")) { + useInlineRendering = false + } val newSpan = when (content) { is MathContent.MathAsSvg -> { ImageSpan( @@ -71,7 +76,7 @@ class MathTagHandler( imageRetriever.loadMathDrawable( content.rawLatex, lineHeight, - equationColor, + resolvedEquationColor, type = if (useInlineRendering) INLINE_TEXT_IMAGE else BLOCK_IMAGE ), useInlineRendering @@ -82,7 +87,7 @@ class MathTagHandler( lineHeight, assetManager, isMathMode = !useInlineRendering, - equationColor + resolvedEquationColor ) } } diff --git a/utility/src/test/java/org/oppia/android/util/parser/html/MathTagHandlerTest.kt b/utility/src/test/java/org/oppia/android/util/parser/html/MathTagHandlerTest.kt index f295d8eb79d..49b734af9a5 100644 --- a/utility/src/test/java/org/oppia/android/util/parser/html/MathTagHandlerTest.kt +++ b/utility/src/test/java/org/oppia/android/util/parser/html/MathTagHandlerTest.kt @@ -306,7 +306,7 @@ class MathTagHandlerTest { } @Test - fun testParseHtml_withMathMarkup_missingFilename_includesCachedInlineLatexImageSpan() { + fun testParseHtml_withMathMarkup_missingFilename_withFrac_includesCachedBlockLatexImageSpan() { val parsedHtml = CustomHtmlContentHandler.fromHtml( html = MATH_WITHOUT_FILENAME_MARKUP, @@ -314,7 +314,7 @@ class MathTagHandlerTest { customTagHandlers = tagHandlersWithCachedMathSupport ) - // The image span is a cached bitmap loaded from LaTeX. + // \frac expressions are always rendered in block mode for the horizontal fraction bar. val imageSpans = parsedHtml.getSpansFromWholeString(ImageSpan::class) assertThat(imageSpans).hasLength(1) verify(mockImageRetriever)!!.loadMathDrawable( @@ -322,11 +322,11 @@ class MathTagHandlerTest { capture(colorCaptor), capture(retrieverTypeCaptor) ) assertThat(stringCaptor.value).isEqualTo("\\frac{2}{5}") - assertThat(retrieverTypeCaptor.value).isEqualTo(ImageRetriever.Type.INLINE_TEXT_IMAGE) + assertThat(retrieverTypeCaptor.value).isEqualTo(ImageRetriever.Type.BLOCK_IMAGE) } @Test - fun testParseHtml_withMathMarkup_missingFilename_inlineMode_includesCachedInlineLatexImageSpan() { + fun testParseHtml_withMathMarkup_missingFilename_inlineMode_withFrac_includesCachedBlockLatexImageSpan() { val parsedHtml = CustomHtmlContentHandler.fromHtml( html = MATH_WITHOUT_FILENAME_RENDER_TYPE_INLINE_MARKUP, @@ -334,7 +334,7 @@ class MathTagHandlerTest { customTagHandlers = tagHandlersWithCachedMathSupport ) - // The image span is a cached bitmap loaded from LaTeX. + // \frac overrides even an explicit inline render-type to ensure the horizontal bar is visible. val imageSpans = parsedHtml.getSpansFromWholeString(ImageSpan::class) assertThat(imageSpans).hasLength(1) verify(mockImageRetriever)!!.loadMathDrawable( @@ -342,7 +342,7 @@ class MathTagHandlerTest { capture(colorCaptor), capture(retrieverTypeCaptor) ) assertThat(stringCaptor.value).isEqualTo("\\frac{2}{5}") - assertThat(retrieverTypeCaptor.value).isEqualTo(ImageRetriever.Type.INLINE_TEXT_IMAGE) + assertThat(retrieverTypeCaptor.value).isEqualTo(ImageRetriever.Type.BLOCK_IMAGE) } @Test @@ -460,7 +460,7 @@ class MathTagHandlerTest { capture(colorCaptor), capture(retrieverTypeCaptor) ) assertThat(stringCaptor.value).isEqualTo("\\frac{2}{5}") - assertThat(retrieverTypeCaptor.value).isEqualTo(ImageRetriever.Type.INLINE_TEXT_IMAGE) + assertThat(retrieverTypeCaptor.value).isEqualTo(ImageRetriever.Type.BLOCK_IMAGE) } @Test @@ -481,7 +481,7 @@ class MathTagHandlerTest { .containsExactly("\\frac{3}{8}", "\\frac{2}{5}") .inOrder() assertThat(retrieverTypeCaptor.allValues) - .containsExactly(ImageRetriever.Type.INLINE_TEXT_IMAGE, ImageRetriever.Type.INLINE_TEXT_IMAGE) + .containsExactly(ImageRetriever.Type.BLOCK_IMAGE, ImageRetriever.Type.BLOCK_IMAGE) .inOrder() } @@ -531,14 +531,53 @@ class MathTagHandlerTest { assertThat(parsedHtmlStr).contains(" and ") } - private fun createMathTagHandler(cacheLatexRendering: Boolean): MathTagHandler { + @Test + fun testParseHtml_withMathMarkup_cachingOff_withProvidedEquationColor_usesProvidedColor() { + val customColor = Color.MAGENTA + val parsedHtml = + CustomHtmlContentHandler.fromHtml( + html = MATH_WITHOUT_FILENAME_MARKUP, + imageRetriever = mockImageRetriever, + customTagHandlers = mapOf( + CUSTOM_MATH_TAG to createMathTagHandler(cacheLatexRendering = false, equationColor = customColor) + ) + ) + + val equationColor = parsedHtml.getSpansFromWholeString(MathExpressionSpan::class) + assertThat(equationColor[0].equationColor).isEqualTo(customColor) + } + + @Test + 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) + } + + private fun createMathTagHandler( + cacheLatexRendering: Boolean, + equationColor: Int? = null + ): MathTagHandler { // Pick an arbitrary line height since rendering doesn't actually happen in tests. return MathTagHandler( consoleLogger, context.assets, lineHeight = 10.0f, cacheLatexRendering, - application = context.applicationContext as Application + application = context.applicationContext as Application, + equationColor = equationColor ) } From 93e396961682a6c81afce16c6d3ce945c1c35f8f Mon Sep 17 00:00:00 2001 From: nikhilkumarpanigrahi Date: Fri, 19 Jun 2026 02:13:53 +0530 Subject: [PATCH 2/7] Update tests and build config for math fraction visibility fix - 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 --- .bazelrc | 3 +++ .gitignore | 3 +++ .../oppia/android/util/parser/html/MathTagHandlerTest.kt | 9 +++++++-- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.bazelrc b/.bazelrc index a3a9a18ed65..cba8ccf0637 100644 --- a/.bazelrc +++ b/.bazelrc @@ -23,3 +23,6 @@ mobile-install --start_app # Ensure that all tests have access to the ANDROID_HOME environment variable. test --test_env=ANDROID_HOME + +# Allow machine-specific local overrides (e.g. Apple Silicon flags). This file is gitignored. +try-import %workspace%/.bazelrc.local diff --git a/.gitignore b/.gitignore index df850b9152c..946f1a45ed3 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,9 @@ utility/build config/oppia-dev-workflow-remote-cache-credentials.json bazel-* .bazelproject +.bazelrc.local +MODULE.bazel +MODULE.bazel.lock .aswb *.pb coverage_reports diff --git a/utility/src/test/java/org/oppia/android/util/parser/html/MathTagHandlerTest.kt b/utility/src/test/java/org/oppia/android/util/parser/html/MathTagHandlerTest.kt index 49b734af9a5..b72dda39fca 100644 --- a/utility/src/test/java/org/oppia/android/util/parser/html/MathTagHandlerTest.kt +++ b/utility/src/test/java/org/oppia/android/util/parser/html/MathTagHandlerTest.kt @@ -67,6 +67,11 @@ private const val MATH_WITHOUT_FILENAME_MARKUP = "" +// A non-\frac expression so these tests exercise the inline code-path (not block-forced by \frac). +private const val MATH_WITHOUT_FILENAME_INLINE_MARKUP = + "" + private const val MATH_WITHOUT_FILENAME_RENDER_TYPE_INLINE_MARKUP = " Date: Fri, 19 Jun 2026 02:41:53 +0530 Subject: [PATCH 3/7] Address Copilot review: fix variable name and decouple color test from block-forcing --- .../oppia/android/util/parser/html/MathTagHandlerTest.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/utility/src/test/java/org/oppia/android/util/parser/html/MathTagHandlerTest.kt b/utility/src/test/java/org/oppia/android/util/parser/html/MathTagHandlerTest.kt index b72dda39fca..553577b64c8 100644 --- a/utility/src/test/java/org/oppia/android/util/parser/html/MathTagHandlerTest.kt +++ b/utility/src/test/java/org/oppia/android/util/parser/html/MathTagHandlerTest.kt @@ -548,15 +548,15 @@ class MathTagHandlerTest { ) ) - val equationColor = parsedHtml.getSpansFromWholeString(MathExpressionSpan::class) - assertThat(equationColor[0].equationColor).isEqualTo(customColor) + val mathExpressionSpans = parsedHtml.getSpansFromWholeString(MathExpressionSpan::class) + assertThat(mathExpressionSpans[0].equationColor).isEqualTo(customColor) } @Test fun testParseHtml_withMathMarkup_cachingOn_withProvidedEquationColor_usesProvidedColor() { val customColor = Color.CYAN CustomHtmlContentHandler.fromHtml( - html = MATH_WITHOUT_FILENAME_MARKUP, + html = MATH_WITHOUT_FILENAME_INLINE_MARKUP, imageRetriever = mockImageRetriever, customTagHandlers = mapOf( CUSTOM_MATH_TAG to createMathTagHandler(cacheLatexRendering = true, equationColor = customColor) @@ -568,7 +568,6 @@ class MathTagHandlerTest { capture(colorCaptor), capture(retrieverTypeCaptor) ) assertThat(colorCaptor.value).isEqualTo(customColor) - assertThat(retrieverTypeCaptor.value).isEqualTo(ImageRetriever.Type.BLOCK_IMAGE) } private fun createMathTagHandler( From d2a9216c75abe2b73ba69658416d8fd9b18fd904 Mon Sep 17 00:00:00 2001 From: nikhilkumarpanigrahi Date: Fri, 19 Jun 2026 02:45:31 +0530 Subject: [PATCH 4/7] Remove unrelated .bazelrc and .gitignore changes from fraction fix PR --- .bazelrc | 3 --- .gitignore | 3 --- 2 files changed, 6 deletions(-) diff --git a/.bazelrc b/.bazelrc index cba8ccf0637..a3a9a18ed65 100644 --- a/.bazelrc +++ b/.bazelrc @@ -23,6 +23,3 @@ mobile-install --start_app # Ensure that all tests have access to the ANDROID_HOME environment variable. test --test_env=ANDROID_HOME - -# Allow machine-specific local overrides (e.g. Apple Silicon flags). This file is gitignored. -try-import %workspace%/.bazelrc.local diff --git a/.gitignore b/.gitignore index 946f1a45ed3..df850b9152c 100644 --- a/.gitignore +++ b/.gitignore @@ -19,9 +19,6 @@ utility/build config/oppia-dev-workflow-remote-cache-credentials.json bazel-* .bazelproject -.bazelrc.local -MODULE.bazel -MODULE.bazel.lock .aswb *.pb coverage_reports From aff511bb3bbb97adde91e5cee8a40deb71f28a38 Mon Sep 17 00:00:00 2001 From: nikhilkumarpanigrahi Date: Fri, 19 Jun 2026 03:36:39 +0530 Subject: [PATCH 5/7] Fix ktlint line-length violations in MathTagHandlerTest 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 --- .../android/util/parser/html/MathTagHandlerTest.kt | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/utility/src/test/java/org/oppia/android/util/parser/html/MathTagHandlerTest.kt b/utility/src/test/java/org/oppia/android/util/parser/html/MathTagHandlerTest.kt index 553577b64c8..401cb3a1e52 100644 --- a/utility/src/test/java/org/oppia/android/util/parser/html/MathTagHandlerTest.kt +++ b/utility/src/test/java/org/oppia/android/util/parser/html/MathTagHandlerTest.kt @@ -331,7 +331,7 @@ class MathTagHandlerTest { } @Test - fun testParseHtml_withMathMarkup_missingFilename_inlineMode_withFrac_includesCachedBlockLatexImageSpan() { + fun testParseHtml_withMathMarkup_missingFilename_inlineMode_includesCachedBlockLatexImageSpan() { val parsedHtml = CustomHtmlContentHandler.fromHtml( html = MATH_WITHOUT_FILENAME_RENDER_TYPE_INLINE_MARKUP, @@ -544,7 +544,10 @@ class MathTagHandlerTest { html = MATH_WITHOUT_FILENAME_MARKUP, imageRetriever = mockImageRetriever, customTagHandlers = mapOf( - CUSTOM_MATH_TAG to createMathTagHandler(cacheLatexRendering = false, equationColor = customColor) + CUSTOM_MATH_TAG to createMathTagHandler( + cacheLatexRendering = false, + equationColor = customColor + ) ) ) @@ -559,7 +562,10 @@ class MathTagHandlerTest { html = MATH_WITHOUT_FILENAME_INLINE_MARKUP, imageRetriever = mockImageRetriever, customTagHandlers = mapOf( - CUSTOM_MATH_TAG to createMathTagHandler(cacheLatexRendering = true, equationColor = customColor) + CUSTOM_MATH_TAG to createMathTagHandler( + cacheLatexRendering = true, + equationColor = customColor + ) ) ) From 54f0cabb7d74c2b760a9928cb2bd09ed54c1d7ba Mon Sep 17 00:00:00 2001 From: nikhilkumarpanigrahi Date: Fri, 19 Jun 2026 04:18:18 +0530 Subject: [PATCH 6/7] Fix HtmlParserTest inline math test to use non-frac expression 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. --- .../java/org/oppia/android/util/parser/html/HtmlParserTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utility/src/test/java/org/oppia/android/util/parser/html/HtmlParserTest.kt b/utility/src/test/java/org/oppia/android/util/parser/html/HtmlParserTest.kt index 327fa9da92d..47dec643b87 100644 --- a/utility/src/test/java/org/oppia/android/util/parser/html/HtmlParserTest.kt +++ b/utility/src/test/java/org/oppia/android/util/parser/html/HtmlParserTest.kt @@ -1029,7 +1029,7 @@ class HtmlParserTest { val textView: TextView = it.findViewById(R.id.test_html_content_text_view) val htmlResult: Spannable = htmlParser.parseOppiaHtml( "" + + ""raw_latex":"\\\\x^2"}\">" + "", textView, supportsLinks = true, @@ -1041,7 +1041,7 @@ class HtmlParserTest { // The rendering mode should be inline for this render type. val loadedInlineImages = testGlideImageLoader.getLoadedMathDrawables() assertThat(loadedInlineImages).hasSize(1) - assertThat(loadedInlineImages.first().rawLatex).isEqualTo("\\frac{2}{5}") + assertThat(loadedInlineImages.first().rawLatex).isEqualTo("x^2") assertThat(loadedInlineImages.first().useInlineRendering).isTrue() } } From 0bebb696c99a590c3f34e48169cec9f0faed68e8 Mon Sep 17 00:00:00 2001 From: nikhilkumarpanigrahi Date: Fri, 19 Jun 2026 04:52:57 +0530 Subject: [PATCH 7/7] Fix HtmlParserTest: remove extra backslash from x^2 latex markup 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. --- .../java/org/oppia/android/util/parser/html/HtmlParserTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utility/src/test/java/org/oppia/android/util/parser/html/HtmlParserTest.kt b/utility/src/test/java/org/oppia/android/util/parser/html/HtmlParserTest.kt index 47dec643b87..652cf960514 100644 --- a/utility/src/test/java/org/oppia/android/util/parser/html/HtmlParserTest.kt +++ b/utility/src/test/java/org/oppia/android/util/parser/html/HtmlParserTest.kt @@ -1029,7 +1029,7 @@ class HtmlParserTest { val textView: TextView = it.findViewById(R.id.test_html_content_text_view) val htmlResult: Spannable = htmlParser.parseOppiaHtml( "" + + ""raw_latex":"x^2"}\">" + "", textView, supportsLinks = true,