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.android.tv.tuner.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 import com.google.android.exoplayer.text.CaptionStyleCompat;
36 import com.google.android.exoplayer.util.Util;
37 import java.util.ArrayList;
38 import java.util.Objects;
39 
40 /**
41  * Since this class does not exist in recent version of ExoPlayer and used by {@link
42  * com.android.tv.tuner.cc.CaptionWindowLayout}, this class is copied from older version of
43  * ExoPlayer. A view for rendering a single caption.
44  */
45 @Deprecated
46 public class SubtitleView extends View {
47     /** Ratio of inner padding to font size. */
48     private static final float INNER_PADDING_RATIO = 0.125f;
49 
50     /** Temporary rectangle used for computing line bounds. */
51     private final RectF mLineBounds = new RectF();
52 
53     // Styled dimensions.
54     private final float mCornerRadius;
55     private final float mOutlineWidth;
56     private final float mShadowRadius;
57     private final float mShadowOffset;
58 
59     private final TextPaint mTextPaint;
60     private final Paint mPaint;
61 
62     private CharSequence mText;
63 
64     private int mForegroundColor;
65     private int mBackgroundColor;
66     private int mEdgeColor;
67     private int mEdgeType;
68 
69     private boolean mHasMeasurements;
70     private int mLastMeasuredWidth;
71     private StaticLayout mLayout;
72 
73     private Alignment mAlignment;
74     private final float mSpacingMult;
75     private final float mSpacingAdd;
76     private int mInnerPaddingX;
77     private float mWhiteSpaceWidth;
78     private ArrayList<Integer> mPrefixSpaces = new ArrayList<>();
79 
SubtitleView(Context context)80     public SubtitleView(Context context) {
81         this(context, null);
82     }
83 
SubtitleView(Context context, AttributeSet attrs)84     public SubtitleView(Context context, AttributeSet attrs) {
85         this(context, attrs, 0);
86     }
87 
SubtitleView(Context context, AttributeSet attrs, int defStyleAttr)88     public SubtitleView(Context context, AttributeSet attrs, int defStyleAttr) {
89         super(context, attrs, defStyleAttr);
90 
91         int[] viewAttr = {
92             android.R.attr.text,
93             android.R.attr.textSize,
94             android.R.attr.lineSpacingExtra,
95             android.R.attr.lineSpacingMultiplier
96         };
97         TypedArray a = context.obtainStyledAttributes(attrs, viewAttr, defStyleAttr, 0);
98         CharSequence text = a.getText(0);
99         int textSize = a.getDimensionPixelSize(1, 15);
100         mSpacingAdd = a.getDimensionPixelSize(2, 0);
101         mSpacingMult = a.getFloat(3, 1);
102         a.recycle();
103 
104         Resources resources = getContext().getResources();
105         DisplayMetrics displayMetrics = resources.getDisplayMetrics();
106         int twoDpInPx =
107                 Math.round((2f * displayMetrics.densityDpi) / DisplayMetrics.DENSITY_DEFAULT);
108         mCornerRadius = twoDpInPx;
109         mOutlineWidth = twoDpInPx;
110         mShadowRadius = twoDpInPx;
111         mShadowOffset = twoDpInPx;
112 
113         mTextPaint = new TextPaint();
114         mTextPaint.setAntiAlias(true);
115         mTextPaint.setSubpixelText(true);
116 
117         mAlignment = Alignment.ALIGN_CENTER;
118 
119         mPaint = new Paint();
120         mPaint.setAntiAlias(true);
121 
122         mInnerPaddingX = 0;
123         setText(text);
124         setTextSize(textSize);
125         setStyle(CaptionStyleCompat.DEFAULT);
126     }
127 
128     @Override
setBackgroundColor(int color)129     public void setBackgroundColor(int color) {
130         mBackgroundColor = color;
131         forceUpdate(false);
132     }
133 
134     /**
135      * Sets the text to be displayed by the view.
136      *
137      * @param text The text to display.
138      */
setText(CharSequence text)139     public void setText(CharSequence text) {
140         this.mText = text;
141         forceUpdate(true);
142     }
143 
144     /**
145      * Sets the text size in pixels.
146      *
147      * @param size The text size in pixels.
148      */
setTextSize(float size)149     public void setTextSize(float size) {
150         if (mTextPaint.getTextSize() != size) {
151             mTextPaint.setTextSize(size);
152             mInnerPaddingX = (int) (size * INNER_PADDING_RATIO + 0.5f);
153             mWhiteSpaceWidth -= mInnerPaddingX * 2;
154             forceUpdate(true);
155         }
156     }
157 
158     /**
159      * Sets the text alignment.
160      *
161      * @param textAlignment The text alignment.
162      */
setTextAlignment(Alignment textAlignment)163     public void setTextAlignment(Alignment textAlignment) {
164         mAlignment = textAlignment;
165     }
166 
167     /**
168      * Configures the view according to the given style.
169      *
170      * @param style A style for the view.
171      */
setStyle(CaptionStyleCompat style)172     public void setStyle(CaptionStyleCompat style) {
173         mForegroundColor = style.foregroundColor;
174         mBackgroundColor = style.backgroundColor;
175         mEdgeType = style.edgeType;
176         mEdgeColor = style.edgeColor;
177         setTypeface(style.typeface);
178         super.setBackgroundColor(style.windowColor);
179         forceUpdate(true);
180     }
181 
setPrefixSpaces(ArrayList<Integer> prefixSpaces)182     public void setPrefixSpaces(ArrayList<Integer> prefixSpaces) {
183         mPrefixSpaces = prefixSpaces;
184     }
185 
setWhiteSpaceWidth(float whiteSpaceWidth)186     public void setWhiteSpaceWidth(float whiteSpaceWidth) {
187         mWhiteSpaceWidth = whiteSpaceWidth;
188     }
189 
setTypeface(Typeface typeface)190     private void setTypeface(Typeface typeface) {
191         if (Objects.equals(mTextPaint.getTypeface(), (typeface))) {
192             mTextPaint.setTypeface(typeface);
193             forceUpdate(true);
194         }
195     }
196 
forceUpdate(boolean needsLayout)197     private void forceUpdate(boolean needsLayout) {
198         if (needsLayout) {
199             mHasMeasurements = false;
200             requestLayout();
201         }
202         invalidate();
203     }
204 
205     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)206     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
207         final int widthSpec = MeasureSpec.getSize(widthMeasureSpec);
208 
209         if (computeMeasurements(widthSpec)) {
210             final StaticLayout layout = this.mLayout;
211             final int paddingX = getPaddingLeft() + getPaddingRight() + mInnerPaddingX * 2;
212             final int height = layout.getHeight() + getPaddingTop() + getPaddingBottom();
213             int width = 0;
214             int lineCount = layout.getLineCount();
215             for (int i = 0; i < lineCount; i++) {
216                 width = Math.max((int) Math.ceil(layout.getLineWidth(i)), width);
217             }
218             width += paddingX;
219             setMeasuredDimension(width, height);
220         } else if (Util.SDK_INT >= 11) {
221             setTooSmallMeasureDimensionV11();
222         } else {
223             setMeasuredDimension(0, 0);
224         }
225     }
226 
227     @TargetApi(11)
setTooSmallMeasureDimensionV11()228     private void setTooSmallMeasureDimensionV11() {
229         setMeasuredDimension(MEASURED_STATE_TOO_SMALL, MEASURED_STATE_TOO_SMALL);
230     }
231 
232     @Override
onLayout(boolean changed, int l, int t, int r, int b)233     public void onLayout(boolean changed, int l, int t, int r, int b) {
234         final int width = r - l;
235         computeMeasurements(width);
236     }
237 
computeMeasurements(int maxWidth)238     private boolean computeMeasurements(int maxWidth) {
239         if (mHasMeasurements && maxWidth == mLastMeasuredWidth) {
240             return true;
241         }
242 
243         // Account for padding.
244         final int paddingX = getPaddingLeft() + getPaddingRight() + mInnerPaddingX * 2;
245         maxWidth -= paddingX;
246         if (maxWidth <= 0) {
247             return false;
248         }
249 
250         mHasMeasurements = true;
251         mLastMeasuredWidth = maxWidth;
252         mLayout =
253                 new StaticLayout(
254                         mText, mTextPaint, maxWidth, mAlignment, mSpacingMult, mSpacingAdd, true);
255         return true;
256     }
257 
258     @Override
onDraw(Canvas c)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