Merge "Calculate line breaks using Minikin"

This commit is contained in:
Raph Levien
2015-03-27 05:59:14 +00:00
committed by Android (Google) Code Review
6 changed files with 114 additions and 622 deletions

View File

@@ -1153,7 +1153,10 @@ public abstract class Layout {
return end - 1;
}
if (ch != ' ' && ch != '\t') {
// Note: keep this in sync with Minikin LineBreaker::isLineEndSpace()
if (!(ch == ' ' || ch == '\t' || ch == 0x1680 ||
(0x2000 <= ch && ch <= 0x200A && ch != 0x2007) ||
ch == 0x205F || ch == 0x3000)) {
break;
}

View File

@@ -170,8 +170,9 @@ public class StaticLayout extends Layout {
* Measurement and break iteration is done in native code. The protocol for using
* the native code is as follows.
*
* For each paragraph, do a nSetText of the paragraph text. Then, for each run within the
* paragraph:
* For each paragraph, do a nSetText of the paragraph text. Also do nSetLineWidth.
*
* Then, for each run within the paragraph:
* - setLocale (this must be done at least for the first run, optional afterwards)
* - one of the following, depending on the type of run:
* + addStyleRun (a text run, to be measured in native code)
@@ -459,7 +460,26 @@ public class StaticLayout extends Layout {
byte[] chdirs = measured.mLevels;
int dir = measured.mDir;
boolean easy = measured.mEasy;
nSetText(b.mNativePtr, chs, paraEnd - paraStart);
// tab stop locations
int[] variableTabStops = null;
if (spanned != null) {
TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
paraEnd, TabStopSpan.class);
if (spans.length > 0) {
int[] stops = new int[spans.length];
for (int i = 0; i < spans.length; i++) {
stops[i] = spans[i].getTabStop();
}
Arrays.sort(stops, 0, stops.length);
variableTabStops = stops;
}
}
int breakStrategy = 0; // 0 = kBreakStrategy_Greedy
nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart,
firstWidth, firstWidthLineCount, restWidth,
variableTabStops, TAB_INCREMENT, breakStrategy);
// measurement has to be done before performing line breaking
// but we don't want to recompute fontmetrics or span ranges the
@@ -505,25 +525,9 @@ public class StaticLayout extends Layout {
spanEndCacheCount++;
}
// tab stop locations
int[] variableTabStops = null;
if (spanned != null) {
TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
paraEnd, TabStopSpan.class);
if (spans.length > 0) {
int[] stops = new int[spans.length];
for (int i = 0; i < spans.length; i++) {
stops[i] = spans[i].getTabStop();
}
Arrays.sort(stops, 0, stops.length);
variableTabStops = stops;
}
}
nGetWidths(b.mNativePtr, widths);
int breakCount = nComputeLineBreaks(b.mNativePtr, paraEnd - paraStart, firstWidth,
firstWidthLineCount, restWidth, variableTabStops, TAB_INCREMENT, false, lineBreaks,
lineBreaks.breaks, lineBreaks.widths, lineBreaks.flags, lineBreaks.breaks.length);
int breakCount = nComputeLineBreaks(b.mNativePtr, lineBreaks, lineBreaks.breaks,
lineBreaks.widths, lineBreaks.flags, lineBreaks.breaks.length);
int[] breaks = lineBreaks.breaks;
float[] lineWidths = lineBreaks.widths;
@@ -966,7 +970,10 @@ public class StaticLayout extends Layout {
private static native void nFinishBuilder(long nativePtr);
private static native void nSetLocale(long nativePtr, String locale);
private static native void nSetText(long nativePtr, char[] text, int length);
// Set up paragraph text and settings; done as one big method to minimize jni crossings
private static native void nSetupParagraph(long nativePtr, char[] text, int length,
float firstWidth, int firstWidthLineCount, float restWidth,
int[] variableTabStops, int defaultTabStop, int breakStrategy);
private static native float nAddStyleRun(long nativePtr, long nativePaint,
long nativeTypeface, int start, int end, boolean isRtl);
@@ -983,9 +990,7 @@ public class StaticLayout extends Layout {
// the arrays inside the LineBreaks objects are passed in as well
// to reduce the number of JNI calls in the common case where the
// arrays do not have to be resized
private static native int nComputeLineBreaks(long nativePtr,
int length, float firstWidth, int firstWidthLineCount, float restWidth,
int[] variableTabStops, int defaultTabStop, boolean optimize, LineBreaks recycle,
private static native int nComputeLineBreaks(long nativePtr, LineBreaks recycle,
int[] recycleBreaks, float[] recycleWidths, boolean[] recycleFlags, int recycleLength);
private int mLineCount;

View File

@@ -26,10 +26,10 @@
namespace android {
void MinikinUtils::doLayout(Layout* layout, const Paint* paint, int bidiFlags, TypefaceImpl* typeface,
const uint16_t* buf, size_t start, size_t count, size_t bufSize) {
TypefaceImpl* resolvedFace = TypefaceImpl_resolveDefault(typeface);
layout->setFontCollection(resolvedFace->fFontCollection);
FontStyle MinikinUtils::prepareMinikinPaint(MinikinPaint* minikinPaint, FontCollection** pFont,
const Paint* paint, TypefaceImpl* typeface) {
const TypefaceImpl* resolvedFace = TypefaceImpl_resolveDefault(typeface);
*pFont = resolvedFace->fFontCollection;
FontStyle resolved = resolvedFace->fStyle;
/* Prepare minikin FontStyle */
@@ -40,15 +40,26 @@ void MinikinUtils::doLayout(Layout* layout, const Paint* paint, int bidiFlags, T
FontStyle minikinStyle(minikinLang, minikinVariant, resolved.getWeight(), resolved.getItalic());
/* Prepare minikin Paint */
MinikinPaint minikinPaint;
minikinPaint.size = (int)/*WHY?!*/paint->getTextSize();
minikinPaint.scaleX = paint->getTextScaleX();
minikinPaint.skewX = paint->getTextSkewX();
minikinPaint.letterSpacing = paint->getLetterSpacing();
minikinPaint.paintFlags = MinikinFontSkia::packPaintFlags(paint);
minikinPaint.fontFeatureSettings = paint->getFontFeatureSettings();
minikinPaint.hyphenEdit = HyphenEdit(paint->getHyphenEdit());
// Note: it would be nice to handle fractional size values (it would improve smooth zoom
// behavior), but historically size has been treated as an int.
// TODO: explore whether to enable fractional sizes, possibly when linear text flag is set.
minikinPaint->size = (int)paint->getTextSize();
minikinPaint->scaleX = paint->getTextScaleX();
minikinPaint->skewX = paint->getTextSkewX();
minikinPaint->letterSpacing = paint->getLetterSpacing();
minikinPaint->paintFlags = MinikinFontSkia::packPaintFlags(paint);
minikinPaint->fontFeatureSettings = paint->getFontFeatureSettings();
minikinPaint->hyphenEdit = HyphenEdit(paint->getHyphenEdit());
return minikinStyle;
}
void MinikinUtils::doLayout(Layout* layout, const Paint* paint, int bidiFlags,
TypefaceImpl* typeface, const uint16_t* buf, size_t start, size_t count,
size_t bufSize) {
FontCollection *font;
MinikinPaint minikinPaint;
FontStyle minikinStyle = prepareMinikinPaint(&minikinPaint, &font, paint, typeface);
layout->setFontCollection(font);
layout->doLayout(buf, start, count, bufSize, bidiFlags, minikinStyle, minikinPaint);
}

View File

@@ -31,22 +31,14 @@
namespace android {
// TODO: these should be defined in Minikin's Layout.h
enum {
kBidi_LTR = 0,
kBidi_RTL = 1,
kBidi_Default_LTR = 2,
kBidi_Default_RTL = 3,
kBidi_Force_LTR = 4,
kBidi_Force_RTL = 5,
kBidi_Mask = 0x7
};
class MinikinUtils {
public:
static void doLayout(Layout* layout, const Paint* paint, int bidiFlags, TypefaceImpl* typeface,
const uint16_t* buf, size_t start, size_t count, size_t bufSize);
static FontStyle prepareMinikinPaint(MinikinPaint* minikinPaint, FontCollection** pFont,
const Paint* paint, TypefaceImpl* typeface);
static void doLayout(Layout* layout, const Paint* paint, int bidiFlags,
TypefaceImpl* typeface, const uint16_t* buf, size_t start, size_t count,
size_t bufSize);
static float xOffsetForTextAlign(Paint* paint, const Layout& layout);

View File

@@ -62,4 +62,4 @@ void TypefaceImpl_setDefault(TypefaceImpl* face);
}
#endif // _ANDROID_GRAPHICS_TYPEFACE_IMPL_H_
#endif // _ANDROID_GRAPHICS_TYPEFACE_IMPL_H_

View File

@@ -34,6 +34,7 @@
#include "MinikinSkia.h"
#include "MinikinUtils.h"
#include "Paint.h"
#include "minikin/LineBreaker.h"
namespace android {
@@ -46,636 +47,116 @@ struct JLineBreaksID {
static jclass gLineBreaks_class;
static JLineBreaksID gLineBreaks_fieldID;
class Builder {
public:
~Builder() {
utext_close(&mUText);
delete mBreakIterator;
}
void setLocale(const icu::Locale& locale) {
delete mBreakIterator;
UErrorCode status = U_ZERO_ERROR;
mBreakIterator = icu::BreakIterator::createLineInstance(locale, status);
// TODO: check status
}
void resize(size_t size) {
mTextBuf.resize(size);
mWidthBuf.resize(size);
}
size_t size() const {
return mTextBuf.size();
}
uint16_t* buffer() {
return mTextBuf.data();
}
float* widths() {
return mWidthBuf.data();
}
// set text to current contents of buffer
void setText() {
UErrorCode status = U_ZERO_ERROR;
utext_openUChars(&mUText, mTextBuf.data(), mTextBuf.size(), &status);
mBreakIterator->setText(&mUText, status);
}
void finish() {
if (mTextBuf.size() > MAX_TEXT_BUF_RETAIN) {
mTextBuf.clear();
mTextBuf.shrink_to_fit();
mWidthBuf.clear();
mWidthBuf.shrink_to_fit();
}
}
icu::BreakIterator* breakIterator() const {
return mBreakIterator;
}
float measureStyleRun(Paint* paint, TypefaceImpl* typeface, size_t start, size_t end,
bool isRtl);
void addReplacement(size_t start, size_t end, float width);
private:
const size_t MAX_TEXT_BUF_RETAIN = 32678;
icu::BreakIterator* mBreakIterator = nullptr;
UText mUText = UTEXT_INITIALIZER;
std::vector<uint16_t>mTextBuf;
std::vector<float>mWidthBuf;
};
static const int CHAR_SPACE = 0x20;
static const int CHAR_TAB = 0x09;
static const int CHAR_NEWLINE = 0x0a;
static const int CHAR_ZWSP = 0x200b;
class TabStops {
public:
// specified stops must be a sorted array (allowed to be null)
TabStops(JNIEnv* env, jintArray stops, jint defaultTabWidth) :
mStops(env), mTabWidth(defaultTabWidth) {
if (stops != nullptr) {
mStops.reset(stops);
mNumStops = mStops.size();
} else {
mNumStops = 0;
}
}
float width(float widthSoFar) const {
const jint* mStopsArray = mStops.get();
for (int i = 0; i < mNumStops; i++) {
if (mStopsArray[i] > widthSoFar) {
return mStopsArray[i];
}
}
// find the next tabstop after widthSoFar
return static_cast<int>((widthSoFar + mTabWidth) / mTabWidth) * mTabWidth;
}
private:
ScopedIntArrayRO mStops;
const int mTabWidth;
int mNumStops;
// set text and set a number of parameters for creating a layout (width, tabstops, strategy)
static void nSetupParagraph(JNIEnv* env, jclass, jlong nativePtr, jcharArray text, jint length,
jfloat firstWidth, jint firstWidthLineLimit, jfloat restWidth,
jintArray variableTabStops, jint defaultTabStop, jint strategy) {
LineBreaker* b = reinterpret_cast<LineBreaker*>(nativePtr);
b->resize(length);
env->GetCharArrayRegion(text, 0, length, b->buffer());
b->setText();
b->setLineWidths(firstWidth, firstWidthLineLimit, restWidth);
if (variableTabStops == nullptr) {
b->setTabStops(nullptr, 0, defaultTabStop);
} else {
ScopedIntArrayRO stops(env, variableTabStops);
b->setTabStops(stops.get(), stops.size(), defaultTabStop);
}
b->setStrategy(static_cast<BreakStrategy>(strategy));
}
// disable copying and assignment
TabStops(const TabStops&);
void operator=(const TabStops&);
};
enum PrimitiveType {
kPrimitiveType_Box,
kPrimitiveType_Glue,
kPrimitiveType_Penalty,
kPrimitiveType_Variable,
kPrimitiveType_Wordbreak
};
static const float PENALTY_INFINITY = 1e7; // forced non-break, negative infinity is forced break
struct Primitive {
PrimitiveType type;
int location;
// 'Box' has width
// 'Glue' has width
// 'Penalty' has width and penalty
// 'Variable' has tabStop
// 'Wordbreak' has penalty
union {
struct {
float width;
float penalty;
};
const TabStops* tabStop;
};
};
class LineWidth {
public:
LineWidth(float firstWidth, int firstWidthLineCount, float restWidth) :
mFirstWidth(firstWidth), mFirstWidthLineCount(firstWidthLineCount),
mRestWidth(restWidth) {}
float getLineWidth(int line) const {
return (line < mFirstWidthLineCount) ? mFirstWidth : mRestWidth;
}
private:
const float mFirstWidth;
const int mFirstWidthLineCount;
const float mRestWidth;
};
class LineBreaker {
public:
LineBreaker(const std::vector<Primitive>& primitives,
const LineWidth& lineWidth) :
mPrimitives(primitives), mLineWidth(lineWidth) {}
virtual ~LineBreaker() {}
virtual void computeBreaks(std::vector<int>* breaks, std::vector<float>* widths,
std::vector<unsigned char>* flags) const = 0;
protected:
const std::vector<Primitive>& mPrimitives;
const LineWidth& mLineWidth;
};
class OptimizingLineBreaker : public LineBreaker {
public:
OptimizingLineBreaker(const std::vector<Primitive>& primitives, const LineWidth& lineWidth) :
LineBreaker(primitives, lineWidth) {}
void computeBreaks(std::vector<int>* breaks, std::vector<float>* widths,
std::vector<unsigned char>* flags) const {
int numBreaks = mPrimitives.size();
Node* opt = new Node[numBreaks];
opt[0].prev = -1;
opt[0].prevCount = 0;
opt[0].width = 0;
opt[0].demerits = 0;
opt[0].flags = false;
opt[numBreaks - 1].prev = -1;
opt[numBreaks - 1].prevCount = 0;
std::list<int> active;
active.push_back(0);
int lastBreak = 0;
for (int i = 0; i < numBreaks; i++) {
const Primitive& p = mPrimitives[i];
if (p.type == kPrimitiveType_Penalty) {
const bool finalBreak = (i + 1 == numBreaks);
bool breakFound = false;
Node bestBreak;
for (std::list<int>::iterator it = active.begin(); it != active.end(); /* incrementing done in loop */) {
const int pos = *it;
bool flags;
float width, printedWidth;
const int lines = opt[pos].prevCount;
const float maxWidth = mLineWidth.getLineWidth(lines);
// we have to compute metrics every time --
// we can't really precompute this stuff and just deal with breaks
// because of the way tab characters work, this makes it computationally
// harder, but this way, we can still optimize while treating tab characters
// correctly
computeMetrics(pos, i, &width, &printedWidth, &flags);
if (printedWidth <= maxWidth) {
float demerits = computeDemerits(maxWidth, printedWidth,
finalBreak, p.penalty) + opt[pos].demerits;
if (!breakFound || demerits < bestBreak.demerits) {
bestBreak.prev = pos;
bestBreak.prevCount = opt[pos].prevCount + 1;
bestBreak.demerits = demerits;
bestBreak.width = printedWidth;
bestBreak.flags = flags;
breakFound = true;
}
++it;
} else {
active.erase(it++); // safe to delete like this
}
}
if (p.penalty == -PENALTY_INFINITY) {
active.clear();
}
if (breakFound) {
opt[i] = bestBreak;
active.push_back(i);
lastBreak = i;
}
if (active.empty()) {
// we can't give up!
float width, printedWidth;
bool flags;
const int lines = opt[lastBreak].prevCount;
const float maxWidth = mLineWidth.getLineWidth(lines);
const int breakIndex = desperateBreak(lastBreak, numBreaks, maxWidth, &width, &printedWidth, &flags);
opt[breakIndex].prev = lastBreak;
opt[breakIndex].prevCount = lines + 1;
opt[breakIndex].demerits = 0; // doesn't matter, it's the only one
opt[breakIndex].width = width;
opt[breakIndex].flags = flags;
active.push_back(breakIndex);
lastBreak = breakIndex;
i = breakIndex; // incremented by i++
}
}
}
int idx = numBreaks - 1;
int count = opt[idx].prevCount;
breaks->resize(count);
widths->resize(count);
flags->resize(count);
while (opt[idx].prev != -1) {
--count;
(*breaks)[count] = mPrimitives[idx].location;
(*widths)[count] = opt[idx].width;
(*flags)[count] = opt[idx].flags;
idx = opt[idx].prev;
}
delete[] opt;
}
private:
inline void computeMetrics(int start, int end, float* width, float* printedWidth, bool* flags) const {
bool f = false;
float w = 0, pw = 0;
for (int i = start; i < end; i++) {
const Primitive& p = mPrimitives[i];
if (p.type == kPrimitiveType_Box || p.type == kPrimitiveType_Glue) {
w += p.width;
if (p.type == kPrimitiveType_Box) {
pw = w;
}
} else if (p.type == kPrimitiveType_Variable) {
w = p.tabStop->width(w);
f = true;
}
}
*width = w;
*printedWidth = pw;
*flags = f;
}
inline float computeDemerits(float maxWidth, float width, bool finalBreak, float penalty) const {
float deviation = finalBreak ? 0 : maxWidth - width;
return (deviation * deviation) + penalty;
}
// returns end pos (chosen break), -1 if fail
inline int desperateBreak(int start, int limit, float maxWidth, float* width, float* printedWidth, bool* flags) const {
float w = 0, pw = 0;
bool breakFound = false;
int breakIndex = 0, firstTabIndex = INT_MAX;
for (int i = start; i < limit; i++) {
const Primitive& p = mPrimitives[i];
if (p.type == kPrimitiveType_Box || p.type == kPrimitiveType_Glue) {
w += p.width;
if (p.type == kPrimitiveType_Box) {
pw = w;
}
} else if (p.type == kPrimitiveType_Variable) {
w = p.tabStop->width(w);
firstTabIndex = std::min(firstTabIndex, i);
}
if (pw > maxWidth) {
if (breakFound) {
break;
} else {
// no choice, keep going
}
}
// must make progress
if (i > start && (p.type == kPrimitiveType_Penalty || p.type == kPrimitiveType_Wordbreak)) {
breakFound = true;
breakIndex = i;
}
}
if (breakFound) {
*width = w;
*printedWidth = pw;
*flags = (start <= firstTabIndex && firstTabIndex < breakIndex);
return breakIndex;
} else {
return -1;
}
}
struct Node {
int prev; // set to sentinel value (-1) for initial node
int prevCount; // number of breaks so far
float demerits;
float width;
bool flags;
};
};
class GreedyLineBreaker : public LineBreaker {
public:
GreedyLineBreaker(const std::vector<Primitive>& primitives, const LineWidth& lineWidth) :
LineBreaker(primitives, lineWidth) {}
void computeBreaks(std::vector<int>* breaks, std::vector<float>* widths,
std::vector<unsigned char>* flags) const {
int lineNum = 0;
float width = 0, printedWidth = 0;
bool breakFound = false, goodBreakFound = false;
int breakIndex = 0, goodBreakIndex = 0;
float breakWidth = 0, goodBreakWidth = 0;
int firstTabIndex = INT_MAX;
float maxWidth = mLineWidth.getLineWidth(lineNum);
const int numPrimitives = mPrimitives.size();
// greedily fit as many characters as possible on each line
// loop over all primitives, and choose the best break point
// (if possible, a break point without splitting a word)
// after going over the maximum length
for (int i = 0; i < numPrimitives; i++) {
const Primitive& p = mPrimitives[i];
// update the current line width
if (p.type == kPrimitiveType_Box || p.type == kPrimitiveType_Glue) {
width += p.width;
if (p.type == kPrimitiveType_Box) {
printedWidth = width;
}
} else if (p.type == kPrimitiveType_Variable) {
width = p.tabStop->width(width);
// keep track of first tab character in the region we are examining
// so we can determine whether or not a line contains a tab
firstTabIndex = std::min(firstTabIndex, i);
}
// find the best break point for the characters examined so far
if (printedWidth > maxWidth) {
if (breakFound || goodBreakFound) {
if (goodBreakFound) {
// a true line break opportunity existed in the characters examined so far,
// so there is no need to split a word
i = goodBreakIndex; // no +1 because of i++
lineNum++;
maxWidth = mLineWidth.getLineWidth(lineNum);
breaks->push_back(mPrimitives[goodBreakIndex].location);
widths->push_back(goodBreakWidth);
flags->push_back(firstTabIndex < goodBreakIndex);
firstTabIndex = INT_MAX;
} else {
// must split a word because there is no other option
i = breakIndex; // no +1 because of i++
lineNum++;
maxWidth = mLineWidth.getLineWidth(lineNum);
breaks->push_back(mPrimitives[breakIndex].location);
widths->push_back(breakWidth);
flags->push_back(firstTabIndex < breakIndex);
firstTabIndex = INT_MAX;
}
printedWidth = width = 0;
goodBreakFound = breakFound = false;
goodBreakWidth = breakWidth = 0;
continue;
} else {
// no choice, keep going... must make progress by putting at least one
// character on a line, even if part of that character is cut off --
// there is no other option
}
}
// update possible break points
if (p.type == kPrimitiveType_Penalty && p.penalty < PENALTY_INFINITY) {
// this does not handle penalties with width
// handle forced line break
if (p.penalty == -PENALTY_INFINITY) {
lineNum++;
maxWidth = mLineWidth.getLineWidth(lineNum);
breaks->push_back(p.location);
widths->push_back(printedWidth);
flags->push_back(firstTabIndex < i);
firstTabIndex = INT_MAX;
printedWidth = width = 0;
goodBreakFound = breakFound = false;
goodBreakWidth = breakWidth = 0;
continue;
}
if (i > breakIndex && (printedWidth <= maxWidth || breakFound == false)) {
breakFound = true;
breakIndex = i;
breakWidth = printedWidth;
}
if (i > goodBreakIndex && printedWidth <= maxWidth) {
goodBreakFound = true;
goodBreakIndex = i;
goodBreakWidth = printedWidth;
}
} else if (p.type == kPrimitiveType_Wordbreak) {
// only do this if necessary -- we don't want to break words
// when possible, but sometimes it is unavoidable
if (i > breakIndex && (printedWidth <= maxWidth || breakFound == false)) {
breakFound = true;
breakIndex = i;
breakWidth = printedWidth;
}
}
}
if (breakFound || goodBreakFound) {
// output last break if there are more characters to output
if (goodBreakFound) {
breaks->push_back(mPrimitives[goodBreakIndex].location);
widths->push_back(goodBreakWidth);
flags->push_back(firstTabIndex < goodBreakIndex);
} else {
breaks->push_back(mPrimitives[breakIndex].location);
widths->push_back(breakWidth);
flags->push_back(firstTabIndex < breakIndex);
}
}
}
};
static jint recycleCopy(JNIEnv* env, jobject recycle, jintArray recycleBreaks,
static void recycleCopy(JNIEnv* env, jobject recycle, jintArray recycleBreaks,
jfloatArray recycleWidths, jbooleanArray recycleFlags,
jint recycleLength, const std::vector<jint>& breaks,
const std::vector<jfloat>& widths, const std::vector<jboolean>& flags) {
int bufferLength = breaks.size();
if (recycleLength < bufferLength) {
jint recycleLength, size_t nBreaks, const jint* breaks,
const jfloat* widths, const jboolean* flags) {
if ((size_t)recycleLength < nBreaks) {
// have to reallocate buffers
recycleBreaks = env->NewIntArray(bufferLength);
recycleWidths = env->NewFloatArray(bufferLength);
recycleFlags = env->NewBooleanArray(bufferLength);
recycleBreaks = env->NewIntArray(nBreaks);
recycleWidths = env->NewFloatArray(nBreaks);
recycleFlags = env->NewBooleanArray(nBreaks);
env->SetObjectField(recycle, gLineBreaks_fieldID.breaks, recycleBreaks);
env->SetObjectField(recycle, gLineBreaks_fieldID.widths, recycleWidths);
env->SetObjectField(recycle, gLineBreaks_fieldID.flags, recycleFlags);
}
// copy data
env->SetIntArrayRegion(recycleBreaks, 0, breaks.size(), &breaks.front());
env->SetFloatArrayRegion(recycleWidths, 0, widths.size(), &widths.front());
env->SetBooleanArrayRegion(recycleFlags, 0, flags.size(), &flags.front());
return bufferLength;
}
void computePrimitives(const jchar* textArr, const jfloat* widthsArr, jint length, const std::vector<int>& breaks,
const TabStops& tabStopCalculator, std::vector<Primitive>* primitives) {
int breaksSize = breaks.size();
int breakIndex = 0;
Primitive p;
for (int i = 0; i < length; i++) {
p.location = i;
jchar c = textArr[i];
if (c == CHAR_SPACE || c == CHAR_ZWSP) {
p.type = kPrimitiveType_Glue;
p.width = widthsArr[i];
primitives->push_back(p);
} else if (c == CHAR_TAB) {
p.type = kPrimitiveType_Variable;
p.tabStop = &tabStopCalculator; // shared between all variable primitives
primitives->push_back(p);
} else if (c != CHAR_NEWLINE) {
while (breakIndex < breaksSize && breaks[breakIndex] < i) breakIndex++;
p.width = 0;
if (breakIndex < breaksSize && breaks[breakIndex] == i) {
p.type = kPrimitiveType_Penalty;
p.penalty = 0;
} else {
p.type = kPrimitiveType_Wordbreak;
}
if (widthsArr[i] != 0) {
primitives->push_back(p);
}
p.type = kPrimitiveType_Box;
p.width = widthsArr[i];
primitives->push_back(p);
}
}
// final break at end of everything
p.location = length;
p.type = kPrimitiveType_Penalty;
p.width = 0;
p.penalty = -PENALTY_INFINITY;
primitives->push_back(p);
}
// sets the text on the builder
static void nSetText(JNIEnv* env, jclass, jlong nativePtr, jcharArray text, int length) {
Builder* b = reinterpret_cast<Builder*>(nativePtr);
b->resize(length);
env->GetCharArrayRegion(text, 0, length, b->buffer());
b->setText();
env->SetIntArrayRegion(recycleBreaks, 0, nBreaks, breaks);
env->SetFloatArrayRegion(recycleWidths, 0, nBreaks, widths);
env->SetBooleanArrayRegion(recycleFlags, 0, nBreaks, flags);
}
static jint nComputeLineBreaks(JNIEnv* env, jclass, jlong nativePtr,
jint length,
jfloat firstWidth, jint firstWidthLineLimit, jfloat restWidth,
jintArray variableTabStops, jint defaultTabStop, jboolean optimize,
jobject recycle, jintArray recycleBreaks,
jfloatArray recycleWidths, jbooleanArray recycleFlags,
jint recycleLength) {
Builder* b = reinterpret_cast<Builder*>(nativePtr);
LineBreaker* b = reinterpret_cast<LineBreaker*>(nativePtr);
std::vector<int> breaks;
size_t nBreaks = b->computeBreaks();
icu::BreakIterator* breakIterator = b->breakIterator();
int loc = breakIterator->first();
while ((loc = breakIterator->next()) != icu::BreakIterator::DONE) {
breaks.push_back(loc);
}
recycleCopy(env, recycle, recycleBreaks, recycleWidths, recycleFlags, recycleLength,
nBreaks, b->getBreaks(), b->getWidths(), b->getFlags());
// TODO: all these allocations can be moved into the builder
std::vector<Primitive> primitives;
TabStops tabStops(env, variableTabStops, defaultTabStop);
computePrimitives(b->buffer(), b->widths(), length, breaks, tabStops, &primitives);
LineWidth lineWidth(firstWidth, firstWidthLineLimit, restWidth);
std::vector<int> computedBreaks;
std::vector<float> computedWidths;
std::vector<unsigned char> computedFlags;
if (optimize) {
OptimizingLineBreaker breaker(primitives, lineWidth);
breaker.computeBreaks(&computedBreaks, &computedWidths, &computedFlags);
} else {
GreedyLineBreaker breaker(primitives, lineWidth);
breaker.computeBreaks(&computedBreaks, &computedWidths, &computedFlags);
}
b->finish();
return recycleCopy(env, recycle, recycleBreaks, recycleWidths, recycleFlags, recycleLength,
computedBreaks, computedWidths, computedFlags);
return static_cast<jint>(nBreaks);
}
static jlong nNewBuilder(JNIEnv*, jclass) {
return reinterpret_cast<jlong>(new Builder);
return reinterpret_cast<jlong>(new LineBreaker);
}
static void nFreeBuilder(JNIEnv*, jclass, jlong nativePtr) {
delete reinterpret_cast<Builder*>(nativePtr);
delete reinterpret_cast<LineBreaker*>(nativePtr);
}
static void nFinishBuilder(JNIEnv*, jclass, jlong nativePtr) {
Builder* b = reinterpret_cast<Builder*>(nativePtr);
LineBreaker* b = reinterpret_cast<LineBreaker*>(nativePtr);
b->finish();
}
static void nSetLocale(JNIEnv* env, jclass, jlong nativePtr, jstring javaLocaleName) {
ScopedIcuLocale icuLocale(env, javaLocaleName);
Builder* b = reinterpret_cast<Builder*>(nativePtr);
LineBreaker* b = reinterpret_cast<LineBreaker*>(nativePtr);
if (icuLocale.valid()) {
b->setLocale(icuLocale.locale());
}
}
float Builder::measureStyleRun(Paint* paint, TypefaceImpl* typeface, size_t start, size_t end,
bool isRtl) {
Layout layout;
int bidiFlags = isRtl ? kBidi_Force_RTL : kBidi_Force_LTR;
// TODO: should we provide more context?
MinikinUtils::doLayout(&layout, paint, bidiFlags, typeface, mTextBuf.data() + start, 0,
end - start, end - start);
layout.getAdvances(mWidthBuf.data() + start);
return layout.getAdvance();
}
void Builder::addReplacement(size_t start, size_t end, float width) {
mWidthBuf[start] = width;
std::fill(&mWidthBuf[start + 1], &mWidthBuf[end], 0.0f);
}
// Basically similar to Paint.getTextRunAdvances but with C++ interface
static jfloat nAddStyleRun(JNIEnv* env, jclass, jlong nativePtr,
jlong nativePaint, jlong nativeTypeface, jint start, jint end, jboolean isRtl) {
Builder* b = reinterpret_cast<Builder*>(nativePtr);
LineBreaker* b = reinterpret_cast<LineBreaker*>(nativePtr);
Paint* paint = reinterpret_cast<Paint*>(nativePaint);
TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(nativeTypeface);
return b->measureStyleRun(paint, typeface, start, end, isRtl);
FontCollection *font;
MinikinPaint minikinPaint;
FontStyle style = MinikinUtils::prepareMinikinPaint(&minikinPaint, &font, paint, typeface);
return b->addStyleRun(&minikinPaint, font, 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) {
Builder* b = reinterpret_cast<Builder*>(nativePtr);
env->GetFloatArrayRegion(widths, start, end - start, b->widths() + start);
LineBreaker* b = reinterpret_cast<LineBreaker*>(nativePtr);
env->GetFloatArrayRegion(widths, start, end - start, b->charWidths() + start);
b->addStyleRun(nullptr, nullptr, FontStyle{}, start, end, false);
}
static void nAddReplacementRun(JNIEnv* env, jclass, jlong nativePtr,
jint start, jint end, jfloat width) {
Builder* b = reinterpret_cast<Builder*>(nativePtr);
LineBreaker* b = reinterpret_cast<LineBreaker*>(nativePtr);
b->addReplacement(start, end, width);
}
static void nGetWidths(JNIEnv* env, jclass, jlong nativePtr, jfloatArray widths) {
Builder* b = reinterpret_cast<Builder*>(nativePtr);
env->SetFloatArrayRegion(widths, 0, b->size(), b->widths());
LineBreaker* b = reinterpret_cast<LineBreaker*>(nativePtr);
env->SetFloatArrayRegion(widths, 0, b->size(), b->charWidths());
}
static JNINativeMethod gMethods[] = {
@@ -684,12 +165,12 @@ static JNINativeMethod gMethods[] = {
{"nFreeBuilder", "(J)V", (void*) nFreeBuilder},
{"nFinishBuilder", "(J)V", (void*) nFinishBuilder},
{"nSetLocale", "(JLjava/lang/String;)V", (void*) nSetLocale},
{"nSetText", "(J[CI)V", (void*) nSetText},
{"nSetupParagraph", "(J[CIFIF[III)V", (void*) nSetupParagraph},
{"nAddStyleRun", "(JJJIIZ)F", (void*) nAddStyleRun},
{"nAddMeasuredRun", "(JII[F)V", (void*) nAddMeasuredRun},
{"nAddReplacementRun", "(JIIF)V", (void*) nAddReplacementRun},
{"nGetWidths", "(J[F)V", (void*) nGetWidths},
{"nComputeLineBreaks", "(JIFIF[IIZLandroid/text/StaticLayout$LineBreaks;[I[F[ZI)I",
{"nComputeLineBreaks", "(JLandroid/text/StaticLayout$LineBreaks;[I[F[ZI)I",
(void*) nComputeLineBreaks}
};