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
6c688891
authored
Mar 24, 2021
by
Ian Baker
Browse files
Options
_('Browse Files')
Download
Plain Diff
Merge pull request #8653 from dlafayet:textemphasis
PiperOrigin-RevId: 364363882
parents
7b4b5cbf
0f0e0c97
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 @
6c688891
...
...
@@ -71,6 +71,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 @
6c688891
...
...
@@ -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 @
6c688891
/*
* 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 @
6c688891
/*
* 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 @
6c688891
/*
* 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 @
6c688891
...
...
@@ -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 @
6c688891
...
...
@@ -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 @
6c688891
...
...
@@ -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 @
6c688891
...
...
@@ -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 @
6c688891
...
...
@@ -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 @
6c688891
...
...
@@ -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 @
6c688891
...
...
@@ -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 @
6c688891
/*
* 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 @
6c688891
...
...
@@ -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 @
6c688891
...
...
@@ -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 @
6c688891
...
...
@@ -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 @
6c688891
...
...
@@ -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 @
6c688891
...
...
@@ -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 @
6c688891
<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 @
6c688891
...
...
@@ -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 @
6c688891
...
...
@@ -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