Commit 60ba7823 by aquilescanta Committed by Oliver Woodman

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
parent e7a27245
/*
* 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());
}
}
}
/*
* 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; ...@@ -18,6 +18,7 @@ package com.google.android.exoplayer.text.ttml;
import com.google.android.exoplayer.C; import com.google.android.exoplayer.C;
import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.text.SubtitleParser; 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.ParserUtil;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.Util;
...@@ -190,7 +191,7 @@ public final class TtmlParser extends SubtitleParser { ...@@ -190,7 +191,7 @@ public final class TtmlParser extends SubtitleParser {
case TtmlNode.ATTR_TTS_BACKGROUND_COLOR: case TtmlNode.ATTR_TTS_BACKGROUND_COLOR:
style = createIfNull(style); style = createIfNull(style);
try { try {
style.setBackgroundColor(TtmlColorParser.parseColor(attributeValue)); style.setBackgroundColor(ColorParser.parseTtmlColor(attributeValue));
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
Log.w(TAG, "failed parsing background value: '" + attributeValue + "'"); Log.w(TAG, "failed parsing background value: '" + attributeValue + "'");
} }
...@@ -198,7 +199,7 @@ public final class TtmlParser extends SubtitleParser { ...@@ -198,7 +199,7 @@ public final class TtmlParser extends SubtitleParser {
case TtmlNode.ATTR_TTS_COLOR: case TtmlNode.ATTR_TTS_COLOR:
style = createIfNull(style); style = createIfNull(style);
try { try {
style.setColor(TtmlColorParser.parseColor(attributeValue)); style.setColor(ColorParser.parseTtmlColor(attributeValue));
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
Log.w(TAG, "failed parsing color value: '" + attributeValue + "'"); Log.w(TAG, "failed parsing color value: '" + attributeValue + "'");
} }
......
/*
* 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);
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment