Use CSS inheritance for background-color in WebViewSubtitleOutput

CSS background-color isn't inherited to inner HTML elements by default:
https://developer.mozilla.org/en-US/docs/Web/CSS/background-color

But Android Span styling assumes an outer BackgroundColorSpan will
affect inner spans. This usually doesn't make a difference, because
HTML elements are transparent by default, so there's an implicit
inheritance by just being able to see through to the 'outer' element
underneath. However this doesn't work if the inner element sits outside
the bounding box of the outer element, e.g. <rt> (ruby text, sits above/below)
or a <span> with font-size > 100%.
END_PUBLIC

Demo of <rt> and font-size problems: http://go/cpl/ruby-backgrounds/1
Demo of CSS inheritance: http://go/cpl/css-inheritance/1

PiperOrigin-RevId: 320915999
This commit is contained in:
ibaker 2020-07-13 10:02:25 +01:00 committed by kim-vde
parent 245459a34a
commit cdfee89181
4 changed files with 179 additions and 65 deletions

View File

@ -32,4 +32,12 @@ import com.google.android.exoplayer2.util.Util;
"rgba(%d,%d,%d,%.3f)", "rgba(%d,%d,%d,%.3f)",
Color.red(color), Color.green(color), Color.blue(color), Color.alpha(color) / 255.0); Color.red(color), Color.green(color), Color.blue(color), Color.alpha(color) / 255.0);
} }
/**
* Returns a CSS selector that selects all elements with {@code class=className} and all their
* descendants.
*/
public static String cssAllClassDescendantsSelector(String className) {
return "." + className + ",." + className + " *";
}
} }

View File

