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 new file mode 100644 index 0000000000..59d5a234fb --- /dev/null +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SpannedToHtmlConverter.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.google.android.exoplayer2.ui; + +import android.graphics.Color; +import android.graphics.Typeface; +import android.text.Html; +import android.text.Spanned; +import android.text.style.ForegroundColorSpan; +import android.text.style.StyleSpan; +import android.text.style.UnderlineSpan; +import android.util.SparseArray; +import androidx.annotation.ColorInt; +import androidx.annotation.Nullable; +import com.google.android.exoplayer2.text.span.RubySpan; +import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.Util; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * Utility class to convert from span-styled text to HTML. + * + *
Supports all of the spans used by ExoPlayer's subtitle decoders, including custom ones found + * in {@link com.google.android.exoplayer2.text.span}. + */ +// TODO: Add support for more span types - only a small selection are currently implemented. +/* package */ final class SpannedToHtmlConverter { + + private SpannedToHtmlConverter() {} + + /** + * Convert {@code text} into HTML, adding tags and styling to match any styling spans present. + * + *
All textual content is HTML-escaped during the conversion.
+ */
+ public static String convert(@Nullable CharSequence text) {
+ if (text == null) {
+ return "";
+ }
+ if (!(text instanceof Spanned)) {
+ return Html.escapeHtml(text);
+ }
+ Spanned spanned = (Spanned) text;
+ SparseArray "
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
new file mode 100644
index 0000000000..382798025e
--- /dev/null
+++ b/library/ui/src/test/java/com/google/android/exoplayer2/ui/SpannedToHtmlConverterTest.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.google.android.exoplayer2.ui;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.graphics.Color;
+import android.graphics.Typeface;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.StyleSpan;
+import android.text.style.UnderlineSpan;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.android.exoplayer2.text.span.RubySpan;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link SpannedToHtmlConverter}. */
+@RunWith(AndroidJUnit4.class)
+public class SpannedToHtmlConverterTest {
+
+ @Test
+ public void convert_supportsForegroundColorSpan() {
+ SpannableString spanned = new SpannableString("String with colored section");
+ spanned.setSpan(
+ new ForegroundColorSpan(Color.argb(128, 64, 32, 16)),
+ "String with ".length(),
+ "String with colored".length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ String html = SpannedToHtmlConverter.convert(spanned);
+
+ assertThat(html)
+ .isEqualTo("String with colored section");
+ }
+
+ @Test
+ public void convert_supportsStyleSpan() {
+ SpannableString spanned =
+ new SpannableString("String with bold, italic and bold-italic sections.");
+ spanned.setSpan(
+ new StyleSpan(Typeface.BOLD),
+ "String with ".length(),
+ "String with bold".length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ spanned.setSpan(
+ new StyleSpan(Typeface.ITALIC),
+ "String with bold, ".length(),
+ "String with bold, italic".length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ spanned.setSpan(
+ new StyleSpan(Typeface.BOLD_ITALIC),
+ "String with bold, italic and ".length(),
+ "String with bold, italic and bold-italic".length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ String html = SpannedToHtmlConverter.convert(spanned);
+
+ assertThat(html)
+ .isEqualTo(
+ "String with bold, italic and bold-italic sections.");
+ }
+
+ @Test
+ public void convert_supportsRubySpan_over() {
+ SpannableString spanned = new SpannableString("String with over-annotated section");
+ spanned.setSpan(
+ new RubySpan("ruby-text", RubySpan.POSITION_OVER),
+ "String with ".length(),
+ "String with over-annotated".length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ String html = SpannedToHtmlConverter.convert(spanned);
+
+ assertThat(html)
+ .isEqualTo(
+ "String with over-annotated"
+ + " section");
+ }
+
+ @Test
+ public void convert_supportsRubySpan_under() {
+ SpannableString spanned = new SpannableString("String with under-annotated section");
+ spanned.setSpan(
+ new RubySpan("ruby-text", RubySpan.POSITION_UNDER),
+ "String with ".length(),
+ "String with under-annotated".length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ String html = SpannedToHtmlConverter.convert(spanned);
+
+ assertThat(html)
+ .isEqualTo(
+ "String with"
+ + " under-annotated"
+ + " section");
+ }
+
+ @Test
+ public void convert_supportsUnderlineSpan() {
+ SpannableString spanned = new SpannableString("String with underlined section.");
+ spanned.setSpan(
+ new UnderlineSpan(),
+ "String with ".length(),
+ "String with underlined".length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ String html = SpannedToHtmlConverter.convert(spanned);
+
+ assertThat(html).isEqualTo("String with underlined section.");
+ }
+
+ @Test
+ public void convert_escapesHtmlInUnspannedString() {
+ String html = SpannedToHtmlConverter.convert("String with bold tags");
+
+ assertThat(html).isEqualTo("String with <b>bold</b> tags");
+ }
+
+ @Test
+ public void convert_escapesUnrecognisedTagInSpannedString() {
+ SpannableString spanned = new SpannableString("String with
");
}
- cueText.append(cues.get(i).text);
+ cueText.append(SpannedToHtmlConverter.convert(cues.get(i).text));
}
webView.loadData(
"