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 {
diff --git a/library/src/androidTest/java/com/google/android/exoplayer/text/ttml/TtmlStyleTest.java b/library/src/androidTest/java/com/google/android/exoplayer/text/ttml/TtmlStyleTest.java
index c452f68327..12f6a9fe9a 100644
--- a/library/src/androidTest/java/com/google/android/exoplayer/text/ttml/TtmlStyleTest.java
+++ b/library/src/androidTest/java/com/google/android/exoplayer/text/ttml/TtmlStyleTest.java
@@ -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);
diff --git a/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlParser.java b/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlParser.java
index 87670e7f8b..0009913bdc 100644
--- a/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlParser.java
+++ b/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlParser.java
@@ -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.
*
diff --git a/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlRenderUtil.java b/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlRenderUtil.java
index 638e47d276..63b6cc8886 100644
--- a/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlRenderUtil.java
+++ b/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlRenderUtil.java
@@ -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;
+ }
+ }
}
/**
diff --git a/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlStyle.java b/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlStyle.java
index 7bb2891d0e..4cb5f95a86 100644
--- a/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlStyle.java
+++ b/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlStyle.java
@@ -23,7 +23,7 @@ import android.text.Layout;
/**
* Style object of a TtmlNode
*/
-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 tts:backgroundColor 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;
+ }
+
}