| /* |
| * Copyright 2021 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.google.android.exoplayer2.text.ttml; |
| |
| import static java.lang.annotation.RetentionPolicy.SOURCE; |
| |
| import android.text.TextUtils; |
| import androidx.annotation.IntDef; |
| import androidx.annotation.Nullable; |
| import com.google.android.exoplayer2.text.Cue; |
| import com.google.android.exoplayer2.text.span.TextAnnotation; |
| import com.google.android.exoplayer2.text.span.TextEmphasisSpan; |
| import com.google.common.base.Ascii; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Sets; |
| import java.lang.annotation.Documented; |
| import java.lang.annotation.Retention; |
| import java.util.Set; |
| import java.util.regex.Pattern; |
| |
| /** |
| * Represents a <a |
| * href="https://www.w3.org/TR/2018/REC-ttml2-20181108/#style-attribute-textEmphasis"> |
| * tts:textEmphasis</a> attribute. |
| */ |
| /* package */ final class TextEmphasis { |
| |
| @Documented |
| @Retention(SOURCE) |
| @IntDef({ |
| TextEmphasisSpan.MARK_SHAPE_NONE, |
| TextEmphasisSpan.MARK_SHAPE_CIRCLE, |
| TextEmphasisSpan.MARK_SHAPE_DOT, |
| TextEmphasisSpan.MARK_SHAPE_SESAME, |
| MARK_SHAPE_AUTO |
| }) |
| @interface MarkShape {} |
| |
| /** |
| * The "auto" mark shape is only defined in TTML and is resolved to a concrete shape when building |
| * the {@link Cue}. Hence, it is not defined in {@link TextEmphasisSpan.MarkShape}. |
| */ |
| public static final int MARK_SHAPE_AUTO = -1; |
| |
| @Documented |
| @Retention(SOURCE) |
| @IntDef({ |
| TextAnnotation.POSITION_UNKNOWN, |
| TextAnnotation.POSITION_BEFORE, |
| TextAnnotation.POSITION_AFTER, |
| POSITION_OUTSIDE |
| }) |
| public @interface Position {} |
| |
| /** |
| * The "outside" position is only defined in TTML and is resolved before outputting a {@link Cue} |
| * object. Hence, it is not defined in {@link TextAnnotation.Position}. |
| */ |
| public static final int POSITION_OUTSIDE = -2; |
| |
| private static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s+"); |
| |
| private static final ImmutableSet<String> SINGLE_STYLE_VALUES = |
| ImmutableSet.of(TtmlNode.TEXT_EMPHASIS_AUTO, TtmlNode.TEXT_EMPHASIS_NONE); |
| |
| private static final ImmutableSet<String> MARK_SHAPE_VALUES = |
| ImmutableSet.of( |
| TtmlNode.TEXT_EMPHASIS_MARK_DOT, |
| TtmlNode.TEXT_EMPHASIS_MARK_SESAME, |
| TtmlNode.TEXT_EMPHASIS_MARK_CIRCLE); |
| |
| private static final ImmutableSet<String> MARK_FILL_VALUES = |
| ImmutableSet.of(TtmlNode.TEXT_EMPHASIS_MARK_FILLED, TtmlNode.TEXT_EMPHASIS_MARK_OPEN); |
| |
| private static final ImmutableSet<String> POSITION_VALUES = |
| ImmutableSet.of( |
| TtmlNode.ANNOTATION_POSITION_AFTER, |
| TtmlNode.ANNOTATION_POSITION_BEFORE, |
| TtmlNode.ANNOTATION_POSITION_OUTSIDE); |
| |
| /** The text emphasis mark shape. */ |
| @MarkShape public final int markShape; |
| |
| /** The fill style of the text emphasis mark. */ |
| @TextEmphasisSpan.MarkFill public final int markFill; |
| |
| /** The position of the text emphasis relative to the base text. */ |
| @Position public final int position; |
| |
| private TextEmphasis( |
| @MarkShape int markShape, |
| @TextEmphasisSpan.MarkFill int markFill, |
| @TextAnnotation.Position int position) { |
| this.markShape = markShape; |
| this.markFill = markFill; |
| this.position = position; |
| } |
| |
| /** |
| * Parses a TTML <a |
| * href="https://www.w3.org/TR/2018/REC-ttml2-20181108/#style-attribute-textEmphasis"> |
| * tts:textEmphasis</a> attribute. Returns null if parsing fails. |
| * |
| * <p>The parser searches for {@code emphasis-style} and {@code emphasis-position} independently. |
| * If a valid style is not found, the default style is used. If a valid position is not found, the |
| * default position is used. |
| * |
| * <p>Not implemented: |
| * |
| * <ul> |
| * <li>{@code emphasis-color} |
| * <li>Quoted string {@code emphasis-style} |
| * </ul> |
| */ |
| @Nullable |
| public static TextEmphasis parse(@Nullable String value) { |
| if (value == null) { |
| return null; |
| } |
| |
| String parsingValue = Ascii.toLowerCase(value.trim()); |
| if (parsingValue.isEmpty()) { |
| return null; |
| } |
| |
| return parseWords(ImmutableSet.copyOf(TextUtils.split(parsingValue, WHITESPACE_PATTERN))); |
| } |
| |
| private static TextEmphasis parseWords(ImmutableSet<String> nodes) { |
| Set<String> matchingPositions = Sets.intersection(POSITION_VALUES, nodes); |
| // If no emphasis position is specified, then the emphasis position must be interpreted as if |
| // a position of outside were specified: |
| // https://www.w3.org/TR/2018/REC-ttml2-20181108/#style-attribute-textEmphasis |
| @Position int position; |
| switch (Iterables.getFirst(matchingPositions, TtmlNode.ANNOTATION_POSITION_OUTSIDE)) { |
| case TtmlNode.ANNOTATION_POSITION_AFTER: |
| position = TextAnnotation.POSITION_AFTER; |
| break; |
| case TtmlNode.ANNOTATION_POSITION_OUTSIDE: |
| position = POSITION_OUTSIDE; |
| break; |
| case TtmlNode.ANNOTATION_POSITION_BEFORE: |
| default: |
| // If an implementation does not recognize or otherwise distinguish an annotation position |
| // value, then it must be interpreted as if a position of 'before' were specified: |
| // https://www.w3.org/TR/2018/REC-ttml2-20181108/#style-attribute-textEmphasis |
| position = TextAnnotation.POSITION_BEFORE; |
| } |
| |
| Set<String> matchingSingleStyles = Sets.intersection(SINGLE_STYLE_VALUES, nodes); |
| if (!matchingSingleStyles.isEmpty()) { |
| // If "none" or "auto" are found in the description, ignore the other style (fill, shape) |
| // attributes. |
| @MarkShape int markShape; |
| switch (matchingSingleStyles.iterator().next()) { |
| case TtmlNode.TEXT_EMPHASIS_NONE: |
| markShape = TextEmphasisSpan.MARK_SHAPE_NONE; |
| break; |
| case TtmlNode.TEXT_EMPHASIS_AUTO: |
| default: |
| markShape = MARK_SHAPE_AUTO; |
| } |
| // markFill is ignored when markShape is NONE or AUTO |
| return new TextEmphasis(markShape, TextEmphasisSpan.MARK_FILL_UNKNOWN, position); |
| } |
| |
| Set<String> matchingFills = Sets.intersection(MARK_FILL_VALUES, nodes); |
| Set<String> matchingShapes = Sets.intersection(MARK_SHAPE_VALUES, nodes); |
| if (matchingFills.isEmpty() && matchingShapes.isEmpty()) { |
| // If an implementation does not recognize or otherwise distinguish an emphasis style value, |
| // then it must be interpreted as if a style of auto were specified; as such, an |
| // implementation that supports text emphasis marks must minimally support the auto value. |
| // https://www.w3.org/TR/ttml2/#style-value-emphasis-style. |
| // |
| // markFill is ignored when markShape is NONE or AUTO. |
| return new TextEmphasis(MARK_SHAPE_AUTO, TextEmphasisSpan.MARK_FILL_UNKNOWN, position); |
| } |
| |
| @TextEmphasisSpan.MarkFill int markFill; |
| switch (Iterables.getFirst(matchingFills, TtmlNode.TEXT_EMPHASIS_MARK_FILLED)) { |
| case TtmlNode.TEXT_EMPHASIS_MARK_OPEN: |
| markFill = TextEmphasisSpan.MARK_FILL_OPEN; |
| break; |
| case TtmlNode.TEXT_EMPHASIS_MARK_FILLED: |
| default: |
| markFill = TextEmphasisSpan.MARK_FILL_FILLED; |
| } |
| |
| @MarkShape int markShape; |
| switch (Iterables.getFirst(matchingShapes, TtmlNode.TEXT_EMPHASIS_MARK_CIRCLE)) { |
| case TtmlNode.TEXT_EMPHASIS_MARK_DOT: |
| markShape = TextEmphasisSpan.MARK_SHAPE_DOT; |
| break; |
| case TtmlNode.TEXT_EMPHASIS_MARK_SESAME: |
| markShape = TextEmphasisSpan.MARK_SHAPE_SESAME; |
| break; |
| case TtmlNode.TEXT_EMPHASIS_MARK_CIRCLE: |
| default: |
| markShape = TextEmphasisSpan.MARK_SHAPE_CIRCLE; |
| } |
| |
| return new TextEmphasis(markShape, markFill, position); |
| } |
| } |