diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SpannedToHtmlConverter.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SpannedToHtmlConverter.java index f534b9e397..e06edd8ded 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SpannedToHtmlConverter.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SpannedToHtmlConverter.java @@ -19,6 +19,7 @@ package com.google.android.exoplayer2.ui; import android.graphics.Typeface; import android.text.Html; import android.text.Spanned; +import android.text.style.AbsoluteSizeSpan; import android.text.style.BackgroundColorSpan; import android.text.style.ForegroundColorSpan; import android.text.style.StyleSpan; @@ -66,8 +67,12 @@ import java.util.regex.Pattern; *
  • WebView/Chromium (the intended destination of this HTML) gracefully handles overlapping * tags and usually renders the same result as spanned text in a TextView. * + * + * @param text The (possibly span-styled) text to convert to HTML. + * @param displayDensity The screen density of the device. WebView treats 1 CSS px as one Android + * dp, so to convert size values from Android px to CSS px we need to know the screen density. */ - public static String convert(@Nullable CharSequence text) { + public static String convert(@Nullable CharSequence text, float displayDensity) { if (text == null) { return ""; } @@ -75,7 +80,7 @@ import java.util.regex.Pattern; return escapeHtml(text); } Spanned spanned = (Spanned) text; - SparseArray spanTransitions = findSpanTransitions(spanned); + SparseArray spanTransitions = findSpanTransitions(spanned, displayDensity); StringBuilder html = new StringBuilder(spanned.length()); int previousTransition = 0; @@ -100,11 +105,12 @@ import java.util.regex.Pattern; return html.toString(); } - private static SparseArray findSpanTransitions(Spanned spanned) { + private static SparseArray findSpanTransitions( + Spanned spanned, float displayDensity) { SparseArray spanTransitions = new SparseArray<>(); for (Object span : spanned.getSpans(0, spanned.length(), Object.class)) { - @Nullable String openingTag = getOpeningTag(span); + @Nullable String openingTag = getOpeningTag(span, displayDensity); @Nullable String closingTag = getClosingTag(span); int spanStart = spanned.getSpanStart(span); int spanEnd = spanned.getSpanEnd(span); @@ -120,7 +126,7 @@ import java.util.regex.Pattern; } @Nullable - private static String getOpeningTag(Object span) { + private static String getOpeningTag(Object span, float displayDensity) { if (span instanceof ForegroundColorSpan) { ForegroundColorSpan colorSpan = (ForegroundColorSpan) span; return Util.formatInvariant( @@ -132,6 +138,13 @@ import java.util.regex.Pattern; HtmlUtils.toCssRgba(colorSpan.getBackgroundColor())); } else if (span instanceof HorizontalTextInVerticalContextSpan) { return ""; + } else if (span instanceof AbsoluteSizeSpan) { + AbsoluteSizeSpan absoluteSizeSpan = (AbsoluteSizeSpan) span; + float sizeCssPx = + absoluteSizeSpan.getDip() + ? absoluteSizeSpan.getSize() + : absoluteSizeSpan.getSize() / displayDensity; + return Util.formatInvariant("", sizeCssPx); } else if (span instanceof TypefaceSpan) { @Nullable String fontFamily = ((TypefaceSpan) span).getFamily(); return fontFamily != null @@ -171,7 +184,8 @@ import java.util.regex.Pattern; private static String getClosingTag(Object span) { if (span instanceof ForegroundColorSpan || span instanceof BackgroundColorSpan - || span instanceof HorizontalTextInVerticalContextSpan) { + || span instanceof HorizontalTextInVerticalContextSpan + || span instanceof AbsoluteSizeSpan) { return ""; } else if (span instanceof TypefaceSpan) { @Nullable String fontFamily = ((TypefaceSpan) span).getFamily(); diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleWebView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleWebView.java index 480d057212..ee081f384e 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleWebView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleWebView.java @@ -309,7 +309,9 @@ import java.util.List; horizontalTranslatePercent, verticalTranslatePercent)) .append(Util.formatInvariant("", backgroundColorCss)) - .append(SpannedToHtmlConverter.convert(cue.text)) + .append( + SpannedToHtmlConverter.convert( + cue.text, getContext().getResources().getDisplayMetrics().density)) .append("") .append(""); } diff --git a/library/ui/src/test/java/com/google/android/exoplayer2/ui/SpannedToHtmlConverterTest.java b/library/ui/src/test/java/com/google/android/exoplayer2/ui/SpannedToHtmlConverterTest.java index b6a6c5a323..c64d5cabcf 100644 --- a/library/ui/src/test/java/com/google/android/exoplayer2/ui/SpannedToHtmlConverterTest.java +++ b/library/ui/src/test/java/com/google/android/exoplayer2/ui/SpannedToHtmlConverterTest.java @@ -22,21 +22,31 @@ import android.graphics.Color; import android.graphics.Typeface; import android.text.SpannableString; import android.text.Spanned; +import android.text.style.AbsoluteSizeSpan; import android.text.style.BackgroundColorSpan; import android.text.style.ForegroundColorSpan; import android.text.style.StyleSpan; import android.text.style.TypefaceSpan; import android.text.style.UnderlineSpan; +import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.text.span.HorizontalTextInVerticalContextSpan; import com.google.android.exoplayer2.text.span.RubySpan; import org.junit.Test; import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; /** Tests for {@link SpannedToHtmlConverter}. */ @RunWith(AndroidJUnit4.class) public class SpannedToHtmlConverterTest { + private final float displayDensity; + + public SpannedToHtmlConverterTest() { + displayDensity = + ApplicationProvider.getApplicationContext().getResources().getDisplayMetrics().density; + } + @Test public void convert_supportsForegroundColorSpan() { SpannableString spanned = new SpannableString("String with colored section"); @@ -46,7 +56,7 @@ public class SpannedToHtmlConverterTest { "String with colored".length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - String html = SpannedToHtmlConverter.convert(spanned); + String html = SpannedToHtmlConverter.convert(spanned, displayDensity); assertThat(html) .isEqualTo("String with colored section"); @@ -61,7 +71,7 @@ public class SpannedToHtmlConverterTest { "String with highlighted".length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - String html = SpannedToHtmlConverter.convert(spanned); + String html = SpannedToHtmlConverter.convert(spanned, displayDensity); assertThat(html) .isEqualTo( @@ -78,7 +88,7 @@ public class SpannedToHtmlConverterTest { "Vertical text with 123".length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - String html = SpannedToHtmlConverter.convert(spanned); + String html = SpannedToHtmlConverter.convert(spanned, displayDensity); assertThat(html) .isEqualTo( @@ -86,6 +96,40 @@ public class SpannedToHtmlConverterTest { + "horizontal numbers"); } + // Set the screen density so we see that px are handled differently to dp. + @Config(qualifiers = "xhdpi") + @Test + public void convert_supportsAbsoluteSizeSpan_px() { + SpannableString spanned = new SpannableString("String with 10px section"); + spanned.setSpan( + new AbsoluteSizeSpan(/* size= */ 10, /* dip= */ false), + "String with ".length(), + "String with 10px".length(), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + + String html = SpannedToHtmlConverter.convert(spanned, displayDensity); + + // 10 Android px are converted to 5 CSS px because WebView treats 1 CSS px as 1 Android dp + // and we're using screen density xhdpi i.e. density=2. + assertThat(html).isEqualTo("String with 10px section"); + } + + // Set the screen density so we see that px are handled differently to dp. + @Config(qualifiers = "xhdpi") + @Test + public void convert_supportsAbsoluteSizeSpan_dp() { + SpannableString spanned = new SpannableString("String with 10dp section"); + spanned.setSpan( + new AbsoluteSizeSpan(/* size= */ 10, /* dip= */ true), + "String with ".length(), + "String with 10dp".length(), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + + String html = SpannedToHtmlConverter.convert(spanned, displayDensity); + + assertThat(html).isEqualTo("String with 10dp section"); + } + @Test public void convert_supportsTypefaceSpan() { SpannableString spanned = new SpannableString("String with Times New Roman section"); @@ -95,7 +139,7 @@ public class SpannedToHtmlConverterTest { "String with Times New Roman".length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - String html = SpannedToHtmlConverter.convert(spanned); + String html = SpannedToHtmlConverter.convert(spanned, displayDensity); assertThat(html) .isEqualTo( @@ -112,7 +156,7 @@ public class SpannedToHtmlConverterTest { "String with unstyled".length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - String html = SpannedToHtmlConverter.convert(spanned); + String html = SpannedToHtmlConverter.convert(spanned, displayDensity); assertThat(html).isEqualTo("String with unstyled section"); } @@ -137,7 +181,7 @@ public class SpannedToHtmlConverterTest { "String with bold, italic and bold-italic".length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - String html = SpannedToHtmlConverter.convert(spanned); + String html = SpannedToHtmlConverter.convert(spanned, displayDensity); assertThat(html) .isEqualTo( @@ -159,7 +203,7 @@ public class SpannedToHtmlConverterTest { "String with over-annotated and under-annotated".length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - String html = SpannedToHtmlConverter.convert(spanned); + String html = SpannedToHtmlConverter.convert(spanned, displayDensity); assertThat(html) .isEqualTo( @@ -185,28 +229,31 @@ public class SpannedToHtmlConverterTest { "String with underlined".length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - String html = SpannedToHtmlConverter.convert(spanned); + String html = SpannedToHtmlConverter.convert(spanned, displayDensity); assertThat(html).isEqualTo("String with underlined section."); } @Test public void convert_escapesHtmlInUnspannedString() { - String html = SpannedToHtmlConverter.convert("String with bold tags"); + String html = SpannedToHtmlConverter.convert("String with bold tags", displayDensity); assertThat(html).isEqualTo("String with <b>bold</b> tags"); } @Test public void convert_handlesLinebreakInUnspannedString() { - String html = SpannedToHtmlConverter.convert("String with\nnew line and\r\ncrlf style too"); + String html = + SpannedToHtmlConverter.convert( + "String with\nnew line and\r\ncrlf style too", displayDensity); assertThat(html).isEqualTo("String with
    new line and
    crlf style too"); } @Test public void convert_doesntConvertAmpersandLineFeedToBrTag() { - String html = SpannedToHtmlConverter.convert("String with new line ampersand code"); + String html = + SpannedToHtmlConverter.convert("String with new line ampersand code", displayDensity); assertThat(html).isEqualTo("String with&#10;new line ampersand code"); } @@ -220,14 +267,16 @@ public class SpannedToHtmlConverterTest { "String with unrecognised".length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - String html = SpannedToHtmlConverter.convert(spanned); + String html = SpannedToHtmlConverter.convert(spanned, displayDensity); assertThat(html).isEqualTo("String with <foo>unrecognised</foo> tags"); } @Test public void convert_handlesLinebreakInSpannedString() { - String html = SpannedToHtmlConverter.convert("String with\nnew line and\r\ncrlf style too"); + String html = + SpannedToHtmlConverter.convert( + "String with\nnew line and\r\ncrlf style too", displayDensity); assertThat(html).isEqualTo("String with
    new line and
    crlf style too"); } @@ -236,7 +285,7 @@ public class SpannedToHtmlConverterTest { public void convert_convertsNonAsciiCharactersToAmpersandCodes() { String html = SpannedToHtmlConverter.convert( - new SpannableString("Strìng with 優しいの non-ASCII characters")); + new SpannableString("Strìng with 優しいの non-ASCII characters"), displayDensity); assertThat(html) .isEqualTo("Strìng with 優しいの non-ASCII characters"); @@ -256,7 +305,7 @@ public class SpannedToHtmlConverterTest { "String with unrecognised".length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - String html = SpannedToHtmlConverter.convert(spanned); + String html = SpannedToHtmlConverter.convert(spanned, displayDensity); assertThat(html).isEqualTo("String with unrecognised span"); } @@ -270,7 +319,7 @@ public class SpannedToHtmlConverterTest { spanned.setSpan(new StyleSpan(Typeface.ITALIC), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); spanned.setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - String html = SpannedToHtmlConverter.convert(spanned); + String html = SpannedToHtmlConverter.convert(spanned, displayDensity); assertThat(html).isEqualTo("String with italic-bold-underlined section"); } @@ -287,7 +336,7 @@ public class SpannedToHtmlConverterTest { "String with italic and bold".length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - String html = SpannedToHtmlConverter.convert(spanned); + String html = SpannedToHtmlConverter.convert(spanned, displayDensity); assertThat(html).isEqualTo("String with italic and bold section"); } @@ -306,7 +355,7 @@ public class SpannedToHtmlConverterTest { "String with italic and bold section".length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - String html = SpannedToHtmlConverter.convert(spanned); + String html = SpannedToHtmlConverter.convert(spanned, displayDensity); assertThat(html).isEqualTo("String with italic and bold section"); }