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