/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.exoplayer.text; import android.annotation.TargetApi; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Join; import android.graphics.Paint.Style; import android.graphics.RectF; import android.graphics.Typeface; import android.text.Layout.Alignment; import android.text.StaticLayout; import android.text.TextPaint; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.view.View; import com.google.android.exoplayer.util.Util; import java.util.ArrayList; /** * Since this class does not exist in recent version of ExoPlayer and used by * {@link com.google.android.tv.tuner.cc.CaptionWindowLayout}, this class is copied from * older version of ExoPlayer. * A view for rendering a single caption. */ @Deprecated public class SubtitleView extends View { // TODO: Change usage of this class to up-to-date class of ExoPlayer. /** * Ratio of inner padding to font size. */ private static final float INNER_PADDING_RATIO = 0.125f; /** * Temporary rectangle used for computing line bounds. */ private final RectF mLineBounds = new RectF(); // Styled dimensions. private final float mCornerRadius; private final float mOutlineWidth; private final float mShadowRadius; private final float mShadowOffset; private final TextPaint mTextPaint; private final Paint mPaint; private CharSequence mText; private int mForegroundColor; private int mBackgroundColor; private int mEdgeColor; private int mEdgeType; private boolean mHasMeasurements; private int mLastMeasuredWidth; private StaticLayout mLayout; private Alignment mAlignment; private final float mSpacingMult; private final float mSpacingAdd; private int mInnerPaddingX; private float mWhiteSpaceWidth; private ArrayList mPrefixSpaces = new ArrayList<>(); public SubtitleView(Context context) { this(context, null); } public SubtitleView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SubtitleView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); int[] viewAttr = {android.R.attr.text, android.R.attr.textSize, android.R.attr.lineSpacingExtra, android.R.attr.lineSpacingMultiplier}; TypedArray a = context.obtainStyledAttributes(attrs, viewAttr, defStyleAttr, 0); CharSequence text = a.getText(0); int textSize = a.getDimensionPixelSize(1, 15); mSpacingAdd = a.getDimensionPixelSize(2, 0); mSpacingMult = a.getFloat(3, 1); a.recycle(); Resources resources = getContext().getResources(); DisplayMetrics displayMetrics = resources.getDisplayMetrics(); int twoDpInPx = Math.round((2f * displayMetrics.densityDpi) / DisplayMetrics.DENSITY_DEFAULT); mCornerRadius = twoDpInPx; mOutlineWidth = twoDpInPx; mShadowRadius = twoDpInPx; mShadowOffset = twoDpInPx; mTextPaint = new TextPaint(); mTextPaint.setAntiAlias(true); mTextPaint.setSubpixelText(true); mAlignment = Alignment.ALIGN_CENTER; mPaint = new Paint(); mPaint.setAntiAlias(true); mInnerPaddingX = 0; setText(text); setTextSize(textSize); setStyle(CaptionStyleCompat.DEFAULT); } @Override public void setBackgroundColor(int color) { mBackgroundColor = color; forceUpdate(false); } /** * Sets the text to be displayed by the view. * * @param text The text to display. */ public void setText(CharSequence text) { this.mText = text; forceUpdate(true); } /** * Sets the text size in pixels. * * @param size The text size in pixels. */ public void setTextSize(float size) { if (mTextPaint.getTextSize() != size) { mTextPaint.setTextSize(size); mInnerPaddingX = (int) (size * INNER_PADDING_RATIO + 0.5f); mWhiteSpaceWidth -= mInnerPaddingX * 2; forceUpdate(true); } } /** * Sets the text alignment. * * @param textAlignment The text alignment. */ public void setTextAlignment(Alignment textAlignment) { mAlignment = textAlignment; } /** * Configures the view according to the given style. * * @param style A style for the view. */ public void setStyle(CaptionStyleCompat style) { mForegroundColor = style.foregroundColor; mBackgroundColor = style.backgroundColor; mEdgeType = style.edgeType; mEdgeColor = style.edgeColor; setTypeface(style.typeface); super.setBackgroundColor(style.windowColor); forceUpdate(true); } public void setPrefixSpaces(ArrayList prefixSpaces) { mPrefixSpaces = prefixSpaces; } public void setWhiteSpaceWidth(float whiteSpaceWidth) { mWhiteSpaceWidth = whiteSpaceWidth; } private void setTypeface(Typeface typeface) { if (mTextPaint.getTypeface() != typeface) { mTextPaint.setTypeface(typeface); forceUpdate(true); } } private void forceUpdate(boolean needsLayout) { if (needsLayout) { mHasMeasurements = false; requestLayout(); } invalidate(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final int widthSpec = MeasureSpec.getSize(widthMeasureSpec); if (computeMeasurements(widthSpec)) { final StaticLayout layout = this.mLayout; final int paddingX = getPaddingLeft() + getPaddingRight() + mInnerPaddingX * 2; final int height = layout.getHeight() + getPaddingTop() + getPaddingBottom(); int width = 0; int lineCount = layout.getLineCount(); for (int i = 0; i < lineCount; i++) { width = Math.max((int) Math.ceil(layout.getLineWidth(i)), width); } width += paddingX; setMeasuredDimension(width, height); } else if (Util.SDK_INT >= 11) { setTooSmallMeasureDimensionV11(); } else { setMeasuredDimension(0, 0); } } @TargetApi(11) private void setTooSmallMeasureDimensionV11() { setMeasuredDimension(MEASURED_STATE_TOO_SMALL, MEASURED_STATE_TOO_SMALL); } @Override public void onLayout(boolean changed, int l, int t, int r, int b) { final int width = r - l; computeMeasurements(width); } private boolean computeMeasurements(int maxWidth) { if (mHasMeasurements && maxWidth == mLastMeasuredWidth) { return true; } // Account for padding. final int paddingX = getPaddingLeft() + getPaddingRight() + mInnerPaddingX * 2; maxWidth -= paddingX; if (maxWidth <= 0) { return false; } mHasMeasurements = true; mLastMeasuredWidth = maxWidth; mLayout = new StaticLayout(mText, mTextPaint, maxWidth, mAlignment, mSpacingMult, mSpacingAdd, true); return true; } @Override protected void onDraw(Canvas c) { final StaticLayout layout = this.mLayout; if (layout == null) { return; } final int saveCount = c.save(); final int innerPaddingX = this.mInnerPaddingX; c.translate(getPaddingLeft() + innerPaddingX, getPaddingTop()); final int lineCount = layout.getLineCount(); final Paint textPaint = this.mTextPaint; final Paint paint = this.mPaint; final RectF bounds = mLineBounds; if (Color.alpha(mBackgroundColor) > 0) { final float cornerRadius = this.mCornerRadius; float previousBottom = layout.getLineTop(0); paint.setColor(mBackgroundColor); paint.setStyle(Style.FILL); for (int i = 0; i < lineCount; i++) { float spacesPadding = 0.0f; if (i < mPrefixSpaces.size()) { spacesPadding += mPrefixSpaces.get(i) * mWhiteSpaceWidth; } bounds.left = layout.getLineLeft(i) - innerPaddingX + spacesPadding; bounds.right = layout.getLineRight(i) + innerPaddingX; bounds.top = previousBottom; bounds.bottom = layout.getLineBottom(i); previousBottom = bounds.bottom; c.drawRoundRect(bounds, cornerRadius, cornerRadius, paint); } } if (mEdgeType == CaptionStyleCompat.EDGE_TYPE_OUTLINE) { textPaint.setStrokeJoin(Join.ROUND); textPaint.setStrokeWidth(mOutlineWidth); textPaint.setColor(mEdgeColor); textPaint.setStyle(Style.FILL_AND_STROKE); layout.draw(c); } else if (mEdgeType == CaptionStyleCompat.EDGE_TYPE_DROP_SHADOW) { textPaint.setShadowLayer(mShadowRadius, mShadowOffset, mShadowOffset, mEdgeColor); } else if (mEdgeType == CaptionStyleCompat.EDGE_TYPE_RAISED || mEdgeType == CaptionStyleCompat.EDGE_TYPE_DEPRESSED) { boolean raised = mEdgeType == CaptionStyleCompat.EDGE_TYPE_RAISED; int colorUp = raised ? Color.WHITE : mEdgeColor; int colorDown = raised ? mEdgeColor : Color.WHITE; float offset = mShadowRadius / 2f; textPaint.setColor(mForegroundColor); textPaint.setStyle(Style.FILL); textPaint.setShadowLayer(mShadowRadius, -offset, -offset, colorUp); layout.draw(c); textPaint.setShadowLayer(mShadowRadius, offset, offset, colorDown); } textPaint.setColor(mForegroundColor); textPaint.setStyle(Style.FILL); layout.draw(c); textPaint.setShadowLayer(0, 0, 0, 0); c.restoreToCount(saveCount); } }