public Layout get(LayoutCacheKey key, LayoutContext ctx, FontCollection collection) { Layout layout = mCache.get(key); if (layout == null) { key.copyText(); layout = new Layout(); key.doLayout(layout, ctx, collection); mCache.put(key, layout); } return(layout); }
public void doLayout(UInt16 buf, int start, int count, int bufSize, bool isRtl, FontStyle style, MinikinPaint paint, FontCollection collection) { std::lock_guard <std::recursive_mutex> _l = new std::lock_guard <std::recursive_mutex>(gMinikinLock); LayoutContext ctx = new LayoutContext(); //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: ctx.style = style; ctx.style.CopyFrom(style); //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: ctx.paint = paint; ctx.paint.CopyFrom(paint); reset(); mAdvances.resize(count, 0); doLayoutRunCached(buf, start, count, bufSize, isRtl, ctx, start, collection, this, null); ctx.clearHbFonts(); }
// Lay out a single bidi run public void doLayoutRun(UInt16[] buf, int start, int count, int bufSize, bool isRtl, LayoutContext ctx, FontCollection collection) { hb_buffer_t buffer = LayoutEngine.getInstance().hbBuffer; vector <FontCollection.Run> items = new vector <FontCollection.Run>(); collection.itemize(buf + start, count, ctx.style, items); vector <hb_feature_t> features = new vector <hb_feature_t>(); // Disable default-on non-required ligature features if letter-spacing // See http://dev.w3.org/csswg/css-text-3/#letter-spacing-property // "When the effective spacing between two characters is not zero (due to // either justification or a non-zero value of letter-spacing), user agents // should not apply optional ligatures." if (Math.Abs(ctx.paint.letterSpacing) > 0.03) { hb_feature_t no_liga = new hb_feature_t(HB_TAG('l', 'i', 'g', 'a'), 0, 0, ~0u); hb_feature_t no_clig = new hb_feature_t(HB_TAG('c', 'l', 'i', 'g'), 0, 0, ~0u); features.push_back(no_liga); features.push_back(no_clig); } addFeatures(ctx.paint.fontFeatureSettings, features); double size = ctx.paint.size; double scaleX = ctx.paint.scaleX; float x = mAdvance; float y = 0F; for (int run_ix = isRtl ? items.size() - 1 : 0; isRtl?run_ix >= 0 : run_ix < (int)items.size(); isRtl ?--run_ix :++run_ix) { FontCollection.Run run = items[run_ix]; if (run.fakedFont.font == null) { ALOGE("no font for run starting u+%04x length %d", buf[run.start], run.end - run.start); continue; } int font_ix = findFace(run.fakedFont, ctx); ctx.paint.font = mFaces[font_ix].font; ctx.paint.fakery = mFaces[font_ix].fakery; HarfBuzzSharp.Font hbFont = ctx.hbFonts[font_ix]; #if VERBOSE_DEBUG ALOGD("Run %zu, font %d [%d:%d]", run_ix, font_ix, run.start, run.end); #endif hb_font_set_ppem(hbFont, size * scaleX, size); hb_font_set_scale(hbFont, HBFloatToFixed(size * scaleX), HBFloatToFixed(size)); bool is_color_bitmap_font = isColorBitmapFont(hbFont); // TODO: if there are multiple scripts within a font in an RTL run, // we need to reorder those runs. This is unlikely with our current // font stack, but should be done for correctness. // Note: scriptRunStart and scriptRunEnd, as well as run.start and run.end, // run between 0 and count. uint scriptRunEnd; for (uint scriptRunStart = run.start; scriptRunStart < run.end; scriptRunStart = scriptRunEnd) { scriptRunEnd = scriptRunStart; hb_script_t script = getScriptRun(buf + start, run.end, ref scriptRunEnd); // After the last line, scriptRunEnd is guaranteed to have increased, // since the only time getScriptRun does not increase its iterator is when // it has already reached the end of the buffer. But that can't happen, // since if we have already reached the end of the buffer, we should have // had (scriptRunEnd == run.end), which means (scriptRunStart == run.end) // which is impossible due to the exit condition of the for loop. So we // can be sure that scriptRunEnd > scriptRunStart. double letterSpace = 0.0; double letterSpaceHalfLeft = 0.0; double letterSpaceHalfRight = 0.0; if (ctx.paint.letterSpacing != 0.0 && isScriptOkForLetterspacing(new hb_script_t(script))) { letterSpace = ctx.paint.letterSpacing * size * scaleX; if ((ctx.paint.paintFlags & LinearTextFlag) == 0) { letterSpace = Math.Round(letterSpace); letterSpaceHalfLeft = Math.Floor(letterSpace * 0.5); } else { letterSpaceHalfLeft = letterSpace * 0.5; } letterSpaceHalfRight = letterSpace - letterSpaceHalfLeft; } hb_buffer_clear_contents(buffer); hb_buffer_set_script(buffer, script); hb_buffer_set_direction(buffer, isRtl ? HB_DIRECTION_RTL : HB_DIRECTION_LTR); FontLanguages langList = FontLanguageListCache.getById(ctx.style.getLanguageListId()); if (langList.size() != 0) { FontLanguage hbLanguage = langList[0]; for (int i = 0; i < langList.size(); ++i) { if (langList[i].supportsHbScript(script)) { hbLanguage = langList[i]; break; } } hb_buffer_set_language(buffer, hbLanguage.getHbLanguage()); } uint clusterStart = addToHbBuffer(buffer, new UInt16(buf), start, count, bufSize, scriptRunStart, scriptRunEnd, ctx.paint.hyphenEdit, hbFont); hb_shape(hbFont, buffer, features.empty() ? null : features[0], features.size()); uint numGlyphs; hb_glyph_info_t[] info = hb_buffer_get_glyph_infos(buffer, numGlyphs); hb_glyph_position_t positions = hb_buffer_get_glyph_positions(buffer, null); // At this point in the code, the cluster values in the info buffer // correspond to the input characters with some shift. The cluster value // clusterStart corresponds to the first character passed to HarfBuzz, // which is at buf[start + scriptRunStart] whose advance needs to be saved // into mAdvances[scriptRunStart]. So cluster values need to be reduced by // (clusterStart - scriptRunStart) to get converted to indices of // mAdvances. uint clusterOffset = clusterStart - scriptRunStart; if (numGlyphs != 0) { mAdvances[info[0].cluster - clusterOffset] += letterSpaceHalfLeft; x += letterSpaceHalfLeft; } for (uint i = 0; i < numGlyphs; i++) { #if VERBOSE_DEBUG ALOGD("%d %d %d %d", positions[i].x_advance, positions[i].y_advance, positions[i].x_offset, positions[i].y_offset); ALOGD("DoLayout %u: %f; %d, %d", info[i].codepoint, HBFixedToFloat(positions[i].x_advance), positions[i].x_offset, positions[i].y_offset); #endif if (i > 0 && info[i - 1].cluster != info[i].cluster) { mAdvances[info[i - 1].cluster - clusterOffset] += letterSpaceHalfRight; mAdvances[info[i].cluster - clusterOffset] += letterSpaceHalfLeft; x += letterSpace; } hb_codepoint_t glyph_ix = info[i].codepoint; float xoff = HBFixedToFloat(positions[i].x_offset); float yoff = -HBFixedToFloat(positions[i].y_offset); xoff += yoff * ctx.paint.skewX; LayoutGlyph glyph = new LayoutGlyph(font_ix, glyph_ix, x + xoff, y + yoff, (uint)(info[i].cluster - clusterOffset)); mGlyphs.push_back(glyph); float xAdvance = HBFixedToFloat(positions[i].x_advance); if ((ctx.paint.paintFlags & LinearTextFlag) == 0) { xAdvance = roundf(xAdvance); } MinikinRect glyphBounds = new MinikinRect(); hb_glyph_extents_t extents = new hb_glyph_extents_t(); if (is_color_bitmap_font && hb_font_get_glyph_extents(hbFont, glyph_ix, extents)) { // Note that it is technically possible for a TrueType font to have // outline and embedded bitmap at the same time. We ignore modified // bbox of hinted outline glyphs in that case. glyphBounds.mLeft = roundf(HBFixedToFloat(extents.x_bearing)); glyphBounds.mTop = roundf(HBFixedToFloat(-extents.y_bearing)); glyphBounds.mRight = roundf(HBFixedToFloat(extents.x_bearing + extents.width)); glyphBounds.mBottom = roundf(HBFixedToFloat(-extents.y_bearing - extents.height)); } else { ctx.paint.font.GetBounds(glyphBounds, glyph_ix, ctx.paint); } glyphBounds.offset(x + xoff, y + yoff); mBounds.join(glyphBounds); if ((int)(info[i].cluster - clusterOffset) < count) { mAdvances[info[i].cluster - clusterOffset] += xAdvance; } else { ALOGE("cluster %zu (start %zu) out of bounds of count %zu", info[i].cluster - clusterOffset, start, count); } x += xAdvance; } if (numGlyphs != 0) { mAdvances[info[numGlyphs - 1].cluster - clusterOffset] += letterSpaceHalfRight; x += letterSpaceHalfRight; } } } mAdvance = x; }
// Lay out a single word public float doLayoutWord(UInt16[] buf, int start, int count, int bufSize, bool isRtl, LayoutContext ctx, int bufStart, FontCollection collection, Layout layout, float[] advances) { LayoutCache cache = LayoutEngine.getInstance().layoutCache.functorMethod; LayoutCacheKey key = new LayoutCacheKey(collection, ctx.paint, new FontStyle(ctx.style), new UInt16(buf), start, count, bufSize, isRtl); float wordSpacing = count == 1 && isWordSpace(buf[start]) ? ctx.paint.wordSpacing : 0F; float advance; if (ctx.paint.skipCache()) { Layout layoutForWord = new Layout(); key.doLayout(layoutForWord, ctx, collection); if (layout != null) { layout.appendLayout(layoutForWord, bufStart, wordSpacing); } if (advances != 0F) { layoutForWord.getAdvances(advances); } advance = layoutForWord.getAdvance(); } else { Layout layoutForWord = cache.get(key, ctx, collection); if (layout != null) { layout.appendLayout(layoutForWord, bufStart, wordSpacing); } if (advances != 0F) { layoutForWord.getAdvances(advances); } advance = layoutForWord.getAdvance(); } if (wordSpacing != 0F) { advance += wordSpacing; if (advances != 0F) { advances[0] += wordSpacing; } } return(advance); }
// Lay out a single bidi run // When layout is not null, layout info will be stored in the object. // When advances is not null, measurement results will be stored in the array. public float doLayoutRunCached(UInt16 buf, int start, int count, int bufSize, bool isRtl, LayoutContext ctx, int dstStart, FontCollection collection, Layout layout, ref float advances) { uint originalHyphen = ctx.paint.hyphenEdit.getHyphen(); float advance = 0F; if (!isRtl) { // left to right int wordstart = start == bufSize != null ? start : getPrevWordBreakForCache(buf, start + 1, bufSize); int wordend = new int(); for (int iter = start; iter < start + count; iter = wordend) { wordend = getNextWordBreakForCache(buf, iter, bufSize); // Only apply hyphen to the first or last word in the string. uint hyphen = new uint(originalHyphen); if (iter != start) { // Not the first word hyphen &= ~HyphenEdit.MASK_START_OF_LINE; } if (wordend < start + count) { // Not the last word hyphen &= ~HyphenEdit.MASK_END_OF_LINE; } ctx.paint.hyphenEdit = hyphen; int wordcount = Math.Min(start + count, wordend) - iter; advance += doLayoutWord(buf + wordstart, iter - wordstart, wordcount, wordend - wordstart, isRtl, ctx, iter - dstStart, collection, layout, advances != 0 ? advances + (iter - start) : advances); //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: wordstart = wordend; wordstart.CopyFrom(wordend); } } else { // right to left int wordstart = new int(); int end = start + count; int wordend = end == 0 ? 0 : getNextWordBreakForCache(buf, end - 1, bufSize); for (int iter = end; iter > start; iter = wordstart) { wordstart = getPrevWordBreakForCache(buf, iter, bufSize); // Only apply hyphen to the first (rightmost) or last (leftmost) word in // the string. uint hyphen = new uint(originalHyphen); if (wordstart > start) { // Not the first word hyphen &= ~HyphenEdit.MASK_START_OF_LINE; } if (iter != end) { // Not the last word hyphen &= ~HyphenEdit.MASK_END_OF_LINE; } ctx.paint.hyphenEdit = hyphen; int bufStart = Math.Max(start, wordstart); advance += doLayoutWord(buf + wordstart, bufStart - wordstart, iter - bufStart, wordend - wordstart, isRtl, ctx, bufStart - dstStart, collection, layout, advances != 0 ? advances + (bufStart - start) : advances); //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: wordend = wordstart; wordend.CopyFrom(wordstart); } } return(advance); }
//C++ TO C# CONVERTER WARNING: 'const' methods are not available in C#: //ORIGINAL LINE: void doLayout(Layout* layout, LayoutContext* ctx, const FontCollection*& collection) const public void doLayout(Layout layout, LayoutContext ctx, FontCollection collection) { layout.mAdvances.resize(mCount, 0); ctx.clearHbFonts(); layout.doLayoutRun(mChars, mStart, mCount, mNchars, mIsRtl, ctx, collection); }
// 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); }