diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/span/HorizontalTextInVerticalContextSpan.java b/library/core/src/main/java/com/google/android/exoplayer2/text/span/HorizontalTextInVerticalContextSpan.java index 587e1647c6..48d4ea667e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/span/HorizontalTextInVerticalContextSpan.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/span/HorizontalTextInVerticalContextSpan.java @@ -29,4 +29,4 @@ package com.google.android.exoplayer2.text.span; // NOTE: There's no Android layout support for this, so this span currently doesn't extend any // styling superclasses (e.g. MetricAffectingSpan). The only way to render this styling is to // extract the spans and do the layout manually. -public final class HorizontalTextInVerticalContextSpan {} +public final class HorizontalTextInVerticalContextSpan implements LanguageFeatureStyle {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/span/LanguageFeatureStyle.java b/library/core/src/main/java/com/google/android/exoplayer2/text/span/LanguageFeatureStyle.java new file mode 100644 index 0000000000..30d30f8f1d --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/span/LanguageFeatureStyle.java @@ -0,0 +1,23 @@ +/* + * Copyright 2021 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.text.span; + +/** + * Marker interface to mark classes that are language features. + */ +public interface LanguageFeatureStyle { +} + diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/span/RubySpan.java b/library/core/src/main/java/com/google/android/exoplayer2/text/span/RubySpan.java index b7fb4c2d61..14efaab91a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/span/RubySpan.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/span/RubySpan.java @@ -30,7 +30,7 @@ package com.google.android.exoplayer2.text.span; // extract the spans and do the layout manually. // TODO: Consider adding support for parenthetical text to be used when rendering doesn't support // rubies (e.g. HTML tag). -public final class RubySpan { +public final class RubySpan implements LanguageFeatureStyle { /** The ruby text, i.e. the smaller explanatory characters. */ public final String rubyText; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/span/TextEmphasisSpan.java b/library/core/src/main/java/com/google/android/exoplayer2/text/span/TextEmphasisSpan.java index 87f37ec2d0..b9745c8e1f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/span/TextEmphasisSpan.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/span/TextEmphasisSpan.java @@ -32,7 +32,7 @@ import java.lang.annotation.Retention; // NOTE: There's no Android layout support for text emphasis, so this span currently doesn't extend // any styling superclasses (e.g. MetricAffectingSpan). The only way to render this emphasis is to // extract the spans and do the layout manually. -public final class TextEmphasisSpan { +public final class TextEmphasisSpan implements LanguageFeatureStyle { /** * The possible mark shapes that can be used. diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java index 7908de8a60..2d0a2fc8b6 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java @@ -36,6 +36,7 @@ import androidx.annotation.IntDef; import androidx.annotation.Nullable; import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.TextOutput; +import com.google.android.exoplayer2.text.span.LanguageFeatureStyle; import com.google.android.exoplayer2.util.Util; import java.lang.annotation.Documented; import java.lang.annotation.Retention; @@ -374,47 +375,9 @@ public final class SubtitleView extends FrameLayout implements TextOutput { } List strippedCues = new ArrayList<>(cues.size()); for (int i = 0; i < cues.size(); i++) { - strippedCues.add(removeEmbeddedStyling(cues.get(i))); + strippedCues.add(SubtitleViewUtils + .removeEmbeddedStyling(cues.get(i), applyEmbeddedStyles, applyEmbeddedFontSizes)); } return strippedCues; } - - private Cue removeEmbeddedStyling(Cue cue) { - @Nullable CharSequence cueText = cue.text; - if (!applyEmbeddedStyles) { - Cue.Builder strippedCue = - cue.buildUpon().setTextSize(Cue.DIMEN_UNSET, Cue.TYPE_UNSET).clearWindowColor(); - if (cueText != null) { - // Remove all spans, regardless of type. - strippedCue.setText(new SpannableString(cueText.toString())); - if (cueText instanceof Spanned) { - SubtitleViewUtils - .preserveJapaneseLanguageFeatures((SpannableString)strippedCue.getText(), - (Spanned) cueText); - } - } - return strippedCue.build(); - } else if (!applyEmbeddedFontSizes) { - if (cueText == null) { - return cue; - } - Cue.Builder strippedCue = cue.buildUpon().setTextSize(Cue.DIMEN_UNSET, Cue.TYPE_UNSET); - if (cueText instanceof Spanned) { - SpannableString spannable = SpannableString.valueOf(cueText); - AbsoluteSizeSpan[] absSpans = - spannable.getSpans(0, spannable.length(), AbsoluteSizeSpan.class); - for (AbsoluteSizeSpan absSpan : absSpans) { - spannable.removeSpan(absSpan); - } - RelativeSizeSpan[] relSpans = - spannable.getSpans(0, spannable.length(), RelativeSizeSpan.class); - for (RelativeSizeSpan relSpan : relSpans) { - spannable.removeSpan(relSpan); - } - strippedCue.setText(spannable); - } - return strippedCue.build(); - } - return cue; - } } diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleViewUtils.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleViewUtils.java index 38dd6ff93c..5e6437878a 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleViewUtils.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleViewUtils.java @@ -17,9 +17,15 @@ package com.google.android.exoplayer2.ui; import android.text.Spannable; +import android.text.SpannableString; import android.text.Spanned; +import android.text.style.AbsoluteSizeSpan; +import android.text.style.RelativeSizeSpan; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.span.HorizontalTextInVerticalContextSpan; +import com.google.android.exoplayer2.text.span.LanguageFeatureStyle; import com.google.android.exoplayer2.text.span.RubySpan; import com.google.android.exoplayer2.text.span.SpanUtil; import com.google.android.exoplayer2.text.span.TextEmphasisSpan; @@ -54,26 +60,42 @@ import com.google.android.exoplayer2.text.span.TextEmphasisSpan; } } - public static void preserveJapaneseLanguageFeatures(Spannable copy, Spanned original) { - RubySpan[] absSpans = - original.getSpans(0, original.length(), RubySpan.class); - for (RubySpan rubySpan : absSpans) { - SpanUtil.addOrReplaceSpan(copy, rubySpan, original.getSpanStart(rubySpan), - original.getSpanEnd(rubySpan), original.getSpanFlags(rubySpan)); - } - TextEmphasisSpan[] textEmphasisSpans = - original.getSpans(0, original.length(), TextEmphasisSpan.class); - for (TextEmphasisSpan textEmphasisSpan : textEmphasisSpans) { - SpanUtil.addOrReplaceSpan(copy, textEmphasisSpan, original.getSpanStart(textEmphasisSpan), - original.getSpanEnd(textEmphasisSpan), original.getSpanFlags(textEmphasisSpan)); - } - HorizontalTextInVerticalContextSpan[] horizontalTextInVerticalContextSpans = - original.getSpans(0, original.length(), HorizontalTextInVerticalContextSpan.class); - - for (HorizontalTextInVerticalContextSpan span : horizontalTextInVerticalContextSpans) { - SpanUtil.addOrReplaceSpan(copy, span, original.getSpanStart(span), - original.getSpanEnd(span), original.getSpanFlags(span)); + /** + * Returns a cue object with the specified styling removed + * @param cue - Cue object that contains all the styling information + * @param applyEmbeddedStyles - if true, styles embedded within the cues should be applied + * @param applyEmbeddedFontSizes - if true, font sizes embedded within the cues should be applied. + * Only takes effect if setApplyEmbeddedStyles is true + * See {@link SubtitleView#setApplyEmbeddedStyles} + * @return New cue object with the specified styling removed + */ + @NonNull + static Cue removeEmbeddedStyling(@NonNull Cue cue, boolean applyEmbeddedStyles, + boolean applyEmbeddedFontSizes) { + @Nullable CharSequence cueText = cue.text; + if (cueText != null && (!applyEmbeddedStyles || !applyEmbeddedFontSizes)) { + Cue.Builder strippedCue = cue.buildUpon().setTextSize(Cue.DIMEN_UNSET, Cue.TYPE_UNSET); + if (!applyEmbeddedStyles) { + strippedCue.clearWindowColor(); + } + if (cueText instanceof Spanned) { + SpannableString spannable = SpannableString.valueOf(cueText); + Object[] spans = spannable.getSpans(0, spannable.length(), Object.class); + for (Object span : spans) { + if (span instanceof LanguageFeatureStyle) { + continue; + } + // applyEmbeddedFontSizes should only be applied if applyEmbeddedStyles is true + if (!applyEmbeddedStyles || span instanceof AbsoluteSizeSpan + || span instanceof RelativeSizeSpan) { + spannable.removeSpan(span); + } + } + strippedCue.setText(spannable); + } + return strippedCue.build(); } + return cue; } private SubtitleViewUtils() {} diff --git a/library/ui/src/test/java/com/google/android/exoplayer2/ui/SubtitleViewUtilsTest.java b/library/ui/src/test/java/com/google/android/exoplayer2/ui/SubtitleViewUtilsTest.java index 66d5a5ecc7..895f8a2a64 100644 --- a/library/ui/src/test/java/com/google/android/exoplayer2/ui/SubtitleViewUtilsTest.java +++ b/library/ui/src/test/java/com/google/android/exoplayer2/ui/SubtitleViewUtilsTest.java @@ -2,23 +2,187 @@ package com.google.android.exoplayer2.ui; import static com.google.android.exoplayer2.testutil.truth.SpannedSubject.assertThat; +import android.graphics.Color; +import android.text.Layout; +import android.text.Spannable; import android.text.SpannableString; import android.text.Spanned; +import android.text.style.AbsoluteSizeSpan; +import android.text.style.RelativeSizeSpan; import android.text.style.UnderlineSpan; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.span.HorizontalTextInVerticalContextSpan; import com.google.android.exoplayer2.text.span.RubySpan; import com.google.android.exoplayer2.text.span.TextAnnotation; import com.google.android.exoplayer2.text.span.TextEmphasisSpan; +import com.google.common.truth.Truth; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class SubtitleViewUtilsTest { + @Test + public void testApplyEmbeddedStyles() { + Cue cue = buildCue(); + Cue strippedCue = SubtitleViewUtils.removeEmbeddedStyling(cue, true, true); + + Truth.assertThat(strippedCue.textAlignment).isEqualTo(cue.textAlignment); + Truth.assertThat(strippedCue.multiRowAlignment).isEqualTo(cue.multiRowAlignment); + Truth.assertThat(strippedCue.line).isEqualTo(cue.line); + Truth.assertThat(strippedCue.lineType).isEqualTo(cue.lineType); + Truth.assertThat(strippedCue.position).isEqualTo(cue.position); + Truth.assertThat(strippedCue.positionAnchor).isEqualTo(cue.positionAnchor); + Truth.assertThat(strippedCue.textSize).isEqualTo(cue.textSize); + Truth.assertThat(strippedCue.textSizeType).isEqualTo(cue.textSizeType); + Truth.assertThat(strippedCue.size).isEqualTo(cue.size); + Truth.assertThat(strippedCue.windowColor).isEqualTo(cue.windowColor); + Truth.assertThat(strippedCue.windowColorSet).isEqualTo(cue.windowColorSet); + Truth.assertThat(strippedCue.verticalType).isEqualTo(cue.verticalType); + Truth.assertThat(strippedCue.shearDegrees).isEqualTo(cue.shearDegrees); + + Truth.assertThat(strippedCue.text).isInstanceOf(Spanned.class); + Spannable spannable = SpannableString.valueOf(strippedCue.text); + assertThat(spannable).hasTextEmphasisSpanBetween( + "Text emphasis ".length(), + "Text emphasis おはよ".length()); + assertThat(spannable).hasRubySpanBetween( + "TextEmphasis おはよ Ruby ".length(), + "TextEmphasis おはよ Ruby ございます".length()); + assertThat(spannable).hasHorizontalTextInVerticalContextSpanBetween( + "TextEmphasis おはよ Ruby ございます ".length(), + "TextEmphasis おはよ Ruby ございます 123".length()); + assertThat(spannable).hasUnderlineSpanBetween( + "TextEmphasis おはよ Ruby ございます 123 ".length(), + "TextEmphasis おはよ Ruby ございます 123 Underline".length()); + assertThat(spannable).hasRelativeSizeSpanBetween( + "TextEmphasis おはよ Ruby ございます 123 Underline ".length(), + "TextEmphasis おはよ Ruby ございます 123 Underline RelativeSize".length()); + assertThat(spannable).hasAbsoluteSizeSpanBetween( + "TextEmphasis おはよ Ruby ございます 123 Underline RelativeSize ".length(), + "TextEmphasis おはよ Ruby ございます 123 Underline RelativeSize AbsoluteSize".length()); + } @Test - public void testPreserveJapaneseLanguageFeatures() { - SpannableString spanned = new SpannableString("TextEmphasis おはよ Ruby ございます 123 Underline"); + public void testApplyEmbeddedStylesFalse() { + Cue cue = buildCue(); + Cue strippedCue = SubtitleViewUtils.removeEmbeddedStyling(cue, false, false); + + Truth.assertThat(strippedCue.textAlignment).isEqualTo(cue.textAlignment); + Truth.assertThat(strippedCue.multiRowAlignment).isEqualTo(cue.multiRowAlignment); + Truth.assertThat(strippedCue.line).isEqualTo(cue.line); + Truth.assertThat(strippedCue.lineType).isEqualTo(cue.lineType); + Truth.assertThat(strippedCue.position).isEqualTo(cue.position); + Truth.assertThat(strippedCue.positionAnchor).isEqualTo(cue.positionAnchor); + Truth.assertThat(strippedCue.textSize).isEqualTo(Cue.DIMEN_UNSET); + Truth.assertThat(strippedCue.textSizeType).isEqualTo(Cue.TYPE_UNSET); + Truth.assertThat(strippedCue.size).isEqualTo(cue.size); + Truth.assertThat(strippedCue.windowColor).isEqualTo(cue.windowColor); + Truth.assertThat(strippedCue.windowColorSet).isEqualTo(false); + Truth.assertThat(strippedCue.verticalType).isEqualTo(cue.verticalType); + Truth.assertThat(strippedCue.shearDegrees).isEqualTo(cue.shearDegrees); + + Truth.assertThat(strippedCue.text).isInstanceOf(Spanned.class); + Spannable spannable = SpannableString.valueOf(strippedCue.text); + assertThat(spannable).hasTextEmphasisSpanBetween( + "Text emphasis ".length(), + "Text emphasis おはよ".length()); + assertThat(spannable).hasRubySpanBetween( + "TextEmphasis おはよ Ruby ".length(), + "TextEmphasis おはよ Ruby ございます".length()); + assertThat(spannable).hasHorizontalTextInVerticalContextSpanBetween( + "TextEmphasis おはよ Ruby ございます ".length(), + "TextEmphasis おはよ Ruby ございます 123".length()); + assertThat(spannable).hasNoUnderlineSpanBetween(0, + "TextEmphasis おはよ Ruby ございます 123 Underline RelativeSize AbsoluteSize".length()); + assertThat(spannable).hasNoRelativeSizeSpanBetween(0, + "TextEmphasis おはよ Ruby ございます 123 Underline RelativeSize AbsoluteSize".length()); + assertThat(spannable).hasNoAbsoluteSizeSpanBetween(0, + "TextEmphasis おはよ Ruby ございます 123 Underline RelativeSize AbsoluteSize".length()); + } + + @Test + public void testApplyEmbeddedStylesFalseWithApplyEmbeddedFontSizes() { + Cue cue = buildCue(); + Cue strippedCue = SubtitleViewUtils.removeEmbeddedStyling(cue, false, true); + + Truth.assertThat(strippedCue.textAlignment).isEqualTo(cue.textAlignment); + Truth.assertThat(strippedCue.multiRowAlignment).isEqualTo(cue.multiRowAlignment); + Truth.assertThat(strippedCue.line).isEqualTo(cue.line); + Truth.assertThat(strippedCue.lineType).isEqualTo(cue.lineType); + Truth.assertThat(strippedCue.position).isEqualTo(cue.position); + Truth.assertThat(strippedCue.positionAnchor).isEqualTo(cue.positionAnchor); + Truth.assertThat(strippedCue.textSize).isEqualTo(Cue.DIMEN_UNSET); + Truth.assertThat(strippedCue.textSizeType).isEqualTo(Cue.TYPE_UNSET); + Truth.assertThat(strippedCue.size).isEqualTo(cue.size); + Truth.assertThat(strippedCue.windowColor).isEqualTo(cue.windowColor); + Truth.assertThat(strippedCue.windowColorSet).isEqualTo(false); + Truth.assertThat(strippedCue.verticalType).isEqualTo(cue.verticalType); + Truth.assertThat(strippedCue.shearDegrees).isEqualTo(cue.shearDegrees); + + Truth.assertThat(strippedCue.text).isInstanceOf(Spanned.class); + Spannable spannable = SpannableString.valueOf(strippedCue.text); + assertThat(spannable).hasTextEmphasisSpanBetween( + "Text emphasis ".length(), + "Text emphasis おはよ".length()); + assertThat(spannable).hasRubySpanBetween( + "TextEmphasis おはよ Ruby ".length(), + "TextEmphasis おはよ Ruby ございます".length()); + assertThat(spannable).hasHorizontalTextInVerticalContextSpanBetween( + "TextEmphasis おはよ Ruby ございます ".length(), + "TextEmphasis おはよ Ruby ございます 123".length()); + assertThat(spannable).hasNoUnderlineSpanBetween(0, + "TextEmphasis おはよ Ruby ございます 123 Underline RelativeSize AbsoluteSize".length()); + assertThat(spannable).hasNoRelativeSizeSpanBetween(0, + "TextEmphasis おはよ Ruby ございます 123 Underline RelativeSize AbsoluteSize".length()); + assertThat(spannable).hasNoAbsoluteSizeSpanBetween(0, + "TextEmphasis おはよ Ruby ございます 123 Underline RelativeSize AbsoluteSize".length()); + } + + + @Test + public void testApplyEmbeddedFontSizes() { + Cue cue = buildCue(); + Cue strippedCue = SubtitleViewUtils.removeEmbeddedStyling(cue, true, false); + + Truth.assertThat(strippedCue.textAlignment).isEqualTo(cue.textAlignment); + Truth.assertThat(strippedCue.multiRowAlignment).isEqualTo(cue.multiRowAlignment); + Truth.assertThat(strippedCue.line).isEqualTo(cue.line); + Truth.assertThat(strippedCue.lineType).isEqualTo(cue.lineType); + Truth.assertThat(strippedCue.position).isEqualTo(cue.position); + Truth.assertThat(strippedCue.positionAnchor).isEqualTo(cue.positionAnchor); + Truth.assertThat(strippedCue.textSize).isEqualTo(Cue.DIMEN_UNSET); + Truth.assertThat(strippedCue.textSizeType).isEqualTo(Cue.TYPE_UNSET); + Truth.assertThat(strippedCue.size).isEqualTo(cue.size); + Truth.assertThat(strippedCue.windowColor).isEqualTo(cue.windowColor); + Truth.assertThat(strippedCue.windowColorSet).isEqualTo(cue.windowColorSet); + Truth.assertThat(strippedCue.verticalType).isEqualTo(cue.verticalType); + Truth.assertThat(strippedCue.shearDegrees).isEqualTo(cue.shearDegrees); + + Truth.assertThat(strippedCue.text).isInstanceOf(Spanned.class); + Spannable spannable = SpannableString.valueOf(strippedCue.text); + assertThat(spannable).hasTextEmphasisSpanBetween( + "Text emphasis ".length(), + "Text emphasis おはよ".length()); + assertThat(spannable).hasRubySpanBetween( + "TextEmphasis おはよ Ruby ".length(), + "TextEmphasis おはよ Ruby ございます".length()); + assertThat(spannable).hasHorizontalTextInVerticalContextSpanBetween( + "TextEmphasis おはよ Ruby ございます ".length(), + "TextEmphasis おはよ Ruby ございます 123".length()); + assertThat(spannable).hasUnderlineSpanBetween( + "TextEmphasis おはよ Ruby ございます 123 ".length(), + "TextEmphasis おはよ Ruby ございます 123 Underline".length()); + assertThat(spannable).hasNoRelativeSizeSpanBetween(0, + "TextEmphasis おはよ Ruby ございます 123 Underline RelativeSize AbsoluteSize".length()); + assertThat(spannable).hasNoAbsoluteSizeSpanBetween(0, + "TextEmphasis おはよ Ruby ございます 123 Underline RelativeSize AbsoluteSize".length()); + } + + + private Cue buildCue() { + SpannableString spanned = new SpannableString( + "TextEmphasis おはよ Ruby ございます 123 Underline RelativeSize AbsoluteSize"); spanned.setSpan( new TextEmphasisSpan( TextEmphasisSpan.MARK_SHAPE_CIRCLE, @@ -42,22 +206,36 @@ public class SubtitleViewUtilsTest { spanned.setSpan( new UnderlineSpan(), - "TextEmphasis おはよ Ruby ございます 123".length(), + "TextEmphasis おはよ Ruby ございます 123 ".length(), "TextEmphasis おはよ Ruby ございます 123 Underline".length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - SpannableString spannable = new SpannableString(spanned.toString()); - assertThat(spannable).hasNoTextEmphasisSpanBetween(0, spannable.length()); + spanned.setSpan( + new RelativeSizeSpan(1f), + "TextEmphasis おはよ Ruby ございます 123 Underline ".length(), + "TextEmphasis おはよ Ruby ございます 123 Underline RelativeSize".length(), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - SubtitleViewUtils.preserveJapaneseLanguageFeatures(spannable, spanned); - assertThat(spannable) - .hasTextEmphasisSpanBetween("Text emphasis ".length(), "Text emphasis おはよ".length()); - assertThat(spannable).hasRubySpanBetween("TextEmphasis おはよ Ruby ".length(), - "TextEmphasis おはよ Ruby ございます".length()); - assertThat(spannable) - .hasHorizontalTextInVerticalContextSpanBetween("TextEmphasis おはよ Ruby ございます ".length(), - "TextEmphasis おはよ Ruby ございます 123".length()); + spanned.setSpan( + new AbsoluteSizeSpan(10), + "TextEmphasis おはよ Ruby ございます 123 Underline RelativeSize ".length(), + "TextEmphasis おはよ Ruby ございます 123 Underline RelativeSize AbsoluteSize".length(), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - assertThat(spannable).hasNoUnderlineSpanBetween(0, spannable.length()); + + return new Cue.Builder() + .setText(spanned) + .setTextAlignment(Layout.Alignment.ALIGN_CENTER) + .setMultiRowAlignment(Layout.Alignment.ALIGN_NORMAL) + .setLine(5, Cue.LINE_TYPE_NUMBER) + .setLineAnchor(Cue.ANCHOR_TYPE_END) + .setPosition(0.4f) + .setPositionAnchor(Cue.ANCHOR_TYPE_MIDDLE) + .setTextSize(0.2f, Cue.TEXT_SIZE_TYPE_FRACTIONAL) + .setSize(0.8f) + .setWindowColor(Color.CYAN) + .setVerticalType(Cue.VERTICAL_TYPE_RL) + .setShearDegrees(-15f) + .build(); } }