Skip to content

Commit

Permalink
adding tests
Browse files Browse the repository at this point in the history
  • Loading branch information
manas-yu committed Jan 7, 2025
1 parent b691a20 commit 29f1c03
Show file tree
Hide file tree
Showing 2 changed files with 199 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package org.oppia.android.util.parser.html

import android.app.Application
import android.content.res.AssetManager
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.drawable.Drawable
import android.text.Editable
import android.text.Spannable
import android.text.style.ImageSpan
Expand Down Expand Up @@ -60,7 +63,7 @@ class MathTagHandler(
}
is MathContent.MathAsLatex -> {
if (cacheLatexRendering) {
ImageSpan(
CenteredLatexImageSpan(
imageRetriever.loadMathDrawable(
content.rawLatex,
lineHeight,
Expand Down Expand Up @@ -139,3 +142,73 @@ class MathTagHandler(
}
}
}

/** An [ImageSpan] that vertically centers a LaTeX drawable within the surrounding text. */
private class CenteredLatexImageSpan(drawable: Drawable?) :
ImageSpan(drawable ?: createEmptyDrawable()) {
override fun getSize(
paint: Paint,
text: CharSequence,
start: Int,
end: Int,
fontMetricsInt: Paint.FontMetricsInt?
): Int {
val d = drawable
val bounds = d.bounds

fontMetricsInt?.let {
val paintFm = paint.fontMetricsInt
val textHeight = paintFm.descent - paintFm.ascent
val latexHeight = bounds.height()

val centeringOffset = (textHeight - latexHeight) / 2

it.ascent = paintFm.ascent + centeringOffset
it.top = paintFm.top + centeringOffset
it.descent = it.ascent + latexHeight
it.bottom = it.top + latexHeight
}

return bounds.right
}

override fun draw(
canvas: Canvas,
text: CharSequence,
start: Int,
end: Int,
x: Float,
top: Int,
y: Int,
bottom: Int,
paint: Paint
) {
val d = drawable
canvas.save()

val fontMetrics = paint.fontMetricsInt
val latexHeight = d.bounds.height()

val centerY = y + (fontMetrics.descent + fontMetrics.ascent) / 2
val drawableY = centerY - (latexHeight / 2)

canvas.translate(x, drawableY.toFloat())
d.draw(canvas)
canvas.restore()
}

companion object {
private fun createEmptyDrawable(): Drawable {
return object : Drawable() {
override fun draw(canvas: Canvas) {}
override fun setAlpha(alpha: Int) {}
override fun setColorFilter(colorFilter: android.graphics.ColorFilter?) {}
override fun getOpacity(): Int = android.graphics.PixelFormat.TRANSPARENT

init {
setBounds(0, 0, 1, 1)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package org.oppia.android.util.parser.html

import android.app.Application
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.text.Html
import android.text.Spannable
import android.text.style.ImageSpan
Expand Down Expand Up @@ -39,7 +41,10 @@ import org.robolectric.annotation.Config
import org.robolectric.annotation.LooperMode
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.abs
import kotlin.reflect.KClass
import org.mockito.Mockito.mock
import org.mockito.Mockito.eq

private const val MATH_MARKUP_1 =
"<oppia-noninteractive-math math_content-with-value=\"{" +
Expand Down Expand Up @@ -107,7 +112,127 @@ class MathTagHandlerTest {
}

// TODO(#3085): Introduce test for verifying that the error log scenario is logged correctly.
@Test
fun testParseHtml_withMathMarkup_cachingOn_imageSpanHasCorrectMetrics() {

val parsedHtml = CustomHtmlContentHandler.fromHtml(
html = MATH_WITHOUT_FILENAME_MARKUP,
imageRetriever = mockImageRetriever,
customTagHandlers = tagHandlersWithCachedMathSupport
)
val imageSpans = parsedHtml.getSpansFromWholeString(ImageSpan::class)
assertThat(imageSpans).hasLength(1)

val paint = Paint()
paint.textSize = 20f
val originalMetrics = Paint.FontMetricsInt()
paint.getFontMetricsInt(originalMetrics)

val spanMetrics = Paint.FontMetricsInt()
imageSpans[0].getSize(paint, parsedHtml, 0, parsedHtml.length, spanMetrics)

// The span's center should align with the text's center
val originalCenter = (originalMetrics.descent + originalMetrics.ascent) / 2
val spanCenter = (spanMetrics.descent + spanMetrics.ascent) / 2
assertThat(abs(originalCenter - spanCenter)).isLessThan(2)
}

@Test
fun testParseHtml_withMathMarkup_cachingOn_drawsAtCorrectVerticalPosition() {

val parsedHtml = CustomHtmlContentHandler.fromHtml(
html = MATH_WITHOUT_FILENAME_MARKUP,
imageRetriever = mockImageRetriever,
customTagHandlers = tagHandlersWithCachedMathSupport
)

val imageSpans = parsedHtml.getSpansFromWholeString(ImageSpan::class)
assertThat(imageSpans).hasLength(1)

val mockCanvas = mock(Canvas::class.java)
val paint = Paint()
paint.textSize = 20f

val metrics = paint.fontMetricsInt
val y = 100
val expectedCenterY = y + (metrics.descent + metrics.ascent) / 2f

imageSpans[0].draw(
mockCanvas,
parsedHtml,
0,
parsedHtml.length,
0f,
0,
y,
200,
paint
)
// The canvas should be translated to center the drawable vertically
verify(mockCanvas).save()
verify(mockCanvas).translate(
eq(0f),
capture(floatCaptor)
)
// The translation should position the drawable centered around the text baseline
val drawable = imageSpans[0].drawable
val expectedTranslation = expectedCenterY - (drawable.bounds.height() / 2)
assertThat(floatCaptor.value).isWithin(1f).of(expectedTranslation)

verify(mockCanvas).restore()
}

@Test
fun testParseHtml_withMathMarkup_cachingOn_maintainsConsistentHeight() {

val parsedHtml = CustomHtmlContentHandler.fromHtml(
html = MATH_WITHOUT_FILENAME_MARKUP,
imageRetriever = mockImageRetriever,
customTagHandlers = tagHandlersWithCachedMathSupport
)

val imageSpans = parsedHtml.getSpansFromWholeString(ImageSpan::class)
assertThat(imageSpans).hasLength(1)

val paint = Paint()
paint.textSize = 20f

val metrics1 = Paint.FontMetricsInt()
val metrics2 = Paint.FontMetricsInt()

val size1 = imageSpans[0].getSize(paint, parsedHtml, 0, parsedHtml.length, metrics1)
val size2 = imageSpans[0].getSize(paint, parsedHtml, 0, parsedHtml.length, metrics2)

assertThat(size1).isEqualTo(size2)
assertThat(metrics1.ascent).isEqualTo(metrics2.ascent)
assertThat(metrics1.descent).isEqualTo(metrics2.descent)
assertThat(metrics1.top).isEqualTo(metrics2.top)
assertThat(metrics1.bottom).isEqualTo(metrics2.bottom)
}

@Test
fun testParseHtml_withMathMarkup_cachingOn_respectsLineHeight() {

val parsedHtml = CustomHtmlContentHandler.fromHtml(
html = MATH_WITHOUT_FILENAME_MARKUP,
imageRetriever = mockImageRetriever,
customTagHandlers = tagHandlersWithCachedMathSupport
)

val imageSpans = parsedHtml.getSpansFromWholeString(ImageSpan::class)
assertThat(imageSpans).hasLength(1)

val paint = Paint()
paint.textSize = 20f

val metrics = Paint.FontMetricsInt()
imageSpans[0].getSize(paint, parsedHtml, 0, parsedHtml.length, metrics)

// Verify that the total height does not exceed the line height
val totalHeight = metrics.bottom - metrics.top
val lineHeight = paint.textSize * 1.2f
assertThat(totalHeight.toFloat()).isLessThan(lineHeight)
}
@Test
fun testParseHtml_emptyString_doesNotIncludeImageSpan() {
val parsedHtml =
Expand Down

0 comments on commit 29f1c03

Please sign in to comment.