EPUB export: support image popup for images and text

Handle relative links on image / text as data for a popup. Pick the images up
from <base directory>/<base name>/<relative url> as a start.

Change-Id: I9b6183d554e3792aa71dfffc19a671a0e4c302cc
Reviewed-on: https://gerrit.libreoffice.org/45601
Tested-by: Jenkins <ci@libreoffice.org>
Reviewed-by: Miklos Vajna <vmiklos@collabora.co.uk>
This commit is contained in:
Miklos Vajna 2017-11-30 16:33:03 +01:00
parent 553a35bed7
commit 7dcb4fef3b
8 changed files with 401 additions and 2 deletions

View file

@ -5053,3 +5053,304 @@ index 96e7623..6b4c7c2 100644
--
2.13.6
From bc80bf98172e8e0f8c803dec0b8e74e233ac482e Mon Sep 17 00:00:00 2001
From: Miklos Vajna <vmiklos@collabora.co.uk>
Date: Fri, 24 Nov 2017 16:28:58 +0100
Subject: [PATCH 1/2] EPUBHTMLGenerator: support image popup for images
If the user of the lib uses the librevenge:mime-type and
office:binary-data keys (instead of xlink:href) for a link around an
image, then open that image in a popup (using the footnote markup).
The usual assumption is that the footnote anchor is trivial (just a
string) and the content is complex, but here the situation is the
opposite: the anchor can be something complex, OTOH the footnote content
is always a single inline image -- this requires the new
closeAnchor mode when adding the footnote label.
---
src/lib/EPUBHTMLGenerator.cpp | 79 ++++++++++++++++++++++++++++++--------
src/test/EPUBTextGeneratorTest.cpp | 34 ++++++++++++++++
2 files changed, 96 insertions(+), 17 deletions(-)
diff --git a/src/lib/EPUBHTMLGenerator.cpp b/src/lib/EPUBHTMLGenerator.cpp
index 6b4c7c2..75b0866 100644
--- a/src/lib/EPUBHTMLGenerator.cpp
+++ b/src/lib/EPUBHTMLGenerator.cpp
@@ -222,7 +222,8 @@ struct TextZoneSink
//! destructor
~TextZoneSink() { }
//! add a label called on main and a label in this ( delayed to allow openParagraph to be called )
- void addLabel(EPUBXMLSink &output, const librevenge::RVNGString &number)
+ //! @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();
@@ -244,9 +245,12 @@ struct TextZoneSink
aAttrs.insert("epub:type", "noteref");
aAttrs.insert("href", ("#data" + lbl).c_str());
output.openElement("a", aAttrs);
- output.insertCharacters(uiLabel.c_str());
- output.closeElement("a");
- output.closeElement("sup");
+ if (closeAnchor)
+ {
+ output.insertCharacters(uiLabel.c_str());
+ output.closeElement("a");
+ output.closeElement("sup");
+ }
}
flush();
if (version == 30)
@@ -259,13 +263,16 @@ struct TextZoneSink
RVNGPropertyList supAttrs;
if (version < 30)
supAttrs.insert("id", ("data" + lbl).c_str());
- 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");
+ 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()
@@ -379,6 +386,7 @@ struct EPUBHTMLGeneratorImpl
, m_version(version)
, m_frameAnchorTypes()
, m_framePropertiesStack()
+ , m_linkPropertiesStack()
, m_stylesMethod(stylesMethod)
, m_actualSink()
, m_sinkStack()
@@ -475,6 +483,8 @@ struct EPUBHTMLGeneratorImpl
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;
@@ -702,14 +712,29 @@ void EPUBHTMLGenerator::openLink(const RVNGPropertyList &propList)
attrs.insert("href", href.c_str());
}
- m_impl->output(false).openElement("a", attrs);
+ 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;
- m_impl->output().closeElement("a");
+ if (!m_impl->m_linkPropertiesStack.empty())
+ m_impl->m_linkPropertiesStack.pop();
+ else
+ m_impl->output().closeElement("a");
}
void EPUBHTMLGenerator::insertTab()
@@ -820,7 +845,8 @@ void EPUBHTMLGenerator::openFootnote(const RVNGPropertyList &propList)
librevenge::RVNGString number;
if (const librevenge::RVNGProperty *numProp = propList["librevenge:number"])
number = numProp->getStr();
- m_impl->getSink().addLabel(output, number);
+ bool closeAnchor = m_impl->m_linkPropertiesStack.empty();
+ m_impl->getSink().addLabel(output, number, closeAnchor);
}
void EPUBHTMLGenerator::closeFootnote()
@@ -838,7 +864,7 @@ void EPUBHTMLGenerator::openEndnote(const RVNGPropertyList &)
return;
EPUBXMLSink &output = m_impl->output();
m_impl->push(EPUBHTMLTextZone::Z_EndNote);
- m_impl->getSink().addLabel(output, librevenge::RVNGString());
+ m_impl->getSink().addLabel(output, librevenge::RVNGString(), true);
}
void EPUBHTMLGenerator::closeEndnote()
@@ -854,7 +880,7 @@ void EPUBHTMLGenerator::openComment(const RVNGPropertyList & /*propList*/)
return;
EPUBXMLSink &output = m_impl->output();
m_impl->push(EPUBHTMLTextZone::Z_Comment);
- m_impl->getSink().addLabel(output, librevenge::RVNGString());
+ m_impl->getSink().addLabel(output, librevenge::RVNGString(), true);
}
void EPUBHTMLGenerator::closeComment()
@@ -1067,7 +1093,26 @@ void EPUBHTMLGenerator::insertBinaryObject(const RVNGPropertyList &propList)
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());
- m_impl->output().insertEmptyElement("img", attrs);
+ if (!m_impl->m_linkPropertiesStack.empty())
+ {
+ RVNGPropertyList &linkProperties = m_impl->m_linkPropertiesStack.top();
+ // Save the main sink, as m_impl->output() will point to the footnote sink.
+ libepubgen::EPUBXMLSink &main = m_impl->output();
+ openFootnote(RVNGPropertyList());
+ main.insertEmptyElement("img", attrs);
+ main.closeElement("a");
+ main.closeElement("sup");
+ 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();
+ }
+ else
+ m_impl->output().insertEmptyElement("img", attrs);
if (!wrapStyle.empty())
{
--
2.13.6
From 6e094bbe9fd8c1784ef3c348d04e2add8b48fc67 Mon Sep 17 00:00:00 2001
From: Miklos Vajna <vmiklos@collabora.co.uk>
Date: Fri, 24 Nov 2017 17:18:53 +0100
Subject: [PATCH 2/2] EPUBHTMLGenerator: support image popup for text
And also make sure that the popup anchor is not superscript.
---
src/lib/EPUBHTMLGenerator.cpp | 44 ++++++++++++++++++++++++++------------
src/lib/EPUBHTMLGenerator.h | 4 ++++
src/test/EPUBTextGeneratorTest.cpp | 31 ++++++++++++++++++++++++++-
3 files changed, 64 insertions(+), 15 deletions(-)
diff --git a/src/lib/EPUBHTMLGenerator.cpp b/src/lib/EPUBHTMLGenerator.cpp
index 75b0866..5c6421c 100644
--- a/src/lib/EPUBHTMLGenerator.cpp
+++ b/src/lib/EPUBHTMLGenerator.cpp
@@ -239,7 +239,8 @@ struct TextZoneSink
{
RVNGPropertyList supAttrs;
supAttrs.insert("id", ("called" + lbl).c_str());
- output.openElement("sup", supAttrs);
+ if (closeAnchor)
+ output.openElement("sup", supAttrs);
RVNGPropertyList aAttrs;
if (version == 30)
aAttrs.insert("epub:type", "noteref");
@@ -769,7 +770,9 @@ void EPUBHTMLGenerator::insertText(const RVNGString &text)
{
if (m_impl->m_ignore)
return;
- m_impl->output().insertCharacters(text);
+ EPUBXMLSink &sink = openPopup();
+ sink.insertCharacters(text);
+ closePopup(sink);
m_impl->m_hasText = true;
}
@@ -1093,15 +1096,37 @@ void EPUBHTMLGenerator::insertBinaryObject(const RVNGPropertyList &propList)
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())
{
- RVNGPropertyList &linkProperties = m_impl->m_linkPropertiesStack.top();
// Save the main sink, as m_impl->output() will point to the footnote sink.
libepubgen::EPUBXMLSink &main = m_impl->output();
openFootnote(RVNGPropertyList());
- main.insertEmptyElement("img", attrs);
+ 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");
- main.closeElement("sup");
const EPUBPath &linkPath = m_impl->m_imageManager.insert(
RVNGBinaryData(linkProperties["office:binary-data"]->getStr()),
linkProperties["librevenge:mime-type"]->getStr());
@@ -1111,15 +1136,6 @@ void EPUBHTMLGenerator::insertBinaryObject(const RVNGPropertyList &propList)
m_impl->output().insertEmptyElement("img", linkAttrs);
closeFootnote();
}
- else
- m_impl->output().insertEmptyElement("img", attrs);
-
- if (!wrapStyle.empty())
- {
- attrs.clear();
- attrs.insert("style", wrapStyle);
- m_impl->output().insertEmptyElement("br", attrs);
- }
}
void EPUBHTMLGenerator::insertEquation(const RVNGPropertyList & /* propList */) {}
diff --git a/src/lib/EPUBHTMLGenerator.h b/src/lib/EPUBHTMLGenerator.h
index 11f20cb..3699179 100644
--- a/src/lib/EPUBHTMLGenerator.h
+++ b/src/lib/EPUBHTMLGenerator.h
@@ -27,6 +27,7 @@ class EPUBSpanStyleManager;
class EPUBParagraphStyleManager;
class EPUBTableStyleManager;
class EPUBPath;
+class EPUBXMLSink;
class EPUBHTMLGenerator : public librevenge::RVNGTextInterface
{
@@ -112,6 +113,9 @@ public:
void insertEquation(const librevenge::RVNGPropertyList &propList) override;
private:
+ EPUBXMLSink &openPopup();
+ void closePopup(EPUBXMLSink &main);
+
std::unique_ptr<EPUBHTMLGeneratorImpl> m_impl;
// Unimplemented to prevent compiler from creating crasher ones
--
2.13.6

View file

@ -89,6 +89,7 @@ public:
void testFontEmbedding();
void testImageLink();
void testFootnote();
void testPopup();
CPPUNIT_TEST_SUITE(EPUBExportTest);
CPPUNIT_TEST(testOutlineLevel);
@ -126,6 +127,7 @@ public:
CPPUNIT_TEST(testFontEmbedding);
CPPUNIT_TEST(testImageLink);
CPPUNIT_TEST(testFootnote);
CPPUNIT_TEST(testPopup);
CPPUNIT_TEST_SUITE_END();
};
@ -702,6 +704,26 @@ void EPUBExportTest::testFootnote()
assertXPath(mpXmlDoc, "//xhtml:body/xhtml:aside", "type", "footnote");
}
void EPUBExportTest::testPopup()
{
createDoc("popup.odt", {});
mpXmlDoc = parseExport("OEBPS/sections/section0001.xhtml");
// Test image popup anchor.
assertXPath(mpXmlDoc, "//xhtml:body/xhtml:p[1]/xhtml:a", "type", "noteref");
assertXPath(mpXmlDoc, "//xhtml:body/xhtml:p[1]/xhtml:a/xhtml:img", 1);
// Test image popup content.
assertXPath(mpXmlDoc, "//xhtml:body/xhtml:aside[1]", "type", "footnote");
assertXPath(mpXmlDoc, "//xhtml:body/xhtml:aside[1]/xhtml:img", 1);
// Test text popup anchor.
assertXPath(mpXmlDoc, "//xhtml:body/xhtml:p[2]/xhtml:span/xhtml:a", "type", "noteref");
assertXPathContent(mpXmlDoc, "//xhtml:body/xhtml:p[2]/xhtml:span/xhtml:a", "link");
// Test text popup content.
assertXPath(mpXmlDoc, "//xhtml:body/xhtml:aside[2]", "type", "footnote");
assertXPath(mpXmlDoc, "//xhtml:body/xhtml:aside[2]/xhtml:img", 1);
}
CPPUNIT_TEST_SUITE_REGISTRATION(EPUBExportTest);
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 766 B

