cortav  Check-in [435c29db6b]

Overview
Comment:beginnings of some support for captions/subtitles, excise dumb ideas from readme and fix typo, black pharaoh but this codebase needs a rewrite
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 435c29db6bd7070b43184954c64101d7d6b60d8c8c69292829ed7cf3475a29df
User & Date: lexi on 2024-07-17 22:21:07
Other Links: manifest | tags
Context
2024-07-17
22:29
cleanup check-in: d85fbc448f user: lexi tags: trunk
22:21
beginnings of some support for captions/subtitles, excise dumb ideas from readme and fix typo, black pharaoh but this codebase needs a rewrite check-in: 435c29db6b user: lexi tags: trunk
20:10
add missing subtitle support check-in: 362f9a6647 user: lexi tags: trunk
Changes

Modified cortav.ct from [4b83863e2a] to [7452348c5b].

  1070   1070   * [*html]: emit HTML and CSS code to typeset the document. [!in progress]
  1071   1071   * [*svg]: emit SVG, taking advantage of its precise layout features to produce a nicely formatted and paginated document. pagination can perhaps be accomplished through emitting multiple files (somewhat problematic) or by assigning one layer to each page. [!long term]
  1072   1072   * [*groff]: the most important output backend, rivalling [*html]. will allow the document to be typeset in a wide variety of formats, including PDF and manpage. [!in progress]
  1073   1073   * [*gemtext]: essentially a downrezzing of cortav to make it readable to Gemini clients
  1074   1074   * [*ast]: produces a human- and/or machine-readable dump of the document's syntax tree, to aid in debugging or for interoperation with systems that do not support `cortav` direcly. mode [`ast:repr] wil allow selecting formats for the dump. [`ast:rel] can be [`tree] (the default) to emit a hierarchical representation, or [`flat] to emit an array of nodes that convey hierarchy [^flatdoc by naming one another], rather than being placed inside one another. [`tree] is easier for humans to parse; [`flat] is easier for computers. origin information can be included for each node with the flag [`ast:debug-syms], but be aware this will greatly increase file size.
  1075   1075   ** [`tabtree] [!(default)]: a hierarchical tree view, with the number of tabs preceding an item showing its depth in the tree
  1076   1076   ** [`sexp]
  1077         -** [`binary]: emit a raw binary format that is easier for programs to read. maybe an lmdb or cdb file?
         1077  +** [`binary]: emit a raw binary format that is easier for programs to read. maybe a msgpack file?
  1078   1078   ** [`json]: obligatory, alas
  1079   1079   
  1080   1080   	flatdoc: ~~~flat sexp example output [scheme]~~~
  1081   1081   		(nodes
  1082   1082   			(section (id . "section1")
  1083   1083   				(anchor "introduction")
  1084   1084   				(kind . "ordinary")

Modified cortav.lua from [76257c7c34] to [f6f6b80f21].

   970    970   	if l:sub(1,1) == '.' then l = l:sub(2) end
   971    971   	return {
   972    972   		kind = "paragraph";
   973    973   		spans = ct.parse_span(l, c);
   974    974   	}
   975    975   end)
   976    976   
   977         -local insert_subtitle = blockwrap(function(l,c)
   978         -	return {
   979         -		kind = "subtitle";
   980         -		spans = ct.parse_span(l:sub(3), c);
          977  +local insert_caption = blockwrap(function(l,c,j,d)
          978  +	if next(d) == nil then
          979  +		c:fail 'subtitle in an unlabeled section is meaningless'
          980  +	end
          981  +
          982  +	local last = d[#d]
          983  +	local me = {
          984  +		kind = 'subtitle';
          985  +		spans = ct.parse_span(l:sub(3):gsub("^%s+",""), c);
          986  +	}
          987  +
          988  +	local captionable = {
          989  +		quote=true, aside=true,
          990  +		table=true, code=true,
          991  +		embed=true, link=true,
   981    992   	}
          993  +
          994  +	if last.kind == 'label' then
          995  +		me.attach = last;
          996  +	elseif last.kind == 'subtitle' then
          997  +		me.attach = last.attach;
          998  +	elseif captionable[last.kind] then
          999  +		me.kind = 'label'
         1000  +		me.captions = last
         1001  +		last.label_node = me
         1002  +	else
         1003  +		c:fail 'subtitle/attribution syntax in improper context'
         1004  +	end
         1005  +
         1006  +	return me
   982   1007   end)
   983   1008   
   984   1009   local function
   985   1010   insert_section(skind) return function(l,c,j)
   986   1011   	local depth, id, t = l:match '^([#§^]+)([^%s]*)%s*(.-)$'
   987   1012   	if id and id ~= "" then
   988   1013   		if c.doc.sections[id] then
................................................................................
  1181   1206   ct.ctlseqs = {
  1182   1207   	{seq = '.', fn = insert_paragraph};
  1183   1208   	{seq = '¶', fn = insert_paragraph};
  1184   1209   	{seq = '❡', fn = insert_paragraph};
  1185   1210   	{seq = '#', fn = insert_section()};
  1186   1211   	{seq = '§', fn = insert_section()};
  1187   1212   	{seq = '^', fn = insert_section 'namespace'};
  1188         -	{seq = '--',fn = insert_subtitle};
         1213  +	{seq = '--',fn = insert_caption};
  1189   1214   	{seq = '+', fn = insert_table_row};
  1190   1215   	{seq = '|', fn = insert_table_row};
  1191   1216   	{seq = '│', fn = insert_table_row};
  1192   1217   	{seq = '!', fn = function(l,c,j,d)
  1193   1218   		local last = d[#d]
  1194   1219   		local txt = l:match '^%s*!%s*(.-)$'
  1195   1220   		if (not last) or last.kind ~= 'aside' then

Modified render/groff.lua from [599907d26e] to [b6c7f1e8a6].

    79     79   		sreq = function(me, r)
    80     80   			me:flush()
    81     81   			table.insert(me.lines, "'"..r)
    82     82   		end;
    83     83   		esc = function(me, e)
    84     84   			me:raw('\\' .. e)
    85     85   		end;
    86         -      draw = function(me, args)
    87         -         for _,v in ipairs(args) do
           86  +		draw = function(me, args)
           87  +			for _,v in ipairs(args) do
    88     88   				me:esc("D'" .. v .. "'")
    89         -         end
    90         -      end;
           89  +			end
           90  +		end;
    91     91   		flush = function(me)
    92     92   			if me.linbuf ~= nil then
    93     93   				local line = me.linbuf:compile()
    94     94   				local first = line:sub(1,1)
    95     95   				-- make sure our lines aren't accidentally interpreted
    96     96   				-- as groff requests. groff is kinda hostile to script
    97     97   				-- generation, huh?
................................................................................
   418    418   	end
   419    419   
   420    420   	local blockRenderers = {}
   421    421   	blockRenderers['horiz-rule'] = function(rc, b, sec)
   422    422   		rc.prop.margin = { top = 0.3 }
   423    423   		rc.prop.underline = 0.1
   424    424   	end
   425         -	function	blockRenderers.label(rc, b, sec)
          425  +	function blockRenderers.label(rc, b, sec)
   426    426   		if ct.sec.is(b.captions) then
   427    427   			local visDepth = b.captions.depth + (b.origin.docDepth or 0)
   428    428   			local sizes = {36,24,12,8,4,2}
   429    429   			local margins = {0,3}
   430    430   			local dedents = {2.5,1.3,0.8,0.4}
   431    431   			local uls = {3,1.5,0.5,0.25}
   432    432   			rc.prop.dsz = sizes[visDepth] or 10
................................................................................
   443    443   				rc.prop.breakBefore = true
   444    444   			end
   445    445   			rs.renderSpans(rc, b.spans, b, sec)
   446    446   		else
   447    447   			ss.bug 'tried to render label for an unknown object type':throw()
   448    448   		end
   449    449   	end
   450         -	function	blockRenderers.paragraph(rc, b, sec)
          450  +	function blockRenderers.paragraph(rc, b, sec)
          451  +		rs.renderSpans(rc, b.spans, b, sec)
          452  +	end
          453  +	function blockRenderers.subtitle(rc, b, sec)
          454  +		rc.prop.dsz = 16 -- TODO base on "parent" label
          455  +		rc.prop.emph = true
   451    456   		rs.renderSpans(rc, b.spans, b, sec)
   452    457   	end
   453         -	function	blockRenderers.macro(rc, b, sec)
          458  +	function blockRenderers.macro(rc, b, sec)
   454    459   		local rc = rc.parent:clone()
   455    460   		rs.renderDoc(rc, b.doc)
   456    461   	end
   457         -	function	blockRenderers.quote(rc, b, sec)
          462  +	function blockRenderers.quote(rc, b, sec)
   458    463   		local rc = rc.parent:clone()
   459    464   		rc.prop.indent = (rc.prop.indent or 0) + 1
   460    465   		local added = rs.renderDoc(rc, b.doc)
   461    466   		 -- select last block of last section and increase bottom margin
   462    467   		local ap = added[#added].blocks
   463    468   		ap = ap[#ap].prop
   464    469   		if ap.margin then
................................................................................
   467    472   			else
   468    473   				ap.margin.bottom = 1.1
   469    474   			end
   470    475   		else
   471    476   			ap.margin = {bottom = 1.1}
   472    477   		end
   473    478   	end
   474         -	function	blockRenderers.table(rc, b, sec)
          479  +	function blockRenderers.table(rc, b, sec)
   475    480   		function rc:begin(g)
   476    481   			g:req 'TS'
   477    482   			local aligns = {}
   478    483   			for i, c in ipairs(b.rows[1]) do
   479    484   				aligns[i] = ({
   480    485   					left = 'l';
   481    486   					center = 'c';

Modified render/html.lua from [e689cbb41c] to [e178fd542c].

   259    259   		subtitle = [[
   260    260   			.subtitle {
   261    261   				color: @tone(0.3 20); 
   262    262   				font-size: 1.2em;
   263    263   				font-style: italic;
   264    264   				margin-left: 1em;
   265    265   			}
   266         -			blockquote + .subtitle {
   267         -				&::before {
   268         -					content: "— ";
   269         -				}
   270         -			}
   271    266   		]];
   272    267   		accent = [[
   273    268   			@media screen {
   274    269   				body { background: @bg; color: @fg }
   275    270   				a[href] {
   276    271   					color: @tone(0.7 30);
   277    272   					text-decoration-color: @tone/0.4(0.7 30);
................................................................................
   313    308   			}
   314    309   			section > aside p:first-child {
   315    310   				margin: 0;
   316    311   			}
   317    312            section aside + aside {
   318    313   				margin-top: 0.5em;
   319    314   			}
   320         -      ]];
          315  +		]];
          316  +		quoteCaption = [[
          317  +			blockquote > div.caption {
          318  +				text-align: right;
          319  +				font-style: italic;
          320  +				&::before {
          321  +					content: "— ";
          322  +				}
          323  +			}
          324  +		]];
   321    325   		code = [[
   322    326   			code {
   323    327   				display: inline-block;
   324    328   				background: @tone(-1);
   325    329   				color: @tone(0.7);
   326    330   				font-family: monospace;
   327    331   				font-size: 90%;
................................................................................
  1021   1025   					return htmlURI(s.uri)
  1022   1026   				elseif s.mode == 'embed' then
  1023   1027   					local mime = s.mime:clone()
  1024   1028   					mime.opts = {}
  1025   1029   					return string.format('data:%s;base64,%s', mime, ss.str.b64e(s.raw))
  1026   1030   				end
  1027   1031   			end
  1028         -			--figure out how to embed the given object
  1029   1032   			local function P(p) -- get prop
  1030   1033   				if b.props and b.props[p] then
  1031   1034   					return b.props[p]
  1032   1035   				end
  1033   1036   				return obj.props[p]
  1034   1037   			end
         1038  +
         1039  +			local cap = b.cap or P'desc' or P'detail'
         1040  +			local capIR = '';
         1041  +			if b.label_node then
         1042  +				local ln = b.label_node
         1043  +				capIR = sr.htmlSpan(ln.spans, ln, s)
         1044  +			elseif cap then
         1045  +				 -- the block here should really be the relevant
         1046  +				 -- ref definition if an override caption isn't
         1047  +				 -- specified, but oh well
         1048  +				capIR = sr.htmlSpan(spanparse(
         1049  +					cap, b.origin
         1050  +				), b, s)
         1051  +			end
         1052  +
         1053  +			--figure out how to embed the given object
  1035   1054   			local embedActs = {
  1036   1055   				{ss.mime'image/*',       function(s,ctr)
  1037   1056   					if s == nil then
  1038   1057   						return {tag = "picture", nodes = {}}
  1039   1058   					else
  1040   1059   						local uri = uriForSource(s)
  1041   1060   						local fbimg, idx
................................................................................
  1139   1158   						goto compatFound
  1140   1159   					end
  1141   1160   				end
  1142   1161   			end
  1143   1162   			-- nothing found; install fallback link
  1144   1163   				if fallback then
  1145   1164   					local lnk = htmlURI(fallback.uri)
  1146         -					return tag('a', {href=lnk},
  1147         -								  tag('div',{class=xref},
  1148         -										string.format("→ %s [%s]", b.cap or '', tostring(fallback.mime))))
         1165  +					return tag('a', {href=lnk}, catenate {
         1166  +							  tag('div',{class=xref}, catenate {
         1167  +								  '→ '; capIR;
         1168  +								  string.format(" [%s]", tostring(fallback.mime));
         1169  +							  })})
  1149   1170   				else
  1150   1171   					addStyle 'docmeta'
  1151   1172   					return tag('div',{class="render-warn"},
  1152   1173   								  'could not embed object type ' .. tostring(obj.srcs.mime))
  1153   1174   				end
  1154   1175   
  1155   1176   			::compatFound::
................................................................................
  1156   1177   			local top = rtype[2]() -- create container
  1157   1178   			for n, src in ipairs(obj.srcs) do
  1158   1179   				if rtype[1] < src.mime then
  1159   1180   					rtype[2](src, top)
  1160   1181   				end
  1161   1182   			end
  1162   1183   			local ft = flatten(top)
  1163         -			local cap = b.cap or P'desc' or P'detail'
  1164   1184   			if b.mode == 'inline' then
  1165   1185   				-- TODO insert caption
  1166   1186   				return ft
  1167   1187   			else
  1168   1188   				local prop = {}
  1169   1189   				if b.mode == 'open' then
  1170   1190   					prop.open = true
  1171   1191   				end
  1172   1192   				return tag('details', prop, catenate {
  1173         -					tag('summary', {},
  1174         -						 cap and (
  1175         -							 -- the block here should really be the relevant
  1176         -							 -- ref definition if an override caption isn't
  1177         -							 -- specified, but oh well
  1178         -							 sr.htmlSpan(spanparse(
  1179         -								 cap, b.origin
  1180         -							 ), b, s)
  1181         -						) or '');
         1193  +					tag('summary', {}, capIR);
  1182   1194   					ft;
  1183   1195   				})
  1184   1196   			end
  1185   1197   		end
  1186   1198   
  1187   1199   		function block_renderers.macro(b,s)
  1188   1200   			local all = renderSubdoc(b.doc)
  1189   1201   			local cat = catenate(ss.map(flatten,all))
  1190   1202   			return tag(nil, {}, cat)
  1191   1203   		end
  1192   1204   
  1193   1205   		function block_renderers.quote(b,s)
  1194   1206   			local ir = renderSubdoc(b.doc)
         1207  +			if b.label_node then
         1208  +				addStyle 'quoteCaption'
         1209  +				table.insert(ir, tag('div', {class='caption'},
         1210  +					sr.htmlSpan(b.label_node.spans, b.label_node, s)))
         1211  +			end
  1195   1212   			return tag('blockquote', b.id and {id=getSafeID(b)} or {}, catenate(ss.map(flatten,ir)))
  1196   1213   		end
  1197   1214   
  1198   1215   		return block_renderers
  1199   1216   	end
  1200   1217   
  1201   1218   	local function getRenderers(procs)