// 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; }