bool SVGTextLayoutEngine::currentLogicalCharacterMetrics(SVGTextLayoutAttributes& logicalAttributes, SVGTextMetrics& logicalMetrics)
{
    logicalMetrics = SVGTextMetrics::emptyMetrics();

    Vector<SVGTextMetrics>& textMetricsValues = logicalAttributes.textMetricsValues();
    unsigned textMetricsSize = textMetricsValues.size();
    while (true) {
        if (m_logicalMetricsListOffset == textMetricsSize) {
            if (!currentLogicalCharacterAttributes(logicalAttributes))
                return false;

            textMetricsValues = logicalAttributes.textMetricsValues();
            textMetricsSize = textMetricsValues.size();
            continue;
        }

        ASSERT(textMetricsSize);
        ASSERT(m_logicalMetricsListOffset < textMetricsSize);
        logicalMetrics = textMetricsValues.at(m_logicalMetricsListOffset);
        if (logicalMetrics == SVGTextMetrics::emptyMetrics() || (!logicalMetrics.width() && !logicalMetrics.height())) {
            advanceToNextLogicalCharacter(logicalMetrics);
            continue;
        }

        // Stop if we found the next valid logical text metrics object.
        return true;
    }

    ASSERT_NOT_REACHED();
    return true;
}
Esempio n. 2
0
void SVGTextQuery::modifyStartEndPositionsRespectingLigatures(Data* queryData, const SVGTextFragment& fragment, int& startPosition, int& endPosition) const
{
    SVGTextLayoutAttributes* layoutAttributes = queryData->textRenderer->layoutAttributes();
    Vector<SVGTextMetrics>& textMetricsValues = layoutAttributes->textMetricsValues();

    unsigned textMetricsOffset = fragment.metricsListOffset;

    // Compute the offset of the fragment within the box, since that's the
    // space <startPosition, endPosition> is in (and that's what we need).
    int fragmentOffsetInBox = fragment.characterOffset - queryData->textBox->start();
    int fragmentEndInBox = fragmentOffsetInBox + fragment.length;

    // Find the text metrics cell that start at or contain the character startPosition.
    while (fragmentOffsetInBox < fragmentEndInBox) {
        SVGTextMetrics& metrics = textMetricsValues[textMetricsOffset];
        int glyphEnd = fragmentOffsetInBox + metrics.length();
        if (startPosition < glyphEnd)
            break;
        fragmentOffsetInBox = glyphEnd;
        textMetricsOffset++;
    }

    startPosition = fragmentOffsetInBox;

    // Find the text metrics cell that contain or ends at the character endPosition.
    while (fragmentOffsetInBox < fragmentEndInBox) {
        SVGTextMetrics& metrics = textMetricsValues[textMetricsOffset];
        fragmentOffsetInBox += metrics.length();
        if (fragmentOffsetInBox >= endPosition)
            break;
        textMetricsOffset++;
    }

    endPosition = fragmentOffsetInBox;
}
Esempio n. 3
0
void SVGTextLayoutAttributesBuilder::assignEmptyLayoutAttributesForCharacter(SVGTextLayoutAttributes& attributes) const
{
    attributes.xValues().append(SVGTextLayoutAttributes::emptyValue());
    attributes.yValues().append(SVGTextLayoutAttributes::emptyValue());
    attributes.dxValues().append(SVGTextLayoutAttributes::emptyValue());
    attributes.dyValues().append(SVGTextLayoutAttributes::emptyValue());
    attributes.rotateValues().append(SVGTextLayoutAttributes::emptyValue());
    // This doesn't add an empty value to textMetricsValues() on purpose!
}
Esempio n. 4
0
static void measureTextRenderer(RenderSVGInlineText* text, MeasureTextData* data, bool processRenderer)
{
    ASSERT(text);

    SVGTextLayoutAttributes* attributes = text->layoutAttributes();
    Vector<SVGTextMetrics>* textMetricsValues = &attributes->textMetricsValues();
    if (processRenderer) {
        if (data->allCharactersMap)
            attributes->clear();
        else
            textMetricsValues->clear();
    }

    SVGTextMetricsCalculator calculator(text);
    bool preserveWhiteSpace = text->style()->whiteSpace() == PRE;
    unsigned surrogatePairCharacters = 0;
    unsigned skippedCharacters = 0;
    unsigned textPosition = 0;
    unsigned textLength = calculator.textLength();

    SVGTextMetrics currentMetrics;
    for (; textPosition < textLength; textPosition += currentMetrics.length()) {
        currentMetrics = calculator.computeMetricsForCharacter(textPosition);
        if (!currentMetrics.length())
            break;

        bool characterIsWhiteSpace = calculator.characterIsWhiteSpace(textPosition);
        if (characterIsWhiteSpace && !preserveWhiteSpace && data->lastCharacterWasWhiteSpace) {
            if (processRenderer)
                textMetricsValues->append(SVGTextMetrics(SVGTextMetrics::SkippedSpaceMetrics));
            if (data->allCharactersMap)
                skippedCharacters += currentMetrics.length();
            continue;
        }

        if (processRenderer) {
            if (data->allCharactersMap) {
                const SVGCharacterDataMap::const_iterator it = data->allCharactersMap->find(data->valueListPosition + textPosition - skippedCharacters - surrogatePairCharacters + 1);
                if (it != data->allCharactersMap->end())
                    attributes->characterDataMap().set(textPosition + 1, it->value);
            }
            textMetricsValues->append(currentMetrics);
        }

        if (data->allCharactersMap && calculator.characterStartsSurrogatePair(textPosition))
            surrogatePairCharacters++;

        data->lastCharacterWasWhiteSpace = characterIsWhiteSpace;
    }

    if (!data->allCharactersMap)
        return;

    data->valueListPosition += textPosition - skippedCharacters;
}
Esempio n. 5
0
void SVGTextLayoutAttributesBuilder::propagateLayoutAttributes(RenderObject* start, unsigned& atCharacter, UChar& lastCharacter) const
{
    for (RenderObject* child = start->firstChild(); child; child = child->nextSibling()) { 
        if (child->isSVGInlineText()) {
            RenderSVGInlineText* text = toRenderSVGInlineText(child);
            const UChar* characters = text->characters();
            unsigned textLength = text->textLength();
            bool preserveWhiteSpace = shouldPreserveAllWhiteSpace(text->style());

            SVGTextLayoutAttributes attributes;
            attributes.reserveCapacity(textLength);
    
            unsigned valueListPosition = atCharacter;
            unsigned metricsLength = 1;
            for (unsigned textPosition = 0; textPosition < textLength; textPosition += metricsLength) {
                const UChar& currentCharacter = characters[textPosition];

                SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(text, textPosition, 1);
                metricsLength = metrics.length();

                if (!preserveWhiteSpace && characterIsSpace(currentCharacter) && characterIsSpaceOrNull(lastCharacter)) {
                    assignEmptyLayoutAttributesForCharacter(attributes);
                    attributes.textMetricsValues().append(SVGTextMetrics::emptyMetrics());
                    continue;
                }

                assignLayoutAttributesForCharacter(attributes, metrics, valueListPosition);

                if (metricsLength > 1) {
                    for (unsigned i = 0; i < metricsLength - 1; ++i)
                        assignEmptyLayoutAttributesForCharacter(attributes);
                }

                lastCharacter = currentCharacter;
                valueListPosition += metricsLength;
            }

#if DUMP_TEXT_LAYOUT_ATTRIBUTES > 0
            fprintf(stderr, "\nDumping layout attributes for RenderSVGInlineText, renderer=%p, node=%p (atCharacter: %i)\n", text, text->node(), atCharacter);
            attributes.dump();
#endif

            text->storeLayoutAttributes(attributes);
            atCharacter = valueListPosition;
            continue;
        }

        if (!child->isSVGInline())
            continue;

        propagateLayoutAttributes(child, atCharacter, lastCharacter);
    }
}
void SVGTextMetricsBuilder::measureTextRenderer(RenderSVGInlineText* text, MeasureTextData* data)
{
    ASSERT(text);

    SVGTextLayoutAttributes* attributes = text->layoutAttributes();
    Vector<SVGTextMetrics>* textMetricsValues = &attributes->textMetricsValues();
    if (data->processRenderer) {
        if (data->allCharactersMap)
            attributes->clear();
        else
            textMetricsValues->clear();
    }

    initializeMeasurementWithTextRenderer(text);
    bool preserveWhiteSpace = text->style()->whiteSpace() == PRE;
    int surrogatePairCharacters = 0;

    while (advance()) {
        const UChar* currentCharacter = m_run.data16(m_textPosition);
        if (*currentCharacter == ' ' && !preserveWhiteSpace && (!data->lastCharacter || *data->lastCharacter == ' ')) {
            if (data->processRenderer)
                textMetricsValues->append(SVGTextMetrics(SVGTextMetrics::SkippedSpaceMetrics));
            if (data->allCharactersMap)
                data->skippedCharacters += m_currentMetrics.length();
            continue;
        }

        if (data->processRenderer) {
            if (data->allCharactersMap) {
                const SVGCharacterDataMap::const_iterator it = data->allCharactersMap->find(data->valueListPosition + m_textPosition - data->skippedCharacters - surrogatePairCharacters + 1);
                if (it != data->allCharactersMap->end())
                    attributes->characterDataMap().set(m_textPosition + 1, it->value);
            }
            textMetricsValues->append(m_currentMetrics);
        }

        if (data->allCharactersMap && currentCharacterStartsSurrogatePair())
            surrogatePairCharacters++;

        data->lastCharacter = currentCharacter;
    }

    if (!data->allCharactersMap)
        return;

    data->valueListPosition += m_textPosition - data->skippedCharacters;
    data->skippedCharacters = 0;
}
Esempio n. 7
0
void RenderSVGText::subtreeChildWasAdded(RenderObject* child)
{
    ASSERT(child);
    if (!shouldHandleSubtreeMutations() || documentBeingDestroyed())
        return;

    // Always protect the cache before clearing text positioning elements when the cache will subsequently be rebuilt.
    FontCachePurgePreventer fontCachePurgePreventer;

    // The positioning elements cache doesn't include the new 'child' yet. Clear the
    // cache, as the next buildLayoutAttributesForTextRenderer() call rebuilds it.
    m_layoutAttributesBuilder.clearTextPositioningElements();

    if (!child->isSVGInlineText() && !child->isSVGInline())
        return;

    // Detect changes in layout attributes and only measure those text parts that have changed!
    Vector<SVGTextLayoutAttributes*> newLayoutAttributes;
    collectLayoutAttributes(this, newLayoutAttributes);
    if (newLayoutAttributes.isEmpty()) {
        ASSERT(m_layoutAttributes.isEmpty());
        return;
    }

    // Compare m_layoutAttributes with newLayoutAttributes to figure out which attribute got added.
    size_t size = newLayoutAttributes.size();
    SVGTextLayoutAttributes* attributes = 0;
    for (size_t i = 0; i < size; ++i) {
        attributes = newLayoutAttributes[i];
        if (m_layoutAttributes.find(attributes) == notFound) {
            // Every time this is invoked, there's only a single new entry in the newLayoutAttributes list, compared to the old in m_layoutAttributes.
            bool stopAfterNext = false;
            SVGTextLayoutAttributes* previous = 0;
            SVGTextLayoutAttributes* next = 0;
            ASSERT_UNUSED(child, &attributes->context() == child);
            findPreviousAndNextAttributes(this, &attributes->context(), stopAfterNext, previous, next);

            if (previous)
                m_layoutAttributesBuilder.buildLayoutAttributesForTextRenderer(previous->context());
            m_layoutAttributesBuilder.buildLayoutAttributesForTextRenderer(attributes->context());
            if (next)
                m_layoutAttributesBuilder.buildLayoutAttributesForTextRenderer(next->context());
            break;
        }
    }

#ifndef NDEBUG
    // Verify that m_layoutAttributes only differs by a maximum of one entry.
    for (size_t i = 0; i < size; ++i)
        ASSERT(m_layoutAttributes.find(newLayoutAttributes[i]) != notFound || newLayoutAttributes[i] == attributes);
#endif

    m_layoutAttributes = newLayoutAttributes;
}
Esempio n. 8
0
static inline void findFirstAndLastAttributesInVector(Vector<SVGTextLayoutAttributes*>& attributes, RenderSVGInlineText* firstContext, RenderSVGInlineText* lastContext,
                                                      SVGTextLayoutAttributes*& first, SVGTextLayoutAttributes*& last)
{
    first = 0;
    last = 0;

    unsigned attributesSize = attributes.size();
    for (unsigned i = 0; i < attributesSize; ++i) {
        SVGTextLayoutAttributes* current = attributes[i];
        if (!first && firstContext == &current->context())
            first = current;
        if (!last && lastContext == &current->context())
            last = current;
        if (first && last)
            break;
    }

    ASSERT(first);
    ASSERT(last);
}
Esempio n. 9
0
void SVGTextLayoutAttributesBuilder::assignLayoutAttributesForCharacter(SVGTextLayoutAttributes& attributes, SVGTextMetrics& metrics, unsigned valueListPosition) const
{
    attributes.xValues().append(nextLayoutValue(XValueAttribute, valueListPosition));
    attributes.yValues().append(nextLayoutValue(YValueAttribute, valueListPosition));
    attributes.dxValues().append(nextLayoutValue(DxValueAttribute, valueListPosition));
    attributes.dyValues().append(nextLayoutValue(DyValueAttribute, valueListPosition));
    attributes.rotateValues().append(nextLayoutValue(RotateValueAttribute, valueListPosition));
    attributes.textMetricsValues().append(metrics);
}
Esempio n. 10
0
void RenderSVGText::rebuildLayoutAttributes(Vector<SVGTextLayoutAttributes*>& affectedAttributes)
{
    // Detect changes in layout attributes and only measure those text parts that have changed!
    Vector<SVGTextLayoutAttributes*> newLayoutAttributes;
    recursiveCollectLayoutAttributes(this, newLayoutAttributes);
    if (newLayoutAttributes.isEmpty()) {
        m_layoutAttributes.clear();
        return;
    }

    // Compare m_layoutAttributes with newLayoutAttributes to figure out which attributes got added/removed.
    size_t size = newLayoutAttributes.size();
    for (size_t i = 0; i < size; ++i) {
        SVGTextLayoutAttributes* attributes = newLayoutAttributes[i];
        if (m_layoutAttributes.find(attributes) == notFound)
            m_layoutAttributesBuilder.rebuildMetricsForTextRenderer(attributes->context());
    }

    size = affectedAttributes.size();
    for (size_t i = 0; i < size; ++i)
        m_layoutAttributesBuilder.rebuildMetricsForTextRenderer(affectedAttributes[i]->context());

    m_layoutAttributes = newLayoutAttributes;
}
Esempio n. 11
0
bool SVGTextLayoutEngine::currentLogicalCharacterAttributes(SVGTextLayoutAttributes& logicalAttributes)
{
    if (m_layoutAttributes.isEmpty())
        return false;

    logicalAttributes = m_layoutAttributes.first();
    if (m_logicalCharacterOffset != logicalAttributes.xValues().size())
        return true;

    m_layoutAttributes.remove(0);
    if (m_layoutAttributes.isEmpty())
        return false;

    logicalAttributes = m_layoutAttributes.first();
    m_logicalMetricsListOffset = 0;
    m_logicalCharacterOffset = 0;
    return true;
}
Esempio n. 12
0
void SVGTextLayoutEngine::layoutTextOnLineOrPath(SVGInlineTextBox* textBox, LineLayoutSVGInlineText textLineLayout, const ComputedStyle& style)
{
    if (m_inPathLayout && !m_textPathCalculator)
        return;

    // Find the start of the current text box in the metrics list.
    m_visualMetricsIterator.advanceToTextStart(&textLineLayout, textBox->start());

    const Font& font = style.font();

    SVGTextLayoutEngineSpacing spacingLayout(font, style.effectiveZoom());
    SVGTextLayoutEngineBaseline baselineLayout(font, style.effectiveZoom());

    bool didStartTextFragment = false;
    bool applySpacingToNextCharacter = false;

    float lastAngle = 0;
    float baselineShift = baselineLayout.calculateBaselineShift(style);
    baselineShift -= baselineLayout.calculateAlignmentBaselineShift(m_isVerticalText, textLineLayout);

    // Main layout algorithm.
    const unsigned boxEndOffset = textBox->start() + textBox->len();
    while (!m_visualMetricsIterator.isAtEnd() && m_visualMetricsIterator.characterOffset() < boxEndOffset) {
        const SVGTextMetrics& visualMetrics = m_visualMetricsIterator.metrics();
        if (visualMetrics.isEmpty()) {
            m_visualMetricsIterator.next();
            continue;
        }

        SVGTextLayoutAttributes* logicalAttributes = nullptr;
        if (!currentLogicalCharacterAttributes(logicalAttributes))
            break;

        ASSERT(logicalAttributes);
        SVGTextMetrics logicalMetrics(SVGTextMetrics::SkippedSpaceMetrics);
        if (!currentLogicalCharacterMetrics(logicalAttributes, logicalMetrics))
            break;

        SVGCharacterDataMap& characterDataMap = logicalAttributes->characterDataMap();
        SVGCharacterData data;
        SVGCharacterDataMap::iterator it = characterDataMap.find(m_logicalCharacterOffset + 1);
        if (it != characterDataMap.end())
            data = it->value;

        float x = data.x;
        float y = data.y;

        // When we've advanced to the box start offset, determine using the original x/y values,
        // whether this character starts a new text chunk, before doing any further processing.
        if (m_visualMetricsIterator.characterOffset() == textBox->start())
            textBox->setStartsNewTextChunk(logicalAttributes->context()->characterStartsNewTextChunk(m_logicalCharacterOffset));

        float angle = SVGTextLayoutAttributes::isEmptyValue(data.rotate) ? 0 : data.rotate;

        // Calculate glyph orientation angle.
        // Font::width() calculates the resolved FontOrientation for each character,
        // but is not exposed today to avoid the API complexity.
        UChar32 currentCharacter = textLineLayout.codepointAt(m_visualMetricsIterator.characterOffset());
        FontOrientation fontOrientation = font.fontDescription().orientation();
        fontOrientation = adjustOrientationForCharacterInMixedVertical(fontOrientation, currentCharacter);

        // Calculate glyph advance.
        // Shaping engine takes care of x/y orientation shifts for different fontOrientation values.
        float glyphAdvance = visualMetrics.advance(fontOrientation);

        // Assign current text position to x/y values, if needed.
        updateCharacterPositionIfNeeded(x, y);

        // Apply dx/dy value adjustments to current text position, if needed.
        updateRelativePositionAdjustmentsIfNeeded(data.dx, data.dy);

        // Calculate CSS 'letter-spacing' and 'word-spacing' for next character, if needed.
        float spacing = spacingLayout.calculateCSSSpacing(currentCharacter);

        float textPathOffset = 0;
        float textPathShiftX = 0;
        float textPathShiftY = 0;
        if (m_inPathLayout) {
            float scaledGlyphAdvance = glyphAdvance * m_textPathScaling;
            if (m_isVerticalText) {
                // If there's an absolute y position available, it marks the beginning of a new position along the path.
                if (!SVGTextLayoutAttributes::isEmptyValue(y))
                    m_textPathCurrentOffset = y + m_textPathStartOffset;

                m_textPathCurrentOffset += m_dy;
                m_dy = 0;

                // Apply dx/dy correction and setup translations that move to the glyph midpoint.
                textPathShiftX += m_dx + baselineShift;
                textPathShiftY -= scaledGlyphAdvance / 2;
            } else {
                // If there's an absolute x position available, it marks the beginning of a new position along the path.
                if (!SVGTextLayoutAttributes::isEmptyValue(x))
                    m_textPathCurrentOffset = x + m_textPathStartOffset;

                m_textPathCurrentOffset += m_dx;
                m_dx = 0;

                // Apply dx/dy correction and setup translations that move to the glyph midpoint.
                textPathShiftX -= scaledGlyphAdvance / 2;
                textPathShiftY += m_dy - baselineShift;
            }

            // Calculate current offset along path.
            textPathOffset = m_textPathCurrentOffset + scaledGlyphAdvance / 2;

            // Move to next character.
            m_textPathCurrentOffset += scaledGlyphAdvance + m_textPathSpacing + spacing * m_textPathScaling;

            // Skip character, if we're before the path.
            if (textPathOffset < 0) {
                advanceToNextLogicalCharacter(logicalMetrics);
                m_visualMetricsIterator.next();
                continue;
            }

            // Stop processing, if the next character lies behind the path.
            if (textPathOffset > m_textPathLength)
                break;

            FloatPoint point;
            bool ok = m_textPathCalculator->pointAndNormalAtLength(textPathOffset, point, angle);
            ASSERT_UNUSED(ok, ok);
            x = point.x();
            y = point.y();

            // For vertical text on path, the actual angle has to be rotated 90 degrees anti-clockwise, not the orientation angle!
            if (m_isVerticalText)
                angle -= 90;
        } else {
            // Apply all previously calculated shift values.
            if (m_isVerticalText)
                x += baselineShift;
            else
                y -= baselineShift;

            x += m_dx;
            y += m_dy;
        }

        // Determine whether we have to start a new fragment.
        bool shouldStartNewFragment = m_dx || m_dy || m_isVerticalText || m_inPathLayout || angle || angle != lastAngle
            || applySpacingToNextCharacter || m_textLengthSpacingInEffect;

        // If we already started a fragment, close it now.
        if (didStartTextFragment && shouldStartNewFragment) {
            applySpacingToNextCharacter = false;
            recordTextFragment(textBox);
        }

        // Eventually start a new fragment, if not yet done.
        if (!didStartTextFragment || shouldStartNewFragment) {
            ASSERT(!m_currentTextFragment.characterOffset);
            ASSERT(!m_currentTextFragment.length);

            didStartTextFragment = true;
            m_currentTextFragment.characterOffset = m_visualMetricsIterator.characterOffset();
            m_currentTextFragment.metricsListOffset = m_visualMetricsIterator.metricsListOffset();
            m_currentTextFragment.x = x;
            m_currentTextFragment.y = y;

            // Build fragment transformation.
            if (angle)
                m_currentTextFragment.transform.rotate(angle);

            if (textPathShiftX || textPathShiftY)
                m_currentTextFragment.transform.translate(textPathShiftX, textPathShiftY);

            // In vertical text, always rotate by 90 degrees regardless of fontOrientation.
            // Shaping engine takes care of the necessary orientation.
            if (m_isVerticalText)
                m_currentTextFragment.transform.rotate(90);

            m_currentTextFragment.isTextOnPath = m_inPathLayout && m_textPathScaling != 1;
            if (m_currentTextFragment.isTextOnPath) {
                if (m_isVerticalText)
                    m_currentTextFragment.lengthAdjustTransform.scaleNonUniform(1, m_textPathScaling);
                else
                    m_currentTextFragment.lengthAdjustTransform.scaleNonUniform(m_textPathScaling, 1);
            }
        }

        // Update current text position, after processing of the current character finished.
        if (m_inPathLayout) {
            updateCurrentTextPosition(x, y, glyphAdvance);
        } else {
            // Apply CSS 'kerning', 'letter-spacing' and 'word-spacing' to next character, if needed.
            if (spacing)
                applySpacingToNextCharacter = true;

            float xNew = x - m_dx;
            float yNew = y - m_dy;

            if (m_isVerticalText)
                xNew -= baselineShift;
            else
                yNew += baselineShift;

            updateCurrentTextPosition(xNew, yNew, glyphAdvance + spacing);
        }

        advanceToNextLogicalCharacter(logicalMetrics);
        m_visualMetricsIterator.next();
        lastAngle = angle;
    }

    if (!didStartTextFragment)
        return;

    // Close last open fragment, if needed.
    recordTextFragment(textBox);
}
Esempio n. 13
0
void SVGTextLayoutEngine::layoutTextOnLineOrPath(SVGInlineTextBox* textBox, RenderSVGInlineText* text, const RenderStyle* style)
{
    SVGElement* lengthContext = static_cast<SVGElement*>(text->parent()->node());
    
    RenderObject* textParent = text->parent();
    bool definesTextLength = textParent ? parentDefinesTextLength(textParent) : false;

    const SVGRenderStyle* svgStyle = style->svgStyle();
    ASSERT(svgStyle);

    m_visualMetricsListOffset = 0;
    m_visualCharacterOffset = 0;

    Vector<SVGTextMetrics>& textMetricsValues = text->layoutAttributes().textMetricsValues();
    const UChar* characters = text->characters();

    const Font& font = style->font();
    SVGTextLayoutEngineSpacing spacingLayout(font);
    SVGTextLayoutEngineBaseline baselineLayout(font);

    bool didStartTextFragment = false;
    bool applySpacingToNextCharacter = false;

    float lastAngle = 0;
    float baselineShift = baselineLayout.calculateBaselineShift(svgStyle, lengthContext);
    baselineShift -= baselineLayout.calculateAlignmentBaselineShift(m_isVerticalText, text);

    // Main layout algorithm.
    while (true) {
        // Find the start of the current text box in this list, respecting ligatures.
        SVGTextMetrics visualMetrics = SVGTextMetrics::emptyMetrics();
        if (!currentVisualCharacterMetrics(textBox, text, visualMetrics))
            break;

        if (visualMetrics == SVGTextMetrics::emptyMetrics()) {
            advanceToNextVisualCharacter(visualMetrics);
            continue;
        }

        SVGTextLayoutAttributes logicalAttributes;
        if (!currentLogicalCharacterAttributes(logicalAttributes))
            break;

        SVGTextMetrics logicalMetrics = SVGTextMetrics::emptyMetrics();
        if (!currentLogicalCharacterMetrics(logicalAttributes, logicalMetrics))
            break;

        Vector<float>& xValues = logicalAttributes.xValues();
        Vector<float>& yValues = logicalAttributes.yValues();
        Vector<float>& dxValues = logicalAttributes.dxValues();
        Vector<float>& dyValues = logicalAttributes.dyValues();
        Vector<float>& rotateValues = logicalAttributes.rotateValues();

        float x = xValues.at(m_logicalCharacterOffset);
        float y = yValues.at(m_logicalCharacterOffset);

        // When we've advanced to the box start offset, determine using the original x/y values,
        // whether this character starts a new text chunk, before doing any further processing.
        if (m_visualCharacterOffset == textBox->start())
            textBox->setStartsNewTextChunk(logicalAttributes.context()->characterStartsNewTextChunk(m_logicalCharacterOffset));

        float angle = 0;
        if (!rotateValues.isEmpty()) {
            float newAngle = rotateValues.at(m_logicalCharacterOffset);
            if (newAngle != SVGTextLayoutAttributes::emptyValue())
                angle = newAngle;
        }

        // Calculate glyph orientation angle.
        const UChar* currentCharacter = characters + m_visualCharacterOffset;
        float orientationAngle = baselineLayout.calculateGlyphOrientationAngle(m_isVerticalText, svgStyle, *currentCharacter);

        // Calculate glyph advance & x/y orientation shifts.
        float xOrientationShift = 0;
        float yOrientationShift = 0;
        float glyphAdvance = baselineLayout.calculateGlyphAdvanceAndOrientation(m_isVerticalText, visualMetrics, orientationAngle, xOrientationShift, yOrientationShift);

        // Assign current text position to x/y values, if needed.
        updateCharacerPositionIfNeeded(x, y);

        // Apply dx/dy value adjustments to current text position, if needed.
        updateRelativePositionAdjustmentsIfNeeded(dxValues, dyValues);

        // Calculate SVG Fonts kerning, if needed.
        float kerning = spacingLayout.calculateSVGKerning(m_isVerticalText, visualMetrics.glyph());

        // Calculate CSS 'kerning', 'letter-spacing' and 'word-spacing' for next character, if needed.
        float spacing = spacingLayout.calculateCSSKerningAndSpacing(svgStyle, lengthContext, currentCharacter);

        float textPathOffset = 0;
        if (m_inPathLayout) {
            float scaledGlyphAdvance = glyphAdvance * m_textPathScaling;
            if (m_isVerticalText) {
                // If there's an absolute y position available, it marks the beginning of a new position along the path.
                if (y != SVGTextLayoutAttributes::emptyValue())
                    m_textPathCurrentOffset = y + m_textPathStartOffset;

                m_textPathCurrentOffset += m_dy - kerning;
                m_dy = 0;

                // Apply dx/dy correction and setup translations that move to the glyph midpoint.
                xOrientationShift += m_dx + baselineShift;
                yOrientationShift -= scaledGlyphAdvance / 2;
            } else {
                // If there's an absolute x position available, it marks the beginning of a new position along the path.
                if (x != SVGTextLayoutAttributes::emptyValue())
                    m_textPathCurrentOffset = x + m_textPathStartOffset;

                m_textPathCurrentOffset += m_dx - kerning;
                m_dx = 0;

                // Apply dx/dy correction and setup translations that move to the glyph midpoint.
                xOrientationShift -= scaledGlyphAdvance / 2;
                yOrientationShift += m_dy - baselineShift;
            }

            // Calculate current offset along path.
            textPathOffset = m_textPathCurrentOffset + scaledGlyphAdvance / 2;

            // Move to next character.
            m_textPathCurrentOffset += scaledGlyphAdvance + m_textPathSpacing + spacing * m_textPathScaling;

            // Skip character, if we're before the path.
            if (textPathOffset < 0) {
                advanceToNextLogicalCharacter(logicalMetrics);
                advanceToNextVisualCharacter(visualMetrics);
                continue;
            }

            // Stop processing, if the next character lies behind the path.
            if (textPathOffset > m_textPathLength)
                break;

            bool ok = false;
            FloatPoint point = m_textPath.pointAtLength(textPathOffset, ok);
            ASSERT(ok);

            x = point.x();
            y = point.y();
            angle = m_textPath.normalAngleAtLength(textPathOffset, ok);
            ASSERT(ok);

            // For vertical text on path, the actual angle has to be rotated 90 degrees anti-clockwise, not the orientation angle!
            if (m_isVerticalText)
                angle -= 90;
        } else {
            // Apply all previously calculated shift values.
            if (m_isVerticalText) {
                x += baselineShift;
                y -= kerning;
            } else {
                x -= kerning;
                y -= baselineShift;
            }

            x += m_dx;
            y += m_dy;
        }

        // Determine wheter we have to start a new fragment.
        bool shouldStartNewFragment = false;

        if (m_dx || m_dy)
            shouldStartNewFragment = true;

        if (!shouldStartNewFragment && (m_isVerticalText || m_inPathLayout))
            shouldStartNewFragment = true;

        if (!shouldStartNewFragment && (angle || angle != lastAngle || orientationAngle))
            shouldStartNewFragment = true;

        if (!shouldStartNewFragment && (kerning || applySpacingToNextCharacter || definesTextLength))
            shouldStartNewFragment = true;

        // If we already started a fragment, close it now.
        if (didStartTextFragment && shouldStartNewFragment) {
            applySpacingToNextCharacter = false;
            recordTextFragment(textBox, textMetricsValues);
        }

        // Eventually start a new fragment, if not yet done.
        if (!didStartTextFragment || shouldStartNewFragment) {
            ASSERT(!m_currentTextFragment.characterOffset);
            ASSERT(!m_currentTextFragment.length);

            didStartTextFragment = true;
            m_currentTextFragment.characterOffset = m_visualCharacterOffset;
            m_currentTextFragment.metricsListOffset = m_visualMetricsListOffset;
            m_currentTextFragment.x = x;
            m_currentTextFragment.y = y;

            // Build fragment transformation.
            if (angle)
                m_currentTextFragment.transform.rotate(angle);

            if (xOrientationShift || yOrientationShift)
                m_currentTextFragment.transform.translate(xOrientationShift, yOrientationShift);

            if (orientationAngle)
                m_currentTextFragment.transform.rotate(orientationAngle);

            m_currentTextFragment.isTextOnPath = m_inPathLayout && m_textPathScaling != 1;
            if (m_currentTextFragment.isTextOnPath) {
                if (m_isVerticalText)
                    m_currentTextFragment.lengthAdjustTransform.scaleNonUniform(1, m_textPathScaling);
                else
                    m_currentTextFragment.lengthAdjustTransform.scaleNonUniform(m_textPathScaling, 1);
            }
        }

        // Update current text position, after processing of the current character finished.
        if (m_inPathLayout)
            updateCurrentTextPosition(x, y, glyphAdvance);
        else {
            // Apply CSS 'kerning', 'letter-spacing' and 'word-spacing' to next character, if needed.
            if (spacing)
                applySpacingToNextCharacter = true;

            float xNew = x - m_dx;
            float yNew = y - m_dy;

            if (m_isVerticalText)
                xNew -= baselineShift;
            else
                yNew += baselineShift;

            updateCurrentTextPosition(xNew, yNew, glyphAdvance + spacing);
        }

        advanceToNextLogicalCharacter(logicalMetrics);
        advanceToNextVisualCharacter(visualMetrics);
        lastAngle = angle;
    }

    if (!didStartTextFragment)
        return;

    // Close last open fragment, if needed.
    recordTextFragment(textBox, textMetricsValues);
}
Esempio n. 14
0
void SVGTextQuery::modifyStartEndPositionsRespectingLigatures(Data* queryData, int& startPosition, int& endPosition) const
{
    SVGTextLayoutAttributes* layoutAttributes = queryData->textRenderer->layoutAttributes();
    Vector<SVGTextMetrics>& textMetricsValues = layoutAttributes->textMetricsValues();
    unsigned boxStart = queryData->textBox->start();
    unsigned boxLength = queryData->textBox->len();

    unsigned textMetricsOffset = 0;
    unsigned textMetricsSize = textMetricsValues.size();

    unsigned positionOffset = 0;
    unsigned positionSize = layoutAttributes->context()->textLength();

    bool alterStartPosition = true;
    bool alterEndPosition = true;

    int lastPositionOffset = -1;
    for (; textMetricsOffset < textMetricsSize && positionOffset < positionSize; ++textMetricsOffset) {
        SVGTextMetrics& metrics = textMetricsValues[textMetricsOffset];

        // Advance to text box start location.
        if (positionOffset < boxStart) {
            positionOffset += metrics.length();
            continue;
        }

        // Stop if we've finished processing this text box.
        if (positionOffset >= boxStart + boxLength)
            break;

        // If the start position maps to a character in the metrics list, we don't need to modify it.
        if (startPosition == static_cast<int>(positionOffset))
            alterStartPosition = false;

        // If the start position maps to a character in the metrics list, we don't need to modify it.
        if (endPosition == static_cast<int>(positionOffset))
            alterEndPosition = false;

        // Detect ligatures.
        if (lastPositionOffset != -1 && lastPositionOffset - positionOffset > 1) {
            if (alterStartPosition && startPosition > lastPositionOffset && startPosition < static_cast<int>(positionOffset)) {
                startPosition = lastPositionOffset;
                alterStartPosition = false;
            }

            if (alterEndPosition && endPosition > lastPositionOffset && endPosition < static_cast<int>(positionOffset)) {
                endPosition = positionOffset;
                alterEndPosition = false;
            }
        }

        if (!alterStartPosition && !alterEndPosition)
            break;

        lastPositionOffset = positionOffset;
        positionOffset += metrics.length();
    }

    if (!alterStartPosition && !alterEndPosition)
        return;

    if (lastPositionOffset != -1 && lastPositionOffset - positionOffset > 1) {
        if (alterStartPosition && startPosition > lastPositionOffset && startPosition < static_cast<int>(positionOffset)) {
            startPosition = lastPositionOffset;
            alterStartPosition = false;
        }

        if (alterEndPosition && endPosition > lastPositionOffset && endPosition < static_cast<int>(positionOffset)) {
            endPosition = positionOffset;
            alterEndPosition = false;
        }
    }
}