bool FontFallbackIterator::rangeSetContributesForHint(const Vector<UChar32> hintList, const FontDataForRangeSet& segmentedFace) { for (auto it = hintList.begin(); it != hintList.end(); ++it) { if (segmentedFace.contains(*it)) { if (!alreadyLoadingRangeForHintChar(*it)) return true; } } return false; }
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(); }
const FontDataForRangeSet FontFallbackIterator::next(const Vector<UChar32>& hintList) { if (m_fallbackStage == OutOfLuck) return FontDataForRangeSet(); if (m_fallbackStage == FallbackPriorityFonts) { // Only try one fallback priority font, // then proceed to regular system fallback. m_fallbackStage = SystemFonts; FontDataForRangeSet fallbackPriorityFontRange(fallbackPriorityFont(hintList[0])); if (fallbackPriorityFontRange.hasFontData()) return fallbackPriorityFontRange; return next(hintList); } if (m_fallbackStage == SystemFonts) { // We've reached pref + system fallback. ASSERT(hintList.size()); RefPtr<SimpleFontData> systemFont = uniqueSystemFontForHint(hintList[0]); if (systemFont) return FontDataForRangeSet(systemFont); // If we don't have options from the system fallback anymore or had // previously returned them, we only have the last resort font left. // TODO: crbug.com/42217 Improve this by doing the last run with a last // resort font that has glyphs for everything, for example the Unicode // LastResort font, not just Times or Arial. FontCache* fontCache = FontCache::fontCache(); m_fallbackStage = OutOfLuck; RefPtr<SimpleFontData> lastResort = fontCache->getLastResortFallbackFont(m_fontDescription).get(); RELEASE_ASSERT(lastResort); return FontDataForRangeSet(lastResort); } ASSERT(m_fallbackStage == FontGroupFonts || m_fallbackStage == SegmentedFace); const FontData* fontData = m_fontFallbackList->fontDataAt( m_fontDescription, m_currentFontDataIndex); if (!fontData) { // If there is no fontData coming from the fallback list, it means // we are now looking at system fonts, either for prioritized symbol // or emoji fonts or by calling system fallback API. m_fallbackStage = isNonTextFallbackPriority(m_fontFallbackPriority) ? FallbackPriorityFonts : SystemFonts; return next(hintList); } // Otherwise we've received a fontData from the font-family: set of fonts, // and a non-segmented one in this case. if (!fontData->isSegmented()) { // Skip forward to the next font family for the next call to next(). m_currentFontDataIndex++; if (!fontData->isLoading()) { RefPtr<SimpleFontData> nonSegmented = const_cast<SimpleFontData*>(toSimpleFontData(fontData)); return FontDataForRangeSet(nonSegmented); } return next(hintList); } // Iterate over ranges of a segmented font below. const SegmentedFontData* segmented = toSegmentedFontData(fontData); if (m_fallbackStage != SegmentedFace) { m_segmentedFaceIndex = 0; m_fallbackStage = SegmentedFace; } ASSERT(m_segmentedFaceIndex < segmented->numFaces()); FontDataForRangeSet currentSegmentedFace = segmented->faceAt(m_segmentedFaceIndex); m_segmentedFaceIndex++; if (m_segmentedFaceIndex == segmented->numFaces()) { // Switch from iterating over a segmented face to the next family from // the font-family: group of fonts. m_fallbackStage = FontGroupFonts; m_currentFontDataIndex++; } if (rangeSetContributesForHint(hintList, currentSegmentedFace)) { if (currentSegmentedFace.fontData()->customFontData()) currentSegmentedFace.fontData()->customFontData()->beginLoadIfNeeded(); if (!currentSegmentedFace.fontData()->isLoading()) return currentSegmentedFace; m_trackedLoadingRangeSets.append(currentSegmentedFace); } return next(hintList); }
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(); }