diff --git a/libraries/common/src/main/java/androidx/media3/common/text/CustomSpanBundler.java b/libraries/common/src/main/java/androidx/media3/common/text/CustomSpanBundler.java index edcda586d2d..0734e684993 100644 --- a/libraries/common/src/main/java/androidx/media3/common/text/CustomSpanBundler.java +++ b/libraries/common/src/main/java/androidx/media3/common/text/CustomSpanBundler.java @@ -53,12 +53,13 @@ *
  • {@link #RUBY} *
  • {@link #TEXT_EMPHASIS} *
  • {@link #HORIZONTAL_TEXT_IN_VERTICAL_CONTEXT} + *
  • {@link #OUTLINE_WIDTH} * */ @Documented @Retention(RetentionPolicy.SOURCE) @Target({TYPE_USE}) - @IntDef({UNKNOWN, RUBY, TEXT_EMPHASIS, HORIZONTAL_TEXT_IN_VERTICAL_CONTEXT}) + @IntDef({UNKNOWN, RUBY, TEXT_EMPHASIS, HORIZONTAL_TEXT_IN_VERTICAL_CONTEXT, OUTLINE_WIDTH}) private @interface CustomSpanType {} private static final int UNKNOWN = -1; @@ -69,6 +70,8 @@ private static final int HORIZONTAL_TEXT_IN_VERTICAL_CONTEXT = 3; + private static final int OUTLINE_WIDTH = 4; + private static final String FIELD_START_INDEX = Util.intToStringMaxRadix(0); private static final String FIELD_END_INDEX = Util.intToStringMaxRadix(1); private static final String FIELD_FLAGS = Util.intToStringMaxRadix(2); @@ -94,6 +97,11 @@ public static ArrayList bundleCustomSpans(Spanned text) { text, span, /* spanType= */ HORIZONTAL_TEXT_IN_VERTICAL_CONTEXT, /* params= */ null); bundledCustomSpans.add(bundle); } + for (OutlineSpan span : text.getSpans(0, text.length(), OutlineSpan.class)) { + Bundle bundle = + spanToBundle(text, span, /* spanType= */ OUTLINE_WIDTH, /* params= */ span.toBundle()); + bundledCustomSpans.add(bundle); + } return bundledCustomSpans; } @@ -113,6 +121,8 @@ public static void unbundleAndApplyCustomSpan(Bundle customSpanBundle, Spannable case HORIZONTAL_TEXT_IN_VERTICAL_CONTEXT: text.setSpan(new HorizontalTextInVerticalContextSpan(), start, end, flags); break; + case OUTLINE_WIDTH: + text.setSpan(OutlineSpan.fromBundle(checkNotNull(span)),start,end,flags); default: break; } diff --git a/libraries/common/src/main/java/androidx/media3/common/text/OutlineSpan.java b/libraries/common/src/main/java/androidx/media3/common/text/OutlineSpan.java new file mode 100644 index 00000000000..a9afd4ffd8f --- /dev/null +++ b/libraries/common/src/main/java/androidx/media3/common/text/OutlineSpan.java @@ -0,0 +1,41 @@ +package androidx.media3.common.text; + +import android.os.Bundle; +import android.text.TextPaint; +import android.text.style.CharacterStyle; +import androidx.annotation.Px; +import androidx.media3.common.util.Util; + +/** + * A styling span for outline width on subtitle text. + */ +public class OutlineSpan extends CharacterStyle { + + /** + * The outline size in pixels. + */ + @Px + public final float outlineWidth; + private static final String FIELD_OUTLINE_WIDTH = Util.intToStringMaxRadix(0); + + public OutlineSpan(@Px float outlineWidth) { + this.outlineWidth = outlineWidth; + } + + public Bundle toBundle() { + Bundle bundle = new Bundle(); + bundle.putFloat(FIELD_OUTLINE_WIDTH, outlineWidth); + return bundle; + } + + public static OutlineSpan fromBundle(Bundle bundle) { + return new OutlineSpan( + /* outlineWidth= */ bundle.getFloat(FIELD_OUTLINE_WIDTH) + ); + } + + @Override + public void updateDrawState(TextPaint tp) { + tp.setStrokeWidth(outlineWidth); + } +} diff --git a/libraries/ui/src/main/java/androidx/media3/ui/SpannedToHtmlConverter.java b/libraries/ui/src/main/java/androidx/media3/ui/SpannedToHtmlConverter.java index 930579f78c0..de56631b9b1 100644 --- a/libraries/ui/src/main/java/androidx/media3/ui/SpannedToHtmlConverter.java +++ b/libraries/ui/src/main/java/androidx/media3/ui/SpannedToHtmlConverter.java @@ -30,6 +30,7 @@ import android.util.SparseArray; import androidx.annotation.Nullable; import androidx.media3.common.text.HorizontalTextInVerticalContextSpan; +import androidx.media3.common.text.OutlineSpan; import androidx.media3.common.text.RubySpan; import androidx.media3.common.text.TextAnnotation; import androidx.media3.common.text.TextEmphasisSpan; @@ -207,6 +208,12 @@ private static String getOpeningTag(Object span, float displayDensity) { + "-webkit-text-emphasis-position:%2$s;text-emphasis-position:%2$s;" + "display:inline-block;'>", style, position); + } else if (span instanceof OutlineSpan) { + OutlineSpan outline = ((OutlineSpan) span); + return Util.formatInvariant( + "", + outline.outlineWidth + ); } else { return null; } @@ -220,7 +227,8 @@ private static String getClosingTag(Object span) { || span instanceof HorizontalTextInVerticalContextSpan || span instanceof AbsoluteSizeSpan || span instanceof RelativeSizeSpan - || span instanceof TextEmphasisSpan) { + || span instanceof TextEmphasisSpan + || span instanceof OutlineSpan) { return ""; } else if (span instanceof TypefaceSpan) { @Nullable String fontFamily = ((TypefaceSpan) span).getFamily(); diff --git a/libraries/ui/src/main/java/androidx/media3/ui/WebViewSubtitleOutput.java b/libraries/ui/src/main/java/androidx/media3/ui/WebViewSubtitleOutput.java index d1ee761e47e..e589cc18237 100644 --- a/libraries/ui/src/main/java/androidx/media3/ui/WebViewSubtitleOutput.java +++ b/libraries/ui/src/main/java/androidx/media3/ui/WebViewSubtitleOutput.java @@ -177,11 +177,13 @@ private void updateWebView() { + "font-size:%s;" + "line-height:%.2f;" + "text-shadow:%s;" + + "-webkit-text-stroke-color: %s;" + "'>", HtmlUtils.toCssRgba(style.foregroundColor), convertTextSizeToCss(defaultTextSizeType, defaultTextSize), CSS_LINE_HEIGHT, - convertCaptionStyleToCssTextShadow(style))); + convertCaptionStyleToCssTextShadow(style), + HtmlUtils.toCssRgba(style.edgeColor))); Map cssRuleSets = new HashMap<>(); cssRuleSets.put(