aboutsummaryrefslogtreecommitdiff
path: root/bridge/src/android/graphics/Typeface_Delegate.java
blob: fb044d95dae128ce24de5ed76b38f04f514e4da3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.graphics;

import com.android.SdkConstants;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.ide.common.rendering.api.ResourceNamespace;
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
import com.android.layoutlib.bridge.android.RenderParamsFlags;
import com.android.layoutlib.bridge.impl.DelegateManager;
import com.android.layoutlib.bridge.impl.RenderAction;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.FontResourcesParser;
import android.graphics.FontFamily_Delegate.FontVariant;
import android.graphics.fonts.FontFamily_Builder_Delegate;
import android.graphics.fonts.FontVariationAxis;

import java.awt.Font;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Spliterator;
import java.util.Spliterators;

import libcore.util.NativeAllocationRegistry_Delegate;

/**
 * Delegate implementing the native methods of android.graphics.Typeface
 * <p>
 * Through the layoutlib_create tool, the original native methods of Typeface have been replaced by
 * calls to methods of the same name in this delegate class.
 * <p>
 * This class behaves like the original native implementation, but in Java, keeping previously
 * native data into its own objects and mapping them to int that are sent back and forth between it
 * and the original Typeface class.
 *
 * @see DelegateManager
 */
public final class Typeface_Delegate {

    public static final String SYSTEM_FONTS = "/system/fonts/";

    public static final Map<String, FontFamily_Delegate[]> sGenericNativeFamilies = new HashMap<>();

    // ---- delegate manager ----
    private static final DelegateManager<Typeface_Delegate> sManager =
            new DelegateManager<>(Typeface_Delegate.class);
    private static long sFinalizer = -1;

    // ---- delegate data ----
    private static long sDefaultTypeface;
    @NonNull
    private final FontFamily_Delegate[] mFontFamilies;  // the reference to FontFamily_Delegate.
    @NonNull
    private final FontFamily_Builder_Delegate[] mFontFamilyBuilders;  // the reference to
    // FontFamily_Builder_Delegate.
    /** @see Font#getStyle() */
    private final int mStyle;
    private final int mWeight;


    // ---- Public Helper methods ----

    private Typeface_Delegate(@NonNull FontFamily_Delegate[] fontFamilies,
            @NonNull FontFamily_Builder_Delegate[] fontFamilyBuilders, int style,
            int weight) {
        mFontFamilies = fontFamilies;
        mFontFamilyBuilders = fontFamilyBuilders;
        mStyle = style;
        mWeight = weight;
    }

    public static Typeface_Delegate getDelegate(long nativeTypeface) {
        return sManager.getDelegate(nativeTypeface);
    }

    // ---- native methods ----

    @LayoutlibDelegate
    /*package*/ static synchronized long nativeCreateFromTypeface(long native_instance, int style) {
        Typeface_Delegate delegate = sManager.getDelegate(native_instance);
        if (delegate == null) {
            delegate = sManager.getDelegate(sDefaultTypeface);
        }
        if (delegate == null) {
            return 0;
        }

        return sManager.addNewDelegate(
                new Typeface_Delegate(delegate.mFontFamilies, delegate.mFontFamilyBuilders, style,
                        delegate.mWeight));
    }

    @LayoutlibDelegate
    /*package*/ static long nativeCreateFromTypefaceWithExactStyle(long native_instance, int weight,
            boolean italic) {
        Typeface_Delegate delegate = sManager.getDelegate(native_instance);
        if (delegate == null) {
            delegate = sManager.getDelegate(sDefaultTypeface);
        }
        if (delegate == null) {
            return 0;
        }

        int style = weight >= 600 ? (italic ? Typeface.BOLD_ITALIC : Typeface.BOLD) :
                (italic ? Typeface.ITALIC : Typeface.NORMAL);
        return sManager.addNewDelegate(
                new Typeface_Delegate(delegate.mFontFamilies, delegate.mFontFamilyBuilders, style,
                        weight));
    }

