diff --git a/CMakeLists.txt b/CMakeLists.txt index 64d1806..2d5f589 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,121 +1,121 @@ cmake_minimum_required(VERSION 3.5) -set(PIM_VERSION "5.14.44") +set(PIM_VERSION "5.14.45") project(KPimTextEdit VERSION ${PIM_VERSION}) # ECM setup set(KF5_MIN_VERSION "5.70.0") find_package(ECM ${KF5_MIN_VERSION} CONFIG REQUIRED) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) if (POLICY CMP0053) cmake_policy(SET CMP0053 NEW) endif() include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(GenerateExportHeader) include(ECMGenerateHeaders) include(ECMGeneratePriFile) include(ECMSetupVersion) include(FeatureSummary) include(ECMAddTests) include(ECMQtDeclareLoggingCategory) set(KPIMTEXTEDIT_LIB_VERSION ${PIM_VERSION}) ecm_setup_version(PROJECT VARIABLE_PREFIX KPIMTEXTEDIT VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/kpimtextedit_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5PimTextEditConfigVersion.cmake" SOVERSION 5 ) set(QT_REQUIRED_VERSION "5.12.0") find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED COMPONENTS Widgets) find_package(Grantlee5 "5.2" CONFIG REQUIRED) set_package_properties(Grantlee5 PROPERTIES DESCRIPTION "A plug-in based String Template system for Qt" URL "https://github.com/steveire/grantlee" PURPOSE "Required for the RichText composer" TYPE REQUIRED ) ########### Find packages ########### find_package(KF5Codecs ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5Config ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5ConfigWidgets ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5CoreAddons ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5I18n ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5IconThemes ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5KIO ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5Sonnet ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5SyntaxHighlighting ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5WidgetsAddons ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5XmlGui ${KF5_MIN_VERSION} CONFIG REQUIRED) option(BUILD_DESIGNERPLUGIN "Build plugin for Qt Designer" ON) add_feature_info(DESIGNERPLUGIN ${BUILD_DESIGNERPLUGIN} "Build plugin for Qt Designer") add_definitions(-DTRANSLATION_DOMAIN=\"libkpimtextedit\") if (EXISTS "${CMAKE_SOURCE_DIR}/.git") add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050e00) add_definitions(-DKF_DISABLE_DEPRECATED_BEFORE_AND_AT=0x054600) endif() add_definitions(-DQT_NO_FOREACH) add_definitions(-DQT_NO_KEYWORDS) find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED COMPONENTS TextToSpeech) if(BUILD_TESTING) find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED COMPONENTS Test) add_definitions(-DBUILD_TESTING) endif() ########### Targets ########### add_subdirectory(src) if(BUILD_TESTING) find_package(KF5TextWidgets ${KF5_MIN_VERSION} CONFIG REQUIRED) add_subdirectory(autotests) add_subdirectory(tests) endif() ########### CMake Config Files ########### set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF5PimTextEdit") configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/KF5PimTextEditConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KF5PimTextEditConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KF5PimTextEditConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KF5PimTextEditConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) install(EXPORT KF5PimTextEditTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KF5PimTextEditTargets.cmake NAMESPACE KF5:: ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kpimtextedit_version.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5} COMPONENT Devel ) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 11d0c50..8ef064c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,252 +1,253 @@ set(kpimtextedit_texteditor_SRCS texteditor/plaintexteditor/plaintexteditfindbar.cpp texteditor/plaintexteditor/plaintexteditor.cpp texteditor/plaintexteditor/plaintexteditorwidget.cpp texteditor/plaintexteditor/plaintextsyntaxspellcheckinghighlighter.cpp texteditor/commonwidget/findutils.cpp texteditor/commonwidget/textfindreplacewidget.cpp texteditor/commonwidget/texteditfindbarbase.cpp texteditor/commonwidget/textgotolinewidget.cpp texteditor/commonwidget/textmessageindicator.cpp texteditor/richtexteditor/richtexteditor.cpp texteditor/richtexteditor/richtexteditfindbar.cpp texteditor/richtexteditor/richtexteditorwidget.cpp ) set(kpimtextedit_composerng_SRCS composer-ng/richtextcomposer.cpp composer-ng/richtextcomposercontroler.cpp composer-ng/richtextcomposeractions.cpp composer-ng/klinkdialog.cpp composer-ng/nestedlisthelper.cpp composer-ng/richtextexternalcomposer.cpp composer-ng/richtextcomposerimages.cpp composer-ng/richtextcomposeremailquotedecorator.cpp composer-ng/richtextcomposeremailquotehighlighter.cpp composer-ng/richtextcomposerwidget.cpp ) set(kpimtextedit_texttospeech_SRCS texttospeech/texttospeech.cpp texttospeech/texttospeechwidget.cpp texttospeech/texttospeechconfigwidget.cpp texttospeech/texttospeechinterface.cpp texttospeech/abstracttexttospeechinterface.cpp texttospeech/abstracttexttospeechconfiginterface.cpp texttospeech/texttospeechconfiginterface.cpp texttospeech/texttospeechconfigdialog.cpp texttospeech/texttospeechlanguagecombobox.cpp texttospeech/texttospeechactions.cpp ) set(kpimtextedit_grantlee_builder_SRCS grantleebuilder/plaintextmarkupbuilder.cpp grantleebuilder/markupdirector.cpp grantleebuilder/texthtmlbuilder.cpp ) set(kpimtextedit_emoticon_builder_SRCS emoticon/emoticontexteditaction.cpp emoticon/emoticontexteditselector.cpp emoticon/emoticonunicodetab.cpp emoticon/emoticonlistwidgetselector.cpp emoticon/emoticonunicodeutils.cpp ) set(kpimtextedit_SRCS ${kpimtextedit_emoticon_builder_SRCS} inserthtmldialog.cpp insertimagedialog.cpp insertimagewidget.cpp inserttabledialog.cpp selectspecialchardialog.cpp tableactionmenu.cpp tablecellformatdialog.cpp tableformatdialog.cpp textutils.cpp inserttablewidget.cpp texteditorcompleter.cpp inserthtmleditor.cpp slidecontainer.cpp editorutil.cpp ) ecm_qt_declare_logging_category(kpimtextedit_SRCS HEADER kpimtextedit_debug.h IDENTIFIER KPIMTEXTEDIT_LOG CATEGORY_NAME org.kde.pim.kpimtextedit DESCRIPTION "kpimtextedit (kpimtextedit)" EXPORT KPIMTEXTEDIT) add_library(KF5PimTextEdit ${kpimtextedit_SRCS} ${kpimtextedit_texteditor_SRCS} ${kpimtextedit_texttospeech_SRCS} ${kpimtextedit_composerng_SRCS} ${kpimtextedit_grantlee_builder_SRCS}) generate_export_header(KF5PimTextEdit BASE_NAME kpimtextedit) add_library(KF5::PimTextEdit ALIAS KF5PimTextEdit) target_include_directories(KF5PimTextEdit INTERFACE "$") target_include_directories(KF5PimTextEdit PUBLIC "$") target_link_libraries(KF5PimTextEdit PRIVATE KF5::Codecs KF5::SonnetUi KF5::SonnetCore KF5::WidgetsAddons KF5::KIOWidgets KF5::ConfigWidgets Grantlee5::TextDocument KF5::XmlGui KF5::I18n KF5::SyntaxHighlighting Qt5::TextToSpeech ) set_target_properties(KF5PimTextEdit PROPERTIES VERSION ${KPIMTEXTEDIT_VERSION_STRING} SOVERSION ${KPIMTEXTEDIT_SOVERSION} EXPORT_NAME PimTextEdit ) install(TARGETS KF5PimTextEdit EXPORT KF5PimTextEditTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) ########### Generate Headers ############### ecm_generate_headers(KPimTextEdit_CamelCase_HEADERS HEADER_NAMES EditorUtil SelectSpecialCharDialog SlideContainer TextEditorCompleter TextUtils PREFIX KPIMTextEdit REQUIRED_HEADERS kpimtextedit_HEADERS ) ecm_generate_headers(KPimTextEdit_CamelCaseemoticon_HEADERS HEADER_NAMES EmoticonUnicodeTab PREFIX KPIMTextEdit REQUIRED_HEADERS kpimtextedit_HEADERS RELATIVE emoticon ) ecm_generate_headers(KPimTextEdit_CamelCasegrantlee_HEADERS HEADER_NAMES PlainTextMarkupBuilder TextHTMLBuilder MarkupDirector + AbstractMarkupBuilder PREFIX KPIMTextEdit REQUIRED_HEADERS kpimtextedit_HEADERS RELATIVE grantleebuilder ) ecm_generate_headers(PimCommon_CamelCasetextrichtexteditor_HEADERS HEADER_NAMES RichTextEditorWidget RichTextEditor RichTextEditFindBar REQUIRED_HEADERS PimCommon_richtexteditor_HEADERS PREFIX KPIMTextEdit RELATIVE texteditor/richtexteditor ) ecm_generate_headers(PimCommon_CamelCaseplaintexteditor_HEADERS HEADER_NAMES PlainTextEditor PlainTextEditorWidget PlainTextEditFindBar PlainTextSyntaxSpellCheckingHighlighter REQUIRED_HEADERS PimCommon_plaintexteditor_HEADERS PREFIX KPIMTextEdit RELATIVE texteditor/plaintexteditor ) ecm_generate_headers(PimCommon_CamelCasetexteditor_commonwidget_HEADERS HEADER_NAMES TextGotoLineWidget TextEditFindBarBase REQUIRED_HEADERS PimCommon_texteditor_commonwidget_HEADERS PREFIX KPIMTextEdit RELATIVE texteditor/commonwidget ) ecm_generate_headers(KPimTextEdit_CamelCasetexttospeechs_HEADERS HEADER_NAMES TextToSpeech TextToSpeechActions TextToSpeechInterface TextToSpeechWidget AbstractTextToSpeechInterface REQUIRED_HEADERS KPimTextEdit_texttospeechs_HEADERS PREFIX KPIMTextEdit RELATIVE texttospeech ) ecm_generate_headers(KPimTextEdit_Camelcasecomposerng_HEADERS HEADER_NAMES RichTextComposer RichTextComposerControler RichTextComposerImages RichTextExternalComposer RichTextComposerActions RichTextComposerEmailQuoteHighlighter RichTextComposerWidget REQUIRED_HEADERS KPimTextEdit_composerng_HEADERS PREFIX KPIMTextEdit RELATIVE composer-ng ) ########### install files ############### install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kpimtextedit_export.h ${kpimtextedit_HEADERS} ${KPimTextEdit_texttospeechs_HEADERS} ${PimCommon_richtexteditor_HEADERS} ${PimCommon_texteditor_commonwidget_HEADERS} ${PimCommon_plaintexteditor_HEADERS} ${KPimTextEdit_composerng_HEADERS} ${KPimTextEdit_emoticon_HEADERS} ${KPimTextEdit_grantlee_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KPIMTextEdit/kpimtextedit COMPONENT Devel ) install(FILES ${KPimTextEdit_CamelCase_HEADERS} ${PimCommon_CamelCasetextrichtexteditor_HEADERS} ${PimCommon_CamelCaseplaintexteditor_HEADERS} ${KPimTextEdit_CamelCasetexttospeechs_HEADERS} ${PimCommon_CamelCasetexteditor_commonwidget_HEADERS} ${KPimTextEdit_Camelcasecomposerng_HEADERS} ${KPimTextEdit_CamelCaseemoticon_HEADERS} ${KPimTextEdit_CamelCasegrantlee_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KPIMTextEdit/KPIMTextEdit/ COMPONENT Devel ) ecm_generate_pri_file(BASE_NAME KPIMTextEdit LIB_NAME KF5PimTextEdit DEPS "" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/KPIMTextEdit/) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) if (BUILD_TESTING) add_subdirectory(texteditor/plaintexteditor/autotests) add_subdirectory(texteditor/richtexteditor/autotests) add_subdirectory(texteditor/commonwidget/autotests) add_subdirectory(texteditor/plaintexteditor/tests) add_subdirectory(texteditor/richtexteditor/tests) add_subdirectory(texttospeech/autotests) add_subdirectory(texttospeech/tests) add_subdirectory(composer-ng/autotests) add_subdirectory(composer-ng/tests) add_subdirectory(grantleebuilder/autotests) endif() if(BUILD_DESIGNERPLUGIN) add_subdirectory(designer) endif() ecm_qt_install_logging_categories(EXPORT KPIMTEXTEDIT FILE kpimtextedit.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}) diff --git a/src/grantleebuilder/abstractmarkupbuilder.h b/src/grantleebuilder/abstractmarkupbuilder.h new file mode 100644 index 0000000..95f040f --- /dev/null +++ b/src/grantleebuilder/abstractmarkupbuilder.h @@ -0,0 +1,275 @@ +/* + Copyright (c) 2020 Montel Laurent + Copyright (c) 2008,2010 Stephen Kelly + + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2 of the License, or (at your + option) any later version. + + This library is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. + +*/ + +#ifndef GRANTLEE_ABSTRACTMARKUPBUILDER_H +#define GRANTLEE_ABSTRACTMARKUPBUILDER_H + +#include "kpimtextedit_export.h" + +#include +#include + +class QBrush; + +namespace KPIMTextEdit { + +class AbstractMarkupBuilderPrivate; + +/// @headerfile abstractmarkupbuilder.h grantlee/abstractmarkupbuilder.h + +/** + @brief Interface for creating marked-up text output. + + The **%AbstractMarkupBuilder** is used by a MarkupDirector to create marked-up + output such as html or markdown. + + See PlainTextMarkupBuilder and TextHTMLBuilder for example implementations. + + This interface can be extended to handle custom format types in a + QTextDocument. @see @ref custom_qtextobject + + @author Stephen Kelly +*/ +class KPIMTEXTEDIT_EXPORT AbstractMarkupBuilder +{ +public: + /** Destructor */ + virtual ~AbstractMarkupBuilder() {} + + /** Begin a bold element in the markup */ + virtual void beginStrong() = 0; + + /** Close the bold element in the markup */ + virtual void endStrong() = 0; + + /** Begin an emphasised element in the markup */ + virtual void beginEmph() = 0; + + /** Close the emphasised element in the markup */ + virtual void endEmph() = 0; + + /** Begin an underlined element in the markup */ + virtual void beginUnderline() = 0; + + /** Close the underlined element in the markup */ + virtual void endUnderline() = 0; + + /** Begin a struck out element in the markup */ + virtual void beginStrikeout() = 0; + + /** Close the struck out element in the markup */ + virtual void endStrikeout() = 0; + + /** + Begin a decorarated foreground element in the markup (A text color) + using @p brush + */ + virtual void beginForeground(const QBrush &brush) = 0; + + /** Close the decorarated foreground element in the markup */ + virtual void endForeground() = 0; + + /** + Begin a decorarated background element in the markup (A text background + color) using @p brush + */ + virtual void beginBackground(const QBrush &brush) = 0; + + /** Close the decorarated background element in the markup */ + virtual void endBackground() = 0; + + /** + Begin a url anchor element in the markup + @param href The href of the anchor. + @param name The name of the anchor. + */ + virtual void beginAnchor(const QString &href = {}, const QString &name = {}) + = 0; + + /** Close the anchor element */ + virtual void endAnchor() = 0; + + /** + Begin a new font familiy element in the markup + @param family The name of the font family to begin. + */ + virtual void beginFontFamily(const QString &family) = 0; + + /** End font family element */ + virtual void endFontFamily() = 0; + + /** + Begin a new font point size element in the markup + @param size The point size to begin. + */ + virtual void beginFontPointSize(int size) = 0; + + /** End font point size element */ + virtual void endFontPointSize() = 0; + + /** + Begin a new paragraph in the markup + @param a The alignment of the new paragraph. + @param top The top margin of the new paragraph. + @param bottom The bottom margin of the new paragraph. + @param left The left margin of the new paragraph. + @param right The right margin of the new paragraph. + */ + virtual void beginParagraph(Qt::Alignment a = Qt::AlignLeft, qreal top = 0.0, + qreal bottom = 0.0, qreal left = 0.0, + qreal right = 0.0) + = 0; + + /** Close the paragraph in the markup. */ + virtual void endParagraph() = 0; + /** Add a newline to the markup. */ + virtual void addNewline() = 0; + + /** + Insert a horizontal rule into the markup. + @param width The width of the rule. Default is full width. + */ + virtual void insertHorizontalRule(int width = -1) = 0; + + /** + Insert a new image element into the markup. + @param url The url of the image + @param width The width of the image + @param height The height of the image. + */ + virtual void insertImage(const QString &url, qreal width, qreal height) = 0; + + /** + Begin a new list element in the markup. + A list element contains list items, and may contain other lists. + @param style The style of list to create. + */ + virtual void beginList(QTextListFormat::Style style) = 0; + + /** + Close the list. + */ + virtual void endList() = 0; + + /** Begin a new list item in the markup */ + virtual void beginListItem() = 0; + + /** End the list item */ + virtual void endListItem() = 0; + + /** Begin a superscript element */ + virtual void beginSuperscript() = 0; + + /** End superscript element */ + virtual void endSuperscript() = 0; + + /** Begin a subscript element */ + virtual void beginSubscript() = 0; + + /** End subscript element */ + virtual void endSubscript() = 0; + + /** + Begin a table element. + + @param cellpadding The padding attribute for the table. + @param cellspacing The spacing attribute for the table. + @param width The width of the table. May be either an integer, or a + percentage value. + */ + virtual void beginTable(qreal cellpadding, qreal cellspacing, + const QString &width) + = 0; + + /** + Begin a new table row + */ + virtual void beginTableRow() = 0; + + /** + Begin a new table header cell. + @param width The width of the cell. + @param colSpan The column span of the cell. + @param rowSpan The row span of the cell. + */ + virtual void beginTableHeaderCell(const QString &width, int colSpan, + int rowSpan) + = 0; + + /** + Begin a new table cell. + @param width The width of the cell. + @param colSpan The column span of the cell. + @param rowSpan The row span of the cell. + */ + virtual void beginTableCell(const QString &width, int colSpan, int rowSpan) + = 0; + + /** End a table element */ + virtual void endTable() = 0; + + /** End a table row */ + virtual void endTableRow() = 0; + + /** End a table header cell */ + virtual void endTableHeaderCell() = 0; + + /** End a table cell */ + virtual void endTableCell() = 0; + + /** + Begin a level @p level header + @param level An integer between 1 and 6 + */ + virtual void beginHeader(int level) = 0; + + /** + End a level @p level header + @param level An integer between 1 and 6 + */ + virtual void endHeader(int level) = 0; + + /** + Append the plain text @p text to the markup + + @param text The text to append. + */ + virtual void appendLiteralText(const QString &text) = 0; + + /** + Append the raw text @p text to the markup. @p text is added unescaped + */ + virtual void appendRawText(const QString &text) = 0; + + /** + Return the fully marked up result of the building process. + + This may contain metadata etc, such as a head element in html. + + @return The fully marked up text. + */ + virtual QString getResult() = 0; + + virtual void addSingleBreakLine() = 0; +}; +} + +#endif diff --git a/src/grantleebuilder/autotests/plaintextmarkupbuildertest.cpp b/src/grantleebuilder/autotests/plaintextmarkupbuildertest.cpp index 6eeef99..90be8d3 100644 --- a/src/grantleebuilder/autotests/plaintextmarkupbuildertest.cpp +++ b/src/grantleebuilder/autotests/plaintextmarkupbuildertest.cpp @@ -1,75 +1,76 @@ /* Copyright (C) 2020 Laurent Montel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "plaintextmarkupbuildertest.h" #include +#include #include #include #include QTEST_MAIN(PlainTextMarkupBuilderTest) PlainTextMarkupBuilderTest::PlainTextMarkupBuilderTest(QObject *parent) : QObject(parent) { } void PlainTextMarkupBuilderTest::testPlainText_data() { QTest::addColumn("text"); QTest::addColumn("regexpText"); QTest::newRow("text with espace at begin") << QString() << QString(); QTest::newRow("bold") << QStringLiteral("This text is bold.") << QStringLiteral("^This \\*text\\* is bold.\\n$"); QTest::newRow("double format") << QStringLiteral("Some formatted text.") << QStringLiteral("^Some (\\*/|/\\*)formatted(\\*/|/\\*) text.\\n$"); QTest::newRow("italic") << QStringLiteral("This text is italic and bold.") << QStringLiteral("^This \\*text is /italic/ and\\* bold.\\n$"); QTest::newRow("double span") << QStringLiteral("Some formatted text.") << QStringLiteral("^Some formatted text.\\n$"); //FIXME bug is about id="..." QTest::newRow("bug345360") << QStringLiteral("
Dear User,

