Merge pull request #8653 from dlafayet:textemphasis
PiperOrigin-RevId: 364363882
This commit is contained in:
commit
6c688891e3
@ -71,6 +71,7 @@
|
|||||||
[#8456](https://github.com/google/ExoPlayer/issues/8456)).
|
[#8456](https://github.com/google/ExoPlayer/issues/8456)).
|
||||||
* 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.
|
||||||
* 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.
|
||||||
|
@ -16,12 +16,6 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.text.span;
|
package com.google.android.exoplayer2.text.span;
|
||||||
|
|
||||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
|
||||||
|
|
||||||
import androidx.annotation.IntDef;
|
|
||||||
import java.lang.annotation.Documented;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A styling span for ruby text.
|
* A styling span for ruby text.
|
||||||
*
|
*
|
||||||
@ -38,48 +32,13 @@ import java.lang.annotation.Retention;
|
|||||||
// rubies (e.g. HTML <rp> tag).
|
// rubies (e.g. HTML <rp> tag).
|
||||||
public final class RubySpan {
|
public final class RubySpan {
|
||||||
|
|
||||||
/** The ruby position is unknown. */
|
|
||||||
public static final int POSITION_UNKNOWN = -1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The ruby text should be positioned above the base text.
|
|
||||||
*
|
|
||||||
* <p>For vertical text it should be positioned to the right, same as CSS's <a
|
|
||||||
* href="https://developer.mozilla.org/en-US/docs/Web/CSS/ruby-position">ruby-position</a>.
|
|
||||||
*/
|
|
||||||
public static final int POSITION_OVER = 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The ruby text should be positioned below the base text.
|
|
||||||
*
|
|
||||||
* <p>For vertical text it should be positioned to the left, same as CSS's <a
|
|
||||||
* href="https://developer.mozilla.org/en-US/docs/Web/CSS/ruby-position">ruby-position</a>.
|
|
||||||
*/
|
|
||||||
public static final int POSITION_UNDER = 2;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The possible positions of the ruby text relative to the base text.
|
|
||||||
*
|
|
||||||
* <p>One of:
|
|
||||||
*
|
|
||||||
* <ul>
|
|
||||||
* <li>{@link #POSITION_UNKNOWN}
|
|
||||||
* <li>{@link #POSITION_OVER}
|
|
||||||
* <li>{@link #POSITION_UNDER}
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
@Documented
|
|
||||||
@Retention(SOURCE)
|
|
||||||
@IntDef({POSITION_UNKNOWN, POSITION_OVER, POSITION_UNDER})
|
|
||||||
public @interface Position {}
|
|
||||||
|
|
||||||
/** The ruby text, i.e. the smaller explanatory characters. */
|
/** The ruby text, i.e. the smaller explanatory characters. */
|
||||||
public final String rubyText;
|
public final String rubyText;
|
||||||
|
|
||||||
/** The position of the ruby text relative to the base text. */
|
/** The position of the ruby text relative to the base text. */
|
||||||
@Position public final int position;
|
@TextAnnotation.Position public final int position;
|
||||||
|
|
||||||
public RubySpan(String rubyText, @Position int position) {
|
public RubySpan(String rubyText, @TextAnnotation.Position int position) {
|
||||||
this.rubyText = rubyText;
|
this.rubyText = rubyText;
|
||||||
this.position = position;
|
this.position = position;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 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.exoplayer2.text.span;
|
||||||
|
|
||||||
|
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||||
|
|
||||||
|
import androidx.annotation.IntDef;
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
|
||||||
|
/** Properties of a text annotation (i.e. ruby, text emphasis marks). */
|
||||||
|
public final class TextAnnotation {
|
||||||
|
/** The text annotation position is unknown. */
|
||||||
|
public static final int POSITION_UNKNOWN = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For horizontal text, the text annotation should be positioned above the base text.
|
||||||
|
*
|
||||||
|
* <p>For vertical text it should be positioned to the right, same as CSS's <a
|
||||||
|
* href="https://developer.mozilla.org/en-US/docs/Web/CSS/ruby-position">ruby-position</a>.
|
||||||
|
*/
|
||||||
|
public static final int POSITION_BEFORE = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For horizontal text, the text annotation should be positioned below the base text.
|
||||||
|
*
|
||||||
|
* <p>For vertical text it should be positioned to the left, same as CSS's <a
|
||||||
|
* href="https://developer.mozilla.org/en-US/docs/Web/CSS/ruby-position">ruby-position</a>.
|
||||||
|
*/
|
||||||
|
public static final int POSITION_AFTER = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The possible positions of the annotation text relative to the base text.
|
||||||
|
*
|
||||||
|
* <p>One of:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link #POSITION_UNKNOWN}
|
||||||
|
* <li>{@link #POSITION_BEFORE}
|
||||||
|
* <li>{@link #POSITION_AFTER}
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
@Documented
|
||||||
|
@Retention(SOURCE)
|
||||||
|
@IntDef({POSITION_UNKNOWN, POSITION_BEFORE, POSITION_AFTER})
|
||||||
|
public @interface Position {}
|
||||||
|
|
||||||
|
private TextAnnotation() {}
|
||||||
|
}
|
@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 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.exoplayer2.text.span;
|
||||||
|
|
||||||
|
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||||
|
|
||||||
|
import androidx.annotation.IntDef;
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A styling span for text emphasis marks.
|
||||||
|
*
|
||||||
|
* <p>These are pronunciation aids such as <a
|
||||||
|
* href="https://www.w3.org/TR/jlreq/?lang=en#term.emphasis-dots">Japanese boutens</a> which can be
|
||||||
|
* rendered using the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/text-emphasis">
|
||||||
|
* text-emphasis</a> CSS property.
|
||||||
|
*/
|
||||||
|
// NOTE: There's no Android layout support for text emphasis, so this span currently doesn't extend
|
||||||
|
// any styling superclasses (e.g. MetricAffectingSpan). The only way to render this emphasis is to
|
||||||
|
// extract the spans and do the layout manually.
|
||||||
|
public final class TextEmphasisSpan {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The possible mark shapes that can be used.
|
||||||
|
*
|
||||||
|
* <p>One of:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link #MARK_SHAPE_NONE}
|
||||||
|
* <li>{@link #MARK_SHAPE_CIRCLE}
|
||||||
|
* <li>{@link #MARK_SHAPE_DOT}
|
||||||
|
* <li>{@link #MARK_SHAPE_SESAME}
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
@Documented
|
||||||
|
@Retention(SOURCE)
|
||||||
|
@IntDef({MARK_SHAPE_NONE, MARK_SHAPE_CIRCLE, MARK_SHAPE_DOT, MARK_SHAPE_SESAME})
|
||||||
|
public @interface MarkShape {}
|
||||||
|
|
||||||
|
public static final int MARK_SHAPE_NONE = 0;
|
||||||
|
public static final int MARK_SHAPE_CIRCLE = 1;
|
||||||
|
public static final int MARK_SHAPE_DOT = 2;
|
||||||
|
public static final int MARK_SHAPE_SESAME = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The possible mark fills that can be used.
|
||||||
|
*
|
||||||
|
* <p>One of:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link #MARK_FILL_UNKNOWN}
|
||||||
|
* <li>{@link #MARK_FILL_FILLED}
|
||||||
|
* <li>{@link #MARK_FILL_OPEN}
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
@Documented
|
||||||
|
@Retention(SOURCE)
|
||||||
|
@IntDef({MARK_FILL_UNKNOWN, MARK_FILL_FILLED, MARK_FILL_OPEN})
|
||||||
|
public @interface MarkFill {}
|
||||||
|
|
||||||
|
public static final int MARK_FILL_UNKNOWN = 0;
|
||||||
|
public static final int MARK_FILL_FILLED = 1;
|
||||||
|
public static final int MARK_FILL_OPEN = 2;
|
||||||
|
|
||||||
|
/** The mark shape used for text emphasis. */
|
||||||
|
@MarkShape public int markShape;
|
||||||
|
|
||||||
|
/** The mark fill for the text emphasis mark. */
|
||||||
|
@MarkShape public int markFill;
|
||||||
|
|
||||||
|
/** The position of the text emphasis relative to the base text. */
|
||||||
|
@TextAnnotation.Position public final int position;
|
||||||
|
|
||||||
|
public TextEmphasisSpan(
|
||||||
|
@MarkShape int shape, @MarkFill int fill, @TextAnnotation.Position int position) {
|
||||||
|
this.markShape = shape;
|
||||||
|
this.markFill = fill;
|
||||||
|
this.position = position;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,217 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 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.exoplayer2.text.ttml;
|
||||||
|
|
||||||
|
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import androidx.annotation.IntDef;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import com.google.android.exoplayer2.text.Cue;
|
||||||
|
import com.google.android.exoplayer2.text.span.TextAnnotation;
|
||||||
|
import com.google.android.exoplayer2.text.span.TextEmphasisSpan;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a <a
|
||||||
|
* href="https://www.w3.org/TR/2018/REC-ttml2-20181108/#style-attribute-textEmphasis">
|
||||||
|
* tts:textEmphasis</a> attribute.
|
||||||
|
*/
|
||||||
|
/* package */ final class TextEmphasis {
|
||||||
|
|
||||||
|
@Documented
|
||||||
|
@Retention(SOURCE)
|
||||||
|
@IntDef({
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_NONE,
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_CIRCLE,
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_DOT,
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_SESAME,
|
||||||
|
MARK_SHAPE_AUTO
|
||||||
|
})
|
||||||
|
@interface MarkShape {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The "auto" mark shape is only defined in TTML and is resolved to a concrete shape when building
|
||||||
|
* the {@link Cue}. Hence, it is not defined in {@link TextEmphasisSpan.MarkShape}.
|
||||||
|
*/
|
||||||
|
public static final int MARK_SHAPE_AUTO = -1;
|
||||||
|
|
||||||
|
@Documented
|
||||||
|
@Retention(SOURCE)
|
||||||
|
@IntDef({
|
||||||
|
TextAnnotation.POSITION_UNKNOWN,
|
||||||
|
TextAnnotation.POSITION_BEFORE,
|
||||||
|
TextAnnotation.POSITION_AFTER,
|
||||||
|
POSITION_OUTSIDE
|
||||||
|
})
|
||||||
|
public @interface Position {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The "outside" position is only defined in TTML and is resolved before outputting a {@link Cue}
|
||||||
|
* object. Hence, it is not defined in {@link TextAnnotation.Position}.
|
||||||
|
*/
|
||||||
|
public static final int POSITION_OUTSIDE = -2;
|
||||||
|
|
||||||
|
private static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s+");
|
||||||
|
|
||||||
|
private static final ImmutableSet<String> SINGLE_STYLE_VALUES =
|
||||||
|
ImmutableSet.of(TtmlNode.TEXT_EMPHASIS_AUTO, TtmlNode.TEXT_EMPHASIS_NONE);
|
||||||
|
|
||||||
|
private static final ImmutableSet<String> MARK_SHAPE_VALUES =
|
||||||
|
ImmutableSet.of(
|
||||||
|
TtmlNode.TEXT_EMPHASIS_MARK_DOT,
|
||||||
|
TtmlNode.TEXT_EMPHASIS_MARK_SESAME,
|
||||||
|
TtmlNode.TEXT_EMPHASIS_MARK_CIRCLE);
|
||||||
|
|
||||||
|
private static final ImmutableSet<String> MARK_FILL_VALUES =
|
||||||
|
ImmutableSet.of(TtmlNode.TEXT_EMPHASIS_MARK_FILLED, TtmlNode.TEXT_EMPHASIS_MARK_OPEN);
|
||||||
|
|
||||||
|
private static final ImmutableSet<String> POSITION_VALUES =
|
||||||
|
ImmutableSet.of(
|
||||||
|
TtmlNode.ANNOTATION_POSITION_AFTER,
|
||||||
|
TtmlNode.ANNOTATION_POSITION_BEFORE,
|
||||||
|
TtmlNode.ANNOTATION_POSITION_OUTSIDE);
|
||||||
|
|
||||||
|
/** The text emphasis mark shape. */
|
||||||
|
@MarkShape public final int markShape;
|
||||||
|
|
||||||
|
/** The fill style of the text emphasis mark. */
|
||||||
|
@TextEmphasisSpan.MarkFill public final int markFill;
|
||||||
|
|
||||||
|
/** The position of the text emphasis relative to the base text. */
|
||||||
|
@Position public final int position;
|
||||||
|
|
||||||
|
private TextEmphasis(
|
||||||
|
@MarkShape int markShape,
|
||||||
|
@TextEmphasisSpan.MarkFill int markFill,
|
||||||
|
@TextAnnotation.Position int position) {
|
||||||
|
this.markShape = markShape;
|
||||||
|
this.markFill = markFill;
|
||||||
|
this.position = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a TTML <a
|
||||||
|
* href="https://www.w3.org/TR/2018/REC-ttml2-20181108/#style-attribute-textEmphasis">
|
||||||
|
* tts:textEmphasis</a> attribute. Returns null if parsing fails.
|
||||||
|
*
|
||||||
|
* <p>The parser searches for {@code emphasis-style} and {@code emphasis-position} independently.
|
||||||
|
* If a valid style is not found, the default style is used. If a valid position is not found, the
|
||||||
|
* default position is used.
|
||||||
|
*
|
||||||
|
* <p>Not implemented:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>{@code emphasis-color}
|
||||||
|
* <li>Quoted string {@code emphasis-style}
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static TextEmphasis parse(@Nullable String value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String parsingValue = value.trim();
|
||||||
|
if (parsingValue.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseWords(ImmutableSet.copyOf(TextUtils.split(parsingValue, WHITESPACE_PATTERN)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TextEmphasis parseWords(ImmutableSet<String> nodes) {
|
||||||
|
Set<String> matchingPositions = Sets.intersection(POSITION_VALUES, nodes);
|
||||||
|
// If no emphasis position is specified, then the emphasis position must be interpreted as if
|
||||||
|
// a position of outside were specified:
|
||||||
|
// https://www.w3.org/TR/2018/REC-ttml2-20181108/#style-attribute-textEmphasis
|
||||||
|
@Position int position;
|
||||||
|
switch (Iterables.getFirst(matchingPositions, TtmlNode.ANNOTATION_POSITION_OUTSIDE)) {
|
||||||
|
case TtmlNode.ANNOTATION_POSITION_AFTER:
|
||||||
|
position = TextAnnotation.POSITION_AFTER;
|
||||||
|
break;
|
||||||
|
case TtmlNode.ANNOTATION_POSITION_OUTSIDE:
|
||||||
|
position = POSITION_OUTSIDE;
|
||||||
|
break;
|
||||||
|
case TtmlNode.ANNOTATION_POSITION_BEFORE:
|
||||||
|
default:
|
||||||
|
// If an implementation does not recognize or otherwise distinguish an annotation position
|
||||||
|
// value, then it must be interpreted as if a position of 'before' were specified:
|
||||||
|
// https://www.w3.org/TR/2018/REC-ttml2-20181108/#style-attribute-textEmphasis
|
||||||
|
position = TextAnnotation.POSITION_BEFORE;
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> matchingSingleStyles = Sets.intersection(SINGLE_STYLE_VALUES, nodes);
|
||||||
|
if (!matchingSingleStyles.isEmpty()) {
|
||||||
|
// If "none" or "auto" are found in the description, ignore the other style (fill, shape)
|
||||||
|
// attributes.
|
||||||
|
@MarkShape int markShape;
|
||||||
|
switch (matchingSingleStyles.iterator().next()) {
|
||||||
|
case TtmlNode.TEXT_EMPHASIS_NONE:
|
||||||
|
markShape = TextEmphasisSpan.MARK_SHAPE_NONE;
|
||||||
|
break;
|
||||||
|
case TtmlNode.TEXT_EMPHASIS_AUTO:
|
||||||
|
default:
|
||||||
|
markShape = MARK_SHAPE_AUTO;
|
||||||
|
}
|
||||||
|
// markFill is ignored when markShape is NONE or AUTO
|
||||||
|
return new TextEmphasis(markShape, TextEmphasisSpan.MARK_FILL_UNKNOWN, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> matchingFills = Sets.intersection(MARK_FILL_VALUES, nodes);
|
||||||
|
Set<String> matchingShapes = Sets.intersection(MARK_SHAPE_VALUES, nodes);
|
||||||
|
if (matchingFills.isEmpty() && matchingShapes.isEmpty()) {
|
||||||
|
// If an implementation does not recognize or otherwise distinguish an emphasis style value,
|
||||||
|
// then it must be interpreted as if a style of auto were specified; as such, an
|
||||||
|
// implementation that supports text emphasis marks must minimally support the auto value.
|
||||||
|
// https://www.w3.org/TR/ttml2/#style-value-emphasis-style.
|
||||||
|
//
|
||||||
|
// markFill is ignored when markShape is NONE or AUTO.
|
||||||
|
return new TextEmphasis(MARK_SHAPE_AUTO, TextEmphasisSpan.MARK_FILL_UNKNOWN, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TextEmphasisSpan.MarkFill int markFill;
|
||||||
|
switch (Iterables.getFirst(matchingFills, TtmlNode.TEXT_EMPHASIS_MARK_FILLED)) {
|
||||||
|
case TtmlNode.TEXT_EMPHASIS_MARK_OPEN:
|
||||||
|
markFill = TextEmphasisSpan.MARK_FILL_OPEN;
|
||||||
|
break;
|
||||||
|
case TtmlNode.TEXT_EMPHASIS_MARK_FILLED:
|
||||||
|
default:
|
||||||
|
markFill = TextEmphasisSpan.MARK_FILL_FILLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MarkShape int markShape;
|
||||||
|
switch (Iterables.getFirst(matchingShapes, TtmlNode.TEXT_EMPHASIS_MARK_CIRCLE)) {
|
||||||
|
case TtmlNode.TEXT_EMPHASIS_MARK_DOT:
|
||||||
|
markShape = TextEmphasisSpan.MARK_SHAPE_DOT;
|
||||||
|
break;
|
||||||
|
case TtmlNode.TEXT_EMPHASIS_MARK_SESAME:
|
||||||
|
markShape = TextEmphasisSpan.MARK_SHAPE_SESAME;
|
||||||
|
break;
|
||||||
|
case TtmlNode.TEXT_EMPHASIS_MARK_CIRCLE:
|
||||||
|
default:
|
||||||
|
markShape = TextEmphasisSpan.MARK_SHAPE_CIRCLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TextEmphasis(markShape, markFill, position);
|
||||||
|
}
|
||||||
|
}
|
@ -22,7 +22,7 @@ import com.google.android.exoplayer2.text.Cue;
|
|||||||
import com.google.android.exoplayer2.text.SimpleSubtitleDecoder;
|
import com.google.android.exoplayer2.text.SimpleSubtitleDecoder;
|
||||||
import com.google.android.exoplayer2.text.Subtitle;
|
import com.google.android.exoplayer2.text.Subtitle;
|
||||||
import com.google.android.exoplayer2.text.SubtitleDecoderException;
|
import com.google.android.exoplayer2.text.SubtitleDecoderException;
|
||||||
import com.google.android.exoplayer2.text.span.RubySpan;
|
import com.google.android.exoplayer2.text.span.TextAnnotation;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.ColorParser;
|
import com.google.android.exoplayer2.util.ColorParser;
|
||||||
import com.google.android.exoplayer2.util.Log;
|
import com.google.android.exoplayer2.util.Log;
|
||||||
@ -582,11 +582,11 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
|||||||
break;
|
break;
|
||||||
case TtmlNode.ATTR_TTS_RUBY_POSITION:
|
case TtmlNode.ATTR_TTS_RUBY_POSITION:
|
||||||
switch (Util.toLowerInvariant(attributeValue)) {
|
switch (Util.toLowerInvariant(attributeValue)) {
|
||||||
case TtmlNode.RUBY_BEFORE:
|
case TtmlNode.ANNOTATION_POSITION_BEFORE:
|
||||||
style = createIfNull(style).setRubyPosition(RubySpan.POSITION_OVER);
|
style = createIfNull(style).setRubyPosition(TextAnnotation.POSITION_BEFORE);
|
||||||
break;
|
break;
|
||||||
case TtmlNode.RUBY_AFTER:
|
case TtmlNode.ANNOTATION_POSITION_AFTER:
|
||||||
style = createIfNull(style).setRubyPosition(RubySpan.POSITION_UNDER);
|
style = createIfNull(style).setRubyPosition(TextAnnotation.POSITION_AFTER);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// ignore
|
// ignore
|
||||||
@ -609,6 +609,11 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case TtmlNode.ATTR_TTS_TEXT_EMPHASIS:
|
||||||
|
style =
|
||||||
|
createIfNull(style)
|
||||||
|
.setTextEmphasis(TextEmphasis.parse(Util.toLowerInvariant(attributeValue)));
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// ignore
|
// ignore
|
||||||
break;
|
break;
|
||||||
|
@ -69,6 +69,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
public static final String ATTR_TTS_TEXT_DECORATION = "textDecoration";
|
public static final String ATTR_TTS_TEXT_DECORATION = "textDecoration";
|
||||||
public static final String ATTR_TTS_TEXT_ALIGN = "textAlign";
|
public static final String ATTR_TTS_TEXT_ALIGN = "textAlign";
|
||||||
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_WRITING_MODE = "writingMode";
|
public static final String ATTR_TTS_WRITING_MODE = "writingMode";
|
||||||
|
|
||||||
// Values for ruby
|
// Values for ruby
|
||||||
@ -79,9 +80,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
public static final String RUBY_TEXT_CONTAINER = "textContainer";
|
public static final String RUBY_TEXT_CONTAINER = "textContainer";
|
||||||
public static final String RUBY_DELIMITER = "delimiter";
|
public static final String RUBY_DELIMITER = "delimiter";
|
||||||
|
|
||||||
// Values for rubyPosition
|
// Values for text annotation (i.e. ruby, text emphasis) position
|
||||||
public static final String RUBY_BEFORE = "before";
|
public static final String ANNOTATION_POSITION_BEFORE = "before";
|
||||||
public static final String RUBY_AFTER = "after";
|
public static final String ANNOTATION_POSITION_AFTER = "after";
|
||||||
|
public static final String ANNOTATION_POSITION_OUTSIDE = "outside";
|
||||||
|
|
||||||
// Values for textDecoration
|
// Values for textDecoration
|
||||||
public static final String LINETHROUGH = "linethrough";
|
public static final String LINETHROUGH = "linethrough";
|
||||||
public static final String NO_LINETHROUGH = "nolinethrough";
|
public static final String NO_LINETHROUGH = "nolinethrough";
|
||||||
@ -106,6 +109,15 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
public static final String VERTICAL_LR = "tblr";
|
public static final String VERTICAL_LR = "tblr";
|
||||||
public static final String VERTICAL_RL = "tbrl";
|
public static final String VERTICAL_RL = "tbrl";
|
||||||
|
|
||||||
|
// Values for textEmphasis
|
||||||
|
public static final String TEXT_EMPHASIS_NONE = "none";
|
||||||
|
public static final String TEXT_EMPHASIS_AUTO = "auto";
|
||||||
|
public static final String TEXT_EMPHASIS_MARK_DOT = "dot";
|
||||||
|
public static final String TEXT_EMPHASIS_MARK_SESAME = "sesame";
|
||||||
|
public static final String TEXT_EMPHASIS_MARK_CIRCLE = "circle";
|
||||||
|
public static final String TEXT_EMPHASIS_MARK_FILLED = "filled";
|
||||||
|
public static final String TEXT_EMPHASIS_MARK_OPEN = "open";
|
||||||
|
|
||||||
@Nullable public final String tag;
|
@Nullable public final String tag;
|
||||||
@Nullable public final String text;
|
@Nullable public final String text;
|
||||||
public final boolean isTextNode;
|
public final boolean isTextNode;
|
||||||
@ -243,7 +255,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
|
|
||||||
TreeMap<String, Cue.Builder> regionTextOutputs = new TreeMap<>();
|
TreeMap<String, Cue.Builder> regionTextOutputs = new TreeMap<>();
|
||||||
traverseForText(timeUs, false, regionId, regionTextOutputs);
|
traverseForText(timeUs, false, regionId, regionTextOutputs);
|
||||||
traverseForStyle(timeUs, globalStyles, regionTextOutputs);
|
traverseForStyle(timeUs, globalStyles, regionMap, regionId, regionTextOutputs);
|
||||||
|
|
||||||
List<Cue> cues = new ArrayList<>();
|
List<Cue> cues = new ArrayList<>();
|
||||||
|
|
||||||
@ -354,26 +366,39 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void traverseForStyle(
|
private void traverseForStyle(
|
||||||
long timeUs, Map<String, TtmlStyle> globalStyles, Map<String, Cue.Builder> regionOutputs) {
|
long timeUs,
|
||||||
|
Map<String, TtmlStyle> globalStyles,
|
||||||
|
Map<String, TtmlRegion> regionMaps,
|
||||||
|
String inheritedRegion,
|
||||||
|
Map<String, Cue.Builder> regionOutputs) {
|
||||||
if (!isActive(timeUs)) {
|
if (!isActive(timeUs)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
String resolvedRegionId = ANONYMOUS_REGION_ID.equals(regionId) ? inheritedRegion : regionId;
|
||||||
|
|
||||||
for (Map.Entry<String, Integer> entry : nodeEndsByRegion.entrySet()) {
|
for (Map.Entry<String, Integer> entry : nodeEndsByRegion.entrySet()) {
|
||||||
String regionId = entry.getKey();
|
String regionId = entry.getKey();
|
||||||
int start = nodeStartsByRegion.containsKey(regionId) ? nodeStartsByRegion.get(regionId) : 0;
|
int start = nodeStartsByRegion.containsKey(regionId) ? nodeStartsByRegion.get(regionId) : 0;
|
||||||
int end = entry.getValue();
|
int end = entry.getValue();
|
||||||
if (start != end) {
|
if (start != end) {
|
||||||
Cue.Builder regionOutput = Assertions.checkNotNull(regionOutputs.get(regionId));
|
Cue.Builder regionOutput = Assertions.checkNotNull(regionOutputs.get(regionId));
|
||||||
applyStyleToOutput(globalStyles, regionOutput, start, end);
|
@Cue.VerticalType
|
||||||
|
int verticalType = Assertions.checkNotNull(regionMaps.get(resolvedRegionId)).verticalType;
|
||||||
|
applyStyleToOutput(globalStyles, regionOutput, start, end, verticalType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (int i = 0; i < getChildCount(); ++i) {
|
for (int i = 0; i < getChildCount(); ++i) {
|
||||||
getChild(i).traverseForStyle(timeUs, globalStyles, regionOutputs);
|
getChild(i)
|
||||||
|
.traverseForStyle(timeUs, globalStyles, regionMaps, resolvedRegionId, regionOutputs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyStyleToOutput(
|
private void applyStyleToOutput(
|
||||||
Map<String, TtmlStyle> globalStyles, Cue.Builder regionOutput, int start, int end) {
|
Map<String, TtmlStyle> globalStyles,
|
||||||
|
Cue.Builder regionOutput,
|
||||||
|
int start,
|
||||||
|
int end,
|
||||||
|
@Cue.VerticalType int verticalType) {
|
||||||
@Nullable TtmlStyle resolvedStyle = TtmlRenderUtil.resolveStyle(style, styleIds, globalStyles);
|
@Nullable TtmlStyle resolvedStyle = TtmlRenderUtil.resolveStyle(style, styleIds, globalStyles);
|
||||||
@Nullable SpannableStringBuilder text = (SpannableStringBuilder) regionOutput.getText();
|
@Nullable SpannableStringBuilder text = (SpannableStringBuilder) regionOutput.getText();
|
||||||
if (text == null) {
|
if (text == null) {
|
||||||
@ -381,7 +406,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
regionOutput.setText(text);
|
regionOutput.setText(text);
|
||||||
}
|
}
|
||||||
if (resolvedStyle != null) {
|
if (resolvedStyle != null) {
|
||||||
TtmlRenderUtil.applyStylesToSpan(text, start, end, resolvedStyle, parent, globalStyles);
|
TtmlRenderUtil.applyStylesToSpan(
|
||||||
|
text, start, end, resolvedStyle, parent, globalStyles, verticalType);
|
||||||
regionOutput.setTextAlignment(resolvedStyle.getTextAlign());
|
regionOutput.setTextAlignment(resolvedStyle.getTextAlign());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.text.ttml;
|
package com.google.android.exoplayer2.text.ttml;
|
||||||
|
|
||||||
|
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
|
||||||
|
|
||||||
import android.text.Spannable;
|
import android.text.Spannable;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
@ -27,9 +29,12 @@ import android.text.style.StyleSpan;
|
|||||||
import android.text.style.TypefaceSpan;
|
import android.text.style.TypefaceSpan;
|
||||||
import android.text.style.UnderlineSpan;
|
import android.text.style.UnderlineSpan;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import com.google.android.exoplayer2.text.Cue;
|
||||||
import com.google.android.exoplayer2.text.span.HorizontalTextInVerticalContextSpan;
|
import com.google.android.exoplayer2.text.span.HorizontalTextInVerticalContextSpan;
|
||||||
import com.google.android.exoplayer2.text.span.RubySpan;
|
import com.google.android.exoplayer2.text.span.RubySpan;
|
||||||
import com.google.android.exoplayer2.text.span.SpanUtil;
|
import com.google.android.exoplayer2.text.span.SpanUtil;
|
||||||
|
import com.google.android.exoplayer2.text.span.TextAnnotation;
|
||||||
|
import com.google.android.exoplayer2.text.span.TextEmphasisSpan;
|
||||||
import com.google.android.exoplayer2.util.Log;
|
import com.google.android.exoplayer2.util.Log;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
@ -83,7 +88,8 @@ import java.util.Map;
|
|||||||
int end,
|
int end,
|
||||||
TtmlStyle style,
|
TtmlStyle style,
|
||||||
@Nullable TtmlNode parent,
|
@Nullable TtmlNode parent,
|
||||||
Map<String, TtmlStyle> globalStyles) {
|
Map<String, TtmlStyle> globalStyles,
|
||||||
|
@Cue.VerticalType int verticalType) {
|
||||||
|
|
||||||
if (style.getStyle() != TtmlStyle.UNSPECIFIED) {
|
if (style.getStyle() != TtmlStyle.UNSPECIFIED) {
|
||||||
builder.setSpan(new StyleSpan(style.getStyle()), start, end,
|
builder.setSpan(new StyleSpan(style.getStyle()), start, end,
|
||||||
@ -119,6 +125,40 @@ import java.util.Map;
|
|||||||
end,
|
end,
|
||||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
}
|
}
|
||||||
|
if (style.getTextEmphasis() != null) {
|
||||||
|
TextEmphasis textEmphasis = checkNotNull(style.getTextEmphasis());
|
||||||
|
@TextEmphasisSpan.MarkShape int markShape;
|
||||||
|
@TextEmphasisSpan.MarkFill int markFill;
|
||||||
|
if (textEmphasis.markShape == TextEmphasis.MARK_SHAPE_AUTO) {
|
||||||
|
// If a vertical writing mode applies, then 'auto' is equivalent to 'filled sesame';
|
||||||
|
// otherwise, it's equivalent to 'filled circle':
|
||||||
|
// https://www.w3.org/TR/ttml2/#style-value-emphasis-style
|
||||||
|
markShape =
|
||||||
|
(verticalType == Cue.VERTICAL_TYPE_LR || verticalType == Cue.VERTICAL_TYPE_RL)
|
||||||
|
? TextEmphasisSpan.MARK_SHAPE_SESAME
|
||||||
|
: TextEmphasisSpan.MARK_SHAPE_CIRCLE;
|
||||||
|
markFill = TextEmphasisSpan.MARK_FILL_FILLED;
|
||||||
|
} else {
|
||||||
|
markShape = textEmphasis.markShape;
|
||||||
|
markFill = textEmphasis.markFill;
|
||||||
|
}
|
||||||
|
|
||||||
|
@TextEmphasis.Position int position;
|
||||||
|
if (textEmphasis.position == TextEmphasis.POSITION_OUTSIDE) {
|
||||||
|
// 'outside' is not supported by TextEmphasisSpan, so treat it as 'before':
|
||||||
|
// https://www.w3.org/TR/ttml2/#style-value-annotation-position
|
||||||
|
position = TextAnnotation.POSITION_BEFORE;
|
||||||
|
} else {
|
||||||
|
position = textEmphasis.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
SpanUtil.addOrReplaceSpan(
|
||||||
|
builder,
|
||||||
|
new TextEmphasisSpan(markShape, markFill, position),
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
}
|
||||||
switch (style.getRubyType()) {
|
switch (style.getRubyType()) {
|
||||||
case TtmlStyle.RUBY_TYPE_BASE:
|
case TtmlStyle.RUBY_TYPE_BASE:
|
||||||
// look for the sibling RUBY_TEXT and add it as span between start & end.
|
// look for the sibling RUBY_TEXT and add it as span between start & end.
|
||||||
@ -141,11 +181,11 @@ import java.util.Map;
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Get rubyPosition from `textNode` when TTML inheritance is implemented.
|
// TODO: Get rubyPosition from `textNode` when TTML inheritance is implemented.
|
||||||
@RubySpan.Position
|
@TextAnnotation.Position
|
||||||
int rubyPosition =
|
int rubyPosition =
|
||||||
containerNode.style != null
|
containerNode.style != null
|
||||||
? containerNode.style.getRubyPosition()
|
? containerNode.style.getRubyPosition()
|
||||||
: RubySpan.POSITION_UNKNOWN;
|
: TextAnnotation.POSITION_UNKNOWN;
|
||||||
builder.setSpan(
|
builder.setSpan(
|
||||||
new RubySpan(rubyText, rubyPosition), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
new RubySpan(rubyText, rubyPosition), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
break;
|
break;
|
||||||
|
@ -19,7 +19,7 @@ import android.graphics.Typeface;
|
|||||||
import android.text.Layout;
|
import android.text.Layout;
|
||||||
import androidx.annotation.IntDef;
|
import androidx.annotation.IntDef;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.text.span.RubySpan;
|
import com.google.android.exoplayer2.text.span.TextAnnotation;
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.Documented;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
@ -83,9 +83,10 @@ import java.lang.annotation.RetentionPolicy;
|
|||||||
private float fontSize;
|
private float fontSize;
|
||||||
@Nullable private String id;
|
@Nullable private String id;
|
||||||
@RubyType private int rubyType;
|
@RubyType private int rubyType;
|
||||||
@RubySpan.Position private int rubyPosition;
|
@TextAnnotation.Position private int rubyPosition;
|
||||||
@Nullable private Layout.Alignment textAlign;
|
@Nullable private Layout.Alignment textAlign;
|
||||||
@OptionalBoolean private int textCombine;
|
@OptionalBoolean private int textCombine;
|
||||||
|
@Nullable private TextEmphasis textEmphasis;
|
||||||
|
|
||||||
public TtmlStyle() {
|
public TtmlStyle() {
|
||||||
linethrough = UNSPECIFIED;
|
linethrough = UNSPECIFIED;
|
||||||
@ -94,7 +95,7 @@ import java.lang.annotation.RetentionPolicy;
|
|||||||
italic = UNSPECIFIED;
|
italic = UNSPECIFIED;
|
||||||
fontSizeUnit = UNSPECIFIED;
|
fontSizeUnit = UNSPECIFIED;
|
||||||
rubyType = UNSPECIFIED;
|
rubyType = UNSPECIFIED;
|
||||||
rubyPosition = RubySpan.POSITION_UNKNOWN;
|
rubyPosition = TextAnnotation.POSITION_UNKNOWN;
|
||||||
textCombine = UNSPECIFIED;
|
textCombine = UNSPECIFIED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,7 +226,7 @@ import java.lang.annotation.RetentionPolicy;
|
|||||||
if (underline == UNSPECIFIED) {
|
if (underline == UNSPECIFIED) {
|
||||||
underline = ancestor.underline;
|
underline = ancestor.underline;
|
||||||
}
|
}
|
||||||
if (rubyPosition == RubySpan.POSITION_UNKNOWN) {
|
if (rubyPosition == TextAnnotation.POSITION_UNKNOWN) {
|
||||||
rubyPosition = ancestor.rubyPosition;
|
rubyPosition = ancestor.rubyPosition;
|
||||||
}
|
}
|
||||||
if (textAlign == null && ancestor.textAlign != null) {
|
if (textAlign == null && ancestor.textAlign != null) {
|
||||||
@ -238,6 +239,9 @@ import java.lang.annotation.RetentionPolicy;
|
|||||||
fontSizeUnit = ancestor.fontSizeUnit;
|
fontSizeUnit = ancestor.fontSizeUnit;
|
||||||
fontSize = ancestor.fontSize;
|
fontSize = ancestor.fontSize;
|
||||||
}
|
}
|
||||||
|
if (textEmphasis == null) {
|
||||||
|
textEmphasis = ancestor.textEmphasis;
|
||||||
|
}
|
||||||
// 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);
|
||||||
@ -269,12 +273,12 @@ import java.lang.annotation.RetentionPolicy;
|
|||||||
return rubyType;
|
return rubyType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TtmlStyle setRubyPosition(@RubySpan.Position int position) {
|
public TtmlStyle setRubyPosition(@TextAnnotation.Position int position) {
|
||||||
this.rubyPosition = position;
|
this.rubyPosition = position;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@RubySpan.Position
|
@TextAnnotation.Position
|
||||||
public int getRubyPosition() {
|
public int getRubyPosition() {
|
||||||
return rubyPosition;
|
return rubyPosition;
|
||||||
}
|
}
|
||||||
@ -299,6 +303,16 @@ import java.lang.annotation.RetentionPolicy;
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public TextEmphasis getTextEmphasis() {
|
||||||
|
return textEmphasis;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TtmlStyle setTextEmphasis(@Nullable TextEmphasis textEmphasis) {
|
||||||
|
this.textEmphasis = textEmphasis;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public TtmlStyle setFontSize(float fontSize) {
|
public TtmlStyle setFontSize(float fontSize) {
|
||||||
this.fontSize = fontSize;
|
this.fontSize = fontSize;
|
||||||
return this;
|
return this;
|
||||||
|
@ -17,7 +17,7 @@ package com.google.android.exoplayer2.text.webvtt;
|
|||||||
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.text.span.RubySpan;
|
import com.google.android.exoplayer2.text.span.TextAnnotation;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.ColorParser;
|
import com.google.android.exoplayer2.util.ColorParser;
|
||||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
@ -195,9 +195,9 @@ import java.util.regex.Pattern;
|
|||||||
style.setBackgroundColor(ColorParser.parseCssColor(value));
|
style.setBackgroundColor(ColorParser.parseCssColor(value));
|
||||||
} else if (PROPERTY_RUBY_POSITION.equals(property)) {
|
} else if (PROPERTY_RUBY_POSITION.equals(property)) {
|
||||||
if (VALUE_OVER.equals(value)) {
|
if (VALUE_OVER.equals(value)) {
|
||||||
style.setRubyPosition(RubySpan.POSITION_OVER);
|
style.setRubyPosition(TextAnnotation.POSITION_BEFORE);
|
||||||
} else if (VALUE_UNDER.equals(value)) {
|
} else if (VALUE_UNDER.equals(value)) {
|
||||||
style.setRubyPosition(RubySpan.POSITION_UNDER);
|
style.setRubyPosition(TextAnnotation.POSITION_AFTER);
|
||||||
}
|
}
|
||||||
} else if (PROPERTY_TEXT_COMBINE_UPRIGHT.equals(property)) {
|
} else if (PROPERTY_TEXT_COMBINE_UPRIGHT.equals(property)) {
|
||||||
style.setCombineUpright(VALUE_ALL.equals(value) || value.startsWith(VALUE_DIGITS));
|
style.setCombineUpright(VALUE_ALL.equals(value) || value.startsWith(VALUE_DIGITS));
|
||||||
|
@ -20,7 +20,7 @@ import android.text.TextUtils;
|
|||||||
import androidx.annotation.ColorInt;
|
import androidx.annotation.ColorInt;
|
||||||
import androidx.annotation.IntDef;
|
import androidx.annotation.IntDef;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.text.span.RubySpan;
|
import com.google.android.exoplayer2.text.span.TextAnnotation;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.Documented;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
@ -95,7 +95,7 @@ public final class WebvttCssStyle {
|
|||||||
@OptionalBoolean private int italic;
|
@OptionalBoolean private int italic;
|
||||||
@FontSizeUnit private int fontSizeUnit;
|
@FontSizeUnit private int fontSizeUnit;
|
||||||
private float fontSize;
|
private float fontSize;
|
||||||
@RubySpan.Position private int rubyPosition;
|
@TextAnnotation.Position private int rubyPosition;
|
||||||
private boolean combineUpright;
|
private boolean combineUpright;
|
||||||
|
|
||||||
public WebvttCssStyle() {
|
public WebvttCssStyle() {
|
||||||
@ -111,7 +111,7 @@ public final class WebvttCssStyle {
|
|||||||
bold = UNSPECIFIED;
|
bold = UNSPECIFIED;
|
||||||
italic = UNSPECIFIED;
|
italic = UNSPECIFIED;
|
||||||
fontSizeUnit = UNSPECIFIED;
|
fontSizeUnit = UNSPECIFIED;
|
||||||
rubyPosition = RubySpan.POSITION_UNKNOWN;
|
rubyPosition = TextAnnotation.POSITION_UNKNOWN;
|
||||||
combineUpright = false;
|
combineUpright = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,12 +272,12 @@ public final class WebvttCssStyle {
|
|||||||
return fontSize;
|
return fontSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
public WebvttCssStyle setRubyPosition(@RubySpan.Position int rubyPosition) {
|
public WebvttCssStyle setRubyPosition(@TextAnnotation.Position int rubyPosition) {
|
||||||
this.rubyPosition = rubyPosition;
|
this.rubyPosition = rubyPosition;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@RubySpan.Position
|
@TextAnnotation.Position
|
||||||
public int getRubyPosition() {
|
public int getRubyPosition() {
|
||||||
return rubyPosition;
|
return rubyPosition;
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,7 @@ import androidx.annotation.Nullable;
|
|||||||
import com.google.android.exoplayer2.text.Cue;
|
import com.google.android.exoplayer2.text.Cue;
|
||||||
import com.google.android.exoplayer2.text.span.HorizontalTextInVerticalContextSpan;
|
import com.google.android.exoplayer2.text.span.HorizontalTextInVerticalContextSpan;
|
||||||
import com.google.android.exoplayer2.text.span.RubySpan;
|
import com.google.android.exoplayer2.text.span.RubySpan;
|
||||||
|
import com.google.android.exoplayer2.text.span.TextAnnotation;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.Log;
|
import com.google.android.exoplayer2.util.Log;
|
||||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
@ -572,7 +573,7 @@ public final class WebvttCueParser {
|
|||||||
StartTag startTag,
|
StartTag startTag,
|
||||||
List<Element> nestedElements,
|
List<Element> nestedElements,
|
||||||
List<WebvttCssStyle> styles) {
|
List<WebvttCssStyle> styles) {
|
||||||
@RubySpan.Position int rubyTagPosition = getRubyPosition(styles, cueId, startTag);
|
@TextAnnotation.Position int rubyTagPosition = getRubyPosition(styles, cueId, startTag);
|
||||||
List<Element> sortedNestedElements = new ArrayList<>(nestedElements.size());
|
List<Element> sortedNestedElements = new ArrayList<>(nestedElements.size());
|
||||||
sortedNestedElements.addAll(nestedElements);
|
sortedNestedElements.addAll(nestedElements);
|
||||||
Collections.sort(sortedNestedElements, Element.BY_START_POSITION_ASC);
|
Collections.sort(sortedNestedElements, Element.BY_START_POSITION_ASC);
|
||||||
@ -585,12 +586,12 @@ public final class WebvttCueParser {
|
|||||||
Element rubyTextElement = sortedNestedElements.get(i);
|
Element rubyTextElement = sortedNestedElements.get(i);
|
||||||
// Use the <rt> element's ruby-position if set, otherwise the <ruby> element's and otherwise
|
// Use the <rt> element's ruby-position if set, otherwise the <ruby> element's and otherwise
|
||||||
// default to OVER.
|
// default to OVER.
|
||||||
@RubySpan.Position
|
@TextAnnotation.Position
|
||||||
int rubyPosition =
|
int rubyPosition =
|
||||||
firstKnownRubyPosition(
|
firstKnownRubyPosition(
|
||||||
getRubyPosition(styles, cueId, rubyTextElement.startTag),
|
getRubyPosition(styles, cueId, rubyTextElement.startTag),
|
||||||
rubyTagPosition,
|
rubyTagPosition,
|
||||||
RubySpan.POSITION_OVER);
|
TextAnnotation.POSITION_BEFORE);
|
||||||
// Move the rubyText from spannedText into the RubySpan.
|
// Move the rubyText from spannedText into the RubySpan.
|
||||||
int adjustedRubyTextStart = rubyTextElement.startTag.position - deletedCharCount;
|
int adjustedRubyTextStart = rubyTextElement.startTag.position - deletedCharCount;
|
||||||
int adjustedRubyTextEnd = rubyTextElement.endPosition - deletedCharCount;
|
int adjustedRubyTextEnd = rubyTextElement.endPosition - deletedCharCount;
|
||||||
@ -607,31 +608,31 @@ public final class WebvttCueParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RubySpan.Position
|
@TextAnnotation.Position
|
||||||
private static int getRubyPosition(
|
private static int getRubyPosition(
|
||||||
List<WebvttCssStyle> styles, @Nullable String cueId, StartTag startTag) {
|
List<WebvttCssStyle> styles, @Nullable String cueId, StartTag startTag) {
|
||||||
List<StyleMatch> styleMatches = getApplicableStyles(styles, cueId, startTag);
|
List<StyleMatch> styleMatches = getApplicableStyles(styles, cueId, startTag);
|
||||||
for (int i = 0; i < styleMatches.size(); i++) {
|
for (int i = 0; i < styleMatches.size(); i++) {
|
||||||
WebvttCssStyle style = styleMatches.get(i).style;
|
WebvttCssStyle style = styleMatches.get(i).style;
|
||||||
if (style.getRubyPosition() != RubySpan.POSITION_UNKNOWN) {
|
if (style.getRubyPosition() != TextAnnotation.POSITION_UNKNOWN) {
|
||||||
return style.getRubyPosition();
|
return style.getRubyPosition();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return RubySpan.POSITION_UNKNOWN;
|
return TextAnnotation.POSITION_UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
@RubySpan.Position
|
@TextAnnotation.Position
|
||||||
private static int firstKnownRubyPosition(
|
private static int firstKnownRubyPosition(
|
||||||
@RubySpan.Position int position1,
|
@TextAnnotation.Position int position1,
|
||||||
@RubySpan.Position int position2,
|
@TextAnnotation.Position int position2,
|
||||||
@RubySpan.Position int position3) {
|
@TextAnnotation.Position int position3) {
|
||||||
if (position1 != RubySpan.POSITION_UNKNOWN) {
|
if (position1 != TextAnnotation.POSITION_UNKNOWN) {
|
||||||
return position1;
|
return position1;
|
||||||
}
|
}
|
||||||
if (position2 != RubySpan.POSITION_UNKNOWN) {
|
if (position2 != TextAnnotation.POSITION_UNKNOWN) {
|
||||||
return position2;
|
return position2;
|
||||||
}
|
}
|
||||||
if (position3 != RubySpan.POSITION_UNKNOWN) {
|
if (position3 != TextAnnotation.POSITION_UNKNOWN) {
|
||||||
return position3;
|
return position3;
|
||||||
}
|
}
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
|
@ -0,0 +1,729 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 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.exoplayer2.text.ttml;
|
||||||
|
|
||||||
|
import static com.google.android.exoplayer2.text.ttml.TextEmphasis.MARK_SHAPE_AUTO;
|
||||||
|
import static com.google.android.exoplayer2.text.ttml.TextEmphasis.POSITION_OUTSIDE;
|
||||||
|
import static com.google.android.exoplayer2.text.ttml.TextEmphasis.parse;
|
||||||
|
import static com.google.common.truth.Truth.assertWithMessage;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import com.google.android.exoplayer2.text.span.TextAnnotation;
|
||||||
|
import com.google.android.exoplayer2.text.span.TextEmphasisSpan;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
/** Unit test for {@link TextEmphasis}. */
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class TextEmphasisTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNull() {
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(null);
|
||||||
|
assertWithMessage("Text Emphasis must be null").that(textEmphasis).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEmpty() {
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse("");
|
||||||
|
assertWithMessage("Text Emphasis must be null").that(textEmphasis).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEmptyWithWhitespace() {
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(" ");
|
||||||
|
assertWithMessage("Text Emphasis must be null").that(textEmphasis).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNone() {
|
||||||
|
String value = "none";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape")
|
||||||
|
.that(textEmphasis.markShape)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_SHAPE_NONE);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextEmphasis.POSITION_OUTSIDE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAuto() {
|
||||||
|
String value = "auto";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape").that(textEmphasis.markShape).isEqualTo(MARK_SHAPE_AUTO);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_UNKNOWN);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextEmphasis.POSITION_OUTSIDE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInvalid() {
|
||||||
|
String value = "invalid";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape").that(textEmphasis.markShape).isEqualTo(MARK_SHAPE_AUTO);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_UNKNOWN);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextEmphasis.POSITION_OUTSIDE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAutoOutside() {
|
||||||
|
String value = "auto outside";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape").that(textEmphasis.markShape).isEqualTo(MARK_SHAPE_AUTO);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_UNKNOWN);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextEmphasis.POSITION_OUTSIDE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAutoAfter() {
|
||||||
|
String value = "auto after";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape").that(textEmphasis.markShape).isEqualTo(MARK_SHAPE_AUTO);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_UNKNOWN);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextAnnotation.POSITION_AFTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If only filled or open is specified, then it is equivalent to filled circle and open circle,
|
||||||
|
* respectively.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testFilled() {
|
||||||
|
String value = "filled";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape")
|
||||||
|
.that(textEmphasis.markShape)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_SHAPE_CIRCLE);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_FILLED);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextEmphasis.POSITION_OUTSIDE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOpen() {
|
||||||
|
String value = "open";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape")
|
||||||
|
.that(textEmphasis.markShape)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_SHAPE_CIRCLE);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_OPEN);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextEmphasis.POSITION_OUTSIDE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOpenAfter() {
|
||||||
|
String value = "open after";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape")
|
||||||
|
.that(textEmphasis.markShape)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_SHAPE_CIRCLE);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_OPEN);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextAnnotation.POSITION_AFTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If only circle, dot, or sesame is specified, then it is equivalent to filled circle, filled
|
||||||
|
* dot, and filled sesame, respectively.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testDotBefore() {
|
||||||
|
String value = "dot before";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape")
|
||||||
|
.that(textEmphasis.markShape)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_SHAPE_DOT);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_FILLED);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextAnnotation.POSITION_BEFORE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCircleBefore() {
|
||||||
|
String value = "circle before";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape")
|
||||||
|
.that(textEmphasis.markShape)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_SHAPE_CIRCLE);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_FILLED);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextAnnotation.POSITION_BEFORE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSesameBefore() {
|
||||||
|
String value = "sesame before";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape")
|
||||||
|
.that(textEmphasis.markShape)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_SHAPE_SESAME);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_FILLED);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextAnnotation.POSITION_BEFORE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDotAfter() {
|
||||||
|
String value = "dot after";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape")
|
||||||
|
.that(textEmphasis.markShape)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_SHAPE_DOT);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_FILLED);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextAnnotation.POSITION_AFTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCircleAfter() {
|
||||||
|
String value = "circle after";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape")
|
||||||
|
.that(textEmphasis.markShape)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_SHAPE_CIRCLE);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_FILLED);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextAnnotation.POSITION_AFTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSesameAfter() {
|
||||||
|
String value = "sesame after";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape")
|
||||||
|
.that(textEmphasis.markShape)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_SHAPE_SESAME);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_FILLED);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextAnnotation.POSITION_AFTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDotOutside() {
|
||||||
|
String value = "dot outside";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape")
|
||||||
|
.that(textEmphasis.markShape)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_SHAPE_DOT);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_FILLED);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextEmphasis.POSITION_OUTSIDE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCircleOutside() {
|
||||||
|
String value = "circle outside";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape")
|
||||||
|
.that(textEmphasis.markShape)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_SHAPE_CIRCLE);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_FILLED);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextEmphasis.POSITION_OUTSIDE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSesameOutside() {
|
||||||
|
String value = "sesame outside";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape")
|
||||||
|
.that(textEmphasis.markShape)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_SHAPE_SESAME);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_FILLED);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextEmphasis.POSITION_OUTSIDE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOpenDotAfter() {
|
||||||
|
String value = "open dot after";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape")
|
||||||
|
.that(textEmphasis.markShape)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_SHAPE_DOT);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_OPEN);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextAnnotation.POSITION_AFTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOpenCircleAfter() {
|
||||||
|
String value = "open circle after";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape")
|
||||||
|
.that(textEmphasis.markShape)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_SHAPE_CIRCLE);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_OPEN);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextAnnotation.POSITION_AFTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOpenSesameAfter() {
|
||||||
|
String value = "open sesame after";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape")
|
||||||
|
.that(textEmphasis.markShape)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_SHAPE_SESAME);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_OPEN);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextAnnotation.POSITION_AFTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOpenDotBefore() {
|
||||||
|
String value = "open dot before";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape")
|
||||||
|
.that(textEmphasis.markShape)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_SHAPE_DOT);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_OPEN);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextAnnotation.POSITION_BEFORE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOpenCircleBefore() {
|
||||||
|
String value = "open circle before";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape")
|
||||||
|
.that(textEmphasis.markShape)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_SHAPE_CIRCLE);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_OPEN);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextAnnotation.POSITION_BEFORE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOpenSesameBefore() {
|
||||||
|
String value = "open sesame before";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape")
|
||||||
|
.that(textEmphasis.markShape)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_SHAPE_SESAME);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_OPEN);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextAnnotation.POSITION_BEFORE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOpenDotOutside() {
|
||||||
|
String value = "open dot Outside";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape")
|
||||||
|
.that(textEmphasis.markShape)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_SHAPE_DOT);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_OPEN);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextEmphasis.POSITION_OUTSIDE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOpenCircleOutside() {
|
||||||
|
String value = "open circle outside";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape")
|
||||||
|
.that(textEmphasis.markShape)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_SHAPE_CIRCLE);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_OPEN);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextEmphasis.POSITION_OUTSIDE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOpenSesameOutside() {
|
||||||
|
String value = "open sesame outside";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape")
|
||||||
|
.that(textEmphasis.markShape)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_SHAPE_SESAME);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_OPEN);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextEmphasis.POSITION_OUTSIDE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFilledDotOutside() {
|
||||||
|
String value = "filled dot outside";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape")
|
||||||
|
.that(textEmphasis.markShape)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_SHAPE_DOT);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_FILLED);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextEmphasis.POSITION_OUTSIDE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFilledCircleOutside() {
|
||||||
|
String value = "filled circle outside";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape")
|
||||||
|
.that(textEmphasis.markShape)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_SHAPE_CIRCLE);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_FILLED);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextEmphasis.POSITION_OUTSIDE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFilledSesameOutside() {
|
||||||
|
String value = "filled sesame outside";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape")
|
||||||
|
.that(textEmphasis.markShape)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_SHAPE_SESAME);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_FILLED);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextEmphasis.POSITION_OUTSIDE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFilledDotAfter() {
|
||||||
|
String value = "filled dot after";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape")
|
||||||
|
.that(textEmphasis.markShape)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_SHAPE_DOT);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_FILLED);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextAnnotation.POSITION_AFTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFilledCircleAfter() {
|
||||||
|
String value = "filled circle after";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape")
|
||||||
|
.that(textEmphasis.markShape)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_SHAPE_CIRCLE);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_FILLED);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextAnnotation.POSITION_AFTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFilledSesameAfter() {
|
||||||
|
String value = "filled sesame after";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape")
|
||||||
|
.that(textEmphasis.markShape)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_SHAPE_SESAME);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_FILLED);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextAnnotation.POSITION_AFTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFilledDotBefore() {
|
||||||
|
String value = "filled dot before";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape")
|
||||||
|
.that(textEmphasis.markShape)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_SHAPE_DOT);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_FILLED);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextAnnotation.POSITION_BEFORE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFilledCircleBefore() {
|
||||||
|
String value = "filled circle before";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape")
|
||||||
|
.that(textEmphasis.markShape)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_SHAPE_CIRCLE);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_FILLED);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextAnnotation.POSITION_BEFORE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFilledSesameBefore() {
|
||||||
|
String value = "filled sesame before";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape")
|
||||||
|
.that(textEmphasis.markShape)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_SHAPE_SESAME);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_FILLED);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextAnnotation.POSITION_BEFORE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBeforeFilledSesame() {
|
||||||
|
String value = "before filled sesame";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape")
|
||||||
|
.that(textEmphasis.markShape)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_SHAPE_SESAME);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_FILLED);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextAnnotation.POSITION_BEFORE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBeforeSesameFilled() {
|
||||||
|
String value = "before sesame filled";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape")
|
||||||
|
.that(textEmphasis.markShape)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_SHAPE_SESAME);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_FILLED);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextAnnotation.POSITION_BEFORE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInvalidMarkShape() {
|
||||||
|
String value = "before sesamee filled";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape")
|
||||||
|
.that(textEmphasis.markShape)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_SHAPE_CIRCLE);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_FILLED);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextAnnotation.POSITION_BEFORE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInvalidMarkFill() {
|
||||||
|
String value = "before sesame filed";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape")
|
||||||
|
.that(textEmphasis.markShape)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_SHAPE_SESAME);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_FILLED);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextAnnotation.POSITION_BEFORE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInvalidPosition() {
|
||||||
|
String value = "befour sesame filled";
|
||||||
|
@Nullable TextEmphasis textEmphasis = parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape")
|
||||||
|
.that(textEmphasis.markShape)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_SHAPE_SESAME);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_FILLED);
|
||||||
|
assertWithMessage("position").that(textEmphasis.position).isEqualTo(POSITION_OUTSIDE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testValidMixedWithInvalidDescription() {
|
||||||
|
String value = "blue open sesame foo bar after";
|
||||||
|
@Nullable TextEmphasis textEmphasis = TextEmphasis.parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape")
|
||||||
|
.that(textEmphasis.markShape)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_SHAPE_SESAME);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_OPEN);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextAnnotation.POSITION_AFTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testColorDescriptionNotSupported() {
|
||||||
|
String value = "blue";
|
||||||
|
@Nullable TextEmphasis textEmphasis = TextEmphasis.parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape").that(textEmphasis.markShape).isEqualTo(MARK_SHAPE_AUTO);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_UNKNOWN);
|
||||||
|
assertWithMessage("position").that(textEmphasis.position).isEqualTo(POSITION_OUTSIDE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testQuotedStringStyleNotSupported() {
|
||||||
|
String value = "\"x\" after";
|
||||||
|
@Nullable TextEmphasis textEmphasis = TextEmphasis.parse(value);
|
||||||
|
assertWithMessage("Text Emphasis must exist").that(textEmphasis).isNotNull();
|
||||||
|
assertWithMessage("markShape").that(textEmphasis.markShape).isEqualTo(MARK_SHAPE_AUTO);
|
||||||
|
assertWithMessage("markFill")
|
||||||
|
.that(textEmphasis.markFill)
|
||||||
|
.isEqualTo(TextEmphasisSpan.MARK_FILL_UNKNOWN);
|
||||||
|
assertWithMessage("position")
|
||||||
|
.that(textEmphasis.position)
|
||||||
|
.isEqualTo(TextAnnotation.POSITION_AFTER);
|
||||||
|
}
|
||||||
|
}
|
@ -27,7 +27,8 @@ import com.google.android.exoplayer2.testutil.TestUtil;
|
|||||||
import com.google.android.exoplayer2.text.Cue;
|
import com.google.android.exoplayer2.text.Cue;
|
||||||
import com.google.android.exoplayer2.text.Subtitle;
|
import com.google.android.exoplayer2.text.Subtitle;
|
||||||
import com.google.android.exoplayer2.text.SubtitleDecoderException;
|
import com.google.android.exoplayer2.text.SubtitleDecoderException;
|
||||||
import com.google.android.exoplayer2.text.span.RubySpan;
|
import com.google.android.exoplayer2.text.span.TextAnnotation;
|
||||||
|
import com.google.android.exoplayer2.text.span.TextEmphasisSpan;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.ColorParser;
|
import com.google.android.exoplayer2.util.ColorParser;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -67,6 +68,7 @@ public final class TtmlDecoderTest {
|
|||||||
private static final String VERTICAL_TEXT_FILE = "media/ttml/vertical_text.xml";
|
private static final String VERTICAL_TEXT_FILE = "media/ttml/vertical_text.xml";
|
||||||
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";
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void inlineAttributes() throws IOException, SubtitleDecoderException {
|
public void inlineAttributes() throws IOException, SubtitleDecoderException {
|
||||||
@ -109,12 +111,12 @@ public final class TtmlDecoderTest {
|
|||||||
* framework level. Tests that <i>lime</i> resolves to <code>#FF00FF00</code> not <code>#00FF00
|
* framework level. Tests that <i>lime</i> resolves to <code>#FF00FF00</code> not <code>#00FF00
|
||||||
* </code>.
|
* </code>.
|
||||||
*
|
*
|
||||||
|
* @throws IOException thrown if reading subtitle file fails.
|
||||||
* @see <a
|
* @see <a
|
||||||
* href="https://github.com/android/platform_frameworks_base/blob/jb-mr2-release/graphics/java/android/graphics/Color.java#L414">
|
* href="https://github.com/android/platform_frameworks_base/blob/jb-mr2-release/graphics/java/android/graphics/Color.java#L414">
|
||||||
* JellyBean Color</a> <a
|
* JellyBean Color</a> <a
|
||||||
* href="https://github.com/android/platform_frameworks_base/blob/kitkat-mr2.2-release/graphics/java/android/graphics/Color.java#L414">
|
* href="https://github.com/android/platform_frameworks_base/blob/kitkat-mr2.2-release/graphics/java/android/graphics/Color.java#L414">
|
||||||
* Kitkat Color</a>
|
* Kitkat Color</a>
|
||||||
* @throws IOException thrown if reading subtitle file fails.
|
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void lime() throws IOException, SubtitleDecoderException {
|
public void lime() throws IOException, SubtitleDecoderException {
|
||||||
@ -646,16 +648,16 @@ public final class TtmlDecoderTest {
|
|||||||
assertThat(firstCue.toString()).isEqualTo("Cue with annotated text.");
|
assertThat(firstCue.toString()).isEqualTo("Cue with annotated text.");
|
||||||
assertThat(firstCue)
|
assertThat(firstCue)
|
||||||
.hasRubySpanBetween("Cue with ".length(), "Cue with annotated".length())
|
.hasRubySpanBetween("Cue with ".length(), "Cue with annotated".length())
|
||||||
.withTextAndPosition("1st rubies", RubySpan.POSITION_OVER);
|
.withTextAndPosition("1st rubies", TextAnnotation.POSITION_BEFORE);
|
||||||
assertThat(firstCue)
|
assertThat(firstCue)
|
||||||
.hasRubySpanBetween("Cue with annotated ".length(), "Cue with annotated text".length())
|
.hasRubySpanBetween("Cue with annotated ".length(), "Cue with annotated text".length())
|
||||||
.withTextAndPosition("2nd rubies", RubySpan.POSITION_UNKNOWN);
|
.withTextAndPosition("2nd rubies", TextAnnotation.POSITION_UNKNOWN);
|
||||||
|
|
||||||
Spanned secondCue = getOnlyCueTextAtTimeUs(subtitle, 20_000_000);
|
Spanned secondCue = getOnlyCueTextAtTimeUs(subtitle, 20_000_000);
|
||||||
assertThat(secondCue.toString()).isEqualTo("Cue with annotated text.");
|
assertThat(secondCue.toString()).isEqualTo("Cue with annotated text.");
|
||||||
assertThat(secondCue)
|
assertThat(secondCue)
|
||||||
.hasRubySpanBetween("Cue with ".length(), "Cue with annotated".length())
|
.hasRubySpanBetween("Cue with ".length(), "Cue with annotated".length())
|
||||||
.withTextAndPosition("rubies", RubySpan.POSITION_UNKNOWN);
|
.withTextAndPosition("rubies", TextAnnotation.POSITION_UNKNOWN);
|
||||||
|
|
||||||
Spanned thirdCue = getOnlyCueTextAtTimeUs(subtitle, 30_000_000);
|
Spanned thirdCue = getOnlyCueTextAtTimeUs(subtitle, 30_000_000);
|
||||||
assertThat(thirdCue.toString()).isEqualTo("Cue with annotated text.");
|
assertThat(thirdCue.toString()).isEqualTo("Cue with annotated text.");
|
||||||
@ -674,6 +676,146 @@ public final class TtmlDecoderTest {
|
|||||||
assertThat(sixthCue).hasNoRubySpanBetween(0, sixthCue.length());
|
assertThat(sixthCue).hasNoRubySpanBetween(0, sixthCue.length());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void textEmphasis() throws IOException, SubtitleDecoderException {
|
||||||
|
TtmlSubtitle subtitle = getSubtitle(TEXT_EMPHASIS_FILE);
|
||||||
|
|
||||||
|
Spanned firstCue = getOnlyCueTextAtTimeUs(subtitle, 10_000_000);
|
||||||
|
assertThat(firstCue)
|
||||||
|
.hasTextEmphasisSpanBetween("None ".length(), "None おはよ".length())
|
||||||
|
.withMarkAndPosition(
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_NONE,
|
||||||
|
TextEmphasisSpan.MARK_FILL_UNKNOWN,
|
||||||
|
TextAnnotation.POSITION_BEFORE);
|
||||||
|
|
||||||
|
Spanned secondCue = getOnlyCueTextAtTimeUs(subtitle, 20_000_000);
|
||||||
|
assertThat(secondCue)
|
||||||
|
.hasTextEmphasisSpanBetween("Auto ".length(), "Auto ございます".length())
|
||||||
|
.withMarkAndPosition(
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_CIRCLE,
|
||||||
|
TextEmphasisSpan.MARK_FILL_FILLED,
|
||||||
|
TextAnnotation.POSITION_BEFORE);
|
||||||
|
|
||||||
|
Spanned thirdCue = getOnlyCueTextAtTimeUs(subtitle, 30_000_000);
|
||||||
|
assertThat(thirdCue)
|
||||||
|
.hasTextEmphasisSpanBetween("Filled circle ".length(), "Filled circle こんばんは".length())
|
||||||
|
.withMarkAndPosition(
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_CIRCLE,
|
||||||
|
TextEmphasisSpan.MARK_FILL_FILLED,
|
||||||
|
TextAnnotation.POSITION_BEFORE);
|
||||||
|
|
||||||
|
Spanned fourthCue = getOnlyCueTextAtTimeUs(subtitle, 40_000_000);
|
||||||
|
assertThat(fourthCue)
|
||||||
|
.hasTextEmphasisSpanBetween("Filled dot ".length(), "Filled dot ございます".length())
|
||||||
|
.withMarkAndPosition(
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_DOT,
|
||||||
|
TextEmphasisSpan.MARK_FILL_FILLED,
|
||||||
|
TextAnnotation.POSITION_BEFORE);
|
||||||
|
|
||||||
|
Spanned fifthCue = getOnlyCueTextAtTimeUs(subtitle, 50_000_000);
|
||||||
|
assertThat(fifthCue)
|
||||||
|
.hasTextEmphasisSpanBetween("Filled sesame ".length(), "Filled sesame おはよ".length())
|
||||||
|
.withMarkAndPosition(
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_SESAME,
|
||||||
|
TextEmphasisSpan.MARK_FILL_FILLED,
|
||||||
|
TextAnnotation.POSITION_BEFORE);
|
||||||
|
|
||||||
|
Spanned sixthCue = getOnlyCueTextAtTimeUs(subtitle, 60_000_000);
|
||||||
|
assertThat(sixthCue)
|
||||||
|
.hasTextEmphasisSpanBetween(
|
||||||
|
"Open circle before ".length(), "Open circle before ございます".length())
|
||||||
|
.withMarkAndPosition(
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_CIRCLE,
|
||||||
|
TextEmphasisSpan.MARK_FILL_OPEN,
|
||||||
|
TextAnnotation.POSITION_BEFORE);
|
||||||
|
|
||||||
|
Spanned seventhCue = getOnlyCueTextAtTimeUs(subtitle, 70_000_000);
|
||||||
|
assertThat(seventhCue)
|
||||||
|
.hasTextEmphasisSpanBetween("Open dot after ".length(), "Open dot after おはよ".length())
|
||||||
|
.withMarkAndPosition(
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_DOT,
|
||||||
|
TextEmphasisSpan.MARK_FILL_OPEN,
|
||||||
|
TextAnnotation.POSITION_AFTER);
|
||||||
|
|
||||||
|
Spanned eighthCue = getOnlyCueTextAtTimeUs(subtitle, 80_000_000);
|
||||||
|
assertThat(eighthCue)
|
||||||
|
.hasTextEmphasisSpanBetween(
|
||||||
|
"Open sesame outside ".length(), "Open sesame outside ございます".length())
|
||||||
|
.withMarkAndPosition(
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_SESAME,
|
||||||
|
TextEmphasisSpan.MARK_FILL_OPEN,
|
||||||
|
TextAnnotation.POSITION_BEFORE);
|
||||||
|
|
||||||
|
Spanned ninthCue = getOnlyCueTextAtTimeUs(subtitle, 90_000_000);
|
||||||
|
assertThat(ninthCue)
|
||||||
|
.hasTextEmphasisSpanBetween("Auto outside ".length(), "Auto outside おはよ".length())
|
||||||
|
.withMarkAndPosition(
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_CIRCLE,
|
||||||
|
TextEmphasisSpan.MARK_FILL_FILLED,
|
||||||
|
TextAnnotation.POSITION_BEFORE);
|
||||||
|
|
||||||
|
Spanned tenthCue = getOnlyCueTextAtTimeUs(subtitle, 100_000_000);
|
||||||
|
assertThat(tenthCue)
|
||||||
|
.hasTextEmphasisSpanBetween("Circle before ".length(), "Circle before ございます".length())
|
||||||
|
.withMarkAndPosition(
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_CIRCLE,
|
||||||
|
TextEmphasisSpan.MARK_FILL_FILLED,
|
||||||
|
TextAnnotation.POSITION_BEFORE);
|
||||||
|
|
||||||
|
Spanned eleventhCue = getOnlyCueTextAtTimeUs(subtitle, 110_000_000);
|
||||||
|
assertThat(eleventhCue)
|
||||||
|
.hasTextEmphasisSpanBetween("Sesame after ".length(), "Sesame after おはよ".length())
|
||||||
|
.withMarkAndPosition(
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_SESAME,
|
||||||
|
TextEmphasisSpan.MARK_FILL_FILLED,
|
||||||
|
TextAnnotation.POSITION_AFTER);
|
||||||
|
|
||||||
|
Spanned twelfthCue = getOnlyCueTextAtTimeUs(subtitle, 120_000_000);
|
||||||
|
assertThat(twelfthCue)
|
||||||
|
.hasTextEmphasisSpanBetween("Dot outside ".length(), "Dot outside ございます".length())
|
||||||
|
.withMarkAndPosition(
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_DOT,
|
||||||
|
TextEmphasisSpan.MARK_FILL_FILLED,
|
||||||
|
TextAnnotation.POSITION_BEFORE);
|
||||||
|
|
||||||
|
Spanned thirteenthCue = getOnlyCueTextAtTimeUs(subtitle, 130_000_000);
|
||||||
|
assertThat(thirteenthCue)
|
||||||
|
.hasNoTextEmphasisSpanBetween(
|
||||||
|
"No textEmphasis property ".length(), "No textEmphasis property おはよ".length());
|
||||||
|
|
||||||
|
Spanned fourteenthCue = getOnlyCueTextAtTimeUs(subtitle, 140_000_000);
|
||||||
|
assertThat(fourteenthCue)
|
||||||
|
.hasTextEmphasisSpanBetween("Auto (TBLR) ".length(), "Auto (TBLR) ございます".length())
|
||||||
|
.withMarkAndPosition(
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_SESAME,
|
||||||
|
TextEmphasisSpan.MARK_FILL_FILLED,
|
||||||
|
TextAnnotation.POSITION_BEFORE);
|
||||||
|
|
||||||
|
Spanned fifteenthCue = getOnlyCueTextAtTimeUs(subtitle, 150_000_000);
|
||||||
|
assertThat(fifteenthCue)
|
||||||
|
.hasTextEmphasisSpanBetween("Auto (TBRL) ".length(), "Auto (TBRL) おはよ".length())
|
||||||
|
.withMarkAndPosition(
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_SESAME,
|
||||||
|
TextEmphasisSpan.MARK_FILL_FILLED,
|
||||||
|
TextAnnotation.POSITION_BEFORE);
|
||||||
|
|
||||||
|
Spanned sixteenthCue = getOnlyCueTextAtTimeUs(subtitle, 160_000_000);
|
||||||
|
assertThat(sixteenthCue)
|
||||||
|
.hasTextEmphasisSpanBetween("Auto (TB) ".length(), "Auto (TB) ございます".length())
|
||||||
|
.withMarkAndPosition(
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_SESAME,
|
||||||
|
TextEmphasisSpan.MARK_FILL_FILLED,
|
||||||
|
TextAnnotation.POSITION_BEFORE);
|
||||||
|
|
||||||
|
Spanned seventeenthCue = getOnlyCueTextAtTimeUs(subtitle, 170_000_000);
|
||||||
|
assertThat(seventeenthCue)
|
||||||
|
.hasTextEmphasisSpanBetween("Auto (LR) ".length(), "Auto (LR) おはよ".length())
|
||||||
|
.withMarkAndPosition(
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_CIRCLE,
|
||||||
|
TextEmphasisSpan.MARK_FILL_FILLED,
|
||||||
|
TextAnnotation.POSITION_BEFORE);
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
package com.google.android.exoplayer2.text.ttml;
|
package com.google.android.exoplayer2.text.ttml;
|
||||||
|
|
||||||
import static android.graphics.Color.BLACK;
|
import static android.graphics.Color.BLACK;
|
||||||
|
import static com.google.android.exoplayer2.text.span.TextAnnotation.POSITION_BEFORE;
|
||||||
import static com.google.android.exoplayer2.text.ttml.TtmlStyle.STYLE_BOLD;
|
import static com.google.android.exoplayer2.text.ttml.TtmlStyle.STYLE_BOLD;
|
||||||
import static com.google.android.exoplayer2.text.ttml.TtmlStyle.STYLE_BOLD_ITALIC;
|
import static com.google.android.exoplayer2.text.ttml.TtmlStyle.STYLE_BOLD_ITALIC;
|
||||||
import static com.google.android.exoplayer2.text.ttml.TtmlStyle.STYLE_ITALIC;
|
import static com.google.android.exoplayer2.text.ttml.TtmlStyle.STYLE_ITALIC;
|
||||||
@ -28,7 +29,8 @@ import android.graphics.Color;
|
|||||||
import android.text.Layout;
|
import android.text.Layout;
|
||||||
import androidx.annotation.ColorInt;
|
import androidx.annotation.ColorInt;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import com.google.android.exoplayer2.text.span.RubySpan;
|
import com.google.android.exoplayer2.text.span.TextAnnotation;
|
||||||
|
import com.google.android.exoplayer2.text.span.TextEmphasisSpan;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
@ -43,9 +45,10 @@ public final class TtmlStyleTest {
|
|||||||
@TtmlStyle.FontSizeUnit private static final int FONT_SIZE_UNIT = TtmlStyle.FONT_SIZE_UNIT_EM;
|
@TtmlStyle.FontSizeUnit private static final int FONT_SIZE_UNIT = TtmlStyle.FONT_SIZE_UNIT_EM;
|
||||||
@ColorInt private static final int BACKGROUND_COLOR = Color.BLACK;
|
@ColorInt private static final int BACKGROUND_COLOR = Color.BLACK;
|
||||||
private static final int RUBY_TYPE = TtmlStyle.RUBY_TYPE_TEXT;
|
private static final int RUBY_TYPE = TtmlStyle.RUBY_TYPE_TEXT;
|
||||||
private static final int RUBY_POSITION = RubySpan.POSITION_UNDER;
|
private static final int RUBY_POSITION = TextAnnotation.POSITION_AFTER;
|
||||||
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";
|
||||||
|
|
||||||
private final TtmlStyle populatedStyle =
|
private final TtmlStyle populatedStyle =
|
||||||
new TtmlStyle()
|
new TtmlStyle()
|
||||||
@ -62,7 +65,8 @@ public final class TtmlStyleTest {
|
|||||||
.setRubyType(RUBY_TYPE)
|
.setRubyType(RUBY_TYPE)
|
||||||
.setRubyPosition(RUBY_POSITION)
|
.setRubyPosition(RUBY_POSITION)
|
||||||
.setTextAlign(TEXT_ALIGN)
|
.setTextAlign(TEXT_ALIGN)
|
||||||
.setTextCombine(TEXT_COMBINE);
|
.setTextCombine(TEXT_COMBINE)
|
||||||
|
.setTextEmphasis(TextEmphasis.parse(TEXT_EMPHASIS_STYLE));
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void inheritStyle() {
|
public void inheritStyle() {
|
||||||
@ -86,6 +90,10 @@ public final class TtmlStyleTest {
|
|||||||
assertWithMessage("backgroundColor should not be inherited")
|
assertWithMessage("backgroundColor should not be inherited")
|
||||||
.that(style.hasBackgroundColor())
|
.that(style.hasBackgroundColor())
|
||||||
.isFalse();
|
.isFalse();
|
||||||
|
assertThat(style.getTextEmphasis()).isNotNull();
|
||||||
|
assertThat(style.getTextEmphasis().markShape).isEqualTo(TextEmphasisSpan.MARK_SHAPE_DOT);
|
||||||
|
assertThat(style.getTextEmphasis().markFill).isEqualTo(TextEmphasisSpan.MARK_FILL_FILLED);
|
||||||
|
assertThat(style.getTextEmphasis().position).isEqualTo(POSITION_BEFORE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -109,6 +117,10 @@ public final class TtmlStyleTest {
|
|||||||
.that(style.getBackgroundColor())
|
.that(style.getBackgroundColor())
|
||||||
.isEqualTo(BACKGROUND_COLOR);
|
.isEqualTo(BACKGROUND_COLOR);
|
||||||
assertWithMessage("rubyType should be chained").that(style.getRubyType()).isEqualTo(RUBY_TYPE);
|
assertWithMessage("rubyType should be chained").that(style.getRubyType()).isEqualTo(RUBY_TYPE);
|
||||||
|
assertThat(style.getTextEmphasis()).isNotNull();
|
||||||
|
assertThat(style.getTextEmphasis().markShape).isEqualTo(TextEmphasisSpan.MARK_SHAPE_DOT);
|
||||||
|
assertThat(style.getTextEmphasis().markFill).isEqualTo(TextEmphasisSpan.MARK_FILL_FILLED);
|
||||||
|
assertThat(style.getTextEmphasis().position).isEqualTo(POSITION_BEFORE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -221,9 +233,9 @@ public final class TtmlStyleTest {
|
|||||||
public void rubyPosition() {
|
public void rubyPosition() {
|
||||||
TtmlStyle style = new TtmlStyle();
|
TtmlStyle style = new TtmlStyle();
|
||||||
|
|
||||||
assertThat(style.getRubyPosition()).isEqualTo(RubySpan.POSITION_UNKNOWN);
|
assertThat(style.getRubyPosition()).isEqualTo(TextAnnotation.POSITION_UNKNOWN);
|
||||||
style.setRubyPosition(RubySpan.POSITION_OVER);
|
style.setRubyPosition(POSITION_BEFORE);
|
||||||
assertThat(style.getRubyPosition()).isEqualTo(RubySpan.POSITION_OVER);
|
assertThat(style.getRubyPosition()).isEqualTo(POSITION_BEFORE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -245,4 +257,14 @@ public final class TtmlStyleTest {
|
|||||||
style.setTextCombine(true);
|
style.setTextCombine(true);
|
||||||
assertThat(style.getTextCombine()).isTrue();
|
assertThat(style.getTextCombine()).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void textEmphasis() {
|
||||||
|
TtmlStyle style = new TtmlStyle();
|
||||||
|
assertThat(style.getTextEmphasis()).isNull();
|
||||||
|
style.setTextEmphasis(TextEmphasis.parse("open sesame after"));
|
||||||
|
assertThat(style.getTextEmphasis().markShape).isEqualTo(TextEmphasisSpan.MARK_SHAPE_SESAME);
|
||||||
|
assertThat(style.getTextEmphasis().markFill).isEqualTo(TextEmphasisSpan.MARK_FILL_OPEN);
|
||||||
|
assertThat(style.getTextEmphasis().position).isEqualTo(TextAnnotation.POSITION_AFTER);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|||||||
import com.google.android.exoplayer2.testutil.TestUtil;
|
import com.google.android.exoplayer2.testutil.TestUtil;
|
||||||
import com.google.android.exoplayer2.text.Cue;
|
import com.google.android.exoplayer2.text.Cue;
|
||||||
import com.google.android.exoplayer2.text.SubtitleDecoderException;
|
import com.google.android.exoplayer2.text.SubtitleDecoderException;
|
||||||
import com.google.android.exoplayer2.text.span.RubySpan;
|
import com.google.android.exoplayer2.text.span.TextAnnotation;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.ColorParser;
|
import com.google.android.exoplayer2.util.ColorParser;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
@ -349,7 +349,7 @@ public class WebvttDecoderTest {
|
|||||||
assertThat(firstCue.text.toString()).isEqualTo("Some text with over-ruby.");
|
assertThat(firstCue.text.toString()).isEqualTo("Some text with over-ruby.");
|
||||||
assertThat((Spanned) firstCue.text)
|
assertThat((Spanned) firstCue.text)
|
||||||
.hasRubySpanBetween("Some ".length(), "Some text with over-ruby".length())
|
.hasRubySpanBetween("Some ".length(), "Some text with over-ruby".length())
|
||||||
.withTextAndPosition("over", RubySpan.POSITION_OVER);
|
.withTextAndPosition("over", TextAnnotation.POSITION_BEFORE);
|
||||||
|
|
||||||
// Check that `under` is read from CSS and unspecified defaults to `over`.
|
// Check that `under` is read from CSS and unspecified defaults to `over`.
|
||||||
Cue secondCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(2)));
|
Cue secondCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(2)));
|
||||||
@ -357,25 +357,25 @@ public class WebvttDecoderTest {
|
|||||||
.isEqualTo("Some text with under-ruby and over-ruby (default).");
|
.isEqualTo("Some text with under-ruby and over-ruby (default).");
|
||||||
assertThat((Spanned) secondCue.text)
|
assertThat((Spanned) secondCue.text)
|
||||||
.hasRubySpanBetween("Some ".length(), "Some text with under-ruby".length())
|
.hasRubySpanBetween("Some ".length(), "Some text with under-ruby".length())
|
||||||
.withTextAndPosition("under", RubySpan.POSITION_UNDER);
|
.withTextAndPosition("under", TextAnnotation.POSITION_AFTER);
|
||||||
assertThat((Spanned) secondCue.text)
|
assertThat((Spanned) secondCue.text)
|
||||||
.hasRubySpanBetween(
|
.hasRubySpanBetween(
|
||||||
"Some text with under-ruby and ".length(),
|
"Some text with under-ruby and ".length(),
|
||||||
"Some text with under-ruby and over-ruby (default)".length())
|
"Some text with under-ruby and over-ruby (default)".length())
|
||||||
.withTextAndPosition("over", RubySpan.POSITION_OVER);
|
.withTextAndPosition("over", TextAnnotation.POSITION_BEFORE);
|
||||||
|
|
||||||
// Check many <rt> tags with different positions nested in a single <ruby> span.
|
// Check many <rt> tags with different positions nested in a single <ruby> span.
|
||||||
Cue thirdCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(4)));
|
Cue thirdCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(4)));
|
||||||
assertThat(thirdCue.text.toString()).isEqualTo("base1base2base3.");
|
assertThat(thirdCue.text.toString()).isEqualTo("base1base2base3.");
|
||||||
assertThat((Spanned) thirdCue.text)
|
assertThat((Spanned) thirdCue.text)
|
||||||
.hasRubySpanBetween(/* start= */ 0, "base1".length())
|
.hasRubySpanBetween(/* start= */ 0, "base1".length())
|
||||||
.withTextAndPosition("over1", RubySpan.POSITION_OVER);
|
.withTextAndPosition("over1", TextAnnotation.POSITION_BEFORE);
|
||||||
assertThat((Spanned) thirdCue.text)
|
assertThat((Spanned) thirdCue.text)
|
||||||
.hasRubySpanBetween("base1".length(), "base1base2".length())
|
.hasRubySpanBetween("base1".length(), "base1base2".length())
|
||||||
.withTextAndPosition("under2", RubySpan.POSITION_UNDER);
|
.withTextAndPosition("under2", TextAnnotation.POSITION_AFTER);
|
||||||
assertThat((Spanned) thirdCue.text)
|
assertThat((Spanned) thirdCue.text)
|
||||||
.hasRubySpanBetween("base1base2".length(), "base1base2base3".length())
|
.hasRubySpanBetween("base1base2".length(), "base1base2base3".length())
|
||||||
.withTextAndPosition("under3", RubySpan.POSITION_UNDER);
|
.withTextAndPosition("under3", TextAnnotation.POSITION_AFTER);
|
||||||
|
|
||||||
// Check a <ruby> span with no <rt> tags.
|
// Check a <ruby> span with no <rt> tags.
|
||||||
Cue fourthCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(6)));
|
Cue fourthCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(6)));
|
||||||
|
@ -31,6 +31,8 @@ import android.util.SparseArray;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.text.span.HorizontalTextInVerticalContextSpan;
|
import com.google.android.exoplayer2.text.span.HorizontalTextInVerticalContextSpan;
|
||||||
import com.google.android.exoplayer2.text.span.RubySpan;
|
import com.google.android.exoplayer2.text.span.RubySpan;
|
||||||
|
import com.google.android.exoplayer2.text.span.TextAnnotation;
|
||||||
|
import com.google.android.exoplayer2.text.span.TextEmphasisSpan;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
@ -186,17 +188,26 @@ import java.util.regex.Pattern;
|
|||||||
} else if (span instanceof RubySpan) {
|
} else if (span instanceof RubySpan) {
|
||||||
RubySpan rubySpan = (RubySpan) span;
|
RubySpan rubySpan = (RubySpan) span;
|
||||||
switch (rubySpan.position) {
|
switch (rubySpan.position) {
|
||||||
case RubySpan.POSITION_OVER:
|
case TextAnnotation.POSITION_BEFORE:
|
||||||
return "<ruby style='ruby-position:over;'>";
|
return "<ruby style='ruby-position:over;'>";
|
||||||
case RubySpan.POSITION_UNDER:
|
case TextAnnotation.POSITION_AFTER:
|
||||||
return "<ruby style='ruby-position:under;'>";
|
return "<ruby style='ruby-position:under;'>";
|
||||||
case RubySpan.POSITION_UNKNOWN:
|
case TextAnnotation.POSITION_UNKNOWN:
|
||||||
return "<ruby style='ruby-position:unset;'>";
|
return "<ruby style='ruby-position:unset;'>";
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} else if (span instanceof UnderlineSpan) {
|
} else if (span instanceof UnderlineSpan) {
|
||||||
return "<u>";
|
return "<u>";
|
||||||
|
} else if (span instanceof TextEmphasisSpan) {
|
||||||
|
TextEmphasisSpan textEmphasisSpan = (TextEmphasisSpan) span;
|
||||||
|
String style = getTextEmphasisStyle(textEmphasisSpan.markShape, textEmphasisSpan.markFill);
|
||||||
|
String position = getTextEmphasisPosition(textEmphasisSpan.position);
|
||||||
|
return Util.formatInvariant(
|
||||||
|
"<span style='-webkit-text-emphasis-style:%1$s;text-emphasis-style:%1$s;"
|
||||||
|
+ "-webkit-text-emphasis-position:%2$s;text-emphasis-position:%2$s;"
|
||||||
|
+ "display:inline-block;'>",
|
||||||
|
style, position);
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -209,7 +220,8 @@ import java.util.regex.Pattern;
|
|||||||
|| span instanceof BackgroundColorSpan
|
|| span instanceof BackgroundColorSpan
|
||||||
|| span instanceof HorizontalTextInVerticalContextSpan
|
|| span instanceof HorizontalTextInVerticalContextSpan
|
||||||
|| span instanceof AbsoluteSizeSpan
|
|| span instanceof AbsoluteSizeSpan
|
||||||
|| span instanceof RelativeSizeSpan) {
|
|| span instanceof RelativeSizeSpan
|
||||||
|
|| span instanceof TextEmphasisSpan) {
|
||||||
return "</span>";
|
return "</span>";
|
||||||
} else if (span instanceof TypefaceSpan) {
|
} else if (span instanceof TypefaceSpan) {
|
||||||
@Nullable String fontFamily = ((TypefaceSpan) span).getFamily();
|
@Nullable String fontFamily = ((TypefaceSpan) span).getFamily();
|
||||||
@ -232,6 +244,52 @@ import java.util.regex.Pattern;
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String getTextEmphasisStyle(
|
||||||
|
@TextEmphasisSpan.MarkShape int shape, @TextEmphasisSpan.MarkFill int fill) {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
switch (fill) {
|
||||||
|
case TextEmphasisSpan.MARK_FILL_FILLED:
|
||||||
|
builder.append("filled ");
|
||||||
|
break;
|
||||||
|
case TextEmphasisSpan.MARK_FILL_OPEN:
|
||||||
|
builder.append("open ");
|
||||||
|
break;
|
||||||
|
case TextEmphasisSpan.MARK_FILL_UNKNOWN:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (shape) {
|
||||||
|
case TextEmphasisSpan.MARK_SHAPE_CIRCLE:
|
||||||
|
builder.append("circle");
|
||||||
|
break;
|
||||||
|
case TextEmphasisSpan.MARK_SHAPE_DOT:
|
||||||
|
builder.append("dot");
|
||||||
|
break;
|
||||||
|
case TextEmphasisSpan.MARK_SHAPE_SESAME:
|
||||||
|
builder.append("sesame");
|
||||||
|
break;
|
||||||
|
case TextEmphasisSpan.MARK_SHAPE_NONE:
|
||||||
|
builder.append("none");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
builder.append("unset");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getTextEmphasisPosition(@TextAnnotation.Position int position) {
|
||||||
|
switch (position) {
|
||||||
|
case TextAnnotation.POSITION_AFTER:
|
||||||
|
return "under left";
|
||||||
|
case TextAnnotation.POSITION_UNKNOWN:
|
||||||
|
case TextAnnotation.POSITION_BEFORE:
|
||||||
|
default:
|
||||||
|
return "over right";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static Transition getOrCreate(SparseArray<Transition> transitions, int key) {
|
private static Transition getOrCreate(SparseArray<Transition> transitions, int key) {
|
||||||
@Nullable Transition transition = transitions.get(key);
|
@Nullable Transition transition = transitions.get(key);
|
||||||
if (transition == null) {
|
if (transition == null) {
|
||||||
|
@ -34,6 +34,8 @@ import androidx.test.core.app.ApplicationProvider;
|
|||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import com.google.android.exoplayer2.text.span.HorizontalTextInVerticalContextSpan;
|
import com.google.android.exoplayer2.text.span.HorizontalTextInVerticalContextSpan;
|
||||||
import com.google.android.exoplayer2.text.span.RubySpan;
|
import com.google.android.exoplayer2.text.span.RubySpan;
|
||||||
|
import com.google.android.exoplayer2.text.span.TextAnnotation;
|
||||||
|
import com.google.android.exoplayer2.text.span.TextEmphasisSpan;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.robolectric.annotation.Config;
|
import org.robolectric.annotation.Config;
|
||||||
@ -250,12 +252,12 @@ public class SpannedToHtmlConverterTest {
|
|||||||
SpannableString spanned =
|
SpannableString spanned =
|
||||||
new SpannableString("String with over-annotated and under-annotated section");
|
new SpannableString("String with over-annotated and under-annotated section");
|
||||||
spanned.setSpan(
|
spanned.setSpan(
|
||||||
new RubySpan("ruby-text", RubySpan.POSITION_OVER),
|
new RubySpan("ruby-text", TextAnnotation.POSITION_BEFORE),
|
||||||
"String with ".length(),
|
"String with ".length(),
|
||||||
"String with over-annotated".length(),
|
"String with over-annotated".length(),
|
||||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
spanned.setSpan(
|
spanned.setSpan(
|
||||||
new RubySpan("non-àscìì-text", RubySpan.POSITION_UNDER),
|
new RubySpan("non-àscìì-text", TextAnnotation.POSITION_AFTER),
|
||||||
"String with over-annotated and ".length(),
|
"String with over-annotated and ".length(),
|
||||||
"String with over-annotated and under-annotated".length(),
|
"String with over-annotated and under-annotated".length(),
|
||||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
@ -279,6 +281,42 @@ public class SpannedToHtmlConverterTest {
|
|||||||
+ "section");
|
+ "section");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void convert_supportsTextEmphasisSpan() {
|
||||||
|
SpannableString spanned = new SpannableString("Text emphasis おはよ ございます");
|
||||||
|
spanned.setSpan(
|
||||||
|
new TextEmphasisSpan(
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_CIRCLE,
|
||||||
|
TextEmphasisSpan.MARK_FILL_FILLED,
|
||||||
|
TextAnnotation.POSITION_BEFORE),
|
||||||
|
"Text emphasis ".length(),
|
||||||
|
"Text emphasis おはよ".length(),
|
||||||
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
|
||||||
|
spanned.setSpan(
|
||||||
|
new TextEmphasisSpan(
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_SESAME,
|
||||||
|
TextEmphasisSpan.MARK_FILL_OPEN,
|
||||||
|
TextAnnotation.POSITION_AFTER),
|
||||||
|
"Text emphasis おはよ ".length(),
|
||||||
|
"Text emphasis おはよ ございます".length(),
|
||||||
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
|
||||||
|
SpannedToHtmlConverter.HtmlAndCss htmlAndCss =
|
||||||
|
SpannedToHtmlConverter.convert(spanned, displayDensity);
|
||||||
|
|
||||||
|
assertThat(htmlAndCss.cssRuleSets).isEmpty();
|
||||||
|
assertThat(htmlAndCss.html)
|
||||||
|
.isEqualTo(
|
||||||
|
"Text emphasis <span style='"
|
||||||
|
+ "-webkit-text-emphasis-style:filled circle;text-emphasis-style:filled circle;"
|
||||||
|
+ "-webkit-text-emphasis-position:over right;text-emphasis-position:over right;"
|
||||||
|
+ "display:inline-block;'>おはよ</span> <span style='"
|
||||||
|
+ "-webkit-text-emphasis-style:open sesame;text-emphasis-style:open sesame;"
|
||||||
|
+ "-webkit-text-emphasis-position:under left;text-emphasis-position:under left;"
|
||||||
|
+ "display:inline-block;'>ございます</span>");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void convert_supportsUnderlineSpan() {
|
public void convert_supportsUnderlineSpan() {
|
||||||
SpannableString spanned = new SpannableString("String with underlined section.");
|
SpannableString spanned = new SpannableString("String with underlined section.");
|
||||||
|
65
testdata/src/test/assets/media/ttml/text_emphasis.xml
vendored
Normal file
65
testdata/src/test/assets/media/ttml/text_emphasis.xml
vendored
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<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">
|
||||||
|
<head>
|
||||||
|
<region xml:id="region_tbrl" tts:extent="80.000% 80.000%" tts:origin="10.000% 10.000%" tts:writingMode="tbrl"/>
|
||||||
|
<region xml:id="region_tblr" tts:extent="80.000% 80.000%" tts:origin="10.000% 10.000%" tts:writingMode="tblr"/>
|
||||||
|
<region xml:id="region_tb" tts:extent="80.000% 80.000%" tts:origin="10.000% 10.000%" tts:writingMode="tb"/>
|
||||||
|
<region xml:id="region_lr" tts:extent="80.000% 80.000%" tts:origin="10.000% 10.000%" tts:writingMode="lr"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<p begin="10s" end="18s">None <span tts:textEmphasis="none">おはよ</span></p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p begin="20s" end="28s">Auto <span tts:textEmphasis="auto">ございます</span></p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p begin="30s" end="38s">Filled circle <span tts:textEmphasis="filled circle">こんばんは</span></p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p begin="40s" end="48s">Filled dot <span tts:textEmphasis="filled dot">ございます</span></p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p begin="50s" end="58s">Filled sesame <span tts:textEmphasis="filled sesame">おはよ</span></p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p begin="60s" end="68s">Open circle before <span tts:textEmphasis="open circle before">ございます</span></p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p begin="70s" end="78s">Open dot after <span tts:textEmphasis="open dot after">おはよ</span></p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p begin="80s" end="88s">Open sesame outside <span tts:textEmphasis="open sesame outside">ございます</span></p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p begin="90s" end="98s">Auto outside <span tts:textEmphasis="auto outside">おはよ</span></p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p begin="100s" end="108s">Circle before <span tts:textEmphasis="circle before">ございます</span></p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p begin="110s" end="118s">Sesame after <span tts:textEmphasis="sesame after">おはよ</span></p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p begin="120s" end="128s">Dot outside <span tts:textEmphasis="dot outside">ございます</span></p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p begin="130s" end="138s">No textEmphasis property <span>おはよ</span></p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p begin="140s" end="148s" region="region_tbrl">Auto (TBLR) <span tts:textEmphasis="auto">ございます</span></p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p begin="150s" end="158s" region="region_tblr">Auto (TBRL) <span tts:textEmphasis="auto">おはよ</span></p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p begin="160s" end="168s" region="region_tb">Auto (TB) <span tts:textEmphasis="auto">ございます</span></p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p begin="170s" end="178s" region="region_lr">Auto (LR) <span tts:textEmphasis="auto">おはよ</span></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</tt>
|
@ -38,6 +38,8 @@ import androidx.annotation.ColorInt;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.text.span.HorizontalTextInVerticalContextSpan;
|
import com.google.android.exoplayer2.text.span.HorizontalTextInVerticalContextSpan;
|
||||||
import com.google.android.exoplayer2.text.span.RubySpan;
|
import com.google.android.exoplayer2.text.span.RubySpan;
|
||||||
|
import com.google.android.exoplayer2.text.span.TextAnnotation;
|
||||||
|
import com.google.android.exoplayer2.text.span.TextEmphasisSpan;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import com.google.common.truth.Fact;
|
import com.google.common.truth.Fact;
|
||||||
import com.google.common.truth.FailureMetadata;
|
import com.google.common.truth.FailureMetadata;
|
||||||
@ -578,6 +580,44 @@ public final class SpannedSubject extends Subject {
|
|||||||
return ALREADY_FAILED_WITH_FLAGS;
|
return ALREADY_FAILED_WITH_FLAGS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks that the subject has an {@link TextEmphasisSpan} from {@code start} to {@code end}.
|
||||||
|
*
|
||||||
|
* @param start The start of the expected span.
|
||||||
|
* @param end The end of the expected span.
|
||||||
|
* @return A {@link EmphasizedText} object for optional additional assertions on the flags.
|
||||||
|
*/
|
||||||
|
public EmphasizedText hasTextEmphasisSpanBetween(int start, int end) {
|
||||||
|
if (actual == null) {
|
||||||
|
failWithoutActual(simpleFact("Spanned must not be null"));
|
||||||
|
return ALREADY_FAILED_WITH_MARK_AND_POSITION;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<TextEmphasisSpan> textEmphasisSpans =
|
||||||
|
findMatchingSpans(start, end, TextEmphasisSpan.class);
|
||||||
|
if (textEmphasisSpans.size() == 1) {
|
||||||
|
return check("TextEmphasisSpan (start=%s,end=%s)", start, end)
|
||||||
|
.about(textEmphasisSubjects(actual))
|
||||||
|
.that(textEmphasisSpans);
|
||||||
|
}
|
||||||
|
failWithExpectedSpan(
|
||||||
|
start, end, TextEmphasisSpan.class, actual.toString().substring(start, end));
|
||||||
|
return ALREADY_FAILED_WITH_MARK_AND_POSITION;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks that the subject has no {@link TextEmphasisSpan}s on any of the text between {@code
|
||||||
|
* start} and {@code end}.
|
||||||
|
*
|
||||||
|
* <p>This fails even if the start and end indexes don't exactly match.
|
||||||
|
*
|
||||||
|
* @param start The start index to start searching for spans.
|
||||||
|
* @param end The end index to stop searching for spans.
|
||||||
|
*/
|
||||||
|
public void hasNoTextEmphasisSpanBetween(int start, int end) {
|
||||||
|
hasNoSpansOfTypeBetween(TextEmphasisSpan.class, start, end);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks that the subject has no {@link HorizontalTextInVerticalContextSpan}s on any of the text
|
* Checks that the subject has no {@link HorizontalTextInVerticalContextSpan}s on any of the text
|
||||||
* between {@code start} and {@code end}.
|
* between {@code start} and {@code end}.
|
||||||
@ -1033,7 +1073,7 @@ public final class SpannedSubject extends Subject {
|
|||||||
* @param position The expected position of the text.
|
* @param position The expected position of the text.
|
||||||
* @return A {@link WithSpanFlags} object for optional additional assertions on the flags.
|
* @return A {@link WithSpanFlags} object for optional additional assertions on the flags.
|
||||||
*/
|
*/
|
||||||
AndSpanFlags withTextAndPosition(String text, @RubySpan.Position int position);
|
AndSpanFlags withTextAndPosition(String text, @TextAnnotation.Position int position);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final RubyText ALREADY_FAILED_WITH_TEXT =
|
private static final RubyText ALREADY_FAILED_WITH_TEXT =
|
||||||
@ -1057,7 +1097,7 @@ public final class SpannedSubject extends Subject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AndSpanFlags withTextAndPosition(String text, @RubySpan.Position int position) {
|
public AndSpanFlags withTextAndPosition(String text, @TextAnnotation.Position int position) {
|
||||||
List<Integer> matchingSpanFlags = new ArrayList<>();
|
List<Integer> matchingSpanFlags = new ArrayList<>();
|
||||||
List<TextAndPosition> spanTextsAndPositions = new ArrayList<>();
|
List<TextAndPosition> spanTextsAndPositions = new ArrayList<>();
|
||||||
for (RubySpan span : actualSpans) {
|
for (RubySpan span : actualSpans) {
|
||||||
@ -1074,7 +1114,7 @@ public final class SpannedSubject extends Subject {
|
|||||||
|
|
||||||
private static final class TextAndPosition {
|
private static final class TextAndPosition {
|
||||||
private final String text;
|
private final String text;
|
||||||
@RubySpan.Position private final int position;
|
@TextAnnotation.Position private final int position;
|
||||||
|
|
||||||
private TextAndPosition(String text, int position) {
|
private TextAndPosition(String text, int position) {
|
||||||
this.text = text;
|
this.text = text;
|
||||||
@ -1110,4 +1150,108 @@ public final class SpannedSubject extends Subject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Allows assertions about a span's text emphasis mark and its position. */
|
||||||
|
public interface EmphasizedText {
|
||||||
|
/**
|
||||||
|
* Checks that at least one of the matched spans has the expected {@code mark} and {@code
|
||||||
|
* position}.
|
||||||
|
*
|
||||||
|
* @param markShape The expected mark shape.
|
||||||
|
* @param markFill The expected mark fill style.
|
||||||
|
* @param position The expected position of the mark.
|
||||||
|
* @return A {@link AndSpanFlags} object for optional additional assertions on the flags.
|
||||||
|
*/
|
||||||
|
AndSpanFlags withMarkAndPosition(
|
||||||
|
@TextEmphasisSpan.MarkShape int markShape,
|
||||||
|
@TextEmphasisSpan.MarkFill int markFill,
|
||||||
|
@TextAnnotation.Position int position);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final EmphasizedText ALREADY_FAILED_WITH_MARK_AND_POSITION =
|
||||||
|
(markShape, markFill, position) -> ALREADY_FAILED_AND_FLAGS;
|
||||||
|
|
||||||
|
private static Factory<TextEmphasisSubject, List<TextEmphasisSpan>> textEmphasisSubjects(
|
||||||
|
Spanned actualSpanned) {
|
||||||
|
return (FailureMetadata metadata, List<TextEmphasisSpan> spans) ->
|
||||||
|
new TextEmphasisSubject(metadata, spans, actualSpanned);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class TextEmphasisSubject extends Subject implements EmphasizedText {
|
||||||
|
|
||||||
|
private final List<TextEmphasisSpan> actualSpans;
|
||||||
|
private final Spanned actualSpanned;
|
||||||
|
|
||||||
|
private TextEmphasisSubject(
|
||||||
|
FailureMetadata metadata, List<TextEmphasisSpan> actualSpans, Spanned actualSpanned) {
|
||||||
|
super(metadata, actualSpans);
|
||||||
|
this.actualSpans = actualSpans;
|
||||||
|
this.actualSpanned = actualSpanned;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AndSpanFlags withMarkAndPosition(
|
||||||
|
@TextEmphasisSpan.MarkShape int markShape,
|
||||||
|
@TextEmphasisSpan.MarkFill int markFill,
|
||||||
|
@TextAnnotation.Position int position) {
|
||||||
|
List<Integer> matchingSpanFlags = new ArrayList<>();
|
||||||
|
List<MarkAndPosition> textEmphasisMarksAndPositions = new ArrayList<>();
|
||||||
|
for (TextEmphasisSpan span : actualSpans) {
|
||||||
|
textEmphasisMarksAndPositions.add(
|
||||||
|
new MarkAndPosition(span.markShape, span.markFill, span.position));
|
||||||
|
if (span.markFill == markFill && span.markShape == markShape && span.position == position) {
|
||||||
|
matchingSpanFlags.add(actualSpanned.getSpanFlags(span));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check("textEmphasisMarkAndPosition")
|
||||||
|
.that(textEmphasisMarksAndPositions)
|
||||||
|
.containsExactly(new MarkAndPosition(markShape, markFill, position));
|
||||||
|
return check("flags").about(spanFlags()).that(matchingSpanFlags);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class MarkAndPosition {
|
||||||
|
|
||||||
|
@TextEmphasisSpan.MarkShape private final int markShape;
|
||||||
|
@TextEmphasisSpan.MarkFill private final int markFill;
|
||||||
|
@TextAnnotation.Position private final int position;
|
||||||
|
|
||||||
|
private MarkAndPosition(
|
||||||
|
@TextEmphasisSpan.MarkShape int markShape,
|
||||||
|
@TextEmphasisSpan.MarkFill int markFill,
|
||||||
|
@TextAnnotation.Position int position) {
|
||||||
|
this.markFill = markFill;
|
||||||
|
this.markShape = markShape;
|
||||||
|
this.position = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(@Nullable Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextEmphasisSubject.MarkAndPosition that = (TextEmphasisSubject.MarkAndPosition) o;
|
||||||
|
return (position == that.position)
|
||||||
|
&& (markShape == that.markShape)
|
||||||
|
&& (markFill == that.markFill);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = markShape;
|
||||||
|
result = 31 * result + markFill;
|
||||||
|
result = 31 * result + position;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format(
|
||||||
|
"{markShape=%s,markFill=%s,position=%s}", markShape, markFill, position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,9 @@ import com.google.android.exoplayer2.testutil.truth.SpannedSubject.AndSpanFlags;
|
|||||||
import com.google.android.exoplayer2.testutil.truth.SpannedSubject.WithSpanFlags;
|
import com.google.android.exoplayer2.testutil.truth.SpannedSubject.WithSpanFlags;
|
||||||
import com.google.android.exoplayer2.text.span.HorizontalTextInVerticalContextSpan;
|
import com.google.android.exoplayer2.text.span.HorizontalTextInVerticalContextSpan;
|
||||||
import com.google.android.exoplayer2.text.span.RubySpan;
|
import com.google.android.exoplayer2.text.span.RubySpan;
|
||||||
|
import com.google.android.exoplayer2.text.span.TextAnnotation;
|
||||||
|
import com.google.android.exoplayer2.text.span.TextEmphasisSpan;
|
||||||
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import com.google.common.truth.ExpectFailure;
|
import com.google.common.truth.ExpectFailure;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
@ -607,23 +610,26 @@ public class SpannedSubjectTest {
|
|||||||
public void rubySpan_success() {
|
public void rubySpan_success() {
|
||||||
SpannableString spannable =
|
SpannableString spannable =
|
||||||
createSpannable(
|
createSpannable(
|
||||||
new RubySpan("ruby text", RubySpan.POSITION_OVER), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
|
new RubySpan("ruby text", TextAnnotation.POSITION_BEFORE),
|
||||||
|
Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
|
||||||
|
|
||||||
assertThat(spannable)
|
assertThat(spannable)
|
||||||
.hasRubySpanBetween(SPAN_START, SPAN_END)
|
.hasRubySpanBetween(SPAN_START, SPAN_END)
|
||||||
.withTextAndPosition("ruby text", RubySpan.POSITION_OVER)
|
.withTextAndPosition("ruby text", TextAnnotation.POSITION_BEFORE)
|
||||||
.andFlags(Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
|
.andFlags(Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void rubySpan_wrongEndIndex() {
|
public void rubySpan_wrongEndIndex() {
|
||||||
checkHasSpanFailsDueToIndexMismatch(
|
checkHasSpanFailsDueToIndexMismatch(
|
||||||
new RubySpan("ruby text", RubySpan.POSITION_OVER), SpannedSubject::hasRubySpanBetween);
|
new RubySpan("ruby text", TextAnnotation.POSITION_BEFORE),
|
||||||
|
SpannedSubject::hasRubySpanBetween);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void rubySpan_wrongText() {
|
public void rubySpan_wrongText() {
|
||||||
SpannableString spannable = createSpannable(new RubySpan("ruby text", RubySpan.POSITION_OVER));
|
SpannableString spannable =
|
||||||
|
createSpannable(new RubySpan("ruby text", TextAnnotation.POSITION_BEFORE));
|
||||||
|
|
||||||
AssertionError expected =
|
AssertionError expected =
|
||||||
expectFailure(
|
expectFailure(
|
||||||
@ -631,7 +637,7 @@ public class SpannedSubjectTest {
|
|||||||
whenTesting
|
whenTesting
|
||||||
.that(spannable)
|
.that(spannable)
|
||||||
.hasRubySpanBetween(SPAN_START, SPAN_END)
|
.hasRubySpanBetween(SPAN_START, SPAN_END)
|
||||||
.withTextAndPosition("incorrect text", RubySpan.POSITION_OVER));
|
.withTextAndPosition("incorrect text", TextAnnotation.POSITION_BEFORE));
|
||||||
|
|
||||||
assertThat(expected).factValue("value of").contains("rubyTextAndPosition");
|
assertThat(expected).factValue("value of").contains("rubyTextAndPosition");
|
||||||
assertThat(expected).factValue("expected").contains("text='incorrect text'");
|
assertThat(expected).factValue("expected").contains("text='incorrect text'");
|
||||||
@ -640,7 +646,8 @@ public class SpannedSubjectTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void rubySpan_wrongPosition() {
|
public void rubySpan_wrongPosition() {
|
||||||
SpannableString spannable = createSpannable(new RubySpan("ruby text", RubySpan.POSITION_OVER));
|
SpannableString spannable =
|
||||||
|
createSpannable(new RubySpan("ruby text", TextAnnotation.POSITION_BEFORE));
|
||||||
|
|
||||||
AssertionError expected =
|
AssertionError expected =
|
||||||
expectFailure(
|
expectFailure(
|
||||||
@ -648,27 +655,32 @@ public class SpannedSubjectTest {
|
|||||||
whenTesting
|
whenTesting
|
||||||
.that(spannable)
|
.that(spannable)
|
||||||
.hasRubySpanBetween(SPAN_START, SPAN_END)
|
.hasRubySpanBetween(SPAN_START, SPAN_END)
|
||||||
.withTextAndPosition("ruby text", RubySpan.POSITION_UNDER));
|
.withTextAndPosition("ruby text", TextAnnotation.POSITION_AFTER));
|
||||||
|
|
||||||
assertThat(expected).factValue("value of").contains("rubyTextAndPosition");
|
assertThat(expected).factValue("value of").contains("rubyTextAndPosition");
|
||||||
assertThat(expected).factValue("expected").contains("position=" + RubySpan.POSITION_UNDER);
|
assertThat(expected)
|
||||||
assertThat(expected).factValue("but was").contains("position=" + RubySpan.POSITION_OVER);
|
.factValue("expected")
|
||||||
|
.contains("position=" + TextAnnotation.POSITION_AFTER);
|
||||||
|
assertThat(expected)
|
||||||
|
.factValue("but was")
|
||||||
|
.contains("position=" + TextAnnotation.POSITION_BEFORE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void rubySpan_wrongFlags() {
|
public void rubySpan_wrongFlags() {
|
||||||
checkHasSpanFailsDueToFlagMismatch(
|
checkHasSpanFailsDueToFlagMismatch(
|
||||||
new RubySpan("ruby text", RubySpan.POSITION_OVER),
|
new RubySpan("ruby text", TextAnnotation.POSITION_BEFORE),
|
||||||
(subject, start, end) ->
|
(subject, start, end) ->
|
||||||
subject
|
subject
|
||||||
.hasRubySpanBetween(start, end)
|
.hasRubySpanBetween(start, end)
|
||||||
.withTextAndPosition("ruby text", RubySpan.POSITION_OVER));
|
.withTextAndPosition("ruby text", TextAnnotation.POSITION_BEFORE));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void noRubySpan_success() {
|
public void noRubySpan_success() {
|
||||||
SpannableString spannable =
|
SpannableString spannable =
|
||||||
createSpannableWithUnrelatedSpanAnd(new RubySpan("ruby text", RubySpan.POSITION_OVER));
|
createSpannableWithUnrelatedSpanAnd(
|
||||||
|
new RubySpan("ruby text", TextAnnotation.POSITION_BEFORE));
|
||||||
|
|
||||||
assertThat(spannable).hasNoRubySpanBetween(UNRELATED_SPAN_START, UNRELATED_SPAN_END);
|
assertThat(spannable).hasNoRubySpanBetween(UNRELATED_SPAN_START, UNRELATED_SPAN_END);
|
||||||
}
|
}
|
||||||
@ -676,7 +688,191 @@ public class SpannedSubjectTest {
|
|||||||
@Test
|
@Test
|
||||||
public void noRubySpan_failure() {
|
public void noRubySpan_failure() {
|
||||||
checkHasNoSpanFails(
|
checkHasNoSpanFails(
|
||||||
new RubySpan("ruby text", RubySpan.POSITION_OVER), SpannedSubject::hasNoRubySpanBetween);
|
new RubySpan("ruby text", TextAnnotation.POSITION_BEFORE),
|
||||||
|
SpannedSubject::hasNoRubySpanBetween);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void textEmphasis_success() {
|
||||||
|
SpannableString spannable =
|
||||||
|
createSpannable(
|
||||||
|
new TextEmphasisSpan(
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_CIRCLE,
|
||||||
|
TextEmphasisSpan.MARK_FILL_FILLED,
|
||||||
|
TextAnnotation.POSITION_AFTER));
|
||||||
|
|
||||||
|
assertThat(spannable)
|
||||||
|
.hasTextEmphasisSpanBetween(SPAN_START, SPAN_END)
|
||||||
|
.withMarkAndPosition(
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_CIRCLE,
|
||||||
|
TextEmphasisSpan.MARK_FILL_FILLED,
|
||||||
|
TextAnnotation.POSITION_AFTER)
|
||||||
|
.andFlags(Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void textEmphasis_wrongIndex() {
|
||||||
|
checkHasSpanFailsDueToIndexMismatch(
|
||||||
|
new TextEmphasisSpan(
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_CIRCLE,
|
||||||
|
TextEmphasisSpan.MARK_FILL_FILLED,
|
||||||
|
TextAnnotation.POSITION_AFTER),
|
||||||
|
SpannedSubject::hasTextEmphasisSpanBetween);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void textEmphasis_wrongMarkShape() {
|
||||||
|
SpannableString spannable =
|
||||||
|
createSpannable(
|
||||||
|
new TextEmphasisSpan(
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_CIRCLE,
|
||||||
|
TextEmphasisSpan.MARK_FILL_FILLED,
|
||||||
|
TextAnnotation.POSITION_AFTER));
|
||||||
|
|
||||||
|
AssertionError expected =
|
||||||
|
expectFailure(
|
||||||
|
whenTesting ->
|
||||||
|
whenTesting
|
||||||
|
.that(spannable)
|
||||||
|
.hasTextEmphasisSpanBetween(SPAN_START, SPAN_END)
|
||||||
|
.withMarkAndPosition(
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_SESAME,
|
||||||
|
TextEmphasisSpan.MARK_FILL_FILLED,
|
||||||
|
TextAnnotation.POSITION_AFTER));
|
||||||
|
|
||||||
|
assertThat(expected).factValue("value of").contains("textEmphasisMarkAndPosition");
|
||||||
|
assertThat(expected)
|
||||||
|
.factValue("expected")
|
||||||
|
.contains(
|
||||||
|
Util.formatInvariant(
|
||||||
|
"{markShape=%d,markFill=%d,position=%d}",
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_SESAME,
|
||||||
|
TextEmphasisSpan.MARK_FILL_FILLED,
|
||||||
|
TextAnnotation.POSITION_AFTER));
|
||||||
|
assertThat(expected)
|
||||||
|
.factValue("but was")
|
||||||
|
.contains(
|
||||||
|
Util.formatInvariant(
|
||||||
|
"{markShape=%d,markFill=%d,position=%d}",
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_CIRCLE,
|
||||||
|
TextEmphasisSpan.MARK_FILL_FILLED,
|
||||||
|
TextAnnotation.POSITION_AFTER));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void textEmphasis_wrongMarkFill() {
|
||||||
|
SpannableString spannable =
|
||||||
|
createSpannable(
|
||||||
|
new TextEmphasisSpan(
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_CIRCLE,
|
||||||
|
TextEmphasisSpan.MARK_FILL_FILLED,
|
||||||
|
TextAnnotation.POSITION_AFTER));
|
||||||
|
|
||||||
|
AssertionError expected =
|
||||||
|
expectFailure(
|
||||||
|
whenTesting ->
|
||||||
|
whenTesting
|
||||||
|
.that(spannable)
|
||||||
|
.hasTextEmphasisSpanBetween(SPAN_START, SPAN_END)
|
||||||
|
.withMarkAndPosition(
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_CIRCLE,
|
||||||
|
TextEmphasisSpan.MARK_FILL_OPEN,
|
||||||
|
TextAnnotation.POSITION_AFTER));
|
||||||
|
|
||||||
|
assertThat(expected).factValue("value of").contains("textEmphasisMarkAndPosition");
|
||||||
|
assertThat(expected)
|
||||||
|
.factValue("expected")
|
||||||
|
.contains(
|
||||||
|
Util.formatInvariant(
|
||||||
|
"{markShape=%d,markFill=%d,position=%d}",
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_CIRCLE,
|
||||||
|
TextEmphasisSpan.MARK_FILL_OPEN,
|
||||||
|
TextAnnotation.POSITION_AFTER));
|
||||||
|
assertThat(expected)
|
||||||
|
.factValue("but was")
|
||||||
|
.contains(
|
||||||
|
Util.formatInvariant(
|
||||||
|
"{markShape=%d,markFill=%d,position=%d}",
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_CIRCLE,
|
||||||
|
TextEmphasisSpan.MARK_FILL_FILLED,
|
||||||
|
TextAnnotation.POSITION_AFTER));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void textEmphasis_wrongPosition() {
|
||||||
|
SpannableString spannable =
|
||||||
|
createSpannable(
|
||||||
|
new TextEmphasisSpan(
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_CIRCLE,
|
||||||
|
TextEmphasisSpan.MARK_FILL_FILLED,
|
||||||
|
TextAnnotation.POSITION_BEFORE));
|
||||||
|
|
||||||
|
AssertionError expected =
|
||||||
|
expectFailure(
|
||||||
|
whenTesting ->
|
||||||
|
whenTesting
|
||||||
|
.that(spannable)
|
||||||
|
.hasTextEmphasisSpanBetween(SPAN_START, SPAN_END)
|
||||||
|
.withMarkAndPosition(
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_CIRCLE,
|
||||||
|
TextEmphasisSpan.MARK_FILL_FILLED,
|
||||||
|
TextAnnotation.POSITION_AFTER));
|
||||||
|
|
||||||
|
assertThat(expected).factValue("value of").contains("textEmphasisMarkAndPosition");
|
||||||
|
assertThat(expected)
|
||||||
|
.factValue("expected")
|
||||||
|
.contains(
|
||||||
|
Util.formatInvariant(
|
||||||
|
"{markShape=%d,markFill=%d,position=%d}",
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_CIRCLE,
|
||||||
|
TextEmphasisSpan.MARK_FILL_FILLED,
|
||||||
|
TextAnnotation.POSITION_AFTER));
|
||||||
|
assertThat(expected)
|
||||||
|
.factValue("but was")
|
||||||
|
.contains(
|
||||||
|
Util.formatInvariant(
|
||||||
|
"{markShape=%d,markFill=%d,position=%d}",
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_CIRCLE,
|
||||||
|
TextEmphasisSpan.MARK_FILL_FILLED,
|
||||||
|
TextAnnotation.POSITION_BEFORE));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void textEmphasis_wrongFlags() {
|
||||||
|
checkHasSpanFailsDueToFlagMismatch(
|
||||||
|
new TextEmphasisSpan(
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_CIRCLE,
|
||||||
|
TextEmphasisSpan.MARK_FILL_FILLED,
|
||||||
|
TextAnnotation.POSITION_AFTER),
|
||||||
|
(subject, start, end) ->
|
||||||
|
subject
|
||||||
|
.hasTextEmphasisSpanBetween(start, end)
|
||||||
|
.withMarkAndPosition(
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_CIRCLE,
|
||||||
|
TextEmphasisSpan.MARK_FILL_FILLED,
|
||||||
|
TextAnnotation.POSITION_AFTER));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void noTextEmphasis_success() {
|
||||||
|
SpannableString spannable =
|
||||||
|
createSpannableWithUnrelatedSpanAnd(
|
||||||
|
new TextEmphasisSpan(
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_CIRCLE,
|
||||||
|
TextEmphasisSpan.MARK_FILL_FILLED,
|
||||||
|
TextAnnotation.POSITION_AFTER));
|
||||||
|
|
||||||
|
assertThat(spannable).hasNoTextEmphasisSpanBetween(UNRELATED_SPAN_START, UNRELATED_SPAN_END);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void noTextEmphasis_failure() {
|
||||||
|
checkHasNoSpanFails(
|
||||||
|
new TextEmphasisSpan(
|
||||||
|
TextEmphasisSpan.MARK_SHAPE_CIRCLE,
|
||||||
|
TextEmphasisSpan.MARK_FILL_FILLED,
|
||||||
|
TextAnnotation.POSITION_AFTER),
|
||||||
|
SpannedSubject::hasNoTextEmphasisSpanBetween);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
Loading…
x
Reference in New Issue
Block a user