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