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