diff --git a/README.md b/README.md index 64d0278..c4a1cb9 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,11 @@ [![Luacheck](https://img.shields.io/github/actions/workflow/status/Omikhleia/markdown.sile/luacheck.yml?branch=main&label=Luacheck&logo=Lua)](https://github.com/Omikhleia/markdown.sile/actions?workflow=Luacheck) [![Luarocks](https://img.shields.io/luarocks/v/Omikhleia/markdown.sile?label=Luarocks&logo=Lua)](https://luarocks.org/modules/Omikhleia/markdown.sile) -This collection of modules for the [SILE](https://github.com/sile-typesetter/sile) typesetting -system provides a complete redesign of its former native Markdown support, with -a great set of Pandoc-like extensions and plenty of extra goodies. +This collection of modules for the [SILE](https://github.com/sile-typesetter/sile) typesetting system provides a complete redesign of its former native Markdown support, with a great set of Pandoc-like extensions and plenty of extra goodies. - **markdown** inputter and package: native support of Markdown files. -- **pandocast** inputter and package: native support of Pandoc JSON AST files. - **djot** inputter and package: native support of Djot files. +- **pandocast** inputter and package: native support of Pandoc JSON AST files. For casual readers, this collection notably aims at easily converting Markdown or Djot documents to print-quality PDFs. @@ -23,7 +21,7 @@ Installation relies on the **luarocks** package manager. To install the latest version, you may use the provided “rockspec”: -``` +```shell luarocks install markdown.sile ``` @@ -31,15 +29,13 @@ luarocks install markdown.sile ## Usage -Basic usage is described just below. A more complete PDF version of the documentation (but not -necessarily the latest, see also further below for generating it from the sources) should be -available [HERE](https://drive.google.com/file/d/19VfSMmfBIZwr43U-W842IkSE349wdgZb/view?usp=sharing), or in our [Calaméo bookshelf](https://www.calameo.com/accounts/7349338). +Basic usage is described just below. A more complete PDF version of the documentation (but not necessarily the latest, see also further below for generating it from the sources) should be available [HERE](https://drive.google.com/file/d/19VfSMmfBIZwr43U-W842IkSE349wdgZb/view?usp=sharing), or in our [Calaméo bookshelf](https://www.calameo.com/accounts/7349338). ### Native Markdown package From command line: -``` +```shell sile -u inputters.markdown somefile.md ``` @@ -61,30 +57,6 @@ Some **Markdown** content \end{raw} ``` -### Pandoc AST alternative package - -_Prerequisites:_ The [LuaJSON](https://github.com/harningt/luajson) module must be installed and available to your SILE environment. -This topic is not covered here. - -First, using the appropriate version of Pandoc, convert your file to a JSON AST: - -``` -pandoc -t json somefile.md -f markdown -o somefile.pandoc -``` - -Then, from command line: - -``` -sile -u inputters.pandocast somefile.pandoc -``` - -Or from SIL documents: - -``` -\use[module=packages.pandocast] -\include[src=somefile.pandoc] -``` - ### Native Djot package [Djot](https://djot.net/) is a fairly recent “light markup syntax” derived from Markdown, fixing most of its complex syntax pitfalls, and also extending it on various aspects. @@ -92,7 +64,7 @@ Since many concepts are similar, it felt rather natural to include it too in thi From command line: -``` +```shell sile -u inputters.djot somefile.dj ``` @@ -114,56 +86,71 @@ Some *Djot* content \end{raw} ``` -## Generating the documentation +### Pandoc AST alternative package -The example documentation/showcase in this repository additionally requires the `autodoc` package, so you may generate a PDF from it with as follows: +_Prerequisites:_ The [LuaJSON](https://github.com/harningt/luajson) module must be installed and available to your SILE environment. +This topic is not covered here. -``` -sile -u inputters.markdown -u packages.autodoc examples/sile-and-markdown.md +First, using the appropriate version of Pandoc, convert your file to a JSON AST: + +```shell +pandoc -t json somefile.md -f markdown -o somefile.pandoc ``` -It assumes a default font, so a few things might not render as expected, and uses SILE's standard book class. +Then, from command line: -**Recommended:** For even best results (in this writer's biased opinion), provided you have installed the [resilient](https://github.com/Omikhleia/resilient.sile) collection of classes and packages: +```shell +sile -u inputters.pandocast somefile.pandoc +``` + +Or from SIL documents: ``` +\use[module=packages.pandocast] +\include[src=somefile.pandoc] +``` + +## Generating the documentation + +The example documentation/showcase in this repository needs the [resilient](https://github.com/Omikhleia/resilient.sile) collection of classes and packages to be installed. + +To generate the PDF documentation from the sources, you may then use the following command: + +```shell sile -u inputters.silm examples/sile-and-markdown-manual.silm ``` -The latter SIL “wrapper” document also loads extra packages before switching to Markdown, and defines additional commands and styles. -Moreover, it includes additional chapters, showcasing other advanced topics and cool use cases. Needed fonts are Libertinus Serif, Symbola and Zallman Caps. ## Supported features -This is but an overview. For more details, please refer to the provided example Markdown document, -which also serves as documentation, showcase and reference guide. +This is but an overview. For more details, please refer to the provided example Markdown document, which also serves as documentation, showcase and reference guide. -- Standard Markdown and Djot typesetting (italic, bold, code, etc.) +- Standard Markdown and Djot typesetting (emphases, strong emphasis, code, etc.) - Smart typography (quotes, apostrophes, ellipsis, dashes, etc.) with Markdown extensions (prime marks) -- Headers and header attributes -- Horizontal dividers (a.k.a. horizontal rules, with provision for asterisms, dinkuses, pendants...) -- Images (and image attributes, e.g. dimensions) -- TeX-like math -- Strikeout (a.k.a. deletions), etc. -- Subscript and superscript -- Footnotes (both regular and inline syntax support) -- Fenced code blocks (with attributes) -- Bracketed spans and fenced divs (with provisions for language change, custom styles, etc.) -- Underlines +- Hard line breaks and non-breaking spaces +- Underline, strikethrough, highlight, deletion, insertion (with provisions for custom styling) - Small caps +- Spans (with provisions for language change, custom styles, etc.) +- Subscript and superscript +- Images (and image attributes, e.g. dimensions) - Links (with special provisions for advanced cross-references) +- Footnotes +- TeX-like math +- Headers and header attributes +- Divs (with provisions for language change, custom styles, etc.) - Lists - Standard ordered lists and bulleted lists - Fancy lists - Task lists (GFM-like syntax) - Definition lists -- Pipe tables (and table captions) +- Horizontal dividers / thematic breaks (with provision for asterisms, dinkuses, pendants...) +- Tables (and table captions) +- Code blocks (with attributes) - Line blocks (with enhanced provision for poetry) -- Hard line breaks -- Raw attributes (escaping to inline SILE or Lua scripting) +- Raw inlines and raw blocks (escaping to SILE, in SIL language or Lua scripting) - Raw inline HTML convenience subset in Markdown -- Advanced use of symbols in Djot (variable substitution and templating) +- Advanced use of symbols in Djot (variable substitution ,and templating) - Advanced configuration (e.g. Markdown variants, headings shifting, etc.) ## Use with the resilient collection @@ -176,7 +163,7 @@ Then, you can automatically benefit from a few advanced features. Conversion from command-line just requires to load a resilient class, and optionally the poetry package. For instance: -``` +```shell sile -c resilient.book -u inputters.markdown -u packages.resilient.poetry somefile.md ``` @@ -205,7 +192,6 @@ when these packages all reach a stable state. Additional thanks to: - Simon Cozens, _et al._ concerned, for the early attempts at using lunamark with SILE. -- Vít Novotný, for the good work on lunamark, and the impressive [witiko/markdown](https://github.com/Witiko/markdown) - package for (La)TeX - a great source of inspiration and a goal of excellence. -- Caleb Maclennan, for his early work on a Pandoc-to-SILE converter which, though on different grounds, - indirectly gave me the idea of the “pandocast” alternative approach. +- Vít Novotný, for the good work on lunamark, and the impressive [witiko/markdown](https://github.com/Witiko/markdown) package for (La)TeX - a great source of inspiration and a goal of excellence. +- Caleb Maclennan, for his early work on a Pandoc-to-SILE converter which, though on different grounds, indirectly gave me the idea of the “pandocast” alternative approach. +- John MacFarlane, for the Djot and Markdown libraries which empowers this module. diff --git a/examples/data-pie.csv b/examples/data-pie.csv new file mode 100644 index 0000000..a6ab92b --- /dev/null +++ b/examples/data-pie.csv @@ -0,0 +1,12 @@ +Players,Games,Points,Ties,Aces +Albert,10,3,1 +Bertrand,20,6,5 +Charly,15,11 +Diane,25,,2 +Elisabeth,29 +Fabrice,6,,4 +Gerard,12 +Herbert,30,7 +Ian,5,,3,40 +John,25,,,30 +Theo,22,5,,25 diff --git a/examples/djot-concept-simplified.svg b/examples/djot-concept-simplified.svg new file mode 100644 index 0000000..8ded127 --- /dev/null +++ b/examples/djot-concept-simplified.svg @@ -0,0 +1,180 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/extra-styles.dj b/examples/extra-styles.dj new file mode 100644 index 0000000..5d2885a --- /dev/null +++ b/examples/extra-styles.dj @@ -0,0 +1,59 @@ +```=sile +% Quick and dirty definition of new custom styles +% using some "resilient" styling feature. +% +\define[command=customframe]{\center{\roughbox[bordercolor=#66a0b3, + fillcolor=220,padding=15pt, enlarge=true]{\parbox[width=90%lw, minimize=true]{\process}}}} +\define[command=customblockframe]{\center{\framebox[bordercolor=#66a0b3, + shadow=true, + fillcolor=250, + padding=4pt, + shadowcolor=220]{\parbox[width=90%lw]{\process}}}} +``` + +```=sile-lua +-- Tapping into the internal scratch variable of the resilient.styles +-- package is not a good practice. +SILE.scratch.styles.alignments["fancy-framed"] = "customframe" +local class = SILE.documentState.documentClass +class:registerStyle("FramedPara", {}, { + paragraph = { + before = { skip = "medskip" }, + after = { skip = "medskip" }, + align = "fancy-framed" + } +}) + +SILE.scratch.styles.alignments["shadow-framed"] = "customblockframe" +local class = SILE.documentState.documentClass +class:registerStyle("CodeBlock", {}, { + paragraph = { + before = { skip = "smallskip" }, + after = { skip = "smallskip" }, + align = "shadow-framed" + } +}) + +local class = SILE.documentState.documentClass +class:registerCommand("Ean13", function (_, content) + local code = content[1] + -- Markdown parser may interpret a dash between digits as smart typography for en-dash. + -- Let's remove those. + code = code:gsub("–","") + SILE.call("ean13", { code = code }) +end) + +class:registerCommand("Initial", function (_, content) + if type(content) ~= "table" then SU.error("Expected a table content in dropcap environment") end + if #content ~= 1 then SU.error("Expected a single letter in dropcap environment") end + local letter = content[1] + + -- discardable style + local dropcapSty = class.styles and class.styles:resolveStyle("CustomDroppedInitial", true) or {} + local family = dropcapSty.font and dropcapSty.font.family or "Zallman Caps" + local lines = dropcapSty.special and dropcapSty.special.lines or 3 + local color = dropcapSty.color + + SILE.call("dropcap", { lines = lines, family = family, color = color, join = true }, { letter }) +end) +``` diff --git a/examples/introduction.dj b/examples/introduction.dj new file mode 100644 index 0000000..ac2edb9 --- /dev/null +++ b/examples/introduction.dj @@ -0,0 +1,187 @@ +# Introduction + +[T]{custom-style=Initial}[his]{.smallcaps} collection of modules for the [SILE](https://github.com/sile-typesetter/sile) typesetting system provides a complete redesign of its former native Markdown support. +Most of the content you are reading right now is written in either Djot or Markdown, and processed using the converters offered by this collection: + + - *markdown* inputter and package: native Markdown support, + - *djot* inputter and package: native Djot support, + - *pandocast* package: native support of Pandoc JSON AST files. + +For casual readers, this collection notably aims at easily converting Markdown or Djot documents to print-quality PDFs. + +With Djot and Markdown being made first-class citizens in SILE, you can now write your documents in these lightweight markup languages. +There is actually more than one solution to achieve great results in that direction: + + 1. Directly using the native converter packages, + 1. Using the Pandoc software to generate an output suitable for SILE. + +![Supported routes from input to output.](./markdown-sile-overview.dot){width="96%fw"} + +The Markdown converter offers a great set of Pandoc-like extensions, and the Djot converter provides additional features as well. +Whether you favor Djot, a recent but serious contender to Markdown, or the more traditional Markdown, there is no need for you to delve into SILE's default language[^sile-default-language] or Lua programming. +Yet, these options remain available under the hood for those seeking a blend of possibilities. + +[^sile-default-language]: That is, the SIL language, whether in its TeX-like syntax or XML flavor. + + +## Installation + +### Standard installation + +This module collection requires SILE v0.14 or upper. + +Installation relies on the *luarocks* package manager. +To install the latest version, you may use the provided “rockspec”: + +{custom-style=CodeBlock} +::: +```bash +luarocks install markdown.sile +``` +::: + +Refer to the SILE manual for more detailed 3rd-party package installation information. + +{#recommended-additional-packages} +### Recommended additional packages + +The following package collections are not strictly required, but they are recommended for a better experience. +When installed, they provide additional features: + + - The [*couyards.sile*](https://github.com/Omikhleia/couyards.sile) collection provides additional support for fancy thematic breaks. + - The [*piecharts.sile*](https://github.com/Omikhleia/piecharts.sile) collection provides support for rendering pie charts from CSV data. + +To install them, use the following commands: + +{custom-style=CodeBlock} +::: +```bash +luarocks install couyards.sile +luarocks install piecharts.sile +``` +::: + +Also recommended is the *resilient.sile* collection, which provides a set of classes and packages for a more advanced usage. +It is described below in §[](#usage-with-the-resilient-collection). +Note that it takes care of installing the above collections as well, so you don't have to install them separately if you go the "resilient" way. + +{#recommended-additional-software} +### Recommended additional software + +This module also installs the [*embedders.sile*](https://github.com/Omikhleia/embedders.sile) collection, a general framework for embedding +images from textual representations, performing their on-the-fly conversion via shell commands. + +These require additional software to be installed on your host system, to invoke the necessary conversion commands. +Please refer to its documentation for more information. +Typically, this manual illustrates the use of the Graphviz collection of tools to render graphs in the DOT language. + +As with anything that relies on invoking external programs on your host system, please be aware of potential security concerns. +Be cautious with the source of the elements you include in your documents! + + +## Usage + +### Usage from command line + +The following chapters describe the packages' features, and how to use them within existing documents (themselves in SIL, Djot or Markdown syntax). +It's perfectly possible, though, to perform direct conversion from the command line. +For instance, here is how it may be done for Markdown document... + +{custom-style=CodeBlock} +::: +```bash +sile -u inputters.markdown somefile.md +``` +::: + +... Or for a Djot document. + +{custom-style=CodeBlock} +::: +```bash +sile -u inputters.djot somefile.dj +``` +::: + +This method directly produces a PDF from the input file, using SILE's standard *book* class.[^intro-book-class] + +[^intro-book-class]: Actually, it uses a *markdown* class derived from the standard book class and loading the required modules. +You don't really have to know that, unless you intend to invoke SILE with the `-c` option to specify another class of your choice; in that case you will need to load additional modules explicitly---unless it is a resilient class, of course. + +{#usage-with-the-resilient-collection} +### Usage with the _re·sil·ient_ collection + +To unleash the full potential of this package collection, we recommend that you also install our [*resilient.sile*](https://github.com/Omikhleia/resilient.sile) collection of classes and packages. + +{custom-style=CodeBlock} +::: +```bash +luarocks install resilient.sile +``` +::: + +Then, you can automatically benefit from a few advanced features. +Conversion from command line just requires to load a resilient document class, and optionally the poetry package. +For instance: + +{custom-style=CodeBlock} +::: +```bash +sile -c resilient.book -u inputters.markdown -u packages.resilient.poetry somefile.md +``` +::: + +(And likewise for the Pandoc AST or Djot processing.) + +A "resilient style file" is also generated. +It can be modified to change many styling decisions and adapt the output at convenience. + +### Advanced usage from existing documents + +While direct conversion from the command line may be adequate for very simple workflows, there are a number of things usually best addressed by using some kind of "wrapper" document. + +The resilient collection introduces a "master document" format, streamlining several usual tasks. Give it a chance, and you may even end up producing a book with SILE from front cover to back cover, without a single statement in SIL. + +This package collection provides several ways for including Markdown or Djot content in documents written in SIL syntax. +Once you have loaded the *djot* or *markdown* package, the `\autodoc:command{\include[src=]}`{=sile} command supports reading a Djot or Markdown file. +Let's illustrate this for Djot, but the same applies to Markdown. + +{custom-style=CodeBlock} +::: +``` +\use[module=packages.djot] +\include[src=somefile.dj] +``` +::: + +Embedding raw Djot content from within a SIL document is also possible. +Again, the same applies to Markdown. + +{custom-style=CodeBlock} +::: +``` +\begin[type=djot]{raw} +Some *Djot* content +\end{raw} +``` +::: + + +## Credits + +We owe a lot to John MacFarlane, for the *djot* and *lunamark* Lua libraries which empower this collection of packages. +Additional thanks go to: + +- Simon Cozens, _et al._ concerned, for the early attempts at using *lunamark* with SILE. +- Vít Novotný, for the good work on lunamark, and the impressive [*witiko/markdown*](https://github.com/Witiko/markdown) package for (La)TeX---a great source of inspiration and a goal of excellence. +- Caleb Maclennan, for his early work on a Pandoc-to-SILE converter which, though on different grounds, indirectly gave me the idea of the Pandoc AST alternative approach proposed here. + +{.dinkus} +--- + +{custom-style=FramedPara} +::: +Thank you for your attention, gentle reader. +We wish you a very pleasant journey through the rest of this manual. +Don't hesitate to provide feedback and help pushing this collection forward. +::: diff --git a/examples/introduction.md b/examples/introduction.md deleted file mode 100644 index d7f9280..0000000 --- a/examples/introduction.md +++ /dev/null @@ -1,93 +0,0 @@ -# Introduction - -This collection of modules for the [SILE](https://github.com/sile-typesetter/sile) typesetting -system provides a complete redesign of its former native Markdown support, with -a great set of Pandoc-like extensions and plenty of extra goodies. - -- `\autodoc:package{markdown}`{=sile} package: native support of Markdown. -- `\autodoc:package{djot}`{=sile} package: native support of Djot. -- `\autodoc:package{pandocast}`{=sile} package: native support of Pandoc JSON AST files. - -For casual readers, this collection notably aims at easily converting Markdown or Djot documents to print-quality PDFs. -There is actually more than one solution to achieve great results in that direction: - - 1. Directly using the native converter packages, - 1. Using the Pandoc software to generate an output suitable for SILE. - -Each of them has its advantages, and a few limitations as well. - -![Supported routes from input to output.](./markdown-sile-overview.dot){width="96%fw"} - -## Installation - -This module collection requires SILE v0.14 or upper. - -Installation relies on the **luarocks** package manager. -To install the latest version, you may use the provided “rockspec”: - -```bash -luarocks install markdown.sile -``` - -Refer to the SILE manual for more detailed 3rd-party package installation information. - -## Usage - -### Usage from command line - -The following chapters describe the packages' features, and how to use them within existing documents (themselves in SIL, Djot or Markdown syntax). -It's perfectly possible, though, to perform direct conversion from the command line. -For instance, here is how it may be done for Markdown document... - -``` -sile -u inputters.markdown somefile.md -``` - -... Or for a Djot document. - -``` -sile -u inputters.djot somefile.dj -``` - -This method directly produces a PDF from the input file, using SILE's standard **book** class.[^intro-book-class] - -[^intro-book-class]: Actually, it uses a **markdown** class derived from the standard book class and loading the required modules. -You don't really have to know that, unless you intend to invoke SILE with the `-c` option to specify another class of your choice; in that case you will need to load additional modules explicitly---unless it is a resilient class, of course. - -### Usage with the resilient collection - -To unleash the full potential of this package collection, we recommend that you also install our [**resilient.sile**](https://github.com/Omikhleia/resilient.sile) collection of classes and packages. - -```bash -luarocks install resilient.sile -``` - -Then, you can automatically benefit from a few advanced features. -Conversion from command line just requires to load a resilient class, and optionally the poetry package. -For instance: - -``` -sile -c resilient.book -u inputters.markdown -u packages.resilient.poetry somefile.md -``` - -(And likewise for the Pandoc AST or Djot processing.) - -A "resilient style file" is also generated. -It can be modified to change many styling decisions and adapt the output at convenience. - -### Advanced usage from existing documents - -While direct conversion from the command line may be adequate for very simple workflows, there are a number of things usually best addressed by using some kind of "wrapper" document. - -This package collection provides several ways for including Markdown or Djot content in documents written in SIL syntax, the default mark-up language provided by SILE. -These are described further in this guide. - -The resilient collection also introduces a "master document" format, streamlining several usual tasks. Give it a chance, and you may even end up producing a book with SILE without a single statement in SIL. - -## Credits - -Additional thanks to: - -- Simon Cozens, _et al._ concerned, for the early attempts at using **lunamark** with SILE. -- Vít Novotný, for the good work on lunamark, and the impressive [**witiko/markdown**](https://github.com/Witiko/markdown) package for (La)TeX---a great source of inspiration and a goal of excellence. -- Caleb Maclennan, for his early work on a Pandoc-to-SILE converter which, though on different grounds, indirectly gave me the idea of the Pandoc AST alternative approach proposed here. diff --git a/examples/lightweight-markup.dj b/examples/lightweight-markup.dj new file mode 100644 index 0000000..3c41252 --- /dev/null +++ b/examples/lightweight-markup.dj @@ -0,0 +1,80 @@ +# General considerations + +[O]{custom-style=Initial}[ne]{.smallcaps} of the primary motivations behind the development of the components provided in this module was the ability to typeset an entire book "from front cover to back cover," using text files written in a lightweight markup language. + +My response to this challenge was the _re·sil·ient_ collection of classes and packages for SILE. +It includes master documents, book matters, a robust book class, and a neat styling paradigm. +As mentioned in the introduction, this module doesn't enforce the use of the resilient collection, but it does strongly recommend it for maximizing the package collection's benefits. +You do not have to believe me on faith, and you can judge for yourself. +After all, you are currently reading a user guide based on it, with styled headings, colorful dropped capitals, and more... + +Resilient meets most of my own requirements, and I hope to improve it further. +However, there was still one issue that needed to be addressed: the input format. + +## On markup languages + +While I once enjoyed using LaTeX, I found it impractical due to its complex and non-user-friendly syntax, as well as difficulties in converting to other formats. +Though Patoline and Pollen appeared promising, their lack of widespread support posed limitations. +Lout, once hailed as a potential LaTeX replacement, is now defunct. +I am going full opinionated here: +SILE does not fare much better with its SIL custom language, burdened by its own spacing idiosyncrasies. +HTML with CSS is too technical for most authors, and the WYSIWYG office suites are not appropriate for large projects. + +Anyhow, these languages are not "lightweight markup languages," as close to plain text as possible... +Forget FOP and XML-FO, insane XSLT transformations, DocBook and the like, for the same reasons. + +In recent exploration, Typst caught my attention. +However, in my views, its custom syntax fails to significantly improve upon existing options, blurring the distinction between content and style and lacking a clear separation of concerns. +Regarding styling, Typst does not revolutionize the field, and does not seem to tackle the challenge. +I am not saying that mixing scripting, layout decisions, styling, and content is _inherently_ bad. +I am doing it casually in this document (via raw inline and raw block elements), and it's fine for advanced tasks. +But for most content, it's not the best approach... + +What if I could get rid of SIL, and any similar custom language, and use a lightweight markup language instead? +Hold on, SILE is actually agnostic to the input format. +Since every bit of its Lua code is exposed to the user, it's possible to extend it to support other input formats, potentially leading to wider community adoption, and independent tooling. +Enter Markdown. +Actually, enter a serious contender, Djot. + +## On Djot over Markdown + +Should you pick Djot over Markdown, as hinted by this author in the following chapters? +Several reasons are purely technical and relate to how the languages are implemented within this module. + +: Easier debugging + + The implementation of Djot excels in tracking line numbers and positions, or at least in providing some context when encountering a syntax issue in the input document. + Conversely, the Markdown implementation lacks this functionality, making debugging and generating meaningful error messages more challenging. + Troubleshooting a minor markup mistake in a lengthy Markdown document can be arduous. + +: Better performances + + The Djot syntax demonstrates less ambiguity compared to Markdown, contributing to a more efficient implementation of the language grammar. + Markdown, while benefiting from a PEG-based implementation in order to accommodate numerous extensions and syntax subleties, suffers from decreased efficiency. + Parsing a comparable document in Djot significantly outperforms Markdown in processing speed. + +{.dinkus} +--- + +Beyond technical considerations, it's also worth examining the functionality of each language and assessing their ease of use. + +: Readability + + Markdown can be intricate, with subtle nuances where even a single space can trigger unexpected effects. + It exhibits non-intuitive behaviors, such as the use of indentation to denote code blocks. + There are inconsistent outcomes with lists, emphases, headings... + See John MacFarlane's ["Beyond Markdown"](https://johnmacfarlane.net/beyond-markdown.html) essay for some more examples, and the reasons that led him propose Djot. + +: Extensibility + + Djot offers greater flexibility and power by supporting attributes on both block and inline elements. These attributes can be leveraged by the converter to produce content with enhanced richness and styling options. + Djot's extensibility is also facilitated through the use of symbols. + In the upcoming chapter, we will delve into various methods employed by this module to expand Djot's capabilities even further. These include generalized captions, templates, variable substitutions, conditional statements, and more --- all while maintaining fidelity to the original concepts. + +: Compatibility + + Markdown enjoys widespread adoption, with native support from numerous tools and platforms. + This could be your main reason to stick with Markdown, especially if you already have a large collection of documents written in this format. + However, its utility is somewhat diminished by the proliferation of dialects and extensions developed over the years. + The Pandoc software adeptly handles a vast array of these variations, making it an excellent tool for converting between different flavors of Markdown. + Nevertheless, since version 3.1.12, Pandoc has extended its support to Djot, rendering the distinction between Markdown and Djot less significant. diff --git a/examples/sile-and-djot.dj b/examples/sile-and-djot.dj index a3142e9..547be75 100644 --- a/examples/sile-and-djot.dj +++ b/examples/sile-and-djot.dj @@ -1,227 +1,923 @@ -# Beyond Markdown: SILE and Djot +# SILE and Djot -{custom-style=raggedleft} -"Djot (/dʒɑt/) is a light markup syntax."[^djot-desc] +> "Djot (/dʒɑt/) is a light markup syntax. +> It derives most of its features from commonmark, but it fixes a few things +> that make ommonmark's syntax complex and difficult to parse efficiently. +> It is also much fuller-featured (...)" +^ From the [Djot website](https://djot.net/). -[^djot-desc]: From the [Djot website](https://djot.net/). +D{custom-style=Initial}[jot]{.smallcaps} is a fairly recent markup syntax, proposed by John MacFarlane in 2022. +It derives from (Common) Markdown, solving a lot of its complex syntax pitfalls, and also extending it on various aspects. +Since mosts concepts are similar, it initially felt rather natural to include it in this collection, so that authors interested in Markdown could give it a try. -Djot is a fairly recent markup syntax, proposed by John MacFarlane in 2022. -It derives from (Common) Markdown, solving a lot of its complex syntax pitfalls, -and also extending it on various aspects. -Since mosts concepts are similar, it felt rather natural to include it too in this collection, -so that authors interested in Markdown can give it a try. +This author has been using Djot in several projects, and it has become his preferred markup language. +Therefore, it's no longer a "side feature" of this collection, and our manual now starts with it. -The very chapter you are currently reading is written in Djot. As the syntax of the -language is fairly well described in its design document, we are not going to repeat it -here. The chapter is therefore a bit dull, mainly reproducing the same things we -achieved for Markdown earlier. At places, however, it focuses of the specifics of the rendering -engine. +The very chapter you are currently reading is written in Djot. +The syntax of the language is fairly well described in its own design document. +However, the Djot specification is a early-stage project, and will likely evolve. +This manual serves as reference for the current implementation of the Djot converter in this package collection, going through all the features. +At places, it also focuses on the specifics of this converter, and the extensions it provides. {% THIS TEXT SHOULD NOT APPEAR IN THE OUTPUT, IT IS A DJOT COMMENT %} -{custom-style=FramedPara} -Please note that this is an early implementation and that things may change in -future versions. +## Core concepts -## The native djot package +In a Djot document, most of the primary content is written as ordinary text, just typed as you would in any other document. +The "light markup" syntax, mostly based on punctuation characters, is employed to denote special elements. -Once you have loaded the `\autodoc:package{djot}`{=sile} package, -the `\autodoc:command{\include[src=]}`{=sile} command supports reading a Djot file. -The native parsing relies on John MacFarlane's *djot* Lua library. +The core concepts in Djot mirror those of Markdown, featuring inline and block elements for text formatting and document structuring, along with attributes to provide additional information or instructions for these elements. +: Ordinary text + Anything that isn't given a special meaning in the following sections is parsed as literal text. + +: Escaped characters + + All ASCII punctuation characters (even those that have no special meaning in Djot) may be "backslash-escaped." + Thus, `\*` is parsed and rendered as a literal \* character. + Backslashes before characters other than ASCII punctuation characters are just treated as literal backslashes, except before a newline, and a space (or a tab) character. + We will get back to this later. + +: Inline elements + + Inline elements are meant to be used within paragraphs or other block elements to enhance or modify specific portions of text. + They typically affect the formatting or styling. + Examples of inline elements include emphasis, links, images, and spans. + +: Block-level elements + + Block elements, on the other hand, create distinct blocks or sections within the document. + These elements typically span across multiple lines and can contain other inline or block elements within them. + Examples of block elements include: headings, paragraphs, lists, quotes, and divs. + +: Attributes + + Attributes are additional properties or metadata that can be associated with certain elements, allowing for more fine-grained control over the appearance and behavior of elements. + + +![Djot concepts at a glance.](./djot-concept-simplified.svg){width="75%lw"} + + +## Attributes + +Actually, let's start with attributes, as they have the same general syntax for both inline and block elements. + +Attributes are put inside curly braces. +On inline elements, attributes are placed immediately after the element they are attached to, with no intervening whitespace. +They may contain line breaks, and may be "stacked," in which case they will be combined. + +To attach attributes to a block-level element, put the attributes on the line immediately before the block. +Block attributes have the same syntax as inline attributes, but they must fit on one line. Repeated attribute specifiers can also be used, and the attributes will accumulate. + +Inside the curly braces, the following syntax is possible: + + - `#foo` specifies an identifier, for referencing purposes. + + An element may have only one identifier; if multiple identifiers are given, the last one is used. + + - `.foo` specifies a class, for styling purposes. + + Multiple classes may be given in this way; they will be combined. + + - `key="value"` or `key=value` specifies a key-value attribute. + + Quotes are not needed when the value consists entirely of ASCII alphanumeric characters or `_` or `:` or `-`. Backslash escapes may be used inside quoted values. + + - `?foo` and `!foo` specify a condition. + + This is an extension specific to this converter, see §[](#djot-conditional-attributes). + +## Inline elements + +### Spaces + +Djot treats multiple spaces as a single space. + +A backslash before a space is parsed as a non-breaking space, a space that will not be used as a line break point. +This converter interprets it as a justifiable interword-space --- that is, a variable-width space for the purpose of justification. +It can be made fixed by adding the `.fixed` attribute just after the space. +In that case, it still has the same width as a regular interword-space, but it does not stretch or shrink. + + +### Line breaks + +Line breaks in inline content are treated as “soft” breaks. +In other words, this converter semantically interprets them as mere spaces. + +To get a hard line break, use a backslash at the end of a line. Hard line breaks...\ +... are supported. + +{custom-style=CodeBlock} +::: +``` +Hard line breaks...\ +... are supported. +``` +::: + +Any backslash before a newline (or before spaces or tabs followed by a newline) is parsed as a hard line break. +Spaces and tab characters before the backslash are ignored in this case. + + +### Smart punctuation + +Smart typography is a standard feature in Djot. + +#### Dashes and ellipsis + +Three dashes (`---`) in an inline text sequence are converted to an em-dash (---), two dashes (`--`) to an en-dash useful for ranges (ex., "it's at pages 12--14"), and three dots +(`...`) to an ellipsis (...). + +#### Smart quotes + +Smart quotes and apostrophes are also automatically handled. +Straight double quotes (`"`) and single quotes (`'`) are converted into appropriate quotes. +The Djot specification states they are replaced by _curly_ quotes, but this converter goes further and takes into account the current language when converting +straight double and single quotation marks to the appropriate typographic variant, see §[](#djot-language-changes). + +Djot is pretty good about figuring out from context which direction of quote is needed. +However, its heuristics can be overridden by using curly braces to mark a quote as an opener `{'` or a closer `'}`: '}Tis Socrates' season to be jolly! + +{custom-style=CodeBlock} +::: +``` +'}Tis Socrates' season to be jolly! +``` +::: + +If you _really_ want a straight quote, use a backslash-escape (`\"` or `\'`). + + +### Emphasis + +Regular _emphasis_ is delimited by `_` characters. +Strong *emphasis* is delimited by `*` characters. +This `_` or `*` can open emphasis only if it is not directly followed by whitespace. +It can close emphasis only if it is not directly preceded by whitespace, and only if there are some characters besides the delimiter character between the opener and the closer. + +Curly braces may be used to force interpretation of a `_` or `*` either as an opener or as a closer.{_ This is emphasized, despite the spaces! _} + +{custom-style=CodeBlock} +::: +``` +{_ This is emphasized, despite the spaces! _} +``` +::: + +Emphasis can be nested. +For regular emphasis, this is interpreted as per usual typography conventions, adequately switching between italics and upright fonts. +_This is _an emphasis inside_ an emphasis._ + +{custom-style=CodeBlock} +::: +``` +_This is _an emphasis inside_ an emphasis._ +``` +::: + + +### Insertions & deletions + +To mark inline text as inserted, use `{+` and `+}`. +To mark it as deleted, use `{-` and `-}`. +The `{` and `}` brackets are mandatory. +Thus: + + - {+insertion+} is `{+insertion+}` + - {-deletion-} is `{-deletion-}` + +For this converter, these are equivalent to spans with `.inserted` and `.deleted` pseudo-classes respectively (see §[](#djot-spans) for details and styling). + + +### Highlighted content + +Inline content between `{= and =}` is treated as highlighted (or "marked"). +The `{` and `}` brackets are mandatory. + +You thus get an {=highlight=} with `{=highlight=}` + +For this converter, this is equivalent to a span with a `.mark` pseudo-class (see §[](#djot-spans) for details and styling). + + +### Superscripts & subscripts + +Superscript is delimited by `^` characters, subscript by `~`. +Curly braces may be used (resp. `{^...^}` and `{~...~}`), but are not required. +Thus, H~2~O is a liquid, and 2^10^ is 1024. + +{custom-style=CodeBlock} +::: +``` +Thus, H~2~O is a liquid, and 2^10^ is 1024. +``` +::: + +As Djot allows attaching arbitrary attributes to the elements, it's worth noting that +we notably support a `fake` boolean attribute here. +Albeit not very useful in practice, it controls whether fake or native superscripts or subscripts are used.[^djot-textsubsuper] +You might notice a small difference between 2^10^{fake=false} and 2^10^{fake=true}. + +{custom-style=CodeBlock} +::: +``` +... difference between 2^10^{fake=false} and 2^10^{fake=true}. +``` +::: + +There is another, likely more important, implication. +This converter assumes that the content of a superscript or subscript consists of text, as it tries to use font features or text scaling techniques to render it. + +[^djot-textsubsuper]: Refer to the *textsubsuper* package for details. + Note that this is also available as a setting, so normally you would not have to rely on this attribute in your documents, unless you use an assortment of fonts where the default behavior is not satisfactory... + + +{#djot-spans} +### Generic inline containers (spans) + +Text in square brackets that is not a link or image and is followed immediately by an attribute is treated as a generic span. + +Djot accepts attributes on any element, so what follows also applies to any other inline element, the spans being a generalization.[^djot-spans-generalization] + +[^djot-spans-generalization]: When this converter encounters attributes on an element, most of the time it actually wraps the latter in an implicit span. + +This converter recognizes a few specific attributes on spans: + + - Identifiers for referencing purposes + - The `custom-style` attribute, see §[](#djot-custom-styles) + - The `lang` attribute, see §[](#djot-language-changes) + +#### Basic styling + +This converter also recognizes the following pseudo-classes on spans: + + - [Small Caps]{.smallcaps}, as `[Small Caps]{.smallcaps}` + - [underlines]{.underline} with `[underlines]{.underline}` + - [struck out]{.strike} with `[struck out]{.strike}` + - [insertions]{.inserted} with `[insertions]{.inserted}` + - [deletions]{.deleted} with `[deletions]{.deleted}` + - [highlighted]{.mark} with `[highlighted]{.mark}` + +The three last ones are equivalent to the `{+...+}` and `{-...-}` syntaxes, and the `{=...=}` syntax, respectively. +In other term, these Djot inline elements are mere shortcuts for the corresponding spans. + +Moreover, if used within in a *resilient* class, the converter uses the `md-underline`, `md-strikethrough`, `md-insertion`, `md-deletion` and `md-mark` styles, respectively, if they are defined in your style file. +Another way to put it is that the converter then does the same thing as a `{custom-style="..."}` attribute (see §[](#djot-custom-styles)). +You can style and fine-tune these elements as you wish, and even use them for other purposes than their original intent.[^djot-spans-hook] + +[^djot-spans-hook]: This is one of the reasons for keeping insertions and deletion distinct from underlines and strikethroughs, as they may have different semantics in some contexts, though their default rendering may be the same. + +#### Smarter typography + +On inline content, the `.decimal` pseudo-class attribute instructs the converter to consider numbers in the content as decimal numbers, formatted with suitable decimal mark and digit grouping according to the usage in the current language. +This allows, say, 1984 to be rendered as "[1984]{.decimal} years ago" in English, or "[1984 années]{.decimal lang=fr}" in French, with appropriate separators. + +{custom-style=CodeBlock} +::: +``` +... rendered as "[1984]{.decimal} years ago" in English, +or "[1984 années]{.decimal lang=fr}" in French ... +``` +::: + +The `.nobreak` pseudo-class attribute ensures that line-breaking will not be applied there. +Use it wisely around small pieces of text, or you might end up with more serious justification issues! +Yet, it might be useful for proper names, etc. + + +{#djot-image} +### Images + +![This man is Gutenberg.](./gutenberg.jpg){#djot-gutenberg width="3cm"} + +This is obtained with the following syntax: + +{custom-style=CodeBlock} +::: +``` +![This man is Gutenberg.](./gutenberg.jpg){width="3cm"} +``` +::: + +Attributes are optional, and are passed through to the underlying SILE package. +You can notably specify the required image width and/or height, as done just above, by appending the `{width=... height=...}` attributes --- Note that any unit system supported by SILE is accepted.[^djot-img-attributes] + +There are actually two kinds of image syntax: (direct) inline images and (indirect) reference images. +Above, we used the former kind. +The reference images are defined elsewhere in the document, and are referenced by their label: + +{custom-style=CodeBlock} +::: +``` +![Good old gutenberg][gutenberg] +... +{width="3cm"} +[gutenberg]: ./gutenberg.jpg +``` +::: + +Note that attributes are specified before the reference label (considered as a block element). +Direct (inline) link's attributes override reference's attributes. + +[^djot-img-attributes]: The converter does not check the validity of the attributes, and it is the responsibility of the underlying package to do so. + +{#djot-captioned-images} +#### Implicit captioned figures + +An image with nonempty caption (i.e. "alternate" text), occurring alone by itself in a paragraph, will be rendered as a figure with a caption, as actually seen above. +Otherwise, the caption is ignored. + +If your document class or previously loaded packages provide a `captioned-figure` environment, it will be wrapped around the image (and it is then assumed to take care of the caption, i.e. to extract and display it appropriately). +Specifically, if the *resilient* book class is used, the caption will be numbered by default, and added to the list of figures. Specify `.unnumbered`, and `.notoc` respectively, if you do not want it. +Otherwise, the converter uses its own fallback method. + +#### Extended image types + +Besides regular image files, a few specific file extensions are also recognized and processed appropriately by this converter. +Notably ![](./examples/manicule.svg){height="1.3ex"} SVG is supported too (`.svg`), as demonstrated. +This inline "manicule" is obtained with: + +{custom-style=CodeBlock} +::: +``` +... ![](./examples/manicule.svg){height="1.3ex"} is supported ... +``` +::: + +Files in Graphviz DOT graph language (`.dot`) are supported and rendered as images, when the *embedders.sile* collection is properly configured. + +![The *markdown.sile* ecosystem (simplified).](./markdown-sile-schema.dot){width="90%lw"} + +This image is obtained with the following syntax. + +{custom-style=CodeBlock} +::: +``` +![The *markdown.sile* ecosystem (simplified).](./markdown-sile-schema.dot){width="90%lw"} +``` +::: + +CSV files are also supported, when the *piecharts.sile* optional collection is properly installed (see §[](#recommended-additional-packages)), and are rendered as pie charts. + +![A pie chart.](./data-pie.csv){height="8em" cutoff="0.07"} + +This chart is obtained with the following syntax. + +{custom-style=CodeBlock} +::: +``` +![A pie chart.](./data-pie.csv){height="8em" cutoff="0.07"} +``` +::: + + +### Links and cross-references + +Here is a link to [the SILE website](https://sile-typesetter.org/), and here is an internal link to the "[Images](#djot-image)" section. + +{custom-style=CodeBlock} +::: +``` +... a link to [the SILE website](https://sile-typesetter.org/) +... an internal link to the "[Images](#djot-image)" section. +``` +::: + +As seen in the example, links can be local (referring to an identifier) or external (for instance, referring to a URL). + +You can use attributes on links: +the [SILE website](https://sile-typesetter.org/){.underline}.[^djot-link-attributes] + +{custom-style=CodeBlock} +::: +``` +... the [SILE website](https://sile-typesetter.org/){.underline}. +``` +::: + +[^djot-link-attributes]: Within a **resilient** class, we'd possibly rather recommend using a style to color links, etc. + +There are actually two kinds of links, inline links and reference links. +Both start with the link text (which may contain arbitrary inline formatting) inside `[`...`]` delimiters. + +Inline links then contain the link destination (URL) in parentheses, as shown above. +There should be no space between the `]` at the end of the link text and the open `(` defining the link destination. + +Reference links use a reference label in square brackets, instead of a destination in parentheses. +This is another link to [the SILE website][sile]. + +{custom-style=CodeBlock} +::: +``` +Another link to [the SILE website][sile]. +... +[sile]: https://sile-typesetter.org/ +``` +::: + +[sile]: https://sile-typesetter.org/ + +The reference label should be defined somewhere in the document. +If the label is empty, then the link text will be taken to be the reference label as well as the link text. + +{#djot-cross-references} +#### Cross-references + +Djot, like Markdown, does not define a proper way to insert cross-references of the kind you would see in a book, as in the following example. + +> The section on "[](#djot-tables){.title}", +> that is [](#djot-tables){.section}, +> is on page [](#djot-tables){.page}. + +This converter takes a bold decision, though unlikely to break anything unexpected. +Empty local links (that is, without inline display content) are interpreted as cross-references. +By default, they are resolved to the closest numbering item, whatever that might be in the hierarchical structure of your document. +A pseudo-class attribute may be used to override the default behavior and specify which type +of reference is expected (page number, section number or title text). +Thus, the above example was obtained from the following input: + +{custom-style=CodeBlock} +::: +``` +The section on "[](#djot-tables){.title}", +that is [] (#djot-tables){.section}, +is on page [](#djot-tables){.page}. +``` +::: + +Besides heading levels, it also works for various elements where you can define an identifier. +For instance we had some centered text in section [](#djot-centered). +With appropriate class and package support,[^djot-sec-label-support] you may even refer to Gutenberg as "figure [](#djot-gutenberg)", or to some poetry verse mentioning the Sun ("Soleil"), in [](#sun){.section}, as "verse [](#sun)". + +[^djot-sec-label-support]: Typically, it works with the *resilient* collection of classes and packages. It won't work with non-supporting class and packages, using the fallback implementation for captioned elements, poetry, etc. + +#### Automatic links + +A URL or email address that is enclosed in angle brackets (`<`...`>`) is rendered as a link. +The content between pointy braces is treated literally (backslash-escapes may not be used). + +Here is a link to the SILE website, . + +{custom-style=CodeBlock} +::: +``` +... to the SILE website, . +``` +::: + + +### Verbatim text + +Verbatim content begins with a string of consecutive backtick characters (`` ` ``) and ends with an equal-lengthed string of consecutive backtick characters. +This is mostly used for `code` --- obtained with `` `code` `` (enjoy the inception). + +Material between the backticks is treated as verbatim text (backslash escapes don't work there). +If the content starts or ends with a backtick character, a single space is removed between the opening or closing backticks and the content. +If the text to be parsed as inline ends before a closing backtick string is encountered, the verbatim text extends to the end. + + +### Maths + +To include TeX-like math, put the math in a verbatim span and prefix it with `$` (for inline math) or `$$` (for display math). +For instance, the following code produces $`e^{i\pi}=-1`. + +{custom-style=CodeBlock} +::: +``` +... the following code produces $`e^{i\pi}=-1`. +``` +::: + +There is an important constraint, though: you have to restrict yourself to the syntax subset supported by SILE. This being said, some nice fomulas may be achieved. +$$`\pi=\sum_{k=0}^\infty\frac{1}{16^k}(\frac{4}{8k+1} − \frac{2}{8k+4} − \frac{1}{8k+5} − \frac{1}{8k+6})` + + +{custom-style=CodeBlock} +::: +``` +... may be achieved. +$$`\pi=\sum_{k=0}^\infty\frac{1}{16^k}(\frac{4}{8k+1} − \frac{2}{8k+4} − \frac{1}{8k+5} − \frac{1}{8k+6})` +``` +::: + +### Footnote calls + +A footnote call[^djot-some-fn] is marked with `^` and the reference label in square brackets. +See §[](#djot-footnotes) for the syntax of the footnote itself. + +{custom-style=CodeBlock} +::: +``` +A footnote call[^djot-some-fn] is marked... +``` +::: + +[^djot-some-fn]: An example footnote. + + +{#djot-symbols} +### Symbols + +Surrounding a word with `:` signs creates a “symbol.” +The Djot specification leaves the interpretation of such symbols to the rendering engine. +This converter uses them in specific ways, as detailed in §[](#djot-advanced-topics). + + +{#djot-raw-inlines} +### Raw inlines + +Raw inline content in any format may be included using a verbatim span followed by `{=format}`. +This content is intended to be passed through verbatim when rendering the designated format, but ignored otherwise. + +This converter supports a `{=sile}` annotation, to pass through its content in SIL language. Let's do something fun`\dotfill`{=sile}! + +{custom-style=CodeBlock} +::: +``` +Let's do something fun`\dotfill`{=sile}! +``` +::: + +It also supports `{=sile-lua}` to pass Lua code. Let's try it here +`SILE.call("hrule", {height="1ex", width="1ex"})`{=sile-lua} + +{custom-style=CodeBlock} +::: +``` +Let's try it here +`SILE.call("hrule", {height="1ex", width="1ex"})`{=sile-lua} +``` +::: + + +## Block elements + +### Paragraphs + +A paragraph is a sequence of non-blank lines that does not meet the condition for being one of the other block-level elements. + +A paragraph ends with a blank line or the end of the document. +The textual content is parsed as a sequence of inline elements. +A single newline is treated as a space. + +### Headings + +A heading starts with a sequence of one or more `#` characters, followed by whitespace. +The number of `#` characters defines the heading level. +The heading text following that sequence is parsed as inline content. + +For example, this very section is a level three heading. +It was therefore obtained with: + +{custom-style=CodeBlock} +::: +``` +### Headings +``` +::: + +The heading text may spill over onto following lines, which may also be preceded by the same number of `#` characters (but these can also be left off). + +The heading ends when a blank line (or the end of the document or enclosing container) is encountered. + +This converter accepts the following pseudo-classes on heading attributes: + + - `.unnumbered` suppresses numbering of the heading. + - `.notoc` suppresses the heading from appearing in the table of contents. + +The heading attributes, obviously, also accept an identifier, for referencing purposes. +Here is an example illustrating the use of these attributes: + +{custom-style=CodeBlock} +::: +``` +{#djot-unnumbered-heading .unnumbered .notoc} +#### Other attributes +``` +::: + +{#djot-unnumbered-heading .unnumbered .notoc} +#### Other attributes + +Other attributes are passed through to the underlying heading implementation, which might do something with them.[^djot-heading-level-mapping] + +[^djot-heading-level-mapping]: The converter assumes it can map heading levels to SILE commands `part`, `chapter`, `section`, `subsection`, `subsubsection`. +It uses a very basic fallback if these are not found (or if the sectioning level gets past that point). +The implication, therefore, is that the document class or other packages have to provide the relevant implementations. +Also note that parts are supported by the *resilient* book class as a level 0 heading, and are therefore only available when header shifting is applied (see §[](#djot-configuration)). + +### Block quotes + +A block quote is a sequence of lines, each of which begins with `>`, followed either by a space or by the end of the line. +The contents of the block quote are parsed as block-level content. + +#### Regular quotes + +{custom-style=CodeBlock} +::: +``` +> This is block quote. +> +> > Such quotes can be nested. ``` -\use[module=packages.djot] -\include[src=somefile.dj] +::: + +> This is block quote. +> +> > Such quotes can be nested. + +If your document class or previously loaded packages provide a `blockquote` environment, it will be used. +Otherwise, the converter uses its own fallback method, with hard-coded styling. + +#### Attributed quotes (epigraphs) + +{ rule="0.4pt" } +> The Library is a sphere whose exact centre is any one of its +> hexagons and whose circumference is inaccessible. +^ Jorge Luis [Borges]{.smallcaps}, "The Library of Babel" + +Standard Djot only honors captions on tables, and ignores them on other block elements. +When using a *resilient* document class, however, a captioned block quote is rendered as an "epigraph", with the caption content used as its "source" (in a broad sense). +The caption is introduced with a `^` character, and can contain inline elements. +For instance, the above quote was obtained with: + +{custom-style=CodeBlock} +::: ``` +{ rule="0.4pt" } +> The Library is a sphere whose exact centre is any one of its +> hexagons and whose circumference is inaccessible. +^ Jorge Luis [Borges]{.smallcaps}, "The Library of Babel" +``` +::: + +Attributes are passed through to an implicit "div" (so as to honor the language, a link target identifier, etc.) and eventually to the underlying epigraph environment. +Any option supported by the *resilient.epigraph* package may thus be used. -Embedding raw Djot content from within a SIL document is also possible: +Be aware that this behavior is currently an extension. +Other Djot converters will therefore likely skip the caption. + +### Lists + +A list item consists of a list marker followed by a space (or a newline) followed by one or more lines, indented relative to the list marker. +{custom-style=CodeBlock} +::: ``` -\begin[type=djot]{raw} -Some *Djot* content -\end{raw} +1. Nesting... + + ... works as intended. + + - Fruits + + - Apple ``` +::: -{#basic-djot-typesetting} -### Basic typesetting -As of formatting, _italic_, *bold*, and `code` all work as expected. + 1. Nesting... -Hard line breaks...\ -... are supported. + ... works as intended. -Smart typography is a standard feature. Three dashes (`---`) in an -inline text sequence are converted to an em-dash (---), two dashes (`--`) -to an en-dash useful for ranges (ex., "it's at pages 12--14"), and three dots -(`...`) to an ellipsis (...). Smart quotes and apostrophes are also automatically -handled. + - Fruits -Superscripts and subscripts are available : H~2~O is a liquid, 2^10^ is 1024. -As Djot allows attaching arbitrary attributes to the elements, it's worth noting that -we notably support a `fake` boolean attribute here.[^djot-textsubsuper] + - Apple -[^djot-textsubsuper]: Controlling whether fake or native superscripts or subscripts are used. -Refer to the `\autodoc:package{textsubsuper}`{=sile} package for details. -Other nice features include: +#### Unordered lists - - {-deletions-} with `{-deletions-}` - - {+insertions+} with `{+insertions+}` - - {=highlight=} with `{=highlight=}` - - [underlines]{.underline} with `[underlines]{.underline}` - - [struck out]{.strike} with `[struck out]{.strike}` - - [Small Caps]{.smallcaps}, as `[Small Caps]{.smallcaps}` +Unordered lists (a.k.a. itemized or bulleted lists) are introduced by a bullet list marker, either `-`, `+`, or `*`. +With this converter, the list marker is not significant, and the supporting package is responsible for rendering nested lists with the appropriate symbol. -### Standard features +{custom-style=CodeBlock} +::: +``` + - This is a bullet list. + + - It can be nested. + + - And nested again. + ``` +::: -Let's go quickly over these features, just to ensure the package does what it says, -with a good parity with what we earlier described for Markdown. + - This is a bullet list. -#### Lists + - It can be nested. -Unordered lists (a.k.a. itemized or bulleted lists) are obviously supported, or -we would not have been able to use them in the previous sections. + - And nested again. -Ordered lists are supported as well. -The starting number is honored, and you have the flexibility to use -digits, roman numbers or letters (in upper or lower case). -Djot also recognizes several delimiter styles. - b. This list uses lowercase letters and starts at 2. Er... at "b", that is. +#### Ordered lists - i) Roman number... - ii) ... followed by a right parenthesis rather than a period. +Djot supports several types of ordered lists. -By the way, +--- - 1. Nesting... +1. Decimal-enumerated, followed by period. - ... works as intended. +1) Decimal-enumerated, followed by parenthesis. - - Fruits +(1) Decimal-enumerated, enclosed in parentheses. - - Apple +a. Lower-alpha-enumerated, followed by period. -Task lists following the GitHub-Flavored Markdown (GFM) format are supported too. +a) Lower-alpha-enumerated, followed by parenthesis. - - [ ] Unchecked item - - [x] Checked item +(a) Lower-alpha-enumerated, enclosed in parentheses. -Definition lists are also decently supported. Note the syntax differs from Pandoc-style Markdown. +A. Upper-alpha-enumerated, followed by period. - : apples +A) Upper-alpha-enumerated, followed by parenthesis. - Good for making applesauce. +(A) Upper-alpha-enumerated, enclosed in parentheses. - : citrus +i. Lower-roman-enumerated, followed by period. - Like oranges but yellow. +i) Lower-roman-enumerated, followed by parenthesis. -Djot attributes are passed to the underlying definition environment.[^djot-resilient-defn] +(i) Lower-roman-enumerated, enclosed in parentheses. -[^djot-resilient-defn]: With the *resilient.defn* environment, the `variant` option may thus be used to switch to an alternate style. +I. Upper-roman-enumerated, followed by period. -#### Block quotes +I) Upper-roman-enumerated, followed by parenthesis. -Block quotes work. +(I) Upper-roman-enumerated, enclosed in parentheses. -> This is block quote. -> -> > They can be nested. +--- -#### Footnotes +In a given type of list, you do not have to number the items in order by yourself, as Djot automatically increments the number for you. +Your starting number, however, is honored, would you want to start at a different number than 1 (in any numbering scheme). -Footnotes work.[^djot-fnt] -Moreover, Djot allows attaching attributes to the footnote reference. -This offers us some additional flexibility. -Here is therefore a possibly different footnote.[^djot-fun-note] It is obtained with: +{custom-style=CodeBlock} +::: +``` +1. Item +1. Item +1. Item +``` +::: +1. Item +1. Item +1. Item + +{custom-style=CodeBlock} +::: ``` -... different footnote[^djot-fun-note]... + b. This list starts at "b" + + iii) This list starts a "iii" in roman numerals. + i) Next item is thus "iv". + + a. The next item here is "c". +``` +::: -{mark="†"} -[^djot-fun-note]: If using... + b. This list starts at "b" + + iii) This list starts a "iii" in roman numerals. + i) Next item is thus "iv". + + a. The next item here is "c". + +#### Definition lists + +In a definition list item, the first line or lines after the `:` marker is parsed as inline content and taken to be the term defined. +Any further blocks included in the item are assumed to be the definition. + +{custom-style=CodeBlock} +::: +``` +: apples + + Good for making applesauce. + +: citrus + + Like oranges but yellow. ``` +::: -It can even be referenced in the flow of text: see note [](#djot-fun-note). -[^djot-fnt]: This is yet another regular footnote. +: apples -{mark="†"} -[^djot-fun-note]: If using a resilient class, this note should be marked with an _obelus_ ("dagger"). -Cross-references are detailed later in this chapter. -For linking purpose, this converter uses the footnote reference identifier by default, but it can of course be overridden via the attribute syntax. -Other attributes are passed through to the underlying footnote implementation. + Good for making applesauce. -#### Languages +: citrus -Language changes within the text are supported, on either blocks or inline -elements. + Like oranges but yellow. -{lang=fr} -> Cette citation est en français! +Block attributes are passed to the underlying definition environment. +When not using a supporting class or package, the converter uses its own fallback method, with hard-coded styling (e.g. the term is typeset in boldface). +When using the *resilient* classes, the converter uses the *resilient.defn* environment, and the `variant` option may thus be used to switch to an alternate style.[^djot-defn-variant] +For the purpose of illustration, let's say you have a "Custom" variant with an _ad hoc_ style. -Or inline in text: ["Encore du français!"]{lang=fr} +{custom-style=CodeBlock} +::: +``` +{variant=Custom} +: Djot + + A light markup syntax. +``` +::: -As can be seen, the current language is taken into account when converting -straight double and single quotation marks to the appropriate typographic variant. +{variant=Custom} +: Djot -#### Custom styles + A light markup syntax. -The converter also supports the `{custom-style="..."}` attribute. +[^djot-defn-variant]: Within *resilient*, you can already fully style the default definition list. +The `variant` option is intended to switch to a different style, when you need more than one for different purposes in the same document. + +#### Task lists + +A bullet list item that begins with `[ ]`, `[X]`, or `[x]` followed by a space is a task list item, either unchecked (`[ ]`) or checked (`[X]` or `[x]`). + +{custom-style=CodeBlock} +::: +``` + - [ ] Unchecked item + - [x] Checked item +``` +::: + + - [ ] Unchecked item + - [x] Checked item + +{#djot-footnotes} +### Footnote references -{#centered-djot custom-style="center"} -This is SILE and Djot at their best{custom-style="strong"}, really! +Footnotes work.[^djot-fnt] +Moreover, Djot allows attaching attributes to the footnote reference. +This offers us some additional flexibility. +Here is therefore a possibly different footnote.[^djot-fun-note] It is obtained with: -#### Images +{custom-style=CodeBlock} +::: +``` +... different footnote[^djot-fun-note]... + +{mark="†"} +[^djot-fun-note]: If using... +``` +::: -![This man is still Gutenberg.](./gutenberg.jpg){#djot-gutenberg width=3cm} +It can even be referenced in the flow of text: see note [](#djot-fun-note). -![xxx](./manicule.svg){height="0.6bs"} Everything seen for Markdown also applies here. +[^djot-fnt]: This is yet another regular footnote. -#### Maths +{mark="†"} +[^djot-fun-note]: If using a resilient document class, this note should be marked with an _obelus_ ("dagger"). +For cross-references, see §[](#djot-cross-references). +For linking purpose, this converter uses the footnote reference identifier by default, but it can of course be overridden via the attribute syntax. +Other attributes are passed through to the underlying footnote implementation. -Inline TeX-like math works, as in $`e^{i\pi}=-1`. -Display math also works: -$$`\pi=\sum_{k=0}^\infty\frac{1}{16^k}(\frac{4}{8k+1} − \frac{2}{8k+4} − \frac{1}{8k+5} − \frac{1}{8k+6})` {#djot-tables} -#### Tables +### Tables -Djot supports the "pipe table" syntax, with its own way for marking the (optional) -caption.[^djot-numbered-caption] +Djot supports the "pipe table" syntax, with its own way for marking the optional caption. | Right | Left | Default | Center | |------:|:-----|---------|:------:| | 12 | 12 | 12 | 12 | | 123 | 123 | 123 | 123 | - ^ Demonstration of a pipe table. -[^djot-numbered-caption]: When using the **resilient** classes, the caption will be numbered by -default, and added to the list of tables. Specify `.unnumbered`, and `.notoc` respectively, as -table attributes, if you do not want it. +{custom-style=CodeBlock} +::: +``` +| Right | Left | Default | Center | +|------:|:-----|---------|:------:| +| 12 | 12 | 12 | 12 | +| 123 | 123 | 123 | 123 | +^ Demonstration of a pipe table. +``` +::: -#### Basic links -Here is a link to [the SILE website](https://sile-typesetter.org/), and -here is an internal link to the "[Basic typesetting](#basic-djot-typesetting)" section. +When using the *resilient* classes, the caption will be numbered by default, and added to the list of tables. +Specify `.unnumbered`, and `.notoc` respectively, as table attributes, if you do not want it. -#### Cross-references +### Code blocks -The converter uses the same "tricks" as with Markdown, interpretating links without display -content as cross-references, and specific pseudo-class attributes to indicate which type of -reference is requested. +A code block is enclosed in a "fence," starting with a line of three or more consecutive backticks and ending with a line of backticks equal in length to the opening line. +The opening line may contain a format string, which is passed through to the converter. +In this version, this is equivalent to specifying a pseudo-class attribute in a block attribute. +(So a `xxx` format string after the opening fence and a `.xxx` class specifier in a block attribute are currently equivalent.) -The section on "[](#djot-tables){ .title }", that is [](#djot-tables){ .section }, -is on page [](#djot-tables){ .page }. Figure\ [](#djot-gutenberg) shows Gutenberg; the Sun ("Soleil") -is mentioned in §[](#sun){.section}, verse [](#sun). +The content of the code block is passed through to the converter, which is responsible for interpreting it. +In most cases, the content is rendered as verbatim text, with some exceptions detailed below, for "recognized" formats. -#### Code blocks +#### Lua code blocks -Code blocks work: +Code blocks marked as being in the Lua language are rendered as verbatim text, with syntax highlighting. +For instance, the following code block... +{custom-style=CodeBlock} +::: +```` ```lua function fib (n) -- Fibonacci numbers @@ -229,61 +925,185 @@ function fib (n) return fib(n - 2) + fib(n - 1) end ``` +```` +::: -Code blocks marked as being in the Graphviz DOT language are rendered as images. - -{width="3.5cm" layout=twopi} -```dot -graph { - node [fillcolor="lightskyblue:darkcyan" style=filled gradientangle=270] - a -- { b d }; - b -- { c e }; - c -- { f g h i }; - e -- { j k l m n o }; -} -``` +... is rendered as: -{render=false width=5cm layout=twopi} -```dot -graph { - node [fillcolor="lightskyblue:darkcyan" style=filled gradientangle=270] - a -- { b d }; - b -- { c e }; - c -- { f g h i }; - e -- { j k l m n o }; -} +{custom-style=CodeBlock} +::: +```lua +function fib (n) + -- Fibonacci numbers + if n < 2 then return 1 end + return fib(n - 2) + fib(n - 1) +end ``` +::: +^ An example of a syntax-highlighted Lua code snippet. + + +This is a very naive approach to syntax-highlighting, until the converter possibly supports a more general solution. + +#### Rendered code blocks + +If the converter knows how to render the content of a code block, it will do so by default. +The `render` attribute can be set to `false` to prevent this behavior, and enforce the content to be rendered as raw verbatim text. + +: Mardown and Djot code blocks + + Code blocks marked as being in Markdown or Djot are interpreted. + For Markdown, attributes are passed to the converter, allowing to possibly use different compatibility options (for Markdown especially, see §[](#markdown-configuration)). + This feature allows switching between those languages, would there be something one does not support yet. + + At the time of writing, for instance, Djot does not support "line blocks". + This chapter being written in Djot, let's just switch to Markdown and type some poetry. + + ```markdown + ::: {.poetry} + | I took up the runes, + | screaming I took them, + | then I fell back from there. + ::: + ``` + + This was obtained with: + + {custom-style=CodeBlock} + :::: + ```` + ```markdown + ::: {.poetry} + | I took up the runes, + | screaming I took them, + | then I fell back from there. + ::: + ``` + ```` + :::: + +: Other rendered code blocks + + Other formats may be recognized, provided the converter knows how to handle them. + Internally, this relies on two extensible mechanisms: + + - SILE "raw" handlers, which may be declared by other packages. + - Renderers for text formats that can be converted to an image, as supported by the *embedders.sile* module, provided the necessary software is installed on your host system. (See §[](#recommended-additional-software) for more details.) + + : Graphviz DOT graphs + + Thus, if you properly installed the *embedders.sile* collection and its software dependencies, then code blocks marked as being in the Graphviz DOT language are rendered as images. + Attributes are passed to the underlying processor. + For instance, the image below is produced with `{.dot width="3.5cm" layout=twopi}`. + + ::: + {width="3.5cm" layout=twopi} + ```dot + graph { + node [fillcolor="lightskyblue:darkcyan" style=filled gradientangle=270] + a -- { b d }; + b -- { c e }; + c -- { f g h i }; + e -- { j k l m n o }; + } + ``` + ::: + ^ An example of a rendered DOT graph. + + The original DOT description used in the code block is shown hereafter. + + {custom-style=CodeBlock} + ::: + {render=false width=5cm layout=twopi} + ```dot + graph { + node [fillcolor="lightskyblue:darkcyan" style=filled + gradientangle=270] + a -- { b d }; + b -- { c e }; + c -- { f g h i }; + e -- { j k l m n o }; + } + ``` + ::: + + : Pie charts + + Likewise, if you installed the optional *piecharts.sile* collection, then code blocks marked as "piechart" are automatically rendered, with all other attributes are passed to the underlying processor. + Consider the following code block, consisting in a CSV table... + + {custom-style=CodeBlock} + ::: + ```` + {height=8em} + ```piechart + Player,Score + Mario,55 + Luigi,23 + Peach,12 + Bowser,10 + ``` + ```` + ::: + + ... then, provided a proper setup, it is rendered as: + + ::: + {height=8em} + ```piechart + Player,Score + Mario,55 + Luigi,23 + Peach,12 + Bowser,10 + ``` + ::: + ^ An example of a rendered pie chart. + + +### Content container divisions (divs) + +A div begins with a line of three or more consecutive colons, optionally followed by white space and a class name (but nothing else). It ends with a line of consecutive colons at least as long as the opening fence, or with the end of the document or containing block. + +Djot accepts attributes on any element, so what follows also applies to any other block element, the divs being a generalization.[^djot-divs-generalization] + +[^djot-divs-generalization]: When this converter encounters attributes on some block elements (such as paragraphs and block quotes), most of the time it actually wraps the latter in an implicit div. + +This converter recognizes a few specific attributes on divs: + + - Identifiers for referencing purposes + - The `custom-style` attribute, see §[](#djot-custom-styles) + - The `lang` attribute, see §[](#djot-language-changes) + +#### Generic captioned figures -Code blocks marked as being in Markdown or Djot are interpreted too (again, unless -the `render` attribute is set to false). -For Markdown, attributes are passed to the converter, allowing to possibly use different compatibility options (see "[Configuration](#configuration)"). -This feature allows switching between those languages, would there be something one does not support yet. -At the time of writing, for instance, Djot does not support "line blocks". - -This very chapter being written in Djot, let's just switch to Markdown and type some poetry. +Standard Djot only honors captions on tables, and ignores them on other block elements. +This converter, however, renders a div witb a caption as a "figure". -```markdown -::: {.poetry} -| I took up the runes, -| screaming I took them, -| then I fell back from there. ::: -``` +_Some content here._ +::: +^ An example of a div used as captioned figure. -#### Raw blocks +The caption is introduced with a `^` character, and can contain inline elements. +For instance, the above quote was obtained with: -```=sile -This \em{entire} sentence is typeset in a \em{raw block}, in SIL language. +{custom-style=CodeBlock} +:::: ``` - -```=sile-lua -SILE.call("em", {}, { 'This' }) -SILE.typesetter:typeset(" is called from Lua.") +::: +_Some content here._ +::: +^ An example of a div used as captioned figure. ``` +:::: -Likewise, on inline code elements: `\em{idem.}`{=sile} +Be aware that this behavior is currently an extension. +Other Djot converters will therefore likely skip the caption. -#### Horizontal dividers +As for implicit captioned images, this feature relies on the document class or other packages to provide a `captioned-figure` environment. See §[](#djot-captioned-images), the same remarks apply here as well. + +### Horizontal dividers Since Djot allows attaching arbitrary attributes to any element, we do not need here to rely on an _ad hoc_ workaround, as we previously did with the native Markdown package, @@ -339,22 +1159,150 @@ pseudo-class. [^djot-hrules]: The order in which they are listed above corresponds to their priority. -#### Smarter typography -On inline content, the `.decimal` pseudo-class attribute instructs the converter to consider numbers -in the content as decimal numbers, formatted with suitable decimal mark and digit grouping according -to the usage in the current language. -This allows, say, 1984 to be rendered as "[1984]{ .decimal } years ago" in English, -or "[1984 années]{ .decimal lang=fr }" in French, with appropriate separators. +{#djot-raw-blocks} +### Raw blocks + +A code block marked as `=format` is interpreted as raw content and passed through verbatim to output in that format. +As for raw inlines (§[](#djot-raw-inlines)), this converter supports the `=sile` and `=sile-lua` annotations. +Other annotations are ignored, and the block content is skipped. + +Let's combine several of the extensibility techniques we have seen so far, and do something cool. + +```=sile +\use[module=packages.barcodes.ean13] +``` + +```=sile-lua +local class = SILE.documentState.documentClass +class:registerCommand("MyEan13", function (_, content) + local code = SU.ast.contentToString(content) + code = code:gsub("[-–]","") + SILE.call("ean13", { scale="SC0", code = code }) +end) +``` + +Why not "style" an EAN-13 ISBN as a nice barcode? + +[[978-2-9539896-6-3]{custom-style=MyEan13}]{custom-style=center} + +First, we ensure the *barcodes.ean13* packages is loaded.[^djot-ean13-assume] +Let's use some SIL language for that. + +[^djot-ean13-assume]: The package is part of the *barcodes.sile* collection, and is not installed by default --- But the *resilient.sile* collection does include it, and this is what we are using here. + +{custom-style=CodeBlock} +::: +```` +```=sile +\use[module=packages.barcodes.ean13] +``` +```` +::: + +Then, we define a new SILE command to render the EAN-13 barcode. +Let's do it in Lua, as we need some string manipulation. + +{custom-style=CodeBlock} +::: +```` +```=sile-lua +local class = SILE.documentState.documentClass +class:registerCommand("MyEan13", function (_, content) + local code = SU.ast.contentToString(content) + -- Remove dashes and (smart) en-dashes + code = code:gsub("[-–]","") + SILE.call("ean13", { scale="SC0", code = code }) +end) +``` +```` +::: + +We can now use our new command as a custom style (see §[](#djot-custom-styles)). + +{custom-style=CodeBlock} +::: +``` +... [978-2-9539896-6-3]{custom-style=MyEan13} +``` +::: + +Other converters will likely ignore the style, and the ISBN will be displayed as-is, so this technique degrades gracefully. + + +{#djot-advanced-topics} +## Advanced topics + +{#djot-language-changes} +### Languages + +Language changes within the text are supported, on either blocks or inline +elements. +It relies on the `lang` key-value attribute, where the value is a BCP47 language code. +It is not much visible below, obviously, but the language setting affects the hyphenation and other properties. +In the case of French, for instance, you can see the special thin space before the exclamation point, the use of appropriate quotation marks, and the internal spacing around quoted text: + +{lang=fr} +> Cette citation est en français! + +Or inline in text: ["Encore du français!"]{lang=fr} + +This was obtained with: + +{custom-style=CodeBlock} +::: +``` +::: {lang=fr} +> Cette citation est en français! +::: + +Or inline in text: ["Encore du français!"]{lang=fr} +``` +::: + +As can be seen, the current language is taken into account when converting +paired straight double and single quotation marks to the appropriate typographic variant: +["English"]{lang=en} / ["Deutsch"]{lang=de} / ["français"]{lang=fr} / ["dansk"]{lang=da} / ["русский"]{lang=ru}; +['English']{lang=en} / ['Deutsch']{lang=de} / ['français']{lang=fr} / ['dansk']{lang=da} / ['русский']{lang=ru}.[^djot-smart-quotes] + +[^djot-smart-quotes]: For most languages, `"` and `'` correspond to the primary and secondary quotations marks, respectively. +In some languages, they are used the other way round, but obviously the user's input is respected in those cases (e.g. respectively ["Ghàidhlig"]{lang=cy} and ['Ghàidhlig']{lang=cy}). + +{#djot-custom-styles} +### Custom styles + +The converter also supports the `{custom-style="..."}` attribute. +It is inspired by the syntax proposed by Pandoc for Markdown, in its *docx* writer, to specify a specific, possibly user-defined, custom style name (in that case, a Word style, obviously). + +Here, if such a named style exists, it is applied. Erm. What does it mean? +Well, in the default implementation, if used within in a *resilient* class and there is a corresponding style, the converter uses it. +Otherwise, if there is a corresponding SILE command by that name, the converter invokes it. +Otherwise, it just ignores the style and processes the content as-is. +Even if you do not use a resilient-compatible class, it thus allows you to use some interesting SILE features. +For instance, here is some block of text marked as "center", with some inline text marked as "strong". + +{#djot-centered custom-style="center"} +This is SILE and Djot at their best{custom-style="strong"}! + +This was obtained with: + +{custom-style="CodeBlock"} +::: +``` +{#djot-centered custom-style="center"} +This is SILE and Djot at their best{custom-style="strong"}! +``` +::: + +That's a fairly contrived way to obtain a bold text, but you get the underlying general idea. +This is one of the ways to use SILE commands in Djot. +While you could invoke _any_ SILE command with this feature, we recommend, though, to restrict it to styling. +Another more powerful way to leverage Djot with SILE’s full processing capabilities, and benefit from the best of both worlds, is to use the "raw" annotations, described in §[](#djot-raw-inlines) and §[](#djot-raw-blocks). -The `.nobreak` pseudo-class attribute on inline content ensures that line-breaking will not be applied -there. Use it wisely around small pieces of text or you might end up with more serious justification issues! Yet, it might be useful for proper names, etc. ### Templates and variable substitution [^:pumpernickel:]: Herbert _"Froggie"_ Pumpernickel{.smallcaps .nobreak} -[^:disclaimer:]: _Big disclaimer:_ This interpretation of symbols is not standard. - We might have to reconsider it as Djot evolves. Let's now pretend that you are writing an essay about some fictitious :pumpernickel:. @@ -366,7 +1314,7 @@ But your problem is that this long name will appear a lot of times. Repeating it is tedious and error-prone. Would you appreciate defining it as a replaceable variable going by a much simpler name? -Well, Djot has the notion of a "symbol", a keyword surrounded by colon signs. +Well, Djot has the notion of a "symbol", a keyword surrounded by colon signs (§[](#djot-symbols)). Although it was initially provisioned for "emojis", the default interpretation is now left to the rendering engine. This converter for SILE is focussed on producing print-quality books, where so-called emojis play little part. @@ -375,17 +1323,18 @@ Back to our initial question, what if you could just type `:pumpernickel:` and h Since it's possible to have unused footnote definitions, let's craft one as shown hereafter, with its reference identifier being the same as our targeted variable. +{custom-style=CodeBlock} +::: ``` [^:pumpernickel:]: Herbert _"Froggie"_ Pumpernickel{.smallcaps .nobreak} ``` +::: When encountering a symbol, this converter looks for such a footnote and expands its content. It works with inline elements as shown above, but also with full blocks, provided the symbol is the only element in a paragraph of its own. Of course, these pseudo-footnotes[^djot-pseudo-footnotes] can in turn contain symbols, which will get replaced too. -{custom-style=FramedPara} -:disclaimer: [^djot-pseudo-footnotes]: You may still use them as regular footnotes. Whether this is a good idea is another question... @@ -401,7 +1350,7 @@ This converter also comes with a few symbols predefined. The above variable substitution mechanism has precedence over these symbols, allowing you to possibly override them. {#djot-metadata-symbols} -### Templates and contextual metadata +### Contextual metadata Some options passed to the converter (see "[](#djot-configuration){.title}") are also available as symbols. @@ -411,32 +1360,8 @@ Let's check how it expands: _:title:._ Again, the variable substitution mechanism also has precedence over these symbols. -## Syntax extensions - -### Attributed quotes (epigraphs) - -{ rule="0.4pt" } -> The Library is a sphere whose exact centre is any one of its -> hexagons and whose circumference is inaccessible. -^ Jorge Luis [Borges]{.smallcaps}, "The Library of Babel" - -Standard Djot only honors captions on tables, and ignores them on other block elements. -When using a **resilient** document class, however, a captioned block quote is rendered as an "epigraph", with the caption content used as its "source" (in a broad sense). -For instance, the above quote was obtained with: - -``` -{ rule="0.4pt" } -> The Library is a sphere whose exact centre is any one of its -> hexagons and whose circumference is inaccessible. -^ Jorge Luis [Borges]{.smallcaps}, "The Library of Babel" -``` - -Attributes are passed through to an implicit "div" (so as to honor the language, a link target identifier, etc.) and eventually to the underlying epigraph environment. -Any option supported by the `\autodoc:package{resilient.epigraph}`{=sile} package may thus be used. - -Be aware that this behavior is currently an extension. -Other Djot converters will therefore likely skip the caption. +{#djot-conditional-attributes} ### Conditionals While the interpretation of symbols presented above is not a standard, the Djot specification leaves it to the rendering engine. @@ -469,21 +1394,27 @@ Therefore, we recommend that you call template files containing conditional cont {#djot-configuration} ## Configuration -You can pass additional options to the `\autodoc:command{\include}`{=sile} command or the `\autodoc:environment[check=false]{raw}`{=sile} environment to tune the behavior of the converter. +The calling context (a wrapper document in SIL syntax, a resilient "master document", etc.) can pass additional options to tune the behavior of the converter. -The `shift_headings` option can take an integer value and causes headers in included or embedded raw content to be offset (that is, shifted by the given amount). -For instance: +As state above (§[](#djot-metadata-symbols) "[](#djot-metadata-symbols){.title}"), any option starting with `meta:` is passed to the converter (with this prefix removed), and is then available as a symbol. +In addition, the `shift_headings` option can take an integer value and causes headers in included or embedded raw content to be offset (that is, shifted by the given amount). + +In SIL documents, you can pass such options to the `\include` command or the `raw` environment. +For instance, you can include a Djot file with shifted headings like this: + +{custom-style=CodeBlock} +::: ``` \include[src=somefile.dj, shift_headings=1] ``` +::: -For document classes supporting it, this feature also allows you to access levels above the default scheme, such as "parts". +For document classes supporting it (in particular, the *resilient* book class), this feature also allows you to access levels above the default scheme, such as "parts". +{custom-style=CodeBlock} +::: ``` \include[src=somefile.dj, shift_headings=-1] ``` - -Any other option starting with `meta:` is passed to the converter (with this prefix removed). -See "[](#djot-metadata-symbols){.title}" above. - +::: diff --git a/examples/sile-and-markdown-bonus.sil b/examples/sile-and-markdown-bonus.sil deleted file mode 100644 index 3c95f05..0000000 --- a/examples/sile-and-markdown-bonus.sil +++ /dev/null @@ -1,75 +0,0 @@ -\begin{document} -% Extra bonus -% Now let's try something very cool... -% - Quick and dirty definition of a new custom style... -% (using some experimental "resilient" styling feature) -\define[command=customframe]{\center{\roughbox[bordercolor=#59b24c, - fillcolor=220,padding=15pt, enlarge=true]{\parbox[width=90%lw, minimize=true]{\process}}}} -\lua{ -SILE.scratch.styles.alignments["framed"] = "customframe" -local class = SILE.documentState.documentClass -class:registerStyle("FramedPara", {}, { - paragraph = { - before = { skip = "medskip" }, - after = { skip = "medskip" }, - align = "framed" - } -}) -} -% - Some other packages -% (Here we could use what resilient book matters offer, but let's pretend we don't know about it) -\lua{ -local class = SILE.documentState.documentClass -class:registerCommand("Ean13", function (_, content) - local code = content[1] - -- Markdown parser may interpret a dash between digits as smart typography for en-dash. - -- Let's remove those. - code = code:gsub("–","") - SILE.call("ean13", { code = code }) -end) -class:registerCommand("Initial", function (_, content) - local letter = content[1] - SILE.call("dropcap", { lines = 3, family = "Zallman Caps", join = true }, { letter }) -end) -} -% - And let's process some inline Markdown using it... -\begin[type=markdown]{raw} -# Markdown Mark-up Marvels - -[T]{ custom-style=Initial }he previous chapter, which stands in its own standalone Markdown file, serves -both as a documentation and a reference guide... Since it is intended to be -either processed from command-line or included in a very generic SILE document, -with the conversion based on the native `\autodoc:package{markdown}`{=sile} package, -it sticks to the bare minimum, by necessity and for the sake of simplicity. - -Here in this bonus chapter, under this sensationalist headline, we'll go at bit further and showcase a few -more things, assuming additional class and package dependencies are available to us. - -## Custom-style bragging - -Custom styles are pretty neat, aren't they? Have a look at the nice capital above, -or to the fancy quote framing below... - -::: {custom-style=FramedPara} -Thanks, gentle reader, for having read this document so far. -So, what _should_ we do now? It's maybe up to **you** now, to provide feedback -and help pushing all these components farther! -::: - -And why not "style" an EAN-13 ISBN, so that it is rendered as a barcode... [978-2-9539896-6-3]{ custom-style=Ean13 } - -## Advanced tables with pandocast - -While the native package accepts supports "pipe tables" only, Pandoc supports the -standard "simple tables" and also extends Markdown with several other methods for declaring tables. -Would you have a document containing such tables, then `\autodoc:package{pandocast}`{=sile} should -be able to render them. Above, we just described that package briefly. It's now showcase time! - -Obviously, it is mostly a question of input syntax, and the converter shouldn't actually have to bother, -since the hard work parsing these tables is done by Pandoc itself, generating a generic JSON AST -representation^[With a word of caution: Some Pandoc AST structures address advanced table features from -other document formats than Markdown. While such documents can be converted to a Pandoc JSON AST, -our package might not support them well. Its main focus is on Markdown parity with Pandoc.]. - -\end{raw} -\end{document} diff --git a/examples/sile-and-markdown-manual-styles.yml b/examples/sile-and-markdown-manual-styles.yml index 8344e0d..c0e0734 100644 --- a/examples/sile-and-markdown-manual-styles.yml +++ b/examples/sile-and-markdown-manual-styles.yml @@ -1,4 +1,35 @@ +defn-term-Custom: + inherit: "defn-base" + origin: "resilient.defn" + style: + font: + features: "+smcp" + paragraph: + after: + vbreak: false + before: + skip: "smallskip" + +defn-desc-Custom: + inherit: "defn-desc" + style: + paragraph: + after: + skip: "smallskip" + before: + vbreak: false + +CodeBlock: + origin: "resilient.book" + style: + paragraph: + after: + skip: "smallskip" + align: "shadow-framed" + before: + skip: "smallskip" + CoverCredit: style: font: @@ -10,16 +41,36 @@ CoverCredit: before: skip: "bigskip" +CustomDroppedInitial: + origin: "resilient.book" + style: + font: + family: "Zallman Caps" + color: "#66a0b3" + special: + lines: 3 + FramedPara: origin: "resilient.book" style: paragraph: after: skip: "medskip" - align: "framed" + align: "fancy-framed" before: skip: "medskip" +Difference: + style: + font: + family: "Libertinus Sans" + size: "0.95em" + paragraph: + after: + skip: "smallskip" + before: + skip: "smallskip" + blockquote: origin: "resilient.book" style: diff --git a/examples/sile-and-markdown-manual.silm b/examples/sile-and-markdown-manual.silm index 37cda20..5ee6849 100644 --- a/examples/sile-and-markdown-manual.silm +++ b/examples/sile-and-markdown-manual.silm @@ -10,9 +10,9 @@ metadata: - Pandoc authors: Didier Willis publisher: Omikhleia - pubdate: 2023-10-31 + pubdate: 2024-03-01 url: https://github.com/Omikhleia/markdown.sile - copyright: © 2021–2023, Didier Willis. + copyright: © 2022–2024, 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, @@ -48,9 +48,10 @@ sile: - resilient.poetry chapters: - toc.dj - - introduction.md - - sile-and-markdown.md - - sile-and-markdown-bonus.sil - - pandoc-tables.pandoc + - extra-styles.dj + - introduction.dj + - lightweight-markup.dj - sile-and-djot.dj + - sile-and-markdown.md - sile-and-pandoc.dj + - pandoc-tables.pandoc diff --git a/examples/sile-and-markdown.md b/examples/sile-and-markdown.md index 261a0d8..e3933e2 100644 --- a/examples/sile-and-markdown.md +++ b/examples/sile-and-markdown.md @@ -1,323 +1,1181 @@ # SILE and Markdown -::: {custom-style=raggedleft} -"Markdown is intended to be as easy-to-read and easy-to-write as is feasible."^[From the -original [Markdown syntax specification](https://daringfireball.net/projects/markdown/syntax).] +```djot +{% We switch to Djot because we have cool epigraphs there %} + +> "Markdown is intended to be as easy-to-read and easy-to-write as is feasible." +^ From the original [Markdown specification](https://daringfireball.net/projects/markdown/syntax). +``` + +[M]{custom-style=Initial}[arkdown]{.smallcaps}, as initially designed, was quite simple, and it quickly became a landmark for documentation, especially technical. +Several variants emerged, extending the original set of features. +Amongst other solutions, the Pandoc converter started supporting a nice set of interesting extensions for lists, footnotes, tables, etc.^[See [IETF RFC 7764, section 3.3](https://datatracker.ietf.org/doc/html/rfc7764#section-3.3).] +--- So that nowadays, Markdown, enriched with these extensions, may be quite appealing to writers and authors alike. + +Within SILE’s aim to produce beautiful printed documents, it's a pretty reasonable assumption that such writers and authors may want to use this fine typesetter with their Markdown content, without having to learn the SIL language and its specifics (but not, either, fully excluding it for some advanced capabilities). + +Guess what, the very chapter you are currently reading now is written in Markdown. +However, even with all its extensions, Markdown is less versatile than Djot, and is sometimes ambiguous. +In this chapter, we will go through the supported syntax, repeating most of the topics covered in the previous chapter. +The idea is that it can be read independently, and yet highlight the important differences between the two languages. + +[comment]: # (THIS TEXT SHOULD NOT APPEAR IN THE OUTPUT. It is actually the most platform independent comment in Markdown.) + +## Core concepts + +The core concepts are basically the same as in Djot. +However, the original Markdown syntax was quite limited. + +This converter enables several Pandoc-like extensions by default, and the following sections assume it is the case. + +Nevertheless, note that you can disable most of the extensions if needed, see §[](#markdown-configuration). + +## Attributes + +Attributes are put inside curly braces: + + - `#foo` specifies an identifier, for referencing purposes. + + An element may have only one identifier; if multiple identifiers are given, the last one is used. + + - `.foo` specifies a class, for styling purposes. + + Multiple classes may be given in this way; they will be combined. + + - `key="value"`, `key='value'` or `key=value` specifies a key-value attribute. + + The value may be double-quoted, single-quoted, or unquoted. + Quotes are needed when the value contains spaces or quotes. + + The key must start with a letter, followed by alphanumeric characters or any of `_`, `-`, `:` and `.` characters. + +Attributes are only supported on a few inline elements. +On these inline elements, attributes are placed immediately after the element they are attached to, with no intervening whitespace. +Attributes must fit on one line, and there can be no more than one set of attributes per element: + + - Spans + - Images and links (at this point, direct syntax only) + + +There are also only supported on a few block-level elements. +The syntax and the restrictions are the same as for inline elements. +On these block elements, attributes are placed _after_ the element they are attached to, possibly with intervening spaces: + + - Headings + - Divs + - Code blocks + +::: {custom-style=Difference} +![](./examples/manicule.svg){height=1.3ex} **Main differences with Djot** + + - Slightly different syntax. + - Supported on less elements. + - No conditionals (Djot extension) + +::: + +## Inline elements + +### Spaces + +Markdown treats multiple spaces as a single space. + +::: {custom-style=Difference} +![](./examples/manicule.svg){height=1.3ex} **Main differences with Djot** + +Backslash-escaped spaces are not interpreted as non-breaking spaces. +To obtain a non-breaking space, the alternative is to use the HTML entity ` ` (see §[](#markdown-html-elements)). +This converter interprets it as a justifiable interword-space. +::: + +### Line breaks + +Line breaks in inline content are treated as “soft” breaks. +In other words, this converter semantically interprets them as mere spaces. + +To get a hard line break, use a backslash at the end of a line. Hard line breaks...\ +... are supported. + +::: {custom-style=CodeBlock} +``` +Hard line breaks...\ +... are supported. +``` +::: + + +::: {custom-style=Difference} +![](./examples/manicule.svg){height=1.3ex} **Main differences with Djot** + +Markdown also supports using two trailing spaces at the end of a line to create a hard line break. +While this syntax comes from the original Markdown specification, it is not recommended to use it, as these spaces are "invisible" and may be easily lost in the editing process. +::: + +### Smart punctuation + +The converter comes by default with smart typography enabled. + +#### Dashes and ellipsis + +Three dashes (`---`) in an inline text sequence are converted to an em-dash (---), two dashes (`--`) to an en-dash useful for ranges (ex., "it's at pages 12--14"), and three dots +(`...`) to an ellipsis (...). + +#### Smart quotes + +Smart quotes and apostrophes are also automatically handled. +Straight double quotes (`"`) and single quotes (`'`) are converted into appropriate quotes. + +This converter takes into account the current language when converting straight double and single quotation marks to the appropriate typographic variant. + +::: {custom-style=Difference} +![](./examples/manicule.svg){height=1.3ex} **Main differences with Djot** + +The smart quotes feature is less robust than in Djot, and may sometimes be defeated (producing the wrong quotes). +In some documents, it may be better to disable it (see §[](#markdown-configuration)). + +You may then need to enter the actual typographic quotes directly, using the appropriate Unicode characters. +::: + +### Emphasis + +Regular _emphasis_ is delimited by `_` or `*` characters. +Strong **emphasis** is delimited by `__` or `**` characters. + +Emphasis can be nested. +For regular emphasis, this is interpreted as per usual typography conventions, adequately switching between italics and upright fonts. +_This is _an emphasis inside_ an emphasis._ + +::: {custom-style=CodeBlock} +``` +_This is _an emphasis inside_ an emphasis._ +``` +::: + +::: {custom-style=Difference} +![](./examples/manicule.svg){height=1.3ex} **Main differences with Djot** + + - Slightly different syntax. + - Edge cases are handled differently. + +::: + +### Deletions + +To mark it as deleted, enclose your content between `~~` delimiters. + +Thus, ~~deletion~~ is `~~deletion~~` + +For this converter, this is equivalent to a span with a `.strike` pseudo-class respectively (see §[](#markdown-spans) for details and styling). + +::: {custom-style=Difference} +![](./examples/manicule.svg){height=1.3ex} **Main differences with Djot** + + - Different syntax and interpretation. + - No direct support for "insertions" + +::: + +### Highlighted content + +Inline content between `== and ==` is treated as highlighted (or "marked"). + +You thus get an ==highlight== with `==highlight==` + +For this converter, this is equivalent to a span with a `.mark` pseudo-class (see §[](#markdown-spans) for details and styling). + +::: {custom-style=Difference} +![](./examples/manicule.svg){height=1.3ex} **Main differences with Djot** + + - Different syntax. + +::: + +### Superscripts & subscripts + +Superscript is delimited by `^` characters, subscript by `~`. +Thus, H~2~O is a liquid, and 2^10^ is 1024. + +::: {custom-style=CodeBlock} +``` +Thus, H~2~O is a liquid, and 2^10^ is 1024. +``` +::: + +This converter assumes that the content of a superscript or subscript consists of text, as it tries to use font features or text scaling techniques to render it. + +::: {custom-style=Difference} +![](./examples/manicule.svg){height=1.3ex} **Main differences with Djot** + + - No attribute support on these elements. + +::: + + +### Generic inline containers (spans) {#markdown-spans} + +Text in square brackets that is not a link or image and is followed immediately by an attribute is treated as a generic span. + +This converter recognizes a few specific attributes on spans: + + - Identifiers for referencing purposes + - The `custom-style` attribute, see §[](#markdown-custom-styles) + - The `lang` attribute, see §[](#markdown-language-changes) + +#### Basic styling + +This converter also recognizes the following pseudo-classes on spans: + + - [Small Caps]{.smallcaps}, as `[Small Caps]{.smallcaps}` + - [underlines]{.underline} with `[underlines]{.underline}` + - [struck out]{.strike} with `[struck out]{.strike}` + - [insertions]{.inserted} with `[insertions]{.inserted}` + - [deletions]{.deleted} with `[deletions]{.deleted}` + - [highlighted]{.mark} with `[highlighted]{.mark}` + +The last one is equivalent to the `==...==` syntax, and the third one is equivalent to the `~~...~~` syntax. +In other term, these Markdown inline elements are mere shortcuts for the corresponding spans. + +Moreover, if used within in a **resilient** class, the converter uses the `md-underline`, `md-strikethrough`, `md-insertion`, `md-deletion` and `md-mark` styles for the last five spans, if they are defined in your style file. +Another way to put it is that the converter then does the same thing as a `{custom-style="..."}` attribute (see §[](#markdown-custom-styles)). +You can style and fine-tune these elements as you wish, and even use them for other purposes than their original intent. + +#### Smarter typography + +On spans, the `.decimal` pseudo-class attribute instructs the converter to consider numbers in the content as decimal numbers, formatted with suitable decimal mark and digit grouping according to the usage in the current language. +This allows, say, 1984 to be rendered as "[1984]{.decimal} years ago" in English, or "[1984 années]{.decimal lang=fr}" in French, with appropriate separators. + +::: {custom-style=CodeBlock} +``` +... rendered as "[1984]{.decimal} years ago" in English, +or "[1984 années]{.decimal lang=fr}" in French ... +``` +::: + +The `.nobreak` pseudo-class attribute ensures that line-breaking will not be applied there. +Use it wisely around small pieces of text, or you might end up with more serious justification issues! +Yet, it might be useful for proper names, etc. + +When smart typography is enabled, the native converter also supports automatically converting straight single and double quotes after digits to single and double primes. +It can be useful for easily typesetting units (e.g. 6") or coordinates (e.g. Oxford is located at 51° 45' 7" N, 1° 15' 27" W). + +::: {custom-style=CodeBlock} +``` +Oxford is located at 51° 45' 7" N, 1° 15' 27" W +``` +::: + + +::: {custom-style=Difference} +![](./examples/manicule.svg){height=1.3ex} **Main differences with Djot** + + - Prime conversion is specific to this Markdown converter. + +::: + +### Images {#markdown-images} + +Here is an image: ![Invisible caption as in inline](./gutenberg.jpg "this is ignored"){width=1.5cm} + +::: {custom-style=CodeBlock} +``` +Here is an image: ![](./gutenberg.jpg){width=1.5cm} +``` +::: + +Attributes are optional, and are passed through to the underlying SILE package. +You can notably specify the required image width and/or height, as done just above, by appending the `{width=... height=...}` attributes --- Note that any unit system supported by SILE is accepted. + +::: {custom-style=Difference} +![](./examples/manicule.svg){height=1.3ex} **Main differences with Djot** + + - The "indirect" syntax is supported too, but does not support attributes (yet), so it is probably not very useful at this point. + +::: + +#### Implicit captioned figures + +An image with nonempty caption, occurring alone by itself in a paragraph, will be rendered as a figure with a caption, as actually seen above. +Otherwise, the caption is ignored. + +![This man is still Gutenberg.](./gutenberg.jpg "ignored"){#markdown-gutenberg width=3cm} + +::: {custom-style=CodeBlock} +``` +![This man is still Gutenberg.](./gutenberg.jpg){width=3cm} +``` +::: + +As with Djot, it relies on the presence of a `captioned-figure` environment in your document class or previously loaded packages. + +Specifically, if the **resilient** book class is used, the caption is numbered by default, and added to the list of figures. Specify `.unnumbered`, and `.notoc` respectively, if you do not want it. + +#### Extended image types + +Besides regular image files, a few specific file extensions are also recognized and processed appropriately by this converter. +Notably ![](./examples/manicule.svg){height="1.3ex"} SVG is supported too (`.svg`), as demonstrated. +This inline "manicule" is obtained with: + +::: {custom-style=CodeBlock} +``` +... ![](./examples/manicule.svg){height="1.3ex"} is supported ... +``` +::: + +Files in Graphviz DOT graph language (`.dot`) are supported and rendered as images, when the **embedders.sile** collection is properly configured. + +![The **markdown.sile** ecosystem (simplified).](./markdown-sile-schema.dot){width="90%lw"} + +This image is obtained with the following syntax. + +::: {custom-style=CodeBlock} +``` +![The **markdown.sile** ecosystem (simplified).](./markdown-sile-schema.dot){width="90%lw"} +``` +::: + +CSV files are also supported, when the **piecharts.sile** optional collection is properly installed (see §[](#recommended-additional-packages)), and are rendered as pie charts. + +![A pie chart.](./data-pie.csv){height="8em" cutoff="0.07"} + +This chart is obtained with the following syntax. + +::: {custom-style=CodeBlock} +``` +![A pie chart.](./data-pie.csv){height="8em" cutoff="0.07"} +``` +::: + + +### Links and cross-references + +Here is a link to [the SILE website](https://sile-typesetter.org/), and here is an internal link to the "[Images](#djot-image)" section. + +::: {custom-style=CodeBlock} +``` +... a link to [the SILE website](https://sile-typesetter.org/) +... an internal link to the "[Images](#djot-image)" section. +``` +::: + +As seen in the example, links can be local (referring to an identifier) or external (for instance, referring to a URL). + +You can use attributes on links: +the [SILE website](https://sile-typesetter.org/){.underline}.[^markdown-link-attributes] + +::: {custom-style=CodeBlock} +``` +... the [SILE website](https://sile-typesetter.org/){.underline}. +``` +::: + +[^markdown-link-attributes]: Within a **resilient** class, we'd possibly rather recommend using a style to color links, etc. + +There are actually two kinds of links, inline links and reference links. +Both start with the link text (which may contain arbitrary inline formatting) inside `[`...`]` delimiters. + +Inline links then contain the link destination (URL) in parentheses, as shown above. +There should be no space between the `]` at the end of the link text and the open `(` defining the link destination. + +Reference links use a reference label in square brackets, instead of a destination in parentheses. +This is another link to [the SILE website][sile]. + +::: {custom-style=CodeBlock} +``` +Another link to [the SILE website][sile]. +... +[sile]: https://sile-typesetter.org/ +``` +::: + +[sile]: https://sile-typesetter.org/ + +The reference label should be defined somewhere in the document. +If the label is empty, then the link text will be taken to be the reference label as well as the link text. + +::: {custom-style=Difference} +![](./examples/manicule.svg){height=1.3ex} **Main differences with Djot** + + - The "indirect" syntax does not support attributes (yet). + +::: + +#### Cross-references {#markdown-cross-references} + +Markdown does not define a proper way to insert cross-references of the kind you would see in a book, as in the following example. + +> The section on "[](#markdown-tables){.title}", +> that is [](#markdown-tables){.section}, +> is on page [](#markdown-tables){.page}. + +This converter takes a bold decision, though unlikely to break anything unexpected. +Empty local links (that is, without inline display content) are interpreted as cross-references. +By default, they are resolved to the closest numbering item, whatever that might be in the hierarchical structure of your document. +A pseudo-class attribute may be used to override the default behavior and specify which type +of reference is expected (page number, section number or title text). +Thus, the above example was obtained from the following input: + +::: {custom-style=CodeBlock} +``` +The section on "[](#markdown-tables){.title}", +that is [](#markdown-tables){.section}, +is on page [](#markdown-tables){.page}. +``` +::: + +Besides heading levels, it also works for various elements where you can define an identifier. +For instance we had some centered text in section [](#markdown-centered). +With appropriate class and package support,[^markdown-sec-label-support] you may even refer to Gutenberg as "figure [](#markdown-gutenberg)", or to some poetry verse mentioning the Sun ("Soleil"), in [](#sun){.section}, as "verse [](#sun)". + +[^markdown-sec-label-support]: Typically, it works with the **resilient** collection of classes and packages. It won't work with non-supporting class and packages, using the fallback implementation for captioned elements, poetry, etc. + +#### Automatic links + +A URL or email address that is enclosed in angle brackets (`<`...`>`) is rendered as a link. +The content between pointy braces is treated literally (backslash-escapes may not be used). + +Here is a link to the SILE website, . + +::: {custom-style=CodeBlock} +``` +... to the SILE website, . +``` +::: + + +Verbatim content begins with a string of consecutive backtick characters (`` ` ``) and ends with an equal-lengthed string of consecutive backtick characters. +This is mostly used for `code` --- obtained with `` `code` `` (enjoy the inception). + +Material between the backticks is treated as verbatim text (backslash escapes don't work there). + +### Maths + +To include TeX-like math, put the math between `$` (for inline math) or `$$` (for display math). +For instance, the following code produces $e^{i\pi}=-1$. + +::: {custom-style=CodeBlock} +``` +... the following code produces $e^{i\pi}=-1$. +``` +::: + +There is an important constraint, though: you have to restrict yourself to the syntax subset supported by SILE. This being said, some nice fomulas may be achieved. +$$\pi=\sum_{k=0}^\infty\frac{1}{16^k}(\frac{4}{8k+1} − \frac{2}{8k+4} − \frac{1}{8k+5} − \frac{1}{8k+6})$$ + + +::: {custom-style=CodeBlock} +``` +... may be achieved. +$$\pi=\sum_{k=0}^\infty\frac{1}{16^k}(\frac{4}{8k+1} − \frac{2}{8k+4} − \frac{1}{8k+5} − \frac{1}{8k+6})$$ +``` +::: + +::: {custom-style=Difference} +![](./examples/manicule.svg){height=1.3ex} **Main differences with Djot** + + - Different syntax (delimiters). + - Sometimes ambiguous, although the converter tries to be smart about it. + (Note that $20,000 and $30,000 don't parse as math, while $e^{i\pi}=-1$ does.) + +::: + +### Footnote calls + +A footnote call[^markdown-some-fn] is marked with `^` and the reference label in square brackets. +See §[](#markdown-footnotes) for the syntax of the footnote itself. + +::: {custom-style=CodeBlock} +``` +A footnote call[^markdown-some-fn] is marked... +``` +::: + +[^markdown-some-fn]: An example footnote. + +Another Pandoc-inspired extension is the possibility to direct inline footnotes. +They are introduced with directly with the caret `^`, immediatelly followed by the footnote content in square brackets, as in the following example.^[Direct footnote.] + +::: {custom-style=CodeBlock} +``` +... the following example.^[Direct footnote.] +``` +::: + +::: {custom-style=Difference} +![](./examples/manicule.svg){height=1.3ex} **Main differences with Djot** + + - Direct inline footnotes are not supported in Djot. + +::: + +### Symbols + +::: {custom-style=Difference} +![](./examples/manicule.svg){height=1.3ex} **Main differences with Djot** + + - No support for symbols in Markdown. + +::: + +### Raw inlines {#markdown-raw-inlines} + +Raw inline content in any format may be included using a verbatim span followed by `{=format}`. +This content is intended to be passed through verbatim when rendering the designated format, but ignored otherwise. + +This converter supports a `{=sile}` annotation, to pass through its content in SIL language. Let's do something fun`\dotfill`{=sile}! + +::: {custom-style=CodeBlock} +``` +Let's do something fun`\dotfill`{=sile}! +``` +::: + +It also supports `{=sile-lua}` to pass Lua code. Let's try it here +`SILE.call("hrule", {height="1ex", width="1ex"})`{=sile-lua} + +::: {custom-style=CodeBlock} +``` +Let's try it here +`SILE.call("hrule", {height="1ex", width="1ex"})`{=sile-lua} +``` +::: + +### HTML elements {#markdown-html-elements} + +For mere convenience, the `
` element is supported in addition to the standard ways to indicate a hard line break. +Therefore...
... it is honored. + +Would you have a long word, such as AAAAABBBBBCCCCCDDDDDEEEEEFFFFFGGGGGHHHHHIIIIIJJJJJKKKKKLLLLLMMMM, the `` element (introduced in HTML5) represents a word break opportunity. +It may help when the line-breaking rules would not otherwise create a break at acceptable locations. +In this admittedly lame example, we used it between groups of a same letter. + +HTML entities are also processed, e.g. `‰` renders as ‰. + +::: {custom-style=Difference} +![](./examples/manicule.svg){height=1.3ex} **Main differences with Djot** + + - HTML elements and entities are not supported in Djot. + +::: + +## Block elements + +### Paragraphs + +A paragraph is a sequence of non-blank lines that does not meet the condition for being one of the other block-level elements. + +A paragraph ends with a blank line or the end of the document. +The textual content is parsed as a sequence of inline elements. +A single newline is treated as a space. + +### Headings + +A heading starts with a sequence of one or more `#` characters, followed by whitespace. +The number of `#` characters defines the heading level. +The heading text following that sequence is parsed as inline content. + +For example, this very section is a level three heading. +It was therefore obtained with: + +::: {custom-style=CodeBlock} +``` +### Headings +``` +::: + +This converter accepts the following pseudo-classes on heading attributes: + + - `.unnumbered` suppresses numbering of the heading. + - `.notoc` suppresses the heading from appearing in the table of contents. + +The heading attributes, obviously, also accept an identifier, for referencing purposes. +Here is an example illustrating the use of these attributes: + +::: {custom-style=CodeBlock} +``` +#### Other attributes {#markdown-unnumbered-heading .unnumbered .notoc} + +``` +::: + +::: {custom-style=Difference} +![](./examples/manicule.svg){height=1.3ex} **Main differences with Djot** + + - Attributes come after the heading text, not on a separate line before it. + - The heading text must be on the same line as the `#` characters. + +::: + +#### Other attributes {#markdown-unnumbered-heading .unnumbered .notoc} + +Other attributes are passed through to the underlying heading implementation, which might do something with them.[^markdown-heading-level-mapping] + +[^markdown-heading-level-mapping]: The converter assumes it can map heading levels to SILE commands `part`, `chapter`, `section`, `subsection`, `subsubsection`. +It uses a very basic fallback if these are not found (or if the sectioning level gets past that point). +The implication, therefore, is that the document class or other packages have to provide the relevant implementations. +Also note that parts are supported by the **resilient** book class as a level 0 heading, and are therefore only available when header shifting is applied (see §[](#markdown-configuration)). + + +#### Alternative syntax + +The above syntax (known as "ATX headings") is generally preferred, but the "Setext" syntax is also supported, for compatibility with other Markdown implementations. +It does not support more than two levels of headings. +It consists of a line of `=` or `-` characters under the heading text, for level 1 and 2 headings, respectively. + +::: {custom-style=Difference} +![](./examples/manicule.svg){height=1.3ex} **Main differences with Djot** + + - The "Setext" syntax is not supported in Djot. + +::: + +### Block quotes + +A block quote is a sequence of lines, each of which begins with `>`, followed either by a space or by the end of the line. +The contents of the block quote are parsed as block-level content. + +::: {custom-style=CodeBlock} +``` +> This is block quote. +> +> > Such quotes can be nested. +``` +::: + +> This is block quote. +> +> > Such quotes can be nested. + +If your document class or previously loaded packages provide a `blockquote` environment, it will be used. +Otherwise, the converter uses its own fallback method, with hard-coded styling. + +::: {custom-style=Difference} +![](./examples/manicule.svg){height=1.3ex} **Main differences with Djot** + + - Attributed quotes (epigraphs) are not supported. + +::: + +### Lists + +A list item consists of a list marker followed by a space (or a newline) followed by one or more lines, indented relative to the list marker. + +::: {custom-style=CodeBlock} +``` +1. Nesting... + + ... works as intended. + + - Fruits + - Apple +``` +::: + + + 1. Nesting... + + ... works as intended. + + - Fruits + - Apple + + +#### Unordered lists + +Unordered lists (a.k.a. itemized or bulleted lists) are introduced by a bullet list marker, either `-`, `+`, or `*`. +With this converter, the list marker is not significant, and the supporting package is responsible for rendering nested lists with the appropriate symbol. + +::: {custom-style=CodeBlock} +``` + - This is a bullet list. + - It can be nested. +``` +::: + + - This is a bullet list. + - It can be nested. + +::: {custom-style=Difference} +![](./examples/manicule.svg){height=1.3ex} **Main differences with Djot** + + - Subtle differences regarding how spaces are handled. + - Djot is stricter on where it expects blank lines. + +::: + +#### Ordered lists + +Markdown supports several types of ordered lists. + +Supported types of ordered lists are: + + - Decimal-enumerated, followed by period. + - Decimal-enumerated, followed by parenthesis. + - Lower-alpha-enumerated, followed by period. + - Lower-alpha-enumerated, followed by parenthesis. + - Upper-alpha-enumerated, followed by period. + - Upper-alpha-enumerated, followed by parenthesis. + - Lower-roman-enumerated, followed by period. + - Lower-roman-enumerated, followed by parenthesis. + - Upper-roman-enumerated, followed by period. + - Upper-roman-enumerated, followed by parenthesis. + +Using a hash (`#`) instead of a digit for an ordered list enumerator is also supported (equivalent to `1`). + +In a given type of list, you do not have to number the items in order by yourself, as Markdown automatically increments the number for you. +Your starting number, however, is honored, would you want to start at a different number than 1 (in any numbering scheme). + +::: {custom-style=CodeBlock} +``` +1. Item +1. Item +1. Item +``` +::: + +1. Item +1. Item +1. Item + +::: {custom-style=CodeBlock} +``` + b. This list starts at "b" + + iii) This list starts a "iii" in roman numerals. + i) Next item is thus "iv". + + a. The next item here is "c". +``` +::: + + b. This list starts at "b" + + iii) This list starts a "iii" in roman numerals. + i) Next item is thus "iv". + + a. The next item here is "c". + + +::: {custom-style=Difference} +![](./examples/manicule.svg){height=1.3ex} **Main differences with Djot** + + - The hash enumerator is specific to Markdown, with no equivalent in Djot. + - Numbering schemes enclosed in parentheses are not (yet) supported + +::: + +#### Definition lists + +To create a definition list, type the term on the first line. +On the next line, after optional spaces, type a colon `:` followed by a space and the definition. + +::: {custom-style=CodeBlock} +``` +apples + : Good for making applesauce. + +citrus + : Like oranges but yellow. +``` +::: + + +apples + : Good for making applesauce. + +citrus + : Like oranges but yellow. + + +::: {custom-style=Difference} +![](./examples/manicule.svg){height=1.3ex} **Main differences with Djot** + + - Different syntax. + - No support for attributes on definition lists. + - Ambiguous and messy syntax, _really._ And don't get this author started on how to have multiple paragraphs in a definition in a robust way... + +::: + +#### Task lists + +A bullet list item that begins with `[ ]`, `[X]`, or `[x]` followed by a space is a task list item, either unchecked (`[ ]`) or checked (`[X]` or `[x]`). + +::: {custom-style=CodeBlock} +``` + - [ ] Unchecked item + - [x] Checked item +``` ::: -While the original Markdown format was indeed quite simple, it quickly became a landmark for -documentation, especially technical. Several variants then emerged. Amongst other solutions, the Pandoc -converter started supporting a nice set of interesting extensions for lists, footnotes, tables, -etc.^[See [IETF RFC 7764, section 3.3](https://datatracker.ietf.org/doc/html/rfc7764#section-3.3).] ----So that nowadays, Markdown, enriched with these extensions, may be quite appealing to writers and -authors alike. + - [ ] Unchecked item + - [x] Checked item + -Within SILE’s aim to produce beautiful printed documents, it's a pretty reasonable assumption that -such writers and authors may want to use this fine typesetter with their Markdown content, without -having to learn the SIL language and its specifics (but not, either, fully excluding it for some -advanced capabilities). Guess what, the very chapter you are currently reading is written in Markdown! +### Footnote references {#markdown-footnotes} -[comment]: # (THIS TEXT SHOULD NOT APPEAR IN THE OUTPUT. It is actually the most platform independent comment in Markdown.) +This is a regular footnote.[^markdown-fnt] + +[^markdown-fnt]: This is a regular footnote. + +::: {custom-style=CodeBlock} +``` +... a regular footnote.[^markdown-fnt] + +[^markdown-fnt]: This is a regular footnote. +``` +::: + +::: {custom-style=Difference} +![](./examples/manicule.svg){height=1.3ex} **Main differences with Djot** -## The native markdown package + - No support for attributes on footnotes references. + - Hence, + - No custom footnote markers, + - No linkable footnotes from the flow of the text. -Once you have loaded the `\autodoc:package{markdown}`{=sile} package, -the `\autodoc:command{\include[src=]}`{=sile} command supports reading a Markdown file[^other-ways]. -The speedy Markdown parsing relies on John MacFarlane's excellent **lunamark** Lua library. +::: + +### Tables {#markdown-tables} + +Markdown supports the "pipe table" syntax, with its own way for marking the optional caption. + +| Right | Left | Default | Center | +|------:|:-----|---------|:------:| +| 12 | 12 | 12 | 12 | +| 123 | 123 | 123 | 123 | -[^other-ways]: The astute reader already knows, from reading the SILE manual, that there are other ways -(e.g. with command-line options) to tell SILE how to process a file in some format --- so we just stick -to the basics here. + : Demonstration of a pipe table. +::: {custom-style=CodeBlock} ``` -\use[module=packages.markdown] -\include[src=somefile.md] +| Right | Left | Default | Center | +|------:|:-----|---------|:------:| +| 12 | 12 | 12 | 12 | +| 123 | 123 | 123 | 123 | + + : Demonstration of a pipe table. ``` +::: + +When using the **resilient** classes, the caption will be numbered by default, and added to the list of tables. -Embedding raw Markdown content from within a SIL document is also possible: +::: {custom-style=Difference} +![](./examples/manicule.svg){height=1.3ex} **Main differences with Djot** + - Different syntax for introducing the caption. + - No support for table attributes. + +::: + +### Code blocks + +A code block is enclosed in a "fence," starting with a line of three or more consecutive backticks and ending with a line of backticks equal in length to the opening line. +The opening line may contain a format string, which is passed through to the converter. +In this version, this is equivalent to specifying a pseudo-class attribute in a block attribute. +(So a `xxx` format string after the opening fence and a `.xxx` class specifier in a block attribute are currently equivalent.) + +The content of the code block is passed through to the converter, which is responsible for interpreting it. +In most cases, the content is rendered as verbatim text, with some exceptions detailed below, for "recognized" formats. + +Erm... Markdown supports yet another syntax for loosely defined verbatim code blocks, which is the "indented code block" syntax. +This author would rather not recommend using it, and frow upon the indentation ambiguities it introduces in some cases. + +::: {custom-style=CodeBlock} + This is an indented code block. +::: + +This is obtained by indenting the content by at least four spaces... + +::: {custom-style=CodeBlock} ``` -\begin[type=markdown]{raw} -Some **Markdown** content -\end{raw} + This is an indented code block. ``` +::: -See also "[Configuration](#configuration)" further below. +::: {custom-style=Difference} +![](./examples/manicule.svg){height=1.3ex} **Main differences with Djot** -### Basic typesetting {#basic-typesetting} + - Different syntax for introducing the format string. + - Block attributes go after the opening fence (instead of the format string), not before it. + - No support for attributes on code blocks. + - Indented code blocks are Markdown-specific, ugly and ambiguous... -As it can be seen here, paragraphs and sectioning obviously works^[With a small caveat. The package maps heading -levels to `\chapter`, `\section`, `\subsection`, `\subsubsection` and uses a very basic fallback -if these are not found (or if the sectioning level gets past that point). The implication, therefore, -is that the class or other packages have to provide the relevant implementations.] are of course -supported. Pseudo-classes `.unnumbered` and `.notoc` are also supported. -As of formatting, *italic*, **bold**, and `code` all work as expected. +::: -Hard line breaks...\ -... are supported too, either using the standard "invisible" method from Markdown (i.e. two trailing -spaces at the end of a line) or a backslash-escaped newline (i.e. a backslash occurring at the -end of a line). +#### Lua code blocks -Several Pandoc-like extensions to Markdown are also supported. -Notably, the converter comes by default with smart typography enabled: three dashes (`---`) in an -inline text sequence are converted to an em-dash (---), two dashes (`--`) -to an en-dash useful for ranges (ex., "it's at pages 12--14"), and three dots -(`...`) to an ellipsis (...) +Code blocks marked as being in the Lua language are rendered as verbatim text, with syntax highlighting. +For instance, the following code block... -By the way, note, from the above example, that smart quotes and apostrophes are also automatically handled. +::: {custom-style=CodeBlock} +```` +```lua +function fib (n) + -- Fibonacci numbers + if n < 2 then return 1 end + return fib(n - 2) + fib(n - 1) +end +``` +```` +::: -Likewise, superscripts and subscripts are available : H~2~O is a liquid, 2^10^ is 1024. This was -obtained with `H~2~O` and `2^10^` respectively. +... is rendered as: -Other nice features include: +::: {custom-style=CodeBlock} +```lua +function fib (n) + -- Fibonacci numbers + if n < 2 then return 1 end + return fib(n - 2) + fib(n - 1) +end +``` +::: - - ~~deletions~~ with `~~deletions~~` - - ==highlight== with `==highlight==` - - [underlines]{.underline} with `[underlines]{.underline}` - - and even [Small Caps]{.smallcaps}, as `[Small Caps]{.smallcaps}` +This is a very naive approach to syntax-highlighting, until the converter possibly supports a more general solution. -The two latter cases use the extended Pandoc-inspired "span" syntax, which is also useful for languages -and custom styles (see further below). They also use the CSS-like class notation that several -Pandoc writers recognize. +#### Rendered code blocks -### Horizontal dividers +If the converter knows how to render the content of a code block, it will do so by default. +The `render` attribute can be set to `false` to prevent this behavior, and enforce the content to be rendered as raw verbatim text. -In standard Markdown, a line containing a row of three or more asterisks, dashes, or underscores -(optionally separated by spaces) are supposed to produce a horizontal rule. This converter -however slightly deviates from that simple specification^[And also from Pandoc, therefore. -Quite obviously, the `\autodoc:package{pandocast}`{=sile} package will also only show -horizontal rules.], -for the mere reason that such a horizontal rule is seldom typographically sound -in many contexts. +**Mardown and Djot code blocks** -Three asterisks produce a centered asterism. +Code blocks marked as being in Markdown or Djot are interpreted. -*** +For Markdown, attributes are passed to the converter, allowing to possibly use different compatibility options (for Markdown especially, see §[](#markdown-configuration)). +This feature allows switching between those languages, would there be something one does not support yet. -Three space-separated asterisks produce a "dinkus". +Our Djot extended implementation, for instance, supports attributed quotes (or epigraphs). +This chapter being written in Markdown, let's switch to Djot and type such a quote... -* * * +```djot +{rule="0.4pt"} +> It is a good thing for an uneducated man to read books +> of quotations. +^ Winston Churchill. +``` -Three dashes produce a centered horizontal rule, taking 20% of the line. +This was obtained with: ---- +:::: {custom-style=CodeBlock} +```` +```djot +{rule="0.4pt"} +> It is a good thing for an uneducated man to read books +> of quotations. +^ Winston Churchill. +``` +```` +:::: -Four dashes produce a centered horizontal rule, taking 33% of the line. +**Other rendered code blocks** ----- +Other formats may be recognized, provided the converter knows how to handle them. +Internally, this relies on two extensible mechanisms: -Four space-separated dashes produce, provided appropriate package support is -available^[I.e. the **couyards.sile** package module is installed.], a nice curvy pendant. -What you see just below therefore depends on that support being present or not. + - SILE "raw" handlers, which may be declared by other packages.* + - Renderers for text formats that can be converted to an image, as supported by the **embedders.sile** module, provided the necessary software is installed on your host system. (See §[](#recommended-additional-software) for more details.) -- - - - +**Graphviz DOT graphs** -Finally, without demonstrating it here, fourteen consecutive dashes enforce a page break.^[Why exactly -fourteen? -"The original says _fourteen_, but there is ample reason to infer that, as used by Asterion, -this numeral stands for _infinite_." (Jorge Luis [Borges]{.smallcaps}, "The House of Asterion". -In _Labyrinths: Selected Stories and Other Writings_, 1964.)] -It gives some flexibility to authors for marking a page break, and still get something visible -in its place with other converters. +Thus, if you properly installed the **embedders.sile** collection and its software dependencies, then code blocks marked as being in the Graphviz DOT language are rendered as images. +Attributes are passed to the underlying processor. +For instance, the image below is produced with `{.dot width="5cm" layout=neato}`. -Otherwise, everything else produces a full rule.^[Since this feature may elvove and support -more patterns, let's guarantee that underscores are reserved, and will always produce a full -horizontal rule. This author finds three or more underscores ugly and never used them in -Markdown; as of bad typography, it renders justice to the full rule.] +``` {.dot width="5cm" layout=neato} +graph { + node [fillcolor="lightskyblue:darkcyan" style=filled gradientangle=270] + a -- { b d }; + b -- { c e }; + c -- { f g h i }; + e -- { j k l m n o }; +} +``` -___ +The original DOT description used in the code block is shown hereafter. -With all these variants at your disposal, you should be able to typeset print-quality -books and novels, with the appropriate dividers within chapters, or at the end of thereof. +::: {custom-style=CodeBlock} +```{.dot render=false width="5cm" layout=neato} +graph { + node [fillcolor="lightskyblue:darkcyan" style=filled + gradientangle=270] + a -- { b d }; + b -- { c e }; + c -- { f g h i }; + e -- { j k l m n o }; +} +``` +::: -### Lists +**Pie charts** -Unordered lists (a.k.a. itemized or bulleted lists) are obviously supported, or -we would not have been able to use them in the previous sections. +Likewise, if you installed the optional **piecharts.sile** collection, then code blocks marked as "piechart" are automatically rendered, with all other attributes are passed to the underlying processor. +Consider the following code block, consisting in a CSV table... -Ordered lists are supported as well, and also accept some of the "fancy lists" features -from Pandoc. The starting number is honored, and you have the flexibility to use -digits, roman numbers or letters (in upper or lower case). -The end delimiter, besides the standard period, can also be a closing parenthesis. +::: {custom-style=CodeBlock} +```` +``` {.piechart height=8em} +Player,Score +Mario,55 +Luigi,23 +Peach,12 +Bowser,10 +``` +```` +::: - b. This list uses lowercase letters and starts at 2. Er... at "b", that is. - i) Roman number... - i) ... followed by a right parenthesis rather than a period. +... then, provided a proper setup, it is rendered as: -By the way, +``` {.piechart height=8em} +Player,Score +Mario,55 +Luigi,23 +Peach,12 +Bowser,10 +``` - 1. Nesting... +### Content container divisions (divs) - ... works as intended. +A div begins with a line of three or more consecutive colons, optionally followed by white space and either a class name or attributes. It ends with a line of consecutive colons at least as long as the opening fence, or with the end of the document or containing block. - - Fruits - - Apple - - Orange - - Vegetables - - Carrot - - Potato +This converter recognizes a few specific attributes on divs: - And that's all about regular lists. + - Identifiers for referencing purposes + - The `custom-style` attribute, see §[](#djot-custom-styles) + - The `lang` attribute, see §[](#djot-language-changes) -Task lists following the GitHub-Flavored Markdown (GFM) format are supported too. +::: {custom-style=Difference} +![](./examples/manicule.svg){height=1.3ex} **Main differences with Djot** - - [ ] Unchecked item - - [x] Checked item + - Different syntax for introducing the class name or attributes. + - No support for divs with a caption (Djot extension) -Definition lists^[As in Pandoc, using the syntax of PHP Markdown Extra with some -extensions.] are also decently supported. +::: -apples - : Good for making applesauce. +### Horizontal dividers -citrus - : Like oranges but yellow. +In standard Markdown, a line containing a row of three or more asterisks, dashes, or underscores (optionally separated by spaces) are supposed to produce a horizontal rule. +This converter however slightly deviates from that simple specification^[And also from Pandoc, therefore. Quite obviously, the `\autodoc:package{pandocast}`{=sile} package will also only show horizontal rules.], +for the mere reason that such a horizontal rule is seldom typographically sound in many contexts. -If your class or previously loaded packages provide a `defn` environment, it will be used. -Otherwise, the converter uses its own fallback method. +Three asterisks produce a centered asterism. -### Block quotes +*** -> Block quotes are written like so. -> -> > They can be nested. +Three space-separated asterisks produce a "dinkus". -There's a small catch here. If your class or previously loaded packages provide -a `blockquote` environment, it will be used. Otherwise, the converter uses its -own fallback method. +* * * -### Footnotes +Three dashes produce a centered horizontal rule, taking 20% of the line. +--- -Here is a footnote call[^1]. +Four dashes produce a centered horizontal rule, taking 33% of the line. -[^1]: And here is some footnote text. But there were already a few foonotes earlier in this -document. Let's just add, as you can check in the source, that the converter supports several -syntaxes for footnotes. +---- -### Languages +Four space-separated dashes produce, provided appropriate package support is available^[I.e. the **couyards.sile** package module is installed.], a nice curvy pendant. +What you see just below therefore depends on that support being present or not. -Language changes within the text are supported, either around "div" blocks or inline -"span" elements (both of those are Pandoc-like extensions to standard Markdown). -It is not much visible below, obviously, but the language setting -affects the hyphenation and other properties. In the case of French, for instance, -you can see the special thin space before the exclamation point, or the internal -spacing around quoted text: +- - - - -::: {lang=fr} -> Cette citation est en français! -::: +Finally, without demonstrating it here, fourteen consecutive dashes enforce a page break.^[Why exactly fourteen? +"The original says _fourteen_, but there is ample reason to infer that, as used by Asterion, this numeral stands for _infinite_." (Jorge Luis [Borges]{.smallcaps}, "The House of Asterion". +In _Labyrinths: Selected Stories and Other Writings_, 1964.)] +It gives some flexibility to authors for marking a page break, and still get something visible in its place with other converters. -Or inline in text: [«Encore du français!»]{lang=fr} +Otherwise, everything else produces a full rule.^[Since this feature may elvove and support more patterns, let's guarantee that underscores are reserved, and will always produce a full horizontal rule. +This author finds three or more underscores ugly and never used them inMarkdown; as of bad typography, it renders justice to the full rule.] -This was obtained with: +___ -``` -::: {lang=fr} -> Cette citation est en français! -::: +With all these variants at your disposal, you should be able to typeset print-quality +books and novels, with the appropriate dividers within chapters, or at the end of thereof. -Or inline in text: [«Encore du français!»]{lang=fr} -``` +::: {custom-style=Difference} +![](./examples/manicule.svg){height=1.3ex} **Main differences with Djot** -Smart typography takes the current language into account when converting straight double and single -quotation marks to the appropriate typographic variant: -["English"]{lang=en} / ["Deutsch"]{lang=de} / ["français"]{lang=fr} / ["dansk"]{lang=da} / ["русский"]{lang=ru}; -['English']{lang=en} / ['Deutsch']{lang=de} / ['français']{lang=fr} / ['dansk']{lang=da} / ['русский']{lang=ru}.^[For -most languages, `"` and `'` correspond to the primary and secondary quotations marks, respectively. -In some languages, they are used the other way round, but obviously the user's input is respected in those cases -(e.g. respectively ["Ghàidhlig"]{lang=cy} and ['Ghàidhlig']{lang=cy}).] - -### Custom styles - -On the "div" and "span" extended elements, the converter also supports the `{custom-style="..."}` -attribute. -This is, as a matter of fact, the same syntax as proposed by Pandoc, for instance, in -its **docx** writer, to specify a specific, possibly user-defined, custom style name (in that -case, a Word style, obviously). So yes, if you had a Pandoc-Markdown document styled for Word, -you might consider that switching to SILE is a viable option! - -If such a named style exists, it is applied. Erm. What does it mean? -Well, in the default implementation, if used within in a **resilient** class and there is a corresponding style, the converter uses it. -Otherwise, if there is a corresponding SILE command by that name, the converter invokes it. -Otherwise, it just ignores the style and processes the content as-is. -Even if you do not use a resilient-compatible class, it thus allows you to use some interesting SILE features. -For instance, here is some block of text marked as "center": + - _Ad hoc_ interpretation of separators, so as to produce more typographically sound results despite not supporting attributes on horizontal rules. + - Less flexibility than the class-based approach in Djot. -::: {#centered custom-style="center"} -This is SILE at its best. ::: -And some inline [message]{custom-style="strong"}, marked as "strong". That's a fairly -contrived way to obtain a bold text, but you get the underlying general idea. +### Raw blocks {#markdown-raw-blocks} -This logic is implemented in the `\autodoc:command{\markdown:custom-style:hook}`{=sile} -command. -Package or class designers may override this hook to support any -other additional styling mechanism they may have or want. -But basically, this is one of the ways to use SILE commands in Markdown. -While you could invoke _any_ SILE command with this feature, we recommend, though, to restrict it to styling. -You will see, further below, another more powerful way to leverage Markdown with SILE’s full processing capabilities. +A code block marked as `=format` is interpreted as raw content and passed through verbatim to output in that format. +As for raw inlines (§[](#markdown-raw-inlines)), this converter supports the `{=sile}` and `{=sile-lua}` annotations. +Other annotations are ignored, and the block content is skipped. -### Images +Let's combine several of the extensibility techniques we have seen so far, and do something cool. -Here is an image: ![Invisible caption as in inline](./gutenberg.jpg "An exemplary image"){width=1.5cm} +```{=sile} +\use[module=packages.barcodes.ean13] +``` -You can specify the required image width and/or height, as done just above actually, -by appending the `{width=... height=...}` attributes^[And possibly other attributes, -they are all passed through to the underlying SILE packages.] after the usual Markdown -image syntax ---Note that any unit system supported by SILE is accepted. +```{=sile-lua} +local class = SILE.documentState.documentClass +class:registerCommand("MyEan13b", function (_, content) + local code = SU.ast.contentToString(content) + code = code:gsub("[-–]","") + SILE.call("ean13", { scale="SC0", code = code }) +end) +``` -![This man is Gutenberg.](./gutenberg.jpg "An exemplary image"){#gutenberg width=3cm} +Why not "style" an EAN-13 ISBN as a nice barcode? -An image with nonempty caption (i.e. "alternate" text), occurring alone by itself in a paragraph, -will be rendered as a figure with a caption. If your class or previously loaded packages -provide a `captioned-figure` environment, it will be wrapped around the image (and it is then assumed to take care of a `\caption` content, i.e. to extract and display it -appropriately).^[When using the **resilient** classes, the caption will be numbered by -default, and added to the list of figures. Specify `.unnumbered`, and `.notoc` respectively, -if you do not want it.] -Otherwise, the converter uses its own fallback method. +[[978-2-9539896-6-3]{custom-style=MyEan13b}]{custom-style=center} -Besides regular image files, a few specific file extensions are also recognized and -processed appropriately. -Notably ![](./examples/manicule.svg){height=0.6bs} SVG is supported too (`.svg`), as demonstrated -here with a "manicule" in that format. -Files in Graphviz DOT graph language (`.dot`) are supported and rendered as images too. +First, we ensure the **barcodes.ean13** packages is loaded.[^markdown-ean13-assume] +Let's use some SIL language for that. -![The **markdown.sile** ecosystem (simplified).](./markdown-sile-schema.dot "A graph"){width="92%fw"} +[^markdown-ean13-assume]: The package is part of the **barcodes.sile** collection, and is not installed by default --- But the **resilient.sile** collection does include it, and this is what we are using here. -### Maths +::: {custom-style=CodeBlock} +```` +```{=sile} +\use[module=packages.barcodes.ean13] +``` +```` +::: -TeX-like math between `$` ("inline mode") or `$$` ("display mode") decently works. -Note that $20,000 and $30,000 don't parse as math, while $e^{i\pi}=-1$ does. -There is an important constraint, though: you have to restrict yourself to the syntax subset supported -by SILE. This being said, some nice fomulas may be achieved: +Then, we define a new SILE command to render the EAN-13 barcode. +Let's do it in Lua, as we need some string manipulation. -$$\pi=\sum_{k=0}^\infty\frac{1}{16^k}(\frac{4}{8k+1} − \frac{2}{8k+4} − \frac{1}{8k+5} − \frac{1}{8k+6})$$ +::: {custom-style=CodeBlock} +```` +```{=sile-lua} +local class = SILE.documentState.documentClass +class:registerCommand("MyEan13b", function (_, content) + local code = SU.ast.contentToString(content) + -- Remove dashes and (smart) en-dashes + code = code:gsub("[-–]","") + SILE.call("ean13", { scale="SC0", code = code }) +end) +``` +```` +::: -### Tables {#tables} +We can now use our new command as a custom style (see §[](#markdown-custom-styles)). -The converter only supports the PHP-like "pipe table" syntax at this point, with an optional -caption. +::: {custom-style=CodeBlock} +``` +... [978-2-9539896-6-3]{custom-style=MyEan13b} +``` +::: -| Right | Left | Default | Center | -|------:|:-----|---------|:------:| -| 12 | 12 | 12 | 12 | -| 123 | 123 | 123 | 123 | +Other converters will likely ignore the style, and the ISBN will be displayed as-is, so this technique degrades gracefully. - : Demonstration of a pipe table. +::: {custom-style=Difference} +![](./examples/manicule.svg){height=1.3ex} **Main differences with Djot** -Regarding captioned tables, there's again a catch. If your class or previously loaded packages -provide a `captioned-table` environment, it will be wrapped around the table (and it is then assumed to -take care of a `\caption` content, i.e. to extract and display it appropriately). Otherwise, -the converter uses its own fallback method. + - Curly braces around the `{=sile}` and `{=sile-lua}` annotations. + +::: ### Line blocks -So called "line blocks", a sequence of lines beginning with a vertical bar (`|`) and followed by a -space, are also supported. The division into lines is preserved in the output. Any additional leading space -is preserved too, interpreted as an em-quad. These blocks can be useful for typesetting addresses -or poetry. +So called "line blocks", a sequence of lines beginning with a vertical bar (`|`) and followed by a space, are supported. +The division into lines is preserved in the output. +Any additional leading space is preserved too, interpreted as an em-quad. These blocks can be useful for typesetting addresses or poetry. ::: { custom-style=em } | Long is one night, long is the @@ -328,9 +1186,7 @@ or poetry. ::: This implementation goes a bit beyond the standard Pandoc-inspired support for line blocks. -In particular, empty lines (i.e. starting with a vertical bar and a single space, but no other content -afterwards) are interpreted as stanza separators, which should be smaller than an empty line -(i.e. a small skip, by default). +In particular, empty lines (i.e. starting with a vertical bar and a single space, but no other content afterwards) are interpreted as stanza separators, which should be smaller than an empty line (i.e. a small skip, by default). ::: { .poetry lang=fr step=4 } | En hiver la terre pleure ; @@ -354,14 +1210,9 @@ afterwards) are interpreted as stanza separators, which should be smaller than a | S’en va le plus tôt qu’il peut. ::: -Moreover, there's once again a nice catch. If your class or previously loaded packages -provide a `poetry` environment, and you set the `.poetry` class specifier on a "div" -element just around the line blocks, then this environment will be used instead of -the default one provided by the converter. It is assumed to implement the same -features and options ---namely, `numbering` (boolean, true by default)^[For consistency -with headers, the `.unnumbered` class specifier is also supported.], `start` and `step` -(integers) and `first` (boolean, false by default)--- as the `\autodoc:package{resilient.poetry}`{=sile} -3rd-party package. For instance: +Moreover, there's once again a nice catch. If your class or previously loaded packages provide a `poetry` environment, and you set the `.poetry` class specifier on a "div" element just around the line blocks, then this environment will be used instead of the default one provided by the converter. +It is assumed to implement the same features and options ---namely, `numbering` (boolean, true by default)^[For consistency with headers, the `.unnumbered` class specifier is also supported.], `start` and `step` +(integers) and `first` (boolean, false by default) --- as the **resilient.poetry** 3rd-party package. For instance: ~~~ :::{ .poetry step=4 first=true } @@ -370,194 +1221,125 @@ with headers, the `.unnumbered` class specifier is also supported.], `start` and ::: ~~~ -### Basic links - -Here is a link to the [SILE website](https://sile-typesetter.org/). -It might not be visible in the PDF output, but hover it and click. It just works. -Likewise, here is an internal link to the "[Basic typesetting](#basic-typesetting)" section. - -You can use attributes on links: -the [SILE website](https://sile-typesetter.org/){.underline}.^[Within a **resilient** class, -we'd possibly recommend using a custom style to color links, etc.] - -### Cross-references - -Neither standard Markdown nor Pandoc defines a proper way to insert cross-references of the kind -you would see in a book, as in the following example. - -> The section on "[](#tables){ .title }", that is [](#tables){ .section }, -> is on p. [](#tables){ .page }. +:::{custom-style=Difference} +![](./examples/manicule.svg){height=1.3ex} **Main differences with Djot** -This converter takes a bold decision, though unlikely to break anything unexpected. Empty local links -(that is, without inline display content) are interpreted as cross-references. By default, they are -resolved to the closest numbering item, whatever that might be in the hierarchical structure of your -document. A pseudo-class attribute may be used to override the default behavior and specify which type -of reference is expected (page number, section number or title text). Thus, the above example was -obtained from the following input: + - Line blocks are not (yet) supported in Djot, and Mardown shines here. -``` -The section on "[](#tables){ .title }", that is [](#tables){ .section }, is on p.[](#tables){ .page }. -``` +::: -Besides heading levels, it also works for various elements where you can define an identifier, -for instance we had some centered text in section [](#centered). With appropriate class and package -support^[Typically, it works with the **resilient** collection of classes and packages. It won't -work with non-supporting class and packages, using the fallback implementation for captioned elements, -poetry, etc.], -you may even refer to Gutenberg as "figure [](#gutenberg)", or to some poetry verse -mentioning the Sun ("Soleil"), in [](#sun){.section}, as "verse [](#sun)". +## Advanced topics {#markdown-advanced-topics} -### Code blocks +### Languages {#markdown-language-changes} -Verbatim code and "fenced code blocks" work: +Language changes within the text are supported, on either blocks or inline +elements. +It relies on the `lang` key-value attribute, where the value is a BCP47 language code. +It is not much visible below, obviously, but the language setting affects the hyphenation and other properties. +In the case of French, for instance, you can see the special thin space before the exclamation point, the use of appropriate quotation marks, and the internal spacing around quoted text: -```lua -function fib (n) - -- Fibonacci numbers - if n < 2 then return 1 end - return fib(n - 2) + fib(n - 1) -end -``` +::: {lang=fr} +> Cette citation est en français! +::: -As shown above, code blocks marked as being in Lua (either with the `lua` information string or the `.lua` class specifier) are syntax-highlighted. This is a naive approach, until the converter possibly supports a more general solution. +Or inline in text: ["Encore du français!"]{lang=fr} -Code blocks marked as being in the Graphviz DOT language (either with the `dot` information string or the `.dot` class specifier) are rendered as images. When the attribute syntax is used, options are passed to the underlying processor. For instance, the image below is produced with -`{.dot width=5cm layout=neato}`. +This was obtained with: -``` {.dot width=5cm layout=neato} -graph { - node [fillcolor="lightskyblue:darkcyan" style=filled gradientangle=270] - a -- { b d }; - b -- { c e }; - c -- { f g h i }; - e -- { j k l m n o }; -} +::: {custom-style=CodeBlock} ``` - -If you want the actual code to be displayed, rather than the converted image, -you can set the `render` attribute to false. - -``` {.dot render=false width=5cm layout=neato} -graph { - node [fillcolor="lightskyblue:darkcyan" style=filled gradientangle=270] - a -- { b d }; - b -- { c e }; - c -- { f g h i }; - e -- { j k l m n o }; -} +::: {lang=fr} +> Cette citation est en français! +::: + +Or inline in text: ["Encore du français!"]{lang=fr} ``` +::: -Code blocks marked as being in Markdown or Djot are interpreted too (again, unless -the `render` attribute is set to false). -This feature allows switching between those languages, would there be something one does not support yet. -For Markdown, attributes are passed to the converter, allowing to possibly use different compatibility options (see "[Configuration](#configuration)"). +As can be seen, the current language is taken into account when converting +paired straight double and single quotation marks to the appropriate typographic variant: +["English"]{lang=en} / ["Deutsch"]{lang=de} / ["français"]{lang=fr} / ["dansk"]{lang=da} / ["русский"]{lang=ru}; +['English']{lang=en} / ['Deutsch']{lang=de} / ['français']{lang=fr} / ['dansk']{lang=da} / ['русский']{lang=ru}.[^markdown-smart-quotes] -### Raw blocks +[^markdown-smart-quotes]: For most languages, `"` and `'` correspond to the primary and secondary quotations marks, respectively. +In some languages, they are used the other way round, but obviously the user's input is respected in those cases (e.g. respectively ["Ghàidhlig"]{lang=cy} and ['Ghàidhlig']{lang=cy}). -Last but not least, the converter supports a `{=sile}` annotation on code blocks, to pass -through their content in SIL language, as shown below.[^raw-comment] +### Custom styles {#markdown-custom-styles} -[^raw-comment]: This is also a Pandoc-inspired extension to standard Markdown. Other `{=xxx}` annotations -than those described in this section are skipped (i.e. their whole content is ignored). -`That's a \LaTeX{} construct, so it's skipped in the SILE output.`{=latex} +The converter also supports the `{custom-style="..."}` attribute on divs and spans. +It is inspired by the syntax proposed by Pandoc for Markdown, in its **docx** writer, to specify a specific, possibly user-defined, custom style name (in that case, a Word style, obviously). -```{=sile} -For instance, this \em{entire} sentence is typeset in a \em{raw block}, in SIL language. -``` +Here, if such a named style exists, it is applied. Erm. What does it mean? +Well, in the default implementation, if used within in a *resilient* class and there is a corresponding style, the converter uses it. +Otherwise, if there is a corresponding SILE command by that name, the converter invokes it. +Otherwise, it just ignores the style and processes the content as-is. +Even if you do not use a resilient-compatible class, it thus allows you to use some interesting SILE features. +For instance, here is some block of text marked as "center", with some inline text marked as "strong". -Likewise, this is available on inline code elements: `\em{idem.}`{=sile} +::: {#markdown-centered custom-style="center"} +This is SILE and Djot at their [best]{custom-style="strong"}! +::: This was obtained with: -~~~ -```{=sile} -For instance, this \em{entire} sentence is typeset in a \em{raw block}, in SIL language. -``` - -Likewise, this is available on inline code elements: `\em{idem.}`{=sile} -~~~ - - -It also supports `{=sile-lua}` to pass Lua code. -This is just a convenience compared to the preceding one, but it allows you to exactly -type the content as if it was in a code block (i.e. without having to bother wrapping -it in a script). - -```{=sile-lua} -SILE.call("em", {}, { 'This' }) -SILE.typesetter:typeset(" is called from Lua.") +::: {custom-style="CodeBlock"} ``` - -This was generated by: - -~~~ -```{=sile-lua} -SILE.call("em", {}, { 'This' }) -SILE.typesetter:typeset(" is called from Lua.") +::: {#markdown-centered custom-style="center"} +This is SILE and Djot at their [best]{custom-style="strong"}! +::: ``` -~~~ +::: -You now have the best of two worlds in your hands, bridging together Markdown and SILE -so that you can achieve wonderful things, we have no idea of. Surprise us! +That's a fairly contrived way to obtain a bold text, but you get the underlying general idea. +This is one of the ways to use SILE commands in Djot. +While you could invoke _any_ SILE command with this feature, we recommend, though, to restrict it to styling. +Another more powerful way to leverage Djot with SILE’s full processing capabilities, and benefit from the best of both worlds, is to use the "raw" annotations, described in §[](#markdown-raw-inlines) and §[](#markdown-raw-blocks). -### HTML elements -For mere convenience, the `
` element is supported in addition to the standard ways to indicate -a hard line break (see the "[Basic typesetting](#basic-typesetting)" section), -therefore...
... it is honored. +## Configuration {#markdown-configuration} -Would you have a long word, such as -AAAAABBBBBCCCCCDDDDDEEEEEFFFFFGGGGGHHHHHIIIIIJJJJJKKKKKLLLLLMMMM, the `` element (introduced in HTML5) represents a word break opportunity. -It may help when the line-breaking rules would not otherwise create a break at acceptable locations. -In this admittedly lame example, we used it between groups of a same letter. +The calling context (a wrapper document in SIL syntax, a resilient "master document", etc.) can pass additional options to tune the behavior of the converter. -HTML entities are also processed, e.g. `‰` renders as ‰. +Any option starting with `meta:` is passed to the converter (with this prefix removed). +It converter cannot use these options -- Markdown does not support symbols as Djot does -- but at least they are passed through to any embedded Djot block, and might be used there. -### Smarter typography +The `shift_headings` option can take an integer value and causes headers in included or embedded raw content to be offset (that is, shifted by the given amount). -On "span" elements, the `.decimal` pseudo-class attribute instructs the converter to consider numbers -in the content as decimal numbers, formatted with suitable decimal mark and digit grouping according -to the usage in the current language. -This allows, say, 1984 to be rendered as "[1984]{ .decimal } years ago" in English, -or "[1984 années]{ .decimal lang=fr }" in French, with appropriate separators. +In SIL documents, you can pass such options to the `\include` command or the `raw` environment. +For instance, you can include a Markdown file with shifted headings like this: -Another pseudo-class `.nobreak` is supported on "span" elements. It ensures the content will not be line-broken. Use it wisely around small pieces of text or you might end up with more serious justification issues! Yet, it might be useful for proper names, etc. +::: {custom-style=CodeBlock} +``` +\include[src=somefile.md, shift_headings=1] +``` +::: -When smart typography is enabled, the native converter also supports automatically converting -straight single and double quotes after digits to single and double primes. -It can be useful for easily typesetting units (e.g. 6") -or coordinates (e.g. Oxford is located at 51° 45' 7" N, 1° 15' 27" W). +For document classes supporting it (in particular, the **resilient** book class), this feature also allows you to access levels above the default scheme, such as "parts". -## Configuration {#configuration} +::: {custom-style=CodeBlock} +``` +\include[src=somefile.md, shift_headings=-1] +``` +::: -Most Markdown syntax extensions are enabled by default. -You can pass additional options to -the `\autodoc:command{\include}`{=sile} command or the `\autodoc:environment[check=false]{raw}`{=sile} environment to tune the behavior of the Markdown parser. +Finally, most Markdown syntax extensions are enabled by default. +The calling context can pass additional options to tune the behavior of the Markdown parser. +:::: {custom-style=FramedPara} ::: {custom-style=raggedright} -> Available options are: `smart`, `smart_primes`, `strikeout`, `mark`, `subscript`, `superscript`, -> `definition_lists`, `notes`, `inline_notes`, -> `fenced_code_blocks`, `fenced_code_attributes`, `bracketed_spans`, `fenced_divs`, -> `raw_attribute`, `link_attributes`, -> `startnum`, `fancy_lists`, `task_list`, `hash_enumerators`, `table_captions`, `pipe_tables`, `header_attributes`, -> `line_blocks`, `escaped_line_breaks`, `tex_math_dollars`. +Available options are: `smart`, `smart_primes`, `strikeout`, `mark`, `subscript`, `superscript`, +`definition_lists`, `notes`, `inline_notes`, +`fenced_code_blocks`, `fenced_code_attributes`, `bracketed_spans`, `fenced_divs`, +`raw_attribute`, `link_attributes`, +`startnum`, `fancy_lists`, `task_list`, `hash_enumerators`, `table_captions`, `pipe_tables`, `header_attributes`, +`line_blocks`, `escaped_line_breaks`, `tex_math_dollars`. ::: +:::: For instance, to disable the smart typography feature: +::: {custom-style=CodeBlock} ``` \include[src=somefile.md, smart=false] ``` - -The `shift_headings` option can take an integer value and causes headers in included or embedded raw content to be offset (that is, shifted by the given amount). -For instance: - -``` -\include[src=somefile.md, shift_headings=1] -``` - -For document classes supporting it, this feature also allows you to access levels above the default scheme, such as "parts". - -``` -\include[src=somefile.md, shift_headings=-1] -``` +::: diff --git a/examples/sile-and-pandoc.dj b/examples/sile-and-pandoc.dj index e4af062..ca3ba20 100644 --- a/examples/sile-and-pandoc.dj +++ b/examples/sile-and-pandoc.dj @@ -1,27 +1,49 @@ # Alternative route: SILE and Pandoc -Pandoc is a free-software document converter, created by the same John MacFarlane who provided the *lunamark* and *djot* libraries which empower SILE’s native Markdown and Djot package. +> "If you need to convert files from one markup format into another, Pandoc is your swiss-army knife." +^ From the [Pandoc website](https://pandoc.org/). + +[P]{custom-style=Initial}[andoc]{.smallcaps} is a free-software document converter, created by the same John MacFarlane who initiated the *lunamark* and *djot* libraries which empower our native Djot and Mardown packages. The latter, though, do not offer as many options and extensions as Pandoc does, for advanced typesetting. -In the event where the native solution would fall short for you ---e.g. would you need some extension of feature it doesn't yet support--- you may want to use Pandoc directly for converting your document to an output suitable for SILE. +In the event where the native solution would fall short for you --- _e.g._ would you need some extension or feature it doesn't yet support --- you may want to use Pandoc directly for converting your document to an output suitable for SILE. + +The following solution is still an experimental proof-of-concept, but you may give it a chance, and help us fill the gaps. + +## Prerequisites -The following solutions are still experimental proof-of-concepts, but you may give them a chance, and help us fill the gaps. +Obviously, you need to have the Pandoc software installed on your system. +You also need to have the LuaJSON module installed and available to your SILE environment. +As of 2024, we recommend using the development version of the module, due to some issues with the current release version. + +{custom-style=CodeBlock} +::: +```bash +luarocks install --dev luajson +``` +::: ## Using Pandoc's AST with the pandocast package -The experimental `\autodoc:package{pandocast}`{=sile} package allows you to use Pandoc’s JSON AST as an input format for documents. +The experimental *pandocast* package allows you to use Pandoc’s JSON AST as an input format for documents. You can obtain an AST output from Pandoc for any supported source format. Keeping the focus on Markdown here: -``` +{custom-style=CodeBlock} +::: +```bash pandoc -t json somefile.md -f markdown -o somefile.pandoc ``` +::: -Once the package is loaded, the `\autodoc:command{\include[src=]}`{=sile} command supports reading and processing such a Pandoc AST file, assuming the `.pandoc` extension or specifying the `format=pandocast` parameter: +Once the package is loaded, the `\autodoc:command{\include[src=]}`{=sile} command supports reading and processing such a Pandoc AST file from a document in SIL syntax, assuming the `.pandoc` extension or specifying the `format=pandocast` parameter: +{custom-style=CodeBlock} +::: ``` \use[module=packages.pandocast] \include[src=somefile.pandoc] ``` +::: This package supports the same advanced features as the native Markdown solution, e.g. the ability to use custom styles, to pass native content through to SILE, etc. @@ -30,20 +52,14 @@ The `shift_headings` option is also available, as with the Markdown and Djot sol There is a small _caveat_, though: one must use a version of Pandoc which generates an AST compatible with our input handler ("inputter"). While the Pandoc AST is somewhat stable, it may change when new features are introduced in the software. -## Using a Pandoc "custom writer" in Lua - -Pandoc also supports "custom writers" developed in Lua[^pandoc-writer]. - -[^pandoc-writer]: - -This custom writer API is fairly recent and might change. -Actually, besides a "Classic style" API (deprecated), there's now also a "New style" API... -While such custom writers may have some rough edges, the idea is quite appealing nevertheless. -After all, SILE is mostly written in Lua, so the skills are there in the community. +## Advanced tables with pandocast -Again, there is no official solution using this conversion path, but some pretty neat experimental results have been achieved[^omi-writer]. -That custom writer targets a specific (non-standard) class and a bunch of specific packages, which might not have been ported to the latest version of SILE... -This said, you can also certainly help pushing the idea forward! +While our native packages accept so-called "pipe tables" only, Pandoc supports the standard "simple tables" from original Markdown, and also extends Markdown with several other methods for declaring tables. +Would you have a document containing such tables, then *pandocast* should be able to render them. Above, we just described that package briefly. +It's now showcase time! -[^omi-writer]: +Obviously, it is mostly a question of input syntax, and the converter shouldn't actually have to bother, since the hard work parsing these tables is done by Pandoc itself, generating a generic JSON AST representation.[^pandoc-ast-caution] +[^pandoc-ast-caution]: Some Pandoc AST structures address advanced table features from other document formats than Markdown. +While such documents can be converted to a Pandoc JSON AST, our package might not support them well. +Its main focus is on Markdown parity with Pandoc. diff --git a/inputters/djot.lua b/inputters/djot.lua index c155c3c..694d9fd 100644 --- a/inputters/djot.lua +++ b/inputters/djot.lua @@ -192,6 +192,7 @@ function Renderer:blockquote (node) local pos = node_pos(node) local out if node.caption then + -- Djot extension: caption supported on blockquotes local caption = self:render_children(node.caption) out = createStructuredCommand("markdown:internal:captioned-blockquote", node.attr or {}, { content, @@ -210,7 +211,18 @@ end function Renderer:div (node) local options = node.attr or {} local content = self:render_children(node) - return createCommand("markdown:internal:div", options, content, node_pos(node)) + local pos = node_pos(node) + local out + --- Djot extension: caption supported on div blocks + out = createCommand("markdown:internal:div", options, content, pos) + if node.caption then + local caption = self:render_children(node.caption) + out = createStructuredCommand("markdown:internal:captioned-figure", node.attr or {}, { + out, + createCommand("caption", {}, caption) + }, pos) + end + return out end function Renderer:section (node) @@ -257,7 +269,15 @@ function Renderer:code_block (node) end end end - return createCommand("markdown:internal:codeblock", options, node.s, node_pos(node)) + local pos = node_pos(node) + local out + out = createCommand("markdown:internal:codeblock", options, node.s, pos) + if node.caption then + -- Potential Djot extension (but not yet -- explicit wrapping in a div block might + -- be sufficient for now. + SU.warn("Caption on code block is not supported (ignored)") + end + return out end function Renderer:table (node) @@ -532,23 +552,17 @@ function Renderer:mark (node) end function Renderer:insert (node) + local options = node.attr or {} local content = self:render_children(node) - local out = { "⟨", content, "⟩" } - if node.attr then - -- Add a div when containing attributes - return createCommand("markdown:internal:span", node.attr, out, node_pos(node)) - end - return out + djotast.insert_attribute(options, "class", "inserted") + return createCommand("markdown:internal:span", options, content, node_pos(node)) end function Renderer:delete (node) + local options = node.attr or {} local content = self:render_children(node) - local out = { "{", content, "}" } - if node.attr then - -- Add a div when containing attributes - return createCommand("markdown:internal:span", node.attr, out, node_pos(node)) - end - return out + djotast.insert_attribute(options, "class", "deleted") + return createCommand("markdown:internal:span", options, content, node_pos(node)) end local function extractAttrValue (attr, key) diff --git a/inputters/markdown.lua b/inputters/markdown.lua index 336c4d5..e82edd1 100644 --- a/inputters/markdown.lua +++ b/inputters/markdown.lua @@ -94,7 +94,6 @@ local function SileAstWriter (writerOps, renderOps) writer.paragraph = simpleCommandWrapper("markdown:internal:paragraph") writer.code = simpleCommandWrapper("code") writer.emphasis = simpleCommandWrapper("em") - writer.strikeout = simpleCommandWrapper("strikethrough") writer.subscript = simpleCommandWrapper("textsubscript") writer.superscript = simpleCommandWrapper("textsuperscript") writer.blockquote = simpleCommandWrapper("markdown:internal:blockquote") @@ -156,6 +155,10 @@ local function SileAstWriter (writerOps, renderOps) return createCommand("markdown:internal:span" , attr, content) end + writer.strikeout = function (content) + return createCommand("markdown:internal:span" , { class = "strike" }, content) + end + writer.div = function (content, attr) return createCommand("markdown:internal:div" , attr, content) end diff --git a/inputters/pandocast.lua b/inputters/pandocast.lua index 2440aa6..2efe739 100644 --- a/inputters/pandocast.lua +++ b/inputters/pandocast.lua @@ -427,11 +427,6 @@ function Renderer:Emph (inlines) local content = self:render(inlines) return createCommand("em", {}, content) end --- Underline [Inline] -function Renderer:Underline (inlines) - local content = self:render(inlines) - return createCommand("underline", {}, content) -end -- Strong [Inline] function Renderer:Strong (inlines) @@ -439,10 +434,16 @@ function Renderer:Strong (inlines) return createCommand("strong", {}, content) end +-- Underline [Inline] +function Renderer:Underline (inlines) + local content = self:render(inlines) + return createCommand("markdown:internal:span", { class = "underline" }, content) +end + -- Strikeout [Inline] function Renderer:Strikeout (inlines) local content = self:render(inlines) - return createCommand("strikethrough", {}, content) + return createCommand("markdown:internal:span" , { class = "strike" }, content) end -- Superscript [Inline] @@ -460,7 +461,7 @@ end -- SmallCaps [Inline] function Renderer:SmallCaps (inlines) local content = self:render(inlines) - return createCommand("font", { features = "+smcp" }, content) + return createCommand("markdown:internal:span", { class = "smallcaps" }, content) end -- Quoted QuoteType [Inline] diff --git a/markdown.sile-dev-1.rockspec b/markdown.sile-dev-1.rockspec index 68a6a35..295a059 100644 --- a/markdown.sile-dev-1.rockspec +++ b/markdown.sile-dev-1.rockspec @@ -31,6 +31,7 @@ build = { ["sile.inputters.pandocast"] = "inputters/pandocast.lua", ["sile.inputters.djot"] = "inputters/djot.lua", ["sile.packages.markdown"] = "packages/markdown/init.lua", + ["sile.packages.markdown.cmbase"] = "packages/markdown/cmbase.lua", ["sile.packages.markdown.commands"] = "packages/markdown/commands.lua", ["sile.packages.markdown.utils"] = "packages/markdown/utils.lua", ["sile.packages.pandocast"] = "packages/pandocast/init.lua", diff --git a/packages/markdown/cmbase.lua b/packages/markdown/cmbase.lua new file mode 100644 index 0000000..11f4336 --- /dev/null +++ b/packages/markdown/cmbase.lua @@ -0,0 +1,133 @@ +---A base class for markdown command packages. +-- +-- It abstracts the low-level details of feature detection and compatibility with resilient components. +-- So the markdown.command packages can focus on their specific features and commands. +-- +-- @copyright License: MIT (c) 2024 Omikhleia, Didier Willis +-- @module packages.markdown.cmbase +-- + +local base = require("packages.base") + +local package = pl.class(base) +package._name = "markdown.cmbase" + +-- For feature detection. +-- NOTE: The previous implementation was clever; +-- local ok, ResilientBase = pcall(require, 'classes.resilient.base') +-- return ok and self.class:is_a(ResilientBase) +-- However this *loads* the class, which loads all the silex extensions, even if +-- the class is not actually used... +-- Enforcing the silex extensions is not what we wanted. +-- So we are back to a more naive implementation, checking the class hierarchy +-- by name. This is perhaps lame and knows too much about internals, but heh. +local function isResilientClass (cl) + while cl do + if cl._name == "resilient.base" then + -- The class is a resilient class or derived from one + return true + end + cl = cl._base + end + return false +end + +--- Load a package with a resilient variant. +---@param resilientpack string The resilient package name. +---@param legacypack string The legacy package name. +function package:loadAltPackage (resilientpack, legacypack) + if not self.class.packages[resilientpack] then + -- In resilient context, try enforcing the use of resilient variants, + -- assuming its compatible with SILE's legacy implementation (command-wise), + -- so that we benefit from its extended features and its styling. + if self.isResilient then + self:loadPackage(resilientpack) + else + self:loadPackage(legacypack) + end + end +end + +--- Load an optional package if present +---@param pack string The package name. +function package:loadOptPackage (pack) + local ok, _ = pcall(function () return self:loadPackage(pack) end) + SU.debug("markdown.commands", "Optional package "..pack.. (ok and " loaded" or " not loaded")) +end + +--- Feature detection, so we can see e.g. with self.hasPackageSupport.xxx if +--- a package is supported or not. +---@param check function The feature detection function. +---@return table The proxy object. +function package:_createSupportProxy (check) + return setmetatable({}, { + __index = check, + __newindex = function (_, key, _) + SU.error("Invalid attempt to set a feature detection value: " .. key) + end, + }) +end + +--- Package initialization. +function package:_init (_) + base._init(self) + + -- Check if document class is a resilient class or derived from one + self.isResilient = isResilientClass(self.class) + SU.debug("markdown.commands", "Feature detection:", + self.isResilient and "used in a resilient class" or "used in a non-resilient class") + + self.hasPackageSupport = self:_createSupportProxy(function (_, name) + local pack = self.class.packages[name] + SU.debug("markdown.commands", "Feature detection: package", name, + pack and "supported" or "not supported") + return pack + end) + + self.styles = self.hasPackageSupport["resilient.styles"] + if self.styles then + SU.debug("markdown.commands", "Feature detection: registering custom styles") + self:registerStyles() + end + + self.hasCommandSupport = self:_createSupportProxy(function (_, name) + -- Checking low-level SILE internals is not that nice... + -- SILE was refactor to have loadPackage() etc. methods, a lot of boilerplate + -- if at the end we still need to tap into low-level internals. + local cmd = SILE.Commands[name] + SU.debug("markdown.commands", "Feature detection: command", name, + cmd and "supported" or "not supported") + return cmd + end) + + self.hasStyleSupport = self:_createSupportProxy(function (_, name) + local style = self.isResilient and self.styles + --and self.styles:hasStyle(name) + -- TODO A hasStyle(name) method would be nice to have in resilient.styles + -- to avoid tapping into internal structures. + -- The resolveStyle(name, true) method returns {} for a (discardable) + -- non-existing style, so is not very handy here. + -- But it's a chicken and egg problem, between resilient and us. + and SILE.scratch.styles.specs[name] -- HACK + SU.debug("markdown.commands", "Feature detection: style", name, + style and "supported" or "not supported") + return style + end) +end + +--- Register a style (as in resilient packages). +function package:registerStyle (name, opts, styledef) + return self.styles:defineStyle(name, opts, styledef, self._name) +end + +--- Register styles (as in resilient packages) +--- For overriding in subclass +function package.registerStyles (_) end + +package.documentation = [[\begin{document} +A base package class for \code{markdown.commands}, hiding the low-level details of feature detection and compatibility with resilient components. + +It is not intended to be used alone. +\end{document}]] + +return package diff --git a/packages/markdown/commands.lua b/packages/markdown/commands.lua index 4070320..a2d8542 100644 --- a/packages/markdown/commands.lua +++ b/packages/markdown/commands.lua @@ -1,4 +1,4 @@ ---- Common commands for Markdown support in SILE, when there is no +--- Common commands for Markdown and Djot support in SILE, when there is no -- direct mapping to existing commands or packages. -- -- Split in a standalone package so that it can be reused and @@ -18,7 +18,7 @@ local createCommand, createStructuredCommand, = SU.ast.createCommand, SU.ast.createStructuredCommand, SU.ast.removeFromTree, SU.ast.subContent -local base = require("packages.base") +local base = require("packages.markdown.cmbase") local package = pl.class(base) package._name = "markdown.commands" @@ -49,24 +49,6 @@ local CommandCascade = pl.class({ end, }) -local UsualSectioning = { "part", "chapter", "section", "subsection", "subsubsection" } -local function getSectioningCommand (level) - local index = level + 1 - if index > 0 and index <= #UsualSectioning then - -- Check we actually have those commands (e.g. some classes might not - -- have subsubsections.) - if SILE.Commands[UsualSectioning[index]] then - return UsualSectioning[index] - end - SU.warn("Unknown command \\"..UsualSectioning[index].." (fallback to a default generic header)") - -- Default to something anyway. - return "markdown:fallback:header" - end - -- Also default to something anyway, but different message - SU.warn("No support found for heading level "..level.." (fallback to a default generic header)") - return "markdown:fallback:header" -end - local function hasLinkContent(tree) if type(tree) == "table" then return #tree > 1 or hasLinkContent(tree[1]) @@ -136,46 +118,9 @@ local function wrapLinkContent (options, content) return content end -function package:loadPackageAlt(resilientpack, legacypack) - if not self.class.packages[resilientpack] then - -- In resilient context, try enforcing the use of resilient variants, - -- assuming its compatible with SILE's legacy implementation (command-wise), - -- so that we benefit from its extended features and its styling. - if self.isResilient then - self:loadPackage(resilientpack) - else - self:loadPackage(legacypack) - end - end -end - --- For feature detection. --- NOTE: The previous implementation was clever; --- local ok, ResilientBase = pcall(require, 'classes.resilient.base') --- return ok and self.class:is_a(ResilientBase) --- However this loads the class, which loads all the silex extensions, even if --- the class is not used... --- Enforcing the silex extensions is not what we wanted. --- So we are back to a more naive implementation, checking the class hierarchy --- by name. --- This is lame and knows too much about internals, but heh. -local function isResilientClass(cl) - while cl do - if cl._name == "resilient.base" then - return true - end - cl = cl._base - end - return false -end - function package:_init (_) base._init(self) - -- Check if document class is a resilient class or derived from one - self.isResilient = isResilientClass(self.class) - SU.debug("markdown", self.isResilient and "Used in a resilient class" or "Used in a non-resilient class") - -- Only load low-level packages (= utilities) -- The class should be responsible for loading the appropriate higher-level -- constructs, see fallback commands further below for more details. @@ -193,20 +138,36 @@ function package:_init (_) self:loadPackage("url") -- Do those at the end so the resilient versions may possibly override things. - self:loadPackageAlt("resilient.lists", "lists") - self:loadPackageAlt("resilient.verbatim", "verbatim") + self:loadAltPackage("resilient.lists", "lists") + self:loadAltPackage("resilient.verbatim", "verbatim") -- Optional packages - pcall(function () return self:loadPackage("couyards") end) + self:loadOptPackage("couyards") + self:loadOptPackage("piecharts") -- Other conditional packages if self.isResilient then self:loadPackage("resilient.epigraph") + self:loadPackage("resilient.defn") end end -function package:hasCouyards () - return self.class.packages["couyards"] +local UsualSectioning = { "part", "chapter", "section", "subsection", "subsubsection" } +function package:_getSectioningCommand (level) + local index = level + 1 + if index > 0 and index <= #UsualSectioning then + -- Check we actually have those commands (e.g. some classes might not + -- have subsubsections.) + if self.hasCommandSupport[UsualSectioning[index]] then + return UsualSectioning[index] + end + SU.warn("Unknown command \\"..UsualSectioning[index].." (fallback to a default generic header)") + -- Default to something anyway. + return "markdown:fallback:header" + end + -- Also default to something anyway, but different message + SU.warn("No support found for heading level "..level.." (fallback to a default generic header)") + return "markdown:fallback:header" end function package.declareSettings (_) @@ -274,10 +235,10 @@ function package:registerCommands () SILE.call("hrule", { width = "33%lw", height = "0.4pt" }) end) end) - elseif hasClass(options, "fullrule") and self:hasCouyards() then + elseif hasClass(options, "fullrule") then -- Full line SILE.call("fullrule", { thickness = "0.4pt" }) - elseif hasClass(options, "pendant") and self:hasCouyards() then + elseif hasClass(options, "pendant") and self.hasPackageSupport.couyards then -- Pendant, with more options available than in Markdown local opts = { type = SU.cast("integer", options.type or 6), @@ -321,7 +282,7 @@ function package:registerCommands () SILE.call("hrule", { width = "33%lw", height = "0.4pt" }) end) end) - elseif options.separator == "- - - -" and self:hasCouyards() then + elseif options.separator == "- - - -" and self.hasPackageSupport.couyards then -- Pendant (fixed choice = the one I regularly use) SILE.call("smallskip") SILE.call("couyard", { type = 6, width = "default" }) @@ -335,7 +296,7 @@ function package:registerCommands () self:registerCommand("markdown:internal:header", function (options, content) local level = SU.required(options, "level", "header") - local command = getSectioningCommand(SU.cast("integer", level)) + local command = self:_getSectioningCommand(SU.cast("integer", level)) -- Pass all attributes to underlying sectioning command, and interpret -- .unnumbered and .notoc pseudo-classes as alternatives to numbering=false -- and toc=false. @@ -398,7 +359,7 @@ Please consider using a resilient-compatible class!]]) -- If the class or loaded packages provide a poetry environment and the div contains -- a lineblock structure only, then use the poetry environment instead. -- (tricky) This assumes a lot of knowledge about the AST... - if SILE.Commands["poetry"] and #content == 1 and content[1].command == "markdown:internal:lineblock" then + if self.hasCommandSupport.poetry and #content == 1 and content[1].command == "markdown:internal:lineblock" then content[1].command = "poetry" content[1].options = content[1].options or {} content[1].options.first = SU.boolean(options.first, false) @@ -435,16 +396,45 @@ Please consider using a resilient-compatible class!]]) cascade:call("font", { features = "+smcp" }) end if hasClass(options, "mark") then - cascade:call("color", { color = "red" }) -- FIXME TODO We'd need real support + cascade:call("markdown:custom-style:hook", { + name = "md-mark", + alt = "markdown:fallback:mark", + scope = "inline" + }) end if hasClass(options, "strike") then - cascade:call("strikethrough") + cascade:call("markdown:custom-style:hook", { + name = "md-strikethrough", + alt = "strikethrough", + scope = "inline" + }) end if hasClass(options, "underline") then - cascade:call("underline") + cascade:call("markdown:custom-style:hook", { + name = "md-underline", + alt = "underline", + scope = "inline" + }) + end + if hasClass(options, "inserted") then + cascade:call("markdown:custom-style:hook", { + name = "md-insertion", + alt = "underline", + scope = "inline" + }) + end + if hasClass(options, "deleted") then + cascade:call("markdown:custom-style:hook", { + name = "md-deletion", + alt = "strikethrough", + scope = "inline" + }) end if options["custom-style"] then - cascade:call("markdown:custom-style:hook", { name = options["custom-style"], scope = "inline" }) + cascade:call("markdown:custom-style:hook", { + name = options["custom-style"], + scope = "inline" + }) end if hasClass(options, "nobreak") then cascade:call("hbox") @@ -463,6 +453,13 @@ Please consider using a resilient-compatible class!]]) elseif ext == "dot" then options.format = "dot" SILE.call("embed", options) + elseif ext == "csv" then + if not (self.hasPackageSupport.piecharts or self.hasPackageSupport.piechart) then -- HACK Some early versions of piecharts have the wrong internal name + SU.error("No piecharts package available to render CSV data ".. uri) + end + options.src = nil + options.csvfile = uri + SILE.call("piechart", options) else SILE.call("img", options) end @@ -502,13 +499,13 @@ Please consider using a resilient-compatible class!]]) end, "Link in Markdown (internal)") self:registerCommand("markdown:internal:footnote", function (options, content) - if not SILE.Commands["footnote"] then + if not self.hasCommandSupport.footnote then -- The reasons for NOT loading a package for this high-level structure -- is that the class or other packages may provide their own implementation -- (e.g. formatted differently, changed to endnotes, etc.). -- So we only do it as a fallback if mising, to degrade gracefully. SU.warn("Trying to enforce fallback for unavailable \\footnote command") - self.class:loadPackage("footnotes") + self:loadAltPackage("resilient.footnotes", "footnotes") end if options.id then content = { @@ -576,7 +573,9 @@ Please consider using a resilient-compatible class!]]) -- So we might have a better version provided by a user-class or package. -- Otherwise, use our own fallback (with hard-coded choices too, but a least -- it does some proper nesting) - if not SILE.Commands["blockquote"] then + -- NOTE: The above applies to SILE 0.14.x. + -- SILE 0.15 is expected to provide a blockquote environment. + if not self.hasCommandSupport.blockquote then SILE.call("markdown:fallback:blockquote", {}, content) else SILE.call("blockquote", {}, content) @@ -588,7 +587,7 @@ Please consider using a resilient-compatible class!]]) -- environment if they want to do so (possibly with more features, -- e.g. managing list of tables, numbering and cross-references etc.), -- while minimally providing a default fallback solution. - if not SILE.Commands["captioned-table"] then + if not self.hasCommandSupport["captioned-table"] then SILE.call("markdown:fallback:captioned-table", {}, content) else local tableopts = {} @@ -607,7 +606,7 @@ Please consider using a resilient-compatible class!]]) -- environment if they want to do so (possibly with more features, -- e.g. managing list of tables, numbering and cross-references etc.), -- while minimally providing a default fallback solution. - if not SILE.Commands["captioned-figure"] then + if not self.hasCommandSupport["captioned-figure"] then SILE.call("markdown:fallback:captioned-figure", {}, content) else local figopts = {} @@ -621,52 +620,65 @@ Please consider using a resilient-compatible class!]]) end end, "Captioned figure in Markdown (internal)") - self:registerCommand("markdown:internal:codeblock", function (options, content) - local render = SU.boolean(options.render, true) - if render and hasClass(options, "dot") then - local handler = SILE.rawHandlers["embed"] - if not handler then - -- Shouldn't occur since we loaded the embedders package - SU.error("No inline handler for image embedding") - end - options.format = options.class - handler(options, content) - elseif render and hasClass(options, "djot") then - SILE.processString(SU.contentToString(content), "djot", nil, options) - elseif render and hasClass(options, "markdown") then - SILE.processString(SU.contentToString(content), "markdown", nil, options) - elseif hasClass(options, "lua") then - -- Naive syntax highlighting for Lua, until we have a more general solution - local tree = {} - if options.id then - tree[#tree+1] = createCommand("label", { marker = options.id }) + -- Code blocks + + self:registerCommand("markdown:internal:lua-highlighter", function (options, content) + -- Naive syntax highlighting for Lua, until we have a more general solution + local tree = {} + if options.id then + tree[#tree+1] = createCommand("label", { marker = options.id }) + end + local toks = pl.lexer.lua(content[1], {}) + for tag, v in toks do + local out = tostring(v) + if tag == "string" then + -- rebuild string quoting... + out = out:match('"') and ("'"..out.."'") or ('"'..out..'"') end - local toks = pl.lexer.lua(content[1], {}) - for tag, v in toks do - local out = tostring(v) - if tag == "string" then - -- rebuild string quoting... - out = out:match('"') and ("'"..out.."'") or ('"'..out..'"') + if naiveLuaCodeTheme[tag] then + local cascade = CommandCascade() + if naiveLuaCodeTheme[tag].color then + cascade:call("color", { color = naiveLuaCodeTheme[tag].color }) end - if naiveLuaCodeTheme[tag] then - local cascade = CommandCascade() - if naiveLuaCodeTheme[tag].color then - cascade:call("color", { color = naiveLuaCodeTheme[tag].color }) - end - if naiveLuaCodeTheme[tag].bold then - cascade:call("strong", {}) - end - if naiveLuaCodeTheme[tag].italic then - cascade:call("em", {}) - end - tree[#tree+1] = cascade:tree({ out }) - else - tree[#tree+1] = SU.utf8charfromcodepoint("U+200B")..out -- HACK with ZWSP to trick the typesetter respecting standalone linebreaks + if naiveLuaCodeTheme[tag].bold then + cascade:call("strong", {}) end + if naiveLuaCodeTheme[tag].italic then + cascade:call("em", {}) + end + tree[#tree+1] = cascade:tree({ out }) + else + tree[#tree+1] = SU.utf8charfromcodepoint("U+200B")..out -- HACK with ZWSP to trick the typesetter respecting standalone linebreaks end - SILE.call("verbatim", {}, tree) - else - -- Just raw unstyled verbatim + end + SILE.call("verbatim", {}, tree) + end, "Lua code block naive syntax highlighting in Markdown or Djot (internal)") + + self:registerCommand("markdown:internal:codeblock", function (options, content) + local render = SU.boolean(options.render, true) + local processed = false + if hasClass(options, "lua") then + -- Comes first as we don't want SILE raw handler to take over here. + -- (There's no such raw handler in the standard SILE distribution currently, + -- but let's be cautious.) + SILE.call("markdown:internal:lua-highlighter", options, content) + processed = true + elseif render then + local handler = utils.hasRawHandler(options) + if handler then + handler(options, content) + processed = true + else + local format, embed = utils.hasEmbedHandler(options) + if format then + options.format = format + embed(options, content) + processed = true + end + end + end + if not processed then + -- Default case: just raw unstyled verbatim text SILE.call("verbatim", {}, options.id and { createCommand("label", { marker = options.id }), @@ -676,7 +688,7 @@ Please consider using a resilient-compatible class!]]) } ) end - SILE.call("smallskip") + SILE.call("par") end, "(Fenced) code block in Markdown (internal)") self:registerCommand("markdown:internal:lineblock", function (_, content) @@ -721,7 +733,7 @@ Please consider using a resilient-compatible class!]]) end local title = removeFromTree(content, "caption") - if SILE.Commands["epigraph"] then -- asssuming the implementation from resilient.epigraph. + if self.hasCommandSupport.epigraph then -- asssuming the implementation from resilient.epigraph. if title then -- Trick: Put the extract title back as "\source" title.command = "source" @@ -737,11 +749,11 @@ Please consider using a resilient-compatible class!]]) end, "Captioned blockquote in Djot (internal)") self:registerCommand("markdown:internal:toc", function (options, _) - if not SILE.Commands["tableofcontents"] then + if not self.hasCommandSupport.tableofcontents then SU.warn("No table of contents command available (skipped)") return end - local tocHeaderCmd = SILE.Commands["tableofcontents:header"] + local tocHeaderCmd = self.hasCommandSupport["tableofcontents:header"] if tocHeaderCmd then -- HACK (opinionated) -- By design, resilient.tableofcontents does not output a header. @@ -758,7 +770,7 @@ Please consider using a resilient-compatible class!]]) -- Makes it easier for class/packages to provide their own definition -- environment if they want to do so (possibly with more features), -- while minimally providing a default fallback solution. - if not SILE.Commands["defn"] then + if not self.hasCommandSupport.defn then SILE.call("markdown:fallback:defn", {}, content) else SILE.call("defn", options, content) @@ -851,40 +863,84 @@ Please consider using a resilient-compatible class!]]) SILE.call("smallskip") end, "A fallback command for Markdown to insert a captioned figure") + self:registerCommand("markdown:fallback:mark", function (_, content) + local leading = SILE.measurement("1bs"):tonumber() + local bsratio = utils.computeBaselineRatio() + if SILE.typesetter.liner then + SILE.typesetter:liner("markdown:fallback:mark", content, + function (box, typesetter, line) + local outputWidth = SU.rationWidth(box.width, box.width, line.ratio) + local H = SU.max(box.height:tonumber(), (1 - bsratio) * leading) + local D = SU.max(box.depth:tonumber(), bsratio * leading) + local X = typesetter.frame.state.cursorX + SILE.outputter:pushColor(SILE.color("yellow")) + SILE.outputter:drawRule(X, typesetter.frame.state.cursorY - H, outputWidth, H + D) + SILE.outputter:popColor() + box:outputContent(typesetter, line) + end + ) + else + SU.debug("markdown.commands", "Feature detection: no liner, using a simpler fallback for mark") + -- Liners are introduced in SILE 0.15. + -- Resilient (with the silex compatibility layer) has them too for SILE 0.14. + -- For now, also support older versions of SILE when used in a non-resilient context. + -- This is not as good, since an hbox can't be broken across lines. + local hbox, hlist = SILE.typesetter:makeHbox(content) + SILE.typesetter:pushHbox({ + width = hbox.width, + height = hbox.height, + depth = hbox.depth, + outputYourself = function (box, typesetter, line) + local outputWidth = SU.rationWidth(box.width, box.width, line.ratio) + local H = SU.max(box.height:tonumber(), (1 - bsratio) * leading) + local D = SU.max(box.depth:tonumber(), bsratio * leading) + local X = typesetter.frame.state.cursorX + SILE.outputter:pushColor(SILE.color("yellow")) + SILE.outputter:drawRule(X, typesetter.frame.state.cursorY - H, outputWidth, H + D) + SILE.outputter:popColor() + hbox:outputYourself(typesetter, line) + end + }) + SILE.typesetter:pushHlist(hlist) + end + end) + -- C. Customizable hooks self:registerCommand("markdown:custom-style:hook", function (options, content) - -- Default implementation for the custom-style hook: - -- If we are in the context of a resilient-compatible class and there's - -- an existing style going by that name, use it. - -- Otherwise, tf there is a corresponding SILE command, we invoke it. - -- otherwise, we just ignore the style and process the content. - -- It allows us, e.g. to already - -- - Use resilient styles in proper context + -- Default/standard implementation for the custom-style hook: + -- 1. If we are in the context of a resilient-compatible class and there's + -- an existing style going by that name, we apply it. + -- 2. Otherwise, if there is a corresponding SILE command, going by the + -- optional "alt" command name or if unspecified, the style name itself, + -- we invoke it. + -- 3. Otherwise, we just silently ignore the style and process the content. + -- + -- It allows us to; + -- - Use resilient styling paradigm if applicable + -- - Use some alternate fallback command if provided -- - Use some interesting commands, such as "custom-style=raggedleft". -- Package or class designers MAY override this hook to support any other -- styling mechanism they may have or want. - -- The available options are the custom-style "name" and a "scope" which - -- can be "inline" (for inline character-level styling) or "block" (for - -- block paragraph-level styling). + -- The available options are the custom-style "name", an optional "alt" + -- command name, and a "scope" which can be "inline" (for inline + -- character-level styling) or "block" (for block paragraph-level styling). local name = SU.required(options, "name", "markdown custom style hook") - if self.isResilient - and self.class.packages["resilient.styles"] - -- HACK TODO we'd need a self.class.packages["resilient.styles"]:hasStyle(name) - -- to avoid tapping into internal structures. - -- self.class.packages["resilient.styles"]:resolveStyle(name, true) returns - -- {} for a (discardable) non-existing style, so is not very handy here. - and SILE.scratch.styles.specs[name] then - if options.scope == "block" then + local scope = SU.required(options, "scope", "markdown custom style hook") + local alt = options.alt or name + + if self.hasStyleSupport[name] then + if scope == "block" then SILE.call("style:apply:paragraph", { name = name }, content) else SILE.call("style:apply", { name = name }, content) end - elseif SILE.Commands[name] then - SILE.call(name, {}, content) + elseif self.hasCommandSupport[alt] then + SILE.call(alt, {}, content) else + SU.debug("markdown.commands", "Feature detection: ignoring unknown custom style:", name) SILE.process(content) - if options.scope == "block" then + if scope == "block" then SILE.call("par") end end @@ -893,7 +949,7 @@ Please consider using a resilient-compatible class!]]) end package.documentation = [[\begin{document} -A helper package for Markdown processing, providing common hooks and fallback commands. +A helper package for Markdown and Djot processing, providing common hooks and fallback commands. It is not intended to be used alone. \end{document}]] diff --git a/packages/markdown/utils.lua b/packages/markdown/utils.lua index 85b7aa6..758bee0 100644 --- a/packages/markdown/utils.lua +++ b/packages/markdown/utils.lua @@ -32,6 +32,10 @@ local function nbspFilter (str) return #t == 1 and t[1] or t end +--- Check if a given class is present in the options. +---@param options table Command options +---@param classname string Pseudo-class specifier +---@return boolean local function hasClass (options, classname) -- N.B. we want a true boolean here if options.class and string.match(' ' .. options.class .. ' ',' '..classname..' ') then @@ -40,9 +44,68 @@ local function hasClass (options, classname) return false end +--- Find the first raw handler suitable for the given pseudo-class attributes. +---@param options table Command options +---@return function|nil Handler function (if found) +local function hasRawHandler (options) + for name, handler in pairs(SILE.rawHandlers) do + if hasClass(options, name) then + SU.debug("markdown", "Found a raw handler for", name) + return handler + end + end + return nil +end + +--- Find the first embedder suitable for the given pseudo-class attributes. +---@param options table Command options +---@return string|nil, function Embedder name and handler function (if found) +local function hasEmbedHandler (options) + local handler = SILE.rawHandlers["embed"] + if not handler then + -- Shouldn't occur since we loaded the embedders package + -- but let's play safe... + return nil + end + -- HACK TODO: Use of a scratch variable is ugly, tapping into the + -- internals of the embedders package. + local embedders = SILE.scratch.embedders + if not embedders then + return nil + end + for name, _ in pairs(SILE.scratch.embedders) do + if hasClass(options, name) then + SU.debug("markdown", "Found an embedder for", name) + return name, handler + end + end + return nil +end + +local metrics = require("fontmetrics") +local bsratiocache = {} + +--- Compute the baseline ratio for the current font. +--- This is a ratio of the descender to the theoretical height of the font. +--- @return number Descender ratio +local computeBaselineRatio = function () + local fontoptions = SILE.font.loadDefaults({}) + local bsratio = bsratiocache[SILE.font._key(fontoptions)] + if not bsratio then + local face = SILE.font.cache(fontoptions, SILE.shaper.getFace) + local m = metrics.get_typographic_extents(face) + bsratio = m.descender / (m.ascender + m.descender) + bsratiocache[SILE.font._key(fontoptions)] = bsratio + end + return bsratio +end + --- @export return { getFileExtension = getFileExtension, nbspFilter = nbspFilter, - hasClass = hasClass + hasClass = hasClass, + hasRawHandler = hasRawHandler, + hasEmbedHandler = hasEmbedHandler, + computeBaselineRatio = computeBaselineRatio, }