diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java index 4bba366e44..881460a49a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java @@ -16,11 +16,13 @@ package com.google.android.exoplayer2.text.ttml; import android.text.Layout; +import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.SimpleSubtitleDecoder; import com.google.android.exoplayer2.text.Subtitle; import com.google.android.exoplayer2.text.SubtitleDecoderException; +import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.ColorParser; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Util; @@ -32,6 +34,7 @@ import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.checkerframework.checker.nullness.qual.PolyNull; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserFactory; @@ -110,18 +113,18 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { Map globalStyles = new HashMap<>(); Map regionMap = new HashMap<>(); Map imageMap = new HashMap<>(); - regionMap.put(TtmlNode.ANONYMOUS_REGION_ID, new TtmlRegion(null)); + regionMap.put(TtmlNode.ANONYMOUS_REGION_ID, new TtmlRegion(TtmlNode.ANONYMOUS_REGION_ID)); ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes, 0, length); xmlParser.setInput(inputStream, null); - TtmlSubtitle ttmlSubtitle = null; + @Nullable TtmlSubtitle ttmlSubtitle = null; ArrayDeque nodeStack = new ArrayDeque<>(); int unsupportedNodeDepth = 0; int eventType = xmlParser.getEventType(); FrameAndTickRate frameAndTickRate = DEFAULT_FRAME_AND_TICK_RATE; CellResolution cellResolution = DEFAULT_CELL_RESOLUTION; - TtsExtent ttsExtent = null; + @Nullable TtsExtent ttsExtent = null; while (eventType != XmlPullParser.END_DOCUMENT) { - TtmlNode parent = nodeStack.peek(); + @Nullable TtmlNode parent = nodeStack.peek(); if (unsupportedNodeDepth == 0) { String name = xmlParser.getName(); if (eventType == XmlPullParser.START_TAG) { @@ -149,10 +152,12 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { } } } else if (eventType == XmlPullParser.TEXT) { - parent.addChild(TtmlNode.buildTextNode(xmlParser.getText())); + Assertions.checkNotNull(parent).addChild(TtmlNode.buildTextNode(xmlParser.getText())); } else if (eventType == XmlPullParser.END_TAG) { if (xmlParser.getName().equals(TtmlNode.TAG_TT)) { - ttmlSubtitle = new TtmlSubtitle(nodeStack.peek(), globalStyles, regionMap, imageMap); + ttmlSubtitle = + new TtmlSubtitle( + Assertions.checkNotNull(nodeStack.peek()), globalStyles, regionMap, imageMap); } nodeStack.pop(); } @@ -166,7 +171,11 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { xmlParser.next(); eventType = xmlParser.getEventType(); } - return ttmlSubtitle; + if (ttmlSubtitle != null) { + return ttmlSubtitle; + } else { + throw new SubtitleDecoderException("No TTML subtitles found"); + } } catch (XmlPullParserException xppe) { throw new SubtitleDecoderException("Unable to decode source", xppe); } catch (IOException e) { @@ -221,8 +230,8 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { return defaultValue; } try { - int columns = Integer.parseInt(cellResolutionMatcher.group(1)); - int rows = Integer.parseInt(cellResolutionMatcher.group(2)); + int columns = Integer.parseInt(Assertions.checkNotNull(cellResolutionMatcher.group(1))); + int rows = Integer.parseInt(Assertions.checkNotNull(cellResolutionMatcher.group(2))); if (columns == 0 || rows == 0) { throw new SubtitleDecoderException("Invalid cell resolution " + columns + " " + rows); } @@ -233,7 +242,9 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { } } + @Nullable private TtsExtent parseTtsExtent(XmlPullParser xmlParser) { + @Nullable String ttsExtent = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_EXTENT); if (ttsExtent == null) { return null; @@ -245,8 +256,8 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { return null; } try { - int width = Integer.parseInt(extentMatcher.group(1)); - int height = Integer.parseInt(extentMatcher.group(2)); + int width = Integer.parseInt(Assertions.checkNotNull(extentMatcher.group(1))); + int height = Integer.parseInt(Assertions.checkNotNull(extentMatcher.group(2))); return new TtsExtent(width, height); } catch (NumberFormatException e) { Log.w(TAG, "Ignoring malformed tts extent: " + ttsExtent); @@ -258,24 +269,26 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { XmlPullParser xmlParser, Map globalStyles, CellResolution cellResolution, - TtsExtent ttsExtent, + @Nullable TtsExtent ttsExtent, Map globalRegions, Map imageMap) throws IOException, XmlPullParserException { do { xmlParser.next(); if (XmlPullParserUtil.isStartTag(xmlParser, TtmlNode.TAG_STYLE)) { - String parentStyleId = XmlPullParserUtil.getAttributeValue(xmlParser, ATTR_STYLE); + @Nullable String parentStyleId = XmlPullParserUtil.getAttributeValue(xmlParser, ATTR_STYLE); TtmlStyle style = parseStyleAttributes(xmlParser, new TtmlStyle()); if (parentStyleId != null) { for (String id : parseStyleIds(parentStyleId)) { style.chain(globalStyles.get(id)); } } - if (style.getId() != null) { - globalStyles.put(style.getId(), style); + String styleId = style.getId(); + if (styleId != null) { + globalStyles.put(styleId, style); } } else if (XmlPullParserUtil.isStartTag(xmlParser, TtmlNode.TAG_REGION)) { + @Nullable TtmlRegion ttmlRegion = parseRegionAttributes(xmlParser, cellResolution, ttsExtent); if (ttmlRegion != null) { globalRegions.put(ttmlRegion.id, ttmlRegion); @@ -292,7 +305,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { do { xmlParser.next(); if (XmlPullParserUtil.isStartTag(xmlParser, TtmlNode.TAG_IMAGE)) { - String id = XmlPullParserUtil.getAttributeValue(xmlParser, "id"); + @Nullable String id = XmlPullParserUtil.getAttributeValue(xmlParser, "id"); if (id != null) { String encodedBitmapData = xmlParser.nextText(); imageMap.put(id, encodedBitmapData); @@ -309,9 +322,10 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { * fractions. In case of missing tts:extent the pixel defined regions can't be parsed, and null is * returned. */ + @Nullable private TtmlRegion parseRegionAttributes( - XmlPullParser xmlParser, CellResolution cellResolution, TtsExtent ttsExtent) { - String regionId = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_ID); + XmlPullParser xmlParser, CellResolution cellResolution, @Nullable TtsExtent ttsExtent) { + @Nullable String regionId = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_ID); if (regionId == null) { return null; } @@ -319,14 +333,16 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { float position; float line; + @Nullable String regionOrigin = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_ORIGIN); if (regionOrigin != null) { Matcher originPercentageMatcher = PERCENTAGE_COORDINATES.matcher(regionOrigin); Matcher originPixelMatcher = PIXEL_COORDINATES.matcher(regionOrigin); if (originPercentageMatcher.matches()) { try { - position = Float.parseFloat(originPercentageMatcher.group(1)) / 100f; - line = Float.parseFloat(originPercentageMatcher.group(2)) / 100f; + position = + Float.parseFloat(Assertions.checkNotNull(originPercentageMatcher.group(1))) / 100f; + line = Float.parseFloat(Assertions.checkNotNull(originPercentageMatcher.group(2))) / 100f; } catch (NumberFormatException e) { Log.w(TAG, "Ignoring region with malformed origin: " + regionOrigin); return null; @@ -337,8 +353,8 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { return null; } try { - int width = Integer.parseInt(originPixelMatcher.group(1)); - int height = Integer.parseInt(originPixelMatcher.group(2)); + int width = Integer.parseInt(Assertions.checkNotNull(originPixelMatcher.group(1))); + int height = Integer.parseInt(Assertions.checkNotNull(originPixelMatcher.group(2))); // Convert pixel values to fractions. position = width / (float) ttsExtent.width; line = height / (float) ttsExtent.height; @@ -362,14 +378,17 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { float width; float height; + @Nullable String regionExtent = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_EXTENT); if (regionExtent != null) { Matcher extentPercentageMatcher = PERCENTAGE_COORDINATES.matcher(regionExtent); Matcher extentPixelMatcher = PIXEL_COORDINATES.matcher(regionExtent); if (extentPercentageMatcher.matches()) { try { - width = Float.parseFloat(extentPercentageMatcher.group(1)) / 100f; - height = Float.parseFloat(extentPercentageMatcher.group(2)) / 100f; + width = + Float.parseFloat(Assertions.checkNotNull(extentPercentageMatcher.group(1))) / 100f; + height = + Float.parseFloat(Assertions.checkNotNull(extentPercentageMatcher.group(2))) / 100f; } catch (NumberFormatException e) { Log.w(TAG, "Ignoring region with malformed extent: " + regionOrigin); return null; @@ -380,8 +399,8 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { return null; } try { - int extentWidth = Integer.parseInt(extentPixelMatcher.group(1)); - int extentHeight = Integer.parseInt(extentPixelMatcher.group(2)); + int extentWidth = Integer.parseInt(Assertions.checkNotNull(extentPixelMatcher.group(1))); + int extentHeight = Integer.parseInt(Assertions.checkNotNull(extentPixelMatcher.group(2))); // Convert pixel values to fractions. width = extentWidth / (float) ttsExtent.width; height = extentHeight / (float) ttsExtent.height; @@ -404,8 +423,9 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { } @Cue.AnchorType int lineAnchor = Cue.ANCHOR_TYPE_START; - String displayAlign = XmlPullParserUtil.getAttributeValue(xmlParser, - TtmlNode.ATTR_TTS_DISPLAY_ALIGN); + @Nullable + String displayAlign = + XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_DISPLAY_ALIGN); if (displayAlign != null) { switch (Util.toLowerInvariant(displayAlign)) { case "center": @@ -440,7 +460,8 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { return parentStyleIds.isEmpty() ? new String[0] : Util.split(parentStyleIds, "\\s+"); } - private TtmlStyle parseStyleAttributes(XmlPullParser parser, TtmlStyle style) { + @PolyNull + private TtmlStyle parseStyleAttributes(XmlPullParser parser, @PolyNull TtmlStyle style) { int attributeCount = parser.getAttributeCount(); for (int i = 0; i < attributeCount; i++) { String attributeValue = parser.getAttributeValue(i); @@ -527,21 +548,24 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { return style; } - private TtmlStyle createIfNull(TtmlStyle style) { + private TtmlStyle createIfNull(@Nullable TtmlStyle style) { return style == null ? new TtmlStyle() : style; } - private TtmlNode parseNode(XmlPullParser parser, TtmlNode parent, - Map regionMap, FrameAndTickRate frameAndTickRate) + private TtmlNode parseNode( + XmlPullParser parser, + @Nullable TtmlNode parent, + Map regionMap, + FrameAndTickRate frameAndTickRate) throws SubtitleDecoderException { long duration = C.TIME_UNSET; long startTime = C.TIME_UNSET; long endTime = C.TIME_UNSET; String regionId = TtmlNode.ANONYMOUS_REGION_ID; - String imageId = null; - String[] styleIds = null; + @Nullable String imageId = null; + @Nullable String[] styleIds = null; int attributeCount = parser.getAttributeCount(); - TtmlStyle style = parseStyleAttributes(parser, null); + @Nullable TtmlStyle style = parseStyleAttributes(parser, null); for (int i = 0; i < attributeCount; i++) { String attr = parser.getAttributeName(i); String value = parser.getAttributeValue(i); @@ -636,7 +660,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { } if (matcher.matches()) { - String unit = matcher.group(3); + String unit = Assertions.checkNotNull(matcher.group(3)); switch (unit) { case "px": out.setFontSizeUnit(TtmlStyle.FONT_SIZE_UNIT_PIXEL); @@ -650,7 +674,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { default: throw new SubtitleDecoderException("Invalid unit for fontSize: '" + unit + "'."); } - out.setFontSize(Float.valueOf(matcher.group(1))); + out.setFontSize(Float.parseFloat(Assertions.checkNotNull(matcher.group(1)))); } else { throw new SubtitleDecoderException("Invalid expression for fontSize: '" + expression + "'."); } @@ -671,18 +695,18 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { throws SubtitleDecoderException { Matcher matcher = CLOCK_TIME.matcher(time); if (matcher.matches()) { - String hours = matcher.group(1); + String hours = Assertions.checkNotNull(matcher.group(1)); double durationSeconds = Long.parseLong(hours) * 3600; - String minutes = matcher.group(2); + String minutes = Assertions.checkNotNull(matcher.group(2)); durationSeconds += Long.parseLong(minutes) * 60; - String seconds = matcher.group(3); + String seconds = Assertions.checkNotNull(matcher.group(3)); durationSeconds += Long.parseLong(seconds); - String fraction = matcher.group(4); + @Nullable String fraction = matcher.group(4); durationSeconds += (fraction != null) ? Double.parseDouble(fraction) : 0; - String frames = matcher.group(5); + @Nullable String frames = matcher.group(5); durationSeconds += (frames != null) ? Long.parseLong(frames) / frameAndTickRate.effectiveFrameRate : 0; - String subframes = matcher.group(6); + @Nullable String subframes = matcher.group(6); durationSeconds += (subframes != null) ? ((double) Long.parseLong(subframes)) / frameAndTickRate.subFrameRate / frameAndTickRate.effectiveFrameRate @@ -691,9 +715,9 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { } matcher = OFFSET_TIME.matcher(time); if (matcher.matches()) { - String timeValue = matcher.group(1); + String timeValue = Assertions.checkNotNull(matcher.group(1)); double offsetSeconds = Double.parseDouble(timeValue); - String unit = matcher.group(2); + String unit = Assertions.checkNotNull(matcher.group(2)); switch (unit) { case "h": offsetSeconds *= 3600; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java index b025a4e139..aee5b07632 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java @@ -31,6 +31,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; import java.util.TreeSet; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** * A package internal representation of TTML node. @@ -93,7 +94,7 @@ import java.util.TreeSet; private final HashMap nodeStartsByRegion; private final HashMap nodeEndsByRegion; - private List children; + @MonotonicNonNull private List children; public static TtmlNode buildTextNode(String text) { return new TtmlNode( @@ -196,6 +197,7 @@ import java.util.TreeSet; } } + @Nullable public String[] getStyleIds() { return styleIds; } @@ -217,7 +219,7 @@ import java.util.TreeSet; // Create image based cues. for (Pair regionImagePair : regionImageOutputs) { - String encodedBitmapData = imageMap.get(regionImagePair.second); + @Nullable String encodedBitmapData = imageMap.get(regionImagePair.second); if (encodedBitmapData == null) { // Image reference points to an invalid image. Do nothing. continue; @@ -225,7 +227,7 @@ import java.util.TreeSet; byte[] bitmapData = Base64.decode(encodedBitmapData, Base64.DEFAULT); Bitmap bitmap = BitmapFactory.decodeByteArray(bitmapData, /* offset= */ 0, bitmapData.length); - TtmlRegion region = regionMap.get(regionImagePair.first); + TtmlRegion region = Assertions.checkNotNull(regionMap.get(regionImagePair.first)); cues.add( new Cue.Builder() @@ -241,7 +243,7 @@ import java.util.TreeSet; // Create text based cues. for (Entry entry : regionTextOutputs.entrySet()) { - TtmlRegion region = regionMap.get(entry.getKey()); + TtmlRegion region = Assertions.checkNotNull(regionMap.get(entry.getKey())); cues.add( new Cue( cleanUpText(entry.getValue()), @@ -286,7 +288,7 @@ import java.util.TreeSet; String resolvedRegionId = ANONYMOUS_REGION_ID.equals(regionId) ? inheritedRegion : regionId; if (isTextNode && descendsPNode) { - getRegionOutput(resolvedRegionId, regionOutputs).append(text); + getRegionOutput(resolvedRegionId, regionOutputs).append(Assertions.checkNotNull(text)); } else if (TAG_BR.equals(tag) && descendsPNode) { getRegionOutput(resolvedRegionId, regionOutputs).append('\n'); } else if (isActive(timeUs)) { @@ -330,7 +332,7 @@ import java.util.TreeSet; int start = nodeStartsByRegion.containsKey(regionId) ? nodeStartsByRegion.get(regionId) : 0; int end = entry.getValue(); if (start != end) { - SpannableStringBuilder regionOutput = regionOutputs.get(regionId); + SpannableStringBuilder regionOutput = Assertions.checkNotNull(regionOutputs.get(regionId)); applyStyleToOutput(globalStyles, regionOutput, start, end); } } @@ -344,7 +346,7 @@ import java.util.TreeSet; SpannableStringBuilder regionOutput, int start, int end) { - TtmlStyle resolvedStyle = TtmlRenderUtil.resolveStyle(style, styleIds, globalStyles); + @Nullable TtmlStyle resolvedStyle = TtmlRenderUtil.resolveStyle(style, styleIds, globalStyles); if (resolvedStyle != null) { TtmlRenderUtil.applyStylesToSpan(regionOutput, start, end, resolvedStyle); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlRenderUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlRenderUtil.java index 25395431de..e1f3d5e887 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlRenderUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlRenderUtil.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.text.ttml; +import android.text.Layout.Alignment; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.style.AbsoluteSizeSpan; @@ -26,6 +27,7 @@ import android.text.style.StrikethroughSpan; import android.text.style.StyleSpan; import android.text.style.TypefaceSpan; import android.text.style.UnderlineSpan; +import androidx.annotation.Nullable; import com.google.android.exoplayer2.text.SpanUtil; import java.util.Map; @@ -34,30 +36,35 @@ import java.util.Map; */ /* package */ final class TtmlRenderUtil { - public static TtmlStyle resolveStyle(TtmlStyle style, String[] styleIds, - Map globalStyles) { - if (style == null && styleIds == null) { - // No styles at all. - return null; - } else if (style == null && styleIds.length == 1) { - // Only one single referential style present. - return globalStyles.get(styleIds[0]); - } else if (style == null && styleIds.length > 1) { - // Only multiple referential styles present. - TtmlStyle chainedStyle = new TtmlStyle(); - for (String id : styleIds) { - chainedStyle.chain(globalStyles.get(id)); + @Nullable + public static TtmlStyle resolveStyle( + @Nullable TtmlStyle style, @Nullable String[] styleIds, Map globalStyles) { + if (style == null) { + if (styleIds == null) { + // No styles at all. + return null; + } else if (styleIds.length == 1) { + // Only one single referential style present. + return globalStyles.get(styleIds[0]); + } else if (styleIds.length > 1) { + // Only multiple referential styles present. + TtmlStyle chainedStyle = new TtmlStyle(); + for (String id : styleIds) { + chainedStyle.chain(globalStyles.get(id)); + } + return chainedStyle; } - return chainedStyle; - } else if (style != null && styleIds != null && styleIds.length == 1) { - // Merge a single referential style into inline style. - return style.chain(globalStyles.get(styleIds[0])); - } else if (style != null && styleIds != null && styleIds.length > 1) { - // Merge multiple referential styles into inline style. - for (String id : styleIds) { - style.chain(globalStyles.get(id)); + } else /* style != null */ { + if (styleIds != null && styleIds.length == 1) { + // Merge a single referential style into inline style. + return style.chain(globalStyles.get(styleIds[0])); + } else if (styleIds != null && styleIds.length > 1) { + // Merge multiple referential styles into inline style. + for (String id : styleIds) { + style.chain(globalStyles.get(id)); + } + return style; } - return style; } // Only inline styles available. return style; @@ -100,10 +107,11 @@ import java.util.Map; end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } - if (style.getTextAlign() != null) { + @Nullable Alignment textAlign = style.getTextAlign(); + if (textAlign != null) { SpanUtil.addOrReplaceSpan( builder, - new AlignmentSpan.Standard(style.getTextAlign()), + new AlignmentSpan.Standard(textAlign), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlStyle.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlStyle.java index 2ccbe7d85b..0c7c90afc5 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlStyle.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlStyle.java @@ -18,9 +18,11 @@ package com.google.android.exoplayer2.text.ttml; import android.graphics.Typeface; import android.text.Layout; import androidx.annotation.IntDef; +import androidx.annotation.Nullable; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** * Style object of a TtmlNode @@ -58,7 +60,7 @@ import java.lang.annotation.RetentionPolicy; private static final int OFF = 0; private static final int ON = 1; - private String fontFamily; + private @MonotonicNonNull String fontFamily; private int fontColor; private boolean hasFontColor; private int backgroundColor; @@ -69,8 +71,8 @@ import java.lang.annotation.RetentionPolicy; @OptionalBoolean private int italic; @FontSizeUnit private int fontSizeUnit; private float fontSize; - private String id; - private Layout.Alignment textAlign; + private @MonotonicNonNull String id; + private Layout.@MonotonicNonNull Alignment textAlign; public TtmlStyle() { linethrough = UNSPECIFIED; @@ -122,6 +124,7 @@ import java.lang.annotation.RetentionPolicy; return this; } + @Nullable public String getFontFamily() { return fontFamily; } @@ -171,7 +174,7 @@ import java.lang.annotation.RetentionPolicy; * * @param ancestor the referential style to inherit from */ - public TtmlStyle chain(TtmlStyle ancestor) { + public TtmlStyle chain(@Nullable TtmlStyle ancestor) { return inherit(ancestor, true); } @@ -182,11 +185,11 @@ import java.lang.annotation.RetentionPolicy; * * @param ancestor the ancestor style to inherit from */ - public TtmlStyle inherit(TtmlStyle ancestor) { + public TtmlStyle inherit(@Nullable TtmlStyle ancestor) { return inherit(ancestor, false); } - private TtmlStyle inherit(TtmlStyle ancestor, boolean chaining) { + private TtmlStyle inherit(@Nullable TtmlStyle ancestor, boolean chaining) { if (ancestor != null) { if (!hasFontColor && ancestor.hasFontColor) { setFontColor(ancestor.fontColor); @@ -197,7 +200,7 @@ import java.lang.annotation.RetentionPolicy; if (italic == UNSPECIFIED) { italic = ancestor.italic; } - if (fontFamily == null) { + if (fontFamily == null && ancestor.fontFamily != null) { fontFamily = ancestor.fontFamily; } if (linethrough == UNSPECIFIED) { @@ -206,7 +209,7 @@ import java.lang.annotation.RetentionPolicy; if (underline == UNSPECIFIED) { underline = ancestor.underline; } - if (textAlign == null) { + if (textAlign == null && ancestor.textAlign != null) { textAlign = ancestor.textAlign; } if (fontSizeUnit == UNSPECIFIED) { @@ -226,10 +229,12 @@ import java.lang.annotation.RetentionPolicy; return this; } + @Nullable public String getId() { return id; } + @Nullable public Layout.Alignment getTextAlign() { return textAlign; }