    @LayoutlibDelegate
    /*package*/ static synchronized long nativeCreateFromTypefaceWithVariation(long native_instance,
            List<FontVariationAxis> axes) {
        long newInstance = nativeCreateFromTypeface(native_instance, 0);

        if (newInstance != 0) {
            Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
                    "nativeCreateFromTypefaceWithVariation is not supported", null, null, null);
        }
        return newInstance;
    }

    @LayoutlibDelegate
    /*package*/ static synchronized int[] nativeGetSupportedAxes(long native_instance) {
        // nativeCreateFromTypefaceWithVariation is not supported so we do not keep the axes
        return null;
    }

    @LayoutlibDelegate
    /*package*/ static long nativeCreateWeightAlias(long native_instance, int weight) {
        Typeface_Delegate delegate = sManager.getDelegate(native_instance);
        if (delegate == null) {
            delegate = sManager.getDelegate(sDefaultTypeface);
        }
        if (delegate == null) {
            return 0;
        }
        Typeface_Delegate weightAlias =
                new Typeface_Delegate(delegate.mFontFamilies, delegate.mFontFamilyBuilders,
                        delegate.mStyle,
                        weight);
        return sManager.addNewDelegate(weightAlias);
    }

    @LayoutlibDelegate
    /*package*/ static synchronized long nativeCreateFromArray(long[] familyArray, int weight,
            int italic) {
        List<FontFamily_Delegate> fontFamilies = new ArrayList<>();
        List<FontFamily_Builder_Delegate> fontFamilyBuilders = new ArrayList<>();
        for (long aFamilyArray : familyArray) {
            try {
                fontFamilies.add(FontFamily_Delegate.getDelegate(aFamilyArray));
            } catch (ClassCastException e) {
                fontFamilyBuilders.add(FontFamily_Builder_Delegate.getDelegate(aFamilyArray));
            }
        }
        if (weight == Typeface.RESOLVE_BY_FONT_TABLE) {
            weight = 400;
        }
        if (italic == Typeface.RESOLVE_BY_FONT_TABLE) {
            italic = 0;
        }
        int style = weight >= 600 ? (italic == 1 ? Typeface.BOLD_ITALIC : Typeface.BOLD) :
                (italic == 1 ? Typeface.ITALIC : Typeface.NORMAL);
        Typeface_Delegate delegate =
                new Typeface_Delegate(fontFamilies.toArray(new FontFamily_Delegate[0]),
                fontFamilyBuilders.toArray(new FontFamily_Builder_Delegate[0]),
                style, weight);
        return sManager.addNewDelegate(delegate);
    }

    @LayoutlibDelegate
    /*package*/ static long nativeGetReleaseFunc() {
        synchronized (Typeface_Delegate.class) {
            if (sFinalizer == -1) {
                sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(
                        sManager::removeJavaReferenceFor);
            }
        }
        return sFinalizer;
    }

    @LayoutlibDelegate
    /*package*/ static int nativeGetStyle(long native_instance) {
        Typeface_Delegate delegate = sManager.getDelegate(native_instance);
        if (delegate == null) {
            return 0;
        }

        return delegate.mStyle;
    }

    @LayoutlibDelegate
    /*package*/ static void nativeSetDefault(long native_instance) {
        sDefaultTypeface = native_instance;
    }

    @LayoutlibDelegate
    /*package*/ static int nativeGetWeight(long native_instance) {
        Typeface_Delegate delegate = sManager.getDelegate(native_instance);
        if (delegate == null) {
            return 0;
        }
        return delegate.mWeight;
    }

    /**
     * Loads a single font or font family from disk
     */
    @Nullable
    public static Typeface createFromDisk(@NonNull BridgeContext context, @NonNull String path,
            boolean isFramework) {
        // Check if this is an asset that we've already loaded dynamically
        Typeface typeface = Typeface.findFromCache(context.getAssets(), path);
        if (typeface != null) {
            return typeface;
        }

        String lowerCaseValue = path.toLowerCase();
        if (lowerCaseValue.endsWith(SdkConstants.DOT_XML)) {
            // create a block parser for the file
            Boolean psiParserSupport = context.getLayoutlibCallback().getFlag(
                    RenderParamsFlags.FLAG_KEY_XML_FILE_PARSER_SUPPORT);
            XmlPullParser parser;
            if (psiParserSupport != null && psiParserSupport) {
                parser = context.getLayoutlibCallback().createXmlParserForPsiFile(path);
            } else {
                parser = context.getLayoutlibCallback().createXmlParserForFile(path);
            }

            if (parser != null) {
                // TODO(b/156609434): The aapt namespace should not matter for parsing font files?
                BridgeXmlBlockParser blockParser =
                        new BridgeXmlBlockParser(
                                parser, context, ResourceNamespace.fromBoolean(isFramework));
                try {
                    FontResourcesParser.FamilyResourceEntry entry =
                            FontResourcesParser.parse(blockParser, context.getResources());
                    typeface = Typeface.createFromResources(entry, context.getAssets(), path);
                } catch (XmlPullParserException | IOException e) {
                    Bridge.getLog().error(null, "Failed to parse file " + path, e, null, null /*data
                    */);
                } finally {
                    blockParser.ensurePopped();
                }
            } else {
                Bridge.getLog().error(LayoutLog.TAG_BROKEN,
                        String.format("File %s does not exist (or is not a file)", path),
                        null, null /*data*/);
            }
        } else {
            typeface = new Typeface.Builder(context.getAssets(), path, false, 0).build();
        }

        return typeface;
    }

    @LayoutlibDelegate
    /*package*/ static Typeface create(String familyName, int style) {
        if (familyName != null && Files.exists(Paths.get(familyName))) {
            // Workaround for b/64137851
            // Support lib will call this method after failing to create the TypefaceCompat.
            return Typeface_Delegate.createFromDisk(RenderAction.getCurrentContext(), familyName,
                    false);
        }
        return Typeface.create_Original(familyName, style);
    }

    @LayoutlibDelegate
    /*package*/ static Typeface create(Typeface family, int style) {
        return Typeface.create_Original(family, style);
    }

    @LayoutlibDelegate
    /*package*/ static Typeface create(Typeface family, int style, boolean isItalic) {
        return Typeface.create_Original(family, style, isItalic);
    }

    @LayoutlibDelegate
    /*package*/ static void nativeRegisterGenericFamily(String str, long nativePtr) {
        Typeface_Delegate delegate = sManager.getDelegate(nativePtr);
        if (delegate == null) {
            return;
        }
        sGenericNativeFamilies.put(str, delegate.mFontFamilies);
    }

    // ---- Private delegate/helper methods ----

    /**
     * Return an Iterable of fonts that match the style and variant. The list is ordered
     * according to preference of fonts.
     * <p>
     * The Iterator may contain null when the font failed to load. If null is reached when trying to
     * render with this list of fonts, then a warning should be logged letting the user know that
     * some font failed to load.
     *
     * @param variant The variant preferred. Can only be {@link FontVariant#COMPACT} or {@link
     * FontVariant#ELEGANT}
     */
    @NonNull
    public Iterable<Font> getFonts(final FontVariant variant) {
        assert variant != FontVariant.NONE;

        return new FontsIterator(mFontFamilies, mFontFamilyBuilders, variant, mWeight, mStyle);
    }

    private static class FontsIterator implements Iterator<Font>, Iterable<Font> {
        private final FontFamily_Delegate[] fontFamilies;
        private final FontFamily_Builder_Delegate[] fontFamilyBuilders;
        private final int weight;
        private final boolean isItalic;
        private final FontVariant variant;

        private int index = 0;

        private FontsIterator(@NonNull FontFamily_Delegate[] fontFamilies,
                @NonNull FontFamily_Builder_Delegate[] fontFamilyBuilders,
                @NonNull FontVariant variant, int weight, int style) {
            // Calculate the required weight based on style and weight of this typeface.
            int boldExtraWeight =
                    ((style & Font.BOLD) == 0 ? 0 : FontFamily_Delegate.BOLD_FONT_WEIGHT_DELTA);
            this.weight = Math.min(Math.max(100, weight + 50 + boldExtraWeight), 1000);
            this.isItalic = (style & Font.ITALIC) != 0;
            this.fontFamilies = fontFamilies;
            this.fontFamilyBuilders = fontFamilyBuilders;
            this.variant = variant;
        }

        @Override
        public boolean hasNext() {
            return index < (fontFamilies.length + fontFamilyBuilders.length);
        }

        @Override
        @Nullable
        public Font next() {
            Font font;
            FontVariant ffdVariant;
            if (index < fontFamilies.length) {
                FontFamily_Delegate ffd = fontFamilies[index++];
                if (ffd == null || !ffd.isValid()) {
                    return null;
                }
                font = ffd.getFont(weight, isItalic);
                ffdVariant = ffd.getVariant();
            } else {
                FontFamily_Builder_Delegate ffd = fontFamilyBuilders[index++ - fontFamilies.length];
                if (ffd == null) {
                    return null;
                }
                font = ffd.getFont(weight, isItalic);
                ffdVariant = ffd.getVariant();
            }

            if (font == null) {
                // The FontFamily is valid but doesn't contain any matching font. This means
                // that the font failed to load. We add null to the list of fonts. Don't throw
                // the warning just yet. If this is a non-english font, we don't want to warn
                // users who are trying to render only english text.
                return null;
            }

            if (ffdVariant == FontVariant.NONE || ffdVariant == variant) {
                return font;
            }

            // We cannot open each font and get locales supported, etc to match the fonts.
            // As a workaround, we hardcode certain assumptions like Elegant and Compact
            // always appear in pairs.
            if (index < fontFamilies.length) {
                assert index < fontFamilies.length - 1;
                FontFamily_Delegate ffd2 = fontFamilies[index++];
                assert ffd2 != null;

                return ffd2.getFont(weight, isItalic);
            } else {
                assert index < fontFamilies.length + fontFamilyBuilders.length - 1;
                FontFamily_Builder_Delegate ffd2 = fontFamilyBuilders[index++ - fontFamilies.length];
                assert ffd2 != null;

                return ffd2.getFont(weight, isItalic);
            }
        }

        @NonNull
        @Override
        public Iterator<Font> iterator() {
            return this;
        }

        @Override
        public Spliterator<Font> spliterator() {
            return Spliterators.spliterator(iterator(),
                    fontFamilies.length + fontFamilyBuilders.length,
                    Spliterator.IMMUTABLE | Spliterator.SIZED);
        }
    }
}