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
jintArrayToFloatVector(JNIEnv * env,jintArray javaArray)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
toNative(jlong ptr)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)
nInit(JNIEnv * env,jclass,jint breakStrategy,jint hyphenationFrequency,jboolean isJustified,jintArray indents,jintArray leftPaddings,jintArray rightPaddings)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
nFinish(jlong nativePtr)84 static void nFinish(jlong nativePtr) {
85 delete toNative(nativePtr);
86 }
87
recycleCopy(JNIEnv * env,jobject recycle,jintArray recycleBreaks,jfloatArray recycleWidths,jfloatArray recycleAscents,jfloatArray recycleDescents,jintArray recycleFlags,jint recycleLength,const minikin::LineBreakResult & result)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
nComputeLineBreaks(JNIEnv * env,jclass,jlong nativePtr,jcharArray javaText,jlong measuredTextPtr,jint length,jfloat firstWidth,jint firstWidthLineCount,jfloat restWidth,jintArray variableTabStops,jint defaultTabStop,jint indentsOffset,jobject recycle,jint recycleLength,jintArray recycleBreaks,jfloatArray recycleWidths,jfloatArray recycleAscents,jfloatArray recycleDescents,jintArray recycleFlags,jfloatArray charWidths)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
register_android_text_StaticLayout(JNIEnv * env)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