bool HarfBuzzShaper::extractShapeResults(hb_buffer_t* harfBuzzBuffer, ShapeResult* shapeResult, bool& fontCycleQueued, const HolesQueueItem& currentQueueItem, const SimpleFontData* currentFont, UScriptCode currentRunScript, bool isLastResort) { enum ClusterResult { Shaped, NotDef, Unknown }; ClusterResult currentClusterResult = Unknown; ClusterResult previousClusterResult = Unknown; unsigned previousCluster = 0; unsigned currentCluster = 0; // Find first notdef glyph in harfBuzzBuffer. unsigned numGlyphs = hb_buffer_get_length(harfBuzzBuffer); hb_glyph_info_t* glyphInfo = hb_buffer_get_glyph_infos(harfBuzzBuffer, 0); unsigned lastChangePosition = 0; if (!numGlyphs) { DLOG(ERROR) << "HarfBuzz returned empty glyph buffer after shaping."; return false; } for (unsigned glyphIndex = 0; glyphIndex <= numGlyphs; ++glyphIndex) { // Iterating by clusters, check for when the state switches from shaped // to non-shaped and vice versa. Taking into account the edge cases of // beginning of the run and end of the run. previousCluster = currentCluster; currentCluster = glyphInfo[glyphIndex].cluster; if (glyphIndex < numGlyphs) { // Still the same cluster, merge shaping status. if (previousCluster == currentCluster && glyphIndex != 0) { if (glyphInfo[glyphIndex].codepoint == 0) { currentClusterResult = NotDef; } else { // We can only call the current cluster fully shapped, if // all characters that are part of it are shaped, so update // currentClusterResult to Shaped only if the previous // characters have been shaped, too. currentClusterResult = currentClusterResult == Shaped ? Shaped : NotDef; } continue; } // We've moved to a new cluster. previousClusterResult = currentClusterResult; currentClusterResult = glyphInfo[glyphIndex].codepoint == 0 ? NotDef : Shaped; } else { // The code below operates on the "flanks"/changes between NotDef // and Shaped. In order to keep the code below from explictly // dealing with character indices and run end, we explicitly // terminate the cluster/run here by setting the result value to the // opposite of what it was, leading to atChange turning true. previousClusterResult = currentClusterResult; currentClusterResult = currentClusterResult == NotDef ? Shaped : NotDef; } bool atChange = (previousClusterResult != currentClusterResult) && previousClusterResult != Unknown; if (!atChange) continue; // Compute the range indices of consecutive shaped or .notdef glyphs. // Cluster information for RTL runs becomes reversed, e.g. character 0 // has cluster index 5 in a run of 6 characters. unsigned numCharacters = 0; unsigned numGlyphsToInsert = 0; unsigned startIndex = 0; if (HB_DIRECTION_IS_FORWARD(hb_buffer_get_direction(harfBuzzBuffer))) { startIndex = glyphInfo[lastChangePosition].cluster; if (glyphIndex == numGlyphs) { numCharacters = currentQueueItem.m_startIndex + currentQueueItem.m_numCharacters - glyphInfo[lastChangePosition].cluster; numGlyphsToInsert = numGlyphs - lastChangePosition; } else { numCharacters = glyphInfo[glyphIndex].cluster - glyphInfo[lastChangePosition].cluster; numGlyphsToInsert = glyphIndex - lastChangePosition; } } else { // Direction Backwards startIndex = glyphInfo[glyphIndex - 1].cluster; if (lastChangePosition == 0) { numCharacters = currentQueueItem.m_startIndex + currentQueueItem.m_numCharacters - glyphInfo[glyphIndex - 1].cluster; } else { numCharacters = glyphInfo[lastChangePosition - 1].cluster - glyphInfo[glyphIndex - 1].cluster; } numGlyphsToInsert = glyphIndex - lastChangePosition; } if (currentClusterResult == Shaped && !isLastResort) { // Now it's clear that we need to continue processing. if (!fontCycleQueued) { appendToHolesQueue(HolesQueueNextFont, 0, 0); fontCycleQueued = true; } // Here we need to put character positions. ASSERT(numCharacters); appendToHolesQueue(HolesQueueRange, startIndex, numCharacters); } // If numCharacters is 0, that means we hit a NotDef before shaping the // whole grapheme. We do not append it here. For the next glyph we // encounter, atChange will be true, and the characters corresponding to // the grapheme will be added to the TODO queue again, attempting to // shape the whole grapheme with the next font. // When we're getting here with the last resort font, we have no other // choice than adding boxes to the ShapeResult. if ((currentClusterResult == NotDef && numCharacters) || isLastResort) { hb_direction_t direction = TextDirectionToHBDirection( m_textRun.direction(), m_font->getFontDescription().orientation(), currentFont); // Here we need to specify glyph positions. ShapeResult::RunInfo* run = new ShapeResult::RunInfo(currentFont, direction, ICUScriptToHBScript(currentRunScript), startIndex, numGlyphsToInsert, numCharacters); shapeResult->insertRun(adoptPtr(run), lastChangePosition, numGlyphsToInsert, harfBuzzBuffer); } lastChangePosition = glyphIndex; } return true; }
PassRefPtr<ShapeResult> HarfBuzzShaper::shapeResult() { RefPtr<ShapeResult> result = ShapeResult::create(m_font, m_normalizedBufferLength, m_textRun.direction()); HarfBuzzScopedPtr<hb_buffer_t> harfBuzzBuffer(hb_buffer_create(), hb_buffer_destroy); const FontDescription& fontDescription = m_font->getFontDescription(); const String& localeString = fontDescription.locale(); CString locale = localeString.latin1(); const hb_language_t language = hb_language_from_string(locale.data(), locale.length()); bool needsCapsHandling = fontDescription.variantCaps() != FontDescription::CapsNormal; OpenTypeCapsSupport capsSupport; RunSegmenter::RunSegmenterRange segmentRange = { 0, 0, USCRIPT_INVALID_CODE, OrientationIterator::OrientationInvalid, FontFallbackPriority::Invalid }; RunSegmenter runSegmenter( m_normalizedBuffer.get(), m_normalizedBufferLength, m_font->getFontDescription().orientation()); Vector<UChar32> fallbackCharsHint; // TODO: Check whether this treatAsZerowidthspace from the previous script // segmentation plays a role here, does the new scriptRuniterator handle that correctly? while (runSegmenter.consume(&segmentRange)) { RefPtr<FontFallbackIterator> fallbackIterator = m_font->createFontFallbackIterator( segmentRange.fontFallbackPriority); appendToHolesQueue(HolesQueueNextFont, 0, 0); appendToHolesQueue(HolesQueueRange, segmentRange.start, segmentRange.end - segmentRange.start); const SimpleFontData* currentFont = nullptr; RefPtr<UnicodeRangeSet> currentFontRangeSet; bool fontCycleQueued = false; while (m_holesQueue.size()) { HolesQueueItem currentQueueItem = m_holesQueue.takeFirst(); if (currentQueueItem.m_action == HolesQueueNextFont) { // For now, we're building a character list with which we probe // for needed fonts depending on the declared unicode-range of a // segmented CSS font. Alternatively, we can build a fake font // for the shaper and check whether any glyphs were found, or // define a new API on the shaper which will give us coverage // information? if (!collectFallbackHintChars(fallbackCharsHint, fallbackIterator->needsHintList())) { // Give up shaping since we cannot retrieve a font fallback // font without a hintlist. m_holesQueue.clear(); break; } FontDataForRangeSet nextFontDataForRangeSet = fallbackIterator->next(fallbackCharsHint); currentFont = nextFontDataForRangeSet.fontData(); currentFontRangeSet = nextFontDataForRangeSet.ranges(); if (!currentFont) { ASSERT(!m_holesQueue.size()); break; } fontCycleQueued = false; continue; } SmallCapsIterator::SmallCapsBehavior smallCapsBehavior = SmallCapsIterator::SmallCapsSameCase; if (needsCapsHandling) { capsSupport = OpenTypeCapsSupport(currentFont->platformData().harfBuzzFace(), fontDescription.variantCaps(), ICUScriptToHBScript(segmentRange.script)); if (capsSupport.needsRunCaseSplitting()) splitUntilNextCaseChange(currentQueueItem, smallCapsBehavior); } ASSERT(currentQueueItem.m_numCharacters); const SimpleFontData* smallcapsAdjustedFont = needsCapsHandling && capsSupport.needsSyntheticFont(smallCapsBehavior) ? currentFont->smallCapsFontData(fontDescription).get() : currentFont; // Compatibility with SimpleFontData approach of keeping a flag for overriding drawing direction. // TODO: crbug.com/506224 This should go away in favor of storing that information elsewhere, for example in // ShapeResult. const SimpleFontData* directionAndSmallCapsAdjustedFont = fontDataAdjustedForOrientation(smallcapsAdjustedFont, m_font->getFontDescription().orientation(), segmentRange.renderOrientation); CaseMapIntend caseMapIntend = CaseMapIntend::KeepSameCase; if (needsCapsHandling) { caseMapIntend = capsSupport.needsCaseChange(smallCapsBehavior); } CaseMappingHarfBuzzBufferFiller( caseMapIntend, harfBuzzBuffer.get(), m_normalizedBuffer.get(), m_normalizedBufferLength, currentQueueItem.m_startIndex, currentQueueItem.m_numCharacters); CapsFeatureSettingsScopedOverlay capsOverlay(m_features, capsSupport.fontFeatureToUse(smallCapsBehavior)); if (!shapeRange(harfBuzzBuffer.get(), currentQueueItem.m_startIndex, currentQueueItem.m_numCharacters, directionAndSmallCapsAdjustedFont, currentFontRangeSet, segmentRange.script, language)) DLOG(ERROR) << "Shaping range failed."; if (!extractShapeResults(harfBuzzBuffer.get(), result.get(), fontCycleQueued, currentQueueItem, directionAndSmallCapsAdjustedFont, segmentRange.script, !fallbackIterator->hasNext())) DLOG(ERROR) << "Shape result extraction failed."; hb_buffer_reset(harfBuzzBuffer.get()); } } return result.release(); }
PassRefPtr<ShapeResult> HarfBuzzShaper::shapeResult() { RefPtr<ShapeResult> result = ShapeResult::create(m_font, m_normalizedBufferLength, m_textRun.direction()); HarfBuzzScopedPtr<hb_buffer_t> harfBuzzBuffer(hb_buffer_create(), hb_buffer_destroy); const FontDescription& fontDescription = m_font->getFontDescription(); const String& localeString = fontDescription.locale(); CString locale = localeString.latin1(); const hb_language_t language = hb_language_from_string(locale.data(), locale.length()); RunSegmenter::RunSegmenterRange segmentRange = { 0, 0, USCRIPT_INVALID_CODE, OrientationIterator::OrientationInvalid, SmallCapsIterator::SmallCapsSameCase, FontFallbackPriority::Invalid }; RunSegmenter runSegmenter( m_normalizedBuffer.get(), m_normalizedBufferLength, m_font->getFontDescription().orientation(), fontDescription.variant()); Vector<UChar32> fallbackCharsHint; // TODO: Check whether this treatAsZerowidthspace from the previous script // segmentation plays a role here, does the new scriptRuniterator handle that correctly? while (runSegmenter.consume(&segmentRange)) { RefPtr<FontFallbackIterator> fallbackIterator = m_font->createFontFallbackIterator( segmentRange.fontFallbackPriority); appendToHolesQueue(HolesQueueNextFont, 0, 0); appendToHolesQueue(HolesQueueRange, segmentRange.start, segmentRange.end - segmentRange.start); const SimpleFontData* currentFont = nullptr; RefPtr<UnicodeRangeSet> currentFontRangeSet; bool fontCycleQueued = false; while (m_holesQueue.size()) { HolesQueueItem currentQueueItem = m_holesQueue.takeFirst(); if (currentQueueItem.m_action == HolesQueueNextFont) { // For now, we're building a character list with which we probe // for needed fonts depending on the declared unicode-range of a // segmented CSS font. Alternatively, we can build a fake font // for the shaper and check whether any glyphs were found, or // define a new API on the shaper which will give us coverage // information? if (!collectFallbackHintChars(fallbackCharsHint, fallbackIterator->needsHintList())) { // Give up shaping since we cannot retrieve a font fallback // font without a hintlist. m_holesQueue.clear(); break; } FontDataForRangeSet nextFontDataForRangeSet = fallbackIterator->next(fallbackCharsHint); currentFont = nextFontDataForRangeSet.fontData().get(); currentFontRangeSet = nextFontDataForRangeSet.ranges(); if (!currentFont) { ASSERT(!m_holesQueue.size()); break; } fontCycleQueued = false; continue; } // TODO crbug.com/522964: Only use smallCapsFontData when the font does not support true smcp. The spec // says: "To match the surrounding text, a font may provide alternate glyphs for caseless characters when // these features are enabled but when a user agent simulates small capitals, it must not attempt to // simulate alternates for codepoints which are considered caseless." const SimpleFontData* smallcapsAdjustedFont = segmentRange.smallCapsBehavior == SmallCapsIterator::SmallCapsUppercaseNeeded ? currentFont->smallCapsFontData(fontDescription).get() : currentFont; // Compatibility with SimpleFontData approach of keeping a flag for overriding drawing direction. // TODO: crbug.com/506224 This should go away in favor of storing that information elsewhere, for example in // ShapeResult. const SimpleFontData* directionAndSmallCapsAdjustedFont = fontDataAdjustedForOrientation(smallcapsAdjustedFont, m_font->getFontDescription().orientation(), segmentRange.renderOrientation); if (!shapeRange(harfBuzzBuffer.get(), currentQueueItem.m_startIndex, currentQueueItem.m_numCharacters, directionAndSmallCapsAdjustedFont, currentFontRangeSet, segmentRange.script, language)) DLOG(ERROR) << "Shaping range failed."; if (!extractShapeResults(harfBuzzBuffer.get(), result.get(), fontCycleQueued, currentQueueItem, directionAndSmallCapsAdjustedFont, segmentRange.script, !fallbackIterator->hasNext())) DLOG(ERROR) << "Shape result extraction failed."; hb_buffer_reset(harfBuzzBuffer.get()); } } return result.release(); }