1 /* 2 * Copyright (C) 2020 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 17 package android.graphics.text; 18 19 import android.annotation.IntRange; 20 import android.annotation.NonNull; 21 import android.graphics.Paint; 22 import android.graphics.Typeface; 23 import android.graphics.fonts.Font; 24 25 import com.android.internal.util.Preconditions; 26 27 import dalvik.annotation.optimization.CriticalNative; 28 29 import libcore.util.NativeAllocationRegistry; 30 31 import java.util.ArrayList; 32 import java.util.Objects; 33 34 /** 35 * Text shaping result object for single style text. 36 * 37 * You can get text shaping result by 38 * {@link TextRunShaper#shapeTextRun(char[], int, int, int, int, float, float, boolean, Paint)} and 39 * {@link TextRunShaper#shapeTextRun(CharSequence, int, int, int, int, float, float, boolean, 40 * Paint)}. 41 * 42 * @see TextRunShaper#shapeTextRun(char[], int, int, int, int, float, float, boolean, Paint) 43 * @see TextRunShaper#shapeTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint) 44 */ 45 public final class PositionedGlyphs { 46 private static final NativeAllocationRegistry REGISTRY = 47 NativeAllocationRegistry.createMalloced( 48 Typeface.class.getClassLoader(), nReleaseFunc()); 49 50 private final long mLayoutPtr; 51 private final float mXOffset; 52 private final float mYOffset; 53 private final ArrayList<Font> mFonts; 54 55 /** 56 * Returns the total amount of advance consumed by this positioned glyphs. 57 * 58 * The advance is an amount of width consumed by the glyph. The total amount of advance is 59 * a total amount of advance consumed by this series of glyphs. In other words, if another 60 * glyph is placed next to this series of glyphs, it's X offset should be shifted this amount 61 * of width. 62 * 63 * @return total amount of advance 64 */ getAdvance()65 public float getAdvance() { 66 return nGetTotalAdvance(mLayoutPtr); 67 } 68 69 /** 70 * Effective ascent value of this positioned glyphs. 71 * 72 * If two or more font files are used in this series of glyphs, the effective ascent will be 73 * the minimum ascent value across the all font files. 74 * 75 * @return effective ascent value 76 */ getAscent()77 public float getAscent() { 78 return nGetAscent(mLayoutPtr); 79 } 80 81 /** 82 * Effective descent value of this positioned glyphs. 83 * 84 * If two or more font files are used in this series of glyphs, the effective descent will be 85 * the maximum descent value across the all font files. 86 * 87 * @return effective descent value 88 */ getDescent()89 public float getDescent() { 90 return nGetDescent(mLayoutPtr); 91 } 92 93 /** 94 * Returns the amount of X offset added to glyph position. 95 * 96 * @return The X offset added to glyph position. 97 */ getOffsetX()98 public float getOffsetX() { 99 return mXOffset; 100 } 101 102 /** 103 * Returns the amount of Y offset added to glyph position. 104 * 105 * @return The Y offset added to glyph position. 106 */ getOffsetY()107 public float getOffsetY() { 108 return mYOffset; 109 } 110 111 /** 112 * Returns the number of glyphs stored. 113 * 114 * @return the number of glyphs 115 */ 116 @IntRange(from = 0) glyphCount()117 public int glyphCount() { 118 return nGetGlyphCount(mLayoutPtr); 119 } 120 121 /** 122 * Returns the font object used for drawing the glyph at the given index. 123 * 124 * @param index the glyph index 125 * @return the font object used for drawing the glyph at the given index 126 */ 127 @NonNull getFont(@ntRangefrom = 0) int index)128 public Font getFont(@IntRange(from = 0) int index) { 129 Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index"); 130 return mFonts.get(index); 131 } 132 133 /** 134 * Returns the glyph ID used for drawing the glyph at the given index. 135 * 136 * @param index the glyph index 137 * @return An glyph ID of the font. 138 */ 139 @IntRange(from = 0) getGlyphId(@ntRangefrom = 0) int index)140 public int getGlyphId(@IntRange(from = 0) int index) { 141 Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index"); 142 return nGetGlyphId(mLayoutPtr, index); 143 } 144 145 /** 146 * Returns the x coordinate of the glyph position at the given index. 147 * 148 * @param index the glyph index 149 * @return A X offset in pixels 150 */ getGlyphX(@ntRangefrom = 0) int index)151 public float getGlyphX(@IntRange(from = 0) int index) { 152 Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index"); 153 return nGetX(mLayoutPtr, index) + mXOffset; 154 } 155 156 /** 157 * Returns the y coordinate of the glyph position at the given index. 158 * 159 * @param index the glyph index 160 * @return A Y offset in pixels. 161 */ getGlyphY(@ntRangefrom = 0) int index)162 public float getGlyphY(@IntRange(from = 0) int index) { 163 Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index"); 164 return nGetY(mLayoutPtr, index) + mYOffset; 165 } 166 167 /** 168 * Create single style layout from native result. 169 * 170 * @hide 171 * 172 * @param layoutPtr the address of native layout object. 173 */ PositionedGlyphs(long layoutPtr, float xOffset, float yOffset)174 public PositionedGlyphs(long layoutPtr, float xOffset, float yOffset) { 175 mLayoutPtr = layoutPtr; 176 int glyphCount = nGetGlyphCount(layoutPtr); 177 mFonts = new ArrayList<>(glyphCount); 178 mXOffset = xOffset; 179 mYOffset = yOffset; 180 181 long prevPtr = 0; 182 Font prevFont = null; 183 for (int i = 0; i < glyphCount; ++i) { 184 long ptr = nGetFont(layoutPtr, i); 185 if (prevPtr != ptr) { 186 prevPtr = ptr; 187 prevFont = new Font(ptr); 188 } 189 mFonts.add(prevFont); 190 } 191 192 REGISTRY.registerNativeAllocation(this, layoutPtr); 193 } 194 195 @CriticalNative nGetGlyphCount(long minikinLayout)196 private static native int nGetGlyphCount(long minikinLayout); 197 @CriticalNative nGetTotalAdvance(long minikinLayout)198 private static native float nGetTotalAdvance(long minikinLayout); 199 @CriticalNative nGetAscent(long minikinLayout)200 private static native float nGetAscent(long minikinLayout); 201 @CriticalNative nGetDescent(long minikinLayout)202 private static native float nGetDescent(long minikinLayout); 203 @CriticalNative nGetGlyphId(long minikinLayout, int i)204 private static native int nGetGlyphId(long minikinLayout, int i); 205 @CriticalNative nGetX(long minikinLayout, int i)206 private static native float nGetX(long minikinLayout, int i); 207 @CriticalNative nGetY(long minikinLayout, int i)208 private static native float nGetY(long minikinLayout, int i); 209 @CriticalNative nGetFont(long minikinLayout, int i)210 private static native long nGetFont(long minikinLayout, int i); 211 @CriticalNative nReleaseFunc()212 private static native long nReleaseFunc(); 213 214 @Override equals(Object o)215 public boolean equals(Object o) { 216 if (this == o) return true; 217 if (!(o instanceof PositionedGlyphs)) return false; 218 PositionedGlyphs that = (PositionedGlyphs) o; 219 220 if (mXOffset != that.mXOffset || mYOffset != that.mYOffset) return false; 221 if (glyphCount() != that.glyphCount()) return false; 222 223 for (int i = 0; i < glyphCount(); ++i) { 224 if (getGlyphId(i) != that.getGlyphId(i)) return false; 225 if (getGlyphX(i) != that.getGlyphX(i)) return false; 226 if (getGlyphY(i) != that.getGlyphY(i)) return false; 227 if (!getFont(i).equals(that.getFont(i))) return false; 228 } 229 230 return true; 231 } 232 233 @Override hashCode()234 public int hashCode() { 235 int hashCode = Objects.hash(mXOffset, mYOffset); 236 for (int i = 0; i < glyphCount(); ++i) { 237 hashCode = Objects.hash(hashCode, 238 getGlyphId(i), getGlyphX(i), getGlyphY(i), getFont(i)); 239 } 240 return hashCode; 241 } 242 243 @Override toString()244 public String toString() { 245 StringBuilder sb = new StringBuilder("["); 246 for (int i = 0; i < glyphCount(); ++i) { 247 if (i != 0) { 248 sb.append(", "); 249 } 250 sb.append("[ ID = " + getGlyphId(i) + "," 251 + " pos = (" + getGlyphX(i) + "," + getGlyphY(i) + ")" 252 + " font = " + getFont(i) + " ]"); 253 } 254 sb.append("]"); 255 return "PositionedGlyphs{" 256 + "glyphs = " + sb.toString() 257 + ", mXOffset=" + mXOffset 258 + ", mYOffset=" + mYOffset 259 + '}'; 260 } 261 } 262