blob: 8d20e9cee7d78e8e3fd4362d31c4114135db1e01 [file] [log] [blame]
/*
* Copyright (C) 2020 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.IntRange;
import android.annotation.NonNull;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.graphics.fonts.Font;
import com.android.internal.util.Preconditions;
import dalvik.annotation.optimization.CriticalNative;
import libcore.util.NativeAllocationRegistry;
import java.util.ArrayList;
import java.util.Objects;
/**
* Text shaping result object for single style text.
*
* You can get text shaping result by
* {@link TextRunShaper#shapeTextRun(char[], int, int, int, int, float, float, boolean, Paint)} and
* {@link TextRunShaper#shapeTextRun(CharSequence, int, int, int, int, float, float, boolean,
* Paint)}.
*
* @see TextRunShaper#shapeTextRun(char[], int, int, int, int, float, float, boolean, Paint)
* @see TextRunShaper#shapeTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)
*/
public final class PositionedGlyphs {
private static final NativeAllocationRegistry REGISTRY =
NativeAllocationRegistry.createMalloced(
Typeface.class.getClassLoader(), nReleaseFunc());
private final long mLayoutPtr;
private final float mXOffset;
private final float mYOffset;
private final ArrayList<Font> mFonts;
/**
* Returns the total amount of advance consumed by this positioned glyphs.
*
* The advance is an amount of width consumed by the glyph. The total amount of advance is
* a total amount of advance consumed by this series of glyphs. In other words, if another
* glyph is placed next to this series of glyphs, it's X offset should be shifted this amount
* of width.
*
* @return total amount of advance
*/
public float getAdvance() {
return nGetTotalAdvance(mLayoutPtr);
}
/**
* Effective ascent value of this positioned glyphs.
*
* If two or more font files are used in this series of glyphs, the effective ascent will be
* the minimum ascent value across the all font files.
*
* @return effective ascent value
*/
public float getAscent() {
return nGetAscent(mLayoutPtr);
}
/**
* Effective descent value of this positioned glyphs.
*
* If two or more font files are used in this series of glyphs, the effective descent will be
* the maximum descent value across the all font files.
*
* @return effective descent value
*/
public float getDescent() {
return nGetDescent(mLayoutPtr);
}
/**
* Returns the amount of X offset added to glyph position.
*
* @return The X offset added to glyph position.
*/
public float getOffsetX() {
return mXOffset;
}
/**
* Returns the amount of Y offset added to glyph position.
*
* @return The Y offset added to glyph position.
*/
public float getOffsetY() {
return mYOffset;
}
/**
* Returns the number of glyphs stored.
*
* @return the number of glyphs
*/
@IntRange(from = 0)
public int glyphCount() {
return nGetGlyphCount(mLayoutPtr);
}
/**
* Returns the font object used for drawing the glyph at the given index.
*
* @param index the glyph index
* @return the font object used for drawing the glyph at the given index
*/
@NonNull
public Font getFont(@IntRange(from = 0) int index) {
Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
return mFonts.get(index);
}
/**
* Returns the glyph ID used for drawing the glyph at the given index.
*
* @param index the glyph index
* @return An glyph ID of the font.
*/
@IntRange(from = 0)
public int getGlyphId(@IntRange(from = 0) int index) {
Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
return nGetGlyphId(mLayoutPtr, index);
}
/**
* Returns the x coordinate of the glyph position at the given index.
*
* @param index the glyph index
* @return A X offset in pixels
*/
public float getGlyphX(@IntRange(from = 0) int index) {
Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
return nGetX(mLayoutPtr, index) + mXOffset;
}
/**
* Returns the y coordinate of the glyph position at the given index.
*
* @param index the glyph index
* @return A Y offset in pixels.
*/
public float getGlyphY(@IntRange(from = 0) int index) {
Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
return nGetY(mLayoutPtr, index) + mYOffset;
}
/**
* Create single style layout from native result.
*
* @hide
*
* @param layoutPtr the address of native layout object.
*/
public PositionedGlyphs(long layoutPtr, float xOffset, float yOffset) {
mLayoutPtr = layoutPtr;
int glyphCount = nGetGlyphCount(layoutPtr);
mFonts = new ArrayList<>(glyphCount);
mXOffset = xOffset;
mYOffset = yOffset;
long prevPtr = 0;
Font prevFont = null;
for (int i = 0; i < glyphCount; ++i) {
long ptr = nGetFont(layoutPtr, i);
if (prevPtr != ptr) {
prevPtr = ptr;
prevFont = new Font(ptr);
}
mFonts.add(prevFont);
}
REGISTRY.registerNativeAllocation(this, layoutPtr);
}
@CriticalNative
private static native int nGetGlyphCount(long minikinLayout);
@CriticalNative
private static native float nGetTotalAdvance(long minikinLayout);
@CriticalNative
private static native float nGetAscent(long minikinLayout);
@CriticalNative
private static native float nGetDescent(long minikinLayout);
@CriticalNative
private static native int nGetGlyphId(long minikinLayout, int i);
@CriticalNative
private static native float nGetX(long minikinLayout, int i);
@CriticalNative
private static native float nGetY(long minikinLayout, int i);
@CriticalNative
private static native long nGetFont(long minikinLayout, int i);
@CriticalNative
private static native long nReleaseFunc();
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof PositionedGlyphs)) return false;
PositionedGlyphs that = (PositionedGlyphs) o;
if (mXOffset != that.mXOffset || mYOffset != that.mYOffset) return false;
if (glyphCount() != that.glyphCount()) return false;
for (int i = 0; i < glyphCount(); ++i) {
if (getGlyphId(i) != that.getGlyphId(i)) return false;
if (getGlyphX(i) != that.getGlyphX(i)) return false;
if (getGlyphY(i) != that.getGlyphY(i)) return false;
if (!getFont(i).equals(that.getFont(i))) return false;
}
return true;
}
@Override
public int hashCode() {
int hashCode = Objects.hash(mXOffset, mYOffset);
for (int i = 0; i < glyphCount(); ++i) {
hashCode = Objects.hash(hashCode,
getGlyphId(i), getGlyphX(i), getGlyphY(i), getFont(i));
}
return hashCode;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("[");
for (int i = 0; i < glyphCount(); ++i) {
if (i != 0) {
sb.append(", ");
}
sb.append("[ ID = " + getGlyphId(i) + ","
+ " pos = (" + getGlyphX(i) + "," + getGlyphY(i) + ")"
+ " font = " + getFont(i) + " ]");
}
sb.append("]");
return "PositionedGlyphs{"
+ "glyphs = " + sb.toString()
+ ", mXOffset=" + mXOffset
+ ", mYOffset=" + mYOffset
+ '}';
}
}