public void addWordBreak(int offset, ParaWidth preBreak, ParaWidth postBreak, int preSpaceCount, int postSpaceCount, float penalty, HyphenationType hyph) { Candidate cand = new Candidate(); ParaWidth width = mCandidates.back().preBreak; if (postBreak - width > currentLineWidth() != null) { // Add desperate breaks. // Note: these breaks are based on the shaping of the (non-broken) original // text; they are imprecise especially in the presence of kerning, // ligatures, and Arabic shaping. int i = mCandidates.back().offset; width += mCharWidths[i++]; for (; i < offset; i++) { float w = mCharWidths[i]; if (w > 0F) { cand.offset = i; cand.preBreak = width; cand.postBreak = width; // postSpaceCount doesn't include trailing spaces cand.preSpaceCount = postSpaceCount; cand.postSpaceCount = postSpaceCount; cand.penalty = SCORE_DESPERATE; cand.hyphenType = HyphenationType.BREAK_AND_DONT_INSERT_HYPHEN; #if VERBOSE_DEBUG ALOGD("desperate cand: %zd %g:%g", mCandidates.size(), cand.postBreak, cand.preBreak); #endif addCandidate(cand); width += w; } } } cand.offset = offset; cand.preBreak = preBreak; cand.postBreak = postBreak; cand.penalty = penalty; cand.preSpaceCount = preSpaceCount; cand.postSpaceCount = postSpaceCount; cand.hyphenType = hyph; #if VERBOSE_DEBUG ALOGD("cand: %zd %g:%g", mCandidates.size(), cand.postBreak, cand.preBreak); #endif addCandidate(cand); }
public void computeBreaksOptimal(bool isRectangle) { int active = 0; int nCand = mCandidates.size(); float width = mLineWidths.getLineWidth(0); float shortLineFactor = mJustified ? 0.75f : 0.5f; float maxShrink = mJustified ? SHRINKABILITY * getSpaceWidth() : 0.0f; // "i" iterates through candidates for the end of the line. for (int i = 1; i < nCand; i++) { bool atEnd = i == nCand - 1; float best = SCORE_INFTY; int bestPrev = 0; int lineNumberLast = 0; if (!isRectangle) { int lineNumberLast = mCandidates[active].lineNumber; width = mLineWidths.getLineWidth(lineNumberLast); } ParaWidth leftEdge = mCandidates[i].postBreak - width; float bestHope = 0F; // "j" iterates through candidates for the beginning of the line. for (int j = active; j < i; j++) { if (!isRectangle) { int lineNumber = mCandidates[j].lineNumber; if (lineNumber != lineNumberLast) { float widthNew = mLineWidths.getLineWidth(lineNumber); if (widthNew != width) { leftEdge = mCandidates[i].postBreak - width; bestHope = 0F; width = widthNew; } lineNumberLast = lineNumber; } } float jScore = mCandidates[j].score; if (jScore + bestHope >= best) { continue; } float delta = mCandidates[j].preBreak - leftEdge; // compute width score for line // Note: the "bestHope" optimization makes the assumption that, when delta // is non-negative, widthScore will increase monotonically as successive // candidate breaks are considered. float widthScore = 0.0f; float additionalPenalty = 0.0f; if ((atEnd || !mJustified) && delta < 0F) { widthScore = SCORE_OVERFULL; } else if (atEnd && mStrategy != kBreakStrategy_Balanced) { // increase penalty for hyphen on last line additionalPenalty = LAST_LINE_PENALTY_MULTIPLIER * mCandidates[j].penalty; // Penalize very short (< 1 - shortLineFactor of total width) lines. float underfill = delta - shortLineFactor * width; widthScore = underfill > 0F ? underfill * underfill : 0; } else { widthScore = delta * delta; if (delta < 0F) { if (-delta < maxShrink * (mCandidates[i].postSpaceCount - mCandidates[j].preSpaceCount)) { widthScore *= SHRINK_PENALTY_MULTIPLIER; } else { widthScore = SCORE_OVERFULL; } } } if (delta < 0F) { //C++ TO C# CONVERTER TODO TASK: The following line was determined to be a copy assignment (rather than a reference assignment) - this should be verified and a 'CopyFrom' method should be created: //ORIGINAL LINE: active = j + 1; active.CopyFrom(j + 1); } else { bestHope = widthScore; } float score = jScore + widthScore + additionalPenalty; if (score <= best) { best = score; //C++ TO C# CONVERTER TODO TASK: The following line was determined to be a copy assignment (rather than a reference assignment) - this should be verified and a 'CopyFrom' method should be created: //ORIGINAL LINE: bestPrev = j; bestPrev.CopyFrom(j); } } mCandidates[i].score = best + mCandidates[i].penalty + mLinePenalty; mCandidates[i].prev = bestPrev; mCandidates[i].lineNumber = mCandidates[bestPrev].lineNumber + 1; #if VERBOSE_DEBUG ALOGD("break %zd: score=%g, prev=%zd", i, mCandidates[i].score, mCandidates[i].prev); #endif } finishBreaksOptimal(); }
// TODO: this class is actually fairly close to being general and not tied to // using Minikin to do the shaping of the strings. The main thing that would // need to be changed is having some kind of callback (or virtual class, or // maybe even template), which could easily be instantiated with Minikin's // Layout. Future work for when needed. public float addStyleRun(MinikinPaint paint, FontCollection typeface, FontStyle style, int start, int end, bool isRtl) { float width = 0.0f; int bidiFlags = isRtl ? kBidi_Force_RTL : kBidi_Force_LTR; float hyphenPenalty = 0.0F; if (paint != null) { width = Layout.measureText(mTextBuf.data(), start, end - start, mTextBuf.size(), bidiFlags, style, paint, typeface, mCharWidths.data() + start); // a heuristic that seems to perform well hyphenPenalty = 0.5 * paint.size * paint.scaleX * mLineWidths.getLineWidth(0); if (mHyphenationFrequency == kHyphenationFrequency_Normal) { hyphenPenalty *= 4.0; // TODO: Replace with a better value after some testing } if (mJustified) { // Make hyphenation more aggressive for fully justified text (so that // "normal" in justified mode is the same as "full" in ragged-right). hyphenPenalty *= 0.25; } else { // Line penalty is zero for justified text. mLinePenalty = Math.Max(mLinePenalty, hyphenPenalty * LINE_PENALTY_MULTIPLIER); } } int current = (int)mWordBreaker.current(); int afterWord = start; int lastBreak = start; ParaWidth lastBreakWidth = mWidth; ParaWidth postBreak = mWidth; int postSpaceCount = mSpaceCount; for (int i = start; i < end; i++) { UInt16 c = mTextBuf[i]; if (c == CHAR_TAB) { mWidth = mPreBreak + mTabStops.nextTab(mWidth - mPreBreak); if (mFirstTabIndex == INT_MAX) { mFirstTabIndex = (int)i; } // fall back to greedy; other modes don't know how to deal with tabs mStrategy = kBreakStrategy_Greedy; } else { if (isWordSpace(new UInt16(c))) { mSpaceCount += 1; } mWidth += mCharWidths[i]; if (!isLineEndSpace(new UInt16(c))) { postBreak = mWidth; postSpaceCount = mSpaceCount; //C++ TO C# CONVERTER TODO TASK: The following line was determined to be a copy assignment (rather than a reference assignment) - this should be verified and a 'CopyFrom' method should be created: //ORIGINAL LINE: afterWord = i + 1; afterWord.CopyFrom(i + 1); } } if (i + 1 == current != null) { int wordStart = mWordBreaker.wordStart(); int wordEnd = mWordBreaker.wordEnd(); if (paint != null && mHyphenator != null && mHyphenationFrequency != kHyphenationFrequency_None && wordStart >= start != null && wordEnd > wordStart && wordEnd - wordStart <= LONGEST_HYPHENATED_WORD) { mHyphenator.hyphenate(mHyphBuf, mTextBuf[wordStart], wordEnd - wordStart, mLocale); #if VERBOSE_DEBUG string hyphenatedString; for (int j = wordStart; j < wordEnd; j++) { if (mHyphBuf[j - wordStart] == HyphenationType.BREAK_AND_INSERT_HYPHEN) { hyphenatedString.push_back('-'); } // Note: only works with ASCII, should do UTF-8 conversion here hyphenatedString.push_back(buffer()[j]); } ALOGD("hyphenated string: %s", hyphenatedString); #endif // measure hyphenated substrings for (int j = wordStart; j < wordEnd; j++) { HyphenationType hyph = mHyphBuf[j - wordStart]; if (hyph != HyphenationType.DONT_BREAK) { paint.hyphenEdit = HyphenEdit.editForThisLine(hyph); float firstPartWidth = Layout.measureText(mTextBuf.data(), lastBreak, j - lastBreak, mTextBuf.size(), bidiFlags, style, paint, typeface, null); ParaWidth hyphPostBreak = lastBreakWidth + firstPartWidth; paint.hyphenEdit = HyphenEdit.editForNextLine(hyph); float secondPartWidth = Layout.measureText(mTextBuf.data(), j, afterWord - j, mTextBuf.size(), bidiFlags, style, paint, typeface, null); ParaWidth hyphPreBreak = postBreak - secondPartWidth; addWordBreak(j, hyphPreBreak, hyphPostBreak, postSpaceCount, postSpaceCount, hyphenPenalty, hyph); paint.hyphenEdit = HyphenEdit.NO_EDIT; } } } // Skip break for zero-width characters inside replacement span if (paint != null || current == end || mCharWidths[current] > 0) { float penalty = hyphenPenalty * mWordBreaker.breakBadness(); addWordBreak(current, mWidth, postBreak, mSpaceCount, postSpaceCount, penalty, HyphenationType.DONT_BREAK); } //C++ TO C# CONVERTER TODO TASK: The following line was determined to be a copy assignment (rather than a reference assignment) - this should be verified and a 'CopyFrom' method should be created: //ORIGINAL LINE: lastBreak = current; lastBreak.CopyFrom(current); lastBreakWidth = mWidth; //C++ TO C# CONVERTER TODO TASK: The following line was determined to be a copy assignment (rather than a reference assignment) - this should be verified and a 'CopyFrom' method should be created: //ORIGINAL LINE: current = (int)mWordBreaker.next(); current.CopyFrom((int)mWordBreaker.next()); } } return(width); }