commit c8d2a69f0dc4074a6548d5031d59498c5d574107
Author: Juergen Spitzmueller <[email protected]>
Date:   Tue Aug 5 08:18:56 2025 +0200

    Implement XHTML premble (#12061)
    
    Original patch by Lorenzo Bertini, slightly reworked and extended
---
 development/FORMAT                |  5 +++
 lib/lyx2lyx/LyX.py                |  4 +-
 lib/lyx2lyx/lyx_2_5.py            | 27 ++++++++++-
 src/Buffer.cpp                    |  6 +++
 src/BufferParams.cpp              | 25 ++++++++++-
 src/BufferParams.h                |  4 ++
 src/frontends/qt/GuiDocument.cpp  | 95 +++++++++++++++++++++++++--------------
 src/frontends/qt/GuiDocument.h    |  6 ++-
 src/frontends/qt/ui/PreambleUi.ui | 68 +++++++++++++++++-----------
 src/version.h                     |  4 +-
 10 files changed, 175 insertions(+), 69 deletions(-)

diff --git a/development/FORMAT b/development/FORMAT
index 1d9594c64c..b6fdeda12b 100644
--- a/development/FORMAT
+++ b/development/FORMAT
@@ -7,6 +7,11 @@ changes happened in particular if possible. A good example 
would be
 
 -----------------------
 
+2025-07-30 Jürgen Spitzmüller <[email protected]>
+       * Format incremented to 643: New buffer param
+         \begin_preamble_html ... \end_preamble_html
+        For user material inserted before <body> in XHTML output.
+
 2025-07-29 Richard Kimberly Heck <[email protected]>
        * Format incremented to 642: Add plimsoll symbol and package
 
diff --git a/lib/lyx2lyx/LyX.py b/lib/lyx2lyx/LyX.py
index 2313030323..3fb9268e67 100644
--- a/lib/lyx2lyx/LyX.py
+++ b/lib/lyx2lyx/LyX.py
@@ -362,7 +362,7 @@ class LyX_base:
 
             line = trim_eol_binary(line)
             decoded = line.decode("latin1")
-            if check_token(decoded, "\\begin_preamble"):
+            if decoded == "\\begin_preamble":
                 while True:
                     line = self.input.readline()
                     if not line:
@@ -388,7 +388,7 @@ class LyX_base:
 
                     self.preamble.append(line)
 
-            if check_token(decoded, "\\end_preamble"):
+            if decoded == "\\end_preamble":
                 continue
 
             line = line.rstrip()
diff --git a/lib/lyx2lyx/lyx_2_5.py b/lib/lyx2lyx/lyx_2_5.py
index 632f6bd8b8..a20dddf4c6 100644
--- a/lib/lyx2lyx/lyx_2_5.py
+++ b/lib/lyx2lyx/lyx_2_5.py
@@ -35,13 +35,14 @@ from lyx2lyx_tools import (
 
 # Uncomment only what you need to import, please (parser_tools):
 #    check_token, count_pars_in_inset, del_complete_lines, 
-#    del_value, find_complete_lines, find_end_of, 
+#    del_value, find_complete_lines, 
 #    find_re, find_token_backwards, find_token_exact,
 #    find_tokens,
 #    get_option_value,
 #    is_in_inset
 from parser_tools import (
     del_token,
+    find_end_of,
     find_end_of_inset,
     find_end_of_layout,
     find_re,
@@ -3216,6 +3217,26 @@ def revert_plimsoll(document):
         return
 
 
+def revert_html_preamble(document):
+    """Revert html preamble to local layout"""
+    i = find_token(document.header, "\\begin_preamble_html", 0)
+    if i == -1:
+        return
+
+    j = find_end_of(document.header, i, "\\begin_preamble_html", 
"\\end_preamble_html")
+    if j == -1:
+        # this should not happen
+        document.warning("Malformed LyX document: Could not find end of html 
preamble.")
+        return
+
+    html_def = document.header[i + 1 : j]
+    document.header[i : j + 1] = []
+    if len(html_def) > 0:
+        html_def.insert(0, "HTMLPreamble")
+        html_def.append("EndPreamble")
+        document.append_local_layout(html_def)
+
+
 ##
 # Conversion hub
 #
@@ -3243,11 +3264,13 @@ convert = [
     [639, [convert_theorem_local_def]],
     [640, []],
     [641, [convert_justification_pref]],
-    [642, []]
+    [642, []],
+    [643, []]
 ]
 
 
 revert = [
+    [642, [revert_html_preamble]],
     [641, [revert_plimsoll]],
     [640, [revert_justification_pref]],
     [639, [revert_prettyref_l7n]],
diff --git a/src/Buffer.cpp b/src/Buffer.cpp
index 65ad4a95a6..bbb8c591cc 100644
--- a/src/Buffer.cpp
+++ b/src/Buffer.cpp
@@ -945,6 +945,7 @@ int Buffer::readHeader(Lexer & lex)
        // Initialize parameters that may be/go lacking in header:
        params().branchlist().clear();
        params().preamble.erase();
+       params().html_preamble.erase();
        params().options.erase();
        params().master.erase();
        params().float_placement.erase();
@@ -2366,6 +2367,11 @@ Buffer::ExportStatus 
Buffer::writeLyXHTMLSource(odocstream & os,
                                         << "\n</style>\n";
                        }
                }
+               // the user-defined preamble
+               if (!params().html_preamble.empty()) {
+                       os << "<!-- User specified HTML <head> commands -->\n"
+                          << params().html_preamble << "\n";
+               }
                os << "</head>\n";
        }
 
diff --git a/src/BufferParams.cpp b/src/BufferParams.cpp
index 74e41f60f1..b43e0322ee 100644
--- a/src/BufferParams.cpp
+++ b/src/BufferParams.cpp
@@ -864,6 +864,10 @@ string BufferParams::readToken(Lexer & lex, string const & 
token,
                        readPreamble(lex);
                        break;
                }
+               if (token == "\\begin_preamble_html") {
+                       readHTMLPreamble(lex);
+                       break;
+               }
                if (token == "\\begin_local_layout") {
                        readLocalLayout(lex, false);
                        break;
@@ -1600,7 +1604,8 @@ void BufferParams::writeFile(ostream & os, Buffer const * 
buf) const
                   << "\n\\end_metadata\n";
        }
 
-       // then the preamble
+       // then the preambles
+       // 1. LaTeX
        if (!preamble.empty()) {
                // remove '\n' from the end of preamble
                docstring const tmppreamble = rtrim(preamble, "\n");
@@ -1608,6 +1613,14 @@ void BufferParams::writeFile(ostream & os, Buffer const 
* buf) const
                   << to_utf8(tmppreamble)
                   << "\n\\end_preamble\n";
        }
+       // 2. HTML
+       if (!html_preamble.empty()) {
+               // remove '\n' from the end of preamble
+               docstring const tmphtmlpreamble = rtrim(html_preamble, "\n");
+               os << "\\begin_preamble_html\n"
+                  << to_utf8(tmphtmlpreamble)
+                  << "\n\\end_preamble_html\n";
+       }
 
        // the options
        if (!options.empty()) {
@@ -3473,6 +3486,16 @@ void BufferParams::readPreamble(Lexer & lex)
 }
 
 
+void BufferParams::readHTMLPreamble(Lexer & lex)
+{
+       if (lex.getString() != "\\begin_preamble_html")
+               lyxerr << "Error (BufferParams::readHTMLPreamble):"
+                       "consistency check failed." << endl;
+
+       html_preamble = lex.getLongString(from_ascii("\\end_preamble_html"));
+}
+
+
 void BufferParams::readDocumentMetadata(Lexer & lex)
 {
        if (lex.getString() != "\\begin_metadata")
diff --git a/src/BufferParams.h b/src/BufferParams.h
index 16d8be5048..adc3fe69ea 100644
--- a/src/BufferParams.h
+++ b/src/BufferParams.h
@@ -396,6 +396,8 @@ public:
        std::string origin;
        ///
        docstring preamble;
+       ///
+       docstring html_preamble;
        /// DocumentMetadata as introduced by LaTeX 2022/06
        docstring document_metadata;
        ///
@@ -670,6 +672,8 @@ private:
        ///
        void readPreamble(support::Lexer &);
        ///
+       void readHTMLPreamble(support::Lexer &);
+       ///
        void readDocumentMetadata(support::Lexer &);
        ///
        void readLocalLayout(support::Lexer &, bool);
diff --git a/src/frontends/qt/GuiDocument.cpp b/src/frontends/qt/GuiDocument.cpp
index 312514cbb0..b48f4a1c7e 100644
--- a/src/frontends/qt/GuiDocument.cpp
+++ b/src/frontends/qt/GuiDocument.cpp
@@ -459,30 +459,35 @@ void ModuleSelectionManager::updateDelPB()
 
 PreambleModule::PreambleModule(QWidget * parent)
        : UiWidget<Ui::PreambleUi>(parent), current_id_(nullptr),
-         highlighter_(new LaTeXHighlighter(preambleTE->document(), true))
+         highlighter_(new LaTeXHighlighter(latexPreambleTE->document(), true))
 {
        // This is not a memory leak. The object will be destroyed
        // with this.
        // @ is letter in the LyX user preamble
-       preambleTE->setFont(guiApp->typewriterSystemFont());
-       preambleTE->setWordWrapMode(QTextOption::NoWrap);
-       setFocusProxy(preambleTE);
+       latexPreambleTE->setFont(guiApp->typewriterSystemFont());
+       latexPreambleTE->setWordWrapMode(QTextOption::NoWrap);
+       setFocusProxy(latexPreambleTE);
+       htmlPreambleTE->setFont(guiApp->typewriterSystemFont());
+       htmlPreambleTE->setWordWrapMode(QTextOption::NoWrap);
        // Install event filter on find line edit to capture return/enter key
        findLE->installEventFilter(this);
-       connect(preambleTE, SIGNAL(textChanged()), this, SIGNAL(changed()));
+       connect(latexPreambleTE, SIGNAL(textChanged()), this, 
SIGNAL(changed()));
+       connect(htmlPreambleTE, SIGNAL(textChanged()), this, SIGNAL(changed()));
        connect(findLE, SIGNAL(textEdited(const QString &)), this, 
SLOT(checkFindButton()));
        connect(findButtonPB, SIGNAL(clicked()), this, SLOT(findText()));
        connect(editPB, SIGNAL(clicked()), this, SLOT(editExternal()));
        connect(findLE, SIGNAL(returnPressed()), this, SLOT(findText()));
        checkFindButton();
        int const tabStop = 4;
-       QFontMetrics metrics(preambleTE->currentFont());
+       QFontMetrics metrics(latexPreambleTE->currentFont());
 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
        // horizontalAdvance() is available starting in 5.11.0
        // setTabStopDistance() is available starting in 5.10.0
-       preambleTE->setTabStopDistance(tabStop * metrics.horizontalAdvance(' 
'));
+       latexPreambleTE->setTabStopDistance(tabStop * 
metrics.horizontalAdvance(' '));
+       htmlPreambleTE->setTabStopDistance(tabStop * 
metrics.horizontalAdvance(' '));
 #else
-       preambleTE->setTabStopWidth(tabStop * metrics.width(' '));
+       latexPreambleTE->setTabStopWidth(tabStop * metrics.width(' '));
+       htmlPreambleTE->setTabStopWidth(tabStop * metrics.width(' '));
 #endif
 }
 
@@ -520,13 +525,14 @@ void PreambleModule::checkFindButton()
 
 void PreambleModule::findText()
 {
-       bool const found = preambleTE->find(findLE->text());
+       QTextEdit * currentTE = 
preamblesTW->currentWidget()->findChild<QTextEdit *>();
+       bool const found = currentTE && currentTE->find(findLE->text());
        if (!found) {
                // wrap
-               QTextCursor qtcur = preambleTE->textCursor();
+               QTextCursor qtcur = currentTE->textCursor();
                qtcur.movePosition(QTextCursor::Start);
-               preambleTE->setTextCursor(qtcur);
-               preambleTE->find(findLE->text());
+               currentTE->setTextCursor(qtcur);
+               currentTE->find(findLE->text());
        }
 }
 
@@ -534,45 +540,62 @@ void PreambleModule::findText()
 void PreambleModule::update(BufferParams const & params, BufferId id)
 {
        QString preamble = toqstr(params.preamble);
+       QString htmlpreamble = toqstr(params.html_preamble);
        // Nothing to do if the params and preamble are unchanged.
        if (id == current_id_
-               && preamble == preambleTE->document()->toPlainText())
+           && preamble == latexPreambleTE->document()->toPlainText()
+           && htmlpreamble == htmlPreambleTE->document()->toPlainText())
                return;
 
-       QTextCursor cur = preambleTE->textCursor();
+       QTextCursor cur = latexPreambleTE->textCursor();
+       QTextCursor htmlcur = htmlPreambleTE->textCursor();
        // Save the coords before switching to the new one.
-       preamble_coords_[current_id_] =
-               make_pair(cur.position(), 
preambleTE->verticalScrollBar()->value());
+       latex_preamble_coords_[current_id_] =
+               make_pair(cur.position(), 
latexPreambleTE->verticalScrollBar()->value());
+       html_preamble_coords_[current_id_] =
+               make_pair(htmlcur.position(), 
htmlPreambleTE->verticalScrollBar()->value());
 
        // Save the params address for further use.
        current_id_ = id;
-       preambleTE->document()->setPlainText(preamble);
-       Coords::const_iterator it = preamble_coords_.find(current_id_);
-       if (it == preamble_coords_.end())
+       latexPreambleTE->document()->setPlainText(preamble);
+       htmlPreambleTE->document()->setPlainText(htmlpreamble);
+       Coords::const_iterator it = latex_preamble_coords_.find(current_id_);
+       Coords::const_iterator htmlit = html_preamble_coords_.find(current_id_);
+       if (it == latex_preamble_coords_.end()) {
                // First time we open this one.
-               preamble_coords_[current_id_] = make_pair(0, 0);
-       else {
+               latex_preamble_coords_[current_id_] = make_pair(0, 0);
+               html_preamble_coords_[current_id_] = make_pair(0, 0);
+       } else {
                // Restore saved coords.
-               cur = preambleTE->textCursor();
+               cur = latexPreambleTE->textCursor();
                cur.setPosition(it->second.first);
-               preambleTE->setTextCursor(cur);
-               preambleTE->verticalScrollBar()->setValue(it->second.second);
+               latexPreambleTE->setTextCursor(cur);
+               
latexPreambleTE->verticalScrollBar()->setValue(it->second.second);
+
+               htmlcur = htmlPreambleTE->textCursor();
+               htmlcur.setPosition(htmlit->second.first);
+               htmlPreambleTE->setTextCursor(htmlcur);
+               
htmlPreambleTE->verticalScrollBar()->setValue(htmlit->second.second);
        }
 }
 
 
 void PreambleModule::apply(BufferParams & params)
 {
-       params.preamble = 
qstring_to_ucs4(preambleTE->document()->toPlainText());
+       params.preamble = 
qstring_to_ucs4(latexPreambleTE->document()->toPlainText());
+       params.html_preamble = 
qstring_to_ucs4(htmlPreambleTE->document()->toPlainText());
 }
 
 
 void PreambleModule::closeEvent(QCloseEvent * e)
 {
        // Save the coords before closing.
-       QTextCursor cur = preambleTE->textCursor();
-       preamble_coords_[current_id_] =
-               make_pair(cur.position(), 
preambleTE->verticalScrollBar()->value());
+       QTextCursor cur = latexPreambleTE->textCursor();
+       QTextCursor htmlcur = htmlPreambleTE->textCursor();
+       latex_preamble_coords_[current_id_] =
+               make_pair(cur.position(), 
latexPreambleTE->verticalScrollBar()->value());
+       html_preamble_coords_[current_id_] =
+               make_pair(htmlcur.position(), 
htmlPreambleTE->verticalScrollBar()->value());
        e->accept();
 }
 
@@ -581,11 +604,15 @@ void PreambleModule::editExternal() {
        if (!current_id_)
                return;
 
+       QTextEdit * currentTE = 
preamblesTW->currentWidget()->findChild<QTextEdit *>();
+       if (!currentTE)
+               return;
+
        if (tempfile_) {
-               preambleTE->setReadOnly(false);
+               currentTE->setReadOnly(false);
                FileName const tempfilename = tempfile_->name();
                docstring const s = tempfilename.fileContents("UTF-8");
-               preambleTE->document()->setPlainText(toqstr(s));
+               currentTE->document()->setPlainText(toqstr(s));
                tempfile_.reset();
                editPB->setText(qt_("&Edit Externally"));
                editPB->setIcon(QIcon());
@@ -600,9 +627,9 @@ void PreambleModule::editExternal() {
        FileName const tempfilename = tempfile_->name();
        string const name = tempfilename.toFilesystemEncoding();
        ofdocstream os(name.c_str());
-       os << qstring_to_ucs4(preambleTE->document()->toPlainText());
+       os << qstring_to_ucs4(currentTE->document()->toPlainText());
        os.close();
-       preambleTE->setReadOnly(true);
+       currentTE->setReadOnly(true);
        theFormats().edit(*current_id_, tempfilename, format);
        editPB->setText(qt_("&End Edit"));
        QIcon warn(guiApp ? guiApp->getScaledPixmap("images/", 
"emblem-shellescape-user")
@@ -1886,7 +1913,7 @@ GuiDocument::GuiDocument(GuiView & lv)
        docPS->addPanel(bulletsModule, N_("Bullets"));
        docPS->addPanel(branchesModule, N_("Branches"));
        docPS->addPanel(outputModule, N_("Output"));
-       docPS->addPanel(preambleModule, N_("LaTeX Preamble"));
+       docPS->addPanel(preambleModule, N_("Preamble"));
        docPS->setCurrentPanel("Document Class");
 
        // Filter out (dark/light) mode changes
@@ -5164,7 +5191,7 @@ bool GuiDocument::isValid()
 
        docPS->markPanelValid(N_("Listings[[inset]]"), listings_valid);
        docPS->markPanelValid(N_("Local Layout"), local_layout_valid && 
localLayout->isValid());
-       docPS->markPanelValid(N_("LaTeX Preamble"), preamble_valid);
+       docPS->markPanelValid(N_("Preamble"), preamble_valid);
        
        return listings_valid && local_layout_valid && preamble_valid;
 }
diff --git a/src/frontends/qt/GuiDocument.h b/src/frontends/qt/GuiDocument.h
index 6c23795b58..d80c439a6a 100644
--- a/src/frontends/qt/GuiDocument.h
+++ b/src/frontends/qt/GuiDocument.h
@@ -424,10 +424,12 @@ protected:
 
 private:
        void closeEvent(QCloseEvent *) override;
-       void on_preambleTE_textChanged() { changed(); }
+       void on_latexPreambleTE_textChanged() { changed(); }
+       void on_htmlPreambleTE_textChanged() { changed(); }
 
        typedef std::map<BufferId, std::pair<int,int> > Coords;
-       Coords preamble_coords_;
+       Coords latex_preamble_coords_;
+       Coords html_preamble_coords_;
        BufferId current_id_;
        std::unique_ptr<support::TempFile> tempfile_;
        /// LaTeX syntax highlighter
diff --git a/src/frontends/qt/ui/PreambleUi.ui 
b/src/frontends/qt/ui/PreambleUi.ui
index eaf56d053e..79cf20a8b4 100644
--- a/src/frontends/qt/ui/PreambleUi.ui
+++ b/src/frontends/qt/ui/PreambleUi.ui
@@ -13,22 +13,48 @@
   <property name="windowTitle">
    <string/>
   </property>
-  <layout class="QGridLayout">
-   <property name="leftMargin">
-    <number>11</number>
-   </property>
-   <property name="topMargin">
-    <number>11</number>
-   </property>
-   <property name="rightMargin">
-    <number>11</number>
-   </property>
-   <property name="bottomMargin">
-    <number>11</number>
-   </property>
-   <property name="spacing">
-    <number>6</number>
-   </property>
+  <layout class="QGridLayout" name="gridLayout_3">
+   <item row="0" column="0" colspan="3">
+    <widget class="QTabWidget" name="preamblesTW">
+     <property name="currentIndex">
+      <number>0</number>
+     </property>
+     <widget class="QWidget" name="tab">
+      <attribute name="title">
+       <string>LaTe&amp;X</string>
+      </attribute>
+      <layout class="QGridLayout" name="gridLayout">
+       <item row="0" column="0">
+        <widget class="QTextEdit" name="latexPreambleTE">
+         <property name="inputMethodHints">
+          <set>Qt::ImhMultiLine|Qt::ImhPreferLatin</set>
+         </property>
+         <property name="acceptRichText">
+          <bool>false</bool>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="tab_2">
+      <attribute name="title">
+       <string>&amp;HTML</string>
+      </attribute>
+      <layout class="QGridLayout" name="gridLayout_2">
+       <item row="0" column="0">
+        <widget class="QTextEdit" name="htmlPreambleTE">
+         <property name="inputMethodHints">
+          <set>Qt::ImhMultiLine|Qt::ImhPreferLatin</set>
+         </property>
+         <property name="acceptRichText">
+          <bool>false</bool>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+    </widget>
+   </item>
    <item row="1" column="0">
     <widget class="QLineEdit" name="findLE">
      <property name="placeholderText">
@@ -53,16 +79,6 @@
      </property>
     </widget>
    </item>
-   <item row="0" column="0" colspan="3">
-    <widget class="QTextEdit" name="preambleTE">
-     <property name="inputMethodHints">
-      <set>Qt::ImhMultiLine|Qt::ImhPreferLatin</set>
-     </property>
-     <property name="acceptRichText">
-      <bool>false</bool>
-     </property>
-    </widget>
-   </item>
   </layout>
  </widget>
  <includes>
diff --git a/src/version.h b/src/version.h
index 8e09451246..ea74976e2a 100644
--- a/src/version.h
+++ b/src/version.h
@@ -32,8 +32,8 @@ extern char const * const lyx_version_info;
 
 // Do not remove the comment below, so we get merge conflict in
 // independent branches. Instead add your own.
-#define LYX_FORMAT_LYX 642 // rikiheck: plimsoll symbol
-#define LYX_FORMAT_TEX2LYX 642
+#define LYX_FORMAT_LYX 643 // spitz: html preamble
+#define LYX_FORMAT_TEX2LYX 643
 
 #if LYX_FORMAT_TEX2LYX != LYX_FORMAT_LYX
 #ifndef _MSC_VER
-- 
lyx-cvs mailing list
[email protected]
https://lists.lyx.org/mailman/listinfo/lyx-cvs

Reply via email to