From 1ca390e9f6b27da2789d661716fe58d96d167407 Mon Sep 17 00:00:00 2001 From: Kohsuke Yatoh Date: Fri, 6 Nov 2020 16:21:30 -0800 Subject: [PATCH] Support serializing Java Typeface. Bug: 169871852 Test: atest FrameworksCoreTests:TypefaceTest Change-Id: I3b4315440e2ef6d45f0f9bf526b1980a974d9807 --- .../src/android/graphics/TypefaceTest.java | 27 ++++++ graphics/java/android/graphics/Typeface.java | 36 ++++++++ libs/hwui/hwui/Typeface.h | 7 +- libs/hwui/jni/Typeface.cpp | 87 +++++++++++++++++++ libs/hwui/jni/fonts/Font.cpp | 51 +++++++---- libs/hwui/jni/fonts/Font.h | 40 +++++++++ 6 files changed, 225 insertions(+), 23 deletions(-) create mode 100644 libs/hwui/jni/fonts/Font.h diff --git a/core/tests/coretests/src/android/graphics/TypefaceTest.java b/core/tests/coretests/src/android/graphics/TypefaceTest.java index 2d16f826b243d..cfed2cef9ce6f 100644 --- a/core/tests/coretests/src/android/graphics/TypefaceTest.java +++ b/core/tests/coretests/src/android/graphics/TypefaceTest.java @@ -17,6 +17,7 @@ package android.graphics; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import android.content.Context; @@ -34,6 +35,9 @@ import com.android.frameworks.coretests.R; import org.junit.Test; import org.junit.runner.RunWith; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.List; import java.util.Random; @RunWith(AndroidJUnit4.class) @@ -171,4 +175,27 @@ public class TypefaceTest { } + @SmallTest + @Test + public void testSerialize() throws Exception { + int size = Typeface.writeTypefaces(null, Arrays.asList(mFaces)); + ByteBuffer buffer = ByteBuffer.allocateDirect(size); + Typeface.writeTypefaces(buffer, Arrays.asList(mFaces)); + List copiedTypefaces = Typeface.readTypefaces(buffer); + assertNotNull(copiedTypefaces); + assertEquals(mFaces.length, copiedTypefaces.size()); + for (int i = 0; i < mFaces.length; i++) { + Typeface original = mFaces[i]; + Typeface copied = copiedTypefaces.get(i); + assertEquals(original.getStyle(), copied.getStyle()); + assertEquals(original.getWeight(), copied.getWeight()); + assertEquals(measureText(original, "hello"), measureText(copied, "hello"), 1e-6); + } + } + + private static float measureText(Typeface typeface, String text) { + Paint paint = new Paint(); + paint.setTypeface(typeface); + return paint.measureText(text); + } } diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 441c163226459..c58e5f36cde39 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -56,6 +56,7 @@ import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -1209,6 +1210,36 @@ public class Typeface { return Arrays.binarySearch(mSupportedAxes, axis) >= 0; } + /** + * Writes Typeface instances to the ByteBuffer and returns the number of bytes written. + * + *

If {@code buffer} is null, this method returns the number of bytes required to serialize + * the typefaces, without writing anything. + * @hide + */ + public static int writeTypefaces( + @Nullable ByteBuffer buffer, @NonNull List typefaces) { + long[] nativePtrs = new long[typefaces.size()]; + for (int i = 0; i < nativePtrs.length; i++) { + nativePtrs[i] = typefaces.get(i).native_instance; + } + return nativeWriteTypefaces(buffer, nativePtrs); + } + + /** + * Reads serialized Typeface instances from the ByteBuffer. Returns null on errors. + * @hide + */ + public static @Nullable List readTypefaces(@NonNull ByteBuffer buffer) { + long[] nativePtrs = nativeReadTypefaces(buffer); + if (nativePtrs == null) return null; + List typefaces = new ArrayList<>(nativePtrs.length); + for (long nativePtr : nativePtrs) { + typefaces.add(new Typeface(nativePtr)); + } + return typefaces; + } + private static native long nativeCreateFromTypeface(long native_instance, int style); private static native long nativeCreateFromTypefaceWithExactStyle( long native_instance, int weight, boolean italic); @@ -1234,4 +1265,9 @@ public class Typeface { private static native long nativeGetReleaseFunc(); private static native void nativeRegisterGenericFamily(String str, long nativePtr); + + private static native int nativeWriteTypefaces( + @Nullable ByteBuffer buffer, @NonNull long[] nativePtrs); + + private static native @Nullable long[] nativeReadTypefaces(@NonNull ByteBuffer buffer); } diff --git a/libs/hwui/hwui/Typeface.h b/libs/hwui/hwui/Typeface.h index ef8d8f4ee4f37..0c3ef01ab26b2 100644 --- a/libs/hwui/hwui/Typeface.h +++ b/libs/hwui/hwui/Typeface.h @@ -41,6 +41,9 @@ public: enum Style : uint8_t { kNormal = 0, kBold = 0x01, kItalic = 0x02, kBoldItalic = 0x03 }; Style fAPIStyle; + // base weight in CSS-style units, 1..1000 + int fBaseWeight; + static const Typeface* resolveDefault(const Typeface* src); // The following three functions create new Typeface from an existing Typeface with a different @@ -81,10 +84,6 @@ public: // Sets roboto font as the default typeface for testing purpose. static void setRobotoTypefaceForTest(); - -private: - // base weight in CSS-style units, 1..1000 - int fBaseWeight; }; } diff --git a/libs/hwui/jni/Typeface.cpp b/libs/hwui/jni/Typeface.cpp index 2a5f402a4fa6d..dc066da36cbea 100644 --- a/libs/hwui/jni/Typeface.cpp +++ b/libs/hwui/jni/Typeface.cpp @@ -16,10 +16,13 @@ #include "FontUtils.h" #include "GraphicsJNI.h" +#include "fonts/Font.h" #include #include +#include "SkData.h" #include "SkTypeface.h" #include +#include #include #include @@ -132,6 +135,88 @@ static void Typeface_registerGenericFamily(JNIEnv *env, jobject, jstring familyN toTypeface(ptr)->fFontCollection); } +static std::function()> readMinikinFontSkia( + minikin::BufferReader* reader) { + std::string_view fontPath = reader->readString(); + int fontIndex = reader->read(); + const minikin::FontVariation* axesPtr; + uint32_t axesCount; + std::tie(axesPtr, axesCount) = reader->readArray(); + return [fontPath, fontIndex, axesPtr, axesCount]() -> std::shared_ptr { + std::string path(fontPath.data(), fontPath.size()); + sk_sp data = SkData::MakeFromFileName(path.c_str()); + const void* fontPtr = data->data(); + size_t fontSize = data->size(); + std::vector axes(axesPtr, axesPtr + axesCount); + std::shared_ptr minikinFont = + fonts::createMinikinFontSkia(std::move(data), fontPath, fontPtr, fontSize, + fontIndex, axes); + if (minikinFont == nullptr) { + ALOGE("Failed to create MinikinFontSkia: %s", path.c_str()); + return nullptr; + } + return minikinFont; + }; +} + +static void writeMinikinFontSkia(minikin::BufferWriter* writer, + const minikin::MinikinFont* typeface) { + writer->writeString(typeface->GetFontPath()); + writer->write(typeface->GetFontIndex()); + const std::vector& axes = typeface->GetAxes(); + writer->writeArray(axes.data(), axes.size()); +} + +static jint Typeface_writeTypefaces(JNIEnv *env, jobject, jobject buffer, jlongArray faceHandles) { + ScopedLongArrayRO faces(env, faceHandles); + std::vector typefaces; + typefaces.reserve(faces.size()); + for (size_t i = 0; i < faces.size(); i++) { + typefaces.push_back(toTypeface(faces[i])); + } + void* addr = buffer == nullptr ? nullptr : env->GetDirectBufferAddress(buffer); + minikin::BufferWriter writer(addr); + std::vector> fontCollections; + std::unordered_map, size_t> fcToIndex; + for (Typeface* typeface : typefaces) { + bool inserted = fcToIndex.emplace(typeface->fFontCollection, fontCollections.size()).second; + if (inserted) { + fontCollections.push_back(typeface->fFontCollection); + } + } + minikin::FontCollection::writeVector(&writer, fontCollections); + writer.write(typefaces.size()); + for (Typeface* typeface : typefaces) { + writer.write(fcToIndex.find(typeface->fFontCollection)->second); + typeface->fStyle.writeTo(&writer); + writer.write(typeface->fAPIStyle); + writer.write(typeface->fBaseWeight); + } + return static_cast(writer.size()); +} + +static jlongArray Typeface_readTypefaces(JNIEnv *env, jobject, jobject buffer) { + void* addr = buffer == nullptr ? nullptr : env->GetDirectBufferAddress(buffer); + if (addr == nullptr) return nullptr; + minikin::BufferReader reader(addr); + std::vector> fontCollections = + minikin::FontCollection::readVector(&reader); + uint32_t typefaceCount = reader.read(); + std::vector faceHandles; + faceHandles.reserve(typefaceCount); + for (uint32_t i = 0; i < typefaceCount; i++) { + Typeface* typeface = new Typeface; + typeface->fFontCollection = fontCollections[reader.read()]; + typeface->fStyle = minikin::FontStyle(&reader); + typeface->fAPIStyle = reader.read(); + typeface->fBaseWeight = reader.read(); + faceHandles.push_back(toJLong(typeface)); + } + const jlongArray result = env->NewLongArray(typefaceCount); + env->SetLongArrayRegion(result, 0, typefaceCount, faceHandles.data()); + return result; +} + /////////////////////////////////////////////////////////////////////////////// static const JNINativeMethod gTypefaceMethods[] = { @@ -150,6 +235,8 @@ static const JNINativeMethod gTypefaceMethods[] = { { "nativeGetSupportedAxes", "(J)[I", (void*)Typeface_getSupportedAxes }, { "nativeRegisterGenericFamily", "(Ljava/lang/String;J)V", (void*)Typeface_registerGenericFamily }, + { "nativeWriteTypefaces", "(Ljava/nio/ByteBuffer;[J)I", (void*)Typeface_writeTypefaces}, + { "nativeReadTypefaces", "(Ljava/nio/ByteBuffer;)[J", (void*)Typeface_readTypefaces}, }; int register_android_graphics_Typeface(JNIEnv* env) diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp index 21fcd2ff375b9..f0c77930cbe3a 100644 --- a/libs/hwui/jni/fonts/Font.cpp +++ b/libs/hwui/jni/fonts/Font.cpp @@ -17,6 +17,7 @@ #undef LOG_TAG #define LOG_TAG "Minikin" +#include "Font.h" #include "SkData.h" #include "SkFont.h" #include "SkFontMetrics.h" @@ -95,29 +96,14 @@ static jlong Font_Builder_build(JNIEnv* env, jobject clazz, jlong builderPtr, jo jobject fontRef = MakeGlobalRefOrDie(env, buffer); sk_sp data(SkData::MakeWithProc(fontPtr, fontSize, release_global_ref, reinterpret_cast(fontRef))); - - FatVector skVariation; - for (const auto& axis : builder->axes) { - skVariation.push_back({axis.axisTag, axis.value}); - } - - std::unique_ptr fontData(new SkMemoryStream(std::move(data))); - - SkFontArguments args; - args.setCollectionIndex(ttcIndex); - args.setVariationDesignPosition({skVariation.data(), static_cast(skVariation.size())}); - - sk_sp fm(SkFontMgr::RefDefault()); - sk_sp face(fm->makeFromStream(std::move(fontData), args)); - if (face == nullptr) { + std::shared_ptr minikinFont = fonts::createMinikinFontSkia( + std::move(data), std::string_view(fontPath.c_str(), fontPath.size()), + fontPtr, fontSize, ttcIndex, builder->axes); + if (minikinFont == nullptr) { jniThrowException(env, "java/lang/IllegalArgumentException", "Failed to create internal object. maybe invalid font data."); return 0; } - std::shared_ptr minikinFont = - std::make_shared(std::move(face), fontPtr, fontSize, - std::string_view(fontPath.c_str(), fontPath.size()), - ttcIndex, builder->axes); std::shared_ptr font = minikin::Font::Builder(minikinFont).setWeight(weight) .setSlant(static_cast(italic)).build(); return reinterpret_cast(new FontWrapper(std::move(font))); @@ -312,4 +298,31 @@ int register_android_graphics_fonts_Font(JNIEnv* env) { gFontBufferHelperMethods, NELEM(gFontBufferHelperMethods)); } +namespace fonts { + +std::shared_ptr createMinikinFontSkia( + sk_sp&& data, std::string_view fontPath, const void *fontPtr, size_t fontSize, + int ttcIndex, const std::vector& axes) { + FatVector skVariation; + for (const auto& axis : axes) { + skVariation.push_back({axis.axisTag, axis.value}); + } + + std::unique_ptr fontData(new SkMemoryStream(std::move(data))); + + SkFontArguments args; + args.setCollectionIndex(ttcIndex); + args.setVariationDesignPosition({skVariation.data(), static_cast(skVariation.size())}); + + sk_sp fm(SkFontMgr::RefDefault()); + sk_sp face(fm->makeFromStream(std::move(fontData), args)); + if (face == nullptr) { + return nullptr; + } + return std::make_shared(std::move(face), fontPtr, fontSize, + fontPath, ttcIndex, axes); } + +} // namespace fonts + +} // namespace android diff --git a/libs/hwui/jni/fonts/Font.h b/libs/hwui/jni/fonts/Font.h new file mode 100644 index 0000000000000..b5d20bf8cc3c8 --- /dev/null +++ b/libs/hwui/jni/fonts/Font.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2020 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. + */ +#ifndef FONTS_FONT_H_ +#define FONTS_FONT_H_ + +#include +#include +#include + +#include +#include + +class SkData; + +namespace android { + +namespace fonts { + +std::shared_ptr createMinikinFontSkia( + sk_sp&& data, std::string_view fontPath, const void *fontPtr, size_t fontSize, + int ttcIndex, const std::vector& axes); + +} // namespace fonts + +} // namespace android + +#endif /* FONTS_FONT_H_ */