diff --git a/classes/resilient/book.lua b/classes/resilient/book.lua index 3fbc005..ee51ad5 100644 --- a/classes/resilient/book.lua +++ b/classes/resilient/book.lua @@ -20,10 +20,17 @@ SILE.scratch.book.headers = { none = true } +local DIVISIONNAME = { + "frontmatter", + "mainmatter", + "backmatter" +} + -- CLASS DEFINITION function class:_init (options) base._init(self, options) + self.resilientState = {} self:loadPackage("resilient.sectioning") self:loadPackage("masters") @@ -86,6 +93,7 @@ function class:_init (options) -- Package "folio" is loaded by the plain class. self:registerCommand("foliostyle", function (_, content) local styleName = SILE.documentState.documentClass:oddPage() and "folio-odd" or "folio-even" + local division = self.resilientState.division or 2 SILE.call("style:apply:paragraph", { name = styleName }, { -- Ensure proper baseline alignment with a strut rule. -- The baseline placement depends on the line output algorithm, and we cannot @@ -94,7 +102,10 @@ function class:_init (options) -- aligned folios, but the 1 is smaller than the 6 and 7, the former ascends above, -- and the latter descends below the baseline). createCommand("strut", { method = "rule"}), - subContent(content) + createCommand("style:apply:number", { + name = "folio-" .. DIVISIONNAME[division], + text = SU.ast.contentToString(content), + }) }) end) @@ -202,6 +213,18 @@ end function class:registerStyles () base.registerStyles(self) + -- Front, main, back matter + local divisionFolio = { + frontmatter = "roman", + } + for _, name in ipairs(DIVISIONNAME) do + self:registerStyle("folio-" .. name, {}, { + numbering = { + display = divisionFolio[name] or "arabic" + } + }) + end + -- Sectioning styles self:registerStyle("sectioning-base", {}, { paragraph = { before = { indent = false }, @@ -283,6 +306,14 @@ function class:registerStyles () reference = "sectioning-other-number", } }, }) + self:registerStyle("sectioning-appendix", { inherit = "sectioning-chapter" }, { + sectioning = { numberstyle= { + main = "sectioning-appendix-main-number", + header = "sectioning-appendix-head-number", + reference = "sectioning-appendix-ref-number", + }, + }, + }) self:registerStyle("sectioning-part-base-number", {}, { numbering = { display = "ROMAN" } @@ -299,6 +330,7 @@ function class:registerStyles () self:registerStyle("sectioning-part-ref-number", { inherit = "sectioning-part-base-number" }, { numbering = { before = { text ="part " } }, }) + self:registerStyle("sectioning-chapter-base-number", {}, { }) self:registerStyle("sectioning-chapter-main-number", { inherit = "sectioning-chapter-base-number" }, { @@ -314,6 +346,17 @@ function class:registerStyles () numbering = { before = { text ="chap. " } }, }) + self:registerStyle("sectioning-appendix-main-number", { inherit = "sectioning-chapter-main-number" }, { + numbering = { before = { text = "Appendix "}, + display = "ALPHA", + }, + }) + self:registerStyle("sectioning-appendix-head-number", { inherit = "sectioning-chapter-head-number" }, { + }) + self:registerStyle("sectioning-appendix-ref-number", { inherit = "sectioning-chapter-ref-number" }, { + numbering = { before = { text ="app. " } }, + }) + self:registerStyle("sectioning-other-number", {}, { numbering = { after = { text = ".", kern = "iwsp" } } }) @@ -562,6 +605,42 @@ function class:registerCommands () end end, "Text to appear on the top odd pages.") + -- front/main/back matter + + self:registerCommand("internal:division", function (options, _) + local division = SU.required(options, "division", "internal:division") + -- Always start on an odd page, so as to be consistent with the folio numbering + -- in case it is reset. + SILE.call("open-on-odd-page") + self.resilientState.division = division + -- Previous section titles (in technical mode) or chapter title (in novel mode) + -- is no longer valid (and in none mode, it's not valid anyway). + SILE.scratch.headers.odd = nil + if self.headers == "technical" then + -- In novel mode, the book title is in the even header, and is still valid + -- So we don't reset it. + -- But in technical mode, even headers contain the current chapter title, + -- invalid upon a new part. + SILE.scratch.headers.even = nil + end + -- Reset folio counter if needed (i.e. on display format change) + local current = self:getCounter("folio") + local folioSty = self:resolveStyle("folio-" .. DIVISIONNAME[division]) + local display = folioSty.numbering and folioSty.numbering.display or "arabic" + if current.display ~= display then + SILE.call("set-counter", { id = "folio", display = display, value = 1 }) + end + end) + + for div, name in ipairs(DIVISIONNAME) do + self:registerCommand(name, function (_, content) + if self.resilientState.division and self.resilientState.division >= div then + SU.error("\\" .. name .. " is not valid after a " .. DIVISIONNAME[self.resilientState.division]) + end + SILE.call("internal:division", { division = div }, content) + end, "Switch to " .. DIVISIONNAME[div] .. " division.") + end + -- Sectioning hooks and commands self:registerCommand("sectioning:part:hook", function (options, _) @@ -614,13 +693,34 @@ function class:registerCommands () end end, "Applies section hooks (footers and headers, etc.)") + self:registerCommand("appendix", function (_, _) + if self.resilientState.appendix then + SU.error("Already in the \\appendix subdivision") + end + if self.resilientState.division and self.resilientState.division < 2 then + SU.error("\\appendix is not valid in " .. DIVISIONNAME[self.resilientState.division]) + end + self.resilientState.appendix = true + SILE.call("set-multilevel-counter", { id = "sections", level = 1, value = 0 }) + end, "Switch to appendix subdivision.") + self:registerCommand("part", function (options, content) + if self.resilientState.division and self.resilientState.division ~= 2 then + -- By definition, parts are unnumbered in all divisions except the mainmatter + options.numbering = false + end options.style = "sectioning-part" + -- Allow appendices again in a new part + self.resilientState.appendix = false SILE.call("sectioning", options, content) end, "Begin a new part.") self:registerCommand("chapter", function (options, content) - options.style = "sectioning-chapter" + if not self.resilientState.appendix and self.resilientState.division and self.resilientState.division ~= 2 then + -- By definition, chapters are unnumbered in all divisions except the mainmatter + options.numbering = false + end + options.style = self.resilientState.appendix and "sectioning-appendix" or "sectioning-chapter" SILE.call("sectioning", options, content) end, "Begin a new chapter.") @@ -823,8 +923,7 @@ function class:registerCommands () -- Kind of a hack dues to restrictions with frame parsers. layout:setPaperHack(SILE.documentState.paperSize[1], SILE.documentState.paperSize[2]) - SILE.call("supereject") - SILE.typesetter:leaveHmode() + SILE.call("open-on-any-page") local oddFrameset, evenFrameset = layout:frameset() self:defineMaster({ diff --git a/examples/images/book-anatomy.svg b/examples/images/book-anatomy.svg index 6501a3d..8a2b4b9 100644 --- a/examples/images/book-anatomy.svg +++ b/examples/images/book-anatomy.svg @@ -11,7 +11,7 @@ inkscape:export-filename="book-anatomy.png" inkscape:export-xdpi="96" inkscape:export-ydpi="96" - inkscape:version="1.2.2 (732a01da63, 2022-12-09, custom)" + inkscape:version="1.3.1 (9b9bdc1480, 2023-11-25, custom)" sodipodi:docname="book-anatomy.svg" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" @@ -27,15 +27,15 @@ inkscape:deskcolor="#d1d1d1" inkscape:document-units="mm" showgrid="false" - inkscape:zoom="0.70710678" - inkscape:cx="-214.96046" - inkscape:cy="161.92745" - inkscape:window-width="1848" - inkscape:window-height="1016" - inkscape:window-x="72" - inkscape:window-y="27" + inkscape:zoom="1.4142136" + inkscape:cx="353.19984" + inkscape:cy="296.98485" + inkscape:window-width="1854" + inkscape:window-height="1011" + inkscape:window-x="0" + inkscape:window-y="0" inkscape:window-maximized="1" - inkscape:current-layer="text4277-3" /> + + + diff --git a/examples/lefevre-tuor-idril-styles.yml b/examples/lefevre-tuor-idril-styles.yml index 58f2680..e740d75 100644 --- a/examples/lefevre-tuor-idril-styles.yml +++ b/examples/lefevre-tuor-idril-styles.yml @@ -172,6 +172,12 @@ figure-caption-ref-number: before: text: "fig. " +folio-backmatter: + origin: "resilient.book" + style: + numbering: + display: "arabic" + folio-base: origin: "resilient.book" style: @@ -187,6 +193,18 @@ folio-even: before: indent: false +folio-frontmatter: + origin: "resilient.book" + style: + numbering: + display: "roman" + +folio-mainmatter: + origin: "resilient.book" + style: + numbering: + display: "arabic" + folio-odd: inherit: "folio-base" origin: "resilient.book" @@ -268,6 +286,66 @@ header-odd: origin: "resilient.book" style: +listing: + origin: "resilient.book" + style: + paragraph: + after: + vbreak: false + before: + indent: false + skip: "smallskip" + +listing-caption: + origin: "resilient.book" + style: + font: + size: "0.95em" + paragraph: + after: + skip: "medskip" + align: "center" + before: + indent: false + skip: "smallskip" + vbreak: false + sectioning: + counter: + id: "listings" + level: 1 + numberstyle: + main: "listing-caption-main-number" + reference: "listing-caption-ref-number" + settings: + bookmark: false + goodbreak: false + toclevel: 7 + +listing-caption-base-number: + origin: "resilient.book" + style: + +listing-caption-main-number: + inherit: "listing-caption-base-number" + origin: "resilient.book" + style: + font: + features: "+smcp" + numbering: + after: + kern: "iwsp" + text: "." + before: + text: "Listing " + +listing-caption-ref-number: + inherit: "listing-caption-base-number" + origin: "resilient.book" + style: + numbering: + before: + text: "listing " + lists-enumerate-alternate1: inherit: "lists-enumerate-base" origin: "resilient.lists" diff --git a/examples/lefevre-tuor-idril.sil b/examples/lefevre-tuor-idril.sil index 3aa00f6..da805b2 100644 --- a/examples/lefevre-tuor-idril.sil +++ b/examples/lefevre-tuor-idril.sil @@ -1,4 +1,4 @@ -\begin[papersize=6in x 9in, class=resilient.book, layout=ateliers demiluxe]{document} +\begin[papersize=6in x 9in, class=resilient.book, layout=ateliers demiluxe, headers=novel]{document} \use[module=packages.resilient.epigraph] \use[module=packages.resilient.abbr] \use[module=packages.background] @@ -107,7 +107,7 @@ Toute représentation ou reproduction intégrale ou partielle faite par quelque \noheaderthispage% \nofoliothispage% -\odd-running-header{Tolkien, le façonnement d’un monde} +\even-running-header{Tolkien, le façonnement d’un monde} \chapter[numbering=false]{Le conte perdu de Mercure} % The actual title had ..."— Tuor et Idril élevés au rang d’astre" % Too long with the default styling, and 2-line chapter titles look ugly. diff --git a/examples/manual-classes/classes.sil b/examples/manual-classes/classes.sil index 731238e..3588edb 100644 --- a/examples/manual-classes/classes.sil +++ b/examples/manual-classes/classes.sil @@ -291,7 +291,7 @@ The class also defines two commands for manipulating the page headers. \begin{row} \cell[valign=top]{\autodoc:command{\odd-tracked-header{}}} \cell[valign=top]{Registers the content to be used in odd running headers, tracked.} - \end{row} + \end{row} \end{ptable} \caption{Commands for manipulating page headers.} \end{table} @@ -313,6 +313,45 @@ The environment relies on the same-named style for its styling and on the \autod The environment also accepts a \autodoc:parameter{variant} option, to switch to an alternate style, assumed to be named \code{blockquote-⟨\em{variant}⟩}. +\section{Additional book divisions} + +The class supports the standard \autodoc:command{\frontmatter}, \autodoc:command{\mainmatter} and \autodoc:command{\backmatter} commands to switch between the different higher-level divisions of the document. +Obviously, these commands can only occur once in the document, and in the order given above. +They start a new page, and influence the style of folios. + +\begin{table} + \begin[cols=40%fw 50%fw, header=true]{ptable} + \begin[background=#eee]{row} + \cell[valign=top]{Style} + \cell[valign=top]{Description} + \end{row} + \begin{row} + \cell[valign=top]{\code{folio-frontmatter}} + \cell[valign=top]{Numbering style applied to folios in the front matter.} + \end{row} + \begin{row} + \cell[valign=top]{\code{folio-mainmatter}} + \cell[valign=top]{Numbering style applied to folios in the main matter.} + \end{row} + \begin{row} + \cell[valign=top]{\code{folio-backmatter}} + \cell[valign=top]{Numbering style applied to folios in the back matter.} + \end{row} + \end{ptable} + \caption{Styles used for folios in different document divisions.} +\end{table} + +Books not using these divisions assume “main” matter by default. +In the “front” and “back” matter divisions, parts and chapters are never numbered. +Not that this is not a configurable style decision, but the very definition of these divisions. +In the main matter, they are numbered by default, although this can be changed either on the sectioning command itself, or globally with adequate styles. + +The class also supports the \autodoc:command{\appendix} command, which can only be invoke once, either in the main matter or in the back matter. +After that, the chapter counter is reset and chapters now use appendix sectioning styles. +By default, these styles derive from the chapter styles, with the numbering changed to uppercase letters, and the prefix text to “Appendix”. +Note that appendices are numbered even in the back matter, by default, according to common practice. +Again, this can be changed on the sectioning command itself, or globally with adequate styles. + \section{Other features} The footnotes are based on the \autodoc:package{resilient.footnotes} package and therefore have the @@ -329,7 +368,9 @@ available. \medskip A few layout-related commands are also provided. -The \autodoc:command{\layout[layout=]} command inserts a page break and changes the page layout from that point. An optional \autodoc:parameter{offset=} may be specified to also alter the binding offset. By default, the global offset (that is, as possibly defined via the corresponding class option) is used. +The \autodoc:command{\layout[layout=]} command inserts a page break if needed, and changes the page layout from that point. +An optional \autodoc:parameter{offset=} may be specified to also alter the binding offset. +By default, the global offset (that is, as possibly defined via the corresponding class option) is used. Mostly intended for documentation, the \autodoc:command{\showlayout[layout=, papersize=]} command outputs an image representing the selected page layout and paper size. Optional parameters are \autodoc:parameter{offset=} for the binding offset (0, that is no offset, by default), \autodoc:parameter{ratio=} for the image down-scaling (dividing the paper size by the specified amount, 6.5 by default), and \autodoc:parameter{rough=} (false by default). diff --git a/examples/manual-masterdoc/bookdivisions.dj b/examples/manual-masterdoc/bookdivisions.dj new file mode 100644 index 0000000..ca0b931 --- /dev/null +++ b/examples/manual-masterdoc/bookdivisions.dj @@ -0,0 +1,124 @@ +# More book divisions + +In §[](#masterdoc-content-basic), we explored how to declare a simple book consisting of chapters or parts. +For books with chapters only: + + +```yaml +content: + chapters: + ⟨...⟩ +``` + +Or for books with parts: + +```yaml +content: + parts: + ⟨...⟩ +``` + +## Front, main and back divisions + +Actually, the above examples are simplified versions of a book's structure. +They are equivalent to the following full syntax: + +```yaml +content: + mainmatter: + ⟨parts or chapters⟩ +``` + +That is, your content is assumed to be part of the main matter by default. +Some books, however, require a more complex structure, dividing content into front matter, main matter, and back matter. +This is particularly common in academic works, where the front matter might include a preface, dedication, and table of contents. + +![Internal book divisions](images/book-divisions.svg){width="65%lw"} + +The back matter is more variable, but for instance, it might include an index, bibliography, or glossary. + +Each division begins on an odd-numbered page. +Divisions influence both the format and numbering of pages: + + - The front matter uses roman numerals for page numbers by default. + - The main and back matter use arabic numerals for page numbers by default. + +These defaults can be overridden to suit specific requirements, with adequate styles. +Page numbering resets whenever the format changes between divisions (e.g., front matter from pages i--viii, main matter from 1--148). + +Divisions also influence the numbering of parts and chapters: + + - In the front matter and back matter, parts and chapters are unnumbered by definition. + - In the main matter, parts and chapters are numbered by default, though this behavior can be customized with styles. + +All default styles can be overridden to suit specific requirements, but we will cover this in a later section. +All divisions can include parts. +However, it is up to you to maintain consistency with your structural expectations. + +```yaml +content: + frontmatter: + ⟨parts or chapters⟩ + mainmatter: + ⟨parts or chapters⟩ + backmatter: + ⟨parts or chapters⟩ +``` + +Below is an example of how to declare a book that incorporates these divisions. + +```yaml +content: + frontmatter: + chapters: + - preface.dj + mainmatter: + parts: + - caption: Part 1 + content: + - chapt1-1.dj + - chapt1-2.dj + - caption: Part 2 + content: + - chapt2-1.dj + - chapt2-2.dj + backmatter: + chapters: + - postface.dj +``` + +## Appendix subdivisions + +In some cases, you may want to include appendices in your book. +Appendices are similar to chapters, but from the point where they begin, the numbering of chapters changes. +With the default styles, appendices are numbered with uppercase letters. + +In some cases, you may want to include appendices as part of your book's structure. +Appendices are similar to chapters and occur at the same level, but usually differ in how they are numbered. +In other terms, from the point where the appendices begin, the chapter-level numbering transitions to a distinct format. +By default, appendices are numbered using uppercase letters, such as "Appendix A". +As with other aspects of your book's structure, appendices can be stlyed to suit your specific requirements. + +Appendices can occur in the main matter or back matter, and in any part. +For instance, with a book without parts: + +```yaml +content: + chapters: + - chapter1.dj + appendices: + - appendixA.dj +``` + +Or with a book with a part containing both chapters and appendices: + +```yaml +content: + mainmatter: + parts: + - caption: Backmatter part + chapters: + - caption: Backmatter chapter + appendices: + - caption: Backmatter appendix +``` diff --git a/examples/manual-masterdoc/masterdoc.dj b/examples/manual-masterdoc/masterdoc.dj index d488018..8115296 100644 --- a/examples/manual-masterdoc/masterdoc.dj +++ b/examples/manual-masterdoc/masterdoc.dj @@ -55,11 +55,11 @@ sile: textsubsuper.fake: false typesetter.italicCorrection: true packages: - - dropcaps - - couyards -chapters: - - chap1.dj - - chap2.dj + - resilient.poetry +content: + chapters: + - chap1.dj + - chap2.dj ``` The `masterfile` entry is a number corresponding to a version, for compatibility purposes.[^masterdoc-compat] @@ -104,6 +104,7 @@ The `sile` object provides intructions for processing the master document with S - The `packages` object lists extra packages that SILE must use. It can be used to load add-on packages needed by your content but not already provided by the document class and its supporting packages. +{#masterdoc-content-basic} ## Up to the content Last but not least, the `chapters` array (or `parts`, see further below) lists the files to include in the work. diff --git a/examples/manual-styling/advanced/folio.md b/examples/manual-styling/advanced/folio.md index 8fd7e86..1f45152 100644 --- a/examples/manual-styling/advanced/folio.md +++ b/examples/manual-styling/advanced/folio.md @@ -22,10 +22,8 @@ folio-odd: indent: false ``` -That common parent style is where we would ideally define the font, for instance -to use old-style numbering. It is also where we can define the numbering format. -Robert Bringhurst says: "It is usual to set folios in the text size"---so we won't -change the font size. +That common parent style is where we would ideally define the font, for instance to use old-style numbering. +Robert Bringhurst says: "It is usual to set folios in the text size"---so we won't change the font size. ```yaml folio-base: @@ -36,7 +34,23 @@ folio-base: display: "arabic" ``` -::: {custom-style="admon"} -Frontmatter / mainmatter / backmatter sections are not implement yet. -So for now, the above only documents the "mainmatter" general styling. -::: +The numbering format is defined in another set of numbering styles. +The usual settings is to have roman numbers in the front matter, and arabic numbers in the main and back matters. + +```yaml +folio-frontmatter: + style: + numbering: + display: "roman" +folio-mainmatter: + style: + numbering: + display: "arabic" +folio-backmatter: + style: + numbering: + display: "arabic" +``` + +In books without these high-level divisions, `folio-mainmatter` is implied for the whole document. +As all of these are numbering styles, you can also extend them further, would you want to style the page numbers differently in these divisions. diff --git a/examples/manual-styling/advanced/intro.md b/examples/manual-styling/advanced/intro.md index 239e9ad..c326492 100644 --- a/examples/manual-styling/advanced/intro.md +++ b/examples/manual-styling/advanced/intro.md @@ -1,12 +1,7 @@ # Advanced styling -Having read the previous chapter, you should now know how to edit styles and -modify them at your convenience. Some of the styles used by resilient collection -of classes and packages, however, are probably worth being depicted with more -details. +Having read the previous chapter, you should now know how to edit styles and modify them at your convenience. +Some of the styles used by the resilient collection of classes and packages, however, are probably worth being depicted with more details. -The aim of this chapter is to present these advanced use cases, and possibly -to answer some frequently asked questions. -Please bear in remind, however, that the style inheritance model is very flexible, -and that you can possibly modify the style hierarchy to make it quite different -from the default setup. +The aim of this chapter is to present these advanced use cases, and possibly to answer some frequently asked questions. +Please bear in remind, however, that the style inheritance model is very flexible, and that you can possibly modify the style hierarchy to make it quite different from the default setup. diff --git a/examples/manual-styling/basics/sectioning.md b/examples/manual-styling/basics/sectioning.md index ea66c66..00caef9 100644 --- a/examples/manual-styling/basics/sectioning.md +++ b/examples/manual-styling/basics/sectioning.md @@ -75,6 +75,8 @@ numbering in the table of contents is handled with a different mechanism, as a part of the ToC styles. Whether there is a need to reconcile these solutions might be considered in a future revision.] Note that if a given context is absent, the section number will not be displayed. +This might be sufficient to disable inherited numbering in some contexts, but +in these cases, you can just set the `main` number style to `null` (without quotes). For the "main" number style, some sections may expect the number to be on its own standalone line rather than just before the section title.—Chapters and parts, diff --git a/examples/sile-resilient-manual-styles.yml b/examples/sile-resilient-manual-styles.yml index bcff38e..bf98f72 100644 --- a/examples/sile-resilient-manual-styles.yml +++ b/examples/sile-resilient-manual-styles.yml @@ -297,6 +297,12 @@ figure-caption-ref-number: before: text: "fig. " +folio-backmatter: + origin: "resilient.book" + style: + numbering: + display: "arabic" + folio-base: origin: "resilient.book" style: @@ -312,6 +318,18 @@ folio-even: before: indent: false +folio-frontmatter: + origin: "resilient.book" + style: + numbering: + display: "roman" + +folio-mainmatter: + origin: "resilient.book" + style: + numbering: + display: "arabic" + folio-odd: inherit: "folio-base" origin: "resilient.book" @@ -679,6 +697,38 @@ prosody: before: skip: "smallskip" +sectioning-appendix: + inherit: "sectioning-chapter" + origin: "resilient.book" + style: + sectioning: + numberstyle: + header: "sectioning-appendix-head-number" + main: "sectioning-appendix-main-number" + reference: "sectioning-appendix-ref-number" + +sectioning-appendix-head-number: + inherit: "sectioning-chapter-head-number" + origin: "resilient.book" + style: + +sectioning-appendix-main-number: + inherit: "sectioning-chapter-main-number" + origin: "resilient.book" + style: + numbering: + before: + text: "Appendix " + display: "ALPHA" + +sectioning-appendix-ref-number: + inherit: "sectioning-chapter-ref-number" + origin: "resilient.book" + style: + numbering: + before: + text: "app. " + sectioning-base: origin: "resilient.book" style: diff --git a/examples/sile-resilient-manual.silm b/examples/sile-resilient-manual.silm index 9d4af7c..ea1c253 100644 --- a/examples/sile-resilient-manual.silm +++ b/examples/sile-resilient-manual.silm @@ -9,9 +9,9 @@ metadata: - book authors: Didier Willis publisher: Omikhleia - pubdate: 2024-10-12 + pubdate: 2025-01-15 url: https://github.com/Omikhleia/resilient.sile - copyright: © 2021–2024, Didier Willis. + copyright: © 2021–2025, Didier Willis. legal: | This material may be distributed only subject to the terms and conditions set forth in the Creative Commons Attribution, Share-Alike License, @@ -43,42 +43,47 @@ sile: packages: - autodoc-resilient # REQUIRED FOR RESILIENT, do not use regular autodoc - lorem -parts: - - content: - - manual-front.sil - - file: manual-parts/part-intro.dj - content: - - manual-intro/howto.dj - - file: manual-parts/part-masterdoc.dj - content: - - manual-masterdoc/masterdoc.dj - - manual-masterdoc/bookmatters.dj - - manual-masterdoc/bibliographies.dj - - file: manual-parts/part-layouts.dj - content: - - manual-layouts/layouts.sil - - file: manual-parts/part-styling.dj - content: - - manual-styling/basics/concepts.md - - manual-styling/basics/definitions.md - - manual-styling/basics/character.md - - manual-styling/basics/lists.md - - manual-styling/basics/paragraph.md - - manual-styling/basics/toc.md - - manual-styling/basics/sectioning.md - - manual-styling/advanced/intro.md - - manual-styling/advanced/folio.md - - manual-styling/advanced/footnotes.md - - manual-styling/advanced/toclevels.md - - manual-styling/advanced/liststyles.md - - manual-styling/advanced/eqno.md - - manual-styling/advanced/dropcap.md - - manual-styling/advanced/other.md - # unfinished - # - manual-styling/captioned.md - - file: manual-parts/part-packages.dj - content: - - manual-packages/packages.sil - - file: manual-parts/part-classes.dj - content: - - manual-classes/classes.sil +content: + frontmatter: + parts: + - content: + - manual-front.sil + mainmatter: + parts: + - file: manual-parts/part-intro.dj + content: + - manual-intro/howto.dj + - file: manual-parts/part-masterdoc.dj + content: + - manual-masterdoc/masterdoc.dj + - manual-masterdoc/bookmatters.dj + - manual-masterdoc/bookdivisions.dj + - manual-masterdoc/bibliographies.dj + - file: manual-parts/part-layouts.dj + content: + - manual-layouts/layouts.sil + - file: manual-parts/part-styling.dj + content: + - manual-styling/basics/concepts.md + - manual-styling/basics/definitions.md + - manual-styling/basics/character.md + - manual-styling/basics/lists.md + - manual-styling/basics/paragraph.md + - manual-styling/basics/toc.md + - manual-styling/basics/sectioning.md + - manual-styling/advanced/intro.md + - manual-styling/advanced/folio.md + - manual-styling/advanced/footnotes.md + - manual-styling/advanced/toclevels.md + - manual-styling/advanced/liststyles.md + - manual-styling/advanced/eqno.md + - manual-styling/advanced/dropcap.md + - manual-styling/advanced/other.md + # unfinished + # - manual-styling/captioned.md + - file: manual-parts/part-packages.dj + content: + - manual-packages/packages.sil + - file: manual-parts/part-classes.dj + content: + - manual-classes/classes.sil diff --git a/inputters/silm.lua b/inputters/silm.lua index b7bdbae..832e9a5 100644 --- a/inputters/silm.lua +++ b/inputters/silm.lua @@ -1,6 +1,6 @@ --- Some YAML master file inputter for SILE -- --- @copyright License: MIT (c) 2023 Omikhleia, Didier Willis +-- @copyright License: MIT (c) 2023, 2025 Omikhleia, Didier Willis -- @module inputters.silm -- local ast = require("silex.ast") @@ -30,43 +30,64 @@ local OptionsSchema = { properties = {} } +local FileSchema = { + type = "object", + properties = { + file = { type = "string" }, + format = { type = "string" }, + options = OptionsSchema + } +} + local SingleFileSchema = { type = { -- oneOf { type = "string" }, - { type = "object", - properties = { - file = { type = "string" }, - format = { type = "string" }, - options = OptionsSchema - } - } + FileSchema } } +local CaptionSchema = { + type = "object", + properties = { + caption = { type = "string" }, + } +} + +local ContentCaptionSchema = pl.tablex.deepcopy(CaptionSchema) +local ContentFileSchema = pl.tablex.deepcopy(FileSchema) local ContentSchema = { type = "array", items = { type = { -- oneOf { type = "string" }, - { type = "object", - properties = { - caption = { type = "string" }, - -- content (self reference, handled below) - } - }, - { type = "object", - properties = { - file = { type = "string" }, - format = { type = "string" }, - options = OptionsSchema, - -- content (self reference, handled below) - } - } + ContentCaptionSchema, + ContentFileSchema, + } + } +} +-- Recursive content +ContentCaptionSchema.properties.content = ContentSchema +ContentFileSchema.properties.content = ContentSchema + +local InPartCaptionSchema = pl.tablex.deepcopy(CaptionSchema) +local InPartFileSchema = pl.tablex.deepcopy(FileSchema) +-- Recursive structured content +InPartCaptionSchema.properties.chapters = ContentSchema +InPartCaptionSchema.properties.appendices = ContentSchema +InPartFileSchema.properties.chapters = ContentSchema +InPartFileSchema.properties.appendices = ContentSchema +local ContentInPartSchema = { + type = "array", + items = { + type = { -- oneOf + { type = "string" }, + ContentCaptionSchema, + ContentFileSchema, + InPartCaptionSchema, + InPartFileSchema, } } } -ContentSchema.items.type[2].properties.content = ContentSchema -ContentSchema.items.type[3].properties.content = ContentSchema local BookSchema = { type = "object", @@ -134,99 +155,147 @@ local BibliographySchema = { } } -local MasterSchema = { +local MetaDataSchema = { type = "object", - additionalProperties = true, -- Allow unknown property for extensibility properties = { - masterfile = { - type = "number", + title = { type = "string" }, + subtitle = { type = "string" }, + subject = { type = "string" }, + keywords = { + type = { -- oneOf + { type = "string" }, + { type = "array", items = { type = "string" } }, + }, }, - metadata = { + authors = { + type = { -- oneOf + { type = "string" }, + { type = "array", items = { type = "string" } }, + }, + }, + translators = { -- oneOf + type = "string", + items = { type = "string" } + }, + publisher = { type = "string" }, + pubdate = { type = "object", + -- tinyyaml converts dates to osdate objects + -- We don't care about hours, minutes, etc. + additionalProperties = true, properties = { - title = { type = "string" }, - subtitle = { type = "string" }, - subject = { type = "string" }, - keywords = { - type = { -- oneOf - { type = "string" }, - { type = "array", items = { type = "string" } }, - }, - }, - authors = { - type = { -- oneOf - { type = "string" }, - { type = "array", items = { type = "string" } }, - }, - }, - translators = { -- oneOf - type = "string", - items = { type = "string" } - }, - publisher = { type = "string" }, - pubdate = { - type = "object", - -- tinyyaml converts dates to osdate objects - -- We don't care about hours, minutes, etc. - additionalProperties = true, - properties = { - year = { type = "number" }, - month = { type = "number" }, - day = { type = "number" } - } - }, - isbn = { type = "string" }, - url = { type = "string" }, - copyright = { type = "string" }, - legal = { type = "string" }, + year = { type = "number" }, + month = { type = "number" }, + day = { type = "number" } } }, - font = { + isbn = { type = "string" }, + url = { type = "string" }, + copyright = { type = "string" }, + legal = { type = "string" }, + } +} + +local FontSchema = { + type = "object", + properties = { + family = { + type = { -- oneOf + { type = "string" }, + { type = "array", items = { type = "string" } }, + }, + }, + size = { type = "string" } + } +} + +local SileConfigurationSchema = { + type = "object", + properties = { + options = { type = "object", properties = { - family = { - type = { -- oneOf - { type = "string" }, - { type = "array", items = { type = "string" } }, - }, - }, - size = { type = "string" } + class = { type = "string" }, + papersize = { type = "string" }, + layout = { type = "string" }, + resolution = { type = "number" }, + headers = { type = "string" }, + offset = { type = "string" }, } }, - language = { type = "string" }, - sile = { + settings = { type = "object", + -- Allow any setting without validation + additionalProperties = true, properties = { - options = { - type = "object", - properties = { - class = { type = "string" }, - papersize = { type = "string" }, - layout = { type = "string" }, - resolution = { type = "number" }, - headers = { type = "string" }, - offset = { type = "string" }, - } - }, - settings = { - type = "object", - -- Allow any setting without validation - additionalProperties = true, - properties = { - } - }, - packages = { - type = "array", - items = { type = "string" } - } } }, + packages = { + type = "array", + items = { type = "string" } + } + } +} + +local PartPropertiesSchema = { + type = "object", + properties = { + parts = ContentInPartSchema, + } +} + +local ChapterAppendicesSchema = { + type = "object", + properties = { + chapters = ContentSchema, + appendices = ContentSchema, + } +} + +local PartOrChapterSchema = { + type = { -- oneOf + PartPropertiesSchema, + ChapterAppendicesSchema, + } +} + +local StructuredContentSchema = { + type = { -- oneOf + PartPropertiesSchema, + ChapterAppendicesSchema, + { type = "object", + properties = { + frontmatter = PartOrChapterSchema, + mainmatter = PartOrChapterSchema, + backmatter = PartOrChapterSchema, + } + }, + } +} + +local MasterSchema = { + type = "object", + additionalProperties = true, -- Allow unknown property for extensibility + properties = { + masterfile = { + type = "number", + }, + metadata = MetaDataSchema, + font = FontSchema, + language = { type = "string" }, + sile = SileConfigurationSchema, book = BookSchema, bibliography = BibliographySchema, - -- parts and chapters are exclusive, but we don't enforce it here. + -- Legacy compatibility: + -- We can have parts and chapters at the root level. + -- parts, chapters and content are mutually exclusive, but we don't enforce it here. -- We will check it later in the inputter - parts = ContentSchema, - chapters = ContentSchema + parts = ContentInPartSchema, + chapters = ContentSchema, + -- New recommended way: + -- content can have parts, chapters or a structured content (frontmatter, mainmatter, backmatter + -- divisons) + content = StructuredContentSchema, } } @@ -380,20 +449,63 @@ local function doLevel (content, entries, shiftHeadings, metaopts) if entry.content then doLevel(content, entry.content, shiftHeadings + 1, metaopts) end + -- Top-level or part-level content, enforced by the schema + if entry.chapters then + doLevel(content, entry.chapters, shiftHeadings + 1, metaopts) + end + if entry.appendices then + content[#content+1] = createCommand("appendix") + doLevel(content, entry.appendices, shiftHeadings + 1, metaopts) + end elseif entry.caption then local command = Levels[shiftHeadings + 2] or SU.error("Invalid master document (too many nested levels)") content[#content+1] = createCommand(command, {}, entry.caption) if entry.content then doLevel(content, entry.content, shiftHeadings + 1, metaopts) end + -- Top-level or part-level content, enforced by the schema + if entry.chapters then + doLevel(content, entry.chapters, shiftHeadings + 1, metaopts) + end + if entry.appendices then + content[#content+1] = createCommand("appendix") + doLevel(content, entry.appendices, shiftHeadings + 1, metaopts) + end elseif entry.content then doLevel(content, entry.content, shiftHeadings + 1, metaopts) - else - SU.error("Invalid master document (invalid content section)") end end end +local function doDivisionContent (content, entry, shiftHeadings, metaopts) + if entry.parts then + doLevel(content, entry.parts, shiftHeadings - 1, metaopts) + else + if entry.chapters then + doLevel(content, entry.chapters, shiftHeadings, metaopts) + end + if entry.appendices then + content[#content+1] = createCommand("appendix") + doLevel(content, entry.appendices, shiftHeadings, metaopts) + end + end +end + +local function doDivision (content, entry, shiftHeadings, metaopts) + if entry.frontmatter then + content[#content+1] = createCommand("frontmatter") + doDivisionContent(content, entry.frontmatter, shiftHeadings, metaopts) + end + if entry.mainmatter then + content[#content+1] = createCommand("mainmatter") + doDivisionContent(content, entry.mainmatter, shiftHeadings, metaopts) + end + if entry.backmatter then + content[#content+1] = createCommand("backmatter") + doDivisionContent(content, entry.backmatter, shiftHeadings, metaopts) + end +end + --- Process back cover content (text included in the back cover) ---@param entry table|string Back cover content entry ---@param metaopts table Metadata options @@ -492,6 +604,9 @@ function inputter:parse (doc) }) end end + -- TODO QUESTION: if not a root document, should we wrap the includes + -- in a language group? Mixing documents with different languages + -- is probably not a good idea, anyhow... local sile = master.sile or {} local metadata = master.metadata or {} @@ -591,13 +706,21 @@ function inputter:parse (doc) end -- Document content - -- TODO QUESTION: if not a root document, should we wrap the includes - -- in a language group? Mixing documents with different languages - -- is probably not a good idea, anyhow... + if master.parts then doLevel(content, master.parts, baseShiftHeadings - 1, metadataOptions) elseif master.chapters then doLevel(content, master.chapters, baseShiftHeadings, metadataOptions) + elseif master.content then + if master.content.parts then + doLevel(content, master.content.parts, baseShiftHeadings - 1, metadataOptions) + elseif master.content.chapters then + doLevel(content, master.content.chapters, baseShiftHeadings, metadataOptions) + elseif master.content.frontmatter or master.content.mainmatter or master.content.backmatter then + doDivision(content, master.content, baseShiftHeadings, metadataOptions) + else + SU.error("Invalid master document (no division, parts or chapters)") + end end if enabledBook then diff --git a/packages/resilient/sectioning/init.lua b/packages/resilient/sectioning/init.lua index fbce6ad..0fc51c8 100644 --- a/packages/resilient/sectioning/init.lua +++ b/packages/resilient/sectioning/init.lua @@ -36,6 +36,21 @@ local function hasContent() return hasNonGlueContent end +local function nameIfNotNull (name) + -- Nothig to fancy but there's a special case for the null value in YAML + -- style definitions. + if not name then + return nil + end + if type(name) == "string" then + return name + end + if tostring(name) == "yaml.null" then + return nil + end + SU.error("Invalid style name, expected a string or YAML null") +end + function package:registerCommands () local resolveSectionStyleDef = function (name) @@ -99,7 +114,8 @@ function package:registerCommands () -- shouldn't trigger a goodbreak in-between). -- Process the section (title) content - local numSty = secStyle.numberstyle.main and self:resolveStyle(secStyle.numberstyle.main) + local numStyName = secStyle.numberstyle.main and nameIfNotNull(secStyle.numberstyle.main) + local numSty = numStyName and self:resolveStyle(numStyName) local numDisplay = numSty and numSty.numbering and numSty.numbering.display or "arabic" -- Counter for numbered sections @@ -124,8 +140,11 @@ function package:registerCommands () hookOptions.counter = secStyle.counter.id hookOptions.level = secStyle.counter.level hookOptions.before = true -- HACK SEE BELOW - local numsty = sty.sectioning and sty.sectioning.numberstyle - and sty.sectioning.numberstyle.header + local numsty = sty.sectioning + and sty.sectioning.numberstyle + -- Only user header number if main number style is defined + and nameIfNotNull(sty.sectioning.numberstyle.main) + and nameIfNotNull(sty.sectioning.numberstyle.header) if numbering and numsty then titleHookContent = { createCommand("style:apply:number", { name = numsty, text = number }), @@ -176,9 +195,9 @@ function package:registerCommands () -- Show section number (if numbering is true AND a main style is defined) if numbering then - if secStyle.numberstyle.main then + if numStyName then titleContent[#titleContent + 1] = - createCommand("style:apply:number", { name = secStyle.numberstyle.main, text = number }) + createCommand("style:apply:number", { name = numStyName, text = number }) if SU.boolean(numSty.numbering and numSty.numbering.standalone, false) then titleContent[#titleContent + 1] = createCommand("hardbreak") diff --git a/packages/resilient/tableofcontents/init.lua b/packages/resilient/tableofcontents/init.lua index 00c849d..b0879e6 100644 --- a/packages/resilient/tableofcontents/init.lua +++ b/packages/resilient/tableofcontents/init.lua @@ -75,6 +75,9 @@ local tocNumberStyles = { {}, {}, } + +local _toc_used = false + function package:_init (options) base._init(self, options) SILE.scratch.tableofcontents = SILE.scratch.tableofcontents or {} @@ -105,7 +108,7 @@ function package.writeToc (_) tocfile:write("return " .. tocdata) tocfile:close() - if not pl.tablex.deepcompare(SILE.scratch.tableofcontents, SILE.scratch._tableofcontents) then + if _toc_used and not pl.tablex.deepcompare(SILE.scratch.tableofcontents, SILE.scratch._tableofcontents) then io.stderr:write("\n! Warning: table of contents has changed, please rerun SILE to update it.") end end @@ -127,7 +130,7 @@ end function package:registerCommands () - -- Warning for users of the legacy tableofcontents + -- Warning for users of the legacy (SILE core) tableofcontents self:registerCommand("tableofcontents:title", function (_, _) SU.error("The resilient.tableofcontents package does not use the tableofcontents:title command.") end) @@ -148,6 +151,8 @@ function package:registerCommands () return end + _toc_used = true + -- Temporarilly kill footnotes and labels (fragile) local oldFt = SILE.Commands["footnote"] SILE.Commands["footnote"] = function () end