All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.android.graphics.text.MeasuredText Maven / Gradle / Ivy

Go to download

A library jar that provides APIs for Applications written for the Google Android Platform.

There is a newer version: 15-robolectric-12650502
Show newest version
/*
 * Copyright (C) 2010 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 android.graphics.text;

import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Px;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.Log;

import com.android.internal.util.Preconditions;

import dalvik.annotation.optimization.CriticalNative;

import libcore.util.NativeAllocationRegistry;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;

/**
 * Result of text shaping of the single paragraph string.
 *
 * 

*

 * 
 * Paint paint = new Paint();
 * Paint bigPaint = new Paint();
 * bigPaint.setTextSize(paint.getTextSize() * 2.0);
 * String text = "Hello, Android.";
 * MeasuredText mt = new MeasuredText.Builder(text.toCharArray())
 *      .appendStyleRun(paint, 7, false)  // Use paint for "Hello, "
 *      .appendStyleRun(bigPaint, 8, false)  // Use bigPaint for "Android."
 *      .build();
 * 
 * 
*

*/ public class MeasuredText { private static final String TAG = "MeasuredText"; private final long mNativePtr; private final boolean mComputeHyphenation; private final boolean mComputeLayout; @NonNull private final char[] mChars; private final int mTop; private final int mBottom; // Use builder instead. private MeasuredText(long ptr, @NonNull char[] chars, boolean computeHyphenation, boolean computeLayout, int top, int bottom) { mNativePtr = ptr; mChars = chars; mComputeHyphenation = computeHyphenation; mComputeLayout = computeLayout; mTop = top; mBottom = bottom; } /** * Returns the characters in the paragraph used to compute this MeasuredText instance. * @hide */ public @NonNull char[] getChars() { return mChars; } /** * Returns the width of a given range. * * @param start an inclusive start index of the range * @param end an exclusive end index of the range */ public @FloatRange(from = 0.0) @Px float getWidth( @IntRange(from = 0) int start, @IntRange(from = 0) int end) { Preconditions.checkArgument(0 <= start && start <= mChars.length, "start(%d) must be 0 <= start <= %d", start, mChars.length); Preconditions.checkArgument(0 <= end && end <= mChars.length, "end(%d) must be 0 <= end <= %d", end, mChars.length); Preconditions.checkArgument(start <= end, "start(%d) is larger than end(%d)", start, end); return nGetWidth(mNativePtr, start, end); } /** * Returns a memory usage of the native object. * * @hide */ public int getMemoryUsage() { return nGetMemoryUsage(mNativePtr); } /** * Retrieves the boundary box of the given range * * @param start an inclusive start index of the range * @param end an exclusive end index of the range * @param rect an output parameter */ public void getBounds(@IntRange(from = 0) int start, @IntRange(from = 0) int end, @NonNull Rect rect) { Preconditions.checkArgument(0 <= start && start <= mChars.length, "start(%d) must be 0 <= start <= %d", start, mChars.length); Preconditions.checkArgument(0 <= end && end <= mChars.length, "end(%d) must be 0 <= end <= %d", end, mChars.length); Preconditions.checkArgument(start <= end, "start(%d) is larger than end(%d)", start, end); Preconditions.checkNotNull(rect); nGetBounds(mNativePtr, mChars, start, end, rect); } /** * Retrieves the font metrics of the given range * * @param start an inclusive start index of the range * @param end an exclusive end index of the range * @param outMetrics an output metrics object */ public void getFontMetricsInt(@IntRange(from = 0) int start, @IntRange(from = 0) int end, @NonNull Paint.FontMetricsInt outMetrics) { Preconditions.checkArgument(0 <= start && start <= mChars.length, "start(%d) must be 0 <= start <= %d", start, mChars.length); Preconditions.checkArgument(0 <= end && end <= mChars.length, "end(%d) must be 0 <= end <= %d", end, mChars.length); Preconditions.checkArgument(start <= end, "start(%d) is larger than end(%d)", start, end); Objects.requireNonNull(outMetrics); long packed = nGetExtent(mNativePtr, mChars, start, end); outMetrics.ascent = (int) (packed >> 32); outMetrics.descent = (int) (packed & 0xFFFFFFFF); outMetrics.top = Math.min(outMetrics.ascent, mTop); outMetrics.bottom = Math.max(outMetrics.descent, mBottom); } /** * Returns the width of the character at the given offset. * * @param offset an offset of the character. */ public @FloatRange(from = 0.0f) @Px float getCharWidthAt(@IntRange(from = 0) int offset) { Preconditions.checkArgument(0 <= offset && offset < mChars.length, "offset(%d) is larger than text length %d" + offset, mChars.length); return nGetCharWidthAt(mNativePtr, offset); } /** * Returns a native pointer of the underlying native object. * * @hide */ public long getNativePtr() { return mNativePtr; } @CriticalNative private static native float nGetWidth(/* Non Zero */ long nativePtr, @IntRange(from = 0) int start, @IntRange(from = 0) int end); @CriticalNative private static native /* Non Zero */ long nGetReleaseFunc(); @CriticalNative private static native int nGetMemoryUsage(/* Non Zero */ long nativePtr); private static native void nGetBounds(long nativePtr, char[] buf, int start, int end, Rect rect); @CriticalNative private static native float nGetCharWidthAt(long nativePtr, int offset); private static native long nGetExtent(long nativePtr, char[] buf, int start, int end); /** * Helper class for creating a {@link MeasuredText}. *

*

     * 
     * Paint paint = new Paint();
     * String text = "Hello, Android.";
     * MeasuredText mt = new MeasuredText.Builder(text.toCharArray())
     *      .appendStyleRun(paint, text.length, false)
     *      .build();
     * 
     * 
*

* * Note: The appendStyle and appendReplacementRun should be called to cover the text length. */ public static final class Builder { private static final NativeAllocationRegistry sRegistry = NativeAllocationRegistry.createMalloced( MeasuredText.class.getClassLoader(), nGetReleaseFunc()); private long mNativePtr; private final @NonNull char[] mText; private boolean mComputeHyphenation = false; private boolean mComputeLayout = true; private boolean mFastHyphenation = false; private int mCurrentOffset = 0; private @Nullable MeasuredText mHintMt = null; private int mTop = 0; private int mBottom = 0; private Paint.FontMetricsInt mCachedMetrics = new Paint.FontMetricsInt(); /** * Construct a builder. * * The MeasuredText returned by build method will hold a reference of the text. Developer is * not supposed to modify the text. * * @param text a text */ public Builder(@NonNull char[] text) { Preconditions.checkNotNull(text); mText = text; mNativePtr = nInitBuilder(); } /** * Construct a builder with existing MeasuredText. * * The MeasuredText returned by build method will hold a reference of the text. Developer is * not supposed to modify the text. * * @param text a text */ public Builder(@NonNull MeasuredText text) { Preconditions.checkNotNull(text); mText = text.mChars; mNativePtr = nInitBuilder(); if (!text.mComputeLayout) { throw new IllegalArgumentException( "The input MeasuredText must not be created with setComputeLayout(false)."); } mComputeHyphenation = text.mComputeHyphenation; mComputeLayout = text.mComputeLayout; mHintMt = text; } /** * Apply styles to the given length. * * Keeps an internal offset which increases at every append. The initial value for this * offset is zero. After the style is applied the internal offset is moved to {@code offset * + length}, and next call will start from this new position. * * @param paint a paint * @param length a length to be applied with a given paint, can not exceed the length of the * text * @param isRtl true if the text is in RTL context, otherwise false. */ public @NonNull Builder appendStyleRun(@NonNull Paint paint, @IntRange(from = 0) int length, boolean isRtl) { return appendStyleRun(paint, null, length, isRtl); } /** * Apply styles to the given length. * * Keeps an internal offset which increases at every append. The initial value for this * offset is zero. After the style is applied the internal offset is moved to {@code offset * + length}, and next call will start from this new position. * * @param paint a paint * @param lineBreakConfig a line break configuration. * @param length a length to be applied with a given paint, can not exceed the length of the * text * @param isRtl true if the text is in RTL context, otherwise false. */ public @NonNull Builder appendStyleRun(@NonNull Paint paint, @Nullable LineBreakConfig lineBreakConfig, @IntRange(from = 0) int length, boolean isRtl) { Preconditions.checkNotNull(paint); Preconditions.checkArgument(length > 0, "length can not be negative"); final int end = mCurrentOffset + length; Preconditions.checkArgument(end <= mText.length, "Style exceeds the text length"); int lbStyle = (lineBreakConfig != null) ? lineBreakConfig.getLineBreakStyle() : LineBreakConfig.LINE_BREAK_STYLE_NONE; int lbWordStyle = (lineBreakConfig != null) ? lineBreakConfig.getLineBreakWordStyle() : LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE; nAddStyleRun(mNativePtr, paint.getNativeInstance(), lbStyle, lbWordStyle, mCurrentOffset, end, isRtl); mCurrentOffset = end; paint.getFontMetricsInt(mCachedMetrics); mTop = Math.min(mTop, mCachedMetrics.top); mBottom = Math.max(mBottom, mCachedMetrics.bottom); return this; } /** * Used to inform the text layout that the given length is replaced with the object of given * width. * * Keeps an internal offset which increases at every append. The initial value for this * offset is zero. After the style is applied the internal offset is moved to {@code offset * + length}, and next call will start from this new position. * * Informs the layout engine that the given length should not be processed, instead the * provided width should be used for calculating the width of that range. * * @param length a length to be replaced with the object, can not exceed the length of the * text * @param width a replacement width of the range */ public @NonNull Builder appendReplacementRun(@NonNull Paint paint, @IntRange(from = 0) int length, @Px @FloatRange(from = 0) float width) { Preconditions.checkArgument(length > 0, "length can not be negative"); final int end = mCurrentOffset + length; Preconditions.checkArgument(end <= mText.length, "Replacement exceeds the text length"); nAddReplacementRun(mNativePtr, paint.getNativeInstance(), mCurrentOffset, end, width); mCurrentOffset = end; return this; } /** * By passing true to this method, the build method will compute all possible hyphenation * pieces as well. * * If you don't want to use automatic hyphenation, you can pass false to this method and * save the computation time of hyphenation. The default value is false. * * Even if you pass false to this method, you can still enable automatic hyphenation of * LineBreaker but line break computation becomes slower. * * @deprecated use setComputeHyphenation(int) instead. * * @param computeHyphenation true if you want to use automatic hyphenations. */ public @NonNull @Deprecated Builder setComputeHyphenation(boolean computeHyphenation) { setComputeHyphenation( computeHyphenation ? HYPHENATION_MODE_NORMAL : HYPHENATION_MODE_NONE); return this; } /** @hide */ @IntDef(prefix = { "HYPHENATION_MODE_" }, value = { HYPHENATION_MODE_NONE, HYPHENATION_MODE_NORMAL, HYPHENATION_MODE_FAST }) @Retention(RetentionPolicy.SOURCE) public @interface HyphenationMode {} /** * A value for hyphenation calculation mode. * * This value indicates that no hyphenation points are calculated. */ public static final int HYPHENATION_MODE_NONE = 0; /** * A value for hyphenation calculation mode. * * This value indicates that hyphenation points are calculated. */ public static final int HYPHENATION_MODE_NORMAL = 1; /** * A value for hyphenation calculation mode. * * This value indicates that hyphenation points are calculated with faster algorithm. This * algorithm measures text width with ignoring the context of hyphen character shaping, e.g. * kerning. */ public static final int HYPHENATION_MODE_FAST = 2; /** * By passing true to this method, the build method will calculate hyphenation break * points faster with ignoring some typographic features, e.g. kerning. * * {@link #HYPHENATION_MODE_NONE} is by default. * * @param mode a hyphenation mode. */ public @NonNull Builder setComputeHyphenation(@HyphenationMode int mode) { switch (mode) { case HYPHENATION_MODE_NONE: mComputeHyphenation = false; mFastHyphenation = false; break; case HYPHENATION_MODE_NORMAL: mComputeHyphenation = true; mFastHyphenation = false; break; case HYPHENATION_MODE_FAST: mComputeHyphenation = true; mFastHyphenation = true; break; default: Log.e(TAG, "Unknown hyphenation mode: " + mode); mComputeHyphenation = false; mFastHyphenation = false; break; } return this; } /** * By passing true to this method, the build method will compute all full layout * information. * * If you don't use {@link MeasuredText#getBounds(int,int,android.graphics.Rect)}, you can * pass false to this method and save the memory spaces. The default value is true. * * Even if you pass false to this method, you can still call getBounds but it becomes * slower. * * @param computeLayout true if you want to retrieve full layout info, e.g. bbox. */ public @NonNull Builder setComputeLayout(boolean computeLayout) { mComputeLayout = computeLayout; return this; } /** * Creates a MeasuredText. * * Once you called build() method, you can't reuse the Builder class again. * @throws IllegalStateException if this Builder is reused. * @throws IllegalStateException if the whole text is not covered by one or more runs (style * or replacement) */ public @NonNull MeasuredText build() { ensureNativePtrNoReuse(); if (mCurrentOffset != mText.length) { throw new IllegalStateException("Style info has not been provided for all text."); } if (mHintMt != null && mHintMt.mComputeHyphenation != mComputeHyphenation) { throw new IllegalArgumentException( "The hyphenation configuration is different from given hint MeasuredText"); } try { long hintPtr = (mHintMt == null) ? 0 : mHintMt.getNativePtr(); long ptr = nBuildMeasuredText(mNativePtr, hintPtr, mText, mComputeHyphenation, mComputeLayout, mFastHyphenation); final MeasuredText res = new MeasuredText(ptr, mText, mComputeHyphenation, mComputeLayout, mTop, mBottom); sRegistry.registerNativeAllocation(res, ptr); return res; } finally { nFreeBuilder(mNativePtr); mNativePtr = 0; } } /** * Ensures {@link #mNativePtr} is not reused. * *

This is a method by itself to help increase testability - eg. Robolectric might want * to override the validation behavior in test environment. */ private void ensureNativePtrNoReuse() { if (mNativePtr == 0) { throw new IllegalStateException("Builder can not be reused."); } } private static native /* Non Zero */ long nInitBuilder(); /** * Apply style to make native measured text. * * @param nativeBuilderPtr The native MeasuredParagraph builder pointer. * @param paintPtr The native paint pointer to be applied. * @param lineBreakStyle The line break style(lb) of the text. * @param lineBreakWordStyle The line break word style(lw) of the text. * @param start The start offset in the copied buffer. * @param end The end offset in the copied buffer. * @param isRtl True if the text is RTL. */ private static native void nAddStyleRun(/* Non Zero */ long nativeBuilderPtr, /* Non Zero */ long paintPtr, int lineBreakStyle, int lineBreakWordStyle, @IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean isRtl); /** * Apply ReplacementRun to make native measured text. * * @param nativeBuilderPtr The native MeasuredParagraph builder pointer. * @param paintPtr The native paint pointer to be applied. * @param start The start offset in the copied buffer. * @param end The end offset in the copied buffer. * @param width The width of the replacement. */ private static native void nAddReplacementRun(/* Non Zero */ long nativeBuilderPtr, /* Non Zero */ long paintPtr, @IntRange(from = 0) int start, @IntRange(from = 0) int end, @FloatRange(from = 0) float width); private static native long nBuildMeasuredText( /* Non Zero */ long nativeBuilderPtr, long hintMtPtr, @NonNull char[] text, boolean computeHyphenation, boolean computeLayout, boolean fastHyphenationMode); private static native void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy