1/* 2 * Copyright (C) 2014 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#define LOG_TAG "StaticLayout" 18 19#include "ScopedIcuLocale.h" 20#include "unicode/locid.h" 21#include "unicode/brkiter.h" 22#include "utils/misc.h" 23#include "utils/Log.h" 24#include <nativehelper/ScopedStringChars.h> 25#include <nativehelper/ScopedPrimitiveArray.h> 26#include <nativehelper/JNIHelp.h> 27#include "core_jni_helpers.h" 28#include <cstdint> 29#include <vector> 30#include <list> 31#include <algorithm> 32 33#include "SkPaint.h" 34#include "SkTypeface.h" 35#include <hwui/MinikinSkia.h> 36#include <hwui/MinikinUtils.h> 37#include <hwui/Paint.h> 38#include <minikin/FontCollection.h> 39#include <minikin/LineBreaker.h> 40#include <minikin/MinikinFont.h> 41 42namespace android { 43 44struct JLineBreaksID { 45 jfieldID breaks; 46 jfieldID widths; 47 jfieldID flags; 48}; 49 50static jclass gLineBreaks_class; 51static JLineBreaksID gLineBreaks_fieldID; 52 53// set text and set a number of parameters for creating a layout (width, tabstops, strategy, 54// hyphenFrequency) 55static void nSetupParagraph(JNIEnv* env, jclass, jlong nativePtr, jcharArray text, jint length, 56 jfloat firstWidth, jint firstWidthLineLimit, jfloat restWidth, 57 jintArray variableTabStops, jint defaultTabStop, jint strategy, jint hyphenFrequency, 58 jboolean isJustified) { 59 minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr); 60 b->resize(length); 61 env->GetCharArrayRegion(text, 0, length, b->buffer()); 62 b->setText(); 63 b->setLineWidths(firstWidth, firstWidthLineLimit, restWidth); 64 if (variableTabStops == nullptr) { 65 b->setTabStops(nullptr, 0, defaultTabStop); 66 } else { 67 ScopedIntArrayRO stops(env, variableTabStops); 68 b->setTabStops(stops.get(), stops.size(), defaultTabStop); 69 } 70 b->setStrategy(static_cast<minikin::BreakStrategy>(strategy)); 71 b->setHyphenationFrequency(static_cast<minikin::HyphenationFrequency>(hyphenFrequency)); 72 b->setJustified(isJustified); 73} 74 75static void recycleCopy(JNIEnv* env, jobject recycle, jintArray recycleBreaks, 76 jfloatArray recycleWidths, jintArray recycleFlags, 77 jint recycleLength, size_t nBreaks, const jint* breaks, 78 const jfloat* widths, const jint* flags) { 79 if ((size_t)recycleLength < nBreaks) { 80 // have to reallocate buffers 81 recycleBreaks = env->NewIntArray(nBreaks); 82 recycleWidths = env->NewFloatArray(nBreaks); 83 recycleFlags = env->NewIntArray(nBreaks); 84 85 env->SetObjectField(recycle, gLineBreaks_fieldID.breaks, recycleBreaks); 86 env->SetObjectField(recycle, gLineBreaks_fieldID.widths, recycleWidths); 87 env->SetObjectField(recycle, gLineBreaks_fieldID.flags, recycleFlags); 88 } 89 // copy data 90 env->SetIntArrayRegion(recycleBreaks, 0, nBreaks, breaks); 91 env->SetFloatArrayRegion(recycleWidths, 0, nBreaks, widths); 92 env->SetIntArrayRegion(recycleFlags, 0, nBreaks, flags); 93} 94 95static jint nComputeLineBreaks(JNIEnv* env, jclass, jlong nativePtr, 96 jobject recycle, jintArray recycleBreaks, 97 jfloatArray recycleWidths, jintArray recycleFlags, 98 jint recycleLength) { 99 minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr); 100 101 size_t nBreaks = b->computeBreaks(); 102 103 recycleCopy(env, recycle, recycleBreaks, recycleWidths, recycleFlags, recycleLength, 104 nBreaks, b->getBreaks(), b->getWidths(), b->getFlags()); 105 106 b->finish(); 107 108 return static_cast<jint>(nBreaks); 109} 110 111static jlong nNewBuilder(JNIEnv*, jclass) { 112 return reinterpret_cast<jlong>(new minikin::LineBreaker); 113} 114 115static void nFreeBuilder(JNIEnv*, jclass, jlong nativePtr) { 116 delete reinterpret_cast<minikin::LineBreaker*>(nativePtr); 117} 118 119static void nFinishBuilder(JNIEnv*, jclass, jlong nativePtr) { 120 minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr); 121 b->finish(); 122} 123 124static jlong nLoadHyphenator(JNIEnv* env, jclass, jobject buffer, jint offset, 125 jint minPrefix, jint minSuffix) { 126 const uint8_t* bytebuf = nullptr; 127 if (buffer != nullptr) { 128 void* rawbuf = env->GetDirectBufferAddress(buffer); 129 if (rawbuf != nullptr) { 130 bytebuf = reinterpret_cast<const uint8_t*>(rawbuf) + offset; 131 } else { 132 ALOGE("failed to get direct buffer address"); 133 } 134 } 135 minikin::Hyphenator* hyphenator = minikin::Hyphenator::loadBinary( 136 bytebuf, minPrefix, minSuffix); 137 return reinterpret_cast<jlong>(hyphenator); 138} 139 140static void nSetLocales(JNIEnv* env, jclass, jlong nativePtr, jstring javaLocaleNames, 141 jlongArray nativeHyphenators) { 142 minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr); 143 144 ScopedUtfChars localeNames(env, javaLocaleNames); 145 ScopedLongArrayRO hyphArr(env, nativeHyphenators); 146 const size_t numLocales = hyphArr.size(); 147 std::vector<minikin::Hyphenator*> hyphVec; 148 hyphVec.reserve(numLocales); 149 for (size_t i = 0; i < numLocales; i++) { 150 hyphVec.push_back(reinterpret_cast<minikin::Hyphenator*>(hyphArr[i])); 151 } 152 b->setLocales(localeNames.c_str(), hyphVec); 153} 154 155static void nSetIndents(JNIEnv* env, jclass, jlong nativePtr, jintArray indents) { 156 ScopedIntArrayRO indentArr(env, indents); 157 std::vector<float> indentVec(indentArr.get(), indentArr.get() + indentArr.size()); 158 minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr); 159 b->setIndents(indentVec); 160} 161 162// Basically similar to Paint.getTextRunAdvances but with C++ interface 163static jfloat nAddStyleRun(JNIEnv* env, jclass, jlong nativePtr, 164 jlong nativePaint, jlong nativeTypeface, jint start, jint end, jboolean isRtl) { 165 minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr); 166 Paint* paint = reinterpret_cast<Paint*>(nativePaint); 167 Typeface* typeface = reinterpret_cast<Typeface*>(nativeTypeface); 168 minikin::MinikinPaint minikinPaint; 169 Typeface* resolvedTypeface = Typeface::resolveDefault(typeface); 170 minikin::FontStyle style = MinikinUtils::prepareMinikinPaint(&minikinPaint, paint, 171 typeface); 172 return b->addStyleRun(&minikinPaint, resolvedTypeface->fFontCollection, style, start, end, 173 isRtl); 174} 175 176// Accept width measurements for the run, passed in from Java 177static void nAddMeasuredRun(JNIEnv* env, jclass, jlong nativePtr, 178 jint start, jint end, jfloatArray widths) { 179 minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr); 180 env->GetFloatArrayRegion(widths, start, end - start, b->charWidths() + start); 181 b->addStyleRun(nullptr, nullptr, minikin::FontStyle{}, start, end, false); 182} 183 184static void nAddReplacementRun(JNIEnv* env, jclass, jlong nativePtr, 185 jint start, jint end, jfloat width) { 186 minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr); 187 b->addReplacement(start, end, width); 188} 189 190static void nGetWidths(JNIEnv* env, jclass, jlong nativePtr, jfloatArray widths) { 191 minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr); 192 env->SetFloatArrayRegion(widths, 0, b->size(), b->charWidths()); 193} 194 195static const JNINativeMethod gMethods[] = { 196 // TODO performance: many of these are candidates for fast jni, awaiting guidance 197 {"nNewBuilder", "()J", (void*) nNewBuilder}, 198 {"nFreeBuilder", "(J)V", (void*) nFreeBuilder}, 199 {"nFinishBuilder", "(J)V", (void*) nFinishBuilder}, 200 {"nLoadHyphenator", "(Ljava/nio/ByteBuffer;III)J", (void*) nLoadHyphenator}, 201 {"nSetLocales", "(JLjava/lang/String;[J)V", (void*) nSetLocales}, 202 {"nSetupParagraph", "(J[CIFIF[IIIIZ)V", (void*) nSetupParagraph}, 203 {"nSetIndents", "(J[I)V", (void*) nSetIndents}, 204 {"nAddStyleRun", "(JJJIIZ)F", (void*) nAddStyleRun}, 205 {"nAddMeasuredRun", "(JII[F)V", (void*) nAddMeasuredRun}, 206 {"nAddReplacementRun", "(JIIF)V", (void*) nAddReplacementRun}, 207 {"nGetWidths", "(J[F)V", (void*) nGetWidths}, 208 {"nComputeLineBreaks", "(JLandroid/text/StaticLayout$LineBreaks;[I[F[II)I", 209 (void*) nComputeLineBreaks} 210}; 211 212int register_android_text_StaticLayout(JNIEnv* env) 213{ 214 gLineBreaks_class = MakeGlobalRefOrDie(env, 215 FindClassOrDie(env, "android/text/StaticLayout$LineBreaks")); 216 217 gLineBreaks_fieldID.breaks = GetFieldIDOrDie(env, gLineBreaks_class, "breaks", "[I"); 218 gLineBreaks_fieldID.widths = GetFieldIDOrDie(env, gLineBreaks_class, "widths", "[F"); 219 gLineBreaks_fieldID.flags = GetFieldIDOrDie(env, gLineBreaks_class, "flags", "[I"); 220 221 return RegisterMethodsOrDie(env, "android/text/StaticLayout", gMethods, NELEM(gMethods)); 222} 223 224} 225