We have updated our Privacy Policy and Terms of Service. Please take a few minutes to read and understand them. To help you understand, we have summarized the changes and provided a comparison of the current and the previous versions.

These changes will take effect on April 19, 2015.  If you continue to use Zoho after April 19, 2015, you will be governed by the new Privacy Policy and Terms of Service.

ddd     
") << QStringLiteral("foo"); } void PlainTextMarkupBuilderTest::testPlainText() { QFETCH(QString, text); QFETCH(QString, regexpText); QEXPECT_FAIL("bug345360", "Currently it failed", Continue); auto doc = new QTextDocument(); doc->setHtml(text); auto hb = new KPIMTextEdit::PlainTextMarkupBuilder(); - auto md = new Grantlee::MarkupDirector(hb); + auto md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); const auto result = hb->getResult(); const QRegularExpression regex(regexpText); const bool regexpHasResult = regex.match(result).hasMatch(); if (!regexpHasResult) { qDebug() << " result found " << result; } QVERIFY(regexpHasResult); delete md; delete hb; delete doc; } diff --git a/src/grantleebuilder/autotests/texthtmlbuildertest.cpp b/src/grantleebuilder/autotests/texthtmlbuildertest.cpp index 3a1b759..55a2d90 100644 --- a/src/grantleebuilder/autotests/texthtmlbuildertest.cpp +++ b/src/grantleebuilder/autotests/texthtmlbuildertest.cpp @@ -1,837 +1,855 @@ /* Copyright (C) 2020 Laurent Montel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "texthtmlbuildertest.h" #include "grantleebuilder/texthtmlbuilder.h" #include "grantleebuilder/markupdirector.h" #include #include #include -#include QTEST_MAIN(TextHTMLBuilderTest) TextHTMLBuilderTest::TextHTMLBuilderTest(QObject *parent) : QObject(parent) { } void TextHTMLBuilderTest::testHtmlWithTab() { auto doc = new QTextDocument(this); QTextCursor cursor(doc); cursor.movePosition(QTextCursor::Start); cursor.insertText(QStringLiteral("\n")); cursor.insertText(QStringLiteral("\t")); cursor.insertText(QStringLiteral("foo")); auto hb = new KPIMTextEdit::TextHTMLBuilder(); auto md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); auto result = hb->getResult(); auto regex = QRegularExpression( QStringLiteral("^

 

    foo

\\n$")); const bool regexpHasResult = regex.match(result).hasMatch(); if (!regexpHasResult) { qDebug() << " result found " << result; } QVERIFY(regexpHasResult); delete md; delete hb; delete doc; } void TextHTMLBuilderTest::testHtmlText_data() { QTest::addColumn("text"); QTest::addColumn("regexpText"); QTest::addColumn("htmlFormat"); QTest::newRow("link") << QStringLiteral("A link to KDE.") << QStringLiteral("^

A link to KDE.

\\n$") << true; QTest::newRow("text with espace") << QStringLiteral(" foo") << QStringLiteral("^

         foo

\\n$") << false; QTest::newRow("text with espace at begin") << QStringLiteral(" foo") << QStringLiteral("^

 foo

\\n$") << false; } void TextHTMLBuilderTest::testHtmlText() { QFETCH(QString, text); QFETCH(QString, regexpText); QFETCH(bool, htmlFormat); auto doc = new QTextDocument(); if (htmlFormat) { doc->setHtml(text); } else { doc->setPlainText(text); } auto hb = new KPIMTextEdit::TextHTMLBuilder(); auto md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); const auto result = hb->getResult(); const QRegularExpression regex(regexpText); const bool regexpHasResult = regex.match(result).hasMatch(); if (!regexpHasResult) { qDebug() << " result found " << result; } QVERIFY(regexpHasResult); delete md; delete hb; delete doc; } void TextHTMLBuilderTest::testSingleFormat() { auto doc = new QTextDocument(); // One format doc->setHtml(QStringLiteral("This text is bold.")); auto hb = new KPIMTextEdit::TextHTMLBuilder(); auto md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); auto result = hb->getResult(); QRegularExpression regex( QStringLiteral("^

This text is bold.

\\n$")); QVERIFY(regex.match(result).hasMatch()); delete md; delete hb; delete doc; } void TextHTMLBuilderTest::testDoubleFormat() { auto doc = new QTextDocument(); // One format doc->setHtml(QStringLiteral("Some formatted text.")); auto hb = new KPIMTextEdit::TextHTMLBuilder(); auto md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); auto result = hb->getResult(); QRegularExpression regex( QStringLiteral("^

Some " "(|)formatted(|) text.

\\n$")); QVERIFY(regex.match(result).hasMatch()); delete md; delete hb; delete doc; } void TextHTMLBuilderTest::testAnchor() { auto doc = new QTextDocument(); doc->setHtml( QStringLiteral("A link to KDE.")); auto hb = new KPIMTextEdit::TextHTMLBuilder(); auto md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); auto result = hb->getResult(); QRegularExpression regex(QStringLiteral( "^

A link to KDE.

\\n$")); QVERIFY(regex.match(result).hasMatch()); delete md; delete hb; delete doc; } void TextHTMLBuilderTest::testAnchorWithFormattedContent() { auto doc = new QTextDocument(); doc->setHtml(QStringLiteral( "A formatted link to KDE.")); auto hb = new KPIMTextEdit::TextHTMLBuilder(); auto md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); auto result = hb->getResult(); QRegularExpression regex(QStringLiteral( "^

A formatted " "link to KDE.

\\n$")); QVERIFY(regex.match(result).hasMatch()); delete md; delete hb; delete doc; } void TextHTMLBuilderTest::testAdjacentAnchors() { auto doc = new QTextDocument(); doc->setHtml( QStringLiteral("Two linksnext to eachother.")); auto hb = new KPIMTextEdit::TextHTMLBuilder(); auto md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); auto result = hb->getResult(); QRegularExpression regex(QStringLiteral( "^

Two linksnext to eachother.

\\n$")); QVERIFY(regex.match(result).hasMatch()); delete md; delete hb; delete doc; } void TextHTMLBuilderTest::testNestedFormatting() { auto doc = new QTextDocument(); doc->setHtml(QStringLiteral("This text is italic and bold.")); auto hb = new KPIMTextEdit::TextHTMLBuilder(); auto md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); auto result = hb->getResult(); QRegularExpression regex(QStringLiteral( "^

This text is italic and bold.

\\n$")); QVERIFY(regex.match(result).hasMatch()); delete md; delete hb; delete doc; } void TextHTMLBuilderTest::testSpan() { auto doc = new QTextDocument(); doc->setHtml(QStringLiteral( "Some formatted text.")); auto hb = new KPIMTextEdit::TextHTMLBuilder(); auto md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); auto result = hb->getResult(); auto regex = QRegularExpression( QStringLiteral("^

Some formatted " "text.

\\n$")); QVERIFY(regex.match(result).hasMatch()); delete md; delete hb; delete doc; } void TextHTMLBuilderTest::testDoubleSpan() { auto doc = new QTextDocument(); doc->setHtml(QStringLiteral("Some formatted text.")); auto hb = new KPIMTextEdit::TextHTMLBuilder(); auto md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); auto result = hb->getResult(); auto regex = QRegularExpression(QStringLiteral( "^

Some formatted text.

\\n$")); QVERIFY(regex.match(result).hasMatch()); delete md; delete hb; delete doc; } void TextHTMLBuilderTest::testSpanNesting() { auto doc = new QTextDocument(); doc->setHtml(QStringLiteral( "Paragraph with some formatted nested text.")); auto hb = new KPIMTextEdit::TextHTMLBuilder(); auto md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); auto result = hb->getResult(); auto regex = QRegularExpression(QStringLiteral( "^

Paragraph with some formatted nested text.

