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