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.google.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 // TODO: Change usage of this class to up-to-date class of ExoPlayer. 49 50 /** 51 * Ratio of inner padding to font size. 52 */ 53 private static final float INNER_PADDING_RATIO = 0.125f; 54 55 /** 56 * Temporary rectangle used for computing line bounds. 57 */ 58 private final RectF mLineBounds = new RectF(); 59 60 // Styled dimensions. 61 private final float mCornerRadius; 62 private final float mOutlineWidth; 63 private final float mShadowRadius; 64 private final float mShadowOffset; 65 66 private final TextPaint mTextPaint; 67 private final Paint mPaint; 68 69 private CharSequence mText; 70 71 private int mForegroundColor; 72 private int mBackgroundColor; 73 private int mEdgeColor; 74 private int mEdgeType; 75 76 private boolean mHasMeasurements; 77 private int mLastMeasuredWidth; 78 private StaticLayout mLayout; 79 80 private Alignment mAlignment; 81 private final float mSpacingMult; 82 private final float mSpacingAdd; 83 private int mInnerPaddingX; 84 private float mWhiteSpaceWidth; 85 private ArrayList<Integer> mPrefixSpaces = new ArrayList<>(); 86 SubtitleView(Context context)87 public SubtitleView(Context context) { 88 this(context, null); 89 } 90 SubtitleView(Context context, AttributeSet attrs)91 public SubtitleView(Context context, AttributeSet attrs) { 92 this(context, attrs, 0); 93 } 94 SubtitleView(Context context, AttributeSet attrs, int defStyleAttr)95 public SubtitleView(Context context, AttributeSet attrs, int defStyleAttr) { 96 super(context, attrs, defStyleAttr); 97 98 int[] viewAttr = {android.R.attr.text, android.R.attr.textSize, 99 android.R.attr.lineSpacingExtra, android.R.attr.lineSpacingMultiplier}; 100 TypedArray a = context.obtainStyledAttributes(attrs, viewAttr, defStyleAttr, 0); 101 CharSequence text = a.getText(0); 102 int textSize = a.getDimensionPixelSize(1, 15); 103 mSpacingAdd = a.getDimensionPixelSize(2, 0); 104 mSpacingMult = a.getFloat(3, 1); 105 a.recycle(); 106 107 Resources resources = getContext().getResources(); 108 DisplayMetrics displayMetrics = resources.getDisplayMetrics(); 109 int twoDpInPx = 110 Math.round((2f * displayMetrics.densityDpi) / DisplayMetrics.DENSITY_DEFAULT); 111 mCornerRadius = twoDpInPx; 112 mOutlineWidth = twoDpInPx; 113 mShadowRadius = twoDpInPx; 114 mShadowOffset = twoDpInPx; 115 116 mTextPaint = new TextPaint(); 117 mTextPaint.setAntiAlias(true); 118 mTextPaint.setSubpixelText(true); 119 120 mAlignment = Alignment.ALIGN_CENTER; 121 122 mPaint = new Paint(); 123 mPaint.setAntiAlias(true); 124 125 mInnerPaddingX = 0; 126 setText(text); 127 setTextSize(textSize); 128 setStyle(CaptionStyleCompat.DEFAULT); 129 } 130 131 @Override setBackgroundColor(int color)132 public void setBackgroundColor(int color) { 133 mBackgroundColor = color; 134 forceUpdate(false); 135 } 136 137 /** 138 * Sets the text to be displayed by the view. 139 * 140 * @param text The text to display. 141 */ setText(CharSequence text)142 public void setText(CharSequence text) { 143 this.mText = text; 144 forceUpdate(true); 145 } 146 147 /** 148 * Sets the text size in pixels. 149 * 150 * @param size The text size in pixels. 151 */ setTextSize(float size)152 public void setTextSize(float size) { 153 if (mTextPaint.getTextSize() != size) { 154 mTextPaint.setTextSize(size); 155 mInnerPaddingX = (int) (size * INNER_PADDING_RATIO + 0.5f); 156 mWhiteSpaceWidth -= mInnerPaddingX * 2; 157 forceUpdate(true); 158 } 159 } 160 161 /** 162 * Sets the text alignment. 163 * 164 * @param textAlignment The text alignment. 165 */ setTextAlignment(Alignment textAlignment)166 public void setTextAlignment(Alignment textAlignment) { 167 mAlignment = textAlignment; 168 } 169 170 /** 171 * Configures the view according to the given style. 172 * 173 * @param style A style for the view. 174 */ setStyle(CaptionStyleCompat style)175 public void setStyle(CaptionStyleCompat style) { 176 mForegroundColor = style.foregroundColor; 177 mBackgroundColor = style.backgroundColor; 178 mEdgeType = style.edgeType; 179 mEdgeColor = style.edgeColor; 180 setTypeface(style.typeface); 181 super.setBackgroundColor(style.windowColor); 182 forceUpdate(true); 183 } 184 setPrefixSpaces(ArrayList<Integer> prefixSpaces)185 public void setPrefixSpaces(ArrayList<Integer> prefixSpaces) { 186 mPrefixSpaces = prefixSpaces; 187 } 188 setWhiteSpaceWidth(float whiteSpaceWidth)189 public void setWhiteSpaceWidth(float whiteSpaceWidth) { 190 mWhiteSpaceWidth = whiteSpaceWidth; 191 } 192 setTypeface(Typeface typeface)193 private void setTypeface(Typeface typeface) { 194 if (mTextPaint.getTypeface() != typeface) { 195 mTextPaint.setTypeface(typeface); 196 forceUpdate(true); 197 } 198 } 199 forceUpdate(boolean needsLayout)200 private void forceUpdate(boolean needsLayout) { 201 if (needsLayout) { 202 mHasMeasurements = false; 203 requestLayout(); 204 } 205 invalidate(); 206 } 207 208 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)209 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 210 final int widthSpec = MeasureSpec.getSize(widthMeasureSpec); 211 212 if (computeMeasurements(widthSpec)) { 213 final StaticLayout layout = this.mLayout; 214 final int paddingX = getPaddingLeft() + getPaddingRight() + mInnerPaddingX * 2; 215 final int height = layout.getHeight() + getPaddingTop() + getPaddingBottom(); 216 int width = 0; 217 int lineCount = layout.getLineCount(); 218 for (int i = 0; i < lineCount; i++) { 219 width = Math.max((int) Math.ceil(layout.getLineWidth(i)), width); 220 } 221 width += paddingX; 222 setMeasuredDimension(width, height); 223 } else if (Util.SDK_INT >= 11) { 224 setTooSmallMeasureDimensionV11(); 225 } else { 226 setMeasuredDimension(0, 0); 227 } 228 } 229 230 @TargetApi(11) setTooSmallMeasureDimensionV11()231 private void setTooSmallMeasureDimensionV11() { 232 setMeasuredDimension(MEASURED_STATE_TOO_SMALL, MEASURED_STATE_TOO_SMALL); 233 } 234 235 @Override onLayout(boolean changed, int l, int t, int r, int b)236 public void onLayout(boolean changed, int l, int t, int r, int b) { 237 final int width = r - l; 238 computeMeasurements(width); 239 } 240 computeMeasurements(int maxWidth)241 private boolean computeMeasurements(int maxWidth) { 242 if (mHasMeasurements && maxWidth == mLastMeasuredWidth) { 243 return true; 244 } 245 246 // Account for padding. 247 final int paddingX = getPaddingLeft() + getPaddingRight() + mInnerPaddingX * 2; 248 maxWidth -= paddingX; 249 if (maxWidth <= 0) { 250 return false; 251 } 252 253 mHasMeasurements = true; 254 mLastMeasuredWidth = maxWidth; 255 mLayout = new StaticLayout(mText, mTextPaint, maxWidth, mAlignment, 256 mSpacingMult, mSpacingAdd, true); 257 return true; 258 } 259 260 @Override onDraw(Canvas c)261 protected void onDraw(Canvas c) { 262 final StaticLayout layout = this.mLayout; 263 if (layout == null) { 264 return; 265 } 266 267 final int saveCount = c.save(); 268 final int innerPaddingX = this.mInnerPaddingX; 269 c.translate(getPaddingLeft() + innerPaddingX, getPaddingTop()); 270 271 final int lineCount = layout.getLineCount(); 272 final Paint textPaint = this.mTextPaint; 273 final Paint paint = this.mPaint; 274 final RectF bounds = mLineBounds; 275 276 if (Color.alpha(mBackgroundColor) > 0) { 277 final float cornerRadius = this.mCornerRadius; 278 float previousBottom = layout.getLineTop(0); 279 280 paint.setColor(mBackgroundColor); 281 paint.setStyle(Style.FILL); 282 283 for (int i = 0; i < lineCount; i++) { 284 float spacesPadding = 0.0f; 285 if (i < mPrefixSpaces.size()) { 286 spacesPadding += mPrefixSpaces.get(i) * mWhiteSpaceWidth; 287 } 288 bounds.left = layout.getLineLeft(i) - innerPaddingX + spacesPadding; 289 bounds.right = layout.getLineRight(i) + innerPaddingX; 290 bounds.top = previousBottom; 291 bounds.bottom = layout.getLineBottom(i); 292 previousBottom = bounds.bottom; 293 294 c.drawRoundRect(bounds, cornerRadius, cornerRadius, paint); 295 } 296 } 297 298 if (mEdgeType == CaptionStyleCompat.EDGE_TYPE_OUTLINE) { 299 textPaint.setStrokeJoin(Join.ROUND); 300 textPaint.setStrokeWidth(mOutlineWidth); 301 textPaint.setColor(mEdgeColor); 302 textPaint.setStyle(Style.FILL_AND_STROKE); 303 layout.draw(c); 304 } else if (mEdgeType == CaptionStyleCompat.EDGE_TYPE_DROP_SHADOW) { 305 textPaint.setShadowLayer(mShadowRadius, mShadowOffset, mShadowOffset, mEdgeColor); 306 } else if (mEdgeType == CaptionStyleCompat.EDGE_TYPE_RAISED 307 || mEdgeType == CaptionStyleCompat.EDGE_TYPE_DEPRESSED) { 308 boolean raised = mEdgeType == CaptionStyleCompat.EDGE_TYPE_RAISED; 309 int colorUp = raised ? Color.WHITE : mEdgeColor; 310 int colorDown = raised ? mEdgeColor : Color.WHITE; 311 float offset = mShadowRadius / 2f; 312 textPaint.setColor(mForegroundColor); 313 textPaint.setStyle(Style.FILL); 314 textPaint.setShadowLayer(mShadowRadius, -offset, -offset, colorUp); 315 layout.draw(c); 316 textPaint.setShadowLayer(mShadowRadius, offset, offset, colorDown); 317 } 318 319 textPaint.setColor(mForegroundColor); 320 textPaint.setStyle(Style.FILL); 321 layout.draw(c); 322 textPaint.setShadowLayer(0, 0, 0, 0); 323 c.restoreToCount(saveCount); 324 } 325 326 } 327