mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Merge pull request #8943 from dlafayet:embeddedstyle2
PiperOrigin-RevId: 375484765
This commit is contained in:
parent
55f50e24eb
commit
69394e6fb5
@ -15,6 +15,10 @@
|
||||
* DRM:
|
||||
* Don't restore offline keys before releasing them. In OEMCrypto v16+ keys
|
||||
must be released without restoring them first.
|
||||
* UI:
|
||||
* Keep subtitle language features embedded (e.g. rubies & tate-chu-yoko)
|
||||
in `Cue.text` even when `SubtitleView#setApplyEmbeddedStyles()` is
|
||||
false.
|
||||
|
||||
### 2.14.0 (2021-05-13)
|
||||
|
||||
|
@ -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 LanguageFeatureSpan {}
|
||||
|
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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 for span classes that carry language features rather than style information. */
|
||||
public interface LanguageFeatureSpan {}
|
@ -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 <rp> tag).
|
||||
public final class RubySpan {
|
||||
public final class RubySpan implements LanguageFeatureSpan {
|
||||
|
||||
/** The ruby text, i.e. the smaller explanatory characters. */
|
||||
public final String rubyText;
|
||||
|
@ -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 LanguageFeatureSpan {
|
||||
|
||||
/**
|
||||
* The possible mark shapes that can be used.
|
||||
|
@ -21,10 +21,6 @@ import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Canvas;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
import android.text.style.AbsoluteSizeSpan;
|
||||
import android.text.style.RelativeSizeSpan;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
@ -380,37 +376,13 @@ public final class SubtitleView extends FrameLayout implements TextOutput {
|
||||
}
|
||||
|
||||
private Cue removeEmbeddedStyling(Cue cue) {
|
||||
@Nullable CharSequence cueText = cue.text;
|
||||
Cue.Builder strippedCue = cue.buildUpon();
|
||||
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(cueText.toString());
|
||||
}
|
||||
return strippedCue.build();
|
||||
SubtitleViewUtils.removeAllEmbeddedStyling(strippedCue);
|
||||
} 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();
|
||||
SubtitleViewUtils.removeEmbeddedFontSizes(strippedCue);
|
||||
}
|
||||
return cue;
|
||||
return strippedCue.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,7 +16,16 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.ui;
|
||||
|
||||
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
|
||||
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
import android.text.style.AbsoluteSizeSpan;
|
||||
import android.text.style.RelativeSizeSpan;
|
||||
import com.google.android.exoplayer2.text.Cue;
|
||||
import com.google.android.exoplayer2.text.span.LanguageFeatureSpan;
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
/** Utility class for subtitle layout logic. */
|
||||
/* package */ final class SubtitleViewUtils {
|
||||
@ -48,5 +57,50 @@ import com.google.android.exoplayer2.text.Cue;
|
||||
}
|
||||
}
|
||||
|
||||
/** Removes all styling information from {@code cue}. */
|
||||
public static void removeAllEmbeddedStyling(Cue.Builder cue) {
|
||||
cue.clearWindowColor();
|
||||
if (cue.getText() instanceof Spanned) {
|
||||
if (!(cue.getText() instanceof Spannable)) {
|
||||
cue.setText(SpannableString.valueOf(cue.getText()));
|
||||
}
|
||||
removeSpansIf(
|
||||
(Spannable) checkNotNull(cue.getText()), span -> !(span instanceof LanguageFeatureSpan));
|
||||
}
|
||||
removeEmbeddedFontSizes(cue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all font size information from {@code cue}.
|
||||
*
|
||||
* <p>This involves:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Clearing {@link Cue.Builder#setTextSize(float, int)}.
|
||||
* <li>Removing all {@link AbsoluteSizeSpan} and {@link RelativeSizeSpan} spans from {@link
|
||||
* Cue#text}.
|
||||
* </ul>
|
||||
*/
|
||||
public static void removeEmbeddedFontSizes(Cue.Builder cue) {
|
||||
cue.setTextSize(Cue.DIMEN_UNSET, Cue.TYPE_UNSET);
|
||||
if (cue.getText() instanceof Spanned) {
|
||||
if (!(cue.getText() instanceof Spannable)) {
|
||||
cue.setText(SpannableString.valueOf(cue.getText()));
|
||||
}
|
||||
removeSpansIf(
|
||||
(Spannable) checkNotNull(cue.getText()),
|
||||
span -> span instanceof AbsoluteSizeSpan || span instanceof RelativeSizeSpan);
|
||||
}
|
||||
}
|
||||
|
||||
private static void removeSpansIf(Spannable spannable, Predicate<Object> removeFilter) {
|
||||
Object[] spans = spannable.getSpans(0, spannable.length(), Object.class);
|
||||
for (Object span : spans) {
|
||||
if (removeFilter.apply(span)) {
|
||||
spannable.removeSpan(span);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private SubtitleViewUtils() {}
|
||||
}
|
||||
|
@ -0,0 +1,199 @@
|
||||
/*
|
||||
* 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.ui;
|
||||
|
||||
import static com.google.android.exoplayer2.testutil.truth.SpannedSubject.assertThat;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.text.Layout;
|
||||
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 org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/** Tests for {@link SubtitleView}. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class SubtitleViewUtilsTest {
|
||||
|
||||
private static final Cue CUE = buildCue();
|
||||
|
||||
@Test
|
||||
public void testRemoveAllEmbeddedStyling() {
|
||||
Cue.Builder cueBuilder = CUE.buildUpon();
|
||||
SubtitleViewUtils.removeAllEmbeddedStyling(cueBuilder);
|
||||
Cue strippedCue = cueBuilder.build();
|
||||
|
||||
Spanned originalText = (Spanned) CUE.text;
|
||||
Spanned strippedText = (Spanned) strippedCue.text;
|
||||
|
||||
// Assert all non styling properties and spans are kept
|
||||
assertThat(strippedCue.textAlignment).isEqualTo(CUE.textAlignment);
|
||||
assertThat(strippedCue.multiRowAlignment).isEqualTo(CUE.multiRowAlignment);
|
||||
assertThat(strippedCue.line).isEqualTo(CUE.line);
|
||||
assertThat(strippedCue.lineType).isEqualTo(CUE.lineType);
|
||||
assertThat(strippedCue.position).isEqualTo(CUE.position);
|
||||
assertThat(strippedCue.positionAnchor).isEqualTo(CUE.positionAnchor);
|
||||
assertThat(strippedCue.textSize).isEqualTo(Cue.DIMEN_UNSET);
|
||||
assertThat(strippedCue.textSizeType).isEqualTo(Cue.TYPE_UNSET);
|
||||
assertThat(strippedCue.size).isEqualTo(CUE.size);
|
||||
assertThat(strippedCue.verticalType).isEqualTo(CUE.verticalType);
|
||||
assertThat(strippedCue.shearDegrees).isEqualTo(CUE.shearDegrees);
|
||||
TextEmphasisSpan expectedTextEmphasisSpan =
|
||||
originalText.getSpans(0, originalText.length(), TextEmphasisSpan.class)[0];
|
||||
assertThat(strippedText)
|
||||
.hasTextEmphasisSpanBetween(
|
||||
originalText.getSpanStart(expectedTextEmphasisSpan),
|
||||
originalText.getSpanEnd(expectedTextEmphasisSpan));
|
||||
RubySpan expectedRubySpan = originalText.getSpans(0, originalText.length(), RubySpan.class)[0];
|
||||
assertThat(strippedText)
|
||||
.hasRubySpanBetween(
|
||||
originalText.getSpanStart(expectedRubySpan), originalText.getSpanEnd(expectedRubySpan));
|
||||
HorizontalTextInVerticalContextSpan expectedHorizontalTextInVerticalContextSpan =
|
||||
originalText
|
||||
.getSpans(0, originalText.length(), HorizontalTextInVerticalContextSpan.class)[0];
|
||||
assertThat(strippedText)
|
||||
.hasHorizontalTextInVerticalContextSpanBetween(
|
||||
originalText.getSpanStart(expectedHorizontalTextInVerticalContextSpan),
|
||||
originalText.getSpanEnd(expectedHorizontalTextInVerticalContextSpan));
|
||||
|
||||
// Assert all styling properties and spans are removed
|
||||
assertThat(strippedCue.windowColorSet).isFalse();
|
||||
assertThat(strippedText).hasNoUnderlineSpanBetween(0, strippedText.length());
|
||||
assertThat(strippedText).hasNoRelativeSizeSpanBetween(0, strippedText.length());
|
||||
assertThat(strippedText).hasNoAbsoluteSizeSpanBetween(0, strippedText.length());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveEmbeddedFontSizes() {
|
||||
Cue.Builder cueBuilder = CUE.buildUpon();
|
||||
SubtitleViewUtils.removeEmbeddedFontSizes(cueBuilder);
|
||||
Cue strippedCue = cueBuilder.build();
|
||||
|
||||
Spanned originalText = (Spanned) CUE.text;
|
||||
Spanned strippedText = (Spanned) strippedCue.text;
|
||||
|
||||
// Assert all non text-size properties and spans are kept
|
||||
assertThat(strippedCue.textAlignment).isEqualTo(CUE.textAlignment);
|
||||
assertThat(strippedCue.multiRowAlignment).isEqualTo(CUE.multiRowAlignment);
|
||||
assertThat(strippedCue.line).isEqualTo(CUE.line);
|
||||
assertThat(strippedCue.lineType).isEqualTo(CUE.lineType);
|
||||
assertThat(strippedCue.position).isEqualTo(CUE.position);
|
||||
assertThat(strippedCue.positionAnchor).isEqualTo(CUE.positionAnchor);
|
||||
assertThat(strippedCue.size).isEqualTo(CUE.size);
|
||||
assertThat(strippedCue.windowColor).isEqualTo(CUE.windowColor);
|
||||
assertThat(strippedCue.windowColorSet).isEqualTo(CUE.windowColorSet);
|
||||
assertThat(strippedCue.verticalType).isEqualTo(CUE.verticalType);
|
||||
assertThat(strippedCue.shearDegrees).isEqualTo(CUE.shearDegrees);
|
||||
TextEmphasisSpan expectedTextEmphasisSpan =
|
||||
originalText.getSpans(0, originalText.length(), TextEmphasisSpan.class)[0];
|
||||
assertThat(strippedText)
|
||||
.hasTextEmphasisSpanBetween(
|
||||
originalText.getSpanStart(expectedTextEmphasisSpan),
|
||||
originalText.getSpanEnd(expectedTextEmphasisSpan));
|
||||
RubySpan expectedRubySpan = originalText.getSpans(0, originalText.length(), RubySpan.class)[0];
|
||||
assertThat(strippedText)
|
||||
.hasRubySpanBetween(
|
||||
originalText.getSpanStart(expectedRubySpan), originalText.getSpanEnd(expectedRubySpan));
|
||||
HorizontalTextInVerticalContextSpan expectedHorizontalTextInVerticalContextSpan =
|
||||
originalText
|
||||
.getSpans(0, originalText.length(), HorizontalTextInVerticalContextSpan.class)[0];
|
||||
assertThat(strippedText)
|
||||
.hasHorizontalTextInVerticalContextSpanBetween(
|
||||
originalText.getSpanStart(expectedHorizontalTextInVerticalContextSpan),
|
||||
originalText.getSpanEnd(expectedHorizontalTextInVerticalContextSpan));
|
||||
UnderlineSpan expectedUnderlineSpan =
|
||||
originalText.getSpans(0, originalText.length(), UnderlineSpan.class)[0];
|
||||
assertThat(strippedText)
|
||||
.hasUnderlineSpanBetween(
|
||||
originalText.getSpanStart(expectedUnderlineSpan),
|
||||
originalText.getSpanEnd(expectedUnderlineSpan));
|
||||
|
||||
// Assert the text-size properties and spans are removed
|
||||
assertThat(strippedCue.textSize).isEqualTo(Cue.DIMEN_UNSET);
|
||||
assertThat(strippedCue.textSizeType).isEqualTo(Cue.TYPE_UNSET);
|
||||
assertThat(strippedText).hasNoRelativeSizeSpanBetween(0, strippedText.length());
|
||||
assertThat(strippedText).hasNoAbsoluteSizeSpanBetween(0, strippedText.length());
|
||||
}
|
||||
|
||||
private static Cue buildCue() {
|
||||
SpannableString spanned =
|
||||
new SpannableString("TextEmphasis おはよ Ruby ございます 123 Underline RelativeSize AbsoluteSize");
|
||||
spanned.setSpan(
|
||||
new TextEmphasisSpan(
|
||||
TextEmphasisSpan.MARK_SHAPE_CIRCLE,
|
||||
TextEmphasisSpan.MARK_FILL_FILLED,
|
||||
TextAnnotation.POSITION_BEFORE),
|
||||
"Text emphasis ".length(),
|
||||
"Text emphasis おはよ".length(),
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
|
||||
spanned.setSpan(
|
||||
new RubySpan("おはよ", TextAnnotation.POSITION_BEFORE),
|
||||
"TextEmphasis おはよ Ruby ".length(),
|
||||
"TextEmphasis おはよ Ruby ございます".length(),
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
|
||||
spanned.setSpan(
|
||||
new HorizontalTextInVerticalContextSpan(),
|
||||
"TextEmphasis おはよ Ruby ございます ".length(),
|
||||
"TextEmphasis おはよ Ruby ございます 123".length(),
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
|
||||
spanned.setSpan(
|
||||
new UnderlineSpan(),
|
||||
"TextEmphasis おはよ Ruby ございます 123 ".length(),
|
||||
"TextEmphasis おはよ Ruby ございます 123 Underline".length(),
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
|
||||
spanned.setSpan(
|
||||
new RelativeSizeSpan(1f),
|
||||
"TextEmphasis おはよ Ruby ございます 123 Underline ".length(),
|
||||
"TextEmphasis おはよ Ruby ございます 123 Underline RelativeSize".length(),
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
|
||||
spanned.setSpan(
|
||||
new AbsoluteSizeSpan(10),
|
||||
"TextEmphasis おはよ Ruby ございます 123 Underline RelativeSize ".length(),
|
||||
"TextEmphasis おはよ Ruby ございます 123 Underline RelativeSize AbsoluteSize".length(),
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user