You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
510 lines
16 KiB
510 lines
16 KiB
/*
|
|
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());
|
|
}
|
|
|
|
TQString 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(TQString(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;
|
|
TQString 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;
|
|
TQString temp = text.data().string();
|
|
|
|
if(!temp.isEmpty())
|
|
{
|
|
if(m_text->getTextDirection() != LTR)
|
|
{
|
|
TQString 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);
|
|
TQString 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;
|
|
TQString 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);
|
|
TQMemArray<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;
|
|
TQString text;
|
|
TQPtrList<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 += TQChar(' ');
|
|
|
|
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();
|
|
TQString shift = TQString("%1%%").tqarg((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 *tqshape, SVGStylableImpl *style, double x, double y, double angle, const TQString &markerId)
|
|
{
|
|
SVGMarkerElementImpl *marker = dynamic_cast<SVGMarkerElementImpl *>(tqshape->ownerSVGElement()->getElementById(markerId));
|
|
if(marker)
|
|
marker->draw(tqshape, x, y, style->getStrokeWidth()->baseVal()->value(), angle);
|
|
}
|
|
|
|
void MarkerHelper::doStartMarker(SVGShapeImpl *tqshape, SVGStylableImpl *style, double x, double y, double angle)
|
|
{
|
|
doMarker(tqshape, style, x, y, angle, style->getStartMarker());
|
|
}
|
|
|
|
void MarkerHelper::doMidMarker(SVGShapeImpl *tqshape, SVGStylableImpl *style, double x, double y, double angle)
|
|
{
|
|
doMarker(tqshape, style, x, y, angle, style->getMidMarker());
|
|
}
|
|
|
|
void MarkerHelper::doEndMarker(SVGShapeImpl *tqshape, SVGStylableImpl *style, double x, double y, double angle)
|
|
{
|
|
doMarker(tqshape, style, x, y, angle, style->getEndMarker());
|
|
}
|
|
|
|
// vim:ts=4:noet
|