Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
SDK
/
exoplayer
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Settings
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
65ab0085
authored
Mar 24, 2021
by
Ian Baker
Committed by
marcbaechinger
Apr 09, 2021
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Merge pull request #8653 from dlafayet:textemphasis
PiperOrigin-RevId: 364363882
parent
871c24b9
Hide whitespace changes
Inline
Side-by-side
Showing
21 changed files
with
1940 additions
and
127 deletions
RELEASENOTES.md
library/core/src/main/java/com/google/android/exoplayer2/text/span/RubySpan.java
library/core/src/main/java/com/google/android/exoplayer2/text/span/TextAnnotation.java
library/core/src/main/java/com/google/android/exoplayer2/text/span/TextEmphasisSpan.java
library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TextEmphasis.java
library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java
library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java
library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlRenderUtil.java
library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlStyle.java
library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/CssParser.java
library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCssStyle.java
library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParser.java
library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TextEmphasisTest.java
library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlDecoderTest.java
library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlStyleTest.java
library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoderTest.java
library/ui/src/main/java/com/google/android/exoplayer2/ui/SpannedToHtmlConverter.java
library/ui/src/test/java/com/google/android/exoplayer2/ui/SpannedToHtmlConverterTest.java
testdata/src/test/assets/media/ttml/text_emphasis.xml
testutils/src/main/java/com/google/android/exoplayer2/testutil/truth/SpannedSubject.java
testutils/src/test/java/com/google/android/exoplayer2/testutil/truth/SpannedSubjectTest.java
RELEASENOTES.md
View file @
65ab0085
...
...
@@ -67,6 +67,7 @@
[
#8456
](
https://github.com/google/ExoPlayer/issues/8456
)
).
*
Fix CEA-708 priority handling to sort cues in the order defined by the
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
only. The
`TimelineQueueEditor`
uses a new
`MediaDescriptionConverter`
for
this purpose and does not rely on the
`ConcatenatingMediaSource`
anymore.
...
...
library/core/src/main/java/com/google/android/exoplayer2/text/span/RubySpan.java
View file @
65ab0085
...
...
@@ -16,12 +16,6 @@
*/
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.
*
...
...
@@ -38,48 +32,13 @@ import java.lang.annotation.Retention;
// rubies (e.g. HTML <rp> tag).
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. */
public
final
String
rubyText
;
/** 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
.
position
=
position
;
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/text/span/TextAnnotation.java
0 → 100644
View file @
65ab0085
/*
* 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
()
{}
}
library/core/src/main/java/com/google/android/exoplayer2/text/span/TextEmphasisSpan.java
0 → 100644
View file @
65ab0085
/*
* 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
;
}
}
library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TextEmphasis.java
0 → 100644
View file @
65ab0085
/*
* 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
);
}
}
library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java
View file @
65ab0085
...
...
@@ -22,7 +22,7 @@ import com.google.android.exoplayer2.text.Cue;
import
com.google.android.exoplayer2.text.SimpleSubtitleDecoder
;
import
com.google.android.exoplayer2.text.Subtitle
;
import
com.google.android.exoplayer2.text.SubtitleDecoderException
;
import
com.google.android.exoplayer2.text.span.
RubySpa
n
;
import
com.google.android.exoplayer2.text.span.
TextAnnotatio
n
;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.ColorParser
;
import
com.google.android.exoplayer2.util.Log
;
...
...
@@ -582,11 +582,11 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
break
;
case
TtmlNode
.
ATTR_TTS_RUBY_POSITION
:
switch
(
Util
.
toLowerInvariant
(
attributeValue
))
{
case
TtmlNode
.
RUBY
_BEFORE
:
style
=
createIfNull
(
style
).
setRubyPosition
(
RubySpan
.
POSITION_OVER
);
case
TtmlNode
.
ANNOTATION_POSITION
_BEFORE
:
style
=
createIfNull
(
style
).
setRubyPosition
(
TextAnnotation
.
POSITION_BEFORE
);
break
;
case
TtmlNode
.
RUBY
_AFTER
:
style
=
createIfNull
(
style
).
setRubyPosition
(
RubySpan
.
POSITION_UND
ER
);
case
TtmlNode
.
ANNOTATION_POSITION
_AFTER
:
style
=
createIfNull
(
style
).
setRubyPosition
(
TextAnnotation
.
POSITION_AFT
ER
);
break
;
default
:
// ignore
...
...
@@ -609,6 +609,11 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
break
;
}
break
;
case
TtmlNode
.
ATTR_TTS_TEXT_EMPHASIS
:
style
=
createIfNull
(
style
)
.
setTextEmphasis
(
TextEmphasis
.
parse
(
Util
.
toLowerInvariant
(
attributeValue
)));
break
;
default
:
// ignore
break
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java
View file @
65ab0085
...
...
@@ -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_ALIGN
=
"textAlign"
;
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"
;
// 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_DELIMITER
=
"delimiter"
;
// Values for rubyPosition
public
static
final
String
RUBY_BEFORE
=
"before"
;
public
static
final
String
RUBY_AFTER
=
"after"
;
// Values for text annotation (i.e. ruby, text emphasis) position
public
static
final
String
ANNOTATION_POSITION_BEFORE
=
"before"
;
public
static
final
String
ANNOTATION_POSITION_AFTER
=
"after"
;
public
static
final
String
ANNOTATION_POSITION_OUTSIDE
=
"outside"
;
// Values for textDecoration
public
static
final
String
LINETHROUGH
=
"linethrough"
;
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_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
text
;
public
final
boolean
isTextNode
;
...
...
@@ -243,7 +255,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
TreeMap
<
String
,
Cue
.
Builder
>
regionTextOutputs
=
new
TreeMap
<>();
traverseForText
(
timeUs
,
false
,
regionId
,
regionTextOutputs
);
traverseForStyle
(
timeUs
,
globalStyles
,
regionTextOutputs
);
traverseForStyle
(
timeUs
,
globalStyles
,
region
Map
,
regionId
,
region
TextOutputs
);
List
<
Cue
>
cues
=
new
ArrayList
<>();
...
...
@@ -354,26 +366,39 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
}
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
))
{
return
;
}
String
resolvedRegionId
=
ANONYMOUS_REGION_ID
.
equals
(
regionId
)
?
inheritedRegion
:
regionId
;
for
(
Map
.
Entry
<
String
,
Integer
>
entry
:
nodeEndsByRegion
.
entrySet
())
{
String
regionId
=
entry
.
getKey
();
int
start
=
nodeStartsByRegion
.
containsKey
(
regionId
)
?
nodeStartsByRegion
.
get
(
regionId
)
:
0
;
int
end
=
entry
.
getValue
();
if
(
start
!=
end
)
{
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
)
{
getChild
(
i
).
traverseForStyle
(
timeUs
,
globalStyles
,
regionOutputs
);
getChild
(
i
)
.
traverseForStyle
(
timeUs
,
globalStyles
,
regionMaps
,
resolvedRegionId
,
regionOutputs
);
}
}
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
SpannableStringBuilder
text
=
(
SpannableStringBuilder
)
regionOutput
.
getText
();
if
(
text
==
null
)
{
...
...
@@ -381,7 +406,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
regionOutput
.
setText
(
text
);
}
if
(
resolvedStyle
!=
null
)
{
TtmlRenderUtil
.
applyStylesToSpan
(
text
,
start
,
end
,
resolvedStyle
,
parent
,
globalStyles
);
TtmlRenderUtil
.
applyStylesToSpan
(
text
,
start
,
end
,
resolvedStyle
,
parent
,
globalStyles
,
verticalType
);
regionOutput
.
setTextAlignment
(
resolvedStyle
.
getTextAlign
());
}
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlRenderUtil.java
View file @
65ab0085
...
...
@@ -15,6 +15,8 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
text
.
ttml
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkNotNull
;
import
android.text.Spannable
;
import
android.text.SpannableStringBuilder
;
import
android.text.Spanned
;
...
...
@@ -27,9 +29,12 @@ import android.text.style.StyleSpan;
import
android.text.style.TypefaceSpan
;
import
android.text.style.UnderlineSpan
;
import
androidx.annotation.Nullable
;
import
com.google.android.exoplayer2.text.Cue
;
import
com.google.android.exoplayer2.text.span.HorizontalTextInVerticalContextSpan
;
import
com.google.android.exoplayer2.text.span.RubySpan
;
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.Util
;
import
java.util.ArrayDeque
;
...
...
@@ -83,7 +88,8 @@ import java.util.Map;
int
end
,
TtmlStyle
style
,
@Nullable
TtmlNode
parent
,
Map
<
String
,
TtmlStyle
>
globalStyles
)
{
Map
<
String
,
TtmlStyle
>
globalStyles
,
@Cue
.
VerticalType
int
verticalType
)
{
if
(
style
.
getStyle
()
!=
TtmlStyle
.
UNSPECIFIED
)
{
builder
.
setSpan
(
new
StyleSpan
(
style
.
getStyle
()),
start
,
end
,
...
...
@@ -119,6 +125,40 @@ import java.util.Map;
end
,
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
())
{
case
TtmlStyle
.
RUBY_TYPE_BASE
:
// 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.
@
RubySpa
n
.
Position
@
TextAnnotatio
n
.
Position
int
rubyPosition
=
containerNode
.
style
!=
null
?
containerNode
.
style
.
getRubyPosition
()
:
RubySpa
n
.
POSITION_UNKNOWN
;
:
TextAnnotatio
n
.
POSITION_UNKNOWN
;
builder
.
setSpan
(
new
RubySpan
(
rubyText
,
rubyPosition
),
start
,
end
,
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
);
break
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlStyle.java
View file @
65ab0085
...
...
@@ -19,7 +19,7 @@ import android.graphics.Typeface;
import
android.text.Layout
;
import
androidx.annotation.IntDef
;
import
androidx.annotation.Nullable
;
import
com.google.android.exoplayer2.text.span.
RubySpa
n
;
import
com.google.android.exoplayer2.text.span.
TextAnnotatio
n
;
import
java.lang.annotation.Documented
;
import
java.lang.annotation.Retention
;
import
java.lang.annotation.RetentionPolicy
;
...
...
@@ -83,9 +83,10 @@ import java.lang.annotation.RetentionPolicy;
private
float
fontSize
;
@Nullable
private
String
id
;
@RubyType
private
int
rubyType
;
@
RubySpa
n
.
Position
private
int
rubyPosition
;
@
TextAnnotatio
n
.
Position
private
int
rubyPosition
;
@Nullable
private
Layout
.
Alignment
textAlign
;
@OptionalBoolean
private
int
textCombine
;
@Nullable
private
TextEmphasis
textEmphasis
;
public
TtmlStyle
()
{
linethrough
=
UNSPECIFIED
;
...
...
@@ -94,7 +95,7 @@ import java.lang.annotation.RetentionPolicy;
italic
=
UNSPECIFIED
;
fontSizeUnit
=
UNSPECIFIED
;
rubyType
=
UNSPECIFIED
;
rubyPosition
=
RubySpa
n
.
POSITION_UNKNOWN
;
rubyPosition
=
TextAnnotatio
n
.
POSITION_UNKNOWN
;
textCombine
=
UNSPECIFIED
;
}
...
...
@@ -225,7 +226,7 @@ import java.lang.annotation.RetentionPolicy;
if
(
underline
==
UNSPECIFIED
)
{
underline
=
ancestor
.
underline
;
}
if
(
rubyPosition
==
RubySpa
n
.
POSITION_UNKNOWN
)
{
if
(
rubyPosition
==
TextAnnotatio
n
.
POSITION_UNKNOWN
)
{
rubyPosition
=
ancestor
.
rubyPosition
;
}
if
(
textAlign
==
null
&&
ancestor
.
textAlign
!=
null
)
{
...
...
@@ -238,6 +239,9 @@ import java.lang.annotation.RetentionPolicy;
fontSizeUnit
=
ancestor
.
fontSizeUnit
;
fontSize
=
ancestor
.
fontSize
;
}
if
(
textEmphasis
==
null
)
{
textEmphasis
=
ancestor
.
textEmphasis
;
}
// attributes not inherited as of http://www.w3.org/TR/ttml1/
if
(
chaining
&&
!
hasBackgroundColor
&&
ancestor
.
hasBackgroundColor
)
{
setBackgroundColor
(
ancestor
.
backgroundColor
);
...
...
@@ -269,12 +273,12 @@ import java.lang.annotation.RetentionPolicy;
return
rubyType
;
}
public
TtmlStyle
setRubyPosition
(
@
RubySpa
n
.
Position
int
position
)
{
public
TtmlStyle
setRubyPosition
(
@
TextAnnotatio
n
.
Position
int
position
)
{
this
.
rubyPosition
=
position
;
return
this
;
}
@
RubySpa
n
.
Position
@
TextAnnotatio
n
.
Position
public
int
getRubyPosition
()
{
return
rubyPosition
;
}
...
...
@@ -299,6 +303,16 @@ import java.lang.annotation.RetentionPolicy;
return
this
;
}
@Nullable
public
TextEmphasis
getTextEmphasis
()
{
return
textEmphasis
;
}
public
TtmlStyle
setTextEmphasis
(
@Nullable
TextEmphasis
textEmphasis
)
{
this
.
textEmphasis
=
textEmphasis
;
return
this
;
}
public
TtmlStyle
setFontSize
(
float
fontSize
)
{
this
.
fontSize
=
fontSize
;
return
this
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/CssParser.java
View file @
65ab0085
...
...
@@ -17,7 +17,7 @@ package com.google.android.exoplayer2.text.webvtt;
import
android.text.TextUtils
;
import
androidx.annotation.Nullable
;
import
com.google.android.exoplayer2.text.span.
RubySpa
n
;
import
com.google.android.exoplayer2.text.span.
TextAnnotatio
n
;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.ColorParser
;
import
com.google.android.exoplayer2.util.ParsableByteArray
;
...
...
@@ -195,9 +195,9 @@ import java.util.regex.Pattern;
style
.
setBackgroundColor
(
ColorParser
.
parseCssColor
(
value
));
}
else
if
(
PROPERTY_RUBY_POSITION
.
equals
(
property
))
{
if
(
VALUE_OVER
.
equals
(
value
))
{
style
.
setRubyPosition
(
RubySpan
.
POSITION_OVER
);
style
.
setRubyPosition
(
TextAnnotation
.
POSITION_BEFORE
);
}
else
if
(
VALUE_UNDER
.
equals
(
value
))
{
style
.
setRubyPosition
(
RubySpan
.
POSITION_UND
ER
);
style
.
setRubyPosition
(
TextAnnotation
.
POSITION_AFT
ER
);
}
}
else
if
(
PROPERTY_TEXT_COMBINE_UPRIGHT
.
equals
(
property
))
{
style
.
setCombineUpright
(
VALUE_ALL
.
equals
(
value
)
||
value
.
startsWith
(
VALUE_DIGITS
));
...
...
library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCssStyle.java
View file @
65ab0085
...
...
@@ -20,7 +20,7 @@ import android.text.TextUtils;
import
androidx.annotation.ColorInt
;
import
androidx.annotation.IntDef
;
import
androidx.annotation.Nullable
;
import
com.google.android.exoplayer2.text.span.
RubySpa
n
;
import
com.google.android.exoplayer2.text.span.
TextAnnotatio
n
;
import
com.google.android.exoplayer2.util.Util
;
import
java.lang.annotation.Documented
;
import
java.lang.annotation.Retention
;
...
...
@@ -95,7 +95,7 @@ public final class WebvttCssStyle {
@OptionalBoolean
private
int
italic
;
@FontSizeUnit
private
int
fontSizeUnit
;
private
float
fontSize
;
@
RubySpa
n
.
Position
private
int
rubyPosition
;
@
TextAnnotatio
n
.
Position
private
int
rubyPosition
;
private
boolean
combineUpright
;
public
WebvttCssStyle
()
{
...
...
@@ -111,7 +111,7 @@ public final class WebvttCssStyle {
bold
=
UNSPECIFIED
;
italic
=
UNSPECIFIED
;
fontSizeUnit
=
UNSPECIFIED
;
rubyPosition
=
RubySpa
n
.
POSITION_UNKNOWN
;
rubyPosition
=
TextAnnotatio
n
.
POSITION_UNKNOWN
;
combineUpright
=
false
;
}
...
...
@@ -272,12 +272,12 @@ public final class WebvttCssStyle {
return
fontSize
;
}
public
WebvttCssStyle
setRubyPosition
(
@
RubySpa
n
.
Position
int
rubyPosition
)
{
public
WebvttCssStyle
setRubyPosition
(
@
TextAnnotatio
n
.
Position
int
rubyPosition
)
{
this
.
rubyPosition
=
rubyPosition
;
return
this
;
}
@
RubySpa
n
.
Position
@
TextAnnotatio
n
.
Position
public
int
getRubyPosition
()
{
return
rubyPosition
;
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParser.java
View file @
65ab0085
...
...
@@ -39,6 +39,7 @@ 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.RubySpan
;
import
com.google.android.exoplayer2.text.span.TextAnnotation
;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.Log
;
import
com.google.android.exoplayer2.util.ParsableByteArray
;
...
...
@@ -572,7 +573,7 @@ public final class WebvttCueParser {
StartTag
startTag
,
List
<
Element
>
nestedElements
,
List
<
WebvttCssStyle
>
styles
)
{
@
RubySpa
n
.
Position
int
rubyTagPosition
=
getRubyPosition
(
styles
,
cueId
,
startTag
);
@
TextAnnotatio
n
.
Position
int
rubyTagPosition
=
getRubyPosition
(
styles
,
cueId
,
startTag
);
List
<
Element
>
sortedNestedElements
=
new
ArrayList
<>(
nestedElements
.
size
());
sortedNestedElements
.
addAll
(
nestedElements
);
Collections
.
sort
(
sortedNestedElements
,
Element
.
BY_START_POSITION_ASC
);
...
...
@@ -585,12 +586,12 @@ public final class WebvttCueParser {
Element
rubyTextElement
=
sortedNestedElements
.
get
(
i
);
// Use the <rt> element's ruby-position if set, otherwise the <ruby> element's and otherwise
// default to OVER.
@
RubySpa
n
.
Position
@
TextAnnotatio
n
.
Position
int
rubyPosition
=
firstKnownRubyPosition
(
getRubyPosition
(
styles
,
cueId
,
rubyTextElement
.
startTag
),
rubyTagPosition
,
RubySpan
.
POSITION_OVER
);
TextAnnotation
.
POSITION_BEFORE
);
// Move the rubyText from spannedText into the RubySpan.
int
adjustedRubyTextStart
=
rubyTextElement
.
startTag
.
position
-
deletedCharCount
;
int
adjustedRubyTextEnd
=
rubyTextElement
.
endPosition
-
deletedCharCount
;
...
...
@@ -607,31 +608,31 @@ public final class WebvttCueParser {
}
}
@
RubySpa
n
.
Position
@
TextAnnotatio
n
.
Position
private
static
int
getRubyPosition
(
List
<
WebvttCssStyle
>
styles
,
@Nullable
String
cueId
,
StartTag
startTag
)
{
List
<
StyleMatch
>
styleMatches
=
getApplicableStyles
(
styles
,
cueId
,
startTag
);
for
(
int
i
=
0
;
i
<
styleMatches
.
size
();
i
++)
{
WebvttCssStyle
style
=
styleMatches
.
get
(
i
).
style
;
if
(
style
.
getRubyPosition
()
!=
RubySpa
n
.
POSITION_UNKNOWN
)
{
if
(
style
.
getRubyPosition
()
!=
TextAnnotatio
n
.
POSITION_UNKNOWN
)
{
return
style
.
getRubyPosition
();
}
}
return
RubySpa
n
.
POSITION_UNKNOWN
;
return
TextAnnotatio
n
.
POSITION_UNKNOWN
;
}
@
RubySpa
n
.
Position
@
TextAnnotatio
n
.
Position
private
static
int
firstKnownRubyPosition
(
@
RubySpa
n
.
Position
int
position1
,
@
RubySpa
n
.
Position
int
position2
,
@
RubySpa
n
.
Position
int
position3
)
{
if
(
position1
!=
RubySpa
n
.
POSITION_UNKNOWN
)
{
@
TextAnnotatio
n
.
Position
int
position1
,
@
TextAnnotatio
n
.
Position
int
position2
,
@
TextAnnotatio
n
.
Position
int
position3
)
{
if
(
position1
!=
TextAnnotatio
n
.
POSITION_UNKNOWN
)
{
return
position1
;
}
if
(
position2
!=
RubySpa
n
.
POSITION_UNKNOWN
)
{
if
(
position2
!=
TextAnnotatio
n
.
POSITION_UNKNOWN
)
{
return
position2
;
}
if
(
position3
!=
RubySpa
n
.
POSITION_UNKNOWN
)
{
if
(
position3
!=
TextAnnotatio
n
.
POSITION_UNKNOWN
)
{
return
position3
;
}
throw
new
IllegalArgumentException
();
...
...
library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TextEmphasisTest.java
0 → 100644
View file @
65ab0085
/*
* 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
);
}
}
library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlDecoderTest.java
View file @
65ab0085
...
...
@@ -27,7 +27,8 @@ import com.google.android.exoplayer2.testutil.TestUtil;
import
com.google.android.exoplayer2.text.Cue
;
import
com.google.android.exoplayer2.text.Subtitle
;
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.ColorParser
;
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
TEXT_COMBINE_FILE
=
"media/ttml/text_combine.xml"
;
private
static
final
String
RUBIES_FILE
=
"media/ttml/rubies.xml"
;
private
static
final
String
TEXT_EMPHASIS_FILE
=
"media/ttml/text_emphasis.xml"
;
@Test
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
* </code>.
*
* @throws IOException thrown if reading subtitle file fails.
* @see <a
* href="https://github.com/android/platform_frameworks_base/blob/jb-mr2-release/graphics/java/android/graphics/Color.java#L414">
* JellyBean Color</a> <a
* href="https://github.com/android/platform_frameworks_base/blob/kitkat-mr2.2-release/graphics/java/android/graphics/Color.java#L414">
* Kitkat Color</a>
* @throws IOException thrown if reading subtitle file fails.
*/
@Test
public
void
lime
()
throws
IOException
,
SubtitleDecoderException
{
...
...
@@ -646,16 +648,16 @@ public final class TtmlDecoderTest {
assertThat
(
firstCue
.
toString
()).
isEqualTo
(
"Cue with annotated text."
);
assertThat
(
firstCue
)
.
hasRubySpanBetween
(
"Cue with "
.
length
(),
"Cue with annotated"
.
length
())
.
withTextAndPosition
(
"1st rubies"
,
RubySpan
.
POSITION_OVER
);
.
withTextAndPosition
(
"1st rubies"
,
TextAnnotation
.
POSITION_BEFORE
);
assertThat
(
firstCue
)
.
hasRubySpanBetween
(
"Cue with annotated "
.
length
(),
"Cue with annotated text"
.
length
())
.
withTextAndPosition
(
"2nd rubies"
,
RubySpa
n
.
POSITION_UNKNOWN
);
.
withTextAndPosition
(
"2nd rubies"
,
TextAnnotatio
n
.
POSITION_UNKNOWN
);
Spanned
secondCue
=
getOnlyCueTextAtTimeUs
(
subtitle
,
20_000_000
);
assertThat
(
secondCue
.
toString
()).
isEqualTo
(
"Cue with annotated text."
);
assertThat
(
secondCue
)
.
hasRubySpanBetween
(
"Cue with "
.
length
(),
"Cue with annotated"
.
length
())
.
withTextAndPosition
(
"rubies"
,
RubySpa
n
.
POSITION_UNKNOWN
);
.
withTextAndPosition
(
"rubies"
,
TextAnnotatio
n
.
POSITION_UNKNOWN
);
Spanned
thirdCue
=
getOnlyCueTextAtTimeUs
(
subtitle
,
30_000_000
);
assertThat
(
thirdCue
.
toString
()).
isEqualTo
(
"Cue with annotated text."
);
...
...
@@ -674,6 +676,146 @@ public final class TtmlDecoderTest {
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
)
{
Cue
cue
=
getOnlyCueAtTimeUs
(
subtitle
,
timeUs
);
assertThat
(
cue
.
text
).
isInstanceOf
(
Spanned
.
class
);
...
...
library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlStyleTest.java
View file @
65ab0085
...
...
@@ -16,6 +16,7 @@
package
com
.
google
.
android
.
exoplayer2
.
text
.
ttml
;
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_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
androidx.annotation.ColorInt
;
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.runner.RunWith
;
...
...
@@ -43,9 +45,10 @@ public final class TtmlStyleTest {
@TtmlStyle
.
FontSizeUnit
private
static
final
int
FONT_SIZE_UNIT
=
TtmlStyle
.
FONT_SIZE_UNIT_EM
;
@ColorInt
private
static
final
int
BACKGROUND_COLOR
=
Color
.
BLACK
;
private
static
final
int
RUBY_TYPE
=
TtmlStyle
.
RUBY_TYPE_TEXT
;
private
static
final
int
RUBY_POSITION
=
RubySpan
.
POSITION_UND
ER
;
private
static
final
int
RUBY_POSITION
=
TextAnnotation
.
POSITION_AFT
ER
;
private
static
final
Layout
.
Alignment
TEXT_ALIGN
=
Layout
.
Alignment
.
ALIGN_CENTER
;
private
static
final
boolean
TEXT_COMBINE
=
true
;
public
static
final
String
TEXT_EMPHASIS_STYLE
=
"dot before"
;
private
final
TtmlStyle
populatedStyle
=
new
TtmlStyle
()
...
...
@@ -62,7 +65,8 @@ public final class TtmlStyleTest {
.
setRubyType
(
RUBY_TYPE
)
.
setRubyPosition
(
RUBY_POSITION
)
.
setTextAlign
(
TEXT_ALIGN
)
.
setTextCombine
(
TEXT_COMBINE
);
.
setTextCombine
(
TEXT_COMBINE
)
.
setTextEmphasis
(
TextEmphasis
.
parse
(
TEXT_EMPHASIS_STYLE
));
@Test
public
void
inheritStyle
()
{
...
...
@@ -86,6 +90,10 @@ public final class TtmlStyleTest {
assertWithMessage
(
"backgroundColor should not be inherited"
)
.
that
(
style
.
hasBackgroundColor
())
.
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
...
...
@@ -109,6 +117,10 @@ public final class TtmlStyleTest {
.
that
(
style
.
getBackgroundColor
())
.
isEqualTo
(
BACKGROUND_COLOR
);
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
...
...
@@ -221,9 +233,9 @@ public final class TtmlStyleTest {
public
void
rubyPosition
()
{
TtmlStyle
style
=
new
TtmlStyle
();
assertThat
(
style
.
getRubyPosition
()).
isEqualTo
(
RubySpa
n
.
POSITION_UNKNOWN
);
style
.
setRubyPosition
(
RubySpan
.
POSITION_OVER
);
assertThat
(
style
.
getRubyPosition
()).
isEqualTo
(
RubySpan
.
POSITION_OVER
);
assertThat
(
style
.
getRubyPosition
()).
isEqualTo
(
TextAnnotatio
n
.
POSITION_UNKNOWN
);
style
.
setRubyPosition
(
POSITION_BEFORE
);
assertThat
(
style
.
getRubyPosition
()).
isEqualTo
(
POSITION_BEFORE
);
}
@Test
...
...
@@ -245,4 +257,14 @@ public final class TtmlStyleTest {
style
.
setTextCombine
(
true
);
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
);
}
}
library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoderTest.java
View file @
65ab0085
...
...
@@ -26,7 +26,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import
com.google.android.exoplayer2.testutil.TestUtil
;
import
com.google.android.exoplayer2.text.Cue
;
import
com.google.android.exoplayer2.text.SubtitleDecoderException
;
import
com.google.android.exoplayer2.text.span.
RubySpa
n
;
import
com.google.android.exoplayer2.text.span.
TextAnnotatio
n
;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.ColorParser
;
import
com.google.common.collect.Iterables
;
...
...
@@ -349,7 +349,7 @@ public class WebvttDecoderTest {
assertThat
(
firstCue
.
text
.
toString
()).
isEqualTo
(
"Some text with over-ruby."
);
assertThat
((
Spanned
)
firstCue
.
text
)
.
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`.
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)."
);
assertThat
((
Spanned
)
secondCue
.
text
)
.
hasRubySpanBetween
(
"Some "
.
length
(),
"Some text with under-ruby"
.
length
())
.
withTextAndPosition
(
"under"
,
RubySpan
.
POSITION_UND
ER
);
.
withTextAndPosition
(
"under"
,
TextAnnotation
.
POSITION_AFT
ER
);
assertThat
((
Spanned
)
secondCue
.
text
)
.
hasRubySpanBetween
(
"Some text with under-ruby and "
.
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.
Cue
thirdCue
=
Iterables
.
getOnlyElement
(
subtitle
.
getCues
(
subtitle
.
getEventTime
(
4
)));
assertThat
(
thirdCue
.
text
.
toString
()).
isEqualTo
(
"base1base2base3."
);
assertThat
((
Spanned
)
thirdCue
.
text
)
.
hasRubySpanBetween
(
/* start= */
0
,
"base1"
.
length
())
.
withTextAndPosition
(
"over1"
,
RubySpan
.
POSITION_OVER
);
.
withTextAndPosition
(
"over1"
,
TextAnnotation
.
POSITION_BEFORE
);
assertThat
((
Spanned
)
thirdCue
.
text
)
.
hasRubySpanBetween
(
"base1"
.
length
(),
"base1base2"
.
length
())
.
withTextAndPosition
(
"under2"
,
RubySpan
.
POSITION_UND
ER
);
.
withTextAndPosition
(
"under2"
,
TextAnnotation
.
POSITION_AFT
ER
);
assertThat
((
Spanned
)
thirdCue
.
text
)
.
hasRubySpanBetween
(
"base1base2"
.
length
(),
"base1base2base3"
.
length
())
.
withTextAndPosition
(
"under3"
,
RubySpan
.
POSITION_UND
ER
);
.
withTextAndPosition
(
"under3"
,
TextAnnotation
.
POSITION_AFT
ER
);
// Check a <ruby> span with no <rt> tags.
Cue
fourthCue
=
Iterables
.
getOnlyElement
(
subtitle
.
getCues
(
subtitle
.
getEventTime
(
6
)));
...
...
library/ui/src/main/java/com/google/android/exoplayer2/ui/SpannedToHtmlConverter.java
View file @
65ab0085
...
...
@@ -31,6 +31,8 @@ import android.util.SparseArray;
import
androidx.annotation.Nullable
;
import
com.google.android.exoplayer2.text.span.HorizontalTextInVerticalContextSpan
;
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.Util
;
import
com.google.common.collect.ImmutableMap
;
...
...
@@ -186,17 +188,26 @@ import java.util.regex.Pattern;
}
else
if
(
span
instanceof
RubySpan
)
{
RubySpan
rubySpan
=
(
RubySpan
)
span
;
switch
(
rubySpan
.
position
)
{
case
RubySpan
.
POSITION_OVER
:
case
TextAnnotation
.
POSITION_BEFORE
:
return
"<ruby style='ruby-position:over;'>"
;
case
RubySpan
.
POSITION_UND
ER
:
case
TextAnnotation
.
POSITION_AFT
ER
:
return
"<ruby style='ruby-position:under;'>"
;
case
RubySpa
n
.
POSITION_UNKNOWN
:
case
TextAnnotatio
n
.
POSITION_UNKNOWN
:
return
"<ruby style='ruby-position:unset;'>"
;
default
:
return
null
;
}
}
else
if
(
span
instanceof
UnderlineSpan
)
{
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
{
return
null
;
}
...
...
@@ -209,7 +220,8 @@ import java.util.regex.Pattern;
||
span
instanceof
BackgroundColorSpan
||
span
instanceof
HorizontalTextInVerticalContextSpan
||
span
instanceof
AbsoluteSizeSpan
||
span
instanceof
RelativeSizeSpan
)
{
||
span
instanceof
RelativeSizeSpan
||
span
instanceof
TextEmphasisSpan
)
{
return
"</span>"
;
}
else
if
(
span
instanceof
TypefaceSpan
)
{
@Nullable
String
fontFamily
=
((
TypefaceSpan
)
span
).
getFamily
();
...
...
@@ -232,6 +244,52 @@ import java.util.regex.Pattern;
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
)
{
@Nullable
Transition
transition
=
transitions
.
get
(
key
);
if
(
transition
==
null
)
{
...
...
library/ui/src/test/java/com/google/android/exoplayer2/ui/SpannedToHtmlConverterTest.java
View file @
65ab0085
...
...
@@ -34,6 +34,8 @@ import androidx.test.core.app.ApplicationProvider;
import
androidx.test.ext.junit.runners.AndroidJUnit4
;
import
com.google.android.exoplayer2.text.span.HorizontalTextInVerticalContextSpan
;
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.runner.RunWith
;
import
org.robolectric.annotation.Config
;
...
...
@@ -250,12 +252,12 @@ public class SpannedToHtmlConverterTest {
SpannableString
spanned
=
new
SpannableString
(
"String with over-annotated and under-annotated section"
);
spanned
.
setSpan
(
new
RubySpan
(
"ruby-text"
,
RubySpan
.
POSITION_OVER
),
new
RubySpan
(
"ruby-text"
,
TextAnnotation
.
POSITION_BEFORE
),
"String with "
.
length
(),
"String with over-annotated"
.
length
(),
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
);
spanned
.
setSpan
(
new
RubySpan
(
"non-àscìì-text"
,
RubySpan
.
POSITION_UND
ER
),
new
RubySpan
(
"non-àscìì-text"
,
TextAnnotation
.
POSITION_AFT
ER
),
"String with over-annotated and "
.
length
(),
"String with over-annotated and under-annotated"
.
length
(),
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
);
...
...
@@ -280,6 +282,42 @@ public class SpannedToHtmlConverterTest {
}
@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
public
void
convert_supportsUnderlineSpan
()
{
SpannableString
spanned
=
new
SpannableString
(
"String with underlined section."
);
spanned
.
setSpan
(
...
...
testdata/src/test/assets/media/ttml/text_emphasis.xml
0 → 100644
View file @
65ab0085
<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>
testutils/src/main/java/com/google/android/exoplayer2/testutil/truth/SpannedSubject.java
View file @
65ab0085
...
...
@@ -38,6 +38,8 @@ import androidx.annotation.ColorInt;
import
androidx.annotation.Nullable
;
import
com.google.android.exoplayer2.text.span.HorizontalTextInVerticalContextSpan
;
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.Fact
;
import
com.google.common.truth.FailureMetadata
;
...
...
@@ -579,6 +581,44 @@ public final class SpannedSubject extends Subject {
}
/**
* 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
* between {@code start} and {@code end}.
*
...
...
@@ -1033,7 +1073,7 @@ public final class SpannedSubject extends Subject {
* @param position The expected position of the text.
* @return A {@link WithSpanFlags} object for optional additional assertions on the flags.
*/
AndSpanFlags
withTextAndPosition
(
String
text
,
@
RubySpa
n
.
Position
int
position
);
AndSpanFlags
withTextAndPosition
(
String
text
,
@
TextAnnotatio
n
.
Position
int
position
);
}
private
static
final
RubyText
ALREADY_FAILED_WITH_TEXT
=
...
...
@@ -1057,7 +1097,7 @@ public final class SpannedSubject extends Subject {
}
@Override
public
AndSpanFlags
withTextAndPosition
(
String
text
,
@
RubySpa
n
.
Position
int
position
)
{
public
AndSpanFlags
withTextAndPosition
(
String
text
,
@
TextAnnotatio
n
.
Position
int
position
)
{
List
<
Integer
>
matchingSpanFlags
=
new
ArrayList
<>();
List
<
TextAndPosition
>
spanTextsAndPositions
=
new
ArrayList
<>();
for
(
RubySpan
span
:
actualSpans
)
{
...
...
@@ -1074,7 +1114,7 @@ public final class SpannedSubject extends Subject {
private
static
final
class
TextAndPosition
{
private
final
String
text
;
@
RubySpa
n
.
Position
private
final
int
position
;
@
TextAnnotatio
n
.
Position
private
final
int
position
;
private
TextAndPosition
(
String
text
,
int
position
)
{
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
);
}
}
}
}
testutils/src/test/java/com/google/android/exoplayer2/testutil/truth/SpannedSubjectTest.java
View file @
65ab0085
...
...
@@ -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.text.span.HorizontalTextInVerticalContextSpan
;
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
org.junit.Test
;
import
org.junit.runner.RunWith
;
...
...
@@ -607,23 +610,26 @@ public class SpannedSubjectTest {
public
void
rubySpan_success
()
{
SpannableString
spannable
=
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
)
.
hasRubySpanBetween
(
SPAN_START
,
SPAN_END
)
.
withTextAndPosition
(
"ruby text"
,
RubySpan
.
POSITION_OVER
)
.
withTextAndPosition
(
"ruby text"
,
TextAnnotation
.
POSITION_BEFORE
)
.
andFlags
(
Spanned
.
SPAN_INCLUSIVE_EXCLUSIVE
);
}
@Test
public
void
rubySpan_wrongEndIndex
()
{
checkHasSpanFailsDueToIndexMismatch
(
new
RubySpan
(
"ruby text"
,
RubySpan
.
POSITION_OVER
),
SpannedSubject:
:
hasRubySpanBetween
);
new
RubySpan
(
"ruby text"
,
TextAnnotation
.
POSITION_BEFORE
),
SpannedSubject:
:
hasRubySpanBetween
);
}
@Test
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
=
expectFailure
(
...
...
@@ -631,7 +637,7 @@ public class SpannedSubjectTest {
whenTesting
.
that
(
spannable
)
.
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
(
"expected"
).
contains
(
"text='incorrect text'"
);
...
...
@@ -640,7 +646,8 @@ public class SpannedSubjectTest {
@Test
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
=
expectFailure
(
...
...
@@ -648,27 +655,32 @@ public class SpannedSubjectTest {
whenTesting
.
that
(
spannable
)
.
hasRubySpanBetween
(
SPAN_START
,
SPAN_END
)
.
withTextAndPosition
(
"ruby text"
,
RubySpan
.
POSITION_UND
ER
));
.
withTextAndPosition
(
"ruby text"
,
TextAnnotation
.
POSITION_AFT
ER
));
assertThat
(
expected
).
factValue
(
"value of"
).
contains
(
"rubyTextAndPosition"
);
assertThat
(
expected
).
factValue
(
"expected"
).
contains
(
"position="
+
RubySpan
.
POSITION_UNDER
);
assertThat
(
expected
).
factValue
(
"but was"
).
contains
(
"position="
+
RubySpan
.
POSITION_OVER
);
assertThat
(
expected
)
.
factValue
(
"expected"
)
.
contains
(
"position="
+
TextAnnotation
.
POSITION_AFTER
);
assertThat
(
expected
)
.
factValue
(
"but was"
)
.
contains
(
"position="
+
TextAnnotation
.
POSITION_BEFORE
);
}
@Test
public
void
rubySpan_wrongFlags
()
{
checkHasSpanFailsDueToFlagMismatch
(
new
RubySpan
(
"ruby text"
,
RubySpan
.
POSITION_OVER
),
new
RubySpan
(
"ruby text"
,
TextAnnotation
.
POSITION_BEFORE
),
(
subject
,
start
,
end
)
->
subject
.
hasRubySpanBetween
(
start
,
end
)
.
withTextAndPosition
(
"ruby text"
,
RubySpan
.
POSITION_OVER
));
.
withTextAndPosition
(
"ruby text"
,
TextAnnotation
.
POSITION_BEFORE
));
}
@Test
public
void
noRubySpan_success
()
{
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
);
}
...
...
@@ -676,7 +688,191 @@ public class SpannedSubjectTest {
@Test
public
void
noRubySpan_failure
()
{
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
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment