VisiblePosition RenderSVGInlineText::positionForPoint(const LayoutPoint& point) { if (!firstTextBox() || !textLength()) return createVisiblePosition(0, DOWNSTREAM); float baseline = m_scaledFont.fontMetrics().floatAscent(); RenderBlock* containingBlock = this->containingBlock(); ASSERT(containingBlock); // Map local point to absolute point, as the character origins stored in the text fragments use absolute coordinates. FloatPoint absolutePoint(point); absolutePoint.moveBy(containingBlock->location()); float closestDistance = std::numeric_limits<float>::max(); float closestDistancePosition = 0; const SVGTextFragment* closestDistanceFragment = 0; SVGInlineTextBox* closestDistanceBox = 0; AffineTransform fragmentTransform; for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) { if (!box->isSVGInlineTextBox()) continue; SVGInlineTextBox* textBox = toSVGInlineTextBox(box); Vector<SVGTextFragment>& fragments = textBox->textFragments(); unsigned textFragmentsSize = fragments.size(); for (unsigned i = 0; i < textFragmentsSize; ++i) { const SVGTextFragment& fragment = fragments.at(i); FloatRect fragmentRect(fragment.x, fragment.y - baseline, fragment.width, fragment.height); fragment.buildFragmentTransform(fragmentTransform); if (!fragmentTransform.isIdentity()) fragmentRect = fragmentTransform.mapRect(fragmentRect); float distance = powf(fragmentRect.x() - absolutePoint.x(), 2) + powf(fragmentRect.y() + fragmentRect.height() / 2 - absolutePoint.y(), 2); if (distance < closestDistance) { closestDistance = distance; closestDistanceBox = textBox; closestDistanceFragment = &fragment; closestDistancePosition = fragmentRect.x(); } } } if (!closestDistanceFragment) return createVisiblePosition(0, DOWNSTREAM); int offset = closestDistanceBox->offsetForPositionInFragment(*closestDistanceFragment, absolutePoint.x() - closestDistancePosition, true); return createVisiblePosition(offset + closestDistanceBox->start(), offset > 0 ? VP_UPSTREAM_IF_POSSIBLE : DOWNSTREAM); }
void SVGInlineTextBoxPainter::paintTextMatchMarker(GraphicsContext* context, const LayoutPoint&, DocumentMarker* marker, const ComputedStyle& style, const Font& font) { // SVG is only interested in the TextMatch markers. if (marker->type() != DocumentMarker::TextMatch) return; LayoutSVGInlineText& textLayoutObject = toLayoutSVGInlineText(m_svgInlineTextBox.layoutObject()); AffineTransform fragmentTransform; for (InlineTextBox* box = textLayoutObject.firstTextBox(); box; box = box->nextTextBox()) { if (!box->isSVGInlineTextBox()) continue; SVGInlineTextBox* textBox = toSVGInlineTextBox(box); int markerStartPosition = std::max<int>(marker->startOffset() - textBox->start(), 0); int markerEndPosition = std::min<int>(marker->endOffset() - textBox->start(), textBox->len()); if (markerStartPosition >= markerEndPosition) continue; const Vector<SVGTextFragment>& fragments = textBox->textFragments(); unsigned textFragmentsSize = fragments.size(); for (unsigned i = 0; i < textFragmentsSize; ++i) { const SVGTextFragment& fragment = fragments.at(i); int fragmentStartPosition = markerStartPosition; int fragmentEndPosition = markerEndPosition; if (!textBox->mapStartEndPositionsIntoFragmentCoordinates(fragment, fragmentStartPosition, fragmentEndPosition)) continue; FloatRect fragmentRect = textBox->selectionRectForTextFragment(fragment, fragmentStartPosition, fragmentEndPosition, style); fragment.buildFragmentTransform(fragmentTransform); // Draw the marker highlight. if (m_svgInlineTextBox.layoutObject().frame()->editor().markedTextMatchesAreHighlighted()) { Color color = marker->activeMatch() ? LayoutTheme::theme().platformActiveTextSearchHighlightColor() : LayoutTheme::theme().platformInactiveTextSearchHighlightColor(); GraphicsContextStateSaver stateSaver(*context); if (!fragmentTransform.isIdentity()) context->concatCTM(fragmentTransform); context->setFillColor(color); context->fillRect(fragmentRect, color); } } } }
PositionWithAffinity LayoutSVGInlineText::positionForPoint(const LayoutPoint& point) { if (!hasTextBoxes() || !textLength()) return createPositionWithAffinity(0); ASSERT(m_scalingFactor); float baseline = m_scaledFont.getFontMetrics().floatAscent() / m_scalingFactor; LayoutBlock* containingBlock = this->containingBlock(); ASSERT(containingBlock); // Map local point to absolute point, as the character origins stored in the text fragments use absolute coordinates. FloatPoint absolutePoint(point); absolutePoint.moveBy(containingBlock->location()); float closestDistance = std::numeric_limits<float>::max(); float closestDistancePosition = 0; const SVGTextFragment* closestDistanceFragment = nullptr; SVGInlineTextBox* closestDistanceBox = nullptr; for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) { if (!box->isSVGInlineTextBox()) continue; SVGInlineTextBox* textBox = toSVGInlineTextBox(box); for (const SVGTextFragment& fragment : textBox->textFragments()) { FloatRect fragmentRect = fragment.boundingBox(baseline); float distance = 0; if (!fragmentRect.contains(absolutePoint)) distance = fragmentRect.squaredDistanceTo(absolutePoint); if (distance <= closestDistance) { closestDistance = distance; closestDistanceBox = textBox; closestDistanceFragment = &fragment; closestDistancePosition = fragmentRect.x(); } } } if (!closestDistanceFragment) return createPositionWithAffinity(0); int offset = closestDistanceBox->offsetForPositionInFragment(*closestDistanceFragment, LayoutUnit(absolutePoint.x() - closestDistancePosition), true); return createPositionWithAffinity(offset + closestDistanceBox->start(), offset > 0 ? VP_UPSTREAM_IF_POSSIBLE : TextAffinity::Downstream); }
void SVGTextLayoutEngine::finalizeTransformMatrices(Vector<SVGInlineTextBox*>& boxes) { unsigned boxCount = boxes.size(); if (!boxCount) return; for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) { SVGInlineTextBox* textBox = boxes.at(boxPosition); AffineTransform textBoxTransformation = m_chunkLayoutBuilder.transformationForTextBox(textBox); if (textBoxTransformation.isIdentity()) continue; Vector<SVGTextFragment>& fragments = textBox->textFragments(); unsigned fragmentCount = fragments.size(); for (unsigned i = 0; i < fragmentCount; ++i) { ASSERT(fragments[i].lengthAdjustTransform.isIdentity()); fragments[i].lengthAdjustTransform = textBoxTransformation; } } boxes.clear(); }
static inline void dumpTextBoxes(Vector<SVGInlineTextBox*>& boxes) { unsigned boxCount = boxes.size(); fprintf(stderr, "Dumping all text fragments in text sub tree, %i boxes\n", boxCount); for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) { SVGInlineTextBox* textBox = boxes.at(boxPosition); Vector<SVGTextFragment>& fragments = textBox->textFragments(); fprintf(stderr, "-> Box %i: Dumping text fragments for SVGInlineTextBox, textBox=%p, textRenderer=%p\n", boxPosition, textBox, textBox->textRenderer()); fprintf(stderr, " textBox properties, start=%i, len=%i, box direction=%i\n", textBox->start(), textBox->len(), textBox->direction()); fprintf(stderr, " textRenderer properties, textLength=%i\n", textBox->textRenderer()->textLength()); const UChar* characters = textBox->textRenderer()->characters(); unsigned fragmentCount = fragments.size(); for (unsigned i = 0; i < fragmentCount; ++i) { SVGTextFragment& fragment = fragments.at(i); String fragmentString(characters + fragment.characterOffset, fragment.length); fprintf(stderr, " -> Fragment %i, x=%lf, y=%lf, width=%lf, height=%lf, characterOffset=%i, length=%i, characters='%s'\n" , i, fragment.x, fragment.y, fragment.width, fragment.height, fragment.characterOffset, fragment.length, fragmentString.utf8().data()); } } }
void SVGTextChunk::calculateLength(float& length, unsigned& characters) const { SVGTextFragment* lastFragment = 0; unsigned boxCount = m_boxes.size(); for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) { SVGInlineTextBox* textBox = m_boxes.at(boxPosition); Vector<SVGTextFragment>& fragments = textBox->textFragments(); unsigned size = fragments.size(); if (!size) continue; for (unsigned i = 0; i < size; ++i) { SVGTextFragment& fragment = fragments.at(i); characters += fragment.length; if (m_chunkStyle & VerticalText) length += fragment.height; else length += fragment.width; if (!lastFragment) { lastFragment = &fragment; continue; } // Resepect gap between chunks. if (m_chunkStyle & VerticalText) length += fragment.y - (lastFragment->y + lastFragment->height); else length += fragment.x - (lastFragment->x + lastFragment->width); lastFragment = &fragment; } } }
void SVGTextChunkBuilder::processTextChunk(const SVGTextChunk& chunk) { bool processTextLength = chunk.hasDesiredTextLength(); bool processTextAnchor = chunk.hasTextAnchor(); if (!processTextAnchor && !processTextLength) return; const Vector<SVGInlineTextBox*>& boxes = chunk.boxes(); unsigned boxCount = boxes.size(); if (!boxCount) return; // Calculate absolute length of whole text chunk (starting from text box 'start', spanning 'length' text boxes). float chunkLength = 0; unsigned chunkCharacters = 0; chunk.calculateLength(chunkLength, chunkCharacters); bool isVerticalText = chunk.isVerticalText(); if (processTextLength) { if (chunk.hasLengthAdjustSpacing()) { float textLengthShift = (chunk.desiredTextLength() - chunkLength) / chunkCharacters; unsigned atCharacter = 0; for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) { Vector<SVGTextFragment>& fragments = boxes[boxPosition]->textFragments(); if (fragments.isEmpty()) continue; processTextLengthSpacingCorrection(isVerticalText, textLengthShift, fragments, atCharacter); } } else { ASSERT(chunk.hasLengthAdjustSpacingAndGlyphs()); float textLengthScale = chunk.desiredTextLength() / chunkLength; AffineTransform spacingAndGlyphsTransform; bool foundFirstFragment = false; for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) { SVGInlineTextBox* textBox = boxes[boxPosition]; Vector<SVGTextFragment>& fragments = textBox->textFragments(); if (fragments.isEmpty()) continue; if (!foundFirstFragment) { foundFirstFragment = true; buildSpacingAndGlyphsTransform(isVerticalText, textLengthScale, fragments.first(), spacingAndGlyphsTransform); } m_textBoxTransformations.set(textBox, spacingAndGlyphsTransform); } } } if (!processTextAnchor) return; // If we previously applied a lengthAdjust="spacing" correction, we have to recalculate the chunk length, to be able to apply the text-anchor shift. if (processTextLength && chunk.hasLengthAdjustSpacing()) { chunkLength = 0; chunkCharacters = 0; chunk.calculateLength(chunkLength, chunkCharacters); } float textAnchorShift = chunk.calculateTextAnchorShift(chunkLength); for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) { Vector<SVGTextFragment>& fragments = boxes[boxPosition]->textFragments(); if (fragments.isEmpty()) continue; processTextAnchorCorrection(isVerticalText, textAnchorShift, fragments); } }
void SVGInlineFlowBox::computeTextMatchMarkerRectForRenderer(RenderSVGInlineText* textRenderer) { ASSERT(textRenderer); Node* node = textRenderer->node(); if (!node || !node->inDocument()) return; RenderStyle* style = textRenderer->style(); ASSERT(style); AffineTransform fragmentTransform; Document* document = textRenderer->document(); Vector<DocumentMarker*> markers = document->markers()->markersFor(textRenderer->node()); Vector<DocumentMarker*>::iterator markerEnd = markers.end(); for (Vector<DocumentMarker*>::iterator markerIt = markers.begin(); markerIt != markerEnd; ++markerIt) { DocumentMarker* marker = *markerIt; // SVG is only interessted in the TextMatch marker, for now. if (marker->type() != DocumentMarker::TextMatch) continue; FloatRect markerRect; for (InlineTextBox* box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) { if (!box->isSVGInlineTextBox()) continue; SVGInlineTextBox* textBox = static_cast<SVGInlineTextBox*>(box); int markerStartPosition = max<int>(marker->startOffset() - textBox->start(), 0); int markerEndPosition = min<int>(marker->endOffset() - textBox->start(), textBox->len()); if (markerStartPosition >= markerEndPosition) continue; int fragmentStartPosition = 0; int fragmentEndPosition = 0; const Vector<SVGTextFragment>& fragments = textBox->textFragments(); unsigned textFragmentsSize = fragments.size(); for (unsigned i = 0; i < textFragmentsSize; ++i) { const SVGTextFragment& fragment = fragments.at(i); fragmentStartPosition = markerStartPosition; fragmentEndPosition = markerEndPosition; if (!textBox->mapStartEndPositionsIntoFragmentCoordinates(fragment, fragmentStartPosition, fragmentEndPosition)) continue; FloatRect fragmentRect = textBox->selectionRectForTextFragment(fragment, fragmentStartPosition, fragmentEndPosition, style); fragment.buildFragmentTransform(fragmentTransform); if (!fragmentTransform.isIdentity()) fragmentRect = fragmentTransform.mapRect(fragmentRect); markerRect.unite(fragmentRect); } } toRenderedDocumentMarker(marker)->setRenderedRect(textRenderer->localToAbsoluteQuad(markerRect).enclosingBoundingBox()); } }
void SVGTextChunkBuilder::handleTextChunk(BoxListConstIterator boxStart, BoxListConstIterator boxEnd) { ASSERT(*boxStart); const LineLayoutSVGInlineText textLineLayout = LineLayoutSVGInlineText((*boxStart)->getLineLayoutItem()); const ComputedStyle& style = textLineLayout.styleRef(); // Handle 'lengthAdjust' property. float desiredTextLength = 0; SVGLengthAdjustType lengthAdjust = SVGLengthAdjustUnknown; if (SVGTextContentElement* textContentElement = SVGTextContentElement::elementFromLineLayoutItem( textLineLayout.parent())) { lengthAdjust = textContentElement->lengthAdjust()->currentValue()->enumValue(); SVGLengthContext lengthContext(textContentElement); if (textContentElement->textLengthIsSpecifiedByUser()) desiredTextLength = textContentElement->textLength()->currentValue()->value( lengthContext); else desiredTextLength = 0; } bool processTextLength = desiredTextLength > 0; bool processTextAnchor = needsTextAnchorAdjustment(style); if (!processTextAnchor && !processTextLength) return; bool isVerticalText = !style.isHorizontalWritingMode(); // Calculate absolute length of whole text chunk (starting from text box // 'start', spanning 'length' text boxes). ChunkLengthAccumulator lengthAccumulator(isVerticalText); lengthAccumulator.processRange(boxStart, boxEnd); if (processTextLength) { float chunkLength = lengthAccumulator.length(); if (lengthAdjust == SVGLengthAdjustSpacing) { float textLengthShift = (desiredTextLength - chunkLength) / lengthAccumulator.numCharacters(); unsigned atCharacter = 0; for (auto boxIter = boxStart; boxIter != boxEnd; ++boxIter) { Vector<SVGTextFragment>& fragments = (*boxIter)->textFragments(); if (fragments.isEmpty()) continue; processTextLengthSpacingCorrection(isVerticalText, textLengthShift, fragments, atCharacter); } // Fragments have been adjusted, we have to recalculate the chunk // length, to be able to apply the text-anchor shift. if (processTextAnchor) { lengthAccumulator.reset(); lengthAccumulator.processRange(boxStart, boxEnd); } } else { ASSERT(lengthAdjust == SVGLengthAdjustSpacingAndGlyphs); float textLengthScale = desiredTextLength / chunkLength; float textLengthBias = 0; bool foundFirstFragment = false; for (auto boxIter = boxStart; boxIter != boxEnd; ++boxIter) { SVGInlineTextBox* textBox = *boxIter; Vector<SVGTextFragment>& fragments = textBox->textFragments(); if (fragments.isEmpty()) continue; if (!foundFirstFragment) { foundFirstFragment = true; textLengthBias = computeTextLengthBias(fragments.first(), textLengthScale); } applyTextLengthScaleAdjustment(textLengthScale, textLengthBias, fragments); } } } if (!processTextAnchor) return; float textAnchorShift = calculateTextAnchorShift(style, lengthAccumulator.length()); for (auto boxIter = boxStart; boxIter != boxEnd; ++boxIter) { Vector<SVGTextFragment>& fragments = (*boxIter)->textFragments(); if (fragments.isEmpty()) continue; processTextAnchorCorrection(isVerticalText, textAnchorShift, fragments); } }