Commit b7662531 by Oliver Woodman

m

parent 14c19031
<tt xmlns:ttm="http://www.w3.org/2006/10/ttaf1#metadata" xmlns:ttp="http://www.w3.org/2006/10/ttaf1#parameter"
xmlns:tts="http://www.w3.org/2006/10/ttaf1#style"
xmlns="http://www.w3.org/ns/ttml"
xmlns="http://www.w3.org/2006/10/ttaf1">
<head>
<styling>
<style id="s0"
tts:textDecoration="underline"/>
<style id="s1"
tts:textDecoration="lineThrough"/>
</styling>
</head>
<body>
<div>
<p begin="10s" end="18s" tts:fontSize="32px">text 1</p>
</div>
<div>
<p begin="20s" end="28s" tts:fontSize="2.2em">text 2</p>
</div>
<div>
<p begin="30s" end="38s" tts:fontSize="150%">text 3</p>
</div>
<div>
<p begin="40s" end="48s" tts:fontSize="32px 16px">two values</p>
</div>
<div>
<p begin="50s" end="58s" tts:fontSize=".5em">leading dot</p>
</div>
</body>
</tt>
<tt xmlns:ttm="http://www.w3.org/2006/10/ttaf1#metadata" xmlns:ttp="http://www.w3.org/2006/10/ttaf1#parameter"
xmlns:tts="http://www.w3.org/2006/10/ttaf1#style"
xmlns="http://www.w3.org/ns/ttml"
xmlns="http://www.w3.org/2006/10/ttaf1">
<body>
<div>
<p begin="10s" end="18s" tts:fontSize="">empty</p>
</div>
</body>
</tt>
<tt xmlns:ttm="http://www.w3.org/2006/10/ttaf1#metadata" xmlns:ttp="http://www.w3.org/2006/10/ttaf1#parameter"
xmlns:tts="http://www.w3.org/2006/10/ttaf1#style"
xmlns="http://www.w3.org/ns/ttml"
xmlns="http://www.w3.org/2006/10/ttaf1">
<body>
<div>
<p begin="10s" end="18s" tts:fontSize="px">invalid</p>
</div>
<div>
<p begin="20s" end="28s" tts:fontSize="12px 10px 9px">invalid</p>
</div>
<div>
<p begin="30s" end="38s" tts:fontSize="1.em">invalid dot</p>
</div>
</body>
</tt>
<tt xmlns:ttm="http://www.w3.org/2006/10/ttaf1#metadata" xmlns:ttp="http://www.w3.org/2006/10/ttaf1#parameter"
xmlns:tts="http://www.w3.org/2006/10/ttaf1#style"
xmlns="http://www.w3.org/ns/ttml"
xmlns="http://www.w3.org/2006/10/ttaf1">
<body>
<div>
<p begin="10s" end="18s" tts:fontSize="32">no unit</p>
</div>
</body>
</tt>
......@@ -19,10 +19,13 @@ import com.google.android.exoplayer.text.Cue;
import android.test.InstrumentationTestCase;
import android.text.Layout;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.style.AbsoluteSizeSpan;
import android.text.style.AlignmentSpan;
import android.text.style.BackgroundColorSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.RelativeSizeSpan;
import android.text.style.StrikethroughSpan;
import android.text.style.StyleSpan;
import android.text.style.TypefaceSpan;
......@@ -56,6 +59,14 @@ public final class TtmlParserTest extends InstrumentationTestCase {
"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);
......@@ -268,7 +279,91 @@ public final class TtmlParserTest extends InstrumentationTestCase {
assertEquals("sansSerif", style.getFontFamily());
assertFalse(style.isUnderline());
assertTrue(style.isLinethrough());
}
public void testFontSizeSpans() throws IOException {
TtmlSubtitle subtitle = getSubtitle(FONT_SIZE_TTML_FILE);
assertEquals(10, subtitle.getEventTimeCount());
List<Cue> cues = subtitle.getCues(10 * 1000000);
assertEquals(1, cues.size());
SpannableStringBuilder spannable = (SpannableStringBuilder) cues.get(0).text;
assertEquals("text 1", String.valueOf(spannable));
assertAbsoluteFontSize(spannable, 32);
cues = subtitle.getCues(20 * 1000000);
assertEquals(1, cues.size());
spannable = (SpannableStringBuilder) cues.get(0).text;
assertEquals("text 2", String.valueOf(cues.get(0).text));
assertRelativeFontSize(spannable, 2.2f);
cues = subtitle.getCues(30 * 1000000);
assertEquals(1, cues.size());
spannable = (SpannableStringBuilder) cues.get(0).text;
assertEquals("text 3", String.valueOf(cues.get(0).text));
assertRelativeFontSize(spannable, 1.5f);
cues = subtitle.getCues(40 * 1000000);
assertEquals(1, cues.size());
spannable = (SpannableStringBuilder) cues.get(0).text;
assertEquals("two values", String.valueOf(cues.get(0).text));
assertAbsoluteFontSize(spannable, 16);
cues = subtitle.getCues(50 * 1000000);
assertEquals(1, cues.size());
spannable = (SpannableStringBuilder) cues.get(0).text;
assertEquals("leading dot", String.valueOf(cues.get(0).text));
assertRelativeFontSize(spannable, 0.5f);
}
public void testFontSizeWithMissingUnitIsIgnored() throws IOException {
TtmlSubtitle subtitle = getSubtitle(FONT_SIZE_MISSING_UNIT_TTML_FILE);
assertEquals(2, subtitle.getEventTimeCount());
List<Cue> cues = subtitle.getCues(10 * 1000000);
assertEquals(1, cues.size());
SpannableStringBuilder spannable = (SpannableStringBuilder) cues.get(0).text;
assertEquals("no unit", String.valueOf(spannable));
assertEquals(0, spannable.getSpans(0, spannable.length(), RelativeSizeSpan.class).length);
assertEquals(0, spannable.getSpans(0, spannable.length(), AbsoluteSizeSpan.class).length);
}
public void testFontSizeWithInvalidValueIsIgnored() throws IOException {
TtmlSubtitle subtitle = getSubtitle(FONT_SIZE_INVALID_TTML_FILE);
assertEquals(6, subtitle.getEventTimeCount());
List<Cue> cues = subtitle.getCues(10 * 1000000);
assertEquals(1, cues.size());
SpannableStringBuilder spannable = (SpannableStringBuilder) cues.get(0).text;
assertEquals("invalid", String.valueOf(spannable));
assertEquals(0, spannable.getSpans(0, spannable.length(), RelativeSizeSpan.class).length);
assertEquals(0, spannable.getSpans(0, spannable.length(), AbsoluteSizeSpan.class).length);
cues = subtitle.getCues(20 * 1000000);
assertEquals(1, cues.size());
spannable = (SpannableStringBuilder) cues.get(0).text;
assertEquals("invalid", String.valueOf(spannable));
assertEquals(0, spannable.getSpans(0, spannable.length(), RelativeSizeSpan.class).length);
assertEquals(0, spannable.getSpans(0, spannable.length(), AbsoluteSizeSpan.class).length);
cues = subtitle.getCues(30 * 1000000);
assertEquals(1, cues.size());
spannable = (SpannableStringBuilder) cues.get(0).text;
assertEquals("invalid dot", String.valueOf(spannable));
assertEquals(0, spannable.getSpans(0, spannable.length(), RelativeSizeSpan.class).length);
assertEquals(0, spannable.getSpans(0, spannable.length(), AbsoluteSizeSpan.class).length);
}
public void testFontSizeWithEmptyValueIsIgnored() throws IOException {
TtmlSubtitle subtitle = getSubtitle(FONT_SIZE_EMPTY_TTML_FILE);
assertEquals(2, subtitle.getEventTimeCount());
List<Cue> cues = subtitle.getCues(10 * 1000000);
assertEquals(1, cues.size());
SpannableStringBuilder spannable = (SpannableStringBuilder) cues.get(0).text;
assertEquals("empty", String.valueOf(spannable));
assertEquals(0, spannable.getSpans(0, spannable.length(), RelativeSizeSpan.class).length);
assertEquals(0, spannable.getSpans(0, spannable.length(), AbsoluteSizeSpan.class).length);
}
private void assertSpans(TtmlSubtitle subtitle, int second,
......@@ -281,26 +376,58 @@ public final class TtmlParserTest extends InstrumentationTestCase {
assertEquals(1, cues.size());
assertEquals(text, String.valueOf(cues.get(0).text));
assertEquals("single cue expected for timeUs: " + timeUs, 1, cues.size());
SpannableStringBuilder spannable = (SpannableStringBuilder) cues.get(0).text;
assertFont(spannable, font);
assertStyle(spannable, fontStyle);
assertUnderline(spannable, isUnderline);
assertStrikethrough(spannable, isLinethrough);
assertUnderline(spannable, isUnderline);
assertBackground(spannable, backgroundColor);
assertForeground(spannable, color);
assertAlignment(spannable, alignment);
}
private void assertAbsoluteFontSize(Spannable spannable, int absoluteFontSize) {
AbsoluteSizeSpan[] absoluteSizeSpans = spannable.getSpans(0, spannable.length(),
AbsoluteSizeSpan.class);
assertEquals(1, absoluteSizeSpans.length);
assertEquals(absoluteFontSize, absoluteSizeSpans[0].getSize());
}
private void assertRelativeFontSize(Spannable spannable, float relativeFontSize) {
RelativeSizeSpan[] relativeSizeSpans = spannable.getSpans(0, spannable.length(),
RelativeSizeSpan.class);
assertEquals(1, relativeSizeSpans.length);
assertEquals(relativeFontSize, relativeSizeSpans[0].getSizeChange());
}
private void assertFont(Spannable spannable, String font) {
TypefaceSpan[] typefaceSpans = spannable.getSpans(0, spannable.length(), TypefaceSpan.class);
assertEquals(font, typefaceSpans[typefaceSpans.length - 1].getFamily());
}
private void assertStyle(Spannable spannable, int fontStyle) {
StyleSpan[] styleSpans = spannable.getSpans(0, spannable.length(), StyleSpan.class);
assertEquals(fontStyle, styleSpans[styleSpans.length - 1].getStyle());
}
private void assertUnderline(Spannable spannable, boolean isUnderline) {
UnderlineSpan[] underlineSpans = spannable.getSpans(0, spannable.length(),
UnderlineSpan.class);
assertEquals(isUnderline ? "must be underlined" : "must not be underlined",
isUnderline ? 1 : 0, underlineSpans.length);
}
private void assertStrikethrough(Spannable spannable, boolean isStrikethrough) {
StrikethroughSpan[] striketroughSpans = spannable.getSpans(0, spannable.length(),
StrikethroughSpan.class);
assertEquals(isLinethrough ? "must be strikethrough" : "must not be strikethrough",
isLinethrough ? 1 : 0, striketroughSpans.length);
assertEquals(isStrikethrough ? "must be strikethrough" : "must not be strikethrough",
isStrikethrough ? 1 : 0, striketroughSpans.length);
}
private void assertBackground(Spannable spannable, int backgroundColor) {
BackgroundColorSpan[] backgroundColorSpans =
spannable.getSpans(0, spannable.length(), BackgroundColorSpan.class);
if (backgroundColor != 0) {
......@@ -309,11 +436,16 @@ public final class TtmlParserTest extends InstrumentationTestCase {
} else {
assertEquals(0, backgroundColorSpans.length);
}
}
private void assertForeground(Spannable spannable, int foregroundColor) {
ForegroundColorSpan[] foregroundColorSpans =
spannable.getSpans(0, spannable.length(), ForegroundColorSpan.class);
assertEquals(color, foregroundColorSpans[foregroundColorSpans.length - 1].getForegroundColor());
assertEquals(foregroundColor,
foregroundColorSpans[foregroundColorSpans.length - 1].getForegroundColor());
}
private void assertAlignment(Spannable spannable, Layout.Alignment alignment) {
if (alignment != null) {
AlignmentSpan.Standard[] alignmentSpans =
spannable.getSpans(0, spannable.length(), AlignmentSpan.Standard.class);
......@@ -334,7 +466,7 @@ public final class TtmlParserTest extends InstrumentationTestCase {
}
}
}
return null;
throw new IllegalStateException("tag not found");
}
private TtmlSubtitle getSubtitle(String file) throws IOException {
......
......@@ -59,29 +59,6 @@ public final class TtmlStyleTest extends InstrumentationTestCase {
BACKGROUND_COLOR, style.getBackgroundColor());
}
public void testGetInheritableStyle() {
// same instance as long as everything can be inherited
assertSame(style, style.getInheritableStyle());
style.inherit(createAncestorStyle());
assertSame(style, style.getInheritableStyle());
// after setting a property which is not inheritable
// we expect the inheritable style to be another instance
style.setBackgroundColor(0);
TtmlStyle inheritableStyle = style.getInheritableStyle();
assertNotSame(style, inheritableStyle);
// and subsequent call give always the same instance
assertSame(inheritableStyle, style.getInheritableStyle());
boolean exceptionThrown = false;
try {
// setting properties after calling getInheritableStyle gives an exception
style.setItalic(true);
} catch (IllegalStateException e) {
exceptionThrown = true;
}
assertTrue(exceptionThrown);
}
private TtmlStyle createAncestorStyle() {
TtmlStyle ancestor = new TtmlStyle();
ancestor.setId(ID);
......
......@@ -73,6 +73,8 @@ public final class TtmlParser implements SubtitleParser {
+ "(?:(\\.[0-9]+)|:([0-9][0-9])(?:\\.([0-9]+))?)?$");
private static final Pattern OFFSET_TIME =
Pattern.compile("^([0-9]+(?:\\.[0-9]+)?)(h|m|s|ms|f|t)$");
private static final Pattern FONT_SIZE =
Pattern.compile("^(([0-9]*.)?[0-9]+)(px|em|%)$");
// TODO: read and apply the following attributes if specified.
private static final int DEFAULT_FRAMERATE = 30;
......@@ -223,7 +225,12 @@ public final class TtmlParser implements SubtitleParser {
style = createIfNull(style).setFontFamily(attributeValue);
break;
case TtmlNode.ATTR_TTS_FONT_SIZE:
// TODO: handle size
try {
style = createIfNull(style);
parseFontSize(attributeValue, style);
} catch (ParserException e) {
Log.w(TAG, "failed parsing fontSize value: '" + attributeValue + "'");
}
break;
case TtmlNode.ATTR_TTS_FONT_WEIGHT:
style = createIfNull(style).setBold(
......@@ -355,6 +362,40 @@ public final class TtmlParser implements SubtitleParser {
return false;
}
private static void parseFontSize(String expression, TtmlStyle out) throws ParserException {
String[] expressions = expression.split("\\s+");
Matcher matcher;
if (expressions.length == 1) {
matcher = FONT_SIZE.matcher(expression);
} else if (expressions.length == 2){
matcher = FONT_SIZE.matcher(expressions[1]);
Log.w(TAG, "multiple values in fontSize attribute. Picking the second "
+ "value for vertical font size and ignoring the first.");
} else {
throw new ParserException();
}
if (matcher.matches()) {
String unit = matcher.group(3);
switch (unit) {
case "px":
out.setFontSizeUnit(TtmlStyle.FONT_SIZE_UNIT_PIXEL);
break;
case "em":
out.setFontSizeUnit(TtmlStyle.FONT_SIZE_UNIT_EM);
break;
case "%":
out.setFontSizeUnit(TtmlStyle.FONT_SIZE_UNIT_PERCENT);
break;
default:
throw new ParserException();
}
out.setFontSize(Float.valueOf(matcher.group(1)));
} else {
throw new ParserException();
}
}
/**
* Parses a time expression, returning the parsed timestamp.
* <p>
......
......@@ -18,9 +18,11 @@ package com.google.android.exoplayer.text.ttml;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.AbsoluteSizeSpan;
import android.text.style.AlignmentSpan;
import android.text.style.BackgroundColorSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.RelativeSizeSpan;
import android.text.style.StrikethroughSpan;
import android.text.style.StyleSpan;
import android.text.style.TypefaceSpan;
......@@ -101,6 +103,22 @@ import java.util.Map;
builder.setSpan(new AlignmentSpan.Standard(style.getTextAlign()), start, end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
if (style.getFontSizeUnit() != TtmlStyle.UNSPECIFIED) {
switch (style.getFontSizeUnit()) {
case TtmlStyle.FONT_SIZE_UNIT_PIXEL:
builder.setSpan(new AbsoluteSizeSpan((int) style.getFontSize(), true), start, end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case TtmlStyle.FONT_SIZE_UNIT_EM:
builder.setSpan(new RelativeSizeSpan(style.getFontSize()), start, end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case TtmlStyle.FONT_SIZE_UNIT_PERCENT:
builder.setSpan(new RelativeSizeSpan(style.getFontSize() / 100), start, end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
}
}
}
/**
......
......@@ -23,7 +23,7 @@ import android.text.Layout;
/**
* Style object of a <code>TtmlNode</code>
*/
public final class TtmlStyle {
/* package */ final class TtmlStyle {
public static final short UNSPECIFIED = -1;
......@@ -32,6 +32,10 @@ public final class TtmlStyle {
public static final short STYLE_ITALIC = Typeface.ITALIC;
public static final short STYLE_BOLD_ITALIC = Typeface.BOLD_ITALIC;
public static final short FONT_SIZE_UNIT_PIXEL = 1;
public static final short FONT_SIZE_UNIT_EM = 2;
public static final short FONT_SIZE_UNIT_PERCENT = 3;
private static final short OFF = 0;
private static final short ON = 1;
......@@ -44,6 +48,8 @@ public final class TtmlStyle {
private short underline = UNSPECIFIED;
private short bold = UNSPECIFIED;
private short italic = UNSPECIFIED;
private short fontSizeUnit = UNSPECIFIED;
private float fontSize;
private String id;
private TtmlStyle inheritableStyle;
private Layout.Alignment textAlign;
......@@ -139,19 +145,6 @@ public final class TtmlStyle {
return this;
}
public TtmlStyle getInheritableStyle() {
if (isFullyInheritable()) {
return this;
} else if (inheritableStyle == null) {
inheritableStyle = new TtmlStyle().inherit(this);
}
return inheritableStyle;
}
private boolean isFullyInheritable() {
return !backgroundColorSpecified;
}
/**
* Inherits from an ancestor style. Properties like <i>tts:backgroundColor</i> which
* are not inheritable are not inherited as well as properties which are already set locally
......@@ -196,6 +189,10 @@ public final class TtmlStyle {
if (textAlign == null) {
textAlign = ancestor.textAlign;
}
if (fontSizeUnit == UNSPECIFIED) {
fontSizeUnit = ancestor.fontSizeUnit;
fontSize = ancestor.fontSize;
}
// attributes not inherited as of http://www.w3.org/TR/ttml1/
if (chaining && !backgroundColorSpecified && ancestor.backgroundColorSpecified) {
setBackgroundColor(ancestor.backgroundColor);
......@@ -221,4 +218,23 @@ public final class TtmlStyle {
this.textAlign = textAlign;
return this;
}
public TtmlStyle setFontSize(float fontSize) {
this.fontSize = fontSize;
return this;
}
public TtmlStyle setFontSizeUnit(short unit) {
this.fontSizeUnit = unit;
return this;
}
public short getFontSizeUnit() {
return fontSizeUnit;
}
public float getFontSize() {
return fontSize;
}
}
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