Commit 6603c6bb by Oliver Woodman

Merge pull request #8720 from dlafayet:tts-shear-block

PiperOrigin-RevId: 365998615
parents dc0ee758 475b1fe3
...@@ -20,8 +20,8 @@ ...@@ -20,8 +20,8 @@
* Add group setting to `PlayerNotificationManager`. * Add group setting to `PlayerNotificationManager`.
* Fix `StyledPlayerView` scrubber not reappearing correctly in some cases * Fix `StyledPlayerView` scrubber not reappearing correctly in some cases
([#8646](https://github.com/google/ExoPlayer/issues/8646)). ([#8646](https://github.com/google/ExoPlayer/issues/8646)).
* Fix measurement of `StyledPlayerView` and `StyledPlayerControlView` * Fix measurement of `StyledPlayerView` and `StyledPlayerControlView` when
when `wrap_content` is used `wrap_content` is used
([#8726](https://github.com/google/ExoPlayer/issues/8726)). ([#8726](https://github.com/google/ExoPlayer/issues/8726)).
* Audio: * Audio:
* Report unexpected discontinuities in * Report unexpected discontinuities in
...@@ -101,6 +101,7 @@ ...@@ -101,6 +101,7 @@
* Fix CEA-708 priority handling to sort cues in the order defined by the * Fix CEA-708 priority handling to sort cues in the order defined by the
spec ([#8704](https://github.com/google/ExoPlayer/issues/8704)). spec ([#8704](https://github.com/google/ExoPlayer/issues/8704)).
* Support TTML `textEmphasis` attributes, used for Japanese boutens. * Support TTML `textEmphasis` attributes, used for Japanese boutens.
* Support TTML `shear` attributes.
* MediaSession extension: Remove dependency to core module and rely on common * MediaSession extension: Remove dependency to core module and rely on common
only. The `TimelineQueueEditor` uses a new `MediaDescriptionConverter` for only. The `TimelineQueueEditor` uses a new `MediaDescriptionConverter` for
this purpose and does not rely on the `ConcatenatingMediaSource` anymore. this purpose and does not rely on the `ConcatenatingMediaSource` anymore.
......
...@@ -270,6 +270,12 @@ public final class Cue { ...@@ -270,6 +270,12 @@ public final class Cue {
public final @VerticalType int verticalType; public final @VerticalType int verticalType;
/** /**
* The shear angle in degrees to be applied to this Cue, expressed in graphics coordinates. This
* results in a skew transform for the block along the inline progression axis.
*/
public final float shearDegrees;
/**
* Creates a text cue whose {@link #textAlignment} is null, whose type parameters are set to * Creates a text cue whose {@link #textAlignment} is null, whose type parameters are set to
* {@link #TYPE_UNSET} and whose dimension parameters are set to {@link #DIMEN_UNSET}. * {@link #TYPE_UNSET} and whose dimension parameters are set to {@link #DIMEN_UNSET}.
* *
...@@ -370,7 +376,8 @@ public final class Cue { ...@@ -370,7 +376,8 @@ public final class Cue {
/* bitmapHeight= */ DIMEN_UNSET, /* bitmapHeight= */ DIMEN_UNSET,
/* windowColorSet= */ false, /* windowColorSet= */ false,
/* windowColor= */ Color.BLACK, /* windowColor= */ Color.BLACK,
/* verticalType= */ TYPE_UNSET); /* verticalType= */ TYPE_UNSET,
/* shearDegrees= */ 0f);
} }
/** /**
...@@ -415,7 +422,8 @@ public final class Cue { ...@@ -415,7 +422,8 @@ public final class Cue {
/* bitmapHeight= */ DIMEN_UNSET, /* bitmapHeight= */ DIMEN_UNSET,
windowColorSet, windowColorSet,
windowColor, windowColor,
/* verticalType= */ TYPE_UNSET); /* verticalType= */ TYPE_UNSET,
/* shearDegrees= */ 0f);
} }
private Cue( private Cue(
...@@ -433,7 +441,8 @@ public final class Cue { ...@@ -433,7 +441,8 @@ public final class Cue {
float bitmapHeight, float bitmapHeight,
boolean windowColorSet, boolean windowColorSet,
int windowColor, int windowColor,
@VerticalType int verticalType) { @VerticalType int verticalType,
float shearDegrees) {
// Exactly one of text or bitmap should be set. // Exactly one of text or bitmap should be set.
if (text == null) { if (text == null) {
Assertions.checkNotNull(bitmap); Assertions.checkNotNull(bitmap);
...@@ -455,6 +464,7 @@ public final class Cue { ...@@ -455,6 +464,7 @@ public final class Cue {
this.textSizeType = textSizeType; this.textSizeType = textSizeType;
this.textSize = textSize; this.textSize = textSize;
this.verticalType = verticalType; this.verticalType = verticalType;
this.shearDegrees = shearDegrees;
} }
/** Returns a new {@link Cue.Builder} initialized with the same values as this Cue. */ /** Returns a new {@link Cue.Builder} initialized with the same values as this Cue. */
...@@ -479,6 +489,7 @@ public final class Cue { ...@@ -479,6 +489,7 @@ public final class Cue {
private boolean windowColorSet; private boolean windowColorSet;
@ColorInt private int windowColor; @ColorInt private int windowColor;
@VerticalType private int verticalType; @VerticalType private int verticalType;
private float shearDegrees;
public Builder() { public Builder() {
text = null; text = null;
...@@ -514,6 +525,7 @@ public final class Cue { ...@@ -514,6 +525,7 @@ public final class Cue {
windowColorSet = cue.windowColorSet; windowColorSet = cue.windowColorSet;
windowColor = cue.windowColor; windowColor = cue.windowColor;
verticalType = cue.verticalType; verticalType = cue.verticalType;
shearDegrees = cue.shearDegrees;
} }
/** /**
...@@ -794,6 +806,12 @@ public final class Cue { ...@@ -794,6 +806,12 @@ public final class Cue {
return this; return this;
} }
/** Sets the shear angle for this Cue. */
public Builder setShearDegrees(float shearDegrees) {
this.shearDegrees = shearDegrees;
return this;
}
/** /**
* Gets the vertical formatting for this Cue. * Gets the vertical formatting for this Cue.
* *
...@@ -821,7 +839,8 @@ public final class Cue { ...@@ -821,7 +839,8 @@ public final class Cue {
bitmapHeight, bitmapHeight,
windowColorSet, windowColorSet,
windowColor, windowColor,
verticalType); verticalType,
shearDegrees);
} }
} }
} }
...@@ -15,6 +15,9 @@ ...@@ -15,6 +15,9 @@
*/ */
package com.google.android.exoplayer2.text.ttml; package com.google.android.exoplayer2.text.ttml;
import static java.lang.Math.max;
import static java.lang.Math.min;
import android.text.Layout; import android.text.Layout;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
...@@ -81,7 +84,8 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { ...@@ -81,7 +84,8 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
private static final Pattern OFFSET_TIME = private static final Pattern OFFSET_TIME =
Pattern.compile("^([0-9]+(?:\\.[0-9]+)?)(h|m|s|ms|f|t)$"); 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|%)$"); private static final Pattern FONT_SIZE = Pattern.compile("^(([0-9]*.)?[0-9]+)(px|em|%)$");
private static final Pattern PERCENTAGE_COORDINATES = static final Pattern SIGNED_PERCENTAGE = Pattern.compile("^([-+]?\\d+\\.?\\d*?)%$");
static final Pattern PERCENTAGE_COORDINATES =
Pattern.compile("^(\\d+\\.?\\d*?)% (\\d+\\.?\\d*?)%$"); Pattern.compile("^(\\d+\\.?\\d*?)% (\\d+\\.?\\d*?)%$");
private static final Pattern PIXEL_COORDINATES = private static final Pattern PIXEL_COORDINATES =
Pattern.compile("^(\\d+\\.?\\d*?)px (\\d+\\.?\\d*?)px$"); Pattern.compile("^(\\d+\\.?\\d*?)px (\\d+\\.?\\d*?)px$");
...@@ -614,6 +618,9 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { ...@@ -614,6 +618,9 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
createIfNull(style) createIfNull(style)
.setTextEmphasis(TextEmphasis.parse(Util.toLowerInvariant(attributeValue))); .setTextEmphasis(TextEmphasis.parse(Util.toLowerInvariant(attributeValue)));
break; break;
case TtmlNode.ATTR_TTS_SHEAR:
style = createIfNull(style).setShearPercentage(parseShear(attributeValue));
break;
default: default:
// ignore // ignore
break; break;
...@@ -756,10 +763,35 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { ...@@ -756,10 +763,35 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
} }
/** /**
* Returns the parsed shear percentage (between -100.0 and +100.0 inclusive), or {@link
* TtmlStyle#UNSPECIFIED_SHEAR} if parsing failed.
*/
private static float parseShear(String expression) {
Matcher matcher = SIGNED_PERCENTAGE.matcher(expression);
if (!matcher.matches()) {
Log.w(TAG, "Invalid value for shear: " + expression);
return TtmlStyle.UNSPECIFIED_SHEAR;
}
try {
String percentage = Assertions.checkNotNull(matcher.group(1));
float value = Float.parseFloat(percentage);
// https://www.w3.org/TR/2018/REC-ttml2-20181108/#semantics-style-procedures-shear
// If the absolute value of the specified percentage is greater than 100%, then it must be
// interpreted as if 100% were specified with the appropriate sign.
value = max(-100f, value);
value = min(100f, value);
return value;
} catch (NumberFormatException e) {
Log.w(TAG, "Failed to parse shear: " + expression, e);
return TtmlStyle.UNSPECIFIED_SHEAR;
}
}
/**
* Parses a time expression, returning the parsed timestamp. * Parses a time expression, returning the parsed timestamp.
* <p> *
* For the format of a time expression, see: * <p>For the format of a time expression, see: <a
* <a href="http://www.w3.org/TR/ttaf1-dfxp/#timing-value-timeExpression">timeExpression</a> * href="http://www.w3.org/TR/ttaf1-dfxp/#timing-value-timeExpression">timeExpression</a>
* *
* @param time A string that includes the time expression. * @param time A string that includes the time expression.
* @param frameAndTickRate The effective frame and tick rates of the stream. * @param frameAndTickRate The effective frame and tick rates of the stream.
......
...@@ -71,6 +71,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -71,6 +71,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
public static final String ATTR_TTS_TEXT_COMBINE = "textCombine"; public static final String ATTR_TTS_TEXT_COMBINE = "textCombine";
public static final String ATTR_TTS_TEXT_EMPHASIS = "textEmphasis"; public static final String ATTR_TTS_TEXT_EMPHASIS = "textEmphasis";
public static final String ATTR_TTS_WRITING_MODE = "writingMode"; public static final String ATTR_TTS_WRITING_MODE = "writingMode";
public static final String ATTR_TTS_SHEAR = "shear";
// Values for ruby // Values for ruby
public static final String RUBY_CONTAINER = "container"; public static final String RUBY_CONTAINER = "container";
...@@ -408,6 +409,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -408,6 +409,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
if (resolvedStyle != null) { if (resolvedStyle != null) {
TtmlRenderUtil.applyStylesToSpan( TtmlRenderUtil.applyStylesToSpan(
text, start, end, resolvedStyle, parent, globalStyles, verticalType); text, start, end, resolvedStyle, parent, globalStyles, verticalType);
if (resolvedStyle.getShearPercentage() != TtmlStyle.UNSPECIFIED_SHEAR && TAG_P.equals(tag)) {
// Shear style should only be applied to P nodes
// https://www.w3.org/TR/2018/REC-ttml2-20181108/#style-attribute-shear
// The spec doesn't specify the coordinate system to use for block shear
// however the spec shows examples of how different values are expected to be rendered.
// See: https://www.w3.org/TR/2018/REC-ttml2-20181108/#style-attribute-shear
// https://www.w3.org/TR/2018/REC-ttml2-20181108/#style-attribute-fontShear
// This maps the shear percentage to shear angle in graphics coordinates
regionOutput.setShearDegrees((resolvedStyle.getShearPercentage() * -90) / 100);
}
regionOutput.setTextAlignment(resolvedStyle.getTextAlign()); regionOutput.setTextAlignment(resolvedStyle.getTextAlign());
} }
} }
......
...@@ -30,6 +30,7 @@ import java.lang.annotation.RetentionPolicy; ...@@ -30,6 +30,7 @@ import java.lang.annotation.RetentionPolicy;
/* package */ final class TtmlStyle { /* package */ final class TtmlStyle {
public static final int UNSPECIFIED = -1; public static final int UNSPECIFIED = -1;
public static final float UNSPECIFIED_SHEAR = Float.MAX_VALUE;
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
...@@ -87,6 +88,7 @@ import java.lang.annotation.RetentionPolicy; ...@@ -87,6 +88,7 @@ import java.lang.annotation.RetentionPolicy;
@Nullable private Layout.Alignment textAlign; @Nullable private Layout.Alignment textAlign;
@OptionalBoolean private int textCombine; @OptionalBoolean private int textCombine;
@Nullable private TextEmphasis textEmphasis; @Nullable private TextEmphasis textEmphasis;
private float shearPercentage;
public TtmlStyle() { public TtmlStyle() {
linethrough = UNSPECIFIED; linethrough = UNSPECIFIED;
...@@ -97,6 +99,7 @@ import java.lang.annotation.RetentionPolicy; ...@@ -97,6 +99,7 @@ import java.lang.annotation.RetentionPolicy;
rubyType = UNSPECIFIED; rubyType = UNSPECIFIED;
rubyPosition = TextAnnotation.POSITION_UNKNOWN; rubyPosition = TextAnnotation.POSITION_UNKNOWN;
textCombine = UNSPECIFIED; textCombine = UNSPECIFIED;
shearPercentage = UNSPECIFIED_SHEAR;
} }
/** /**
...@@ -185,6 +188,15 @@ import java.lang.annotation.RetentionPolicy; ...@@ -185,6 +188,15 @@ import java.lang.annotation.RetentionPolicy;
return hasBackgroundColor; return hasBackgroundColor;
} }
public TtmlStyle setShearPercentage(float shearPercentage) {
this.shearPercentage = shearPercentage;
return this;
}
public float getShearPercentage() {
return shearPercentage;
}
/** /**
* Chains this style to referential style. Local properties which are already set are never * Chains this style to referential style. Local properties which are already set are never
* overridden. * overridden.
...@@ -242,6 +254,9 @@ import java.lang.annotation.RetentionPolicy; ...@@ -242,6 +254,9 @@ import java.lang.annotation.RetentionPolicy;
if (textEmphasis == null) { if (textEmphasis == null) {
textEmphasis = ancestor.textEmphasis; textEmphasis = ancestor.textEmphasis;
} }
if (shearPercentage == UNSPECIFIED_SHEAR) {
shearPercentage = ancestor.shearPercentage;
}
// attributes not inherited as of http://www.w3.org/TR/ttml1/ // attributes not inherited as of http://www.w3.org/TR/ttml1/
if (chaining && !hasBackgroundColor && ancestor.hasBackgroundColor) { if (chaining && !hasBackgroundColor && ancestor.hasBackgroundColor) {
setBackgroundColor(ancestor.backgroundColor); setBackgroundColor(ancestor.backgroundColor);
......
...@@ -45,6 +45,7 @@ public class CueTest { ...@@ -45,6 +45,7 @@ public class CueTest {
.setSize(0.8f) .setSize(0.8f)
.setWindowColor(Color.CYAN) .setWindowColor(Color.CYAN)
.setVerticalType(Cue.VERTICAL_TYPE_RL) .setVerticalType(Cue.VERTICAL_TYPE_RL)
.setShearDegrees(-15f)
.build(); .build();
Cue modifiedCue = cue.buildUpon().build(); Cue modifiedCue = cue.buildUpon().build();
...@@ -61,6 +62,7 @@ public class CueTest { ...@@ -61,6 +62,7 @@ public class CueTest {
assertThat(cue.windowColor).isEqualTo(Color.CYAN); assertThat(cue.windowColor).isEqualTo(Color.CYAN);
assertThat(cue.windowColorSet).isTrue(); assertThat(cue.windowColorSet).isTrue();
assertThat(cue.verticalType).isEqualTo(Cue.VERTICAL_TYPE_RL); assertThat(cue.verticalType).isEqualTo(Cue.VERTICAL_TYPE_RL);
assertThat(cue.shearDegrees).isEqualTo(-15f);
assertThat(modifiedCue.text).isSameInstanceAs(cue.text); assertThat(modifiedCue.text).isSameInstanceAs(cue.text);
assertThat(modifiedCue.textAlignment).isEqualTo(cue.textAlignment); assertThat(modifiedCue.textAlignment).isEqualTo(cue.textAlignment);
...@@ -74,6 +76,7 @@ public class CueTest { ...@@ -74,6 +76,7 @@ public class CueTest {
assertThat(modifiedCue.windowColor).isEqualTo(cue.windowColor); assertThat(modifiedCue.windowColor).isEqualTo(cue.windowColor);
assertThat(modifiedCue.windowColorSet).isEqualTo(cue.windowColorSet); assertThat(modifiedCue.windowColorSet).isEqualTo(cue.windowColorSet);
assertThat(modifiedCue.verticalType).isEqualTo(cue.verticalType); assertThat(modifiedCue.verticalType).isEqualTo(cue.verticalType);
assertThat(modifiedCue.shearDegrees).isEqualTo(cue.shearDegrees);
} }
@Test @Test
......
...@@ -69,6 +69,7 @@ public final class TtmlDecoderTest { ...@@ -69,6 +69,7 @@ public final class TtmlDecoderTest {
private static final String TEXT_COMBINE_FILE = "media/ttml/text_combine.xml"; private static final String TEXT_COMBINE_FILE = "media/ttml/text_combine.xml";
private static final String RUBIES_FILE = "media/ttml/rubies.xml"; private static final String RUBIES_FILE = "media/ttml/rubies.xml";
private static final String TEXT_EMPHASIS_FILE = "media/ttml/text_emphasis.xml"; private static final String TEXT_EMPHASIS_FILE = "media/ttml/text_emphasis.xml";
private static final String SHEAR_FILE = "media/ttml/shear.xml";
@Test @Test
public void inlineAttributes() throws IOException, SubtitleDecoderException { public void inlineAttributes() throws IOException, SubtitleDecoderException {
...@@ -816,6 +817,35 @@ public final class TtmlDecoderTest { ...@@ -816,6 +817,35 @@ public final class TtmlDecoderTest {
TextAnnotation.POSITION_BEFORE); TextAnnotation.POSITION_BEFORE);
} }
@Test
public void shear() throws IOException, SubtitleDecoderException {
TtmlSubtitle subtitle = getSubtitle(SHEAR_FILE);
Cue firstCue = getOnlyCueAtTimeUs(subtitle, 10_000_000);
assertThat(firstCue.shearDegrees).isZero();
Cue secondCue = getOnlyCueAtTimeUs(subtitle, 20_000_000);
assertThat(secondCue.shearDegrees).isWithin(0.01f).of(-15f);
Cue thirdCue = getOnlyCueAtTimeUs(subtitle, 30_000_000);
assertThat(thirdCue.shearDegrees).isWithin(0.01f).of(15f);
Cue fourthCue = getOnlyCueAtTimeUs(subtitle, 40_000_000);
assertThat(fourthCue.shearDegrees).isWithin(0.01f).of(-15f);
Cue fifthCue = getOnlyCueAtTimeUs(subtitle, 50_000_000);
assertThat(fifthCue.shearDegrees).isWithin(0.01f).of(-22.5f);
Cue sixthCue = getOnlyCueAtTimeUs(subtitle, 60_000_000);
assertThat(sixthCue.shearDegrees).isWithin(0.01f).of(0f);
Cue seventhCue = getOnlyCueAtTimeUs(subtitle, 70_000_000);
assertThat(seventhCue.shearDegrees).isWithin(0.01f).of(-90f);
Cue eighthCue = getOnlyCueAtTimeUs(subtitle, 80_000_000);
assertThat(eighthCue.shearDegrees).isWithin(0.01f).of(90f);
}
private static Spanned getOnlyCueTextAtTimeUs(Subtitle subtitle, long timeUs) { private static Spanned getOnlyCueTextAtTimeUs(Subtitle subtitle, long timeUs) {
Cue cue = getOnlyCueAtTimeUs(subtitle, timeUs); Cue cue = getOnlyCueAtTimeUs(subtitle, timeUs);
assertThat(cue.text).isInstanceOf(Spanned.class); assertThat(cue.text).isInstanceOf(Spanned.class);
......
...@@ -49,6 +49,7 @@ public final class TtmlStyleTest { ...@@ -49,6 +49,7 @@ public final class TtmlStyleTest {
private static final Layout.Alignment TEXT_ALIGN = Layout.Alignment.ALIGN_CENTER; private static final Layout.Alignment TEXT_ALIGN = Layout.Alignment.ALIGN_CENTER;
private static final boolean TEXT_COMBINE = true; private static final boolean TEXT_COMBINE = true;
public static final String TEXT_EMPHASIS_STYLE = "dot before"; public static final String TEXT_EMPHASIS_STYLE = "dot before";
public static final float SHEAR_PERCENTAGE = 16f;
private final TtmlStyle populatedStyle = private final TtmlStyle populatedStyle =
new TtmlStyle() new TtmlStyle()
...@@ -66,7 +67,8 @@ public final class TtmlStyleTest { ...@@ -66,7 +67,8 @@ public final class TtmlStyleTest {
.setRubyPosition(RUBY_POSITION) .setRubyPosition(RUBY_POSITION)
.setTextAlign(TEXT_ALIGN) .setTextAlign(TEXT_ALIGN)
.setTextCombine(TEXT_COMBINE) .setTextCombine(TEXT_COMBINE)
.setTextEmphasis(TextEmphasis.parse(TEXT_EMPHASIS_STYLE)); .setTextEmphasis(TextEmphasis.parse(TEXT_EMPHASIS_STYLE))
.setShearPercentage(SHEAR_PERCENTAGE);
@Test @Test
public void inheritStyle() { public void inheritStyle() {
...@@ -94,6 +96,7 @@ public final class TtmlStyleTest { ...@@ -94,6 +96,7 @@ public final class TtmlStyleTest {
assertThat(style.getTextEmphasis().markShape).isEqualTo(TextEmphasisSpan.MARK_SHAPE_DOT); assertThat(style.getTextEmphasis().markShape).isEqualTo(TextEmphasisSpan.MARK_SHAPE_DOT);
assertThat(style.getTextEmphasis().markFill).isEqualTo(TextEmphasisSpan.MARK_FILL_FILLED); assertThat(style.getTextEmphasis().markFill).isEqualTo(TextEmphasisSpan.MARK_FILL_FILLED);
assertThat(style.getTextEmphasis().position).isEqualTo(POSITION_BEFORE); assertThat(style.getTextEmphasis().position).isEqualTo(POSITION_BEFORE);
assertThat(style.getShearPercentage()).isEqualTo(SHEAR_PERCENTAGE);
} }
@Test @Test
...@@ -121,6 +124,7 @@ public final class TtmlStyleTest { ...@@ -121,6 +124,7 @@ public final class TtmlStyleTest {
assertThat(style.getTextEmphasis().markShape).isEqualTo(TextEmphasisSpan.MARK_SHAPE_DOT); assertThat(style.getTextEmphasis().markShape).isEqualTo(TextEmphasisSpan.MARK_SHAPE_DOT);
assertThat(style.getTextEmphasis().markFill).isEqualTo(TextEmphasisSpan.MARK_FILL_FILLED); assertThat(style.getTextEmphasis().markFill).isEqualTo(TextEmphasisSpan.MARK_FILL_FILLED);
assertThat(style.getTextEmphasis().position).isEqualTo(POSITION_BEFORE); assertThat(style.getTextEmphasis().position).isEqualTo(POSITION_BEFORE);
assertThat(style.getShearPercentage()).isEqualTo(SHEAR_PERCENTAGE);
} }
@Test @Test
...@@ -267,4 +271,16 @@ public final class TtmlStyleTest { ...@@ -267,4 +271,16 @@ public final class TtmlStyleTest {
assertThat(style.getTextEmphasis().markFill).isEqualTo(TextEmphasisSpan.MARK_FILL_OPEN); assertThat(style.getTextEmphasis().markFill).isEqualTo(TextEmphasisSpan.MARK_FILL_OPEN);
assertThat(style.getTextEmphasis().position).isEqualTo(TextAnnotation.POSITION_AFTER); assertThat(style.getTextEmphasis().position).isEqualTo(TextAnnotation.POSITION_AFTER);
} }
@Test
public void shear() {
TtmlStyle style = new TtmlStyle();
assertThat(style.getShearPercentage()).isEqualTo(TtmlStyle.UNSPECIFIED_SHEAR);
style.setShearPercentage(101f);
assertThat(style.getShearPercentage()).isEqualTo(101f);
style.setShearPercentage(-200f);
assertThat(style.getShearPercentage()).isEqualTo(-200f);
style.setShearPercentage(0.1f);
assertThat(style.getShearPercentage()).isEqualTo(0.1f);
}
} }
...@@ -286,7 +286,8 @@ import java.util.Map; ...@@ -286,7 +286,8 @@ import java.util.Map;
+ "writing-mode:%s;" + "writing-mode:%s;"
+ "font-size:%s;" + "font-size:%s;"
+ "background-color:%s;" + "background-color:%s;"
+ "transform:translate(%s%%,%s%%);" + "transform:translate(%s%%,%s%%)"
+ "%s;"
+ "'>", + "'>",
/* z-index */ i, /* z-index */ i,
positionProperty, positionProperty,
...@@ -300,7 +301,8 @@ import java.util.Map; ...@@ -300,7 +301,8 @@ import java.util.Map;
cueTextSizeCssPx, cueTextSizeCssPx,
windowCssColor, windowCssColor,
horizontalTranslatePercent, horizontalTranslatePercent,
verticalTranslatePercent)) verticalTranslatePercent,
getBlockShearTransformFunction(cue)))
.append(Util.formatInvariant("<span class='%s'>", DEFAULT_BACKGROUND_CSS_CLASS)) .append(Util.formatInvariant("<span class='%s'>", DEFAULT_BACKGROUND_CSS_CLASS))
.append(htmlAndCss.html) .append(htmlAndCss.html)
.append("</span>") .append("</span>")
...@@ -322,6 +324,17 @@ import java.util.Map; ...@@ -322,6 +324,17 @@ import java.util.Map;
"base64"); "base64");
} }
private static String getBlockShearTransformFunction(Cue cue) {
if (cue.shearDegrees != 0.0f) {
String direction =
(cue.verticalType == Cue.VERTICAL_TYPE_LR || cue.verticalType == Cue.VERTICAL_TYPE_RL)
? "skewY"
: "skewX";
return Util.formatInvariant("%s(%.2fdeg)", direction, cue.shearDegrees);
}
return "";
}
/** /**
* Converts a text size to a CSS px value. * Converts a text size to a CSS px value.
* *
......
<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:shear="0%">0%</p>
</div>
<div>
<p begin="20s" end="28s" tts:shear="16.67%">16.67%</p>
</div>
<div>
<p begin="30s" end="38s" tts:shear="-16.67%">-16.67%</p>
</div>
<div>
<p begin="40s" end="48s" tts:shear="+16.67%">+16.67%</p>
</div>
<div>
<p begin="50s" end="58s" tts:shear="+25%">+25%</p>
</div>
<div>
<p begin="60s" end="68s" tts:shear="Invalid">Invalid</p>
</div>
<div>
<p begin="70s" end="78s" tts:shear="101.01%">100.01%</p>
</div>
<div>
<p begin="80s" end="88s" tts:shear="-101.1%">-101.1%</p>
</div>
</body>
</tt>
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