summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Hung <marklh9@gmail.com>2018-04-24 21:26:49 +0800
committerMiklos Vajna <vmiklos@collabora.co.uk>2018-04-26 17:16:53 +0200
commit4fb081704811b66194ea11e528ad792957b7ccfd (patch)
tree51d74a4fde41ad0887b124d02400d1b71479c65d
parent7a30e0d63d37eeb7b5c1e30791de17a51ddd0652 (diff)
tdf#116822 export ruby text and base text to epub.
Backport aa254c9e6f2d1ecfa2512111746a77c05ba9717f from libepubgen, implement XMLRubyContext, XMLRubyTextContext, XMLRubyBaseContext. Character formats of ruby text, ruby alignment, and ruby position are not implemented yet. Change-Id: I6c3708e6bc8e9e36a68171a037fd393f45d8d34f Reviewed-on: https://gerrit.libreoffice.org/53408 Tested-by: Jenkins <ci@libreoffice.org> Reviewed-by: Miklos Vajna <vmiklos@collabora.co.uk>
-rw-r--r--external/libepubgen/0001-Enclose-span-with-ruby-if-text-ruby-text-is-set.patch.1117
-rw-r--r--external/libepubgen/UnpackedTarball_libepubgen.mk2
-rw-r--r--writerperfect/qa/unit/EPUBExportTest.cxx10
-rw-r--r--writerperfect/qa/unit/data/writer/epubexport/simple-ruby.odtbin0 -> 8307 bytes
-rw-r--r--writerperfect/source/writer/exp/txtparai.cxx85
5 files changed, 214 insertions, 0 deletions
diff --git a/external/libepubgen/0001-Enclose-span-with-ruby-if-text-ruby-text-is-set.patch.1 b/external/libepubgen/0001-Enclose-span-with-ruby-if-text-ruby-text-is-set.patch.1
new file mode 100644
index 000000000000..99c8523a9dc9
--- /dev/null
+++ b/external/libepubgen/0001-Enclose-span-with-ruby-if-text-ruby-text-is-set.patch.1
@@ -0,0 +1,117 @@
+From 16c4e93af6d5eb9d021a671c54af664edc120df9 Mon Sep 17 00:00:00 2001
+From: Mark Hung <marklh9@gmail.com>
+Date: Mon, 23 Apr 2018 01:24:48 +0800
+Subject: [PATCH] Enclose <span> with <ruby> if text:ruby-text is set.
+
+---
+ src/lib/EPUBHTMLGenerator.cpp | 22 ++++++++++++++++++++++
+ src/test/EPUBTextGeneratorTest.cpp | 25 +++++++++++++++++++++++++
+ 2 files changed, 47 insertions(+)
+
+diff --git a/src/lib/EPUBHTMLGenerator.cpp b/src/lib/EPUBHTMLGenerator.cpp
+index 0080816..a4467a9 100644
+--- a/src/lib/EPUBHTMLGenerator.cpp
++++ b/src/lib/EPUBHTMLGenerator.cpp
+@@ -397,6 +397,7 @@ struct EPUBHTMLGeneratorImpl
+ , m_linkPropertiesStack()
+ , m_paragraphAttributesStack()
+ , m_spanAttributesStack()
++ , m_rubyText()
+ , m_stylesMethod(stylesMethod)
+ , m_layoutMethod(layoutMethod)
+ , m_actualSink()
+@@ -500,6 +501,9 @@ struct EPUBHTMLGeneratorImpl
+ std::stack<RVNGPropertyList> m_paragraphAttributesStack;
+ std::stack<RVNGPropertyList> m_spanAttributesStack;
+
++ /// This is set when the span has ruby text and should be wrapped in <ruby></ruby>.
++ std::string m_rubyText;
++
+ EPUBStylesMethod m_stylesMethod;
+ EPUBLayoutMethod m_layoutMethod;
+
+@@ -743,6 +747,14 @@ void EPUBHTMLGenerator::openSpan(const RVNGPropertyList &propList)
+ attrs.insert("style", m_impl->m_spanManager.getStyle(propList, false).c_str());
+ break;
+ }
++
++ const librevenge::RVNGProperty *rubyText = propList["text:ruby-text"];
++ if (rubyText)
++ {
++ m_impl->m_rubyText = rubyText->getStr().cstr();
++ m_impl->output(false).openElement("ruby", attrs);
++ }
++
+ m_impl->output(false).openElement("span", attrs);
+
+ librevenge::RVNGPropertyList::Iter i(attrs);
+@@ -761,6 +773,16 @@ void EPUBHTMLGenerator::closeSpan()
+ m_impl->m_spanAttributesStack.pop();
+
+ m_impl->output().closeElement("span");
++
++ if (m_impl->m_rubyText.length())
++ {
++ m_impl->output().openElement("rt");
++ m_impl->output().insertCharacters(m_impl->m_rubyText.c_str());
++ m_impl->output().closeElement("rt");
++ m_impl->output().closeElement("ruby");
++ m_impl->m_hasText = true;
++ m_impl->m_rubyText.clear();
++ }
+ }
+
+ void EPUBHTMLGenerator::openLink(const RVNGPropertyList &propList)
+diff --git a/src/test/EPUBTextGeneratorTest.cpp b/src/test/EPUBTextGeneratorTest.cpp
+index f03824f..61c7cac 100644
+--- a/src/test/EPUBTextGeneratorTest.cpp
++++ b/src/test/EPUBTextGeneratorTest.cpp
+@@ -240,6 +240,7 @@ private:
+ CPPUNIT_TEST(testSplitOnHeadingInPageSpan);
+ CPPUNIT_TEST(testSplitOnSizeInPageSpan);
+ CPPUNIT_TEST(testManyWritingModes);
++ CPPUNIT_TEST(testRubyElements);
+ CPPUNIT_TEST_SUITE_END();
+
+ private:
+@@ -284,6 +285,7 @@ private:
+ void testSplitOnHeadingInPageSpan();
+ void testSplitOnSizeInPageSpan();
+ void testManyWritingModes();
++ void testRubyElements();
+
+ /// Asserts that exactly one xpath exists in buffer, and its content equals content.
+ void assertXPathContent(xmlBufferPtr buffer, const std::string &xpath, const std::string &content);
+@@ -1507,6 +1509,29 @@ void EPUBTextGeneratorTest::testManyWritingModes()
+ assertXPath(package.m_streams["OEBPS/sections/section0002.xhtml"], "//xhtml:body", "class", "body1");
+ }
+
++void EPUBTextGeneratorTest::testRubyElements()
++{
++ StringEPUBPackage package;
++ libepubgen::EPUBTextGenerator generator(&package);
++ generator.startDocument(librevenge::RVNGPropertyList());
++ generator.openParagraph(librevenge::RVNGPropertyList());
++ {
++ librevenge::RVNGPropertyList span;
++ span.insert("text:ruby-text", "ruby text");
++ generator.openSpan(span);
++ generator.insertText("base text");
++ generator.closeSpan();
++ }
++ generator.closeParagraph();
++ generator.endDocument();
++
++ // Expects: <ruby><span>base text</span><rt>ruby text</rt></ruby>
++ CPPUNIT_ASSERT_XPATH(package.m_streams["OEBPS/sections/section0001.xhtml"], "//xhtml:ruby", 1);
++ CPPUNIT_ASSERT_XPATH_CONTENT(package.m_streams["OEBPS/sections/section0001.xhtml"], "//xhtml:ruby/xhtml:rt", "ruby text");
++ CPPUNIT_ASSERT_XPATH_CONTENT(package.m_streams["OEBPS/sections/section0001.xhtml"], "//xhtml:ruby/xhtml:span", "base text");
++}
++
++
+ CPPUNIT_TEST_SUITE_REGISTRATION(EPUBTextGeneratorTest);
+
+ }
+--
+2.14.1
+
diff --git a/external/libepubgen/UnpackedTarball_libepubgen.mk b/external/libepubgen/UnpackedTarball_libepubgen.mk
index a6b2020f5f07..d7159f7e7bb5 100644
--- a/external/libepubgen/UnpackedTarball_libepubgen.mk
+++ b/external/libepubgen/UnpackedTarball_libepubgen.mk
@@ -16,6 +16,8 @@ epubgen_patches += 0001-Support-writing-mode-for-reflowable-layout-method.patch.
epubgen_patches += 0002-Always-keep-page-properties-when-splitting-the-HTML-.patch.1
# Backport of <https://sourceforge.net/p/libepubgen/code/ci/1f602fcaa74fc9dbc6457019d11c602ff4040a4e/>.
epubgen_patches += 0003-Ensure-page-properties-in-the-page-span-works.patch.1
+# Backport of <https://sourceforge.net/p/libepubgen/code/ci/aa254c9e6f2d1ecfa2512111746a77c05ba9717f/>
+epubgen_patches += 0001-Enclose-span-with-ruby-if-text-ruby-text-is-set.patch.1
ifeq ($(COM_IS_CLANG),TRUE)
ifneq ($(filter -fsanitize=%,$(CC)),)
diff --git a/writerperfect/qa/unit/EPUBExportTest.cxx b/writerperfect/qa/unit/EPUBExportTest.cxx
index ce8f2964b0b7..90e97ba77799 100644
--- a/writerperfect/qa/unit/EPUBExportTest.cxx
+++ b/writerperfect/qa/unit/EPUBExportTest.cxx
@@ -103,6 +103,7 @@ public:
void testTdf115623SingleWritingMode();
void testTdf115623SplitByChapter();
void testTdf115623ManyPageSpans();
+ void testSimpleRuby();
CPPUNIT_TEST_SUITE(EPUBExportTest);
CPPUNIT_TEST(testOutlineLevel);
@@ -152,6 +153,7 @@ public:
CPPUNIT_TEST(testTdf115623SingleWritingMode);
CPPUNIT_TEST(testTdf115623SplitByChapter);
CPPUNIT_TEST(testTdf115623ManyPageSpans);
+ CPPUNIT_TEST(testSimpleRuby);
CPPUNIT_TEST_SUITE_END();
};
@@ -967,6 +969,14 @@ void EPUBExportTest::testTdf115623ManyPageSpans()
}
}
+void EPUBExportTest::testSimpleRuby()
+{
+ createDoc("simple-ruby.odt", {});
+ mpXmlDoc = parseExport("OEBPS/sections/section0001.xhtml");
+ assertXPathContent(mpXmlDoc, "//xhtml:body/xhtml:p/xhtml:ruby/xhtml:span", "base text");
+ assertXPathContent(mpXmlDoc, "//xhtml:body/xhtml:p/xhtml:ruby/xhtml:rt", "ruby text");
+}
+
CPPUNIT_TEST_SUITE_REGISTRATION(EPUBExportTest);
}
diff --git a/writerperfect/qa/unit/data/writer/epubexport/simple-ruby.odt b/writerperfect/qa/unit/data/writer/epubexport/simple-ruby.odt
new file mode 100644
index 000000000000..160dd00ee1e7
--- /dev/null
+++ b/writerperfect/qa/unit/data/writer/epubexport/simple-ruby.odt
Binary files differ
diff --git a/writerperfect/source/writer/exp/txtparai.cxx b/writerperfect/source/writer/exp/txtparai.cxx
index f219cf711880..297c5824ccf8 100644
--- a/writerperfect/source/writer/exp/txtparai.cxx
+++ b/writerperfect/source/writer/exp/txtparai.cxx
@@ -148,6 +148,89 @@ void XMLSpanContext::characters(const OUString& rChars)
mrImport.GetGenerator().closeSpan();
}
+/// Handler for <text:ruby>.
+class XMLRubyContext : public XMLImportContext
+{
+public:
+ XMLRubyContext(XMLImport& rImport, const librevenge::RVNGPropertyList& rPropertyList);
+
+ rtl::Reference<XMLImportContext>
+ CreateChildContext(const OUString& rName,
+ const css::uno::Reference<css::xml::sax::XAttributeList>& xAttribs) override;
+
+ void SAL_CALL endElement(const OUString& rName) override;
+
+ OUString m_sRubyText;
+ OUString m_sRubyBase;
+
+private:
+ librevenge::RVNGPropertyList m_aPropertyList;
+};
+
+/// Handler for <text:ruby-text>.
+class XMLRubyTextContext : public XMLImportContext
+{
+public:
+ XMLRubyTextContext(XMLImport& rImport, XMLRubyContext& rParent)
+ : XMLImportContext(rImport)
+ , m_rParent(rParent)
+ {
+ }
+
+ void SAL_CALL characters(const OUString& rChars) override { m_rParent.m_sRubyText = rChars; }
+
+private:
+ XMLRubyContext& m_rParent;
+};
+
+/// Handler for <text:ruby-base>.
+class XMLRubyBaseContext : public XMLImportContext
+{
+public:
+ XMLRubyBaseContext(XMLImport& rImport, XMLRubyContext& rParent)
+ : XMLImportContext(rImport)
+ , m_rParent(rParent)
+ {
+ }
+
+ void SAL_CALL characters(const OUString& rChars) override { m_rParent.m_sRubyBase += rChars; }
+
+private:
+ XMLRubyContext& m_rParent;
+};
+
+XMLRubyContext::XMLRubyContext(XMLImport& rImport,
+ const librevenge::RVNGPropertyList& rPropertyList)
+ : XMLImportContext(rImport)
+ , m_sRubyText()
+{
+ // Inherit properties from parent.
+ librevenge::RVNGPropertyList::Iter itProp(rPropertyList);
+ for (itProp.rewind(); itProp.next();)
+ m_aPropertyList.insert(itProp.key(), itProp()->clone());
+}
+
+rtl::Reference<XMLImportContext> XMLRubyContext::CreateChildContext(
+ const OUString& rName, const css::uno::Reference<css::xml::sax::XAttributeList>& /*xAttribs*/)
+{
+ if (rName == "text:ruby-base")
+ return new XMLRubyBaseContext(mrImport, *this);
+ if (rName == "text:ruby-text")
+ return new XMLRubyTextContext(mrImport, *this);
+ return nullptr;
+}
+
+void XMLRubyContext::endElement(const OUString& /*rName*/)
+{
+ OString sRubyText = OUStringToOString(m_sRubyText, RTL_TEXTENCODING_UTF8);
+ OString sRubyBase = OUStringToOString(m_sRubyBase, RTL_TEXTENCODING_UTF8);
+ if (sRubyText.getLength())
+ m_aPropertyList.insert("text:ruby-text", sRubyText.getStr());
+ mrImport.GetGenerator().openSpan(m_aPropertyList);
+ mrImport.GetGenerator().insertText(sRubyBase.getStr());
+ mrImport.GetGenerator().closeSpan();
+}
+
/// Base class for contexts that represent a single character only.
class XMLCharContext : public XMLImportContext
{
@@ -426,6 +509,8 @@ rtl::Reference<XMLImportContext> XMLParaContext::CreateChildContext(
return new XMLHyperlinkContext(mrImport, m_aTextPropertyList);
if (rName == "draw:a")
return new XMLTextFrameHyperlinkContext(mrImport, m_aTextPropertyList);
+ if (rName == "text:ruby")
+ return new XMLRubyContext(mrImport, m_aTextPropertyList);
return CreateParagraphOrSpanChildContext(mrImport, rName, m_aTextPropertyList);
}