| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #define LOG_TAG "StaticLayout" |
| |
| #include "ScopedIcuLocale.h" |
| #include "unicode/locid.h" |
| #include "unicode/brkiter.h" |
| #include "utils/misc.h" |
| #include "utils/Log.h" |
| #include <nativehelper/ScopedStringChars.h> |
| #include <nativehelper/ScopedPrimitiveArray.h> |
| #include <nativehelper/JNIHelp.h> |
| #include "core_jni_helpers.h" |
| #include <cstdint> |
| #include <vector> |
| #include <list> |
| #include <algorithm> |
| |
| #include "SkPaint.h" |
| #include "SkTypeface.h" |
| #include <hwui/MinikinSkia.h> |
| #include <hwui/MinikinUtils.h> |
| #include <hwui/Paint.h> |
| #include <minikin/FontCollection.h> |
| #include <minikin/LineBreaker.h> |
| #include <minikin/MinikinFont.h> |
| |
| namespace android { |
| |
| struct JLineBreaksID { |
| jfieldID breaks; |
| jfieldID widths; |
| jfieldID ascents; |
| jfieldID descents; |
| jfieldID flags; |
| }; |
| |
| static jclass gLineBreaks_class; |
| static JLineBreaksID gLineBreaks_fieldID; |
| |
| class JNILineBreakerLineWidth : public minikin::LineBreaker::LineWidthDelegate { |
| public: |
| JNILineBreakerLineWidth(float firstWidth, int32_t firstLineCount, float restWidth, |
| std::vector<float>&& indents, int32_t indentsOffset) |
| : mFirstWidth(firstWidth), mFirstLineCount(firstLineCount), mRestWidth(restWidth), |
| mIndents(std::move(indents)), mIndentsOffset(indentsOffset) {} |
| |
| float getLineWidth(size_t lineNo) override { |
| const float width = ((ssize_t)lineNo < (ssize_t)mFirstLineCount) |
| ? mFirstWidth : mRestWidth; |
| if (mIndents.empty()) { |
| return width; |
| } |
| |
| const size_t indentIndex = lineNo + mIndentsOffset; |
| if (indentIndex < mIndents.size()) { |
| return width - mIndents[indentIndex]; |
| } else { |
| return width - mIndents.back(); |
| } |
| } |
| |
| private: |
| const float mFirstWidth; |
| const int32_t mFirstLineCount; |
| const float mRestWidth; |
| const std::vector<float> mIndents; |
| const int32_t mIndentsOffset; |
| }; |
| |
| // set text and set a number of parameters for creating a layout (width, tabstops, strategy, |
| // hyphenFrequency) |
| static void nSetupParagraph(JNIEnv* env, jclass, jlong nativePtr, jcharArray text, jint length, |
| jfloat firstWidth, jint firstWidthLineLimit, jfloat restWidth, |
| jintArray variableTabStops, jint defaultTabStop, jint strategy, jint hyphenFrequency, |
| jboolean isJustified, jintArray indents, jint indentsOffset) { |
| minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr); |
| b->resize(length); |
| env->GetCharArrayRegion(text, 0, length, b->buffer()); |
| b->setText(); |
| if (variableTabStops == nullptr) { |
| b->setTabStops(nullptr, 0, defaultTabStop); |
| } else { |
| ScopedIntArrayRO stops(env, variableTabStops); |
| b->setTabStops(stops.get(), stops.size(), defaultTabStop); |
| } |
| b->setStrategy(static_cast<minikin::BreakStrategy>(strategy)); |
| b->setHyphenationFrequency(static_cast<minikin::HyphenationFrequency>(hyphenFrequency)); |
| b->setJustified(isJustified); |
| |
| std::vector<float> indentVec; |
| // TODO: copy indents only once when LineBreaker is started to be used. |
| if (indents != nullptr) { |
| ScopedIntArrayRO indentArr(env, indents); |
| indentVec.assign(indentArr.get(), indentArr.get() + indentArr.size()); |
| } |
| b->setLineWidthDelegate(std::make_unique<JNILineBreakerLineWidth>( |
| firstWidth, firstWidthLineLimit, restWidth, std::move(indentVec), indentsOffset)); |
| } |
| |
| static void recycleCopy(JNIEnv* env, jobject recycle, jintArray recycleBreaks, |
| jfloatArray recycleWidths, jfloatArray recycleAscents, |
| jfloatArray recycleDescents, jintArray recycleFlags, |
| jint recycleLength, size_t nBreaks, const jint* breaks, |
| const jfloat* widths, const jfloat* ascents, const jfloat* descents, |
| const jint* flags) { |
| if ((size_t)recycleLength < nBreaks) { |
| // have to reallocate buffers |
| recycleBreaks = env->NewIntArray(nBreaks); |
| recycleWidths = env->NewFloatArray(nBreaks); |
| recycleAscents = env->NewFloatArray(nBreaks); |
| recycleDescents = env->NewFloatArray(nBreaks); |
| recycleFlags = env->NewIntArray(nBreaks); |
| |
| env->SetObjectField(recycle, gLineBreaks_fieldID.breaks, recycleBreaks); |
| env->SetObjectField(recycle, gLineBreaks_fieldID.widths, recycleWidths); |
| env->SetObjectField(recycle, gLineBreaks_fieldID.ascents, recycleAscents); |
| env->SetObjectField(recycle, gLineBreaks_fieldID.descents, recycleDescents); |
| env->SetObjectField(recycle, gLineBreaks_fieldID.flags, recycleFlags); |
| } |
| // copy data |
| env->SetIntArrayRegion(recycleBreaks, 0, nBreaks, breaks); |
| env->SetFloatArrayRegion(recycleWidths, 0, nBreaks, widths); |
| env->SetFloatArrayRegion(recycleAscents, 0, nBreaks, ascents); |
| env->SetFloatArrayRegion(recycleDescents, 0, nBreaks, descents); |
| env->SetIntArrayRegion(recycleFlags, 0, nBreaks, flags); |
| } |
| |
| static jint nComputeLineBreaks(JNIEnv* env, jclass, jlong nativePtr, |
| jobject recycle, jintArray recycleBreaks, |
| jfloatArray recycleWidths, jfloatArray recycleAscents, |
| jfloatArray recycleDescents, jintArray recycleFlags, |
| jint recycleLength) { |
| minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr); |
| |
| size_t nBreaks = b->computeBreaks(); |
| |
| recycleCopy(env, recycle, recycleBreaks, recycleWidths, recycleAscents, recycleDescents, |
| recycleFlags, recycleLength, nBreaks, b->getBreaks(), b->getWidths(), b->getAscents(), |
| b->getDescents(), b->getFlags()); |
| |
| b->finish(); |
| |
| return static_cast<jint>(nBreaks); |
| } |
| |
| static jlong nNewBuilder(JNIEnv*, jclass) { |
| return reinterpret_cast<jlong>(new minikin::LineBreaker); |
| } |
| |
| static void nFreeBuilder(JNIEnv*, jclass, jlong nativePtr) { |
| delete reinterpret_cast<minikin::LineBreaker*>(nativePtr); |
| } |
| |
| static void nFinishBuilder(JNIEnv*, jclass, jlong nativePtr) { |
| minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr); |
| b->finish(); |
| } |
| |
| static jlong nLoadHyphenator(JNIEnv* env, jclass, jobject buffer, jint offset, |
| jint minPrefix, jint minSuffix) { |
| const uint8_t* bytebuf = nullptr; |
| if (buffer != nullptr) { |
| void* rawbuf = env->GetDirectBufferAddress(buffer); |
| if (rawbuf != nullptr) { |
| bytebuf = reinterpret_cast<const uint8_t*>(rawbuf) + offset; |
| } else { |
| ALOGE("failed to get direct buffer address"); |
| } |
| } |
| minikin::Hyphenator* hyphenator = minikin::Hyphenator::loadBinary( |
| bytebuf, minPrefix, minSuffix); |
| return reinterpret_cast<jlong>(hyphenator); |
| } |
| |
| static void nSetLocales(JNIEnv* env, jclass, jlong nativePtr, jstring javaLocaleNames, |
| jlongArray nativeHyphenators) { |
| minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr); |
| |
| ScopedUtfChars localeNames(env, javaLocaleNames); |
| ScopedLongArrayRO hyphArr(env, nativeHyphenators); |
| const size_t numLocales = hyphArr.size(); |
| std::vector<minikin::Hyphenator*> hyphVec; |
| hyphVec.reserve(numLocales); |
| for (size_t i = 0; i < numLocales; i++) { |
| hyphVec.push_back(reinterpret_cast<minikin::Hyphenator*>(hyphArr[i])); |
| } |
| b->setLocales(localeNames.c_str(), hyphVec); |
| } |
| |
| // Basically similar to Paint.getTextRunAdvances but with C++ interface |
| static jfloat nAddStyleRun(JNIEnv* env, jclass, jlong nativePtr, jlong nativePaint, jint start, |
| jint end, jboolean isRtl) { |
| minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr); |
| Paint* paint = reinterpret_cast<Paint*>(nativePaint); |
| const Typeface* typeface = paint->getAndroidTypeface(); |
| minikin::MinikinPaint minikinPaint; |
| const Typeface* resolvedTypeface = Typeface::resolveDefault(typeface); |
| minikin::FontStyle style = MinikinUtils::prepareMinikinPaint(&minikinPaint, paint, |
| typeface); |
| return b->addStyleRun(&minikinPaint, resolvedTypeface->fFontCollection, style, start, end, |
| isRtl); |
| } |
| |
| // Accept width measurements for the run, passed in from Java |
| static void nAddMeasuredRun(JNIEnv* env, jclass, jlong nativePtr, |
| jint start, jint end, jfloatArray widths) { |
| minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr); |
| env->GetFloatArrayRegion(widths, start, end - start, b->charWidths() + start); |
| b->addStyleRun(nullptr, nullptr, minikin::FontStyle{}, start, end, false); |
| } |
| |
| static void nAddReplacementRun(JNIEnv* env, jclass, jlong nativePtr, |
| jint start, jint end, jfloat width) { |
| minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr); |
| b->addReplacement(start, end, width); |
| } |
| |
| static void nGetWidths(JNIEnv* env, jclass, jlong nativePtr, jfloatArray widths) { |
| minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr); |
| env->SetFloatArrayRegion(widths, 0, b->size(), b->charWidths()); |
| } |
| |
| static const JNINativeMethod gMethods[] = { |
| // TODO performance: many of these are candidates for fast jni, awaiting guidance |
| {"nNewBuilder", "()J", (void*) nNewBuilder}, |
| {"nFreeBuilder", "(J)V", (void*) nFreeBuilder}, |
| {"nFinishBuilder", "(J)V", (void*) nFinishBuilder}, |
| {"nLoadHyphenator", "(Ljava/nio/ByteBuffer;III)J", (void*) nLoadHyphenator}, |
| {"nSetLocales", "(JLjava/lang/String;[J)V", (void*) nSetLocales}, |
| {"nSetupParagraph", "(J[CIFIF[IIIIZ[II)V", (void*) nSetupParagraph}, |
| {"nAddStyleRun", "(JJIIZ)F", (void*) nAddStyleRun}, |
| {"nAddMeasuredRun", "(JII[F)V", (void*) nAddMeasuredRun}, |
| {"nAddReplacementRun", "(JIIF)V", (void*) nAddReplacementRun}, |
| {"nGetWidths", "(J[F)V", (void*) nGetWidths}, |
| {"nComputeLineBreaks", "(JLandroid/text/StaticLayout$LineBreaks;[I[F[F[F[II)I", |
| (void*) nComputeLineBreaks} |
| }; |
| |
| int register_android_text_StaticLayout(JNIEnv* env) |
| { |
| gLineBreaks_class = MakeGlobalRefOrDie(env, |
| FindClassOrDie(env, "android/text/StaticLayout$LineBreaks")); |
| |
| gLineBreaks_fieldID.breaks = GetFieldIDOrDie(env, gLineBreaks_class, "breaks", "[I"); |
| gLineBreaks_fieldID.widths = GetFieldIDOrDie(env, gLineBreaks_class, "widths", "[F"); |
| gLineBreaks_fieldID.ascents = GetFieldIDOrDie(env, gLineBreaks_class, "ascents", "[F"); |
| gLineBreaks_fieldID.descents = GetFieldIDOrDie(env, gLineBreaks_class, "descents", "[F"); |
| gLineBreaks_fieldID.flags = GetFieldIDOrDie(env, gLineBreaks_class, "flags", "[I"); |
| |
| return RegisterMethodsOrDie(env, "android/text/StaticLayout", gMethods, NELEM(gMethods)); |
| } |
| |
| } |