Commit 26b3defb authored by Victor Yacovlev's avatar Victor Yacovlev

DocBookViewer: Implemented subset of MathML

parent 0fdfd6d5
......@@ -42,6 +42,7 @@ set(SOURCES
printdialog.cpp
printrenderer.cpp
sidepanel.cpp
mathmlrenderer.cpp
)
set(FORMS
......
#include "contentview.h"
#include "docbookmodel.h"
#include "mathmlrenderer.h"
#include <QtCore>
#include <QtGui>
......@@ -60,6 +61,39 @@ QString ContentView::renderChapter(ModelPtr data) const
return result;
}
QString ContentView::renderAbstract(ModelPtr data) const
{
QString result;
result += "<div class='abstract'>";
foreach (ModelPtr child, data->children()) {
result += renderElement(child);
}
result += "</div>";
return result;
}
QString ContentView::renderArticle(ModelPtr data) const
{
QString result;
result += "<h1 align='center'>" + normalizeText(data->title()) + "</h1>\n";
ModelPtr abstract;
foreach (ModelPtr child, data->children()) {
if (child == DocBookModel::Abstract) {
abstract = child;
break;
}
}
if (abstract) {
result += renderAbstract(abstract);
}
result += "<hr/>";
foreach (ModelPtr child, data->children()) {
result += renderElement(child);
}
return result;
}
QString ContentView::wrapHTML(const QString &body) const
{
return QString() +
......@@ -70,8 +104,9 @@ QString ContentView::wrapHTML(const QString &body) const
" font-weight: bold;"
" text-decoration: none;"
"}"
"p {"
" text-indent: 30;"
".abstract {"
" margin: 30;"
" font-style: italic;"
"}"
"body {"
" font-family: " + MainFontFamily + ";"
......@@ -152,6 +187,9 @@ QString ContentView::renderElement(ModelPtr data) const
else if (data == DocBookModel::Chapter) {
return renderChapter(data);
}
else if (data == DocBookModel::Article) {
return renderArticle(data);
}
else if (data == DocBookModel::Section) {
return renderSection(data);
}
......@@ -197,6 +235,12 @@ QString ContentView::renderElement(ModelPtr data) const
else if (data == DocBookModel::Superscript) {
return renderSuperscript(data);
}
else if (data == DocBookModel::MediaObject) {
return renderMediaObject(data);
}
else if (data == DocBookModel::Caption) {
return renderCaption(data);
}
else if (data == DocBookModel::InlineMediaObject) {
return renderInlineMediaObject(data);
}
......@@ -212,6 +256,12 @@ QString ContentView::renderElement(ModelPtr data) const
else if (data == DocBookModel::Parameter) {
return renderParameter(data);
}
else if (data == DocBookModel::Type) {
return renderType(data);
}
else if (data == DocBookModel::MathML_Math) {
return renderMathML(data);
}
else if (data == DocBookModel::ListOfExamples) {
return renderListOfExamples(data);
}
......@@ -574,9 +624,10 @@ QString ContentView::renderItemContextLink(ModelPtr data) const
const QString & contextTitle = sectionNumber(context) + "&nbsp;" +
context->title();
const QString contextLink = "model_ptr:" + modelToLink(context);
result += "<p><b>" + tr("Context:") + "</b> ";
result += "<a href='" + contextLink + "'>" +
contextTitle + "</a></p>";
const QString contextAHref = "<a href='" + contextLink + "'>" +
contextTitle + "</a>";
result += "<p align='left'>"+tr("See %1 for more details.")
.arg(contextAHref)+"</p>";
}
return result;
}
......@@ -639,12 +690,33 @@ QString ContentView::renderFuncSynopsys(ModelPtr data) const
"<span style='font-weight:normal;'>" +
normalizeText(data->title()) + "</span>" +
"</h2>\n";
bool hasMoreThanText = false;
if (data->parent()) {
foreach (ModelPtr child, data->parent()->children()) {
if (child != DocBookModel::Para &&
child != DocBookModel::FuncSynopsys)
{
hasMoreThanText = true;
break;
}
}
}
if (hasMoreThanText) {
result += renderItemContextLink(data);
}
if (info)
result += renderFuncSynopsysInfo(info);
}
if (prototype) {
result += "<table border='0' width='100%'><tr><td>";
result += "<tr><td height='10'>&nbsp;</td></tr>\n";
result += "<br/>";
// result += "<tr><td height='1'>&nbsp;</td></tr>\n";
result += "<b>" + tr("Synopsis:") + "</b>";
result += "</td></tr><tr><td>";
result += "<table border='1' bordercolor='gray' cellspacing='0' cellpadding='10' width='100%'>";
......@@ -686,6 +758,16 @@ QString ContentView::renderParameter(ModelPtr data) const
return result;
}
QString ContentView::renderType(ModelPtr data) const
{
QString result;
result += "<span class='code'><b>" + renderChilds(data) + "</b></span>";
wrapInlineElement(data, result, true, true);
if (!result.endsWith(" ") && data->parent() == DocBookModel::FuncDef)
result += " ";
return result;
}
QString ContentView::renderFuncSynopsysInfo(ModelPtr data) const
{
QString result;
......@@ -849,6 +931,37 @@ QString ContentView::renderParagraph(ModelPtr data) const
return result;
}
QString ContentView::renderMediaObject(ModelPtr data) const
{
QString result;
ModelPtr mediaObject = findImageData(data);
ModelPtr caption;
foreach (ModelPtr child, data->children()) {
if (child == DocBookModel::Caption) {
caption = child;
break;
}
}
if (mediaObject) {
result += "<div align='center' width='100%' padding='10'>" +
renderElement(mediaObject);
if (caption) {
result += renderCaption(caption);
}
result += "</div>\n";
}
return result;
}
QString ContentView::renderCaption(ModelPtr data) const
{
QString result;
result += "<div align='center' width='100%'>";
result += renderChilds(data);
result += "</div>";
return result;
}
QString ContentView::renderInlineMediaObject(ModelPtr data) const
{
QString result;
......@@ -873,6 +986,29 @@ QString ContentView::renderImageObject(ModelPtr data) const
return result;
}
QString ContentView::renderMathML(ModelPtr data) const
{
QString result;
QList<ModelPtr> rows;
foreach (ModelPtr child, data->children()) {
if (child == DocBookModel::MathML_MRow) {
rows << child;
}
}
if (rows.size() > 1) {
foreach (ModelPtr row, rows) {
result += "<p align='center'>";
result += "<img src='model_ptr:"+modelToLink(row)+"'>";
result += "</p>";
}
}
else if (rows.size() == 1) {
result += "<img src='model_ptr:"+modelToLink(rows[0])+"'>";
wrapInlineElement(data, result, true, true);
}
return result;
}
QString ContentView::renderListOfExamples(ModelPtr data) const
{
QString result;
......@@ -916,6 +1052,11 @@ QVariant ContentView::loadResource(int type, const QUrl &name)
const QImage & image = model->imageData();
result = image;
}
else if (model->modelType() == DocBookModel::MathML_MRow) {
MathMLRenderer::self()->render(model->self());
const QImage & image = model->imageData();
result = image;
}
}
}
}
......
......@@ -32,6 +32,8 @@ private:
QString renderElement(ModelPtr data) const;
QString renderChapter(ModelPtr data) const;
QString renderArticle(ModelPtr data) const;
QString renderAbstract(ModelPtr data) const;
QString renderTOC(ModelPtr data) const;
QString renderTOCElement(ModelPtr data, quint8 level, bool enumerate) const;
QString renderPlainPage(ModelPtr data) const;
......@@ -63,6 +65,8 @@ private:
QString renderRow(ModelPtr data) const;
QString renderEntry(ModelPtr data) const;
QString renderCaption(ModelPtr data) const;
QString renderMediaObject(ModelPtr data) const;
QString renderInlineMediaObject(ModelPtr data) const;
QString renderImageObject(ModelPtr data) const;
......@@ -73,11 +77,14 @@ private:
QString renderFunction(ModelPtr data) const;
QString renderParamDef(ModelPtr data) const;
QString renderParameter(ModelPtr data) const;
QString renderType(ModelPtr data) const;
QString renderListOfExamples(ModelPtr data) const;
QString renderListOfFunctions(ModelPtr data) const;
QString renderListOfTables(ModelPtr data) const;
QString renderMathML(ModelPtr data) const;
bool isPlainPage(ModelPtr data) const;
ModelPtr topLevelModel(ModelPtr data) const;
......
......@@ -113,6 +113,8 @@ bool DocBookFactory::startElement(
const QXmlAttributes &atts)
{
const QString element = localName.toLower();
static const QRegExp XInclude("http://www.w3.org/\\d+/XInclude");
static const QRegExp MathML("http://www.w3.org/\\d+/Math/MathML");
DocBookModel * model = 0;
if (element == "article") {
model = new DocBookModel(root_, DocBookModel::Article);
......@@ -184,6 +186,12 @@ bool DocBookFactory::startElement(
else if (element == "entry") {
model = new DocBookModel(root_, DocBookModel::Entry);
}
else if (element == "mediaobject") {
model = new DocBookModel(root_, DocBookModel::MediaObject);
}
else if (element == "caption") {
model = new DocBookModel(root_, DocBookModel::Caption);
}
else if (element == "inlinemediaobject") {
model = new DocBookModel(root_, DocBookModel::InlineMediaObject);
}
......@@ -220,11 +228,77 @@ bool DocBookFactory::startElement(
else if (element == "parameter") {
model = new DocBookModel(root_, DocBookModel::Parameter);
}
else if (element == "type") {
model = new DocBookModel(root_, DocBookModel::Type);
}
else if (element == "package") {
model = new DocBookModel(root_, DocBookModel::Package);
}
else if (element == "xref") {
model = new DocBookModel(root_, DocBookModel::Xref);
model->xrefLinkEnd_ = atts.value("linkend");
model->xrefEndTerm_ = atts.value("endterm");
}
else if (element == "title" || element == "subtitle") {
buffer_.clear();
}
else if (element == "include" && XInclude.indexIn(namespaceURI)!=-1) {
const QString href = atts.value("href");
if (href.length() > 0) {
const QUrl hrefUrl = url_.resolved(href);
// TODO network url support
const QString fileName = hrefUrl.toLocalFile();
QFile file(fileName);
if (file.open(QIODevice::ReadOnly)) {
DocBookFactory* innerFactory = new DocBookFactory();
QString localError;
ModelPtr include =
innerFactory->parseDocument(&file, hrefUrl, &localError);
if (include) {
if (root_) {
include->parent_ = root_;
root_->children_.append(include);
}
else {
root_ = include;
}
}
file.close();
delete innerFactory;
}
}
}
else if (element == "math" && MathML.indexIn(namespaceURI) != -1) {
model = new DocBookModel(root_, DocBookModel::MathML_Math);
}
else if (element == "mrow" && MathML.indexIn(namespaceURI) != -1) {
model = new DocBookModel(root_, DocBookModel::MathML_MRow);
}
else if (element == "msqrt" && MathML.indexIn(namespaceURI) != -1) {
model = new DocBookModel(root_, DocBookModel::MathML_MSqrt);
}
else if (element == "mfrac" && MathML.indexIn(namespaceURI) != -1) {
model = new DocBookModel(root_, DocBookModel::MathML_MFrac);
}
else if (element == "mi" && MathML.indexIn(namespaceURI) != -1) {
model = new DocBookModel(root_, DocBookModel::MathML_MI);
}
else if (element == "mn" && MathML.indexIn(namespaceURI) != -1) {
model = new DocBookModel(root_, DocBookModel::MathML_MN);
}
else if (element == "mo" && MathML.indexIn(namespaceURI) != -1) {
model = new DocBookModel(root_, DocBookModel::MathML_MO);
}
else if (element == "mtext" && MathML.indexIn(namespaceURI) != -1) {
model = new DocBookModel(root_, DocBookModel::MathML_MText);
}
else if (element == "msup" && MathML.indexIn(namespaceURI) != -1) {
model = new DocBookModel(root_, DocBookModel::MathML_MSup);
}
else {
model = new DocBookModel(root_, DocBookModel::Unknown);
buffer_.clear();
}
if (model) {
if (root_ && buffer_.length() > 0) {
DocBookModel* text = new DocBookModel(root_, DocBookModel::Text);
......@@ -259,35 +333,6 @@ bool DocBookFactory::startElement(
}
root_ = ModelPtr(model);
}
else if (element == "include") {
const QString href = atts.value("href");
if (href.length() > 0) {
const QUrl hrefUrl = url_.resolved(href);
// TODO network url support
const QString fileName = hrefUrl.toLocalFile();
QFile file(fileName);
if (file.open(QIODevice::ReadOnly)) {
DocBookFactory* innerFactory = new DocBookFactory();
QString localError;
ModelPtr include =
innerFactory->parseDocument(&file, hrefUrl, &localError);
if (include) {
if (root_) {
include->parent_ = root_;
root_->children_.append(include);
}
else {
root_ = include;
}
}
file.close();
delete innerFactory;
}
}
}
else {
buffer_.clear();
}
return true;
}
......@@ -333,6 +378,15 @@ bool DocBookFactory::skippedEntity(const QString &name)
// See: http://www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent
buffer_.push_back(QChar(0x2026)); // three dots at bottom
}
else if (name == "alpha") {
buffer_.push_back(QChar(0x03B1));
}
else if (name == "beta") {
buffer_.push_back(QChar(0x03B2));
}
else if (name == "gamma") {
buffer_.push_back(QChar(0x03B3));
}
return true;
}
......@@ -340,20 +394,8 @@ bool DocBookFactory::endElement(const QString &namespaceURI,
const QString &localName,
const QString &qName)
{
static const QStringList tagsToClose = QStringList()
<< "book" << "article" << "set"
<< "chapter" << "section" << "para"
<< "listitem" << "itemizedlist" << "orderedlist"
<< "example" << "programlisting" << "code"
<< "preface" << "abstract" << "reference"
<< "informaltable" << "table" << "thead" << "tbody" << "row" << "entry"
<< "inlinemediaobject" << "imageobject" << "imagedata"
<< "subscript" << "superscript"
<< "funcsynopsis" << "funcsynopsisinfo" << "package"
<< "funcprototype" << "funcdef" << "function"
<< "paramdef" << "parameter"
<< "emphasis" << "xref" << "keycombo" << "keysym";
const QString element = localName.toLower();
static const QRegExp XInclude("http://www.w3.org/\\d+/XInclude");
if (root_ && element == "title") {
root_->title_ = buffer_;
buffer_.clear();
......@@ -362,7 +404,10 @@ bool DocBookFactory::endElement(const QString &namespaceURI,
root_->subtitle_ = buffer_;
buffer_.clear();
}
else if (root_ && tagsToClose.contains(element)) {
else if (element == "include" && XInclude.indexIn(namespaceURI)!=-1) {
// do nothing here
}
else if (root_) {
if (root_->title().isEmpty()) {
if (root_ == DocBookModel::Example || root_ == DocBookModel::FuncSynopsys) {
ModelPtr parent = root_->parent();
......
......@@ -85,6 +85,26 @@ const QList<ModelPtr>& DocBookModel::children() const
return children_;
}
ModelPtr DocBookModel::self() const
{
ModelPtr result;
if (parent()) {
foreach (ModelPtr child, parent()->children()) {
if (child.data() == this) {
result = child;
break;
}
}
}
if (!result) foreach (ModelPtr child, children()) {
if (child->parent().data() == this) {
result = child->parent();
break;
}
}
return result;
}
bool DocBookModel::isSectioningNode() const
{
if (title_.length() == 0) {
......@@ -127,6 +147,7 @@ const QImage& DocBookModel::imageData() const
if (svgRenderer_ && cachedImage_.isNull()) {
const QSize size = svgRenderer_->defaultSize();
QImage img(size, QImage::Format_ARGB32);
img.fill(0);
QPainter painter(&img);
svgRenderer_->render(&painter);
cachedImage_ = img;
......
......@@ -20,8 +20,11 @@ typedef QSharedPointer<QSvgRenderer> SvgRendererPtr;
class DocBookModel
{
friend class DocBookFactory;
friend class MathMLRenderer;
public:
enum ModelType {
Unknown,
Text,
Book,
Article,
......@@ -52,6 +55,8 @@ public:
TBody,
Row,
Entry,
MediaObject,
Caption,
InlineMediaObject,
ImageObject,
ImageData,
......@@ -66,6 +71,17 @@ public:
Function,
Parameter,
Package,
Type,
MathML_Math,
MathML_MRow,
MathML_MSqrt,
MathML_MFrac,
MathML_MI,
MathML_MN,
MathML_MO,
MathML_MText,
MathML_MSup,
// virtual entries
ListOfExamples,
......@@ -91,6 +107,8 @@ public:
const QImage& imageData() const;
ModelPtr self() const;
protected /*methods*/:
explicit DocBookModel(ModelPtr parent, const ModelType modelType);
......
#include "mathmlrenderer.h"
#include <QtCore>
#include <QtGui>
namespace DocBookViewer {
static const qreal BASE_FONT_SIZE = 14.0; /* pt */
static const qreal MIN_FONT_SIZE = 9.0; /* pt */
static const int GRAPHICS_SPACING = 1; /* px */
static const int REGULAR_LINE_WIDTH = 2; /* px */
static const int FRAC_LINE_WIDTH = 1; /* px */
MathMLRenderer* MathMLRenderer::self()
{
static MathMLRenderer * instance = new MathMLRenderer();
return instance;
}
const QImage& MathMLRenderer::render(ModelPtr data)
{
if (data->cachedImage_.isNull()) {
font_ = mathFont(BASE_FONT_SIZE);
data->cachedImage_ = renderBlock(data);
}
return data->cachedImage_;
}
QImage MathMLRenderer::renderBlock(ModelPtr block)
{
QList<QImage> blockImages;
foreach (ModelPtr child, block->children()) {
QImage blockImage = renderElement(child);
if (!blockImage.isNull()) {
blockImages.append(blockImage);
}
}
int height = 0;
int width = 0;
foreach (const QImage & blockImage, blockImages) {
height = qMax(height, blockImage.height());
width += blockImage.width();
}
QImage result;
if (height > 0 && width > 0) {
result = QImage(width, height, QImage::Format_ARGB32);
result.fill(0);
QPainter painter(&result);
int x = 0;
foreach (const QImage & blockImage, blockImages) {
int y = (height - blockImage.height()) / 2;
painter.drawImage(x, y, blockImage);
x += blockImage.width();
}
painter.end();
}
return result;
}
QImage MathMLRenderer::renderElement(ModelPtr element)
{
if (element == DocBookModel::Text) {
return