Skip to content

Commit

Permalink
Fix #5022, #5030, and oppia#5031: Fix localization issues in Beta 0.11 (
Browse files Browse the repository at this point in the history
oppia#5040)

## Explanation
Fixes #5022
Fixes #5030
Fixes oppia#5031

First, it was found during investigation that some images aren't showing
up in Arabic or Brazilian Portuguese translations of lessons due to
their image control character (that indicates to Android an image needs
to be rendered) being replaced by empty reading text. This has been
addressed with a more robust content description check and three new
tests to detect this case and addresses #5022 and #5030. This was
determined due to an excellent investigation & finding by
@adhiamboperes.

Second, some specific images wouldn't render in Arabic specifically.
This was due to a bug in AndroidSVG (our dependency library used for
rendering SVG files). Per
https://github.com/oppia/androidsvg/blob/4bc1d26412f0fb9fd4ef263fa93f6a64f4d4dbcf/androidsvg/src/main/java/com/caverock/androidsvg/utils/CSSFontVariationSettings.java#L73
AndroidSVG uses ``DecimalFormat`` to format certain CSS font settings
(particularly in the case for dynamic fonts), and AndroidSVG isn't
following the conventional advice of
https://developer.android.com/reference/java/util/Locale (under "Be wary
of the default locale") and
https://docs.oracle.com/javase/8/docs/api/java/text/DecimalFormat.html
(specific advice to create formatters for specific locales). In summary,
AndroidSVG was formatting certain CSS parameters to floats without
accounting for the fact that the default locale may change the numeral
system when formatting those decimals. A patch to Oppia's fork of
AndroidSVG fixes this problem
(oppia/androidsvg@1265eb1).
Note that this fix will also be included in the later AndroidSVG changes
that the app will later be moved to. Additionally, these are
unfortunately not changes that can be easily tested without finding
specific SVGs to include in tests, and either scanning logcat or relying
on screenshot testing. A more suitable testing strategy would be
end-to-end tests vis-a-vis oppia#3839.

