unsigned SimpleShaper::advanceInternal(TextIterator& textIterator, GlyphBuffer* glyphBuffer) { bool hasExtraSpacing = (m_font->fontDescription().letterSpacing() || m_font->fontDescription().wordSpacing() || m_expansion) && !m_run.spacingDisabled(); const SimpleFontData* primaryFont = m_font->primaryFont(); const SimpleFontData* lastFontData = primaryFont; bool normalizeSpace = m_run.normalizeSpace(); CharacterData charData; while (textIterator.consume(charData.character, charData.clusterLength)) { charData.characterOffset = textIterator.currentCharacter(); GlyphData glyphData = glyphDataForCharacter(charData, normalizeSpace); // Some fonts do not have a glyph for zero-width-space, // in that case use the space character and override the width. float width; if (!glyphData.glyph && Character::treatAsZeroWidthSpaceInComplexScript(charData.character)) { charData.character = space; glyphData = glyphDataForCharacter(charData); width = 0; } else { width = characterWidth(charData.character, glyphData); } Glyph glyph = glyphData.glyph; const SimpleFontData* fontData = glyphData.fontData; ASSERT(fontData); if (m_fallbackFonts && lastFontData != fontData && width) { lastFontData = fontData; cacheFallbackFont(fontData, primaryFont); } if (hasExtraSpacing) width = adjustSpacing(width, charData, *fontData, glyphBuffer); if (m_bounds) updateGlyphBounds(glyphData, width, !charData.characterOffset); if (m_forTextEmphasis && !Character::canReceiveTextEmphasis(charData.character)) glyph = 0; // Advance past the character we just dealt with. textIterator.advance(charData.clusterLength); m_runWidthSoFar += width; if (glyphBuffer) glyphBuffer->add(glyph, fontData, width); } unsigned consumedCharacters = textIterator.currentCharacter() - m_currentCharacter; m_currentCharacter = textIterator.currentCharacter(); return consumedCharacters; }
// FIXME: This function may not work if the emphasis mark uses a complex script, but none of the // standard emphasis marks do so. bool Font::getEmphasisMarkGlyphData(const AtomicString& mark, GlyphData& glyphData) const { if (mark.isEmpty()) return false; #if ENABLE(SVG_FONTS) // FIXME: Implement for SVG fonts. if (primaryFont()->isSVGFont()) return false; #endif UChar32 character = mark[0]; if (U16_IS_SURROGATE(character)) { if (!U16_IS_SURROGATE_LEAD(character)) return false; if (mark.length() < 2) return false; UChar low = mark[1]; if (!U16_IS_TRAIL(low)) return false; character = U16_GET_SUPPLEMENTARY(character, low); } glyphData = glyphDataForCharacter(character, false, EmphasisMarkVariant); return true; }
void FontCascadeFonts::GlyphPageCacheEntry::setGlyphDataForCharacter(UChar32 character, GlyphData glyphData) { ASSERT(!glyphDataForCharacter(character).glyph); if (!m_mixedFont) { m_mixedFont = std::make_unique<MixedFontGlyphPage>(m_singleFont.get()); m_singleFont = nullptr; } m_mixedFont->setGlyphDataForCharacter(character, glyphData); }
const SimpleFontData* Font::fontDataForCombiningCharacterSequence(const UChar* characters, size_t length, FontDataVariant variant) const { UChar32 baseCharacter; size_t baseCharacterLength = 0; U16_NEXT(characters, baseCharacterLength, length, baseCharacter); GlyphData baseCharacterGlyphData = glyphDataForCharacter(baseCharacter, false, variant); if (!baseCharacterGlyphData.glyph) return 0; if (length == baseCharacterLength) return baseCharacterGlyphData.fontData; bool triedBaseCharacterFontData = false; unsigned i = 0; for (const FontData* fontData = fontDataAt(0); fontData; fontData = fontDataAt(++i)) { const SimpleFontData* simpleFontData = fontData->fontDataForCharacter(baseCharacter); if (variant == NormalVariant) { if (simpleFontData->platformData().orientation() == Vertical) { if (isCJKIdeographOrSymbol(baseCharacter) && !simpleFontData->hasVerticalGlyphs()) { variant = BrokenIdeographVariant; simpleFontData = simpleFontData->brokenIdeographFontData().get(); } else if (m_fontDescription.nonCJKGlyphOrientation() == NonCJKGlyphOrientationVerticalRight) { SimpleFontData* verticalRightFontData = simpleFontData->verticalRightOrientationFontData().get(); Glyph verticalRightGlyph = verticalRightFontData->glyphForCharacter(baseCharacter); if (verticalRightGlyph == baseCharacterGlyphData.glyph) simpleFontData = verticalRightFontData; } else { SimpleFontData* uprightFontData = simpleFontData->uprightOrientationFontData().get(); Glyph uprightGlyph = uprightFontData->glyphForCharacter(baseCharacter); if (uprightGlyph != baseCharacterGlyphData.glyph) simpleFontData = uprightFontData; } } } else { if (const SimpleFontData* variantFontData = simpleFontData->variantFontData(m_fontDescription, variant).get()) simpleFontData = variantFontData; } if (simpleFontData == baseCharacterGlyphData.fontData) triedBaseCharacterFontData = true; if (simpleFontData->canRenderCombiningCharacterSequence(characters, length)) return simpleFontData; } if (!triedBaseCharacterFontData && baseCharacterGlyphData.fontData && baseCharacterGlyphData.fontData->canRenderCombiningCharacterSequence(characters, length)) return baseCharacterGlyphData.fontData; return SimpleFontData::systemFallback(); }
// FIXME: This function may not work if the emphasis mark uses a complex script, but none of the // standard emphasis marks do so. bool Font::getEmphasisMarkGlyphData(const AtomicString& mark, GlyphData& glyphData) const { if (mark.isEmpty()) return false; UChar32 character = mark[0]; if (U16_IS_SURROGATE(character)) { if (!U16_IS_SURROGATE_LEAD(character)) return false; if (mark.length() < 2) return false; UChar low = mark[1]; if (!U16_IS_TRAIL(low)) return false; character = U16_GET_SUPPLEMENTARY(character, low); } glyphData = glyphDataForCharacter(character, false, EmphasisMarkVariant); return true; }
GlyphData FontCascadeFonts::glyphDataForSystemFallback(UChar32 c, const FontCascadeDescription& description, FontVariant variant) { // System fallback is character-dependent. auto& primaryRanges = realizeFallbackRangesAt(description, 0); auto* originalFont = primaryRanges.fontForCharacter(c); if (!originalFont) originalFont = &primaryRanges.fontForFirstRange(); auto systemFallbackFont = originalFont->systemFallbackFontForCharacter(c, description, m_isForPlatformFont); if (!systemFallbackFont) return GlyphData(); if (systemFallbackFont->platformData().orientation() == Vertical && !systemFallbackFont->hasVerticalGlyphs() && FontCascade::isCJKIdeographOrSymbol(c)) variant = BrokenIdeographVariant; GlyphData fallbackGlyphData; if (variant == NormalVariant) fallbackGlyphData = systemFallbackFont->glyphDataForCharacter(c); else fallbackGlyphData = systemFallbackFont->variantFont(description, variant)->glyphDataForCharacter(c); if (fallbackGlyphData.font && fallbackGlyphData.font->platformData().orientation() == Vertical && !fallbackGlyphData.font->isTextOrientationFallback()) { if (variant == NormalVariant && !FontCascade::isCJKIdeographOrSymbol(c)) fallbackGlyphData = glyphDataForNonCJKCharacterWithGlyphOrientation(c, description.nonCJKGlyphOrientation(), fallbackGlyphData); #if PLATFORM(COCOA) || USE(CAIRO) if (fallbackGlyphData.font->platformData().syntheticOblique() && FontCascade::isCJKIdeographOrSymbol(c)) fallbackGlyphData = glyphDataForCJKCharacterWithoutSyntheticItalic(c, fallbackGlyphData); #endif } // Keep the system fallback fonts we use alive. if (fallbackGlyphData.glyph) m_systemFallbackFontSet.add(WTFMove(systemFallbackFont)); return fallbackGlyphData; }
inline unsigned WidthIterator::advanceInternal(TextIterator& textIterator, GlyphBuffer* glyphBuffer) { bool rtl = m_run.rtl(); bool hasExtraSpacing = (m_font->letterSpacing() || m_font->wordSpacing() || m_expansion) && !m_run.spacingDisabled(); float widthSinceLastRounding = m_runWidthSoFar; m_runWidthSoFar = floorf(m_runWidthSoFar); widthSinceLastRounding -= m_runWidthSoFar; float lastRoundingWidth = m_finalRoundingWidth; FloatRect bounds; const SimpleFontData* primaryFont = m_font->primaryFont(); const SimpleFontData* lastFontData = primaryFont; int lastGlyphCount = glyphBuffer ? glyphBuffer->size() : 0; UChar32 character = 0; unsigned clusterLength = 0; CharactersTreatedAsSpace charactersTreatedAsSpace; while (textIterator.consume(character, clusterLength)) { unsigned advanceLength = clusterLength; const GlyphData& glyphData = glyphDataForCharacter(character, rtl, textIterator.currentCharacter(), advanceLength); Glyph glyph = glyphData.glyph; const SimpleFontData* fontData = glyphData.fontData; ASSERT(fontData); // Now that we have a glyph and font data, get its width. float width; if (character == '\t' && m_run.allowTabs()) width = m_font->tabWidth(*fontData, m_run.tabSize(), m_run.xPos() + m_runWidthSoFar + widthSinceLastRounding); else { width = fontData->widthForGlyph(glyph); #if ENABLE(SVG) // SVG uses horizontalGlyphStretch(), when textLength is used to stretch/squeeze text. width *= m_run.horizontalGlyphStretch(); #endif // We special case spaces in two ways when applying word rounding. // First, we round spaces to an adjusted width in all fonts. // Second, in fixed-pitch fonts we ensure that all characters that // match the width of the space character have the same width as the space character. if (m_run.applyWordRounding() && width == fontData->spaceWidth() && (fontData->pitch() == FixedPitch || glyph == fontData->spaceGlyph())) width = fontData->adjustedSpaceWidth(); } if (fontData != lastFontData && width) { if (shouldApplyFontTransforms()) m_runWidthSoFar += applyFontTransforms(glyphBuffer, m_run.ltr(), lastGlyphCount, lastFontData, m_typesettingFeatures, charactersTreatedAsSpace); lastFontData = fontData; if (m_fallbackFonts && fontData != primaryFont) { // FIXME: This does a little extra work that could be avoided if // glyphDataForCharacter() returned whether it chose to use a small caps font. if (!m_font->isSmallCaps() || character == toUpper(character)) m_fallbackFonts->add(fontData); else { const GlyphData& uppercaseGlyphData = m_font->glyphDataForCharacter(toUpper(character), rtl); if (uppercaseGlyphData.fontData != primaryFont) m_fallbackFonts->add(uppercaseGlyphData.fontData); } } } if (hasExtraSpacing) { // Account for letter-spacing. if (width && m_font->letterSpacing()) width += m_font->letterSpacing(); static bool expandAroundIdeographs = Font::canExpandAroundIdeographsInComplexText(); bool treatAsSpace = Font::treatAsSpace(character); if (treatAsSpace || (expandAroundIdeographs && Font::isCJKIdeographOrSymbol(character))) { // Distribute the run's total expansion evenly over all expansion opportunities in the run. if (m_expansion) { float previousExpansion = m_expansion; if (!treatAsSpace && !m_isAfterExpansion) { // Take the expansion opportunity before this ideograph. m_expansion -= m_expansionPerOpportunity; float expansionAtThisOpportunity = !m_run.applyWordRounding() ? m_expansionPerOpportunity : roundf(previousExpansion) - roundf(m_expansion); m_runWidthSoFar += expansionAtThisOpportunity; if (glyphBuffer) { if (glyphBuffer->isEmpty()) glyphBuffer->add(fontData->spaceGlyph(), fontData, expansionAtThisOpportunity); else glyphBuffer->expandLastAdvance(expansionAtThisOpportunity); } previousExpansion = m_expansion; } if (m_run.allowsTrailingExpansion() || (m_run.ltr() && textIterator.currentCharacter() + advanceLength < static_cast<size_t>(m_run.length())) || (m_run.rtl() && textIterator.currentCharacter())) { m_expansion -= m_expansionPerOpportunity; width += !m_run.applyWordRounding() ? m_expansionPerOpportunity : roundf(previousExpansion) - roundf(m_expansion); m_isAfterExpansion = true; } } else m_isAfterExpansion = false; // Account for word spacing. // We apply additional space between "words" by adding width to the space character. if (treatAsSpace && (character != '\t' || !m_run.allowTabs()) && textIterator.currentCharacter() && m_font->wordSpacing()) width += m_font->wordSpacing(); } else m_isAfterExpansion = false; } if (shouldApplyFontTransforms() && glyphBuffer && Font::treatAsSpace(character)) charactersTreatedAsSpace.append(make_pair(glyphBuffer->size(), OriginalAdvancesForCharacterTreatedAsSpace(character == ' ', glyphBuffer->size() ? glyphBuffer->advanceAt(glyphBuffer->size() - 1) : 0, width))); if (m_accountForGlyphBounds) { bounds = fontData->boundsForGlyph(glyph); if (!textIterator.currentCharacter()) m_firstGlyphOverflow = max<float>(0, -bounds.x()); } if (m_forTextEmphasis && !Font::canReceiveTextEmphasis(character)) glyph = 0; // Advance past the character we just dealt with. textIterator.advance(advanceLength); float oldWidth = width; // Force characters that are used to determine word boundaries for the rounding hack // to be integer width, so following words will start on an integer boundary. if (m_run.applyWordRounding() && Font::isRoundingHackCharacter(character)) { width = ceilf(width); // Since widthSinceLastRounding can lose precision if we include measurements for // preceding whitespace, we bypass it here. m_runWidthSoFar += width; // Since this is a rounding hack character, we should have reset this sum on the previous // iteration. ASSERT(!widthSinceLastRounding); } else { // Check to see if the next character is a "rounding hack character", if so, adjust // width so that the total run width will be on an integer boundary. if ((m_run.applyWordRounding() && textIterator.currentCharacter() < m_run.length() && Font::isRoundingHackCharacter(*(textIterator.characters()))) || (m_run.applyRunRounding() && textIterator.currentCharacter() >= m_run.length())) { float totalWidth = widthSinceLastRounding + width; widthSinceLastRounding = ceilf(totalWidth); width += widthSinceLastRounding - totalWidth; m_runWidthSoFar += widthSinceLastRounding; widthSinceLastRounding = 0; } else widthSinceLastRounding += width; } if (glyphBuffer) glyphBuffer->add(glyph, fontData, (rtl ? oldWidth + lastRoundingWidth : width)); lastRoundingWidth = width - oldWidth; if (m_accountForGlyphBounds) { m_maxGlyphBoundingBoxY = max(m_maxGlyphBoundingBoxY, bounds.maxY()); m_minGlyphBoundingBoxY = min(m_minGlyphBoundingBoxY, bounds.y()); m_lastGlyphOverflow = max<float>(0, bounds.maxX() - width); } } if (shouldApplyFontTransforms()) m_runWidthSoFar += applyFontTransforms(glyphBuffer, m_run.ltr(), lastGlyphCount, lastFontData, m_typesettingFeatures, charactersTreatedAsSpace); unsigned consumedCharacters = textIterator.currentCharacter() - m_currentCharacter; m_currentCharacter = textIterator.currentCharacter(); m_runWidthSoFar += widthSinceLastRounding; m_finalRoundingWidth = lastRoundingWidth; return consumedCharacters; }
unsigned SimpleShaper::advanceInternal(TextIterator& textIterator, GlyphBuffer* glyphBuffer) { bool hasExtraSpacing = (m_font->getFontDescription().letterSpacing() || m_font->getFontDescription().wordSpacing() || m_expansion) && !m_textRun.spacingDisabled(); const SimpleFontData* lastFontData = m_font->primaryFont(); bool normalizeSpace = m_textRun.normalizeSpace(); const float initialRunWidth = m_runWidthSoFar; CharacterData charData; while (textIterator.consume(charData.character)) { charData.characterOffset = textIterator.offset(); charData.clusterLength = textIterator.glyphLength(); GlyphData glyphData = glyphDataForCharacter(charData, normalizeSpace); // Some fonts do not have a glyph for zero-width-space, // in that case use the space character and override the width. float width; bool spaceUsedAsZeroWidthSpace = false; if (!glyphData.glyph && Character::treatAsZeroWidthSpace(charData.character)) { charData.character = spaceCharacter; glyphData = glyphDataForCharacter(charData); width = 0; spaceUsedAsZeroWidthSpace = true; } else { width = characterWidth(charData.character, glyphData); } Glyph glyph = glyphData.glyph; const SimpleFontData* fontData = glyphData.fontData; ASSERT(fontData); if (m_fallbackFonts && lastFontData != fontData && width) { lastFontData = fontData; trackNonPrimaryFallbackFont(fontData); } if (hasExtraSpacing && !spaceUsedAsZeroWidthSpace) width = adjustSpacing(width, charData); if (m_glyphBoundingBox) { ASSERT(glyphData.fontData); FloatRect glyphBounds = glyphData.fontData->boundsForGlyph(glyphData.glyph); // We are handling simple text run here, so Y-Offset will be zero. // FIXME: Computing bounds relative to the initial advance seems odd. Are we adjusting // these someplace else? If not, we'll end up with different bounds depending on how // we segment our advance() calls. glyphBounds.move(m_runWidthSoFar - initialRunWidth, 0); m_glyphBoundingBox->unite(glyphBounds); } if (glyphBuffer) { if (!forTextEmphasis()) { glyphBuffer->add(glyph, fontData, m_runWidthSoFar); } else if (Character::canReceiveTextEmphasis(charData.character)) { addEmphasisMark(glyphBuffer, m_runWidthSoFar + width / 2); } } // Advance past the character we just dealt with. textIterator.advance(); m_runWidthSoFar += width; } unsigned consumedCharacters = textIterator.offset() - m_currentCharacter; m_currentCharacter = textIterator.offset(); return consumedCharacters; }