mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Add vertical text support to TtmlDecoder
I needed to use Cue.Builder instead of just SpannableStringBuilder for the regionOutput values, so I could attach the vertical info where appropriate (since this is a property of the Cue, not a span). PiperOrigin-RevId: 290709294
This commit is contained in:
parent
37908dd4df
commit
3aa52c2317
@ -497,12 +497,36 @@ public final class Cue {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sets the cue image. */
|
/**
|
||||||
|
* Gets the cue text.
|
||||||
|
*
|
||||||
|
* @see Cue#text
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public CharSequence getText() {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the cue image.
|
||||||
|
*
|
||||||
|
* @see Cue#bitmap
|
||||||
|
*/
|
||||||
public Builder setBitmap(Bitmap bitmap) {
|
public Builder setBitmap(Bitmap bitmap) {
|
||||||
this.bitmap = bitmap;
|
this.bitmap = bitmap;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the cue image.
|
||||||
|
*
|
||||||
|
* @see Cue#bitmap
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public Bitmap getBitmap() {
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the alignment of the cue text within the cue box.
|
* Sets the alignment of the cue text within the cue box.
|
||||||
*
|
*
|
||||||
@ -515,6 +539,16 @@ public final class Cue {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the alignment of the cue text within the cue box, or null if the alignment is undefined.
|
||||||
|
*
|
||||||
|
* @see Cue#textAlignment
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public Alignment getTextAlignment() {
|
||||||
|
return textAlignment;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the position of the {@code lineAnchor} of the cue box within the viewport in the
|
* Sets the position of the {@code lineAnchor} of the cue box within the viewport in the
|
||||||
* direction orthogonal to the writing direction.
|
* direction orthogonal to the writing direction.
|
||||||
@ -561,6 +595,26 @@ public final class Cue {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the position of the {@code lineAnchor} of the cue box within the viewport in the
|
||||||
|
* direction orthogonal to the writing direction.
|
||||||
|
*
|
||||||
|
* @see Cue#line
|
||||||
|
*/
|
||||||
|
public float getLine() {
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the type of the value of {@link #getLine()}.
|
||||||
|
*
|
||||||
|
* @see Cue#lineType
|
||||||
|
*/
|
||||||
|
@LineType
|
||||||
|
public int getLineType() {
|
||||||
|
return lineType;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the cue box anchor positioned by {@link #setLine(float, int) line}.
|
* Sets the cue box anchor positioned by {@link #setLine(float, int) line}.
|
||||||
*
|
*
|
||||||
@ -575,6 +629,16 @@ public final class Cue {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the cue box anchor positioned by {@link #setLine(float, int) line}.
|
||||||
|
*
|
||||||
|
* @see Cue#lineAnchor
|
||||||
|
*/
|
||||||
|
@AnchorType
|
||||||
|
public int getLineAnchor() {
|
||||||
|
return lineAnchor;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the fractional position of the {@link #setPositionAnchor(int) positionAnchor} of the cue
|
* Sets the fractional position of the {@link #setPositionAnchor(int) positionAnchor} of the cue
|
||||||
* box within the viewport in the direction orthogonal to {@link #setLine(float, int) line}.
|
* box within the viewport in the direction orthogonal to {@link #setLine(float, int) line}.
|
||||||
@ -590,6 +654,16 @@ public final class Cue {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the fractional position of the {@link #setPositionAnchor(int) positionAnchor} of the cue
|
||||||
|
* box within the viewport in the direction orthogonal to {@link #setLine(float, int) line}.
|
||||||
|
*
|
||||||
|
* @see Cue#position
|
||||||
|
*/
|
||||||
|
public float getPosition() {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the cue box anchor positioned by {@link #setPosition(float) position}.
|
* Sets the cue box anchor positioned by {@link #setPosition(float) position}.
|
||||||
*
|
*
|
||||||
@ -605,7 +679,17 @@ public final class Cue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the default text size type for this cue's text.
|
* Gets the cue box anchor positioned by {@link #setPosition(float) position}.
|
||||||
|
*
|
||||||
|
* @see Cue#positionAnchor
|
||||||
|
*/
|
||||||
|
@AnchorType
|
||||||
|
public int getPositionAnchor() {
|
||||||
|
return positionAnchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the default text size and type for this cue's text.
|
||||||
*
|
*
|
||||||
* @see Cue#textSize
|
* @see Cue#textSize
|
||||||
* @see Cue#textSizeType
|
* @see Cue#textSizeType
|
||||||
@ -616,12 +700,29 @@ public final class Cue {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the default text size type for this cue's text.
|
||||||
|
*
|
||||||
|
* @see Cue#textSizeType
|
||||||
|
*/
|
||||||
|
@TextSizeType
|
||||||
|
public int getTextSizeType() {
|
||||||
|
return textSizeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the default text size for this cue's text.
|
||||||
|
*
|
||||||
|
* @see Cue#textSize
|
||||||
|
*/
|
||||||
|
public float getTextSize() {
|
||||||
|
return textSize;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the size of the cue box in the writing direction specified as a fraction of the viewport
|
* Sets the size of the cue box in the writing direction specified as a fraction of the viewport
|
||||||
* size in that direction.
|
* size in that direction.
|
||||||
*
|
*
|
||||||
* @see Cue#textSize
|
|
||||||
* @see Cue#textSizeType
|
|
||||||
* @see Cue#size
|
* @see Cue#size
|
||||||
*/
|
*/
|
||||||
public Builder setSize(float size) {
|
public Builder setSize(float size) {
|
||||||
@ -630,7 +731,17 @@ public final class Cue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the bitmap height as a fraction of the of the viewport size.
|
* Gets the size of the cue box in the writing direction specified as a fraction of the viewport
|
||||||
|
* size in that direction.
|
||||||
|
*
|
||||||
|
* @see Cue#size
|
||||||
|
*/
|
||||||
|
public float getSize() {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the bitmap height as a fraction of the viewport size.
|
||||||
*
|
*
|
||||||
* @see Cue#bitmapHeight
|
* @see Cue#bitmapHeight
|
||||||
*/
|
*/
|
||||||
@ -639,6 +750,15 @@ public final class Cue {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the bitmap height as a fraction of the viewport size.
|
||||||
|
*
|
||||||
|
* @see Cue#bitmapHeight
|
||||||
|
*/
|
||||||
|
public float getBitmapHeight() {
|
||||||
|
return bitmapHeight;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the fill color of the window.
|
* Sets the fill color of the window.
|
||||||
*
|
*
|
||||||
@ -653,6 +773,25 @@ public final class Cue {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the fill color of the window is set.
|
||||||
|
*
|
||||||
|
* @see Cue#windowColorSet
|
||||||
|
*/
|
||||||
|
public boolean isWindowColorSet() {
|
||||||
|
return windowColorSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the fill color of the window.
|
||||||
|
*
|
||||||
|
* @see Cue#windowColor
|
||||||
|
*/
|
||||||
|
@ColorInt
|
||||||
|
public int getWindowColor() {
|
||||||
|
return windowColor;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the vertical formatting for this Cue.
|
* Sets the vertical formatting for this Cue.
|
||||||
*
|
*
|
||||||
@ -663,6 +802,16 @@ public final class Cue {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the vertical formatting for this Cue.
|
||||||
|
*
|
||||||
|
* @see Cue#verticalType
|
||||||
|
*/
|
||||||
|
@VerticalType
|
||||||
|
public int getVerticalType() {
|
||||||
|
return verticalType;
|
||||||
|
}
|
||||||
|
|
||||||
/** Build the cue. */
|
/** Build the cue. */
|
||||||
public Cue build() {
|
public Cue build() {
|
||||||
return new Cue(
|
return new Cue(
|
||||||
|
@ -540,6 +540,21 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case TtmlNode.ATTR_TTS_WRITING_MODE:
|
||||||
|
switch (Util.toLowerInvariant(attributeValue)) {
|
||||||
|
// TODO: Support horizontal RTL modes.
|
||||||
|
case TtmlNode.VERTICAL:
|
||||||
|
case TtmlNode.VERTICAL_LR:
|
||||||
|
style = createIfNull(style).setVerticalType(Cue.VERTICAL_TYPE_LR);
|
||||||
|
break;
|
||||||
|
case TtmlNode.VERTICAL_RL:
|
||||||
|
style = createIfNull(style).setVerticalType(Cue.VERTICAL_TYPE_RL);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// ignore
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// ignore
|
// ignore
|
||||||
break;
|
break;
|
||||||
|
@ -28,7 +28,6 @@ import java.util.ArrayList;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
@ -67,7 +66,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
public static final String ATTR_TTS_COLOR = "color";
|
public static final String ATTR_TTS_COLOR = "color";
|
||||||
public static final String ATTR_TTS_TEXT_DECORATION = "textDecoration";
|
public static final String ATTR_TTS_TEXT_DECORATION = "textDecoration";
|
||||||
public static final String ATTR_TTS_TEXT_ALIGN = "textAlign";
|
public static final String ATTR_TTS_TEXT_ALIGN = "textAlign";
|
||||||
|
public static final String ATTR_TTS_WRITING_MODE = "writingMode";
|
||||||
|
|
||||||
|
// Values for textDecoration
|
||||||
public static final String LINETHROUGH = "linethrough";
|
public static final String LINETHROUGH = "linethrough";
|
||||||
public static final String NO_LINETHROUGH = "nolinethrough";
|
public static final String NO_LINETHROUGH = "nolinethrough";
|
||||||
public static final String UNDERLINE = "underline";
|
public static final String UNDERLINE = "underline";
|
||||||
@ -75,12 +76,18 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
public static final String ITALIC = "italic";
|
public static final String ITALIC = "italic";
|
||||||
public static final String BOLD = "bold";
|
public static final String BOLD = "bold";
|
||||||
|
|
||||||
|
// Values for textAlign
|
||||||
public static final String LEFT = "left";
|
public static final String LEFT = "left";
|
||||||
public static final String CENTER = "center";
|
public static final String CENTER = "center";
|
||||||
public static final String RIGHT = "right";
|
public static final String RIGHT = "right";
|
||||||
public static final String START = "start";
|
public static final String START = "start";
|
||||||
public static final String END = "end";
|
public static final String END = "end";
|
||||||
|
|
||||||
|
// Values for writingMode
|
||||||
|
public static final String VERTICAL = "tb";
|
||||||
|
public static final String VERTICAL_LR = "tblr";
|
||||||
|
public static final String VERTICAL_RL = "tbrl";
|
||||||
|
|
||||||
@Nullable public final String tag;
|
@Nullable public final String tag;
|
||||||
@Nullable public final String text;
|
@Nullable public final String text;
|
||||||
public final boolean isTextNode;
|
public final boolean isTextNode;
|
||||||
@ -211,7 +218,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
List<Pair<String, String>> regionImageOutputs = new ArrayList<>();
|
List<Pair<String, String>> regionImageOutputs = new ArrayList<>();
|
||||||
traverseForImage(timeUs, regionId, regionImageOutputs);
|
traverseForImage(timeUs, regionId, regionImageOutputs);
|
||||||
|
|
||||||
TreeMap<String, SpannableStringBuilder> regionTextOutputs = new TreeMap<>();
|
TreeMap<String, Cue.Builder> regionTextOutputs = new TreeMap<>();
|
||||||
traverseForText(timeUs, false, regionId, regionTextOutputs);
|
traverseForText(timeUs, false, regionId, regionTextOutputs);
|
||||||
traverseForStyle(timeUs, globalStyles, regionTextOutputs);
|
traverseForStyle(timeUs, globalStyles, regionTextOutputs);
|
||||||
|
|
||||||
@ -242,20 +249,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create text based cues.
|
// Create text based cues.
|
||||||
for (Entry<String, SpannableStringBuilder> entry : regionTextOutputs.entrySet()) {
|
for (Map.Entry<String, Cue.Builder> entry : regionTextOutputs.entrySet()) {
|
||||||
TtmlRegion region = Assertions.checkNotNull(regionMap.get(entry.getKey()));
|
TtmlRegion region = Assertions.checkNotNull(regionMap.get(entry.getKey()));
|
||||||
cues.add(
|
Cue.Builder regionOutput = entry.getValue();
|
||||||
new Cue(
|
cleanUpText((SpannableStringBuilder) Assertions.checkNotNull(regionOutput.getText()));
|
||||||
cleanUpText(entry.getValue()),
|
regionOutput.setLine(region.line, region.lineType);
|
||||||
/* textAlignment= */ null,
|
regionOutput.setLineAnchor(region.lineAnchor);
|
||||||
region.line,
|
regionOutput.setPosition(region.position);
|
||||||
region.lineType,
|
regionOutput.setSize(region.width);
|
||||||
region.lineAnchor,
|
regionOutput.setTextSize(region.textSize, region.textSizeType);
|
||||||
region.position,
|
cues.add(regionOutput.build());
|
||||||
/* positionAnchor= */ Cue.TYPE_UNSET,
|
|
||||||
region.width,
|
|
||||||
region.textSizeType,
|
|
||||||
region.textSize));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return cues;
|
return cues;
|
||||||
@ -277,7 +280,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
long timeUs,
|
long timeUs,
|
||||||
boolean descendsPNode,
|
boolean descendsPNode,
|
||||||
String inheritedRegion,
|
String inheritedRegion,
|
||||||
Map<String, SpannableStringBuilder> regionOutputs) {
|
Map<String, Cue.Builder> regionOutputs) {
|
||||||
nodeStartsByRegion.clear();
|
nodeStartsByRegion.clear();
|
||||||
nodeEndsByRegion.clear();
|
nodeEndsByRegion.clear();
|
||||||
if (TAG_METADATA.equals(tag)) {
|
if (TAG_METADATA.equals(tag)) {
|
||||||
@ -288,13 +291,14 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
String resolvedRegionId = ANONYMOUS_REGION_ID.equals(regionId) ? inheritedRegion : regionId;
|
String resolvedRegionId = ANONYMOUS_REGION_ID.equals(regionId) ? inheritedRegion : regionId;
|
||||||
|
|
||||||
if (isTextNode && descendsPNode) {
|
if (isTextNode && descendsPNode) {
|
||||||
getRegionOutput(resolvedRegionId, regionOutputs).append(Assertions.checkNotNull(text));
|
getRegionOutputText(resolvedRegionId, regionOutputs).append(Assertions.checkNotNull(text));
|
||||||
} else if (TAG_BR.equals(tag) && descendsPNode) {
|
} else if (TAG_BR.equals(tag) && descendsPNode) {
|
||||||
getRegionOutput(resolvedRegionId, regionOutputs).append('\n');
|
getRegionOutputText(resolvedRegionId, regionOutputs).append('\n');
|
||||||
} else if (isActive(timeUs)) {
|
} else if (isActive(timeUs)) {
|
||||||
// This is a container node, which can contain zero or more children.
|
// This is a container node, which can contain zero or more children.
|
||||||
for (Entry<String, SpannableStringBuilder> entry : regionOutputs.entrySet()) {
|
for (Map.Entry<String, Cue.Builder> entry : regionOutputs.entrySet()) {
|
||||||
nodeStartsByRegion.put(entry.getKey(), entry.getValue().length());
|
nodeStartsByRegion.put(
|
||||||
|
entry.getKey(), Assertions.checkNotNull(entry.getValue().getText()).length());
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isPNode = TAG_P.equals(tag);
|
boolean isPNode = TAG_P.equals(tag);
|
||||||
@ -303,36 +307,38 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
regionOutputs);
|
regionOutputs);
|
||||||
}
|
}
|
||||||
if (isPNode) {
|
if (isPNode) {
|
||||||
TtmlRenderUtil.endParagraph(getRegionOutput(resolvedRegionId, regionOutputs));
|
TtmlRenderUtil.endParagraph(getRegionOutputText(resolvedRegionId, regionOutputs));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Entry<String, SpannableStringBuilder> entry : regionOutputs.entrySet()) {
|
for (Map.Entry<String, Cue.Builder> entry : regionOutputs.entrySet()) {
|
||||||
nodeEndsByRegion.put(entry.getKey(), entry.getValue().length());
|
nodeEndsByRegion.put(
|
||||||
|
entry.getKey(), Assertions.checkNotNull(entry.getValue().getText()).length());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SpannableStringBuilder getRegionOutput(
|
private static SpannableStringBuilder getRegionOutputText(
|
||||||
String resolvedRegionId, Map<String, SpannableStringBuilder> regionOutputs) {
|
String resolvedRegionId, Map<String, Cue.Builder> regionOutputs) {
|
||||||
if (!regionOutputs.containsKey(resolvedRegionId)) {
|
if (!regionOutputs.containsKey(resolvedRegionId)) {
|
||||||
regionOutputs.put(resolvedRegionId, new SpannableStringBuilder());
|
Cue.Builder regionOutput = new Cue.Builder();
|
||||||
|
regionOutput.setText(new SpannableStringBuilder());
|
||||||
|
regionOutputs.put(resolvedRegionId, regionOutput);
|
||||||
}
|
}
|
||||||
return regionOutputs.get(resolvedRegionId);
|
return (SpannableStringBuilder)
|
||||||
|
Assertions.checkNotNull(regionOutputs.get(resolvedRegionId).getText());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void traverseForStyle(
|
private void traverseForStyle(
|
||||||
long timeUs,
|
long timeUs, Map<String, TtmlStyle> globalStyles, Map<String, Cue.Builder> regionOutputs) {
|
||||||
Map<String, TtmlStyle> globalStyles,
|
|
||||||
Map<String, SpannableStringBuilder> regionOutputs) {
|
|
||||||
if (!isActive(timeUs)) {
|
if (!isActive(timeUs)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (Entry<String, Integer> entry : nodeEndsByRegion.entrySet()) {
|
for (Map.Entry<String, Integer> entry : nodeEndsByRegion.entrySet()) {
|
||||||
String regionId = entry.getKey();
|
String regionId = entry.getKey();
|
||||||
int start = nodeStartsByRegion.containsKey(regionId) ? nodeStartsByRegion.get(regionId) : 0;
|
int start = nodeStartsByRegion.containsKey(regionId) ? nodeStartsByRegion.get(regionId) : 0;
|
||||||
int end = entry.getValue();
|
int end = entry.getValue();
|
||||||
if (start != end) {
|
if (start != end) {
|
||||||
SpannableStringBuilder regionOutput = Assertions.checkNotNull(regionOutputs.get(regionId));
|
Cue.Builder regionOutput = Assertions.checkNotNull(regionOutputs.get(regionId));
|
||||||
applyStyleToOutput(globalStyles, regionOutput, start, end);
|
applyStyleToOutput(globalStyles, regionOutput, start, end);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -342,17 +348,20 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void applyStyleToOutput(
|
private void applyStyleToOutput(
|
||||||
Map<String, TtmlStyle> globalStyles,
|
Map<String, TtmlStyle> globalStyles, Cue.Builder regionOutput, int start, int end) {
|
||||||
SpannableStringBuilder regionOutput,
|
|
||||||
int start,
|
|
||||||
int end) {
|
|
||||||
@Nullable TtmlStyle resolvedStyle = TtmlRenderUtil.resolveStyle(style, styleIds, globalStyles);
|
@Nullable TtmlStyle resolvedStyle = TtmlRenderUtil.resolveStyle(style, styleIds, globalStyles);
|
||||||
|
@Nullable SpannableStringBuilder text = (SpannableStringBuilder) regionOutput.getText();
|
||||||
|
if (text == null) {
|
||||||
|
text = new SpannableStringBuilder();
|
||||||
|
regionOutput.setText(text);
|
||||||
|
}
|
||||||
if (resolvedStyle != null) {
|
if (resolvedStyle != null) {
|
||||||
TtmlRenderUtil.applyStylesToSpan(regionOutput, start, end, resolvedStyle);
|
TtmlRenderUtil.applyStylesToSpan(text, start, end, resolvedStyle);
|
||||||
|
regionOutput.setVerticalType(resolvedStyle.getVerticalType());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private SpannableStringBuilder cleanUpText(SpannableStringBuilder builder) {
|
private static void cleanUpText(SpannableStringBuilder builder) {
|
||||||
// Having joined the text elements, we need to do some final cleanup on the result.
|
// Having joined the text elements, we need to do some final cleanup on the result.
|
||||||
// 1. Collapse multiple consecutive spaces into a single space.
|
// 1. Collapse multiple consecutive spaces into a single space.
|
||||||
int builderLength = builder.length();
|
int builderLength = builder.length();
|
||||||
@ -396,7 +405,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
builder.delete(builderLength - 1, builderLength);
|
builder.delete(builderLength - 1, builderLength);
|
||||||
/*builderLength--;*/
|
/*builderLength--;*/
|
||||||
}
|
}
|
||||||
return builder;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,8 @@ import android.graphics.Typeface;
|
|||||||
import android.text.Layout;
|
import android.text.Layout;
|
||||||
import androidx.annotation.IntDef;
|
import androidx.annotation.IntDef;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import com.google.android.exoplayer2.text.Cue;
|
||||||
|
import com.google.android.exoplayer2.text.Cue.VerticalType;
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.Documented;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
@ -73,6 +75,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
private float fontSize;
|
private float fontSize;
|
||||||
private @MonotonicNonNull String id;
|
private @MonotonicNonNull String id;
|
||||||
private Layout.@MonotonicNonNull Alignment textAlign;
|
private Layout.@MonotonicNonNull Alignment textAlign;
|
||||||
|
@Cue.VerticalType private int verticalType;
|
||||||
|
|
||||||
public TtmlStyle() {
|
public TtmlStyle() {
|
||||||
linethrough = UNSPECIFIED;
|
linethrough = UNSPECIFIED;
|
||||||
@ -80,6 +83,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
bold = UNSPECIFIED;
|
bold = UNSPECIFIED;
|
||||||
italic = UNSPECIFIED;
|
italic = UNSPECIFIED;
|
||||||
fontSizeUnit = UNSPECIFIED;
|
fontSizeUnit = UNSPECIFIED;
|
||||||
|
verticalType = Cue.TYPE_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -220,6 +224,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
if (chaining && !hasBackgroundColor && ancestor.hasBackgroundColor) {
|
if (chaining && !hasBackgroundColor && ancestor.hasBackgroundColor) {
|
||||||
setBackgroundColor(ancestor.backgroundColor);
|
setBackgroundColor(ancestor.backgroundColor);
|
||||||
}
|
}
|
||||||
|
if (chaining && verticalType != Cue.TYPE_UNSET && ancestor.verticalType == Cue.TYPE_UNSET) {
|
||||||
|
setVerticalType(ancestor.verticalType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -262,4 +269,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
return fontSize;
|
return fontSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TtmlStyle setVerticalType(@VerticalType int verticalType) {
|
||||||
|
this.verticalType = verticalType;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@VerticalType
|
||||||
|
public int getVerticalType() {
|
||||||
|
return verticalType;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
17
library/core/src/test/assets/ttml/vertical_text.xml
Normal file
17
library/core/src/test/assets/ttml/vertical_text.xml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<tt xmlns:ttm="http://www.w3.org/2006/10/ttaf1#metadata"
|
||||||
|
xmlns:ttp="http://www.w3.org/2006/10/ttaf1#parameter"
|
||||||
|
xmlns:tts="http://www.w3.org/2006/10/ttaf1#style"
|
||||||
|
xmlns="http://www.w3.org/ns/ttml"
|
||||||
|
xmlns="http://www.w3.org/2006/10/ttaf1">
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<p begin="10s" end="18s" tts:writingMode="tbrl">Vertical right-to-left (e.g. Japanese)</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p begin="20s" end="28s" tts:writingMode="tblr">Vertical left-to-right (e.g. Mongolian)</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p begin="30s" end="38s">Horizontal text</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</tt>
|
@ -66,6 +66,7 @@ public final class TtmlDecoderTest {
|
|||||||
private static final String BITMAP_REGION_FILE = "ttml/bitmap_percentage_region.xml";
|
private static final String BITMAP_REGION_FILE = "ttml/bitmap_percentage_region.xml";
|
||||||
private static final String BITMAP_PIXEL_REGION_FILE = "ttml/bitmap_pixel_region.xml";
|
private static final String BITMAP_PIXEL_REGION_FILE = "ttml/bitmap_pixel_region.xml";
|
||||||
private static final String BITMAP_UNSUPPORTED_REGION_FILE = "ttml/bitmap_unsupported_region.xml";
|
private static final String BITMAP_UNSUPPORTED_REGION_FILE = "ttml/bitmap_unsupported_region.xml";
|
||||||
|
private static final String VERTICAL_TEXT_FILE = "ttml/vertical_text.xml";
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInlineAttributes() throws IOException, SubtitleDecoderException {
|
public void testInlineAttributes() throws IOException, SubtitleDecoderException {
|
||||||
@ -587,6 +588,26 @@ public final class TtmlDecoderTest {
|
|||||||
assertThat(cue.bitmapHeight).isEqualTo(Cue.DIMEN_UNSET);
|
assertThat(cue.bitmapHeight).isEqualTo(Cue.DIMEN_UNSET);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testVerticalText() throws IOException, SubtitleDecoderException {
|
||||||
|
TtmlSubtitle subtitle = getSubtitle(VERTICAL_TEXT_FILE);
|
||||||
|
|
||||||
|
List<Cue> firstCues = subtitle.getCues(10_000_000);
|
||||||
|
assertThat(firstCues).hasSize(1);
|
||||||
|
Cue firstCue = firstCues.get(0);
|
||||||
|
assertThat(firstCue.verticalType).isEqualTo(Cue.VERTICAL_TYPE_RL);
|
||||||
|
|
||||||
|
List<Cue> secondCues = subtitle.getCues(20_000_000);
|
||||||
|
assertThat(secondCues).hasSize(1);
|
||||||
|
Cue secondCue = secondCues.get(0);
|
||||||
|
assertThat(secondCue.verticalType).isEqualTo(Cue.VERTICAL_TYPE_LR);
|
||||||
|
|
||||||
|
List<Cue> thirdCues = subtitle.getCues(30_000_000);
|
||||||
|
assertThat(thirdCues).hasSize(1);
|
||||||
|
Cue thirdCue = thirdCues.get(0);
|
||||||
|
assertThat(thirdCue.verticalType).isEqualTo(Cue.TYPE_UNSET);
|
||||||
|
}
|
||||||
|
|
||||||
private void assertSpans(
|
private void assertSpans(
|
||||||
TtmlSubtitle subtitle,
|
TtmlSubtitle subtitle,
|
||||||
int second,
|
int second,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user