Commit 8c7d6447 by dlafayet Committed by bachinger

Merge #8858: Support ebutts:multiRowAlign in TTML text renderer

Imported from GitHub PR https://github.com/google/ExoPlayer/pull/8858

Fix bug in text alignment inheritance where child does not correctly inherit ancestor's setting

@icbaker
Merge 70eb4bceb73b3f07e2f8d545b4fa7961189ac52a into 45616f91

COPYBARA_INTEGRATE_REVIEW=https://github.com/google/ExoPlayer/pull/8877 from dlafayet:multirowalign-cue d942b50a40525fea5d11b35a33d3bbc512550960
PiperOrigin-RevId: 371306966
parent 99492902
...@@ -131,6 +131,7 @@ ...@@ -131,6 +131,7 @@
([#8435](https://github.com/google/ExoPlayer/issues/8435)). ([#8435](https://github.com/google/ExoPlayer/issues/8435)).
* Ensure TTML `tts:textAlign` is correctly propagated from `<p>` nodes to * Ensure TTML `tts:textAlign` is correctly propagated from `<p>` nodes to
child nodes. child nodes.
* Support TTML `ebutts:multiRowAlign` attributes.
* MediaSession extension: Remove dependency to core module and rely on common * MediaSession extension: Remove dependency to core module and rely on common
only. The `TimelineQueueEditor` uses a new `MediaDescriptionConverter` for only. The `TimelineQueueEditor` uses a new `MediaDescriptionConverter` for
this purpose and does not rely on the `ConcatenatingMediaSource` anymore. this purpose and does not rely on the `ConcatenatingMediaSource` anymore.
......
...@@ -140,6 +140,12 @@ public final class Cue { ...@@ -140,6 +140,12 @@ public final class Cue {
/** The alignment of the cue text within the cue box, or null if the alignment is undefined. */ /** The alignment of the cue text within the cue box, or null if the alignment is undefined. */
@Nullable public final Alignment textAlignment; @Nullable public final Alignment textAlignment;
/**
* The alignment of multiple lines of text relative to the longest line, or null if the alignment
* is undefined.
*/
@Nullable public final Alignment multiRowAlignment;
/** The cue image, or null if this is a text cue. */ /** The cue image, or null if this is a text cue. */
@Nullable public final Bitmap bitmap; @Nullable public final Bitmap bitmap;
...@@ -364,6 +370,7 @@ public final class Cue { ...@@ -364,6 +370,7 @@ public final class Cue {
this( this(
text, text,
textAlignment, textAlignment,
/* multiRowAlignment= */ null,
/* bitmap= */ null, /* bitmap= */ null,
line, line,
lineType, lineType,
...@@ -410,6 +417,7 @@ public final class Cue { ...@@ -410,6 +417,7 @@ public final class Cue {
this( this(
text, text,
textAlignment, textAlignment,
/* multiRowAlignment= */ null,
/* bitmap= */ null, /* bitmap= */ null,
line, line,
lineType, lineType,
...@@ -429,6 +437,7 @@ public final class Cue { ...@@ -429,6 +437,7 @@ public final class Cue {
private Cue( private Cue(
@Nullable CharSequence text, @Nullable CharSequence text,
@Nullable Alignment textAlignment, @Nullable Alignment textAlignment,
@Nullable Alignment multiRowAlignment,
@Nullable Bitmap bitmap, @Nullable Bitmap bitmap,
float line, float line,
@LineType int lineType, @LineType int lineType,
...@@ -451,6 +460,7 @@ public final class Cue { ...@@ -451,6 +460,7 @@ public final class Cue {
} }
this.text = text; this.text = text;
this.textAlignment = textAlignment; this.textAlignment = textAlignment;
this.multiRowAlignment = multiRowAlignment;
this.bitmap = bitmap; this.bitmap = bitmap;
this.line = line; this.line = line;
this.lineType = lineType; this.lineType = lineType;
...@@ -477,6 +487,7 @@ public final class Cue { ...@@ -477,6 +487,7 @@ public final class Cue {
@Nullable private CharSequence text; @Nullable private CharSequence text;
@Nullable private Bitmap bitmap; @Nullable private Bitmap bitmap;
@Nullable private Alignment textAlignment; @Nullable private Alignment textAlignment;
@Nullable private Alignment multiRowAlignment;
private float line; private float line;
@LineType private int lineType; @LineType private int lineType;
@AnchorType private int lineAnchor; @AnchorType private int lineAnchor;
...@@ -495,6 +506,7 @@ public final class Cue { ...@@ -495,6 +506,7 @@ public final class Cue {
text = null; text = null;
bitmap = null; bitmap = null;
textAlignment = null; textAlignment = null;
multiRowAlignment = null;
line = DIMEN_UNSET; line = DIMEN_UNSET;
lineType = TYPE_UNSET; lineType = TYPE_UNSET;
lineAnchor = TYPE_UNSET; lineAnchor = TYPE_UNSET;
...@@ -513,6 +525,7 @@ public final class Cue { ...@@ -513,6 +525,7 @@ public final class Cue {
text = cue.text; text = cue.text;
bitmap = cue.bitmap; bitmap = cue.bitmap;
textAlignment = cue.textAlignment; textAlignment = cue.textAlignment;
multiRowAlignment = cue.multiRowAlignment;
line = cue.line; line = cue.line;
lineType = cue.lineType; lineType = cue.lineType;
lineAnchor = cue.lineAnchor; lineAnchor = cue.lineAnchor;
...@@ -593,6 +606,18 @@ public final class Cue { ...@@ -593,6 +606,18 @@ public final class Cue {
} }
/** /**
* Sets the multi-row alignment of the cue.
*
* <p>Passing null means the alignment is undefined.
*
* @see Cue#multiRowAlignment
*/
public Builder setMultiRowAlignment(@Nullable Layout.Alignment multiRowAlignment) {
this.multiRowAlignment = multiRowAlignment;
return this;
}
/**
* Sets the position of the cue box within the viewport in the direction orthogonal to the * Sets the position of the cue box within the viewport in the direction orthogonal to the
* writing direction. * writing direction.
* *
...@@ -827,6 +852,7 @@ public final class Cue { ...@@ -827,6 +852,7 @@ public final class Cue {
return new Cue( return new Cue(
text, text,
textAlignment, textAlignment,
multiRowAlignment,
bitmap, bitmap,
line, line,
lineType, lineType,
......
...@@ -534,22 +534,10 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { ...@@ -534,22 +534,10 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
TtmlNode.ITALIC.equalsIgnoreCase(attributeValue)); TtmlNode.ITALIC.equalsIgnoreCase(attributeValue));
break; break;
case TtmlNode.ATTR_TTS_TEXT_ALIGN: case TtmlNode.ATTR_TTS_TEXT_ALIGN:
switch (Ascii.toLowerCase(attributeValue)) { style = createIfNull(style).setTextAlign(parseAlignment(attributeValue));
case TtmlNode.LEFT: break;
case TtmlNode.START: case TtmlNode.ATTR_EBUTTS_MULTI_ROW_ALIGN:
style = createIfNull(style).setTextAlign(Layout.Alignment.ALIGN_NORMAL); style = createIfNull(style).setMultiRowAlign(parseAlignment(attributeValue));
break;
case TtmlNode.RIGHT:
case TtmlNode.END:
style = createIfNull(style).setTextAlign(Layout.Alignment.ALIGN_OPPOSITE);
break;
case TtmlNode.CENTER:
style = createIfNull(style).setTextAlign(Layout.Alignment.ALIGN_CENTER);
break;
default:
// ignore
break;
}
break; break;
case TtmlNode.ATTR_TTS_TEXT_COMBINE: case TtmlNode.ATTR_TTS_TEXT_COMBINE:
switch (Ascii.toLowerCase(attributeValue)) { switch (Ascii.toLowerCase(attributeValue)) {
...@@ -632,6 +620,22 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { ...@@ -632,6 +620,22 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
return style == null ? new TtmlStyle() : style; return style == null ? new TtmlStyle() : style;
} }
@Nullable
private static Layout.Alignment parseAlignment(String alignment) {
switch (Ascii.toLowerCase(alignment)) {
case TtmlNode.LEFT:
case TtmlNode.START:
return Layout.Alignment.ALIGN_NORMAL;
case TtmlNode.RIGHT:
case TtmlNode.END:
return Layout.Alignment.ALIGN_OPPOSITE;
case TtmlNode.CENTER:
return Layout.Alignment.ALIGN_CENTER;
default:
return null;
}
}
private static TtmlNode parseNode( private static TtmlNode parseNode(
XmlPullParser parser, XmlPullParser parser,
@Nullable TtmlNode parent, @Nullable TtmlNode parent,
......
...@@ -72,6 +72,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -72,6 +72,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
public static final String ATTR_TTS_TEXT_EMPHASIS = "textEmphasis"; public static final String ATTR_TTS_TEXT_EMPHASIS = "textEmphasis";
public static final String ATTR_TTS_WRITING_MODE = "writingMode"; public static final String ATTR_TTS_WRITING_MODE = "writingMode";
public static final String ATTR_TTS_SHEAR = "shear"; public static final String ATTR_TTS_SHEAR = "shear";
public static final String ATTR_EBUTTS_MULTI_ROW_ALIGN = "multiRowAlign";
// Values for ruby // Values for ruby
public static final String RUBY_CONTAINER = "container"; public static final String RUBY_CONTAINER = "container";
...@@ -376,7 +377,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -376,7 +377,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
return; return;
} }
String resolvedRegionId = ANONYMOUS_REGION_ID.equals(regionId) ? inheritedRegion : regionId; String resolvedRegionId = ANONYMOUS_REGION_ID.equals(regionId) ? inheritedRegion : regionId;
for (Map.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;
...@@ -423,6 +423,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -423,6 +423,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
if (resolvedStyle.getTextAlign() != null) { if (resolvedStyle.getTextAlign() != null) {
regionOutput.setTextAlignment(resolvedStyle.getTextAlign()); regionOutput.setTextAlignment(resolvedStyle.getTextAlign());
} }
if (resolvedStyle.getMultiRowAlign() != null) {
regionOutput.setMultiRowAlignment(resolvedStyle.getMultiRowAlign());
}
} }
} }
} }
......
...@@ -86,6 +86,7 @@ import java.lang.annotation.RetentionPolicy; ...@@ -86,6 +86,7 @@ import java.lang.annotation.RetentionPolicy;
@RubyType private int rubyType; @RubyType private int rubyType;
@TextAnnotation.Position private int rubyPosition; @TextAnnotation.Position private int rubyPosition;
@Nullable private Layout.Alignment textAlign; @Nullable private Layout.Alignment textAlign;
@Nullable private Layout.Alignment multiRowAlign;
@OptionalBoolean private int textCombine; @OptionalBoolean private int textCombine;
@Nullable private TextEmphasis textEmphasis; @Nullable private TextEmphasis textEmphasis;
private float shearPercentage; private float shearPercentage;
...@@ -244,6 +245,9 @@ import java.lang.annotation.RetentionPolicy; ...@@ -244,6 +245,9 @@ import java.lang.annotation.RetentionPolicy;
if (textAlign == null && ancestor.textAlign != null) { if (textAlign == null && ancestor.textAlign != null) {
textAlign = ancestor.textAlign; textAlign = ancestor.textAlign;
} }
if (multiRowAlign == null && ancestor.multiRowAlign != null) {
multiRowAlign = ancestor.multiRowAlign;
}
if (textCombine == UNSPECIFIED) { if (textCombine == UNSPECIFIED) {
textCombine = ancestor.textCombine; textCombine = ancestor.textCombine;
} }
...@@ -308,6 +312,16 @@ import java.lang.annotation.RetentionPolicy; ...@@ -308,6 +312,16 @@ import java.lang.annotation.RetentionPolicy;
return this; return this;
} }
@Nullable
public Layout.Alignment getMultiRowAlign() {
return multiRowAlign;
}
public TtmlStyle setMultiRowAlign(@Nullable Layout.Alignment multiRowAlign) {
this.multiRowAlign = multiRowAlign;
return this;
}
/** Returns true if the source entity has {@code tts:textCombine=all}. */ /** Returns true if the source entity has {@code tts:textCombine=all}. */
public boolean getTextCombine() { public boolean getTextCombine() {
return textCombine == ON; return textCombine == ON;
......
...@@ -37,6 +37,7 @@ public class CueTest { ...@@ -37,6 +37,7 @@ public class CueTest {
new Cue.Builder() new Cue.Builder()
.setText(SpannedString.valueOf("text")) .setText(SpannedString.valueOf("text"))
.setTextAlignment(Layout.Alignment.ALIGN_CENTER) .setTextAlignment(Layout.Alignment.ALIGN_CENTER)
.setMultiRowAlignment(Layout.Alignment.ALIGN_NORMAL)
.setLine(5, Cue.LINE_TYPE_NUMBER) .setLine(5, Cue.LINE_TYPE_NUMBER)
.setLineAnchor(Cue.ANCHOR_TYPE_END) .setLineAnchor(Cue.ANCHOR_TYPE_END)
.setPosition(0.4f) .setPosition(0.4f)
...@@ -52,6 +53,7 @@ public class CueTest { ...@@ -52,6 +53,7 @@ public class CueTest {
assertThat(cue.text.toString()).isEqualTo("text"); assertThat(cue.text.toString()).isEqualTo("text");
assertThat(cue.textAlignment).isEqualTo(Layout.Alignment.ALIGN_CENTER); assertThat(cue.textAlignment).isEqualTo(Layout.Alignment.ALIGN_CENTER);
assertThat(cue.multiRowAlignment).isEqualTo(Layout.Alignment.ALIGN_NORMAL);
assertThat(cue.line).isEqualTo(5); assertThat(cue.line).isEqualTo(5);
assertThat(cue.lineType).isEqualTo(Cue.LINE_TYPE_NUMBER); assertThat(cue.lineType).isEqualTo(Cue.LINE_TYPE_NUMBER);
assertThat(cue.position).isEqualTo(0.4f); assertThat(cue.position).isEqualTo(0.4f);
...@@ -66,6 +68,7 @@ public class CueTest { ...@@ -66,6 +68,7 @@ public class CueTest {
assertThat(modifiedCue.text).isSameInstanceAs(cue.text); assertThat(modifiedCue.text).isSameInstanceAs(cue.text);
assertThat(modifiedCue.textAlignment).isEqualTo(cue.textAlignment); assertThat(modifiedCue.textAlignment).isEqualTo(cue.textAlignment);
assertThat(modifiedCue.multiRowAlignment).isEqualTo(cue.multiRowAlignment);
assertThat(modifiedCue.line).isEqualTo(cue.line); assertThat(modifiedCue.line).isEqualTo(cue.line);
assertThat(modifiedCue.lineType).isEqualTo(cue.lineType); assertThat(modifiedCue.lineType).isEqualTo(cue.lineType);
assertThat(modifiedCue.position).isEqualTo(cue.position); assertThat(modifiedCue.position).isEqualTo(cue.position);
......
...@@ -65,6 +65,7 @@ public final class TtmlDecoderTest { ...@@ -65,6 +65,7 @@ public final class TtmlDecoderTest {
private static final String BITMAP_UNSUPPORTED_REGION_FILE = private static final String BITMAP_UNSUPPORTED_REGION_FILE =
"media/ttml/bitmap_unsupported_region.xml"; "media/ttml/bitmap_unsupported_region.xml";
private static final String TEXT_ALIGN_FILE = "media/ttml/text_align.xml"; private static final String TEXT_ALIGN_FILE = "media/ttml/text_align.xml";
private static final String MULTI_ROW_ALIGN_FILE = "media/ttml/multi_row_align.xml";
private static final String VERTICAL_TEXT_FILE = "media/ttml/vertical_text.xml"; private static final String VERTICAL_TEXT_FILE = "media/ttml/vertical_text.xml";
private static final String TEXT_COMBINE_FILE = "media/ttml/text_combine.xml"; private static final String TEXT_COMBINE_FILE = "media/ttml/text_combine.xml";
private static final String RUBIES_FILE = "media/ttml/rubies.xml"; private static final String RUBIES_FILE = "media/ttml/rubies.xml";
...@@ -618,6 +619,32 @@ public final class TtmlDecoderTest { ...@@ -618,6 +619,32 @@ public final class TtmlDecoderTest {
} }
@Test @Test
public void multiRowAlign() throws IOException, SubtitleDecoderException {
TtmlSubtitle subtitle = getSubtitle(MULTI_ROW_ALIGN_FILE);
Cue firstCue = getOnlyCueAtTimeUs(subtitle, 10_000_000);
assertThat(firstCue.multiRowAlignment).isEqualTo(Layout.Alignment.ALIGN_NORMAL);
Cue secondCue = getOnlyCueAtTimeUs(subtitle, 20_000_000);
assertThat(secondCue.multiRowAlignment).isEqualTo(Layout.Alignment.ALIGN_CENTER);
Cue thirdCue = getOnlyCueAtTimeUs(subtitle, 30_000_000);
assertThat(thirdCue.multiRowAlignment).isEqualTo(Layout.Alignment.ALIGN_OPPOSITE);
Cue fourthCue = getOnlyCueAtTimeUs(subtitle, 40_000_000);
assertThat(fourthCue.multiRowAlignment).isEqualTo(Layout.Alignment.ALIGN_NORMAL);
Cue fifthCue = getOnlyCueAtTimeUs(subtitle, 50_000_000);
assertThat(fifthCue.multiRowAlignment).isEqualTo(Layout.Alignment.ALIGN_OPPOSITE);
Cue sixthCue = getOnlyCueAtTimeUs(subtitle, 60_000_000);
assertThat(sixthCue.multiRowAlignment).isNull();
Cue seventhCue = getOnlyCueAtTimeUs(subtitle, 70_000_000);
assertThat(seventhCue.multiRowAlignment).isNull();
}
@Test
public void verticalText() throws IOException, SubtitleDecoderException { public void verticalText() throws IOException, SubtitleDecoderException {
TtmlSubtitle subtitle = getSubtitle(VERTICAL_TEXT_FILE); TtmlSubtitle subtitle = getSubtitle(VERTICAL_TEXT_FILE);
......
...@@ -66,6 +66,7 @@ public final class TtmlStyleTest { ...@@ -66,6 +66,7 @@ public final class TtmlStyleTest {
.setRubyType(RUBY_TYPE) .setRubyType(RUBY_TYPE)
.setRubyPosition(RUBY_POSITION) .setRubyPosition(RUBY_POSITION)
.setTextAlign(TEXT_ALIGN) .setTextAlign(TEXT_ALIGN)
.setMultiRowAlign(Layout.Alignment.ALIGN_NORMAL)
.setTextCombine(TEXT_COMBINE) .setTextCombine(TEXT_COMBINE)
.setTextEmphasis(TextEmphasis.parse(TEXT_EMPHASIS_STYLE)) .setTextEmphasis(TextEmphasis.parse(TEXT_EMPHASIS_STYLE))
.setShearPercentage(SHEAR_PERCENTAGE); .setShearPercentage(SHEAR_PERCENTAGE);
...@@ -85,6 +86,7 @@ public final class TtmlStyleTest { ...@@ -85,6 +86,7 @@ public final class TtmlStyleTest {
assertThat(style.getFontSizeUnit()).isEqualTo(FONT_SIZE_UNIT); assertThat(style.getFontSizeUnit()).isEqualTo(FONT_SIZE_UNIT);
assertThat(style.getRubyPosition()).isEqualTo(RUBY_POSITION); assertThat(style.getRubyPosition()).isEqualTo(RUBY_POSITION);
assertThat(style.getTextAlign()).isEqualTo(TEXT_ALIGN); assertThat(style.getTextAlign()).isEqualTo(TEXT_ALIGN);
assertThat(style.getMultiRowAlign()).isEqualTo(Layout.Alignment.ALIGN_NORMAL);
assertThat(style.getTextCombine()).isEqualTo(TEXT_COMBINE); assertThat(style.getTextCombine()).isEqualTo(TEXT_COMBINE);
assertWithMessage("rubyType should not be inherited") assertWithMessage("rubyType should not be inherited")
.that(style.getRubyType()) .that(style.getRubyType())
...@@ -115,6 +117,7 @@ public final class TtmlStyleTest { ...@@ -115,6 +117,7 @@ public final class TtmlStyleTest {
assertThat(style.getFontSizeUnit()).isEqualTo(FONT_SIZE_UNIT); assertThat(style.getFontSizeUnit()).isEqualTo(FONT_SIZE_UNIT);
assertThat(style.getRubyPosition()).isEqualTo(RUBY_POSITION); assertThat(style.getRubyPosition()).isEqualTo(RUBY_POSITION);
assertThat(style.getTextAlign()).isEqualTo(TEXT_ALIGN); assertThat(style.getTextAlign()).isEqualTo(TEXT_ALIGN);
assertThat(style.getMultiRowAlign()).isEqualTo(Layout.Alignment.ALIGN_NORMAL);
assertThat(style.getTextCombine()).isEqualTo(TEXT_COMBINE); assertThat(style.getTextCombine()).isEqualTo(TEXT_COMBINE);
assertWithMessage("backgroundColor should be chained") assertWithMessage("backgroundColor should be chained")
.that(style.getBackgroundColor()) .that(style.getBackgroundColor())
...@@ -254,6 +257,18 @@ public final class TtmlStyleTest { ...@@ -254,6 +257,18 @@ public final class TtmlStyleTest {
} }
@Test @Test
public void multiRowAlign() {
TtmlStyle style = new TtmlStyle();
assertThat(style.getMultiRowAlign()).isEqualTo(null);
style.setMultiRowAlign(Layout.Alignment.ALIGN_CENTER);
assertThat(style.getMultiRowAlign()).isEqualTo(Layout.Alignment.ALIGN_CENTER);
style.setMultiRowAlign(Layout.Alignment.ALIGN_NORMAL);
assertThat(style.getMultiRowAlign()).isEqualTo(Layout.Alignment.ALIGN_NORMAL);
style.setMultiRowAlign(Layout.Alignment.ALIGN_OPPOSITE);
assertThat(style.getMultiRowAlign()).isEqualTo(Layout.Alignment.ALIGN_OPPOSITE);
}
@Test
public void textCombine() { public void textCombine() {
TtmlStyle style = new TtmlStyle(); TtmlStyle style = new TtmlStyle();
......
...@@ -302,11 +302,22 @@ import java.util.Map; ...@@ -302,11 +302,22 @@ import java.util.Map;
horizontalTranslatePercent, horizontalTranslatePercent,
verticalTranslatePercent, verticalTranslatePercent,
getBlockShearTransformFunction(cue))) getBlockShearTransformFunction(cue)))
.append(Util.formatInvariant("<span class='%s'>", DEFAULT_BACKGROUND_CSS_CLASS)) .append(Util.formatInvariant("<span class='%s'>", DEFAULT_BACKGROUND_CSS_CLASS));
.append(htmlAndCss.html)
.append("</span>") if (cue.multiRowAlignment != null) {
.append("</div>"); html.append(
Util.formatInvariant(
"<span style='display:inline-block; text-align:%s;'>",
convertAlignmentToCss(cue.multiRowAlignment)))
.append(htmlAndCss.html)
.append("</span>");
} else {
html.append(htmlAndCss.html);
}
html.append("</span>").append("</div>");
} }
html.append("</div></body></html>"); html.append("</div></body></html>");
StringBuilder htmlHead = new StringBuilder(); StringBuilder htmlHead = new StringBuilder();
......
<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:ebutts="urn:ebu:tt:style" >
<head>
<region xml:id="region_tbrl" tts:extent="80.000% 80.000%" tts:origin="10.000% 10.000%" tts:writingMode="tbrl"/>
<region xml:id="region_tblr" tts:extent="80.000% 80.000%" tts:origin="10.000% 10.000%" tts:writingMode="tblr"/>
<region xml:id="region_lr" tts:extent="80.000% 80.000%" tts:origin="10.000% 10.000%" tts:writingMode="lr"/>
</head>
<body>
<div>
<p begin="10s" end="18s" region="region_lr" ebutts:multiRowAlign="start">multi row<br/>align start</p>
</div>
<div>
<p begin="20s" end="28s" region="region_tblr" ebutts:multiRowAlign="center">multi row<br/>align center</p>
</div>
<div>
<p begin="30s" end="38s" region="region_tbrl" ebutts:multiRowAlign="end">multi row<br/>align end</p>
</div>
<div>
<p begin="40s" end="48s" region="region_lr" ebutts:multiRowAlign="left">multi row<br/>align left</p>
</div>
<div>
<p begin="50s" end="58s" region="region_lr" ebutts:multiRowAlign="right">multi row<br/>align right</p>
</div>
<div>
<p begin="60s" end="68s" region="region_lr">no multi row<br/>align</p>
</div>
<div>
<p begin="70s" end="78s" region="region_lr"><span ebutts:multiRowAlign="end">align set on<br/>span (invalid)</span></p>
</div>
</body>
</tt>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment