cortav  Check-in [9215a9c850]

Overview
Comment:add beginnings of groff renderer, document more planned syntaxes
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 9215a9c850343f45b4116f6270b8487bdf63be0fea80a875f58c7e2b994931dd
User & Date: lexi on 2021-12-27 13:20:36
Other Links: manifest | tags
Context
2021-12-29
12:19
continue iterating on groff renderer; add headings, basic formatting, beginnings of a footnote and link system, colors check-in: 7ba2577283 user: lexi tags: trunk
2021-12-27
13:20
add beginnings of groff renderer, document more planned syntaxes check-in: 9215a9c850 user: lexi tags: trunk
06:06
add hue spread check-in: 560e69cc54 user: lexi tags: trunk
Changes

Modified cli.lua from [9f14981767] to [ad6ab18d31].

   216    216   		input.stream = file
   217    217   		input.src.file = args[1]
   218    218   	end
   219    219   
   220    220   	return main(input, outp, log, mode, suggestions, vars, extrule)
   221    221   end
   222    222   
   223         -local ok, e = pcall(entry_cli)
   224         --- local ok, e = true, entry_cli()
          223  +-- local ok, e = pcall(entry_cli)
          224  +local ok, e = true, entry_cli()
   225    225   if not ok then
   226    226   	local str = 'translation failure'
   227    227   	if ss.exn.is(e) then
   228    228   		str = e.kind.desc
   229    229   	end
   230    230   	local color = false
   231    231   	if log:seek() == nil then