Third, bullets weren't rendering correctly in Arabic views of lessons
(oppia#5031). This was due to two issues:
1. The previous logic was always assuming a rightward shift to center
the bullet which doesn't work in RTL.
2. The previous logic was using the canvas width to invert X coordinates
rather than the clipped space. This wasn't caught previously because the
main tested content for bullets was a test revision card whose content
spans the width of the screen (which corresponds to the drawing area of
the canvas). However, lessons use a more constrained area due to content
margins, so the bullets were being placed off screen in the margin
whitespace that was being clipped.

See the following screenshots for a demonstration of the new content
description tests failing without the fix, and passing with it:
| Without fix | With fix |
|--------|--------|
|
![image](https://github.com/oppia/oppia-android/assets/12983742/bef30eb2-a966-426c-8902-8ee23850d94a)
|
![image](https://github.com/oppia/oppia-android/assets/12983742/4a69feae-13de-47ad-acb5-c28f4ee72dc3)
|

A few caveats with the bullet fixes:
- The FAQ bullets will still not render correctly in Arabic (technically
in all non-English languages) due to oppia#5034 which requires fixes in the
translation strings. They're actually missing the necessary markup
elements needed to show their bullet lists correctly.
- The FAQ style fix can only be tested via basic measurement
verification, but fortunately the existing test did require an update
and changed in the expected way (the calculated x position of the
bullets in the test have moved left 18 pixels). This isn't nearly as
good as a visual test, but that will require oppia#1815 to be finished,
first.

**Note** that this PR will need to be cherry-picked into the 0.11
release branch.

## Essential Checklist
- [x] The PR title and explanation each start with "Fix #bugnum: " (If
this PR fixes part of an issue, prefix the title with "Fix part of
#bugnum: ...".)
- [x] Any changes to
[scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets)
files have their rationale included in the PR explanation.
- [x] The PR follows the [style
guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide).
- [x] The PR does not contain any unnecessary code changes from Android
Studio
([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)).
- [x] The PR is made from a branch that's **not** called "develop" and
is up-to-date with "develop".
- [x] The PR is **assigned** to the appropriate reviewers
([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)).

## For UI-specific PRs only

Demonstrations of fixes for #5022 (note that, for the first Arabic
screenshots, the lesson image isn't actually localized in the content;
also, the Naija image does load without the PR fixes but it can be a bit
delayed):
| Before (without fixes) | After (with the fixes of this PR) |
|--------|--------|
|
![image](https://github.com/oppia/oppia-android/assets/12983742/6039721d-37e0-4d63-9c70-47cf60548e51)
|
![image](https://github.com/oppia/oppia-android/assets/12983742/7375e028-0d00-4aee-9c14-11cbde10d536)
|
|
![image](https://github.com/oppia/oppia-android/assets/12983742/8fdc7a59-4f49-4ce0-b166-68e3c69377f4)
|
![image](https://github.com/oppia/oppia-android/assets/12983742/90f80b21-308f-49df-b977-2454ed0a7cef)
|
|
![image](https://github.com/oppia/oppia-android/assets/12983742/b5a912b1-7583-40d8-9632-1a7e5562c1e1)
|
![image](https://github.com/oppia/oppia-android/assets/12983742/04a6e2b5-0e71-4e30-8b47-b1c99e16d3cf)
|

Demonstrations of fixes for #5030 (only some images actually hit this
bug):

| Before (without fixes) | After (with the fixes of this PR) |
|--------|--------|
|
![image](https://github.com/oppia/oppia-android/assets/12983742/95a989d6-88d0-4c85-ade8-c86356a0986a)
|
![image](https://github.com/oppia/oppia-android/assets/12983742/905dc36b-de9a-45c1-8ee3-1c202d2602c4)
|
|
![image](https://github.com/oppia/oppia-android/assets/12983742/2cab94b6-c13c-48e5-b53c-7c2826c6f413)
|
![image](https://github.com/oppia/oppia-android/assets/12983742/b5f4d2f9-dafb-476c-8763-eca84f407431)
|
|
![image](https://github.com/oppia/oppia-android/assets/12983742/0c292e78-a00a-45cb-abb9-9bd141dfc7ff)
|
![image](https://github.com/oppia/oppia-android/assets/12983742/6d67652f-b15a-421e-8b87-8ed4365d19f3)
|

Demonstrations of fixes for #5022, #5030, and oppia#5031 (since Arabic
lessons have conflated failures for images, and some of these images
also demonstrate the bullet list issue):

| Before (without fixes) | After (with the fixes of this PR) |
|--------|--------|
|
![image](https://github.com/oppia/oppia-android/assets/12983742/78a6d788-3968-4441-afcf-f7ea9cde270c)
|
![image](https://github.com/oppia/oppia-android/assets/12983742/08b02727-a6fe-43d9-9631-acdd9f03fe02)
|
|
![image](https://github.com/oppia/oppia-android/assets/12983742/8e4943e6-6277-4e97-80f8-a57e384c5354)
|
![image](https://github.com/oppia/oppia-android/assets/12983742/d292e871-7ebc-425f-9127-49edbad92ff7)
|
|
![image](https://github.com/oppia/oppia-android/assets/12983742/315d65a9-10ff-43e8-b472-322df5f4ef37)
|
![image](https://github.com/oppia/oppia-android/assets/12983742/b62e0226-aec4-4a49-acc4-f6f83c1c7509)
|

---------

Co-authored-by: Adhiambo Peres <[email protected]>
  • Loading branch information
BenHenning and adhiamboperes committed Jun 9, 2023
1 parent dae6359 commit 68e4574
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 13 deletions.
4 changes: 2 additions & 2 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,9 @@ git_repository(
# to correctly size in-line SVGs (such as those needed for LaTeX-based math expressions).
git_repository(
name = "androidsvg",
commit = "4bc1d26412f0fb9fd4ef263fa93f6a64f4d4dbcf",
commit = "1265eb1087056cf3fc2e10442e5545bc65c109ce",
remote = "https://github.com/oppia/androidsvg",
shallow_since = "1647295507 -0700",
shallow_since = "1686302944 -0700",
)

git_repository(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -562,16 +562,16 @@ class ListItemLeadingMarginSpanTest {
textView.layout
)

assertThat(shadowCanvas.getDrawnCircle(0).centerX).isWithin(1e-5f).of(944.0f)
assertThat(shadowCanvas.getDrawnCircle(0).centerX).isWithin(1e-5f).of(926.0f)
assertThat(shadowCanvas.getDrawnCircle(0).centerY).isWithin(1e-5f).of(48.0f)

assertThat(shadowCanvas.getDrawnCircle(1).centerX).isWithin(1e-5f).of(878.0f)
assertThat(shadowCanvas.getDrawnCircle(1).centerX).isWithin(1e-5f).of(860.0f)
assertThat(shadowCanvas.getDrawnCircle(1).centerY).isWithin(1e-5f).of(139.0f)

assertThat(shadowCanvas.getDrawnCircle(2).centerX).isWithin(1e-5f).of(878.0f)
assertThat(shadowCanvas.getDrawnCircle(2).centerX).isWithin(1e-5f).of(860.0f)
assertThat(shadowCanvas.getDrawnCircle(2).centerY).isWithin(1e-5f).of(225.0f)

assertThat(shadowCanvas.getDrawnCircle(3).centerX).isWithin(1e-5f).of(944.0f)
assertThat(shadowCanvas.getDrawnCircle(3).centerX).isWithin(1e-5f).of(926.0f)
assertThat(shadowCanvas.getDrawnCircle(3).centerY).isWithin(1e-5f).of(397.0f)
}

Expand Down
2 changes: 1 addition & 1 deletion utility/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ dependencies {
'androidx.appcompat:appcompat:1.0.2',
'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-alpha03',
'androidx.work:work-runtime-ktx:2.4.0',
'com.github.oppia:androidsvg:4bc1d26412f0fb9fd4ef263fa93f6a64f4d4dbcf',
'com.github.oppia:androidsvg:1265eb1087056cf3fc2e10442e5545bc65c109ce',
'com.github.oppia:kotlitex:43139c140833c7120f351d63d74b42c253d2b213',
'com.github.bumptech.glide:glide:4.11.0',
'com.google.dagger:dagger:2.24',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,12 @@ class ImageTagHandler(
)
}
} else consoleLogger.e("ImageTagHandler", "Failed to parse image tag")
if (contentDescription != null) {
if (!contentDescription.isNullOrBlank()) {
val spannableBuilder = SpannableStringBuilder(contentDescription)
spannableBuilder.setSpan(
contentDescription,
/* start= */ 0,
/* end= */ contentDescription.length,
/* start = */ 0,
/* end = */ contentDescription.length,
Spannable.SPAN_INCLUSIVE_EXCLUSIVE
)
output.replace(openIndex, output.length, spannableBuilder)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ sealed class ListItemLeadingMarginSpan : LeadingMarginSpan {
private val isRtl by lazy {
displayLocale.getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL
}
private val clipBounds by lazy { Rect() }

override fun drawLeadingMargin(
canvas: Canvas,
Expand All @@ -69,8 +70,14 @@ sealed class ListItemLeadingMarginSpan : LeadingMarginSpan {
val bulletDrawRadius = bulletRadius.toFloat()

val indentedX = parentAbsoluteLeadingMargin + spacingBeforeBullet
val bulletStartX = (if (isRtl) canvas.width - indentedX - 1 else indentedX).toFloat()
val bulletCenterX = bulletStartX + bulletDrawRadius
val bulletCenterLtrX = indentedX + bulletDrawRadius
val bulletCenterX = if (isRtl) {
// See https://stackoverflow.com/a/21845993/3689782 for 'right' property exclusivity.
val maxDrawX = if (canvas.getClipBounds(clipBounds)) {
clipBounds.right - 1
} else canvas.width - 1
maxDrawX - bulletCenterLtrX
} else bulletCenterLtrX
val bulletCenterY = (top + bottom) / 2f
when (indentationLevel) {
0 -> {
Expand All @@ -90,7 +97,7 @@ sealed class ListItemLeadingMarginSpan : LeadingMarginSpan {
val rectSize = bulletDiameter.toFloat()
canvas.drawRect(
RectF().apply {
left = bulletStartX
left = bulletCenterX
right = left + rectSize
this.top = bulletCenterY - bulletDrawRadius
this.bottom = this.top + rectSize
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,21 @@ private const val IMAGE_TAG_WITHOUT_ALT_VALUE_MARKUP =
"<oppia-noninteractive-image caption-with-value=\"&amp;quot;&amp;quot;\" " +
"filepath-with-value=\"&amp;quot;test_image1.png&amp;quot;\"></oppia-noninteractive-image>"

private const val IMAGE_TAG_WITH_EMPTY_ALT_VALUE_MARKUP =
"<oppia-noninteractive-image alt-with-value=\"\" " +
"caption-with-value=\"&amp;quot;&amp;quot;\" " +
"filepath-with-value=\"&amp;quot;test_image1.png&amp;quot;\"></oppia-noninteractive-image>"

private const val IMAGE_TAG_WITH_EMPTY_STRING_ALT_VALUE_MARKUP =
"<oppia-noninteractive-image alt-with-value=\"&amp;quot;&amp;quot;\" " +
"caption-with-value=\"&amp;quot;&amp;quot;\" " +
"filepath-with-value=\"&amp;quot;test_image1.png&amp;quot;\"></oppia-noninteractive-image>"

private const val IMAGE_TAG_WITH_SPACE_ONLY_ALT_VALUE_MARKUP =
"<oppia-noninteractive-image alt-with-value=\"&amp;quot; &amp;quot;\" " +
"caption-with-value=\"&amp;quot;&amp;quot;\" " +
"filepath-with-value=\"&amp;quot;test_image1.png&amp;quot;\"></oppia-noninteractive-image>"

/** Tests for [ImageTagHandler]. */
@RunWith(AndroidJUnit4::class)
@LooperMode(LooperMode.Mode.PAUSED)
Expand Down Expand Up @@ -138,6 +153,51 @@ class ImageTagHandlerTest {
assertThat(parsedHtml.first().isObjectReplacementCharacter()).isFalse()
}

@Test
fun testParseHtml_withEmptyImageCardMarkup_hasNoReadableText() {
val parsedHtml =
CustomHtmlContentHandler.fromHtml(
html = IMAGE_TAG_WITH_EMPTY_ALT_VALUE_MARKUP,
imageRetriever = mockImageRetriever,
customTagHandlers = tagHandlersWithImageTagSupport
)

// If the alt text is present but empty, then only the image control character should show.
val parsedHtmlStr = parsedHtml.toString()
assertThat(parsedHtmlStr).hasLength(1)
assertThat(parsedHtmlStr.first().isObjectReplacementCharacter()).isTrue()
}

@Test
fun testParseHtml_withEmptyImageCardMarkupString_hasNoReadableText() {
val parsedHtml =
CustomHtmlContentHandler.fromHtml(
html = IMAGE_TAG_WITH_EMPTY_STRING_ALT_VALUE_MARKUP,
imageRetriever = mockImageRetriever,
customTagHandlers = tagHandlersWithImageTagSupport
)

// If the alt text is present but empty, then only the image control character should show.
val parsedHtmlStr = parsedHtml.toString()
assertThat(parsedHtmlStr).hasLength(1)
assertThat(parsedHtmlStr.first().isObjectReplacementCharacter()).isTrue()
}

@Test
fun testParseHtml_withSpaceOnlyImageCardMarkup_hasNoReadableText() {
val parsedHtml =
CustomHtmlContentHandler.fromHtml(
html = IMAGE_TAG_WITH_SPACE_ONLY_ALT_VALUE_MARKUP,
imageRetriever = mockImageRetriever,
customTagHandlers = tagHandlersWithImageTagSupport
)

// If the alt text is present but only spaces, then the image control character should show.
val parsedHtmlStr = parsedHtml.toString()
assertThat(parsedHtmlStr).hasLength(1)
assertThat(parsedHtmlStr.first().isObjectReplacementCharacter()).isTrue()
}

@Test
fun testParseHtml_withImageCardMarkup_missingFilename_doesNotIncludeImageSpan() {
val parsedHtml =
Expand Down

0 comments on commit 68e4574

Please sign in to comment.