An initial commit of pixmap rendering. This is very ugly code,

but it does work a bit (well, except for the font loading:
http://www.trolltech.com/developer/task-tracker/index_html?method=entry&id=130976
is a bad problem)

svn path=/trunk/playground/graphics/okular/; revision=587041
This commit is contained in:
Brad Hards 2006-09-21 11:41:46 +00:00
parent 62e29bcc8f
commit 4b68351612
2 changed files with 349 additions and 17 deletions

View file

@ -19,8 +19,10 @@
#include <qdatetime.h>
#include <qfile.h>
#include <qfontdatabase.h>
#include <qimage.h>
#include <qlist.h>
#include <qpainter.h>
#include <qpixmap.h>
#include <qthread.h>
#include <kglobal.h>
@ -32,8 +34,229 @@
OKULAR_EXPORT_PLUGIN(XpsGenerator)
// From Qt4
static int hex2int(char hex)
{
QChar hexchar = QLatin1Char(hex);
int v;
if (hexchar.isDigit())
v = hexchar.digitValue();
else if (hexchar >= QLatin1Char('A') && hexchar <= QLatin1Char('F'))
v = hexchar.cell() - 'A' + 10;
else if (hexchar >= QLatin1Char('a') && hexchar <= QLatin1Char('f'))
v = hexchar.cell() - 'a' + 10;
else
v = -1;
return v;
}
XpsPage::XpsPage(KZip *archive, const QString &fileName)
// Modified from Qt4
static QColor hexToRgba(const char *name)
{
if(name[0] != '#')
return QColor();
name++; // eat the leading '#'
int len = qstrlen(name);
int r, g, b;
int a = 255;
if (len == 6) {
r = (hex2int(name[0]) << 4) + hex2int(name[1]);
g = (hex2int(name[2]) << 4) + hex2int(name[3]);
b = (hex2int(name[4]) << 4) + hex2int(name[5]);
} else if (len == 8) {
a = (hex2int(name[0]) << 4) + hex2int(name[1]);
r = (hex2int(name[2]) << 4) + hex2int(name[3]);
g = (hex2int(name[4]) << 4) + hex2int(name[5]);
b = (hex2int(name[6]) << 4) + hex2int(name[7]);
} else {
r = g = b = -1;
}
if ((uint)r > 255 || (uint)g > 255 || (uint)b > 255) {
return QColor();
}
return QColor(r,g,b,a);
}
static QRectF stringToRectF( const QString &data )
{
QStringList numbers = data.split(',');
QPointF origin( numbers.at(0).toDouble(), numbers.at(1).toDouble() );
QSizeF size( numbers.at(2).toDouble(), numbers.at(3).toDouble() );
return QRectF( origin, size );
}
static QPointF getPointFromString( const QString &data, int *curPos )
{
// find the first ',' after the current position
int endOfNumberPos = data.indexOf(',', *curPos);
QString numberPart = data.mid(*curPos, endOfNumberPos - *curPos);
// kDebug() << "Number part 1: " << numberPart << endl;
double firstVal = numberPart.toDouble();
*curPos = endOfNumberPos + 1; //eat the number and the following comma
endOfNumberPos = data.indexOf(' ', *curPos);
numberPart = data.mid (*curPos, endOfNumberPos - *curPos);
// kDebug() << "Number part 2: " << numberPart << endl;
double secondVal = numberPart.toDouble();
*curPos = endOfNumberPos;
// kDebug() << "firstVal: " << firstVal << endl;
// kDebug() << "secondVal: " << secondVal << endl;
return QPointF( firstVal, secondVal );
}
// TODO: convert this into a member
static QPainterPath abbreviatedDataToPath( const QString &data)
{
QPainterPath path;
kDebug() << data << endl;
enum OperationType { moveTo, relMoveTo, lineTo, relLineTo, cubicTo, relCubicTo };
OperationType operation;
// TODO: Implement a proper parser here.
for (int curPos = 0; curPos < data.length(); ++curPos) {
// kDebug() << "curPos: " << curPos << endl;
if (data.at(curPos) == 'M') {
// kDebug() << "operation moveTo" << endl;
operation = moveTo;
} else if (data.at(curPos) == 'L') {
// kDebug() << "operation lineTo" << endl;
operation = lineTo;
} else if (data.at(curPos) == 'C') {
operation = cubicTo;
} else if ( (data.at(curPos) == 'z') || (data.at(curPos) == 'Z') ){
// kDebug() << "operation close" << endl;
path.closeSubpath();
} else if (data.at(curPos).isNumber()) {
if ( operation == moveTo ) {
QPointF point = getPointFromString( data, &curPos );
path.moveTo( point );
} else if ( operation == lineTo ) {
QPointF point = getPointFromString( data, &curPos );
path.lineTo( point );
} else if (operation == cubicTo ) {
QPointF point1 = getPointFromString( data, &curPos );
while ((data.at(curPos).isSpace())) { ++curPos; }
QPointF point2 = getPointFromString( data, &curPos );
while (data.at(curPos).isSpace()) { curPos++; }
QPointF point3 = getPointFromString( data, &curPos );
// kDebug() << "cubic" << point1 << " : " << point2 << " : " << point3 << endl;
path.cubicTo( point1, point2, point3 );
}
} else if (data.at(curPos) == ' ') {
// Do nothing
// kDebug() << "eating a space" << endl;
} else {
kDebug() << "Unexpected data: " << data.at(curPos) << endl;
}
}
return path;
}
XpsHandler::XpsHandler(QPixmap *p, XpsPage *page): m_page(page), m_pixmap( p )
{
}
XpsHandler::~XpsHandler()
{}
bool XpsHandler::startDocument()
{
// kDebug() << "start document" << endl;
m_pixmap->fill(); // default fill colour is white
m_painter = new QPainter(m_pixmap);
return true;
}
bool XpsHandler::startElement( const QString &nameSpace,
const QString &localName,
const QString &qname,
const QXmlAttributes & atts )
{
if (localName == "Glyphs") {
QFontDatabase fontDB;
m_painter->save();
int fontId = m_page->loadFontByName( atts.value("FontUri") );
kDebug() << "Font families: " << QFontDatabase::applicationFontFamilies( fontId ).at(0) << endl;
QString fontFamily = fontDB.applicationFontFamilies( fontId ).at(0);
// TODO: this handling of font styles is broken
kDebug() << "Styles: " << fontDB.styles( fontFamily ) << endl;
kDebug() << "Families: " << fontDB.families() << endl;
QFont font = fontDB.font(fontFamily, QString("Normal"), 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() );
m_painter->setBrush(fillColor);
m_painter->drawText( origin, atts.value("UnicodeString") );
m_painter->restore();
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") {
// kDebug() << "Path: " << atts.value("Data") << ", " << atts.value("Fill") << endl;
if (! atts.value("Data").isEmpty() ) {
m_currentPath = abbreviatedDataToPath( atts.value("Data") );
}
if (! atts.value("Fill").isEmpty() ) {
QColor fillColor;
if ( atts.value("Fill").startsWith('#') ) {
fillColor = hexToRgba( atts.value("Fill").toLatin1() );
} else {
kDebug() << "Unknown / unhandled fill color representation:" << atts.value("Fill") << ":ende" << endl;
}
m_currentBrush = QBrush( fillColor );
}
} else if ( localName == "SolidColorBrush" ) {
if (! atts.value("Color").isEmpty() ) {
QColor fillColor;
if (atts.value("Color").startsWith('#') ) {
fillColor = hexToRgba( atts.value("Color").toLatin1() );
} 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 == "Path.Fill" ) {
// this doesn't have any attributes - just other elements that we handle elsewhere
} else {
kDebug() << "unknown start element: " << localName << endl;
}
return true;
}
bool XpsHandler::endElement( const QString &nameSpace,
const QString &localName,
const QString &qname)
{
if ( localName == "Path" ) {
m_painter->save();
m_painter->setBrush( m_currentBrush );
m_painter->setPen( m_currentPen );
m_painter->drawPath( m_currentPath );
if (! m_image.isNull() ) {
m_painter->drawImage( m_viewport, m_image, m_viewbox );
}
m_painter->restore();
m_currentPath = QPainterPath();
m_currentBrush = QBrush();
m_currentPen = QPen();
m_image = QImage();
}
return true;
}
XpsPage::XpsPage(KZip *archive, const QString &fileName): m_archive( archive ),
m_fileName( fileName ), m_pageIsRendered(false)
{
kDebug() << "page file name: " << fileName << endl;
@ -49,19 +272,71 @@ XpsPage::XpsPage(KZip *archive, const QString &fileName)
<< errLine << " : " << errCol << endl;
}
QDomNode node = m_dom.documentElement();
QDomElement element = node.toElement();
QDomElement element = m_dom.documentElement().toElement();
m_pageSize.setWidth( element.attribute("Width").toInt() );
m_pageSize.setHeight( element.attribute("Height").toInt() );
m_pagePixmap = new QPixmap( m_pageSize );
delete pageDevice;
}
bool XpsPage::renderToPixmap( QPixmap *p )
{
if (! m_pageIsRendered) {
XpsHandler *handler = new XpsHandler( m_pagePixmap, this );
QXmlSimpleReader *parser = new QXmlSimpleReader();
parser->setContentHandler( handler );
parser->setErrorHandler( handler );
QXmlInputSource *source = new QXmlInputSource();
source->setData( m_dom.toString() );
bool ok = parser->parse( source );
kDebug() << "Parse result: " << ok << endl;
delete source;
delete parser;
delete handler;
m_pageIsRendered = true;
}
if ( size() == p->size() )
*p = *m_pagePixmap;
else
*p = m_pagePixmap->scaled( p->size(), Qt::KeepAspectRatio );
return true;
}
QSize XpsPage::size() const
{
return m_pageSize;
}
int XpsPage::loadFontByName( const QString &fileName )
{
kDebug() << "font file name: " << fileName << endl;
KZipFileEntry* fontFile = static_cast<const KZipFileEntry *>(m_archive->directory()->entry( fileName ));
QByteArray fontData = fontFile->data(); // once per file, according to the docs
bool result = QFontDatabase::addApplicationFontFromData( fontData );
if (-1 == result) {
kDebug() << "Failed to load application font from data" << endl;
}
return result; // a font ID
}
QImage XpsPage::loadImageFromFile( const QString &fileName )
{
kDebug() << "image file name: " << fileName << endl;
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;
return image;
}
XpsDocument::XpsDocument(KZip *archive, const QString &fileName)
{
kDebug() << "document file name: " << fileName << endl;
@ -198,6 +473,10 @@ bool XpsFile::loadDocument(const QString &filename)
if( !e.isNull() ) {
if (e.tagName() == "DocumentReference") {
XpsDocument *doc = new XpsDocument( xpsArchive, e.attribute("Source") );
for (int lv = 0; lv < doc->numPages(); ++lv) {
// our own copy of the pages list
m_pages.append( doc->page( lv ) );
}
m_documents.append(doc);
} else {
kDebug() << "Unhandled entry in FixedDocumentSequence" << e.tagName() << endl;
@ -291,13 +570,7 @@ bool XpsFile::closeDocument()
int XpsFile::numPages() const
{
int pages = 0;
foreach( const XpsDocument *doc, m_documents ) {
pages += doc->numPages();
}
return pages;
return m_pages.size();
}
int XpsFile::numDocuments() const
@ -307,7 +580,12 @@ int XpsFile::numDocuments() const
XpsDocument* XpsFile::document(int documentNum) const
{
return m_documents.at(documentNum);
return m_documents.at( documentNum );
}
XpsPage* XpsFile::page(int pageNum) const
{
return m_pages.at( pageNum );
}
XpsGenerator::XpsGenerator( Okular::Document * document )
@ -351,13 +629,16 @@ bool XpsGenerator::closeDocument()
bool XpsGenerator::canGeneratePixmap( bool /*async*/ )
{
return false; // for now
return true;
}
void XpsGenerator::generatePixmap( Okular::PixmapRequest * request )
{
QPixmap * p = new QPixmap( request->width, request->height );
QSize size( (int)request->page->width(), (int)request->page->height() );
QPixmap * p = new QPixmap(size);
XpsPage *pageToRender = m_xpsFile->page( request->page->number() );
pageToRender->renderToPixmap( p );
request->page->setPixmap( request->id, p );
#if 0
if ( TIFFSetDirectory( d->tiff, request->page->number() ) )
{

View file

@ -21,9 +21,41 @@
#define _OKULAR_GENERATOR_XPS_H_
#include "core/generator.h"
#include <QXmlDefaultHandler>
#include <kzip.h>
class XpsPage;
class XpsHandler: public QXmlDefaultHandler
{
public:
XpsHandler( QPixmap *p, XpsPage *page );
~XpsHandler();
bool startElement( const QString & nameSpace,
const QString & localName,
const QString & qname,
const QXmlAttributes & atts );
bool endElement( const QString & nameSpace,
const QString & localName,
const QString & qname );
bool startDocument();
private:
XpsPage *m_page;
QPixmap *m_pixmap;
QPainter *m_painter;
QBrush m_currentBrush;
QPen m_currentPen;
QPainterPath m_currentPath;
QImage m_image;
QRectF m_viewbox;
QRectF m_viewport;
};
class XpsPage
{
public:
@ -31,8 +63,14 @@ public:
~XpsPage();
QSize size() const;
bool renderToPixmap( QPixmap *p );
QImage loadImageFromFile( const QString &filename );
int loadFontByName( const QString &fontName );
private:
KZip *m_archive;
const QString m_fileName;
QDomDocument m_dom;
QSize m_pageSize;
@ -42,6 +80,8 @@ private:
QImage m_thumbnail;
bool m_thumbnailIsLoaded;
QPixmap *m_pagePixmap;
bool m_pageIsRendered;
};
/**
@ -100,17 +140,28 @@ public:
*/
int numPages() const;
/**
a page from the file
\param pageNum the page number of the page to return
\note page numbers are zero based - they run from 0 to
numPages() - 1
*/
XpsPage* page(int pageNum) const;
/**
obtain a certain document from this file
\param documentNum the number of the document to return
\note page numbers are zero based - they run from 0 to
\note document numbers are zero based - they run from 0 to
numDocuments() - 1
*/
XpsDocument* document(int documentNum) const;
private:
QList<XpsDocument*> m_documents;
QList<XpsPage*> m_pages;
QString m_thumbnailFileName;
bool m_thumbnailMightBeAvailable;