@ -33,10 +33,15 @@ import com.google.android.exoplayer2.text.span.HorizontalTextInVerticalContextSp
import com.google.android.exoplayer2.text.span.RubySpan; import com.google.android.exoplayer2.text.span.RubySpan;
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 java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern; import java.util.regex.Pattern;
/** /**
@ -46,7 +51,6 @@ import java.util.regex.Pattern;
* <p>Supports all of the spans used by ExoPlayer's subtitle decoders, including custom ones found * <p>Supports all of the spans used by ExoPlayer's subtitle decoders, including custom ones found
* in {@link com.google.android.exoplayer2.text.span}. * in {@link com.google.android.exoplayer2.text.span}.
*/ */
// TODO: Add support for more span types - only a small selection are currently implemented.
/* package */ final class SpannedToHtmlConverter { /* package */ final class SpannedToHtmlConverter {
// Matches /n and /r/n in ampersand-encoding (returned from Html.escapeHtml). // Matches /n and /r/n in ampersand-encoding (returned from Html.escapeHtml).
@ -74,16 +78,29 @@ import java.util.regex.Pattern;
* @param displayDensity The screen density of the device. WebView treats 1 CSS px as one Android * @param displayDensity The screen density of the device. WebView treats 1 CSS px as one Android
* dp, so to convert size values from Android px to CSS px we need to know the screen density. * dp, so to convert size values from Android px to CSS px we need to know the screen density.
*/ */
public static String convert(@Nullable CharSequence text, float displayDensity) { public static HtmlAndCss convert(@Nullable CharSequence text, float displayDensity) {
if (text == null) { if (text == null) {
return ""; return new HtmlAndCss("", /* cssRuleSets= */ ImmutableMap.of());
} }
if (!(text instanceof Spanned)) { if (!(text instanceof Spanned)) {
return escapeHtml(text); return new HtmlAndCss(escapeHtml(text), /* cssRuleSets= */ ImmutableMap.of());
} }
Spanned spanned = (Spanned) text; Spanned spanned = (Spanned) text;
SparseArray<Transition> spanTransitions = findSpanTransitions(spanned, displayDensity);
// Use CSS inheritance to ensure BackgroundColorSpans affect all inner elements
Set<Integer> backgroundColors = new HashSet<>();
for (BackgroundColorSpan backgroundColorSpan :
spanned.getSpans(0, spanned.length(), BackgroundColorSpan.class)) {
backgroundColors.add(backgroundColorSpan.getBackgroundColor());
}
HashMap<String, String> cssRuleSets = new HashMap<>();
for (int backgroundColor : backgroundColors) {
cssRuleSets.put(
HtmlUtils.cssAllClassDescendantsSelector("bg_" + backgroundColor),
Util.formatInvariant("background-color:%s;", HtmlUtils.toCssRgba(backgroundColor)));
}
SparseArray<Transition> spanTransitions = findSpanTransitions(spanned, displayDensity);
StringBuilder html = new StringBuilder(spanned.length()); StringBuilder html = new StringBuilder(spanned.length());
int previousTransition = 0; int previousTransition = 0;
for (int i = 0; i < spanTransitions.size(); i++) { for (int i = 0; i < spanTransitions.size(); i++) {
@ -104,7 +121,7 @@ import java.util.regex.Pattern;
html.append(escapeHtml(spanned.subSequence(previousTransition, spanned.length()))); html.append(escapeHtml(spanned.subSequence(previousTransition, spanned.length())));
return html.toString(); return new HtmlAndCss(html.toString(), cssRuleSets);
} }
private static SparseArray<Transition> findSpanTransitions( private static SparseArray<Transition> findSpanTransitions(
@ -137,9 +154,7 @@ import java.util.regex.Pattern;
"<span style='color:%s;'>", HtmlUtils.toCssRgba(colorSpan.getForegroundColor())); "<span style='color:%s;'>", HtmlUtils.toCssRgba(colorSpan.getForegroundColor()));
} else if (span instanceof BackgroundColorSpan) { } else if (span instanceof BackgroundColorSpan) {
BackgroundColorSpan colorSpan = (BackgroundColorSpan) span; BackgroundColorSpan colorSpan = (BackgroundColorSpan) span;
return Util.formatInvariant( return Util.formatInvariant("<span class='bg_%s'>", colorSpan.getBackgroundColor());
"<span style='background-color:%s;'>",
HtmlUtils.toCssRgba(colorSpan.getBackgroundColor()));
} else if (span instanceof HorizontalTextInVerticalContextSpan) { } else if (span instanceof HorizontalTextInVerticalContextSpan) {
return "<span style='text-combine-upright:all;'>"; return "<span style='text-combine-upright:all;'>";
} else if (span instanceof AbsoluteSizeSpan) { } else if (span instanceof AbsoluteSizeSpan) {
@ -231,6 +246,26 @@ import java.util.regex.Pattern;
return NEWLINE_PATTERN.matcher(escaped).replaceAll("<br>"); return NEWLINE_PATTERN.matcher(escaped).replaceAll("<br>");
} }
/** Container class for an HTML string and associated CSS rulesets. */
public static class HtmlAndCss {
/** A raw HTML string. */
public final String html;
/**
* CSS rulesets used to style {@link #html}.
*
* <p>Each key is a CSS selector, and each value is a CSS declaration (i.e. a semi-colon
* separated list of colon-separated key-value pairs, e.g "prop1:val1;prop2:val2;").
*/
public final Map<String, String> cssRuleSets;
private HtmlAndCss(String html, Map<String, String> cssRuleSets) {
this.html = html;
this.cssRuleSets = cssRuleSets;
}
}
private static final class SpanInfo { private static final class SpanInfo {
/** /**
* Sort by end index (descending), then by opening tag and then closing tag (both ascending, for * Sort by end index (descending), then by opening tag and then closing tag (both ascending, for

View File

@ -31,11 +31,14 @@ import android.widget.FrameLayout;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.text.CaptionStyleCompat; import com.google.android.exoplayer2.text.CaptionStyleCompat;
import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.Cue;
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.base.Charsets; import com.google.common.base.Charsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* A {@link SubtitleView.Output} that uses a {@link WebView} to render subtitles. * A {@link SubtitleView.Output} that uses a {@link WebView} to render subtitles.
@ -51,6 +54,8 @@ import java.util.List;
*/ */
private static final float CSS_LINE_HEIGHT = 1.2f; private static final float CSS_LINE_HEIGHT = 1.2f;
private static final String DEFAULT_BACKGROUND_CSS_CLASS = "default_bg";
/** /**
* A {@link CanvasSubtitleOutput} used for displaying bitmap cues. * A {@link CanvasSubtitleOutput} used for displaying bitmap cues.
* *
@ -162,7 +167,7 @@ import java.util.List;
StringBuilder html = new StringBuilder(); StringBuilder html = new StringBuilder();
html.append( html.append(
Util.formatInvariant( Util.formatInvariant(
"<html><body><div style='" "<body><div style='"
+ "-webkit-user-select:none;" + "-webkit-user-select:none;"
+ "position:fixed;" + "position:fixed;"
+ "top:0;" + "top:0;"
@ -179,8 +184,10 @@ import java.util.List;
CSS_LINE_HEIGHT, CSS_LINE_HEIGHT,
convertCaptionStyleToCssTextShadow(style))); convertCaptionStyleToCssTextShadow(style)));
String backgroundColorCss = HtmlUtils.toCssRgba(style.backgroundColor); Map<String, String> cssRuleSets = new HashMap<>();
cssRuleSets.put(
HtmlUtils.cssAllClassDescendantsSelector(DEFAULT_BACKGROUND_CSS_CLASS),
Util.formatInvariant("background-color:%s;", HtmlUtils.toCssRgba(style.backgroundColor)));
for (int i = 0; i < textCues.size(); i++) { for (int i = 0; i < textCues.size(); i++) {
Cue cue = textCues.get(i); Cue cue = textCues.get(i);
float positionPercent = (cue.position != Cue.DIMEN_UNSET) ? (cue.position * 100) : 50; float positionPercent = (cue.position != Cue.DIMEN_UNSET) ? (cue.position * 100) : 50;
@ -255,6 +262,18 @@ import java.util.List;
verticalTranslatePercent = lineAnchorTranslatePercent; verticalTranslatePercent = lineAnchorTranslatePercent;
} }
SpannedToHtmlConverter.HtmlAndCss htmlAndCss =
SpannedToHtmlConverter.convert(
cue.text, getContext().getResources().getDisplayMetrics().density);
for (String cssSelector : cssRuleSets.keySet()) {
@Nullable
String previousCssDeclarationBlock =
cssRuleSets.put(cssSelector, cssRuleSets.get(cssSelector));
Assertions.checkState(
previousCssDeclarationBlock == null
|| previousCssDeclarationBlock.equals(cssRuleSets.get(cssSelector)));
}
html.append( html.append(
Util.formatInvariant( Util.formatInvariant(
"<div style='" "<div style='"
@ -280,16 +299,21 @@ import java.util.List;
windowCssColor, windowCssColor,
horizontalTranslatePercent, horizontalTranslatePercent,
verticalTranslatePercent)) verticalTranslatePercent))
.append(Util.formatInvariant("<span style='background-color:%s;'>", backgroundColorCss)) .append(Util.formatInvariant("<span class='%s'>", DEFAULT_BACKGROUND_CSS_CLASS))
.append( .append(htmlAndCss.html)
SpannedToHtmlConverter.convert(
cue.text, getContext().getResources().getDisplayMetrics().density))
.append("</span>") .append("</span>")
.append("</div>"); .append("</div>");
} }
html.append("</div></body></html>"); html.append("</div></body></html>");
StringBuilder htmlHead = new StringBuilder();
htmlHead.append("<html><head><style>");
for (String cssSelector : cssRuleSets.keySet()) {
htmlHead.append(cssSelector).append("{").append(cssRuleSets.get(cssSelector)).append("}");
}
htmlHead.append("</style></head>");
html.insert(0, htmlHead.toString());
webView.loadData( webView.loadData(
Base64.encodeToString(html.toString().getBytes(Charsets.UTF_8), Base64.NO_PADDING), Base64.encodeToString(html.toString().getBytes(Charsets.UTF_8), Base64.NO_PADDING),
"text/html", "text/html",

View File

@ -58,27 +58,33 @@ public class SpannedToHtmlConverterTest {
"String with colored".length(), "String with colored".length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
String html = SpannedToHtmlConverter.convert(spanned, displayDensity); SpannedToHtmlConverter.HtmlAndCss htmlAndCss =
SpannedToHtmlConverter.convert(spanned, displayDensity);
assertThat(html) assertThat(htmlAndCss.cssRuleSets).isEmpty();
assertThat(htmlAndCss.html)
.isEqualTo("String with <span style='color:rgba(64,32,16,0.200);'>colored</span> section"); .isEqualTo("String with <span style='color:rgba(64,32,16,0.200);'>colored</span> section");
} }
@Test @Test
public void convert_supportsBackgroundColorSpan() { public void convert_supportsBackgroundColorSpan() {
SpannableString spanned = new SpannableString("String with highlighted section"); SpannableString spanned = new SpannableString("String with highlighted section");
int color = Color.argb(51, 64, 32, 16);
spanned.setSpan( spanned.setSpan(
new BackgroundColorSpan(Color.argb(51, 64, 32, 16)), new BackgroundColorSpan(color),
"String with ".length(), "String with ".length(),
"String with highlighted".length(), "String with highlighted".length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
String html = SpannedToHtmlConverter.convert(spanned, displayDensity); SpannedToHtmlConverter.HtmlAndCss htmlAndCss =
SpannedToHtmlConverter.convert(spanned, displayDensity);
assertThat(html) // Double check the color int is being used for the class name as we expect.
.isEqualTo( assertThat(color).isEqualTo(859840528);
"String with <span style='background-color:rgba(64,32,16,0.200);'>highlighted</span>" assertThat(htmlAndCss.cssRuleSets)
+ " section"); .containsExactly(".bg_859840528,.bg_859840528 *", "background-color:rgba(64,32,16,0.200);");
assertThat(htmlAndCss.html)
.isEqualTo("String with <span class='bg_859840528'>highlighted</span>" + " section");
} }
@Test @Test
@ -90,9 +96,11 @@ public class SpannedToHtmlConverterTest {
"Vertical text with 123".length(), "Vertical text with 123".length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
String html = SpannedToHtmlConverter.convert(spanned, displayDensity); SpannedToHtmlConverter.HtmlAndCss htmlAndCss =
SpannedToHtmlConverter.convert(spanned, displayDensity);
assertThat(html) assertThat(htmlAndCss.cssRuleSets).isEmpty();
assertThat(htmlAndCss.html)
.isEqualTo( .isEqualTo(
"Vertical text with <span style='text-combine-upright:all;'>123</span> " "Vertical text with <span style='text-combine-upright:all;'>123</span> "
+ "horizontal numbers"); + "horizontal numbers");
@ -109,11 +117,14 @@ public class SpannedToHtmlConverterTest {
"String with 10px".length(), "String with 10px".length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
String html = SpannedToHtmlConverter.convert(spanned, displayDensity); SpannedToHtmlConverter.HtmlAndCss htmlAndCss =
SpannedToHtmlConverter.convert(spanned, displayDensity);
// 10 Android px are converted to 5 CSS px because WebView treats 1 CSS px as 1 Android dp // 10 Android px are converted to 5 CSS px because WebView treats 1 CSS px as 1 Android dp
// and we're using screen density xhdpi i.e. density=2. // and we're using screen density xhdpi i.e. density=2.
assertThat(html).isEqualTo("String with <span style='font-size:5.00px;'>10px</span> section"); assertThat(htmlAndCss.cssRuleSets).isEmpty();
assertThat(htmlAndCss.html)
.isEqualTo("String with <span style='font-size:5.00px;'>10px</span> section");
} }
// Set the screen density so we see that px are handled differently to dp. // Set the screen density so we see that px are handled differently to dp.
@ -127,9 +138,12 @@ public class SpannedToHtmlConverterTest {
"String with 10dp".length(), "String with 10dp".length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
String html = SpannedToHtmlConverter.convert(spanned, displayDensity); SpannedToHtmlConverter.HtmlAndCss htmlAndCss =
SpannedToHtmlConverter.convert(spanned, displayDensity);
assertThat(html).isEqualTo("String with <span style='font-size:10.00px;'>10dp</span> section"); assertThat(htmlAndCss.cssRuleSets).isEmpty();
assertThat(htmlAndCss.html)
.isEqualTo("String with <span style='font-size:10.00px;'>10dp</span> section");
} }
@Test @Test
@ -141,9 +155,12 @@ public class SpannedToHtmlConverterTest {
"String with 10%".length(), "String with 10%".length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
String html = SpannedToHtmlConverter.convert(spanned, displayDensity); SpannedToHtmlConverter.HtmlAndCss htmlAndCss =
SpannedToHtmlConverter.convert(spanned, displayDensity);
assertThat(html).isEqualTo("String with <span style='font-size:10.00%;'>10%</span> section"); assertThat(htmlAndCss.cssRuleSets).isEmpty();
assertThat(htmlAndCss.html)
.isEqualTo("String with <span style='font-size:10.00%;'>10%</span> section");
} }
@Test @Test
@ -155,9 +172,11 @@ public class SpannedToHtmlConverterTest {
"String with Times New Roman".length(), "String with Times New Roman".length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
String html = SpannedToHtmlConverter.convert(spanned, displayDensity); SpannedToHtmlConverter.HtmlAndCss htmlAndCss =
SpannedToHtmlConverter.convert(spanned, displayDensity);
assertThat(html) assertThat(htmlAndCss.cssRuleSets).isEmpty();
assertThat(htmlAndCss.html)
.isEqualTo( .isEqualTo(
"String with <span style='font-family:\"Times New Roman\";'>Times New Roman</span>" "String with <span style='font-family:\"Times New Roman\";'>Times New Roman</span>"
+ " section"); + " section");
@ -172,9 +191,11 @@ public class SpannedToHtmlConverterTest {
"String with unstyled".length(), "String with unstyled".length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
String html = SpannedToHtmlConverter.convert(spanned, displayDensity); SpannedToHtmlConverter.HtmlAndCss htmlAndCss =
SpannedToHtmlConverter.convert(spanned, displayDensity);
assertThat(html).isEqualTo("String with unstyled section"); assertThat(htmlAndCss.cssRuleSets).isEmpty();
assertThat(htmlAndCss.html).isEqualTo("String with unstyled section");
} }
@Test @Test
@ -186,9 +207,11 @@ public class SpannedToHtmlConverterTest {
"String with crossed-out".length(), "String with crossed-out".length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
String html = SpannedToHtmlConverter.convert(spanned, displayDensity); SpannedToHtmlConverter.HtmlAndCss htmlAndCss =
SpannedToHtmlConverter.convert(spanned, displayDensity);
assertThat(html) assertThat(htmlAndCss.cssRuleSets).isEmpty();
assertThat(htmlAndCss.html)
.isEqualTo( .isEqualTo(
"String with <span style='text-decoration:line-through;'>crossed-out</span> section"); "String with <span style='text-decoration:line-through;'>crossed-out</span> section");
} }
@ -213,9 +236,11 @@ public class SpannedToHtmlConverterTest {
"String with bold, italic and bold-italic".length(), "String with bold, italic and bold-italic".length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
String html = SpannedToHtmlConverter.convert(spanned, displayDensity); SpannedToHtmlConverter.HtmlAndCss htmlAndCss =
SpannedToHtmlConverter.convert(spanned, displayDensity);
assertThat(html) assertThat(htmlAndCss.cssRuleSets).isEmpty();
assertThat(htmlAndCss.html)
.isEqualTo( .isEqualTo(
"String with <b>bold</b>, <i>italic</i> and <b><i>bold-italic</i></b> sections."); "String with <b>bold</b>, <i>italic</i> and <b><i>bold-italic</i></b> sections.");
} }
@ -235,9 +260,11 @@ public class SpannedToHtmlConverterTest {
"String with over-annotated and under-annotated".length(), "String with over-annotated and under-annotated".length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
String html = SpannedToHtmlConverter.convert(spanned, displayDensity); SpannedToHtmlConverter.HtmlAndCss htmlAndCss =
SpannedToHtmlConverter.convert(spanned, displayDensity);
assertThat(html) assertThat(htmlAndCss.cssRuleSets).isEmpty();
assertThat(htmlAndCss.html)
.isEqualTo( .isEqualTo(
"String with " "String with "
+ "<ruby style='ruby-position:over;'>" + "<ruby style='ruby-position:over;'>"
@ -261,33 +288,39 @@ public class SpannedToHtmlConverterTest {
"String with underlined".length(), "String with underlined".length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
String html = SpannedToHtmlConverter.convert(spanned, displayDensity); SpannedToHtmlConverter.HtmlAndCss htmlAndCss =
SpannedToHtmlConverter.convert(spanned, displayDensity);
assertThat(html).isEqualTo("String with <u>underlined</u> section."); assertThat(htmlAndCss.cssRuleSets).isEmpty();
assertThat(htmlAndCss.html).isEqualTo("String with <u>underlined</u> section.");
} }
@Test @Test
public void convert_escapesHtmlInUnspannedString() { public void convert_escapesHtmlInUnspannedString() {
String html = SpannedToHtmlConverter.convert("String with <b>bold</b> tags", displayDensity); SpannedToHtmlConverter.HtmlAndCss htmlAndCss =
SpannedToHtmlConverter.convert("String with <b>bold</b> tags", displayDensity);
assertThat(html).isEqualTo("String with &lt;b&gt;bold&lt;/b&gt; tags"); assertThat(htmlAndCss.cssRuleSets).isEmpty();
assertThat(htmlAndCss.html).isEqualTo("String with &lt;b&gt;bold&lt;/b&gt; tags");
} }
@Test @Test
public void convert_handlesLinebreakInUnspannedString() { public void convert_handlesLinebreakInUnspannedString() {
String html = SpannedToHtmlConverter.HtmlAndCss htmlAndCss =
SpannedToHtmlConverter.convert( SpannedToHtmlConverter.convert(
"String with\nnew line and\r\ncrlf style too", displayDensity); "String with\nnew line and\r\ncrlf style too", displayDensity);
assertThat(html).isEqualTo("String with<br>new line and<br>crlf style too"); assertThat(htmlAndCss.cssRuleSets).isEmpty();
assertThat(htmlAndCss.html).isEqualTo("String with<br>new line and<br>crlf style too");
} }
@Test @Test
public void convert_doesntConvertAmpersandLineFeedToBrTag() { public void convert_doesntConvertAmpersandLineFeedToBrTag() {
String html = SpannedToHtmlConverter.HtmlAndCss htmlAndCss =
SpannedToHtmlConverter.convert("String with&#10;new line ampersand code", displayDensity); SpannedToHtmlConverter.convert("String with&#10;new line ampersand code", displayDensity);
assertThat(html).isEqualTo("String with&amp;#10;new line ampersand code"); assertThat(htmlAndCss.cssRuleSets).isEmpty();
assertThat(htmlAndCss.html).isEqualTo("String with&amp;#10;new line ampersand code");
} }
@Test @Test
@ -299,27 +332,32 @@ public class SpannedToHtmlConverterTest {
"String with <foo>unrecognised</foo>".length(), "String with <foo>unrecognised</foo>".length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
String html = SpannedToHtmlConverter.convert(spanned, displayDensity); SpannedToHtmlConverter.HtmlAndCss htmlAndCss =
SpannedToHtmlConverter.convert(spanned, displayDensity);
assertThat(html).isEqualTo("String with <i>&lt;foo&gt;unrecognised&lt;/foo&gt;</i> tags"); assertThat(htmlAndCss.cssRuleSets).isEmpty();
assertThat(htmlAndCss.html)
.isEqualTo("String with <i>&lt;foo&gt;unrecognised&lt;/foo&gt;</i> tags");
} }
@Test @Test
public void convert_handlesLinebreakInSpannedString() { public void convert_handlesLinebreakInSpannedString() {
String html = SpannedToHtmlConverter.HtmlAndCss htmlAndCss =
SpannedToHtmlConverter.convert( SpannedToHtmlConverter.convert(
"String with\nnew line and\r\ncrlf style too", displayDensity); "String with\nnew line and\r\ncrlf style too", displayDensity);
assertThat(html).isEqualTo("String with<br>new line and<br>crlf style too"); assertThat(htmlAndCss.cssRuleSets).isEmpty();
assertThat(htmlAndCss.html).isEqualTo("String with<br>new line and<br>crlf style too");
} }
@Test @Test
public void convert_convertsNonAsciiCharactersToAmpersandCodes() { public void convert_convertsNonAsciiCharactersToAmpersandCodes() {
String html = SpannedToHtmlConverter.HtmlAndCss htmlAndCss =
SpannedToHtmlConverter.convert( SpannedToHtmlConverter.convert(
new SpannableString("Strìng with 優しいの non-ASCII characters"), displayDensity); new SpannableString("Strìng with 優しいの non-ASCII characters"), displayDensity);
assertThat(html) assertThat(htmlAndCss.cssRuleSets).isEmpty();
assertThat(htmlAndCss.html)
.isEqualTo("Str&#236;ng with &#20778;&#12375;&#12356;&#12398; non-ASCII characters"); .isEqualTo("Str&#236;ng with &#20778;&#12375;&#12356;&#12398; non-ASCII characters");
} }
@ -337,9 +375,11 @@ public class SpannedToHtmlConverterTest {
"String with unrecognised".length(), "String with unrecognised".length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
String html = SpannedToHtmlConverter.convert(spanned, displayDensity); SpannedToHtmlConverter.HtmlAndCss htmlAndCss =
SpannedToHtmlConverter.convert(spanned, displayDensity);
assertThat(html).isEqualTo("String with unrecognised span"); assertThat(htmlAndCss.cssRuleSets).isEmpty();
assertThat(htmlAndCss.html).isEqualTo("String with unrecognised span");
} }
@Test @Test
@ -351,9 +391,12 @@ public class SpannedToHtmlConverterTest {
spanned.setSpan(new StyleSpan(Typeface.ITALIC), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); spanned.setSpan(new StyleSpan(Typeface.ITALIC), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
spanned.setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); spanned.setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
String html = SpannedToHtmlConverter.convert(spanned, displayDensity); SpannedToHtmlConverter.HtmlAndCss htmlAndCss =
SpannedToHtmlConverter.convert(spanned, displayDensity);
assertThat(html).isEqualTo("String with <b><i><u>italic-bold-underlined</u></i></b> section"); assertThat(htmlAndCss.cssRuleSets).isEmpty();
assertThat(htmlAndCss.html)
.isEqualTo("String with <b><i><u>italic-bold-underlined</u></i></b> section");
} }
@Test @Test
@ -368,9 +411,11 @@ public class SpannedToHtmlConverterTest {
"String with italic and bold".length(), "String with italic and bold".length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
String html = SpannedToHtmlConverter.convert(spanned, displayDensity); SpannedToHtmlConverter.HtmlAndCss htmlAndCss =
SpannedToHtmlConverter.convert(spanned, displayDensity);
assertThat(html).isEqualTo("String with <i>italic and <b>bold</b></i> section"); assertThat(htmlAndCss.cssRuleSets).isEmpty();
assertThat(htmlAndCss.html).isEqualTo("String with <i>italic and <b>bold</b></i> section");
} }
@Test @Test
@ -387,8 +432,10 @@ public class SpannedToHtmlConverterTest {
"String with italic and bold section".length(), "String with italic and bold section".length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
String html = SpannedToHtmlConverter.convert(spanned, displayDensity); SpannedToHtmlConverter.HtmlAndCss htmlAndCss =
SpannedToHtmlConverter.convert(spanned, displayDensity);
assertThat(html).isEqualTo("<i>String with italic <b>and bold</i> section</b>"); assertThat(htmlAndCss.cssRuleSets).isEmpty();
assertThat(htmlAndCss.html).isEqualTo("<i>String with italic <b>and bold</i> section</b>");
} }
} }