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 SubtitleView(Context context)85 public SubtitleView(Context context) { 86 this(context, null); 87 } 88 SubtitleView(Context context, AttributeSet attrs)89 public SubtitleView(Context context, AttributeSet attrs) { 90 this(context, attrs, 0); 91 } 92 SubtitleView(Context context, AttributeSet attrs, int defStyleAttr)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 setBackgroundColor(int color)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 */ setText(CharSequence text)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 */ setTextSize(float size)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 */ setTextAlignment(Alignment textAlignment)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 */ setStyle(CaptionStyleCompat style)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 setPrefixSpaces(ArrayList<Integer> prefixSpaces)183 public void setPrefixSpaces(ArrayList<Integer> prefixSpaces) { 184 mPrefixSpaces = prefixSpaces; 185 } 186 setWhiteSpaceWidth(float whiteSpaceWidth)187 public void setWhiteSpaceWidth(float whiteSpaceWidth) { 188 mWhiteSpaceWidth = whiteSpaceWidth; 189 } 190 setTypeface(Typeface typeface)191 private void setTypeface(Typeface typeface) { 192 if (mTextPaint.getTypeface() != typeface) { 193 mTextPaint.setTypeface(typeface); 194 forceUpdate(true); 195 } 196 } 197 forceUpdate(boolean needsLayout)198 private void forceUpdate(boolean needsLayout) { 199 if (needsLayout) { 200 mHasMeasurements = false; 201 requestLayout(); 202 } 203 invalidate(); 204 } 205 206 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)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) setTooSmallMeasureDimensionV11()229 private void setTooSmallMeasureDimensionV11() { 230 setMeasuredDimension(MEASURED_STATE_TOO_SMALL, MEASURED_STATE_TOO_SMALL); 231 } 232 233 @Override onLayout(boolean changed, int l, int t, int r, int b)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 computeMeasurements(int maxWidth)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 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 } 325