Add support for TTML regions.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=121564959
This commit is contained in:
olly 2016-05-05 02:58:12 -07:00 committed by Oliver Woodman
parent d978398faf
commit 1fea3fe3c0
18 changed files with 381 additions and 503 deletions

View File

@ -1,7 +1,8 @@
<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">
<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">
<head>
<styling>
<style id="s0"

View File

@ -1,7 +1,8 @@
<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">
<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">
<head>
<styling>
<style id="s0"

View File

@ -1,7 +1,8 @@
<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">
<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">
<head>
<styling>
<style id="s0"

View File

@ -0,0 +1,29 @@
<tt xmlns="http://www.w3.org/ns/ttml"
xmlns="http://www.w3.org/2006/10/ttaf1"
xmlns:id="http://www.w3.org/XML/1998/namespace"
xmlns:ttp="http://www.w3.org/ns/ttml#parameter"
xmlns:tts="http://www.w3.org/ns/ttml#styling"
xmlns:ttm="http://www.w3.org/ns/ttml#metadata">
<head>
<layout>
<region xml:id="region1" ttm:origin="10% 10%" extent="20% 20%"/>
<region xml:id="region2" ttm:origin="40% 40%" extent="20% 20%"/>
<region xml:id="region3" ttm:origin="10% 80%" extent="10% 10%"/>
<region xml:id="region4" ttm:origin="60% 10%" extent="20% 20%"/>
<region xml:id="ultimate" ttm:origin="45% 45%" extent="35% 35%"/>
</layout>
</head>
<body>
<div>
<p begin="1s" end="4s" region="region1">lorem</p>
<p begin="5s" end="8s" region="region2">ipsum</p>
<p begin="9s" end="18s" region="region3">dolor</p>
<p begin="1s" end="4s" region="region4">amet</p>
</div>
<div region="ultimate">
<p begin="21s" end="34s">She first said this</p>
<p begin="25s" end="34s">Then this</p>
<p begin="29s" end="34s">Finally this</p>
</div>
</body>
</tt>

View File

@ -1,17 +0,0 @@
<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:backgroundColor="black"
abc:fontFamily="sansSerif"
def:fontStyle="italic"
ghi:textDecoration="lineThrough"
jkl:color="yellow">text 1</p>
</div>
</body>
</tt>

View File

@ -1,13 +0,0 @@
<tt>
<body>
<div>
<p begin="10s" end="18s"
tts:backgroundColor="black"
abc:fontFamily="sansSerif"
def:fontStyle="italic"
ghi:textDecoration="lineThrough"
jkl:color="yellow">text 1</p>
</div>
</body>
</tt>

View File

@ -1,115 +0,0 @@
/*
* Copyright (C) 2014 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.exoplayer.text.ttml;
import android.graphics.Color;
import android.test.InstrumentationTestCase;
/**
* Unit test for <code>TtmlColorParser</code>.
*/
public class TtmlColorParserTest extends InstrumentationTestCase {
public void testHexCodeParsing() {
assertEquals(Color.WHITE, TtmlColorParser.parseColor("#FFFFFF"));
assertEquals(Color.WHITE, TtmlColorParser.parseColor("#FFFFFFFF"));
assertEquals(Color.parseColor("#FF123456"), TtmlColorParser.parseColor("#123456"));
// Hex colors in TTML are RGBA, where-as {@link Color#parseColor} takes ARGB.
assertEquals(Color.parseColor("#00FFFFFF"), TtmlColorParser.parseColor("#FFFFFF00"));
assertEquals(Color.parseColor("#78123456"), TtmlColorParser.parseColor("#12345678"));
}
public void testColorNameParsing() {
assertEquals(TtmlColorParser.TRANSPARENT, TtmlColorParser.parseColor("transparent"));
assertEquals(TtmlColorParser.BLACK, TtmlColorParser.parseColor("black"));
assertEquals(TtmlColorParser.GRAY, TtmlColorParser.parseColor("gray"));
assertEquals(TtmlColorParser.SILVER, TtmlColorParser.parseColor("silver"));
assertEquals(TtmlColorParser.WHITE, TtmlColorParser.parseColor("white"));
assertEquals(TtmlColorParser.MAROON, TtmlColorParser.parseColor("maroon"));
assertEquals(TtmlColorParser.RED, TtmlColorParser.parseColor("red"));
assertEquals(TtmlColorParser.PURPLE, TtmlColorParser.parseColor("purple"));
assertEquals(TtmlColorParser.FUCHSIA, TtmlColorParser.parseColor("fuchsia"));
assertEquals(TtmlColorParser.MAGENTA, TtmlColorParser.parseColor("magenta"));
assertEquals(TtmlColorParser.GREEN, TtmlColorParser.parseColor("green"));
assertEquals(TtmlColorParser.LIME, TtmlColorParser.parseColor("lime"));
assertEquals(TtmlColorParser.OLIVE, TtmlColorParser.parseColor("olive"));
assertEquals(TtmlColorParser.YELLOW, TtmlColorParser.parseColor("yellow"));
assertEquals(TtmlColorParser.NAVY, TtmlColorParser.parseColor("navy"));
assertEquals(TtmlColorParser.BLUE, TtmlColorParser.parseColor("blue"));
assertEquals(TtmlColorParser.TEAL, TtmlColorParser.parseColor("teal"));
assertEquals(TtmlColorParser.AQUA, TtmlColorParser.parseColor("aqua"));
assertEquals(TtmlColorParser.CYAN, TtmlColorParser.parseColor("cyan"));
}
public void testParseUnknownColor() {
try {
TtmlColorParser.parseColor("colorOfAnElectron");
fail();
} catch (IllegalArgumentException e) {
// expected
}
}
public void testParseNull() {
try {
TtmlColorParser.parseColor(null);
fail();
} catch (IllegalArgumentException e) {
// expected
}
}
public void testParseEmpty() {
try {
TtmlColorParser.parseColor("");
fail();
} catch (IllegalArgumentException e) {
// expected
}
}
public void testRgbColorParsing() {
assertEquals(Color.WHITE, TtmlColorParser.parseColor("rgb(255,255,255)"));
// spaces do not matter
assertEquals(Color.WHITE, TtmlColorParser.parseColor(" rgb ( 255, 255, 255)"));
}
public void testRgbColorParsing_rgbValuesOutOfBounds() {
int outOfBounds = TtmlColorParser.parseColor("rgb(999, 999, 999)");
int color = Color.rgb(999, 999, 999);
// behave like framework Color behaves
assertEquals(color, outOfBounds);
}
public void testRgbColorParsing_rgbValuesNegative() {
try {
TtmlColorParser.parseColor("rgb(-4, 55, 209)");
fail();
} catch (IllegalArgumentException e) {
// expected
}
}
public void testRgbaColorParsing() {
assertEquals(Color.WHITE, TtmlColorParser.parseColor("rgba(255,255,255,0)"));
assertEquals(Color.argb(0, 255, 255, 255), TtmlColorParser.parseColor("rgba(255,255,255,255)"));
assertEquals(Color.BLACK, TtmlColorParser.parseColor("rgba(0, 0, 0, 0)"));
assertEquals(Color.argb(0, 0, 0, 0), TtmlColorParser.parseColor("rgba(0, 0, 0, 255)"));
assertEquals(Color.RED, TtmlColorParser.parseColor("rgba(255, 0, 0, 0)"));
assertEquals(Color.argb(0, 255, 0, 0), TtmlColorParser.parseColor("rgba(255, 0, 0, 255)"));
assertEquals(Color.argb(205, 255, 0, 0), TtmlColorParser.parseColor("rgba(255, 0, 0, 50)"));
}
}

