/* * This file is part of the WebKit project. * * Copyright (C) 2006 Oliver Hunt * (C) 2006 Apple Computer Inc. * * 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., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. * */ #include "config.h" #if ENABLE(SVG) #include "SVGInlineFlowBox.h" #include "GraphicsContext.h" #include "InlineTextBox.h" #include "KCanvasRenderingStyle.h" #include "RootInlineBox.h" #include "SVGLengthList.h" #include "SVGNames.h" #include "SVGPaintServer.h" #include "SVGResourceClipper.h" #include "SVGResourceFilter.h" #include "SVGResourceMasker.h" #include "SVGTextPositioningElement.h" #include "SVGURIReference.h" using std::min; using std::max; namespace WebCore { using namespace SVGNames; void SVGInlineFlowBox::paint(RenderObject::PaintInfo& paintInfo, int tx, int ty) { paintSVGInlineFlow(this, object(), paintInfo, tx, ty); } int SVGInlineFlowBox::placeBoxesHorizontally(int x, int& leftPosition, int& rightPosition, bool& needsWordSpacing) { return placeSVGFlowHorizontally(this, x, leftPosition, rightPosition, needsWordSpacing); } void SVGInlineFlowBox::verticallyAlignBoxes(int& heightOfBlock) { placeSVGFlowVertically(this, heightOfBlock); } void paintSVGInlineFlow(InlineFlowBox* flow, RenderObject* object, RenderObject::PaintInfo& paintInfo, int tx, int ty) { if (paintInfo.context->paintingDisabled()) return; paintInfo.context->save(); paintInfo.context->concatCTM(object->localTransform()); FloatRect boundingBox(tx + flow->xPos(), ty + flow->yPos(), flow->width(), flow->height()); SVGElement* svgElement = static_cast(object->element()); ASSERT(svgElement && svgElement->document() && svgElement->isStyled()); SVGStyledElement* styledElement = static_cast(svgElement); const SVGRenderStyle* svgStyle = object->style()->svgStyle(); AtomicString filterId(SVGURIReference::getTarget(svgStyle->filter())); AtomicString clipperId(SVGURIReference::getTarget(svgStyle->clipPath())); AtomicString maskerId(SVGURIReference::getTarget(svgStyle->maskElement())); #if ENABLE(SVG_EXPERIMENTAL_FEATURES) SVGResourceFilter* filter = getFilterById(object->document(), filterId); #endif SVGResourceClipper* clipper = getClipperById(object->document(), clipperId); SVGResourceMasker* masker = getMaskerById(object->document(), maskerId); #if ENABLE(SVG_EXPERIMENTAL_FEATURES) if (filter) filter->prepareFilter(paintInfo.context, boundingBox); else if (!filterId.isEmpty()) svgElement->document()->accessSVGExtensions()->addPendingResource(filterId, styledElement); #endif if (clipper) { clipper->addClient(styledElement); clipper->applyClip(paintInfo.context, boundingBox); } else if (!clipperId.isEmpty()) svgElement->document()->accessSVGExtensions()->addPendingResource(clipperId, styledElement); if (masker) { masker->addClient(styledElement); masker->applyMask(paintInfo.context, boundingBox); } else if (!maskerId.isEmpty()) svgElement->document()->accessSVGExtensions()->addPendingResource(maskerId, styledElement); RenderObject::PaintInfo pi(paintInfo); if (!flow->isRootInlineBox()) pi.rect = (object->localTransform()).inverse().mapRect(pi.rect); float opacity = object->style()->opacity(); if (opacity < 1.0f) { paintInfo.context->clip(enclosingIntRect(boundingBox)); paintInfo.context->beginTransparencyLayer(opacity); } SVGPaintServer* fillPaintServer = KSVGPainterFactory::fillPaintServer(object->style(), object); if (fillPaintServer) { if (fillPaintServer->setup(pi.context, object, ApplyToFillTargetType, true)) { flow->InlineFlowBox::paint(pi, tx, ty); fillPaintServer->teardown(pi.context, object, ApplyToFillTargetType, true); } } SVGPaintServer* strokePaintServer = KSVGPainterFactory::strokePaintServer(object->style(), object); if (strokePaintServer) { if (strokePaintServer->setup(pi.context, object, ApplyToStrokeTargetType, true)) { flow->InlineFlowBox::paint(pi, tx, ty); strokePaintServer->teardown(pi.context, object, ApplyToStrokeTargetType, true); } } #if ENABLE(SVG_EXPERIMENTAL_FEATURES) if (filter) filter->applyFilter(paintInfo.context, boundingBox); #endif if (opacity < 1.0f) paintInfo.context->endTransparencyLayer(); paintInfo.context->restore(); } static bool translateBox(InlineBox* box, int x, int y, bool topLevel) { if (box->object()->isText()) { box->setXPos(box->xPos() + x); box->setYPos(box->yPos() + y); } else if (!box->object()->element()->hasTagName(aTag)) { InlineFlowBox* flow = static_cast(box); SVGTextPositioningElement* text = static_cast(box->object()->element()); if (topLevel || !(text->x()->getFirst().value() || text->y()->getFirst().value() || text->dx()->getFirst().value() || text->dy()->getFirst().value())) { box->setXPos(box->xPos() + x); box->setYPos(box->yPos() + y); for (InlineBox* curr = flow->firstChild(); curr; curr = curr->nextOnLine()) { if (!translateBox(curr, x, y, false)) return false; } } } return true; } static int placePositionedBoxesHorizontally(InlineFlowBox* flow, int x, int& leftPosition, int& rightPosition, int& leftAlign, int& rightAlign, bool& needsWordSpacing, int xPos, bool positioned) { int mn = INT_MAX; int mx = INT_MIN; int amn = INT_MAX; int amx = INT_MIN; int startx = x; bool seenPositionedElement = false; flow->setXPos(x); for (InlineBox* curr = flow->firstChild(); curr; curr = curr->nextOnLine()) { if (curr->object()->isText()) { mn = min(mn, x); amn = min(amn, x); InlineTextBox* text = static_cast(curr); RenderText* rt = static_cast(text->object()); if (rt->textLength()) { if (needsWordSpacing && DeprecatedChar(rt->characters()[text->start()]).isSpace()) x += rt->style(flow->isFirstLineStyle())->font().wordSpacing(); needsWordSpacing = !DeprecatedChar(rt->characters()[text->end()]).isSpace(); } text->setXPos(x); x += text->width(); mx = max(mx, x); amx = max(amx, x); } else if (curr->object()->isInlineFlow()) { InlineFlowBox* flow = static_cast(curr); if (flow->object()->element()->hasTagName(aTag)) { x = placePositionedBoxesHorizontally(flow, x, mn, mx, amn, amx, needsWordSpacing, xPos, false); } else { SVGTextPositioningElement* text = static_cast(flow->object()->element()); x += (int)(text->dx()->getFirst().value()); if (text->x()->numberOfItems() > 0) x = (int)(text->x()->getFirst().value() - xPos); if (text->x()->numberOfItems() > 0 || text->y()->numberOfItems() > 0 || text->dx()->numberOfItems() > 0 || text->dy()->numberOfItems() > 0) { seenPositionedElement = true; needsWordSpacing = false; int ignoreX, ignoreY; x = placePositionedBoxesHorizontally(flow, x, mn, mx, ignoreX, ignoreY, needsWordSpacing, xPos, true); } else if (seenPositionedElement) { int ignoreX, ignoreY; x = placePositionedBoxesHorizontally(flow, x, mn, mx, ignoreX, ignoreY, needsWordSpacing, xPos, false); } else x = placePositionedBoxesHorizontally(flow, x, mn, mx, amn, amx, needsWordSpacing, xPos, false); } } } if (mn > mx) mn = mx = startx; if (amn > amx) amn = amx = startx; int width = mx - mn; flow->setWidth(width); int awidth = amx - amn; int dx = 0; if (positioned) { switch (flow->object()->style()->svgStyle()->textAnchor()) { case TA_MIDDLE: translateBox(flow, dx = -awidth / 2, 0, true); break; case TA_END: translateBox(flow, dx = -awidth, 0, true); break; case TA_START: default: break; } if (dx) { x += dx; mn += dx; mx += dx; } } leftPosition = min(leftPosition, mn); rightPosition = max(rightPosition, mx); leftAlign = min(leftAlign, amn); rightAlign = max(rightAlign, amx); return x; } int placeSVGFlowHorizontally(InlineFlowBox* flow, int x, int& leftPosition, int& rightPosition, bool& needsWordSpacing) { int ignoreX, ignoreY; x = placePositionedBoxesHorizontally(flow, x, leftPosition, rightPosition, ignoreX, ignoreY, needsWordSpacing, flow->object()->xPos(), true); needsWordSpacing = false; return x; } static void placeBoxesVerticallyWithAbsBaseline(InlineFlowBox* flow, int& heightOfBlock, int& minY, int& maxY, int& baseline, int yPos) { for (InlineBox* curr = flow->firstChild(); curr; curr = curr->nextOnLine()) { if (curr->isInlineFlowBox() && !curr->object()->element()->hasTagName(aTag)) { SVGTextPositioningElement* text = static_cast(curr->object()->element()); baseline += (int)(text->dy()->getFirst().value()); if (text->y()->numberOfItems() > 0) baseline = (int)(text->y()->getFirst().value() - yPos); placeBoxesVerticallyWithAbsBaseline(static_cast(curr), heightOfBlock, minY, maxY, baseline, yPos); } const Font& font = curr->object()->firstLineStyle()->font(); // FIXME: Is it right to always use firstLineStyle here? int ascent = font.ascent(); int position = baseline - ascent; int height = ascent + font.descent(); curr->setBaseline(ascent); curr->setYPos(position); curr->setHeight(height); if (position < minY) minY = position; if (position + height > maxY) maxY = position + height; } if (flow->isRootInlineBox()) { flow->setYPos(minY); flow->setHeight(maxY - minY); flow->setBaseline(baseline - minY); heightOfBlock += maxY - minY; } } void placeSVGFlowVertically(InlineFlowBox* flow, int& heightOfBlock) { int top = INT_MAX; int bottom = INT_MIN; int baseline = heightOfBlock; placeBoxesVerticallyWithAbsBaseline(flow, heightOfBlock, top, bottom, baseline, flow->object()->yPos()); flow->setVerticalOverflowPositions(top, bottom); flow->setVerticalSelectionPositions(top, bottom); } } // namespace WebCore #endif // ENABLE(SVG)