View file

@ -14,11 +14,15 @@
#include <libepubgen/EPUBTextGenerator.h>
#include <libepubgen/libepubgen-decls.h>
#include <com/sun/star/beans/PropertyAttribute.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/frame/XModel.hpp>
#include <com/sun/star/lang/XInitialization.hpp>
#include <com/sun/star/uno/XComponentContext.hpp>
#include <com/sun/star/xml/sax/XDocumentHandler.hpp>
#include <comphelper/genericpropertyset.hxx>
#include <comphelper/propertysetinfo.hxx>
#include <cppuhelper/supportsservice.hxx>
#include "exp/xmlimp.hxx"
@ -82,7 +86,17 @@ sal_Bool EPUBExportFilter::filter(const uno::Sequence<beans::PropertyValue> &rDe
uno::Reference<xml::sax::XDocumentHandler> xExportHandler(new exp::XMLImport(mxContext, aGenerator, aSourceURL, rDescriptor));
uno::Reference<lang::XInitialization> xInitialization(mxContext->getServiceManager()->createInstanceWithContext("com.sun.star.comp.Writer.XMLOasisExporter", mxContext), uno::UNO_QUERY);
xInitialization->initialize({uno::makeAny(xExportHandler)});
// A subset of parameters are passed in as a property set.
comphelper::PropertyMapEntry const aInfoMap[] =
{
{OUString("BaseURI"), 0, cppu::UnoType<OUString>::get(), beans::PropertyAttribute::MAYBEVOID, 0},
{OUString(), 0, css::uno::Type(), 0, 0}
};
uno::Reference<beans::XPropertySet> xInfoSet(comphelper::GenericPropertySet_CreateInstance(new comphelper::PropertySetInfo(aInfoMap)));
xInfoSet->setPropertyValue("BaseURI", uno::makeAny(aSourceURL));
xInitialization->initialize({uno::makeAny(xExportHandler), uno::makeAny(xInfoSet)});
uno::Reference<document::XExporter> xExporter(xInitialization, uno::UNO_QUERY);
xExporter->setSourceDocument(mxSourceDocument);
uno::Reference<document::XFilter> xFilter(xInitialization, uno::UNO_QUERY);

View file

@ -265,6 +265,9 @@ void XMLTextFrameHyperlinkContext::startElement(const OUString &/*rName*/, const
FillStyles(rAttributeValue, mrImport.GetAutomaticTextStyles(), mrImport.GetTextStyles(), m_aPropertyList);
else
{
if (rAttributeName == "xlink:href" && mrImport.FillPopupData(rAttributeValue, aPropertyList))
continue;
// This affects the link's properties.
OString sName = OUStringToOString(rAttributeName, RTL_TEXTENCODING_UTF8);
OString sValue = OUStringToOString(rAttributeValue, RTL_TEXTENCODING_UTF8);
@ -331,6 +334,9 @@ void XMLHyperlinkContext::startElement(const OUString &/*rName*/, const css::uno
FillStyles(rAttributeValue, mrImport.GetAutomaticTextStyles(), mrImport.GetTextStyles(), m_aPropertyList);
else
{
if (rAttributeName == "xlink:href" && mrImport.FillPopupData(rAttributeValue, aPropertyList))
continue;
// This affects the link's properties.
OString sName = OUStringToOString(rAttributeName, RTL_TEXTENCODING_UTF8);
OString sValue = OUStringToOString(rAttributeValue, RTL_TEXTENCODING_UTF8);

View file

@ -12,6 +12,7 @@
#include <initializer_list>
#include <unordered_map>
#include <com/sun/star/uri/UriReferenceFactory.hpp>
#include <com/sun/star/xml/sax/InputSource.hpp>
#include <com/sun/star/xml/sax/Parser.hpp>
#include <rtl/uri.hxx>
@ -242,7 +243,8 @@ rtl::Reference<XMLImportContext> XMLOfficeDocContext::CreateChildContext(const O
XMLImport::XMLImport(const uno::Reference<uno::XComponentContext> &xContext, librevenge::RVNGTextInterface &rGenerator, const OUString &rURL, const uno::Sequence<beans::PropertyValue> &rDescriptor)
: mrGenerator(rGenerator),
mxContext(xContext)
mxContext(xContext),
maDocumentBaseURL(rURL)
{
uno::Sequence<beans::PropertyValue> aFilterData;
for (sal_Int32 i = 0; i < rDescriptor.getLength(); ++i)
@ -270,6 +272,8 @@ XMLImport::XMLImport(const uno::Reference<uno::XComponentContext> &xContext, lib
}
FindXMPMetadata(mxContext, rURL, aFilterData, maMetaData);
mxUriReferenceFactory = uri::UriReferenceFactory::create(mxContext);
}
const librevenge::RVNGPropertyListVector &XMLImport::GetCoverImages()
@ -282,6 +286,54 @@ const librevenge::RVNGPropertyList &XMLImport::GetMetaData()
return maMetaData;
}
bool XMLImport::FillPopupData(const OUString &rURL, librevenge::RVNGPropertyList &rPropList)
{
uno::Reference<uri::XUriReference> xUriRef;
try
{
xUriRef = mxUriReferenceFactory->parse(rURL);
}
catch (const uno::Exception &rException)
{
SAL_WARN("writerperfect", "XMLImport::FillPopupData: XUriReference::parse() failed:" << rException.Message);
}
bool bRelative = false;
if (xUriRef.is())
bRelative = !xUriRef->isAbsolute();
if (!bRelative)
return false;
OUString aAbs;
INetURLObject aBaseURL(maDocumentBaseURL);
try
{
aAbs = rtl::Uri::convertRelToAbs(maDocumentBaseURL, aBaseURL.GetBase() + "/" + rURL);
}
catch (const rtl::MalformedUriException &rException)
{
SAL_WARN("writerperfect", "XMLImport::FillPopupData: convertRelToAbs() failed:" << rException.getMessage());
}
if (aAbs.isEmpty())
return false;
SvFileStream aStream(aAbs, StreamMode::READ);
if (aStream.IsOpen())
{
librevenge::RVNGBinaryData aBinaryData;
SvMemoryStream aMemoryStream;
aMemoryStream.WriteStream(aStream);
aBinaryData.append(static_cast<const unsigned char *>(aMemoryStream.GetBuffer()), aMemoryStream.GetSize());
rPropList.insert("office:binary-data", aBinaryData);
INetURLObject aAbsURL(aAbs);
OUString aMimeType = GetMimeType(aAbsURL.GetExtension());
rPropList.insert("librevenge:mime-type", aMimeType.toUtf8().getStr());
return true;
}
return false;
}
rtl::Reference<XMLImportContext> XMLImport::CreateContext(const OUString &rName, const css::uno::Reference<css::xml::sax::XAttributeList> &/*xAttribs*/)
{
if (rName == "office:document")

View file

@ -17,6 +17,7 @@
#include <com/sun/star/beans/PropertyValue.hpp>
#include <com/sun/star/uno/XComponentContext.hpp>
#include <com/sun/star/uri/XUriReferenceFactory.hpp>
#include <com/sun/star/xml/sax/XDocumentHandler.hpp>
#include <cppuhelper/implbase.hxx>
@ -55,6 +56,8 @@ class XMLImport : public cppu::WeakImplHelper
/// Author, date, etc -- overwrites what would be from the document out of the box.
librevenge::RVNGPropertyList maMetaData;
const css::uno::Reference<css::uno::XComponentContext> &mxContext;
css::uno::Reference<css::uri::XUriReferenceFactory> mxUriReferenceFactory;
OUString maDocumentBaseURL;
public:
XMLImport(const css::uno::Reference<css::uno::XComponentContext> &xContext, librevenge::RVNGTextInterface &rGenerator, const OUString &rURL, const css::uno::Sequence<css::beans::PropertyValue> &rDescriptor);
@ -78,6 +81,7 @@ public:
std::map<OUString, librevenge::RVNGPropertyList> &GetGraphicStyles();
const librevenge::RVNGPropertyListVector &GetCoverImages();
const librevenge::RVNGPropertyList &GetMetaData();
bool FillPopupData(const OUString &rURL, librevenge::RVNGPropertyList &rPropList);
// XDocumentHandler
void SAL_CALL startDocument() override;