diff --git a/library/src/androidTest/java/com/google/android/exoplayer/text/ttml/TtmlColorParserTest.java b/library/src/androidTest/java/com/google/android/exoplayer/text/ttml/TtmlColorParserTest.java
new file mode 100644
index 0000000000..922b054011
--- /dev/null
+++ b/library/src/androidTest/java/com/google/android/exoplayer/text/ttml/TtmlColorParserTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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 TtmlColorParser
.
+ */
+public class TtmlColorParserTest extends InstrumentationTestCase {
+
+ public void testHexCodeParsing() {
+ assertEquals(Color.WHITE, TtmlColorParser.parseColor("#ffffff"));
+ assertEquals(Color.WHITE, TtmlColorParser.parseColor("#ffffffff"));
+ assertEquals(Color.parseColor("#00ffffff"), TtmlColorParser.parseColor("#00ffffff"));
+ assertEquals(Color.parseColor("#12341234"), TtmlColorParser.parseColor("#12341234"));
+ }
+
+ 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)"));
+ }
+}
diff --git a/library/src/androidTest/java/com/google/android/exoplayer/text/ttml/TtmlParserTest.java b/library/src/androidTest/java/com/google/android/exoplayer/text/ttml/TtmlParserTest.java
index 9d5d953977..4133c3ce3d 100644
--- a/library/src/androidTest/java/com/google/android/exoplayer/text/ttml/TtmlParserTest.java
+++ b/library/src/androidTest/java/com/google/android/exoplayer/text/ttml/TtmlParserTest.java
@@ -17,7 +17,6 @@ package com.google.android.exoplayer.text.ttml;
import com.google.android.exoplayer.text.Cue;
-import android.graphics.Color;
import android.test.InstrumentationTestCase;
import android.text.Layout;
import android.text.SpannableStringBuilder;
@@ -67,8 +66,8 @@ 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(Color.parseColor("yellow"), firstPStyle.getColor());
- assertEquals(Color.parseColor("blue"), firstPStyle.getBackgroundColor());
+ assertEquals(TtmlColorParser.parseColor("yellow"), firstPStyle.getColor());
+ assertEquals(TtmlColorParser.parseColor("blue"), firstPStyle.getBackgroundColor());
assertEquals("serif", firstPStyle.getFontFamily());
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, firstPStyle.getStyle());
assertTrue(firstPStyle.isUnderline());
@@ -78,24 +77,43 @@ public final class TtmlParserTest extends InstrumentationTestCase {
TtmlSubtitle subtitle = getSubtitle(INLINE_ATTRIBUTES_TTML_FILE);
assertEquals(4, subtitle.getEventTimeCount());
assertSpans(subtitle, 20, "text 2", "sansSerif", TtmlStyle.STYLE_ITALIC,
- Color.CYAN, Color.parseColor("lime"), false, true, null);
+ TtmlColorParser.CYAN, TtmlColorParser.parseColor("lime"), false, true, null);
+ }
+
+ /**
+ * regression test for devices on JellyBean where some named colors are not correctly defined
+ * on framework level. Tests that lime resolves to #FF00FF00
not
+ * #00FF00
.
+ *
+ * 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
+ *
+ * @throws IOException thrown if reading subtitle file fails.
+ */
+ public void testLime() throws IOException {
+ TtmlSubtitle subtitle = getSubtitle(INLINE_ATTRIBUTES_TTML_FILE);
+ assertEquals(4, subtitle.getEventTimeCount());
+ assertSpans(subtitle, 20, "text 2", "sansSerif", TtmlStyle.STYLE_ITALIC,
+ TtmlColorParser.CYAN, TtmlColorParser.LIME, false, true, null);
}
public void testInheritGlobalStyle() throws IOException {
TtmlSubtitle subtitle = getSubtitle(INHERIT_STYLE_TTML_FILE);
assertEquals(2, subtitle.getEventTimeCount());
assertSpans(subtitle, 10, "text 1", "serif", TtmlStyle.STYLE_BOLD_ITALIC,
- Color.BLUE, Color.YELLOW, true, false, null);
+ TtmlColorParser.BLUE, TtmlColorParser.YELLOW, true, false, null);
}
public void testInheritGlobalStyleOverriddenByInlineAttributes() throws IOException {
TtmlSubtitle subtitle = getSubtitle(INHERIT_STYLE_OVERRIDE_TTML_FILE);
assertEquals(4, subtitle.getEventTimeCount());
- assertSpans(subtitle, 10, "text 1", "serif", TtmlStyle.STYLE_BOLD_ITALIC, Color.BLUE,
- Color.YELLOW, true, false, null);
- assertSpans(subtitle, 20, "text 2", "sansSerif", TtmlStyle.STYLE_ITALIC, Color.RED,
- Color.YELLOW, true, false, null);
+ assertSpans(subtitle, 10, "text 1", "serif", TtmlStyle.STYLE_BOLD_ITALIC, TtmlColorParser.BLUE,
+ TtmlColorParser.YELLOW, true, false, null);
+ assertSpans(subtitle, 20, "text 2", "sansSerif", TtmlStyle.STYLE_ITALIC, TtmlColorParser.RED,
+ TtmlColorParser.YELLOW, true, false, null);
}
public void testInheritGlobalAndParent() throws IOException {
@@ -103,9 +121,10 @@ public final class TtmlParserTest extends InstrumentationTestCase {
assertEquals(4, subtitle.getEventTimeCount());
assertSpans(subtitle, 10, "text 1", "sansSerif", TtmlStyle.STYLE_NORMAL,
- Color.RED, Color.parseColor("lime"), false, true, Layout.Alignment.ALIGN_CENTER);
+ TtmlColorParser.RED, TtmlColorParser.parseColor("lime"), false, true,
+ Layout.Alignment.ALIGN_CENTER);
assertSpans(subtitle, 20, "text 2", "serif", TtmlStyle.STYLE_BOLD_ITALIC,
- Color.BLUE, Color.YELLOW, true, true, Layout.Alignment.ALIGN_CENTER);
+ TtmlColorParser.BLUE, TtmlColorParser.YELLOW, true, true, Layout.Alignment.ALIGN_CENTER);
}
public void testInheritMultipleStyles() throws IOException {
@@ -113,7 +132,7 @@ public final class TtmlParserTest extends InstrumentationTestCase {
assertEquals(12, subtitle.getEventTimeCount());
assertSpans(subtitle, 10, "text 1", "sansSerif", TtmlStyle.STYLE_BOLD_ITALIC,
- Color.BLUE, Color.YELLOW, false, true, null);
+ TtmlColorParser.BLUE, TtmlColorParser.YELLOW, false, true, null);
}
public void testInheritMultipleStylesWithoutLocalAttributes() throws IOException {
@@ -121,7 +140,7 @@ public final class TtmlParserTest extends InstrumentationTestCase {
assertEquals(12, subtitle.getEventTimeCount());
assertSpans(subtitle, 20, "text 2", "sansSerif", TtmlStyle.STYLE_BOLD_ITALIC,
- Color.BLUE, Color.BLACK, false, true, null);
+ TtmlColorParser.BLUE, TtmlColorParser.BLACK, false, true, null);
}
@@ -130,7 +149,7 @@ public final class TtmlParserTest extends InstrumentationTestCase {
assertEquals(12, subtitle.getEventTimeCount());
assertSpans(subtitle, 30, "text 2.5", "sansSerifInline", TtmlStyle.STYLE_ITALIC,
- Color.RED, Color.YELLOW, true, true, null);
+ TtmlColorParser.RED, TtmlColorParser.YELLOW, true, true, null);
}
public void testEmptyStyleAttribute() throws IOException {
@@ -175,16 +194,16 @@ public final class TtmlParserTest extends InstrumentationTestCase {
TtmlStyle style = globalStyles.get("s2");
assertEquals("serif", style.getFontFamily());
- assertEquals(Color.RED, style.getBackgroundColor());
- assertEquals(Color.BLACK, style.getColor());
+ assertEquals(TtmlColorParser.RED, style.getBackgroundColor());
+ assertEquals(TtmlColorParser.BLACK, style.getColor());
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, style.getStyle());
assertTrue(style.isLinethrough());
style = globalStyles.get("s3");
// only difference: color must be RED
- assertEquals(Color.RED, style.getColor());
+ assertEquals(TtmlColorParser.RED, style.getColor());
assertEquals("serif", style.getFontFamily());
- assertEquals(Color.RED, style.getBackgroundColor());
+ assertEquals(TtmlColorParser.RED, style.getBackgroundColor());
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, style.getStyle());
assertTrue(style.isLinethrough());
}
@@ -224,8 +243,8 @@ public final class TtmlParserTest extends InstrumentationTestCase {
TtmlStyle style = queryChildrenForTag(div, TtmlNode.TAG_P, 0).style;
assertNotNull(style);
- assertEquals(Color.BLACK, style.getBackgroundColor());
- assertEquals(Color.YELLOW, style.getColor());
+ assertEquals(TtmlColorParser.BLACK, style.getBackgroundColor());
+ assertEquals(TtmlColorParser.YELLOW, style.getColor());
assertEquals(TtmlStyle.STYLE_ITALIC, style.getStyle());
assertEquals("sansSerif", style.getFontFamily());
assertFalse(style.isUnderline());
@@ -243,8 +262,8 @@ public final class TtmlParserTest extends InstrumentationTestCase {
TtmlStyle style = queryChildrenForTag(div, TtmlNode.TAG_P, 0).style;
assertNotNull(style);
- assertEquals(Color.BLACK, style.getBackgroundColor());
- assertEquals(Color.YELLOW, style.getColor());
+ assertEquals(TtmlColorParser.BLACK, style.getBackgroundColor());
+ assertEquals(TtmlColorParser.YELLOW, style.getColor());
assertEquals(TtmlStyle.STYLE_ITALIC, style.getStyle());
assertEquals("sansSerif", style.getFontFamily());
assertFalse(style.isUnderline());
diff --git a/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlColorParser.java b/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlColorParser.java
new file mode 100644
index 0000000000..4a519dae7e
--- /dev/null
+++ b/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlColorParser.java
@@ -0,0 +1,138 @@
+/*
+ * 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 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) == '#') {
+ // Use a long to avoid rollovers on #ffXXXXXX
+ long color = Long.parseLong(colorExpression.substring(1), 16);
+ if (colorExpression.length() == 7) {
+ // Set the alpha value
+ color |= 0x00000000ff000000;
+ } else if (colorExpression.length() != 9) {
+ throw new IllegalArgumentException();
+ }
+ return (int) 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);
+ }
+
+}
diff --git a/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlParser.java b/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlParser.java
index a9e6c1b99a..87670e7f8b 100644
--- a/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlParser.java
+++ b/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlParser.java
@@ -23,7 +23,6 @@ import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.ParserUtil;
import com.google.android.exoplayer.util.Util;
-import android.graphics.Color;
import android.text.Layout;
import android.util.Log;
@@ -207,7 +206,7 @@ public final class TtmlParser implements SubtitleParser {
case TtmlNode.ATTR_TTS_BACKGROUND_COLOR:
style = createIfNull(style);
try {
- style.setBackgroundColor(Color.parseColor(attributeValue));
+ style.setBackgroundColor(TtmlColorParser.parseColor(attributeValue));
} catch (IllegalArgumentException e) {
Log.w(TAG, "failed parsing background value: '" + attributeValue + "'");
}
@@ -215,7 +214,7 @@ public final class TtmlParser implements SubtitleParser {
case TtmlNode.ATTR_TTS_COLOR:
style = createIfNull(style);
try {
- style.setColor(Color.parseColor(attributeValue));
+ style.setColor(TtmlColorParser.parseColor(attributeValue));
} catch (IllegalArgumentException e) {
Log.w(TAG, "failed parsing color value: '" + attributeValue + "'");
}