\\n$")); QVERIFY(regex.match(result).hasMatch()); delete md; delete hb; delete doc; } void TextHTMLBuilderTest::testDoubleStartDifferentFinish() { auto doc = new QTextDocument(); doc->setHtml( QStringLiteral("Paragraph with some formatted text.")); auto hb = new KPIMTextEdit::TextHTMLBuilder(); auto md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); auto result = hb->getResult(); auto regex = QRegularExpression( QStringLiteral("^

Paragraph with some " "formatted text.

\\n$")); QVERIFY(regex.match(result).hasMatch()); delete md; delete hb; delete doc; } void TextHTMLBuilderTest::testDoubleStartDifferentFinishReverseOrder() { auto doc = new QTextDocument(); doc->setHtml( QStringLiteral("Paragraph with some formatted text.")); auto hb = new KPIMTextEdit::TextHTMLBuilder(); auto md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); auto result = hb->getResult(); auto regex = QRegularExpression( QStringLiteral("^

Paragraph with some " "formatted text.

\\n$")); QVERIFY(regex.match(result).hasMatch()); delete md; delete hb; delete doc; } void TextHTMLBuilderTest::testDifferentStartDoubleFinish() { auto doc = new QTextDocument(); doc->setHtml( QStringLiteral("Paragraph with some formatted text.")); auto hb = new KPIMTextEdit::TextHTMLBuilder(); auto md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); auto result = hb->getResult(); auto regex = QRegularExpression( QStringLiteral("^

Paragraph with some " "formatted text.

\\n$")); QVERIFY(regex.match(result).hasMatch()); delete md; delete hb; delete doc; } void TextHTMLBuilderTest::testDifferentStartDoubleFinishReverseOrder() { auto doc = new QTextDocument(); doc->setHtml( QStringLiteral("Paragraph with some formatted text.")); auto hb = new KPIMTextEdit::TextHTMLBuilder(); auto md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); auto result = hb->getResult(); auto regex = QRegularExpression( QStringLiteral("^

Paragraph with some " "formatted text.

\\n$")); QVERIFY(regex.match(result).hasMatch()); delete md; delete hb; delete doc; } void TextHTMLBuilderTest::testOverlap() { auto doc = new QTextDocument(); doc->setHtml(QStringLiteral( "Paragraph with some formatted text.")); auto hb = new KPIMTextEdit::TextHTMLBuilder(); auto md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); auto result = hb->getResult(); auto regex = QRegularExpression( QStringLiteral("^

Paragraph with some " "formatted text.

\\n$")); QVERIFY(regex.match(result).hasMatch()); delete md; delete hb; delete doc; } void TextHTMLBuilderTest::testEdgeCaseLeft() { auto doc = new QTextDocument(); doc->setHtml(QStringLiteral("Paragraph with some formatted text.")); auto hb = new KPIMTextEdit::TextHTMLBuilder(); auto md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); auto result = hb->getResult(); auto regex = QRegularExpression(QStringLiteral( "^

Paragraph with some formatted text.

\\n$")); QVERIFY(regex.match(result).hasMatch()); delete md; delete hb; delete doc; } void TextHTMLBuilderTest::testEdgeCaseRight() { auto doc = new QTextDocument(); doc->setHtml(QStringLiteral("Paragraph with some formatted text.")); auto hb = new KPIMTextEdit::TextHTMLBuilder(); auto md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); auto result = hb->getResult(); auto regex = QRegularExpression(QStringLiteral( "^

Paragraph with some formatted text.

\\n$")); QVERIFY(regex.match(result).hasMatch()); delete md; delete hb; delete doc; } void TextHTMLBuilderTest::testImage() { auto doc = new QTextDocument(); doc->setHtml( QStringLiteral("Paragraph with an inline image.")); auto hb = new KPIMTextEdit::TextHTMLBuilder(); auto md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); auto result = hb->getResult(); auto regex = QRegularExpression( QStringLiteral("^

Paragraph with an inline  image.

\\n$")); QVERIFY(regex.match(result).hasMatch()); delete md; delete hb; delete doc; } void TextHTMLBuilderTest::testImageResized() { QString result; QRegularExpression regex; auto doc = new QTextDocument(); // width doc->setHtml(QStringLiteral( "Paragraph with an inline image.")); auto hb = new KPIMTextEdit::TextHTMLBuilder(); auto md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); result = hb->getResult(); regex = QRegularExpression(QStringLiteral( "^

Paragraph with an inline  image.

\\n$")); QVERIFY(regex.match(result).hasMatch()); // height doc->setHtml(QStringLiteral( "Paragraph with an inline image.")); hb = new KPIMTextEdit::TextHTMLBuilder(); md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); result = hb->getResult(); regex = QRegularExpression(QStringLiteral( "^

Paragraph with an inline  image.

\\n$")); QVERIFY(regex.match(result).hasMatch()); // height and width doc->setHtml(QStringLiteral( "Paragraph with an inline image.")); hb = new KPIMTextEdit::TextHTMLBuilder(); md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); result = hb->getResult(); regex = QRegularExpression(QStringLiteral( "^

Paragraph with an inline  image.

\\n$")); QVERIFY(regex.match(result).hasMatch()); delete md; delete hb; delete doc; } void TextHTMLBuilderTest::testEachFormatTagSingly() { QString result; QRegularExpression regex; auto doc = new QTextDocument(); // Test bold doc->setHtml(QStringLiteral("Some formatted text.")); auto hb = new KPIMTextEdit::TextHTMLBuilder(); auto md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); result = hb->getResult(); regex = QRegularExpression( QStringLiteral("^

Some formatted text.

\\n$")); QVERIFY(regex.match(result).hasMatch()); // Test Italic doc->setHtml(QStringLiteral("Some formatted text.")); hb = new KPIMTextEdit::TextHTMLBuilder(); md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); result = hb->getResult(); regex = QRegularExpression( QStringLiteral("^

Some formatted text.

\\n$")); QVERIFY(regex.match(result).hasMatch()); // Test Underline doc->setHtml(QStringLiteral("Some formatted text.")); hb = new KPIMTextEdit::TextHTMLBuilder(); md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); result = hb->getResult(); regex = QRegularExpression( QStringLiteral("^

Some formatted text.

\\n$")); QVERIFY(regex.match(result).hasMatch()); // Test Strikeout doc->setHtml(QStringLiteral("Some formatted text.")); hb = new KPIMTextEdit::TextHTMLBuilder(); md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); result = hb->getResult(); regex = QRegularExpression( QStringLiteral("^

Some formatted text.

\\n$")); QVERIFY(regex.match(result).hasMatch()); // Test Superscript doc->setHtml(QStringLiteral("Some formatted text.")); hb = new KPIMTextEdit::TextHTMLBuilder(); md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); result = hb->getResult(); regex = QRegularExpression( QStringLiteral("^

Some formatted text.

\\n$")); QVERIFY(regex.match(result).hasMatch()); // Test Subscript doc->setHtml(QStringLiteral("Some formatted text.")); hb = new KPIMTextEdit::TextHTMLBuilder(); md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); result = hb->getResult(); regex = QRegularExpression( QStringLiteral("^

Some formatted text.

\\n$")); QVERIFY(regex.match(result).hasMatch()); // Test Foreground doc->setHtml(QStringLiteral( "Some formatted text.")); hb = new KPIMTextEdit::TextHTMLBuilder(); md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); result = hb->getResult(); regex = QRegularExpression( QStringLiteral("^

Some formatted " "text.

\\n$")); QVERIFY(regex.match(result).hasMatch()); // Test Background doc->setHtml(QStringLiteral( "Some formatted text.")); hb = new KPIMTextEdit::TextHTMLBuilder(); md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); result = hb->getResult(); regex = QRegularExpression(QStringLiteral( "^

Some formatted " "text.

\\n$")); QVERIFY(regex.match(result).hasMatch()); // Test Font Family doc->setHtml(QStringLiteral( "Some formatted text.")); hb = new KPIMTextEdit::TextHTMLBuilder(); md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); result = hb->getResult(); regex = QRegularExpression(QStringLiteral( "^

Some formatted " "text.

\\n$")); QVERIFY(regex.match(result).hasMatch()); // Test Font Size doc->setHtml(QStringLiteral( "Some formatted text.")); hb = new KPIMTextEdit::TextHTMLBuilder(); md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); result = hb->getResult(); regex = QRegularExpression(QStringLiteral( "^

Some formatted " "text.

\\n$")); QVERIFY(regex.match(result).hasMatch()); delete md; delete hb; delete doc; } void TextHTMLBuilderTest::testHorizontalRule() { auto doc = new QTextDocument(); doc->setHtml( QStringLiteral("

Foo


Bar

")); auto hb = new KPIMTextEdit::TextHTMLBuilder(); auto md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); auto result = hb->getResult(); auto regex = QRegularExpression( QStringLiteral("^

Foo

\\n
\\n

Bar

\\n$")); QVERIFY(regex.match(result).hasMatch()); delete md; delete hb; delete doc; } void TextHTMLBuilderTest::testNewlines() { auto doc = new QTextDocument(); doc->setHtml(QStringLiteral("

Foo

\n

Bar

")); auto hb = new KPIMTextEdit::TextHTMLBuilder(); auto md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); auto result = hb->getResult(); auto regex = QRegularExpression( QStringLiteral("^

Foo

\\n

 

 

\\n

Bar

\\n$")); QVERIFY(regex.match(result).hasMatch()); delete md; delete hb; delete doc; } void TextHTMLBuilderTest::testNewlinesThroughQTextCursor() { auto doc = new QTextDocument(this); QTextCursor cursor(doc); cursor.movePosition(QTextCursor::Start); cursor.insertText(QStringLiteral("Foo")); cursor.insertText(QStringLiteral("\n")); cursor.insertText(QStringLiteral("\n")); cursor.insertText(QStringLiteral("\n")); cursor.insertText(QStringLiteral("Bar")); auto hb = new KPIMTextEdit::TextHTMLBuilder(); auto md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); auto result = hb->getResult(); auto regex = QRegularExpression( QStringLiteral("^

Foo

\\n

 

 

Bar

\\n$")); QVERIFY(regex.match(result).hasMatch()); delete md; delete hb; delete doc; } void TextHTMLBuilderTest::testInsertImage() { auto doc = new QTextDocument(this); QTextCursor cursor(doc); cursor.movePosition(QTextCursor::Start); cursor.insertText(QStringLiteral("Foo")); cursor.insertText(QStringLiteral("\n")); cursor.insertText(QStringLiteral("\n")); cursor.insertText(QStringLiteral("\n")); cursor.insertText(QStringLiteral("Bar")); const QString imagePath = QStringLiteral(GRANTLEEBUILDER_DIR "/object-fill.svg"); const QImage image(imagePath); QString imageNameToAdd = QStringLiteral("imagename"); doc->addResource(QTextDocument::ImageResource, QUrl(imageNameToAdd), image); cursor.insertImage(imageNameToAdd); auto hb = new KPIMTextEdit::TextHTMLBuilder(); auto md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); auto result = hb->getResult(); auto regex = QRegularExpression( QStringLiteral("^

Foo

\\n

 

 

Bar

\\n$")); QVERIFY(regex.match(result).hasMatch()); delete md; delete hb; delete doc; } void TextHTMLBuilderTest::testInsertImageWithSize() { auto doc = new QTextDocument(this); QTextCursor cursor(doc); cursor.movePosition(QTextCursor::Start); cursor.insertText(QStringLiteral("Foo")); cursor.insertText(QStringLiteral("\n")); cursor.insertText(QStringLiteral("\n")); cursor.insertText(QStringLiteral("\n")); cursor.insertText(QStringLiteral("Bar")); const QString imagePath = QStringLiteral(GRANTLEEBUILDER_DIR "/object-fill.svg"); const QImage image(imagePath); QString imageNameToAdd = QStringLiteral("imagename"); doc->addResource(QTextDocument::ImageResource, QUrl(imageNameToAdd), image); QTextImageFormat format; format.setName(imageNameToAdd); format.setWidth(100); format.setHeight(120); cursor.insertImage(format); auto hb = new KPIMTextEdit::TextHTMLBuilder(); auto md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); auto result = hb->getResult(); auto regex = QRegularExpression( QStringLiteral("^

Foo

\\n

 

 

Bar

\\n$")); QVERIFY(regex.match(result).hasMatch()); delete md; delete hb; delete doc; } void TextHTMLBuilderTest::testTitle1() { const int boundedLevel = 1; auto doc = new QTextDocument(this); QTextCursor cursor(doc); cursor.movePosition(QTextCursor::Start); cursor.insertText(QStringLiteral("Foo")); const int sizeAdjustment = boundedLevel > 0 ? 5 - boundedLevel : 0; QTextBlockFormat blkfmt; blkfmt.setHeadingLevel(boundedLevel); cursor.mergeBlockFormat(blkfmt); QTextCharFormat chrfmt; chrfmt.setFontWeight(boundedLevel > 0 ? QFont::Bold : QFont::Normal); chrfmt.setProperty(QTextFormat::FontSizeAdjustment, sizeAdjustment); QTextCursor selectCursor = cursor; QTextCursor top = selectCursor; top.setPosition(qMin(top.anchor(), top.position())); top.movePosition(QTextCursor::StartOfBlock); QTextCursor bottom = selectCursor; bottom.setPosition(qMax(bottom.anchor(), bottom.position())); bottom.movePosition(QTextCursor::EndOfBlock); selectCursor.setPosition(top.position(), QTextCursor::MoveAnchor); selectCursor.setPosition(bottom.position(), QTextCursor::KeepAnchor); selectCursor.mergeCharFormat(chrfmt); cursor.mergeBlockCharFormat(chrfmt); auto hb = new KPIMTextEdit::TextHTMLBuilder(); auto md = new KPIMTextEdit::MarkupDirector(hb); md->processDocument(doc); auto result = hb->getResult(); auto regex = QRegularExpression( QStringLiteral("^

Foo

\n$")); - qDebug() << " result : " << result; + //qDebug() << " result " << result; //TODO implement header support now. QVERIFY(regex.match(result).hasMatch()); delete md; delete hb; delete doc; } + +void TextHTMLBuilderTest::testBug421908() +{ + auto doc = new QTextDocument(); + doc->setHtml(QStringLiteral("

some colored text
some colored text

")); + + auto hb = new KPIMTextEdit::TextHTMLBuilder(); + auto md = new KPIMTextEdit::MarkupDirector(hb); + md->processDocument(doc); + auto result = hb->getResult(); + + auto regex = QRegularExpression( + QStringLiteral("^

