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