Index: Source/platform/fonts/mac/ComplexTextController.cpp |
diff --git a/Source/platform/fonts/mac/ComplexTextController.cpp b/Source/platform/fonts/mac/ComplexTextController.cpp |
new file mode 100644 |
index 0000000000000000000000000000000000000000..b3048b5c13eaa898f2fd4f271dbd44a426b2081e |
--- /dev/null |
+++ b/Source/platform/fonts/mac/ComplexTextController.cpp |
@@ -0,0 +1,614 @@ |
+/* |
+ * Copyright (C) 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved. |
+ * |
+ * Redistribution and use in source and binary forms, with or without |
+ * modification, are permitted provided that the following conditions |
+ * are met: |
+ * 1. Redistributions of source code must retain the above copyright |
+ * notice, this list of conditions and the following disclaimer. |
+ * 2. Redistributions in binary form must reproduce the above copyright |
+ * notice, this list of conditions and the following disclaimer in the |
+ * documentation and/or other materials provided with the distribution. |
+ * |
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY |
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
+ * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON |
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
+ */ |
+ |
+#include "config.h" |
+#include "platform/fonts/mac/ComplexTextController.h" |
+ |
+#include "platform/fonts/Character.h" |
+#include "platform/fonts/Font.h" |
+#include "platform/fonts/GlyphBuffer.h" |
+#include "platform/geometry/FloatSize.h" |
+#include "platform/text/TextBreakIterator.h" |
+#include "platform/text/TextRun.h" |
+#include "wtf/StdLibExtras.h" |
+#include "wtf/unicode/CharacterNames.h" |
+#include <ApplicationServices/ApplicationServices.h> |
+ |
+namespace blink { |
+ |
+ComplexTextController::ComplexTextController(const Font* font, const TextRun& run, bool mayUseNaturalWritingDirection, HashSet<const SimpleFontData*>* fallbackFonts, bool forTextEmphasis) |
+ : m_font(*font) |
+ , m_run(run) |
+ , m_isLTROnly(true) |
+ , m_mayUseNaturalWritingDirection(mayUseNaturalWritingDirection) |
+ , m_forTextEmphasis(forTextEmphasis) |
+ , m_currentCharacter(0) |
+ , m_end(run.length()) |
+ , m_totalWidth(0) |
+ , m_runWidthSoFar(0) |
+ , m_numGlyphsSoFar(0) |
+ , m_currentRun(0) |
+ , m_glyphInCurrentRun(0) |
+ , m_characterInCurrentGlyph(0) |
+ , m_expansion(run.expansion()) |
+ , m_leadingExpansion(0) |
+ , m_afterExpansion(!run.allowsLeadingExpansion()) |
+ , m_fallbackFonts(fallbackFonts) |
+ , m_minGlyphBoundingBoxX(std::numeric_limits<float>::max()) |
+ , m_maxGlyphBoundingBoxX(std::numeric_limits<float>::min()) |
+ , m_minGlyphBoundingBoxY(std::numeric_limits<float>::max()) |
+ , m_maxGlyphBoundingBoxY(std::numeric_limits<float>::min()) |
+{ |
+ if (!m_expansion) |
+ m_expansionPerOpportunity = 0; |
+ else { |
+ bool isAfterExpansion = m_afterExpansion; |
+ unsigned expansionOpportunityCount; |
+ if (m_run.is8Bit()) |
+ expansionOpportunityCount = Character::expansionOpportunityCount(m_run.characters8(), m_end, m_run.direction(), isAfterExpansion); |
+ else |
+ expansionOpportunityCount = Character::expansionOpportunityCount(m_run.characters16(), m_end, m_run.direction(), isAfterExpansion); |
+ if (isAfterExpansion && !m_run.allowsTrailingExpansion()) |
+ expansionOpportunityCount--; |
+ |
+ if (!expansionOpportunityCount) |
+ m_expansionPerOpportunity = 0; |
+ else |
+ m_expansionPerOpportunity = m_expansion / expansionOpportunityCount; |
+ } |
+ |
+ collectComplexTextRuns(); |
+ adjustGlyphsAndAdvances(); |
+ |
+ if (!m_isLTROnly) { |
+ m_runIndices.reserveInitialCapacity(m_complexTextRuns.size()); |
+ |
+ m_glyphCountFromStartToIndex.reserveInitialCapacity(m_complexTextRuns.size()); |
+ unsigned glyphCountSoFar = 0; |
+ for (unsigned i = 0; i < m_complexTextRuns.size(); ++i) { |
+ m_glyphCountFromStartToIndex.uncheckedAppend(glyphCountSoFar); |
+ glyphCountSoFar += m_complexTextRuns[i]->glyphCount(); |
+ } |
+ } |
+ |
+ m_runWidthSoFar = m_leadingExpansion; |
+} |
+ |
+int ComplexTextController::offsetForPosition(float h, bool includePartialGlyphs) |
+{ |
+ if (h >= m_totalWidth) |
+ return m_run.ltr() ? m_end : 0; |
+ |
+ h -= m_leadingExpansion; |
+ if (h < 0) |
+ return m_run.ltr() ? 0 : m_end; |
+ |
+ CGFloat x = h; |
+ |
+ size_t runCount = m_complexTextRuns.size(); |
+ size_t offsetIntoAdjustedGlyphs = 0; |
+ |
+ for (size_t r = 0; r < runCount; ++r) { |
+ const ComplexTextRun& complexTextRun = *m_complexTextRuns[r]; |
+ for (unsigned j = 0; j < complexTextRun.glyphCount(); ++j) { |
+ CGFloat adjustedAdvance = m_adjustedAdvances[offsetIntoAdjustedGlyphs + j].width; |
+ if (x < adjustedAdvance) { |
+ CFIndex hitGlyphStart = complexTextRun.indexAt(j); |
+ CFIndex hitGlyphEnd; |
+ if (m_run.ltr()) |
+ hitGlyphEnd = std::max<CFIndex>(hitGlyphStart, j + 1 < complexTextRun.glyphCount() ? complexTextRun.indexAt(j + 1) : static_cast<CFIndex>(complexTextRun.indexEnd())); |
+ else |
+ hitGlyphEnd = std::max<CFIndex>(hitGlyphStart, j > 0 ? complexTextRun.indexAt(j - 1) : static_cast<CFIndex>(complexTextRun.indexEnd())); |
+ |
+ // FIXME: Instead of dividing the glyph's advance equally between the characters, this |
+ // could use the glyph's "ligature carets". However, there is no Core Text API to get the |
+ // ligature carets. |
+ CFIndex hitIndex = hitGlyphStart + (hitGlyphEnd - hitGlyphStart) * (m_run.ltr() ? x / adjustedAdvance : 1 - x / adjustedAdvance); |
+ int stringLength = complexTextRun.stringLength(); |
+ TextBreakIterator* cursorPositionIterator = cursorMovementIterator(complexTextRun.characters(), stringLength); |
+ int clusterStart; |
+ if (cursorPositionIterator->isBoundary(hitIndex)) |
+ clusterStart = hitIndex; |
+ else { |
+ clusterStart = cursorPositionIterator->preceding(hitIndex); |
+ if (clusterStart == TextBreakDone) |
+ clusterStart = 0; |
+ } |
+ |
+ if (!includePartialGlyphs) |
+ return complexTextRun.stringLocation() + clusterStart; |
+ |
+ int clusterEnd = cursorPositionIterator->following(hitIndex); |
+ if (clusterEnd == TextBreakDone) |
+ clusterEnd = stringLength; |
+ |
+ CGFloat clusterWidth; |
+ // FIXME: The search stops at the boundaries of complexTextRun. In theory, it should go on into neighboring ComplexTextRuns |
+ // derived from the same CTLine. In practice, we do not expect there to be more than one CTRun in a CTLine, as no |
+ // reordering and no font fallback should occur within a CTLine. |
+ if (clusterEnd - clusterStart > 1) { |
+ clusterWidth = adjustedAdvance; |
+ int firstGlyphBeforeCluster = j - 1; |
+ while (firstGlyphBeforeCluster >= 0 && complexTextRun.indexAt(firstGlyphBeforeCluster) >= clusterStart && complexTextRun.indexAt(firstGlyphBeforeCluster) < clusterEnd) { |
+ CGFloat width = m_adjustedAdvances[offsetIntoAdjustedGlyphs + firstGlyphBeforeCluster].width; |
+ clusterWidth += width; |
+ x += width; |
+ firstGlyphBeforeCluster--; |
+ } |
+ unsigned firstGlyphAfterCluster = j + 1; |
+ while (firstGlyphAfterCluster < complexTextRun.glyphCount() && complexTextRun.indexAt(firstGlyphAfterCluster) >= clusterStart && complexTextRun.indexAt(firstGlyphAfterCluster) < clusterEnd) { |
+ clusterWidth += m_adjustedAdvances[offsetIntoAdjustedGlyphs + firstGlyphAfterCluster].width; |
+ firstGlyphAfterCluster++; |
+ } |
+ } else { |
+ clusterWidth = adjustedAdvance / (hitGlyphEnd - hitGlyphStart); |
+ x -= clusterWidth * (m_run.ltr() ? hitIndex - hitGlyphStart : hitGlyphEnd - hitIndex - 1); |
+ } |
+ if (x <= clusterWidth / 2) |
+ return complexTextRun.stringLocation() + (m_run.ltr() ? clusterStart : clusterEnd); |
+ else |
+ return complexTextRun.stringLocation() + (m_run.ltr() ? clusterEnd : clusterStart); |
+ } |
+ x -= adjustedAdvance; |
+ } |
+ offsetIntoAdjustedGlyphs += complexTextRun.glyphCount(); |
+ } |
+ |
+ ASSERT_NOT_REACHED(); |
+ return 0; |
+} |
+ |
+static bool advanceByCombiningCharacterSequence(const UChar*& iterator, const UChar* end, UChar32& baseCharacter, unsigned& markCount) |
+{ |
+ ASSERT(iterator < end); |
+ |
+ markCount = 0; |
+ |
+ baseCharacter = *iterator++; |
+ |
+ if (U16_IS_SURROGATE(baseCharacter)) { |
+ if (!U16_IS_LEAD(baseCharacter)) |
+ return false; |
+ if (iterator == end) |
+ return false; |
+ UChar trail = *iterator++; |
+ if (!U16_IS_TRAIL(trail)) |
+ return false; |
+ baseCharacter = U16_GET_SUPPLEMENTARY(baseCharacter, trail); |
+ } |
+ |
+ // Consume marks. |
+ while (iterator < end) { |
+ UChar32 nextCharacter; |
+ int markLength = 0; |
+ U16_NEXT(iterator, markLength, end - iterator, nextCharacter); |
+ if (!(U_GET_GC_MASK(nextCharacter) & U_GC_M_MASK)) |
+ break; |
+ markCount += markLength; |
+ iterator += markLength; |
+ } |
+ |
+ return true; |
+} |
+ |
+void ComplexTextController::collectComplexTextRuns() |
+{ |
+ if (!m_end) |
+ return; |
+ |
+ // We break up glyph run generation for the string by FontData. |
+ const UChar* cp; |
+ |
+ if (m_run.is8Bit()) { |
+ String stringFor8BitRun = String::make16BitFrom8BitSource(m_run.characters8(), m_run.length()); |
+ cp = stringFor8BitRun.characters16(); |
+ m_stringsFor8BitRuns.append(stringFor8BitRun); |
+ } else |
+ cp = m_run.characters16(); |
+ |
+ if (m_font.fontDescription().variant() == FontVariantSmallCaps) |
+ m_smallCapsBuffer.resize(m_end); |
+ |
+ unsigned indexOfFontTransition = 0; |
+ const UChar* curr = cp; |
+ const UChar* end = cp + m_end; |
+ |
+ const SimpleFontData* fontData; |
+ bool isMissingGlyph; |
+ const SimpleFontData* nextFontData; |
+ bool nextIsMissingGlyph; |
+ |
+ unsigned markCount; |
+ const UChar* sequenceStart = curr; |
+ UChar32 baseCharacter; |
+ if (!advanceByCombiningCharacterSequence(curr, end, baseCharacter, markCount)) |
+ return; |
+ |
+ UChar uppercaseCharacter = 0; |
+ |
+ bool isSmallCaps; |
+ bool nextIsSmallCaps = m_font.fontDescription().variant() == FontVariantSmallCaps && !(U_GET_GC_MASK(baseCharacter) & U_GC_M_MASK) && (uppercaseCharacter = u_toupper(baseCharacter)) != baseCharacter; |
+ |
+ if (nextIsSmallCaps) { |
+ m_smallCapsBuffer[sequenceStart - cp] = uppercaseCharacter; |
+ for (unsigned i = 0; i < markCount; ++i) |
+ m_smallCapsBuffer[sequenceStart - cp + i + 1] = sequenceStart[i + 1]; |
+ } |
+ |
+ nextIsMissingGlyph = false; |
+ nextFontData = m_font.fontDataForCombiningCharacterSequence(sequenceStart, curr - sequenceStart, nextIsSmallCaps ? SmallCapsVariant : NormalVariant); |
+ if (!nextFontData) |
+ nextIsMissingGlyph = true; |
+ |
+ while (curr < end) { |
+ fontData = nextFontData; |
+ isMissingGlyph = nextIsMissingGlyph; |
+ isSmallCaps = nextIsSmallCaps; |
+ int index = curr - cp; |
+ |
+ if (!advanceByCombiningCharacterSequence(curr, end, baseCharacter, markCount)) |
+ return; |
+ |
+ if (m_font.fontDescription().variant()) { |
+ nextIsSmallCaps = (uppercaseCharacter = u_toupper(baseCharacter)) != baseCharacter; |
+ if (nextIsSmallCaps) { |
+ m_smallCapsBuffer[index] = uppercaseCharacter; |
+ for (unsigned i = 0; i < markCount; ++i) |
+ m_smallCapsBuffer[index + i + 1] = cp[index + i + 1]; |
+ } |
+ } |
+ |
+ nextIsMissingGlyph = false; |
+ if (baseCharacter == zeroWidthJoiner) |
+ nextFontData = fontData; |
+ else { |
+ nextFontData = m_font.fontDataForCombiningCharacterSequence(cp + index, curr - cp - index, nextIsSmallCaps ? SmallCapsVariant : NormalVariant); |
+ if (!nextFontData) |
+ nextIsMissingGlyph = true; |
+ } |
+ |
+ if (nextFontData != fontData || nextIsMissingGlyph != isMissingGlyph) { |
+ int itemStart = static_cast<int>(indexOfFontTransition); |
+ int itemLength = index - indexOfFontTransition; |
+ collectComplexTextRunsForCharacters((isSmallCaps ? m_smallCapsBuffer.data() : cp) + itemStart, itemLength, itemStart, !isMissingGlyph ? fontData : 0); |
+ indexOfFontTransition = index; |
+ } |
+ } |
+ |
+ int itemLength = m_end - indexOfFontTransition; |
+ if (itemLength) { |
+ int itemStart = indexOfFontTransition; |
+ collectComplexTextRunsForCharacters((nextIsSmallCaps ? m_smallCapsBuffer.data() : cp) + itemStart, itemLength, itemStart, !nextIsMissingGlyph ? nextFontData : 0); |
+ } |
+ |
+ if (!m_run.ltr()) |
+ m_complexTextRuns.reverse(); |
+} |
+ |
+CFIndex ComplexTextController::ComplexTextRun::indexAt(size_t i) const |
+{ |
+ return m_coreTextIndices[i]; |
+} |
+ |
+void ComplexTextController::ComplexTextRun::setIsNonMonotonic() |
+{ |
+ ASSERT(m_isMonotonic); |
+ m_isMonotonic = false; |
+ |
+ Vector<bool, 64> mappedIndices(m_stringLength); |
+ for (size_t i = 0; i < m_glyphCount; ++i) { |
+ ASSERT(indexAt(i) < static_cast<CFIndex>(m_stringLength)); |
+ mappedIndices[indexAt(i)] = true; |
+ } |
+ |
+ m_glyphEndOffsets.grow(m_glyphCount); |
+ for (size_t i = 0; i < m_glyphCount; ++i) { |
+ CFIndex nextMappedIndex = m_indexEnd; |
+ for (size_t j = indexAt(i) + 1; j < m_stringLength; ++j) { |
+ if (mappedIndices[j]) { |
+ nextMappedIndex = j; |
+ break; |
+ } |
+ } |
+ m_glyphEndOffsets[i] = nextMappedIndex; |
+ } |
+} |
+ |
+unsigned ComplexTextController::findNextRunIndex(unsigned runIndex) const |
+{ |
+ const unsigned runOffset = stringEnd(*m_complexTextRuns[runIndex]); |
+ |
+ // Finds the run with the lowest stringBegin() offset that starts at or |
+ // after |runOffset|. |
+ // |
+ // Note that this can't just find a run whose stringBegin() equals the |
+ // stringEnd() of the previous run because CoreText on Mac OS X 10.6 does |
+ // not return runs covering BiDi control chars, so this has to handle the |
+ // resulting gaps. |
+ unsigned result = 0; |
+ unsigned lowestOffset = UINT_MAX; |
+ for (unsigned i = 0; i < m_complexTextRuns.size(); ++i) { |
+ unsigned offset = stringBegin(*m_complexTextRuns[i]); |
+ if (i != runIndex && offset >= runOffset && offset < lowestOffset) { |
+ lowestOffset = offset; |
+ result = i; |
+ } |
+ } |
+ |
+ ASSERT(lowestOffset != UINT_MAX); |
+ return result; |
+} |
+ |
+unsigned ComplexTextController::indexOfCurrentRun(unsigned& leftmostGlyph) |
+{ |
+ leftmostGlyph = 0; |
+ |
+ size_t runCount = m_complexTextRuns.size(); |
+ if (m_currentRun >= runCount) |
+ return runCount; |
+ |
+ if (m_isLTROnly) { |
+ for (unsigned i = 0; i < m_currentRun; ++i) |
+ leftmostGlyph += m_complexTextRuns[i]->glyphCount(); |
+ return m_currentRun; |
+ } |
+ |
+ if (m_runIndices.isEmpty()) { |
+ unsigned firstRun = 0; |
+ unsigned firstRunOffset = stringBegin(*m_complexTextRuns[0]); |
+ for (unsigned i = 1; i < runCount; ++i) { |
+ unsigned offset = stringBegin(*m_complexTextRuns[i]); |
+ if (offset < firstRunOffset) { |
+ firstRun = i; |
+ firstRunOffset = offset; |
+ } |
+ } |
+ m_runIndices.uncheckedAppend(firstRun); |
+ } |
+ |
+ while (m_runIndices.size() <= m_currentRun) { |
+ m_runIndices.uncheckedAppend(findNextRunIndex(m_runIndices.last())); |
+ } |
+ |
+ unsigned currentRunIndex = m_runIndices[m_currentRun]; |
+ leftmostGlyph = m_glyphCountFromStartToIndex[currentRunIndex]; |
+ return currentRunIndex; |
+} |
+ |
+unsigned ComplexTextController::incrementCurrentRun(unsigned& leftmostGlyph) |
+{ |
+ if (m_isLTROnly) { |
+ leftmostGlyph += m_complexTextRuns[m_currentRun++]->glyphCount(); |
+ return m_currentRun; |
+ } |
+ |
+ m_currentRun++; |
+ leftmostGlyph = 0; |
+ return indexOfCurrentRun(leftmostGlyph); |
+} |
+ |
+void ComplexTextController::advance(unsigned offset, GlyphBuffer* glyphBuffer, GlyphIterationStyle iterationStyle, HashSet<const SimpleFontData*>* fallbackFonts) |
+{ |
+ if (static_cast<int>(offset) > m_end) |
+ offset = m_end; |
+ |
+ if (offset <= m_currentCharacter) { |
+ m_runWidthSoFar = m_leadingExpansion; |
+ m_numGlyphsSoFar = 0; |
+ m_currentRun = 0; |
+ m_glyphInCurrentRun = 0; |
+ m_characterInCurrentGlyph = 0; |
+ } |
+ |
+ m_currentCharacter = offset; |
+ |
+ size_t runCount = m_complexTextRuns.size(); |
+ |
+ unsigned leftmostGlyph = 0; |
+ unsigned currentRunIndex = indexOfCurrentRun(leftmostGlyph); |
+ while (m_currentRun < runCount) { |
+ const ComplexTextRun& complexTextRun = *m_complexTextRuns[currentRunIndex]; |
+ bool ltr = complexTextRun.isLTR(); |
+ size_t glyphCount = complexTextRun.glyphCount(); |
+ unsigned g = ltr ? m_glyphInCurrentRun : glyphCount - 1 - m_glyphInCurrentRun; |
+ unsigned k = leftmostGlyph + g; |
+ if (fallbackFonts && complexTextRun.fontData() != m_font.primaryFont()) |
+ fallbackFonts->add(complexTextRun.fontData()); |
+ |
+ while (m_glyphInCurrentRun < glyphCount) { |
+ unsigned glyphStartOffset = complexTextRun.indexAt(g); |
+ unsigned glyphEndOffset; |
+ if (complexTextRun.isMonotonic()) { |
+ if (ltr) |
+ glyphEndOffset = std::max<unsigned>(glyphStartOffset, static_cast<unsigned>(g + 1 < glyphCount ? complexTextRun.indexAt(g + 1) : complexTextRun.indexEnd())); |
+ else |
+ glyphEndOffset = std::max<unsigned>(glyphStartOffset, static_cast<unsigned>(g > 0 ? complexTextRun.indexAt(g - 1) : complexTextRun.indexEnd())); |
+ } else |
+ glyphEndOffset = complexTextRun.endOffsetAt(g); |
+ |
+ CGSize adjustedAdvance = m_adjustedAdvances[k]; |
+ |
+ if (glyphStartOffset + complexTextRun.stringLocation() >= m_currentCharacter) |
+ return; |
+ |
+ if (glyphBuffer && !m_characterInCurrentGlyph) |
+ glyphBuffer->add(m_adjustedGlyphs[k], complexTextRun.fontData(), FloatSize(adjustedAdvance)); |
+ |
+ unsigned oldCharacterInCurrentGlyph = m_characterInCurrentGlyph; |
+ m_characterInCurrentGlyph = std::min(m_currentCharacter - complexTextRun.stringLocation(), glyphEndOffset) - glyphStartOffset; |
+ // FIXME: Instead of dividing the glyph's advance equally between the characters, this |
+ // could use the glyph's "ligature carets". However, there is no Core Text API to get the |
+ // ligature carets. |
+ if (glyphStartOffset == glyphEndOffset) { |
+ // When there are multiple glyphs per character we need to advance by the full width of the glyph. |
+ ASSERT(m_characterInCurrentGlyph == oldCharacterInCurrentGlyph); |
+ m_runWidthSoFar += adjustedAdvance.width; |
+ } else if (iterationStyle == ByWholeGlyphs) { |
+ if (!oldCharacterInCurrentGlyph) |
+ m_runWidthSoFar += adjustedAdvance.width; |
+ } else |
+ m_runWidthSoFar += adjustedAdvance.width * (m_characterInCurrentGlyph - oldCharacterInCurrentGlyph) / (glyphEndOffset - glyphStartOffset); |
+ |
+ if (glyphEndOffset + complexTextRun.stringLocation() > m_currentCharacter) |
+ return; |
+ |
+ m_numGlyphsSoFar++; |
+ m_glyphInCurrentRun++; |
+ m_characterInCurrentGlyph = 0; |
+ if (ltr) { |
+ g++; |
+ k++; |
+ } else { |
+ g--; |
+ k--; |
+ } |
+ } |
+ currentRunIndex = incrementCurrentRun(leftmostGlyph); |
+ m_glyphInCurrentRun = 0; |
+ } |
+} |
+ |
+void ComplexTextController::adjustGlyphsAndAdvances() |
+{ |
+ CGFloat widthSinceLastCommit = 0; |
+ size_t runCount = m_complexTextRuns.size(); |
+ bool hasExtraSpacing = (m_font.fontDescription().letterSpacing() || m_font.fontDescription().wordSpacing() || m_expansion) && !m_run.spacingDisabled(); |
+ for (size_t r = 0; r < runCount; ++r) { |
+ ComplexTextRun& complexTextRun = *m_complexTextRuns[r]; |
+ unsigned glyphCount = complexTextRun.glyphCount(); |
+ const SimpleFontData* fontData = complexTextRun.fontData(); |
+ |
+ if (!complexTextRun.isLTR()) |
+ m_isLTROnly = false; |
+ |
+ const CGGlyph* glyphs = complexTextRun.glyphs(); |
+ const CGSize* advances = complexTextRun.advances(); |
+ |
+ bool lastRun = r + 1 == runCount; |
+ bool roundsAdvances = fontData->platformData().roundsGlyphAdvances(); |
+ float spaceWidth = fontData->spaceWidth() - fontData->syntheticBoldOffset(); |
+ const UChar* cp = complexTextRun.characters(); |
+ CGPoint glyphOrigin = CGPointZero; |
+ CFIndex lastCharacterIndex = m_run.ltr() ? std::numeric_limits<CFIndex>::min() : std::numeric_limits<CFIndex>::max(); |
+ bool isMonotonic = true; |
+ |
+ for (unsigned i = 0; i < glyphCount; i++) { |
+ CFIndex characterIndex = complexTextRun.indexAt(i); |
+ if (m_run.ltr()) { |
+ if (characterIndex < lastCharacterIndex) |
+ isMonotonic = false; |
+ } else { |
+ if (characterIndex > lastCharacterIndex) |
+ isMonotonic = false; |
+ } |
+ UChar ch = *(cp + characterIndex); |
+ bool lastGlyph = lastRun && i + 1 == glyphCount; |
+ UChar nextCh; |
+ if (lastGlyph) |
+ nextCh = ' '; |
+ else if (i + 1 < glyphCount) |
+ nextCh = *(cp + complexTextRun.indexAt(i + 1)); |
+ else |
+ nextCh = *(m_complexTextRuns[r + 1]->characters() + m_complexTextRuns[r + 1]->indexAt(0)); |
+ |
+ bool treatAsSpace = Character::treatAsSpace(ch); |
+ CGGlyph glyph = treatAsSpace ? fontData->spaceGlyph() : glyphs[i]; |
+ CGSize advance = treatAsSpace ? CGSizeMake(spaceWidth, advances[i].height) : advances[i]; |
+ |
+ if (ch == '\t' && m_run.allowTabs()) { |
+ advance.width = m_font.tabWidth(*fontData, m_run.tabSize(), m_run.xPos() + m_totalWidth + widthSinceLastCommit); |
+ } else if (Character::treatAsZeroWidthSpace(ch) && !treatAsSpace) { |
+ advance.width = 0; |
+ glyph = fontData->spaceGlyph(); |
+ } |
+ |
+ float roundedAdvanceWidth = roundf(advance.width); |
+ if (roundsAdvances) |
+ advance.width = roundedAdvanceWidth; |
+ |
+ advance.width += fontData->syntheticBoldOffset(); |
+ |
+ if (hasExtraSpacing) { |
+ // If we're a glyph with an advance, go ahead and add in letter-spacing. |
+ // That way we weed out zero width lurkers. This behavior matches the fast text code path. |
+ if (advance.width && m_font.fontDescription().letterSpacing()) |
+ advance.width += m_font.fontDescription().letterSpacing(); |
+ |
+ // Handle justification and word-spacing. |
+ if (treatAsSpace || Character::isCJKIdeographOrSymbol(ch)) { |
+ // Distribute the run's total expansion evenly over all expansion opportunities in the run. |
+ if (m_expansion) { |
+ if (!treatAsSpace && !m_afterExpansion) { |
+ // Take the expansion opportunity before this ideograph. |
+ m_expansion -= m_expansionPerOpportunity; |
+ float expansionAtThisOpportunity = m_expansionPerOpportunity; |
+ m_totalWidth += expansionAtThisOpportunity; |
+ if (m_adjustedAdvances.isEmpty()) |
+ m_leadingExpansion = expansionAtThisOpportunity; |
+ else |
+ m_adjustedAdvances.last().width += expansionAtThisOpportunity; |
+ } |
+ if (!lastGlyph || m_run.allowsTrailingExpansion()) { |
+ m_expansion -= m_expansionPerOpportunity; |
+ advance.width += m_expansionPerOpportunity; |
+ m_afterExpansion = true; |
+ } |
+ } else |
+ m_afterExpansion = false; |
+ |
+ // Account for word-spacing. |
+ if (treatAsSpace && (ch != '\t' || !m_run.allowTabs()) && (characterIndex > 0 || r > 0) && m_font.fontDescription().wordSpacing()) |
+ advance.width += m_font.fontDescription().wordSpacing(); |
+ } else |
+ m_afterExpansion = false; |
+ } |
+ |
+ widthSinceLastCommit += advance.width; |
+ |
+ // FIXME: Combining marks should receive a text emphasis mark if they are combine with a space. |
+ if (m_forTextEmphasis && (!Character::canReceiveTextEmphasis(ch) || (U_GET_GC_MASK(ch) & U_GC_M_MASK))) |
+ glyph = 0; |
+ |
+ advance.height *= -1; |
+ m_adjustedAdvances.append(advance); |
+ m_adjustedGlyphs.append(glyph); |
+ |
+ FloatRect glyphBounds = fontData->boundsForGlyph(glyph); |
+ glyphBounds.move(glyphOrigin.x, glyphOrigin.y); |
+ m_minGlyphBoundingBoxX = std::min(m_minGlyphBoundingBoxX, glyphBounds.x()); |
+ m_maxGlyphBoundingBoxX = std::max(m_maxGlyphBoundingBoxX, glyphBounds.maxX()); |
+ m_minGlyphBoundingBoxY = std::min(m_minGlyphBoundingBoxY, glyphBounds.y()); |
+ m_maxGlyphBoundingBoxY = std::max(m_maxGlyphBoundingBoxY, glyphBounds.maxY()); |
+ glyphOrigin.x += advance.width; |
+ glyphOrigin.y += advance.height; |
+ |
+ lastCharacterIndex = characterIndex; |
+ } |
+ if (!isMonotonic) |
+ complexTextRun.setIsNonMonotonic(); |
+ } |
+ m_totalWidth += widthSinceLastCommit; |
+} |
+ |
+} // namespace blink |