some colored text
some colored text

\n$")); + QVERIFY(regex.match(result).hasMatch()); + delete md; + delete hb; + delete doc; + +} diff --git a/src/grantleebuilder/autotests/texthtmlbuildertest.h b/src/grantleebuilder/autotests/texthtmlbuildertest.h index d739b7b..30c36d7 100644 --- a/src/grantleebuilder/autotests/texthtmlbuildertest.h +++ b/src/grantleebuilder/autotests/texthtmlbuildertest.h @@ -1,62 +1,64 @@ /* Copyright (C) 2020 Laurent Montel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef TEXTHTMLBUILDERTEST_H #define TEXTHTMLBUILDERTEST_H #include class TextHTMLBuilderTest : public QObject { Q_OBJECT public: explicit TextHTMLBuilderTest(QObject *parent = nullptr); ~TextHTMLBuilderTest() = default; private Q_SLOTS: void testHtmlText_data(); void testHtmlText(); void testHtmlWithTab(); void testSingleFormat(); void testDoubleFormat(); void testDoubleStartDifferentFinish(); void testDoubleStartDifferentFinishReverseOrder(); void testDifferentStartDoubleFinish(); void testDifferentStartDoubleFinishReverseOrder(); void testOverlap(); void testAnchor(); void testAnchorWithFormattedContent(); void testAdjacentAnchors(); void testNestedFormatting(); void testSpan(); void testDoubleSpan(); void testSpanNesting(); void testEdgeCaseLeft(); void testEdgeCaseRight(); void testImage(); void testImageResized(); void testEachFormatTagSingly(); void testHorizontalRule(); void testNewlines(); void testNewlinesThroughQTextCursor(); void testInsertImage(); void testInsertImageWithSize(); + void testTitle1(); + void testBug421908(); }; #endif // TEXTHTMLBUILDERTEST_H diff --git a/src/grantleebuilder/markupdirector.cpp b/src/grantleebuilder/markupdirector.cpp index 5dda1a2..7f894e1 100644 --- a/src/grantleebuilder/markupdirector.cpp +++ b/src/grantleebuilder/markupdirector.cpp @@ -1,923 +1,923 @@ /* Copyright (c) 2019-2020 Montel Laurent This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "markupdirector.h" #include "markupdirector_p.h" -#include +#include "abstractmarkupbuilder.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KPIMTextEdit; -MarkupDirector::MarkupDirector(Grantlee::AbstractMarkupBuilder *builder) +MarkupDirector::MarkupDirector(KPIMTextEdit::AbstractMarkupBuilder *builder) : d_ptr(new MarkupDirectorPrivate(this)) , m_builder(builder) { } MarkupDirector::~MarkupDirector() { delete d_ptr; } //#define ADD_HEADER_SUPPORT 1 QTextFrame::iterator MarkupDirector::processBlockContents(QTextFrame::iterator frameIt, const QTextBlock &block) { //Same code as grantlee but interprete margin const auto blockFormat = block.blockFormat(); const auto blockAlignment = blockFormat.alignment(); // TODO: decide when to use

etc. #ifdef ADD_HEADER_SUPPORT if (blockFormat.headingLevel() > 0) { //Header qDebug() << " header " << blockFormat.headingLevel(); m_builder->beginHeader(blockFormat.headingLevel()); } #endif if (blockFormat.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)) { m_builder->insertHorizontalRule(); if (!frameIt.atEnd()) { return ++frameIt; } return frameIt; } auto it = block.begin(); // The beginning is the end. This is an empty block. Insert a newline and // move // on. if (it.atEnd()) { m_builder->addNewline(); if (!frameIt.atEnd()) { return ++frameIt; } return frameIt; } // Don't have p tags inside li tags. if (!block.textList()) { //Laurent : we need this margin as it's necessary to show blockquote // Don't instruct builders to use margins. The rich text widget doesn't // have // an action for them yet, // So users can't edit them. See bug // https://bugs.kde.org/show_bug.cgi?id=160600 m_builder->beginParagraph( blockAlignment, blockFormat.topMargin(), blockFormat.bottomMargin(), blockFormat.leftMargin(), blockFormat.rightMargin() ); } while (!it.atEnd()) { it = processFragment(it, it.fragment(), block.document()); } // Don't have p tags inside li tags. if (!block.textList()) { m_builder->endParagraph(); } if (!frameIt.atEnd()) { return ++frameIt; } return frameIt; } QTextBlock::iterator MarkupDirector::processFragment(QTextBlock::iterator it, const QTextFragment &fragment, QTextDocument const *doc) { //Same code as Grantlee + a fix ! // Q_D( MarkupDirector ); const auto charFormat = fragment.charFormat(); //Need to check if it's a image format. if (charFormat.isImageFormat()) { const auto imageFormat = charFormat.toImageFormat(); return processImage(it, imageFormat, doc); } if (charFormat.objectType() >= QTextFormat::UserObject) { processCustomFragment(fragment, doc); if (!it.atEnd()) { return ++it; } return it; } auto textObject = doc->objectForFormat(charFormat); if (textObject) { return processCharTextObject(it, fragment, textObject); } if (fragment.text().at(0).category() == QChar::Separator_Line) { m_builder->addNewline(); if (!it.atEnd()) { return ++it; } return it; } // The order of closing and opening tags can determine whether generated // html // is valid or not. // When processing a document with formatting which appears as // 'Some // formatted text', // the correct generated output will contain 'Some // formatted text'. // However, processing text which appears as 'Some formatted // text' might be incorrectly rendered // as 'Some formatted text' if tags which start at // the same fragment are // opened out of order. Here, tags are not nested properly, and the html // would // not be valid or render correctly by unforgiving parsers (like QTextEdit). // One solution is to make the order of opening tags dynamic. In the above // case, the em tag would // be opened before the strong tag 'Some formatted // text'. That would // require knowledge of which tag is going to close first. That might be // possible by examining // the 'next' QTextFragment while processing one. // // The other option is to do pessimistic closing of tags. // In the above case, this means that if a fragment has two or more formats // applied (bold and italic here), // and one of them is closed, then all tags should be closed first. They // will // of course be reopened // if necessary while processing the next fragment. // The above case would be rendered as 'Some // formatted text'. // // The first option is taken here, as the redundant opening and closing tags // in the second option // didn't appeal. // See testDoubleStartDifferentFinish, // testDoubleStartDifferentFinishReverseOrder processOpeningElements(it); // If a sequence such as '

