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
fcda8d47
authored
Feb 04, 2021
by
Denise LaFayette
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Support tts:textEmphasis in TTML parser and WebView output
parent
da52de66
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
917 additions
and
15 deletions
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/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/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
library/core/src/main/java/com/google/android/exoplayer2/text/span/TextEmphasisSpan.java
0 → 100644
View file @
fcda8d47
/*
* Copyright (C) 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
;
public
final
class
TextEmphasisSpan
{
/**
* <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/text-emphasis-position">Text Emphasis Position</a>
*/
/**
* The text emphasis position is unknown. 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; as such, an implementation that supports text annotation marks must
* minimally support the before value.
*/
public
static
final
int
POSITION_UNKNOWN
=
-
1
;
/**
* The emphasis marks should be positioned above the base text in horizontal writing mode The
* emphasis marks should be positioned to the right of the base text in vertical writing mode
*/
public
static
final
int
POSITION_BEFORE
=
1
;
/**
* The emphasis marks should be positioned below the base text in horizontal writing mode The
* emphasis marks should be positioned to the left of the base text in vertical writing mode
*/
public
static
final
int
POSITION_AFTER
=
2
;
/**
* The text emphasis should be positioned in following way:
*
* <ul>
* <li>Equivalent to {@link #POSITION_BEFORE} for (1) the only line area of or (2) the first line
* area of the last block area generated by a p element which contains annotated text
* <li>otherwise, equivalent to {@link #POSITION_AFTER}
* </ul>
*/
public
static
final
int
POSITION_OUTSIDE
=
3
;
/**
* The possible positions of the emphasis marks relative to the base text.
*
* <p>One of:
*
* <ul>
* <li>{@link #POSITION_UNKNOWN}
* <li>{@link #POSITION_BEFORE}
* <li>{@link #POSITION_AFTER}
* <li>{@link #POSITION_OUTSIDE}
* </ul>
*/
@Documented
@Retention
(
SOURCE
)
@IntDef
({
POSITION_UNKNOWN
,
POSITION_BEFORE
,
POSITION_AFTER
,
POSITION_OUTSIDE
})
public
@interface
Position
{
}
/**
* The text emphasis position is unknown.
*/
public
static
final
int
MARK_UNKNOWN
=
-
1
;
public
static
final
int
MARK_AUTO
=
1
;
public
static
final
int
MARK_FILLED_CIRCLE
=
2
;
public
static
final
int
MARK_FILLED_DOT
=
3
;
public
static
final
int
MARK_FILLED_SESAME
=
4
;
public
static
final
int
MARK_OPEN_CIRCLE
=
5
;
public
static
final
int
MARK_OPEN_DOT
=
6
;
public
static
final
int
MARK_OPEN_SESAME
=
7
;
/**
* The possible types of annotations used.
*
* <p>One of:
*
* <ul>
* <li>{@link #MARK_UNKNOWN}
* <li>{@link #MARK_AUTO}
* <li>{@link #MARK_FILLED_CIRCLE}
* <li>{@link #MARK_FILLED_DOT}
* <li>{@link #MARK_FILLED_SESAME}
* <li>{@link #MARK_OPEN_CIRCLE}
* <li>{@link #MARK_OPEN_DOT}
* <li>{@link #MARK_OPEN_SESAME}
* </ul>
*/
@Documented
@Retention
(
SOURCE
)
@IntDef
({
MARK_UNKNOWN
,
MARK_AUTO
,
MARK_FILLED_CIRCLE
,
MARK_FILLED_DOT
,
MARK_FILLED_SESAME
,
MARK_OPEN_CIRCLE
,
MARK_OPEN_DOT
,
MARK_OPEN_SESAME
})
public
@interface
Mark
{
}
/**
* The position of the text emphasis relative to the base text
*/
@TextEmphasisSpan
.
Position
public
final
int
position
;
/**
* The text emphasis mark
*/
@TextEmphasisSpan
.
Mark
public
final
int
mark
;
public
TextEmphasisSpan
(
@TextEmphasisSpan
.
Mark
int
mark
,
@TextEmphasisSpan
.
Position
int
position
)
{
this
.
mark
=
mark
;
this
.
position
=
position
;
}
}
library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TextEmphasis.java
0 → 100644
View file @
fcda8d47
/*
* Copyright (C) 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
.
span
.
TextEmphasisSpan
.
MARK_AUTO
;
import
static
com
.
google
.
android
.
exoplayer2
.
text
.
span
.
TextEmphasisSpan
.
MARK_FILLED_CIRCLE
;
import
static
com
.
google
.
android
.
exoplayer2
.
text
.
span
.
TextEmphasisSpan
.
MARK_FILLED_DOT
;
import
static
com
.
google
.
android
.
exoplayer2
.
text
.
span
.
TextEmphasisSpan
.
MARK_FILLED_SESAME
;
import
static
com
.
google
.
android
.
exoplayer2
.
text
.
span
.
TextEmphasisSpan
.
MARK_OPEN_CIRCLE
;
import
static
com
.
google
.
android
.
exoplayer2
.
text
.
span
.
TextEmphasisSpan
.
MARK_OPEN_DOT
;
import
static
com
.
google
.
android
.
exoplayer2
.
text
.
span
.
TextEmphasisSpan
.
MARK_OPEN_SESAME
;
import
static
com
.
google
.
android
.
exoplayer2
.
text
.
span
.
TextEmphasisSpan
.
POSITION_AFTER
;
import
static
com
.
google
.
android
.
exoplayer2
.
text
.
span
.
TextEmphasisSpan
.
POSITION_BEFORE
;
import
static
com
.
google
.
android
.
exoplayer2
.
text
.
span
.
TextEmphasisSpan
.
POSITION_OUTSIDE
;
import
static
com
.
google
.
android
.
exoplayer2
.
text
.
span
.
TextEmphasisSpan
.
POSITION_UNKNOWN
;
import
androidx.annotation.NonNull
;
import
androidx.annotation.Nullable
;
import
com.google.android.exoplayer2.text.span.TextEmphasisSpan
;
/**
* This class is used to emphasize text using markers above or below the text. For example, markers
* known as boutens are commonly used in Japanese texts. Boutens are dots placed above or below a
* word or phrase that act as literal points of emphasis, equivalent to the use of italics in
* English. Boutens can help express implied meanings which provide a richer and more dynamic
* translation.
*/
/* package */
final
class
TextEmphasis
{
/**
* The position of the text emphasis relative to the base text.
*/
@TextEmphasisSpan
.
Position
public
final
int
position
;
/**
* The desired emphasis mark
*/
@TextEmphasisSpan
.
Mark
public
final
int
mark
;
private
TextEmphasis
(
@TextEmphasisSpan
.
Mark
int
mark
,
@TextEmphasisSpan
.
Position
int
position
)
{
this
.
mark
=
mark
;
this
.
position
=
position
;
}
@Override
public
String
toString
()
{
return
"TextEmphasis{"
+
"position="
+
position
+
", mark="
+
mark
+
'}'
;
}
public
static
TextEmphasis
createTextEmphasis
(
@Nullable
String
value
)
{
if
(
value
==
null
)
{
return
null
;
}
String
parsingValue
=
value
.
toLowerCase
().
trim
();
if
(
""
.
equals
(
parsingValue
))
{
return
null
;
}
String
[]
nodes
=
parsingValue
.
split
(
"\\s+"
);
switch
(
nodes
.
length
)
{
case
0
:
return
null
;
case
1
:
return
handleOneNode
(
nodes
[
0
]);
case
2
:
return
handleTwoNodes
(
nodes
[
0
],
nodes
[
1
]);
default
:
// We ignore anything after third entry in value
return
handleThreeNodes
(
nodes
[
0
],
nodes
[
1
],
nodes
[
2
]);
}
}
private
static
@Nullable
TextEmphasis
handleOneNode
(
@NonNull
String
value
)
{
if
(
TtmlNode
.
TEXT_EMPHASIS_NONE
.
equals
(
value
))
{
return
null
;
}
// Handle "auto" or unknown value
// 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.
return
new
TextEmphasis
(
MARK_AUTO
,
POSITION_UNKNOWN
);
}
private
static
@Nullable
TextEmphasis
handleTwoNodes
(
@NonNull
String
mark
,
@NonNull
String
position
)
{
@TextEmphasisSpan
.
Position
int
positionEntry
=
getPosition
(
position
);
@TextEmphasisSpan
.
Mark
int
markEntry
;
switch
(
mark
)
{
case
TtmlNode
.
TEXT_EMPHASIS_AUTO
:
markEntry
=
MARK_AUTO
;
break
;
// If only circle, dot, or sesame is specified, then it is equivalent to filled circle,
// filled dot, and filled sesame, respectively.
case
TtmlNode
.
TEXT_EMPHASIS_MARK_DOT
:
markEntry
=
MARK_FILLED_DOT
;
break
;
case
TtmlNode
.
TEXT_EMPHASIS_MARK_SESAME
:
markEntry
=
MARK_FILLED_SESAME
;
break
;
case
TtmlNode
.
TEXT_EMPHASIS_MARK_CIRCLE
:
markEntry
=
MARK_FILLED_CIRCLE
;
break
;
default
:
// This is use case for: "filled dot" when position is not specified.
return
handleWithPosition
(
mark
,
position
,
POSITION_UNKNOWN
);
}
return
new
TextEmphasis
(
markEntry
,
positionEntry
);
}
private
static
@Nullable
TextEmphasis
handleWithPosition
(
@NonNull
String
markStyle
,
@Nullable
String
mark
,
@TextEmphasisSpan
.
Position
int
position
)
{
switch
(
mark
)
{
case
TtmlNode
.
TEXT_EMPHASIS_MARK_DOT
:
if
(
TtmlNode
.
TEXT_EMPHASIS_MARK_FILLED
.
equals
(
markStyle
))
{
return
new
TextEmphasis
(
MARK_FILLED_DOT
,
position
);
}
else
if
(
TtmlNode
.
TEXT_EMPHASIS_MARK_OPEN
.
equals
(
markStyle
))
{
return
new
TextEmphasis
(
MARK_OPEN_DOT
,
position
);
}
else
{
return
new
TextEmphasis
(
MARK_FILLED_DOT
,
position
);
}
case
TtmlNode
.
TEXT_EMPHASIS_MARK_SESAME
:
if
(
TtmlNode
.
TEXT_EMPHASIS_MARK_FILLED
.
equals
(
markStyle
))
{
return
new
TextEmphasis
(
MARK_FILLED_SESAME
,
position
);
}
else
if
(
TtmlNode
.
TEXT_EMPHASIS_MARK_OPEN
.
equals
(
markStyle
))
{
return
new
TextEmphasis
(
MARK_OPEN_SESAME
,
position
);
}
else
{
return
new
TextEmphasis
(
MARK_FILLED_SESAME
,
position
);
}
case
TtmlNode
.
TEXT_EMPHASIS_MARK_CIRCLE
:
if
(
TtmlNode
.
TEXT_EMPHASIS_MARK_FILLED
.
equals
(
markStyle
))
{
return
new
TextEmphasis
(
MARK_FILLED_CIRCLE
,
position
);
}
else
if
(
TtmlNode
.
TEXT_EMPHASIS_MARK_OPEN
.
equals
(
markStyle
))
{
return
new
TextEmphasis
(
MARK_OPEN_CIRCLE
,
position
);
}
else
{
return
new
TextEmphasis
(
MARK_FILLED_CIRCLE
,
position
);
}
default
:
// Not supported, default to AUTO.
break
;
}
return
new
TextEmphasis
(
MARK_AUTO
,
POSITION_UNKNOWN
);
}
private
static
@Nullable
TextEmphasis
handleThreeNodes
(
@NonNull
String
markStyle
,
@NonNull
String
mark
,
@NonNull
String
position
)
{
@TextEmphasisSpan
.
Position
int
positionEntry
=
getPosition
(
position
);
return
handleWithPosition
(
markStyle
,
mark
,
positionEntry
);
}
private
static
@TextEmphasisSpan
.
Position
int
getPosition
(
@NonNull
String
value
)
{
switch
(
value
)
{
case
TtmlNode
.
TEXT_EMPHASIS_POSITION_AFTER
:
return
POSITION_AFTER
;
case
TtmlNode
.
TEXT_EMPHASIS_POSITION_BEFORE
:
return
POSITION_BEFORE
;
case
TtmlNode
.
TEXT_EMPHASIS_POSITION_OUTSIDE
:
return
POSITION_OUTSIDE
;
default
:
// ignore
break
;
}
return
POSITION_UNKNOWN
;
}
}
library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java
View file @
fcda8d47
...
@@ -609,6 +609,10 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
...
@@ -609,6 +609,10 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
break
;
break
;
}
}
break
;
break
;
case
TtmlNode
.
ATTR_TTS_TEXT_EMPHASIS
:
style
=
createIfNull
(
style
).
setTextEmphasis
(
TextEmphasis
.
createTextEmphasis
(
Util
.
toLowerInvariant
(
attributeValue
)));
break
;
default
:
default
:
// ignore
// ignore
break
;
break
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java
View file @
fcda8d47
...
@@ -69,6 +69,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
...
@@ -69,6 +69,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
public
static
final
String
ATTR_TTS_TEXT_DECORATION
=
"textDecoration"
;
public
static
final
String
ATTR_TTS_TEXT_DECORATION
=
"textDecoration"
;
public
static
final
String
ATTR_TTS_TEXT_ALIGN
=
"textAlign"
;
public
static
final
String
ATTR_TTS_TEXT_ALIGN
=
"textAlign"
;
public
static
final
String
ATTR_TTS_TEXT_COMBINE
=
"textCombine"
;
public
static
final
String
ATTR_TTS_TEXT_COMBINE
=
"textCombine"
;
public
static
final
String
ATTR_TTS_TEXT_EMPHASIS
=
"textEmphasis"
;
public
static
final
String
ATTR_TTS_WRITING_MODE
=
"writingMode"
;
public
static
final
String
ATTR_TTS_WRITING_MODE
=
"writingMode"
;
// Values for ruby
// Values for ruby
...
@@ -106,6 +107,19 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
...
@@ -106,6 +107,19 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
public
static
final
String
VERTICAL_LR
=
"tblr"
;
public
static
final
String
VERTICAL_LR
=
"tblr"
;
public
static
final
String
VERTICAL_RL
=
"tbrl"
;
public
static
final
String
VERTICAL_RL
=
"tbrl"
;
// Values for textEmphasis
public
static
final
String
TEXT_EMPHASIS_NONE
=
"none"
;
public
static
final
String
TEXT_EMPHASIS_AUTO
=
"auto"
;
public
static
final
String
TEXT_EMPHASIS_MARK_DOT
=
"dot"
;
public
static
final
String
TEXT_EMPHASIS_MARK_SESAME
=
"sesame"
;
public
static
final
String
TEXT_EMPHASIS_MARK_CIRCLE
=
"circle"
;
public
static
final
String
TEXT_EMPHASIS_MARK_FILLED
=
"filled"
;
public
static
final
String
TEXT_EMPHASIS_MARK_OPEN
=
"open"
;
public
static
final
String
TEXT_EMPHASIS_POSITION_AFTER
=
"after"
;
public
static
final
String
TEXT_EMPHASIS_POSITION_BEFORE
=
"before"
;
public
static
final
String
TEXT_EMPHASIS_POSITION_OUTSIDE
=
"outside"
;
@Nullable
public
final
String
tag
;
@Nullable
public
final
String
tag
;
@Nullable
public
final
String
text
;
@Nullable
public
final
String
text
;
public
final
boolean
isTextNode
;
public
final
boolean
isTextNode
;
...
@@ -243,7 +257,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
...
@@ -243,7 +257,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
TreeMap
<
String
,
Cue
.
Builder
>
regionTextOutputs
=
new
TreeMap
<>();
TreeMap
<
String
,
Cue
.
Builder
>
regionTextOutputs
=
new
TreeMap
<>();
traverseForText
(
timeUs
,
false
,
regionId
,
regionTextOutputs
);
traverseForText
(
timeUs
,
false
,
regionId
,
regionTextOutputs
);
traverseForStyle
(
timeUs
,
globalStyles
,
regionTextOutputs
);
traverseForStyle
(
timeUs
,
globalStyles
,
region
Map
,
regionId
,
region
TextOutputs
);
List
<
Cue
>
cues
=
new
ArrayList
<>();
List
<
Cue
>
cues
=
new
ArrayList
<>();
...
@@ -354,26 +368,32 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
...
@@ -354,26 +368,32 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
}
}
private
void
traverseForStyle
(
private
void
traverseForStyle
(
long
timeUs
,
Map
<
String
,
TtmlStyle
>
globalStyles
,
Map
<
String
,
Cue
.
Builder
>
regionOutputs
)
{
long
timeUs
,
Map
<
String
,
TtmlStyle
>
globalStyles
,
Map
<
String
,
TtmlRegion
>
regionMaps
,
String
inheritedRegion
,
Map
<
String
,
Cue
.
Builder
>
regionOutputs
)
{
if
(!
isActive
(
timeUs
))
{
if
(!
isActive
(
timeUs
))
{
return
;
return
;
}
}
String
resolvedRegionId
=
ANONYMOUS_REGION_ID
.
equals
(
regionId
)
?
inheritedRegion
:
regionId
;
for
(
Map
.
Entry
<
String
,
Integer
>
entry
:
nodeEndsByRegion
.
entrySet
())
{
for
(
Map
.
Entry
<
String
,
Integer
>
entry
:
nodeEndsByRegion
.
entrySet
())
{
String
regionId
=
entry
.
getKey
();
String
regionId
=
entry
.
getKey
();
int
start
=
nodeStartsByRegion
.
containsKey
(
regionId
)
?
nodeStartsByRegion
.
get
(
regionId
)
:
0
;
int
start
=
nodeStartsByRegion
.
containsKey
(
regionId
)
?
nodeStartsByRegion
.
get
(
regionId
)
:
0
;
int
end
=
entry
.
getValue
();
int
end
=
entry
.
getValue
();
if
(
start
!=
end
)
{
if
(
start
!=
end
)
{
Cue
.
Builder
regionOutput
=
Assertions
.
checkNotNull
(
regionOutputs
.
get
(
regionId
));
Cue
.
Builder
regionOutput
=
Assertions
.
checkNotNull
(
regionOutputs
.
get
(
regionId
));
applyStyleToOutput
(
globalStyles
,
regionOutput
,
start
,
end
);
@Cue
.
VerticalType
int
verticalType
=
Assertions
.
checkNotNull
(
regionMaps
.
get
(
resolvedRegionId
)).
verticalType
;
applyStyleToOutput
(
globalStyles
,
regionOutput
,
start
,
end
,
verticalType
);
}
}
}
}
for
(
int
i
=
0
;
i
<
getChildCount
();
++
i
)
{
for
(
int
i
=
0
;
i
<
getChildCount
();
++
i
)
{
getChild
(
i
).
traverseForStyle
(
timeUs
,
globalStyles
,
regionOutputs
);
getChild
(
i
).
traverseForStyle
(
timeUs
,
globalStyles
,
region
Maps
,
resolvedRegionId
,
region
Outputs
);
}
}
}
}
private
void
applyStyleToOutput
(
private
void
applyStyleToOutput
(
Map
<
String
,
TtmlStyle
>
globalStyles
,
Cue
.
Builder
regionOutput
,
int
start
,
int
end
)
{
Map
<
String
,
TtmlStyle
>
globalStyles
,
Cue
.
Builder
regionOutput
,
int
start
,
int
end
,
@Cue
.
VerticalType
int
verticalType
)
{
@Nullable
TtmlStyle
resolvedStyle
=
TtmlRenderUtil
.
resolveStyle
(
style
,
styleIds
,
globalStyles
);
@Nullable
TtmlStyle
resolvedStyle
=
TtmlRenderUtil
.
resolveStyle
(
style
,
styleIds
,
globalStyles
);
@Nullable
SpannableStringBuilder
text
=
(
SpannableStringBuilder
)
regionOutput
.
getText
();
@Nullable
SpannableStringBuilder
text
=
(
SpannableStringBuilder
)
regionOutput
.
getText
();
if
(
text
==
null
)
{
if
(
text
==
null
)
{
...
@@ -381,7 +401,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
...
@@ -381,7 +401,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
regionOutput
.
setText
(
text
);
regionOutput
.
setText
(
text
);
}
}
if
(
resolvedStyle
!=
null
)
{
if
(
resolvedStyle
!=
null
)
{
TtmlRenderUtil
.
applyStylesToSpan
(
text
,
start
,
end
,
resolvedStyle
,
parent
,
globalStyles
);
TtmlRenderUtil
.
applyStylesToSpan
(
text
,
start
,
end
,
resolvedStyle
,
parent
,
globalStyles
,
verticalType
);
regionOutput
.
setTextAlignment
(
resolvedStyle
.
getTextAlign
());
regionOutput
.
setTextAlignment
(
resolvedStyle
.
getTextAlign
());
}
}
}
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlRenderUtil.java
View file @
fcda8d47
...
@@ -27,9 +27,11 @@ import android.text.style.StyleSpan;
...
@@ -27,9 +27,11 @@ import android.text.style.StyleSpan;
import
android.text.style.TypefaceSpan
;
import
android.text.style.TypefaceSpan
;
import
android.text.style.UnderlineSpan
;
import
android.text.style.UnderlineSpan
;
import
androidx.annotation.Nullable
;
import
androidx.annotation.Nullable
;
import
com.google.android.exoplayer2.text.Cue
;
import
com.google.android.exoplayer2.text.span.HorizontalTextInVerticalContextSpan
;
import
com.google.android.exoplayer2.text.span.HorizontalTextInVerticalContextSpan
;
import
com.google.android.exoplayer2.text.span.RubySpan
;
import
com.google.android.exoplayer2.text.span.RubySpan
;
import
com.google.android.exoplayer2.text.span.SpanUtil
;
import
com.google.android.exoplayer2.text.span.SpanUtil
;
import
com.google.android.exoplayer2.text.span.TextEmphasisSpan
;
import
com.google.android.exoplayer2.util.Log
;
import
com.google.android.exoplayer2.util.Log
;
import
com.google.android.exoplayer2.util.Util
;
import
com.google.android.exoplayer2.util.Util
;
import
java.util.ArrayDeque
;
import
java.util.ArrayDeque
;
...
@@ -83,7 +85,8 @@ import java.util.Map;
...
@@ -83,7 +85,8 @@ import java.util.Map;
int
end
,
int
end
,
TtmlStyle
style
,
TtmlStyle
style
,
@Nullable
TtmlNode
parent
,
@Nullable
TtmlNode
parent
,
Map
<
String
,
TtmlStyle
>
globalStyles
)
{
Map
<
String
,
TtmlStyle
>
globalStyles
,
@Cue
.
VerticalType
int
verticalType
)
{
if
(
style
.
getStyle
()
!=
TtmlStyle
.
UNSPECIFIED
)
{
if
(
style
.
getStyle
()
!=
TtmlStyle
.
UNSPECIFIED
)
{
builder
.
setSpan
(
new
StyleSpan
(
style
.
getStyle
()),
start
,
end
,
builder
.
setSpan
(
new
StyleSpan
(
style
.
getStyle
()),
start
,
end
,
...
@@ -119,6 +122,27 @@ import java.util.Map;
...
@@ -119,6 +122,27 @@ import java.util.Map;
end
,
end
,
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
);
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
);
}
}
if
(
style
.
getTextEmphasis
()
!=
null
)
{
TextEmphasis
textEmphasis
=
style
.
getTextEmphasis
();
// https://www.w3.org/TR/ttml2/#style-value-emphasis-style
// 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.
// If a vertical writing mode applies, then equivalent to filled sesame; otherwise, equivalent
// to filled circle.
@TextEmphasisSpan
.
Mark
int
mark
=
textEmphasis
.
mark
;
if
(
textEmphasis
.
mark
==
TextEmphasisSpan
.
MARK_AUTO
||
textEmphasis
.
mark
==
TextEmphasisSpan
.
MARK_UNKNOWN
)
{
mark
=
(
verticalType
==
Cue
.
VERTICAL_TYPE_LR
||
verticalType
==
Cue
.
VERTICAL_TYPE_RL
)
?
TextEmphasisSpan
.
MARK_FILLED_SESAME
:
TextEmphasisSpan
.
MARK_FILLED_CIRCLE
;
}
SpanUtil
.
addOrReplaceSpan
(
builder
,
new
TextEmphasisSpan
(
mark
,
textEmphasis
.
position
),
start
,
end
,
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
);
}
switch
(
style
.
getRubyType
())
{
switch
(
style
.
getRubyType
())
{
case
TtmlStyle
.
RUBY_TYPE_BASE
:
case
TtmlStyle
.
RUBY_TYPE_BASE
:
// look for the sibling RUBY_TEXT and add it as span between start & end.
// look for the sibling RUBY_TEXT and add it as span between start & end.
...
...
library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlStyle.java
View file @
fcda8d47
...
@@ -87,6 +87,8 @@ import java.lang.annotation.RetentionPolicy;
...
@@ -87,6 +87,8 @@ import java.lang.annotation.RetentionPolicy;
@Nullable
private
Layout
.
Alignment
textAlign
;
@Nullable
private
Layout
.
Alignment
textAlign
;
@OptionalBoolean
private
int
textCombine
;
@OptionalBoolean
private
int
textCombine
;
private
TextEmphasis
textEmphasis
;
public
TtmlStyle
()
{
public
TtmlStyle
()
{
linethrough
=
UNSPECIFIED
;
linethrough
=
UNSPECIFIED
;
underline
=
UNSPECIFIED
;
underline
=
UNSPECIFIED
;
...
@@ -238,6 +240,9 @@ import java.lang.annotation.RetentionPolicy;
...
@@ -238,6 +240,9 @@ import java.lang.annotation.RetentionPolicy;
fontSizeUnit
=
ancestor
.
fontSizeUnit
;
fontSizeUnit
=
ancestor
.
fontSizeUnit
;
fontSize
=
ancestor
.
fontSize
;
fontSize
=
ancestor
.
fontSize
;
}
}
if
(
textEmphasis
==
null
)
{
textEmphasis
=
ancestor
.
textEmphasis
;
}
// attributes not inherited as of http://www.w3.org/TR/ttml1/
// attributes not inherited as of http://www.w3.org/TR/ttml1/
if
(
chaining
&&
!
hasBackgroundColor
&&
ancestor
.
hasBackgroundColor
)
{
if
(
chaining
&&
!
hasBackgroundColor
&&
ancestor
.
hasBackgroundColor
)
{
setBackgroundColor
(
ancestor
.
backgroundColor
);
setBackgroundColor
(
ancestor
.
backgroundColor
);
...
@@ -299,6 +304,16 @@ import java.lang.annotation.RetentionPolicy;
...
@@ -299,6 +304,16 @@ import java.lang.annotation.RetentionPolicy;
return
this
;
return
this
;
}
}
@Nullable
public
TextEmphasis
getTextEmphasis
()
{
return
textEmphasis
;
}
public
TtmlStyle
setTextEmphasis
(
@Nullable
TextEmphasis
textEmphasis
)
{
this
.
textEmphasis
=
textEmphasis
;
return
this
;
}
public
TtmlStyle
setFontSize
(
float
fontSize
)
{
public
TtmlStyle
setFontSize
(
float
fontSize
)
{
this
.
fontSize
=
fontSize
;
this
.
fontSize
=
fontSize
;
return
this
;
return
this
;
...
...
library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TextEmphasisTest.java
0 → 100644
View file @
fcda8d47
This diff is collapsed.
Click to expand it.
library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlDecoderTest.java
View file @
fcda8d47
...
@@ -28,6 +28,7 @@ import com.google.android.exoplayer2.text.Cue;
...
@@ -28,6 +28,7 @@ import com.google.android.exoplayer2.text.Cue;
import
com.google.android.exoplayer2.text.Subtitle
;
import
com.google.android.exoplayer2.text.Subtitle
;
import
com.google.android.exoplayer2.text.SubtitleDecoderException
;
import
com.google.android.exoplayer2.text.SubtitleDecoderException
;
import
com.google.android.exoplayer2.text.span.RubySpan
;
import
com.google.android.exoplayer2.text.span.RubySpan
;
import
com.google.android.exoplayer2.text.span.TextEmphasisSpan
;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.ColorParser
;
import
com.google.android.exoplayer2.util.ColorParser
;
import
java.io.IOException
;
import
java.io.IOException
;
...
@@ -36,7 +37,9 @@ import java.util.Map;
...
@@ -36,7 +37,9 @@ import java.util.Map;
import
org.junit.Test
;
import
org.junit.Test
;
import
org.junit.runner.RunWith
;
import
org.junit.runner.RunWith
;
/** Unit test for {@link TtmlDecoder}. */
/**
* Unit test for {@link TtmlDecoder}.
*/
@RunWith
(
AndroidJUnit4
.
class
)
@RunWith
(
AndroidJUnit4
.
class
)
public
final
class
TtmlDecoderTest
{
public
final
class
TtmlDecoderTest
{
...
@@ -67,6 +70,7 @@ public final class TtmlDecoderTest {
...
@@ -67,6 +70,7 @@ public final class TtmlDecoderTest {
private
static
final
String
VERTICAL_TEXT_FILE
=
"media/ttml/vertical_text.xml"
;
private
static
final
String
VERTICAL_TEXT_FILE
=
"media/ttml/vertical_text.xml"
;
private
static
final
String
TEXT_COMBINE_FILE
=
"media/ttml/text_combine.xml"
;
private
static
final
String
TEXT_COMBINE_FILE
=
"media/ttml/text_combine.xml"
;
private
static
final
String
RUBIES_FILE
=
"media/ttml/rubies.xml"
;
private
static
final
String
RUBIES_FILE
=
"media/ttml/rubies.xml"
;
private
static
final
String
TEXT_EMPHASIS_FILE
=
"media/ttml/text_emphasis.xml"
;
@Test
@Test
public
void
inlineAttributes
()
throws
IOException
,
SubtitleDecoderException
{
public
void
inlineAttributes
()
throws
IOException
,
SubtitleDecoderException
{
...
@@ -109,12 +113,10 @@ public final class TtmlDecoderTest {
...
@@ -109,12 +113,10 @@ public final class TtmlDecoderTest {
* framework level. Tests that <i>lime</i> resolves to <code>#FF00FF00</code> not <code>#00FF00
* framework level. Tests that <i>lime</i> resolves to <code>#FF00FF00</code> not <code>#00FF00
* </code>.
* </code>.
*
*
* @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.
* @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>
*/
*/
@Test
@Test
public
void
lime
()
throws
IOException
,
SubtitleDecoderException
{
public
void
lime
()
throws
IOException
,
SubtitleDecoderException
{
...
@@ -674,6 +676,105 @@ public final class TtmlDecoderTest {
...
@@ -674,6 +676,105 @@ public final class TtmlDecoderTest {
assertThat
(
sixthCue
).
hasNoRubySpanBetween
(
0
,
sixthCue
.
length
());
assertThat
(
sixthCue
).
hasNoRubySpanBetween
(
0
,
sixthCue
.
length
());
}
}
@Test
public
void
textEmphasis
()
throws
IOException
,
SubtitleDecoderException
{
TtmlSubtitle
subtitle
=
getSubtitle
(
TEXT_EMPHASIS_FILE
);
Spanned
firstCue
=
getOnlyCueTextAtTimeUs
(
subtitle
,
10_000_000
);
assertThat
(
firstCue
)
.
hasNoTextEmphasisSpanBetween
(
"None "
.
length
(),
"None おはよ"
.
length
());
Spanned
secondCue
=
getOnlyCueTextAtTimeUs
(
subtitle
,
20_000_000
);
assertThat
(
secondCue
)
.
hasTextEmphasisSpanBetween
(
"Auto "
.
length
(),
"Auto ございます"
.
length
())
.
withMarkAndPosition
(
TextEmphasisSpan
.
MARK_FILLED_CIRCLE
,
TextEmphasisSpan
.
POSITION_UNKNOWN
);
Spanned
thirdCue
=
getOnlyCueTextAtTimeUs
(
subtitle
,
30_000_000
);
assertThat
(
thirdCue
)
.
hasTextEmphasisSpanBetween
(
"Filled circle "
.
length
(),
"Filled circle こんばんは"
.
length
())
.
withMarkAndPosition
(
TextEmphasisSpan
.
MARK_FILLED_CIRCLE
,
TextEmphasisSpan
.
POSITION_UNKNOWN
);
Spanned
fourthCue
=
getOnlyCueTextAtTimeUs
(
subtitle
,
40_000_000
);
assertThat
(
fourthCue
)
.
hasTextEmphasisSpanBetween
(
"Filled dot "
.
length
(),
"Filled dot ございます"
.
length
())
.
withMarkAndPosition
(
TextEmphasisSpan
.
MARK_FILLED_DOT
,
TextEmphasisSpan
.
POSITION_UNKNOWN
);
Spanned
fifthCue
=
getOnlyCueTextAtTimeUs
(
subtitle
,
50_000_000
);
assertThat
(
fifthCue
)
.
hasTextEmphasisSpanBetween
(
"Filled sesame "
.
length
(),
"Filled sesame おはよ"
.
length
())
.
withMarkAndPosition
(
TextEmphasisSpan
.
MARK_FILLED_SESAME
,
TextEmphasisSpan
.
POSITION_UNKNOWN
);
Spanned
sixthCue
=
getOnlyCueTextAtTimeUs
(
subtitle
,
60_000_000
);
assertThat
(
sixthCue
)
.
hasTextEmphasisSpanBetween
(
"Open circle before "
.
length
(),
"Open circle before ございます"
.
length
())
.
withMarkAndPosition
(
TextEmphasisSpan
.
MARK_OPEN_CIRCLE
,
TextEmphasisSpan
.
POSITION_BEFORE
);
Spanned
seventhCue
=
getOnlyCueTextAtTimeUs
(
subtitle
,
70_000_000
);
assertThat
(
seventhCue
)
.
hasTextEmphasisSpanBetween
(
"Open dot after "
.
length
(),
"Open dot after おはよ"
.
length
())
.
withMarkAndPosition
(
TextEmphasisSpan
.
MARK_OPEN_DOT
,
TextEmphasisSpan
.
POSITION_AFTER
);
Spanned
eighthCue
=
getOnlyCueTextAtTimeUs
(
subtitle
,
80_000_000
);
assertThat
(
eighthCue
)
.
hasTextEmphasisSpanBetween
(
"Open sesame outside "
.
length
(),
"Open sesame outside ございます"
.
length
())
.
withMarkAndPosition
(
TextEmphasisSpan
.
MARK_OPEN_SESAME
,
TextEmphasisSpan
.
POSITION_OUTSIDE
);
Spanned
ninthCue
=
getOnlyCueTextAtTimeUs
(
subtitle
,
90_000_000
);
assertThat
(
ninthCue
)
.
hasTextEmphasisSpanBetween
(
"Auto outside "
.
length
(),
"Auto outside おはよ"
.
length
())
.
withMarkAndPosition
(
TextEmphasisSpan
.
MARK_FILLED_CIRCLE
,
TextEmphasisSpan
.
POSITION_OUTSIDE
);
Spanned
tenthCue
=
getOnlyCueTextAtTimeUs
(
subtitle
,
100_000_000
);
assertThat
(
tenthCue
)
.
hasTextEmphasisSpanBetween
(
"Circle before "
.
length
(),
"Circle before ございます"
.
length
())
.
withMarkAndPosition
(
TextEmphasisSpan
.
MARK_FILLED_CIRCLE
,
TextEmphasisSpan
.
POSITION_BEFORE
);
Spanned
eleventhCue
=
getOnlyCueTextAtTimeUs
(
subtitle
,
110_000_000
);
assertThat
(
eleventhCue
)
.
hasTextEmphasisSpanBetween
(
"Sesame after "
.
length
(),
"Sesame after おはよ"
.
length
())
.
withMarkAndPosition
(
TextEmphasisSpan
.
MARK_FILLED_SESAME
,
TextEmphasisSpan
.
POSITION_AFTER
);
Spanned
twelfthCue
=
getOnlyCueTextAtTimeUs
(
subtitle
,
120_000_000
);
assertThat
(
twelfthCue
)
.
hasTextEmphasisSpanBetween
(
"Dot outside "
.
length
(),
"Dot outside ございます"
.
length
())
.
withMarkAndPosition
(
TextEmphasisSpan
.
MARK_FILLED_DOT
,
TextEmphasisSpan
.
POSITION_OUTSIDE
);
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_FILLED_SESAME
,
TextEmphasisSpan
.
POSITION_UNKNOWN
);
Spanned
fifteenthCue
=
getOnlyCueTextAtTimeUs
(
subtitle
,
150_000_000
);
assertThat
(
fifteenthCue
)
.
hasTextEmphasisSpanBetween
(
"Auto (TBRL) "
.
length
(),
"Auto (TBRL) おはよ"
.
length
())
.
withMarkAndPosition
(
TextEmphasisSpan
.
MARK_FILLED_SESAME
,
TextEmphasisSpan
.
POSITION_UNKNOWN
);
Spanned
sixteenthCue
=
getOnlyCueTextAtTimeUs
(
subtitle
,
160_000_000
);
assertThat
(
sixteenthCue
)
.
hasTextEmphasisSpanBetween
(
"Auto (TB) "
.
length
(),
"Auto (TB) ございます"
.
length
())
.
withMarkAndPosition
(
TextEmphasisSpan
.
MARK_FILLED_SESAME
,
TextEmphasisSpan
.
POSITION_UNKNOWN
);
Spanned
seventeenthCue
=
getOnlyCueTextAtTimeUs
(
subtitle
,
170_000_000
);
assertThat
(
seventeenthCue
)
.
hasTextEmphasisSpanBetween
(
"Auto (LR) "
.
length
(),
"Auto (LR) おはよ"
.
length
())
.
withMarkAndPosition
(
TextEmphasisSpan
.
MARK_FILLED_CIRCLE
,
TextEmphasisSpan
.
POSITION_UNKNOWN
);
}
private
static
Spanned
getOnlyCueTextAtTimeUs
(
Subtitle
subtitle
,
long
timeUs
)
{
private
static
Spanned
getOnlyCueTextAtTimeUs
(
Subtitle
subtitle
,
long
timeUs
)
{
Cue
cue
=
getOnlyCueAtTimeUs
(
subtitle
,
timeUs
);
Cue
cue
=
getOnlyCueAtTimeUs
(
subtitle
,
timeUs
);
assertThat
(
cue
.
text
).
isInstanceOf
(
Spanned
.
class
);
assertThat
(
cue
.
text
).
isInstanceOf
(
Spanned
.
class
);
...
...
library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlStyleTest.java
View file @
fcda8d47
...
@@ -16,6 +16,10 @@
...
@@ -16,6 +16,10 @@
package
com
.
google
.
android
.
exoplayer2
.
text
.
ttml
;
package
com
.
google
.
android
.
exoplayer2
.
text
.
ttml
;
import
static
android
.
graphics
.
Color
.
BLACK
;
import
static
android
.
graphics
.
Color
.
BLACK
;
import
static
com
.
google
.
android
.
exoplayer2
.
text
.
span
.
TextEmphasisSpan
.
MARK_FILLED_DOT
;
import
static
com
.
google
.
android
.
exoplayer2
.
text
.
span
.
TextEmphasisSpan
.
MARK_OPEN_SESAME
;
import
static
com
.
google
.
android
.
exoplayer2
.
text
.
span
.
TextEmphasisSpan
.
POSITION_AFTER
;
import
static
com
.
google
.
android
.
exoplayer2
.
text
.
span
.
TextEmphasisSpan
.
POSITION_BEFORE
;
import
static
com
.
google
.
android
.
exoplayer2
.
text
.
ttml
.
TtmlStyle
.
STYLE_BOLD
;
import
static
com
.
google
.
android
.
exoplayer2
.
text
.
ttml
.
TtmlStyle
.
STYLE_BOLD
;
import
static
com
.
google
.
android
.
exoplayer2
.
text
.
ttml
.
TtmlStyle
.
STYLE_BOLD_ITALIC
;
import
static
com
.
google
.
android
.
exoplayer2
.
text
.
ttml
.
TtmlStyle
.
STYLE_BOLD_ITALIC
;
import
static
com
.
google
.
android
.
exoplayer2
.
text
.
ttml
.
TtmlStyle
.
STYLE_ITALIC
;
import
static
com
.
google
.
android
.
exoplayer2
.
text
.
ttml
.
TtmlStyle
.
STYLE_ITALIC
;
...
@@ -46,6 +50,7 @@ public final class TtmlStyleTest {
...
@@ -46,6 +50,7 @@ public final class TtmlStyleTest {
private
static
final
int
RUBY_POSITION
=
RubySpan
.
POSITION_UNDER
;
private
static
final
int
RUBY_POSITION
=
RubySpan
.
POSITION_UNDER
;
private
static
final
Layout
.
Alignment
TEXT_ALIGN
=
Layout
.
Alignment
.
ALIGN_CENTER
;
private
static
final
Layout
.
Alignment
TEXT_ALIGN
=
Layout
.
Alignment
.
ALIGN_CENTER
;
private
static
final
boolean
TEXT_COMBINE
=
true
;
private
static
final
boolean
TEXT_COMBINE
=
true
;
public
static
final
String
TEXT_EMPHASIS_STYLE
=
"dot before"
;
private
final
TtmlStyle
populatedStyle
=
private
final
TtmlStyle
populatedStyle
=
new
TtmlStyle
()
new
TtmlStyle
()
...
@@ -62,7 +67,8 @@ public final class TtmlStyleTest {
...
@@ -62,7 +67,8 @@ public final class TtmlStyleTest {
.
setRubyType
(
RUBY_TYPE
)
.
setRubyType
(
RUBY_TYPE
)
.
setRubyPosition
(
RUBY_POSITION
)
.
setRubyPosition
(
RUBY_POSITION
)
.
setTextAlign
(
TEXT_ALIGN
)
.
setTextAlign
(
TEXT_ALIGN
)
.
setTextCombine
(
TEXT_COMBINE
);
.
setTextCombine
(
TEXT_COMBINE
)
.
setTextEmphasis
(
TextEmphasis
.
createTextEmphasis
(
TEXT_EMPHASIS_STYLE
));
@Test
@Test
public
void
inheritStyle
()
{
public
void
inheritStyle
()
{
...
@@ -86,6 +92,9 @@ public final class TtmlStyleTest {
...
@@ -86,6 +92,9 @@ public final class TtmlStyleTest {
assertWithMessage
(
"backgroundColor should not be inherited"
)
assertWithMessage
(
"backgroundColor should not be inherited"
)
.
that
(
style
.
hasBackgroundColor
())
.
that
(
style
.
hasBackgroundColor
())
.
isFalse
();
.
isFalse
();
assertThat
(
style
.
getTextEmphasis
()).
isNotNull
();
assertThat
(
style
.
getTextEmphasis
().
mark
).
isEqualTo
(
MARK_FILLED_DOT
);
assertThat
(
style
.
getTextEmphasis
().
position
).
isEqualTo
(
POSITION_BEFORE
);
}
}
@Test
@Test
...
@@ -109,6 +118,9 @@ public final class TtmlStyleTest {
...
@@ -109,6 +118,9 @@ public final class TtmlStyleTest {
.
that
(
style
.
getBackgroundColor
())
.
that
(
style
.
getBackgroundColor
())
.
isEqualTo
(
BACKGROUND_COLOR
);
.
isEqualTo
(
BACKGROUND_COLOR
);
assertWithMessage
(
"rubyType should be chained"
).
that
(
style
.
getRubyType
()).
isEqualTo
(
RUBY_TYPE
);
assertWithMessage
(
"rubyType should be chained"
).
that
(
style
.
getRubyType
()).
isEqualTo
(
RUBY_TYPE
);
assertThat
(
style
.
getTextEmphasis
()).
isNotNull
();
assertThat
(
style
.
getTextEmphasis
().
mark
).
isEqualTo
(
MARK_FILLED_DOT
);
assertThat
(
style
.
getTextEmphasis
().
position
).
isEqualTo
(
POSITION_BEFORE
);
}
}
@Test
@Test
...
@@ -245,4 +257,13 @@ public final class TtmlStyleTest {
...
@@ -245,4 +257,13 @@ public final class TtmlStyleTest {
style
.
setTextCombine
(
true
);
style
.
setTextCombine
(
true
);
assertThat
(
style
.
getTextCombine
()).
isTrue
();
assertThat
(
style
.
getTextCombine
()).
isTrue
();
}
}
@Test
public
void
textEmphasis
()
{
TtmlStyle
style
=
new
TtmlStyle
();
assertThat
(
style
.
getTextEmphasis
()).
isNull
();
style
.
setTextEmphasis
(
TextEmphasis
.
createTextEmphasis
(
"open sesame after"
));
assertThat
(
style
.
getTextEmphasis
().
mark
).
isEqualTo
(
MARK_OPEN_SESAME
);
assertThat
(
style
.
getTextEmphasis
().
position
).
isEqualTo
(
POSITION_AFTER
);
}
}
}
library/ui/src/main/java/com/google/android/exoplayer2/ui/SpannedToHtmlConverter.java
View file @
fcda8d47
...
@@ -31,6 +31,7 @@ import android.util.SparseArray;
...
@@ -31,6 +31,7 @@ import android.util.SparseArray;
import
androidx.annotation.Nullable
;
import
androidx.annotation.Nullable
;
import
com.google.android.exoplayer2.text.span.HorizontalTextInVerticalContextSpan
;
import
com.google.android.exoplayer2.text.span.HorizontalTextInVerticalContextSpan
;
import
com.google.android.exoplayer2.text.span.RubySpan
;
import
com.google.android.exoplayer2.text.span.RubySpan
;
import
com.google.android.exoplayer2.text.span.TextEmphasisSpan
;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.Util
;
import
com.google.android.exoplayer2.util.Util
;
import
com.google.common.collect.ImmutableMap
;
import
com.google.common.collect.ImmutableMap
;
...
@@ -197,6 +198,14 @@ import java.util.regex.Pattern;
...
@@ -197,6 +198,14 @@ import java.util.regex.Pattern;
}
}
}
else
if
(
span
instanceof
UnderlineSpan
)
{
}
else
if
(
span
instanceof
UnderlineSpan
)
{
return
"<u>"
;
return
"<u>"
;
}
else
if
(
span
instanceof
TextEmphasisSpan
)
{
TextEmphasisSpan
textEmphasisSpan
=
(
TextEmphasisSpan
)
span
;
String
style
=
getTextEmphasisStyle
(
textEmphasisSpan
.
mark
);
String
position
=
getTextEmphasisPosition
(
textEmphasisSpan
.
position
);
return
Util
.
formatInvariant
(
"<span style='-webkit-text-emphasis-style: %s; text-emphasis-style: %s; "
+
"-webkit-text-emphasis-position: %s; text-emphasis-position: %s;'>"
,
style
,
style
,
position
,
position
);
}
else
{
}
else
{
return
null
;
return
null
;
}
}
...
@@ -209,7 +218,8 @@ import java.util.regex.Pattern;
...
@@ -209,7 +218,8 @@ import java.util.regex.Pattern;
||
span
instanceof
BackgroundColorSpan
||
span
instanceof
BackgroundColorSpan
||
span
instanceof
HorizontalTextInVerticalContextSpan
||
span
instanceof
HorizontalTextInVerticalContextSpan
||
span
instanceof
AbsoluteSizeSpan
||
span
instanceof
AbsoluteSizeSpan
||
span
instanceof
RelativeSizeSpan
)
{
||
span
instanceof
RelativeSizeSpan
||
span
instanceof
TextEmphasisSpan
)
{
return
"</span>"
;
return
"</span>"
;
}
else
if
(
span
instanceof
TypefaceSpan
)
{
}
else
if
(
span
instanceof
TypefaceSpan
)
{
@Nullable
String
fontFamily
=
((
TypefaceSpan
)
span
).
getFamily
();
@Nullable
String
fontFamily
=
((
TypefaceSpan
)
span
).
getFamily
();
...
@@ -232,6 +242,47 @@ import java.util.regex.Pattern;
...
@@ -232,6 +242,47 @@ import java.util.regex.Pattern;
return
null
;
return
null
;
}
}
private
static
String
getTextEmphasisStyle
(
@TextEmphasisSpan
.
Mark
int
mark
)
{
switch
(
mark
)
{
case
TextEmphasisSpan
.
MARK_FILLED_CIRCLE
:
return
"filled circle"
;
case
TextEmphasisSpan
.
MARK_FILLED_DOT
:
return
"filled dot"
;
case
TextEmphasisSpan
.
MARK_FILLED_SESAME
:
return
"filled sesame"
;
case
TextEmphasisSpan
.
MARK_OPEN_CIRCLE
:
return
"open circle"
;
case
TextEmphasisSpan
.
MARK_OPEN_DOT
:
return
"open dot"
;
case
TextEmphasisSpan
.
MARK_OPEN_SESAME
:
return
"open sesame"
;
case
TextEmphasisSpan
.
MARK_AUTO
:
// TODO
// https://www.w3.org/TR/ttml2/#style-value-emphasis-style
// If a vertical writing mode applies, then equivalent to filled sesame; otherwise,
// equivalent to filled circle.
case
TextEmphasisSpan
.
MARK_UNKNOWN
:
default
:
return
"unset"
;
}
}
private
static
String
getTextEmphasisPosition
(
@TextEmphasisSpan
.
Position
int
position
){
switch
(
position
)
{
case
TextEmphasisSpan
.
POSITION_AFTER
:
return
"under left"
;
case
TextEmphasisSpan
.
POSITION_UNKNOWN
:
case
TextEmphasisSpan
.
POSITION_BEFORE
:
case
TextEmphasisSpan
.
POSITION_OUTSIDE
:
/* Not supported, fallback to "before" */
default
:
// https://www.w3.org/TR/ttml2/#style-value-annotation-position
// 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; as such,
// an implementation that supports text annotation marks must minimally support the before
// value.
return
"over right"
;
}
}
private
static
Transition
getOrCreate
(
SparseArray
<
Transition
>
transitions
,
int
key
)
{
private
static
Transition
getOrCreate
(
SparseArray
<
Transition
>
transitions
,
int
key
)
{
@Nullable
Transition
transition
=
transitions
.
get
(
key
);
@Nullable
Transition
transition
=
transitions
.
get
(
key
);
if
(
transition
==
null
)
{
if
(
transition
==
null
)
{
...
...
library/ui/src/test/java/com/google/android/exoplayer2/ui/SpannedToHtmlConverterTest.java
View file @
fcda8d47
...
@@ -34,6 +34,7 @@ import androidx.test.core.app.ApplicationProvider;
...
@@ -34,6 +34,7 @@ import androidx.test.core.app.ApplicationProvider;
import
androidx.test.ext.junit.runners.AndroidJUnit4
;
import
androidx.test.ext.junit.runners.AndroidJUnit4
;
import
com.google.android.exoplayer2.text.span.HorizontalTextInVerticalContextSpan
;
import
com.google.android.exoplayer2.text.span.HorizontalTextInVerticalContextSpan
;
import
com.google.android.exoplayer2.text.span.RubySpan
;
import
com.google.android.exoplayer2.text.span.RubySpan
;
import
com.google.android.exoplayer2.text.span.TextEmphasisSpan
;
import
org.junit.Test
;
import
org.junit.Test
;
import
org.junit.runner.RunWith
;
import
org.junit.runner.RunWith
;
import
org.robolectric.annotation.Config
;
import
org.robolectric.annotation.Config
;
...
@@ -280,6 +281,37 @@ public class SpannedToHtmlConverterTest {
...
@@ -280,6 +281,37 @@ public class SpannedToHtmlConverterTest {
}
}
@Test
@Test
public
void
convert_supportsTextEmphasisSpan
()
{
SpannableString
spanned
=
new
SpannableString
(
"Text emphasis おはよ ございます "
);
spanned
.
setSpan
(
new
TextEmphasisSpan
(
TextEmphasisSpan
.
MARK_FILLED_CIRCLE
,
TextEmphasisSpan
.
POSITION_BEFORE
),
"Text emphasis "
.
length
(),
"Text emphasis おはよ"
.
length
(),
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
);
spanned
.
setSpan
(
new
TextEmphasisSpan
(
TextEmphasisSpan
.
MARK_OPEN_SESAME
,
TextEmphasisSpan
.
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;"
+
"'>おはよ</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;"
+
"'>ございます </span>"
);
}
@Test
public
void
convert_supportsUnderlineSpan
()
{
public
void
convert_supportsUnderlineSpan
()
{
SpannableString
spanned
=
new
SpannableString
(
"String with underlined section."
);
SpannableString
spanned
=
new
SpannableString
(
"String with underlined section."
);
spanned
.
setSpan
(
spanned
.
setSpan
(
...
...
testdata/src/test/assets/media/ttml/text_emphasis.xml
0 → 100644
View file @
fcda8d47
<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=
"region1"
tts:extent=
"80.000% 80.000%"
tts:origin=
"10.000% 10.000%"
tts:writingMode=
"tbrl"
/>
<region
xml:id=
"region2"
tts:extent=
"80.000% 80.000%"
tts:origin=
"10.000% 10.000%"
tts:writingMode=
"tblr"
/>
<region
xml:id=
"region3"
tts:extent=
"80.000% 80.000%"
tts:origin=
"10.000% 10.000%"
tts:writingMode=
"tb"
/>
<region
xml:id=
"region4"
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=
"region1"
>
Auto (TBLR)
<span
tts:textEmphasis=
"auto"
>
ございます
</span></p>
</div>
<div>
<p
begin=
"150s"
end=
"158s"
region=
"region2"
>
Auto (TBRL)
<span
tts:textEmphasis=
"auto"
>
おはよ
</span></p>
</div>
<div>
<p
begin=
"160s"
end=
"168s"
region=
"region3"
>
Auto (TB)
<span
tts:textEmphasis=
"auto"
>
ございます
</span></p>
</div>
<div>
<p
begin=
"170s"
end=
"178s"
region=
"region4"
>
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 @
fcda8d47
...
@@ -38,6 +38,7 @@ import androidx.annotation.ColorInt;
...
@@ -38,6 +38,7 @@ import androidx.annotation.ColorInt;
import
androidx.annotation.Nullable
;
import
androidx.annotation.Nullable
;
import
com.google.android.exoplayer2.text.span.HorizontalTextInVerticalContextSpan
;
import
com.google.android.exoplayer2.text.span.HorizontalTextInVerticalContextSpan
;
import
com.google.android.exoplayer2.text.span.RubySpan
;
import
com.google.android.exoplayer2.text.span.RubySpan
;
import
com.google.android.exoplayer2.text.span.TextEmphasisSpan
;
import
com.google.android.exoplayer2.util.Util
;
import
com.google.android.exoplayer2.util.Util
;
import
com.google.common.truth.Fact
;
import
com.google.common.truth.Fact
;
import
com.google.common.truth.FailureMetadata
;
import
com.google.common.truth.FailureMetadata
;
...
@@ -579,6 +580,47 @@ public final class SpannedSubject extends Subject {
...
@@ -579,6 +580,47 @@ 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 WithSpanFlags} object for optional additional assertions on the flags.
*/
public
TextEmphasisDescription
hasTextEmphasisSpanBetween
(
int
start
,
int
end
)
{
if
(
actual
==
null
)
{
failWithoutActual
(
simpleFact
(
"Spanned must not be null"
));
return
ALREADY_FAILED_WITH_MARK
;
}
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
;
}
/**
* Checks that the subject has no {@link TextEmphasisSpan}s on any of the text between
* {@code start} and {@code end}.
*
* <p>This fails even if the start and end indexes don't exactly match.
*
* @param start The start index to start searching for spans.
* @param end The end index to stop searching for spans.
*/
public
void
hasNoTextEmphasisSpanBetween
(
int
start
,
int
end
)
{
hasNoSpansOfTypeBetween
(
TextEmphasisSpan
.
class
,
start
,
end
);
}
/**
* Checks that the subject has no {@link HorizontalTextInVerticalContextSpan}s on any of the text
* Checks that the subject has no {@link HorizontalTextInVerticalContextSpan}s on any of the text
* between {@code start} and {@code end}.
* between {@code start} and {@code end}.
*
*
...
@@ -1110,4 +1152,95 @@ public final class SpannedSubject extends Subject {
...
@@ -1110,4 +1152,95 @@ public final class SpannedSubject extends Subject {
}
}
}
}
}
}
/** Allows assertions about a span's textEmphasis mark and its position. */
public
interface
TextEmphasisDescription
{
/**
* Checks that at least one of the matched spans has the expected {@code mark} and {@code position}.
*
* @param mark The expected mark
* @param position The expected position of the mark
* @return A {@link WithSpanFlags} object for optional additional assertions on the flags.
*/
AndSpanFlags
withMarkAndPosition
(
@TextEmphasisSpan
.
Mark
int
mark
,
@TextEmphasisSpan
.
Position
int
position
);
}
private
static
final
TextEmphasisDescription
ALREADY_FAILED_WITH_MARK
=
(
mark
,
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
TextEmphasisDescription
{
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
.
Mark
int
mark
,
@TextEmphasisSpan
.
Position
int
position
)
{
List
<
Integer
>
matchingSpanFlags
=
new
ArrayList
<>();
List
<
MarkAndPosition
>
textEmphasisMarksAndPositions
=
new
ArrayList
<>();
for
(
TextEmphasisSpan
span
:
actualSpans
)
{
textEmphasisMarksAndPositions
.
add
(
new
MarkAndPosition
(
span
.
mark
,
span
.
position
));
if
(
span
.
mark
==
mark
&&
span
.
position
==
position
)
{
matchingSpanFlags
.
add
(
actualSpanned
.
getSpanFlags
(
span
));
}
}
check
(
"textEmphasisMarkAndPosition"
)
.
that
(
textEmphasisMarksAndPositions
)
.
containsExactly
(
new
MarkAndPosition
(
mark
,
position
));
return
check
(
"flags"
).
about
(
spanFlags
()).
that
(
matchingSpanFlags
);
}
private
static
final
class
MarkAndPosition
{
@TextEmphasisSpan
.
Mark
private
final
int
mark
;
@TextEmphasisSpan
.
Position
private
final
int
position
;
private
MarkAndPosition
(
@TextEmphasisSpan
.
Mark
int
mark
,
@TextEmphasisSpan
.
Position
int
position
)
{
this
.
mark
=
mark
;
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
)
&&
(
mark
==
that
.
mark
);
}
@Override
public
int
hashCode
()
{
int
result
=
34613
*
mark
+
position
;
return
result
;
}
@Override
public
String
toString
()
{
return
String
.
format
(
"{mark=%s,position=%s}"
,
mark
,
position
);
}
}
}
}
}
testutils/src/test/java/com/google/android/exoplayer2/testutil/truth/SpannedSubjectTest.java
View file @
fcda8d47
...
@@ -41,6 +41,8 @@ import com.google.android.exoplayer2.testutil.truth.SpannedSubject.AndSpanFlags;
...
@@ -41,6 +41,8 @@ import com.google.android.exoplayer2.testutil.truth.SpannedSubject.AndSpanFlags;
import
com.google.android.exoplayer2.testutil.truth.SpannedSubject.WithSpanFlags
;
import
com.google.android.exoplayer2.testutil.truth.SpannedSubject.WithSpanFlags
;
import
com.google.android.exoplayer2.text.span.HorizontalTextInVerticalContextSpan
;
import
com.google.android.exoplayer2.text.span.HorizontalTextInVerticalContextSpan
;
import
com.google.android.exoplayer2.text.span.RubySpan
;
import
com.google.android.exoplayer2.text.span.RubySpan
;
import
com.google.android.exoplayer2.text.span.TextEmphasisSpan
;
import
com.google.android.exoplayer2.util.Util
;
import
com.google.common.truth.ExpectFailure
;
import
com.google.common.truth.ExpectFailure
;
import
org.junit.Test
;
import
org.junit.Test
;
import
org.junit.runner.RunWith
;
import
org.junit.runner.RunWith
;
...
@@ -680,6 +682,104 @@ public class SpannedSubjectTest {
...
@@ -680,6 +682,104 @@ public class SpannedSubjectTest {
}
}
@Test
@Test
public
void
textEmphasis_success
()
{
SpannableString
spannable
=
createSpannable
(
new
TextEmphasisSpan
(
TextEmphasisSpan
.
MARK_FILLED_CIRCLE
,
TextEmphasisSpan
.
POSITION_AFTER
));
assertThat
(
spannable
)
.
hasTextEmphasisSpanBetween
(
SPAN_START
,
SPAN_END
)
.
withMarkAndPosition
(
TextEmphasisSpan
.
MARK_FILLED_CIRCLE
,
TextEmphasisSpan
.
POSITION_AFTER
)
.
andFlags
(
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
);
}
@Test
public
void
textEmphasis_wrongIndex
()
{
checkHasSpanFailsDueToIndexMismatch
(
new
TextEmphasisSpan
(
TextEmphasisSpan
.
MARK_FILLED_CIRCLE
,
TextEmphasisSpan
.
POSITION_AFTER
),
SpannedSubject:
:
hasTextEmphasisSpanBetween
);
}
@Test
public
void
textEmphasis_wrongMark
()
{
SpannableString
spannable
=
createSpannable
(
new
TextEmphasisSpan
(
TextEmphasisSpan
.
MARK_FILLED_CIRCLE
,
TextEmphasisSpan
.
POSITION_AFTER
));
AssertionError
expected
=
expectFailure
(
whenTesting
->
whenTesting
.
that
(
spannable
)
.
hasTextEmphasisSpanBetween
(
SPAN_START
,
SPAN_END
)
.
withMarkAndPosition
(
TextEmphasisSpan
.
MARK_OPEN_DOT
,
TextEmphasisSpan
.
POSITION_AFTER
));
assertThat
(
expected
).
factValue
(
"value of"
).
contains
(
"textEmphasisMarkAndPosition"
);
assertThat
(
expected
).
factValue
(
"expected"
).
contains
(
Util
.
formatInvariant
(
"{mark=%d,position=%d}"
,
TextEmphasisSpan
.
MARK_OPEN_DOT
,
TextEmphasisSpan
.
POSITION_AFTER
));
assertThat
(
expected
).
factValue
(
"but was"
).
contains
(
Util
.
formatInvariant
(
"{mark=%d,position=%d}"
,
TextEmphasisSpan
.
MARK_FILLED_CIRCLE
,
TextEmphasisSpan
.
POSITION_AFTER
));
}
@Test
public
void
textEmphasis_wrongPosition
()
{
SpannableString
spannable
=
createSpannable
(
new
TextEmphasisSpan
(
TextEmphasisSpan
.
MARK_OPEN_SESAME
,
TextEmphasisSpan
.
POSITION_BEFORE
));
AssertionError
expected
=
expectFailure
(
whenTesting
->
whenTesting
.
that
(
spannable
)
.
hasTextEmphasisSpanBetween
(
SPAN_START
,
SPAN_END
)
.
withMarkAndPosition
(
TextEmphasisSpan
.
MARK_OPEN_SESAME
,
TextEmphasisSpan
.
POSITION_AFTER
));
assertThat
(
expected
).
factValue
(
"value of"
).
contains
(
"textEmphasisMarkAndPosition"
);
assertThat
(
expected
).
factValue
(
"expected"
).
contains
(
Util
.
formatInvariant
(
"{mark=%d,position=%d}"
,
TextEmphasisSpan
.
MARK_OPEN_SESAME
,
TextEmphasisSpan
.
POSITION_AFTER
));
assertThat
(
expected
).
factValue
(
"but was"
).
contains
(
Util
.
formatInvariant
(
"{mark=%d,position=%d}"
,
TextEmphasisSpan
.
MARK_OPEN_SESAME
,
TextEmphasisSpan
.
POSITION_BEFORE
));
}
@Test
public
void
textEmphasis_wrongFlags
()
{
checkHasSpanFailsDueToFlagMismatch
(
new
TextEmphasisSpan
(
TextEmphasisSpan
.
MARK_OPEN_SESAME
,
TextEmphasisSpan
.
POSITION_BEFORE
),
(
subject
,
start
,
end
)
->
subject
.
hasTextEmphasisSpanBetween
(
start
,
end
)
.
withMarkAndPosition
(
TextEmphasisSpan
.
MARK_OPEN_SESAME
,
TextEmphasisSpan
.
POSITION_BEFORE
));
}
@Test
public
void
noTextEmphasis_success
()
{
SpannableString
spannable
=
createSpannableWithUnrelatedSpanAnd
(
new
TextEmphasisSpan
(
TextEmphasisSpan
.
MARK_FILLED_CIRCLE
,
TextEmphasisSpan
.
POSITION_AFTER
));
assertThat
(
spannable
).
hasNoTextEmphasisSpanBetween
(
UNRELATED_SPAN_START
,
UNRELATED_SPAN_END
);
}
@Test
public
void
noTextEmphasis_failure
()
{
checkHasNoSpanFails
(
new
TextEmphasisSpan
(
TextEmphasisSpan
.
MARK_FILLED_CIRCLE
,
TextEmphasisSpan
.
POSITION_AFTER
),
SpannedSubject:
:
hasNoTextEmphasisSpanBetween
);
}
@Test
public
void
horizontalTextInVerticalContextSpan_success
()
{
public
void
horizontalTextInVerticalContextSpan_success
()
{
SpannableString
spannable
=
SpannableString
spannable
=
createSpannable
(
createSpannable
(
...
...
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