/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
* This file is part of the libepubgen project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <cmath>
#include <memory>
#include <sstream>
#include <stack>
#include <string>
#include <boost/algorithm/string/replace.hpp>
#include "EPUBHTMLGenerator.h"
#include "EPUBImageManager.h"
#include "EPUBListStyleManager.h"
#include "EPUBParagraphStyleManager.h"
#include "EPUBPath.h"
#include "EPUBSpanStyleManager.h"
#include "EPUBTableStyleManager.h"
#include "libepubgen_utils.h"
namespace libepubgen
{
using librevenge::RVNGBinaryData;
using librevenge::RVNGPropertyList;
using librevenge::RVNGString;
using std::string;
namespace
{
class ZoneSinkImpl
{
public:
ZoneSinkImpl();
void openElement(const char *name, const librevenge::RVNGPropertyList &attributes);
void closeElement(const char *name);
void insertCharacters(const librevenge::RVNGString &characters);
void append(const ZoneSinkImpl &other);
const EPUBXMLSink &get() const;
EPUBXMLSink &get();
bool endsInLineBreak() const;
private:
EPUBXMLSink m_sink;
std::string m_lastCloseElement;
};
ZoneSinkImpl::ZoneSinkImpl()
: m_sink()
, m_lastCloseElement()
{
}
void ZoneSinkImpl::openElement(const char *const name, const librevenge::RVNGPropertyList &attributes)
{
m_sink.openElement(name, attributes);
m_lastCloseElement.clear();
}
void ZoneSinkImpl::closeElement(const char *const name)
{
m_sink.closeElement(name);
m_lastCloseElement = name;
}
void ZoneSinkImpl::insertCharacters(const librevenge::RVNGString &characters)
{
m_sink.insertCharacters(characters);
m_lastCloseElement.clear();
}
void ZoneSinkImpl::append(const ZoneSinkImpl &other)
{
m_sink.append(other.m_sink);
m_lastCloseElement = other.m_lastCloseElement;
}
const EPUBXMLSink &ZoneSinkImpl::get() const
{
return m_sink;
}
EPUBXMLSink &ZoneSinkImpl::get()
{
return m_sink;
}
bool ZoneSinkImpl::endsInLineBreak() const
{
return m_lastCloseElement == "p"
|| m_lastCloseElement == "ul"
|| m_lastCloseElement == "ol"
|| m_lastCloseElement == "br"
;
}
/// Convers inches to CSS pixels.
int inchToCSSPixel(const librevenge::RVNGProperty *property)
{
return round(property->getDouble() * 96);
}
}
namespace
{
struct TextZoneSink;
//! a zone to regroup footnote/endnote,... data
struct EPUBHTMLTextZone
{
friend struct TextZoneSink;
//! the different zone
enum Type { Z_Comment=0, Z_EndNote, Z_FootNote, Z_Main, Z_MetaData, Z_TextBox, Z_Unknown, Z_NumZones= Z_Unknown+1};
//! constructor for basic stream
EPUBHTMLTextZone(Type tp=Z_Unknown) : m_type(tp), m_actualId(0), m_zoneSinks(), m_version(20)
{
}
//! the type
Type type() const
{
return m_type;
}
//! the type
void setType(Type tp)
{
m_type=tp;
}
void setVersion(int version)
{
m_version = version;
}
int getVersion() const
{
return m_version;
}
//! returns a new sink corresponding to this zone
std::unique_ptr<TextZoneSink> getNewSink();
//! returns true if there is no data
bool isEmpty() const
{
for (const auto &zoneSink : m_zoneSinks)
if (!zoneSink.get().empty())
return false;
return true;
}
//! send the zone data
void send(EPUBXMLSink &out) const
{
if (isEmpty() || m_type==Z_Unknown || m_type==Z_Main)
return;
if (m_type!=Z_MetaData && m_version < 30)
{
out.openElement("hr", RVNGPropertyList());
out.closeElement("hr");
}
if (m_type==Z_MetaData)
{
for (const auto &zoneSink : m_zoneSinks)
out.append(zoneSink.get());
return;
}
if (m_type==Z_TextBox)
{
out.openElement("p", RVNGPropertyList());
out.openElement("b", RVNGPropertyList());
out.insertCharacters("TEXT BOXES");
out.closeElement("b");
out.closeElement("p");
for (const auto &zoneSink : m_zoneSinks)
{
out.append(zoneSink.get());
out.openElement("hr", RVNGPropertyList());
out.closeElement("hr");
}
return;
}
for (const auto &zoneSink : m_zoneSinks)
{
out.append(zoneSink.get());
// check if we need to add a return line
if (!zoneSink.endsInLineBreak())
{
out.openElement("br", RVNGPropertyList());
out.closeElement("br");
}
}
}
protected:
//! return a label corresponding to the zone
std::string label(int id) const;
//! the zone type
Type m_type;
//! the actual id
mutable int m_actualId;
//! the list of data string
std::vector<ZoneSinkImpl> m_zoneSinks;
int m_version;
private:
EPUBHTMLTextZone(EPUBHTMLTextZone const &orig);
EPUBHTMLTextZone operator=(EPUBHTMLTextZone const &orig);
};
struct TextZoneSink
{
//! constructor
TextZoneSink(EPUBHTMLTextZone *zone): m_zone(zone), m_zoneId(0), m_sink(), m_delayedLabel()
{
if (m_zone)
m_zoneId=m_zone->m_actualId++;
}
//! destructor
~TextZoneSink() { }
//! add a label called on main and a label in this ( delayed to allow openParagraph to be called )
//! @param closeAnchor determintes if the anchor on the main sink should be closed or not.
void addLabel(EPUBXMLSink &output, const librevenge::RVNGString &number, bool closeAnchor)
{
// Unique label, e.g. 'F1' for the first footnote.
std::string lbl=label();
// User-visible label, e.g. '1'.
std::string uiLabel = lbl;
if (!number.empty())
uiLabel = number.cstr();
if (!lbl.length())
return;
int version = 30;
if (m_zone)
version = m_zone->getVersion();
{
RVNGPropertyList supAttrs;
supAttrs.insert("id", ("called" + lbl).c_str());
if (closeAnchor)
output.openElement("sup", supAttrs);
RVNGPropertyList aAttrs;
if (version >= 30)
aAttrs.insert("epub:type", "noteref");
aAttrs.insert("href", ("#data" + lbl).c_str());
output.openElement("a", aAttrs);
if (closeAnchor)
{
output.insertCharacters(uiLabel.c_str());
output.closeElement("a");
output.closeElement("sup");
}
}
flush();
if (version >= 30)
{
RVNGPropertyList asideAttrs;
asideAttrs.insert("epub:type", "footnote");
asideAttrs.insert("id", ("data" + lbl).c_str());
m_sink.openElement("aside", asideAttrs);
}
RVNGPropertyList supAttrs;
if (version < 30)
supAttrs.insert("id", ("data" + lbl).c_str());
if (closeAnchor)
{
m_delayedLabel.openElement("sup", supAttrs);
RVNGPropertyList aAttrs;
aAttrs.insert("href", ("#called" + lbl).c_str());
m_delayedLabel.openElement("a", aAttrs);
m_delayedLabel.insertCharacters(uiLabel.c_str());
m_delayedLabel.closeElement("a");
m_delayedLabel.closeElement("sup");
}
}
//! flush delayed label, ...
void flush()
{
m_sink.append(m_delayedLabel);
m_delayedLabel = ZoneSinkImpl();
}
//! return the sink
EPUBXMLSink &getSink()
{
return m_sink.get();
}
//! send the data to the zone
void send()
{
if (!m_zone || m_zone->m_type==EPUBHTMLTextZone::Z_Main)
{
EPUBGEN_DEBUG_MSG(("TextZoneSink::send: must not be called\n"));
return;
}
flush();
if (m_zone->m_zoneSinks.size() <= size_t(m_zoneId))
m_zone->m_zoneSinks.resize(size_t(m_zoneId)+1);
m_zone->m_zoneSinks[size_t(m_zoneId)] = m_sink;
}
//! send the data to the zone
void sendMain(EPUBXMLSink &output)
{
flush();
output.append(m_sink.get());
}
protected:
//! return the zone label
std::string label() const
{
if (!m_zone || m_zone->m_type==EPUBHTMLTextZone::Z_Main)
{
EPUBGEN_DEBUG_MSG(("TextZoneSink::label: must not be called\n"));
return "";
}
return m_zone->label(m_zoneId);
}
//! a zone
EPUBHTMLTextZone *m_zone;
//! the zone id
int m_zoneId;
//! the sink
ZoneSinkImpl m_sink;
//! the label
ZoneSinkImpl m_delayedLabel;
private:
TextZoneSink(TextZoneSink const &orig);
TextZoneSink operator=(TextZoneSink const &orig);
};
std::unique_ptr<TextZoneSink> EPUBHTMLTextZone::getNewSink()
{
return make_unique<TextZoneSink>(this);
}
std::string EPUBHTMLTextZone::label(int id) const
{
char c=0;
switch (m_type)
{
case Z_Comment:
c='C';
break;
case Z_EndNote:
c='E';
break;
case Z_FootNote:
c='F';
break;
case Z_TextBox:
c='T';
break;
case Z_Main:
case Z_MetaData:
case Z_Unknown:
case Z_NumZones:
default:
break;
}
if (c==0)
return "";
std::stringstream s;
s << c << id+1;
return s.str();
}
}
//! the internal state of a html document generator
struct EPUBHTMLGeneratorImpl
{
//! constructor
EPUBHTMLGeneratorImpl(EPUBXMLSink &document, EPUBImageManager &imageManager, EPUBFontManager &fontManager, EPUBListStyleManager &listStyleManager, EPUBParagraphStyleManager ¶graphStyleManager, EPUBSpanStyleManager &spanStyleManager, EPUBTableStyleManager &tableStyleManager, const EPUBPath &path, const EPUBPath &stylesheetPath, EPUBStylesMethod stylesMethod, EPUBLayoutMethod layoutMethod, int version)
: m_document(document)
, m_imageManager(imageManager)
, m_fontManager(fontManager)
, m_listManager(listStyleManager)
, m_paragraphManager(paragraphStyleManager)
, m_spanManager(spanStyleManager)
, m_tableManager(tableStyleManager)
, m_path(path)
, m_stylesheetPath(stylesheetPath)
, m_actualPage(0)
, m_actualPageProperties()
, m_ignore(false)
, m_hasText(false)
, m_version(version)
, m_frameAnchorTypes()
, m_framePropertiesStack()
, m_linkPropertiesStack()
, m_stylesMethod(stylesMethod)
, m_layoutMethod(layoutMethod)
, m_actualSink()
, m_sinkStack()
{
for (int i = 0; i < EPUBHTMLTextZone::Z_NumZones; ++i)
{
m_zones[i].setType(EPUBHTMLTextZone::Type(i));
m_zones[i].setVersion(version);
}
m_actualSink=m_zones[EPUBHTMLTextZone::Z_Main].getNewSink();
}
//! destructor
~EPUBHTMLGeneratorImpl()
{
}
//! returns the actual output ( sending delayed data if needed)
EPUBXMLSink &output(bool sendDelayed=true)
{
if (sendDelayed)
m_actualSink->flush();
return m_actualSink->getSink();
}
//! returns the actual sink
TextZoneSink &getSink()
{
return *m_actualSink;
}
void push(EPUBHTMLTextZone::Type type)
{
m_sinkStack.push(std::move(m_actualSink));
if (type==EPUBHTMLTextZone::Z_Main || type==EPUBHTMLTextZone::Z_NumZones)
{
EPUBGEN_DEBUG_MSG(("EPUBHTMLGeneratorImpl::push: can not push the main zone\n"));
type=EPUBHTMLTextZone::Z_Unknown;
}
m_actualSink=m_zones[type].getNewSink();
}
void pop()
{
if (m_sinkStack.empty())
{
EPUBGEN_DEBUG_MSG(("EPUBHTMLGenerator::pop: can not pop sink\n"));
return;
}
if (m_actualSink)
{
m_actualSink->send();
m_actualSink.reset();
}
m_actualSink = std::move(m_sinkStack.top());
m_sinkStack.pop();
}
void sendMetaData(EPUBXMLSink &out)
{
m_zones[EPUBHTMLTextZone::Z_MetaData].send(out);
}
void flushUnsent(EPUBXMLSink &out)
{
if (!m_sinkStack.empty())
{
EPUBGEN_DEBUG_MSG(("EPUBHTMLGenerator::flushUnsent: the sink stack is not empty\n"));
while (!m_sinkStack.empty())
pop();
}
// first send the main data
if (!m_actualSink)
{
EPUBGEN_DEBUG_MSG(("EPUBHTMLGenerator::flushUnsent: can not find the main block\n"));
}
else
m_actualSink->sendMain(out);
m_zones[EPUBHTMLTextZone::Z_Comment].send(out);
m_zones[EPUBHTMLTextZone::Z_FootNote].send(out);
m_zones[EPUBHTMLTextZone::Z_EndNote].send(out);
m_zones[EPUBHTMLTextZone::Z_TextBox].send(out);
}
EPUBXMLSink &m_document;
EPUBImageManager &m_imageManager;
EPUBFontManager &m_fontManager;
EPUBListStyleManager &m_listManager;
EPUBParagraphStyleManager &m_paragraphManager;
EPUBSpanStyleManager &m_spanManager;
EPUBTableStyleManager &m_tableManager;
const EPUBPath m_path;
const EPUBPath m_stylesheetPath;
int m_actualPage;
RVNGPropertyList m_actualPageProperties;
bool m_ignore;
/// Does the currently opened paragraph have some text?
bool m_hasText;
int m_version;
std::stack<std::string> m_frameAnchorTypes;
std::stack<RVNGPropertyList> m_framePropertiesStack;
/// This is used for links which don't have a href.
std::stack<RVNGPropertyList> m_linkPropertiesStack;
EPUBStylesMethod m_stylesMethod;
EPUBLayoutMethod m_layoutMethod;
protected:
std::unique_ptr<TextZoneSink> m_actualSink;
std::stack<std::unique_ptr<TextZoneSink>> m_sinkStack;
EPUBHTMLTextZone m_zones[EPUBHTMLTextZone::Z_NumZones];
private:
EPUBHTMLGeneratorImpl(EPUBHTMLGeneratorImpl const &orig);
EPUBHTMLGeneratorImpl operator=(EPUBHTMLGeneratorImpl const &orig);
};
EPUBHTMLGenerator::EPUBHTMLGenerator(EPUBXMLSink &document, EPUBImageManager &imageManager, EPUBFontManager &fontManager, EPUBListStyleManager &listStyleManager, EPUBParagraphStyleManager ¶graphStyleManager, EPUBSpanStyleManager &spanStyleManager, EPUBTableStyleManager &tableStyleManager, const EPUBPath &path, const EPUBPath &stylesheetPath, EPUBStylesMethod stylesMethod, EPUBLayoutMethod layoutMethod, int version)
: m_impl(new EPUBHTMLGeneratorImpl(document, imageManager, fontManager, listStyleManager, paragraphStyleManager, spanStyleManager, tableStyleManager, path, stylesheetPath, stylesMethod, layoutMethod, version))
{
}
EPUBHTMLGenerator::~EPUBHTMLGenerator()
{
}
void EPUBHTMLGenerator::setDocumentMetaData(const RVNGPropertyList &propList)
{
m_impl->push(EPUBHTMLTextZone::Z_MetaData);
EPUBXMLSink &meta=m_impl->output();
static char const *wpdMetaFields[9]=
{
"meta:initial-creator", "dc:creator", "dc:subject", "dc:publisher", "meta:keywords",
"dc:language", "dc:description", "librevenge:descriptive-name", "librevenge:descriptive-type"
};
static char const *metaFields[9]=
{
"author", "typist", "subject", "publisher", "keywords",
"language", "abstract", "descriptive-name", "descriptive-type"
};
for (int i = 0; i < 9; i++)
{
if (!propList[wpdMetaFields[i]])
continue;
RVNGPropertyList attrs;
attrs.insert("name", metaFields[i]);
attrs.insert("content", propList[wpdMetaFields[i]]->getStr());
meta.openElement("meta", attrs);
meta.closeElement("meta");
}
meta.openElement("title", RVNGPropertyList());
if (propList["librevenge:descriptive-name"])
meta.insertCharacters(propList["librevenge:descriptive-name"]->getStr());
meta.closeElement("title");
m_impl->pop();
}
void EPUBHTMLGenerator::startDocument(const RVNGPropertyList &)
{
}
void EPUBHTMLGenerator::endDocument()
{
RVNGPropertyList htmlAttrs;
// TODO: set lang and xml:lang from metadata
htmlAttrs.insert("xmlns", "http://www.w3.org/1999/xhtml");
m_impl->m_document.openElement("html", htmlAttrs);
m_impl->m_document.openElement("head", RVNGPropertyList());
if (m_impl->m_version < 30)
{
m_impl->m_document.openElement("title", RVNGPropertyList());
m_impl->m_document.closeElement("title");
}
RVNGPropertyList metaAttrs;
metaAttrs.insert("http-equiv", "content-type");
metaAttrs.insert("content", "text/html; charset=UTF-8");
m_impl->m_document.openElement("meta", metaAttrs);
m_impl->m_document.closeElement("meta");
if (m_impl->m_version >= 30 && m_impl->m_layoutMethod == EPUB_LAYOUT_METHOD_FIXED)
{
metaAttrs.clear();
metaAttrs.insert("name", "viewport");
std::stringstream content;
if (const librevenge::RVNGProperty *pageWidth = m_impl->m_actualPageProperties["fo:page-width"])
{
content << "width=";
content << inchToCSSPixel(pageWidth);
}
if (const librevenge::RVNGProperty *pageHeight = m_impl->m_actualPageProperties["fo:page-height"])
{
content << ", height=";
content << inchToCSSPixel(pageHeight);
}
metaAttrs.insert("content", content.str().c_str());
m_impl->m_document.openElement("meta", metaAttrs);
m_impl->m_document.closeElement("meta");
}
if (m_impl->m_version < 30)
m_impl->sendMetaData(m_impl->m_document);
RVNGPropertyList linkAttrs;
linkAttrs.insert("href", m_impl->m_stylesheetPath.relativeTo(m_impl->m_path).str().c_str());
linkAttrs.insert("type", "text/css");
linkAttrs.insert("rel", "stylesheet");
m_impl->m_document.insertEmptyElement("link", linkAttrs);
m_impl->m_document.closeElement("head");
RVNGPropertyList bodyAttrs;
if (m_impl->m_version >= 30)
bodyAttrs.insert("xmlns:epub", "http://www.idpf.org/2007/ops");
m_impl->m_document.openElement("body", bodyAttrs);
m_impl->flushUnsent(m_impl->m_document);
m_impl->m_document.closeElement("body");
m_impl->m_document.closeElement("html");
}
void EPUBHTMLGenerator::defineEmbeddedFont(const RVNGPropertyList &propList)
{
m_impl->m_fontManager.insert(propList, m_impl->m_path);
}
void EPUBHTMLGenerator::openPageSpan(const RVNGPropertyList &propList)
{
m_impl->m_actualPage++;
librevenge::RVNGPropertyList::Iter i(propList);
for (i.rewind(); i.next();)
m_impl->m_actualPageProperties.insert(i.key(), i()->clone());
}
void EPUBHTMLGenerator::closePageSpan()
{
}
void EPUBHTMLGenerator::definePageStyle(const RVNGPropertyList &) {}
void EPUBHTMLGenerator::openHeader(const RVNGPropertyList & /* propList */)
{
m_impl->m_ignore = true;
}
void EPUBHTMLGenerator::closeHeader()
{
m_impl->m_ignore = false;
}
void EPUBHTMLGenerator::openFooter(const RVNGPropertyList & /* propList */)
{
m_impl->m_ignore = true;
}
void EPUBHTMLGenerator::closeFooter()
{
m_impl->m_ignore = false;
}
void EPUBHTMLGenerator::defineSectionStyle(const RVNGPropertyList &) {}
void EPUBHTMLGenerator::openSection(const RVNGPropertyList & /* propList */)
{
// Once output is produced here, EPUBTextGenerator::openSection() will need
// to call EPUBSplitGuard::openLevel().
}
void EPUBHTMLGenerator::closeSection()
{
// Once output is produced here, EPUBTextGenerator::closeSection() will need
// to call EPUBSplitGuard::closeLevel().
}
void EPUBHTMLGenerator::defineParagraphStyle(const RVNGPropertyList &propList)
{
m_impl->m_paragraphManager.defineParagraph(propList);
}
void EPUBHTMLGenerator::openParagraph(const RVNGPropertyList &propList)
{
if (m_impl->m_ignore)
return;
RVNGPropertyList attrs;
switch (m_impl->m_stylesMethod)
{
case EPUB_STYLES_METHOD_CSS:
attrs.insert("class", m_impl->m_paragraphManager.getClass(propList).c_str());
break;
case EPUB_STYLES_METHOD_INLINE:
attrs.insert("style", m_impl->m_paragraphManager.getStyle(propList).c_str());
break;
}
m_impl->output(false).openElement("p", attrs);
m_impl->m_hasText = false;
}
void EPUBHTMLGenerator::closeParagraph()
{
if (m_impl->m_ignore)
return;
if (!m_impl->m_hasText)
insertSpace();
m_impl->output().closeElement("p");
}
void EPUBHTMLGenerator::defineCharacterStyle(const RVNGPropertyList &propList)
{
m_impl->m_spanManager.defineSpan(propList);
}
void EPUBHTMLGenerator::openSpan(const RVNGPropertyList &propList)
{
if (m_impl->m_ignore)
return;
RVNGPropertyList attrs;
switch (m_impl->m_stylesMethod)
{
case EPUB_STYLES_METHOD_CSS:
attrs.insert("class", m_impl->m_spanManager.getClass(propList).c_str());
break;
case EPUB_STYLES_METHOD_INLINE:
attrs.insert("style", m_impl->m_spanManager.getStyle(propList).c_str());
break;
}
m_impl->output(false).openElement("span", attrs);
}
void EPUBHTMLGenerator::closeSpan()
{
if (m_impl->m_ignore)
return;
m_impl->output().closeElement("span");
}
void EPUBHTMLGenerator::openLink(const RVNGPropertyList &propList)
{
if (m_impl->m_ignore)
return;
if (!propList["xlink:type"])
{
EPUBGEN_DEBUG_MSG(("EPUBHTMLGenerator::openLink: xlink:type: not filled, suppose link\n"));
}
RVNGPropertyList attrs;
if (propList["xlink:href"])
{
std::string href(propList["xlink:href"]->getStr().cstr());
// Basic URL sanitization.
boost::replace_all(href, "http:///", "http://");
boost::replace_all(href, "https:///", "https://");
attrs.insert("href", href.c_str());
}
const librevenge::RVNGProperty *binaryDataProp = propList["office:binary-data"];
const librevenge::RVNGProperty *mimeTypeProp = propList["librevenge:mime-type"];
if (binaryDataProp && mimeTypeProp)
{
// This is not a real link, but more an additional image on top of an
// existing one, map it to footnotes instead.
RVNGPropertyList linkProperties;
linkProperties.insert("office:binary-data", binaryDataProp->clone());
linkProperties.insert("librevenge:mime-type", mimeTypeProp->clone());
m_impl->m_linkPropertiesStack.push(linkProperties);
}
else
m_impl->output(false).openElement("a", attrs);
}
void EPUBHTMLGenerator::closeLink()
{
if (m_impl->m_ignore)
return;
if (!m_impl->m_linkPropertiesStack.empty())
m_impl->m_linkPropertiesStack.pop();
else
m_impl->output().closeElement("a");
}
void EPUBHTMLGenerator::insertTab()
{
if (m_impl->m_ignore)
return;
// \t would not have a lot of effect since tabs in html are ignorable
// white-space. Write NBSPs and a breakable space instead.
for (int i = 0; i < 15; ++i)
m_impl->output().insertCharacters("\xc2\xa0");
m_impl->output().insertCharacters(" ");
m_impl->m_hasText = true;
}
void EPUBHTMLGenerator::insertLineBreak()
{
if (m_impl->m_ignore)
return;
m_impl->output().openElement("br", RVNGPropertyList());
m_impl->output().closeElement("br");
}
void EPUBHTMLGenerator::insertField(const RVNGPropertyList & /* propList */)
{
if (m_impl->m_ignore)
return;
m_impl->output().insertCharacters("#");
}
void EPUBHTMLGenerator::insertText(const RVNGString &text)
{
if (m_impl->m_ignore)
return;
EPUBXMLSink &sink = openPopup();
sink.insertCharacters(text);
closePopup(sink);
m_impl->m_hasText = true;
}
void EPUBHTMLGenerator::insertSpace()
{
if (m_impl->m_ignore)
return;
// NBSP.
m_impl->output().insertCharacters("\xc2\xa0");
}
void EPUBHTMLGenerator::openOrderedListLevel(const RVNGPropertyList &propList)
{
if (m_impl->m_ignore)
return;
m_impl->m_listManager.defineLevel(propList, true);
RVNGPropertyList attrs;
attrs.insert("class", m_impl->m_listManager.openLevel(propList, true).c_str());
// fixme: if level is > 1, we must first insert a div here
m_impl->output(false).openElement("ol", attrs);
}
void EPUBHTMLGenerator::closeOrderedListLevel()
{
if (m_impl->m_ignore)
return;
m_impl->m_listManager.closeLevel();
m_impl->output().closeElement("ol");
}
void EPUBHTMLGenerator::openUnorderedListLevel(const RVNGPropertyList &propList)
{
if (m_impl->m_ignore)
return;
m_impl->m_listManager.defineLevel(propList, false);
RVNGPropertyList attrs;
attrs.insert("class", m_impl->m_listManager.openLevel(propList, false).c_str());
// fixme: if level is > 1, we must first insert a div here
m_impl->output(false).openElement("ul", attrs);
}
void EPUBHTMLGenerator::closeUnorderedListLevel()
{
if (m_impl->m_ignore)
return;
m_impl->m_listManager.closeLevel();
m_impl->output().closeElement("ul");
}
void EPUBHTMLGenerator::openListElement(const RVNGPropertyList &propList)
{
if (m_impl->m_ignore)
return;
RVNGPropertyList attrs;
attrs.insert("class", m_impl->m_listManager.getClass(propList).c_str());
m_impl->output(false).openElement("li", attrs);
}
void EPUBHTMLGenerator::closeListElement()
{
if (m_impl->m_ignore)
return;
m_impl->output().closeElement("li");
}
void EPUBHTMLGenerator::openFootnote(const RVNGPropertyList &propList)
{
if (m_impl->m_ignore)
return;
EPUBXMLSink &output = m_impl->output();
m_impl->push(EPUBHTMLTextZone::Z_FootNote);
librevenge::RVNGString number;
if (const librevenge::RVNGProperty *numProp = propList["librevenge:number"])
number = numProp->getStr();
bool closeAnchor = m_impl->m_linkPropertiesStack.empty();
m_impl->getSink().addLabel(output, number, closeAnchor);
}
void EPUBHTMLGenerator::closeFootnote()
{
if (m_impl->m_ignore)
return;
if (m_impl->m_version >= 30)
m_impl->output().closeElement("aside");
m_impl->pop();
}
void EPUBHTMLGenerator::openEndnote(const RVNGPropertyList &)
{
if (m_impl->m_ignore)
return;
EPUBXMLSink &output = m_impl->output();
m_impl->push(EPUBHTMLTextZone::Z_EndNote);
m_impl->getSink().addLabel(output, librevenge::RVNGString(), true);
}
void EPUBHTMLGenerator::closeEndnote()
{
if (m_impl->m_ignore)
return;
m_impl->pop();
}
void EPUBHTMLGenerator::openComment(const RVNGPropertyList & /*propList*/)
{
if (m_impl->m_ignore)
return;
EPUBXMLSink &output = m_impl->output();
m_impl->push(EPUBHTMLTextZone::Z_Comment);
m_impl->getSink().addLabel(output, librevenge::RVNGString(), true);
}
void EPUBHTMLGenerator::closeComment()
{
if (m_impl->m_ignore)
return;
m_impl->pop();
}
void EPUBHTMLGenerator::openTextBox(const RVNGPropertyList & /*propList*/)
{
if (m_impl->m_ignore)
return;
RVNGPropertyList attrs;
if (!m_impl->m_framePropertiesStack.empty())
{
RVNGPropertyList &frameProperties = m_impl->m_framePropertiesStack.top();
switch (m_impl->m_stylesMethod)
{
case EPUB_STYLES_METHOD_CSS:
attrs.insert("class", m_impl->m_imageManager.getFrameClass(frameProperties).c_str());
break;
case EPUB_STYLES_METHOD_INLINE:
attrs.insert("style", m_impl->m_imageManager.getFrameStyle(frameProperties).c_str());
break;
}
}
m_impl->output().openElement("div", attrs);
}
void EPUBHTMLGenerator::closeTextBox()
{
if (m_impl->m_ignore)
return;
m_impl->output().closeElement("div");
if (!m_impl->m_framePropertiesStack.empty())
{
RVNGPropertyList &frameProperties = m_impl->m_framePropertiesStack.top();
RVNGString wrapStyle = m_impl->m_imageManager.getWrapStyle(frameProperties).c_str();
if (!wrapStyle.empty())
{
RVNGPropertyList attrs;
attrs.insert("style", wrapStyle);
m_impl->output().insertEmptyElement("br", attrs);
}
}
}
void EPUBHTMLGenerator::openTable(const RVNGPropertyList &propList)
{
if (m_impl->m_ignore)
return;
m_impl->m_tableManager.openTable(propList);
RVNGPropertyList attrs;
switch (m_impl->m_stylesMethod)
{
case EPUB_STYLES_METHOD_CSS:
attrs.insert("class", m_impl->m_tableManager.getTableClass(propList).c_str());
break;
case EPUB_STYLES_METHOD_INLINE:
attrs.insert("style", m_impl->m_tableManager.getTableStyle(propList).c_str());
break;
}
m_impl->output().openElement("table", attrs);
m_impl->output().openElement("tbody", RVNGPropertyList());
}
void EPUBHTMLGenerator::openTableRow(const RVNGPropertyList &propList)
{
if (m_impl->m_ignore)
return;
RVNGPropertyList attrs;
switch (m_impl->m_stylesMethod)
{
case EPUB_STYLES_METHOD_CSS:
attrs.insert("class", m_impl->m_tableManager.getRowClass(propList).c_str());
break;
case EPUB_STYLES_METHOD_INLINE:
attrs.insert("style", m_impl->m_tableManager.getRowStyle(propList).c_str());
break;
}
m_impl->output().openElement("tr", attrs);
}
void EPUBHTMLGenerator::closeTableRow()
{
if (m_impl->m_ignore)
return;
m_impl->output().closeElement("tr");
}
void EPUBHTMLGenerator::openTableCell(const RVNGPropertyList &propList)
{
if (m_impl->m_ignore)
return;
RVNGPropertyList attrs;
switch (m_impl->m_stylesMethod)
{
case EPUB_STYLES_METHOD_CSS:
attrs.insert("class", m_impl->m_tableManager.getCellClass(propList).c_str());
break;
case EPUB_STYLES_METHOD_INLINE:
attrs.insert("style", m_impl->m_tableManager.getCellStyle(propList).c_str());
break;
}
if (propList["table:number-columns-spanned"])
attrs.insert("colspan", propList["table:number-columns-spanned"]->getInt());
if (propList["table:number-rows-spanned"])
attrs.insert("rowspan", propList["table:number-rows-spanned"]->getInt());
m_impl->output().openElement("td", attrs);
}
void EPUBHTMLGenerator::closeTableCell()
{
if (m_impl->m_ignore)
return;
m_impl->output().closeElement("td");
}
void EPUBHTMLGenerator::insertCoveredTableCell(const RVNGPropertyList & /* propList */) {}
void EPUBHTMLGenerator::closeTable()
{
if (m_impl->m_ignore)
return;
m_impl->output().closeElement("tbody");
m_impl->output().closeElement("table");
m_impl->m_tableManager.closeTable();
}
void EPUBHTMLGenerator::openFrame(const RVNGPropertyList &propList)
{
librevenge::RVNGPropertyList::Iter i(propList);
std::string anchorType;
RVNGPropertyList frameProperties;
for (i.rewind(); i.next();)
{
if (std::string("text:anchor-type") == i.key())
anchorType = i()->getStr().cstr();
// Remember the property for binary object purposes.
frameProperties.insert(i.key(), i()->clone());
}
if (anchorType == "page")
// Other anchor types are already inside a paragraph.
m_impl->output().openElement("p", RVNGPropertyList());
m_impl->m_frameAnchorTypes.push(anchorType);
m_impl->m_framePropertiesStack.push(frameProperties);
}
void EPUBHTMLGenerator::closeFrame()
{
if (!m_impl->m_framePropertiesStack.empty())
m_impl->m_framePropertiesStack.pop();
if (m_impl->m_frameAnchorTypes.empty())
return;
if (m_impl->m_frameAnchorTypes.top() == "page")
m_impl->output().closeElement("p");
m_impl->m_frameAnchorTypes.pop();
}
void EPUBHTMLGenerator::openGroup(const librevenge::RVNGPropertyList & /* propList */) {}
void EPUBHTMLGenerator::closeGroup() {}
void EPUBHTMLGenerator::defineGraphicStyle(const librevenge::RVNGPropertyList & /* propList */) {}
void EPUBHTMLGenerator::drawRectangle(const librevenge::RVNGPropertyList & /* propList */) {}
void EPUBHTMLGenerator::drawEllipse(const librevenge::RVNGPropertyList & /* propList */) {}
void EPUBHTMLGenerator::drawPolygon(const librevenge::RVNGPropertyList & /* propList */) {}
void EPUBHTMLGenerator::drawPolyline(const librevenge::RVNGPropertyList & /* propList */) {}
void EPUBHTMLGenerator::drawPath(const librevenge::RVNGPropertyList & /* propList */) {}
void EPUBHTMLGenerator::drawConnector(const librevenge::RVNGPropertyList & /* propList */) {}
void EPUBHTMLGenerator::insertBinaryObject(const RVNGPropertyList &propList)
{
const EPUBPath &path = m_impl->m_imageManager.insert(
RVNGBinaryData(propList["office:binary-data"]->getStr()),
propList["librevenge:mime-type"]->getStr());
RVNGPropertyList attrs;
RVNGString wrapStyle;
if (!m_impl->m_framePropertiesStack.empty())
{
RVNGPropertyList &frameProperties = m_impl->m_framePropertiesStack.top();
switch (m_impl->m_stylesMethod)
{
case EPUB_STYLES_METHOD_CSS:
attrs.insert("class", m_impl->m_imageManager.getFrameClass(frameProperties).c_str());
break;
case EPUB_STYLES_METHOD_INLINE:
attrs.insert("style", m_impl->m_imageManager.getFrameStyle(frameProperties).c_str());
break;
}
wrapStyle = m_impl->m_imageManager.getWrapStyle(frameProperties).c_str();
}
attrs.insert("src", path.relativeTo(m_impl->m_path).str().c_str());
// FIXME: use alternative repr. if available
attrs.insert("alt", path.str().c_str());
EPUBXMLSink &sink = openPopup();
sink.insertEmptyElement("img", attrs);
closePopup(sink);
if (!wrapStyle.empty())
{
attrs.clear();
attrs.insert("style", wrapStyle);
m_impl->output().insertEmptyElement("br", attrs);
}
}
EPUBXMLSink &EPUBHTMLGenerator::openPopup()
{
if (!m_impl->m_linkPropertiesStack.empty())
{
// Save the main sink, as m_impl->output() will point to the footnote sink.
libepubgen::EPUBXMLSink &main = m_impl->output();
openFootnote(RVNGPropertyList());
return main;
}
else
return m_impl->output();
}
void EPUBHTMLGenerator::closePopup(EPUBXMLSink &main)
{
if (!m_impl->m_linkPropertiesStack.empty())
{
RVNGPropertyList &linkProperties = m_impl->m_linkPropertiesStack.top();
main.closeElement("a");
const EPUBPath &linkPath = m_impl->m_imageManager.insert(
RVNGBinaryData(linkProperties["office:binary-data"]->getStr()),
linkProperties["librevenge:mime-type"]->getStr());
RVNGPropertyList linkAttrs;
linkAttrs.insert("src", linkPath.relativeTo(m_impl->m_path).str().c_str());
linkAttrs.insert("alt", linkPath.str().c_str());
m_impl->output().insertEmptyElement("img", linkAttrs);
closeFootnote();
}
}
void EPUBHTMLGenerator::insertEquation(const RVNGPropertyList & /* propList */) {}
void EPUBHTMLGenerator::getPageProperties(librevenge::RVNGPropertyList &propList) const
{
propList.clear();
librevenge::RVNGPropertyList::Iter i(m_impl->m_actualPageProperties);
for (i.rewind(); i.next();)
propList.insert(i.key(), i()->clone());
}
void EPUBHTMLGenerator::setPageProperties(const librevenge::RVNGPropertyList &propList)
{
m_impl->m_actualPageProperties.clear();
librevenge::RVNGPropertyList::Iter i(propList);
for (i.rewind(); i.next();)
m_impl->m_actualPageProperties.insert(i.key(), i()->clone());
}
}
/* vim:set shiftwidth=2 softtabstop=2 expandtab: */