' is imported into a document with // setHtml, LineSeparator // characters are inserted. Here I make sure to put them back. auto sl = fragment.text().split(QChar(QChar::LineSeparator)); QStringListIterator i(sl); auto paraClosed = false; while (i.hasNext()) { m_builder->appendLiteralText(i.next()); if (i.hasNext()) { if (i.peekNext().isEmpty()) { if (!paraClosed) { m_builder->endParagraph(); paraClosed = true; } m_builder->addNewline(); } else if (paraClosed) { m_builder->beginParagraph(/* blockAlignment */); paraClosed = false; } else { - //Bug fixing : missing end line here - m_builder->addNewline(); + //Bug fixing : add missing single break line + m_builder->addSingleBreakLine(); } } } if (!it.atEnd()) { ++it; } processClosingElements(it); return it; } void MarkupDirector::processDocumentContents(QTextFrame::iterator start, QTextFrame::iterator end) { while (!start.atEnd() && start != end) { auto frame = start.currentFrame(); if (frame) { auto table = qobject_cast(frame); if (table) { start = processTable(start, table); } else { start = processFrame(start, frame); } } else { auto block = start.currentBlock(); Q_ASSERT(block.isValid()); start = processBlock(start, block); } } } QTextFrame::iterator MarkupDirector::processFrame(QTextFrame::iterator it, QTextFrame *frame) { if (frame) { processDocumentContents(frame->begin(), frame->end()); } if (!it.atEnd()) return ++it; return it; } QTextFrame::iterator MarkupDirector::processBlock(QTextFrame::iterator it, const QTextBlock &block) { if (block.isValid()) { auto fmt = block.blockFormat(); auto object = block.document()->objectForFormat(fmt); if (object) { return processObject(it, block, object); } else { return processBlockContents(it, block); } } if (!it.atEnd()) return ++it; return it; } QTextFrame::iterator MarkupDirector::processTable(QTextFrame::iterator it, QTextTable *table) { const auto format = table->format(); const auto colLengths = format.columnWidthConstraints(); const auto tableWidth = format.width(); QString sWidth; if (tableWidth.type() == QTextLength::PercentageLength) { sWidth = QStringLiteral("%1%"); sWidth = sWidth.arg(tableWidth.rawValue()); } else if (tableWidth.type() == QTextLength::FixedLength) { sWidth = QStringLiteral("%1"); sWidth = sWidth.arg(tableWidth.rawValue()); } m_builder->beginTable(format.cellPadding(), format.cellSpacing(), sWidth); const auto headerRowCount = format.headerRowCount(); QList alreadyProcessedCells; for (int row = 0, total = table->rows(); row < total; ++row) { // Put a thead element around here somewhere? // if (row < headerRowCount) // { // beginTableHeader(); // } m_builder->beginTableRow(); // Header attribute should really be on cells, not determined by number // of // rows. // http://www.webdesignfromscratch.com/html-tables.cfm for (int column = 0, total = table->columns(); column < total; ++column) { auto tableCell = table->cellAt(row, column); auto columnSpan = tableCell.columnSpan(); auto rowSpan = tableCell.rowSpan(); if ((rowSpan > 1) || (columnSpan > 1)) { if (alreadyProcessedCells.contains(tableCell)) { // Already processed this cell. Move on. continue; } else { alreadyProcessedCells.append(tableCell); } } auto cellWidth = colLengths.at(column); QString sCellWidth; if (cellWidth.type() == QTextLength::PercentageLength) { sCellWidth = QStringLiteral("%1%").arg(cellWidth.rawValue()); } else if (cellWidth.type() == QTextLength::FixedLength) { sCellWidth = QStringLiteral("%1").arg(cellWidth.rawValue()); } // TODO: Use THEAD instead if (row < headerRowCount) { m_builder->beginTableHeaderCell(sCellWidth, columnSpan, rowSpan); } else { m_builder->beginTableCell(sCellWidth, columnSpan, rowSpan); } processTableCell(tableCell, table); if (row < headerRowCount) { m_builder->endTableHeaderCell(); } else { m_builder->endTableCell(); } } m_builder->endTableRow(); } m_builder->endTable(); if (!it.atEnd()) return ++it; return it; } void MarkupDirector::processTableCell(const QTextTableCell &tableCell, QTextTable *table) { Q_UNUSED(table) processDocumentContents(tableCell.begin(), tableCell.end()); } QPair MarkupDirector::processList(QTextFrame::iterator it, const QTextBlock &_block, QTextList *list) { const auto style = list->format().style(); m_builder->beginList(style); auto block = _block; while (block.isValid() && block.textList()) { m_builder->beginListItem(); processBlockContents(it, block); m_builder->endListItem(); if (!it.atEnd()) ++it; block = block.next(); if (block.isValid()) { auto obj = block.document()->objectForFormat(block.blockFormat()); auto group = qobject_cast(obj); if (group && group != list) { auto pair = processBlockGroup(it, block, group); it = pair.first; block = pair.second; } } } m_builder->endList(); return qMakePair(it, block); } void MarkupDirector::processCustomFragment(const QTextFragment &fragment, const QTextDocument *doc) { Q_UNUSED(fragment) Q_UNUSED(doc) } QTextFrame::iterator MarkupDirector::processObject(QTextFrame::iterator it, const QTextBlock &block, QTextObject *object) { auto group = qobject_cast(object); if (group) { return processBlockGroup(it, block, group).first; } if (!it.atEnd()) return ++it; return it; } QPair MarkupDirector::skipBlockGroup(QTextFrame::iterator it, const QTextBlock &_block, QTextBlockGroup *blockGroup) { auto block = _block; auto lastBlock = _block; auto lastIt = it; auto obj = block.document()->objectForFormat(block.blockFormat()); QTextBlockGroup *nextGroup; if (!obj) return qMakePair(lastIt, lastBlock); auto group = qobject_cast(obj); if (!group) return qMakePair(lastIt, lastBlock); while (block.isValid()) { if (!group) break; block = block.next(); if (!it.atEnd()) ++it; obj = block.document()->objectForFormat(block.blockFormat()); if (obj) continue; nextGroup = qobject_cast(obj); if (group == blockGroup || !nextGroup) { lastBlock = block; lastIt = it; } group = nextGroup; } return qMakePair(lastIt, lastBlock); } QPair MarkupDirector::processBlockGroup(QTextFrame::iterator it, const QTextBlock &block, QTextBlockGroup *blockGroup) { const auto list = qobject_cast(blockGroup); if (list) { return processList(it, block, list); } return skipBlockGroup(it, block, blockGroup); } void MarkupDirector::processDocument(QTextDocument *doc) { processFrame(QTextFrame::iterator(), doc->rootFrame()); } QTextBlock::iterator MarkupDirector::processCharTextObject(QTextBlock::iterator it, const QTextFragment &fragment, QTextObject *textObject) { const auto fragmentFormat = fragment.charFormat(); if (fragmentFormat.isImageFormat()) { const auto imageFormat = fragmentFormat.toImageFormat(); return processImage(it, imageFormat, textObject->document()); } if (!it.atEnd()) return ++it; return it; } QTextBlock::iterator MarkupDirector::processImage(QTextBlock::iterator it, const QTextImageFormat &imageFormat, const QTextDocument *doc) { Q_UNUSED(doc) // TODO: Close any open format elements? m_builder->insertImage(imageFormat.name(), imageFormat.width(), imageFormat.height()); if (!it.atEnd()) return ++it; return it; } void MarkupDirector::processClosingElements(QTextBlock::iterator it) { Q_D(MarkupDirector); // The order of closing elements is determined by the order they were opened // in. // The order of opened elements is in the openElements member list. // see testDifferentStartDoubleFinish and // testDifferentStartDoubleFinishReverseOrder if (d->m_openElements.isEmpty()) return; auto elementsToClose = getElementsToClose(it); int previousSize; auto remainingSize = elementsToClose.size(); while (!elementsToClose.isEmpty()) { auto tag = d->m_openElements.last(); if (elementsToClose.contains(tag)) { switch (tag) { case Strong: m_builder->endStrong(); break; case Emph: m_builder->endEmph(); break; case Underline: m_builder->endUnderline(); break; case StrikeOut: m_builder->endStrikeout(); break; case SpanFontPointSize: m_builder->endFontPointSize(); break; case SpanFontFamily: m_builder->endFontFamily(); break; case SpanBackground: m_builder->endBackground(); break; case SpanForeground: m_builder->endForeground(); break; case Anchor: m_builder->endAnchor(); break; case SubScript: m_builder->endSubscript(); break; case SuperScript: m_builder->endSuperscript(); break; default: break; } d->m_openElements.removeLast(); elementsToClose.remove(tag); } previousSize = remainingSize; remainingSize = elementsToClose.size(); if (previousSize == remainingSize) { // Iterated once through without closing any tags. // This means that there's overlap in the tags, such as // 'text with some formatting tags' // See testOverlap. // The top element in openElements must be a blocker, so close it on // next // iteration. elementsToClose.insert(d->m_openElements.last()); } } } void MarkupDirector::processOpeningElements(QTextBlock::iterator it) { Q_D(MarkupDirector); auto fragment = it.fragment(); if (!fragment.isValid()) return; const auto fragmentFormat = fragment.charFormat(); const auto elementsToOpenList = getElementsToOpen(it); for (int tag : elementsToOpenList) { switch (tag) { case Strong: m_builder->beginStrong(); break; case Emph: m_builder->beginEmph(); break; case Underline: m_builder->beginUnderline(); break; case StrikeOut: m_builder->beginStrikeout(); break; case SpanFontPointSize: m_builder->beginFontPointSize(fragmentFormat.font().pointSize()); d->m_openFontPointSize = fragmentFormat.font().pointSize(); break; case SpanFontFamily: m_builder->beginFontFamily(fragmentFormat.fontFamily()); d->m_openFontFamily = fragmentFormat.fontFamily(); break; case SpanBackground: m_builder->beginBackground(fragmentFormat.background()); d->m_openBackground = fragmentFormat.background(); break; case SpanForeground: m_builder->beginForeground(fragmentFormat.foreground()); d->m_openForeground = fragmentFormat.foreground(); break; case Anchor: { // TODO: Multiple anchor names here. auto anchorNames = fragmentFormat.anchorNames(); if (!anchorNames.isEmpty()) { while (!anchorNames.isEmpty()) { auto n = anchorNames.last(); anchorNames.removeLast(); if (anchorNames.isEmpty()) { // Doesn't matter if anchorHref is empty. m_builder->beginAnchor(fragmentFormat.anchorHref(), n); break; } else { // Empty tags allow multiple names for the same // section. m_builder->beginAnchor(QString(), n); m_builder->endAnchor(); } } } else { m_builder->beginAnchor(fragmentFormat.anchorHref()); } d->m_openAnchorHref = fragmentFormat.anchorHref(); break; } case SuperScript: m_builder->beginSuperscript(); break; case SubScript: m_builder->beginSubscript(); break; default: break; } d->m_openElements.append(tag); d->m_elementsToOpen.remove(tag); } } QSet MarkupDirector::getElementsToClose(QTextBlock::iterator it) const { Q_D(const MarkupDirector); QSet closedElements; if (it.atEnd()) { // End of block?. Close all open tags. const QList openElement = d->m_openElements; #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) auto elementsToClose = d->m_openElements.toSet(); #else auto elementsToClose = QSet(openElement.begin(), openElement.end()); #endif return elementsToClose.unite(d->m_elementsToOpen); } auto fragment = it.fragment(); if (!fragment.isValid()) return closedElements; const auto fragmentFormat = fragment.charFormat(); const auto fontWeight = fragmentFormat.fontWeight(); const auto fontItalic = fragmentFormat.fontItalic(); const auto fontUnderline = fragmentFormat.fontUnderline(); const auto fontStrikeout = fragmentFormat.fontStrikeOut(); const auto fontForeground = fragmentFormat.foreground(); const auto fontBackground = fragmentFormat.background(); const auto fontFamily = fragmentFormat.fontFamily(); const auto fontPointSize = fragmentFormat.font().pointSize(); const auto anchorHref = fragmentFormat.anchorHref(); const auto vAlign = fragmentFormat.verticalAlignment(); const auto superscript = (vAlign == QTextCharFormat::AlignSuperScript); const auto subscript = (vAlign == QTextCharFormat::AlignSubScript); if (!fontStrikeout && (d->m_openElements.contains(StrikeOut) || d->m_elementsToOpen.contains(StrikeOut))) { closedElements.insert(StrikeOut); } if (!fontUnderline && (d->m_openElements.contains(Underline) || d->m_elementsToOpen.contains(Underline)) && !(d->m_openElements.contains(Anchor) || d->m_elementsToOpen.contains(Anchor))) { closedElements.insert(Underline); } if (!fontItalic && (d->m_openElements.contains(Emph) || d->m_elementsToOpen.contains(Emph))) { closedElements.insert(Emph); } if (fontWeight != QFont::Bold && (d->m_openElements.contains(Strong) || d->m_elementsToOpen.contains(Strong))) { closedElements.insert(Strong); } if ((d->m_openElements.contains(SpanFontPointSize) || d->m_elementsToOpen.contains(SpanFontPointSize)) && (d->m_openFontPointSize != fontPointSize)) { closedElements.insert(SpanFontPointSize); } if ((d->m_openElements.contains(SpanFontFamily) || d->m_elementsToOpen.contains(SpanFontFamily)) && (d->m_openFontFamily != fontFamily)) { closedElements.insert(SpanFontFamily); } if ((d->m_openElements.contains(SpanBackground) && (d->m_openBackground != fontBackground)) || (d->m_elementsToOpen.contains(SpanBackground) && (d->m_backgroundToOpen != fontBackground))) { closedElements.insert(SpanBackground); } if ((d->m_openElements.contains(SpanForeground) && (d->m_openForeground != fontForeground)) || (d->m_elementsToOpen.contains(SpanForeground) && (d->m_foregroundToOpen != fontForeground))) { closedElements.insert(SpanForeground); } if ((d->m_openElements.contains(Anchor) && (d->m_openAnchorHref != anchorHref)) || (d->m_elementsToOpen.contains(Anchor) && (d->m_anchorHrefToOpen != anchorHref))) { closedElements.insert(Anchor); } if (!subscript && (d->m_openElements.contains(SubScript) || d->m_elementsToOpen.contains(SubScript))) { closedElements.insert(SubScript); } if (!superscript && (d->m_openElements.contains(SuperScript) || d->m_elementsToOpen.contains(SuperScript))) { closedElements.insert(SuperScript); } return closedElements; } QList MarkupDirector::getElementsToOpen(QTextBlock::iterator it) { Q_D(MarkupDirector); auto fragment = it.fragment(); if (!fragment.isValid()) { return QList(); } const auto fragmentFormat = fragment.charFormat(); const auto fontWeight = fragmentFormat.fontWeight(); const auto fontItalic = fragmentFormat.fontItalic(); const auto fontUnderline = fragmentFormat.fontUnderline(); const auto fontStrikeout = fragmentFormat.fontStrikeOut(); const auto fontForeground = fragmentFormat.foreground(); const auto fontBackground = fragmentFormat.background(); const auto fontFamily = fragmentFormat.fontFamily(); const auto fontPointSize = fragmentFormat.font().pointSize(); const auto anchorHref = fragmentFormat.anchorHref(); const auto vAlign = fragmentFormat.verticalAlignment(); const auto superscript = (vAlign == QTextCharFormat::AlignSuperScript); const auto subscript = (vAlign == QTextCharFormat::AlignSubScript); if (superscript && !(d->m_openElements.contains(SuperScript))) { d->m_elementsToOpen.insert(SuperScript); } if (subscript && !(d->m_openElements.contains(SubScript))) { d->m_elementsToOpen.insert(SubScript); } if (!anchorHref.isEmpty() && !(d->m_openElements.contains(Anchor)) && (d->m_openAnchorHref != anchorHref)) { d->m_elementsToOpen.insert(Anchor); d->m_anchorHrefToOpen = anchorHref; } if (fontForeground != Qt::NoBrush && !(d->m_openElements.contains(SpanForeground)) // Can only open one // foreground element // at a time. && (fontForeground != d->m_openForeground) && !((d->m_openElements.contains( Anchor) // Links can't have a foreground color. || d->m_elementsToOpen.contains(Anchor)))) { d->m_elementsToOpen.insert(SpanForeground); d->m_foregroundToOpen = fontForeground; } if (fontBackground != Qt::NoBrush && !(d->m_openElements.contains(SpanBackground)) && (fontBackground != d->m_openBackground)) { d->m_elementsToOpen.insert(SpanBackground); d->m_backgroundToOpen = fontBackground; } if (!fontFamily.isEmpty() && !(d->m_openElements.contains(SpanFontFamily)) && (fontFamily != d->m_openFontFamily)) { d->m_elementsToOpen.insert(SpanFontFamily); d->m_fontFamilyToOpen = fontFamily; } if ((QTextCharFormat().font().pointSize() != fontPointSize) // Different from the default. && !(d->m_openElements.contains(SpanFontPointSize)) && (fontPointSize != d->m_openFontPointSize)) { d->m_elementsToOpen.insert(SpanFontPointSize); d->m_fontPointSizeToOpen = fontPointSize; } // Only open a new bold tag if one is not already open. // eg, some mixed format should be as is, rather than // some mixed format if (fontWeight == QFont::Bold && !(d->m_openElements.contains(Strong))) { d->m_elementsToOpen.insert(Strong); } if (fontItalic && !(d->m_openElements.contains(Emph))) { d->m_elementsToOpen.insert(Emph); } if (fontUnderline && !(d->m_openElements.contains(Underline)) && !(d->m_openElements.contains(Anchor) || d->m_elementsToOpen.contains( Anchor)) // Can't change the underline state of a link. ) { d->m_elementsToOpen.insert(Underline); } if (fontStrikeout && !(d->m_openElements.contains(StrikeOut))) { d->m_elementsToOpen.insert(StrikeOut); } if (d->m_elementsToOpen.size() <= 1) { return d->m_elementsToOpen.values(); } return sortOpeningOrder(d->m_elementsToOpen, it); } QList MarkupDirector::sortOpeningOrder(QSet openingOrder, QTextBlock::iterator it) const { QList sortedOpenedElements; // This is an insertion sort in a way. elements in openingOrder are assumed // to // be out of order. // The rest of the block is traversed until there are no more elements to // sort, or the end is reached. while (openingOrder.count()) { if (!it.atEnd()) { it++; if (!it.atEnd()) { // Because I've iterated, this returns the elements that will // be closed by the next fragment. const auto elementsToClose = getElementsToClose(it); // The exact order these are opened in is irrelevant, as all // will be // closed on the same block. // See testDoubleFormat. for (int tag : elementsToClose) { if (openingOrder.remove(tag)) { sortedOpenedElements.prepend(tag); } } } } else { // End of block. Need to close all open elements. // Order irrelevant in this case. for (int tag : qAsConst(openingOrder)) { sortedOpenedElements.prepend(tag); } break; } } return sortedOpenedElements; } diff --git a/src/grantleebuilder/markupdirector.h b/src/grantleebuilder/markupdirector.h index 3925cbf..d73438f 100644 --- a/src/grantleebuilder/markupdirector.h +++ b/src/grantleebuilder/markupdirector.h @@ -1,305 +1,305 @@ /* Copyright (c) 2019-2020 Montel Laurent This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef MARKUPDIRECTOR_H #define MARKUPDIRECTOR_H -#include +#include "abstractmarkupbuilder.h" #include "kpimtextedit_export.h" #include #include class QTextTable; class QTextTableCell; class QTextList; class QTextCharFormat; namespace KPIMTextEdit { class MarkupDirectorPrivate; class AbstractMarkupBuilder; /// @headerfile markupdirector.h grantlee/markupdirector.h /** @brief Instructs a builder object to create markup output The **%MarkupDirector** is used with an implementation of AbstractMarkupBuilder to create a marked up document output. Usage can be quite simple. @code auto doc = editor->document(); // editor is a QTextEdit auto builder = new HTMLBuilder(); auto md = new MarkupDirector(builder); md->processDocument(doc); browser->setHtml(builder->getResult()); // browser is a QTextBrowser. @endcode Or with a different builder: @code auto builder = new PlainTextMarkupBuilder(); auto md = new MarkupDirector(builder); md->processDocument(doc); browser->setPlainText(builder->getResult()); @endcode The **%MarkupDirector** also provides API for processing just part of a QTextDocument, such as a QTextFrame or a QTextBlock. The appropriate method may then be called with an invalid iterator as appropriate. @code // ... Do some processing to get a QTextFrame. auto frame = getFrame(); auto builder = new PlainTextMarkupBuilder(); auto md = new MarkupDirector(builder); // Create output from only the frame. md->processFrame(QTextFrame::iterator(), frame); browser->setPlainText(builder->getResult()); @endcode The behaviour of the **%MarkupDirector** can be customized by subclassing. Support for custom types can also be added by implementing the @ref processCustomFragment method. @see @ref custom_qtextobject @author Stephen Kelly */ class KPIMTEXTEDIT_EXPORT MarkupDirector { public: /** Constructor */ - MarkupDirector(Grantlee::AbstractMarkupBuilder *builder); + explicit MarkupDirector(KPIMTextEdit::AbstractMarkupBuilder *builder); /** Destructor */ virtual ~MarkupDirector(); /** Constructs the output by directing the builder to create the markup. */ virtual void processDocument(QTextDocument *doc); /** Directs the builder to create output for the single @p frame. If calling this method directly, an invalid QTextFrame::iterator may be used. */ virtual QTextFrame::iterator processFrame(QTextFrame::iterator it, QTextFrame *frame); /** Directs the builder to create output for the single @p block. If calling this method directly, an invalid QTextFrame::iterator may be used. This method does not process the contents of the @p block, but uses the @ref processBlockContents method to do so. */ virtual QTextFrame::iterator processBlock(QTextFrame::iterator it, const QTextBlock &block); /** Directs the builder to create output for the single @p textObject. If calling this method directly, an invalid QTextFrame::iterator may be used. The block @p block is the container of the @p textObject. */ virtual QTextFrame::iterator processObject(QTextFrame::iterator it, const QTextBlock &block, QTextObject *textObject); /** Directs the builder to create output for the single @p textBlockGroup. If calling this method directly, an invalid QTextFrame::iterator may be used. The block @p block is the first block in the @p textBlockGroup. */ virtual QPair processBlockGroup(QTextFrame::iterator it, const QTextBlock &block, QTextBlockGroup *textBlockGroup); /** Directs the builder to create output for the single @p textList. If calling this method directly, an invalid QTextFrame::iterator may be used. The block @p block is the first block in the @p textList. */ virtual QPair processList(QTextFrame::iterator it, const QTextBlock &block, QTextList *textList); /** Directs the builder to create output for the contents of the single @p block. If calling this method directly, an invalid QTextFrame::iterator may be used. */ virtual QTextFrame::iterator processBlockContents(QTextFrame::iterator it, const QTextBlock &block); /** Hook for instructing the builder to create output for the @p fragemnt with a custom type. @p doc is the document the fragment is in. */ virtual void processCustomFragment(const QTextFragment &fragment, QTextDocument const *doc); /** Directs the builder to create output for the contents of the single @p fragment. If calling this method directly, an invalid QTextBlock::iterator may be used. @p doc is the document the fragment is in. */ virtual QTextBlock::iterator processFragment(QTextBlock::iterator it, const QTextFragment &fragment, QTextDocument const *doc); /** Directs the builder to create output for the contents of the single @p textObject. The @p textObject is represented in the QTextDocument with the QTextFragment @p fragment. If calling this method directly, an invalid QTextBlock::iterator may be used. */ virtual QTextBlock::iterator processCharTextObject(QTextBlock::iterator it, const QTextFragment &fragment, QTextObject *textObject); /** Directs the builder to create output for the image represented by the @p imageFormat. If calling this method directly, an invalid QTextBlock::iterator may be used. @p doc is the document the fragment is in. */ virtual QTextBlock::iterator processImage(QTextBlock::iterator it, const QTextImageFormat &imageFormat, QTextDocument const *doc); /** Directs the builder to create output for the contents of the single @p table. If calling this method directly, an invalid QTextFrame::iterator may be used. */ virtual QTextFrame::iterator processTable(QTextFrame::iterator it, QTextTable *table); /** Directs the builder to create output for the contents of the single @p tableCell. The tableCell is in the @p table. */ virtual void processTableCell(const QTextTableCell &tableCell, QTextTable *table); protected: /** Processes the document between @p begin and @p end */ void processDocumentContents(QTextFrame::iterator begin, QTextFrame::iterator end); /** Iterates the iterator @p it to the first block after @p blockGroup. @p _block is any block in the @p blockGroup. The return pair is the iterator pointing after the end of @p blockGroup and the first block after @p blockGroup. */ QPair skipBlockGroup(QTextFrame::iterator it, const QTextBlock &_block, QTextBlockGroup *blockGroup); /** Returns a list of tags contained in @p openingTags sorted so they can be opened in order and will be closed in the correct order. @p openingTags should be a set of tags opened at the fragment pointed to by @p it. */ QList sortOpeningOrder(QSet openingTags, QTextBlock::iterator it) const; /** Directs the builder to close the appropriate tags at the position of @p it. */ virtual void processClosingElements(QTextBlock::iterator it); /** Directs the builder to open the appropriate tags at the position of @p it. */ virtual void processOpeningElements(QTextBlock::iterator it); /** Returns the tags that should be closed at the position of @p it. */ virtual QSet getElementsToClose(QTextBlock::iterator it) const; /** Returns the tags that should be opened at the position of @p it. */ virtual QList getElementsToOpen(QTextBlock::iterator it); /** Flags for the tags that may be open. */ enum OpenElementValues { None = 0x0, /// No tags are open SuperScript = 0x01, /// A superscript tag is open SubScript = 0x02, /// A subscript tag is open Anchor = 0x04, /// An anchor tag is open SpanForeground = 0x08, /// A foreground altering span tag is open. SpanBackground = 0x10, /// A background altering span tag is open. SpanFontFamily = 0x20, /// A font family altering span tag is open. SpanFontPointSize = 0x40, /// A font size altering span tag is open. Strong = 0x80, /// A strong tag is open. Emph = 0x100, /// A emphasis tag is open. Underline = 0x200, /// An underline tag is open. StrikeOut = 0x400 /// A strikeout tag is open. }; protected: #ifndef Q_QDOC MarkupDirectorPrivate *const d_ptr; #endif /** The builder this MarkupDirector is operating on. This is available when subclassing to customize behaviour. */ - Grantlee::AbstractMarkupBuilder *m_builder; + KPIMTextEdit::AbstractMarkupBuilder *m_builder; #ifndef Q_QDOC private: Q_DECLARE_PRIVATE(MarkupDirector) #endif }; } #endif diff --git a/src/grantleebuilder/markupdirector_p.h b/src/grantleebuilder/markupdirector_p.h index 810110f..323af3d 100644 --- a/src/grantleebuilder/markupdirector_p.h +++ b/src/grantleebuilder/markupdirector_p.h @@ -1,67 +1,67 @@ /* This file is part of the Grantlee template system. Copyright (c) 2008 Stephen Kelly This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the Licence, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef GRANTLEE_MARKUPDIRECTOR_P_H #define GRANTLEE_MARKUPDIRECTOR_P_H #include "markupdirector.h" #include //@cond PRIVATE namespace KPIMTextEdit { /** @internal Maintainability class for MarkupDirector */ class MarkupDirectorPrivate { - MarkupDirectorPrivate(MarkupDirector *md) : q_ptr(md) {} - - Q_DECLARE_PUBLIC(MarkupDirector) - MarkupDirector *const q_ptr; - - QString m_openAnchorHref; - QString m_anchorHrefToOpen; - QString m_openAnchorName; - - QBrush m_openForeground; - QBrush m_foregroundToOpen; - QBrush m_openBackground; - QBrush m_backgroundToOpen; - int m_openFontPointSize; - int m_fontPointSizeToOpen; - QString m_openFontFamily; - QString m_fontFamilyToOpen; - - // An ordered list containing the order elements were opened in. - QList m_openElements; - - // Elements that have yet to be opened. Used while determine the order to - // open them. - QSet m_elementsToOpen; + MarkupDirectorPrivate(MarkupDirector *md) : q_ptr(md) {} + + Q_DECLARE_PUBLIC(MarkupDirector) + MarkupDirector *const q_ptr; + + QString m_openAnchorHref; + QString m_anchorHrefToOpen; + QString m_openAnchorName; + + QBrush m_openForeground; + QBrush m_foregroundToOpen; + QBrush m_openBackground; + QBrush m_backgroundToOpen; + int m_openFontPointSize; + int m_fontPointSizeToOpen; + QString m_openFontFamily; + QString m_fontFamilyToOpen; + + // An ordered list containing the order elements were opened in. + QList m_openElements; + + // Elements that have yet to be opened. Used while determine the order to + // open them. + QSet m_elementsToOpen; }; } #endif //@endcond diff --git a/src/grantleebuilder/plaintextmarkupbuilder.cpp b/src/grantleebuilder/plaintextmarkupbuilder.cpp index 2998bb4..4ca92c7 100644 --- a/src/grantleebuilder/plaintextmarkupbuilder.cpp +++ b/src/grantleebuilder/plaintextmarkupbuilder.cpp @@ -1,536 +1,542 @@ /* Copyright (c) 2019-2020 Montel Laurent This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "plaintextmarkupbuilder.h" #include namespace KPIMTextEdit { class PlainTextMarkupBuilderPrivate { public: PlainTextMarkupBuilderPrivate(PlainTextMarkupBuilder *b) : q_ptr(b) {} /** Get a letter string to represent a number. The numbers 1-26 are represented by a-z, and 27-52 by aa-az, 53-79 by ba-bz etc. @param The number to convert @return The letter string representation of the number. */ Q_REQUIRED_RESULT QString getLetterString(int itemNumber); Q_REQUIRED_RESULT QString getRomanString(int itemNumber); /** Gets a block of references in the body of the text. This is an ordered list of links and images in the text. */ Q_REQUIRED_RESULT QString getReferences(); QStringList m_urls; QList currentListItemStyles; QList currentListItemNumbers; QString activeLink; QString m_text; QString m_quoteprefix; PlainTextMarkupBuilder *q_ptr; Q_DECLARE_PUBLIC(PlainTextMarkupBuilder) }; } using namespace KPIMTextEdit; PlainTextMarkupBuilder::PlainTextMarkupBuilder() : d_ptr(new PlainTextMarkupBuilderPrivate(this)) { } QString PlainTextMarkupBuilderPrivate::getRomanString(int item) { QString result; // Code based to gui/text/qtextlist.cpp if (item < 5000) { QString romanNumeral; // works for up to 4999 items auto romanSymbols = QStringLiteral("iiivixxxlxcccdcmmmm"); int c[] = {1, 4, 5, 9, 10, 40, 50, 90, 100, 400, 500, 900, 1000}; auto n = item; for (auto i = 12; i >= 0; n %= c[i], i--) { auto q = n / c[i]; if (q > 0) { auto startDigit = i + (i + 3) / 4; int numDigits; if (i % 4) { // c[i] == 4|5|9|40|50|90|400|500|900 if ((i - 2) % 4) { // c[i] == 4|9|40|90|400|900 => with subtraction (IV, // IX, XL, XC, // ...) numDigits = 2; } else { // c[i] == 5|50|500 (V, L, D) numDigits = 1; } } else { // c[i] == 1|10|100|1000 (I, II, III, X, XX, ...) numDigits = q; } romanNumeral.append(romanSymbols.midRef(startDigit, numDigits)); } } result = romanNumeral; } else { result = QStringLiteral("?"); } return result; } QString PlainTextMarkupBuilderPrivate::getLetterString(int itemNumber) { QString letterString; while (true) { // Create the letter string by prepending one char at a time. // The itemNumber is converted to a number in the base 36 (number of // letters // in the // alphabet plus 10) after being increased by 10 (to pass out the digits // 0 // to 9). letterString.prepend(QStringLiteral("%1").arg( (itemNumber % LETTERSINALPHABET) + DIGITSOFFSET, 0, // no padding while building this string. LETTERSINALPHABET + DIGITSOFFSET)); if ((itemNumber >= LETTERSINALPHABET)) { itemNumber = itemNumber / LETTERSINALPHABET; itemNumber--; } else { break; } } return letterString; } QString PlainTextMarkupBuilderPrivate::getReferences() { QString refs; if (!m_urls.isEmpty()) { refs.append(QStringLiteral("\n--------\n")); int index = 1; while (!m_urls.isEmpty()) { refs.append( QStringLiteral("[%1] %2\n").arg(index++).arg(m_urls.takeFirst())); } } return refs; } PlainTextMarkupBuilder::~PlainTextMarkupBuilder() { delete d_ptr; } void PlainTextMarkupBuilder::setQuotePrefix(const QString &prefix) { Q_D(PlainTextMarkupBuilder); d->m_quoteprefix = prefix; } void PlainTextMarkupBuilder::beginStrong() { Q_D(PlainTextMarkupBuilder); d->m_text.append(QLatin1Char('*')); } void PlainTextMarkupBuilder::endStrong() { Q_D(PlainTextMarkupBuilder); d->m_text.append(QLatin1Char('*')); } void PlainTextMarkupBuilder::beginEmph() { Q_D(PlainTextMarkupBuilder); d->m_text.append(QLatin1Char('/')); } void PlainTextMarkupBuilder::endEmph() { Q_D(PlainTextMarkupBuilder); d->m_text.append(QLatin1Char('/')); } void PlainTextMarkupBuilder::beginUnderline() { Q_D(PlainTextMarkupBuilder); d->m_text.append(QLatin1Char('_')); } void PlainTextMarkupBuilder::endUnderline() { Q_D(PlainTextMarkupBuilder); d->m_text.append(QLatin1Char('_')); } void PlainTextMarkupBuilder::beginStrikeout() { Q_D(PlainTextMarkupBuilder); d->m_text.append(QLatin1Char('-')); } void PlainTextMarkupBuilder::endStrikeout() { Q_D(PlainTextMarkupBuilder); d->m_text.append(QLatin1Char('-')); } void PlainTextMarkupBuilder::beginAnchor(const QString &href, const QString &name) { Q_D(PlainTextMarkupBuilder); Q_UNUSED(name); if (!d->m_urls.contains(href)) { d->m_urls.append(href); } d->activeLink = href; } void PlainTextMarkupBuilder::endAnchor() { Q_D(PlainTextMarkupBuilder); d->m_text.append( QStringLiteral("[%1]").arg(d->m_urls.indexOf(d->activeLink) + 1)); } void PlainTextMarkupBuilder::endParagraph() { Q_D(PlainTextMarkupBuilder); d->m_text.append(QLatin1Char('\n')); } void PlainTextMarkupBuilder::addNewline() { Q_D(PlainTextMarkupBuilder); d->m_text.append(QLatin1Char('\n')); } void PlainTextMarkupBuilder::insertHorizontalRule(int width) { Q_UNUSED(width) Q_D(PlainTextMarkupBuilder); d->m_text.append(QStringLiteral("--------------------\n")); } int PlainTextMarkupBuilder::addReference(const QString &reference) { Q_D(PlainTextMarkupBuilder); if (!d->m_urls.contains(reference)) d->m_urls.append(reference); return d->m_urls.indexOf(reference) + 1; } void PlainTextMarkupBuilder::insertImage(const QString &src, qreal width, qreal height) { Q_D(PlainTextMarkupBuilder); Q_UNUSED(width) Q_UNUSED(height) auto ref = addReference(src); d->m_text.append(QStringLiteral("[%1]").arg(ref)); } void PlainTextMarkupBuilder::beginList(QTextListFormat::Style style) { Q_D(PlainTextMarkupBuilder); d->currentListItemStyles.append(style); d->currentListItemNumbers.append(0); } void PlainTextMarkupBuilder::endList() { Q_D(PlainTextMarkupBuilder); if (!d->currentListItemNumbers.isEmpty()) { d->currentListItemStyles.removeLast(); d->currentListItemNumbers.removeLast(); } } void PlainTextMarkupBuilder::beginListItem() { Q_D(PlainTextMarkupBuilder); for (int i = 0, total = d->currentListItemNumbers.size(); i < total; ++i) { d->m_text.append(QStringLiteral(" ")); } auto itemNumber = d->currentListItemNumbers.last(); switch (d->currentListItemStyles.last()) { case QTextListFormat::ListDisc: d->m_text.append(QStringLiteral(" * ")); break; case QTextListFormat::ListCircle: d->m_text.append(QStringLiteral(" o ")); break; case QTextListFormat::ListSquare: d->m_text.append(QStringLiteral(" - ")); break; case QTextListFormat::ListDecimal: d->m_text.append(QStringLiteral(" %1. ").arg(itemNumber + 1)); break; case QTextListFormat::ListLowerAlpha: d->m_text.append( QStringLiteral(" %1. ").arg(d->getLetterString(itemNumber))); break; case QTextListFormat::ListUpperAlpha: d->m_text.append( QStringLiteral(" %1. ").arg(d->getLetterString(itemNumber).toUpper())); break; case QTextListFormat::ListLowerRoman: d->m_text.append( QStringLiteral(" %1. ").arg(d->getRomanString(itemNumber + 1))); break; case QTextListFormat::ListUpperRoman: d->m_text.append(QStringLiteral(" %1. ").arg( d->getRomanString(itemNumber + 1).toUpper())); break; default: break; } } void PlainTextMarkupBuilder::endListItem() { Q_D(PlainTextMarkupBuilder); d->currentListItemNumbers.last() = d->currentListItemNumbers.last() + 1; d->m_text.append(QLatin1Char('\n')); } void PlainTextMarkupBuilder::beginSuperscript() { Q_D(PlainTextMarkupBuilder); d->m_text.append(QStringLiteral("^{")); } void PlainTextMarkupBuilder::endSuperscript() { Q_D(PlainTextMarkupBuilder); d->m_text.append(QLatin1Char('}')); } void PlainTextMarkupBuilder::beginSubscript() { Q_D(PlainTextMarkupBuilder); d->m_text.append(QStringLiteral("_{")); } void PlainTextMarkupBuilder::endSubscript() { Q_D(PlainTextMarkupBuilder); d->m_text.append(QLatin1Char('}')); } void PlainTextMarkupBuilder::appendLiteralText(const QString &text) { Q_D(PlainTextMarkupBuilder); d->m_text.append(text); } void PlainTextMarkupBuilder::appendRawText(const QString &text) { Q_D(PlainTextMarkupBuilder); d->m_text.append(text); } QString PlainTextMarkupBuilder::getResult() { Q_D(PlainTextMarkupBuilder); auto ret = d->m_text; ret.append(d->getReferences()); d->m_text.clear(); return ret; } void PlainTextMarkupBuilder::beginParagraph(Qt::Alignment a, qreal top, qreal bottom, qreal left, qreal right) { Q_UNUSED(a); Q_D(PlainTextMarkupBuilder); if (isQuoteBlock(top, bottom, left, right)) { d->m_text.append(d->m_quoteprefix); } } bool PlainTextMarkupBuilder::isQuoteBlock(qreal top, qreal bottom, qreal left, qreal right) const { Q_UNUSED(top); Q_UNUSED(bottom); return /*(top == 12) && (bottom == 12) &&*/ (left == 40) && (right == 40); } void PlainTextMarkupBuilder::beginBackground(const QBrush &brush) { Q_UNUSED(brush); } void PlainTextMarkupBuilder::beginFontFamily(const QString &family) { Q_UNUSED(family); } void PlainTextMarkupBuilder::beginFontPointSize(int size) { Q_UNUSED(size); } void PlainTextMarkupBuilder::beginForeground(const QBrush &brush) { Q_UNUSED(brush); } void PlainTextMarkupBuilder::beginHeader(int level) { Q_D(PlainTextMarkupBuilder); switch (level) { case 1: d->m_text.append(QStringLiteral("# ")); break; case 2: d->m_text.append(QStringLiteral("## ")); break; case 3: d->m_text.append(QStringLiteral("### ")); break; case 4: d->m_text.append(QStringLiteral("#### ")); break; case 5: d->m_text.append(QStringLiteral("##### ")); break; case 6: d->m_text.append(QStringLiteral("###### ")); break; default: break; } } void PlainTextMarkupBuilder::beginTable(qreal cellpadding, qreal cellspacing, const QString &width) { Q_UNUSED(cellpadding); Q_UNUSED(cellspacing); Q_UNUSED(width); } void PlainTextMarkupBuilder::beginTableCell(const QString &width, int colSpan, int rowSpan) { Q_UNUSED(width); Q_UNUSED(colSpan); Q_UNUSED(rowSpan); } void PlainTextMarkupBuilder::beginTableHeaderCell(const QString &width, int colSpan, int rowSpan) { Q_UNUSED(width); Q_UNUSED(colSpan); Q_UNUSED(rowSpan); } void PlainTextMarkupBuilder::beginTableRow() { } void PlainTextMarkupBuilder::endBackground() { } void PlainTextMarkupBuilder::endFontFamily() { } void PlainTextMarkupBuilder::endFontPointSize() { } void PlainTextMarkupBuilder::endForeground() { } void PlainTextMarkupBuilder::endHeader(int level) { Q_D(PlainTextMarkupBuilder); qDebug() << " void PlainTextMarkupBuilder::endHeader(int level)"<< level; switch (level) { case 1: d->m_text.append(QStringLiteral(" #\n")); break; case 2: d->m_text.append(QStringLiteral(" ##\n")); break; case 3: d->m_text.append(QStringLiteral(" ###\n")); break; case 4: d->m_text.append(QStringLiteral(" ####\n")); break; case 5: d->m_text.append(QStringLiteral(" #####\n")); break; case 6: d->m_text.append(QStringLiteral(" ######\n")); break; default: break; } } void PlainTextMarkupBuilder::endTable() { } void PlainTextMarkupBuilder::endTableCell() { } void PlainTextMarkupBuilder::endTableHeaderCell() { } void PlainTextMarkupBuilder::endTableRow() { } + +void PlainTextMarkupBuilder::addSingleBreakLine() +{ + Q_D(PlainTextMarkupBuilder); + d->m_text.append(QLatin1Char('\n')); +} diff --git a/src/grantleebuilder/plaintextmarkupbuilder.h b/src/grantleebuilder/plaintextmarkupbuilder.h index 1cb87e7..9dd9912 100644 --- a/src/grantleebuilder/plaintextmarkupbuilder.h +++ b/src/grantleebuilder/plaintextmarkupbuilder.h @@ -1,133 +1,136 @@ /* Copyright (c) 2019-2020 Montel Laurent based on grantlee PlainTextMarkupBuilder This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef PLAINTEXTMARKUPBUILDER_H #define PLAINTEXTMARKUPBUILDER_H #include "kpimtextedit_export.h" -#include -#include +#include "abstractmarkupbuilder.h" + +#define LETTERSINALPHABET 26 +#define DIGITSOFFSET 10 namespace KPIMTextEdit { class PlainTextMarkupBuilderPrivate; - class KPIMTEXTEDIT_EXPORT PlainTextMarkupBuilder - : virtual public Grantlee::AbstractMarkupBuilder + : virtual public KPIMTextEdit::AbstractMarkupBuilder { public: /** Construct a new PlainTextHTMLMarkupBuilder. */ PlainTextMarkupBuilder(); ~PlainTextMarkupBuilder() override; void setQuotePrefix(const QString &prefix); void beginStrong() override; void endStrong() override; void beginEmph() override; void endEmph() override; void beginUnderline() override; void endUnderline() override; void beginStrikeout() override; void endStrikeout() override; void beginAnchor(const QString &href = QString(), const QString &name = QString()) override; void endAnchor() override; void beginParagraph(Qt::Alignment a = Qt::AlignLeft, qreal top = 0.0, qreal bottom = 0.0, qreal left = 0.0, qreal right = 0.0) override; void endParagraph() override; void addNewline() override; void insertHorizontalRule(int width = -1) override; void insertImage(const QString &src, qreal width, qreal height) override; void beginList(QTextListFormat::Style style) override; void endList() override; void beginListItem() override; void endListItem() override; void beginSuperscript() override; void endSuperscript() override; void beginSubscript() override; void endSubscript() override; void beginTable(qreal cellpadding, qreal cellspacing, const QString &width) override; void beginTableRow() override; void beginTableHeaderCell(const QString &width, int colSpan, int rowSpan) override; void beginTableCell(const QString &width, int colSpan, int rowSpan) override; void appendLiteralText(const QString &text) override; void appendRawText(const QString &text) override; /** Adds a reference to @p reference to the internal list of references in the document. */ int addReference(const QString &reference); /** Returns the finalised plain text markup, including references at the end. */ QString getResult() override; bool isQuoteBlock(qreal top, qreal bottom, qreal left, qreal right) const; void beginForeground(const QBrush &brush) override; void endForeground() override; void beginBackground(const QBrush &brush) override; void endBackground() override; void beginFontFamily(const QString &family) override; void endFontFamily() override; void beginFontPointSize(int size) override; void endFontPointSize() override; void endTable() override; void endTableRow() override; void endTableHeaderCell() override; void endTableCell() override; void beginHeader(int level) override; void endHeader(int level) override; + void addSingleBreakLine() override; private: PlainTextMarkupBuilderPrivate *const d_ptr; Q_DECLARE_PRIVATE(PlainTextMarkupBuilder) + }; } #endif // PLAINTEXTMARKUPBUILDER_H diff --git a/src/grantleebuilder/texthtmlbuilder.cpp b/src/grantleebuilder/texthtmlbuilder.cpp index c1e92e2..8610bde 100644 --- a/src/grantleebuilder/texthtmlbuilder.cpp +++ b/src/grantleebuilder/texthtmlbuilder.cpp @@ -1,102 +1,500 @@ /* Copyright (C) 2020 Laurent Montel based on code from Stephen Kelly This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "texthtmlbuilder.h" #include #include #include + + +namespace KPIMTextEdit +{ +class TextHTMLBuilderPrivate +{ +public: + TextHTMLBuilderPrivate(TextHTMLBuilder *b) : q_ptr(b) {} + + QList currentListItemStyles; + QString m_text; + + TextHTMLBuilder *q_ptr; + + Q_DECLARE_PUBLIC(TextHTMLBuilder) +}; +} + using namespace KPIMTextEdit; TextHTMLBuilder::TextHTMLBuilder() - : Grantlee::TextHTMLBuilder() + : AbstractMarkupBuilder(), d_ptr(new TextHTMLBuilderPrivate(this)) { } -TextHTMLBuilder::~TextHTMLBuilder() +TextHTMLBuilder::~TextHTMLBuilder() { delete d_ptr; } + +void TextHTMLBuilder::beginStrong() { + Q_D(TextHTMLBuilder); + ; + d->m_text.append(QStringLiteral("")); } -//Add   for avoiding to remove space in html -void TextHTMLBuilder::appendLiteralText(const QString &text) +void TextHTMLBuilder::endStrong() { - const QString textEscaped = text.toHtmlEscaped(); - QString textEscapedResult; - for (int i = 0, total = textEscaped.count(); i < total; ++i) { - const QChar c = textEscaped.at(i); + Q_D(TextHTMLBuilder); + d->m_text.append(QStringLiteral("")); +} - if (c == QLatin1Char(' ')) { - if (i == 0) { - textEscapedResult += QStringLiteral(" "); - } else { - if (i + 1 < textEscaped.count() && (textEscaped.at(i + 1) == QLatin1Char(' '))) { - textEscapedResult += QStringLiteral(" "); - } else { - textEscapedResult += c; - } - } - } else if (c == QLatin1Char('\t')) { - textEscapedResult += QStringLiteral("    "); +void TextHTMLBuilder::beginEmph() +{ + Q_D(TextHTMLBuilder); + d->m_text.append(QStringLiteral("")); +} + +void TextHTMLBuilder::endEmph() +{ + Q_D(TextHTMLBuilder); + d->m_text.append(QStringLiteral("")); +} + +void TextHTMLBuilder::beginUnderline() +{ + Q_D(TextHTMLBuilder); + d->m_text.append(QStringLiteral("")); +} + +void TextHTMLBuilder::endUnderline() +{ + Q_D(TextHTMLBuilder); + d->m_text.append(QStringLiteral("")); +} + +void TextHTMLBuilder::beginStrikeout() +{ + Q_D(TextHTMLBuilder); + d->m_text.append(QStringLiteral("")); +} + +void TextHTMLBuilder::endStrikeout() +{ + Q_D(TextHTMLBuilder); + d->m_text.append(QStringLiteral("")); +} + +void TextHTMLBuilder::beginForeground(const QBrush &brush) +{ + Q_D(TextHTMLBuilder); + d->m_text.append( + QStringLiteral("").arg(brush.color().name())); +} + +void TextHTMLBuilder::endForeground() +{ + Q_D(TextHTMLBuilder); + d->m_text.append(QStringLiteral("")); +} + +void TextHTMLBuilder::beginBackground(const QBrush &brush) +{ + Q_D(TextHTMLBuilder); + d->m_text.append(QStringLiteral("") + .arg(brush.color().name())); +} + +void TextHTMLBuilder::endBackground() +{ + Q_D(TextHTMLBuilder); + d->m_text.append(QStringLiteral("")); +} + +void TextHTMLBuilder::beginAnchor(const QString &href, const QString &name) +{ + Q_D(TextHTMLBuilder); + if (!href.isEmpty()) { + if (!name.isEmpty()) { + d->m_text.append( + QStringLiteral("").arg(href, name)); } else { - textEscapedResult += c; + d->m_text.append(QStringLiteral("").arg(href)); + } + } else { + if (!name.isEmpty()) { + d->m_text.append(QStringLiteral("").arg(name)); } } - appendRawText(textEscapedResult); } +void TextHTMLBuilder::endAnchor() +{ + Q_D(TextHTMLBuilder); + d->m_text.append(QStringLiteral("")); +} +void TextHTMLBuilder::beginFontFamily(const QString &family) +{ + Q_D(TextHTMLBuilder); + d->m_text.append( + QStringLiteral("").arg(family)); +} + +void TextHTMLBuilder::endFontFamily() +{ + Q_D(TextHTMLBuilder); + d->m_text.append(QStringLiteral("")); +} + +void TextHTMLBuilder::beginFontPointSize(int size) +{ + Q_D(TextHTMLBuilder); + d->m_text.append(QStringLiteral("") + .arg(QString::number(size))); +} + +void TextHTMLBuilder::endFontPointSize() +{ + Q_D(TextHTMLBuilder); + d->m_text.append(QStringLiteral("")); +} void TextHTMLBuilder::beginParagraph(Qt::Alignment al, qreal topMargin, - qreal bottomMargin, qreal leftMargin, - qreal rightMargin) + qreal bottomMargin, qreal leftMargin, + qreal rightMargin) { + Q_D(TextHTMLBuilder); // Don't put paragraph tags inside li tags. Qt bug reported. // if (currentListItemStyles.size() != 0) // { QString styleString; styleString.append(QStringLiteral("margin-top:%1;").arg(topMargin)); styleString.append(QStringLiteral("margin-bottom:%1;").arg(bottomMargin)); styleString.append(QStringLiteral("margin-left:%1;").arg(leftMargin)); styleString.append(QStringLiteral("margin-right:%1;").arg(rightMargin)); // Using == doesn't work here. // Using bitwise comparison because an alignment can contain a vertical and // a // horizontal part. if (al & Qt::AlignRight) { - appendRawText(QStringLiteral("

