mirror of
https://github.com/samsonjs/media.git
synced 2026-04-25 14:47:40 +00:00
Add Css styles in the WebVTT parser
This is the first version and is still not linked to the WebVTT parser nor does it support all the intended features, but it was left this way to ease the review a little bit. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=117722492
This commit is contained in:
parent
e7a27245e7
commit
60ba7823e0
7 changed files with 1162 additions and 53 deletions
|
|
@ -17,6 +17,7 @@ package com.google.android.exoplayer.text.ttml;
|
|||
|
||||
import com.google.android.exoplayer.testutil.TestUtil;
|
||||
import com.google.android.exoplayer.text.Cue;
|
||||
import com.google.android.exoplayer.util.ColorParser;
|
||||
|
||||
import android.test.InstrumentationTestCase;
|
||||
import android.text.Layout;
|
||||
|
|
@ -41,32 +42,23 @@ import java.util.Map;
|
|||
*/
|
||||
public final class TtmlParserTest extends InstrumentationTestCase {
|
||||
|
||||
private static final String INLINE_ATTRIBUTES_TTML_FILE =
|
||||
"ttml/inline_style_attributes.xml";
|
||||
private static final String INHERIT_STYLE_TTML_FILE =
|
||||
"ttml/inherit_style.xml";
|
||||
private static final String INLINE_ATTRIBUTES_TTML_FILE = "ttml/inline_style_attributes.xml";
|
||||
private static final String INHERIT_STYLE_TTML_FILE = "ttml/inherit_style.xml";
|
||||
private static final String INHERIT_STYLE_OVERRIDE_TTML_FILE =
|
||||
"ttml/inherit_and_override_style.xml";
|
||||
private static final String INHERIT_GLOBAL_AND_PARENT_TTML_FILE =
|
||||
"ttml/inherit_global_and_parent.xml";
|
||||
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 CHAIN_MULTIPLE_STYLES_TTML_FILE = "ttml/chain_multiple_styles.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";
|
||||
private static final String FONT_SIZE_EMPTY_TTML_FILE =
|
||||
"ttml/font_size_empty.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";
|
||||
private static final String FONT_SIZE_EMPTY_TTML_FILE = "ttml/font_size_empty.xml";
|
||||
|
||||
public void testInlineAttributes() throws IOException {
|
||||
TtmlSubtitle subtitle = getSubtitle(INLINE_ATTRIBUTES_TTML_FILE);
|
||||
|
|
@ -77,8 +69,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(TtmlColorParser.parseColor("yellow"), firstPStyle.getColor());
|
||||
assertEquals(TtmlColorParser.parseColor("blue"), firstPStyle.getBackgroundColor());
|
||||
assertEquals(ColorParser.parseTtmlColor("yellow"), firstPStyle.getColor());
|
||||
assertEquals(ColorParser.parseTtmlColor("blue"), firstPStyle.getBackgroundColor());
|
||||
assertEquals("serif", firstPStyle.getFontFamily());
|
||||
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, firstPStyle.getStyle());
|
||||
assertTrue(firstPStyle.isUnderline());
|
||||
|
|
@ -88,7 +80,7 @@ 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,
|
||||
TtmlColorParser.CYAN, TtmlColorParser.parseColor("lime"), false, true, null);
|
||||
0xFF00FFFF, ColorParser.parseTtmlColor("lime"), false, true, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -106,61 +98,59 @@ public final class TtmlParserTest extends InstrumentationTestCase {
|
|||
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);
|
||||
assertSpans(subtitle, 20, "text 2", "sansSerif", TtmlStyle.STYLE_ITALIC, 0xFF00FFFF, 0xFF00FF00,
|
||||
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,
|
||||
TtmlColorParser.BLUE, TtmlColorParser.YELLOW, true, false, null);
|
||||
assertSpans(subtitle, 10, "text 1", "serif", TtmlStyle.STYLE_BOLD_ITALIC, 0xFF0000FF,
|
||||
0xFFFFFF00, 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, TtmlColorParser.BLUE,
|
||||
TtmlColorParser.YELLOW, true, false, null);
|
||||
assertSpans(subtitle, 20, "text 2", "sansSerif", TtmlStyle.STYLE_ITALIC, TtmlColorParser.RED,
|
||||
TtmlColorParser.YELLOW, true, false, null);
|
||||
assertSpans(subtitle, 10, "text 1", "serif", TtmlStyle.STYLE_BOLD_ITALIC, 0xFF0000FF,
|
||||
0xFFFFFF00, true, false, null);
|
||||
assertSpans(subtitle, 20, "text 2", "sansSerif", TtmlStyle.STYLE_ITALIC, 0xFFFF0000, 0xFFFFFF00,
|
||||
true, false, null);
|
||||
}
|
||||
|
||||
public void testInheritGlobalAndParent() throws IOException {
|
||||
TtmlSubtitle subtitle = getSubtitle(INHERIT_GLOBAL_AND_PARENT_TTML_FILE);
|
||||
assertEquals(4, subtitle.getEventTimeCount());
|
||||
|
||||
assertSpans(subtitle, 10, "text 1", "sansSerif", TtmlStyle.STYLE_NORMAL,
|
||||
TtmlColorParser.RED, TtmlColorParser.parseColor("lime"), false, true,
|
||||
Layout.Alignment.ALIGN_CENTER);
|
||||
assertSpans(subtitle, 20, "text 2", "serif", TtmlStyle.STYLE_BOLD_ITALIC,
|
||||
TtmlColorParser.BLUE, TtmlColorParser.YELLOW, true, true, Layout.Alignment.ALIGN_CENTER);
|
||||
assertSpans(subtitle, 10, "text 1", "sansSerif", TtmlStyle.STYLE_NORMAL, 0xFFFF0000,
|
||||
ColorParser.parseTtmlColor("lime"), false, true, Layout.Alignment.ALIGN_CENTER);
|
||||
assertSpans(subtitle, 20, "text 2", "serif", TtmlStyle.STYLE_BOLD_ITALIC, 0xFF0000FF,
|
||||
0xFFFFFF00, true, true, Layout.Alignment.ALIGN_CENTER);
|
||||
}
|
||||
|
||||
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,
|
||||
TtmlColorParser.BLUE, TtmlColorParser.YELLOW, false, true, null);
|
||||
assertSpans(subtitle, 10, "text 1", "sansSerif", TtmlStyle.STYLE_BOLD_ITALIC, 0xFF0000FF,
|
||||
0xFFFFFF00, false, true, null);
|
||||
}
|
||||
|
||||
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,
|
||||
TtmlColorParser.BLUE, TtmlColorParser.BLACK, false, true, null);
|
||||
|
||||
assertSpans(subtitle, 20, "text 2", "sansSerif", TtmlStyle.STYLE_BOLD_ITALIC, 0xFF0000FF,
|
||||
0xFF000000, false, true, null);
|
||||
}
|
||||
|
||||
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,
|
||||
TtmlColorParser.RED, TtmlColorParser.YELLOW, true, true, null);
|
||||
assertSpans(subtitle, 30, "text 2.5", "sansSerifInline", TtmlStyle.STYLE_ITALIC, 0xFFFF0000,
|
||||
0xFFFFFF00, true, true, null);
|
||||
}
|
||||
|
||||
public void testEmptyStyleAttribute() throws IOException {
|
||||
|
|
@ -205,16 +195,16 @@ public final class TtmlParserTest extends InstrumentationTestCase {
|
|||
|
||||
TtmlStyle style = globalStyles.get("s2");
|
||||
assertEquals("serif", style.getFontFamily());
|
||||
assertEquals(TtmlColorParser.RED, style.getBackgroundColor());
|
||||
assertEquals(TtmlColorParser.BLACK, style.getColor());
|
||||
assertEquals(0xFFFF0000, style.getBackgroundColor());
|
||||
assertEquals(0xFF000000, style.getColor());
|
||||
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, style.getStyle());
|
||||
assertTrue(style.isLinethrough());
|
||||
|
||||
style = globalStyles.get("s3");
|
||||
// only difference: color must be RED
|
||||
assertEquals(TtmlColorParser.RED, style.getColor());
|
||||
assertEquals(0xFFFF0000, style.getColor());
|
||||
assertEquals("serif", style.getFontFamily());
|
||||
assertEquals(TtmlColorParser.RED, style.getBackgroundColor());
|
||||
assertEquals(0xFFFF0000, style.getBackgroundColor());
|
||||
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, style.getStyle());
|
||||
assertTrue(style.isLinethrough());
|
||||
}
|
||||
|
|
@ -254,8 +244,8 @@ public final class TtmlParserTest extends InstrumentationTestCase {
|
|||
TtmlStyle style = queryChildrenForTag(div, TtmlNode.TAG_P, 0).style;
|
||||
|
||||
assertNotNull(style);
|
||||
assertEquals(TtmlColorParser.BLACK, style.getBackgroundColor());
|
||||
assertEquals(TtmlColorParser.YELLOW, style.getColor());
|
||||
assertEquals(0xFF000000, style.getBackgroundColor());
|
||||
assertEquals(0xFFFFFF00, style.getColor());
|
||||
assertEquals(TtmlStyle.STYLE_ITALIC, style.getStyle());
|
||||
assertEquals("sansSerif", style.getFontFamily());
|
||||
assertFalse(style.isUnderline());
|
||||
|
|
@ -273,8 +263,8 @@ public final class TtmlParserTest extends InstrumentationTestCase {
|
|||
TtmlStyle style = queryChildrenForTag(div, TtmlNode.TAG_P, 0).style;
|
||||
|
||||
assertNotNull(style);
|
||||
assertEquals(TtmlColorParser.BLACK, style.getBackgroundColor());
|
||||
assertEquals(TtmlColorParser.YELLOW, style.getColor());
|
||||
assertEquals(0xFF000000, style.getBackgroundColor());
|
||||
assertEquals(0xFFFFFF00, style.getColor());
|
||||
assertEquals(TtmlStyle.STYLE_ITALIC, style.getStyle());
|
||||
assertEquals("sansSerif", style.getFontFamily());
|
||||
assertFalse(style.isUnderline());
|
||||
|
|
@ -414,8 +404,7 @@ public final class TtmlParserTest extends InstrumentationTestCase {
|
|||
}
|
||||
|
||||
private void assertUnderline(Spannable spannable, boolean isUnderline) {
|
||||
UnderlineSpan[] underlineSpans = spannable.getSpans(0, spannable.length(),
|
||||
UnderlineSpan.class);
|
||||
UnderlineSpan[] underlineSpans = spannable.getSpans(0, spannable.length(), UnderlineSpan.class);
|
||||
assertEquals(isUnderline ? "must be underlined" : "must not be underlined",
|
||||
isUnderline ? 1 : 0, underlineSpans.length);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,229 @@
|
|||
/*
|
||||
* 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.webvtt;
|
||||
|
||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||
|
||||
import android.test.InstrumentationTestCase;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Unit test for {@link CssParser}.
|
||||
*/
|
||||
public final class CssParserTest extends InstrumentationTestCase {
|
||||
|
||||
private CssParser parser;
|
||||
|
||||
@Override
|
||||
public void setUp() {
|
||||
parser = new CssParser();
|
||||
}
|
||||
|
||||
public void testSkipWhitespacesAndComments() {
|
||||
// Skip only whitespaces
|
||||
String skipOnlyWhitespaces = " \t\r\n\f End of skip\n /* */";
|
||||
assertSkipsToEndOfSkip("End of skip", skipOnlyWhitespaces);
|
||||
|
||||
// Skip only comments.
|
||||
String skipOnlyComments = "/*A comment***//*/It even has spaces in it*/End of skip";
|
||||
assertSkipsToEndOfSkip("End of skip", skipOnlyComments);
|
||||
|
||||
// Skip interleaved.
|
||||
String skipInterleaved = " /* We have comments and */\t\n/* whitespaces*/ End of skip";
|
||||
assertSkipsToEndOfSkip("End of skip", skipInterleaved);
|
||||
|
||||
// Skip nothing.
|
||||
String skipNothing = "End of skip\n \t \r";
|
||||
assertSkipsToEndOfSkip("End of skip", skipNothing);
|
||||
|
||||
// Skip everything.
|
||||
String skipEverything = "\t/* Comment */\n\r/* And another */";
|
||||
assertSkipsToEndOfSkip(null, skipEverything);
|
||||
}
|
||||
|
||||
public void testGetInputLimit() {
|
||||
// \r After 3 lines.
|
||||
String threeLinesThen3Cr = "One Line\nThen other\rAnd finally\r\r\r";
|
||||
assertInputLimit("", threeLinesThen3Cr);
|
||||
|
||||
// \r\r After 3 lines
|
||||
String threeLinesThen2Cr = "One Line\nThen other\r\nAnd finally\r\r";
|
||||
assertInputLimit(null, threeLinesThen2Cr);
|
||||
|
||||
// \n\n After 3 lines.
|
||||
String threeLinesThen2Lf = "One Line\nThen other\r\nAnd finally\n\nFinal\n\n\nLine";
|
||||
assertInputLimit("Final", threeLinesThen2Lf);
|
||||
|
||||
// \r\n\n After 3 lines.
|
||||
String threeLinesThenCr2Lf = " \n \r\n \r\n\nFinal\n\n\nLine";
|
||||
assertInputLimit("Final", threeLinesThenCr2Lf);
|
||||
|
||||
// Limit immediately.
|
||||
String immediateEmptyLine = "\nLine\nEnd";
|
||||
assertInputLimit("Line", immediateEmptyLine);
|
||||
|
||||
// Empty string.
|
||||
assertInputLimit(null, "");
|
||||
}
|
||||
|
||||
public void testParseMethodSimpleInput() {
|
||||
String styleBlock = " ::cue { color : black; background-color: PapayaWhip }";
|
||||
// Expected style map construction.
|
||||
Map<String, WebvttCssStyle> expectedResult = new HashMap<>();
|
||||
expectedResult.put("", new WebvttCssStyle());
|
||||
WebvttCssStyle style = expectedResult.get("");
|
||||
style.setFontColor(0xFF000000);
|
||||
style.setBackgroundColor(0xFFFFEFD5);
|
||||
|
||||
assertCssProducesExpectedMap(expectedResult, new String[] { styleBlock });
|
||||
}
|
||||
|
||||
public void testParseSimpleInputSeparately() {
|
||||
String styleBlock1 = " ::cue { color : black }\n\n::cue { color : invalid }";
|
||||
String styleBlock2 = " \n::cue {\n background-color\n:#00fFFe}";
|
||||
|
||||
// Expected style map construction.
|
||||
Map<String, WebvttCssStyle> expectedResult = new HashMap<>();
|
||||
expectedResult.put("", new WebvttCssStyle());
|
||||
WebvttCssStyle style = expectedResult.get("");
|
||||
style.setFontColor(0xFF000000);
|
||||
style.setBackgroundColor(0xFF00FFFE);
|
||||
|
||||
assertCssProducesExpectedMap(expectedResult, new String[] { styleBlock1, styleBlock2 });
|
||||
}
|
||||
|
||||
public void testDifferentSelectors() {
|
||||
String styleBlock1 = " ::cue(\n#id ){text-decoration:underline;}";
|
||||
String styleBlock2 = "::cue(elem ){font-family:Courier}";
|
||||
String styleBlock3 = "::cue(.class ){font-weight: bold;}";
|
||||
|
||||
// Expected style map construction.
|
||||
Map<String, WebvttCssStyle> expectedResult = new HashMap<>();
|
||||
expectedResult.put("#id", new WebvttCssStyle().setUnderline(true));
|
||||
expectedResult.put("elem", new WebvttCssStyle().setFontFamily("courier"));
|
||||
expectedResult.put(".class", new WebvttCssStyle().setBold(true));
|
||||
|
||||
assertCssProducesExpectedMap(expectedResult, new String[] { styleBlock1, styleBlock2,
|
||||
styleBlock3});
|
||||
}
|
||||
|
||||
public void testMultiplePropertiesInBlock() {
|
||||
String styleBlock = "::cue(#id){text-decoration:underline; background-color:green;"
|
||||
+ "color:red; font-family:Courier; font-weight:bold}";
|
||||
|
||||
// Expected style map construction.
|
||||
Map<String, WebvttCssStyle> expectedResult = new HashMap<>();
|
||||
WebvttCssStyle expectedStyle = new WebvttCssStyle();
|
||||
expectedResult.put("#id", expectedStyle);
|
||||
expectedStyle.setUnderline(true);
|
||||
expectedStyle.setBackgroundColor(0xFF008000);
|
||||
expectedStyle.setFontColor(0xFFFF0000);
|
||||
expectedStyle.setFontFamily("courier");
|
||||
expectedStyle.setBold(true);
|
||||
|
||||
assertCssProducesExpectedMap(expectedResult, new String[] { styleBlock });
|
||||
}
|
||||
|
||||
public void testRgbaColorExpression() {
|
||||
String styleBlock = "::cue(#rgb){background-color: rgba(\n10/* Ugly color */,11\t, 12\n,.1);"
|
||||
+ "color:rgb(1,1,\n1)}";
|
||||
|
||||
// Expected style map construction.
|
||||
Map<String, WebvttCssStyle> expectedResult = new HashMap<>();
|
||||
WebvttCssStyle expectedStyle = new WebvttCssStyle();
|
||||
expectedResult.put("#rgb", expectedStyle);
|
||||
expectedStyle.setBackgroundColor(0x190A0B0C);
|
||||
expectedStyle.setFontColor(0xFF010101);
|
||||
|
||||
assertCssProducesExpectedMap(expectedResult, new String[] { styleBlock });
|
||||
}
|
||||
|
||||
public void testGetNextToken() {
|
||||
String stringInput = " lorem:ipsum\n{dolor}#sit,amet;lorem:ipsum\r\t\f\ndolor(())\n";
|
||||
ParsableByteArray input = new ParsableByteArray(stringInput.getBytes());
|
||||
StringBuilder builder = new StringBuilder();
|
||||
assertEquals(CssParser.parseNextToken(input, builder), "lorem");
|
||||
assertEquals(CssParser.parseNextToken(input, builder), ":");
|
||||
assertEquals(CssParser.parseNextToken(input, builder), "ipsum");
|
||||
assertEquals(CssParser.parseNextToken(input, builder), "{");
|
||||
assertEquals(CssParser.parseNextToken(input, builder), "dolor");
|
||||
assertEquals(CssParser.parseNextToken(input, builder), "}");
|
||||
assertEquals(CssParser.parseNextToken(input, builder), "#sit");
|
||||
assertEquals(CssParser.parseNextToken(input, builder), ",");
|
||||
assertEquals(CssParser.parseNextToken(input, builder), "amet");
|
||||
assertEquals(CssParser.parseNextToken(input, builder), ";");
|
||||
assertEquals(CssParser.parseNextToken(input, builder), "lorem");
|
||||
assertEquals(CssParser.parseNextToken(input, builder), ":");
|
||||
assertEquals(CssParser.parseNextToken(input, builder), "ipsum");
|
||||
assertEquals(CssParser.parseNextToken(input, builder), "dolor");
|
||||
assertEquals(CssParser.parseNextToken(input, builder), "(");
|
||||
assertEquals(CssParser.parseNextToken(input, builder), "(");
|
||||
assertEquals(CssParser.parseNextToken(input, builder), ")");
|
||||
assertEquals(CssParser.parseNextToken(input, builder), ")");
|
||||
assertEquals(CssParser.parseNextToken(input, builder), null);
|
||||
}
|
||||
|
||||
// Utility methods.
|
||||
|
||||
private void assertSkipsToEndOfSkip(String expectedLine, String s) {
|
||||
ParsableByteArray input = new ParsableByteArray(s.getBytes());
|
||||
CssParser.skipWhitespaceAndComments(input);
|
||||
assertEquals(expectedLine, input.readLine());
|
||||
}
|
||||
|
||||
private void assertInputLimit(String expectedLine, String s) {
|
||||
ParsableByteArray input = new ParsableByteArray(s.getBytes());
|
||||
CssParser.skipStyleBlock(input);
|
||||
assertEquals(expectedLine, input.readLine());
|
||||
}
|
||||
|
||||
private void assertCssProducesExpectedMap(Map<String, WebvttCssStyle> expectedResult,
|
||||
String[] styleBlocks){
|
||||
Map<String, WebvttCssStyle> actualStyleMap = new HashMap<>();
|
||||
for (String s : styleBlocks) {
|
||||
ParsableByteArray input = new ParsableByteArray(s.getBytes());
|
||||
parser.parseBlock(input, actualStyleMap);
|
||||
}
|
||||
assertStyleMapsAreEqual(expectedResult, actualStyleMap);
|
||||
}
|
||||
|
||||
private void assertStyleMapsAreEqual(Map<String, WebvttCssStyle> expected,
|
||||
Map<String, WebvttCssStyle> actual) {
|
||||
assertEquals(expected.size(), actual.size());
|
||||
for (String k : expected.keySet()) {
|
||||
WebvttCssStyle expectedElem = expected.get(k);
|
||||
WebvttCssStyle actualElem = actual.get(k);
|
||||
assertEquals(expectedElem.hasBackgroundColor(), actualElem.hasBackgroundColor());
|
||||
if (expectedElem.hasBackgroundColor()) {
|
||||
assertEquals(expectedElem.getBackgroundColor(), actualElem.getBackgroundColor());
|
||||
}
|
||||
assertEquals(expectedElem.hasFontColor(), actualElem.hasFontColor());
|
||||
if (expectedElem.hasFontColor()) {
|
||||
assertEquals(expectedElem.getFontColor(), actualElem.getFontColor());
|
||||
}
|
||||
assertEquals(expectedElem.getFontFamily(), actualElem.getFontFamily());
|
||||
assertEquals(expectedElem.getFontSize(), actualElem.getFontSize());
|
||||
assertEquals(expectedElem.getFontSizeUnit(), actualElem.getFontSizeUnit());
|
||||
assertEquals(expectedElem.getStyle(), actualElem.getStyle());
|
||||
assertEquals(expectedElem.isLinethrough(), actualElem.isLinethrough());
|
||||
assertEquals(expectedElem.isUnderline(), actualElem.isUnderline());
|
||||
assertEquals(expectedElem.getTextAlign(), actualElem.getTextAlign());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* 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.util;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.test.InstrumentationTestCase;
|
||||
|
||||
/**
|
||||
* Unit test for <code>ColorParser</code>.
|
||||
*/
|
||||
public class ColorParserTest extends InstrumentationTestCase {
|
||||
|
||||
// Negative tests.
|
||||
|
||||
public void testParseUnknownColor() {
|
||||
try {
|
||||
ColorParser.parseTtmlColor("colorOfAnElectron");
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
public void testParseNull() {
|
||||
try {
|
||||
ColorParser.parseTtmlColor(null);
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
public void testParseEmpty() {
|
||||
try {
|
||||
ColorParser.parseTtmlColor("");
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
public void testRgbColorParsingRgbValuesNegative() {
|
||||
try {
|
||||
ColorParser.parseTtmlColor("rgb(-4, 55, 209)");
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
// Positive tests.
|
||||
|
||||
public void testHexCodeParsing() {
|
||||
assertEquals(Color.WHITE, ColorParser.parseTtmlColor("#FFFFFF"));
|
||||
assertEquals(Color.WHITE, ColorParser.parseTtmlColor("#FFFFFFFF"));
|
||||
assertEquals(Color.parseColor("#FF123456"), ColorParser.parseTtmlColor("#123456"));
|
||||
// Hex colors in ColorParser are RGBA, where-as {@link Color#parseColor} takes ARGB.
|
||||
assertEquals(Color.parseColor("#00FFFFFF"), ColorParser.parseTtmlColor("#FFFFFF00"));
|
||||
assertEquals(Color.parseColor("#78123456"), ColorParser.parseTtmlColor("#12345678"));
|
||||
}
|
||||
|
||||
public void testRgbColorParsing() {
|
||||
assertEquals(Color.WHITE, ColorParser.parseTtmlColor("rgb(255,255,255)"));
|
||||
// Spaces are ignored.
|
||||
assertEquals(Color.WHITE, ColorParser.parseTtmlColor(" rgb ( 255, 255, 255)"));
|
||||
}
|
||||
|
||||
public void testRgbColorParsingRgbValuesOutOfBounds() {
|
||||
int outOfBounds = ColorParser.parseTtmlColor("rgb(999, 999, 999)");
|
||||
int color = Color.rgb(999, 999, 999);
|
||||
// Behave like the framework does.
|
||||
assertEquals(color, outOfBounds);
|
||||
}
|
||||
|
||||
public void testRgbaColorParsing() {
|
||||
assertEquals(Color.WHITE, ColorParser.parseTtmlColor("rgba(255,255,255,255)"));
|
||||
assertEquals(Color.argb(255, 255, 255, 255),
|
||||
ColorParser.parseTtmlColor("rgba(255,255,255,255)"));
|
||||
assertEquals(Color.BLACK, ColorParser.parseTtmlColor("rgba(0, 0, 0, 255)"));
|
||||
assertEquals(Color.argb(0, 0, 0, 255), ColorParser.parseTtmlColor("rgba(0, 0, 255, 0)"));
|
||||
assertEquals(Color.RED, ColorParser.parseTtmlColor("rgba(255, 0, 0, 255)"));
|
||||
assertEquals(Color.argb(0, 255, 0, 255), ColorParser.parseTtmlColor("rgba(255, 0, 255, 0)"));
|
||||
assertEquals(Color.argb(205, 255, 0, 0), ColorParser.parseTtmlColor("rgba(255, 0, 0, 205)"));
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ package com.google.android.exoplayer.text.ttml;
|
|||
import com.google.android.exoplayer.C;
|
||||
import com.google.android.exoplayer.ParserException;
|
||||
import com.google.android.exoplayer.text.SubtitleParser;
|
||||
import com.google.android.exoplayer.util.ColorParser;
|
||||
import com.google.android.exoplayer.util.ParserUtil;
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
|
||||
|
|
@ -190,7 +191,7 @@ public final class TtmlParser extends SubtitleParser {
|
|||
case TtmlNode.ATTR_TTS_BACKGROUND_COLOR:
|
||||
style = createIfNull(style);
|
||||
try {
|
||||
style.setBackgroundColor(TtmlColorParser.parseColor(attributeValue));
|
||||
style.setBackgroundColor(ColorParser.parseTtmlColor(attributeValue));
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.w(TAG, "failed parsing background value: '" + attributeValue + "'");
|
||||
}
|
||||
|
|
@ -198,7 +199,7 @@ public final class TtmlParser extends SubtitleParser {
|
|||
case TtmlNode.ATTR_TTS_COLOR:
|
||||
style = createIfNull(style);
|
||||
try {
|
||||
style.setColor(TtmlColorParser.parseColor(attributeValue));
|
||||
style.setColor(ColorParser.parseTtmlColor(attributeValue));
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.w(TAG, "failed parsing color value: '" + attributeValue + "'");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,310 @@
|
|||
/*
|
||||
* 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.webvtt;
|
||||
|
||||
import com.google.android.exoplayer.util.ColorParser;
|
||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Provides a CSS parser for STYLE blocks in Webvtt files. Supports only a subset of the CSS
|
||||
* features.
|
||||
*/
|
||||
/* package */ final class CssParser {
|
||||
|
||||
private static final String PROPERTY_BGCOLOR = "background-color";
|
||||
private static final String PROPERTY_FONT_FAMILY = "font-family";
|
||||
private static final String PROPERTY_FONT_WEIGHT = "font-weight";
|
||||
private static final String PROPERTY_TEXT_DECORATION = "text-decoration";
|
||||
|
||||
private static final String VALUE_BOLD = "bold";
|
||||
private static final String VALUE_UNDERLINE = "underline";
|
||||
|
||||
// Temporary utility data structures.
|
||||
private final ParsableByteArray styleInput;
|
||||
private final StringBuilder stringBuilder;
|
||||
|
||||
public CssParser() {
|
||||
styleInput = new ParsableByteArray();
|
||||
stringBuilder = new StringBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a CSS style block and consumes up to the first empty line found. Attempts to parse the
|
||||
* contents of the style block and returns a {@link WebvttCssStyle} instance if successful, or
|
||||
* {@code null} otherwise.
|
||||
*
|
||||
* @param input The input from which the style block should be read.
|
||||
* @param styleMap The map that contains styles accessible by selector.
|
||||
*/
|
||||
public void parseBlock(ParsableByteArray input, Map<String, WebvttCssStyle> styleMap) {
|
||||
int initialInputPosition = input.getPosition();
|
||||
skipStyleBlock(input);
|
||||
styleInput.reset(input.data, input.getPosition());
|
||||
styleInput.setPosition(initialInputPosition);
|
||||
String selector = parseSelector(styleInput, stringBuilder);
|
||||
if (selector == null) {
|
||||
return;
|
||||
}
|
||||
String token = parseNextToken(styleInput, stringBuilder);
|
||||
if (!"{".equals(token)) {
|
||||
return;
|
||||
}
|
||||
if (!styleMap.containsKey(selector)) {
|
||||
styleMap.put(selector, new WebvttCssStyle());
|
||||
}
|
||||
WebvttCssStyle style = styleMap.get(selector);
|
||||
boolean blockEndFound = false;
|
||||
while (!blockEndFound) {
|
||||
int position = styleInput.getPosition();
|
||||
token = parseNextToken(styleInput, stringBuilder);
|
||||
if (token == null || "}".equals(token)) {
|
||||
blockEndFound = true;
|
||||
} else {
|
||||
styleInput.setPosition(position);
|
||||
parseStyleDeclaration(styleInput, style, stringBuilder);
|
||||
}
|
||||
}
|
||||
// Only one style block may appear after a STYLE line.
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string containing the selector. Empty string is the universal selector, and null
|
||||
* means syntax error.
|
||||
*
|
||||
* <p> Expected inputs are:
|
||||
* ::cue
|
||||
* ::cue(#id)
|
||||
* ::cue(elem)
|
||||
* ::cue(.class)
|
||||
* ::cue(elem.class)
|
||||
* ::cue(v[voice="Someone"])
|
||||
*
|
||||
* @param input From which the selector is obtained.
|
||||
* @return A string containing the target, empty string if targets all cues and null if an error
|
||||
* was encountered.
|
||||
*/
|
||||
private static String parseSelector(ParsableByteArray input, StringBuilder stringBuilder) {
|
||||
skipWhitespaceAndComments(input);
|
||||
if (input.bytesLeft() < 5) {
|
||||
return null;
|
||||
}
|
||||
String cueSelector = input.readString(5);
|
||||
if (!"::cue".equals(cueSelector)) {
|
||||
return null;
|
||||
}
|
||||
int position = input.getPosition();
|
||||
String token = parseNextToken(input, stringBuilder);
|
||||
if (token == null) {
|
||||
return null;
|
||||
}
|
||||
if ("{".equals(token)) {
|
||||
input.setPosition(position);
|
||||
return "";
|
||||
}
|
||||
String target = null;
|
||||
if ("(".equals(token)) {
|
||||
target = readCueTarget(input);
|
||||
}
|
||||
token = parseNextToken(input, stringBuilder);
|
||||
if (!")".equals(token) || token == null) {
|
||||
return null;
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the contents of ::cue() and returns it as a string.
|
||||
*/
|
||||
private static String readCueTarget(ParsableByteArray input) {
|
||||
int position = input.getPosition();
|
||||
int limit = input.limit();
|
||||
boolean cueTargetEndFound = false;
|
||||
while (position < limit && !cueTargetEndFound) {
|
||||
char c = (char) input.data[position++];
|
||||
cueTargetEndFound = c == ')';
|
||||
}
|
||||
return input.readString(--position - input.getPosition()).trim();
|
||||
// --offset to return ')' to the input.
|
||||
}
|
||||
|
||||
private static void parseStyleDeclaration(ParsableByteArray input, WebvttCssStyle style,
|
||||
StringBuilder stringBuilder) {
|
||||
skipWhitespaceAndComments(input);
|
||||
String property = parseIdentifier(input, stringBuilder);
|
||||
if ("".equals(property)) {
|
||||
return;
|
||||
}
|
||||
if (!":".equals(parseNextToken(input, stringBuilder))) {
|
||||
return;
|
||||
}
|
||||
skipWhitespaceAndComments(input);
|
||||
String value = parsePropertyValue(input, stringBuilder);
|
||||
if (value == null || "".equals(value)) {
|
||||
return;
|
||||
}
|
||||
int position = input.getPosition();
|
||||
String token = parseNextToken(input, stringBuilder);
|
||||
if (";".equals(token)) {
|
||||
// The style declaration is well formed.
|
||||
} else if ("}".equals(token)) {
|
||||
// The style declaration is well formed and we can go on, but the closing bracket had to be
|
||||
// fed back.
|
||||
input.setPosition(position);
|
||||
} else {
|
||||
// The style declaration is not well formed.
|
||||
return;
|
||||
}
|
||||
// At this point we have a presumably valid declaration, we need to parse it and fill the style.
|
||||
if ("color".equals(property)) {
|
||||
style.setFontColor(ColorParser.parseCssColor(value));
|
||||
} else if (PROPERTY_BGCOLOR.equals(property)) {
|
||||
style.setBackgroundColor(ColorParser.parseCssColor(value));
|
||||
} else if (PROPERTY_TEXT_DECORATION.equals(property)) {
|
||||
if (VALUE_UNDERLINE.equals(value)) {
|
||||
style.setUnderline(true);
|
||||
}
|
||||
} else if (PROPERTY_FONT_FAMILY.equals(property)) {
|
||||
style.setFontFamily(value);
|
||||
} else if (PROPERTY_FONT_WEIGHT.equals(property)) {
|
||||
if (VALUE_BOLD.equals(value)) {
|
||||
style.setBold(true);
|
||||
}
|
||||
}
|
||||
// TODO: Fill remaining supported styles.
|
||||
}
|
||||
|
||||
// Visible for testing.
|
||||
/* package */ static void skipWhitespaceAndComments(ParsableByteArray input) {
|
||||
boolean skipping = true;
|
||||
while (input.bytesLeft() > 0 && skipping) {
|
||||
skipping = maybeSkipWhitespace(input) || maybeSkipComment(input);
|
||||
}
|
||||
}
|
||||
|
||||
// Visible for testing.
|
||||
/* package */ static String parseNextToken(ParsableByteArray input, StringBuilder stringBuilder) {
|
||||
skipWhitespaceAndComments(input);
|
||||
if (input.bytesLeft() == 0) {
|
||||
return null;
|
||||
}
|
||||
String identifier = parseIdentifier(input, stringBuilder);
|
||||
if (!"".equals(identifier)) {
|
||||
return identifier;
|
||||
}
|
||||
// We found a delimiter.
|
||||
return "" + (char) input.readUnsignedByte();
|
||||
}
|
||||
|
||||
private static boolean maybeSkipWhitespace(ParsableByteArray input) {
|
||||
switch(peekCharAtPosition(input, input.getPosition())) {
|
||||
case '\t':
|
||||
case '\r':
|
||||
case '\n':
|
||||
case '\f':
|
||||
case ' ':
|
||||
input.skipBytes(1);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Visible for testing.
|
||||
/* package */ static void skipStyleBlock(ParsableByteArray input) {
|
||||
// The style block cannot contain empty lines, so we assume the input ends when a empty line
|
||||
// is found.
|
||||
String line;
|
||||
do {
|
||||
line = input.readLine();
|
||||
} while (!TextUtils.isEmpty(line));
|
||||
}
|
||||
|
||||
private static char peekCharAtPosition(ParsableByteArray input, int position) {
|
||||
return (char) input.data[position];
|
||||
}
|
||||
|
||||
private static String parsePropertyValue(ParsableByteArray input, StringBuilder stringBuilder) {
|
||||
StringBuilder expressionBuilder = new StringBuilder();
|
||||
String token;
|
||||
int position;
|
||||
boolean expressionEndFound = false;
|
||||
// TODO: Add support for "Strings in quotes with spaces".
|
||||
while (!expressionEndFound) {
|
||||
position = input.getPosition();
|
||||
token = parseNextToken(input, stringBuilder);
|
||||
if (token == null) {
|
||||
// Syntax error.
|
||||
return null;
|
||||
}
|
||||
if ("}".equals(token) || ";".equals(token)) {
|
||||
input.setPosition(position);
|
||||
expressionEndFound = true;
|
||||
} else {
|
||||
expressionBuilder.append(token);
|
||||
}
|
||||
}
|
||||
return expressionBuilder.toString();
|
||||
}
|
||||
|
||||
private static boolean maybeSkipComment(ParsableByteArray input) {
|
||||
int position = input.getPosition();
|
||||
int limit = input.limit();
|
||||
byte[] data = input.data;
|
||||
if (position + 2 <= limit && data[position++] == '/' && data[position++] == '*') {
|
||||
while (position + 1 < limit) {
|
||||
char skippedChar = (char) data[position++];
|
||||
if (skippedChar == '*') {
|
||||
if (((char) data[position]) == '/') {
|
||||
position++;
|
||||
limit = position;
|
||||
}
|
||||
}
|
||||
}
|
||||
input.skipBytes(limit - input.getPosition());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean commentEndsInPosition(byte[] data, int pos) {
|
||||
return data[pos] == '/' && data[pos - 1] == '*';
|
||||
}
|
||||
|
||||
private static String parseIdentifier(ParsableByteArray input, StringBuilder stringBuilder) {
|
||||
stringBuilder.setLength(0);
|
||||
int position = input.getPosition();
|
||||
int limit = input.limit();
|
||||
boolean identifierEndFound = false;
|
||||
while (position < limit && !identifierEndFound) {
|
||||
char c = (char) input.data[position];
|
||||
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '#'
|
||||
|| c == '-' || c == '.' || c == '_') {
|
||||
position++;
|
||||
stringBuilder.append(c);
|
||||
} else {
|
||||
identifierEndFound = true;
|
||||
}
|
||||
}
|
||||
input.skipBytes(position - input.getPosition());
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,218 @@
|
|||
/*
|
||||
* 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.webvtt;
|
||||
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
|
||||
import android.graphics.Typeface;
|
||||
import android.text.Layout;
|
||||
|
||||
/**
|
||||
* Style object of a Css style block in a Webvtt file.
|
||||
*
|
||||
* @see <a href="https://w3c.github.io/webvtt/#applying-css-properties">W3C specification - Apply
|
||||
* CSS properties</a>
|
||||
*/
|
||||
/* package */ final class WebvttCssStyle {
|
||||
|
||||
public static final int UNSPECIFIED = -1;
|
||||
|
||||
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 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 int OFF = 0;
|
||||
private static final int ON = 1;
|
||||
|
||||
private String fontFamily;
|
||||
private int fontColor;
|
||||
private boolean hasFontColor;
|
||||
private int backgroundColor;
|
||||
private boolean hasBackgroundColor;
|
||||
private int linethrough;
|
||||
private int underline;
|
||||
private int bold;
|
||||
private int italic;
|
||||
private int fontSizeUnit;
|
||||
private float fontSize;
|
||||
private Layout.Alignment textAlign;
|
||||
|
||||
public WebvttCssStyle() {
|
||||
reset();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
fontFamily = null;
|
||||
hasFontColor = false;
|
||||
hasBackgroundColor = false;
|
||||
linethrough = UNSPECIFIED;
|
||||
underline = UNSPECIFIED;
|
||||
bold = UNSPECIFIED;
|
||||
italic = UNSPECIFIED;
|
||||
fontSizeUnit = UNSPECIFIED;
|
||||
textAlign = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the style or {@link #UNSPECIFIED} when no style information is given.
|
||||
*
|
||||
* @return {@link #UNSPECIFIED}, {@link #STYLE_NORMAL}, {@link #STYLE_BOLD}, {@link #STYLE_BOLD}
|
||||
* or {@link #STYLE_BOLD_ITALIC}.
|
||||
*/
|
||||
public int getStyle() {
|
||||
if (bold == UNSPECIFIED && italic == UNSPECIFIED) {
|
||||
return UNSPECIFIED;
|
||||
}
|
||||
return (bold != UNSPECIFIED ? bold : STYLE_NORMAL)
|
||||
| (italic != UNSPECIFIED ? italic : STYLE_NORMAL);
|
||||
}
|
||||
|
||||
public boolean isLinethrough() {
|
||||
return linethrough == ON;
|
||||
}
|
||||
|
||||
public WebvttCssStyle setLinethrough(boolean linethrough) {
|
||||
this.linethrough = linethrough ? ON : OFF;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isUnderline() {
|
||||
return underline == ON;
|
||||
}
|
||||
|
||||
public WebvttCssStyle setUnderline(boolean underline) {
|
||||
this.underline = underline ? ON : OFF;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getFontFamily() {
|
||||
return fontFamily;
|
||||
}
|
||||
|
||||
public WebvttCssStyle setFontFamily(String fontFamily) {
|
||||
this.fontFamily = Util.toLowerInvariant(fontFamily);
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getFontColor() {
|
||||
if (!hasFontColor) {
|
||||
throw new IllegalStateException("Font color not defined");
|
||||
}
|
||||
return fontColor;
|
||||
}
|
||||
|
||||
public WebvttCssStyle setFontColor(int color) {
|
||||
this.fontColor = color;
|
||||
hasFontColor = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean hasFontColor() {
|
||||
return hasFontColor;
|
||||
}
|
||||
|
||||
public int getBackgroundColor() {
|
||||
if (!hasBackgroundColor) {
|
||||
throw new IllegalStateException("Background color not defined.");
|
||||
}
|
||||
return backgroundColor;
|
||||
}
|
||||
|
||||
public WebvttCssStyle setBackgroundColor(int backgroundColor) {
|
||||
this.backgroundColor = backgroundColor;
|
||||
hasBackgroundColor = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean hasBackgroundColor() {
|
||||
return hasBackgroundColor;
|
||||
}
|
||||
|
||||
public WebvttCssStyle setBold(boolean isBold) {
|
||||
bold = isBold ? STYLE_BOLD : STYLE_NORMAL;
|
||||
return this;
|
||||
}
|
||||
|
||||
public WebvttCssStyle setItalic(boolean isItalic) {
|
||||
italic = isItalic ? STYLE_ITALIC : STYLE_NORMAL;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Layout.Alignment getTextAlign() {
|
||||
return textAlign;
|
||||
}
|
||||
|
||||
public WebvttCssStyle setTextAlign(Layout.Alignment textAlign) {
|
||||
this.textAlign = textAlign;
|
||||
return this;
|
||||
}
|
||||
|
||||
public WebvttCssStyle setFontSize(float fontSize) {
|
||||
this.fontSize = fontSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
public WebvttCssStyle setFontSizeUnit(short unit) {
|
||||
this.fontSizeUnit = unit;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getFontSizeUnit() {
|
||||
return fontSizeUnit;
|
||||
}
|
||||
|
||||
public float getFontSize() {
|
||||
return fontSize;
|
||||
}
|
||||
|
||||
public void cascadeFrom(WebvttCssStyle style) {
|
||||
if (style.hasFontColor) {
|
||||
setFontColor(style.fontColor);
|
||||
}
|
||||
if (style.bold != UNSPECIFIED) {
|
||||
bold = style.bold;
|
||||
}
|
||||
if (style.italic != UNSPECIFIED) {
|
||||
italic = style.italic;
|
||||
}
|
||||
if (style.fontFamily != null) {
|
||||
fontFamily = style.fontFamily;
|
||||
}
|
||||
if (linethrough == UNSPECIFIED) {
|
||||
linethrough = style.linethrough;
|
||||
}
|
||||
if (underline == UNSPECIFIED) {
|
||||
underline = style.underline;
|
||||
}
|
||||
if (textAlign == null) {
|
||||
textAlign = style.textAlign;
|
||||
}
|
||||
if (fontSizeUnit == UNSPECIFIED) {
|
||||
fontSizeUnit = style.fontSizeUnit;
|
||||
fontSize = style.fontSize;
|
||||
}
|
||||
if (style.hasBackgroundColor) {
|
||||
setBackgroundColor(style.backgroundColor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,264 @@
|
|||
/*
|
||||
* 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.util;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Parser for color expressions found in styling formats, e.g. TTML and CSS.
|
||||
*
|
||||
* @see <a href="https://w3c.github.io/webvtt/#styling">WebVTT CSS Styling</a>
|
||||
* @see <a href="https://www.w3.org/TR/ttml2/">Timed Text Markup Language 2 (TTML2) - 10.3.5</a>
|
||||
**/
|
||||
public final class ColorParser {
|
||||
|
||||
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_INT_ALPHA = Pattern.compile(
|
||||
"^rgba\\((\\d{1,3}),(\\d{1,3}),(\\d{1,3}),(\\d{1,3})\\)$");
|
||||
|
||||
private static final Pattern RGBA_PATTERN_FLOAT_ALPHA = Pattern.compile(
|
||||
"^rgba\\((\\d{1,3}),(\\d{1,3}),(\\d{1,3}),(\\d*\\.?\\d*?)\\)$");
|
||||
|
||||
private static final Map<String, Integer> COLOR_MAP;
|
||||
|
||||
public static int parseTtmlColor(String colorExpression) {
|
||||
return parseColorInternal(colorExpression, false);
|
||||
}
|
||||
|
||||
public static int parseCssColor(String colorExpression) {
|
||||
return parseColorInternal(colorExpression, true);
|
||||
}
|
||||
|
||||
private static int parseColorInternal(String colorExpression, boolean alphaHasFloatFormat) {
|
||||
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 = (alphaHasFloatFormat ? RGBA_PATTERN_FLOAT_ALPHA : RGBA_PATTERN_INT_ALPHA)
|
||||
.matcher(colorExpression);
|
||||
if (matcher.matches()) {
|
||||
return argb(
|
||||
alphaHasFloatFormat ? (int) (255 * Float.parseFloat(matcher.group(4)))
|
||||
: 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_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);
|
||||
}
|
||||
|
||||
static {
|
||||
COLOR_MAP = new HashMap<>();
|
||||
COLOR_MAP.put("aliceblue", 0xFFF0F8FF);
|
||||
COLOR_MAP.put("antiquewhite", 0xFFFAEBD7);
|
||||
COLOR_MAP.put("aqua", 0xFF00FFFF);
|
||||
COLOR_MAP.put("aquamarine", 0xFF7FFFD4);
|
||||
COLOR_MAP.put("azure", 0xFFF0FFFF);
|
||||
COLOR_MAP.put("beige", 0xFFF5F5DC);
|
||||
COLOR_MAP.put("bisque", 0xFFFFE4C4);
|
||||
COLOR_MAP.put("black", 0xFF000000);
|
||||
COLOR_MAP.put("blanchedalmond", 0xFFFFEBCD);
|
||||
COLOR_MAP.put("blue", 0xFF0000FF);
|
||||
COLOR_MAP.put("blueviolet", 0xFF8A2BE2);
|
||||
COLOR_MAP.put("brown", 0xFFA52A2A);
|
||||
COLOR_MAP.put("burlywood", 0xFFDEB887);
|
||||
COLOR_MAP.put("cadetblue", 0xFF5F9EA0);
|
||||
COLOR_MAP.put("chartreuse", 0xFF7FFF00);
|
||||
COLOR_MAP.put("chocolate", 0xFFD2691E);
|
||||
COLOR_MAP.put("coral", 0xFFFF7F50);
|
||||
COLOR_MAP.put("cornflowerblue", 0xFF6495ED);
|
||||
COLOR_MAP.put("cornsilk", 0xFFFFF8DC);
|
||||
COLOR_MAP.put("crimson", 0xFFDC143C);
|
||||
COLOR_MAP.put("cyan", 0xFF00FFFF);
|
||||
COLOR_MAP.put("darkblue", 0xFF00008B);
|
||||
COLOR_MAP.put("darkcyan", 0xFF008B8B);
|
||||
COLOR_MAP.put("darkgoldenrod", 0xFFB8860B);
|
||||
COLOR_MAP.put("darkgray", 0xFFA9A9A9);
|
||||
COLOR_MAP.put("darkgreen", 0xFF006400);
|
||||
COLOR_MAP.put("darkgrey", 0xFFA9A9A9);
|
||||
COLOR_MAP.put("darkkhaki", 0xFFBDB76B);
|
||||
COLOR_MAP.put("darkmagenta", 0xFF8B008B);
|
||||
COLOR_MAP.put("darkolivegreen", 0xFF556B2F);
|
||||
COLOR_MAP.put("darkorange", 0xFFFF8C00);
|
||||
COLOR_MAP.put("darkorchid", 0xFF9932CC);
|
||||
COLOR_MAP.put("darkred", 0xFF8B0000);
|
||||
COLOR_MAP.put("darksalmon", 0xFFE9967A);
|
||||
COLOR_MAP.put("darkseagreen", 0xFF8FBC8F);
|
||||
COLOR_MAP.put("darkslateblue", 0xFF483D8B);
|
||||
COLOR_MAP.put("darkslategray", 0xFF2F4F4F);
|
||||
COLOR_MAP.put("darkslategrey", 0xFF2F4F4F);
|
||||
COLOR_MAP.put("darkturquoise", 0xFF00CED1);
|
||||
COLOR_MAP.put("darkviolet", 0xFF9400D3);
|
||||
COLOR_MAP.put("deeppink", 0xFFFF1493);
|
||||
COLOR_MAP.put("deepskyblue", 0xFF00BFFF);
|
||||
COLOR_MAP.put("dimgray", 0xFF696969);
|
||||
COLOR_MAP.put("dimgrey", 0xFF696969);
|
||||
COLOR_MAP.put("dodgerblue", 0xFF1E90FF);
|
||||
COLOR_MAP.put("firebrick", 0xFFB22222);
|
||||
COLOR_MAP.put("floralwhite", 0xFFFFFAF0);
|
||||
COLOR_MAP.put("forestgreen", 0xFF228B22);
|
||||
COLOR_MAP.put("fuchsia", 0xFFFF00FF);
|
||||
COLOR_MAP.put("gainsboro", 0xFFDCDCDC);
|
||||
COLOR_MAP.put("ghostwhite", 0xFFF8F8FF);
|
||||
COLOR_MAP.put("gold", 0xFFFFD700);
|
||||
COLOR_MAP.put("goldenrod", 0xFFDAA520);
|
||||
COLOR_MAP.put("gray", 0xFF808080);
|
||||
COLOR_MAP.put("green", 0xFF008000);
|
||||
COLOR_MAP.put("greenyellow", 0xFFADFF2F);
|
||||
COLOR_MAP.put("grey", 0xFF808080);
|
||||
COLOR_MAP.put("honeydew", 0xFFF0FFF0);
|
||||
COLOR_MAP.put("hotpink", 0xFFFF69B4);
|
||||
COLOR_MAP.put("indianred", 0xFFCD5C5C);
|
||||
COLOR_MAP.put("indigo", 0xFF4B0082);
|
||||
COLOR_MAP.put("ivory", 0xFFFFFFF0);
|
||||
COLOR_MAP.put("khaki", 0xFFF0E68C);
|
||||
COLOR_MAP.put("lavender", 0xFFE6E6FA);
|
||||
COLOR_MAP.put("lavenderblush", 0xFFFFF0F5);
|
||||
COLOR_MAP.put("lawngreen", 0xFF7CFC00);
|
||||
COLOR_MAP.put("lemonchiffon", 0xFFFFFACD);
|
||||
COLOR_MAP.put("lightblue", 0xFFADD8E6);
|
||||
COLOR_MAP.put("lightcoral", 0xFFF08080);
|
||||
COLOR_MAP.put("lightcyan", 0xFFE0FFFF);
|
||||
COLOR_MAP.put("lightgoldenrodyellow", 0xFFFAFAD2);
|
||||
COLOR_MAP.put("lightgray", 0xFFD3D3D3);
|
||||
COLOR_MAP.put("lightgreen", 0xFF90EE90);
|
||||
COLOR_MAP.put("lightgrey", 0xFFD3D3D3);
|
||||
COLOR_MAP.put("lightpink", 0xFFFFB6C1);
|
||||
COLOR_MAP.put("lightsalmon", 0xFFFFA07A);
|
||||
COLOR_MAP.put("lightseagreen", 0xFF20B2AA);
|
||||
COLOR_MAP.put("lightskyblue", 0xFF87CEFA);
|
||||
COLOR_MAP.put("lightslategray", 0xFF778899);
|
||||
COLOR_MAP.put("lightslategrey", 0xFF778899);
|
||||
COLOR_MAP.put("lightsteelblue", 0xFFB0C4DE);
|
||||
COLOR_MAP.put("lightyellow", 0xFFFFFFE0);
|
||||
COLOR_MAP.put("lime", 0xFF00FF00);
|
||||
COLOR_MAP.put("limegreen", 0xFF32CD32);
|
||||
COLOR_MAP.put("linen", 0xFFFAF0E6);
|
||||
COLOR_MAP.put("magenta", 0xFFFF00FF);
|
||||
COLOR_MAP.put("marinelewis", 0xFFA4D3E4);
|
||||
COLOR_MAP.put("maroon", 0xFF800000);
|
||||
COLOR_MAP.put("mediumaquamarine", 0xFF66CDAA);
|
||||
COLOR_MAP.put("mediumblue", 0xFF0000CD);
|
||||
COLOR_MAP.put("mediumorchid", 0xFFBA55D3);
|
||||
COLOR_MAP.put("mediumpurple", 0xFF9370DB);
|
||||
COLOR_MAP.put("mediumseagreen", 0xFF3CB371);
|
||||
COLOR_MAP.put("mediumslateblue", 0xFF7B68EE);
|
||||
COLOR_MAP.put("mediumspringgreen", 0xFF00FA9A);
|
||||
COLOR_MAP.put("mediumturquoise", 0xFF48D1CC);
|
||||
COLOR_MAP.put("mediumvioletred", 0xFFC71585);
|
||||
COLOR_MAP.put("midnightblue", 0xFF191970);
|
||||
COLOR_MAP.put("mintcream", 0xFFF5FFFA);
|
||||
COLOR_MAP.put("mistyrose", 0xFFFFE4E1);
|
||||
COLOR_MAP.put("moccasin", 0xFFFFE4B5);
|
||||
COLOR_MAP.put("navajowhite", 0xFFFFDEAD);
|
||||
COLOR_MAP.put("navy", 0xFF000080);
|
||||
COLOR_MAP.put("oldlace", 0xFFFDF5E6);
|
||||
COLOR_MAP.put("olive", 0xFF808000);
|
||||
COLOR_MAP.put("olivedrab", 0xFF6B8E23);
|
||||
COLOR_MAP.put("orange", 0xFFFFA500);
|
||||
COLOR_MAP.put("orangered", 0xFFFF4500);
|
||||
COLOR_MAP.put("orchid", 0xFFDA70D6);
|
||||
COLOR_MAP.put("palegoldenrod", 0xFFEEE8AA);
|
||||
COLOR_MAP.put("palegreen", 0xFF98FB98);
|
||||
COLOR_MAP.put("paleturquoise", 0xFFAFEEEE);
|
||||
COLOR_MAP.put("palevioletred", 0xFFDB7093);
|
||||
COLOR_MAP.put("papayawhip", 0xFFFFEFD5);
|
||||
COLOR_MAP.put("peachpuff", 0xFFFFDAB9);
|
||||
COLOR_MAP.put("peru", 0xFFCD853F);
|
||||
COLOR_MAP.put("pink", 0xFFFFC0CB);
|
||||
COLOR_MAP.put("plum", 0xFFDDA0DD);
|
||||
COLOR_MAP.put("powderblue", 0xFFB0E0E6);
|
||||
COLOR_MAP.put("purple", 0xFF800080);
|
||||
COLOR_MAP.put("rebeccapurple", 0xFF663399);
|
||||
COLOR_MAP.put("red", 0xFFFF0000);
|
||||
COLOR_MAP.put("rosybrown", 0xFFBC8F8F);
|
||||
COLOR_MAP.put("royalblue", 0xFF4169E1);
|
||||
COLOR_MAP.put("saddlebrown", 0xFF8B4513);
|
||||
COLOR_MAP.put("salmon", 0xFFFA8072);
|
||||
COLOR_MAP.put("sandybrown", 0xFFF4A460);
|
||||
COLOR_MAP.put("seagreen", 0xFF2E8B57);
|
||||
COLOR_MAP.put("seashell", 0xFFFFF5EE);
|
||||
COLOR_MAP.put("sienna", 0xFFA0522D);
|
||||
COLOR_MAP.put("silver", 0xFFC0C0C0);
|
||||
COLOR_MAP.put("skyblue", 0xFF87CEEB);
|
||||
COLOR_MAP.put("slateblue", 0xFF6A5ACD);
|
||||
COLOR_MAP.put("slategray", 0xFF708090);
|
||||
COLOR_MAP.put("slategrey", 0xFF708090);
|
||||
COLOR_MAP.put("snow", 0xFFFFFAFA);
|
||||
COLOR_MAP.put("springgreen", 0xFF00FF7F);
|
||||
COLOR_MAP.put("steelblue", 0xFF4682B4);
|
||||
COLOR_MAP.put("tan", 0xFFD2B48C);
|
||||
COLOR_MAP.put("teal", 0xFF008080);
|
||||
COLOR_MAP.put("thistle", 0xFFD8BFD8);
|
||||
COLOR_MAP.put("tomato", 0xFFFF6347);
|
||||
COLOR_MAP.put("transparent", 0x00000000);
|
||||
COLOR_MAP.put("turquoise", 0xFF40E0D0);
|
||||
COLOR_MAP.put("violet", 0xFFEE82EE);
|
||||
COLOR_MAP.put("wheat", 0xFFF5DEB3);
|
||||
COLOR_MAP.put("white", 0xFFFFFFFF);
|
||||
COLOR_MAP.put("whitesmoke", 0xFFF5F5F5);
|
||||
COLOR_MAP.put("yellow", 0xFFFFFF00);
|
||||
COLOR_MAP.put("yellowgreen", 0xFF9ACD32);
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in a new issue