View File

@ -51,10 +51,9 @@ public final class TtmlParserTest extends InstrumentationTestCase {
private static final String INHERIT_MULTIPLE_STYLES_TTML_FILE =
"ttml/inherit_multiple_styles.xml";
private static final String CHAIN_MULTIPLE_STYLES_TTML_FILE = "ttml/chain_multiple_styles.xml";
private static final String MULTIPLE_REGIONS_TTML_FILE = "ttml/multiple_regions.xml";
private static final String NO_UNDERLINE_LINETHROUGH_TTML_FILE =
"ttml/no_underline_linethrough.xml";
private static final String NAMESPACE_CONFUSION_TTML_FILE = "ttml/namespace_confusion.xml";
private static final String NAMESPACE_NOT_DECLARED_TTML_FILE = "ttml/namespace_not_declared.xml";
private static final String FONT_SIZE_TTML_FILE = "ttml/font_size.xml";
private static final String FONT_SIZE_MISSING_UNIT_TTML_FILE = "ttml/font_size_no_unit.xml";
private static final String FONT_SIZE_INVALID_TTML_FILE = "ttml/font_size_invalid.xml";
@ -69,7 +68,7 @@ public final class TtmlParserTest extends InstrumentationTestCase {
TtmlNode body = queryChildrenForTag(root, TtmlNode.TAG_BODY, 0);
TtmlNode firstDiv = queryChildrenForTag(body, TtmlNode.TAG_DIV, 0);
TtmlStyle firstPStyle = queryChildrenForTag(firstDiv, TtmlNode.TAG_P, 0).style;
assertEquals(ColorParser.parseTtmlColor("yellow"), firstPStyle.getColor());
assertEquals(ColorParser.parseTtmlColor("yellow"), firstPStyle.getFontColor());
assertEquals(ColorParser.parseTtmlColor("blue"), firstPStyle.getBackgroundColor());
assertEquals("serif", firstPStyle.getFontFamily());
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, firstPStyle.getStyle());
@ -84,15 +83,14 @@ public final class TtmlParserTest extends InstrumentationTestCase {
}
/**
* regression test for devices on JellyBean where some named colors are not correctly defined
* Regression test for devices on JellyBean where some named colors are not correctly defined
* on framework level. Tests that <i>lime</i> resolves to <code>#FF00FF00</code> not
* <code>#00FF00</code>.
*
* See: https://github.com/android/platform_frameworks_base/blob/jb-mr2-release/
* graphics/java/android/graphics/Color.java#L414
* https://github.com/android/platform_frameworks_base/blob/kitkat-mr2.2-release/
* graphics/java/android/graphics/Color.java#L414
*
* @see <a href="https://github.com/android/platform_frameworks_base/blob/jb-mr2-release/graphics/java/android/graphics/Color.java#L414">
* JellyBean Color</a>
* <a href="https://github.com/android/platform_frameworks_base/blob/kitkat-mr2.2-release/graphics/java/android/graphics/Color.java#L414">
* Kitkat Color</a>
* @throws IOException thrown if reading subtitle file fails.
*/
public void testLime() throws IOException {
@ -132,7 +130,6 @@ public final class TtmlParserTest extends InstrumentationTestCase {
public void testInheritMultipleStyles() throws IOException {
TtmlSubtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE);
assertEquals(12, subtitle.getEventTimeCount());
assertSpans(subtitle, 10, "text 1", "sansSerif", TtmlStyle.STYLE_BOLD_ITALIC, 0xFF0000FF,
0xFFFFFF00, false, true, null);
}
@ -140,7 +137,6 @@ public final class TtmlParserTest extends InstrumentationTestCase {
public void testInheritMultipleStylesWithoutLocalAttributes() throws IOException {
TtmlSubtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE);
assertEquals(12, subtitle.getEventTimeCount());
assertSpans(subtitle, 20, "text 2", "sansSerif", TtmlStyle.STYLE_BOLD_ITALIC, 0xFF0000FF,
0xFF000000, false, true, null);
}
@ -148,11 +144,54 @@ public final class TtmlParserTest extends InstrumentationTestCase {
public void testMergeMultipleStylesWithParentStyle() throws IOException {
TtmlSubtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE);
assertEquals(12, subtitle.getEventTimeCount());
assertSpans(subtitle, 30, "text 2.5", "sansSerifInline", TtmlStyle.STYLE_ITALIC, 0xFFFF0000,
0xFFFFFF00, true, true, null);
}
public void testMultipleRegions() throws IOException {
TtmlSubtitle subtitle = getSubtitle(MULTIPLE_REGIONS_TTML_FILE);
List<Cue> output = subtitle.getCues(1000000);
assertEquals(2, output.size());
Cue ttmlCue = output.get(0);
assertEquals("lorem", ttmlCue.text.toString());
assertEquals(10.f / 100.f, ttmlCue.position);
assertEquals(10.f / 100.f, ttmlCue.line);
ttmlCue = output.get(1);
assertEquals("amet", ttmlCue.text.toString());
assertEquals(60.f / 100.f, ttmlCue.position);
assertEquals(10.f / 100.f, ttmlCue.line);
output = subtitle.getCues(5000000);
assertEquals(1, output.size());
ttmlCue = output.get(0);
assertEquals("ipsum", ttmlCue.text.toString());
assertEquals(40.f / 100.f, ttmlCue.position);
assertEquals(40.f / 100.f, ttmlCue.line);
output = subtitle.getCues(9000000);
assertEquals(1, output.size());
ttmlCue = output.get(0);
assertEquals("dolor", ttmlCue.text.toString());
assertEquals(10.f / 100.f, ttmlCue.position);
assertEquals(80.f / 100.f, ttmlCue.line);
output = subtitle.getCues(21000000);
assertEquals(1, output.size());
ttmlCue = output.get(0);
assertEquals("She first said this", ttmlCue.text.toString());
assertEquals(45.f / 100.f, ttmlCue.position);
assertEquals(45.f / 100.f, ttmlCue.line);
output = subtitle.getCues(25000000);
ttmlCue = output.get(0);
assertEquals("She first said this\nThen this", ttmlCue.text.toString());
output = subtitle.getCues(29000000);
assertEquals(1, output.size());
ttmlCue = output.get(0);
assertEquals("She first said this\nThen this\nFinally this", ttmlCue.text.toString());
assertEquals(45.f / 100.f, ttmlCue.position);
assertEquals(45.f / 100.f, ttmlCue.line);
}
public void testEmptyStyleAttribute() throws IOException {
TtmlSubtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE);
assertEquals(12, subtitle.getEventTimeCount());
@ -196,13 +235,13 @@ public final class TtmlParserTest extends InstrumentationTestCase {
TtmlStyle style = globalStyles.get("s2");
assertEquals("serif", style.getFontFamily());
assertEquals(0xFFFF0000, style.getBackgroundColor());
assertEquals(0xFF000000, style.getColor());
assertEquals(0xFF000000, style.getFontColor());
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, style.getStyle());
assertTrue(style.isLinethrough());
style = globalStyles.get("s3");
// only difference: color must be RED
assertEquals(0xFFFF0000, style.getColor());
assertEquals(0xFFFF0000, style.getFontColor());
assertEquals("serif", style.getFontFamily());
assertEquals(0xFFFF0000, style.getBackgroundColor());
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, style.getStyle());
@ -234,43 +273,6 @@ public final class TtmlParserTest extends InstrumentationTestCase {
style.isLinethrough());
}
public void testNamspaceConfusionDoesNotHurt() throws IOException {
TtmlSubtitle subtitle = getSubtitle(NAMESPACE_CONFUSION_TTML_FILE);
assertEquals(2, subtitle.getEventTimeCount());
TtmlNode root = subtitle.getRoot();
TtmlNode body = queryChildrenForTag(root, TtmlNode.TAG_BODY, 0);
TtmlNode div = queryChildrenForTag(body, TtmlNode.TAG_DIV, 0);
TtmlStyle style = queryChildrenForTag(div, TtmlNode.TAG_P, 0).style;
assertNotNull(style);
assertEquals(0xFF000000, style.getBackgroundColor());
assertEquals(0xFFFFFF00, style.getColor());
assertEquals(TtmlStyle.STYLE_ITALIC, style.getStyle());
assertEquals("sansSerif", style.getFontFamily());
assertFalse(style.isUnderline());
assertTrue(style.isLinethrough());
}
public void testNamespaceNotDeclared() throws IOException {
TtmlSubtitle subtitle = getSubtitle(NAMESPACE_NOT_DECLARED_TTML_FILE);
assertEquals(2, subtitle.getEventTimeCount());
TtmlNode root = subtitle.getRoot();
TtmlNode body = queryChildrenForTag(root, TtmlNode.TAG_BODY, 0);
TtmlNode div = queryChildrenForTag(body, TtmlNode.TAG_DIV, 0);
TtmlStyle style = queryChildrenForTag(div, TtmlNode.TAG_P, 0).style;
assertNotNull(style);
assertEquals(0xFF000000, style.getBackgroundColor());
assertEquals(0xFFFFFF00, style.getColor());
assertEquals(TtmlStyle.STYLE_ITALIC, style.getStyle());
assertEquals("sansSerif", style.getFontFamily());
assertFalse(style.isUnderline());
assertTrue(style.isLinethrough());
}
public void testFontSizeSpans() throws IOException {
TtmlSubtitle subtitle = getSubtitle(FONT_SIZE_TTML_FILE);
assertEquals(10, subtitle.getEventTimeCount());

View File

@ -48,7 +48,7 @@ public class TtmlRenderUtilTest extends InstrumentationTestCase {
// inherited from s0
assertEquals(Color.BLACK, resolved.getBackgroundColor());
// inherited from s1
assertEquals(Color.RED, resolved.getColor());
assertEquals(Color.RED, resolved.getFontColor());
// merged from s0 and s1
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, resolved.getStyle());
}
@ -101,7 +101,7 @@ public class TtmlRenderUtilTest extends InstrumentationTestCase {
TtmlStyle s1 = new TtmlStyle();
s1.setId("s1");
s1.setBackgroundColor(Color.RED);
s1.setColor(Color.RED);
s1.setFontColor(Color.RED);
s1.setItalic(true);
globalStyles.put(s1.getId(), s1);

View File

@ -42,8 +42,8 @@ public final class TtmlStyleTest extends InstrumentationTestCase {
assertTrue(style.isLinethrough());
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, style.getStyle());
assertEquals(FONT_FAMILY, style.getFontFamily());
assertEquals(Color.WHITE, style.getColor());
assertFalse("do not inherit backgroundColor", style.hasBackgroundColorSpecified());
assertEquals(Color.WHITE, style.getFontColor());
assertFalse("do not inherit backgroundColor", style.hasBackgroundColor());
}
public void testChainStyle() {
@ -53,7 +53,7 @@ public final class TtmlStyleTest extends InstrumentationTestCase {
assertTrue(style.isLinethrough());
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, style.getStyle());
assertEquals(FONT_FAMILY, style.getFontFamily());
assertEquals(FOREGROUND_COLOR, style.getColor());
assertEquals(FOREGROUND_COLOR, style.getFontColor());
// do inherit backgroundColor when chaining
assertEquals("do not inherit backgroundColor when chaining",
BACKGROUND_COLOR, style.getBackgroundColor());
@ -65,7 +65,7 @@ public final class TtmlStyleTest extends InstrumentationTestCase {
ancestor.setItalic(true);
ancestor.setBold(true);
ancestor.setBackgroundColor(BACKGROUND_COLOR);
ancestor.setColor(FOREGROUND_COLOR);
ancestor.setFontColor(FOREGROUND_COLOR);
ancestor.setLinethrough(true);
ancestor.setUnderline(true);
ancestor.setFontFamily(FONT_FAMILY);
@ -109,17 +109,17 @@ public final class TtmlStyleTest extends InstrumentationTestCase {
}
public void testColor() {
assertFalse(style.hasColorSpecified());
style.setColor(Color.BLACK);
assertEquals(Color.BLACK, style.getColor());
assertTrue(style.hasColorSpecified());
assertFalse(style.hasFontColor());
style.setFontColor(Color.BLACK);
assertEquals(Color.BLACK, style.getFontColor());
assertTrue(style.hasFontColor());
}
public void testBackgroundColor() {
assertFalse(style.hasBackgroundColorSpecified());
assertFalse(style.hasBackgroundColor());
style.setBackgroundColor(Color.BLACK);
assertEquals(Color.BLACK, style.getBackgroundColor());
assertTrue(style.hasBackgroundColorSpecified());
assertTrue(style.hasBackgroundColor());
}
public void testId() {

View File

@ -1,140 +0,0 @@
/*
* Copyright (C) 2014 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.exoplayer.text.ttml;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.Util;
import android.text.TextUtils;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Parser to parse ttml color value expression
* (http://www.w3.org/TR/ttml1/#style-value-color)
*/
/*package*/ final class TtmlColorParser {
private static final String RGB = "rgb";
private static final String RGBA = "rgba";
private static final Pattern RGB_PATTERN = Pattern.compile(
"^rgb\\((\\d{1,3}),(\\d{1,3}),(\\d{1,3})\\)$");
private static final Pattern RGBA_PATTERN = Pattern.compile(
"^rgba\\((\\d{1,3}),(\\d{1,3}),(\\d{1,3}),(\\d{1,3})\\)$");
static final int TRANSPARENT = 0x00000000;
static final int BLACK = 0xFF000000;
static final int SILVER = 0xFFC0C0C0;
static final int GRAY = 0xFF808080;
static final int WHITE = 0xFFFFFFFF;
static final int MAROON = 0xFF800000;
static final int RED = 0xFFFF0000;
static final int PURPLE = 0xFF800080;
static final int FUCHSIA = 0xFFFF00FF;
static final int MAGENTA = FUCHSIA;
static final int GREEN = 0xFF008000;
static final int LIME = 0xFF00FF00;
static final int OLIVE = 0xFF808000;
static final int YELLOW = 0xFFFFFF00;
static final int NAVY = 0xFF000080;
static final int BLUE = 0xFF0000FF;
static final int TEAL = 0xFF008080;
static final int AQUA = 0x00FFFFFF;
static final int CYAN = 0xFF00FFFF;
private static final Map<String, Integer> COLOR_NAME_MAP;
static {
COLOR_NAME_MAP = new HashMap<>();
COLOR_NAME_MAP.put("transparent", TRANSPARENT);
COLOR_NAME_MAP.put("black", BLACK);
COLOR_NAME_MAP.put("silver", SILVER);
COLOR_NAME_MAP.put("gray", GRAY);
COLOR_NAME_MAP.put("white", WHITE);
COLOR_NAME_MAP.put("maroon", MAROON);
COLOR_NAME_MAP.put("red", RED);
COLOR_NAME_MAP.put("purple", PURPLE);
COLOR_NAME_MAP.put("fuchsia", FUCHSIA);
COLOR_NAME_MAP.put("magenta", MAGENTA);
COLOR_NAME_MAP.put("green", GREEN);
COLOR_NAME_MAP.put("lime", LIME);
COLOR_NAME_MAP.put("olive", OLIVE);
COLOR_NAME_MAP.put("yellow", YELLOW);
COLOR_NAME_MAP.put("navy", NAVY);
COLOR_NAME_MAP.put("blue", BLUE);
COLOR_NAME_MAP.put("teal", TEAL);
COLOR_NAME_MAP.put("aqua", AQUA);
COLOR_NAME_MAP.put("cyan", CYAN);
}
public static int parseColor(String colorExpression) {
Assertions.checkArgument(!TextUtils.isEmpty(colorExpression));
colorExpression = colorExpression.replace(" ", "");
if (colorExpression.charAt(0) == '#') {
// Parse using Long to avoid failure when colorExpression is greater than #7FFFFFFF.
int color = (int) Long.parseLong(colorExpression.substring(1), 16);
if (colorExpression.length() == 7) {
// Set the alpha value
color |= 0xFF000000;
} else if (colorExpression.length() == 9) {
// We have #RRGGBBAA, but we need #AARRGGBB
color = ((color & 0xFF) << 24) | (color >>> 8);
} else {
throw new IllegalArgumentException();
}
return color;
} else if (colorExpression.startsWith(RGBA)) {
Matcher matcher = RGBA_PATTERN.matcher(colorExpression);
if (matcher.matches()) {
return argb(
255 - Integer.parseInt(matcher.group(4), 10),
Integer.parseInt(matcher.group(1), 10),
Integer.parseInt(matcher.group(2), 10),
Integer.parseInt(matcher.group(3), 10)
);
}
} else if (colorExpression.startsWith(RGB)) {
Matcher matcher = RGB_PATTERN.matcher(colorExpression);
if (matcher.matches()) {
return rgb(
Integer.parseInt(matcher.group(1), 10),
Integer.parseInt(matcher.group(2), 10),
Integer.parseInt(matcher.group(3), 10)
);
}
} else {
// we use our own color map
Integer color = COLOR_NAME_MAP.get(Util.toLowerInvariant(colorExpression));
if (color != null) {
return color;
}
}
throw new IllegalArgumentException();
}
private static int argb(int alpha, int red, int green, int blue) {
return (alpha << 24) | (red << 16) | (green << 8) | blue;
}
private static int rgb(int red, int green, int blue) {
return argb(0xFF, red, green, blue);
}
}

View File

@ -15,12 +15,17 @@
*/
package com.google.android.exoplayer.text.ttml;
import com.google.android.exoplayer.text.Cue;
import com.google.android.exoplayer.util.Assertions;
import android.text.SpannableStringBuilder;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.TreeSet;
/**
@ -45,6 +50,7 @@ import java.util.TreeSet;
public static final String TAG_SMPTE_DATA = "smpte:data";
public static final String TAG_SMPTE_INFORMATION = "smpte:information";
public static final String ANONYMOUS_REGION_ID = "";
public static final String ATTR_ID = "id";
public static final String ATTR_TTS_BACKGROUND_COLOR = "backgroundColor";
public static final String ATTR_TTS_FONT_STYLE = "fontStyle";
@ -52,6 +58,7 @@ import java.util.TreeSet;
public static final String ATTR_TTS_FONT_FAMILY = "fontFamily";
public static final String ATTR_TTS_FONT_WEIGHT = "fontWeight";
public static final String ATTR_TTS_COLOR = "color";
public static final String ATTR_TTS_ORIGIN = "origin";
public static final String ATTR_TTS_TEXT_DECORATION = "textDecoration";
public static final String ATTR_TTS_TEXT_ALIGN = "textAlign";
@ -74,24 +81,26 @@ import java.util.TreeSet;
public final long startTimeUs;
public final long endTimeUs;
public final TtmlStyle style;
public final String regionId;
private final String[] styleIds;
private final HashMap<String, Integer> nodeStartsByRegion;
private final HashMap<String, Integer> nodeEndsByRegion;
private List<TtmlNode> children;
private int start;
private int end;
public static TtmlNode buildTextNode(String text) {
return new TtmlNode(null, TtmlRenderUtil.applyTextElementSpacePolicy(text), UNDEFINED_TIME,
UNDEFINED_TIME, null, null);
UNDEFINED_TIME, null, null, ANONYMOUS_REGION_ID);
}
public static TtmlNode buildNode(String tag, long startTimeUs, long endTimeUs,
TtmlStyle style, String[] styleIds) {
return new TtmlNode(tag, null, startTimeUs, endTimeUs, style, styleIds);
TtmlStyle style, String[] styleIds, String regionId) {
return new TtmlNode(tag, null, startTimeUs, endTimeUs, style, styleIds, regionId);
}
private TtmlNode(String tag, String text, long startTimeUs, long endTimeUs,
TtmlStyle style, String[] styleIds) {
TtmlStyle style, String[] styleIds, String regionId) {
this.tag = tag;
this.text = text;
this.style = style;
@ -99,6 +108,9 @@ import java.util.TreeSet;
this.isTextNode = text != null;
this.startTimeUs = startTimeUs;
this.endTimeUs = endTimeUs;
this.regionId = Assertions.checkNotNull(regionId);
nodeStartsByRegion = new HashMap<>();
nodeEndsByRegion = new HashMap<>();
}
public boolean isActive(long timeUs) {
@ -130,10 +142,8 @@ import java.util.TreeSet;
TreeSet<Long> eventTimeSet = new TreeSet<>();
getEventTimes(eventTimeSet, false);
long[] eventTimes = new long[eventTimeSet.size()];
Iterator<Long> eventTimeIterator = eventTimeSet.iterator();
int i = 0;
while (eventTimeIterator.hasNext()) {
long eventTimeUs = eventTimeIterator.next();
for (long eventTimeUs : eventTimeSet) {
eventTimes[i++] = eventTimeUs;
}
return eventTimes;
@ -161,10 +171,83 @@ import java.util.TreeSet;
return styleIds;
}
public CharSequence getText(long timeUs, Map<String, TtmlStyle> globalStyles) {
SpannableStringBuilder builder = new SpannableStringBuilder();
traverseForText(timeUs, builder, false);
traverseForStyle(builder, globalStyles);
public List<Cue> getCues(long timeUs, Map<String, TtmlStyle> globalStyles,
Map<String, TtmlRegion> regionMap) {
TreeMap<String, SpannableStringBuilder> regionOutputs = new TreeMap<>();
traverseForText(timeUs, false, regionId, regionOutputs);
traverseForStyle(globalStyles, regionOutputs);
List<Cue> cues = new ArrayList<>();
for (Entry<String, SpannableStringBuilder> entry : regionOutputs.entrySet()) {
TtmlRegion region = regionMap.get(entry.getKey());
cues.add(new Cue(cleanUpText(entry.getValue()), null, region.line, Cue.TYPE_UNSET,
Cue.TYPE_UNSET, region.position, Cue.TYPE_UNSET, Cue.DIMEN_UNSET));
}
return cues;
}
private void traverseForText(long timeUs, boolean descendsPNode,
String inheritedRegion, Map<String, SpannableStringBuilder> regionOutputs) {
nodeStartsByRegion.clear();
nodeEndsByRegion.clear();
String resolvedRegionId = regionId;
if (ANONYMOUS_REGION_ID.equals(resolvedRegionId)) {
resolvedRegionId = inheritedRegion;
}
if (isTextNode && descendsPNode) {
getRegionOutput(resolvedRegionId, regionOutputs).append(text);
} else if (TAG_BR.equals(tag) && descendsPNode) {
getRegionOutput(resolvedRegionId, regionOutputs).append('\n');
} else if (TAG_METADATA.equals(tag)) {
// Do nothing.
} else if (isActive(timeUs)) {
boolean isPNode = TAG_P.equals(tag);
for (Entry<String, SpannableStringBuilder> entry : regionOutputs.entrySet()) {
nodeStartsByRegion.put(entry.getKey(), entry.getValue().length());
}
for (int i = 0; i < getChildCount(); i++) {
getChild(i).traverseForText(timeUs, descendsPNode || isPNode, resolvedRegionId,
regionOutputs);
}
if (isPNode) {
TtmlRenderUtil.endParagraph(getRegionOutput(resolvedRegionId, regionOutputs));
}
for (Entry<String, SpannableStringBuilder> entry : regionOutputs.entrySet()) {
nodeEndsByRegion.put(entry.getKey(), entry.getValue().length());
}
}
}
private static SpannableStringBuilder getRegionOutput(String resolvedRegionId,
Map<String, SpannableStringBuilder> regionOutputs) {
if (!regionOutputs.containsKey(resolvedRegionId)) {
regionOutputs.put(resolvedRegionId, new SpannableStringBuilder());
}
return regionOutputs.get(resolvedRegionId);
}
private void traverseForStyle(Map<String, TtmlStyle> globalStyles,
Map<String, SpannableStringBuilder> regionOutputs) {
for (Entry<String, Integer> entry : nodeEndsByRegion.entrySet()) {
String regionId = entry.getKey();
int start = nodeStartsByRegion.containsKey(regionId) ? nodeStartsByRegion.get(regionId) : 0;
applyStyleToOutput(globalStyles, regionOutputs.get(regionId), start, entry.getValue());
for (int i = 0; i < getChildCount(); ++i) {
getChild(i).traverseForStyle(globalStyles, regionOutputs);
}
}
}
private void applyStyleToOutput(Map<String, TtmlStyle> globalStyles,
SpannableStringBuilder regionOutput, int start, int end) {
if (start != end) {
TtmlStyle resolvedStyle = TtmlRenderUtil.resolveStyle(style, styleIds, globalStyles);
if (resolvedStyle != null) {
TtmlRenderUtil.applyStylesToSpan(regionOutput, start, end, resolvedStyle);
}
}
}
private SpannableStringBuilder cleanUpText(SpannableStringBuilder builder) {
// Having joined the text elements, we need to do some final cleanup on the result.
// 1. Collapse multiple consecutive spaces into a single space.
int builderLength = builder.length();
@ -208,44 +291,7 @@ import java.util.TreeSet;
builder.delete(builderLength - 1, builderLength);
/*builderLength--;*/
}
return builder;
}
private SpannableStringBuilder traverseForText(long timeUs, SpannableStringBuilder builder,
boolean descendsPNode) {
start = builder.length();
end = start;
if (isTextNode && descendsPNode) {
builder.append(text);
} else if (TAG_BR.equals(tag) && descendsPNode) {
builder.append('\n');
} else if (TAG_METADATA.equals(tag)) {
// Do nothing.
} else if (isActive(timeUs)) {
boolean isPNode = TAG_P.equals(tag);
for (int i = 0; i < getChildCount(); ++i) {
getChild(i).traverseForText(timeUs, builder, descendsPNode || isPNode);
}
if (isPNode) {
TtmlRenderUtil.endParagraph(builder);
}
end = builder.length();
}
return builder;
}
private void traverseForStyle(SpannableStringBuilder builder,
Map<String, TtmlStyle> globalStyles) {
if (start != end) {
TtmlStyle resolvedStyle = TtmlRenderUtil.resolveStyle(style, styleIds, globalStyles);
if (resolvedStyle != null) {
TtmlRenderUtil.applyStylesToSpan(builder, start, end, resolvedStyle);
}
for (int i = 0; i < getChildCount(); ++i) {
getChild(i).traverseForStyle(builder, globalStyles);
}
}
}
}

