/* * Copyright (C) 2006 Oliver Hunt * Copyright (C) 2006 Apple Inc. * Copyright (C) 2007 Nikolas Zimmermann * Copyright (C) Research In Motion Limited 2010. All rights reserved. * Copyright (C) 2011 Torch Mobile (Beijing) CO. Ltd. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "config.h" #include "SVGRootInlineBox.h" #include "GraphicsContext.h" #include "RenderSVGText.h" #include "RenderSVGTextPath.h" #include "SVGInlineFlowBox.h" #include "SVGInlineTextBox.h" #include "SVGNames.h" #include "SVGRenderingContext.h" #include "SVGTextPositioningElement.h" namespace WebCore { SVGRootInlineBox::SVGRootInlineBox(RenderSVGText& renderSVGText) : RootInlineBox(renderSVGText) , m_logicalHeight(0) { } RenderSVGText& SVGRootInlineBox::renderSVGText() { return downcast(blockFlow()); } void SVGRootInlineBox::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset, LayoutUnit, LayoutUnit) { ASSERT(paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection); ASSERT(!paintInfo.context().paintingDisabled()); bool isPrinting = renderSVGText().document().printing(); bool hasSelection = !isPrinting && selectionState() != RenderObject::SelectionNone; bool shouldPaintSelectionHighlight = !(paintInfo.paintBehavior & PaintBehaviorSkipSelectionHighlight); PaintInfo childPaintInfo(paintInfo); if (hasSelection && shouldPaintSelectionHighlight) { for (InlineBox* child = firstChild(); child; child = child->nextOnLine()) { if (is(*child)) downcast(*child).paintSelectionBackground(childPaintInfo); else if (is(*child)) downcast(*child).paintSelectionBackground(childPaintInfo); } } SVGRenderingContext renderingContext(renderSVGText(), paintInfo, SVGRenderingContext::SaveGraphicsContext); if (renderingContext.isRenderingPrepared()) { for (InlineBox* child = firstChild(); child; child = child->nextOnLine()) child->paint(paintInfo, paintOffset, 0, 0); } } void SVGRootInlineBox::computePerCharacterLayoutInformation() { auto& textRoot = downcast(blockFlow()); Vector& layoutAttributes = textRoot.layoutAttributes(); if (layoutAttributes.isEmpty()) return; if (textRoot.needsReordering()) reorderValueLists(layoutAttributes); // Perform SVG text layout phase two (see SVGTextLayoutEngine for details). SVGTextLayoutEngine characterLayout(layoutAttributes); layoutCharactersInTextBoxes(this, characterLayout); // Perform SVG text layout phase three (see SVGTextChunkBuilder for details). characterLayout.finishLayout(); // Perform SVG text layout phase four // Position & resize all SVGInlineText/FlowBoxes in the inline box tree, resize the root box as well as the RenderSVGText parent block. FloatRect childRect; layoutChildBoxes(this, &childRect); layoutRootBox(childRect); } void SVGRootInlineBox::layoutCharactersInTextBoxes(InlineFlowBox* start, SVGTextLayoutEngine& characterLayout) { for (InlineBox* child = start->firstChild(); child; child = child->nextOnLine()) { if (is(*child)) { ASSERT(is(child->renderer())); characterLayout.layoutInlineTextBox(downcast(*child)); } else { // Skip generated content. Node* node = child->renderer().node(); if (!node) continue; auto& flowBox = downcast(*child); bool isTextPath = node->hasTagName(SVGNames::textPathTag); if (isTextPath) { // Build text chunks for all children, using the line layout algorithm. // This is needeed as text-anchor is just an additional startOffset for text paths. SVGTextLayoutEngine lineLayout(characterLayout.layoutAttributes()); layoutCharactersInTextBoxes(&flowBox, lineLayout); characterLayout.beginTextPathLayout(downcast(child->renderer()), lineLayout); } layoutCharactersInTextBoxes(&flowBox, characterLayout); if (isTextPath) characterLayout.endTextPathLayout(); } } } void SVGRootInlineBox::layoutChildBoxes(InlineFlowBox* start, FloatRect* childRect) { for (InlineBox* child = start->firstChild(); child; child = child->nextOnLine()) { FloatRect boxRect; if (is(*child)) { ASSERT(is(child->renderer())); auto& textBox = downcast(*child); boxRect = textBox.calculateBoundaries(); textBox.setX(boxRect.x()); textBox.setY(boxRect.y()); textBox.setLogicalWidth(boxRect.width()); textBox.setLogicalHeight(boxRect.height()); } else { // Skip generated content. if (!child->renderer().node()) continue; auto& flowBox = downcast(*child); layoutChildBoxes(&flowBox); boxRect = flowBox.calculateBoundaries(); flowBox.setX(boxRect.x()); flowBox.setY(boxRect.y()); flowBox.setLogicalWidth(boxRect.width()); flowBox.setLogicalHeight(boxRect.height()); } if (childRect) childRect->unite(boxRect); } } void SVGRootInlineBox::layoutRootBox(const FloatRect& childRect) { RenderSVGText& parentBlock = renderSVGText(); // Finally, assign the root block position, now that all content is laid out. LayoutRect boundingRect = enclosingLayoutRect(childRect); parentBlock.setLocation(boundingRect.location()); parentBlock.setSize(boundingRect.size()); // Position all children relative to the parent block. for (InlineBox* child = firstChild(); child; child = child->nextOnLine()) { // Skip generated content. if (!child->renderer().node()) continue; child->adjustPosition(-childRect.x(), -childRect.y()); } // Position ourselves. setX(0); setY(0); setLogicalWidth(childRect.width()); setLogicalHeight(childRect.height()); setLineTopBottomPositions(0, boundingRect.height(), 0, boundingRect.height()); } InlineBox* SVGRootInlineBox::closestLeafChildForPosition(const LayoutPoint& point) { InlineBox* firstLeaf = firstLeafChild(); InlineBox* lastLeaf = lastLeafChild(); if (firstLeaf == lastLeaf) return firstLeaf; // FIXME: Check for vertical text! InlineBox* closestLeaf = nullptr; for (InlineBox* leaf = firstLeaf; leaf; leaf = leaf->nextLeafChild()) { if (!leaf->isSVGInlineTextBox()) continue; if (point.y() < leaf->y()) continue; if (point.y() > leaf->y() + leaf->virtualLogicalHeight()) continue; closestLeaf = leaf; if (point.x() < leaf->left() + leaf->logicalWidth()) return leaf; } return closestLeaf ? closestLeaf : lastLeaf; } bool SVGRootInlineBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, LayoutUnit lineTop, LayoutUnit lineBottom, HitTestAction hitTestAction) { for (InlineBox* leaf = firstLeafChild(); leaf; leaf = leaf->nextLeafChild()) { if (!leaf->isSVGInlineTextBox()) continue; if (leaf->nodeAtPoint(request, result, locationInContainer, accumulatedOffset, lineTop, lineBottom, hitTestAction)) return true; } return false; } static inline void swapItemsInLayoutAttributes(SVGTextLayoutAttributes* firstAttributes, SVGTextLayoutAttributes* lastAttributes, unsigned firstPosition, unsigned lastPosition) { SVGCharacterDataMap::iterator itFirst = firstAttributes->characterDataMap().find(firstPosition + 1); SVGCharacterDataMap::iterator itLast = lastAttributes->characterDataMap().find(lastPosition + 1); bool firstPresent = itFirst != firstAttributes->characterDataMap().end(); bool lastPresent = itLast != lastAttributes->characterDataMap().end(); if (!firstPresent && !lastPresent) return; if (firstPresent && lastPresent) { std::swap(itFirst->value, itLast->value); return; } if (firstPresent && !lastPresent) { lastAttributes->characterDataMap().set(lastPosition + 1, itFirst->value); return; } // !firstPresent && lastPresent firstAttributes->characterDataMap().set(firstPosition + 1, itLast->value); } static inline void findFirstAndLastAttributesInVector(Vector& attributes, RenderSVGInlineText* firstContext, RenderSVGInlineText* lastContext, SVGTextLayoutAttributes*& first, SVGTextLayoutAttributes*& last) { first = nullptr; last = nullptr; unsigned attributesSize = attributes.size(); for (unsigned i = 0; i < attributesSize; ++i) { SVGTextLayoutAttributes* current = attributes[i]; if (!first && firstContext == ¤t->context()) first = current; if (!last && lastContext == ¤t->context()) last = current; if (first && last) break; } ASSERT(first); ASSERT(last); } static inline void reverseInlineBoxRangeAndValueListsIfNeeded(void* userData, Vector::iterator first, Vector::iterator last) { ASSERT(userData); Vector& attributes = *reinterpret_cast*>(userData); // This is a copy of std::reverse(first, last). It additionally assures that the metrics map within the renderers belonging to the InlineBoxes are reordered as well. while (true) { if (first == last || first == --last) return; if (!is(**last) || !is(**first)) { InlineBox* temp = *first; *first = *last; *last = temp; ++first; continue; } auto& firstTextBox = downcast(**first); auto& lastTextBox = downcast(**last); // Reordering is only necessary for BiDi text that is _absolutely_ positioned. if (firstTextBox.len() == 1 && firstTextBox.len() == lastTextBox.len()) { RenderSVGInlineText& firstContext = firstTextBox.renderer(); RenderSVGInlineText& lastContext = lastTextBox.renderer(); SVGTextLayoutAttributes* firstAttributes = nullptr; SVGTextLayoutAttributes* lastAttributes = nullptr; findFirstAndLastAttributesInVector(attributes, &firstContext, &lastContext, firstAttributes, lastAttributes); swapItemsInLayoutAttributes(firstAttributes, lastAttributes, firstTextBox.start(), lastTextBox.start()); } InlineBox* temp = *first; *first = *last; *last = temp; ++first; } } void SVGRootInlineBox::reorderValueLists(Vector& attributes) { Vector leafBoxesInLogicalOrder; collectLeafBoxesInLogicalOrder(leafBoxesInLogicalOrder, reverseInlineBoxRangeAndValueListsIfNeeded, &attributes); } } // namespace WebCore