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