m_text.append(QStringLiteral("

m_text.append(QStringLiteral("

m_text.append(QStringLiteral("

m_text.append(QStringLiteral("m_text.append(QStringLiteral(" style is not defined if (!styleString.isEmpty()) { - appendRawText(QStringLiteral(" style=\"") + styleString + QLatin1Char('"')); + d->m_text.append(QStringLiteral(" style=\"") + styleString + QLatin1Char('"')); } - appendRawText(QStringLiteral(">")); + d->m_text.append(QLatin1Char('>')); // } +} + +void TextHTMLBuilder::beginHeader(int level) +{ + Q_D(TextHTMLBuilder); + switch (level) { + case 1: + d->m_text.append(QStringLiteral("

")); + break; + case 2: + d->m_text.append(QStringLiteral("

")); + break; + case 3: + d->m_text.append(QStringLiteral("

")); + break; + case 4: + d->m_text.append(QStringLiteral("

")); + break; + case 5: + d->m_text.append(QStringLiteral("

")); + break; + case 6: + d->m_text.append(QStringLiteral("
")); + break; + default: + break; + } +} + +void TextHTMLBuilder::endHeader(int level) +{ + Q_D(TextHTMLBuilder); + switch (level) { + case 1: + d->m_text.append(QStringLiteral("
")); + break; + case 2: + d->m_text.append(QStringLiteral("

")); + break; + case 3: + d->m_text.append(QStringLiteral("")); + break; + case 4: + d->m_text.append(QStringLiteral("")); + break; + case 5: + d->m_text.append(QStringLiteral("")); + break; + case 6: + d->m_text.append(QStringLiteral("")); + break; + default: + break; + } +} +void TextHTMLBuilder::endParagraph() +{ + Q_D(TextHTMLBuilder); + d->m_text.append(QStringLiteral("

\n")); +} + +void TextHTMLBuilder::addNewline() +{ + Q_D(TextHTMLBuilder); + d->m_text.append(QStringLiteral("

 ")); +} + +void TextHTMLBuilder::insertHorizontalRule(int width) +{ + Q_D(TextHTMLBuilder); + if (width != -1) { + d->m_text.append(QStringLiteral("


\n").arg(width)); + } + d->m_text.append(QStringLiteral("
\n")); +} + +void TextHTMLBuilder::insertImage(const QString &src, qreal width, qreal height) +{ + Q_D(TextHTMLBuilder); + d->m_text.append(QStringLiteral("m_text.append(QStringLiteral("width=\"%2\" ").arg(width)); + if (height != 0) + d->m_text.append(QStringLiteral("height=\"%2\" ").arg(height)); + d->m_text.append(QStringLiteral("/>")); +} + +void TextHTMLBuilder::beginList(QTextListFormat::Style type) +{ + Q_D(TextHTMLBuilder); + d->currentListItemStyles.append(type); + switch (type) { + case QTextListFormat::ListDisc: + d->m_text.append(QStringLiteral("
    \n")); + break; + case QTextListFormat::ListCircle: + d->m_text.append(QStringLiteral("\n
      \n")); + break; + case QTextListFormat::ListSquare: + d->m_text.append(QStringLiteral("\n
        \n")); + break; + case QTextListFormat::ListDecimal: + d->m_text.append(QStringLiteral("\n
          \n")); + break; + case QTextListFormat::ListLowerAlpha: + d->m_text.append(QStringLiteral("\n
            \n")); + break; + case QTextListFormat::ListUpperAlpha: + d->m_text.append(QStringLiteral("\n
              \n")); + break; + case QTextListFormat::ListLowerRoman: + d->m_text.append(QStringLiteral("\n
                \n")); + break; + case QTextListFormat::ListUpperRoman: + d->m_text.append(QStringLiteral("\n
                  \n")); + break; + default: + break; + } +} +void TextHTMLBuilder::endList() +{ + Q_D(TextHTMLBuilder); + switch (d->currentListItemStyles.last()) { + case QTextListFormat::ListDisc: + case QTextListFormat::ListCircle: + case QTextListFormat::ListSquare: + d->m_text.append(QStringLiteral("
      \n")); + break; + case QTextListFormat::ListDecimal: + case QTextListFormat::ListLowerAlpha: + case QTextListFormat::ListUpperAlpha: + case QTextListFormat::ListLowerRoman: + case QTextListFormat::ListUpperRoman: + d->m_text.append(QStringLiteral("\n")); + break; + default: + break; + } + d->currentListItemStyles.removeLast(); +} +void TextHTMLBuilder::beginListItem() +{ + Q_D(TextHTMLBuilder); + d->m_text.append(QStringLiteral("
    • ")); +} + +void TextHTMLBuilder::endListItem() +{ + Q_D(TextHTMLBuilder); + d->m_text.append(QStringLiteral("
    • \n")); +} + +void TextHTMLBuilder::beginSuperscript() +{ + Q_D(TextHTMLBuilder); + d->m_text.append(QStringLiteral("")); +} + +void TextHTMLBuilder::endSuperscript() +{ + Q_D(TextHTMLBuilder); + d->m_text.append(QStringLiteral("")); +} + +void TextHTMLBuilder::beginSubscript() +{ + Q_D(TextHTMLBuilder); + d->m_text.append(QStringLiteral("")); +} + +void TextHTMLBuilder::endSubscript() +{ + Q_D(TextHTMLBuilder); + d->m_text.append(QStringLiteral("")); +} + +void TextHTMLBuilder::beginTable(qreal cellpadding, qreal cellspacing, + const QString &width) +{ + Q_D(TextHTMLBuilder); + d->m_text.append( + QStringLiteral("") + .arg(cellpadding) + .arg(cellspacing) + .arg(width)); +} + +void TextHTMLBuilder::beginTableRow() +{ + Q_D(TextHTMLBuilder); + d->m_text.append(QStringLiteral("")); +} + +void TextHTMLBuilder::beginTableHeaderCell(const QString &width, int colspan, + int rowspan) +{ + Q_D(TextHTMLBuilder); + d->m_text.append( + QStringLiteral("
      ") + .arg(width) + .arg(colspan) + .arg(rowspan)); +} + +void TextHTMLBuilder::beginTableCell(const QString &width, int colspan, + int rowspan) +{ + Q_D(TextHTMLBuilder); + d->m_text.append( + QStringLiteral("") + .arg(width) + .arg(colspan) + .arg(rowspan)); +} + +void TextHTMLBuilder::endTable() +{ + Q_D(TextHTMLBuilder); + d->m_text.append(QStringLiteral("
      ")); +} + +void TextHTMLBuilder::endTableRow() +{ + Q_D(TextHTMLBuilder); + d->m_text.append(QStringLiteral("")); +} + +void TextHTMLBuilder::endTableHeaderCell() +{ + Q_D(TextHTMLBuilder); + d->m_text.append(QStringLiteral("")); +} + +void TextHTMLBuilder::endTableCell() +{ + Q_D(TextHTMLBuilder); + d->m_text.append(QStringLiteral("")); +} + +void TextHTMLBuilder::appendLiteralText(const QString &text) +{ + Q_D(TextHTMLBuilder); + const QString textEscaped = text.toHtmlEscaped(); + QString textEscapedResult; + for (int i = 0, total = textEscaped.count(); i < total; ++i) { + const QChar c = textEscaped.at(i); + + if (c == QLatin1Char(' ')) { + if (i == 0) { + textEscapedResult += QStringLiteral(" "); + } else { + if (i + 1 < textEscaped.count() && (textEscaped.at(i + 1) == QLatin1Char(' '))) { + textEscapedResult += QStringLiteral(" "); + } else { + textEscapedResult += c; + } + } + } else if (c == QLatin1Char('\t')) { + textEscapedResult += QStringLiteral("    "); + } else { + textEscapedResult += c; + } + } + d->m_text.append(textEscapedResult); +} + +void TextHTMLBuilder::appendRawText(const QString &text) +{ + Q_D(TextHTMLBuilder); + d->m_text.append(text); +} + +QString TextHTMLBuilder::getResult() +{ + Q_D(TextHTMLBuilder); + auto ret = d->m_text; + d->m_text.clear(); + return ret; +} + + +void KPIMTextEdit::TextHTMLBuilder::addSingleBreakLine() +{ + Q_D(TextHTMLBuilder); + d->m_text.append(QLatin1String("
      ")); } diff --git a/src/grantleebuilder/texthtmlbuilder.h b/src/grantleebuilder/texthtmlbuilder.h index 8ff3a27..1339198 100644 --- a/src/grantleebuilder/texthtmlbuilder.h +++ b/src/grantleebuilder/texthtmlbuilder.h @@ -1,41 +1,235 @@ /* Copyright (C) 2020 Laurent Montel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef TEXTHTMLBUILDER_H #define TEXTHTMLBUILDER_H #include "kpimtextedit_export.h" -#include +#include "abstractmarkupbuilder.h" namespace KPIMTextEdit { + +class TextHTMLBuilderPrivate; + +/// @headerfile texthtmlbuilder.h grantlee/texthtmlbuilder.h + +/** + @brief The TextHTMLBuilder creates a clean html markup output. + + This class creates html output which is as minimal as possible and restricted + to the rich text features supported in %Qt. + (https://doc.qt.io/qt-5/richtext-html-subset.html) + + The output contains only the body content, not the head element or other + metadata. + + eg: + + @code +

      + This is some formatted content in a paragraph. +

      + @endcode + + instead of the content produced by %Qt: + + @code + + +

      This is some formatted content in a paragraph. +

      + @endcode + + Such tags should be created separately. For example: + + @code + auto b = new TextHTMLBuilder(); + auto md = new MarkupDirector(b); + md->constructContent(); + QString cleanHtml( + "\n%1\n\n%2\n") + .arg(document.metaInformation(QTextDocument::DocumentTitle)) + .arg(b->getOutput()); + file.write(cleanHtml); + @endcode + + Font formatting information on elements is represented by individual span + elements. + + eg: + @code + + + Blue text on red background + + + @endcode + + instead of + + @code + + Blue text on red background + + @endcode + + It my be possible to change this if necessary. + + @author Stephen Kelly +*/ class KPIMTEXTEDIT_EXPORT TextHTMLBuilder - : virtual public Grantlee::TextHTMLBuilder + : virtual public KPIMTextEdit::AbstractMarkupBuilder { public: TextHTMLBuilder(); ~TextHTMLBuilder() override; + void beginStrong() override; + void endStrong() override; + void beginEmph() override; + void endEmph() override; + void beginUnderline() override; + void endUnderline() override; + void beginStrikeout() override; + void endStrikeout() override; + void beginForeground(const QBrush &brush) override; + void endForeground() override; + void beginBackground(const QBrush &brush) override; + void endBackground() override; + void beginAnchor(const QString &href = {}, const QString &name = {}) override; + void endAnchor() override; + + // Maybe this stuff should just be added to a list, and then when I add + // literal text, + // add some kind of style attribute in one span instead of many. + void beginFontFamily(const QString &family) override; + void endFontFamily() override; + + /** + Begin a new font point size + @param size The new size to begin. + */ + void beginFontPointSize(int size) override; + void endFontPointSize() override; + + /** + Begin a new paragraph + @param al The new paragraph alignment + @param topMargin The new paragraph topMargin + @param bottomMargin The new paragraph bottomMargin + @param leftMargin The new paragraph leftMargin + @param rightMargin The new paragraph rightMargin + */ + void beginParagraph(Qt::Alignment al = Qt::AlignLeft, qreal topMargin = 0.0, + qreal bottomMargin = 0.0, qreal leftMargin = 0.0, + qreal rightMargin = 0.0) override; + + /** + Begin a new header element. + @param level The new level to begin. + */ + void beginHeader(int level) override; + + /** + End a header element. + @param level The new level to end. + */ + void endHeader(int level) override; + + void endParagraph() override; + void addNewline() override; + + void insertHorizontalRule(int width = -1) override; + + void insertImage(const QString &src, qreal width, qreal height) override; + + void beginList(QTextListFormat::Style type) override; + + void endList() override; + + void beginListItem() override; + void endListItem() override; + + void beginSuperscript() override; + + void endSuperscript() override; + + void beginSubscript() override; + + void endSubscript() override; + + void beginTable(qreal cellpadding, qreal cellspacing, + const QString &width) override; + + void beginTableRow() override; + void beginTableHeaderCell(const QString &width, int colspan, + int rowspan) override; + + void beginTableCell(const QString &width, int colspan, int rowspan) override; + + void endTable() override; + void endTableRow() override; + void endTableHeaderCell() override; + void endTableCell() override; + + /** + Reimplemented from AbstractMarkupBuilder. + + This implementation escapes the text before appending so that + + @verbatim + A sample bold word. + @endverbatim + + becomes + + @verbatim + A sample <b>bold</b> word. + @endverbatim + */ void appendLiteralText(const QString &text) override; - void beginParagraph(Qt::Alignment al, qreal topMargin, qreal bottomMargin, qreal leftMargin, qreal rightMargin) override; + /** + Append @p text without escaping. + + This is useful if extending MarkupDirector + */ + void appendRawText(const QString &text) override; + + Q_REQUIRED_RESULT QString getResult() override; + + void addSingleBreakLine() override; +private: + TextHTMLBuilderPrivate *d_ptr; + Q_DECLARE_PRIVATE(TextHTMLBuilder) + }; + } #endif // TEXTHTMLBUILDER_H