Kaydet (Commit) 3ed8466b authored tarafından Miklos Vajna's avatar Miklos Vajna

EPUB export, fixed layout: switch to a metafile-based approach

Trying to guess layout from flat ODF output is an approach that doesn't
scale, think of complex documents with split tables, etc.

Do it similar to the PDF export instead: take a metafile of each page
and use the existing SVG writer to embed it into XHTML.

Change-Id: I6e860834beb8025519d3e367f858077ae9e9c006
Reviewed-on: https://gerrit.libreoffice.org/45647Tested-by: 's avatarJenkins <ci@libreoffice.org>
Reviewed-by: 's avatarMiklos Vajna <vmiklos@collabora.co.uk>
üst d1e2205c
......@@ -5831,3 +5831,28 @@ index c3bc963..02c299a 100644
--
2.13.6
From 88b9d9a1efb9b064ea99c57ec273f76712d361ff Mon Sep 17 00:00:00 2001
From: Miklos Vajna <vmiklos@collabora.co.uk>
Date: Thu, 30 Nov 2017 11:32:41 +0100
Subject: [PATCH] EPUBTextGenerator: allow a single image on a page
---
src/lib/EPUBTextGenerator.cpp | 1 +
src/test/EPUBTextGeneratorTest.cpp | 24 ++++++++++++++++++++++++
2 files changed, 25 insertions(+)
diff --git a/src/lib/EPUBTextGenerator.cpp b/src/lib/EPUBTextGenerator.cpp
index 02c299a..38573ec 100644
--- a/src/lib/EPUBTextGenerator.cpp
+++ b/src/lib/EPUBTextGenerator.cpp
@@ -684,6 +684,7 @@ void EPUBTextGenerator::insertBinaryObject(const librevenge::RVNGPropertyList &p
if (m_impl->m_inHeader || m_impl->m_inFooter)
m_impl->m_currentHeaderOrFooter->addInsertBinaryObject(newPropList);
+ m_impl->getSplitGuard().incrementSize(1);
m_impl->getHtml()->insertBinaryObject(newPropList);
}
--
2.13.6
......@@ -36,6 +36,7 @@ $(eval $(call gb_Library_use_libraries,wpftwriter,\
sal \
sfx \
sot \
svt \
svx \
tl \
ucbhelper \
......
......@@ -60,6 +60,7 @@ public:
void testEPUB2();
void testEPUBFixedLayout();
void testEPUBFixedLayoutOption();
void testEPUBFixedLayoutImplicitBreak();
void testPageBreakSplit();
void testSpanAutostyle();
void testParaAutostyleCharProps();
......@@ -102,6 +103,7 @@ public:
CPPUNIT_TEST(testEPUB2);
CPPUNIT_TEST(testEPUBFixedLayout);
CPPUNIT_TEST(testEPUBFixedLayoutOption);
CPPUNIT_TEST(testEPUBFixedLayoutImplicitBreak);
CPPUNIT_TEST(testPageBreakSplit);
CPPUNIT_TEST(testSpanAutostyle);
CPPUNIT_TEST(testParaAutostyleCharProps);
......@@ -327,6 +329,21 @@ void EPUBExportTest::testEPUBFixedLayoutOption()
assertXPathContent(mpXmlDoc, "/opf:package/opf:metadata/opf:meta[@property='rendition:layout']", "pre-paginated");
}
void EPUBExportTest::testEPUBFixedLayoutImplicitBreak()
{
uno::Sequence<beans::PropertyValue> aFilterData(comphelper::InitPropertySequence(
{
// Explicitly request fixed layout.
{"EPUBLayoutMethod", uno::makeAny(static_cast<sal_Int32>(libepubgen::EPUB_LAYOUT_METHOD_FIXED))}
}));
createDoc("fxl-2page.fodt", aFilterData);
CPPUNIT_ASSERT(mxZipFile->hasByName("OEBPS/sections/section0001.xhtml"));
// This was missing, implicit page break (as calculated by the layout) was lost on export.
CPPUNIT_ASSERT(mxZipFile->hasByName("OEBPS/sections/section0002.xhtml"));
CPPUNIT_ASSERT(!mxZipFile->hasByName("OEBPS/sections/section0003.xhtml"));
}
void EPUBExportTest::testPageBreakSplit()
{
uno::Sequence<beans::PropertyValue> aFilterData(comphelper::InitPropertySequence(
......
......@@ -18,12 +18,16 @@
#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/text/XPageCursor.hpp>
#include <com/sun/star/text/XTextViewCursorSupplier.hpp>
#include <com/sun/star/uno/XComponentContext.hpp>
#include <com/sun/star/view/XRenderable.hpp>
#include <com/sun/star/xml/sax/XDocumentHandler.hpp>
#include <comphelper/genericpropertyset.hxx>
#include <comphelper/propertysetinfo.hxx>
#include <cppuhelper/supportsservice.hxx>
#include <svtools/DocumentToGraphicRenderer.hxx>
#include "exp/xmlimp.hxx"
#include "EPUBPackage.hxx"
......@@ -103,7 +107,14 @@ sal_Bool EPUBExportFilter::filter(const uno::Sequence<beans::PropertyValue> &rDe
uno::Reference<frame::XModel> xSourceModel(mxSourceDocument, uno::UNO_QUERY);
if (xSourceModel.is())
aSourceURL = xSourceModel->getURL();
uno::Reference<xml::sax::XDocumentHandler> xExportHandler(new exp::XMLImport(mxContext, aGenerator, aSourceURL, rDescriptor));
std::vector<std::pair<uno::Sequence<sal_Int8>, Size>> aPageMetafiles;
#if LIBEPUBGEN_VERSION_SUPPORT
if (nLayoutMethod == libepubgen::EPUB_LAYOUT_METHOD_FIXED)
CreateMetafiles(aPageMetafiles);
#endif
uno::Reference<xml::sax::XDocumentHandler> xExportHandler(new exp::XMLImport(mxContext, aGenerator, aSourceURL, rDescriptor, aPageMetafiles));
uno::Reference<lang::XInitialization> xInitialization(mxContext->getServiceManager()->createInstanceWithContext("com.sun.star.comp.Writer.XMLOasisExporter", mxContext), uno::UNO_QUERY);
......@@ -111,7 +122,7 @@ sal_Bool EPUBExportFilter::filter(const uno::Sequence<beans::PropertyValue> &rDe
comphelper::PropertyMapEntry const aInfoMap[] =
{
{OUString("BaseURI"), 0, cppu::UnoType<OUString>::get(), beans::PropertyAttribute::MAYBEVOID, 0},
{OUString(), 0, css::uno::Type(), 0, 0}
{OUString(), 0, uno::Type(), 0, 0}
};
uno::Reference<beans::XPropertySet> xInfoSet(comphelper::GenericPropertySet_CreateInstance(new comphelper::PropertySetInfo(aInfoMap)));
xInfoSet->setPropertyValue("BaseURI", uno::makeAny(aSourceURL));
......@@ -120,9 +131,43 @@ sal_Bool EPUBExportFilter::filter(const uno::Sequence<beans::PropertyValue> &rDe
uno::Reference<document::XExporter> xExporter(xInitialization, uno::UNO_QUERY);
xExporter->setSourceDocument(mxSourceDocument);
uno::Reference<document::XFilter> xFilter(xInitialization, uno::UNO_QUERY);
return xFilter->filter(rDescriptor);
}
void EPUBExportFilter::CreateMetafiles(std::vector<std::pair<uno::Sequence<sal_Int8>, Size>> &rPageMetafiles)
{
DocumentToGraphicRenderer aRenderer(mxSourceDocument, /*bSelectionOnly=*/false);
uno::Reference<frame::XModel> xModel(mxSourceDocument, uno::UNO_QUERY);
if (!xModel.is())
return;
uno::Reference<text::XTextViewCursorSupplier> xTextViewCursorSupplier(xModel->getCurrentController(), uno::UNO_QUERY);
if (!xTextViewCursorSupplier.is())
return;
uno::Reference<text::XPageCursor> xCursor(xTextViewCursorSupplier->getViewCursor(), uno::UNO_QUERY);
if (!xCursor.is())
return;
xCursor->jumpToLastPage();
sal_Int16 nPages = xCursor->getPage();
for (sal_Int16 nPage = 1; nPage <= nPages; ++nPage)
{
Size aDocumentSizePixel = aRenderer.getDocumentSizeInPixels(nPage);
Graphic aGraphic = aRenderer.renderToGraphic(nPage, aDocumentSizePixel, aDocumentSizePixel, COL_WHITE);
const GDIMetaFile &rGDIMetaFile = aGraphic.GetGDIMetaFile();
SvMemoryStream aMemoryStream;
const_cast<GDIMetaFile &>(rGDIMetaFile).Write(aMemoryStream);
uno::Sequence<sal_Int8> aSequence(static_cast<const sal_Int8 *>(aMemoryStream.GetData()), aMemoryStream.Tell());
Size aLogic = aRenderer.getDocumentSizeIn100mm(nPage);
// Get the CSS pixel size of the page (mm100 -> pixel using 96 DPI, independent from system DPI).
Size aCss(static_cast<double>(aLogic.getWidth()) / 26.4583, static_cast<double>(aLogic.getHeight()) / 26.4583);
rPageMetafiles.emplace_back(aSequence, aCss);
}
}
void EPUBExportFilter::cancel()
{
}
......
......@@ -10,12 +10,15 @@
#ifndef INCLUDED_WRITERPERFECT_SOURCE_WRITER_EPUBEXPORTFILTER_HXX
#define INCLUDED_WRITERPERFECT_SOURCE_WRITER_EPUBEXPORTFILTER_HXX
#include <vector>
#include <cppuhelper/implbase.hxx>
#include <com/sun/star/document/XFilter.hpp>
#include <com/sun/star/document/XExporter.hpp>
#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/uno/XComponentContext.hpp>
#include <tools/gen.hxx>
namespace writerperfect
{
......@@ -52,6 +55,10 @@ public:
static sal_Int32 GetDefaultSplitMethod();
/// Gives the default layout method.
static sal_Int32 GetDefaultLayoutMethod();
private:
/// Create page metafiles in case of fixed layout.
void CreateMetafiles(std::vector<std::pair<css::uno::Sequence<sal_Int8>, Size>> &rPageMetafiles);
};
} // namespace writerperfect
......
......@@ -12,9 +12,11 @@
#include <initializer_list>
#include <unordered_map>
#include <com/sun/star/svg/XSVGWriter.hpp>
#include <com/sun/star/uri/UriReferenceFactory.hpp>
#include <com/sun/star/xml/sax/InputSource.hpp>
#include <com/sun/star/xml/sax/Parser.hpp>
#include <com/sun/star/xml/sax/Writer.hpp>
#include <rtl/uri.hxx>
#include <tools/stream.hxx>
#include <tools/urlobj.hxx>
......@@ -210,7 +212,7 @@ class XMLBodyContext : public XMLImportContext
public:
XMLBodyContext(XMLImport &rImport);
rtl::Reference<XMLImportContext> CreateChildContext(const OUString &rName, const css::uno::Reference<css::xml::sax::XAttributeList> &/*xAttribs*/) override;
rtl::Reference<XMLImportContext> CreateChildContext(const OUString &rName, const uno::Reference<xml::sax::XAttributeList> &/*xAttribs*/) override;
};
XMLBodyContext::XMLBodyContext(XMLImport &rImport)
......@@ -218,7 +220,7 @@ XMLBodyContext::XMLBodyContext(XMLImport &rImport)
{
}
rtl::Reference<XMLImportContext> XMLBodyContext::CreateChildContext(const OUString &rName, const css::uno::Reference<css::xml::sax::XAttributeList> &/*xAttribs*/)
rtl::Reference<XMLImportContext> XMLBodyContext::CreateChildContext(const OUString &rName, const uno::Reference<xml::sax::XAttributeList> &/*xAttribs*/)
{
if (rName == "office:text")
return new XMLBodyContentContext(mrImport);
......@@ -231,7 +233,10 @@ class XMLOfficeDocContext : public XMLImportContext
public:
XMLOfficeDocContext(XMLImport &rImport);
rtl::Reference<XMLImportContext> CreateChildContext(const OUString &rName, const css::uno::Reference<css::xml::sax::XAttributeList> &/*xAttribs*/) override;
rtl::Reference<XMLImportContext> CreateChildContext(const OUString &rName, const uno::Reference<xml::sax::XAttributeList> &/*xAttribs*/) override;
// Handles metafile for a single page.
void HandleFixedLayoutPage(const uno::Sequence<sal_Int8> &rPage, const Size &rSize, bool bFirst);
};
XMLOfficeDocContext::XMLOfficeDocContext(XMLImport &rImport)
......@@ -239,26 +244,85 @@ XMLOfficeDocContext::XMLOfficeDocContext(XMLImport &rImport)
{
}
rtl::Reference<XMLImportContext> XMLOfficeDocContext::CreateChildContext(const OUString &rName, const css::uno::Reference<css::xml::sax::XAttributeList> &/*xAttribs*/)
rtl::Reference<XMLImportContext> XMLOfficeDocContext::CreateChildContext(const OUString &rName, const uno::Reference<xml::sax::XAttributeList> &/*xAttribs*/)
{
if (rName == "office:body")
return new XMLBodyContext(mrImport);
else if (rName == "office:meta")
if (rName == "office:meta")
return new XMLMetaDocumentContext(mrImport);
else if (rName == "office:automatic-styles")
if (rName == "office:automatic-styles")
return new XMLStylesContext(mrImport, XMLStylesContext::StyleType_AUTOMATIC);
else if (rName == "office:styles")
if (rName == "office:styles")
return new XMLStylesContext(mrImport, XMLStylesContext::StyleType_NONE);
else if (rName == "office:font-face-decls")
if (rName == "office:font-face-decls")
return new XMLFontFaceDeclsContext(mrImport);
else if (rName == "office:master-styles")
if (rName == "office:master-styles")
return new XMLStylesContext(mrImport, XMLStylesContext::StyleType_MASTER);
if (rName == "office:body")
{
if (mrImport.GetPageMetafiles().empty())
return new XMLBodyContext(mrImport);
else
{
// Ignore text from doc model in the fixed layout case, instead
// insert the page metafiles.
bool bFirst = true;
for (const auto &rPage : mrImport.GetPageMetafiles())
{
HandleFixedLayoutPage(rPage.first, rPage.second, bFirst);
if (bFirst)
bFirst = false;
}
}
}
return nullptr;
}
XMLImport::XMLImport(const uno::Reference<uno::XComponentContext> &xContext, librevenge::RVNGTextInterface &rGenerator, const OUString &rURL, const uno::Sequence<beans::PropertyValue> &rDescriptor)
void XMLOfficeDocContext::HandleFixedLayoutPage(const uno::Sequence<sal_Int8> &rPage, const Size &rSize, bool bFirst)
{
uno::Reference<uno::XComponentContext> xCtx = mrImport.GetComponentContext();
uno::Reference<xml::sax::XWriter> xSaxWriter = xml::sax::Writer::create(xCtx);
if (!xSaxWriter.is())
return;
uno::Sequence<uno::Any> aArguments;
uno::Reference<svg::XSVGWriter> xSVGWriter(xCtx->getServiceManager()->createInstanceWithArgumentsAndContext("com.sun.star.svg.SVGWriter", aArguments, xCtx), uno::UNO_QUERY);
if (!xSVGWriter.is())
return;
SvMemoryStream aMemoryStream;
xSaxWriter->setOutputStream(new utl::OStreamWrapper(aMemoryStream));
xSVGWriter->write(xSaxWriter, rPage);
// Have all the info, invoke libepubgen.
librevenge::RVNGPropertyList aPageProperties;
// Pixel -> inch.
double fWidth = rSize.getWidth();
fWidth /= 96;
aPageProperties.insert("fo:page-width", fWidth);
double fHeight = rSize.getHeight();
fHeight /= 96;
aPageProperties.insert("fo:page-height", fHeight);
mrImport.GetGenerator().openPageSpan(aPageProperties);
librevenge::RVNGPropertyList aParagraphProperties;
if (!bFirst)
// All pages except the first one needs a page break before the page
// metafile.
aParagraphProperties.insert("fo:break-before", "page");
mrImport.GetGenerator().openParagraph(aParagraphProperties);
librevenge::RVNGPropertyList aImageProperties;
aImageProperties.insert("librevenge:mime-type", "image/svg+xml");
librevenge::RVNGBinaryData aBinaryData;
aBinaryData.append(static_cast<const unsigned char *>(aMemoryStream.GetBuffer()), aMemoryStream.GetSize());
aImageProperties.insert("office:binary-data", aBinaryData);
mrImport.GetGenerator().insertBinaryObject(aImageProperties);
mrImport.GetGenerator().closeParagraph();
mrImport.GetGenerator().closePageSpan();
}
XMLImport::XMLImport(const uno::Reference<uno::XComponentContext> &xContext, librevenge::RVNGTextInterface &rGenerator, const OUString &rURL, const uno::Sequence<beans::PropertyValue> &rDescriptor, const std::vector<std::pair<uno::Sequence<sal_Int8>, Size>> &rPageMetafiles)
: mrGenerator(rGenerator),
mxContext(xContext)
mxContext(xContext),
mrPageMetafiles(rPageMetafiles)
{
uno::Sequence<beans::PropertyValue> aFilterData;
for (sal_Int32 i = 0; i < rDescriptor.getLength(); ++i)
......@@ -351,7 +415,17 @@ bool XMLImport::IsPageSpanOpened() const
return mbPageSpanOpened;
}
rtl::Reference<XMLImportContext> XMLImport::CreateContext(const OUString &rName, const css::uno::Reference<css::xml::sax::XAttributeList> &/*xAttribs*/)
const std::vector<std::pair<uno::Sequence<sal_Int8>, Size>> &XMLImport::GetPageMetafiles() const
{
return mrPageMetafiles;
}
const uno::Reference<uno::XComponentContext> &XMLImport::GetComponentContext() const
{
return mxContext;
}
rtl::Reference<XMLImportContext> XMLImport::CreateContext(const OUString &rName, const uno::Reference<xml::sax::XAttributeList> &/*xAttribs*/)
{
if (rName == "office:document")
return new XMLOfficeDocContext(*this);
......@@ -458,7 +532,7 @@ void XMLImport::endDocument()
mrGenerator.endDocument();
}
void XMLImport::startElement(const OUString &rName, const css::uno::Reference<css::xml::sax::XAttributeList> &xAttribs)
void XMLImport::startElement(const OUString &rName, const uno::Reference<xml::sax::XAttributeList> &xAttribs)
{
rtl::Reference<XMLImportContext> xContext;
if (!maContexts.empty())
......@@ -500,7 +574,7 @@ void XMLImport::processingInstruction(const OUString &/*rTarget*/, const OUStrin
{
}
void XMLImport::setDocumentLocator(const css::uno::Reference<css::xml::sax::XLocator> &/*xLocator*/)
void XMLImport::setDocumentLocator(const uno::Reference<xml::sax::XLocator> &/*xLocator*/)
{
}
......
......@@ -12,6 +12,7 @@
#include <map>
#include <stack>
#include <vector>
#include <librevenge/librevenge.h>
......@@ -22,6 +23,7 @@
#include <cppuhelper/implbase.hxx>
#include <rtl/ref.hxx>
#include <tools/gen.hxx>
namespace writerperfect
{
......@@ -61,9 +63,10 @@ class XMLImport : public cppu::WeakImplHelper
css::uno::Reference<css::uri::XUriReferenceFactory> mxUriReferenceFactory;
OUString maMediaDir;
bool mbPageSpanOpened = false;
const std::vector<std::pair<css::uno::Sequence<sal_Int8>, Size>> &mrPageMetafiles;
public:
XMLImport(const css::uno::Reference<css::uno::XComponentContext> &xContext, librevenge::RVNGTextInterface &rGenerator, const OUString &rURL, const css::uno::Sequence<css::beans::PropertyValue> &rDescriptor);
XMLImport(const css::uno::Reference<css::uno::XComponentContext> &xContext, librevenge::RVNGTextInterface &rGenerator, const OUString &rURL, const css::uno::Sequence<css::beans::PropertyValue> &rDescriptor, const std::vector<std::pair<css::uno::Sequence<sal_Int8>, Size>> &rPageMetafiles);
rtl::Reference<XMLImportContext> CreateContext(const OUString &rName, const css::uno::Reference<css::xml::sax::XAttributeList> &xAttribs);
......@@ -89,6 +92,8 @@ public:
bool FillPopupData(const OUString &rURL, librevenge::RVNGPropertyList &rPropList);
void SetPageSpanOpened(bool bPageSpanOpened);
bool IsPageSpanOpened() const;
const std::vector<std::pair<css::uno::Sequence<sal_Int8>, Size>> &GetPageMetafiles() const;
const css::uno::Reference<css::uno::XComponentContext> &GetComponentContext() const;
// XDocumentHandler
void SAL_CALL startDocument() override;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment