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 static com.android.text.flags.Flags.FLAG_NEW_FONTS_FALLBACK_XML; 20 21 import android.annotation.FlaggedApi; 22 import android.annotation.IntRange; 23 import android.annotation.NonNull; 24 import android.graphics.Paint; 25 import android.graphics.Typeface; 26 import android.graphics.fonts.Font; 27 28 import com.android.internal.util.Preconditions; 29 30 import dalvik.annotation.optimization.CriticalNative; 31 32 import libcore.util.NativeAllocationRegistry; 33 34 import java.util.ArrayList; 35 import java.util.Objects; 36 37 /** 38 * Text shaping result object for single style text. 39 * 40 * You can get text shaping result by 41 * {@link TextRunShaper#shapeTextRun(char[], int, int, int, int, float, float, boolean, Paint)} and 42 * {@link TextRunShaper#shapeTextRun(CharSequence, int, int, int, int, float, float, boolean, 43 * Paint)}. 44 * 45 * @see TextRunShaper#shapeTextRun(char[], int, int, int, int, float, float, boolean, Paint) 46 * @see TextRunShaper#shapeTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint) 47 */ 48 public final class PositionedGlyphs { 49 private static class NoImagePreloadHolder { 50 private static final NativeAllocationRegistry REGISTRY = 51 NativeAllocationRegistry.createMalloced( 52 Typeface.class.getClassLoader(), nReleaseFunc()); 53 } 54 55 private final long mLayoutPtr; 56 private final float mXOffset; 57 private final float mYOffset; 58 private final ArrayList<Font> mFonts; 59 60 /** 61 * Returns the total amount of advance consumed by this positioned glyphs. 62 * 63 * The advance is an amount of width consumed by the glyph. The total amount of advance is 64 * a total amount of advance consumed by this series of glyphs. In other words, if another 65 * glyph is placed next to this series of glyphs, it's X offset should be shifted this amount 66 * of width. 67 * 68 * @return total amount of advance 69 */ getAdvance()70 public float getAdvance() { 71 return nGetTotalAdvance(mLayoutPtr); 72 } 73 74 /** 75 * Effective ascent value of this positioned glyphs. 76 * 77 * If two or more font files are used in this series of glyphs, the effective ascent will be 78 * the minimum ascent value across the all font files. 79 * 80 * @return effective ascent value 81 */ getAscent()82 public float getAscent() { 83 return nGetAscent(mLayoutPtr); 84 } 85 86 /** 87 * Effective descent value of this positioned glyphs. 88 * 89 * If two or more font files are used in this series of glyphs, the effective descent will be 90 * the maximum descent value across the all font files. 91 * 92 * @return effective descent value 93 */ getDescent()94 public float getDescent() { 95 return nGetDescent(mLayoutPtr); 96 } 97 98 /** 99 * Returns the amount of X offset added to glyph position. 100 * 101 * @return The X offset added to glyph position. 102 */ getOffsetX()103 public float getOffsetX() { 104 return mXOffset; 105 } 106 107 /** 108 * Returns the amount of Y offset added to glyph position. 109 * 110 * @return The Y offset added to glyph position. 111 */ getOffsetY()112 public float getOffsetY() { 113 return mYOffset; 114 } 115 116 /** 117 * Returns the number of glyphs stored. 118 * 119 * @return the number of glyphs 120 */ 121 @IntRange(from = 0) glyphCount()122 public int glyphCount() { 123 return nGetGlyphCount(mLayoutPtr); 124 } 125 126 /** 127 * Returns the font object used for drawing the glyph at the given index. 128 * 129 * @param index the glyph index 130 * @return the font object used for drawing the glyph at the given index 131 */ 132 @NonNull getFont(@ntRangefrom = 0) int index)133 public Font getFont(@IntRange(from = 0) int index) { 134 Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index"); 135 return mFonts.get(index); 136 } 137 138 /** 139 * Returns the glyph ID used for drawing the glyph at the given index. 140 * 141 * @param index the glyph index 142 * @return A glyph ID of the font. 143 */ 144 @IntRange(from = 0) getGlyphId(@ntRangefrom = 0) int index)145 public int getGlyphId(@IntRange(from = 0) int index) { 146 Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index"); 147 return nGetGlyphId(mLayoutPtr, index); 148 } 149 150 /** 151 * Returns the x coordinate of the glyph position at the given index. 152 * 153 * @param index the glyph index 154 * @return A X offset in pixels 155 */ getGlyphX(@ntRangefrom = 0) int index)156 public float getGlyphX(@IntRange(from = 0) int index) { 157 Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index"); 158 return nGetX(mLayoutPtr, index) + mXOffset; 159 } 160 161 /** 162 * Returns the y coordinate of the glyph position at the given index. 163 * 164 * @param index the glyph index 165 * @return A Y offset in pixels. 166 */ getGlyphY(@ntRangefrom = 0) int index)167 public float getGlyphY(@IntRange(from = 0) int index) { 168 Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index"); 169 return nGetY(mLayoutPtr, index) + mYOffset; 170 } 171 172 /** 173 * Returns true if the fake bold option used for drawing, otherwise false. 174 * 175 * @param index the glyph index 176 * @return true if the fake bold option is on, otherwise off. 177 */ 178 @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML) getFakeBold(@ntRangefrom = 0) int index)179 public boolean getFakeBold(@IntRange(from = 0) int index) { 180 Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index"); 181 return nGetFakeBold(mLayoutPtr, index); 182 } 183 184 /** 185 * Returns true if the fake italic option used for drawing, otherwise false. 186 * 187 * @param index the glyph index 188 * @return true if the fake italic option is on, otherwise off. 189 */ 190 @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML) getFakeItalic(@ntRangefrom = 0) int index)191 public boolean getFakeItalic(@IntRange(from = 0) int index) { 192 Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index"); 193 return nGetFakeItalic(mLayoutPtr, index); 194 } 195 196 /** 197 * A special value returned by {@link #getWeightOverride(int)} and 198 * {@link #getItalicOverride(int)} that indicates no font variation setting is overridden. 199 */ 200 @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML) 201 public static final float NO_OVERRIDE = Float.MIN_VALUE; 202 203 /** 204 * Returns overridden weight value if the font is variable font and `wght` value is overridden 205 * for drawing. Otherwise returns {@link #NO_OVERRIDE}. 206 * 207 * @param index the glyph index 208 * @return overridden weight value or {@link #NO_OVERRIDE}. 209 */ 210 @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML) getWeightOverride(@ntRangefrom = 0) int index)211 public float getWeightOverride(@IntRange(from = 0) int index) { 212 Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index"); 213 float value = nGetWeightOverride(mLayoutPtr, index); 214 if (value == -1) { 215 return NO_OVERRIDE; 216 } else { 217 return value; 218 } 219 } 220 221 /** 222 * Returns overridden italic value if the font is variable font and `ital` value is overridden 223 * for drawing. Otherwise returns {@link #NO_OVERRIDE}. 224 * 225 * @param index the glyph index 226 * @return overridden weight value or {@link #NO_OVERRIDE}. 227 */ 228 @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML) getItalicOverride(@ntRangefrom = 0) int index)229 public float getItalicOverride(@IntRange(from = 0) int index) { 230 Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index"); 231 float value = nGetItalicOverride(mLayoutPtr, index); 232 if (value == -1) { 233 return NO_OVERRIDE; 234 } else { 235 return value; 236 } 237 } 238 239 /** 240 * Create single style layout from native result. 241 * 242 * @hide 243 * 244 * @param layoutPtr the address of native layout object. 245 */ PositionedGlyphs(long layoutPtr, float xOffset, float yOffset)246 public PositionedGlyphs(long layoutPtr, float xOffset, float yOffset) { 247 mLayoutPtr = layoutPtr; 248 int glyphCount = nGetGlyphCount(layoutPtr); 249 mFonts = new ArrayList<>(glyphCount); 250 mXOffset = xOffset; 251 mYOffset = yOffset; 252 253 long prevPtr = 0; 254 Font prevFont = null; 255 for (int i = 0; i < glyphCount; ++i) { 256 long ptr = nGetFont(layoutPtr, i); 257 if (prevPtr != ptr) { 258 prevPtr = ptr; 259 prevFont = new Font(ptr); 260 } 261 mFonts.add(prevFont); 262 } 263 264 NoImagePreloadHolder.REGISTRY.registerNativeAllocation(this, layoutPtr); 265 } 266 267 @CriticalNative nGetGlyphCount(long minikinLayout)268 private static native int nGetGlyphCount(long minikinLayout); 269 @CriticalNative nGetTotalAdvance(long minikinLayout)270 private static native float nGetTotalAdvance(long minikinLayout); 271 @CriticalNative nGetAscent(long minikinLayout)272 private static native float nGetAscent(long minikinLayout); 273 @CriticalNative nGetDescent(long minikinLayout)274 private static native float nGetDescent(long minikinLayout); 275 @CriticalNative nGetGlyphId(long minikinLayout, int i)276 private static native int nGetGlyphId(long minikinLayout, int i); 277 @CriticalNative nGetX(long minikinLayout, int i)278 private static native float nGetX(long minikinLayout, int i); 279 @CriticalNative nGetY(long minikinLayout, int i)280 private static native float nGetY(long minikinLayout, int i); 281 @CriticalNative nGetFont(long minikinLayout, int i)282 private static native long nGetFont(long minikinLayout, int i); 283 @CriticalNative nReleaseFunc()284 private static native long nReleaseFunc(); 285 @CriticalNative nGetFakeBold(long minikinLayout, int i)286 private static native boolean nGetFakeBold(long minikinLayout, int i); 287 @CriticalNative nGetFakeItalic(long minikinLayout, int i)288 private static native boolean nGetFakeItalic(long minikinLayout, int i); 289 @CriticalNative nGetWeightOverride(long minikinLayout, int i)290 private static native float nGetWeightOverride(long minikinLayout, int i); 291 @CriticalNative nGetItalicOverride(long minikinLayout, int i)292 private static native float nGetItalicOverride(long minikinLayout, int i); 293 294 @Override equals(Object o)295 public boolean equals(Object o) { 296 if (this == o) return true; 297 if (!(o instanceof PositionedGlyphs)) return false; 298 PositionedGlyphs that = (PositionedGlyphs) o; 299 300 if (mXOffset != that.mXOffset || mYOffset != that.mYOffset) return false; 301 if (glyphCount() != that.glyphCount()) return false; 302 303 for (int i = 0; i < glyphCount(); ++i) { 304 if (getGlyphId(i) != that.getGlyphId(i)) return false; 305 if (getGlyphX(i) != that.getGlyphX(i)) return false; 306 if (getGlyphY(i) != that.getGlyphY(i)) return false; 307 if (!getFont(i).equals(that.getFont(i))) return false; 308 } 309 310 return true; 311 } 312 313 @Override hashCode()314 public int hashCode() { 315 int hashCode = Objects.hash(mXOffset, mYOffset); 316 for (int i = 0; i < glyphCount(); ++i) { 317 hashCode = Objects.hash(hashCode, 318 getGlyphId(i), getGlyphX(i), getGlyphY(i), getFont(i)); 319 } 320 return hashCode; 321 } 322 323 @Override toString()324 public String toString() { 325 StringBuilder sb = new StringBuilder("["); 326 for (int i = 0; i < glyphCount(); ++i) { 327 if (i != 0) { 328 sb.append(", "); 329 } 330 sb.append("[ ID = " + getGlyphId(i) + "," 331 + " pos = (" + getGlyphX(i) + "," + getGlyphY(i) + ")" 332 + " font = " + getFont(i) + " ]"); 333 } 334 sb.append("]"); 335 return "PositionedGlyphs{" 336 + "glyphs = " + sb.toString() 337 + ", mXOffset=" + mXOffset 338 + ", mYOffset=" + mYOffset 339 + '}'; 340 } 341 } 342