Merge "Introduce FontFamily and its builder"
diff --git a/api/current.txt b/api/current.txt
index 7f4fae5..d70d1e7 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -15211,6 +15211,17 @@
     method public android.graphics.fonts.Font.Builder setWeight(int);
   }
 
+  public class FontFamily {
+    method public android.graphics.fonts.Font getFont(int);
+    method public int getFontCount();
+  }
+
+  public static class FontFamily.Builder {
+    ctor public FontFamily.Builder(android.graphics.fonts.Font);
+    method public android.graphics.fonts.FontFamily.Builder addFont(android.graphics.fonts.Font);
+    method public android.graphics.fonts.FontFamily build();
+  }
+
   public final class FontVariationAxis {
     ctor public FontVariationAxis(java.lang.String, float);
     method public static android.graphics.fonts.FontVariationAxis[] fromFontVariationSettings(java.lang.String);
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 6856e29..6316da5 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -155,6 +155,7 @@
         "android/graphics/Utils.cpp",
         "android/graphics/YuvToJpegEncoder.cpp",
         "android/graphics/fonts/Font.cpp",
+        "android/graphics/fonts/FontFamily.cpp",
         "android/graphics/pdf/PdfDocument.cpp",
         "android/graphics/pdf/PdfEditor.cpp",
         "android/graphics/pdf/PdfRenderer.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 1820888..7fe095b 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -141,6 +141,7 @@
 extern int register_android_graphics_drawable_AnimatedVectorDrawable(JNIEnv* env);
 extern int register_android_graphics_drawable_VectorDrawable(JNIEnv* env);
 extern int register_android_graphics_fonts_Font(JNIEnv* env);
+extern int register_android_graphics_fonts_FontFamily(JNIEnv* env);
 extern int register_android_graphics_pdf_PdfDocument(JNIEnv* env);
 extern int register_android_graphics_pdf_PdfEditor(JNIEnv* env);
 extern int register_android_graphics_pdf_PdfRenderer(JNIEnv* env);
@@ -1408,6 +1409,7 @@
     REG_JNI(register_android_graphics_drawable_AnimatedVectorDrawable),
     REG_JNI(register_android_graphics_drawable_VectorDrawable),
     REG_JNI(register_android_graphics_fonts_Font),
+    REG_JNI(register_android_graphics_fonts_FontFamily),
     REG_JNI(register_android_graphics_pdf_PdfDocument),
     REG_JNI(register_android_graphics_pdf_PdfEditor),
     REG_JNI(register_android_graphics_pdf_PdfRenderer),
diff --git a/core/jni/android/graphics/fonts/FontFamily.cpp b/core/jni/android/graphics/fonts/FontFamily.cpp
new file mode 100644
index 0000000..4597386
--- /dev/null
+++ b/core/jni/android/graphics/fonts/FontFamily.cpp
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#define LOG_TAG "Minikin"
+
+#include <nativehelper/JNIHelp.h>
+#include <core_jni_helpers.h>
+
+#include "FontUtils.h"
+
+#include <minikin/FontFamily.h>
+
+#include <memory>
+
+namespace android {
+
+struct NativeFamilyBuilder {
+    std::vector<minikin::Font> fonts;
+};
+
+static inline NativeFamilyBuilder* toBuilder(jlong ptr) {
+    return reinterpret_cast<NativeFamilyBuilder*>(ptr);
+}
+
+static inline FontWrapper* toFontWrapper(jlong ptr) {
+    return reinterpret_cast<FontWrapper*>(ptr);
+}
+
+static void releaseFontFamily(jlong family) {
+    delete reinterpret_cast<FontFamilyWrapper*>(family);
+}
+
+// Regular JNI
+static jlong FontFamily_Builder_initBuilder(JNIEnv*, jobject) {
+    return reinterpret_cast<jlong>(new NativeFamilyBuilder());
+}
+
+// Critical Native
+static void FontFamily_Builder_addFont(jlong builderPtr, jlong fontPtr) {
+    toBuilder(builderPtr)->fonts.push_back(toFontWrapper(fontPtr)->font);
+}
+
+// Regular JNI
+static jlong FontFamily_Builder_build(JNIEnv* env, jobject clazz, jlong builderPtr) {
+    std::unique_ptr<NativeFamilyBuilder> builder(toBuilder(builderPtr));
+    std::shared_ptr<minikin::FontFamily> family =
+            std::make_shared<minikin::FontFamily>(std::move(builder->fonts));
+    if (family->getCoverage().length() == 0) {
+        // No coverage means minikin rejected given font for some reasons.
+        jniThrowException(env, "java/lang/IllegalArgumentException",
+                          "Failed to create internal object. maybe invalid font data");
+        return 0;
+    }
+    return reinterpret_cast<jlong>(new FontFamilyWrapper(std::move(family)));
+}
+
+// CriticalNative
+static jlong FontFamily_Builder_GetReleaseFunc() {
+    return reinterpret_cast<jlong>(releaseFontFamily);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static const JNINativeMethod gFontFamilyBuilderMethods[] = {
+    { "nInitBuilder", "()J", (void*) FontFamily_Builder_initBuilder },
+    { "nAddFont", "(JJ)V", (void*) FontFamily_Builder_addFont },
+    { "nBuild", "(J)J", (void*) FontFamily_Builder_build },
+
+    { "nGetReleaseNativeFamily", "()J", (void*) FontFamily_Builder_GetReleaseFunc },
+};
+
+int register_android_graphics_fonts_FontFamily(JNIEnv* env) {
+    return RegisterMethodsOrDie(env, "android/graphics/fonts/FontFamily$Builder",
+            gFontFamilyBuilderMethods, NELEM(gFontFamilyBuilderMethods));
+}
+
+}
diff --git a/graphics/java/android/graphics/fonts/Font.java b/graphics/java/android/graphics/fonts/Font.java
index 9da61db9..9d94a64 100644
--- a/graphics/java/android/graphics/fonts/Font.java
+++ b/graphics/java/android/graphics/fonts/Font.java
@@ -469,4 +469,9 @@
     public long getNativePtr() {
         return mNativePtr;
     }
+
+    @Override
+    public String toString() {
+        return "Font {weight=" + mWeight + ", italic=" + mItalic + "}";
+    }
 }
diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java
new file mode 100644
index 0000000..74b58ea
--- /dev/null
+++ b/graphics/java/android/graphics/fonts/FontFamily.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2018 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.fonts;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+
+import com.android.internal.util.Preconditions;
+
+import dalvik.annotation.optimization.CriticalNative;
+
+import libcore.util.NativeAllocationRegistry;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+
+/**
+ * A font family class can be used for creating Typeface.
+ *
+ * <p>
+ * A font family is a bundle of fonts for drawing text in various styles.
+ * For example, you can bundle regular style font and bold style font into a single font family,
+ * then system will select the correct style font from family for drawing.
+ *
+ * <pre>
+ *  FontFamily family = new FontFamily.Builder(new Font.Builder("regular.ttf").build())
+ *      .addFont(new Font.Builder("bold.ttf").build()).build();
+ *  Typeface typeface = new Typeface.Builder2(family).build();
+ *
+ *  SpannableStringBuilder ssb = new SpannableStringBuilder("Hello, World.");
+ *  ssb.setSpan(new StyleSpan(Typeface.Bold), 6, 12, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ *
+ *  textView.setTypeface(typeface);
+ *  textView.setText(ssb);
+ * </pre>
+ *
+ * In this example, "Hello, " is drawn with "regular.ttf", and "World." is drawn with "bold.ttf".
+ *
+ * If there is no font exactly matches with the text style, the system will select the closest font.
+ * </p>
+ *
+ */
+public class FontFamily {
+    private static final String TAG = "FontFamily";
+
+    /**
+     * A builder class for creating new FontFamily.
+     */
+    public static class Builder {
+        private static final NativeAllocationRegistry sFamilyRegistory =
+                new NativeAllocationRegistry(FontFamily.class.getClassLoader(),
+                    nGetReleaseNativeFamily(), 64);
+
+        private final ArrayList<Font> mFonts = new ArrayList<>();
+        private final HashSet<Integer> mStyleHashSet = new HashSet<>();
+
+        /**
+         * Constructs a builder.
+         *
+         * @param font a font
+         */
+        public Builder(@NonNull Font font) {
+            Preconditions.checkNotNull(font, "font can not be null");
+            mStyleHashSet.add(makeStyleIdentifier(font));
+            mFonts.add(font);
+        }
+
+        /**
+         * Adds different style font to the builder.
+         *
+         * System will select the font if the text style is closest to the font.
+         * If the same style font is already added to the builder, this method will fail with
+         * {@link IllegalArgumentException}.
+         *
+         * Note that system assumes all fonts bundled in FontFamily have the same coverage for the
+         * code points. For example, regular style font and bold style font must have the same code
+         * point coverage, otherwise some character may be shown as tofu.
+         *
+         * @param font a font
+         * @return this builder
+         */
+        public @NonNull Builder addFont(@NonNull Font font) {
+            Preconditions.checkNotNull(font, "font can not be null");
+            if (!mStyleHashSet.add(makeStyleIdentifier(font))) {
+                throw new IllegalArgumentException(font + " has already been added");
+            }
+            mFonts.add(font);
+            return this;
+        }
+
+        /**
+         * Build the font family
+         * @return a font family
+         */
+        public @NonNull FontFamily build() {
+            final long builderPtr = nInitBuilder();
+            for (int i = 0; i < mFonts.size(); ++i) {
+                nAddFont(builderPtr, mFonts.get(i).getNativePtr());
+            }
+            final long ptr = nBuild(builderPtr);
+            final FontFamily family = new FontFamily(mFonts, ptr);
+            sFamilyRegistory.registerNativeAllocation(family, ptr);
+            return family;
+        }
+
+        private static int makeStyleIdentifier(@NonNull Font font) {
+            return font.getWeight() | (font.isItalic() ? (1 << 16) : 0);
+        }
+
+        private static native long nInitBuilder();
+        @CriticalNative
+        private static native void nAddFont(long builderPtr, long fontPtr);
+        private static native long nBuild(long builderPtr);
+        @CriticalNative
+        private static native long nGetReleaseNativeFamily();
+    }
+
+    private final ArrayList<Font> mFonts;
+    private final long mNativePtr;
+
+    // Use Builder instead.
+    private FontFamily(@NonNull ArrayList<Font> fonts, long ptr) {
+        mFonts = fonts;
+        mNativePtr = ptr;
+    }
+
+    /**
+     * Returns a font
+     *
+     * @param index an index of the font
+     * @return a registered font
+     */
+    public Font getFont(@IntRange(from = 0) int index) {
+        return mFonts.get(index);
+    }
+
+    /**
+     * Returns the number of fonts in this FontFamily.
+     *
+     * @return the number of fonts registered in this family.
+     */
+    public int getFontCount() {
+        return mFonts.size();
+    }
+
+    /** @hide */
+    public long getNativePtr() {
+        return mNativePtr;
+    }
+}