@@ -2,7 +2,9 @@ package org.oppia.android.util.parser.html
2
2
3
3
import android.app.Application
4
4
import android.content.Context
5
+ import android.graphics.Canvas
5
6
import android.graphics.Color
7
+ import android.graphics.Paint
6
8
import android.text.Html
7
9
import android.text.Spannable
8
10
import android.text.style.ImageSpan
@@ -21,6 +23,8 @@ import org.junit.runner.RunWith
21
23
import org.mockito.ArgumentCaptor
22
24
import org.mockito.Captor
23
25
import org.mockito.Mock
26
+ import org.mockito.Mockito.eq
27
+ import org.mockito.Mockito.mock
24
28
import org.mockito.Mockito.times
25
29
import org.mockito.Mockito.verify
26
30
import org.mockito.Mockito.verifyNoMoreInteractions
@@ -39,6 +43,7 @@ import org.robolectric.annotation.Config
39
43
import org.robolectric.annotation.LooperMode
40
44
import javax.inject.Inject
41
45
import javax.inject.Singleton
46
+ import kotlin.math.abs
42
47
import kotlin.reflect.KClass
43
48
44
49
private const val MATH_MARKUP_1 =
@@ -107,6 +112,129 @@ class MathTagHandlerTest {
107
112
}
108
113
109
114
// TODO(#3085): Introduce test for verifying that the error log scenario is logged correctly.
115
+ @Test
116
+ fun testParseHtml_withMathMarkup_cachingOn_imageSpanHasCorrectMetrics () {
117
+
118
+ val parsedHtml = CustomHtmlContentHandler .fromHtml(
119
+ html = MATH_WITHOUT_FILENAME_MARKUP ,
120
+ imageRetriever = mockImageRetriever,
121
+ customTagHandlers = tagHandlersWithCachedMathSupport
122
+ )
123
+ val imageSpans = parsedHtml.getSpansFromWholeString(ImageSpan ::class )
124
+ assertThat(imageSpans).hasLength(1 )
125
+
126
+ val paint = Paint ()
127
+ paint.textSize = 20f
128
+ val originalMetrics = Paint .FontMetricsInt ()
129
+ paint.getFontMetricsInt(originalMetrics)
130
+
131
+ val spanMetrics = Paint .FontMetricsInt ()
132
+ imageSpans[0 ].getSize(paint, parsedHtml, 0 , parsedHtml.length, spanMetrics)
133
+
134
+ // The span's center should align with the text's center
135
+ val originalCenter = (originalMetrics.descent + originalMetrics.ascent) / 2
136
+ val spanCenter = (spanMetrics.descent + spanMetrics.ascent) / 2
137
+ assertThat(abs(originalCenter - spanCenter)).isLessThan(2 )
138
+ }
139
+
140
+ @Test
141
+ fun testParseHtml_withMathMarkup_cachingOn_drawsAtCorrectVerticalPosition () {
142
+
143
+ val parsedHtml = CustomHtmlContentHandler .fromHtml(
144
+ html = MATH_WITHOUT_FILENAME_MARKUP ,
145
+ imageRetriever = mockImageRetriever,
146
+ customTagHandlers = tagHandlersWithCachedMathSupport
147
+ )
148
+
149
+ val imageSpans = parsedHtml.getSpansFromWholeString(ImageSpan ::class )
150
+ assertThat(imageSpans).hasLength(1 )
151
+
152
+ val mockCanvas = mock(Canvas ::class .java)
153
+ val paint = Paint ()
154
+ paint.textSize = 20f
155
+
156
+ val metrics = paint.fontMetricsInt
157
+ val y = 100
158
+
159
+ imageSpans[0 ].draw(
160
+ mockCanvas,
161
+ parsedHtml,
162
+ 0 ,
163
+ parsedHtml.length,
164
+ 0f ,
165
+ 0 ,
166
+ y,
167
+ 200 ,
168
+ paint
169
+ )
170
+
171
+ val textHeight = (metrics.descent - metrics.ascent).toFloat()
172
+ val textMidline = y.toFloat() - (textHeight / 2f )
173
+ val verticalShift = metrics.descent * 0.9f
174
+ val drawable = imageSpans[0 ].drawable
175
+ val expectedTranslation = textMidline + verticalShift - (drawable.bounds.height() / 2f )
176
+
177
+ // The translation should position the drawable centered around the text baseline
178
+ verify(mockCanvas).save()
179
+ verify(mockCanvas).translate(
180
+ eq(0f ),
181
+ capture(floatCaptor)
182
+ )
183
+ assertThat(floatCaptor.value).isWithin(1f ).of(expectedTranslation)
184
+ verify(mockCanvas).restore()
185
+ }
186
+
187
+ @Test
188
+ fun testParseHtml_withMathMarkup_cachingOn_maintainsConsistentHeight () {
189
+
190
+ val parsedHtml = CustomHtmlContentHandler .fromHtml(
191
+ html = MATH_WITHOUT_FILENAME_MARKUP ,
192
+ imageRetriever = mockImageRetriever,
193
+ customTagHandlers = tagHandlersWithCachedMathSupport
194
+ )
195
+
196
+ val imageSpans = parsedHtml.getSpansFromWholeString(ImageSpan ::class )
197
+ assertThat(imageSpans).hasLength(1 )
198
+
199
+ val paint = Paint ()
200
+ paint.textSize = 20f
201
+
202
+ val metrics1 = Paint .FontMetricsInt ()
203
+ val metrics2 = Paint .FontMetricsInt ()
204
+
205
+ val size1 = imageSpans[0 ].getSize(paint, parsedHtml, 0 , parsedHtml.length, metrics1)
206
+ val size2 = imageSpans[0 ].getSize(paint, parsedHtml, 0 , parsedHtml.length, metrics2)
207
+
208
+ assertThat(size1).isEqualTo(size2)
209
+ assertThat(metrics1.ascent).isEqualTo(metrics2.ascent)
210
+ assertThat(metrics1.descent).isEqualTo(metrics2.descent)
211
+ assertThat(metrics1.top).isEqualTo(metrics2.top)
212
+ assertThat(metrics1.bottom).isEqualTo(metrics2.bottom)
213
+ }
214
+
215
+ @Test
216
+ fun testParseHtml_withMathMarkup_cachingOn_respectsLineHeight () {
217
+
218
+ val parsedHtml = CustomHtmlContentHandler .fromHtml(
219
+ html = MATH_WITHOUT_FILENAME_MARKUP ,
220
+ imageRetriever = mockImageRetriever,
221
+ customTagHandlers = tagHandlersWithCachedMathSupport
222
+ )
223
+
224
+ val imageSpans = parsedHtml.getSpansFromWholeString(ImageSpan ::class )
225
+ assertThat(imageSpans).hasLength(1 )
226
+
227
+ val paint = Paint ()
228
+ paint.textSize = 20f
229
+
230
+ val metrics = Paint .FontMetricsInt ()
231
+ imageSpans[0 ].getSize(paint, parsedHtml, 0 , parsedHtml.length, metrics)
232
+
233
+ // Verify that the total height does not exceed the line height
234
+ val totalHeight = metrics.bottom - metrics.top
235
+ val lineHeight = paint.textSize * 1.2f
236
+ assertThat(totalHeight.toFloat()).isLessThan(lineHeight)
237
+ }
110
238
111
239
@Test
112
240
fun testParseHtml_emptyString_doesNotIncludeImageSpan () {
0 commit comments