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&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>&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