View File

@ -24,6 +24,7 @@ import com.google.android.exoplayer.util.Util;
import android.text.Layout;
import android.util.Log;
import android.util.Pair;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@ -66,6 +67,7 @@ public final class TtmlParser extends SimpleSubtitleParser {
private static final String ATTR_DURATION = "dur";
private static final String ATTR_END = "end";
private static final String ATTR_STYLE = "style";
private static final String ATTR_REGION = "region";
private static final Pattern CLOCK_TIME =
Pattern.compile("^([0-9][0-9]+):([0-9][0-9]):([0-9][0-9])"
@ -74,6 +76,8 @@ public final class TtmlParser extends SimpleSubtitleParser {
Pattern.compile("^([0-9]+(?:\\.[0-9]+)?)(h|m|s|ms|f|t)$");
private static final Pattern FONT_SIZE =
Pattern.compile("^(([0-9]*.)?[0-9]+)(px|em|%)$");
private static final Pattern ORIGIN_COORDINATES =
Pattern.compile("^(\\d+\\.?\\d*?)% (\\d+\\.?\\d*?)%$");
// TODO: read and apply the following attributes if specified.
private static final int DEFAULT_FRAMERATE = 30;
@ -85,6 +89,7 @@ public final class TtmlParser extends SimpleSubtitleParser {
public TtmlParser() {
try {
xmlParserFactory = XmlPullParserFactory.newInstance();
xmlParserFactory.setNamespaceAware(true);
} catch (XmlPullParserException e) {
throw new RuntimeException("Couldn't create XmlPullParserFactory instance", e);
}
@ -95,6 +100,8 @@ public final class TtmlParser extends SimpleSubtitleParser {
try {
XmlPullParser xmlParser = xmlParserFactory.newPullParser();
Map<String, TtmlStyle> globalStyles = new HashMap<>();
Map<String, TtmlRegion> regionMap = new HashMap<>();
regionMap.put(TtmlNode.ANONYMOUS_REGION_ID, new TtmlRegion());
ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes, 0, length);
xmlParser.setInput(inputStream, null);
TtmlSubtitle ttmlSubtitle = null;
@ -110,10 +117,10 @@ public final class TtmlParser extends SimpleSubtitleParser {
Log.i(TAG, "Ignoring unsupported tag: " + xmlParser.getName());
unsupportedNodeDepth++;
} else if (TtmlNode.TAG_HEAD.equals(name)) {
parseHeader(xmlParser, globalStyles);
parseHeader(xmlParser, globalStyles, regionMap);
} else {
try {
TtmlNode node = parseNode(xmlParser, parent);
TtmlNode node = parseNode(xmlParser, parent, regionMap);
nodeStack.addLast(node);
if (parent != null) {
parent.addChild(node);
@ -128,7 +135,7 @@ public final class TtmlParser extends SimpleSubtitleParser {
parent.addChild(TtmlNode.buildTextNode(xmlParser.getText()));
} else if (eventType == XmlPullParser.END_TAG) {
if (xmlParser.getName().equals(TtmlNode.TAG_TT)) {
ttmlSubtitle = new TtmlSubtitle(nodeStack.getLast(), globalStyles);
ttmlSubtitle = new TtmlSubtitle(nodeStack.getLast(), globalStyles, regionMap);
}
nodeStack.removeLast();
}
@ -151,28 +158,50 @@ public final class TtmlParser extends SimpleSubtitleParser {
}
private Map<String, TtmlStyle> parseHeader(XmlPullParser xmlParser,
Map<String, TtmlStyle> globalStyles)
Map<String, TtmlStyle> globalStyles, Map<String, TtmlRegion> globalRegions)
throws IOException, XmlPullParserException {
do {
xmlParser.next();
if (ParserUtil.isStartTag(xmlParser, TtmlNode.TAG_STYLE)) {
String parentStyleId = xmlParser.getAttributeValue(null, ATTR_STYLE);
String parentStyleId = ParserUtil.getAttributeValue(xmlParser, ATTR_STYLE);
TtmlStyle style = parseStyleAttributes(xmlParser, new TtmlStyle());
if (parentStyleId != null) {
String[] ids = parseStyleIds(parentStyleId);
for (String id : ids) {
for (String id : parseStyleIds(parentStyleId)) {
style.chain(globalStyles.get(id));
}
}
if (style.getId() != null) {
globalStyles.put(style.getId(), style);
}
} else if (ParserUtil.isStartTag(xmlParser, TtmlNode.TAG_REGION)) {
Pair<String, TtmlRegion> ttmlRegionInfo = parseRegionAttributes(xmlParser);
if (ttmlRegionInfo != null) {
globalRegions.put(ttmlRegionInfo.first, ttmlRegionInfo.second);
}
}
} while (!ParserUtil.isEndTag(xmlParser, TtmlNode.TAG_HEAD));
return globalStyles;
}
private Pair<String, TtmlRegion> parseRegionAttributes(XmlPullParser xmlParser) {
String regionId = ParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_ID);
String regionOrigin = ParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_ORIGIN);
if (regionOrigin == null || regionId == null) {
return null;
}
Matcher originMatcher = ORIGIN_COORDINATES.matcher(regionOrigin);
if (originMatcher.matches()) {
try {
float position = Float.parseFloat(originMatcher.group(1)) / 100.f;
float line = Float.parseFloat(originMatcher.group(2)) / 100.f;
return new Pair<>(regionId, new TtmlRegion(position, line));
} catch (NumberFormatException e) {
Log.w(TAG, "Ignoring malformed region declaration: '" + regionOrigin + "'", e);
}
}
return null;
}
private String[] parseStyleIds(String parentStyleIds) {
return parentStyleIds.split("\\s+");
}
@ -180,9 +209,8 @@ public final class TtmlParser extends SimpleSubtitleParser {
private TtmlStyle parseStyleAttributes(XmlPullParser parser, TtmlStyle style) {
int attributeCount = parser.getAttributeCount();
for (int i = 0; i < attributeCount; i++) {
String attributeName = parser.getAttributeName(i);
String attributeValue = parser.getAttributeValue(i);
switch (ParserUtil.removeNamespacePrefix(attributeName)) {
switch (parser.getAttributeName(i)) {
case TtmlNode.ATTR_ID:
if (TtmlNode.TAG_STYLE.equals(parser.getName())) {
style = createIfNull(style).setId(attributeValue);
@ -199,7 +227,7 @@ public final class TtmlParser extends SimpleSubtitleParser {
case TtmlNode.ATTR_TTS_COLOR:
style = createIfNull(style);
try {
style.setColor(ColorParser.parseTtmlColor(attributeValue));
style.setFontColor(ColorParser.parseTtmlColor(attributeValue));
} catch (IllegalArgumentException e) {
Log.w(TAG, "failed parsing color value: '" + attributeValue + "'");
}
@ -270,15 +298,17 @@ public final class TtmlParser extends SimpleSubtitleParser {
return style == null ? new TtmlStyle() : style;
}
private TtmlNode parseNode(XmlPullParser parser, TtmlNode parent) throws ParserException {
private TtmlNode parseNode(XmlPullParser parser, TtmlNode parent,
Map<String, TtmlRegion> regionMap) throws ParserException {
long duration = 0;
long startTime = TtmlNode.UNDEFINED_TIME;
long endTime = TtmlNode.UNDEFINED_TIME;
String regionId = TtmlNode.ANONYMOUS_REGION_ID;
String[] styleIds = null;
int attributeCount = parser.getAttributeCount();
TtmlStyle style = parseStyleAttributes(parser, null);
for (int i = 0; i < attributeCount; i++) {
String attr = ParserUtil.removeNamespacePrefix(parser.getAttributeName(i));
String attr = parser.getAttributeName(i);
String value = parser.getAttributeValue(i);
switch (attr) {
case ATTR_BEGIN:
@ -300,6 +330,13 @@ public final class TtmlParser extends SimpleSubtitleParser {
styleIds = ids;
}
break;
case ATTR_REGION:
if (regionMap.containsKey(value)) {
// If the region has not been correctly declared or does not define a position, we use
// the anonymous region.
regionId = value;
}
break;
default:
// Do nothing.
break;
@ -322,7 +359,7 @@ public final class TtmlParser extends SimpleSubtitleParser {
endTime = parent.endTimeUs;
}
}
return TtmlNode.buildNode(parser.getName(), startTime, endTime, style, styleIds);
return TtmlNode.buildNode(parser.getName(), startTime, endTime, style, styleIds, regionId);
}
private static boolean isSupportedTag(String tag) {

View File

@ -0,0 +1,37 @@
/*
* Copyright (C) 2016 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.exoplayer.text.ttml;
import com.google.android.exoplayer.text.Cue;
/**
* Represents a TTML Region.
*/
/* package */ final class TtmlRegion {
public final float position;
public final float line;
public TtmlRegion() {
this(Cue.DIMEN_UNSET, Cue.DIMEN_UNSET);
}
public TtmlRegion(float position, float line) {
this.position = position;
this.line = line;
}
}

View File

@ -38,29 +38,29 @@ import java.util.Map;
public static TtmlStyle resolveStyle(TtmlStyle style, String[] styleIds,
Map<String, TtmlStyle> globalStyles) {
if (style == null && styleIds == null) {
// no styles at all
// No styles at all.
return null;
} else if (style == null && styleIds.length == 1) {
// only one single referential style present
// Only one single referential style present.
return globalStyles.get(styleIds[0]);
} else if (style == null && styleIds.length > 1) {
// only multiple referential styles present
// Only multiple referential styles present.
TtmlStyle chainedStyle = new TtmlStyle();
for (String styleId : styleIds) {
chainedStyle.chain(globalStyles.get(styleId));
for (String id : styleIds) {
chainedStyle.chain(globalStyles.get(id));
}
return chainedStyle;
} else if (style != null && styleIds != null && styleIds.length == 1) {
// merge a single referential style into inline style
// Merge a single referential style into inline style.
return style.chain(globalStyles.get(styleIds[0]));
} else if (style != null && styleIds != null && styleIds.length > 1) {
// merge multiple referential styles into inline style
for (String styleId : styleIds) {
style.chain(globalStyles.get(styleId));
// Merge multiple referential styles into inline style.
for (String id : styleIds) {
style.chain(globalStyles.get(id));
}
return style;
}
// only inline styles available
// Only inline styles available.
return style;
}
@ -77,11 +77,11 @@ import java.util.Map;
if (style.isUnderline()) {
builder.setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
if (style.hasColorSpecified()) {
builder.setSpan(new ForegroundColorSpan(style.getColor()), start, end,
if (style.hasFontColor()) {
builder.setSpan(new ForegroundColorSpan(style.getFontColor()), start, end,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
if (style.hasBackgroundColorSpecified()) {
if (style.hasBackgroundColor()) {
builder.setSpan(new BackgroundColorSpan(style.getBackgroundColor()), start, end,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}

View File

@ -25,53 +25,55 @@ import android.text.Layout;
*/
/* package */ final class TtmlStyle {
public static final short UNSPECIFIED = -1;
public static final int UNSPECIFIED = -1;
public static final short STYLE_NORMAL = Typeface.NORMAL;
public static final short STYLE_BOLD = Typeface.BOLD;
public static final short STYLE_ITALIC = Typeface.ITALIC;
public static final short STYLE_BOLD_ITALIC = Typeface.BOLD_ITALIC;
public static final int STYLE_NORMAL = Typeface.NORMAL;
public static final int STYLE_BOLD = Typeface.BOLD;
public static final int STYLE_ITALIC = Typeface.ITALIC;
public static final int STYLE_BOLD_ITALIC = Typeface.BOLD_ITALIC;
public static final short FONT_SIZE_UNIT_PIXEL = 1;
public static final short FONT_SIZE_UNIT_EM = 2;
public static final short FONT_SIZE_UNIT_PERCENT = 3;
public static final int FONT_SIZE_UNIT_PIXEL = 1;
public static final int FONT_SIZE_UNIT_EM = 2;
public static final int FONT_SIZE_UNIT_PERCENT = 3;
private static final short OFF = 0;
private static final short ON = 1;
private static final int OFF = 0;
private static final int ON = 1;
private String fontFamily;
private int color;
private boolean colorSpecified;
private int fontColor;
private boolean hasFontColor;
private int backgroundColor;
private boolean backgroundColorSpecified;
private short linethrough = UNSPECIFIED;
private short underline = UNSPECIFIED;
private short bold = UNSPECIFIED;
private short italic = UNSPECIFIED;
private short fontSizeUnit = UNSPECIFIED;
private boolean hasBackgroundColor;
private int linethrough;
private int underline;
private int bold;
private int italic;
private int fontSizeUnit;
private float fontSize;
private String id;
private TtmlStyle inheritableStyle;
private Layout.Alignment textAlign;
public TtmlStyle() {
linethrough = UNSPECIFIED;
underline = UNSPECIFIED;
bold = UNSPECIFIED;
italic = UNSPECIFIED;
fontSizeUnit = UNSPECIFIED;
}
/**
* Returns the style or <code>UNSPECIFIED</code> when no style information is given.
* Returns the style or {@link #UNSPECIFIED} when no style information is given.
*
* @return UNSPECIFIED, STYLE_NORMAL, STYLE_BOLD, STYLE_BOLD or STYLE_BOLD_ITALIC
* @return {@link #UNSPECIFIED}, {@link #STYLE_NORMAL}, {@link #STYLE_BOLD}, {@link #STYLE_BOLD}
* or {@link #STYLE_BOLD_ITALIC}.
*/
public short getStyle() {
public int getStyle() {
if (bold == UNSPECIFIED && italic == UNSPECIFIED) {
return UNSPECIFIED;
}
short style = STYLE_NORMAL;
if (bold != UNSPECIFIED) {
style += bold;
}
if (italic != UNSPECIFIED){
style += italic;
}
return style;
return (bold != UNSPECIFIED ? bold : STYLE_NORMAL)
| (italic != UNSPECIFIED ? italic : STYLE_NORMAL);
}
public boolean isLinethrough() {
@ -104,33 +106,39 @@ import android.text.Layout;
return this;
}
public int getColor() {
return color;
public int getFontColor() {
if (!hasFontColor) {
throw new IllegalStateException("Font color has not been defined.");
}
return fontColor;
}
public TtmlStyle setColor(int color) {
public TtmlStyle setFontColor(int fontColor) {
Assertions.checkState(inheritableStyle == null);
this.color = color;
colorSpecified = true;
this.fontColor = fontColor;
hasFontColor = true;
return this;
}
public boolean hasColorSpecified() {
return colorSpecified;
public boolean hasFontColor() {
return hasFontColor;
}
public int getBackgroundColor() {
if (!hasBackgroundColor) {
throw new IllegalStateException("Background color has not been defined.");
}
return backgroundColor;
}
public TtmlStyle setBackgroundColor(int backgroundColor) {
this.backgroundColor = backgroundColor;
backgroundColorSpecified = true;
hasBackgroundColor = true;
return this;
}
public boolean hasBackgroundColorSpecified() {
return backgroundColorSpecified;
public boolean hasBackgroundColor() {
return hasBackgroundColor;
}
public TtmlStyle setBold(boolean isBold) {
@ -168,8 +176,8 @@ import android.text.Layout;
private TtmlStyle inherit(TtmlStyle ancestor, boolean chaining) {
if (ancestor != null) {
if (!colorSpecified && ancestor.colorSpecified) {
setColor(ancestor.color);
if (!hasFontColor && ancestor.hasFontColor) {
setFontColor(ancestor.fontColor);
}
if (bold == UNSPECIFIED) {
bold = ancestor.bold;
@ -194,7 +202,7 @@ import android.text.Layout;
fontSize = ancestor.fontSize;
}
// attributes not inherited as of http://www.w3.org/TR/ttml1/
if (chaining && !backgroundColorSpecified && ancestor.backgroundColorSpecified) {
if (chaining && !hasBackgroundColor && ancestor.hasBackgroundColor) {
setBackgroundColor(ancestor.backgroundColor);
}
}
@ -224,12 +232,12 @@ import android.text.Layout;
return this;
}
public TtmlStyle setFontSizeUnit(short unit) {
this.fontSizeUnit = unit;
public TtmlStyle setFontSizeUnit(int fontSizeUnit) {
this.fontSizeUnit = fontSizeUnit;
return this;
}
public short getFontSizeUnit() {
public int getFontSizeUnit() {
return fontSizeUnit;
}

View File

@ -26,14 +26,17 @@ import java.util.Map;
/**
* A representation of a TTML subtitle.
*/
public final class TtmlSubtitle implements Subtitle {
/* package */ final class TtmlSubtitle implements Subtitle {
private final TtmlNode root;
private final long[] eventTimesUs;
private final Map<String, TtmlStyle> globalStyles;
private final Map<String, TtmlRegion> regionMap;
public TtmlSubtitle(TtmlNode root, Map<String, TtmlStyle> globalStyles) {
public TtmlSubtitle(TtmlNode root, Map<String, TtmlStyle> globalStyles,
Map<String, TtmlRegion> regionMap) {
this.root = root;
this.regionMap = regionMap;
this.globalStyles = globalStyles != null
? Collections.unmodifiableMap(globalStyles) : Collections.<String, TtmlStyle>emptyMap();
this.eventTimesUs = root.getEventTimesUs();
@ -62,13 +65,7 @@ public final class TtmlSubtitle implements Subtitle {
@Override
public List<Cue> getCues(long timeUs) {
CharSequence cueText = root.getText(timeUs, globalStyles);
if (cueText == null) {
return Collections.emptyList();
} else {
Cue cue = new Cue(cueText);
return Collections.singletonList(cue);
}
return root.getCues(timeUs, globalStyles, regionMap);
}
/* @VisibleForTesting */

View File

@ -26,26 +26,30 @@ public final class ParserUtil {
private ParserUtil() {}
public static boolean isEndTag(XmlPullParser xpp, String name) throws XmlPullParserException {
return xpp.getEventType() == XmlPullParser.END_TAG && name.equals(xpp.getName());
return isEndTag(xpp) && xpp.getName().equals(name);
}
public static boolean isEndTag(XmlPullParser xpp) throws XmlPullParserException {
return xpp.getEventType() == XmlPullParser.END_TAG;
}
public static boolean isStartTag(XmlPullParser xpp, String name)
throws XmlPullParserException {
return xpp.getEventType() == XmlPullParser.START_TAG && name.equals(xpp.getName());
return isStartTag(xpp) && xpp.getName().equals(name);
}
public static boolean isStartTag(XmlPullParser xpp) throws XmlPullParserException {
return xpp.getEventType() == XmlPullParser.START_TAG;
}
/**
* Removes the namespace part ('^.*:') of the attributeName.
*
* @param attributeName the string to remove the namespace prefix from
* @return the name of the attribute without the prefix
*/
public static String removeNamespacePrefix(String attributeName) {
return attributeName.replaceFirst("^.*:", "");
public static String getAttributeValue(XmlPullParser xpp, String attributeName) {
int attributeCount = xpp.getAttributeCount();
for (int i = 0; i < attributeCount; i++) {
if (attributeName.equals(xpp.getAttributeName(i))) {
return xpp.getAttributeValue(i);
}
}
return null;
}
}