mirror of
https://invent.kde.org/graphics/okular
synced 2024-08-27 03:30:20 +00:00
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:
parent
62e29bcc8f
commit
4b68351612
|
@ -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() ) )
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue