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 "scoped_nullable_primitive_array.h" 29 #include <cstdint> 30 #include <vector> 31 #include <list> 32 #include <algorithm> 33 34 #include "SkPaint.h" 35 #include "SkTypeface.h" 36 #include <hwui/MinikinSkia.h> 37 #include <hwui/MinikinUtils.h> 38 #include <hwui/Paint.h> 39 #include <minikin/FontCollection.h> 40 #include <minikin/AndroidLineBreakerHelper.h> 41 #include <minikin/MinikinFont.h> 42 43 namespace android { 44 45 struct JLineBreaksID { 46 jfieldID breaks; 47 jfieldID widths; 48 jfieldID ascents; 49 jfieldID descents; 50 jfieldID flags; 51 }; 52 53 static jclass gLineBreaks_class; 54 static JLineBreaksID gLineBreaks_fieldID; 55 56 static inline std::vector<float> jintArrayToFloatVector(JNIEnv* env, jintArray javaArray) { 57 if (javaArray == nullptr) { 58 return std::vector<float>(); 59 } else { 60 ScopedIntArrayRO intArr(env, javaArray); 61 return std::vector<float>(intArr.get(), intArr.get() + intArr.size()); 62 } 63 } 64 65 static inline minikin::android::StaticLayoutNative* toNative(jlong ptr) { 66 return reinterpret_cast<minikin::android::StaticLayoutNative*>(ptr); 67 } 68 69 // set text and set a number of parameters for creating a layout (width, tabstops, strategy, 70 // hyphenFrequency) 71 static jlong nInit(JNIEnv* env, jclass /* unused */, 72 jint breakStrategy, jint hyphenationFrequency, jboolean isJustified, 73 jintArray indents, jintArray leftPaddings, jintArray rightPaddings) { 74 return reinterpret_cast<jlong>(new minikin::android::StaticLayoutNative( 75 static_cast<minikin::BreakStrategy>(breakStrategy), 76 static_cast<minikin::HyphenationFrequency>(hyphenationFrequency), 77 isJustified, 78 jintArrayToFloatVector(env, indents), 79 jintArrayToFloatVector(env, leftPaddings), 80 jintArrayToFloatVector(env, rightPaddings))); 81 } 82 83 // CriticalNative 84 static void nFinish(jlong nativePtr) { 85 delete toNative(nativePtr); 86 } 87 88 static void recycleCopy(JNIEnv* env, jobject recycle, jintArray recycleBreaks, 89 jfloatArray recycleWidths, jfloatArray recycleAscents, 90 jfloatArray recycleDescents, jintArray recycleFlags, 91 jint recycleLength, const minikin::LineBreakResult& result) { 92 const size_t nBreaks = result.breakPoints.size(); 93 if ((size_t)recycleLength < nBreaks) { 94 // have to reallocate buffers 95 recycleBreaks = env->NewIntArray(nBreaks); 96 recycleWidths = env->NewFloatArray(nBreaks); 97 recycleAscents = env->NewFloatArray(nBreaks); 98 recycleDescents = env->NewFloatArray(nBreaks); 99 recycleFlags = env->NewIntArray(nBreaks); 100 101 env->SetObjectField(recycle, gLineBreaks_fieldID.breaks, recycleBreaks); 102 env->SetObjectField(recycle, gLineBreaks_fieldID.widths, recycleWidths); 103 env->SetObjectField(recycle, gLineBreaks_fieldID.ascents, recycleAscents); 104 env->SetObjectField(recycle, gLineBreaks_fieldID.descents, recycleDescents); 105 env->SetObjectField(recycle, gLineBreaks_fieldID.flags, recycleFlags); 106 } 107 // copy data 108 env->SetIntArrayRegion(recycleBreaks, 0, nBreaks, result.breakPoints.data()); 109 env->SetFloatArrayRegion(recycleWidths, 0, nBreaks, result.widths.data()); 110 env->SetFloatArrayRegion(recycleAscents, 0, nBreaks, result.ascents.data()); 111 env->SetFloatArrayRegion(recycleDescents, 0, nBreaks, result.descents.data()); 112 env->SetIntArrayRegion(recycleFlags, 0, nBreaks, result.flags.data()); 113 } 114 115 static jint nComputeLineBreaks(JNIEnv* env, jclass, jlong nativePtr, 116 // Inputs 117 jcharArray javaText, 118 jlong measuredTextPtr, 119 jint length, 120 jfloat firstWidth, 121 jint firstWidthLineCount, 122 jfloat restWidth, 123 jintArray variableTabStops, 124 jint defaultTabStop, 125 jint indentsOffset, 126 127 // Outputs 128 jobject recycle, 129 jint recycleLength, 130 jintArray recycleBreaks, 131 jfloatArray recycleWidths, 132 jfloatArray recycleAscents, 133 jfloatArray recycleDescents, 134 jintArray recycleFlags, 135 jfloatArray charWidths) { 136 137 minikin::android::StaticLayoutNative* builder = toNative(nativePtr); 138 139 ScopedCharArrayRO text(env, javaText); 140 ScopedNullableIntArrayRO tabStops(env, variableTabStops); 141 142 minikin::U16StringPiece u16Text(text.get(), length); 143 minikin::MeasuredText* measuredText = reinterpret_cast<minikin::MeasuredText*>(measuredTextPtr); 144 minikin::LineBreakResult result = builder->computeBreaks( 145 u16Text, *measuredText, firstWidth, firstWidthLineCount, restWidth, indentsOffset, 146 tabStops.get(), tabStops.size(), defaultTabStop); 147 148 recycleCopy(env, recycle, recycleBreaks, recycleWidths, recycleAscents, recycleDescents, 149 recycleFlags, recycleLength, result); 150 151 env->SetFloatArrayRegion(charWidths, 0, measuredText->widths.size(), 152 measuredText->widths.data()); 153 154 return static_cast<jint>(result.breakPoints.size()); 155 } 156 157 static const JNINativeMethod gMethods[] = { 158 // Fast Natives 159 {"nInit", "(" 160 "I" // breakStrategy 161 "I" // hyphenationFrequency 162 "Z" // isJustified 163 "[I" // indents 164 "[I" // left paddings 165 "[I" // right paddings 166 ")J", (void*) nInit}, 167 168 // Critical Natives 169 {"nFinish", "(J)V", (void*) nFinish}, 170 171 // Regular JNI 172 {"nComputeLineBreaks", "(" 173 "J" // nativePtr 174 175 // Inputs 176 "[C" // text 177 "J" // MeasuredParagraph ptr. 178 "I" // length 179 "F" // firstWidth 180 "I" // firstWidthLineCount 181 "F" // restWidth 182 "[I" // variableTabStops 183 "I" // defaultTabStop 184 "I" // indentsOffset 185 186 // Outputs 187 "Landroid/text/StaticLayout$LineBreaks;" // recycle 188 "I" // recycleLength 189 "[I" // recycleBreaks 190 "[F" // recycleWidths 191 "[F" // recycleAscents 192 "[F" // recycleDescents 193 "[I" // recycleFlags 194 "[F" // charWidths 195 ")I", (void*) nComputeLineBreaks} 196 }; 197 198 int register_android_text_StaticLayout(JNIEnv* env) 199 { 200 gLineBreaks_class = MakeGlobalRefOrDie(env, 201 FindClassOrDie(env, "android/text/StaticLayout$LineBreaks")); 202 203 gLineBreaks_fieldID.breaks = GetFieldIDOrDie(env, gLineBreaks_class, "breaks", "[I"); 204 gLineBreaks_fieldID.widths = GetFieldIDOrDie(env, gLineBreaks_class, "widths", "[F"); 205 gLineBreaks_fieldID.ascents = GetFieldIDOrDie(env, gLineBreaks_class, "ascents", "[F"); 206 gLineBreaks_fieldID.descents = GetFieldIDOrDie(env, gLineBreaks_class, "descents", "[F"); 207 gLineBreaks_fieldID.flags = GetFieldIDOrDie(env, gLineBreaks_class, "flags", "[I"); 208 209 return RegisterMethodsOrDie(env, "android/text/StaticLayout", gMethods, NELEM(gMethods)); 210 } 211 212 } 213