Overview
Comment: | add blockquote support for html, subdocument mechanisms, mode to generate epub-compatible XHTML5; various fixes and improvements |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA3-256: |
35ea3c5797d672b430bd3a8ce9ee9913 |
User & Date: | lexi on 2022-09-05 18:49:58 |
Other Links: | manifest | tags |
Context
2022-09-05
| ||
20:15 | cleanup check-in: e551f71321 user: lexi tags: trunk | |
18:49 | add blockquote support for html, subdocument mechanisms, mode to generate epub-compatible XHTML5; various fixes and improvements check-in: 35ea3c5797 user: lexi tags: trunk | |
2022-04-17
| ||
21:16 | various updates fuck man idr what all i did check-in: c0bdfa46df user: lexi tags: trunk | |
Changes
Modified cortav.ct from [2cc3a528df] to [03a705bddd].
25 25 * [*lists] use a different syntax from markdown. you can start a line with a [`*] to create an unordered list, or [`:] to create an ordered list; indentation doesn't matter. if you want to nest list items, instead of putting two spaces before the child item, you just add another star or colon. and of course, you can nest lists of different kinds within one another. 26 26 * [*horizontal rules] use roughly the same syntax: three or more hyphens on a line of their own ([`\---]). underlines also work ([`___], [`-_-], [`__-__-__] etc), as do horizontal unicode box drawing characters ([`─ ━ ┈] etc). 27 27 * some markdown implementations support [*tables]. cortav does too, using a very simple notation similar to the usual notation used in markdown. a key difference, however, is that cortav table cells can contain any formatting a paragraph can. 28 28 * [*underlines] are supported by some markdown implementations. in cortav, you can apply them with the notation [`\[_my underlined text\]] -- please just use them sparingly when you render to HTML! 29 29 * [*strikethrough] is supported by some extended versions of markdown. cortav uses the notation [`\[~my deleted text\]], with the intended semantics of text that is being removed by some revision of a document. (you can also denote text that is being [!added] by using a plus sign instead of a tilde) 30 30 * [*images] are a bit more complicated, but much more versatile. see the section on [>rsrc resources] for an explanation. 31 31 * [*smart quotes] and [*em dashes] are inserted automatically, just as in markdown, provided you have the [>tsmog transmogrify] extension available. (it is part of the reference implementation and defined by the spec, but not required.) in fact, you can insert longer dashes than em dashes just by increasing the number of hyphens. the reference implementation's transmogrifier also translates ascii arrows like [`\-->] into their unicode equivalents ([`→]). 32 -* [*literals] (also known as [*code text]) can be inserted with the [`\[`int main(void);] syntax. note however that literals are not protected from the transmogrifier, and are parsed like any other span, which may cause problems if the source code you're quoting makes use of such forbidden runes. in this case, you'll want to wrap the code span in a raw span. the syntax for this is [`\[`[\\int main(void);\]]], but since this is a bit unwieldy it can also be abbreviated as [`\[`\\int main(void);\]]. 32 +* [*literals] (also known as [*code text]) can be inserted with the [`\[`int main(void);\]] syntax. note however that literals are not protected from the transmogrifier, and are parsed like any other span, which may cause problems if the source code you're quoting makes use of such forbidden runes. in this case, you'll want to wrap the code span in a raw span. the syntax for this is [`\[`[\\int main(void);\]]], but since this is a bit unwieldy it can also be abbreviated as [`\[`\\int main(void);\]]. 33 33 34 34 of course, this is only a small taste of what cortav can do, not even touching on key features like macros, footnotes, or equation formatting. read the sections on [>onblocks blocks] and [>onspans spans] for all the gory details. 35 35 36 36 ## encoding 37 37 a cortav document is made up of a sequence of codepoints. UTF-8 must be supported, but other encodings (such as UTF-32 or C6B) may be supported as well. lines will be derived by splitting the codepoints at the linefeed character or equivalent. note that unearthly encodings like C6B or EBCDIC will need to select their own control sequences. 38 38 39 39 ## file type ................................................................................ 76 76 77 77 ##onblocks structure 78 78 cortav is based on an HTML-like block model, where a document consists of sections, which are made up of blocks, which may contain a sequence of spans. flows of text are automatically conjoined into spans, and blocks are separated by one or more newlines. this means that, unlike in markdown, a single logical paragraph [*cannot] span multiple ASCII lines. the primary purpose of this was to ensure ease of parsing, but also, both markdown and cortav are supposed to be readable from within a plain text editor. this is the 21st century. every reasonable text editor supports soft word wrap, and if yours doesn't, that's entirely your own damn fault. hard-wrapping lines is incredibly user-hostile, especially to users on mobile devices with small screens. cortav does not allow it. 79 79 80 80 the first character(s) of every line (the "control sequence") indicates the role of that line. if no control sequence is recognized, the line is treated as a paragraph. the currently supported control sequences are listed below. some control sequences have alternate forms, in order to support modern, readable unicode characters as well as plain ascii text. 81 81 82 82 * [*paragraphs] ([`.] [` ¶] [`❡]): a paragraph is a simple block of text. the period control sequence is only necessary if the paragraph text starts with text that would be interpreted as a control sequence otherwise 83 -* newlines [` \\]: inserts a line break into previous paragraph and attaches the following text. mostly useful for poetry or lyrics 83 +* [*newlines] [` \\]: inserts a line break into previous paragraph and attaches the following text. mostly useful for poetry or lyrics 84 84 * [*section starts] [`#] [`§]: starts a new section. all sections have an associated depth, determined by the number of sequence repetitions (e.g. "###" indicates depth three). sections may have headers and IDs; both are optional. IDs, if present, are a sequence of raw-text immediately following the hash marks. if the line has one or more space character followed by styled-text, a header will be attached. the character immediately following the hashes can specify a particular type of section. e.g.: 85 85 ** [`#] is a simple section break. 86 86 ** [`#anchor] opens a new section with the ID [`anchor]. 87 87 ** [`# header] opens a new section with the title "header". 88 88 ** [`#anchor header] opens a new section with both the ID [`anchor] and the title "header". 89 89 * [*nonprinting sections] ([`^]): sometimes, you'll want to create a namespace without actually adding a visible new section to the document. you can achieve this by creating a [!nonprinting section] and defining resources within it. nonprinting sections can also be used to store comments, notes, to-dos, or other meta-information that is useful to have in the source file without it becoming a part of the output. nonprinting sections can be used for a sort of "literate markup," where resource and reference definitions can intermingle with human-readable narrative about those definitions. 90 -* [*resource] ([`@]): defines a [!resource]. a resource is a file or object that exists outside of the document but which will are to be included in the document somehow. common examples of resources include images, videos, iframes, or headers/footers. see [>rsrc resources] for more information. 90 +* [*resource] ([`@]): defines a [!resource]. a resource is a file or object that is to be embedded in the document somehow. common examples of resources include images, videos, iframes, or headers/footers. resources can be defined inline, or reference external objects. see [>rsrc resources] for more information. 91 91 * [*lists] ([`*] [`:]): these are like paragraph nodes, but list nodes that occur next to each other will be arranged so as to show they compose a sequence. depth is determined by the number of stars/colons. like headers, a list entry may have an ID that can be used to refer back to it; it is indicated in the same way. if colons are used, this indicates that the order of the items is signifiant. [`:]-lists and [`*]-lists may be intermixed; however, note than only the last character in the sequence actually controls the type. a blank line terminates the current list. 92 92 * [*directives] ([`%]): a directive issues a hint to the renderer in the form of an arbitrary string. directives are normally ignored if they are not supported, but you may cause a warning to be emitted where the directive is not supported with [`%!] or mark a directive critical with [`%!!] so that rendering will entirely fail if it cannot be obeyed. 93 93 * [*comments] ([`%%]): a comment is a line of text that is simply ignored by the renderer. 94 94 * [*asides] ([`!]): indicates text that diverges from the narrative, and can be skipped without interrupting it. think of it like block-level parentheses. asides which follow one another are merged as paragraphs of the same aside, usually represented as a sort of box. if the first line of an aside contains a colon, the stretch of styled-text from the beginning to the aside to the colon will be treated as a "type heading," e.g. "Warning:" 95 95 * [*code] ([`~~~]): a line beginning with ~~~ begins or terminates a block of code. code blocks are by default not parsed, but parsing can be activated by preceding the code block with an [`%[*expand]] directive. the opening line should look like one of the below 96 96 ** [`~~~] 97 97 ** [`~~~ language] (markdown-style shorthand syntax) ................................................................................ 124 124 ##onspans styled text 125 125 most blocks contain a sequence of spans. these spans are produced by interpreting a stream of [*styled-text] following the control sequence. styled-text is a sequence of codepoints potentially interspersed with escapes. an escape is formed by an open square bracket [`\[] followed by a [*span control sequence], and arguments for that sequence like more styled-text. escapes can be nested. 126 126 127 127 * strong {obj *|styled-text}: causes its text to stand out from the narrative, generally rendered as bold or a brighter color. 128 128 * emphatic {obj !|styled-text}: indicates that its text should be spoken with emphasis, generally rendered as italics 129 129 * custom style {span .|id|[$styled-text]}: applies a specially defined font style. for example, if you have defined [`caution] to mean "demibold italic underline", cortav will try to apply the proper weight and styling within the constraints of the current font to the span [$styled-text]. see the [>fonts-sty fonts section] for more information about this mechanism. 130 130 * literal {obj `|styled-text}: indicates that its text is a reference to a literal sequence of characters or other discrete token. generally rendered in monospace 131 -* variable {obj $|styled-text}: indicates that its text is a stand-in that will be replaced with what it names. generally rendered in italic monospace, ideally of a different color 131 +* variable {obj $|styled-text}: indicates to the reader that its text is a placeholder, rather than a literal representation. generally rendered in italic monospace, ideally of a different color 132 132 * underline {obj _|styled-text}: underlines the text. use sparingly on text intended for webpages -- underlined text [!is] distinct from links, but underlining non-links is still a violation of convention. 133 133 * strikeout {obj ~|styled-text}: indicates that its text should be struck through or otherwise indicated for deletion 134 134 * insertion {obj +|styled-text}: indicates that its text should be indicated as a new addition to the text body. 135 135 ** consider using a macro definition [`\edit: [~[#1]][+[#2]]] to save typing if you are doing editing work 136 136 * link \[>[!ref] [!styled-text]\]: produces a hyperlink or cross-reference denoted by [$ref], which may be either a URL specified with a reference or the name of an object like an image or section elsewhere in the document. the unicode characters [`→] and [`🔗] can also be used instead of [`>] to denote a link. 137 -* footnote {span ^|ref|[$styled-text]}: annotates the text with a defined footnote. in interactive output media [`\[^citations.qtheo Quantum Theosophy: A Neophyte's Catechism]] will insert a link with the text [`Quantum Theosophy: A Neophyte's Catechism] that, when clicked, causes a footnote to pop up on the screen. for static output media, the text will simply have a superscript integer after it denoting where the footnote is to be found. 137 +* footnote {span ^|ref|[$styled-text]}: annotates the text with a defined footnote. in interactive output media [`\[^citations.qtheo Quantum Theosophy: A Neophyte's Catechism\]] will insert a link with the text [`Quantum Theosophy: A Neophyte's Catechism] that, when clicked, causes a footnote to pop up on the screen. for static output media, the text will simply have a superscript integer after it denoting where the footnote is to be found. 138 138 * superscript {obj '|[$styled-text]} 139 139 * subscript {obj ,|[$styled-text]} 140 140 * raw {obj \\ |[$raw-text]}: causes all characters within to be interpreted literally, without expansion. the only special characters are square brackets, which must have a matching closing bracket, and backslashes. 141 141 * raw literal \[$\\[!raw-text]\]: shorthand for [\[$[\…]]] 142 142 * macro [` \{[!name] [!arguments]}]: invokes a [>ex.mac macro], specified with a reference 143 143 * argument {obj #|var}: in macros only, inserts the [$var]-th argument. otherwise, inserts a context variable provided by the renderer. 144 144 * raw argument {obj ##|var}: like above, but does not evaluate [$var]. ................................................................................ 648 648 the interpreter should provide a [`cortav] table with the objects: 649 649 * [`ctx]: contains context variables 650 650 651 651 used files should return a table with the following members 652 652 * [`macros]: an array of functions that return strings or arrays of strings when invoked. these will be injected into the global macro namespace. 653 653 654 654 ###ts ts 655 -the [*ts] extension allows documents to be marked up for basic classification constraints and automatically redacted. if you are seriously relying on ts for confidentiality, make damn sure you start the file with [$%[*requires] ts], so that rendering will fail with an error if the extension isn't supported. 655 +the [*ts] extension allows documents to be marked up for basic classification constraints and automatically redacted. if you are seriously relying on [`ts] for confidentiality, make damn sure you start the file with [$%[*requires] ts], so that rendering will fail with an error if the extension isn't supported. 656 656 657 -ts enables the directives: 658 -* [`%[*ts] class [$scope level] ([$styled-text])]: indicates a classification level for either the while document (scope [$doc]) or the next section (scope [$sec]). if the ts level is below [$level], the section will be redacted or rendering will fail with an error, as appropriate. if styled-text is included, this will be treated as the name of the classification level. 657 +[`ts] currently has no support for misinformation. 658 + 659 +[`ts] enables the directives: 660 +* [`%[*ts] class [$scope level] ([$styled-text])]: indicates a classification level for either the whole document (scope [$doc]) or the next section (scope [$sec]). if the ts level is below [$level], the section will be redacted or rendering will fail with an error, as appropriate. if styled-text is included, this will be treated as the name of the classification level. 659 661 * [`%[*ts] word [$scope word] ([$styled-text])]: indicates a codeword clearance that must be present for the text to render. if styled-text is present, this will be used to render the name of the codeword instead of [$word]. 660 662 * [`%[*when] ts level [$level]] 661 663 * [`%[*when] ts word [$word]] 662 664 663 -ts enables the spans: 664 -* [`\[🔒#[!level] [$styled-text]\]]: redacts the span if the security level is below that specified. 665 -* [`\[🔒.[!word] [$styled-text]\]]: redacts the span if the specified codeword clearance is not enabled. 665 +[`ts] enables the spans: 666 +* [` \[🔒#[$level] [$styled-text]\]]: redacts the span if the security level is below that specified. 667 +* [` \[🔒.[$word] [$styled-text]\]]: redacts the span if the specified codeword clearance is not enabled. 666 668 (the padlock emoji is shorthand for [`%[*ts]].) 667 669 668 -ts redacts spans securely; that is, they are simply replaced with an indicator that they have been redacted, without visually leaking the length of the redacted text. 670 +[`ts] redacts spans securely; that is, they are simply replaced with an indicator that they have been redacted, without visually leaking the length of the redacted text. redacted sections are simply omitted. 669 671 670 672 ~~~#ts-example example [cortav] ~~~ 671 673 %ts word doc sorrowful-pines SORROWFUL PINES 672 674 673 675 # intercept R1440 TCT S3 674 676 this communication between the ambassador of [*POLITY DOORMAT CRIMSON] "Socialist League world Glory" and an unknown noble of [*POLITY ROSE] "the Empire of a Thousand Suns" was intercepted by [*SYSTEM SUPINE WARBLE]. 675 677 ................................................................................ 829 831 830 832 corran: http://ʞ.cc/fic/spirals/society 831 833 tengwar: https://en.wikipedia.org/wiki/Tengwar 832 834 833 835 ###refimpl-switches switches 834 836 [`cortav.lua] offers various switches to control its behavior. 835 837 + long + short + function + 836 -| [`\--out [!file]] :|:[`-o]:| sets the output file (default stdout) | 837 -| [`\--log [!file]] :|:[`-l]:| sets the log file (default stderr) | 838 -| [`\--define [!var] [!val]] :|:[`-d]:| sets the context variable [$var] to [$val] | 839 -| [`\--mode-set [!mode]] :|:[`-y]:| activates the [>refimpl-mode mode] with ID [!mode] 840 -| [`\--mode-clear [!mode]] :|:[`-n]:| disables the mode with ID [!mode] | 841 -| [`\--mode [!id] [!val]] :|:[`-m]:| configures mode [!id] with the value [!val] | 842 -| [`\--mode-set-weak [!mode]] :|:[`-Y]:| activates the [>refimpl-mode mode] with ID [!mode] if the source file does not specify otherwise 843 -| [`\--mode-clear-weak [!mode]] :|:[`-N]:| disables the mode with ID [$mode] if the source file does not specify otherwise 844 -| [`\--mode-weak [!id] [!val]] :|:[`-M]:| configures mode [$id] with the value [$val] if the source file does not specify otherwise 845 -| [`\--help] :|:[`-h]:| display online help | 846 -| [`\--version] :|:[`-V]:| display the interpreter version | 838 +| [`--out [$file]] :|:[`-o]:| sets the output file (default stdout) | 839 +| [`--log [$file]] :|:[`-l]:| sets the log file (default stderr) | 840 +| [`--define [$var] [$val]] :|:[`-d]:| sets the context variable [$var] to [$val] | 841 +| [`--mode-set [$mode]] :|:[`-y]:| activates the [>refimpl-mode mode] with ID [!mode] 842 +| [`--mode-clear [$mode]] :|:[`-n]:| disables the mode with ID [!mode] | 843 +| [`--mode [$id] [$val]] :|:[`-m]:| configures mode [$id] with the value [$val] | 844 +| [`--mode-set-weak [$mode]] :|:[`-Y]:| activates the [>refimpl-mode mode] with ID [$mode] if the source file does not specify otherwise 845 +| [`--mode-clear-weak [$mode]] :|:[`-N]:| disables the mode with ID [$mode] if the source file does not specify otherwise 846 +| [`--mode-weak [$id] [$val]] :|:[`-M]:| configures mode [$id] with the value [$val] if the source file does not specify otherwise 847 +| [`--help] :|:[`-h]:| display online help | 848 +| [`--version] :|:[`-V]:| display the interpreter version | 847 849 848 850 ###refimpl-mode modes 849 851 most of [`cortav.lua]'s implementation-specific behavior is controlled by use of [!modes]. these are namespaced options which may have a boolean, string, or numeric value. boolean modes are set with the [`-y] [`-n] flags; other modes use the [`-m] flags. 850 852 851 853 most modes are defined by the renderer backend. the following modes affect the behavior of the frontend: 852 854 853 855 + ID + type + effect ................................................................................ 863 865 ####refimpl-rend-html-modes modes 864 866 [`html] supports the following modes: 865 867 866 868 * string (css length) [`html:width] sets a maximum width for the body content in order to make the page more readable on large displays 867 869 * number [`html:accent] applies an accent hue to the generated webpage. the hue is specified in degrees, e.g. [$-m html:accent 0] applies a red accent. 868 870 * flag [`html:dark-on-light] uses dark-on-light styling, instead of the default light-on-dark 869 871 * flag [`html:fossil-uv] outputs an HTML snippet suitable for use with the Fossil VCS webserver. this is intended to be used with the unversioned content mechanism to host rendered versions of documentation written in cortav that's stored in a Fossil repository. 872 +* flag [`html:xhtml] generates syntactically-`valid' XHTML5 873 +* flag [`html:epub] generates XHTML5 suitable for use in an EPUB3 archive 870 874 * number [`html:hue-spread] generates a color palette based on the supplied accent hue. the larger the value, the more the other colors diverge from the accent hue. 871 875 * string [`html:link-css] generates a document linking to the named stylesheet 872 876 * flag [`html:gen-styles] embeds appropriate CSS styles in the document (default on) 873 -* flag [`html:snippet] produces a snippet of html instead of an entire web page. note that proper CSS scoping is not yet implemented (and can't be implemented hygienically since [$scoped] was removed 😢) 877 +* flag [`html:snippet] produces a snippet of html instead of an entire web page. note that proper CSS scoping is not yet implemented (and can't be implemented hygienically since [`scoped] was removed 😢) 874 878 * string [`html:title] specifies the webpage titlebar contents (normally autodetected from the document based on headings or directives) 875 879 * string [`html:font] specifies the default font to use when rendering as a CSS font specification (e.g. [`-m html:font 'Alegreya, Junicode, Georgia, "Times New Roman"]) 876 880 877 881 ~~~ 878 882 $ cortav readme.ct --out readme.html \ 879 883 -m render:format html \ 880 884 -m html:width 40em \ ................................................................................ 1018 1022 currently all internal colors are expressed and stored as HSL, and we're using code borrowed from my Minetest mod [`[>sorcrep sorcery]] & written by glowpelt for converting that into RGB for non-HTML outputs like groff. probably what should be done is to switch to LCH internally, accept both HSL and LCH input, and "downres" to the best representation each format can support. that's probably going to have to wait until someone who knows a bit more about color theory and math than me can do it (paging Lea Verou) 1019 1023 1020 1024 sorcrep: https://c.hale.su/sorcery 1021 1025 1022 1026 ### intent files 1023 1027 there's currently no standard way to describe the intent and desired formatting of a document besides placing pragmata in the source file itself. this is extremely suboptimal, as when generating collections of documents, it's ideal to be able to keep all formatting information in one place. users should also be able to specify their own styling overrides that describe the way they prefer to read [`cortav] files, especially for uses like gemini or gopher integration. 1024 1028 1025 -at some point soon [`cortav] needs to address this by adding intent files that can be activated from outside the source file, such as with a command line flag or a configuration file setting. these will probably consist of lines that are interpreted as pragmata. in addition to the standard intent format however, individual implementations should feel free to provide their own ways to provide intent metadata; e.g. the reference implementation, which has a lua interpreter available, should be able to take a lua script that runs after the parse stage and generates . this will be particularly useful for the end-user who wishes to specify a particular format she likes reading her files in without forcing that format on everyone she sends the compiled document to, as it will be able to interrogate the document and make intelligent decisions about what pragmata to apply. 1029 +at some point soon [`cortav] needs to address this by adding intent files that can be activated from outside the source file, such as with a command line flag or a configuration file setting. these will probably consist of lines that are interpreted as pragmata. in addition to the standard intent format however, individual implementations should feel free to provide their own ways to provide intent metadata; e.g. the reference implementation, which has a lua interpreter available, should be able to take a lua script that runs after the parse stage and makes arbitrary alterations to the AST. this will be particularly useful for the end-user who wishes to specify a particular format she likes reading her files in without forcing that format on everyone she sends the compiled document to, as it will be able to interrogate the document and make intelligent decisions about what pragmata to apply. 1026 1030 1027 1031 intent files should also be able to define [>rsrc resources], [>ctxvar context variables], and macros.
Modified cortav.lua from [c52f4282f5] to [18c311a386].
113 113 ct.exns.tx(msg, self.src.file, self.line or 0, ...):throw() 114 114 end; 115 115 insert = function(self, block) 116 116 block.origin = self:clone() 117 117 table.insert(self.sec.blocks,block) 118 118 return block 119 119 end; 120 + init = function(ctx, doc, src) 121 + ctx.line = 0 122 + ctx.doc = doc 123 + ctx.doc.src = src 124 + ctx.sec = doc:mksec() -- toplevel section 125 + ctx.sec.origin = ctx:clone() 126 + end; 120 127 ref = function(self,id) 121 128 if not id:find'%.' then 122 129 local rid = self.sec.refs[id] 123 130 if self.sec.refs[id] then 124 131 return self.sec.refs[id], id, self.sec 125 132 else self:fail("no such ref %s in current section", id or '') end 126 133 else ................................................................................ 184 191 end 185 192 return ct.ext.loaded[name].default 186 193 end; 187 194 context_var = function(self, var, ctx, test) 188 195 local fail = function(...) 189 196 if test then return false end 190 197 ctx:fail(...) 198 + end 199 + local scanParents = function(k) 200 + for k,p in pairs(self.parents) do 201 + local v = p:context_var(k, ctx, true) 202 + if v ~= false then return v end 203 + end 191 204 end 192 205 if startswith(var, 'cortav.') then 193 206 local v = var:sub(8) 194 207 if v == 'page' then 195 208 if ctx.page then return tostring(ctx.page) 196 209 else return '(unpaged)' end 197 210 elseif v == 'renderer' then ................................................................................ 217 230 local val = os.getenv(v) 218 231 if not val then 219 232 return fail('undefined environment variable %s', v) 220 233 end 221 234 elseif self.stage.kind == 'render' and startswith(var, self.stage.format..'.') then 222 235 -- TODO query the renderer somehow 223 236 return fail('renderer %s does not implement variable %s', self.stage.format, var) 237 + elseif startswith(var, 'super.') then 238 + local sp = scanParents(var:sub(8)) 239 + if sp == nil then 240 + if test then return false else return '' end 241 + else 242 + return sp 243 + end 224 244 elseif self.vars[var] then 225 245 return self.vars[var] 226 246 else 247 + local sp = scanParents(var) 248 + if sp then return sp end 227 249 if test then return false end 228 250 return '' -- is this desirable behavior? 229 251 end 230 252 end; 231 253 job = function(self, name, pred, ...) -- convenience func 232 254 return self.docjob:fork(name, pred, ...) 233 - end 255 + end; 256 + sub = function(self, ctx) 257 + -- convenience function for single-inheritance structure 258 + -- sets up a doc/ctx pair for a subdocument embedded in the source 259 + -- of a gretaer document, pointing subdoc props to global tables/values 260 + local newdoc = ct.doc.mk(self) 261 + newdoc.meta = self.meta 262 + newdoc.ext = self.ext 263 + newdoc.enc = self.enc 264 + newdoc.stage = self.stage 265 + -- vars are handled through proper recursion across all parents and 266 + -- are intentionally excluded here; subdocs can have their own vars 267 + -- without losing access to parent vars 268 + local nctx = ctx:clone() 269 + nctx:init(newdoc, ctx.src) 270 + nctx.line = ctx.line 271 + return newdoc, nctx 272 + end; 234 273 }; 235 - mk = function() return { 274 + mk = function(...) return { 236 275 sections = {}; 237 276 secorder = {}; 238 277 embed = {}; 239 278 meta = {}; 240 279 vars = {}; 280 + parents = {...}; 241 281 ext = { 242 282 inhibit = {}; 243 283 need = {}; 244 284 use = {}; 245 285 }; 246 286 enc = ss.str.enc.utf8; 247 287 } end; ................................................................................ 594 634 cmd = cmd; 595 635 args = args; 596 636 crit = crit; 597 637 failthru = failthru; 598 638 spans = spans; 599 639 } 600 640 end 641 + end 642 + local function rawcode(s, c) -- raw 643 + local o = c:clone(); 644 + local str = '' 645 + for c, p in ss.str.each(c.doc.enc, s) do 646 + local q = p:esc() 647 + if q then 648 + str = str .. q 649 + p.next.byte = p.next.byte + #q 650 + else 651 + str = str .. c 652 + end 653 + end 654 + return { 655 + kind = 'format'; 656 + style = 'literal'; 657 + spans = {{ 658 + kind = 'raw'; 659 + spans = {str}; 660 + origin = o; 661 + }}; 662 + origin = o; 663 + } 601 664 end 602 665 ct.spanctls = { 603 666 {seq = '!', parse = formatter 'emph'}; 604 667 {seq = '*', parse = formatter 'strong'}; 605 668 {seq = '~', parse = formatter 'strike'}; 606 669 {seq = '+', parse = formatter 'insert'}; 670 + {seq = '`\\', parse = rawcode}; 671 + {seq = '\\\\', parse = rawcode}; 607 672 {seq = '\\', parse = function(s, c) -- raw 608 673 return { 609 674 kind = 'raw'; 610 675 spans = {s}; 611 676 origin = c:clone(); 612 677 } 613 - end}; 614 - {seq = '`\\', parse = function(s, c) -- raw 615 - local o = c:clone(); 616 - local str = '' 617 - for c, p in ss.str.each(c.doc.enc, s) do 618 - local q = p:esc() 619 - if q then 620 - str = str .. q 621 - p.next.byte = p.next.byte + #q 622 - else 623 - str = str .. c 624 - end 625 - end 626 - return { 627 - kind = 'format'; 628 - style = 'literal'; 629 - spans = {{ 630 - kind = 'raw'; 631 - spans = {str}; 632 - origin = o; 633 - }}; 634 - origin = o; 635 - } 636 678 end}; 637 679 {seq = '`', parse = formatter 'literal'}; 638 680 {seq = '$', parse = formatter 'variable'}; 639 681 {seq = '^', parse = function(s,c) --footnotes 640 682 local r, t = s:match '^([^%s]+)%s*(.-)$' 641 683 return { 642 684 kind = 'footnote'; ................................................................................ 984 1026 local sp = ct.parse_span(txt, c) 985 1027 c.doc.docjob:hook('meddle_span', sp, last) 986 1028 table.insert(last.lines, sp) 987 1029 j:hook('block_aside_attach', c, last, sp, l) 988 1030 j:hook('block_aside_line_insert', c, last, sp, l) 989 1031 end 990 1032 end}; 991 - {pred = function(s,c) return s:match'^[*:]' end, fn = blockwrap(function(l,c) -- list 1033 + {pred = function(s,c) return s:match'^[*:]' end, 1034 + fn = blockwrap(function(l,c) -- list 992 1035 local stars = l:match '^([*:]+)' 993 1036 local depth = utf8.len(stars) 994 1037 local id, txt = l:sub(#stars+1):match '^(.-)%s*(.-)$' 995 1038 local ordered = stars:sub(#stars) == ':' 996 1039 if id == '' then id = nil end 997 1040 return { 998 1041 kind = 'list-item'; ................................................................................ 1073 1116 c:fail('extension %s does not support critical directive %s', cmd, topcmd) 1074 1117 end 1075 1118 end 1076 1119 elseif crit == '!' then 1077 1120 c:fail('critical directive %s not supported',cmd) 1078 1121 end 1079 1122 end;}; 1123 + {pred = function(s) return s:match '^(>+)([^%s]*)%s*(.*)$' end, 1124 + fn = function(l,c,j,d) 1125 + local lvl,id,txt = l:match '^(>+)([^%s]*)%s*(.*)$' 1126 + lvl = utf8.len(lvl) 1127 + local last = d[#d] 1128 + local node 1129 + local ctx 1130 + if last and last.kind == 'quote' and (id == nil or id == '' or id == last.id) then 1131 + node = last 1132 + ctx = node.ctx 1133 + ctx.line = c.line -- is this enough?? 1134 + else 1135 + local doc 1136 + doc, ctx = c.doc:sub(c) 1137 + node = { kind = 'quote', doc = doc, ctx = ctx, id = id } 1138 + j:hook('block_insert', c, node, l) 1139 + table.insert(d, node) 1140 + end 1141 + 1142 + ct.parse_line(txt, ctx, ctx.sec.blocks) 1143 + end}; 1080 1144 {seq = '~~~', fn = blockwrap(function(l,c,j) 1081 1145 local extract = function(ptn, str) 1082 1146 local start, stop = str:find(ptn) 1083 1147 if not start then return nil, str end 1084 1148 local ex = str:sub(start,stop) 1085 1149 local n = str:sub(1,start-1) .. str:sub(stop+1) 1086 1150 return ex, n ................................................................................ 1167 1231 if ctx.mode.expand 1168 1232 then newline = ct.parse_span(l, ctx) 1169 1233 else newline = {l} 1170 1234 end 1171 1235 table.insert(ctx.mode.listing.lines, newline) 1172 1236 job:hook('block_listing_newline',ctx,ctx.mode.listing,newline) 1173 1237 end 1174 - else 1238 + elseif ctx.mode.kind == 'quote' then 1239 + else 1175 1240 local mf = job:proc('modes', ctx.mode.kind) 1176 1241 if not mf then 1177 1242 ctx:fail('unimplemented syntax mode %s', ctx.mode.kind) 1178 1243 end 1179 1244 mf(job, ctx, l, dest) --NOTE: you are responsible for triggering the appropriate hooks if you insert anything! 1180 1245 end 1181 1246 else ................................................................................ 1190 1255 end 1191 1256 return false 1192 1257 end 1193 1258 1194 1259 if not tryseqs(ct.ctlseqs) then 1195 1260 local found = false 1196 1261 1197 - for eb, ext, state in job:each('blocks') do 1262 + for eb, ext, state in job:each 'blocks' do 1198 1263 if tryseqs(eb, state) then found = true break end 1199 1264 end 1200 1265 1201 1266 if not found then 1202 1267 ctx:fail 'incomprehensible input line' 1203 1268 end 1204 1269 end ................................................................................ 1214 1279 end 1215 1280 1216 1281 function ct.parse(file, src, mode, setup) 1217 1282 -- this object is threaded down through the parse tree 1218 1283 -- and copied to store information like the origin of the 1219 1284 -- element in the source code 1220 1285 local ctx = ct.ctx.mk(src) 1221 - ctx.line = 0 1222 - ctx.doc = ct.doc.mk() 1223 - ctx.doc.src = src 1224 - ctx.sec = ctx.doc:mksec() -- toplevel section 1225 - ctx.sec.origin = ctx:clone() 1286 + ctx:init(ct.doc.mk(), src) 1226 1287 ctx.lang = mode['meta:lang'] 1227 1288 if mode['parse:enc'] then 1228 1289 local e = ss.str.enc[mode['parse:enc']] 1229 1290 if not e then 1230 1291 ct.exns.enc('requested encoding not supported',mode['parse:enc']):throw() 1231 1292 end 1232 1293 ctx.doc.enc = e
Modified desk/cortav.xml from [d0eba8f9ef] to [bda20d41cd].
109 109 110 110 <context name='span-del' attribute='Deleted Text' lineEndContext='#pop'> 111 111 <IncludeRules context='span'/> 112 112 </context> 113 113 114 114 <context name='span-cue' attribute='Span Cue' lineEndContext='#pop' fallthroughContext="error"> 115 115 <StringDetect attribute='Span Cue' String='`\' context='#pop!flat-span' /> 116 + <StringDetect attribute='Span Cue' String='\\' context='#pop!flat-span' /> 116 117 117 118 <DetectChar attribute='Span Cue' char='!' context='#pop!span-emph' /> 118 119 <DetectChar attribute='Span Cue' char='*' context='#pop!span-strong' /> 119 120 <DetectChar attribute='Span Cue' char='~' context='#pop!span-del' /> 120 121 121 122 <AnyChar attribute='Span Cue' String='`$+🔒' context='#pop!span' /> 122 123 <StringDetect attribute='Span Cue' String='→' context='#pop!ref' /> ................................................................................ 129 130 <DetectChar attribute='Span Cue' char='\' context='#pop!flat-span' /> 130 131 <Detect2Chars attribute='Comment' char='%' char1='%' context='#pop!inline-comment' /> 131 132 <Detect2Chars attribute='Critical Directive Cue' char='%' char1='!' context='#pop!inline-directive' /> 132 133 <DetectChar attribute='Directive Cue' char='%' context='#pop!inline-directive' /> 133 134 </context> 134 135 135 136 <context name='flat-span' attribute='Unstyled Text' lineEndContext='#pop'> 137 + <DetectChar attribute='Unstyled Text' context='flat-span' char='['/> 136 138 <Detect2Chars attribute='Escaped Char' context='#stay' char='\' char1=']'/> 137 139 <DetectChar attribute='Span Delimiter' context='#pop' char=']'/> 138 140 </context> 139 141 140 142 <context name='inline-comment' attribute='Comment' lineEndContext='#pop'> 141 143 <IncludeRules context='flat-span'/> 142 144 </context>
Modified ext/transmogrify.lua from [ffa0ca0a64] to [ad59e4c740].
37 37 ['(SM)'] = '℠'; 38 38 }; 39 39 }; 40 40 } 41 41 42 42 local quotes = { 43 43 [ss.str.enc.utf8] = { 44 - ['en'] = {'“', '”'; '‘', '’'}; 45 - ['de'] = {'„', '“'; '‚', '‘'}; 46 - ['sp'] = {'«', '»'; '‹', '›'}; 47 - ['ja'] = {'「', '」'; '『', '』'}; 48 - ['fr'] = {'« ', ' »'; '‹ ', ' ›'}; 49 - [true] = {'“', '”'; '‘', '’'}; 44 + -- 5 = elision char 45 + ['en'] = {'“', '”'; '‘', '’'; '’'}; 46 + ['de'] = {'„', '“'; '‚', '‘'; '’'}; 47 + ['sp'] = {'«', '»'; '‹', '›'; "’"}; 48 + ['ja'] = {'「', '」'; '『', '』'; "'"}; 49 + ['fr'] = {'« ',' »'; '‹ ',' ›'; "’"}; 50 + [true] = {'“', '”'; '‘', '’'; '’'}; 50 51 }; 51 52 } 52 53 53 54 local function meddle(ctx, t) 54 55 local pts = patterns[ctx.doc.enc] 55 56 if not pts then return t end 56 57 local str = '' ................................................................................ 139 140 version = ss.version {0,1; 'devel'}; 140 141 contributors = {{name='lexi hale', handle='velartrill', mail='lexi@hale.su', homepage='https://hale.su'}}; 141 142 default = true; -- on unless inhibited 142 143 slow = true; 143 144 hook = { 144 145 doc_meddle_ast = function(job) 145 146 for n, sec in pairs(job.doc.secorder) do 146 - if sec.kind=='ordinary' or sec.kind=='blockquote' 147 + if sec.kind=='ordinary' or sec.kind=='quote' 147 148 or sec.kind=='footnote' then 148 149 for i, block in pairs(sec.blocks) do 149 150 if type(block.spans) == 'table' then 150 151 enterspan(block.origin, block.spans) 151 152 elseif type(block.spans) == 'string' then 152 153 block.spans = meddle(block.origin, block.spans) 153 154 end 154 155 end 155 156 end 156 157 end 157 158 end; 158 159 }; 159 160 }
Modified render/html.lua from [39c7338664] to [5903337619].
13 13 function ct.render.html(doc, opts, setup) 14 14 local doctitle = opts['title'] 15 15 local f = string.format 16 16 local getSafeID = ct.tool.namespace() 17 17 18 18 local footnotes = {} 19 19 local footnotecount = 0 20 + 21 + local cdata = function(...) return ... end 22 + if opts.epub then 23 + opts.xhtml = true 24 + end 25 + 26 + if opts.xhtml then 27 + cdata = function(s) 28 + return '<![CDATA[' .. s .. ']]>' 29 + end 30 + end 20 31 21 32 local langsused = {} 22 33 local langpairs = { 23 34 lua = { color = 0x9377ff }; 24 35 terra = { color = 0xff77c8 }; 25 36 c = { name = 'C', color = 0x77ffe8 }; 26 37 html = { color = 0xfff877 }; ................................................................................ 44 55 li { 45 56 padding: 0.1em 0; 46 57 } 47 58 ]]; 48 59 list_ordered = [[]]; 49 60 list_unordered = [[]]; 50 61 footnote = [[ 51 - div.footnote { 62 + aside.footnote { 52 63 font-family: 90%; 53 64 grid-template-columns: 1em 1fr min-content; 54 65 grid-template-rows: 1fr min-content; 55 66 position: fixed; 56 67 padding: 1em; 57 68 background: @tone(0.03); 58 69 margin:auto; 59 70 } 60 71 @media screen { 61 - div.footnote { 72 + aside.footnote { 62 73 display: grid; 63 74 left: 10em; 64 75 right: 10em; 65 76 max-width: calc(@width + 2em); 66 77 max-height: 30vw; 67 78 bottom: 1em; 68 79 border: 1px solid black; 69 80 transform: translateY(200%); 70 81 transition: 0.4s; 71 82 z-index: 100; 72 83 } 73 - div.footnote:target { 84 + aside.footnote:target { 74 85 transform: translateY(0%); 75 86 } 76 87 #cover { 77 88 position: fixed; 78 89 top: 0; 79 90 left: 0; 80 91 height: 100vh; width: 100vw; ................................................................................ 82 93 @tone/0.8(-0.07), 83 94 @tone/0.4(-0.07)); 84 95 opacity: 0%; 85 96 transition: 1s; 86 97 pointer-events: none; 87 98 backdrop-filter: blur(0px); 88 99 } 89 - div.footnote:target ~ #cover { 100 + aside.footnote:target ~ #cover { 90 101 opacity: 100%; 91 102 pointer-events: all; 92 103 backdrop-filter: blur(5px); 93 104 } 94 105 } 95 106 @media print { 96 - div.footnote { 107 + aside.footnote { 97 108 display: grid; 98 109 position: relative; 99 110 } 100 - div.footnote:first-of-type { 111 + aside.footnote:first-of-type { 101 112 border-top: 1px solid black; 102 113 } 103 114 } 104 115 105 - div.footnote > a[href="#0"]{ 116 + aside.footnote > a[href="#0"]{ 106 117 grid-row: 2/3; 107 118 grid-column: 3/4; 108 119 display: block; 109 120 text-align: center; 110 121 padding: 0 0.3em; 111 122 text-decoration: none; 112 123 background: @tone(0.2); ................................................................................ 116 127 font-size: 150%; 117 128 -webkit-user-select: none; 118 129 -ms-user-select: none; 119 130 user-select: none; 120 131 -webkit-user-drag: none; 121 132 user-drag: none; 122 133 } 123 - div.footnote > a[href="#0"]:hover { 134 + aside.footnote > a[href="#0"]:hover { 124 135 background: @tone(0.3); 125 136 color: @tone(2); 126 137 } 127 - div.footnote > a[href="#0"]:active { 138 + aside.footnote > a[href="#0"]:active { 128 139 background: @tone(0.05); 129 140 color: @tone(0.4); 130 141 } 131 142 @media print { 132 - div.footnote > a[href="#0"]{ 143 + aside.footnote > a[href="#0"]{ 133 144 display:none; 134 145 } 135 146 } 136 - div.footnote > div.number { 147 + aside.footnote > div.number { 137 148 text-align:right; 138 149 grid-row: 1/2; 139 150 grid-column: 1/2; 140 151 } 141 - div.footnote > div.text { 152 + aside.footnote > div.text { 142 153 grid-row: 1/2; 143 154 grid-column: 2/4; 144 155 padding-left: 1em; 145 156 overflow-y: auto; 146 157 } 147 - div.footnote > div.text > p:first-child { 158 + aside.footnote > div.text > p:first-child { 148 159 margin-top: 0; 149 160 } 150 161 ]]; 151 162 header = [[ 152 163 body { padding: 0 2.5em !important } 153 164 h1,h2,h3,h4,h5,h6 { border-bottom: 1px solid @tone(0.7); } 154 165 h1 { font-size: 200%; border-bottom-style: double !important; border-bottom-width: 3px !important; margin: 0em -1em; } ................................................................................ 161 172 h1,h2,h3,h4,h5,h6 { 162 173 margin-top: 0; 163 174 margin-bottom: 0; 164 175 } 165 176 :is(h1,h2,h3,h4,h5,h6) + p { 166 177 margin-top: 0.4em; 167 178 } 168 - 169 179 ]]; 170 180 headingAnchors = [[ 171 181 :is(h1,h2,h3,h4,h5,h6) > a[href].anchor { 172 182 text-decoration: none; 173 183 font-size: 1.2em; 174 184 padding: 0.3em; 175 185 opacity: 0%; ................................................................................ 251 261 } 252 262 section > aside p { 253 263 margin: 0; 254 264 margin-top: 0.6em; 255 265 } 256 266 section > aside p:first-child { 257 267 margin: 0; 268 + } 269 + section aside + aside { 270 + margin-top: 0.5em; 258 271 } 259 272 ]]; 260 273 code = [[ 261 274 code { 262 275 display: inline-block; 263 276 background: @tone(-1); 264 277 color: @tone(0.7); ................................................................................ 436 449 doc.stage.job = renderJob; 437 450 438 451 local runhook = function(h, ...) 439 452 return renderJob:hook(h, render_state_handle, ...) 440 453 end 441 454 442 455 local tagproc do 443 - local elt = function(t,attrs) 444 - return f('<%s%s>', t, 445 - attrs and ss.reduce(function(a,b) return a..b end, '', 456 + local html_open = function(t,attrs) 457 + if attrs then 458 + return t .. ss.reduce(function(a,b) return a..b end, '', 446 459 ss.map(function(v,k) 447 460 if v == true 448 461 then return ' '..k 449 462 elseif v then return f(' %s="%s"', k, v) 450 463 end 451 - end, attrs)) or '') 464 + end, attrs)) 465 + else return t end 466 + end 467 + 468 + local elt = function(t,attrs) 469 + if opts.xhtml then 470 + return f('<%s />', html_open(t,attrs)) 471 + end 472 + return f('<%s>', html_open(t,attrs)) 452 473 end 453 474 454 475 tagproc = { 455 476 toTXT = { 456 477 tag = function(t,a,v) return v end; 457 478 elt = function(t,a) return '' end; 458 479 catenate = table.concat; ................................................................................ 468 489 } end; 469 490 470 491 catenate = function(...) return ... end; 471 492 }; 472 493 toHTML = { 473 494 elt = elt; 474 495 tag = function(t,attrs,body) 475 - return f('%s%s</%s>', elt(t,attrs), body, t) 496 + return f('<%s>%s</%s>', html_open(t,attrs), body, t) 476 497 end; 477 498 catenate = table.concat; 478 499 }; 479 500 } 480 501 end 481 502 482 503 local function getBaseRenderers(procs, span_renderers) 483 504 local tag, elt, catenate = procs.tag, procs.elt, procs.catenate 484 505 local htmlDoc = function(title, head, body) 485 - return [[<!doctype html>]] .. tag('html',nil, 506 + local attrs 507 + local header = [[<!doctype html>]] 508 + if opts['epub'] then 509 + -- so cursed 510 + attrs = { 511 + xmlns = "http://www.w3.org/1999/xhtml"; 512 + ['xmlns:epub'] = "http://www.idpf.org/2007/ops"; 513 + } 514 + header = [[<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE html>]] 515 + end 516 + return header .. tag('html',attrs, 486 517 tag('head', nil, 487 - elt('meta',{charset = 'utf-8'}) .. 518 + (opts.epub and '' or elt('meta',{charset = 'utf-8'})) .. 488 519 (title and tag('title', nil, title) or '') .. 489 520 (head or '')) .. 490 521 tag('body', nil, body or '')) 491 522 end 492 523 493 524 local function htmlSpan(spans, block, sec) 494 525 local text = {} ................................................................................ 645 676 elseif d.crit then 646 677 b.origin:fail('critical extension %s unavailable', d.ext) 647 678 elseif d.failthru then 648 679 return htmlSpan(d.spans, b, s) 649 680 end 650 681 end 651 682 function span_renderers.footnote(f,b,s) 652 - addStyle 'footnote' 683 + local linkattr = {} 684 + if opts.epub then 685 + linkattr['epub:type'] = 'noteref' 686 + else 687 + addStyle 'footnote' 688 + end 653 689 local source, sid, ssec = b.origin:ref(f.ref) 654 690 local cnc = getSafeID(ssec) .. ' ' .. sid 655 691 local fn 656 692 if footnotes[cnc] then 657 693 fn = footnotes[cnc] 658 694 else 659 695 footnotecount = footnotecount + 1 660 696 fn = {num = footnotecount, origin = b.origin, fnid=cnc, source = source} 661 697 fn.id = getSafeID(fn) 662 698 footnotes[cnc] = fn 663 699 end 664 - return tag('a', {href='#'..fn.id}, htmlSpan(f.spans) .. 700 + linkattr.href = '#'..fn.id 701 + return tag('a', linkattr, htmlSpan(f.spans) .. 665 702 tag('sup',nil, fn.num)) 666 703 end 667 704 668 705 return span_renderers 669 706 end 707 + 708 + local astproc 670 709 671 710 local function getBlockRenderers(procs, sr) 672 711 local tag, elt, catenate = procs.tag, procs.elt, procs.catenate 673 712 local null = function() return catenate{} end 674 713 675 714 local block_renderers = { 676 715 anchor = function(b,s) ................................................................................ 746 785 return tag('aside', {}, bn) 747 786 end; 748 787 ['break'] = function() -- HACK 749 788 -- lists need to be rewritten to work like asides 750 789 return ''; 751 790 end; 752 791 } 792 + 793 + function block_renderers.quote(b,s) 794 + local ir = {} 795 + local toIR = block_renderers 796 + for i, sec in ipairs(b.doc.secorder) do 797 + local secnodes = {} 798 + for i, bl in ipairs(sec.blocks) do 799 + if toIR[bl.kind] then 800 + table.insert(secnodes, toIR[bl.kind](bl,sec)) 801 + end 802 + end 803 + if next(secnodes) then 804 + if b.doc.secorder[2] then --#secs>1? 805 + -- only wrap in a section if >1 section 806 + table.insert(ir, tag('section', 807 + {id = getSafeID(sec)}, 808 + secnodes)) 809 + else 810 + ir = secnodes 811 + end 812 + end 813 + end 814 + return tag('blockquote', b.id and {id=getSafeID(b)} or {}, catenate(ir)) 815 + end 816 + 753 817 return block_renderers; 754 818 end 755 819 756 820 local function getRenderers(procs) 757 821 local span_renderers = getSpanRenderers(procs) 758 822 local r = getBaseRenderers(procs,span_renderers) 759 823 r.block_renderers = getBlockRenderers(procs, r) 760 824 return r 761 825 end 762 826 763 - local astproc = { 827 + astproc = { 764 828 toHTML = getRenderers(tagproc.toHTML); 765 829 toTXT = getRenderers(tagproc.toTXT); 766 830 toIR = { }; 767 831 } 768 832 astproc.toIR.span_renderers = ss.clone(astproc.toHTML); 769 833 astproc.toIR.block_renderers = getBlockRenderers(tagproc.toIR,astproc.toHTML); 770 834 -- note we use HTML here instead of IR span renderers, because as things ................................................................................ 776 840 render_state_handle.tagproc = tagproc; 777 841 778 842 -- bind to legacy names 779 843 -- yikes this needs to be cleaned up so badly 780 844 local ir = {} 781 845 local dr = astproc.toHTML -- default renderers 782 846 local plainr = astproc.toTXT 783 - local irBlockRdrs = astproc.toIR.block_renderers; 784 847 785 848 render_state_handle.ir = ir; 786 849 787 850 local function renderBlocks(blocks, irs) 788 851 for i, block in ipairs(blocks) do 789 852 local rd 790 - if irBlockRdrs[block.kind] then 791 - rd = irBlockRdrs[block.kind](block,sec) 853 + if astproc.toIR.block_renderers[block.kind] then 854 + rd = astproc.toIR.block_renderers[block.kind](block,sec) 792 855 else 793 856 local rdr = renderJob:proc('render',block.kind,'html') 794 857 if rdr then 795 858 rd = rdr({ 796 859 state = render_state_handle; 797 860 tagproc = tagproc.toIR; 798 861 astproc = astproc.toIR; ................................................................................ 814 877 rd.attrs.lang = rd.src.origin.lang 815 878 end 816 879 table.insert(irs.nodes, rd) 817 880 runhook('ir_section_node_insert', rd, irs, sec) 818 881 end 819 882 end 820 883 end 884 + 821 885 runhook('ir_assemble', ir) 822 886 for i, sec in ipairs(doc.secorder) do 823 887 if doctitle == nil and sec.depth == 1 and sec.heading_node then 824 888 doctitle = astproc.toTXT.htmlSpan(sec.heading_node.spans, sec.heading_node, sec) 825 889 end 826 890 local irs 827 891 if sec.kind == 'ordinary' then 828 892 if #(sec.blocks) > 0 then 829 893 irs = {tag='section',attrs={id = getSafeID(sec)},nodes={}} 830 894 runhook('ir_section_build', irs, sec) 831 895 renderBlocks(sec.blocks, irs) 832 896 end 833 - elseif sec.kind == 'blockquote' then 897 + elseif sec.kind == 'quote' then 834 898 elseif sec.kind == 'listing' then 835 899 elseif sec.kind == 'embed' then 836 900 end 837 901 if irs then table.insert(ir, irs) end 838 902 end 839 903 840 - for _, fn in pairs(footnotes) do 841 - local tag = tagproc.toIR.tag 842 - local body = {nodes={}} 843 - local ftir = {} 844 - for l in fn.source:gmatch('([^\n]*)') do 845 - ct.parse_line(l, fn.origin, ftir) 904 + do local fnsorted = {} 905 + for _, fn in pairs(footnotes) do 906 + fnsorted[fn.num] = fn 907 + end 908 + 909 + for _, fn in ipairs(fnsorted) do 910 + local tag = tagproc.toIR.tag 911 + local body = {nodes={}} 912 + local ftir = {} 913 + for l in fn.source:gmatch('([^\n]*)') do 914 + ct.parse_line(l, fn.origin, ftir) 915 + end 916 + renderBlocks(ftir,body) 917 + local fattr = {id=fn.id} 918 + if opts.epub then 919 + ---UUUUUUGHHH 920 + local npfx = string.format('(%u) ', fn.num) 921 + if next(body.nodes) then 922 + local n = body.nodes[1] 923 + repeat 924 + if n.nodes[1] then 925 + if type(n.nodes[1]) == 'string' then 926 + n.nodes[1] = npfx .. n.nodes[1] 927 + break 928 + end 929 + n = n.nodes[1] 930 + else 931 + n.nodes[1] = {tag='p',attrs={},nodes={npfx}} 932 + break 933 + end 934 + until false 935 + 936 + else 937 + body.nodes[1] = {tag='p',attrs={},nodes={npfx}} 938 + end 939 + fattr['epub:type'] = 'footnote' 940 + else 941 + fattr.class = 'footnote' 942 + end 943 + local note = tag('aside', fattr, opts.epub and body.nodes or { 944 + tag('div',{class='number'}, tostring(fn.num)), 945 + tag('div',{class='text'}, body.nodes), 946 + tag('a',{href='#0'},'⤫') 947 + }) 948 + table.insert(ir, note) 846 949 end 847 - renderBlocks(ftir,body) 848 - local note = tag('div',{class='footnote',id=fn.id}, { 849 - tag('div',{class='number'}, tostring(fn.num)), 850 - tag('div',{class='text'}, body.nodes), 851 - tag('a',{href='#0'},'⤫') 852 - }) 853 - table.insert(ir, note) 854 950 end 855 - if next(footnotes) then 951 + if next(footnotes) and not opts.epub then 856 952 table.insert(ir, tagproc.toIR.tag('div',{id='cover'},'')) 857 953 end 858 954 859 955 -- restructure passes 860 956 runhook('ir_restructure_pre', ir) 861 957 862 958 ---- list insertion pass ................................................................................ 1012 1108 local styles = {} 1013 1109 if opts.width then 1014 1110 table.insert(styles, string.format([[body {padding:0 1em;margin:auto;max-width:%s}]], opts.width)) 1015 1111 end 1016 1112 if opts.accent then 1017 1113 table.insert(styles, string.format(':root {--accent:%s}', opts.accent)) 1018 1114 end 1019 - if opts.accent or (not opts['dark-on-light']) and (not opts['fossil-uv']) then 1115 + if not opts.epub and (opts.accent or (not opts['dark-on-light']) and (not opts['fossil-uv'])) then 1020 1116 addStyle 'accent' 1021 1117 end 1022 1118 1023 1119 1024 1120 for _,k in pairs(stylesNeeded.order) do 1025 1121 if not stylesets[k] then ct.exns.unimpl('styleset %s not implemented (!)', k):throw() end 1026 1122 table.insert(styles, prepcss(stylesets[k])) ................................................................................ 1031 1127 if opts['link-css'] then 1032 1128 local css = opts['link-css'] 1033 1129 if type(css) ~= 'string' then ct.exns.mode('must be a string', 'html:link-css'):throw() end 1034 1130 styletag = styletag .. tagproc.toHTML.elt('link',{rel='stylesheet',type='text/css',href=opts['link-css']}) 1035 1131 end 1036 1132 if next(styles) then 1037 1133 if opts['gen-styles'] then 1038 - styletag = styletag .. tagproc.toHTML.tag('style',{type='text/css'},table.concat(styles)) 1134 + styletag = styletag .. tagproc.toHTML.tag('style',{type='text/css'},cdata(table.concat(styles))) 1039 1135 end 1040 1136 table.insert(head, styletag) 1041 1137 end 1042 1138 1043 1139 if opts['fossil-uv'] then 1044 1140 return tagproc.toHTML.tag('div',{class='fossil-doc',['data-title']=doctitle},styletag .. body) 1045 1141 elseif opts.snippet then 1046 1142 return styletag .. body 1047 1143 else 1048 1144 return dr.htmlDoc(doctitle, next(head) and table.concat(head), body) 1049 1145 end 1050 1146 end