Home | History | Annotate | Download | only in text
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.google.android.exoplayer.text;
     17 
     18 import android.annotation.TargetApi;
     19 import android.content.Context;
     20 import android.content.res.Resources;
     21 import android.content.res.TypedArray;
     22 import android.graphics.Canvas;
     23 import android.graphics.Color;
     24 import android.graphics.Paint;
     25 import android.graphics.Paint.Join;
     26 import android.graphics.Paint.Style;
     27 import android.graphics.RectF;
     28 import android.graphics.Typeface;
     29 import android.text.Layout.Alignment;
     30 import android.text.StaticLayout;
     31 import android.text.TextPaint;
     32 import android.util.AttributeSet;
     33 import android.util.DisplayMetrics;
     34 import android.view.View;
     35 
     36 import com.google.android.exoplayer.util.Util;
     37 
     38 import java.util.ArrayList;
     39 
     40 /**
     41  * Since this class does not exist in recent version of ExoPlayer and used by
     42  * {@link com.android.tv.tuner.cc.CaptionWindowLayout}, this class is copied from
     43  * older version of ExoPlayer.
     44  * A view for rendering a single caption.
     45  */
     46 @Deprecated
     47 public class SubtitleView extends View {
     48     /**
     49      * Ratio of inner padding to font size.
     50      */
     51     private static final float INNER_PADDING_RATIO = 0.125f;
     52 
     53     /**
     54      * Temporary rectangle used for computing line bounds.
     55      */
     56     private final RectF mLineBounds = new RectF();
     57 
     58     // Styled dimensions.
     59     private final float mCornerRadius;
     60     private final float mOutlineWidth;
     61     private final float mShadowRadius;
     62     private final float mShadowOffset;
     63 
     64     private final TextPaint mTextPaint;
     65     private final Paint mPaint;
     66 
     67     private CharSequence mText;
     68 
     69     private int mForegroundColor;
     70     private int mBackgroundColor;
     71     private int mEdgeColor;
     72     private int mEdgeType;
     73 
     74     private boolean mHasMeasurements;
     75     private int mLastMeasuredWidth;
     76     private StaticLayout mLayout;
     77 
     78     private Alignment mAlignment;
     79     private final float mSpacingMult;
     80     private final float mSpacingAdd;
     81     private int mInnerPaddingX;
     82     private float mWhiteSpaceWidth;
     83     private ArrayList<Integer> mPrefixSpaces = new ArrayList<>();
     84 
     85     public SubtitleView(Context context) {
     86         this(context, null);
     87     }
     88 
     89     public SubtitleView(Context context, AttributeSet attrs) {
     90         this(context, attrs, 0);
     91     }
     92 
     93     public SubtitleView(Context context, AttributeSet attrs, int defStyleAttr) {
     94         super(context, attrs, defStyleAttr);
     95 
     96         int[] viewAttr = {android.R.attr.text, android.R.attr.textSize,
     97                 android.R.attr.lineSpacingExtra, android.R.attr.lineSpacingMultiplier};
     98         TypedArray a = context.obtainStyledAttributes(attrs, viewAttr, defStyleAttr, 0);
     99         CharSequence text = a.getText(0);
    100         int textSize = a.getDimensionPixelSize(1, 15);
    101         mSpacingAdd = a.getDimensionPixelSize(2, 0);
    102         mSpacingMult = a.getFloat(3, 1);
    103         a.recycle();
    104 
    105         Resources resources = getContext().getResources();
    106         DisplayMetrics displayMetrics = resources.getDisplayMetrics();
    107         int twoDpInPx =
    108                 Math.round((2f * displayMetrics.densityDpi) / DisplayMetrics.DENSITY_DEFAULT);
    109         mCornerRadius = twoDpInPx;
    110         mOutlineWidth = twoDpInPx;
    111         mShadowRadius = twoDpInPx;
    112         mShadowOffset = twoDpInPx;
    113 
    114         mTextPaint = new TextPaint();
    115         mTextPaint.setAntiAlias(true);
    116         mTextPaint.setSubpixelText(true);
    117 
    118         mAlignment = Alignment.ALIGN_CENTER;
    119 
    120         mPaint = new Paint();
    121         mPaint.setAntiAlias(true);
    122 
    123         mInnerPaddingX = 0;
    124         setText(text);
    125         setTextSize(textSize);
    126         setStyle(CaptionStyleCompat.DEFAULT);
    127     }
    128 
    129     @Override
    130     public void setBackgroundColor(int color) {
    131         mBackgroundColor = color;
    132         forceUpdate(false);
    133     }
    134 
    135     /**
    136      * Sets the text to be displayed by the view.
    137      *
    138      * @param text The text to display.
    139      */
    140     public void setText(CharSequence text) {
    141         this.mText = text;
    142         forceUpdate(true);
    143     }
    144 
    145     /**
    146      * Sets the text size in pixels.
    147      *
    148      * @param size The text size in pixels.
    149      */
    150     public void setTextSize(float size) {
    151         if (mTextPaint.getTextSize() != size) {
    152             mTextPaint.setTextSize(size);
    153             mInnerPaddingX = (int) (size * INNER_PADDING_RATIO + 0.5f);
    154             mWhiteSpaceWidth -= mInnerPaddingX * 2;
    155             forceUpdate(true);
    156         }
    157     }
    158 
    159     /**
    160      * Sets the text alignment.
    161      *
    162      * @param textAlignment The text alignment.
    163      */
    164     public void setTextAlignment(Alignment textAlignment) {
    165         mAlignment = textAlignment;
    166     }
    167 
    168     /**
    169      * Configures the view according to the given style.
    170      *
    171      * @param style A style for the view.
    172      */
    173     public void setStyle(CaptionStyleCompat style) {
    174         mForegroundColor = style.foregroundColor;
    175         mBackgroundColor = style.backgroundColor;
    176         mEdgeType = style.edgeType;
    177         mEdgeColor = style.edgeColor;
    178         setTypeface(style.typeface);
    179         super.setBackgroundColor(style.windowColor);
    180         forceUpdate(true);
    181     }
    182 
    183     public void setPrefixSpaces(ArrayList<Integer> prefixSpaces) {
    184         mPrefixSpaces = prefixSpaces;
    185     }
    186 
    187     public void setWhiteSpaceWidth(float whiteSpaceWidth) {
    188         mWhiteSpaceWidth = whiteSpaceWidth;
    189     }
    190 
    191     private void setTypeface(Typeface typeface) {
    192         if (mTextPaint.getTypeface() != typeface) {
    193             mTextPaint.setTypeface(typeface);
    194             forceUpdate(true);
    195         }
    196     }
    197 
    198     private void forceUpdate(boolean needsLayout) {
    199         if (needsLayout) {
    200             mHasMeasurements = false;
    201             requestLayout();
    202         }
    203         invalidate();
    204     }
    205 
    206     @Override
    207     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    208         final int widthSpec = MeasureSpec.getSize(widthMeasureSpec);
    209 
    210         if (computeMeasurements(widthSpec)) {
    211             final StaticLayout layout = this.mLayout;
    212             final int paddingX = getPaddingLeft() + getPaddingRight() + mInnerPaddingX * 2;
    213             final int height = layout.getHeight() + getPaddingTop() + getPaddingBottom();
    214             int width = 0;
    215             int lineCount = layout.getLineCount();
    216             for (int i = 0; i < lineCount; i++) {
    217                 width = Math.max((int) Math.ceil(layout.getLineWidth(i)), width);
    218             }
    219             width += paddingX;
    220             setMeasuredDimension(width, height);
    221         } else if (Util.SDK_INT >= 11) {
    222             setTooSmallMeasureDimensionV11();
    223         } else {
    224             setMeasuredDimension(0, 0);
    225         }
    226     }
    227 
    228     @TargetApi(11)
    229     private void setTooSmallMeasureDimensionV11() {
    230         setMeasuredDimension(MEASURED_STATE_TOO_SMALL, MEASURED_STATE_TOO_SMALL);
    231     }
    232 
    233     @Override
    234     public void onLayout(boolean changed, int l, int t, int r, int b) {
    235         final int width = r - l;
    236         computeMeasurements(width);
    237     }
    238 
    239     private boolean computeMeasurements(int maxWidth) {
    240         if (mHasMeasurements && maxWidth == mLastMeasuredWidth) {
    241             return true;
    242         }
    243 
    244         // Account for padding.
    245         final int paddingX = getPaddingLeft() + getPaddingRight() + mInnerPaddingX * 2;
    246         maxWidth -= paddingX;
    247         if (maxWidth <= 0) {
    248             return false;
    249         }
    250 
    251         mHasMeasurements = true;
    252         mLastMeasuredWidth = maxWidth;
    253         mLayout = new StaticLayout(mText, mTextPaint, maxWidth, mAlignment,
    254                 mSpacingMult, mSpacingAdd, true);
    255         return true;
    256     }
    257 
    258     @Override
    259     protected void onDraw(Canvas c) {
    260         final StaticLayout layout = this.mLayout;
    261         if (layout == null) {
    262             return;
    263         }
    264 
    265         final int saveCount = c.save();
    266         final int innerPaddingX = this.mInnerPaddingX;
    267         c.translate(getPaddingLeft() + innerPaddingX, getPaddingTop());
    268 
    269         final int lineCount = layout.getLineCount();
    270         final Paint textPaint = this.mTextPaint;
    271         final Paint paint = this.mPaint;
    272         final RectF bounds = mLineBounds;
    273 
    274         if (Color.alpha(mBackgroundColor) > 0) {
    275             final float cornerRadius = this.mCornerRadius;
    276             float previousBottom = layout.getLineTop(0);
    277 
    278             paint.setColor(mBackgroundColor);
    279             paint.setStyle(Style.FILL);
    280 
    281             for (int i = 0; i < lineCount; i++) {
    282                 float spacesPadding = 0.0f;
    283                 if (i < mPrefixSpaces.size()) {
    284                     spacesPadding += mPrefixSpaces.get(i) * mWhiteSpaceWidth;
    285                 }
    286                 bounds.left = layout.getLineLeft(i) - innerPaddingX + spacesPadding;
    287                 bounds.right = layout.getLineRight(i) + innerPaddingX;
    288                 bounds.top = previousBottom;
    289                 bounds.bottom = layout.getLineBottom(i);
    290                 previousBottom = bounds.bottom;
    291 
    292                 c.drawRoundRect(bounds, cornerRadius, cornerRadius, paint);
    293             }
    294         }
    295 
    296         if (mEdgeType == CaptionStyleCompat.EDGE_TYPE_OUTLINE) {
    297             textPaint.setStrokeJoin(Join.ROUND);
    298             textPaint.setStrokeWidth(mOutlineWidth);
    299             textPaint.setColor(mEdgeColor);
    300             textPaint.setStyle(Style.FILL_AND_STROKE);
    301             layout.draw(c);
    302         } else if (mEdgeType == CaptionStyleCompat.EDGE_TYPE_DROP_SHADOW) {
    303             textPaint.setShadowLayer(mShadowRadius, mShadowOffset, mShadowOffset, mEdgeColor);
    304         } else if (mEdgeType == CaptionStyleCompat.EDGE_TYPE_RAISED
    305                 || mEdgeType == CaptionStyleCompat.EDGE_TYPE_DEPRESSED) {
    306             boolean raised = mEdgeType == CaptionStyleCompat.EDGE_TYPE_RAISED;
    307             int colorUp = raised ? Color.WHITE : mEdgeColor;
    308             int colorDown = raised ? mEdgeColor : Color.WHITE;
    309             float offset = mShadowRadius / 2f;
    310             textPaint.setColor(mForegroundColor);
    311             textPaint.setStyle(Style.FILL);
    312             textPaint.setShadowLayer(mShadowRadius, -offset, -offset, colorUp);
    313             layout.draw(c);
    314             textPaint.setShadowLayer(mShadowRadius, offset, offset, colorDown);
    315         }
    316 
    317         textPaint.setColor(mForegroundColor);
    318         textPaint.setStyle(Style.FILL);
    319         layout.draw(c);
    320         textPaint.setShadowLayer(0, 0, 0, 0);
    321         c.restoreToCount(saveCount);
    322     }
    323 
    324 }
    325