commit e569426b1583df3b7dd6c606cc0feadc37e5723b
Author: Juergen Spitzmueller <[email protected]>
Date:   Mon Aug 3 11:29:12 2026 +0200

    Support for front/main/backmatter (#12424)
    
    Based on a patch by Daniel Ramöller
    
    File and layout format change
---
 development/FORMAT            |   5 ++
 lib/doc/Customization.lyx     | 121 ++++++++++++++++++++++++++++++++++++++++++
 lib/doc/UserGuide.lyx         |  72 +++++++++++++++++++++++++
 lib/doc/de/Customization.lyx  |  76 ++++++++++++++++++++++++++
 lib/doc/de/UserGuide.lyx      |  76 ++++++++++++++++++++++++++
 lib/layouts/amsbook.layout    |   1 +
 lib/layouts/book.layout       |   4 +-
 lib/layouts/memoir.layout     |   1 +
 lib/layouts/recipebook.layout |   1 +
 lib/layouts/scrbook.layout    |   1 +
 lib/layouts/tufte-book.layout |   1 +
 lib/lyx2lyx/lyx_2_6.py        |  33 +++++++++++-
 lib/lyx2lyx/parser_tools.py   |   1 +
 lib/scripts/layout2layout.py  |   7 ++-
 lib/ui/stdmenus.inc           |   7 +++
 src/Buffer.cpp                |  11 +++-
 src/Counters.cpp              |   4 +-
 src/Counters.h                |   9 ++++
 src/FuncCode.h                |   1 +
 src/LayoutEnums.h             |  12 +++++
 src/LyXAction.cpp             |  10 ++++
 src/ParagraphParameters.cpp   |  46 ++++++++++++++++
 src/ParagraphParameters.h     |  12 +++++
 src/RowPainter.cpp            |  47 ++++++++++++----
 src/RowPainter.h              |   2 +-
 src/Text.cpp                  |  44 +++++++++++++++
 src/TextClass.cpp             |  11 +++-
 src/TextClass.h               |   4 ++
 src/TextMetrics.cpp           |   7 +--
 src/output_latex.cpp          |  15 ++++++
 src/tex2lyx/text.cpp          |  72 +++++++++++++++++++++++++
 src/version.h                 |   4 +-
 32 files changed, 692 insertions(+), 26 deletions(-)

diff --git a/development/FORMAT b/development/FORMAT
index 21bad579ed..fae968c761 100644
--- a/development/FORMAT
+++ b/development/FORMAT
@@ -7,6 +7,11 @@ changes happened in particular if possible. A good example 
would be
 
 -----------------------
 
+2026-08-03 Jürgen Spitzmüller <[email protected]>
+       * Format incremented to 657: Support for \fronmatter, \mainmatter,
+         and \backmatter via paragraph parameter
+         \start_of_matter {front,main,back}
+
 2026-08-02 Jürgen Spitzmüller <[email protected]>
        * Format incremented to 656: Support for parfillskip
          New buffer param \parfillskip "<type>" "<glue length>"
diff --git a/lib/doc/Customization.lyx b/lib/doc/Customization.lyx
index c4e396b38f..fad374325d 100644
--- a/lib/doc/Customization.lyx
+++ b/lib/doc/Customization.lyx
@@ -11524,6 +11524,107 @@ nolink "false"
 \end_inset
 
  for the list of features.
+\change_inserted -712698321 1785747170
+
+\end_layout
+
+\begin_layout Description
+
+\change_inserted -712698321 1785747231
+\begin_inset Flex Code
+status collapsed
+
+\begin_layout Plain Layout
+
+\change_inserted -712698321 1785747178
+ProvidesMatters
+\end_layout
+
+\end_inset
+
+ [
+\begin_inset Flex Code
+status collapsed
+
+\begin_layout Plain Layout
+
+\change_inserted -712698321 1785747171
+
+\emph on
+0
+\end_layout
+
+\end_inset
+
+
+\begin_inset space \thinspace{}
+\end_inset
+
+
+\begin_inset Flex Code
+status collapsed
+
+\begin_layout Plain Layout
+
+\change_inserted -712698321 1785747171
+1
+\end_layout
+
+\end_inset
+
+] indicates whether the text class provides for 
+\begin_inset Flex Code
+status collapsed
+
+\begin_layout Plain Layout
+
+\change_inserted -712698321 1785747217
+
+\backslash
+frontmatter
+\change_unchanged
+
+\end_layout
+
+\end_inset
+
+,
+ 
+\begin_inset Flex Code
+status collapsed
+
+\begin_layout Plain Layout
+
+\change_inserted -712698321 1785747224
+
+\backslash
+mainmatter
+\change_unchanged
+
+\end_layout
+
+\end_inset
+
+,
+ and 
+\begin_inset Flex Code
+status collapsed
+
+\begin_layout Plain Layout
+
+\change_inserted -712698321 1785747231
+
+\backslash
+backmatter
+\change_unchanged
+
+\end_layout
+
+\end_inset
+
+ document division.
+\change_unchanged
+
 \end_layout
 
 \begin_layout Description
@@ -12607,6 +12708,8 @@ status collapsed
 
 \change_inserted -712698321 1785664317
 ParfillskipNone
+\change_unchanged
+
 \end_layout
 
 \end_inset
@@ -12619,6 +12722,8 @@ status collapsed
 
 \change_inserted -712698321 1785664297
 string
+\change_unchanged
+
 \end_layout
 
 \end_inset
@@ -12647,6 +12752,8 @@ status collapsed
 
 \change_inserted -712698321 1785664387
 parfullskip=none
+\change_unchanged
+
 \end_layout
 
 \end_inset
@@ -12709,6 +12816,8 @@ status collapsed
 
 \change_inserted -712698321 1785664507
 ParskipFull parskip=full
+\change_unchanged
+
 \end_layout
 
 \end_inset
@@ -12728,6 +12837,8 @@ status collapsed
 
 \change_inserted -712698321 1785664326
 ParfillskipQuarter
+\change_unchanged
+
 \end_layout
 
 \end_inset
@@ -12740,6 +12851,8 @@ status collapsed
 
 \change_inserted -712698321 1785664297
 string
+\change_unchanged
+
 \end_layout
 
 \end_inset
@@ -12752,6 +12865,8 @@ status collapsed
 
 \change_inserted -712698321 1785664574
 ParfillskipNone
+\change_unchanged
+
 \end_layout
 
 \end_inset
@@ -12784,6 +12899,8 @@ status collapsed
 
 \change_inserted -712698321 1785664337
 ParfillskipThird
+\change_unchanged
+
 \end_layout
 
 \end_inset
@@ -12796,6 +12913,8 @@ status collapsed
 
 \change_inserted -712698321 1785664331
 string
+\change_unchanged
+
 \end_layout
 
 \end_inset
@@ -12808,6 +12927,8 @@ status collapsed
 
 \change_inserted -712698321 1785664602
 ParfillskipNone
+\change_unchanged
+
 \end_layout
 
 \end_inset
diff --git a/lib/doc/UserGuide.lyx b/lib/doc/UserGuide.lyx
index 227bc04b78..3f96a26068 100644
--- a/lib/doc/UserGuide.lyx
+++ b/lib/doc/UserGuide.lyx
@@ -33664,6 +33664,78 @@ Restore value of counter:
 The dialog allows you to choose which counter you wish to modify from all 
those that are available in the current document class.
 \end_layout
 
+\begin_layout Section
+
+\change_inserted -712698321 1785747694
+Structural Units
+\end_layout
+
+\begin_layout Standard
+
+\change_inserted -712698321 1785747937
+Some document classes,
+ particularly book classes,
+ support the division of documents in structural units,
+ named 
+\begin_inset Quotes eld
+\end_inset
+
+front matter
+\begin_inset Quotes erd
+\end_inset
+
+ (for the preliminary parts of the book such as titling,
+ table of contents,
+ preface),
+ 
+\begin_inset Quotes eld
+\end_inset
+
+main matter
+\begin_inset Quotes erd
+\end_inset
+
+ (for the main part),
+ and 
+\begin_inset Quotes eld
+\end_inset
+
+back matter
+\begin_inset Quotes erd
+\end_inset
+
+ (for appendices and the like).
+ Usually,
+ front matters are differently paginated (e.g.,
+ using roman numbering),
+ whereas main matters start with page 1.
+ Back matters often do not use section numbering.
+ The details how the three units are supposed to look are defined in the class.
+\end_layout
+
+\begin_layout Standard
+
+\change_inserted -712698321 1785748007
+In \SpecialChar LyX
+,
+ you can start these units via 
+\family sans
+Document\SpecialChar menuseparator
+Start
+\begin_inset space ~
+\end_inset
+
+Structural Unit
+\begin_inset space ~
+\end_inset
+
+Here
+\family default
+ with the classes that support these divisions.
+\change_unchanged
+
+\end_layout
+
 \begin_layout Section
 Appendices
 \begin_inset Index idx
diff --git a/lib/doc/de/Customization.lyx b/lib/doc/de/Customization.lyx
index 5efed2f20e..4ec0000336 100644
--- a/lib/doc/de/Customization.lyx
+++ b/lib/doc/de/Customization.lyx
@@ -9966,6 +9966,82 @@ nolink "false"
 \begin_inset Flex Code
 status collapsed
 
+\begin_layout Plain Layout
+ProvidesMatters
+\end_layout
+
+\end_inset
+
+ [
+\begin_inset Flex Code
+status collapsed
+
+\begin_layout Plain Layout
+
+\emph on
+0
+\end_layout
+
+\end_inset
+
+
+\begin_inset space \thinspace{}
+\end_inset
+
+
+\begin_inset Flex Code
+status collapsed
+
+\begin_layout Plain Layout
+1
+\end_layout
+
+\end_inset
+
+] zeigt an ob die Klasse die Strukturierung des Dokuments mittels 
+\begin_inset Flex Code
+status collapsed
+
+\begin_layout Plain Layout
+
+\backslash
+frontmatter
+\end_layout
+
+\end_inset
+
+,
+ 
+\begin_inset Flex Code
+status collapsed
+
+\begin_layout Plain Layout
+
+\backslash
+mainmatter
+\end_layout
+
+\end_inset
+
+ und 
+\begin_inset Flex Code
+status collapsed
+
+\begin_layout Plain Layout
+
+\backslash
+backmatter
+\end_layout
+
+\end_inset
+
+ unterstützt.
+\end_layout
+
+\begin_layout Description
+\begin_inset Flex Code
+status collapsed
+
 \begin_layout Plain Layout
 ProvidesModule
 \end_layout
diff --git a/lib/doc/de/UserGuide.lyx b/lib/doc/de/UserGuide.lyx
index e5ab356c92..60e0a9ee0a 100644
--- a/lib/doc/de/UserGuide.lyx
+++ b/lib/doc/de/UserGuide.lyx
@@ -32850,6 +32850,82 @@ In diesem Dialog können Sie aus allen Zählern,
  den Sie ändern möchten.
 \end_layout
 
+\begin_layout Section
+Strukturelle Einheiten
+\end_layout
+
+\begin_layout Standard
+Manche Dokumentklassen,
+ besonders die für Bücher,
+ unterstützen die Einteilung von Dokumenten in strukturelle Einheiten,
+ nämlich Vorspann (engl.
+ 
+\begin_inset Quotes gld
+\end_inset
+
+
+\lang english
+front matter
+\lang ngerman
+
+\begin_inset Quotes grd
+\end_inset
+
+,
+ für die Titelei,
+ das Inhaltsverzeichnis,
+ Vorworte usw.),
+ Hauptteil (engl.
+ 
+\begin_inset Quotes gld
+\end_inset
+
+
+\lang english
+main matter
+\lang ngerman
+
+\begin_inset Quotes grd
+\end_inset
+
+) und Nachspann (engl.
+ 
+\begin_inset Quotes gld
+\end_inset
+
+
+\lang english
+back matter
+\lang ngerman
+
+\begin_inset Quotes grd
+\end_inset
+
+,
+ für Anhänge und Ähnliches).
+ Meistens ist der Vorspann anders paginiert (bspw.
+ mit römischen Seitenzahlen),
+ wobei der Hauptteil dann mit Seite
+\begin_inset space ~
+\end_inset
+
+1 beginnt.
+ Der Nachspann verwendet oft keine Abschnittsnummerierung.
+ Wie es genau ausschaut definiert aber die jeweilige Klasse.
+\end_layout
+
+\begin_layout Standard
+In \SpecialChar LyX
+ können Sie diese Einheiten über 
+\family sans
+Dokument\SpecialChar menuseparator
+Strukturelle Einheit hier beginnen 
+\family default
+im Fall der Klassen,
+ die solche Unterteilungen unterstützen,
+ markieren.
+\end_layout
+
 \begin_layout Section
 Anhänge
 \begin_inset CommandInset label
diff --git a/lib/layouts/amsbook.layout b/lib/layouts/amsbook.layout
index 5546c9dc3b..c82c372528 100644
--- a/lib/layouts/amsbook.layout
+++ b/lib/layouts/amsbook.layout
@@ -17,6 +17,7 @@ Format 119
 Columns                 1
 Sides                   2
 PageStyle               Headers
+ProvidesMatters         1
 DocBookRoot             book
 
 Provides amsmath         1
diff --git a/lib/layouts/book.layout b/lib/layouts/book.layout
index 3e56a6cdc2..e13894d6be 100644
--- a/lib/layouts/book.layout
+++ b/lib/layouts/book.layout
@@ -11,10 +11,10 @@ Format 119
 Input stdclass.inc
 Input numreport.inc
 
-
 Sides                 2
 PageStyle             Headings
-DocBookRoot                  book
+ProvidesMatters       1
+DocBookRoot          book
 DocBookForceAbstract  true
 
 
diff --git a/lib/layouts/memoir.layout b/lib/layouts/memoir.layout
index 8d52a03e59..9f3502cbe0 100644
--- a/lib/layouts/memoir.layout
+++ b/lib/layouts/memoir.layout
@@ -12,6 +12,7 @@ Columns            1
 Sides              2
 SecNumDepth        1
 TocDepth           1
+ProvidesMatters    1
 DefaultStyle       Standard
 PageStyle          Headings
 PageSize           letter
diff --git a/lib/layouts/recipebook.layout b/lib/layouts/recipebook.layout
index 08ced25e8e..8295994c51 100644
--- a/lib/layouts/recipebook.layout
+++ b/lib/layouts/recipebook.layout
@@ -35,6 +35,7 @@ Input numreport.inc
 SecNumDepth            2
 TocDepth               3
 PageStyle              Headings
+ProvidesMatters         1
 DocBookRoot            book
 
 # There is no abstract environment in scrbook.cls
diff --git a/lib/layouts/scrbook.layout b/lib/layouts/scrbook.layout
index 4e5860c409..6ba036fa70 100644
--- a/lib/layouts/scrbook.layout
+++ b/lib/layouts/scrbook.layout
@@ -10,6 +10,7 @@ Input numreport.inc
 
 Sides                  2
 PageStyle              Headings
+ProvidesMatters         1
 DocBookRoot            book
 
 NoStyle Abstract
diff --git a/lib/layouts/tufte-book.layout b/lib/layouts/tufte-book.layout
index fb683e4ff4..c91192f04c 100644
--- a/lib/layouts/tufte-book.layout
+++ b/lib/layouts/tufte-book.layout
@@ -12,6 +12,7 @@ Provides natbib-internal 1
 
 Sides          2
 PageStyle      Headings
+ProvidesMatters 1
 DocBookRoot    book
 
 ClassOptions
diff --git a/lib/lyx2lyx/lyx_2_6.py b/lib/lyx2lyx/lyx_2_6.py
index 5eed5cbe05..c123d1fb64 100644
--- a/lib/lyx2lyx/lyx_2_6.py
+++ b/lib/lyx2lyx/lyx_2_6.py
@@ -1600,6 +1600,35 @@ def revert_parfillskip(document):
     if preamble != "":
         add_to_preamble(document, preamble)
 
+
+def revert_matters(document):
+    """Revert \\frontmatter, \\mainmatter and \\backmatter to ERT"""
+
+    for matter in ["front", "main", "back"]:
+        i = 0
+        while True:
+            i = find_token(document.body, "\\start_of_matter " + matter, i)
+            if i == -1:
+                break
+            
+            # Add ERT
+            lay = get_containing_layout(document.body, i)
+            if lay == False:
+                document.warning("Matter has no layout!")
+                i += 1
+                continue
+            beglay = lay[1]
+            endlay = find_end_of_layout(document.body, beglay)
+            if endlay == False:
+                document.warning("Matter has no endlayout!")
+                i += 1
+                continue
+            
+            layb = document.body[beglay]
+            laye = document.body[endlay]
+            document.body[i : i + 1] = put_cmd_in_ert(["\\" + matter + 
"matter"]) + ["", laye, "", layb]
+            i += 1
+
 ##
 # Conversion hub
 #
@@ -1618,11 +1647,13 @@ convert = [
     [653, []],
     [654, []],
     [655, []],
-    [656, []]
+    [656, []],
+    [657, []]
 ]
 
 
 revert = [
+    [656, [revert_matters]],
     [655, [revert_parfillskip]],
     [654, [revert_hex32colors]],
     [653, [revert_mintedlangs]],
diff --git a/lib/lyx2lyx/parser_tools.py b/lib/lyx2lyx/parser_tools.py
index 839b922278..3e3de99125 100644
--- a/lib/lyx2lyx/parser_tools.py
+++ b/lib/lyx2lyx/parser_tools.py
@@ -684,6 +684,7 @@ def get_containing_layout(lines, i):
         "\\indent-toggle",
         "\\leftindent",
         "\\start_of_appendix",
+        "\\start_of_matter",
         "\\paragraph_spacing",
         "\\align",
         "\\labelwidthstring",
diff --git a/lib/scripts/layout2layout.py b/lib/scripts/layout2layout.py
index f506b5deb5..cbaa08904b 100644
--- a/lib/scripts/layout2layout.py
+++ b/lib/scripts/layout2layout.py
@@ -9,7 +9,7 @@
 # This script will update a .layout file to current format
 
 # The latest layout format is also defined in src/TextClass.cpp
-currentFormat = 119
+currentFormat = 120
 
 
 # Incremented to format 4, 6 April 2007, lasgouttes
@@ -404,6 +404,9 @@ currentFormat = 119
 # New class tags ParfillskipNone, ParfillskipQuarter
 # and ParfillskipThird
 
+# Incremented to format 120, 3 August 2026 by spitz
+# New class tag ProvidesMatters
+
 # Do not forget to document format change in Customization
 # Manual (section "Declaring a new text class").
 
@@ -743,7 +746,7 @@ def convert(lines, end_format):
                 i += 1
             continue
 
-        if 101 <= format <= 119:
+        if 101 <= format <= 120:
             # nothing to do.
             i += 1
             continue
diff --git a/lib/ui/stdmenus.inc b/lib/ui/stdmenus.inc
index cecf4616c3..edc736a62a 100644
--- a/lib/ui/stdmenus.inc
+++ b/lib/ui/stdmenus.inc
@@ -569,6 +569,7 @@ Menuset
                Item "LaTeX Log|L" "dialog-show latexlog"
                Item "Processing Errors|r" "errors-show"
                Item "Start Appendix Here|x" "appendix"
+               OptSubmenu "Start Structural Unit Here" "matter"
                Separator
                ViewFormats
                UpdateFormats
@@ -592,6 +593,12 @@ Menuset
                Item "Show Changes in Output|S" "changes-output"
        End
 
+       Menu "matter"
+               OptItem "Front Matter" "matter front"
+               OptItem "Main Matter" "matter main"
+               OptItem "Back Matter" "matter back"
+       End
+
 #
 # NAVIGATE MENU
 #
diff --git a/src/Buffer.cpp b/src/Buffer.cpp
index 85124d0f1f..e0dbb0659b 100644
--- a/src/Buffer.cpp
+++ b/src/Buffer.cpp
@@ -5405,6 +5405,11 @@ void Buffer::Impl::setLabel(ParIterator & it, UpdateType 
utype) const
        }
        par.params().appendix(counters.appendix());
 
+       if (par.params().startOfMatter() != MATTER_NONE) {
+               counters.mainMatter(par.params().startOfMatter() == 
MATTER_MAIN);
+       }
+       par.params().mainMatter(counters.mainMatter());
+
        // Compute the item depth of the paragraph
        par.itemdepth = getItemDepth(it);
 
@@ -5494,8 +5499,10 @@ void Buffer::Impl::setLabel(ParIterator & it, UpdateType 
utype) const
                docstring const & lcounter = layout.counter;
                if (!lcounter.empty()) {
                        if (layout.toclevel <= bp.secnumdepth
-                                               && (layout.latextype != 
LATEX_ENVIRONMENT
-                                       || 
it.text()->isFirstInSequence(it.pit()))) {
+                                       && (layout.latextype != 
LATEX_ENVIRONMENT
+                                               || 
it.text()->isFirstInSequence(it.pit()))
+                                       // No numbering for chapters outside of 
main matter.
+                                       && (layout.toclevel != 0 || 
par.params().mainMatter())) {
                                if (counters.hasCounter(lcounter))
                                        counters.step(lcounter, utype);
                                
par.params().labelString(par.expandLabel(layout, bp));
diff --git a/src/Counters.cpp b/src/Counters.cpp
index a93274558a..12e6867187 100644
--- a/src/Counters.cpp
+++ b/src/Counters.cpp
@@ -250,7 +250,8 @@ Counter::StringMap & Counter::flatLabelStrings(bool 
in_appendix) const
 }
 
 
-Counters::Counters() : appendix_(false), subfloat_(false), longtable_(false)
+Counters::Counters() : appendix_(false), main_matter_(true), subfloat_(false),
+       longtable_(false)
 {
        layout_stack_.push_back(nullptr);
        counter_stack_.push_back(from_ascii(""));
@@ -444,6 +445,7 @@ docstring const & Counters::latexName(docstring const & 
cntr) const
 void Counters::reset()
 {
        appendix_ = false;
+       main_matter_ = true;
        subfloat_ = false;
        current_float_.erase();
        for (auto & ctr : counterList_)
diff --git a/src/Counters.h b/src/Counters.h
index d522a7b757..e1265b8ccb 100644
--- a/src/Counters.h
+++ b/src/Counters.h
@@ -16,6 +16,7 @@
 #define COUNTERS_H
 
 #include "OutputEnums.h"
+#include "LayoutEnums.h"
 
 #include "support/docstring.h"
 
@@ -149,6 +150,8 @@ public:
        /// \return true on success
        bool read(support::Lexer & lex, docstring const & name, bool makenew);
        ///
+       void setEnabled(docstring const & ctr, bool const enabled);
+       ///
        void set(docstring const & ctr, int val);
        ///
        void addto(docstring const & ctr, int val);
@@ -210,6 +213,10 @@ public:
        bool appendix() const { return appendix_; }
        /// Set the state variable indicating whether we are in appendix.
        void appendix(bool a) { appendix_ = a; }
+       /// Are we in main matter?
+       bool mainMatter() const { return main_matter_; }
+       /// Set the state variable indicating whether which matter we are in.
+       void mainMatter(bool m) { main_matter_ = m; }
        /// Returns the current enclosing float.
        std::string const & current_float() const { return current_float_; }
        /// Sets the current enclosing float.
@@ -275,6 +282,8 @@ private:
        CounterList counterList_;
        /// Are we in an appendix?
        bool appendix_;
+       /// Are we in the main matter?
+       bool main_matter_;
        /// The current enclosing float.
        std::string current_float_;
        /// Are we in a subfloat?
diff --git a/src/FuncCode.h b/src/FuncCode.h
index 4f78596b9c..d8ad69e67b 100644
--- a/src/FuncCode.h
+++ b/src/FuncCode.h
@@ -516,6 +516,7 @@ enum FuncCode
        LFUN_ERROR_NEXT,                // spitz 20200101,
        /// 405
        LFUN_BUFFER_UPDATE_EXTERNAL,    // spitz 20250406,
+       LFUN_MATTER,                    // spitz/daniel, 20260805
        LFUN_LASTACTION                 // end of the table
 };
 
diff --git a/src/LayoutEnums.h b/src/LayoutEnums.h
index 48e849f1a4..3215311806 100644
--- a/src/LayoutEnums.h
+++ b/src/LayoutEnums.h
@@ -158,6 +158,18 @@ enum ArgPassThru {
        PT_TRUE
 };
 
+///
+enum Matter {
+       ///
+       MATTER_NONE,
+       ///
+       MATTER_FRONT,
+       ///
+       MATTER_MAIN,
+       ///
+       MATTER_BACK
+};
+
 } // namespace lyx
 
 #endif
diff --git a/src/LyXAction.cpp b/src/LyXAction.cpp
index bd9c9df054..f7e174e1bf 100644
--- a/src/LyXAction.cpp
+++ b/src/LyXAction.cpp
@@ -298,6 +298,16 @@ void LyXAction::init()
  */
                { LFUN_APPENDIX, "appendix", Noop, Edit },
 
+/*!
+ * \var lyx::FuncCode lyx::LFUN_MATTER
+ * \li Action: Start (or unstart) matter on the given cursor position.
+ * \li Syntax: matter <type>
+ * \li Params: <type>: front, main, back.
+ * \li Origin: daniel/spitz, 3 Aug 2026
+ * \endvar
+ */
+               { LFUN_MATTER, "matter", Noop, Edit },
+
 
 /*!
  * \var lyx::FuncCode lyx::LFUN_ARGUMENT_INSERT
diff --git a/src/ParagraphParameters.cpp b/src/ParagraphParameters.cpp
index 32dc44900b..ec50faafdf 100644
--- a/src/ParagraphParameters.cpp
+++ b/src/ParagraphParameters.cpp
@@ -41,6 +41,7 @@ static char const * const string_align[] = {
 ParagraphParameters::ParagraphParameters()
        : noindent_(false),
                start_of_appendix_(false), appendix_(false),
+               start_of_matter_(MATTER_NONE), main_matter_(true),
          align_(LYX_ALIGN_LAYOUT), depth_(0)
 {}
 
@@ -132,6 +133,30 @@ void ParagraphParameters::appendix(bool a)
 }
 
 
+Matter ParagraphParameters::startOfMatter() const
+{
+       return start_of_matter_;
+}
+
+
+void ParagraphParameters::startOfMatter(Matter som)
+{
+       start_of_matter_ = som;
+}
+
+
+bool ParagraphParameters::mainMatter() const
+{
+       return main_matter_;
+}
+
+
+void ParagraphParameters::mainMatter(bool m)
+{
+       main_matter_ = m;
+}
+
+
 docstring const & ParagraphParameters::labelString() const
 {
        return labelstring_;
@@ -207,6 +232,15 @@ void ParagraphParameters::read(Lexer & lex, bool merge)
                        leftIndent(value);
                } else if (token == "\\start_of_appendix") {
                        startOfAppendix(true);
+               } else if (token == "\\start_of_matter") {
+                       lex.next();
+                       string const tmp = rtrim(lex.getString());
+                       if (tmp == "front")
+                               startOfMatter(MATTER_FRONT);
+                       else if (tmp == "main")
+                               startOfMatter(MATTER_MAIN);
+                       else if (tmp == "back")
+                               startOfMatter(MATTER_BACK);
                } else if (token == "\\paragraph_spacing") {
                        lex.next();
                        string const tmp = rtrim(lex.getString());
@@ -269,6 +303,18 @@ void ParagraphParameters::write(ostream & os) const
        if (startOfAppendix())
                os << "\\start_of_appendix\n";
 
+       // Start of front matter?
+       if (startOfMatter() != MATTER_NONE) {
+               string tmp;
+               if (startOfMatter() == MATTER_FRONT)
+                       tmp = "front";
+               else if (startOfMatter() == MATTER_MAIN)
+                       tmp = "main";
+               else if (startOfMatter() == MATTER_BACK)
+                       tmp = "back";
+               os << "\\start_of_matter " << tmp << "\n";
+       }
+
        // Noindent?
        if (noindent())
                os << "\\noindent\n";
diff --git a/src/ParagraphParameters.h b/src/ParagraphParameters.h
index 540ea95679..9ff42b0cf2 100644
--- a/src/ParagraphParameters.h
+++ b/src/ParagraphParameters.h
@@ -63,6 +63,14 @@ public:
        ///
        void appendix(bool);
        ///
+       Matter startOfMatter() const;
+       ///
+       void startOfMatter(Matter);
+       ///
+       bool mainMatter() const;
+       ///
+       void mainMatter(bool);
+       ///
        docstring const & labelString() const;
        ///
        void labelString(docstring const &);
@@ -102,6 +110,10 @@ private:
        ///
        bool appendix_;
        ///
+       Matter start_of_matter_;
+       ///
+       bool main_matter_;
+       ///
        LyXAlignment align_;
        ///
        depth_type depth_;
diff --git a/src/RowPainter.cpp b/src/RowPainter.cpp
index 6d2dd5ec65..8a6a5a6800 100644
--- a/src/RowPainter.cpp
+++ b/src/RowPainter.cpp
@@ -30,6 +30,7 @@
 #include "ParagraphList.h"
 #include "ParagraphParameters.h"
 #include "Text.h"
+#include "TextClass.h"
 #include "TextMetrics.h"
 
 #include "frontends/FontMetrics.h"
@@ -324,12 +325,14 @@ void RowPainter::paintChangeBar() const
 void RowPainter::paintAppendix() const
 {
        // only draw the appendix frame once (for the main text)
-       if (!par_.params().appendix() || !text_.isMainText())
+       if ((!par_.params().appendix() && par_.params().mainMatter())
+           || !text_.isMainText())
                return;
 
        int y = yo_ - row_.ascent();
 
-       if (par_.params().startOfAppendix())
+       if (par_.params().startOfAppendix()
+           || par_.params().startOfMatter() != MATTER_NONE)
                y += 2 * defaultRowHeight();
 
        pi_.pain.line(1, y, 1, yo_ + row_.descent(), Color_appendix);
@@ -381,7 +384,7 @@ void RowPainter::paintDepthBar() const
 }
 
 
-void RowPainter::paintAppendixStart(int y) const
+void RowPainter::paintMatterStart(int y, docstring const & label) const
 {
        FontInfo pb_font = sane_font;
        pb_font.setColor(Color_appendix);
@@ -391,7 +394,6 @@ void RowPainter::paintAppendixStart(int y) const
        int a = 0;
        int d = 0;
 
-       docstring const label = _("Appendix");
        theFontMetrics(pb_font).rectText(label, w, a, d);
 
        int const text_start = int(xo_ + (tm_.width() - w) / 2);
@@ -423,21 +425,46 @@ void RowPainter::paintTooLargeMarks(bool const left, bool 
const right) const
 
 void RowPainter::paintFirst() const
 {
+       Layout const & TOCLayout = 
pi_.base.bv->buffer().params().documentClass().getTOCLayout();
        Layout const & layout = par_.layout();
 
+       docstring label;
        // start of appendix?
        if (par_.params().startOfAppendix())
-           paintAppendixStart(yo_ - row_.ascent() + 2 * defaultRowHeight());
+               label = _("Appendix");
+       // start of matter?
+       if (par_.params().startOfMatter() != MATTER_NONE) {
+               if (!label.empty())
+                       label += " & ";
+               switch (par_.params().startOfMatter()) {
+                       case MATTER_FRONT:
+                               label += _("Front Matter");
+                               break;
+                       case MATTER_MAIN:
+                               label += _("Main Matter");
+                               break;
+                       case MATTER_BACK:
+                               label += _("Back Matter");
+                               break;
+               }
+               // Matters are supported only by book (sectioning toplevel 0)
+               if (TOCLayout.toclevel != 0)
+                       label += _(" (Senseless!!!)");
+       }
+       if (!label.empty())
+               paintMatterStart(yo_ - row_.ascent() + 2 * defaultRowHeight(), 
label);
 
        bool const is_first =
                text_.isFirstInSequence(row_.pit()) || 
!layout.isParagraphGroup();
        //lyxerr << "paintFirst: " << par_.id() << " is_seq: " << is_seq << 
endl;
 
-       if (layout.labelIsInline()
-           && (layout.labeltype != LABEL_STATIC || is_first))
-               paintLabel();
-       else if (is_first && layout.labelIsAbove())
-               paintTopLevelLabel();
+       if (par_.layout().toclevel != 0 || par_.params().mainMatter()) {
+               if (layout.labelIsInline()
+                       && (layout.labeltype != LABEL_STATIC || is_first))
+                       paintLabel();
+               else if (is_first && layout.labelIsAbove())
+                       paintTopLevelLabel();
+       }
 }
 
 
diff --git a/src/RowPainter.h b/src/RowPainter.h
index 918fc9f989..ce77554ac5 100644
--- a/src/RowPainter.h
+++ b/src/RowPainter.h
@@ -54,7 +54,7 @@ private:
        void paintTextDecoration(Row::Element const & e) const;
        void paintMisspelledMark(Row::Element const & e) const;
        void paintChange(Row::Element const & e) const;
-       void paintAppendixStart(int y) const;
+       void paintMatterStart(int y, docstring const & label) const;
        void paintInset(Row::Element const & e) const;
 
        /// return the label font for this row (end label when \c end is true)
diff --git a/src/Text.cpp b/src/Text.cpp
index 22e1c2c87b..0a8be834fe 100644
--- a/src/Text.cpp
+++ b/src/Text.cpp
@@ -798,9 +798,11 @@ static void breakParagraph(Text & text, pit_type 
par_offset, pos_type pos,
 
        if (!isempty) {
                bool const soa = par.params().startOfAppendix();
+               Matter const som = par.params().startOfMatter();
                par.params().clear();
                // do not lose start of appendix marker (bug 4212)
                par.params().startOfAppendix(soa);
+               par.params().startOfMatter(som);
                par.setPlainOrDefaultLayout(bparams.documentClass());
        }
 
@@ -3294,10 +3296,13 @@ bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
                       min(old.pit() + 1, old.lastpit()));
        ParagraphList & plist = old.text()->paragraphs();
        bool const soa = oldpar.params().startOfAppendix();
+       Matter const som = oldpar.params().startOfMatter();
        plist.erase(plist.iterator_at(old.pit()));
        // do not lose start of appendix marker (bug 4212)
        if (soa && old.pit() < pit_type(plist.size()))
                plist[old.pit()].params().startOfAppendix(true);
+       if (som != MATTER_NONE && old.pit() < pit_type(plist.size()))
+               plist[old.pit()].params().startOfMatter(som);
 
        // see #warning (FIXME?) above
        if (cur.depth() >= old.depth()) {
@@ -4135,6 +4140,27 @@ void Text::dispatch(Cursor & cur, FuncRequest & cmd)
                break;
        }
 
+       case LFUN_MATTER: {
+               Paragraph & par = cur.paragraph();
+               docstring const & arg = cmd.argument();
+               Matter start = MATTER_NONE;
+               if (arg == "front") {
+                       start = par.params().startOfMatter() == MATTER_FRONT
+                                                  ? MATTER_NONE : MATTER_FRONT;
+               } else if (arg == "main") {
+                       start = par.params().startOfMatter() == MATTER_MAIN
+                                                  ? MATTER_NONE : MATTER_MAIN;
+               } else if (arg == "back") {
+                       start = par.params().startOfMatter() == MATTER_BACK
+                                                  ? MATTER_NONE : MATTER_BACK;
+               }
+               cur.recordUndo();
+               par.params().startOfMatter(start);
+               // we can set the refreshing parameters now
+               cur.forceBufferUpdate();
+               break;
+       }
+
        case LFUN_WORD_DELETE_FORWARD:
                if (cur.selection())
                        cutSelection(cur, false);
@@ -6396,6 +6422,24 @@ bool Text::getStatus(Cursor & cur, FuncRequest const & 
cmd,
                status.setOnOff(cur.paragraph().params().startOfAppendix());
                break;
 
+       case LFUN_MATTER:
+               if (!cur.buffer()->params().documentClass().hasMatters()) {
+                       enable = false;
+                       break;
+               }
+               // FIXME We really should not allow this to be put, e.g.,
+               // in a footnote, or in ERT. But it would make sense in a
+               // branch, so I'm not sure what to do.
+               if (cmd.argument() == "front")
+                       
status.setOnOff(cur.paragraph().params().startOfMatter() == MATTER_FRONT);
+               else if (cmd.argument() == "main")
+                       
status.setOnOff(cur.paragraph().params().startOfMatter() == MATTER_MAIN);
+               else if (cmd.argument() == "back")
+                       
status.setOnOff(cur.paragraph().params().startOfMatter() == MATTER_BACK);
+               else
+                       enable = false;
+               break;
+
        case LFUN_DIALOG_SHOW_NEW_INSET:
                if (cmd.argument() == "bibitem")
                        code = BIBITEM_CODE;
diff --git a/src/TextClass.cpp b/src/TextClass.cpp
index d908473aff..c0477533e3 100644
--- a/src/TextClass.cpp
+++ b/src/TextClass.cpp
@@ -60,7 +60,7 @@ namespace lyx {
 // You should also run the development/tools/updatelayouts.py script,
 // to update the format of all of our layout files.
 //
-int const LAYOUT_FORMAT = 119; // spitz: ParfillSkip
+int const LAYOUT_FORMAT = 120; // spitz: ProvidesMatters
 
 
 // Layout format for the current lyx file format. Controls which format is
@@ -142,7 +142,7 @@ TextClass::TextClass()
          columns_(1), sides_(OneSide), secnumdepth_(3), tocdepth_(3), 
outputType_(LATEX),
          outputFormat_("latex"), has_output_format_(false), 
defaultfont_(sane_font), 
          titletype_(TITLE_COMMAND_AFTER), titlename_("maketitle"),
-         min_toclevel_(0), max_toclevel_(0), maxcitenames_(2),
+         min_toclevel_(0), max_toclevel_(0), has_matters_(false), 
maxcitenames_(2),
          cite_full_author_list_(true), bibintoc_(false)
 {}
 
@@ -185,6 +185,7 @@ enum TextClassTags {
        TC_DEFAULTFONT,
        TC_SECNUMDEPTH,
        TC_TOCDEPTH,
+       TC_PROVIDESMATTERS,
        TC_CLASSOPTIONS,
        TC_SPECIALCHARS,
        TC_PREAMBLE,
@@ -278,6 +279,7 @@ LexerKeyword textClassTags[] = {
        { "preamble",          TC_PREAMBLE },
        { "provideinsetlayout", TC_PROVIDEINSETLAYOUT },
        { "provides",          TC_PROVIDES },
+       { "providesmatters",   TC_PROVIDESMATTERS },
        { "providesmodule",    TC_PROVIDESMODULE },
        { "providestyle",      TC_PROVIDESTYLE },
        { "requires",          TC_REQUIRES },
@@ -661,6 +663,11 @@ TextClass::ReturnValues TextClass::read(Lexer & lexrc, 
ReadType rt)
                        tocdepth_ = lexrc.getInteger();
                        break;
 
+               case TC_PROVIDESMATTERS:
+                       if (lexrc.next())
+                               has_matters_ = lexrc.getBool();
+                       break;
+
                // First step to support options
                case TC_CLASSOPTIONS:
                        readClassOptions(lexrc);
diff --git a/src/TextClass.h b/src/TextClass.h
index f7673b10d8..e8bf0d768e 100644
--- a/src/TextClass.h
+++ b/src/TextClass.h
@@ -209,6 +209,8 @@ public:
        std::string const & outputFormat() const { return outputFormat_; }
        /// Does this class redefine the output format?
        bool hasOutputFormat() const { return has_output_format_; }
+       /// Does this class provide \forntmatter, \mainmatter, and \backmatter?
+       bool hasMatters() const { return has_matters_; }
        /// Return the non-localised names for the toc types.
        std::map<std::string, docstring> const &
                outlinerNames() const { return outliner_names_; }
@@ -368,6 +370,8 @@ protected:
        int min_toclevel_;
        /// The maximal TocLevel of sectioning layouts
        int max_toclevel_;
+       /// Does this class provide \frontmatter, \mainmatter, and \backmatter?
+       bool has_matters_;
        /// Citation formatting information
        std::map<CiteEngineType, std::map<std::string, std::string> > 
cite_formats_;
        /// Citation macros
diff --git a/src/TextMetrics.cpp b/src/TextMetrics.cpp
index fe4f775809..06e3fce2db 100644
--- a/src/TextMetrics.cpp
+++ b/src/TextMetrics.cpp
@@ -1309,14 +1309,15 @@ int TextMetrics::parTopSpacing(pit_type const pit) const
                        && pars[pit - 1].getDepth() == 0))) {
                asc += bparams.getDefSkip().inPixels(*bv_);
        }
-
-       if (par.params().startOfAppendix())
+       // extra row height for start of appendix and matters
+       if (par.params().startOfAppendix() || par.params().startOfMatter() != 
MATTER_NONE)
                asc += int(3 * dh);
 
        // special code for the top label
        if (layout.labelIsAbove()
            && (!layout.isParagraphGroup() || text_->isFirstInSequence(pit))
-           && !par.labelString().empty()) {
+           && !par.labelString().empty()
+               && (layout.toclevel != 0 || par.params().mainMatter())) {
                FontInfo labelfont = text_->labelFont(par);
                FontMetrics const & lfm = theFontMetrics(labelfont);
                asc += int(lfm.maxHeight() * layout.spacing.getValue()
diff --git a/src/output_latex.cpp b/src/output_latex.cpp
index 94ed36b3f3..8aef8c75d0 100644
--- a/src/output_latex.cpp
+++ b/src/output_latex.cpp
@@ -1237,6 +1237,21 @@ void TeXOnePar(Buffer const & buf,
                if (par.params().startOfAppendix()) {
                        os << "\n\\appendix\n";
                }
+               if (bparams.documentClass().hasMatters()) {
+                       switch (par.params().startOfMatter()) {
+                               case MATTER_FRONT:
+                                       os << "\n\\frontmatter\n";
+                                       break;
+                               case MATTER_MAIN:
+                                       os << "\n\\mainmatter\n";
+                                       break;
+                               case MATTER_BACK:
+                                       os << "\n\\backmatter\n";
+                                       break;
+                               default:
+                                       break;
+                       }
+               }
 
                // InTitle commands must use switches (not environments)
                // inside the commands (see #9332)
diff --git a/src/tex2lyx/text.cpp b/src/tex2lyx/text.cpp
index a8ebbf9364..31b509ffcc 100644
--- a/src/tex2lyx/text.cpp
+++ b/src/tex2lyx/text.cpp
@@ -3945,6 +3945,78 @@ void parse_text(Parser & p, ostream & os, unsigned 
flags, bool outer,
                        continue;
                }
 
+               if (t.cs() == "frontmatter") {
+                       context.add_par_extra_stuff("\\start_of_matter 
front\n");
+                       // We need to start a new paragraph. Otherwise the
+                       // frontmatter in 'bla\frontmatter\chapter{' would start
+                       // too late.
+                       context.new_paragraph(os);
+                       // We need to make sure that the paragraph is
+                       // generated even if it is empty. Otherwise the
+                       // frontmatter in '\par\frontmatter\par\chapter{' would
+                       // start too late.
+                       context.check_layout(os);
+                       // FIXME: This is a hack to prevent paragraph
+                       // deletion if it is empty. Handle this better!
+                       output_comment(p, os,
+                               "dummy comment inserted by tex2lyx to "
+                               "ensure that this paragraph is not empty",
+                               context);
+                       // Both measures above may generate an additional
+                       // empty paragraph, but that does not hurt, because
+                       // whitespace does not matter here.
+                       eat_whitespace(p, os, context, true);
+                       continue;
+               }
+
+               if (t.cs() == "mainmatter") {
+                       context.add_par_extra_stuff("\\start_of_matter main\n");
+                       // We need to start a new paragraph. Otherwise the
+                       // mainmatter in 'bla\mainmatter\chapter{' would start
+                       // too late.
+                       context.new_paragraph(os);
+                       // We need to make sure that the paragraph is
+                       // generated even if it is empty. Otherwise the
+                       // mainmatter in '\par\mainmatter\par\chapter{' would
+                       // start too late.
+                       context.check_layout(os);
+                       // FIXME: This is a hack to prevent paragraph
+                       // deletion if it is empty. Handle this better!
+                       output_comment(p, os,
+                               "dummy comment inserted by tex2lyx to "
+                               "ensure that this paragraph is not empty",
+                               context);
+                       // Both measures above may generate an additional
+                       // empty paragraph, but that does not hurt, because
+                       // whitespace does not matter here.
+                       eat_whitespace(p, os, context, true);
+                       continue;
+               }
+
+               if (t.cs() == "backmatter") {
+                       context.add_par_extra_stuff("\\start_of_matter back\n");
+                       // We need to start a new paragraph. Otherwise the
+                       // mainmatter in 'bla\mainmatter\chapter{' would start
+                       // too late.
+                       context.new_paragraph(os);
+                       // We need to make sure that the paragraph is
+                       // generated even if it is empty. Otherwise the
+                       // mainmatter in '\par\mainmatter\par\chapter{' would
+                       // start too late.
+                       context.check_layout(os);
+                       // FIXME: This is a hack to prevent paragraph
+                       // deletion if it is empty. Handle this better!
+                       output_comment(p, os,
+                               "dummy comment inserted by tex2lyx to "
+                               "ensure that this paragraph is not empty",
+                               context);
+                       // Both measures above may generate an additional
+                       // empty paragraph, but that does not hurt, because
+                       // whitespace does not matter here.
+                       eat_whitespace(p, os, context, true);
+                       continue;
+               }
+
                // Must catch empty dates before findLayout is called below
                if (t.cs() == "date") {
                        eat_whitespace(p, os, context, false);
diff --git a/src/version.h b/src/version.h
index cd47b6268d..93eeff04a6 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 656 // spitz: parfillskip
-#define LYX_FORMAT_TEX2LYX 656
+#define LYX_FORMAT_LYX 657 // spitz: matters
+#define LYX_FORMAT_TEX2LYX 657
 
 #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