Fix TTML ruby decoding to resolve styles by ID

The current code only works if the tts:ruby attributes are defined
directly on the in-line elements. This changes that so we also consider
tts:ruby attributes on `style` nodes referenced by ID.
PiperOrigin-RevId: 319515177
This commit is contained in:
ibaker 2020-07-03 15:12:42 +01:00 committed by Ian Baker
parent 08478d1163
commit f39b1d0f90
4 changed files with 48 additions and 17 deletions

View File

@ -379,7 +379,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
regionOutput.setText(text);
}
if (resolvedStyle != null) {
TtmlRenderUtil.applyStylesToSpan(text, start, end, resolvedStyle, parent);
TtmlRenderUtil.applyStylesToSpan(text, start, end, resolvedStyle, parent, globalStyles);
regionOutput
.setTextAlignment(resolvedStyle.getTextAlign())
.setVerticalType(resolvedStyle.getVerticalType());

View File

@ -78,7 +78,12 @@ import java.util.Map;
}
public static void applyStylesToSpan(
Spannable builder, int start, int end, TtmlStyle style, @Nullable TtmlNode parent) {
Spannable builder,
int start,
int end,
TtmlStyle style,
@Nullable TtmlNode parent,
Map<String, TtmlStyle> globalStyles) {
if (style.getStyle() != TtmlStyle.UNSPECIFIED) {
builder.setSpan(new StyleSpan(style.getStyle()), start, end,
@ -117,12 +122,12 @@ import java.util.Map;
switch (style.getRubyType()) {
case TtmlStyle.RUBY_TYPE_BASE:
// look for the sibling RUBY_TEXT and add it as span between start & end.
@Nullable TtmlNode containerNode = findRubyContainerNode(parent);
@Nullable TtmlNode containerNode = findRubyContainerNode(parent, globalStyles);
if (containerNode == null) {
// No matching container node
break;
}
@Nullable TtmlNode textNode = findRubyTextNode(containerNode);
@Nullable TtmlNode textNode = findRubyTextNode(containerNode, globalStyles);
if (textNode == null) {
// no matching text node
break;
@ -200,12 +205,15 @@ import java.util.Map;
}
@Nullable
private static TtmlNode findRubyTextNode(TtmlNode rubyContainerNode) {
private static TtmlNode findRubyTextNode(
TtmlNode rubyContainerNode, Map<String, TtmlStyle> globalStyles) {
Deque<TtmlNode> childNodesStack = new ArrayDeque<>();
childNodesStack.push(rubyContainerNode);
while (!childNodesStack.isEmpty()) {
TtmlNode childNode = childNodesStack.pop();
if (childNode.style != null && childNode.style.getRubyType() == TtmlStyle.RUBY_TYPE_TEXT) {
@Nullable
TtmlStyle style = resolveStyle(childNode.style, childNode.getStyleIds(), globalStyles);
if (style != null && style.getRubyType() == TtmlStyle.RUBY_TYPE_TEXT) {
return childNode;
}
for (int i = childNode.getChildCount() - 1; i >= 0; i--) {
@ -217,9 +225,10 @@ import java.util.Map;
}
@Nullable
private static TtmlNode findRubyContainerNode(@Nullable TtmlNode node) {
private static TtmlNode findRubyContainerNode(
@Nullable TtmlNode node, Map<String, TtmlStyle> globalStyles) {
while (node != null) {
@Nullable TtmlStyle style = node.style;
@Nullable TtmlStyle style = resolveStyle(node.style, node.getStyleIds(), globalStyles);
if (style != null && style.getRubyType() == TtmlStyle.RUBY_TYPE_CONTAINER) {
return node;
}

View File

@ -656,15 +656,19 @@ public final class TtmlDecoderTest {
Spanned thirdCue = getOnlyCueTextAtTimeUs(subtitle, 30_000_000);
assertThat(thirdCue.toString()).isEqualTo("Cue with annotated text.");
assertThat(thirdCue).hasNoRubySpanBetween(0, thirdCue.length());
assertThat(thirdCue).hasRubySpanBetween("Cue with ".length(), "Cue with annotated".length());
Spanned fourthCue = getOnlyCueTextAtTimeUs(subtitle, 40_000_000);
assertThat(fourthCue.toString()).isEqualTo("Cue with text.");
assertThat(fourthCue.toString()).isEqualTo("Cue with annotated text.");
assertThat(fourthCue).hasNoRubySpanBetween(0, fourthCue.length());
Spanned fifthCue = getOnlyCueTextAtTimeUs(subtitle, 50_000_000);
assertThat(fifthCue.toString()).isEqualTo("Cue with annotated text.");
assertThat(fifthCue.toString()).isEqualTo("Cue with text.");
assertThat(fifthCue).hasNoRubySpanBetween(0, fifthCue.length());
Spanned sixthCue = getOnlyCueTextAtTimeUs(subtitle, 60_000_000);
assertThat(sixthCue.toString()).isEqualTo("Cue with annotated text.");
assertThat(sixthCue).hasNoRubySpanBetween(0, sixthCue.length());
}
private static Spanned getOnlyCueTextAtTimeUs(Subtitle subtitle, long timeUs) {

View File

@ -16,9 +16,15 @@
-->
<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:tts="http://www.w3.org/2006/10/ttaf1#style" xmlns="http://www.w3.org/ns/ttml"
xmlns="http://www.w3.org/2006/10/ttaf1">
<head>
<styling>
<style id="cont" tts:ruby="container" />
<style id="base" tts:ruby="base" />
<style id="text" tts:ruby="text" />
</styling>
</head>
<body>
<div>
<!-- Base before and after text, one with explicit position -->
@ -47,17 +53,29 @@
</p>
</div>
<div>
<!-- No text section -> no span -->
<!-- ruby info in style block -->
<p begin="30s" end="38s">
Cue with
<span style="cont">
<span style="base">annotated</span>
<span style="text">rubies</span>
text.
</span>
</p>
</div>
<div>
<!-- No text section -> no span -->
<p begin="40s" end="48s">
Cue with
<span tts:ruby="container" tts:rubyPosition="before">
<span tts:ruby="base">annotated</span>
</span>
text.</p>
text.
</p>
</div>
<div>
<!-- No base section -> text still stripped-->
<p begin="40s" end="48s">
<p begin="50s" end="58s">
Cue with
<span tts:ruby="container" tts:rubyPosition="before">
<span tts:ruby="text">rubies</span>
@ -67,7 +85,7 @@
</div>
<div>
<!-- No container section -> text still stripped-->
<p begin="50s" end="58s">
<p begin="60s" end="68s">
Cue with
<span tts:ruby="text">rubies</span>
<span tts:ruby="base">annotated</span>