Rewrite of page rendering. Now it respects context and actual work is done at the end of element so child elements are taken into account

svn path=/trunk/playground/graphics/okular/; revision=632324
remotes/origin/KDE/4.0
Jiri Klement 19 years ago
parent e24f08a75f
commit f0eaa1fbff
  1. 399
      generators/xps/generator_xps.cpp
  2. 72
      generators/xps/generator_xps.h

@ -159,7 +159,10 @@ static bool nextAbbPathToken(AbbPathToken *token)
return true;
}
QPointF XpsHandler::getPointFromString(AbbPathToken *token, bool relative) {
/**
Read point (two reals delimited by comma) from abbreviated path data
*/
static QPointF getPointFromString(AbbPathToken *token, bool relative, const QPointF currentPosition) {
//TODO Check grammar
QPointF result;
@ -171,18 +174,23 @@ QPointF XpsHandler::getPointFromString(AbbPathToken *token, bool relative) {
if (relative)
{
result += m_currentPath.currentPosition();
result += currentPosition;
}
return result;
}
void XpsHandler::parseAbbreviatedPathData( const QString &data)
/**
Parse an abbreviated path "Data" description
\param data the string containing the whitespace separated values
\see XPS specification 4.2.3 and Appendix G
*/
static QPainterPath parseAbbreviatedPathData( const QString &data)
{
AbbPathToken token;
QPainterPath path = QPainterPath();
kDebug() << data << endl;
AbbPathToken token;
token.data = data;
token.curPos = 0;
@ -201,11 +209,12 @@ void XpsHandler::parseAbbreviatedPathData( const QString &data)
{
kDebug() << "Error in parsing abbreviated path data" << endl;
}
return;
return path;
}
char command = QChar(token.command).toLower().cell();
bool isRelative = QChar(token.command).isLower();
QPointF currPos = path.currentPosition();
nextAbbPathToken(&token);
switch (command) {
@ -214,52 +223,52 @@ void XpsHandler::parseAbbreviatedPathData( const QString &data)
rule = (int)token.number;
if (rule == 0)
{
m_currentPath.setFillRule(Qt::OddEvenFill);
path.setFillRule(Qt::OddEvenFill);
}
else if (rule == 1)
{
// In xps specs rule 1 means NonZero fill. I think it's equivalent to WindingFill but I'm not sure
m_currentPath.setFillRule(Qt::WindingFill);
path.setFillRule(Qt::WindingFill);
}
nextAbbPathToken(&token);
break;
case 'm': // Move
while (token.type == abtNumber)
{
QPointF point = getPointFromString(&token, isRelative);
m_currentPath.moveTo(point);
QPointF point = getPointFromString(&token, isRelative, currPos);
path.moveTo(point);
}
break;
case 'l': // Line
while (token.type == abtNumber)
{
QPointF point = getPointFromString(&token, isRelative);
m_currentPath.lineTo(point);
QPointF point = getPointFromString(&token, isRelative, currPos);
path.lineTo(point);
}
break;
case 'h': // Horizontal line
while (token.type == abtNumber)
{
double x = token.number + isRelative?m_currentPath.currentPosition().x():0;
m_currentPath.lineTo(x, m_currentPath.currentPosition().y());
double x = token.number + isRelative?path.currentPosition().x():0;
path.lineTo(x, path.currentPosition().y());
nextAbbPathToken(&token);
}
break;
case 'v': // Vertical line
while (token.type == abtNumber)
{
double y = token.number + isRelative?m_currentPath.currentPosition().y():0;
m_currentPath.lineTo(m_currentPath.currentPosition().x(), y);
double y = token.number + isRelative?path.currentPosition().y():0;
path.lineTo(path.currentPosition().x(), y);
nextAbbPathToken(&token);
}
break;
case 'c': // Cubic bezier curve
while (token.type == abtNumber)
{
QPointF firstControl = getPointFromString(&token, isRelative);
QPointF secondControl = getPointFromString(&token, isRelative);
QPointF endPoint = getPointFromString(&token, isRelative);
m_currentPath.cubicTo(firstControl, secondControl, endPoint);
QPointF firstControl = getPointFromString(&token, isRelative, currPos);
QPointF secondControl = getPointFromString(&token, isRelative, currPos);
QPointF endPoint = getPointFromString(&token, isRelative, currPos);
path.cubicTo(firstControl, secondControl, endPoint);
lastSecondControlPoint = secondControl;
}
@ -267,9 +276,9 @@ void XpsHandler::parseAbbreviatedPathData( const QString &data)
case 'q': // Quadratic bezier curve
while (token.type == abtNumber)
{
QPointF point1 = getPointFromString(&token, isRelative);
QPointF point2 = getPointFromString(&token, isRelative);
m_currentPath.quadTo(point2, point2);
QPointF point1 = getPointFromString(&token, isRelative, currPos);
QPointF point2 = getPointFromString(&token, isRelative, currPos);
path.quadTo(point2, point2);
}
break;
case 's': // Smooth cubic bezier curve
@ -278,38 +287,40 @@ void XpsHandler::parseAbbreviatedPathData( const QString &data)
QPointF firstControl;
if ((lastCommand == 's') || (lastCommand == 'c'))
{
firstControl = lastSecondControlPoint + (lastSecondControlPoint + m_currentPath.currentPosition());
firstControl = lastSecondControlPoint + (lastSecondControlPoint + path.currentPosition());
}
else
{
firstControl = m_currentPath.currentPosition();
firstControl = path.currentPosition();
}
QPointF secondControl = getPointFromString(&token, isRelative);
QPointF endPoint = getPointFromString(&token, isRelative);
m_currentPath.cubicTo(firstControl, secondControl, endPoint);
QPointF secondControl = getPointFromString(&token, isRelative, currPos);
QPointF endPoint = getPointFromString(&token, isRelative, currPos);
path.cubicTo(firstControl, secondControl, endPoint);
}
break;
case 'a': // Arc
//TODO Implement Arc drawing
while (token.type == abtNumber)
{
/*QPointF rp =*/ getPointFromString(&token, isRelative);
/*QPointF rp =*/ getPointFromString(&token, isRelative, currPos);
/*double r = token.number;*/
nextAbbPathToken(&token);
/*double fArc = token.number; */
nextAbbPathToken(&token);
/*double fSweep = token.number; */
nextAbbPathToken(&token);
/*QPointF point = */getPointFromString(&token, isRelative);
/*QPointF point = */getPointFromString(&token, isRelative, currPos);
}
break;
case 'z': // Close path
m_currentPath.closeSubpath();
path.closeSubpath();
break;
}
lastCommand = command;
}
return path;
}
QMatrix XpsHandler::attsToMatrix( const QString &csv )
@ -323,6 +334,28 @@ QMatrix XpsHandler::attsToMatrix( const QString &csv )
values.at(4).toDouble(), values.at(5).toDouble() );
}
QBrush XpsHandler::parseRscRefColor( const QString &data )
{
if (data[0] == '{') {
//TODO
kDebug() << "Reference" << data << endl;
return QBrush();
} else {
return QBrush( hexToRgba( data.toLatin1() ) );
}
}
QMatrix XpsHandler::parseRscRefMatrix( const QString &data )
{
if (data[0] == '{') {
//TODO
kDebug() << "Reference" << data << endl;
return QMatrix();
} else {
return attsToMatrix( data );
}
}
XpsHandler::XpsHandler(XpsPage *page): m_page(page)
{
m_painter = new QPainter(m_page->m_pageImage);
@ -335,82 +368,30 @@ XpsHandler::~XpsHandler()
bool XpsHandler::startDocument()
{
// kDebug() << "start document" << endl;
kDebug() << "start document" << m_page->m_fileName << endl;
m_page->m_pageImage->fill( QColor("White").rgba() );
XpsRenderNode node;
node.name = "document";
m_nodes.push(node);
return true;
}
bool XpsHandler::startElement( const QString &nameSpace,
const QString &localName,
const QString &qname,
const QXmlAttributes & atts )
{
Q_UNUSED(nameSpace);
Q_UNUSED(qname);
if (localName == "Glyphs") {
m_painter->save();
int fontId = m_page->getFontByName( atts.value("FontUri") );
// kDebug() << "Font families: (" << fontId << ") " << QFontDatabase::applicationFontFamilies( fontId ).at(0) << endl;
QString fontFamily = m_page->m_fontDatabase.applicationFontFamilies( fontId ).at(0);
// kDebug() << "Styles: " << m_page->m_fontDatabase.styles( fontFamily ) << endl;
QString fontStyle = m_page->m_fontDatabase.styles( fontFamily ).at(0);
// TODO: We may not be picking the best font size here
QFont font = m_page->m_fontDatabase.font(fontFamily, fontStyle, qRound(atts.value("FontRenderingEmSize").toFloat()) );
m_painter->setFont(font);
QPointF origin( atts.value("OriginX").toDouble(), atts.value("OriginY").toDouble() );
QColor fillColor = hexToRgba( atts.value("Fill").toLatin1() );
kDebug() << "Indices " << atts.value("Indices") << endl;
m_painter->setBrush(fillColor);
m_painter->setPen(fillColor);
m_painter->drawText( origin, atts.value("UnicodeString") );
// kDebug() << "Glyphs: " << atts.value("Fill") << ", " << atts.value("FontUri") << endl;
// kDebug() << " Origin: " << atts.value("OriginX") << "," << atts.value("OriginY") << endl;
// kDebug() << " Unicode: " << atts.value("UnicodeString") << endl;
} else if (localName == "Path") {
m_painter->save();
// kDebug() << "Path: " << atts.value("Data") << ", " << atts.value("Fill") << endl;
if (! atts.value("Data").isEmpty() ) {
parseAbbreviatedPathData( atts.value("Data") );
}
if (! atts.value("Fill").isEmpty() ) {
QColor fillColor;
if ( atts.value("Fill").startsWith('#') ) {
fillColor = hexToRgba( atts.value("Fill").toLatin1() );
m_currentBrush = QBrush( fillColor );
m_currentPen = QPen ( fillColor );
} else {
m_currentBrush = QBrush();
kDebug() << "Unknown / unhandled fill color representation:" << atts.value("Fill") << ":ende" << endl;
}
}
} else if ( localName == "SolidColorBrush" ) {
if (! atts.value("Color").isEmpty() ) {
QColor fillColor;
if (atts.value("Color").startsWith('#') ) {
fillColor = hexToRgba( atts.value("Color").toLatin1() );
// kDebug() << "Solid colour: " << fillColor << endl;
} else {
kDebug() << "Unknown / unhandled fill color representation:" << atts.value("Color") << ":ende" << endl;
}
m_currentBrush = QBrush( fillColor );
}
} else if ( localName == "ImageBrush" ) {
kDebug() << "ImageBrush Attributes: " << atts.count() << ", " << atts.value("Transform") << ", " << atts.value("TileMode") << endl;
m_image = m_page->loadImageFromFile( atts.value("ImageSource" ) );
m_viewbox = stringToRectF( atts.value("Viewbox") );
m_viewport = stringToRectF( atts.value("Viewport") );
} else if ( localName == "Canvas" ) {
// TODO
m_painter->save();
} else if ( localName == "MatrixTransform" ) {
// kDebug() << "Matrix transform: " << atts.value("Matrix") << endl;
m_painter->setWorldMatrix( attsToMatrix( atts.value("Matrix") ), true );
} else if ( localName == "Path.Fill" ) {
// this doesn't have any attributes - just other elements that we handle elsewhere
} else {
kDebug() << "unknown start element: " << localName << endl;
}
Q_UNUSED( nameSpace )
Q_UNUSED( qname )
XpsRenderNode node;
node.name = localName;
node.attributes = atts;
processStartElement( node );
m_nodes.push(node);
return true;
}
@ -419,30 +400,189 @@ bool XpsHandler::endElement( const QString &nameSpace,
const QString &localName,
const QString &qname)
{
Q_UNUSED(nameSpace);
Q_UNUSED(qname);
if ( localName == "Path" ) {
m_painter->save();
m_painter->setBrush( m_currentBrush );
m_painter->setPen( m_currentPen );
m_painter->drawPath( m_currentPath );
Q_UNUSED( nameSpace )
Q_UNUSED( qname )
XpsRenderNode node = m_nodes.pop();
if (node.name != localName) {
kDebug() << "Name doesn't match" << endl;
}
processEndElement( node );
node.children.clear();
m_nodes.top().children.append(node);
return true;
}
if (! m_image.isNull() ) {
m_painter->drawImage( m_viewport, m_image, m_viewbox );
void XpsHandler::processGlyph( XpsRenderNode &node )
{
//TODO Currently ignored attributes: BidiLevel, CaretStops, DeviceFontName, IsSideways, Indices, StyleSimulation, Clip, Opacity, OpacityMask, Name, FixedPage.NavigateURI, xml:lang, x:key
//TODO Currently ignored child elements: Clip, OpacityMask
//Handled separately: RenderTransform
QString att;
m_painter->save();
// Get font (doesn't work well because qt doesn't allow to load font from file)
int fontId = m_page->getFontByName( node.attributes.value("FontUri") );
// kDebug() << "Font families: (" << fontId << ") " << QFontDatabase::applicationFontFamilies( fontId ).at(0) << endl;
QString fontFamily = m_page->m_fontDatabase.applicationFontFamilies( fontId ).at(0);
// kDebug() << "Styles: " << m_page->m_fontDatabase.styles( fontFamily ) << endl;
QString fontStyle = m_page->m_fontDatabase.styles( fontFamily ).at(0);
// TODO: We may not be picking the best font size here
QFont font = m_page->m_fontDatabase.font(fontFamily, fontStyle, qRound(node.attributes.value("FontRenderingEmSize").toFloat()) );
m_painter->setFont(font);
//Origin
QPointF origin( node.attributes.value("OriginX").toDouble(), node.attributes.value("OriginY").toDouble() );
//Fill
QBrush brush;
att = node.attributes.value("Fill");
if (att.isEmpty()) {
XpsRenderNode * child = node.findChild("Glyphs.Fill");
if (child != NULL) {
brush = *(XpsFill *)child->data;
} else {
brush = QBrush();
}
} else {
brush = parseRscRefColor( att );
}
m_painter->setBrush( brush );
m_painter->setPen( QPen( brush, 0 ) );
//RenderTransform
att = node.attributes.value("RenderTransform");
if (!att.isEmpty()) {
m_painter->setWorldMatrix( parseRscRefMatrix( att ), true);
}
m_painter->drawText( origin, node.attributes.value("UnicodeString") );
// kDebug() << "Glyphs: " << atts.value("Fill") << ", " << atts.value("FontUri") << endl;
// kDebug() << " Origin: " << atts.value("OriginX") << "," << atts.value("OriginY") << endl;
// kDebug() << " Unicode: " << atts.value("UnicodeString") << endl;
m_painter->restore();
}
void XpsHandler::processFill( XpsRenderNode &node )
{
//TODO Ignored child elements: ImageBrush, LinearGradientBrush, RadialGradientBrush, VirtualBrush
QBrush brush;
XpsRenderNode * child;
child = node.findChild("SolidColorBrush");
if (child != NULL) {
brush = QBrush( *(QColor *) child->data );
}
child = node.findChild("ImageBrush");
if (child != NULL) {
brush = *(XpsImageBrush *) child->data;
}
node.data = new QBrush( brush );
}
void XpsHandler::processImageBrush( XpsRenderNode &node )
{
//TODO Ignored attributes: Opacity, x:key, Transform, Viewbox, Viewport, TileMode, ViewBoxUnits, ViewPortUnits
//TODO Ignored child elements: ImageBrush.Transform
QPixmap image = m_page->loadImageFromFile( node.attributes.value( "ImageSource" ) );
// image = image.convertToFormat( QImage::Format_ARGB32 );
kDebug() << image.size() << endl;
QBrush brush = QBrush( image );
kDebug() << brush << endl;
kDebug() << brush.isOpaque() << endl;
node.data = new XpsImageBrush ( brush );
}
void XpsHandler::processPath( XpsRenderNode &node )
{
//TODO Ignored attributes: Clip, Opacity, OpacityMask, Stroke, StrokeDashArray, StrokeDashCap, StrokeDashOffset, StrokeEndLineCap, StorkeStartLineCap, StrokeLineJoin, StrokeMitterLimit, StrokeThickness, Name, FixedPage.NavigateURI, xml:lang, x:key, AutomationProperties.Name, AutomationProperties.HelpText, SnapsToDevicePixels
//TODO Ignored child elements: RenderTransform, Clip, OpacityMask, Stroke, Data
// Handled separately: RenderTransform
m_painter->save();
QString att;
QPainterPath path;
// Get path
att = node.attributes.value( "Data" );
if (! att.isEmpty() ) {
path = parseAbbreviatedPathData( att );
} else {
path = QPainterPath(); //TODO
}
// Set Fill
att = node.attributes.value( "Fill" );
QBrush brush;
if (! att.isEmpty() ) {
brush = parseRscRefColor( att );
} else {
XpsRenderNode * child = node.findChild("Path.Fill");
if (child != NULL) {
brush = *(XpsFill *)child->data;
} else {
brush = QBrush();
}
}
m_painter->setBrush( brush );
m_painter->setPen( QPen( brush, 0 ) );
// RenderTransform
att = node.attributes.value( "RenderTransform" );
if (! att.isEmpty() ) {
m_painter->setWorldMatrix( parseRscRefMatrix( att ), true );
}
m_painter->drawPath( path );
m_painter->restore();
}
void XpsHandler::processStartElement( XpsRenderNode &node )
{
if (node.name == "Canvas") {
m_painter->save();
}
}
void XpsHandler::processEndElement( XpsRenderNode &node )
{
if (node.name == "Glyphs") {
processGlyph( node );
} else if (node.name == "Path") {
processPath( node );
} else if (node.name == "MatrixTransform") {
//TODO Ignoring x:key
node.data = new QMatrix ( attsToMatrix( node.attributes.value( "Matrix" ) ) );
} else if ((node.name == "Canvas.RenderTransform") || (node.name == "Glyphs.RenderTransform") || (node.name == "Path.RenderTransform")) {
XpsRenderNode * child = node.findChild( "MatrixTransform" );
if (child == NULL) {
kDebug() << "Required element MatrixTransform is missing" << endl;
} else {
m_painter->setWorldMatrix( *(XpsMatrixTransform *)child->data, true );
}
node.data = NULL;
} else if (node.name == "Canvas") {
m_painter->restore();
m_currentPath = QPainterPath();
m_currentBrush = QBrush();
m_currentPen = QPen();
m_image = QImage();
m_painter->restore();
} else if ( localName == "Canvas" ) {
m_painter->restore();
} else if ( localName == "Glyphs" ) {
m_painter->restore();
} else if ((node.name == "Path.Fill") || (node.name == "Glyphs.Fill")) {
processFill( node );
} else if (node.name == "SolidColorBrush") {
//TODO Ignoring opacity, x:key
node.data = new QColor (hexToRgba( node.attributes.value( "Color" ).toLatin1() ));
} else if (node.name == "ImageBrush") {
processImageBrush( node );
} else {
//kDebug() << "Unknown element: " << node->name << endl;
}
return true;
}
bool XpsPageSizeHandler::startElement ( const QString &nameSpace, const QString &localName, const QString &qname, const QXmlAttributes &atts)
@ -507,7 +647,7 @@ bool XpsPage::renderToImage( QImage *p )
if ((m_pageImage == NULL) || (m_pageImage->size() != p->size())) {
delete m_pageImage;
m_pageImage = new QImage( p->size(), QImage::Format_RGB32 );
m_pageImage = new QImage( p->size(), QImage::Format_ARGB32 );
m_pageIsRendered = false;
}
if (! m_pageIsRendered) {
@ -593,17 +733,17 @@ int XpsPage::loadFontByName( const QString &fileName )
return result; // a font ID
}
QImage XpsPage::loadImageFromFile( const QString &fileName )
QPixmap XpsPage::loadImageFromFile( const QString &fileName )
{
kDebug() << "image file name: " << fileName << endl;
//kDebug() << "image file name: " << fileName << endl;
const KZipFileEntry* imageFile = static_cast<const KZipFileEntry *>(m_archive->directory()->entry( fileName ));
QByteArray imageData = imageFile->data(); // once per file, according to the docs
QImage image;
bool result = image.loadFromData( imageData );
kDebug() << "Image load result: " << result << ", " << image.size() << endl;
QPixmap image;
image.loadFromData( imageData);
//kDebug() << "Image load result: " << result << ", " << image.size() << endl;
return image;
}
@ -928,6 +1068,17 @@ const Okular::DocumentInfo * XpsGenerator::generateDocumentInfo()
return m_xpsFile->generateDocumentInfo();
}
XpsRenderNode * XpsRenderNode::findChild( const QString &name )
{
for (int i = 0; i < children.size(); i++) {
if (children[i].name == name) {
return &children[i];
}
}
return NULL;
}
#include "generator_xps.moc"

@ -24,21 +24,44 @@
#include <QDomDocument>
#include <QXmlDefaultHandler>
#include <QStack>
#include <kzip.h>
//TODO Make this type private
typedef enum {abtCommand, abtNumber, abtComma, abtEOF} AbbPathTokenType;
typedef struct {
class AbbPathToken {
public:
QString data;
int curPos;
AbbPathTokenType type;
char command;
double number;
} AbbPathToken;
};
/**
Holds information about xml element during SAX parsing of page
*/
class XpsRenderNode
{
public:
QString name;
QVector<XpsRenderNode> children;
QXmlAttributes attributes;
void * data;
XpsRenderNode * findChild( const QString &name );
};
/**
Types of data in XpsRenderNode::data. Name of each type consist of Xps and
name of xml element which data it holds
*/
typedef QMatrix XpsMatrixTransform;
typedef QMatrix XpsRenderTransform;
typedef QBrush XpsFill;
typedef QBrush XpsImageBrush;
class XpsPage;
@ -58,20 +81,6 @@ public:
bool startDocument();
private:
/**
Parse an abbreviated path "Data" description
\param data the string containing the whitespace separated values
\note sets the m_currentPath and possibly other private variables
\see XPS specification 4.2.3 and Appendix G
*/
void parseAbbreviatedPathData( const QString &data);
/**
Read point (two reals delimited by comma) from abbreviated path data
*/
QPointF getPointFromString(AbbPathToken *token, bool relative);
/**
Parse a "Matrix" attribute string
@ -83,16 +92,31 @@ private:
*/
QMatrix attsToMatrix( const QString &csv );
void processStartElement( XpsRenderNode &node );
void processEndElement( XpsRenderNode &node );
// Methods for processing of diferent xml elements
void processGlyph( XpsRenderNode &node );
void processPath( XpsRenderNode &node );
void processFill( XpsRenderNode &node );
void processImageBrush (XpsRenderNode &node );
/**
\return Brush with given color or brush specified by reference to resource
*/
QBrush parseRscRefColor( const QString &data );
/**
\return Matrix specified by given data or by referenced dictionary
*/
QMatrix parseRscRefMatrix( const QString &data );
XpsPage *m_page;
QPainter *m_painter;
QBrush m_currentBrush;
QPen m_currentPen;
QPainterPath m_currentPath;
QImage m_image;
QRectF m_viewbox;
QRectF m_viewport;
QStack<XpsRenderNode> m_nodes;
friend class XpsPage;
};
@ -125,7 +149,7 @@ public:
QSize size() const;
bool renderToImage( QImage *p );
QImage loadImageFromFile( const QString &filename );
QPixmap loadImageFromFile( const QString &filename );
int loadFontByName( const QString &fontName );
int getFontByName( const QString &fontName );

Loading…
Cancel
Save