Modified cortav.ct from [0c4afc6088] to [4ed3bc7476].

   115    115   * [*empty lines] (that is, lines consisting of nothing but whitespace) constitute a [!break], which terminates multiline objects that do not have a dedicated termination sequence, for example lists and asides.
   116    116   
   117    117   ##onspans styled text
   118    118   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.
   119    119   
   120    120   * strong {obj *|styled-text}: causes its text to stand out from the narrative, generally rendered as bold or a brighter color.
   121    121   * emphatic {obj !|styled-text}: indicates that its text should be spoken with emphasis, generally rendered as italics
          122  +* 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.
   122    123   * 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
   123    124   * 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
   124    125   * 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.
   125    126   * strikeout {obj ~|styled-text}: indicates that its text should be struck through or otherwise indicated for deletion
   126    127   * insertion {obj +|styled-text}: indicates that its text should be indicated as a new addition to the text body.
   127    128   ** consider using a macro definition [`\edit: [~[#1]][+[#2]]] to save typing if you are doing editing work
   128    129   * 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.
   129    130   * 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 next [`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.
   130    131   * superscript {obj '|[$styled-text]}
   131    132   * subscript {obj ,|[$styled-text]}
   132         -* raw \[\\[`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
          133  +* 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
   133    134   * raw literal \[$\\[!raw-text]\]: shorthand for [\[$[\…]]]
   134    135   * macro [`\{[!name] [!arguments]\}]: invokes a [>ex.mac macro], specified with a reference
   135    136   * argument {obj #|var}: in macros only, inserts the [$var]-th argument. otherwise, inserts a context variable provided by the renderer.
   136    137   * raw argument {obj ##|var}: like above, but does not evaluate [$var].
   137    138   * term {obj &|name}, {span &|name|[$expansion]}: quotes a defined term with a link to its definition, optionally with a custom expansion of the term (for instance, to expand the first use of an acronym)
   138    139   * inline image {obj &@|name}: shows a small image or other object inline. the unicode character [`🖼] can also be used instead of [`&@].
   139    140   * unicode codepoint {obj U+|hex-integer}: inserts an arbitrary UCS codepoint in the output, specified by [$hex-integer]. lowercase [`u] is also legal.
................................................................................
   237    238   * {def cortav.page} the number of the page currently being rendered
   238    239   * {def cortav.id} the identifier of the renderer
   239    240   * {def cortav.hash} the SHA3 hash of the source file being rendered
   240    241   	def: [*[#1]]:
   241    242   
   242    243   on systems with environment variables, these may be accessed as context variables by prefixing their name with [`env.].
   243    244   
   244         -different renderers may provide context in different ways, such as from command line options or a context file. any predefined variables should carry an appropriate prefix to prevent conflation. 
          245  +different renderers may provide context in different ways, such as from command line options or a context file. any predefined variables should carry an appropriate prefix to prevent conflation.
   245    246   
   246    247   ##fonts fonts
   247    248   for output backends that support font specification, cortav provides a sophisticated font management system by means of the [!font stack].
   248    249   
   249    250   when a document parse begins, the font stack is empty (unless a default font has already been loaded by an intent file).
   250    251   when the font stack is empty, cortav does not include font specifications in its output, and thus will use whatever the default of the various rendering programs is.
   251    252   
................................................................................
   256    257   #^fonts
   257    258   %% we then define each font as a resource
   258    259   @serif
   259    260   	src: auto font name:Alegreya
   260    261   		embed  font/ttf file:project-fonts/alegreya.ttf
   261    262   		link font/woff2 file:/assets/font/alegreya.woff2
   262    263   		auto font name:Times New Roman
          264  +		auto font dit:TR/bold=TRB/italic=TRI/bold,italic=TRBI
   263    265   @sans
   264    266   	src: link font name:Alegreya Sans
   265    267   		link font name:Open Sans
   266    268   		link font name:sans-serif
   267    269   ~~~
   268    270   
   269    271   here we have defined two font families, [`fonts.serif] and [`fonts.sans]. each contains a list of references to fonts which will be tried in order. for example, this could be translated into the following CSS:
................................................................................
   343    345   
   344    346   &$cursive-quote Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident
   345    347   
   346    348   %% without affecting the overall font context. in fact, since 'cursive-quote' creates
   347    349   %% its context using 'dup', it would import all font specifications besides 'body'
   348    350   %% from the environment it is invoked in
   349    351   ~~~
          352  +
          353  +you may have noticed the rather odd bit at the end of our font definition, with the [`dit] URI. the reasons for this are tragic. groff, while delightful, has a thoroughly antiquated understanding of fonts, and doesn't support normal font formats like truetype. groff ships with a limited number of fonts in its own format, identified by obscurantist letter code ([`HBI] is "Helvetica Bold Italic", for instance) and lacking normal metadata. for this reason, you'll have to tell cortav how you want your fonts translated.
          354  +
          355  +it is possible to use modern fonts with groff, but to do that you'll have to convert and install them, which is outside the scope of this document. however, even if you do this, you should specify a fallback font (if possible) so that people rendering your document on other machines still get somewhat sensible output.
          356  +
          357  +the syntax of a [`dit] specification is [`dit:[$regular]], where [$regular] specifies the name of the regular font. this can be followed by any number of variant specifications [`/[$variant]=[$name]], where [$variant] is one of the tags described in the [>fonts-sty custom] style section, and [$name] is the name of a DIT font. so the URI in the example names a font [`T] with bold [`TB], italic [`TI], and bold-italic [`TBI].
          358  +
          359  +the [`groff] backend does do a little magic to make this mess more bearable, however. some of groff's built-in fonts can be accessed by a [`name] URI instead of having to construct them by hand with a [`dit] URI -- the backend hardcodes metadata for these fonts so that documents can render somewhat intellgibly in groff even if the original author did not make special provisions for this. the groff fonts accessible by [`name] are:
          360  +* Times New Roman
          361  +* Helvetica
          362  +* Courier
          363  +* Bookman
          364  +additionally, as a shortcut, if the regular, bold, italic, and bold-italic variants of a DIT font have the predictable pattern of [`[$X]], [`[$X]B], [`[$X]I], [`[$X]BI] (which many do), you can simply write the URI [`dit:[$X]] and cortav will infer the rest. so the example above could be rewritten as [`dit:T] to exactly the same effect.
          365  +
          366  +###fonts-sty custom styles
          367  +sometimes you want to be able to issue more specific formatting instructions than "italic" or "bold". cortav provides a simple [!custom style] mechanism to allow this. a custom style is simply a reference that binds a name to a sequence of space-separated formatting directives. these directives include:
          368  +* [`regular]: applies the regulat form of the font, overriding any previous set styles
          369  +* [`medium]: applies a weight of the font between [`regular] and [`bold], defaulting to [`regular] if one is not available
          370  +* [`demibold]: applies a weight of the font between [`regular] and [`bold], defaulting to [`bold] if one is not available
          371  +* [`bold]: applies the usual "bold" weight of the font
          372  +* [`dense]: applies the heaviest available weight of th the font. usually the same as [`bold]
          373  +* [`light]: applies the usual "light" weight of the font. most fonts do not have a light weight, so this will be the same as [`regular].
          374  +* [`thin]: applies the slimmest available weight of the font. usually the same as [`light].
          375  +* [`underline]: underlines the text
          376  +* [`strike]: strikes the text out
          377  +* [`italic]: applies a slanted variant of the font
          378  +* [`oblique]: applies the most slanted variant of the font available. usually the same as [`italic]
          379  +* [`font=[$id]]: switches to font [$id] for the duration of the span. [$id] must be the ID of a resource defining a font.
          380  +* [`[$ext].[$prop]=[$word]]: attaches extra information for use by formatting extensions. [$ext] must be the ID of the extension.
          381  +
          382  +once a custom style is defined, you can make use of it using the [` \[.[$id] [$styled-text]\]] span notation, where [$id] is the identifier of the reference containing your style. for instance, to define and use a style named [`important] that [^pls-no specifies a dense, underlined variant of font [`impact]] and applies the CSS class [`blink] when rendered with the [`html] backend:
          383  +	pls-no: please do not do this
          384  +~~~cortav
          385  +this paragraph contains some [.important truly important] information.
          386  +	important: dense underline font=impact html.class=blink
          387  +~~~
          388  +you should always give your styles semantic names where practicable, instead of simply describing their graphical characteristics. this is good practice in general, but especially because your document will be renderable to different formats with different characteristics, and what makes text look important on a manpage in the terminal may be quite different from how it looks in a webpage or PDF.
   350    389   
   351    390   ##dir directives
   352    391   	d: [`%[*[##1]]]
   353    392   * {d author} encodes document authorship. multiple author directives can be issued to add additional coauthors
   354    393   * {d cols} specifies the number of columns the next object should be rendered with
   355    394   * {d include} transcludes another file
   356    395   * {d import} reads in the contents of another file as an embeddable section

Modified desk/cortav.xml from [b82e1b14f3] to [d0eba8f9ef].

   119    119   				<DetectChar   attribute='Span Cue' char='~' context='#pop!span-del' />
   120    120   
   121    121   				<AnyChar      attribute='Span Cue' String='`$+🔒' context='#pop!span' />
   122    122   				<StringDetect attribute='Span Cue' String='→' context='#pop!ref' />
   123    123   				<StringDetect attribute='Span Cue' String='🔗' context='#pop!ref' />
   124    124   				<DetectChar   attribute='Span Cue' char='>' context='#pop!ref' />
   125    125   				<DetectChar   attribute='Span Cue' char='^' context='#pop!ref' />
          126  +				<DetectChar   attribute='Span Cue' char='"' context='#pop!ref' />
   126    127   				<DetectChar   attribute='Span Cue' char='&amp;' context='#pop!ref' />
   127    128   				<DetectChar   attribute='Span Cue' char='#' context='#pop!var-ref' />
   128    129   				<DetectChar   attribute='Span Cue' char='\' context='#pop!flat-span' />
   129    130   				<Detect2Chars attribute='Comment' char='%' char1='%' context='#pop!inline-comment' />
   130    131   				<Detect2Chars attribute='Critical Directive Cue' char='%' char1='!' context='#pop!inline-directive' />
   131    132   				<DetectChar   attribute='Directive Cue' char='%' context='#pop!inline-directive' />
   132    133   			</context>

Modified ext/toc.lua from [35da212f52] to [e8a2cf6308].

    82     82   			}
    83     83   			ol.toc > ol > li {
    84     84   				list-style: decimal;
    85     85   			}
    86     86   				ol.toc > li > ol > li > ol > li {
    87     87   					list-style: enclosed;
    88     88   				}
           89  +					ol.toc > li > ol > li > ol > li > ol > li {
           90  +						list-style: lower-roman;
           91  +					}
    89     92   	}
    90     93   ]]
    91     94   
    92     95   ct.ext.install {
    93     96   	id = 'toc';
    94     97   	desc = 'provides a table of contents for HTML renderer plus generic fallback';
    95     98   	version = ss.version {0,1; 'devel'};

Added render/groff.lua version [a43bfa19e3].

            1  +-- [ʞ] render/groff.lua
            2  +--  ~ lexi hale <lexi@hale.su>
            3  +--  🄯 AGPLv3
            4  +--  ? renders cortav to groff source code, for creating pdfs,
            5  +--    dvis, manapages, and html files that are grievously
            6  +--    inferior compared to our own illustrious direct-html
            7  +--    renderer.
            8  +--  > cortav -m render:format groff
            9  +
           10  +local ct = require 'cortav'
           11  +local ss = require 'sirsem'
           12  +
           13  +local tcat = function(a,b)
           14  +	for i,v in ipairs(b) do
           15  +		table.insert(a, b)
           16  +	end
           17  +	return a
           18  +end
           19  +local lines = function(...)
           20  +	local s = ss.strac()
           21  +	for _, v in pairs{...} do s(v) end
           22  +	return s
           23  +end
           24  +
           25  +function ct.render.groff(doc, opts)
           26  +	-- rs contains state specific to this render job
           27  +	-- that modules will need access to
           28  +	local rs = {};
           29  +	rs.macsets = {
           30  +		strike = {
           31  +			'.de ST';
           32  +			[[.nr ww \w'\\$1']];
           33  +			[[\Z@\v'-.25m'\l'\\n[ww]u'@\\$1']];
           34  +			'..';
           35  +		};
           36  +	}
           37  +	rs.macsNeeded = {
           38  +		order = {};
           39  +		count = 0;
           40  +	}
           41  +	function rs.macAdd(id)
           42  +		if rs.macsets[id] then
           43  +			rs.macsNeeded.count = macsNeeded.count + 1
           44  +			rs.macsNeeded.order[rs.macsNeeded.count] = id
           45  +			return true
           46  +		else return false end
           47  +	end
           48  +	local job = doc:job('render_groff',nil,rs)
           49  +
           50  +	-- the way this module works is we build up a table for each block
           51  +	-- of individual strings paired with attributes that say how they
           52  +	-- should be rendered. we then iterate over the table, applying
           53  +	-- formats as need be, and inserting blanks after each block
           54  +
           55  +	local spanRenderers = {}
           56  +	function spanRenderers.format(rc, s, b, sec)
           57  +		local rcc = rc:clone()
           58  +		if s.style == 'strong' then
           59  +			rcc.prop.bold = true
           60  +		elseif s.style == 'emph' then
           61  +			rcc.prop.emph = true
           62  +		elseif s.style == 'strike' then
           63  +			rcc.prop.strike = true
           64  +			rs.macAdd 'strike'
           65  +		elseif s.style == 'insert' then
           66  +		end
           67  +		rs.renderSpans(rcc, s.spans, b, sec)
           68  +	end;
           69  +
           70  +	function rs.renderSpans(rc, sp, b, sec)
           71  +		for i, v in ipairs(sp) do
           72  +			if type(v) == 'string' then
           73  +				rc:add(v)
           74  +			elseif spanRenderers[v.kind] then
           75  +				spanRenderers[v.kind](rc, v, b, sec)
           76  +			end
           77  +		end
           78  +	end
           79  +
           80  +	local blockRenderers = {}
           81  +	function	blockRenderers.paragraph(rc, b, sec)
           82  +		rs.renderSpans(rc, b.spans, b, sec)
           83  +	end
           84  +	function rs.renderBlock(b, sec)
           85  +		local rc = {
           86  +			clone = function(self)
           87  +				return {
           88  +					clone = self.clone;
           89  +					lines = self.lines;
           90  +					prop = ss.clone(self.prop);
           91  +					mk = self.mk;
           92  +					add = self.add;
           93  +				}
           94  +			end;
           95  +			lines = {};
           96  +			prop = {};
           97  +			mk = function(self, ln)
           98  +				local p = ss.clone(self.prop)
           99  +				p.txt = ln
          100  +				return p
          101  +			end;
          102  +			add = function(self, ln)
          103  +				table.insert(self.lines, self:mk(ln))
          104  +			end;
          105  +		}
          106  +		if blockRenderers[b.kind] then
          107  +			blockRenderers[b.kind](rc, b, sec)
          108  +		end
          109  +		return rc.lines
          110  +	end
          111  +
          112  +	function rs.emitLine(ln)
          113  +		local q = ss.strac()
          114  +		if ln.dsz then
          115  +			q('\\ps +' .. tostring(ln.dsz))
          116  +		elseif ln.sz then
          117  +			q('\\ps ' .. tostring(ln.dsz))
          118  +		end
          119  +
          120  +		if ln.bold and ln.emph then
          121  +			q '\\f(BI'
          122  +		elseif ln.bold then
          123  +			q '\\fB'
          124  +		elseif ln.emph then
          125  +			q '\\fI'
          126  +		end
          127  +
          128  +
          129  +		q(ln.txt)
          130  +
          131  +		if ln.bold or ln.emph then
          132  +			q'\\f[]'
          133  +		end
          134  +
          135  +		if ln.dsz then
          136  +			q('.ps -' .. tostring(ln.dsz))
          137  +		elseif ln.sz then
          138  +			q '.ps'
          139  +		end
          140  +		return q
          141  +	end
          142  +
          143  +	local ir = {}
          144  +	for i, sec in ipairs(doc.secorder) do
          145  +		if sec.kind == 'ordinary' then
          146  +			local blks = {}
          147  +			for j, b in ipairs(sec.blocks) do
          148  +				local r = rs.renderBlock(b, sec)
          149  +				if r then table.insert(blks, r) end
          150  +			end
          151  +			table.insert(ir, blks)
          152  +		end
          153  +	end
          154  +
          155  +	local rd = ss.strac()
          156  +	for i, s in ipairs(ir) do
          157  +		for j, b in ipairs(s) do
          158  +			for z, l in ipairs(b) do
          159  +				rd(rs.emitLine(l))
          160  +			end
          161  +			rd'\n'
          162  +		end
          163  +	end
          164  +
          165  +	local macs = ss.strac()
          166  +	for _, m in pairs(rs.macsNeeded.order) do
          167  +		for _, ln in pairs(m) do macs(ln) end
          168  +	end
          169  +	return macs:compile'\n' .. rd:compile''
          170  +end

Modified render/html.lua from [35b47400ac] to [c1f1be8e43].

            1  +-- [ʞ] render/html.lua
            2  +--  ~ lexi hale <lexi@hale.su>
            3  +--  🄯 AGPLv3
            4  +--  ? renders cortav to beautiful, highly customizable
            5  +--    webpages full of css trickery to make them look
            6  +--    good both on a screen and when printed.
            7  +--  > cortav -m render:format html
            8  +
     1      9   local ct = require 'cortav'
     2     10   local ss = require 'sirsem'
     3     11   
     4     12   -- install rendering function for html
     5     13   function ct.render.html(doc, opts)
     6     14   	local doctitle = opts['title']
     7     15   	local f = string.format
................................................................................
   298    306   		abbr = [[
   299    307   			abbr[title] { cursor: help; }
   300    308   		]];
   301    309   		editors_markup = [[]];
   302    310   		block_code_listing = [[
   303    311   			figure.listing {
   304    312   				font-family: monospace;
          313  +				font-size: 85%;
   305    314   				background: @tone(0.05 20);
   306    315   				color: @tone(1 20);
   307    316   				padding: 0;
   308    317   				margin: 0.3em 0;
   309    318   				counter-reset: line-number;
   310    319   				position: relative;
   311    320   				border: 1px solid @tone(1 20);

Modified sirsem.lua from [dc1f0ae1fb] to [4b787982a7].

   885    885   				end
   886    886   			else
   887    887   				me:react(sym)
   888    888   			end
   889    889   		end;
   890    890   	};
   891    891   }
          892  +
          893  +-- convenience buffer for holding strings under
          894  +-- construction, accumulating and compiling then in
          895  +-- as quick a way as lua permits
          896  +ss.strac = ss.declare {
          897  +	ident = 'string-accumulator';
          898  +	mk = function() return {
          899  +		strs = {};
          900  +		strc = 0;
          901  +		plain = true;
          902  +	} end;
          903  +	call = function(self, s, ...)
          904  +		if s == nil then return end
          905  +		self.strc = self.strc + 1
          906  +		self.strs[self.strc] = s
          907  +		if type(s) ~= 'string' then self.plain = false end
          908  +		self(...)
          909  +	end;
          910  +	cast = {
          911  +		string = function(self)
          912  +			return self:compile()
          913  +		end;
          914  +	};
          915  +	fns = {
          916  +		compile = function(self, delim)
          917  +			if self.plain then
          918  +				return table.concat(self.strs, delim)
          919  +			end
          920  +			local tbl = {}
          921  +			local function delve(a)
          922  +				for i=1,a.strc do
          923  +					local s = a.strs[i]
          924  +					if type(s) == 'string' then
          925  +						table.insert(tbl, s)
          926  +					elseif ss.strac.is(s) then
          927  +						delve(s)
          928  +					elseif s ~= nil then
          929  +						table.insert(tbl, tostring(s))
          930  +					end
          931  +				end
          932  +			end
          933  +			delve(self)
          934  +			return table.concat(tbl, delim)
          935  +		end;
          936  +		wrap = function(self,a,b)
          937  +			table.insert(self.strs, 1, a)
          938  +			table.insert(self.strs, b)
          939  +		end;
          940  +	};
          941  +}