blob: 1f7277a7e98dbd711f4333c93c06337605b065ce [file] [log] [blame]
Anish Athalye88b5b0b2014-06-24 14:39:43 -07001/*
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"
Steven Moreland2279b252017-07-19 09:50:45 -070024#include <nativehelper/ScopedStringChars.h>
25#include <nativehelper/ScopedPrimitiveArray.h>
26#include <nativehelper/JNIHelp.h>
Andreas Gampeed6b9df2014-11-20 22:02:20 -080027#include "core_jni_helpers.h"
Anish Athalyec8f9e622014-07-21 15:26:34 -070028#include <cstdint>
Anish Athalye88b5b0b2014-06-24 14:39:43 -070029#include <vector>
Anish Athalyec8f9e622014-07-21 15:26:34 -070030#include <list>
31#include <algorithm>
Anish Athalye88b5b0b2014-06-24 14:39:43 -070032
Raph Levien70616ec2015-03-04 10:41:30 -080033#include "SkPaint.h"
34#include "SkTypeface.h"
sergeyvdccca442016-03-21 15:38:21 -070035#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>
Raph Levien4c1f12e2015-03-02 16:29:23 -080041
Anish Athalye88b5b0b2014-06-24 14:39:43 -070042namespace android {
43
Anish Athalyec8f9e622014-07-21 15:26:34 -070044struct JLineBreaksID {
45 jfieldID breaks;
46 jfieldID widths;
Roozbeh Pournader737dfea2017-08-10 11:32:24 -070047 jfieldID ascents;
48 jfieldID descents;
Anish Athalyec8f9e622014-07-21 15:26:34 -070049 jfieldID flags;
50};
51
52static jclass gLineBreaks_class;
53static JLineBreaksID gLineBreaks_fieldID;
54
Seigo Nonaka749e57e2017-08-29 15:00:05 -070055class JNILineBreakerLineWidth : public minikin::LineBreaker::LineWidthDelegate {
56 public:
57 JNILineBreakerLineWidth(float firstWidth, int32_t firstLineCount, float restWidth,
Roozbeh Pournader0bba7422017-09-28 12:35:10 -070058 std::vector<float>&& indents, std::vector<float>&& leftPaddings,
59 std::vector<float>&& rightPaddings, int32_t indentsAndPaddingsOffset)
Seigo Nonaka749e57e2017-08-29 15:00:05 -070060 : mFirstWidth(firstWidth), mFirstLineCount(firstLineCount), mRestWidth(restWidth),
Roozbeh Pournader0bba7422017-09-28 12:35:10 -070061 mIndents(std::move(indents)), mLeftPaddings(std::move(leftPaddings)),
62 mRightPaddings(std::move(rightPaddings)), mOffset(indentsAndPaddingsOffset) {}
Seigo Nonaka749e57e2017-08-29 15:00:05 -070063
64 float getLineWidth(size_t lineNo) override {
65 const float width = ((ssize_t)lineNo < (ssize_t)mFirstLineCount)
66 ? mFirstWidth : mRestWidth;
Roozbeh Pournader0bba7422017-09-28 12:35:10 -070067 return width - get(mIndents, lineNo);
68 }
Seigo Nonaka749e57e2017-08-29 15:00:05 -070069
Roozbeh Pournader0bba7422017-09-28 12:35:10 -070070 float getLeftPadding(size_t lineNo) override {
71 return get(mLeftPaddings, lineNo);
72 }
73
74 float getRightPadding(size_t lineNo) override {
75 return get(mRightPaddings, lineNo);
Seigo Nonaka749e57e2017-08-29 15:00:05 -070076 }
77
78 private:
Roozbeh Pournader0bba7422017-09-28 12:35:10 -070079 float get(const std::vector<float>& vec, size_t lineNo) {
80 if (vec.empty()) {
81 return 0;
82 }
83 const size_t index = lineNo + mOffset;
84 if (index < vec.size()) {
85 return vec[index];
86 } else {
87 return vec.back();
88 }
89 }
90
Seigo Nonaka749e57e2017-08-29 15:00:05 -070091 const float mFirstWidth;
92 const int32_t mFirstLineCount;
93 const float mRestWidth;
94 const std::vector<float> mIndents;
Roozbeh Pournader0bba7422017-09-28 12:35:10 -070095 const std::vector<float> mLeftPaddings;
96 const std::vector<float> mRightPaddings;
97 const int32_t mOffset;
Seigo Nonaka749e57e2017-08-29 15:00:05 -070098};
99
Roozbeh Pournader0bba7422017-09-28 12:35:10 -0700100static inline std::vector<float> jintArrayToFloatVector(JNIEnv* env, jintArray javaArray) {
101 if (javaArray == nullptr) {
102 return std::vector<float>();
103 } else {
104 ScopedIntArrayRO intArr(env, javaArray);
105 return std::vector<float>(intArr.get(), intArr.get() + intArr.size());
106 }
107}
108
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700109// set text and set a number of parameters for creating a layout (width, tabstops, strategy,
110// hyphenFrequency)
Raph Levienc94f7422015-03-06 19:19:48 -0800111static void nSetupParagraph(JNIEnv* env, jclass, jlong nativePtr, jcharArray text, jint length,
112 jfloat firstWidth, jint firstWidthLineLimit, jfloat restWidth,
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900113 jintArray variableTabStops, jint defaultTabStop, jint strategy, jint hyphenFrequency,
Roozbeh Pournader9a9179d2017-08-29 14:14:28 -0700114 jboolean isJustified, jintArray indents, jintArray leftPaddings, jintArray rightPaddings,
Roozbeh Pournader0bba7422017-09-28 12:35:10 -0700115 jint indentsAndPaddingsOffset) {
Seigo Nonakaae1aa852016-06-09 19:42:51 +0900116 minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr);
Raph Levienc94f7422015-03-06 19:19:48 -0800117 b->resize(length);
118 env->GetCharArrayRegion(text, 0, length, b->buffer());
119 b->setText();
Raph Levienc94f7422015-03-06 19:19:48 -0800120 if (variableTabStops == nullptr) {
121 b->setTabStops(nullptr, 0, defaultTabStop);
122 } else {
123 ScopedIntArrayRO stops(env, variableTabStops);
124 b->setTabStops(stops.get(), stops.size(), defaultTabStop);
125 }
Seigo Nonakaae1aa852016-06-09 19:42:51 +0900126 b->setStrategy(static_cast<minikin::BreakStrategy>(strategy));
127 b->setHyphenationFrequency(static_cast<minikin::HyphenationFrequency>(hyphenFrequency));
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900128 b->setJustified(isJustified);
Seigo Nonakabafe1972017-08-24 15:30:29 -0700129
Roozbeh Pournader0bba7422017-09-28 12:35:10 -0700130 // TODO: copy indents and paddings only once when LineBreaker is started to be used.
Seigo Nonaka749e57e2017-08-29 15:00:05 -0700131 b->setLineWidthDelegate(std::make_unique<JNILineBreakerLineWidth>(
Roozbeh Pournader0bba7422017-09-28 12:35:10 -0700132 firstWidth, firstWidthLineLimit, restWidth, jintArrayToFloatVector(env, indents),
133 jintArrayToFloatVector(env, leftPaddings), jintArrayToFloatVector(env, rightPaddings),
134 indentsAndPaddingsOffset));
Raph Levienc94f7422015-03-06 19:19:48 -0800135}
Anish Athalyec8f9e622014-07-21 15:26:34 -0700136
Raph Levienc94f7422015-03-06 19:19:48 -0800137static void recycleCopy(JNIEnv* env, jobject recycle, jintArray recycleBreaks,
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700138 jfloatArray recycleWidths, jfloatArray recycleAscents,
139 jfloatArray recycleDescents, jintArray recycleFlags,
Raph Levienc94f7422015-03-06 19:19:48 -0800140 jint recycleLength, size_t nBreaks, const jint* breaks,
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700141 const jfloat* widths, const jfloat* ascents, const jfloat* descents,
142 const jint* flags) {
Raph Levienc94f7422015-03-06 19:19:48 -0800143 if ((size_t)recycleLength < nBreaks) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700144 // have to reallocate buffers
Raph Levienc94f7422015-03-06 19:19:48 -0800145 recycleBreaks = env->NewIntArray(nBreaks);
146 recycleWidths = env->NewFloatArray(nBreaks);
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700147 recycleAscents = env->NewFloatArray(nBreaks);
148 recycleDescents = env->NewFloatArray(nBreaks);
Raph Levien26d443a2015-03-30 14:18:32 -0700149 recycleFlags = env->NewIntArray(nBreaks);
Anish Athalyec8f9e622014-07-21 15:26:34 -0700150
151 env->SetObjectField(recycle, gLineBreaks_fieldID.breaks, recycleBreaks);
152 env->SetObjectField(recycle, gLineBreaks_fieldID.widths, recycleWidths);
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700153 env->SetObjectField(recycle, gLineBreaks_fieldID.ascents, recycleAscents);
154 env->SetObjectField(recycle, gLineBreaks_fieldID.descents, recycleDescents);
Anish Athalyec8f9e622014-07-21 15:26:34 -0700155 env->SetObjectField(recycle, gLineBreaks_fieldID.flags, recycleFlags);
156 }
157 // copy data
Raph Levienc94f7422015-03-06 19:19:48 -0800158 env->SetIntArrayRegion(recycleBreaks, 0, nBreaks, breaks);
159 env->SetFloatArrayRegion(recycleWidths, 0, nBreaks, widths);
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700160 env->SetFloatArrayRegion(recycleAscents, 0, nBreaks, ascents);
161 env->SetFloatArrayRegion(recycleDescents, 0, nBreaks, descents);
Raph Levien26d443a2015-03-30 14:18:32 -0700162 env->SetIntArrayRegion(recycleFlags, 0, nBreaks, flags);
Raph Levien70616ec2015-03-04 10:41:30 -0800163}
164
Raph Levien4c1f12e2015-03-02 16:29:23 -0800165static jint nComputeLineBreaks(JNIEnv* env, jclass, jlong nativePtr,
Anish Athalyec8f9e622014-07-21 15:26:34 -0700166 jobject recycle, jintArray recycleBreaks,
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700167 jfloatArray recycleWidths, jfloatArray recycleAscents,
168 jfloatArray recycleDescents, jintArray recycleFlags,
Seigo Nonaka2dd1a552017-10-06 11:41:00 -0700169 jint recycleLength, jfloatArray charWidths) {
Seigo Nonakaae1aa852016-06-09 19:42:51 +0900170 minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr);
Raph Levien4c1f12e2015-03-02 16:29:23 -0800171
Raph Levienc94f7422015-03-06 19:19:48 -0800172 size_t nBreaks = b->computeBreaks();
Anish Athalye88b5b0b2014-06-24 14:39:43 -0700173
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700174 recycleCopy(env, recycle, recycleBreaks, recycleWidths, recycleAscents, recycleDescents,
175 recycleFlags, recycleLength, nBreaks, b->getBreaks(), b->getWidths(), b->getAscents(),
176 b->getDescents(), b->getFlags());
Anish Athalye88b5b0b2014-06-24 14:39:43 -0700177
Seigo Nonaka2dd1a552017-10-06 11:41:00 -0700178 env->SetFloatArrayRegion(charWidths, 0, b->size(), b->charWidths());
179
Raph Levien4c1f12e2015-03-02 16:29:23 -0800180 b->finish();
Anish Athalye88b5b0b2014-06-24 14:39:43 -0700181
Raph Levienc94f7422015-03-06 19:19:48 -0800182 return static_cast<jint>(nBreaks);
Anish Athalye88b5b0b2014-06-24 14:39:43 -0700183}
184
Raph Levien4c1f12e2015-03-02 16:29:23 -0800185static jlong nNewBuilder(JNIEnv*, jclass) {
Seigo Nonakaae1aa852016-06-09 19:42:51 +0900186 return reinterpret_cast<jlong>(new minikin::LineBreaker);
Raph Levien4c1f12e2015-03-02 16:29:23 -0800187}
188
189static void nFreeBuilder(JNIEnv*, jclass, jlong nativePtr) {
Seigo Nonakaae1aa852016-06-09 19:42:51 +0900190 delete reinterpret_cast<minikin::LineBreaker*>(nativePtr);
Raph Levien4c1f12e2015-03-02 16:29:23 -0800191}
192
193static void nFinishBuilder(JNIEnv*, jclass, jlong nativePtr) {
Seigo Nonakaae1aa852016-06-09 19:42:51 +0900194 minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr);
Raph Levien4c1f12e2015-03-02 16:29:23 -0800195 b->finish();
196}
197
Seigo Nonaka1bee3332017-09-27 18:37:37 -0700198class ScopedNullableUtfString {
199public:
200 ScopedNullableUtfString(JNIEnv* env, jstring s) : mEnv(env), mStr(s) {
201 if (s == nullptr) {
202 mUtf8Chars = nullptr;
203 } else {
204 mUtf8Chars = mEnv->GetStringUTFChars(s, nullptr);
205 }
206 }
207
208 ~ScopedNullableUtfString() {
209 if (mUtf8Chars != nullptr) {
210 mEnv->ReleaseStringUTFChars(mStr, mUtf8Chars);
211 }
212 }
213
214 const char* get() const {
215 return mUtf8Chars;
216 }
217
218private:
219 JNIEnv* mEnv;
220 jstring mStr;
221 const char* mUtf8Chars;
222};
223
224static std::vector<minikin::Hyphenator*> makeHyphenators(JNIEnv* env, jlongArray hyphenators) {
225 std::vector<minikin::Hyphenator*> out;
226 if (hyphenators == nullptr) {
227 return out;
228 }
229 ScopedLongArrayRO longArray(env, hyphenators);
230 size_t size = longArray.size();
231 out.reserve(size);
232 for (size_t i = 0; i < size; i++) {
233 out.push_back(reinterpret_cast<minikin::Hyphenator*>(longArray[i]));
234 }
235 return out;
236}
237
Raph Levien70616ec2015-03-04 10:41:30 -0800238// Basically similar to Paint.getTextRunAdvances but with C++ interface
Seigo Nonaka5cc93482017-10-02 16:01:14 -0700239static void nAddStyleRun(JNIEnv* env, jclass, jlong nativePtr, jlong nativePaint, jint start,
Seigo Nonaka5d518dc2017-08-31 14:01:54 -0700240 jint end, jboolean isRtl, jstring langTags, jlongArray hyphenators) {
Seigo Nonakaae1aa852016-06-09 19:42:51 +0900241 minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr);
Raph Levien70616ec2015-03-04 10:41:30 -0800242 Paint* paint = reinterpret_cast<Paint*>(nativePaint);
Seigo Nonaka318ca042017-08-01 16:36:18 -0700243 const Typeface* typeface = paint->getAndroidTypeface();
Seigo Nonakaae1aa852016-06-09 19:42:51 +0900244 minikin::MinikinPaint minikinPaint;
Seigo Nonaka318ca042017-08-01 16:36:18 -0700245 const Typeface* resolvedTypeface = Typeface::resolveDefault(typeface);
Seigo Nonakac7064142017-02-10 16:53:31 +0900246 minikin::FontStyle style = MinikinUtils::prepareMinikinPaint(&minikinPaint, paint,
Seigo Nonakaae1aa852016-06-09 19:42:51 +0900247 typeface);
Seigo Nonaka5d518dc2017-08-31 14:01:54 -0700248
Seigo Nonaka1bee3332017-09-27 18:37:37 -0700249 ScopedNullableUtfString langTagsString(env, langTags);
Seigo Nonaka5cc93482017-10-02 16:01:14 -0700250 b->addStyleRun(&minikinPaint, resolvedTypeface->fFontCollection, style, start,
Seigo Nonaka1bee3332017-09-27 18:37:37 -0700251 end, isRtl, langTagsString.get(), makeHyphenators(env, hyphenators));
Raph Levien70616ec2015-03-04 10:41:30 -0800252}
253
Raph Levien70616ec2015-03-04 10:41:30 -0800254static void nAddReplacementRun(JNIEnv* env, jclass, jlong nativePtr,
Seigo Nonaka1bee3332017-09-27 18:37:37 -0700255 jint start, jint end, jfloat width, jstring langTags, jlongArray hyphenators) {
Seigo Nonakaae1aa852016-06-09 19:42:51 +0900256 minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr);
Seigo Nonaka1bee3332017-09-27 18:37:37 -0700257 ScopedNullableUtfString langTagsString(env, langTags);
258 b->addReplacement(start, end, width, langTagsString.get(), makeHyphenators(env, hyphenators));
Raph Levien70616ec2015-03-04 10:41:30 -0800259}
260
Daniel Micay76f6a862015-09-19 17:31:01 -0400261static const JNINativeMethod gMethods[] = {
Raph Levien70616ec2015-03-04 10:41:30 -0800262 // TODO performance: many of these are candidates for fast jni, awaiting guidance
Raph Levien4c1f12e2015-03-02 16:29:23 -0800263 {"nNewBuilder", "()J", (void*) nNewBuilder},
264 {"nFreeBuilder", "(J)V", (void*) nFreeBuilder},
265 {"nFinishBuilder", "(J)V", (void*) nFinishBuilder},
Roozbeh Pournader9a9179d2017-08-29 14:14:28 -0700266 {"nSetupParagraph", "(J[CIFIF[IIIIZ[I[I[II)V", (void*) nSetupParagraph},
Seigo Nonaka5cc93482017-10-02 16:01:14 -0700267 {"nAddStyleRun", "(JJIIZLjava/lang/String;[J)V", (void*) nAddStyleRun},
Seigo Nonaka1bee3332017-09-27 18:37:37 -0700268 {"nAddReplacementRun", "(JIIFLjava/lang/String;[J)V", (void*) nAddReplacementRun},
Seigo Nonaka2dd1a552017-10-06 11:41:00 -0700269 {"nComputeLineBreaks", "(JLandroid/text/StaticLayout$LineBreaks;[I[F[F[F[II[F)I",
Raph Levien4c1f12e2015-03-02 16:29:23 -0800270 (void*) nComputeLineBreaks}
Anish Athalye88b5b0b2014-06-24 14:39:43 -0700271};
272
273int register_android_text_StaticLayout(JNIEnv* env)
274{
Andreas Gampe158b6c92014-11-21 14:03:34 -0800275 gLineBreaks_class = MakeGlobalRefOrDie(env,
276 FindClassOrDie(env, "android/text/StaticLayout$LineBreaks"));
Anish Athalyec8f9e622014-07-21 15:26:34 -0700277
Andreas Gampe158b6c92014-11-21 14:03:34 -0800278 gLineBreaks_fieldID.breaks = GetFieldIDOrDie(env, gLineBreaks_class, "breaks", "[I");
279 gLineBreaks_fieldID.widths = GetFieldIDOrDie(env, gLineBreaks_class, "widths", "[F");
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700280 gLineBreaks_fieldID.ascents = GetFieldIDOrDie(env, gLineBreaks_class, "ascents", "[F");
281 gLineBreaks_fieldID.descents = GetFieldIDOrDie(env, gLineBreaks_class, "descents", "[F");
Raph Levien26d443a2015-03-30 14:18:32 -0700282 gLineBreaks_fieldID.flags = GetFieldIDOrDie(env, gLineBreaks_class, "flags", "[I");
Anish Athalyec8f9e622014-07-21 15:26:34 -0700283
Andreas Gampeed6b9df2014-11-20 22:02:20 -0800284 return RegisterMethodsOrDie(env, "android/text/StaticLayout", gMethods, NELEM(gMethods));
Anish Athalye88b5b0b2014-06-24 14:39:43 -0700285}
286
287}