diff options
Diffstat (limited to 'ksvg/core/CanvasItems.cpp')
-rw-r--r-- | ksvg/core/CanvasItems.cpp | 509 |
1 files changed, 509 insertions, 0 deletions
diff --git a/ksvg/core/CanvasItems.cpp b/ksvg/core/CanvasItems.cpp new file mode 100644 index 00000000..0ffd017c --- /dev/null +++ b/ksvg/core/CanvasItems.cpp @@ -0,0 +1,509 @@ +/* + Copyright (C) 2001-2003 KSVG Team + This file is part of the KDE project + + 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 + aint 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 "KSVGCanvas.h" +#include "KSVGHelper.h" +#include "KSVGTextChunk.h" + +#include "CanvasItems.h" + +#include "SVGMatrixImpl.h" +#include "SVGDocumentImpl.h" +#include "SVGSVGElementImpl.h" +#include "SVGPathElementImpl.h" +#include "SVGMarkerElementImpl.h" +#include "SVGTSpanElementImpl.h" +#include "SVGAnimatedLengthImpl.h" +#include "SVGAnimatedStringImpl.h" +#include "SVGAnimatedLengthListImpl.h" +#include "SVGAnimatedEnumerationImpl.h" + +#include <Glyph.h> +#include <Converter.h> +#include <Font.h> + +#include <kdebug.h> + +using namespace KSVG; + +CanvasText::CanvasText(SVGTextElementImpl *text) : CanvasItem(), m_text(text) +{ +} + +CanvasText::~CanvasText() +{ +} + +void CanvasText::handleTSpan(KSVGCanvas *canvas, const SVGMatrixImpl *screenCTM, int &curx, int &cury, int &endx, int &endy, SVGElementImpl *element, KSVGTextChunk *textChunk, T2P::BezierPath *bpath) +{ + SVGTSpanElementImpl *tspan = dynamic_cast<SVGTSpanElementImpl *>(element); + if(!tspan) + return; + + if(!tspan->text().isEmpty() || element->nodeName() == "tref") + { + if((KSVG_TOKEN_NOT_PARSED_ELEMENT(SVGTextPositioningElementImpl::X, tspan) && KSVG_TOKEN_NOT_PARSED_ELEMENT(SVGTextPositioningElementImpl::Y, tspan)))// && !bpath) + textChunk->addText(tspan->text(), tspan); + else + { + // new absolute value for next textChunk, render old one + if(textChunk->count() > 0) + { + createGlyphs(textChunk, canvas, screenCTM, curx, cury, curx, cury, bpath); + textChunk->clear(); + } + + int usex, usey; + bool bMultipleX = false; + bool bMultipleY = false; + + if(tspan->x()->baseVal()->numberOfItems() == 0) + { + usex = curx; + + if(tspan->dx()->baseVal()->numberOfItems() > 0) + usex += int(tspan->dx()->baseVal()->getItem(0)->value()); + } + else + { + if(tspan->x()->baseVal()->numberOfItems() > 1) + bMultipleX = true; + + usex = int(tspan->x()->baseVal()->getItem(0)->value()); + } + + if(tspan->y()->baseVal()->numberOfItems() == 0) + { + usey = cury; + + if(tspan->dy()->baseVal()->numberOfItems() > 0) + usey += int(tspan->dy()->baseVal()->getItem(0)->value()); + } + else + { + if(tspan->y()->baseVal()->numberOfItems() > 1) + bMultipleY = true; + + usey = int(tspan->y()->baseVal()->getItem(0)->value()); + } + + QString text = tspan->text(); + if(!text.isEmpty()) + { + T2P::GlyphLayoutParams *params = tspan->layoutParams(); + + if(bMultipleX || bMultipleY) + { + for(unsigned int i = 0; i < text.length(); i++) + { + if(bMultipleX && i < tspan->x()->baseVal()->numberOfItems()) + usex = int(tspan->x()->baseVal()->getItem(i)->value()); + if(bMultipleY && i < tspan->y()->baseVal()->numberOfItems()) + usey = int(tspan->y()->baseVal()->getItem(i)->value()); + + textChunk->addText(QString(text.at(i)), tspan); + createGlyphs(textChunk, canvas, screenCTM, usex, usey, endx, endy, bpath); + textChunk->clear(); + + if(!params->tb()) + usex += endx; + else + usey += endy; + } + } + else + { + textChunk->addText(text, tspan); + //createGlyphs(textChunk, canvas, screenCTM, usex, usey, endx, endy, bpath); + //textChunk->clear(); + } + + curx = usex; + cury = usey; + + if(!params->tb()) + curx += endx; + else + cury += endy; + + delete params; + } + } + } + + DOM::Node node = (tspan->getTextDirection() == LTR) ? tspan->firstChild() : tspan->lastChild(); + + bool tspanFound = false; + for(; !node.isNull(); node = ((tspan->getTextDirection() == LTR) ? node.nextSibling() : node.previousSibling())) + { + SVGElementImpl *element = m_text->ownerDoc()->getElementFromHandle(node.handle()); + if(node.nodeType() == DOM::Node::TEXT_NODE) + { + if(tspanFound) + { + DOM::Text text = node; + QString temp = text.data().string(); + textChunk->addText(temp, tspan); + } + } + else if(element->nodeName() == "tspan" || element->nodeName() == "tref") + { + tspanFound = true; + handleTSpan(canvas, screenCTM, curx, cury, endx, endy, element, textChunk, 0); + } + } + +} + +KSVGTextChunk *CanvasText::createTextChunk(KSVGCanvas *canvas, const SVGMatrixImpl *screenCTM, int &curx, int &cury, int &endx, int &endy) +{ + KSVGTextChunk *textChunk = new KSVGTextChunk(); + + SVGLengthImpl *length = m_text->x()->baseVal()->getItem(0); + if(length) + curx = int(length->value()); + + length = m_text->y()->baseVal()->getItem(0); + if(length) + cury = int(length->value()); + + // Otherwhise some js scripts which require a child, don't work (Niko) + if(!m_text->hasChildNodes()) + { + DOM::Text impl = static_cast<DOM::Document *>(m_text->ownerDoc())->createTextNode(DOM::DOMString("")); + m_text->appendChild(impl); + } + else + { + DOM::Node node = (m_text->getTextDirection() == LTR) ? m_text->firstChild() : m_text->lastChild(); + + for(; !node.isNull(); node = ((m_text->getTextDirection() == LTR) ? node.nextSibling() : node.previousSibling())) + { + if(node.nodeType() == DOM::Node::TEXT_NODE) + { + DOM::Text text = node; + QString temp = text.data().string(); + + if(!temp.isEmpty()) + { + if(m_text->getTextDirection() != LTR) + { + QString convert = temp; + + for(int i = temp.length(); i > 0; i--) + convert[temp.length() - i] = temp[i - 1]; + + temp = convert; + } + + textChunk->addText(temp, m_text); + } + } + else + { + SVGElementImpl *element = m_text->ownerDoc()->getElementFromHandle(node.handle()); + if(element->nodeName() == "textPath") + { + // new absolute value for next textChunk, render old one + if(textChunk->count() > 0) + { + createGlyphs(textChunk, canvas, screenCTM, curx, cury, curx, cury); + textChunk->clear(); + } + + SVGTextPathElementImpl *tpath = dynamic_cast<SVGTextPathElementImpl *>(element); + QString target = SVGURIReferenceImpl::getTarget(tpath->href()->baseVal().string()); + SVGPathElementImpl *path = dynamic_cast<SVGPathElementImpl *>(tpath->ownerSVGElement()->getElementById(target)); + + T2P::BezierPath *bpath = 0; + if(path && path->item()) + bpath = tpath->ownerDoc()->canvas()->toBezierPath(path->item()); + + DOM::Node iterate = tpath->firstChild(); + for(; !iterate.isNull(); iterate = iterate.nextSibling()) + { + if(iterate.nodeType() == DOM::Node::TEXT_NODE) + { + DOM::Text text = iterate; + QString temp = text.data().string(); + + if(!temp.isEmpty()) + textChunk->addText(temp, tpath); + } + else + { + kdDebug() << "FOUND TSPAN IN TEXTPATH! BPATH:" << bpath <<endl; + + SVGElementImpl *itelement = m_text->ownerDoc()->getElementFromHandle(iterate.handle()); + handleTSpan(canvas, screenCTM, curx, cury, endx, endy, itelement, textChunk, bpath); + } + } + + if(textChunk->count() > 0) + { + int usex = 0, usey = 0; + createGlyphs(textChunk, canvas, screenCTM, usex, usey, endx, endy, bpath); + textChunk->clear(); + + curx = usex; + cury = usey; + + T2P::GlyphLayoutParams *params = tpath->layoutParams(); + + if(!params->tb()) + curx += endx; + else + cury += endy; + + delete params; + } + } + else if(element->nodeName() == "tspan" || element->nodeName() == "tref") + handleTSpan(canvas, screenCTM, curx, cury, endx, endy, element, textChunk, 0); + } + } + } + + return textChunk; +} + +void CanvasText::createGlyphs(KSVGTextChunk *textChunk, KSVGCanvas *canvas, const SVGMatrixImpl *screenCTM, int curx, int cury, int &endx, int &endy, T2P::BezierPath *bpath) const +{ + double _curx = double(curx); + QMemArray<double> _cury(1); + _cury[0] = double(cury); + + T2P::GlyphLayoutParams *params = m_text->layoutParams(); + SVGTextPositioningElementImpl *tp = textChunk->getTextElement(0); + SVGTextContentElementImpl *tc = textChunk->getTextContentElement(0); + SVGTextContentElementImpl *tc0 = tc; + + T2P::SharedFont font; + QString text; + QPtrList<T2P::GlyphSet> glyphs; + glyphs.setAutoDelete(true); + + double pathAdvance = 0; + SVGTextPathElementImpl *tpath = dynamic_cast<SVGTextPathElementImpl *>(tc0); + if(tpath) + pathAdvance = tpath->startOffset()->baseVal()->value(); + double pathLength = bpath ? bpath->length() : 0; + double pathDy = 0; + for(unsigned int i = 0; i < textChunk->count(); i++) + { + tp = textChunk->getTextElement(i); + tc = textChunk->getTextContentElement(i); + + if(tp && tp->dx()->baseVal()->numberOfItems() > 0) + if(bpath) + pathAdvance += tp->dx()->baseVal()->getItem(0)->value() / pathLength; + else + _curx += tp->dx()->baseVal()->getItem(0)->value(); + _cury[i] += (tp && tp->dy()->baseVal()->numberOfItems() > 0) ? tp->dy()->baseVal()->getItem(0)->value() : 0; + + SVGMatrixImpl *tempMatrix = SVGSVGElementImpl::createSVGMatrix(); + tempMatrix->translate(_curx, _cury[i]); + + text = textChunk->getText(i); + if(i != textChunk->count() - 1) + text += QChar(' '); + + if(!canvas->fontContext()->ready()) + canvas->fontContext()->init(); + + font = canvas->fontContext()->requestFont(canvas->fontVisualParams(tc)); + + if(!font) + break; + + double addLetterSpacing = 0; + + // Apply affine corrections, through lengthAdjust + textLength + if(tp && tp->textLength()->baseVal()->value() != -1) + { + // #1 Measure text + SVGTextElementImpl *textElement = dynamic_cast<SVGTextElementImpl *>(tp); + const SVGMatrixImpl *ctm = textElement->screenCTM(); + + T2P::Affine affine; + { + SVGMatrixImpl *temp = SVGSVGElementImpl::createSVGMatrix(); + + temp->multiply(ctm); + temp->translate(_curx, _cury[0]); + + KSVGHelper::matrixToAffine(temp, affine); + + temp->deref(); + } + + T2P::GlyphSet *measure = canvas->fontContext()->calcString(font.get(), text.ucs2(), text.length(), affine, params, bpath); + + // Free bpath's + measure->set().clear(); + + // #2 Calculate textLength + double textLength = tp->textLength()->baseVal()->value(); + + // #3 Apply the spacing + if(tp->lengthAdjust()->baseVal() == LENGTHADJUST_SPACINGANDGLYPHS) + tempMatrix->scaleNonUniform((textLength * ctm->a()) / measure->width(), 1); + else if(tp->lengthAdjust()->baseVal() == LENGTHADJUST_SPACING) + addLetterSpacing = ((textLength - (measure->width() / ctm->a())) / text.length()); + + // #4 cleanup + delete measure; + } + + { + T2P::GlyphLayoutParams *params = tc->layoutParams(); + params->setLetterSpacing(params->letterSpacing() + addLetterSpacing); + if(bpath) + { + params->setTextPathStartOffset(pathAdvance); + if(tp && tp->dy()->baseVal()->numberOfItems() > 0) + pathDy += tp->dy()->baseVal()->getItem(0)->value(); + QString shift = QString("%1%%").arg((pathDy / font->fontParams()->size()) * -100.0); + params->setBaselineShift(shift.latin1()); + } + + T2P::Affine affine; + KSVGHelper::matrixToAffine(tempMatrix, affine); + tempMatrix->deref(); + + T2P::GlyphSet *glyph = canvas->fontContext()->calcString(font.get(), text.ucs2(), text.length(), affine, params, bpath); + if(bpath) + pathAdvance += double(glyph->width()) / pathLength; + _curx += (params->tb() ? 0 : glyph->xpen()); + _cury.resize(i + 2); + _cury[i + 1] = _cury[i] + (params->tb() ? glyph->ypen() : 0); + if(!glyph) + break; + else + glyphs.append(glyph); + + delete params; + } + } + + // Calculate text-anchor + double anchor = 0; + + // anchor == "start" is the default here (Rob) + if(tc->getTextAnchor() == TAMIDDLE) + { + if(!params->tb()) + anchor = ((_curx - curx) + 1) / 2; + else + anchor = ((_cury[textChunk->count()] - cury) + 1) / 2; + } + else if(tc->getTextAnchor() == TAEND) + { + if(!params->tb()) + anchor = (_curx - curx); + else + anchor = (_cury[textChunk->count()] - cury); + } + + // Render all glyphs of the text chunk + // Take first glyphset + T2P::GlyphSet *glyph = glyphs.at(0); + if(!glyph) + return; + + // Draw 'text-decoration' + // TODO: Currently just ignore text-decoration on vertical layouts, is that correct? + // Underline and overline have to be drawn before the glyphs are rendered + if(tc0->getTextDecoration() & UNDERLINE && !params->tb()) + addTextDecoration(tc0, (curx - anchor), (cury + (glyph->underlinePosition() - glyph->pixelBaseline())), + _curx - curx, glyph->underlineThickness()); + if(tc0->getTextDecoration() & OVERLINE && !params->tb()) + addTextDecoration(tc0, (curx - anchor), (cury + (glyph->overlinePosition() - glyph->pixelBaseline())), + _curx - curx, glyph->underlineThickness()); + + for(unsigned int j = 0; j < glyphs.count(); j++) + { + glyph = glyphs.at(j); + SVGTextContentElementImpl *style = textChunk->getTextContentElement(j); + + // Draw 'text-decoration' + // TODO: Currently just ignore text-decoration on vertical layouts, is that correct? + // Underline and overline have to be drawn before the glyphs are rendered + if(style->getAttribute("text-decoration") == "underline" && !params->tb()) + addTextDecoration(style, glyph->bboxX() - anchor, (cury + (glyph->underlinePosition() - glyph->pixelBaseline())), + glyph->width(), glyph->underlineThickness()); + else if(style->getAttribute("text-decoration") == "overline" && !params->tb()) + addTextDecoration(style, glyph->bboxX() - anchor, (cury + (glyph->overlinePosition() - glyph->pixelBaseline())), + glyph->width(), glyph->underlineThickness()); + + renderCallback(style, screenCTM, glyph, params, anchor); + + // Clear GlyphAffinePair's + for(std::vector<T2P::GlyphAffinePair *>::iterator it = glyph->set().begin(); it != glyph->set().end(); ++it) + { + T2P::GlyphAffinePair *glyphAffine = *it; + delete glyphAffine; + } + + glyph->set().clear(); + + // Draw 'line-through' text decoration + // Line-through has to be drawn after the glyphs are rendered + if(style->getAttribute("text-decoration") == "line-through" && !params->tb()) + addTextDecoration(style, glyph->bboxX() - anchor, (cury + (glyph->strikeThroughPosition() - glyph->pixelBaseline())), glyph->width(), glyph->underlineThickness()); + + } + + endx = glyph->bboxX() + glyph->width(); + endy = int(_cury[glyphs.count() - 1]); + + // Draw 'line-through' text decoration + // Line-through has to be drawn after the glyphs are rendered + if(tc0->getTextDecoration() & LINE_THROUGH && !params->tb()) + addTextDecoration(tc0, (curx - anchor), (cury + (glyph->strikeThroughPosition() - glyph->pixelBaseline())), _curx - curx, glyph->underlineThickness()); + + delete params; +} + +// ##### + +void MarkerHelper::doMarker(SVGShapeImpl *shape, SVGStylableImpl *style, double x, double y, double angle, const QString &markerId) +{ + SVGMarkerElementImpl *marker = dynamic_cast<SVGMarkerElementImpl *>(shape->ownerSVGElement()->getElementById(markerId)); + if(marker) + marker->draw(shape, x, y, style->getStrokeWidth()->baseVal()->value(), angle); +} + +void MarkerHelper::doStartMarker(SVGShapeImpl *shape, SVGStylableImpl *style, double x, double y, double angle) +{ + doMarker(shape, style, x, y, angle, style->getStartMarker()); +} + +void MarkerHelper::doMidMarker(SVGShapeImpl *shape, SVGStylableImpl *style, double x, double y, double angle) +{ + doMarker(shape, style, x, y, angle, style->getMidMarker()); +} + +void MarkerHelper::doEndMarker(SVGShapeImpl *shape, SVGStylableImpl *style, double x, double y, double angle) +{ + doMarker(shape, style, x, y, angle, style->getEndMarker()); +} + +// vim:ts=4:noet |